2021-03-24 10:26:50

by Hermes Zhang

[permalink] [raw]
Subject: [PATCH 1/2] leds: leds-multi-gpio: Add multiple GPIOs LED driver

From: Hermes Zhang <[email protected]>

Introduce a new multiple GPIOs LED driver. This LED will made of
multiple GPIOs (up to 8) and will map different brightness to different
GPIOs states which defined in dts file.

Signed-off-by: Hermes Zhang <[email protected]>
---
drivers/leds/Kconfig | 12 +++
drivers/leds/Makefile | 1 +
drivers/leds/leds-multi-gpio.c | 140 +++++++++++++++++++++++++++++++++
3 files changed, 153 insertions(+)
create mode 100644 drivers/leds/leds-multi-gpio.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index b6742b4231bf..e3ff84080192 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -370,6 +370,18 @@ config LEDS_GPIO
defined as platform devices and/or OpenFirmware platform devices.
The code to use these bindings can be selected below.

+config LEDS_MULTI_GPIO
+ tristate "LED Support for multiple GPIOs LED"
+ depends on LEDS_CLASS
+ depends on GPIOLIB
+ help
+ This option enables support for a multiple GPIOs LED. Such LED is made
+ of multiple GPIOs and could change the brightness by setting different
+ states of the GPIOs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called leds-multi-gpio.
+
config LEDS_LP3944
tristate "LED Support for N.S. LP3944 (Fun Light) I2C chip"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 2a698df9da57..984201ec5375 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o
obj-$(CONFIG_LEDS_FSG) += leds-fsg.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
+obj-$(CONFIG_LEDS_MULTI_GPIO) += leds-multi-gpio.o
obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o
diff --git a/drivers/leds/leds-multi-gpio.c b/drivers/leds/leds-multi-gpio.c
new file mode 100644
index 000000000000..54d92c81a476
--- /dev/null
+++ b/drivers/leds/leds-multi-gpio.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Axis Communications AB
+ */
+
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#define MAX_GPIO_NUM 8
+
+struct multi_gpio_led_priv {
+ struct led_classdev cdev;
+
+ struct gpio_descs *gpios;
+
+ u8 *states;
+ int nr_states;
+};
+
+
+static void multi_gpio_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct multi_gpio_led_priv *priv;
+ int idx;
+
+ DECLARE_BITMAP(values, MAX_GPIO_NUM);
+
+ priv = container_of(led_cdev, struct multi_gpio_led_priv, cdev);
+
+ idx = (value - LED_OFF) * priv->nr_states / (LED_FULL + 1);
+
+ values[0] = priv->states[idx];
+
+ gpiod_set_array_value(priv->gpios->ndescs, priv->gpios->desc,
+ priv->gpios->info, values);
+}
+
+static int multi_gpio_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct multi_gpio_led_priv *priv = NULL;
+ int ret;
+ const char *state = NULL;
+ struct led_init_data init_data = {};
+
+ priv = devm_kzalloc(dev, sizeof(struct multi_gpio_led_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->gpios = devm_gpiod_get_array(dev, "led", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->gpios))
+ return PTR_ERR(priv->gpios);
+
+ if (priv->gpios->ndescs >= MAX_GPIO_NUM) {
+ dev_err(dev, "Too many GPIOs\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_count_u8_elems(node, "led-states");
+ if (ret < 0)
+ return ret;
+
+ priv->nr_states = ret;
+ priv->states = devm_kzalloc(dev, sizeof(*priv->states) * priv->nr_states, GFP_KERNEL);
+ if (!priv->states)
+ return -ENOMEM;
+
+ ret = of_property_read_u8_array(node, "led-states", priv->states, priv->nr_states);
+ if (ret)
+ return ret;
+
+ priv->cdev.max_brightness = LED_FULL;
+ priv->cdev.default_trigger = of_get_property(node, "linux,default-trigger", NULL);
+ priv->cdev.brightness_set = multi_gpio_led_set;
+
+ init_data.fwnode = of_fwnode_handle(node);
+
+ ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data);
+ if (ret < 0)
+ return ret;
+
+ of_property_read_string(node, "default-state", &state);
+ if (!strcmp(state, "on"))
+ multi_gpio_led_set(&priv->cdev, LED_FULL);
+ else
+ multi_gpio_led_set(&priv->cdev, LED_OFF);
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static void multi_gpio_led_shutdown(struct platform_device *pdev)
+{
+ struct multi_gpio_led_priv *priv = platform_get_drvdata(pdev);
+
+ multi_gpio_led_set(&priv->cdev, LED_OFF);
+}
+
+static int multi_gpio_led_remove(struct platform_device *pdev)
+{
+ multi_gpio_led_shutdown(pdev);
+
+ return 0;
+}
+
+static const struct of_device_id of_multi_gpio_led_match[] = {
+ { .compatible = "multi-gpio-led", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_multi_gpio_led_match);
+
+static struct platform_driver multi_gpio_led_driver = {
+ .probe = multi_gpio_led_probe,
+ .remove = multi_gpio_led_remove,
+ .shutdown = multi_gpio_led_shutdown,
+ .driver = {
+ .name = "multi-gpio-led",
+ .of_match_table = of_multi_gpio_led_match,
+ },
+};
+
+module_platform_driver(multi_gpio_led_driver);
+
+MODULE_AUTHOR("Hermes Zhang <[email protected]>");
+MODULE_DESCRIPTION("Multiple GPIOs LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-multi-gpio");
--
2.20.1


2021-03-24 11:16:28

by Marek Behún

[permalink] [raw]
Subject: Re: [PATCH 1/2] leds: leds-multi-gpio: Add multiple GPIOs LED driver

On Wed, 24 Mar 2021 15:56:30 +0800
Hermes Zhang <[email protected]> wrote:

> From: Hermes Zhang <[email protected]>
>
> Introduce a new multiple GPIOs LED driver. This LED will made of
> multiple GPIOs (up to 8) and will map different brightness to different
> GPIOs states which defined in dts file.

I wonder how many boards have such LEDs.

Also if it wouldn't be better to expand the original leds-gpio driver.
Probably depends on how much larger would such expansion make the
leds-gpio driver.

> +#include <linux/err.h>
> +#include <linux/gpio.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/kernel.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/slab.h>

Why do you include slab.h?

> +
> +#define MAX_GPIO_NUM 8
> +
> +struct multi_gpio_led_priv {
> + struct led_classdev cdev;
> +
> + struct gpio_descs *gpios;
> +
> + u8 *states;
> + int nr_states;
> +};

Use flexible array members. Allocate with
devm_kzalloc(dev, struct_size(priv, states, priv->nr_states),
GFP_KERNEL)

> +
> +
> +static void multi_gpio_led_set(struct led_classdev *led_cdev,
> + enum led_brightness value)
> +{
> + struct multi_gpio_led_priv *priv;
> + int idx;
> +
> + DECLARE_BITMAP(values, MAX_GPIO_NUM);
> +
> + priv = container_of(led_cdev, struct multi_gpio_led_priv, cdev);
> +
> + idx = (value - LED_OFF) * priv->nr_states / (LED_FULL + 1);

LED_FULL / LED_OFF are deprecated, don't use them.

> +
> + values[0] = priv->states[idx];
> +
> + gpiod_set_array_value(priv->gpios->ndescs, priv->gpios->desc,
> + priv->gpios->info, values);
> +}
> +
> +static int multi_gpio_led_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *node = dev->of_node;
> + struct multi_gpio_led_priv *priv = NULL;
> + int ret;
> + const char *state = NULL;
> + struct led_init_data init_data = {};
> +
> + priv = devm_kzalloc(dev, sizeof(struct multi_gpio_led_priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->gpios = devm_gpiod_get_array(dev, "led", GPIOD_OUT_LOW);
> + if (IS_ERR(priv->gpios))
> + return PTR_ERR(priv->gpios);
> +
> + if (priv->gpios->ndescs >= MAX_GPIO_NUM) {
> + dev_err(dev, "Too many GPIOs\n");
> + return -EINVAL;
> + }
> +
> + ret = of_property_count_u8_elems(node, "led-states");
> + if (ret < 0)
> + return ret;
> +
> + priv->nr_states = ret;
> + priv->states = devm_kzalloc(dev, sizeof(*priv->states) * priv->nr_states, GFP_KERNEL);
> + if (!priv->states)
> + return -ENOMEM;
> +
> + ret = of_property_read_u8_array(node, "led-states", priv->states, priv->nr_states);
> + if (ret)
> + return ret;
> +
> + priv->cdev.max_brightness = LED_FULL;

???? max_brightness is not 255 (= LED_FULL). max_brightness must be
derived from the led-states property.


> + priv->cdev.default_trigger = of_get_property(node, "linux,default-trigger", NULL);
> + priv->cdev.brightness_set = multi_gpio_led_set;
> +
> + init_data.fwnode = of_fwnode_handle(node);
> +
> + ret = devm_led_classdev_register_ext(dev, &priv->cdev, &init_data);
> + if (ret < 0)
> + return ret;
> +
> + of_property_read_string(node, "default-state", &state);
> + if (!strcmp(state, "on"))
> + multi_gpio_led_set(&priv->cdev, LED_FULL);
> + else
> + multi_gpio_led_set(&priv->cdev, LED_OFF);

Again LED_FULL and LED_OFF...
What about default-state = "keep" ?

Hermes, do you actually have a device that controls LEDs this way? How
many brightness options do they have?

Also I think this functionality could be easily incorporated into the
existing leds-gpio driver, instead of creating new driver.

Moreover your driver can control only one LED, so it needs to be
probed multiple times for multiple LEDs. Meanwhile the leds-gpio driver
can register multiple LEDs in one probe...

Marek

2021-03-24 11:40:15

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH 1/2] leds: leds-multi-gpio: Add multiple GPIOs LED driver

On Wed 2021-03-24 15:56:30, Hermes Zhang wrote:
> From: Hermes Zhang <[email protected]>
>
> Introduce a new multiple GPIOs LED driver. This LED will made of
> multiple GPIOs (up to 8) and will map different brightness to different
> GPIOs states which defined in dts file.
>
> Signed-off-by: Hermes Zhang <[email protected]>
> ---
> drivers/leds/Kconfig | 12 +++
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-multi-gpio.c | 140 +++++++++++++++++++++++++++++++++
> 3 files changed, 153 insertions(+)
> create mode 100644 drivers/leds/leds-multi-gpio.c

Let's put it into drivers/leds/simple. You may need to create it.

Best regards,
Pavel
--
http://www.livejournal.com/~pavelmachek


Attachments:
(No filename) (746.00 B)
signature.asc (201.00 B)
Download all attachments

2021-03-24 11:40:23

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH 1/2] leds: leds-multi-gpio: Add multiple GPIOs LED driver

Hi!

> > + of_property_read_string(node, "default-state", &state);
> > + if (!strcmp(state, "on"))
> > + multi_gpio_led_set(&priv->cdev, LED_FULL);
> > + else
> > + multi_gpio_led_set(&priv->cdev, LED_OFF);
>
> Again LED_FULL and LED_OFF...
> What about default-state = "keep" ?

Let's not support default-state unless you need it.

Best regards,
Pavel
--
http://www.livejournal.com/~pavelmachek


Attachments:
(No filename) (424.00 B)
signature.asc (201.00 B)
Download all attachments

2021-03-24 11:41:10

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH 1/2] leds: leds-multi-gpio: Add multiple GPIOs LED driver

Hi!

> > From: Hermes Zhang <[email protected]>
> >
> > Introduce a new multiple GPIOs LED driver. This LED will made of
> > multiple GPIOs (up to 8) and will map different brightness to different
> > GPIOs states which defined in dts file.
>
> I wonder how many boards have such LEDs.
>
> Also if it wouldn't be better to expand the original leds-gpio driver.
> Probably depends on how much larger would such expansion make the
> leds-gpio driver.

Let's start with separate.

> Use flexible array members. Allocate with
> devm_kzalloc(dev, struct_size(priv, states, priv->nr_states),
> GFP_KERNEL)

Better yet, assume the brightness is 0..2^(num leds) and avoid this
complexity.

> Again LED_FULL and LED_OFF...
> What about default-state = "keep" ?
>
> Hermes, do you actually have a device that controls LEDs this way? How
> many brightness options do they have?

He has two bits.

> Also I think this functionality could be easily incorporated into the
> existing leds-gpio driver, instead of creating new driver.

> Moreover your driver can control only one LED, so it needs to be
> probed multiple times for multiple LEDs. Meanwhile the leds-gpio driver
> can register multiple LEDs in one probe...

The current version is mostly fine. Let's not overcomplicate it.

Best regards,
Pavel
--
http://www.livejournal.com/~pavelmachek


Attachments:
(No filename) (1.38 kB)
signature.asc (201.00 B)
Download all attachments

2021-03-25 06:08:56

by Hermes Zhang

[permalink] [raw]
Subject: Re: [PATCH 1/2] leds: leds-multi-gpio: Add multiple GPIOs LED driver

Hi Marek,

On 3/24/21 5:34 PM, Marek Behun wrote:
>> +#include <linux/err.h>
>> +#include <linux/gpio.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/kernel.h>
>> +#include <linux/leds.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_gpio.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/property.h>
>> +#include <linux/slab.h>
> Why do you include slab.h?
Yeah, I will clean it in next commit.
>> +
>> +
>> +static void multi_gpio_led_set(struct led_classdev *led_cdev,
>> + enum led_brightness value)
>> +{
>> + struct multi_gpio_led_priv *priv;
>> + int idx;
>> +
>> + DECLARE_BITMAP(values, MAX_GPIO_NUM);
>> +
>> + priv = container_of(led_cdev, struct multi_gpio_led_priv, cdev);
>> +
>> + idx = (value - LED_OFF) * priv->nr_states / (LED_FULL + 1);
> LED_FULL / LED_OFF are deprecated, don't use them.

Then could I use just 0 (instead LED_OFF) and led_cdev->max_brightness

(instead of LED_FULL) here? The idea here is map the states defined in dts

to the full brightness range.

> +
> + priv->nr_states = ret;
> + priv->states = devm_kzalloc(dev, sizeof(*priv->states) * priv->nr_states, GFP_KERNEL);
> + if (!priv->states)
> + return -ENOMEM;
> +
> + ret = of_property_read_u8_array(node, "led-states", priv->states, priv->nr_states);
> + if (ret)
> + return ret;
> +
> + priv->cdev.max_brightness = LED_FULL;
> ???? max_brightness is not 255 (= LED_FULL). max_brightness must be
> derived from the led-states property.

Yeah, I will fix this. the max-brightness should for the whole LED,
right? So

it will at same level with led-states.



2021-03-25 12:30:29

by Marek Behún

[permalink] [raw]
Subject: Re: [PATCH 1/2] leds: leds-multi-gpio: Add multiple GPIOs LED driver

On Thu, 25 Mar 2021 06:04:43 +0000
Hermes Zhang <[email protected]> wrote:

> > LED_FULL / LED_OFF are deprecated, don't use them.
>
> Then could I use just 0 (instead LED_OFF) and led_cdev->max_brightness
>
> (instead of LED_FULL) here? The idea here is map the states defined in dts
>
> to the full brightness range.

Yes, you can and should use 0 insted of LED_OFF.

> > + priv->cdev.max_brightness = LED_FULL;
> > ???? max_brightness is not 255 (= LED_FULL). max_brightness must be
> > derived from the led-states property.
>
> Yeah, I will fix this. the max-brightness should for the whole LED,
> right? So
>
> it will at same level with led-states.

max_brightness should be (the number of states - 1). I.e. if you have 4
gpios and the LED supports full 2^4 = 16 states, max brightness should
be 15.

Marek