diff --git a/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml b/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml new file mode 100644 index 000000000000..48215ad82d9a --- /dev/null +++ b/Documentation/devicetree/bindings/leds/adi,ltc3220.yaml @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/adi,ltc3220.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices LTC3220 LED Driver + +maintainers: + - Edelweise Escala + +description: > + The LTC3220 is a multi-display LED driver, which contains a high-efficiency, + low-noise charge pump to provide power to up to 18 LED current sources. + The LEDs are individually configurable to 64-step linear brightness control, + blinking and gradation control via 2-wire I2C interface. + + For more product information please see the link below + https://www.analog.com/en/products/ltc3220.html + +properties: + compatible: + const: adi,ltc3220 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + reset-gpios: + maxItems: 1 + +patternProperties: + '^led@([1-9a-f]|1[0-2])$': + type: object + $ref: /schemas/leds/common.yaml# + unevaluatedProperties: false + properties: + reg: + description: + Output channel for the LED (1-18 maps to LED outputs D1-D18). + Unit-address must be in hexadecimal (1-12 hex = 1-18 decimal). + For aggregated LED control, define only one LED node with reg = <0x1> + and use led-sources to list all controlled outputs. Only reg 1 should + be present when using led-sources. + items: + - minimum: 1 + maximum: 18 + + required: + - reg + + if: + required: + - led-sources + then: + properties: + reg: + items: + - const: 1 + +required: + - compatible + - reg + - '#address-cells' + - '#size-cells' + +additionalProperties: false + +examples: + - | + // Independent LEDs + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@1c { + compatible = "adi,ltc3220"; + reg = <0x1c>; + #address-cells = <1>; + #size-cells = <0>; + reset-gpios = <&gpio 17 GPIO_ACTIVE_LOW>; + + led@1 { + reg = <0x1>; + function = LED_FUNCTION_INDICATOR; + function-enumerator = <1>; + }; + + led@2 { + reg = <0x2>; + function = LED_FUNCTION_INDICATOR; + function-enumerator = <2>; + }; + + led@3 { + reg = <0x3>; + function = LED_FUNCTION_INDICATOR; + function-enumerator = <3>; + }; + }; + }; + + - | + // Aggregated LED + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@1c { + compatible = "adi,ltc3220"; + reg = <0x1c>; + #address-cells = <1>; + #size-cells = <0>; + + led@1 { + reg = <0x1>; + led-sources = <0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf 0x10 0x11 0x12>; + function = LED_FUNCTION_BACKLIGHT; + }; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml b/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml new file mode 100644 index 000000000000..95fa311fddb5 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/brcm,bcm6358-leds.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/brcm,bcm6358-leds.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LEDs connected to Broadcom BCM6358 controller + +description: | + This controller is present on BCM6358 and BCM6368. + In these SoCs there are Serial LEDs (LEDs connected to a 74x164 controller), + which can either be controlled by software (exporting the 74x164 as spi-gpio. + See Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml), or + by hardware using this driver. + +maintainers: + - Álvaro Fernández Rojas + +properties: + compatible: + const: brcm,bcm6358-leds + + reg: + maxItems: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + brcm,clk-div: + description: SCK signal divider. + default: 1 + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [1, 2, 4, 8] + + brcm,clk-dat-low: + description: Makes clock and data signals active low. + type: boolean + +patternProperties: + "^led@1?[0-9a-f]$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + description: Each LED is represented as a sub-node of + this device. + + properties: + reg: + description: LED pin number (0 to 31). + maxItems: 1 + + required: + - reg + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + led-controller@fffe00d0 { + compatible = "brcm,bcm6358-leds"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xfffe00d0 0x8>; + + led@0 { + reg = <0>; + active-low; + label = "white:alarm"; + }; + led@2 { + reg = <2>; + active-low; + label = "white:tv"; + }; + led@3 { + reg = <3>; + active-low; + label = "white:tel"; + }; + led@4 { + reg = <4>; + active-low; + label = "white:adsl"; + }; + }; +... diff --git a/Documentation/devicetree/bindings/leds/common.yaml b/Documentation/devicetree/bindings/leds/common.yaml index f4e44b33f56d..a0ef2f2f0724 100644 --- a/Documentation/devicetree/bindings/leds/common.yaml +++ b/Documentation/devicetree/bindings/leds/common.yaml @@ -73,6 +73,16 @@ properties: - keep default: off + default-intensity: + description: + The initial intensity of the LED color component. As the intensity of + each sub-LED is multiplied with the overall brightness, without this + property on a sub-LED, it may effectively be initialized at minimum + brightness regardless of its linux,default-trigger and default-brightness + properties. + $ref: /schemas/types.yaml#/definitions/uint32 + default: 0 + linux,default-trigger: description: This parameter, if present, is a string defining the trigger assigned to @@ -106,6 +116,8 @@ properties: - bluetooth-power # LED indicates camera flash state - flash + # LED indicates level of GPIO input referenced by trigger-sources + - gpio # LED indicated keyboard capslock - kbd-capslock # LED indicates MTD memory activity diff --git a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt b/Documentation/devicetree/bindings/leds/leds-bcm6358.txt deleted file mode 100644 index 211ffc3c4a20..000000000000 --- a/Documentation/devicetree/bindings/leds/leds-bcm6358.txt +++ /dev/null @@ -1,143 +0,0 @@ -LEDs connected to Broadcom BCM6358 controller - -This controller is present on BCM6358 and BCM6368. -In these SoCs there are Serial LEDs (LEDs connected to a 74x164 controller), -which can either be controlled by software (exporting the 74x164 as spi-gpio. -See Documentation/devicetree/bindings/gpio/fairchild,74hc595.yaml), or -by hardware using this driver. - -Required properties: - - compatible : should be "brcm,bcm6358-leds". - - #address-cells : must be 1. - - #size-cells : must be 0. - - reg : BCM6358 LED controller address and size. - -Optional properties: - - brcm,clk-div : SCK signal divider. Possible values are 1, 2, 4 and 8. - Default : 1 - - brcm,clk-dat-low : Boolean, makes clock and data signals active low. - Default : false - -Each LED is represented as a sub-node of the brcm,bcm6358-leds device. - -LED sub-node required properties: - - reg : LED pin number (only LEDs 0 to 31 are valid). - -LED sub-node optional properties: - - label : see Documentation/devicetree/bindings/leds/common.txt - - default-state : see - Documentation/devicetree/bindings/leds/common.txt - - linux,default-trigger : see - Documentation/devicetree/bindings/leds/common.txt - -Examples: -Scenario 1 : BCM6358 - leds0: led-controller@fffe00d0 { - compatible = "brcm,bcm6358-leds"; - #address-cells = <1>; - #size-cells = <0>; - reg = <0xfffe00d0 0x8>; - - alarm_white { - reg = <0>; - active-low; - label = "white:alarm"; - }; - tv_white { - reg = <2>; - active-low; - label = "white:tv"; - }; - tel_white { - reg = <3>; - active-low; - label = "white:tel"; - }; - adsl_white { - reg = <4>; - active-low; - label = "white:adsl"; - }; - }; - -Scenario 2 : BCM6368 - leds0: led-controller@100000d0 { - compatible = "brcm,bcm6358-leds"; - #address-cells = <1>; - #size-cells = <0>; - reg = <0x100000d0 0x8>; - brcm,pol-low; - brcm,clk-div = <4>; - - power_red { - reg = <0>; - active-low; - label = "red:power"; - }; - power_green { - reg = <1>; - active-low; - label = "green:power"; - default-state = "on"; - }; - power_blue { - reg = <2>; - label = "blue:power"; - }; - broadband_red { - reg = <3>; - active-low; - label = "red:broadband"; - }; - broadband_green { - reg = <4>; - label = "green:broadband"; - }; - broadband_blue { - reg = <5>; - active-low; - label = "blue:broadband"; - }; - wireless_red { - reg = <6>; - active-low; - label = "red:wireless"; - }; - wireless_green { - reg = <7>; - active-low; - label = "green:wireless"; - }; - wireless_blue { - reg = <8>; - label = "blue:wireless"; - }; - phone_red { - reg = <9>; - active-low; - label = "red:phone"; - }; - phone_green { - reg = <10>; - active-low; - label = "green:phone"; - }; - phone_blue { - reg = <11>; - label = "blue:phone"; - }; - upgrading_red { - reg = <12>; - active-low; - label = "red:upgrading"; - }; - upgrading_green { - reg = <13>; - active-low; - label = "green:upgrading"; - }; - upgrading_blue { - reg = <14>; - label = "blue:upgrading"; - }; - }; diff --git a/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml b/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml index a31a202afe5c..7bf687c89411 100644 --- a/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml +++ b/Documentation/devicetree/bindings/leds/leds-pwm-multicolor.yaml @@ -45,6 +45,8 @@ properties: color: true + default-intensity: true + required: - pwms - color @@ -63,12 +65,14 @@ examples: multi-led { color = ; + linux,default-trigger = "default-on"; function = LED_FUNCTION_INDICATOR; max-brightness = <65535>; led-red { pwms = <&pwm1 0 1000000>; color = ; + default-intensity = <65535>; }; led-green { diff --git a/Documentation/leds/leds-st1202.rst b/Documentation/leds/leds-st1202.rst index 1a09fbfcedcf..ed32eb3a27d4 100644 --- a/Documentation/leds/leds-st1202.rst +++ b/Documentation/leds/leds-st1202.rst @@ -16,8 +16,9 @@ in terms of PWM duty-cycle and duration (ms). To be compatible with the hardware pattern format, maximum 8 tuples of brightness (PWM) and duration must be written to hw_pattern. +- Brightness range: 0-255 - Min pattern duration: 22 ms -- Max pattern duration: 5660 ms +- Max pattern duration: 5610 ms The format of the hardware pattern values should be: "brightness duration brightness duration ..." @@ -26,9 +27,7 @@ The format of the hardware pattern values should be: ---------------------------- Specify a pattern repeat number, which is common for all channels. -Default is 1; negative numbers and 0 are invalid. +Default is 1. Writing 0 is invalid. Writing -1 or 255 repeats the +pattern indefinitely. This file will always return the originally written repeat number. - -When the 255 value is written to it, all patterns will repeat -indefinitely. diff --git a/MAINTAINERS b/MAINTAINERS index 6352047d6d04..d6dce1c7e341 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15451,6 +15451,14 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/temperature/adi,ltc2983.yaml F: drivers/iio/temperature/ltc2983.c +LTC3220 LED DRIVER +M: Edelweise Escala +L: linux-leds@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/leds/adi,ltc3220.yaml +F: drivers/leds/leds-ltc3220.c + LTC4282 HARDWARE MONITOR DRIVER M: Nuno Sa L: linux-hwmon@vger.kernel.org diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f4a0a3c8c870..31b1e3ff094c 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -1000,6 +1000,19 @@ config LEDS_ST1202 Say Y to enable support for LEDs connected to LED1202 LED driver chips accessed via the I2C bus. +config LEDS_LTC3220 + tristate "LED Driver for Analog Devices Inc. LTC3220" + depends on I2C && LEDS_CLASS + select REGMAP_I2C + help + Say Y to enable support for the Analog Devices LTC3220 + 18-channel LED controller with I2C interface. + The driver supports individual LED brightness control (64 steps), + hardware-assisted blinking and gradation effects. + + To compile this driver as a module, choose M here: the module will + be called leds-ltc3220. + config LEDS_TPS6105X tristate "LED support for TI TPS6105X" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 7db3768912ca..a68244bd50fb 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o +obj-$(CONFIG_LEDS_LTC3220) += leds-ltc3220.o obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o obj-$(CONFIG_LEDS_MAX77705) += leds-max77705.o diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c index 45c0662df933..12b70ee31b9a 100644 --- a/drivers/leds/blink/leds-bcm63138.c +++ b/drivers/leds/blink/leds-bcm63138.c @@ -236,8 +236,8 @@ static void bcm63138_leds_create_led(struct bcm63138_leds *leds, pinctrl = devm_pinctrl_get_select_default(led->cdev.dev); if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) { - dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n", - np, PTR_ERR(pinctrl)); + dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %pe\n", + np, pinctrl); } bit = BIT(led->pin); diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 8ae71c2e91e0..08d5a911ac25 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -211,7 +211,6 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, const struct gpio_led *template) { struct gpio_desc *gpiod; - int ret; /* * This means the LED does not come from the device tree @@ -222,16 +221,29 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, gpiod = devm_gpiod_get_index_optional(dev, NULL, idx, GPIOD_OUT_LOW); if (IS_ERR(gpiod)) return gpiod; - if (gpiod) { - gpiod_set_consumer_name(gpiod, template->name); - return gpiod; - } - /* - * This is the legacy code path for platform code that - * still uses GPIO numbers. Ultimately we would like to get - * rid of this block completely. - */ + gpiod_set_consumer_name(gpiod, template->name); + return gpiod; +} + +#ifdef CONFIG_GPIOLIB_LEGACY +/* + * This is the legacy code path for platform code that still uses + * GPIO numbers, mainly MIPS and SuperH board files. + * Ultimately we would like to get rid of this block completely. + * + * ppc44x-warp sets the template->gpiod directly instead of + * adding a lookup table or device properties. This is not + * much better. + */ +static struct gpio_desc *gpio_led_get_legacy_gpiod(struct device *dev, int idx, + const struct gpio_led *template) +{ + struct gpio_desc *gpiod; + int ret; + + if (template->gpiod) + return template->gpiod; /* skip leds that aren't available */ if (!gpio_is_valid(template->gpio)) @@ -251,6 +263,13 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, return gpiod; } +#else +static struct gpio_desc *gpio_led_get_legacy_gpiod(struct device *dev, int idx, + const struct gpio_led *template) +{ + return template->gpiod ?: ERR_PTR(-ENOENT); +} +#endif static int gpio_led_probe(struct platform_device *pdev) { @@ -269,14 +288,13 @@ static int gpio_led_probe(struct platform_device *pdev) const struct gpio_led *template = &pdata->leds[i]; struct gpio_led_data *led_dat = &priv->leds[i]; - if (template->gpiod) - led_dat->gpiod = template->gpiod; - else + led_dat->gpiod = gpio_led_get_gpiod(dev, i, template); + if (!led_dat->gpiod) led_dat->gpiod = - gpio_led_get_gpiod(dev, i, template); + gpio_led_get_legacy_gpiod(dev, i, template); if (IS_ERR(led_dat->gpiod)) { - dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n", - template->gpio, template->name); + dev_info(dev, "Skipping unavailable LED gpio %s\n", + template->name); continue; } diff --git a/drivers/leds/leds-ltc3220.c b/drivers/leds/leds-ltc3220.c new file mode 100644 index 000000000000..5060d5042fe1 --- /dev/null +++ b/drivers/leds/leds-ltc3220.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LTC3220 18-Channel LED Driver + * + * Copyright 2026 Analog Devices Inc. + * + * Author: Edelweise Escala + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LTC3220 Registers */ +#define LTC3220_COMMAND_REG 0x00 +#define LTC3220_QUICK_WRITE_MASK BIT(0) +#define LTC3220_SHUTDOWN_MASK BIT(3) + +#define LTC3220_ULED_REG(x) (0x01 + (x)) +#define LTC3220_LED_CURRENT_MASK GENMASK(5, 0) +#define LTC3220_LED_MODE_MASK GENMASK(7, 6) + +#define LTC3220_GRAD_BLINK_REG 0x13 +#define LTC3220_GRADATION_MASK GENMASK(2, 0) +#define LTC3220_GRADATION_DIRECTION_MASK BIT(0) +#define LTC3220_GRADATION_PERIOD_MASK GENMASK(2, 1) +#define LTC3220_BLINK_MASK GENMASK(4, 3) + +#define LTC3220_NUM_LEDS 18 +#define LTC3220_MAX_BRIGHTNESS 63 + +#define LTC3220_GRADATION_RAMP_TIME_240MS 240 +#define LTC3220_GRADATION_RAMP_TIME_480MS 480 + +#define LTC3220_BLINK_ON_156MS 156 +#define LTC3220_BLINK_ON_625MS 625 +#define LTC3220_BLINK_PERIOD_1250MS 1250 +#define LTC3220_BLINK_PERIOD_2500MS 2500 + +#define LTC3220_BLINK_SHORT_ON_TIME BIT(0) +#define LTC3220_BLINK_LONG_PERIOD BIT(1) + +enum ltc3220_led_mode { + LTC3220_NORMAL_MODE, + LTC3220_BLINK_MODE, + LTC3220_GRADATION_MODE, +}; + +enum ltc3220_blink_mode { + LTC3220_BLINK_MODE_625MS_1250MS, + LTC3220_BLINK_MODE_156MS_1250MS, + LTC3220_BLINK_MODE_625MS_2500MS, + LTC3220_BLINK_MODE_156MS_2500MS +}; + +enum ltc3220_gradation_mode { + LTC3220_GRADATION_MODE_DISABLED, + LTC3220_GRADATION_MODE_240MS_RAMP_TIME, + LTC3220_GRADATION_MODE_480MS_RAMP_TIME, + LTC3220_GRADATION_MODE_960MS_RAMP_TIME +}; + +static const struct regmap_config ltc3220_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LTC3220_GRAD_BLINK_REG, + .cache_type = REGCACHE_FLAT_S, +}; + +struct ltc3220_uled_cfg { + struct led_classdev led_cdev; + u8 reg_value; + u8 led_index; + bool registered; +}; + +struct ltc3220 { + struct ltc3220_uled_cfg uled_cfg[LTC3220_NUM_LEDS]; + struct regmap *regmap; + struct mutex lock; +}; + +/* + * Set LED brightness. Hardware supports 0-63 brightness levels. + * Mode switching (blink/gradation) is handled through dedicated callbacks. + * + * In aggregated mode only a single LED (reg = 1) is registered and the + * hardware quick-write feature propagates the write to all 18 channels, so + * there is no need to update the other registers explicitly. + */ +static int __ltc3220_set_led_data(struct ltc3220 *ltc3220, + struct ltc3220_uled_cfg *uled_cfg, + enum led_brightness brightness) +{ + int ret; + + brightness &= LTC3220_LED_CURRENT_MASK; + + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + brightness); + if (ret) + return ret; + + uled_cfg->reg_value = brightness; + + return 0; +} + +static int ltc3220_set_led_data(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220, + uled_cfg[0]); + int ret; + + mutex_lock(<c3220->lock); + ret = __ltc3220_set_led_data(ltc3220, uled_cfg, brightness); + mutex_unlock(<c3220->lock); + + return ret; +} + +static enum led_brightness ltc3220_get_led_data(struct led_classdev *led_cdev) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + + return uled_cfg->reg_value; +} + +/* + * LTC3220 pattern support for hardware-assisted breathing/gradation. + * The hardware supports 3 gradation ramp times (240ms, 480ms, 960ms) + * and can ramp up or down. The gradation period and direction are chip-global + * registers (LTC3220_GRAD_BLINK_REG), affecting all 18 channels simultaneously. + * This is a hardware limitation, not a driver bug. + * + * Pattern array interpretation: + * pattern[0].brightness = start brightness (0-63) + * pattern[0].delta_t = ramp time in milliseconds + * pattern[1].brightness = end brightness (0-63) + * pattern[1].delta_t = (optional, can be 0 or same as pattern[0].delta_t) + */ +static int ltc3220_pattern_set(struct led_classdev *led_cdev, + struct led_pattern *pattern, + u32 len, int repeat) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220, + uled_cfg[0]); + u8 gradation_period; + u8 start_brightness; + u8 end_brightness; + u8 gradation_val; + u8 led_mode; + bool is_increasing; + int ret; + + if (len != 2) + return -EINVAL; + + start_brightness = clamp_val(pattern[0].brightness, 0, LTC3220_LED_CURRENT_MASK); + end_brightness = clamp_val(pattern[1].brightness, 0, LTC3220_LED_CURRENT_MASK); + + is_increasing = end_brightness > start_brightness; + + if (pattern[0].delta_t == 0) + gradation_period = LTC3220_GRADATION_MODE_DISABLED; + else if (pattern[0].delta_t <= LTC3220_GRADATION_RAMP_TIME_240MS) + gradation_period = LTC3220_GRADATION_MODE_240MS_RAMP_TIME; + else if (pattern[0].delta_t <= LTC3220_GRADATION_RAMP_TIME_480MS) + gradation_period = LTC3220_GRADATION_MODE_480MS_RAMP_TIME; + else + gradation_period = LTC3220_GRADATION_MODE_960MS_RAMP_TIME; + + gradation_val = FIELD_PREP(LTC3220_GRADATION_PERIOD_MASK, gradation_period); + gradation_val |= FIELD_PREP(LTC3220_GRADATION_DIRECTION_MASK, is_increasing); + + /* + * With the ramp disabled (delta_t == 0) there is no gradation to run, + * so apply the end brightness directly in NORMAL mode instead of + * leaving the channel in gradation mode with a disabled ramp. + */ + led_mode = gradation_period == LTC3220_GRADATION_MODE_DISABLED ? + LTC3220_NORMAL_MODE : LTC3220_GRADATION_MODE; + + mutex_lock(<c3220->lock); + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_GRAD_BLINK_REG, + LTC3220_GRADATION_MASK, gradation_val); + if (ret) + goto unlock; + + if (led_mode == LTC3220_GRADATION_MODE) { + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + start_brightness & LTC3220_LED_CURRENT_MASK); + if (ret) + goto unlock; + + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + FIELD_PREP(LTC3220_LED_MODE_MASK, led_mode) | + (end_brightness & LTC3220_LED_CURRENT_MASK)); + if (ret) + goto unlock; + + uled_cfg->reg_value = end_brightness; + } else { + ret = __ltc3220_set_led_data(ltc3220, uled_cfg, end_brightness); + if (ret) + goto unlock; + } + +unlock: + mutex_unlock(<c3220->lock); + return ret; +} + +static int ltc3220_pattern_clear(struct led_classdev *led_cdev) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220, + uled_cfg[0]); + int ret; + + mutex_lock(<c3220->lock); + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + LTC3220_LED_MODE_MASK, LTC3220_NORMAL_MODE); + if (ret) + goto unlock; + + ret = __ltc3220_set_led_data(ltc3220, uled_cfg, LED_OFF); + +unlock: + mutex_unlock(<c3220->lock); + return ret; +} + +/* + * LTC3220 has a global blink configuration that affects all LEDs. + * This implementation allows per-LED blink requests via sysfs, but setting + * blink on any LED reprograms the timing for all 18 channels simultaneously. + * The delay values are mapped to the hardware's discrete blink rates. + * + * HARDWARE LIMITATION: This is not a driver bug. Per-LED blink timing control + * is not possible with this hardware due to the global blink register. + */ +static int ltc3220_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct ltc3220_uled_cfg *uled_cfg = container_of(led_cdev, struct ltc3220_uled_cfg, + led_cdev); + struct ltc3220 *ltc3220 = container_of(uled_cfg - uled_cfg->led_index, struct ltc3220, + uled_cfg[0]); + u8 blink_brightness; + u8 blink_mode = 0; + int ret; + + if (*delay_on <= LTC3220_BLINK_ON_156MS) + blink_mode = LTC3220_BLINK_SHORT_ON_TIME; + + if (*delay_on + *delay_off > LTC3220_BLINK_PERIOD_1250MS) + blink_mode |= LTC3220_BLINK_LONG_PERIOD; + + switch (blink_mode) { + case LTC3220_BLINK_MODE_625MS_1250MS: + *delay_on = LTC3220_BLINK_ON_625MS; + *delay_off = LTC3220_BLINK_PERIOD_1250MS - LTC3220_BLINK_ON_625MS; + break; + case LTC3220_BLINK_MODE_156MS_1250MS: + *delay_on = LTC3220_BLINK_ON_156MS; + *delay_off = LTC3220_BLINK_PERIOD_1250MS - LTC3220_BLINK_ON_156MS; + break; + case LTC3220_BLINK_MODE_625MS_2500MS: + *delay_on = LTC3220_BLINK_ON_625MS; + *delay_off = LTC3220_BLINK_PERIOD_2500MS - LTC3220_BLINK_ON_625MS; + break; + case LTC3220_BLINK_MODE_156MS_2500MS: + *delay_on = LTC3220_BLINK_ON_156MS; + *delay_off = LTC3220_BLINK_PERIOD_2500MS - LTC3220_BLINK_ON_156MS; + break; + } + + mutex_lock(<c3220->lock); + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_GRAD_BLINK_REG, + LTC3220_BLINK_MASK, FIELD_PREP(LTC3220_BLINK_MASK, blink_mode)); + if (ret) + goto unlock; + + blink_brightness = uled_cfg->reg_value ? : led_cdev->max_brightness; + + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(uled_cfg->led_index), + FIELD_PREP(LTC3220_LED_MODE_MASK, LTC3220_BLINK_MODE) | + (blink_brightness & LTC3220_LED_CURRENT_MASK)); + if (ret) + goto unlock; + + uled_cfg->reg_value = blink_brightness; + +unlock: + mutex_unlock(<c3220->lock); + return ret; +} + +static void ltc3220_reset_gpio_action(void *data) +{ + struct gpio_desc *reset_gpio = data; + + gpiod_set_value_cansleep(reset_gpio, 1); +} + +static int ltc3220_reset(struct ltc3220 *ltc3220, struct i2c_client *client) +{ + struct gpio_desc *reset_gpio; + int ret; + + reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(reset_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(reset_gpio), "Failed on reset GPIO\n"); + + if (reset_gpio) { + usleep_range(10000, 12000); + gpiod_set_value_cansleep(reset_gpio, 0); + usleep_range(10000, 12000); + + ret = devm_add_action_or_reset(&client->dev, ltc3220_reset_gpio_action, + reset_gpio); + if (ret) + return ret; + } + + ret = regmap_write(ltc3220->regmap, LTC3220_COMMAND_REG, 0); + if (ret) + return ret; + + for (int i = 0; i < LTC3220_NUM_LEDS; i++) { + ret = regmap_write(ltc3220->regmap, LTC3220_ULED_REG(i), 0); + if (ret) + return ret; + } + + return regmap_write(ltc3220->regmap, LTC3220_GRAD_BLINK_REG, 0); +} + +static int ltc3220_suspend(struct device *dev) +{ + struct ltc3220 *ltc3220 = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG, + LTC3220_SHUTDOWN_MASK, LTC3220_SHUTDOWN_MASK); + if (ret) + return ret; + + regcache_mark_dirty(ltc3220->regmap); + + return 0; +} + +static int ltc3220_resume(struct device *dev) +{ + struct ltc3220 *ltc3220 = i2c_get_clientdata(to_i2c_client(dev)); + bool quick_write_enabled; + unsigned int command_reg; + int ret; + + ret = regmap_read(ltc3220->regmap, LTC3220_COMMAND_REG, &command_reg); + if (ret) + return ret; + + quick_write_enabled = command_reg & LTC3220_QUICK_WRITE_MASK; + + if (quick_write_enabled) { + ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG, + LTC3220_QUICK_WRITE_MASK, 0); + if (ret) + return ret; + } + + ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG, + LTC3220_SHUTDOWN_MASK, 0); + if (ret) + return ret; + + usleep_range(10000, 12000); + + ret = regcache_sync(ltc3220->regmap); + if (ret) + return ret; + + if (quick_write_enabled) { + ret = regmap_update_bits(ltc3220->regmap, LTC3220_COMMAND_REG, + LTC3220_QUICK_WRITE_MASK, + LTC3220_QUICK_WRITE_MASK); + if (ret) + return ret; + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ltc3220_pm_ops, ltc3220_suspend, ltc3220_resume); + +static int ltc3220_probe(struct i2c_client *client) +{ + struct ltc3220 *ltc3220; + bool aggregated_led_found = false; + int num_leds = 0; + u8 led_index = 0; + int ret; + + ltc3220 = devm_kzalloc(&client->dev, sizeof(*ltc3220), GFP_KERNEL); + if (!ltc3220) + return -ENOMEM; + + ltc3220->regmap = devm_regmap_init_i2c(client, <c3220_regmap_config); + if (IS_ERR(ltc3220->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(ltc3220->regmap), + "Failed to initialize regmap\n"); + + ret = devm_mutex_init(&client->dev, <c3220->lock); + if (ret) + return ret; + + i2c_set_clientdata(client, ltc3220); + + ret = ltc3220_reset(ltc3220, client); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to reset device\n"); + + /* First pass: validate configuration and set up LED structures */ + device_for_each_child_node_scoped(&client->dev, child) { + struct ltc3220_uled_cfg *led; + u32 source; + + ret = fwnode_property_read_u32(child, "reg", &source); + if (ret) + return dev_err_probe(&client->dev, ret, "Couldn't read LED address\n"); + + if (!source || source > LTC3220_NUM_LEDS) + return dev_err_probe(&client->dev, -EINVAL, "LED address out of range\n"); + + if (fwnode_property_present(child, "led-sources")) { + u32 led_sources[LTC3220_NUM_LEDS]; + int count; + + if (source != 1) + return dev_err_probe(&client->dev, -EINVAL, + "Aggregated LED out of range\n"); + + if (aggregated_led_found) + return dev_err_probe(&client->dev, -EINVAL, + "One Aggregated LED only\n"); + + count = fwnode_property_count_u32(child, "led-sources"); + if (count != LTC3220_NUM_LEDS) + return dev_err_probe(&client->dev, -EINVAL, + "Aggregated mode requires all %d outputs in led-sources, got %d\n", + LTC3220_NUM_LEDS, count); + + ret = fwnode_property_read_u32_array(child, "led-sources", + led_sources, LTC3220_NUM_LEDS); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to read led-sources array\n"); + + /* + * Validate array contents for DT correctness. The hardware + * quick-write broadcasts to all 18 channels regardless of + * array contents, but checking helps catch DT mistakes. + */ + for (int i = 0; i < LTC3220_NUM_LEDS; i++) { + if (led_sources[i] < 1 || led_sources[i] > LTC3220_NUM_LEDS) + return dev_err_probe(&client->dev, -EINVAL, + "Invalid output %u in led-sources\n", + led_sources[i]); + } + + aggregated_led_found = true; + } + + num_leds++; + + /* LED node reg/index/address goes from 1 to 18 */ + led_index = source - 1; + led = <c3220->uled_cfg[led_index]; + + if (led->registered) + return dev_err_probe(&client->dev, -EINVAL, + "Duplicate LED reg %u found\n", source); + + led->registered = true; + led->led_index = led_index; + led->reg_value = 0; + led->led_cdev.brightness_set_blocking = ltc3220_set_led_data; + led->led_cdev.brightness_get = ltc3220_get_led_data; + led->led_cdev.max_brightness = LTC3220_MAX_BRIGHTNESS; + led->led_cdev.blink_set = ltc3220_blink_set; + led->led_cdev.pattern_set = ltc3220_pattern_set; + led->led_cdev.pattern_clear = ltc3220_pattern_clear; + } + + /* + * Aggregated LED mode uses hardware quick-write to control all 18 LEDs + * simultaneously. This is mutually exclusive with individual LED control. + * See Documentation/devicetree/bindings/leds/adi,ltc3220.yaml for details + * on how to configure aggregated LED mode. + */ + if (aggregated_led_found && num_leds > 1) + return dev_err_probe(&client->dev, -EINVAL, + "Aggregated LED must be the only LED node\n"); + + if (num_leds == 0) + return dev_err_probe(&client->dev, -EINVAL, + "No LED nodes found in device tree\n"); + + if (aggregated_led_found) { + ret = regmap_update_bits(ltc3220->regmap, + LTC3220_COMMAND_REG, + LTC3220_QUICK_WRITE_MASK, + LTC3220_QUICK_WRITE_MASK); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to set quick write mode\n"); + } + + /* Second pass: register LEDs after validation */ + device_for_each_child_node_scoped(&client->dev, child) { + struct led_init_data init_data = {}; + struct ltc3220_uled_cfg *led; + u32 source; + + ret = fwnode_property_read_u32(child, "reg", &source); + if (ret) + return ret; + + if (!source || source > LTC3220_NUM_LEDS) + return dev_err_probe(&client->dev, -EINVAL, + "LED address out of range in second pass\n"); + + init_data.fwnode = child; + init_data.devicename = "ltc3220"; + + led_index = source - 1; + led = <c3220->uled_cfg[led_index]; + + ret = devm_led_classdev_register_ext(&client->dev, &led->led_cdev, &init_data); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to register LED class\n"); + } + + return 0; +} + +static const struct of_device_id ltc3220_of_match[] = { + { .compatible = "adi,ltc3220" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc3220_of_match); + +static struct i2c_driver ltc3220_led_driver = { + .driver = { + .name = "ltc3220", + .of_match_table = ltc3220_of_match, + .pm = pm_sleep_ptr(<c3220_pm_ops), + }, + .probe = ltc3220_probe, +}; +module_i2c_driver(ltc3220_led_driver); + +MODULE_AUTHOR("Edelweise Escala "); +MODULE_DESCRIPTION("LED driver for LTC3220 controllers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index f3bf59495b68..b2e081c8f139 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -327,9 +327,9 @@ static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset, struct pca9532_led *led = &data->leds[offset]; if (val) - led->state = PCA9532_ON; - else led->state = PCA9532_OFF; + else + led->state = PCA9532_ON; pca9532_setled(led); @@ -349,7 +349,7 @@ static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset) static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset) { /* To use as input ensure pin is not driven */ - pca9532_gpio_set_value(gc, offset, 0); + pca9532_gpio_set_value(gc, offset, 1); return 0; } @@ -397,10 +397,14 @@ static int pca9532_configure(struct i2c_client *client, for (i = 0; i < 2; i++) { data->pwm[i] = pdata->pwm[i]; data->psc[i] = pdata->psc[i]; - i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i), - data->pwm[i]); - i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i), - data->psc[i]); + err = i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i), + data->pwm[i]); + if (err < 0) + return err; + err = i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i), + data->psc[i]); + if (err < 0) + return err; } data->hw_blink = true; diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c index 7f68d956f694..168df5ecf27b 100644 --- a/drivers/leds/leds-st1202.c +++ b/drivers/leds/leds-st1202.c @@ -31,10 +31,11 @@ #define ST1202_ILED_REG0 0x09 #define ST1202_MAX_LEDS 12 #define ST1202_MAX_PATTERNS 8 -#define ST1202_MILLIS_PATTERN_DUR_MAX 5660 +#define ST1202_MILLIS_PATTERN_DUR_MAX (ST1202_MILLIS_PATTERN_DUR_MIN * U8_MAX) #define ST1202_MILLIS_PATTERN_DUR_MIN 22 #define ST1202_PATTERN_DUR 0x16 #define ST1202_PATTERN_PWM 0x1E +#define ST1202_PATTERN_PWM_FULL 0x0FFF #define ST1202_PATTERN_REP 0x15 struct st1202_led { @@ -85,7 +86,7 @@ static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val) static uint8_t st1202_prescalar_to_miliseconds(unsigned int value) { - return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1; + return value / ST1202_MILLIS_PATTERN_DUR_MIN; } static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num, @@ -127,37 +128,11 @@ static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern, st1202_prescalar_to_miliseconds(value)); } -static void st1202_brightness_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct st1202_led *led = cdev_to_st1202_led(led_cdev); - struct st1202_chip *chip = led->chip; - - guard(mutex)(&chip->lock); - - st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); -} - -static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) -{ - struct st1202_led *led = cdev_to_st1202_led(led_cdev); - struct st1202_chip *chip = led->chip; - u8 value = 0; - - guard(mutex)(&chip->lock); - - st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value); - - return value; -} - -static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) +static int __st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) { u8 chan_low, chan_high; int ret; - guard(mutex)(&chip->lock); - if (led_num <= 7) { ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low); if (ret < 0) @@ -185,6 +160,40 @@ static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active return 0; } +static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) +{ + guard(mutex)(&chip->lock); + + return __st1202_channel_set(chip, led_num, active); +} + +static void st1202_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + + guard(mutex)(&chip->lock); + + for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) + st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL); + st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); + __st1202_channel_set(chip, led->led_num, !!value); +} + +static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + u8 value = 0; + + guard(mutex)(&chip->lock); + + st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value); + + return value; +} + static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value) { struct st1202_led *led = cdev_to_st1202_led(ldev); @@ -200,12 +209,16 @@ static int st1202_led_pattern_clear(struct led_classdev *ldev) guard(mutex)(&chip->lock); + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + if (ret != 0) + return ret; + for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) { - ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF); + ret = st1202_pwm_pattern_write(chip, led->led_num, patt, ST1202_PATTERN_PWM_FULL); if (ret != 0) return ret; - ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN); + ret = st1202_write_reg(chip, ST1202_PATTERN_DUR + patt, 0); if (ret != 0) return ret; } @@ -224,13 +237,19 @@ static int st1202_led_pattern_set(struct led_classdev *ldev, if (len > ST1202_MAX_PATTERNS) return -EINVAL; - guard(mutex)(&chip->lock); - for (int patt = 0; patt < len; patt++) { if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) return -EINVAL; + } + guard(mutex)(&chip->lock); + + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + if (ret != 0) + return ret; + + for (int patt = 0; patt < len; patt++) { ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness); if (ret != 0) return ret; @@ -244,6 +263,10 @@ static int st1202_led_pattern_set(struct led_classdev *ldev, if (ret != 0) return ret; + ret = __st1202_channel_set(chip, led->led_num, true); + if (ret != 0) + return ret; + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR | ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT)); if (ret != 0) @@ -256,13 +279,19 @@ static int st1202_dt_init(struct st1202_chip *chip) { struct device *dev = &chip->client->dev; struct st1202_led *led; - int err, reg; + int err; + u32 reg; for_each_available_child_of_node_scoped(dev_of_node(dev), child) { err = of_property_read_u32(child, "reg", ®); if (err) return dev_err_probe(dev, err, "Invalid register\n"); + if (reg >= ST1202_MAX_LEDS) + return dev_err_probe(dev, -EINVAL, + "LED reg %u out of range [0, %d]\n", + reg, ST1202_MAX_LEDS - 1); + led = &chip->leds[reg]; led->is_active = true; led->fwnode = of_fwnode_handle(child); @@ -322,11 +351,6 @@ static int st1202_setup(struct st1202_chip *chip) if (ret < 0) return ret; - ret = st1202_write_reg(chip, ST1202_CONFIG_REG, - ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR); - if (ret < 0) - return ret; - return 0; } diff --git a/drivers/leds/rgb/leds-lp5860-core.c b/drivers/leds/rgb/leds-lp5860-core.c index fd0e2f6e6e0f..e21d5f2302be 100644 --- a/drivers/leds/rgb/leds-lp5860-core.c +++ b/drivers/leds/rgb/leds-lp5860-core.c @@ -204,9 +204,9 @@ int lp5860_device_init(struct device *dev) mutex_lock(&lp->lock); ret = regmap_update_bits(lp->regmap, LP5860_REG_DEV_INITIAL, LP5860_MODE_MASK, LP5860_MODE_1 << LP5860_MODE_SHIFT); + mutex_unlock(&lp->lock); if (ret) goto err_disable; - mutex_unlock(&lp->lock); ret = lp5860_init_dt(lp); if (ret) @@ -215,7 +215,6 @@ int lp5860_device_init(struct device *dev) return 0; err_disable: - mutex_unlock(&lp->lock); lp5860_chip_enable(lp, LP5860_CHIP_DISABLE); return ret; } diff --git a/drivers/leds/rgb/leds-lp5860-spi.c b/drivers/leds/rgb/leds-lp5860-spi.c index 5e0c44854a68..6bf6a625c28a 100644 --- a/drivers/leds/rgb/leds-lp5860-spi.c +++ b/drivers/leds/rgb/leds-lp5860-spi.c @@ -38,6 +38,7 @@ static int lp5860_probe(struct spi_device *spi) struct device *dev = &spi->dev; struct lp5860 *lp5860; unsigned int multi_leds; + int ret; multi_leds = device_get_child_node_count(dev); if (!multi_leds) { @@ -61,7 +62,10 @@ static int lp5860_probe(struct spi_device *spi) "Failed to initialise Regmap.\n"); lp5860->dev = dev; - mutex_init(&lp5860->lock); + + ret = devm_mutex_init(dev, &lp5860->lock); + if (ret) + return ret; spi_set_drvdata(spi, lp5860); @@ -70,10 +74,6 @@ static int lp5860_probe(struct spi_device *spi) static void lp5860_remove(struct spi_device *spi) { - struct lp5860 *lp5860 = spi_get_drvdata(spi); - - mutex_destroy(&lp5860->lock); - lp5860_device_remove(&spi->dev); } diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c index d5b303aab5d6..6365459ec27f 100644 --- a/drivers/leds/rgb/leds-pwm-multicolor.c +++ b/drivers/leds/rgb/leds-pwm-multicolor.c @@ -94,6 +94,8 @@ static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv, } subled[priv->mc_cdev.num_colors].color_index = color; + fwnode_property_read_u32(fwnode, "default-intensity", + &subled[priv->mc_cdev.num_colors].intensity); priv->mc_cdev.num_colors++; } diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index 64c078e997f2..5b4e92c14dbb 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -129,6 +129,22 @@ static void set_baseline_state(struct led_netdev_data *trigger_data) trigger_data->link_speed == SPEED_10000) blink_on = true; + if (test_bit(TRIGGER_NETDEV_LINK_25000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_25000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_40000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_40000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_50000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_50000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_100000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_100000) + blink_on = true; + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) && trigger_data->duplex == DUPLEX_HALF) blink_on = true; @@ -342,6 +358,10 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf, case TRIGGER_NETDEV_LINK_2500: case TRIGGER_NETDEV_LINK_5000: case TRIGGER_NETDEV_LINK_10000: + case TRIGGER_NETDEV_LINK_25000: + case TRIGGER_NETDEV_LINK_40000: + case TRIGGER_NETDEV_LINK_50000: + case TRIGGER_NETDEV_LINK_100000: case TRIGGER_NETDEV_HALF_DUPLEX: case TRIGGER_NETDEV_FULL_DUPLEX: case TRIGGER_NETDEV_TX: @@ -378,6 +398,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, case TRIGGER_NETDEV_LINK_2500: case TRIGGER_NETDEV_LINK_5000: case TRIGGER_NETDEV_LINK_10000: + case TRIGGER_NETDEV_LINK_25000: + case TRIGGER_NETDEV_LINK_40000: + case TRIGGER_NETDEV_LINK_50000: + case TRIGGER_NETDEV_LINK_100000: case TRIGGER_NETDEV_HALF_DUPLEX: case TRIGGER_NETDEV_FULL_DUPLEX: case TRIGGER_NETDEV_TX: @@ -401,7 +425,11 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, test_bit(TRIGGER_NETDEV_LINK_1000, &mode) || test_bit(TRIGGER_NETDEV_LINK_2500, &mode) || test_bit(TRIGGER_NETDEV_LINK_5000, &mode) || - test_bit(TRIGGER_NETDEV_LINK_10000, &mode))) + test_bit(TRIGGER_NETDEV_LINK_10000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_25000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_40000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_50000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_100000, &mode))) return -EINVAL; cancel_delayed_work_sync(&trigger_data->work); @@ -438,6 +466,10 @@ DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000); DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500); DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000); DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000); +DEFINE_NETDEV_TRIGGER(link_25000, TRIGGER_NETDEV_LINK_25000); +DEFINE_NETDEV_TRIGGER(link_40000, TRIGGER_NETDEV_LINK_40000); +DEFINE_NETDEV_TRIGGER(link_50000, TRIGGER_NETDEV_LINK_50000); +DEFINE_NETDEV_TRIGGER(link_100000, TRIGGER_NETDEV_LINK_100000); DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX); DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX); DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX); @@ -526,6 +558,10 @@ static umode_t netdev_trig_link_speed_visible(struct kobject *kobj, CHECK_LINK_MODE_ATTR(2500); CHECK_LINK_MODE_ATTR(5000); CHECK_LINK_MODE_ATTR(10000); + CHECK_LINK_MODE_ATTR(25000); + CHECK_LINK_MODE_ATTR(40000); + CHECK_LINK_MODE_ATTR(50000); + CHECK_LINK_MODE_ATTR(100000); } return 0; @@ -538,6 +574,10 @@ static struct attribute *netdev_trig_link_speed_attrs[] = { &dev_attr_link_2500.attr, &dev_attr_link_5000.attr, &dev_attr_link_10000.attr, + &dev_attr_link_25000.attr, + &dev_attr_link_40000.attr, + &dev_attr_link_50000.attr, + &dev_attr_link_100000.attr, NULL }; @@ -673,6 +713,10 @@ static void netdev_trig_work(struct work_struct *work) test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_25000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_40000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_50000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_100000, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode); interval = jiffies_to_msecs( diff --git a/include/linux/leds.h b/include/linux/leds.h index b16b803cc1ac..a515f075c29a 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -607,6 +607,10 @@ enum led_trigger_netdev_modes { TRIGGER_NETDEV_LINK_2500, TRIGGER_NETDEV_LINK_5000, TRIGGER_NETDEV_LINK_10000, + TRIGGER_NETDEV_LINK_25000, + TRIGGER_NETDEV_LINK_40000, + TRIGGER_NETDEV_LINK_50000, + TRIGGER_NETDEV_LINK_100000, TRIGGER_NETDEV_HALF_DUPLEX, TRIGGER_NETDEV_FULL_DUPLEX, TRIGGER_NETDEV_TX, @@ -676,8 +680,10 @@ typedef int (*gpio_blink_set_t)(struct gpio_desc *desc, int state, struct gpio_led { const char *name; const char *default_trigger; +#ifdef CONFIG_GPIOLIB_LEGACY unsigned gpio; unsigned active_low : 1; +#endif unsigned retain_state_suspended : 1; unsigned panic_indicator : 1; unsigned default_state : 2;