162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Support for power management features of the OLPC XO-1 laptop 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2010 Andres Salomon <dilinger@queued.net> 662306a36Sopenharmony_ci * Copyright (C) 2010 One Laptop per Child 762306a36Sopenharmony_ci * Copyright (C) 2006 Red Hat, Inc. 862306a36Sopenharmony_ci * Copyright (C) 2006 Advanced Micro Devices, Inc. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/cs5535.h> 1262306a36Sopenharmony_ci#include <linux/platform_device.h> 1362306a36Sopenharmony_ci#include <linux/export.h> 1462306a36Sopenharmony_ci#include <linux/pm.h> 1562306a36Sopenharmony_ci#include <linux/suspend.h> 1662306a36Sopenharmony_ci#include <linux/olpc-ec.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <asm/io.h> 1962306a36Sopenharmony_ci#include <asm/olpc.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define DRV_NAME "olpc-xo1-pm" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic unsigned long acpi_base; 2462306a36Sopenharmony_cistatic unsigned long pms_base; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic u16 wakeup_mask = CS5536_PM_PWRBTN; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic struct { 2962306a36Sopenharmony_ci unsigned long address; 3062306a36Sopenharmony_ci unsigned short segment; 3162306a36Sopenharmony_ci} ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS }; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* Set bits in the wakeup mask */ 3462306a36Sopenharmony_civoid olpc_xo1_pm_wakeup_set(u16 value) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci wakeup_mask |= value; 3762306a36Sopenharmony_ci} 3862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set); 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/* Clear bits in the wakeup mask */ 4162306a36Sopenharmony_civoid olpc_xo1_pm_wakeup_clear(u16 value) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci wakeup_mask &= ~value; 4462306a36Sopenharmony_ci} 4562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear); 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic int xo1_power_state_enter(suspend_state_t pm_state) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci unsigned long saved_sci_mask; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci /* Only STR is supported */ 5262306a36Sopenharmony_ci if (pm_state != PM_SUSPEND_MEM) 5362306a36Sopenharmony_ci return -EINVAL; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci /* 5662306a36Sopenharmony_ci * Save SCI mask (this gets lost since PM1_EN is used as a mask for 5762306a36Sopenharmony_ci * wakeup events, which is not necessarily the same event set) 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_ci saved_sci_mask = inl(acpi_base + CS5536_PM1_STS); 6062306a36Sopenharmony_ci saved_sci_mask &= 0xffff0000; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* Save CPU state */ 6362306a36Sopenharmony_ci do_olpc_suspend_lowlevel(); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* Resume path starts here */ 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci /* Restore SCI mask (using dword access to CS5536_PM1_EN) */ 6862306a36Sopenharmony_ci outl(saved_sci_mask, acpi_base + CS5536_PM1_STS); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci return 0; 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ciasmlinkage __visible int xo1_do_sleep(u8 sleep_state) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci void *pgd_addr = __va(read_cr3_pa()); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci /* Program wakeup mask (using dword access to CS5536_PM1_EN) */ 7862306a36Sopenharmony_ci outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci __asm__("movl %0,%%eax" : : "r" (pgd_addr)); 8162306a36Sopenharmony_ci __asm__("call *(%%edi); cld" 8262306a36Sopenharmony_ci : : "D" (&ofw_bios_entry)); 8362306a36Sopenharmony_ci __asm__("movb $0x34, %al\n\t" 8462306a36Sopenharmony_ci "outb %al, $0x70\n\t" 8562306a36Sopenharmony_ci "movb $0x30, %al\n\t" 8662306a36Sopenharmony_ci "outb %al, $0x71\n\t"); 8762306a36Sopenharmony_ci return 0; 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic void xo1_power_off(void) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* Enable all of these controls with 0 delay */ 9562306a36Sopenharmony_ci outl(0x40000000, pms_base + CS5536_PM_SCLK); 9662306a36Sopenharmony_ci outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL); 9762306a36Sopenharmony_ci outl(0x40000000, pms_base + CS5536_PM_WKXD); 9862306a36Sopenharmony_ci outl(0x40000000, pms_base + CS5536_PM_WKD); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci /* Clear status bits (possibly unnecessary) */ 10162306a36Sopenharmony_ci outl(0x0002ffff, pms_base + CS5536_PM_SSC); 10262306a36Sopenharmony_ci outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci /* Write SLP_EN bit to start the machinery */ 10562306a36Sopenharmony_ci outl(0x00002000, acpi_base + CS5536_PM1_CNT); 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic int xo1_power_state_valid(suspend_state_t pm_state) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci /* suspend-to-RAM only */ 11162306a36Sopenharmony_ci return pm_state == PM_SUSPEND_MEM; 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic const struct platform_suspend_ops xo1_suspend_ops = { 11562306a36Sopenharmony_ci .valid = xo1_power_state_valid, 11662306a36Sopenharmony_ci .enter = xo1_power_state_enter, 11762306a36Sopenharmony_ci}; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic int xo1_pm_probe(struct platform_device *pdev) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci struct resource *res; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci /* don't run on non-XOs */ 12462306a36Sopenharmony_ci if (!machine_is_olpc()) 12562306a36Sopenharmony_ci return -ENODEV; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_IO, 0); 12862306a36Sopenharmony_ci if (!res) { 12962306a36Sopenharmony_ci dev_err(&pdev->dev, "can't fetch device resource info\n"); 13062306a36Sopenharmony_ci return -EIO; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci if (strcmp(pdev->name, "cs5535-pms") == 0) 13362306a36Sopenharmony_ci pms_base = res->start; 13462306a36Sopenharmony_ci else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 13562306a36Sopenharmony_ci acpi_base = res->start; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* If we have both addresses, we can override the poweroff hook */ 13862306a36Sopenharmony_ci if (pms_base && acpi_base) { 13962306a36Sopenharmony_ci suspend_set_ops(&xo1_suspend_ops); 14062306a36Sopenharmony_ci pm_power_off = xo1_power_off; 14162306a36Sopenharmony_ci printk(KERN_INFO "OLPC XO-1 support registered\n"); 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic int xo1_pm_remove(struct platform_device *pdev) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci if (strcmp(pdev->name, "cs5535-pms") == 0) 15062306a36Sopenharmony_ci pms_base = 0; 15162306a36Sopenharmony_ci else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) 15262306a36Sopenharmony_ci acpi_base = 0; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci pm_power_off = NULL; 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic struct platform_driver cs5535_pms_driver = { 15962306a36Sopenharmony_ci .driver = { 16062306a36Sopenharmony_ci .name = "cs5535-pms", 16162306a36Sopenharmony_ci }, 16262306a36Sopenharmony_ci .probe = xo1_pm_probe, 16362306a36Sopenharmony_ci .remove = xo1_pm_remove, 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic struct platform_driver cs5535_acpi_driver = { 16762306a36Sopenharmony_ci .driver = { 16862306a36Sopenharmony_ci .name = "olpc-xo1-pm-acpi", 16962306a36Sopenharmony_ci }, 17062306a36Sopenharmony_ci .probe = xo1_pm_probe, 17162306a36Sopenharmony_ci .remove = xo1_pm_remove, 17262306a36Sopenharmony_ci}; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic int __init xo1_pm_init(void) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci int r; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci r = platform_driver_register(&cs5535_pms_driver); 17962306a36Sopenharmony_ci if (r) 18062306a36Sopenharmony_ci return r; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci r = platform_driver_register(&cs5535_acpi_driver); 18362306a36Sopenharmony_ci if (r) 18462306a36Sopenharmony_ci platform_driver_unregister(&cs5535_pms_driver); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci return r; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ciarch_initcall(xo1_pm_init); 189