* [PATCH v5 0/2] leds: Add a driver for KTD202x
@ 2023-10-01 13:52 André Apitzsch
2023-10-01 13:52 ` [PATCH v5 1/2] dt-bindings: leds: Add Kinetic KTD2026/2027 LED André Apitzsch
2023-10-01 13:52 ` [PATCH v5 2/2] leds: add ktd202x driver André Apitzsch
0 siblings, 2 replies; 8+ messages in thread
From: André Apitzsch @ 2023-10-01 13:52 UTC (permalink / raw)
To: Pavel Machek, Lee Jones, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: linux-leds, devicetree, linux-kernel, phone-devel,
~postmarketos/upstreaming, Uwe Kleine-König,
André Apitzsch, Krzysztof Kozlowski
Add the binding description and the corresponding driver for
the Kinetic KTD2026 and KTD2027.
Signed-off-by: André Apitzsch <git@apitzsch.eu>
---
Changes in v5:
- Restructure brightness_set() + add comments to it to be easier understandable
- Add some line breaks + remove little line-wraps to improve readability
- Move parts of add_led() to setup_led_{rgb,single}()
- Move mutex_init() to the end of probe to omit gotos
- Fix grammar
- Set initial intensity to max brightness to avoid LED staying off when
brightness is changed after switching to timer trigger, because of zero
intensity
- Link to v4: https://lore.kernel.org/r/20230923-ktd202x-v4-0-14f724f6d43b@apitzsch.eu
Changes in v4:
- Annotate struct ktd202x with __counted_by
- Link to v3: https://lore.kernel.org/r/20230906-ktd202x-v3-0-7fcb91c65d3a@apitzsch.eu
Changes in v3:
- Add r-b to bindings patch
- Replace .probe_new by .probe
- Link to v2: https://lore.kernel.org/r/20230901-ktd202x-v2-0-3cb8b0ca02ed@apitzsch.eu
Changes in v2:
- Make binding description filename match compatible
- Address comments by Lee Jones
- Extend driver description in Kconfig
- Add copyright + link to datasheet
- Add unit to definition/variable names, where needed
- Define magic numbers
- Remove forward declaration of 'struct ktd202x'
- Remove superfluous comments
- Get rid of struct ktd202x_info
- Join ktd202x_chip_init() with ktd202x_chip_enable()
- Return the error on ktd202x_chip_disable()
- Remove unreachable case from chip_in_use()
- Rename ktd202x_brightness_set() argument from num_colors to num_channels
- Forward errors received in ktd202x_brightness_set()
- Remove variable for 'num_channels = 1'
- Add some explanations to blink time calculation
- Remove unneeded lcdev from ktd202x_blink_*_set()
- Add define for max brightness and replace deprecated LED_FULL by it
- Move setting led_classdev.brightness to ktd202x_brightness_*_set()
- Move mutex_lock inside ktd202x_blink_set()
- Add comment that 'color' property is optional (allow EINVAL)
- Replace escaped double quotes by single quotes
- Avoid overloading variable 'color'
- Do not lock during probe
- Remove usage of 'of_match_ptr'
- Document interrupt and pull-up supply, like done for aw2013[1]
- Fix error in num_steps calculation
- Link to v1: https://lore.kernel.org/r/20230618-ktd202x-v1-0-fc182fefadd7@apitzsch.eu
[1] https://lore.kernel.org/linux-leds/20230815-aw2013-vio-v3-0-2505296b0856@gerhold.net/
---
André Apitzsch (2):
dt-bindings: leds: Add Kinetic KTD2026/2027 LED
leds: add ktd202x driver
.../devicetree/bindings/leds/kinetic,ktd202x.yaml | 171 ++++++
drivers/leds/rgb/Kconfig | 13 +
drivers/leds/rgb/Makefile | 1 +
drivers/leds/rgb/leds-ktd202x.c | 619 +++++++++++++++++++++
4 files changed, 804 insertions(+)
---
base-commit: 165adeea3617ea22dc49f8880474ebf3a98b696d
change-id: 20230618-ktd202x-546b2a7d240b
Best regards,
--
André Apitzsch <git@apitzsch.eu>
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v5 1/2] dt-bindings: leds: Add Kinetic KTD2026/2027 LED 2023-10-01 13:52 [PATCH v5 0/2] leds: Add a driver for KTD202x André Apitzsch @ 2023-10-01 13:52 ` André Apitzsch 2023-10-01 13:52 ` [PATCH v5 2/2] leds: add ktd202x driver André Apitzsch 1 sibling, 0 replies; 8+ messages in thread From: André Apitzsch @ 2023-10-01 13:52 UTC (permalink / raw) To: Pavel Machek, Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-leds, devicetree, linux-kernel, phone-devel, ~postmarketos/upstreaming, Uwe Kleine-König, André Apitzsch, Krzysztof Kozlowski Document Kinetic KTD2026/2027 LED driver devicetree bindings. Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> Signed-off-by: André Apitzsch <git@apitzsch.eu> --- .../devicetree/bindings/leds/kinetic,ktd202x.yaml | 171 +++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/Documentation/devicetree/bindings/leds/kinetic,ktd202x.yaml b/Documentation/devicetree/bindings/leds/kinetic,ktd202x.yaml new file mode 100644 index 000000000000..832c030a5acf --- /dev/null +++ b/Documentation/devicetree/bindings/leds/kinetic,ktd202x.yaml @@ -0,0 +1,171 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/kinetic,ktd202x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Kinetic KTD2026/7 RGB/White LED Driver + +maintainers: + - André Apitzsch <git@apitzsch.eu> + +description: | + The KTD2026/7 is a RGB/White LED driver with I2C interface. + + The data sheet can be found at: + https://www.kinet-ic.com/uploads/KTD2026-7-04h.pdf + +properties: + compatible: + enum: + - kinetic,ktd2026 + - kinetic,ktd2027 + + reg: + maxItems: 1 + + vin-supply: + description: Regulator providing power to the "VIN" pin. + + vio-supply: + description: Regulator providing power for pull-up of the I/O lines. + Note that this regulator does not directly connect to KTD2026, but is + needed for the correct operation of the status ("ST") and I2C lines. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + multi-led: + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties: + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + patternProperties: + "^led@[0-3]$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + description: Index of the LED. + minimum: 0 + maximum: 3 + + required: + - reg + - color + + required: + - "#address-cells" + - "#size-cells" + +patternProperties: + "^led@[0-3]$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + description: Index of the LED. + minimum: 0 + maximum: 3 + + required: + - reg + +required: + - compatible + - reg + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include <dt-bindings/leds/common.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@30 { + compatible = "kinetic,ktd2026"; + reg = <0x30>; + #address-cells = <1>; + #size-cells = <0>; + + vin-supply = <&pm8916_l17>; + vio-supply = <&pm8916_l6>; + + led@0 { + reg = <0>; + function = LED_FUNCTION_STATUS; + color = <LED_COLOR_ID_RED>; + }; + + led@1 { + reg = <1>; + function = LED_FUNCTION_STATUS; + color = <LED_COLOR_ID_GREEN>; + }; + + led@2 { + reg = <2>; + function = LED_FUNCTION_STATUS; + color = <LED_COLOR_ID_BLUE>; + }; + }; + }; + - | + #include <dt-bindings/leds/common.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@30 { + compatible = "kinetic,ktd2026"; + reg = <0x30>; + #address-cells = <1>; + #size-cells = <0>; + + vin-supply = <&pm8916_l17>; + vio-supply = <&pm8916_l6>; + + multi-led { + color = <LED_COLOR_ID_RGB>; + function = LED_FUNCTION_STATUS; + + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + color = <LED_COLOR_ID_RED>; + }; + + led@1 { + reg = <1>; + color = <LED_COLOR_ID_GREEN>; + }; + + led@2 { + reg = <2>; + color = <LED_COLOR_ID_BLUE>; + }; + }; + }; + }; -- 2.42.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v5 2/2] leds: add ktd202x driver 2023-10-01 13:52 [PATCH v5 0/2] leds: Add a driver for KTD202x André Apitzsch 2023-10-01 13:52 ` [PATCH v5 1/2] dt-bindings: leds: Add Kinetic KTD2026/2027 LED André Apitzsch @ 2023-10-01 13:52 ` André Apitzsch 2023-10-01 15:15 ` Christophe JAILLET 1 sibling, 1 reply; 8+ messages in thread From: André Apitzsch @ 2023-10-01 13:52 UTC (permalink / raw) To: Pavel Machek, Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley Cc: linux-leds, devicetree, linux-kernel, phone-devel, ~postmarketos/upstreaming, Uwe Kleine-König, André Apitzsch This commit adds support for Kinetic KTD2026/7 RGB/White LED driver. Signed-off-by: André Apitzsch <git@apitzsch.eu> --- drivers/leds/rgb/Kconfig | 13 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-ktd202x.c | 619 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 633 insertions(+) diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 183bccc06cf3..a6a21f564673 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -14,6 +14,19 @@ config LEDS_GROUP_MULTICOLOR To compile this driver as a module, choose M here: the module will be called leds-group-multicolor. +config LEDS_KTD202X + tristate "LED support for KTD202x Chips" + depends on I2C + depends on OF + select REGMAP_I2C + help + This option enables support for the Kinetic KTD2026/KTD2027 + RGB/White LED driver found in different BQ mobile phones. + It is a 3 or 4 channel LED driver programmed via an I2C interface. + + To compile this driver as a module, choose M here: the module + will be called leds-ktd202x. + config LEDS_PWM_MULTICOLOR tristate "PWM driven multi-color LED Support" depends on PWM diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index c11cc56384e7..243f31e4d70d 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o +obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o diff --git a/drivers/leds/rgb/leds-ktd202x.c b/drivers/leds/rgb/leds-ktd202x.c new file mode 100644 index 000000000000..57f69d2c30f8 --- /dev/null +++ b/drivers/leds/rgb/leds-ktd202x.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Kinetic KTD2026/7 RGB/White LED driver with I2C interface + * + * Copyright 2023 André Apitzsch <git@apitzsch.eu> + * + * Datasheet: https://www.kinet-ic.com/uploads/KTD2026-7-04h.pdf + */ + +#include <linux/i2c.h> +#include <linux/led-class-multicolor.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#define KTD2026_NUM_LEDS 3 +#define KTD2027_NUM_LEDS 4 +#define KTD202X_MAX_LEDS 4 + +/* Register bank */ +#define KTD202X_REG_RESET_CONTROL 0x00 +#define KTD202X_REG_FLASH_PERIOD 0x01 +#define KTD202X_REG_PWM1_TIMER 0x02 +#define KTD202X_REG_PWM2_TIMER 0x03 +#define KTD202X_REG_CHANNEL_CTRL 0x04 +#define KTD202X_REG_TRISE_FALL 0x05 +#define KTD202X_REG_LED_IOUT(x) (0x06 + (x)) + +/* Register 0 */ +#define KTD202X_TIMER_SLOT_CONTROL_TSLOT1 0x00 +#define KTD202X_TIMER_SLOT_CONTROL_TSLOT2 0x01 +#define KTD202X_TIMER_SLOT_CONTROL_TSLOT3 0x02 +#define KTD202X_TIMER_SLOT_CONTROL_TSLOT4 0x03 +#define KTD202X_RSTR_RESET 0x07 + +#define KTD202X_ENABLE_CTRL_WAKE 0x00 /* SCL High & SDA High */ +#define KTD202X_ENABLE_CTRL_SLEEP 0x08 /* SCL High & SDA Toggling */ + +#define KTD202X_TRISE_FALL_SCALE_NORMAL 0x00 +#define KTD202X_TRISE_FALL_SCALE_SLOW_X2 0x20 +#define KTD202X_TRISE_FALL_SCALE_SLOW_X4 0x40 +#define KTD202X_TRISE_FALL_SCALE_FAST_X8 0x60 + +/* Register 1 */ +#define KTD202X_FLASH_PERIOD_256_MS_LOG_RAMP 0x00 + +/* Register 2-3 */ +#define KTD202X_FLASH_ON_TIME_0_4_PERCENT 0x01 + +/* Register 4 */ +#define KTD202X_CHANNEL_CTRL_MASK(x) (BIT(2 * (x)) | BIT(2 * (x) + 1)) +#define KTD202X_CHANNEL_CTRL_OFF 0x00 +#define KTD202X_CHANNEL_CTRL_ON(x) BIT(2 * (x)) +#define KTD202X_CHANNEL_CTRL_PWM1(x) BIT(2 * (x) + 1) +#define KTD202X_CHANNEL_CTRL_PWM2(x) (BIT(2 * (x)) | BIT(2 * (x) + 1)) + +/* Register 5 */ +#define KTD202X_RAMP_TIMES_2_MS 0x00 + +/* Register 6-9 */ +#define KTD202X_LED_CURRENT_10_mA 0x4f + +#define KTD202X_FLASH_PERIOD_MIN_MS 256 +#define KTD202X_FLASH_PERIOD_STEP_MS 128 +#define KTD202X_FLASH_PERIOD_MAX_STEPS 126 +#define KTD202X_FLASH_ON_MAX 256 + +#define KTD202X_MAX_BRIGHTNESS 192 + +static const struct reg_default ktd202x_reg_defaults[] = { + { KTD202X_REG_RESET_CONTROL, KTD202X_TIMER_SLOT_CONTROL_TSLOT1 | + KTD202X_ENABLE_CTRL_WAKE | KTD202X_TRISE_FALL_SCALE_NORMAL }, + { KTD202X_REG_FLASH_PERIOD, KTD202X_FLASH_PERIOD_256_MS_LOG_RAMP }, + { KTD202X_REG_PWM1_TIMER, KTD202X_FLASH_ON_TIME_0_4_PERCENT }, + { KTD202X_REG_PWM2_TIMER, KTD202X_FLASH_ON_TIME_0_4_PERCENT }, + { KTD202X_REG_CHANNEL_CTRL, KTD202X_CHANNEL_CTRL_OFF }, + { KTD202X_REG_TRISE_FALL, KTD202X_RAMP_TIMES_2_MS }, + { KTD202X_REG_LED_IOUT(0), KTD202X_LED_CURRENT_10_mA }, + { KTD202X_REG_LED_IOUT(1), KTD202X_LED_CURRENT_10_mA }, + { KTD202X_REG_LED_IOUT(2), KTD202X_LED_CURRENT_10_mA }, + { KTD202X_REG_LED_IOUT(3), KTD202X_LED_CURRENT_10_mA }, +}; + +struct ktd202x_led { + struct ktd202x *chip; + union { + struct led_classdev cdev; + struct led_classdev_mc mcdev; + }; + u32 index; +}; + +struct ktd202x { + struct mutex mutex; + struct regulator_bulk_data regulators[2]; + struct device *dev; + struct regmap *regmap; + bool enabled; + int num_leds; + struct ktd202x_led leds[] __counted_by(num_leds); +}; + +static int ktd202x_chip_disable(struct ktd202x *chip) +{ + int ret; + + if (!chip->enabled) + return 0; + + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_ENABLE_CTRL_SLEEP); + + ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret) { + dev_err(chip->dev, "Failed to disable regulators: %d\n", ret); + return ret; + } + + chip->enabled = false; + return 0; +} + +static int ktd202x_chip_enable(struct ktd202x *chip) +{ + int ret; + + if (chip->enabled) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret) { + dev_err(chip->dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + chip->enabled = true; + + ret = regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_ENABLE_CTRL_WAKE); + + if (ret) { + dev_err(chip->dev, "Failed to enable the chip: %d\n", ret); + ktd202x_chip_disable(chip); + } + + return ret; +} + +static bool ktd202x_chip_in_use(struct ktd202x *chip) +{ + int i; + + for (i = 0; i < chip->num_leds; i++) { + if (chip->leds[i].cdev.brightness) + return true; + } + + return false; +} + +static int ktd202x_brightness_set(struct ktd202x_led *led, + struct mc_subled *subleds, + unsigned int num_channels) +{ + enum led_brightness brightness; + bool mode_blink = false; + int channel_state; + int channel; + int state; + int mode; + int ret; + int i; + + if (ktd202x_chip_in_use(led->chip)) { + ret = ktd202x_chip_enable(led->chip); + if (ret) + return ret; + } + + ret = regmap_read(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, &state); + if (ret) + return ret; + + /* + * In multicolor case, assume blink mode if PWM is set for at least one + * channel because another channel cannot be in state ON at the same time + */ + for (i = 0; i < num_channels; i++) { + channel = subleds[i].channel; + channel_state = (state >> 2 * channel) & KTD202X_CHANNEL_CTRL_MASK(0); + if (channel_state == KTD202X_CHANNEL_CTRL_OFF) + continue; + mode_blink = channel_state == KTD202X_CHANNEL_CTRL_PWM1(0); + break; + } + + for (i = 0; i < num_channels; i++) { + brightness = subleds[i].brightness; + channel = subleds[i].channel; + + if (brightness) { + /* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */ + ret = regmap_write(led->chip->regmap, KTD202X_REG_LED_IOUT(channel), + brightness - 1); + if (ret) + return ret; + + if (mode_blink) + mode = KTD202X_CHANNEL_CTRL_PWM1(channel); + else + mode = KTD202X_CHANNEL_CTRL_ON(channel); + } else { + mode = KTD202X_CHANNEL_CTRL_OFF; + } + ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, + KTD202X_CHANNEL_CTRL_MASK(channel), mode); + if (ret) + return ret; + } + + if (!ktd202x_chip_in_use(led->chip)) + return ktd202x_chip_disable(led->chip); + + return 0; +} + +static int ktd202x_brightness_single_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct ktd202x_led *led = container_of(cdev, struct ktd202x_led, cdev); + struct mc_subled info; + int ret; + + cdev->brightness = value; + + mutex_lock(&led->chip->mutex); + + info.brightness = value; + info.channel = led->index; + ret = ktd202x_brightness_set(led, &info, 1); + + mutex_unlock(&led->chip->mutex); + + return ret; +} + +static int ktd202x_brightness_mc_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); + struct ktd202x_led *led = container_of(mc, struct ktd202x_led, mcdev); + int ret; + + cdev->brightness = value; + + mutex_lock(&led->chip->mutex); + + led_mc_calc_color_components(mc, value); + ret = ktd202x_brightness_set(led, mc->subled_info, mc->num_colors); + + mutex_unlock(&led->chip->mutex); + + return ret; +} + +static int ktd202x_blink_set(struct ktd202x_led *led, unsigned long *delay_on, + unsigned long *delay_off, struct mc_subled *subleds, + unsigned int num_channels) +{ + unsigned long delay_total_ms; + int ret, num_steps, on; + u8 ctrl_mask = 0; + u8 ctrl_pwm1 = 0; + u8 ctrl_on = 0; + int i, channel; + + mutex_lock(&led->chip->mutex); + + for (i = 0; i < num_channels; i++) { + channel = subleds[i].channel; + ctrl_mask |= KTD202X_CHANNEL_CTRL_MASK(channel); + ctrl_on |= KTD202X_CHANNEL_CTRL_ON(channel); + ctrl_pwm1 |= KTD202X_CHANNEL_CTRL_PWM1(channel); + } + + /* Never off - brightness is already set, disable blinking */ + if (!*delay_off) { + ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, + ctrl_mask, ctrl_on); + goto out; + } + + /* Convert into values the HW will understand. */ + + /* Integer representation of time of flash period */ + num_steps = (*delay_on + *delay_off - KTD202X_FLASH_PERIOD_MIN_MS) / + KTD202X_FLASH_PERIOD_STEP_MS; + num_steps = clamp(num_steps, 0, KTD202X_FLASH_PERIOD_MAX_STEPS); + + /* Integer representation of percentage of LED ON time */ + on = (*delay_on * KTD202X_FLASH_ON_MAX) / (*delay_on + *delay_off); + + /* Actually used delay_{on,off} values */ + delay_total_ms = num_steps * KTD202X_FLASH_PERIOD_STEP_MS + KTD202X_FLASH_PERIOD_MIN_MS; + *delay_on = (delay_total_ms * on) / KTD202X_FLASH_ON_MAX; + *delay_off = delay_total_ms - *delay_on; + + /* Set timings */ + ret = regmap_write(led->chip->regmap, KTD202X_REG_FLASH_PERIOD, num_steps); + if (ret) + goto out; + + ret = regmap_write(led->chip->regmap, KTD202X_REG_PWM1_TIMER, on); + if (ret) + goto out; + + ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, + ctrl_mask, ctrl_pwm1); +out: + mutex_unlock(&led->chip->mutex); + return ret; +} + +static int ktd202x_blink_single_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct ktd202x_led *led = container_of(cdev, struct ktd202x_led, cdev); + struct mc_subled info; + int ret; + + if (!cdev->brightness) { + ret = ktd202x_brightness_single_set(cdev, KTD202X_MAX_BRIGHTNESS); + if (ret) + return ret; + } + + /* If no blink specified, default to 1 Hz. */ + if (!*delay_off && !*delay_on) { + *delay_off = 500; + *delay_on = 500; + } + + /* Never on - just set to off */ + if (!*delay_on) + return ktd202x_brightness_single_set(cdev, LED_OFF); + + info.channel = led->index; + + return ktd202x_blink_set(led, delay_on, delay_off, &info, 1); +} + +static int ktd202x_blink_mc_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); + struct ktd202x_led *led = container_of(mc, struct ktd202x_led, mcdev); + int ret; + + if (!cdev->brightness) { + ret = ktd202x_brightness_mc_set(cdev, KTD202X_MAX_BRIGHTNESS); + if (ret) + return ret; + } + + /* If no blink specified, default to 1 Hz. */ + if (!*delay_off && !*delay_on) { + *delay_off = 500; + *delay_on = 500; + } + + /* Never on - just set to off */ + if (!*delay_on) + return ktd202x_brightness_mc_set(cdev, LED_OFF); + + return ktd202x_blink_set(led, delay_on, delay_off, mc->subled_info, + mc->num_colors); +} + +static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np, + struct ktd202x_led *led, struct led_init_data *init_data) +{ + struct led_classdev *cdev; + struct device_node *child; + struct mc_subled *info; + int num_channels; + int i = 0; + u32 reg; + int ret; + + num_channels = of_get_available_child_count(np); + if (!num_channels || num_channels > chip->num_leds) + return -EINVAL; + + info = devm_kcalloc(chip->dev, num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + for_each_available_child_of_node(np, child) { + u32 mono_color = 0; + + ret = of_property_read_u32(child, "reg", ®); + if (ret != 0 || reg >= chip->num_leds) { + dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np); + return -EINVAL; + } + + ret = of_property_read_u32(child, "color", &mono_color); + if (ret < 0 && ret != -EINVAL) { + dev_err(chip->dev, "failed to parse 'color' of %pOF\n", np); + return ret; + } + + info[i].color_index = mono_color; + info[i].channel = reg; + info[i].intensity = KTD202X_MAX_BRIGHTNESS; + i++; + } + + led->mcdev.subled_info = info; + led->mcdev.num_colors = num_channels; + + cdev = &led->mcdev.led_cdev; + cdev->brightness_set_blocking = ktd202x_brightness_mc_set; + cdev->blink_set = ktd202x_blink_mc_set; + + return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, init_data); +} + +static int ktd202x_setup_led_single(struct ktd202x *chip, struct device_node *np, + struct ktd202x_led *led, struct led_init_data *init_data) +{ + struct led_classdev *cdev; + u32 reg; + int ret; + + ret = of_property_read_u32(np, "reg", ®); + if (ret != 0 || reg >= chip->num_leds) { + dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np); + return -EINVAL; + } + led->index = reg; + + cdev = &led->cdev; + cdev->brightness_set_blocking = ktd202x_brightness_single_set; + cdev->blink_set = ktd202x_blink_single_set; + + return devm_led_classdev_register_ext(chip->dev, &led->cdev, init_data); +} + +static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigned int index) +{ + struct ktd202x_led *led = &chip->leds[index]; + struct led_init_data init_data = {}; + struct led_classdev *cdev; + u32 color = 0; + int ret; + + /* Color property is optional in single color case */ + ret = of_property_read_u32(np, "color", &color); + if (ret < 0 && ret != -EINVAL) { + dev_err(chip->dev, "failed to parse 'color' of %pOF\n", np); + return ret; + } + + led->chip = chip; + init_data.fwnode = of_fwnode_handle(np); + + if (color == LED_COLOR_ID_RGB) { + cdev = &led->mcdev.led_cdev; + ret = ktd202x_setup_led_rgb(chip, np, led, &init_data); + } else { + cdev = &led->cdev; + ret = ktd202x_setup_led_single(chip, np, led, &init_data); + } + + if (ret) { + dev_err(chip->dev, "unable to register %s\n", cdev->name); + of_node_put(np); + return ret; + } + + cdev->max_brightness = KTD202X_MAX_BRIGHTNESS; + + return 0; +} + +static int ktd202x_probe_dt(struct ktd202x *chip) +{ + struct device_node *np = dev_of_node(chip->dev), *child; + unsigned int i; + int count, ret; + + chip->num_leds = (int)(unsigned long)of_device_get_match_data(chip->dev); + + count = of_get_available_child_count(np); + if (!count || count > chip->num_leds) + return -EINVAL; + + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET); + + /* Allow the device to execute the complete reset */ + usleep_range(200, 300); + + i = 0; + for_each_available_child_of_node(np, child) { + ret = ktd202x_add_led(chip, child, i); + if (ret) + return ret; + i++; + } + + return 0; +} + +static const struct regmap_config ktd202x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x09, + .cache_type = REGCACHE_FLAT, + .reg_defaults = ktd202x_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ktd202x_reg_defaults), +}; + +static int ktd202x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ktd202x *chip; + int count; + int ret; + + count = device_get_child_node_count(dev); + if (!count || count > KTD202X_MAX_LEDS) + return dev_err_probe(dev, -EINVAL, "Incorrect number of leds (%d)", count); + + chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = dev; + i2c_set_clientdata(client, chip); + + chip->regmap = devm_regmap_init_i2c(client, &ktd202x_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = dev_err_probe(dev, PTR_ERR(chip->regmap), + "Failed to allocate register map.\n"); + return ret; + } + + chip->regulators[0].supply = "vin"; + chip->regulators[1].supply = "vio"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to request regulators.\n"); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret) { + dev_err_probe(dev, ret, "Failed to enable regulators.\n"); + return ret; + } + + ret = ktd202x_probe_dt(chip); + if (ret < 0) { + regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators); + return ret; + } + + ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret) { + dev_err_probe(dev, ret, "Failed to disable regulators.\n"); + return ret; + } + + mutex_init(&chip->mutex); + + return 0; +} + +static void ktd202x_remove(struct i2c_client *client) +{ + struct ktd202x *chip = i2c_get_clientdata(client); + + ktd202x_chip_disable(chip); + + mutex_destroy(&chip->mutex); +} + +static void ktd202x_shutdown(struct i2c_client *client) +{ + struct ktd202x *chip = i2c_get_clientdata(client); + + /* Reset registers to make sure all LEDs are off before shutdown */ + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET); +} + +static const struct of_device_id ktd202x_match_table[] = { + { .compatible = "kinetic,ktd2026", .data = (void *)KTD2026_NUM_LEDS }, + { .compatible = "kinetic,ktd2027", .data = (void *)KTD2027_NUM_LEDS }, + {}, +}; +MODULE_DEVICE_TABLE(of, ktd202x_match_table); + +static struct i2c_driver ktd202x_driver = { + .driver = { + .name = "leds-ktd202x", + .of_match_table = ktd202x_match_table, + }, + .probe = ktd202x_probe, + .remove = ktd202x_remove, + .shutdown = ktd202x_shutdown, +}; +module_i2c_driver(ktd202x_driver); + +MODULE_AUTHOR("André Apitzsch <git@apitzsch.eu>"); +MODULE_DESCRIPTION("Kinetic KTD2026/7 LED driver"); +MODULE_LICENSE("GPL"); -- 2.42.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v5 2/2] leds: add ktd202x driver 2023-10-01 13:52 ` [PATCH v5 2/2] leds: add ktd202x driver André Apitzsch @ 2023-10-01 15:15 ` Christophe JAILLET 2023-10-01 16:56 ` André Apitzsch 0 siblings, 1 reply; 8+ messages in thread From: Christophe JAILLET @ 2023-10-01 15:15 UTC (permalink / raw) To: git Cc: conor+dt, devicetree, krzysztof.kozlowski+dt, lee, linux-kernel, linux-leds, pavel, phone-devel, robh+dt, u.kleine-koenig, ~postmarketos/upstreaming Le 01/10/2023 à 15:52, André Apitzsch a écrit : > This commit adds support for Kinetic KTD2026/7 RGB/White LED driver. > > Signed-off-by: André Apitzsch <git-AtRKszJ1oGPsq35pWSNszA@public.gmane.org> ... > +static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np, > + struct ktd202x_led *led, struct led_init_data *init_data) > +{ > + struct led_classdev *cdev; > + struct device_node *child; > + struct mc_subled *info; > + int num_channels; > + int i = 0; > + u32 reg; > + int ret; > + > + num_channels = of_get_available_child_count(np); > + if (!num_channels || num_channels > chip->num_leds) > + return -EINVAL; > + > + info = devm_kcalloc(chip->dev, num_channels, sizeof(*info), GFP_KERNEL); > + if (!info) > + return -ENOMEM; > + > + for_each_available_child_of_node(np, child) { > + u32 mono_color = 0; Un-needed init. And, why is it defined here, while reg is defined out-side the loop? > + > + ret = of_property_read_u32(child, "reg", ®); > + if (ret != 0 || reg >= chip->num_leds) { > + dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np); Mossing of_node_put(np);? > + return -EINVAL; > + } > + > + ret = of_property_read_u32(child, "color", &mono_color); > + if (ret < 0 && ret != -EINVAL) { > + dev_err(chip->dev, "failed to parse 'color' of %pOF\n", np); Mossing of_node_put(np);? > + return ret; > + } > + > + info[i].color_index = mono_color; > + info[i].channel = reg; > + info[i].intensity = KTD202X_MAX_BRIGHTNESS; > + i++; > + } > + > + led->mcdev.subled_info = info; > + led->mcdev.num_colors = num_channels; > + > + cdev = &led->mcdev.led_cdev; > + cdev->brightness_set_blocking = ktd202x_brightness_mc_set; > + cdev->blink_set = ktd202x_blink_mc_set; > + > + return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, init_data); > +} > + > +static int ktd202x_setup_led_single(struct ktd202x *chip, struct device_node *np, > + struct ktd202x_led *led, struct led_init_data *init_data) > +{ > + struct led_classdev *cdev; > + u32 reg; > + int ret; > + > + ret = of_property_read_u32(np, "reg", ®); > + if (ret != 0 || reg >= chip->num_leds) { > + dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np); > + return -EINVAL; > + } > + led->index = reg; > + > + cdev = &led->cdev; > + cdev->brightness_set_blocking = ktd202x_brightness_single_set; > + cdev->blink_set = ktd202x_blink_single_set; > + > + return devm_led_classdev_register_ext(chip->dev, &led->cdev, init_data); > +} > + > +static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigned int index) > +{ > + struct ktd202x_led *led = &chip->leds[index]; > + struct led_init_data init_data = {}; > + struct led_classdev *cdev; > + u32 color = 0; Un-needed init. > + int ret; > + > + /* Color property is optional in single color case */ > + ret = of_property_read_u32(np, "color", &color); > + if (ret < 0 && ret != -EINVAL) { > + dev_err(chip->dev, "failed to parse 'color' of %pOF\n", np); > + return ret; > + } > + > + led->chip = chip; > + init_data.fwnode = of_fwnode_handle(np); > + > + if (color == LED_COLOR_ID_RGB) { > + cdev = &led->mcdev.led_cdev; > + ret = ktd202x_setup_led_rgb(chip, np, led, &init_data); > + } else { > + cdev = &led->cdev; > + ret = ktd202x_setup_led_single(chip, np, led, &init_data); > + } > + > + if (ret) { > + dev_err(chip->dev, "unable to register %s\n", cdev->name); > + of_node_put(np); This is strange to have it here. Why not above after "if (ret < 0 && ret != -EINVAL) {"? It would look much more natural to have it a few lines below, ... [1] > + return ret; > + } > + > + cdev->max_brightness = KTD202X_MAX_BRIGHTNESS; > + > + return 0; > +} > + > +static int ktd202x_probe_dt(struct ktd202x *chip) > +{ > + struct device_node *np = dev_of_node(chip->dev), *child; > + unsigned int i; > + int count, ret; > + > + chip->num_leds = (int)(unsigned long)of_device_get_match_data(chip->dev); > + > + count = of_get_available_child_count(np); > + if (!count || count > chip->num_leds) > + return -EINVAL; > + > + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET); > + > + /* Allow the device to execute the complete reset */ > + usleep_range(200, 300); > + > + i = 0; > + for_each_available_child_of_node(np, child) { > + ret = ktd202x_add_led(chip, child, i); > + if (ret) [1] ... here. Otherwise, it is likely that, thanks to a static checker, an additionnal of_node_put() will be added on early exit of the loop. CJ > + return ret; > + i++; > + } > + > + return 0; > +} ... ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 2/2] leds: add ktd202x driver 2023-10-01 15:15 ` Christophe JAILLET @ 2023-10-01 16:56 ` André Apitzsch 2023-10-01 17:05 ` Uwe Kleine-König 2023-10-01 20:46 ` Christophe JAILLET 0 siblings, 2 replies; 8+ messages in thread From: André Apitzsch @ 2023-10-01 16:56 UTC (permalink / raw) To: Christophe JAILLET Cc: conor+dt, devicetree, krzysztof.kozlowski+dt, lee, linux-kernel, linux-leds, pavel, phone-devel, robh+dt, u.kleine-koenig, ~postmarketos/upstreaming Hi Christophe, Am Sonntag, dem 01.10.2023 um 17:15 +0200 schrieb Christophe JAILLET: > Le 01/10/2023 à 15:52, André Apitzsch a écrit : > > This commit adds support for Kinetic KTD2026/7 RGB/White LED > > driver. > > > > Signed-off-by: André Apitzsch > > <git-AtRKszJ1oGPsq35pWSNszA@public.gmane.org> > > ... > > > +static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct > > device_node *np, > > + struct ktd202x_led *led, struct > > led_init_data *init_data) > > +{ > > + struct led_classdev *cdev; > > + struct device_node *child; > > + struct mc_subled *info; > > + int num_channels; > > + int i = 0; > > + u32 reg; > > + int ret; > > + > > + num_channels = of_get_available_child_count(np); > > + if (!num_channels || num_channels > chip->num_leds) > > + return -EINVAL; > > + > > + info = devm_kcalloc(chip->dev, num_channels, sizeof(*info), > > GFP_KERNEL); > > + if (!info) > > + return -ENOMEM; > > + > > + for_each_available_child_of_node(np, child) { > > + u32 mono_color = 0; > > Un-needed init. > And, why is it defined here, while reg is defined out-side the loop? I'll move it out-side the loop (without initialization). > > > + > > + ret = of_property_read_u32(child, "reg", ®); > > + if (ret != 0 || reg >= chip->num_leds) { > > + dev_err(chip->dev, "invalid 'reg' of > > %pOFn\n", np); > > Mossing of_node_put(np);? It shouldn't be needed here if handled in the calling function, right? > > > + return -EINVAL; > > + } > > + > > + ret = of_property_read_u32(child, "color", > > &mono_color); > > + if (ret < 0 && ret != -EINVAL) { > > + dev_err(chip->dev, "failed to parse 'color' > > of %pOF\n", np); > > Mossing of_node_put(np);? > > > + return ret; > > + } > > + > > + info[i].color_index = mono_color; > > + info[i].channel = reg; > > + info[i].intensity = KTD202X_MAX_BRIGHTNESS; > > + i++; > > + } > > + > > + led->mcdev.subled_info = info; > > + led->mcdev.num_colors = num_channels; > > + > > + cdev = &led->mcdev.led_cdev; > > + cdev->brightness_set_blocking = ktd202x_brightness_mc_set; > > + cdev->blink_set = ktd202x_blink_mc_set; > > + > > + return devm_led_classdev_multicolor_register_ext(chip->dev, > > &led->mcdev, init_data); > > +} > > + > > +static int ktd202x_setup_led_single(struct ktd202x *chip, struct > > device_node *np, > > + struct ktd202x_led *led, struct > > led_init_data *init_data) > > +{ > > + struct led_classdev *cdev; > > + u32 reg; > > + int ret; > > + > > + ret = of_property_read_u32(np, "reg", ®); > > + if (ret != 0 || reg >= chip->num_leds) { > > + dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np); > > + return -EINVAL; > > + } > > + led->index = reg; > > + > > + cdev = &led->cdev; > > + cdev->brightness_set_blocking = > > ktd202x_brightness_single_set; > > + cdev->blink_set = ktd202x_blink_single_set; > > + > > + return devm_led_classdev_register_ext(chip->dev, &led- > > >cdev, init_data); > > +} > > + > > +static int ktd202x_add_led(struct ktd202x *chip, struct > > device_node *np, unsigned int index) > > +{ > > + struct ktd202x_led *led = &chip->leds[index]; > > + struct led_init_data init_data = {}; > > + struct led_classdev *cdev; > > + u32 color = 0; > Un-needed init. > > > + int ret; > > + > > + /* Color property is optional in single color case */ > > + ret = of_property_read_u32(np, "color", &color); > > + if (ret < 0 && ret != -EINVAL) { > > + dev_err(chip->dev, "failed to parse 'color' of > > %pOF\n", np); > > + return ret; > > + } > > + > > + led->chip = chip; > > + init_data.fwnode = of_fwnode_handle(np); > > + > > + if (color == LED_COLOR_ID_RGB) { > > + cdev = &led->mcdev.led_cdev; > > + ret = ktd202x_setup_led_rgb(chip, np, led, > > &init_data); > > + } else { > > + cdev = &led->cdev; > > + ret = ktd202x_setup_led_single(chip, np, led, > > &init_data); > > + } > > + > > + if (ret) { > > + dev_err(chip->dev, "unable to register %s\n", cdev- > > >name); > > + of_node_put(np); > > This is strange to have it here. > Why not above after "if (ret < 0 && ret != -EINVAL) {"? > > It would look much more natural to have it a few lines below, ... [1] Good catch. I'll move of_node_put(np); to [1] and [2]. > > > + return ret; > > + } > > + > > + cdev->max_brightness = KTD202X_MAX_BRIGHTNESS; > > + > > + return 0; > > +} > > + > > +static int ktd202x_probe_dt(struct ktd202x *chip) > > +{ > > + struct device_node *np = dev_of_node(chip->dev), *child; > > + unsigned int i; > > + int count, ret; > > + > > + chip->num_leds = (int)(unsigned > > long)of_device_get_match_data(chip->dev); > > + > > + count = of_get_available_child_count(np); > > + if (!count || count > chip->num_leds) [2]. > > + return -EINVAL; > > + > > + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, > > KTD202X_RSTR_RESET); > > + > > + /* Allow the device to execute the complete reset */ > > + usleep_range(200, 300); > > + > > + i = 0; > > + for_each_available_child_of_node(np, child) { > > + ret = ktd202x_add_led(chip, child, i); > > + if (ret) > > [1] ... here. > > Otherwise, it is likely that, thanks to a static checker, an > additionnal > of_node_put() will be added on early exit of the loop. > > CJ > > > + return ret; > > + i++; > > + } > > + > > + return 0; > > +} > > ... > Best regards, André ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 2/2] leds: add ktd202x driver 2023-10-01 16:56 ` André Apitzsch @ 2023-10-01 17:05 ` Uwe Kleine-König 2023-10-01 20:46 ` Christophe JAILLET 1 sibling, 0 replies; 8+ messages in thread From: Uwe Kleine-König @ 2023-10-01 17:05 UTC (permalink / raw) To: André Apitzsch Cc: Christophe JAILLET, conor+dt, devicetree, krzysztof.kozlowski+dt, lee, linux-kernel, linux-leds, pavel, phone-devel, robh+dt, ~postmarketos/upstreaming [-- Attachment #1: Type: text/plain, Size: 799 bytes --] Hello André, On Sun, Oct 01, 2023 at 06:56:20PM +0200, André Apitzsch wrote: > Am Sonntag, dem 01.10.2023 um 17:15 +0200 schrieb Christophe JAILLET: > > Le 01/10/2023 à 15:52, André Apitzsch a écrit : > > > + for_each_available_child_of_node(np, child) { > > > + u32 mono_color = 0; > > > > Un-needed init. > > And, why is it defined here, while reg is defined out-side the loop? > > I'll move it out-side the loop (without initialization). In my book a variable with a narrow scope is better. I didn't check, but if you can restrict both variables to the for loop, that's nicer. Best regards Uwe -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | https://www.pengutronix.de/ | [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 2/2] leds: add ktd202x driver 2023-10-01 16:56 ` André Apitzsch 2023-10-01 17:05 ` Uwe Kleine-König @ 2023-10-01 20:46 ` Christophe JAILLET 2023-10-02 6:09 ` André Apitzsch 1 sibling, 1 reply; 8+ messages in thread From: Christophe JAILLET @ 2023-10-01 20:46 UTC (permalink / raw) To: André Apitzsch Cc: conor+dt, devicetree, krzysztof.kozlowski+dt, lee, linux-kernel, linux-leds, pavel, phone-devel, robh+dt, u.kleine-koenig, ~postmarketos/upstreaming Le 01/10/2023 à 18:56, André Apitzsch a écrit : > Hi Christophe, > > Am Sonntag, dem 01.10.2023 um 17:15 +0200 schrieb Christophe JAILLET: >> Le 01/10/2023 à 15:52, André Apitzsch a écrit : >>> This commit adds support for Kinetic KTD2026/7 RGB/White LED >>> driver. >>> >>> Signed-off-by: André Apitzsch >>> <git-AtRKszJ1oGPsq35pWSNszA@public.gmane.org> >> >> ... >> >>> +static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct >>> device_node *np, >>> + struct ktd202x_led *led, struct >>> led_init_data *init_data) >>> +{ >>> + struct led_classdev *cdev; >>> + struct device_node *child; >>> + struct mc_subled *info; >>> + int num_channels; >>> + int i = 0; >>> + u32 reg; >>> + int ret; >>> + >>> + num_channels = of_get_available_child_count(np); >>> + if (!num_channels || num_channels > chip->num_leds) >>> + return -EINVAL; >>> + >>> + info = devm_kcalloc(chip->dev, num_channels, sizeof(*info), >>> GFP_KERNEL); >>> + if (!info) >>> + return -ENOMEM; >>> + >>> + for_each_available_child_of_node(np, child) { >>> + u32 mono_color = 0; >> >> Un-needed init. >> And, why is it defined here, while reg is defined out-side the loop? > > I'll move it out-side the loop (without initialization). > >> >>> + >>> + ret = of_property_read_u32(child, "reg", ®); >>> + if (ret != 0 || reg >= chip->num_leds) { >>> + dev_err(chip->dev, "invalid 'reg' of >>> %pOFn\n", np); >> >> Mossing of_node_put(np);? > > It shouldn't be needed here if handled in the calling function, right? How can the caller do this? The goal of this of_node_put() is to release a reference taken by the for_each_available_child_of_node() loop, in case of early exit. The caller can't know if np needs to be released or not. An error code is returned either if an error occurs within the for_each loop, or if devm_led_classdev_multicolor_register_ext() fails. More over, in your case the caller is ktd202x_add_led(). From there either ktd202x_setup_led_rgb() or ktd202x_setup_led_single() is called. ktd202x_setup_led_single() does not take any reference to np. But if it fails, of_node_put() would still be called. > >> >>> + return -EINVAL; >>> + } >>> + >>> + ret = of_property_read_u32(child, "color", >>> &mono_color); >>> + if (ret < 0 && ret != -EINVAL) { >>> + dev_err(chip->dev, "failed to parse 'color' >>> of %pOF\n", np); >> >> Mossing of_node_put(np);? >> >>> + return ret; >>> + } >>> + >>> + info[i].color_index = mono_color; >>> + info[i].channel = reg; >>> + info[i].intensity = KTD202X_MAX_BRIGHTNESS; >>> + i++; >>> + } >>> + >>> + led->mcdev.subled_info = info; >>> + led->mcdev.num_colors = num_channels; >>> + >>> + cdev = &led->mcdev.led_cdev; >>> + cdev->brightness_set_blocking = ktd202x_brightness_mc_set; >>> + cdev->blink_set = ktd202x_blink_mc_set; >>> + >>> + return devm_led_classdev_multicolor_register_ext(chip->dev, >>> &led->mcdev, init_data); >>> +} >>> + >>> +static int ktd202x_setup_led_single(struct ktd202x *chip, struct >>> device_node *np, >>> + struct ktd202x_led *led, struct >>> led_init_data *init_data) >>> +{ >>> + struct led_classdev *cdev; >>> + u32 reg; >>> + int ret; >>> + >>> + ret = of_property_read_u32(np, "reg", ®); >>> + if (ret != 0 || reg >= chip->num_leds) { >>> + dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np); >>> + return -EINVAL; >>> + } >>> + led->index = reg; >>> + >>> + cdev = &led->cdev; >>> + cdev->brightness_set_blocking = >>> ktd202x_brightness_single_set; >>> + cdev->blink_set = ktd202x_blink_single_set; >>> + >>> + return devm_led_classdev_register_ext(chip->dev, &led- >>>> cdev, init_data); >>> +} >>> + >>> +static int ktd202x_add_led(struct ktd202x *chip, struct >>> device_node *np, unsigned int index) >>> +{ >>> + struct ktd202x_led *led = &chip->leds[index]; >>> + struct led_init_data init_data = {}; >>> + struct led_classdev *cdev; >>> + u32 color = 0; >> Un-needed init. >> >>> + int ret; >>> + >>> + /* Color property is optional in single color case */ >>> + ret = of_property_read_u32(np, "color", &color); >>> + if (ret < 0 && ret != -EINVAL) { >>> + dev_err(chip->dev, "failed to parse 'color' of >>> %pOF\n", np); >>> + return ret; >>> + } >>> + >>> + led->chip = chip; >>> + init_data.fwnode = of_fwnode_handle(np); >>> + >>> + if (color == LED_COLOR_ID_RGB) { >>> + cdev = &led->mcdev.led_cdev; >>> + ret = ktd202x_setup_led_rgb(chip, np, led, >>> &init_data); >>> + } else { >>> + cdev = &led->cdev; >>> + ret = ktd202x_setup_led_single(chip, np, led, >>> &init_data); >>> + } >>> + >>> + if (ret) { >>> + dev_err(chip->dev, "unable to register %s\n", cdev- >>>> name); >>> + of_node_put(np); >> >> This is strange to have it here. >> Why not above after "if (ret < 0 && ret != -EINVAL) {"? >> >> It would look much more natural to have it a few lines below, ... [1] > > Good catch. I'll move of_node_put(np); to [1] and [2]. Why [2]? It does not seem needed here. of_get_available_child_count() does not keep any reference. CJ > >> >>> + return ret; >>> + } >>> + >>> + cdev->max_brightness = KTD202X_MAX_BRIGHTNESS; >>> + >>> + return 0; >>> +} >>> + >>> +static int ktd202x_probe_dt(struct ktd202x *chip) >>> +{ >>> + struct device_node *np = dev_of_node(chip->dev), *child; >>> + unsigned int i; >>> + int count, ret; >>> + >>> + chip->num_leds = (int)(unsigned >>> long)of_device_get_match_data(chip->dev); >>> + >>> + count = of_get_available_child_count(np); >>> + if (!count || count > chip->num_leds) > > [2]. > >>> + return -EINVAL; >>> + >>> + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, >>> KTD202X_RSTR_RESET); >>> + >>> + /* Allow the device to execute the complete reset */ >>> + usleep_range(200, 300); >>> + >>> + i = 0; >>> + for_each_available_child_of_node(np, child) { >>> + ret = ktd202x_add_led(chip, child, i); >>> + if (ret) >> >> [1] ... here. >> >> Otherwise, it is likely that, thanks to a static checker, an >> additionnal >> of_node_put() will be added on early exit of the loop. >> >> CJ >> >>> + return ret; >>> + i++; >>> + } >>> + >>> + return 0; >>> +} >> >> ... >> > > Best regards, > André > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 2/2] leds: add ktd202x driver 2023-10-01 20:46 ` Christophe JAILLET @ 2023-10-02 6:09 ` André Apitzsch 0 siblings, 0 replies; 8+ messages in thread From: André Apitzsch @ 2023-10-02 6:09 UTC (permalink / raw) To: Christophe JAILLET Cc: conor+dt, devicetree, krzysztof.kozlowski+dt, lee, linux-kernel, linux-leds, pavel, phone-devel, robh+dt, u.kleine-koenig, ~postmarketos/upstreaming Am Sonntag, dem 01.10.2023 um 22:46 +0200 schrieb Christophe JAILLET: > Le 01/10/2023 à 18:56, André Apitzsch a écrit : > > Hi Christophe, > > > > Am Sonntag, dem 01.10.2023 um 17:15 +0200 schrieb Christophe > > JAILLET: > > > Le 01/10/2023 à 15:52, André Apitzsch a écrit : > > > > This commit adds support for Kinetic KTD2026/7 RGB/White LED > > > > driver. > > > > > > > > Signed-off-by: André Apitzsch > > > > <git-AtRKszJ1oGPsq35pWSNszA@public.gmane.org> > > > > > > ... > > > > > > > +static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct > > > > device_node *np, > > > > + struct ktd202x_led *led, > > > > struct > > > > led_init_data *init_data) > > > > +{ > > > > + struct led_classdev *cdev; > > > > + struct device_node *child; > > > > + struct mc_subled *info; > > > > + int num_channels; > > > > + int i = 0; > > > > + u32 reg; > > > > + int ret; > > > > + > > > > + num_channels = of_get_available_child_count(np); > > > > + if (!num_channels || num_channels > chip->num_leds) > > > > + return -EINVAL; > > > > + > > > > + info = devm_kcalloc(chip->dev, num_channels, > > > > sizeof(*info), > > > > GFP_KERNEL); > > > > + if (!info) > > > > + return -ENOMEM; > > > > + > > > > + for_each_available_child_of_node(np, child) { > > > > + u32 mono_color = 0; > > > > > > Un-needed init. > > > And, why is it defined here, while reg is defined out-side the > > > loop? > > > > I'll move it out-side the loop (without initialization). > > > > > > > > > + > > > > + ret = of_property_read_u32(child, "reg", ®); > > > > + if (ret != 0 || reg >= chip->num_leds) { > > > > + dev_err(chip->dev, "invalid 'reg' of > > > > %pOFn\n", np); > > > > > > Mossing of_node_put(np);? > > > > It shouldn't be needed here if handled in the calling function, > > right? > > How can the caller do this? > > The goal of this of_node_put() is to release a reference taken by the > for_each_available_child_of_node() loop, in case of early exit. > > The caller can't know if np needs to be released or not. An error > code > is returned either if an error occurs within the for_each loop, or if > devm_led_classdev_multicolor_register_ext() fails. > > More over, in your case the caller is ktd202x_add_led(). > From there either ktd202x_setup_led_rgb() or > ktd202x_setup_led_single() > is called. > > ktd202x_setup_led_single() does not take any reference to np. > But if it fails, of_node_put() would still be called. > > Hello Christophe, It seems I misunderstood when of_node_put() is used. Thanks for the explanation. While checking the usage of of_node_put(), I noticed that dev_err() (and of_node_put()) should take "child" and not "np", here. André > > > > > > > + return -EINVAL; > > > > + } > > > > + > > > > + ret = of_property_read_u32(child, "color", > > > > &mono_color); > > > > + if (ret < 0 && ret != -EINVAL) { > > > > + dev_err(chip->dev, "failed to parse > > > > 'color' > > > > of %pOF\n", np); > > > > > > Mossing of_node_put(np);? > > > > > > > + return ret; > > > > + } > > > > + > > > > + info[i].color_index = mono_color; > > > > + info[i].channel = reg; > > > > + info[i].intensity = KTD202X_MAX_BRIGHTNESS; > > > > + i++; > > > > + } > > > > + > > > > + led->mcdev.subled_info = info; > > > > + led->mcdev.num_colors = num_channels; > > > > + > > > > + cdev = &led->mcdev.led_cdev; > > > > + cdev->brightness_set_blocking = > > > > ktd202x_brightness_mc_set; > > > > + cdev->blink_set = ktd202x_blink_mc_set; > > > > + > > > > + return devm_led_classdev_multicolor_register_ext(chip- > > > > >dev, > > > > &led->mcdev, init_data); > > > > +} > > > > + > > > > +static int ktd202x_setup_led_single(struct ktd202x *chip, > > > > struct > > > > device_node *np, > > > > + struct ktd202x_led *led, > > > > struct > > > > led_init_data *init_data) > > > > +{ > > > > + struct led_classdev *cdev; > > > > + u32 reg; > > > > + int ret; > > > > + > > > > + ret = of_property_read_u32(np, "reg", ®); > > > > + if (ret != 0 || reg >= chip->num_leds) { > > > > + dev_err(chip->dev, "invalid 'reg' of %pOFn\n", > > > > np); > > > > + return -EINVAL; > > > > + } > > > > + led->index = reg; > > > > + > > > > + cdev = &led->cdev; > > > > + cdev->brightness_set_blocking = > > > > ktd202x_brightness_single_set; > > > > + cdev->blink_set = ktd202x_blink_single_set; > > > > + > > > > + return devm_led_classdev_register_ext(chip->dev, &led- > > > > > cdev, init_data); > > > > +} > > > > + > > > > +static int ktd202x_add_led(struct ktd202x *chip, struct > > > > device_node *np, unsigned int index) > > > > +{ > > > > + struct ktd202x_led *led = &chip->leds[index]; > > > > + struct led_init_data init_data = {}; > > > > + struct led_classdev *cdev; > > > > + u32 color = 0; > > > Un-needed init. > > > > > > > + int ret; > > > > + > > > > + /* Color property is optional in single color case */ > > > > + ret = of_property_read_u32(np, "color", &color); > > > > + if (ret < 0 && ret != -EINVAL) { > > > > + dev_err(chip->dev, "failed to parse 'color' of > > > > %pOF\n", np); > > > > + return ret; > > > > + } > > > > + > > > > + led->chip = chip; > > > > + init_data.fwnode = of_fwnode_handle(np); > > > > + > > > > + if (color == LED_COLOR_ID_RGB) { > > > > + cdev = &led->mcdev.led_cdev; > > > > + ret = ktd202x_setup_led_rgb(chip, np, led, > > > > &init_data); > > > > + } else { > > > > + cdev = &led->cdev; > > > > + ret = ktd202x_setup_led_single(chip, np, led, > > > > &init_data); > > > > + } > > > > + > > > > + if (ret) { > > > > + dev_err(chip->dev, "unable to register %s\n", > > > > cdev- > > > > > name); > > > > + of_node_put(np); > > > > > > This is strange to have it here. > > > Why not above after "if (ret < 0 && ret != -EINVAL) {"? > > > > > > It would look much more natural to have it a few lines below, ... > > > [1] > > > > Good catch. I'll move of_node_put(np); to [1] and [2]. > > Why [2]? > It does not seem needed here. > > of_get_available_child_count() does not keep any reference. > > CJ > > > > > > > > > > + return ret; > > > > + } > > > > + > > > > + cdev->max_brightness = KTD202X_MAX_BRIGHTNESS; > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static int ktd202x_probe_dt(struct ktd202x *chip) > > > > +{ > > > > + struct device_node *np = dev_of_node(chip->dev), > > > > *child; > > > > + unsigned int i; > > > > + int count, ret; > > > > + > > > > + chip->num_leds = (int)(unsigned > > > > long)of_device_get_match_data(chip->dev); > > > > + > > > > + count = of_get_available_child_count(np); > > > > + if (!count || count > chip->num_leds) > > > > [2]. > > > > > > + return -EINVAL; > > > > + > > > > + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, > > > > KTD202X_RSTR_RESET); > > > > + > > > > + /* Allow the device to execute the complete reset */ > > > > + usleep_range(200, 300); > > > > + > > > > + i = 0; > > > > + for_each_available_child_of_node(np, child) { > > > > + ret = ktd202x_add_led(chip, child, i); > > > > + if (ret) > > > > > > [1] ... here. > > > > > > Otherwise, it is likely that, thanks to a static checker, an > > > additionnal > > > of_node_put() will be added on early exit of the loop. > > > > > > CJ > > > > > > > + return ret; > > > > + i++; > > > > + } > > > > + > > > > + return 0; > > > > +} > > > > > > ... > > > > > > > Best regards, > > André > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2023-10-02 6:10 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-10-01 13:52 [PATCH v5 0/2] leds: Add a driver for KTD202x André Apitzsch 2023-10-01 13:52 ` [PATCH v5 1/2] dt-bindings: leds: Add Kinetic KTD2026/2027 LED André Apitzsch 2023-10-01 13:52 ` [PATCH v5 2/2] leds: add ktd202x driver André Apitzsch 2023-10-01 15:15 ` Christophe JAILLET 2023-10-01 16:56 ` André Apitzsch 2023-10-01 17:05 ` Uwe Kleine-König 2023-10-01 20:46 ` Christophe JAILLET 2023-10-02 6:09 ` André Apitzsch
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).