// SPDX-License-Identifier: GPL-2.0+ /* * Loongson-2 PM Support * * Copyright (C) 2023 Loongson Technology Corporation Limited */ #include #include #include #include #include #include #include #include #include #include #include #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");