aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
authorVivien Didelot <vivien.didelot@savoirfairelinux.com>2014-04-14 16:50:19 -0400
committerJiri Kosina <jkosina@suse.cz>2014-04-15 08:42:51 -0400
commitf70ed8a6f7cd9c55cc16287c584cb26efb53cbd7 (patch)
tree1a63df88938c20c499f97c5e7bb3bab7fe32fa42 /drivers/hid
parentaee114fd3c94f1be0f95af84d6ed25cd47702c41 (diff)
HID: thingm: refactor blink(1) support
This patch refactors the way the thingm driver registers a blink(1) LED. In order to make the driver simpler and more standard, drop the "rgb" sysfs attribute and create one instance of LED class per RGB channel. Actually, the name of the LED class instance registered for a blink(1) device is "blink1::ABCD", where ABCD is the last 4 chars of the serial number. The driver now registers 3 instances per RGB chip, named "thingmX:{red,green,blue}:ledY" where X is the hidraw minor number and Y is the RGB chip number (as seen by the firmware). This patch also uses work queues to defer calls with the device, which now allows triggers to work as expected with this LED device. Also remove the brightness structure field and the brightness_get backend, as it is already handled by the LED class, and changes the prefix of functions and structures to thingm_ to match the driver name. Signed-off-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/hid-thingm.c304
1 files changed, 207 insertions, 97 deletions
diff --git a/drivers/hid/hid-thingm.c b/drivers/hid/hid-thingm.c
index e3b6647e00ce..0af0eb446636 100644
--- a/drivers/hid/hid-thingm.c
+++ b/drivers/hid/hid-thingm.c
@@ -1,7 +1,7 @@
1/* 1/*
2 * ThingM blink(1) USB RGB LED driver 2 * ThingM blink(1) USB RGB LED driver
3 * 3 *
4 * Copyright 2013 Savoir-faire Linux Inc. 4 * Copyright 2013-2014 Savoir-faire Linux Inc.
5 * Vivien Didelot <vivien.didelot@savoirfairelinux.com> 5 * Vivien Didelot <vivien.didelot@savoirfairelinux.com>
6 * 6 *
7 * This program is free software; you can redistribute it and/or 7 * This program is free software; you can redistribute it and/or
@@ -10,170 +10,280 @@
10 */ 10 */
11 11
12#include <linux/hid.h> 12#include <linux/hid.h>
13#include <linux/hidraw.h>
13#include <linux/leds.h> 14#include <linux/leds.h>
14#include <linux/module.h> 15#include <linux/module.h>
16#include <linux/mutex.h>
17#include <linux/workqueue.h>
15 18
16#include "hid-ids.h" 19#include "hid-ids.h"
17 20
18#define BLINK1_CMD_SIZE 9 21#define REPORT_ID 1
22#define REPORT_SIZE 9
19 23
20#define blink1_rgb_to_r(rgb) ((rgb & 0xFF0000) >> 16) 24/* Firmware major number of supported devices */
21#define blink1_rgb_to_g(rgb) ((rgb & 0x00FF00) >> 8) 25#define THINGM_MAJOR_MK1 '1'
22#define blink1_rgb_to_b(rgb) ((rgb & 0x0000FF) >> 0)
23 26
24/** 27struct thingm_fwinfo {
25 * struct blink1_data - blink(1) device specific data 28 char major;
26 * @hdev: HID device. 29 unsigned numrgb;
27 * @led_cdev: LED class instance. 30 unsigned first;
28 * @rgb: 8-bit per channel RGB notation. 31};
29 * @brightness: brightness coefficient. 32
30 */ 33const struct thingm_fwinfo thingm_fwinfo[] = {
31struct blink1_data { 34 {
35 .major = THINGM_MAJOR_MK1,
36 .numrgb = 1,
37 .first = 0,
38 }
39};
40
41/* A red, green or blue channel, part of an RGB chip */
42struct thingm_led {
43 struct thingm_rgb *rgb;
44 struct led_classdev ldev;
45 char name[32];
46};
47
48/* Basically a WS2812 5050 RGB LED chip */
49struct thingm_rgb {
50 struct thingm_device *tdev;
51 struct thingm_led red;
52 struct thingm_led green;
53 struct thingm_led blue;
54 struct work_struct work;
55 u8 num;
56};
57
58struct thingm_device {
32 struct hid_device *hdev; 59 struct hid_device *hdev;
33 struct led_classdev led_cdev; 60 struct {
34 u32 rgb; 61 char major;
35 u8 brightness; 62 char minor;
63 } version;
64 const struct thingm_fwinfo *fwinfo;
65 struct mutex lock;
66 struct thingm_rgb *rgb;
36}; 67};
37 68
38static int blink1_send_command(struct blink1_data *data, 69static int thingm_send(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
39 u8 buf[BLINK1_CMD_SIZE])
40{ 70{
41 int ret; 71 int ret;
42 72
43 hid_dbg(data->hdev, "command: %d%c%.2x%.2x%.2x%.2x%.2x%.2x%.2x\n", 73 hid_dbg(tdev->hdev, "-> %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n",
44 buf[0], buf[1], buf[2], buf[3], buf[4], 74 buf[0], buf[1], buf[2], buf[3], buf[4],
45 buf[5], buf[6], buf[7], buf[8]); 75 buf[5], buf[6], buf[7], buf[8]);
46 76
47 ret = hid_hw_raw_request(data->hdev, buf[0], buf, BLINK1_CMD_SIZE, 77 ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
48 HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 78 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
49 79
50 return ret < 0 ? ret : 0; 80 return ret < 0 ? ret : 0;
51} 81}
52 82
53static int blink1_update_color(struct blink1_data *data) 83static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE])
54{ 84{
55 u8 buf[BLINK1_CMD_SIZE] = { 1, 'n', 0, 0, 0, 0, 0, 0, 0 }; 85 int ret;
56 86
57 if (data->brightness) { 87 ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE,
58 unsigned int coef = DIV_ROUND_CLOSEST(255, data->brightness); 88 HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
89 if (ret < 0)
90 return ret;
59 91
60 buf[2] = DIV_ROUND_CLOSEST(blink1_rgb_to_r(data->rgb), coef); 92 hid_dbg(tdev->hdev, "<- %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n",
61 buf[3] = DIV_ROUND_CLOSEST(blink1_rgb_to_g(data->rgb), coef); 93 buf[0], buf[1], buf[2], buf[3], buf[4],
62 buf[4] = DIV_ROUND_CLOSEST(blink1_rgb_to_b(data->rgb), coef); 94 buf[5], buf[6], buf[7], buf[8]);
63 }
64 95
65 return blink1_send_command(data, buf); 96 return 0;
66} 97}
67 98
68static void blink1_led_set(struct led_classdev *led_cdev, 99static int thingm_version(struct thingm_device *tdev)
69 enum led_brightness brightness)
70{ 100{
71 struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent); 101 u8 buf[REPORT_SIZE] = { REPORT_ID, 'v', 0, 0, 0, 0, 0, 0, 0 };
102 int err;
103
104 err = thingm_send(tdev, buf);
105 if (err)
106 return err;
107
108 err = thingm_recv(tdev, buf);
109 if (err)
110 return err;
72 111
73 data->brightness = brightness; 112 tdev->version.major = buf[3];
74 if (blink1_update_color(data)) 113 tdev->version.minor = buf[4];
75 hid_err(data->hdev, "failed to update color\n"); 114
115 return 0;
76} 116}
77 117
78static enum led_brightness blink1_led_get(struct led_classdev *led_cdev) 118static int thingm_write_color(struct thingm_rgb *rgb)
79{ 119{
80 struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent); 120 u8 buf[REPORT_SIZE] = { REPORT_ID, 'n', 0, 0, 0, 0, 0, 0, 0 };
121
122 buf[2] = rgb->red.ldev.brightness;
123 buf[3] = rgb->green.ldev.brightness;
124 buf[4] = rgb->blue.ldev.brightness;
81 125
82 return data->brightness; 126 return thingm_send(rgb->tdev, buf);
83} 127}
84 128
85static ssize_t blink1_show_rgb(struct device *dev, 129static void thingm_work(struct work_struct *work)
86 struct device_attribute *attr, char *buf)
87{ 130{
88 struct blink1_data *data = dev_get_drvdata(dev->parent); 131 struct thingm_rgb *rgb = container_of(work, struct thingm_rgb, work);
89 132
90 return sprintf(buf, "%.6X\n", data->rgb); 133 mutex_lock(&rgb->tdev->lock);
134
135 if (thingm_write_color(rgb))
136 hid_err(rgb->tdev->hdev, "failed to write color\n");
137
138 mutex_unlock(&rgb->tdev->lock);
91} 139}
92 140
93static ssize_t blink1_store_rgb(struct device *dev, 141static void thingm_led_set(struct led_classdev *ldev,
94 struct device_attribute *attr, const char *buf, size_t count) 142 enum led_brightness brightness)
95{ 143{
96 struct blink1_data *data = dev_get_drvdata(dev->parent); 144 struct thingm_led *led = container_of(ldev, struct thingm_led, ldev);
97 long unsigned int rgb;
98 int ret;
99 145
100 ret = kstrtoul(buf, 16, &rgb); 146 /* the ledclass has already stored the brightness value */
101 if (ret) 147 schedule_work(&led->rgb->work);
102 return ret; 148}
103 149
104 /* RGB triplet notation is 24-bit hexadecimal */ 150static int thingm_init_rgb(struct thingm_rgb *rgb)
105 if (rgb > 0xFFFFFF) 151{
106 return -EINVAL; 152 const int minor = ((struct hidraw *) rgb->tdev->hdev->hidraw)->minor;
153 int err;
154
155 /* Register the red diode */
156 snprintf(rgb->red.name, sizeof(rgb->red.name),
157 "thingm%d:red:led%d", minor, rgb->num);
158 rgb->red.ldev.name = rgb->red.name;
159 rgb->red.ldev.max_brightness = 255;
160 rgb->red.ldev.brightness_set = thingm_led_set;
161 rgb->red.rgb = rgb;
162
163 err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->red.ldev);
164 if (err)
165 return err;
166
167 /* Register the green diode */
168 snprintf(rgb->green.name, sizeof(rgb->green.name),
169 "thingm%d:green:led%d", minor, rgb->num);
170 rgb->green.ldev.name = rgb->green.name;
171 rgb->green.ldev.max_brightness = 255;
172 rgb->green.ldev.brightness_set = thingm_led_set;
173 rgb->green.rgb = rgb;
174
175 err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->green.ldev);
176 if (err)
177 goto unregister_red;
178
179 /* Register the blue diode */
180 snprintf(rgb->blue.name, sizeof(rgb->blue.name),
181 "thingm%d:blue:led%d", minor, rgb->num);
182 rgb->blue.ldev.name = rgb->blue.name;
183 rgb->blue.ldev.max_brightness = 255;
184 rgb->blue.ldev.brightness_set = thingm_led_set;
185 rgb->blue.rgb = rgb;
186
187 err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->blue.ldev);
188 if (err)
189 goto unregister_green;
190
191 INIT_WORK(&rgb->work, thingm_work);
107 192
108 data->rgb = rgb; 193 return 0;
109 ret = blink1_update_color(data);
110 194
111 return ret ? ret : count; 195unregister_green:
112} 196 led_classdev_unregister(&rgb->green.ldev);
113 197
114static DEVICE_ATTR(rgb, S_IRUGO | S_IWUSR, blink1_show_rgb, blink1_store_rgb); 198unregister_red:
199 led_classdev_unregister(&rgb->red.ldev);
115 200
116static const struct attribute_group blink1_sysfs_group = { 201 return err;
117 .attrs = (struct attribute *[]) { 202}
118 &dev_attr_rgb.attr, 203
119 NULL 204static void thingm_remove_rgb(struct thingm_rgb *rgb)
120 }, 205{
121}; 206 flush_work(&rgb->work);
207 led_classdev_unregister(&rgb->red.ldev);
208 led_classdev_unregister(&rgb->green.ldev);
209 led_classdev_unregister(&rgb->blue.ldev);
210}
122 211
123static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id) 212static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id)
124{ 213{
125 struct blink1_data *data; 214 struct thingm_device *tdev;
126 struct led_classdev *led; 215 int i, err;
127 char led_name[13];
128 int ret;
129 216
130 data = devm_kzalloc(&hdev->dev, sizeof(struct blink1_data), GFP_KERNEL); 217 tdev = devm_kzalloc(&hdev->dev, sizeof(struct thingm_device),
131 if (!data) 218 GFP_KERNEL);
219 if (!tdev)
132 return -ENOMEM; 220 return -ENOMEM;
133 221
134 hid_set_drvdata(hdev, data); 222 tdev->hdev = hdev;
135 data->hdev = hdev; 223 hid_set_drvdata(hdev, tdev);
136 data->rgb = 0xFFFFFF; /* set a default white color */
137 224
138 ret = hid_parse(hdev); 225 err = hid_parse(hdev);
139 if (ret) 226 if (err)
140 goto error; 227 goto error;
141 228
142 ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); 229 err = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
143 if (ret) 230 if (err)
144 goto error; 231 goto error;
145 232
146 /* blink(1) serial numbers range is 0x1A001000 to 0x1A002FFF */ 233 mutex_init(&tdev->lock);
147 led = &data->led_cdev; 234
148 snprintf(led_name, sizeof(led_name), "blink1::%s", hdev->uniq + 4); 235 err = thingm_version(tdev);
149 led->name = led_name; 236 if (err)
150 led->brightness_set = blink1_led_set;
151 led->brightness_get = blink1_led_get;
152 ret = led_classdev_register(&hdev->dev, led);
153 if (ret)
154 goto stop; 237 goto stop;
155 238
156 ret = sysfs_create_group(&led->dev->kobj, &blink1_sysfs_group); 239 hid_dbg(hdev, "firmware version: %c.%c\n",
157 if (ret) 240 tdev->version.major, tdev->version.minor);
158 goto remove_led;
159 241
160 return 0; 242 for (i = 0; i < ARRAY_SIZE(thingm_fwinfo) && !tdev->fwinfo; ++i)
243 if (thingm_fwinfo[i].major == tdev->version.major)
244 tdev->fwinfo = &thingm_fwinfo[i];
245
246 if (!tdev->fwinfo) {
247 hid_err(hdev, "unsupported firmware %c\n", tdev->version.major);
248 goto stop;
249 }
250
251 tdev->rgb = devm_kzalloc(&hdev->dev,
252 sizeof(struct thingm_rgb) * tdev->fwinfo->numrgb,
253 GFP_KERNEL);
254 if (!tdev->rgb) {
255 err = -ENOMEM;
256 goto stop;
257 }
258
259 for (i = 0; i < tdev->fwinfo->numrgb; ++i) {
260 struct thingm_rgb *rgb = tdev->rgb + i;
261
262 rgb->tdev = tdev;
263 rgb->num = tdev->fwinfo->first + i;
264 err = thingm_init_rgb(rgb);
265 if (err) {
266 while (--i >= 0)
267 thingm_remove_rgb(tdev->rgb + i);
268 goto stop;
269 }
270 }
161 271
162remove_led: 272 return 0;
163 led_classdev_unregister(led);
164stop: 273stop:
165 hid_hw_stop(hdev); 274 hid_hw_stop(hdev);
166error: 275error:
167 return ret; 276 return err;
168} 277}
169 278
170static void thingm_remove(struct hid_device *hdev) 279static void thingm_remove(struct hid_device *hdev)
171{ 280{
172 struct blink1_data *data = hid_get_drvdata(hdev); 281 struct thingm_device *tdev = hid_get_drvdata(hdev);
173 struct led_classdev *led = &data->led_cdev; 282 int i;
283
284 for (i = 0; i < tdev->fwinfo->numrgb; ++i)
285 thingm_remove_rgb(tdev->rgb + i);
174 286
175 sysfs_remove_group(&led->dev->kobj, &blink1_sysfs_group);
176 led_classdev_unregister(led);
177 hid_hw_stop(hdev); 287 hid_hw_stop(hdev);
178} 288}
179 289