834 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * leds-blinkm.c4 * (c) Jan-Simon Möller (dl9pf@gmx.de)5 * (c) Joseph Strauss (jstrauss@mailbox.org)6 */7 8#include <linux/module.h>9#include <linux/slab.h>10#include <linux/jiffies.h>11#include <linux/i2c.h>12#include <linux/err.h>13#include <linux/mutex.h>14#include <linux/sysfs.h>15#include <linux/printk.h>16#include <linux/pm_runtime.h>17#include <linux/leds.h>18#include <linux/delay.h>19#include <linux/led-class-multicolor.h>20#include <linux/kconfig.h>21 22#define NUM_LEDS 323 24/* Addresses to scan - BlinkM is on 0x09 by default*/25static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END };26 27static int blinkm_transfer_hw(struct i2c_client *client, int cmd);28static int blinkm_test_run(struct i2c_client *client);29 30/* Contains structs for both the color-separated sysfs classes, and the new multicolor class */31struct blinkm_led {32 struct i2c_client *i2c_client;33 union {34 /* used when multicolor support is disabled */35 struct led_classdev led_cdev;36 struct led_classdev_mc mcled_cdev;37 } cdev;38 int id;39};40 41#define led_cdev_to_blmled(c) container_of(c, struct blinkm_led, cdev.led_cdev)42#define mcled_cdev_to_led(c) container_of(c, struct blinkm_led, cdev.mcled_cdev)43 44struct blinkm_data {45 struct i2c_client *i2c_client;46 struct mutex update_lock;47 /* used for led class interface */48 struct blinkm_led blinkm_leds[NUM_LEDS];49 /* used for "blinkm" sysfs interface */50 u8 red; /* color red */51 u8 green; /* color green */52 u8 blue; /* color blue */53 /* next values to use for transfer */54 u8 next_red; /* color red */55 u8 next_green; /* color green */56 u8 next_blue; /* color blue */57 /* internal use */58 u8 args[7]; /* set of args for transmission */59 u8 i2c_addr; /* i2c addr */60 u8 fw_ver; /* firmware version */61 /* used, but not from userspace */62 u8 hue; /* HSB hue */63 u8 saturation; /* HSB saturation */64 u8 brightness; /* HSB brightness */65 u8 next_hue; /* HSB hue */66 u8 next_saturation; /* HSB saturation */67 u8 next_brightness; /* HSB brightness */68 /* currently unused / todo */69 u8 fade_speed; /* fade speed 1 - 255 */70 s8 time_adjust; /* time adjust -128 - 127 */71 u8 fade:1; /* fade on = 1, off = 0 */72 u8 rand:1; /* rand fade mode on = 1 */73 u8 script_id; /* script ID */74 u8 script_repeats; /* repeats of script */75 u8 script_startline; /* line to start */76};77 78/* Colors */79#define RED 080#define GREEN 181#define BLUE 282 83/* mapping command names to cmd chars - see datasheet */84#define BLM_GO_RGB 085#define BLM_FADE_RGB 186#define BLM_FADE_HSB 287#define BLM_FADE_RAND_RGB 388#define BLM_FADE_RAND_HSB 489#define BLM_PLAY_SCRIPT 590#define BLM_STOP_SCRIPT 691#define BLM_SET_FADE_SPEED 792#define BLM_SET_TIME_ADJ 893#define BLM_GET_CUR_RGB 994#define BLM_WRITE_SCRIPT_LINE 1095#define BLM_READ_SCRIPT_LINE 1196#define BLM_SET_SCRIPT_LR 12 /* Length & Repeats */97#define BLM_SET_ADDR 1398#define BLM_GET_ADDR 1499#define BLM_GET_FW_VER 15100#define BLM_SET_STARTUP_PARAM 16101 102/* BlinkM Commands103 * as extracted out of the datasheet:104 *105 * cmdchar = command (ascii)106 * cmdbyte = command in hex107 * nr_args = number of arguments (to send)108 * nr_ret = number of return values (to read)109 * dir = direction (0 = read, 1 = write, 2 = both)110 *111 */112static const struct {113 char cmdchar;114 u8 cmdbyte;115 u8 nr_args;116 u8 nr_ret;117 u8 dir:2;118} blinkm_cmds[17] = {119 /* cmdchar, cmdbyte, nr_args, nr_ret, dir */120 { 'n', 0x6e, 3, 0, 1},121 { 'c', 0x63, 3, 0, 1},122 { 'h', 0x68, 3, 0, 1},123 { 'C', 0x43, 3, 0, 1},124 { 'H', 0x48, 3, 0, 1},125 { 'p', 0x70, 3, 0, 1},126 { 'o', 0x6f, 0, 0, 1},127 { 'f', 0x66, 1, 0, 1},128 { 't', 0x74, 1, 0, 1},129 { 'g', 0x67, 0, 3, 0},130 { 'W', 0x57, 7, 0, 1},131 { 'R', 0x52, 2, 5, 2},132 { 'L', 0x4c, 3, 0, 1},133 { 'A', 0x41, 4, 0, 1},134 { 'a', 0x61, 0, 1, 0},135 { 'Z', 0x5a, 0, 1, 0},136 { 'B', 0x42, 5, 0, 1},137};138 139static ssize_t show_color_common(struct device *dev, char *buf, int color)140{141 struct i2c_client *client;142 struct blinkm_data *data;143 int ret;144 145 client = to_i2c_client(dev);146 data = i2c_get_clientdata(client);147 148 ret = blinkm_transfer_hw(client, BLM_GET_CUR_RGB);149 if (ret < 0)150 return ret;151 switch (color) {152 case RED:153 return sysfs_emit(buf, "%02X\n", data->red);154 case GREEN:155 return sysfs_emit(buf, "%02X\n", data->green);156 case BLUE:157 return sysfs_emit(buf, "%02X\n", data->blue);158 default:159 return -EINVAL;160 }161 return -EINVAL;162}163 164static int store_color_common(struct device *dev, const char *buf, int color)165{166 struct i2c_client *client;167 struct blinkm_data *data;168 int ret;169 u8 value;170 171 client = to_i2c_client(dev);172 data = i2c_get_clientdata(client);173 174 ret = kstrtou8(buf, 10, &value);175 if (ret < 0) {176 dev_err(dev, "BlinkM: value too large!\n");177 return ret;178 }179 180 switch (color) {181 case RED:182 data->next_red = value;183 break;184 case GREEN:185 data->next_green = value;186 break;187 case BLUE:188 data->next_blue = value;189 break;190 default:191 return -EINVAL;192 }193 194 dev_dbg(dev, "next_red = %d, next_green = %d, next_blue = %d\n",195 data->next_red, data->next_green, data->next_blue);196 197 /* if mode ... */198 ret = blinkm_transfer_hw(client, BLM_GO_RGB);199 if (ret < 0) {200 dev_err(dev, "BlinkM: can't set RGB\n");201 return ret;202 }203 return 0;204}205 206static ssize_t red_show(struct device *dev, struct device_attribute *attr,207 char *buf)208{209 return show_color_common(dev, buf, RED);210}211 212static ssize_t red_store(struct device *dev, struct device_attribute *attr,213 const char *buf, size_t count)214{215 int ret;216 217 ret = store_color_common(dev, buf, RED);218 if (ret < 0)219 return ret;220 return count;221}222 223static DEVICE_ATTR_RW(red);224 225static ssize_t green_show(struct device *dev, struct device_attribute *attr,226 char *buf)227{228 return show_color_common(dev, buf, GREEN);229}230 231static ssize_t green_store(struct device *dev, struct device_attribute *attr,232 const char *buf, size_t count)233{234 235 int ret;236 237 ret = store_color_common(dev, buf, GREEN);238 if (ret < 0)239 return ret;240 return count;241}242 243static DEVICE_ATTR_RW(green);244 245static ssize_t blue_show(struct device *dev, struct device_attribute *attr,246 char *buf)247{248 return show_color_common(dev, buf, BLUE);249}250 251static ssize_t blue_store(struct device *dev, struct device_attribute *attr,252 const char *buf, size_t count)253{254 int ret;255 256 ret = store_color_common(dev, buf, BLUE);257 if (ret < 0)258 return ret;259 return count;260}261 262static DEVICE_ATTR_RW(blue);263 264static ssize_t test_show(struct device *dev, struct device_attribute *attr,265 char *buf)266{267 return sysfs_emit(buf,268 "#Write into test to start test sequence!#\n");269}270 271static ssize_t test_store(struct device *dev, struct device_attribute *attr,272 const char *buf, size_t count)273{274 275 struct i2c_client *client;276 int ret;277 client = to_i2c_client(dev);278 279 /*test */280 ret = blinkm_test_run(client);281 if (ret < 0)282 return ret;283 284 return count;285}286 287static DEVICE_ATTR_RW(test);288 289/* TODO: HSB, fade, timeadj, script ... */290 291static struct attribute *blinkm_attrs[] = {292 &dev_attr_red.attr,293 &dev_attr_green.attr,294 &dev_attr_blue.attr,295 &dev_attr_test.attr,296 NULL,297};298 299static const struct attribute_group blinkm_group = {300 .name = "blinkm",301 .attrs = blinkm_attrs,302};303 304static int blinkm_write(struct i2c_client *client, int cmd, u8 *arg)305{306 int result;307 int i;308 int arglen = blinkm_cmds[cmd].nr_args;309 /* write out cmd to blinkm - always / default step */310 result = i2c_smbus_write_byte(client, blinkm_cmds[cmd].cmdbyte);311 if (result < 0)312 return result;313 /* no args to write out */314 if (arglen == 0)315 return 0;316 317 for (i = 0; i < arglen; i++) {318 /* repeat for arglen */319 result = i2c_smbus_write_byte(client, arg[i]);320 if (result < 0)321 return result;322 }323 return 0;324}325 326static int blinkm_read(struct i2c_client *client, int cmd, u8 *arg)327{328 int result;329 int i;330 int retlen = blinkm_cmds[cmd].nr_ret;331 for (i = 0; i < retlen; i++) {332 /* repeat for retlen */333 result = i2c_smbus_read_byte(client);334 if (result < 0)335 return result;336 arg[i] = result;337 }338 339 return 0;340}341 342static int blinkm_transfer_hw(struct i2c_client *client, int cmd)343{344 /* the protocol is simple but non-standard:345 * e.g. cmd 'g' (= 0x67) for "get device address"346 * - which defaults to 0x09 - would be the sequence:347 * a) write 0x67 to the device (byte write)348 * b) read the value (0x09) back right after (byte read)349 *350 * Watch out for "unfinished" sequences (i.e. not enough reads351 * or writes after a command. It will make the blinkM misbehave.352 * Sequence is key here.353 */354 355 /* args / return are in private data struct */356 struct blinkm_data *data = i2c_get_clientdata(client);357 358 /* We start hardware transfers which are not to be359 * mixed with other commands. Aquire a lock now. */360 if (mutex_lock_interruptible(&data->update_lock) < 0)361 return -EAGAIN;362 363 /* switch cmd - usually write before reads */364 switch (cmd) {365 case BLM_FADE_RAND_RGB:366 case BLM_GO_RGB:367 case BLM_FADE_RGB:368 data->args[0] = data->next_red;369 data->args[1] = data->next_green;370 data->args[2] = data->next_blue;371 blinkm_write(client, cmd, data->args);372 data->red = data->args[0];373 data->green = data->args[1];374 data->blue = data->args[2];375 break;376 case BLM_FADE_HSB:377 case BLM_FADE_RAND_HSB:378 data->args[0] = data->next_hue;379 data->args[1] = data->next_saturation;380 data->args[2] = data->next_brightness;381 blinkm_write(client, cmd, data->args);382 data->hue = data->next_hue;383 data->saturation = data->next_saturation;384 data->brightness = data->next_brightness;385 break;386 case BLM_PLAY_SCRIPT:387 data->args[0] = data->script_id;388 data->args[1] = data->script_repeats;389 data->args[2] = data->script_startline;390 blinkm_write(client, cmd, data->args);391 break;392 case BLM_STOP_SCRIPT:393 blinkm_write(client, cmd, NULL);394 break;395 case BLM_GET_CUR_RGB:396 data->args[0] = data->red;397 data->args[1] = data->green;398 data->args[2] = data->blue;399 blinkm_write(client, cmd, NULL);400 blinkm_read(client, cmd, data->args);401 data->red = data->args[0];402 data->green = data->args[1];403 data->blue = data->args[2];404 break;405 case BLM_GET_ADDR:406 data->args[0] = data->i2c_addr;407 blinkm_write(client, cmd, NULL);408 blinkm_read(client, cmd, data->args);409 data->i2c_addr = data->args[0];410 break;411 case BLM_SET_TIME_ADJ:412 case BLM_SET_FADE_SPEED:413 case BLM_READ_SCRIPT_LINE:414 case BLM_WRITE_SCRIPT_LINE:415 case BLM_SET_SCRIPT_LR:416 case BLM_SET_ADDR:417 case BLM_GET_FW_VER:418 case BLM_SET_STARTUP_PARAM:419 dev_err(&client->dev,420 "BlinkM: cmd %d not implemented yet.\n", cmd);421 break;422 default:423 dev_err(&client->dev, "BlinkM: unknown command %d\n", cmd);424 mutex_unlock(&data->update_lock);425 return -EINVAL;426 } /* end switch(cmd) */427 428 /* transfers done, unlock */429 mutex_unlock(&data->update_lock);430 return 0;431}432 433static int blinkm_set_mc_brightness(struct led_classdev *led_cdev,434 enum led_brightness value)435{436 struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);437 struct blinkm_led *led = mcled_cdev_to_led(mcled_cdev);438 struct blinkm_data *data = i2c_get_clientdata(led->i2c_client);439 440 led_mc_calc_color_components(mcled_cdev, value);441 442 data->next_red = (u8) mcled_cdev->subled_info[RED].brightness;443 data->next_green = (u8) mcled_cdev->subled_info[GREEN].brightness;444 data->next_blue = (u8) mcled_cdev->subled_info[BLUE].brightness;445 446 blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB);447 448 return 0;449}450 451static int blinkm_led_common_set(struct led_classdev *led_cdev,452 enum led_brightness value, int color)453{454 /* led_brightness is 0, 127 or 255 - we just use it here as-is */455 struct blinkm_led *led = led_cdev_to_blmled(led_cdev);456 struct blinkm_data *data = i2c_get_clientdata(led->i2c_client);457 458 switch (color) {459 case RED:460 /* bail out if there's no change */461 if (data->next_red == (u8) value)462 return 0;463 data->next_red = (u8) value;464 break;465 case GREEN:466 /* bail out if there's no change */467 if (data->next_green == (u8) value)468 return 0;469 data->next_green = (u8) value;470 break;471 case BLUE:472 /* bail out if there's no change */473 if (data->next_blue == (u8) value)474 return 0;475 data->next_blue = (u8) value;476 break;477 478 default:479 dev_err(&led->i2c_client->dev, "BlinkM: unknown color.\n");480 return -EINVAL;481 }482 483 blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB);484 dev_dbg(&led->i2c_client->dev,485 "# DONE # next_red = %d, next_green = %d,"486 " next_blue = %d\n",487 data->next_red, data->next_green,488 data->next_blue);489 return 0;490}491 492static int blinkm_led_red_set(struct led_classdev *led_cdev,493 enum led_brightness value)494{495 return blinkm_led_common_set(led_cdev, value, RED);496}497 498static int blinkm_led_green_set(struct led_classdev *led_cdev,499 enum led_brightness value)500{501 return blinkm_led_common_set(led_cdev, value, GREEN);502}503 504static int blinkm_led_blue_set(struct led_classdev *led_cdev,505 enum led_brightness value)506{507 return blinkm_led_common_set(led_cdev, value, BLUE);508}509 510static void blinkm_init_hw(struct i2c_client *client)511{512 blinkm_transfer_hw(client, BLM_STOP_SCRIPT);513 blinkm_transfer_hw(client, BLM_GO_RGB);514}515 516static int blinkm_test_run(struct i2c_client *client)517{518 int ret;519 struct blinkm_data *data = i2c_get_clientdata(client);520 521 data->next_red = 0x01;522 data->next_green = 0x05;523 data->next_blue = 0x10;524 ret = blinkm_transfer_hw(client, BLM_GO_RGB);525 if (ret < 0)526 return ret;527 msleep(2000);528 529 data->next_red = 0x25;530 data->next_green = 0x10;531 data->next_blue = 0x31;532 ret = blinkm_transfer_hw(client, BLM_FADE_RGB);533 if (ret < 0)534 return ret;535 msleep(2000);536 537 data->next_hue = 0x50;538 data->next_saturation = 0x10;539 data->next_brightness = 0x20;540 ret = blinkm_transfer_hw(client, BLM_FADE_HSB);541 if (ret < 0)542 return ret;543 msleep(2000);544 545 return 0;546}547 548/* Return 0 if detection is successful, -ENODEV otherwise */549static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)550{551 struct i2c_adapter *adapter = client->adapter;552 int ret;553 int count = 99;554 u8 tmpargs[7];555 556 if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA557 | I2C_FUNC_SMBUS_WORD_DATA558 | I2C_FUNC_SMBUS_WRITE_BYTE))559 return -ENODEV;560 561 /* Now, we do the remaining detection. Simple for now. */562 /* We might need more guards to protect other i2c slaves */563 564 /* make sure the blinkM is balanced (read/writes) */565 while (count > 0) {566 ret = blinkm_write(client, BLM_GET_ADDR, NULL);567 if (ret)568 return ret;569 usleep_range(5000, 10000);570 ret = blinkm_read(client, BLM_GET_ADDR, tmpargs);571 if (ret)572 return ret;573 usleep_range(5000, 10000);574 if (tmpargs[0] == 0x09)575 count = 0;576 count--;577 }578 579 /* Step 1: Read BlinkM address back - cmd_char 'a' */580 ret = blinkm_write(client, BLM_GET_ADDR, NULL);581 if (ret < 0)582 return ret;583 usleep_range(20000, 30000); /* allow a small delay */584 ret = blinkm_read(client, BLM_GET_ADDR, tmpargs);585 if (ret < 0)586 return ret;587 588 if (tmpargs[0] != 0x09) {589 dev_err(&client->dev, "enodev DEV ADDR = 0x%02X\n", tmpargs[0]);590 return -ENODEV;591 }592 593 strscpy(info->type, "blinkm", I2C_NAME_SIZE);594 return 0;595}596 597static int register_separate_colors(struct i2c_client *client, struct blinkm_data *data)598{599 /* 3 separate classes for red, green, and blue respectively */600 struct blinkm_led *leds[NUM_LEDS];601 int err;602 char blinkm_led_name[28];603 /* Register red, green, and blue sysfs classes */604 for (int i = 0; i < NUM_LEDS; i++) {605 /* RED = 0, GREEN = 1, BLUE = 2 */606 leds[i] = &data->blinkm_leds[i];607 leds[i]->i2c_client = client;608 leds[i]->id = i;609 leds[i]->cdev.led_cdev.max_brightness = 255;610 leds[i]->cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME;611 switch (i) {612 case RED:613 scnprintf(blinkm_led_name, sizeof(blinkm_led_name),614 "blinkm-%d-%d-red",615 client->adapter->nr,616 client->addr);617 leds[i]->cdev.led_cdev.name = blinkm_led_name;618 leds[i]->cdev.led_cdev.brightness_set_blocking =619 blinkm_led_red_set;620 err = led_classdev_register(&client->dev,621 &leds[i]->cdev.led_cdev);622 if (err < 0) {623 dev_err(&client->dev,624 "couldn't register LED %s\n",625 leds[i]->cdev.led_cdev.name);626 goto failred;627 }628 break;629 case GREEN:630 scnprintf(blinkm_led_name, sizeof(blinkm_led_name),631 "blinkm-%d-%d-green",632 client->adapter->nr,633 client->addr);634 leds[i]->cdev.led_cdev.name = blinkm_led_name;635 leds[i]->cdev.led_cdev.brightness_set_blocking =636 blinkm_led_green_set;637 err = led_classdev_register(&client->dev,638 &leds[i]->cdev.led_cdev);639 if (err < 0) {640 dev_err(&client->dev,641 "couldn't register LED %s\n",642 leds[i]->cdev.led_cdev.name);643 goto failgreen;644 }645 break;646 case BLUE:647 scnprintf(blinkm_led_name, sizeof(blinkm_led_name),648 "blinkm-%d-%d-blue",649 client->adapter->nr,650 client->addr);651 leds[i]->cdev.led_cdev.name = blinkm_led_name;652 leds[i]->cdev.led_cdev.brightness_set_blocking =653 blinkm_led_blue_set;654 err = led_classdev_register(&client->dev,655 &leds[i]->cdev.led_cdev);656 if (err < 0) {657 dev_err(&client->dev,658 "couldn't register LED %s\n",659 leds[i]->cdev.led_cdev.name);660 goto failblue;661 }662 break;663 default:664 break;665 } /* end switch */666 } /* end for */667 return 0;668 669failblue:670 led_classdev_unregister(&leds[GREEN]->cdev.led_cdev);671failgreen:672 led_classdev_unregister(&leds[RED]->cdev.led_cdev);673failred:674 sysfs_remove_group(&client->dev.kobj, &blinkm_group);675 676 return err;677}678 679static int register_multicolor(struct i2c_client *client, struct blinkm_data *data)680{681 struct blinkm_led *mc_led;682 struct mc_subled *mc_led_info;683 char blinkm_led_name[28];684 int err;685 686 /* Register multicolor sysfs class */687 /* The first element of leds is used for multicolor facilities */688 mc_led = &data->blinkm_leds[RED];689 mc_led->i2c_client = client;690 691 mc_led_info = devm_kcalloc(&client->dev, NUM_LEDS, sizeof(*mc_led_info),692 GFP_KERNEL);693 if (!mc_led_info)694 return -ENOMEM;695 696 mc_led_info[RED].color_index = LED_COLOR_ID_RED;697 mc_led_info[GREEN].color_index = LED_COLOR_ID_GREEN;698 mc_led_info[BLUE].color_index = LED_COLOR_ID_BLUE;699 700 mc_led->cdev.mcled_cdev.subled_info = mc_led_info;701 mc_led->cdev.mcled_cdev.num_colors = NUM_LEDS;702 mc_led->cdev.mcled_cdev.led_cdev.brightness = 255;703 mc_led->cdev.mcled_cdev.led_cdev.max_brightness = 255;704 mc_led->cdev.mcled_cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME;705 706 scnprintf(blinkm_led_name, sizeof(blinkm_led_name),707 "blinkm-%d-%d:rgb:indicator",708 client->adapter->nr,709 client->addr);710 mc_led->cdev.mcled_cdev.led_cdev.name = blinkm_led_name;711 mc_led->cdev.mcled_cdev.led_cdev.brightness_set_blocking = blinkm_set_mc_brightness;712 713 err = led_classdev_multicolor_register(&client->dev, &mc_led->cdev.mcled_cdev);714 if (err < 0) {715 dev_err(&client->dev, "couldn't register LED %s\n",716 mc_led->cdev.led_cdev.name);717 sysfs_remove_group(&client->dev.kobj, &blinkm_group);718 }719 return 0;720}721 722static int blinkm_probe(struct i2c_client *client)723{724 struct blinkm_data *data;725 int err;726 727 data = devm_kzalloc(&client->dev,728 sizeof(struct blinkm_data), GFP_KERNEL);729 if (!data)730 return -ENOMEM;731 732 data->i2c_addr = 0x08;733 /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */734 data->fw_ver = 0xfe;735 /* firmware version - use fake until we read real value736 * (currently broken - BlinkM confused!)737 */738 data->script_id = 0x01;739 data->i2c_client = client;740 741 i2c_set_clientdata(client, data);742 mutex_init(&data->update_lock);743 744 /* Register sysfs hooks */745 err = sysfs_create_group(&client->dev.kobj, &blinkm_group);746 if (err < 0) {747 dev_err(&client->dev, "couldn't register sysfs group\n");748 return err;749 }750 751 if (!IS_ENABLED(CONFIG_LEDS_BLINKM_MULTICOLOR)) {752 err = register_separate_colors(client, data);753 if (err < 0)754 return err;755 } else {756 err = register_multicolor(client, data);757 if (err < 0)758 return err;759 }760 761 blinkm_init_hw(client);762 763 return 0;764}765 766static void blinkm_remove(struct i2c_client *client)767{768 struct blinkm_data *data = i2c_get_clientdata(client);769 int ret = 0;770 int i;771 772 /* make sure no workqueue entries are pending */773 for (i = 0; i < NUM_LEDS; i++)774 led_classdev_unregister(&data->blinkm_leds[i].cdev.led_cdev);775 776 /* reset rgb */777 data->next_red = 0x00;778 data->next_green = 0x00;779 data->next_blue = 0x00;780 ret = blinkm_transfer_hw(client, BLM_FADE_RGB);781 if (ret < 0)782 dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n");783 784 /* reset hsb */785 data->next_hue = 0x00;786 data->next_saturation = 0x00;787 data->next_brightness = 0x00;788 ret = blinkm_transfer_hw(client, BLM_FADE_HSB);789 if (ret < 0)790 dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n");791 792 /* red fade to off */793 data->next_red = 0xff;794 ret = blinkm_transfer_hw(client, BLM_GO_RGB);795 if (ret < 0)796 dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n");797 798 /* off */799 data->next_red = 0x00;800 ret = blinkm_transfer_hw(client, BLM_FADE_RGB);801 if (ret < 0)802 dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n");803 804 sysfs_remove_group(&client->dev.kobj, &blinkm_group);805}806 807static const struct i2c_device_id blinkm_id[] = {808 { "blinkm" },809 {}810};811 812MODULE_DEVICE_TABLE(i2c, blinkm_id);813 814 /* This is the driver that will be inserted */815static struct i2c_driver blinkm_driver = {816 .class = I2C_CLASS_HWMON,817 .driver = {818 .name = "blinkm",819 },820 .probe = blinkm_probe,821 .remove = blinkm_remove,822 .id_table = blinkm_id,823 .detect = blinkm_detect,824 .address_list = normal_i2c,825};826 827module_i2c_driver(blinkm_driver);828 829MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>");830MODULE_AUTHOR("Joseph Strauss <jstrauss@mailbox.org>");831MODULE_DESCRIPTION("BlinkM RGB LED driver");832MODULE_LICENSE("GPL");833 834