|
@@ -29,6 +29,7 @@
|
|
#include <sound/hdaudio_ext.h>
|
|
#include <sound/hdaudio_ext.h>
|
|
#include <sound/hda_i915.h>
|
|
#include <sound/hda_i915.h>
|
|
#include <sound/pcm_drm_eld.h>
|
|
#include <sound/pcm_drm_eld.h>
|
|
|
|
+#include <sound/hda_chmap.h>
|
|
#include "../../hda/local.h"
|
|
#include "../../hda/local.h"
|
|
#include "hdac_hdmi.h"
|
|
#include "hdac_hdmi.h"
|
|
|
|
|
|
@@ -60,11 +61,17 @@ struct hdac_hdmi_cvt {
|
|
struct hdac_hdmi_cvt_params params;
|
|
struct hdac_hdmi_cvt_params params;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/* Currently only spk_alloc, more to be added */
|
|
|
|
+struct hdac_hdmi_parsed_eld {
|
|
|
|
+ u8 spk_alloc;
|
|
|
|
+};
|
|
|
|
+
|
|
struct hdac_hdmi_eld {
|
|
struct hdac_hdmi_eld {
|
|
bool monitor_present;
|
|
bool monitor_present;
|
|
bool eld_valid;
|
|
bool eld_valid;
|
|
int eld_size;
|
|
int eld_size;
|
|
char eld_buffer[ELD_MAX_SIZE];
|
|
char eld_buffer[ELD_MAX_SIZE];
|
|
|
|
+ struct hdac_hdmi_parsed_eld info;
|
|
};
|
|
};
|
|
|
|
|
|
struct hdac_hdmi_pin {
|
|
struct hdac_hdmi_pin {
|
|
@@ -76,6 +83,10 @@ struct hdac_hdmi_pin {
|
|
struct hdac_ext_device *edev;
|
|
struct hdac_ext_device *edev;
|
|
int repoll_count;
|
|
int repoll_count;
|
|
struct delayed_work work;
|
|
struct delayed_work work;
|
|
|
|
+ struct mutex lock;
|
|
|
|
+ bool chmap_set;
|
|
|
|
+ unsigned char chmap[8]; /* ALSA API channel-map */
|
|
|
|
+ int channels; /* current number of channels */
|
|
};
|
|
};
|
|
|
|
|
|
struct hdac_hdmi_pcm {
|
|
struct hdac_hdmi_pcm {
|
|
@@ -100,8 +111,22 @@ struct hdac_hdmi_priv {
|
|
int num_pin;
|
|
int num_pin;
|
|
int num_cvt;
|
|
int num_cvt;
|
|
struct mutex pin_mutex;
|
|
struct mutex pin_mutex;
|
|
|
|
+ struct hdac_chmap chmap;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static struct hdac_hdmi_pcm *get_hdmi_pcm_from_id(struct hdac_hdmi_priv *hdmi,
|
|
|
|
+ int pcm_idx)
|
|
|
|
+{
|
|
|
|
+ struct hdac_hdmi_pcm *pcm;
|
|
|
|
+
|
|
|
|
+ list_for_each_entry(pcm, &hdmi->pcm_list, head) {
|
|
|
|
+ if (pcm->pcm_id == pcm_idx)
|
|
|
|
+ return pcm;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return NULL;
|
|
|
|
+}
|
|
|
|
+
|
|
static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev)
|
|
static inline struct hdac_ext_device *to_hda_ext_device(struct device *dev)
|
|
{
|
|
{
|
|
struct hdac_device *hdac = dev_to_hdac_dev(dev);
|
|
struct hdac_device *hdac = dev_to_hdac_dev(dev);
|
|
@@ -278,26 +303,31 @@ static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac,
|
|
int i;
|
|
int i;
|
|
const u8 *eld_buf;
|
|
const u8 *eld_buf;
|
|
u8 conn_type;
|
|
u8 conn_type;
|
|
- int channels = 2;
|
|
|
|
|
|
+ int channels, ca;
|
|
|
|
|
|
list_for_each_entry(pin, &hdmi->pin_list, head) {
|
|
list_for_each_entry(pin, &hdmi->pin_list, head) {
|
|
if (pin->nid == pin_nid)
|
|
if (pin->nid == pin_nid)
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ ca = snd_hdac_channel_allocation(&hdac->hdac, pin->eld.info.spk_alloc,
|
|
|
|
+ pin->channels, pin->chmap_set, true, pin->chmap);
|
|
|
|
+
|
|
|
|
+ channels = snd_hdac_get_active_channels(ca);
|
|
|
|
+ hdmi->chmap.ops.set_channel_count(&hdac->hdac, cvt_nid, channels);
|
|
|
|
+
|
|
|
|
+ snd_hdac_setup_channel_mapping(&hdmi->chmap, pin->nid, false, ca,
|
|
|
|
+ pin->channels, pin->chmap, pin->chmap_set);
|
|
|
|
+
|
|
eld_buf = pin->eld.eld_buffer;
|
|
eld_buf = pin->eld.eld_buffer;
|
|
conn_type = drm_eld_get_conn_type(eld_buf);
|
|
conn_type = drm_eld_get_conn_type(eld_buf);
|
|
|
|
|
|
- /* setup channel count */
|
|
|
|
- snd_hdac_codec_write(&hdac->hdac, cvt_nid, 0,
|
|
|
|
- AC_VERB_SET_CVT_CHAN_COUNT, channels - 1);
|
|
|
|
-
|
|
|
|
switch (conn_type) {
|
|
switch (conn_type) {
|
|
case DRM_ELD_CONN_TYPE_HDMI:
|
|
case DRM_ELD_CONN_TYPE_HDMI:
|
|
hdmi_audio_infoframe_init(&frame);
|
|
hdmi_audio_infoframe_init(&frame);
|
|
|
|
|
|
- /* Default stereo for now */
|
|
|
|
frame.channels = channels;
|
|
frame.channels = channels;
|
|
|
|
+ frame.channel_allocation = ca;
|
|
|
|
|
|
ret = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
|
|
ret = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer));
|
|
if (ret < 0)
|
|
if (ret < 0)
|
|
@@ -311,7 +341,7 @@ static int hdac_hdmi_setup_audio_infoframe(struct hdac_ext_device *hdac,
|
|
dp_ai.len = 0x1b;
|
|
dp_ai.len = 0x1b;
|
|
dp_ai.ver = 0x11 << 2;
|
|
dp_ai.ver = 0x11 << 2;
|
|
dp_ai.CC02_CT47 = channels - 1;
|
|
dp_ai.CC02_CT47 = channels - 1;
|
|
- dp_ai.CA = 0;
|
|
|
|
|
|
+ dp_ai.CA = ca;
|
|
|
|
|
|
dip = (u8 *)&dp_ai;
|
|
dip = (u8 *)&dp_ai;
|
|
break;
|
|
break;
|
|
@@ -370,17 +400,23 @@ static int hdac_hdmi_playback_prepare(struct snd_pcm_substream *substream,
|
|
struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai);
|
|
struct hdac_ext_device *hdac = snd_soc_dai_get_drvdata(dai);
|
|
struct hdac_hdmi_priv *hdmi = hdac->private_data;
|
|
struct hdac_hdmi_priv *hdmi = hdac->private_data;
|
|
struct hdac_hdmi_dai_pin_map *dai_map;
|
|
struct hdac_hdmi_dai_pin_map *dai_map;
|
|
|
|
+ struct hdac_hdmi_pin *pin;
|
|
struct hdac_ext_dma_params *dd;
|
|
struct hdac_ext_dma_params *dd;
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
dai_map = &hdmi->dai_map[dai->id];
|
|
dai_map = &hdmi->dai_map[dai->id];
|
|
|
|
+ pin = dai_map->pin;
|
|
|
|
|
|
dd = (struct hdac_ext_dma_params *)snd_soc_dai_get_dma_data(dai, substream);
|
|
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",
|
|
dev_dbg(&hdac->hdac.dev, "stream tag from cpu dai %d format in cvt 0x%x\n",
|
|
dd->stream_tag, dd->format);
|
|
dd->stream_tag, dd->format);
|
|
|
|
|
|
|
|
+ mutex_lock(&pin->lock);
|
|
|
|
+ pin->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,
|
|
dai_map->pin->nid);
|
|
dai_map->pin->nid);
|
|
|
|
+ mutex_unlock(&pin->lock);
|
|
if (ret < 0)
|
|
if (ret < 0)
|
|
return ret;
|
|
return ret;
|
|
|
|
|
|
@@ -640,6 +676,12 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream,
|
|
snd_hdac_codec_write(&hdac->hdac, dai_map->pin->nid, 0,
|
|
snd_hdac_codec_write(&hdac->hdac, dai_map->pin->nid, 0,
|
|
AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
|
|
AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
|
|
|
|
|
|
|
|
+ mutex_lock(&dai_map->pin->lock);
|
|
|
|
+ dai_map->pin->chmap_set = false;
|
|
|
|
+ memset(dai_map->pin->chmap, 0, sizeof(dai_map->pin->chmap));
|
|
|
|
+ dai_map->pin->channels = 0;
|
|
|
|
+ mutex_unlock(&dai_map->pin->lock);
|
|
|
|
+
|
|
dai_map->pin = NULL;
|
|
dai_map->pin = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -647,10 +689,19 @@ static void hdac_hdmi_pcm_close(struct snd_pcm_substream *substream,
|
|
static int
|
|
static int
|
|
hdac_hdmi_query_cvt_params(struct hdac_device *hdac, struct hdac_hdmi_cvt *cvt)
|
|
hdac_hdmi_query_cvt_params(struct hdac_device *hdac, struct hdac_hdmi_cvt *cvt)
|
|
{
|
|
{
|
|
|
|
+ unsigned int chans;
|
|
|
|
+ struct hdac_ext_device *edev = to_ehdac_device(hdac);
|
|
|
|
+ struct hdac_hdmi_priv *hdmi = edev->private_data;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
- /* Only stereo supported as of now */
|
|
|
|
- cvt->params.channels_min = cvt->params.channels_max = 2;
|
|
|
|
|
|
+ chans = get_wcaps(hdac, cvt->nid);
|
|
|
|
+ chans = get_wcaps_channels(chans);
|
|
|
|
+
|
|
|
|
+ cvt->params.channels_min = 2;
|
|
|
|
+
|
|
|
|
+ cvt->params.channels_max = chans;
|
|
|
|
+ if (chans > hdmi->chmap.channels_max)
|
|
|
|
+ hdmi->chmap.channels_max = chans;
|
|
|
|
|
|
err = snd_hdac_query_supported_pcm(hdac, cvt->nid,
|
|
err = snd_hdac_query_supported_pcm(hdac, cvt->nid,
|
|
&cvt->params.rates,
|
|
&cvt->params.rates,
|
|
@@ -1008,6 +1059,12 @@ static int hdac_hdmi_add_cvt(struct hdac_ext_device *edev, hda_nid_t nid)
|
|
return hdac_hdmi_query_cvt_params(&edev->hdac, cvt);
|
|
return hdac_hdmi_query_cvt_params(&edev->hdac, cvt);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void hdac_hdmi_parse_eld(struct hdac_ext_device *edev,
|
|
|
|
+ struct hdac_hdmi_pin *pin)
|
|
|
|
+{
|
|
|
|
+ pin->eld.info.spk_alloc = pin->eld.eld_buffer[DRM_ELD_SPEAKER];
|
|
|
|
+}
|
|
|
|
+
|
|
static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
|
|
static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
|
|
{
|
|
{
|
|
struct hdac_ext_device *edev = pin->edev;
|
|
struct hdac_ext_device *edev = pin->edev;
|
|
@@ -1065,6 +1122,7 @@ static void hdac_hdmi_present_sense(struct hdac_hdmi_pin *pin, int repoll)
|
|
|
|
|
|
snd_jack_report(pcm->jack, SND_JACK_AVOUT);
|
|
snd_jack_report(pcm->jack, SND_JACK_AVOUT);
|
|
}
|
|
}
|
|
|
|
+ hdac_hdmi_parse_eld(edev, pin);
|
|
|
|
|
|
print_hex_dump_bytes("ELD: ", DUMP_PREFIX_OFFSET,
|
|
print_hex_dump_bytes("ELD: ", DUMP_PREFIX_OFFSET,
|
|
pin->eld.eld_buffer, pin->eld.eld_size);
|
|
pin->eld.eld_buffer, pin->eld.eld_size);
|
|
@@ -1123,6 +1181,7 @@ static int hdac_hdmi_add_pin(struct hdac_ext_device *edev, hda_nid_t nid)
|
|
hdmi->num_pin++;
|
|
hdmi->num_pin++;
|
|
|
|
|
|
pin->edev = edev;
|
|
pin->edev = edev;
|
|
|
|
+ mutex_init(&pin->lock);
|
|
INIT_DELAYED_WORK(&pin->work, hdac_hdmi_repoll_eld);
|
|
INIT_DELAYED_WORK(&pin->work, hdac_hdmi_repoll_eld);
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
@@ -1342,6 +1401,19 @@ static struct i915_audio_component_audio_ops aops = {
|
|
.pin_eld_notify = hdac_hdmi_eld_notify_cb,
|
|
.pin_eld_notify = hdac_hdmi_eld_notify_cb,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static struct snd_pcm *hdac_hdmi_get_pcm_from_id(struct snd_soc_card *card,
|
|
|
|
+ int device)
|
|
|
|
+{
|
|
|
|
+ struct snd_soc_pcm_runtime *rtd;
|
|
|
|
+
|
|
|
|
+ list_for_each_entry(rtd, &card->rtd_list, list) {
|
|
|
|
+ if (rtd->pcm && (rtd->pcm->device == device))
|
|
|
|
+ return rtd->pcm;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return NULL;
|
|
|
|
+}
|
|
|
|
+
|
|
int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
|
|
int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
|
|
{
|
|
{
|
|
char jack_name[NAME_SIZE];
|
|
char jack_name[NAME_SIZE];
|
|
@@ -1351,6 +1423,8 @@ int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
|
|
snd_soc_component_get_dapm(&codec->component);
|
|
snd_soc_component_get_dapm(&codec->component);
|
|
struct hdac_hdmi_priv *hdmi = edev->private_data;
|
|
struct hdac_hdmi_priv *hdmi = edev->private_data;
|
|
struct hdac_hdmi_pcm *pcm;
|
|
struct hdac_hdmi_pcm *pcm;
|
|
|
|
+ struct snd_pcm *snd_pcm;
|
|
|
|
+ int err;
|
|
|
|
|
|
/*
|
|
/*
|
|
* this is a new PCM device, create new pcm and
|
|
* this is a new PCM device, create new pcm and
|
|
@@ -1362,6 +1436,18 @@ int hdac_hdmi_jack_init(struct snd_soc_dai *dai, int device)
|
|
pcm->pcm_id = device;
|
|
pcm->pcm_id = device;
|
|
pcm->cvt = hdmi->dai_map[dai->id].cvt;
|
|
pcm->cvt = hdmi->dai_map[dai->id].cvt;
|
|
|
|
|
|
|
|
+ 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);
|
|
|
|
+ if (err < 0) {
|
|
|
|
+ dev_err(&edev->hdac.dev,
|
|
|
|
+ "chmap control add failed with err: %d for pcm: %d\n",
|
|
|
|
+ err, device);
|
|
|
|
+ kfree(pcm);
|
|
|
|
+ return err;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
list_add_tail(&pcm->head, &hdmi->pcm_list);
|
|
list_add_tail(&pcm->head, &hdmi->pcm_list);
|
|
|
|
|
|
sprintf(jack_name, "HDMI/DP, pcm=%d Jack", device);
|
|
sprintf(jack_name, "HDMI/DP, pcm=%d Jack", device);
|
|
@@ -1483,6 +1569,60 @@ static struct snd_soc_codec_driver hdmi_hda_codec = {
|
|
.idle_bias_off = true,
|
|
.idle_bias_off = true,
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static void hdac_hdmi_get_chmap(struct hdac_device *hdac, int pcm_idx,
|
|
|
|
+ unsigned char *chmap)
|
|
|
|
+{
|
|
|
|
+ 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_pin *pin = pcm->pin;
|
|
|
|
+
|
|
|
|
+ /* chmap is already set to 0 in caller */
|
|
|
|
+ if (!pin)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ memcpy(chmap, pin->chmap, ARRAY_SIZE(pin->chmap));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void hdac_hdmi_set_chmap(struct hdac_device *hdac, int pcm_idx,
|
|
|
|
+ unsigned char *chmap, int prepared)
|
|
|
|
+{
|
|
|
|
+ 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_pin *pin = pcm->pin;
|
|
|
|
+
|
|
|
|
+ mutex_lock(&pin->lock);
|
|
|
|
+ pin->chmap_set = true;
|
|
|
|
+ memcpy(pin->chmap, chmap, ARRAY_SIZE(pin->chmap));
|
|
|
|
+ if (prepared)
|
|
|
|
+ hdac_hdmi_setup_audio_infoframe(edev, pcm->cvt->nid, pin->nid);
|
|
|
|
+ mutex_unlock(&pin->lock);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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_pin *pin = pcm->pin;
|
|
|
|
+
|
|
|
|
+ return pin ? true:false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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_pin *pin = pcm->pin;
|
|
|
|
+
|
|
|
|
+ if (!pin || !pin->eld.eld_valid)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ return pin->eld.info.spk_alloc;
|
|
|
|
+}
|
|
|
|
+
|
|
static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
|
|
static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
|
|
{
|
|
{
|
|
struct hdac_device *codec = &edev->hdac;
|
|
struct hdac_device *codec = &edev->hdac;
|
|
@@ -1501,6 +1641,11 @@ static int hdac_hdmi_dev_probe(struct hdac_ext_device *edev)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
|
|
|
|
edev->private_data = hdmi_priv;
|
|
edev->private_data = hdmi_priv;
|
|
|
|
+ snd_hdac_register_chmap_ops(codec, &hdmi_priv->chmap);
|
|
|
|
+ hdmi_priv->chmap.ops.get_chmap = hdac_hdmi_get_chmap;
|
|
|
|
+ hdmi_priv->chmap.ops.set_chmap = hdac_hdmi_set_chmap;
|
|
|
|
+ hdmi_priv->chmap.ops.is_pcm_attached = is_hdac_hdmi_pcm_attached;
|
|
|
|
+ hdmi_priv->chmap.ops.get_spk_alloc = hdac_hdmi_get_spk_alloc;
|
|
|
|
|
|
dev_set_drvdata(&codec->dev, edev);
|
|
dev_set_drvdata(&codec->dev, edev);
|
|
|
|
|