File Index
Files and directories
Sign in
FILE

loongson2_pm.c

File preview with syntax highlighting.

linux-6.19.14/drivers/soc/loongson/loongson2_pm.c 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
 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(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
125free_dev:
126	input_free_device(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(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
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, 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
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");
~/ Read-only file preview.