/* * Some codecs handle micbias/pullup enablement in codec * drivers itself and micbias is not needed for regular * plug type detection. So if micbias_control callback function * is defined, just return.
*/ if (mbhc->mbhc_cb->mbhc_micbias_control) return;
switch (cs_mb_en) { case WCD_MBHC_EN_CS:
wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 0);
wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 3); /* Program Button threshold registers as per CS */
wcd_program_btn_threshold(mbhc, false); break; case WCD_MBHC_EN_MB:
wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0);
wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); /* Disable PULL_UP_EN & enable MICBIAS */
wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 2); /* Program Button threshold registers as per MICBIAS */
wcd_program_btn_threshold(mbhc, true); break; case WCD_MBHC_EN_PULLUP:
wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 3);
wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1);
wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 1); /* Program Button threshold registers as per MICBIAS */
wcd_program_btn_threshold(mbhc, true); break; case WCD_MBHC_EN_NONE:
wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0);
wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1);
wcd_mbhc_write_field(mbhc, WCD_MBHC_MICB_CTRL, 0); break; default:
dev_err(mbhc->dev, "%s: Invalid parameter", __func__); break;
}
}
int wcd_mbhc_event_notify(struct wcd_mbhc *mbhc, unsignedlong event)
{
if (mbhc->mbhc_cb->micbias_enable_status)
micbias2 = mbhc->mbhc_cb->micbias_enable_status(component, MIC_BIAS_2);
switch (event) { /* MICBIAS usage change */ case WCD_EVENT_POST_DAPM_MICBIAS_2_ON:
mbhc->is_hs_recording = true; break; case WCD_EVENT_POST_MICBIAS_2_ON: /* Disable current source if micbias2 enabled */ if (mbhc->mbhc_cb->mbhc_micbias_control) { if (wcd_mbhc_read_field(mbhc, WCD_MBHC_FSM_EN))
wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 0);
} else {
mbhc->is_hs_recording = true;
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB);
} break; case WCD_EVENT_PRE_MICBIAS_2_OFF: /* * Before MICBIAS_2 is turned off, if FSM is enabled, * make sure current source is enabled so as to detect * button press/release events
*/ if (mbhc->mbhc_cb->mbhc_micbias_control/* && !mbhc->micbias_enable*/) { if (wcd_mbhc_read_field(mbhc, WCD_MBHC_FSM_EN))
wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_ISRC_CTL, 3);
} break; /* MICBIAS usage change */ case WCD_EVENT_POST_DAPM_MICBIAS_2_OFF:
mbhc->is_hs_recording = false; break; case WCD_EVENT_POST_MICBIAS_2_OFF: if (!mbhc->mbhc_cb->mbhc_micbias_control)
mbhc->is_hs_recording = false;
/* Enable PULL UP if PA's are enabled */ if ((test_bit(WCD_MBHC_EVENT_PA_HPHL, &mbhc->event_state)) ||
(test_bit(WCD_MBHC_EVENT_PA_HPHR, &mbhc->event_state))) /* enable pullup and cs, disable mb */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_PULLUP); else /* enable current source and disable mb, pullup*/
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_CS);
break; case WCD_EVENT_POST_HPHL_PA_OFF:
clear_bit(WCD_MBHC_EVENT_PA_HPHL, &mbhc->event_state);
/* check if micbias is enabled */ if (micbias2) /* Disable cs, pullup & enable micbias */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); else /* Disable micbias, pullup & enable cs */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_CS); break; case WCD_EVENT_POST_HPHR_PA_OFF:
clear_bit(WCD_MBHC_EVENT_PA_HPHR, &mbhc->event_state); /* check if micbias is enabled */ if (micbias2) /* Disable cs, pullup & enable micbias */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); else /* Disable micbias, pullup & enable cs */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_CS); break; case WCD_EVENT_PRE_HPHL_PA_ON:
set_bit(WCD_MBHC_EVENT_PA_HPHL, &mbhc->event_state); /* check if micbias is enabled */ if (micbias2) /* Disable cs, pullup & enable micbias */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); else /* Disable micbias, enable pullup & cs */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_PULLUP); break; case WCD_EVENT_PRE_HPHR_PA_ON:
set_bit(WCD_MBHC_EVENT_PA_HPHR, &mbhc->event_state); /* check if micbias is enabled */ if (micbias2) /* Disable cs, pullup & enable micbias */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_MB); else /* Disable micbias, enable pullup & cs */
wcd_mbhc_curr_micbias_control(mbhc, WCD_MBHC_EN_PULLUP); break; default: break;
} return 0;
}
EXPORT_SYMBOL_GPL(wcd_mbhc_event_notify);
/* Do not calculate impedance again for lineout * as during playback pa is on and impedance values * will not be correct resulting in lineout detected * as headphone.
*/ if (is_pa_on && mbhc->force_linein) {
jack_type = SND_JACK_LINEOUT;
mbhc->current_plug = MBHC_PLUG_TYPE_HIGH_HPH; if (mbhc->hph_status) {
mbhc->hph_status &= ~(SND_JACK_HEADSET |
SND_JACK_LINEOUT);
snd_soc_jack_report(mbhc->jack, mbhc->hph_status,
WCD_MBHC_JACK_MASK);
}
}
mbhc->hph_status |= jack_type;
if (jack_type == SND_JACK_HEADPHONE && mbhc->mbhc_cb->mbhc_micb_ramp_control)
mbhc->mbhc_cb->mbhc_micb_ramp_control(mbhc->component, false);
/* Set the detection type appropriately */
wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_DETECTION_TYPE, 1);
enable_irq(mbhc->intr_ids->mbhc_hs_ins_intr);
}
ret = pm_runtime_get_sync(component->dev); if (ret < 0 && ret != -EACCES) {
dev_err_ratelimited(component->dev, "pm_runtime_get_sync failed in %s, ret %d\n",
__func__, ret);
pm_runtime_put_noidle(component->dev); return ret;
}
mutex_lock(&mbhc->lock);
if (mbhc->cfg->typec_analog_mux)
mbhc->swap_thr = GND_MIC_USBC_SWAP_THRESHOLD; else
mbhc->swap_thr = GND_MIC_SWAP_THRESHOLD;
/* Plug detect is triggered manually if analog goes through USBCC */ if (mbhc->cfg->typec_analog_mux)
wcd_mbhc_write_field(mbhc, WCD_MBHC_L_DET_EN, 0); else
wcd_mbhc_write_field(mbhc, WCD_MBHC_L_DET_EN, 1);
if (mbhc->cfg->typec_analog_mux) /* Insertion debounce set to 48ms */
wcd_mbhc_write_field(mbhc, WCD_MBHC_INSREM_DBNC, 4); else /* Insertion debounce set to 96ms */
wcd_mbhc_write_field(mbhc, WCD_MBHC_INSREM_DBNC, 6);
/* Button Debounce set to 16ms */
wcd_mbhc_write_field(mbhc, WCD_MBHC_BTN_DBNC, 2);
staticint wcd_get_voltage_from_adc(u8 val, int micbias)
{ /* Formula for calculating voltage from ADC * Voltage = ADC_RESULT*12.5mV*V_MICBIAS/1.8
*/ return ((val * 125 * micbias)/(WCD_MBHC_ADC_MICBIAS_MV * 10));
}
staticint wcd_measure_adc_continuous(struct wcd_mbhc *mbhc)
{
u8 adc_result; int output_mv; int retry = 3;
u8 adc_en;
/* Pre-requisites for ADC continuous measurement */ /* Read legacy electircal detection and disable */
wcd_mbhc_write_field(mbhc, WCD_MBHC_ELECT_SCHMT_ISRC, 0x00); /* Set ADC to continuous measurement */
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 1); /* Read ADC Enable bit to restore after adc measurement */
adc_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_EN); /* Disable ADC_ENABLE bit */
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 0); /* Disable MBHC FSM */
wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 0); /* Set the MUX selection to IN2P */
wcd_mbhc_write_field(mbhc, WCD_MBHC_MUX_CTL, MUX_CTL_IN2P); /* Enable MBHC FSM */
wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1); /* Enable ADC_ENABLE bit */
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 1);
while (retry--) { /* wait for 3 msec before reading ADC result */
usleep_range(3000, 3100);
adc_result = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_RESULT);
}
/* Restore ADC Enable */
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, adc_en); /* Get voltage from ADC result */
output_mv = wcd_get_voltage_from_adc(adc_result, wcd_mbhc_get_micbias(mbhc));
return output_mv;
}
staticint wcd_measure_adc_once(struct wcd_mbhc *mbhc, int mux_ctl)
{ struct device *dev = mbhc->dev;
u8 adc_timeout = 0;
u8 adc_complete = 0;
u8 adc_result; int retry = 6; int ret; int output_mv = 0;
u8 adc_en;
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 0); /* Read ADC Enable bit to restore after adc measurement */
adc_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_EN); /* Trigger ADC one time measurement */
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 0);
wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 0); /* Set the appropriate MUX selection */
wcd_mbhc_write_field(mbhc, WCD_MBHC_MUX_CTL, mux_ctl);
wcd_mbhc_write_field(mbhc, WCD_MBHC_FSM_EN, 1);
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 1);
while (retry--) { /* wait for 600usec to get adc results */
usleep_range(600, 610);
/* check for ADC Timeout */
adc_timeout = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_TIMEOUT); if (adc_timeout) continue;
/* Read ADC complete bit */
adc_complete = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_COMPLETE); if (!adc_complete) continue;
/* Read ADC result */
adc_result = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_RESULT);
/* Get voltage from ADC result */
output_mv = wcd_get_voltage_from_adc(adc_result,
wcd_mbhc_get_micbias(mbhc)); break;
}
/* Read and set ADC to single measurement */
adc_mode = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_MODE); /* Read ADC Enable bit to restore after adc measurement */
adc_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_ADC_EN); /* Read FSM status */
fsm_en = wcd_mbhc_read_field(mbhc, WCD_MBHC_FSM_EN);
/* Get adc result for HPH L */
hphl_adc_res = wcd_measure_adc_once(mbhc, MUX_CTL_HPH_L); if (hphl_adc_res < 0) return hphl_adc_res;
/* Get adc result for HPH R in mV */
hphr_adc_res = wcd_measure_adc_once(mbhc, MUX_CTL_HPH_R); if (hphr_adc_res < 0) return hphr_adc_res;
/* Back MIC_BIAS2 to 1.8v if the type is not special headset */ if (!is_spl_hs) {
mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(mbhc->component, MIC_BIAS_2, false); /* Add 10ms delay for micbias to settle */
usleep_range(10000, 10100);
}
/* * Report plug type if it is either headset or headphone * else start the 3 sec loop
*/ switch (plug_type) { case MBHC_PLUG_TYPE_HEADPHONE:
wcd_mbhc_find_plug_and_report(mbhc, plug_type); break; case MBHC_PLUG_TYPE_HEADSET:
wcd_mbhc_find_plug_and_report(mbhc, plug_type);
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_MODE, 0);
wcd_mbhc_write_field(mbhc, WCD_MBHC_ADC_EN, 0);
wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 1); break; default: break;
}
while (!time_after(jiffies, timeout)) { if (mbhc->hs_detect_work_stop) {
wcd_micbias_disable(mbhc); gotoexit;
}
msleep(180); /* * Use ADC single mode to minimize the chance of missing out * btn press/release for HEADSET type during correct work.
*/
output_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P);
plug_type = wcd_mbhc_get_plug_from_adc(mbhc, output_mv);
is_pa_on = wcd_mbhc_read_field(mbhc, WCD_MBHC_HPH_PA_EN);
/* * Set DETECTION_DONE bit for HEADSET * so that btn press/release interrupt can be generated. * For other plug type, clear the bit.
*/ if (plug_type == MBHC_PLUG_TYPE_HEADSET)
wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 1); else
wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 0);
if (mbhc->mbhc_cb->mbhc_micbias_control)
wcd_mbhc_adc_update_fsm_source(mbhc, plug_type);
exit: if (mbhc->mbhc_cb->mbhc_micbias_control/* && !mbhc->micbias_enable*/)
mbhc->mbhc_cb->mbhc_micbias_control(component, MIC_BIAS_2, MICB_DISABLE);
/* * If plug type is corrected from special headset to headphone, * clear the micbias enable flag, set micbias back to 1.8V and * disable micbias.
*/ if (plug_type == MBHC_PLUG_TYPE_HEADPHONE) {
wcd_micbias_disable(mbhc); /* * Enable ADC COMPLETE interrupt for HEADPHONE. * Btn release may happen after the correct work, ADC COMPLETE * interrupt needs to be captured to correct plug type.
*/
enable_irq(mbhc->intr_ids->mbhc_hs_ins_intr);
}
if (mbhc->mbhc_cb->hph_pull_down_ctrl)
mbhc->mbhc_cb->hph_pull_down_ctrl(component, true);
do {
retry++; /* * read output_mv every 10ms to look for * any change in IN2_P
*/
usleep_range(10000, 10100);
output_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P);
/* Check for fake removal */ if ((output_mv <= adc_threshold) && retry > FAKE_REM_RETRY_ATTEMPTS) gotoexit;
} while (!time_after(jiffies, timeout));
/* * ADC COMPLETE and ELEC_REM interrupts are both enabled for * HEADPHONE, need to reject the ADC COMPLETE interrupt which * follows ELEC_REM one when HEADPHONE is removed.
*/ if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE)
mbhc->extn_cable_hph_rem = true;
/* * ADC COMPLETE and ELEC_REM interrupts are both enabled for HEADPHONE, * need to reject the ADC COMPLETE interrupt which follows ELEC_REM one * when HEADPHONE is removed.
*/ if (mbhc->extn_cable_hph_rem == true) {
mbhc->extn_cable_hph_rem = false; return IRQ_HANDLED;
}
do {
clamp_state = wcd_mbhc_read_field(mbhc, WCD_MBHC_IN2P_CLAMP_STATE); if (clamp_state) return IRQ_HANDLED; /* * check clamp for 120ms but at 30ms chunks to leave * room for other interrupts to be processed
*/
usleep_range(30000, 30100);
} while (--clamp_retry);
/* * If current plug is headphone then there is no chance to * get ADC complete interrupt, so connected cable should be * headset not headphone.
*/ if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
disable_irq_nosync(mbhc->intr_ids->mbhc_hs_ins_intr);
wcd_mbhc_write_field(mbhc, WCD_MBHC_DETECTION_DONE, 1);
wcd_mbhc_find_plug_and_report(mbhc, MBHC_PLUG_TYPE_HEADSET); return IRQ_HANDLED;
}
int wcd_dt_parse_mbhc_data(struct device *dev, struct wcd_mbhc_config *cfg)
{ struct device_node *np = dev->of_node; int ret, i, microvolt;
if (of_property_read_bool(np, "qcom,hphl-jack-type-normally-closed"))
cfg->hphl_swh = false; else
cfg->hphl_swh = true;
if (of_property_read_bool(np, "qcom,ground-jack-type-normally-closed"))
cfg->gnd_swh = false; else
cfg->gnd_swh = true;
ret = of_property_read_u32(np, "qcom,mbhc-headset-vthreshold-microvolt",
µvolt); if (ret)
dev_dbg(dev, "missing qcom,mbhc-hs-mic-max-vthreshold--microvolt in dt node\n"); else
cfg->hs_thr = microvolt/1000;
ret = of_property_read_u32(np, "qcom,mbhc-headphone-vthreshold-microvolt",
µvolt); if (ret)
dev_dbg(dev, "missing qcom,mbhc-hs-mic-min-vthreshold-microvolt entry\n"); else
cfg->hph_thr = microvolt/1000;
ret = of_property_read_u32_array(np, "qcom,mbhc-buttons-vthreshold-microvolt",
&cfg->btn_high[0],
WCD_MBHC_DEF_BUTTONS); if (ret)
dev_err(dev, "missing qcom,mbhc-buttons-vthreshold-microvolt entry\n");
for (i = 0; i < WCD_MBHC_DEF_BUTTONS; i++) { if (ret) /* default voltage */
cfg->btn_high[i] = 500000; else /* Micro to Milli Volts */
cfg->btn_high[i] = cfg->btn_high[i]/1000;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.24Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.