linux-leds.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Akinobu Mita <akinobu.mita@gmail.com>
To: linux-leds@vger.kernel.org, linux-gpio@vger.kernel.org
Cc: Akinobu Mita <akinobu.mita@gmail.com>,
	Linus Walleij <linus.walleij@linaro.org>,
	Bartosz Golaszewski <bgolaszewski@baylibre.com>,
	Jacek Anaszewski <jacek.anaszewski@gmail.com>,
	Pavel Machek <pavel@ucw.cz>, Dan Murphy <dmurphy@ti.com>
Subject: [PATCH] leds: gpio: support multi-level brightness
Date: Sat,  5 Oct 2019 00:34:59 +0900	[thread overview]
Message-ID: <1570203299-4270-1-git-send-email-akinobu.mita@gmail.com> (raw)

Currently, GPIO LED driver allows the GPIO properties to contain one GPIO
phandle.  This enables to contain more than one GPIO phandle and the
brightness of the LEDs is proportional to the number of active GPIOs.

Describing multi-level brightness GPIO LED is only supported in DT.  It is
not supported in ACPI and platform data.

Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Bartosz Golaszewski <bgolaszewski@baylibre.com>
Cc: Jacek Anaszewski <jacek.anaszewski@gmail.com>
Cc: Pavel Machek <pavel@ucw.cz>
Cc: Dan Murphy <dmurphy@ti.com>
Signed-off-by: Akinobu Mita <akinobu.mita@gmail.com>
---
 drivers/leds/leds-gpio.c | 185 +++++++++++++++++++++++++++++++++++++----------
 1 file changed, 147 insertions(+), 38 deletions(-)

diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c
index a5c73f3..6fad64b 100644
--- a/drivers/leds/leds-gpio.c
+++ b/drivers/leds/leds-gpio.c
@@ -9,6 +9,7 @@
 #include <linux/err.h>
 #include <linux/gpio.h>
 #include <linux/gpio/consumer.h>
+#include <linux/of_gpio.h>
 #include <linux/kernel.h>
 #include <linux/leds.h>
 #include <linux/module.h>
@@ -19,7 +20,8 @@
 
 struct gpio_led_data {
 	struct led_classdev cdev;
-	struct gpio_desc *gpiod;
+	int num_gpios;
+	struct gpio_desc **gpios;
 	u8 can_sleep;
 	u8 blinking;
 	gpio_blink_set_t platform_gpio_blink_set;
@@ -35,23 +37,24 @@ static void gpio_led_set(struct led_classdev *led_cdev,
 	enum led_brightness value)
 {
 	struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);
-	int level;
+	int i;
+	int num_active_gpios =
+		DIV_ROUND_UP(led_dat->num_gpios * value, LED_FULL);
 
-	if (value == LED_OFF)
-		level = 0;
-	else
-		level = 1;
+	for (i = 0; i < led_dat->num_gpios; i++) {
+		int level = i < num_active_gpios ? 1 : 0;
 
-	if (led_dat->blinking) {
-		led_dat->platform_gpio_blink_set(led_dat->gpiod, level,
-						 NULL, NULL);
-		led_dat->blinking = 0;
-	} else {
-		if (led_dat->can_sleep)
-			gpiod_set_value_cansleep(led_dat->gpiod, level);
+		if (led_dat->blinking)
+			led_dat->platform_gpio_blink_set(led_dat->gpios[i],
+							 level, NULL, NULL);
+		else if (led_dat->can_sleep)
+			gpiod_set_value_cansleep(led_dat->gpios[i], level);
 		else
-			gpiod_set_value(led_dat->gpiod, level);
+			gpiod_set_value(led_dat->gpios[i], level);
 	}
+
+	if (led_dat->blinking)
+		led_dat->blinking = 0;
 }
 
 static int gpio_led_set_blocking(struct led_classdev *led_cdev,
@@ -65,10 +68,72 @@ static int gpio_blink_set(struct led_classdev *led_cdev,
 	unsigned long *delay_on, unsigned long *delay_off)
 {
 	struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);
+	int ret = 0;
+	int i;
 
 	led_dat->blinking = 1;
-	return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK,
+
+	for (i = 0; i < led_dat->num_gpios && !ret; i++) {
+		ret = led_dat->platform_gpio_blink_set(led_dat->gpios[i],
+						GPIO_LED_BLINK,
 						delay_on, delay_off);
+	}
+
+	return ret;
+}
+
+static enum led_brightness
+gpio_led_brightness_get(struct gpio_led_data *led_dat)
+{
+	int num_active_gpios = 0;
+	int i;
+
+	for (i = 0; i < led_dat->num_gpios; i++) {
+		int ret = gpiod_get_value_cansleep(led_dat->gpios[i]);
+
+		if (ret < 0)
+			return ret;
+
+		if (ret)
+			num_active_gpios++;
+	}
+
+	return LED_FULL * num_active_gpios / led_dat->num_gpios;
+}
+
+static int gpio_led_set_default(struct gpio_led_data *led_dat,
+				unsigned int default_state)
+{
+	enum led_brightness brightness;
+	int num_active_gpios;
+	int i;
+
+	if (default_state == LEDS_GPIO_DEFSTATE_KEEP) {
+		brightness = gpio_led_brightness_get(led_dat);
+		if (brightness < 0)
+			return brightness;
+	} else {
+		if (default_state == LEDS_GPIO_DEFSTATE_ON)
+			brightness = LED_FULL;
+		else
+			brightness = LED_OFF;
+	}
+
+	led_dat->cdev.brightness = brightness;
+
+	num_active_gpios =
+		DIV_ROUND_UP(led_dat->num_gpios * brightness, LED_FULL);
+
+	for (i = 0; i < led_dat->num_gpios; i++) {
+		int state = i < num_active_gpios ? 1 : 0;
+		int ret;
+
+		ret = gpiod_direction_output(led_dat->gpios[i], state);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static int create_gpio_led(const struct gpio_led *template,
@@ -76,10 +141,18 @@ static int create_gpio_led(const struct gpio_led *template,
 	struct fwnode_handle *fwnode, gpio_blink_set_t blink_set)
 {
 	struct led_init_data init_data = {};
-	int ret, state;
+	int ret, i;
 
 	led_dat->cdev.default_trigger = template->default_trigger;
-	led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
+
+	led_dat->can_sleep = true;
+	for (i = 0; i < led_dat->num_gpios; i++) {
+		if (!gpiod_cansleep(led_dat->gpios[i])) {
+			led_dat->can_sleep = false;
+			break;
+		}
+	}
+
 	if (!led_dat->can_sleep)
 		led_dat->cdev.brightness_set = gpio_led_set;
 	else
@@ -89,14 +162,11 @@ static int create_gpio_led(const struct gpio_led *template,
 		led_dat->platform_gpio_blink_set = blink_set;
 		led_dat->cdev.blink_set = gpio_blink_set;
 	}
-	if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
-		state = gpiod_get_value_cansleep(led_dat->gpiod);
-		if (state < 0)
-			return state;
-	} else {
-		state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
-	}
-	led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
+
+	ret = gpio_led_set_default(led_dat, template->default_state);
+	if (ret)
+		return ret;
+
 	if (!template->retain_state_suspended)
 		led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
 	if (template->panic_indicator)
@@ -104,10 +174,6 @@ static int create_gpio_led(const struct gpio_led *template,
 	if (template->retain_state_shutdown)
 		led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
 
-	ret = gpiod_direction_output(led_dat->gpiod, state);
-	if (ret < 0)
-		return ret;
-
 	if (template->name) {
 		led_dat->cdev.name = template->name;
 		ret = devm_led_classdev_register(parent, &led_dat->cdev);
@@ -131,6 +197,30 @@ static inline int sizeof_gpio_leds_priv(int num_leds)
 		(sizeof(struct gpio_led_data) * num_leds);
 }
 
+static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" };
+
+static int fwnode_gpio_count(struct fwnode_handle *child)
+{
+	char propname[32]; /* 32 is max size of property name */
+	int i;
+
+	if (!child)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
+		snprintf(propname, sizeof(propname), "%s",
+			 gpio_suffixes[i]);
+
+		/* Only DT is supported for now */
+		if (is_of_node(child))
+			return of_gpio_named_count(to_of_node(child), propname);
+		else
+			return -EINVAL;
+	}
+
+	return -ENOENT;
+}
+
 static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -150,16 +240,28 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
 		struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
 		struct gpio_led led = {};
 		const char *state = NULL;
+		int i;
+
+		led_dat->num_gpios = fwnode_gpio_count(child);
+		if (led_dat->num_gpios < 0)
+			led_dat->num_gpios = 1;
 
-		led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
-							     GPIOD_ASIS,
-							     led.name);
-		if (IS_ERR(led.gpiod)) {
+		led_dat->gpios = devm_kcalloc(dev, led_dat->num_gpios,
+					sizeof(led_dat->gpios[0]), GFP_KERNEL);
+		if (!led_dat->gpios) {
 			fwnode_handle_put(child);
-			return ERR_CAST(led.gpiod);
+			return ERR_PTR(-ENOMEM);
 		}
 
-		led_dat->gpiod = led.gpiod;
+		for (i = 0; i < led_dat->num_gpios; i++) {
+			led_dat->gpios[i] =
+				devm_fwnode_get_index_gpiod_from_child(dev,
+					NULL, i, child, GPIOD_ASIS, led.name);
+			if (IS_ERR(led_dat->gpios[i])) {
+				fwnode_handle_put(child);
+				return ERR_CAST(led_dat->gpios[i]);
+			}
+		}
 
 		fwnode_property_read_string(child, "linux,default-trigger",
 					    &led.default_trigger);
@@ -263,13 +365,20 @@ static int gpio_led_probe(struct platform_device *pdev)
 			const struct gpio_led *template = &pdata->leds[i];
 			struct gpio_led_data *led_dat = &priv->leds[i];
 
+			led_dat->num_gpios = 1;
+			led_dat->gpios = devm_kcalloc(&pdev->dev,
+					led_dat->num_gpios,
+					sizeof(led_dat->gpios[0]), GFP_KERNEL);
+			if (!led_dat->gpios)
+				return -ENOMEM;
+
 			if (template->gpiod)
-				led_dat->gpiod = template->gpiod;
+				led_dat->gpios[0] = template->gpiod;
 			else
-				led_dat->gpiod =
+				led_dat->gpios[0] =
 					gpio_led_get_gpiod(&pdev->dev,
 							   i, template);
-			if (IS_ERR(led_dat->gpiod)) {
+			if (IS_ERR(led_dat->gpios[0])) {
 				dev_info(&pdev->dev, "Skipping unavailable LED gpio %d (%s)\n",
 					 template->gpio, template->name);
 				continue;
-- 
2.7.4


             reply	other threads:[~2019-10-04 15:35 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-10-04 15:34 Akinobu Mita [this message]
2019-10-04 15:42 ` [PATCH] leds: gpio: support multi-level brightness Dan Murphy
2019-10-05 13:13   ` Akinobu Mita
2019-11-04  9:09     ` Pavel Machek
2019-10-04 21:17 ` Jacek Anaszewski
2019-10-05 13:20   ` Akinobu Mita
2019-10-05 19:17     ` Jacek Anaszewski
2019-10-06 14:11       ` Akinobu Mita
2019-10-08 20:53         ` Jacek Anaszewski
2019-10-09 14:43           ` Akinobu Mita
2019-10-09 18:14             ` Jacek Anaszewski
2019-10-06  4:06 ` Bjorn Andersson
2019-10-06 14:13   ` Akinobu Mita

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1570203299-4270-1-git-send-email-akinobu.mita@gmail.com \
    --to=akinobu.mita@gmail.com \
    --cc=bgolaszewski@baylibre.com \
    --cc=dmurphy@ti.com \
    --cc=jacek.anaszewski@gmail.com \
    --cc=linus.walleij@linaro.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=pavel@ucw.cz \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).