| // 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"); |