1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Gemini power management controller 4 * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> 5 * 6 * Inspired by code from the SL3516 board support by Jason Lee 7 * Inspired by code from Janos Laube <janos.dev@gmail.com> 8 */ 9 #include <linux/of.h> 10 #include <linux/of_platform.h> 11 #include <linux/platform_device.h> 12 #include <linux/pm.h> 13 #include <linux/bitops.h> 14 #include <linux/interrupt.h> 15 #include <linux/io.h> 16 #include <linux/reboot.h> 17 18 #define GEMINI_PWC_ID 0x00010500 19 #define GEMINI_PWC_IDREG 0x00 20 #define GEMINI_PWC_CTRLREG 0x04 21 #define GEMINI_PWC_STATREG 0x08 22 23 #define GEMINI_CTRL_SHUTDOWN BIT(0) 24 #define GEMINI_CTRL_ENABLE BIT(1) 25 #define GEMINI_CTRL_IRQ_CLR BIT(2) 26 27 #define GEMINI_STAT_CIR BIT(4) 28 #define GEMINI_STAT_RTC BIT(5) 29 #define GEMINI_STAT_POWERBUTTON BIT(6) 30 31 struct gemini_powercon { 32 struct device *dev; 33 void __iomem *base; 34 }; 35 36 static irqreturn_t gemini_powerbutton_interrupt(int irq, void *data) 37 { 38 struct gemini_powercon *gpw = data; 39 u32 val; 40 41 /* ACK the IRQ */ 42 val = readl(gpw->base + GEMINI_PWC_CTRLREG); 43 val |= GEMINI_CTRL_IRQ_CLR; 44 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 45 46 val = readl(gpw->base + GEMINI_PWC_STATREG); 47 val &= 0x70U; 48 switch (val) { 49 case GEMINI_STAT_CIR: 50 dev_info(gpw->dev, "infrared poweroff\n"); 51 orderly_poweroff(true); 52 break; 53 case GEMINI_STAT_RTC: 54 dev_info(gpw->dev, "RTC poweroff\n"); 55 orderly_poweroff(true); 56 break; 57 case GEMINI_STAT_POWERBUTTON: 58 dev_info(gpw->dev, "poweroff button pressed\n"); 59 orderly_poweroff(true); 60 break; 61 default: 62 dev_info(gpw->dev, "other power management IRQ\n"); 63 break; 64 } 65 66 return IRQ_HANDLED; 67 } 68 69 /* This callback needs this static local as it has void as argument */ 70 static struct gemini_powercon *gpw_poweroff; 71 72 static void gemini_poweroff(void) 73 { 74 struct gemini_powercon *gpw = gpw_poweroff; 75 u32 val; 76 77 dev_crit(gpw->dev, "Gemini power off\n"); 78 val = readl(gpw->base + GEMINI_PWC_CTRLREG); 79 val |= GEMINI_CTRL_ENABLE | GEMINI_CTRL_IRQ_CLR; 80 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 81 82 val &= ~GEMINI_CTRL_ENABLE; 83 val |= GEMINI_CTRL_SHUTDOWN; 84 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 85 } 86 87 static int gemini_poweroff_probe(struct platform_device *pdev) 88 { 89 struct device *dev = &pdev->dev; 90 struct resource *res; 91 struct gemini_powercon *gpw; 92 u32 val; 93 int irq; 94 int ret; 95 96 gpw = devm_kzalloc(dev, sizeof(*gpw), GFP_KERNEL); 97 if (!gpw) 98 return -ENOMEM; 99 100 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 101 gpw->base = devm_ioremap_resource(dev, res); 102 if (IS_ERR(gpw->base)) 103 return PTR_ERR(gpw->base); 104 105 irq = platform_get_irq(pdev, 0); 106 if (!irq) 107 return -EINVAL; 108 109 gpw->dev = dev; 110 111 val = readl(gpw->base + GEMINI_PWC_IDREG); 112 val &= 0xFFFFFF00U; 113 if (val != GEMINI_PWC_ID) { 114 dev_err(dev, "wrong power controller ID: %08x\n", 115 val); 116 return -ENODEV; 117 } 118 119 /* Clear the power management IRQ */ 120 val = readl(gpw->base + GEMINI_PWC_CTRLREG); 121 val |= GEMINI_CTRL_IRQ_CLR; 122 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 123 124 ret = devm_request_irq(dev, irq, gemini_powerbutton_interrupt, 0, 125 "poweroff", gpw); 126 if (ret) 127 return ret; 128 129 pm_power_off = gemini_poweroff; 130 gpw_poweroff = gpw; 131 132 /* 133 * Enable the power controller. This is crucial on Gemini 134 * systems: if this is not done, pressing the power button 135 * will result in unconditional poweroff without any warning. 136 * This makes the kernel handle the poweroff. 137 */ 138 val = readl(gpw->base + GEMINI_PWC_CTRLREG); 139 val |= GEMINI_CTRL_ENABLE; 140 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 141 142 dev_info(dev, "Gemini poweroff driver registered\n"); 143 144 return 0; 145 } 146 147 static const struct of_device_id gemini_poweroff_of_match[] = { 148 { 149 .compatible = "cortina,gemini-power-controller", 150 }, 151 {} 152 }; 153 154 static struct platform_driver gemini_poweroff_driver = { 155 .probe = gemini_poweroff_probe, 156 .driver = { 157 .name = "gemini-poweroff", 158 .of_match_table = gemini_poweroff_of_match, 159 }, 160 }; 161 builtin_platform_driver(gemini_poweroff_driver); 162