xref: /openbmc/linux/drivers/video/backlight/pwm_bl.c (revision ee89bd6b)
1 /*
2  * linux/drivers/video/backlight/pwm_bl.c
3  *
4  * simple PWM based backlight control, board code has to setup
5  * 1) pin configuration so PWM waveforms can output
6  * 2) platform_data being correctly configured
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2 as
10  * published by the Free Software Foundation.
11  */
12 
13 #include <linux/module.h>
14 #include <linux/kernel.h>
15 #include <linux/init.h>
16 #include <linux/platform_device.h>
17 #include <linux/fb.h>
18 #include <linux/backlight.h>
19 #include <linux/err.h>
20 #include <linux/pwm.h>
21 #include <linux/pwm_backlight.h>
22 #include <linux/slab.h>
23 
24 struct pwm_bl_data {
25 	struct pwm_device	*pwm;
26 	struct device		*dev;
27 	unsigned int		period;
28 	unsigned int		lth_brightness;
29 	unsigned int		*levels;
30 	int			(*notify)(struct device *,
31 					  int brightness);
32 	void			(*notify_after)(struct device *,
33 					int brightness);
34 	int			(*check_fb)(struct device *, struct fb_info *);
35 	void			(*exit)(struct device *);
36 };
37 
38 static int pwm_backlight_update_status(struct backlight_device *bl)
39 {
40 	struct pwm_bl_data *pb = bl_get_data(bl);
41 	int brightness = bl->props.brightness;
42 	int max = bl->props.max_brightness;
43 
44 	if (bl->props.power != FB_BLANK_UNBLANK ||
45 	    bl->props.fb_blank != FB_BLANK_UNBLANK ||
46 	    bl->props.state & BL_CORE_FBBLANK)
47 		brightness = 0;
48 
49 	if (pb->notify)
50 		brightness = pb->notify(pb->dev, brightness);
51 
52 	if (brightness == 0) {
53 		pwm_config(pb->pwm, 0, pb->period);
54 		pwm_disable(pb->pwm);
55 	} else {
56 		int duty_cycle;
57 
58 		if (pb->levels) {
59 			duty_cycle = pb->levels[brightness];
60 			max = pb->levels[max];
61 		} else {
62 			duty_cycle = brightness;
63 		}
64 
65 		duty_cycle = pb->lth_brightness +
66 		     (duty_cycle * (pb->period - pb->lth_brightness) / max);
67 		pwm_config(pb->pwm, duty_cycle, pb->period);
68 		pwm_enable(pb->pwm);
69 	}
70 
71 	if (pb->notify_after)
72 		pb->notify_after(pb->dev, brightness);
73 
74 	return 0;
75 }
76 
77 static int pwm_backlight_get_brightness(struct backlight_device *bl)
78 {
79 	return bl->props.brightness;
80 }
81 
82 static int pwm_backlight_check_fb(struct backlight_device *bl,
83 				  struct fb_info *info)
84 {
85 	struct pwm_bl_data *pb = bl_get_data(bl);
86 
87 	return !pb->check_fb || pb->check_fb(pb->dev, info);
88 }
89 
90 static const struct backlight_ops pwm_backlight_ops = {
91 	.update_status	= pwm_backlight_update_status,
92 	.get_brightness	= pwm_backlight_get_brightness,
93 	.check_fb	= pwm_backlight_check_fb,
94 };
95 
96 #ifdef CONFIG_OF
97 static int pwm_backlight_parse_dt(struct device *dev,
98 				  struct platform_pwm_backlight_data *data)
99 {
100 	struct device_node *node = dev->of_node;
101 	struct property *prop;
102 	int length;
103 	u32 value;
104 	int ret;
105 
106 	if (!node)
107 		return -ENODEV;
108 
109 	memset(data, 0, sizeof(*data));
110 
111 	/* determine the number of brightness levels */
112 	prop = of_find_property(node, "brightness-levels", &length);
113 	if (!prop)
114 		return -EINVAL;
115 
116 	data->max_brightness = length / sizeof(u32);
117 
118 	/* read brightness levels from DT property */
119 	if (data->max_brightness > 0) {
120 		size_t size = sizeof(*data->levels) * data->max_brightness;
121 
122 		data->levels = devm_kzalloc(dev, size, GFP_KERNEL);
123 		if (!data->levels)
124 			return -ENOMEM;
125 
126 		ret = of_property_read_u32_array(node, "brightness-levels",
127 						 data->levels,
128 						 data->max_brightness);
129 		if (ret < 0)
130 			return ret;
131 
132 		ret = of_property_read_u32(node, "default-brightness-level",
133 					   &value);
134 		if (ret < 0)
135 			return ret;
136 
137 		data->dft_brightness = value;
138 		data->max_brightness--;
139 	}
140 
141 	/*
142 	 * TODO: Most users of this driver use a number of GPIOs to control
143 	 *       backlight power. Support for specifying these needs to be
144 	 *       added.
145 	 */
146 
147 	return 0;
148 }
149 
150 static struct of_device_id pwm_backlight_of_match[] = {
151 	{ .compatible = "pwm-backlight" },
152 	{ }
153 };
154 
155 MODULE_DEVICE_TABLE(of, pwm_backlight_of_match);
156 #else
157 static int pwm_backlight_parse_dt(struct device *dev,
158 				  struct platform_pwm_backlight_data *data)
159 {
160 	return -ENODEV;
161 }
162 #endif
163 
164 static int pwm_backlight_probe(struct platform_device *pdev)
165 {
166 	struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
167 	struct platform_pwm_backlight_data defdata;
168 	struct backlight_properties props;
169 	struct backlight_device *bl;
170 	struct pwm_bl_data *pb;
171 	unsigned int max;
172 	int ret;
173 
174 	if (!data) {
175 		ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
176 		if (ret < 0) {
177 			dev_err(&pdev->dev, "failed to find platform data\n");
178 			return ret;
179 		}
180 
181 		data = &defdata;
182 	}
183 
184 	if (data->init) {
185 		ret = data->init(&pdev->dev);
186 		if (ret < 0)
187 			return ret;
188 	}
189 
190 	pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL);
191 	if (!pb) {
192 		dev_err(&pdev->dev, "no memory for state\n");
193 		ret = -ENOMEM;
194 		goto err_alloc;
195 	}
196 
197 	if (data->levels) {
198 		max = data->levels[data->max_brightness];
199 		pb->levels = data->levels;
200 	} else
201 		max = data->max_brightness;
202 
203 	pb->notify = data->notify;
204 	pb->notify_after = data->notify_after;
205 	pb->check_fb = data->check_fb;
206 	pb->exit = data->exit;
207 	pb->dev = &pdev->dev;
208 
209 	pb->pwm = devm_pwm_get(&pdev->dev, NULL);
210 	if (IS_ERR(pb->pwm)) {
211 		dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
212 
213 		pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
214 		if (IS_ERR(pb->pwm)) {
215 			dev_err(&pdev->dev, "unable to request legacy PWM\n");
216 			ret = PTR_ERR(pb->pwm);
217 			goto err_alloc;
218 		}
219 	}
220 
221 	dev_dbg(&pdev->dev, "got pwm for backlight\n");
222 
223 	/*
224 	 * The DT case will set the pwm_period_ns field to 0 and store the
225 	 * period, parsed from the DT, in the PWM device. For the non-DT case,
226 	 * set the period from platform data.
227 	 */
228 	if (data->pwm_period_ns > 0)
229 		pwm_set_period(pb->pwm, data->pwm_period_ns);
230 
231 	pb->period = pwm_get_period(pb->pwm);
232 	pb->lth_brightness = data->lth_brightness * (pb->period / max);
233 
234 	memset(&props, 0, sizeof(struct backlight_properties));
235 	props.type = BACKLIGHT_RAW;
236 	props.max_brightness = data->max_brightness;
237 	bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
238 				       &pwm_backlight_ops, &props);
239 	if (IS_ERR(bl)) {
240 		dev_err(&pdev->dev, "failed to register backlight\n");
241 		ret = PTR_ERR(bl);
242 		goto err_alloc;
243 	}
244 
245 	if (data->dft_brightness > data->max_brightness) {
246 		dev_warn(&pdev->dev,
247 			 "invalid default brightness level: %u, using %u\n",
248 			 data->dft_brightness, data->max_brightness);
249 		data->dft_brightness = data->max_brightness;
250 	}
251 
252 	bl->props.brightness = data->dft_brightness;
253 	backlight_update_status(bl);
254 
255 	platform_set_drvdata(pdev, bl);
256 	return 0;
257 
258 err_alloc:
259 	if (data->exit)
260 		data->exit(&pdev->dev);
261 	return ret;
262 }
263 
264 static int pwm_backlight_remove(struct platform_device *pdev)
265 {
266 	struct backlight_device *bl = platform_get_drvdata(pdev);
267 	struct pwm_bl_data *pb = bl_get_data(bl);
268 
269 	backlight_device_unregister(bl);
270 	pwm_config(pb->pwm, 0, pb->period);
271 	pwm_disable(pb->pwm);
272 	if (pb->exit)
273 		pb->exit(&pdev->dev);
274 	return 0;
275 }
276 
277 #ifdef CONFIG_PM_SLEEP
278 static int pwm_backlight_suspend(struct device *dev)
279 {
280 	struct backlight_device *bl = dev_get_drvdata(dev);
281 	struct pwm_bl_data *pb = bl_get_data(bl);
282 
283 	if (pb->notify)
284 		pb->notify(pb->dev, 0);
285 	pwm_config(pb->pwm, 0, pb->period);
286 	pwm_disable(pb->pwm);
287 	if (pb->notify_after)
288 		pb->notify_after(pb->dev, 0);
289 	return 0;
290 }
291 
292 static int pwm_backlight_resume(struct device *dev)
293 {
294 	struct backlight_device *bl = dev_get_drvdata(dev);
295 
296 	backlight_update_status(bl);
297 	return 0;
298 }
299 #endif
300 
301 static SIMPLE_DEV_PM_OPS(pwm_backlight_pm_ops, pwm_backlight_suspend,
302 			 pwm_backlight_resume);
303 
304 static struct platform_driver pwm_backlight_driver = {
305 	.driver		= {
306 		.name		= "pwm-backlight",
307 		.owner		= THIS_MODULE,
308 		.pm		= &pwm_backlight_pm_ops,
309 		.of_match_table	= of_match_ptr(pwm_backlight_of_match),
310 	},
311 	.probe		= pwm_backlight_probe,
312 	.remove		= pwm_backlight_remove,
313 };
314 
315 module_platform_driver(pwm_backlight_driver);
316 
317 MODULE_DESCRIPTION("PWM based Backlight Driver");
318 MODULE_LICENSE("GPL");
319 MODULE_ALIAS("platform:pwm-backlight");
320 
321