The following diff adds a preliminary driver for the system-level control registers of Xilinx Zynq-7000. It enables system reset. It also adds clock bits for use with the SDIO and Gigabit Ethernet controllers.
On some arm64 and armv7 platforms, there are separate drivers for clocks and resets. However, on Zynq-7000 it looks more natural to use a single driver. Below is an outline of the relevant part of the device tree: slcr: slcr@f8000000 { #address-cells = <1>; #size-cells = <1>; compatible = "xlnx,zynq-slcr", "syscon", "simple-mfd"; reg = <0xF8000000 0x1000>; ranges; clkc: clkc@100 { #clock-cells = <1>; compatible = "xlnx,ps7-clkc"; fclk-enable = <0>; clock-output-names = "armpll", "ddrpll", ...; reg = <0x100 0x100>; }; rstc: rstc@200 { compatible = "xlnx,zynq-reset"; reg = <0x200 0x48>; #reset-cells = <1>; syscon = <&slcr>; }; pinctrl0: pinctrl@700 { compatible = "xlnx,pinctrl-zynq"; reg = <0x700 0x200>; syscon = <&slcr>; }; }; OK? Index: share/man/man4/man4.armv7/Makefile =================================================================== RCS file: src/share/man/man4/man4.armv7/Makefile,v retrieving revision 1.28 diff -u -p -r1.28 Makefile --- share/man/man4/man4.armv7/Makefile 10 Apr 2020 22:26:46 -0000 1.28 +++ share/man/man4/man4.armv7/Makefile 26 Apr 2021 14:14:29 -0000 @@ -6,7 +6,7 @@ MAN= agtimer.4 amdisplay.4 ampintc.4 amp omap.4 omclock.4 omcm.4 omdog.4 omgpio.4 ommmc.4 omrng.4 omsysc.4 \ omwugen.4 prcm.4 \ sxie.4 sxiintc.4 \ - sxitimer.4 sxits.4 sysreg.4 + sxitimer.4 sxits.4 sysreg.4 zqsyscon.4 MANSUBDIR=armv7 Index: share/man/man4/man4.armv7/zqsyscon.4 =================================================================== RCS file: share/man/man4/man4.armv7/zqsyscon.4 diff -N share/man/man4/man4.armv7/zqsyscon.4 --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ share/man/man4/man4.armv7/zqsyscon.4 26 Apr 2021 14:14:29 -0000 @@ -0,0 +1,38 @@ +.\" $OpenBSD$ +.\" +.\" Copyright (c) 2021 Visa Hankala +.\" +.\" 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. +.\" +.Dd $Mdocdate$ +.Dt ZQSYSCON 4 +.Os +.Sh NAME +.Nm zqsyscon +.Nd Xilinx Zynq-7000 system controller +.Sh SYNOPSIS +.Cd "zqsyscon* at fdt?" +.Sh DESCRIPTION +The +.Nm +driver +provides access to a set of miscellaneous hardware registers +to other device drivers. +It also provides system reset functionality. +.Sh SEE ALSO +.Xr intro 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Ox 7.0 . Index: sys/arch/armv7/conf/GENERIC =================================================================== RCS file: src/sys/arch/armv7/conf/GENERIC,v retrieving revision 1.135 diff -u -p -r1.135 GENERIC --- sys/arch/armv7/conf/GENERIC 24 Apr 2021 07:49:11 -0000 1.135 +++ sys/arch/armv7/conf/GENERIC 26 Apr 2021 14:14:29 -0000 @@ -213,6 +213,7 @@ dwdog* at fdt? # Xilinx Zynq-7000 cduart* at fdt? +zqsyscon* at fdt? # I2C devices abcrtc* at iic? # Abracon x80x RTC Index: sys/arch/armv7/conf/RAMDISK =================================================================== RCS file: src/sys/arch/armv7/conf/RAMDISK,v retrieving revision 1.121 diff -u -p -r1.121 RAMDISK --- sys/arch/armv7/conf/RAMDISK 24 Apr 2021 07:49:11 -0000 1.121 +++ sys/arch/armv7/conf/RAMDISK 26 Apr 2021 14:14:29 -0000 @@ -198,6 +198,7 @@ dwdog* at fdt? # Xilinx Zynq-7000 cduart* at fdt? +zqsyscon* at fdt? axppmic* at iic? # axp209 pmic crosec* at iic? Index: sys/arch/armv7/conf/files.armv7 =================================================================== RCS file: src/sys/arch/armv7/conf/files.armv7,v retrieving revision 1.37 diff -u -p -r1.37 files.armv7 --- sys/arch/armv7/conf/files.armv7 5 Jun 2018 20:41:19 -0000 1.37 +++ sys/arch/armv7/conf/files.armv7 26 Apr 2021 14:14:29 -0000 @@ -75,3 +75,4 @@ include "arch/armv7/exynos/files.exynos" include "arch/armv7/vexpress/files.vexpress" include "arch/armv7/broadcom/files.broadcom" include "arch/armv7/marvell/files.marvell" +include "arch/armv7/xilinx/files.xilinx" Index: sys/arch/armv7/xilinx/files.xilinx =================================================================== RCS file: sys/arch/armv7/xilinx/files.xilinx diff -N sys/arch/armv7/xilinx/files.xilinx --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/arch/armv7/xilinx/files.xilinx 26 Apr 2021 14:14:29 -0000 @@ -0,0 +1,5 @@ +# $OpenBSD$ + +device zqsyscon +attach zqsyscon at fdt +file arch/armv7/xilinx/zqsyscon.c zqsyscon Index: sys/arch/armv7/xilinx/zqsyscon.c =================================================================== RCS file: sys/arch/armv7/xilinx/zqsyscon.c diff -N sys/arch/armv7/xilinx/zqsyscon.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sys/arch/armv7/xilinx/zqsyscon.c 26 Apr 2021 14:14:29 -0000 @@ -0,0 +1,407 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2021 Visa Hankala + * + * Permission to use, copy, modify, and/or 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. + */ + +/* + * Driver for Xilinx Zynq-7000 System Level Control Registers. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/mutex.h> + +#include <machine/bus.h> +#include <machine/fdt.h> + +#include <dev/ofw/fdt.h> +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_clock.h> + +extern void (*cpuresetfn)(void); + +#define SLCR_LOCK 0x0004 +#define SLCR_LOCK_KEY 0x767b +#define SLCR_UNLOCK 0x0008 +#define SLCR_UNLOCK_KEY 0xdf0d +#define SLCR_ARM_PLL_CTRL 0x0100 +#define SLCR_DDR_PLL_CTRL 0x0104 +#define SLCR_IO_PLL_CTRL 0x0108 +#define SLCR_PLL_CTRL_FDIV_MASK 0x7f +#define SLCR_PLL_CTRL_FDIV_SHIFT 12 +#define SLCR_GEM0_CLK_CTRL 0x0140 +#define SLCR_GEM1_CLK_CTRL 0x0144 +#define SLCR_SDIO_CLK_CTRL 0x0150 +#define SLCR_UART_CLK_CTRL 0x0154 +#define SLCR_CLK_CTRL_DIVISOR1(x) (((x) >> 20) & 0x3f) +#define SLCR_CLK_CTRL_DIVISOR1_SHIFT 20 +#define SLCR_CLK_CTRL_DIVISOR(x) (((x) >> 8) & 0x3f) +#define SLCR_CLK_CTRL_DIVISOR_SHIFT 8 +#define SLCR_CLK_CTRL_SRCSEL_MASK (0x7 << 4) +#define SLCR_CLK_CTRL_SRCSEL_DDR (0x3 << 4) +#define SLCR_CLK_CTRL_SRCSEL_ARM (0x2 << 4) +#define SLCR_CLK_CTRL_SRCSEL_IO (0x1 << 4) +#define SLCR_CLK_CTRL_CLKACT(i) (0x1 << (i)) +#define SLCR_PSS_RST_CTRL 0x0200 +#define SLCR_PSS_RST_CTRL_SOFT_RST (1 << 0) + +#define SLCR_DIV_MASK 0x3f + +#define CLK_ARM_PLL 0 +#define CLK_DDR_PLL 1 +#define CLK_IO_PLL 2 +#define CLK_CPU_6OR4X 3 +#define CLK_CPU_3OR2X 4 +#define CLK_CPU_2X 5 +#define CLK_CPU_1X 6 +#define CLK_DDR_2X 7 +#define CLK_DDR_3X 8 +#define CLK_DCI 9 +#define CLK_LQSPI 10 +#define CLK_SMC 11 +#define CLK_PCAP 12 +#define CLK_GEM0 13 +#define CLK_GEM1 14 +#define CLK_FCLK0 15 +#define CLK_FCLK1 16 +#define CLK_FCLK2 17 +#define CLK_FCLK3 18 +#define CLK_CAN0 19 +#define CLK_CAN1 20 +#define CLK_SDIO0 21 +#define CLK_SDIO1 22 +#define CLK_UART0 23 +#define CLK_UART1 24 +#define CLK_SPI0 25 +#define CLK_SPI1 26 +#define CLK_DMA 27 + +struct zqsyscon_softc { + struct device sc_dev; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + struct mutex sc_mtx; + + uint32_t sc_psclk_freq; /* in Hz */ + + struct clock_device sc_cd; +}; + +int zqsyscon_match(struct device *, void *, void *); +void zqsyscon_attach(struct device *, struct device *, void *); + +void zqsyscon_enable(void *, uint32_t *, int); +uint32_t zqsyscon_get_frequency(void *, uint32_t *); +int zqsyscon_set_frequency(void *, uint32_t *, uint32_t); + +void zqsyscon_cpureset(void); + +struct zqsyscon_clock { + uint16_t clk_ctl_reg; + uint8_t clk_has_div1; + uint8_t clk_index; +}; + +const struct zqsyscon_clock zqsyscon_clocks[] = { + [CLK_GEM0] = { SLCR_GEM0_CLK_CTRL, 1, 0 }, + [CLK_GEM1] = { SLCR_GEM1_CLK_CTRL, 1, 0 }, + [CLK_SDIO0] = { SLCR_SDIO_CLK_CTRL, 0, 0 }, + [CLK_SDIO1] = { SLCR_SDIO_CLK_CTRL, 0, 1 }, + [CLK_UART0] = { SLCR_UART_CLK_CTRL, 0, 0 }, + [CLK_UART1] = { SLCR_UART_CLK_CTRL, 0, 1 }, +}; + +const struct cfattach zqsyscon_ca = { + sizeof(struct zqsyscon_softc), zqsyscon_match, zqsyscon_attach +}; + +struct cfdriver zqsyscon_cd = { + NULL, "zqsyscon", DV_DULL +}; + +struct zqsyscon_softc *zqsyscon_sc; + +static uint32_t +zqsyscon_read(struct zqsyscon_softc *sc, uint32_t reg) +{ + return bus_space_read_4(sc->sc_iot, sc->sc_ioh, reg); +} + +static void +zqsyscon_write(struct zqsyscon_softc *sc, uint32_t reg, uint32_t val) +{ + MUTEX_ASSERT_LOCKED(&sc->sc_mtx); + + bus_space_write_4(sc->sc_iot, sc->sc_ioh, SLCR_UNLOCK, SLCR_UNLOCK_KEY); + bus_space_write_4(sc->sc_iot, sc->sc_ioh, reg, val); + bus_space_write_4(sc->sc_iot, sc->sc_ioh, SLCR_LOCK, SLCR_LOCK_KEY); +} + +int +zqsyscon_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + if (OF_is_compatible(faa->fa_node, "xlnx,zynq-slcr")) + return 10; /* Must beat syscon(4). */ + + return 0; +} + +void +zqsyscon_attach(struct device *parent, struct device *self, void *aux) +{ + struct fdt_attach_args *faa = aux; + struct zqsyscon_softc *sc = (struct zqsyscon_softc *)self; + int node; + + 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) != 0) { + printf(": can't map registers\n"); + return; + } + + for (node = OF_child(faa->fa_node); node != 0; node = OF_peer(node)) { + if (OF_is_compatible(node, "xlnx,ps7-clkc")) + break; + } + if (node == 0) { + printf(": can't find clkc node\n"); + goto fail; + } + + mtx_init(&sc->sc_mtx, IPL_HIGH); + + sc->sc_psclk_freq = OF_getpropint(node, "ps-clk-frequency", 33333333); + + printf(": %u MHz PS clock\n", (sc->sc_psclk_freq + 500000) / 1000000); + + zqsyscon_sc = sc; + cpuresetfn = zqsyscon_cpureset; + + sc->sc_cd.cd_node = node; + sc->sc_cd.cd_cookie = sc; + sc->sc_cd.cd_enable = zqsyscon_enable; + sc->sc_cd.cd_get_frequency = zqsyscon_get_frequency; + sc->sc_cd.cd_set_frequency = zqsyscon_set_frequency; + clock_register(&sc->sc_cd); + + return; + +fail: + if (sc->sc_ioh != 0) + bus_space_unmap(sc->sc_iot, sc->sc_ioh, faa->fa_reg[0].size); +} + +const struct zqsyscon_clock * +zqsyscon_get_clock(uint32_t idx) +{ + const struct zqsyscon_clock *clock; + + if (idx >= nitems(zqsyscon_clocks)) + return NULL; + + clock = &zqsyscon_clocks[idx]; + if (clock->clk_ctl_reg == 0) + return NULL; + + return clock; +} + +uint32_t +zqsyscon_get_pll_frequency(struct zqsyscon_softc *sc, uint32_t clk_ctrl) +{ + uint32_t reg, val; + + switch (clk_ctrl & SLCR_CLK_CTRL_SRCSEL_MASK) { + case SLCR_CLK_CTRL_SRCSEL_ARM: + reg = SLCR_ARM_PLL_CTRL; + break; + case SLCR_CLK_CTRL_SRCSEL_DDR: + reg = SLCR_DDR_PLL_CTRL; + break; + default: + reg = SLCR_IO_PLL_CTRL; + break; + } + + val = zqsyscon_read(sc, reg); + return sc->sc_psclk_freq * ((val >> SLCR_PLL_CTRL_FDIV_SHIFT) & + SLCR_PLL_CTRL_FDIV_MASK); +} + +uint32_t +zqsyscon_get_frequency(void *cookie, uint32_t *cells) +{ + const struct zqsyscon_clock *clock; + struct zqsyscon_softc *sc = cookie; + uint32_t idx = cells[0]; + uint32_t ctl, div, freq; + + clock = zqsyscon_get_clock(idx); + if (clock == NULL) + return 0; + + mtx_enter(&sc->sc_mtx); + + ctl = zqsyscon_read(sc, clock->clk_ctl_reg); + + div = SLCR_CLK_CTRL_DIVISOR(ctl); + if (clock->clk_has_div1) + div *= SLCR_CLK_CTRL_DIVISOR1(ctl); + + freq = zqsyscon_get_pll_frequency(sc, ctl); + freq = (freq + div / 2) / div; + + mtx_leave(&sc->sc_mtx); + + return freq; +} + +int +zqsyscon_set_frequency(void *cookie, uint32_t *cells, uint32_t freq) +{ + static const uint32_t srcsels[] = { + SLCR_CLK_CTRL_SRCSEL_IO, + SLCR_CLK_CTRL_SRCSEL_ARM, + SLCR_CLK_CTRL_SRCSEL_DDR, + }; + const struct zqsyscon_clock *clock; + struct zqsyscon_softc *sc = cookie; + uint32_t idx = cells[0]; + uint32_t best_delta = ~0U; + uint32_t best_div1 = 0; + uint32_t best_si = 0; + uint32_t best_pllf = 0; + uint32_t ctl, div, div1, maxdiv1, si; + int error = 0; + + clock = zqsyscon_get_clock(idx); + if (clock == NULL) + return EINVAL; + + if (freq == 0) + return EINVAL; + + mtx_enter(&sc->sc_mtx); + + maxdiv1 = 1; + if (clock->clk_has_div1) + maxdiv1 = SLCR_DIV_MASK; + + /* Find PLL and divisors that give best frequency. */ + for (si = 0; si < nitems(srcsels); si++) { + uint32_t delta, f, pllf; + + pllf = zqsyscon_get_pll_frequency(sc, srcsels[si]); + if (freq > pllf) + continue; + + for (div1 = 1; div1 <= maxdiv1; div1++) { + div = (pllf + (freq * div1 / 2)) / (freq * div1); + if (div > SLCR_DIV_MASK) + continue; + if (div == 0) + break; + + f = (pllf + (div * div1 / 2)) / (div * div1); + delta = abs(f - freq); + if (best_div1 == 0 || delta < best_delta) { + best_delta = delta; + best_div1 = div1; + best_pllf = pllf; + best_si = si; + + if (delta == 0) + goto found; + } + } + } + + if (best_div1 == 0) { + error = EINVAL; + goto out; + } + +found: + div1 = best_div1; + div = (best_pllf + (freq * div1 / 2)) / (freq * div1); + + KASSERT(div > 0 && div <= SLCR_DIV_MASK); + KASSERT(div1 > 0 && div1 <= SLCR_DIV_MASK); + + ctl = zqsyscon_read(sc, clock->clk_ctl_reg); + + ctl &= ~SLCR_CLK_CTRL_SRCSEL_MASK; + ctl |= srcsels[best_si]; + ctl &= ~(SLCR_DIV_MASK << SLCR_CLK_CTRL_DIVISOR_SHIFT); + ctl |= (div & SLCR_DIV_MASK) << SLCR_CLK_CTRL_DIVISOR_SHIFT; + if (clock->clk_has_div1) { + ctl &= ~(SLCR_DIV_MASK << SLCR_CLK_CTRL_DIVISOR1_SHIFT); + ctl |= (div1 & SLCR_DIV_MASK) << SLCR_CLK_CTRL_DIVISOR1_SHIFT; + } + + zqsyscon_write(sc, clock->clk_ctl_reg, ctl); + +out: + mtx_leave(&sc->sc_mtx); + + return error; +} + +void +zqsyscon_enable(void *cookie, uint32_t *cells, int on) +{ + const struct zqsyscon_clock *clock; + struct zqsyscon_softc *sc = cookie; + uint32_t idx = cells[0]; + uint32_t ctl; + + if (idx >= nitems(zqsyscon_clocks)) + return; + + clock = &zqsyscon_clocks[idx]; + if (clock->clk_ctl_reg == 0) + return; + + mtx_enter(&sc->sc_mtx); + + ctl = zqsyscon_read(sc, clock->clk_ctl_reg); + if (on) + ctl |= SLCR_CLK_CTRL_CLKACT(clock->clk_index); + else + ctl &= ~SLCR_CLK_CTRL_CLKACT(clock->clk_index); + zqsyscon_write(sc, clock->clk_ctl_reg, ctl); + + mtx_leave(&sc->sc_mtx); +} + +void +zqsyscon_cpureset(void) +{ + struct zqsyscon_softc *sc = zqsyscon_sc; + + mtx_enter(&sc->sc_mtx); + zqsyscon_write(sc, SLCR_PSS_RST_CTRL, SLCR_PSS_RST_CTRL_SOFT_RST); + mtx_leave(&sc->sc_mtx); +}