diff options
Diffstat (limited to 'sys/dev/acpica/acpi.c')
-rw-r--r-- | sys/dev/acpica/acpi.c | 213 |
1 files changed, 210 insertions, 3 deletions
diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c index 2bdeaf6..8564bce 100644 --- a/sys/dev/acpica/acpi.c +++ b/sys/dev/acpica/acpi.c @@ -59,6 +59,10 @@ #include <dev/acpica/acpiio.h> #include <contrib/dev/acpica/acnamesp.h> +#include "pci_if.h" +#include <dev/pci/pcivar.h> +#include <dev/pci/pci_private.h> + MALLOC_DEFINE(M_ACPIDEV, "acpidev", "ACPI devices"); /* Hooks for the ACPI CA debugging infrastructure */ @@ -87,10 +91,14 @@ static int acpi_modevent(struct module *mod, int event, void *junk); static void acpi_identify(driver_t *driver, device_t parent); static int acpi_probe(device_t dev); static int acpi_attach(device_t dev); +static int acpi_suspend(device_t dev); +static int acpi_resume(device_t dev); static int acpi_shutdown(device_t dev); static device_t acpi_add_child(device_t bus, int order, const char *name, int unit); static int acpi_print_child(device_t bus, device_t child); +static void acpi_probe_nomatch(device_t bus, device_t child); +static void acpi_driver_added(device_t dev, driver_t *driver); static int acpi_read_ivar(device_t dev, device_t child, int index, uintptr_t *result); static int acpi_write_ivar(device_t dev, device_t child, int index, @@ -110,10 +118,14 @@ static char *acpi_device_id_probe(device_t bus, device_t dev, char **ids); static ACPI_STATUS acpi_device_eval_obj(device_t bus, device_t dev, ACPI_STRING pathname, ACPI_OBJECT_LIST *parameters, ACPI_BUFFER *ret); +static int acpi_device_pwr_for_sleep(device_t bus, device_t dev, + int *dstate); static ACPI_STATUS acpi_device_scan_cb(ACPI_HANDLE h, UINT32 level, void *context, void **retval); static ACPI_STATUS acpi_device_scan_children(device_t bus, device_t dev, int max_depth, acpi_scan_cb_t user_fn, void *arg); +static int acpi_set_powerstate_method(device_t bus, device_t child, + int state); static int acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids); static void acpi_probe_children(device_t bus); @@ -145,12 +157,14 @@ static device_method_t acpi_methods[] = { DEVMETHOD(device_attach, acpi_attach), DEVMETHOD(device_shutdown, acpi_shutdown), DEVMETHOD(device_detach, bus_generic_detach), - DEVMETHOD(device_suspend, bus_generic_suspend), - DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_suspend, acpi_suspend), + DEVMETHOD(device_resume, acpi_resume), /* Bus interface */ DEVMETHOD(bus_add_child, acpi_add_child), DEVMETHOD(bus_print_child, acpi_print_child), + DEVMETHOD(bus_probe_nomatch, acpi_probe_nomatch), + DEVMETHOD(bus_driver_added, acpi_driver_added), DEVMETHOD(bus_read_ivar, acpi_read_ivar), DEVMETHOD(bus_write_ivar, acpi_write_ivar), DEVMETHOD(bus_get_resource_list, acpi_get_rlist), @@ -160,7 +174,6 @@ static device_method_t acpi_methods[] = { DEVMETHOD(bus_release_resource, acpi_release_resource), DEVMETHOD(bus_child_pnpinfo_str, acpi_child_pnpinfo_str_method), DEVMETHOD(bus_child_location_str, acpi_child_location_str_method), - DEVMETHOD(bus_driver_added, bus_generic_driver_added), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), @@ -169,8 +182,12 @@ static device_method_t acpi_methods[] = { /* ACPI bus */ DEVMETHOD(acpi_id_probe, acpi_device_id_probe), DEVMETHOD(acpi_evaluate_object, acpi_device_eval_obj), + DEVMETHOD(acpi_pwr_for_sleep, acpi_device_pwr_for_sleep), DEVMETHOD(acpi_scan_children, acpi_device_scan_children), + /* PCI emulation */ + DEVMETHOD(pci_set_powerstate, acpi_set_powerstate_method), + /* ISA emulation */ DEVMETHOD(isa_pnp_probe, acpi_isa_pnp_probe), @@ -212,6 +229,12 @@ SYSCTL_STRING(_debug_acpi, OID_AUTO, acpi_ca_version, CTLFLAG_RD, static int acpi_serialize_methods; TUNABLE_INT("hw.acpi.serialize_methods", &acpi_serialize_methods); +/* Power devices off and on in suspend and resume. XXX Remove once tested. */ +static int acpi_do_powerstate = 1; +TUNABLE_INT("debug.acpi.do_powerstate", &acpi_do_powerstate); +SYSCTL_INT(_debug_acpi, OID_AUTO, do_powerstate, CTLFLAG_RW, + &acpi_do_powerstate, 1, "Turn off devices when suspending."); + /* * ACPI can only be loaded as a module by the loader; activating it after * system bootstrap time is not useful, and can be fatal to the system. @@ -570,6 +593,72 @@ acpi_attach(device_t dev) } static int +acpi_suspend(device_t dev) +{ + struct acpi_softc *sc; + device_t child, *devlist; + int error, i, numdevs, pstate; + + /* First give child devices a chance to suspend. */ + error = bus_generic_suspend(dev); + if (error) + return (error); + + /* + * Now, set them into the appropriate power state, usually D3. If the + * device has an _SxD method for the next sleep state, use that power + * state instead. + */ + sc = device_get_softc(dev); + device_get_children(dev, &devlist, &numdevs); + for (i = 0; i < numdevs; i++) { + /* If the device is not attached, we've powered it down elsewhere. */ + child = devlist[i]; + if (!device_is_attached(child)) + continue; + + /* + * Default to D3 for all sleep states. The _SxD method is optional + * so set the powerstate even if it's absent. + */ + pstate = PCI_POWERSTATE_D3; + error = acpi_device_pwr_for_sleep(device_get_parent(child), + child, &pstate); + if ((error == 0 || error == ESRCH) && acpi_do_powerstate) + pci_set_powerstate(child, pstate); + } + free(devlist, M_TEMP); + error = 0; + + return (error); +} + +static int +acpi_resume(device_t dev) +{ + ACPI_HANDLE handle; + int i, numdevs; + device_t child, *devlist; + + /* + * Put all devices in D0 before resuming them. Call _S0D on each one + * since some systems expect this. + */ + device_get_children(dev, &devlist, &numdevs); + for (i = 0; i < numdevs; i++) { + child = devlist[i]; + handle = acpi_get_handle(child); + if (handle) + AcpiEvaluateObject(handle, "_S0D", NULL, NULL); + if (device_is_attached(child) && acpi_do_powerstate) + pci_set_powerstate(child, PCI_POWERSTATE_D0); + } + free(devlist, M_TEMP); + + return (bus_generic_resume(dev)); +} + +static int acpi_shutdown(device_t dev) { @@ -624,6 +713,45 @@ acpi_print_child(device_t bus, device_t child) return (retval); } +/* + * If this device is an ACPI child but no one claimed it, attempt + * to power it off. We'll power it back up when a driver is added. + * + * XXX Disabled for now since many necessary devices (like fdc and + * ATA) don't claim the devices we created for them but still expect + * them to be powered up. + */ +static void +acpi_probe_nomatch(device_t bus, device_t child) +{ + + /* pci_set_powerstate(child, PCI_POWERSTATE_D3); */ +} + +/* + * If a new driver has a chance to probe a child, first power it up. + * + * XXX Disabled for now (see acpi_probe_nomatch for details). + */ +static void +acpi_driver_added(device_t dev, driver_t *driver) +{ + device_t child, *devlist; + int i, numdevs; + + DEVICE_IDENTIFY(driver, dev); + device_get_children(dev, &devlist, &numdevs); + for (i = 0; i < numdevs; i++) { + child = devlist[i]; + if (device_get_state(child) == DS_NOTPRESENT) { + /* pci_set_powerstate(child, PCI_POWERSTATE_D0); */ + if (device_probe_and_attach(child) != 0) + ; /* pci_set_powerstate(child, PCI_POWERSTATE_D3); */ + } + } + free(devlist, M_TEMP); +} + /* Location hint for devctl(8) */ static int acpi_child_location_str_method(device_t cbdev, device_t child, char *buf, @@ -1064,6 +1192,57 @@ acpi_device_eval_obj(device_t bus, device_t dev, ACPI_STRING pathname, return (AcpiEvaluateObject(h, pathname, parameters, ret)); } +static int +acpi_device_pwr_for_sleep(device_t bus, device_t dev, int *dstate) +{ + struct acpi_softc *sc; + ACPI_HANDLE handle; + ACPI_STATUS status; + char sxd[8]; + int error; + + sc = device_get_softc(bus); + handle = acpi_get_handle(dev); + + /* + * XXX If we find these devices, don't try to power them down. + * The serial and IRDA ports on my T23 hang the system when + * set to D3 and it appears that such legacy devices may + * need special handling in their drivers. + */ + if (handle == NULL || + acpi_MatchHid(handle, "PNP0500") || + acpi_MatchHid(handle, "PNP0501") || + acpi_MatchHid(handle, "PNP0502") || + acpi_MatchHid(handle, "PNP0510") || + acpi_MatchHid(handle, "PNP0511")) + return (ENXIO); + + /* + * Override next state with the value from _SxD, if present. If no + * dstate argument was provided, don't fetch the return value. + */ + snprintf(sxd, sizeof(sxd), "_S%dD", sc->acpi_sstate); + if (dstate) + status = acpi_GetInteger(handle, sxd, dstate); + else + status = AcpiEvaluateObject(handle, sxd, NULL, NULL); + + switch (status) { + case AE_OK: + error = 0; + break; + case AE_NOT_FOUND: + error = ESRCH; + break; + default: + error = ENXIO; + break; + } + + return (error); +} + /* Callback arg for our implementation of walking the namespace. */ struct acpi_device_scan_ctx { acpi_scan_cb_t user_fn; @@ -1138,6 +1317,34 @@ acpi_device_scan_children(device_t bus, device_t dev, int max_depth, acpi_device_scan_cb, &ctx, NULL)); } +/* + * Even though ACPI devices are not PCI, we use the PCI approach for setting + * device power states since it's close enough to ACPI. + */ +static int +acpi_set_powerstate_method(device_t bus, device_t child, int state) +{ + ACPI_HANDLE h; + ACPI_STATUS status; + int error; + + error = 0; + h = acpi_get_handle(child); + if (state < ACPI_STATE_D0 || state > ACPI_STATE_D3) + return (EINVAL); + if (h == NULL) + return (0); + + /* Ignore errors if the power methods aren't present. */ + status = acpi_pwr_switch_consumer(h, state); + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND + && status != AE_BAD_PARAMETER) + device_printf(bus, "failed to set ACPI power state D%d on %s: %s\n", + state, acpi_name(h), AcpiFormatException(status)); + + return (error); +} + static int acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids) { |