hid-thingm.c revision e4aecaf2f53bc6635b484ee2f1b8a1e4c73e7997
1/* 2 * ThingM blink(1) USB RGB LED driver 3 * 4 * Copyright 2013-2014 Savoir-faire Linux Inc. 5 * Vivien Didelot <vivien.didelot@savoirfairelinux.com> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation, version 2. 10 */ 11 12#include <linux/hid.h> 13#include <linux/hidraw.h> 14#include <linux/leds.h> 15#include <linux/module.h> 16#include <linux/mutex.h> 17#include <linux/workqueue.h> 18 19#include "hid-ids.h" 20 21#define REPORT_ID 1 22#define REPORT_SIZE 9 23 24/* Firmware major number of supported devices */ 25#define THINGM_MAJOR_MK1 '1' 26#define THINGM_MAJOR_MK2 '2' 27 28struct thingm_fwinfo { 29 char major; 30 unsigned numrgb; 31 unsigned first; 32}; 33 34static const struct thingm_fwinfo thingm_fwinfo[] = { 35 { 36 .major = THINGM_MAJOR_MK1, 37 .numrgb = 1, 38 .first = 0, 39 }, { 40 .major = THINGM_MAJOR_MK2, 41 .numrgb = 2, 42 .first = 1, 43 } 44}; 45 46/* A red, green or blue channel, part of an RGB chip */ 47struct thingm_led { 48 struct thingm_rgb *rgb; 49 struct led_classdev ldev; 50 char name[32]; 51}; 52 53/* Basically a WS2812 5050 RGB LED chip */ 54struct thingm_rgb { 55 struct thingm_device *tdev; 56 struct thingm_led red; 57 struct thingm_led green; 58 struct thingm_led blue; 59 struct work_struct work; 60 u8 num; 61}; 62 63struct thingm_device { 64 struct hid_device *hdev; 65 struct { 66 char major; 67 char minor; 68 } version; 69 const struct thingm_fwinfo *fwinfo; 70 struct mutex lock; 71 struct thingm_rgb *rgb; 72}; 73 74static int thingm_send(struct thingm_device *tdev, u8 buf[REPORT_SIZE]) 75{ 76 int ret; 77 78 hid_dbg(tdev->hdev, "-> %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n", 79 buf[0], buf[1], buf[2], buf[3], buf[4], 80 buf[5], buf[6], buf[7], buf[8]); 81 82 ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE, 83 HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 84 85 return ret < 0 ? ret : 0; 86} 87 88static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE]) 89{ 90 int ret; 91 92 ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE, 93 HID_FEATURE_REPORT, HID_REQ_GET_REPORT); 94 if (ret < 0) 95 return ret; 96 97 hid_dbg(tdev->hdev, "<- %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n", 98 buf[0], buf[1], buf[2], buf[3], buf[4], 99 buf[5], buf[6], buf[7], buf[8]); 100 101 return 0; 102} 103 104static int thingm_version(struct thingm_device *tdev) 105{ 106 u8 buf[REPORT_SIZE] = { REPORT_ID, 'v', 0, 0, 0, 0, 0, 0, 0 }; 107 int err; 108 109 err = thingm_send(tdev, buf); 110 if (err) 111 return err; 112 113 err = thingm_recv(tdev, buf); 114 if (err) 115 return err; 116 117 tdev->version.major = buf[3]; 118 tdev->version.minor = buf[4]; 119 120 return 0; 121} 122 123static int thingm_write_color(struct thingm_rgb *rgb) 124{ 125 u8 buf[REPORT_SIZE] = { REPORT_ID, 'c', 0, 0, 0, 0, 0, rgb->num, 0 }; 126 127 buf[2] = rgb->red.ldev.brightness; 128 buf[3] = rgb->green.ldev.brightness; 129 buf[4] = rgb->blue.ldev.brightness; 130 131 return thingm_send(rgb->tdev, buf); 132} 133 134static void thingm_work(struct work_struct *work) 135{ 136 struct thingm_rgb *rgb = container_of(work, struct thingm_rgb, work); 137 138 mutex_lock(&rgb->tdev->lock); 139 140 if (thingm_write_color(rgb)) 141 hid_err(rgb->tdev->hdev, "failed to write color\n"); 142 143 mutex_unlock(&rgb->tdev->lock); 144} 145 146static void thingm_led_set(struct led_classdev *ldev, 147 enum led_brightness brightness) 148{ 149 struct thingm_led *led = container_of(ldev, struct thingm_led, ldev); 150 151 /* the ledclass has already stored the brightness value */ 152 schedule_work(&led->rgb->work); 153} 154 155static int thingm_init_rgb(struct thingm_rgb *rgb) 156{ 157 const int minor = ((struct hidraw *) rgb->tdev->hdev->hidraw)->minor; 158 int err; 159 160 /* Register the red diode */ 161 snprintf(rgb->red.name, sizeof(rgb->red.name), 162 "thingm%d:red:led%d", minor, rgb->num); 163 rgb->red.ldev.name = rgb->red.name; 164 rgb->red.ldev.max_brightness = 255; 165 rgb->red.ldev.brightness_set = thingm_led_set; 166 rgb->red.rgb = rgb; 167 168 err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->red.ldev); 169 if (err) 170 return err; 171 172 /* Register the green diode */ 173 snprintf(rgb->green.name, sizeof(rgb->green.name), 174 "thingm%d:green:led%d", minor, rgb->num); 175 rgb->green.ldev.name = rgb->green.name; 176 rgb->green.ldev.max_brightness = 255; 177 rgb->green.ldev.brightness_set = thingm_led_set; 178 rgb->green.rgb = rgb; 179 180 err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->green.ldev); 181 if (err) 182 goto unregister_red; 183 184 /* Register the blue diode */ 185 snprintf(rgb->blue.name, sizeof(rgb->blue.name), 186 "thingm%d:blue:led%d", minor, rgb->num); 187 rgb->blue.ldev.name = rgb->blue.name; 188 rgb->blue.ldev.max_brightness = 255; 189 rgb->blue.ldev.brightness_set = thingm_led_set; 190 rgb->blue.rgb = rgb; 191 192 err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->blue.ldev); 193 if (err) 194 goto unregister_green; 195 196 INIT_WORK(&rgb->work, thingm_work); 197 198 return 0; 199 200unregister_green: 201 led_classdev_unregister(&rgb->green.ldev); 202 203unregister_red: 204 led_classdev_unregister(&rgb->red.ldev); 205 206 return err; 207} 208 209static void thingm_remove_rgb(struct thingm_rgb *rgb) 210{ 211 flush_work(&rgb->work); 212 led_classdev_unregister(&rgb->red.ldev); 213 led_classdev_unregister(&rgb->green.ldev); 214 led_classdev_unregister(&rgb->blue.ldev); 215} 216 217static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id) 218{ 219 struct thingm_device *tdev; 220 int i, err; 221 222 tdev = devm_kzalloc(&hdev->dev, sizeof(struct thingm_device), 223 GFP_KERNEL); 224 if (!tdev) 225 return -ENOMEM; 226 227 tdev->hdev = hdev; 228 hid_set_drvdata(hdev, tdev); 229 230 err = hid_parse(hdev); 231 if (err) 232 goto error; 233 234 err = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 235 if (err) 236 goto error; 237 238 mutex_init(&tdev->lock); 239 240 err = thingm_version(tdev); 241 if (err) 242 goto stop; 243 244 hid_dbg(hdev, "firmware version: %c.%c\n", 245 tdev->version.major, tdev->version.minor); 246 247 for (i = 0; i < ARRAY_SIZE(thingm_fwinfo) && !tdev->fwinfo; ++i) 248 if (thingm_fwinfo[i].major == tdev->version.major) 249 tdev->fwinfo = &thingm_fwinfo[i]; 250 251 if (!tdev->fwinfo) { 252 hid_err(hdev, "unsupported firmware %c\n", tdev->version.major); 253 goto stop; 254 } 255 256 tdev->rgb = devm_kzalloc(&hdev->dev, 257 sizeof(struct thingm_rgb) * tdev->fwinfo->numrgb, 258 GFP_KERNEL); 259 if (!tdev->rgb) { 260 err = -ENOMEM; 261 goto stop; 262 } 263 264 for (i = 0; i < tdev->fwinfo->numrgb; ++i) { 265 struct thingm_rgb *rgb = tdev->rgb + i; 266 267 rgb->tdev = tdev; 268 rgb->num = tdev->fwinfo->first + i; 269 err = thingm_init_rgb(rgb); 270 if (err) { 271 while (--i >= 0) 272 thingm_remove_rgb(tdev->rgb + i); 273 goto stop; 274 } 275 } 276 277 return 0; 278stop: 279 hid_hw_stop(hdev); 280error: 281 return err; 282} 283 284static void thingm_remove(struct hid_device *hdev) 285{ 286 struct thingm_device *tdev = hid_get_drvdata(hdev); 287 int i; 288 289 for (i = 0; i < tdev->fwinfo->numrgb; ++i) 290 thingm_remove_rgb(tdev->rgb + i); 291 292 hid_hw_stop(hdev); 293} 294 295static const struct hid_device_id thingm_table[] = { 296 { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) }, 297 { } 298}; 299MODULE_DEVICE_TABLE(hid, thingm_table); 300 301static struct hid_driver thingm_driver = { 302 .name = "thingm", 303 .probe = thingm_probe, 304 .remove = thingm_remove, 305 .id_table = thingm_table, 306}; 307 308module_hid_driver(thingm_driver); 309 310MODULE_LICENSE("GPL"); 311MODULE_AUTHOR("Vivien Didelot <vivien.didelot@savoirfairelinux.com>"); 312MODULE_DESCRIPTION("ThingM blink(1) USB RGB LED driver"); 313