#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: clock.c,v 1.60 2026/03/28 22:19:32 thorpej Exp $");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/timetc.h>
#include <machine/psl.h>
#include <machine/cpu.h>
#include <amiga/amiga/device.h>
#include <amiga/amiga/custom.h>
#include <amiga/amiga/cia.h>
#ifdef DRACO
#include <amiga/amiga/drcustom.h>
#include <m68k/include/asm_single.h>
#endif
#include <amiga/dev/rtc.h>
#include <amiga/dev/zbusvar.h>
#if defined(PROF) && defined(PROFTIMER)
#include <sys/PROF.h>
#endif
int clockmatch(device_t, cfdata_t, void *);
void clockattach(device_t, device_t, void *);
void cpu_initclocks(void);
static void calibrate_delay(device_t);
int amiga_clk_interval;
int eclockfreq;
struct CIA *clockcia;
static u_int clk_getcounter(struct timecounter *);
static struct timecounter clk_timecounter = {
.tc_get_timecount = clk_getcounter,
.tc_counter_mask = ~0u,
.tc_quality = 100,
};
CFATTACH_DECL_NEW(clock, 0,
clockmatch, clockattach, NULL, NULL);
int
clockmatch(device_t parent, cfdata_t cf, void *aux)
{
if (matchname("clock", aux))
return(1);
return(0);
}
void
clockattach(device_t parent, device_t self, void *aux)
{
const char *clockchip;
unsigned short interval;
int chipfreq;
#ifdef DRACO
u_char dracorev;
#endif
if (eclockfreq == 0)
eclockfreq = 715909;
chipfreq = eclockfreq;
#ifdef DRACO
dracorev = is_draco();
if (dracorev >= 4) {
chipfreq = eclockfreq / 7;
clockchip = "QuickLogic";
} else if (dracorev) {
clockcia = (struct CIA *)CIAAbase;
clockchip = "CIA A";
} else
#endif
{
clockcia = (struct CIA *)CIABbase;
clockchip = "CIA B";
}
amiga_clk_interval = chipfreq / hz;
if (chipfreq % hz >= hz / 2)
amiga_clk_interval++;
if (self != NULL) {
printf(": %s system hz %d hardware hz %d\n", clockchip, hz,
chipfreq);
clk_timecounter.tc_name = clockchip;
clk_timecounter.tc_frequency = chipfreq;
tc_init(&clk_timecounter);
}
#ifdef DRACO
if (dracorev >= 4) {
draco_ioct->io_timerlo = amiga_clk_interval & 0xff;
draco_ioct->io_timerhi = amiga_clk_interval >> 8;
calibrate_delay(self);
return;
}
#endif
clockcia->cra = clockcia->cra & 0xc0;
clockcia->icr = 1 << 0;
interval = clockcia->icr;
interval = amiga_clk_interval - 1;
clockcia->talo = interval & 0xff;
clockcia->tahi = interval >> 8;
clockcia->cra = (clockcia->cra & 0xc0) | 1;
calibrate_delay(self);
}
void
cpu_initclocks(void)
{
#ifdef DRACO
unsigned char dracorev;
dracorev = is_draco();
if (dracorev >= 4) {
draco_ioct->io_timerlo = amiga_clk_interval & 0xFF;
draco_ioct->io_timerhi = amiga_clk_interval >> 8;
draco_ioct->io_timerrst = 0;
single_inst_bset_b(draco_ioct->io_status2, DRSTAT2_TMRINTENA);
return;
}
#endif
clockcia->icr = (1<<7) | (1<<0);
clockcia->cra = (clockcia->cra & 0xc0) | 1;
#ifdef DRACO
if (dracorev)
single_inst_bset_b(*draco_intena, DRIRQ_INT2);
else
#endif
custom.intena = INTF_SETCLR | INTF_EXTER;
}
void
setstatclockrate(int hertz)
{
}
static u_int
clk_gettick(void)
{
u_int interval;
u_char hi, hi2, lo;
#ifdef DRACO
if (is_draco() >= 4) {
hi2 = draco_ioct->io_chiprev;
hi = draco_ioct->io_timerhi;
lo = draco_ioct->io_timerlo;
interval = ((hi<<8) | lo);
if (interval > amiga_clk_interval)
interval = 65536 + amiga_clk_interval - interval;
else
interval = amiga_clk_interval - interval;
} else
#endif
{
hi = clockcia->tahi;
lo = clockcia->talo;
hi2 = clockcia->tahi;
if (hi != hi2) {
lo = clockcia->talo;
hi = hi2;
}
interval = (amiga_clk_interval - 1) - ((hi<<8) | lo);
}
return interval;
}
static u_int
clk_getcounter(struct timecounter *tc)
{
static int prev_hardclock;
static u_int prev_counter;
int cur_hardclock;
u_int counter;
do {
cur_hardclock = getticks();
counter = clk_gettick();
} while (cur_hardclock != getticks());
if (cur_hardclock < prev_hardclock)
cur_hardclock = prev_hardclock;
if (counter < prev_counter && cur_hardclock == prev_hardclock)
cur_hardclock++;
prev_hardclock = cur_hardclock;
prev_counter = counter;
return cur_hardclock * amiga_clk_interval + counter;
}
__CTASSERT(DELAY_MAGSHIFT == 10);
static void
calibrate_delay(device_t self)
{
unsigned long t1, t2;
if (self)
printf("Calibrating delay loop... ");
do {
t1 = clk_gettick();
delay(1024);
t2 = clk_gettick();
} while (t2 <= t1);
t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz);
delay_divisor = (delay_divisor * t2 + 1023) >> 10;
#ifdef DEBUG
if (self)
printf("\ndiff %ld us, new divisor %u/1024 us\n", t2,
delay_divisor);
do {
t1 = clk_gettick();
delay(1024);
t2 = clk_gettick();
} while (t2 <= t1);
t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz);
delay_divisor = (delay_divisor * t2 + 1023) >> 10;
if (self)
printf("diff %ld us, new divisor %u/1024 us\n", t2,
delay_divisor);
#endif
do {
t1 = clk_gettick();
delay(1024);
t2 = clk_gettick();
} while (t2 <= t1);
t2 = ((t2 - t1) * 1000000) / (amiga_clk_interval * hz);
delay_divisor = (delay_divisor * t2 + 1023) >> 10;
#ifdef DEBUG
if (self)
printf("diff %ld us, new divisor ", t2);
#endif
if (self)
printf("%u/1024 us\n", delay_divisor);
}
#if notyet
#include "clock.h"
#if NCLOCK > 0
#include <sys/proc.h>
#include <sys/resourcevar.h>
#include <sys/ioctl.h>
#include <uvm/uvm_extern.h>
#include <amiga/amiga/clockioctl.h>
#include <sys/specdev.h>
#include <sys/vnode.h>
#include <sys/mman.h>
int clockon = 0;
#ifdef PROFTIMER
int profprocs = 0;
#endif
#ifdef DEBUG
int clockdebug = 0;
#endif
int
clockopen(dev_t dev, int flags)
{
#ifdef PROFTIMER
#ifdef PROF
if (profiling)
return(EBUSY);
#endif
if (profprocs)
return(EBUSY);
#endif
if (!clockon) {
startclock();
clockon++;
}
return(0);
}
int
clockclose(dev_t dev, int flags)
{
(void) clockunmmap(dev, (void *)0, curproc);
stopclock();
clockon = 0;
return(0);
}
int
clockioctl(dev_t dev, u_long cmd, void *data, int flag, struct proc *p)
{
int error = 0;
switch (cmd) {
case CLOCKMAP:
error = clockmmap(dev, (void **)data, p);
break;
case CLOCKUNMAP:
error = clockunmmap(dev, *(void **)data, p);
break;
case CLOCKGETRES:
*(int *)data = CLK_RESOLUTION;
break;
default:
error = EINVAL;
break;
}
return(error);
}
void
clockmap(dev_t dev, int off, int prot)
{
return MD_BTOP(off + (INTIOBASE+CLKBASE+CLKSR-1));
}
int
clockmmap(dev_t dev, void **addrp, struct proc *p)
{
int error;
struct vnode vn;
struct specinfo si;
int flags;
flags = MAP_FILE|MAP_SHARED;
if (*addrp)
flags |= MAP_FIXED;
else
*addrp = (void *)0x1000000;
vn.v_type = VCHR;
vn.v_specinfo = &si;
vn.v_rdev = dev;
error = vm_mmap(&p->p_vmspace->vm_map, (vm_offset_t *)addrp,
PAGE_SIZE, VM_PROT_ALL, flags, (void *)&vn, 0);
return(error);
}
int
clockunmmap(dev_t dev, void *addr, struct proc *p)
{
int rv;
if (addr == 0)
return(EINVAL);
uvm_deallocate(p->p_vmspace->vm_map, (vm_offset_t)addr, PAGE_SIZE);
return 0;
}
void
startclock(void)
{
register struct clkreg *clk = (struct clkreg *)clkstd[0];
clk->clk_msb2 = -1; clk->clk_lsb2 = -1;
clk->clk_msb3 = -1; clk->clk_lsb3 = -1;
clk->clk_cr2 = CLK_CR3;
clk->clk_cr3 = CLK_OENAB|CLK_8BIT;
clk->clk_cr2 = CLK_CR1;
clk->clk_cr1 = CLK_IENAB;
}
void
stopclock(void)
{
register struct clkreg *clk = (struct clkreg *)clkstd[0];
clk->clk_cr2 = CLK_CR3;
clk->clk_cr3 = 0;
clk->clk_cr2 = CLK_CR1;
clk->clk_cr1 = CLK_IENAB;
}
#endif
#endif
#ifdef PROFTIMER
int profint = PRF_INTERVAL;
int profscale = 0;
char profon = 0;
#define PRF_NONE 0x00
#define PRF_USER 0x01
#define PRF_KERNEL 0x80
void
initprofclock(void)
{
#if NCLOCK > 0
struct proc *p = curproc;
if (clockon) {
p->p_stats->p_prof.pr_scale = 0;
return;
}
if (p->p_stats->p_prof.pr_scale)
profprocs++;
else
profprocs--;
#endif
if (profint > amiga_clk_interval || (amiga_clk_interval % profint) != 0)
profint = amiga_clk_interval;
profscale = amiga_clk_interval / profint;
}
void
startprofclock(void)
{
unsigned short interval;
clockcia->crb = clockcia->crb & 0xc0;
interval = profint - 1;
clockcia->tblo = interval & 0xff;
clockcia->tbhi = interval >> 8;
clockcia->icr = (1<<7) | (1<<1);
clockcia->crb = (clockcia->crb & 0xc0) | 1;
}
void
stopprofclock(void)
{
clockcia->crb = clockcia->crb & 0xc0;
}
#ifdef PROF
void
profclock(void *pc, int ps)
{
if (USERMODE(ps)) {
if (p->p_stats.p_prof.pr_scale)
addupc(pc, &curproc->p_stats.p_prof, 1);
}
else if (profiling < 2) {
register int s = pc - s_lowpc;
if (s < s_textsize)
kcount[s / (HISTFRACTION * sizeof (*kcount))]++;
}
if (profiling && (profon & PRF_KERNEL)) {
profon &= ~PRF_KERNEL;
if (profon == PRF_NONE)
stopprofclock();
}
}
#endif
#endif