Index: head/sys/conf/files =================================================================== --- head/sys/conf/files +++ head/sys/conf/files @@ -2755,6 +2755,7 @@ dev/pwm/pwmbus_if.m optional pwm | pwmbus dev/pwm/ofw_pwm.c optional pwm fdt | pwmbus fdt dev/pwm/ofw_pwmbus.c optional pwm fdt | pwmbus fdt +dev/pwm/pwm_backlight.c optional pwm pwm_backlight fdt dev/quicc/quicc_core.c optional quicc dev/ral/rt2560.c optional ral dev/ral/rt2661.c optional ral Index: head/sys/dev/pwm/pwm_backlight.c =================================================================== --- head/sys/dev/pwm/pwm_backlight.c +++ head/sys/dev/pwm/pwm_backlight.c @@ -0,0 +1,311 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Emmanuel Vadot + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +__FBSDID("$FreeBSD$"); + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#include "backlight_if.h" +#include "pwmbus_if.h" + +struct pwm_backlight_softc { + device_t pwmdev; + struct cdev *cdev; + + pwm_channel_t channel; + uint32_t *levels; + ssize_t nlevels; + int default_level; + ssize_t current_level; + + regulator_t power_supply; + uint64_t period; + uint64_t duty; + bool enabled; +}; + +static int pwm_backlight_find_level_per_percent(struct pwm_backlight_softc *sc, int percent); + +static struct ofw_compat_data compat_data[] = { + { "pwm-backlight", 1 }, + { NULL, 0 } +}; + +static int +pwm_backlight_probe(device_t dev) +{ + + if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "PWM Backlight"); + return (BUS_PROBE_DEFAULT); +} + +static int +pwm_backlight_attach(device_t dev) +{ + struct pwm_backlight_softc *sc; + phandle_t node; + int rv; + + sc = device_get_softc(dev); + node = ofw_bus_get_node(dev); + + rv = pwm_get_by_ofw_propidx(dev, node, "pwms", 0, &sc->channel); + if (rv != 0) { + device_printf(dev, "Cannot map pwm channel %d\n", rv); + return (ENXIO); + } + + if (regulator_get_by_ofw_property(dev, 0, "power-supply", + &sc->power_supply) != 0) { + device_printf(dev, "No power-supply property\n"); + return (ENXIO); + } + + if (OF_hasprop(node, "brightness-levels")) { + sc->nlevels = OF_getencprop_alloc(node, "brightness-levels", + (void **)&sc->levels); + if (sc->nlevels <= 0) { + device_printf(dev, "Cannot parse brightness levels\n"); + return (ENXIO); + } + sc->nlevels /= sizeof(uint32_t); + + if (OF_getencprop(node, "default-brightness-level", + &sc->default_level, sizeof(uint32_t)) <= 0) { + device_printf(dev, "No default-brightness-level while brightness-levels is specified\n"); + return (ENXIO); + } else { + if (sc->default_level > sc->nlevels) { + device_printf(dev, "default-brightness-level isn't present in brightness-levels range\n"); + return (ENXIO); + } + sc->channel->duty = sc->channel->period * sc->levels[sc->default_level] / 100; + } + + if (bootverbose) { + device_printf(dev, "Number of levels: %zd\n", sc->nlevels); + device_printf(dev, "Configured period time: %lu\n", sc->channel->period); + device_printf(dev, "Default duty cycle: %lu\n", sc->channel->duty); + } + } else { + /* Get the current backlight level */ + PWMBUS_CHANNEL_GET_CONFIG(sc->channel->dev, + sc->channel->channel, + (unsigned int *)&sc->channel->period, + (unsigned int *)&sc->channel->duty); + if (sc->channel->duty > sc->channel->period) + sc->channel->duty = sc->channel->period; + if (bootverbose) { + device_printf(dev, "Configured period time: %lu\n", sc->channel->period); + device_printf(dev, "Default duty cycle: %lu\n", sc->channel->duty); + } + } + + regulator_enable(sc->power_supply); + sc->channel->enabled = true; + PWMBUS_CHANNEL_CONFIG(sc->channel->dev, sc->channel->channel, + sc->channel->period, sc->channel->duty); + PWMBUS_CHANNEL_ENABLE(sc->channel->dev, sc->channel->channel, + sc->channel->enabled); + + sc->current_level = pwm_backlight_find_level_per_percent(sc, + sc->channel->period / sc->channel->duty); + sc->cdev = backlight_register("pwm_backlight", dev); + if (sc->cdev == NULL) + device_printf(dev, "Cannot register as a backlight\n"); + + return (0); +} + +static int +pwm_backlight_detach(device_t dev) +{ + struct pwm_backlight_softc *sc; + + sc = device_get_softc(dev); + if (sc->nlevels > 0) + OF_prop_free(sc->levels); + regulator_disable(sc->power_supply); + backlight_destroy(sc->cdev); + return (0); +} + +static int +pwm_backlight_find_level_per_percent(struct pwm_backlight_softc *sc, int percent) +{ + int i; + int diff; + + if (percent < 0 || percent > 100) + return (-1); + + for (i = 0, diff = 0; i < sc->nlevels; i++) { + if (sc->levels[i] == percent) + return (i); + else if (sc->levels[i] < percent) + diff = percent - sc->levels[i]; + else { + if (diff < abs((percent - sc->levels[i]))) + return (i - 1); + else + return (i); + } + } + + return (-1); +} + +static int +pwm_backlight_update_status(device_t dev, struct backlight_props *props) +{ + struct pwm_backlight_softc *sc; + int reg_status; + int error; + + sc = device_get_softc(dev); + + if (sc->nlevels != 0) { + error = pwm_backlight_find_level_per_percent(sc, + props->brightness); + if (error < 0) + return (ERANGE); + sc->current_level = error; + sc->channel->duty = sc->channel->period * + sc->levels[sc->current_level] / 100; + } else { + if (props->brightness > 100 || props->brightness < 0) + return (ERANGE); + sc->channel->duty = sc->channel->period * + props->brightness / 100; + } + sc->channel->enabled = true; + PWMBUS_CHANNEL_CONFIG(sc->channel->dev, sc->channel->channel, + sc->channel->period, sc->channel->duty); + PWMBUS_CHANNEL_ENABLE(sc->channel->dev, sc->channel->channel, + sc->channel->enabled); + error = regulator_status(sc->power_supply, ®_status); + if (error != 0) + device_printf(dev, + "Cannot get power-supply status: %d\n", error); + else { + if (props->brightness > 0) { + if (reg_status != REGULATOR_STATUS_ENABLED) + regulator_enable(sc->power_supply); + } else { + if (reg_status == REGULATOR_STATUS_ENABLED) + regulator_disable(sc->power_supply); + } + } + + return (0); +} + +static int +pwm_backlight_get_status(device_t dev, struct backlight_props *props) +{ + struct pwm_backlight_softc *sc; + int i; + + sc = device_get_softc(dev); + + if (sc->nlevels != 0) { + props->brightness = sc->levels[sc->current_level]; + props->nlevels = sc->nlevels; + for (i = 0; i < sc->nlevels; i++) + props->levels[i] = sc->levels[i]; + } else { + props->brightness = sc->channel->duty * 100 / sc->channel->period; + props->nlevels = 0; + } + return (0); +} + +static int +pwm_backlight_get_info(device_t dev, struct backlight_info *info) +{ + + info->type = BACKLIGHT_TYPE_PANEL; + strlcpy(info->name, "pwm-backlight", BACKLIGHTMAXNAMELENGTH); + return (0); +} + +static device_method_t pwm_backlight_methods[] = { + /* device_if */ + DEVMETHOD(device_probe, pwm_backlight_probe), + DEVMETHOD(device_attach, pwm_backlight_attach), + DEVMETHOD(device_detach, pwm_backlight_detach), + + /* backlight interface */ + DEVMETHOD(backlight_update_status, pwm_backlight_update_status), + DEVMETHOD(backlight_get_status, pwm_backlight_get_status), + DEVMETHOD(backlight_get_info, pwm_backlight_get_info), + DEVMETHOD_END +}; + +driver_t pwm_backlight_driver = { + "pwm_backlight", + pwm_backlight_methods, + sizeof(struct pwm_backlight_softc), +}; +devclass_t pwm_backlight_devclass; + +DRIVER_MODULE(pwm_backlight, simplebus, pwm_backlight_driver, + pwm_backlight_devclass, 0, 0); +OFWBUS_PNP_INFO(compat_data); Index: head/sys/modules/pwm/Makefile =================================================================== --- head/sys/modules/pwm/Makefile +++ head/sys/modules/pwm/Makefile @@ -5,5 +5,9 @@ SUBDIR = \ pwmbus \ pwmc \ - + +.if !empty(OPT_FDT) +SUBDIR += pwm_backlight +.endif + .include Index: head/sys/modules/pwm/pwm_backlight/Makefile =================================================================== --- head/sys/modules/pwm/pwm_backlight/Makefile +++ head/sys/modules/pwm/pwm_backlight/Makefile @@ -0,0 +1,15 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/pwm +KMOD= pwm_backlight +SRCS= pwm_backlight.c + +SRCS+= \ + backlight_if.h \ + bus_if.h \ + device_if.h \ + opt_platform.h \ + pwmbus_if.h \ + ofw_bus_if.h + +.include