1a3128588SDaniel Drake /* 2a3128588SDaniel Drake * Support for power management features of the OLPC XO-1 laptop 3a3128588SDaniel Drake * 4a3128588SDaniel Drake * Copyright (C) 2010 Andres Salomon <dilinger@queued.net> 5a3128588SDaniel Drake * Copyright (C) 2010 One Laptop per Child 6a3128588SDaniel Drake * Copyright (C) 2006 Red Hat, Inc. 7a3128588SDaniel Drake * Copyright (C) 2006 Advanced Micro Devices, Inc. 8a3128588SDaniel Drake * 9a3128588SDaniel Drake * This program is free software; you can redistribute it and/or modify 10a3128588SDaniel Drake * it under the terms of the GNU General Public License as published by 11a3128588SDaniel Drake * the Free Software Foundation; either version 2 of the License, or 12a3128588SDaniel Drake * (at your option) any later version. 13a3128588SDaniel Drake */ 14a3128588SDaniel Drake 15a3128588SDaniel Drake #include <linux/cs5535.h> 16a3128588SDaniel Drake #include <linux/platform_device.h> 17*69c60c88SPaul Gortmaker #include <linux/export.h> 18a3128588SDaniel Drake #include <linux/pm.h> 19a3128588SDaniel Drake #include <linux/mfd/core.h> 2097c4cb71SDaniel Drake #include <linux/suspend.h> 21a3128588SDaniel Drake 22a3128588SDaniel Drake #include <asm/io.h> 23a3128588SDaniel Drake #include <asm/olpc.h> 24a3128588SDaniel Drake 25a3128588SDaniel Drake #define DRV_NAME "olpc-xo1-pm" 26a3128588SDaniel Drake 27a3128588SDaniel Drake static unsigned long acpi_base; 28a3128588SDaniel Drake static unsigned long pms_base; 29a3128588SDaniel Drake 3097c4cb71SDaniel Drake static u16 wakeup_mask = CS5536_PM_PWRBTN; 3197c4cb71SDaniel Drake 3297c4cb71SDaniel Drake static struct { 3397c4cb71SDaniel Drake unsigned long address; 3497c4cb71SDaniel Drake unsigned short segment; 3597c4cb71SDaniel Drake } ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS }; 3697c4cb71SDaniel Drake 3797c4cb71SDaniel Drake /* Set bits in the wakeup mask */ 3897c4cb71SDaniel Drake void olpc_xo1_pm_wakeup_set(u16 value) 3997c4cb71SDaniel Drake { 4097c4cb71SDaniel Drake wakeup_mask |= value; 4197c4cb71SDaniel Drake } 4297c4cb71SDaniel Drake EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set); 4397c4cb71SDaniel Drake 4497c4cb71SDaniel Drake /* Clear bits in the wakeup mask */ 4597c4cb71SDaniel Drake void olpc_xo1_pm_wakeup_clear(u16 value) 4697c4cb71SDaniel Drake { 4797c4cb71SDaniel Drake wakeup_mask &= ~value; 4897c4cb71SDaniel Drake } 4997c4cb71SDaniel Drake EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear); 5097c4cb71SDaniel Drake 5197c4cb71SDaniel Drake static int xo1_power_state_enter(suspend_state_t pm_state) 5297c4cb71SDaniel Drake { 5397c4cb71SDaniel Drake unsigned long saved_sci_mask; 5497c4cb71SDaniel Drake int r; 5597c4cb71SDaniel Drake 5697c4cb71SDaniel Drake /* Only STR is supported */ 5797c4cb71SDaniel Drake if (pm_state != PM_SUSPEND_MEM) 5897c4cb71SDaniel Drake return -EINVAL; 5997c4cb71SDaniel Drake 6097c4cb71SDaniel Drake r = olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0); 6197c4cb71SDaniel Drake if (r) 6297c4cb71SDaniel Drake return r; 6397c4cb71SDaniel Drake 6497c4cb71SDaniel Drake /* 6597c4cb71SDaniel Drake * Save SCI mask (this gets lost since PM1_EN is used as a mask for 6697c4cb71SDaniel Drake * wakeup events, which is not necessarily the same event set) 6797c4cb71SDaniel Drake */ 6897c4cb71SDaniel Drake saved_sci_mask = inl(acpi_base + CS5536_PM1_STS); 6997c4cb71SDaniel Drake saved_sci_mask &= 0xffff0000; 7097c4cb71SDaniel Drake 7197c4cb71SDaniel Drake /* Save CPU state */ 7297c4cb71SDaniel Drake do_olpc_suspend_lowlevel(); 7397c4cb71SDaniel Drake 7497c4cb71SDaniel Drake /* Resume path starts here */ 7597c4cb71SDaniel Drake 7697c4cb71SDaniel Drake /* Restore SCI mask (using dword access to CS5536_PM1_EN) */ 7797c4cb71SDaniel Drake outl(saved_sci_mask, acpi_base + CS5536_PM1_STS); 7897c4cb71SDaniel Drake 7997c4cb71SDaniel Drake /* Tell the EC to stop inhibiting SCIs */ 8097c4cb71SDaniel Drake olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0); 8197c4cb71SDaniel Drake 8297c4cb71SDaniel Drake /* 8397c4cb71SDaniel Drake * Tell the wireless module to restart USB communication. 8497c4cb71SDaniel Drake * Must be done twice. 8597c4cb71SDaniel Drake */ 8697c4cb71SDaniel Drake olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0); 8797c4cb71SDaniel Drake olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0); 8897c4cb71SDaniel Drake 8997c4cb71SDaniel Drake return 0; 9097c4cb71SDaniel Drake } 9197c4cb71SDaniel Drake 9297c4cb71SDaniel Drake asmlinkage int xo1_do_sleep(u8 sleep_state) 9397c4cb71SDaniel Drake { 9497c4cb71SDaniel Drake void *pgd_addr = __va(read_cr3()); 9597c4cb71SDaniel Drake 9697c4cb71SDaniel Drake /* Program wakeup mask (using dword access to CS5536_PM1_EN) */ 9797c4cb71SDaniel Drake outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS); 9897c4cb71SDaniel Drake 9997c4cb71SDaniel Drake __asm__("movl %0,%%eax" : : "r" (pgd_addr)); 10097c4cb71SDaniel Drake __asm__("call *(%%edi); cld" 10197c4cb71SDaniel Drake : : "D" (&ofw_bios_entry)); 10297c4cb71SDaniel Drake __asm__("movb $0x34, %al\n\t" 10397c4cb71SDaniel Drake "outb %al, $0x70\n\t" 10497c4cb71SDaniel Drake "movb $0x30, %al\n\t" 10597c4cb71SDaniel Drake "outb %al, $0x71\n\t"); 10697c4cb71SDaniel Drake return 0; 10797c4cb71SDaniel Drake } 10897c4cb71SDaniel Drake 109a3128588SDaniel Drake static void xo1_power_off(void) 110a3128588SDaniel Drake { 111a3128588SDaniel Drake printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); 112a3128588SDaniel Drake 113a3128588SDaniel Drake /* Enable all of these controls with 0 delay */ 114a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_SCLK); 115a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL); 116a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_WKXD); 117a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_WKD); 118a3128588SDaniel Drake 119a3128588SDaniel Drake /* Clear status bits (possibly unnecessary) */ 120a3128588SDaniel Drake outl(0x0002ffff, pms_base + CS5536_PM_SSC); 121a3128588SDaniel Drake outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); 122a3128588SDaniel Drake 123a3128588SDaniel Drake /* Write SLP_EN bit to start the machinery */ 124a3128588SDaniel Drake outl(0x00002000, acpi_base + CS5536_PM1_CNT); 125a3128588SDaniel Drake } 126a3128588SDaniel Drake 12797c4cb71SDaniel Drake static int xo1_power_state_valid(suspend_state_t pm_state) 12897c4cb71SDaniel Drake { 12997c4cb71SDaniel Drake /* suspend-to-RAM only */ 13097c4cb71SDaniel Drake return pm_state == PM_SUSPEND_MEM; 13197c4cb71SDaniel Drake } 13297c4cb71SDaniel Drake 13397c4cb71SDaniel Drake static const struct platform_suspend_ops xo1_suspend_ops = { 13497c4cb71SDaniel Drake .valid = xo1_power_state_valid, 13597c4cb71SDaniel Drake .enter = xo1_power_state_enter, 13697c4cb71SDaniel Drake }; 13797c4cb71SDaniel Drake 138a3128588SDaniel Drake static int __devinit xo1_pm_probe(struct platform_device *pdev) 139a3128588SDaniel Drake { 140a3128588SDaniel Drake struct resource *res; 141a3128588SDaniel Drake int err; 142a3128588SDaniel Drake 143a3128588SDaniel Drake /* don't run on non-XOs */ 144a3128588SDaniel Drake if (!machine_is_olpc()) 145a3128588SDaniel Drake return -ENODEV; 146a3128588SDaniel Drake 147a3128588SDaniel Drake err = mfd_cell_enable(pdev); 148a3128588SDaniel Drake if (err) 149a3128588SDaniel Drake return err; 150a3128588SDaniel Drake 151a3128588SDaniel Drake res = platform_get_resource(pdev, IORESOURCE_IO, 0); 152a3128588SDaniel Drake if (!res) { 153a3128588SDaniel Drake dev_err(&pdev->dev, "can't fetch device resource info\n"); 154a3128588SDaniel Drake return -EIO; 155a3128588SDaniel Drake } 156a3128588SDaniel Drake if (strcmp(pdev->name, "cs5535-pms") == 0) 157a3128588SDaniel Drake pms_base = res->start; 158a3128588SDaniel Drake else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 159a3128588SDaniel Drake acpi_base = res->start; 160a3128588SDaniel Drake 161a3128588SDaniel Drake /* If we have both addresses, we can override the poweroff hook */ 162a3128588SDaniel Drake if (pms_base && acpi_base) { 16397c4cb71SDaniel Drake suspend_set_ops(&xo1_suspend_ops); 164a3128588SDaniel Drake pm_power_off = xo1_power_off; 165a3128588SDaniel Drake printk(KERN_INFO "OLPC XO-1 support registered\n"); 166a3128588SDaniel Drake } 167a3128588SDaniel Drake 168a3128588SDaniel Drake return 0; 169a3128588SDaniel Drake } 170a3128588SDaniel Drake 171a3128588SDaniel Drake static int __devexit xo1_pm_remove(struct platform_device *pdev) 172a3128588SDaniel Drake { 173a3128588SDaniel Drake mfd_cell_disable(pdev); 174a3128588SDaniel Drake 175a3128588SDaniel Drake if (strcmp(pdev->name, "cs5535-pms") == 0) 176a3128588SDaniel Drake pms_base = 0; 177a3128588SDaniel Drake else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 178a3128588SDaniel Drake acpi_base = 0; 179a3128588SDaniel Drake 180a3128588SDaniel Drake pm_power_off = NULL; 181a3128588SDaniel Drake return 0; 182a3128588SDaniel Drake } 183a3128588SDaniel Drake 184a3128588SDaniel Drake static struct platform_driver cs5535_pms_driver = { 185a3128588SDaniel Drake .driver = { 186a3128588SDaniel Drake .name = "cs5535-pms", 187a3128588SDaniel Drake .owner = THIS_MODULE, 188a3128588SDaniel Drake }, 189a3128588SDaniel Drake .probe = xo1_pm_probe, 190a3128588SDaniel Drake .remove = __devexit_p(xo1_pm_remove), 191a3128588SDaniel Drake }; 192a3128588SDaniel Drake 193a3128588SDaniel Drake static struct platform_driver cs5535_acpi_driver = { 194a3128588SDaniel Drake .driver = { 195a3128588SDaniel Drake .name = "olpc-xo1-pm-acpi", 196a3128588SDaniel Drake .owner = THIS_MODULE, 197a3128588SDaniel Drake }, 198a3128588SDaniel Drake .probe = xo1_pm_probe, 199a3128588SDaniel Drake .remove = __devexit_p(xo1_pm_remove), 200a3128588SDaniel Drake }; 201a3128588SDaniel Drake 202a3128588SDaniel Drake static int __init xo1_pm_init(void) 203a3128588SDaniel Drake { 204a3128588SDaniel Drake int r; 205a3128588SDaniel Drake 206a3128588SDaniel Drake r = platform_driver_register(&cs5535_pms_driver); 207a3128588SDaniel Drake if (r) 208a3128588SDaniel Drake return r; 209a3128588SDaniel Drake 210a3128588SDaniel Drake r = platform_driver_register(&cs5535_acpi_driver); 211a3128588SDaniel Drake if (r) 212a3128588SDaniel Drake platform_driver_unregister(&cs5535_pms_driver); 213a3128588SDaniel Drake 214a3128588SDaniel Drake return r; 215a3128588SDaniel Drake } 216a3128588SDaniel Drake arch_initcall(xo1_pm_init); 217