x86, olpc: Add XO-1 suspend/resume support
Add code needed for basic suspend/resume of the XO-1 laptop. Based on earlier work by Jordan Crouse, Andres Salomon, and others. This patch incorporates all earlier feedback from Thomas Gleixner. To clarify a certain point (now more obvious in the code itself): On resume, OpenFirmware returns execution to Linux in protected mode with a kernel-compatible GDT already set up. The changes and simplifications suggested have all been included. Signed-off-by: Daniel Drake <dsd@laptop.org> Link: http://lkml.kernel.org/r/1309019658-1712-5-git-send-email-dsd@laptop.org Acked-by: Andres Salomon <dilinger@queued.net> Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
This commit is contained in:

committed by
H. Peter Anvin

parent
a3128588b3
commit
97c4cb71c1
@@ -16,6 +16,7 @@
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/olpc.h>
|
||||
@@ -25,6 +26,85 @@
|
||||
static unsigned long acpi_base;
|
||||
static unsigned long pms_base;
|
||||
|
||||
static u16 wakeup_mask = CS5536_PM_PWRBTN;
|
||||
|
||||
static struct {
|
||||
unsigned long address;
|
||||
unsigned short segment;
|
||||
} ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS };
|
||||
|
||||
/* Set bits in the wakeup mask */
|
||||
void olpc_xo1_pm_wakeup_set(u16 value)
|
||||
{
|
||||
wakeup_mask |= value;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set);
|
||||
|
||||
/* Clear bits in the wakeup mask */
|
||||
void olpc_xo1_pm_wakeup_clear(u16 value)
|
||||
{
|
||||
wakeup_mask &= ~value;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear);
|
||||
|
||||
static int xo1_power_state_enter(suspend_state_t pm_state)
|
||||
{
|
||||
unsigned long saved_sci_mask;
|
||||
int r;
|
||||
|
||||
/* Only STR is supported */
|
||||
if (pm_state != PM_SUSPEND_MEM)
|
||||
return -EINVAL;
|
||||
|
||||
r = olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0);
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
/*
|
||||
* Save SCI mask (this gets lost since PM1_EN is used as a mask for
|
||||
* wakeup events, which is not necessarily the same event set)
|
||||
*/
|
||||
saved_sci_mask = inl(acpi_base + CS5536_PM1_STS);
|
||||
saved_sci_mask &= 0xffff0000;
|
||||
|
||||
/* Save CPU state */
|
||||
do_olpc_suspend_lowlevel();
|
||||
|
||||
/* Resume path starts here */
|
||||
|
||||
/* Restore SCI mask (using dword access to CS5536_PM1_EN) */
|
||||
outl(saved_sci_mask, acpi_base + CS5536_PM1_STS);
|
||||
|
||||
/* Tell the EC to stop inhibiting SCIs */
|
||||
olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0);
|
||||
|
||||
/*
|
||||
* Tell the wireless module to restart USB communication.
|
||||
* Must be done twice.
|
||||
*/
|
||||
olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
|
||||
olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
asmlinkage int xo1_do_sleep(u8 sleep_state)
|
||||
{
|
||||
void *pgd_addr = __va(read_cr3());
|
||||
|
||||
/* Program wakeup mask (using dword access to CS5536_PM1_EN) */
|
||||
outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS);
|
||||
|
||||
__asm__("movl %0,%%eax" : : "r" (pgd_addr));
|
||||
__asm__("call *(%%edi); cld"
|
||||
: : "D" (&ofw_bios_entry));
|
||||
__asm__("movb $0x34, %al\n\t"
|
||||
"outb %al, $0x70\n\t"
|
||||
"movb $0x30, %al\n\t"
|
||||
"outb %al, $0x71\n\t");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xo1_power_off(void)
|
||||
{
|
||||
printk(KERN_INFO "OLPC XO-1 power off sequence...\n");
|
||||
@@ -43,6 +123,17 @@ static void xo1_power_off(void)
|
||||
outl(0x00002000, acpi_base + CS5536_PM1_CNT);
|
||||
}
|
||||
|
||||
static int xo1_power_state_valid(suspend_state_t pm_state)
|
||||
{
|
||||
/* suspend-to-RAM only */
|
||||
return pm_state == PM_SUSPEND_MEM;
|
||||
}
|
||||
|
||||
static const struct platform_suspend_ops xo1_suspend_ops = {
|
||||
.valid = xo1_power_state_valid,
|
||||
.enter = xo1_power_state_enter,
|
||||
};
|
||||
|
||||
static int __devinit xo1_pm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
@@ -68,6 +159,7 @@ static int __devinit xo1_pm_probe(struct platform_device *pdev)
|
||||
|
||||
/* If we have both addresses, we can override the poweroff hook */
|
||||
if (pms_base && acpi_base) {
|
||||
suspend_set_ops(&xo1_suspend_ops);
|
||||
pm_power_off = xo1_power_off;
|
||||
printk(KERN_INFO "OLPC XO-1 support registered\n");
|
||||
}
|
||||
|
Reference in New Issue
Block a user