167694c07SYinbo Zhu // SPDX-License-Identifier: GPL-2.0+
267694c07SYinbo Zhu /*
367694c07SYinbo Zhu  * Loongson-2 PM Support
467694c07SYinbo Zhu  *
567694c07SYinbo Zhu  * Copyright (C) 2023 Loongson Technology Corporation Limited
667694c07SYinbo Zhu  */
767694c07SYinbo Zhu 
867694c07SYinbo Zhu #include <linux/io.h>
967694c07SYinbo Zhu #include <linux/of.h>
1067694c07SYinbo Zhu #include <linux/init.h>
1167694c07SYinbo Zhu #include <linux/input.h>
1267694c07SYinbo Zhu #include <linux/suspend.h>
1367694c07SYinbo Zhu #include <linux/interrupt.h>
14*a2fd5422SBinbin Zhou #include <linux/of_platform.h>
1567694c07SYinbo Zhu #include <linux/pm_wakeirq.h>
1667694c07SYinbo Zhu #include <linux/platform_device.h>
1767694c07SYinbo Zhu #include <asm/bootinfo.h>
1867694c07SYinbo Zhu #include <asm/suspend.h>
1967694c07SYinbo Zhu 
2067694c07SYinbo Zhu #define LOONGSON2_PM1_CNT_REG		0x14
2167694c07SYinbo Zhu #define LOONGSON2_PM1_STS_REG		0x0c
2267694c07SYinbo Zhu #define LOONGSON2_PM1_ENA_REG		0x10
2367694c07SYinbo Zhu #define LOONGSON2_GPE0_STS_REG		0x28
2467694c07SYinbo Zhu #define LOONGSON2_GPE0_ENA_REG		0x2c
2567694c07SYinbo Zhu 
2667694c07SYinbo Zhu #define LOONGSON2_PM1_PWRBTN_STS	BIT(8)
2767694c07SYinbo Zhu #define LOONGSON2_PM1_PCIEXP_WAKE_STS	BIT(14)
2867694c07SYinbo Zhu #define LOONGSON2_PM1_WAKE_STS		BIT(15)
2967694c07SYinbo Zhu #define LOONGSON2_PM1_CNT_INT_EN	BIT(0)
3067694c07SYinbo Zhu #define LOONGSON2_PM1_PWRBTN_EN		LOONGSON2_PM1_PWRBTN_STS
3167694c07SYinbo Zhu 
3267694c07SYinbo Zhu static struct loongson2_pm {
3367694c07SYinbo Zhu 	void __iomem			*base;
3467694c07SYinbo Zhu 	struct input_dev		*dev;
3567694c07SYinbo Zhu 	bool				suspended;
3667694c07SYinbo Zhu } loongson2_pm;
3767694c07SYinbo Zhu 
3867694c07SYinbo Zhu #define loongson2_pm_readw(reg)		readw(loongson2_pm.base + reg)
3967694c07SYinbo Zhu #define loongson2_pm_readl(reg)		readl(loongson2_pm.base + reg)
4067694c07SYinbo Zhu #define loongson2_pm_writew(val, reg)	writew(val, loongson2_pm.base + reg)
4167694c07SYinbo Zhu #define loongson2_pm_writel(val, reg)	writel(val, loongson2_pm.base + reg)
4267694c07SYinbo Zhu 
loongson2_pm_status_clear(void)4367694c07SYinbo Zhu static void loongson2_pm_status_clear(void)
4467694c07SYinbo Zhu {
4567694c07SYinbo Zhu 	u16 value;
4667694c07SYinbo Zhu 
4767694c07SYinbo Zhu 	value = loongson2_pm_readw(LOONGSON2_PM1_STS_REG);
4867694c07SYinbo Zhu 	value |= (LOONGSON2_PM1_PWRBTN_STS | LOONGSON2_PM1_PCIEXP_WAKE_STS |
4967694c07SYinbo Zhu 		  LOONGSON2_PM1_WAKE_STS);
5067694c07SYinbo Zhu 	loongson2_pm_writew(value, LOONGSON2_PM1_STS_REG);
5167694c07SYinbo Zhu 	loongson2_pm_writel(loongson2_pm_readl(LOONGSON2_GPE0_STS_REG), LOONGSON2_GPE0_STS_REG);
5267694c07SYinbo Zhu }
5367694c07SYinbo Zhu 
loongson2_pm_irq_enable(void)5467694c07SYinbo Zhu static void loongson2_pm_irq_enable(void)
5567694c07SYinbo Zhu {
5667694c07SYinbo Zhu 	u16 value;
5767694c07SYinbo Zhu 
5867694c07SYinbo Zhu 	value = loongson2_pm_readw(LOONGSON2_PM1_CNT_REG);
5967694c07SYinbo Zhu 	value |= LOONGSON2_PM1_CNT_INT_EN;
6067694c07SYinbo Zhu 	loongson2_pm_writew(value, LOONGSON2_PM1_CNT_REG);
6167694c07SYinbo Zhu 
6267694c07SYinbo Zhu 	value = loongson2_pm_readw(LOONGSON2_PM1_ENA_REG);
6367694c07SYinbo Zhu 	value |= LOONGSON2_PM1_PWRBTN_EN;
6467694c07SYinbo Zhu 	loongson2_pm_writew(value, LOONGSON2_PM1_ENA_REG);
6567694c07SYinbo Zhu }
6667694c07SYinbo Zhu 
loongson2_suspend_enter(suspend_state_t state)6767694c07SYinbo Zhu static int loongson2_suspend_enter(suspend_state_t state)
6867694c07SYinbo Zhu {
6967694c07SYinbo Zhu 	loongson2_pm_status_clear();
7067694c07SYinbo Zhu 	loongarch_common_suspend();
7167694c07SYinbo Zhu 	loongarch_suspend_enter();
7267694c07SYinbo Zhu 	loongarch_common_resume();
7367694c07SYinbo Zhu 	loongson2_pm_irq_enable();
7467694c07SYinbo Zhu 	pm_set_resume_via_firmware();
7567694c07SYinbo Zhu 
7667694c07SYinbo Zhu 	return 0;
7767694c07SYinbo Zhu }
7867694c07SYinbo Zhu 
loongson2_suspend_begin(suspend_state_t state)7967694c07SYinbo Zhu static int loongson2_suspend_begin(suspend_state_t state)
8067694c07SYinbo Zhu {
8167694c07SYinbo Zhu 	pm_set_suspend_via_firmware();
8267694c07SYinbo Zhu 
8367694c07SYinbo Zhu 	return 0;
8467694c07SYinbo Zhu }
8567694c07SYinbo Zhu 
loongson2_suspend_valid_state(suspend_state_t state)8667694c07SYinbo Zhu static int loongson2_suspend_valid_state(suspend_state_t state)
8767694c07SYinbo Zhu {
8867694c07SYinbo Zhu 	return (state == PM_SUSPEND_MEM);
8967694c07SYinbo Zhu }
9067694c07SYinbo Zhu 
9167694c07SYinbo Zhu static const struct platform_suspend_ops loongson2_suspend_ops = {
9267694c07SYinbo Zhu 	.valid	= loongson2_suspend_valid_state,
9367694c07SYinbo Zhu 	.begin	= loongson2_suspend_begin,
9467694c07SYinbo Zhu 	.enter	= loongson2_suspend_enter,
9567694c07SYinbo Zhu };
9667694c07SYinbo Zhu 
loongson2_power_button_init(struct device * dev,int irq)9767694c07SYinbo Zhu static int loongson2_power_button_init(struct device *dev, int irq)
9867694c07SYinbo Zhu {
9967694c07SYinbo Zhu 	int ret;
10067694c07SYinbo Zhu 	struct input_dev *button;
10167694c07SYinbo Zhu 
10267694c07SYinbo Zhu 	button = input_allocate_device();
10367694c07SYinbo Zhu 	if (!dev)
10467694c07SYinbo Zhu 		return -ENOMEM;
10567694c07SYinbo Zhu 
10667694c07SYinbo Zhu 	button->name = "Power Button";
10767694c07SYinbo Zhu 	button->phys = "pm/button/input0";
10867694c07SYinbo Zhu 	button->id.bustype = BUS_HOST;
10967694c07SYinbo Zhu 	button->dev.parent = NULL;
11067694c07SYinbo Zhu 	input_set_capability(button, EV_KEY, KEY_POWER);
11167694c07SYinbo Zhu 
11267694c07SYinbo Zhu 	ret = input_register_device(button);
11367694c07SYinbo Zhu 	if (ret)
11467694c07SYinbo Zhu 		goto free_dev;
11567694c07SYinbo Zhu 
11667694c07SYinbo Zhu 	dev_pm_set_wake_irq(&button->dev, irq);
11767694c07SYinbo Zhu 	device_set_wakeup_capable(&button->dev, true);
11867694c07SYinbo Zhu 	device_set_wakeup_enable(&button->dev, true);
11967694c07SYinbo Zhu 
12067694c07SYinbo Zhu 	loongson2_pm.dev = button;
12167694c07SYinbo Zhu 	dev_info(dev, "Power Button: Init successful!\n");
12267694c07SYinbo Zhu 
12367694c07SYinbo Zhu 	return 0;
12467694c07SYinbo Zhu 
12567694c07SYinbo Zhu free_dev:
12667694c07SYinbo Zhu 	input_free_device(button);
12767694c07SYinbo Zhu 
12867694c07SYinbo Zhu 	return ret;
12967694c07SYinbo Zhu }
13067694c07SYinbo Zhu 
loongson2_pm_irq_handler(int irq,void * dev_id)13167694c07SYinbo Zhu static irqreturn_t loongson2_pm_irq_handler(int irq, void *dev_id)
13267694c07SYinbo Zhu {
13367694c07SYinbo Zhu 	u16 status = loongson2_pm_readw(LOONGSON2_PM1_STS_REG);
13467694c07SYinbo Zhu 
13567694c07SYinbo Zhu 	if (!loongson2_pm.suspended && (status & LOONGSON2_PM1_PWRBTN_STS)) {
13667694c07SYinbo Zhu 		pr_info("Power Button pressed...\n");
13767694c07SYinbo Zhu 		input_report_key(loongson2_pm.dev, KEY_POWER, 1);
13867694c07SYinbo Zhu 		input_sync(loongson2_pm.dev);
13967694c07SYinbo Zhu 		input_report_key(loongson2_pm.dev, KEY_POWER, 0);
14067694c07SYinbo Zhu 		input_sync(loongson2_pm.dev);
14167694c07SYinbo Zhu 	}
14267694c07SYinbo Zhu 
14367694c07SYinbo Zhu 	loongson2_pm_status_clear();
14467694c07SYinbo Zhu 
14567694c07SYinbo Zhu 	return IRQ_HANDLED;
14667694c07SYinbo Zhu }
14767694c07SYinbo Zhu 
loongson2_pm_suspend(struct device * dev)14867694c07SYinbo Zhu static int __maybe_unused loongson2_pm_suspend(struct device *dev)
14967694c07SYinbo Zhu {
15067694c07SYinbo Zhu 	loongson2_pm.suspended = true;
15167694c07SYinbo Zhu 
15267694c07SYinbo Zhu 	return 0;
15367694c07SYinbo Zhu }
15467694c07SYinbo Zhu 
loongson2_pm_resume(struct device * dev)15567694c07SYinbo Zhu static int __maybe_unused loongson2_pm_resume(struct device *dev)
15667694c07SYinbo Zhu {
15767694c07SYinbo Zhu 	loongson2_pm.suspended = false;
15867694c07SYinbo Zhu 
15967694c07SYinbo Zhu 	return 0;
16067694c07SYinbo Zhu }
16167694c07SYinbo Zhu static SIMPLE_DEV_PM_OPS(loongson2_pm_ops, loongson2_pm_suspend, loongson2_pm_resume);
16267694c07SYinbo Zhu 
loongson2_pm_probe(struct platform_device * pdev)16367694c07SYinbo Zhu static int loongson2_pm_probe(struct platform_device *pdev)
16467694c07SYinbo Zhu {
16567694c07SYinbo Zhu 	int irq, retval;
16667694c07SYinbo Zhu 	u64 suspend_addr;
16767694c07SYinbo Zhu 	struct device *dev = &pdev->dev;
16867694c07SYinbo Zhu 
16967694c07SYinbo Zhu 	loongson2_pm.base = devm_platform_ioremap_resource(pdev, 0);
17067694c07SYinbo Zhu 	if (IS_ERR(loongson2_pm.base))
17167694c07SYinbo Zhu 		return PTR_ERR(loongson2_pm.base);
17267694c07SYinbo Zhu 
17367694c07SYinbo Zhu 	irq = platform_get_irq(pdev, 0);
17467694c07SYinbo Zhu 	if (irq < 0)
17567694c07SYinbo Zhu 		return irq;
17667694c07SYinbo Zhu 
17767694c07SYinbo Zhu 	if (!device_property_read_u64(dev, "loongson,suspend-address", &suspend_addr))
17867694c07SYinbo Zhu 		loongson_sysconf.suspend_addr = (u64)phys_to_virt(suspend_addr);
17967694c07SYinbo Zhu 	else
18067694c07SYinbo Zhu 		dev_err(dev, "No loongson,suspend-address, could not support S3!\n");
18167694c07SYinbo Zhu 
18267694c07SYinbo Zhu 	if (loongson2_power_button_init(dev, irq))
18367694c07SYinbo Zhu 		return -EINVAL;
18467694c07SYinbo Zhu 
18567694c07SYinbo Zhu 	retval = devm_request_irq(&pdev->dev, irq, loongson2_pm_irq_handler,
18667694c07SYinbo Zhu 				  IRQF_SHARED, "pm_irq", &loongson2_pm);
18767694c07SYinbo Zhu 	if (retval)
18867694c07SYinbo Zhu 		return retval;
18967694c07SYinbo Zhu 
19067694c07SYinbo Zhu 	loongson2_pm_irq_enable();
19167694c07SYinbo Zhu 	loongson2_pm_status_clear();
19267694c07SYinbo Zhu 
19367694c07SYinbo Zhu 	if (loongson_sysconf.suspend_addr)
19467694c07SYinbo Zhu 		suspend_set_ops(&loongson2_suspend_ops);
19567694c07SYinbo Zhu 
196*a2fd5422SBinbin Zhou 	/* Populate children */
197*a2fd5422SBinbin Zhou 	retval = devm_of_platform_populate(dev);
198*a2fd5422SBinbin Zhou 	if (retval)
199*a2fd5422SBinbin Zhou 		dev_err(dev, "Error populating children, reboot and poweroff might not work properly\n");
200*a2fd5422SBinbin Zhou 
20167694c07SYinbo Zhu 	return 0;
20267694c07SYinbo Zhu }
20367694c07SYinbo Zhu 
20467694c07SYinbo Zhu static const struct of_device_id loongson2_pm_match[] = {
20567694c07SYinbo Zhu 	{ .compatible = "loongson,ls2k0500-pmc", },
20667694c07SYinbo Zhu 	{},
20767694c07SYinbo Zhu };
20867694c07SYinbo Zhu 
20967694c07SYinbo Zhu static struct platform_driver loongson2_pm_driver = {
21067694c07SYinbo Zhu 	.driver = {
21167694c07SYinbo Zhu 		.name = "ls2k-pm",
21267694c07SYinbo Zhu 		.pm = &loongson2_pm_ops,
21367694c07SYinbo Zhu 		.of_match_table = loongson2_pm_match,
21467694c07SYinbo Zhu 	},
21567694c07SYinbo Zhu 	.probe = loongson2_pm_probe,
21667694c07SYinbo Zhu };
21767694c07SYinbo Zhu module_platform_driver(loongson2_pm_driver);
21867694c07SYinbo Zhu 
21967694c07SYinbo Zhu MODULE_DESCRIPTION("Loongson-2 PM driver");
22067694c07SYinbo Zhu MODULE_LICENSE("GPL");
221