Commit | Line | Data |
---|---|---|
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 | ||
4f340333 MB |
34 | #define ARIZONA_DEFAULT_HP 32 |
35 | ||
34efe4dc MB |
36 | #define ARIZONA_NUM_BUTTONS 6 |
37 | ||
4f340333 MB |
38 | #define ARIZONA_ACCDET_MODE_MIC 0 |
39 | #define ARIZONA_ACCDET_MODE_HPL 1 | |
40 | #define ARIZONA_ACCDET_MODE_HPR 2 | |
41 | ||
f2c32a88 MB |
42 | struct arizona_extcon_info { |
43 | struct device *dev; | |
44 | struct arizona *arizona; | |
45 | struct mutex lock; | |
46 | struct regulator *micvdd; | |
34efe4dc | 47 | struct input_dev *input; |
f2c32a88 MB |
48 | |
49 | int micd_mode; | |
50 | const struct arizona_micd_config *micd_modes; | |
51 | int micd_num_modes; | |
52 | ||
53 | bool micd_reva; | |
dab63eb2 | 54 | bool micd_clamp; |
f2c32a88 | 55 | |
4f340333 MB |
56 | bool hpdet_active; |
57 | ||
f2c32a88 MB |
58 | bool mic; |
59 | bool detecting; | |
60 | int jack_flips; | |
61 | ||
4f340333 MB |
62 | int hpdet_ip; |
63 | ||
f2c32a88 MB |
64 | struct extcon_dev edev; |
65 | }; | |
66 | ||
67 | static const struct arizona_micd_config micd_default_modes[] = { | |
68 | { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 }, | |
69 | { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, | |
70 | }; | |
71 | ||
34efe4dc MB |
72 | static struct { |
73 | u16 status; | |
74 | int report; | |
75 | } arizona_lvl_to_key[ARIZONA_NUM_BUTTONS] = { | |
76 | { 0x1, BTN_0 }, | |
77 | { 0x2, BTN_1 }, | |
78 | { 0x4, BTN_2 }, | |
79 | { 0x8, BTN_3 }, | |
80 | { 0x10, BTN_4 }, | |
81 | { 0x20, BTN_5 }, | |
82 | }; | |
83 | ||
325c6423 MB |
84 | #define ARIZONA_CABLE_MECHANICAL 0 |
85 | #define ARIZONA_CABLE_MICROPHONE 1 | |
86 | #define ARIZONA_CABLE_HEADPHONE 2 | |
4f340333 | 87 | #define ARIZONA_CABLE_LINEOUT 3 |
f2c32a88 MB |
88 | |
89 | static const char *arizona_cable[] = { | |
325c6423 MB |
90 | "Mechanical", |
91 | "Microphone", | |
92 | "Headphone", | |
4f340333 | 93 | "Line-out", |
f2c32a88 MB |
94 | NULL, |
95 | }; | |
96 | ||
f2c32a88 MB |
97 | static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) |
98 | { | |
99 | struct arizona *arizona = info->arizona; | |
100 | ||
cd74f7b3 MB |
101 | if (arizona->pdata.micd_pol_gpio > 0) |
102 | gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio, | |
103 | info->micd_modes[mode].gpio); | |
f2c32a88 MB |
104 | regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, |
105 | ARIZONA_MICD_BIAS_SRC_MASK, | |
106 | info->micd_modes[mode].bias); | |
107 | regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, | |
108 | ARIZONA_ACCDET_SRC, info->micd_modes[mode].src); | |
109 | ||
110 | info->micd_mode = mode; | |
111 | ||
112 | dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode); | |
113 | } | |
114 | ||
4f340333 MB |
115 | static struct { |
116 | unsigned int factor_a; | |
117 | unsigned int factor_b; | |
118 | } arizona_hpdet_b_ranges[] = { | |
119 | { 5528, 362464 }, | |
120 | { 11084, 6186851 }, | |
121 | { 11065, 65460395 }, | |
122 | }; | |
123 | ||
124 | static struct { | |
125 | int min; | |
126 | int max; | |
127 | } arizona_hpdet_c_ranges[] = { | |
128 | { 0, 30 }, | |
129 | { 8, 100 }, | |
130 | { 100, 1000 }, | |
131 | { 1000, 10000 }, | |
132 | }; | |
133 | ||
134 | static int arizona_hpdet_read(struct arizona_extcon_info *info) | |
135 | { | |
136 | struct arizona *arizona = info->arizona; | |
137 | unsigned int val, range; | |
138 | int ret; | |
139 | ||
140 | ret = regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_2, &val); | |
141 | if (ret != 0) { | |
142 | dev_err(arizona->dev, "Failed to read HPDET status: %d\n", | |
143 | ret); | |
144 | return ret; | |
145 | } | |
146 | ||
147 | switch (info->hpdet_ip) { | |
148 | case 0: | |
149 | if (!(val & ARIZONA_HP_DONE)) { | |
150 | dev_err(arizona->dev, "HPDET did not complete: %x\n", | |
151 | val); | |
152 | val = ARIZONA_DEFAULT_HP; | |
153 | } | |
154 | ||
155 | val &= ARIZONA_HP_LVL_MASK; | |
156 | break; | |
157 | ||
158 | case 1: | |
159 | if (!(val & ARIZONA_HP_DONE_B)) { | |
160 | dev_err(arizona->dev, "HPDET did not complete: %x\n", | |
161 | val); | |
162 | return ARIZONA_DEFAULT_HP; | |
163 | } | |
164 | ||
165 | ret = regmap_read(arizona->regmap, ARIZONA_HP_DACVAL, &val); | |
166 | if (ret != 0) { | |
167 | dev_err(arizona->dev, "Failed to read HP value: %d\n", | |
168 | ret); | |
169 | return ARIZONA_DEFAULT_HP; | |
170 | } | |
171 | ||
172 | regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, | |
173 | &range); | |
174 | range = (range & ARIZONA_HP_IMPEDANCE_RANGE_MASK) | |
175 | >> ARIZONA_HP_IMPEDANCE_RANGE_SHIFT; | |
176 | ||
177 | if (range < ARRAY_SIZE(arizona_hpdet_b_ranges) - 1 && | |
178 | (val < 100 || val > 0x3fb)) { | |
179 | range++; | |
180 | dev_dbg(arizona->dev, "Moving to HPDET range %d\n", | |
181 | range); | |
182 | regmap_update_bits(arizona->regmap, | |
183 | ARIZONA_HEADPHONE_DETECT_1, | |
184 | ARIZONA_HP_IMPEDANCE_RANGE_MASK, | |
185 | range << | |
186 | ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); | |
187 | return -EAGAIN; | |
188 | } | |
189 | ||
190 | /* If we go out of range report top of range */ | |
191 | if (val < 100 || val > 0x3fb) { | |
192 | dev_dbg(arizona->dev, "Measurement out of range\n"); | |
193 | return 10000; | |
194 | } | |
195 | ||
196 | dev_dbg(arizona->dev, "HPDET read %d in range %d\n", | |
197 | val, range); | |
198 | ||
199 | val = arizona_hpdet_b_ranges[range].factor_b | |
200 | / ((val * 100) - | |
201 | arizona_hpdet_b_ranges[range].factor_a); | |
202 | break; | |
203 | ||
204 | default: | |
205 | dev_warn(arizona->dev, "Unknown HPDET IP revision %d\n", | |
206 | info->hpdet_ip); | |
207 | case 2: | |
208 | if (!(val & ARIZONA_HP_DONE_B)) { | |
209 | dev_err(arizona->dev, "HPDET did not complete: %x\n", | |
210 | val); | |
211 | return ARIZONA_DEFAULT_HP; | |
212 | } | |
213 | ||
214 | val &= ARIZONA_HP_LVL_B_MASK; | |
215 | ||
216 | regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, | |
217 | &range); | |
218 | range = (range & ARIZONA_HP_IMPEDANCE_RANGE_MASK) | |
219 | >> ARIZONA_HP_IMPEDANCE_RANGE_SHIFT; | |
220 | ||
221 | /* Skip up or down a range? */ | |
222 | if (range && (val < arizona_hpdet_c_ranges[range].min)) { | |
223 | range--; | |
224 | dev_dbg(arizona->dev, "Moving to HPDET range %d-%d\n", | |
225 | arizona_hpdet_c_ranges[range].min, | |
226 | arizona_hpdet_c_ranges[range].max); | |
227 | regmap_update_bits(arizona->regmap, | |
228 | ARIZONA_HEADPHONE_DETECT_1, | |
229 | ARIZONA_HP_IMPEDANCE_RANGE_MASK, | |
230 | range << | |
231 | ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); | |
232 | return -EAGAIN; | |
233 | } | |
234 | ||
235 | if (range < ARRAY_SIZE(arizona_hpdet_c_ranges) - 1 && | |
236 | (val >= arizona_hpdet_c_ranges[range].max)) { | |
237 | range++; | |
238 | dev_dbg(arizona->dev, "Moving to HPDET range %d-%d\n", | |
239 | arizona_hpdet_c_ranges[range].min, | |
240 | arizona_hpdet_c_ranges[range].max); | |
241 | regmap_update_bits(arizona->regmap, | |
242 | ARIZONA_HEADPHONE_DETECT_1, | |
243 | ARIZONA_HP_IMPEDANCE_RANGE_MASK, | |
244 | range << | |
245 | ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); | |
246 | return -EAGAIN; | |
247 | } | |
248 | } | |
249 | ||
250 | dev_dbg(arizona->dev, "HP impedance %d ohms\n", val); | |
251 | return val; | |
252 | } | |
253 | ||
254 | static irqreturn_t arizona_hpdet_irq(int irq, void *data) | |
255 | { | |
256 | struct arizona_extcon_info *info = data; | |
257 | struct arizona *arizona = info->arizona; | |
258 | int report = ARIZONA_CABLE_HEADPHONE; | |
259 | int ret; | |
260 | ||
261 | mutex_lock(&info->lock); | |
262 | ||
263 | /* If we got a spurious IRQ for some reason then ignore it */ | |
264 | if (!info->hpdet_active) { | |
265 | dev_warn(arizona->dev, "Spurious HPDET IRQ\n"); | |
266 | mutex_unlock(&info->lock); | |
267 | return IRQ_NONE; | |
268 | } | |
269 | ||
270 | /* If the cable was removed while measuring ignore the result */ | |
271 | ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); | |
272 | if (ret < 0) { | |
273 | dev_err(arizona->dev, "Failed to check cable state: %d\n", | |
274 | ret); | |
275 | goto out; | |
276 | } else if (!ret) { | |
277 | dev_dbg(arizona->dev, "Ignoring HPDET for removed cable\n"); | |
278 | goto done; | |
279 | } | |
280 | ||
281 | ret = arizona_hpdet_read(info); | |
282 | if (ret == -EAGAIN) { | |
283 | goto out; | |
284 | } else if (ret < 0) { | |
285 | goto done; | |
286 | } | |
287 | ||
288 | /* Reset back to starting range */ | |
289 | regmap_update_bits(arizona->regmap, | |
290 | ARIZONA_HEADPHONE_DETECT_1, | |
291 | ARIZONA_HP_IMPEDANCE_RANGE_MASK, 0); | |
292 | ||
293 | /* Report high impedence cables as line outputs */ | |
294 | if (ret >= 5000) | |
295 | report = ARIZONA_CABLE_LINEOUT; | |
296 | else | |
297 | report = ARIZONA_CABLE_HEADPHONE; | |
298 | ||
299 | ret = extcon_set_cable_state_(&info->edev, report, true); | |
300 | if (ret != 0) | |
301 | dev_err(arizona->dev, "Failed to report HP/line: %d\n", | |
302 | ret); | |
303 | ||
304 | ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0); | |
305 | if (ret != 0) | |
306 | dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); | |
307 | ||
308 | ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0); | |
309 | if (ret != 0) | |
310 | dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); | |
311 | ||
312 | done: | |
313 | regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, | |
314 | ARIZONA_HP_POLL, 0); | |
315 | ||
316 | /* Revert back to MICDET mode */ | |
317 | regmap_update_bits(arizona->regmap, | |
318 | ARIZONA_ACCESSORY_DETECT_MODE_1, | |
319 | ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); | |
320 | ||
321 | /* If we have a mic then reenable MICDET */ | |
322 | if (info->mic) | |
323 | arizona_start_mic(info); | |
324 | ||
325 | if (info->hpdet_active) { | |
326 | pm_runtime_put_autosuspend(info->dev); | |
327 | info->hpdet_active = false; | |
328 | } | |
329 | ||
330 | out: | |
331 | mutex_unlock(&info->lock); | |
332 | ||
333 | return IRQ_HANDLED; | |
334 | } | |
335 | ||
336 | static void arizona_identify_headphone(struct arizona_extcon_info *info) | |
337 | { | |
338 | struct arizona *arizona = info->arizona; | |
339 | int ret; | |
340 | ||
341 | dev_dbg(arizona->dev, "Starting HPDET\n"); | |
342 | ||
343 | /* Make sure we keep the device enabled during the measurement */ | |
344 | pm_runtime_get(info->dev); | |
345 | ||
346 | info->hpdet_active = true; | |
347 | ||
348 | if (info->mic) | |
349 | arizona_stop_mic(info); | |
350 | ||
351 | ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000); | |
352 | if (ret != 0) | |
353 | dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); | |
354 | ||
355 | ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0x4000); | |
356 | if (ret != 0) | |
357 | dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); | |
358 | ||
359 | ret = regmap_update_bits(arizona->regmap, | |
360 | ARIZONA_ACCESSORY_DETECT_MODE_1, | |
361 | ARIZONA_ACCDET_MODE_MASK, | |
362 | ARIZONA_ACCDET_MODE_HPL); | |
363 | if (ret != 0) { | |
364 | dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret); | |
365 | goto err; | |
366 | } | |
367 | ||
368 | ret = regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, | |
369 | ARIZONA_HP_POLL, ARIZONA_HP_POLL); | |
370 | if (ret != 0) { | |
371 | dev_err(arizona->dev, "Can't start HPDETL measurement: %d\n", | |
372 | ret); | |
373 | goto err; | |
374 | } | |
375 | ||
376 | return; | |
377 | ||
378 | err: | |
379 | regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, | |
380 | ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); | |
381 | ||
382 | /* Just report headphone */ | |
383 | ret = extcon_update_state(&info->edev, | |
384 | 1 << ARIZONA_CABLE_HEADPHONE, | |
385 | 1 << ARIZONA_CABLE_HEADPHONE); | |
386 | if (ret != 0) | |
387 | dev_err(arizona->dev, "Failed to report headphone: %d\n", ret); | |
388 | ||
389 | if (info->mic) | |
390 | arizona_start_mic(info); | |
391 | ||
392 | info->hpdet_active = false; | |
393 | } | |
394 | } | |
395 | ||
396 | info->hpdet_active = false; | |
397 | } | |
398 | ||
f2c32a88 MB |
399 | static void arizona_start_mic(struct arizona_extcon_info *info) |
400 | { | |
401 | struct arizona *arizona = info->arizona; | |
402 | bool change; | |
403 | int ret; | |
404 | ||
405 | info->detecting = true; | |
406 | info->mic = false; | |
407 | info->jack_flips = 0; | |
408 | ||
409 | /* Microphone detection can't use idle mode */ | |
410 | pm_runtime_get(info->dev); | |
411 | ||
412 | ret = regulator_enable(info->micvdd); | |
413 | if (ret != 0) { | |
414 | dev_err(arizona->dev, "Failed to enable MICVDD: %d\n", | |
415 | ret); | |
416 | } | |
417 | ||
418 | if (info->micd_reva) { | |
419 | regmap_write(arizona->regmap, 0x80, 0x3); | |
420 | regmap_write(arizona->regmap, 0x294, 0); | |
421 | regmap_write(arizona->regmap, 0x80, 0x0); | |
422 | } | |
423 | ||
4f340333 MB |
424 | regmap_update_bits(arizona->regmap, |
425 | ARIZONA_ACCESSORY_DETECT_MODE_1, | |
426 | ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); | |
427 | ||
f2c32a88 MB |
428 | regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, |
429 | ARIZONA_MICD_ENA, ARIZONA_MICD_ENA, | |
430 | &change); | |
431 | if (!change) { | |
432 | regulator_disable(info->micvdd); | |
433 | pm_runtime_put_autosuspend(info->dev); | |
434 | } | |
435 | } | |
436 | ||
437 | static void arizona_stop_mic(struct arizona_extcon_info *info) | |
438 | { | |
439 | struct arizona *arizona = info->arizona; | |
440 | bool change; | |
441 | ||
442 | regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, | |
443 | ARIZONA_MICD_ENA, 0, | |
444 | &change); | |
445 | ||
446 | if (info->micd_reva) { | |
447 | regmap_write(arizona->regmap, 0x80, 0x3); | |
448 | regmap_write(arizona->regmap, 0x294, 2); | |
449 | regmap_write(arizona->regmap, 0x80, 0x0); | |
450 | } | |
451 | ||
452 | if (change) { | |
453 | regulator_disable(info->micvdd); | |
34efe4dc | 454 | pm_runtime_mark_last_busy(info->dev); |
f2c32a88 MB |
455 | pm_runtime_put_autosuspend(info->dev); |
456 | } | |
457 | } | |
458 | ||
459 | static irqreturn_t arizona_micdet(int irq, void *data) | |
460 | { | |
461 | struct arizona_extcon_info *info = data; | |
462 | struct arizona *arizona = info->arizona; | |
34efe4dc MB |
463 | unsigned int val, lvl; |
464 | int ret, i; | |
f2c32a88 MB |
465 | |
466 | mutex_lock(&info->lock); | |
467 | ||
468 | ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val); | |
469 | if (ret != 0) { | |
470 | dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret); | |
be31cc0b | 471 | mutex_unlock(&info->lock); |
f2c32a88 MB |
472 | return IRQ_NONE; |
473 | } | |
474 | ||
475 | dev_dbg(arizona->dev, "MICDET: %x\n", val); | |
476 | ||
477 | if (!(val & ARIZONA_MICD_VALID)) { | |
478 | dev_warn(arizona->dev, "Microphone detection state invalid\n"); | |
479 | mutex_unlock(&info->lock); | |
480 | return IRQ_NONE; | |
481 | } | |
482 | ||
483 | /* Due to jack detect this should never happen */ | |
484 | if (!(val & ARIZONA_MICD_STS)) { | |
485 | dev_warn(arizona->dev, "Detected open circuit\n"); | |
486 | info->detecting = false; | |
487 | goto handled; | |
488 | } | |
489 | ||
490 | /* If we got a high impedence we should have a headset, report it. */ | |
491 | if (info->detecting && (val & 0x400)) { | |
4f340333 MB |
492 | arizona_identify_headphone(info); |
493 | ||
325c6423 | 494 | ret = extcon_update_state(&info->edev, |
4f340333 MB |
495 | 1 << ARIZONA_CABLE_MICROPHONE, |
496 | 1 << ARIZONA_CABLE_MICROPHONE); | |
f2c32a88 MB |
497 | |
498 | if (ret != 0) | |
499 | dev_err(arizona->dev, "Headset report failed: %d\n", | |
500 | ret); | |
501 | ||
502 | info->mic = true; | |
503 | info->detecting = false; | |
504 | goto handled; | |
505 | } | |
506 | ||
507 | /* If we detected a lower impedence during initial startup | |
508 | * then we probably have the wrong polarity, flip it. Don't | |
509 | * do this for the lowest impedences to speed up detection of | |
510 | * plain headphones. If both polarities report a low | |
511 | * impedence then give up and report headphones. | |
512 | */ | |
513 | if (info->detecting && (val & 0x3f8)) { | |
514 | info->jack_flips++; | |
515 | ||
516 | if (info->jack_flips >= info->micd_num_modes) { | |
4f340333 MB |
517 | dev_dbg(arizona->dev, "Detected HP/line\n"); |
518 | arizona_identify_headphone(info); | |
519 | ||
f2c32a88 | 520 | info->detecting = false; |
9ef2224d | 521 | |
4f340333 | 522 | arizona_stop_mic(info); |
f2c32a88 MB |
523 | } else { |
524 | info->micd_mode++; | |
525 | if (info->micd_mode == info->micd_num_modes) | |
526 | info->micd_mode = 0; | |
527 | arizona_extcon_set_mode(info, info->micd_mode); | |
528 | ||
529 | info->jack_flips++; | |
530 | } | |
531 | ||
532 | goto handled; | |
533 | } | |
534 | ||
535 | /* | |
536 | * If we're still detecting and we detect a short then we've | |
34efe4dc | 537 | * got a headphone. Otherwise it's a button press. |
f2c32a88 MB |
538 | */ |
539 | if (val & 0x3fc) { | |
540 | if (info->mic) { | |
541 | dev_dbg(arizona->dev, "Mic button detected\n"); | |
542 | ||
34efe4dc MB |
543 | lvl = val & ARIZONA_MICD_LVL_MASK; |
544 | lvl >>= ARIZONA_MICD_LVL_SHIFT; | |
545 | ||
546 | for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) | |
547 | if (lvl & arizona_lvl_to_key[i].status) | |
548 | input_report_key(info->input, | |
549 | arizona_lvl_to_key[i].report, | |
550 | 1); | |
551 | input_sync(info->input); | |
552 | ||
f2c32a88 MB |
553 | } else if (info->detecting) { |
554 | dev_dbg(arizona->dev, "Headphone detected\n"); | |
555 | info->detecting = false; | |
556 | arizona_stop_mic(info); | |
557 | ||
4f340333 | 558 | arizona_identify_headphone(info); |
f2c32a88 MB |
559 | } else { |
560 | dev_warn(arizona->dev, "Button with no mic: %x\n", | |
561 | val); | |
562 | } | |
563 | } else { | |
564 | dev_dbg(arizona->dev, "Mic button released\n"); | |
34efe4dc MB |
565 | for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) |
566 | input_report_key(info->input, | |
567 | arizona_lvl_to_key[i].report, 0); | |
568 | input_sync(info->input); | |
f2c32a88 MB |
569 | } |
570 | ||
571 | handled: | |
572 | pm_runtime_mark_last_busy(info->dev); | |
573 | mutex_unlock(&info->lock); | |
574 | ||
575 | return IRQ_HANDLED; | |
576 | } | |
577 | ||
578 | static irqreturn_t arizona_jackdet(int irq, void *data) | |
579 | { | |
580 | struct arizona_extcon_info *info = data; | |
581 | struct arizona *arizona = info->arizona; | |
92a49871 | 582 | unsigned int val, present, mask; |
34efe4dc | 583 | int ret, i; |
f2c32a88 MB |
584 | |
585 | pm_runtime_get_sync(info->dev); | |
586 | ||
587 | mutex_lock(&info->lock); | |
588 | ||
92a49871 MB |
589 | if (arizona->pdata.jd_gpio5) { |
590 | mask = ARIZONA_MICD_CLAMP_STS; | |
591 | present = 0; | |
592 | } else { | |
593 | mask = ARIZONA_JD1_STS; | |
594 | present = ARIZONA_JD1_STS; | |
595 | } | |
596 | ||
f2c32a88 MB |
597 | ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); |
598 | if (ret != 0) { | |
599 | dev_err(arizona->dev, "Failed to read jackdet status: %d\n", | |
600 | ret); | |
601 | mutex_unlock(&info->lock); | |
602 | pm_runtime_put_autosuspend(info->dev); | |
603 | return IRQ_NONE; | |
604 | } | |
605 | ||
92a49871 | 606 | if ((val & mask) == present) { |
f2c32a88 | 607 | dev_dbg(arizona->dev, "Detected jack\n"); |
325c6423 MB |
608 | ret = extcon_set_cable_state_(&info->edev, |
609 | ARIZONA_CABLE_MECHANICAL, true); | |
f2c32a88 MB |
610 | |
611 | if (ret != 0) | |
612 | dev_err(arizona->dev, "Mechanical report failed: %d\n", | |
613 | ret); | |
614 | ||
615 | arizona_start_mic(info); | |
616 | } else { | |
617 | dev_dbg(arizona->dev, "Detected jack removal\n"); | |
618 | ||
619 | arizona_stop_mic(info); | |
620 | ||
92a49871 | 621 | |
34efe4dc MB |
622 | for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) |
623 | input_report_key(info->input, | |
624 | arizona_lvl_to_key[i].report, 0); | |
625 | input_sync(info->input); | |
626 | ||
f2c32a88 MB |
627 | ret = extcon_update_state(&info->edev, 0xffffffff, 0); |
628 | if (ret != 0) | |
629 | dev_err(arizona->dev, "Removal report failed: %d\n", | |
630 | ret); | |
631 | } | |
632 | ||
633 | mutex_unlock(&info->lock); | |
634 | ||
635 | pm_runtime_mark_last_busy(info->dev); | |
636 | pm_runtime_put_autosuspend(info->dev); | |
637 | ||
638 | return IRQ_HANDLED; | |
639 | } | |
640 | ||
44f34fd4 | 641 | static int arizona_extcon_probe(struct platform_device *pdev) |
f2c32a88 MB |
642 | { |
643 | struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); | |
644 | struct arizona_pdata *pdata; | |
645 | struct arizona_extcon_info *info; | |
92a49871 | 646 | int jack_irq_fall, jack_irq_rise; |
34efe4dc | 647 | int ret, mode, i; |
f2c32a88 MB |
648 | |
649 | pdata = dev_get_platdata(arizona->dev); | |
650 | ||
651 | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); | |
652 | if (!info) { | |
8e5f5018 | 653 | dev_err(&pdev->dev, "Failed to allocate memory\n"); |
f2c32a88 MB |
654 | ret = -ENOMEM; |
655 | goto err; | |
656 | } | |
657 | ||
658 | info->micvdd = devm_regulator_get(arizona->dev, "MICVDD"); | |
659 | if (IS_ERR(info->micvdd)) { | |
660 | ret = PTR_ERR(info->micvdd); | |
661 | dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret); | |
662 | goto err; | |
663 | } | |
664 | ||
665 | mutex_init(&info->lock); | |
666 | info->arizona = arizona; | |
667 | info->dev = &pdev->dev; | |
668 | info->detecting = true; | |
669 | platform_set_drvdata(pdev, info); | |
670 | ||
671 | switch (arizona->type) { | |
672 | case WM5102: | |
673 | switch (arizona->rev) { | |
674 | case 0: | |
675 | info->micd_reva = true; | |
676 | break; | |
677 | default: | |
dab63eb2 | 678 | info->micd_clamp = true; |
4f340333 | 679 | info->hpdet_ip = 1; |
f2c32a88 MB |
680 | break; |
681 | } | |
682 | break; | |
683 | default: | |
684 | break; | |
685 | } | |
686 | ||
687 | info->edev.name = "Headset Jack"; | |
688 | info->edev.supported_cable = arizona_cable; | |
f2c32a88 MB |
689 | |
690 | ret = extcon_dev_register(&info->edev, arizona->dev); | |
691 | if (ret < 0) { | |
8e5f5018 | 692 | dev_err(arizona->dev, "extcon_dev_register() failed: %d\n", |
f2c32a88 MB |
693 | ret); |
694 | goto err; | |
695 | } | |
696 | ||
697 | if (pdata->num_micd_configs) { | |
698 | info->micd_modes = pdata->micd_configs; | |
699 | info->micd_num_modes = pdata->num_micd_configs; | |
700 | } else { | |
701 | info->micd_modes = micd_default_modes; | |
702 | info->micd_num_modes = ARRAY_SIZE(micd_default_modes); | |
703 | } | |
704 | ||
705 | if (arizona->pdata.micd_pol_gpio > 0) { | |
706 | if (info->micd_modes[0].gpio) | |
707 | mode = GPIOF_OUT_INIT_HIGH; | |
708 | else | |
709 | mode = GPIOF_OUT_INIT_LOW; | |
710 | ||
711 | ret = devm_gpio_request_one(&pdev->dev, | |
712 | arizona->pdata.micd_pol_gpio, | |
713 | mode, | |
714 | "MICD polarity"); | |
715 | if (ret != 0) { | |
716 | dev_err(arizona->dev, "Failed to request GPIO%d: %d\n", | |
717 | arizona->pdata.micd_pol_gpio, ret); | |
718 | goto err_register; | |
719 | } | |
720 | } | |
721 | ||
b17e5462 MB |
722 | if (arizona->pdata.micd_bias_start_time) |
723 | regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, | |
724 | ARIZONA_MICD_BIAS_STARTTIME_MASK, | |
725 | arizona->pdata.micd_bias_start_time | |
726 | << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); | |
727 | ||
dab63eb2 | 728 | /* |
92a49871 MB |
729 | * If we have a clamp use it, activating in conjunction with |
730 | * GPIO5 if that is connected for jack detect operation. | |
dab63eb2 MB |
731 | */ |
732 | if (info->micd_clamp) { | |
92a49871 MB |
733 | if (arizona->pdata.jd_gpio5) { |
734 | /* Put the GPIO into input mode */ | |
735 | regmap_write(arizona->regmap, ARIZONA_GPIO5_CTRL, | |
736 | 0xc101); | |
737 | ||
738 | regmap_update_bits(arizona->regmap, | |
739 | ARIZONA_MICD_CLAMP_CONTROL, | |
740 | ARIZONA_MICD_CLAMP_MODE_MASK, 0x9); | |
741 | } else { | |
742 | regmap_update_bits(arizona->regmap, | |
743 | ARIZONA_MICD_CLAMP_CONTROL, | |
744 | ARIZONA_MICD_CLAMP_MODE_MASK, 0x4); | |
745 | } | |
746 | ||
dab63eb2 MB |
747 | regmap_update_bits(arizona->regmap, |
748 | ARIZONA_JACK_DETECT_DEBOUNCE, | |
749 | ARIZONA_MICD_CLAMP_DB, | |
750 | ARIZONA_MICD_CLAMP_DB); | |
751 | } | |
752 | ||
f2c32a88 MB |
753 | arizona_extcon_set_mode(info, 0); |
754 | ||
3d44ea1c | 755 | info->input = devm_input_allocate_device(&pdev->dev); |
34efe4dc MB |
756 | if (!info->input) { |
757 | dev_err(arizona->dev, "Can't allocate input dev\n"); | |
758 | ret = -ENOMEM; | |
759 | goto err_register; | |
760 | } | |
761 | ||
762 | for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) | |
763 | input_set_capability(info->input, EV_KEY, | |
764 | arizona_lvl_to_key[i].report); | |
765 | info->input->name = "Headset"; | |
766 | info->input->phys = "arizona/extcon"; | |
767 | info->input->dev.parent = &pdev->dev; | |
768 | ||
f2c32a88 MB |
769 | pm_runtime_enable(&pdev->dev); |
770 | pm_runtime_idle(&pdev->dev); | |
771 | pm_runtime_get_sync(&pdev->dev); | |
772 | ||
92a49871 MB |
773 | if (arizona->pdata.jd_gpio5) { |
774 | jack_irq_rise = ARIZONA_IRQ_MICD_CLAMP_RISE; | |
775 | jack_irq_fall = ARIZONA_IRQ_MICD_CLAMP_FALL; | |
776 | } else { | |
777 | jack_irq_rise = ARIZONA_IRQ_JD_RISE; | |
778 | jack_irq_fall = ARIZONA_IRQ_JD_FALL; | |
779 | } | |
780 | ||
781 | ret = arizona_request_irq(arizona, jack_irq_rise, | |
f2c32a88 MB |
782 | "JACKDET rise", arizona_jackdet, info); |
783 | if (ret != 0) { | |
784 | dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n", | |
785 | ret); | |
34efe4dc | 786 | goto err_input; |
f2c32a88 MB |
787 | } |
788 | ||
92a49871 | 789 | ret = arizona_set_irq_wake(arizona, jack_irq_rise, 1); |
f2c32a88 MB |
790 | if (ret != 0) { |
791 | dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n", | |
792 | ret); | |
793 | goto err_rise; | |
794 | } | |
795 | ||
92a49871 | 796 | ret = arizona_request_irq(arizona, jack_irq_fall, |
f2c32a88 MB |
797 | "JACKDET fall", arizona_jackdet, info); |
798 | if (ret != 0) { | |
799 | dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret); | |
800 | goto err_rise_wake; | |
801 | } | |
802 | ||
92a49871 | 803 | ret = arizona_set_irq_wake(arizona, jack_irq_fall, 1); |
f2c32a88 MB |
804 | if (ret != 0) { |
805 | dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n", | |
806 | ret); | |
807 | goto err_fall; | |
808 | } | |
809 | ||
810 | ret = arizona_request_irq(arizona, ARIZONA_IRQ_MICDET, | |
811 | "MICDET", arizona_micdet, info); | |
812 | if (ret != 0) { | |
813 | dev_err(&pdev->dev, "Failed to get MICDET IRQ: %d\n", ret); | |
814 | goto err_fall_wake; | |
815 | } | |
816 | ||
4f340333 MB |
817 | ret = arizona_request_irq(arizona, ARIZONA_IRQ_HPDET, |
818 | "HPDET", arizona_hpdet_irq, info); | |
819 | if (ret != 0) { | |
820 | dev_err(&pdev->dev, "Failed to get HPDET IRQ: %d\n", ret); | |
821 | goto err_micdet; | |
822 | } | |
823 | ||
f2c32a88 | 824 | regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, |
f2c32a88 | 825 | ARIZONA_MICD_RATE_MASK, |
f2c32a88 MB |
826 | 8 << ARIZONA_MICD_RATE_SHIFT); |
827 | ||
828 | arizona_clk32k_enable(arizona); | |
829 | regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE, | |
830 | ARIZONA_JD1_DB, ARIZONA_JD1_DB); | |
831 | regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, | |
832 | ARIZONA_JD1_ENA, ARIZONA_JD1_ENA); | |
833 | ||
b8575a11 MB |
834 | ret = regulator_allow_bypass(info->micvdd, true); |
835 | if (ret != 0) | |
836 | dev_warn(arizona->dev, "Failed to set MICVDD to bypass: %d\n", | |
837 | ret); | |
838 | ||
f2c32a88 MB |
839 | pm_runtime_put(&pdev->dev); |
840 | ||
34efe4dc MB |
841 | ret = input_register_device(info->input); |
842 | if (ret) { | |
843 | dev_err(&pdev->dev, "Can't register input device: %d\n", ret); | |
4f340333 | 844 | goto err_hpdet; |
34efe4dc MB |
845 | } |
846 | ||
f2c32a88 MB |
847 | return 0; |
848 | ||
4f340333 MB |
849 | err_hpdet: |
850 | arizona_free_irq(arizona, ARIZONA_IRQ_HPDET, info); | |
80732cc1 MB |
851 | err_micdet: |
852 | arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); | |
f2c32a88 | 853 | err_fall_wake: |
92a49871 | 854 | arizona_set_irq_wake(arizona, jack_irq_fall, 0); |
f2c32a88 | 855 | err_fall: |
92a49871 | 856 | arizona_free_irq(arizona, jack_irq_fall, info); |
f2c32a88 | 857 | err_rise_wake: |
92a49871 | 858 | arizona_set_irq_wake(arizona, jack_irq_rise, 0); |
f2c32a88 | 859 | err_rise: |
92a49871 | 860 | arizona_free_irq(arizona, jack_irq_rise, info); |
34efe4dc | 861 | err_input: |
f2c32a88 MB |
862 | err_register: |
863 | pm_runtime_disable(&pdev->dev); | |
864 | extcon_dev_unregister(&info->edev); | |
865 | err: | |
866 | return ret; | |
867 | } | |
868 | ||
93ed0327 | 869 | static int arizona_extcon_remove(struct platform_device *pdev) |
f2c32a88 MB |
870 | { |
871 | struct arizona_extcon_info *info = platform_get_drvdata(pdev); | |
872 | struct arizona *arizona = info->arizona; | |
92a49871 | 873 | int jack_irq_rise, jack_irq_fall; |
f2c32a88 MB |
874 | |
875 | pm_runtime_disable(&pdev->dev); | |
876 | ||
dab63eb2 MB |
877 | regmap_update_bits(arizona->regmap, |
878 | ARIZONA_MICD_CLAMP_CONTROL, | |
879 | ARIZONA_MICD_CLAMP_MODE_MASK, 0); | |
880 | ||
92a49871 MB |
881 | if (arizona->pdata.jd_gpio5) { |
882 | jack_irq_rise = ARIZONA_IRQ_MICD_CLAMP_RISE; | |
883 | jack_irq_fall = ARIZONA_IRQ_MICD_CLAMP_FALL; | |
884 | } else { | |
885 | jack_irq_rise = ARIZONA_IRQ_JD_RISE; | |
886 | jack_irq_fall = ARIZONA_IRQ_JD_FALL; | |
887 | } | |
888 | ||
889 | arizona_set_irq_wake(arizona, jack_irq_rise, 0); | |
890 | arizona_set_irq_wake(arizona, jack_irq_fall, 0); | |
891 | arizona_free_irq(arizona, ARIZONA_IRQ_HPDET, info); | |
f2c32a88 | 892 | arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); |
92a49871 MB |
893 | arizona_free_irq(arizona, jack_irq_rise, info); |
894 | arizona_free_irq(arizona, jack_irq_fall, info); | |
f2c32a88 MB |
895 | regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, |
896 | ARIZONA_JD1_ENA, 0); | |
897 | arizona_clk32k_disable(arizona); | |
898 | extcon_dev_unregister(&info->edev); | |
899 | ||
900 | return 0; | |
901 | } | |
902 | ||
903 | static struct platform_driver arizona_extcon_driver = { | |
904 | .driver = { | |
905 | .name = "arizona-extcon", | |
906 | .owner = THIS_MODULE, | |
907 | }, | |
908 | .probe = arizona_extcon_probe, | |
5f7e2228 | 909 | .remove = arizona_extcon_remove, |
f2c32a88 MB |
910 | }; |
911 | ||
912 | module_platform_driver(arizona_extcon_driver); | |
913 | ||
914 | MODULE_DESCRIPTION("Arizona Extcon driver"); | |
915 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | |
916 | MODULE_LICENSE("GPL"); | |
917 | MODULE_ALIAS("platform:extcon-arizona"); |