aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/extcon
diff options
context:
space:
mode:
authorMark Brown <broonie@opensource.wolfsonmicro.com>2012-06-24 07:09:45 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-06-26 19:01:02 -0400
commitf2c32a882d2c1cde6fc552a5a3d34b4c1330edb8 (patch)
tree811b6a02363ccad00713a88cb4301d639ec406c6 /drivers/extcon
parentdf6b3cfe20f3e22a7997255f1df753005deb914f (diff)
Extcon: Arizona: Add driver for Wolfson Arizona class devices
Most Wolfson Arizona class audio hub CODECs include a flexible ultra low power accessory detection subsystem. This driver exposes initial support for this subsystem via the Extcon framework, implementing support for ultra low power detection of headphone and headset with the ability to detect the polarity of a headset. The functionality of the devices is much richer and more flexible than the current driver, future patches will extend the features of the driver to more fully exploit this. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/extcon')
-rw-r--r--drivers/extcon/Kconfig8
-rw-r--r--drivers/extcon/Makefile1
-rw-r--r--drivers/extcon/extcon-arizona.c491
3 files changed, 500 insertions, 0 deletions
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index 29c5cf852efc..bb385ac629a8 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -29,4 +29,12 @@ config EXTCON_MAX8997
29 Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory 29 Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory
30 detector and switch. 30 detector and switch.
31 31
32config EXTCON_ARIZONA
33 tristate "Wolfson Arizona EXTCON support"
34 depends on MFD_ARIZONA
35 help
36 Say Y here to enable support for external accessory detection
37 with Wolfson Arizona devices. These are audio CODECs with
38 advanced audio accessory detection support.
39
32endif # MULTISTATE_SWITCH 40endif # MULTISTATE_SWITCH
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 86020bdb6da0..e932caaa311c 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -5,3 +5,4 @@
5obj-$(CONFIG_EXTCON) += extcon_class.o 5obj-$(CONFIG_EXTCON) += extcon_class.o
6obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.o 6obj-$(CONFIG_EXTCON_GPIO) += extcon_gpio.o
7obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o 7obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o
8obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c
new file mode 100644
index 000000000000..b068bc9defe1
--- /dev/null
+++ b/drivers/extcon/extcon-arizona.c
@@ -0,0 +1,491 @@
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>
24#include <linux/platform_device.h>
25#include <linux/pm_runtime.h>
26#include <linux/regulator/consumer.h>
27#include <linux/extcon.h>
28
29#include <linux/mfd/arizona/core.h>
30#include <linux/mfd/arizona/pdata.h>
31#include <linux/mfd/arizona/registers.h>
32
33struct arizona_extcon_info {
34 struct device *dev;
35 struct arizona *arizona;
36 struct mutex lock;
37 struct regulator *micvdd;
38
39 int micd_mode;
40 const struct arizona_micd_config *micd_modes;
41 int micd_num_modes;
42
43 bool micd_reva;
44
45 bool mic;
46 bool detecting;
47 int jack_flips;
48
49 struct extcon_dev edev;
50};
51
52static const struct arizona_micd_config micd_default_modes[] = {
53 { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 },
54 { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 },
55};
56
57#define ARIZONA_CABLE_MECHANICAL "Mechanical"
58#define ARIZONA_CABLE_HEADPHONE "Headphone"
59#define ARIZONA_CABLE_HEADSET "Headset"
60
61static const char *arizona_cable[] = {
62 ARIZONA_CABLE_MECHANICAL,
63 ARIZONA_CABLE_HEADSET,
64 ARIZONA_CABLE_HEADPHONE,
65 NULL,
66};
67
68static const u32 arizona_exclusions[] = {
69 0x6, /* Headphone and headset */
70 0,
71};
72
73static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode)
74{
75 struct arizona *arizona = info->arizona;
76
77 gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio,
78 info->micd_modes[mode].gpio);
79 regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
80 ARIZONA_MICD_BIAS_SRC_MASK,
81 info->micd_modes[mode].bias);
82 regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1,
83 ARIZONA_ACCDET_SRC, info->micd_modes[mode].src);
84
85 info->micd_mode = mode;
86
87 dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode);
88}
89
90static void arizona_start_mic(struct arizona_extcon_info *info)
91{
92 struct arizona *arizona = info->arizona;
93 bool change;
94 int ret;
95
96 info->detecting = true;
97 info->mic = false;
98 info->jack_flips = 0;
99
100 /* Microphone detection can't use idle mode */
101 pm_runtime_get(info->dev);
102
103 ret = regulator_enable(info->micvdd);
104 if (ret != 0) {
105 dev_err(arizona->dev, "Failed to enable MICVDD: %d\n",
106 ret);
107 }
108
109 if (info->micd_reva) {
110 regmap_write(arizona->regmap, 0x80, 0x3);
111 regmap_write(arizona->regmap, 0x294, 0);
112 regmap_write(arizona->regmap, 0x80, 0x0);
113 }
114
115 regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
116 ARIZONA_MICD_ENA, ARIZONA_MICD_ENA,
117 &change);
118 if (!change) {
119 regulator_disable(info->micvdd);
120 pm_runtime_put_autosuspend(info->dev);
121 }
122}
123
124static void arizona_stop_mic(struct arizona_extcon_info *info)
125{
126 struct arizona *arizona = info->arizona;
127 bool change;
128
129 regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
130 ARIZONA_MICD_ENA, 0,
131 &change);
132
133 if (info->micd_reva) {
134 regmap_write(arizona->regmap, 0x80, 0x3);
135 regmap_write(arizona->regmap, 0x294, 2);
136 regmap_write(arizona->regmap, 0x80, 0x0);
137 }
138
139 if (change) {
140 regulator_disable(info->micvdd);
141 pm_runtime_put_autosuspend(info->dev);
142 }
143}
144
145static irqreturn_t arizona_micdet(int irq, void *data)
146{
147 struct arizona_extcon_info *info = data;
148 struct arizona *arizona = info->arizona;
149 unsigned int val;
150 int ret;
151
152 mutex_lock(&info->lock);
153
154 ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val);
155 if (ret != 0) {
156 dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret);
157 return IRQ_NONE;
158 }
159
160 dev_dbg(arizona->dev, "MICDET: %x\n", val);
161
162 if (!(val & ARIZONA_MICD_VALID)) {
163 dev_warn(arizona->dev, "Microphone detection state invalid\n");
164 mutex_unlock(&info->lock);
165 return IRQ_NONE;
166 }
167
168 /* Due to jack detect this should never happen */
169 if (!(val & ARIZONA_MICD_STS)) {
170 dev_warn(arizona->dev, "Detected open circuit\n");
171 info->detecting = false;
172 goto handled;
173 }
174
175 /* If we got a high impedence we should have a headset, report it. */
176 if (info->detecting && (val & 0x400)) {
177 ret = extcon_set_cable_state(&info->edev,
178 ARIZONA_CABLE_HEADSET, true);
179
180 if (ret != 0)
181 dev_err(arizona->dev, "Headset report failed: %d\n",
182 ret);
183
184 info->mic = true;
185 info->detecting = false;
186 goto handled;
187 }
188
189 /* If we detected a lower impedence during initial startup
190 * then we probably have the wrong polarity, flip it. Don't
191 * do this for the lowest impedences to speed up detection of
192 * plain headphones. If both polarities report a low
193 * impedence then give up and report headphones.
194 */
195 if (info->detecting && (val & 0x3f8)) {
196 info->jack_flips++;
197
198 if (info->jack_flips >= info->micd_num_modes) {
199 dev_dbg(arizona->dev, "Detected headphone\n");
200 info->detecting = false;
201 ret = extcon_set_cable_state(&info->edev,
202 ARIZONA_CABLE_HEADPHONE,
203 true);
204 if (ret != 0)
205 dev_err(arizona->dev,
206 "Headphone report failed: %d\n",
207 ret);
208 } else {
209 info->micd_mode++;
210 if (info->micd_mode == info->micd_num_modes)
211 info->micd_mode = 0;
212 arizona_extcon_set_mode(info, info->micd_mode);
213
214 info->jack_flips++;
215 }
216
217 goto handled;
218 }
219
220 /*
221 * If we're still detecting and we detect a short then we've
222 * got a headphone. Otherwise it's a button press, the
223 * button reporting is stubbed out for now.
224 */
225 if (val & 0x3fc) {
226 if (info->mic) {
227 dev_dbg(arizona->dev, "Mic button detected\n");
228
229 } else if (info->detecting) {
230 dev_dbg(arizona->dev, "Headphone detected\n");
231 info->detecting = false;
232 arizona_stop_mic(info);
233
234 ret = extcon_set_cable_state(&info->edev,
235 ARIZONA_CABLE_HEADPHONE,
236 true);
237 if (ret != 0)
238 dev_err(arizona->dev,
239 "Headphone report failed: %d\n",
240 ret);
241 } else {
242 dev_warn(arizona->dev, "Button with no mic: %x\n",
243 val);
244 }
245 } else {
246 dev_dbg(arizona->dev, "Mic button released\n");
247 }
248
249handled:
250 pm_runtime_mark_last_busy(info->dev);
251 mutex_unlock(&info->lock);
252
253 return IRQ_HANDLED;
254}
255
256static irqreturn_t arizona_jackdet(int irq, void *data)
257{
258 struct arizona_extcon_info *info = data;
259 struct arizona *arizona = info->arizona;
260 unsigned int val;
261 int ret;
262
263 pm_runtime_get_sync(info->dev);
264
265 mutex_lock(&info->lock);
266
267 ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val);
268 if (ret != 0) {
269 dev_err(arizona->dev, "Failed to read jackdet status: %d\n",
270 ret);
271 mutex_unlock(&info->lock);
272 pm_runtime_put_autosuspend(info->dev);
273 return IRQ_NONE;
274 }
275
276 if (val & ARIZONA_JD1_STS) {
277 dev_dbg(arizona->dev, "Detected jack\n");
278 ret = extcon_set_cable_state(&info->edev,
279 ARIZONA_CABLE_MECHANICAL, true);
280
281 if (ret != 0)
282 dev_err(arizona->dev, "Mechanical report failed: %d\n",
283 ret);
284
285 arizona_start_mic(info);
286 } else {
287 dev_dbg(arizona->dev, "Detected jack removal\n");
288
289 arizona_stop_mic(info);
290
291 ret = extcon_update_state(&info->edev, 0xffffffff, 0);
292 if (ret != 0)
293 dev_err(arizona->dev, "Removal report failed: %d\n",
294 ret);
295 }
296
297 mutex_unlock(&info->lock);
298
299 pm_runtime_mark_last_busy(info->dev);
300 pm_runtime_put_autosuspend(info->dev);
301
302 return IRQ_HANDLED;
303}
304
305static int __devinit arizona_extcon_probe(struct platform_device *pdev)
306{
307 struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
308 struct arizona_pdata *pdata;
309 struct arizona_extcon_info *info;
310 int ret, mode;
311
312 pdata = dev_get_platdata(arizona->dev);
313
314 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
315 if (!info) {
316 dev_err(&pdev->dev, "failed to allocate memory\n");
317 ret = -ENOMEM;
318 goto err;
319 }
320
321 info->micvdd = devm_regulator_get(arizona->dev, "MICVDD");
322 if (IS_ERR(info->micvdd)) {
323 ret = PTR_ERR(info->micvdd);
324 dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret);
325 goto err;
326 }
327
328 mutex_init(&info->lock);
329 info->arizona = arizona;
330 info->dev = &pdev->dev;
331 info->detecting = true;
332 platform_set_drvdata(pdev, info);
333
334 switch (arizona->type) {
335 case WM5102:
336 switch (arizona->rev) {
337 case 0:
338 info->micd_reva = true;
339 break;
340 default:
341 break;
342 }
343 break;
344 default:
345 break;
346 }
347
348 info->edev.name = "Headset Jack";
349 info->edev.supported_cable = arizona_cable;
350 info->edev.mutually_exclusive = arizona_exclusions;
351
352 ret = extcon_dev_register(&info->edev, arizona->dev);
353 if (ret < 0) {
354 dev_err(arizona->dev, "extcon_dev_regster() failed: %d\n",
355 ret);
356 goto err;
357 }
358
359 if (pdata->num_micd_configs) {
360 info->micd_modes = pdata->micd_configs;
361 info->micd_num_modes = pdata->num_micd_configs;
362 } else {
363 info->micd_modes = micd_default_modes;
364 info->micd_num_modes = ARRAY_SIZE(micd_default_modes);
365 }
366
367 if (arizona->pdata.micd_pol_gpio > 0) {
368 if (info->micd_modes[0].gpio)
369 mode = GPIOF_OUT_INIT_HIGH;
370 else
371 mode = GPIOF_OUT_INIT_LOW;
372
373 ret = devm_gpio_request_one(&pdev->dev,
374 arizona->pdata.micd_pol_gpio,
375 mode,
376 "MICD polarity");
377 if (ret != 0) {
378 dev_err(arizona->dev, "Failed to request GPIO%d: %d\n",
379 arizona->pdata.micd_pol_gpio, ret);
380 goto err_register;
381 }
382 }
383
384 arizona_extcon_set_mode(info, 0);
385
386 pm_runtime_enable(&pdev->dev);
387 pm_runtime_idle(&pdev->dev);
388 pm_runtime_get_sync(&pdev->dev);
389
390 ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_RISE,
391 "JACKDET rise", arizona_jackdet, info);
392 if (ret != 0) {
393 dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n",
394 ret);
395 goto err_register;
396 }
397
398 ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1);
399 if (ret != 0) {
400 dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n",
401 ret);
402 goto err_rise;
403 }
404
405 ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_FALL,
406 "JACKDET fall", arizona_jackdet, info);
407 if (ret != 0) {
408 dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret);
409 goto err_rise_wake;
410 }
411
412 ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 1);
413 if (ret != 0) {
414 dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n",
415 ret);
416 goto err_fall;
417 }
418
419 ret = arizona_request_irq(arizona, ARIZONA_IRQ_MICDET,
420 "MICDET", arizona_micdet, info);
421 if (ret != 0) {
422 dev_err(&pdev->dev, "Failed to get MICDET IRQ: %d\n", ret);
423 goto err_fall_wake;
424 }
425
426 regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
427 ARIZONA_MICD_BIAS_STARTTIME_MASK |
428 ARIZONA_MICD_RATE_MASK,
429 7 << ARIZONA_MICD_BIAS_STARTTIME_SHIFT |
430 8 << ARIZONA_MICD_RATE_SHIFT);
431
432 arizona_clk32k_enable(arizona);
433 regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE,
434 ARIZONA_JD1_DB, ARIZONA_JD1_DB);
435 regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
436 ARIZONA_JD1_ENA, ARIZONA_JD1_ENA);
437
438 pm_runtime_put(&pdev->dev);
439
440 return 0;
441
442err_fall_wake:
443 arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0);
444err_fall:
445 arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info);
446err_rise_wake:
447 arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0);
448err_rise:
449 arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info);
450err_register:
451 pm_runtime_disable(&pdev->dev);
452 extcon_dev_unregister(&info->edev);
453err:
454 return ret;
455}
456
457static int __devexit arizona_extcon_remove(struct platform_device *pdev)
458{
459 struct arizona_extcon_info *info = platform_get_drvdata(pdev);
460 struct arizona *arizona = info->arizona;
461
462 pm_runtime_disable(&pdev->dev);
463
464 arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0);
465 arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0);
466 arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info);
467 arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info);
468 arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info);
469 regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
470 ARIZONA_JD1_ENA, 0);
471 arizona_clk32k_disable(arizona);
472 extcon_dev_unregister(&info->edev);
473
474 return 0;
475}
476
477static struct platform_driver arizona_extcon_driver = {
478 .driver = {
479 .name = "arizona-extcon",
480 .owner = THIS_MODULE,
481 },
482 .probe = arizona_extcon_probe,
483 .remove = __devexit_p(arizona_extcon_remove),
484};
485
486module_platform_driver(arizona_extcon_driver);
487
488MODULE_DESCRIPTION("Arizona Extcon driver");
489MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
490MODULE_LICENSE("GPL");
491MODULE_ALIAS("platform:extcon-arizona");