> Date: Sun, 6 Oct 2019 23:32:07 +0200 > From: Krystian Lewandowski <k.lewandow...@me.com> > > Good evening Mark, tech@, > I was trying to enable brightness control on Pinebook, so it could be set via: > wsconsctl display.brightness=N%. > > I added sxipwm and pwmbl drivers. First relies on "pwm" bindings, > 2nd - on "pwm-backlight". > > I had it working for some time now. For PWM, I tried to replicate SPI approach > (spivar.h, pwm parent device, etc.). > With latest pwm_... support all these are no longer necessary and I updated my > sxipwm so pwm_... functions are now used. > > I'm quite happy with sxipwm, pwmbl also does work for me but I didn't know how > to properly use it in simplefb. You can see nasty approach in the patch. > Do you have any suggestion how it should be done? > > Device tree related parts: > pwm: pwm@1c21400 { > compatible = "allwinner,sun50i-a64-pwm", > "allwinner,sun5i-a13-pwm"; > reg = <0x01c21400 0x400>; > clocks = <&osc24M>; > pinctrl-names = "default"; > pinctrl-0 = <&pwm_pin>; > #pwm-cells = <3>; > [...] > > pwm_pin: pwm_pin { > pins = "PD22"; > function = "pwm"; > }; > > backlight: backlight { > compatible = "pwm-backlight"; > pwms = <&pwm 0 50000 0>; > brightness-levels = <0 5 10 15 20 30 40 55 70 85 100>; > default-brightness-level = <2>; > enable-gpios = <&pio 3 23 GPIO_ACTIVE_HIGH>; /* PD23 */ > power-supply = <&vdd_bl>; > }
Cool. The idea is that pwmbl(4) should implement pwmbl_get_param() and pwmbl_set_para() functions and set the ws_get_param and ws_set_param global function parameters like we do in dev/acpi/acpivout.c. And have simplefb(4) call these function pointers if they're set. But before that can be done on arm64 those function pointers need to be moved since dev/pci/vga_pci.c isn't included in the arm64 kernel. I'll see if I can get that fixed. > Index: sys/arch/arm64/conf/GENERIC > =================================================================== > RCS file: /cvs/src/sys/arch/arm64/conf/GENERIC,v > retrieving revision 1.126 > diff -u -p -r1.126 GENERIC > --- sys/arch/arm64/conf/GENERIC 30 Sep 2019 20:48:15 -0000 1.126 > +++ sys/arch/arm64/conf/GENERIC 6 Oct 2019 21:04:59 -0000 > @@ -105,6 +105,7 @@ option WSDISPLAY_DEFAULTSCREENS=6 # init > > simplefb* at fdt? > wsdisplay* at simplefb? > +pwmbl* at fdt? # PWM backlight > > radeondrm* at pci? > drm* at radeondrm? > @@ -203,6 +204,7 @@ sdmmc* at sximmc? # SD/MMC bus > sxisyscon* at fdt? early 1 # System controller > sxitemp* at fdt? # Temperature sensor > sxitwi* at fdt? # I2C controller > +sxipwm* at fdt? # PWM controller > iic* at sxitwi? # I2C bus > dwxe* at fdt? > > Index: sys/dev/fdt/files.fdt > =================================================================== > RCS file: /cvs/src/sys/dev/fdt/files.fdt,v > retrieving revision 1.95 > diff -u -p -r1.95 files.fdt > --- sys/dev/fdt/files.fdt 30 Sep 2019 20:44:13 -0000 1.95 > +++ sys/dev/fdt/files.fdt 6 Oct 2019 21:05:05 -0000 > @@ -48,6 +48,14 @@ device sxitwi: i2cbus > attach sxitwi at fdt > file dev/fdt/sxitwi.c sxitwi > > +device sxipwm > +attach sxipwm at fdt > +file dev/fdt/sxipwm.c sxipwm > + > +device pwmbl > +attach pwmbl at fdt > +file dev/fdt/pwmbl.c pwmbl > + > device axppmic > attach axppmic at i2c > attach axppmic at rsb with axppmic_rsb > @@ -205,7 +213,7 @@ attach rkgpio at fdt > file dev/fdt/rkgpio.c rkgpio > > device rkiic: i2cbus > -attach rkiic at fdt > +attach rkiic at fdt > file dev/fdt/rkiic.c rkiic > > device rkpmic > Index: sys/dev/fdt/pwmbl.c > =================================================================== > RCS file: sys/dev/fdt/pwmbl.c > diff -N sys/dev/fdt/pwmbl.c > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ sys/dev/fdt/pwmbl.c 6 Oct 2019 21:05:07 -0000 > @@ -0,0 +1,219 @@ > +/* $OpenBSD$ */ > +/* > + * 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_gpio.h> > +#include <dev/ofw/ofw_misc.h> > + > +#include <dev/wscons/wsconsio.h> > + > +struct pwmbl_softc { > + struct device sc_dev; > + uint32_t *sc_pwm; > + uint32_t sc_period; > + uint32_t sc_flags; > + uint32_t sc_nlevels; > + uint32_t *sc_levels; > + uint32_t sc_def_level; > +}; > + > +extern int (*simplefb_read_cmd)(u_long, caddr_t); > +extern int (*simplefb_write_cmd)(u_long, caddr_t); > + > +struct pwmbl_softc *sc_pwmbl; > +int pwmbl_match(struct device *, void *, void *); > +void pwmbl_attach(struct device *, struct device *, void *); > +int pwmbl_get_brightness(void *, uint32_t *); > +int pwmbl_set_brightness(void *, uint32_t); > + > +int pwmbl_simplefb_read_cmd(ulong, caddr_t); > +int pwmbl_simplefb_write_cmd(ulong, caddr_t); > + > +struct cfattach pwmbl_ca = { > + sizeof (struct pwmbl_softc), pwmbl_match, pwmbl_attach > +}; > + > +struct cfdriver pwmbl_cd = { > + NULL, "pwmbl", DV_DULL > +}; > + > +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 <= 12) { > + 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_period = sc->sc_pwm[2]; > + if(len >= 16) { > + sc->sc_flags = sc->sc_pwm[3]; > + } else { > + sc->sc_flags = 0; > + } > + > + len = OF_getproplen(faa->fa_node, "enable-gpios"); > + if (len < 0) { > + 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) { > + 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_def_level = OF_getpropint(faa->fa_node, > "default-brightness-level", > + sc->sc_levels[sc->sc_nlevels-1]); > + > + // TODO: let simplefb to manage brightness via pwmbl > + sc_pwmbl = sc; > + simplefb_read_cmd = pwmbl_simplefb_read_cmd; > + simplefb_write_cmd = pwmbl_simplefb_write_cmd; > +} > + > +int > +pwmbl_get_brightness(void *cookie, uint32_t *level) { > + struct pwmbl_softc *sc = cookie; > + struct pwm_state ps; > + uint32_t bl_val; > + int i; > + > + if (pwm_get_state(sc->sc_pwm, &ps)) > + return EINVAL; > + > + bl_val = (ps.ps_pulse_width * sc->sc_levels[sc->sc_nlevels - 1]) / > sc->sc_period; > + > + for (i = 0; i < sc->sc_nlevels; i++) { > + if (bl_val <= sc->sc_levels[i]) { > + *level = i; > + break; > + } > + } > + > + return 0; > +} > + > +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; > + > + ps.ps_period = sc->sc_period; > + ps.ps_flags = sc->sc_flags; > + ps.ps_enabled = 1; > + ps.ps_pulse_width = (sc->sc_period * sc->sc_levels[level]) / > sc->sc_levels[sc->sc_nlevels - 1]; > + return pwm_set_state(sc->sc_pwm, &ps); > +} > + > +// TODO: find a better way for simplefb to manage brightness via pwmbl > +int > +pwmbl_simplefb_read_cmd(ulong cmd, caddr_t data) > +{ > + struct wsdisplay_param *dp = (struct wsdisplay_param *)data; > + struct pwmbl_softc *sc = (struct pwmbl_softc *)sc_pwmbl; > + uint32_t level; > + > + switch (cmd) { > + case WSDISPLAYIO_GETPARAM: > + switch (dp->param) { > + case WSDISPLAYIO_PARAM_BRIGHTNESS: > + dp->min = sc->sc_levels[0]; > + dp->max = sc->sc_levels[sc->sc_nlevels - 1]; > + > + if (pwmbl_get_brightness(sc, &level) >= 0) { > + dp->curval = sc->sc_levels[level]; > + return 0; > + } > + break; > + } > + break; > + default: > + return -1; > + } > + return -1; > +} > + > +int > +pwmbl_simplefb_write_cmd(ulong cmd, caddr_t data) > +{ > + struct wsdisplay_param *dp = (struct wsdisplay_param *)data; > + struct pwmbl_softc *sc = (struct pwmbl_softc *)sc_pwmbl; > + u_int i; > + > + switch (cmd) { > + case WSDISPLAYIO_SETPARAM: > + switch (dp->param) { > + case WSDISPLAYIO_PARAM_BRIGHTNESS: > + for (i = 0; i < sc->sc_nlevels; i++) { > + if (dp->curval <= sc->sc_levels[i]) > + break; > + } > + > + if (pwmbl_set_brightness(sc, i) >= 0) > + return 0; > + break; > + } > + break; > + default: > + return -1; > + } > + return -1; > +} > Index: sys/dev/fdt/simplefb.c > =================================================================== > RCS file: /cvs/src/sys/dev/fdt/simplefb.c,v > retrieving revision 1.5 > diff -u -p -r1.5 simplefb.c > --- sys/dev/fdt/simplefb.c 27 Aug 2018 09:30:07 -0000 1.5 > +++ sys/dev/fdt/simplefb.c 6 Oct 2019 21:05:10 -0000 > @@ -72,6 +72,9 @@ struct simplefb_softc { > psize_t sc_psize; > }; > > +int (*simplefb_read_cmd)(u_long, caddr_t); > +int (*simplefb_write_cmd)(u_long, caddr_t); > + > struct rasops_info simplefb_ri; > struct wsscreen_descr simplefb_wsd = { "std" }; > struct wsdisplay_charcell simplefb_bs[SIMPLEFB_WIDTH * SIMPLEFB_HEIGHT]; > @@ -230,9 +233,32 @@ int > simplefb_wsioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p) > { > struct rasops_info *ri = v; > + struct wsdisplay_param *dp = (struct wsdisplay_param *)data; > struct wsdisplay_fbinfo *wdf; > > switch (cmd) { > + case WSDISPLAYIO_GETPARAM: > + switch (dp->param) { > + case WSDISPLAYIO_PARAM_BRIGHTNESS: > + if (simplefb_read_cmd) { > + return simplefb_read_cmd(cmd, data); > + } > + break; > + default: > + return -1; > + } > + break; > + case WSDISPLAYIO_SETPARAM: > + switch (dp->param) { > + case WSDISPLAYIO_PARAM_BRIGHTNESS: > + if (simplefb_write_cmd) { > + return simplefb_write_cmd(cmd, data); > + } > + break; > + default: > + return -1; > + } > + break; > case WSDISPLAYIO_GTYPE: > *(int *)data = WSDISPLAY_TYPE_EFIFB; > return 0; > Index: sys/dev/fdt/sxipwm.c > =================================================================== > RCS file: sys/dev/fdt/sxipwm.c > diff -N sys/dev/fdt/sxipwm.c > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ sys/dev/fdt/sxipwm.c 6 Oct 2019 21:05:13 -0000 > @@ -0,0 +1,250 @@ > +/* $OpenBSD$ */ > +/* > + * 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 PWM_CH0_PERIOD 0x4 > +#define PWM_CH0_PRESCAL 0xf > + > +#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_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 mask; > +}; > + > +struct sxipwm_prescaler a64_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; > + > + struct sxipwm_prescaler *sc_prescalers; > + uint32_t sc_clkin; > + struct pwm_device sc_pd; > +}; > + > +int sxipwm_match(struct device *, void *, void *); > +void sxipwm_attach(struct device *, struct device *, void *); > +int sxipwm_get_state(void *, uint32_t *, struct pwm_state *); > +int sxipwm_set_state(void *, uint32_t *, struct pwm_state *); > + > +struct cfattach sxipwm_ca = { > + sizeof (struct sxipwm_softc), sxipwm_match, sxipwm_attach > +}; > + > +struct cfdriver sxipwm_cd = { > + NULL, "sxipwm", DV_DULL > +}; > + > +int > +sxipwm_match(struct device *parent, void *match, void *aux) > +{ > + struct fdt_attach_args *faa = aux; > + > + return OF_is_compatible(faa->fa_node, "allwinner,sun50i-a64-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); > + > + if (OF_is_compatible(faa->fa_node, "allwinner,sun50i-a64-pwm")) { > + sc->sc_prescalers = a64_prescalers; > + } > + > + if (sc->sc_prescalers == NULL) > + return; > + > + 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_reg, ch_period_reg; > + uint16_t cycles, act_cycles; > + uint32_t rate; > + int i, prescaler; > + > + if (idx != 0) > + return EINVAL; > + > + ctrl_reg = HREAD4(sc, PWM_CTRL_REG); > + ch_period_reg = HREAD4(sc, PWM_CH0_PERIOD); > + > + prescaler = -1; > + for (i = 0; sc->sc_prescalers[i].divider; i++) { > + if ((ctrl_reg & PWM_CH0_PRESCAL) == sc->sc_prescalers[i].mask) { > + prescaler = i; > + } > + } > + > + if (prescaler < 0) > + return EINVAL; > + > + rate = sc->sc_clkin / sc->sc_prescalers[prescaler].divider; > + cycles = ((ch_period_reg >> PWM_CH0_CYCLES_SHIFT) + 1 ) & > PWM_CH0_CYCLES_MAX; > + act_cycles = (ch_period_reg >> PWM_CH0_ACT_CYCLES_SHIFT) & > PWM_CH0_CYCLES_MAX; > + > + memset(ps, 0, sizeof(struct pwm_state)); > + ps->ps_period = NS_PER_S / (rate / cycles); > + ps->ps_pulse_width = NS_PER_S / (rate / act_cycles); > + ps->ps_enabled = (ctrl_reg & (PWM_CH0_EN | SCLK_CH0_GATING)) == > (PWM_CH0_EN | SCLK_CH0_GATING); > + > + 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]; > + uint32_t reg, period_freq, duty_freq, rate; > + uint16_t cycles, act_cycles; > + int i, prescaler; > + > + period_freq = NS_PER_S / ps->ps_period; > + duty_freq = NS_PER_S / (ps->ps_pulse_width ? ps->ps_pulse_width : 1); > + > + if (idx != 0) > + return EINVAL; > + > + if (period_freq > sc->sc_clkin) > + return EINVAL; > + > + if (duty_freq < period_freq) > + return EINVAL; > + > + prescaler = -1; > + for (i = 0; sc->sc_prescalers[i].divider; i++) { > + rate = sc->sc_clkin / (period_freq * > sc->sc_prescalers[i].divider); > + if ((rate - 1) < PWM_CH0_CYCLES_MAX) { > + prescaler = i; > + break; > + } > + } > + > + if (prescaler < 0) > + return EINVAL; > + > + 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 |= sc->sc_prescalers[prescaler].mask; > + HWRITE4(sc, PWM_CTRL_REG, reg); > + > + rate = sc->sc_clkin / sc->sc_prescalers[prescaler].divider; > + cycles = rate / period_freq; > + act_cycles = rate / duty_freq; > + > + reg = ((cycles - 1) << PWM_CH0_CYCLES_SHIFT) | > + (act_cycles << PWM_CH0_ACT_CYCLES_SHIFT); > + HWRITE4(sc, PWM_CH0_PERIOD, reg); > + > + return 0; > +} > >