221 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Loongson-2 PM Support4 *5 * Copyright (C) 2023 Loongson Technology Corporation Limited6 */7 8#include <linux/io.h>9#include <linux/of.h>10#include <linux/init.h>11#include <linux/input.h>12#include <linux/suspend.h>13#include <linux/interrupt.h>14#include <linux/of_platform.h>15#include <linux/pm_wakeirq.h>16#include <linux/platform_device.h>17#include <asm/bootinfo.h>18#include <asm/suspend.h>19 20#define LOONGSON2_PM1_CNT_REG 0x1421#define LOONGSON2_PM1_STS_REG 0x0c22#define LOONGSON2_PM1_ENA_REG 0x1023#define LOONGSON2_GPE0_STS_REG 0x2824#define LOONGSON2_GPE0_ENA_REG 0x2c25 26#define LOONGSON2_PM1_PWRBTN_STS BIT(8)27#define LOONGSON2_PM1_PCIEXP_WAKE_STS BIT(14)28#define LOONGSON2_PM1_WAKE_STS BIT(15)29#define LOONGSON2_PM1_CNT_INT_EN BIT(0)30#define LOONGSON2_PM1_PWRBTN_EN LOONGSON2_PM1_PWRBTN_STS31 32static struct loongson2_pm {33 void __iomem *base;34 struct input_dev *dev;35 bool suspended;36} loongson2_pm;37 38#define loongson2_pm_readw(reg) readw(loongson2_pm.base + reg)39#define loongson2_pm_readl(reg) readl(loongson2_pm.base + reg)40#define loongson2_pm_writew(val, reg) writew(val, loongson2_pm.base + reg)41#define loongson2_pm_writel(val, reg) writel(val, loongson2_pm.base + reg)42 43static void loongson2_pm_status_clear(void)44{45 u16 value;46 47 value = loongson2_pm_readw(LOONGSON2_PM1_STS_REG);48 value |= (LOONGSON2_PM1_PWRBTN_STS | LOONGSON2_PM1_PCIEXP_WAKE_STS |49 LOONGSON2_PM1_WAKE_STS);50 loongson2_pm_writew(value, LOONGSON2_PM1_STS_REG);51 loongson2_pm_writel(loongson2_pm_readl(LOONGSON2_GPE0_STS_REG), LOONGSON2_GPE0_STS_REG);52}53 54static void loongson2_pm_irq_enable(void)55{56 u16 value;57 58 value = loongson2_pm_readw(LOONGSON2_PM1_CNT_REG);59 value |= LOONGSON2_PM1_CNT_INT_EN;60 loongson2_pm_writew(value, LOONGSON2_PM1_CNT_REG);61 62 value = loongson2_pm_readw(LOONGSON2_PM1_ENA_REG);63 value |= LOONGSON2_PM1_PWRBTN_EN;64 loongson2_pm_writew(value, LOONGSON2_PM1_ENA_REG);65}66 67static int loongson2_suspend_enter(suspend_state_t state)68{69 loongson2_pm_status_clear();70 loongarch_common_suspend();71 loongarch_suspend_enter();72 loongarch_common_resume();73 loongson2_pm_irq_enable();74 pm_set_resume_via_firmware();75 76 return 0;77}78 79static int loongson2_suspend_begin(suspend_state_t state)80{81 pm_set_suspend_via_firmware();82 83 return 0;84}85 86static int loongson2_suspend_valid_state(suspend_state_t state)87{88 return (state == PM_SUSPEND_MEM);89}90 91static const struct platform_suspend_ops loongson2_suspend_ops = {92 .valid = loongson2_suspend_valid_state,93 .begin = loongson2_suspend_begin,94 .enter = loongson2_suspend_enter,95};96 97static int loongson2_power_button_init(struct device *dev, int irq)98{99 int ret;100 struct input_dev *button;101 102 button = input_allocate_device();103 if (!dev)104 return -ENOMEM;105 106 button->name = "Power Button";107 button->phys = "pm/button/input0";108 button->id.bustype = BUS_HOST;109 button->dev.parent = NULL;110 input_set_capability(button, EV_KEY, KEY_POWER);111 112 ret = input_register_device(button);113 if (ret)114 goto free_dev;115 116 dev_pm_set_wake_irq(&button->dev, irq);117 device_set_wakeup_capable(&button->dev, true);118 device_set_wakeup_enable(&button->dev, true);119 120 loongson2_pm.dev = button;121 dev_info(dev, "Power Button: Init successful!\n");122 123 return 0;124 125free_dev:126 input_free_device(button);127 128 return ret;129}130 131static irqreturn_t loongson2_pm_irq_handler(int irq, void *dev_id)132{133 u16 status = loongson2_pm_readw(LOONGSON2_PM1_STS_REG);134 135 if (!loongson2_pm.suspended && (status & LOONGSON2_PM1_PWRBTN_STS)) {136 pr_info("Power Button pressed...\n");137 input_report_key(loongson2_pm.dev, KEY_POWER, 1);138 input_sync(loongson2_pm.dev);139 input_report_key(loongson2_pm.dev, KEY_POWER, 0);140 input_sync(loongson2_pm.dev);141 }142 143 loongson2_pm_status_clear();144 145 return IRQ_HANDLED;146}147 148static int __maybe_unused loongson2_pm_suspend(struct device *dev)149{150 loongson2_pm.suspended = true;151 152 return 0;153}154 155static int __maybe_unused loongson2_pm_resume(struct device *dev)156{157 loongson2_pm.suspended = false;158 159 return 0;160}161static SIMPLE_DEV_PM_OPS(loongson2_pm_ops, loongson2_pm_suspend, loongson2_pm_resume);162 163static int loongson2_pm_probe(struct platform_device *pdev)164{165 int irq, retval;166 u64 suspend_addr;167 struct device *dev = &pdev->dev;168 169 loongson2_pm.base = devm_platform_ioremap_resource(pdev, 0);170 if (IS_ERR(loongson2_pm.base))171 return PTR_ERR(loongson2_pm.base);172 173 irq = platform_get_irq(pdev, 0);174 if (irq < 0)175 return irq;176 177 if (!device_property_read_u64(dev, "loongson,suspend-address", &suspend_addr))178 loongson_sysconf.suspend_addr = (u64)phys_to_virt(suspend_addr);179 else180 dev_err(dev, "No loongson,suspend-address, could not support S3!\n");181 182 if (loongson2_power_button_init(dev, irq))183 return -EINVAL;184 185 retval = devm_request_irq(&pdev->dev, irq, loongson2_pm_irq_handler,186 IRQF_SHARED, "pm_irq", &loongson2_pm);187 if (retval)188 return retval;189 190 loongson2_pm_irq_enable();191 loongson2_pm_status_clear();192 193 if (loongson_sysconf.suspend_addr)194 suspend_set_ops(&loongson2_suspend_ops);195 196 /* Populate children */197 retval = devm_of_platform_populate(dev);198 if (retval)199 dev_err(dev, "Error populating children, reboot and poweroff might not work properly\n");200 201 return 0;202}203 204static const struct of_device_id loongson2_pm_match[] = {205 { .compatible = "loongson,ls2k0500-pmc", },206 {},207};208 209static struct platform_driver loongson2_pm_driver = {210 .driver = {211 .name = "ls2k-pm",212 .pm = &loongson2_pm_ops,213 .of_match_table = loongson2_pm_match,214 },215 .probe = loongson2_pm_probe,216};217module_platform_driver(loongson2_pm_driver);218 219MODULE_DESCRIPTION("Loongson-2 PM driver");220MODULE_LICENSE("GPL");221