Add zqclock(4), a driver for Zynq-7000 clocks.
authorvisa <visa@openbsd.org>
Fri, 30 Apr 2021 13:25:24 +0000 (13:25 +0000)
committervisa <visa@openbsd.org>
Fri, 30 Apr 2021 13:25:24 +0000 (13:25 +0000)
Input and OK kettenis@

share/man/man4/man4.armv7/Makefile
share/man/man4/man4.armv7/zqclock.4 [new file with mode: 0644]
sys/arch/armv7/conf/GENERIC
sys/arch/armv7/conf/RAMDISK
sys/arch/armv7/xilinx/files.xilinx
sys/arch/armv7/xilinx/zqclock.c [new file with mode: 0644]

index 58adbf1..ad10272 100644 (file)
@@ -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 (file)
index 0000000..d564a9d
--- /dev/null
@@ -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 .
index f1a5956..345889a 100644 (file)
@@ -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
index 385e636..677b482 100644 (file)
@@ -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
index 3907579..50ebc94 100644 (file)
@@ -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 (file)
index 0000000..b523c08
--- /dev/null
@@ -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 <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>
+#include <dev/ofw/ofw_misc.h>
+
+#include <armv7/xilinx/slcreg.h>
+
+#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);
+}