Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
41c42ff5 LF |
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> | |
41c42ff5 LF |
10 | */ |
11 | ||
12 | #include <linux/module.h> | |
13 | #include <linux/kernel.h> | |
41c42ff5 | 14 | #include <linux/platform_device.h> |
3192f141 | 15 | #include <linux/of.h> |
41c42ff5 LF |
16 | #include <linux/leds.h> |
17 | #include <linux/err.h> | |
18 | #include <linux/pwm.h> | |
5a0e3ad6 | 19 | #include <linux/slab.h> |
3d3d65bd | 20 | #include "leds.h" |
41c42ff5 | 21 | |
141f15c6 DOH |
22 | struct led_pwm { |
23 | const char *name; | |
141f15c6 | 24 | u8 active_low; |
3d3d65bd | 25 | u8 default_state; |
141f15c6 | 26 | unsigned int max_brightness; |
141f15c6 DOH |
27 | }; |
28 | ||
41c42ff5 LF |
29 | struct led_pwm_data { |
30 | struct led_classdev cdev; | |
31 | struct pwm_device *pwm; | |
dd47a834 | 32 | struct pwm_state pwmstate; |
dcba9105 | 33 | unsigned int active_low; |
41c42ff5 LF |
34 | }; |
35 | ||
0f86815a PU |
36 | struct led_pwm_priv { |
37 | int num_leds; | |
7bbec6c4 | 38 | struct led_pwm_data leds[]; |
0f86815a PU |
39 | }; |
40 | ||
247bde13 TR |
41 | static int led_pwm_set(struct led_classdev *led_cdev, |
42 | enum led_brightness brightness) | |
41c42ff5 LF |
43 | { |
44 | struct led_pwm_data *led_dat = | |
45 | container_of(led_cdev, struct led_pwm_data, cdev); | |
e4590620 | 46 | unsigned int max = led_dat->cdev.max_brightness; |
dd47a834 | 47 | unsigned long long duty = led_dat->pwmstate.period; |
41c42ff5 | 48 | |
fc1aee03 XL |
49 | duty *= brightness; |
50 | do_div(duty, max); | |
d19a8a70 RK |
51 | |
52 | if (led_dat->active_low) | |
dd47a834 | 53 | duty = led_dat->pwmstate.period - duty; |
c971ff18 | 54 | |
dd47a834 | 55 | led_dat->pwmstate.duty_cycle = duty; |
974afccd UKK |
56 | /* |
57 | * Disabling a PWM doesn't guarantee that it emits the inactive level. | |
58 | * So keep it on. Only for suspending the PWM should be disabled because | |
59 | * otherwise it refuses to suspend. The possible downside is that the | |
60 | * LED might stay (or even go) on. | |
61 | */ | |
62 | led_dat->pwmstate.enabled = !(led_cdev->flags & LED_SUSPENDED); | |
c748a6d7 | 63 | return pwm_apply_might_sleep(led_dat->pwm, &led_dat->pwmstate); |
41c42ff5 LF |
64 | } |
65 | ||
19d2e0ce | 66 | __attribute__((nonnull)) |
5f7b03dc | 67 | static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, |
3f467ebe | 68 | struct led_pwm *led, struct fwnode_handle *fwnode) |
41c42ff5 | 69 | { |
5f7b03dc | 70 | struct led_pwm_data *led_data = &priv->leds[priv->num_leds]; |
de73f275 | 71 | struct led_init_data init_data = { .fwnode = fwnode }; |
aa1a6d6d | 72 | int ret; |
41c42ff5 | 73 | |
5f7b03dc | 74 | led_data->active_low = led->active_low; |
5f7b03dc | 75 | led_data->cdev.name = led->name; |
5f7b03dc RK |
76 | led_data->cdev.brightness = LED_OFF; |
77 | led_data->cdev.max_brightness = led->max_brightness; | |
78 | led_data->cdev.flags = LED_CORE_SUSPENDRESUME; | |
41c42ff5 | 79 | |
19d2e0ce | 80 | led_data->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL); |
7e8da605 KK |
81 | if (IS_ERR(led_data->pwm)) |
82 | return dev_err_probe(dev, PTR_ERR(led_data->pwm), | |
83 | "unable to request PWM for %s\n", | |
84 | led->name); | |
08541cbc | 85 | |
247bde13 | 86 | led_data->cdev.brightness_set_blocking = led_pwm_set; |
41c42ff5 | 87 | |
3d3d65bd DOH |
88 | /* init PWM state */ |
89 | switch (led->default_state) { | |
90 | case LEDS_DEFSTATE_KEEP: | |
91 | pwm_get_state(led_data->pwm, &led_data->pwmstate); | |
92 | if (led_data->pwmstate.period) | |
93 | break; | |
94 | led->default_state = LEDS_DEFSTATE_OFF; | |
95 | dev_warn(dev, | |
96 | "failed to read period for %s, default to off", | |
97 | led->name); | |
98 | fallthrough; | |
99 | default: | |
100 | pwm_init_state(led_data->pwm, &led_data->pwmstate); | |
101 | break; | |
102 | } | |
103 | ||
104 | /* set brightness */ | |
105 | switch (led->default_state) { | |
106 | case LEDS_DEFSTATE_ON: | |
107 | led_data->cdev.brightness = led->max_brightness; | |
108 | break; | |
109 | case LEDS_DEFSTATE_KEEP: | |
110 | { | |
111 | uint64_t brightness; | |
112 | ||
113 | brightness = led->max_brightness; | |
114 | brightness *= led_data->pwmstate.duty_cycle; | |
115 | do_div(brightness, led_data->pwmstate.period); | |
116 | led_data->cdev.brightness = brightness; | |
117 | } | |
118 | break; | |
119 | } | |
1b50673d | 120 | |
de73f275 | 121 | ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data); |
44c606b0 | 122 | if (ret) { |
5f7b03dc RK |
123 | dev_err(dev, "failed to register PWM led for %s: %d\n", |
124 | led->name, ret); | |
44c606b0 | 125 | return ret; |
5f7b03dc RK |
126 | } |
127 | ||
3d3d65bd DOH |
128 | if (led->default_state != LEDS_DEFSTATE_KEEP) { |
129 | ret = led_pwm_set(&led_data->cdev, led_data->cdev.brightness); | |
130 | if (ret) { | |
131 | dev_err(dev, "failed to set led PWM value for %s: %d", | |
132 | led->name, ret); | |
133 | return ret; | |
134 | } | |
44c606b0 DOH |
135 | } |
136 | ||
137 | priv->num_leds++; | |
138 | return 0; | |
5f7b03dc | 139 | } |
c971ff18 | 140 | |
3f467ebe | 141 | static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv) |
41c42ff5 | 142 | { |
3f467ebe | 143 | struct fwnode_handle *fwnode; |
b795e6d9 | 144 | struct led_pwm led; |
95138e01 | 145 | int ret; |
41c42ff5 | 146 | |
3f467ebe | 147 | device_for_each_child_node(dev, fwnode) { |
d8960dfb HW |
148 | memset(&led, 0, sizeof(led)); |
149 | ||
3f467ebe NV |
150 | ret = fwnode_property_read_string(fwnode, "label", &led.name); |
151 | if (ret && is_of_node(fwnode)) | |
152 | led.name = to_of_node(fwnode)->name; | |
08541cbc | 153 | |
3f467ebe | 154 | if (!led.name) { |
cadb2de2 | 155 | ret = -EINVAL; |
95138e01 | 156 | goto err_child_out; |
3f467ebe NV |
157 | } |
158 | ||
3f467ebe NV |
159 | led.active_low = fwnode_property_read_bool(fwnode, |
160 | "active-low"); | |
161 | fwnode_property_read_u32(fwnode, "max-brightness", | |
162 | &led.max_brightness); | |
c971ff18 | 163 | |
3d3d65bd DOH |
164 | led.default_state = led_init_default_state_get(fwnode); |
165 | ||
3f467ebe | 166 | ret = led_pwm_add(dev, priv, &led, fwnode); |
95138e01 AS |
167 | if (ret) |
168 | goto err_child_out; | |
08541cbc PU |
169 | } |
170 | ||
95138e01 AS |
171 | return 0; |
172 | ||
173 | err_child_out: | |
174 | fwnode_handle_put(fwnode); | |
aa1a6d6d | 175 | return ret; |
08541cbc PU |
176 | } |
177 | ||
178 | static int led_pwm_probe(struct platform_device *pdev) | |
179 | { | |
08541cbc | 180 | struct led_pwm_priv *priv; |
aa1a6d6d | 181 | int ret = 0; |
19d2e0ce | 182 | int count; |
aa1a6d6d | 183 | |
19d2e0ce | 184 | count = device_get_child_node_count(&pdev->dev); |
aa1a6d6d PU |
185 | |
186 | if (!count) | |
187 | return -EINVAL; | |
08541cbc | 188 | |
d4b02200 | 189 | priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count), |
aa1a6d6d PU |
190 | GFP_KERNEL); |
191 | if (!priv) | |
192 | return -ENOMEM; | |
08541cbc | 193 | |
19d2e0ce | 194 | ret = led_pwm_create_fwnode(&pdev->dev, priv); |
5f7b03dc | 195 | |
e5a0436d | 196 | if (ret) |
5f7b03dc | 197 | return ret; |
41c42ff5 | 198 | |
0f86815a | 199 | platform_set_drvdata(pdev, priv); |
41c42ff5 LF |
200 | |
201 | return 0; | |
41c42ff5 LF |
202 | } |
203 | ||
08541cbc PU |
204 | static const struct of_device_id of_pwm_leds_match[] = { |
205 | { .compatible = "pwm-leds", }, | |
206 | {}, | |
207 | }; | |
208 | MODULE_DEVICE_TABLE(of, of_pwm_leds_match); | |
209 | ||
41c42ff5 LF |
210 | static struct platform_driver led_pwm_driver = { |
211 | .probe = led_pwm_probe, | |
41c42ff5 LF |
212 | .driver = { |
213 | .name = "leds_pwm", | |
1e08f72d | 214 | .of_match_table = of_pwm_leds_match, |
41c42ff5 LF |
215 | }, |
216 | }; | |
217 | ||
892a8843 | 218 | module_platform_driver(led_pwm_driver); |
41c42ff5 LF |
219 | |
220 | MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>"); | |
49651c6c UKK |
221 | MODULE_DESCRIPTION("generic PWM LED driver"); |
222 | MODULE_LICENSE("GPL v2"); | |
41c42ff5 | 223 | MODULE_ALIAS("platform:leds-pwm"); |