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