Skip to content

Commit 9fa2762

Browse files
svenschwermerpavelmachek
authored andcommitted
leds: Add PWM multicolor driver
By allowing to group multiple monochrome PWM LEDs into multicolor LEDs, all involved LEDs can be controlled in-sync. This enables using effects using triggers, etc. Signed-off-by: Sven Schwermer <[email protected]> Reviewed-by: Andy Shevchenko <[email protected]> Signed-off-by: Pavel Machek <[email protected]>
1 parent ac12374 commit 9fa2762

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

drivers/leds/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,17 @@ config LEDS_PWM
552552
help
553553
This option enables support for pwm driven LEDs
554554

555+
config LEDS_PWM_MULTICOLOR
556+
tristate "PWM driven multi-color LED Support"
557+
depends on LEDS_CLASS_MULTICOLOR
558+
depends on PWM
559+
help
560+
This option enables support for PWM driven monochrome LEDs that are
561+
grouped into multicolor LEDs.
562+
563+
To compile this driver as a module, choose M here: the module
564+
will be called leds-pwm-multicolor.
565+
555566
config LEDS_REGULATOR
556567
tristate "REGULATOR driven LED support"
557568
depends on LEDS_CLASS

drivers/leds/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o
7373
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
7474
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
7575
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
76+
obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
7677
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
7778
obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o
7879
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o

drivers/leds/leds-pwm-multicolor.c

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* PWM-based multi-color LED control
4+
*
5+
* Copyright 2022 Sven Schwermer <[email protected]>
6+
*/
7+
8+
#include <linux/err.h>
9+
#include <linux/kernel.h>
10+
#include <linux/led-class-multicolor.h>
11+
#include <linux/leds.h>
12+
#include <linux/mod_devicetable.h>
13+
#include <linux/module.h>
14+
#include <linux/mutex.h>
15+
#include <linux/platform_device.h>
16+
#include <linux/property.h>
17+
#include <linux/pwm.h>
18+
19+
struct pwm_led {
20+
struct pwm_device *pwm;
21+
struct pwm_state state;
22+
};
23+
24+
struct pwm_mc_led {
25+
struct led_classdev_mc mc_cdev;
26+
struct mutex lock;
27+
struct pwm_led leds[];
28+
};
29+
30+
static int led_pwm_mc_set(struct led_classdev *cdev,
31+
enum led_brightness brightness)
32+
{
33+
struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
34+
struct pwm_mc_led *priv = container_of(mc_cdev, struct pwm_mc_led, mc_cdev);
35+
unsigned long long duty;
36+
int ret = 0;
37+
int i;
38+
39+
led_mc_calc_color_components(mc_cdev, brightness);
40+
41+
mutex_lock(&priv->lock);
42+
43+
for (i = 0; i < mc_cdev->num_colors; i++) {
44+
duty = priv->leds[i].state.period;
45+
duty *= mc_cdev->subled_info[i].brightness;
46+
do_div(duty, cdev->max_brightness);
47+
48+
priv->leds[i].state.duty_cycle = duty;
49+
priv->leds[i].state.enabled = duty > 0;
50+
ret = pwm_apply_state(priv->leds[i].pwm,
51+
&priv->leds[i].state);
52+
if (ret)
53+
break;
54+
}
55+
56+
mutex_unlock(&priv->lock);
57+
58+
return ret;
59+
}
60+
61+
static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv,
62+
struct fwnode_handle *mcnode)
63+
{
64+
struct mc_subled *subled = priv->mc_cdev.subled_info;
65+
struct fwnode_handle *fwnode;
66+
struct pwm_led *pwmled;
67+
u32 color;
68+
int ret;
69+
70+
/* iterate over the nodes inside the multi-led node */
71+
fwnode_for_each_child_node(mcnode, fwnode) {
72+
pwmled = &priv->leds[priv->mc_cdev.num_colors];
73+
pwmled->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL);
74+
if (IS_ERR(pwmled->pwm)) {
75+
ret = PTR_ERR(pwmled->pwm);
76+
dev_err(dev, "unable to request PWM: %d\n", ret);
77+
goto release_fwnode;
78+
}
79+
pwm_init_state(pwmled->pwm, &pwmled->state);
80+
81+
ret = fwnode_property_read_u32(fwnode, "color", &color);
82+
if (ret) {
83+
dev_err(dev, "cannot read color: %d\n", ret);
84+
goto release_fwnode;
85+
}
86+
87+
subled[priv->mc_cdev.num_colors].color_index = color;
88+
priv->mc_cdev.num_colors++;
89+
}
90+
91+
return 0;
92+
93+
release_fwnode:
94+
fwnode_handle_put(fwnode);
95+
return ret;
96+
}
97+
98+
static int led_pwm_mc_probe(struct platform_device *pdev)
99+
{
100+
struct fwnode_handle *mcnode, *fwnode;
101+
struct led_init_data init_data = {};
102+
struct led_classdev *cdev;
103+
struct mc_subled *subled;
104+
struct pwm_mc_led *priv;
105+
int count = 0;
106+
int ret = 0;
107+
108+
mcnode = device_get_named_child_node(&pdev->dev, "multi-led");
109+
if (!mcnode)
110+
return dev_err_probe(&pdev->dev, -ENODEV,
111+
"expected multi-led node\n");
112+
113+
/* count the nodes inside the multi-led node */
114+
fwnode_for_each_child_node(mcnode, fwnode)
115+
count++;
116+
117+
priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count),
118+
GFP_KERNEL);
119+
if (!priv) {
120+
ret = -ENOMEM;
121+
goto release_mcnode;
122+
}
123+
mutex_init(&priv->lock);
124+
125+
subled = devm_kcalloc(&pdev->dev, count, sizeof(*subled), GFP_KERNEL);
126+
if (!subled) {
127+
ret = -ENOMEM;
128+
goto release_mcnode;
129+
}
130+
priv->mc_cdev.subled_info = subled;
131+
132+
/* init the multicolor's LED class device */
133+
cdev = &priv->mc_cdev.led_cdev;
134+
fwnode_property_read_u32(mcnode, "max-brightness",
135+
&cdev->max_brightness);
136+
cdev->flags = LED_CORE_SUSPENDRESUME;
137+
cdev->brightness_set_blocking = led_pwm_mc_set;
138+
139+
ret = iterate_subleds(&pdev->dev, priv, mcnode);
140+
if (ret)
141+
goto release_mcnode;
142+
143+
init_data.fwnode = mcnode;
144+
ret = devm_led_classdev_multicolor_register_ext(&pdev->dev,
145+
&priv->mc_cdev,
146+
&init_data);
147+
if (ret) {
148+
dev_err(&pdev->dev,
149+
"failed to register multicolor PWM led for %s: %d\n",
150+
cdev->name, ret);
151+
goto release_mcnode;
152+
}
153+
154+
ret = led_pwm_mc_set(cdev, cdev->brightness);
155+
if (ret)
156+
return dev_err_probe(&pdev->dev, ret,
157+
"failed to set led PWM value for %s: %d",
158+
cdev->name, ret);
159+
160+
platform_set_drvdata(pdev, priv);
161+
return 0;
162+
163+
release_mcnode:
164+
fwnode_handle_put(mcnode);
165+
return ret;
166+
}
167+
168+
static const struct of_device_id of_pwm_leds_mc_match[] = {
169+
{ .compatible = "pwm-leds-multicolor", },
170+
{}
171+
};
172+
MODULE_DEVICE_TABLE(of, of_pwm_leds_mc_match);
173+
174+
static struct platform_driver led_pwm_mc_driver = {
175+
.probe = led_pwm_mc_probe,
176+
.driver = {
177+
.name = "leds_pwm_multicolor",
178+
.of_match_table = of_pwm_leds_mc_match,
179+
},
180+
};
181+
module_platform_driver(led_pwm_mc_driver);
182+
183+
MODULE_AUTHOR("Sven Schwermer <[email protected]>");
184+
MODULE_DESCRIPTION("multi-color PWM LED driver");
185+
MODULE_LICENSE("GPL v2");
186+
MODULE_ALIAS("platform:leds-pwm-multicolor");

0 commit comments

Comments
 (0)