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