diff options
Diffstat (limited to 'drivers/extcon')
-rw-r--r-- | drivers/extcon/extcon-arizona.c | 391 |
1 files changed, 287 insertions, 104 deletions
diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index b2892797212..7a1b4a7791b 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -33,12 +33,17 @@ #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 ARIZONA_HPDET_MAX 10000 + +#define HPDET_DEBOUNCE 500 +#define DEFAULT_MICD_TIMEOUT 2000 + struct arizona_extcon_info { struct device *dev; struct arizona *arizona; @@ -46,17 +51,27 @@ 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; + + int micd_timeout; + bool micd_reva; bool micd_clamp; struct delayed_work hpdet_work; + struct delayed_work micd_detect_work; + struct delayed_work micd_timeout_work; bool hpdet_active; bool hpdet_done; + bool hpdet_retried; int num_hpdet_res; unsigned int hpdet_res[3]; @@ -71,20 +86,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,6 +120,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) { @@ -153,6 +175,8 @@ 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); @@ -379,7 +403,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", @@ -440,7 +464,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; @@ -452,32 +477,8 @@ 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; - } - /* 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, @@ -496,22 +497,28 @@ 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]; + /* 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. + * If we measure the mic as */ - if ((info->hpdet_res[0] > info->hpdet_res[1] * 2) || - (id_gpio && info->hpdet_res[2] > 10)) { + 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"); @@ -534,6 +541,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); @@ -569,7 +577,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) { @@ -599,7 +607,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) { @@ -674,6 +682,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"); @@ -683,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, @@ -697,12 +705,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; @@ -721,28 +735,58 @@ err: info->hpdet_active = false; } -static irqreturn_t arizona_micdet(int irq, void *data) +static void arizona_micd_timeout_work(struct work_struct *work) { - struct arizona_extcon_info *info = data; + 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 void arizona_micd_detect(struct work_struct *work) +{ + struct arizona_extcon_info *info = container_of(work, + struct arizona_extcon_info, + micd_detect_work.work); struct arizona *arizona = info->arizona; - unsigned int val, lvl; - int ret, i; + unsigned int val = 0, lvl; + int ret, i, key; + + cancel_delayed_work_sync(&info->micd_timeout_work); 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; + } - 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"); + if (!(val & ARIZONA_MICD_VALID)) { + dev_warn(arizona->dev, "Microphone detection state invalid\n"); + mutex_unlock(&info->lock); + 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 */ @@ -783,7 +827,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); @@ -813,12 +857,17 @@ 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); + 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) { + 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"); @@ -832,16 +881,41 @@ 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); } handled: + if (info->detecting) + schedule_delayed_work(&info->micd_timeout_work, + msecs_to_jiffies(info->micd_timeout)); + 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; } @@ -862,11 +936,13 @@ 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_hp, cancelled_mic; int ret, i; - pm_runtime_get_sync(info->dev); + cancelled_hp = cancel_delayed_work_sync(&info->hpdet_work); + cancelled_mic = cancel_delayed_work_sync(&info->micd_timeout_work); - cancel_delayed_work_sync(&info->hpdet_work); + pm_runtime_get_sync(info->dev); mutex_lock(&info->lock); @@ -887,7 +963,22 @@ 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_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(info->micd_timeout)); + + 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); @@ -904,7 +995,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, @@ -920,10 +1011,11 @@ 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 < 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); @@ -937,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 | @@ -944,6 +1041,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); @@ -952,13 +1050,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; @@ -982,7 +1101,10 @@ 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); + 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); switch (arizona->type) { @@ -1011,6 +1133,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; @@ -1066,15 +1199,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, @@ -1093,20 +1290,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); |