xref: /freebsd/sys/dev/pwm/pwm_backlight.c (revision b3e7694832e81d7a904a10f525f8797b753bf0d3)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/bio.h>
35 #include <sys/bus.h>
36 #include <sys/conf.h>
37 #include <sys/endian.h>
38 #include <sys/fcntl.h>
39 #include <sys/ioccom.h>
40 #include <sys/kernel.h>
41 #include <sys/kthread.h>
42 #include <sys/lock.h>
43 #include <sys/malloc.h>
44 #include <sys/module.h>
45 #include <sys/mutex.h>
46 #include <sys/priv.h>
47 #include <sys/slicer.h>
48 #include <sys/sysctl.h>
49 #include <sys/time.h>
50 
51 #include <dev/ofw/ofw_bus.h>
52 #include <dev/ofw/ofw_bus_subr.h>
53 
54 #include <dev/extres/regulator/regulator.h>
55 
56 #include <dev/backlight/backlight.h>
57 
58 #include <dev/pwm/ofw_pwm.h>
59 
60 #include "backlight_if.h"
61 #include "pwmbus_if.h"
62 
63 struct pwm_backlight_softc {
64 	device_t	pwmdev;
65 	struct cdev	*cdev;
66 
67 	pwm_channel_t	channel;
68 	uint32_t	*levels;
69 	ssize_t		nlevels;
70 	int		default_level;
71 	ssize_t		current_level;
72 
73 	regulator_t	power_supply;
74 	uint64_t	period;
75 	uint64_t	duty;
76 	bool		enabled;
77 };
78 
79 static int pwm_backlight_find_level_per_percent(struct pwm_backlight_softc *sc, int percent);
80 
81 static struct ofw_compat_data compat_data[] = {
82 	{ "pwm-backlight",	1 },
83 	{ NULL,			0 }
84 };
85 
86 static int
87 pwm_backlight_probe(device_t dev)
88 {
89 
90 	if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
91 		return (ENXIO);
92 
93 	device_set_desc(dev, "PWM Backlight");
94 	return (BUS_PROBE_DEFAULT);
95 }
96 
97 static int
98 pwm_backlight_attach(device_t dev)
99 {
100 	struct pwm_backlight_softc *sc;
101 	phandle_t node;
102 	int rv;
103 
104 	sc = device_get_softc(dev);
105 	node = ofw_bus_get_node(dev);
106 
107 	rv = pwm_get_by_ofw_propidx(dev, node, "pwms", 0, &sc->channel);
108 	if (rv != 0) {
109 		device_printf(dev, "Cannot map pwm channel %d\n", rv);
110 		return (ENXIO);
111 	}
112 
113 	if (regulator_get_by_ofw_property(dev, 0, "power-supply",
114 	    &sc->power_supply) != 0) {
115 		device_printf(dev, "No power-supply property\n");
116 		return (ENXIO);
117 	}
118 
119 	if (OF_hasprop(node, "brightness-levels")) {
120 		sc->nlevels = OF_getencprop_alloc(node, "brightness-levels",
121 		    (void **)&sc->levels);
122 		if (sc->nlevels <= 0) {
123 			device_printf(dev, "Cannot parse brightness levels\n");
124 			return (ENXIO);
125 		}
126 		sc->nlevels /= sizeof(uint32_t);
127 
128 		if (OF_getencprop(node, "default-brightness-level",
129 		    &sc->default_level, sizeof(uint32_t)) <= 0) {
130 			device_printf(dev, "No default-brightness-level while brightness-levels is specified\n");
131 			return (ENXIO);
132 		} else {
133 			if (sc->default_level > sc->nlevels) {
134 				device_printf(dev, "default-brightness-level isn't present in brightness-levels range\n");
135 				return (ENXIO);
136 			}
137 			sc->channel->duty = sc->channel->period * sc->levels[sc->default_level] / 100;
138 		}
139 
140 		if (bootverbose) {
141 			device_printf(dev, "Number of levels: %zd\n", sc->nlevels);
142 			device_printf(dev, "Configured period time: %ju\n", (uintmax_t)sc->channel->period);
143 			device_printf(dev, "Default duty cycle: %ju\n", (uintmax_t)sc->channel->duty);
144 		}
145 	} else {
146 		/* Get the current backlight level */
147 		PWMBUS_CHANNEL_GET_CONFIG(sc->channel->dev,
148 		    sc->channel->channel,
149 		    (unsigned int *)&sc->channel->period,
150 		    (unsigned int *)&sc->channel->duty);
151 		if (sc->channel->duty > sc->channel->period)
152 			sc->channel->duty = sc->channel->period;
153 		if (bootverbose) {
154 			device_printf(dev, "Configured period time: %ju\n", (uintmax_t)sc->channel->period);
155 			device_printf(dev, "Default duty cycle: %ju\n", (uintmax_t)sc->channel->duty);
156 		}
157 	}
158 
159 	regulator_enable(sc->power_supply);
160 	sc->channel->enabled = true;
161 	PWMBUS_CHANNEL_CONFIG(sc->channel->dev, sc->channel->channel,
162 	    sc->channel->period, sc->channel->duty);
163 	PWMBUS_CHANNEL_ENABLE(sc->channel->dev, sc->channel->channel,
164 	    sc->channel->enabled);
165 
166 	sc->current_level = pwm_backlight_find_level_per_percent(sc,
167 	    sc->channel->period / sc->channel->duty);
168 	sc->cdev = backlight_register("pwm_backlight", dev);
169 	if (sc->cdev == NULL)
170 		device_printf(dev, "Cannot register as a backlight\n");
171 
172 	return (0);
173 }
174 
175 static int
176 pwm_backlight_detach(device_t dev)
177 {
178 	struct pwm_backlight_softc *sc;
179 
180 	sc = device_get_softc(dev);
181 	if (sc->nlevels > 0)
182 		OF_prop_free(sc->levels);
183 	regulator_disable(sc->power_supply);
184 	backlight_destroy(sc->cdev);
185 	return (0);
186 }
187 
188 static int
189 pwm_backlight_find_level_per_percent(struct pwm_backlight_softc *sc, int percent)
190 {
191 	int i;
192 	int diff;
193 
194 	if (percent < 0 || percent > 100)
195 		return (-1);
196 
197 	for (i = 0, diff = 0; i < sc->nlevels; i++) {
198 		if (sc->levels[i] == percent)
199 			return (i);
200 		else if (sc->levels[i] < percent)
201 			diff = percent - sc->levels[i];
202 		else {
203 			if (diff < abs((percent - sc->levels[i])))
204 				return (i - 1);
205 			else
206 				return (i);
207 		}
208 	}
209 
210 	return (-1);
211 }
212 
213 static int
214 pwm_backlight_update_status(device_t dev, struct backlight_props *props)
215 {
216 	struct pwm_backlight_softc *sc;
217 	int reg_status;
218 	int error;
219 
220 	sc = device_get_softc(dev);
221 
222 	if (sc->nlevels != 0) {
223 		error = pwm_backlight_find_level_per_percent(sc,
224 		    props->brightness);
225 		if (error < 0)
226 			return (ERANGE);
227 		sc->current_level = error;
228 		sc->channel->duty = sc->channel->period *
229 			sc->levels[sc->current_level] / 100;
230 	} else {
231 		if (props->brightness > 100 || props->brightness < 0)
232 			return (ERANGE);
233 		sc->channel->duty = sc->channel->period *
234 			props->brightness / 100;
235 	}
236 	sc->channel->enabled = true;
237 	PWMBUS_CHANNEL_CONFIG(sc->channel->dev, sc->channel->channel,
238 	    sc->channel->period, sc->channel->duty);
239 	PWMBUS_CHANNEL_ENABLE(sc->channel->dev, sc->channel->channel,
240 	    sc->channel->enabled);
241 	error = regulator_status(sc->power_supply, &reg_status);
242 	if (error != 0)
243 		device_printf(dev,
244 		    "Cannot get power-supply status: %d\n", error);
245 	else {
246 		if (props->brightness > 0) {
247 			if (reg_status != REGULATOR_STATUS_ENABLED)
248 				regulator_enable(sc->power_supply);
249 		} else {
250 			if (reg_status == REGULATOR_STATUS_ENABLED)
251 				regulator_disable(sc->power_supply);
252 		}
253 	}
254 
255 	return (0);
256 }
257 
258 static int
259 pwm_backlight_get_status(device_t dev, struct backlight_props *props)
260 {
261 	struct pwm_backlight_softc *sc;
262 	int i;
263 
264 	sc = device_get_softc(dev);
265 
266 	if (sc->nlevels != 0) {
267 		props->brightness = sc->levels[sc->current_level];
268 		props->nlevels = sc->nlevels;
269 		for (i = 0; i < sc->nlevels; i++)
270 			props->levels[i] = sc->levels[i];
271 	} else {
272 		props->brightness = sc->channel->duty * 100 / sc->channel->period;
273 		props->nlevels = 0;
274 	}
275 	return (0);
276 }
277 
278 static int
279 pwm_backlight_get_info(device_t dev, struct backlight_info *info)
280 {
281 
282 	info->type = BACKLIGHT_TYPE_PANEL;
283 	strlcpy(info->name, "pwm-backlight", BACKLIGHTMAXNAMELENGTH);
284 	return (0);
285 }
286 
287 static device_method_t pwm_backlight_methods[] = {
288 	/* device_if */
289 	DEVMETHOD(device_probe, pwm_backlight_probe),
290 	DEVMETHOD(device_attach, pwm_backlight_attach),
291 	DEVMETHOD(device_detach, pwm_backlight_detach),
292 
293 	/* backlight interface */
294 	DEVMETHOD(backlight_update_status, pwm_backlight_update_status),
295 	DEVMETHOD(backlight_get_status, pwm_backlight_get_status),
296 	DEVMETHOD(backlight_get_info, pwm_backlight_get_info),
297 	DEVMETHOD_END
298 };
299 
300 driver_t pwm_backlight_driver = {
301 	"pwm_backlight",
302 	pwm_backlight_methods,
303 	sizeof(struct pwm_backlight_softc),
304 };
305 
306 DRIVER_MODULE(pwm_backlight, simplebus, pwm_backlight_driver, 0, 0);
307 MODULE_DEPEND(pwm_backlight, backlight, 1, 1, 1);
308 OFWBUS_PNP_INFO(compat_data);
309