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> 17a3128588SDaniel Drake #include <linux/pm.h> 18a3128588SDaniel Drake #include <linux/mfd/core.h> 19*97c4cb71SDaniel Drake #include <linux/suspend.h> 20a3128588SDaniel Drake 21a3128588SDaniel Drake #include <asm/io.h> 22a3128588SDaniel Drake #include <asm/olpc.h> 23a3128588SDaniel Drake 24a3128588SDaniel Drake #define DRV_NAME "olpc-xo1-pm" 25a3128588SDaniel Drake 26a3128588SDaniel Drake static unsigned long acpi_base; 27a3128588SDaniel Drake static unsigned long pms_base; 28a3128588SDaniel Drake 29*97c4cb71SDaniel Drake static u16 wakeup_mask = CS5536_PM_PWRBTN; 30*97c4cb71SDaniel Drake 31*97c4cb71SDaniel Drake static struct { 32*97c4cb71SDaniel Drake unsigned long address; 33*97c4cb71SDaniel Drake unsigned short segment; 34*97c4cb71SDaniel Drake } ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS }; 35*97c4cb71SDaniel Drake 36*97c4cb71SDaniel Drake /* Set bits in the wakeup mask */ 37*97c4cb71SDaniel Drake void olpc_xo1_pm_wakeup_set(u16 value) 38*97c4cb71SDaniel Drake { 39*97c4cb71SDaniel Drake wakeup_mask |= value; 40*97c4cb71SDaniel Drake } 41*97c4cb71SDaniel Drake EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set); 42*97c4cb71SDaniel Drake 43*97c4cb71SDaniel Drake /* Clear bits in the wakeup mask */ 44*97c4cb71SDaniel Drake void olpc_xo1_pm_wakeup_clear(u16 value) 45*97c4cb71SDaniel Drake { 46*97c4cb71SDaniel Drake wakeup_mask &= ~value; 47*97c4cb71SDaniel Drake } 48*97c4cb71SDaniel Drake EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear); 49*97c4cb71SDaniel Drake 50*97c4cb71SDaniel Drake static int xo1_power_state_enter(suspend_state_t pm_state) 51*97c4cb71SDaniel Drake { 52*97c4cb71SDaniel Drake unsigned long saved_sci_mask; 53*97c4cb71SDaniel Drake int r; 54*97c4cb71SDaniel Drake 55*97c4cb71SDaniel Drake /* Only STR is supported */ 56*97c4cb71SDaniel Drake if (pm_state != PM_SUSPEND_MEM) 57*97c4cb71SDaniel Drake return -EINVAL; 58*97c4cb71SDaniel Drake 59*97c4cb71SDaniel Drake r = olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0); 60*97c4cb71SDaniel Drake if (r) 61*97c4cb71SDaniel Drake return r; 62*97c4cb71SDaniel Drake 63*97c4cb71SDaniel Drake /* 64*97c4cb71SDaniel Drake * Save SCI mask (this gets lost since PM1_EN is used as a mask for 65*97c4cb71SDaniel Drake * wakeup events, which is not necessarily the same event set) 66*97c4cb71SDaniel Drake */ 67*97c4cb71SDaniel Drake saved_sci_mask = inl(acpi_base + CS5536_PM1_STS); 68*97c4cb71SDaniel Drake saved_sci_mask &= 0xffff0000; 69*97c4cb71SDaniel Drake 70*97c4cb71SDaniel Drake /* Save CPU state */ 71*97c4cb71SDaniel Drake do_olpc_suspend_lowlevel(); 72*97c4cb71SDaniel Drake 73*97c4cb71SDaniel Drake /* Resume path starts here */ 74*97c4cb71SDaniel Drake 75*97c4cb71SDaniel Drake /* Restore SCI mask (using dword access to CS5536_PM1_EN) */ 76*97c4cb71SDaniel Drake outl(saved_sci_mask, acpi_base + CS5536_PM1_STS); 77*97c4cb71SDaniel Drake 78*97c4cb71SDaniel Drake /* Tell the EC to stop inhibiting SCIs */ 79*97c4cb71SDaniel Drake olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0); 80*97c4cb71SDaniel Drake 81*97c4cb71SDaniel Drake /* 82*97c4cb71SDaniel Drake * Tell the wireless module to restart USB communication. 83*97c4cb71SDaniel Drake * Must be done twice. 84*97c4cb71SDaniel Drake */ 85*97c4cb71SDaniel Drake olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0); 86*97c4cb71SDaniel Drake olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0); 87*97c4cb71SDaniel Drake 88*97c4cb71SDaniel Drake return 0; 89*97c4cb71SDaniel Drake } 90*97c4cb71SDaniel Drake 91*97c4cb71SDaniel Drake asmlinkage int xo1_do_sleep(u8 sleep_state) 92*97c4cb71SDaniel Drake { 93*97c4cb71SDaniel Drake void *pgd_addr = __va(read_cr3()); 94*97c4cb71SDaniel Drake 95*97c4cb71SDaniel Drake /* Program wakeup mask (using dword access to CS5536_PM1_EN) */ 96*97c4cb71SDaniel Drake outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS); 97*97c4cb71SDaniel Drake 98*97c4cb71SDaniel Drake __asm__("movl %0,%%eax" : : "r" (pgd_addr)); 99*97c4cb71SDaniel Drake __asm__("call *(%%edi); cld" 100*97c4cb71SDaniel Drake : : "D" (&ofw_bios_entry)); 101*97c4cb71SDaniel Drake __asm__("movb $0x34, %al\n\t" 102*97c4cb71SDaniel Drake "outb %al, $0x70\n\t" 103*97c4cb71SDaniel Drake "movb $0x30, %al\n\t" 104*97c4cb71SDaniel Drake "outb %al, $0x71\n\t"); 105*97c4cb71SDaniel Drake return 0; 106*97c4cb71SDaniel Drake } 107*97c4cb71SDaniel Drake 108a3128588SDaniel Drake static void xo1_power_off(void) 109a3128588SDaniel Drake { 110a3128588SDaniel Drake printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); 111a3128588SDaniel Drake 112a3128588SDaniel Drake /* Enable all of these controls with 0 delay */ 113a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_SCLK); 114a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL); 115a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_WKXD); 116a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_WKD); 117a3128588SDaniel Drake 118a3128588SDaniel Drake /* Clear status bits (possibly unnecessary) */ 119a3128588SDaniel Drake outl(0x0002ffff, pms_base + CS5536_PM_SSC); 120a3128588SDaniel Drake outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); 121a3128588SDaniel Drake 122a3128588SDaniel Drake /* Write SLP_EN bit to start the machinery */ 123a3128588SDaniel Drake outl(0x00002000, acpi_base + CS5536_PM1_CNT); 124a3128588SDaniel Drake } 125a3128588SDaniel Drake 126*97c4cb71SDaniel Drake static int xo1_power_state_valid(suspend_state_t pm_state) 127*97c4cb71SDaniel Drake { 128*97c4cb71SDaniel Drake /* suspend-to-RAM only */ 129*97c4cb71SDaniel Drake return pm_state == PM_SUSPEND_MEM; 130*97c4cb71SDaniel Drake } 131*97c4cb71SDaniel Drake 132*97c4cb71SDaniel Drake static const struct platform_suspend_ops xo1_suspend_ops = { 133*97c4cb71SDaniel Drake .valid = xo1_power_state_valid, 134*97c4cb71SDaniel Drake .enter = xo1_power_state_enter, 135*97c4cb71SDaniel Drake }; 136*97c4cb71SDaniel Drake 137a3128588SDaniel Drake static int __devinit xo1_pm_probe(struct platform_device *pdev) 138a3128588SDaniel Drake { 139a3128588SDaniel Drake struct resource *res; 140a3128588SDaniel Drake int err; 141a3128588SDaniel Drake 142a3128588SDaniel Drake /* don't run on non-XOs */ 143a3128588SDaniel Drake if (!machine_is_olpc()) 144a3128588SDaniel Drake return -ENODEV; 145a3128588SDaniel Drake 146a3128588SDaniel Drake err = mfd_cell_enable(pdev); 147a3128588SDaniel Drake if (err) 148a3128588SDaniel Drake return err; 149a3128588SDaniel Drake 150a3128588SDaniel Drake res = platform_get_resource(pdev, IORESOURCE_IO, 0); 151a3128588SDaniel Drake if (!res) { 152a3128588SDaniel Drake dev_err(&pdev->dev, "can't fetch device resource info\n"); 153a3128588SDaniel Drake return -EIO; 154a3128588SDaniel Drake } 155a3128588SDaniel Drake if (strcmp(pdev->name, "cs5535-pms") == 0) 156a3128588SDaniel Drake pms_base = res->start; 157a3128588SDaniel Drake else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 158a3128588SDaniel Drake acpi_base = res->start; 159a3128588SDaniel Drake 160a3128588SDaniel Drake /* If we have both addresses, we can override the poweroff hook */ 161a3128588SDaniel Drake if (pms_base && acpi_base) { 162*97c4cb71SDaniel Drake suspend_set_ops(&xo1_suspend_ops); 163a3128588SDaniel Drake pm_power_off = xo1_power_off; 164a3128588SDaniel Drake printk(KERN_INFO "OLPC XO-1 support registered\n"); 165a3128588SDaniel Drake } 166a3128588SDaniel Drake 167a3128588SDaniel Drake return 0; 168a3128588SDaniel Drake } 169a3128588SDaniel Drake 170a3128588SDaniel Drake static int __devexit xo1_pm_remove(struct platform_device *pdev) 171a3128588SDaniel Drake { 172a3128588SDaniel Drake mfd_cell_disable(pdev); 173a3128588SDaniel Drake 174a3128588SDaniel Drake if (strcmp(pdev->name, "cs5535-pms") == 0) 175a3128588SDaniel Drake pms_base = 0; 176a3128588SDaniel Drake else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 177a3128588SDaniel Drake acpi_base = 0; 178a3128588SDaniel Drake 179a3128588SDaniel Drake pm_power_off = NULL; 180a3128588SDaniel Drake return 0; 181a3128588SDaniel Drake } 182a3128588SDaniel Drake 183a3128588SDaniel Drake static struct platform_driver cs5535_pms_driver = { 184a3128588SDaniel Drake .driver = { 185a3128588SDaniel Drake .name = "cs5535-pms", 186a3128588SDaniel Drake .owner = THIS_MODULE, 187a3128588SDaniel Drake }, 188a3128588SDaniel Drake .probe = xo1_pm_probe, 189a3128588SDaniel Drake .remove = __devexit_p(xo1_pm_remove), 190a3128588SDaniel Drake }; 191a3128588SDaniel Drake 192a3128588SDaniel Drake static struct platform_driver cs5535_acpi_driver = { 193a3128588SDaniel Drake .driver = { 194a3128588SDaniel Drake .name = "olpc-xo1-pm-acpi", 195a3128588SDaniel Drake .owner = THIS_MODULE, 196a3128588SDaniel Drake }, 197a3128588SDaniel Drake .probe = xo1_pm_probe, 198a3128588SDaniel Drake .remove = __devexit_p(xo1_pm_remove), 199a3128588SDaniel Drake }; 200a3128588SDaniel Drake 201a3128588SDaniel Drake static int __init xo1_pm_init(void) 202a3128588SDaniel Drake { 203a3128588SDaniel Drake int r; 204a3128588SDaniel Drake 205a3128588SDaniel Drake r = platform_driver_register(&cs5535_pms_driver); 206a3128588SDaniel Drake if (r) 207a3128588SDaniel Drake return r; 208a3128588SDaniel Drake 209a3128588SDaniel Drake r = platform_driver_register(&cs5535_acpi_driver); 210a3128588SDaniel Drake if (r) 211a3128588SDaniel Drake platform_driver_unregister(&cs5535_pms_driver); 212a3128588SDaniel Drake 213a3128588SDaniel Drake return r; 214a3128588SDaniel Drake } 215a3128588SDaniel Drake arch_initcall(xo1_pm_init); 216