]> Pileus Git - ~andy/linux/blob - drivers/hid/hid-thingm.c
Merge tag 'usb-3.14-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
[~andy/linux] / drivers / hid / hid-thingm.c
1 /*
2  * ThingM blink(1) USB RGB LED driver
3  *
4  * Copyright 2013 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/leds.h>
14 #include <linux/module.h>
15
16 #include "hid-ids.h"
17
18 #define BLINK1_CMD_SIZE         9
19
20 #define blink1_rgb_to_r(rgb)    ((rgb & 0xFF0000) >> 16)
21 #define blink1_rgb_to_g(rgb)    ((rgb & 0x00FF00) >> 8)
22 #define blink1_rgb_to_b(rgb)    ((rgb & 0x0000FF) >> 0)
23
24 /**
25  * struct blink1_data - blink(1) device specific data
26  * @hdev:               HID device.
27  * @led_cdev:           LED class instance.
28  * @rgb:                8-bit per channel RGB notation.
29  * @fade:               fade time in hundredths of a second.
30  * @brightness:         brightness coefficient.
31  * @play:               play/pause in-memory patterns.
32  */
33 struct blink1_data {
34         struct hid_device *hdev;
35         struct led_classdev led_cdev;
36         u32 rgb;
37         u16 fade;
38         u8 brightness;
39         bool play;
40 };
41
42 static int blink1_send_command(struct blink1_data *data,
43                 u8 buf[BLINK1_CMD_SIZE])
44 {
45         int ret;
46
47         hid_dbg(data->hdev, "command: %d%c%.2x%.2x%.2x%.2x%.2x%.2x%.2x\n",
48                         buf[0], buf[1], buf[2], buf[3], buf[4],
49                         buf[5], buf[6], buf[7], buf[8]);
50
51         ret = data->hdev->hid_output_raw_report(data->hdev, buf,
52                         BLINK1_CMD_SIZE, HID_FEATURE_REPORT);
53
54         return ret < 0 ? ret : 0;
55 }
56
57 static int blink1_update_color(struct blink1_data *data)
58 {
59         u8 buf[BLINK1_CMD_SIZE] = { 1, 'n', 0, 0, 0, 0, 0, 0, 0 };
60
61         if (data->brightness) {
62                 unsigned int coef = DIV_ROUND_CLOSEST(255, data->brightness);
63
64                 buf[2] = DIV_ROUND_CLOSEST(blink1_rgb_to_r(data->rgb), coef);
65                 buf[3] = DIV_ROUND_CLOSEST(blink1_rgb_to_g(data->rgb), coef);
66                 buf[4] = DIV_ROUND_CLOSEST(blink1_rgb_to_b(data->rgb), coef);
67         }
68
69         if (data->fade) {
70                 buf[1] = 'c';
71                 buf[5] = (data->fade & 0xFF00) >> 8;
72                 buf[6] = (data->fade & 0x00FF);
73         }
74
75         return blink1_send_command(data, buf);
76 }
77
78 static void blink1_led_set(struct led_classdev *led_cdev,
79                 enum led_brightness brightness)
80 {
81         struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent);
82
83         data->brightness = brightness;
84         if (blink1_update_color(data))
85                 hid_err(data->hdev, "failed to update color\n");
86 }
87
88 static enum led_brightness blink1_led_get(struct led_classdev *led_cdev)
89 {
90         struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent);
91
92         return data->brightness;
93 }
94
95 static ssize_t blink1_show_rgb(struct device *dev,
96                 struct device_attribute *attr, char *buf)
97 {
98         struct blink1_data *data = dev_get_drvdata(dev->parent);
99
100         return sprintf(buf, "%.6X\n", data->rgb);
101 }
102
103 static ssize_t blink1_store_rgb(struct device *dev,
104                 struct device_attribute *attr, const char *buf, size_t count)
105 {
106         struct blink1_data *data = dev_get_drvdata(dev->parent);
107         long unsigned int rgb;
108         int ret;
109
110         ret = kstrtoul(buf, 16, &rgb);
111         if (ret)
112                 return ret;
113
114         /* RGB triplet notation is 24-bit hexadecimal */
115         if (rgb > 0xFFFFFF)
116                 return -EINVAL;
117
118         data->rgb = rgb;
119         ret = blink1_update_color(data);
120
121         return ret ? ret : count;
122 }
123
124 static DEVICE_ATTR(rgb, S_IRUGO | S_IWUSR, blink1_show_rgb, blink1_store_rgb);
125
126 static ssize_t blink1_show_fade(struct device *dev,
127                 struct device_attribute *attr, char *buf)
128 {
129         struct blink1_data *data = dev_get_drvdata(dev->parent);
130
131         return sprintf(buf, "%d\n", data->fade * 10);
132 }
133
134 static ssize_t blink1_store_fade(struct device *dev,
135                 struct device_attribute *attr, const char *buf, size_t count)
136 {
137         struct blink1_data *data = dev_get_drvdata(dev->parent);
138         long unsigned int fade;
139         int ret;
140
141         ret = kstrtoul(buf, 10, &fade);
142         if (ret)
143                 return ret;
144
145         /* blink(1) accepts 16-bit fade time, number of 10ms ticks */
146         fade = DIV_ROUND_CLOSEST(fade, 10);
147         if (fade > 65535)
148                 return -EINVAL;
149
150         data->fade = fade;
151
152         return count;
153 }
154
155 static DEVICE_ATTR(fade, S_IRUGO | S_IWUSR,
156                 blink1_show_fade, blink1_store_fade);
157
158 static ssize_t blink1_show_play(struct device *dev,
159                 struct device_attribute *attr, char *buf)
160 {
161         struct blink1_data *data = dev_get_drvdata(dev->parent);
162
163         return sprintf(buf, "%d\n", data->play);
164 }
165
166 static ssize_t blink1_store_play(struct device *dev,
167                 struct device_attribute *attr, const char *buf, size_t count)
168 {
169         struct blink1_data *data = dev_get_drvdata(dev->parent);
170         u8 cmd[BLINK1_CMD_SIZE] = { 1, 'p', 0, 0, 0, 0, 0, 0, 0 };
171         long unsigned int play;
172         int ret;
173
174         ret = kstrtoul(buf, 10, &play);
175         if (ret)
176                 return ret;
177
178         data->play = !!play;
179         cmd[2] = data->play;
180         ret = blink1_send_command(data, cmd);
181
182         return ret ? ret : count;
183 }
184
185 static DEVICE_ATTR(play, S_IRUGO | S_IWUSR,
186                 blink1_show_play, blink1_store_play);
187
188 static const struct attribute_group blink1_sysfs_group = {
189         .attrs = (struct attribute *[]) {
190                 &dev_attr_rgb.attr,
191                 &dev_attr_fade.attr,
192                 &dev_attr_play.attr,
193                 NULL
194         },
195 };
196
197 static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id)
198 {
199         struct blink1_data *data;
200         struct led_classdev *led;
201         char led_name[13];
202         int ret;
203
204         data = devm_kzalloc(&hdev->dev, sizeof(struct blink1_data), GFP_KERNEL);
205         if (!data)
206                 return -ENOMEM;
207
208         hid_set_drvdata(hdev, data);
209         data->hdev = hdev;
210         data->rgb = 0xFFFFFF; /* set a default white color */
211
212         ret = hid_parse(hdev);
213         if (ret)
214                 goto error;
215
216         ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
217         if (ret)
218                 goto error;
219
220         /* blink(1) serial numbers range is 0x1A001000 to 0x1A002FFF */
221         led = &data->led_cdev;
222         snprintf(led_name, sizeof(led_name), "blink1::%s", hdev->uniq + 4);
223         led->name = led_name;
224         led->brightness_set = blink1_led_set;
225         led->brightness_get = blink1_led_get;
226         ret = led_classdev_register(&hdev->dev, led);
227         if (ret)
228                 goto stop;
229
230         ret = sysfs_create_group(&led->dev->kobj, &blink1_sysfs_group);
231         if (ret)
232                 goto remove_led;
233
234         return 0;
235
236 remove_led:
237         led_classdev_unregister(led);
238 stop:
239         hid_hw_stop(hdev);
240 error:
241         return ret;
242 }
243
244 static void thingm_remove(struct hid_device *hdev)
245 {
246         struct blink1_data *data = hid_get_drvdata(hdev);
247         struct led_classdev *led = &data->led_cdev;
248
249         sysfs_remove_group(&led->dev->kobj, &blink1_sysfs_group);
250         led_classdev_unregister(led);
251         hid_hw_stop(hdev);
252 }
253
254 static const struct hid_device_id thingm_table[] = {
255         { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
256         { }
257 };
258 MODULE_DEVICE_TABLE(hid, thingm_table);
259
260 static struct hid_driver thingm_driver = {
261         .name = "thingm",
262         .probe = thingm_probe,
263         .remove = thingm_remove,
264         .id_table = thingm_table,
265 };
266
267 module_hid_driver(thingm_driver);
268
269 MODULE_LICENSE("GPL");
270 MODULE_AUTHOR("Vivien Didelot <vivien.didelot@savoirfairelinux.com>");
271 MODULE_DESCRIPTION("ThingM blink(1) USB RGB LED driver");