Here is an attempt to handle the clocks on armv7 in a similar way as we do with gpio, pinctrl and and regulators.
Currently the API is fairly limited. There is an interface to get the frequency at which a clock is running and there is an interface to enable a clock. Some devices have multiple clocks. In that case the device tree node for the device is supposed to have a "clock-names" property, and you can use those names to query/enable a particular clock. If there is only one clock, you can pass NULL instead. There are also interfaces with an _idx suffix. Those accept an index to specify a particular clock. These should not be used unless there is no "clock-names" property. For an example on how to use these APIs, see the changes to com_fdt.c. There no longer is a need to harcode the frequencies for particular hardware! The clock framework handles "simple" clocks such as "fixed-clock" and "fixed-factor-clock" all by itself. More complicated clock devices need to register themselves with the framework. The diff contains changes to sxiccmu(4) to add support for a few of the clocks found on the Allwinner A20. Here the clocks are nicely contained under /clocks in the device tree. The code simply walks that part of the tree and registers the clocks it recognized. We don't attach a full device driver for these. There simply are too many clocks and having a driver for each would just create a lot of dmesg spam. I think for sunxi(4) I've found a design that doesn't require an insane amount of code. And once all the sunxi drivers have been converted, some of the existing code will disappear. Any thoughts on the design? If it looks ok, I'd like to commit the code in dev/ofw/ofw_clock.[ch] and continue to write the sunxi code necessary to support clocks for the serial ports. Once that's done, the com_fdt.c bit can be committed. Index: dev/ofw/ofw_clock.c =================================================================== RCS file: dev/ofw/ofw_clock.c diff -N dev/ofw/ofw_clock.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ dev/ofw/ofw_clock.c 21 Aug 2016 14:53:15 -0000 @@ -0,0 +1,212 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2016 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 <sys/types.h> +#include <sys/systm.h> +#include <sys/malloc.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_clock.h> + +LIST_HEAD(, clock_device) clock_devices = + LIST_HEAD_INITIALIZER(clock_devices); + +void +clock_register(struct clock_device *cd) +{ + cd->cd_cells = OF_getpropint(cd->cd_node, "#clock-cells", 0); + cd->cd_phandle = OF_getpropint(cd->cd_node, "phandle", 0); + if (cd->cd_phandle == 0) + return; + + LIST_INSERT_HEAD(&clock_devices, cd, cd_list); +} + +uint32_t +clock_get_frequency_cells(uint32_t *cells) +{ + struct clock_device *cd; + uint32_t phandle = cells[0]; + int node; + + LIST_FOREACH(cd, &clock_devices, cd_list) { + if (cd->cd_phandle == phandle) + break; + } + + if (cd && cd->cd_get_frequency) + return cd->cd_get_frequency(cd->cd_cookie, &cells[1]); + + node = OF_getnodebyphandle(phandle); + if (node == 0) + return 0; + + if (OF_is_compatible(node, "fixed-clock")) + return OF_getpropint(node, "clock-frequency", 0); + + if (OF_is_compatible(node, "fixed-factor-clock")) { + uint32_t mult, div, freq; + + mult = OF_getpropint(node, "clock-mult", 1); + div = OF_getpropint(node, "clock-div", 1); + freq = clock_get_frequency(node, NULL); + return (freq * mult) / div; + } + + return 0; +} + +void +clock_enable_cells(uint32_t *cells) +{ + struct clock_device *cd; + uint32_t phandle = cells[0]; + + LIST_FOREACH(cd, &clock_devices, cd_list) { + if (cd->cd_phandle == phandle) + break; + } + + if (cd && cd->cd_enable) + cd->cd_enable(cd->cd_cookie, &cells[1], 1); +} + +uint32_t * +clock_next_clock(uint32_t *cells) +{ + uint32_t phandle = cells[0]; + int node, ncells; + + node = OF_getnodebyphandle(phandle); + if (node == 0) + return NULL; + + ncells = OF_getpropint(node, "#clock-cells", 0); + return cells + ncells + 1; +} + +int +clock_index(int node, const char *clock) +{ + char *names; + char *name; + char *end; + int idx = 0; + int len; + + if (clock == NULL) + return 0; + + len = OF_getproplen(node, "clock-names"); + if (len <= 0) + return -1; + + names = malloc(len, M_TEMP, M_WAITOK); + OF_getprop(node, "clock-names", names, len); + end = names + len; + name = names; + while (name < end) { + if (strcmp(name, clock) == 0) { + free(names, M_TEMP, len); + return idx; + } + name += strlen(name) + 1; + idx++; + } + free(names, M_TEMP, len); + return -1; +} + +uint32_t +clock_get_frequency_idx(int node, int idx) +{ + uint32_t *clocks; + uint32_t *clock; + uint32_t freq = 0; + int len; + + len = OF_getproplen(node, "clocks"); + if (len <= 0) + return 0; + + clocks = malloc(len, M_TEMP, M_WAITOK); + OF_getpropintarray(node, "clocks", clocks, len); + + clock = clocks; + while (clock && clock < clocks + (len / sizeof(uint32_t))) { + if (idx == 0) { + freq = clock_get_frequency_cells(clock); + break; + } + clock = clock_next_clock(clock); + idx--; + } + + free(clocks, M_TEMP, len); + return freq; +} + +uint32_t +clock_get_frequency(int node, const char *name) +{ + int idx; + + idx = clock_index(node, name); + if (idx == -1) + return 0; + + return clock_get_frequency_idx(node, idx); +} + +void +clock_enable_idx(int node, int idx) +{ + uint32_t *clocks; + uint32_t *clock; + int len; + + len = OF_getproplen(node, "clocks"); + if (len <= 0) + return; + + clocks = malloc(len, M_TEMP, M_WAITOK); + OF_getpropintarray(node, "clocks", clocks, len); + + clock = clocks; + while (clock && clock < clocks + (len / sizeof(uint32_t))) { + if (idx == 0) { + clock_enable_cells(clock); + break; + } + clock = clock_next_clock(clock); + idx--; + } + + free(clocks, M_TEMP, len); +} + +void +clock_enable(int node, const char *name) +{ + int idx; + + idx = clock_index(node, name); + if (idx == -1) + return; + + clock_enable_idx(node, idx); +} Index: dev/ofw/ofw_clock.h =================================================================== RCS file: dev/ofw/ofw_clock.h diff -N dev/ofw/ofw_clock.h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ dev/ofw/ofw_clock.h 21 Aug 2016 14:53:15 -0000 @@ -0,0 +1,39 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2016 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. + */ + +#ifndef _DEV_OFW_CLOCK_H_ +#define _DEV_OFW_CLOCK_H_ + +struct clock_device { + int cd_node; + void *cd_cookie; + uint32_t (*cd_get_frequency)(void *, uint32_t *); + void (*cd_enable)(void *, uint32_t *, int); + + LIST_ENTRY(clock_device) cd_list; + uint32_t cd_phandle; + uint32_t cd_cells; +}; + +void clock_register(struct clock_device *); + +uint32_t clock_get_frequency(int, const char *); +uint32_t clock_get_frequency_idx(int, int); +void clock_enable(int, const char *); +void clock_enable_idx(int, int); + +#endif /* _DEV_OFW_CLOCK_H_ */ Index: arch/armv7/conf/files.armv7 =================================================================== RCS file: /cvs/src/sys/arch/armv7/conf/files.armv7,v retrieving revision 1.24 diff -u -p -r1.24 files.armv7 --- arch/armv7/conf/files.armv7 15 Aug 2016 13:42:49 -0000 1.24 +++ arch/armv7/conf/files.armv7 21 Aug 2016 14:53:15 -0000 @@ -25,6 +25,7 @@ file arch/armv7/armv7/platform.c file arch/arm/arm/disksubr.c disk # FDT support +file dev/ofw/ofw_clock.c file dev/ofw/ofw_gpio.c file dev/ofw/ofw_pinctrl.c file dev/ofw/ofw_regulator.c Index: arch/armv7/dev/com_fdt.c =================================================================== RCS file: /cvs/src/sys/arch/armv7/dev/com_fdt.c,v retrieving revision 1.8 diff -u -p -r1.8 com_fdt.c --- arch/armv7/dev/com_fdt.c 20 Aug 2016 15:44:04 -0000 1.8 +++ arch/armv7/dev/com_fdt.c 21 Aug 2016 14:53:15 -0000 @@ -36,6 +36,7 @@ #include <dev/ofw/fdt.h> #include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_clock.h> #include <dev/ofw/ofw_pinctrl.h> #define com_usr 31 /* Synopsys DesignWare UART */ @@ -113,11 +114,22 @@ com_fdt_attach(struct device *parent, st struct com_fdt_softc *sc = (struct com_fdt_softc *)self; struct fdt_attach_args *faa = aux; int (*intr)(void *) = comintr; - int node; + uint32_t freq; if (faa->fa_nreg < 1) return; + clock_enable(faa->fa_node, NULL); + + /* + * Determine the clock frequency after enabling the clock. + * This gives the clock code a chance to configure the + * appropriate frequency for us. + */ + freq = OF_getpropint(faa->fa_node, "clock-frequency", 0); + if (freq == 0) + freq = clock_get_frequency(faa->fa_node, NULL); + /* * XXX This sucks. We need to get rid of the a4x bus tag * altogether. For this we will need to change com(4). @@ -129,26 +141,14 @@ com_fdt_attach(struct device *parent, st sc->sc.sc_iot = &sc->sc_iot; sc->sc.sc_iobase = faa->fa_reg[0].addr; sc->sc.sc_uarttype = COM_UART_16550; - sc->sc.sc_frequency = COM_FREQ; + sc->sc.sc_frequency = freq ? freq : COM_FREQ; if (OF_is_compatible(faa->fa_node, "snps,dw-apb-uart")) intr = com_fdt_intr_designware; - if (OF_is_compatible(faa->fa_node, "brcm,bcm2835-aux-uart")) - sc->sc.sc_frequency = 500000000; - if (OF_is_compatible(faa->fa_node, "ti,omap3-uart") || - OF_is_compatible(faa->fa_node, "ti,omap4-uart")) { + OF_is_compatible(faa->fa_node, "ti,omap4-uart")) sc->sc.sc_uarttype = COM_UART_TI16750; - sc->sc.sc_frequency = 48000000; - } - - if ((node = OF_finddevice("/")) != 0 && - (OF_is_compatible(node, "allwinner,sun4i-a10") || - OF_is_compatible(node, "allwinner,sun5i-a10s") || - OF_is_compatible(node, "allwinner,sun5i-r8") || - OF_is_compatible(node, "allwinner,sun7i-a20"))) - sc->sc.sc_frequency = 24000000; if (stdout_node == faa->fa_node) { SET(sc->sc.sc_hwflags, COM_HW_CONSOLE); Index: arch/armv7/sunxi/sxiccmu.c =================================================================== RCS file: /cvs/src/sys/arch/armv7/sunxi/sxiccmu.c,v retrieving revision 1.7 diff -u -p -r1.7 sxiccmu.c --- arch/armv7/sunxi/sxiccmu.c 20 Aug 2016 19:34:44 -0000 1.7 +++ arch/armv7/sunxi/sxiccmu.c 21 Aug 2016 14:53:15 -0000 @@ -20,6 +20,7 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> +#include <sys/malloc.h> #include <sys/time.h> #include <sys/device.h> @@ -32,6 +33,9 @@ #include <armv7/sunxi/sunxireg.h> #include <armv7/sunxi/sxiccmuvar.h> +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_clock.h> + #ifdef DEBUG_CCMU #define DPRINTF(x) do { printf x; } while (0) #else @@ -149,11 +153,14 @@ struct cfdriver sxiccmu_cd = { NULL, "sxiccmu", DV_DULL }; +void sxiccmu_attach_clock(struct sxiccmu_softc *, int); + void sxiccmu_attach(struct device *parent, struct device *self, void *args) { struct sxiccmu_softc *sc = (struct sxiccmu_softc *)self; struct armv7_attach_args *aa = args; + int node; sc->sc_iot = aa->aa_iot; @@ -161,7 +168,173 @@ sxiccmu_attach(struct device *parent, st aa->aa_dev->mem[0].size, 0, &sc->sc_ioh)) panic("sxiccmu_attach: bus_space_map failed!"); + node = OF_finddevice("/clocks"); + if (node == -1) + panic("%s: can't find clocks", __func__); + printf("\n"); + + for (node = OF_child(node); node; node = OF_peer(node)) + sxiccmu_attach_clock(sc, node); +} + +struct sxiccmu_clock { + struct clock_device sc_cd; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + int sc_node; +}; + +struct sxiccmu_device { + const char *compat; + uint32_t (*get_frequency)(void *, uint32_t *); + void (*enable)(void *, uint32_t *, int); +}; + +uint32_t sxiccmu_gen_get_frequency(void *, uint32_t *); +uint32_t sxiccmu_osc_get_frequency(void *, uint32_t *); +uint32_t sxiccmu_pll6_get_frequency(void *, uint32_t *); +uint32_t sxiccmu_apb1_get_frequency(void *, uint32_t *); + +void sxiccmu_gate_enable(void *, uint32_t *, int); + +struct sxiccmu_device sxiccmu_devices[] = { + { + .compat = "allwinner,sun4i-a10-osc-clk", + .get_frequency = sxiccmu_osc_get_frequency, + }, + { + .compat = "allwinner,sun4i-a10-pll6-clk", + .get_frequency = sxiccmu_pll6_get_frequency, + }, + { + .compat = "allwinner,sun4i-a10-apb1-clk", + .get_frequency = sxiccmu_apb1_get_frequency, + }, + { + .compat = "allwinner,sun7i-a20-apb1-gates-clk", + .get_frequency = sxiccmu_gen_get_frequency, + .enable = sxiccmu_gate_enable + }, +}; + +void +sxiccmu_attach_clock(struct sxiccmu_softc *sc, int node) +{ + struct sxiccmu_clock *clock; + uint32_t reg[2]; + int i; + + for (i = 0; i < nitems(sxiccmu_devices); i++) + if (OF_is_compatible(node, sxiccmu_devices[i].compat)) + break; + if (i == nitems(sxiccmu_devices)) + return; + + clock = malloc(sizeof(*clock), M_DEVBUF, M_WAITOK); + clock->sc_node = node; + + if (OF_getpropintarray(node, "reg", reg, sizeof(reg)) == sizeof(reg)) { + clock->sc_iot = sc->sc_iot; + if (bus_space_map(clock->sc_iot, reg[0], reg[1], 0, + &clock->sc_ioh)) { + printf("%s: can't map registers", sc->sc_dev.dv_xname); + free(clock, M_DEVBUF, sizeof(*clock)); + return; + } + } + + clock->sc_cd.cd_node = node; + clock->sc_cd.cd_cookie = clock; + clock->sc_cd.cd_get_frequency = sxiccmu_devices[i].get_frequency; + clock->sc_cd.cd_enable = sxiccmu_devices[i].enable; + clock_register(&clock->sc_cd); +} + +/* + * A "generic" function that simply gets the clock frequency from the + * parent clock. Useful for clock gating devices that don't scale the + * their clocks. + */ +uint32_t +sxiccmu_gen_get_frequency(void *cookie, uint32_t *cells) +{ + struct sxiccmu_clock *sc = cookie; + + return clock_get_frequency(sc->sc_node, NULL); +} + +uint32_t +sxiccmu_osc_get_frequency(void *cookie, uint32_t *cells) +{ + struct sxiccmu_clock *sc = cookie; + + return OF_getpropint(sc->sc_node, "clock-frequency", 24000000); +} + +#define CCU_PLL6_FACTOR_N(x) (((x) >> 8) & 0x1f) +#define CCU_PLL6_FACTOR_K(x) (((x) >> 4) & 0x3) +#define CCU_PLL6_FACTOR_M(x) (((x) >> 0) & 0x3) + +uint32_t +sxiccmu_pll6_get_frequency(void *cookie, uint32_t *cells) +{ + struct sxiccmu_clock *sc = cookie; + uint32_t reg, k, m, n, freq; + uint32_t idx = cells[0]; + + /* XXX Assume bypass is disabled. */ + reg = SXIREAD4(sc, 0); + k = CCU_PLL6_FACTOR_K(reg) + 1; + m = CCU_PLL6_FACTOR_M(reg) + 1; + n = CCU_PLL6_FACTOR_N(reg); + + freq = clock_get_frequency_idx(sc->sc_node, 0); + switch (idx) { + case 0: + return (freq * n * k) / m / 6; /* pll6_sata */ + case 1: + return (freq * n * k) / m / 2; /* pll6_other */ + case 2: + return (freq * n * k) / m; /* pll6 */ + case 3: + return (freq * n * k) / m / 4; /* pll6_div_4 */ + } + + return 0; +} + +#define CCU_APB1_CLK_RAT_N(x) (((x) >> 16) & 0x3) +#define CCU_APB1_CLK_RAT_M(x) (((x) >> 0) & 0x1f) +#define CCU_APB1_CLK_SRC_SEL(x) (((x) >> 24) & 0x3) + +uint32_t +sxiccmu_apb1_get_frequency(void *cookie, uint32_t *cells) +{ + struct sxiccmu_clock *sc = cookie; + uint32_t reg, m, n, freq; + int idx; + + reg = SXIREAD4(sc, 0); + m = CCU_APB1_CLK_RAT_M(reg); + n = CCU_APB1_CLK_RAT_N(reg); + idx = CCU_APB1_CLK_SRC_SEL(reg); + + freq = clock_get_frequency_idx(sc->sc_node, idx); + return freq / (1 << n) / (m + 1); +} + +void +sxiccmu_gate_enable(void *cookie, uint32_t *cells, int on) +{ + struct sxiccmu_clock *sc = cookie; + int reg = cells[0] / 32; + int bit = cells[0] % 32; + + if (on) + SXISET4(sc, reg * 4, (1U << bit)); + else + SXICLR4(sc, reg * 4, (1U << bit)); } /* XXX spl? */