1297627Sjmcneill/*- 2297627Sjmcneill * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca> 3297627Sjmcneill * All rights reserved. 4297627Sjmcneill * 5297627Sjmcneill * Redistribution and use in source and binary forms, with or without 6297627Sjmcneill * modification, are permitted provided that the following conditions 7297627Sjmcneill * are met: 8297627Sjmcneill * 1. Redistributions of source code must retain the above copyright 9297627Sjmcneill * notice, this list of conditions and the following disclaimer. 10297627Sjmcneill * 2. Redistributions in binary form must reproduce the above copyright 11297627Sjmcneill * notice, this list of conditions and the following disclaimer in the 12297627Sjmcneill * documentation and/or other materials provided with the distribution. 13297627Sjmcneill * 14297627Sjmcneill * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15297627Sjmcneill * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16297627Sjmcneill * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17297627Sjmcneill * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18297627Sjmcneill * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19297627Sjmcneill * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20297627Sjmcneill * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 21297627Sjmcneill * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22297627Sjmcneill * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23297627Sjmcneill * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24297627Sjmcneill * SUCH DAMAGE. 25297627Sjmcneill * 26297627Sjmcneill * $FreeBSD: stable/11/sys/arm/allwinner/clk/aw_hdmiclk.c 308324 2016-11-05 04:17:32Z mmel $ 27297627Sjmcneill */ 28297627Sjmcneill 29297627Sjmcneill/* 30297627Sjmcneill * Allwinner HDMI clock 31297627Sjmcneill */ 32297627Sjmcneill 33297627Sjmcneill#include <sys/cdefs.h> 34297627Sjmcneill__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_hdmiclk.c 308324 2016-11-05 04:17:32Z mmel $"); 35297627Sjmcneill 36297627Sjmcneill#include <sys/param.h> 37297627Sjmcneill#include <sys/systm.h> 38297627Sjmcneill#include <sys/bus.h> 39297627Sjmcneill#include <sys/rman.h> 40297627Sjmcneill#include <sys/kernel.h> 41297627Sjmcneill#include <sys/module.h> 42297627Sjmcneill#include <machine/bus.h> 43297627Sjmcneill 44297627Sjmcneill#include <dev/ofw/ofw_bus.h> 45297627Sjmcneill#include <dev/ofw/ofw_bus_subr.h> 46297627Sjmcneill#include <dev/ofw/ofw_subr.h> 47297627Sjmcneill 48297627Sjmcneill#include <dev/extres/clk/clk_mux.h> 49297627Sjmcneill#include <dev/extres/clk/clk_gate.h> 50297627Sjmcneill 51297627Sjmcneill#include "clkdev_if.h" 52297627Sjmcneill 53297627Sjmcneill#define SCLK_GATING (1 << 31) 54297627Sjmcneill#define CLK_SRC_SEL (0x3 << 24) 55297627Sjmcneill#define CLK_SRC_SEL_SHIFT 24 56297627Sjmcneill#define CLK_SRC_SEL_MAX 0x3 57297627Sjmcneill#define CLK_RATIO_N (0x3 << 16) 58297627Sjmcneill#define CLK_RATIO_N_SHIFT 16 59297627Sjmcneill#define CLK_RATIO_N_MAX 0x3 60297627Sjmcneill#define CLK_RATIO_M (0x1f << 0) 61297627Sjmcneill#define CLK_RATIO_M_SHIFT 0 62297627Sjmcneill#define CLK_RATIO_M_MAX 0x1f 63297627Sjmcneill 64297627Sjmcneill#define CLK_IDX_PLL3_1X 0 65297627Sjmcneill 66297627Sjmcneillstatic struct ofw_compat_data compat_data[] = { 67297627Sjmcneill { "allwinner,sun4i-a10-hdmi-clk", 1 }, 68297627Sjmcneill { NULL, 0 } 69297627Sjmcneill}; 70297627Sjmcneill 71297627Sjmcneillstruct aw_hdmiclk_sc { 72297627Sjmcneill device_t clkdev; 73297627Sjmcneill bus_addr_t reg; 74297627Sjmcneill}; 75297627Sjmcneill 76297627Sjmcneill#define HDMICLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) 77297627Sjmcneill#define HDMICLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) 78297627Sjmcneill#define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) 79297627Sjmcneill#define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) 80297627Sjmcneill 81297627Sjmcneillstatic int 82297627Sjmcneillaw_hdmiclk_init(struct clknode *clk, device_t dev) 83297627Sjmcneill{ 84297627Sjmcneill struct aw_hdmiclk_sc *sc; 85297627Sjmcneill uint32_t val, index; 86297627Sjmcneill 87297627Sjmcneill sc = clknode_get_softc(clk); 88297627Sjmcneill 89297627Sjmcneill /* Select PLL3(1X) clock source */ 90297627Sjmcneill index = CLK_IDX_PLL3_1X; 91297627Sjmcneill 92297627Sjmcneill DEVICE_LOCK(sc); 93297627Sjmcneill HDMICLK_READ(sc, &val); 94297627Sjmcneill val &= ~CLK_SRC_SEL; 95297627Sjmcneill val |= (index << CLK_SRC_SEL_SHIFT); 96297627Sjmcneill HDMICLK_WRITE(sc, val); 97297627Sjmcneill DEVICE_UNLOCK(sc); 98297627Sjmcneill 99297627Sjmcneill clknode_init_parent_idx(clk, index); 100297627Sjmcneill return (0); 101297627Sjmcneill} 102297627Sjmcneill 103297627Sjmcneillstatic int 104297627Sjmcneillaw_hdmiclk_set_mux(struct clknode *clk, int index) 105297627Sjmcneill{ 106297627Sjmcneill struct aw_hdmiclk_sc *sc; 107297627Sjmcneill uint32_t val; 108297627Sjmcneill 109297627Sjmcneill sc = clknode_get_softc(clk); 110297627Sjmcneill 111297627Sjmcneill if (index < 0 || index > CLK_SRC_SEL_MAX) 112297627Sjmcneill return (ERANGE); 113297627Sjmcneill 114297627Sjmcneill DEVICE_LOCK(sc); 115297627Sjmcneill HDMICLK_READ(sc, &val); 116297627Sjmcneill val &= ~CLK_SRC_SEL; 117297627Sjmcneill val |= (index << CLK_SRC_SEL_SHIFT); 118297627Sjmcneill HDMICLK_WRITE(sc, val); 119297627Sjmcneill DEVICE_UNLOCK(sc); 120297627Sjmcneill 121297627Sjmcneill return (0); 122297627Sjmcneill} 123297627Sjmcneill 124297627Sjmcneillstatic int 125297627Sjmcneillaw_hdmiclk_set_gate(struct clknode *clk, bool enable) 126297627Sjmcneill{ 127297627Sjmcneill struct aw_hdmiclk_sc *sc; 128297627Sjmcneill uint32_t val; 129297627Sjmcneill 130297627Sjmcneill sc = clknode_get_softc(clk); 131297627Sjmcneill 132297627Sjmcneill DEVICE_LOCK(sc); 133297627Sjmcneill HDMICLK_READ(sc, &val); 134297627Sjmcneill if (enable) 135297627Sjmcneill val |= SCLK_GATING; 136297627Sjmcneill else 137297627Sjmcneill val &= ~SCLK_GATING; 138297627Sjmcneill HDMICLK_WRITE(sc, val); 139297627Sjmcneill DEVICE_UNLOCK(sc); 140297627Sjmcneill 141297627Sjmcneill return (0); 142297627Sjmcneill} 143297627Sjmcneill 144297627Sjmcneillstatic int 145297627Sjmcneillaw_hdmiclk_recalc_freq(struct clknode *clk, uint64_t *freq) 146297627Sjmcneill{ 147297627Sjmcneill struct aw_hdmiclk_sc *sc; 148297627Sjmcneill uint32_t val, m, n; 149297627Sjmcneill 150297627Sjmcneill sc = clknode_get_softc(clk); 151297627Sjmcneill 152297627Sjmcneill DEVICE_LOCK(sc); 153297627Sjmcneill HDMICLK_READ(sc, &val); 154297627Sjmcneill DEVICE_UNLOCK(sc); 155297627Sjmcneill 156297627Sjmcneill n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT); 157297627Sjmcneill m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1; 158297627Sjmcneill 159297627Sjmcneill *freq = *freq / n / m; 160297627Sjmcneill 161297627Sjmcneill return (0); 162297627Sjmcneill} 163297627Sjmcneill 164297627Sjmcneillstatic int 165297627Sjmcneillaw_hdmiclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, 166297627Sjmcneill int flags, int *stop) 167297627Sjmcneill{ 168297627Sjmcneill struct aw_hdmiclk_sc *sc; 169297627Sjmcneill uint32_t val, m, n, best_m, best_n; 170297627Sjmcneill uint64_t cur_freq; 171297627Sjmcneill int64_t best_diff, cur_diff; 172297627Sjmcneill 173297627Sjmcneill sc = clknode_get_softc(clk); 174297627Sjmcneill best_n = best_m = 0; 175297627Sjmcneill best_diff = (int64_t)*fout; 176297627Sjmcneill 177297627Sjmcneill for (n = 0; n <= CLK_RATIO_N_MAX; n++) 178297627Sjmcneill for (m = 0; m <= CLK_RATIO_M_MAX; m++) { 179297627Sjmcneill cur_freq = fin / (1 << n) / (m + 1); 180297627Sjmcneill cur_diff = (int64_t)*fout - cur_freq; 181297627Sjmcneill if (cur_diff >= 0 && cur_diff < best_diff) { 182297627Sjmcneill best_diff = cur_diff; 183297627Sjmcneill best_m = m; 184297627Sjmcneill best_n = n; 185297627Sjmcneill } 186297627Sjmcneill } 187297627Sjmcneill 188297627Sjmcneill if (best_diff == (int64_t)*fout) 189297627Sjmcneill return (ERANGE); 190297627Sjmcneill 191297627Sjmcneill DEVICE_LOCK(sc); 192297627Sjmcneill HDMICLK_READ(sc, &val); 193297627Sjmcneill val &= ~(CLK_RATIO_N | CLK_RATIO_M); 194297627Sjmcneill val |= (best_n << CLK_RATIO_N_SHIFT); 195297627Sjmcneill val |= (best_m << CLK_RATIO_M_SHIFT); 196297627Sjmcneill HDMICLK_WRITE(sc, val); 197297627Sjmcneill DEVICE_UNLOCK(sc); 198297627Sjmcneill 199297627Sjmcneill *fout = fin / (1 << best_n) / (best_m + 1); 200297627Sjmcneill *stop = 1; 201297627Sjmcneill 202297627Sjmcneill return (0); 203297627Sjmcneill} 204297627Sjmcneill 205297627Sjmcneillstatic clknode_method_t aw_hdmiclk_clknode_methods[] = { 206297627Sjmcneill /* Device interface */ 207297627Sjmcneill CLKNODEMETHOD(clknode_init, aw_hdmiclk_init), 208297627Sjmcneill CLKNODEMETHOD(clknode_set_gate, aw_hdmiclk_set_gate), 209297627Sjmcneill CLKNODEMETHOD(clknode_set_mux, aw_hdmiclk_set_mux), 210297627Sjmcneill CLKNODEMETHOD(clknode_recalc_freq, aw_hdmiclk_recalc_freq), 211297627Sjmcneill CLKNODEMETHOD(clknode_set_freq, aw_hdmiclk_set_freq), 212297627Sjmcneill CLKNODEMETHOD_END 213297627Sjmcneill}; 214297627SjmcneillDEFINE_CLASS_1(aw_hdmiclk_clknode, aw_hdmiclk_clknode_class, 215297627Sjmcneill aw_hdmiclk_clknode_methods, sizeof(struct aw_hdmiclk_sc), clknode_class); 216297627Sjmcneill 217297627Sjmcneillstatic int 218297627Sjmcneillaw_hdmiclk_probe(device_t dev) 219297627Sjmcneill{ 220297627Sjmcneill if (!ofw_bus_status_okay(dev)) 221297627Sjmcneill return (ENXIO); 222297627Sjmcneill 223297627Sjmcneill if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 224297627Sjmcneill return (ENXIO); 225297627Sjmcneill 226297627Sjmcneill device_set_desc(dev, "Allwinner HDMI Clock"); 227297627Sjmcneill return (BUS_PROBE_DEFAULT); 228297627Sjmcneill} 229297627Sjmcneill 230297627Sjmcneillstatic int 231297627Sjmcneillaw_hdmiclk_attach(device_t dev) 232297627Sjmcneill{ 233297627Sjmcneill struct clknode_init_def def; 234297627Sjmcneill struct aw_hdmiclk_sc *sc; 235297627Sjmcneill struct clkdom *clkdom; 236297627Sjmcneill struct clknode *clk; 237297627Sjmcneill clk_t clk_parent; 238297627Sjmcneill bus_addr_t paddr; 239297627Sjmcneill bus_size_t psize; 240297627Sjmcneill phandle_t node; 241297627Sjmcneill int error; 242297627Sjmcneill 243297627Sjmcneill node = ofw_bus_get_node(dev); 244297627Sjmcneill 245297627Sjmcneill if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { 246297627Sjmcneill device_printf(dev, "cannot parse 'reg' property\n"); 247297627Sjmcneill return (ENXIO); 248297627Sjmcneill } 249297627Sjmcneill 250297627Sjmcneill clkdom = clkdom_create(dev); 251297627Sjmcneill 252308324Smmel error = clk_get_by_ofw_index(dev, 0, 0, &clk_parent); 253297627Sjmcneill if (error != 0) { 254297627Sjmcneill device_printf(dev, "cannot parse clock parent\n"); 255297627Sjmcneill return (ENXIO); 256297627Sjmcneill } 257297627Sjmcneill 258297627Sjmcneill memset(&def, 0, sizeof(def)); 259297627Sjmcneill error = clk_parse_ofw_clk_name(dev, node, &def.name); 260297627Sjmcneill if (error != 0) { 261297627Sjmcneill device_printf(dev, "cannot parse clock name\n"); 262297627Sjmcneill error = ENXIO; 263297627Sjmcneill goto fail; 264297627Sjmcneill } 265297627Sjmcneill def.id = 1; 266297627Sjmcneill def.parent_names = malloc(sizeof(char *), M_OFWPROP, M_WAITOK); 267297627Sjmcneill def.parent_names[0] = clk_get_name(clk_parent); 268297627Sjmcneill def.parent_cnt = 1; 269297627Sjmcneill 270297627Sjmcneill clk = clknode_create(clkdom, &aw_hdmiclk_clknode_class, &def); 271297627Sjmcneill if (clk == NULL) { 272297627Sjmcneill device_printf(dev, "cannot create clknode\n"); 273297627Sjmcneill error = ENXIO; 274297627Sjmcneill goto fail; 275297627Sjmcneill } 276297627Sjmcneill 277297627Sjmcneill sc = clknode_get_softc(clk); 278297627Sjmcneill sc->reg = paddr; 279297627Sjmcneill sc->clkdev = device_get_parent(dev); 280297627Sjmcneill 281297627Sjmcneill clknode_register(clkdom, clk); 282297627Sjmcneill 283297627Sjmcneill if (clkdom_finit(clkdom) != 0) { 284297627Sjmcneill device_printf(dev, "cannot finalize clkdom initialization\n"); 285297627Sjmcneill error = ENXIO; 286297627Sjmcneill goto fail; 287297627Sjmcneill } 288297627Sjmcneill 289297627Sjmcneill if (bootverbose) 290297627Sjmcneill clkdom_dump(clkdom); 291297627Sjmcneill 292297627Sjmcneill return (0); 293297627Sjmcneill 294297627Sjmcneillfail: 295297627Sjmcneill return (error); 296297627Sjmcneill} 297297627Sjmcneill 298297627Sjmcneillstatic device_method_t aw_hdmiclk_methods[] = { 299297627Sjmcneill /* Device interface */ 300297627Sjmcneill DEVMETHOD(device_probe, aw_hdmiclk_probe), 301297627Sjmcneill DEVMETHOD(device_attach, aw_hdmiclk_attach), 302297627Sjmcneill 303297627Sjmcneill DEVMETHOD_END 304297627Sjmcneill}; 305297627Sjmcneill 306297627Sjmcneillstatic driver_t aw_hdmiclk_driver = { 307297627Sjmcneill "aw_hdmiclk", 308297627Sjmcneill aw_hdmiclk_methods, 309297627Sjmcneill 0 310297627Sjmcneill}; 311297627Sjmcneill 312297627Sjmcneillstatic devclass_t aw_hdmiclk_devclass; 313297627Sjmcneill 314297627SjmcneillEARLY_DRIVER_MODULE(aw_hdmiclk, simplebus, aw_hdmiclk_driver, 315297627Sjmcneill aw_hdmiclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 316