aw_mmcclk.c revision 309756
1/*- 2 * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD: stable/11/sys/arm/allwinner/clk/aw_mmcclk.c 309756 2016-12-09 20:07:01Z manu $ 27 */ 28 29/* 30 * Allwinner MMC clocks 31 */ 32 33#include <sys/cdefs.h> 34__FBSDID("$FreeBSD: stable/11/sys/arm/allwinner/clk/aw_mmcclk.c 309756 2016-12-09 20:07:01Z manu $"); 35 36#include <sys/param.h> 37#include <sys/systm.h> 38#include <sys/bus.h> 39#include <sys/rman.h> 40#include <sys/kernel.h> 41#include <sys/module.h> 42#include <machine/bus.h> 43 44#include <dev/ofw/ofw_bus.h> 45#include <dev/ofw/ofw_bus_subr.h> 46#include <dev/ofw/ofw_subr.h> 47 48#include <dev/extres/clk/clk_mux.h> 49#include <dev/extres/clk/clk_gate.h> 50 51#include "clkdev_if.h" 52 53#define SCLK_GATING (1 << 31) 54#define CLK_SRC_SEL (0x3 << 24) 55#define CLK_SRC_SEL_SHIFT 24 56#define CLK_SRC_SEL_MAX 0x3 57#define CLK_SRC_SEL_OSC24M 0 58#define CLK_SRC_SEL_PLL6 1 59#define CLK_PHASE_CTR (0x7 << 20) 60#define CLK_PHASE_CTR_SHIFT 20 61#define CLK_RATIO_N (0x3 << 16) 62#define CLK_RATIO_N_SHIFT 16 63#define CLK_RATIO_N_MAX 0x3 64#define OUTPUT_CLK_PHASE_CTR (0x7 << 8) 65#define OUTPUT_CLK_PHASE_CTR_SHIFT 8 66#define CLK_RATIO_M (0xf << 0) 67#define CLK_RATIO_M_SHIFT 0 68#define CLK_RATIO_M_MAX 0xf 69 70static struct ofw_compat_data compat_data[] = { 71 { "allwinner,sun4i-a10-mmc-clk", 1 }, 72 { NULL, 0 } 73}; 74 75struct aw_mmcclk_sc { 76 device_t clkdev; 77 bus_addr_t reg; 78}; 79 80#define MODCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) 81#define MODCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) 82#define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) 83#define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) 84 85static int 86aw_mmcclk_init(struct clknode *clk, device_t dev) 87{ 88 struct aw_mmcclk_sc *sc; 89 uint32_t val, index; 90 91 sc = clknode_get_softc(clk); 92 93 DEVICE_LOCK(sc); 94 MODCLK_READ(sc, &val); 95 DEVICE_UNLOCK(sc); 96 97 index = (val & CLK_SRC_SEL) >> CLK_SRC_SEL_SHIFT; 98 99 clknode_init_parent_idx(clk, index); 100 return (0); 101} 102 103static int 104aw_mmcclk_set_mux(struct clknode *clk, int index) 105{ 106 struct aw_mmcclk_sc *sc; 107 uint32_t val; 108 109 sc = clknode_get_softc(clk); 110 111 if (index < 0 || index > CLK_SRC_SEL_MAX) 112 return (ERANGE); 113 114 DEVICE_LOCK(sc); 115 MODCLK_READ(sc, &val); 116 val &= ~CLK_SRC_SEL; 117 val |= (index << CLK_SRC_SEL_SHIFT); 118 MODCLK_WRITE(sc, val); 119 DEVICE_UNLOCK(sc); 120 121 return (0); 122} 123 124static int 125aw_mmcclk_set_gate(struct clknode *clk, bool enable) 126{ 127 struct aw_mmcclk_sc *sc; 128 uint32_t val; 129 130 sc = clknode_get_softc(clk); 131 132 DEVICE_LOCK(sc); 133 MODCLK_READ(sc, &val); 134 if (enable) 135 val |= SCLK_GATING; 136 else 137 val &= ~SCLK_GATING; 138 MODCLK_WRITE(sc, val); 139 DEVICE_UNLOCK(sc); 140 141 return (0); 142} 143 144static int 145aw_mmcclk_recalc_freq(struct clknode *clk, uint64_t *freq) 146{ 147 struct aw_mmcclk_sc *sc; 148 uint32_t val, m, n; 149 150 sc = clknode_get_softc(clk); 151 152 DEVICE_LOCK(sc); 153 MODCLK_READ(sc, &val); 154 DEVICE_UNLOCK(sc); 155 156 n = 1 << ((val & CLK_RATIO_N) >> CLK_RATIO_N_SHIFT); 157 m = ((val & CLK_RATIO_M) >> CLK_RATIO_M_SHIFT) + 1; 158 159 *freq = *freq / n / m; 160 161 return (0); 162} 163 164static int 165aw_mmcclk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, 166 int flags, int *stop) 167{ 168 struct aw_mmcclk_sc *sc; 169 uint32_t val, m, n, phase, ophase; 170 int parent_idx, error; 171 172 sc = clknode_get_softc(clk); 173 174 /* XXX 175 * The ophase/phase values should be set by the MMC driver, but 176 * there is currently no way to do this with the clk API 177 */ 178 if (*fout <= 400000) { 179 parent_idx = CLK_SRC_SEL_OSC24M; 180 ophase = 0; 181 phase = 0; 182 n = 2; 183 } else if (*fout <= 25000000) { 184 parent_idx = CLK_SRC_SEL_PLL6; 185 ophase = 0; 186 phase = 5; 187 n = 2; 188 } else if (*fout <= 52000000) { 189 parent_idx = CLK_SRC_SEL_PLL6; 190 ophase = 3; 191 phase = 5; 192 n = 0; 193 } else 194 return (ERANGE); 195 196 /* Switch parent clock, if necessary */ 197 if (parent_idx != clknode_get_parent_idx(clk)) { 198 error = clknode_set_parent_by_idx(clk, parent_idx); 199 if (error != 0) 200 return (error); 201 202 /* Fetch new input frequency */ 203 error = clknode_get_freq(clknode_get_parent(clk), &fin); 204 if (error != 0) 205 return (error); 206 } 207 208 m = ((fin / (1 << n)) / *fout) - 1; 209 210 DEVICE_LOCK(sc); 211 MODCLK_READ(sc, &val); 212 val &= ~(CLK_RATIO_N | CLK_RATIO_M | CLK_PHASE_CTR | 213 OUTPUT_CLK_PHASE_CTR); 214 val |= (n << CLK_RATIO_N_SHIFT); 215 val |= (m << CLK_RATIO_M_SHIFT); 216 val |= (phase << CLK_PHASE_CTR_SHIFT); 217 val |= (ophase << OUTPUT_CLK_PHASE_CTR_SHIFT); 218 MODCLK_WRITE(sc, val); 219 DEVICE_UNLOCK(sc); 220 221 *fout = fin / (1 << n) / (m + 1); 222 *stop = 1; 223 224 return (0); 225} 226 227static clknode_method_t aw_mmcclk_clknode_methods[] = { 228 /* Device interface */ 229 CLKNODEMETHOD(clknode_init, aw_mmcclk_init), 230 CLKNODEMETHOD(clknode_set_gate, aw_mmcclk_set_gate), 231 CLKNODEMETHOD(clknode_set_mux, aw_mmcclk_set_mux), 232 CLKNODEMETHOD(clknode_recalc_freq, aw_mmcclk_recalc_freq), 233 CLKNODEMETHOD(clknode_set_freq, aw_mmcclk_set_freq), 234 CLKNODEMETHOD_END 235}; 236DEFINE_CLASS_1(aw_mmcclk_clknode, aw_mmcclk_clknode_class, 237 aw_mmcclk_clknode_methods, sizeof(struct aw_mmcclk_sc), clknode_class); 238 239static int 240aw_mmcclk_probe(device_t dev) 241{ 242 if (!ofw_bus_status_okay(dev)) 243 return (ENXIO); 244 245 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 246 return (ENXIO); 247 248 device_set_desc(dev, "Allwinner MMC Clock"); 249 return (BUS_PROBE_DEFAULT); 250} 251 252static int 253aw_mmcclk_attach(device_t dev) 254{ 255 struct clknode_init_def def; 256 struct aw_mmcclk_sc *sc; 257 struct clkdom *clkdom; 258 struct clknode *clk; 259 const char **names; 260 uint32_t *indices; 261 clk_t clk_parent; 262 bus_addr_t paddr; 263 bus_size_t psize; 264 phandle_t node; 265 int error, nout, ncells, i; 266 267 node = ofw_bus_get_node(dev); 268 269 if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { 270 device_printf(dev, "cannot parse 'reg' property\n"); 271 return (ENXIO); 272 } 273 274 error = ofw_bus_parse_xref_list_get_length(node, "clocks", 275 "#clock-cells", &ncells); 276 if (error != 0 || ncells == 0) { 277 device_printf(dev, "couldn't find parent clocks\n"); 278 return (ENXIO); 279 } 280 281 clkdom = clkdom_create(dev); 282 283 nout = clk_parse_ofw_out_names(dev, node, &names, &indices); 284 if (nout == 0) { 285 device_printf(dev, "no output clocks found\n"); 286 error = ENXIO; 287 goto fail; 288 } 289 290 memset(&def, 0, sizeof(def)); 291 def.name = names[0]; 292 def.id = 0; 293 def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); 294 for (i = 0; i < ncells; i++) { 295 error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); 296 if (error != 0) { 297 device_printf(dev, "cannot get clock %d\n", i); 298 goto fail; 299 } 300 def.parent_names[i] = clk_get_name(clk_parent); 301 clk_release(clk_parent); 302 } 303 def.parent_cnt = ncells; 304 def.flags = CLK_NODE_GLITCH_FREE; 305 306 clk = clknode_create(clkdom, &aw_mmcclk_clknode_class, &def); 307 if (clk == NULL) { 308 device_printf(dev, "cannot create clknode\n"); 309 error = ENXIO; 310 goto fail; 311 } 312 313 sc = clknode_get_softc(clk); 314 sc->reg = paddr; 315 sc->clkdev = device_get_parent(dev); 316 317 clknode_register(clkdom, clk); 318 319 if (clkdom_finit(clkdom) != 0) { 320 device_printf(dev, "cannot finalize clkdom initialization\n"); 321 error = ENXIO; 322 goto fail; 323 } 324 325 if (bootverbose) 326 clkdom_dump(clkdom); 327 328 return (0); 329 330fail: 331 return (error); 332} 333 334static device_method_t aw_mmcclk_methods[] = { 335 /* Device interface */ 336 DEVMETHOD(device_probe, aw_mmcclk_probe), 337 DEVMETHOD(device_attach, aw_mmcclk_attach), 338 339 DEVMETHOD_END 340}; 341 342static driver_t aw_mmcclk_driver = { 343 "aw_mmcclk", 344 aw_mmcclk_methods, 345 0 346}; 347 348static devclass_t aw_mmcclk_devclass; 349 350EARLY_DRIVER_MODULE(aw_mmcclk, simplebus, aw_mmcclk_driver, 351 aw_mmcclk_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); 352