aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
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