diff options
-rw-r--r-- | Documentation/devicetree/bindings/leds/leds-cr0014114.txt | 54 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/leds/leds-lm3601x.txt | 45 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/leds/leds-sc27xx-bltc.txt | 41 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/vendor-prefixes.txt | 1 | ||||
-rw-r--r-- | drivers/leds/Kconfig | 33 | ||||
-rw-r--r-- | drivers/leds/Makefile | 3 | ||||
-rw-r--r-- | drivers/leds/led-class.c | 10 | ||||
-rw-r--r-- | drivers/leds/leds-cr0014114.c | 315 | ||||
-rw-r--r-- | drivers/leds/leds-lm3601x.c | 487 | ||||
-rw-r--r-- | drivers/leds/leds-sc27xx-bltc.c | 244 | ||||
-rw-r--r-- | drivers/leds/leds-wm831x-status.c | 22 |
11 files changed, 1237 insertions, 18 deletions
diff --git a/Documentation/devicetree/bindings/leds/leds-cr0014114.txt b/Documentation/devicetree/bindings/leds/leds-cr0014114.txt new file mode 100644 index 000000000000..4255b19ad25c --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-cr0014114.txt @@ -0,0 +1,54 @@ +Crane Merchandising System - cr0014114 LED driver +------------------------------------------------- + +This LED Board is widely used in vending machines produced +by Crane Merchandising Systems. + +Required properties: +- compatible: "crane,cr0014114" + +Property rules described in Documentation/devicetree/bindings/spi/spi-bus.txt +apply. In particular, "reg" and "spi-max-frequency" properties must be given. + +LED sub-node properties: +- label : + see Documentation/devicetree/bindings/leds/common.txt +- linux,default-trigger : (optional) + see Documentation/devicetree/bindings/leds/common.txt + +Example +------- + +led-controller@0 { + compatible = "crane,cr0014114"; + reg = <0>; + spi-max-frequency = <50000>; + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + label = "red:coin"; + }; + led@1 { + reg = <1>; + label = "green:coin"; + }; + led@2 { + reg = <2>; + label = "blue:coin"; + }; + led@3 { + reg = <3>; + label = "red:bill"; + }; + led@4 { + reg = <4>; + label = "green:bill"; + }; + led@5 { + reg = <5>; + label = "blue:bill"; + }; + ... +}; diff --git a/Documentation/devicetree/bindings/leds/leds-lm3601x.txt b/Documentation/devicetree/bindings/leds/leds-lm3601x.txt new file mode 100644 index 000000000000..a88b2c41e75b --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-lm3601x.txt @@ -0,0 +1,45 @@ +* Texas Instruments - lm3601x Single-LED Flash Driver + +The LM3601X are ultra-small LED flash drivers that +provide a high level of adjustability. + +Required properties: + - compatible : Can be one of the following + "ti,lm36010" + "ti,lm36011" + - reg : I2C slave address + - #address-cells : 1 + - #size-cells : 0 + +Required child properties: + - reg : 0 - Indicates a IR mode + 1 - Indicates a Torch (white LED) mode + +Required properties for flash LED child nodes: + See Documentation/devicetree/bindings/leds/common.txt + - flash-max-microamp : Range from 11mA - 1.5A + - flash-max-timeout-us : Range from 40ms - 1600ms + - led-max-microamp : Range from 2.4mA - 376mA + +Optional child properties: + - label : see Documentation/devicetree/bindings/leds/common.txt + +Example: +led-controller@64 { + compatible = "ti,lm36010"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x64>; + + led@0 { + reg = <1>; + label = "white:torch"; + led-max-microamp = <376000>; + flash-max-microamp = <1500000>; + flash-max-timeout-us = <1600000>; + }; +} + +For more product information please see the links below: +http://www.ti.com/product/LM36010 +http://www.ti.com/product/LM36011 diff --git a/Documentation/devicetree/bindings/leds/leds-sc27xx-bltc.txt b/Documentation/devicetree/bindings/leds/leds-sc27xx-bltc.txt new file mode 100644 index 000000000000..dddf84f9c7ea --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-sc27xx-bltc.txt @@ -0,0 +1,41 @@ +LEDs connected to Spreadtrum SC27XX PMIC breathing light controller + +The SC27xx breathing light controller supports to 3 outputs: +red LED, green LED and blue LED. Each LED can work at normal +PWM mode or breath light mode. + +Required properties: +- compatible: Should be "sprd,sc2731-bltc". +- #address-cells: Must be 1. +- #size-cells: Must be 0. +- reg: Specify the controller address. + +Required child properties: +- reg: Port this LED is connected to. + +Optional child properties: +- label: See Documentation/devicetree/bindings/leds/common.txt. + +Examples: + +led-controller@200 { + compatible = "sprd,sc2731-bltc"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x200>; + + led@0 { + label = "red"; + reg = <0x0>; + }; + + led@1 { + label = "green"; + reg = <0x1>; + }; + + led@2 { + label = "blue"; + reg = <0x2>; + }; +}; diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index a38d8bfae19c..36003832c2a8 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -75,6 +75,7 @@ cnxt Conexant Systems, Inc. compulab CompuLab Ltd. cortina Cortina Systems, Inc. cosmic Cosmic Circuits +crane Crane Connectivity Solutions creative Creative Technology Ltd crystalfontz Crystalfontz America, Inc. cubietech Cubietech, Ltd. diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 2c896c0e69e1..6e3a998f3370 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -104,6 +104,19 @@ config LEDS_CPCAP This option enables support for LEDs offered by Motorola's CPCAP PMIC. +config LEDS_CR0014114 + tristate "LED Support for Crane CR0014114" + depends on LEDS_CLASS + depends on SPI + depends on OF + help + This option enables support for CR0014114 LED Board which + is widely used in vending machines produced by + Crane Merchandising Systems. + + To compile this driver as a module, choose M here: the module + will be called leds-cr0014114. + config LEDS_LM3530 tristate "LCD Backlight driver for LM3530" depends on LEDS_CLASS @@ -145,6 +158,15 @@ config LEDS_LM3692X This option enables support for the TI LM3692x family of white LED string drivers used for backlighting. +config LEDS_LM3601X + tristate "LED support for LM3601x Chips" + depends on LEDS_CLASS && I2C + depends on LEDS_CLASS_FLASH + select REGMAP_I2C + help + This option enables support for the TI LM3601x family + of flash, torch and indicator classes. + config LEDS_LOCOMO tristate "LED Support for Locomo device" depends on LEDS_CLASS @@ -647,6 +669,17 @@ config LEDS_IS31FL32XX LED controllers. They are I2C devices with multiple constant-current channels, each with independent 256-level PWM control. +config LEDS_SC27XX_BLTC + tristate "LED support for the SC27xx breathing light controller" + depends on LEDS_CLASS && MFD_SC27XX_PMIC + depends on OF + help + Say Y here to include support for the SC27xx breathing light controller + LEDs. + + This driver can also be built as a module. If so the module will be + called leds-sc27xx-bltc. + comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" config LEDS_BLINKM diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 91eca81cae82..420b5d2cfa62 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -76,8 +76,11 @@ obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o obj-$(CONFIG_LEDS_LM3692X) += leds-lm3692x.o +obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o +obj-$(CONFIG_LEDS_LM3601X) += leds-lm3601x.o # LED SPI Drivers +obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o # LED Userspace Drivers diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index b0e2d55acbd6..3c7e3487b373 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -260,10 +260,14 @@ int of_led_classdev_register(struct device *parent, struct device_node *np, if (ret < 0) return ret; + mutex_init(&led_cdev->led_access); + mutex_lock(&led_cdev->led_access); led_cdev->dev = device_create_with_groups(leds_class, parent, 0, led_cdev, led_cdev->groups, "%s", name); - if (IS_ERR(led_cdev->dev)) + if (IS_ERR(led_cdev->dev)) { + mutex_unlock(&led_cdev->led_access); return PTR_ERR(led_cdev->dev); + } led_cdev->dev->of_node = np; if (ret) @@ -274,6 +278,7 @@ int of_led_classdev_register(struct device *parent, struct device_node *np, ret = led_add_brightness_hw_changed(led_cdev); if (ret) { device_unregister(led_cdev->dev); + mutex_unlock(&led_cdev->led_access); return ret; } } @@ -285,7 +290,6 @@ int of_led_classdev_register(struct device *parent, struct device_node *np, #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED led_cdev->brightness_hw_changed = -1; #endif - mutex_init(&led_cdev->led_access); /* add to the list of leds */ down_write(&leds_list_lock); list_add_tail(&led_cdev->node, &leds_list); @@ -302,6 +306,8 @@ int of_led_classdev_register(struct device *parent, struct device_node *np, led_trigger_set_default(led_cdev); #endif + mutex_unlock(&led_cdev->led_access); + dev_dbg(parent, "Registered led device: %s\n", led_cdev->name); diff --git a/drivers/leds/leds-cr0014114.c b/drivers/leds/leds-cr0014114.c new file mode 100644 index 000000000000..a4b1c1dcce7f --- /dev/null +++ b/drivers/leds/leds-cr0014114.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018 Crane Merchandising Systems. All rights reserved. +// Copyright (C) 2018 Oleh Kravchenko <oleg@kaa.org.ua> + +#include <linux/delay.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> +#include <linux/workqueue.h> +#include <uapi/linux/uleds.h> + +/* + * CR0014114 SPI protocol descrtiption: + * +----+-----------------------------------+----+ + * | CMD| BRIGHTNESS |CRC | + * +----+-----------------------------------+----+ + * | | LED0| LED1| LED2| LED3| LED4| LED5| | + * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | |R|G|B|R|G|B|R|G|B|R|G|B|R|G|B|R|G|B| | + * | 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 1 | + * | |1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1| | + * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | 18 | | + * +----+-----------------------------------+----+ + * | 20 | + * +---------------------------------------------+ + * + * PS: Boards can be connected to the chain: + * SPI -> board0 -> board1 -> board2 .. + */ + +/* CR0014114 SPI commands */ +#define CR_SET_BRIGHTNESS 0x80 +#define CR_INIT_REENUMERATE 0x81 +#define CR_NEXT_REENUMERATE 0x82 + +/* CR0014114 default settings */ +#define CR_MAX_BRIGHTNESS GENMASK(6, 0) +#define CR_FW_DELAY_MSEC 10 +#define CR_RECOUNT_DELAY (HZ * 3600) + +struct cr0014114_led { + char name[LED_MAX_NAME_SIZE]; + struct cr0014114 *priv; + struct led_classdev ldev; + u8 brightness; +}; + +struct cr0014114 { + bool do_recount; + size_t count; + struct delayed_work work; + struct device *dev; + struct mutex lock; + struct spi_device *spi; + u8 *buf; + unsigned long delay; + struct cr0014114_led leds[]; +}; + +static void cr0014114_calc_crc(u8 *buf, const size_t len) +{ + size_t i; + u8 crc; + + for (i = 1, crc = 1; i < len - 1; i++) + crc += buf[i]; + crc |= BIT(7); + + /* special case when CRC matches the SPI commands */ + if (crc == CR_SET_BRIGHTNESS || + crc == CR_INIT_REENUMERATE || + crc == CR_NEXT_REENUMERATE) + crc = 0xfe; + + buf[len - 1] = crc; +} + +static int cr0014114_recount(struct cr0014114 *priv) +{ + int ret; + size_t i; + u8 cmd; + + dev_dbg(priv->dev, "LEDs recount is started\n"); + + cmd = CR_INIT_REENUMERATE; + ret = spi_write(priv->spi, &cmd, sizeof(cmd)); + if (ret) + goto err; + + cmd = CR_NEXT_REENUMERATE; + for (i = 0; i < priv->count; i++) { + msleep(CR_FW_DELAY_MSEC); + + ret = spi_write(priv->spi, &cmd, sizeof(cmd)); + if (ret) + goto err; + } + +err: + dev_dbg(priv->dev, "LEDs recount is finished\n"); + + if (ret) + dev_err(priv->dev, "with error %d", ret); + + return ret; +} + +static int cr0014114_sync(struct cr0014114 *priv) +{ + int ret; + size_t i; + unsigned long udelay, now = jiffies; + + /* to avoid SPI mistiming with firmware we should wait some time */ + if (time_after(priv->delay, now)) { + udelay = jiffies_to_usecs(priv->delay - now); + usleep_range(udelay, udelay + 1); + } + + if (unlikely(priv->do_recount)) { + ret = cr0014114_recount(priv); + if (ret) + goto err; + + priv->do_recount = false; + msleep(CR_FW_DELAY_MSEC); + } + + priv->buf[0] = CR_SET_BRIGHTNESS; + for (i = 0; i < priv->count; i++) + priv->buf[i + 1] = priv->leds[i].brightness; + cr0014114_calc_crc(priv->buf, priv->count + 2); + ret = spi_write(priv->spi, priv->buf, priv->count + 2); + +err: + priv->delay = jiffies + msecs_to_jiffies(CR_FW_DELAY_MSEC); + + return ret; +} + +static void cr0014114_recount_work(struct work_struct *work) +{ + int ret; + struct cr0014114 *priv = container_of(work, + struct cr0014114, + work.work); + + mutex_lock(&priv->lock); + priv->do_recount = true; + ret = cr0014114_sync(priv); + mutex_unlock(&priv->lock); + + if (ret) + dev_warn(priv->dev, "sync of LEDs failed %d\n", ret); + + schedule_delayed_work(&priv->work, CR_RECOUNT_DELAY); +} + +static int cr0014114_set_sync(struct led_classdev *ldev, + enum led_brightness brightness) +{ + int ret; + struct cr0014114_led *led = container_of(ldev, + struct cr0014114_led, + ldev); + + dev_dbg(led->priv->dev, "Set brightness of %s to %d\n", + led->name, brightness); + + mutex_lock(&led->priv->lock); + led->brightness = (u8)brightness; + ret = cr0014114_sync(led->priv); + mutex_unlock(&led->priv->lock); + + return ret; +} + +static int cr0014114_probe_dt(struct cr0014114 *priv) +{ + size_t i = 0; + struct cr0014114_led *led; + struct fwnode_handle *child; + struct device_node *np; + int ret; + const char *str; + + device_for_each_child_node(priv->dev, child) { + np = to_of_node(child); + led = &priv->leds[i]; + + ret = fwnode_property_read_string(child, "label", &str); + if (ret) + snprintf(led->name, sizeof(led->name), + "cr0014114::"); + else + snprintf(led->name, sizeof(led->name), + "cr0014114:%s", str); + + fwnode_property_read_string(child, "linux,default-trigger", + &led->ldev.default_trigger); + + led->priv = priv; + led->ldev.name = led->name; + led->ldev.max_brightness = CR_MAX_BRIGHTNESS; + led->ldev.brightness_set_blocking = cr0014114_set_sync; + + ret = devm_of_led_classdev_register(priv->dev, np, + &led->ldev); + if (ret) { + dev_err(priv->dev, + "failed to register LED device %s, err %d", + led->name, ret); + fwnode_handle_put(child); + return ret; + } + + led->ldev.dev->of_node = np; + + i++; + } + + return 0; +} + +static int cr0014114_probe(struct spi_device *spi) +{ + struct cr0014114 *priv; + size_t count; + int ret; + + count = device_get_child_node_count(&spi->dev); + if (!count) { + dev_err(&spi->dev, "LEDs are not defined in device tree!"); + return -ENODEV; + } + + priv = devm_kzalloc(&spi->dev, + sizeof(*priv) + sizeof(*priv->leds) * count, + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->buf = devm_kzalloc(&spi->dev, count + 2, GFP_KERNEL); + if (!priv->buf) + return -ENOMEM; + + mutex_init(&priv->lock); + INIT_DELAYED_WORK(&priv->work, cr0014114_recount_work); + priv->count = count; + priv->dev = &spi->dev; + priv->spi = spi; + priv->delay = jiffies - + msecs_to_jiffies(CR_FW_DELAY_MSEC); + + priv->do_recount = true; + ret = cr0014114_sync(priv); + if (ret) { + dev_err(priv->dev, "first recount failed %d\n", ret); + return ret; + } + + priv->do_recount = true; + ret = cr0014114_sync(priv); + if (ret) { + dev_err(priv->dev, "second recount failed %d\n", ret); + return ret; + } + + ret = cr0014114_probe_dt(priv); + if (ret) + return ret; + + /* setup recount work to workaround buggy firmware */ + schedule_delayed_work(&priv->work, CR_RECOUNT_DELAY); + + spi_set_drvdata(spi, priv); + + return 0; +} + +static int cr0014114_remove(struct spi_device *spi) +{ + struct cr0014114 *priv = spi_get_drvdata(spi); + + cancel_delayed_work_sync(&priv->work); + mutex_destroy(&priv->lock); + + return 0; +} + +static const struct of_device_id cr0014114_dt_ids[] = { + { .compatible = "crane,cr0014114", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, cr0014114_dt_ids); + +static struct spi_driver cr0014114_driver = { + .probe = cr0014114_probe, + .remove = cr0014114_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = cr0014114_dt_ids, + }, +}; + +module_spi_driver(cr0014114_driver); + +MODULE_AUTHOR("Oleh Kravchenko <oleg@kaa.org.ua>"); +MODULE_DESCRIPTION("cr0014114 LED driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:cr0014114"); diff --git a/drivers/leds/leds-lm3601x.c b/drivers/leds/leds-lm3601x.c new file mode 100644 index 000000000000..081aa71e43a3 --- /dev/null +++ b/drivers/leds/leds-lm3601x.c @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: GPL-2.0 +// Flash and torch driver for Texas Instruments LM3601X LED +// Flash driver chip family +// Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/led-class-flash.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <uapi/linux/uleds.h> + +#define LM3601X_LED_IR 0x0 +#define LM3601X_LED_TORCH 0x1 + +/* Registers */ +#define LM3601X_ENABLE_REG 0x01 +#define LM3601X_CFG_REG 0x02 +#define LM3601X_LED_FLASH_REG 0x03 +#define LM3601X_LED_TORCH_REG 0x04 +#define LM3601X_FLAGS_REG 0x05 +#define LM3601X_DEV_ID_REG 0x06 + +#define LM3601X_SW_RESET BIT(7) + +/* Enable Mode bits */ +#define LM3601X_MODE_STANDBY 0x00 +#define LM3601X_MODE_IR_DRV BIT(0) +#define LM3601X_MODE_TORCH BIT(1) +#define LM3601X_MODE_STROBE (BIT(0) | BIT(1)) +#define LM3601X_STRB_EN BIT(2) +#define LM3601X_STRB_EDGE_TRIG BIT(3) +#define LM3601X_IVFM_EN BIT(4) + +#define LM36010_BOOST_LIMIT_28 BIT(5) +#define LM36010_BOOST_FREQ_4MHZ BIT(6) +#define LM36010_BOOST_MODE_PASS BIT(7) + +/* Flag Mask */ +#define LM3601X_FLASH_TIME_OUT BIT(0) +#define LM3601X_UVLO_FAULT BIT(1) +#define LM3601X_THERM_SHUTDOWN BIT(2) +#define LM3601X_THERM_CURR BIT(3) +#define LM36010_CURR_LIMIT BIT(4) +#define LM3601X_SHORT_FAULT BIT(5) +#define LM3601X_IVFM_TRIP BIT(6) +#define LM36010_OVP_FAULT BIT(7) + +#define LM3601X_MAX_TORCH_I_UA 376000 +#define LM3601X_MIN_TORCH_I_UA 2400 +#define LM3601X_TORCH_REG_DIV 2965 + +#define LM3601X_MAX_STROBE_I_UA 1500000 +#define LM3601X_MIN_STROBE_I_UA 11000 +#define LM3601X_STROBE_REG_DIV 11800 + +#define LM3601X_TIMEOUT_MASK 0x1e +#define LM3601X_ENABLE_MASK (LM3601X_MODE_IR_DRV | LM3601X_MODE_TORCH) + +#define LM3601X_LOWER_STEP_US 40000 +#define LM3601X_UPPER_STEP_US 200000 +#define LM3601X_MIN_TIMEOUT_US 40000 +#define LM3601X_MAX_TIMEOUT_US 1600000 +#define LM3601X_TIMEOUT_XOVER_US 400000 + +enum lm3601x_type { + CHIP_LM36010 = 0, + CHIP_LM36011, +}; + +/** + * struct lm3601x_led - + * @fled_cdev: flash LED class device pointer + * @client: Pointer to the I2C client + * @regmap: Devices register map + * @lock: Lock for reading/writing the device + * @led_name: LED label for the Torch or IR LED + * @flash_timeout: the timeout for the flash + * @last_flag: last known flags register value + * @torch_current_max: maximum current for the torch + * @flash_current_max: maximum current for the flash + * @max_flash_timeout: maximum timeout for the flash + * @led_mode: The mode to enable either IR or Torch + */ +struct lm3601x_led { + struct led_classdev_flash fled_cdev; + struct i2c_client *client; + struct regmap *regmap; + struct mutex lock; + + char led_name[LED_MAX_NAME_SIZE]; + + unsigned int flash_timeout; + unsigned int last_flag; + + u32 torch_current_max; + u32 flash_current_max; + u32 max_flash_timeout; + + u32 led_mode; +}; + +static const struct reg_default lm3601x_regmap_defs[] = { + { LM3601X_ENABLE_REG, 0x20 }, + { LM3601X_CFG_REG, 0x15 }, + { LM3601X_LED_FLASH_REG, 0x00 }, + { LM3601X_LED_TORCH_REG, 0x00 }, +}; + +static bool lm3601x_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LM3601X_FLAGS_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config lm3601x_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = LM3601X_DEV_ID_REG, + .reg_defaults = lm3601x_regmap_defs, + .num_reg_defaults = ARRAY_SIZE(lm3601x_regmap_defs), + .cache_type = REGCACHE_RBTREE, + .volatile_reg = lm3601x_volatile_reg, +}; + +static struct lm3601x_led *fled_cdev_to_led(struct led_classdev_flash *fled_cdev) +{ + return container_of(fled_cdev, struct lm3601x_led, fled_cdev); +} + +static int lm3601x_read_faults(struct lm3601x_led *led) +{ + int flags_val; + int ret; + + ret = regmap_read(led->regmap, LM3601X_FLAGS_REG, &flags_val); + if (ret < 0) + return -EIO; + + led->last_flag = 0; + + if (flags_val & LM36010_OVP_FAULT) + led->last_flag |= LED_FAULT_OVER_VOLTAGE; + + if (flags_val & (LM3601X_THERM_SHUTDOWN | LM3601X_THERM_CURR)) + led->last_flag |= LED_FAULT_OVER_TEMPERATURE; + + if (flags_val & LM3601X_SHORT_FAULT) + led->last_flag |= LED_FAULT_SHORT_CIRCUIT; + + if (flags_val & LM36010_CURR_LIMIT) + led->last_flag |= LED_FAULT_OVER_CURRENT; + + if (flags_val & LM3601X_UVLO_FAULT) + led->last_flag |= LED_FAULT_UNDER_VOLTAGE; + + if (flags_val & LM3601X_IVFM_TRIP) + led->last_flag |= LED_FAULT_INPUT_VOLTAGE; + + if (flags_val & LM3601X_THERM_SHUTDOWN) + led->last_flag |= LED_FAULT_LED_OVER_TEMPERATURE; + + return led->last_flag; +} + +static int lm3601x_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev); + struct lm3601x_led *led = fled_cdev_to_led(fled_cdev); + int ret, led_mode_val; + + mutex_lock(&led->lock); + + ret = lm3601x_read_faults(led); + if (ret < 0) + goto out; + + if (led->led_mode == LM3601X_LED_TORCH) + led_mode_val = LM3601X_MODE_TORCH; + else + led_mode_val = LM3601X_MODE_IR_DRV; + + if (brightness == LED_OFF) { + ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG, + led_mode_val, LED_OFF); + goto out; + } + + ret = regmap_write(led->regmap, LM3601X_LED_TORCH_REG, brightness); + if (ret < 0) + goto out; + + ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG, + LM3601X_MODE_TORCH | LM3601X_MODE_IR_DRV, + led_mode_val); +out: + mutex_unlock(&led->lock); + return ret; +} + +static int lm3601x_strobe_set(struct led_classdev_flash *fled_cdev, + bool state) +{ + struct lm3601x_led *led = fled_cdev_to_led(fled_cdev); + int timeout_reg_val; + int current_timeout; + int ret; + + mutex_lock(&led->lock); + + ret = regmap_read(led->regmap, LM3601X_CFG_REG, ¤t_timeout); + if (ret < 0) + goto out; + + if (led->flash_timeout >= LM3601X_TIMEOUT_XOVER_US) + timeout_reg_val = led->flash_timeout / LM3601X_UPPER_STEP_US + 0x07; + else + timeout_reg_val = led->flash_timeout / LM3601X_LOWER_STEP_US - 0x01; + + if (led->flash_timeout != current_timeout) + ret = regmap_update_bits(led->regmap, LM3601X_CFG_REG, + LM3601X_TIMEOUT_MASK, timeout_reg_val); + + if (state) + ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG, + LM3601X_MODE_TORCH | LM3601X_MODE_IR_DRV, + LM3601X_MODE_STROBE); + else + ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG, + LM3601X_MODE_STROBE, LED_OFF); + + ret = lm3601x_read_faults(led); +out: + mutex_unlock(&led->lock); + return ret; +} + +static int lm3601x_flash_brightness_set(struct led_classdev_flash *fled_cdev, + u32 brightness) +{ + struct lm3601x_led *led = fled_cdev_to_led(fled_cdev); + u8 brightness_val; + int ret; + + mutex_lock(&led->lock); + ret = lm3601x_read_faults(led); + if (ret < 0) + goto out; + + if (brightness == LED_OFF) { + ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG, + LM3601X_MODE_STROBE, LED_OFF); + goto out; + } + + brightness_val = brightness / LM3601X_STROBE_REG_DIV; + + ret = regmap_write(led->regmap, LM3601X_LED_FLASH_REG, brightness_val); +out: + mutex_unlock(&led->lock); + return ret; +} + +static int lm3601x_flash_timeout_set(struct led_classdev_flash *fled_cdev, + u32 timeout) +{ + struct lm3601x_led *led = fled_cdev_to_led(fled_cdev); + + mutex_lock(&led->lock); + + led->flash_timeout = timeout; + + mutex_unlock(&led->lock); + + return 0; +} + +static int lm3601x_strobe_get(struct led_classdev_flash *fled_cdev, bool *state) +{ + struct lm3601x_led *led = fled_cdev_to_led(fled_cdev); + int strobe_state; + int ret; + + mutex_lock(&led->lock); + + ret = regmap_read(led->regmap, LM3601X_ENABLE_REG, &strobe_state); + if (ret < 0) + goto out; + + *state = strobe_state & LM3601X_MODE_STROBE; + +out: + mutex_unlock(&led->lock); + return ret; +} + +static int lm3601x_flash_fault_get(struct led_classdev_flash *fled_cdev, + u32 *fault) +{ + struct lm3601x_led *led = fled_cdev_to_led(fled_cdev); + + lm3601x_read_faults(led); + + *fault = led->last_flag; + + return 0; +} + +static const struct led_flash_ops flash_ops = { + .flash_brightness_set = lm3601x_flash_brightness_set, + .strobe_set = lm3601x_strobe_set, + .strobe_get = lm3601x_strobe_get, + .timeout_set = lm3601x_flash_timeout_set, + .fault_get = lm3601x_flash_fault_get, +}; + +static int lm3601x_register_leds(struct lm3601x_led *led) +{ + struct led_classdev *led_cdev; + struct led_flash_setting *setting; + + led->fled_cdev.ops = &flash_ops; + + setting = &led->fled_cdev.timeout; + setting->min = LM3601X_MIN_TIMEOUT_US; + setting->max = led->max_flash_timeout; + setting->step = LM3601X_LOWER_STEP_US; + setting->val = led->max_flash_timeout; + + setting = &led->fled_cdev.brightness; + setting->min = LM3601X_MIN_STROBE_I_UA; + setting->max = led->flash_current_max; + setting->step = LM3601X_TORCH_REG_DIV; + setting->val = led->flash_current_max; + + led_cdev = &led->fled_cdev.led_cdev; + led_cdev->name = led->led_name; + led_cdev->brightness_set_blocking = lm3601x_brightness_set; + led_cdev->max_brightness = DIV_ROUND_UP(led->torch_current_max, + LM3601X_TORCH_REG_DIV); + led_cdev->flags |= LED_DEV_CAP_FLASH; + + return led_classdev_flash_register(&led->client->dev, &led->fled_cdev); +} + +static int lm3601x_parse_node(struct lm3601x_led *led) +{ + struct fwnode_handle *child = NULL; + int ret = -ENODEV; + const char *name; + + child = device_get_next_child_node(&led->client->dev, child); + if (!child) { + dev_err(&led->client->dev, "No LED Child node\n"); + return ret; + } + + ret = fwnode_property_read_u32(child, "reg", &led->led_mode); + if (ret) { + dev_err(&led->client->dev, "reg DT property missing\n"); + goto out_err; + } + + if (led->led_mode > LM3601X_LED_TORCH || + led->led_mode < LM3601X_LED_IR) { + dev_warn(&led->client->dev, "Invalid led mode requested\n"); + ret = -EINVAL; + goto out_err; + } + + ret = fwnode_property_read_string(child, "label", &name); + if (ret) { + if (led->led_mode == LM3601X_LED_TORCH) + name = "torch"; + else + name = "infrared"; + } + + snprintf(led->led_name, sizeof(led->led_name), + "%s:%s", led->client->name, name); + + ret = fwnode_property_read_u32(child, "led-max-microamp", + &led->torch_current_max); + if (ret) { + dev_warn(&led->client->dev, + "led-max-microamp DT property missing\n"); + goto out_err; + } + + ret = fwnode_property_read_u32(child, "flash-max-microamp", + &led->flash_current_max); + if (ret) { + dev_warn(&led->client->dev, + "flash-max-microamp DT property missing\n"); + goto out_err; + } + + ret = fwnode_property_read_u32(child, "flash-max-timeout-us", + &led->max_flash_timeout); + if (ret) { + dev_warn(&led->client->dev, + "flash-max-timeout-us DT property missing\n"); + goto out_err; + } + +out_err: + fwnode_handle_put(child); + return ret; +} + +static int lm3601x_probe(struct i2c_client *client) +{ + struct lm3601x_led *led; + int ret; + + led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->client = client; + i2c_set_clientdata(client, led); + + ret = lm3601x_parse_node(led); + if (ret) + return -ENODEV; + + led->regmap = devm_regmap_init_i2c(client, &lm3601x_regmap); + if (IS_ERR(led->regmap)) { + ret = PTR_ERR(led->regmap); + dev_err(&client->dev, + "Failed to allocate register map: %d\n", ret); + return ret; + } + + mutex_init(&led->lock); + + return lm3601x_register_leds(led); +} + +static int lm3601x_remove(struct i2c_client *client) +{ + struct lm3601x_led *led = i2c_get_clientdata(client); + + led_classdev_flash_unregister(&led->fled_cdev); + mutex_destroy(&led->lock); + + return regmap_update_bits(led->regmap, LM3601X_ENABLE_REG, + LM3601X_ENABLE_MASK, + LM3601X_MODE_STANDBY); +} + +static const struct i2c_device_id lm3601x_id[] = { + { "LM36010", CHIP_LM36010 }, + { "LM36011", CHIP_LM36011 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm3601x_id); + +static const struct of_device_id of_lm3601x_leds_match[] = { + { .compatible = "ti,lm36010", }, + { .compatible = "ti,lm36011", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_lm3601x_leds_match); + +static struct i2c_driver lm3601x_i2c_driver = { + .driver = { + .name = "lm3601x", + .of_match_table = of_lm3601x_leds_match, + }, + .probe_new = lm3601x_probe, + .remove = lm3601x_remove, + .id_table = lm3601x_id, +}; +module_i2c_driver(lm3601x_i2c_driver); + +MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM3601X"); +MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-sc27xx-bltc.c b/drivers/leds/leds-sc27xx-bltc.c new file mode 100644 index 000000000000..9d9b7aab843f --- /dev/null +++ b/drivers/leds/leds-sc27xx-bltc.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Spreadtrum Communications Inc. + +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <uapi/linux/uleds.h> + +/* PMIC global control register definition */ +#define SC27XX_MODULE_EN0 0xc08 +#define SC27XX_CLK_EN0 0xc18 +#define SC27XX_RGB_CTRL 0xebc + +#define SC27XX_BLTC_EN BIT(9) +#define SC27XX_RTC_EN BIT(7) +#define SC27XX_RGB_PD BIT(0) + +/* Breathing light controller register definition */ +#define SC27XX_LEDS_CTRL 0x00 +#define SC27XX_LEDS_PRESCALE 0x04 +#define SC27XX_LEDS_DUTY 0x08 +#define SC27XX_LEDS_CURVE0 0x0c +#define SC27XX_LEDS_CURVE1 0x10 + +#define SC27XX_CTRL_SHIFT 4 +#define SC27XX_LED_RUN BIT(0) +#define SC27XX_LED_TYPE BIT(1) + +#define SC27XX_DUTY_SHIFT 8 +#define SC27XX_DUTY_MASK GENMASK(15, 0) +#define SC27XX_MOD_MASK GENMASK(7, 0) + +#define SC27XX_LEDS_OFFSET 0x10 +#define SC27XX_LEDS_MAX 3 + +struct sc27xx_led { + char name[LED_MAX_NAME_SIZE]; + struct led_classdev ldev; + struct sc27xx_led_priv *priv; + u8 line; + bool active; +}; + +struct sc27xx_led_priv { + struct sc27xx_led leds[SC27XX_LEDS_MAX]; + struct regmap *regmap; + struct mutex lock; + u32 base; +}; + +#define to_sc27xx_led(ldev) \ + container_of(ldev, struct sc27xx_led, ldev) + +static int sc27xx_led_init(struct regmap *regmap) +{ + int err; + + err = regmap_update_bits(regmap, SC27XX_MODULE_EN0, SC27XX_BLTC_EN, + SC27XX_BLTC_EN); + if (err) + return err; + + err = regmap_update_bits(regmap, SC27XX_CLK_EN0, SC27XX_RTC_EN, + SC27XX_RTC_EN); + if (err) + return err; + + return regmap_update_bits(regmap, SC27XX_RGB_CTRL, SC27XX_RGB_PD, 0); +} + +static u32 sc27xx_led_get_offset(struct sc27xx_led *leds) +{ + return leds->priv->base + SC27XX_LEDS_OFFSET * leds->line; +} + +static int sc27xx_led_enable(struct sc27xx_led *leds, enum led_brightness value) +{ + u32 base = sc27xx_led_get_offset(leds); + u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL; + u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line; + struct regmap *regmap = leds->priv->regmap; + int err; + + err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY, + SC27XX_DUTY_MASK, + (value << SC27XX_DUTY_SHIFT) | + SC27XX_MOD_MASK); + if (err) + return err; + + return regmap_update_bits(regmap, ctrl_base, + (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, + (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift); +} + +static int sc27xx_led_disable(struct sc27xx_led *leds) +{ + struct regmap *regmap = leds->priv->regmap; + u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL; + u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line; + + return regmap_update_bits(regmap, ctrl_base, + (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0); +} + +static int sc27xx_led_set(struct led_classdev *ldev, enum led_brightness value) +{ + struct sc27xx_led *leds = to_sc27xx_led(ldev); + int err; + + mutex_lock(&leds->priv->lock); + + if (value == LED_OFF) + err = sc27xx_led_disable(leds); + else + err = sc27xx_led_enable(leds, value); + + mutex_unlock(&leds->priv->lock); + + return err; +} + +static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv) +{ + int i, err; + + err = sc27xx_led_init(priv->regmap); + if (err) + return err; + + for (i = 0; i < SC27XX_LEDS_MAX; i++) { + struct sc27xx_led *led = &priv->leds[i]; + + if (!led->active) + continue; + + led->line = i; + led->priv = priv; + led->ldev.name = led->name; + led->ldev.brightness_set_blocking = sc27xx_led_set; + + err = devm_led_classdev_register(dev, &led->ldev); + if (err) + return err; + } + + return 0; +} + +static int sc27xx_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node, *child; + struct sc27xx_led_priv *priv; + const char *str; + u32 base, count, reg; + int err; + + count = of_get_child_count(np); + if (!count || count > SC27XX_LEDS_MAX) + return -EINVAL; + + err = of_property_read_u32(np, "reg", &base); + if (err) { + dev_err(dev, "fail to get reg of property\n"); + return err; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + mutex_init(&priv->lock); + priv->base = base; + priv->regmap = dev_get_regmap(dev->parent, NULL); + if (!priv->regmap) { + err = -ENODEV; + dev_err(dev, "failed to get regmap: %d\n", err); + return err; + } + + for_each_child_of_node(np, child) { + err = of_property_read_u32(child, "reg", ®); + if (err) { + of_node_put(child); + mutex_destroy(&priv->lock); + return err; + } + + if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) { + of_node_put(child); + mutex_destroy(&priv->lock); + return -EINVAL; + } + + priv->leds[reg].active = true; + + err = of_property_read_string(child, "label", &str); + if (err) + snprintf(priv->leds[reg].name, LED_MAX_NAME_SIZE, + "sc27xx::"); + else + snprintf(priv->leds[reg].name, LED_MAX_NAME_SIZE, + "sc27xx:%s", str); + } + + err = sc27xx_led_register(dev, priv); + if (err) + mutex_destroy(&priv->lock); + + return err; +} + +static int sc27xx_led_remove(struct platform_device *pdev) +{ + struct sc27xx_led_priv *priv = platform_get_drvdata(pdev); + + mutex_destroy(&priv->lock); + return 0; +} + +static const struct of_device_id sc27xx_led_of_match[] = { + { .compatible = "sprd,sc2731-bltc", }, + { } +}; +MODULE_DEVICE_TABLE(of, sc27xx_led_of_match); + +static struct platform_driver sc27xx_led_driver = { + .driver = { + .name = "sprd-bltc", + .of_match_table = sc27xx_led_of_match, + }, + .probe = sc27xx_led_probe, + .remove = sc27xx_led_remove, +}; + +module_platform_driver(sc27xx_led_driver); + +MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver"); +MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c index be93b20e792a..c5798b92e4d3 100644 --- a/drivers/leds/leds-wm831x-status.c +++ b/drivers/leds/leds-wm831x-status.c @@ -188,24 +188,14 @@ static ssize_t wm831x_status_src_store(struct device *dev, { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct wm831x_status *led = to_wm831x_status(led_cdev); - char name[20]; int i; - size_t len; - name[sizeof(name) - 1] = '\0'; - strncpy(name, buf, sizeof(name) - 1); - len = strlen(name); - - if (len && name[len - 1] == '\n') - name[len - 1] = '\0'; - - for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) { - if (!strcmp(name, led_src_texts[i])) { - mutex_lock(&led->mutex); - led->src = i; - mutex_unlock(&led->mutex); - wm831x_status_set(led); - } + i = sysfs_match_string(led_src_texts, buf); + if (i >= 0) { + mutex_lock(&led->mutex); + led->src = i; + mutex_unlock(&led->mutex); + wm831x_status_set(led); } return size; |