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