From: Imre Kaloz Date: Thu, 15 Jan 2015 13:59:01 +0000 (+0000) Subject: mvebu: add the new tlc59116 driver and migrate the Mamba to it X-Git-Tag: reboot~4699 X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=9a8bf84879ec239200b8ea13292a19d6b7a053e2;p=openwrt%2Fstaging%2Fblogic.git mvebu: add the new tlc59116 driver and migrate the Mamba to it Signed-off-by: Imre Kaloz SVN-Revision: 43975 --- diff --git a/target/linux/mvebu/files/arch/arm/boot/dts/armada-xp-mamba.dts b/target/linux/mvebu/files/arch/arm/boot/dts/armada-xp-mamba.dts index b544389031f5..8593fde63b5f 100644 --- a/target/linux/mvebu/files/arch/arm/boot/dts/armada-xp-mamba.dts +++ b/target/linux/mvebu/files/arch/arm/boot/dts/armada-xp-mamba.dts @@ -153,10 +153,61 @@ }; tlc59116@68 { + #address-cells = <1>; + #size-cells = <0>; #gpio-cells = <2>; - compatible = "gpio,tlc59116"; + compatible = "ti,tlc59116"; reg = <0x68>; - gpio-controller; + + wan_amber@0 { + label = "mamba:amber:wan"; + reg = <0x0>; + }; + + wan_white@1 { + label = "mamba:white:wan"; + reg = <0x1>; + }; + + wlan_2g@2 { + label = "mamba:white:wlan_2g"; + reg = <0x2>; + }; + + wlan_5g@3 { + label = "mamba:white:wlan_5g"; + reg = <0x3>; + }; + + esata@4 { + label = "mamba:white:esata"; + reg = <0x4>; + }; + + usb2@5 { + label = "mamba:white:usb2"; + reg = <0x5>; + }; + + usb3_1@6 { + label = "mamba:white:usb3_1"; + reg = <0x6>; + }; + + usb3_2@7 { + label = "mamba:white:usb3_2"; + reg = <0x7>; + }; + + wps_white@8 { + label = "mamba:white:wps"; + reg = <0x8>; + }; + + wps_amber@9 { + label = "mamba:amber:wps"; + reg = <0x9>; + }; }; }; diff --git a/target/linux/mvebu/patches-3.18/003-add_leds_tlc59116_driver.patch b/target/linux/mvebu/patches-3.18/003-add_leds_tlc59116_driver.patch deleted file mode 100644 index cb9f1572d103..000000000000 --- a/target/linux/mvebu/patches-3.18/003-add_leds_tlc59116_driver.patch +++ /dev/null @@ -1,529 +0,0 @@ ---- a/drivers/leds/Kconfig -+++ b/drivers/leds/Kconfig -@@ -505,6 +505,15 @@ config LEDS_VERSATILE - This option enabled support for the LEDs on the ARM Versatile - and RealView boards. Say Y to enabled these. - -+config LEDS_TLC59116 -+ tristate "LED driver for TLC59116F dimmer" -+ depends on LEDS_CLASS -+ depends on I2C -+ help -+ This option enables support for Texas Instruments TLC59116F -+ LED controller. It is generally only useful -+ as a platform driver -+ - comment "LED Triggers" - source "drivers/leds/trigger/Kconfig" - ---- a/drivers/leds/Makefile -+++ b/drivers/leds/Makefile -@@ -56,6 +56,7 @@ obj-$(CONFIG_LEDS_BLINKM) += leds-blink - obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o - obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o - obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o -+obj-$(CONFIG_LEDS_TLC59116) += leds-tlc59116.o - - # LED SPI Drivers - obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o ---- /dev/null -+++ b/drivers/leds/leds-tlc59116.c -@@ -0,0 +1,498 @@ -+/* -+ * Copyright 2014 Belkin Inc. -+ * -+ * Author: Belkin Inc. -+ * -+ * This file is subject to the terms and conditions of version 2 of -+ * the GNU General Public License. See the file COPYING in the main -+ * directory of this archive for more details. -+ * -+ * LED driver for various TLC59116 I2C LED drivers -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* LED select registers determine the source that drives LED outputs */ -+#define TLC59116_LED_OFF 0x0 /* Output LOW */ -+#define TLC59116_LED_ON 0x1 /* Output HI-Z */ -+#define TLC59116_DIM 0x2 /* Dimming */ -+#define TLC59116_BLINK 0x3 /* Blinking */ -+ -+#define TLC59116_PINS 16 -+#define TLC59116_REG_MODE1 0x00 /* Mode register 0 */ -+#define MODE1_RESPON_ADDR_MASK 0xF0 -+#define MODE1_NORMAL_MODE (0 << 4) -+#define MODE1_SPEED_MODE (1 << 4) -+ -+#define TLC59116_REG_MODE2 0x01 /* Mode register 1 */ -+#define MODE2_DIM (0 << 5) -+#define MODE2_BLINK (1 << 5) -+#define MODE2_OCH_STOP (0 << 3) -+#define MODE2_OCH_ACK (1 << 3) -+ -+#define TLC59116_REG_PWM0 0x02 -+#define TLC59116_REG_PWM1 0x03 -+#define TLC59116_REG_PWM2 0x04 -+#define TLC59116_REG_PWM3 0x05 -+#define TLC59116_REG_PWM4 0x06 -+#define TLC59116_REG_PWM5 0x07 -+#define TLC59116_REG_PWM6 0x08 -+#define TLC59116_REG_PWM7 0x09 -+#define TLC59116_REG_PWM8 0x0a -+#define TLC59116_REG_PWM9 0x0b -+#define TLC59116_REG_PWM10 0x0c -+#define TLC59116_REG_PWM11 0x0d -+#define TLC59116_REG_PWM12 0x0e -+#define TLC59116_REG_PWM13 0x0f -+#define TLC59116_REG_PWM14 0x10 -+#define TLC59116_REG_PWM15 0x01 -+ -+#define TLC59116_REG_GRPPWM 0x12 -+#define TLC59116_REG_GRPFREQ 0x13 -+ -+#define TLC59116_PERIOD_MIN 41 /* 41ms */ -+#define TLC59116_PERIOD_MAX 10730 /* 10.73s */ -+ -+#define TLC59116_REG_LEDOUT0 0x14 /* LED [3:0] driver output state registers */ -+#define TLC59116_REG_LEDOUT1 0x15 /* LED [7:4] driver output state registers */ -+#define TLC59116_REG_LEDOUT2 0x16 /* LED [11:8] driver output state registers */ -+#define TLC59116_REG_LEDOUT3 0x17 /* LED [15:12] driver output state registers */ -+ -+#define GPIO0_MASK 0x3 -+ -+#define DEBUG 0 -+ -+#if DEBUG > 1 -+#define led_dbg(fmt, arg...) printk(KERN_DEBUG "tlc59116:%s " fmt "\n", __func__ , ## arg) -+#else -+#define led_dbg(fmt, arg...) -+#endif -+ -+ -+#if DEBUG > 0 -+#define led_info(fmt, arg...) printk("tlc59116:%s " fmt "\n", __func__ , ## arg) -+#else -+#define led_info(fmt, arg...) -+#endif -+ -+enum tlc59116_type { -+ tlc59116, -+}; -+ -+struct tlc59116_chipdef { -+ int bits; -+ u8 slv_addr; /* 7-bit slave address mask */ -+ int slv_addr_shift; /* Number of bits to ignore */ -+}; -+ -+static struct tlc59116_chipdef tlc59116_chipdefs[] = { -+ [tlc59116] = { -+ .bits = 16, -+ .slv_addr = /* 1100xxx */ 0x68, -+ .slv_addr_shift = 3, -+ }, -+}; -+ -+static const struct i2c_device_id tlc59116_id[] = { -+ { "tlc59116", tlc59116 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(i2c, tlc59116_id); -+ -+struct tlc59116_led { -+ struct tlc59116_chipdef *chipdef; -+ struct i2c_client *client; -+ struct work_struct work; -+ spinlock_t lock; -+ enum led_brightness brightness; -+ struct led_classdev led_cdev; -+ int led_num; /* 0 .. 8 potentially */ -+ char name[32]; -+}; -+ -+//#define DUMP_REGS -+ -+#ifdef DUMP_REGS -+void dump_regs(struct i2c_client *client) -+{ -+ int i, j = 0; -+ u8 data; -+ printk ("\n-------------------------------------\n"); -+ for (i = 0; i< 0x20; i++) -+ { -+ data = i2c_smbus_read_byte_data(client, i); -+ printk ("[0x%x] = 0x%x ",i, data); -+ j++; -+ if (j == 5) -+ { -+ printk ("\n"); -+ j = 0; -+ } -+ } -+ printk ("\n"); -+} -+#endif -+ -+static int tlc59116_set_mode(struct i2c_client *client, uint8_t mode) -+{ -+ uint8_t val = 0; -+ -+ if ((mode != MODE2_DIM) && (mode != MODE2_BLINK)) -+ mode = MODE2_DIM; -+ -+ /* Configure MODE1 register */ -+ val &= 0x0; -+ val &= MODE1_RESPON_ADDR_MASK; -+ val |= MODE1_NORMAL_MODE; -+ i2c_smbus_write_byte_data(client, TLC59116_REG_MODE1, val); -+ -+ /* Configure MODE2 Reg */ -+ val &= 0x00; -+ val |= MODE2_OCH_STOP; -+ -+ val |= mode; -+ -+ i2c_smbus_write_byte_data(client, TLC59116_REG_MODE2, val); -+ mdelay(100); -+ -+ return 0; -+} -+ -+static int tlc59116_set_gpio_act(struct i2c_client *client, u8 gpio_no, u8 act_mode) -+{ -+ char data, addr = 0, i; -+ -+ if ((gpio_no >= 0) && (gpio_no < 4)) -+ addr = TLC59116_REG_LEDOUT0; -+ else if ((gpio_no >= 4) && (gpio_no < 8)) -+ addr = TLC59116_REG_LEDOUT1; -+ else if ((gpio_no >= 8) && (gpio_no < 12)) -+ addr = TLC59116_REG_LEDOUT2; -+ else if ((gpio_no >=12 ) && (gpio_no < 16)) -+ addr = TLC59116_REG_LEDOUT3; -+ -+ data = i2c_smbus_read_byte_data(client, addr); -+ -+ i = (gpio_no % 4) * 2; -+ -+ data &= ~(GPIO0_MASK << i); -+ act_mode = act_mode << i; -+ data |= act_mode; -+ -+ if(i2c_smbus_write_byte_data(client, addr, data) != 0) { -+ return -1; -+ } -+ return 0; -+} -+ -+static int tlc59116_set_gpio(struct i2c_client *client, uint8_t gpio_no, uint8_t val) -+{ -+ val &= 0x03; -+ tlc59116_set_gpio_act(client, gpio_no, val); -+#ifdef DUMP_REGS -+ dump_regs(client); -+#endif -+ return 0; -+} -+ -+static int tlc59116_get_gpio(struct i2c_client *client, uint8_t gpio_no) -+{ -+ uint8_t val, reg, data; -+ -+ led_dbg("gpio = %d\n", gpio_no); -+ reg = TLC59116_REG_LEDOUT0; -+ -+ if ((gpio_no >= 0) && (gpio_no < 4)) -+ reg = TLC59116_REG_LEDOUT0; -+ else if ((gpio_no >= 4) && (gpio_no < 8)) { -+ reg = TLC59116_REG_LEDOUT1; -+ gpio_no = gpio_no - 4; -+ } -+ else if ((gpio_no >= 8) && (gpio_no < 12)) { -+ reg = TLC59116_REG_LEDOUT2; -+ gpio_no = gpio_no - 8; -+ } -+ else if ((gpio_no >=12 ) && (gpio_no < 16)) { -+ reg = TLC59116_REG_LEDOUT3; -+ gpio_no = gpio_no - 12; -+ } -+ -+ val = i2c_smbus_read_byte_data(client, reg); -+ -+ data = (val >> (gpio_no * 2)) & 0x03; -+ return data; -+} -+ -+/* -+ * gpio_no [0..7] -+ * duty_cycle [0..99]% -+ * -+ * */ -+static int tlc59116_individual_brighness_control(struct i2c_client *client, uint8_t gpio_no, uint8_t brightness) -+{ -+ uint8_t pwm; -+ -+ pwm = gpio_no + TLC59116_REG_PWM0; -+ i2c_smbus_write_byte_data(client, pwm, brightness); -+ -+ return 0; -+} -+ -+static void tlc59116_led_work(struct work_struct *work) -+{ -+ struct tlc59116_led *tlc59116; -+ -+ tlc59116 = container_of(work, struct tlc59116_led, work); -+ -+ led_dbg("\nbrighness = %d \n", tlc59116->brightness); -+ switch (tlc59116->brightness) { -+ case LED_OFF: -+ led_info("\nLed off\n"); -+ tlc59116_set_gpio(tlc59116->client, tlc59116->led_num, TLC59116_LED_OFF); -+ break; -+ -+ case LED_FULL: -+ led_info("\nLed on\n"); -+ tlc59116_set_gpio(tlc59116->client, tlc59116->led_num, TLC59116_LED_ON); -+ break; -+ default: -+ led_info("\nBrightness is %d\n", tlc59116->brightness); -+ if (TLC59116_BLINK != tlc59116_get_gpio(tlc59116->client, tlc59116->led_num)) -+ tlc59116_set_gpio(tlc59116->client, tlc59116->led_num, TLC59116_DIM); -+ -+ tlc59116_individual_brighness_control(tlc59116->client, tlc59116->led_num, tlc59116->brightness); -+ break; -+ } -+ -+} -+ -+static void tlc59116_led_set(struct led_classdev *led_cdev, enum led_brightness value) -+{ -+ struct tlc59116_led *tlc59116; -+ -+ tlc59116 = container_of(led_cdev, struct tlc59116_led, led_cdev); -+ -+ spin_lock(&tlc59116->lock); -+ tlc59116->brightness = value; -+ -+ /* -+ * Must use workqueue for the actual I/O since I2C operations -+ * can sleep. -+ */ -+ schedule_work(&tlc59116->work); -+ -+ spin_unlock(&tlc59116->lock); -+} -+ -+/* -+ * delay_on, delay_off: units are in ms -+ * -+ */ -+ -+static int tlc59116_set_blink(struct led_classdev *led_cdev, -+ unsigned long *delay_on, -+ unsigned long *delay_off) -+{ -+ struct tlc59116_led *tlc59116; -+ uint16_t period; -+ uint16_t duty_cycle; -+ uint8_t gdc; -+ uint8_t gfrq; -+ -+ tlc59116 = container_of(led_cdev, struct tlc59116_led, led_cdev); -+ led_info ("Blinking: delay_on = %ldms, delay_off = %ldms, brightness = %d\n", -+ *delay_on, *delay_off, tlc59116->brightness); -+ -+ // Hardware blinking only for tricolor leds . The rest will have software blinking -+ if (tlc59116->led_num > 2) -+ return 1; -+ -+ if ((*delay_on == 0) && (*delay_off ==0)) { -+ spin_lock(&tlc59116->lock); -+ -+ /* MODE2[DMBLNK] = 1 */ -+ tlc59116_set_mode(tlc59116->client, MODE2_BLINK); -+ -+ /* Set LDRx = 11 */ -+ tlc59116_set_gpio(tlc59116->client, tlc59116->led_num, TLC59116_BLINK); -+ -+ tlc59116_individual_brighness_control(tlc59116->client, tlc59116->led_num, tlc59116->brightness); -+ spin_unlock(&tlc59116->lock); -+ return 0; -+ } -+ -+ -+ if ((*delay_on + *delay_off) > TLC59116_PERIOD_MAX) -+ { -+ led_dbg ("Max period is %dms\n", TLC59116_PERIOD_MAX); -+ return -EINVAL; -+ } -+ -+ if ((*delay_on + *delay_off) < TLC59116_PERIOD_MIN) -+ { -+ *delay_on = TLC59116_PERIOD_MIN/2 + 1; -+ *delay_off = TLC59116_PERIOD_MIN/2 + 1; -+ } -+ period = (*delay_on) + (*delay_off); -+ -+ duty_cycle = (100 * (*delay_on)) / period; -+ -+ spin_lock(&tlc59116->lock); -+ -+ /* MODE2[DMBLNK] = 1 */ -+ tlc59116_set_mode(tlc59116->client, MODE2_BLINK); -+ -+ /* Set LDRx = 11 */ -+ tlc59116_set_gpio(tlc59116->client, tlc59116->led_num, TLC59116_BLINK); -+ -+ tlc59116_individual_brighness_control(tlc59116->client, tlc59116->led_num, tlc59116->brightness); -+ -+ gdc = (duty_cycle * 256)/100; -+ i2c_smbus_write_byte_data(tlc59116->client, TLC59116_REG_GRPPWM, gdc); -+ -+ gfrq = (24 * period)/1000 - 1; /* unit is in second (convert from ms to second) */ -+ i2c_smbus_write_byte_data(tlc59116->client, TLC59116_REG_GRPFREQ, gfrq); -+ -+ -+#ifdef DUMP_REGS -+ dump_regs(tlc59116->client); -+#endif -+ -+ spin_unlock(&tlc59116->lock); -+ return 0; -+} -+ -+static int tlc59116_probe(struct i2c_client *client, -+ const struct i2c_device_id *id) -+{ -+ struct tlc59116_led *tlc59116; -+ struct tlc59116_chipdef *chip; -+ struct i2c_adapter *adapter; -+ struct led_platform_data *pdata; -+ int i, err; -+ -+ chip = &tlc59116_chipdefs[id->driver_data]; -+ adapter = to_i2c_adapter(client->dev.parent); -+ pdata = client->dev.platform_data; -+ -+ /* Make sure the slave address / chip type combo given is possible */ -+ if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) != -+ chip->slv_addr) { -+ dev_err(&client->dev, "invalid slave address %02x\n", -+ client->addr); -+ return -ENODEV; -+ } -+ -+ printk(KERN_INFO "leds-tlc59116: Using %s %d-bit LED driver at " -+ "slave address 0x%02x\n", -+ id->name, chip->bits, client->addr); -+ -+ if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) -+ return -EIO; -+ -+ if (pdata) { -+ if (pdata->num_leds != chip->bits) { -+ dev_err(&client->dev, "board info claims %d LEDs" -+ " on a %d-bit chip\n", -+ pdata->num_leds, chip->bits); -+ return -ENODEV; -+ } -+ } -+ -+ tlc59116 = devm_kzalloc(&client->dev, sizeof(*tlc59116) * chip->bits, GFP_KERNEL); -+ if (!tlc59116) -+ return -ENOMEM; -+ -+ i2c_set_clientdata(client, tlc59116); -+ -+ for (i = 0; i < chip->bits; i++) { -+ tlc59116[i].chipdef = chip; -+ tlc59116[i].client = client; -+ tlc59116[i].led_num = i; -+ -+ /* Platform data can specify LED names and default triggers */ -+ if (pdata) { -+ if (pdata->leds[i].name) -+ snprintf(tlc59116[i].name, -+ sizeof(tlc59116[i].name), "tlc59116:%s", -+ pdata->leds[i].name); -+ if (pdata->leds[i].default_trigger) -+ tlc59116[i].led_cdev.default_trigger = -+ pdata->leds[i].default_trigger; -+ } else { -+ snprintf(tlc59116[i].name, sizeof(tlc59116[i].name), -+ "tlc59116:%d", i); -+ } -+ -+ spin_lock_init(&tlc59116[i].lock); -+ -+ tlc59116[i].led_cdev.name = tlc59116[i].name; -+ tlc59116[i].led_cdev.brightness_set = tlc59116_led_set; -+ tlc59116[i].led_cdev.blink_set = tlc59116_set_blink; -+ tlc59116[i].led_cdev.brightness = 0; -+ -+ INIT_WORK(&tlc59116[i].work, tlc59116_led_work); -+ -+ err = led_classdev_register(&client->dev, &tlc59116[i].led_cdev); -+ if (err < 0) -+ goto exit; -+ } -+ -+ tlc59116_set_mode(client, MODE2_DIM); -+ -+ /* Turn off LEDs */ -+ for (i = 0; i < chip->bits; i++) -+ tlc59116_set_gpio(client, i, TLC59116_LED_OFF); -+ -+ return 0; -+ -+exit: -+ while (i--) { -+ led_classdev_unregister(&tlc59116[i].led_cdev); -+ cancel_work_sync(&tlc59116[i].work); -+ } -+ -+ devm_kfree(&client->dev, tlc59116); -+ i2c_set_clientdata(client, NULL); -+ -+ return err; -+} -+ -+static int tlc59116_remove(struct i2c_client *client) -+{ -+ struct tlc59116_led *tlc59116 = i2c_get_clientdata(client); -+ int i; -+ -+ for (i = 0; i < tlc59116->chipdef->bits; i++) { -+ led_classdev_unregister(&tlc59116[i].led_cdev); -+ cancel_work_sync(&tlc59116[i].work); -+ } -+ -+ devm_kfree(&client->dev, tlc59116); -+ i2c_set_clientdata(client, NULL); -+ -+ return 0; -+} -+ -+static struct i2c_driver tlc59116_driver = { -+ .driver = { -+ .name = "leds-tlc59116", -+ .owner = THIS_MODULE, -+ }, -+ .probe = tlc59116_probe, -+ .remove = tlc59116_remove, -+ .id_table = tlc59116_id, -+}; -+ -+module_i2c_driver(tlc59116_driver); -+ -+MODULE_AUTHOR("Belkin Inc."); -+MODULE_DESCRIPTION("TLC59116 LED driver"); -+MODULE_LICENSE("GPL v2"); diff --git a/target/linux/mvebu/patches-3.18/050-leds_tlc59116_document_binding.patch b/target/linux/mvebu/patches-3.18/050-leds_tlc59116_document_binding.patch new file mode 100644 index 000000000000..e55eca3e042d --- /dev/null +++ b/target/linux/mvebu/patches-3.18/050-leds_tlc59116_document_binding.patch @@ -0,0 +1,51 @@ +Document the binding for the TLC59116 LED driver. + +Signed-off-by: Andrew Lunn +--- + .../devicetree/bindings/leds/leds-tlc59116.txt | 40 ++++++++++++++++++++++ + 1 file changed, 40 insertions(+) + create mode 100644 Documentation/devicetree/bindings/leds/leds-tlc59116.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/leds/leds-tlc59116.txt +@@ -0,0 +1,40 @@ ++LEDs connected to tcl59116 ++ ++Required properties ++- compatible: should be "ti,tlc59116" ++- #address-cells: must be 1 ++- #size-cells: must be 0 ++- reg: typically 0x68 ++ ++Each led is represented as a sub-node of the ti,,tlc59116. ++See Documentation/devicetree/bindings/leds/common.txt ++ ++LED sub-node properties: ++- reg: number of LED line, 0 to 15 ++- label: (optional) name of LED ++- linux,default-trigger : (optional) ++ ++Examples: ++ ++tlc59116@68 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "ti,tlc59116"; ++ reg = <0x68>; ++ ++ wan@0 { ++ label = "wrt1900ac:amber:wan"; ++ reg = <0x0>; ++ }; ++ ++ 2g@2 { ++ label = "wrt1900ac:white:2g"; ++ reg = <0x2>; ++ }; ++ ++ alive@9 { ++ label = "wrt1900ac:green:alive"; ++ reg = <0x9>; ++ linux,default_trigger = "heartbeat"; ++ }; ++}; diff --git a/target/linux/mvebu/patches-3.18/051-leds_tlc59116_add_driver.patch b/target/linux/mvebu/patches-3.18/051-leds_tlc59116_add_driver.patch new file mode 100644 index 000000000000..fd22aef58031 --- /dev/null +++ b/target/linux/mvebu/patches-3.18/051-leds_tlc59116_add_driver.patch @@ -0,0 +1,297 @@ +The TLC59116 is an I2C bus controlled 16-channel LED driver. Each LED +output has its own 8-bit fixed-frequency PWM controller to control the +brightness of the LED. + +This is based on a driver from Belkin, but has been extensively +rewritten. + +Signed-off-by: Andrew Lunn +--- + drivers/leds/Kconfig | 8 ++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tlc59116.c | 252 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 261 insertions(+) + create mode 100644 drivers/leds/leds-tlc59116.c + +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -446,6 +446,14 @@ config LEDS_TCA6507 + LED driver chips accessed via the I2C bus. + Driver support brightness control and hardware-assisted blinking. + ++config LEDS_TLC59116 ++ tristate "LED driver for TLC59116F controllers" ++ depends on LEDS_CLASS && I2C ++ select REGMAP_I2C ++ help ++ This option enables support for Texas Instruments TLC59116F ++ LED controller. ++ + config LEDS_MAX8997 + tristate "LED support for MAX8997 PMIC" + depends on LEDS_CLASS && MFD_MAX8997 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -29,6 +29,7 @@ obj-$(CONFIG_LEDS_LP5562) += leds-lp556 + obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o + obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o + obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o ++obj-$(CONFIG_LEDS_TLC59116) += leds-tlc59116.o + obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o + obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o + obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o +--- /dev/null ++++ b/drivers/leds/leds-tlc59116.c +@@ -0,0 +1,252 @@ ++/* ++ * Copyright 2014 Belkin Inc. ++ * Copyright 2014 Andrew Lunn ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; version 2 of the License. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define TLC59116_LEDS 16 ++ ++#define TLC59116_REG_MODE1 0x00 ++#define MODE1_RESPON_ADDR_MASK 0xF0 ++#define MODE1_NORMAL_MODE (0 << 4) ++#define MODE1_SPEED_MODE (1 << 4) ++ ++#define TLC59116_REG_MODE2 0x01 ++#define MODE2_DIM (0 << 5) ++#define MODE2_BLINK (1 << 5) ++#define MODE2_OCH_STOP (0 << 3) ++#define MODE2_OCH_ACK (1 << 3) ++ ++#define TLC59116_REG_PWM(x) (0x02 + (x)) ++ ++#define TLC59116_REG_GRPPWM 0x12 ++#define TLC59116_REG_GRPFREQ 0x13 ++ ++/* LED Driver Output State, determine the source that drives LED outputs */ ++#define TLC59116_REG_LEDOUT(x) (0x14 + ((x) >> 2)) ++#define TLC59116_LED_OFF 0x0 /* Output LOW */ ++#define TLC59116_LED_ON 0x1 /* Output HI-Z */ ++#define TLC59116_DIM 0x2 /* Dimming */ ++#define TLC59116_BLINK 0x3 /* Blinking */ ++#define LED_MASK 0x3 ++ ++#define ldev_to_led(c) container_of(c, struct tlc59116_led, ldev) ++#define work_to_led(work) container_of(work, struct tlc59116_led, work) ++ ++struct tlc59116_led { ++ bool active; ++ struct regmap *regmap; ++ unsigned int led_no; ++ struct led_classdev ldev; ++ struct work_struct work; ++}; ++ ++struct tlc59116_priv { ++ struct tlc59116_led leds[TLC59116_LEDS]; ++}; ++ ++static int ++tlc59116_set_mode(struct regmap *regmap, u8 mode) ++{ ++ int err; ++ u8 val; ++ ++ if ((mode != MODE2_DIM) && (mode != MODE2_BLINK)) ++ mode = MODE2_DIM; ++ ++ /* Configure MODE1 register */ ++ err = regmap_write(regmap, TLC59116_REG_MODE1, MODE1_NORMAL_MODE); ++ if (err) ++ return err; ++ ++ /* Configure MODE2 Reg */ ++ val = MODE2_OCH_STOP | mode; ++ ++ return regmap_write(regmap, TLC59116_REG_MODE2, val); ++} ++ ++static int ++tlc59116_set_led(struct tlc59116_led *led, u8 val) ++{ ++ struct regmap *regmap = led->regmap; ++ unsigned int i = (led->led_no % 4) * 2; ++ unsigned int addr = TLC59116_REG_LEDOUT(led->led_no); ++ unsigned int mask = LED_MASK << i; ++ ++ val = val << i; ++ ++ return regmap_update_bits(regmap, addr, mask, val); ++} ++ ++static void ++tlc59116_led_work(struct work_struct *work) ++{ ++ struct tlc59116_led *led = work_to_led(work); ++ struct regmap *regmap = led->regmap; ++ int err; ++ u8 pwm; ++ ++ pwm = TLC59116_REG_PWM(led->led_no); ++ err = regmap_write(regmap, pwm, led->ldev.brightness); ++ if (err) ++ dev_err(led->ldev.dev, "Failed setting brightness\n"); ++} ++ ++static void ++tlc59116_led_set(struct led_classdev *led_cdev, enum led_brightness value) ++{ ++ struct tlc59116_led *led = ldev_to_led(led_cdev); ++ ++ led->ldev.brightness = value; ++ schedule_work(&led->work); ++} ++ ++static void ++tlc59116_destroy_devices(struct tlc59116_priv *priv, unsigned int i) ++{ ++ while (--i >= 0) { ++ if (priv->leds[i].active) { ++ led_classdev_unregister(&priv->leds[i].ldev); ++ cancel_work_sync(&priv->leds[i].work); ++ } ++ } ++} ++ ++static int ++tlc59116_configure(struct device *dev, ++ struct tlc59116_priv *priv, ++ struct regmap *regmap) ++{ ++ unsigned int i; ++ int err = 0; ++ ++ tlc59116_set_mode(regmap, MODE2_DIM); ++ for (i = 0; i < TLC59116_LEDS; i++) { ++ struct tlc59116_led *led = &priv->leds[i]; ++ ++ if (!led->active) ++ continue; ++ ++ led->regmap = regmap; ++ led->led_no = i; ++ led->ldev.brightness_set = tlc59116_led_set; ++ led->ldev.max_brightness = LED_FULL; ++ INIT_WORK(&led->work, tlc59116_led_work); ++ err = led_classdev_register(dev, &led->ldev); ++ if (err < 0) { ++ dev_err(dev, "couldn't register LED %s\n", ++ led->ldev.name); ++ goto exit; ++ } ++ tlc59116_set_led(led, TLC59116_DIM); ++ } ++ ++ return 0; ++ ++exit: ++ tlc59116_destroy_devices(priv, i); ++ return err; ++} ++ ++static const struct regmap_config tlc59116_regmap = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = 0x1e, ++}; ++ ++static int ++tlc59116_probe(struct i2c_client *client, ++ const struct i2c_device_id *id) ++{ ++ struct tlc59116_priv *priv = i2c_get_clientdata(client); ++ struct device *dev = &client->dev; ++ struct device_node *np = client->dev.of_node, *child; ++ struct regmap *regmap; ++ int err, count, reg; ++ ++ count = of_get_child_count(np); ++ if (!count || count > TLC59116_LEDS) ++ return -EINVAL; ++ ++ if (!i2c_check_functionality(client->adapter, ++ I2C_FUNC_SMBUS_BYTE_DATA)) ++ return -EIO; ++ ++ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ regmap = devm_regmap_init_i2c(client, &tlc59116_regmap); ++ if (IS_ERR(regmap)) { ++ err = PTR_ERR(regmap); ++ dev_err(dev, "Failed to allocate register map: %d\n", err); ++ return err; ++ } ++ ++ i2c_set_clientdata(client, priv); ++ ++ for_each_child_of_node(np, child) { ++ err = of_property_read_u32(child, "reg", ®); ++ if (err) ++ return err; ++ if (reg < 0 || reg >= TLC59116_LEDS) ++ return -EINVAL; ++ if (priv->leds[reg].active) ++ return -EINVAL; ++ priv->leds[reg].active = true; ++ priv->leds[reg].ldev.name = ++ of_get_property(child, "label", NULL) ? : child->name; ++ priv->leds[reg].ldev.default_trigger = ++ of_get_property(child, "linux,default-trigger", NULL); ++ } ++ return tlc59116_configure(dev, priv, regmap); ++} ++ ++static int ++tlc59116_remove(struct i2c_client *client) ++{ ++ struct tlc59116_priv *priv = i2c_get_clientdata(client); ++ ++ tlc59116_destroy_devices(priv, TLC59116_LEDS); ++ ++ return 0; ++} ++ ++static const struct of_device_id of_tlc59116_leds_match[] = { ++ { .compatible = "ti,tlc59116", }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, of_tlc59116_leds_match); ++ ++static const struct i2c_device_id tlc59116_id[] = { ++ { "tlc59116" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(i2c, tlc59116_id); ++ ++static struct i2c_driver tlc59116_driver = { ++ .driver = { ++ .name = "tlc59116", ++ .of_match_table = of_match_ptr(of_tlc59116_leds_match), ++ }, ++ .probe = tlc59116_probe, ++ .remove = tlc59116_remove, ++ .id_table = tlc59116_id, ++}; ++ ++module_i2c_driver(tlc59116_driver); ++ ++MODULE_AUTHOR("Andrew Lunn "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("TLC59116 LED driver");