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 */ |
21 | enum atc260x_onkey_reset_status { |
22 | KEY_RESET_HW_DEFAULT, |
23 | KEY_RESET_DISABLED, |
24 | KEY_RESET_USER_SEL, |
25 | }; |
26 | |
27 | struct 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 | |
40 | struct 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 | |
48 | static 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 | |
61 | static 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 | |
74 | static 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 | |
101 | static 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 | |
144 | static 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 | |
151 | static 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 | |
173 | static 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 | |
182 | static 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 | |
190 | static 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 | |
294 | static struct platform_driver atc260x_onkey_driver = { |
295 | .probe = atc260x_onkey_probe, |
296 | .driver = { |
297 | .name = "atc260x-onkey" , |
298 | }, |
299 | }; |
300 | |
301 | module_platform_driver(atc260x_onkey_driver); |
302 | |
303 | MODULE_DESCRIPTION("Onkey driver for ATC260x PMICs" ); |
304 | MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>" ); |
305 | MODULE_LICENSE("GPL" ); |
306 | |