From 5f12af7ce8ef75e82c2f917767f1d4546db0ab2c Mon Sep 17 00:00:00 2001 From: kettenis Date: Wed, 17 Sep 2025 09:32:55 +0000 Subject: [PATCH] Add rpipwm(4), a driver for the PWM controller on the RP1 chip. Together with the previous RP1-related commits this makes the fan work. Also based on eralier work by mglocker@ ok on principle tb@ ok mglocker@ --- sys/arch/arm64/conf/GENERIC | 3 +- sys/arch/arm64/conf/RAMDISK | 3 +- sys/arch/arm64/conf/files.arm64 | 6 +- sys/arch/arm64/dev/rpipwm.c | 187 ++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 sys/arch/arm64/dev/rpipwm.c diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC index de4157bc1a3..93273767ec7 100644 --- a/sys/arch/arm64/conf/GENERIC +++ b/sys/arch/arm64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.306 2025/09/17 09:23:43 kettenis Exp $ +# $OpenBSD: GENERIC,v 1.307 2025/09/17 09:32:55 kettenis Exp $ # # GENERIC machine description file # @@ -256,6 +256,7 @@ usb* at dwctwo? rpone* at pci? rpiclock* at fdt? early 1 rpigpio* at fdt? early 1 +rpipwm* at fdt? rpirtc* at fdt? # Amlogic SoCs diff --git a/sys/arch/arm64/conf/RAMDISK b/sys/arch/arm64/conf/RAMDISK index 7ca087be0d2..1b855e5ecad 100644 --- a/sys/arch/arm64/conf/RAMDISK +++ b/sys/arch/arm64/conf/RAMDISK @@ -1,4 +1,4 @@ -# $OpenBSD: RAMDISK,v 1.233 2025/09/17 09:23:43 kettenis Exp $ +# $OpenBSD: RAMDISK,v 1.234 2025/09/17 09:32:55 kettenis Exp $ machine arm64 maxusers 4 @@ -189,6 +189,7 @@ usb* at dwctwo? rpone* at pci? rpiclock* at fdt? early 1 rpigpio* at fdt? early 1 +rpipwm* at fdt? rpirtc* at fdt? # Amlogic SoCs diff --git a/sys/arch/arm64/conf/files.arm64 b/sys/arch/arm64/conf/files.arm64 index ffe9c49fd2f..34d9f5d35f5 100644 --- a/sys/arch/arm64/conf/files.arm64 +++ b/sys/arch/arm64/conf/files.arm64 @@ -1,4 +1,4 @@ -# $OpenBSD: files.arm64,v 1.78 2025/09/17 09:23:43 kettenis Exp $ +# $OpenBSD: files.arm64,v 1.79 2025/09/17 09:32:55 kettenis Exp $ maxpartitions 16 maxusers 2 8 128 @@ -276,6 +276,10 @@ device rpigpio attach rpigpio at fdt file arch/arm64/dev/rpigpio.c rpigpio +device rpipwm +attach rpipwm at fdt +file arch/arm64/dev/rpipwm.c rpipwm + device rpirtc attach rpirtc at fdt file arch/arm64/dev/rpirtc.c rpirtc diff --git a/sys/arch/arm64/dev/rpipwm.c b/sys/arch/arm64/dev/rpipwm.c new file mode 100644 index 00000000000..9fefd8fe336 --- /dev/null +++ b/sys/arch/arm64/dev/rpipwm.c @@ -0,0 +1,187 @@ +/* $OpenBSD: rpipwm.c,v 1.1 2025/09/17 09:32:55 kettenis Exp $ */ + +/* + * Copyright (c) 2025 Marcus Glocker + * Copyright (c) 2025 Mark Kettenis + * + * 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 +#include +#include + +#include + +#include +#include +#include +#include +#include + +/* Bus helper macros. */ +#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)) +#define HSET4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) +#define HCLR4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) + +/* Registers. */ +#define GLOBAL_CTRL 0x00 +#define GLOBAL_CTRL_SET_UPDATE (1U << 31) +#define GLOBAL_CTRL_CHAN_EN(_chan) (1U << (_chan)) +#define CHAN_CTRL(_chan) (0x14 + ((_chan) * 16)) +#define CHAN_CTRL_FIFO_POP_MASK (1U << 8) +#define CHAN_CTRL_INVERT (1U << 3) +#define CHAN_CTRL_MODE_TRAILING_EDGE (0x1 << 0) +#define CHAN_RANGE(_chan) (0x18 + ((_chan) * 16)) +#define CHAN_DUTY(_chan) (0x20 + ((_chan) * 16)) + +struct rpipwm_softc { + struct device sc_dev; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + uint32_t sc_clkin; + uint32_t sc_initialized; + struct pwm_device sc_pd; +}; + +int rpipwm_match(struct device *, void *, void *); +void rpipwm_attach(struct device *, struct device *, void *); + +const struct cfattach rpipwm_ca = { + sizeof(struct rpipwm_softc), rpipwm_match, rpipwm_attach +}; + +struct cfdriver rpipwm_cd = { + NULL, "rpipwm", DV_DULL +}; + +int rpipwm_get_state(void *, uint32_t *, struct pwm_state *); +int rpipwm_set_state(void *, uint32_t *, struct pwm_state *); + +int +rpipwm_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + return OF_is_compatible(faa->fa_node, "raspberrypi,rp1-pwm"); +} + +void +rpipwm_attach(struct device *parent, struct device *self, void *aux) +{ + struct rpipwm_softc *sc = (struct rpipwm_softc *)self; + struct fdt_attach_args *faa = aux; + + if (faa->fa_nreg < 1) { + printf(": no registers\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_set_assigned(faa->fa_node); + clock_enable_all(faa->fa_node); + + sc->sc_clkin = clock_get_frequency(faa->fa_node, NULL); + + sc->sc_pd.pd_node = faa->fa_node; + sc->sc_pd.pd_cookie = sc; + sc->sc_pd.pd_get_state = rpipwm_get_state; + sc->sc_pd.pd_set_state = rpipwm_set_state; + pwm_register(&sc->sc_pd); +} + +static inline uint32_t +cycles_to_ns(uint64_t clk_freq, uint32_t cycles) +{ + return cycles * 1000000000ULL / clk_freq; +} + +static inline uint32_t +ns_to_cycles(uint64_t clk_freq, uint32_t ns) +{ + return ns * clk_freq / 1000000000ULL; +} + +int +rpipwm_get_state(void *cookie, uint32_t *cells, struct pwm_state *ps) +{ + struct rpipwm_softc *sc = cookie; + uint32_t chan = cells[0]; + uint32_t ctrl, range, duty; + + if (chan >= 4 || sc->sc_clkin == 0) + return EINVAL; + + /* Initialize a channel to a know state when we first touch it. */ + if ((sc->sc_initialized & GLOBAL_CTRL_CHAN_EN(chan)) == 0) { + HWRITE4(sc, CHAN_CTRL(chan), + CHAN_CTRL_FIFO_POP_MASK | CHAN_CTRL_MODE_TRAILING_EDGE); + sc->sc_initialized |= GLOBAL_CTRL_CHAN_EN(chan); + } + + ctrl = HREAD4(sc, GLOBAL_CTRL); + ps->ps_enabled = !!(ctrl & GLOBAL_CTRL_CHAN_EN(chan)); + + range = HREAD4(sc, CHAN_RANGE(chan)); + duty = HREAD4(sc, CHAN_DUTY(chan)); + ps->ps_period = cycles_to_ns(sc->sc_clkin, range); + ps->ps_pulse_width = cycles_to_ns(sc->sc_clkin, duty); + + return 0; +} + +int +rpipwm_set_state(void *cookie, uint32_t *cells, struct pwm_state *ps) +{ + struct rpipwm_softc *sc = cookie; + uint32_t chan = cells[0]; + uint32_t range, duty; + + if (chan >= 4 || sc->sc_clkin == 0) + return EINVAL; + + range = ns_to_cycles(sc->sc_clkin, ps->ps_period); + HWRITE4(sc, CHAN_RANGE(chan), range); + + duty = ns_to_cycles(sc->sc_clkin, ps->ps_pulse_width); + HWRITE4(sc, CHAN_DUTY(chan), duty); + + if (ps->ps_flags & PWM_POLARITY_INVERTED) + HSET4(sc, CHAN_CTRL(chan), CHAN_CTRL_INVERT); + else + HCLR4(sc, CHAN_CTRL(chan), CHAN_CTRL_INVERT); + + if (ps->ps_enabled) + HSET4(sc, GLOBAL_CTRL, GLOBAL_CTRL_CHAN_EN(chan)); + else + HCLR4(sc, GLOBAL_CTRL, GLOBAL_CTRL_CHAN_EN(chan)); + + /* activate configuration */ + HSET4(sc, GLOBAL_CTRL, GLOBAL_CTRL_SET_UPDATE); + + return 0; +}