Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 260299
b: refs/heads/master
c: 97c4cb7
h: refs/heads/master
i:
  260297: ac9f4a6
  260295: 0be00dd
v: v3
  • Loading branch information
Daniel Drake authored and H. Peter Anvin committed Jul 6, 2011
1 parent dc3f982 commit 42b4c20
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 7 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: a3128588b3c6be634a9013a375903e0b55668f0a
refs/heads/master: 97c4cb71c18fe045a763ff6681a8ebbbbbec0b2b
4 changes: 2 additions & 2 deletions trunk/arch/x86/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 12 additions & 3 deletions trunk/arch/x86/include/asm/olpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -88,9 +94,12 @@ extern int olpc_ec_mask_unset(uint8_t bits);

/* EC commands */

#define EC_FIRMWARE_REV 0x08
#define EC_WLAN_ENTER_RESET 0x35
#define EC_WLAN_LEAVE_RESET 0x25
#define EC_FIRMWARE_REV 0x08
#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 */

Expand Down
2 changes: 1 addition & 1 deletion trunk/arch/x86/platform/olpc/Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
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 changes: 92 additions & 0 deletions trunk/arch/x86/platform/olpc/olpc-xo1-pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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");
Expand All @@ -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;
Expand All @@ -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");
}
Expand Down
124 changes: 124 additions & 0 deletions trunk/arch/x86/platform/olpc/xo1-wakeup.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
.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
3 changes: 3 additions & 0 deletions trunk/include/linux/cs5535.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
#define CS5536_PM1_CNT 0x08
#define CS5536_PM_GPE0_STS 0x18

/* CS5536_PM1_EN bits */
#define CS5536_PM_PWRBTN (1 << 8)

/* VSA2 magic values */
#define VSA_VRC_INDEX 0xAC1C
#define VSA_VRC_DATA 0xAC1E
Expand Down

0 comments on commit 42b4c20

Please sign in to comment.