12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2a3128588SDaniel Drake /* 3a3128588SDaniel Drake * Support for power management features of the OLPC XO-1 laptop 4a3128588SDaniel Drake * 5a3128588SDaniel Drake * Copyright (C) 2010 Andres Salomon <dilinger@queued.net> 6a3128588SDaniel Drake * Copyright (C) 2010 One Laptop per Child 7a3128588SDaniel Drake * Copyright (C) 2006 Red Hat, Inc. 8a3128588SDaniel Drake * Copyright (C) 2006 Advanced Micro Devices, Inc. 9a3128588SDaniel Drake */ 10a3128588SDaniel Drake 11a3128588SDaniel Drake #include <linux/cs5535.h> 12a3128588SDaniel Drake #include <linux/platform_device.h> 1369c60c88SPaul Gortmaker #include <linux/export.h> 14a3128588SDaniel Drake #include <linux/pm.h> 1597c4cb71SDaniel Drake #include <linux/suspend.h> 163bf9428fSAndres Salomon #include <linux/olpc-ec.h> 17a3128588SDaniel Drake 18a3128588SDaniel Drake #include <asm/io.h> 19a3128588SDaniel Drake #include <asm/olpc.h> 20a3128588SDaniel Drake 21a3128588SDaniel Drake #define DRV_NAME "olpc-xo1-pm" 22a3128588SDaniel Drake 23a3128588SDaniel Drake static unsigned long acpi_base; 24a3128588SDaniel Drake static unsigned long pms_base; 25a3128588SDaniel Drake 2697c4cb71SDaniel Drake static u16 wakeup_mask = CS5536_PM_PWRBTN; 2797c4cb71SDaniel Drake 2897c4cb71SDaniel Drake static struct { 2997c4cb71SDaniel Drake unsigned long address; 3097c4cb71SDaniel Drake unsigned short segment; 3197c4cb71SDaniel Drake } ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS }; 3297c4cb71SDaniel Drake 3397c4cb71SDaniel Drake /* Set bits in the wakeup mask */ 3497c4cb71SDaniel Drake void olpc_xo1_pm_wakeup_set(u16 value) 3597c4cb71SDaniel Drake { 3697c4cb71SDaniel Drake wakeup_mask |= value; 3797c4cb71SDaniel Drake } 3897c4cb71SDaniel Drake EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set); 3997c4cb71SDaniel Drake 4097c4cb71SDaniel Drake /* Clear bits in the wakeup mask */ 4197c4cb71SDaniel Drake void olpc_xo1_pm_wakeup_clear(u16 value) 4297c4cb71SDaniel Drake { 4397c4cb71SDaniel Drake wakeup_mask &= ~value; 4497c4cb71SDaniel Drake } 4597c4cb71SDaniel Drake EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear); 4697c4cb71SDaniel Drake 4797c4cb71SDaniel Drake static int xo1_power_state_enter(suspend_state_t pm_state) 4897c4cb71SDaniel Drake { 4997c4cb71SDaniel Drake unsigned long saved_sci_mask; 5097c4cb71SDaniel Drake 5197c4cb71SDaniel Drake /* Only STR is supported */ 5297c4cb71SDaniel Drake if (pm_state != PM_SUSPEND_MEM) 5397c4cb71SDaniel Drake return -EINVAL; 5497c4cb71SDaniel Drake 5597c4cb71SDaniel Drake /* 5697c4cb71SDaniel Drake * Save SCI mask (this gets lost since PM1_EN is used as a mask for 5797c4cb71SDaniel Drake * wakeup events, which is not necessarily the same event set) 5897c4cb71SDaniel Drake */ 5997c4cb71SDaniel Drake saved_sci_mask = inl(acpi_base + CS5536_PM1_STS); 6097c4cb71SDaniel Drake saved_sci_mask &= 0xffff0000; 6197c4cb71SDaniel Drake 6297c4cb71SDaniel Drake /* Save CPU state */ 6397c4cb71SDaniel Drake do_olpc_suspend_lowlevel(); 6497c4cb71SDaniel Drake 6597c4cb71SDaniel Drake /* Resume path starts here */ 6697c4cb71SDaniel Drake 6797c4cb71SDaniel Drake /* Restore SCI mask (using dword access to CS5536_PM1_EN) */ 6897c4cb71SDaniel Drake outl(saved_sci_mask, acpi_base + CS5536_PM1_STS); 6997c4cb71SDaniel Drake 7097c4cb71SDaniel Drake return 0; 7197c4cb71SDaniel Drake } 7297c4cb71SDaniel Drake 732605fc21SAndi Kleen asmlinkage __visible int xo1_do_sleep(u8 sleep_state) 7497c4cb71SDaniel Drake { 756c690ee1SAndy Lutomirski void *pgd_addr = __va(read_cr3_pa()); 7697c4cb71SDaniel Drake 7797c4cb71SDaniel Drake /* Program wakeup mask (using dword access to CS5536_PM1_EN) */ 7897c4cb71SDaniel Drake outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS); 7997c4cb71SDaniel Drake 8097c4cb71SDaniel Drake __asm__("movl %0,%%eax" : : "r" (pgd_addr)); 8197c4cb71SDaniel Drake __asm__("call *(%%edi); cld" 8297c4cb71SDaniel Drake : : "D" (&ofw_bios_entry)); 8397c4cb71SDaniel Drake __asm__("movb $0x34, %al\n\t" 8497c4cb71SDaniel Drake "outb %al, $0x70\n\t" 8597c4cb71SDaniel Drake "movb $0x30, %al\n\t" 8697c4cb71SDaniel Drake "outb %al, $0x71\n\t"); 8797c4cb71SDaniel Drake return 0; 8897c4cb71SDaniel Drake } 8997c4cb71SDaniel Drake 90a3128588SDaniel Drake static void xo1_power_off(void) 91a3128588SDaniel Drake { 92a3128588SDaniel Drake printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); 93a3128588SDaniel Drake 94a3128588SDaniel Drake /* Enable all of these controls with 0 delay */ 95a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_SCLK); 96a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL); 97a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_WKXD); 98a3128588SDaniel Drake outl(0x40000000, pms_base + CS5536_PM_WKD); 99a3128588SDaniel Drake 100a3128588SDaniel Drake /* Clear status bits (possibly unnecessary) */ 101a3128588SDaniel Drake outl(0x0002ffff, pms_base + CS5536_PM_SSC); 102a3128588SDaniel Drake outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); 103a3128588SDaniel Drake 104a3128588SDaniel Drake /* Write SLP_EN bit to start the machinery */ 105a3128588SDaniel Drake outl(0x00002000, acpi_base + CS5536_PM1_CNT); 106a3128588SDaniel Drake } 107a3128588SDaniel Drake 10897c4cb71SDaniel Drake static int xo1_power_state_valid(suspend_state_t pm_state) 10997c4cb71SDaniel Drake { 11097c4cb71SDaniel Drake /* suspend-to-RAM only */ 11197c4cb71SDaniel Drake return pm_state == PM_SUSPEND_MEM; 11297c4cb71SDaniel Drake } 11397c4cb71SDaniel Drake 11497c4cb71SDaniel Drake static const struct platform_suspend_ops xo1_suspend_ops = { 11597c4cb71SDaniel Drake .valid = xo1_power_state_valid, 11697c4cb71SDaniel Drake .enter = xo1_power_state_enter, 11797c4cb71SDaniel Drake }; 11897c4cb71SDaniel Drake 119a18e3690SGreg Kroah-Hartman static int xo1_pm_probe(struct platform_device *pdev) 120a3128588SDaniel Drake { 121a3128588SDaniel Drake struct resource *res; 122a3128588SDaniel Drake 123a3128588SDaniel Drake /* don't run on non-XOs */ 124a3128588SDaniel Drake if (!machine_is_olpc()) 125a3128588SDaniel Drake return -ENODEV; 126a3128588SDaniel Drake 127a3128588SDaniel Drake res = platform_get_resource(pdev, IORESOURCE_IO, 0); 128a3128588SDaniel Drake if (!res) { 129a3128588SDaniel Drake dev_err(&pdev->dev, "can't fetch device resource info\n"); 130a3128588SDaniel Drake return -EIO; 131a3128588SDaniel Drake } 132a3128588SDaniel Drake if (strcmp(pdev->name, "cs5535-pms") == 0) 133a3128588SDaniel Drake pms_base = res->start; 134a3128588SDaniel Drake else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 135a3128588SDaniel Drake acpi_base = res->start; 136a3128588SDaniel Drake 137a3128588SDaniel Drake /* If we have both addresses, we can override the poweroff hook */ 138a3128588SDaniel Drake if (pms_base && acpi_base) { 13997c4cb71SDaniel Drake suspend_set_ops(&xo1_suspend_ops); 140a3128588SDaniel Drake pm_power_off = xo1_power_off; 141a3128588SDaniel Drake printk(KERN_INFO "OLPC XO-1 support registered\n"); 142a3128588SDaniel Drake } 143a3128588SDaniel Drake 144a3128588SDaniel Drake return 0; 145a3128588SDaniel Drake } 146a3128588SDaniel Drake 147*c741ccfdSUwe Kleine-König static void xo1_pm_remove(struct platform_device *pdev) 148a3128588SDaniel Drake { 149a3128588SDaniel Drake if (strcmp(pdev->name, "cs5535-pms") == 0) 150a3128588SDaniel Drake pms_base = 0; 151a3128588SDaniel Drake else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 152a3128588SDaniel Drake acpi_base = 0; 153a3128588SDaniel Drake 154a3128588SDaniel Drake pm_power_off = NULL; 155a3128588SDaniel Drake } 156a3128588SDaniel Drake 157a3128588SDaniel Drake static struct platform_driver cs5535_pms_driver = { 158a3128588SDaniel Drake .driver = { 159a3128588SDaniel Drake .name = "cs5535-pms", 160a3128588SDaniel Drake }, 161a3128588SDaniel Drake .probe = xo1_pm_probe, 162*c741ccfdSUwe Kleine-König .remove_new = xo1_pm_remove, 163a3128588SDaniel Drake }; 164a3128588SDaniel Drake 165a3128588SDaniel Drake static struct platform_driver cs5535_acpi_driver = { 166a3128588SDaniel Drake .driver = { 167a3128588SDaniel Drake .name = "olpc-xo1-pm-acpi", 168a3128588SDaniel Drake }, 169a3128588SDaniel Drake .probe = xo1_pm_probe, 170*c741ccfdSUwe Kleine-König .remove_new = xo1_pm_remove, 171a3128588SDaniel Drake }; 172a3128588SDaniel Drake 173a3128588SDaniel Drake static int __init xo1_pm_init(void) 174a3128588SDaniel Drake { 175a3128588SDaniel Drake int r; 176a3128588SDaniel Drake 177a3128588SDaniel Drake r = platform_driver_register(&cs5535_pms_driver); 178a3128588SDaniel Drake if (r) 179a3128588SDaniel Drake return r; 180a3128588SDaniel Drake 181a3128588SDaniel Drake r = platform_driver_register(&cs5535_acpi_driver); 182a3128588SDaniel Drake if (r) 183a3128588SDaniel Drake platform_driver_unregister(&cs5535_pms_driver); 184a3128588SDaniel Drake 185a3128588SDaniel Drake return r; 186a3128588SDaniel Drake } 187a3128588SDaniel Drake arch_initcall(xo1_pm_init); 188