Skip to content

Commit

Permalink
leds: aw21024: Add support for Awinic's AW21024
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
kyle-swenson-est authored and intel-lab-lkp committed May 13, 2022
1 parent 73bce57 commit 38eeda6
Show file tree
Hide file tree
Showing 3 changed files with 326 additions and 0 deletions.
11 changes: 11 additions & 0 deletions drivers/leds/Kconfig
Expand Up @@ -104,6 +104,17 @@ config LEDS_AW2013
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
Expand Down
1 change: 1 addition & 0 deletions drivers/leds/Makefile
Expand Up @@ -16,6 +16,7 @@ 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
Expand Down
314 changes: 314 additions & 0 deletions 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");

0 comments on commit 38eeda6

Please sign in to comment.