162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * leds-blinkm.c 462306a36Sopenharmony_ci * (c) Jan-Simon Möller (dl9pf@gmx.de) 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/slab.h> 962306a36Sopenharmony_ci#include <linux/jiffies.h> 1062306a36Sopenharmony_ci#include <linux/i2c.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/mutex.h> 1362306a36Sopenharmony_ci#include <linux/sysfs.h> 1462306a36Sopenharmony_ci#include <linux/printk.h> 1562306a36Sopenharmony_ci#include <linux/pm_runtime.h> 1662306a36Sopenharmony_ci#include <linux/leds.h> 1762306a36Sopenharmony_ci#include <linux/delay.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* Addresses to scan - BlinkM is on 0x09 by default*/ 2062306a36Sopenharmony_cistatic const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END }; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic int blinkm_transfer_hw(struct i2c_client *client, int cmd); 2362306a36Sopenharmony_cistatic int blinkm_test_run(struct i2c_client *client); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct blinkm_led { 2662306a36Sopenharmony_ci struct i2c_client *i2c_client; 2762306a36Sopenharmony_ci struct led_classdev led_cdev; 2862306a36Sopenharmony_ci int id; 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev) 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistruct blinkm_data { 3462306a36Sopenharmony_ci struct i2c_client *i2c_client; 3562306a36Sopenharmony_ci struct mutex update_lock; 3662306a36Sopenharmony_ci /* used for led class interface */ 3762306a36Sopenharmony_ci struct blinkm_led blinkm_leds[3]; 3862306a36Sopenharmony_ci /* used for "blinkm" sysfs interface */ 3962306a36Sopenharmony_ci u8 red; /* color red */ 4062306a36Sopenharmony_ci u8 green; /* color green */ 4162306a36Sopenharmony_ci u8 blue; /* color blue */ 4262306a36Sopenharmony_ci /* next values to use for transfer */ 4362306a36Sopenharmony_ci u8 next_red; /* color red */ 4462306a36Sopenharmony_ci u8 next_green; /* color green */ 4562306a36Sopenharmony_ci u8 next_blue; /* color blue */ 4662306a36Sopenharmony_ci /* internal use */ 4762306a36Sopenharmony_ci u8 args[7]; /* set of args for transmission */ 4862306a36Sopenharmony_ci u8 i2c_addr; /* i2c addr */ 4962306a36Sopenharmony_ci u8 fw_ver; /* firmware version */ 5062306a36Sopenharmony_ci /* used, but not from userspace */ 5162306a36Sopenharmony_ci u8 hue; /* HSB hue */ 5262306a36Sopenharmony_ci u8 saturation; /* HSB saturation */ 5362306a36Sopenharmony_ci u8 brightness; /* HSB brightness */ 5462306a36Sopenharmony_ci u8 next_hue; /* HSB hue */ 5562306a36Sopenharmony_ci u8 next_saturation; /* HSB saturation */ 5662306a36Sopenharmony_ci u8 next_brightness; /* HSB brightness */ 5762306a36Sopenharmony_ci /* currently unused / todo */ 5862306a36Sopenharmony_ci u8 fade_speed; /* fade speed 1 - 255 */ 5962306a36Sopenharmony_ci s8 time_adjust; /* time adjust -128 - 127 */ 6062306a36Sopenharmony_ci u8 fade:1; /* fade on = 1, off = 0 */ 6162306a36Sopenharmony_ci u8 rand:1; /* rand fade mode on = 1 */ 6262306a36Sopenharmony_ci u8 script_id; /* script ID */ 6362306a36Sopenharmony_ci u8 script_repeats; /* repeats of script */ 6462306a36Sopenharmony_ci u8 script_startline; /* line to start */ 6562306a36Sopenharmony_ci}; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci/* Colors */ 6862306a36Sopenharmony_ci#define RED 0 6962306a36Sopenharmony_ci#define GREEN 1 7062306a36Sopenharmony_ci#define BLUE 2 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci/* mapping command names to cmd chars - see datasheet */ 7362306a36Sopenharmony_ci#define BLM_GO_RGB 0 7462306a36Sopenharmony_ci#define BLM_FADE_RGB 1 7562306a36Sopenharmony_ci#define BLM_FADE_HSB 2 7662306a36Sopenharmony_ci#define BLM_FADE_RAND_RGB 3 7762306a36Sopenharmony_ci#define BLM_FADE_RAND_HSB 4 7862306a36Sopenharmony_ci#define BLM_PLAY_SCRIPT 5 7962306a36Sopenharmony_ci#define BLM_STOP_SCRIPT 6 8062306a36Sopenharmony_ci#define BLM_SET_FADE_SPEED 7 8162306a36Sopenharmony_ci#define BLM_SET_TIME_ADJ 8 8262306a36Sopenharmony_ci#define BLM_GET_CUR_RGB 9 8362306a36Sopenharmony_ci#define BLM_WRITE_SCRIPT_LINE 10 8462306a36Sopenharmony_ci#define BLM_READ_SCRIPT_LINE 11 8562306a36Sopenharmony_ci#define BLM_SET_SCRIPT_LR 12 /* Length & Repeats */ 8662306a36Sopenharmony_ci#define BLM_SET_ADDR 13 8762306a36Sopenharmony_ci#define BLM_GET_ADDR 14 8862306a36Sopenharmony_ci#define BLM_GET_FW_VER 15 8962306a36Sopenharmony_ci#define BLM_SET_STARTUP_PARAM 16 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci/* BlinkM Commands 9262306a36Sopenharmony_ci * as extracted out of the datasheet: 9362306a36Sopenharmony_ci * 9462306a36Sopenharmony_ci * cmdchar = command (ascii) 9562306a36Sopenharmony_ci * cmdbyte = command in hex 9662306a36Sopenharmony_ci * nr_args = number of arguments (to send) 9762306a36Sopenharmony_ci * nr_ret = number of return values (to read) 9862306a36Sopenharmony_ci * dir = direction (0 = read, 1 = write, 2 = both) 9962306a36Sopenharmony_ci * 10062306a36Sopenharmony_ci */ 10162306a36Sopenharmony_cistatic const struct { 10262306a36Sopenharmony_ci char cmdchar; 10362306a36Sopenharmony_ci u8 cmdbyte; 10462306a36Sopenharmony_ci u8 nr_args; 10562306a36Sopenharmony_ci u8 nr_ret; 10662306a36Sopenharmony_ci u8 dir:2; 10762306a36Sopenharmony_ci} blinkm_cmds[17] = { 10862306a36Sopenharmony_ci /* cmdchar, cmdbyte, nr_args, nr_ret, dir */ 10962306a36Sopenharmony_ci { 'n', 0x6e, 3, 0, 1}, 11062306a36Sopenharmony_ci { 'c', 0x63, 3, 0, 1}, 11162306a36Sopenharmony_ci { 'h', 0x68, 3, 0, 1}, 11262306a36Sopenharmony_ci { 'C', 0x43, 3, 0, 1}, 11362306a36Sopenharmony_ci { 'H', 0x48, 3, 0, 1}, 11462306a36Sopenharmony_ci { 'p', 0x70, 3, 0, 1}, 11562306a36Sopenharmony_ci { 'o', 0x6f, 0, 0, 1}, 11662306a36Sopenharmony_ci { 'f', 0x66, 1, 0, 1}, 11762306a36Sopenharmony_ci { 't', 0x74, 1, 0, 1}, 11862306a36Sopenharmony_ci { 'g', 0x67, 0, 3, 0}, 11962306a36Sopenharmony_ci { 'W', 0x57, 7, 0, 1}, 12062306a36Sopenharmony_ci { 'R', 0x52, 2, 5, 2}, 12162306a36Sopenharmony_ci { 'L', 0x4c, 3, 0, 1}, 12262306a36Sopenharmony_ci { 'A', 0x41, 4, 0, 1}, 12362306a36Sopenharmony_ci { 'a', 0x61, 0, 1, 0}, 12462306a36Sopenharmony_ci { 'Z', 0x5a, 0, 1, 0}, 12562306a36Sopenharmony_ci { 'B', 0x42, 5, 0, 1}, 12662306a36Sopenharmony_ci}; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic ssize_t show_color_common(struct device *dev, char *buf, int color) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct i2c_client *client; 13162306a36Sopenharmony_ci struct blinkm_data *data; 13262306a36Sopenharmony_ci int ret; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci client = to_i2c_client(dev); 13562306a36Sopenharmony_ci data = i2c_get_clientdata(client); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_GET_CUR_RGB); 13862306a36Sopenharmony_ci if (ret < 0) 13962306a36Sopenharmony_ci return ret; 14062306a36Sopenharmony_ci switch (color) { 14162306a36Sopenharmony_ci case RED: 14262306a36Sopenharmony_ci return sysfs_emit(buf, "%02X\n", data->red); 14362306a36Sopenharmony_ci case GREEN: 14462306a36Sopenharmony_ci return sysfs_emit(buf, "%02X\n", data->green); 14562306a36Sopenharmony_ci case BLUE: 14662306a36Sopenharmony_ci return sysfs_emit(buf, "%02X\n", data->blue); 14762306a36Sopenharmony_ci default: 14862306a36Sopenharmony_ci return -EINVAL; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci return -EINVAL; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic int store_color_common(struct device *dev, const char *buf, int color) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct i2c_client *client; 15662306a36Sopenharmony_ci struct blinkm_data *data; 15762306a36Sopenharmony_ci int ret; 15862306a36Sopenharmony_ci u8 value; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci client = to_i2c_client(dev); 16162306a36Sopenharmony_ci data = i2c_get_clientdata(client); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci ret = kstrtou8(buf, 10, &value); 16462306a36Sopenharmony_ci if (ret < 0) { 16562306a36Sopenharmony_ci dev_err(dev, "BlinkM: value too large!\n"); 16662306a36Sopenharmony_ci return ret; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci switch (color) { 17062306a36Sopenharmony_ci case RED: 17162306a36Sopenharmony_ci data->next_red = value; 17262306a36Sopenharmony_ci break; 17362306a36Sopenharmony_ci case GREEN: 17462306a36Sopenharmony_ci data->next_green = value; 17562306a36Sopenharmony_ci break; 17662306a36Sopenharmony_ci case BLUE: 17762306a36Sopenharmony_ci data->next_blue = value; 17862306a36Sopenharmony_ci break; 17962306a36Sopenharmony_ci default: 18062306a36Sopenharmony_ci return -EINVAL; 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci dev_dbg(dev, "next_red = %d, next_green = %d, next_blue = %d\n", 18462306a36Sopenharmony_ci data->next_red, data->next_green, data->next_blue); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* if mode ... */ 18762306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_GO_RGB); 18862306a36Sopenharmony_ci if (ret < 0) { 18962306a36Sopenharmony_ci dev_err(dev, "BlinkM: can't set RGB\n"); 19062306a36Sopenharmony_ci return ret; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci return 0; 19362306a36Sopenharmony_ci} 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic ssize_t red_show(struct device *dev, struct device_attribute *attr, 19662306a36Sopenharmony_ci char *buf) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci return show_color_common(dev, buf, RED); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic ssize_t red_store(struct device *dev, struct device_attribute *attr, 20262306a36Sopenharmony_ci const char *buf, size_t count) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci int ret; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci ret = store_color_common(dev, buf, RED); 20762306a36Sopenharmony_ci if (ret < 0) 20862306a36Sopenharmony_ci return ret; 20962306a36Sopenharmony_ci return count; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic DEVICE_ATTR_RW(red); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cistatic ssize_t green_show(struct device *dev, struct device_attribute *attr, 21562306a36Sopenharmony_ci char *buf) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci return show_color_common(dev, buf, GREEN); 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic ssize_t green_store(struct device *dev, struct device_attribute *attr, 22162306a36Sopenharmony_ci const char *buf, size_t count) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci int ret; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci ret = store_color_common(dev, buf, GREEN); 22762306a36Sopenharmony_ci if (ret < 0) 22862306a36Sopenharmony_ci return ret; 22962306a36Sopenharmony_ci return count; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic DEVICE_ATTR_RW(green); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic ssize_t blue_show(struct device *dev, struct device_attribute *attr, 23562306a36Sopenharmony_ci char *buf) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci return show_color_common(dev, buf, BLUE); 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic ssize_t blue_store(struct device *dev, struct device_attribute *attr, 24162306a36Sopenharmony_ci const char *buf, size_t count) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci int ret; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci ret = store_color_common(dev, buf, BLUE); 24662306a36Sopenharmony_ci if (ret < 0) 24762306a36Sopenharmony_ci return ret; 24862306a36Sopenharmony_ci return count; 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_cistatic DEVICE_ATTR_RW(blue); 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic ssize_t test_show(struct device *dev, struct device_attribute *attr, 25462306a36Sopenharmony_ci char *buf) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci return sysfs_emit(buf, 25762306a36Sopenharmony_ci "#Write into test to start test sequence!#\n"); 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cistatic ssize_t test_store(struct device *dev, struct device_attribute *attr, 26162306a36Sopenharmony_ci const char *buf, size_t count) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci struct i2c_client *client; 26562306a36Sopenharmony_ci int ret; 26662306a36Sopenharmony_ci client = to_i2c_client(dev); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci /*test */ 26962306a36Sopenharmony_ci ret = blinkm_test_run(client); 27062306a36Sopenharmony_ci if (ret < 0) 27162306a36Sopenharmony_ci return ret; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci return count; 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(test); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci/* TODO: HSB, fade, timeadj, script ... */ 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic struct attribute *blinkm_attrs[] = { 28162306a36Sopenharmony_ci &dev_attr_red.attr, 28262306a36Sopenharmony_ci &dev_attr_green.attr, 28362306a36Sopenharmony_ci &dev_attr_blue.attr, 28462306a36Sopenharmony_ci &dev_attr_test.attr, 28562306a36Sopenharmony_ci NULL, 28662306a36Sopenharmony_ci}; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic const struct attribute_group blinkm_group = { 28962306a36Sopenharmony_ci .name = "blinkm", 29062306a36Sopenharmony_ci .attrs = blinkm_attrs, 29162306a36Sopenharmony_ci}; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_cistatic int blinkm_write(struct i2c_client *client, int cmd, u8 *arg) 29462306a36Sopenharmony_ci{ 29562306a36Sopenharmony_ci int result; 29662306a36Sopenharmony_ci int i; 29762306a36Sopenharmony_ci int arglen = blinkm_cmds[cmd].nr_args; 29862306a36Sopenharmony_ci /* write out cmd to blinkm - always / default step */ 29962306a36Sopenharmony_ci result = i2c_smbus_write_byte(client, blinkm_cmds[cmd].cmdbyte); 30062306a36Sopenharmony_ci if (result < 0) 30162306a36Sopenharmony_ci return result; 30262306a36Sopenharmony_ci /* no args to write out */ 30362306a36Sopenharmony_ci if (arglen == 0) 30462306a36Sopenharmony_ci return 0; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci for (i = 0; i < arglen; i++) { 30762306a36Sopenharmony_ci /* repeat for arglen */ 30862306a36Sopenharmony_ci result = i2c_smbus_write_byte(client, arg[i]); 30962306a36Sopenharmony_ci if (result < 0) 31062306a36Sopenharmony_ci return result; 31162306a36Sopenharmony_ci } 31262306a36Sopenharmony_ci return 0; 31362306a36Sopenharmony_ci} 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_cistatic int blinkm_read(struct i2c_client *client, int cmd, u8 *arg) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci int result; 31862306a36Sopenharmony_ci int i; 31962306a36Sopenharmony_ci int retlen = blinkm_cmds[cmd].nr_ret; 32062306a36Sopenharmony_ci for (i = 0; i < retlen; i++) { 32162306a36Sopenharmony_ci /* repeat for retlen */ 32262306a36Sopenharmony_ci result = i2c_smbus_read_byte(client); 32362306a36Sopenharmony_ci if (result < 0) 32462306a36Sopenharmony_ci return result; 32562306a36Sopenharmony_ci arg[i] = result; 32662306a36Sopenharmony_ci } 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci return 0; 32962306a36Sopenharmony_ci} 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_cistatic int blinkm_transfer_hw(struct i2c_client *client, int cmd) 33262306a36Sopenharmony_ci{ 33362306a36Sopenharmony_ci /* the protocol is simple but non-standard: 33462306a36Sopenharmony_ci * e.g. cmd 'g' (= 0x67) for "get device address" 33562306a36Sopenharmony_ci * - which defaults to 0x09 - would be the sequence: 33662306a36Sopenharmony_ci * a) write 0x67 to the device (byte write) 33762306a36Sopenharmony_ci * b) read the value (0x09) back right after (byte read) 33862306a36Sopenharmony_ci * 33962306a36Sopenharmony_ci * Watch out for "unfinished" sequences (i.e. not enough reads 34062306a36Sopenharmony_ci * or writes after a command. It will make the blinkM misbehave. 34162306a36Sopenharmony_ci * Sequence is key here. 34262306a36Sopenharmony_ci */ 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci /* args / return are in private data struct */ 34562306a36Sopenharmony_ci struct blinkm_data *data = i2c_get_clientdata(client); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci /* We start hardware transfers which are not to be 34862306a36Sopenharmony_ci * mixed with other commands. Aquire a lock now. */ 34962306a36Sopenharmony_ci if (mutex_lock_interruptible(&data->update_lock) < 0) 35062306a36Sopenharmony_ci return -EAGAIN; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci /* switch cmd - usually write before reads */ 35362306a36Sopenharmony_ci switch (cmd) { 35462306a36Sopenharmony_ci case BLM_FADE_RAND_RGB: 35562306a36Sopenharmony_ci case BLM_GO_RGB: 35662306a36Sopenharmony_ci case BLM_FADE_RGB: 35762306a36Sopenharmony_ci data->args[0] = data->next_red; 35862306a36Sopenharmony_ci data->args[1] = data->next_green; 35962306a36Sopenharmony_ci data->args[2] = data->next_blue; 36062306a36Sopenharmony_ci blinkm_write(client, cmd, data->args); 36162306a36Sopenharmony_ci data->red = data->args[0]; 36262306a36Sopenharmony_ci data->green = data->args[1]; 36362306a36Sopenharmony_ci data->blue = data->args[2]; 36462306a36Sopenharmony_ci break; 36562306a36Sopenharmony_ci case BLM_FADE_HSB: 36662306a36Sopenharmony_ci case BLM_FADE_RAND_HSB: 36762306a36Sopenharmony_ci data->args[0] = data->next_hue; 36862306a36Sopenharmony_ci data->args[1] = data->next_saturation; 36962306a36Sopenharmony_ci data->args[2] = data->next_brightness; 37062306a36Sopenharmony_ci blinkm_write(client, cmd, data->args); 37162306a36Sopenharmony_ci data->hue = data->next_hue; 37262306a36Sopenharmony_ci data->saturation = data->next_saturation; 37362306a36Sopenharmony_ci data->brightness = data->next_brightness; 37462306a36Sopenharmony_ci break; 37562306a36Sopenharmony_ci case BLM_PLAY_SCRIPT: 37662306a36Sopenharmony_ci data->args[0] = data->script_id; 37762306a36Sopenharmony_ci data->args[1] = data->script_repeats; 37862306a36Sopenharmony_ci data->args[2] = data->script_startline; 37962306a36Sopenharmony_ci blinkm_write(client, cmd, data->args); 38062306a36Sopenharmony_ci break; 38162306a36Sopenharmony_ci case BLM_STOP_SCRIPT: 38262306a36Sopenharmony_ci blinkm_write(client, cmd, NULL); 38362306a36Sopenharmony_ci break; 38462306a36Sopenharmony_ci case BLM_GET_CUR_RGB: 38562306a36Sopenharmony_ci data->args[0] = data->red; 38662306a36Sopenharmony_ci data->args[1] = data->green; 38762306a36Sopenharmony_ci data->args[2] = data->blue; 38862306a36Sopenharmony_ci blinkm_write(client, cmd, NULL); 38962306a36Sopenharmony_ci blinkm_read(client, cmd, data->args); 39062306a36Sopenharmony_ci data->red = data->args[0]; 39162306a36Sopenharmony_ci data->green = data->args[1]; 39262306a36Sopenharmony_ci data->blue = data->args[2]; 39362306a36Sopenharmony_ci break; 39462306a36Sopenharmony_ci case BLM_GET_ADDR: 39562306a36Sopenharmony_ci data->args[0] = data->i2c_addr; 39662306a36Sopenharmony_ci blinkm_write(client, cmd, NULL); 39762306a36Sopenharmony_ci blinkm_read(client, cmd, data->args); 39862306a36Sopenharmony_ci data->i2c_addr = data->args[0]; 39962306a36Sopenharmony_ci break; 40062306a36Sopenharmony_ci case BLM_SET_TIME_ADJ: 40162306a36Sopenharmony_ci case BLM_SET_FADE_SPEED: 40262306a36Sopenharmony_ci case BLM_READ_SCRIPT_LINE: 40362306a36Sopenharmony_ci case BLM_WRITE_SCRIPT_LINE: 40462306a36Sopenharmony_ci case BLM_SET_SCRIPT_LR: 40562306a36Sopenharmony_ci case BLM_SET_ADDR: 40662306a36Sopenharmony_ci case BLM_GET_FW_VER: 40762306a36Sopenharmony_ci case BLM_SET_STARTUP_PARAM: 40862306a36Sopenharmony_ci dev_err(&client->dev, 40962306a36Sopenharmony_ci "BlinkM: cmd %d not implemented yet.\n", cmd); 41062306a36Sopenharmony_ci break; 41162306a36Sopenharmony_ci default: 41262306a36Sopenharmony_ci dev_err(&client->dev, "BlinkM: unknown command %d\n", cmd); 41362306a36Sopenharmony_ci mutex_unlock(&data->update_lock); 41462306a36Sopenharmony_ci return -EINVAL; 41562306a36Sopenharmony_ci } /* end switch(cmd) */ 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci /* transfers done, unlock */ 41862306a36Sopenharmony_ci mutex_unlock(&data->update_lock); 41962306a36Sopenharmony_ci return 0; 42062306a36Sopenharmony_ci} 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_cistatic int blinkm_led_common_set(struct led_classdev *led_cdev, 42362306a36Sopenharmony_ci enum led_brightness value, int color) 42462306a36Sopenharmony_ci{ 42562306a36Sopenharmony_ci /* led_brightness is 0, 127 or 255 - we just use it here as-is */ 42662306a36Sopenharmony_ci struct blinkm_led *led = cdev_to_blmled(led_cdev); 42762306a36Sopenharmony_ci struct blinkm_data *data = i2c_get_clientdata(led->i2c_client); 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci switch (color) { 43062306a36Sopenharmony_ci case RED: 43162306a36Sopenharmony_ci /* bail out if there's no change */ 43262306a36Sopenharmony_ci if (data->next_red == (u8) value) 43362306a36Sopenharmony_ci return 0; 43462306a36Sopenharmony_ci data->next_red = (u8) value; 43562306a36Sopenharmony_ci break; 43662306a36Sopenharmony_ci case GREEN: 43762306a36Sopenharmony_ci /* bail out if there's no change */ 43862306a36Sopenharmony_ci if (data->next_green == (u8) value) 43962306a36Sopenharmony_ci return 0; 44062306a36Sopenharmony_ci data->next_green = (u8) value; 44162306a36Sopenharmony_ci break; 44262306a36Sopenharmony_ci case BLUE: 44362306a36Sopenharmony_ci /* bail out if there's no change */ 44462306a36Sopenharmony_ci if (data->next_blue == (u8) value) 44562306a36Sopenharmony_ci return 0; 44662306a36Sopenharmony_ci data->next_blue = (u8) value; 44762306a36Sopenharmony_ci break; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci default: 45062306a36Sopenharmony_ci dev_err(&led->i2c_client->dev, "BlinkM: unknown color.\n"); 45162306a36Sopenharmony_ci return -EINVAL; 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB); 45562306a36Sopenharmony_ci dev_dbg(&led->i2c_client->dev, 45662306a36Sopenharmony_ci "# DONE # next_red = %d, next_green = %d," 45762306a36Sopenharmony_ci " next_blue = %d\n", 45862306a36Sopenharmony_ci data->next_red, data->next_green, 45962306a36Sopenharmony_ci data->next_blue); 46062306a36Sopenharmony_ci return 0; 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_cistatic int blinkm_led_red_set(struct led_classdev *led_cdev, 46462306a36Sopenharmony_ci enum led_brightness value) 46562306a36Sopenharmony_ci{ 46662306a36Sopenharmony_ci return blinkm_led_common_set(led_cdev, value, RED); 46762306a36Sopenharmony_ci} 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_cistatic int blinkm_led_green_set(struct led_classdev *led_cdev, 47062306a36Sopenharmony_ci enum led_brightness value) 47162306a36Sopenharmony_ci{ 47262306a36Sopenharmony_ci return blinkm_led_common_set(led_cdev, value, GREEN); 47362306a36Sopenharmony_ci} 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_cistatic int blinkm_led_blue_set(struct led_classdev *led_cdev, 47662306a36Sopenharmony_ci enum led_brightness value) 47762306a36Sopenharmony_ci{ 47862306a36Sopenharmony_ci return blinkm_led_common_set(led_cdev, value, BLUE); 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_cistatic void blinkm_init_hw(struct i2c_client *client) 48262306a36Sopenharmony_ci{ 48362306a36Sopenharmony_ci blinkm_transfer_hw(client, BLM_STOP_SCRIPT); 48462306a36Sopenharmony_ci blinkm_transfer_hw(client, BLM_GO_RGB); 48562306a36Sopenharmony_ci} 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_cistatic int blinkm_test_run(struct i2c_client *client) 48862306a36Sopenharmony_ci{ 48962306a36Sopenharmony_ci int ret; 49062306a36Sopenharmony_ci struct blinkm_data *data = i2c_get_clientdata(client); 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci data->next_red = 0x01; 49362306a36Sopenharmony_ci data->next_green = 0x05; 49462306a36Sopenharmony_ci data->next_blue = 0x10; 49562306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_GO_RGB); 49662306a36Sopenharmony_ci if (ret < 0) 49762306a36Sopenharmony_ci return ret; 49862306a36Sopenharmony_ci msleep(2000); 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci data->next_red = 0x25; 50162306a36Sopenharmony_ci data->next_green = 0x10; 50262306a36Sopenharmony_ci data->next_blue = 0x31; 50362306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_FADE_RGB); 50462306a36Sopenharmony_ci if (ret < 0) 50562306a36Sopenharmony_ci return ret; 50662306a36Sopenharmony_ci msleep(2000); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci data->next_hue = 0x50; 50962306a36Sopenharmony_ci data->next_saturation = 0x10; 51062306a36Sopenharmony_ci data->next_brightness = 0x20; 51162306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_FADE_HSB); 51262306a36Sopenharmony_ci if (ret < 0) 51362306a36Sopenharmony_ci return ret; 51462306a36Sopenharmony_ci msleep(2000); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci return 0; 51762306a36Sopenharmony_ci} 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci/* Return 0 if detection is successful, -ENODEV otherwise */ 52062306a36Sopenharmony_cistatic int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info) 52162306a36Sopenharmony_ci{ 52262306a36Sopenharmony_ci struct i2c_adapter *adapter = client->adapter; 52362306a36Sopenharmony_ci int ret; 52462306a36Sopenharmony_ci int count = 99; 52562306a36Sopenharmony_ci u8 tmpargs[7]; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA 52862306a36Sopenharmony_ci | I2C_FUNC_SMBUS_WORD_DATA 52962306a36Sopenharmony_ci | I2C_FUNC_SMBUS_WRITE_BYTE)) 53062306a36Sopenharmony_ci return -ENODEV; 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci /* Now, we do the remaining detection. Simple for now. */ 53362306a36Sopenharmony_ci /* We might need more guards to protect other i2c slaves */ 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci /* make sure the blinkM is balanced (read/writes) */ 53662306a36Sopenharmony_ci while (count > 0) { 53762306a36Sopenharmony_ci ret = blinkm_write(client, BLM_GET_ADDR, NULL); 53862306a36Sopenharmony_ci if (ret) 53962306a36Sopenharmony_ci return ret; 54062306a36Sopenharmony_ci usleep_range(5000, 10000); 54162306a36Sopenharmony_ci ret = blinkm_read(client, BLM_GET_ADDR, tmpargs); 54262306a36Sopenharmony_ci if (ret) 54362306a36Sopenharmony_ci return ret; 54462306a36Sopenharmony_ci usleep_range(5000, 10000); 54562306a36Sopenharmony_ci if (tmpargs[0] == 0x09) 54662306a36Sopenharmony_ci count = 0; 54762306a36Sopenharmony_ci count--; 54862306a36Sopenharmony_ci } 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci /* Step 1: Read BlinkM address back - cmd_char 'a' */ 55162306a36Sopenharmony_ci ret = blinkm_write(client, BLM_GET_ADDR, NULL); 55262306a36Sopenharmony_ci if (ret < 0) 55362306a36Sopenharmony_ci return ret; 55462306a36Sopenharmony_ci usleep_range(20000, 30000); /* allow a small delay */ 55562306a36Sopenharmony_ci ret = blinkm_read(client, BLM_GET_ADDR, tmpargs); 55662306a36Sopenharmony_ci if (ret < 0) 55762306a36Sopenharmony_ci return ret; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci if (tmpargs[0] != 0x09) { 56062306a36Sopenharmony_ci dev_err(&client->dev, "enodev DEV ADDR = 0x%02X\n", tmpargs[0]); 56162306a36Sopenharmony_ci return -ENODEV; 56262306a36Sopenharmony_ci } 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci strscpy(info->type, "blinkm", I2C_NAME_SIZE); 56562306a36Sopenharmony_ci return 0; 56662306a36Sopenharmony_ci} 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_cistatic int blinkm_probe(struct i2c_client *client) 56962306a36Sopenharmony_ci{ 57062306a36Sopenharmony_ci struct blinkm_data *data; 57162306a36Sopenharmony_ci struct blinkm_led *led[3]; 57262306a36Sopenharmony_ci int err, i; 57362306a36Sopenharmony_ci char blinkm_led_name[28]; 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci data = devm_kzalloc(&client->dev, 57662306a36Sopenharmony_ci sizeof(struct blinkm_data), GFP_KERNEL); 57762306a36Sopenharmony_ci if (!data) { 57862306a36Sopenharmony_ci err = -ENOMEM; 57962306a36Sopenharmony_ci goto exit; 58062306a36Sopenharmony_ci } 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci data->i2c_addr = 0x08; 58362306a36Sopenharmony_ci /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */ 58462306a36Sopenharmony_ci data->fw_ver = 0xfe; 58562306a36Sopenharmony_ci /* firmware version - use fake until we read real value 58662306a36Sopenharmony_ci * (currently broken - BlinkM confused!) */ 58762306a36Sopenharmony_ci data->script_id = 0x01; 58862306a36Sopenharmony_ci data->i2c_client = client; 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci i2c_set_clientdata(client, data); 59162306a36Sopenharmony_ci mutex_init(&data->update_lock); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci /* Register sysfs hooks */ 59462306a36Sopenharmony_ci err = sysfs_create_group(&client->dev.kobj, &blinkm_group); 59562306a36Sopenharmony_ci if (err < 0) { 59662306a36Sopenharmony_ci dev_err(&client->dev, "couldn't register sysfs group\n"); 59762306a36Sopenharmony_ci goto exit; 59862306a36Sopenharmony_ci } 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci for (i = 0; i < 3; i++) { 60162306a36Sopenharmony_ci /* RED = 0, GREEN = 1, BLUE = 2 */ 60262306a36Sopenharmony_ci led[i] = &data->blinkm_leds[i]; 60362306a36Sopenharmony_ci led[i]->i2c_client = client; 60462306a36Sopenharmony_ci led[i]->id = i; 60562306a36Sopenharmony_ci led[i]->led_cdev.max_brightness = 255; 60662306a36Sopenharmony_ci led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME; 60762306a36Sopenharmony_ci switch (i) { 60862306a36Sopenharmony_ci case RED: 60962306a36Sopenharmony_ci snprintf(blinkm_led_name, sizeof(blinkm_led_name), 61062306a36Sopenharmony_ci "blinkm-%d-%d-red", 61162306a36Sopenharmony_ci client->adapter->nr, 61262306a36Sopenharmony_ci client->addr); 61362306a36Sopenharmony_ci led[i]->led_cdev.name = blinkm_led_name; 61462306a36Sopenharmony_ci led[i]->led_cdev.brightness_set_blocking = 61562306a36Sopenharmony_ci blinkm_led_red_set; 61662306a36Sopenharmony_ci err = led_classdev_register(&client->dev, 61762306a36Sopenharmony_ci &led[i]->led_cdev); 61862306a36Sopenharmony_ci if (err < 0) { 61962306a36Sopenharmony_ci dev_err(&client->dev, 62062306a36Sopenharmony_ci "couldn't register LED %s\n", 62162306a36Sopenharmony_ci led[i]->led_cdev.name); 62262306a36Sopenharmony_ci goto failred; 62362306a36Sopenharmony_ci } 62462306a36Sopenharmony_ci break; 62562306a36Sopenharmony_ci case GREEN: 62662306a36Sopenharmony_ci snprintf(blinkm_led_name, sizeof(blinkm_led_name), 62762306a36Sopenharmony_ci "blinkm-%d-%d-green", 62862306a36Sopenharmony_ci client->adapter->nr, 62962306a36Sopenharmony_ci client->addr); 63062306a36Sopenharmony_ci led[i]->led_cdev.name = blinkm_led_name; 63162306a36Sopenharmony_ci led[i]->led_cdev.brightness_set_blocking = 63262306a36Sopenharmony_ci blinkm_led_green_set; 63362306a36Sopenharmony_ci err = led_classdev_register(&client->dev, 63462306a36Sopenharmony_ci &led[i]->led_cdev); 63562306a36Sopenharmony_ci if (err < 0) { 63662306a36Sopenharmony_ci dev_err(&client->dev, 63762306a36Sopenharmony_ci "couldn't register LED %s\n", 63862306a36Sopenharmony_ci led[i]->led_cdev.name); 63962306a36Sopenharmony_ci goto failgreen; 64062306a36Sopenharmony_ci } 64162306a36Sopenharmony_ci break; 64262306a36Sopenharmony_ci case BLUE: 64362306a36Sopenharmony_ci snprintf(blinkm_led_name, sizeof(blinkm_led_name), 64462306a36Sopenharmony_ci "blinkm-%d-%d-blue", 64562306a36Sopenharmony_ci client->adapter->nr, 64662306a36Sopenharmony_ci client->addr); 64762306a36Sopenharmony_ci led[i]->led_cdev.name = blinkm_led_name; 64862306a36Sopenharmony_ci led[i]->led_cdev.brightness_set_blocking = 64962306a36Sopenharmony_ci blinkm_led_blue_set; 65062306a36Sopenharmony_ci err = led_classdev_register(&client->dev, 65162306a36Sopenharmony_ci &led[i]->led_cdev); 65262306a36Sopenharmony_ci if (err < 0) { 65362306a36Sopenharmony_ci dev_err(&client->dev, 65462306a36Sopenharmony_ci "couldn't register LED %s\n", 65562306a36Sopenharmony_ci led[i]->led_cdev.name); 65662306a36Sopenharmony_ci goto failblue; 65762306a36Sopenharmony_ci } 65862306a36Sopenharmony_ci break; 65962306a36Sopenharmony_ci } /* end switch */ 66062306a36Sopenharmony_ci } /* end for */ 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci /* Initialize the blinkm */ 66362306a36Sopenharmony_ci blinkm_init_hw(client); 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci return 0; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_cifailblue: 66862306a36Sopenharmony_ci led_classdev_unregister(&led[GREEN]->led_cdev); 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_cifailgreen: 67162306a36Sopenharmony_ci led_classdev_unregister(&led[RED]->led_cdev); 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_cifailred: 67462306a36Sopenharmony_ci sysfs_remove_group(&client->dev.kobj, &blinkm_group); 67562306a36Sopenharmony_ciexit: 67662306a36Sopenharmony_ci return err; 67762306a36Sopenharmony_ci} 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_cistatic void blinkm_remove(struct i2c_client *client) 68062306a36Sopenharmony_ci{ 68162306a36Sopenharmony_ci struct blinkm_data *data = i2c_get_clientdata(client); 68262306a36Sopenharmony_ci int ret = 0; 68362306a36Sopenharmony_ci int i; 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci /* make sure no workqueue entries are pending */ 68662306a36Sopenharmony_ci for (i = 0; i < 3; i++) 68762306a36Sopenharmony_ci led_classdev_unregister(&data->blinkm_leds[i].led_cdev); 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci /* reset rgb */ 69062306a36Sopenharmony_ci data->next_red = 0x00; 69162306a36Sopenharmony_ci data->next_green = 0x00; 69262306a36Sopenharmony_ci data->next_blue = 0x00; 69362306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_FADE_RGB); 69462306a36Sopenharmony_ci if (ret < 0) 69562306a36Sopenharmony_ci dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci /* reset hsb */ 69862306a36Sopenharmony_ci data->next_hue = 0x00; 69962306a36Sopenharmony_ci data->next_saturation = 0x00; 70062306a36Sopenharmony_ci data->next_brightness = 0x00; 70162306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_FADE_HSB); 70262306a36Sopenharmony_ci if (ret < 0) 70362306a36Sopenharmony_ci dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci /* red fade to off */ 70662306a36Sopenharmony_ci data->next_red = 0xff; 70762306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_GO_RGB); 70862306a36Sopenharmony_ci if (ret < 0) 70962306a36Sopenharmony_ci dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci /* off */ 71262306a36Sopenharmony_ci data->next_red = 0x00; 71362306a36Sopenharmony_ci ret = blinkm_transfer_hw(client, BLM_FADE_RGB); 71462306a36Sopenharmony_ci if (ret < 0) 71562306a36Sopenharmony_ci dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci sysfs_remove_group(&client->dev.kobj, &blinkm_group); 71862306a36Sopenharmony_ci} 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_cistatic const struct i2c_device_id blinkm_id[] = { 72162306a36Sopenharmony_ci {"blinkm", 0}, 72262306a36Sopenharmony_ci {} 72362306a36Sopenharmony_ci}; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, blinkm_id); 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci /* This is the driver that will be inserted */ 72862306a36Sopenharmony_cistatic struct i2c_driver blinkm_driver = { 72962306a36Sopenharmony_ci .class = I2C_CLASS_HWMON, 73062306a36Sopenharmony_ci .driver = { 73162306a36Sopenharmony_ci .name = "blinkm", 73262306a36Sopenharmony_ci }, 73362306a36Sopenharmony_ci .probe = blinkm_probe, 73462306a36Sopenharmony_ci .remove = blinkm_remove, 73562306a36Sopenharmony_ci .id_table = blinkm_id, 73662306a36Sopenharmony_ci .detect = blinkm_detect, 73762306a36Sopenharmony_ci .address_list = normal_i2c, 73862306a36Sopenharmony_ci}; 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_cimodule_i2c_driver(blinkm_driver); 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ciMODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>"); 74362306a36Sopenharmony_ciMODULE_DESCRIPTION("BlinkM RGB LED driver"); 74462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 74562306a36Sopenharmony_ci 746