diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2019-10-21 20:52:34 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2019-10-21 20:52:34 +0000 |
commit | 709446bf07c5f824ba8ecdcea2e0e279dcfb2498 (patch) | |
tree | 1cd0d2e38bd010099aa8f6a3f8d89b83731d5574 | |
parent | 7d77fc2d19559804abde1369627b9e6909c14490 (diff) |
Add sxipwm(4) and pwmbl(4). Thse two drivers together add support for the
backlight controller on the Pinebook.
ok patrick@, jsg@
-rw-r--r-- | sys/arch/arm64/conf/GENERIC | 4 | ||||
-rw-r--r-- | sys/dev/fdt/files.fdt | 10 | ||||
-rw-r--r-- | sys/dev/fdt/pwmbl.c | 205 | ||||
-rw-r--r-- | sys/dev/fdt/sxipwm.c | 233 |
4 files changed, 450 insertions, 2 deletions
diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC index 9103787dcd9..6d41ca4e311 100644 --- a/sys/arch/arm64/conf/GENERIC +++ b/sys/arch/arm64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.130 2019/10/17 22:32:06 kettenis Exp $ +# $OpenBSD: GENERIC,v 1.131 2019/10/21 20:52:32 kettenis Exp $ # # GENERIC machine description file # @@ -105,6 +105,7 @@ option WSDISPLAY_DEFAULTSCREENS=6 # initial number of text consoles simplefb* at fdt? wsdisplay* at simplefb? +pwmbl* at fdt? # PWM backlight radeondrm* at pci? drm* at radeondrm? @@ -200,6 +201,7 @@ sxipio* at fdt? early 1 # GPIO pins for leds & PHYs gpio* at sxipio? sxiccmu* at fdt? early 1 # Clock Control Module/Unit sxidog* at fdt? +sxipwm* at fdt? sxirsb* at fdt? early 1 # Reduced Serial Bus axppmic* at rsb? sxirtc* at fdt? early 1 # Real Time Clock diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt index b2f0edc584f..731579e52de 100644 --- a/sys/dev/fdt/files.fdt +++ b/sys/dev/fdt/files.fdt @@ -1,4 +1,4 @@ -# $OpenBSD: files.fdt,v 1.100 2019/10/16 22:11:17 kettenis Exp $ +# $OpenBSD: files.fdt,v 1.101 2019/10/21 20:52:33 kettenis Exp $ # # Config file and device description for machine-independent FDT code. # Included by ports that need it. @@ -28,6 +28,10 @@ device sxirsb: rsb attach sxirsb at fdt file dev/fdt/sxirsb.c sxirsb +device sxipwm +attach sxipwm at fdt +file dev/fdt/sxipwm.c sxipwm + device sxirtc attach sxirtc at fdt file dev/fdt/sxirtc.c sxirtc @@ -132,6 +136,10 @@ device syscon: fdt attach syscon at fdt file dev/fdt/syscon.c syscon +device pwmbl +attach pwmbl at fdt +file dev/fdt/pwmbl.c pwmbl + device pwmreg attach pwmreg at fdt file dev/fdt/pwmreg.c pwmreg diff --git a/sys/dev/fdt/pwmbl.c b/sys/dev/fdt/pwmbl.c new file mode 100644 index 00000000000..d2c336568b8 --- /dev/null +++ b/sys/dev/fdt/pwmbl.c @@ -0,0 +1,205 @@ +/* $OpenBSD: pwmbl.c,v 1.1 2019/10/21 20:52:33 kettenis Exp $ */ +/* + * Copyright (c) 2019 Krystian Lewandowski + * Copyright (c) 2019 Mark Kettenis <kettenis@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/malloc.h> + +#include <machine/fdt.h> +#include <machine/bus.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_gpio.h> +#include <dev/ofw/ofw_misc.h> + +#include <dev/wscons/wsconsio.h> +#include <dev/wscons/wsdisplayvar.h> + +struct pwmbl_softc { + struct device sc_dev; + uint32_t *sc_pwm; + int sc_pwm_len; + uint32_t *sc_levels; + int sc_nlevels; + uint32_t sc_max_level; + uint32_t sc_def_level; +}; + +struct pwmbl_softc *sc_pwmbl; + +int pwmbl_match(struct device *, void *, void *); +void pwmbl_attach(struct device *, struct device *, void *); + +struct cfattach pwmbl_ca = { + sizeof(struct pwmbl_softc), pwmbl_match, pwmbl_attach +}; + +struct cfdriver pwmbl_cd = { + NULL, "pwmbl", DV_DULL +}; + +int pwmbl_get_brightness(void *, uint32_t *); +int pwmbl_set_brightness(void *, uint32_t); +int pwmbl_get_param(struct wsdisplay_param *); +int pwmbl_set_param(struct wsdisplay_param *); + +int +pwmbl_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + return OF_is_compatible(faa->fa_node, "pwm-backlight"); +} + +void +pwmbl_attach(struct device *parent, struct device *self, void *aux) +{ + struct pwmbl_softc *sc = (struct pwmbl_softc *)self; + struct fdt_attach_args *faa = aux; + uint32_t *gpios; + int len; + + len = OF_getproplen(faa->fa_node, "pwms"); + if (len < 0) { + printf(": no pwm\n"); + return; + } + + sc->sc_pwm = malloc(len, M_DEVBUF, M_WAITOK); + OF_getpropintarray(faa->fa_node, "pwms", sc->sc_pwm, len); + sc->sc_pwm_len = len; + + len = OF_getproplen(faa->fa_node, "enable-gpios"); + if (len < 0) { + free(sc->sc_pwm, M_DEVBUF, sc->sc_pwm_len); + printf(": no gpio\n"); + return; + } + + gpios = malloc(len, M_TEMP, M_WAITOK); + OF_getpropintarray(faa->fa_node, "enable-gpios", gpios, len); + gpio_controller_config_pin(&gpios[0], GPIO_CONFIG_OUTPUT); + gpio_controller_set_pin(&gpios[0], 1); + free(gpios, M_TEMP, len); + + len = OF_getproplen(faa->fa_node, "brightness-levels"); + if (len < 0) { + free(sc->sc_pwm, M_DEVBUF, sc->sc_pwm_len); + printf(": no brightness levels\n"); + return; + } + + printf("\n"); + + sc->sc_levels = malloc(len, M_DEVBUF, M_WAITOK); + OF_getpropintarray(faa->fa_node, "brightness-levels", + sc->sc_levels, len); + sc->sc_nlevels = len / sizeof(uint32_t); + + sc->sc_max_level = sc->sc_levels[sc->sc_nlevels - 1]; + sc->sc_def_level = OF_getpropint(faa->fa_node, + "default-brightness-level", sc->sc_max_level); + + sc_pwmbl = sc; + ws_get_param = pwmbl_get_param; + ws_set_param = pwmbl_set_param; +} + +int +pwmbl_get_brightness(void *cookie, uint32_t *level) +{ + struct pwmbl_softc *sc = cookie; + struct pwm_state ps; + + if (pwm_get_state(sc->sc_pwm, &ps)) + return EINVAL; + + *level = (ps.ps_pulse_width * sc->sc_max_level) / ps.ps_period; + return 0; +} + +uint32_t +pwmbl_find_brightness(struct pwmbl_softc *sc, uint32_t level) +{ + uint32_t mid; + int i; + + for (i = 0; i < sc->sc_nlevels - 1; i++) { + mid = (sc->sc_levels[i] + sc->sc_levels[i + 1]) / 2; + if (sc->sc_levels[i] <= level && level <= mid) + return sc->sc_levels[i]; + if (mid < level && level <= sc->sc_levels[i + 1]) + return sc->sc_levels[i + 1]; + } + if (level < sc->sc_levels[0]) + return sc->sc_levels[0]; + else + return sc->sc_levels[i]; +} + +int +pwmbl_set_brightness(void *cookie, uint32_t level) +{ + struct pwmbl_softc *sc = cookie; + struct pwm_state ps; + + if (pwm_init_state(sc->sc_pwm, &ps)) + return EINVAL; + + level = pwmbl_find_brightness(sc, level); + + ps.ps_enabled = 1; + ps.ps_pulse_width = (ps.ps_period * level) / sc->sc_max_level; + return pwm_set_state(sc->sc_pwm, &ps); +} + +int +pwmbl_get_param(struct wsdisplay_param *dp) +{ + struct pwmbl_softc *sc = (struct pwmbl_softc *)sc_pwmbl; + uint32_t level; + + switch (dp->param) { + case WSDISPLAYIO_PARAM_BRIGHTNESS: + if (pwmbl_get_brightness(sc, &level)) + return -1; + + dp->min = 0; + dp->max = sc->sc_max_level; + dp->curval = level; + return 0; + default: + return -1; + } +} + +int +pwmbl_set_param(struct wsdisplay_param *dp) +{ + struct pwmbl_softc *sc = (struct pwmbl_softc *)sc_pwmbl; + + switch (dp->param) { + case WSDISPLAYIO_PARAM_BRIGHTNESS: + if (pwmbl_set_brightness(sc, dp->curval)) + return -1; + return 0; + default: + return -1; + } +} diff --git a/sys/dev/fdt/sxipwm.c b/sys/dev/fdt/sxipwm.c new file mode 100644 index 00000000000..a055604f522 --- /dev/null +++ b/sys/dev/fdt/sxipwm.c @@ -0,0 +1,233 @@ +/* $OpenBSD: sxipwm.c,v 1.1 2019/10/21 20:52:33 kettenis Exp $ */ +/* + * Copyright (c) 2019 Krystian Lewandowski + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/malloc.h> + +#include <machine/fdt.h> +#include <machine/bus.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_clock.h> +#include <dev/ofw/ofw_misc.h> +#include <dev/ofw/ofw_pinctrl.h> +#include <dev/ofw/fdt.h> + +#define PWM_CTRL_REG 0x0 +#define PWM0_RDY (1 << 28) +#define SCLK_CH0_GATING (1 << 6) +#define PWM_CH0_ACT_STA (1 << 5) +#define PWM_CH0_EN (1 << 4) +#define PWM_CH0_PRESCAL 0xf +#define PWM_CH0_PERIOD 0x4 +#define PWM_CH0_CYCLES_SHIFT 16 +#define PWM_CH0_ACT_CYCLES_SHIFT 0 +#define PWM_CH0_CYCLES_MAX 0xffff + +#define NS_PER_S 1000000000 + +#define HREAD4(sc, reg) \ + (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) +#define HWRITE4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) + +struct sxipwm_prescaler { + uint32_t divider; + uint8_t value; +}; + +const struct sxipwm_prescaler sxipwm_prescalers[] = { + { 1, 0xf }, + { 120, 0x0 }, + { 180, 0x1 }, + { 240, 0x2 }, + { 360, 0x3 }, + { 480, 0x4 }, + { 12000, 0x8 }, + { 24000, 0x9 }, + { 36000, 0xa }, + { 48000, 0xb }, + { 72000, 0xc }, + { 0 } +}; + +struct sxipwm_softc { + struct device sc_dev; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + uint32_t sc_clkin; + struct pwm_device sc_pd; +}; + +int sxipwm_match(struct device *, void *, void *); +void sxipwm_attach(struct device *, struct device *, void *); + +struct cfattach sxipwm_ca = { + sizeof(struct sxipwm_softc), sxipwm_match, sxipwm_attach +}; + +struct cfdriver sxipwm_cd = { + NULL, "sxipwm", DV_DULL +}; + +int sxipwm_get_state(void *, uint32_t *, struct pwm_state *); +int sxipwm_set_state(void *, uint32_t *, struct pwm_state *); + +int +sxipwm_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + return OF_is_compatible(faa->fa_node, "allwinner,sun5i-a13-pwm"); +} + +void +sxipwm_attach(struct device *parent, struct device *self, void *aux) +{ + struct sxipwm_softc *sc = (struct sxipwm_softc *)self; + struct fdt_attach_args *faa = aux; + + if (faa->fa_nreg < 1) { + printf(": no registers\n"); + return; + } + + sc->sc_clkin = clock_get_frequency_idx(faa->fa_node, 0); + if (sc->sc_clkin == 0) { + printf(": no clock\n"); + return; + } + + sc->sc_iot = faa->fa_iot; + if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, + faa->fa_reg[0].size, 0, &sc->sc_ioh)) { + printf(": can't map registers\n"); + return; + } + + printf("\n"); + + pinctrl_byname(faa->fa_node, "default"); + + clock_enable_all(faa->fa_node); + reset_deassert_all(faa->fa_node); + + sc->sc_pd.pd_node = faa->fa_node; + sc->sc_pd.pd_cookie = sc; + sc->sc_pd.pd_get_state = sxipwm_get_state; + sc->sc_pd.pd_set_state = sxipwm_set_state; + + pwm_register(&sc->sc_pd); +} + +int +sxipwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps) +{ + struct sxipwm_softc *sc = cookie; + uint32_t idx = cells[0]; + uint32_t ctrl, ch_period; + uint64_t rate, cycles, act_cycles; + int i, prescaler; + + if (idx != 0) + return EINVAL; + + ctrl = HREAD4(sc, PWM_CTRL_REG); + ch_period = HREAD4(sc, PWM_CH0_PERIOD); + + prescaler = -1; + for (i = 0; sxipwm_prescalers[i].divider; i++) { + if ((ctrl & PWM_CH0_PRESCAL) == sxipwm_prescalers[i].value) { + prescaler = i; + break; + } + } + if (prescaler < 0) + return EINVAL; + + rate = sc->sc_clkin / sxipwm_prescalers[prescaler].divider; + cycles = ((ch_period >> PWM_CH0_CYCLES_SHIFT) & + PWM_CH0_CYCLES_MAX) + 1; + act_cycles = (ch_period >> PWM_CH0_ACT_CYCLES_SHIFT) & + PWM_CH0_CYCLES_MAX; + + memset(ps, 0, sizeof(struct pwm_state)); + ps->ps_period = (NS_PER_S * cycles) / rate; + ps->ps_pulse_width = (NS_PER_S * act_cycles) / rate; + if ((ctrl & PWM_CH0_EN) && (ctrl & SCLK_CH0_GATING)) + ps->ps_enabled = 1; + + return 0; +} + +int +sxipwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps) +{ + struct sxipwm_softc *sc = cookie; + uint32_t idx = cells[0]; + uint64_t rate, cycles, act_cycles; + uint32_t reg; + int i, prescaler; + + if (idx != 0) + return EINVAL; + + prescaler = -1; + for (i = 0; sxipwm_prescalers[i].divider; i++) { + rate = sc->sc_clkin / sxipwm_prescalers[i].divider; + cycles = (rate * ps->ps_period) / NS_PER_S; + if ((cycles - 1) < PWM_CH0_CYCLES_MAX) { + prescaler = i; + break; + } + } + if (prescaler < 0) + return EINVAL; + + rate = sc->sc_clkin / sxipwm_prescalers[prescaler].divider; + cycles = (rate * ps->ps_period) / NS_PER_S; + act_cycles = (rate * ps->ps_pulse_width) / NS_PER_S; + if (cycles < 1 || act_cycles > cycles) + return EINVAL; + + KASSERT(cycles - 1 <= PWM_CH0_CYCLES_MAX); + KASSERT(act_cycles <= PWM_CH0_CYCLES_MAX); + + reg = HREAD4(sc, PWM_CTRL_REG); + if (reg & PWM0_RDY) + return EBUSY; + if (ps->ps_enabled) + reg |= (PWM_CH0_EN | SCLK_CH0_GATING); + else + reg &= ~(PWM_CH0_EN | SCLK_CH0_GATING); + if (ps->ps_flags & PWM_POLARITY_INVERTED) + reg &= ~PWM_CH0_ACT_STA; + else + reg |= PWM_CH0_ACT_STA; + reg &= ~PWM_CH0_PRESCAL; + reg |= sxipwm_prescalers[prescaler].value; + HWRITE4(sc, PWM_CTRL_REG, reg); + + reg = ((cycles - 1) << PWM_CH0_CYCLES_SHIFT) | + (act_cycles << PWM_CH0_ACT_CYCLES_SHIFT); + HWRITE4(sc, PWM_CH0_PERIOD, reg); + + return 0; +} |