]>
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 ([email protected]) | |
8 | * | |
9 | * based on leds-gpio.c by Raphael Assenat <[email protected]> | |
41c42ff5 LF |
10 | */ |
11 | ||
12 | #include <linux/module.h> | |
13 | #include <linux/kernel.h> | |
41c42ff5 | 14 | #include <linux/platform_device.h> |
08541cbc | 15 | #include <linux/of_platform.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 UKK |
55 | led_dat->pwmstate.duty_cycle = duty; |
56 | led_dat->pwmstate.enabled = duty > 0; | |
57 | return pwm_apply_state(led_dat->pwm, &led_dat->pwmstate); | |
41c42ff5 LF |
58 | } |
59 | ||
19d2e0ce | 60 | __attribute__((nonnull)) |
5f7b03dc | 61 | static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, |
3f467ebe | 62 | struct led_pwm *led, struct fwnode_handle *fwnode) |
41c42ff5 | 63 | { |
5f7b03dc | 64 | struct led_pwm_data *led_data = &priv->leds[priv->num_leds]; |
de73f275 | 65 | struct led_init_data init_data = { .fwnode = fwnode }; |
aa1a6d6d | 66 | int ret; |
41c42ff5 | 67 | |
5f7b03dc | 68 | led_data->active_low = led->active_low; |
5f7b03dc | 69 | led_data->cdev.name = led->name; |
5f7b03dc RK |
70 | led_data->cdev.brightness = LED_OFF; |
71 | led_data->cdev.max_brightness = led->max_brightness; | |
72 | led_data->cdev.flags = LED_CORE_SUSPENDRESUME; | |
41c42ff5 | 73 | |
19d2e0ce | 74 | led_data->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL); |
7e8da605 KK |
75 | if (IS_ERR(led_data->pwm)) |
76 | return dev_err_probe(dev, PTR_ERR(led_data->pwm), | |
77 | "unable to request PWM for %s\n", | |
78 | led->name); | |
08541cbc | 79 | |
247bde13 | 80 | led_data->cdev.brightness_set_blocking = led_pwm_set; |
41c42ff5 | 81 | |
3d3d65bd DOH |
82 | /* init PWM state */ |
83 | switch (led->default_state) { | |
84 | case LEDS_DEFSTATE_KEEP: | |
85 | pwm_get_state(led_data->pwm, &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(led_data->pwm, &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 | } | |
1b50673d | 114 | |
de73f275 | 115 | ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data); |
44c606b0 | 116 | if (ret) { |
5f7b03dc RK |
117 | dev_err(dev, "failed to register PWM led for %s: %d\n", |
118 | led->name, ret); | |
44c606b0 | 119 | return ret; |
5f7b03dc RK |
120 | } |
121 | ||
3d3d65bd DOH |
122 | if (led->default_state != LEDS_DEFSTATE_KEEP) { |
123 | ret = led_pwm_set(&led_data->cdev, 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 | } | |
44c606b0 DOH |
129 | } |
130 | ||
131 | priv->num_leds++; | |
132 | return 0; | |
5f7b03dc | 133 | } |
c971ff18 | 134 | |
3f467ebe | 135 | static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv) |
41c42ff5 | 136 | { |
3f467ebe | 137 | struct fwnode_handle *fwnode; |
b795e6d9 | 138 | struct led_pwm led; |
95138e01 | 139 | int ret; |
41c42ff5 | 140 | |
b795e6d9 | 141 | memset(&led, 0, sizeof(led)); |
08541cbc | 142 | |
3f467ebe NV |
143 | device_for_each_child_node(dev, fwnode) { |
144 | ret = fwnode_property_read_string(fwnode, "label", &led.name); | |
145 | if (ret && is_of_node(fwnode)) | |
146 | led.name = to_of_node(fwnode)->name; | |
08541cbc | 147 | |
3f467ebe | 148 | if (!led.name) { |
95138e01 AS |
149 | ret = EINVAL; |
150 | goto err_child_out; | |
3f467ebe NV |
151 | } |
152 | ||
3f467ebe NV |
153 | led.active_low = fwnode_property_read_bool(fwnode, |
154 | "active-low"); | |
155 | fwnode_property_read_u32(fwnode, "max-brightness", | |
156 | &led.max_brightness); | |
c971ff18 | 157 | |
3d3d65bd DOH |
158 | led.default_state = led_init_default_state_get(fwnode); |
159 | ||
3f467ebe | 160 | ret = led_pwm_add(dev, priv, &led, fwnode); |
95138e01 AS |
161 | if (ret) |
162 | goto err_child_out; | |
08541cbc PU |
163 | } |
164 | ||
95138e01 AS |
165 | return 0; |
166 | ||
167 | err_child_out: | |
168 | fwnode_handle_put(fwnode); | |
aa1a6d6d | 169 | return ret; |
08541cbc PU |
170 | } |
171 | ||
172 | static int led_pwm_probe(struct platform_device *pdev) | |
173 | { | |
08541cbc | 174 | struct led_pwm_priv *priv; |
aa1a6d6d | 175 | int ret = 0; |
19d2e0ce | 176 | int count; |
aa1a6d6d | 177 | |
19d2e0ce | 178 | count = device_get_child_node_count(&pdev->dev); |
aa1a6d6d PU |
179 | |
180 | if (!count) | |
181 | return -EINVAL; | |
08541cbc | 182 | |
d4b02200 | 183 | priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count), |
aa1a6d6d PU |
184 | GFP_KERNEL); |
185 | if (!priv) | |
186 | return -ENOMEM; | |
08541cbc | 187 | |
19d2e0ce | 188 | ret = led_pwm_create_fwnode(&pdev->dev, priv); |
5f7b03dc | 189 | |
e5a0436d | 190 | if (ret) |
5f7b03dc | 191 | return ret; |
41c42ff5 | 192 | |
0f86815a | 193 | platform_set_drvdata(pdev, priv); |
41c42ff5 LF |
194 | |
195 | return 0; | |
41c42ff5 LF |
196 | } |
197 | ||
08541cbc PU |
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 | ||
41c42ff5 LF |
204 | static struct platform_driver led_pwm_driver = { |
205 | .probe = led_pwm_probe, | |
41c42ff5 LF |
206 | .driver = { |
207 | .name = "leds_pwm", | |
1e08f72d | 208 | .of_match_table = of_pwm_leds_match, |
41c42ff5 LF |
209 | }, |
210 | }; | |
211 | ||
892a8843 | 212 | module_platform_driver(led_pwm_driver); |
41c42ff5 LF |
213 | |
214 | MODULE_AUTHOR("Luotao Fu <[email protected]>"); | |
49651c6c UKK |
215 | MODULE_DESCRIPTION("generic PWM LED driver"); |
216 | MODULE_LICENSE("GPL v2"); | |
41c42ff5 | 217 | MODULE_ALIAS("platform:leds-pwm"); |