linux/drivers/extcon/extcon-arizona.c
<<
>>
Prefs
   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
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.