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