#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/clockintr.h>
#include <sys/device.h>
#include <sys/timetc.h>
#include <dev/clock_subr.h>
#include <sh/clock.h>
#include <sh/trap.h>
#include <sh/rtcreg.h>
#include <sh/tmureg.h>
#include <machine/intr.h>
#define NWDOG 0
#define MINYEAR 2002
#define SH_RTC_CLOCK 16384
struct {
uint32_t hz_cnt;
uint32_t cpucycle_1us;
uint32_t tmuclk;
struct rtc_ops rtc;
int rtc_initialized;
uint32_t pclock;
uint32_t cpuclock;
int flags;
struct timecounter tc;
} sh_clock = {
#ifdef PCLOCK
.pclock = PCLOCK,
#endif
.rtc = {
.init = sh_rtc_init,
.get = sh_rtc_get,
.set = sh_rtc_set
}
};
uint32_t maxwdog;
int sh3_clock_intr(void *);
int sh4_clock_intr(void *);
u_int sh_timecounter_get(struct timecounter *);
#define TMU_START(x) \
do { \
_reg_bclr_1(SH_(TSTR), TSTR_STR##x); \
_reg_write_4(SH_(TCNT ## x), 0xffffffff); \
_reg_bset_1(SH_(TSTR), TSTR_STR##x); \
} while (0)
#define TMU_ELAPSED(x) \
(0xffffffff - _reg_read_4(SH_(TCNT ## x)))
void
sh_clock_init(int flags, struct rtc_ops *rtc)
{
uint32_t s, t0, cnt_1s;
sh_clock.flags = flags;
if (rtc != NULL)
sh_clock.rtc = *rtc;
_reg_write_2(SH_(TCR0), 0);
_reg_write_2(SH_(TCR1), 0);
_reg_write_2(SH_(TCR2), 0);
_reg_write_1(SH_(RCR1), 0);
_reg_write_1(SH_(TSTR), 0);
if (sh_clock.flags & SH_CLOCK_NORTC) {
_reg_write_2(SH_(TCR0), TCR_TPSC_P16);
sh_clock.tmuclk = sh_clock.pclock / 16;
} else {
_reg_write_2(SH_(TCR0),
CPU_IS_SH3 ? SH3_TCR_TPSC_RTC : SH4_TCR_TPSC_RTC);
sh_clock.tmuclk = SH_RTC_CLOCK;
_reg_bset_1(SH_(RCR2), SH_RCR2_ENABLE);
}
s = _cpu_exception_suspend();
_cpu_spin(1);
TMU_START(0);
_cpu_spin(10000000);
t0 = TMU_ELAPSED(0);
_cpu_exception_resume(s);
sh_clock.cpucycle_1us = (sh_clock.tmuclk * 10) / t0;
cnt_1s = ((uint64_t)sh_clock.tmuclk * 10000000 * 10 + t0 / 2) / t0;
if (CPU_IS_SH4)
sh_clock.cpuclock = cnt_1s / 2;
else
sh_clock.cpuclock = cnt_1s;
if (sh_clock.pclock == 0) {
uint32_t t1;
_reg_write_2(SH_(TCR1), TCR_TPSC_P4);
s = _cpu_exception_suspend();
_cpu_spin(1);
TMU_START(0);
TMU_START(1);
_cpu_spin(cnt_1s);
t0 = TMU_ELAPSED(0);
t1 = TMU_ELAPSED(1);
_cpu_exception_resume(s);
sh_clock.pclock =
((uint64_t)t1 * 4 * SH_RTC_CLOCK + t0 / 2) / t0;
}
_reg_write_1(SH_(TSTR), 0);
#undef TMU_START
#undef TMU_ELAPSED
}
int
sh_clock_get_cpuclock(void)
{
return (sh_clock.cpuclock);
}
int
sh_clock_get_pclock(void)
{
return (sh_clock.pclock);
}
void
setstatclockrate(int newhz)
{
}
u_int
sh_timecounter_get(struct timecounter *tc)
{
return 0xffffffff - _reg_read_4(SH_(TCNT2));
}
void
delay(int n)
{
_cpu_spin(sh_clock.cpucycle_1us * n);
}
extern todr_chip_handle_t todr_handle;
struct todr_chip_handle rtc_todr;
int
rtc_gettime(struct todr_chip_handle *handle, struct timeval *tv)
{
struct clock_ymdhms dt;
sh_clock.rtc.get(sh_clock.rtc._cookie, tv->tv_sec, &dt);
tv->tv_sec = clock_ymdhms_to_secs(&dt);
tv->tv_usec = 0;
return 0;
}
int
rtc_settime(struct todr_chip_handle *handle, struct timeval *tv)
{
struct clock_ymdhms dt;
clock_secs_to_ymdhms(tv->tv_sec, &dt);
sh_clock.rtc.set(sh_clock.rtc._cookie, &dt);
return 0;
}
void
cpu_initclocks(void)
{
if (sh_clock.pclock == 0)
panic("No PCLOCK information.");
tick = 1000000 / hz;
tick_nsec = 1000000000 / hz;
stathz = hz;
profhz = stathz;
}
void
cpu_startclock(void)
{
clockintr_cpu_init(NULL);
_reg_bclr_1(SH_(TSTR), TSTR_STR0);
if (sh_clock.flags & SH_CLOCK_NORTC) {
_reg_write_2(SH_(TCR0), TCR_UNIE | TCR_TPSC_P16);
} else {
_reg_write_2(SH_(TCR0), TCR_UNIE |
(CPU_IS_SH3 ? SH3_TCR_TPSC_RTC : SH4_TCR_TPSC_RTC));
}
sh_clock.hz_cnt = sh_clock.tmuclk / hz - 1;
_reg_write_4(SH_(TCOR0), sh_clock.hz_cnt);
_reg_write_4(SH_(TCNT0), sh_clock.hz_cnt);
intc_intr_establish(SH_INTEVT_TMU0_TUNI0, IST_LEVEL, IPL_CLOCK,
CPU_IS_SH3 ? sh3_clock_intr : sh4_clock_intr, NULL, "clock");
_reg_bset_1(SH_(TSTR), TSTR_STR0);
_reg_write_2(SH_(TCR1), TCR_UNIE | TCR_TPSC_P4);
_reg_write_4(SH_(TCOR1), 0xffffffff);
_reg_write_2(SH_(TCR2), TCR_TPSC_P4);
_reg_write_4(SH_(TCOR2), 0xffffffff);
_reg_bset_1(SH_(TSTR), TSTR_STR2);
sh_clock.tc.tc_get_timecount = sh_timecounter_get;
sh_clock.tc.tc_frequency = sh_clock.pclock / 4;
sh_clock.tc.tc_name = "tmu_pclock_4";
sh_clock.tc.tc_quality = 100;
sh_clock.tc.tc_counter_mask = 0xffffffff;
tc_init(&sh_clock.tc);
if (sh_clock.rtc.init != NULL)
sh_clock.rtc.init(sh_clock.rtc._cookie);
rtc_todr.todr_gettime = rtc_gettime;
rtc_todr.todr_settime = rtc_settime;
todr_handle = &rtc_todr;
}
#ifdef SH3
int
sh3_clock_intr(void *arg)
{
#if (NWDOG > 0)
uint32_t i;
i = (uint32_t)SHREG_WTCNT_R;
if (i > maxwdog)
maxwdog = i;
wdog_wr_cnt(0);
#endif
_reg_bclr_2(SH3_TCR0, TCR_UNF);
clockintr_dispatch(arg);
return (1);
}
#endif
#ifdef SH4
int
sh4_clock_intr(void *arg)
{
#if (NWDOG > 0)
uint32_t i;
i = (uint32_t)SHREG_WTCNT_R;
if (i > maxwdog)
maxwdog = i;
wdog_wr_cnt(0);
#endif
_reg_bclr_2(SH4_TCR0, TCR_UNF);
clockintr_dispatch(arg);
return (1);
}
#endif
void
sh_rtc_init(void *cookie)
{
_reg_write_1(SH_(RCR2), SH_RCR2_ENABLE | SH_RCR2_START);
}
void
sh_rtc_get(void *cookie, time_t base, struct clock_ymdhms *dt)
{
int retry = 8;
_reg_bclr_1(SH_(RCR1), SH_RCR1_CIE);
do {
uint8_t r = _reg_read_1(SH_(RCR1));
r &= ~SH_RCR1_CF;
r |= SH_RCR1_AF;
_reg_write_1(SH_(RCR1), r);
if (CPU_IS_SH3)
dt->dt_year = FROMBCD(_reg_read_1(SH3_RYRCNT));
else
dt->dt_year = FROMBCD(_reg_read_2(SH4_RYRCNT) & 0x00ff);
#define RTCGET(x, y) dt->dt_ ## x = FROMBCD(_reg_read_1(SH_(R ## y ## CNT)))
RTCGET(mon, MON);
RTCGET(wday, WK);
RTCGET(day, DAY);
RTCGET(hour, HR);
RTCGET(min, MIN);
RTCGET(sec, SEC);
#undef RTCGET
} while ((_reg_read_1(SH_(RCR1)) & SH_RCR1_CF) && --retry > 0);
if (retry == 0) {
printf("rtc_gettime: couldn't read RTC register.\n");
memset(dt, 0, sizeof(*dt));
return;
}
dt->dt_year = (dt->dt_year % 100) + 1900;
if (dt->dt_year < 1970)
dt->dt_year += 100;
}
void
sh_rtc_set(void *cookie, struct clock_ymdhms *dt)
{
uint8_t r;
r = _reg_read_1(SH_(RCR2));
r |= SH_RCR2_RESET;
r &= ~SH_RCR2_START;
_reg_write_1(SH_(RCR2), r);
if (CPU_IS_SH3)
_reg_write_1(SH3_RYRCNT, TOBCD(dt->dt_year % 100));
else
_reg_write_2(SH4_RYRCNT, TOBCD(dt->dt_year % 100));
#define RTCSET(x, y) _reg_write_1(SH_(R ## x ## CNT), TOBCD(dt->dt_ ## y))
RTCSET(MON, mon);
RTCSET(WK, wday);
RTCSET(DAY, day);
RTCSET(HR, hour);
RTCSET(MIN, min);
RTCSET(SEC, sec);
#undef RTCSET
_reg_write_1(SH_(RCR2), r | SH_RCR2_START);
}