gemini-poweroff.c (4400B)
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 31struct gemini_powercon { 32 struct device *dev; 33 void __iomem *base; 34}; 35 36static 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 /* 51 * We do not yet have a driver for the infrared 52 * controller so it can cause spurious poweroff 53 * events. Ignore those for now. 54 */ 55 dev_info(gpw->dev, "infrared poweroff - ignored\n"); 56 break; 57 case GEMINI_STAT_RTC: 58 dev_info(gpw->dev, "RTC poweroff\n"); 59 orderly_poweroff(true); 60 break; 61 case GEMINI_STAT_POWERBUTTON: 62 dev_info(gpw->dev, "poweroff button pressed\n"); 63 orderly_poweroff(true); 64 break; 65 default: 66 dev_info(gpw->dev, "other power management IRQ\n"); 67 break; 68 } 69 70 return IRQ_HANDLED; 71} 72 73/* This callback needs this static local as it has void as argument */ 74static struct gemini_powercon *gpw_poweroff; 75 76static void gemini_poweroff(void) 77{ 78 struct gemini_powercon *gpw = gpw_poweroff; 79 u32 val; 80 81 dev_crit(gpw->dev, "Gemini power off\n"); 82 val = readl(gpw->base + GEMINI_PWC_CTRLREG); 83 val |= GEMINI_CTRL_ENABLE | GEMINI_CTRL_IRQ_CLR; 84 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 85 86 val &= ~GEMINI_CTRL_ENABLE; 87 val |= GEMINI_CTRL_SHUTDOWN; 88 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 89} 90 91static int gemini_poweroff_probe(struct platform_device *pdev) 92{ 93 struct device *dev = &pdev->dev; 94 struct resource *res; 95 struct gemini_powercon *gpw; 96 u32 val; 97 int irq; 98 int ret; 99 100 gpw = devm_kzalloc(dev, sizeof(*gpw), GFP_KERNEL); 101 if (!gpw) 102 return -ENOMEM; 103 104 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 105 gpw->base = devm_ioremap_resource(dev, res); 106 if (IS_ERR(gpw->base)) 107 return PTR_ERR(gpw->base); 108 109 irq = platform_get_irq(pdev, 0); 110 if (irq < 0) 111 return irq; 112 113 gpw->dev = dev; 114 115 val = readl(gpw->base + GEMINI_PWC_IDREG); 116 val &= 0xFFFFFF00U; 117 if (val != GEMINI_PWC_ID) { 118 dev_err(dev, "wrong power controller ID: %08x\n", 119 val); 120 return -ENODEV; 121 } 122 123 /* 124 * Enable the power controller. This is crucial on Gemini 125 * systems: if this is not done, pressing the power button 126 * will result in unconditional poweroff without any warning. 127 * This makes the kernel handle the poweroff. 128 */ 129 val = readl(gpw->base + GEMINI_PWC_CTRLREG); 130 val |= GEMINI_CTRL_ENABLE; 131 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 132 133 /* Clear the IRQ */ 134 val = readl(gpw->base + GEMINI_PWC_CTRLREG); 135 val |= GEMINI_CTRL_IRQ_CLR; 136 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 137 138 /* Wait for this to clear */ 139 val = readl(gpw->base + GEMINI_PWC_STATREG); 140 while (val & 0x70U) 141 val = readl(gpw->base + GEMINI_PWC_STATREG); 142 143 /* Clear the IRQ again */ 144 val = readl(gpw->base + GEMINI_PWC_CTRLREG); 145 val |= GEMINI_CTRL_IRQ_CLR; 146 writel(val, gpw->base + GEMINI_PWC_CTRLREG); 147 148 ret = devm_request_irq(dev, irq, gemini_powerbutton_interrupt, 0, 149 "poweroff", gpw); 150 if (ret) 151 return ret; 152 153 pm_power_off = gemini_poweroff; 154 gpw_poweroff = gpw; 155 156 dev_info(dev, "Gemini poweroff driver registered\n"); 157 158 return 0; 159} 160 161static const struct of_device_id gemini_poweroff_of_match[] = { 162 { 163 .compatible = "cortina,gemini-power-controller", 164 }, 165 {} 166}; 167 168static struct platform_driver gemini_poweroff_driver = { 169 .probe = gemini_poweroff_probe, 170 .driver = { 171 .name = "gemini-poweroff", 172 .of_match_table = gemini_poweroff_of_match, 173 }, 174}; 175builtin_platform_driver(gemini_poweroff_driver);