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@xxxxxxxx> --- 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@xxxxxxxx>"); +MODULE_DESCRIPTION("Awinic AW21024 LED driver"); +MODULE_LICENSE("GPL"); -- 2.36.1