1 // SPDX-License-Identifier: GPL-2.0-only
3 * PWM-based multi-color LED control
5 * Copyright 2022 Sven Schwermer <sven.schwermer@disruptive-technologies.com>
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>
20 struct pwm_device
*pwm
;
21 struct pwm_state state
;
26 struct led_classdev_mc mc_cdev
;
28 struct pwm_led leds
[];
31 static int led_pwm_mc_set(struct led_classdev
*cdev
,
32 enum led_brightness brightness
)
34 struct led_classdev_mc
*mc_cdev
= lcdev_to_mccdev(cdev
);
35 struct pwm_mc_led
*priv
= container_of(mc_cdev
, struct pwm_mc_led
, mc_cdev
);
36 unsigned long long duty
;
40 led_mc_calc_color_components(mc_cdev
, brightness
);
42 mutex_lock(&priv
->lock
);
44 for (i
= 0; i
< mc_cdev
->num_colors
; i
++) {
45 duty
= priv
->leds
[i
].state
.period
;
46 duty
*= mc_cdev
->subled_info
[i
].brightness
;
47 do_div(duty
, cdev
->max_brightness
);
49 if (priv
->leds
[i
].active_low
)
50 duty
= priv
->leds
[i
].state
.period
- duty
;
52 priv
->leds
[i
].state
.duty_cycle
= duty
;
53 priv
->leds
[i
].state
.enabled
= duty
> 0;
54 ret
= pwm_apply_might_sleep(priv
->leds
[i
].pwm
,
55 &priv
->leds
[i
].state
);
60 mutex_unlock(&priv
->lock
);
65 static int iterate_subleds(struct device
*dev
, struct pwm_mc_led
*priv
,
66 struct fwnode_handle
*mcnode
)
68 struct mc_subled
*subled
= priv
->mc_cdev
.subled_info
;
69 struct fwnode_handle
*fwnode
;
70 struct pwm_led
*pwmled
;
74 /* iterate over the nodes inside the multi-led node */
75 fwnode_for_each_child_node(mcnode
, fwnode
) {
76 pwmled
= &priv
->leds
[priv
->mc_cdev
.num_colors
];
77 pwmled
->pwm
= devm_fwnode_pwm_get(dev
, fwnode
, NULL
);
78 if (IS_ERR(pwmled
->pwm
)) {
79 ret
= dev_err_probe(dev
, PTR_ERR(pwmled
->pwm
), "unable to request PWM\n");
82 pwm_init_state(pwmled
->pwm
, &pwmled
->state
);
83 pwmled
->active_low
= fwnode_property_read_bool(fwnode
, "active-low");
85 ret
= fwnode_property_read_u32(fwnode
, "color", &color
);
87 dev_err(dev
, "cannot read color: %d\n", ret
);
91 subled
[priv
->mc_cdev
.num_colors
].color_index
= color
;
92 priv
->mc_cdev
.num_colors
++;
98 fwnode_handle_put(fwnode
);
102 static int led_pwm_mc_probe(struct platform_device
*pdev
)
104 struct fwnode_handle
*mcnode
, *fwnode
;
105 struct led_init_data init_data
= {};
106 struct led_classdev
*cdev
;
107 struct mc_subled
*subled
;
108 struct pwm_mc_led
*priv
;
112 mcnode
= device_get_named_child_node(&pdev
->dev
, "multi-led");
114 return dev_err_probe(&pdev
->dev
, -ENODEV
,
115 "expected multi-led node\n");
117 /* count the nodes inside the multi-led node */
118 fwnode_for_each_child_node(mcnode
, fwnode
)
121 priv
= devm_kzalloc(&pdev
->dev
, struct_size(priv
, leds
, count
),
127 mutex_init(&priv
->lock
);
129 subled
= devm_kcalloc(&pdev
->dev
, count
, sizeof(*subled
), GFP_KERNEL
);
134 priv
->mc_cdev
.subled_info
= subled
;
136 /* init the multicolor's LED class device */
137 cdev
= &priv
->mc_cdev
.led_cdev
;
138 fwnode_property_read_u32(mcnode
, "max-brightness",
139 &cdev
->max_brightness
);
140 cdev
->flags
= LED_CORE_SUSPENDRESUME
;
141 cdev
->brightness_set_blocking
= led_pwm_mc_set
;
143 ret
= iterate_subleds(&pdev
->dev
, priv
, mcnode
);
147 init_data
.fwnode
= mcnode
;
148 ret
= devm_led_classdev_multicolor_register_ext(&pdev
->dev
,
153 "failed to register multicolor PWM led for %s: %d\n",
158 ret
= led_pwm_mc_set(cdev
, cdev
->brightness
);
160 return dev_err_probe(&pdev
->dev
, ret
,
161 "failed to set led PWM value for %s\n",
164 platform_set_drvdata(pdev
, priv
);
168 fwnode_handle_put(mcnode
);
172 static const struct of_device_id of_pwm_leds_mc_match
[] = {
173 { .compatible
= "pwm-leds-multicolor", },
176 MODULE_DEVICE_TABLE(of
, of_pwm_leds_mc_match
);
178 static struct platform_driver led_pwm_mc_driver
= {
179 .probe
= led_pwm_mc_probe
,
181 .name
= "leds_pwm_multicolor",
182 .of_match_table
= of_pwm_leds_mc_match
,
185 module_platform_driver(led_pwm_mc_driver
);
187 MODULE_AUTHOR("Sven Schwermer <sven.schwermer@disruptive-technologies.com>");
188 MODULE_DESCRIPTION("multi-color PWM LED driver");
189 MODULE_LICENSE("GPL v2");
190 MODULE_ALIAS("platform:leds-pwm-multicolor");