summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2019-10-21 20:52:34 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2019-10-21 20:52:34 +0000
commit709446bf07c5f824ba8ecdcea2e0e279dcfb2498 (patch)
tree1cd0d2e38bd010099aa8f6a3f8d89b83731d5574
parent7d77fc2d19559804abde1369627b9e6909c14490 (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/GENERIC4
-rw-r--r--sys/dev/fdt/files.fdt10
-rw-r--r--sys/dev/fdt/pwmbl.c205
-rw-r--r--sys/dev/fdt/sxipwm.c233
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;
+}