diff options
author | Jan-Simon Möller <jansimon.moeller@gmx.de> | 2012-07-20 04:49:06 -0400 |
---|---|---|
committer | Bryan Wu <bryan.wu@canonical.com> | 2012-07-24 04:00:51 -0400 |
commit | b54cf35a7f656c61dd695509e8cf8cc7e1dc3e53 (patch) | |
tree | 1a05f14876c2b783b762f448f48b4b3586861354 | |
parent | eb18618b8018bffad6e62f1bc40e4e0c7ee2fa19 (diff) |
LEDS: add BlinkM RGB LED driver, documentation and update MAINTAINERS
Add driver for BlinkM device to drivers/leds/.
Add entry to MAINTAINERS file.
Add documentation in Documentation/leds/.
A BlinkM is a RGB LED controlled through I2C.
This driver implements an interface to the LED framework
and another sysfs group to access the internal options
of the BlinkM.
rev6: Use module_i2c_driver().
rev5: Removed own workqueue in favor of events wq.
rev4: Fixed comments by Bryan Wu.
rev3: Fixed issues found by Jonathan Neuschäfer.
(bryan.wu@canonical.com: remove 2 trailing whitespace)
Signed-off-by: Jan-Simon Möller <jansimon.moeller@gmx.de>
Signed-off-by: Bryan Wu <bryan.wu@canonical.com>
-rw-r--r-- | Documentation/leds/leds-blinkm.txt | 80 | ||||
-rw-r--r-- | MAINTAINERS | 5 | ||||
-rw-r--r-- | drivers/leds/Kconfig | 8 | ||||
-rw-r--r-- | drivers/leds/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/leds-blinkm.c | 812 |
5 files changed, 906 insertions, 0 deletions
diff --git a/Documentation/leds/leds-blinkm.txt b/Documentation/leds/leds-blinkm.txt new file mode 100644 index 000000000000..9dd92f4cf4e1 --- /dev/null +++ b/Documentation/leds/leds-blinkm.txt | |||
@@ -0,0 +1,80 @@ | |||
1 | The leds-blinkm driver supports the devices of the BlinkM family. | ||
2 | |||
3 | They are RGB-LED modules driven by a (AT)tiny microcontroller and | ||
4 | communicate through I2C. The default address of these modules is | ||
5 | 0x09 but this can be changed through a command. By this you could | ||
6 | dasy-chain up to 127 BlinkMs on an I2C bus. | ||
7 | |||
8 | The device accepts RGB and HSB color values through separate commands. | ||
9 | Also you can store blinking sequences as "scripts" in | ||
10 | the controller and run them. Also fading is an option. | ||
11 | |||
12 | The interface this driver provides is 2-fold: | ||
13 | |||
14 | a) LED class interface for use with triggers | ||
15 | ############################################ | ||
16 | |||
17 | The registration follows the scheme: | ||
18 | blinkm-<i2c-bus-nr>-<i2c-device-nr>-<color> | ||
19 | |||
20 | $ ls -h /sys/class/leds/blinkm-6-* | ||
21 | /sys/class/leds/blinkm-6-9-blue: | ||
22 | brightness device max_brightness power subsystem trigger uevent | ||
23 | |||
24 | /sys/class/leds/blinkm-6-9-green: | ||
25 | brightness device max_brightness power subsystem trigger uevent | ||
26 | |||
27 | /sys/class/leds/blinkm-6-9-red: | ||
28 | brightness device max_brightness power subsystem trigger uevent | ||
29 | |||
30 | (same is /sys/bus/i2c/devices/6-0009/leds) | ||
31 | |||
32 | We can control the colors separated into red, green and blue and | ||
33 | assign triggers on each color. | ||
34 | |||
35 | E.g.: | ||
36 | |||
37 | $ cat blinkm-6-9-blue/brightness | ||
38 | 05 | ||
39 | |||
40 | $ echo 200 > blinkm-6-9-blue/brightness | ||
41 | $ | ||
42 | |||
43 | $ modprobe ledtrig-heartbeat | ||
44 | $ echo heartbeat > blinkm-6-9-green/trigger | ||
45 | $ | ||
46 | |||
47 | |||
48 | b) Sysfs group to control rgb, fade, hsb, scripts ... | ||
49 | ##################################################### | ||
50 | |||
51 | This extended interface is available as folder blinkm | ||
52 | in the sysfs folder of the I2C device. | ||
53 | E.g. below /sys/bus/i2c/devices/6-0009/blinkm | ||
54 | |||
55 | $ ls -h /sys/bus/i2c/devices/6-0009/blinkm/ | ||
56 | blue green red test | ||
57 | |||
58 | Currently supported is just setting red, green, blue | ||
59 | and a test sequence. | ||
60 | |||
61 | E.g.: | ||
62 | |||
63 | $ cat * | ||
64 | 00 | ||
65 | 00 | ||
66 | 00 | ||
67 | #Write into test to start test sequence!# | ||
68 | |||
69 | $ echo 1 > test | ||
70 | $ | ||
71 | |||
72 | $ echo 255 > red | ||
73 | $ | ||
74 | |||
75 | |||
76 | |||
77 | as of 6/2012 | ||
78 | |||
79 | dl9pf <at> gmx <dot> de | ||
80 | |||
diff --git a/MAINTAINERS b/MAINTAINERS index fe643e7b9df6..4d8811af9edd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -1518,6 +1518,11 @@ W: http://blackfin.uclinux.org/ | |||
1518 | S: Supported | 1518 | S: Supported |
1519 | F: drivers/i2c/busses/i2c-bfin-twi.c | 1519 | F: drivers/i2c/busses/i2c-bfin-twi.c |
1520 | 1520 | ||
1521 | BLINKM RGB LED DRIVER | ||
1522 | M: Jan-Simon Moeller <jansimon.moeller@gmx.de> | ||
1523 | S: Maintained | ||
1524 | F: drivers/leds/leds-blinkm.c | ||
1525 | |||
1521 | BLOCK LAYER | 1526 | BLOCK LAYER |
1522 | M: Jens Axboe <axboe@kernel.dk> | 1527 | M: Jens Axboe <axboe@kernel.dk> |
1523 | T: git git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git | 1528 | T: git git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git |
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f028f0348e83..f0aaf415316d 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig | |||
@@ -430,6 +430,14 @@ config LEDS_OT200 | |||
430 | This option enables support for the LEDs on the Bachmann OT200. | 430 | This option enables support for the LEDs on the Bachmann OT200. |
431 | Say Y to enable LEDs on the Bachmann OT200. | 431 | Say Y to enable LEDs on the Bachmann OT200. |
432 | 432 | ||
433 | config LEDS_BLINKM | ||
434 | tristate "LED support for the BlinkM I2C RGB LED" | ||
435 | depends on LEDS_CLASS | ||
436 | depends on I2C | ||
437 | help | ||
438 | This option enables support for the BlinkM RGB LED connected | ||
439 | through I2C. Say Y to enable support for the BlinkM LED. | ||
440 | |||
433 | config LEDS_TRIGGERS | 441 | config LEDS_TRIGGERS |
434 | bool "LED Trigger support" | 442 | bool "LED Trigger support" |
435 | depends on LEDS_CLASS | 443 | depends on LEDS_CLASS |
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 5eebd7bce4be..d94d18f8a4be 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile | |||
@@ -48,6 +48,7 @@ obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o | |||
48 | obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o | 48 | obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o |
49 | obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o | 49 | obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o |
50 | obj-$(CONFIG_LEDS_LM3556) += leds-lm3556.o | 50 | obj-$(CONFIG_LEDS_LM3556) += leds-lm3556.o |
51 | obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o | ||
51 | 52 | ||
52 | # LED SPI Drivers | 53 | # LED SPI Drivers |
53 | obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o | 54 | obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o |
diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c new file mode 100644 index 000000000000..5a9df43e5302 --- /dev/null +++ b/drivers/leds/leds-blinkm.c | |||
@@ -0,0 +1,812 @@ | |||
1 | /* | ||
2 | * leds-blinkm.c | ||
3 | * (c) Jan-Simon Möller (dl9pf@gmx.de) | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; if not, write to the Free Software | ||
17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
18 | */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/slab.h> | ||
23 | #include <linux/jiffies.h> | ||
24 | #include <linux/i2c.h> | ||
25 | #include <linux/err.h> | ||
26 | #include <linux/mutex.h> | ||
27 | #include <linux/sysfs.h> | ||
28 | #include <linux/printk.h> | ||
29 | #include <linux/pm_runtime.h> | ||
30 | #include <linux/leds.h> | ||
31 | #include <linux/delay.h> | ||
32 | |||
33 | /* Addresses to scan - BlinkM is on 0x09 by default*/ | ||
34 | static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END }; | ||
35 | |||
36 | static int blinkm_transfer_hw(struct i2c_client *client, int cmd); | ||
37 | static int blinkm_test_run(struct i2c_client *client); | ||
38 | |||
39 | struct blinkm_led { | ||
40 | struct i2c_client *i2c_client; | ||
41 | struct led_classdev led_cdev; | ||
42 | int id; | ||
43 | atomic_t active; | ||
44 | }; | ||
45 | |||
46 | struct blinkm_work { | ||
47 | struct blinkm_led *blinkm_led; | ||
48 | struct work_struct work; | ||
49 | }; | ||
50 | |||
51 | #define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev) | ||
52 | #define work_to_blmwork(c) container_of(c, struct blinkm_work, work) | ||
53 | |||
54 | struct blinkm_data { | ||
55 | struct i2c_client *i2c_client; | ||
56 | struct mutex update_lock; | ||
57 | /* used for led class interface */ | ||
58 | struct blinkm_led blinkm_leds[3]; | ||
59 | /* used for "blinkm" sysfs interface */ | ||
60 | u8 red; /* color red */ | ||
61 | u8 green; /* color green */ | ||
62 | u8 blue; /* color blue */ | ||
63 | /* next values to use for transfer */ | ||
64 | u8 next_red; /* color red */ | ||
65 | u8 next_green; /* color green */ | ||
66 | u8 next_blue; /* color blue */ | ||
67 | /* internal use */ | ||
68 | u8 args[7]; /* set of args for transmission */ | ||
69 | u8 i2c_addr; /* i2c addr */ | ||
70 | u8 fw_ver; /* firmware version */ | ||
71 | /* used, but not from userspace */ | ||
72 | u8 hue; /* HSB hue */ | ||
73 | u8 saturation; /* HSB saturation */ | ||
74 | u8 brightness; /* HSB brightness */ | ||
75 | u8 next_hue; /* HSB hue */ | ||
76 | u8 next_saturation; /* HSB saturation */ | ||
77 | u8 next_brightness; /* HSB brightness */ | ||
78 | /* currently unused / todo */ | ||
79 | u8 fade_speed; /* fade speed 1 - 255 */ | ||
80 | s8 time_adjust; /* time adjust -128 - 127 */ | ||
81 | u8 fade:1; /* fade on = 1, off = 0 */ | ||
82 | u8 rand:1; /* rand fade mode on = 1 */ | ||
83 | u8 script_id; /* script ID */ | ||
84 | u8 script_repeats; /* repeats of script */ | ||
85 | u8 script_startline; /* line to start */ | ||
86 | }; | ||
87 | |||
88 | /* Colors */ | ||
89 | #define RED 0 | ||
90 | #define GREEN 1 | ||
91 | #define BLUE 2 | ||
92 | |||
93 | /* mapping command names to cmd chars - see datasheet */ | ||
94 | #define BLM_GO_RGB 0 | ||
95 | #define BLM_FADE_RGB 1 | ||
96 | #define BLM_FADE_HSB 2 | ||
97 | #define BLM_FADE_RAND_RGB 3 | ||
98 | #define BLM_FADE_RAND_HSB 4 | ||
99 | #define BLM_PLAY_SCRIPT 5 | ||
100 | #define BLM_STOP_SCRIPT 6 | ||
101 | #define BLM_SET_FADE_SPEED 7 | ||
102 | #define BLM_SET_TIME_ADJ 8 | ||
103 | #define BLM_GET_CUR_RGB 9 | ||
104 | #define BLM_WRITE_SCRIPT_LINE 10 | ||
105 | #define BLM_READ_SCRIPT_LINE 11 | ||
106 | #define BLM_SET_SCRIPT_LR 12 /* Length & Repeats */ | ||
107 | #define BLM_SET_ADDR 13 | ||
108 | #define BLM_GET_ADDR 14 | ||
109 | #define BLM_GET_FW_VER 15 | ||
110 | #define BLM_SET_STARTUP_PARAM 16 | ||
111 | |||
112 | /* BlinkM Commands | ||
113 | * as extracted out of the datasheet: | ||
114 | * | ||
115 | * cmdchar = command (ascii) | ||
116 | * cmdbyte = command in hex | ||
117 | * nr_args = number of arguments (to send) | ||
118 | * nr_ret = number of return values (to read) | ||
119 | * dir = direction (0 = read, 1 = write, 2 = both) | ||
120 | * | ||
121 | */ | ||
122 | static const struct { | ||
123 | char cmdchar; | ||
124 | u8 cmdbyte; | ||
125 | u8 nr_args; | ||
126 | u8 nr_ret; | ||
127 | u8 dir:2; | ||
128 | } blinkm_cmds[17] = { | ||
129 | /* cmdchar, cmdbyte, nr_args, nr_ret, dir */ | ||
130 | { 'n', 0x6e, 3, 0, 1}, | ||
131 | { 'c', 0x63, 3, 0, 1}, | ||
132 | { 'h', 0x68, 3, 0, 1}, | ||
133 | { 'C', 0x43, 3, 0, 1}, | ||
134 | { 'H', 0x48, 3, 0, 1}, | ||
135 | { 'p', 0x70, 3, 0, 1}, | ||
136 | { 'o', 0x6f, 0, 0, 1}, | ||
137 | { 'f', 0x66, 1, 0, 1}, | ||
138 | { 't', 0x74, 1, 0, 1}, | ||
139 | { 'g', 0x67, 0, 3, 0}, | ||
140 | { 'W', 0x57, 7, 0, 1}, | ||
141 | { 'R', 0x52, 2, 5, 2}, | ||
142 | { 'L', 0x4c, 3, 0, 1}, | ||
143 | { 'A', 0x41, 4, 0, 1}, | ||
144 | { 'a', 0x61, 0, 1, 0}, | ||
145 | { 'Z', 0x5a, 0, 1, 0}, | ||
146 | { 'B', 0x42, 5, 0, 1}, | ||
147 | }; | ||
148 | |||
149 | static ssize_t show_color_common(struct device *dev, char *buf, int color) | ||
150 | { | ||
151 | struct i2c_client *client; | ||
152 | struct blinkm_data *data; | ||
153 | int ret; | ||
154 | |||
155 | client = to_i2c_client(dev); | ||
156 | data = i2c_get_clientdata(client); | ||
157 | |||
158 | ret = blinkm_transfer_hw(client, BLM_GET_CUR_RGB); | ||
159 | if (ret < 0) | ||
160 | return ret; | ||
161 | switch (color) { | ||
162 | case RED: | ||
163 | return scnprintf(buf, PAGE_SIZE, "%02X\n", data->red); | ||
164 | break; | ||
165 | case GREEN: | ||
166 | return scnprintf(buf, PAGE_SIZE, "%02X\n", data->green); | ||
167 | break; | ||
168 | case BLUE: | ||
169 | return scnprintf(buf, PAGE_SIZE, "%02X\n", data->blue); | ||
170 | break; | ||
171 | default: | ||
172 | return -EINVAL; | ||
173 | } | ||
174 | return -EINVAL; | ||
175 | } | ||
176 | |||
177 | static int store_color_common(struct device *dev, const char *buf, int color) | ||
178 | { | ||
179 | struct i2c_client *client; | ||
180 | struct blinkm_data *data; | ||
181 | int ret; | ||
182 | u8 value; | ||
183 | |||
184 | client = to_i2c_client(dev); | ||
185 | data = i2c_get_clientdata(client); | ||
186 | |||
187 | ret = kstrtou8(buf, 10, &value); | ||
188 | if (ret < 0) { | ||
189 | dev_err(dev, "BlinkM: value too large!\n"); | ||
190 | return ret; | ||
191 | } | ||
192 | |||
193 | switch (color) { | ||
194 | case RED: | ||
195 | data->next_red = value; | ||
196 | break; | ||
197 | case GREEN: | ||
198 | data->next_green = value; | ||
199 | break; | ||
200 | case BLUE: | ||
201 | data->next_blue = value; | ||
202 | break; | ||
203 | default: | ||
204 | return -EINVAL; | ||
205 | } | ||
206 | |||
207 | dev_dbg(dev, "next_red = %d, next_green = %d, next_blue = %d\n", | ||
208 | data->next_red, data->next_green, data->next_blue); | ||
209 | |||
210 | /* if mode ... */ | ||
211 | ret = blinkm_transfer_hw(client, BLM_GO_RGB); | ||
212 | if (ret < 0) { | ||
213 | dev_err(dev, "BlinkM: can't set RGB\n"); | ||
214 | return ret; | ||
215 | } | ||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | static ssize_t show_red(struct device *dev, struct device_attribute *attr, | ||
220 | char *buf) | ||
221 | { | ||
222 | return show_color_common(dev, buf, RED); | ||
223 | } | ||
224 | |||
225 | static ssize_t store_red(struct device *dev, struct device_attribute *attr, | ||
226 | const char *buf, size_t count) | ||
227 | { | ||
228 | int ret; | ||
229 | |||
230 | ret = store_color_common(dev, buf, RED); | ||
231 | if (ret < 0) | ||
232 | return ret; | ||
233 | return count; | ||
234 | } | ||
235 | |||
236 | static DEVICE_ATTR(red, S_IRUGO | S_IWUSR, show_red, store_red); | ||
237 | |||
238 | static ssize_t show_green(struct device *dev, struct device_attribute *attr, | ||
239 | char *buf) | ||
240 | { | ||
241 | return show_color_common(dev, buf, GREEN); | ||
242 | } | ||
243 | |||
244 | static ssize_t store_green(struct device *dev, struct device_attribute *attr, | ||
245 | const char *buf, size_t count) | ||
246 | { | ||
247 | |||
248 | int ret; | ||
249 | |||
250 | ret = store_color_common(dev, buf, GREEN); | ||
251 | if (ret < 0) | ||
252 | return ret; | ||
253 | return count; | ||
254 | } | ||
255 | |||
256 | static DEVICE_ATTR(green, S_IRUGO | S_IWUSR, show_green, store_green); | ||
257 | |||
258 | static ssize_t show_blue(struct device *dev, struct device_attribute *attr, | ||
259 | char *buf) | ||
260 | { | ||
261 | return show_color_common(dev, buf, BLUE); | ||
262 | } | ||
263 | |||
264 | static ssize_t store_blue(struct device *dev, struct device_attribute *attr, | ||
265 | const char *buf, size_t count) | ||
266 | { | ||
267 | int ret; | ||
268 | |||
269 | ret = store_color_common(dev, buf, BLUE); | ||
270 | if (ret < 0) | ||
271 | return ret; | ||
272 | return count; | ||
273 | } | ||
274 | |||
275 | static DEVICE_ATTR(blue, S_IRUGO | S_IWUSR, show_blue, store_blue); | ||
276 | |||
277 | static ssize_t show_test(struct device *dev, struct device_attribute *attr, | ||
278 | char *buf) | ||
279 | { | ||
280 | return scnprintf(buf, PAGE_SIZE, | ||
281 | "#Write into test to start test sequence!#\n"); | ||
282 | } | ||
283 | |||
284 | static ssize_t store_test(struct device *dev, struct device_attribute *attr, | ||
285 | const char *buf, size_t count) | ||
286 | { | ||
287 | |||
288 | struct i2c_client *client; | ||
289 | int ret; | ||
290 | client = to_i2c_client(dev); | ||
291 | |||
292 | /*test */ | ||
293 | ret = blinkm_test_run(client); | ||
294 | if (ret < 0) | ||
295 | return ret; | ||
296 | |||
297 | return count; | ||
298 | } | ||
299 | |||
300 | static DEVICE_ATTR(test, S_IRUGO | S_IWUSR, show_test, store_test); | ||
301 | |||
302 | /* TODO: HSB, fade, timeadj, script ... */ | ||
303 | |||
304 | static struct attribute *blinkm_attrs[] = { | ||
305 | &dev_attr_red.attr, | ||
306 | &dev_attr_green.attr, | ||
307 | &dev_attr_blue.attr, | ||
308 | &dev_attr_test.attr, | ||
309 | NULL, | ||
310 | }; | ||
311 | |||
312 | static struct attribute_group blinkm_group = { | ||
313 | .name = "blinkm", | ||
314 | .attrs = blinkm_attrs, | ||
315 | }; | ||
316 | |||
317 | static int blinkm_write(struct i2c_client *client, int cmd, u8 *arg) | ||
318 | { | ||
319 | int result; | ||
320 | int i; | ||
321 | int arglen = blinkm_cmds[cmd].nr_args; | ||
322 | /* write out cmd to blinkm - always / default step */ | ||
323 | result = i2c_smbus_write_byte(client, blinkm_cmds[cmd].cmdbyte); | ||
324 | if (result < 0) | ||
325 | return result; | ||
326 | /* no args to write out */ | ||
327 | if (arglen == 0) | ||
328 | return 0; | ||
329 | |||
330 | for (i = 0; i < arglen; i++) { | ||
331 | /* repeat for arglen */ | ||
332 | result = i2c_smbus_write_byte(client, arg[i]); | ||
333 | if (result < 0) | ||
334 | return result; | ||
335 | } | ||
336 | return 0; | ||
337 | } | ||
338 | |||
339 | static int blinkm_read(struct i2c_client *client, int cmd, u8 *arg) | ||
340 | { | ||
341 | int result; | ||
342 | int i; | ||
343 | int retlen = blinkm_cmds[cmd].nr_ret; | ||
344 | for (i = 0; i < retlen; i++) { | ||
345 | /* repeat for retlen */ | ||
346 | result = i2c_smbus_read_byte(client); | ||
347 | if (result < 0) | ||
348 | return result; | ||
349 | arg[i] = result; | ||
350 | } | ||
351 | |||
352 | return 0; | ||
353 | } | ||
354 | |||
355 | static int blinkm_transfer_hw(struct i2c_client *client, int cmd) | ||
356 | { | ||
357 | /* the protocol is simple but non-standard: | ||
358 | * e.g. cmd 'g' (= 0x67) for "get device address" | ||
359 | * - which defaults to 0x09 - would be the sequence: | ||
360 | * a) write 0x67 to the device (byte write) | ||
361 | * b) read the value (0x09) back right after (byte read) | ||
362 | * | ||
363 | * Watch out for "unfinished" sequences (i.e. not enough reads | ||
364 | * or writes after a command. It will make the blinkM misbehave. | ||
365 | * Sequence is key here. | ||
366 | */ | ||
367 | |||
368 | /* args / return are in private data struct */ | ||
369 | struct blinkm_data *data = i2c_get_clientdata(client); | ||
370 | |||
371 | /* We start hardware transfers which are not to be | ||
372 | * mixed with other commands. Aquire a lock now. */ | ||
373 | if (mutex_lock_interruptible(&data->update_lock) < 0) | ||
374 | return -EAGAIN; | ||
375 | |||
376 | /* switch cmd - usually write before reads */ | ||
377 | switch (cmd) { | ||
378 | case BLM_FADE_RAND_RGB: | ||
379 | case BLM_GO_RGB: | ||
380 | case BLM_FADE_RGB: | ||
381 | data->args[0] = data->next_red; | ||
382 | data->args[1] = data->next_green; | ||
383 | data->args[2] = data->next_blue; | ||
384 | blinkm_write(client, cmd, data->args); | ||
385 | data->red = data->args[0]; | ||
386 | data->green = data->args[1]; | ||
387 | data->blue = data->args[2]; | ||
388 | break; | ||
389 | case BLM_FADE_HSB: | ||
390 | case BLM_FADE_RAND_HSB: | ||
391 | data->args[0] = data->next_hue; | ||
392 | data->args[1] = data->next_saturation; | ||
393 | data->args[2] = data->next_brightness; | ||
394 | blinkm_write(client, cmd, data->args); | ||
395 | data->hue = data->next_hue; | ||
396 | data->saturation = data->next_saturation; | ||
397 | data->brightness = data->next_brightness; | ||
398 | break; | ||
399 | case BLM_PLAY_SCRIPT: | ||
400 | data->args[0] = data->script_id; | ||
401 | data->args[1] = data->script_repeats; | ||
402 | data->args[2] = data->script_startline; | ||
403 | blinkm_write(client, cmd, data->args); | ||
404 | break; | ||
405 | case BLM_STOP_SCRIPT: | ||
406 | blinkm_write(client, cmd, NULL); | ||
407 | break; | ||
408 | case BLM_GET_CUR_RGB: | ||
409 | data->args[0] = data->red; | ||
410 | data->args[1] = data->green; | ||
411 | data->args[2] = data->blue; | ||
412 | blinkm_write(client, cmd, NULL); | ||
413 | blinkm_read(client, cmd, data->args); | ||
414 | data->red = data->args[0]; | ||
415 | data->green = data->args[1]; | ||
416 | data->blue = data->args[2]; | ||
417 | break; | ||
418 | case BLM_GET_ADDR: | ||
419 | data->args[0] = data->i2c_addr; | ||
420 | blinkm_write(client, cmd, NULL); | ||
421 | blinkm_read(client, cmd, data->args); | ||
422 | data->i2c_addr = data->args[0]; | ||
423 | break; | ||
424 | case BLM_SET_TIME_ADJ: | ||
425 | case BLM_SET_FADE_SPEED: | ||
426 | case BLM_READ_SCRIPT_LINE: | ||
427 | case BLM_WRITE_SCRIPT_LINE: | ||
428 | case BLM_SET_SCRIPT_LR: | ||
429 | case BLM_SET_ADDR: | ||
430 | case BLM_GET_FW_VER: | ||
431 | case BLM_SET_STARTUP_PARAM: | ||
432 | dev_err(&client->dev, | ||
433 | "BlinkM: cmd %d not implemented yet.\n", cmd); | ||
434 | break; | ||
435 | default: | ||
436 | dev_err(&client->dev, "BlinkM: unknown command %d\n", cmd); | ||
437 | mutex_unlock(&data->update_lock); | ||
438 | return -EINVAL; | ||
439 | } /* end switch(cmd) */ | ||
440 | |||
441 | /* transfers done, unlock */ | ||
442 | mutex_unlock(&data->update_lock); | ||
443 | return 0; | ||
444 | } | ||
445 | |||
446 | static void led_work(struct work_struct *work) | ||
447 | { | ||
448 | int ret; | ||
449 | struct blinkm_led *led; | ||
450 | struct blinkm_data *data ; | ||
451 | struct blinkm_work *blm_work = work_to_blmwork(work); | ||
452 | |||
453 | led = blm_work->blinkm_led; | ||
454 | data = i2c_get_clientdata(led->i2c_client); | ||
455 | ret = blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB); | ||
456 | atomic_dec(&led->active); | ||
457 | dev_dbg(&led->i2c_client->dev, | ||
458 | "# DONE # next_red = %d, next_green = %d," | ||
459 | " next_blue = %d, active = %d\n", | ||
460 | data->next_red, data->next_green, | ||
461 | data->next_blue, atomic_read(&led->active)); | ||
462 | kfree(blm_work); | ||
463 | } | ||
464 | |||
465 | static int blinkm_led_common_set(struct led_classdev *led_cdev, | ||
466 | enum led_brightness value, int color) | ||
467 | { | ||
468 | /* led_brightness is 0, 127 or 255 - we just use it here as-is */ | ||
469 | struct blinkm_led *led = cdev_to_blmled(led_cdev); | ||
470 | struct blinkm_data *data = i2c_get_clientdata(led->i2c_client); | ||
471 | struct blinkm_work *bl_work = kzalloc(sizeof(struct blinkm_work), | ||
472 | GFP_ATOMIC); | ||
473 | |||
474 | switch (color) { | ||
475 | case RED: | ||
476 | /* bail out if there's no change */ | ||
477 | if (data->next_red == (u8) value) | ||
478 | return 0; | ||
479 | /* we assume a quite fast sequence here ([off]->on->off) | ||
480 | * think of network led trigger - we cannot blink that fast, so | ||
481 | * in case we already have a off->on->off transition queued up, | ||
482 | * we refuse to queue up more. | ||
483 | * Revisit: fast-changing brightness. */ | ||
484 | if (atomic_read(&led->active) > 1) | ||
485 | return 0; | ||
486 | data->next_red = (u8) value; | ||
487 | break; | ||
488 | case GREEN: | ||
489 | /* bail out if there's no change */ | ||
490 | if (data->next_green == (u8) value) | ||
491 | return 0; | ||
492 | /* we assume a quite fast sequence here ([off]->on->off) | ||
493 | * Revisit: fast-changing brightness. */ | ||
494 | if (atomic_read(&led->active) > 1) | ||
495 | return 0; | ||
496 | data->next_green = (u8) value; | ||
497 | break; | ||
498 | case BLUE: | ||
499 | /* bail out if there's no change */ | ||
500 | if (data->next_blue == (u8) value) | ||
501 | return 0; | ||
502 | /* we assume a quite fast sequence here ([off]->on->off) | ||
503 | * Revisit: fast-changing brightness. */ | ||
504 | if (atomic_read(&led->active) > 1) | ||
505 | return 0; | ||
506 | data->next_blue = (u8) value; | ||
507 | break; | ||
508 | |||
509 | default: | ||
510 | dev_err(&led->i2c_client->dev, "BlinkM: unknown color.\n"); | ||
511 | return -EINVAL; | ||
512 | } | ||
513 | |||
514 | atomic_inc(&led->active); | ||
515 | dev_dbg(&led->i2c_client->dev, | ||
516 | "#TO_SCHED# next_red = %d, next_green = %d," | ||
517 | " next_blue = %d, active = %d\n", | ||
518 | data->next_red, data->next_green, | ||
519 | data->next_blue, atomic_read(&led->active)); | ||
520 | |||
521 | /* a fresh work _item_ for each change */ | ||
522 | bl_work->blinkm_led = led; | ||
523 | INIT_WORK(&bl_work->work, led_work); | ||
524 | /* queue work in own queue for easy sync on exit*/ | ||
525 | schedule_work(&bl_work->work); | ||
526 | |||
527 | return 0; | ||
528 | } | ||
529 | |||
530 | static void blinkm_led_red_set(struct led_classdev *led_cdev, | ||
531 | enum led_brightness value) | ||
532 | { | ||
533 | blinkm_led_common_set(led_cdev, value, RED); | ||
534 | } | ||
535 | |||
536 | static void blinkm_led_green_set(struct led_classdev *led_cdev, | ||
537 | enum led_brightness value) | ||
538 | { | ||
539 | blinkm_led_common_set(led_cdev, value, GREEN); | ||
540 | } | ||
541 | |||
542 | static void blinkm_led_blue_set(struct led_classdev *led_cdev, | ||
543 | enum led_brightness value) | ||
544 | { | ||
545 | blinkm_led_common_set(led_cdev, value, BLUE); | ||
546 | } | ||
547 | |||
548 | static void blinkm_init_hw(struct i2c_client *client) | ||
549 | { | ||
550 | int ret; | ||
551 | ret = blinkm_transfer_hw(client, BLM_STOP_SCRIPT); | ||
552 | ret = blinkm_transfer_hw(client, BLM_GO_RGB); | ||
553 | } | ||
554 | |||
555 | static int blinkm_test_run(struct i2c_client *client) | ||
556 | { | ||
557 | int ret; | ||
558 | struct blinkm_data *data = i2c_get_clientdata(client); | ||
559 | |||
560 | data->next_red = 0x01; | ||
561 | data->next_green = 0x05; | ||
562 | data->next_blue = 0x10; | ||
563 | ret = blinkm_transfer_hw(client, BLM_GO_RGB); | ||
564 | if (ret < 0) | ||
565 | return ret; | ||
566 | msleep(2000); | ||
567 | |||
568 | data->next_red = 0x25; | ||
569 | data->next_green = 0x10; | ||
570 | data->next_blue = 0x31; | ||
571 | ret = blinkm_transfer_hw(client, BLM_FADE_RGB); | ||
572 | if (ret < 0) | ||
573 | return ret; | ||
574 | msleep(2000); | ||
575 | |||
576 | data->next_hue = 0x50; | ||
577 | data->next_saturation = 0x10; | ||
578 | data->next_brightness = 0x20; | ||
579 | ret = blinkm_transfer_hw(client, BLM_FADE_HSB); | ||
580 | if (ret < 0) | ||
581 | return ret; | ||
582 | msleep(2000); | ||
583 | |||
584 | return 0; | ||
585 | } | ||
586 | |||
587 | /* Return 0 if detection is successful, -ENODEV otherwise */ | ||
588 | static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info) | ||
589 | { | ||
590 | struct i2c_adapter *adapter = client->adapter; | ||
591 | int ret; | ||
592 | int count = 99; | ||
593 | u8 tmpargs[7]; | ||
594 | |||
595 | if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | ||
596 | | I2C_FUNC_SMBUS_WORD_DATA | ||
597 | | I2C_FUNC_SMBUS_WRITE_BYTE)) | ||
598 | return -ENODEV; | ||
599 | |||
600 | /* Now, we do the remaining detection. Simple for now. */ | ||
601 | /* We might need more guards to protect other i2c slaves */ | ||
602 | |||
603 | /* make sure the blinkM is balanced (read/writes) */ | ||
604 | while (count > 0) { | ||
605 | ret = blinkm_write(client, BLM_GET_ADDR, NULL); | ||
606 | usleep_range(5000, 10000); | ||
607 | ret = blinkm_read(client, BLM_GET_ADDR, tmpargs); | ||
608 | usleep_range(5000, 10000); | ||
609 | if (tmpargs[0] == 0x09) | ||
610 | count = 0; | ||
611 | count--; | ||
612 | } | ||
613 | |||
614 | /* Step 1: Read BlinkM address back - cmd_char 'a' */ | ||
615 | ret = blinkm_write(client, BLM_GET_ADDR, NULL); | ||
616 | if (ret < 0) | ||
617 | return -ENODEV; | ||
618 | usleep_range(20000, 30000); /* allow a small delay */ | ||
619 | ret = blinkm_read(client, BLM_GET_ADDR, tmpargs); | ||
620 | if (ret < 0) | ||
621 | return -ENODEV; | ||
622 | |||
623 | if (tmpargs[0] != 0x09) { | ||
624 | dev_err(&client->dev, "enodev DEV ADDR = 0x%02X\n", tmpargs[0]); | ||
625 | return -ENODEV; | ||
626 | } | ||
627 | |||
628 | strlcpy(info->type, "blinkm", I2C_NAME_SIZE); | ||
629 | return 0; | ||
630 | } | ||
631 | |||
632 | static int __devinit blinkm_probe(struct i2c_client *client, | ||
633 | const struct i2c_device_id *id) | ||
634 | { | ||
635 | struct blinkm_data *data; | ||
636 | struct blinkm_led *led[3]; | ||
637 | int err, i; | ||
638 | char blinkm_led_name[28]; | ||
639 | |||
640 | data = devm_kzalloc(&client->dev, | ||
641 | sizeof(struct blinkm_data), GFP_KERNEL); | ||
642 | if (!data) { | ||
643 | err = -ENOMEM; | ||
644 | goto exit; | ||
645 | } | ||
646 | |||
647 | data->i2c_addr = 0x09; | ||
648 | data->i2c_addr = 0x08; | ||
649 | /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */ | ||
650 | data->fw_ver = 0xfe; | ||
651 | /* firmware version - use fake until we read real value | ||
652 | * (currently broken - BlinkM confused!) */ | ||
653 | data->script_id = 0x01; | ||
654 | data->i2c_client = client; | ||
655 | |||
656 | i2c_set_clientdata(client, data); | ||
657 | mutex_init(&data->update_lock); | ||
658 | |||
659 | /* Register sysfs hooks */ | ||
660 | err = sysfs_create_group(&client->dev.kobj, &blinkm_group); | ||
661 | if (err < 0) { | ||
662 | dev_err(&client->dev, "couldn't register sysfs group\n"); | ||
663 | goto exit; | ||
664 | } | ||
665 | |||
666 | for (i = 0; i < 3; i++) { | ||
667 | /* RED = 0, GREEN = 1, BLUE = 2 */ | ||
668 | led[i] = &data->blinkm_leds[i]; | ||
669 | led[i]->i2c_client = client; | ||
670 | led[i]->id = i; | ||
671 | led[i]->led_cdev.max_brightness = 255; | ||
672 | led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME; | ||
673 | atomic_set(&led[i]->active, 0); | ||
674 | switch (i) { | ||
675 | case RED: | ||
676 | snprintf(blinkm_led_name, sizeof(blinkm_led_name), | ||
677 | "blinkm-%d-%d-red", | ||
678 | client->adapter->nr, | ||
679 | client->addr); | ||
680 | led[i]->led_cdev.name = blinkm_led_name; | ||
681 | led[i]->led_cdev.brightness_set = blinkm_led_red_set; | ||
682 | err = led_classdev_register(&client->dev, | ||
683 | &led[i]->led_cdev); | ||
684 | if (err < 0) { | ||
685 | dev_err(&client->dev, | ||
686 | "couldn't register LED %s\n", | ||
687 | led[i]->led_cdev.name); | ||
688 | goto failred; | ||
689 | } | ||
690 | break; | ||
691 | case GREEN: | ||
692 | snprintf(blinkm_led_name, sizeof(blinkm_led_name), | ||
693 | "blinkm-%d-%d-green", | ||
694 | client->adapter->nr, | ||
695 | client->addr); | ||
696 | led[i]->led_cdev.name = blinkm_led_name; | ||
697 | led[i]->led_cdev.brightness_set = blinkm_led_green_set; | ||
698 | err = led_classdev_register(&client->dev, | ||
699 | &led[i]->led_cdev); | ||
700 | if (err < 0) { | ||
701 | dev_err(&client->dev, | ||
702 | "couldn't register LED %s\n", | ||
703 | led[i]->led_cdev.name); | ||
704 | goto failgreen; | ||
705 | } | ||
706 | break; | ||
707 | case BLUE: | ||
708 | snprintf(blinkm_led_name, sizeof(blinkm_led_name), | ||
709 | "blinkm-%d-%d-blue", | ||
710 | client->adapter->nr, | ||
711 | client->addr); | ||
712 | led[i]->led_cdev.name = blinkm_led_name; | ||
713 | led[i]->led_cdev.brightness_set = blinkm_led_blue_set; | ||
714 | err = led_classdev_register(&client->dev, | ||
715 | &led[i]->led_cdev); | ||
716 | if (err < 0) { | ||
717 | dev_err(&client->dev, | ||
718 | "couldn't register LED %s\n", | ||
719 | led[i]->led_cdev.name); | ||
720 | goto failblue; | ||
721 | } | ||
722 | break; | ||
723 | } /* end switch */ | ||
724 | } /* end for */ | ||
725 | |||
726 | /* Initialize the blinkm */ | ||
727 | blinkm_init_hw(client); | ||
728 | |||
729 | return 0; | ||
730 | |||
731 | failblue: | ||
732 | led_classdev_unregister(&led[GREEN]->led_cdev); | ||
733 | |||
734 | failgreen: | ||
735 | led_classdev_unregister(&led[RED]->led_cdev); | ||
736 | |||
737 | failred: | ||
738 | sysfs_remove_group(&client->dev.kobj, &blinkm_group); | ||
739 | exit: | ||
740 | return err; | ||
741 | } | ||
742 | |||
743 | static int __devexit blinkm_remove(struct i2c_client *client) | ||
744 | { | ||
745 | struct blinkm_data *data = i2c_get_clientdata(client); | ||
746 | int ret = 0; | ||
747 | int i; | ||
748 | |||
749 | /* make sure no workqueue entries are pending */ | ||
750 | for (i = 0; i < 3; i++) { | ||
751 | flush_scheduled_work(); | ||
752 | led_classdev_unregister(&data->blinkm_leds[i].led_cdev); | ||
753 | } | ||
754 | |||
755 | /* reset rgb */ | ||
756 | data->next_red = 0x00; | ||
757 | data->next_green = 0x00; | ||
758 | data->next_blue = 0x00; | ||
759 | ret = blinkm_transfer_hw(client, BLM_FADE_RGB); | ||
760 | if (ret < 0) | ||
761 | dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); | ||
762 | |||
763 | /* reset hsb */ | ||
764 | data->next_hue = 0x00; | ||
765 | data->next_saturation = 0x00; | ||
766 | data->next_brightness = 0x00; | ||
767 | ret = blinkm_transfer_hw(client, BLM_FADE_HSB); | ||
768 | if (ret < 0) | ||
769 | dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); | ||
770 | |||
771 | /* red fade to off */ | ||
772 | data->next_red = 0xff; | ||
773 | ret = blinkm_transfer_hw(client, BLM_GO_RGB); | ||
774 | if (ret < 0) | ||
775 | dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); | ||
776 | |||
777 | /* off */ | ||
778 | data->next_red = 0x00; | ||
779 | ret = blinkm_transfer_hw(client, BLM_FADE_RGB); | ||
780 | if (ret < 0) | ||
781 | dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); | ||
782 | |||
783 | sysfs_remove_group(&client->dev.kobj, &blinkm_group); | ||
784 | return 0; | ||
785 | } | ||
786 | |||
787 | static const struct i2c_device_id blinkm_id[] = { | ||
788 | {"blinkm", 0}, | ||
789 | {} | ||
790 | }; | ||
791 | |||
792 | MODULE_DEVICE_TABLE(i2c, blinkm_id); | ||
793 | |||
794 | /* This is the driver that will be inserted */ | ||
795 | static struct i2c_driver blinkm_driver = { | ||
796 | .class = I2C_CLASS_HWMON, | ||
797 | .driver = { | ||
798 | .name = "blinkm", | ||
799 | }, | ||
800 | .probe = blinkm_probe, | ||
801 | .remove = __devexit_p(blinkm_remove), | ||
802 | .id_table = blinkm_id, | ||
803 | .detect = blinkm_detect, | ||
804 | .address_list = normal_i2c, | ||
805 | }; | ||
806 | |||
807 | module_i2c_driver(blinkm_driver); | ||
808 | |||
809 | MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>"); | ||
810 | MODULE_DESCRIPTION("BlinkM RGB LED driver"); | ||
811 | MODULE_LICENSE("GPL"); | ||
812 | |||