Add the binding description and the corresponding driver for
the Kinetic KTD2026 and KTD2027.
Signed-off-by: André Apitzsch <[email protected]>
---
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/[email protected]
Changes in v4:
- Annotate struct ktd202x with __counted_by
- Link to v3: https://lore.kernel.org/r/[email protected]
Changes in v3:
- Add r-b to bindings patch
- Replace .probe_new by .probe
- Link to v2: https://lore.kernel.org/r/[email protected]
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/[email protected]
[1] https://lore.kernel.org/linux-leds/[email protected]/
---
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 <[email protected]>
This commit adds support for Kinetic KTD2026/7 RGB/White LED driver.
Signed-off-by: André Apitzsch <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_DESCRIPTION("Kinetic KTD2026/7 LED driver");
+MODULE_LICENSE("GPL");
--
2.42.0
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
> > <[email protected]>
>
> ...
>
> > +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é
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/ |
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
>>> <[email protected]>
>>
>> ...
>>
>>> +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é
>
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
> > > > <[email protected]>
> > >
> > > ...
> > >
> > > > +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é
> >
>
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 <[email protected]>
...
> +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;
> +}
...