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 | */ |
32 | struct 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 | */ |
44 | struct 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 | */ |
53 | static 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: ®); |
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 | */ |
85 | static 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 | */ |
105 | static 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 | */ |
155 | static 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 | |
232 | err_free_irq: |
233 | cancel_delayed_work_sync(dwork: &pwron->input_work); |
234 | free_irq(pwron->irq, pwron); |
235 | err_free_input: |
236 | input_free_device(dev: input_dev); |
237 | err_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 | */ |
248 | static 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 | */ |
269 | static 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 | */ |
290 | static 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 | |
301 | static DEFINE_SIMPLE_DEV_PM_OPS(palmas_pwron_pm, |
302 | palmas_pwron_suspend, palmas_pwron_resume); |
303 | |
304 | #ifdef CONFIG_OF |
305 | static const struct of_device_id of_palmas_pwr_match[] = { |
306 | { .compatible = "ti,palmas-pwrbutton" }, |
307 | { }, |
308 | }; |
309 | |
310 | MODULE_DEVICE_TABLE(of, of_palmas_pwr_match); |
311 | #endif |
312 | |
313 | static 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 | }; |
322 | module_platform_driver(palmas_pwron_driver); |
323 | |
324 | MODULE_ALIAS("platform:palmas-pwrbutton" ); |
325 | MODULE_DESCRIPTION("Palmas Power Button" ); |
326 | MODULE_LICENSE("GPL v2" ); |
327 | MODULE_AUTHOR("Texas Instruments Inc." ); |
328 | |