xref: /freebsd/sys/dev/pwm/pwm_backlight.c (revision dd41de95a84d979615a2ef11df6850622bf6184e)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2020 Emmanuel Vadot <manu@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD$
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/bio.h>
37 #include <sys/bus.h>
38 #include <sys/conf.h>
39 #include <sys/endian.h>
40 #include <sys/fcntl.h>
41 #include <sys/ioccom.h>
42 #include <sys/kernel.h>
43 #include <sys/kthread.h>
44 #include <sys/lock.h>
45 #include <sys/malloc.h>
46 #include <sys/module.h>
47 #include <sys/mutex.h>
48 #include <sys/priv.h>
49 #include <sys/slicer.h>
50 #include <sys/sysctl.h>
51 #include <sys/time.h>
52 
53 #include <dev/ofw/ofw_bus.h>
54 #include <dev/ofw/ofw_bus_subr.h>
55 
56 #include <dev/extres/regulator/regulator.h>
57 
58 #include <dev/backlight/backlight.h>
59 
60 #include <dev/pwm/ofw_pwm.h>
61 
62 #include "backlight_if.h"
63 #include "pwmbus_if.h"
64 
65 struct pwm_backlight_softc {
66 	device_t	pwmdev;
67 	struct cdev	*cdev;
68 
69 	pwm_channel_t	channel;
70 	uint32_t	*levels;
71 	ssize_t		nlevels;
72 	int		default_level;
73 	ssize_t		current_level;
74 
75 	regulator_t	power_supply;
76 	uint64_t	period;
77 	uint64_t	duty;
78 	bool		enabled;
79 };
80 
81 static int pwm_backlight_find_level_per_percent(struct pwm_backlight_softc *sc, int percent);
82 
83 static struct ofw_compat_data compat_data[] = {
84 	{ "pwm-backlight",	1 },
85 	{ NULL,			0 }
86 };
87 
88 static int
89 pwm_backlight_probe(device_t dev)
90 {
91 
92 	if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
93 		return (ENXIO);
94 
95 	device_set_desc(dev, "PWM Backlight");
96 	return (BUS_PROBE_DEFAULT);
97 }
98 
99 static int
100 pwm_backlight_attach(device_t dev)
101 {
102 	struct pwm_backlight_softc *sc;
103 	phandle_t node;
104 	int rv;
105 
106 	sc = device_get_softc(dev);
107 	node = ofw_bus_get_node(dev);
108 
109 	rv = pwm_get_by_ofw_propidx(dev, node, "pwms", 0, &sc->channel);
110 	if (rv != 0) {
111 		device_printf(dev, "Cannot map pwm channel %d\n", rv);
112 		return (ENXIO);
113 	}
114 
115 	if (regulator_get_by_ofw_property(dev, 0, "power-supply",
116 	    &sc->power_supply) != 0) {
117 		device_printf(dev, "No power-supply property\n");
118 		return (ENXIO);
119 	}
120 
121 	if (OF_hasprop(node, "brightness-levels")) {
122 		sc->nlevels = OF_getencprop_alloc(node, "brightness-levels",
123 		    (void **)&sc->levels);
124 		if (sc->nlevels <= 0) {
125 			device_printf(dev, "Cannot parse brightness levels\n");
126 			return (ENXIO);
127 		}
128 		sc->nlevels /= sizeof(uint32_t);
129 
130 		if (OF_getencprop(node, "default-brightness-level",
131 		    &sc->default_level, sizeof(uint32_t)) <= 0) {
132 			device_printf(dev, "No default-brightness-level while brightness-levels is specified\n");
133 			return (ENXIO);
134 		} else {
135 			if (sc->default_level > sc->nlevels) {
136 				device_printf(dev, "default-brightness-level isn't present in brightness-levels range\n");
137 				return (ENXIO);
138 			}
139 			sc->channel->duty = sc->channel->period * sc->levels[sc->default_level] / 100;
140 		}
141 
142 		if (bootverbose) {
143 			device_printf(dev, "Number of levels: %zd\n", sc->nlevels);
144 			device_printf(dev, "Configured period time: %ju\n", (uintmax_t)sc->channel->period);
145 			device_printf(dev, "Default duty cycle: %ju\n", (uintmax_t)sc->channel->duty);
146 		}
147 	} else {
148 		/* Get the current backlight level */
149 		PWMBUS_CHANNEL_GET_CONFIG(sc->channel->dev,
150 		    sc->channel->channel,
151 		    (unsigned int *)&sc->channel->period,
152 		    (unsigned int *)&sc->channel->duty);
153 		if (sc->channel->duty > sc->channel->period)
154 			sc->channel->duty = sc->channel->period;
155 		if (bootverbose) {
156 			device_printf(dev, "Configured period time: %ju\n", (uintmax_t)sc->channel->period);
157 			device_printf(dev, "Default duty cycle: %ju\n", (uintmax_t)sc->channel->duty);
158 		}
159 	}
160 
161 	regulator_enable(sc->power_supply);
162 	sc->channel->enabled = true;
163 	PWMBUS_CHANNEL_CONFIG(sc->channel->dev, sc->channel->channel,
164 	    sc->channel->period, sc->channel->duty);
165 	PWMBUS_CHANNEL_ENABLE(sc->channel->dev, sc->channel->channel,
166 	    sc->channel->enabled);
167 
168 	sc->current_level = pwm_backlight_find_level_per_percent(sc,
169 	    sc->channel->period / sc->channel->duty);
170 	sc->cdev = backlight_register("pwm_backlight", dev);
171 	if (sc->cdev == NULL)
172 		device_printf(dev, "Cannot register as a backlight\n");
173 
174 	return (0);
175 }
176 
177 static int
178 pwm_backlight_detach(device_t dev)
179 {
180 	struct pwm_backlight_softc *sc;
181 
182 	sc = device_get_softc(dev);
183 	if (sc->nlevels > 0)
184 		OF_prop_free(sc->levels);
185 	regulator_disable(sc->power_supply);
186 	backlight_destroy(sc->cdev);
187 	return (0);
188 }
189 
190 static int
191 pwm_backlight_find_level_per_percent(struct pwm_backlight_softc *sc, int percent)
192 {
193 	int i;
194 	int diff;
195 
196 	if (percent < 0 || percent > 100)
197 		return (-1);
198 
199 	for (i = 0, diff = 0; i < sc->nlevels; i++) {
200 		if (sc->levels[i] == percent)
201 			return (i);
202 		else if (sc->levels[i] < percent)
203 			diff = percent - sc->levels[i];
204 		else {
205 			if (diff < abs((percent - sc->levels[i])))
206 				return (i - 1);
207 			else
208 				return (i);
209 		}
210 	}
211 
212 	return (-1);
213 }
214 
215 static int
216 pwm_backlight_update_status(device_t dev, struct backlight_props *props)
217 {
218 	struct pwm_backlight_softc *sc;
219 	int reg_status;
220 	int error;
221 
222 	sc = device_get_softc(dev);
223 
224 	if (sc->nlevels != 0) {
225 		error = pwm_backlight_find_level_per_percent(sc,
226 		    props->brightness);
227 		if (error < 0)
228 			return (ERANGE);
229 		sc->current_level = error;
230 		sc->channel->duty = sc->channel->period *
231 			sc->levels[sc->current_level] / 100;
232 	} else {
233 		if (props->brightness > 100 || props->brightness < 0)
234 			return (ERANGE);
235 		sc->channel->duty = sc->channel->period *
236 			props->brightness / 100;
237 	}
238 	sc->channel->enabled = true;
239 	PWMBUS_CHANNEL_CONFIG(sc->channel->dev, sc->channel->channel,
240 	    sc->channel->period, sc->channel->duty);
241 	PWMBUS_CHANNEL_ENABLE(sc->channel->dev, sc->channel->channel,
242 	    sc->channel->enabled);
243 	error = regulator_status(sc->power_supply, &reg_status);
244 	if (error != 0)
245 		device_printf(dev,
246 		    "Cannot get power-supply status: %d\n", error);
247 	else {
248 		if (props->brightness > 0) {
249 			if (reg_status != REGULATOR_STATUS_ENABLED)
250 				regulator_enable(sc->power_supply);
251 		} else {
252 			if (reg_status == REGULATOR_STATUS_ENABLED)
253 				regulator_disable(sc->power_supply);
254 		}
255 	}
256 
257 	return (0);
258 }
259 
260 static int
261 pwm_backlight_get_status(device_t dev, struct backlight_props *props)
262 {
263 	struct pwm_backlight_softc *sc;
264 	int i;
265 
266 	sc = device_get_softc(dev);
267 
268 	if (sc->nlevels != 0) {
269 		props->brightness = sc->levels[sc->current_level];
270 		props->nlevels = sc->nlevels;
271 		for (i = 0; i < sc->nlevels; i++)
272 			props->levels[i] = sc->levels[i];
273 	} else {
274 		props->brightness = sc->channel->duty * 100 / sc->channel->period;
275 		props->nlevels = 0;
276 	}
277 	return (0);
278 }
279 
280 static int
281 pwm_backlight_get_info(device_t dev, struct backlight_info *info)
282 {
283 
284 	info->type = BACKLIGHT_TYPE_PANEL;
285 	strlcpy(info->name, "pwm-backlight", BACKLIGHTMAXNAMELENGTH);
286 	return (0);
287 }
288 
289 static device_method_t pwm_backlight_methods[] = {
290 	/* device_if */
291 	DEVMETHOD(device_probe, pwm_backlight_probe),
292 	DEVMETHOD(device_attach, pwm_backlight_attach),
293 	DEVMETHOD(device_detach, pwm_backlight_detach),
294 
295 	/* backlight interface */
296 	DEVMETHOD(backlight_update_status, pwm_backlight_update_status),
297 	DEVMETHOD(backlight_get_status, pwm_backlight_get_status),
298 	DEVMETHOD(backlight_get_info, pwm_backlight_get_info),
299 	DEVMETHOD_END
300 };
301 
302 driver_t pwm_backlight_driver = {
303 	"pwm_backlight",
304 	pwm_backlight_methods,
305 	sizeof(struct pwm_backlight_softc),
306 };
307 devclass_t pwm_backlight_devclass;
308 
309 DRIVER_MODULE(pwm_backlight, simplebus, pwm_backlight_driver,
310     pwm_backlight_devclass, 0, 0);
311 MODULE_DEPEND(pwm_backlight, backlight, 1, 1, 1);
312 OFWBUS_PNP_INFO(compat_data);
313