Provide new ACPI method tracking the target system state, for use
during suspend() and other PM calls.  It returns ACPI_STATE_S0
except during true suspend paths.

Use that to finally implement the platform_pci_choose_state() hook
on ACPI platforms.  It calls "_S3D" and similar methods, and uses
the result appropriately.

Fix pci_choose_state() to finally behave sanely too.

Minor whitespace fixes.

Lightly tested -- STR only, with only USB affected by the new code.

Signed-off-by: David Brownell <[EMAIL PROTECTED]>

---
 drivers/acpi/sleep/main.c |   29 +++++++++++-
 drivers/pci/pci-acpi.c    |  107 +++++++++++++++++++++++++++++++++-------------
 drivers/pci/pci.c         |   51 ++++++++++++---------
 include/acpi/acpixf.h     |    2 
 4 files changed, 135 insertions(+), 54 deletions(-)

--- g26.orig/include/acpi/acpixf.h      2007-05-09 08:57:37.000000000 -0700
+++ g26/include/acpi/acpixf.h   2007-05-09 08:58:33.000000000 -0700
@@ -329,6 +329,8 @@ acpi_get_sleep_type_data(u8 sleep_state,
 
 acpi_status acpi_enter_sleep_state_prep(u8 sleep_state);
 
+int acpi_get_target_sleep_state(void);
+
 acpi_status asmlinkage acpi_enter_sleep_state(u8 sleep_state);
 
 acpi_status asmlinkage acpi_enter_sleep_state_s4bios(void);
--- g26.orig/drivers/acpi/sleep/main.c  2007-05-09 08:57:37.000000000 -0700
+++ g26/drivers/acpi/sleep/main.c       2007-05-09 08:58:33.000000000 -0700
@@ -35,6 +35,20 @@ static u32 acpi_suspend_states[] = {
 
 static int init_8259A_after_S1;
 
+static u8 acpi_target_sleep_state = ACPI_STATE_S0;
+
+/**
+ *     acpi_get_target_sleep_state - return target ACPI S-state
+ *
+ *     When used during suspend processing, this returns the target state
+ *     such as ACPI_STATE_S3.  Otherwise it returns ACPI_STATE_S0.
+ */
+int acpi_get_target_sleep_state(void)
+{
+       return acpi_target_sleep_state;
+}
+/* EXPORT_SYMBOL(acpi_get_target_sleep_state); ... if you need it */
+
 /**
  *     acpi_pm_prepare - Do preliminary suspend work.
  *     @pm_state:              suspend state we're entering.
@@ -50,12 +64,16 @@ extern void acpi_power_off(void);
 static int acpi_pm_prepare(suspend_state_t pm_state)
 {
        u32 acpi_state = acpi_suspend_states[pm_state];
+       int status;
 
        if (!sleep_states[acpi_state]) {
                printk("acpi_pm_prepare does not support %d \n", pm_state);
                return -EPERM;
        }
-       return acpi_sleep_prepare(acpi_state);
+       status = acpi_sleep_prepare(acpi_state);
+       if (status == 0)
+               acpi_target_sleep_state = acpi_state;
+       return status;
 }
 
 /**
@@ -78,8 +96,10 @@ static int acpi_pm_enter(suspend_state_t
        /* Do arch specific saving of state. */
        if (pm_state > PM_SUSPEND_STANDBY) {
                int error = acpi_save_state_mem();
-               if (error)
+               if (error) {
+                       acpi_target_sleep_state = ACPI_STATE_S0;
                        return error;
+               }
        }
 
        local_irq_save(flags);
@@ -103,6 +123,10 @@ static int acpi_pm_enter(suspend_state_t
                break;
 
        default:
+               /* "should never happen" */
+               acpi_target_sleep_state = ACPI_STATE_S0;
+               local_irq_restore(flags);
+               WARN_ON(1);
                return -EINVAL;
        }
 
@@ -113,6 +137,7 @@ static int acpi_pm_enter(suspend_state_t
        if (ACPI_SUCCESS(status) && (acpi_state == ACPI_STATE_S3))
                acpi_clear_event(ACPI_EVENT_POWER_BUTTON);
 
+       acpi_target_sleep_state = ACPI_STATE_S0;
        local_irq_restore(flags);
        printk(KERN_DEBUG "Back to C!\n");
 
--- g26.orig/drivers/pci/pci-acpi.c     2007-05-09 08:57:37.000000000 -0700
+++ g26/drivers/pci/pci-acpi.c  2007-05-09 12:16:25.000000000 -0700
@@ -225,52 +225,99 @@ acpi_status pci_osc_control_set(acpi_han
 EXPORT_SYMBOL(pci_osc_control_set);
 
 /*
- * _SxD returns the D-state with the highest power
- * (lowest D-state number) supported in the S-state "x".
+ * PCI devices in the ACPI tables may have various PM-related methods,
+ * like _SxD and _SxW (where 'x' is a target S-state).
  *
- * If the devices does not have a _PRW
- * (Power Resources for Wake) supporting system wakeup from "x"
- * then the OS is free to choose a lower power (higher number
- * D-state) than the return value from _SxD.
+ * Not all devices are listed in the ACPI tables; PCI/Cardbus/.. addon
+ * devices are not known to ACPI.  We ignore those devices.  However,
+ * support for PME# depends on the PCI root bridge, which *is* known
+ * to ACPI ... and which may need to enable a GPE if any child devices
+ * are wakeup-enabled.
  *
- * But if _PRW is enabled at S-state "x", the OS
- * must not choose a power lower than _SxD --
- * unless the device has an _SxW method specifying
- * the lowest power (highest D-state number) the device
- * may enter while still able to wake the system.
+ * All PM-enabled PCI devices can support PCI_D3.
  *
- * ie. depending on global OS policy:
- *
- * if (_PRW at S-state x)
- *     choose from highest power _SxD to lowest power _SxW
- * else // no _PRW at S-state x
- *     choose highest power _SxD or any lower power
- *
- * currently we simply return _SxD, if present.
+ * For now, prefer lowest power state unless ACPI suggests otherwise.
  */
-
 static int acpi_pci_choose_state(struct pci_dev *pdev, pm_message_t state)
 {
-       /* TBD */
+       static const u8 state_conv[] = {
+               [ACPI_STATE_D0] = PCI_D0,       /* highest power */
+               [ACPI_STATE_D1] = PCI_D1,
+               [ACPI_STATE_D2] = PCI_D2,
+               [ACPI_STATE_D3] = PCI_D3hot,
+       };
+
+       acpi_handle             handle = DEVICE_ACPI_HANDLE(&pdev->dev);
+       struct acpi_device      *adev;
+       char                    sxd[] = "_SxD";
+       unsigned long           d_min, d_max;
+       int                     s_state;
+
+       /* Device isn't known to ACPI? */
+       if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &adev)))
+               return -ENODEV;
+
+       s_state = acpi_get_target_sleep_state();
+       if (s_state > ACPI_STATE_S3)
+               return PCI_D3cold;
+
+       /* Any device can handle D0 and D3; we'll assume that if it can
+        * wake, it can wake from D3 *or* ACPI will tell us.
+        */
+       d_min = ACPI_STATE_D3;
+       d_max = ACPI_STATE_D3;
+
+       /* If present, _SxD methods give the minimum D-state we may use
+        * for each S-state ... with lowest latency state switching.
+        *
+        * We rely on acpi_evaluate_integer() not clobbering the integer
+        * provided -- that's our fault recovery, we ignore retval.
+        */
+       sxd[2] = '0' + s_state;
+       if (s_state > ACPI_STATE_S0)
+               acpi_evaluate_integer(handle, sxd, NULL, &d_min);
+
+       /* If _PRW says we can wake from the upcoming system state, the
+        * _SxD value can wake ... and we'll assume a wakeup-aware driver.
+        * If _SxW methods exist (ACPI 3.x), they give the lowest power
+        * D-state that can also wake the system.  _S0W can be valid.
+        */
+       if (device_may_wakeup(&pdev->dev)
+                       && adev->wakeup.sleep_state <= s_state) {
+               d_max = d_min;
+               sxd[3] = 'W';
+               acpi_evaluate_integer(handle, sxd, NULL, &d_max);
+       }
 
-       return -ENODEV;
+       /* Use the lowest power option ACPI allows */
+       return state_conv[d_max];
 }
 
 static int acpi_pci_set_power_state(struct pci_dev *dev, pci_power_t state)
 {
        acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev);
-       static int state_conv[] = {
-               [0] = 0,
-               [1] = 1,
-               [2] = 2,
-               [3] = 3,
-               [4] = 3
+
+       static const u8 state_conv[] = {
+               [PCI_D0] = ACPI_STATE_D0,
+               [PCI_D1] = ACPI_STATE_D1,
+               [PCI_D2] = ACPI_STATE_D2,
+               [PCI_D3hot] = ACPI_STATE_D3,
+               [PCI_D3cold] = ACPI_STATE_D3
        };
-       int acpi_state = state_conv[(int __force) state];
 
        if (!handle)
                return -ENODEV;
-       return acpi_bus_set_power(handle, acpi_state);
+
+       switch (state) {
+       case PCI_D0:
+       case PCI_D1:
+       case PCI_D2:
+       case PCI_D3hot:
+       case PCI_D3cold:
+               return acpi_bus_set_power(handle, state_conv[state]);
+       }
+
+       return -EINVAL;
 }
 
 
--- g26.orig/drivers/pci/pci.c  2007-05-09 08:57:37.000000000 -0700
+++ g26/drivers/pci/pci.c       2007-05-09 12:17:08.000000000 -0700
@@ -500,43 +500,50 @@ pci_set_power_state(struct pci_dev *dev,
 }
 
 int (*platform_pci_choose_state)(struct pci_dev *dev, pm_message_t state);
- 
+
 /**
  * pci_choose_state - Choose the power state of a PCI device
  * @dev: PCI device to be suspended
- * @state: target sleep state for the whole system. This is the value
- *     that is passed to suspend() function.
+ * @mesg: The value passed to the suspend() function.
  *
  * Returns PCI power state suitable for given device and given system
- * message.
+ * power transition.
  */
 
-pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state)
+pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t mesg)
 {
-       int ret;
-
-       if (!pci_find_capability(dev, PCI_CAP_ID_PM))
-               return PCI_D0;
-
-       if (platform_pci_choose_state) {
-               ret = platform_pci_choose_state(dev, state);
-               if (ret >= 0)
-                       state.event = ret;
-       }
+       pci_power_t state = PCI_D0;
+       int pos;
 
-       switch (state.event) {
+       switch (mesg.event) {
        case PM_EVENT_ON:
-               return PCI_D0;
        case PM_EVENT_FREEZE:
        case PM_EVENT_PRETHAW:
-               /* REVISIT both freeze and pre-thaw "should" use D0 */
+               break;
+
+       /* Speed things up by only using PCI power states when going into
+        * a real system suspend state.
+        */
        case PM_EVENT_SUSPEND:
-               return PCI_D3hot;
+               pos = pci_find_capability(dev, PCI_CAP_ID_PM);
+               if (pos) {
+                       int ret = -ENOSYS;
+
+                       if (platform_pci_choose_state && !pci_no_d1d2(dev))
+                               ret = platform_pci_choose_state(dev, mesg);
+
+                       /* FIXME if the device is wakeup-enabled, check its
+                        * PM capabilities.  Never return a state that can't
+                        * issue wakeup events.
+                        */
+                       if (ret < 0)
+                               state = PCI_D3hot;
+               }
+               break;
        default:
-               printk("Unrecognized suspend event %d\n", state.event);
-               BUG();
+               WARN_ON(1);
        }
-       return PCI_D0;
+       return state;
 }
 
 EXPORT_SYMBOL(pci_choose_state);
-
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to