Blame view

drivers/hid/hid-thingm.c 6.96 KB
30ba2fbde   Vivien Didelot   HID: add ThingM b...
1
2
3
  /*
   * ThingM blink(1) USB RGB LED driver
   *
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
4
   * Copyright 2013-2014 Savoir-faire Linux Inc.
30ba2fbde   Vivien Didelot   HID: add ThingM b...
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   Vivien Didelot   HID: thingm: refa...
13
  #include <linux/hidraw.h>
30ba2fbde   Vivien Didelot   HID: add ThingM b...
14
15
  #include <linux/leds.h>
  #include <linux/module.h>
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
16
17
  #include <linux/mutex.h>
  #include <linux/workqueue.h>
30ba2fbde   Vivien Didelot   HID: add ThingM b...
18
19
  
  #include "hid-ids.h"
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
20
21
  #define REPORT_ID	1
  #define REPORT_SIZE	9
30ba2fbde   Vivien Didelot   HID: add ThingM b...
22

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
23
24
  /* Firmware major number of supported devices */
  #define THINGM_MAJOR_MK1	'1'
3121b1c44   Vivien Didelot   HID: thingm: add ...
25
  #define THINGM_MAJOR_MK2	'2'
30ba2fbde   Vivien Didelot   HID: add ThingM b...
26

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
27
28
29
30
31
  struct thingm_fwinfo {
  	char major;
  	unsigned numrgb;
  	unsigned first;
  };
e4aecaf2f   Jiri Kosina   HID: thingm: thin...
32
  static const struct thingm_fwinfo thingm_fwinfo[] = {
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
33
34
35
36
  	{
  		.major = THINGM_MAJOR_MK1,
  		.numrgb = 1,
  		.first = 0,
3121b1c44   Vivien Didelot   HID: thingm: add ...
37
38
39
40
  	}, {
  		.major = THINGM_MAJOR_MK2,
  		.numrgb = 2,
  		.first = 1,
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Vivien Didelot   HID: add ThingM b...
62
  	struct hid_device *hdev;
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Vivien Didelot   HID: add ThingM b...
70
  };
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
71
  static int thingm_send(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
30ba2fbde   Vivien Didelot   HID: add ThingM b...
72
73
  {
  	int ret;
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
74
75
  	hid_dbg(tdev->hdev, "-> %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx
  ",
30ba2fbde   Vivien Didelot   HID: add ThingM b...
76
77
  			buf[0], buf[1], buf[2], buf[3], buf[4],
  			buf[5], buf[6], buf[7], buf[8]);
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
78
79
  	ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
  			HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
80
81
82
  
  	return ret < 0 ? ret : 0;
  }
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
83
  static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
30ba2fbde   Vivien Didelot   HID: add ThingM b...
84
  {
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
85
  	int ret;
30ba2fbde   Vivien Didelot   HID: add ThingM b...
86

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Vivien Didelot   HID: add ThingM b...
91

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Vivien Didelot   HID: add ThingM b...
96

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
97
  	return 0;
30ba2fbde   Vivien Didelot   HID: add ThingM b...
98
  }
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
99
  static int thingm_version(struct thingm_device *tdev)
30ba2fbde   Vivien Didelot   HID: add ThingM b...
100
  {
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Vivien Didelot   HID: add ThingM b...
111

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
112
113
114
115
  	tdev->version.major = buf[3];
  	tdev->version.minor = buf[4];
  
  	return 0;
30ba2fbde   Vivien Didelot   HID: add ThingM b...
116
  }
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
117
  static int thingm_write_color(struct thingm_rgb *rgb)
30ba2fbde   Vivien Didelot   HID: add ThingM b...
118
  {
3121b1c44   Vivien Didelot   HID: thingm: add ...
119
  	u8 buf[REPORT_SIZE] = { REPORT_ID, 'c', 0, 0, 0, 0, 0, rgb->num, 0 };
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
120
121
122
123
  
  	buf[2] = rgb->red.ldev.brightness;
  	buf[3] = rgb->green.ldev.brightness;
  	buf[4] = rgb->blue.ldev.brightness;
30ba2fbde   Vivien Didelot   HID: add ThingM b...
124

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
125
  	return thingm_send(rgb->tdev, buf);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
126
  }
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
127
  static void thingm_work(struct work_struct *work)
30ba2fbde   Vivien Didelot   HID: add ThingM b...
128
  {
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
129
  	struct thingm_rgb *rgb = container_of(work, struct thingm_rgb, work);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
130

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Vivien Didelot   HID: add ThingM b...
138
  }
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
139
140
  static void thingm_led_set(struct led_classdev *ldev,
  		enum led_brightness brightness)
30ba2fbde   Vivien Didelot   HID: add ThingM b...
141
  {
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
142
  	struct thingm_led *led = container_of(ldev, struct thingm_led, ldev);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
143

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
144
145
146
  	/* the ledclass has already stored the brightness value */
  	schedule_work(&led->rgb->work);
  }
30ba2fbde   Vivien Didelot   HID: add ThingM b...
147

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Vivien Didelot   HID: add ThingM b...
190

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
191
  	return 0;
30ba2fbde   Vivien Didelot   HID: add ThingM b...
192

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
193
194
  unregister_green:
  	led_classdev_unregister(&rgb->green.ldev);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
195

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
196
197
  unregister_red:
  	led_classdev_unregister(&rgb->red.ldev);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
198

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
199
200
201
202
203
  	return err;
  }
  
  static void thingm_remove_rgb(struct thingm_rgb *rgb)
  {
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
204
205
206
  	led_classdev_unregister(&rgb->red.ldev);
  	led_classdev_unregister(&rgb->green.ldev);
  	led_classdev_unregister(&rgb->blue.ldev);
67a978458   Jiri Kosina   HID: thingm: fix ...
207
  	flush_work(&rgb->work);
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
208
  }
30ba2fbde   Vivien Didelot   HID: add ThingM b...
209
210
211
  
  static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id)
  {
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
212
213
  	struct thingm_device *tdev;
  	int i, err;
30ba2fbde   Vivien Didelot   HID: add ThingM b...
214

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
215
216
217
  	tdev = devm_kzalloc(&hdev->dev, sizeof(struct thingm_device),
  			GFP_KERNEL);
  	if (!tdev)
30ba2fbde   Vivien Didelot   HID: add ThingM b...
218
  		return -ENOMEM;
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
219
220
  	tdev->hdev = hdev;
  	hid_set_drvdata(hdev, tdev);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
221

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
222
223
  	err = hid_parse(hdev);
  	if (err)
30ba2fbde   Vivien Didelot   HID: add ThingM b...
224
  		goto error;
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
225
226
  	err = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
  	if (err)
30ba2fbde   Vivien Didelot   HID: add ThingM b...
227
  		goto error;
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
228
229
230
231
  	mutex_init(&tdev->lock);
  
  	err = thingm_version(tdev);
  	if (err)
30ba2fbde   Vivien Didelot   HID: add ThingM b...
232
  		goto stop;
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
233
234
235
  	hid_dbg(hdev, "firmware version: %c.%c
  ",
  			tdev->version.major, tdev->version.minor);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
236

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Benjamin Tissoires   HID: thingm: set ...
244
  		err = -ENODEV;
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
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   Vivien Didelot   HID: add ThingM b...
268

f70ed8a6f   Vivien Didelot   HID: thingm: refa...
269
  	return 0;
30ba2fbde   Vivien Didelot   HID: add ThingM b...
270
271
272
  stop:
  	hid_hw_stop(hdev);
  error:
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
273
  	return err;
30ba2fbde   Vivien Didelot   HID: add ThingM b...
274
275
276
277
  }
  
  static void thingm_remove(struct hid_device *hdev)
  {
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
278
279
  	struct thingm_device *tdev = hid_get_drvdata(hdev);
  	int i;
67a978458   Jiri Kosina   HID: thingm: fix ...
280
  	hid_hw_stop(hdev);
f70ed8a6f   Vivien Didelot   HID: thingm: refa...
281
282
  	for (i = 0; i < tdev->fwinfo->numrgb; ++i)
  		thingm_remove_rgb(tdev->rgb + i);
30ba2fbde   Vivien Didelot   HID: add ThingM b...
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");