xref: /linux/drivers/soc/loongson/loongson2_pm.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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 
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 
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 
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 
79 static int loongson2_suspend_begin(suspend_state_t state)
80 {
81 	pm_set_suspend_via_firmware();
82 
83 	return 0;
84 }
85 
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 
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 
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 
148 static int __maybe_unused loongson2_pm_suspend(struct device *dev)
149 {
150 	loongson2_pm.suspended = true;
151 
152 	return 0;
153 }
154 
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 
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