extcon: arizona: Check we report a valid impedance
[pandora-kernel.git] / drivers / extcon / extcon-arizona.c
index dc357a4..4022fe2 100644 (file)
 #include <linux/mfd/arizona/pdata.h>
 #include <linux/mfd/arizona/registers.h>
 
-#define ARIZONA_NUM_BUTTONS 6
+#define ARIZONA_MAX_MICD_RANGE 8
 
 #define ARIZONA_ACCDET_MODE_MIC 0
 #define ARIZONA_ACCDET_MODE_HPL 1
 #define ARIZONA_ACCDET_MODE_HPR 2
 
+#define HPDET_DEBOUNCE 250
+
 struct arizona_extcon_info {
        struct device *dev;
        struct arizona *arizona;
@@ -46,10 +48,15 @@ struct arizona_extcon_info {
        struct regulator *micvdd;
        struct input_dev *input;
 
+       u16 last_jackdet;
+
        int micd_mode;
        const struct arizona_micd_config *micd_modes;
        int micd_num_modes;
 
+       const struct arizona_micd_range *micd_ranges;
+       int num_micd_ranges;
+
        bool micd_reva;
        bool micd_clamp;
 
@@ -71,20 +78,25 @@ struct arizona_extcon_info {
 };
 
 static const struct arizona_micd_config micd_default_modes[] = {
-       { 0,                  2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 },
        { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 },
+       { 0,                  2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 },
 };
 
-static struct {
-       u16 status;
-       int report;
-} arizona_lvl_to_key[ARIZONA_NUM_BUTTONS] = {
-       {  0x1, BTN_0 },
-       {  0x2, BTN_1 },
-       {  0x4, BTN_2 },
-       {  0x8, BTN_3 },
-       { 0x10, BTN_4 },
-       { 0x20, BTN_5 },
+static const struct arizona_micd_range micd_default_ranges[] = {
+       { .max =  11, .key = BTN_0 },
+       { .max =  28, .key = BTN_1 },
+       { .max =  54, .key = BTN_2 },
+       { .max = 100, .key = BTN_3 },
+       { .max = 186, .key = BTN_4 },
+       { .max = 430, .key = BTN_5 },
+};
+
+static const int arizona_micd_levels[] = {
+       3, 6, 8, 11, 13, 16, 18, 21, 23, 26, 28, 31, 34, 36, 39, 41, 44, 46,
+       49, 52, 54, 57, 60, 62, 65, 67, 70, 73, 75, 78, 81, 83, 89, 94, 100,
+       105, 111, 116, 122, 127, 139, 150, 161, 173, 186, 196, 209, 220, 245,
+       270, 295, 321, 348, 375, 402, 430, 489, 550, 614, 681, 752, 903, 1071,
+       1257,
 };
 
 #define ARIZONA_CABLE_MECHANICAL 0
@@ -100,10 +112,61 @@ static const char *arizona_cable[] = {
        NULL,
 };
 
+static void arizona_extcon_do_magic(struct arizona_extcon_info *info,
+                                   unsigned int magic)
+{
+       struct arizona *arizona = info->arizona;
+       int ret;
+
+       mutex_lock(&arizona->dapm->card->dapm_mutex);
+
+       arizona->hpdet_magic = magic;
+
+       /* Keep the HP output stages disabled while doing the magic */
+       if (magic) {
+               ret = regmap_update_bits(arizona->regmap,
+                                        ARIZONA_OUTPUT_ENABLES_1,
+                                        ARIZONA_OUT1L_ENA |
+                                        ARIZONA_OUT1R_ENA, 0);
+               if (ret != 0)
+                       dev_warn(arizona->dev,
+                               "Failed to disable headphone outputs: %d\n",
+                                ret);
+       }
+
+       ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000,
+                                magic);
+       if (ret != 0)
+               dev_warn(arizona->dev, "Failed to do magic: %d\n",
+                                ret);
+
+       ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000,
+                                magic);
+       if (ret != 0)
+               dev_warn(arizona->dev, "Failed to do magic: %d\n",
+                        ret);
+
+       /* Restore the desired state while not doing the magic */
+       if (!magic) {
+               ret = regmap_update_bits(arizona->regmap,
+                                        ARIZONA_OUTPUT_ENABLES_1,
+                                        ARIZONA_OUT1L_ENA |
+                                        ARIZONA_OUT1R_ENA, arizona->hp_ena);
+               if (ret != 0)
+                       dev_warn(arizona->dev,
+                                "Failed to restore headphone outputs: %d\n",
+                                ret);
+       }
+
+       mutex_unlock(&arizona->dapm->card->dapm_mutex);
+}
+
 static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode)
 {
        struct arizona *arizona = info->arizona;
 
+       mode %= info->micd_num_modes;
+
        if (arizona->pdata.micd_pol_gpio > 0)
                gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio,
                                        info->micd_modes[mode].gpio);
@@ -460,7 +523,7 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading)
                 * measure the mic as high impedance.
                 */
                if ((info->hpdet_res[0] > info->hpdet_res[1] * 2) ||
-                   (id_gpio && info->hpdet_res[2] > 10)) {
+                   (id_gpio && info->hpdet_res[2] > 1257)) {
                        dev_dbg(arizona->dev, "Detected mic\n");
                        info->mic = true;
                        info->detecting = true;
@@ -484,7 +547,6 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
        struct arizona *arizona = info->arizona;
        int id_gpio = arizona->pdata.hpdet_id_gpio;
        int report = ARIZONA_CABLE_HEADPHONE;
-       unsigned int val;
        int ret, reading;
 
        mutex_lock(&info->lock);
@@ -539,28 +601,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data)
                dev_err(arizona->dev, "Failed to report HP/line: %d\n",
                        ret);
 
-       mutex_lock(&arizona->dapm->card->dapm_mutex);
-
-       ret = regmap_read(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, &val);
-       if (ret != 0) {
-               dev_err(arizona->dev, "Failed to read output enables: %d\n",
-                       ret);
-               val = 0;
-       }
-
-       if (!(val & (ARIZONA_OUT1L_ENA | ARIZONA_OUT1R_ENA))) {
-               ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0);
-               if (ret != 0)
-                       dev_warn(arizona->dev, "Failed to undo magic: %d\n",
-                                ret);
-
-               ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0);
-               if (ret != 0)
-                       dev_warn(arizona->dev, "Failed to undo magic: %d\n",
-                                ret);
-       }
-
-       mutex_unlock(&arizona->dapm->card->dapm_mutex);
+       arizona_extcon_do_magic(info, 0);
 
 done:
        if (id_gpio)
@@ -606,13 +647,7 @@ static void arizona_identify_headphone(struct arizona_extcon_info *info)
        if (info->mic)
                arizona_stop_mic(info);
 
-       ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000);
-       if (ret != 0)
-               dev_warn(arizona->dev, "Failed to do magic: %d\n", ret);
-
-       ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0x4000);
-       if (ret != 0)
-               dev_warn(arizona->dev, "Failed to do magic: %d\n", ret);
+       arizona_extcon_do_magic(info, 0x4000);
 
        ret = regmap_update_bits(arizona->regmap,
                                 ARIZONA_ACCESSORY_DETECT_MODE_1,
@@ -653,7 +688,6 @@ err:
 static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
 {
        struct arizona *arizona = info->arizona;
-       unsigned int val;
        int ret;
 
        dev_dbg(arizona->dev, "Starting identification via HPDET\n");
@@ -663,32 +697,7 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
 
        info->hpdet_active = true;
 
-       arizona_extcon_pulse_micbias(info);
-
-       mutex_lock(&arizona->dapm->card->dapm_mutex);
-
-       ret = regmap_read(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, &val);
-       if (ret != 0) {
-               dev_err(arizona->dev, "Failed to read output enables: %d\n",
-                       ret);
-               val = 0;
-       }
-
-       if (!(val & (ARIZONA_OUT1L_ENA | ARIZONA_OUT1R_ENA))) {
-               ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000,
-                                        0x4000);
-               if (ret != 0)
-                       dev_warn(arizona->dev, "Failed to do magic: %d\n",
-                                ret);
-
-               ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000,
-                                        0x4000);
-               if (ret != 0)
-                       dev_warn(arizona->dev, "Failed to do magic: %d\n",
-                                ret);
-       }
-
-       mutex_unlock(&arizona->dapm->card->dapm_mutex);
+       arizona_extcon_do_magic(info, 0x4000);
 
        ret = regmap_update_bits(arizona->regmap,
                                 ARIZONA_ACCESSORY_DETECT_MODE_1,
@@ -728,22 +737,30 @@ static irqreturn_t arizona_micdet(int irq, void *data)
 {
        struct arizona_extcon_info *info = data;
        struct arizona *arizona = info->arizona;
-       unsigned int val, lvl;
-       int ret, i;
+       unsigned int val = 0, lvl;
+       int ret, i, key;
 
        mutex_lock(&info->lock);
 
-       ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val);
-       if (ret != 0) {
-               dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret);
-               mutex_unlock(&info->lock);
-               return IRQ_NONE;
-       }
+       for (i = 0; i < 10 && !(val & 0x7fc); i++) {
+               ret = regmap_read(arizona->regmap, ARIZONA_MIC_DETECT_3, &val);
+               if (ret != 0) {
+                       dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret);
+                       mutex_unlock(&info->lock);
+                       return IRQ_NONE;
+               }
+
+               dev_dbg(arizona->dev, "MICDET: %x\n", val);
 
-       dev_dbg(arizona->dev, "MICDET: %x\n", val);
+               if (!(val & ARIZONA_MICD_VALID)) {
+                       dev_warn(arizona->dev, "Microphone detection state invalid\n");
+                       mutex_unlock(&info->lock);
+                       return IRQ_NONE;
+               }
+       }
 
-       if (!(val & ARIZONA_MICD_VALID)) {
-               dev_warn(arizona->dev, "Microphone detection state invalid\n");
+       if (i == 10 && !(val & 0x7fc)) {
+               dev_err(arizona->dev, "Failed to get valid MICDET value\n");
                mutex_unlock(&info->lock);
                return IRQ_NONE;
        }
@@ -786,7 +803,7 @@ static irqreturn_t arizona_micdet(int irq, void *data)
         * impedence then give up and report headphones.
         */
        if (info->detecting && (val & 0x3f8)) {
-               if (info->jack_flips >= info->micd_num_modes) {
+               if (info->jack_flips >= info->micd_num_modes * 10) {
                        dev_dbg(arizona->dev, "Detected HP/line\n");
                        arizona_identify_headphone(info);
 
@@ -816,12 +833,13 @@ static irqreturn_t arizona_micdet(int irq, void *data)
                        lvl = val & ARIZONA_MICD_LVL_MASK;
                        lvl >>= ARIZONA_MICD_LVL_SHIFT;
 
-                       for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
-                               if (lvl & arizona_lvl_to_key[i].status)
-                                       input_report_key(info->input,
-                                                        arizona_lvl_to_key[i].report,
-                                                        1);
-                       input_sync(info->input);
+                       WARN_ON(!lvl);
+                       WARN_ON(ffs(lvl) - 1 >= info->num_micd_ranges);
+                       if (lvl && ffs(lvl) - 1 < info->num_micd_ranges) {
+                               key = info->micd_ranges[ffs(lvl) - 1].key;
+                               input_report_key(info->input, key, 1);
+                               input_sync(info->input);
+                       }
 
                } else if (info->detecting) {
                        dev_dbg(arizona->dev, "Headphone detected\n");
@@ -835,9 +853,9 @@ static irqreturn_t arizona_micdet(int irq, void *data)
                }
        } else {
                dev_dbg(arizona->dev, "Mic button released\n");
-               for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
+               for (i = 0; i < info->num_micd_ranges; i++)
                        input_report_key(info->input,
-                                        arizona_lvl_to_key[i].report, 0);
+                                        info->micd_ranges[i].key, 0);
                input_sync(info->input);
                arizona_extcon_pulse_micbias(info);
        }
@@ -865,11 +883,12 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
        struct arizona_extcon_info *info = data;
        struct arizona *arizona = info->arizona;
        unsigned int val, present, mask;
+       bool cancelled;
        int ret, i;
 
-       pm_runtime_get_sync(info->dev);
+       cancelled = cancel_delayed_work_sync(&info->hpdet_work);
 
-       cancel_delayed_work_sync(&info->hpdet_work);
+       pm_runtime_get_sync(info->dev);
 
        mutex_lock(&info->lock);
 
@@ -890,7 +909,18 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
                return IRQ_NONE;
        }
 
-       if ((val & mask) == present) {
+       val &= mask;
+       if (val == info->last_jackdet) {
+               dev_dbg(arizona->dev, "Suppressing duplicate JACKDET\n");
+               if (cancelled)
+                       schedule_delayed_work(&info->hpdet_work,
+                                             msecs_to_jiffies(HPDET_DEBOUNCE));
+
+               goto out;
+       }
+       info->last_jackdet = val;
+
+       if (info->last_jackdet == present) {
                dev_dbg(arizona->dev, "Detected jack\n");
                ret = extcon_set_cable_state_(&info->edev,
                                              ARIZONA_CABLE_MECHANICAL, true);
@@ -907,7 +937,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
                        arizona_start_mic(info);
                } else {
                        schedule_delayed_work(&info->hpdet_work,
-                                             msecs_to_jiffies(250));
+                                             msecs_to_jiffies(HPDET_DEBOUNCE));
                }
 
                regmap_update_bits(arizona->regmap,
@@ -924,9 +954,9 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
                info->mic = false;
                info->hpdet_done = false;
 
-               for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
+               for (i = 0; i < info->num_micd_ranges; i++)
                        input_report_key(info->input,
-                                        arizona_lvl_to_key[i].report, 0);
+                                        info->micd_ranges[i].key, 0);
                input_sync(info->input);
 
                ret = extcon_update_state(&info->edev, 0xffffffff, 0);
@@ -947,6 +977,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
                     ARIZONA_JD1_FALL_TRIG_STS |
                     ARIZONA_JD1_RISE_TRIG_STS);
 
+out:
        mutex_unlock(&info->lock);
 
        pm_runtime_mark_last_busy(info->dev);
@@ -955,13 +986,34 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
        return IRQ_HANDLED;
 }
 
+/* Map a level onto a slot in the register bank */
+static void arizona_micd_set_level(struct arizona *arizona, int index,
+                                  unsigned int level)
+{
+       int reg;
+       unsigned int mask;
+
+       reg = ARIZONA_MIC_DETECT_LEVEL_4 - (index / 2);
+
+       if (!(index % 2)) {
+               mask = 0x3f00;
+               level <<= 8;
+       } else {
+               mask = 0x3f;
+       }
+
+       /* Program the level itself */
+       regmap_update_bits(arizona->regmap, reg, mask, level);
+}
+
 static int arizona_extcon_probe(struct platform_device *pdev)
 {
        struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
        struct arizona_pdata *pdata;
        struct arizona_extcon_info *info;
+       unsigned int val;
        int jack_irq_fall, jack_irq_rise;
-       int ret, mode, i;
+       int ret, mode, i, j;
 
        if (!arizona->dapm || !arizona->dapm->card)
                return -EPROBE_DEFER;
@@ -985,6 +1037,7 @@ static int arizona_extcon_probe(struct platform_device *pdev)
        mutex_init(&info->lock);
        info->arizona = arizona;
        info->dev = &pdev->dev;
+       info->last_jackdet = ~(ARIZONA_MICD_CLAMP_STS | ARIZONA_JD1_STS);
        INIT_DELAYED_WORK(&info->hpdet_work, arizona_hpdet_work);
        platform_set_drvdata(pdev, info);
 
@@ -1014,6 +1067,17 @@ static int arizona_extcon_probe(struct platform_device *pdev)
                goto err;
        }
 
+       info->input = devm_input_allocate_device(&pdev->dev);
+       if (!info->input) {
+               dev_err(arizona->dev, "Can't allocate input dev\n");
+               ret = -ENOMEM;
+               goto err_register;
+       }
+
+       info->input->name = "Headset";
+       info->input->phys = "arizona/extcon";
+       info->input->dev.parent = &pdev->dev;
+
        if (pdata->num_micd_configs) {
                info->micd_modes = pdata->micd_configs;
                info->micd_num_modes = pdata->num_micd_configs;
@@ -1069,15 +1133,79 @@ static int arizona_extcon_probe(struct platform_device *pdev)
                                   arizona->pdata.micd_dbtime
                                   << ARIZONA_MICD_DBTIME_SHIFT);
 
+       BUILD_BUG_ON(ARRAY_SIZE(arizona_micd_levels) != 0x40);
+
+       if (arizona->pdata.num_micd_ranges) {
+               info->micd_ranges = pdata->micd_ranges;
+               info->num_micd_ranges = pdata->num_micd_ranges;
+       } else {
+               info->micd_ranges = micd_default_ranges;
+               info->num_micd_ranges = ARRAY_SIZE(micd_default_ranges);
+       }
+
+       if (arizona->pdata.num_micd_ranges > ARIZONA_MAX_MICD_RANGE) {
+               dev_err(arizona->dev, "Too many MICD ranges: %d\n",
+                       arizona->pdata.num_micd_ranges);
+       }
+
+       if (info->num_micd_ranges > 1) {
+               for (i = 1; i < info->num_micd_ranges; i++) {
+                       if (info->micd_ranges[i - 1].max >
+                           info->micd_ranges[i].max) {
+                               dev_err(arizona->dev,
+                                       "MICD ranges must be sorted\n");
+                               ret = -EINVAL;
+                               goto err_input;
+                       }
+               }
+       }
+
+       /* Disable all buttons by default */
+       regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_2,
+                          ARIZONA_MICD_LVL_SEL_MASK, 0x81);
+
+       /* Set up all the buttons the user specified */
+       for (i = 0; i < info->num_micd_ranges; i++) {
+               for (j = 0; j < ARRAY_SIZE(arizona_micd_levels); j++)
+                       if (arizona_micd_levels[j] >= info->micd_ranges[i].max)
+                               break;
+
+               if (j == ARRAY_SIZE(arizona_micd_levels)) {
+                       dev_err(arizona->dev, "Unsupported MICD level %d\n",
+                               info->micd_ranges[i].max);
+                       ret = -EINVAL;
+                       goto err_input;
+               }
+
+               dev_dbg(arizona->dev, "%d ohms for MICD threshold %d\n",
+                       arizona_micd_levels[j], i);
+
+               arizona_micd_set_level(arizona, i, j);
+               input_set_capability(info->input, EV_KEY,
+                                    info->micd_ranges[i].key);
+
+               /* Enable reporting of that range */
+               regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_2,
+                                  1 << i, 1 << i);
+       }
+
+       /* Set all the remaining keys to a maximum */
+       for (; i < ARIZONA_MAX_MICD_RANGE; i++)
+               arizona_micd_set_level(arizona, i, 0x3f);
+
        /*
         * If we have a clamp use it, activating in conjunction with
         * GPIO5 if that is connected for jack detect operation.
         */
        if (info->micd_clamp) {
                if (arizona->pdata.jd_gpio5) {
-                       /* Put the GPIO into input mode */
+                       /* Put the GPIO into input mode with optional pull */
+                       val = 0xc101;
+                       if (arizona->pdata.jd_gpio5_nopull)
+                               val &= ~ARIZONA_GPN_PU;
+
                        regmap_write(arizona->regmap, ARIZONA_GPIO5_CTRL,
-                                    0xc101);
+                                    val);
 
                        regmap_update_bits(arizona->regmap,
                                           ARIZONA_MICD_CLAMP_CONTROL,
@@ -1096,20 +1224,6 @@ static int arizona_extcon_probe(struct platform_device *pdev)
 
        arizona_extcon_set_mode(info, 0);
 
-       info->input = devm_input_allocate_device(&pdev->dev);
-       if (!info->input) {
-               dev_err(arizona->dev, "Can't allocate input dev\n");
-               ret = -ENOMEM;
-               goto err_register;
-       }
-
-       for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
-               input_set_capability(info->input, EV_KEY,
-                                    arizona_lvl_to_key[i].report);
-       info->input->name = "Headset";
-       info->input->phys = "arizona/extcon";
-       info->input->dev.parent = &pdev->dev;
-
        pm_runtime_enable(&pdev->dev);
        pm_runtime_idle(&pdev->dev);
        pm_runtime_get_sync(&pdev->dev);