1 /* $OpenBSD: exclock.c,v 1.4 2016/07/26 22:10:10 patrick Exp $ */ 2 /* 3 * Copyright (c) 2012-2013 Patrick Wildt <patrick@blueri.se> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/param.h> 19 #include <sys/systm.h> 20 #include <sys/queue.h> 21 #include <sys/malloc.h> 22 #include <sys/sysctl.h> 23 #include <sys/device.h> 24 #include <sys/evcount.h> 25 #include <sys/socket.h> 26 #include <sys/timeout.h> 27 #include <machine/intr.h> 28 #include <machine/bus.h> 29 #if NFDT > 0 30 #include <machine/fdt.h> 31 #endif 32 #include <armv7/armv7/armv7var.h> 33 34 /* registers */ 35 #define CLOCK_APLL_CON0 0x0100 36 #define CLOCK_APLL_CON1 0x0104 37 #define CLOCK_BPLL_CON0 0x0110 38 #define CLOCK_BPLL_CON1 0x0114 39 #define CLOCK_EPLL_CON0 0x0130 40 #define CLOCK_EPLL_CON1 0x0134 41 #define CLOCK_EPLL_CON2 0x0138 42 #define CLOCK_VPLL_CON0 0x0140 43 #define CLOCK_VPLL_CON1 0x0144 44 #define CLOCK_VPLL_CON2 0x0148 45 #define CLOCK_CLK_DIV_CPU0 0x0500 46 #define CLOCK_CLK_DIV_CPU1 0x0504 47 #define CLOCK_CLK_DIV_TOP0 0x0510 48 #define CLOCK_CLK_DIV_TOP1 0x0514 49 #define CLOCK_PLL_DIV2_SEL 0x0A24 50 #define CLOCK_MPLL_CON0 0x4100 51 #define CLOCK_MPLL_CON1 0x4104 52 53 /* bits and bytes */ 54 #define MPLL_FOUT_SEL_SHIFT 0x4 55 #define MPLL_FOUT_SEL_MASK 0x1 56 #define BPLL_FOUT_SEL_SHIFT 0x0 57 #define BPLL_FOUT_SEL_MASK 0x1 58 59 #define HCLK_FREQ 24000 60 61 #define HREAD4(sc, reg) \ 62 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) 63 #define HWRITE4(sc, reg, val) \ 64 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) 65 #define HSET4(sc, reg, bits) \ 66 HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) 67 #define HCLR4(sc, reg, bits) \ 68 HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) 69 70 struct exclock_softc { 71 struct device sc_dev; 72 bus_space_tag_t sc_iot; 73 bus_space_handle_t sc_ioh; 74 }; 75 76 enum clocks { 77 /* OSC */ 78 OSC, /* 24 MHz OSC */ 79 80 /* PLLs */ 81 APLL, /* ARM core clock */ 82 MPLL, /* System bus clock for memory controller */ 83 BPLL, /* Graphic 3D processor clock and 1066 MHz clock for memory controller if necessary */ 84 CPLL, /* Multi Format Video Hardware Codec clock */ 85 GPLL, /* Graphic 3D processor clock or other clocks for DVFS flexibility */ 86 EPLL, /* Audio interface clocks and clocks for other external device interfaces */ 87 VPLL, /* dithered PLL, helps to reduce the EMI of display and camera */ 88 }; 89 90 struct exclock_softc *exclock_sc; 91 92 int exclock_match(struct device *parent, void *v, void *aux); 93 void exclock_attach(struct device *parent, struct device *self, void *args); 94 int exclock_cpuspeed(int *); 95 unsigned int exclock_decode_pll_clk(enum clocks, unsigned int, unsigned int); 96 unsigned int exclock_get_pll_clk(enum clocks); 97 unsigned int exclock_get_armclk(void); 98 unsigned int exclock_get_i2cclk(void); 99 100 struct cfattach exclock_ca = { 101 sizeof (struct exclock_softc), NULL, exclock_attach 102 }; 103 struct cfattach exclock_fdt_ca = { 104 sizeof (struct exclock_softc), exclock_match, exclock_attach 105 }; 106 107 struct cfdriver exclock_cd = { 108 NULL, "exclock", DV_DULL 109 }; 110 111 int 112 exclock_match(struct device *parent, void *v, void *aux) 113 { 114 #if NFDT > 0 115 struct armv7_attach_args *aa = aux; 116 117 if (fdt_node_compatible("samsung,exynos5250-clock", aa->aa_node)) 118 return 1; 119 #endif 120 121 return 0; 122 } 123 124 void 125 exclock_attach(struct device *parent, struct device *self, void *args) 126 { 127 struct armv7_attach_args *aa = args; 128 struct exclock_softc *sc = (struct exclock_softc *) self; 129 struct armv7mem mem; 130 131 exclock_sc = sc; 132 sc->sc_iot = aa->aa_iot; 133 #if NFDT > 0 134 if (aa->aa_node) { 135 struct fdt_reg reg; 136 if (fdt_get_reg(aa->aa_node, 0, ®)) 137 panic("%s: could not extract memory data from FDT", 138 __func__); 139 mem.addr = reg.addr; 140 mem.size = reg.size; 141 } else 142 #endif 143 { 144 145 mem.addr = aa->aa_dev->mem[0].addr; 146 mem.size = aa->aa_dev->mem[0].size; 147 } 148 if (bus_space_map(sc->sc_iot, mem.addr, mem.size, 0, &sc->sc_ioh)) 149 panic("%s: bus_space_map failed!", __func__); 150 151 printf(": Exynos 5 CPU freq: %d MHz", 152 exclock_get_armclk() / 1000); 153 154 printf("\n"); 155 156 cpu_cpuspeed = exclock_cpuspeed; 157 } 158 159 int 160 exclock_cpuspeed(int *freq) 161 { 162 *freq = exclock_get_armclk() / 1000; 163 return (0); 164 } 165 166 unsigned int 167 exclock_decode_pll_clk(enum clocks pll, unsigned int r, unsigned int k) 168 { 169 uint32_t m, p, s = 0, mask, fout, freq; 170 /* 171 * APLL_CON: MIDV [25:16] 172 * MPLL_CON: MIDV [25:16] 173 * EPLL_CON: MIDV [24:16] 174 * VPLL_CON: MIDV [24:16] 175 * BPLL_CON: MIDV [25:16]: Exynos5 176 */ 177 178 switch (pll) 179 { 180 case APLL: 181 case MPLL: 182 case BPLL: 183 mask = 0x3ff; 184 break; 185 default: 186 mask = 0x1ff; 187 } 188 189 m = (r >> 16) & mask; 190 191 /* PDIV [13:8] */ 192 p = (r >> 8) & 0x3f; 193 /* SDIV [2:0] */ 194 s = r & 0x7; 195 196 freq = HCLK_FREQ; 197 198 if (pll == EPLL) { 199 k = k & 0xffff; 200 /* FOUT = (MDIV + K / 65536) * FIN / (PDIV * 2^SDIV) */ 201 fout = (m + k / 65536) * (freq / (p * (1 << s))); 202 } else if (pll == VPLL) { 203 k = k & 0xfff; 204 /* FOUT = (MDIV + K / 1024) * FIN / (PDIV * 2^SDIV) */ 205 fout = (m + k / 1024) * (freq / (p * (1 << s))); 206 } else { 207 /* FOUT = MDIV * FIN / (PDIV * 2^(SDIV - 1)) */ 208 fout = m * (freq / (p * (1 << s))); 209 } 210 211 return fout; 212 } 213 214 unsigned int 215 exclock_get_pll_clk(enum clocks pll) 216 { 217 struct exclock_softc *sc = exclock_sc; 218 uint32_t freq; 219 220 switch (pll) { 221 case APLL: 222 freq = exclock_decode_pll_clk(pll, 223 HREAD4(sc, CLOCK_APLL_CON0), 224 0); 225 break; 226 case MPLL: 227 freq = exclock_decode_pll_clk(pll, 228 HREAD4(sc, CLOCK_MPLL_CON0), 229 0); 230 break; 231 case BPLL: 232 freq = exclock_decode_pll_clk(pll, 233 HREAD4(sc, CLOCK_BPLL_CON0), 234 0); 235 break; 236 case EPLL: 237 freq = exclock_decode_pll_clk(pll, 238 HREAD4(sc, CLOCK_EPLL_CON0), 239 HREAD4(sc, CLOCK_EPLL_CON1)); 240 break; 241 case VPLL: 242 freq = exclock_decode_pll_clk(pll, 243 HREAD4(sc, CLOCK_VPLL_CON0), 244 HREAD4(sc, CLOCK_VPLL_CON1)); 245 break; 246 default: 247 return 0; 248 } 249 250 /* 251 * According to the user manual, in EVT1 MPLL and BPLL always gives 252 * 1.6GHz clock, so divide by 2 to get 800MHz MPLL clock. 253 */ 254 if (pll == MPLL || pll == BPLL) { 255 uint32_t freq_sel; 256 uint32_t pll_div2_sel = HREAD4(sc, CLOCK_PLL_DIV2_SEL); 257 258 switch (pll) { 259 case MPLL: 260 freq_sel = (pll_div2_sel >> MPLL_FOUT_SEL_SHIFT) 261 & MPLL_FOUT_SEL_MASK; 262 break; 263 case BPLL: 264 freq_sel = (pll_div2_sel >> BPLL_FOUT_SEL_SHIFT) 265 & BPLL_FOUT_SEL_MASK; 266 break; 267 default: 268 freq_sel = -1; 269 break; 270 } 271 272 if (freq_sel == 0) 273 freq /= 2; 274 } 275 276 return freq; 277 } 278 279 unsigned int 280 exclock_get_armclk() 281 { 282 struct exclock_softc *sc = exclock_sc; 283 uint32_t div, armclk, arm_ratio, arm2_ratio; 284 285 div = HREAD4(sc, CLOCK_CLK_DIV_CPU0); 286 287 /* ARM_RATIO: [2:0], ARM2_RATIO: [30:28] */ 288 arm_ratio = (div >> 0) & 0x7; 289 arm2_ratio = (div >> 28) & 0x7; 290 291 armclk = exclock_get_pll_clk(APLL) / (arm_ratio + 1); 292 armclk /= (arm2_ratio + 1); 293 294 return armclk; 295 } 296 297 unsigned int 298 exclock_get_i2cclk() 299 { 300 struct exclock_softc *sc = exclock_sc; 301 uint32_t aclk_66, aclk_66_pre, div, ratio; 302 303 div = HREAD4(sc, CLOCK_CLK_DIV_TOP1); 304 ratio = (div >> 24) & 0x7; 305 aclk_66_pre = exclock_get_pll_clk(MPLL) / (ratio + 1); 306 div = HREAD4(sc, CLOCK_CLK_DIV_TOP0); 307 ratio = (div >> 0) & 0x7; 308 aclk_66 = aclk_66_pre / (ratio + 1); 309 310 return aclk_66; 311 } 312