1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Texas Instruments' Palmas Power Button Input Driver
4 *
5 * Copyright (C) 2012-2014 Texas Instruments Incorporated - http://www.ti.com/
6 * Girish S Ghongdemath
7 * Nishanth Menon
8 */
9
10#include <linux/bitfield.h>
11#include <linux/init.h>
12#include <linux/input.h>
13#include <linux/interrupt.h>
14#include <linux/kernel.h>
15#include <linux/mfd/palmas.h>
16#include <linux/module.h>
17#include <linux/of.h>
18#include <linux/platform_device.h>
19#include <linux/slab.h>
20
21#define PALMAS_LPK_TIME_MASK 0x0c
22#define PALMAS_PWRON_DEBOUNCE_MASK 0x03
23#define PALMAS_PWR_KEY_Q_TIME_MS 20
24
25/**
26 * struct palmas_pwron - Palmas power on data
27 * @palmas: pointer to palmas device
28 * @input_dev: pointer to input device
29 * @input_work: work for detecting release of key
30 * @irq: irq that we are hooked on to
31 */
32struct palmas_pwron {
33 struct palmas *palmas;
34 struct input_dev *input_dev;
35 struct delayed_work input_work;
36 int irq;
37};
38
39/**
40 * struct palmas_pwron_config - configuration of palmas power on
41 * @long_press_time_val: value for long press h/w shutdown event
42 * @pwron_debounce_val: value for debounce of power button
43 */
44struct palmas_pwron_config {
45 u8 long_press_time_val;
46 u8 pwron_debounce_val;
47};
48
49/**
50 * palmas_power_button_work() - Detects the button release event
51 * @work: work item to detect button release
52 */
53static void palmas_power_button_work(struct work_struct *work)
54{
55 struct palmas_pwron *pwron = container_of(work,
56 struct palmas_pwron,
57 input_work.work);
58 struct input_dev *input_dev = pwron->input_dev;
59 unsigned int reg;
60 int error;
61
62 error = palmas_read(palmas: pwron->palmas, PALMAS_INTERRUPT_BASE,
63 PALMAS_INT1_LINE_STATE, val: &reg);
64 if (error) {
65 dev_err(input_dev->dev.parent,
66 "Cannot read palmas PWRON status: %d\n", error);
67 } else if (reg & BIT(1)) {
68 /* The button is released, report event. */
69 input_report_key(dev: input_dev, KEY_POWER, value: 0);
70 input_sync(dev: input_dev);
71 } else {
72 /* The button is still depressed, keep checking. */
73 schedule_delayed_work(dwork: &pwron->input_work,
74 delay: msecs_to_jiffies(PALMAS_PWR_KEY_Q_TIME_MS));
75 }
76}
77
78/**
79 * pwron_irq() - button press isr
80 * @irq: irq
81 * @palmas_pwron: pwron struct
82 *
83 * Return: IRQ_HANDLED
84 */
85static irqreturn_t pwron_irq(int irq, void *palmas_pwron)
86{
87 struct palmas_pwron *pwron = palmas_pwron;
88 struct input_dev *input_dev = pwron->input_dev;
89
90 input_report_key(dev: input_dev, KEY_POWER, value: 1);
91 pm_wakeup_event(dev: input_dev->dev.parent, msec: 0);
92 input_sync(dev: input_dev);
93
94 mod_delayed_work(wq: system_wq, dwork: &pwron->input_work,
95 delay: msecs_to_jiffies(PALMAS_PWR_KEY_Q_TIME_MS));
96
97 return IRQ_HANDLED;
98}
99
100/**
101 * palmas_pwron_params_ofinit() - device tree parameter parser
102 * @dev: palmas button device
103 * @config: configuration params that this fills up
104 */
105static void palmas_pwron_params_ofinit(struct device *dev,
106 struct palmas_pwron_config *config)
107{
108 struct device_node *np;
109 u32 val;
110 int i, error;
111 static const u8 lpk_times[] = { 6, 8, 10, 12 };
112 static const int pwr_on_deb_ms[] = { 15, 100, 500, 1000 };
113
114 memset(config, 0, sizeof(*config));
115
116 /* Default config parameters */
117 config->long_press_time_val = ARRAY_SIZE(lpk_times) - 1;
118
119 np = dev->of_node;
120 if (!np)
121 return;
122
123 error = of_property_read_u32(np, propname: "ti,palmas-long-press-seconds", out_value: &val);
124 if (!error) {
125 for (i = 0; i < ARRAY_SIZE(lpk_times); i++) {
126 if (val <= lpk_times[i]) {
127 config->long_press_time_val = i;
128 break;
129 }
130 }
131 }
132
133 error = of_property_read_u32(np,
134 propname: "ti,palmas-pwron-debounce-milli-seconds",
135 out_value: &val);
136 if (!error) {
137 for (i = 0; i < ARRAY_SIZE(pwr_on_deb_ms); i++) {
138 if (val <= pwr_on_deb_ms[i]) {
139 config->pwron_debounce_val = i;
140 break;
141 }
142 }
143 }
144
145 dev_info(dev, "h/w controlled shutdown duration=%d seconds\n",
146 lpk_times[config->long_press_time_val]);
147}
148
149/**
150 * palmas_pwron_probe() - probe
151 * @pdev: platform device for the button
152 *
153 * Return: 0 for successful probe else appropriate error
154 */
155static int palmas_pwron_probe(struct platform_device *pdev)
156{
157 struct palmas *palmas = dev_get_drvdata(dev: pdev->dev.parent);
158 struct device *dev = &pdev->dev;
159 struct input_dev *input_dev;
160 struct palmas_pwron *pwron;
161 struct palmas_pwron_config config;
162 int val;
163 int error;
164
165 palmas_pwron_params_ofinit(dev, config: &config);
166
167 pwron = kzalloc(size: sizeof(*pwron), GFP_KERNEL);
168 if (!pwron)
169 return -ENOMEM;
170
171 input_dev = input_allocate_device();
172 if (!input_dev) {
173 dev_err(dev, "Can't allocate power button\n");
174 error = -ENOMEM;
175 goto err_free_mem;
176 }
177
178 input_dev->name = "palmas_pwron";
179 input_dev->phys = "palmas_pwron/input0";
180 input_dev->dev.parent = dev;
181
182 input_set_capability(dev: input_dev, EV_KEY, KEY_POWER);
183
184 /*
185 * Setup default hardware shutdown option (long key press)
186 * and debounce.
187 */
188 val = FIELD_PREP(PALMAS_LPK_TIME_MASK, config.long_press_time_val) |
189 FIELD_PREP(PALMAS_PWRON_DEBOUNCE_MASK, config.pwron_debounce_val);
190 error = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE,
191 PALMAS_LONG_PRESS_KEY,
192 PALMAS_LPK_TIME_MASK |
193 PALMAS_PWRON_DEBOUNCE_MASK,
194 val);
195 if (error) {
196 dev_err(dev, "LONG_PRESS_KEY_UPDATE failed: %d\n", error);
197 goto err_free_input;
198 }
199
200 pwron->palmas = palmas;
201 pwron->input_dev = input_dev;
202
203 INIT_DELAYED_WORK(&pwron->input_work, palmas_power_button_work);
204
205 pwron->irq = platform_get_irq(pdev, 0);
206 if (pwron->irq < 0) {
207 error = pwron->irq;
208 goto err_free_input;
209 }
210
211 error = request_threaded_irq(irq: pwron->irq, NULL, thread_fn: pwron_irq,
212 IRQF_TRIGGER_HIGH |
213 IRQF_TRIGGER_LOW |
214 IRQF_ONESHOT,
215 name: dev_name(dev), dev: pwron);
216 if (error) {
217 dev_err(dev, "Can't get IRQ for pwron: %d\n", error);
218 goto err_free_input;
219 }
220
221 error = input_register_device(input_dev);
222 if (error) {
223 dev_err(dev, "Can't register power button: %d\n", error);
224 goto err_free_irq;
225 }
226
227 platform_set_drvdata(pdev, data: pwron);
228 device_init_wakeup(dev, enable: true);
229
230 return 0;
231
232err_free_irq:
233 cancel_delayed_work_sync(dwork: &pwron->input_work);
234 free_irq(pwron->irq, pwron);
235err_free_input:
236 input_free_device(dev: input_dev);
237err_free_mem:
238 kfree(objp: pwron);
239 return error;
240}
241
242/**
243 * palmas_pwron_remove() - Cleanup on removal
244 * @pdev: platform device for the button
245 *
246 * Return: 0
247 */
248static int palmas_pwron_remove(struct platform_device *pdev)
249{
250 struct palmas_pwron *pwron = platform_get_drvdata(pdev);
251
252 free_irq(pwron->irq, pwron);
253 cancel_delayed_work_sync(dwork: &pwron->input_work);
254
255 input_unregister_device(pwron->input_dev);
256 kfree(objp: pwron);
257
258 return 0;
259}
260
261/**
262 * palmas_pwron_suspend() - suspend handler
263 * @dev: power button device
264 *
265 * Cancel all pending work items for the power button, setup irq for wakeup
266 *
267 * Return: 0
268 */
269static int palmas_pwron_suspend(struct device *dev)
270{
271 struct platform_device *pdev = to_platform_device(dev);
272 struct palmas_pwron *pwron = platform_get_drvdata(pdev);
273
274 cancel_delayed_work_sync(dwork: &pwron->input_work);
275
276 if (device_may_wakeup(dev))
277 enable_irq_wake(irq: pwron->irq);
278
279 return 0;
280}
281
282/**
283 * palmas_pwron_resume() - resume handler
284 * @dev: power button device
285 *
286 * Just disable the wakeup capability of irq here.
287 *
288 * Return: 0
289 */
290static int palmas_pwron_resume(struct device *dev)
291{
292 struct platform_device *pdev = to_platform_device(dev);
293 struct palmas_pwron *pwron = platform_get_drvdata(pdev);
294
295 if (device_may_wakeup(dev))
296 disable_irq_wake(irq: pwron->irq);
297
298 return 0;
299}
300
301static DEFINE_SIMPLE_DEV_PM_OPS(palmas_pwron_pm,
302 palmas_pwron_suspend, palmas_pwron_resume);
303
304#ifdef CONFIG_OF
305static const struct of_device_id of_palmas_pwr_match[] = {
306 { .compatible = "ti,palmas-pwrbutton" },
307 { },
308};
309
310MODULE_DEVICE_TABLE(of, of_palmas_pwr_match);
311#endif
312
313static struct platform_driver palmas_pwron_driver = {
314 .probe = palmas_pwron_probe,
315 .remove = palmas_pwron_remove,
316 .driver = {
317 .name = "palmas_pwrbutton",
318 .of_match_table = of_match_ptr(of_palmas_pwr_match),
319 .pm = pm_sleep_ptr(&palmas_pwron_pm),
320 },
321};
322module_platform_driver(palmas_pwron_driver);
323
324MODULE_ALIAS("platform:palmas-pwrbutton");
325MODULE_DESCRIPTION("Palmas Power Button");
326MODULE_LICENSE("GPL v2");
327MODULE_AUTHOR("Texas Instruments Inc.");
328

source code of linux/drivers/input/misc/palmas-pwrbutton.c