/* $NetBSD: rtc.c,v 1.2 2017/07/24 09:56:45 mrg Exp $ */
/*
* Copyright 2002 Wasabi Systems, Inc.
* All rights reserved.
*
* Written by Simon Burge for Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the NetBSD Project by
* Wasabi Systems, Inc.
* 4. The name of Wasabi Systems, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rtc.c,v 1.2 2017/07/24 09:56:45 mrg 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;
};
/* "types" for RTCs we support */
#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;
/*
* Xicor X1241 RTC support.
*/
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; /* Two-byte register addresses on the Xicor */
/* Set up MI todr(9) stuff */
sc->sc_ct.cookie = sc;
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(); /* XXX */
}
static int
xirtc_settime(todr_chip_handle_t handle, struct clock_ymdhms *ymdhms)
{
struct rtc_softc *sc = handle->cookie;
uint8_t year, y2k;
time_smbus_init(sc->sc_smbus_chan);
/* unlock writes to the CCR */
WRITERTC(sc, X1241REG_SR, X1241REG_SR_WEL);
WRITERTC(sc, X1241REG_SR, X1241REG_SR_WEL | X1241REG_SR_RWEL);
/* set the time */
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));
/* set the date */
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));
/* lock writes again */
WRITERTC(sc, X1241REG_SR, 0);
return (0);
}
static int
xirtc_gettime(todr_chip_handle_t handle, struct clock_ymdhms *ymdhms)
{
struct rtc_softc *sc = handle->cookie;
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);
}
/*
* ST M41T81 RTC support.
*/
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; /* One-byte register addresses on the ST */
/* Set up MI todr(9) stuff */
sc->sc_ct.cookie = sc;
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(); /* XXX */
}
static int
strtc_settime(todr_chip_handle_t handle, struct clock_ymdhms *ymdhms)
{
struct rtc_softc *sc = handle->cookie;
uint8_t hour;
time_smbus_init(sc->sc_smbus_chan);
hour = bintobcd(ymdhms->dt_hour);
if (ymdhms->dt_year >= 2000) /* Should be always true! */
hour |= M41T81_HOUR_CB | M41T81_HOUR_CEB;
/* set the time */
WRITERTC(sc, M41T81_SEC, bintobcd(ymdhms->dt_sec));
WRITERTC(sc, M41T81_MIN, bintobcd(ymdhms->dt_min));
WRITERTC(sc, M41T81_HOUR, hour);
/* set the date */
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 = handle->cookie;
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))
/*
* Since it takes so long to read the complete time/date values from
* the RTC over the SMBus, we only read the seconds value.
* Later versions of the SWARM will hopefully have the RTC interrupt
* attached so we can do the clock calibration much more quickly and
* with a higher resolution.
*/
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; /* XXX XXX */
printf("%s: calibrating CPU clock", device_xname(the_rtc->sc_dev));
/*
* Run the loop an extra time to wait for the second to tick over
* and to prime the cache.
*/
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;
/* Wait for the timer to tick over. */
do {
// time_smbus_init(the_rtc->sc_smbus_chan);
sec = RTC_SECONDS(the_rtc);
} while (lastsec == sec);
endctr = mips3_cp0_count_read();
diff = sec - lastsec;
if (diff < 0)
diff += 60;
/* Sometimes we appear to skip a second. Clock jitter? */
if (diff > 1)
goto again;
if (endctr < startctr)
ctrdiff[i] = 0xffffffff - startctr + endctr;
else
ctrdiff[i] = endctr - startctr;
}
printf("\n");
/* Compute the number of cycles per second. */
curcpu()->ci_cpu_freq = ((ctrdiff[1] + ctrdiff[2]) / 2);
/* Compute the delay divisor. */
curcpu()->ci_divisor_delay = curcpu()->ci_cpu_freq / 1000000;
/* Compute clock cycles per hz */
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
/* XXX eville direct-access-to-the-device code follows... */
/*
* Copyright 2000,2001
* Broadcom Corporation. All rights reserved.
*
* This software is furnished under license and may be used and copied only
* in accordance with the following terms and conditions. Subject to these
* conditions, you may download, copy, install, use, modify and distribute
* modified or unmodified copies of this software in source and/or binary
* form. No title or ownership is transferred hereby.
*
* 1) Any source code used, modified or distributed must reproduce and
* retain this copyright notice and list of conditions as they appear in
* the source file.
*
* 2) No right is granted to use any trade name, trademark, or logo of
* Broadcom Corporation. The "Broadcom Corporation" name may not be
* used to endorse or promote products derived from this software
* without the prior written permission of Broadcom Corporation.
*
* 3) THIS SOFTWARE IS PROVIDED "AS-IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL BROADCOM BE LIABLE
* FOR ANY DAMAGES WHATSOEVER, AND IN PARTICULAR, BROADCOM SHALL NOT BE
* LIABLE FOR DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE), EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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); /* not in direct mode, no interrupts, will poll */
}
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;
/*
* Make sure the bus is idle (probably should
* ignore error here)
*/
if (time_waitready(chan) < 0)
return (-1);
if (type == SMB_2BYTE_ADDR) {
/*
* Write the device address to the controller. There are two
* parts, the high part goes in the "CMD" field, and the
* low part is the data field.
*/
reg = A_SMB_REGISTER(chan, R_SMB_CMD);
WRITE_REG(reg, (devaddr >> 8) & 0x7);
/*
* Write the data to the controller
*/
reg = A_SMB_REGISTER(chan, R_SMB_DATA);
WRITE_REG(reg, (devaddr & 0xff) & 0xff);
} else { /* SMB_1BYTE_ADDR */
/*
* Write the device address to the controller.
*/
reg = A_SMB_REGISTER(chan, R_SMB_CMD);
WRITE_REG(reg, devaddr & 0xff);
}
/*
* Start the command
*/
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 /* SMB_1BYTE_ADDR */
WRITE_REG(reg, V_SMB_TT(K_SMB_TT_WR1BYTE) | V_SMB_ADDR(slaveaddr));
/*
* Wait till done
*/
err = time_waitready(chan);
if (err < 0)
return (err);
/*
* Read the data byte
*/
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;
/*
* Make sure the bus is idle (probably should
* ignore error here)
*/
if (time_waitready(chan) < 0)
return (-1);
/*
* Write the device address to the controller. There are two
* parts, the high part goes in the "CMD" field, and the
* low part is the data field.
*/
reg = A_SMB_REGISTER(chan, R_SMB_CMD);
if (type == SMB_2BYTE_ADDR)
WRITE_REG(reg, (devaddr >> 8) & 0x7);
else /* SMB_1BYTE_ADDR */
WRITE_REG(reg, devaddr & 0xff);
/*
* Write the data to the controller
*/
reg = A_SMB_REGISTER(chan, R_SMB_DATA);
if (type == SMB_2BYTE_ADDR)
WRITE_REG(reg, (devaddr & 0xff) | ((b & 0xff) << 8));
else /* SMB_1BYTE_ADDR */
WRITE_REG(reg, b & 0xff);
/*
* Start the command. Keep pounding on the device until it
* submits or the timer expires, whichever comes first. The
* datasheet says writes can take up to 10ms, so we'll give it 500.
*/
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 /* SMB_1BYTE_ADDR */
WRITE_REG(reg, V_SMB_TT(K_SMB_TT_WR2BYTE) | V_SMB_ADDR(slaveaddr));
/*
* Wait till the SMBus interface is done
*/
err = time_waitready(chan);
if (err < 0)
return (err);
/*
* Pound on the device with a current address read
* to poll for the write complete
*/
err = -1;
timer = 100000000; /* XXX */
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);
}