#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rtc.c,v 1.3 2025/09/07 21:45:13 thorpej Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/cpu.h>
#include <dev/clock_subr.h>
#include <evbmips/sbmips/swarm.h>
#include <evbmips/sbmips/systemsw.h>
#include <mips/locore.h>
#include <mips/sibyte/dev/sbsmbusvar.h>
#include <dev/smbus/m41t81reg.h>
#include <dev/smbus/x1241reg.h>
struct rtc_softc {
device_t sc_dev;
int sc_smbus_chan;
int sc_smbus_addr;
int sc_type;
struct todr_chip_handle sc_ct;
};
#define SMB_1BYTE_ADDR 1
#define SMB_2BYTE_ADDR 2
static int xirtc_match(device_t, cfdata_t , void *);
static void xirtc_attach(device_t, device_t, void *);
static int xirtc_gettime(todr_chip_handle_t, struct clock_ymdhms *);
static int xirtc_settime(todr_chip_handle_t, struct clock_ymdhms *);
static int strtc_match(device_t, cfdata_t , void *);
static void strtc_attach(device_t, device_t, void *);
static int strtc_gettime(todr_chip_handle_t, struct clock_ymdhms *);
static int strtc_settime(todr_chip_handle_t, struct clock_ymdhms *);
static void rtc_cal_timer(void);
static void time_smbus_init(int);
static int time_waitready(int);
static int time_readrtc(int, int, int, int);
static int time_writertc(int, int, int, int, int);
#define WRITERTC(sc, dev, val) \
time_writertc((sc)->sc_smbus_chan, (sc)->sc_smbus_addr, (dev), (sc)->sc_type, (val))
#define READRTC(sc, dev) \
time_readrtc((sc)->sc_smbus_chan, (sc)->sc_smbus_addr, (dev), (sc)->sc_type)
CFATTACH_DECL_NEW(xirtc, sizeof(struct rtc_softc),
xirtc_match, xirtc_attach, NULL, NULL);
CFATTACH_DECL_NEW(m41t81rtc, sizeof(struct rtc_softc),
strtc_match, strtc_attach, NULL, NULL);
static int rtcfound = 0;
struct rtc_softc *the_rtc;
static int
xirtc_match(device_t parent, cfdata_t cf, void *aux)
{
struct smbus_attach_args *sa = aux;
int ret;
time_smbus_init(sa->sa_interface);
if ((sa->sa_interface != X1241_SMBUS_CHAN) ||
(sa->sa_device != X1241_RTC_SLAVEADDR))
return (0);
ret = time_readrtc(sa->sa_interface, sa->sa_device, SMB_2BYTE_ADDR, X1241REG_SC);
if (ret < 0)
return (0);
return (!rtcfound);
}
static void
xirtc_attach(device_t parent, device_t self, void *aux)
{
struct smbus_attach_args *sa = aux;
struct rtc_softc *sc = device_private(self);
rtcfound = 1;
the_rtc = sc;
sc->sc_dev = self;
sc->sc_smbus_chan = sa->sa_interface;
sc->sc_smbus_addr = sa->sa_device;
sc->sc_type = SMB_2BYTE_ADDR;
sc->sc_ct.todr_dev = self;
sc->sc_ct.todr_settime_ymdhms = xirtc_settime;
sc->sc_ct.todr_gettime_ymdhms = xirtc_gettime;
todr_attach(&sc->sc_ct);
aprint_normal("\n");
rtc_cal_timer();
}
static int
xirtc_settime(todr_chip_handle_t handle, struct clock_ymdhms *ymdhms)
{
struct rtc_softc *sc = device_private(handle->todr_dev);
uint8_t year, y2k;
time_smbus_init(sc->sc_smbus_chan);
WRITERTC(sc, X1241REG_SR, X1241REG_SR_WEL);
WRITERTC(sc, X1241REG_SR, X1241REG_SR_WEL | X1241REG_SR_RWEL);
WRITERTC(sc, X1241REG_HR, bintobcd(ymdhms->dt_hour) | X1241REG_HR_MIL);
WRITERTC(sc, X1241REG_MN, bintobcd(ymdhms->dt_min));
WRITERTC(sc, X1241REG_SC, bintobcd(ymdhms->dt_sec));
y2k = (ymdhms->dt_year >= 2000) ? 0x20 : 0x19;
year = ymdhms->dt_year % 100;
WRITERTC(sc, X1241REG_MO, bintobcd(ymdhms->dt_mon));
WRITERTC(sc, X1241REG_DT, bintobcd(ymdhms->dt_day));
WRITERTC(sc, X1241REG_YR, bintobcd(year));
WRITERTC(sc, X1241REG_Y2K, bintobcd(y2k));
WRITERTC(sc, X1241REG_SR, 0);
return (0);
}
static int
xirtc_gettime(todr_chip_handle_t handle, struct clock_ymdhms *ymdhms)
{
struct rtc_softc *sc = device_private(handle->todr_dev);
uint8_t hour, year, y2k;
uint8_t status;
time_smbus_init(sc->sc_smbus_chan);
ymdhms->dt_day = bcdtobin(READRTC(sc, X1241REG_DT));
ymdhms->dt_mon = bcdtobin(READRTC(sc, X1241REG_MO));
year = READRTC(sc, X1241REG_YR);
y2k = READRTC(sc, X1241REG_Y2K);
ymdhms->dt_year = bcdtobin(y2k) * 100 + bcdtobin(year);
ymdhms->dt_sec = bcdtobin(READRTC(sc, X1241REG_SC));
ymdhms->dt_min = bcdtobin(READRTC(sc, X1241REG_MN));
hour = READRTC(sc, X1241REG_HR);
ymdhms->dt_hour = bcdtobin(hour & ~X1241REG_HR_MIL);
status = READRTC(sc, X1241REG_SR);
if (status & X1241REG_SR_RTCF) {
printf("%s: battery has failed, clock setting is not accurate\n",
device_xname(sc->sc_dev));
return (EIO);
}
return (0);
}
static int
strtc_match(device_t parent, cfdata_t cf, void *aux)
{
struct smbus_attach_args *sa = aux;
int ret;
if ((sa->sa_interface != M41T81_SMBUS_CHAN) ||
(sa->sa_device != M41T81_SLAVEADDR))
return (0);
time_smbus_init(sa->sa_interface);
ret = time_readrtc(sa->sa_interface, sa->sa_device, SMB_1BYTE_ADDR, M41T81_SEC);
if (ret < 0)
return (0);
return (!rtcfound);
}
static void
strtc_attach(device_t parent, device_t self, void *aux)
{
struct smbus_attach_args *sa = aux;
struct rtc_softc *sc = device_private(self);
rtcfound = 1;
the_rtc = sc;
sc->sc_dev = self;
sc->sc_smbus_chan = sa->sa_interface;
sc->sc_smbus_addr = sa->sa_device;
sc->sc_type = SMB_1BYTE_ADDR;
sc->sc_ct.todr_dev = self;
sc->sc_ct.todr_settime_ymdhms = strtc_settime;
sc->sc_ct.todr_gettime_ymdhms = strtc_gettime;
todr_attach(&sc->sc_ct);
aprint_normal("\n");
rtc_cal_timer();
}
static int
strtc_settime(todr_chip_handle_t handle, struct clock_ymdhms *ymdhms)
{
struct rtc_softc *sc = device_private(handle->todr_dev);
uint8_t hour;
time_smbus_init(sc->sc_smbus_chan);
hour = bintobcd(ymdhms->dt_hour);
if (ymdhms->dt_year >= 2000)
hour |= M41T81_HOUR_CB | M41T81_HOUR_CEB;
WRITERTC(sc, M41T81_SEC, bintobcd(ymdhms->dt_sec));
WRITERTC(sc, M41T81_MIN, bintobcd(ymdhms->dt_min));
WRITERTC(sc, M41T81_HOUR, hour);
WRITERTC(sc, M41T81_DATE, bintobcd(ymdhms->dt_day));
WRITERTC(sc, M41T81_MON, bintobcd(ymdhms->dt_mon));
WRITERTC(sc, M41T81_YEAR, bintobcd(ymdhms->dt_year % 100));
return (0);
}
static int
strtc_gettime(todr_chip_handle_t handle, struct clock_ymdhms *ymdhms)
{
struct rtc_softc *sc = device_private(handle->todr_dev);
uint8_t hour;
time_smbus_init(sc->sc_smbus_chan);
ymdhms->dt_sec = bcdtobin(READRTC(sc, M41T81_SEC));
ymdhms->dt_min = bcdtobin(READRTC(sc, M41T81_MIN));
hour = READRTC(sc, M41T81_HOUR & M41T81_HOUR_MASK);
ymdhms->dt_hour = bcdtobin(hour & M41T81_HOUR_MASK);
ymdhms->dt_day = bcdtobin(READRTC(sc, M41T81_DATE));
ymdhms->dt_mon = bcdtobin(READRTC(sc, M41T81_MON));
ymdhms->dt_year = 1900 + bcdtobin(READRTC(sc, M41T81_YEAR));
if (hour & M41T81_HOUR_CB)
ymdhms->dt_year += 100;
return (0);
}
#define NITERS 3
#define RTC_SECONDS(rtc) bcdtobin(READRTC((rtc), X1241REG_SC))
static void
rtc_cal_timer(void)
{
uint32_t ctrdiff[NITERS], startctr, endctr;
int sec, lastsec, i;
if (rtcfound == 0) {
printf("rtc_cal_timer before rtc attached\n");
return;
}
return;
printf("%s: calibrating CPU clock", device_xname(the_rtc->sc_dev));
time_smbus_init(the_rtc->sc_smbus_chan);
sec = RTC_SECONDS(the_rtc);
endctr = mips3_cp0_count_read();
for (i = 0; i < NITERS; i++) {
int diff;
again:
lastsec = sec;
startctr = endctr;
do {
sec = RTC_SECONDS(the_rtc);
} while (lastsec == sec);
endctr = mips3_cp0_count_read();
diff = sec - lastsec;
if (diff < 0)
diff += 60;
if (diff > 1)
goto again;
if (endctr < startctr)
ctrdiff[i] = 0xffffffff - startctr + endctr;
else
ctrdiff[i] = endctr - startctr;
}
printf("\n");
curcpu()->ci_cpu_freq = ((ctrdiff[1] + ctrdiff[2]) / 2);
curcpu()->ci_divisor_delay = curcpu()->ci_cpu_freq / 1000000;
curcpu()->ci_cycles_per_hz = curcpu()->ci_cpu_freq / hz;
printf("%s: timer calibration: %lu cycles/sec [(%u, %u)]\n",
device_xname(the_rtc->sc_dev), curcpu()->ci_cpu_freq,
ctrdiff[1], ctrdiff[2]);
}
#undef RTC_SECONDS
#include <mips/sibyte/include/sb1250_regs.h>
#include <mips/sibyte/include/sb1250_smbus.h>
#define READ_REG(rp) mips3_ld((register_t)(MIPS_PHYS_TO_KSEG1(rp)))
#define WRITE_REG(rp, val) mips3_sd((register_t)(MIPS_PHYS_TO_KSEG1(rp)), (val))
static void
time_smbus_init(int chan)
{
uint32_t reg;
reg = A_SMB_REGISTER(chan, R_SMB_FREQ);
WRITE_REG(reg, K_SMB_FREQ_100KHZ);
reg = A_SMB_REGISTER(chan, R_SMB_CONTROL);
WRITE_REG(reg, 0);
}
static int
time_waitready(int chan)
{
uint32_t reg;
uint64_t status;
reg = A_SMB_REGISTER(chan, R_SMB_STATUS);
for (;;) {
status = READ_REG(reg);
if (status & M_SMB_BUSY)
continue;
break;
}
if (status & M_SMB_ERROR) {
WRITE_REG(reg, (status & M_SMB_ERROR));
return (-1);
}
return (0);
}
static int
time_readrtc(int chan, int slaveaddr, int devaddr, int type)
{
uint32_t reg;
int err;
if (time_waitready(chan) < 0)
return (-1);
if (type == SMB_2BYTE_ADDR) {
reg = A_SMB_REGISTER(chan, R_SMB_CMD);
WRITE_REG(reg, (devaddr >> 8) & 0x7);
reg = A_SMB_REGISTER(chan, R_SMB_DATA);
WRITE_REG(reg, (devaddr & 0xff) & 0xff);
} else {
reg = A_SMB_REGISTER(chan, R_SMB_CMD);
WRITE_REG(reg, devaddr & 0xff);
}
reg = A_SMB_REGISTER(chan, R_SMB_START);
if (type == SMB_2BYTE_ADDR)
WRITE_REG(reg, V_SMB_TT(K_SMB_TT_WR2BYTE) | V_SMB_ADDR(slaveaddr));
else
WRITE_REG(reg, V_SMB_TT(K_SMB_TT_WR1BYTE) | V_SMB_ADDR(slaveaddr));
err = time_waitready(chan);
if (err < 0)
return (err);
WRITE_REG(reg, V_SMB_TT(K_SMB_TT_RD1BYTE) | V_SMB_ADDR(slaveaddr));
err = time_waitready(chan);
if (err < 0)
return (err);
reg = A_SMB_REGISTER(chan, R_SMB_DATA);
err = READ_REG(reg);
return (err & 0xff);
}
static int
time_writertc(int chan, int slaveaddr, int devaddr, int type, int b)
{
uint32_t reg;
int err, timer;
if (time_waitready(chan) < 0)
return (-1);
reg = A_SMB_REGISTER(chan, R_SMB_CMD);
if (type == SMB_2BYTE_ADDR)
WRITE_REG(reg, (devaddr >> 8) & 0x7);
else
WRITE_REG(reg, devaddr & 0xff);
reg = A_SMB_REGISTER(chan, R_SMB_DATA);
if (type == SMB_2BYTE_ADDR)
WRITE_REG(reg, (devaddr & 0xff) | ((b & 0xff) << 8));
else
WRITE_REG(reg, b & 0xff);
reg = A_SMB_REGISTER(chan, R_SMB_START);
if (type == SMB_2BYTE_ADDR)
WRITE_REG(reg, V_SMB_TT(K_SMB_TT_WR3BYTE) | V_SMB_ADDR(slaveaddr));
else
WRITE_REG(reg, V_SMB_TT(K_SMB_TT_WR2BYTE) | V_SMB_ADDR(slaveaddr));
err = time_waitready(chan);
if (err < 0)
return (err);
err = -1;
timer = 100000000;
while (timer-- > 0) {
WRITE_REG(reg, V_SMB_TT(K_SMB_TT_RD1BYTE) | V_SMB_ADDR(slaveaddr));
err = time_waitready(chan);
if (err == 0)
break;
}
return (err);
}