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