1 | /* |
2 | * axp20x power button driver. |
3 | * |
4 | * Copyright (C) 2013 Carlo Caione <carlo@caione.org> |
5 | * |
6 | * This file is subject to the terms and conditions of the GNU General |
7 | * Public License. See the file "COPYING" in the main directory of this |
8 | * archive for more details. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | */ |
15 | |
16 | #include <linux/acpi.h> |
17 | #include <linux/errno.h> |
18 | #include <linux/irq.h> |
19 | #include <linux/init.h> |
20 | #include <linux/input.h> |
21 | #include <linux/interrupt.h> |
22 | #include <linux/kernel.h> |
23 | #include <linux/mfd/axp20x.h> |
24 | #include <linux/module.h> |
25 | #include <linux/platform_data/x86/soc.h> |
26 | #include <linux/platform_device.h> |
27 | #include <linux/regmap.h> |
28 | #include <linux/slab.h> |
29 | |
30 | #define AXP20X_PEK_STARTUP_MASK (0xc0) |
31 | #define AXP20X_PEK_SHUTDOWN_MASK (0x03) |
32 | |
33 | struct axp20x_info { |
34 | const struct axp20x_time *startup_time; |
35 | unsigned int startup_mask; |
36 | const struct axp20x_time *shutdown_time; |
37 | unsigned int shutdown_mask; |
38 | }; |
39 | |
40 | struct axp20x_pek { |
41 | struct axp20x_dev *axp20x; |
42 | struct input_dev *input; |
43 | struct axp20x_info *info; |
44 | int irq_dbr; |
45 | int irq_dbf; |
46 | }; |
47 | |
48 | struct axp20x_time { |
49 | unsigned int time; |
50 | unsigned int idx; |
51 | }; |
52 | |
53 | static const struct axp20x_time startup_time[] = { |
54 | { .time = 128, .idx = 0 }, |
55 | { .time = 1000, .idx = 2 }, |
56 | { .time = 3000, .idx = 1 }, |
57 | { .time = 2000, .idx = 3 }, |
58 | }; |
59 | |
60 | static const struct axp20x_time axp221_startup_time[] = { |
61 | { .time = 128, .idx = 0 }, |
62 | { .time = 1000, .idx = 1 }, |
63 | { .time = 2000, .idx = 2 }, |
64 | { .time = 3000, .idx = 3 }, |
65 | }; |
66 | |
67 | static const struct axp20x_time shutdown_time[] = { |
68 | { .time = 4000, .idx = 0 }, |
69 | { .time = 6000, .idx = 1 }, |
70 | { .time = 8000, .idx = 2 }, |
71 | { .time = 10000, .idx = 3 }, |
72 | }; |
73 | |
74 | static const struct axp20x_info axp20x_info = { |
75 | .startup_time = startup_time, |
76 | .startup_mask = AXP20X_PEK_STARTUP_MASK, |
77 | .shutdown_time = shutdown_time, |
78 | .shutdown_mask = AXP20X_PEK_SHUTDOWN_MASK, |
79 | }; |
80 | |
81 | static const struct axp20x_info axp221_info = { |
82 | .startup_time = axp221_startup_time, |
83 | .startup_mask = AXP20X_PEK_STARTUP_MASK, |
84 | .shutdown_time = shutdown_time, |
85 | .shutdown_mask = AXP20X_PEK_SHUTDOWN_MASK, |
86 | }; |
87 | |
88 | static ssize_t axp20x_show_attr(struct device *dev, |
89 | const struct axp20x_time *time, |
90 | unsigned int mask, char *buf) |
91 | { |
92 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
93 | unsigned int val; |
94 | int ret, i; |
95 | |
96 | ret = regmap_read(map: axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, val: &val); |
97 | if (ret != 0) |
98 | return ret; |
99 | |
100 | val &= mask; |
101 | val >>= ffs(mask) - 1; |
102 | |
103 | for (i = 0; i < 4; i++) |
104 | if (val == time[i].idx) |
105 | val = time[i].time; |
106 | |
107 | return sprintf(buf, fmt: "%u\n" , val); |
108 | } |
109 | |
110 | static ssize_t axp20x_show_attr_startup(struct device *dev, |
111 | struct device_attribute *attr, |
112 | char *buf) |
113 | { |
114 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
115 | |
116 | return axp20x_show_attr(dev, time: axp20x_pek->info->startup_time, |
117 | mask: axp20x_pek->info->startup_mask, buf); |
118 | } |
119 | |
120 | static ssize_t axp20x_show_attr_shutdown(struct device *dev, |
121 | struct device_attribute *attr, |
122 | char *buf) |
123 | { |
124 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
125 | |
126 | return axp20x_show_attr(dev, time: axp20x_pek->info->shutdown_time, |
127 | mask: axp20x_pek->info->shutdown_mask, buf); |
128 | } |
129 | |
130 | static ssize_t axp20x_store_attr(struct device *dev, |
131 | const struct axp20x_time *time, |
132 | unsigned int mask, const char *buf, |
133 | size_t count) |
134 | { |
135 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
136 | char val_str[20]; |
137 | size_t len; |
138 | int ret, i; |
139 | unsigned int val, idx = 0; |
140 | unsigned int best_err = UINT_MAX; |
141 | |
142 | val_str[sizeof(val_str) - 1] = '\0'; |
143 | strncpy(p: val_str, q: buf, size: sizeof(val_str) - 1); |
144 | len = strlen(val_str); |
145 | |
146 | if (len && val_str[len - 1] == '\n') |
147 | val_str[len - 1] = '\0'; |
148 | |
149 | ret = kstrtouint(s: val_str, base: 10, res: &val); |
150 | if (ret) |
151 | return ret; |
152 | |
153 | for (i = 3; i >= 0; i--) { |
154 | unsigned int err; |
155 | |
156 | err = abs(time[i].time - val); |
157 | if (err < best_err) { |
158 | best_err = err; |
159 | idx = time[i].idx; |
160 | } |
161 | |
162 | if (!err) |
163 | break; |
164 | } |
165 | |
166 | idx <<= ffs(mask) - 1; |
167 | ret = regmap_update_bits(map: axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, |
168 | mask, val: idx); |
169 | if (ret != 0) |
170 | return -EINVAL; |
171 | |
172 | return count; |
173 | } |
174 | |
175 | static ssize_t axp20x_store_attr_startup(struct device *dev, |
176 | struct device_attribute *attr, |
177 | const char *buf, size_t count) |
178 | { |
179 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
180 | |
181 | return axp20x_store_attr(dev, time: axp20x_pek->info->startup_time, |
182 | mask: axp20x_pek->info->startup_mask, buf, count); |
183 | } |
184 | |
185 | static ssize_t axp20x_store_attr_shutdown(struct device *dev, |
186 | struct device_attribute *attr, |
187 | const char *buf, size_t count) |
188 | { |
189 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
190 | |
191 | return axp20x_store_attr(dev, time: axp20x_pek->info->shutdown_time, |
192 | mask: axp20x_pek->info->shutdown_mask, buf, count); |
193 | } |
194 | |
195 | static DEVICE_ATTR(startup, 0644, axp20x_show_attr_startup, |
196 | axp20x_store_attr_startup); |
197 | static DEVICE_ATTR(shutdown, 0644, axp20x_show_attr_shutdown, |
198 | axp20x_store_attr_shutdown); |
199 | |
200 | static struct attribute *axp20x_attrs[] = { |
201 | &dev_attr_startup.attr, |
202 | &dev_attr_shutdown.attr, |
203 | NULL, |
204 | }; |
205 | ATTRIBUTE_GROUPS(axp20x); |
206 | |
207 | static irqreturn_t axp20x_pek_irq(int irq, void *pwr) |
208 | { |
209 | struct input_dev *idev = pwr; |
210 | struct axp20x_pek *axp20x_pek = input_get_drvdata(dev: idev); |
211 | |
212 | /* |
213 | * The power-button is connected to ground so a falling edge (dbf) |
214 | * means it is pressed. |
215 | */ |
216 | if (irq == axp20x_pek->irq_dbf) |
217 | input_report_key(dev: idev, KEY_POWER, value: true); |
218 | else if (irq == axp20x_pek->irq_dbr) |
219 | input_report_key(dev: idev, KEY_POWER, value: false); |
220 | |
221 | input_sync(dev: idev); |
222 | |
223 | return IRQ_HANDLED; |
224 | } |
225 | |
226 | static int axp20x_pek_probe_input_device(struct axp20x_pek *axp20x_pek, |
227 | struct platform_device *pdev) |
228 | { |
229 | struct axp20x_dev *axp20x = axp20x_pek->axp20x; |
230 | struct input_dev *idev; |
231 | int error; |
232 | |
233 | axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR" ); |
234 | if (axp20x_pek->irq_dbr < 0) |
235 | return axp20x_pek->irq_dbr; |
236 | axp20x_pek->irq_dbr = regmap_irq_get_virq(data: axp20x->regmap_irqc, |
237 | irq: axp20x_pek->irq_dbr); |
238 | |
239 | axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF" ); |
240 | if (axp20x_pek->irq_dbf < 0) |
241 | return axp20x_pek->irq_dbf; |
242 | axp20x_pek->irq_dbf = regmap_irq_get_virq(data: axp20x->regmap_irqc, |
243 | irq: axp20x_pek->irq_dbf); |
244 | |
245 | axp20x_pek->input = devm_input_allocate_device(&pdev->dev); |
246 | if (!axp20x_pek->input) |
247 | return -ENOMEM; |
248 | |
249 | idev = axp20x_pek->input; |
250 | |
251 | idev->name = "axp20x-pek" ; |
252 | idev->phys = "m1kbd/input2" ; |
253 | idev->dev.parent = &pdev->dev; |
254 | |
255 | input_set_capability(dev: idev, EV_KEY, KEY_POWER); |
256 | |
257 | input_set_drvdata(dev: idev, data: axp20x_pek); |
258 | |
259 | error = devm_request_any_context_irq(dev: &pdev->dev, irq: axp20x_pek->irq_dbr, |
260 | handler: axp20x_pek_irq, irqflags: 0, |
261 | devname: "axp20x-pek-dbr" , dev_id: idev); |
262 | if (error < 0) { |
263 | dev_err(&pdev->dev, "Failed to request dbr IRQ#%d: %d\n" , |
264 | axp20x_pek->irq_dbr, error); |
265 | return error; |
266 | } |
267 | |
268 | error = devm_request_any_context_irq(dev: &pdev->dev, irq: axp20x_pek->irq_dbf, |
269 | handler: axp20x_pek_irq, irqflags: 0, |
270 | devname: "axp20x-pek-dbf" , dev_id: idev); |
271 | if (error < 0) { |
272 | dev_err(&pdev->dev, "Failed to request dbf IRQ#%d: %d\n" , |
273 | axp20x_pek->irq_dbf, error); |
274 | return error; |
275 | } |
276 | |
277 | error = input_register_device(idev); |
278 | if (error) { |
279 | dev_err(&pdev->dev, "Can't register input device: %d\n" , |
280 | error); |
281 | return error; |
282 | } |
283 | |
284 | device_init_wakeup(dev: &pdev->dev, enable: true); |
285 | |
286 | return 0; |
287 | } |
288 | |
289 | static bool axp20x_pek_should_register_input(struct axp20x_pek *axp20x_pek) |
290 | { |
291 | if (IS_ENABLED(CONFIG_INPUT_SOC_BUTTON_ARRAY) && |
292 | axp20x_pek->axp20x->variant == AXP288_ID) { |
293 | /* |
294 | * On Cherry Trail platforms (hrv == 3), do not register the |
295 | * input device if there is an "INTCFD9" or "ACPI0011" gpio |
296 | * button ACPI device, as that handles the power button too, |
297 | * and otherwise we end up reporting all presses twice. |
298 | */ |
299 | if (soc_intel_is_cht() && |
300 | (acpi_dev_present(hid: "INTCFD9" , NULL, hrv: -1) || |
301 | acpi_dev_present(hid: "ACPI0011" , NULL, hrv: -1))) |
302 | return false; |
303 | } |
304 | |
305 | return true; |
306 | } |
307 | |
308 | static int axp20x_pek_probe(struct platform_device *pdev) |
309 | { |
310 | struct axp20x_pek *axp20x_pek; |
311 | const struct platform_device_id *match = platform_get_device_id(pdev); |
312 | int error; |
313 | |
314 | if (!match) { |
315 | dev_err(&pdev->dev, "Failed to get platform_device_id\n" ); |
316 | return -EINVAL; |
317 | } |
318 | |
319 | axp20x_pek = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct axp20x_pek), |
320 | GFP_KERNEL); |
321 | if (!axp20x_pek) |
322 | return -ENOMEM; |
323 | |
324 | axp20x_pek->axp20x = dev_get_drvdata(dev: pdev->dev.parent); |
325 | |
326 | if (axp20x_pek_should_register_input(axp20x_pek)) { |
327 | error = axp20x_pek_probe_input_device(axp20x_pek, pdev); |
328 | if (error) |
329 | return error; |
330 | } |
331 | |
332 | axp20x_pek->info = (struct axp20x_info *)match->driver_data; |
333 | |
334 | platform_set_drvdata(pdev, data: axp20x_pek); |
335 | |
336 | return 0; |
337 | } |
338 | |
339 | static int axp20x_pek_suspend(struct device *dev) |
340 | { |
341 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
342 | |
343 | /* |
344 | * As nested threaded IRQs are not automatically disabled during |
345 | * suspend, we must explicitly disable non-wakeup IRQs. |
346 | */ |
347 | if (device_may_wakeup(dev)) { |
348 | enable_irq_wake(irq: axp20x_pek->irq_dbf); |
349 | enable_irq_wake(irq: axp20x_pek->irq_dbr); |
350 | } else { |
351 | disable_irq(irq: axp20x_pek->irq_dbf); |
352 | disable_irq(irq: axp20x_pek->irq_dbr); |
353 | } |
354 | |
355 | return 0; |
356 | } |
357 | |
358 | static int axp20x_pek_resume(struct device *dev) |
359 | { |
360 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
361 | |
362 | if (device_may_wakeup(dev)) { |
363 | disable_irq_wake(irq: axp20x_pek->irq_dbf); |
364 | disable_irq_wake(irq: axp20x_pek->irq_dbr); |
365 | } else { |
366 | enable_irq(irq: axp20x_pek->irq_dbf); |
367 | enable_irq(irq: axp20x_pek->irq_dbr); |
368 | } |
369 | |
370 | return 0; |
371 | } |
372 | |
373 | static int __maybe_unused axp20x_pek_resume_noirq(struct device *dev) |
374 | { |
375 | struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); |
376 | |
377 | if (axp20x_pek->axp20x->variant != AXP288_ID) |
378 | return 0; |
379 | |
380 | /* |
381 | * Clear interrupts from button presses during suspend, to avoid |
382 | * a wakeup power-button press getting reported to userspace. |
383 | */ |
384 | regmap_write(map: axp20x_pek->axp20x->regmap, |
385 | AXP20X_IRQ1_STATE + AXP288_IRQ_POKN / 8, |
386 | BIT(AXP288_IRQ_POKN % 8)); |
387 | |
388 | return 0; |
389 | } |
390 | |
391 | static const struct dev_pm_ops axp20x_pek_pm_ops = { |
392 | SYSTEM_SLEEP_PM_OPS(axp20x_pek_suspend, axp20x_pek_resume) |
393 | .resume_noirq = pm_sleep_ptr(axp20x_pek_resume_noirq), |
394 | }; |
395 | |
396 | static const struct platform_device_id axp_pek_id_match[] = { |
397 | { |
398 | .name = "axp20x-pek" , |
399 | .driver_data = (kernel_ulong_t)&axp20x_info, |
400 | }, |
401 | { |
402 | .name = "axp221-pek" , |
403 | .driver_data = (kernel_ulong_t)&axp221_info, |
404 | }, |
405 | { /* sentinel */ } |
406 | }; |
407 | MODULE_DEVICE_TABLE(platform, axp_pek_id_match); |
408 | |
409 | static struct platform_driver axp20x_pek_driver = { |
410 | .probe = axp20x_pek_probe, |
411 | .id_table = axp_pek_id_match, |
412 | .driver = { |
413 | .name = "axp20x-pek" , |
414 | .pm = pm_sleep_ptr(&axp20x_pek_pm_ops), |
415 | .dev_groups = axp20x_groups, |
416 | }, |
417 | }; |
418 | module_platform_driver(axp20x_pek_driver); |
419 | |
420 | MODULE_DESCRIPTION("axp20x Power Button" ); |
421 | MODULE_AUTHOR("Carlo Caione <carlo@caione.org>" ); |
422 | MODULE_LICENSE("GPL" ); |
423 | |