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
32 static 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
loongson2_pm_status_clear(void)43 static 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
loongson2_pm_irq_enable(void)54 static 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
loongson2_suspend_enter(suspend_state_t state)67 static 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
loongson2_suspend_begin(suspend_state_t state)79 static int loongson2_suspend_begin(suspend_state_t state)
80 {
81 pm_set_suspend_via_firmware();
82
83 return 0;
84 }
85
loongson2_suspend_valid_state(suspend_state_t state)86 static int loongson2_suspend_valid_state(suspend_state_t state)
87 {
88 return (state == PM_SUSPEND_MEM);
89 }
90
91 static 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
loongson2_power_button_init(struct device * dev,int irq)97 static 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
125 free_dev:
126 input_free_device(button);
127
128 return ret;
129 }
130
loongson2_pm_irq_handler(int irq,void * dev_id)131 static 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
loongson2_pm_suspend(struct device * dev)148 static int __maybe_unused loongson2_pm_suspend(struct device *dev)
149 {
150 loongson2_pm.suspended = true;
151
152 return 0;
153 }
154
loongson2_pm_resume(struct device * dev)155 static int __maybe_unused loongson2_pm_resume(struct device *dev)
156 {
157 loongson2_pm.suspended = false;
158
159 return 0;
160 }
161 static SIMPLE_DEV_PM_OPS(loongson2_pm_ops, loongson2_pm_suspend, loongson2_pm_resume);
162
loongson2_pm_probe(struct platform_device * pdev)163 static 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
204 static const struct of_device_id loongson2_pm_match[] = {
205 { .compatible = "loongson,ls2k0500-pmc", },
206 {},
207 };
208
209 static 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 };
217 module_platform_driver(loongson2_pm_driver);
218
219 MODULE_DESCRIPTION("Loongson-2 PM driver");
220 MODULE_LICENSE("GPL");
221