diff mbox series

[v2,2/2] leds: Add LED1202 I2C driver

Message ID ZnGMAK9bd3pZjWmG@admins-Air
State Superseded
Headers show
Series None | expand

Commit Message

Vicentiu Galanopulo June 18, 2024, 1:30 p.m. UTC
The LED1202 is a 12-channel low quiescent 
current LED driver. The output current can 
be adjusted separately for each channel by 
8-bit analog (current sink input) and 
12-bit digital (PWM) dimming control.

The LED1202 implements 12 low-side current 
generators with independent dimming control.
Internal volatile memory allows the user 
to store up to 8 different patterns, each 
pattern is a particular output configuration 
in terms of PWM duty-cycle (on 4096 steps).
Analog dimming (on 256 steps) is 
per channel but common to all patterns.

Each active=1 device tree LED node will
have a corresponding entry in /sys/class/leds
with the label name.
The brightness property corresponds to the
per channel analog dimming, while the 
patterns[1-8] to the PWM dimming control.

Signed-off-by: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
---

Changes in v2:
  - Fix build error for device_attribute modes

 drivers/leds/Kconfig        |  10 +
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-led1202.c | 617 ++++++++++++++++++++++++++++++++++++
 3 files changed, 628 insertions(+)
 create mode 100644 drivers/leds/leds-led1202.c

Comments

Lee Jones June 20, 2024, 5:55 p.m. UTC | #1
I'm going do a very quick once through of this one.

There is a lot of work to do.

> The LED1202 is a 12-channel low quiescent 

Please line wrap at 70-something chars not 40

> current LED driver. The output current can 
> be adjusted separately for each channel by 
> 8-bit analog (current sink input) and 
> 12-bit digital (PWM) dimming control.
> 
> The LED1202 implements 12 low-side current 
> generators with independent dimming control.
> Internal volatile memory allows the user 
> to store up to 8 different patterns, each 
> pattern is a particular output configuration 
> in terms of PWM duty-cycle (on 4096 steps).
> Analog dimming (on 256 steps) is 
> per channel but common to all patterns.
> 
> Each active=1 device tree LED node will
> have a corresponding entry in /sys/class/leds
> with the label name.
> The brightness property corresponds to the
> per channel analog dimming, while the 
> patterns[1-8] to the PWM dimming control.
> 
> Signed-off-by: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
> ---
> 
> Changes in v2:
>   - Fix build error for device_attribute modes
> 
>  drivers/leds/Kconfig        |  10 +
>  drivers/leds/Makefile       |   1 +
>  drivers/leds/leds-led1202.c | 617 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 628 insertions(+)
>  create mode 100644 drivers/leds/leds-led1202.c
> 
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 05e6af88b88c..c65f2b1bbe30 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -899,6 +899,16 @@ config LEDS_LM36274
>  	  Say Y to enable the LM36274 LED driver for TI LMU devices.
>  	  This supports the LED device LM36274.
>  
> +config LEDS_LED1202
> +	tristate "LED Support for LED1202 I2C chips"
> +	depends on LEDS_CLASS
> +	depends on I2C
> +	depends on OF
> +	help
> +	  Say Y to enable support for LEDs connected to LED1202
> +	  LED driver chips accessed via the I2C bus.
> +	  Supported devices include LED1202.
> +
>  config LEDS_TPS6105X
>  	tristate "LED support for TI TPS6105X"
>  	depends on LEDS_CLASS
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index effdfc6f1e95..80423fa8818e 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_IP30)			+= leds-ip30.o
>  obj-$(CONFIG_LEDS_IPAQ_MICRO)		+= leds-ipaq-micro.o
>  obj-$(CONFIG_LEDS_IS31FL319X)		+= leds-is31fl319x.o
>  obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
> +obj-$(CONFIG_LEDS_LED1202)		+= leds-led1202.o
>  obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
>  obj-$(CONFIG_LEDS_LM3532)		+= leds-lm3532.o
>  obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
> diff --git a/drivers/leds/leds-led1202.c b/drivers/leds/leds-led1202.c
> new file mode 100644
> index 000000000000..4e82f0e66168
> --- /dev/null
> +++ b/drivers/leds/leds-led1202.c
> @@ -0,0 +1,617 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Simple LED driver for ST LED1202 chip

Just a simple 600+ line driver?

ST as in STMicroelectronics?

Please make that clear in the header and in the Kconfig entry.

> + * Copyright (C) 2024 Remote-Tech Ltd. UK
> + */

New line here.

> +#include <linux/module.h>
> +#include <linux/string.h>
> +#include <linux/ctype.h>
> +#include <linux/leds.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/slab.h>
> +#include <linux/gpio.h>
> +#include <linux/delay.h>
> +
> +#define DRIVER_NAME "led-driver-1202"

Remove this and user dev_err() instead.

> +#define DRIVER_VERSION "0.0.1"

Remove this entirely, it's useless.

> +
> +#define LL1202_MAX_LEDS 12
> +
> +#define LL1202_DEVICE_ID 0x00
> +#define LL1202_DEV_ENABLE 0x01
> +#define LL1202_CHAN_ENABLE_LOW 0x02
> +#define LL1202_CHAN_ENABLE_HIGH 0x03
> +#define LL1202_CONFIG_REG 0x04
> +#define LL1202_ILED_REG0 0x09
> +#define LL1202_PATTERN_REP 0x15
> +#define LL1202_PATTERN_DUR 0x16
> +#define LL1202_PATTERN_PWM 0x1E
> +#define LL1202_CLOCK_REG 0xE0

Tab out the values so they line up.

> +struct ll1202_led {
> +	struct led_classdev led_cdev;
> +	struct ll1202_chip *chip;
> +	int led_num;
> +	char name[32];

Define this and all magic numbers.

> +	int is_active;
> +};
> +
> +struct ll1202_chip {
> +	struct i2c_client *client;
> +	struct mutex lock;
> +	struct ll1202_led leds[LL1202_MAX_LEDS];
> +};
> +
> +static struct ll1202_led *cdev_to_ll1202_led(struct led_classdev *cdev)
> +{
> +	return container_of(cdev, struct ll1202_led, led_cdev);
> +}
> +
> +static int ll1202_read_reg(struct ll1202_chip *chip, int reg, uint8_t *val)
> +{
> +	int ret = i2c_smbus_read_byte_data(chip->client, reg);

Separate the declaration and the function call.

> +
> +	if (ret < 0)
> +		return ret;
> +
> +	*val = (uint8_t)ret;
> +	return 0;
> +}
> +
> +static int ll1202_write_reg(struct ll1202_chip *chip, int reg, uint8_t val)

What's "ll"?

> +{
> +	return i2c_smbus_write_byte_data(chip->client, reg, val);
> +}
> +
> +static int ll1202_get_channel(struct device *dev)
> +{
> +	struct device_node *np = dev->parent->of_node, *child;
> +	int err, ret = -1;

What is -1?

> +	for_each_child_of_node(np, child) {
> +		if (strncmp(dev->kobj.name,
> +			    of_get_property(child, "label", NULL),
> +			    strnlen(dev->kobj.name, MAX_INPUT)) == 0) {

Pull all of these embedded functions out.

> +			err = of_property_read_u32(child, "reg", &ret);
> +			if (err) {
> +				of_node_put(child);
> +				pr_err(DRIVER_NAME
> +				       ": Failed to read property %s", child->name);
> +				return ret;
> +			}
> +			break;
> +		}
> +	}
> +	return ret;
> +}
> +
> +static ssize_t ll1202_show_all_registers(struct device *dev,
> +					 struct device_attribute *devattr,
> +					 char *buf)
> +{
> +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> +	uint8_t reg_value = 0;
> +	int ret, i;
> +	char *bufp = buf;
> +
> +	mutex_lock(&chip->lock);
> +
> +	for (i = LL1202_DEVICE_ID; i <= LL1202_CLOCK_REG; i++) {
> +		ret = ll1202_read_reg(chip, i, &reg_value);
> +		if (ret != 0)
> +			dev_err(&chip->client->dev,
> +				"Reading register [0x%x] failed.\n", i);
> +
> +		bufp += snprintf(bufp, PAGE_SIZE, "Addr[0x%x] = 0x%x\n", i,
> +				 reg_value);
> +	}
> +
> +	mutex_unlock(&chip->lock);
> +	return strlen(buf);
> +}

Does this have any real world use?

> +static ssize_t
> +ll1202_show_patt_sequence_repetition(struct device *dev,
> +				     struct device_attribute *attr, char *buf)
> +{
> +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> +	unsigned int ret;
> +	uint8_t reg_value;
> +	char *bufp = buf;
> +
> +	mutex_lock(&chip->lock);
> +	ret = ll1202_read_reg(chip, LL1202_PATTERN_REP, &reg_value);
> +	if (ret != 0)
> +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n", LL1202_PATTERN_REP);
> +	mutex_unlock(&chip->lock);
> +	bufp += snprintf(bufp, PAGE_SIZE,
> +			 "Pattern sequence register, repetition value = %d (times)\n",
> +			 reg_value);
> +	return strlen(buf);
> +}
> +
> +static ssize_t
> +ll1202_store_patt_sequence_repetition(struct device *dev,
> +				      struct device_attribute *attr,
> +				      const char *buf, size_t count)
> +{
> +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> +	unsigned int ret;
> +	unsigned long duration;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	ret = kstrtoul(buf, 10, &duration);
> +	if (ret) {
> +		dev_err(&chip->client->dev, "sscanf failed with error :%d\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	mutex_lock(&chip->lock);
> +	ret = ll1202_write_reg(chip, LL1202_PATTERN_REP, duration);
> +	if (ret != 0)
> +		dev_err(&chip->client->dev, "Writing register [0x%x] failed\n",
> +			LL1202_PATTERN_REP);
> +	mutex_unlock(&chip->lock);
> +	return count;
> +}
> +
> +static int ll1202_prescalar_to_miliamps(uint8_t reg_value)
> +{
> +	return reg_value * 20 / 255;

Define _all_ magic numbers.

> +}
> +
> +static int ll1202_prescalar_to_miliseconds(uint8_t reg_value)
> +{
> +	return reg_value * 5660 / 255;
> +}
> +
> +static ssize_t ll1202_show_channel_mA_current(struct device *dev,
> +					      struct device_attribute *attr,
> +					      char *buf)
> +{
> +	struct ll1202_chip *chip = dev_get_drvdata(dev->parent);
> +	unsigned int ret;
> +	uint8_t reg_value;
> +	char *bufp = buf;
> +	int led_num = ll1202_get_channel(dev);
> +
> +	if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {
> +		dev_err(&chip->client->dev,
> +			"Invalid register [0x%x] (out of range)\n",
> +			led_num);
> +	}
> +	mutex_lock(&chip->lock);
> +	ret = ll1202_read_reg(chip, LL1202_ILED_REG0 + led_num, &reg_value);
> +	if (ret != 0)
> +		dev_err(&chip->client->dev, "Reading analog dimming register [0x%x] failed\n",
> +			led_num);
> +	mutex_unlock(&chip->lock);
> +	bufp += snprintf(bufp, PAGE_SIZE, "Channel[%d] = %d mA\n", led_num,
> +			 ll1202_prescalar_to_miliamps(reg_value));
> +	return strlen(buf);

Space out the code properly - this is really tough to read.

> +}
> +
> +static int ll1202_channel_activate(struct ll1202_led *led)
> +{
> +	struct ll1202_chip *chip;
> +	uint8_t reg_chan_low, reg_chan_high;
> +	int ret = 0;
> +
> +	chip = led->chip;
> +	if (led->is_active) {

Reverse this logic and unindent this block.

> +		mutex_lock(&chip->lock);
> +
> +		ret = ll1202_read_reg(chip, LL1202_CHAN_ENABLE_LOW,
> +				      &reg_chan_low);
> +		if (ret < 0) {
> +			dev_err(&chip->client->dev,
> +				"Failed reading register [0x%x]\n", LL1202_CHAN_ENABLE_LOW);
> +		}
> +
> +		ret = ll1202_read_reg(chip, LL1202_CHAN_ENABLE_HIGH,
> +				      &reg_chan_high);
> +		if (ret < 0) {
> +			dev_err(&chip->client->dev,
> +				"Failed reading register [0x%x]\n", LL1202_CHAN_ENABLE_HIGH);
> +		}
> +
> +		reg_chan_low = reg_chan_low | BIT(led->led_num);
> +		ret = ll1202_write_reg(chip, LL1202_CHAN_ENABLE_LOW,
> +				       reg_chan_low);
> +		if (ret < 0) {
> +			dev_err(&chip->client->dev,
> +				"Failed writing to register [0x%x]\n", LL1202_CHAN_ENABLE_LOW);
> +		}
> +		reg_chan_high = reg_chan_high | (BIT(led->led_num) >> 7);
> +		ret = ll1202_write_reg(chip, LL1202_CHAN_ENABLE_HIGH,
> +				       reg_chan_high);
> +		if (ret < 0) {
> +			dev_err(&chip->client->dev,
> +				"Failed writing to register [0x%x]\n", LL1202_CHAN_ENABLE_HIGH);
> +		}

Provide a comment as to why this cycle needs to be done twice.

> +		mutex_unlock(&chip->lock);
> +	}
> +	return ret;
> +}
> +
> +#define LL1202_PWM_PATTERN_ATTR(pattern)                                          \

No chance!

Where else do you see code like this?

> +	static ssize_t ll1202_show_pwm_pattern##pattern(                          \
> +		struct device *dev, struct device_attribute *attr, char *buf)     \
> +	{                                                                         \
> +		struct ll1202_chip *chip = dev_get_drvdata(dev->parent);          \
> +		uint8_t duration = 0;                                             \
> +		uint8_t reg_value_l = 0;                                          \
> +		uint8_t reg_value_h = 0;                                          \
> +		uint16_t reg_value = 0;                                           \
> +		int ret;                                                          \
> +		char *bufp = buf;                                                 \
> +		int led_num = ll1202_get_channel(dev);                            \
> +		if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {                  \
> +			dev_err(&chip->client->dev,                               \
> +				"Invalid register [0x%x] (out of range)\n",  \
> +				led_num);                                         \
> +		}                                                                 \
> +		mutex_lock(&chip->lock);                                          \
> +		ret = ll1202_read_reg(                                            \
> +			chip,                                                     \
> +			(LL1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),    \
> +			&reg_value_l);                                            \
> +		if (ret != 0)                                                     \
> +			dev_err(&chip->client->dev,                               \
> +				"Reading pattern PWM register [0x%x] failed\n", led_num);     \
> +		ret = ll1202_read_reg(chip,                                       \
> +				      (LL1202_PATTERN_PWM + 0x1 +                 \
> +				       (led_num * 2) + 0x18 * pattern),           \
> +				      &reg_value_h);                              \
> +		if (ret != 0)                                                     \
> +			dev_err(&chip->client->dev,                               \
> +				"Reading pattern PWM register [0x%x] failed\n", led_num);     \
> +		reg_value = (uint16_t)reg_value_h << 8 | reg_value_l;             \
> +		ret = ll1202_read_reg(chip, (LL1202_PATTERN_DUR + pattern),       \
> +				      &duration);                                 \
> +		if (ret != 0)                                                     \
> +			dev_err(&chip->client->dev,                               \
> +				"Reading pattern durating register [0x%x] failed\n", led_num);     \
> +		bufp += snprintf(                                                 \
> +			bufp, PAGE_SIZE,                                          \
> +			"Pattern[%d][cs%d]: PWM = 0x%03X; DURATION = %d ms\n",    \
> +			pattern, led_num, reg_value,                              \
> +			ll1202_prescalar_to_miliseconds(duration));               \
> +		mutex_unlock(&chip->lock);                                        \
> +		return strlen(buf);                                               \
> +	}                                                                         \
> +	static ssize_t ll1202_store_pwm_pattern##pattern(                         \
> +		struct device *dev, struct device_attribute *attr,                \
> +		const char *buf, size_t count)                                    \
> +	{                                                                         \
> +		struct ll1202_chip *chip = dev_get_drvdata(dev->parent);          \
> +		unsigned int ret, reg_value;                                      \
> +		unsigned long duration;                                           \
> +		char buf_u8[16];                                                  \
> +		uint8_t reg_value_l = 0;                                          \
> +		uint8_t reg_value_h = 0;                                          \
> +		int led_num = ll1202_get_channel(dev);                            \
> +		if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {                  \
> +			dev_err(&chip->client->dev,                               \
> +				"Invalid register [0x%x] (out of range)\n",  \
> +				led_num);                                         \
> +			return count;                                             \
> +		}                                                                 \
> +		if (!count)                                                       \
> +			return -EINVAL;                                           \
> +		ret = sscanf(buf, "%X %s", &reg_value, buf_u8);                   \
> +		if (ret == 0) {                                                   \
> +			dev_err(&chip->client->dev,                               \
> +				"sscanf failed with error :%d\n", ret);           \
> +			return ret;                                               \
> +		}                                                                 \
> +		ret = kstrtoul(buf_u8, 10, &duration);                            \
> +		if (ret)                                                          \
> +			return ret;                                               \
> +		reg_value_l = (uint8_t)reg_value;                                 \
> +		reg_value_h = (uint8_t)(reg_value >> 8);                          \
> +		mutex_lock(&chip->lock);                                          \
> +		ret = ll1202_write_reg(                                           \
> +			chip,                                                     \
> +			(LL1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),    \
> +			(uint8_t)reg_value_l);                                    \
> +		if (ret != 0)                                                     \
> +			dev_err(&chip->client->dev,                               \
> +				"Writing to register [0x%x] failed, value %d\n",  \
> +				LL1202_PATTERN_PWM + (led_num * 2) +              \
> +					0x18 * pattern,                           \
> +				reg_value_l);                                     \
> +		ret = ll1202_write_reg(chip,                                      \
> +				       (LL1202_PATTERN_PWM + 0x1 +                \
> +					(led_num * 2) + 0x18 * pattern),          \
> +				       (uint8_t)reg_value_h);                     \
> +		if (ret != 0)                                                     \
> +			dev_err(&chip->client->dev,                               \
> +				"Writing to register [0x%x] failed, value %d\n",  \
> +				(LL1202_PATTERN_PWM + 0x1 + (led_num * 2) +       \
> +				 0x18 * pattern),                                 \
> +				reg_value_h);                                     \
> +		ret = ll1202_write_reg(chip, (LL1202_PATTERN_DUR + pattern),      \
> +				       (u8)duration);                             \
> +		if (ret != 0)                                                     \
> +			dev_err(&chip->client->dev,                               \
> +				"Writing to register [0x%x] failed, value %d\n", \
> +				(LL1202_PATTERN_DUR + pattern), (u8)duration);    \
> +		ret = ll1202_write_reg(chip, LL1202_CONFIG_REG,                   \
> +				       (0xC0 | pattern));                         \
> +		if (ret != 0) {                                                   \
> +			dev_err(&chip->client->dev,                               \
> +				"Failed writing to reg [0x%x]\n", LL1202_CONFIG_REG);     \
> +		}                                                                 \
> +		mutex_unlock(&chip->lock);                                        \
> +		ll1202_channel_activate(&chip->leds[led_num]);                    \
> +		return count;                                                     \
> +	}                                                                         \
> +	static struct device_attribute dev_attr_led_pwm_pattern##pattern = {		\
> +	.attr = {							\
> +	.name = __stringify(pwm_pattern##pattern),					\
> +	.mode =  00444 | 00200,						\
> +	},								\
> +	.show = ll1202_show_pwm_pattern##pattern,				\
> +	.store = ll1202_store_pwm_pattern##pattern,				\
> +}
> +
> +LL1202_PWM_PATTERN_ATTR(0);
> +LL1202_PWM_PATTERN_ATTR(1);
> +LL1202_PWM_PATTERN_ATTR(2);
> +LL1202_PWM_PATTERN_ATTR(3);
> +LL1202_PWM_PATTERN_ATTR(4);
> +LL1202_PWM_PATTERN_ATTR(5);
> +LL1202_PWM_PATTERN_ATTR(6);
> +LL1202_PWM_PATTERN_ATTR(7);

We already have global helpers for this type of thing.

> +static DEVICE_ATTR(led_device_regsdump, 00444, ll1202_show_all_registers,
> +		   NULL);
> +static DEVICE_ATTR(patt_sequence_repetition, 00444 | 00200,
> +		   ll1202_show_patt_sequence_repetition,
> +		   ll1202_store_patt_sequence_repetition);
> +static DEVICE_ATTR(current_mA, 00444, ll1202_show_channel_mA_current, NULL);
> +
> +static struct attribute *led_attrs[] = {
> +	&dev_attr_led_device_regsdump.attr,
> +	&dev_attr_patt_sequence_repetition.attr,
> +	NULL,
> +};
> +
> +static struct attribute *led_group_attrs[] = {
> +	&dev_attr_led_pwm_pattern0.attr, &dev_attr_led_pwm_pattern1.attr,
> +	&dev_attr_led_pwm_pattern2.attr, &dev_attr_led_pwm_pattern3.attr,
> +	&dev_attr_led_pwm_pattern4.attr, &dev_attr_led_pwm_pattern5.attr,
> +	&dev_attr_led_pwm_pattern6.attr, &dev_attr_led_pwm_pattern7.attr,
> +	&dev_attr_current_mA.attr,	 NULL,
> +};
> +
> +static struct attribute_group attr_group = {
> +	.attrs = led_attrs,
> +};
> +
> +static struct attribute_group attr_pat_group = {
> +	.attrs = led_group_attrs,
> +};
> +
> +static const struct attribute_group *ll1202_groups[] = { &attr_pat_group,
> +							 NULL };
> +
> +static void ll1202_brightness_set(struct led_classdev *led_cdev,
> +				  enum led_brightness value)
> +{
> +	struct ll1202_led *led = cdev_to_ll1202_led(led_cdev);
> +	struct ll1202_chip *chip = led->chip;
> +	int ret;
> +
> +	mutex_lock(&chip->lock);
> +	ret = ll1202_write_reg(chip, LL1202_ILED_REG0 + led->led_num, value);
> +	if (ret != 0)
> +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n",
> +			LL1202_ILED_REG0 + led->led_num);
> +	mutex_unlock(&chip->lock);
> +}
> +
> +static enum led_brightness ll1202_brightness_get(struct led_classdev *led_cdev)
> +{
> +	struct ll1202_led *led = cdev_to_ll1202_led(led_cdev);
> +	struct ll1202_chip *chip = led->chip;
> +	uint8_t reg_value;
> +	int ret;
> +
> +	mutex_lock(&chip->lock);
> +	ret = ll1202_read_reg(chip, LL1202_ILED_REG0 + led->led_num,
> +			      &reg_value);
> +	if (ret != 0)
> +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n",
> +			LL1202_ILED_REG0 + led->led_num);
> +
> +	mutex_unlock(&chip->lock);
> +	return reg_value;
> +}
> +
> +static int ll1202_dt_init(struct ll1202_chip *chip)
> +{
> +	struct device_node *np = chip->client->dev.of_node, *child;
> +	struct ll1202_led *led;
> +	int err, reg;
> +
> +	for_each_child_of_node(np, child) {
> +		err = of_property_read_u32(child, "reg", &reg);
> +		if (err) {
> +			of_node_put(child);
> +			pr_err(DRIVER_NAME ": Failed to get child node");
> +			return err;
> +		}
> +		if (reg < 0 || reg >= LL1202_MAX_LEDS) {
> +			of_node_put(child);
> +			pr_err(DRIVER_NAME ": Invalid register value [0x%x] (out of range)", reg);
> +			return -EINVAL;
> +		}
> +
> +		led = &chip->leds[reg];
> +		led->led_cdev.name = of_get_property(child, "label", NULL) ?:
> +					     child->name;
> +
> +		err = of_property_read_u32(child, "active", &led->is_active);
> +		if (err) {
> +			of_node_put(child);
> +			pr_err(DRIVER_NAME ": Failed to get child node");
> +			return err;
> +		}
> +
> +		led->led_cdev.brightness_set = ll1202_brightness_set;
> +		led->led_cdev.brightness_get = ll1202_brightness_get;
> +		led->led_cdev.groups = ll1202_groups;
> +	}
> +	return 0;
> +}
> +
> +static int ll1202_setup(struct ll1202_chip *chip)
> +{
> +	int ret;
> +
> +	mutex_lock(&chip->lock);
> +	ret = ll1202_write_reg(chip, LL1202_DEV_ENABLE, 0x1);
> +	if (ret < 0) {
> +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> +			LL1202_DEV_ENABLE);
> +	}
> +	mutex_unlock(&chip->lock);
> +	usleep_range(6500, 10000);
> +	mutex_lock(&chip->lock);
> +	ret = ll1202_write_reg(chip, LL1202_DEV_ENABLE, 0x80);
> +	if (ret < 0) {
> +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> +			LL1202_DEV_ENABLE);
> +	}
> +	mutex_unlock(&chip->lock);
> +	usleep_range(6500, 10000);
> +	mutex_lock(&chip->lock);
> +	ret = ll1202_write_reg(chip, LL1202_PATTERN_REP, 0xFF);
> +	if (ret < 0) {
> +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> +			LL1202_PATTERN_REP);
> +		return ret;
> +	}
> +	mutex_unlock(&chip->lock);
> +	return ret;
> +}
> +
> +static int ll1202_probe(struct i2c_client *client)
> +{
> +	struct ll1202_chip *chip;
> +	struct ll1202_led *led;
> +	int ret, err;
> +	int i;
> +
> +	pr_info(DRIVER_NAME ": (I2C) " DRIVER_VERSION "\n");
> +
> +	if (!i2c_check_functionality(client->adapter,
> +				     I2C_FUNC_SMBUS_BYTE_DATA)) {
> +		dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
> +		return -EIO;
> +	}
> +
> +	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
> +	if (!chip)
> +		return -ENOMEM;
> +
> +	i2c_set_clientdata(client, chip);
> +
> +	mutex_init(&chip->lock);
> +	chip->client = client;
> +
> +	/* Device tree setup */
> +	ret = ll1202_dt_init(chip);
> +	if (ret < 0)
> +		goto exit;
> +
> +	/* Configuration setup */
> +	ret = ll1202_setup(chip);
> +	if (ret < 0)
> +		goto exit;
> +
> +	for (i = 0; i < LL1202_MAX_LEDS; i++) {
> +		led = &chip->leds[i];
> +		led->chip = chip;
> +		led->led_num = i;
> +		if (led->is_active) {
> +			err = led_classdev_register(&client->dev,
> +						    &led->led_cdev);
> +			if (err < 0) {
> +				pr_err(DRIVER_NAME
> +				       ": Failed to register LED class dev");
> +				goto exit;
> +			}
> +		}
> +	}
> +
> +	ret = sysfs_create_group(&client->dev.kobj, &attr_group);
> +	if (ret) {
> +		dev_err(&client->dev,
> +			"Failed to create sysfs group for ll1202\n");
> +		goto err_setup;
> +	}
> +
> +	return 0;
> +
> +err_setup:
> +	for (i = 0; i < LL1202_MAX_LEDS; i++)
> +		led_classdev_unregister(&chip->leds[i].led_cdev);
> +exit:
> +	mutex_destroy(&chip->lock);
> +	devm_kfree(&client->dev, chip);
> +	return ret;
> +}
> +
> +static void ll1202_remove(struct i2c_client *client)
> +{
> +	struct ll1202_chip *dev = i2c_get_clientdata(client);
> +	int i;
> +
> +	for (i = 0; i < LL1202_MAX_LEDS; i++)
> +		led_classdev_unregister(&dev->leds[i].led_cdev);
> +
> +	sysfs_remove_group(&client->dev.kobj, &attr_group);
> +
> +	mutex_destroy(&dev->lock);
> +	devm_kfree(&client->dev, dev->leds);
> +	devm_kfree(&client->dev, dev);
> +}
> +
> +static const struct i2c_device_id ll1202_id[] = {
> +	{ DRIVER_NAME "-i2c", 0 },
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, ll1202_id);
> +
> +static const struct of_device_id ll1202_dt_ids[] = {
> +	{
> +		.compatible = "st,led1202",
> +	},
> +};
> +
> +MODULE_DEVICE_TABLE(of, ll1202_dt_ids);
> +
> +static struct i2c_driver ll1202_driver = {
> +	.driver = {
> +		.name = "ll1202",
> +		.of_match_table = of_match_ptr(ll1202_dt_ids),
> +	},
> +	.probe = ll1202_probe,
> +	.remove = ll1202_remove,
> +	.id_table = ll1202_id,
> +};
> +
> +module_i2c_driver(ll1202_driver);
> +
> +MODULE_AUTHOR("Remote Tech LTD");
> +MODULE_DESCRIPTION("LED1202 : 12-channel constant current LED driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.25.1
>
Vicentiu Galanopulo June 24, 2024, 2:31 p.m. UTC | #2
On Thu, Jun 20, 2024 at 06:55:43PM +0100, Lee Jones wrote:
> I'm going do a very quick once through of this one.
> 
> There is a lot of work to do.
> 
> > The LED1202 is a 12-channel low quiescent 
> 
> Please line wrap at 70-something chars not 40
>

Ok, will do. I'm using Visual Code as editor.
Do you know any config options for it?
If not maybe another editor that is free
and works on Mac ARM.
 
> > current LED driver. The output current can 
> > be adjusted separately for each channel by 
> > 8-bit analog (current sink input) and 
> > 12-bit digital (PWM) dimming control.
> > 
> > The LED1202 implements 12 low-side current 
> > generators with independent dimming control.
> > Internal volatile memory allows the user 
> > to store up to 8 different patterns, each 
> > pattern is a particular output configuration 
> > in terms of PWM duty-cycle (on 4096 steps).
> > Analog dimming (on 256 steps) is 
> > per channel but common to all patterns.
> > 
> > Each active=1 device tree LED node will
> > have a corresponding entry in /sys/class/leds
> > with the label name.
> > The brightness property corresponds to the
> > per channel analog dimming, while the 
> > patterns[1-8] to the PWM dimming control.
> > 
> > Signed-off-by: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
> > ---
> > 
> > Changes in v2:
> >   - Fix build error for device_attribute modes
> > 
> >  drivers/leds/Kconfig        |  10 +
> >  drivers/leds/Makefile       |   1 +
> >  drivers/leds/leds-led1202.c | 617 ++++++++++++++++++++++++++++++++++++
> >  3 files changed, 628 insertions(+)
> >  create mode 100644 drivers/leds/leds-led1202.c
> > 
> > diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> > index 05e6af88b88c..c65f2b1bbe30 100644
> > --- a/drivers/leds/Kconfig
> > +++ b/drivers/leds/Kconfig
> > @@ -899,6 +899,16 @@ config LEDS_LM36274
> >  	  Say Y to enable the LM36274 LED driver for TI LMU devices.
> >  	  This supports the LED device LM36274.
> >  
> > +config LEDS_LED1202
> > +	tristate "LED Support for LED1202 I2C chips"
> > +	depends on LEDS_CLASS
> > +	depends on I2C
> > +	depends on OF
> > +	help
> > +	  Say Y to enable support for LEDs connected to LED1202
> > +	  LED driver chips accessed via the I2C bus.
> > +	  Supported devices include LED1202.
> > +
> >  config LEDS_TPS6105X
> >  	tristate "LED support for TI TPS6105X"
> >  	depends on LEDS_CLASS
> > diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> > index effdfc6f1e95..80423fa8818e 100644
> > --- a/drivers/leds/Makefile
> > +++ b/drivers/leds/Makefile
> > @@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_IP30)			+= leds-ip30.o
> >  obj-$(CONFIG_LEDS_IPAQ_MICRO)		+= leds-ipaq-micro.o
> >  obj-$(CONFIG_LEDS_IS31FL319X)		+= leds-is31fl319x.o
> >  obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
> > +obj-$(CONFIG_LEDS_LED1202)		+= leds-led1202.o
> >  obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
> >  obj-$(CONFIG_LEDS_LM3532)		+= leds-lm3532.o
> >  obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
> > diff --git a/drivers/leds/leds-led1202.c b/drivers/leds/leds-led1202.c
> > new file mode 100644
> > index 000000000000..4e82f0e66168
> > --- /dev/null
> > +++ b/drivers/leds/leds-led1202.c
> > @@ -0,0 +1,617 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Simple LED driver for ST LED1202 chip
> 
> Just a simple 600+ line driver?
> 

With you help maybe I can reduce it.

> ST as in STMicroelectronics?
> 
> Please make that clear in the header and in the Kconfig entry.
> 

Sure, will do.

> > + * Copyright (C) 2024 Remote-Tech Ltd. UK
> > + */
> 
> New line here.
> 
Noted, will add.

> > +#include <linux/module.h>
> > +#include <linux/string.h>
> > +#include <linux/ctype.h>
> > +#include <linux/leds.h>
> > +#include <linux/err.h>
> > +#include <linux/i2c.h>
> > +#include <linux/slab.h>
> > +#include <linux/gpio.h>
> > +#include <linux/delay.h>
> > +
> > +#define DRIVER_NAME "led-driver-1202"
> 
> Remove this and user dev_err() instead.
> 
> > +#define DRIVER_VERSION "0.0.1"
> 
> Remove this entirely, it's useless.
>

Will do in v3. 

> > +
> > +#define LL1202_MAX_LEDS 12
> > +
> > +#define LL1202_DEVICE_ID 0x00
> > +#define LL1202_DEV_ENABLE 0x01
> > +#define LL1202_CHAN_ENABLE_LOW 0x02
> > +#define LL1202_CHAN_ENABLE_HIGH 0x03
> > +#define LL1202_CONFIG_REG 0x04
> > +#define LL1202_ILED_REG0 0x09
> > +#define LL1202_PATTERN_REP 0x15
> > +#define LL1202_PATTERN_DUR 0x16
> > +#define LL1202_PATTERN_PWM 0x1E
> > +#define LL1202_CLOCK_REG 0xE0
> 
> Tab out the values so they line up.
>
OK 
> > +struct ll1202_led {
> > +	struct led_classdev led_cdev;
> > +	struct ll1202_chip *chip;
> > +	int led_num;
> > +	char name[32];
> 
> Define this and all magic numbers.
> 
Ok, there is some bitwise operations to form a 16bit from 8bit values.
And some magic number coming from the datasheet.
Those also?
> > +	int is_active;
> > +};
> > +
> > +struct ll1202_chip {
> > +	struct i2c_client *client;
> > +	struct mutex lock;
> > +	struct ll1202_led leds[LL1202_MAX_LEDS];
> > +};
> > +
> > +static struct ll1202_led *cdev_to_ll1202_led(struct led_classdev *cdev)
> > +{
> > +	return container_of(cdev, struct ll1202_led, led_cdev);
> > +}
> > +
> > +static int ll1202_read_reg(struct ll1202_chip *chip, int reg, uint8_t *val)
> > +{
> > +	int ret = i2c_smbus_read_byte_data(chip->client, reg);
> 
> Separate the declaration and the function call.
> 
Ok
> > +
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	*val = (uint8_t)ret;
> > +	return 0;
> > +}
> > +
> > +static int ll1202_write_reg(struct ll1202_chip *chip, int reg, uint8_t val)
> 
> What's "ll"?
>
 
I reused some naming. Should it be led1202_ for all?

> > +{
> > +	return i2c_smbus_write_byte_data(chip->client, reg, val);
> > +}
> > +
> > +static int ll1202_get_channel(struct device *dev)
> > +{
> > +	struct device_node *np = dev->parent->of_node, *child;
> > +	int err, ret = -1;
> 
> What is -1?

Just negative return value. It's a helper function that returns the LED
channel number based on struct device.

If this is not appropiate or custom practice I can redo it, but I need some pointers
on where to look as "good" examples.

 
> > +	for_each_child_of_node(np, child) {
> > +		if (strncmp(dev->kobj.name,
> > +			    of_get_property(child, "label", NULL),
> > +			    strnlen(dev->kobj.name, MAX_INPUT)) == 0) {
> 
> Pull all of these embedded functions out.
> 
Ok.
> > +			err = of_property_read_u32(child, "reg", &ret);
> > +			if (err) {
> > +				of_node_put(child);
> > +				pr_err(DRIVER_NAME
> > +				       ": Failed to read property %s", child->name);
> > +				return ret;
> > +			}
> > +			break;
> > +		}
> > +	}
> > +	return ret;
> > +}
> > +
> > +static ssize_t ll1202_show_all_registers(struct device *dev,
> > +					 struct device_attribute *devattr,
> > +					 char *buf)
> > +{
> > +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> > +	uint8_t reg_value = 0;
> > +	int ret, i;
> > +	char *bufp = buf;
> > +
> > +	mutex_lock(&chip->lock);
> > +
> > +	for (i = LL1202_DEVICE_ID; i <= LL1202_CLOCK_REG; i++) {
> > +		ret = ll1202_read_reg(chip, i, &reg_value);
> > +		if (ret != 0)
> > +			dev_err(&chip->client->dev,
> > +				"Reading register [0x%x] failed.\n", i);
> > +
> > +		bufp += snprintf(bufp, PAGE_SIZE, "Addr[0x%x] = 0x%x\n", i,
> > +				 reg_value);
> > +	}
> > +
> > +	mutex_unlock(&chip->lock);
> > +	return strlen(buf);
> > +}
> 
> Does this have any real world use?
>
 
A dump of all the registers with their values. I didn't add show/get functions for
all the registers.
Remove it?

> > +static ssize_t
> > +ll1202_show_patt_sequence_repetition(struct device *dev,
> > +				     struct device_attribute *attr, char *buf)
> > +{
> > +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> > +	unsigned int ret;
> > +	uint8_t reg_value;
> > +	char *bufp = buf;
> > +
> > +	mutex_lock(&chip->lock);
> > +	ret = ll1202_read_reg(chip, LL1202_PATTERN_REP, &reg_value);
> > +	if (ret != 0)
> > +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n", LL1202_PATTERN_REP);
> > +	mutex_unlock(&chip->lock);
> > +	bufp += snprintf(bufp, PAGE_SIZE,
> > +			 "Pattern sequence register, repetition value = %d (times)\n",
> > +			 reg_value);
> > +	return strlen(buf);
> > +}
> > +
> > +static ssize_t
> > +ll1202_store_patt_sequence_repetition(struct device *dev,
> > +				      struct device_attribute *attr,
> > +				      const char *buf, size_t count)
> > +{
> > +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> > +	unsigned int ret;
> > +	unsigned long duration;
> > +
> > +	if (!count)
> > +		return -EINVAL;
> > +
> > +	ret = kstrtoul(buf, 10, &duration);
> > +	if (ret) {
> > +		dev_err(&chip->client->dev, "sscanf failed with error :%d\n",
> > +			ret);
> > +		return ret;
> > +	}
> > +
> > +	mutex_lock(&chip->lock);
> > +	ret = ll1202_write_reg(chip, LL1202_PATTERN_REP, duration);
> > +	if (ret != 0)
> > +		dev_err(&chip->client->dev, "Writing register [0x%x] failed\n",
> > +			LL1202_PATTERN_REP);
> > +	mutex_unlock(&chip->lock);
> > +	return count;
> > +}
> > +
> > +static int ll1202_prescalar_to_miliamps(uint8_t reg_value)
> > +{
> > +	return reg_value * 20 / 255;
> 
> Define _all_ magic numbers.
>
Ok, will be done in v3 
> > +}
> > +
> > +static int ll1202_prescalar_to_miliseconds(uint8_t reg_value)
> > +{
> > +	return reg_value * 5660 / 255;
> > +}
> > +
> > +static ssize_t ll1202_show_channel_mA_current(struct device *dev,
> > +					      struct device_attribute *attr,
> > +					      char *buf)
> > +{
> > +	struct ll1202_chip *chip = dev_get_drvdata(dev->parent);
> > +	unsigned int ret;
> > +	uint8_t reg_value;
> > +	char *bufp = buf;
> > +	int led_num = ll1202_get_channel(dev);
> > +
> > +	if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {
> > +		dev_err(&chip->client->dev,
> > +			"Invalid register [0x%x] (out of range)\n",
> > +			led_num);
> > +	}
> > +	mutex_lock(&chip->lock);
> > +	ret = ll1202_read_reg(chip, LL1202_ILED_REG0 + led_num, &reg_value);
> > +	if (ret != 0)
> > +		dev_err(&chip->client->dev, "Reading analog dimming register [0x%x] failed\n",
> > +			led_num);
> > +	mutex_unlock(&chip->lock);
> > +	bufp += snprintf(bufp, PAGE_SIZE, "Channel[%d] = %d mA\n", led_num,
> > +			 ll1202_prescalar_to_miliamps(reg_value));
> > +	return strlen(buf);
> 
> Space out the code properly - this is really tough to read.
> 
Ok.. with or without the help of the IDE, it shall be done
> > +}
> > +
> > +static int ll1202_channel_activate(struct ll1202_led *led)
> > +{
> > +	struct ll1202_chip *chip;
> > +	uint8_t reg_chan_low, reg_chan_high;
> > +	int ret = 0;
> > +
> > +	chip = led->chip;
> > +	if (led->is_active) {
> 
> Reverse this logic and unindent this block.
> 
Sorry, I need some more details on what I need to do here.

> > +		mutex_lock(&chip->lock);
> > +
> > +		ret = ll1202_read_reg(chip, LL1202_CHAN_ENABLE_LOW,
> > +				      &reg_chan_low);
> > +		if (ret < 0) {
> > +			dev_err(&chip->client->dev,
> > +				"Failed reading register [0x%x]\n", LL1202_CHAN_ENABLE_LOW);
> > +		}
> > +
> > +		ret = ll1202_read_reg(chip, LL1202_CHAN_ENABLE_HIGH,
> > +				      &reg_chan_high);
> > +		if (ret < 0) {
> > +			dev_err(&chip->client->dev,
> > +				"Failed reading register [0x%x]\n", LL1202_CHAN_ENABLE_HIGH);
> > +		}
> > +
> > +		reg_chan_low = reg_chan_low | BIT(led->led_num);
> > +		ret = ll1202_write_reg(chip, LL1202_CHAN_ENABLE_LOW,
> > +				       reg_chan_low);
> > +		if (ret < 0) {
> > +			dev_err(&chip->client->dev,
> > +				"Failed writing to register [0x%x]\n", LL1202_CHAN_ENABLE_LOW);
> > +		}
> > +		reg_chan_high = reg_chan_high | (BIT(led->led_num) >> 7);
> > +		ret = ll1202_write_reg(chip, LL1202_CHAN_ENABLE_HIGH,
> > +				       reg_chan_high);
> > +		if (ret < 0) {
> > +			dev_err(&chip->client->dev,
> > +				"Failed writing to register [0x%x]\n", LL1202_CHAN_ENABLE_HIGH);
> > +		}
> 
> Provide a comment as to why this cycle needs to be done twice.

I will paste the description from the datasheet in v3

> 
> > +		mutex_unlock(&chip->lock);
> > +	}
> > +	return ret;
> > +}
> > +
> > +#define LL1202_PWM_PATTERN_ATTR(pattern)                                          \
> 
> No chance!
> 
> Where else do you see code like this?
>

The driver version is the one used for our custom f1c200s board.
It's a customization of the lctech,pi-f1c200s.

The kernel version provided by the SoC vendor is 5.2.0.
https://elixir.bootlin.com/linux/v5.2/source/drivers/leds/leds-bd2802.c
line 317

 
> > +	static ssize_t ll1202_show_pwm_pattern##pattern(                          \
> > +		struct device *dev, struct device_attribute *attr, char *buf)     \
> > +	{                                                                         \
> > +		struct ll1202_chip *chip = dev_get_drvdata(dev->parent);          \
> > +		uint8_t duration = 0;                                             \
> > +		uint8_t reg_value_l = 0;                                          \
> > +		uint8_t reg_value_h = 0;                                          \
> > +		uint16_t reg_value = 0;                                           \
> > +		int ret;                                                          \
> > +		char *bufp = buf;                                                 \
> > +		int led_num = ll1202_get_channel(dev);                            \
> > +		if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {                  \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Invalid register [0x%x] (out of range)\n",  \
> > +				led_num);                                         \
> > +		}                                                                 \
> > +		mutex_lock(&chip->lock);                                          \
> > +		ret = ll1202_read_reg(                                            \
> > +			chip,                                                     \
> > +			(LL1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),    \
> > +			&reg_value_l);                                            \
> > +		if (ret != 0)                                                     \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Reading pattern PWM register [0x%x] failed\n", led_num);     \
> > +		ret = ll1202_read_reg(chip,                                       \
> > +				      (LL1202_PATTERN_PWM + 0x1 +                 \
> > +				       (led_num * 2) + 0x18 * pattern),           \
> > +				      &reg_value_h);                              \
> > +		if (ret != 0)                                                     \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Reading pattern PWM register [0x%x] failed\n", led_num);     \
> > +		reg_value = (uint16_t)reg_value_h << 8 | reg_value_l;             \
> > +		ret = ll1202_read_reg(chip, (LL1202_PATTERN_DUR + pattern),       \
> > +				      &duration);                                 \
> > +		if (ret != 0)                                                     \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Reading pattern durating register [0x%x] failed\n", led_num);     \
> > +		bufp += snprintf(                                                 \
> > +			bufp, PAGE_SIZE,                                          \
> > +			"Pattern[%d][cs%d]: PWM = 0x%03X; DURATION = %d ms\n",    \
> > +			pattern, led_num, reg_value,                              \
> > +			ll1202_prescalar_to_miliseconds(duration));               \
> > +		mutex_unlock(&chip->lock);                                        \
> > +		return strlen(buf);                                               \
> > +	}                                                                         \
> > +	static ssize_t ll1202_store_pwm_pattern##pattern(                         \
> > +		struct device *dev, struct device_attribute *attr,                \
> > +		const char *buf, size_t count)                                    \
> > +	{                                                                         \
> > +		struct ll1202_chip *chip = dev_get_drvdata(dev->parent);          \
> > +		unsigned int ret, reg_value;                                      \
> > +		unsigned long duration;                                           \
> > +		char buf_u8[16];                                                  \
> > +		uint8_t reg_value_l = 0;                                          \
> > +		uint8_t reg_value_h = 0;                                          \
> > +		int led_num = ll1202_get_channel(dev);                            \
> > +		if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {                  \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Invalid register [0x%x] (out of range)\n",  \
> > +				led_num);                                         \
> > +			return count;                                             \
> > +		}                                                                 \
> > +		if (!count)                                                       \
> > +			return -EINVAL;                                           \
> > +		ret = sscanf(buf, "%X %s", &reg_value, buf_u8);                   \
> > +		if (ret == 0) {                                                   \
> > +			dev_err(&chip->client->dev,                               \
> > +				"sscanf failed with error :%d\n", ret);           \
> > +			return ret;                                               \
> > +		}                                                                 \
> > +		ret = kstrtoul(buf_u8, 10, &duration);                            \
> > +		if (ret)                                                          \
> > +			return ret;                                               \
> > +		reg_value_l = (uint8_t)reg_value;                                 \
> > +		reg_value_h = (uint8_t)(reg_value >> 8);                          \
> > +		mutex_lock(&chip->lock);                                          \
> > +		ret = ll1202_write_reg(                                           \
> > +			chip,                                                     \
> > +			(LL1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),    \
> > +			(uint8_t)reg_value_l);                                    \
> > +		if (ret != 0)                                                     \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Writing to register [0x%x] failed, value %d\n",  \
> > +				LL1202_PATTERN_PWM + (led_num * 2) +              \
> > +					0x18 * pattern,                           \
> > +				reg_value_l);                                     \
> > +		ret = ll1202_write_reg(chip,                                      \
> > +				       (LL1202_PATTERN_PWM + 0x1 +                \
> > +					(led_num * 2) + 0x18 * pattern),          \
> > +				       (uint8_t)reg_value_h);                     \
> > +		if (ret != 0)                                                     \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Writing to register [0x%x] failed, value %d\n",  \
> > +				(LL1202_PATTERN_PWM + 0x1 + (led_num * 2) +       \
> > +				 0x18 * pattern),                                 \
> > +				reg_value_h);                                     \
> > +		ret = ll1202_write_reg(chip, (LL1202_PATTERN_DUR + pattern),      \
> > +				       (u8)duration);                             \
> > +		if (ret != 0)                                                     \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Writing to register [0x%x] failed, value %d\n", \
> > +				(LL1202_PATTERN_DUR + pattern), (u8)duration);    \
> > +		ret = ll1202_write_reg(chip, LL1202_CONFIG_REG,                   \
> > +				       (0xC0 | pattern));                         \
> > +		if (ret != 0) {                                                   \
> > +			dev_err(&chip->client->dev,                               \
> > +				"Failed writing to reg [0x%x]\n", LL1202_CONFIG_REG);     \
> > +		}                                                                 \
> > +		mutex_unlock(&chip->lock);                                        \
> > +		ll1202_channel_activate(&chip->leds[led_num]);                    \
> > +		return count;                                                     \
> > +	}                                                                         \
> > +	static struct device_attribute dev_attr_led_pwm_pattern##pattern = {		\
> > +	.attr = {							\
> > +	.name = __stringify(pwm_pattern##pattern),					\
> > +	.mode =  00444 | 00200,						\
> > +	},								\
> > +	.show = ll1202_show_pwm_pattern##pattern,				\
> > +	.store = ll1202_store_pwm_pattern##pattern,				\
> > +}
> > +
> > +LL1202_PWM_PATTERN_ATTR(0);
> > +LL1202_PWM_PATTERN_ATTR(1);
> > +LL1202_PWM_PATTERN_ATTR(2);
> > +LL1202_PWM_PATTERN_ATTR(3);
> > +LL1202_PWM_PATTERN_ATTR(4);
> > +LL1202_PWM_PATTERN_ATTR(5);
> > +LL1202_PWM_PATTERN_ATTR(6);
> > +LL1202_PWM_PATTERN_ATTR(7);
> 
> We already have global helpers for this type of thing.
> 
Ok, could you please point me to the file/link?

> > +static DEVICE_ATTR(led_device_regsdump, 00444, ll1202_show_all_registers,
> > +		   NULL);
> > +static DEVICE_ATTR(patt_sequence_repetition, 00444 | 00200,
> > +		   ll1202_show_patt_sequence_repetition,
> > +		   ll1202_store_patt_sequence_repetition);
> > +static DEVICE_ATTR(current_mA, 00444, ll1202_show_channel_mA_current, NULL);
> > +
> > +static struct attribute *led_attrs[] = {
> > +	&dev_attr_led_device_regsdump.attr,
> > +	&dev_attr_patt_sequence_repetition.attr,
> > +	NULL,
> > +};
> > +
> > +static struct attribute *led_group_attrs[] = {
> > +	&dev_attr_led_pwm_pattern0.attr, &dev_attr_led_pwm_pattern1.attr,
> > +	&dev_attr_led_pwm_pattern2.attr, &dev_attr_led_pwm_pattern3.attr,
> > +	&dev_attr_led_pwm_pattern4.attr, &dev_attr_led_pwm_pattern5.attr,
> > +	&dev_attr_led_pwm_pattern6.attr, &dev_attr_led_pwm_pattern7.attr,
> > +	&dev_attr_current_mA.attr,	 NULL,
> > +};
> > +
> > +static struct attribute_group attr_group = {
> > +	.attrs = led_attrs,
> > +};
> > +
> > +static struct attribute_group attr_pat_group = {
> > +	.attrs = led_group_attrs,
> > +};
> > +
> > +static const struct attribute_group *ll1202_groups[] = { &attr_pat_group,
> > +							 NULL };
> > +
> > +static void ll1202_brightness_set(struct led_classdev *led_cdev,
> > +				  enum led_brightness value)
> > +{
> > +	struct ll1202_led *led = cdev_to_ll1202_led(led_cdev);
> > +	struct ll1202_chip *chip = led->chip;
> > +	int ret;
> > +
> > +	mutex_lock(&chip->lock);
> > +	ret = ll1202_write_reg(chip, LL1202_ILED_REG0 + led->led_num, value);
> > +	if (ret != 0)
> > +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n",
> > +			LL1202_ILED_REG0 + led->led_num);
> > +	mutex_unlock(&chip->lock);
> > +}
> > +
> > +static enum led_brightness ll1202_brightness_get(struct led_classdev *led_cdev)
> > +{
> > +	struct ll1202_led *led = cdev_to_ll1202_led(led_cdev);
> > +	struct ll1202_chip *chip = led->chip;
> > +	uint8_t reg_value;
> > +	int ret;
> > +
> > +	mutex_lock(&chip->lock);
> > +	ret = ll1202_read_reg(chip, LL1202_ILED_REG0 + led->led_num,
> > +			      &reg_value);
> > +	if (ret != 0)
> > +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n",
> > +			LL1202_ILED_REG0 + led->led_num);
> > +
> > +	mutex_unlock(&chip->lock);
> > +	return reg_value;
> > +}
> > +
> > +static int ll1202_dt_init(struct ll1202_chip *chip)
> > +{
> > +	struct device_node *np = chip->client->dev.of_node, *child;
> > +	struct ll1202_led *led;
> > +	int err, reg;
> > +
> > +	for_each_child_of_node(np, child) {
> > +		err = of_property_read_u32(child, "reg", &reg);
> > +		if (err) {
> > +			of_node_put(child);
> > +			pr_err(DRIVER_NAME ": Failed to get child node");
> > +			return err;
> > +		}
> > +		if (reg < 0 || reg >= LL1202_MAX_LEDS) {
> > +			of_node_put(child);
> > +			pr_err(DRIVER_NAME ": Invalid register value [0x%x] (out of range)", reg);
> > +			return -EINVAL;
> > +		}
> > +
> > +		led = &chip->leds[reg];
> > +		led->led_cdev.name = of_get_property(child, "label", NULL) ?:
> > +					     child->name;
> > +
> > +		err = of_property_read_u32(child, "active", &led->is_active);
> > +		if (err) {
> > +			of_node_put(child);
> > +			pr_err(DRIVER_NAME ": Failed to get child node");
> > +			return err;
> > +		}
> > +
> > +		led->led_cdev.brightness_set = ll1202_brightness_set;
> > +		led->led_cdev.brightness_get = ll1202_brightness_get;
> > +		led->led_cdev.groups = ll1202_groups;
> > +	}
> > +	return 0;
> > +}
> > +
> > +static int ll1202_setup(struct ll1202_chip *chip)
> > +{
> > +	int ret;
> > +
> > +	mutex_lock(&chip->lock);
> > +	ret = ll1202_write_reg(chip, LL1202_DEV_ENABLE, 0x1);
> > +	if (ret < 0) {
> > +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> > +			LL1202_DEV_ENABLE);
> > +	}
> > +	mutex_unlock(&chip->lock);
> > +	usleep_range(6500, 10000);
> > +	mutex_lock(&chip->lock);
> > +	ret = ll1202_write_reg(chip, LL1202_DEV_ENABLE, 0x80);
> > +	if (ret < 0) {
> > +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> > +			LL1202_DEV_ENABLE);
> > +	}
> > +	mutex_unlock(&chip->lock);
> > +	usleep_range(6500, 10000);
> > +	mutex_lock(&chip->lock);
> > +	ret = ll1202_write_reg(chip, LL1202_PATTERN_REP, 0xFF);
> > +	if (ret < 0) {
> > +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> > +			LL1202_PATTERN_REP);
> > +		return ret;
> > +	}
> > +	mutex_unlock(&chip->lock);
> > +	return ret;
> > +}
> > +
> > +static int ll1202_probe(struct i2c_client *client)
> > +{
> > +	struct ll1202_chip *chip;
> > +	struct ll1202_led *led;
> > +	int ret, err;
> > +	int i;
> > +
> > +	pr_info(DRIVER_NAME ": (I2C) " DRIVER_VERSION "\n");
> > +
> > +	if (!i2c_check_functionality(client->adapter,
> > +				     I2C_FUNC_SMBUS_BYTE_DATA)) {
> > +		dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
> > +		return -EIO;
> > +	}
> > +
> > +	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
> > +	if (!chip)
> > +		return -ENOMEM;
> > +
> > +	i2c_set_clientdata(client, chip);
> > +
> > +	mutex_init(&chip->lock);
> > +	chip->client = client;
> > +
> > +	/* Device tree setup */
> > +	ret = ll1202_dt_init(chip);
> > +	if (ret < 0)
> > +		goto exit;
> > +
> > +	/* Configuration setup */
> > +	ret = ll1202_setup(chip);
> > +	if (ret < 0)
> > +		goto exit;
> > +
> > +	for (i = 0; i < LL1202_MAX_LEDS; i++) {
> > +		led = &chip->leds[i];
> > +		led->chip = chip;
> > +		led->led_num = i;
> > +		if (led->is_active) {
> > +			err = led_classdev_register(&client->dev,
> > +						    &led->led_cdev);
> > +			if (err < 0) {
> > +				pr_err(DRIVER_NAME
> > +				       ": Failed to register LED class dev");
> > +				goto exit;
> > +			}
> > +		}
> > +	}
> > +
> > +	ret = sysfs_create_group(&client->dev.kobj, &attr_group);
> > +	if (ret) {
> > +		dev_err(&client->dev,
> > +			"Failed to create sysfs group for ll1202\n");
> > +		goto err_setup;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_setup:
> > +	for (i = 0; i < LL1202_MAX_LEDS; i++)
> > +		led_classdev_unregister(&chip->leds[i].led_cdev);
> > +exit:
> > +	mutex_destroy(&chip->lock);
> > +	devm_kfree(&client->dev, chip);
> > +	return ret;
> > +}
> > +
> > +static void ll1202_remove(struct i2c_client *client)
> > +{
> > +	struct ll1202_chip *dev = i2c_get_clientdata(client);
> > +	int i;
> > +
> > +	for (i = 0; i < LL1202_MAX_LEDS; i++)
> > +		led_classdev_unregister(&dev->leds[i].led_cdev);
> > +
> > +	sysfs_remove_group(&client->dev.kobj, &attr_group);
> > +
> > +	mutex_destroy(&dev->lock);
> > +	devm_kfree(&client->dev, dev->leds);
> > +	devm_kfree(&client->dev, dev);
> > +}
> > +
> > +static const struct i2c_device_id ll1202_id[] = {
> > +	{ DRIVER_NAME "-i2c", 0 },
> > +	{}
> > +};
> > +
> > +MODULE_DEVICE_TABLE(i2c, ll1202_id);
> > +
> > +static const struct of_device_id ll1202_dt_ids[] = {
> > +	{
> > +		.compatible = "st,led1202",
> > +	},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, ll1202_dt_ids);
> > +
> > +static struct i2c_driver ll1202_driver = {
> > +	.driver = {
> > +		.name = "ll1202",
> > +		.of_match_table = of_match_ptr(ll1202_dt_ids),
> > +	},
> > +	.probe = ll1202_probe,
> > +	.remove = ll1202_remove,
> > +	.id_table = ll1202_id,
> > +};
> > +
> > +module_i2c_driver(ll1202_driver);
> > +
> > +MODULE_AUTHOR("Remote Tech LTD");
> > +MODULE_DESCRIPTION("LED1202 : 12-channel constant current LED driver");
> > +MODULE_LICENSE("GPL");
> > -- 
> > 2.25.1
> > 

Thank you Lee for the review!

Kind regards,
Vicentiu

> 
> -- 
> Lee Jones [李琼斯]
Lee Jones June 25, 2024, 7:16 a.m. UTC | #3
On Mon, 24 Jun 2024, Vicentiu Galanopulo wrote:

> On Thu, Jun 20, 2024 at 06:55:43PM +0100, Lee Jones wrote:
> > I'm going do a very quick once through of this one.
> > 
> > There is a lot of work to do.
> > 
> > > The LED1202 is a 12-channel low quiescent 
> > 
> > Please line wrap at 70-something chars not 40
> >
> 
> Ok, will do. I'm using Visual Code as editor.
> Do you know any config options for it?
> If not maybe another editor that is free
> and works on Mac ARM.

No idea.  I use NeoVim (with kickstart.nvim).

https://dev.to/lico/set-up-neovim-with-kickstartnvim-on-mac-as-a-vimginner-53f5

> > > current LED driver. The output current can 
> > > be adjusted separately for each channel by 
> > > 8-bit analog (current sink input) and 
> > > 12-bit digital (PWM) dimming control.
> > > 
> > > The LED1202 implements 12 low-side current 
> > > generators with independent dimming control.
> > > Internal volatile memory allows the user 
> > > to store up to 8 different patterns, each 
> > > pattern is a particular output configuration 
> > > in terms of PWM duty-cycle (on 4096 steps).
> > > Analog dimming (on 256 steps) is 
> > > per channel but common to all patterns.
> > > 
> > > Each active=1 device tree LED node will
> > > have a corresponding entry in /sys/class/leds
> > > with the label name.
> > > The brightness property corresponds to the
> > > per channel analog dimming, while the 
> > > patterns[1-8] to the PWM dimming control.
> > > 
> > > Signed-off-by: Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
> > > ---
> > > 
> > > Changes in v2:
> > >   - Fix build error for device_attribute modes
> > > 
> > >  drivers/leds/Kconfig        |  10 +
> > >  drivers/leds/Makefile       |   1 +
> > >  drivers/leds/leds-led1202.c | 617 ++++++++++++++++++++++++++++++++++++
> > >  3 files changed, 628 insertions(+)
> > >  create mode 100644 drivers/leds/leds-led1202.c
> > > 
> > > diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> > > index 05e6af88b88c..c65f2b1bbe30 100644
> > > --- a/drivers/leds/Kconfig
> > > +++ b/drivers/leds/Kconfig
> > > @@ -899,6 +899,16 @@ config LEDS_LM36274
> > >  	  Say Y to enable the LM36274 LED driver for TI LMU devices.
> > >  	  This supports the LED device LM36274.
> > >  
> > > +config LEDS_LED1202
> > > +	tristate "LED Support for LED1202 I2C chips"
> > > +	depends on LEDS_CLASS
> > > +	depends on I2C
> > > +	depends on OF
> > > +	help
> > > +	  Say Y to enable support for LEDs connected to LED1202
> > > +	  LED driver chips accessed via the I2C bus.
> > > +	  Supported devices include LED1202.
> > > +
> > >  config LEDS_TPS6105X
> > >  	tristate "LED support for TI TPS6105X"
> > >  	depends on LEDS_CLASS
> > > diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> > > index effdfc6f1e95..80423fa8818e 100644
> > > --- a/drivers/leds/Makefile
> > > +++ b/drivers/leds/Makefile
> > > @@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_IP30)			+= leds-ip30.o
> > >  obj-$(CONFIG_LEDS_IPAQ_MICRO)		+= leds-ipaq-micro.o
> > >  obj-$(CONFIG_LEDS_IS31FL319X)		+= leds-is31fl319x.o
> > >  obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
> > > +obj-$(CONFIG_LEDS_LED1202)		+= leds-led1202.o
> > >  obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
> > >  obj-$(CONFIG_LEDS_LM3532)		+= leds-lm3532.o
> > >  obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
> > > diff --git a/drivers/leds/leds-led1202.c b/drivers/leds/leds-led1202.c
> > > new file mode 100644
> > > index 000000000000..4e82f0e66168
> > > --- /dev/null
> > > +++ b/drivers/leds/leds-led1202.c
> > > @@ -0,0 +1,617 @@
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +/*
> > > + * Simple LED driver for ST LED1202 chip
> > 
> > Just a simple 600+ line driver?
> > 
> 
> With you help maybe I can reduce it.
> 
> > ST as in STMicroelectronics?
> > 
> > Please make that clear in the header and in the Kconfig entry.
> > 
> 
> Sure, will do.
> 
> > > + * Copyright (C) 2024 Remote-Tech Ltd. UK
> > > + */
> > 
> > New line here.
> > 
> Noted, will add.
> 
> > > +#include <linux/module.h>
> > > +#include <linux/string.h>
> > > +#include <linux/ctype.h>
> > > +#include <linux/leds.h>
> > > +#include <linux/err.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/gpio.h>
> > > +#include <linux/delay.h>
> > > +
> > > +#define DRIVER_NAME "led-driver-1202"
> > 
> > Remove this and user dev_err() instead.
> > 
> > > +#define DRIVER_VERSION "0.0.1"
> > 
> > Remove this entirely, it's useless.
> >
> 
> Will do in v3. 

Please strip out review comments that you agree with.

> > > +
> > > +#define LL1202_MAX_LEDS 12
> > > +
> > > +#define LL1202_DEVICE_ID 0x00
> > > +#define LL1202_DEV_ENABLE 0x01
> > > +#define LL1202_CHAN_ENABLE_LOW 0x02
> > > +#define LL1202_CHAN_ENABLE_HIGH 0x03
> > > +#define LL1202_CONFIG_REG 0x04
> > > +#define LL1202_ILED_REG0 0x09
> > > +#define LL1202_PATTERN_REP 0x15
> > > +#define LL1202_PATTERN_DUR 0x16
> > > +#define LL1202_PATTERN_PWM 0x1E
> > > +#define LL1202_CLOCK_REG 0xE0
> > 
> > Tab out the values so they line up.
> >
> OK 
> > > +struct ll1202_led {
> > > +	struct led_classdev led_cdev;
> > > +	struct ll1202_chip *chip;
> > > +	int led_num;
> > > +	char name[32];
> > 
> > Define this and all magic numbers.
> > 
> Ok, there is some bitwise operations to form a 16bit from 8bit values.
> And some magic number coming from the datasheet.
> Those also?

Numbers should be easily identifiable/readable by humans.

> > > +	int is_active;
> > > +};
> > > +
> > > +struct ll1202_chip {
> > > +	struct i2c_client *client;
> > > +	struct mutex lock;
> > > +	struct ll1202_led leds[LL1202_MAX_LEDS];
> > > +};
> > > +
> > > +static struct ll1202_led *cdev_to_ll1202_led(struct led_classdev *cdev)
> > > +{
> > > +	return container_of(cdev, struct ll1202_led, led_cdev);
> > > +}
> > > +
> > > +static int ll1202_read_reg(struct ll1202_chip *chip, int reg, uint8_t *val)
> > > +{
> > > +	int ret = i2c_smbus_read_byte_data(chip->client, reg);
> > 
> > Separate the declaration and the function call.
> > 
> Ok
> > > +
> > > +	if (ret < 0)
> > > +		return ret;
> > > +
> > > +	*val = (uint8_t)ret;
> > > +	return 0;
> > > +}
> > > +
> > > +static int ll1202_write_reg(struct ll1202_chip *chip, int reg, uint8_t val)
> > 
> > What's "ll"?
> >
>  
> I reused some naming. Should it be led1202_ for all?

st1202_?

> > > +{
> > > +	return i2c_smbus_write_byte_data(chip->client, reg, val);
> > > +}
> > > +
> > > +static int ll1202_get_channel(struct device *dev)
> > > +{
> > > +	struct device_node *np = dev->parent->of_node, *child;
> > > +	int err, ret = -1;
> > 
> > What is -1?
> 
> Just negative return value. It's a helper function that returns the LED
> channel number based on struct device.
> 
> If this is not appropiate or custom practice I can redo it, but I need some pointers
> on where to look as "good" examples.

Google: "Linux Error Codes"

`git grep "return " -- drivers`

> > > +	for_each_child_of_node(np, child) {
> > > +		if (strncmp(dev->kobj.name,
> > > +			    of_get_property(child, "label", NULL),
> > > +			    strnlen(dev->kobj.name, MAX_INPUT)) == 0) {
> > 
> > Pull all of these embedded functions out.
> > 
> Ok.
> > > +			err = of_property_read_u32(child, "reg", &ret);
> > > +			if (err) {
> > > +				of_node_put(child);
> > > +				pr_err(DRIVER_NAME
> > > +				       ": Failed to read property %s", child->name);
> > > +				return ret;
> > > +			}
> > > +			break;
> > > +		}
> > > +	}
> > > +	return ret;
> > > +}
> > > +
> > > +static ssize_t ll1202_show_all_registers(struct device *dev,
> > > +					 struct device_attribute *devattr,
> > > +					 char *buf)
> > > +{
> > > +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> > > +	uint8_t reg_value = 0;
> > > +	int ret, i;
> > > +	char *bufp = buf;
> > > +
> > > +	mutex_lock(&chip->lock);
> > > +
> > > +	for (i = LL1202_DEVICE_ID; i <= LL1202_CLOCK_REG; i++) {
> > > +		ret = ll1202_read_reg(chip, i, &reg_value);
> > > +		if (ret != 0)
> > > +			dev_err(&chip->client->dev,
> > > +				"Reading register [0x%x] failed.\n", i);
> > > +
> > > +		bufp += snprintf(bufp, PAGE_SIZE, "Addr[0x%x] = 0x%x\n", i,
> > > +				 reg_value);
> > > +	}
> > > +
> > > +	mutex_unlock(&chip->lock);
> > > +	return strlen(buf);
> > > +}
> > 
> > Does this have any real world use?
> >
>  
> A dump of all the registers with their values. I didn't add show/get functions for
> all the registers.
> Remove it?

How often are people going to need that after initial authorship, really?

> > > +static ssize_t
> > > +ll1202_show_patt_sequence_repetition(struct device *dev,
> > > +				     struct device_attribute *attr, char *buf)
> > > +{
> > > +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> > > +	unsigned int ret;
> > > +	uint8_t reg_value;
> > > +	char *bufp = buf;
> > > +
> > > +	mutex_lock(&chip->lock);
> > > +	ret = ll1202_read_reg(chip, LL1202_PATTERN_REP, &reg_value);
> > > +	if (ret != 0)
> > > +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n", LL1202_PATTERN_REP);
> > > +	mutex_unlock(&chip->lock);
> > > +	bufp += snprintf(bufp, PAGE_SIZE,
> > > +			 "Pattern sequence register, repetition value = %d (times)\n",
> > > +			 reg_value);
> > > +	return strlen(buf);
> > > +}
> > > +
> > > +static ssize_t
> > > +ll1202_store_patt_sequence_repetition(struct device *dev,
> > > +				      struct device_attribute *attr,
> > > +				      const char *buf, size_t count)
> > > +{
> > > +	struct ll1202_chip *chip = dev_get_drvdata(dev);
> > > +	unsigned int ret;
> > > +	unsigned long duration;
> > > +
> > > +	if (!count)
> > > +		return -EINVAL;
> > > +
> > > +	ret = kstrtoul(buf, 10, &duration);
> > > +	if (ret) {
> > > +		dev_err(&chip->client->dev, "sscanf failed with error :%d\n",
> > > +			ret);
> > > +		return ret;
> > > +	}
> > > +
> > > +	mutex_lock(&chip->lock);
> > > +	ret = ll1202_write_reg(chip, LL1202_PATTERN_REP, duration);
> > > +	if (ret != 0)
> > > +		dev_err(&chip->client->dev, "Writing register [0x%x] failed\n",
> > > +			LL1202_PATTERN_REP);
> > > +	mutex_unlock(&chip->lock);
> > > +	return count;
> > > +}
> > > +
> > > +static int ll1202_prescalar_to_miliamps(uint8_t reg_value)
> > > +{
> > > +	return reg_value * 20 / 255;
> > 
> > Define _all_ magic numbers.
> >
> Ok, will be done in v3 
> > > +}
> > > +
> > > +static int ll1202_prescalar_to_miliseconds(uint8_t reg_value)
> > > +{
> > > +	return reg_value * 5660 / 255;
> > > +}
> > > +
> > > +static ssize_t ll1202_show_channel_mA_current(struct device *dev,
> > > +					      struct device_attribute *attr,
> > > +					      char *buf)
> > > +{
> > > +	struct ll1202_chip *chip = dev_get_drvdata(dev->parent);
> > > +	unsigned int ret;
> > > +	uint8_t reg_value;
> > > +	char *bufp = buf;
> > > +	int led_num = ll1202_get_channel(dev);
> > > +
> > > +	if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {
> > > +		dev_err(&chip->client->dev,
> > > +			"Invalid register [0x%x] (out of range)\n",
> > > +			led_num);
> > > +	}
> > > +	mutex_lock(&chip->lock);
> > > +	ret = ll1202_read_reg(chip, LL1202_ILED_REG0 + led_num, &reg_value);
> > > +	if (ret != 0)
> > > +		dev_err(&chip->client->dev, "Reading analog dimming register [0x%x] failed\n",
> > > +			led_num);
> > > +	mutex_unlock(&chip->lock);
> > > +	bufp += snprintf(bufp, PAGE_SIZE, "Channel[%d] = %d mA\n", led_num,
> > > +			 ll1202_prescalar_to_miliamps(reg_value));
> > > +	return strlen(buf);
> > 
> > Space out the code properly - this is really tough to read.
> > 
> Ok.. with or without the help of the IDE, it shall be done

I mean new lines between functional groups.

> > > +}
> > > +
> > > +static int ll1202_channel_activate(struct ll1202_led *led)
> > > +{
> > > +	struct ll1202_chip *chip;
> > > +	uint8_t reg_chan_low, reg_chan_high;
> > > +	int ret = 0;
> > > +
> > > +	chip = led->chip;
> > > +	if (led->is_active) {
> > 
> > Reverse this logic and unindent this block.
> > 
> Sorry, I need some more details on what I need to do here.

	if (!led->is_active)
		return ret;

> > > +		mutex_lock(&chip->lock);
> > > +
> > > +		ret = ll1202_read_reg(chip, LL1202_CHAN_ENABLE_LOW,
> > > +				      &reg_chan_low);
> > > +		if (ret < 0) {
> > > +			dev_err(&chip->client->dev,
> > > +				"Failed reading register [0x%x]\n", LL1202_CHAN_ENABLE_LOW);
> > > +		}
> > > +
> > > +		ret = ll1202_read_reg(chip, LL1202_CHAN_ENABLE_HIGH,
> > > +				      &reg_chan_high);
> > > +		if (ret < 0) {
> > > +			dev_err(&chip->client->dev,
> > > +				"Failed reading register [0x%x]\n", LL1202_CHAN_ENABLE_HIGH);
> > > +		}
> > > +
> > > +		reg_chan_low = reg_chan_low | BIT(led->led_num);
> > > +		ret = ll1202_write_reg(chip, LL1202_CHAN_ENABLE_LOW,
> > > +				       reg_chan_low);
> > > +		if (ret < 0) {
> > > +			dev_err(&chip->client->dev,
> > > +				"Failed writing to register [0x%x]\n", LL1202_CHAN_ENABLE_LOW);
> > > +		}
> > > +		reg_chan_high = reg_chan_high | (BIT(led->led_num) >> 7);
> > > +		ret = ll1202_write_reg(chip, LL1202_CHAN_ENABLE_HIGH,
> > > +				       reg_chan_high);
> > > +		if (ret < 0) {
> > > +			dev_err(&chip->client->dev,
> > > +				"Failed writing to register [0x%x]\n", LL1202_CHAN_ENABLE_HIGH);
> > > +		}
> > 
> > Provide a comment as to why this cycle needs to be done twice.
> 
> I will paste the description from the datasheet in v3
> 
> > 
> > > +		mutex_unlock(&chip->lock);
> > > +	}
> > > +	return ret;
> > > +}
> > > +
> > > +#define LL1202_PWM_PATTERN_ATTR(pattern)                                          \
> > 
> > No chance!
> > 
> > Where else do you see code like this?
> >
> 
> The driver version is the one used for our custom f1c200s board.
> It's a customization of the lctech,pi-f1c200s.
> 
> The kernel version provided by the SoC vendor is 5.2.0.
> https://elixir.bootlin.com/linux/v5.2/source/drivers/leds/leds-bd2802.c
> line 317
> 
>  
> > > +	static ssize_t ll1202_show_pwm_pattern##pattern(                          \
> > > +		struct device *dev, struct device_attribute *attr, char *buf)     \
> > > +	{                                                                         \
> > > +		struct ll1202_chip *chip = dev_get_drvdata(dev->parent);          \
> > > +		uint8_t duration = 0;                                             \
> > > +		uint8_t reg_value_l = 0;                                          \
> > > +		uint8_t reg_value_h = 0;                                          \
> > > +		uint16_t reg_value = 0;                                           \
> > > +		int ret;                                                          \
> > > +		char *bufp = buf;                                                 \
> > > +		int led_num = ll1202_get_channel(dev);                            \
> > > +		if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {                  \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Invalid register [0x%x] (out of range)\n",  \
> > > +				led_num);                                         \
> > > +		}                                                                 \
> > > +		mutex_lock(&chip->lock);                                          \
> > > +		ret = ll1202_read_reg(                                            \
> > > +			chip,                                                     \
> > > +			(LL1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),    \
> > > +			&reg_value_l);                                            \
> > > +		if (ret != 0)                                                     \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Reading pattern PWM register [0x%x] failed\n", led_num);     \
> > > +		ret = ll1202_read_reg(chip,                                       \
> > > +				      (LL1202_PATTERN_PWM + 0x1 +                 \
> > > +				       (led_num * 2) + 0x18 * pattern),           \
> > > +				      &reg_value_h);                              \
> > > +		if (ret != 0)                                                     \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Reading pattern PWM register [0x%x] failed\n", led_num);     \
> > > +		reg_value = (uint16_t)reg_value_h << 8 | reg_value_l;             \
> > > +		ret = ll1202_read_reg(chip, (LL1202_PATTERN_DUR + pattern),       \
> > > +				      &duration);                                 \
> > > +		if (ret != 0)                                                     \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Reading pattern durating register [0x%x] failed\n", led_num);     \
> > > +		bufp += snprintf(                                                 \
> > > +			bufp, PAGE_SIZE,                                          \
> > > +			"Pattern[%d][cs%d]: PWM = 0x%03X; DURATION = %d ms\n",    \
> > > +			pattern, led_num, reg_value,                              \
> > > +			ll1202_prescalar_to_miliseconds(duration));               \
> > > +		mutex_unlock(&chip->lock);                                        \
> > > +		return strlen(buf);                                               \
> > > +	}                                                                         \
> > > +	static ssize_t ll1202_store_pwm_pattern##pattern(                         \
> > > +		struct device *dev, struct device_attribute *attr,                \
> > > +		const char *buf, size_t count)                                    \
> > > +	{                                                                         \
> > > +		struct ll1202_chip *chip = dev_get_drvdata(dev->parent);          \
> > > +		unsigned int ret, reg_value;                                      \
> > > +		unsigned long duration;                                           \
> > > +		char buf_u8[16];                                                  \
> > > +		uint8_t reg_value_l = 0;                                          \
> > > +		uint8_t reg_value_h = 0;                                          \
> > > +		int led_num = ll1202_get_channel(dev);                            \
> > > +		if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {                  \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Invalid register [0x%x] (out of range)\n",  \
> > > +				led_num);                                         \
> > > +			return count;                                             \
> > > +		}                                                                 \
> > > +		if (!count)                                                       \
> > > +			return -EINVAL;                                           \
> > > +		ret = sscanf(buf, "%X %s", &reg_value, buf_u8);                   \
> > > +		if (ret == 0) {                                                   \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"sscanf failed with error :%d\n", ret);           \
> > > +			return ret;                                               \
> > > +		}                                                                 \
> > > +		ret = kstrtoul(buf_u8, 10, &duration);                            \
> > > +		if (ret)                                                          \
> > > +			return ret;                                               \
> > > +		reg_value_l = (uint8_t)reg_value;                                 \
> > > +		reg_value_h = (uint8_t)(reg_value >> 8);                          \
> > > +		mutex_lock(&chip->lock);                                          \
> > > +		ret = ll1202_write_reg(                                           \
> > > +			chip,                                                     \
> > > +			(LL1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),    \
> > > +			(uint8_t)reg_value_l);                                    \
> > > +		if (ret != 0)                                                     \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Writing to register [0x%x] failed, value %d\n",  \
> > > +				LL1202_PATTERN_PWM + (led_num * 2) +              \
> > > +					0x18 * pattern,                           \
> > > +				reg_value_l);                                     \
> > > +		ret = ll1202_write_reg(chip,                                      \
> > > +				       (LL1202_PATTERN_PWM + 0x1 +                \
> > > +					(led_num * 2) + 0x18 * pattern),          \
> > > +				       (uint8_t)reg_value_h);                     \
> > > +		if (ret != 0)                                                     \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Writing to register [0x%x] failed, value %d\n",  \
> > > +				(LL1202_PATTERN_PWM + 0x1 + (led_num * 2) +       \
> > > +				 0x18 * pattern),                                 \
> > > +				reg_value_h);                                     \
> > > +		ret = ll1202_write_reg(chip, (LL1202_PATTERN_DUR + pattern),      \
> > > +				       (u8)duration);                             \
> > > +		if (ret != 0)                                                     \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Writing to register [0x%x] failed, value %d\n", \
> > > +				(LL1202_PATTERN_DUR + pattern), (u8)duration);    \
> > > +		ret = ll1202_write_reg(chip, LL1202_CONFIG_REG,                   \
> > > +				       (0xC0 | pattern));                         \
> > > +		if (ret != 0) {                                                   \
> > > +			dev_err(&chip->client->dev,                               \
> > > +				"Failed writing to reg [0x%x]\n", LL1202_CONFIG_REG);     \
> > > +		}                                                                 \
> > > +		mutex_unlock(&chip->lock);                                        \
> > > +		ll1202_channel_activate(&chip->leds[led_num]);                    \
> > > +		return count;                                                     \
> > > +	}                                                                         \
> > > +	static struct device_attribute dev_attr_led_pwm_pattern##pattern = {		\
> > > +	.attr = {							\
> > > +	.name = __stringify(pwm_pattern##pattern),					\
> > > +	.mode =  00444 | 00200,						\
> > > +	},								\
> > > +	.show = ll1202_show_pwm_pattern##pattern,				\
> > > +	.store = ll1202_store_pwm_pattern##pattern,				\
> > > +}
> > > +
> > > +LL1202_PWM_PATTERN_ATTR(0);
> > > +LL1202_PWM_PATTERN_ATTR(1);
> > > +LL1202_PWM_PATTERN_ATTR(2);
> > > +LL1202_PWM_PATTERN_ATTR(3);
> > > +LL1202_PWM_PATTERN_ATTR(4);
> > > +LL1202_PWM_PATTERN_ATTR(5);
> > > +LL1202_PWM_PATTERN_ATTR(6);
> > > +LL1202_PWM_PATTERN_ATTR(7);
> > 
> > We already have global helpers for this type of thing.
> > 
> Ok, could you please point me to the file/link?

I suggest you pull as much of this out to another _normal_ function as
you can, then have the fewest lines possible inside the macro instead.

> > > +static DEVICE_ATTR(led_device_regsdump, 00444, ll1202_show_all_registers,
> > > +		   NULL);
> > > +static DEVICE_ATTR(patt_sequence_repetition, 00444 | 00200,
> > > +		   ll1202_show_patt_sequence_repetition,
> > > +		   ll1202_store_patt_sequence_repetition);
> > > +static DEVICE_ATTR(current_mA, 00444, ll1202_show_channel_mA_current, NULL);
> > > +
> > > +static struct attribute *led_attrs[] = {
> > > +	&dev_attr_led_device_regsdump.attr,
> > > +	&dev_attr_patt_sequence_repetition.attr,
> > > +	NULL,
> > > +};
> > > +
> > > +static struct attribute *led_group_attrs[] = {
> > > +	&dev_attr_led_pwm_pattern0.attr, &dev_attr_led_pwm_pattern1.attr,
> > > +	&dev_attr_led_pwm_pattern2.attr, &dev_attr_led_pwm_pattern3.attr,
> > > +	&dev_attr_led_pwm_pattern4.attr, &dev_attr_led_pwm_pattern5.attr,
> > > +	&dev_attr_led_pwm_pattern6.attr, &dev_attr_led_pwm_pattern7.attr,
> > > +	&dev_attr_current_mA.attr,	 NULL,
> > > +};
> > > +
> > > +static struct attribute_group attr_group = {
> > > +	.attrs = led_attrs,
> > > +};
> > > +
> > > +static struct attribute_group attr_pat_group = {
> > > +	.attrs = led_group_attrs,
> > > +};
> > > +
> > > +static const struct attribute_group *ll1202_groups[] = { &attr_pat_group,
> > > +							 NULL };
> > > +
> > > +static void ll1202_brightness_set(struct led_classdev *led_cdev,
> > > +				  enum led_brightness value)
> > > +{
> > > +	struct ll1202_led *led = cdev_to_ll1202_led(led_cdev);
> > > +	struct ll1202_chip *chip = led->chip;
> > > +	int ret;
> > > +
> > > +	mutex_lock(&chip->lock);
> > > +	ret = ll1202_write_reg(chip, LL1202_ILED_REG0 + led->led_num, value);
> > > +	if (ret != 0)
> > > +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n",
> > > +			LL1202_ILED_REG0 + led->led_num);
> > > +	mutex_unlock(&chip->lock);
> > > +}
> > > +
> > > +static enum led_brightness ll1202_brightness_get(struct led_classdev *led_cdev)
> > > +{
> > > +	struct ll1202_led *led = cdev_to_ll1202_led(led_cdev);
> > > +	struct ll1202_chip *chip = led->chip;
> > > +	uint8_t reg_value;
> > > +	int ret;
> > > +
> > > +	mutex_lock(&chip->lock);
> > > +	ret = ll1202_read_reg(chip, LL1202_ILED_REG0 + led->led_num,
> > > +			      &reg_value);
> > > +	if (ret != 0)
> > > +		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n",
> > > +			LL1202_ILED_REG0 + led->led_num);
> > > +
> > > +	mutex_unlock(&chip->lock);
> > > +	return reg_value;
> > > +}
> > > +
> > > +static int ll1202_dt_init(struct ll1202_chip *chip)
> > > +{
> > > +	struct device_node *np = chip->client->dev.of_node, *child;
> > > +	struct ll1202_led *led;
> > > +	int err, reg;
> > > +
> > > +	for_each_child_of_node(np, child) {
> > > +		err = of_property_read_u32(child, "reg", &reg);
> > > +		if (err) {
> > > +			of_node_put(child);
> > > +			pr_err(DRIVER_NAME ": Failed to get child node");
> > > +			return err;
> > > +		}
> > > +		if (reg < 0 || reg >= LL1202_MAX_LEDS) {
> > > +			of_node_put(child);
> > > +			pr_err(DRIVER_NAME ": Invalid register value [0x%x] (out of range)", reg);
> > > +			return -EINVAL;
> > > +		}
> > > +
> > > +		led = &chip->leds[reg];
> > > +		led->led_cdev.name = of_get_property(child, "label", NULL) ?:
> > > +					     child->name;
> > > +
> > > +		err = of_property_read_u32(child, "active", &led->is_active);
> > > +		if (err) {
> > > +			of_node_put(child);
> > > +			pr_err(DRIVER_NAME ": Failed to get child node");
> > > +			return err;
> > > +		}
> > > +
> > > +		led->led_cdev.brightness_set = ll1202_brightness_set;
> > > +		led->led_cdev.brightness_get = ll1202_brightness_get;
> > > +		led->led_cdev.groups = ll1202_groups;
> > > +	}
> > > +	return 0;
> > > +}
> > > +
> > > +static int ll1202_setup(struct ll1202_chip *chip)
> > > +{
> > > +	int ret;
> > > +
> > > +	mutex_lock(&chip->lock);
> > > +	ret = ll1202_write_reg(chip, LL1202_DEV_ENABLE, 0x1);
> > > +	if (ret < 0) {
> > > +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> > > +			LL1202_DEV_ENABLE);
> > > +	}
> > > +	mutex_unlock(&chip->lock);
> > > +	usleep_range(6500, 10000);
> > > +	mutex_lock(&chip->lock);
> > > +	ret = ll1202_write_reg(chip, LL1202_DEV_ENABLE, 0x80);
> > > +	if (ret < 0) {
> > > +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> > > +			LL1202_DEV_ENABLE);
> > > +	}
> > > +	mutex_unlock(&chip->lock);
> > > +	usleep_range(6500, 10000);
> > > +	mutex_lock(&chip->lock);
> > > +	ret = ll1202_write_reg(chip, LL1202_PATTERN_REP, 0xFF);
> > > +	if (ret < 0) {
> > > +		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
> > > +			LL1202_PATTERN_REP);
> > > +		return ret;
> > > +	}
> > > +	mutex_unlock(&chip->lock);
> > > +	return ret;
> > > +}
> > > +
> > > +static int ll1202_probe(struct i2c_client *client)
> > > +{
> > > +	struct ll1202_chip *chip;
> > > +	struct ll1202_led *led;
> > > +	int ret, err;
> > > +	int i;
> > > +
> > > +	pr_info(DRIVER_NAME ": (I2C) " DRIVER_VERSION "\n");
> > > +
> > > +	if (!i2c_check_functionality(client->adapter,
> > > +				     I2C_FUNC_SMBUS_BYTE_DATA)) {
> > > +		dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
> > > +		return -EIO;
> > > +	}
> > > +
> > > +	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
> > > +	if (!chip)
> > > +		return -ENOMEM;
> > > +
> > > +	i2c_set_clientdata(client, chip);
> > > +
> > > +	mutex_init(&chip->lock);
> > > +	chip->client = client;
> > > +
> > > +	/* Device tree setup */
> > > +	ret = ll1202_dt_init(chip);
> > > +	if (ret < 0)
> > > +		goto exit;
> > > +
> > > +	/* Configuration setup */
> > > +	ret = ll1202_setup(chip);
> > > +	if (ret < 0)
> > > +		goto exit;
> > > +
> > > +	for (i = 0; i < LL1202_MAX_LEDS; i++) {
> > > +		led = &chip->leds[i];
> > > +		led->chip = chip;
> > > +		led->led_num = i;
> > > +		if (led->is_active) {
> > > +			err = led_classdev_register(&client->dev,
> > > +						    &led->led_cdev);
> > > +			if (err < 0) {
> > > +				pr_err(DRIVER_NAME
> > > +				       ": Failed to register LED class dev");
> > > +				goto exit;
> > > +			}
> > > +		}
> > > +	}
> > > +
> > > +	ret = sysfs_create_group(&client->dev.kobj, &attr_group);
> > > +	if (ret) {
> > > +		dev_err(&client->dev,
> > > +			"Failed to create sysfs group for ll1202\n");
> > > +		goto err_setup;
> > > +	}
> > > +
> > > +	return 0;
> > > +
> > > +err_setup:
> > > +	for (i = 0; i < LL1202_MAX_LEDS; i++)
> > > +		led_classdev_unregister(&chip->leds[i].led_cdev);
> > > +exit:
> > > +	mutex_destroy(&chip->lock);
> > > +	devm_kfree(&client->dev, chip);
> > > +	return ret;
> > > +}
> > > +
> > > +static void ll1202_remove(struct i2c_client *client)
> > > +{
> > > +	struct ll1202_chip *dev = i2c_get_clientdata(client);
> > > +	int i;
> > > +
> > > +	for (i = 0; i < LL1202_MAX_LEDS; i++)
> > > +		led_classdev_unregister(&dev->leds[i].led_cdev);
> > > +
> > > +	sysfs_remove_group(&client->dev.kobj, &attr_group);
> > > +
> > > +	mutex_destroy(&dev->lock);
> > > +	devm_kfree(&client->dev, dev->leds);
> > > +	devm_kfree(&client->dev, dev);
> > > +}
> > > +
> > > +static const struct i2c_device_id ll1202_id[] = {
> > > +	{ DRIVER_NAME "-i2c", 0 },
> > > +	{}
> > > +};
> > > +
> > > +MODULE_DEVICE_TABLE(i2c, ll1202_id);
> > > +
> > > +static const struct of_device_id ll1202_dt_ids[] = {
> > > +	{
> > > +		.compatible = "st,led1202",
> > > +	},
> > > +};
> > > +
> > > +MODULE_DEVICE_TABLE(of, ll1202_dt_ids);
> > > +
> > > +static struct i2c_driver ll1202_driver = {
> > > +	.driver = {
> > > +		.name = "ll1202",
> > > +		.of_match_table = of_match_ptr(ll1202_dt_ids),
> > > +	},
> > > +	.probe = ll1202_probe,
> > > +	.remove = ll1202_remove,
> > > +	.id_table = ll1202_id,
> > > +};
> > > +
> > > +module_i2c_driver(ll1202_driver);
> > > +
> > > +MODULE_AUTHOR("Remote Tech LTD");
> > > +MODULE_DESCRIPTION("LED1202 : 12-channel constant current LED driver");
> > > +MODULE_LICENSE("GPL");
> > > -- 
> > > 2.25.1
> > > 
> 
> Thank you Lee for the review!
> 
> Kind regards,
> Vicentiu
> 
> > 
> > -- 
> > Lee Jones [李琼斯]
Vicentiu Galanopulo June 25, 2024, 9:50 p.m. UTC | #4
On Tue, Jun 25, 2024 at 08:16:10AM +0100, Lee Jones wrote:
> No idea.  I use NeoVim (with kickstart.nvim).
> 
> https://dev.to/lico/set-up-neovim-with-kickstartnvim-on-mac-as-a-vimginner-53f5

Thanks for the pointer.

> 
> Please strip out review comments that you agree with.
Hopefully like I did for the rest of comments?

> 
> Numbers should be easily identifiable/readable by humans.
Ok, will do my best 

> > I reused some naming. Should it be led1202_ for all?
> 
> st1202_?
st1202 will be in v3

 
> > If this is not appropiate or custom practice I can redo it, but I need some pointers
> > on where to look as "good" examples.
> 
> Google: "Linux Error Codes"
> 
> `git grep "return " -- drivers`
My concern was mostly with how I'm extracting the channel(LED number).
ll1202_get_channel is called inside functions where only struct device is available.
So, I extract the device_node to have access to the device tree "label".
I'm char compairing label value and dev->kobj.name, and if they're the same, I use the
"reg" value property from the device tree to get the LED number.

For most if not all of the functions I did see some similar setup in other drivers files,
but I might be doing something the wrong way...
 


 
> > A dump of all the registers with their values. I didn't add show/get functions for
> > all the registers.
> > Remove it?
> 
> How often are people going to need that after initial authorship, really?
>
No idea. I'll just remove it.
 
> > > 
> > > Space out the code properly - this is really tough to read.
> > > 
> > Ok.. with or without the help of the IDE, it shall be done
> 
> I mean new lines between functional groups.
>
Understood.
 
> > > > +}
> > > > +
> > > > +static int ll1202_channel_activate(struct ll1202_led *led)
> > > > +{
> > > > +	struct ll1202_chip *chip;
> > > > +	uint8_t reg_chan_low, reg_chan_high;
> > > > +	int ret = 0;
> > > > +
> > > > +	chip = led->chip;
> > > > +	if (led->is_active) {
> > > 
> > > Reverse this logic and unindent this block.
> > > 
> > Sorry, I need some more details on what I need to do here.
> 
> 	if (!led->is_active)
> 		return ret;
> 

Thanks for explaining this.

> > > 
> > > We already have global helpers for this type of thing.
> > > 
> > Ok, could you please point me to the file/link?
> 
> I suggest you pull as much of this out to another _normal_ function as
> you can, then have the fewest lines possible inside the macro instead.
>
Ok. Will do.

 
> -- 
> Lee Jones [李琼斯]
Thanks Lee.

May I now push a v3?
Lee Jones June 26, 2024, 9:27 a.m. UTC | #5
On Tue, 25 Jun 2024, Vicentiu Galanopulo wrote:

> On Tue, Jun 25, 2024 at 08:16:10AM +0100, Lee Jones wrote:
> > No idea.  I use NeoVim (with kickstart.nvim).
> > 
> > https://dev.to/lico/set-up-neovim-with-kickstartnvim-on-mac-as-a-vimginner-53f5
> 
> Thanks for the pointer.
> 
> > 
> > Please strip out review comments that you agree with.
> Hopefully like I did for the rest of comments?
> 
> > 
> > Numbers should be easily identifiable/readable by humans.
> Ok, will do my best 

What does the comment above say?

If you agree with a review comment, you don't need to reply to it.

> > > I reused some naming. Should it be led1202_ for all?
> > 
> > st1202_?
> st1202 will be in v3
> 
>  
> > > If this is not appropiate or custom practice I can redo it, but I need some pointers
> > > on where to look as "good" examples.
> > 
> > Google: "Linux Error Codes"
> > 
> > `git grep "return " -- drivers`
> My concern was mostly with how I'm extracting the channel(LED number).
> ll1202_get_channel is called inside functions where only struct device is available.
> So, I extract the device_node to have access to the device tree "label".
> I'm char compairing label value and dev->kobj.name, and if they're the same, I use the

That seems wrong.

I haven't looked at what you're doing in detail, but dev_name() is
usually used to fetch the name of the device.

See include/linux/device.h for more generic APIs.

> "reg" value property from the device tree to get the LED number.
> 
> For most if not all of the functions I did see some similar setup in other drivers files,
> but I might be doing something the wrong way...


> > > A dump of all the registers with their values. I didn't add show/get functions for
> > > all the registers.
> > > Remove it?
> > 
> > How often are people going to need that after initial authorship, really?
> >
> No idea. I'll just remove it.
>  
> > > > 
> > > > Space out the code properly - this is really tough to read.
> > > > 
> > > Ok.. with or without the help of the IDE, it shall be done
> > 
> > I mean new lines between functional groups.
> >
> Understood.
>  
> > > > > +}
> > > > > +
> > > > > +static int ll1202_channel_activate(struct ll1202_led *led)
> > > > > +{
> > > > > +	struct ll1202_chip *chip;
> > > > > +	uint8_t reg_chan_low, reg_chan_high;
> > > > > +	int ret = 0;
> > > > > +
> > > > > +	chip = led->chip;
> > > > > +	if (led->is_active) {
> > > > 
> > > > Reverse this logic and unindent this block.
> > > > 
> > > Sorry, I need some more details on what I need to do here.
> > 
> > 	if (!led->is_active)
> > 		return ret;
> > 
> 
> Thanks for explaining this.
> 
> > > > 
> > > > We already have global helpers for this type of thing.
> > > > 
> > > Ok, could you please point me to the file/link?
> > 
> > I suggest you pull as much of this out to another _normal_ function as
> > you can, then have the fewest lines possible inside the macro instead.
> >
> Ok. Will do.
> 
>  
> > -- 
> > Lee Jones [李琼斯]
> Thanks Lee.
> 
> May I now push a v3?

No one will stop you. :)
diff mbox series

Patch

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 05e6af88b88c..c65f2b1bbe30 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -899,6 +899,16 @@  config LEDS_LM36274
 	  Say Y to enable the LM36274 LED driver for TI LMU devices.
 	  This supports the LED device LM36274.
 
+config LEDS_LED1202
+	tristate "LED Support for LED1202 I2C chips"
+	depends on LEDS_CLASS
+	depends on I2C
+	depends on OF
+	help
+	  Say Y to enable support for LEDs connected to LED1202
+	  LED driver chips accessed via the I2C bus.
+	  Supported devices include LED1202.
+
 config LEDS_TPS6105X
 	tristate "LED support for TI TPS6105X"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index effdfc6f1e95..80423fa8818e 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -36,6 +36,7 @@  obj-$(CONFIG_LEDS_IP30)			+= leds-ip30.o
 obj-$(CONFIG_LEDS_IPAQ_MICRO)		+= leds-ipaq-micro.o
 obj-$(CONFIG_LEDS_IS31FL319X)		+= leds-is31fl319x.o
 obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
+obj-$(CONFIG_LEDS_LED1202)		+= leds-led1202.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
 obj-$(CONFIG_LEDS_LM3532)		+= leds-lm3532.o
 obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
diff --git a/drivers/leds/leds-led1202.c b/drivers/leds/leds-led1202.c
new file mode 100644
index 000000000000..4e82f0e66168
--- /dev/null
+++ b/drivers/leds/leds-led1202.c
@@ -0,0 +1,617 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Simple LED driver for ST LED1202 chip
+ *
+ * Copyright (C) 2024 Remote-Tech Ltd. UK
+ */
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+
+#define DRIVER_NAME "led-driver-1202"
+#define DRIVER_VERSION "0.0.1"
+
+#define LL1202_MAX_LEDS 12
+
+#define LL1202_DEVICE_ID 0x00
+#define LL1202_DEV_ENABLE 0x01
+#define LL1202_CHAN_ENABLE_LOW 0x02
+#define LL1202_CHAN_ENABLE_HIGH 0x03
+#define LL1202_CONFIG_REG 0x04
+#define LL1202_ILED_REG0 0x09
+#define LL1202_PATTERN_REP 0x15
+#define LL1202_PATTERN_DUR 0x16
+#define LL1202_PATTERN_PWM 0x1E
+#define LL1202_CLOCK_REG 0xE0
+
+struct ll1202_led {
+	struct led_classdev led_cdev;
+	struct ll1202_chip *chip;
+	int led_num;
+	char name[32];
+	int is_active;
+};
+
+struct ll1202_chip {
+	struct i2c_client *client;
+	struct mutex lock;
+	struct ll1202_led leds[LL1202_MAX_LEDS];
+};
+
+static struct ll1202_led *cdev_to_ll1202_led(struct led_classdev *cdev)
+{
+	return container_of(cdev, struct ll1202_led, led_cdev);
+}
+
+static int ll1202_read_reg(struct ll1202_chip *chip, int reg, uint8_t *val)
+{
+	int ret = i2c_smbus_read_byte_data(chip->client, reg);
+
+	if (ret < 0)
+		return ret;
+
+	*val = (uint8_t)ret;
+	return 0;
+}
+
+static int ll1202_write_reg(struct ll1202_chip *chip, int reg, uint8_t val)
+{
+	return i2c_smbus_write_byte_data(chip->client, reg, val);
+}
+
+static int ll1202_get_channel(struct device *dev)
+{
+	struct device_node *np = dev->parent->of_node, *child;
+	int err, ret = -1;
+
+	for_each_child_of_node(np, child) {
+		if (strncmp(dev->kobj.name,
+			    of_get_property(child, "label", NULL),
+			    strnlen(dev->kobj.name, MAX_INPUT)) == 0) {
+			err = of_property_read_u32(child, "reg", &ret);
+			if (err) {
+				of_node_put(child);
+				pr_err(DRIVER_NAME
+				       ": Failed to read property %s", child->name);
+				return ret;
+			}
+			break;
+		}
+	}
+	return ret;
+}
+
+static ssize_t ll1202_show_all_registers(struct device *dev,
+					 struct device_attribute *devattr,
+					 char *buf)
+{
+	struct ll1202_chip *chip = dev_get_drvdata(dev);
+	uint8_t reg_value = 0;
+	int ret, i;
+	char *bufp = buf;
+
+	mutex_lock(&chip->lock);
+
+	for (i = LL1202_DEVICE_ID; i <= LL1202_CLOCK_REG; i++) {
+		ret = ll1202_read_reg(chip, i, &reg_value);
+		if (ret != 0)
+			dev_err(&chip->client->dev,
+				"Reading register [0x%x] failed.\n", i);
+
+		bufp += snprintf(bufp, PAGE_SIZE, "Addr[0x%x] = 0x%x\n", i,
+				 reg_value);
+	}
+
+	mutex_unlock(&chip->lock);
+	return strlen(buf);
+}
+
+static ssize_t
+ll1202_show_patt_sequence_repetition(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct ll1202_chip *chip = dev_get_drvdata(dev);
+	unsigned int ret;
+	uint8_t reg_value;
+	char *bufp = buf;
+
+	mutex_lock(&chip->lock);
+	ret = ll1202_read_reg(chip, LL1202_PATTERN_REP, &reg_value);
+	if (ret != 0)
+		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n", LL1202_PATTERN_REP);
+	mutex_unlock(&chip->lock);
+	bufp += snprintf(bufp, PAGE_SIZE,
+			 "Pattern sequence register, repetition value = %d (times)\n",
+			 reg_value);
+	return strlen(buf);
+}
+
+static ssize_t
+ll1202_store_patt_sequence_repetition(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct ll1202_chip *chip = dev_get_drvdata(dev);
+	unsigned int ret;
+	unsigned long duration;
+
+	if (!count)
+		return -EINVAL;
+
+	ret = kstrtoul(buf, 10, &duration);
+	if (ret) {
+		dev_err(&chip->client->dev, "sscanf failed with error :%d\n",
+			ret);
+		return ret;
+	}
+
+	mutex_lock(&chip->lock);
+	ret = ll1202_write_reg(chip, LL1202_PATTERN_REP, duration);
+	if (ret != 0)
+		dev_err(&chip->client->dev, "Writing register [0x%x] failed\n",
+			LL1202_PATTERN_REP);
+	mutex_unlock(&chip->lock);
+	return count;
+}
+
+static int ll1202_prescalar_to_miliamps(uint8_t reg_value)
+{
+	return reg_value * 20 / 255;
+}
+
+static int ll1202_prescalar_to_miliseconds(uint8_t reg_value)
+{
+	return reg_value * 5660 / 255;
+}
+
+static ssize_t ll1202_show_channel_mA_current(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct ll1202_chip *chip = dev_get_drvdata(dev->parent);
+	unsigned int ret;
+	uint8_t reg_value;
+	char *bufp = buf;
+	int led_num = ll1202_get_channel(dev);
+
+	if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {
+		dev_err(&chip->client->dev,
+			"Invalid register [0x%x] (out of range)\n",
+			led_num);
+	}
+	mutex_lock(&chip->lock);
+	ret = ll1202_read_reg(chip, LL1202_ILED_REG0 + led_num, &reg_value);
+	if (ret != 0)
+		dev_err(&chip->client->dev, "Reading analog dimming register [0x%x] failed\n",
+			led_num);
+	mutex_unlock(&chip->lock);
+	bufp += snprintf(bufp, PAGE_SIZE, "Channel[%d] = %d mA\n", led_num,
+			 ll1202_prescalar_to_miliamps(reg_value));
+	return strlen(buf);
+}
+
+static int ll1202_channel_activate(struct ll1202_led *led)
+{
+	struct ll1202_chip *chip;
+	uint8_t reg_chan_low, reg_chan_high;
+	int ret = 0;
+
+	chip = led->chip;
+	if (led->is_active) {
+		mutex_lock(&chip->lock);
+
+		ret = ll1202_read_reg(chip, LL1202_CHAN_ENABLE_LOW,
+				      &reg_chan_low);
+		if (ret < 0) {
+			dev_err(&chip->client->dev,
+				"Failed reading register [0x%x]\n", LL1202_CHAN_ENABLE_LOW);
+		}
+
+		ret = ll1202_read_reg(chip, LL1202_CHAN_ENABLE_HIGH,
+				      &reg_chan_high);
+		if (ret < 0) {
+			dev_err(&chip->client->dev,
+				"Failed reading register [0x%x]\n", LL1202_CHAN_ENABLE_HIGH);
+		}
+
+		reg_chan_low = reg_chan_low | BIT(led->led_num);
+		ret = ll1202_write_reg(chip, LL1202_CHAN_ENABLE_LOW,
+				       reg_chan_low);
+		if (ret < 0) {
+			dev_err(&chip->client->dev,
+				"Failed writing to register [0x%x]\n", LL1202_CHAN_ENABLE_LOW);
+		}
+		reg_chan_high = reg_chan_high | (BIT(led->led_num) >> 7);
+		ret = ll1202_write_reg(chip, LL1202_CHAN_ENABLE_HIGH,
+				       reg_chan_high);
+		if (ret < 0) {
+			dev_err(&chip->client->dev,
+				"Failed writing to register [0x%x]\n", LL1202_CHAN_ENABLE_HIGH);
+		}
+		mutex_unlock(&chip->lock);
+	}
+	return ret;
+}
+
+#define LL1202_PWM_PATTERN_ATTR(pattern)                                          \
+	static ssize_t ll1202_show_pwm_pattern##pattern(                          \
+		struct device *dev, struct device_attribute *attr, char *buf)     \
+	{                                                                         \
+		struct ll1202_chip *chip = dev_get_drvdata(dev->parent);          \
+		uint8_t duration = 0;                                             \
+		uint8_t reg_value_l = 0;                                          \
+		uint8_t reg_value_h = 0;                                          \
+		uint16_t reg_value = 0;                                           \
+		int ret;                                                          \
+		char *bufp = buf;                                                 \
+		int led_num = ll1202_get_channel(dev);                            \
+		if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {                  \
+			dev_err(&chip->client->dev,                               \
+				"Invalid register [0x%x] (out of range)\n",  \
+				led_num);                                         \
+		}                                                                 \
+		mutex_lock(&chip->lock);                                          \
+		ret = ll1202_read_reg(                                            \
+			chip,                                                     \
+			(LL1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),    \
+			&reg_value_l);                                            \
+		if (ret != 0)                                                     \
+			dev_err(&chip->client->dev,                               \
+				"Reading pattern PWM register [0x%x] failed\n", led_num);     \
+		ret = ll1202_read_reg(chip,                                       \
+				      (LL1202_PATTERN_PWM + 0x1 +                 \
+				       (led_num * 2) + 0x18 * pattern),           \
+				      &reg_value_h);                              \
+		if (ret != 0)                                                     \
+			dev_err(&chip->client->dev,                               \
+				"Reading pattern PWM register [0x%x] failed\n", led_num);     \
+		reg_value = (uint16_t)reg_value_h << 8 | reg_value_l;             \
+		ret = ll1202_read_reg(chip, (LL1202_PATTERN_DUR + pattern),       \
+				      &duration);                                 \
+		if (ret != 0)                                                     \
+			dev_err(&chip->client->dev,                               \
+				"Reading pattern durating register [0x%x] failed\n", led_num);     \
+		bufp += snprintf(                                                 \
+			bufp, PAGE_SIZE,                                          \
+			"Pattern[%d][cs%d]: PWM = 0x%03X; DURATION = %d ms\n",    \
+			pattern, led_num, reg_value,                              \
+			ll1202_prescalar_to_miliseconds(duration));               \
+		mutex_unlock(&chip->lock);                                        \
+		return strlen(buf);                                               \
+	}                                                                         \
+	static ssize_t ll1202_store_pwm_pattern##pattern(                         \
+		struct device *dev, struct device_attribute *attr,                \
+		const char *buf, size_t count)                                    \
+	{                                                                         \
+		struct ll1202_chip *chip = dev_get_drvdata(dev->parent);          \
+		unsigned int ret, reg_value;                                      \
+		unsigned long duration;                                           \
+		char buf_u8[16];                                                  \
+		uint8_t reg_value_l = 0;                                          \
+		uint8_t reg_value_h = 0;                                          \
+		int led_num = ll1202_get_channel(dev);                            \
+		if (led_num < 0 || led_num >= LL1202_MAX_LEDS) {                  \
+			dev_err(&chip->client->dev,                               \
+				"Invalid register [0x%x] (out of range)\n",  \
+				led_num);                                         \
+			return count;                                             \
+		}                                                                 \
+		if (!count)                                                       \
+			return -EINVAL;                                           \
+		ret = sscanf(buf, "%X %s", &reg_value, buf_u8);                   \
+		if (ret == 0) {                                                   \
+			dev_err(&chip->client->dev,                               \
+				"sscanf failed with error :%d\n", ret);           \
+			return ret;                                               \
+		}                                                                 \
+		ret = kstrtoul(buf_u8, 10, &duration);                            \
+		if (ret)                                                          \
+			return ret;                                               \
+		reg_value_l = (uint8_t)reg_value;                                 \
+		reg_value_h = (uint8_t)(reg_value >> 8);                          \
+		mutex_lock(&chip->lock);                                          \
+		ret = ll1202_write_reg(                                           \
+			chip,                                                     \
+			(LL1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),    \
+			(uint8_t)reg_value_l);                                    \
+		if (ret != 0)                                                     \
+			dev_err(&chip->client->dev,                               \
+				"Writing to register [0x%x] failed, value %d\n",  \
+				LL1202_PATTERN_PWM + (led_num * 2) +              \
+					0x18 * pattern,                           \
+				reg_value_l);                                     \
+		ret = ll1202_write_reg(chip,                                      \
+				       (LL1202_PATTERN_PWM + 0x1 +                \
+					(led_num * 2) + 0x18 * pattern),          \
+				       (uint8_t)reg_value_h);                     \
+		if (ret != 0)                                                     \
+			dev_err(&chip->client->dev,                               \
+				"Writing to register [0x%x] failed, value %d\n",  \
+				(LL1202_PATTERN_PWM + 0x1 + (led_num * 2) +       \
+				 0x18 * pattern),                                 \
+				reg_value_h);                                     \
+		ret = ll1202_write_reg(chip, (LL1202_PATTERN_DUR + pattern),      \
+				       (u8)duration);                             \
+		if (ret != 0)                                                     \
+			dev_err(&chip->client->dev,                               \
+				"Writing to register [0x%x] failed, value %d\n", \
+				(LL1202_PATTERN_DUR + pattern), (u8)duration);    \
+		ret = ll1202_write_reg(chip, LL1202_CONFIG_REG,                   \
+				       (0xC0 | pattern));                         \
+		if (ret != 0) {                                                   \
+			dev_err(&chip->client->dev,                               \
+				"Failed writing to reg [0x%x]\n", LL1202_CONFIG_REG);     \
+		}                                                                 \
+		mutex_unlock(&chip->lock);                                        \
+		ll1202_channel_activate(&chip->leds[led_num]);                    \
+		return count;                                                     \
+	}                                                                         \
+	static struct device_attribute dev_attr_led_pwm_pattern##pattern = {		\
+	.attr = {							\
+	.name = __stringify(pwm_pattern##pattern),					\
+	.mode =  00444 | 00200,						\
+	},								\
+	.show = ll1202_show_pwm_pattern##pattern,				\
+	.store = ll1202_store_pwm_pattern##pattern,				\
+}
+
+LL1202_PWM_PATTERN_ATTR(0);
+LL1202_PWM_PATTERN_ATTR(1);
+LL1202_PWM_PATTERN_ATTR(2);
+LL1202_PWM_PATTERN_ATTR(3);
+LL1202_PWM_PATTERN_ATTR(4);
+LL1202_PWM_PATTERN_ATTR(5);
+LL1202_PWM_PATTERN_ATTR(6);
+LL1202_PWM_PATTERN_ATTR(7);
+
+static DEVICE_ATTR(led_device_regsdump, 00444, ll1202_show_all_registers,
+		   NULL);
+static DEVICE_ATTR(patt_sequence_repetition, 00444 | 00200,
+		   ll1202_show_patt_sequence_repetition,
+		   ll1202_store_patt_sequence_repetition);
+static DEVICE_ATTR(current_mA, 00444, ll1202_show_channel_mA_current, NULL);
+
+static struct attribute *led_attrs[] = {
+	&dev_attr_led_device_regsdump.attr,
+	&dev_attr_patt_sequence_repetition.attr,
+	NULL,
+};
+
+static struct attribute *led_group_attrs[] = {
+	&dev_attr_led_pwm_pattern0.attr, &dev_attr_led_pwm_pattern1.attr,
+	&dev_attr_led_pwm_pattern2.attr, &dev_attr_led_pwm_pattern3.attr,
+	&dev_attr_led_pwm_pattern4.attr, &dev_attr_led_pwm_pattern5.attr,
+	&dev_attr_led_pwm_pattern6.attr, &dev_attr_led_pwm_pattern7.attr,
+	&dev_attr_current_mA.attr,	 NULL,
+};
+
+static struct attribute_group attr_group = {
+	.attrs = led_attrs,
+};
+
+static struct attribute_group attr_pat_group = {
+	.attrs = led_group_attrs,
+};
+
+static const struct attribute_group *ll1202_groups[] = { &attr_pat_group,
+							 NULL };
+
+static void ll1202_brightness_set(struct led_classdev *led_cdev,
+				  enum led_brightness value)
+{
+	struct ll1202_led *led = cdev_to_ll1202_led(led_cdev);
+	struct ll1202_chip *chip = led->chip;
+	int ret;
+
+	mutex_lock(&chip->lock);
+	ret = ll1202_write_reg(chip, LL1202_ILED_REG0 + led->led_num, value);
+	if (ret != 0)
+		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n",
+			LL1202_ILED_REG0 + led->led_num);
+	mutex_unlock(&chip->lock);
+}
+
+static enum led_brightness ll1202_brightness_get(struct led_classdev *led_cdev)
+{
+	struct ll1202_led *led = cdev_to_ll1202_led(led_cdev);
+	struct ll1202_chip *chip = led->chip;
+	uint8_t reg_value;
+	int ret;
+
+	mutex_lock(&chip->lock);
+	ret = ll1202_read_reg(chip, LL1202_ILED_REG0 + led->led_num,
+			      &reg_value);
+	if (ret != 0)
+		dev_err(&chip->client->dev, "Reading register [0x%x] failed\n",
+			LL1202_ILED_REG0 + led->led_num);
+
+	mutex_unlock(&chip->lock);
+	return reg_value;
+}
+
+static int ll1202_dt_init(struct ll1202_chip *chip)
+{
+	struct device_node *np = chip->client->dev.of_node, *child;
+	struct ll1202_led *led;
+	int err, reg;
+
+	for_each_child_of_node(np, child) {
+		err = of_property_read_u32(child, "reg", &reg);
+		if (err) {
+			of_node_put(child);
+			pr_err(DRIVER_NAME ": Failed to get child node");
+			return err;
+		}
+		if (reg < 0 || reg >= LL1202_MAX_LEDS) {
+			of_node_put(child);
+			pr_err(DRIVER_NAME ": Invalid register value [0x%x] (out of range)", reg);
+			return -EINVAL;
+		}
+
+		led = &chip->leds[reg];
+		led->led_cdev.name = of_get_property(child, "label", NULL) ?:
+					     child->name;
+
+		err = of_property_read_u32(child, "active", &led->is_active);
+		if (err) {
+			of_node_put(child);
+			pr_err(DRIVER_NAME ": Failed to get child node");
+			return err;
+		}
+
+		led->led_cdev.brightness_set = ll1202_brightness_set;
+		led->led_cdev.brightness_get = ll1202_brightness_get;
+		led->led_cdev.groups = ll1202_groups;
+	}
+	return 0;
+}
+
+static int ll1202_setup(struct ll1202_chip *chip)
+{
+	int ret;
+
+	mutex_lock(&chip->lock);
+	ret = ll1202_write_reg(chip, LL1202_DEV_ENABLE, 0x1);
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
+			LL1202_DEV_ENABLE);
+	}
+	mutex_unlock(&chip->lock);
+	usleep_range(6500, 10000);
+	mutex_lock(&chip->lock);
+	ret = ll1202_write_reg(chip, LL1202_DEV_ENABLE, 0x80);
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
+			LL1202_DEV_ENABLE);
+	}
+	mutex_unlock(&chip->lock);
+	usleep_range(6500, 10000);
+	mutex_lock(&chip->lock);
+	ret = ll1202_write_reg(chip, LL1202_PATTERN_REP, 0xFF);
+	if (ret < 0) {
+		dev_err(&chip->client->dev, "Failed writing to register [0x%x]\n",
+			LL1202_PATTERN_REP);
+		return ret;
+	}
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int ll1202_probe(struct i2c_client *client)
+{
+	struct ll1202_chip *chip;
+	struct ll1202_led *led;
+	int ret, err;
+	int i;
+
+	pr_info(DRIVER_NAME ": (I2C) " DRIVER_VERSION "\n");
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
+		return -EIO;
+	}
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, chip);
+
+	mutex_init(&chip->lock);
+	chip->client = client;
+
+	/* Device tree setup */
+	ret = ll1202_dt_init(chip);
+	if (ret < 0)
+		goto exit;
+
+	/* Configuration setup */
+	ret = ll1202_setup(chip);
+	if (ret < 0)
+		goto exit;
+
+	for (i = 0; i < LL1202_MAX_LEDS; i++) {
+		led = &chip->leds[i];
+		led->chip = chip;
+		led->led_num = i;
+		if (led->is_active) {
+			err = led_classdev_register(&client->dev,
+						    &led->led_cdev);
+			if (err < 0) {
+				pr_err(DRIVER_NAME
+				       ": Failed to register LED class dev");
+				goto exit;
+			}
+		}
+	}
+
+	ret = sysfs_create_group(&client->dev.kobj, &attr_group);
+	if (ret) {
+		dev_err(&client->dev,
+			"Failed to create sysfs group for ll1202\n");
+		goto err_setup;
+	}
+
+	return 0;
+
+err_setup:
+	for (i = 0; i < LL1202_MAX_LEDS; i++)
+		led_classdev_unregister(&chip->leds[i].led_cdev);
+exit:
+	mutex_destroy(&chip->lock);
+	devm_kfree(&client->dev, chip);
+	return ret;
+}
+
+static void ll1202_remove(struct i2c_client *client)
+{
+	struct ll1202_chip *dev = i2c_get_clientdata(client);
+	int i;
+
+	for (i = 0; i < LL1202_MAX_LEDS; i++)
+		led_classdev_unregister(&dev->leds[i].led_cdev);
+
+	sysfs_remove_group(&client->dev.kobj, &attr_group);
+
+	mutex_destroy(&dev->lock);
+	devm_kfree(&client->dev, dev->leds);
+	devm_kfree(&client->dev, dev);
+}
+
+static const struct i2c_device_id ll1202_id[] = {
+	{ DRIVER_NAME "-i2c", 0 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, ll1202_id);
+
+static const struct of_device_id ll1202_dt_ids[] = {
+	{
+		.compatible = "st,led1202",
+	},
+};
+
+MODULE_DEVICE_TABLE(of, ll1202_dt_ids);
+
+static struct i2c_driver ll1202_driver = {
+	.driver = {
+		.name = "ll1202",
+		.of_match_table = of_match_ptr(ll1202_dt_ids),
+	},
+	.probe = ll1202_probe,
+	.remove = ll1202_remove,
+	.id_table = ll1202_id,
+};
+
+module_i2c_driver(ll1202_driver);
+
+MODULE_AUTHOR("Remote Tech LTD");
+MODULE_DESCRIPTION("LED1202 : 12-channel constant current LED driver");
+MODULE_LICENSE("GPL");