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