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> 213bf9428fSAndres 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 5697c4cb71SDaniel Drake /* Only STR is supported */ 5797c4cb71SDaniel Drake if (pm_state != PM_SUSPEND_MEM) 5897c4cb71SDaniel Drake return -EINVAL; 5997c4cb71SDaniel Drake 6097c4cb71SDaniel Drake /* 6197c4cb71SDaniel Drake * Save SCI mask (this gets lost since PM1_EN is used as a mask for 6297c4cb71SDaniel Drake * wakeup events, which is not necessarily the same event set) 6397c4cb71SDaniel Drake */ 6497c4cb71SDaniel Drake saved_sci_mask = inl(acpi_base + CS5536_PM1_STS); 6597c4cb71SDaniel Drake saved_sci_mask &= 0xffff0000; 6697c4cb71SDaniel Drake 6797c4cb71SDaniel Drake /* Save CPU state */ 6897c4cb71SDaniel Drake do_olpc_suspend_lowlevel(); 6997c4cb71SDaniel Drake 7097c4cb71SDaniel Drake /* Resume path starts here */ 7197c4cb71SDaniel Drake 7297c4cb71SDaniel Drake /* Restore SCI mask (using dword access to CS5536_PM1_EN) */ 7397c4cb71SDaniel Drake outl(saved_sci_mask, acpi_base + CS5536_PM1_STS); 7497c4cb71SDaniel Drake 7597c4cb71SDaniel Drake return 0; 7697c4cb71SDaniel Drake } 7797c4cb71SDaniel Drake 782605fc21SAndi Kleen asmlinkage __visible int xo1_do_sleep(u8 sleep_state) 7997c4cb71SDaniel Drake { 80*6c690ee1SAndy Lutomirski void *pgd_addr = __va(read_cr3_pa()); 8197c4cb71SDaniel Drake 8297c4cb71SDaniel Drake /* Program wakeup mask (using dword access to CS5536_PM1_EN) */ 8397c4cb71SDaniel Drake outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS); 8497c4cb71SDaniel Drake 8597c4cb71SDaniel Drake __asm__("movl %0,%%eax" : : "r" (pgd_addr)); 8697c4cb71SDaniel Drake __asm__("call *(%%edi); cld" 8797c4cb71SDaniel Drake : : "D" (&ofw_bios_entry)); 8897c4cb71SDaniel Drake __asm__("movb $0x34, %al\n\t" 8997c4cb71SDaniel Drake "outb %al, $0x70\n\t" 9097c4cb71SDaniel Drake "movb $0x30, %al\n\t" 9197c4cb71SDaniel Drake "outb %al, $0x71\n\t"); 9297c4cb71SDaniel Drake return 0; 9397c4cb71SDaniel Drake } 9497c4cb71SDaniel Drake 95a3128588SDaniel Drake static void xo1_power_off(void) 96a3128588SDaniel Drake { 97a3128588SDaniel Drake printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); 98a3128588SDaniel Drake 99a3128588SDaniel Drake /* Enable all of these controls with 0 delay */ 100a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_SCLK); 101a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL); 102a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_WKXD); 103a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_WKD); 104a3128588SDaniel Drake 105a3128588SDaniel Drake /* Clear status bits (possibly unnecessary) */ 106a3128588SDaniel Drake outl(0x0002ffff, pms_base + CS5536_PM_SSC); 107a3128588SDaniel Drake outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); 108a3128588SDaniel Drake 109a3128588SDaniel Drake /* Write SLP_EN bit to start the machinery */ 110a3128588SDaniel Drake outl(0x00002000, acpi_base + CS5536_PM1_CNT); 111a3128588SDaniel Drake } 112a3128588SDaniel Drake 11397c4cb71SDaniel Drake static int xo1_power_state_valid(suspend_state_t pm_state) 11497c4cb71SDaniel Drake { 11597c4cb71SDaniel Drake /* suspend-to-RAM only */ 11697c4cb71SDaniel Drake return pm_state == PM_SUSPEND_MEM; 11797c4cb71SDaniel Drake } 11897c4cb71SDaniel Drake 11997c4cb71SDaniel Drake static const struct platform_suspend_ops xo1_suspend_ops = { 12097c4cb71SDaniel Drake .valid = xo1_power_state_valid, 12197c4cb71SDaniel Drake .enter = xo1_power_state_enter, 12297c4cb71SDaniel Drake }; 12397c4cb71SDaniel Drake 124a18e3690SGreg Kroah-Hartman static int xo1_pm_probe(struct platform_device *pdev) 125a3128588SDaniel Drake { 126a3128588SDaniel Drake struct resource *res; 127a3128588SDaniel Drake int err; 128a3128588SDaniel Drake 129a3128588SDaniel Drake /* don't run on non-XOs */ 130a3128588SDaniel Drake if (!machine_is_olpc()) 131a3128588SDaniel Drake return -ENODEV; 132a3128588SDaniel Drake 133a3128588SDaniel Drake err = mfd_cell_enable(pdev); 134a3128588SDaniel Drake if (err) 135a3128588SDaniel Drake return err; 136a3128588SDaniel Drake 137a3128588SDaniel Drake res = platform_get_resource(pdev, IORESOURCE_IO, 0); 138a3128588SDaniel Drake if (!res) { 139a3128588SDaniel Drake dev_err(&pdev->dev, "can't fetch device resource info\n"); 140a3128588SDaniel Drake return -EIO; 141a3128588SDaniel Drake } 142a3128588SDaniel Drake if (strcmp(pdev->name, "cs5535-pms") == 0) 143a3128588SDaniel Drake pms_base = res->start; 144a3128588SDaniel Drake else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 145a3128588SDaniel Drake acpi_base = res->start; 146a3128588SDaniel Drake 147a3128588SDaniel Drake /* If we have both addresses, we can override the poweroff hook */ 148a3128588SDaniel Drake if (pms_base && acpi_base) { 14997c4cb71SDaniel Drake suspend_set_ops(&xo1_suspend_ops); 150a3128588SDaniel Drake pm_power_off = xo1_power_off; 151a3128588SDaniel Drake printk(KERN_INFO "OLPC XO-1 support registered\n"); 152a3128588SDaniel Drake } 153a3128588SDaniel Drake 154a3128588SDaniel Drake return 0; 155a3128588SDaniel Drake } 156a3128588SDaniel Drake 157a18e3690SGreg Kroah-Hartman static int xo1_pm_remove(struct platform_device *pdev) 158a3128588SDaniel Drake { 159a3128588SDaniel Drake mfd_cell_disable(pdev); 160a3128588SDaniel Drake 161a3128588SDaniel Drake if (strcmp(pdev->name, "cs5535-pms") == 0) 162a3128588SDaniel Drake pms_base = 0; 163a3128588SDaniel Drake else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 164a3128588SDaniel Drake acpi_base = 0; 165a3128588SDaniel Drake 166a3128588SDaniel Drake pm_power_off = NULL; 167a3128588SDaniel Drake return 0; 168a3128588SDaniel Drake } 169a3128588SDaniel Drake 170a3128588SDaniel Drake static struct platform_driver cs5535_pms_driver = { 171a3128588SDaniel Drake .driver = { 172a3128588SDaniel Drake .name = "cs5535-pms", 173a3128588SDaniel Drake }, 174a3128588SDaniel Drake .probe = xo1_pm_probe, 175a18e3690SGreg Kroah-Hartman .remove = xo1_pm_remove, 176a3128588SDaniel Drake }; 177a3128588SDaniel Drake 178a3128588SDaniel Drake static struct platform_driver cs5535_acpi_driver = { 179a3128588SDaniel Drake .driver = { 180a3128588SDaniel Drake .name = "olpc-xo1-pm-acpi", 181a3128588SDaniel Drake }, 182a3128588SDaniel Drake .probe = xo1_pm_probe, 183a18e3690SGreg Kroah-Hartman .remove = xo1_pm_remove, 184a3128588SDaniel Drake }; 185a3128588SDaniel Drake 186a3128588SDaniel Drake static int __init xo1_pm_init(void) 187a3128588SDaniel Drake { 188a3128588SDaniel Drake int r; 189a3128588SDaniel Drake 190a3128588SDaniel Drake r = platform_driver_register(&cs5535_pms_driver); 191a3128588SDaniel Drake if (r) 192a3128588SDaniel Drake return r; 193a3128588SDaniel Drake 194a3128588SDaniel Drake r = platform_driver_register(&cs5535_acpi_driver); 195a3128588SDaniel Drake if (r) 196a3128588SDaniel Drake platform_driver_unregister(&cs5535_pms_driver); 197a3128588SDaniel Drake 198a3128588SDaniel Drake return r; 199a3128588SDaniel Drake } 200a3128588SDaniel Drake arch_initcall(xo1_pm_init); 201