All of lore.kernel.org
 help / color / mirror / Atom feed
From: Nicolas Belin <nbelin@baylibre.com>
To: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org,
	jacek.anaszewski@gmail.com, pavel@ucw.cz, dmurphy@ti.com,
	devicetree@vger.kernel.org
Cc: baylibre-upstreaming@groups.io, Nicolas Belin <nbelin@baylibre.com>
Subject: [PATCH v3 3/3] drivers: leds: add support for apa102c leds
Date: Fri,  6 Mar 2020 14:40:10 +0100	[thread overview]
Message-ID: <1583502010-16210-4-git-send-email-nbelin@baylibre.com> (raw)
In-Reply-To: <1583502010-16210-1-git-send-email-nbelin@baylibre.com>

Initilial commit in order to support the apa102c RGB leds. The
RGB and global brightness management is done by creating 4 leds
from the Led Framework per apa102c led.

Signed-off-by: Nicolas Belin <nbelin@baylibre.com>
---
 drivers/leds/Kconfig        |  11 ++
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-apa102c.c | 306 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 318 insertions(+)
 create mode 100644 drivers/leds/leds-apa102c.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index d82f1dea3711..28fa6c4f65cc 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -69,6 +69,17 @@ config LEDS_AN30259A
 	  To compile this driver as a module, choose M here: the module
 	  will be called leds-an30259a.
 
+config LEDS_APA102C
+	tristate "LED Support for Shiji APA102C"
+	depends on SPI
+	depends on LEDS_CLASS
+	help
+	  This option enables support for the APA102C RGB LEDs
+	  from Shiji Lighting.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called leds-apa102c.
+
 config LEDS_APU
 	tristate "Front panel LED support for PC Engines APU/APU2/APU3 boards"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index d7e1107753fb..28dfe82900c5 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -88,6 +88,7 @@ obj-$(CONFIG_LEDS_LM36274)		+= leds-lm36274.o
 obj-$(CONFIG_LEDS_TPS6105X)		+= leds-tps6105x.o
 
 # LED SPI Drivers
+obj-$(CONFIG_LEDS_APA102C)		+= leds-apa102c.o
 obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o
 obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
 obj-$(CONFIG_LEDS_EL15203000)		+= leds-el15203000.o
diff --git a/drivers/leds/leds-apa102c.c b/drivers/leds/leds-apa102c.c
new file mode 100644
index 000000000000..0043e7a6235b
--- /dev/null
+++ b/drivers/leds/leds-apa102c.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <uapi/linux/uleds.h>
+#include "leds.h"
+
+/*
+ * Copyright (C) 2020 BayLibre, SAS
+ * Author: Nicolas Belin <nbelin@baylibre.com>
+ */
+
+/*
+ *  APA102C SPI protocol description:
+ *  +------+----------------------------------------+------+
+ *  |START |               DATA FIELD:              | END  |
+ *  |FRAME |               N LED FRAMES             |FRAME |
+ *  +------+------+------+------+------+-----+------+------+
+ *  | 0*32 | LED1 | LED2 | LED3 | LED4 | --- | LEDN | 1*32 |
+ *  +------+------+------+------+------+-----+------+------+
+ *
+ *  +-----------------------------------+
+ *  |START FRAME 32bits                 |
+ *  +--------+--------+--------+--------+
+ *  |00000000|00000000|00000000|00000000|
+ *  +--------+--------+--------+--------+
+ *
+ *  +------------------------------------+
+ *  |LED  FRAME 32bits                   |
+ *  +---+-----+--------+--------+--------+
+ *  |111|LUMA |  BLUE  | GREEN  |  RED   |
+ *  +---+-----+--------+--------+--------+
+ *  |3b |5bits| 8bits  | 8bits  | 8bits  |
+ *  +---+-----+--------+--------+--------+
+ *  |MSB   LSB|MSB  LSB|MSB  LSB|MSB  LSB|
+ *  +---+-----+--------+--------+--------+
+ *
+ *  +-----------------------------------+
+ *  |END FRAME 32bits                   |
+ *  +--------+--------+--------+--------+
+ *  |11111111|11111111|11111111|11111111|
+ *  +--------+--------+--------+--------+
+ */
+
+/* apa102c default settings */
+#define GLOBAL_MAX_BRIGHTNESS	GENMASK(4, 0)
+#define RGB_MAX_BRIGHTNESS	GENMASK(7, 0)
+#define START_BYTE		0
+#define END_BYTE		GENMASK(7, 0)
+#define LED_FRAME_HEADER	GENMASK(7, 5)
+
+struct apa102c_led {
+	struct apa102c		*priv;
+	char			name[LED_MAX_NAME_SIZE];
+	int			color_id;
+	struct led_classdev	cdev;
+};
+
+struct apa102c_rgbled {
+	/* the 4 components of the apa102c rgb led:
+	 * global brightness, blue, green and red in that order
+	 */
+	struct apa102c_led	component[4];
+};
+
+struct apa102c {
+	size_t			led_count;
+	struct device		*dev;
+	struct mutex		lock;
+	struct spi_device	*spi;
+	u8			*spi_buf;
+	struct apa102c_rgbled	rgb_leds[];
+};
+
+static int apa102c_sync(struct apa102c *priv)
+{
+	int	ret;
+	size_t	i;
+	struct apa102c_rgbled *leds = priv->rgb_leds;
+	u8	*buf = priv->spi_buf;
+
+	/* creating the data that will be sent to all the leds at once */
+	for (i = 0; i < 4; i++)
+		*buf++ = START_BYTE;
+
+	/* looping on each RGB led component, getting the global brightness
+	 * then B, G and R values
+	 */
+	for (i = 0; i < priv->led_count; i++) {
+		*buf++ = LED_FRAME_HEADER |
+			 leds[i].component[0].cdev.brightness;
+		*buf++ = leds[i].component[1].cdev.brightness;
+		*buf++ = leds[i].component[2].cdev.brightness;
+		*buf++ = leds[i].component[3].cdev.brightness;
+	}
+
+	for (i = 0; i < 4; i++)
+		*buf++ = END_BYTE;
+
+	ret = spi_write(priv->spi, priv->spi_buf,
+			(priv->led_count + 2) * sizeof(u32));
+
+	return ret;
+}
+
+static int apa102c_brightness_set(struct led_classdev *cdev,
+			   enum led_brightness brightness)
+{
+	int			ret;
+	struct apa102c_led	*led = container_of(cdev,
+						    struct apa102c_led,
+						    cdev);
+
+	mutex_lock(&led->priv->lock);
+	/* updating the brightness then synching all the leds */
+	cdev->brightness = brightness;
+	ret = apa102c_sync(led->priv);
+	mutex_unlock(&led->priv->lock);
+
+	return ret;
+}
+
+static int apa102c_probe_dt(struct apa102c *priv)
+{
+	int			ret = 0;
+	u32			i;
+	struct apa102c_rgbled	*rgb_led;
+	struct apa102c_led	*led;
+	struct fwnode_handle	*child;
+	const char		*rgb_led_name;
+	const char		*led_component_name;
+
+	/* each node corresponds to an rgb led */
+	device_for_each_child_node(priv->dev, child) {
+
+		ret = fwnode_property_read_u32(child, "reg", &i);
+		if (ret) {
+			dev_err(priv->dev, "coudld not read reg %d\n", ret);
+			goto child_out;
+		}
+
+		if (i >= priv->led_count) {
+			ret = -EINVAL;
+			dev_err(priv->dev, "reg value too big\n");
+			goto child_out;
+		}
+
+		rgb_led = &priv->rgb_leds[i];
+		/* checking if this led config is already used by checking if
+		 * the priv member of the global_brightness led as already
+		 * been set
+		 */
+		if (rgb_led->component[0].priv) {
+			ret = -EINVAL;
+			dev_err(priv->dev, "using the same reg value twice\n");
+			goto child_out;
+		}
+
+		ret = fwnode_property_read_string(child, "label",
+						  &rgb_led_name);
+		if (ret) {
+			ret = -EINVAL;
+			dev_err(priv->dev, "missing rgb led name\n");
+			goto child_out;
+		}
+
+		/* setting a color_id value for each of the 4 components of the
+		 * apa102c RGB led. The first component is the global brightness
+		 * of the led and thus has no color. The order of the colors
+		 * after the global brightness is then blue, green and red
+		 * in that order. It corresponds to the order in which the
+		 * values are sent using spi
+		 */
+		rgb_led->component[0].color_id = -1; //no color
+		rgb_led->component[1].color_id = LED_COLOR_ID_BLUE;
+		rgb_led->component[2].color_id = LED_COLOR_ID_GREEN;
+		rgb_led->component[3].color_id = LED_COLOR_ID_RED;
+
+		/* now looping on each component and registering a corresponding
+		 * led
+		 */
+		for (i = 0; i < 4; i++) {
+			led = &rgb_led->component[i];
+			if (led->color_id == -1) {
+				/* the global brightness has no defined name
+				 * so setting it to "brightness". It also has
+				 * a different MAX_BRIGHTNESS value.
+				 * If a trigger is present, setting it on this
+				 * component
+				 */
+				led->cdev.max_brightness =
+					GLOBAL_MAX_BRIGHTNESS;
+				led_component_name = "brightness";
+				fwnode_property_read_string(child,
+					"linux,default-trigger",
+					&led->cdev.default_trigger);
+			} else {
+				/* using the color name defined by the framework
+				 * for the B, G and R components
+				 */
+				led->cdev.max_brightness = RGB_MAX_BRIGHTNESS;
+				led_component_name = led_colors[led->color_id];
+			}
+
+			/* the rest is common to all the components */
+			led->priv = priv;
+			led->cdev.brightness_set_blocking =
+				apa102c_brightness_set;
+			/* creating our own led name to differentiate the 4
+			 * components
+			 */
+			snprintf(led->name, sizeof(led->name),
+				 "%s_%s", rgb_led_name, led_component_name);
+			led->cdev.name = led->name;
+
+			ret = devm_led_classdev_register_ext(priv->dev,
+							     &led->cdev,
+							     NULL);
+			if (ret) {
+				dev_err(priv->dev, "led register err: %d\n",
+					ret);
+				goto child_out;
+			}
+		}
+	}
+
+child_out:
+	return ret;
+}
+
+static int apa102c_probe(struct spi_device *spi)
+{
+	struct apa102c	*priv;
+	size_t		led_count;
+	int		ret;
+
+	led_count = device_get_child_node_count(&spi->dev);
+	if (!led_count) {
+		dev_err(&spi->dev, "No LEDs defined in device tree!");
+		return -ENODEV;
+	}
+
+	priv = devm_kzalloc(&spi->dev,
+			    struct_size(priv, rgb_leds, led_count),
+			    GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->spi_buf = devm_kzalloc(&spi->dev,
+				     (led_count + 2) * sizeof(u32),
+				      GFP_KERNEL);
+	if (!priv->spi_buf)
+		return -ENOMEM;
+
+	mutex_init(&priv->lock);
+	priv->led_count	= led_count;
+	priv->dev	= &spi->dev;
+	priv->spi	= spi;
+
+	ret = apa102c_probe_dt(priv);
+	if (ret)
+		return ret;
+
+	/* Set the LEDs with default values at start */
+	apa102c_sync(priv);
+	if (ret)
+		return ret;
+
+	spi_set_drvdata(spi, priv);
+
+	return 0;
+}
+
+static int apa102c_remove(struct spi_device *spi)
+{
+	struct apa102c *priv = spi_get_drvdata(spi);
+
+	mutex_destroy(&priv->lock);
+
+	return 0;
+}
+
+static const struct of_device_id apa102c_dt_ids[] = {
+	{ .compatible = "shiji,apa102c", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, apa102c_dt_ids);
+
+static struct spi_driver apa102c_driver = {
+	.probe		= apa102c_probe,
+	.remove		= apa102c_remove,
+	.driver = {
+		.name		= KBUILD_MODNAME,
+		.of_match_table	= apa102c_dt_ids,
+	},
+};
+
+module_spi_driver(apa102c_driver);
+
+MODULE_AUTHOR("Nicolas Belin <nbelin@baylibre.com>");
+MODULE_DESCRIPTION("apa102c LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("spi:apa102c");
-- 
2.7.4


  parent reply	other threads:[~2020-03-06 13:41 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-03-06 13:40 [PATCH v3 0/3] leds: add support for apa102c leds Nicolas Belin
2020-03-06 13:40 ` [PATCH v3 1/3] dt-bindings: Document shiji vendor-prefix Nicolas Belin
2020-03-09 21:05   ` Rob Herring
2020-03-06 13:40 ` [PATCH v3 2/3] dt-bindings: leds: Shiji Lighting APA102C LED Nicolas Belin
2020-03-06 20:19   ` Jacek Anaszewski
2020-03-13 13:06   ` Rob Herring
2020-03-06 13:40 ` Nicolas Belin [this message]
2020-03-06 20:20   ` [PATCH v3 3/3] drivers: leds: add support for apa102c leds Jacek Anaszewski
2020-03-09  9:59     ` Nicolas Belin
2020-03-24  9:42   ` Pavel Machek

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=1583502010-16210-4-git-send-email-nbelin@baylibre.com \
    --to=nbelin@baylibre.com \
    --cc=baylibre-upstreaming@groups.io \
    --cc=devicetree@vger.kernel.org \
    --cc=dmurphy@ti.com \
    --cc=jacek.anaszewski@gmail.com \
    --cc=linux-kernel@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.