1 /* $NetBSD: clock.c,v 1.60 2026/03/28 22:19:32 thorpej Exp $ */ 2 3 /* 4 * Copyright (c) 1988 University of Utah. 5 * Copyright (c) 1982, 1990 The Regents of the University of California. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * the Systems Programming Group of the University of Utah Computer 10 * Science Department. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 * 36 * from: Utah $Hdr: clock.c 1.18 91/01/21$ 37 * 38 * @(#)clock.c 7.6 (Berkeley) 5/7/91 39 */ 40 41 #include <sys/cdefs.h> 42 __KERNEL_RCSID(0, "$NetBSD: clock.c,v 1.60 2026/03/28 22:19:32 thorpej Exp $"); 43 44 #include <sys/param.h> 45 #include <sys/kernel.h> 46 #include <sys/device.h> 47 #include <sys/systm.h> 48 #include <sys/timetc.h> 49 #include <machine/psl.h> 50 #include <machine/cpu.h> 51 #include <amiga/amiga/device.h> 52 #include <amiga/amiga/custom.h> 53 #include <amiga/amiga/cia.h> 54 #ifdef DRACO 55 #include <amiga/amiga/drcustom.h> 56 #include <m68k/include/asm_single.h> 57 #endif 58 #include <amiga/dev/rtc.h> 59 #include <amiga/dev/zbusvar.h> 60 61 #if defined(PROF) && defined(PROFTIMER) 62 #include <sys/PROF.h> 63 #endif 64 65 /* 66 * Machine-dependent clock routines. 67 * 68 * Startrtclock restarts the real-time clock, which provides 69 * hardclock interrupts to kern_clock.c. 70 * 71 * Inittodr initializes the time of day hardware which provides 72 * date functions. 73 * 74 * Resettodr restores the time of day hardware after a time change. 75 * 76 * A note on the real-time clock: 77 * We actually load the clock with amiga_clk_interval-1 instead of amiga_clk_interval. 78 * This is because the counter decrements to zero after N+1 enabled clock 79 * periods where N is the value loaded into the counter. 80 */ 81 82 int clockmatch(device_t, cfdata_t, void *); 83 void clockattach(device_t, device_t, void *); 84 void cpu_initclocks(void); 85 static void calibrate_delay(device_t); 86 87 /* the clocks run at NTSC: 715.909kHz or PAL: 709.379kHz. 88 We're using a 100 Hz clock. */ 89 int amiga_clk_interval; 90 int eclockfreq; 91 struct CIA *clockcia; 92 93 static u_int clk_getcounter(struct timecounter *); 94 95 static struct timecounter clk_timecounter = { 96 .tc_get_timecount = clk_getcounter, 97 .tc_counter_mask = ~0u, 98 .tc_quality = 100, 99 }; 100 101 CFATTACH_DECL_NEW(clock, 0, 102 clockmatch, clockattach, NULL, NULL); 103 104 int 105 clockmatch(device_t parent, cfdata_t cf, void *aux) 106 { 107 if (matchname("clock", aux)) 108 return(1); 109 return(0); 110 } 111 112 /* 113 * Start the real-time clock. 114 */ 115 void 116 clockattach(device_t parent, device_t self, void *aux) 117 { 118 const char *clockchip; 119 unsigned short interval; 120 int chipfreq; 121 #ifdef DRACO 122 u_char dracorev; 123 #endif 124 125 if (eclockfreq == 0) 126 eclockfreq = 715909; /* guess NTSC */ 127 128 chipfreq = eclockfreq; 129 130 #ifdef DRACO 131 dracorev = is_draco(); 132 if (dracorev >= 4) { 133 chipfreq = eclockfreq / 7; 134 clockchip = "QuickLogic"; 135 } else if (dracorev) { 136 clockcia = (struct CIA *)CIAAbase; 137 clockchip = "CIA A"; 138 } else 139 #endif 140 { 141 clockcia = (struct CIA *)CIABbase; 142 clockchip = "CIA B"; 143 } 144 145 /* round nearest to mitigate clock drift for PAL */ 146 amiga_clk_interval = chipfreq / hz; 147 if (chipfreq % hz >= hz / 2) 148 amiga_clk_interval++; 149 150 if (self != NULL) { /* real autoconfig? */ 151 printf(": %s system hz %d hardware hz %d\n", clockchip, hz, 152 chipfreq); 153 154 clk_timecounter.tc_name = clockchip; 155 clk_timecounter.tc_frequency = chipfreq; 156 tc_init(&clk_timecounter); 157 } 158 159 #ifdef DRACO 160 if (dracorev >= 4) { 161 /* 162 * can't preload anything beforehand, timer is free_running; 163 * but need this for delay calibration. 164 */ 165 166 draco_ioct->io_timerlo = amiga_clk_interval & 0xff; 167 draco_ioct->io_timerhi = amiga_clk_interval >> 8; 168 169 calibrate_delay(self); 170 171 return; 172 } 173 #endif 174 /* 175 * stop timer A 176 */ 177 clockcia->cra = clockcia->cra & 0xc0; 178 clockcia->icr = 1 << 0; /* disable timer A interrupt */ 179 interval = clockcia->icr; /* and make sure it's clear */ 180 181 /* 182 * load interval into registers. 183 * the clocks run at NTSC: 715.909kHz or PAL: 709.379kHz 184 */ 185 interval = amiga_clk_interval - 1; 186 187 /* 188 * order of setting is important ! 189 */ 190 clockcia->talo = interval & 0xff; 191 clockcia->tahi = interval >> 8; 192 /* 193 * start timer A in continuous mode 194 */ 195 clockcia->cra = (clockcia->cra & 0xc0) | 1; 196 197 calibrate_delay(self); 198 } 199 200 void 201 cpu_initclocks(void) 202 { 203 #ifdef DRACO 204 unsigned char dracorev; 205 dracorev = is_draco(); 206 if (dracorev >= 4) { 207 draco_ioct->io_timerlo = amiga_clk_interval & 0xFF; 208 draco_ioct->io_timerhi = amiga_clk_interval >> 8; 209 draco_ioct->io_timerrst = 0; /* any value resets */ 210 single_inst_bset_b(draco_ioct->io_status2, DRSTAT2_TMRINTENA); 211 212 return; 213 } 214 #endif 215 /* 216 * enable interrupts for timer A 217 */ 218 clockcia->icr = (1<<7) | (1<<0); 219 220 /* 221 * start timer A in continuous shot mode 222 */ 223 clockcia->cra = (clockcia->cra & 0xc0) | 1; 224 225 /* 226 * and globally enable interrupts for ciab 227 */ 228 #ifdef DRACO 229 if (dracorev) /* we use cia a on DraCo */ 230 single_inst_bset_b(*draco_intena, DRIRQ_INT2); 231 else 232 #endif 233 custom.intena = INTF_SETCLR | INTF_EXTER; 234 235 } 236 237 void 238 setstatclockrate(int hertz) 239 { 240 } 241 242 /* 243 * Returns ticks since last recorded clock "tick" 244 * (i.e. clock interrupt). 245 */ 246 static u_int 247 clk_gettick(void) 248 { 249 u_int interval; 250 u_char hi, hi2, lo; 251 252 #ifdef DRACO 253 if (is_draco() >= 4) { 254 hi2 = draco_ioct->io_chiprev; /* latch timer */ 255 hi = draco_ioct->io_timerhi; 256 lo = draco_ioct->io_timerlo; 257 interval = ((hi<<8) | lo); 258 if (interval > amiga_clk_interval) /* timer underflow */ 259 interval = 65536 + amiga_clk_interval - interval; 260 else 261 interval = amiga_clk_interval - interval; 262 263 } else 264 #endif 265 { 266 hi = clockcia->tahi; 267 lo = clockcia->talo; 268 hi2 = clockcia->tahi; 269 if (hi != hi2) { 270 lo = clockcia->talo; 271 hi = hi2; 272 } 273 274 interval = (amiga_clk_interval - 1) - ((hi<<8) | lo); 275 276 /* 277 * should read ICR and if there's an int pending, adjust 278 * interval. However, since reading ICR clears the interrupt, 279 * we'd lose a hardclock int, and this is not tolerable. 280 */ 281 } 282 283 return interval; 284 } 285 286 static u_int 287 clk_getcounter(struct timecounter *tc) 288 { 289 static int prev_hardclock; 290 static u_int prev_counter; 291 int cur_hardclock; 292 u_int counter; 293 294 do { 295 cur_hardclock = getticks(); 296 counter = clk_gettick(); 297 } while (cur_hardclock != getticks()); 298 299 /* 300 * Handle the situation of a wrapped interval counter, while 301 * the hardclock() interrupt was not yet executed to update 302 * hardclock_ticks. 303 */ 304 if (cur_hardclock < prev_hardclock) 305 cur_hardclock = prev_hardclock; 306 if (counter < prev_counter && cur_hardclock == prev_hardclock) 307 cur_hardclock++; 308 309 prev_hardclock = cur_hardclock; 310 prev_counter = counter; 311 312 return cur_hardclock * amiga_clk_interval + counter; 313 } 314 315 /* 316 * Calibrate delay loop. 317 * We use two iterations because we don't have enough bits to do a factor of 318 * 8 with better than 1%. 319 * 320 * XXX Note that we MUST stay below 1 tick if using clk_gettick(), even for 321 * underestimated values of delay_divisor. 322 * 323 * XXX the "ns" below is only correct for a shift of 10 bits, and even then 324 * off by 2.4% 325 */ 326 __CTASSERT(DELAY_MAGSHIFT == 10); /* XXX assumptions made here */ 327 static void 328 calibrate_delay(device_t self) 329 { 330 unsigned long t1, t2; 331 332 if (self) 333 printf("Calibrating delay loop... "); 334 335 do { 336 t1 = clk_gettick(); 337 delay(1024); 338 t2 = clk_gettick(); 339 } while (t2 <= t1); 340 t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz); 341 delay_divisor = (delay_divisor * t2 + 1023) >> 10; 342 #ifdef DEBUG 343 if (self) 344 printf("\ndiff %ld us, new divisor %u/1024 us\n", t2, 345 delay_divisor); 346 do { 347 t1 = clk_gettick(); 348 delay(1024); 349 t2 = clk_gettick(); 350 } while (t2 <= t1); 351 t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz); 352 delay_divisor = (delay_divisor * t2 + 1023) >> 10; 353 if (self) 354 printf("diff %ld us, new divisor %u/1024 us\n", t2, 355 delay_divisor); 356 #endif 357 do { 358 t1 = clk_gettick(); 359 delay(1024); 360 t2 = clk_gettick(); 361 } while (t2 <= t1); 362 t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz); 363 delay_divisor = (delay_divisor * t2 + 1023) >> 10; 364 #ifdef DEBUG 365 if (self) 366 printf("diff %ld us, new divisor ", t2); 367 #endif 368 if (self) 369 printf("%u/1024 us\n", delay_divisor); 370 } 371 372 #if notyet 373 374 /* implement this later. I'd suggest using both timers in CIA-A, they're 375 not yet used. */ 376 377 #include "clock.h" 378 #if NCLOCK > 0 379 /* 380 * /dev/clock: mappable high resolution timer. 381 * 382 * This code implements a 32-bit recycling counter (with a 4 usec period) 383 * using timers 2 & 3 on the 6840 clock chip. The counter can be mapped 384 * RO into a user's address space to achieve low overhead (no system calls), 385 * high-precision timing. 386 * 387 * Note that timer 3 is also used for the high precision profiling timer 388 * (PROFTIMER code above). Care should be taken when both uses are 389 * configured as only a token effort is made to avoid conflicting use. 390 */ 391 #include <sys/proc.h> 392 #include <sys/resourcevar.h> 393 #include <sys/ioctl.h> 394 #include <uvm/uvm_extern.h> 395 #include <amiga/amiga/clockioctl.h> 396 #include <sys/specdev.h> 397 #include <sys/vnode.h> 398 #include <sys/mman.h> 399 400 int clockon = 0; /* non-zero if high-res timer enabled */ 401 #ifdef PROFTIMER 402 int profprocs = 0; /* # of procs using profiling timer */ 403 #endif 404 #ifdef DEBUG 405 int clockdebug = 0; 406 #endif 407 408 /*ARGSUSED*/ 409 int 410 clockopen(dev_t dev, int flags) 411 { 412 #ifdef PROFTIMER 413 #ifdef PROF 414 /* 415 * Kernel profiling enabled, give up. 416 */ 417 if (profiling) 418 return(EBUSY); 419 #endif 420 /* 421 * If any user processes are profiling, give up. 422 */ 423 if (profprocs) 424 return(EBUSY); 425 #endif 426 if (!clockon) { 427 startclock(); 428 clockon++; 429 } 430 return(0); 431 } 432 433 /*ARGSUSED*/ 434 int 435 clockclose(dev_t dev, int flags) 436 { 437 (void) clockunmmap(dev, (void *)0, curproc); /* XXX */ 438 stopclock(); 439 clockon = 0; 440 return(0); 441 } 442 443 /*ARGSUSED*/ 444 int 445 clockioctl(dev_t dev, u_long cmd, void *data, int flag, struct proc *p) 446 { 447 int error = 0; 448 449 switch (cmd) { 450 451 case CLOCKMAP: 452 error = clockmmap(dev, (void **)data, p); 453 break; 454 455 case CLOCKUNMAP: 456 error = clockunmmap(dev, *(void **)data, p); 457 break; 458 459 case CLOCKGETRES: 460 *(int *)data = CLK_RESOLUTION; 461 break; 462 463 default: 464 error = EINVAL; 465 break; 466 } 467 return(error); 468 } 469 470 /*ARGSUSED*/ 471 void 472 clockmap(dev_t dev, int off, int prot) 473 { 474 return MD_BTOP(off + (INTIOBASE+CLKBASE+CLKSR-1)); 475 } 476 477 int 478 clockmmap(dev_t dev, void **addrp, struct proc *p) 479 { 480 int error; 481 struct vnode vn; 482 struct specinfo si; 483 int flags; 484 485 flags = MAP_FILE|MAP_SHARED; 486 if (*addrp) 487 flags |= MAP_FIXED; 488 else 489 *addrp = (void *)0x1000000; /* XXX */ 490 vn.v_type = VCHR; /* XXX */ 491 vn.v_specinfo = &si; /* XXX */ 492 vn.v_rdev = dev; /* XXX */ 493 error = vm_mmap(&p->p_vmspace->vm_map, (vm_offset_t *)addrp, 494 PAGE_SIZE, VM_PROT_ALL, flags, (void *)&vn, 0); 495 return(error); 496 } 497 498 int 499 clockunmmap(dev_t dev, void *addr, struct proc *p) 500 { 501 int rv; 502 503 if (addr == 0) 504 return(EINVAL); /* XXX: how do we deal with this? */ 505 uvm_deallocate(p->p_vmspace->vm_map, (vm_offset_t)addr, PAGE_SIZE); 506 return 0; 507 } 508 509 void 510 startclock(void) 511 { 512 register struct clkreg *clk = (struct clkreg *)clkstd[0]; 513 514 clk->clk_msb2 = -1; clk->clk_lsb2 = -1; 515 clk->clk_msb3 = -1; clk->clk_lsb3 = -1; 516 517 clk->clk_cr2 = CLK_CR3; 518 clk->clk_cr3 = CLK_OENAB|CLK_8BIT; 519 clk->clk_cr2 = CLK_CR1; 520 clk->clk_cr1 = CLK_IENAB; 521 } 522 523 void 524 stopclock(void) 525 { 526 register struct clkreg *clk = (struct clkreg *)clkstd[0]; 527 528 clk->clk_cr2 = CLK_CR3; 529 clk->clk_cr3 = 0; 530 clk->clk_cr2 = CLK_CR1; 531 clk->clk_cr1 = CLK_IENAB; 532 } 533 #endif 534 535 #endif 536 537 538 #ifdef PROFTIMER 539 /* 540 * This code allows the amiga kernel to use one of the extra timers on 541 * the clock chip for profiling, instead of the regular system timer. 542 * The advantage of this is that the profiling timer can be turned up to 543 * a higher interrupt rate, giving finer resolution timing. The profclock 544 * routine is called from the lev6intr in locore, and is a specialized 545 * routine that calls addupc. The overhead then is far less than if 546 * hardclock/softclock was called. Further, the context switch code in 547 * locore has been changed to turn the profile clock on/off when switching 548 * into/out of a process that is profiling (startprofclock/stopprofclock). 549 * This reduces the impact of the profiling clock on other users, and might 550 * possibly increase the accuracy of the profiling. 551 */ 552 int profint = PRF_INTERVAL; /* Clock ticks between interrupts */ 553 int profscale = 0; /* Scale factor from sys clock to prof clock */ 554 char profon = 0; /* Is profiling clock on? */ 555 556 /* profon values - do not change, locore.s assumes these values */ 557 #define PRF_NONE 0x00 558 #define PRF_USER 0x01 559 #define PRF_KERNEL 0x80 560 561 void 562 initprofclock(void) 563 { 564 #if NCLOCK > 0 565 struct proc *p = curproc; /* XXX */ 566 567 /* 568 * If the high-res timer is running, force profiling off. 569 * Unfortunately, this gets reflected back to the user not as 570 * an error but as a lack of results. 571 */ 572 if (clockon) { 573 p->p_stats->p_prof.pr_scale = 0; 574 return; 575 } 576 /* 577 * Keep track of the number of user processes that are profiling 578 * by checking the scale value. 579 * 580 * XXX: this all assumes that the profiling code is well behaved; 581 * i.e. profil() is called once per process with pcscale non-zero 582 * to turn it on, and once with pcscale zero to turn it off. 583 * Also assumes you don't do any forks or execs. Oh well, there 584 * is always adb... 585 */ 586 if (p->p_stats->p_prof.pr_scale) 587 profprocs++; 588 else 589 profprocs--; 590 #endif 591 /* 592 * The profile interrupt interval must be an even divisor 593 * of the amiga_clk_interval so that scaling from a system clock 594 * tick to a profile clock tick is possible using integer math. 595 */ 596 if (profint > amiga_clk_interval || (amiga_clk_interval % profint) != 0) 597 profint = amiga_clk_interval; 598 profscale = amiga_clk_interval / profint; 599 } 600 601 void 602 startprofclock(void) 603 { 604 unsigned short interval; 605 606 /* stop timer B */ 607 clockcia->crb = clockcia->crb & 0xc0; 608 609 /* load interval into registers. 610 the clocks run at NTSC: 715.909kHz or PAL: 709.379kHz */ 611 612 interval = profint - 1; 613 614 /* order of setting is important ! */ 615 clockcia->tblo = interval & 0xff; 616 clockcia->tbhi = interval >> 8; 617 618 /* enable interrupts for timer B */ 619 clockcia->icr = (1<<7) | (1<<1); 620 621 /* start timer B in continuous shot mode */ 622 clockcia->crb = (clockcia->crb & 0xc0) | 1; 623 } 624 625 void 626 stopprofclock(void) 627 { 628 /* stop timer B */ 629 clockcia->crb = clockcia->crb & 0xc0; 630 } 631 632 #ifdef PROF 633 /* 634 * profclock() is expanded in line in lev6intr() unless profiling kernel. 635 * Assumes it is called with clock interrupts blocked. 636 */ 637 void 638 profclock(void *pc, int ps) 639 { 640 /* 641 * Came from user mode. 642 * If this process is being profiled record the tick. 643 */ 644 if (USERMODE(ps)) { 645 if (p->p_stats.p_prof.pr_scale) 646 addupc(pc, &curproc->p_stats.p_prof, 1); 647 } 648 /* 649 * Came from kernel (supervisor) mode. 650 * If we are profiling the kernel, record the tick. 651 */ 652 else if (profiling < 2) { 653 register int s = pc - s_lowpc; 654 655 if (s < s_textsize) 656 kcount[s / (HISTFRACTION * sizeof (*kcount))]++; 657 } 658 /* 659 * Kernel profiling was on but has been disabled. 660 * Mark as no longer profiling kernel and if all profiling done, 661 * disable the clock. 662 */ 663 if (profiling && (profon & PRF_KERNEL)) { 664 profon &= ~PRF_KERNEL; 665 if (profon == PRF_NONE) 666 stopprofclock(); 667 } 668 } 669 #endif 670 #endif 671