Merge changes Iccd3819c,If5856ead into msm-3.0
* changes:
ASoC: msm8960: add headphone over current protection type
ASoC: wcd9310: add headphone over current protection feature
diff --git a/include/linux/mfd/wcd9310/pdata.h b/include/linux/mfd/wcd9310/pdata.h
index 9ca6a8c..af801f0 100644
--- a/include/linux/mfd/wcd9310/pdata.h
+++ b/include/linux/mfd/wcd9310/pdata.h
@@ -27,6 +27,30 @@
#define MAX_AMIC_CHANNEL 7
+#define TABLA_OCP_300_MA 0x0
+#define TABLA_OCP_350_MA 0x2
+#define TABLA_OCP_365_MA 0x3
+#define TABLA_OCP_150_MA 0x4
+#define TABLA_OCP_190_MA 0x6
+#define TABLA_OCP_220_MA 0x7
+
+#define TABLA_DCYCLE_255 0x0
+#define TABLA_DCYCLE_511 0x1
+#define TABLA_DCYCLE_767 0x2
+#define TABLA_DCYCLE_1023 0x3
+#define TABLA_DCYCLE_1279 0x4
+#define TABLA_DCYCLE_1535 0x5
+#define TABLA_DCYCLE_1791 0x6
+#define TABLA_DCYCLE_2047 0x7
+#define TABLA_DCYCLE_2303 0x8
+#define TABLA_DCYCLE_2559 0x9
+#define TABLA_DCYCLE_2815 0xA
+#define TABLA_DCYCLE_3071 0xB
+#define TABLA_DCYCLE_3327 0xC
+#define TABLA_DCYCLE_3583 0xD
+#define TABLA_DCYCLE_3839 0xE
+#define TABLA_DCYCLE_4095 0xF
+
struct tabla_amic {
/*legacy mode, txfe_enable and txfe_buff take 7 input
* each bit represent the channel / TXFE number
@@ -62,6 +86,14 @@
u8 bias4_cfilt_sel;
};
+struct tabla_ocp_setting {
+ unsigned int use_pdata:1; /* 0 - use sys default as recommended */
+ unsigned int num_attempts:4; /* up to 15 attempts */
+ unsigned int run_time:4; /* in duty cycle */
+ unsigned int wait_time:4; /* in duty cycle */
+ unsigned int hph_ocp_limit:3; /* Headphone OCP current limit */
+};
+
struct tabla_pdata {
int irq;
int irq_base;
@@ -70,6 +102,7 @@
struct tabla_amic amic_settings;
struct slim_device slimbus_slave_device;
struct tabla_micbias_setting micbias;
+ struct tabla_ocp_setting ocp;
};
#endif
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index ad8190c..7f7bdc9 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -37,6 +37,8 @@
#define TABLA_RX_DAI_ID 1
#define TABLA_TX_DAI_ID 2
+#define TABLA_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR)
+
static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0);
static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1);
static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 25, 1);
@@ -84,6 +86,14 @@
struct mbhc_micbias_regs mbhc_bias_regs;
u8 cfilt_k_value;
bool mbhc_micbias_switched;
+
+ u32 hph_status; /* track headhpone status */
+ /* define separate work for left and right headphone OCP to avoid
+ * additional checking on which OCP event to report so no locking
+ * to ensure synchronization is required
+ */
+ struct work_struct hphlocp_work; /* reporting left hph ocp off */
+ struct work_struct hphrocp_work; /* reporting right hph ocp off */
};
#ifdef CONFIG_DEBUG_FS
@@ -1285,6 +1295,42 @@
return 0;
}
+static void hphocp_off_report(struct tabla_priv *tabla,
+ u32 jack_status, int irq)
+{
+ struct snd_soc_codec *codec;
+
+ if (tabla) {
+ pr_info("%s: clear ocp status %x\n", __func__, jack_status);
+ codec = tabla->codec;
+ tabla->hph_status &= ~jack_status;
+ if (tabla->headset_jack)
+ snd_soc_jack_report(tabla->headset_jack,
+ tabla->hph_status, TABLA_JACK_MASK);
+ snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10,
+ 0x00);
+ snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10,
+ 0x10);
+ tabla_enable_irq(codec->control_data, irq);
+ } else {
+ pr_err("%s: Bad tabla private data\n", __func__);
+ }
+}
+
+static void hphlocp_off_report(struct work_struct *work)
+{
+ struct tabla_priv *tabla = container_of(work, struct tabla_priv,
+ hphlocp_work);
+ hphocp_off_report(tabla, SND_JACK_OC_HPHL, TABLA_IRQ_HPH_PA_OCPL_FAULT);
+}
+
+static void hphrocp_off_report(struct work_struct *work)
+{
+ struct tabla_priv *tabla = container_of(work, struct tabla_priv,
+ hphrocp_work);
+ hphocp_off_report(tabla, SND_JACK_OC_HPHR, TABLA_IRQ_HPH_PA_OCPR_FAULT);
+}
+
static int tabla_hph_pa_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@@ -1305,7 +1351,17 @@
break;
case SND_SOC_DAPM_POST_PMD:
-
+ /* schedule work is required because at the time HPH PA DAPM
+ * event callback is called by DAPM framework, CODEC dapm mutex
+ * would have been locked while snd_soc_jack_report also
+ * attempts to acquire same lock.
+ */
+ if ((tabla->hph_status & SND_JACK_OC_HPHL) &&
+ strnstr(w->name, "HPHL", 4))
+ schedule_work(&tabla->hphlocp_work);
+ else if ((tabla->hph_status & SND_JACK_OC_HPHR) &&
+ strnstr(w->name, "HPHR", 4))
+ schedule_work(&tabla->hphrocp_work);
if (tabla->mbhc_micbias_switched)
tabla_codec_switch_micbias(codec, 0);
@@ -2495,6 +2551,8 @@
struct tabla_mbhc_calibration *calibration)
{
struct tabla_priv *tabla;
+ int rc;
+
if (!codec || !calibration) {
pr_err("Error: no codec or calibration\n");
return -EINVAL;
@@ -2506,7 +2564,20 @@
tabla_get_mbhc_micbias_regs(codec, &tabla->mbhc_bias_regs);
INIT_DELAYED_WORK(&tabla->btn0_dwork, btn0_lpress_fn);
- return tabla_codec_enable_hs_detect(codec, 1);
+ INIT_WORK(&tabla->hphlocp_work, hphlocp_off_report);
+ INIT_WORK(&tabla->hphrocp_work, hphrocp_off_report);
+ rc = tabla_codec_enable_hs_detect(codec, 1);
+
+ if (!IS_ERR_VALUE(rc)) {
+ snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL, 0x10,
+ 0x10);
+ tabla_enable_irq(codec->control_data,
+ TABLA_IRQ_HPH_PA_OCPL_FAULT);
+ tabla_enable_irq(codec->control_data,
+ TABLA_IRQ_HPH_PA_OCPR_FAULT);
+ }
+
+ return rc;
}
EXPORT_SYMBOL_GPL(tabla_hs_detect);
@@ -2636,6 +2707,52 @@
tabla->mbhc_polling_active = false;
}
+static irqreturn_t tabla_hphl_ocp_irq(int irq, void *data)
+{
+ struct tabla_priv *tabla = data;
+ struct snd_soc_codec *codec;
+
+ pr_info("%s: received HPHL OCP irq\n", __func__);
+
+ if (tabla) {
+ codec = tabla->codec;
+ tabla_disable_irq(codec->control_data,
+ TABLA_IRQ_HPH_PA_OCPL_FAULT);
+ tabla->hph_status |= SND_JACK_OC_HPHL;
+ if (tabla->headset_jack) {
+ snd_soc_jack_report(tabla->headset_jack,
+ tabla->hph_status, TABLA_JACK_MASK);
+ }
+ } else {
+ pr_err("%s: Bad tabla private data\n", __func__);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t tabla_hphr_ocp_irq(int irq, void *data)
+{
+ struct tabla_priv *tabla = data;
+ struct snd_soc_codec *codec;
+
+ pr_info("%s: received HPHR OCP irq\n", __func__);
+
+ if (tabla) {
+ codec = tabla->codec;
+ tabla_disable_irq(codec->control_data,
+ TABLA_IRQ_HPH_PA_OCPR_FAULT);
+ tabla->hph_status |= SND_JACK_OC_HPHR;
+ if (tabla->headset_jack) {
+ snd_soc_jack_report(tabla->headset_jack,
+ tabla->hph_status, TABLA_JACK_MASK);
+ }
+ } else {
+ pr_err("%s: Bad tabla private data\n", __func__);
+ }
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t tabla_hs_insert_irq(int irq, void *data)
{
struct tabla_priv *priv = data;
@@ -2683,10 +2800,11 @@
*/
if (priv->mbhc_micbias_switched)
tabla_codec_switch_micbias(codec, 0);
+ priv->hph_status &= ~SND_JACK_HEADSET;
if (priv->headset_jack) {
pr_debug("%s: Reporting removal\n", __func__);
- snd_soc_jack_report(priv->headset_jack, 0,
- SND_JACK_HEADSET);
+ snd_soc_jack_report(priv->headset_jack,
+ priv->hph_status, TABLA_JACK_MASK);
}
tabla_codec_shutdown_hs_removal_detect(codec);
tabla_codec_enable_hs_detect(codec, 1);
@@ -2702,12 +2820,12 @@
} else if (mic_voltage < threshold_no_mic) {
pr_debug("%s: Headphone Detected, mic_voltage = %x\n",
__func__, mic_voltage);
-
+ priv->hph_status |= SND_JACK_HEADPHONE;
if (priv->headset_jack) {
pr_debug("%s: Reporting insertion %d\n", __func__,
SND_JACK_HEADPHONE);
snd_soc_jack_report(priv->headset_jack,
- SND_JACK_HEADPHONE, SND_JACK_HEADSET);
+ priv->hph_status, TABLA_JACK_MASK);
}
tabla_codec_shutdown_hs_polling(codec);
tabla_codec_enable_hs_detect(codec, 0);
@@ -2715,11 +2833,12 @@
} else {
pr_debug("%s: Headset detected, mic_voltage = %x\n",
__func__, mic_voltage);
+ priv->hph_status |= SND_JACK_HEADSET;
if (priv->headset_jack) {
pr_debug("%s: Reporting insertion %d\n", __func__,
SND_JACK_HEADSET);
snd_soc_jack_report(priv->headset_jack,
- SND_JACK_HEADSET, SND_JACK_HEADSET);
+ priv->hph_status, TABLA_JACK_MASK);
}
tabla_codec_start_hs_polling(codec);
}
@@ -2754,10 +2873,11 @@
*/
if (priv->mbhc_micbias_switched)
tabla_codec_switch_micbias(codec, 0);
+ priv->hph_status &= ~SND_JACK_HEADSET;
if (priv->headset_jack) {
pr_debug("%s: Reporting removal\n", __func__);
snd_soc_jack_report(priv->headset_jack, 0,
- SND_JACK_HEADSET);
+ TABLA_JACK_MASK);
}
tabla_codec_shutdown_hs_polling(codec);
@@ -2888,6 +3008,21 @@
snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN,
0x13, value);
}
+
+ if (pdata->ocp.use_pdata) {
+ /* not defined in CODEC specification */
+ if (pdata->ocp.hph_ocp_limit == 1 ||
+ pdata->ocp.hph_ocp_limit == 5) {
+ rc = -EINVAL;
+ goto done;
+ }
+ snd_soc_update_bits(codec, TABLA_A_RX_COM_OCP_CTL,
+ 0x0F, pdata->ocp.num_attempts);
+ snd_soc_write(codec, TABLA_A_RX_COM_OCP_COUNT,
+ ((pdata->ocp.run_time << 4) | pdata->ocp.wait_time));
+ snd_soc_update_bits(codec, TABLA_A_RX_HPH_OCP_CTL,
+ 0xE0, (pdata->ocp.hph_ocp_limit << 5));
+ }
done:
return rc;
}
@@ -2956,6 +3091,8 @@
}
static const struct tabla_reg_mask_val tabla_codec_reg_init_val[] = {
+ /* Initialize current threshold to 350MA */
+ {TABLA_A_RX_HPH_OCP_CTL, 0xE0, 0x60},
{TABLA_A_QFUSE_CTL, 0xFF, 0x03},
@@ -3118,12 +3255,34 @@
tabla_interface_reg_write(codec->control_data,
TABLA_SLIM_PGD_PORT_INT_EN0 + i, 0xFF);
+ ret = tabla_request_irq(codec->control_data,
+ TABLA_IRQ_HPH_PA_OCPL_FAULT, tabla_hphl_ocp_irq,
+ "HPH_L OCP detect", tabla);
+ if (ret) {
+ pr_err("%s: Failed to request irq %d\n", __func__,
+ TABLA_IRQ_HPH_PA_OCPL_FAULT);
+ goto err_hphl_ocp_irq;
+ }
+
+ ret = tabla_request_irq(codec->control_data,
+ TABLA_IRQ_HPH_PA_OCPR_FAULT, tabla_hphr_ocp_irq,
+ "HPH_R OCP detect", tabla);
+ if (ret) {
+ pr_err("%s: Failed to request irq %d\n", __func__,
+ TABLA_IRQ_HPH_PA_OCPR_FAULT);
+ goto err_hphr_ocp_irq;
+ }
+
#ifdef CONFIG_DEBUG_FS
debug_tabla_priv = tabla;
#endif
return ret;
+err_hphr_ocp_irq:
+ tabla_free_irq(codec->control_data, TABLA_IRQ_HPH_PA_OCPL_FAULT, tabla);
+err_hphl_ocp_irq:
+ tabla_free_irq(codec->control_data, TABLA_IRQ_SLIMBUS, tabla);
err_slimbus_irq:
tabla_free_irq(codec->control_data, TABLA_IRQ_MBHC_RELEASE, tabla);
err_release_irq:
diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c
index f5f5893..d129fb2 100644
--- a/sound/soc/msm/msm8960.c
+++ b/sound/soc/msm/msm8960.c
@@ -598,7 +598,8 @@
snd_soc_dapm_sync(dapm);
err = snd_soc_jack_new(codec, "Headset Jack",
- SND_JACK_HEADSET, &hs_jack);
+ (SND_JACK_HEADSET | SND_JACK_OC_HPHL | SND_JACK_OC_HPHR),
+ &hs_jack);
if (err) {
pr_err("failed to create new jack\n");
return err;