diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-07-09 08:59:39 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-07-09 08:59:39 -0700 |
commit | 8a3367cc8005842efcefc0cb5c29780370818572 (patch) | |
tree | 1e672969fab002a1b5594d696b3bc23ca31a0986 /drivers/leds | |
parent | 6e2bbb688aa6d05073dd1dd0b836d9becec195c1 (diff) | |
parent | 2605085fba22792f3d4a6b856c7c5a05492d1fde (diff) | |
download | linux-8a3367cc8005842efcefc0cb5c29780370818572.tar.bz2 |
Merge tag 'leds-for-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds
Pull LED updates from Jacek Anaszewski:
- Add a new LED common module for ti-lmu driver family
- Modify MFD ti-lmu bindings
- add ti,brightness-resolution
- add the ramp up/down property
- Add regulator support for LM36274 driver to lm363x-regulator.c
- New LED class drivers with DT bindings:
- leds-spi-byte
- leds-lm36274
- leds-lm3697 (move the support from MFD to LED subsystem)
- Simplify getting the I2C adapter of a client:
- leds-tca6507
- leds-pca955x
- Convert LED documentation to ReST
* tag 'leds-for-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds:
dt: leds-lm36274.txt: fix a broken reference to ti-lmu.txt
docs: leds: convert to ReST
leds: leds-tca6507: simplify getting the adapter of a client
leds: leds-pca955x: simplify getting the adapter of a client
leds: lm36274: Introduce the TI LM36274 LED driver
dt-bindings: leds: Add LED bindings for the LM36274
regulator: lm363x: Add support for LM36274
mfd: ti-lmu: Add LM36274 support to the ti-lmu
dt-bindings: mfd: Add lm36274 bindings to ti-lmu
leds: max77650: Remove set but not used variable 'parent'
leds: avoid flush_work in atomic context
leds: lm3697: Introduce the lm3697 driver
mfd: ti-lmu: Remove support for LM3697
dt-bindings: ti-lmu: Modify dt bindings for the LM3697
leds: TI LMU: Add common code for TI LMU devices
leds: spi-byte: add single byte SPI LED driver
dt-bindings: leds: Add binding for spi-byte LED.
dt-bindings: mfd: LMU: Add ti,brightness-resolution
dt-bindings: mfd: LMU: Add the ramp up/down property
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 35 | ||||
-rw-r--r-- | drivers/leds/Makefile | 4 | ||||
-rw-r--r-- | drivers/leds/leds-lm36274.c | 172 | ||||
-rw-r--r-- | drivers/leds/leds-lm3697.c | 395 | ||||
-rw-r--r-- | drivers/leds/leds-max77650.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-pca955x.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-spi-byte.c | 161 | ||||
-rw-r--r-- | drivers/leds/leds-tca6507.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-ti-lmu-common.c | 156 | ||||
-rw-r--r-- | drivers/leds/trigger/Kconfig | 2 | ||||
-rw-r--r-- | drivers/leds/trigger/ledtrig-transient.c | 2 |
11 files changed, 927 insertions, 6 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 760f73a49c9f..b0fdeef10bd9 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -784,6 +784,41 @@ config LEDS_NIC78BX To compile this driver as a module, choose M here: the module will be called leds-nic78bx. +config LEDS_SPI_BYTE + tristate "LED support for SPI LED controller with a single byte" + depends on LEDS_CLASS + depends on SPI + depends on OF + help + This option enables support for LED controller which use a single byte + for controlling the brightness. Currently the following controller is + supported: Ubiquiti airCube ISP microcontroller based LED controller. + +config LEDS_TI_LMU_COMMON + tristate "LED driver for TI LMU" + depends on LEDS_CLASS + depends on REGMAP + help + Say Y to enable the LED driver for TI LMU devices. + This supports common features between the TI LM3532, LM3631, LM3632, + LM3633, LM3695 and LM3697. + +config LEDS_LM3697 + tristate "LED driver for LM3697" + depends on LEDS_TI_LMU_COMMON + depends on I2C && OF + help + Say Y to enable the LM3697 LED driver for TI LMU devices. + This supports the LED device LM3697. + +config LEDS_LM36274 + tristate "LED driver for LM36274" + depends on LEDS_TI_LMU_COMMON + depends on MFD_TI_LMU + help + Say Y to enable the LM36274 LED driver for TI LMU devices. + This supports the LED device LM36274. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 1e9702ebffee..41fb073a39c1 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -77,10 +77,14 @@ obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o +obj-$(CONFIG_LEDS_SPI_BYTE) += leds-spi-byte.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 +obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o +obj-$(CONFIG_LEDS_LM3697) += leds-lm3697.o +obj-$(CONFIG_LEDS_LM36274) += leds-lm36274.o # LED SPI Drivers obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o diff --git a/drivers/leds/leds-lm36274.c b/drivers/leds/leds-lm36274.c new file mode 100644 index 000000000000..ed9dc857ec8f --- /dev/null +++ b/drivers/leds/leds-lm36274.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +// TI LM36274 LED chip family driver +// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/ + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/leds.h> +#include <linux/leds-ti-lmu-common.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +#include <linux/mfd/ti-lmu.h> +#include <linux/mfd/ti-lmu-register.h> + +#include <uapi/linux/uleds.h> + +#define LM36274_MAX_STRINGS 4 +#define LM36274_BL_EN BIT(4) + +/** + * struct lm36274 + * @pdev: platform device + * @led_dev: led class device + * @lmu_data: Register and setting values for common code + * @regmap: Devices register map + * @dev: Pointer to the devices device struct + * @led_sources - The LED strings supported in this array + * @num_leds - Number of LED strings are supported in this array + */ +struct lm36274 { + struct platform_device *pdev; + struct led_classdev led_dev; + struct ti_lmu_bank lmu_data; + struct regmap *regmap; + struct device *dev; + + u32 led_sources[LM36274_MAX_STRINGS]; + int num_leds; +}; + +static int lm36274_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct lm36274 *led = container_of(led_cdev, struct lm36274, led_dev); + + return ti_lmu_common_set_brightness(&led->lmu_data, brt_val); +} + +static int lm36274_init(struct lm36274 *lm36274_data) +{ + int enable_val = 0; + int i; + + for (i = 0; i < lm36274_data->num_leds; i++) + enable_val |= (1 << lm36274_data->led_sources[i]); + + if (!enable_val) { + dev_err(lm36274_data->dev, "No LEDs were enabled\n"); + return -EINVAL; + } + + enable_val |= LM36274_BL_EN; + + return regmap_write(lm36274_data->regmap, LM36274_REG_BL_EN, + enable_val); +} + +static int lm36274_parse_dt(struct lm36274 *lm36274_data) +{ + struct fwnode_handle *child = NULL; + char label[LED_MAX_NAME_SIZE]; + struct device *dev = &lm36274_data->pdev->dev; + const char *name; + int child_cnt; + int ret = -EINVAL; + + /* There should only be 1 node */ + child_cnt = device_get_child_node_count(dev); + if (child_cnt != 1) + return -EINVAL; + + device_for_each_child_node(dev, child) { + ret = fwnode_property_read_string(child, "label", &name); + if (ret) + snprintf(label, sizeof(label), + "%s::", lm36274_data->pdev->name); + else + snprintf(label, sizeof(label), + "%s:%s", lm36274_data->pdev->name, name); + + lm36274_data->num_leds = fwnode_property_read_u32_array(child, + "led-sources", + NULL, 0); + if (lm36274_data->num_leds <= 0) + return -ENODEV; + + ret = fwnode_property_read_u32_array(child, "led-sources", + lm36274_data->led_sources, + lm36274_data->num_leds); + if (ret) { + dev_err(dev, "led-sources property missing\n"); + return ret; + } + + fwnode_property_read_string(child, "linux,default-trigger", + &lm36274_data->led_dev.default_trigger); + + } + + lm36274_data->lmu_data.regmap = lm36274_data->regmap; + lm36274_data->lmu_data.max_brightness = MAX_BRIGHTNESS_11BIT; + lm36274_data->lmu_data.msb_brightness_reg = LM36274_REG_BRT_MSB; + lm36274_data->lmu_data.lsb_brightness_reg = LM36274_REG_BRT_LSB; + + lm36274_data->led_dev.name = label; + lm36274_data->led_dev.max_brightness = MAX_BRIGHTNESS_11BIT; + lm36274_data->led_dev.brightness_set_blocking = lm36274_brightness_set; + + return 0; +} + +static int lm36274_probe(struct platform_device *pdev) +{ + struct ti_lmu *lmu = dev_get_drvdata(pdev->dev.parent); + struct lm36274 *lm36274_data; + int ret; + + lm36274_data = devm_kzalloc(&pdev->dev, sizeof(*lm36274_data), + GFP_KERNEL); + if (!lm36274_data) + return -ENOMEM; + + lm36274_data->pdev = pdev; + lm36274_data->dev = lmu->dev; + lm36274_data->regmap = lmu->regmap; + dev_set_drvdata(&pdev->dev, lm36274_data); + + ret = lm36274_parse_dt(lm36274_data); + if (ret) { + dev_err(lm36274_data->dev, "Failed to parse DT node\n"); + return ret; + } + + ret = lm36274_init(lm36274_data); + if (ret) { + dev_err(lm36274_data->dev, "Failed to init the device\n"); + return ret; + } + + return devm_led_classdev_register(lm36274_data->dev, + &lm36274_data->led_dev); +} + +static const struct of_device_id of_lm36274_leds_match[] = { + { .compatible = "ti,lm36274-backlight", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_lm36274_leds_match); + +static struct platform_driver lm36274_driver = { + .probe = lm36274_probe, + .driver = { + .name = "lm36274-leds", + }, +}; +module_platform_driver(lm36274_driver) + +MODULE_DESCRIPTION("Texas Instruments LM36274 LED driver"); +MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-lm3697.c b/drivers/leds/leds-lm3697.c new file mode 100644 index 000000000000..54e0e35df824 --- /dev/null +++ b/drivers/leds/leds-lm3697.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +// TI LM3697 LED chip family driver +// Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ + +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/leds-ti-lmu-common.h> + +#define LM3697_REV 0x0 +#define LM3697_RESET 0x1 +#define LM3697_OUTPUT_CONFIG 0x10 +#define LM3697_CTRL_A_RAMP 0x11 +#define LM3697_CTRL_B_RAMP 0x12 +#define LM3697_CTRL_A_B_RT_RAMP 0x13 +#define LM3697_CTRL_A_B_RAMP_CFG 0x14 +#define LM3697_CTRL_A_B_BRT_CFG 0x16 +#define LM3697_CTRL_A_FS_CURR_CFG 0x17 +#define LM3697_CTRL_B_FS_CURR_CFG 0x18 +#define LM3697_PWM_CFG 0x1c +#define LM3697_CTRL_A_BRT_LSB 0x20 +#define LM3697_CTRL_A_BRT_MSB 0x21 +#define LM3697_CTRL_B_BRT_LSB 0x22 +#define LM3697_CTRL_B_BRT_MSB 0x23 +#define LM3697_CTRL_ENABLE 0x24 + +#define LM3697_SW_RESET BIT(0) + +#define LM3697_CTRL_A_EN BIT(0) +#define LM3697_CTRL_B_EN BIT(1) +#define LM3697_CTRL_A_B_EN (LM3697_CTRL_A_EN | LM3697_CTRL_B_EN) + +#define LM3697_MAX_LED_STRINGS 3 + +#define LM3697_CONTROL_A 0 +#define LM3697_CONTROL_B 1 +#define LM3697_MAX_CONTROL_BANKS 2 + +/** + * struct lm3697_led - + * @hvled_strings: Array of LED strings associated with a control bank + * @label: LED label + * @led_dev: LED class device + * @priv: Pointer to the device struct + * @lmu_data: Register and setting values for common code + * @control_bank: Control bank the LED is associated to. 0 is control bank A + * 1 is control bank B + */ +struct lm3697_led { + u32 hvled_strings[LM3697_MAX_LED_STRINGS]; + char label[LED_MAX_NAME_SIZE]; + struct led_classdev led_dev; + struct lm3697 *priv; + struct ti_lmu_bank lmu_data; + int control_bank; + int enabled; + int num_leds; +}; + +/** + * struct lm3697 - + * @enable_gpio: Hardware enable gpio + * @regulator: LED supply regulator pointer + * @client: Pointer to the I2C client + * @regmap: Devices register map + * @dev: Pointer to the devices device struct + * @lock: Lock for reading/writing the device + * @leds: Array of LED strings + */ +struct lm3697 { + struct gpio_desc *enable_gpio; + struct regulator *regulator; + struct i2c_client *client; + struct regmap *regmap; + struct device *dev; + struct mutex lock; + + int bank_cfg; + + struct lm3697_led leds[]; +}; + +static const struct reg_default lm3697_reg_defs[] = { + {LM3697_OUTPUT_CONFIG, 0x6}, + {LM3697_CTRL_A_RAMP, 0x0}, + {LM3697_CTRL_B_RAMP, 0x0}, + {LM3697_CTRL_A_B_RT_RAMP, 0x0}, + {LM3697_CTRL_A_B_RAMP_CFG, 0x0}, + {LM3697_CTRL_A_B_BRT_CFG, 0x0}, + {LM3697_CTRL_A_FS_CURR_CFG, 0x13}, + {LM3697_CTRL_B_FS_CURR_CFG, 0x13}, + {LM3697_PWM_CFG, 0xc}, + {LM3697_CTRL_A_BRT_LSB, 0x0}, + {LM3697_CTRL_A_BRT_MSB, 0x0}, + {LM3697_CTRL_B_BRT_LSB, 0x0}, + {LM3697_CTRL_B_BRT_MSB, 0x0}, + {LM3697_CTRL_ENABLE, 0x0}, +}; + +static const struct regmap_config lm3697_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = LM3697_CTRL_ENABLE, + .reg_defaults = lm3697_reg_defs, + .num_reg_defaults = ARRAY_SIZE(lm3697_reg_defs), + .cache_type = REGCACHE_FLAT, +}; + +static int lm3697_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct lm3697_led *led = container_of(led_cdev, struct lm3697_led, + led_dev); + int ctrl_en_val = (1 << led->control_bank); + int ret; + + mutex_lock(&led->priv->lock); + + if (brt_val == LED_OFF) { + ret = regmap_update_bits(led->priv->regmap, LM3697_CTRL_ENABLE, + ctrl_en_val, ~ctrl_en_val); + if (ret) { + dev_err(&led->priv->client->dev, "Cannot write ctrl register\n"); + goto brightness_out; + } + + led->enabled = LED_OFF; + } else { + ret = ti_lmu_common_set_brightness(&led->lmu_data, brt_val); + if (ret) { + dev_err(&led->priv->client->dev, + "Cannot write brightness\n"); + goto brightness_out; + } + + if (!led->enabled) { + ret = regmap_update_bits(led->priv->regmap, + LM3697_CTRL_ENABLE, + ctrl_en_val, ctrl_en_val); + if (ret) { + dev_err(&led->priv->client->dev, + "Cannot enable the device\n"); + goto brightness_out; + } + + led->enabled = brt_val; + } + } + +brightness_out: + mutex_unlock(&led->priv->lock); + return ret; +} + +static int lm3697_init(struct lm3697 *priv) +{ + struct lm3697_led *led; + int i, ret; + + if (priv->enable_gpio) { + gpiod_direction_output(priv->enable_gpio, 1); + } else { + ret = regmap_write(priv->regmap, LM3697_RESET, LM3697_SW_RESET); + if (ret) { + dev_err(&priv->client->dev, "Cannot reset the device\n"); + goto out; + } + } + + ret = regmap_write(priv->regmap, LM3697_CTRL_ENABLE, 0x0); + if (ret) { + dev_err(&priv->client->dev, "Cannot write ctrl enable\n"); + goto out; + } + + ret = regmap_write(priv->regmap, LM3697_OUTPUT_CONFIG, priv->bank_cfg); + if (ret) + dev_err(&priv->client->dev, "Cannot write OUTPUT config\n"); + + for (i = 0; i < LM3697_MAX_CONTROL_BANKS; i++) { + led = &priv->leds[i]; + ret = ti_lmu_common_set_ramp(&led->lmu_data); + if (ret) + dev_err(&priv->client->dev, "Setting the ramp rate failed\n"); + } +out: + return ret; +} + +static int lm3697_probe_dt(struct lm3697 *priv) +{ + struct fwnode_handle *child = NULL; + struct lm3697_led *led; + const char *name; + int control_bank; + size_t i = 0; + int ret = -EINVAL; + int j; + + priv->enable_gpio = devm_gpiod_get_optional(&priv->client->dev, + "enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->enable_gpio)) { + ret = PTR_ERR(priv->enable_gpio); + dev_err(&priv->client->dev, "Failed to get enable gpio: %d\n", + ret); + return ret; + } + + priv->regulator = devm_regulator_get(&priv->client->dev, "vled"); + if (IS_ERR(priv->regulator)) + priv->regulator = NULL; + + device_for_each_child_node(priv->dev, child) { + ret = fwnode_property_read_u32(child, "reg", &control_bank); + if (ret) { + dev_err(&priv->client->dev, "reg property missing\n"); + fwnode_handle_put(child); + goto child_out; + } + + if (control_bank > LM3697_CONTROL_B) { + dev_err(&priv->client->dev, "reg property is invalid\n"); + ret = -EINVAL; + fwnode_handle_put(child); + goto child_out; + } + + led = &priv->leds[i]; + + ret = ti_lmu_common_get_brt_res(&priv->client->dev, + child, &led->lmu_data); + if (ret) + dev_warn(&priv->client->dev, "brightness resolution property missing\n"); + + led->control_bank = control_bank; + led->lmu_data.regmap = priv->regmap; + led->lmu_data.runtime_ramp_reg = LM3697_CTRL_A_RAMP + + control_bank; + led->lmu_data.msb_brightness_reg = LM3697_CTRL_A_BRT_MSB + + led->control_bank * 2; + led->lmu_data.lsb_brightness_reg = LM3697_CTRL_A_BRT_LSB + + led->control_bank * 2; + + led->num_leds = fwnode_property_read_u32_array(child, + "led-sources", + NULL, 0); + + if (led->num_leds > LM3697_MAX_LED_STRINGS) { + dev_err(&priv->client->dev, "To many LED strings defined\n"); + continue; + } + + ret = fwnode_property_read_u32_array(child, "led-sources", + led->hvled_strings, + led->num_leds); + if (ret) { + dev_err(&priv->client->dev, "led-sources property missing\n"); + fwnode_handle_put(child); + goto child_out; + } + + for (j = 0; j < led->num_leds; j++) + priv->bank_cfg |= + (led->control_bank << led->hvled_strings[j]); + + ret = ti_lmu_common_get_ramp_params(&priv->client->dev, + child, &led->lmu_data); + if (ret) + dev_warn(&priv->client->dev, "runtime-ramp properties missing\n"); + + fwnode_property_read_string(child, "linux,default-trigger", + &led->led_dev.default_trigger); + + ret = fwnode_property_read_string(child, "label", &name); + if (ret) + snprintf(led->label, sizeof(led->label), + "%s::", priv->client->name); + else + snprintf(led->label, sizeof(led->label), + "%s:%s", priv->client->name, name); + + led->priv = priv; + led->led_dev.name = led->label; + led->led_dev.max_brightness = led->lmu_data.max_brightness; + led->led_dev.brightness_set_blocking = lm3697_brightness_set; + + ret = devm_led_classdev_register(priv->dev, &led->led_dev); + if (ret) { + dev_err(&priv->client->dev, "led register err: %d\n", + ret); + fwnode_handle_put(child); + goto child_out; + } + + i++; + } + +child_out: + return ret; +} + +static int lm3697_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lm3697 *led; + int count; + int ret; + + count = device_get_child_node_count(&client->dev); + if (!count) { + dev_err(&client->dev, "LEDs are not defined in device tree!"); + return -ENODEV; + } + + led = devm_kzalloc(&client->dev, struct_size(led, leds, count), + GFP_KERNEL); + if (!led) + return -ENOMEM; + + mutex_init(&led->lock); + i2c_set_clientdata(client, led); + + led->client = client; + led->dev = &client->dev; + led->regmap = devm_regmap_init_i2c(client, &lm3697_regmap_config); + if (IS_ERR(led->regmap)) { + ret = PTR_ERR(led->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret = lm3697_probe_dt(led); + if (ret) + return ret; + + return lm3697_init(led); +} + +static int lm3697_remove(struct i2c_client *client) +{ + struct lm3697 *led = i2c_get_clientdata(client); + int ret; + + ret = regmap_update_bits(led->regmap, LM3697_CTRL_ENABLE, + LM3697_CTRL_A_B_EN, 0); + if (ret) { + dev_err(&led->client->dev, "Failed to disable the device\n"); + return ret; + } + + if (led->enable_gpio) + gpiod_direction_output(led->enable_gpio, 0); + + if (led->regulator) { + ret = regulator_disable(led->regulator); + if (ret) + dev_err(&led->client->dev, + "Failed to disable regulator\n"); + } + + mutex_destroy(&led->lock); + + return 0; +} + +static const struct i2c_device_id lm3697_id[] = { + { "lm3697", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm3697_id); + +static const struct of_device_id of_lm3697_leds_match[] = { + { .compatible = "ti,lm3697", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_lm3697_leds_match); + +static struct i2c_driver lm3697_driver = { + .driver = { + .name = "lm3697", + .of_match_table = of_lm3697_leds_match, + }, + .probe = lm3697_probe, + .remove = lm3697_remove, + .id_table = lm3697_id, +}; +module_i2c_driver(lm3697_driver); + +MODULE_DESCRIPTION("Texas Instruments LM3697 LED driver"); +MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-max77650.c b/drivers/leds/leds-max77650.c index 6b74ce9cac12..8a8e5c65b157 100644 --- a/drivers/leds/leds-max77650.c +++ b/drivers/leds/leds-max77650.c @@ -64,7 +64,6 @@ static int max77650_led_probe(struct platform_device *pdev) { struct device_node *of_node, *child; struct max77650_led *leds, *led; - struct device *parent; struct device *dev; struct regmap *map; const char *label; @@ -72,7 +71,6 @@ static int max77650_led_probe(struct platform_device *pdev) u32 reg; dev = &pdev->dev; - parent = dev->parent; of_node = dev->of_node; if (!of_node) diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index c2bc8f569760..4037c504589c 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -429,7 +429,7 @@ static int pca955x_probe(struct i2c_client *client, int ngpios = 0; chip = &pca955x_chipdefs[id->driver_data]; - adapter = to_i2c_adapter(client->dev.parent); + adapter = client->adapter; pdata = dev_get_platdata(&client->dev); if (!pdata) { pdata = pca955x_get_pdata(client, chip); diff --git a/drivers/leds/leds-spi-byte.c b/drivers/leds/leds-spi-byte.c new file mode 100644 index 000000000000..b231b563b7bb --- /dev/null +++ b/drivers/leds/leds-spi-byte.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Christian Mauderer <oss@c-mauderer.de> + +/* + * The driver supports controllers with a very simple SPI protocol: + * - one LED is controlled by a single byte on MOSI + * - the value of the byte gives the brightness between two values (lowest to + * highest) + * - no return value is necessary (no MISO signal) + * + * The value for minimum and maximum brightness depends on the device + * (compatible string). + * + * Supported devices: + * - "ubnt,acb-spi-led": Microcontroller (SONiX 8F26E611LA) based device used + * for example in Ubiquiti airCube ISP. Reverse engineered protocol for this + * controller: + * * Higher two bits set a mode. Lower six bits are a parameter. + * * Mode: 00 -> set brightness between 0x00 (min) and 0x3F (max) + * * Mode: 01 -> pulsing pattern (min -> max -> min) with an interval. From + * some tests, the period is about (50ms + 102ms * parameter). There is a + * slightly different pattern starting from 0x10 (longer gap between the + * pulses) but the time still follows that calculation. + * * Mode: 10 -> same as 01 but with only a ramp from min to max. Again a + * slight jump in the pattern at 0x10. + * * Mode: 11 -> blinking (off -> 25% -> off -> 25% -> ...) with a period of + * (105ms * parameter) + * NOTE: This driver currently only supports mode 00. + */ + +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> +#include <linux/mutex.h> +#include <uapi/linux/uleds.h> + +struct spi_byte_chipdef { + /* SPI byte that will be send to switch the LED off */ + u8 off_value; + /* SPI byte that will be send to switch the LED to maximum brightness */ + u8 max_value; +}; + +struct spi_byte_led { + struct led_classdev ldev; + struct spi_device *spi; + char name[LED_MAX_NAME_SIZE]; + struct mutex mutex; + const struct spi_byte_chipdef *cdef; +}; + +static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef = { + .off_value = 0x0, + .max_value = 0x3F, +}; + +static const struct of_device_id spi_byte_dt_ids[] = { + { .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef }, + {}, +}; + +MODULE_DEVICE_TABLE(of, spi_byte_dt_ids); + +static int spi_byte_brightness_set_blocking(struct led_classdev *dev, + enum led_brightness brightness) +{ + struct spi_byte_led *led = container_of(dev, struct spi_byte_led, ldev); + u8 value; + int ret; + + value = (u8) brightness + led->cdef->off_value; + + mutex_lock(&led->mutex); + ret = spi_write(led->spi, &value, sizeof(value)); + mutex_unlock(&led->mutex); + + return ret; +} + +static int spi_byte_probe(struct spi_device *spi) +{ + const struct of_device_id *of_dev_id; + struct device_node *child; + struct device *dev = &spi->dev; + struct spi_byte_led *led; + const char *name = "leds-spi-byte::"; + const char *state; + int ret; + + of_dev_id = of_match_device(spi_byte_dt_ids, dev); + if (!of_dev_id) + return -EINVAL; + + if (of_get_child_count(dev->of_node) != 1) { + dev_err(dev, "Device must have exactly one LED sub-node."); + return -EINVAL; + } + child = of_get_next_child(dev->of_node, NULL); + + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + of_property_read_string(child, "label", &name); + strlcpy(led->name, name, sizeof(led->name)); + led->spi = spi; + mutex_init(&led->mutex); + led->cdef = of_dev_id->data; + led->ldev.name = led->name; + led->ldev.brightness = LED_OFF; + led->ldev.max_brightness = led->cdef->max_value - led->cdef->off_value; + led->ldev.brightness_set_blocking = spi_byte_brightness_set_blocking; + + state = of_get_property(child, "default-state", NULL); + if (state) { + if (!strcmp(state, "on")) { + led->ldev.brightness = led->ldev.max_brightness; + } else if (strcmp(state, "off")) { + /* all other cases except "off" */ + dev_err(dev, "default-state can only be 'on' or 'off'"); + return -EINVAL; + } + } + spi_byte_brightness_set_blocking(&led->ldev, + led->ldev.brightness); + + ret = devm_led_classdev_register(&spi->dev, &led->ldev); + if (ret) { + mutex_destroy(&led->mutex); + return ret; + } + spi_set_drvdata(spi, led); + + return 0; +} + +static int spi_byte_remove(struct spi_device *spi) +{ + struct spi_byte_led *led = spi_get_drvdata(spi); + + mutex_destroy(&led->mutex); + + return 0; +} + +static struct spi_driver spi_byte_driver = { + .probe = spi_byte_probe, + .remove = spi_byte_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = spi_byte_dt_ids, + }, +}; + +module_spi_driver(spi_byte_driver); + +MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>"); +MODULE_DESCRIPTION("single byte SPI LED driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:leds-spi-byte"); diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index c59035e157d1..58be20cae183 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -758,7 +758,7 @@ static int tca6507_probe(struct i2c_client *client, int err; int i = 0; - adapter = to_i2c_adapter(client->dev.parent); + adapter = client->adapter; pdata = dev_get_platdata(&client->dev); if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) diff --git a/drivers/leds/leds-ti-lmu-common.c b/drivers/leds/leds-ti-lmu-common.c new file mode 100644 index 000000000000..adc7293004f1 --- /dev/null +++ b/drivers/leds/leds-ti-lmu-common.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2015 Texas Instruments +// Copyright 2018 Sebastian Reichel +// Copyright 2018 Pavel Machek <pavel@ucw.cz> +// TI LMU LED common framework, based on previous work from +// Milo Kim <milo.kim@ti.com> + +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/of_device.h> + +#include <linux/leds-ti-lmu-common.h> + +const static int ramp_table[16] = {2048, 262000, 524000, 1049000, 2090000, + 4194000, 8389000, 16780000, 33550000, 41940000, + 50330000, 58720000, 67110000, 83880000, + 100660000, 117440000}; + +static int ti_lmu_common_update_brightness(struct ti_lmu_bank *lmu_bank, + int brightness) +{ + struct regmap *regmap = lmu_bank->regmap; + u8 reg, val; + int ret; + + /* + * Brightness register update + * + * 11 bit dimming: update LSB bits and write MSB byte. + * MSB brightness should be shifted. + * 8 bit dimming: write MSB byte. + */ + if (lmu_bank->max_brightness == MAX_BRIGHTNESS_11BIT) { + reg = lmu_bank->lsb_brightness_reg; + ret = regmap_update_bits(regmap, reg, + LMU_11BIT_LSB_MASK, + brightness); + if (ret) + return ret; + + val = brightness >> LMU_11BIT_MSB_SHIFT; + } else { + val = brightness; + } + + reg = lmu_bank->msb_brightness_reg; + + return regmap_write(regmap, reg, val); +} + +int ti_lmu_common_set_brightness(struct ti_lmu_bank *lmu_bank, int brightness) +{ + return ti_lmu_common_update_brightness(lmu_bank, brightness); +} +EXPORT_SYMBOL(ti_lmu_common_set_brightness); + +static int ti_lmu_common_convert_ramp_to_index(unsigned int usec) +{ + int size = ARRAY_SIZE(ramp_table); + int i; + + if (usec <= ramp_table[0]) + return 0; + + if (usec > ramp_table[size - 1]) + return size - 1; + + for (i = 1; i < size; i++) { + if (usec == ramp_table[i]) + return i; + + /* Find an approximate index by looking up the table */ + if (usec > ramp_table[i - 1] && usec < ramp_table[i]) { + if (usec - ramp_table[i - 1] < ramp_table[i] - usec) + return i - 1; + else + return i; + } + } + + return -EINVAL; +} + +int ti_lmu_common_set_ramp(struct ti_lmu_bank *lmu_bank) +{ + struct regmap *regmap = lmu_bank->regmap; + u8 ramp, ramp_up, ramp_down; + + if (lmu_bank->ramp_up_usec == 0 && lmu_bank->ramp_down_usec == 0) { + ramp_up = 0; + ramp_down = 0; + } else { + ramp_up = ti_lmu_common_convert_ramp_to_index(lmu_bank->ramp_up_usec); + ramp_down = ti_lmu_common_convert_ramp_to_index(lmu_bank->ramp_down_usec); + } + + if (ramp_up < 0 || ramp_down < 0) + return -EINVAL; + + ramp = (ramp_up << 4) | ramp_down; + + return regmap_write(regmap, lmu_bank->runtime_ramp_reg, ramp); + +} +EXPORT_SYMBOL(ti_lmu_common_set_ramp); + +int ti_lmu_common_get_ramp_params(struct device *dev, + struct fwnode_handle *child, + struct ti_lmu_bank *lmu_data) +{ + int ret; + + ret = fwnode_property_read_u32(child, "ramp-up-us", + &lmu_data->ramp_up_usec); + if (ret) + dev_warn(dev, "ramp-up-us property missing\n"); + + + ret = fwnode_property_read_u32(child, "ramp-down-us", + &lmu_data->ramp_down_usec); + if (ret) + dev_warn(dev, "ramp-down-us property missing\n"); + + return 0; +} +EXPORT_SYMBOL(ti_lmu_common_get_ramp_params); + +int ti_lmu_common_get_brt_res(struct device *dev, struct fwnode_handle *child, + struct ti_lmu_bank *lmu_data) +{ + int ret; + + ret = device_property_read_u32(dev, "ti,brightness-resolution", + &lmu_data->max_brightness); + if (ret) + ret = fwnode_property_read_u32(child, + "ti,brightness-resolution", + &lmu_data->max_brightness); + if (lmu_data->max_brightness <= 0) { + lmu_data->max_brightness = MAX_BRIGHTNESS_8BIT; + return ret; + } + + if (lmu_data->max_brightness > MAX_BRIGHTNESS_11BIT) + lmu_data->max_brightness = MAX_BRIGHTNESS_11BIT; + + + return 0; +} +EXPORT_SYMBOL(ti_lmu_common_get_brt_res); + +MODULE_DESCRIPTION("TI LMU common LED framework"); +MODULE_AUTHOR("Sebastian Reichel"); +MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("ti-lmu-led-common"); diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 7fa9d174a40c..ce9429ca6dde 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -15,7 +15,7 @@ config LEDS_TRIGGER_TIMER This allows LEDs to be controlled by a programmable timer via sysfs. Some LED hardware can be programmed to start blinking the LED without any further software interaction. - For more details read Documentation/leds/leds-class.txt. + For more details read Documentation/leds/leds-class.rst. If unsure, say Y. diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c index a80bb82aacc2..80635183fac8 100644 --- a/drivers/leds/trigger/ledtrig-transient.c +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -3,7 +3,7 @@ // LED Kernel Transient Trigger // // Transient trigger allows one shot timer activation. Please refer to -// Documentation/leds/ledtrig-transient.txt for details +// Documentation/leds/ledtrig-transient.rst for details // Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com> // // Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's |