162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Loongson-2 PM Support
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2023 Loongson Technology Corporation Limited
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/of.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/input.h>
1262306a36Sopenharmony_ci#include <linux/suspend.h>
1362306a36Sopenharmony_ci#include <linux/interrupt.h>
1462306a36Sopenharmony_ci#include <linux/of_platform.h>
1562306a36Sopenharmony_ci#include <linux/pm_wakeirq.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <asm/bootinfo.h>
1862306a36Sopenharmony_ci#include <asm/suspend.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define LOONGSON2_PM1_CNT_REG		0x14
2162306a36Sopenharmony_ci#define LOONGSON2_PM1_STS_REG		0x0c
2262306a36Sopenharmony_ci#define LOONGSON2_PM1_ENA_REG		0x10
2362306a36Sopenharmony_ci#define LOONGSON2_GPE0_STS_REG		0x28
2462306a36Sopenharmony_ci#define LOONGSON2_GPE0_ENA_REG		0x2c
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define LOONGSON2_PM1_PWRBTN_STS	BIT(8)
2762306a36Sopenharmony_ci#define LOONGSON2_PM1_PCIEXP_WAKE_STS	BIT(14)
2862306a36Sopenharmony_ci#define LOONGSON2_PM1_WAKE_STS		BIT(15)
2962306a36Sopenharmony_ci#define LOONGSON2_PM1_CNT_INT_EN	BIT(0)
3062306a36Sopenharmony_ci#define LOONGSON2_PM1_PWRBTN_EN		LOONGSON2_PM1_PWRBTN_STS
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic struct loongson2_pm {
3362306a36Sopenharmony_ci	void __iomem			*base;
3462306a36Sopenharmony_ci	struct input_dev		*dev;
3562306a36Sopenharmony_ci	bool				suspended;
3662306a36Sopenharmony_ci} loongson2_pm;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci#define loongson2_pm_readw(reg)		readw(loongson2_pm.base + reg)
3962306a36Sopenharmony_ci#define loongson2_pm_readl(reg)		readl(loongson2_pm.base + reg)
4062306a36Sopenharmony_ci#define loongson2_pm_writew(val, reg)	writew(val, loongson2_pm.base + reg)
4162306a36Sopenharmony_ci#define loongson2_pm_writel(val, reg)	writel(val, loongson2_pm.base + reg)
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic void loongson2_pm_status_clear(void)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	u16 value;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	value = loongson2_pm_readw(LOONGSON2_PM1_STS_REG);
4862306a36Sopenharmony_ci	value |= (LOONGSON2_PM1_PWRBTN_STS | LOONGSON2_PM1_PCIEXP_WAKE_STS |
4962306a36Sopenharmony_ci		  LOONGSON2_PM1_WAKE_STS);
5062306a36Sopenharmony_ci	loongson2_pm_writew(value, LOONGSON2_PM1_STS_REG);
5162306a36Sopenharmony_ci	loongson2_pm_writel(loongson2_pm_readl(LOONGSON2_GPE0_STS_REG), LOONGSON2_GPE0_STS_REG);
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic void loongson2_pm_irq_enable(void)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	u16 value;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	value = loongson2_pm_readw(LOONGSON2_PM1_CNT_REG);
5962306a36Sopenharmony_ci	value |= LOONGSON2_PM1_CNT_INT_EN;
6062306a36Sopenharmony_ci	loongson2_pm_writew(value, LOONGSON2_PM1_CNT_REG);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	value = loongson2_pm_readw(LOONGSON2_PM1_ENA_REG);
6362306a36Sopenharmony_ci	value |= LOONGSON2_PM1_PWRBTN_EN;
6462306a36Sopenharmony_ci	loongson2_pm_writew(value, LOONGSON2_PM1_ENA_REG);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic int loongson2_suspend_enter(suspend_state_t state)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	loongson2_pm_status_clear();
7062306a36Sopenharmony_ci	loongarch_common_suspend();
7162306a36Sopenharmony_ci	loongarch_suspend_enter();
7262306a36Sopenharmony_ci	loongarch_common_resume();
7362306a36Sopenharmony_ci	loongson2_pm_irq_enable();
7462306a36Sopenharmony_ci	pm_set_resume_via_firmware();
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	return 0;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic int loongson2_suspend_begin(suspend_state_t state)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	pm_set_suspend_via_firmware();
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	return 0;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic int loongson2_suspend_valid_state(suspend_state_t state)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	return (state == PM_SUSPEND_MEM);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic const struct platform_suspend_ops loongson2_suspend_ops = {
9262306a36Sopenharmony_ci	.valid	= loongson2_suspend_valid_state,
9362306a36Sopenharmony_ci	.begin	= loongson2_suspend_begin,
9462306a36Sopenharmony_ci	.enter	= loongson2_suspend_enter,
9562306a36Sopenharmony_ci};
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic int loongson2_power_button_init(struct device *dev, int irq)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	int ret;
10062306a36Sopenharmony_ci	struct input_dev *button;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	button = input_allocate_device();
10362306a36Sopenharmony_ci	if (!dev)
10462306a36Sopenharmony_ci		return -ENOMEM;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	button->name = "Power Button";
10762306a36Sopenharmony_ci	button->phys = "pm/button/input0";
10862306a36Sopenharmony_ci	button->id.bustype = BUS_HOST;
10962306a36Sopenharmony_ci	button->dev.parent = NULL;
11062306a36Sopenharmony_ci	input_set_capability(button, EV_KEY, KEY_POWER);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	ret = input_register_device(button);
11362306a36Sopenharmony_ci	if (ret)
11462306a36Sopenharmony_ci		goto free_dev;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	dev_pm_set_wake_irq(&button->dev, irq);
11762306a36Sopenharmony_ci	device_set_wakeup_capable(&button->dev, true);
11862306a36Sopenharmony_ci	device_set_wakeup_enable(&button->dev, true);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	loongson2_pm.dev = button;
12162306a36Sopenharmony_ci	dev_info(dev, "Power Button: Init successful!\n");
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return 0;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cifree_dev:
12662306a36Sopenharmony_ci	input_free_device(button);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return ret;
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic irqreturn_t loongson2_pm_irq_handler(int irq, void *dev_id)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	u16 status = loongson2_pm_readw(LOONGSON2_PM1_STS_REG);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (!loongson2_pm.suspended && (status & LOONGSON2_PM1_PWRBTN_STS)) {
13662306a36Sopenharmony_ci		pr_info("Power Button pressed...\n");
13762306a36Sopenharmony_ci		input_report_key(loongson2_pm.dev, KEY_POWER, 1);
13862306a36Sopenharmony_ci		input_sync(loongson2_pm.dev);
13962306a36Sopenharmony_ci		input_report_key(loongson2_pm.dev, KEY_POWER, 0);
14062306a36Sopenharmony_ci		input_sync(loongson2_pm.dev);
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	loongson2_pm_status_clear();
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return IRQ_HANDLED;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic int __maybe_unused loongson2_pm_suspend(struct device *dev)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	loongson2_pm.suspended = true;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return 0;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic int __maybe_unused loongson2_pm_resume(struct device *dev)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	loongson2_pm.suspended = false;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	return 0;
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(loongson2_pm_ops, loongson2_pm_suspend, loongson2_pm_resume);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic int loongson2_pm_probe(struct platform_device *pdev)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	int irq, retval;
16662306a36Sopenharmony_ci	u64 suspend_addr;
16762306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	loongson2_pm.base = devm_platform_ioremap_resource(pdev, 0);
17062306a36Sopenharmony_ci	if (IS_ERR(loongson2_pm.base))
17162306a36Sopenharmony_ci		return PTR_ERR(loongson2_pm.base);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
17462306a36Sopenharmony_ci	if (irq < 0)
17562306a36Sopenharmony_ci		return irq;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (!device_property_read_u64(dev, "loongson,suspend-address", &suspend_addr))
17862306a36Sopenharmony_ci		loongson_sysconf.suspend_addr = (u64)phys_to_virt(suspend_addr);
17962306a36Sopenharmony_ci	else
18062306a36Sopenharmony_ci		dev_err(dev, "No loongson,suspend-address, could not support S3!\n");
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (loongson2_power_button_init(dev, irq))
18362306a36Sopenharmony_ci		return -EINVAL;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	retval = devm_request_irq(&pdev->dev, irq, loongson2_pm_irq_handler,
18662306a36Sopenharmony_ci				  IRQF_SHARED, "pm_irq", &loongson2_pm);
18762306a36Sopenharmony_ci	if (retval)
18862306a36Sopenharmony_ci		return retval;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	loongson2_pm_irq_enable();
19162306a36Sopenharmony_ci	loongson2_pm_status_clear();
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (loongson_sysconf.suspend_addr)
19462306a36Sopenharmony_ci		suspend_set_ops(&loongson2_suspend_ops);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	/* Populate children */
19762306a36Sopenharmony_ci	retval = devm_of_platform_populate(dev);
19862306a36Sopenharmony_ci	if (retval)
19962306a36Sopenharmony_ci		dev_err(dev, "Error populating children, reboot and poweroff might not work properly\n");
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	return 0;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic const struct of_device_id loongson2_pm_match[] = {
20562306a36Sopenharmony_ci	{ .compatible = "loongson,ls2k0500-pmc", },
20662306a36Sopenharmony_ci	{},
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic struct platform_driver loongson2_pm_driver = {
21062306a36Sopenharmony_ci	.driver = {
21162306a36Sopenharmony_ci		.name = "ls2k-pm",
21262306a36Sopenharmony_ci		.pm = &loongson2_pm_ops,
21362306a36Sopenharmony_ci		.of_match_table = loongson2_pm_match,
21462306a36Sopenharmony_ci	},
21562306a36Sopenharmony_ci	.probe = loongson2_pm_probe,
21662306a36Sopenharmony_ci};
21762306a36Sopenharmony_cimodule_platform_driver(loongson2_pm_driver);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ciMODULE_DESCRIPTION("Loongson-2 PM driver");
22062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
221