1 /* $NetBSD: loongson_clock.c,v 1.2 2020/05/29 12:30:40 rin Exp $ */ 2 3 /* 4 * Copyright (c) 2011, 2016 Michael Lorenz 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 __KERNEL_RCSID(0, "$NetBSD: loongson_clock.c,v 1.2 2020/05/29 12:30:40 rin Exp $"); 30 31 #include <sys/param.h> 32 #include <sys/systm.h> 33 #include <sys/kernel.h> 34 #include <sys/device.h> 35 #include <sys/cpu.h> 36 #include <sys/timetc.h> 37 #include <sys/sysctl.h> 38 39 #include <mips/mips3_clock.h> 40 #include <mips/locore.h> 41 #include <mips/bonito/bonitoreg.h> 42 #include <mips/bonito/bonitovar.h> 43 44 #ifdef LOONGSON_CLOCK_DEBUG 45 #define DPRINTF aprint_error 46 #else 47 #define DPRINTF while (0) printf 48 #endif 49 50 static uint32_t sc_last; 51 static uint32_t sc_scale[8]; 52 static uint32_t sc_count; /* should probably be 64 bit */ 53 static int sc_step = 7; 54 static int sc_step_wanted = 7; 55 static void *sc_shutdown_cookie; 56 57 /* 0, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, 1 */ 58 static int scale_m[] = {1, 1, 3, 1, 5, 3, 7, 1}; 59 static int scale_d[] = {0, 4, 8, 2, 8, 4, 8, 1}; 60 static int cycles[8]; 61 62 #define scale(x, f) (x * scale_d[f] / scale_m[f]) 63 #define rscale(x, f) (x * scale_m[f] / scale_d[f]) 64 65 static void loongson_set_speed(int); 66 static int loongson_cpuspeed_temp(SYSCTLFN_ARGS); 67 static int loongson_cpuspeed_cur(SYSCTLFN_ARGS); 68 static int loongson_cpuspeed_available(SYSCTLFN_ARGS); 69 70 static void loongson_clock_shutdown(void *); 71 static u_int get_loongson_timecount(struct timecounter *); 72 void loongson_delay(int); 73 void loongson_setstatclockrate(int); 74 void loongson_initclocks(void); 75 76 static struct timecounter loongson_timecounter = { 77 .tc_get_timecount = get_loongson_timecount, 78 .tc_counter_mask = 0xffffffff, 79 .tc_name = "loongson", 80 .tc_quality = 100, 81 }; 82 83 void 84 loongson_initclocks(void) 85 { 86 const struct sysctlnode *sysctl_node, *me, *freq; 87 int clk; 88 89 /* 90 * Establish a hook so on shutdown we can set the CPU clock back to 91 * full speed. This is necessary because PMON doesn't change the 92 * clock scale register on a warm boot, the MIPS clock code gets 93 * confused if we're too slow and the loongson-specific bits run 94 * too late in the boot process 95 */ 96 sc_shutdown_cookie = shutdownhook_establish(loongson_clock_shutdown, NULL); 97 98 for (clk = 1; clk < 8; clk++) { 99 sc_scale[clk] = rscale(curcpu()->ci_cpu_freq / 1000000, clk); 100 cycles[clk] = 101 (rscale(curcpu()->ci_cpu_freq, clk) + hz / 2) / (2 * hz); 102 } 103 #ifdef LOONGSON_CLOCK_DEBUG 104 for (clk = 1; clk < 8; clk++) { 105 aprint_normal("frequencies: %d/8: %d\n", clk + 1, 106 sc_scale[clk]); 107 } 108 #endif 109 110 /* now setup sysctl */ 111 if (sysctl_createv(NULL, 0, NULL, 112 &me, 113 CTLFLAG_READWRITE, CTLTYPE_NODE, "loongson", NULL, NULL, 114 0, NULL, 0, CTL_MACHDEP, CTL_CREATE, CTL_EOL) != 0) 115 aprint_error("couldn't create 'loongson' node\n"); 116 117 if (sysctl_createv(NULL, 0, NULL, 118 &freq, 119 CTLFLAG_READWRITE, CTLTYPE_NODE, "frequency", NULL, NULL, 0, NULL, 120 0, CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL) != 0) 121 aprint_error("couldn't create 'frequency' node\n"); 122 123 if (sysctl_createv(NULL, 0, NULL, 124 &sysctl_node, 125 CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 126 CTLTYPE_INT, "target", "CPU speed", loongson_cpuspeed_temp, 127 0, NULL, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, 128 CTL_CREATE, CTL_EOL) == 0) { 129 } else 130 aprint_error("couldn't create 'target' node\n"); 131 132 if (sysctl_createv(NULL, 0, NULL, 133 &sysctl_node, 134 CTLFLAG_READWRITE, 135 CTLTYPE_INT, "current", NULL, loongson_cpuspeed_cur, 136 1, NULL, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, 137 CTL_CREATE, CTL_EOL) == 0) { 138 } else 139 aprint_error("couldn't create 'current' node\n"); 140 141 if (sysctl_createv(NULL, 0, NULL, 142 &sysctl_node, 143 CTLFLAG_READWRITE, 144 CTLTYPE_STRING, "available", NULL, loongson_cpuspeed_available, 145 2, NULL, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, 146 CTL_CREATE, CTL_EOL) == 0) { 147 } else 148 aprint_error("couldn't create 'available' node\n"); 149 150 sc_count = 0; 151 loongson_timecounter.tc_frequency = curcpu()->ci_cpu_freq / 2; 152 curcpu()->ci_cctr_freq = loongson_timecounter.tc_frequency; 153 154 sc_last = mips3_cp0_count_read(); 155 mips3_cp0_compare_write(sc_last + curcpu()->ci_cycles_per_hz); 156 157 tc_init(&loongson_timecounter); 158 159 /* 160 * Now we can enable all interrupts including hardclock(9) 161 * by CPU INT5. 162 */ 163 spl0(); 164 printf("boom\n"); 165 } 166 167 static void 168 loongson_clock_shutdown(void *cookie) 169 { 170 171 /* just in case the interrupt handler runs again after this */ 172 sc_step_wanted = 7; 173 /* set the clock to full speed */ 174 REGVAL(LS2F_CHIPCFG0) = 175 (REGVAL(LS2F_CHIPCFG0) & ~LS2FCFG_FREQSCALE_MASK) | 7; 176 } 177 178 void 179 loongson_set_speed(int speed) 180 { 181 182 if ((speed < 1) || (speed > 7)) 183 return; 184 sc_step_wanted = speed; 185 DPRINTF("%s: %d\n", __func__, speed); 186 } 187 188 /* 189 * the clock interrupt handler 190 * we don't have a CPU clock independent, high resolution counter so we're 191 * stuck with a PWM that can't count and a CP0 counter that slows down or 192 * speeds up with the actual CPU speed. In order to still get halfway 193 * accurate time we do the following: 194 * - only change CPU speed in the timer interrupt 195 * - each timer interrupt we measure how many CP0 cycles passed since last 196 * time, adjust for CPU speed since we can be sure it didn't change, use 197 * that to update a separate counter 198 * - when reading the time counter we take the number of CP0 ticks since 199 * the last timer interrupt, scale it to CPU clock, return that plus the 200 * interrupt updated counter mentioned above to get something close to 201 * CP0 running at full speed 202 * - when changing CPU speed do it as close to taking the time from CP0 as 203 * possible to keep the period of time we spend with CP0 running at the 204 * wrong frequency as short as possible - hopefully short enough to stay 205 * insignificant compared to other noise since switching speeds isn't 206 * going to happen all that often 207 */ 208 209 void 210 mips3_clockintr(struct clockframe *cf) 211 { 212 uint32_t now, diff, next, new_cnt; 213 214 /* 215 * this looks kinda funny but what we want here is this: 216 * - reading the counter and changing the CPU clock should be as 217 * close together as possible in order to remain halfway accurate 218 * - we need to use the previous sc_step in order to scale the 219 * interval passed since the last clock interrupt correctly, so 220 * we only change sc_step after doing that 221 */ 222 if (sc_step_wanted != sc_step) { 223 224 REGVAL(LS2F_CHIPCFG0) = 225 (REGVAL(LS2F_CHIPCFG0) & ~LS2FCFG_FREQSCALE_MASK) | 226 sc_step_wanted; 227 } 228 229 now = mips3_cp0_count_read(); 230 diff = now - sc_last; 231 sc_count += scale(diff, sc_step); 232 sc_last = now; 233 if (sc_step_wanted != sc_step) { 234 sc_step = sc_step_wanted; 235 curcpu()->ci_cycles_per_hz = cycles[sc_step]; 236 } 237 next = now + curcpu()->ci_cycles_per_hz; 238 curcpu()->ci_ev_count_compare.ev_count++; 239 240 mips3_cp0_compare_write(next); 241 242 /* Check for lost clock interrupts */ 243 new_cnt = mips3_cp0_count_read(); 244 245 /* 246 * Missed one or more clock interrupts, so let's start 247 * counting again from the current value. 248 */ 249 if ((next - new_cnt) & 0x80000000) { 250 251 next = new_cnt + curcpu()->ci_cycles_per_hz; 252 mips3_cp0_compare_write(next); 253 curcpu()->ci_ev_count_compare_missed.ev_count++; 254 } 255 256 hardclock(cf); 257 } 258 259 static u_int 260 get_loongson_timecount(struct timecounter *tc) 261 { 262 uint32_t now, diff; 263 264 now = mips3_cp0_count_read(); 265 diff = now - sc_last; 266 return sc_count + scale(diff, sc_step); 267 } 268 269 static int 270 loongson_cpuspeed_temp(SYSCTLFN_ARGS) 271 { 272 struct sysctlnode node = *rnode; 273 int mhz, i; 274 275 mhz = sc_scale[sc_step_wanted]; 276 277 node.sysctl_data = &mhz; 278 if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 279 int new_reg; 280 281 new_reg = *(int *)node.sysctl_data; 282 i = 1; 283 while ((i < 8) && (sc_scale[i] != new_reg)) 284 i++; 285 if (i > 7) 286 return EINVAL; 287 loongson_set_speed(i); 288 return 0; 289 } 290 return EINVAL; 291 } 292 293 static int 294 loongson_cpuspeed_cur(SYSCTLFN_ARGS) 295 { 296 struct sysctlnode node = *rnode; 297 int mhz; 298 299 mhz = sc_scale[sc_step]; 300 node.sysctl_data = &mhz; 301 return sysctl_lookup(SYSCTLFN_CALL(&node)); 302 } 303 304 static int 305 loongson_cpuspeed_available(SYSCTLFN_ARGS) 306 { 307 struct sysctlnode node = *rnode; 308 char buf[128]; 309 310 snprintf(buf, 128, "%d %d %d %d %d %d %d", sc_scale[1], 311 sc_scale[2], sc_scale[3], sc_scale[4], 312 sc_scale[5], sc_scale[6], sc_scale[7]); 313 node.sysctl_data = buf; 314 return(sysctl_lookup(SYSCTLFN_CALL(&node))); 315 } 316 317 /* 318 * Wait for at least "n" microseconds. 319 */ 320 void 321 loongson_delay(int n) 322 { 323 u_long divisor_delay; 324 uint32_t cur, last, delta, usecs; 325 326 last = mips3_cp0_count_read(); 327 delta = usecs = 0; 328 329 divisor_delay = rscale(curcpu()->ci_divisor_delay, sc_step); 330 if (divisor_delay == 0) { 331 /* 332 * Frequency values in curcpu() are not initialized. 333 * Assume faster frequency since longer delays are harmless. 334 * Note CPU_MIPS_DOUBLE_COUNT is ignored here. 335 */ 336 #define FAST_FREQ (300 * 1000 * 1000) /* fast enough? */ 337 divisor_delay = FAST_FREQ / (1000 * 1000); 338 } 339 340 while (n > usecs) { 341 cur = mips3_cp0_count_read(); 342 343 /* 344 * The MIPS3 CP0 counter always counts upto UINT32_MAX, 345 * so no need to check wrapped around case. 346 */ 347 delta += (cur - last); 348 349 last = cur; 350 351 while (delta >= divisor_delay) { 352 /* 353 * delta is not so larger than divisor_delay here, 354 * and using DIV/DIVU ops could be much slower. 355 * (though longer delay may be harmless) 356 */ 357 usecs++; 358 delta -= divisor_delay; 359 } 360 } 361 } 362 363 SYSCTL_SETUP(sysctl_ams_setup, "sysctl obio subtree setup") 364 { 365 366 sysctl_createv(NULL, 0, NULL, NULL, 367 CTLFLAG_PERMANENT, 368 CTLTYPE_NODE, "machdep", NULL, 369 NULL, 0, NULL, 0, 370 CTL_MACHDEP, CTL_EOL); 371 } 372 373 /* 374 * We assume newhz is either stathz or profhz, and that neither will 375 * change after being set up above. Could recalculate intervals here 376 * but that would be a drag. 377 */ 378 void 379 loongson_setstatclockrate(int newhz) 380 { 381 382 /* nothing we can do */ 383 } 384 385 __weak_alias(setstatclockrate, loongson_setstatclockrate); 386 __weak_alias(cpu_initclocks, loongson_initclocks); 387 __weak_alias(delay, loongson_delay); 388