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 RFC v2 3/3] drivers: leds: add support for apa102c leds
Date: Wed, 26 Feb 2020 15:33:12 +0100	[thread overview]
Message-ID: <1582727592-4510-4-git-send-email-nbelin@baylibre.com> (raw)
In-Reply-To: <1582727592-4510-1-git-send-email-nbelin@baylibre.com>

Initilial commit in order to support the apa102c RGB leds. This
is based on the Multicolor Framework.

Reviewed-by: Corentin Labbe <clabbe@baylibre.com>
Signed-off-by: Nicolas Belin <nbelin@baylibre.com>
---
 drivers/leds/Kconfig        |   7 ++
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-apa102c.c | 291 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 299 insertions(+)
 create mode 100644 drivers/leds/leds-apa102c.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 5dc6535a88ef..71e29727c6ec 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -79,6 +79,13 @@ 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_MULTI_COLOR
+	help
+	  This option enables support for APA102C LEDs.
+
 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 b5305b7d43fb..8334cb6dc7e8 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -90,6 +90,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..b46db0db7501
--- /dev/null
+++ b/drivers/leds/leds-apa102c.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <linux/led-class-multicolor.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 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)
+
+#define IS_RGB	(1<<LED_COLOR_ID_RED \
+		| 1<<LED_COLOR_ID_GREEN \
+		| 1<<LED_COLOR_ID_BLUE)
+
+#define APA_DEV_NAME		"apa102c"
+
+struct apa102c_led {
+	struct apa102c		*priv;
+	struct led_classdev	ldev;
+	struct led_classdev_mc	mc_cdev;
+};
+
+struct apa102c {
+	size_t			led_count;
+	struct device		*dev;
+	struct mutex		lock;
+	struct spi_device	*spi;
+	u8			*buf;
+	struct apa102c_led	leds[];
+};
+
+static void apa102c_set_rgb_bytes(u8 *bgr_buf, struct list_head *color_list)
+{
+	struct led_mc_color_entry *color_data;
+
+	/* Looking for the RGB values and putting them in the buffer in
+	 * BGR order
+	 */
+	list_for_each_entry(color_data, color_list, list) {
+		switch (color_data->led_color_id) {
+		case LED_COLOR_ID_RED:
+			bgr_buf[2] = color_data->intensity;
+			break;
+		case LED_COLOR_ID_GREEN:
+			bgr_buf[1] = color_data->intensity;
+			break;
+		case LED_COLOR_ID_BLUE:
+			bgr_buf[0] = color_data->intensity;
+			break;
+		}
+	}
+}
+
+static int apa102c_sync(struct apa102c *priv)
+{
+	int	ret;
+	size_t	i;
+	struct apa102c_led *leds = priv->leds;
+	u8	*buf = priv->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 multicolor led getting the luma and the RGB values */
+	for (i = 0; i < priv->led_count; i++) {
+		*buf++ = LED_FRAME_HEADER | leds[i].ldev.brightness;
+		apa102c_set_rgb_bytes(buf, &leds[i].mc_cdev.color_list);
+		buf += 3;
+	}
+
+	for (i = 0; i < 4; i++)
+		*buf++ = END_BYTE;
+
+	ret = spi_write(priv->spi, priv->buf, priv->led_count*4 + 8);
+
+	return ret;
+}
+
+static int apa102c_brightness_set(struct led_classdev *ldev,
+			   enum led_brightness brightness)
+{
+	int			ret;
+	struct apa102c_led	*led = container_of(ldev,
+						    struct apa102c_led,
+						    ldev);
+
+	mutex_lock(&led->priv->lock);
+	/* updating the brightness then synching all the leds */
+	ldev->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;
+	int			num_colors;
+	u32			color_id, i;
+	struct apa102c_led	*led;
+	struct fwnode_handle	*child, *grandchild;
+	struct led_init_data	init_data;
+
+	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;
+		}
+
+		led = &priv->leds[i];
+		/* checking if this led config is already used */
+		if (led->mc_cdev.led_cdev) {
+			ret = -EINVAL;
+			dev_err(priv->dev, "using the same reg value twice\n");
+			goto child_out;
+		}
+
+		led->priv			= priv;
+		led->ldev.max_brightness	= MAX_BRIGHTNESS;
+		fwnode_property_read_string(child, "linux,default-trigger",
+					    &led->ldev.default_trigger);
+
+		init_data.fwnode = child;
+		init_data.devicename = APA_DEV_NAME;
+		init_data.default_label = ":";
+
+		num_colors = 0;
+		fwnode_for_each_child_node(child, grandchild) {
+			ret = fwnode_property_read_u32(grandchild, "color",
+						       &color_id);
+			if (ret) {
+				dev_err(priv->dev, "Cannot read color\n");
+				goto child_out;
+			}
+
+			set_bit(color_id, &led->mc_cdev.available_colors);
+			num_colors++;
+		}
+
+		if (num_colors != 3) {
+			ret = -EINVAL;
+			dev_err(priv->dev, "There should be 3 colors\n");
+			goto child_out;
+		}
+
+		if (led->mc_cdev.available_colors != IS_RGB) {
+			ret = -EINVAL;
+			dev_err(priv->dev, "The led is expected to be RGB\n");
+			goto child_out;
+		}
+
+		led->mc_cdev.num_leds = num_colors;
+		led->mc_cdev.led_cdev = &led->ldev;
+		led->ldev.brightness_set_blocking = apa102c_brightness_set;
+		ret = devm_led_classdev_multicolor_register_ext(priv->dev,
+								&led->mc_cdev,
+								&init_data);
+		if (ret) {
+			dev_err(priv->dev, "led register err: %d\n", ret);
+			fwnode_handle_put(child);
+			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, leds, led_count),
+			    GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->buf = devm_kzalloc(&spi->dev, (led_count + 2) * 4, 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-26 14:33 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-26 14:33 [PATCH RFC v2 0/3] leds: add support for apa102c leds Nicolas Belin
2020-02-26 14:33 ` [PATCH RFC v2 1/3] dt-bindings: Document shiji vendor-prefix Nicolas Belin
2020-03-03 14:29   ` Rob Herring
2020-02-26 14:33 ` [PATCH RFC v2 2/3] dt-bindings: leds: Shiji Lighting APA102C LED driver Nicolas Belin
2020-02-26 21:56   ` Rob Herring
2020-02-26 14:33 ` Nicolas Belin [this message]
2020-02-26 20:13   ` [PATCH RFC v2 3/3] drivers: leds: add support for apa102c leds Jacek Anaszewski
2020-03-03 10:30     ` Nicolas Belin
2020-03-03 19:37       ` Jacek Anaszewski

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=1582727592-4510-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.