]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blame - drivers/extcon/extcon-arizona.c
extcon: arizona: Allow configuration of MICBIAS rise time
[mirror_ubuntu-bionic-kernel.git] / drivers / extcon / extcon-arizona.c
CommitLineData
f2c32a88
MB
1/*
2 * extcon-arizona.c - Extcon driver Wolfson Arizona devices
3 *
4 * Copyright (C) 2012 Wolfson Microelectronics plc
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17#include <linux/kernel.h>
18#include <linux/module.h>
19#include <linux/i2c.h>
20#include <linux/slab.h>
21#include <linux/interrupt.h>
22#include <linux/err.h>
23#include <linux/gpio.h>
34efe4dc 24#include <linux/input.h>
f2c32a88
MB
25#include <linux/platform_device.h>
26#include <linux/pm_runtime.h>
27#include <linux/regulator/consumer.h>
28#include <linux/extcon.h>
29
30#include <linux/mfd/arizona/core.h>
31#include <linux/mfd/arizona/pdata.h>
32#include <linux/mfd/arizona/registers.h>
33
34efe4dc
MB
34#define ARIZONA_NUM_BUTTONS 6
35
f2c32a88
MB
36struct arizona_extcon_info {
37 struct device *dev;
38 struct arizona *arizona;
39 struct mutex lock;
40 struct regulator *micvdd;
34efe4dc 41 struct input_dev *input;
f2c32a88
MB
42
43 int micd_mode;
44 const struct arizona_micd_config *micd_modes;
45 int micd_num_modes;
46
47 bool micd_reva;
48
49 bool mic;
50 bool detecting;
51 int jack_flips;
52
53 struct extcon_dev edev;
54};
55
56static const struct arizona_micd_config micd_default_modes[] = {
57 { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 },
58 { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 },
59};
60
34efe4dc
MB
61static struct {
62 u16 status;
63 int report;
64} arizona_lvl_to_key[ARIZONA_NUM_BUTTONS] = {
65 { 0x1, BTN_0 },
66 { 0x2, BTN_1 },
67 { 0x4, BTN_2 },
68 { 0x8, BTN_3 },
69 { 0x10, BTN_4 },
70 { 0x20, BTN_5 },
71};
72
325c6423
MB
73#define ARIZONA_CABLE_MECHANICAL 0
74#define ARIZONA_CABLE_MICROPHONE 1
75#define ARIZONA_CABLE_HEADPHONE 2
f2c32a88
MB
76
77static const char *arizona_cable[] = {
325c6423
MB
78 "Mechanical",
79 "Microphone",
80 "Headphone",
f2c32a88
MB
81 NULL,
82};
83
f2c32a88
MB
84static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode)
85{
86 struct arizona *arizona = info->arizona;
87
cd74f7b3
MB
88 if (arizona->pdata.micd_pol_gpio > 0)
89 gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio,
90 info->micd_modes[mode].gpio);
f2c32a88
MB
91 regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
92 ARIZONA_MICD_BIAS_SRC_MASK,
93 info->micd_modes[mode].bias);
94 regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1,
95 ARIZONA_ACCDET_SRC, info->micd_modes[mode].src);
96
97 info->micd_mode = mode;
98
99 dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode);
100}
101
102static void arizona_start_mic(struct arizona_extcon_info *info)
103{
104 struct arizona *arizona = info->arizona;
105 bool change;
106 int ret;
107
108 info->detecting = true;
109 info->mic = false;
110 info->jack_flips = 0;
111
112 /* Microphone detection can't use idle mode */
113 pm_runtime_get(info->dev);
114
115 ret = regulator_enable(info->micvdd);
116 if (ret != 0) {
117 dev_err(arizona->dev, "Failed to enable MICVDD: %d\n",
118 ret);
119 }
120
121 if (info->micd_reva) {
122 regmap_write(arizona->regmap, 0x80, 0x3);
123 regmap_write(arizona->regmap, 0x294, 0);
124 regmap_write(arizona->regmap, 0x80, 0x0);
125 }
126
127 regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
128 ARIZONA_MICD_ENA, ARIZONA_MICD_ENA,
129 &change);
130 if (!change) {
131 regulator_disable(info->micvdd);
132 pm_runtime_put_autosuspend(info->dev);
133 }
134}
135
136static void arizona_stop_mic(struct arizona_extcon_info *info)
137{
138 struct arizona *arizona = info->arizona;
139 bool change;
140
141 regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
142 ARIZONA_MICD_ENA, 0,
143 &change);
144
145 if (info->micd_reva) {
146 regmap_write(arizona->regmap, 0x80, 0x3);
147 regmap_write(arizona->regmap, 0x294, 2);
148 regmap_write(arizona->regmap, 0x80, 0x0);
149 }
150
151 if (change) {
152 regulator_disable(info->micvdd);
34efe4dc 153 pm_runtime_mark_last_busy(info->dev);
f2c32a88
MB
154 pm_runtime_put_autosuspend(info->dev);
155 }
156}
157
158static irqreturn_t arizona_micdet(int irq, void *data)
159{
160 struct arizona_extcon_info *info = data;
161 struct arizona *arizona = info->arizona;
34efe4dc
MB
162 unsigned int val, lvl;
163 int ret, i;
f2c32a88
MB
164
165 mutex_lock(&info->lock);
166
167 ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val);
168 if (ret != 0) {
169 dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret);
be31cc0b 170 mutex_unlock(&info->lock);
f2c32a88
MB
171 return IRQ_NONE;
172 }
173
174 dev_dbg(arizona->dev, "MICDET: %x\n", val);
175
176 if (!(val & ARIZONA_MICD_VALID)) {
177 dev_warn(arizona->dev, "Microphone detection state invalid\n");
178 mutex_unlock(&info->lock);
179 return IRQ_NONE;
180 }
181
182 /* Due to jack detect this should never happen */
183 if (!(val & ARIZONA_MICD_STS)) {
184 dev_warn(arizona->dev, "Detected open circuit\n");
185 info->detecting = false;
186 goto handled;
187 }
188
189 /* If we got a high impedence we should have a headset, report it. */
190 if (info->detecting && (val & 0x400)) {
325c6423
MB
191 ret = extcon_update_state(&info->edev,
192 1 << ARIZONA_CABLE_MICROPHONE |
193 1 << ARIZONA_CABLE_HEADPHONE,
194 1 << ARIZONA_CABLE_MICROPHONE |
195 1 << ARIZONA_CABLE_HEADPHONE);
f2c32a88
MB
196
197 if (ret != 0)
198 dev_err(arizona->dev, "Headset report failed: %d\n",
199 ret);
200
201 info->mic = true;
202 info->detecting = false;
203 goto handled;
204 }
205
206 /* If we detected a lower impedence during initial startup
207 * then we probably have the wrong polarity, flip it. Don't
208 * do this for the lowest impedences to speed up detection of
209 * plain headphones. If both polarities report a low
210 * impedence then give up and report headphones.
211 */
212 if (info->detecting && (val & 0x3f8)) {
213 info->jack_flips++;
214
215 if (info->jack_flips >= info->micd_num_modes) {
216 dev_dbg(arizona->dev, "Detected headphone\n");
217 info->detecting = false;
9ef2224d
MB
218 arizona_stop_mic(info);
219
325c6423
MB
220 ret = extcon_set_cable_state_(&info->edev,
221 ARIZONA_CABLE_HEADPHONE,
222 true);
f2c32a88
MB
223 if (ret != 0)
224 dev_err(arizona->dev,
225 "Headphone report failed: %d\n",
226 ret);
227 } else {
228 info->micd_mode++;
229 if (info->micd_mode == info->micd_num_modes)
230 info->micd_mode = 0;
231 arizona_extcon_set_mode(info, info->micd_mode);
232
233 info->jack_flips++;
234 }
235
236 goto handled;
237 }
238
239 /*
240 * If we're still detecting and we detect a short then we've
34efe4dc 241 * got a headphone. Otherwise it's a button press.
f2c32a88
MB
242 */
243 if (val & 0x3fc) {
244 if (info->mic) {
245 dev_dbg(arizona->dev, "Mic button detected\n");
246
34efe4dc
MB
247 lvl = val & ARIZONA_MICD_LVL_MASK;
248 lvl >>= ARIZONA_MICD_LVL_SHIFT;
249
250 for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
251 if (lvl & arizona_lvl_to_key[i].status)
252 input_report_key(info->input,
253 arizona_lvl_to_key[i].report,
254 1);
255 input_sync(info->input);
256
f2c32a88
MB
257 } else if (info->detecting) {
258 dev_dbg(arizona->dev, "Headphone detected\n");
259 info->detecting = false;
260 arizona_stop_mic(info);
261
325c6423
MB
262 ret = extcon_set_cable_state_(&info->edev,
263 ARIZONA_CABLE_HEADPHONE,
264 true);
f2c32a88
MB
265 if (ret != 0)
266 dev_err(arizona->dev,
267 "Headphone report failed: %d\n",
268 ret);
269 } else {
270 dev_warn(arizona->dev, "Button with no mic: %x\n",
271 val);
272 }
273 } else {
274 dev_dbg(arizona->dev, "Mic button released\n");
34efe4dc
MB
275 for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
276 input_report_key(info->input,
277 arizona_lvl_to_key[i].report, 0);
278 input_sync(info->input);
f2c32a88
MB
279 }
280
281handled:
282 pm_runtime_mark_last_busy(info->dev);
283 mutex_unlock(&info->lock);
284
285 return IRQ_HANDLED;
286}
287
288static irqreturn_t arizona_jackdet(int irq, void *data)
289{
290 struct arizona_extcon_info *info = data;
291 struct arizona *arizona = info->arizona;
292 unsigned int val;
34efe4dc 293 int ret, i;
f2c32a88
MB
294
295 pm_runtime_get_sync(info->dev);
296
297 mutex_lock(&info->lock);
298
299 ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val);
300 if (ret != 0) {
301 dev_err(arizona->dev, "Failed to read jackdet status: %d\n",
302 ret);
303 mutex_unlock(&info->lock);
304 pm_runtime_put_autosuspend(info->dev);
305 return IRQ_NONE;
306 }
307
308 if (val & ARIZONA_JD1_STS) {
309 dev_dbg(arizona->dev, "Detected jack\n");
325c6423
MB
310 ret = extcon_set_cable_state_(&info->edev,
311 ARIZONA_CABLE_MECHANICAL, true);
f2c32a88
MB
312
313 if (ret != 0)
314 dev_err(arizona->dev, "Mechanical report failed: %d\n",
315 ret);
316
317 arizona_start_mic(info);
318 } else {
319 dev_dbg(arizona->dev, "Detected jack removal\n");
320
321 arizona_stop_mic(info);
322
34efe4dc
MB
323 for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
324 input_report_key(info->input,
325 arizona_lvl_to_key[i].report, 0);
326 input_sync(info->input);
327
f2c32a88
MB
328 ret = extcon_update_state(&info->edev, 0xffffffff, 0);
329 if (ret != 0)
330 dev_err(arizona->dev, "Removal report failed: %d\n",
331 ret);
332 }
333
334 mutex_unlock(&info->lock);
335
336 pm_runtime_mark_last_busy(info->dev);
337 pm_runtime_put_autosuspend(info->dev);
338
339 return IRQ_HANDLED;
340}
341
44f34fd4 342static int arizona_extcon_probe(struct platform_device *pdev)
f2c32a88
MB
343{
344 struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
345 struct arizona_pdata *pdata;
346 struct arizona_extcon_info *info;
34efe4dc 347 int ret, mode, i;
f2c32a88
MB
348
349 pdata = dev_get_platdata(arizona->dev);
350
351 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
352 if (!info) {
8e5f5018 353 dev_err(&pdev->dev, "Failed to allocate memory\n");
f2c32a88
MB
354 ret = -ENOMEM;
355 goto err;
356 }
357
358 info->micvdd = devm_regulator_get(arizona->dev, "MICVDD");
359 if (IS_ERR(info->micvdd)) {
360 ret = PTR_ERR(info->micvdd);
361 dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret);
362 goto err;
363 }
364
365 mutex_init(&info->lock);
366 info->arizona = arizona;
367 info->dev = &pdev->dev;
368 info->detecting = true;
369 platform_set_drvdata(pdev, info);
370
371 switch (arizona->type) {
372 case WM5102:
373 switch (arizona->rev) {
374 case 0:
375 info->micd_reva = true;
376 break;
377 default:
378 break;
379 }
380 break;
381 default:
382 break;
383 }
384
385 info->edev.name = "Headset Jack";
386 info->edev.supported_cable = arizona_cable;
f2c32a88
MB
387
388 ret = extcon_dev_register(&info->edev, arizona->dev);
389 if (ret < 0) {
8e5f5018 390 dev_err(arizona->dev, "extcon_dev_register() failed: %d\n",
f2c32a88
MB
391 ret);
392 goto err;
393 }
394
395 if (pdata->num_micd_configs) {
396 info->micd_modes = pdata->micd_configs;
397 info->micd_num_modes = pdata->num_micd_configs;
398 } else {
399 info->micd_modes = micd_default_modes;
400 info->micd_num_modes = ARRAY_SIZE(micd_default_modes);
401 }
402
403 if (arizona->pdata.micd_pol_gpio > 0) {
404 if (info->micd_modes[0].gpio)
405 mode = GPIOF_OUT_INIT_HIGH;
406 else
407 mode = GPIOF_OUT_INIT_LOW;
408
409 ret = devm_gpio_request_one(&pdev->dev,
410 arizona->pdata.micd_pol_gpio,
411 mode,
412 "MICD polarity");
413 if (ret != 0) {
414 dev_err(arizona->dev, "Failed to request GPIO%d: %d\n",
415 arizona->pdata.micd_pol_gpio, ret);
416 goto err_register;
417 }
418 }
419
b17e5462
MB
420 if (arizona->pdata.micd_bias_start_time)
421 regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
422 ARIZONA_MICD_BIAS_STARTTIME_MASK,
423 arizona->pdata.micd_bias_start_time
424 << ARIZONA_MICD_BIAS_STARTTIME_SHIFT);
425
f2c32a88
MB
426 arizona_extcon_set_mode(info, 0);
427
3d44ea1c 428 info->input = devm_input_allocate_device(&pdev->dev);
34efe4dc
MB
429 if (!info->input) {
430 dev_err(arizona->dev, "Can't allocate input dev\n");
431 ret = -ENOMEM;
432 goto err_register;
433 }
434
435 for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
436 input_set_capability(info->input, EV_KEY,
437 arizona_lvl_to_key[i].report);
438 info->input->name = "Headset";
439 info->input->phys = "arizona/extcon";
440 info->input->dev.parent = &pdev->dev;
441
f2c32a88
MB
442 pm_runtime_enable(&pdev->dev);
443 pm_runtime_idle(&pdev->dev);
444 pm_runtime_get_sync(&pdev->dev);
445
446 ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_RISE,
447 "JACKDET rise", arizona_jackdet, info);
448 if (ret != 0) {
449 dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n",
450 ret);
34efe4dc 451 goto err_input;
f2c32a88
MB
452 }
453
454 ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1);
455 if (ret != 0) {
456 dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n",
457 ret);
458 goto err_rise;
459 }
460
461 ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_FALL,
462 "JACKDET fall", arizona_jackdet, info);
463 if (ret != 0) {
464 dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret);
465 goto err_rise_wake;
466 }
467
468 ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 1);
469 if (ret != 0) {
470 dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n",
471 ret);
472 goto err_fall;
473 }
474
475 ret = arizona_request_irq(arizona, ARIZONA_IRQ_MICDET,
476 "MICDET", arizona_micdet, info);
477 if (ret != 0) {
478 dev_err(&pdev->dev, "Failed to get MICDET IRQ: %d\n", ret);
479 goto err_fall_wake;
480 }
481
482 regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
f2c32a88 483 ARIZONA_MICD_RATE_MASK,
f2c32a88
MB
484 8 << ARIZONA_MICD_RATE_SHIFT);
485
486 arizona_clk32k_enable(arizona);
487 regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE,
488 ARIZONA_JD1_DB, ARIZONA_JD1_DB);
489 regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
490 ARIZONA_JD1_ENA, ARIZONA_JD1_ENA);
491
b8575a11
MB
492 ret = regulator_allow_bypass(info->micvdd, true);
493 if (ret != 0)
494 dev_warn(arizona->dev, "Failed to set MICVDD to bypass: %d\n",
495 ret);
496
f2c32a88
MB
497 pm_runtime_put(&pdev->dev);
498
34efe4dc
MB
499 ret = input_register_device(info->input);
500 if (ret) {
501 dev_err(&pdev->dev, "Can't register input device: %d\n", ret);
80732cc1 502 goto err_micdet;
34efe4dc
MB
503 }
504
f2c32a88
MB
505 return 0;
506
80732cc1
MB
507err_micdet:
508 arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info);
f2c32a88
MB
509err_fall_wake:
510 arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0);
511err_fall:
512 arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info);
513err_rise_wake:
514 arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0);
515err_rise:
516 arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info);
34efe4dc 517err_input:
f2c32a88
MB
518err_register:
519 pm_runtime_disable(&pdev->dev);
520 extcon_dev_unregister(&info->edev);
521err:
522 return ret;
523}
524
93ed0327 525static int arizona_extcon_remove(struct platform_device *pdev)
f2c32a88
MB
526{
527 struct arizona_extcon_info *info = platform_get_drvdata(pdev);
528 struct arizona *arizona = info->arizona;
529
530 pm_runtime_disable(&pdev->dev);
531
532 arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0);
533 arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0);
534 arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info);
535 arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info);
536 arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info);
537 regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
538 ARIZONA_JD1_ENA, 0);
539 arizona_clk32k_disable(arizona);
540 extcon_dev_unregister(&info->edev);
541
542 return 0;
543}
544
545static struct platform_driver arizona_extcon_driver = {
546 .driver = {
547 .name = "arizona-extcon",
548 .owner = THIS_MODULE,
549 },
550 .probe = arizona_extcon_probe,
5f7e2228 551 .remove = arizona_extcon_remove,
f2c32a88
MB
552};
553
554module_platform_driver(arizona_extcon_driver);
555
556MODULE_DESCRIPTION("Arizona Extcon driver");
557MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
558MODULE_LICENSE("GPL");
559MODULE_ALIAS("platform:extcon-arizona");