From 84eaa13616b6e7d001b7f7b909228087779b677b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 25 Jan 2013 20:14:44 +0800 Subject: extcon: arizona: Attempt more microphone measurements In some pathological use cases users may insert an accessory very slowly causing multiple indeterminate measurements. Handle this by retrying many measurements before we give up and declare a headphone. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index b28927972128..4bb0e9ae405d 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -153,6 +153,8 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) { struct arizona *arizona = info->arizona; + mode %= info->num_micd_modes; + if (arizona->pdata.micd_pol_gpio > 0) gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio, info->micd_modes[mode].gpio); @@ -783,7 +785,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); -- cgit v1.2.3 From 6fed4d869a11fdbb4c6a5e444dfb2c22f92c3e46 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 22:03:06 +0100 Subject: extcon: arizona: Allow configuration of button detection The Arizona button detection circuit is configurable, allowing the system integrator to program a range of thresholds for the buttons supported on the accessory but currently the driver uses the default button ranges and does not provide any flexibility in how this is exposed to the application layer. Provide platform data allowing the user to control this and to map the buttons to keys in the input subsystem. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 164 +++++++++++++++++++++++++++++--------- include/linux/mfd/arizona/pdata.h | 9 +++ 2 files changed, 134 insertions(+), 39 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 4bb0e9ae405d..e2339629126a 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -33,7 +33,7 @@ #include #include -#define ARIZONA_NUM_BUTTONS 6 +#define ARIZONA_MAX_MICD_RANGE 8 #define ARIZONA_ACCDET_MODE_MIC 0 #define ARIZONA_ACCDET_MODE_HPL 1 @@ -50,6 +50,9 @@ struct arizona_extcon_info { 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 +74,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 @@ -153,7 +161,7 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) { struct arizona *arizona = info->arizona; - mode %= info->num_micd_modes; + mode %= info->micd_num_modes; if (arizona->pdata.micd_pol_gpio > 0) gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio, @@ -728,7 +736,7 @@ 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; + int ret, i, key; mutex_lock(&info->lock); @@ -815,12 +823,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"); @@ -834,9 +843,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); } @@ -923,9 +932,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); @@ -954,13 +963,33 @@ 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; 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; @@ -1013,6 +1042,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; @@ -1068,6 +1108,66 @@ 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. @@ -1095,20 +1195,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); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 455c51d22d6b..eb11a8ac6db2 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -86,6 +86,11 @@ struct arizona_micd_config { bool gpio; }; +struct arizona_micd_range { + int max; /** Ohms */ + int key; /** Key to report to input layer */ +}; + struct arizona_pdata { int reset; /** GPIO controlling /RESET, if any */ int ldoena; /** GPIO controlling LODENA, if any */ @@ -138,6 +143,10 @@ struct arizona_pdata { /** Force MICBIAS on for mic detect */ bool micd_force_micbias; + /** Mic detect level parameters */ + const struct arizona_micd_range *micd_ranges; + int num_micd_ranges; + /** Headset polarity configurations */ struct arizona_micd_config *micd_configs; int num_micd_configs; -- cgit v1.2.3 From 77ff4f95d77ddb14fe827e70d8b4be4a692790e9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:02:37 +0100 Subject: extcon: arizona: Don't pulse MICBIAS for HPDET identification There is no need to do this as HPDET identification will cause MICBIAS to be powered down again. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index e2339629126a..95748d3cbc4e 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -693,8 +693,6 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) info->hpdet_active = true; - arizona_extcon_pulse_micbias(info); - arizona_extcon_do_magic(info, 0x4000); ret = regmap_update_bits(arizona->regmap, -- cgit v1.2.3 From e56a0a572be150c79cdbf62ff98f4a63419e1c0b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:03:52 +0100 Subject: extcon: arizona: Allow pull to be disabled on GPIO5 when used for JACKET In some designs an external pull won't be needed. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 9 +++++++-- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 95748d3cbc4e..132bc99fdc06 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -986,6 +986,7 @@ 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, j; @@ -1172,9 +1173,13 @@ static int arizona_extcon_probe(struct platform_device *pdev) */ 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, diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index eb11a8ac6db2..008b8c40549f 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -122,6 +122,9 @@ struct arizona_pdata { /** GPIO5 is used for jack detection */ bool jd_gpio5; + /** Internal pull on GPIO5 is disabled when used for jack detection */ + bool jd_gpio5_nopull; + /** Use the headphone detect circuit to identify the accessory */ bool hpdet_acc_id; -- cgit v1.2.3 From 82e2e0fd3fcf2aec9d5796e31d7f29c738331f6b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:04:43 +0100 Subject: extcon: arizona: Raise minimum microphone impedance for HPDET method Ensure greater reliability by increasing the minimum threashold for identifying a microphone. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 132bc99fdc06..2f0bd4938fcc 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -519,7 +519,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; -- cgit v1.2.3 From a3e2078d6a14bc67e733f7dbd32d1bc4051c9d90 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:05:27 +0100 Subject: extcon: arizona: Suppress duplicate JACKDET reports In cases where we see a brief (dis)connection of the jack detection signals we may see a noop jack insertion or removal where the jack has returned to the original state by the time the interrupt is serviced. Suppress these events in order to save work and avoid confusing the rest of the code. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 2f0bd4938fcc..9e4bffe610b6 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -39,6 +39,8 @@ #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,6 +48,8 @@ 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; @@ -871,11 +875,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); @@ -896,7 +901,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); @@ -913,7 +929,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, @@ -953,6 +969,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); @@ -1012,6 +1029,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); -- cgit v1.2.3 From e2c0f476ec90dbb020b1da7e399072e062ad6c9e Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 1 Apr 2013 19:06:29 +0100 Subject: extcon: arizona: Check we report a valid impedance Occasionally we can trigger an interrupt before we have completed impedance measurement, although the valid bit will still be set. This patch spins reading the impedance value until a valid value is seen. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 9e4bffe610b6..4022fe207926 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -737,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; + 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; } -- cgit v1.2.3 From 2643fd641af28603ccd42244011a5ebc66016f8f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:07:28 +0100 Subject: extcon: arizona: Tune up HPDET debounce Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 4022fe207926..5344f435f689 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -39,7 +39,7 @@ #define ARIZONA_ACCDET_MODE_HPL 1 #define ARIZONA_ACCDET_MODE_HPR 2 -#define HPDET_DEBOUNCE 250 +#define HPDET_DEBOUNCE 500 struct arizona_extcon_info { struct device *dev; -- cgit v1.2.3 From 9dd5e53d9d2f933039eb2d5e4052afa249f638ba Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:09:45 +0100 Subject: extcon: arizona: Retry HPDET identification for high impedance Sometimes we can trigger measurements early if contacts are shorted during a slow insertion. As well as debouncing add further robustness by retrying if we get a high impedance measurement for headphones as this can indicate that the headphones were not yet connected. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 5344f435f689..c18cf14067c6 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -39,6 +39,8 @@ #define ARIZONA_ACCDET_MODE_HPL 1 #define ARIZONA_ACCDET_MODE_HPR 2 +#define ARIZONA_HPDET_MAX 10000 + #define HPDET_DEBOUNCE 500 struct arizona_extcon_info { @@ -64,6 +66,7 @@ struct arizona_extcon_info { bool hpdet_active; bool hpdet_done; + bool hpdet_retried; int num_hpdet_res; unsigned int hpdet_res[3]; @@ -112,6 +115,8 @@ static const char *arizona_cable[] = { NULL, }; +static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info); + static void arizona_extcon_do_magic(struct arizona_extcon_info *info, unsigned int magic) { @@ -393,7 +398,7 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) /* If we go out of range report top of range */ if (val < 100 || val > 0x3fb) { dev_dbg(arizona->dev, "Measurement out of range\n"); - return 10000; + return ARIZONA_HPDET_MAX; } dev_dbg(arizona->dev, "HPDET read %d in range %d\n", @@ -518,6 +523,16 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) /* Take the headphone impedance for the main report */ *reading = info->hpdet_res[0]; + /* Sometimes we get false readings due to slow insert */ + if (*reading >= ARIZONA_HPDET_MAX && !info->hpdet_retried) { + dev_dbg(arizona->dev, "Retrying high impedance\n"); + info->num_hpdet_res = 0; + info->hpdet_retried = true; + arizona_start_hpdet_acc_id(info); + pm_runtime_put(info->dev); + return -EAGAIN; + } + /* * Either the two grounds measure differently or we * measure the mic as high impedance. @@ -953,6 +968,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) info->hpdet_res[i] = 0; info->mic = false; info->hpdet_done = false; + info->hpdet_retried = false; for (i = 0; i < info->num_micd_ranges; i++) input_report_key(info->input, -- cgit v1.2.3 From db924ff5c7297cca85eb0faa79ea29e988f96420 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:10:28 +0100 Subject: extcon: arizona: Don't ground flip when using HPDET identification This extra check makes the procedure take longer and is of marginal use in identification so do not execute it. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index c18cf14067c6..7c4ce812d735 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -470,29 +470,7 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) */ if (arizona->pdata.hpdet_acc_id) { info->hpdet_res[info->num_hpdet_res++] = *reading; - - /* - * If the impedence is too high don't measure the - * second ground. - */ - if (info->num_hpdet_res == 1 && *reading >= 45) { - dev_dbg(arizona->dev, "Skipping ground flip\n"); - info->hpdet_res[info->num_hpdet_res++] = *reading; - } - - if (info->num_hpdet_res == 1) { - dev_dbg(arizona->dev, "Flipping ground\n"); - - regmap_update_bits(arizona->regmap, - ARIZONA_ACCESSORY_DETECT_MODE_1, - ARIZONA_ACCDET_SRC, - ~info->micd_modes[0].src); - - regmap_update_bits(arizona->regmap, - ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_POLL, ARIZONA_HP_POLL); - return -EAGAIN; - } + info->hpdet_res[info->num_hpdet_res++] = *reading; /* Only check the mic directly if we didn't already ID it */ if (id_gpio && info->num_hpdet_res == 2 && -- cgit v1.2.3 From 9c2ba270eaa227c999af451e1c2c9bf0d24aa8e5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 25 Feb 2013 23:42:31 +0000 Subject: extcon: arizona: Simplify HPDET based identification Rather than measuring both HP channels we can simply directly measure the microphone impedance and then rely on MICDET for final confirmation of the presence of a suitable microphone. This improves the overall performance of the identification process. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 46 +++++++++++++++++++++------------------ include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 28 insertions(+), 21 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 7c4ce812d735..a83ca27aa99f 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -459,7 +459,8 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) return val; } -static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) +static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading, + bool *mic) { struct arizona *arizona = info->arizona; int id_gpio = arizona->pdata.hpdet_id_gpio; @@ -470,11 +471,9 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) */ if (arizona->pdata.hpdet_acc_id) { info->hpdet_res[info->num_hpdet_res++] = *reading; - info->hpdet_res[info->num_hpdet_res++] = *reading; /* Only check the mic directly if we didn't already ID it */ - if (id_gpio && info->num_hpdet_res == 2 && - !((info->hpdet_res[0] > info->hpdet_res[1] * 2))) { + if (id_gpio && info->num_hpdet_res == 1) { dev_dbg(arizona->dev, "Measuring mic\n"); regmap_update_bits(arizona->regmap, @@ -493,10 +492,8 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) } /* OK, got both. Now, compare... */ - dev_dbg(arizona->dev, "HPDET measured %d %d %d\n", - info->hpdet_res[0], info->hpdet_res[1], - info->hpdet_res[2]); - + dev_dbg(arizona->dev, "HPDET measured %d %d\n", + info->hpdet_res[0], info->hpdet_res[1]); /* Take the headphone impedance for the main report */ *reading = info->hpdet_res[0]; @@ -512,13 +509,11 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) } /* - * Either the two grounds measure differently or we - * measure the mic as high impedance. + * If we measure the mic as */ - if ((info->hpdet_res[0] > info->hpdet_res[1] * 2) || - (id_gpio && info->hpdet_res[2] > 1257)) { + if (!id_gpio || info->hpdet_res[1] > 50) { dev_dbg(arizona->dev, "Detected mic\n"); - info->mic = true; + *mic = true; info->detecting = true; } else { dev_dbg(arizona->dev, "Detected headphone\n"); @@ -541,6 +536,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) int id_gpio = arizona->pdata.hpdet_id_gpio; int report = ARIZONA_CABLE_HEADPHONE; int ret, reading; + bool mic = false; mutex_lock(&info->lock); @@ -576,7 +572,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL, 0); - ret = arizona_hpdet_do_id(info, &reading); + ret = arizona_hpdet_do_id(info, &reading, &mic); if (ret == -EAGAIN) { goto out; } else if (ret < 0) { @@ -606,7 +602,7 @@ done: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* If we have a mic then reenable MICDET */ - if (info->mic) + if (mic || info->mic) arizona_start_mic(info); if (info->hpdet_active) { @@ -681,6 +677,8 @@ err: static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; + int hp_reading = 32; + bool mic; int ret; dev_dbg(arizona->dev, "Starting identification via HPDET\n"); @@ -702,12 +700,18 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) goto err; } - ret = regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_POLL, ARIZONA_HP_POLL); - if (ret != 0) { - dev_err(arizona->dev, "Can't start HPDETL measurement: %d\n", - ret); - goto err; + if (arizona->pdata.hpdet_acc_id_line) { + ret = regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + if (ret != 0) { + dev_err(arizona->dev, + "Can't start HPDETL measurement: %d\n", + ret); + goto err; + } + } else { + arizona_hpdet_do_id(info, &hp_reading, &mic); } return; diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 008b8c40549f..45c84777c624 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -128,6 +128,9 @@ struct arizona_pdata { /** Use the headphone detect circuit to identify the accessory */ bool hpdet_acc_id; + /** Check for line output with HPDET method */ + bool hpdet_acc_id_line; + /** GPIO used for mic isolation with HPDET */ int hpdet_id_gpio; -- cgit v1.2.3 From 939c5671d11d86ae783f416b703c705647ac563b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:17:34 +0100 Subject: extcon: arizona: Time out if MICDET fails to report In pathological cases the microphone detection may fail to report, for example due to a failure to get a stable measurement. Provide a timeout to cover such cases. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index a83ca27aa99f..e2d881a58ca6 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -42,6 +42,7 @@ #define ARIZONA_HPDET_MAX 10000 #define HPDET_DEBOUNCE 500 +#define MICD_TIMEOUT 2000 struct arizona_extcon_info { struct device *dev; @@ -63,6 +64,7 @@ struct arizona_extcon_info { bool micd_clamp; struct delayed_work hpdet_work; + struct delayed_work micd_timeout_work; bool hpdet_active; bool hpdet_done; @@ -730,6 +732,24 @@ err: info->hpdet_active = false; } +static void arizona_micd_timeout_work(struct work_struct *work) +{ + struct arizona_extcon_info *info = container_of(work, + struct arizona_extcon_info, + micd_timeout_work.work); + + mutex_lock(&info->lock); + + dev_dbg(info->arizona->dev, "MICD timed out, reporting HP\n"); + arizona_identify_headphone(info); + + info->detecting = false; + + arizona_stop_mic(info); + + mutex_unlock(&info->lock); +} + static irqreturn_t arizona_micdet(int irq, void *data) { struct arizona_extcon_info *info = data; @@ -737,6 +757,8 @@ static irqreturn_t arizona_micdet(int irq, void *data) unsigned int val = 0, lvl; int ret, i, key; + cancel_delayed_work_sync(&info->micd_timeout_work); + mutex_lock(&info->lock); for (i = 0; i < 10 && !(val & 0x7fc); i++) { @@ -858,6 +880,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) } handled: + if (info->detecting) + schedule_delayed_work(&info->micd_timeout_work, + msecs_to_jiffies(MICD_TIMEOUT)); + pm_runtime_mark_last_busy(info->dev); mutex_unlock(&info->lock); @@ -880,10 +906,11 @@ 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; + bool cancelled_hp, cancelled_mic; int ret, i; - cancelled = cancel_delayed_work_sync(&info->hpdet_work); + cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work); + cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work); pm_runtime_get_sync(info->dev); @@ -909,10 +936,14 @@ static irqreturn_t arizona_jackdet(int irq, void *data) val &= mask; if (val == info->last_jackdet) { dev_dbg(arizona->dev, "Suppressing duplicate JACKDET\n"); - if (cancelled) + if (cancelled_hp) schedule_delayed_work(&info->hpdet_work, msecs_to_jiffies(HPDET_DEBOUNCE)); + if (cancelled_mic) + schedule_delayed_work(&info->micd_timeout_work, + msecs_to_jiffies(MICD_TIMEOUT)); + goto out; } info->last_jackdet = val; @@ -1037,6 +1068,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) info->dev = &pdev->dev; info->last_jackdet = ~(ARIZONA_MICD_CLAMP_STS | ARIZONA_JD1_STS); INIT_DELAYED_WORK(&info->hpdet_work, arizona_hpdet_work); + INIT_DELAYED_WORK(&info->micd_timeout_work, arizona_micd_timeout_work); platform_set_drvdata(pdev, info); switch (arizona->type) { -- cgit v1.2.3 From 41a57850b5e5c450da351465efcc41383def7f8a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:18:18 +0100 Subject: extcon: arizona: Clear existing button reports before reporting new one If the user moves directly from one button to another then we won't get a no buttons pressed event and will therefore end up reporting that two buttons are simultaneously pressed which isn't supported by the hardware. Make sure we clear any existing button reports before reporting any new ones. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index e2d881a58ca6..26f9a1ae15c4 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -852,6 +852,10 @@ static irqreturn_t arizona_micdet(int irq, void *data) lvl = val & ARIZONA_MICD_LVL_MASK; lvl >>= ARIZONA_MICD_LVL_SHIFT; + for (i = 0; i < info->num_micd_ranges; i++) + input_report_key(info->input, + info->micd_ranges[i].key, 0); + WARN_ON(!lvl); WARN_ON(ffs(lvl) - 1 >= info->num_micd_ranges); if (lvl && ffs(lvl) - 1 < info->num_micd_ranges) { -- cgit v1.2.3 From cd59e79656f4e7137909166248a935d422b1245a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:21:48 +0100 Subject: extcon: arizona: Allow additional debounce during microphone detection Help mitigate against mechanical bounce during the initial detection by allowing the configuration of an additional debounce on top of that the hardware does during the initial phase of microphone detection operation. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 35 ++++++++++++++++++++++++++++++----- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 33 insertions(+), 5 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 26f9a1ae15c4..c7f8eb4299d2 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -64,6 +64,7 @@ struct arizona_extcon_info { bool micd_clamp; struct delayed_work hpdet_work; + struct delayed_work micd_detect_work; struct delayed_work micd_timeout_work; bool hpdet_active; @@ -750,9 +751,11 @@ static void arizona_micd_timeout_work(struct work_struct *work) mutex_unlock(&info->lock); } -static irqreturn_t arizona_micdet(int irq, void *data) +static void arizona_micd_detect(struct work_struct *work) { - struct arizona_extcon_info *info = data; + struct arizona_extcon_info *info = container_of(work, + struct arizona_extcon_info, + micd_detect_work.work); struct arizona *arizona = info->arizona; unsigned int val = 0, lvl; int ret, i, key; @@ -766,7 +769,7 @@ static irqreturn_t arizona_micdet(int irq, void *data) if (ret != 0) { dev_err(arizona->dev, "Failed to read MICDET: %d\n", ret); mutex_unlock(&info->lock); - return IRQ_NONE; + return; } dev_dbg(arizona->dev, "MICDET: %x\n", val); @@ -774,14 +777,14 @@ static irqreturn_t arizona_micdet(int irq, void *data) if (!(val & ARIZONA_MICD_VALID)) { dev_warn(arizona->dev, "Microphone detection state invalid\n"); mutex_unlock(&info->lock); - return IRQ_NONE; + return; } } if (i == 10 && !(val & 0x7fc)) { dev_err(arizona->dev, "Failed to get valid MICDET value\n"); mutex_unlock(&info->lock); - return IRQ_NONE; + return; } /* Due to jack detect this should never happen */ @@ -890,6 +893,27 @@ handled: pm_runtime_mark_last_busy(info->dev); mutex_unlock(&info->lock); +} + +static irqreturn_t arizona_micdet(int irq, void *data) +{ + struct arizona_extcon_info *info = data; + struct arizona *arizona = info->arizona; + int debounce = arizona->pdata.micd_detect_debounce; + + cancel_delayed_work_sync(&info->micd_detect_work); + cancel_delayed_work_sync(&info->micd_timeout_work); + + mutex_lock(&info->lock); + if (!info->detecting) + debounce = 0; + mutex_unlock(&info->lock); + + if (debounce) + schedule_delayed_work(&info->micd_detect_work, + msecs_to_jiffies(debounce)); + else + arizona_micd_detect(&info->micd_detect_work.work); return IRQ_HANDLED; } @@ -1072,6 +1096,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) info->dev = &pdev->dev; info->last_jackdet = ~(ARIZONA_MICD_CLAMP_STS | ARIZONA_JD1_STS); INIT_DELAYED_WORK(&info->hpdet_work, arizona_hpdet_work); + INIT_DELAYED_WORK(&info->micd_detect_work, arizona_micd_detect); INIT_DELAYED_WORK(&info->micd_timeout_work, arizona_micd_timeout_work); platform_set_drvdata(pdev, info); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 45c84777c624..3ef300baa2e6 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -134,6 +134,9 @@ struct arizona_pdata { /** GPIO used for mic isolation with HPDET */ int hpdet_id_gpio; + /** Extra debounce timeout used during initial mic detection (ms) */ + int micd_detect_debounce; + /** GPIO for mic detection polarity */ int micd_pol_gpio; -- cgit v1.2.3 From 7abd4e2a8f1c3e534da44c35e2d3d6353573e51f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 1 Apr 2013 19:25:55 +0100 Subject: extcon: arizona: Make mic detection timeout configurable Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 13 ++++++++++--- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'drivers/extcon') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index c7f8eb4299d2..7a1b4a7791ba 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -42,7 +42,7 @@ #define ARIZONA_HPDET_MAX 10000 #define HPDET_DEBOUNCE 500 -#define MICD_TIMEOUT 2000 +#define DEFAULT_MICD_TIMEOUT 2000 struct arizona_extcon_info { struct device *dev; @@ -60,6 +60,8 @@ struct arizona_extcon_info { const struct arizona_micd_range *micd_ranges; int num_micd_ranges; + int micd_timeout; + bool micd_reva; bool micd_clamp; @@ -889,7 +891,7 @@ static void arizona_micd_detect(struct work_struct *work) handled: if (info->detecting) schedule_delayed_work(&info->micd_timeout_work, - msecs_to_jiffies(MICD_TIMEOUT)); + msecs_to_jiffies(info->micd_timeout)); pm_runtime_mark_last_busy(info->dev); mutex_unlock(&info->lock); @@ -970,7 +972,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) if (cancelled_mic) schedule_delayed_work(&info->micd_timeout_work, - msecs_to_jiffies(MICD_TIMEOUT)); + msecs_to_jiffies(info->micd_timeout)); goto out; } @@ -1027,6 +1029,11 @@ static irqreturn_t arizona_jackdet(int irq, void *data) ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); } + if (arizona->pdata.micd_timeout) + info->micd_timeout = arizona->pdata.micd_timeout; + else + info->micd_timeout = DEFAULT_MICD_TIMEOUT; + /* Clear trig_sts to make sure DCVDD is not forced up */ regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, ARIZONA_MICD_CLAMP_FALL_TRIG_STS | diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 3ef300baa2e6..a0f940987a3e 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -149,6 +149,9 @@ struct arizona_pdata { /** Mic detect debounce level */ int micd_dbtime; + /** Mic detect timeout (ms) */ + int micd_timeout; + /** Force MICBIAS on for mic detect */ bool micd_force_micbias; -- cgit v1.2.3