2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2020 Emmanuel Vadot <manu@FreeBSD.org>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
34 #include <sys/param.h>
35 #include <sys/systm.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>
45 #include <sys/malloc.h>
46 #include <sys/module.h>
47 #include <sys/mutex.h>
49 #include <sys/slicer.h>
50 #include <sys/sysctl.h>
53 #include <dev/ofw/ofw_bus.h>
54 #include <dev/ofw/ofw_bus_subr.h>
56 #include <dev/extres/regulator/regulator.h>
58 #include <dev/backlight/backlight.h>
60 #include <dev/pwm/ofw_pwm.h>
62 #include "backlight_if.h"
63 #include "pwmbus_if.h"
65 struct pwm_backlight_softc {
69 pwm_channel_t channel;
73 ssize_t current_level;
75 regulator_t power_supply;
81 static int pwm_backlight_find_level_per_percent(struct pwm_backlight_softc *sc, int percent);
83 static struct ofw_compat_data compat_data[] = {
84 { "pwm-backlight", 1 },
89 pwm_backlight_probe(device_t dev)
92 if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
95 device_set_desc(dev, "PWM Backlight");
96 return (BUS_PROBE_DEFAULT);
100 pwm_backlight_attach(device_t dev)
102 struct pwm_backlight_softc *sc;
106 sc = device_get_softc(dev);
107 node = ofw_bus_get_node(dev);
109 rv = pwm_get_by_ofw_propidx(dev, node, "pwms", 0, &sc->channel);
111 device_printf(dev, "Cannot map pwm channel %d\n", rv);
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");
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");
128 sc->nlevels /= sizeof(uint32_t);
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");
135 if (sc->default_level > sc->nlevels) {
136 device_printf(dev, "default-brightness-level isn't present in brightness-levels range\n");
139 sc->channel->duty = sc->channel->period * sc->levels[sc->default_level] / 100;
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);
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;
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);
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);
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");
178 pwm_backlight_detach(device_t dev)
180 struct pwm_backlight_softc *sc;
182 sc = device_get_softc(dev);
184 OF_prop_free(sc->levels);
185 regulator_disable(sc->power_supply);
186 backlight_destroy(sc->cdev);
191 pwm_backlight_find_level_per_percent(struct pwm_backlight_softc *sc, int percent)
196 if (percent < 0 || percent > 100)
199 for (i = 0, diff = 0; i < sc->nlevels; i++) {
200 if (sc->levels[i] == percent)
202 else if (sc->levels[i] < percent)
203 diff = percent - sc->levels[i];
205 if (diff < abs((percent - sc->levels[i])))
216 pwm_backlight_update_status(device_t dev, struct backlight_props *props)
218 struct pwm_backlight_softc *sc;
222 sc = device_get_softc(dev);
224 if (sc->nlevels != 0) {
225 error = pwm_backlight_find_level_per_percent(sc,
229 sc->current_level = error;
230 sc->channel->duty = sc->channel->period *
231 sc->levels[sc->current_level] / 100;
233 if (props->brightness > 100 || props->brightness < 0)
235 sc->channel->duty = sc->channel->period *
236 props->brightness / 100;
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);
246 "Cannot get power-supply status: %d\n", error);
248 if (props->brightness > 0) {
249 if (reg_status != REGULATOR_STATUS_ENABLED)
250 regulator_enable(sc->power_supply);
252 if (reg_status == REGULATOR_STATUS_ENABLED)
253 regulator_disable(sc->power_supply);
261 pwm_backlight_get_status(device_t dev, struct backlight_props *props)
263 struct pwm_backlight_softc *sc;
266 sc = device_get_softc(dev);
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];
274 props->brightness = sc->channel->duty * 100 / sc->channel->period;
281 pwm_backlight_get_info(device_t dev, struct backlight_info *info)
284 info->type = BACKLIGHT_TYPE_PANEL;
285 strlcpy(info->name, "pwm-backlight", BACKLIGHTMAXNAMELENGTH);
289 static device_method_t pwm_backlight_methods[] = {
291 DEVMETHOD(device_probe, pwm_backlight_probe),
292 DEVMETHOD(device_attach, pwm_backlight_attach),
293 DEVMETHOD(device_detach, pwm_backlight_detach),
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),
302 driver_t pwm_backlight_driver = {
304 pwm_backlight_methods,
305 sizeof(struct pwm_backlight_softc),
307 devclass_t pwm_backlight_devclass;
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);