1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2020 Emmanuel Vadot <[email protected]>
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
pwm_backlight_probe(device_t dev)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
pwm_backlight_attach(device_t dev)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
pwm_backlight_detach(device_t dev)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
pwm_backlight_find_level_per_percent(struct pwm_backlight_softc * sc,int percent)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
pwm_backlight_update_status(device_t dev,struct backlight_props * props)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, ®_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
pwm_backlight_get_status(device_t dev,struct backlight_props * props)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
pwm_backlight_get_info(device_t dev,struct backlight_info * info)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