Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 97c4cb71 authored by Daniel Drake's avatar Daniel Drake Committed by H. Peter Anvin
Browse files

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: default avatarDaniel Drake <dsd@laptop.org>
Link: http://lkml.kernel.org/r/1309019658-1712-5-git-send-email-dsd@laptop.org


Acked-by: default avatarAndres Salomon <dilinger@queued.net>
Signed-off-by: default avatarH. Peter Anvin <hpa@linux.intel.com>
parent a3128588
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -2075,10 +2075,10 @@ config OLPC

config OLPC_XO1_PM
	bool "OLPC XO-1 Power Management"
	depends on OLPC && MFD_CS5535
	depends on OLPC && MFD_CS5535 && PM_SLEEP
	select MFD_CORE
	---help---
	  Add support for poweroff of the OLPC XO-1 laptop.
	  Add support for poweroff and suspend of the OLPC XO-1 laptop.

endif # X86_32

+12 −3
Original line number Diff line number Diff line
@@ -76,6 +76,12 @@ static inline int olpc_has_dcon(void)

#endif

#ifdef CONFIG_OLPC_XO1_PM
extern void do_olpc_suspend_lowlevel(void);
extern void olpc_xo1_pm_wakeup_set(u16 value);
extern void olpc_xo1_pm_wakeup_clear(u16 value);
#endif

extern int pci_olpc_init(void);

/* EC related functions */
@@ -89,8 +95,11 @@ extern int olpc_ec_mask_unset(uint8_t bits);
/* EC commands */

#define EC_FIRMWARE_REV			0x08
#define EC_WLAN_ENTER_RESET	0x35
#define EC_WAKE_UP_WLAN			0x24
#define EC_WLAN_LEAVE_RESET		0x25
#define EC_SET_SCI_INHIBIT		0x32
#define EC_SET_SCI_INHIBIT_RELEASE	0x34
#define EC_WLAN_ENTER_RESET		0x35

/* SCI source values */

+1 −1
Original line number Diff line number Diff line
obj-$(CONFIG_OLPC)		+= olpc.o olpc_ofw.o olpc_dt.o
obj-$(CONFIG_OLPC_XO1_PM)		+= olpc-xo1-pm.o
obj-$(CONFIG_OLPC_XO1_PM)		+= olpc-xo1-pm.o xo1-wakeup.o
+92 −0
Original line number Diff line number Diff line
@@ -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");
	}
+124 −0
Original line number Diff line number Diff line
.text
#include <linux/linkage.h>
#include <asm/segment.h>
#include <asm/page.h>
#include <asm/pgtable_32.h>

	.macro writepost,value
		movb $0x34, %al
		outb %al, $0x70
		movb $\value, %al
		outb %al, $0x71
	.endm

wakeup_start:
	# OFW lands us here, running in protected mode, with a
	# kernel-compatible GDT already setup.

	# Clear any dangerous flags
	pushl $0
	popfl

	writepost 0x31

	# Set up %cr3
	movl $initial_page_table - __PAGE_OFFSET, %eax
	movl %eax, %cr3

	movl saved_cr4, %eax
	movl %eax, %cr4

	movl saved_cr0, %eax
	movl %eax, %cr0

	# Control registers were modified, pipeline resync is needed
	jmp 1f
1:

	movw    $__KERNEL_DS, %ax
	movw    %ax, %ss
	movw    %ax, %ds
	movw    %ax, %es
	movw    %ax, %fs
	movw    %ax, %gs

	lgdt    saved_gdt
	lidt    saved_idt
	lldt    saved_ldt
	ljmp    $(__KERNEL_CS),$1f
1:
	movl    %cr3, %eax
	movl    %eax, %cr3
	wbinvd

	# Go back to the return point
	jmp ret_point

save_registers:
	sgdt  saved_gdt
	sidt  saved_idt
	sldt  saved_ldt

	pushl %edx
	movl %cr4, %edx
	movl %edx, saved_cr4

	movl %cr0, %edx
	movl %edx, saved_cr0

	popl %edx

	movl %ebx, saved_context_ebx
	movl %ebp, saved_context_ebp
	movl %esi, saved_context_esi
	movl %edi, saved_context_edi

	pushfl
	popl saved_context_eflags

	ret

restore_registers:
	movl saved_context_ebp, %ebp
	movl saved_context_ebx, %ebx
	movl saved_context_esi, %esi
	movl saved_context_edi, %edi

	pushl saved_context_eflags
	popfl

	ret

ENTRY(do_olpc_suspend_lowlevel)
	call	save_processor_state
	call	save_registers

	# This is the stack context we want to remember
	movl %esp, saved_context_esp

	pushl	$3
	call	xo1_do_sleep

	jmp	wakeup_start
	.p2align 4,,7
ret_point:
	movl    saved_context_esp, %esp

	writepost 0x32

	call	restore_registers
	call	restore_processor_state
	ret

.data
saved_gdt:             .long   0,0
saved_idt:             .long   0,0
saved_ldt:             .long   0
saved_cr4:             .long   0
saved_cr0:             .long   0
saved_context_esp:     .long   0
saved_context_edi:     .long   0
saved_context_esi:     .long   0
saved_context_ebx:     .long   0
saved_context_ebp:     .long   0
saved_context_eflags:  .long   0
Loading