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
Cc: Nicolas Belin <nbelin@baylibre.com>
Subject: [PATCH 3/3] drivers: leds: add support for apa102c leds
Date: Tue, 18 Feb 2020 10:37:37 +0100	[thread overview]
Message-ID: <1582018657-5720-4-git-send-email-nbelin@baylibre.com> (raw)
In-Reply-To: <1582018657-5720-1-git-send-email-nbelin@baylibre.com>

Initilial commit in order to support the apa102c RGB leds.

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

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index d82f1dea3711..4fafeaaf6ee8 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 LEDS_CLASS
+	depends on SPI
+	help
+	  This option enables support for the Shiji Lighthing APA102C RGB full color
+	  LEDs.
+
+	  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..ab17f90347cb 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_LEDS_TRIGGERS)		+= led-triggers.o
 # LED Platform Drivers
 obj-$(CONFIG_LEDS_88PM860X)		+= leds-88pm860x.o
 obj-$(CONFIG_LEDS_AAT1290)		+= leds-aat1290.o
+obj-$(CONFIG_LEDS_APA102C)		+= leds-apa102c.o
 obj-$(CONFIG_LEDS_APU)			+= leds-apu.o
 obj-$(CONFIG_LEDS_AS3645A)		+= leds-as3645a.o
 obj-$(CONFIG_LEDS_AN30259A)		+= leds-an30259a.o
diff --git a/drivers/leds/leds-apa102c.c b/drivers/leds/leds-apa102c.c
new file mode 100644
index 000000000000..e7abe3f5b7c2
--- /dev/null
+++ b/drivers/leds/leds-apa102c.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (C) 2020 BayLibre, SAS
+ * Author: Nicolas Belin <nbelin@baylibre.com>
+ */
+
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <uapi/linux/uleds.h>
+
+/*
+ *  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 CR_MAX_BRIGHTNESS	GENMASK(7, 0)
+#define LM_MAX_BRIGHTNESS	GENMASK(4, 0)
+#define CH_NUM			4
+#define START_BYTE		0
+#define END_BYTE		GENMASK(7, 0)
+#define LED_FRAME_HEADER	GENMASK(7, 5)
+
+enum led_channels {
+	RED,
+	GREEN,
+	BLUE,
+	LUMA,
+};
+
+struct apa102c_led {
+	char			name[LED_MAX_NAME_SIZE];
+	struct apa102c		*priv;
+	struct led_classdev	ldev;
+	u8			brightness;
+};
+
+struct apa102c {
+	size_t			led_count;
+	struct device		*dev;
+	struct mutex		lock;
+	struct spi_device	*spi;
+	u8			*buf;
+	struct apa102c_led	leds[];
+};
+
+static int apa102c_sync(struct apa102c *priv)
+{
+	int	ret;
+	size_t	i;
+	size_t	bytes = 0;
+
+	for (i = 0; i < 4; i++)
+		priv->buf[bytes++] = START_BYTE;
+
+	for (i = 0; i < priv->led_count; i++) {
+		priv->buf[bytes++] = LED_FRAME_HEADER |
+				     priv->leds[i * CH_NUM + LUMA].brightness;
+		priv->buf[bytes++] = priv->leds[i * CH_NUM + BLUE].brightness;
+		priv->buf[bytes++] = priv->leds[i * CH_NUM + GREEN].brightness;
+		priv->buf[bytes++] = priv->leds[i * CH_NUM + RED].brightness;
+	}
+
+	for (i = 0; i < 4; i++)
+		priv->buf[bytes++] = END_BYTE;
+
+	ret = spi_write(priv->spi, priv->buf, bytes);
+
+	return ret;
+}
+
+static int apa102c_set_sync(struct led_classdev *ldev,
+			   enum led_brightness brightness)
+{
+	int			ret;
+	struct apa102c_led	*led = container_of(ldev,
+						    struct apa102c_led,
+						    ldev);
+
+	dev_dbg(led->priv->dev, "Set brightness of %s to %d\n",
+		led->name, brightness);
+
+	mutex_lock(&led->priv->lock);
+	led->brightness = (u8)brightness;
+	ret = apa102c_sync(led->priv);
+	mutex_unlock(&led->priv->lock);
+
+	return ret;
+}
+
+static int apa102c_probe_dt(struct apa102c *priv)
+{
+	u32			i = 0;
+	int			j = 0;
+	struct apa102c_led	*led;
+	struct fwnode_handle	*child;
+	struct device_node	*np;
+	int			ret;
+	int			use_index;
+	const char		*str;
+	static const char	* const rgb_name[] = {"red",
+						      "green",
+						      "blue",
+						      "luma"};
+
+	device_for_each_child_node(priv->dev, child) {
+		np = to_of_node(child);
+
+		ret = fwnode_property_read_u32(child, "reg", &i);
+		if (ret)
+			return ret;
+
+		if (i >= priv->led_count)
+			return -EINVAL;
+
+		/* use the index to create the name if the label is not set */
+		use_index = fwnode_property_read_string(child, "label", &str);
+
+		/* for each physical LED, 4 LEDs are created representing
+		 * the 4 components: red, green, blue and global luma.
+		 */
+		for (j = 0; j < CH_NUM; j++) {
+			led = &priv->leds[i * CH_NUM + j];
+
+			if (use_index)
+				snprintf(led->name, sizeof(led->name),
+					 "apa102c:%s:%d", rgb_name[j], i);
+			else
+				snprintf(led->name, sizeof(led->name),
+					 "apa102c:%s:%s", rgb_name[j], str);
+
+			fwnode_property_read_string(child,
+						    "linux,default-trigger",
+						    &led->ldev.default_trigger);
+
+			led->priv			 = priv;
+			led->ldev.name			 = led->name;
+			if (j == LUMA) {
+				led->ldev.brightness	 = led->brightness
+							 = LM_MAX_BRIGHTNESS;
+				led->ldev.max_brightness = LM_MAX_BRIGHTNESS;
+			} else {
+				led->ldev.brightness	 = led->brightness
+							 = 0;
+				led->ldev.max_brightness = CR_MAX_BRIGHTNESS;
+			}
+
+			led->ldev.brightness_set_blocking = apa102c_set_sync;
+
+			ret = devm_led_classdev_register(priv->dev, &led->ldev);
+			if (ret) {
+				dev_err(priv->dev,
+					"failed to register LED %s, err %d",
+					led->name, ret);
+				fwnode_handle_put(child);
+				return ret;
+			}
+
+			led->ldev.dev->of_node = np;
+
+		}
+	}
+
+	return 0;
+}
+
+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, leds, led_count * CH_NUM),
+			    GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->buf = devm_kzalloc(&spi->dev, led_count * CH_NUM + 8, GFP_KERNEL);
+	if (!priv->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-02-18  9:38 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-18  9:37 [PATCH 0/3] leds: add support for apa102c leds Nicolas Belin
2020-02-18  9:37 ` [PATCH 1/3] dt-bindings: Document shiji vendor-prefix Nicolas Belin
2020-02-18 12:40   ` Dan Murphy
2020-02-18  9:37 ` [PATCH 2/3] dt-bindings: leds: Shiji Lighting APA102C LED driver Nicolas Belin
2020-02-18 20:28   ` Jacek Anaszewski
2020-02-20 10:30   ` Geert Uytterhoeven
2020-02-20 19:41     ` Jacek Anaszewski
2020-02-18  9:37 ` Nicolas Belin [this message]
2020-02-18 21:13   ` [PATCH 3/3] drivers: leds: add support for apa102c leds Jacek Anaszewski
2020-02-20 10:25     ` Nicolas Belin
2020-02-18 12:43 ` [PATCH 0/3] " Dan Murphy
2020-02-18 21:26   ` Jacek Anaszewski
2020-02-20 10:19   ` Nicolas Belin
2020-02-26 14:19     ` 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=1582018657-5720-4-git-send-email-nbelin@baylibre.com \
    --to=nbelin@baylibre.com \
    --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.