Root/drivers/extcon/extcon-arizona.c

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 0
58#define ARIZONA_CABLE_MICROPHONE 1
59#define ARIZONA_CABLE_HEADPHONE 2
60
61static const char *arizona_cable[] = {
62    "Mechanical",
63    "Microphone",
64    "Headphone",
65    NULL,
66};
67
68static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode)
69{
70    struct arizona *arizona = info->arizona;
71
72    gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio,
73                info->micd_modes[mode].gpio);
74    regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
75               ARIZONA_MICD_BIAS_SRC_MASK,
76               info->micd_modes[mode].bias);
77    regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1,
78               ARIZONA_ACCDET_SRC, info->micd_modes[mode].src);
79
80    info->micd_mode = mode;
81
82    dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode);
83}
84
85static void arizona_start_mic(struct arizona_extcon_info *info)
86{
87    struct arizona *arizona = info->arizona;
88    bool change;
89    int ret;
90
91    info->detecting = true;
92    info->mic = false;
93    info->jack_flips = 0;
94
95    /* Microphone detection can't use idle mode */
96    pm_runtime_get(info->dev);
97
98    ret = regulator_enable(info->micvdd);
99    if (ret != 0) {
100        dev_err(arizona->dev, "Failed to enable MICVDD: %d\n",
101            ret);
102    }
103
104    if (info->micd_reva) {
105        regmap_write(arizona->regmap, 0x80, 0x3);
106        regmap_write(arizona->regmap, 0x294, 0);
107        regmap_write(arizona->regmap, 0x80, 0x0);
108    }
109
110    regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
111                 ARIZONA_MICD_ENA, ARIZONA_MICD_ENA,
112                 &change);
113    if (!change) {
114        regulator_disable(info->micvdd);
115        pm_runtime_put_autosuspend(info->dev);
116    }
117}
118
119static void arizona_stop_mic(struct arizona_extcon_info *info)
120{
121    struct arizona *arizona = info->arizona;
122    bool change;
123
124    regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
125                 ARIZONA_MICD_ENA, 0,
126                 &change);
127
128    if (info->micd_reva) {
129        regmap_write(arizona->regmap, 0x80, 0x3);
130        regmap_write(arizona->regmap, 0x294, 2);
131        regmap_write(arizona->regmap, 0x80, 0x0);
132    }
133
134    if (change) {
135        regulator_disable(info->micvdd);
136        pm_runtime_put_autosuspend(info->dev);
137    }
138}
139
140static irqreturn_t arizona_micdet(int irq, void *data)
141{
142    struct arizona_extcon_info *info = data;
143    struct arizona *arizona = info->arizona;
144    unsigned int val;
145    int ret;
146
147    mutex_lock(&info->lock);
148
149    ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val);
150    if (ret != 0) {
151        dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret);
152        return IRQ_NONE;
153    }
154
155    dev_dbg(arizona->dev, "MICDET: %x\n", val);
156
157    if (!(val & ARIZONA_MICD_VALID)) {
158        dev_warn(arizona->dev, "Microphone detection state invalid\n");
159        mutex_unlock(&info->lock);
160        return IRQ_NONE;
161    }
162
163    /* Due to jack detect this should never happen */
164    if (!(val & ARIZONA_MICD_STS)) {
165        dev_warn(arizona->dev, "Detected open circuit\n");
166        info->detecting = false;
167        goto handled;
168    }
169
170    /* If we got a high impedence we should have a headset, report it. */
171    if (info->detecting && (val & 0x400)) {
172        ret = extcon_update_state(&info->edev,
173                      1 << ARIZONA_CABLE_MICROPHONE |
174                      1 << ARIZONA_CABLE_HEADPHONE,
175                      1 << ARIZONA_CABLE_MICROPHONE |
176                      1 << ARIZONA_CABLE_HEADPHONE);
177
178        if (ret != 0)
179            dev_err(arizona->dev, "Headset report failed: %d\n",
180                ret);
181
182        info->mic = true;
183        info->detecting = false;
184        goto handled;
185    }
186
187    /* If we detected a lower impedence during initial startup
188     * then we probably have the wrong polarity, flip it. Don't
189     * do this for the lowest impedences to speed up detection of
190     * plain headphones. If both polarities report a low
191     * impedence then give up and report headphones.
192     */
193    if (info->detecting && (val & 0x3f8)) {
194        info->jack_flips++;
195
196        if (info->jack_flips >= info->micd_num_modes) {
197            dev_dbg(arizona->dev, "Detected headphone\n");
198            info->detecting = false;
199            arizona_stop_mic(info);
200
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
351    ret = extcon_dev_register(&info->edev, arizona->dev);
352    if (ret < 0) {
353        dev_err(arizona->dev, "extcon_dev_regster() failed: %d\n",
354            ret);
355        goto err;
356    }
357
358    if (pdata->num_micd_configs) {
359        info->micd_modes = pdata->micd_configs;
360        info->micd_num_modes = pdata->num_micd_configs;
361    } else {
362        info->micd_modes = micd_default_modes;
363        info->micd_num_modes = ARRAY_SIZE(micd_default_modes);
364    }
365
366    if (arizona->pdata.micd_pol_gpio > 0) {
367        if (info->micd_modes[0].gpio)
368            mode = GPIOF_OUT_INIT_HIGH;
369        else
370            mode = GPIOF_OUT_INIT_LOW;
371
372        ret = devm_gpio_request_one(&pdev->dev,
373                        arizona->pdata.micd_pol_gpio,
374                        mode,
375                        "MICD polarity");
376        if (ret != 0) {
377            dev_err(arizona->dev, "Failed to request GPIO%d: %d\n",
378                arizona->pdata.micd_pol_gpio, ret);
379            goto err_register;
380        }
381    }
382
383    arizona_extcon_set_mode(info, 0);
384
385    pm_runtime_enable(&pdev->dev);
386    pm_runtime_idle(&pdev->dev);
387    pm_runtime_get_sync(&pdev->dev);
388
389    ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_RISE,
390                  "JACKDET rise", arizona_jackdet, info);
391    if (ret != 0) {
392        dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n",
393            ret);
394        goto err_register;
395    }
396
397    ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1);
398    if (ret != 0) {
399        dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n",
400            ret);
401        goto err_rise;
402    }
403
404    ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_FALL,
405                  "JACKDET fall", arizona_jackdet, info);
406    if (ret != 0) {
407        dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret);
408        goto err_rise_wake;
409    }
410
411    ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 1);
412    if (ret != 0) {
413        dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n",
414            ret);
415        goto err_fall;
416    }
417
418    ret = arizona_request_irq(arizona, ARIZONA_IRQ_MICDET,
419                  "MICDET", arizona_micdet, info);
420    if (ret != 0) {
421        dev_err(&pdev->dev, "Failed to get MICDET IRQ: %d\n", ret);
422        goto err_fall_wake;
423    }
424
425    regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
426               ARIZONA_MICD_BIAS_STARTTIME_MASK |
427               ARIZONA_MICD_RATE_MASK,
428               7 << ARIZONA_MICD_BIAS_STARTTIME_SHIFT |
429               8 << ARIZONA_MICD_RATE_SHIFT);
430
431    arizona_clk32k_enable(arizona);
432    regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE,
433               ARIZONA_JD1_DB, ARIZONA_JD1_DB);
434    regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
435               ARIZONA_JD1_ENA, ARIZONA_JD1_ENA);
436
437    pm_runtime_put(&pdev->dev);
438
439    return 0;
440
441err_fall_wake:
442    arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0);
443err_fall:
444    arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info);
445err_rise_wake:
446    arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0);
447err_rise:
448    arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info);
449err_register:
450    pm_runtime_disable(&pdev->dev);
451    extcon_dev_unregister(&info->edev);
452err:
453    return ret;
454}
455
456static int __devexit arizona_extcon_remove(struct platform_device *pdev)
457{
458    struct arizona_extcon_info *info = platform_get_drvdata(pdev);
459    struct arizona *arizona = info->arizona;
460
461    pm_runtime_disable(&pdev->dev);
462
463    arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0);
464    arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0);
465    arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info);
466    arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info);
467    arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info);
468    regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
469               ARIZONA_JD1_ENA, 0);
470    arizona_clk32k_disable(arizona);
471    extcon_dev_unregister(&info->edev);
472
473    return 0;
474}
475
476static struct platform_driver arizona_extcon_driver = {
477    .driver = {
478        .name = "arizona-extcon",
479        .owner = THIS_MODULE,
480    },
481    .probe = arizona_extcon_probe,
482    .remove = __devexit_p(arizona_extcon_remove),
483};
484
485module_platform_driver(arizona_extcon_driver);
486
487MODULE_DESCRIPTION("Arizona Extcon driver");
488MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
489MODULE_LICENSE("GPL");
490MODULE_ALIAS("platform:extcon-arizona");
491

Archive Download this file



interactive