Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
f7a388d6 LW |
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: | |
4a9be940 LW |
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"); | |
f7a388d6 LW |
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 */ | |
74 | static struct gemini_powercon *gpw_poweroff; | |
75 | ||
76 | static 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 | ||
91 | static 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) | |
111 | return -EINVAL; | |
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 | ||
4a9be940 LW |
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 | ||
ada1de89 LW |
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 */ | |
f7a388d6 LW |
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 | ||
f7a388d6 LW |
156 | dev_info(dev, "Gemini poweroff driver registered\n"); |
157 | ||
158 | return 0; | |
159 | } | |
160 | ||
161 | static const struct of_device_id gemini_poweroff_of_match[] = { | |
162 | { | |
163 | .compatible = "cortina,gemini-power-controller", | |
164 | }, | |
165 | {} | |
166 | }; | |
167 | ||
168 | static 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 | }; | |
175 | builtin_platform_driver(gemini_poweroff_driver); |