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