Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Support for power management features of the OLPC XO-1 laptop
0004  *
0005  * Copyright (C) 2010 Andres Salomon <dilinger@queued.net>
0006  * Copyright (C) 2010 One Laptop per Child
0007  * Copyright (C) 2006 Red Hat, Inc.
0008  * Copyright (C) 2006 Advanced Micro Devices, Inc.
0009  */
0010 
0011 #include <linux/cs5535.h>
0012 #include <linux/platform_device.h>
0013 #include <linux/export.h>
0014 #include <linux/pm.h>
0015 #include <linux/suspend.h>
0016 #include <linux/olpc-ec.h>
0017 
0018 #include <asm/io.h>
0019 #include <asm/olpc.h>
0020 
0021 #define DRV_NAME "olpc-xo1-pm"
0022 
0023 static unsigned long acpi_base;
0024 static unsigned long pms_base;
0025 
0026 static u16 wakeup_mask = CS5536_PM_PWRBTN;
0027 
0028 static struct {
0029     unsigned long address;
0030     unsigned short segment;
0031 } ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS };
0032 
0033 /* Set bits in the wakeup mask */
0034 void olpc_xo1_pm_wakeup_set(u16 value)
0035 {
0036     wakeup_mask |= value;
0037 }
0038 EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set);
0039 
0040 /* Clear bits in the wakeup mask */
0041 void olpc_xo1_pm_wakeup_clear(u16 value)
0042 {
0043     wakeup_mask &= ~value;
0044 }
0045 EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear);
0046 
0047 static int xo1_power_state_enter(suspend_state_t pm_state)
0048 {
0049     unsigned long saved_sci_mask;
0050 
0051     /* Only STR is supported */
0052     if (pm_state != PM_SUSPEND_MEM)
0053         return -EINVAL;
0054 
0055     /*
0056      * Save SCI mask (this gets lost since PM1_EN is used as a mask for
0057      * wakeup events, which is not necessarily the same event set)
0058      */
0059     saved_sci_mask = inl(acpi_base + CS5536_PM1_STS);
0060     saved_sci_mask &= 0xffff0000;
0061 
0062     /* Save CPU state */
0063     do_olpc_suspend_lowlevel();
0064 
0065     /* Resume path starts here */
0066 
0067     /* Restore SCI mask (using dword access to CS5536_PM1_EN) */
0068     outl(saved_sci_mask, acpi_base + CS5536_PM1_STS);
0069 
0070     return 0;
0071 }
0072 
0073 asmlinkage __visible int xo1_do_sleep(u8 sleep_state)
0074 {
0075     void *pgd_addr = __va(read_cr3_pa());
0076 
0077     /* Program wakeup mask (using dword access to CS5536_PM1_EN) */
0078     outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS);
0079 
0080     __asm__("movl %0,%%eax" : : "r" (pgd_addr));
0081     __asm__("call *(%%edi); cld"
0082         : : "D" (&ofw_bios_entry));
0083     __asm__("movb $0x34, %al\n\t"
0084         "outb %al, $0x70\n\t"
0085         "movb $0x30, %al\n\t"
0086         "outb %al, $0x71\n\t");
0087     return 0;
0088 }
0089 
0090 static void xo1_power_off(void)
0091 {
0092     printk(KERN_INFO "OLPC XO-1 power off sequence...\n");
0093 
0094     /* Enable all of these controls with 0 delay */
0095     outl(0x40000000, pms_base + CS5536_PM_SCLK);
0096     outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL);
0097     outl(0x40000000, pms_base + CS5536_PM_WKXD);
0098     outl(0x40000000, pms_base + CS5536_PM_WKD);
0099 
0100     /* Clear status bits (possibly unnecessary) */
0101     outl(0x0002ffff, pms_base  + CS5536_PM_SSC);
0102     outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS);
0103 
0104     /* Write SLP_EN bit to start the machinery */
0105     outl(0x00002000, acpi_base + CS5536_PM1_CNT);
0106 }
0107 
0108 static int xo1_power_state_valid(suspend_state_t pm_state)
0109 {
0110     /* suspend-to-RAM only */
0111     return pm_state == PM_SUSPEND_MEM;
0112 }
0113 
0114 static const struct platform_suspend_ops xo1_suspend_ops = {
0115     .valid = xo1_power_state_valid,
0116     .enter = xo1_power_state_enter,
0117 };
0118 
0119 static int xo1_pm_probe(struct platform_device *pdev)
0120 {
0121     struct resource *res;
0122 
0123     /* don't run on non-XOs */
0124     if (!machine_is_olpc())
0125         return -ENODEV;
0126 
0127     res = platform_get_resource(pdev, IORESOURCE_IO, 0);
0128     if (!res) {
0129         dev_err(&pdev->dev, "can't fetch device resource info\n");
0130         return -EIO;
0131     }
0132     if (strcmp(pdev->name, "cs5535-pms") == 0)
0133         pms_base = res->start;
0134     else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0)
0135         acpi_base = res->start;
0136 
0137     /* If we have both addresses, we can override the poweroff hook */
0138     if (pms_base && acpi_base) {
0139         suspend_set_ops(&xo1_suspend_ops);
0140         pm_power_off = xo1_power_off;
0141         printk(KERN_INFO "OLPC XO-1 support registered\n");
0142     }
0143 
0144     return 0;
0145 }
0146 
0147 static int xo1_pm_remove(struct platform_device *pdev)
0148 {
0149     if (strcmp(pdev->name, "cs5535-pms") == 0)
0150         pms_base = 0;
0151     else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0)
0152         acpi_base = 0;
0153 
0154     pm_power_off = NULL;
0155     return 0;
0156 }
0157 
0158 static struct platform_driver cs5535_pms_driver = {
0159     .driver = {
0160         .name = "cs5535-pms",
0161     },
0162     .probe = xo1_pm_probe,
0163     .remove = xo1_pm_remove,
0164 };
0165 
0166 static struct platform_driver cs5535_acpi_driver = {
0167     .driver = {
0168         .name = "olpc-xo1-pm-acpi",
0169     },
0170     .probe = xo1_pm_probe,
0171     .remove = xo1_pm_remove,
0172 };
0173 
0174 static int __init xo1_pm_init(void)
0175 {
0176     int r;
0177 
0178     r = platform_driver_register(&cs5535_pms_driver);
0179     if (r)
0180         return r;
0181 
0182     r = platform_driver_register(&cs5535_acpi_driver);
0183     if (r)
0184         platform_driver_unregister(&cs5535_pms_driver);
0185 
0186     return r;
0187 }
0188 arch_initcall(xo1_pm_init);