/*- * Copyright (c) 2000, 2001 Michael Smith * Copyright (c) 2000 BSDi * 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. */ #include #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * A timecounter based on the free-running ACPI timer. * * Based on the i386-only mp_clock.c by . */ /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_TIMER ACPI_MODULE_NAME("TIMER") struct acpi_timer_softc { device_t dev; struct resource *reg; struct timecounter *tc; struct timecounter *old_tc; }; #define ACPI_TIMER_FREQUENCY (14318182 / 4) static void acpi_timer_identify(driver_t *driver, device_t parent); static int acpi_timer_probe(device_t dev); static int acpi_timer_attach(device_t dev); static void acpi_timer_resume_handler(struct acpi_timer_softc *, enum power_stype); static void acpi_timer_suspend_handler(struct acpi_timer_softc *, enum power_stype); static u_int acpi_timer_get_timecount(struct timecounter *tc); static int acpi_timer_sysctl_freq(SYSCTL_HANDLER_ARGS); static device_method_t acpi_timer_methods[] = { DEVMETHOD(device_identify, acpi_timer_identify), DEVMETHOD(device_probe, acpi_timer_probe), DEVMETHOD(device_attach, acpi_timer_attach), DEVMETHOD_END }; static driver_t acpi_timer_driver = { "acpi_timer", acpi_timer_methods, sizeof(struct acpi_timer_softc), }; DRIVER_MODULE(acpi_timer, acpi, acpi_timer_driver, 0, 0); MODULE_DEPEND(acpi_timer, acpi, 1, 1, 1); static struct timecounter acpi_timer_timecounter = { acpi_timer_get_timecount, /* get_timecount function */ 0, /* no poll_pps */ 0, /* no default counter_mask */ 0, /* no default frequency */ "ACPI", /* name */ 900, /* quality */ TC_FLAGS_SUSPEND_SAFE /* flags */ }; static __inline uint32_t acpi_timer_read(struct acpi_timer_softc *sc) { return (bus_read_4(sc->reg, 0)); } /* * Locate the ACPI timer using the FADT, set up and allocate the I/O resources * we will be using. */ static void acpi_timer_identify(driver_t *driver, device_t parent) { device_t dev; rman_res_t rlen, rstart; int rtype; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (acpi_disabled("timer") || (acpi_quirks & ACPI_Q_TIMER) || device_find_child(parent, "acpi_timer", DEVICE_UNIT_ANY) != NULL || AcpiGbl_FADT.PmTimerLength == 0) return_VOID; if ((dev = BUS_ADD_CHILD(parent, 2, "acpi_timer", 0)) == NULL) { device_printf(parent, "could not add acpi_timer0\n"); return_VOID; } switch (AcpiGbl_FADT.XPmTimerBlock.SpaceId) { case ACPI_ADR_SPACE_SYSTEM_MEMORY: rtype = SYS_RES_MEMORY; break; case ACPI_ADR_SPACE_SYSTEM_IO: rtype = SYS_RES_IOPORT; break; default: return_VOID; } rlen = AcpiGbl_FADT.PmTimerLength; rstart = AcpiGbl_FADT.XPmTimerBlock.Address; if (bus_set_resource(dev, rtype, 0, rstart, rlen)) device_printf(dev, "couldn't set resource (%s 0x%jx+0x%jx)\n", (rtype == SYS_RES_IOPORT) ? "port" : "mem", rstart, rlen); return_VOID; } static int acpi_timer_probe(device_t dev) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); device_set_descf(dev, "%d-bit timer at %u.%06uMHz", (AcpiGbl_FADT.Flags & ACPI_FADT_32BIT_TIMER) != 0 ? 32 : 24, ACPI_TIMER_FREQUENCY / 1000000, ACPI_TIMER_FREQUENCY % 1000000); return (BUS_PROBE_NOWILDCARD); } static int acpi_timer_attach(device_t dev) { struct acpi_timer_softc *sc = device_get_softc(dev); eventhandler_tag eh; int rtype; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc->dev = dev; switch (AcpiGbl_FADT.XPmTimerBlock.SpaceId) { case ACPI_ADR_SPACE_SYSTEM_MEMORY: rtype = SYS_RES_MEMORY; break; case ACPI_ADR_SPACE_SYSTEM_IO: rtype = SYS_RES_IOPORT; break; default: return (ENXIO); } sc->reg = bus_alloc_resource_any(dev, rtype, 0, RF_ACTIVE); if (sc->reg == NULL) return (ENXIO); /* Register resume and suspend event handlers. */ eh = EVENTHANDLER_REGISTER(power_suspend, acpi_timer_suspend_handler, sc, EVENTHANDLER_PRI_LAST); if (eh == NULL) { device_printf(dev, "failed to register suspend event handler\n"); bus_release_resource(dev, sc->reg); return (ENXIO); } if (EVENTHANDLER_REGISTER(power_resume, acpi_timer_resume_handler, sc, EVENTHANDLER_PRI_LAST) == NULL) { device_printf(dev, "failed to register resume event handler\n"); EVENTHANDLER_DEREGISTER(power_suspend, eh); bus_release_resource(dev, sc->reg); return (ENXIO); } if (AcpiGbl_FADT.Flags & ACPI_FADT_32BIT_TIMER) acpi_timer_timecounter.tc_counter_mask = 0xffffffff; else acpi_timer_timecounter.tc_counter_mask = 0x00ffffff; acpi_timer_timecounter.tc_frequency = ACPI_TIMER_FREQUENCY; acpi_timer_timecounter.tc_priv = sc; sc->tc = &acpi_timer_timecounter; tc_init(sc->tc); SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_machdep), OID_AUTO, "acpi_timer_freq", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, sc->tc, 0, acpi_timer_sysctl_freq, "I", "ACPI timer frequency"); return (0); } static void acpi_timer_resume_handler(struct acpi_timer_softc *sc, enum power_stype stype) { struct timecounter *newtc, *tc; tc = timecounter; newtc = sc->old_tc; sc->old_tc = NULL; if (newtc != NULL && tc != newtc) { if (bootverbose) device_printf(sc->dev, "restoring timecounter, %s -> %s\n", tc->tc_name, newtc->tc_name); (void)newtc->tc_get_timecount(newtc); timecounter = newtc; } } static void acpi_timer_suspend_handler(struct acpi_timer_softc *sc, enum power_stype stype) { struct timecounter *tc; tc = timecounter; sc->old_tc = NULL; if ((tc->tc_flags & TC_FLAGS_SUSPEND_SAFE) != 0) { /* * If we are using a suspend safe timecounter, don't * save/restore it across suspend/resume. */ return; } /* * Our timecounter is suspend safe, so must not be currently * active. */ MPASS(tc != sc->tc); if (bootverbose) device_printf(sc->dev, "switching timecounter, %s -> %s\n", tc->tc_name, sc->tc->tc_name); (void)acpi_timer_read(sc); (void)acpi_timer_read(sc); timecounter = sc->tc; sc->old_tc = tc; } static u_int acpi_timer_get_timecount(struct timecounter *tc) { struct acpi_timer_softc *sc = tc->tc_priv; return (acpi_timer_read(sc)); } /* * Timecounter frequency adjustment interface. */ static int acpi_timer_sysctl_freq(SYSCTL_HANDLER_ARGS) { struct timecounter *tc = arg1; int error; u_int freq; freq = tc->tc_frequency; error = sysctl_handle_int(oidp, &freq, 0, req); if (error == 0 && req->newptr != NULL) { tc->tc_frequency = freq; } return (error); }