Register the event handlers and program the codec by sending required verbs in the event handlers.
Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/codecs/hdac_generic.c | 329 +++++++++++++++++++++++++++++++++++++++- sound/soc/codecs/hdac_generic.h | 3 + 2 files changed, 326 insertions(+), 6 deletions(-)
diff --git a/sound/soc/codecs/hdac_generic.c b/sound/soc/codecs/hdac_generic.c index 6bd7b2b..354e81d 100644 --- a/sound/soc/codecs/hdac_generic.c +++ b/sound/soc/codecs/hdac_generic.c @@ -75,6 +75,305 @@ static void hdac_generic_set_power_state(struct hdac_ext_device *edev, AC_VERB_SET_POWER_STATE, pwr_state); }
+/* TODO: Add feature to program by querying capability */ +static void hdac_generic_set_eapd(struct hdac_ext_device *edev, + hda_nid_t nid, bool enable) +{ + u32 pin_caps = snd_hdac_read_parm_uncached(&edev->hdac, + nid, AC_PAR_PIN_CAP); + + if (pin_caps & AC_PINCAP_EAPD) + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_EAPD_BTLENABLE, enable ? 2 : 0); +} + +static int hdac_generic_pin_io_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + int val; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + val = snd_hdac_codec_read(&edev->hdac, wid->nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (w->id == snd_soc_dapm_output) { + /* TODO: program using a eapd widget */ + hdac_generic_set_eapd(edev, wid->nid, true); + val |= AC_PINCTL_OUT_EN; + } else { + + /* TODO: program vref using a mixer control */ + val |= AC_PINCTL_VREF_80; + val |= AC_PINCTL_IN_EN; + } + + break; + + case SND_SOC_DAPM_POST_PMD: + if (w->id == snd_soc_dapm_output) { + /* TODO: program using a eapd widget */ + hdac_generic_set_eapd(edev, wid->nid, false); + val &= ~AC_PINCTL_OUT_EN; + } else { + val &= AC_PINCTL_VREF_HIZ; + val &= ~AC_PINCTL_IN_EN; + } + + break; + + default: + dev_warn(&edev->hdac.dev, "Event %d not handled\n", event); + return 0; + } + + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, val); + + return 0; +} + +static int hdac_generic_pin_mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + int mux_idx; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + if (!kc) + kc = w->kcontrols[0]; + + mux_idx = dapm_kcontrol_get_value(kc); + if (mux_idx > 0) { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_CONNECT_SEL, (mux_idx - 1)); + } + + return 0; +} + +static int hdac_generic_pin_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + if (event == SND_SOC_DAPM_POST_PMD) { + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D3); + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + } else { + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D0); + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + } + + return 0; +} + +static int hdac_generic_widget_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + hdac_generic_set_power_state(edev, wid->nid, + (event == SND_SOC_DAPM_POST_PMD ? AC_PWRST_D3:AC_PWRST_D0)); + + return 0; +} + +static int hdac_generic_cvt_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + + hdac_generic_widget_power_event(w, kc, event); + + if (event == SND_SOC_DAPM_POST_PMD) + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_MUTE(0)); + else + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0) | 0x5b); + + return 0; +} + +static int get_mixer_control_index(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc) +{ + int i; + + for (i = 0; i < w->num_kcontrols; i++) { + if (w->kcontrols[i] == kc) + return i; + } + + return -EINVAL; +} + +static int hdac_generic_mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + bool no_input = true; + int i; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D0); + + + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + + for (i = 0; i < w->num_kcontrols; i++) { + if (dapm_kcontrol_get_value(w->kcontrols[i])) { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(i)); + } + } + + return 0; + + case SND_SOC_DAPM_POST_PMD: + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + for (i = 0; i < w->num_kcontrols; i++) { + if (dapm_kcontrol_get_value(w->kcontrols[i])) { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_MUTE(i)); + } + } + + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D3); + + return 0; + + case SND_SOC_DAPM_POST_REG: + i = get_mixer_control_index(w, kc); + if (i < 0) { + dev_err(&edev->hdac.dev, + "%s: Wrong kcontrol event: %s\n", + __func__, kc->id.name); + return i; + } + if (dapm_kcontrol_get_value(kc)) { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(i)); + no_input = false; + } else { + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(i)); + } + + if (no_input) + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + + return 0; + + default: + dev_warn(&edev->hdac.dev, "Event %d not handled\n", event); + return 0; + } + + return 0; +} + +static void update_mux_amp_switch(struct hdac_ext_device *edev, + hda_nid_t nid, struct snd_kcontrol *kc, bool enable) +{ + struct soc_enum *e = (struct soc_enum *)kc->private_value; + int mux_idx, i; + + mux_idx = dapm_kcontrol_get_value(kc); + + if (!enable || !mux_idx) { + for (i = 1; i < (e->items - 1); i++) + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(i - 1)); + + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE); + } else { + for (i = 1; i < (e->items - 1); i++) { + if (i == mux_idx) + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(i - 1)); + else + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_MUTE(i - 1)); + } + + snd_hdac_codec_write(&edev->hdac, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + } +} + +static int hdac_generic_selector_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kc, int event) +{ + struct hdac_ext_device *edev = to_hda_ext_device(w->dapm->dev); + struct hdac_codec_widget *wid = w->priv; + + dev_dbg(&edev->hdac.dev, "%s: widget: %s event: %x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D0); + snd_hdac_codec_write(&edev->hdac, wid->nid, 0, + AC_VERB_SET_CONNECT_SEL, + (dapm_kcontrol_get_value(w->kcontrols[0]) - 1)); + update_mux_amp_switch(edev, wid->nid, w->kcontrols[0], true); + + return 0; + + case SND_SOC_DAPM_POST_REG: + update_mux_amp_switch(edev, wid->nid, kc, true); + + return 0; + + case SND_SOC_DAPM_POST_PMD: + update_mux_amp_switch(edev, wid->nid, w->kcontrols[0], false); + hdac_generic_set_power_state(edev, wid->nid, AC_PWRST_D3); + + return 0; + + default: + dev_warn(&edev->hdac.dev, "Event %d not handled\n", event); + return 0; + } + + return 0; +} + static bool is_duplicate_route(struct list_head *route_list, const char *sink, const char *control, const char *src) { @@ -480,7 +779,8 @@ static int hdac_generic_alloc_mux_widget(struct snd_soc_dapm_context *dapm,
ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], snd_soc_dapm_mux, wid, widget_name, NULL, kc, 1, - NULL, 0); + hdac_generic_selector_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_REG);
if (ret < 0) return ret; @@ -534,7 +834,9 @@ static int hdac_codec_alloc_cvt_widget(struct snd_soc_dapm_context *dapm, ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], wid->type == AC_WID_AUD_IN ? snd_soc_dapm_aif_in : snd_soc_dapm_aif_out, - wid, widget_name, dai_strm_name, NULL, 0, NULL, 0); + wid, widget_name, dai_strm_name, NULL, 0, + hdac_generic_cvt_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret;
@@ -587,7 +889,9 @@ static int hdac_codec_alloc_mixer_widget(struct snd_soc_dapm_context *dapm, sprintf(widget_name, "Mixer %x", wid->nid); ret = hdac_generic_fill_widget_info(dapm->dev, &w[index], snd_soc_dapm_mixer, wid, widget_name, NULL, - kc, wid->num_inputs, NULL, 0); + kc, wid->num_inputs, hdac_generic_mixer_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_POST_REG); if (ret < 0) return ret;
@@ -634,7 +938,9 @@ static int hdac_codec_alloc_pin_widget(struct snd_soc_dapm_context *dapm,
ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[i], input ? snd_soc_dapm_input : snd_soc_dapm_output, - wid, widget_name, NULL, NULL, 0, NULL, 0); + wid, widget_name, NULL, NULL, 0, + hdac_generic_pin_io_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret;
@@ -644,7 +950,8 @@ static int hdac_codec_alloc_pin_widget(struct snd_soc_dapm_context *dapm, sprintf(widget_name, "Pin %x PGA", wid->nid); ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[i], snd_soc_dapm_pga, wid, widget_name, NULL, - NULL, 0, NULL, 0); + NULL, 0, hdac_generic_pin_pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret;
@@ -656,6 +963,15 @@ static int hdac_codec_alloc_pin_widget(struct snd_soc_dapm_context *dapm, ret = hdac_generic_alloc_mux_widget(dapm, widgets, i, wid); if (ret < 0) return ret; + /* + * Pin mux will not use generic selector handler, so + * override. Also mux widget create will increment the + * index, so assign the previous widget. + */ + widgets[i].event_flags = SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD | + SND_SOC_DAPM_POST_REG; + widgets[i].event = hdac_generic_pin_mux_event;
wid_ref[2] = &widgets[i++]; } @@ -677,7 +993,8 @@ static int hdac_codec_alloc_power_widget(struct snd_soc_dapm_context *dapm, sprintf(widget_name, "Power %x", wid->nid); ret = hdac_generic_fill_widget_info(dapm->dev, &widgets[index], snd_soc_dapm_supply, wid, widget_name, - NULL, NULL, 0, NULL, 0); + NULL, NULL, 0, hdac_generic_widget_power_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD); if (ret < 0) return ret;
diff --git a/sound/soc/codecs/hdac_generic.h b/sound/soc/codecs/hdac_generic.h index 5ca713a..a938b50 100644 --- a/sound/soc/codecs/hdac_generic.h +++ b/sound/soc/codecs/hdac_generic.h @@ -21,5 +21,8 @@ #define __HDAC_GENERIC_H__
#define HDAC_GENERIC_NAME_SIZE 32 +#define AMP_OUT_MUTE 0xb080 +#define AMP_OUT_UNMUTE 0xb000 +#define PIN_OUT (AC_PINCTL_OUT_EN)
#endif /* __HDAC_GENERIC_H__ */