Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 7 Oct 2012 01:58:33 +0000 (UTC)
From:      Warner Losh <imp@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r241307 - head/sys/arm/at91
Message-ID:  <201210070158.q971wXQk089972@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: imp
Date: Sun Oct  7 01:58:32 2012
New Revision: 241307
URL: http://svn.freebsd.org/changeset/base/241307

Log:
  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

Modified:
  head/sys/arm/at91/at91_rtc.c
  head/sys/arm/at91/at91_rtcreg.h

Modified: head/sys/arm/at91/at91_rtc.c
==============================================================================
--- head/sys/arm/at91/at91_rtc.c	Sat Oct  6 22:14:19 2012	(r241306)
+++ head/sys/arm/at91/at91_rtc.c	Sun Oct  7 01:58:32 2012	(r241307)
@@ -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 ti
 	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 ti
 {
 	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 = {

Modified: head/sys/arm/at91/at91_rtcreg.h
==============================================================================
--- head/sys/arm/at91/at91_rtcreg.h	Sat Oct  6 22:14:19 2012	(r241306)
+++ head/sys/arm/at91/at91_rtcreg.h	Sun Oct  7 01:58:32 2012	(r241307)
@@ -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 */



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201210070158.q971wXQk089972>