All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kyle Swenson <kyle.swenson@est.tech>
To: pavel@ucw.cz, robh+dt@kernel.org, krzk+dt@kernel.org
Cc: linux-leds@vger.kernel.org, devicetree@vger.kernel.org,
	Kyle Swenson <kyle.swenson@est.tech>
Subject: [PATCH 1/2] leds: aw21024: Add support for Awinic's AW21024
Date: Fri, 13 May 2022 13:04:08 -0600	[thread overview]
Message-ID: <20220513190409.3682501-1-kyle.swenson@est.tech> (raw)

The Awinic AW21024 LED controller is a 24-channel RGB LED controller.
Each LED on the controller can be controlled individually or grouped
with other LEDs on the controller to form a multi-color LED.  Arbitrary
combinations of individual and grouped LED control should be possible.

Signed-off-by: Kyle Swenson <kyle.swenson@est.tech>
---
 drivers/leds/Kconfig        |  11 ++
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-aw21024.c | 314 ++++++++++++++++++++++++++++++++++++
 3 files changed, 326 insertions(+)
 create mode 100644 drivers/leds/leds-aw21024.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index a49979f41eee..c813d7c16ff8 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -102,10 +102,21 @@ config LEDS_AW2013
 	  LED driver.
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called leds-aw2013.
 
+config LEDS_AW21024
+	tristate "LED Support for Awinic AW21024"
+	depends on LEDS_CLASS
+	depends on LEDS_CLASS_MULTICOLOR || !LEDS_CLASS_MULTICOLOR
+	help
+	  If you say yes here you get support for Awinic's AW21024, a 24-channel
+	  RGB LED Driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called leds-aw21024.
+
 config LEDS_BCM6328
 	tristate "LED Support for Broadcom BCM6328"
 	depends on LEDS_CLASS
 	depends on HAS_IOMEM
 	depends on OF
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 4fd2f92cd198..09a0e3cb21cb 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -14,10 +14,11 @@ obj-$(CONFIG_LEDS_ADP5520)		+= leds-adp5520.o
 obj-$(CONFIG_LEDS_AN30259A)		+= leds-an30259a.o
 obj-$(CONFIG_LEDS_APU)			+= leds-apu.o
 obj-$(CONFIG_LEDS_ARIEL)		+= leds-ariel.o
 obj-$(CONFIG_LEDS_ASIC3)		+= leds-asic3.o
 obj-$(CONFIG_LEDS_AW2013)		+= leds-aw2013.o
+obj-$(CONFIG_LEDS_AW21024)		+= leds-aw21024.o
 obj-$(CONFIG_LEDS_BCM6328)		+= leds-bcm6328.o
 obj-$(CONFIG_LEDS_BCM6358)		+= leds-bcm6358.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_BLINKM)		+= leds-blinkm.o
 obj-$(CONFIG_LEDS_CLEVO_MAIL)		+= leds-clevo-mail.o
diff --git a/drivers/leds/leds-aw21024.c b/drivers/leds/leds-aw21024.c
new file mode 100644
index 000000000000..4eebc3de1a8a
--- /dev/null
+++ b/drivers/leds/leds-aw21024.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+// Awinic AW21024 LED chip driver
+// Copyright (C) 2022 Nordix Foundation https://www.nordix.org
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <uapi/linux/uleds.h>
+
+#include <linux/led-class-multicolor.h>
+
+/* Called COL0, COL1,..., COL23 in datasheet */
+#define AW21024_REG_DC_CURRENT(_led)	(0x4a + (_led))
+
+/* Called BR0, BR1,..., BR23 in datasheet */
+#define AW21024_REG_BRIGHTNESS(_led)	(0x01 + (_led))
+
+#define AW21024_REG_UPDATE				0x49 /* Write 0x00 to update BR */
+
+#define AW21024_REG_GCR0				0x00 /* Global configuration register */
+#define AW21024_REG_GCC					0x6e /* Global current control */
+#define AW21024_REG_SW_RESET			0x7f
+#define AW21024_REG_VERSION				0x7e
+
+#define AW21024_GCR0_CHIPEN				BIT(0)
+#define AW21024_CHIP_ID					0x18
+#define AW21024_CHIP_VERSION			0xA8
+
+struct aw21024_led_data {
+	struct led_classdev_mc mc_cdev;
+	struct work_struct work;
+	unsigned int *regs;
+	unsigned int nregs;
+	struct aw21024 *parent;
+};
+
+struct aw21024 {
+	struct i2c_client *client;
+	struct device *dev;
+	struct gpio_desc *enable_gpio;
+	struct mutex lock;
+	struct aw21024_led_data **leds;
+	unsigned int nleds;
+};
+
+static int aw21024_led_brightness_set(struct led_classdev *led_cdev,
+					enum led_brightness brightness)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
+	struct aw21024_led_data *led = container_of(mc_cdev, struct aw21024_led_data, mc_cdev);
+	struct aw21024 *parent = led->parent;
+	int i;
+	int ret = 0;
+
+	mutex_lock(&parent->lock);
+	if (mc_cdev->num_colors && mc_cdev->subled_info) {
+		for (i = 0; i < led->nregs; i++) {
+			ret = i2c_smbus_write_byte_data(parent->client,
+					AW21024_REG_DC_CURRENT(led->regs[i]),
+					mc_cdev->subled_info[i].intensity);
+			if (ret < 0)
+				goto unlock_ret;
+
+			ret = i2c_smbus_write_byte_data(parent->client,
+					AW21024_REG_BRIGHTNESS(led->regs[i]),
+					brightness);
+			if (ret < 0)
+				goto unlock_ret;
+		}
+	} else {
+		ret = i2c_smbus_write_byte_data(parent->client,
+					AW21024_REG_DC_CURRENT(led->regs[0]), 0xFF);
+		if (ret < 0)
+			goto unlock_ret;
+
+		ret = i2c_smbus_write_byte_data(parent->client,
+					AW21024_REG_BRIGHTNESS(led->regs[0]),
+					brightness);
+		if (ret < 0)
+			goto unlock_ret;
+	}
+	ret = i2c_smbus_write_byte_data(parent->client, AW21024_REG_UPDATE, 0x0);
+unlock_ret:
+	mutex_unlock(&parent->lock);
+	return ret;
+}
+
+static int aw21024_probe_dt(struct aw21024 *data)
+{
+	struct device *dev = &data->client->dev;
+	struct fwnode_handle *child = NULL;
+	struct fwnode_handle *led_node = NULL;
+	struct led_init_data init_data = {};
+	u32 color_id;
+	int  ret, num_colors;
+	unsigned int nleds = 0;
+	struct aw21024_led_data *led;
+	struct led_classdev *led_cdev;
+	struct mc_subled *mc_led_info;
+
+	nleds = device_get_child_node_count(dev);
+
+	data->leds = devm_kcalloc(dev, nleds, sizeof(*(data->leds)), GFP_KERNEL);
+	if (!data->leds)
+		return -ENOMEM;
+
+	device_for_each_child_node(dev, child) {
+		led = devm_kzalloc(dev, sizeof(struct aw21024_led_data), GFP_KERNEL);
+		if (!led) {
+			ret = -ENOMEM;
+			goto ret_put_child;
+		}
+		led->parent = data;
+		led_cdev = &led->mc_cdev.led_cdev;
+		init_data.fwnode = child;
+
+		led_cdev->brightness_set_blocking = aw21024_led_brightness_set;
+		data->leds[data->nleds] = led;
+
+		ret = fwnode_property_count_u32(child, "reg");
+		if (ret < 0) {
+			dev_err(dev, "reg property is invalid in node %s\n",
+					fwnode_get_name(child));
+			goto ret_put_child;
+		}
+
+		led->regs = devm_kcalloc(dev, ret, sizeof(*(led->regs)), GFP_KERNEL);
+		led->nregs = ret;
+		if (!led->regs) {
+			ret = -ENOMEM;
+			goto ret_put_child;
+		}
+
+		ret = fwnode_property_read_u32_array(child, "reg", led->regs, led->nregs);
+		if (ret) {
+			dev_err(dev, "Failed to read reg array, error=%d\n", ret);
+			goto ret_put_child;
+		}
+
+		if (led->nregs > 1) {
+			mc_led_info = devm_kcalloc(dev, led->nregs,
+						    sizeof(*mc_led_info), GFP_KERNEL);
+			if (!mc_led_info) {
+				ret = -ENOMEM;
+				goto ret_put_child;
+			}
+
+			num_colors = 0;
+			fwnode_for_each_child_node(child, led_node) {
+				if (num_colors > led->nregs) {
+					ret = -EINVAL;
+					fwnode_handle_put(led_node);
+					goto ret_put_child;
+				}
+
+				ret = fwnode_property_read_u32(led_node, "color",
+							       &color_id);
+				if (ret) {
+					fwnode_handle_put(led_node);
+					goto ret_put_child;
+				}
+				mc_led_info[num_colors].color_index = color_id;
+				num_colors++;
+			}
+
+			led->mc_cdev.num_colors = num_colors;
+			led->mc_cdev.subled_info = mc_led_info;
+			ret = devm_led_classdev_multicolor_register_ext(dev,
+								&led->mc_cdev,
+								&init_data);
+			if (ret < 0) {
+				dev_warn(dev, "Failed to register multicolor LED %s, err=%d\n",
+						    fwnode_get_name(child), ret);
+				goto ret_put_child;
+			}
+		} else {
+			ret = devm_led_classdev_register_ext(dev,
+							    &led->mc_cdev.led_cdev, &init_data);
+			if (ret < 0) {
+				dev_warn(dev, "Failed to register LED %s, err=%d\n",
+						fwnode_get_name(child), ret);
+				goto ret_put_child;
+			}
+		}
+		data->nleds++;
+	}
+
+	return 0;
+
+ret_put_child:
+	fwnode_handle_put(child);
+	return ret;
+}
+
+/* Expected to be called prior to registering with the LEDs class */
+static int aw21024_configure(struct aw21024 *priv)
+{
+	int ret = 0;
+	struct i2c_client *client = priv->client;
+
+	ret = i2c_smbus_write_byte_data(client, AW21024_REG_GCR0, AW21024_GCR0_CHIPEN);
+	if (ret < 0) {
+		dev_err(&client->dev, "Failed to write chip enable\n");
+		return -ENODEV;
+	}
+
+	ret = i2c_smbus_read_byte_data(client, AW21024_REG_SW_RESET);
+	if (ret < 0) {
+		dev_err(&client->dev, "Failed to read chip id\n");
+		return -ENODEV;
+	}
+
+	if (ret != AW21024_CHIP_ID) {
+		dev_err(&client->dev, "Chip ID 0x%02X doesn't match expected (0x%02X)\n",
+								ret, AW21024_CHIP_ID);
+		return -ENODEV;
+	}
+
+	ret = i2c_smbus_read_byte_data(client, AW21024_REG_VERSION);
+	if (ret < 0) {
+		dev_err(&client->dev, "Failed to read chip version\n");
+		return -ENODEV;
+	}
+	if (ret != AW21024_CHIP_VERSION)
+		dev_warn(&client->dev, "Chip version 0x%02X doesn't match expected 0x%02X\n",
+								ret, AW21024_CHIP_VERSION);
+
+	i2c_smbus_write_byte_data(client, AW21024_REG_SW_RESET, 0x00);
+	mdelay(2);
+	i2c_smbus_write_byte_data(client, AW21024_REG_GCR0, AW21024_GCR0_CHIPEN);
+	i2c_smbus_write_byte_data(client, AW21024_REG_GCC, 0xFF);
+
+	return 0;
+}
+
+static int aw21024_probe(struct i2c_client *client)
+{
+	struct aw21024 *priv;
+	int ret;
+
+	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->client = client;
+	priv->dev = &client->dev;
+
+	mutex_init(&priv->lock);
+
+	priv->enable_gpio = devm_gpiod_get_optional(priv->dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(priv->enable_gpio))
+		return dev_err_probe(priv->dev, PTR_ERR(priv->enable_gpio),
+				       "Failed to get enable GPIO\n");
+
+	if (priv->enable_gpio) {
+		mdelay(1);
+		gpiod_direction_output(priv->enable_gpio, 1);
+		mdelay(1);
+	}
+
+	i2c_set_clientdata(client, priv);
+
+	ret = aw21024_configure(priv);
+	if (ret < 0)
+		return ret;
+
+	return aw21024_probe_dt(priv);
+}
+
+static int aw21024_remove(struct i2c_client *client)
+{
+	struct aw21024 *priv = i2c_get_clientdata(client);
+	int ret;
+
+	ret = gpiod_direction_output(priv->enable_gpio, 0);
+	if (ret)
+		dev_err(priv->dev, "Failed to disable chip, err=%d\n", ret);
+
+	mutex_destroy(&priv->lock);
+	return 0;
+}
+
+static const struct i2c_device_id aw21024_id[] = {
+	{ "aw21024", 0 }, /* 24 Channel */
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, aw21024_id);
+
+static const struct of_device_id of_aw21024_leds_match[] = {
+	{ .compatible = "awinic,aw21024", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, of_aw21024_leds_match);
+
+static struct i2c_driver aw21024_driver = {
+	.driver		= {
+		.name	= "aw21024",
+		.of_match_table = of_match_ptr(of_aw21024_leds_match),
+	},
+	.probe_new		= aw21024_probe,
+	.remove		= aw21024_remove,
+	.id_table = aw21024_id,
+};
+module_i2c_driver(aw21024_driver);
+
+MODULE_AUTHOR("Kyle Swenson <kyle.swenson@est.tech>");
+MODULE_DESCRIPTION("Awinic AW21024 LED driver");
+MODULE_LICENSE("GPL");
-- 
2.36.1


             reply	other threads:[~2022-05-13 19:05 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-13 19:04 Kyle Swenson [this message]
2022-05-13 19:04 ` [PATCH 2/2] dt-bindings: leds: Add aw21024 binding Kyle Swenson
2022-05-13 20:48   ` Rob Herring
2022-05-17  9:08   ` Krzysztof Kozlowski
2022-05-17 18:31     ` Kyle Swenson
2022-05-18  8:17       ` Krzysztof Kozlowski
2022-05-18 21:18         ` Kyle Swenson
2022-05-19  8:04           ` Krzysztof Kozlowski
2022-05-16 10:21 ` [PATCH 1/2] leds: aw21024: Add support for Awinic's AW21024 kernel test robot
2022-05-16 10:42 ` kernel test robot
2022-05-16 12:34 ` kernel test robot
2022-05-17  9:11 ` Krzysztof Kozlowski
2022-05-17 18:36   ` Kyle Swenson
2022-05-18  8:30     ` Krzysztof Kozlowski

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=20220513190409.3682501-1-kyle.swenson@est.tech \
    --to=kyle.swenson@est.tech \
    --cc=devicetree@vger.kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=pavel@ucw.cz \
    --cc=robh+dt@kernel.org \
    /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.