1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/drivers/leds-pwm.c |
4 | * |
5 | * simple PWM based LED control |
6 | * |
7 | * Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de) |
8 | * |
9 | * based on leds-gpio.c by Raphael Assenat <raph@8d.com> |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/of.h> |
16 | #include <linux/leds.h> |
17 | #include <linux/err.h> |
18 | #include <linux/pwm.h> |
19 | #include <linux/slab.h> |
20 | #include "leds.h" |
21 | |
22 | struct led_pwm { |
23 | const char *name; |
24 | u8 active_low; |
25 | u8 default_state; |
26 | unsigned int max_brightness; |
27 | }; |
28 | |
29 | struct led_pwm_data { |
30 | struct led_classdev cdev; |
31 | struct pwm_device *pwm; |
32 | struct pwm_state pwmstate; |
33 | unsigned int active_low; |
34 | }; |
35 | |
36 | struct led_pwm_priv { |
37 | int num_leds; |
38 | struct led_pwm_data leds[]; |
39 | }; |
40 | |
41 | static int led_pwm_set(struct led_classdev *led_cdev, |
42 | enum led_brightness brightness) |
43 | { |
44 | struct led_pwm_data *led_dat = |
45 | container_of(led_cdev, struct led_pwm_data, cdev); |
46 | unsigned int max = led_dat->cdev.max_brightness; |
47 | unsigned long long duty = led_dat->pwmstate.period; |
48 | |
49 | duty *= brightness; |
50 | do_div(duty, max); |
51 | |
52 | if (led_dat->active_low) |
53 | duty = led_dat->pwmstate.period - duty; |
54 | |
55 | led_dat->pwmstate.duty_cycle = duty; |
56 | led_dat->pwmstate.enabled = true; |
57 | return pwm_apply_state(pwm: led_dat->pwm, state: &led_dat->pwmstate); |
58 | } |
59 | |
60 | __attribute__((nonnull)) |
61 | static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, |
62 | struct led_pwm *led, struct fwnode_handle *fwnode) |
63 | { |
64 | struct led_pwm_data *led_data = &priv->leds[priv->num_leds]; |
65 | struct led_init_data init_data = { .fwnode = fwnode }; |
66 | int ret; |
67 | |
68 | led_data->active_low = led->active_low; |
69 | led_data->cdev.name = led->name; |
70 | led_data->cdev.brightness = LED_OFF; |
71 | led_data->cdev.max_brightness = led->max_brightness; |
72 | led_data->cdev.flags = LED_CORE_SUSPENDRESUME; |
73 | |
74 | led_data->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL); |
75 | if (IS_ERR(ptr: led_data->pwm)) |
76 | return dev_err_probe(dev, err: PTR_ERR(ptr: led_data->pwm), |
77 | fmt: "unable to request PWM for %s\n" , |
78 | led->name); |
79 | |
80 | led_data->cdev.brightness_set_blocking = led_pwm_set; |
81 | |
82 | /* init PWM state */ |
83 | switch (led->default_state) { |
84 | case LEDS_DEFSTATE_KEEP: |
85 | pwm_get_state(pwm: led_data->pwm, state: &led_data->pwmstate); |
86 | if (led_data->pwmstate.period) |
87 | break; |
88 | led->default_state = LEDS_DEFSTATE_OFF; |
89 | dev_warn(dev, |
90 | "failed to read period for %s, default to off" , |
91 | led->name); |
92 | fallthrough; |
93 | default: |
94 | pwm_init_state(pwm: led_data->pwm, state: &led_data->pwmstate); |
95 | break; |
96 | } |
97 | |
98 | /* set brightness */ |
99 | switch (led->default_state) { |
100 | case LEDS_DEFSTATE_ON: |
101 | led_data->cdev.brightness = led->max_brightness; |
102 | break; |
103 | case LEDS_DEFSTATE_KEEP: |
104 | { |
105 | uint64_t brightness; |
106 | |
107 | brightness = led->max_brightness; |
108 | brightness *= led_data->pwmstate.duty_cycle; |
109 | do_div(brightness, led_data->pwmstate.period); |
110 | led_data->cdev.brightness = brightness; |
111 | } |
112 | break; |
113 | } |
114 | |
115 | ret = devm_led_classdev_register_ext(parent: dev, led_cdev: &led_data->cdev, init_data: &init_data); |
116 | if (ret) { |
117 | dev_err(dev, "failed to register PWM led for %s: %d\n" , |
118 | led->name, ret); |
119 | return ret; |
120 | } |
121 | |
122 | if (led->default_state != LEDS_DEFSTATE_KEEP) { |
123 | ret = led_pwm_set(led_cdev: &led_data->cdev, brightness: led_data->cdev.brightness); |
124 | if (ret) { |
125 | dev_err(dev, "failed to set led PWM value for %s: %d" , |
126 | led->name, ret); |
127 | return ret; |
128 | } |
129 | } |
130 | |
131 | priv->num_leds++; |
132 | return 0; |
133 | } |
134 | |
135 | static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv) |
136 | { |
137 | struct fwnode_handle *fwnode; |
138 | struct led_pwm led; |
139 | int ret; |
140 | |
141 | device_for_each_child_node(dev, fwnode) { |
142 | memset(&led, 0, sizeof(led)); |
143 | |
144 | ret = fwnode_property_read_string(fwnode, propname: "label" , val: &led.name); |
145 | if (ret && is_of_node(fwnode)) |
146 | led.name = to_of_node(fwnode)->name; |
147 | |
148 | if (!led.name) { |
149 | ret = -EINVAL; |
150 | goto err_child_out; |
151 | } |
152 | |
153 | led.active_low = fwnode_property_read_bool(fwnode, |
154 | propname: "active-low" ); |
155 | fwnode_property_read_u32(fwnode, propname: "max-brightness" , |
156 | val: &led.max_brightness); |
157 | |
158 | led.default_state = led_init_default_state_get(fwnode); |
159 | |
160 | ret = led_pwm_add(dev, priv, led: &led, fwnode); |
161 | if (ret) |
162 | goto err_child_out; |
163 | } |
164 | |
165 | return 0; |
166 | |
167 | err_child_out: |
168 | fwnode_handle_put(fwnode); |
169 | return ret; |
170 | } |
171 | |
172 | static int led_pwm_probe(struct platform_device *pdev) |
173 | { |
174 | struct led_pwm_priv *priv; |
175 | int ret = 0; |
176 | int count; |
177 | |
178 | count = device_get_child_node_count(dev: &pdev->dev); |
179 | |
180 | if (!count) |
181 | return -EINVAL; |
182 | |
183 | priv = devm_kzalloc(dev: &pdev->dev, struct_size(priv, leds, count), |
184 | GFP_KERNEL); |
185 | if (!priv) |
186 | return -ENOMEM; |
187 | |
188 | ret = led_pwm_create_fwnode(dev: &pdev->dev, priv); |
189 | |
190 | if (ret) |
191 | return ret; |
192 | |
193 | platform_set_drvdata(pdev, data: priv); |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | static const struct of_device_id of_pwm_leds_match[] = { |
199 | { .compatible = "pwm-leds" , }, |
200 | {}, |
201 | }; |
202 | MODULE_DEVICE_TABLE(of, of_pwm_leds_match); |
203 | |
204 | static struct platform_driver led_pwm_driver = { |
205 | .probe = led_pwm_probe, |
206 | .driver = { |
207 | .name = "leds_pwm" , |
208 | .of_match_table = of_pwm_leds_match, |
209 | }, |
210 | }; |
211 | |
212 | module_platform_driver(led_pwm_driver); |
213 | |
214 | MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>" ); |
215 | MODULE_DESCRIPTION("generic PWM LED driver" ); |
216 | MODULE_LICENSE("GPL v2" ); |
217 | MODULE_ALIAS("platform:leds-pwm" ); |
218 | |