forked from torvalds/linux
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
leds: aw21024: Add support for Awinic's AW21024
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
1 parent
73bce57
commit 38eeda6
Showing
3 changed files
with
326 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); |