mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 22:59:29 +02:00
leds: pca963x: Add multicolor LED class support
Allow grouping of individual PCA963x PWM channels into a single multicolor LED device by adding support for the LED multicolor class. A child node with sub-children is treated as a multicolor group, others are treated as single leds, keeping full backwards compatibility. Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com> Link: https://patch.msgid.link/20260727-monza-leds-v8-3-6e7e93d44dba@oss.qualcomm.com Signed-off-by: Lee Jones <lee@kernel.org>
This commit is contained in:
@@ -596,6 +596,7 @@ config LEDS_PCA963X
|
||||
tristate "LED support for PCA963x I2C chip"
|
||||
depends on LEDS_CLASS
|
||||
depends on I2C
|
||||
select LEDS_CLASS_MULTICOLOR
|
||||
help
|
||||
This option enables support for LEDs connected to the PCA963x
|
||||
LED driver chip accessed via the I2C bus. Supported
|
||||
|
||||
+149
-44
@@ -27,6 +27,7 @@
|
||||
#include <linux/string.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/led-class-multicolor.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/property.h>
|
||||
@@ -101,8 +102,11 @@ struct pca963x;
|
||||
struct pca963x_led {
|
||||
struct pca963x *chip;
|
||||
struct led_classdev led_cdev;
|
||||
struct led_classdev_mc mc_cdev;
|
||||
struct mc_subled subleds[4];
|
||||
int led_num; /* 0 .. 15 potentially */
|
||||
bool blinking;
|
||||
bool is_mc;
|
||||
u8 gdc;
|
||||
u8 gfrq;
|
||||
};
|
||||
@@ -115,7 +119,7 @@ struct pca963x {
|
||||
struct pca963x_led leds[];
|
||||
};
|
||||
|
||||
static int pca963x_brightness(struct pca963x_led *led,
|
||||
static int pca963x_brightness(struct pca963x_led *led, unsigned int led_num,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct i2c_client *client = led->chip->client;
|
||||
@@ -124,8 +128,8 @@ static int pca963x_brightness(struct pca963x_led *led,
|
||||
int shift;
|
||||
int ret;
|
||||
|
||||
ledout_addr = chipdef->ledout_base + (led->led_num / 4);
|
||||
shift = 2 * (led->led_num % 4);
|
||||
ledout_addr = chipdef->ledout_base + (led_num / 4);
|
||||
shift = 2 * (led_num % 4);
|
||||
mask = 0x3 << shift;
|
||||
ledout = i2c_smbus_read_byte_data(client, ledout_addr);
|
||||
|
||||
@@ -135,7 +139,7 @@ static int pca963x_brightness(struct pca963x_led *led,
|
||||
val = (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift);
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
PCA963X_PWM_BASE +
|
||||
led->led_num,
|
||||
led_num,
|
||||
LED_FULL);
|
||||
} else {
|
||||
val = (ledout & ~mask) | (PCA963X_LED_ON << shift);
|
||||
@@ -150,7 +154,7 @@ static int pca963x_brightness(struct pca963x_led *led,
|
||||
default:
|
||||
ret = i2c_smbus_write_byte_data(client,
|
||||
PCA963X_PWM_BASE +
|
||||
led->led_num,
|
||||
led_num,
|
||||
brightness);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -199,20 +203,24 @@ static void pca963x_blink(struct pca963x_led *led)
|
||||
led->blinking = true;
|
||||
}
|
||||
|
||||
static int pca963x_power_state(struct pca963x_led *led)
|
||||
static void pca963x_track_power_state(struct pca963x_led *led, unsigned int led_num,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
unsigned long *leds_on = &led->chip->leds_on;
|
||||
|
||||
if (brightness)
|
||||
set_bit(led_num, leds_on);
|
||||
else
|
||||
clear_bit(led_num, leds_on);
|
||||
}
|
||||
|
||||
static int pca963x_sync_power_state(struct pca963x_led *led, unsigned long cached_leds)
|
||||
{
|
||||
struct i2c_client *client = led->chip->client;
|
||||
unsigned long *leds_on = &led->chip->leds_on;
|
||||
unsigned long cached_leds = *leds_on;
|
||||
|
||||
if (led->led_cdev.brightness)
|
||||
set_bit(led->led_num, leds_on);
|
||||
else
|
||||
clear_bit(led->led_num, leds_on);
|
||||
|
||||
if (!(*leds_on) != !cached_leds)
|
||||
if (!led->chip->leds_on != !cached_leds)
|
||||
return i2c_smbus_write_byte_data(client, PCA963X_MODE1,
|
||||
*leds_on ? 0 : BIT(4));
|
||||
led->chip->leds_on ? 0 : BIT(4));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -221,22 +229,60 @@ static int pca963x_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct pca963x_led *led;
|
||||
unsigned long cached_leds;
|
||||
int ret;
|
||||
|
||||
led = container_of(led_cdev, struct pca963x_led, led_cdev);
|
||||
|
||||
mutex_lock(&led->chip->mutex);
|
||||
|
||||
ret = pca963x_brightness(led, value);
|
||||
if (ret < 0)
|
||||
cached_leds = led->chip->leds_on;
|
||||
ret = pca963x_brightness(led, led->led_num, value);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
ret = pca963x_power_state(led);
|
||||
|
||||
pca963x_track_power_state(led, led->led_num, value);
|
||||
ret = pca963x_sync_power_state(led, cached_leds);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&led->chip->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pca963x_led_mc_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
|
||||
struct pca963x_led *led = container_of(mc_cdev, struct pca963x_led, mc_cdev);
|
||||
unsigned long cached_leds;
|
||||
int ret = 0, sync_ret;
|
||||
|
||||
led_mc_calc_color_components(mc_cdev, value);
|
||||
|
||||
guard(mutex)(&led->chip->mutex);
|
||||
|
||||
cached_leds = led->chip->leds_on;
|
||||
for (unsigned int i = 0; i < mc_cdev->num_colors; i++) {
|
||||
unsigned int channel = mc_cdev->subled_info[i].channel;
|
||||
|
||||
ret = pca963x_brightness(led, channel,
|
||||
mc_cdev->subled_info[i].brightness);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
pca963x_track_power_state(led, channel,
|
||||
mc_cdev->subled_info[i].brightness);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some channels may already have been updated before the error, so
|
||||
* still sync the global on/off state to reflect what actually changed.
|
||||
*/
|
||||
sync_ret = pca963x_sync_power_state(led, cached_leds);
|
||||
|
||||
return ret ? : sync_ret;
|
||||
}
|
||||
|
||||
static unsigned int pca963x_period_scale(struct pca963x_led *led,
|
||||
unsigned int val)
|
||||
{
|
||||
@@ -300,6 +346,81 @@ static int pca963x_blink_set(struct led_classdev *led_cdev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca963x_parse_mc_subleds(struct device *dev, struct pca963x_led *led,
|
||||
struct fwnode_handle *fwnode,
|
||||
const struct pca963x_chipdef *chipdef)
|
||||
{
|
||||
unsigned int num_colors = 0;
|
||||
int ret;
|
||||
|
||||
fwnode_for_each_child_node_scoped(fwnode, sub) {
|
||||
u32 color, subreg;
|
||||
|
||||
if (num_colors >= ARRAY_SIZE(led->subleds))
|
||||
return dev_err_probe(dev, -EINVAL, "Too many LEDs for node %pfw\n", fwnode);
|
||||
|
||||
ret = fwnode_property_read_u32(sub, "reg", &subreg);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Missing 'reg' for sub-LED %pfw\n", sub);
|
||||
if (subreg >= chipdef->n_leds)
|
||||
return dev_err_probe(dev, -EINVAL, "Invalid 'reg' for sub-LED %pfw\n", sub);
|
||||
|
||||
ret = fwnode_property_read_u32(sub, "color", &color);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Missing 'color' for sub-LED %pfw\n", sub);
|
||||
|
||||
led->subleds[num_colors].channel = subreg;
|
||||
led->subleds[num_colors].color_index = color;
|
||||
led->subleds[num_colors].intensity = LED_FULL;
|
||||
num_colors++;
|
||||
}
|
||||
|
||||
led->mc_cdev.subled_info = led->subleds;
|
||||
led->mc_cdev.num_colors = num_colors;
|
||||
led->mc_cdev.led_cdev.max_brightness = LED_FULL;
|
||||
led->mc_cdev.led_cdev.brightness_set_blocking = pca963x_led_mc_set;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca963x_register_led(struct device *dev, struct pca963x_led *led,
|
||||
u32 reg, struct fwnode_handle *fwnode,
|
||||
const struct pca963x_chipdef *chipdef,
|
||||
bool hw_blink)
|
||||
{
|
||||
struct i2c_client *client = led->chip->client;
|
||||
struct led_init_data init_data = {};
|
||||
char label[32];
|
||||
int ret;
|
||||
|
||||
led->led_num = reg;
|
||||
|
||||
/* A node with sub-children groups several channels into a multicolor LED. */
|
||||
led->is_mc = fwnode_get_child_node_count(fwnode) > 0;
|
||||
|
||||
if (led->is_mc) {
|
||||
ret = pca963x_parse_mc_subleds(dev, led, fwnode, chipdef);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
led->led_cdev.brightness_set_blocking = pca963x_led_set;
|
||||
if (hw_blink)
|
||||
led->led_cdev.blink_set = pca963x_blink_set;
|
||||
}
|
||||
|
||||
init_data.fwnode = fwnode;
|
||||
/* Keep the legacy device name to preserve existing sysfs LED names. */
|
||||
init_data.devicename = "pca963x";
|
||||
snprintf(label, sizeof(label), "%d:%.2x:%u", client->adapter->nr, client->addr, reg);
|
||||
init_data.default_label = label;
|
||||
|
||||
if (led->is_mc)
|
||||
return devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev,
|
||||
&init_data);
|
||||
|
||||
return devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data);
|
||||
}
|
||||
|
||||
static int pca963x_register_leds(struct i2c_client *client,
|
||||
struct pca963x *chip)
|
||||
{
|
||||
@@ -338,37 +459,21 @@ static int pca963x_register_leds(struct i2c_client *client,
|
||||
return ret;
|
||||
|
||||
device_for_each_child_node_scoped(dev, child) {
|
||||
struct led_init_data init_data = {};
|
||||
char default_label[32];
|
||||
|
||||
ret = fwnode_property_read_u32(child, "reg", ®);
|
||||
if (ret || reg >= chipdef->n_leds) {
|
||||
dev_err(dev, "Invalid 'reg' property for node %pfw\n",
|
||||
child);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret,
|
||||
"Missing 'reg' property for node %pfw\n", child);
|
||||
if (reg >= chipdef->n_leds)
|
||||
return dev_err_probe(dev, -EINVAL,
|
||||
"Invalid 'reg' property for node %pfw\n", child);
|
||||
|
||||
led->led_num = reg;
|
||||
led->chip = chip;
|
||||
led->led_cdev.brightness_set_blocking = pca963x_led_set;
|
||||
if (hw_blink)
|
||||
led->led_cdev.blink_set = pca963x_blink_set;
|
||||
led->blinking = false;
|
||||
|
||||
init_data.fwnode = child;
|
||||
/* for backwards compatibility */
|
||||
init_data.devicename = "pca963x";
|
||||
snprintf(default_label, sizeof(default_label), "%d:%.2x:%u",
|
||||
client->adapter->nr, client->addr, reg);
|
||||
init_data.default_label = default_label;
|
||||
|
||||
ret = devm_led_classdev_register_ext(dev, &led->led_cdev,
|
||||
&init_data);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register LED for node %pfw\n",
|
||||
child);
|
||||
return ret;
|
||||
}
|
||||
ret = pca963x_register_led(dev, led, reg, child, chipdef, hw_blink);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to register LED for node %pfw\n",
|
||||
child);
|
||||
|
||||
++led;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user