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(dev: 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(dev: &button->dev, irq);
117 device_set_wakeup_capable(dev: &button->dev, capable: true);
118 device_set_wakeup_enable(dev: &button->dev, enable: 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(dev: 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(dev: loongson2_pm.dev, KEY_POWER, value: 1);
138 input_sync(dev: loongson2_pm.dev);
139 input_report_key(dev: loongson2_pm.dev, KEY_POWER, value: 0);
140 input_sync(dev: 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, index: 0);
170 if (IS_ERR(ptr: loongson2_pm.base))
171 return PTR_ERR(ptr: 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, propname: "loongson,suspend-address", val: &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(dev: &pdev->dev, irq, handler: loongson2_pm_irq_handler,
186 IRQF_SHARED, devname: "pm_irq", dev_id: &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(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

source code of linux/drivers/soc/loongson/loongson2_pm.c