Index: head/sys/arm/allwinner/clk/aw_debeclk.c =================================================================== --- head/sys/arm/allwinner/clk/aw_debeclk.c (revision 310177) +++ head/sys/arm/allwinner/clk/aw_debeclk.c (revision 310178) @@ -1,351 +1,354 @@ /*- * Copyright (c) 2016 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Allwinner display backend clocks */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #include "hwreset_if.h" #define SCLK_GATING (1 << 31) #define BE_RST (1 << 30) #define CLK_SRC_SEL (0x3 << 24) #define CLK_SRC_SEL_SHIFT 24 #define CLK_SRC_SEL_MAX 2 #define CLK_SRC_SEL_PLL3 0 #define CLK_SRC_SEL_PLL7 1 #define CLK_SRC_SEL_PLL5 2 #define CLK_RATIO_M (0xf << 0) #define CLK_RATIO_M_SHIFT 0 #define CLK_RATIO_M_MAX 0xf static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-de-be-clk", 1 }, { NULL, 0 } }; struct aw_debeclk_softc { device_t clkdev; bus_addr_t reg; }; #define DEBECLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define DEBECLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEBECLK_MODIFY(sc, clr, set) \ CLKDEV_MODIFY_4((sc)->clkdev, (sc)->reg, (clr), (set)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_debeclk_hwreset_assert(device_t dev, intptr_t id, bool value) { struct aw_debeclk_softc *sc; int error; sc = device_get_softc(dev); DEVICE_LOCK(sc); error = DEBECLK_MODIFY(sc, BE_RST, value ? 0 : BE_RST); DEVICE_UNLOCK(sc); return (error); } static int aw_debeclk_hwreset_is_asserted(device_t dev, intptr_t id, bool *value) { struct aw_debeclk_softc *sc; uint32_t val; int error; sc = device_get_softc(dev); DEVICE_LOCK(sc); error = DEBECLK_READ(sc, &val); DEVICE_UNLOCK(sc); if (error) return (error); *value = (val & BE_RST) != 0 ? false : true; return (0); } static int aw_debeclk_init(struct clknode *clk, device_t dev) { struct aw_debeclk_softc *sc; uint32_t val, index; sc = clknode_get_softc(clk); /* Set BE source to PLL5 (DDR external peripheral clock) */ index = CLK_SRC_SEL_PLL5; DEVICE_LOCK(sc); DEBECLK_READ(sc, &val); val &= ~CLK_SRC_SEL; val |= (index << CLK_SRC_SEL_SHIFT); DEBECLK_WRITE(sc, val); DEVICE_UNLOCK(sc); clknode_init_parent_idx(clk, index); return (0); } static int aw_debeclk_set_mux(struct clknode *clk, int index) { struct aw_debeclk_softc *sc; uint32_t val; sc = clknode_get_softc(clk); if (index < 0 || index > CLK_SRC_SEL_MAX) return (ERANGE); DEVICE_LOCK(sc); DEBECLK_READ(sc, &val); val &= ~CLK_SRC_SEL; val |= (index << CLK_SRC_SEL_SHIFT); DEBECLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_debeclk_set_gate(struct clknode *clk, bool enable) { struct aw_debeclk_softc *sc; uint32_t val; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); DEBECLK_READ(sc, &val); if (enable) val |= SCLK_GATING; else val &= ~SCLK_GATING; DEBECLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_debeclk_recalc_freq(struct clknode *clk, uint64_t *freq) { struct aw_debeclk_softc *sc; uint32_t val, m; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); DEBECLK_READ(sc, &val); DEVICE_UNLOCK(sc); m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1; *freq = *freq / m; return (0); } static int aw_debeclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop) { struct aw_debeclk_softc *sc; uint32_t val, m; sc = clknode_get_softc(clk); m = howmany(fin, *fout) - 1; + *fout = fin / (m + 1); + *stop = 1; + + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); DEBECLK_READ(sc, &val); val &= ~CLK_RATIO_M; val |= (m << CLK_RATIO_M_SHIFT); DEBECLK_WRITE(sc, val); DEVICE_UNLOCK(sc); - - *fout = fin / (m + 1); - *stop = 1; return (0); } static clknode_method_t aw_debeclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_debeclk_init), CLKNODEMETHOD(clknode_set_gate, aw_debeclk_set_gate), CLKNODEMETHOD(clknode_set_mux, aw_debeclk_set_mux), CLKNODEMETHOD(clknode_recalc_freq, aw_debeclk_recalc_freq), CLKNODEMETHOD(clknode_set_freq, aw_debeclk_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_debeclk_clknode, aw_debeclk_clknode_class, aw_debeclk_clknode_methods, sizeof(struct aw_debeclk_softc), clknode_class); static int aw_debeclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner Display Engine Backend Clock"); return (BUS_PROBE_DEFAULT); } static int aw_debeclk_attach(device_t dev) { struct clknode_init_def def; struct aw_debeclk_softc *sc, *clk_sc; struct clkdom *clkdom; struct clknode *clk; clk_t clk_parent; bus_size_t psize; phandle_t node; int error, ncells, i; sc = device_get_softc(dev); sc->clkdev = device_get_parent(dev); node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0) { device_printf(dev, "cannot get clock count\n"); return (error); } clkdom = clkdom_create(dev); memset(&def, 0, sizeof(def)); error = clk_parse_ofw_clk_name(dev, node, &def.name); if (error != 0) { device_printf(dev, "cannot parse clock name\n"); error = ENXIO; goto fail; } def.id = 1; def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", i); goto fail; } def.parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } def.parent_cnt = ncells; clk = clknode_create(clkdom, &aw_debeclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } clk_sc = clknode_get_softc(clk); clk_sc->reg = sc->reg; clk_sc->clkdev = device_get_parent(dev); clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); hwreset_register_ofw_provider(dev); return (0); fail: return (error); } static device_method_t aw_debeclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_debeclk_probe), DEVMETHOD(device_attach, aw_debeclk_attach), /* Reset interface */ DEVMETHOD(hwreset_assert, aw_debeclk_hwreset_assert), DEVMETHOD(hwreset_is_asserted, aw_debeclk_hwreset_is_asserted), DEVMETHOD_END }; static driver_t aw_debeclk_driver = { "aw_debeclk", aw_debeclk_methods, sizeof(struct aw_debeclk_softc) }; static devclass_t aw_debeclk_devclass; EARLY_DRIVER_MODULE(aw_debeclk, simplebus, aw_debeclk_driver, aw_debeclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/arm/allwinner/clk/aw_hdmiclk.c =================================================================== --- head/sys/arm/allwinner/clk/aw_hdmiclk.c (revision 310177) +++ head/sys/arm/allwinner/clk/aw_hdmiclk.c (revision 310178) @@ -1,315 +1,318 @@ /*- * Copyright (c) 2016 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Allwinner HDMI clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define SCLK_GATING (1 << 31) #define CLK_SRC_SEL (0x3 << 24) #define CLK_SRC_SEL_SHIFT 24 #define CLK_SRC_SEL_MAX 0x3 #define CLK_RATIO_N (0x3 << 16) #define CLK_RATIO_N_SHIFT 16 #define CLK_RATIO_N_MAX 0x3 #define CLK_RATIO_M (0x1f << 0) #define CLK_RATIO_M_SHIFT 0 #define CLK_RATIO_M_MAX 0x1f #define CLK_IDX_PLL3_1X 0 static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-hdmi-clk", 1 }, { NULL, 0 } }; struct aw_hdmiclk_sc { device_t clkdev; bus_addr_t reg; }; #define HDMICLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define HDMICLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_hdmiclk_init(struct clknode *clk, device_t dev) { struct aw_hdmiclk_sc *sc; uint32_t val, index; sc = clknode_get_softc(clk); /* Select PLL3(1X) clock source */ index = CLK_IDX_PLL3_1X; DEVICE_LOCK(sc); HDMICLK_READ(sc, &val); val &= ~CLK_SRC_SEL; val |= (index << CLK_SRC_SEL_SHIFT); HDMICLK_WRITE(sc, val); DEVICE_UNLOCK(sc); clknode_init_parent_idx(clk, index); return (0); } static int aw_hdmiclk_set_mux(struct clknode *clk, int index) { struct aw_hdmiclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); if (index < 0 || index > CLK_SRC_SEL_MAX) return (ERANGE); DEVICE_LOCK(sc); HDMICLK_READ(sc, &val); val &= ~CLK_SRC_SEL; val |= (index << CLK_SRC_SEL_SHIFT); HDMICLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_hdmiclk_set_gate(struct clknode *clk, bool enable) { struct aw_hdmiclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); HDMICLK_READ(sc, &val); if (enable) val |= SCLK_GATING; else val &= ~SCLK_GATING; HDMICLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_hdmiclk_recalc_freq(struct clknode *clk, uint64_t *freq) { struct aw_hdmiclk_sc *sc; uint32_t val, m, n; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); HDMICLK_READ(sc, &val); DEVICE_UNLOCK(sc); n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT); m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1; *freq = *freq / n / m; return (0); } static int aw_hdmiclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop) { struct aw_hdmiclk_sc *sc; uint32_t val, m, n, best_m, best_n; uint64_t cur_freq; int64_t best_diff, cur_diff; sc = clknode_get_softc(clk); best_n = best_m = 0; best_diff = (int64_t)*fout; for (n = 0; n <= CLK_RATIO_N_MAX; n++) for (m = 0; m <= CLK_RATIO_M_MAX; m++) { cur_freq = fin / (1 << n) / (m + 1); cur_diff = (int64_t)*fout - cur_freq; if (cur_diff >= 0 && cur_diff < best_diff) { best_diff = cur_diff; best_m = m; best_n = n; } } if (best_diff == (int64_t)*fout) return (ERANGE); + *fout = fin / (1 << best_n) / (best_m + 1); + *stop = 1; + + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); HDMICLK_READ(sc, &val); val &= ~(CLK_RATIO_N | CLK_RATIO_M); val |= (best_n << CLK_RATIO_N_SHIFT); val |= (best_m << CLK_RATIO_M_SHIFT); HDMICLK_WRITE(sc, val); DEVICE_UNLOCK(sc); - - *fout = fin / (1 << best_n) / (best_m + 1); - *stop = 1; return (0); } static clknode_method_t aw_hdmiclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_hdmiclk_init), CLKNODEMETHOD(clknode_set_gate, aw_hdmiclk_set_gate), CLKNODEMETHOD(clknode_set_mux, aw_hdmiclk_set_mux), CLKNODEMETHOD(clknode_recalc_freq, aw_hdmiclk_recalc_freq), CLKNODEMETHOD(clknode_set_freq, aw_hdmiclk_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_hdmiclk_clknode, aw_hdmiclk_clknode_class, aw_hdmiclk_clknode_methods, sizeof(struct aw_hdmiclk_sc), clknode_class); static int aw_hdmiclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner HDMI Clock"); return (BUS_PROBE_DEFAULT); } static int aw_hdmiclk_attach(device_t dev) { struct clknode_init_def def; struct aw_hdmiclk_sc *sc; struct clkdom *clkdom; struct clknode *clk; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; int error; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } clkdom = clkdom_create(dev); error = clk_get_by_ofw_index(dev, 0, 0, &clk_parent); if (error != 0) { device_printf(dev, "cannot parse clock parent\n"); return (ENXIO); } memset(&def, 0, sizeof(def)); error = clk_parse_ofw_clk_name(dev, node, &def.name); if (error != 0) { device_printf(dev, "cannot parse clock name\n"); error = ENXIO; goto fail; } def.id = 1; def.parent_names = malloc(sizeof(char *), M_OFWPROP, M_WAITOK); def.parent_names[0] = clk_get_name(clk_parent); def.parent_cnt = 1; clk = clknode_create(clkdom, &aw_hdmiclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } sc = clknode_get_softc(clk); sc->reg = paddr; sc->clkdev = device_get_parent(dev); clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_hdmiclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_hdmiclk_probe), DEVMETHOD(device_attach, aw_hdmiclk_attach), DEVMETHOD_END }; static driver_t aw_hdmiclk_driver = { "aw_hdmiclk", aw_hdmiclk_methods, 0 }; static devclass_t aw_hdmiclk_devclass; EARLY_DRIVER_MODULE(aw_hdmiclk, simplebus, aw_hdmiclk_driver, aw_hdmiclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/arm/allwinner/clk/aw_lcdclk.c =================================================================== --- head/sys/arm/allwinner/clk/aw_lcdclk.c (revision 310177) +++ head/sys/arm/allwinner/clk/aw_lcdclk.c (revision 310178) @@ -1,614 +1,627 @@ /*- * Copyright (c) 2016 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Allwinner LCD clocks */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #include "hwreset_if.h" /* CH0 */ #define CH0_SCLK_GATING (1 << 31) #define CH0_LCD_RST (1 << 30) #define CH0_CLK_SRC_SEL (0x3 << 24) #define CH0_CLK_SRC_SEL_SHIFT 24 #define CH0_CLK_SRC_SEL_PLL3_1X 0 #define CH0_CLK_SRC_SEL_PLL7_1X 1 #define CH0_CLK_SRC_SEL_PLL3_2X 2 #define CH0_CLK_SRC_SEL_PLL6 3 /* CH1 */ #define CH1_SCLK2_GATING (1 << 31) #define CH1_SCLK2_SEL (0x3 << 24) #define CH1_SCLK2_SEL_SHIFT 24 #define CH1_SCLK2_SEL_PLL3_1X 0 #define CH1_SCLK2_SEL_PLL7_1X 1 #define CH1_SCLK2_SEL_PLL3_2X 2 #define CH1_SCLK2_SEL_PLL7_2X 3 #define CH1_SCLK1_GATING (1 << 15) #define CH1_SCLK1_SEL (0x1 << 11) #define CH1_SCLK1_SEL_SHIFT 11 #define CH1_SCLK1_SEL_SCLK2 0 #define CH1_SCLK1_SEL_SCLK2_DIV2 1 #define CH1_CLK_DIV_RATIO_M (0x1f << 0) #define CH1_CLK_DIV_RATIO_M_SHIFT 0 #define TCON_PLLREF 3000000ULL #define TCON_PLLREF_FRAC1 297000000ULL #define TCON_PLLREF_FRAC2 270000000ULL #define TCON_PLL_M_MIN 1 #define TCON_PLL_M_MAX 15 #define TCON_PLL_N_MIN 9 #define TCON_PLL_N_MAX 127 #define CLK_IDX_CH1_SCLK1 0 #define CLK_IDX_CH1_SCLK2 1 #define CLK_IDX_ enum aw_lcdclk_type { AW_LCD_CH0 = 1, AW_LCD_CH1, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-lcd-ch0-clk", AW_LCD_CH0 }, { "allwinner,sun4i-a10-lcd-ch1-clk", AW_LCD_CH1 }, { NULL, 0 } }; struct aw_lcdclk_softc { enum aw_lcdclk_type type; device_t clkdev; bus_addr_t reg; int id; }; #define LCDCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define LCDCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define LCDCLK_MODIFY(sc, clr, set) \ CLKDEV_MODIFY_4((sc)->clkdev, (sc)->reg, (clr), (set)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_lcdclk_hwreset_assert(device_t dev, intptr_t id, bool value) { struct aw_lcdclk_softc *sc; int error; sc = device_get_softc(dev); if (sc->type != AW_LCD_CH0) return (ENXIO); DEVICE_LOCK(sc); error = LCDCLK_MODIFY(sc, CH0_LCD_RST, value ? 0 : CH0_LCD_RST); DEVICE_UNLOCK(sc); return (error); } static int aw_lcdclk_hwreset_is_asserted(device_t dev, intptr_t id, bool *value) { struct aw_lcdclk_softc *sc; uint32_t val; int error; sc = device_get_softc(dev); if (sc->type != AW_LCD_CH0) return (ENXIO); DEVICE_LOCK(sc); error = LCDCLK_READ(sc, &val); DEVICE_UNLOCK(sc); if (error) return (error); *value = (val & CH0_LCD_RST) != 0 ? false : true; return (0); } static int aw_lcdclk_init(struct clknode *clk, device_t dev) { struct aw_lcdclk_softc *sc; uint32_t val, index; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); LCDCLK_READ(sc, &val); DEVICE_UNLOCK(sc); switch (sc->type) { case AW_LCD_CH0: index = (val & CH0_CLK_SRC_SEL) >> CH0_CLK_SRC_SEL_SHIFT; break; case AW_LCD_CH1: switch (sc->id) { case CLK_IDX_CH1_SCLK1: index = 0; break; case CLK_IDX_CH1_SCLK2: index = (val & CH1_SCLK2_SEL_SHIFT) >> CH1_SCLK2_SEL_SHIFT; break; default: return (ENXIO); } break; default: return (ENXIO); } clknode_init_parent_idx(clk, index); return (0); } static int aw_lcdclk_set_mux(struct clknode *clk, int index) { struct aw_lcdclk_softc *sc; uint32_t val; sc = clknode_get_softc(clk); switch (sc->type) { case AW_LCD_CH0: DEVICE_LOCK(sc); LCDCLK_READ(sc, &val); val &= ~CH0_CLK_SRC_SEL; val |= (index << CH0_CLK_SRC_SEL_SHIFT); LCDCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); break; case AW_LCD_CH1: switch (sc->id) { case CLK_IDX_CH1_SCLK2: DEVICE_LOCK(sc); LCDCLK_READ(sc, &val); val &= ~CH1_SCLK2_SEL; val |= (index << CH1_SCLK2_SEL_SHIFT); LCDCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); break; default: return (ENXIO); } break; default: return (ENXIO); } return (0); } static int aw_lcdclk_set_gate(struct clknode *clk, bool enable) { struct aw_lcdclk_softc *sc; uint32_t val, mask; sc = clknode_get_softc(clk); switch (sc->type) { case AW_LCD_CH0: mask = CH0_SCLK_GATING; break; case AW_LCD_CH1: mask = (sc->id == CLK_IDX_CH1_SCLK1) ? CH1_SCLK1_GATING : CH1_SCLK2_GATING; break; default: return (ENXIO); } DEVICE_LOCK(sc); LCDCLK_READ(sc, &val); if (enable) val |= mask; else val &= ~mask; LCDCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_lcdclk_recalc_freq(struct clknode *clk, uint64_t *freq) { struct aw_lcdclk_softc *sc; uint32_t val, m, src_sel; sc = clknode_get_softc(clk); if (sc->type != AW_LCD_CH1) return (0); DEVICE_LOCK(sc); LCDCLK_READ(sc, &val); DEVICE_UNLOCK(sc); m = ((val & CH1_CLK_DIV_RATIO_M) >> CH1_CLK_DIV_RATIO_M_SHIFT) + 1; *freq = *freq / m; if (sc->id == CLK_IDX_CH1_SCLK1) { src_sel = (val & CH1_SCLK1_SEL) >> CH1_SCLK1_SEL_SHIFT; if (src_sel == CH1_SCLK1_SEL_SCLK2_DIV2) *freq /= 2; } return (0); } static void calc_tcon_pll_integer(uint64_t fin, uint64_t fout, uint32_t *pm, uint32_t *pn) { int64_t diff, fcur, best; int m, n; best = fout; for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) { for (n = TCON_PLL_N_MIN; n <= TCON_PLL_N_MAX; n++) { fcur = (n * fin) / m; diff = (int64_t)fout - fcur; if (diff > 0 && diff < best) { best = diff; *pm = m; *pn = n; } } } } static int calc_tcon_pll_fractional(uint64_t fin, uint64_t fout, int *clk_div) { int m; /* Test for 1X match */ for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) { if (fout == (fin / m)) { *clk_div = m; return (CH0_CLK_SRC_SEL_PLL3_1X); } } /* Test for 2X match */ for (m = TCON_PLL_M_MIN; m <= TCON_PLL_M_MAX; m++) { if (fout == ((fin * 2) / m)) { *clk_div = m; return (CH0_CLK_SRC_SEL_PLL3_2X); } } return (-1); } static int calc_tcon_pll(uint64_t fin, uint64_t fout, uint64_t *pll_freq, int *tcon_pll_div) { uint32_t m, m2, n, n2; uint64_t fsingle, fdouble; int src_sel; bool dbl; /* Test fractional freq first */ src_sel = calc_tcon_pll_fractional(TCON_PLLREF_FRAC1, fout, tcon_pll_div); if (src_sel != -1) { *pll_freq = TCON_PLLREF_FRAC1; return src_sel; } src_sel = calc_tcon_pll_fractional(TCON_PLLREF_FRAC2, fout, tcon_pll_div); if (src_sel != -1) { *pll_freq = TCON_PLLREF_FRAC2; return src_sel; } m = n = m2 = n2 = 0; dbl = false; /* Find the frequency closes to the target dot clock, using * both 1X and 2X PLL inputs as possible candidates. */ calc_tcon_pll_integer(TCON_PLLREF, fout, &m, &n); calc_tcon_pll_integer(TCON_PLLREF * 2, fout, &m2, &n2); fsingle = m ? (n * TCON_PLLREF) / m : 0; fdouble = m2 ? (n2 * TCON_PLLREF * 2) / m2 : 0; if (fdouble > fsingle) { dbl = true; m = m2; n = n2; } /* Set desired parent frequency */ *pll_freq = n * TCON_PLLREF; *tcon_pll_div = m; /* Return the desired source clock */ return (dbl ? CH0_CLK_SRC_SEL_PLL3_2X : CH0_CLK_SRC_SEL_PLL3_1X); } static int aw_lcdclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop) { struct aw_lcdclk_softc *sc; + struct clknode *parent_clk; + const char **parent_names; uint64_t pll_freq; uint32_t val, src_sel; int error, tcon_pll_div; sc = clknode_get_softc(clk); - switch (sc->type) { - case AW_LCD_CH0: + if (sc->type == AW_LCD_CH0) { *stop = 0; - break; - case AW_LCD_CH1: - if (sc->id != CLK_IDX_CH1_SCLK2) - return (ENXIO); + return (0); + } - src_sel = calc_tcon_pll(fin, *fout, &pll_freq, &tcon_pll_div); + if (sc->id != CLK_IDX_CH1_SCLK2) + return (ENXIO); - /* Switch parent clock if necessary */ - if (src_sel != clknode_get_parent_idx(clk)) { - error = clknode_set_parent_by_idx(clk, src_sel); - if (error != 0) - return (error); - } + src_sel = calc_tcon_pll(fin, *fout, &pll_freq, &tcon_pll_div); - error = clknode_set_freq(clknode_get_parent(clk), pll_freq, - 0, 0); - if (error != 0) - return (error); + parent_names = clknode_get_parent_names(clk); + parent_clk = clknode_find_by_name(parent_names[src_sel]); - error = clknode_enable(clknode_get_parent(clk)); - if (error != 0) - return (error); + if (parent_clk == NULL) + return (ERANGE); - /* Fetch new input frequency */ - error = clknode_get_freq(clknode_get_parent(clk), &pll_freq); - if (error != 0) - return (error); + /* Fetch input frequency */ + error = clknode_get_freq(parent_clk, &pll_freq); + if (error != 0) + return (error); - /* Set LCD divisor */ - DEVICE_LOCK(sc); - LCDCLK_READ(sc, &val); - val &= ~CH1_CLK_DIV_RATIO_M; - val |= ((tcon_pll_div - 1) << CH1_CLK_DIV_RATIO_M_SHIFT); - LCDCLK_WRITE(sc, val); - DEVICE_UNLOCK(sc); + *fout = pll_freq / tcon_pll_div; + *stop = 1; - *fout = pll_freq / tcon_pll_div; - *stop = 1; + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); - break; - } + /* Switch parent clock if necessary */ + error = clknode_set_parent_by_idx(clk, src_sel); + if (error != 0) + return (error); + + error = clknode_set_freq(parent_clk, pll_freq, + 0, 0); + if (error != 0) + return (error); + + /* Fetch new input frequency */ + error = clknode_get_freq(parent_clk, &pll_freq); + if (error != 0) + return (error); + + *fout = pll_freq / tcon_pll_div; + + error = clknode_enable(parent_clk); + if (error != 0) + return (error); + + /* Set LCD divisor */ + DEVICE_LOCK(sc); + LCDCLK_READ(sc, &val); + val &= ~CH1_CLK_DIV_RATIO_M; + val |= ((tcon_pll_div - 1) << CH1_CLK_DIV_RATIO_M_SHIFT); + LCDCLK_WRITE(sc, val); + DEVICE_UNLOCK(sc); return (0); } static clknode_method_t aw_lcdclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_lcdclk_init), CLKNODEMETHOD(clknode_set_gate, aw_lcdclk_set_gate), CLKNODEMETHOD(clknode_set_mux, aw_lcdclk_set_mux), CLKNODEMETHOD(clknode_recalc_freq, aw_lcdclk_recalc_freq), CLKNODEMETHOD(clknode_set_freq, aw_lcdclk_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_lcdclk_clknode, aw_lcdclk_clknode_class, aw_lcdclk_clknode_methods, sizeof(struct aw_lcdclk_softc), clknode_class); static int aw_lcdclk_create(device_t dev, struct clkdom *clkdom, const char **parent_names, int parent_cnt, const char *name, int index) { struct aw_lcdclk_softc *sc, *clk_sc; struct clknode_init_def def; struct clknode *clk; phandle_t node; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); memset(&def, 0, sizeof(def)); def.id = index; def.name = name; def.parent_names = parent_names; def.parent_cnt = parent_cnt; clk = clknode_create(clkdom, &aw_lcdclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); return (ENXIO); } clk_sc = clknode_get_softc(clk); clk_sc->type = sc->type; clk_sc->reg = sc->reg; clk_sc->clkdev = sc->clkdev; clk_sc->id = index; clknode_register(clkdom, clk); return (0); } static int aw_lcdclk_probe(device_t dev) { enum aw_lcdclk_type type; if (!ofw_bus_status_okay(dev)) return (ENXIO); type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; switch (type) { case AW_LCD_CH0: device_set_desc(dev, "Allwinner LCD CH0 Clock"); break; case AW_LCD_CH1: device_set_desc(dev, "Allwinner LCD CH1 Clock"); break; default: return (ENXIO); } return (BUS_PROBE_DEFAULT); } static int aw_lcdclk_attach(device_t dev) { struct aw_lcdclk_softc *sc; struct clkdom *clkdom; clk_t clk_parent; bus_size_t psize; phandle_t node; uint32_t *indices; const char **parent_names; const char **names; int error, ncells, nout, i; sc = device_get_softc(dev); sc->clkdev = device_get_parent(dev); sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &sc->reg, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0) { device_printf(dev, "cannot get clock count\n"); return (error); } parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", i); goto fail; } parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } nout = clk_parse_ofw_out_names(dev, node, &names, &indices); if (nout == 0) { device_printf(dev, "no clock outputs found\n"); return (error); } clkdom = clkdom_create(dev); for (i = 0; i < nout; i++) { error = aw_lcdclk_create(dev, clkdom, parent_names, ncells, names[i], nout == 1 ? 1 : i); if (error) goto fail; } if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); if (sc->type == AW_LCD_CH0) hwreset_register_ofw_provider(dev); OF_prop_free(parent_names); return (0); fail: OF_prop_free(parent_names); return (error); } static device_method_t aw_lcdclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_lcdclk_probe), DEVMETHOD(device_attach, aw_lcdclk_attach), /* Reset interface */ DEVMETHOD(hwreset_assert, aw_lcdclk_hwreset_assert), DEVMETHOD(hwreset_is_asserted, aw_lcdclk_hwreset_is_asserted), DEVMETHOD_END }; static driver_t aw_lcdclk_driver = { "aw_lcdclk", aw_lcdclk_methods, sizeof(struct aw_lcdclk_softc) }; static devclass_t aw_lcdclk_devclass; EARLY_DRIVER_MODULE(aw_lcdclk, simplebus, aw_lcdclk_driver, aw_lcdclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/arm/allwinner/clk/aw_mmcclk.c =================================================================== --- head/sys/arm/allwinner/clk/aw_mmcclk.c (revision 310177) +++ head/sys/arm/allwinner/clk/aw_mmcclk.c (revision 310178) @@ -1,351 +1,364 @@ /*- * Copyright (c) 2016 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Allwinner MMC clocks */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define SCLK_GATING (1 << 31) #define CLK_SRC_SEL (0x3 << 24) #define CLK_SRC_SEL_SHIFT 24 #define CLK_SRC_SEL_MAX 0x3 #define CLK_SRC_SEL_OSC24M 0 #define CLK_SRC_SEL_PLL6 1 #define CLK_PHASE_CTR (0x7 << 20) #define CLK_PHASE_CTR_SHIFT 20 #define CLK_RATIO_N (0x3 << 16) #define CLK_RATIO_N_SHIFT 16 #define CLK_RATIO_N_MAX 0x3 #define OUTPUT_CLK_PHASE_CTR (0x7 << 8) #define OUTPUT_CLK_PHASE_CTR_SHIFT 8 #define CLK_RATIO_M (0xf << 0) #define CLK_RATIO_M_SHIFT 0 #define CLK_RATIO_M_MAX 0xf static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-mmc-clk", 1 }, { NULL, 0 } }; struct aw_mmcclk_sc { device_t clkdev; bus_addr_t reg; }; +struct phase_clk { + uint64_t freq; + int parent_idx; + uint32_t ophase; + uint32_t phase; + uint32_t n; +}; + +static struct phase_clk aw_mmcclk_phase[] = { + {400000, CLK_SRC_SEL_OSC24M, 0, 0, 2}, + {25000000, CLK_SRC_SEL_PLL6, 0, 5, 2}, + {52000000, CLK_SRC_SEL_PLL6, 3, 5, 0}, +}; + #define MODCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define MODCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_mmcclk_init(struct clknode *clk, device_t dev) { struct aw_mmcclk_sc *sc; uint32_t val, index; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); DEVICE_UNLOCK(sc); index = (val & CLK_SRC_SEL) >> CLK_SRC_SEL_SHIFT; clknode_init_parent_idx(clk, index); return (0); } static int aw_mmcclk_set_mux(struct clknode *clk, int index) { struct aw_mmcclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); if (index < 0 || index > CLK_SRC_SEL_MAX) return (ERANGE); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); val &= ~CLK_SRC_SEL; val |= (index << CLK_SRC_SEL_SHIFT); MODCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_mmcclk_set_gate(struct clknode *clk, bool enable) { struct aw_mmcclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); if (enable) val |= SCLK_GATING; else val &= ~SCLK_GATING; MODCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_mmcclk_recalc_freq(struct clknode *clk, uint64_t *freq) { struct aw_mmcclk_sc *sc; uint32_t val, m, n; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); DEVICE_UNLOCK(sc); n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT); m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1; *freq = *freq / n / m; return (0); } static int aw_mmcclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop) { struct aw_mmcclk_sc *sc; - uint32_t val, m, n, phase, ophase; - int parent_idx, error; + struct clknode *parent_clk; + const char **parent_names; + uint32_t val, m; + int parent_idx, error, phase; sc = clknode_get_softc(clk); /* XXX * The ophase/phase values should be set by the MMC driver, but * there is currently no way to do this with the clk API */ - if (*fout <= 400000) { - parent_idx = CLK_SRC_SEL_OSC24M; - ophase = 0; - phase = 0; - n = 2; - } else if (*fout <= 25000000) { - parent_idx = CLK_SRC_SEL_PLL6; - ophase = 0; - phase = 5; - n = 2; - } else if (*fout <= 52000000) { - parent_idx = CLK_SRC_SEL_PLL6; - ophase = 3; - phase = 5; - n = 0; - } else + for (phase = 0; phase < nitems(aw_mmcclk_phase); phase++) { + if (*fout <= aw_mmcclk_phase[phase].freq) + break; + } + + if (phase == nitems(aw_mmcclk_phase)) return (ERANGE); - /* Switch parent clock, if necessary */ - if (parent_idx != clknode_get_parent_idx(clk)) { - error = clknode_set_parent_by_idx(clk, parent_idx); - if (error != 0) - return (error); + parent_names = clknode_get_parent_names(clk); + parent_idx = aw_mmcclk_phase[phase].parent_idx; + parent_clk = clknode_find_by_name(parent_names[parent_idx]); - /* Fetch new input frequency */ - error = clknode_get_freq(clknode_get_parent(clk), &fin); - if (error != 0) - return (error); - } + if (parent_clk == NULL) + return (ERANGE); - m = ((fin / (1 << n)) / *fout) - 1; + error = clknode_get_freq(parent_clk, &fin); + if (error != 0) + return (error); + m = ((fin / (1 << aw_mmcclk_phase[phase].n)) / *fout) - 1; + + *fout = fin / (1 << aw_mmcclk_phase[phase].n) / (m + 1); + *stop = 1; + + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + + /* Switch to the correct parent if needed */ + error = clknode_set_parent_by_idx(clk, parent_idx); + if (error != 0) + return (error); + DEVICE_LOCK(sc); MODCLK_READ(sc, &val); val &= ~(CLK_RATIO_N | CLK_RATIO_M | CLK_PHASE_CTR | OUTPUT_CLK_PHASE_CTR); - val |= (n << CLK_RATIO_N_SHIFT); + val |= (aw_mmcclk_phase[phase].n << CLK_RATIO_N_SHIFT); val |= (m << CLK_RATIO_M_SHIFT); - val |= (phase << CLK_PHASE_CTR_SHIFT); - val |= (ophase << OUTPUT_CLK_PHASE_CTR_SHIFT); + val |= (aw_mmcclk_phase[phase].phase << CLK_PHASE_CTR_SHIFT); + val |= (aw_mmcclk_phase[phase].ophase << OUTPUT_CLK_PHASE_CTR_SHIFT); MODCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); - - *fout = fin / (1 << n) / (m + 1); - *stop = 1; return (0); } static clknode_method_t aw_mmcclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_mmcclk_init), CLKNODEMETHOD(clknode_set_gate, aw_mmcclk_set_gate), CLKNODEMETHOD(clknode_set_mux, aw_mmcclk_set_mux), CLKNODEMETHOD(clknode_recalc_freq, aw_mmcclk_recalc_freq), CLKNODEMETHOD(clknode_set_freq, aw_mmcclk_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_mmcclk_clknode, aw_mmcclk_clknode_class, aw_mmcclk_clknode_methods, sizeof(struct aw_mmcclk_sc), clknode_class); static int aw_mmcclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner MMC Clock"); return (BUS_PROBE_DEFAULT); } static int aw_mmcclk_attach(device_t dev) { struct clknode_init_def def; struct aw_mmcclk_sc *sc; struct clkdom *clkdom; struct clknode *clk; const char **names; uint32_t *indices; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; int error, nout, ncells, i; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0 || ncells == 0) { device_printf(dev, "couldn't find parent clocks\n"); return (ENXIO); } clkdom = clkdom_create(dev); nout = clk_parse_ofw_out_names(dev, node, &names, &indices); if (nout == 0) { device_printf(dev, "no output clocks found\n"); error = ENXIO; goto fail; } memset(&def, 0, sizeof(def)); def.name = names[0]; def.id = 0; def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", i); goto fail; } def.parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } def.parent_cnt = ncells; def.flags = CLK_NODE_GLITCH_FREE; clk = clknode_create(clkdom, &aw_mmcclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } sc = clknode_get_softc(clk); sc->reg = paddr; sc->clkdev = device_get_parent(dev); clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_mmcclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_mmcclk_probe), DEVMETHOD(device_attach, aw_mmcclk_attach), DEVMETHOD_END }; static driver_t aw_mmcclk_driver = { "aw_mmcclk", aw_mmcclk_methods, 0 }; static devclass_t aw_mmcclk_devclass; EARLY_DRIVER_MODULE(aw_mmcclk, simplebus, aw_mmcclk_driver, aw_mmcclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/arm/allwinner/clk/aw_modclk.c =================================================================== --- head/sys/arm/allwinner/clk/aw_modclk.c (revision 310177) +++ head/sys/arm/allwinner/clk/aw_modclk.c (revision 310178) @@ -1,338 +1,344 @@ /*- * Copyright (c) 2016 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Allwinner module clocks */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define SCLK_GATING (1 << 31) #define CLK_SRC_SEL (0x3 << 24) #define CLK_SRC_SEL_SHIFT 24 #define CLK_RATIO_N (0x3 << 16) #define CLK_RATIO_N_SHIFT 16 #define CLK_RATIO_N_MAX 0x3 #define CLK_RATIO_M (0x1f << 0) #define CLK_RATIO_M_SHIFT 0 #define CLK_RATIO_M_MAX 0x1f static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-mod0-clk", 1 }, { NULL, 0 } }; struct aw_modclk_sc { device_t clkdev; bus_addr_t reg; - u_int parent_cnt; }; #define MODCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define MODCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_modclk_init(struct clknode *clk, device_t dev) { struct aw_modclk_sc *sc; uint32_t val, index; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); DEVICE_UNLOCK(sc); index = (val & CLK_SRC_SEL) >> CLK_SRC_SEL_SHIFT; clknode_init_parent_idx(clk, index); return (0); } static int aw_modclk_set_mux(struct clknode *clk, int index) { struct aw_modclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); - if (index < 0 || index >= sc->parent_cnt) + if (index < 0 || index >= clknode_get_parents_num(clk)) return (ERANGE); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); val &= ~CLK_SRC_SEL; val |= (index << CLK_SRC_SEL_SHIFT); MODCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_modclk_set_gate(struct clknode *clk, bool enable) { struct aw_modclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); if (enable) val |= SCLK_GATING; else val &= ~SCLK_GATING; MODCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_modclk_recalc_freq(struct clknode *clk, uint64_t *freq) { struct aw_modclk_sc *sc; uint32_t val, m, n; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); DEVICE_UNLOCK(sc); n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT); m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1; *freq = *freq / n / m; return (0); } static int aw_modclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop) { struct aw_modclk_sc *sc; + struct clknode *parent_clk, *best_parent; + const char **parent_names; uint32_t val, m, n, src, best_m, best_n, best_src; uint64_t cur_freq; int64_t best_diff, cur_diff; int error; sc = clknode_get_softc(clk); best_n = best_m = 0; best_diff = (int64_t)*fout; best_src = 0; - for (src = 0; src < sc->parent_cnt; src++) { - error = clknode_set_parent_by_idx(clk, src); - if (error != 0) + parent_names = clknode_get_parent_names(clk); + for (src = 0; src < clknode_get_parents_num(clk); src++) { + parent_clk = clknode_find_by_name(parent_names[src]); + if (parent_clk == NULL) continue; - error = clknode_get_freq(clknode_get_parent(clk), &fin); + error = clknode_get_freq(parent_clk, &fin); if (error != 0) continue; for (n = 0; n <= CLK_RATIO_N_MAX; n++) for (m = 0; m <= CLK_RATIO_M_MAX; m++) { cur_freq = fin / (1 << n) / (m + 1); cur_diff = (int64_t)*fout - cur_freq; if (cur_diff >= 0 && cur_diff < best_diff) { best_src = src; + best_parent = parent_clk; best_diff = cur_diff; best_m = m; best_n = n; } } } if (best_diff == (int64_t)*fout) return (ERANGE); - error = clknode_set_parent_by_idx(clk, best_src); + error = clknode_get_freq(best_parent, &fin); if (error != 0) return (error); - error = clknode_get_freq(clknode_get_parent(clk), &fin); + + *fout = fin / (1 << best_n) / (best_m + 1); + *stop = 1; + + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + + error = clknode_set_parent_by_idx(clk, best_src); if (error != 0) return (error); DEVICE_LOCK(sc); MODCLK_READ(sc, &val); val &= ~(CLK_RATIO_N | CLK_RATIO_M); val |= (best_n << CLK_RATIO_N_SHIFT); val |= (best_m << CLK_RATIO_M_SHIFT); MODCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); - *fout = fin / (1 << best_n) / (best_m + 1); - *stop = 1; - return (0); } static clknode_method_t aw_modclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_modclk_init), CLKNODEMETHOD(clknode_set_gate, aw_modclk_set_gate), CLKNODEMETHOD(clknode_set_mux, aw_modclk_set_mux), CLKNODEMETHOD(clknode_recalc_freq, aw_modclk_recalc_freq), CLKNODEMETHOD(clknode_set_freq, aw_modclk_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_modclk_clknode, aw_modclk_clknode_class, aw_modclk_clknode_methods, sizeof(struct aw_modclk_sc), clknode_class); static int aw_modclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner Module Clock"); return (BUS_PROBE_DEFAULT); } static int aw_modclk_attach(device_t dev) { struct clknode_init_def def; struct aw_modclk_sc *sc; struct clkdom *clkdom; struct clknode *clk; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; int error, ncells, i; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0) { device_printf(dev, "cannot get clock count\n"); return (error); } clkdom = clkdom_create(dev); memset(&def, 0, sizeof(def)); error = clk_parse_ofw_clk_name(dev, node, &def.name); if (error != 0) { device_printf(dev, "cannot parse clock name\n"); error = ENXIO; goto fail; } def.id = 1; def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", i); goto fail; } def.parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } def.parent_cnt = ncells; clk = clknode_create(clkdom, &aw_modclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } sc = clknode_get_softc(clk); sc->reg = paddr; sc->clkdev = device_get_parent(dev); - sc->parent_cnt = def.parent_cnt; clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_modclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_modclk_probe), DEVMETHOD(device_attach, aw_modclk_attach), DEVMETHOD_END }; static driver_t aw_modclk_driver = { "aw_modclk", aw_modclk_methods, 0 }; static devclass_t aw_modclk_devclass; EARLY_DRIVER_MODULE(aw_modclk, simplebus, aw_modclk_driver, aw_modclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/arm/allwinner/clk/aw_pll.c =================================================================== --- head/sys/arm/allwinner/clk/aw_pll.c (revision 310177) +++ head/sys/arm/allwinner/clk/aw_pll.c (revision 310178) @@ -1,1325 +1,1349 @@ /*- * Copyright (c) 2016 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Allwinner PLL clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define SUN4I_A10_PLL2_1X 0 #define SUN4I_A10_PLL2_2X 1 #define SUN4I_A10_PLL2_4X 2 #define SUN4I_A10_PLL2_8X 3 #define AW_PLL_ENABLE (1 << 31) #define A10_PLL1_OUT_EXT_DIVP (0x3 << 16) #define A10_PLL1_OUT_EXT_DIVP_SHIFT 16 #define A10_PLL1_FACTOR_N (0x1f << 8) #define A10_PLL1_FACTOR_N_SHIFT 8 #define A10_PLL1_FACTOR_K (0x3 << 4) #define A10_PLL1_FACTOR_K_SHIFT 4 #define A10_PLL1_FACTOR_M (0x3 << 0) #define A10_PLL1_FACTOR_M_SHIFT 0 #define A10_PLL2_POST_DIV (0xf << 26) #define A10_PLL2_POST_DIV_SHIFT 26 #define A10_PLL2_FACTOR_N (0x7f << 8) #define A10_PLL2_FACTOR_N_SHIFT 8 #define A10_PLL2_PRE_DIV (0x1f << 0) #define A10_PLL2_PRE_DIV_SHIFT 0 #define A10_PLL3_MODE_SEL (0x1 << 15) #define A10_PLL3_MODE_SEL_FRACT (0 << 15) #define A10_PLL3_MODE_SEL_INT (1 << 15) #define A10_PLL3_FUNC_SET (0x1 << 14) #define A10_PLL3_FUNC_SET_270MHZ (0 << 14) #define A10_PLL3_FUNC_SET_297MHZ (1 << 14) #define A10_PLL3_FACTOR_M (0x7f << 0) #define A10_PLL3_FACTOR_M_SHIFT 0 #define A10_PLL3_REF_FREQ 3000000 #define A10_PLL5_OUT_EXT_DIVP (0x3 << 16) #define A10_PLL5_OUT_EXT_DIVP_SHIFT 16 #define A10_PLL5_FACTOR_N (0x1f << 8) #define A10_PLL5_FACTOR_N_SHIFT 8 #define A10_PLL5_FACTOR_K (0x3 << 4) #define A10_PLL5_FACTOR_K_SHIFT 4 #define A10_PLL5_FACTOR_M1 (0x3 << 2) #define A10_PLL5_FACTOR_M1_SHIFT 2 #define A10_PLL5_FACTOR_M (0x3 << 0) #define A10_PLL5_FACTOR_M_SHIFT 0 #define A10_PLL6_BYPASS_EN (1 << 30) #define A10_PLL6_SATA_CLK_EN (1 << 14) #define A10_PLL6_FACTOR_N (0x1f << 8) #define A10_PLL6_FACTOR_N_SHIFT 8 #define A10_PLL6_FACTOR_K (0x3 << 4) #define A10_PLL6_FACTOR_K_SHIFT 4 #define A10_PLL6_FACTOR_M (0x3 << 0) #define A10_PLL6_FACTOR_M_SHIFT 0 #define A10_PLL2_POST_DIV (0xf << 26) #define A13_PLL2_POST_DIV (0xf << 26) #define A13_PLL2_POST_DIV_SHIFT 26 #define A13_PLL2_FACTOR_N (0x7f << 8) #define A13_PLL2_FACTOR_N_SHIFT 8 #define A13_PLL2_PRE_DIV (0x1f << 0) #define A13_PLL2_PRE_DIV_SHIFT 0 #define A23_PLL1_FACTOR_P (0x3 << 16) #define A23_PLL1_FACTOR_P_SHIFT 16 #define A23_PLL1_FACTOR_N (0x1f << 8) #define A23_PLL1_FACTOR_N_SHIFT 8 #define A23_PLL1_FACTOR_K (0x3 << 4) #define A23_PLL1_FACTOR_K_SHIFT 4 #define A23_PLL1_FACTOR_M (0x3 << 0) #define A23_PLL1_FACTOR_M_SHIFT 0 #define A31_PLL1_LOCK (1 << 28) #define A31_PLL1_CPU_SIGMA_DELTA_EN (1 << 24) #define A31_PLL1_FACTOR_N (0x1f << 8) #define A31_PLL1_FACTOR_N_SHIFT 8 #define A31_PLL1_FACTOR_K (0x3 << 4) #define A31_PLL1_FACTOR_K_SHIFT 4 #define A31_PLL1_FACTOR_M (0x3 << 0) #define A31_PLL1_FACTOR_M_SHIFT 0 #define A31_PLL6_LOCK (1 << 28) #define A31_PLL6_BYPASS_EN (1 << 25) #define A31_PLL6_CLK_OUT_EN (1 << 24) #define A31_PLL6_24M_OUT_EN (1 << 18) #define A31_PLL6_24M_POST_DIV (0x3 << 16) #define A31_PLL6_24M_POST_DIV_SHIFT 16 #define A31_PLL6_FACTOR_N (0x1f << 8) #define A31_PLL6_FACTOR_N_SHIFT 8 #define A31_PLL6_FACTOR_K (0x3 << 4) #define A31_PLL6_FACTOR_K_SHIFT 4 #define A31_PLL6_DEFAULT_N 0x18 #define A31_PLL6_DEFAULT_K 0x1 #define A31_PLL6_TIMEOUT 10 #define A64_PLLHSIC_LOCK (1 << 28) #define A64_PLLHSIC_FRAC_CLK_OUT (1 << 25) #define A64_PLLHSIC_PLL_MODE_SEL (1 << 24) #define A64_PLLHSIC_PLL_SDM_EN (1 << 20) #define A64_PLLHSIC_FACTOR_N (0x7f << 8) #define A64_PLLHSIC_FACTOR_N_SHIFT 8 #define A64_PLLHSIC_PRE_DIV_M (0xf << 0) #define A64_PLLHSIC_PRE_DIV_M_SHIFT 0 #define A80_PLL4_CLK_OUT_EN (1 << 20) #define A80_PLL4_PLL_DIV2 (1 << 18) #define A80_PLL4_PLL_DIV1 (1 << 16) #define A80_PLL4_FACTOR_N (0xff << 8) #define A80_PLL4_FACTOR_N_SHIFT 8 #define A83T_PLLCPUX_LOCK_TIME (0x7 << 24) #define A83T_PLLCPUX_LOCK_TIME_SHIFT 24 #define A83T_PLLCPUX_CLOCK_OUTPUT_DIS (1 << 20) #define A83T_PLLCPUX_OUT_EXT_DIVP (1 << 16) #define A83T_PLLCPUX_FACTOR_N (0xff << 8) #define A83T_PLLCPUX_FACTOR_N_SHIFT 8 #define A83T_PLLCPUX_FACTOR_N_MIN 12 #define A83T_PLLCPUX_FACTOR_N_MAX 125 #define A83T_PLLCPUX_POSTDIV_M (0x3 << 0) #define A83T_PLLCPUX_POSTDIV_M_SHIFT 0 #define H3_PLL2_LOCK (1 << 28) #define H3_PLL2_SDM_EN (1 << 24) #define H3_PLL2_POST_DIV (0xf << 16) #define H3_PLL2_POST_DIV_SHIFT 16 #define H3_PLL2_FACTOR_N (0x7f << 8) #define H3_PLL2_FACTOR_N_SHIFT 8 #define H3_PLL2_PRE_DIV (0x1f << 0) #define H3_PLL2_PRE_DIV_SHIFT 0 #define CLKID_A10_PLL5_DDR 0 #define CLKID_A10_PLL5_OTHER 1 #define CLKID_A10_PLL6_SATA 0 #define CLKID_A10_PLL6_OTHER 1 #define CLKID_A10_PLL6 2 #define CLKID_A10_PLL6_DIV_4 3 #define CLKID_A31_PLL6 0 #define CLKID_A31_PLL6_X2 1 struct aw_pll_factor { unsigned int n; unsigned int k; unsigned int m; unsigned int p; uint64_t freq; }; #define PLLFACTOR(_n, _k, _m, _p, _freq) \ { .n = (_n), .k = (_k), .m = (_m), .p = (_p), .freq = (_freq) } static struct aw_pll_factor aw_a10_pll1_factors[] = { PLLFACTOR(6, 0, 0, 0, 144000000), PLLFACTOR(12, 0, 0, 0, 312000000), PLLFACTOR(21, 0, 0, 0, 528000000), PLLFACTOR(29, 0, 0, 0, 720000000), PLLFACTOR(18, 1, 0, 0, 864000000), PLLFACTOR(19, 1, 0, 0, 912000000), PLLFACTOR(20, 1, 0, 0, 960000000), }; static struct aw_pll_factor aw_a23_pll1_factors[] = { PLLFACTOR(9, 0, 0, 2, 60000000), PLLFACTOR(10, 0, 0, 2, 66000000), PLLFACTOR(11, 0, 0, 2, 72000000), PLLFACTOR(12, 0, 0, 2, 78000000), PLLFACTOR(13, 0, 0, 2, 84000000), PLLFACTOR(14, 0, 0, 2, 90000000), PLLFACTOR(15, 0, 0, 2, 96000000), PLLFACTOR(16, 0, 0, 2, 102000000), PLLFACTOR(17, 0, 0, 2, 108000000), PLLFACTOR(18, 0, 0, 2, 114000000), PLLFACTOR(9, 0, 0, 1, 120000000), PLLFACTOR(10, 0, 0, 1, 132000000), PLLFACTOR(11, 0, 0, 1, 144000000), PLLFACTOR(12, 0, 0, 1, 156000000), PLLFACTOR(13, 0, 0, 1, 168000000), PLLFACTOR(14, 0, 0, 1, 180000000), PLLFACTOR(15, 0, 0, 1, 192000000), PLLFACTOR(16, 0, 0, 1, 204000000), PLLFACTOR(17, 0, 0, 1, 216000000), PLLFACTOR(18, 0, 0, 1, 228000000), PLLFACTOR(9, 0, 0, 0, 240000000), PLLFACTOR(10, 0, 0, 0, 264000000), PLLFACTOR(11, 0, 0, 0, 288000000), PLLFACTOR(12, 0, 0, 0, 312000000), PLLFACTOR(13, 0, 0, 0, 336000000), PLLFACTOR(14, 0, 0, 0, 360000000), PLLFACTOR(15, 0, 0, 0, 384000000), PLLFACTOR(16, 0, 0, 0, 408000000), PLLFACTOR(17, 0, 0, 0, 432000000), PLLFACTOR(18, 0, 0, 0, 456000000), PLLFACTOR(19, 0, 0, 0, 480000000), PLLFACTOR(20, 0, 0, 0, 504000000), PLLFACTOR(21, 0, 0, 0, 528000000), PLLFACTOR(22, 0, 0, 0, 552000000), PLLFACTOR(23, 0, 0, 0, 576000000), PLLFACTOR(24, 0, 0, 0, 600000000), PLLFACTOR(25, 0, 0, 0, 624000000), PLLFACTOR(26, 0, 0, 0, 648000000), PLLFACTOR(27, 0, 0, 0, 672000000), PLLFACTOR(28, 0, 0, 0, 696000000), PLLFACTOR(29, 0, 0, 0, 720000000), PLLFACTOR(15, 1, 0, 0, 768000000), PLLFACTOR(10, 2, 0, 0, 792000000), PLLFACTOR(16, 1, 0, 0, 816000000), PLLFACTOR(17, 1, 0, 0, 864000000), PLLFACTOR(18, 1, 0, 0, 912000000), PLLFACTOR(12, 2, 0, 0, 936000000), PLLFACTOR(19, 1, 0, 0, 960000000), PLLFACTOR(20, 1, 0, 0, 1008000000), PLLFACTOR(21, 1, 0, 0, 1056000000), PLLFACTOR(14, 2, 0, 0, 1080000000), PLLFACTOR(22, 1, 0, 0, 1104000000), PLLFACTOR(23, 1, 0, 0, 1152000000), PLLFACTOR(24, 1, 0, 0, 1200000000), PLLFACTOR(16, 2, 0, 0, 1224000000), PLLFACTOR(25, 1, 0, 0, 1248000000), PLLFACTOR(26, 1, 0, 0, 1296000000), PLLFACTOR(27, 1, 0, 0, 1344000000), PLLFACTOR(18, 2, 0, 0, 1368000000), PLLFACTOR(28, 1, 0, 0, 1392000000), PLLFACTOR(29, 1, 0, 0, 1440000000), PLLFACTOR(20, 2, 0, 0, 1512000000), PLLFACTOR(15, 3, 0, 0, 1536000000), PLLFACTOR(21, 2, 0, 0, 1584000000), PLLFACTOR(16, 3, 0, 0, 1632000000), PLLFACTOR(22, 2, 0, 0, 1656000000), PLLFACTOR(23, 2, 0, 0, 1728000000), PLLFACTOR(24, 2, 0, 0, 1800000000), PLLFACTOR(18, 3, 0, 0, 1824000000), PLLFACTOR(25, 2, 0, 0, 1872000000), }; static struct aw_pll_factor aw_h3_pll2_factors[] = { PLLFACTOR(13, 0, 0, 13, 24576000), PLLFACTOR(6, 0, 0, 7, 22579200), }; enum aw_pll_type { AWPLL_A10_PLL1 = 1, AWPLL_A10_PLL2, AWPLL_A10_PLL3, AWPLL_A10_PLL5, AWPLL_A10_PLL6, AWPLL_A13_PLL2, AWPLL_A23_PLL1, AWPLL_A31_PLL1, AWPLL_A31_PLL6, AWPLL_A64_PLLHSIC, AWPLL_A80_PLL4, AWPLL_A83T_PLLCPUX, AWPLL_H3_PLL1, AWPLL_H3_PLL2, }; struct aw_pll_sc { enum aw_pll_type type; device_t clkdev; bus_addr_t reg; int id; }; struct aw_pll_funcs { int (*recalc)(struct aw_pll_sc *, uint64_t *); int (*set_freq)(struct aw_pll_sc *, uint64_t, uint64_t *, int); int (*init)(device_t, bus_addr_t, struct clknode_init_def *); }; #define PLL_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define PLL_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int a10_pll1_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { struct aw_pll_factor *f; uint32_t val; int n; f = NULL; for (n = 0; n < nitems(aw_a10_pll1_factors); n++) { if (aw_a10_pll1_factors[n].freq == *fout) { f = &aw_a10_pll1_factors[n]; break; } } if (f == NULL) return (EINVAL); + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~(A10_PLL1_FACTOR_N|A10_PLL1_FACTOR_K|A10_PLL1_FACTOR_M| A10_PLL1_OUT_EXT_DIVP); val |= (f->p << A10_PLL1_OUT_EXT_DIVP_SHIFT); val |= (f->n << A10_PLL1_FACTOR_N_SHIFT); val |= (f->k << A10_PLL1_FACTOR_K_SHIFT); val |= (f->m << A10_PLL1_FACTOR_M_SHIFT); PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int a10_pll1_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, n, k, p; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); p = 1 << ((val & A10_PLL1_OUT_EXT_DIVP) >> A10_PLL1_OUT_EXT_DIVP_SHIFT); m = ((val & A10_PLL1_FACTOR_M) >> A10_PLL1_FACTOR_M_SHIFT) + 1; k = ((val & A10_PLL1_FACTOR_K) >> A10_PLL1_FACTOR_K_SHIFT) + 1; n = (val & A10_PLL1_FACTOR_N) >> A10_PLL1_FACTOR_N_SHIFT; if (n == 0) n = 1; *freq = (*freq * n * k) / (m * p); return (0); } static int a10_pll2_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, post_div, n, pre_div; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); post_div = (val & A10_PLL2_POST_DIV) >> A10_PLL2_POST_DIV_SHIFT; if (post_div == 0) post_div = 1; n = (val & A10_PLL2_FACTOR_N) >> A10_PLL2_FACTOR_N_SHIFT; if (n == 0) n = 1; pre_div = (val & A10_PLL2_PRE_DIV) >> A10_PLL2_PRE_DIV_SHIFT; if (pre_div == 0) pre_div = 1; switch (sc->id) { case SUN4I_A10_PLL2_1X: *freq = (*freq * 2 * n) / pre_div / post_div / 2; break; case SUN4I_A10_PLL2_2X: *freq = (*freq * 2 * n) / pre_div / 4; break; case SUN4I_A10_PLL2_4X: *freq = (*freq * 2 * n) / pre_div / 2; break; case SUN4I_A10_PLL2_8X: *freq = (*freq * 2 * n) / pre_div; break; default: return (EINVAL); } return (0); } static int a10_pll2_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t val, post_div, n, pre_div; if (sc->id != SUN4I_A10_PLL2_1X) return (ENXIO); /* * Audio Codec needs PLL2-1X to be either 24576000 or 22579200. * * PLL2-1X output frequency is (48MHz * n) / pre_div / post_div / 2. * To get as close as possible to the desired rate, we use a * pre-divider of 21 and a post-divider of 4. With these values, * a multiplier of 86 or 79 gets us close to the target rates. */ if (*fout != 24576000 && *fout != 22579200) return (EINVAL); pre_div = 21; post_div = 4; n = (*fout * pre_div * post_div * 2) / (2 * fin); + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~(A10_PLL2_POST_DIV | A10_PLL2_FACTOR_N | A10_PLL2_PRE_DIV); val |= (post_div << A10_PLL2_POST_DIV_SHIFT); val |= (n << A10_PLL2_FACTOR_N_SHIFT); val |= (pre_div << A10_PLL2_PRE_DIV_SHIFT); PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int a10_pll3_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); if ((val & A10_PLL3_MODE_SEL) == A10_PLL3_MODE_SEL_INT) { /* In integer mode, output is 3MHz * m */ m = (val & A10_PLL3_FACTOR_M) >> A10_PLL3_FACTOR_M_SHIFT; *freq = A10_PLL3_REF_FREQ * m; } else { /* In fractional mode, output is either 270MHz or 297MHz */ if ((val & A10_PLL3_FUNC_SET) == A10_PLL3_FUNC_SET_270MHZ) *freq = 270000000; else *freq = 297000000; } return (0); } static int a10_pll3_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t val, m, mode, func; if (*fout == 297000000) { func = A10_PLL3_FUNC_SET_297MHZ; mode = A10_PLL3_MODE_SEL_FRACT; m = 0; } else if (*fout == 270000000) { func = A10_PLL3_FUNC_SET_270MHZ; mode = A10_PLL3_MODE_SEL_FRACT; m = 0; } else { mode = A10_PLL3_MODE_SEL_INT; func = 0; m = *fout / A10_PLL3_REF_FREQ; *fout = m * A10_PLL3_REF_FREQ; } + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~(A10_PLL3_MODE_SEL | A10_PLL3_FUNC_SET | A10_PLL3_FACTOR_M); val |= mode; val |= func; val |= (m << A10_PLL3_FACTOR_M_SHIFT); PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int a10_pll3_init(device_t dev, bus_addr_t reg, struct clknode_init_def *def) { uint32_t val; /* Allow changing PLL frequency while enabled */ def->flags = CLK_NODE_GLITCH_FREE; /* Set PLL to 297MHz */ CLKDEV_DEVICE_LOCK(dev); CLKDEV_READ_4(dev, reg, &val); val &= ~(A10_PLL3_MODE_SEL | A10_PLL3_FUNC_SET | A10_PLL3_FACTOR_M); val |= A10_PLL3_MODE_SEL_FRACT; val |= A10_PLL3_FUNC_SET_297MHZ; CLKDEV_WRITE_4(dev, reg, val); CLKDEV_DEVICE_UNLOCK(dev); return (0); } static int a10_pll5_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, n, k, p; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); p = 1 << ((val & A10_PLL5_OUT_EXT_DIVP) >> A10_PLL5_OUT_EXT_DIVP_SHIFT); m = ((val & A10_PLL5_FACTOR_M) >> A10_PLL5_FACTOR_M_SHIFT) + 1; k = ((val & A10_PLL5_FACTOR_K) >> A10_PLL5_FACTOR_K_SHIFT) + 1; n = (val & A10_PLL5_FACTOR_N) >> A10_PLL5_FACTOR_N_SHIFT; if (n == 0) return (ENXIO); switch (sc->id) { case CLKID_A10_PLL5_DDR: *freq = (*freq * n * k) / m; break; case CLKID_A10_PLL5_OTHER: *freq = (*freq * n * k) / p; break; default: return (ENXIO); } return (0); } static int a10_pll6_init(device_t dev, bus_addr_t reg, struct clknode_init_def *def) { uint32_t val, m, n, k; /* * SATA needs PLL6 to be a 100MHz clock. * * The SATA output frequency is (24MHz * n * k) / m / 6. * To get to 100MHz, k & m must be equal and n must be 25. */ m = k = 0; n = 25; CLKDEV_DEVICE_LOCK(dev); CLKDEV_READ_4(dev, reg, &val); val &= ~(A10_PLL6_FACTOR_N | A10_PLL6_FACTOR_K | A10_PLL6_FACTOR_M); val &= ~A10_PLL6_BYPASS_EN; val |= A10_PLL6_SATA_CLK_EN; val |= (n << A10_PLL6_FACTOR_N_SHIFT); val |= (k << A10_PLL6_FACTOR_K_SHIFT); val |= (m << A10_PLL6_FACTOR_M_SHIFT); CLKDEV_WRITE_4(dev, reg, val); CLKDEV_DEVICE_UNLOCK(dev); return (0); } static int a10_pll6_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, k, n; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); m = ((val & A10_PLL6_FACTOR_M) >> A10_PLL6_FACTOR_M_SHIFT) + 1; k = ((val & A10_PLL6_FACTOR_K) >> A10_PLL6_FACTOR_K_SHIFT) + 1; n = (val & A10_PLL6_FACTOR_N) >> A10_PLL6_FACTOR_N_SHIFT; if (n == 0) return (ENXIO); switch (sc->id) { case CLKID_A10_PLL6_SATA: *freq = (*freq * n * k) / m / 6; break; case CLKID_A10_PLL6_OTHER: *freq = (*freq * n * k) / 2; break; case CLKID_A10_PLL6: *freq = (*freq * n * k); break; case CLKID_A10_PLL6_DIV_4: *freq = (*freq * n * k) / 4; break; default: return (ENXIO); } return (0); } static int a10_pll6_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { if (sc->id != CLKID_A10_PLL6_SATA) return (ENXIO); /* PLL6 SATA output has been set to 100MHz in a10_pll6_init */ if (*fout != 100000000) return (ERANGE); return (0); } static int a13_pll2_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, post_div, n, pre_div; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); post_div = ((val & A13_PLL2_POST_DIV) >> A13_PLL2_POST_DIV_SHIFT) + 1; if (post_div == 0) post_div = 1; n = (val & A13_PLL2_FACTOR_N) >> A13_PLL2_FACTOR_N_SHIFT; if (n == 0) n = 1; pre_div = ((val & A13_PLL2_PRE_DIV) >> A13_PLL2_PRE_DIV_SHIFT) + 1; if (pre_div == 0) pre_div = 1; switch (sc->id) { case SUN4I_A10_PLL2_1X: *freq = (*freq * 2 * n) / pre_div / post_div / 2; break; case SUN4I_A10_PLL2_2X: *freq = (*freq * 2 * n) / pre_div / 4; break; case SUN4I_A10_PLL2_4X: *freq = (*freq * 2 * n) / pre_div / 2; break; case SUN4I_A10_PLL2_8X: *freq = (*freq * 2 * n) / pre_div; break; default: return (EINVAL); } return (0); } static int a13_pll2_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t val, post_div, n, pre_div; if (sc->id != SUN4I_A10_PLL2_1X) return (ENXIO); /* * Audio Codec needs PLL2-1X to be either 24576000 or 22579200. * * PLL2-1X output frequency is (48MHz * n) / pre_div / post_div / 2. * To get as close as possible to the desired rate, we use a * pre-divider of 21 and a post-divider of 4. With these values, * a multiplier of 86 or 79 gets us close to the target rates. */ if (*fout != 24576000 && *fout != 22579200) return (EINVAL); pre_div = 21; post_div = 4; n = (*fout * pre_div * post_div * 2) / (2 * fin); + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~(A13_PLL2_POST_DIV | A13_PLL2_FACTOR_N | A13_PLL2_PRE_DIV); val |= ((post_div - 1) << A13_PLL2_POST_DIV_SHIFT); val |= (n << A13_PLL2_FACTOR_N_SHIFT); val |= ((pre_div - 1) << A13_PLL2_PRE_DIV_SHIFT); PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int h3_pll2_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, p, n, m; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); p = ((val & H3_PLL2_POST_DIV) >> H3_PLL2_POST_DIV_SHIFT) + 1; n = ((val & H3_PLL2_FACTOR_N) >> H3_PLL2_FACTOR_N_SHIFT) + 1; m = ((val & H3_PLL2_PRE_DIV) >> H3_PLL2_PRE_DIV_SHIFT) + 1; switch (sc->id) { case SUN4I_A10_PLL2_1X: *freq = (*freq * n) / (m * p); break; case SUN4I_A10_PLL2_2X: *freq = (*freq * 2 * n) / m / 4; break; case SUN4I_A10_PLL2_4X: *freq = (*freq * 2 * n) / m / 2; break; case SUN4I_A10_PLL2_8X: *freq = (*freq * 2 * n) / m; break; default: return (EINVAL); } return (0); } static int h3_pll2_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { struct aw_pll_factor *f; uint32_t val; int n, error, retry; if (sc->id != SUN4I_A10_PLL2_1X) return (ENXIO); f = NULL; for (n = 0; n < nitems(aw_h3_pll2_factors); n++) { if (aw_h3_pll2_factors[n].freq == *fout) { f = &aw_h3_pll2_factors[n]; break; } } if (f == NULL) return (EINVAL); + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~(H3_PLL2_POST_DIV|H3_PLL2_FACTOR_N|H3_PLL2_PRE_DIV); val |= (f->p << H3_PLL2_POST_DIV_SHIFT); val |= (f->n << H3_PLL2_FACTOR_N_SHIFT); val |= (f->m << H3_PLL2_PRE_DIV_SHIFT); val |= AW_PLL_ENABLE; PLL_WRITE(sc, val); /* Wait for lock */ error = 0; for (retry = 0; retry < 1000; retry++) { PLL_READ(sc, &val); if ((val & H3_PLL2_LOCK) != 0) break; DELAY(100); } if (retry == 0) error = ETIMEDOUT; DEVICE_UNLOCK(sc); return (error); } static int a23_pll1_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { struct aw_pll_factor *f; uint32_t val; int n; f = NULL; for (n = 0; n < nitems(aw_a23_pll1_factors); n++) { if (aw_a23_pll1_factors[n].freq == *fout) { f = &aw_a23_pll1_factors[n]; break; } } if (f == NULL) return (EINVAL); + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~(A23_PLL1_FACTOR_N|A23_PLL1_FACTOR_K|A23_PLL1_FACTOR_M| A23_PLL1_FACTOR_P); val |= (f->n << A23_PLL1_FACTOR_N_SHIFT); val |= (f->k << A23_PLL1_FACTOR_K_SHIFT); val |= (f->m << A23_PLL1_FACTOR_M_SHIFT); val |= (f->p << A23_PLL1_FACTOR_P_SHIFT); PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int a23_pll1_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, n, k, p; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); m = ((val & A23_PLL1_FACTOR_M) >> A23_PLL1_FACTOR_M_SHIFT) + 1; k = ((val & A23_PLL1_FACTOR_K) >> A23_PLL1_FACTOR_K_SHIFT) + 1; n = ((val & A23_PLL1_FACTOR_N) >> A23_PLL1_FACTOR_N_SHIFT) + 1; p = ((val & A23_PLL1_FACTOR_P) >> A23_PLL1_FACTOR_P_SHIFT) + 1; *freq = (*freq * n * k) / (m * p); return (0); } static int h3_pll1_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { struct aw_pll_factor *f; uint32_t val, n, k, m, p; int i; f = NULL; for (i = 0; i < nitems(aw_a23_pll1_factors); i++) { if (aw_a23_pll1_factors[i].freq == *fout) { f = &aw_a23_pll1_factors[i]; break; } } if (f == NULL) return (EINVAL); + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); PLL_READ(sc, &val); n = (val & A23_PLL1_FACTOR_N) >> A23_PLL1_FACTOR_N_SHIFT; k = (val & A23_PLL1_FACTOR_K) >> A23_PLL1_FACTOR_K_SHIFT; m = (val & A23_PLL1_FACTOR_M) >> A23_PLL1_FACTOR_M_SHIFT; p = (val & A23_PLL1_FACTOR_P) >> A23_PLL1_FACTOR_P_SHIFT; if (p < f->p) { val &= ~A23_PLL1_FACTOR_P; val |= (f->p << A23_PLL1_FACTOR_P_SHIFT); PLL_WRITE(sc, val); DELAY(2000); } if (m < f->m) { val &= ~A23_PLL1_FACTOR_M; val |= (f->m << A23_PLL1_FACTOR_M_SHIFT); PLL_WRITE(sc, val); DELAY(2000); } val &= ~(A23_PLL1_FACTOR_N|A23_PLL1_FACTOR_K); val |= (f->n << A23_PLL1_FACTOR_N_SHIFT); val |= (f->k << A23_PLL1_FACTOR_K_SHIFT); PLL_WRITE(sc, val); DELAY(2000); if (m > f->m) { val &= ~A23_PLL1_FACTOR_M; val |= (f->m << A23_PLL1_FACTOR_M_SHIFT); PLL_WRITE(sc, val); DELAY(2000); } if (p > f->p) { val &= ~A23_PLL1_FACTOR_P; val |= (f->p << A23_PLL1_FACTOR_P_SHIFT); PLL_WRITE(sc, val); DELAY(2000); } DEVICE_UNLOCK(sc); return (0); } static int a31_pll1_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, m, n, k; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); m = ((val & A31_PLL1_FACTOR_M) >> A31_PLL1_FACTOR_M_SHIFT) + 1; k = ((val & A31_PLL1_FACTOR_K) >> A31_PLL1_FACTOR_K_SHIFT) + 1; n = ((val & A31_PLL1_FACTOR_N) >> A31_PLL1_FACTOR_N_SHIFT) + 1; *freq = (*freq * n * k) / m; return (0); } static int a31_pll6_init(device_t dev, bus_addr_t reg, struct clknode_init_def *def) { uint32_t val; int retry; if (def->id != CLKID_A31_PLL6) return (0); /* * The datasheet recommends that PLL6 output should be fixed to * 600MHz. */ CLKDEV_DEVICE_LOCK(dev); CLKDEV_READ_4(dev, reg, &val); val &= ~(A31_PLL6_FACTOR_N | A31_PLL6_FACTOR_K | A31_PLL6_BYPASS_EN); val |= (A31_PLL6_DEFAULT_N << A31_PLL6_FACTOR_N_SHIFT); val |= (A31_PLL6_DEFAULT_K << A31_PLL6_FACTOR_K_SHIFT); val |= AW_PLL_ENABLE; CLKDEV_WRITE_4(dev, reg, val); /* Wait for PLL to become stable */ for (retry = A31_PLL6_TIMEOUT; retry > 0; retry--) { CLKDEV_READ_4(dev, reg, &val); if ((val & A31_PLL6_LOCK) == A31_PLL6_LOCK) break; DELAY(1); } CLKDEV_DEVICE_UNLOCK(dev); return (0); } static int a31_pll6_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, k, n; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); k = ((val & A10_PLL6_FACTOR_K) >> A10_PLL6_FACTOR_K_SHIFT) + 1; n = ((val & A10_PLL6_FACTOR_N) >> A10_PLL6_FACTOR_N_SHIFT) + 1; switch (sc->id) { case CLKID_A31_PLL6: *freq = (*freq * n * k) / 2; break; case CLKID_A31_PLL6_X2: *freq = *freq * n * k; break; default: return (ENXIO); } return (0); } static int a80_pll4_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, n, div1, div2; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); n = (val & A80_PLL4_FACTOR_N) >> A80_PLL4_FACTOR_N_SHIFT; div1 = (val & A80_PLL4_PLL_DIV1) == 0 ? 1 : 2; div2 = (val & A80_PLL4_PLL_DIV2) == 0 ? 1 : 2; *freq = (*freq * n) / div1 / div2; return (0); } static int a64_pllhsic_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, n, m; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); n = ((val & A64_PLLHSIC_FACTOR_N) >> A64_PLLHSIC_FACTOR_N_SHIFT) + 1; m = ((val & A64_PLLHSIC_PRE_DIV_M) >> A64_PLLHSIC_PRE_DIV_M_SHIFT) + 1; *freq = (*freq * n) / m; return (0); } static int a64_pllhsic_init(device_t dev, bus_addr_t reg, struct clknode_init_def *def) { uint32_t val; /* * PLL_HSIC default is 480MHz, just enable it. */ CLKDEV_DEVICE_LOCK(dev); CLKDEV_READ_4(dev, reg, &val); val |= AW_PLL_ENABLE; CLKDEV_WRITE_4(dev, reg, val); CLKDEV_DEVICE_UNLOCK(dev); return (0); } static int a83t_pllcpux_recalc(struct aw_pll_sc *sc, uint64_t *freq) { uint32_t val, n, p; DEVICE_LOCK(sc); PLL_READ(sc, &val); DEVICE_UNLOCK(sc); n = (val & A83T_PLLCPUX_FACTOR_N) >> A83T_PLLCPUX_FACTOR_N_SHIFT; p = (val & A83T_PLLCPUX_OUT_EXT_DIVP) ? 4 : 1; *freq = (*freq * n) / p; return (0); } static int a83t_pllcpux_set_freq(struct aw_pll_sc *sc, uint64_t fin, uint64_t *fout, int flags) { uint32_t val; u_int n; n = *fout / fin; if (n < A83T_PLLCPUX_FACTOR_N_MIN || n > A83T_PLLCPUX_FACTOR_N_MAX) return (EINVAL); + + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); DEVICE_LOCK(sc); PLL_READ(sc, &val); val &= ~A83T_PLLCPUX_FACTOR_N; val |= (n << A83T_PLLCPUX_FACTOR_N_SHIFT); val &= ~A83T_PLLCPUX_CLOCK_OUTPUT_DIS; PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } #define PLL(_type, _recalc, _set_freq, _init) \ [(_type)] = { \ .recalc = (_recalc), \ .set_freq = (_set_freq), \ .init = (_init) \ } static struct aw_pll_funcs aw_pll_func[] = { PLL(AWPLL_A10_PLL1, a10_pll1_recalc, a10_pll1_set_freq, NULL), PLL(AWPLL_A10_PLL2, a10_pll2_recalc, a10_pll2_set_freq, NULL), PLL(AWPLL_A10_PLL3, a10_pll3_recalc, a10_pll3_set_freq, a10_pll3_init), PLL(AWPLL_A10_PLL5, a10_pll5_recalc, NULL, NULL), PLL(AWPLL_A10_PLL6, a10_pll6_recalc, a10_pll6_set_freq, a10_pll6_init), PLL(AWPLL_A13_PLL2, a13_pll2_recalc, a13_pll2_set_freq, NULL), PLL(AWPLL_A23_PLL1, a23_pll1_recalc, a23_pll1_set_freq, NULL), PLL(AWPLL_A31_PLL1, a31_pll1_recalc, NULL, NULL), PLL(AWPLL_A31_PLL6, a31_pll6_recalc, NULL, a31_pll6_init), PLL(AWPLL_A80_PLL4, a80_pll4_recalc, NULL, NULL), PLL(AWPLL_A83T_PLLCPUX, a83t_pllcpux_recalc, a83t_pllcpux_set_freq, NULL), PLL(AWPLL_A64_PLLHSIC, a64_pllhsic_recalc, NULL, a64_pllhsic_init), PLL(AWPLL_H3_PLL1, a23_pll1_recalc, h3_pll1_set_freq, NULL), PLL(AWPLL_H3_PLL2, h3_pll2_recalc, h3_pll2_set_freq, NULL), }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-pll1-clk", AWPLL_A10_PLL1 }, { "allwinner,sun4i-a10-pll2-clk", AWPLL_A10_PLL2 }, { "allwinner,sun4i-a10-pll3-clk", AWPLL_A10_PLL3 }, { "allwinner,sun4i-a10-pll5-clk", AWPLL_A10_PLL5 }, { "allwinner,sun4i-a10-pll6-clk", AWPLL_A10_PLL6 }, { "allwinner,sun5i-a13-pll2-clk", AWPLL_A13_PLL2 }, { "allwinner,sun6i-a31-pll1-clk", AWPLL_A31_PLL1 }, { "allwinner,sun6i-a31-pll6-clk", AWPLL_A31_PLL6 }, { "allwinner,sun8i-a23-pll1-clk", AWPLL_A23_PLL1 }, { "allwinner,sun8i-a83t-pllcpux-clk", AWPLL_A83T_PLLCPUX }, { "allwinner,sun8i-h3-pll1-clk", AWPLL_H3_PLL1 }, { "allwinner,sun8i-h3-pll2-clk", AWPLL_H3_PLL2 }, { "allwinner,sun9i-a80-pll4-clk", AWPLL_A80_PLL4 }, { "allwinner,sun50i-a64-pllhsic-clk", AWPLL_A64_PLLHSIC }, { NULL, 0 } }; static int aw_pll_init(struct clknode *clk, device_t dev) { clknode_init_parent_idx(clk, 0); return (0); } static int aw_pll_set_gate(struct clknode *clk, bool enable) { struct aw_pll_sc *sc; uint32_t val; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); PLL_READ(sc, &val); if (enable) val |= AW_PLL_ENABLE; else val &= ~AW_PLL_ENABLE; PLL_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_pll_recalc(struct clknode *clk, uint64_t *freq) { struct aw_pll_sc *sc; sc = clknode_get_softc(clk); if (aw_pll_func[sc->type].recalc == NULL) return (ENXIO); return (aw_pll_func[sc->type].recalc(sc, freq)); } static int aw_pll_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop) { struct aw_pll_sc *sc; sc = clknode_get_softc(clk); *stop = 1; if (aw_pll_func[sc->type].set_freq == NULL) return (ENXIO); return (aw_pll_func[sc->type].set_freq(sc, fin, fout, flags)); } static clknode_method_t aw_pll_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_pll_init), CLKNODEMETHOD(clknode_set_gate, aw_pll_set_gate), CLKNODEMETHOD(clknode_recalc_freq, aw_pll_recalc), CLKNODEMETHOD(clknode_set_freq, aw_pll_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_pll_clknode, aw_pll_clknode_class, aw_pll_clknode_methods, sizeof(struct aw_pll_sc), clknode_class); static int aw_pll_create(device_t dev, bus_addr_t paddr, struct clkdom *clkdom, const char *pclkname, const char *clkname, int index) { enum aw_pll_type type; struct clknode_init_def clkdef; struct aw_pll_sc *sc; struct clknode *clk; int error; type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; memset(&clkdef, 0, sizeof(clkdef)); clkdef.id = index; clkdef.name = clkname; if (pclkname != NULL) { clkdef.parent_names = malloc(sizeof(char *), M_OFWPROP, M_WAITOK); clkdef.parent_names[0] = pclkname; clkdef.parent_cnt = 1; } else clkdef.parent_cnt = 0; if (aw_pll_func[type].init != NULL) { error = aw_pll_func[type].init(device_get_parent(dev), paddr, &clkdef); if (error != 0) { device_printf(dev, "clock %s init failed\n", clkname); return (error); } } clk = clknode_create(clkdom, &aw_pll_clknode_class, &clkdef); if (clk == NULL) { device_printf(dev, "cannot create clock node\n"); return (ENXIO); } sc = clknode_get_softc(clk); sc->clkdev = device_get_parent(dev); sc->reg = paddr; sc->type = type; sc->id = clkdef.id; clknode_register(clkdom, clk); OF_prop_free(__DECONST(char *, clkdef.parent_names)); return (0); } static int aw_pll_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner PLL Clock"); return (BUS_PROBE_DEFAULT); } static int aw_pll_attach(device_t dev) { struct clkdom *clkdom; const char **names; int index, nout, error; clk_t clk_parent; uint32_t *indices; bus_addr_t paddr; bus_size_t psize; phandle_t node; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "couldn't parse 'reg' property\n"); return (ENXIO); } clkdom = clkdom_create(dev); nout = clk_parse_ofw_out_names(dev, node, &names, &indices); if (nout == 0) { device_printf(dev, "no clock outputs found\n"); error = ENOENT; goto fail; } if (clk_get_by_ofw_index(dev, 0, 0, &clk_parent) != 0) clk_parent = NULL; for (index = 0; index < nout; index++) { error = aw_pll_create(dev, paddr, clkdom, clk_parent ? clk_get_name(clk_parent) : NULL, names[index], nout == 1 ? 1 : index); if (error) goto fail; } if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_pll_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_pll_probe), DEVMETHOD(device_attach, aw_pll_attach), DEVMETHOD_END }; static driver_t aw_pll_driver = { "aw_pll", aw_pll_methods, 0, }; static devclass_t aw_pll_devclass; EARLY_DRIVER_MODULE(aw_pll, simplebus, aw_pll_driver, aw_pll_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/arm/allwinner/clk/aw_thsclk.c =================================================================== --- head/sys/arm/allwinner/clk/aw_thsclk.c (revision 310177) +++ head/sys/arm/allwinner/clk/aw_thsclk.c (revision 310178) @@ -1,321 +1,324 @@ /*- * Copyright (c) 2016 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Allwinner THS clocks */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define SCLK_GATING (1 << 31) #define CLK_SRC_SEL (0x3 << 24) #define CLK_SRC_SEL_SHIFT 24 #define CLK_SRC_SEL_MAX 1 #define CLK_DIV_RATIO (0x3 << 0) #define CLK_DIV_RATIO_SHIFT 0 #define CLK_DIV_RATIO_MAX 3 static struct ofw_compat_data compat_data[] = { { "allwinner,sun8i-h3-ths-clk", 1 }, { "allwinner,sun50i-a64-ths-clk", 1 }, { NULL, 0 } }; struct aw_thsclk_sc { device_t clkdev; bus_addr_t reg; }; #define THSCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define THSCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_thsclk_init(struct clknode *clk, device_t dev) { struct aw_thsclk_sc *sc; uint32_t val, index; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); THSCLK_READ(sc, &val); DEVICE_UNLOCK(sc); index = (val & CLK_SRC_SEL) >> CLK_SRC_SEL_SHIFT; clknode_init_parent_idx(clk, index); return (0); } static int aw_thsclk_set_mux(struct clknode *clk, int index) { struct aw_thsclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); if (index < 0 || index >= CLK_SRC_SEL_MAX) return (ERANGE); DEVICE_LOCK(sc); THSCLK_READ(sc, &val); val &= ~CLK_SRC_SEL; val |= (index << CLK_SRC_SEL_SHIFT); THSCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_thsclk_set_gate(struct clknode *clk, bool enable) { struct aw_thsclk_sc *sc; uint32_t val; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); THSCLK_READ(sc, &val); if (enable) val |= SCLK_GATING; else val &= ~SCLK_GATING; THSCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static int aw_thsclk_recalc_freq(struct clknode *clk, uint64_t *freq) { struct aw_thsclk_sc *sc; uint32_t val, div; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); THSCLK_READ(sc, &val); DEVICE_UNLOCK(sc); switch (val & CLK_DIV_RATIO) { case 3: div = 6; break; default: div = 1 << (val & CLK_DIV_RATIO); break; } *freq = *freq / div; return (0); } static int aw_thsclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop) { struct aw_thsclk_sc *sc; uint32_t val, div, n, best_div, best_n; uint64_t cur_freq; int64_t best_diff, cur_diff; sc = clknode_get_softc(clk); best_diff = (int64_t)*fout; best_n = 0; for (div = 0; div <= CLK_DIV_RATIO_MAX; div++) { n = (div == 3) ? 6 : (1 << div); cur_freq = fin / n; cur_diff = (int64_t)*fout - cur_freq; if (cur_diff >= 0 && cur_diff < best_diff) { best_diff = cur_diff; best_div = div; best_n = n; } } if (best_diff == (int64_t)*fout || best_n == 0) return (ERANGE); + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + DEVICE_LOCK(sc); THSCLK_READ(sc, &val); val &= ~CLK_DIV_RATIO; val |= (best_div << CLK_DIV_RATIO_SHIFT); THSCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); *fout = fin / best_n; *stop = 1; return (0); } static clknode_method_t aw_thsclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_thsclk_init), CLKNODEMETHOD(clknode_set_gate, aw_thsclk_set_gate), CLKNODEMETHOD(clknode_set_mux, aw_thsclk_set_mux), CLKNODEMETHOD(clknode_recalc_freq, aw_thsclk_recalc_freq), CLKNODEMETHOD(clknode_set_freq, aw_thsclk_set_freq), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_thsclk_clknode, aw_thsclk_clknode_class, aw_thsclk_clknode_methods, sizeof(struct aw_thsclk_sc), clknode_class); static int aw_thsclk_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner THS Clock"); return (BUS_PROBE_DEFAULT); } static int aw_thsclk_attach(device_t dev) { struct clknode_init_def def; struct aw_thsclk_sc *sc; struct clkdom *clkdom; struct clknode *clk; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; int error, ncells, i; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0) { device_printf(dev, "cannot get clock count\n"); return (error); } clkdom = clkdom_create(dev); memset(&def, 0, sizeof(def)); error = clk_parse_ofw_clk_name(dev, node, &def.name); if (error != 0) { device_printf(dev, "cannot parse clock name\n"); error = ENXIO; goto fail; } def.id = 1; def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", i); goto fail; } def.parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } def.parent_cnt = ncells; clk = clknode_create(clkdom, &aw_thsclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } sc = clknode_get_softc(clk); sc->reg = paddr; sc->clkdev = device_get_parent(dev); clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_thsclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_thsclk_probe), DEVMETHOD(device_attach, aw_thsclk_attach), DEVMETHOD_END }; static driver_t aw_thsclk_driver = { "aw_thsclk", aw_thsclk_methods, 0 }; static devclass_t aw_thsclk_devclass; EARLY_DRIVER_MODULE(aw_thsclk, simplebus, aw_thsclk_driver, aw_thsclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);