1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Onkey driver for Actions Semi ATC260x PMICs.
4 *
5 * Copyright (c) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com>
6 */
7
8#include <linux/bitfield.h>
9#include <linux/input.h>
10#include <linux/interrupt.h>
11#include <linux/mfd/atc260x/core.h>
12#include <linux/module.h>
13#include <linux/of.h>
14#include <linux/platform_device.h>
15#include <linux/regmap.h>
16
17/* <2s for short press, >2s for long press */
18#define KEY_PRESS_TIME_SEC 2
19
20/* Driver internals */
21enum atc260x_onkey_reset_status {
22 KEY_RESET_HW_DEFAULT,
23 KEY_RESET_DISABLED,
24 KEY_RESET_USER_SEL,
25};
26
27struct atc260x_onkey_params {
28 u32 reg_int_ctl;
29 u32 kdwn_state_bm;
30 u32 long_int_pnd_bm;
31 u32 short_int_pnd_bm;
32 u32 kdwn_int_pnd_bm;
33 u32 press_int_en_bm;
34 u32 kdwn_int_en_bm;
35 u32 press_time_bm;
36 u32 reset_en_bm;
37 u32 reset_time_bm;
38};
39
40struct atc260x_onkey {
41 struct atc260x *atc260x;
42 const struct atc260x_onkey_params *params;
43 struct input_dev *input_dev;
44 struct delayed_work work;
45 int irq;
46};
47
48static const struct atc260x_onkey_params atc2603c_onkey_params = {
49 .reg_int_ctl = ATC2603C_PMU_SYS_CTL2,
50 .long_int_pnd_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_LONG_PRESS,
51 .short_int_pnd_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_SHORT_PRESS,
52 .kdwn_int_pnd_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_PD,
53 .press_int_en_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_INT_EN,
54 .kdwn_int_en_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_INT_EN,
55 .kdwn_state_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS,
56 .press_time_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_TIME,
57 .reset_en_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_RESET_EN,
58 .reset_time_bm = ATC2603C_PMU_SYS_CTL2_ONOFF_RESET_TIME_SEL,
59};
60
61static const struct atc260x_onkey_params atc2609a_onkey_params = {
62 .reg_int_ctl = ATC2609A_PMU_SYS_CTL2,
63 .long_int_pnd_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_LONG_PRESS,
64 .short_int_pnd_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_SHORT_PRESS,
65 .kdwn_int_pnd_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS_PD,
66 .press_int_en_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_LSP_INT_EN,
67 .kdwn_int_en_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS_INT_EN,
68 .kdwn_state_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS,
69 .press_time_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS_TIME,
70 .reset_en_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_RESET_EN,
71 .reset_time_bm = ATC2609A_PMU_SYS_CTL2_ONOFF_RESET_TIME_SEL,
72};
73
74static int atc2603x_onkey_hw_init(struct atc260x_onkey *onkey,
75 enum atc260x_onkey_reset_status reset_status,
76 u32 reset_time, u32 press_time)
77{
78 u32 reg_bm, reg_val;
79
80 reg_bm = onkey->params->long_int_pnd_bm |
81 onkey->params->short_int_pnd_bm |
82 onkey->params->kdwn_int_pnd_bm |
83 onkey->params->press_int_en_bm |
84 onkey->params->kdwn_int_en_bm;
85
86 reg_val = reg_bm | press_time;
87 reg_bm |= onkey->params->press_time_bm;
88
89 if (reset_status == KEY_RESET_DISABLED) {
90 reg_bm |= onkey->params->reset_en_bm;
91 } else if (reset_status == KEY_RESET_USER_SEL) {
92 reg_bm |= onkey->params->reset_en_bm |
93 onkey->params->reset_time_bm;
94 reg_val |= onkey->params->reset_en_bm | reset_time;
95 }
96
97 return regmap_update_bits(map: onkey->atc260x->regmap,
98 reg: onkey->params->reg_int_ctl, mask: reg_bm, val: reg_val);
99}
100
101static void atc260x_onkey_query(struct atc260x_onkey *onkey)
102{
103 u32 reg_bits;
104 int ret, key_down;
105
106 ret = regmap_read(map: onkey->atc260x->regmap,
107 reg: onkey->params->reg_int_ctl, val: &key_down);
108 if (ret) {
109 key_down = 1;
110 dev_err(onkey->atc260x->dev,
111 "Failed to read onkey status: %d\n", ret);
112 } else {
113 key_down &= onkey->params->kdwn_state_bm;
114 }
115
116 /*
117 * The hardware generates interrupt only when the onkey pin is
118 * asserted. Hence, the deassertion of the pin is simulated through
119 * work queue.
120 */
121 if (key_down) {
122 schedule_delayed_work(dwork: &onkey->work, delay: msecs_to_jiffies(m: 200));
123 return;
124 }
125
126 /*
127 * The key-down status bit is cleared when the On/Off button
128 * is released.
129 */
130 input_report_key(dev: onkey->input_dev, KEY_POWER, value: 0);
131 input_sync(dev: onkey->input_dev);
132
133 reg_bits = onkey->params->long_int_pnd_bm |
134 onkey->params->short_int_pnd_bm |
135 onkey->params->kdwn_int_pnd_bm |
136 onkey->params->press_int_en_bm |
137 onkey->params->kdwn_int_en_bm;
138
139 /* Clear key press pending events and enable key press interrupts. */
140 regmap_update_bits(map: onkey->atc260x->regmap, reg: onkey->params->reg_int_ctl,
141 mask: reg_bits, val: reg_bits);
142}
143
144static void atc260x_onkey_work(struct work_struct *work)
145{
146 struct atc260x_onkey *onkey = container_of(work, struct atc260x_onkey,
147 work.work);
148 atc260x_onkey_query(onkey);
149}
150
151static irqreturn_t atc260x_onkey_irq(int irq, void *data)
152{
153 struct atc260x_onkey *onkey = data;
154 int ret;
155
156 /* Disable key press interrupts. */
157 ret = regmap_update_bits(map: onkey->atc260x->regmap,
158 reg: onkey->params->reg_int_ctl,
159 mask: onkey->params->press_int_en_bm |
160 onkey->params->kdwn_int_en_bm, val: 0);
161 if (ret)
162 dev_err(onkey->atc260x->dev,
163 "Failed to disable interrupts: %d\n", ret);
164
165 input_report_key(dev: onkey->input_dev, KEY_POWER, value: 1);
166 input_sync(dev: onkey->input_dev);
167
168 atc260x_onkey_query(onkey);
169
170 return IRQ_HANDLED;
171}
172
173static int atc260x_onkey_open(struct input_dev *dev)
174{
175 struct atc260x_onkey *onkey = input_get_drvdata(dev);
176
177 enable_irq(irq: onkey->irq);
178
179 return 0;
180}
181
182static void atc260x_onkey_close(struct input_dev *dev)
183{
184 struct atc260x_onkey *onkey = input_get_drvdata(dev);
185
186 disable_irq(irq: onkey->irq);
187 cancel_delayed_work_sync(dwork: &onkey->work);
188}
189
190static int atc260x_onkey_probe(struct platform_device *pdev)
191{
192 struct atc260x *atc260x = dev_get_drvdata(dev: pdev->dev.parent);
193 struct atc260x_onkey *onkey;
194 struct input_dev *input_dev;
195 enum atc260x_onkey_reset_status reset_status;
196 u32 press_time = KEY_PRESS_TIME_SEC, reset_time = 0;
197 int val, error;
198
199 onkey = devm_kzalloc(dev: &pdev->dev, size: sizeof(*onkey), GFP_KERNEL);
200 if (!onkey)
201 return -ENOMEM;
202
203 error = device_property_read_u32(dev: pdev->dev.parent,
204 propname: "reset-time-sec", val: &val);
205 if (error) {
206 reset_status = KEY_RESET_HW_DEFAULT;
207 } else if (val) {
208 if (val < 6 || val > 12) {
209 dev_err(&pdev->dev, "reset-time-sec out of range\n");
210 return -EINVAL;
211 }
212
213 reset_status = KEY_RESET_USER_SEL;
214 reset_time = (val - 6) / 2;
215 } else {
216 reset_status = KEY_RESET_DISABLED;
217 dev_dbg(&pdev->dev, "Disabled reset on long-press\n");
218 }
219
220 switch (atc260x->ic_type) {
221 case ATC2603C:
222 onkey->params = &atc2603c_onkey_params;
223 press_time = FIELD_PREP(ATC2603C_PMU_SYS_CTL2_ONOFF_PRESS_TIME,
224 press_time);
225 reset_time = FIELD_PREP(ATC2603C_PMU_SYS_CTL2_ONOFF_RESET_TIME_SEL,
226 reset_time);
227 break;
228 case ATC2609A:
229 onkey->params = &atc2609a_onkey_params;
230 press_time = FIELD_PREP(ATC2609A_PMU_SYS_CTL2_ONOFF_PRESS_TIME,
231 press_time);
232 reset_time = FIELD_PREP(ATC2609A_PMU_SYS_CTL2_ONOFF_RESET_TIME_SEL,
233 reset_time);
234 break;
235 default:
236 dev_err(&pdev->dev,
237 "OnKey not supported for ATC260x PMIC type: %u\n",
238 atc260x->ic_type);
239 return -EINVAL;
240 }
241
242 input_dev = devm_input_allocate_device(&pdev->dev);
243 if (!input_dev) {
244 dev_err(&pdev->dev, "Failed to allocate input device\n");
245 return -ENOMEM;
246 }
247
248 onkey->input_dev = input_dev;
249 onkey->atc260x = atc260x;
250
251 input_dev->name = "atc260x-onkey";
252 input_dev->phys = "atc260x-onkey/input0";
253 input_dev->open = atc260x_onkey_open;
254 input_dev->close = atc260x_onkey_close;
255
256 input_set_capability(dev: input_dev, EV_KEY, KEY_POWER);
257 input_set_drvdata(dev: input_dev, data: onkey);
258
259 INIT_DELAYED_WORK(&onkey->work, atc260x_onkey_work);
260
261 onkey->irq = platform_get_irq(pdev, 0);
262 if (onkey->irq < 0)
263 return onkey->irq;
264
265 error = devm_request_threaded_irq(dev: &pdev->dev, irq: onkey->irq, NULL,
266 thread_fn: atc260x_onkey_irq, IRQF_ONESHOT,
267 devname: dev_name(dev: &pdev->dev), dev_id: onkey);
268 if (error) {
269 dev_err(&pdev->dev,
270 "Failed to register IRQ %d: %d\n", onkey->irq, error);
271 return error;
272 }
273
274 /* Keep IRQ disabled until atc260x_onkey_open() is called. */
275 disable_irq(irq: onkey->irq);
276
277 error = input_register_device(input_dev);
278 if (error) {
279 dev_err(&pdev->dev,
280 "Failed to register input device: %d\n", error);
281 return error;
282 }
283
284 error = atc2603x_onkey_hw_init(onkey, reset_status,
285 reset_time, press_time);
286 if (error)
287 return error;
288
289 device_init_wakeup(dev: &pdev->dev, enable: true);
290
291 return 0;
292}
293
294static struct platform_driver atc260x_onkey_driver = {
295 .probe = atc260x_onkey_probe,
296 .driver = {
297 .name = "atc260x-onkey",
298 },
299};
300
301module_platform_driver(atc260x_onkey_driver);
302
303MODULE_DESCRIPTION("Onkey driver for ATC260x PMICs");
304MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>");
305MODULE_LICENSE("GPL");
306

source code of linux/drivers/input/misc/atc260x-onkey.c