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