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>