@@ -88,6 +88,7 @@ struct hdac_hdmi_pin {
};
struct hdac_hdmi_port {
+ struct list_head head;
int id;
struct hdac_hdmi_pin *pin;
int num_mux_nids;
@@ -96,15 +97,17 @@ struct hdac_hdmi_port {
bool chmap_set;
unsigned char chmap[8]; /* ALSA API channel-map */
int channels; /* current number of channels */
+ bool is_connected; /* flag to indicate device connected to port */
};
struct hdac_hdmi_pcm {
struct list_head head;
struct mutex lock;
int pcm_id;
- struct hdac_hdmi_port *port;
+ struct list_head port_list;
struct hdac_hdmi_cvt *cvt;
struct snd_jack *jack;
+ int jack_event;
};
struct hdac_hdmi_dai_port_map {
@@ -125,6 +128,39 @@ struct hdac_hdmi_priv {
struct hdac_chmap chmap;
};
+static void hdac_hdmi_jack_report(struct hdac_hdmi_pcm *pcm,
+ struct hdac_hdmi_port *port, bool is_connect)
+{
+ struct hdac_ext_device *edev = port->pin->edev;
+
+ if (is_connect) {
+ /*
+ * Report Jack connect event when a device is connected
+ * for the first time where same PCM is attached to mutiple
+ * ports.
+ */
+ if (pcm->jack_event == 0) {
+ dev_dbg(&edev->hdac.dev,
+ "jack report for pcm=%d\n",
+ pcm->pcm_id);
+ snd_jack_report(pcm->jack, SND_JACK_AVOUT);
+ }
+ pcm->jack_event++;
+ port->is_connected = true;
+ } else {
+ /*
+ * Report Jack disconnect event when a device is disconnected
+ * is the only last connected device when same PCM is attached
+ * to mutiple ports.
+ */
+ if (pcm->jack_event == 1)
+ snd_jack_report(pcm->jack, 0);
+ if (pcm->jack_event > 0)
+ pcm->jack_event--;
+ port->is_connected = false;
+ }
+}
+
static struct hdac_hdmi_pcm *hdac_hdmi_get_pcm_from_cvt(
struct hdac_hdmi_priv *hdmi,
struct hdac_hdmi_cvt *cvt)
@@ -212,7 +248,7 @@ static void hdac_hdmi_enable_cvt(struct hdac_ext_device *edev,
struct hdac_hdmi_dai_port_map *dai_map);
static int hdac_hdmi_enable_pin(struct hdac_ext_device *hdac,
- struct hdac_hdmi_dai_port_map *dai_map);
+ struct hdac_hdmi_port *port, struct hdac_hdmi_cvt *cvt);
static struct hdac_hdmi_pcm *get_hdmi_pcm_from_id(struct hdac_hdmi_priv *hdmi,
int pcm_idx)
@@ -274,13 +310,12 @@ format_constraint:
}
static int hdac_hdmi_setup_stream(struct hdac_ext_device *hdac,
- hda_nid_t cvt_nid, hda_nid_t pin_nid,
- u32 stream_tag, int format)
+ hda_nid_t cvt_nid, u32 stream_tag, int format)
{
unsigned int val;
- dev_dbg(&hdac->hdac.dev, "cvt nid %d pnid %d stream %d format 0x%x\n",
- cvt_nid, pin_nid, stream_tag, format);
+ dev_dbg(&hdac->hdac.dev, "cvt nid %d stream %d format 0x%x\n",
+ cvt_nid, stream_tag, format);
val = (stream_tag << 4);
@@ -409,9 +444,10 @@ static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac,
}
static void hdac_hdmi_set_power_state(struct hdac_ext_device *edev,
- struct hdac_hdmi_dai_port_map *dai_map, unsigned int pwr_state)
+ struct hdac_hdmi_cvt *cvt, struct hdac_hdmi_port *port,
+ unsigned int pwr_state)
{
- struct hdac_hdmi_pin *pin = dai_map->port->pin;
+ struct hdac_hdmi_pin *pin = port->pin;
/* Power up pin widget */
if (!snd_hdac_check_power_state(&edev->hdac, pin->nid,
@@ -420,9 +456,9 @@ static void hdac_hdmi_set_power_state(struct hdac_ext_device *edev,
AC_VERB_SET_POWER_STATE, pwr_state);
/* Power up converter */
- if (!snd_hdac_check_power_state(&edev->hdac, dai_map->cvt->nid,
+ if (!snd_hdac_check_power_state(&edev->hdac, cvt->nid,
pwr_state))
- snd_hdac_codec_write(&edev->hdac, dai_map->cvt->nid, 0,
+ snd_hdac_codec_write(&edev->hdac, cvt->nid, 0,
AC_VERB_SET_POWER_STATE, pwr_state);
}
@@ -438,7 +474,6 @@ static int hdac_hdmi_playback_prepare(struct snd_pcm_substream *substream,
int ret;
dai_map = &hdmi->dai_map[dai->id];
- port = dai_map->port;
dd = (struct hdac_ext_dma_params *)snd_soc_dai_get_dma_data(dai, substream);
dev_dbg(&hdac->hdac.dev, "stream tag from cpu dai %d format in cvt 0x%x\n",
@@ -450,21 +485,30 @@ static int hdac_hdmi_playback_prepare(struct snd_pcm_substream *substream,
if (!pcm)
return -EIO;
- mutex_lock(&pcm->lock);
- ret = hdac_hdmi_enable_pin(hdac, dai_map);
- if (ret < 0)
- return ret;
+ if (list_empty(&pcm->port_list))
+ return -EIO;
- port->channels = substream->runtime->channels;
+ mutex_lock(&pcm->lock);
+ list_for_each_entry(port, &pcm->port_list, head) {
+ if (port->is_connected) {
+ ret = hdac_hdmi_enable_pin(hdac, port, dai_map->cvt);
+ if (ret < 0) {
+ mutex_unlock(&pcm->lock);
+ return ret;
+ }
+ port->channels = substream->runtime->channels;
- ret = hdac_hdmi_setup_audio_infoframe(hdac, dai_map->cvt->nid,
+ ret = hdac_hdmi_setup_audio_infoframe(hdac, dai_map->cvt->nid,
port->pin->nid, port);
+ if (ret < 0) {
+ mutex_unlock(&pcm->lock);
+ return ret;
+ }
+ }
+ }
mutex_unlock(&pcm->lock);
- if (ret < 0)
- return ret;
- return hdac_hdmi_setup_stream(hdac, dai_map->cvt->nid,
- port->pin->nid, dd->stream_tag, dd->format);
+ return hdac_hdmi_setup_stream(hdac, dai_map->cvt->nid, dd->stream_tag, dd->format);
}
static int hdac_hdmi_set_hw_params(struct snd_pcm_substream *substream,
@@ -538,17 +582,16 @@ static void hdac_hdmi_enable_cvt(struct hdac_ext_device *edev,
}
static int hdac_hdmi_enable_pin(struct hdac_ext_device *hdac,
- struct hdac_hdmi_dai_port_map *dai_map)
+ struct hdac_hdmi_port *port, struct hdac_hdmi_cvt *cvt)
{
int mux_idx;
- struct hdac_hdmi_port *port = dai_map->port;
/* set the device if pin is mst_capable */
if (hdac_hdmi_port_select_set(hdac, port) < 0)
return -EIO;
for (mux_idx = 0; mux_idx < port->num_mux_nids; mux_idx++) {
- if (port->mux_nids[mux_idx] == dai_map->cvt->nid) {
+ if (port->mux_nids[mux_idx] == cvt->nid) {
snd_hdac_codec_write(&hdac->hdac, port->pin->nid, 0,
AC_VERB_SET_CONNECT_SEL, mux_idx);
break;
@@ -562,7 +605,7 @@ static int hdac_hdmi_enable_pin(struct hdac_ext_device *hdac,
snd_hdac_codec_write(&hdac->hdac, port->pin->nid, 0,
AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
- hdac_hdmi_set_power_state(hdac, dai_map, AC_PWRST_D0);
+ hdac_hdmi_set_power_state(hdac, cvt, port, AC_PWRST_D0);
snd_hdac_codec_write(&hdac->hdac, port->pin->nid, 0,
AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE);
@@ -614,30 +657,33 @@ static struct hdac_hdmi_port *hdac_hdmi_get_port_from_cvt(
struct hdac_hdmi_cvt *cvt)
{
struct hdac_hdmi_pcm *pcm;
- struct hdac_hdmi_port *port = NULL;
+ struct hdac_hdmi_port *port = NULL, *valid_port = NULL;
int ret, i;
list_for_each_entry(pcm, &hdmi->pcm_list, head) {
if (pcm->cvt == cvt) {
- port = pcm->port;
- break;
- }
- }
+ if (list_empty(&pcm->port_list))
+ continue;
- if (port) {
- mutex_lock(&pcm->lock);
- ret = hdac_hdmi_query_port_connlist(edev, port->pin, port);
- mutex_unlock(&pcm->lock);
+ list_for_each_entry(port, &pcm->port_list, head) {
+ mutex_lock(&pcm->lock);
+ ret = hdac_hdmi_query_port_connlist(edev, port->pin, port);
+ mutex_unlock(&pcm->lock);
- if (ret < 0)
- return NULL;
+ if (ret < 0)
+ return NULL;
- for (i = 0; i < port->num_mux_nids; i++) {
- if (port->mux_nids[i] == cvt->nid)
- return port;
+ for (i = 0; i < port->num_mux_nids; i++) {
+ if (port->mux_nids[i] == cvt->nid)
+ valid_port = port;
+ }
+ }
}
}
+ if (valid_port)
+ return valid_port;
+
return NULL;
}
@@ -667,7 +713,6 @@ static int hdac_hdmi_pcm_open(struct snd_pcm_substream *substream,
*/
if (!port)
return 0;
-
if ((!port->eld.monitor_present) ||
(!port->eld.eld_valid)) {
@@ -693,19 +738,10 @@ static int hdac_hdmi_pcm_open(struct snd_pcm_substream *substream,
static int hdac_hdmi_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
- struct hdac_hdmi_dai_port_map *dai_map;
- struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai);
- struct hdac_hdmi_priv *hdmi = hdac->private_data;
- int ret;
-
- dai_map = &hdmi->dai_map[dai->id];
- if (cmd == SNDRV_PCM_TRIGGER_RESUME) {
- ret = hdac_hdmi_enable_pin(hdac, dai_map);
- if (ret < 0)
- return ret;
+ if (cmd == SNDRV_PCM_TRIGGER_RESUME)
return hdac_hdmi_playback_prepare(substream, dai);
- }
+
return 0;
}
@@ -718,32 +754,39 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream,
struct hdac_hdmi_dai_port_map *dai_map;
struct hdac_hdmi_port *port;
struct hdac_hdmi_pcm *pcm;
+ struct hdac_hdmi_cvt *cvt;
dai_map = &hdmi->dai_map[dai->id];
pcm = hdac_hdmi_get_pcm_from_cvt(hdmi, dai_map->cvt);
+ cvt = dai_map->cvt;
+
if (dai_map->port && pcm) {
- port = dai_map->port;
snd_hdac_codec_write(&hdac->hdac, dai_map->cvt->nid, 0,
AC_VERB_SET_CHANNEL_STREAMID, 0);
snd_hdac_codec_write(&hdac->hdac, dai_map->cvt->nid, 0,
AC_VERB_SET_STREAM_FORMAT, 0);
- mutex_lock(&pcm->lock);
- /* set the device if pin is mst_capable */
- if (!hdac_hdmi_port_select_set(hdac, port)) {
- hdac_hdmi_set_power_state(hdac, dai_map, AC_PWRST_D3);
+ if (!list_empty(&pcm->port_list)) {
+ mutex_lock(&pcm->lock);
+ list_for_each_entry(port, &pcm->port_list, head) {
+ /* set the device if pin is mst_capable */
+ if (!hdac_hdmi_port_select_set(hdac, port)) {
+ hdac_hdmi_set_power_state(hdac, cvt, port,
+ AC_PWRST_D3);
- snd_hdac_codec_write(&hdac->hdac, dai_map->port->pin->nid, 0,
- AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
- }
- dai_map->port->chmap_set = false;
- memset(dai_map->port->chmap, 0, sizeof(dai_map->port->chmap));
- dai_map->port->channels = 0;
- mutex_unlock(&pcm->lock);
+ snd_hdac_codec_write(&hdac->hdac, port->pin->nid, 0,
+ AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+ }
- dai_map->port = NULL;
+ port->chmap_set = false;
+ memset(port->chmap, 0, sizeof(port->chmap));
+ port->channels = 0;
+ dai_map->port = NULL;
+ }
+ mutex_unlock(&pcm->lock);
+ }
}
}
@@ -813,13 +856,16 @@ static struct hdac_hdmi_pcm *hdac_hdmi_get_pcm(struct hdac_ext_device *edev,
{
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_pcm *pcm = NULL;
+ struct hdac_hdmi_port *p;
list_for_each_entry(pcm, &hdmi->pcm_list, head) {
- if (!pcm->port)
+ if (list_empty(&pcm->port_list))
continue;
- if (pcm->port == port)
- return pcm;
+ list_for_each_entry(p, &pcm->port_list, head) {
+ if (p->id == port->id && port->pin == p->pin)
+ return pcm;
+ }
}
return NULL;
@@ -832,6 +878,7 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int ret;
+ struct hdac_hdmi_port *p, *p_next;
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
struct snd_soc_dapm_context *dapm = w->dapm;
@@ -850,25 +897,30 @@ static int hdac_hdmi_set_pin_port_mux(struct snd_kcontrol *kcontrol,
mutex_lock(&hdmi->pin_mutex);
list_for_each_entry(pcm, &hdmi->pcm_list, head) {
- if (!pcm->port && pcm->port == port &&
- pcm->port->id == port->id)
- pcm->port = NULL;
+ if (list_empty(&pcm->port_list))
+ continue;
- /*
- * Jack status is not reported during device probe as the
- * PCMs are not registered by then. So report it here.
- */
- if (!strcmp(cvt_name, pcm->cvt->name) && !pcm->port) {
- pcm->port = port;
- if (port->eld.monitor_present && port->eld.eld_valid) {
- dev_dbg(&edev->hdac.dev,
- "jack report for pcm=%d\n",
- pcm->pcm_id);
+ list_for_each_entry_safe(p, p_next, &pcm->port_list, head) {
+ if (p == port && p->id == port->id &&
+ p->pin == port->pin) {
+ hdac_hdmi_jack_report(pcm, port, false);
+ list_del(&p->head);
+ }
+ }
+ }
- snd_jack_report(pcm->jack, SND_JACK_AVOUT);
+ /*
+ * Jack status is not reported during device probe as the
+ * PCMs are not registered by then. So report it here.
+ */
+ list_for_each_entry(pcm, &hdmi->pcm_list, head) {
+ if (!strcmp(cvt_name, pcm->cvt->name)) {
+ list_add_tail(&port->head, &pcm->port_list);
+ if (port->eld.monitor_present && port->eld.eld_valid) {
+ hdac_hdmi_jack_report(pcm, port, true);
+ mutex_unlock(&hdmi->pin_mutex);
+ return ret;
}
- mutex_unlock(&hdmi->pin_mutex);
- return ret;
}
}
mutex_unlock(&hdmi->pin_mutex);
@@ -1209,25 +1261,16 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin,
* report jack here. It will be done in usermode mux
* control select.
*/
- if (pcm) {
- dev_dbg(&edev->hdac.dev,
- "jack report for pcm=%d\n", pcm->pcm_id);
-
- snd_jack_report(pcm->jack, 0);
- }
+ if (pcm)
+ hdac_hdmi_jack_report(pcm, port, false);
mutex_unlock(&hdmi->pin_mutex);
return;
}
if (port->eld.monitor_present && port->eld.eld_valid) {
- if (pcm) {
- dev_dbg(&edev->hdac.dev,
- "jack report for pcm=%d\n",
- pcm->pcm_id);
-
- snd_jack_report(pcm->jack, SND_JACK_AVOUT);
- }
+ if (pcm)
+ hdac_hdmi_jack_report(pcm, port, true);
print_hex_dump_debug("ELD: ", DUMP_PREFIX_OFFSET, 16, 1,
port->eld.eld_buffer, port->eld.eld_size, false);
@@ -1551,8 +1594,9 @@ int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
return -ENOMEM;
pcm->pcm_id = device;
pcm->cvt = hdmi->dai_map[dai->id].cvt;
+ pcm->jack_event = 0;
mutex_init(&pcm->lock);
-
+ INIT_LIST_HEAD(&pcm->port_list);
snd_pcm = hdac_hdmi_get_pcm_from_id(dai->component->card, device);
if (snd_pcm) {
err = snd_hdac_add_chmap_ctls(snd_pcm, device, &hdmi->chmap);
@@ -1610,6 +1654,11 @@ static int hdmi_codec_probe(struct snd_soc_codec *codec)
return ret;
}
+ /* FIXME
+ * At boot driver does not know if the port is mst capable, to get
+ * the ELD, i915 acomp API expects to pass pipe id as -1 when the
+ * port is non mst.
+ */
list_for_each_entry(pin, &hdmi->pin_list, head)
for (i = 0; i < pin->num_ports; i++)
hdac_hdmi_present_sense(pin, &pin->ports[i]);
@@ -1700,13 +1749,15 @@ static void hdac_hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx,
struct hdac_ext_device *edev = to_ehdac_device(hdac);
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx);
- struct hdac_hdmi_port *port = pcm->port;
+ struct hdac_hdmi_port *port;
- /* chmap is already set to 0 in caller */
- if (!port)
+ if (list_empty(&pcm->port_list))
return;
- memcpy(chmap, port->chmap, ARRAY_SIZE(port->chmap));
+ list_for_each_entry(port, &pcm->port_list, head) {
+ /* chmap is already set to 0 in caller */
+ memcpy(chmap, port->chmap, ARRAY_SIZE(port->chmap));
+ }
}
static void hdac_hdmi_set_chmap(struct hdac_device *hdac, int pcm_idx,
@@ -1715,16 +1766,20 @@ static void hdac_hdmi_set_chmap(struct hdac_device *hdac, int pcm_idx,
struct hdac_ext_device *edev = to_ehdac_device(hdac);
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx);
- struct hdac_hdmi_port *port = pcm->port;
- struct hdac_hdmi_pin *pin = port->pin;
+ struct hdac_hdmi_port *port;
+
+ if (list_empty(&pcm->port_list))
+ return;
if (pcm) {
mutex_lock(&pcm->lock);
- port->chmap_set = true;
- memcpy(port->chmap, chmap, ARRAY_SIZE(port->chmap));
- if (prepared)
- hdac_hdmi_setup_audio_infoframe(edev, pcm->cvt->nid,
- pin->nid, port);
+ list_for_each_entry(port, &pcm->port_list, head) {
+ port->chmap_set = true;
+ memcpy(port->chmap, chmap, ARRAY_SIZE(port->chmap));
+ if (prepared)
+ hdac_hdmi_setup_audio_infoframe(edev, pcm->cvt->nid,
+ port->pin->nid, port);
+ }
mutex_unlock(&pcm->lock);
}
}
@@ -1734,9 +1789,11 @@ static bool is_hdac_hdmi_pcm_attached(struct hdac_device *hdac, int pcm_idx)
struct hdac_ext_device *edev = to_ehdac_device(hdac);
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx);
- struct hdac_hdmi_port *port = pcm->port;
- return port ? true:false;
+ if (list_empty(&pcm->port_list))
+ return false;
+
+ return true;
}
static int hdac_hdmi_get_spk_alloc(struct hdac_device *hdac, int pcm_idx)
@@ -1744,7 +1801,15 @@ static int hdac_hdmi_get_spk_alloc(struct hdac_device *hdac, int pcm_idx)
struct hdac_ext_device *edev = to_ehdac_device(hdac);
struct hdac_hdmi_priv *hdmi = edev->private_data;
struct hdac_hdmi_pcm *pcm = get_hdmi_pcm_from_id(hdmi, pcm_idx);
- struct hdac_hdmi_port *port = pcm->port;
+ struct hdac_hdmi_port *port;
+
+ if (list_empty(&pcm->port_list))
+ return 0;
+
+ port = list_first_entry(&pcm->port_list, struct hdac_hdmi_port, head);
+
+ if (!port)
+ return 0;
if (!port || !port->eld.eld_valid)
return 0;
@@ -1822,13 +1887,19 @@ static int hdac_hdmi_dev_remove(struct hdac_ext_device *edev)
struct hdac_hdmi_pin *pin, *pin_next;
struct hdac_hdmi_cvt *cvt, *cvt_next;
struct hdac_hdmi_pcm *pcm, *pcm_next;
+ struct hdac_hdmi_port *port;
int i;
snd_soc_unregister_codec(&edev->hdac.dev);
list_for_each_entry_safe(pcm, pcm_next, &hdmi->pcm_list, head) {
pcm->cvt = NULL;
- pcm->port = NULL;
+ if (list_empty(&pcm->port_list))
+ continue;
+
+ list_for_each_entry(port, &pcm->port_list, head)
+ port = NULL;
+
list_del(&pcm->head);
kfree(pcm);
}