summaryrefslogtreecommitdiffstats
path: root/sys/x86/isa/atrtc.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/x86/isa/atrtc.c')
-rw-r--r--sys/x86/isa/atrtc.c331
1 files changed, 331 insertions, 0 deletions
diff --git a/sys/x86/isa/atrtc.c b/sys/x86/isa/atrtc.c
new file mode 100644
index 0000000..777c720
--- /dev/null
+++ b/sys/x86/isa/atrtc.c
@@ -0,0 +1,331 @@
+/*-
+ * Copyright (c) 2008 Poul-Henning Kamp
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_isa.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/clock.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <isa/rtc.h>
+#ifdef DEV_ISA
+#include <isa/isareg.h>
+#include <isa/isavar.h>
+#endif
+
+#define RTC_LOCK mtx_lock_spin(&clock_lock)
+#define RTC_UNLOCK mtx_unlock_spin(&clock_lock)
+
+int atrtcclock_disable = 0;
+
+static int rtc_reg = -1;
+static u_char rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
+static u_char rtc_statusb = RTCSB_24HR;
+
+/*
+ * RTC support routines
+ */
+
+int
+rtcin(int reg)
+{
+ u_char val;
+
+ RTC_LOCK;
+ if (rtc_reg != reg) {
+ inb(0x84);
+ outb(IO_RTC, reg);
+ rtc_reg = reg;
+ inb(0x84);
+ }
+ val = inb(IO_RTC + 1);
+ RTC_UNLOCK;
+ return (val);
+}
+
+void
+writertc(int reg, u_char val)
+{
+
+ RTC_LOCK;
+ if (rtc_reg != reg) {
+ inb(0x84);
+ outb(IO_RTC, reg);
+ rtc_reg = reg;
+ inb(0x84);
+ }
+ outb(IO_RTC + 1, val);
+ inb(0x84);
+ RTC_UNLOCK;
+}
+
+static __inline int
+readrtc(int port)
+{
+ return(bcd2bin(rtcin(port)));
+}
+
+void
+atrtc_start(void)
+{
+
+ writertc(RTC_STATUSA, rtc_statusa);
+ writertc(RTC_STATUSB, RTCSB_24HR);
+}
+
+void
+atrtc_rate(unsigned rate)
+{
+
+ rtc_statusa = RTCSA_DIVIDER | rate;
+ writertc(RTC_STATUSA, rtc_statusa);
+}
+
+void
+atrtc_enable_intr(void)
+{
+
+ rtc_statusb |= RTCSB_PINTR;
+ writertc(RTC_STATUSB, rtc_statusb);
+ rtcin(RTC_INTR);
+}
+
+void
+atrtc_restore(void)
+{
+
+ /* Restore all of the RTC's "status" (actually, control) registers. */
+ rtcin(RTC_STATUSA); /* dummy to get rtc_reg set */
+ writertc(RTC_STATUSB, RTCSB_24HR);
+ writertc(RTC_STATUSA, rtc_statusa);
+ writertc(RTC_STATUSB, rtc_statusb);
+ rtcin(RTC_INTR);
+}
+
+int
+atrtc_setup_clock(void)
+{
+ int diag;
+
+ if (atrtcclock_disable)
+ return (0);
+
+ diag = rtcin(RTC_DIAG);
+ if (diag != 0) {
+ printf("RTC BIOS diagnostic error %b\n",
+ diag, RTCDG_BITS);
+ return (0);
+ }
+
+ stathz = RTC_NOPROFRATE;
+ profhz = RTC_PROFRATE;
+
+ return (1);
+}
+
+/**********************************************************************
+ * RTC driver for subr_rtc
+ */
+
+#include "clock_if.h"
+
+#include <sys/rman.h>
+
+struct atrtc_softc {
+ int port_rid, intr_rid;
+ struct resource *port_res;
+ struct resource *intr_res;
+};
+
+/*
+ * Attach to the ISA PnP descriptors for the timer and realtime clock.
+ */
+static struct isa_pnp_id atrtc_ids[] = {
+ { 0x000bd041 /* PNP0B00 */, "AT realtime clock" },
+ { 0 }
+};
+
+static int
+atrtc_probe(device_t dev)
+{
+ int result;
+
+ device_set_desc(dev, "AT Real Time Clock");
+ result = ISA_PNP_PROBE(device_get_parent(dev), dev, atrtc_ids);
+ /* ENXIO if wrong PnP-ID, ENOENT ifno PnP-ID, zero if good PnP-iD */
+ if (result != ENOENT)
+ return(result);
+ /* All PC's have an RTC, and we're hosed without it, so... */
+ return (BUS_PROBE_LOW_PRIORITY);
+}
+
+static int
+atrtc_attach(device_t dev)
+{
+ struct atrtc_softc *sc;
+ int i;
+
+ /*
+ * Not that we need them or anything, but grab our resources
+ * so they show up, correctly attributed, in the big picture.
+ */
+
+ sc = device_get_softc(dev);
+ if (!(sc->port_res = bus_alloc_resource(dev, SYS_RES_IOPORT,
+ &sc->port_rid, IO_RTC, IO_RTC + 1, 2, RF_ACTIVE)))
+ device_printf(dev,"Warning: Couldn't map I/O.\n");
+ if (!(sc->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ,
+ &sc->intr_rid, 8, 8, 1, RF_ACTIVE)))
+ device_printf(dev,"Warning: Couldn't map Interrupt.\n");
+ clock_register(dev, 1000000);
+ if (resource_int_value("atrtc", 0, "clock", &i) == 0 && i == 0)
+ atrtcclock_disable = 1;
+ return(0);
+}
+
+static int
+atrtc_resume(device_t dev)
+{
+
+ atrtc_restore();
+ return(0);
+}
+
+static int
+atrtc_settime(device_t dev __unused, struct timespec *ts)
+{
+ struct clocktime ct;
+
+ clock_ts_to_ct(ts, &ct);
+
+ /* Disable RTC updates and interrupts. */
+ writertc(RTC_STATUSB, RTCSB_HALT | RTCSB_24HR);
+
+ writertc(RTC_SEC, bin2bcd(ct.sec)); /* Write back Seconds */
+ writertc(RTC_MIN, bin2bcd(ct.min)); /* Write back Minutes */
+ writertc(RTC_HRS, bin2bcd(ct.hour)); /* Write back Hours */
+
+ writertc(RTC_WDAY, ct.dow + 1); /* Write back Weekday */
+ writertc(RTC_DAY, bin2bcd(ct.day)); /* Write back Day */
+ writertc(RTC_MONTH, bin2bcd(ct.mon)); /* Write back Month */
+ writertc(RTC_YEAR, bin2bcd(ct.year % 100)); /* Write back Year */
+#ifdef USE_RTC_CENTURY
+ writertc(RTC_CENTURY, bin2bcd(ct.year / 100)); /* ... and Century */
+#endif
+
+ /* Reenable RTC updates and interrupts. */
+ writertc(RTC_STATUSB, rtc_statusb);
+ rtcin(RTC_INTR);
+ return (0);
+}
+
+static int
+atrtc_gettime(device_t dev, struct timespec *ts)
+{
+ struct clocktime ct;
+ int s;
+
+ /* Look if we have a RTC present and the time is valid */
+ if (!(rtcin(RTC_STATUSD) & RTCSD_PWR)) {
+ device_printf(dev, "WARNING: Battery failure indication\n");
+ return (EINVAL);
+ }
+
+ /* wait for time update to complete */
+ /* If RTCSA_TUP is zero, we have at least 244us before next update */
+ s = splhigh();
+ while (rtcin(RTC_STATUSA) & RTCSA_TUP) {
+ splx(s);
+ s = splhigh();
+ }
+ ct.nsec = 0;
+ ct.sec = readrtc(RTC_SEC);
+ ct.min = readrtc(RTC_MIN);
+ ct.hour = readrtc(RTC_HRS);
+ ct.day = readrtc(RTC_DAY);
+ ct.dow = readrtc(RTC_WDAY) - 1;
+ ct.mon = readrtc(RTC_MONTH);
+ ct.year = readrtc(RTC_YEAR);
+#ifdef USE_RTC_CENTURY
+ ct.year += readrtc(RTC_CENTURY) * 100;
+#else
+ ct.year += 2000;
+#endif
+ /* Set dow = -1 because some clocks don't set it correctly. */
+ ct.dow = -1;
+ return (clock_ct_to_ts(&ct, ts));
+}
+
+static device_method_t atrtc_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, atrtc_probe),
+ DEVMETHOD(device_attach, atrtc_attach),
+ DEVMETHOD(device_detach, bus_generic_detach),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ /* XXX stop statclock? */
+ DEVMETHOD(device_resume, atrtc_resume),
+
+ /* clock interface */
+ DEVMETHOD(clock_gettime, atrtc_gettime),
+ DEVMETHOD(clock_settime, atrtc_settime),
+
+ { 0, 0 }
+};
+
+static driver_t atrtc_driver = {
+ "atrtc",
+ atrtc_methods,
+ sizeof(struct atrtc_softc),
+};
+
+static devclass_t atrtc_devclass;
+
+DRIVER_MODULE(atrtc, isa, atrtc_driver, atrtc_devclass, 0, 0);
+DRIVER_MODULE(atrtc, acpi, atrtc_driver, atrtc_devclass, 0, 0);
+
+#include "opt_ddb.h"
+#ifdef DDB
+#include <ddb/ddb.h>
+
+DB_SHOW_COMMAND(rtc, rtc)
+{
+ printf("%02x/%02x/%02x %02x:%02x:%02x, A = %02x, B = %02x, C = %02x\n",
+ rtcin(RTC_YEAR), rtcin(RTC_MONTH), rtcin(RTC_DAY),
+ rtcin(RTC_HRS), rtcin(RTC_MIN), rtcin(RTC_SEC),
+ rtcin(RTC_STATUSA), rtcin(RTC_STATUSB), rtcin(RTC_INTR));
+}
+#endif /* DDB */
OpenPOWER on IntegriCloud