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