1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Loongson-2 PM Support 4 * 5 * Copyright (C) 2023 Loongson Technology Corporation Limited 6 */ 7 8 #include <linux/io.h> 9 #include <linux/of.h> 10 #include <linux/init.h> 11 #include <linux/input.h> 12 #include <linux/suspend.h> 13 #include <linux/interrupt.h> 14 #include <linux/pm_wakeirq.h> 15 #include <linux/platform_device.h> 16 #include <asm/bootinfo.h> 17 #include <asm/suspend.h> 18 19 #define LOONGSON2_PM1_CNT_REG 0x14 20 #define LOONGSON2_PM1_STS_REG 0x0c 21 #define LOONGSON2_PM1_ENA_REG 0x10 22 #define LOONGSON2_GPE0_STS_REG 0x28 23 #define LOONGSON2_GPE0_ENA_REG 0x2c 24 25 #define LOONGSON2_PM1_PWRBTN_STS BIT(8) 26 #define LOONGSON2_PM1_PCIEXP_WAKE_STS BIT(14) 27 #define LOONGSON2_PM1_WAKE_STS BIT(15) 28 #define LOONGSON2_PM1_CNT_INT_EN BIT(0) 29 #define LOONGSON2_PM1_PWRBTN_EN LOONGSON2_PM1_PWRBTN_STS 30 31 static struct loongson2_pm { 32 void __iomem *base; 33 struct input_dev *dev; 34 bool suspended; 35 } loongson2_pm; 36 37 #define loongson2_pm_readw(reg) readw(loongson2_pm.base + reg) 38 #define loongson2_pm_readl(reg) readl(loongson2_pm.base + reg) 39 #define loongson2_pm_writew(val, reg) writew(val, loongson2_pm.base + reg) 40 #define loongson2_pm_writel(val, reg) writel(val, loongson2_pm.base + reg) 41 42 static void loongson2_pm_status_clear(void) 43 { 44 u16 value; 45 46 value = loongson2_pm_readw(LOONGSON2_PM1_STS_REG); 47 value |= (LOONGSON2_PM1_PWRBTN_STS | LOONGSON2_PM1_PCIEXP_WAKE_STS | 48 LOONGSON2_PM1_WAKE_STS); 49 loongson2_pm_writew(value, LOONGSON2_PM1_STS_REG); 50 loongson2_pm_writel(loongson2_pm_readl(LOONGSON2_GPE0_STS_REG), LOONGSON2_GPE0_STS_REG); 51 } 52 53 static void loongson2_pm_irq_enable(void) 54 { 55 u16 value; 56 57 value = loongson2_pm_readw(LOONGSON2_PM1_CNT_REG); 58 value |= LOONGSON2_PM1_CNT_INT_EN; 59 loongson2_pm_writew(value, LOONGSON2_PM1_CNT_REG); 60 61 value = loongson2_pm_readw(LOONGSON2_PM1_ENA_REG); 62 value |= LOONGSON2_PM1_PWRBTN_EN; 63 loongson2_pm_writew(value, LOONGSON2_PM1_ENA_REG); 64 } 65 66 static int loongson2_suspend_enter(suspend_state_t state) 67 { 68 loongson2_pm_status_clear(); 69 loongarch_common_suspend(); 70 loongarch_suspend_enter(); 71 loongarch_common_resume(); 72 loongson2_pm_irq_enable(); 73 pm_set_resume_via_firmware(); 74 75 return 0; 76 } 77 78 static int loongson2_suspend_begin(suspend_state_t state) 79 { 80 pm_set_suspend_via_firmware(); 81 82 return 0; 83 } 84 85 static int loongson2_suspend_valid_state(suspend_state_t state) 86 { 87 return (state == PM_SUSPEND_MEM); 88 } 89 90 static const struct platform_suspend_ops loongson2_suspend_ops = { 91 .valid = loongson2_suspend_valid_state, 92 .begin = loongson2_suspend_begin, 93 .enter = loongson2_suspend_enter, 94 }; 95 96 static int loongson2_power_button_init(struct device *dev, int irq) 97 { 98 int ret; 99 struct input_dev *button; 100 101 button = input_allocate_device(); 102 if (!dev) 103 return -ENOMEM; 104 105 button->name = "Power Button"; 106 button->phys = "pm/button/input0"; 107 button->id.bustype = BUS_HOST; 108 button->dev.parent = NULL; 109 input_set_capability(button, EV_KEY, KEY_POWER); 110 111 ret = input_register_device(button); 112 if (ret) 113 goto free_dev; 114 115 dev_pm_set_wake_irq(&button->dev, irq); 116 device_set_wakeup_capable(&button->dev, true); 117 device_set_wakeup_enable(&button->dev, true); 118 119 loongson2_pm.dev = button; 120 dev_info(dev, "Power Button: Init successful!\n"); 121 122 return 0; 123 124 free_dev: 125 input_free_device(button); 126 127 return ret; 128 } 129 130 static irqreturn_t loongson2_pm_irq_handler(int irq, void *dev_id) 131 { 132 u16 status = loongson2_pm_readw(LOONGSON2_PM1_STS_REG); 133 134 if (!loongson2_pm.suspended && (status & LOONGSON2_PM1_PWRBTN_STS)) { 135 pr_info("Power Button pressed...\n"); 136 input_report_key(loongson2_pm.dev, KEY_POWER, 1); 137 input_sync(loongson2_pm.dev); 138 input_report_key(loongson2_pm.dev, KEY_POWER, 0); 139 input_sync(loongson2_pm.dev); 140 } 141 142 loongson2_pm_status_clear(); 143 144 return IRQ_HANDLED; 145 } 146 147 static int __maybe_unused loongson2_pm_suspend(struct device *dev) 148 { 149 loongson2_pm.suspended = true; 150 151 return 0; 152 } 153 154 static int __maybe_unused loongson2_pm_resume(struct device *dev) 155 { 156 loongson2_pm.suspended = false; 157 158 return 0; 159 } 160 static SIMPLE_DEV_PM_OPS(loongson2_pm_ops, loongson2_pm_suspend, loongson2_pm_resume); 161 162 static int loongson2_pm_probe(struct platform_device *pdev) 163 { 164 int irq, retval; 165 u64 suspend_addr; 166 struct device *dev = &pdev->dev; 167 168 loongson2_pm.base = devm_platform_ioremap_resource(pdev, 0); 169 if (IS_ERR(loongson2_pm.base)) 170 return PTR_ERR(loongson2_pm.base); 171 172 irq = platform_get_irq(pdev, 0); 173 if (irq < 0) 174 return irq; 175 176 if (!device_property_read_u64(dev, "loongson,suspend-address", &suspend_addr)) 177 loongson_sysconf.suspend_addr = (u64)phys_to_virt(suspend_addr); 178 else 179 dev_err(dev, "No loongson,suspend-address, could not support S3!\n"); 180 181 if (loongson2_power_button_init(dev, irq)) 182 return -EINVAL; 183 184 retval = devm_request_irq(&pdev->dev, irq, loongson2_pm_irq_handler, 185 IRQF_SHARED, "pm_irq", &loongson2_pm); 186 if (retval) 187 return retval; 188 189 loongson2_pm_irq_enable(); 190 loongson2_pm_status_clear(); 191 192 if (loongson_sysconf.suspend_addr) 193 suspend_set_ops(&loongson2_suspend_ops); 194 195 return 0; 196 } 197 198 static const struct of_device_id loongson2_pm_match[] = { 199 { .compatible = "loongson,ls2k0500-pmc", }, 200 { .compatible = "loongson,ls2k1000-pmc", }, 201 {}, 202 }; 203 204 static struct platform_driver loongson2_pm_driver = { 205 .driver = { 206 .name = "ls2k-pm", 207 .pm = &loongson2_pm_ops, 208 .of_match_table = loongson2_pm_match, 209 }, 210 .probe = loongson2_pm_probe, 211 }; 212 module_platform_driver(loongson2_pm_driver); 213 214 MODULE_DESCRIPTION("Loongson-2 PM driver"); 215 MODULE_LICENSE("GPL"); 216