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