@@ -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
@@ -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
new file mode 100644
@@ -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");
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