#include <linux/regulator/consumer.h>
#include <linux/extcon.h>
+#include <sound/soc.h>
+
#include <linux/mfd/arizona/core.h>
#include <linux/mfd/arizona/pdata.h>
#include <linux/mfd/arizona/registers.h>
-#define ARIZONA_DEFAULT_HP 32
-
#define ARIZONA_NUM_BUTTONS 6
#define ARIZONA_ACCDET_MODE_MIC 0
bool micd_reva;
bool micd_clamp;
+ struct delayed_work hpdet_work;
+
bool hpdet_active;
+ bool hpdet_done;
+
+ int num_hpdet_res;
+ unsigned int hpdet_res[3];
bool mic;
bool detecting;
};
static const struct arizona_micd_config micd_default_modes[] = {
- { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 },
{ 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 },
+ { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 },
};
static struct {
dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode);
}
+static const char *arizona_extcon_get_micbias(struct arizona_extcon_info *info)
+{
+ switch (info->micd_modes[0].bias >> ARIZONA_MICD_BIAS_SRC_SHIFT) {
+ case 1:
+ return "MICBIAS1";
+ case 2:
+ return "MICBIAS2";
+ case 3:
+ return "MICBIAS3";
+ default:
+ return "MICVDD";
+ }
+}
+
+static void arizona_extcon_pulse_micbias(struct arizona_extcon_info *info)
+{
+ struct arizona *arizona = info->arizona;
+ const char *widget = arizona_extcon_get_micbias(info);
+ struct snd_soc_dapm_context *dapm = arizona->dapm;
+ int ret;
+
+ mutex_lock(&dapm->card->dapm_mutex);
+
+ ret = snd_soc_dapm_force_enable_pin(dapm, widget);
+ if (ret != 0)
+ dev_warn(arizona->dev, "Failed to enable %s: %d\n",
+ widget, ret);
+
+ mutex_unlock(&dapm->card->dapm_mutex);
+
+ snd_soc_dapm_sync(dapm);
+
+ if (!arizona->pdata.micd_force_micbias) {
+ mutex_lock(&dapm->card->dapm_mutex);
+
+ ret = snd_soc_dapm_disable_pin(arizona->dapm, widget);
+ if (ret != 0)
+ dev_warn(arizona->dev, "Failed to disable %s: %d\n",
+ widget, ret);
+
+ mutex_unlock(&dapm->card->dapm_mutex);
+
+ snd_soc_dapm_sync(dapm);
+ }
+}
+
+static void arizona_start_mic(struct arizona_extcon_info *info)
+{
+ struct arizona *arizona = info->arizona;
+ bool change;
+ int ret;
+
+ /* Microphone detection can't use idle mode */
+ pm_runtime_get(info->dev);
+
+ if (info->detecting) {
+ ret = regulator_allow_bypass(info->micvdd, false);
+ if (ret != 0) {
+ dev_err(arizona->dev,
+ "Failed to regulate MICVDD: %d\n",
+ ret);
+ }
+ }
+
+ ret = regulator_enable(info->micvdd);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to enable MICVDD: %d\n",
+ ret);
+ }
+
+ if (info->micd_reva) {
+ regmap_write(arizona->regmap, 0x80, 0x3);
+ regmap_write(arizona->regmap, 0x294, 0);
+ regmap_write(arizona->regmap, 0x80, 0x0);
+ }
+
+ regmap_update_bits(arizona->regmap,
+ ARIZONA_ACCESSORY_DETECT_MODE_1,
+ ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC);
+
+ arizona_extcon_pulse_micbias(info);
+
+ regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
+ ARIZONA_MICD_ENA, ARIZONA_MICD_ENA,
+ &change);
+ if (!change) {
+ regulator_disable(info->micvdd);
+ pm_runtime_put_autosuspend(info->dev);
+ }
+}
+
+static void arizona_stop_mic(struct arizona_extcon_info *info)
+{
+ struct arizona *arizona = info->arizona;
+ const char *widget = arizona_extcon_get_micbias(info);
+ struct snd_soc_dapm_context *dapm = arizona->dapm;
+ bool change;
+ int ret;
+
+ regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
+ ARIZONA_MICD_ENA, 0,
+ &change);
+
+ mutex_lock(&dapm->card->dapm_mutex);
+
+ ret = snd_soc_dapm_disable_pin(dapm, widget);
+ if (ret != 0)
+ dev_warn(arizona->dev,
+ "Failed to disable %s: %d\n",
+ widget, ret);
+
+ mutex_unlock(&dapm->card->dapm_mutex);
+
+ snd_soc_dapm_sync(dapm);
+
+ if (info->micd_reva) {
+ regmap_write(arizona->regmap, 0x80, 0x3);
+ regmap_write(arizona->regmap, 0x294, 2);
+ regmap_write(arizona->regmap, 0x80, 0x0);
+ }
+
+ ret = regulator_allow_bypass(info->micvdd, true);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to bypass MICVDD: %d\n",
+ ret);
+ }
+
+ if (change) {
+ regulator_disable(info->micvdd);
+ pm_runtime_mark_last_busy(info->dev);
+ pm_runtime_put_autosuspend(info->dev);
+ }
+}
+
static struct {
unsigned int factor_a;
unsigned int factor_b;
if (!(val & ARIZONA_HP_DONE)) {
dev_err(arizona->dev, "HPDET did not complete: %x\n",
val);
- val = ARIZONA_DEFAULT_HP;
+ return -EAGAIN;
}
val &= ARIZONA_HP_LVL_MASK;
if (!(val & ARIZONA_HP_DONE_B)) {
dev_err(arizona->dev, "HPDET did not complete: %x\n",
val);
- return ARIZONA_DEFAULT_HP;
+ return -EAGAIN;
}
ret = regmap_read(arizona->regmap, ARIZONA_HP_DACVAL, &val);
if (ret != 0) {
dev_err(arizona->dev, "Failed to read HP value: %d\n",
ret);
- return ARIZONA_DEFAULT_HP;
+ return -EAGAIN;
}
regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1,
if (!(val & ARIZONA_HP_DONE_B)) {
dev_err(arizona->dev, "HPDET did not complete: %x\n",
val);
- return ARIZONA_DEFAULT_HP;
+ return -EAGAIN;
}
val &= ARIZONA_HP_LVL_B_MASK;
return val;
}
+static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading)
+{
+ struct arizona *arizona = info->arizona;
+ int id_gpio = arizona->pdata.hpdet_id_gpio;
+
+ /*
+ * If we're using HPDET for accessory identification we need
+ * to take multiple measurements, step through them in sequence.
+ */
+ 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))) {
+ dev_dbg(arizona->dev, "Measuring mic\n");
+
+ regmap_update_bits(arizona->regmap,
+ ARIZONA_ACCESSORY_DETECT_MODE_1,
+ ARIZONA_ACCDET_MODE_MASK |
+ ARIZONA_ACCDET_SRC,
+ ARIZONA_ACCDET_MODE_HPR |
+ info->micd_modes[0].src);
+
+ gpio_set_value_cansleep(id_gpio, 1);
+
+ regmap_update_bits(arizona->regmap,
+ ARIZONA_HEADPHONE_DETECT_1,
+ ARIZONA_HP_POLL, ARIZONA_HP_POLL);
+ return -EAGAIN;
+ }
+
+ /* 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]);
+
+
+ /* Take the headphone impedance for the main report */
+ *reading = info->hpdet_res[0];
+
+ /*
+ * Either the two grounds measure differently or we
+ * measure the mic as high impedance.
+ */
+ if ((info->hpdet_res[0] > info->hpdet_res[1] * 2) ||
+ (id_gpio && info->hpdet_res[2] > 10)) {
+ dev_dbg(arizona->dev, "Detected mic\n");
+ info->mic = true;
+ info->detecting = true;
+ } else {
+ dev_dbg(arizona->dev, "Detected headphone\n");
+ }
+
+ /* Make sure everything is reset back to the real polarity */
+ regmap_update_bits(arizona->regmap,
+ ARIZONA_ACCESSORY_DETECT_MODE_1,
+ ARIZONA_ACCDET_SRC,
+ info->micd_modes[0].src);
+ }
+
+ return 0;
+}
+
static irqreturn_t arizona_hpdet_irq(int irq, void *data)
{
struct arizona_extcon_info *info = data;
struct arizona *arizona = info->arizona;
+ int id_gpio = arizona->pdata.hpdet_id_gpio;
int report = ARIZONA_CABLE_HEADPHONE;
- int ret;
+ unsigned int val;
+ int ret, reading;
mutex_lock(&info->lock);
} else if (ret < 0) {
goto done;
}
+ reading = ret;
/* Reset back to starting range */
regmap_update_bits(arizona->regmap,
ARIZONA_HEADPHONE_DETECT_1,
- ARIZONA_HP_IMPEDANCE_RANGE_MASK, 0);
+ ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL,
+ 0);
+
+ ret = arizona_hpdet_do_id(info, &reading);
+ if (ret == -EAGAIN) {
+ goto out;
+ } else if (ret < 0) {
+ goto done;
+ }
/* Report high impedence cables as line outputs */
- if (ret >= 5000)
+ if (reading >= 5000)
report = ARIZONA_CABLE_LINEOUT;
else
report = ARIZONA_CABLE_HEADPHONE;
dev_err(arizona->dev, "Failed to report HP/line: %d\n",
ret);
- ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0);
- if (ret != 0)
- dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret);
+ mutex_lock(&arizona->dapm->card->dapm_mutex);
- ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0);
- if (ret != 0)
- dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret);
+ 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);
done:
- regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1,
- ARIZONA_HP_POLL, 0);
+ if (id_gpio)
+ gpio_set_value_cansleep(id_gpio, 0);
/* Revert back to MICDET mode */
regmap_update_bits(arizona->regmap,
info->hpdet_active = false;
}
+ info->hpdet_done = true;
+
out:
mutex_unlock(&info->lock);
struct arizona *arizona = info->arizona;
int ret;
+ if (info->hpdet_done)
+ return;
+
dev_dbg(arizona->dev, "Starting HPDET\n");
/* Make sure we keep the device enabled during the measurement */
info->hpdet_active = false;
}
- }
- info->hpdet_active = false;
-}
-
-static void arizona_start_mic(struct arizona_extcon_info *info)
+static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info)
{
struct arizona *arizona = info->arizona;
- bool change;
+ unsigned int val;
int ret;
- info->detecting = true;
- info->mic = false;
- info->jack_flips = 0;
+ dev_dbg(arizona->dev, "Starting identification via HPDET\n");
- /* Microphone detection can't use idle mode */
- pm_runtime_get(info->dev);
+ /* Make sure we keep the device enabled during the measurement */
+ pm_runtime_get_sync(info->dev);
- ret = regulator_enable(info->micvdd);
+ 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 enable MICVDD: %d\n",
+ dev_err(arizona->dev, "Failed to read output enables: %d\n",
ret);
+ val = 0;
}
- if (info->micd_reva) {
- regmap_write(arizona->regmap, 0x80, 0x3);
- regmap_write(arizona->regmap, 0x294, 0);
- regmap_write(arizona->regmap, 0x80, 0x0);
+ 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);
}
- regmap_update_bits(arizona->regmap,
- ARIZONA_ACCESSORY_DETECT_MODE_1,
- ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC);
+ mutex_unlock(&arizona->dapm->card->dapm_mutex);
- regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
- ARIZONA_MICD_ENA, ARIZONA_MICD_ENA,
- &change);
- if (!change) {
- regulator_disable(info->micvdd);
- pm_runtime_put_autosuspend(info->dev);
+ ret = regmap_update_bits(arizona->regmap,
+ ARIZONA_ACCESSORY_DETECT_MODE_1,
+ ARIZONA_ACCDET_SRC | ARIZONA_ACCDET_MODE_MASK,
+ info->micd_modes[0].src |
+ ARIZONA_ACCDET_MODE_HPL);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret);
+ goto err;
}
-}
-static void arizona_stop_mic(struct arizona_extcon_info *info)
-{
- struct arizona *arizona = info->arizona;
- bool change;
+ 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;
+ }
- regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1,
- ARIZONA_MICD_ENA, 0,
- &change);
+ return;
- if (info->micd_reva) {
- regmap_write(arizona->regmap, 0x80, 0x3);
- regmap_write(arizona->regmap, 0x294, 2);
- regmap_write(arizona->regmap, 0x80, 0x0);
- }
+err:
+ regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1,
+ ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC);
- if (change) {
- regulator_disable(info->micvdd);
- pm_runtime_mark_last_busy(info->dev);
- pm_runtime_put_autosuspend(info->dev);
- }
+ /* Just report headphone */
+ ret = extcon_update_state(&info->edev,
+ 1 << ARIZONA_CABLE_HEADPHONE,
+ 1 << ARIZONA_CABLE_HEADPHONE);
+ if (ret != 0)
+ dev_err(arizona->dev, "Failed to report headphone: %d\n", ret);
+
+ info->hpdet_active = false;
}
static irqreturn_t arizona_micdet(int irq, void *data)
dev_err(arizona->dev, "Headset report failed: %d\n",
ret);
+ /* Don't need to regulate for button detection */
+ ret = regulator_allow_bypass(info->micvdd, false);
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to bypass MICVDD: %d\n",
+ ret);
+ }
+
info->mic = true;
info->detecting = false;
goto handled;
* impedence then give up and report headphones.
*/
if (info->detecting && (val & 0x3f8)) {
- info->jack_flips++;
-
if (info->jack_flips >= info->micd_num_modes) {
dev_dbg(arizona->dev, "Detected HP/line\n");
arizona_identify_headphone(info);
input_report_key(info->input,
arizona_lvl_to_key[i].report, 0);
input_sync(info->input);
+ arizona_extcon_pulse_micbias(info);
}
handled:
return IRQ_HANDLED;
}
+static void arizona_hpdet_work(struct work_struct *work)
+{
+ struct arizona_extcon_info *info = container_of(work,
+ struct arizona_extcon_info,
+ hpdet_work.work);
+
+ mutex_lock(&info->lock);
+ arizona_start_hpdet_acc_id(info);
+ mutex_unlock(&info->lock);
+}
+
static irqreturn_t arizona_jackdet(int irq, void *data)
{
struct arizona_extcon_info *info = data;
pm_runtime_get_sync(info->dev);
+ cancel_delayed_work_sync(&info->hpdet_work);
+
mutex_lock(&info->lock);
if (arizona->pdata.jd_gpio5) {
dev_err(arizona->dev, "Mechanical report failed: %d\n",
ret);
- arizona_start_mic(info);
+ if (!arizona->pdata.hpdet_acc_id) {
+ info->detecting = true;
+ info->mic = false;
+ info->jack_flips = 0;
+
+ arizona_start_mic(info);
+ } else {
+ schedule_delayed_work(&info->hpdet_work,
+ msecs_to_jiffies(250));
+ }
+
+ regmap_update_bits(arizona->regmap,
+ ARIZONA_JACK_DETECT_DEBOUNCE,
+ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, 0);
} else {
dev_dbg(arizona->dev, "Detected jack removal\n");
arizona_stop_mic(info);
+ info->num_hpdet_res = 0;
+ for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++)
+ info->hpdet_res[i] = 0;
+ info->mic = false;
+ info->hpdet_done = false;
for (i = 0; i < ARIZONA_NUM_BUTTONS; i++)
input_report_key(info->input,
if (ret != 0)
dev_err(arizona->dev, "Removal report failed: %d\n",
ret);
+
+ regmap_update_bits(arizona->regmap,
+ ARIZONA_JACK_DETECT_DEBOUNCE,
+ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB,
+ ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB);
}
+ /* 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 |
+ ARIZONA_MICD_CLAMP_RISE_TRIG_STS |
+ ARIZONA_JD1_FALL_TRIG_STS |
+ ARIZONA_JD1_RISE_TRIG_STS);
+
mutex_unlock(&info->lock);
pm_runtime_mark_last_busy(info->dev);
int jack_irq_fall, jack_irq_rise;
int ret, mode, i;
+ if (!arizona->dapm || !arizona->dapm->card)
+ return -EPROBE_DEFER;
+
pdata = dev_get_platdata(arizona->dev);
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
mutex_init(&info->lock);
info->arizona = arizona;
info->dev = &pdev->dev;
- info->detecting = true;
+ INIT_DELAYED_WORK(&info->hpdet_work, arizona_hpdet_work);
platform_set_drvdata(pdev, info);
switch (arizona->type) {
}
}
+ if (arizona->pdata.hpdet_id_gpio > 0) {
+ ret = devm_gpio_request_one(&pdev->dev,
+ arizona->pdata.hpdet_id_gpio,
+ GPIOF_OUT_INIT_LOW,
+ "HPDET");
+ if (ret != 0) {
+ dev_err(arizona->dev, "Failed to request GPIO%d: %d\n",
+ arizona->pdata.hpdet_id_gpio, ret);
+ goto err_register;
+ }
+ }
+
if (arizona->pdata.micd_bias_start_time)
regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
ARIZONA_MICD_BIAS_STARTTIME_MASK,
arizona->pdata.micd_bias_start_time
<< ARIZONA_MICD_BIAS_STARTTIME_SHIFT);
+ if (arizona->pdata.micd_rate)
+ regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
+ ARIZONA_MICD_RATE_MASK,
+ arizona->pdata.micd_rate
+ << ARIZONA_MICD_RATE_SHIFT);
+
+ if (arizona->pdata.micd_dbtime)
+ regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
+ ARIZONA_MICD_DBTIME_MASK,
+ arizona->pdata.micd_dbtime
+ << ARIZONA_MICD_DBTIME_SHIFT);
+
/*
* If we have a clamp use it, activating in conjunction with
* GPIO5 if that is connected for jack detect operation.
goto err_micdet;
}
- regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1,
- ARIZONA_MICD_RATE_MASK,
- 8 << ARIZONA_MICD_RATE_SHIFT);
-
arizona_clk32k_enable(arizona);
regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE,
ARIZONA_JD1_DB, ARIZONA_JD1_DB);
arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info);
arizona_free_irq(arizona, jack_irq_rise, info);
arizona_free_irq(arizona, jack_irq_fall, info);
+ cancel_delayed_work_sync(&info->hpdet_work);
regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE,
ARIZONA_JD1_ENA, 0);
arizona_clk32k_disable(arizona);