summaryrefslogtreecommitdiffstats
path: root/sys/arm/at91
diff options
context:
space:
mode:
authorimp <imp@FreeBSD.org>2012-10-07 01:58:32 +0000
committerimp <imp@FreeBSD.org>2012-10-07 01:58:32 +0000
commit1a947de79c3c25fc6fa4c7c03377661e6481e1bd (patch)
treea13afb6824f3bfa3c346e416cd9f6a458e9a61f0 /sys/arm/at91
parent562e7d7aa0e0f73154a53f5d449bdfa0d3b83416 (diff)
downloadFreeBSD-src-1a947de79c3c25fc6fa4c7c03377661e6481e1bd.zip
FreeBSD-src-1a947de79c3c25fc6fa4c7c03377661e6481e1bd.tar.gz
Use the RTC unit to get the time. This works on all known AT91SAM9*
processors, either on reboot or after power down with battery backup. However, the AT91RM9200 RTC always resets on reboot making it just about useless at the moment (if we support a low-power mode or an extended sleep mode, it might become useful). Submitted by: Ian Lepore
Diffstat (limited to 'sys/arm/at91')
-rw-r--r--sys/arm/at91/at91_rtc.c138
-rw-r--r--sys/arm/at91/at91_rtcreg.h23
2 files changed, 136 insertions, 25 deletions
diff --git a/sys/arm/at91/at91_rtc.c b/sys/arm/at91/at91_rtc.c
index 934fd22..71fb367 100644
--- a/sys/arm/at91/at91_rtc.c
+++ b/sys/arm/at91/at91_rtc.c
@@ -1,5 +1,6 @@
/*-
* Copyright (c) 2006 M. Warner Losh. All rights reserved.
+ * Copyright (c) 2012 Ian Lepore. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -23,6 +24,18 @@
* SUCH DAMAGE.
*/
+/*
+ * Driver for the at91 on-chip realtime clock.
+ *
+ * This driver does not currently support alarms, just date and time.
+ *
+ * Note that on an rm9200 the RTC is not your typical battery-driven clock that
+ * keeps time while the system is powered down. In fact, it doesn't even
+ * survive a chip reset to keep time across a reboot. About the only thing it
+ * might be good for is keeping time while the cpu clock is turned off for power
+ * savings. On later chips, a battery backup feature is available.
+ */
+
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
@@ -39,11 +52,20 @@ __FBSDID("$FreeBSD$");
#include <sys/mutex.h>
#include <sys/rman.h>
#include <machine/bus.h>
+#include <machine/cpu.h>
#include <arm/at91/at91_rtcreg.h>
#include "clock_if.h"
+/*
+ * The driver has all the infrastructure to use interrupts but doesn't actually
+ * have any need to do so right now. There's a non-zero cost for installing the
+ * handler because the RTC shares the system interrupt (IRQ 1), and thus will
+ * get called a lot for no reason at all.
+ */
+#define AT91_RTC_USE_INTERRUPTS_NOT
+
struct at91_rtc_softc
{
device_t dev; /* Myself */
@@ -81,12 +103,32 @@ static devclass_t at91_rtc_devclass;
static int at91_rtc_probe(device_t dev);
static int at91_rtc_attach(device_t dev);
static int at91_rtc_detach(device_t dev);
-static int at91_rtc_intr(void *);
/* helper routines */
static int at91_rtc_activate(device_t dev);
static void at91_rtc_deactivate(device_t dev);
+#ifdef AT91_RTC_USE_INTERRUPTS
+static int
+at91_rtc_intr(void *xsc)
+{
+ struct at91_rtc_softc *sc;
+ uint32_t status;
+
+ sc = xsc;
+ /* Must clear the status bits after reading them to re-arm. */
+ status = RD4(sc, RTC_SR);
+ WR4(sc, RTC_SCCR, status);
+ if (status == 0)
+ return;
+ AT91_RTC_LOCK(sc);
+ /* Do something here */
+ AT91_RTC_UNLOCK(sc);
+ wakeup(sc);
+ return (FILTER_HANDLED);
+}
+#endif
+
static int
at91_rtc_probe(device_t dev)
{
@@ -108,15 +150,35 @@ at91_rtc_attach(device_t dev)
AT91_RTC_LOCK_INIT(sc);
/*
- * Activate the interrupt, but disable all interrupts in the hardware
+ * Disable all interrupts in the hardware.
+ * Clear all bits in the status register.
+ * Set 24-hour-clock mode.
*/
WR4(sc, RTC_IDR, 0xffffffff);
+ WR4(sc, RTC_SCCR, 0x1f);
+ WR4(sc, RTC_MR, 0);
+
+#ifdef AT91_RTC_USE_INTERRUPTS
err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC,
at91_rtc_intr, NULL, sc, &sc->intrhand);
if (err) {
AT91_RTC_LOCK_DESTROY(sc);
goto out;
}
+#endif
+
+ /*
+ * Read the calendar register. If the century is 19 then the clock has
+ * never been set. Try to store an invalid value into the register,
+ * which will turn on the error bit in RTC_VER, and our getclock code
+ * knows to return EINVAL if any error bits are on.
+ */
+ if (RTC_CALR_CEN(RD4(sc, RTC_CALR)) == 19)
+ WR4(sc, RTC_CALR, 0);
+
+ /*
+ * Register as a time of day clock with 1-second resolution.
+ */
clock_register(dev, 1000000);
out:
if (err)
@@ -142,11 +204,13 @@ at91_rtc_activate(device_t dev)
RF_ACTIVE);
if (sc->mem_res == NULL)
goto errout;
+#ifdef AT91_RTC_USE_INTERRUPTS
rid = 0;
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
RF_ACTIVE | RF_SHAREABLE);
if (sc->irq_res == NULL)
goto errout;
+#endif
return (0);
errout:
at91_rtc_deactivate(dev);
@@ -159,39 +223,26 @@ at91_rtc_deactivate(device_t dev)
struct at91_rtc_softc *sc;
sc = device_get_softc(dev);
+#ifdef AT91_RTC_USE_INTERRUPTS
+ WR4(sc, RTC_IDR, 0xffffffff);
if (sc->intrhand)
bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
sc->intrhand = 0;
+#endif
bus_generic_detach(sc->dev);
if (sc->mem_res)
- bus_release_resource(dev, SYS_RES_IOPORT,
+ bus_release_resource(dev, SYS_RES_MEMORY,
rman_get_rid(sc->mem_res), sc->mem_res);
sc->mem_res = 0;
+#ifdef AT91_RTC_USE_INTERRUPTS
if (sc->irq_res)
bus_release_resource(dev, SYS_RES_IRQ,
rman_get_rid(sc->irq_res), sc->irq_res);
sc->irq_res = 0;
+#endif
return;
}
-static int
-at91_rtc_intr(void *xsc)
-{
- struct at91_rtc_softc *sc = xsc;
-#if 0
- uint32_t status;
-
- /* Reading the status also clears the interrupt */
- status = RD4(sc, RTC_SR);
- if (status == 0)
- return;
- AT91_RTC_LOCK(sc);
- AT91_RTC_UNLOCK(sc);
-#endif
- wakeup(sc);
- return (FILTER_HANDLED);
-}
-
/*
* Get the time of day clock and return it in ts.
* Return 0 on success, an error number otherwise.
@@ -204,6 +255,12 @@ at91_rtc_gettime(device_t dev, struct timespec *ts)
struct at91_rtc_softc *sc;
sc = device_get_softc(dev);
+
+ /* If the error bits are set we can't return useful values. */
+
+ if (RD4(sc, RTC_VER) & (RTC_VER_NVTIM | RTC_VER_NVCAL))
+ return EINVAL;
+
timr = RD4(sc, RTC_TIMR);
calr = RD4(sc, RTC_CALR);
ct.nsec = 0;
@@ -226,11 +283,46 @@ at91_rtc_settime(device_t dev, struct timespec *ts)
{
struct at91_rtc_softc *sc;
struct clocktime ct;
+ int rv;
sc = device_get_softc(dev);
clock_ts_to_ct(ts, &ct);
+
+ /*
+ * Can't set the clock unless a second has elapsed since we last did so.
+ */
+ while ((RD4(sc, RTC_SR) & RTC_SR_SECEV) == 0)
+ cpu_spinwait();
+
+ /*
+ * Stop the clocks for an update; wait until hardware is ready.
+ * Clear the update-ready status after it gets asserted (the manual says
+ * to do this before updating the value registers).
+ */
+ WR4(sc, RTC_CR, RTC_CR_UPDCAL | RTC_CR_UPDTIM);
+ while ((RD4(sc, RTC_SR) & RTC_SR_ACKUPD) == 0)
+ cpu_spinwait();
+ WR4(sc, RTC_SCCR, RTC_SR_ACKUPD);
+
+ /*
+ * Set the values in the hardware, then check whether the hardware was
+ * happy with them so we can return the correct status.
+ */
WR4(sc, RTC_TIMR, RTC_TIMR_MK(ct.hour, ct.min, ct.sec));
- WR4(sc, RTC_CALR, RTC_CALR_MK(ct.year, ct.mon, ct.day, ct.dow));
+ WR4(sc, RTC_CALR, RTC_CALR_MK(ct.year, ct.mon, ct.day, ct.dow+1));
+
+ if (RD4(sc, RTC_VER) & (RTC_VER_NVTIM | RTC_VER_NVCAL))
+ rv = EINVAL;
+ else
+ rv = 0;
+
+ /*
+ * Restart the clocks (turn off the update bits).
+ * Clear the second-event bit (because the manual says to).
+ */
+ WR4(sc, RTC_CR, RD4(sc, RTC_CR) & ~(RTC_CR_UPDCAL | RTC_CR_UPDTIM));
+ WR4(sc, RTC_SCCR, RTC_SR_SECEV);
+
return (0);
}
@@ -244,7 +336,7 @@ static device_method_t at91_rtc_methods[] = {
DEVMETHOD(clock_gettime, at91_rtc_gettime),
DEVMETHOD(clock_settime, at91_rtc_settime),
- { 0, 0 }
+ DEVMETHOD_END
};
static driver_t at91_rtc_driver = {
diff --git a/sys/arm/at91/at91_rtcreg.h b/sys/arm/at91/at91_rtcreg.h
index 1a02a64..923db0a 100644
--- a/sys/arm/at91/at91_rtcreg.h
+++ b/sys/arm/at91/at91_rtcreg.h
@@ -42,6 +42,10 @@
#define RTC_IMR 0x28 /* RTC Interrupt Mask Register */
#define RTC_VER 0x2c /* RTC Valid Entry Register */
+/* CR */
+#define RTC_CR_UPDTIM (0x1u << 0) /* Request update of time register */
+#define RTC_CR_UPDCAL (0x1u << 1) /* Request update of calendar reg. */
+
/* TIMR */
#define RTC_TIMR_SEC_M 0x7fUL
#define RTC_TIMR_SEC_S 0
@@ -71,14 +75,29 @@
#define RTC_CALR_DOW_M 0x00d0000UL
#define RTC_CALR_DOW_S 21
#define RTC_CALR_DOW(x) FROMBCD(((x) & RTC_CALR_DOW_M) >> RTC_CALR_DOW_S)
-#define RTC_CALR_DAY_M 0x3f00000UL
+#define RTC_CALR_DAY_M 0x3f000000UL
#define RTC_CALR_DAY_S 24
#define RTC_CALR_DAY(x) FROMBCD(((x) & RTC_CALR_DAY_M) >> RTC_CALR_DAY_S)
#define RTC_CALR_MK(yr, mon, day, dow) \
- ((TOBCD((yr) / 100 + 19) << RTC_CALR_CEN_S) | \
+ ((TOBCD((yr) / 100) << RTC_CALR_CEN_S) | \
(TOBCD((yr) % 100) << RTC_CALR_YEAR_S) | \
(TOBCD(mon) << RTC_CALR_MON_S) | \
(TOBCD(dow) << RTC_CALR_DOW_S) | \
(TOBCD(day) << RTC_CALR_DAY_S))
+/* SR */
+
+#define RTC_SR_ACKUPD (0x1u << 0) /* Acknowledge for Update */
+#define RTC_SR_ALARM (0x1u << 1) /* Alarm Flag */
+#define RTC_SR_SECEV (0x1u << 2) /* Second Event */
+#define RTC_SR_TIMEV (0x1u << 3) /* Time Event */
+#define RTC_SR_CALEV (0x1u << 4) /* Calendar event */
+
+/* VER */
+
+#define RTC_VER_NVTIM (0x1 << 0) /* Non-valid time */
+#define RTC_VER_NVCAL (0x1 << 1) /* Non-valid calendar */
+#define RTC_VER_NVTIMALR (0x1 << 2) /* Non-valid time alarm */
+#define RTC_VER_NVCALALR (0x1 << 3) /* Non-valid calendar alarm */
+
#endif /* ARM_AT91_AT91_RTCREG_H */
OpenPOWER on IntegriCloud