]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/extcon/extcon-arizona.c
extcon: arizona: Update cable reporting calls and split headset
[karo-tx-linux.git] / 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
33 struct 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
52 static 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
61 static const char *arizona_cable[] = {
62         "Mechanical",
63         "Microphone",
64         "Headphone",
65         NULL,
66 };
67
68 static 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
85 static 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
119 static 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
140 static 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                         ret = extcon_set_cable_state_(&info->edev,
200                                                       ARIZONA_CABLE_HEADPHONE,
201                                                       true);
202                         if (ret != 0)
203                                 dev_err(arizona->dev,
204                                         "Headphone report failed: %d\n",
205                                 ret);
206                 } else {
207                         info->micd_mode++;
208                         if (info->micd_mode == info->micd_num_modes)
209                                 info->micd_mode = 0;
210                         arizona_extcon_set_mode(info, info->micd_mode);
211
212                         info->jack_flips++;
213                 }
214
215                 goto handled;
216         }
217
218         /*
219          * If we're still detecting and we detect a short then we've
220          * got a headphone.  Otherwise it's a button press, the
221          * button reporting is stubbed out for now.
222          */
223         if (val & 0x3fc) {
224                 if (info->mic) {
225                         dev_dbg(arizona->dev, "Mic button detected\n");
226
227                 } else if (info->detecting) {
228                         dev_dbg(arizona->dev, "Headphone detected\n");
229                         info->detecting = false;
230                         arizona_stop_mic(info);
231
232                         ret = extcon_set_cable_state_(&info->edev,
233                                                       ARIZONA_CABLE_HEADPHONE,
234                                                       true);
235                         if (ret != 0)
236                                 dev_err(arizona->dev,
237                                         "Headphone report failed: %d\n",
238                                 ret);
239                 } else {
240                         dev_warn(arizona->dev, "Button with no mic: %x\n",
241                                  val);
242                 }
243         } else {
244                 dev_dbg(arizona->dev, "Mic button released\n");
245         }
246
247 handled:
248         pm_runtime_mark_last_busy(info->dev);
249         mutex_unlock(&info->lock);
250
251         return IRQ_HANDLED;
252 }
253
254 static irqreturn_t arizona_jackdet(int irq, void *data)
255 {
256         struct arizona_extcon_info *info = data;
257         struct arizona *arizona = info->arizona;
258         unsigned int val;
259         int ret;
260
261         pm_runtime_get_sync(info->dev);
262
263         mutex_lock(&info->lock);
264
265         ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val);
266         if (ret != 0) {
267                 dev_err(arizona->dev, "Failed to read jackdet status: %d\n",
268                         ret);
269                 mutex_unlock(&info->lock);
270                 pm_runtime_put_autosuspend(info->dev);
271                 return IRQ_NONE;
272         }
273
274         if (val & ARIZONA_JD1_STS) {
275                 dev_dbg(arizona->dev, "Detected jack\n");
276                 ret = extcon_set_cable_state_(&info->edev,
277                                               ARIZONA_CABLE_MECHANICAL, true);
278
279                 if (ret != 0)
280                         dev_err(arizona->dev, "Mechanical report failed: %d\n",
281                                 ret);
282
283                 arizona_start_mic(info);
284         } else {
285                 dev_dbg(arizona->dev, "Detected jack removal\n");
286
287                 arizona_stop_mic(info);
288
289                 ret = extcon_update_state(&info->edev, 0xffffffff, 0);
290                 if (ret != 0)
291                         dev_err(arizona->dev, "Removal report failed: %d\n",
292                                 ret);
293         }
294
295         mutex_unlock(&info->lock);
296
297         pm_runtime_mark_last_busy(info->dev);
298         pm_runtime_put_autosuspend(info->dev);
299
300         return IRQ_HANDLED;
301 }
302
303 static int __devinit arizona_extcon_probe(struct platform_device *pdev)
304 {
305         struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
306         struct arizona_pdata *pdata;
307         struct arizona_extcon_info *info;
308         int ret, mode;
309
310         pdata = dev_get_platdata(arizona->dev);
311
312         info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
313         if (!info) {
314                 dev_err(&pdev->dev, "failed to allocate memory\n");
315                 ret = -ENOMEM;
316                 goto err;
317         }
318
319         info->micvdd = devm_regulator_get(arizona->dev, "MICVDD");
320         if (IS_ERR(info->micvdd)) {
321                 ret = PTR_ERR(info->micvdd);
322                 dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret);
323                 goto err;
324         }
325
326         mutex_init(&info->lock);
327         info->arizona = arizona;
328         info->dev = &pdev->dev;
329         info->detecting = true;
330         platform_set_drvdata(pdev, info);
331
332         switch (arizona->type) {
333         case WM5102:
334                 switch (arizona->rev) {
335                 case 0:
336                         info->micd_reva = true;
337                         break;
338                 default:
339                         break;
340                 }
341                 break;
342         default:
343                 break;
344         }
345
346         info->edev.name = "Headset Jack";
347         info->edev.supported_cable = arizona_cable;
348
349         ret = extcon_dev_register(&info->edev, arizona->dev);
350         if (ret < 0) {
351                 dev_err(arizona->dev, "extcon_dev_regster() failed: %d\n",
352                         ret);
353                 goto err;
354         }
355
356         if (pdata->num_micd_configs) {
357                 info->micd_modes = pdata->micd_configs;
358                 info->micd_num_modes = pdata->num_micd_configs;
359         } else {
360                 info->micd_modes = micd_default_modes;
361                 info->micd_num_modes = ARRAY_SIZE(micd_default_modes);
362         }
363
364         if (arizona->pdata.micd_pol_gpio > 0) {
365                 if (info->micd_modes[0].gpio)
366                         mode = GPIOF_OUT_INIT_HIGH;
367                 else
368                         mode = GPIOF_OUT_INIT_LOW;
369
370                 ret = devm_gpio_request_one(&pdev->dev,
371                                             arizona->pdata.micd_pol_gpio,
372                                             mode,
373                                             "MICD polarity");
374                 if (ret != 0) {
375                         dev_err(arizona->dev, "Failed to request GPIO%d: %d\n",
376                                 arizona->pdata.micd_pol_gpio, ret);
377                         goto err_register;
378                 }
379         }
380
381         arizona_extcon_set_mode(info, 0);
382
383         pm_runtime_enable(&pdev->dev);
384         pm_runtime_idle(&pdev->dev);
385         pm_runtime_get_sync(&pdev->dev);
386
387         ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_RISE,
388                                   "JACKDET rise", arizona_jackdet, info);
389         if (ret != 0) {
390                 dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n",
391                         ret);
392                 goto err_register;
393         }
394
395         ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1);
396         if (ret != 0) {
397                 dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n",
398                         ret);
399                 goto err_rise;
400         }
401
402         ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_FALL,
403                                   "JACKDET fall", arizona_jackdet, info);
404         if (ret != 0) {
405                 dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret);
406                 goto err_rise_wake;
407         }
408
409         ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 1);
410         if (ret != 0) {
411                 dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n",
412                         ret);
413                 goto err_fall;
414         }
415
416         ret = arizona_request_irq(arizona, ARIZONA_IRQ_MICDET,
417                                   "MICDET", arizona_micdet, info);
418         if (ret != 0) {
419                 dev_err(&pdev->dev, "Failed to get MICDET IRQ: %d\n", ret);
420                 goto err_fall_wake;
421         }
422
423         regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
424                            ARIZONA_MICD_BIAS_STARTTIME_MASK |
425                            ARIZONA_MICD_RATE_MASK,
426                            7 << ARIZONA_MICD_BIAS_STARTTIME_SHIFT |
427                            8 << ARIZONA_MICD_RATE_SHIFT);
428
429         arizona_clk32k_enable(arizona);
430         regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE,
431                            ARIZONA_JD1_DB, ARIZONA_JD1_DB);
432         regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
433                            ARIZONA_JD1_ENA, ARIZONA_JD1_ENA);
434
435         pm_runtime_put(&pdev->dev);
436
437         return 0;
438
439 err_fall_wake:
440         arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0);
441 err_fall:
442         arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info);
443 err_rise_wake:
444         arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0);
445 err_rise:
446         arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info);
447 err_register:
448         pm_runtime_disable(&pdev->dev);
449         extcon_dev_unregister(&info->edev);
450 err:
451         return ret;
452 }
453
454 static int __devexit arizona_extcon_remove(struct platform_device *pdev)
455 {
456         struct arizona_extcon_info *info = platform_get_drvdata(pdev);
457         struct arizona *arizona = info->arizona;
458
459         pm_runtime_disable(&pdev->dev);
460
461         arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0);
462         arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0);
463         arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info);
464         arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info);
465         arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info);
466         regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
467                            ARIZONA_JD1_ENA, 0);
468         arizona_clk32k_disable(arizona);
469         extcon_dev_unregister(&info->edev);
470
471         return 0;
472 }
473
474 static struct platform_driver arizona_extcon_driver = {
475         .driver         = {
476                 .name   = "arizona-extcon",
477                 .owner  = THIS_MODULE,
478         },
479         .probe          = arizona_extcon_probe,
480         .remove         = __devexit_p(arizona_extcon_remove),
481 };
482
483 module_platform_driver(arizona_extcon_driver);
484
485 MODULE_DESCRIPTION("Arizona Extcon driver");
486 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
487 MODULE_LICENSE("GPL");
488 MODULE_ALIAS("platform:extcon-arizona");