Hi, Yinbo, On Mon, May 22, 2023 at 5:33 PM Yinbo Zhu <zhuyinbo@xxxxxxxxxxx> wrote: > > The Loongson-2's Power Management Controller was ACPI, supports ACPI > S2Idle (Suspend To Idle), ACPI S3 (Suspend To RAM), ACPI S4 (Suspend To > Disk), ACPI S5 (Soft Shutdown) and supports multiple wake-up methods > (USB, GMAC, PWRBTN, etc.). This driver was to add Power Management > Controller support that base on dts for Loongson-2 series SoCs. > > Signed-off-by: Liu Yun <liuyun@xxxxxxxxxxx> > Signed-off-by: Liu Peibao <liupeibao@xxxxxxxxxxx> > Signed-off-by: Yinbo Zhu <zhuyinbo@xxxxxxxxxxx> > --- > MAINTAINERS | 1 + > drivers/soc/loongson/Kconfig | 10 ++ > drivers/soc/loongson/Makefile | 1 + > drivers/soc/loongson/loongson2_pm.c | 235 ++++++++++++++++++++++++++++ > 4 files changed, 247 insertions(+) > create mode 100644 drivers/soc/loongson/loongson2_pm.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index bcd05f1fa5c1..7c4ad0cbaeff 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -12195,6 +12195,7 @@ M: Yinbo Zhu <zhuyinbo@xxxxxxxxxxx> > L: linux-pm@xxxxxxxxxxxxxxx > S: Maintained > F: Documentation/devicetree/bindings/soc/loongson/loongson,ls2k-pmc.yaml > +F: drivers/soc/loongson/loongson2_pm.c > > LOONGSON-2 SOC SERIES PINCTRL DRIVER > M: zhanghongchen <zhanghongchen@xxxxxxxxxxx> > diff --git a/drivers/soc/loongson/Kconfig b/drivers/soc/loongson/Kconfig > index 707f56358dc4..2431a0bcbd84 100644 > --- a/drivers/soc/loongson/Kconfig > +++ b/drivers/soc/loongson/Kconfig > @@ -16,3 +16,13 @@ config LOONGSON2_GUTS > SoCs. Initially only reading SVR and registering soc device are > supported. Other guts accesses, such as reading firmware configuration > by default, should eventually be added into this driver as well. > + > +config LOONGSON2_PM > + bool "Loongson-2 SoC Power Management Controller Driver" > + depends on LOONGARCH && OF > + help > + The Loongson-2's Power Management Controller was ACPI, supports ACPI > + S2Idle (Suspend To Idle), ACPI S3 (Suspend To RAM), ACPI S4 (Suspend To > + Disk), ACPI S5 (Soft Shutdown) and supports multiple wake-up methods > + (USB, GMAC, PWRBTN, etc.). This driver was to add Power Management > + Controller support that base on dts for Loongson-2 series SoCs. > diff --git a/drivers/soc/loongson/Makefile b/drivers/soc/loongson/Makefile > index 263c486df638..4118f50f55e2 100644 > --- a/drivers/soc/loongson/Makefile > +++ b/drivers/soc/loongson/Makefile > @@ -4,3 +4,4 @@ > # > > obj-$(CONFIG_LOONGSON2_GUTS) += loongson2_guts.o > +obj-$(CONFIG_LOONGSON2_PM) += loongson2_pm.o > diff --git a/drivers/soc/loongson/loongson2_pm.c b/drivers/soc/loongson/loongson2_pm.c > new file mode 100644 > index 000000000000..cd96a1ebbb6c > --- /dev/null > +++ b/drivers/soc/loongson/loongson2_pm.c > @@ -0,0 +1,235 @@ > +// 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/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); > +} > + > +static void loongson2_pm_pwrbtn_irq_enable(void) > +{ > + u16 value; > + > + loongson2_pm_irq_enable(); > + > + value = loongson2_pm_readw(LOONGSON2_PM1_ENA_REG); > + value |= LOONGSON2_PM1_PWRBTN_EN; > + loongson2_pm_writew(value, LOONGSON2_PM1_ENA_REG); > +} You can combine these two functions as loongson2_power_button_irq_enable(). > + > +static void loongson2_pm_mach_resume(void) > +{ > + loongson_common_resume(); > + loongson2_pm_irq_enable(); > +} > + > +static void loongson2_pm_mach_suspend(void) > +{ > + loongson2_pm_status_clear(); > + loongson_common_suspend(); > +} > + > +static int loongson2_suspend_enter(suspend_state_t state) > +{ > + loongson2_pm_mach_suspend(); > + loongson_suspend_enter(); > + pm_set_resume_via_firmware(); > + loongson2_pm_mach_resume(); > + > + return 0; > +} After some thinkings, I found these three simple function can be combined as: static int loongson2_suspend_enter(suspend_state_t state) { loongson2_pm_status_clear(); loongson_common_suspend(); loongson_suspend_enter(); loongson_common_resume(); loongson2_pm_irq_enable(); pm_set_resume_via_firmware(); return 0; } After this combining, loongson_common_suspend()/loongson_suspend_enter()/loongson_common_resume() can be still use the old naming loongarch_common_suspend()/loongarch_suspend_enter()/loongarch_common_resume(). > + > +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) > +{ > + if (state == PM_SUSPEND_MEM) > + return !!loongson_sysconf.suspend_addr; > + > + return 0; > +} > + > +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_pm_pwrbtn_init(struct device *dev, int irq) > +{ > + int ret; > + struct input_dev *pwrbt; Rename loongson2_pm_pwrbtn_init() to loongson2_power_button_init() and rename 'pwrbt' to 'pwrbtn' or just 'button' is better. Huacai > + > + pwrbt = input_allocate_device(); > + if (!dev) > + return -ENOMEM; > + > + pwrbt->name = "Power Button"; > + pwrbt->phys = "pm/button/input0"; > + pwrbt->id.bustype = BUS_HOST; > + pwrbt->dev.parent = NULL; > + input_set_capability(pwrbt, EV_KEY, KEY_POWER); > + > + ret = input_register_device(pwrbt); > + if (ret) > + goto free_dev; > + > + dev_pm_set_wake_irq(&pwrbt->dev, irq); > + device_set_wakeup_capable(&pwrbt->dev, true); > + device_set_wakeup_enable(&pwrbt->dev, true); > + > + loongson2_pm.dev = pwrbt; > + dev_info(dev, "Power Button: Init successful!\n"); > + > + return 0; > + > +free_dev: > + input_free_device(pwrbt); > + > + 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; > + u32 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_u32(dev, "suspend-address", &suspend_addr)) > + loongson_sysconf.suspend_addr = (u64)phys_to_virt(suspend_addr); > + else > + dev_err(dev, "No suspend-address, could not support S3!\n"); > + > + if (loongson2_pm_pwrbtn_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_pwrbtn_irq_enable(); > + loongson2_pm_status_clear(); > + > + if (loongson_sysconf.suspend_addr) > + suspend_set_ops(&loongson2_suspend_ops); > + > + return 0; > +} > + > +static const struct of_device_id loongson2_pm_match[] = { > + { .compatible = "loongson,ls2k-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"); > -- > 2.20.1 > >