a2fd542287
The syscon poweroff and reboot nodes logically belong to the Power Management Unit so populate possible children. Without it, the reboot/poweroff feature becomes unavailable. Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
221 lines
5.4 KiB
C
221 lines
5.4 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Loongson-2 PM Support
|
|
*
|
|
* Copyright (C) 2023 Loongson Technology Corporation Limited
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/of.h>
|
|
#include <linux/init.h>
|
|
#include <linux/input.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pm_wakeirq.h>
|
|
#include <linux/platform_device.h>
|
|
#include <asm/bootinfo.h>
|
|
#include <asm/suspend.h>
|
|
|
|
#define LOONGSON2_PM1_CNT_REG 0x14
|
|
#define LOONGSON2_PM1_STS_REG 0x0c
|
|
#define LOONGSON2_PM1_ENA_REG 0x10
|
|
#define LOONGSON2_GPE0_STS_REG 0x28
|
|
#define LOONGSON2_GPE0_ENA_REG 0x2c
|
|
|
|
#define LOONGSON2_PM1_PWRBTN_STS BIT(8)
|
|
#define LOONGSON2_PM1_PCIEXP_WAKE_STS BIT(14)
|
|
#define LOONGSON2_PM1_WAKE_STS BIT(15)
|
|
#define LOONGSON2_PM1_CNT_INT_EN BIT(0)
|
|
#define LOONGSON2_PM1_PWRBTN_EN LOONGSON2_PM1_PWRBTN_STS
|
|
|
|
static struct loongson2_pm {
|
|
void __iomem *base;
|
|
struct input_dev *dev;
|
|
bool suspended;
|
|
} loongson2_pm;
|
|
|
|
#define loongson2_pm_readw(reg) readw(loongson2_pm.base + reg)
|
|
#define loongson2_pm_readl(reg) readl(loongson2_pm.base + reg)
|
|
#define loongson2_pm_writew(val, reg) writew(val, loongson2_pm.base + reg)
|
|
#define loongson2_pm_writel(val, reg) writel(val, loongson2_pm.base + reg)
|
|
|
|
static void loongson2_pm_status_clear(void)
|
|
{
|
|
u16 value;
|
|
|
|
value = loongson2_pm_readw(LOONGSON2_PM1_STS_REG);
|
|
value |= (LOONGSON2_PM1_PWRBTN_STS | LOONGSON2_PM1_PCIEXP_WAKE_STS |
|
|
LOONGSON2_PM1_WAKE_STS);
|
|
loongson2_pm_writew(value, LOONGSON2_PM1_STS_REG);
|
|
loongson2_pm_writel(loongson2_pm_readl(LOONGSON2_GPE0_STS_REG), LOONGSON2_GPE0_STS_REG);
|
|
}
|
|
|
|
static void loongson2_pm_irq_enable(void)
|
|
{
|
|
u16 value;
|
|
|
|
value = loongson2_pm_readw(LOONGSON2_PM1_CNT_REG);
|
|
value |= LOONGSON2_PM1_CNT_INT_EN;
|
|
loongson2_pm_writew(value, LOONGSON2_PM1_CNT_REG);
|
|
|
|
value = loongson2_pm_readw(LOONGSON2_PM1_ENA_REG);
|
|
value |= LOONGSON2_PM1_PWRBTN_EN;
|
|
loongson2_pm_writew(value, LOONGSON2_PM1_ENA_REG);
|
|
}
|
|
|
|
static int loongson2_suspend_enter(suspend_state_t state)
|
|
{
|
|
loongson2_pm_status_clear();
|
|
loongarch_common_suspend();
|
|
loongarch_suspend_enter();
|
|
loongarch_common_resume();
|
|
loongson2_pm_irq_enable();
|
|
pm_set_resume_via_firmware();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int loongson2_suspend_begin(suspend_state_t state)
|
|
{
|
|
pm_set_suspend_via_firmware();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int loongson2_suspend_valid_state(suspend_state_t state)
|
|
{
|
|
return (state == PM_SUSPEND_MEM);
|
|
}
|
|
|
|
static const struct platform_suspend_ops loongson2_suspend_ops = {
|
|
.valid = loongson2_suspend_valid_state,
|
|
.begin = loongson2_suspend_begin,
|
|
.enter = loongson2_suspend_enter,
|
|
};
|
|
|
|
static int loongson2_power_button_init(struct device *dev, int irq)
|
|
{
|
|
int ret;
|
|
struct input_dev *button;
|
|
|
|
button = input_allocate_device();
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
button->name = "Power Button";
|
|
button->phys = "pm/button/input0";
|
|
button->id.bustype = BUS_HOST;
|
|
button->dev.parent = NULL;
|
|
input_set_capability(button, EV_KEY, KEY_POWER);
|
|
|
|
ret = input_register_device(button);
|
|
if (ret)
|
|
goto free_dev;
|
|
|
|
dev_pm_set_wake_irq(&button->dev, irq);
|
|
device_set_wakeup_capable(&button->dev, true);
|
|
device_set_wakeup_enable(&button->dev, true);
|
|
|
|
loongson2_pm.dev = button;
|
|
dev_info(dev, "Power Button: Init successful!\n");
|
|
|
|
return 0;
|
|
|
|
free_dev:
|
|
input_free_device(button);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t loongson2_pm_irq_handler(int irq, void *dev_id)
|
|
{
|
|
u16 status = loongson2_pm_readw(LOONGSON2_PM1_STS_REG);
|
|
|
|
if (!loongson2_pm.suspended && (status & LOONGSON2_PM1_PWRBTN_STS)) {
|
|
pr_info("Power Button pressed...\n");
|
|
input_report_key(loongson2_pm.dev, KEY_POWER, 1);
|
|
input_sync(loongson2_pm.dev);
|
|
input_report_key(loongson2_pm.dev, KEY_POWER, 0);
|
|
input_sync(loongson2_pm.dev);
|
|
}
|
|
|
|
loongson2_pm_status_clear();
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int __maybe_unused loongson2_pm_suspend(struct device *dev)
|
|
{
|
|
loongson2_pm.suspended = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused loongson2_pm_resume(struct device *dev)
|
|
{
|
|
loongson2_pm.suspended = false;
|
|
|
|
return 0;
|
|
}
|
|
static SIMPLE_DEV_PM_OPS(loongson2_pm_ops, loongson2_pm_suspend, loongson2_pm_resume);
|
|
|
|
static int loongson2_pm_probe(struct platform_device *pdev)
|
|
{
|
|
int irq, retval;
|
|
u64 suspend_addr;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
loongson2_pm.base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(loongson2_pm.base))
|
|
return PTR_ERR(loongson2_pm.base);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
if (!device_property_read_u64(dev, "loongson,suspend-address", &suspend_addr))
|
|
loongson_sysconf.suspend_addr = (u64)phys_to_virt(suspend_addr);
|
|
else
|
|
dev_err(dev, "No loongson,suspend-address, could not support S3!\n");
|
|
|
|
if (loongson2_power_button_init(dev, irq))
|
|
return -EINVAL;
|
|
|
|
retval = devm_request_irq(&pdev->dev, irq, loongson2_pm_irq_handler,
|
|
IRQF_SHARED, "pm_irq", &loongson2_pm);
|
|
if (retval)
|
|
return retval;
|
|
|
|
loongson2_pm_irq_enable();
|
|
loongson2_pm_status_clear();
|
|
|
|
if (loongson_sysconf.suspend_addr)
|
|
suspend_set_ops(&loongson2_suspend_ops);
|
|
|
|
/* Populate children */
|
|
retval = devm_of_platform_populate(dev);
|
|
if (retval)
|
|
dev_err(dev, "Error populating children, reboot and poweroff might not work properly\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id loongson2_pm_match[] = {
|
|
{ .compatible = "loongson,ls2k0500-pmc", },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver loongson2_pm_driver = {
|
|
.driver = {
|
|
.name = "ls2k-pm",
|
|
.pm = &loongson2_pm_ops,
|
|
.of_match_table = loongson2_pm_match,
|
|
},
|
|
.probe = loongson2_pm_probe,
|
|
};
|
|
module_platform_driver(loongson2_pm_driver);
|
|
|
|
MODULE_DESCRIPTION("Loongson-2 PM driver");
|
|
MODULE_LICENSE("GPL");
|