From: visa Date: Fri, 30 Apr 2021 13:25:24 +0000 (+0000) Subject: Add zqclock(4), a driver for Zynq-7000 clocks. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=6fd70764649c1845f55fe37082767b24713c0a89;p=openbsd Add zqclock(4), a driver for Zynq-7000 clocks. Input and OK kettenis@ --- diff --git a/share/man/man4/man4.armv7/Makefile b/share/man/man4/man4.armv7/Makefile index 58adbf1896b..ad10272b9d0 100644 --- a/share/man/man4/man4.armv7/Makefile +++ b/share/man/man4/man4.armv7/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.29 2021/04/30 13:20:14 visa Exp $ +# $OpenBSD: Makefile,v 1.30 2021/04/30 13:25:24 visa Exp $ MAN= agtimer.4 amdisplay.4 ampintc.4 amptimer.4 armliicc.4 \ cortex.4 cpsw.4 dmtimer.4 edma.4 gptimer.4 \ @@ -6,7 +6,7 @@ MAN= agtimer.4 amdisplay.4 ampintc.4 amptimer.4 armliicc.4 \ 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 zqreset.4 + sxitimer.4 sxits.4 sysreg.4 zqclock.4 zqreset.4 MANSUBDIR=armv7 diff --git a/share/man/man4/man4.armv7/zqclock.4 b/share/man/man4/man4.armv7/zqclock.4 new file mode 100644 index 00000000000..d564a9d4f07 --- /dev/null +++ b/share/man/man4/man4.armv7/zqclock.4 @@ -0,0 +1,37 @@ +.\" $OpenBSD: zqclock.4,v 1.1 2021/04/30 13:25:24 visa Exp $ +.\" +.\" 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: April 30 2021 $ +.Dt ZQCLOCK 4 +.Os +.Sh NAME +.Nm zqclock +.Nd Xilinx Zynq-7000 clock controller +.Sh SYNOPSIS +.Cd "zqclock* at fdt?" +.Sh DESCRIPTION +The +.Nm +driver controls the clock signals for the integrated components +of Zynq-7000 SoCs. +.Sh SEE ALSO +.Xr intro 4 , +.Xr zqreset 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Ox 7.0 . diff --git a/sys/arch/armv7/conf/GENERIC b/sys/arch/armv7/conf/GENERIC index f1a59567033..345889a9e7e 100644 --- a/sys/arch/armv7/conf/GENERIC +++ b/sys/arch/armv7/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.136 2021/04/30 13:20:14 visa Exp $ +# $OpenBSD: GENERIC,v 1.137 2021/04/30 13:25:24 visa Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -213,6 +213,7 @@ dwdog* at fdt? # Xilinx Zynq-7000 cduart* at fdt? +zqclock* at fdt? zqreset* at fdt? # I2C devices diff --git a/sys/arch/armv7/conf/RAMDISK b/sys/arch/armv7/conf/RAMDISK index 385e636fd0b..677b482052d 100644 --- a/sys/arch/armv7/conf/RAMDISK +++ b/sys/arch/armv7/conf/RAMDISK @@ -1,4 +1,4 @@ -# $OpenBSD: RAMDISK,v 1.122 2021/04/30 13:20:14 visa Exp $ +# $OpenBSD: RAMDISK,v 1.123 2021/04/30 13:25:24 visa Exp $ machine armv7 arm @@ -198,6 +198,7 @@ dwdog* at fdt? # Xilinx Zynq-7000 cduart* at fdt? +zqclock* at fdt? zqreset* at fdt? axppmic* at iic? # axp209 pmic diff --git a/sys/arch/armv7/xilinx/files.xilinx b/sys/arch/armv7/xilinx/files.xilinx index 390757996a2..50ebc948cea 100644 --- a/sys/arch/armv7/xilinx/files.xilinx +++ b/sys/arch/armv7/xilinx/files.xilinx @@ -1,4 +1,8 @@ -# $OpenBSD: files.xilinx,v 1.1 2021/04/30 13:20:14 visa Exp $ +# $OpenBSD: files.xilinx,v 1.2 2021/04/30 13:25:24 visa Exp $ + +device zqclock +attach zqclock at fdt +file arch/armv7/xilinx/zqclock.c zqclock device zqreset attach zqreset at fdt diff --git a/sys/arch/armv7/xilinx/zqclock.c b/sys/arch/armv7/xilinx/zqclock.c new file mode 100644 index 00000000000..b523c085a8c --- /dev/null +++ b/sys/arch/armv7/xilinx/zqclock.c @@ -0,0 +1,316 @@ +/* $OpenBSD: zqclock.c,v 1.1 2021/04/30 13:25:24 visa Exp $ */ + +/* + * 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 clock controller. + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#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 zqclock_softc { + struct device sc_dev; + struct regmap *sc_rm; + + struct clock_device sc_cd; + uint32_t sc_psclk_freq; /* in Hz */ +}; + +int zqclock_match(struct device *, void *, void *); +void zqclock_attach(struct device *, struct device *, void *); + +void zqclock_enable(void *, uint32_t *, int); +uint32_t zqclock_get_frequency(void *, uint32_t *); +int zqclock_set_frequency(void *, uint32_t *, uint32_t); + +const struct cfattach zqclock_ca = { + sizeof(struct zqclock_softc), zqclock_match, zqclock_attach +}; + +struct cfdriver zqclock_cd = { + NULL, "zqclock", DV_DULL +}; + +struct zqclock_clock { + uint16_t clk_ctl_reg; + uint8_t clk_has_div1; + uint8_t clk_index; +}; + +const struct zqclock_clock zqclock_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 }, +}; + +int +zqclock_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + return OF_is_compatible(faa->fa_node, "xlnx,ps7-clkc"); +} + +void +zqclock_attach(struct device *parent, struct device *self, void *aux) +{ + struct fdt_attach_args *faa = aux; + struct zqclock_softc *sc = (struct zqclock_softc *)self; + + sc->sc_rm = regmap_bynode(OF_parent(faa->fa_node)); + if (sc->sc_rm == NULL) { + printf(": can't get regmap\n"); + return; + } + + sc->sc_psclk_freq = OF_getpropint(faa->fa_node, "ps-clk-frequency", + 33333333); + + printf(": %u MHz PS clock\n", (sc->sc_psclk_freq + 500000) / 1000000); + + sc->sc_cd.cd_node = faa->fa_node; + sc->sc_cd.cd_cookie = sc; + sc->sc_cd.cd_enable = zqclock_enable; + sc->sc_cd.cd_get_frequency = zqclock_get_frequency; + sc->sc_cd.cd_set_frequency = zqclock_set_frequency; + clock_register(&sc->sc_cd); +} + +const struct zqclock_clock * +zqclock_get_clock(uint32_t idx) +{ + const struct zqclock_clock *clock; + + if (idx >= nitems(zqclock_clocks)) + return NULL; + + clock = &zqclock_clocks[idx]; + if (clock->clk_ctl_reg == 0) + return NULL; + + return clock; +} + +uint32_t +zqclock_get_pll_frequency(struct zqclock_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 = zynq_slcr_read(sc->sc_rm, reg); + return sc->sc_psclk_freq * ((val >> SLCR_PLL_CTRL_FDIV_SHIFT) & + SLCR_PLL_CTRL_FDIV_MASK); +} + +uint32_t +zqclock_get_frequency(void *cookie, uint32_t *cells) +{ + const struct zqclock_clock *clock; + struct zqclock_softc *sc = cookie; + uint32_t idx = cells[0]; + uint32_t ctl, div, freq; + + clock = zqclock_get_clock(idx); + if (clock == NULL) + return 0; + + mtx_enter(&zynq_slcr_lock); + + ctl = zynq_slcr_read(sc->sc_rm, clock->clk_ctl_reg); + + div = SLCR_CLK_CTRL_DIVISOR(ctl); + if (clock->clk_has_div1) + div *= SLCR_CLK_CTRL_DIVISOR1(ctl); + + freq = zqclock_get_pll_frequency(sc, ctl); + freq = (freq + div / 2) / div; + + mtx_leave(&zynq_slcr_lock); + + return freq; +} + +int +zqclock_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 zqclock_clock *clock; + struct zqclock_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 = zqclock_get_clock(idx); + if (clock == NULL) + return EINVAL; + + if (freq == 0) + return EINVAL; + + mtx_enter(&zynq_slcr_lock); + + 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 = zqclock_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 = zynq_slcr_read(sc->sc_rm, 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; + } + + zynq_slcr_write(sc->sc_rm, clock->clk_ctl_reg, ctl); + +out: + mtx_leave(&zynq_slcr_lock); + + return error; +} + +void +zqclock_enable(void *cookie, uint32_t *cells, int on) +{ + const struct zqclock_clock *clock; + struct zqclock_softc *sc = cookie; + uint32_t idx = cells[0]; + uint32_t ctl; + + clock = zqclock_get_clock(idx); + if (clock == NULL) + return; + + mtx_enter(&zynq_slcr_lock); + + ctl = zynq_slcr_read(sc->sc_rm, clock->clk_ctl_reg); + if (on) + ctl |= SLCR_CLK_CTRL_CLKACT(clock->clk_index); + else + ctl &= ~SLCR_CLK_CTRL_CLKACT(clock->clk_index); + zynq_slcr_write(sc->sc_rm, clock->clk_ctl_reg, ctl); + + mtx_leave(&zynq_slcr_lock); +}