]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/pwm/pwm_backlight.c
Update OpenZFS to 2.0.0-rc3-gfc5966
[FreeBSD/FreeBSD.git] / sys / dev / pwm / pwm_backlight.c
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 OFWBUS_PNP_INFO(compat_data);