GNU Linux-libre 6.7.9-gnu
[releases.git] / drivers / soc / loongson / loongson2_pm.c
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");