[PATCH 3/4] snd: add support for displayport multi-stream to hda codec.

Dave Airlie
2015-06-17T04:02:13Z

From: Dave Airlie <airlied@redhat.com>

Add new verbs GET_DP_STREAM_ID and SET_DP_STREAM_ID from Intel docs.
get stream id and print in proc
split ELD to be per device not per pin
handle pd/eldv per device not per pin
setup codec->dp_mst earlier.

Signed-off-by: Dave Airlie <airlied@redhat.com>
---
include/sound/hda_verbs.h | 2 +
sound/pci/hda/hda_codec.c | 1 +
sound/pci/hda/hda_proc.c | 5 +-
sound/pci/hda/patch_hdmi.c | 181 +++++++++++++++++++++++++++++++--------------
4 files changed, 131 insertions(+), 58 deletions(-)

diff --git a/include/sound/hda_verbs.h b/include/sound/hda_verbs.h
index d0509db..3b62ac5 100644
--- a/include/sound/hda_verbs.h
+++ b/include/sound/hda_verbs.h
@@ -75,6 +75,7 @@ enum {
#define AC_VERB_GET_HDMI_CHAN_SLOT 0x0f34
#define AC_VERB_GET_DEVICE_SEL 0xf35
#define AC_VERB_GET_DEVICE_LIST 0xf36
+#define AC_VERB_GET_DP_STREAM_ID 0xf3c

/*
* SET verbs
@@ -115,6 +116,7 @@ enum {
#define AC_VERB_SET_HDMI_CP_CTRL 0x733
#define AC_VERB_SET_HDMI_CHAN_SLOT 0x734
#define AC_VERB_SET_DEVICE_SEL 0x735
+#define AC_VERB_SET_DP_STREAM_ID 0x73C

/*
* Parameter IDs
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index 5645481..3981c63 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -482,6 +482,7 @@ int snd_hda_get_devices(struct hda_codec *codec, hda_nid_t nid,
}
return devices;
}
+EXPORT_SYMBOL_GPL(snd_hda_get_devices);

/*
* destructor
diff --git a/sound/pci/hda/hda_proc.c b/sound/pci/hda/hda_proc.c
index baaf7ed0..39fac53 100644
--- a/sound/pci/hda/hda_proc.c
+++ b/sound/pci/hda/hda_proc.c
@@ -644,10 +644,13 @@ static void print_device_list(struct snd_info_buffer *buffer,
int i, curr = -1;
u8 dev_list[AC_MAX_DEV_LIST_LEN];
int devlist_len;
+ int dp_s_id;

+ dp_s_id = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_DP_STREAM_ID, 0);
devlist_len = snd_hda_get_devices(codec, nid, dev_list,
AC_MAX_DEV_LIST_LEN);
- snd_iprintf(buffer, " Devices: %d\n", devlist_len);
+ snd_iprintf(buffer, " Devices: %d: 0x%x\n", devlist_len, dp_s_id);
if (devlist_len <= 0)
return;

diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index 5f44f60..8272656 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -68,6 +68,17 @@ struct hdmi_spec_per_cvt {
/* max. connections to a widget */
#define HDA_MAX_CONNECTIONS 32

+struct hdmi_spec_per_pin;
+#define HDA_MAX_DEVICES 3
+struct hdmi_spec_per_device {
+ struct hdmi_spec_per_pin *pin;
+ int device_idx;
+ struct hdmi_eld sink_eld;
+#ifdef CONFIG_PROC_FS
+ struct snd_info_entry *proc_entry;
+#endif
+};
+
struct hdmi_spec_per_pin {
hda_nid_t pin_nid;
int num_mux_nids;
@@ -76,7 +87,11 @@ struct hdmi_spec_per_pin {
hda_nid_t cvt_nid;

struct hda_codec *codec;
- struct hdmi_eld sink_eld;
+
+ int num_devices;
+ u8 dev_list[AC_MAX_DEV_LIST_LEN];
+ struct hdmi_spec_per_device devices[HDA_MAX_DEVICES];
+
struct mutex lock;
struct delayed_work work;
struct snd_kcontrol *eld_ctl;
@@ -86,9 +101,6 @@ struct hdmi_spec_per_pin {
bool non_pcm;
bool chmap_set; /* channel-map override by ALSA API? */
unsigned char chmap[8]; /* ALSA API channel-map */
-#ifdef CONFIG_PROC_FS
- struct snd_info_entry *proc_entry;
-#endif
};

struct cea_channel_speaker_allocation;
@@ -409,7 +421,7 @@ static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol,

pin_idx = kcontrol->private_value;
per_pin = get_pin(spec, pin_idx);
- eld = &per_pin->sink_eld;
+ eld = &per_pin->devices[0].sink_eld;

mutex_lock(&per_pin->lock);
uinfo->count = eld->eld_valid ? eld->eld_size : 0;
@@ -429,7 +441,7 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,

pin_idx = kcontrol->private_value;
per_pin = get_pin(spec, pin_idx);
- eld = &per_pin->sink_eld;
+ eld = &per_pin->devices[0].sink_eld;

mutex_lock(&per_pin->lock);
if (eld->eld_size > ARRAY_SIZE(ucontrol->value.bytes.data)) {
@@ -549,60 +561,63 @@ static void hdmi_set_channel_count(struct hda_codec *codec,
*/

#ifdef CONFIG_PROC_FS
-static void print_eld_info(struct snd_info_entry *entry,
- struct snd_info_buffer *buffer)
+static void print_eld_info_device(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
{
- struct hdmi_spec_per_pin *per_pin = entry->private_data;
+ struct hdmi_spec_per_device *per_device = entry->private_data;

- mutex_lock(&per_pin->lock);
- snd_hdmi_print_eld_info(&per_pin->sink_eld, buffer);
- mutex_unlock(&per_pin->lock);
+ mutex_lock(&per_device->pin->lock);
+ snd_hdmi_print_eld_info(&per_device->sink_eld, buffer);
+ mutex_unlock(&per_device->pin->lock);
}

-static void write_eld_info(struct snd_info_entry *entry,
- struct snd_info_buffer *buffer)
+static void write_eld_info_device(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
{
- struct hdmi_spec_per_pin *per_pin = entry->private_data;
+ struct hdmi_spec_per_device *per_device = entry->private_data;

- mutex_lock(&per_pin->lock);
- snd_hdmi_write_eld_info(&per_pin->sink_eld, buffer);
- mutex_unlock(&per_pin->lock);
+ mutex_lock(&per_device->pin->lock);
+ snd_hdmi_write_eld_info(&per_device->sink_eld, buffer);
+ mutex_unlock(&per_device->pin->lock);
}

-static int eld_proc_new(struct hdmi_spec_per_pin *per_pin, int index)
+static int eld_device_proc_new(struct hdmi_spec_per_device *per_device, int pin_idx, int dev_idx)
{
char name[32];
- struct hda_codec *codec = per_pin->codec;
+ struct hda_codec *codec = per_device->pin->codec;
struct snd_info_entry *entry;
int err;

- snprintf(name, sizeof(name), "eld#%d.%d", codec->addr, index);
+ if (dev_idx == -1)
+ snprintf(name, sizeof(name), "eld#%d.%d", codec->addr, pin_idx);
+ else
+ snprintf(name, sizeof(name), "eld#%d.%d.%d", codec->addr, pin_idx, dev_idx);
err = snd_card_proc_new(codec->card, name, &entry);
if (err < 0)
return err;

- snd_info_set_text_ops(entry, per_pin, print_eld_info);
- entry->c.text.write = write_eld_info;
+ snd_info_set_text_ops(entry, per_device, print_eld_info_device);
+ entry->c.text.write = write_eld_info_device;
entry->mode |= S_IWUSR;
- per_pin->proc_entry = entry;
+ per_device->proc_entry = entry;

return 0;
}

-static void eld_proc_free(struct hdmi_spec_per_pin *per_pin)
+static void eld_device_proc_free(struct hdmi_spec_per_device *per_device)
{
- if (!per_pin->codec->bus->shutdown && per_pin->proc_entry) {
- snd_device_free(per_pin->codec->card, per_pin->proc_entry);
- per_pin->proc_entry = NULL;
+ if (!per_device->pin->codec->bus->shutdown && per_device->proc_entry) {
+ snd_device_free(per_device->pin->codec->card, per_device->proc_entry);
+ per_device->proc_entry = NULL;
}
}
#else
-static inline int eld_proc_new(struct hdmi_spec_per_pin *per_pin,
- int index)
+static inline int eld_device_proc_new(struct hdmi_spec_per_device *per_device,
+ int pin_idx, int dev_idx)
{
return 0;
}
-static inline void eld_proc_free(struct hdmi_spec_per_pin *per_pin)
+static inline void eld_device_proc_free(struct hdmi_spec_per_device *per_device)
{
}
#endif
@@ -1112,13 +1127,13 @@ static void hdmi_pin_setup_infoframe(struct hda_codec *codec,

static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
struct hdmi_spec_per_pin *per_pin,
+ struct hdmi_eld *eld,
bool non_pcm)
{
struct hdmi_spec *spec = codec->spec;
hda_nid_t pin_nid = per_pin->pin_nid;
int channels = per_pin->channels;
int active_channels;
- struct hdmi_eld *eld;
int ca, ordered_ca;

if (!channels)
@@ -1129,7 +1144,8 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
AC_VERB_SET_AMP_GAIN_MUTE,
AMP_OUT_UNMUTE);

- eld = &per_pin->sink_eld;
+ if (!eld)
+ eld = &per_pin->devices[0].sink_eld;

if (!non_pcm && per_pin->chmap_set)
ca = hdmi_manual_channel_allocation(channels, per_pin->chmap);
@@ -1191,7 +1207,7 @@ static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res)
return;
jack->jack_dirty = 1;

- codec_dbg(codec,
+ codec_info(codec,
"HDMI hot plug event: Codec=%d Pin=%d Device=%d Inactive=%d Presence_Detect=%d ELD_Valid=%d\n",
codec->addr, jack->nid, dev_entry, !!(res & AC_UNSOL_RES_IA),
!!(res & AC_UNSOL_RES_PD), !!(res & AC_UNSOL_RES_ELDV));
@@ -1449,7 +1465,7 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
if (snd_BUG_ON(pin_idx < 0))
return -EINVAL;
per_pin = get_pin(spec, pin_idx);
- eld = &per_pin->sink_eld;
+ eld = &per_pin->devices[0].sink_eld;

err = hdmi_choose_cvt(codec, pin_idx, &cvt_idx, &mux_idx);
if (err < 0)
@@ -1530,7 +1546,7 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
struct hda_codec *codec = per_pin->codec;
struct hdmi_spec *spec = codec->spec;
struct hdmi_eld *eld = &spec->temp_eld;
- struct hdmi_eld *pin_eld = &per_pin->sink_eld;
+ struct hdmi_eld *pin_eld;
hda_nid_t pin_nid = per_pin->pin_nid;
/*
* Always execute a GetPinSense verb here, even when called from
@@ -1544,20 +1560,41 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
bool update_eld = false;
bool eld_changed = false;
bool ret;
+ int device_num = 0;
+ bool need_repoll = false;
+ bool any_eld_valid = false;

snd_hda_power_up_pm(codec);
present = snd_hda_pin_sense(codec, pin_nid);

mutex_lock(&per_pin->lock);
- pin_eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE);
- if (pin_eld->monitor_present)
- eld->eld_valid = !!(present & AC_PINSENSE_ELDV);
- else
- eld->eld_valid = false;

- codec_dbg(codec,
- "HDMI status: Codec=%d Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
- codec->addr, pin_nid, pin_eld->monitor_present, eld->eld_valid);
+ if (codec->dp_mst)
+ per_pin->num_devices = snd_hda_get_devices(codec, pin_nid, per_pin->dev_list,
+ AC_MAX_DEV_LIST_LEN);
+next_device:
+ pin_eld = &per_pin->devices[device_num].sink_eld;
+ eld_changed = false;
+ update_eld = false;
+ if (per_pin->num_devices) {
+ pin_eld->monitor_present = !!(per_pin->dev_list[device_num] & AC_DE_PD);
+ if (pin_eld->monitor_present)
+ eld->eld_valid = !!(per_pin->dev_list[device_num] & AC_DE_ELDV);
+ else
+ eld->eld_valid = false;
+ if (eld->eld_valid)
+ snd_hda_codec_write(codec, pin_nid, 0, AC_VERB_SET_DEVICE_SEL, device_num);
+ } else {
+ pin_eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE);
+ if (pin_eld->monitor_present)
+ eld->eld_valid = !!(present & AC_PINSENSE_ELDV);
+ else
+ eld->eld_valid = false;
+ }
+
+ codec_info(codec,
+ "HDMI status: Codec=%d Pin=%d Device=%d Presence_Detect=%d ELD_Valid=%d\n",
+ codec->addr, pin_nid, device_num, pin_eld->monitor_present, eld->eld_valid);

if (eld->eld_valid) {
if (spec->ops.pin_get_eld(codec, pin_nid, eld->eld_buffer,
@@ -1573,11 +1610,11 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
if (eld->eld_valid) {
snd_hdmi_show_eld(codec, &eld->info);
update_eld = true;
+ any_eld_valid = true;
}
else if (repoll) {
- schedule_delayed_work(&per_pin->work,
- msecs_to_jiffies(300));
- goto unlock;
+ need_repoll = true;
+ goto skip_to_next_device;
}
}

@@ -1614,7 +1651,7 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
per_pin->mux_idx);
}

- hdmi_setup_audio_infoframe(codec, per_pin,
+ hdmi_setup_audio_infoframe(codec, per_pin, eld,
per_pin->non_pcm);
}
}
@@ -1623,8 +1660,19 @@ static bool hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
snd_ctl_notify(codec->card,
SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO,
&per_pin->eld_ctl->id);
- unlock:
- ret = !repoll || !pin_eld->monitor_present || pin_eld->eld_valid;
+skip_to_next_device:
+ if (codec->dp_mst) {
+ device_num++;
+ if (device_num < per_pin->num_devices)
+ goto next_device;
+ }
+
+ if (need_repoll) {
+ schedule_delayed_work(&per_pin->work,
+ msecs_to_jiffies(300));
+ repoll = true;
+ }
+ ret = !repoll || any_eld_valid;

jack = snd_hda_jack_tbl_get(codec, pin_nid);
if (jack)
@@ -1807,7 +1855,7 @@ static int generic_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
per_pin->channels = substream->runtime->channels;
per_pin->setup = true;

- hdmi_setup_audio_infoframe(codec, per_pin, non_pcm);
+ hdmi_setup_audio_infoframe(codec, per_pin, NULL, non_pcm);
mutex_unlock(&per_pin->lock);

if (spec->dyn_pin_out) {
@@ -2035,7 +2083,7 @@ static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
per_pin->chmap_set = true;
memcpy(per_pin->chmap, chmap, sizeof(chmap));
if (prepared)
- hdmi_setup_audio_infoframe(codec, per_pin, per_pin->non_pcm);
+ hdmi_setup_audio_infoframe(codec, per_pin, NULL, per_pin->non_pcm);
mutex_unlock(&per_pin->lock);

return 0;
@@ -2147,7 +2195,7 @@ static int generic_hdmi_build_controls(struct hda_codec *codec)
static int generic_hdmi_init_per_pins(struct hda_codec *codec)
{
struct hdmi_spec *spec = codec->spec;
- int pin_idx;
+ int pin_idx, dev_idx;

for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
@@ -2155,7 +2203,20 @@ static int generic_hdmi_init_per_pins(struct hda_codec *codec)
per_pin->codec = codec;
mutex_init(&per_pin->lock);
INIT_DELAYED_WORK(&per_pin->work, hdmi_repoll_eld);
- eld_proc_new(per_pin, pin_idx);
+
+ if (codec->dp_mst) {
+ for (dev_idx = 0; dev_idx < HDA_MAX_DEVICES; dev_idx++) {
+ per_pin->devices[dev_idx].device_idx = dev_idx;
+ per_pin->devices[dev_idx].pin = per_pin;
+
+ eld_device_proc_new(&per_pin->devices[dev_idx], pin_idx, dev_idx);
+ }
+ } else {
+ per_pin->num_devices = 0;
+ per_pin->devices[0].device_idx = 0;
+ per_pin->devices[0].pin = per_pin;
+ eld_device_proc_new(&per_pin->devices[0], pin_idx, -1);
+ }
}
return 0;
}
@@ -2191,13 +2252,19 @@ static void hdmi_array_free(struct hdmi_spec *spec)
static void generic_hdmi_free(struct hda_codec *codec)
{
struct hdmi_spec *spec = codec->spec;
- int pin_idx;
+ int pin_idx, dev_idx;

for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);

cancel_delayed_work_sync(&per_pin->work);
- eld_proc_free(per_pin);
+ if (per_pin->num_devices) {
+ for (dev_idx = 0; dev_idx < per_pin->num_devices; dev_idx++) {
+ struct hdmi_spec_per_device *per_device = &per_pin->devices[dev_idx];
+ eld_device_proc_free(per_device);
+ }
+ } else
+ eld_device_proc_free(&per_pin->devices[0]);
}

hdmi_array_free(spec);
@@ -2333,6 +2400,7 @@ static int patch_generic_hdmi(struct hda_codec *codec)
if (is_haswell_plus(codec)) {
intel_haswell_enable_all_pins(codec, true);
intel_haswell_fixup_enable_dp12(codec);
+ codec->dp_mst = true;
}

if (is_haswell_plus(codec) || is_valleyview_plus(codec))
@@ -2346,7 +2414,6 @@ static int patch_generic_hdmi(struct hda_codec *codec)
codec->patch_ops = generic_hdmi_patch_ops;
if (is_haswell_plus(codec)) {
codec->patch_ops.set_power_state = haswell_set_power_state;
- codec->dp_mst = true;
}

generic_hdmi_init_per_pins(codec);
--
2.4.1