Merge pull request #2680 from fritsch/xvba-last

Fusion: Give them the rest
This commit is contained in:
Stefan Saraev 2013-10-05 10:58:05 -07:00
commit 6f57c5ec05
2 changed files with 805 additions and 3 deletions

View File

@ -0,0 +1,805 @@
Combined patch for ATI/AMD HDMI.
Actual patchset has been posted to ALSA-devel.
--
Anssi Hannula <anssi.hannula@iki.fi>
diff --git a/sound/pci/hda/hda_eld.c b/sound/pci/hda/hda_eld.c
index d0d7ac1e..750841e 100644
--- a/sound/pci/hda/hda_eld.c
+++ b/sound/pci/hda/hda_eld.c
@@ -2,6 +2,7 @@
* Generic routines and proc interface for ELD(EDID Like Data) information
*
* Copyright(c) 2008 Intel Corporation.
+ * Copyright (c) 2013 Anssi Hannula <anssi.hannula@iki.fi>
*
* Authors:
* Wu Fengguang <wfg@linux.intel.com>
@@ -316,6 +317,9 @@ int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid)
AC_DIPSIZE_ELD_BUF);
}
+static int atihdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
+ unsigned char *buf, int *eld_size);
+
int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
unsigned char *buf, int *eld_size)
{
@@ -323,6 +327,9 @@ int snd_hdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
int ret = 0;
int size;
+ if (is_atihdmi(codec))
+ return atihdmi_get_eld(codec, nid, buf, eld_size);
+
/*
* ELD size is initialized to zero in caller function. If no errors and
* ELD is valid, actual eld_size is assigned.
@@ -671,3 +678,153 @@ void snd_hdmi_eld_update_pcm_info(struct parsed_hdmi_eld *e,
hinfo->maxbps = min(hinfo->maxbps, maxbps);
hinfo->channels_max = min(hinfo->channels_max, channels_max);
}
+
+
+/* ATI/AMD specific stuff (ELD emulation) */
+
+#define ATI_VERB_SET_AUDIO_DESCRIPTOR 0x776
+#define ATI_VERB_SET_SINK_INFO_INDEX 0x780
+#define ATI_VERB_GET_SPEAKER_ALLOCATION 0xf70
+#define ATI_VERB_GET_AUDIO_DESCRIPTOR 0xf76
+#define ATI_VERB_GET_AUDIO_VIDEO_DELAY 0xf7b
+#define ATI_VERB_GET_SINK_INFO_INDEX 0xf80
+#define ATI_VERB_GET_SINK_INFO_DATA 0xf81
+
+#define ATI_SPKALLOC_SPKALLOC 0x007f
+#define ATI_SPKALLOC_TYPE_HDMI 0x0100
+#define ATI_SPKALLOC_TYPE_DISPLAYPORT 0x0200
+
+/* first three bytes are just standard SAD */
+#define ATI_AUDIODESC_CHANNELS 0x00000007
+#define ATI_AUDIODESC_RATES 0x0000ff00
+#define ATI_AUDIODESC_LPCM_STEREO_RATES 0xff000000
+
+/* in standard HDMI VSDB format */
+#define ATI_DELAY_VIDEO_LATENCY 0x000000ff
+#define ATI_DELAY_AUDIO_LATENCY 0x0000ff00
+
+enum ati_sink_info_idx {
+ ATI_INFO_IDX_MANUFACTURER_ID = 0,
+ ATI_INFO_IDX_PRODUCT_ID = 1,
+ ATI_INFO_IDX_SINK_DESC_LEN = 2,
+ ATI_INFO_IDX_PORT_ID_LOW = 3,
+ ATI_INFO_IDX_PORT_ID_HIGH = 4,
+ ATI_INFO_IDX_SINK_DESC_FIRST = 5,
+ ATI_INFO_IDX_SINK_DESC_LAST = 22, /* max len 18 bytes */
+};
+
+static int atihdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
+ unsigned char *buf, int *eld_size)
+{
+ int spkalloc, ati_sad, aud_synch;
+ int sink_desc_len = 0;
+ int pos, i;
+
+ /* ATI/AMD does not have ELD, emulate it */
+
+ spkalloc = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SPEAKER_ALLOCATION, 0);
+
+ if (!spkalloc) {
+ snd_printd(KERN_INFO "HDMI ATI/AMD: no speaker allocation for ELD\n");
+ return -EINVAL;
+ }
+
+ memset(buf, 0, ELD_FIXED_BYTES + ELD_MAX_MNL + ELD_MAX_SAD * 3);
+
+ /* version */
+ buf[0] = ELD_VER_CEA_861D << 3;
+
+ /* speaker allocation from EDID */
+ buf[7] = spkalloc & ATI_SPKALLOC_SPKALLOC;
+
+ /* is DisplayPort? */
+ if (spkalloc & ATI_SPKALLOC_TYPE_DISPLAYPORT)
+ buf[5] |= 0x04;
+
+ pos = ELD_FIXED_BYTES;
+
+ if (is_amdhdmi_rev3(codec)) {
+ int sink_info;
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PORT_ID_LOW);
+ sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ put_unaligned_le32(sink_info, buf + 8);
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PORT_ID_HIGH);
+ sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ put_unaligned_le32(sink_info, buf + 12);
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_MANUFACTURER_ID);
+ sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ put_unaligned_le16(sink_info, buf + 16);
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_PRODUCT_ID);
+ sink_info = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ put_unaligned_le16(sink_info, buf + 18);
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_SINK_DESC_LEN);
+ sink_desc_len = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+
+ if (sink_desc_len > ELD_MAX_MNL) {
+ snd_printd(KERN_INFO "HDMI ATI/AMD: Truncating HDMI sink description with length %d\n",
+ sink_desc_len);
+ sink_desc_len = ELD_MAX_MNL;
+ }
+
+ buf[4] |= sink_desc_len;
+
+ for (i = 0; i < sink_desc_len; i++) {
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_SINK_INFO_INDEX, ATI_INFO_IDX_SINK_DESC_FIRST + i);
+ buf[pos++] = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_SINK_INFO_DATA, 0);
+ }
+ }
+
+ for (i = AUDIO_CODING_TYPE_LPCM; i <= AUDIO_CODING_TYPE_WMAPRO; i++) {
+ if (i == AUDIO_CODING_TYPE_SACD || i == AUDIO_CODING_TYPE_DST)
+ continue; /* not handled by ATI/AMD */
+
+ snd_hda_codec_write(codec, nid, 0, ATI_VERB_SET_AUDIO_DESCRIPTOR, i << 3);
+ ati_sad = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_AUDIO_DESCRIPTOR, 0);
+
+ if (ati_sad & ATI_AUDIODESC_RATES) {
+ /* format is supported, copy SAD as-is */
+ buf[pos++] = (ati_sad & 0x0000ff) >> 0;
+ buf[pos++] = (ati_sad & 0x00ff00) >> 8;
+ buf[pos++] = (ati_sad & 0xff0000) >> 16;
+ }
+
+ if (i == AUDIO_CODING_TYPE_LPCM
+ && (ati_sad & ATI_AUDIODESC_LPCM_STEREO_RATES)
+ && (ati_sad & ATI_AUDIODESC_LPCM_STEREO_RATES) >> 16 != (ati_sad & ATI_AUDIODESC_RATES)) {
+ /* for PCM there is a separate stereo rate mask */
+ buf[pos++] = ((ati_sad & 0x000000ff) & ~ATI_AUDIODESC_CHANNELS) | 0x1;
+ /* rates from the extra byte */
+ buf[pos++] = (ati_sad & 0xff000000) >> 24;
+ buf[pos++] = (ati_sad & 0x00ff0000) >> 16;
+ }
+ }
+
+ if (pos == ELD_FIXED_BYTES + sink_desc_len) {
+ snd_printd(KERN_INFO "HDMI ATI/AMD: no audio descriptors for ELD\n");
+ return -EINVAL;
+ }
+
+ aud_synch = snd_hda_codec_read(codec, nid, 0, ATI_VERB_GET_AUDIO_VIDEO_DELAY, 0);
+ if ((aud_synch & ATI_DELAY_VIDEO_LATENCY) && (aud_synch & ATI_DELAY_AUDIO_LATENCY)) {
+ int video_latency = (aud_synch & ATI_DELAY_VIDEO_LATENCY) - 1;
+ int audio_latency = ((aud_synch & ATI_DELAY_AUDIO_LATENCY) >> 8) - 1;
+
+ if (video_latency > audio_latency)
+ buf[6] = min(video_latency - audio_latency, 0xfa);
+ }
+
+ /* Baseline length */
+ buf[2] = pos - 4;
+
+ /* SAD count */
+ buf[5] |= ((pos - ELD_FIXED_BYTES - sink_desc_len) / 3) << 4;
+
+ *eld_size = pos;
+
+ return 0;
+}
diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h
index 2e7493e..7c0b89e 100644
--- a/sound/pci/hda/hda_local.h
+++ b/sound/pci/hda/hda_local.h
@@ -786,4 +786,9 @@ static inline void snd_hda_eld_proc_free(struct hda_codec *codec,
#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80
void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen);
+/* shared with patch_hdmi.c and hda_eld.c */
+#define is_atihdmi(codec) (((codec)->vendor_id & 0xffff0000) == 0x10020000)
+#define is_amdhdmi_rev3(codec) \
+ ((codec)->vendor_id == 0x1002791a && ((codec)->revision_id & 0xff00) >= 0x0300)
+
#endif /* __SOUND_HDA_LOCAL_H */
diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index 3d8cd044..22f30fe 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -6,6 +6,7 @@
* Copyright (c) 2006 ATI Technologies Inc.
* Copyright (c) 2008 NVIDIA Corp. All rights reserved.
* Copyright (c) 2008 Wei Ni <wni@nvidia.com>
+ * Copyright (c) 2013 Anssi Hannula <anssi.hannula@iki.fi>
*
* Authors:
* Wu Fengguang <wfg@linux.intel.com>
@@ -46,6 +47,9 @@ MODULE_PARM_DESC(static_hdmi_pcm, "Don't restrict PCM parameters per ELD info");
#define is_haswell(codec) ((codec)->vendor_id == 0x80862807)
+/* is_atihdmi() and is_amdhdmi_rev3() are in hda_local.h */
+#define has_amd_full_remap_support(codec) is_amdhdmi_rev3(codec)
+
struct hdmi_spec_per_cvt {
hda_nid_t cvt_nid;
int assigned;
@@ -89,7 +93,7 @@ struct hdmi_spec {
struct hdmi_eld temp_eld;
/*
- * Non-generic ATI/NVIDIA specific
+ * Non-generic VIA/NVIDIA specific
*/
struct hda_multi_out multiout;
struct hda_pcm_stream pcm_playback;
@@ -573,6 +577,20 @@ static int hdmi_channel_allocation(struct hdmi_eld *eld, int channels)
return ca;
}
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+static int atihdmi_get_chan_slot(struct hda_codec *codec, hda_nid_t pin_nid, int asp_slot);
+
+static int hdmi_get_chan_slot(struct hda_codec *codec, hda_nid_t pin_nid, int asp_slot)
+{
+ if (is_atihdmi(codec))
+ return atihdmi_get_chan_slot(codec, pin_nid, asp_slot);
+
+ return snd_hda_codec_read(codec, pin_nid, 0,
+ AC_VERB_GET_HDMI_CHAN_SLOT,
+ asp_slot);
+}
+#endif
+
static void hdmi_debug_channel_mapping(struct hda_codec *codec,
hda_nid_t pin_nid)
{
@@ -581,14 +599,26 @@ static void hdmi_debug_channel_mapping(struct hda_codec *codec,
int slot;
for (i = 0; i < 8; i++) {
- slot = snd_hda_codec_read(codec, pin_nid, 0,
- AC_VERB_GET_HDMI_CHAN_SLOT, i);
+ slot = hdmi_get_chan_slot(codec, pin_nid, i);
printk(KERN_DEBUG "HDMI: ASP channel %d => slot %d\n",
slot >> 4, slot & 0xf);
}
#endif
}
+static int atihdmi_set_chan_slot(struct hda_codec *codec, hda_nid_t pin_nid,
+ int chanslot_setup);
+
+static int hdmi_set_chan_slot(struct hda_codec *codec, hda_nid_t pin_nid,
+ int chanslot_setup)
+{
+ if (is_atihdmi(codec))
+ return atihdmi_set_chan_slot(codec, pin_nid, chanslot_setup);
+
+ return snd_hda_codec_write(codec, pin_nid, 0,
+ AC_VERB_SET_HDMI_CHAN_SLOT,
+ chanslot_setup);
+}
static void hdmi_std_setup_channel_mapping(struct hda_codec *codec,
hda_nid_t pin_nid,
@@ -617,9 +647,8 @@ static void hdmi_std_setup_channel_mapping(struct hda_codec *codec,
}
for (i = 0; i < 8; i++) {
- err = snd_hda_codec_write(codec, pin_nid, 0,
- AC_VERB_SET_HDMI_CHAN_SLOT,
- non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i]);
+ err = hdmi_set_chan_slot(codec, pin_nid,
+ non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i]);
if (err) {
snd_printdd(KERN_NOTICE
"HDMI: channel mapping failed\n");
@@ -728,8 +757,7 @@ static int hdmi_manual_setup_channel_mapping(struct hda_codec *codec,
else
val = 0xf;
val |= (i << 4);
- err = snd_hda_codec_write(codec, pin_nid, 0,
- AC_VERB_SET_HDMI_CHAN_SLOT, val);
+ err = hdmi_set_chan_slot(codec, pin_nid, val);
if (err)
return -EINVAL;
}
@@ -883,6 +911,8 @@ static bool hdmi_infoframe_uptodate(struct hda_codec *codec, hda_nid_t pin_nid,
return true;
}
+static void atihdmi_set_ca(struct hda_codec *codec, hda_nid_t pin_nid, int ca);
+
static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
struct hdmi_spec_per_pin *per_pin,
bool non_pcm)
@@ -912,6 +942,16 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec,
if (ca < 0)
ca = 0;
+ if (is_atihdmi(codec)) {
+ /* for ATI/AMD we just want to map channels and set ca */
+ hdmi_setup_channel_mapping(codec, pin_nid, non_pcm, ca,
+ channels, per_pin->chmap,
+ per_pin->chmap_set);
+ atihdmi_set_ca(codec, pin_nid, ca);
+ per_pin->non_pcm = non_pcm;
+ return;
+ }
+
memset(&ai, 0, sizeof(ai));
if (eld->info.conn_type == 0) { /* HDMI */
struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi;
@@ -1100,7 +1140,7 @@ static int hdmi_setup_stream(struct hda_codec *codec, hda_nid_t cvt_nid,
new_pinctl);
}
- if (is_hbr_format(format) && !new_pinctl) {
+ if (is_hbr_format(format) && !new_pinctl && !is_atihdmi(codec)) {
snd_printdd("hdmi_setup_stream: HBR is not supported\n");
return -EINVAL;
}
@@ -1603,6 +1643,8 @@ static int hdmi_chmap_ctl_info(struct snd_kcontrol *kcontrol,
return 0;
}
+static int atihdmi_swap_fc_lfe(int pos);
+
static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
unsigned int size, unsigned int __user *tlv)
{
@@ -1613,6 +1655,10 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
FL | FR | RL | RR | LFE | FC | RLC | RRC;
unsigned int __user *dst;
int chs, count = 0;
+ int tlv_type = SNDRV_CTL_TLVT_CHMAP_VAR;
+
+ if (is_atihdmi(codec) && !has_amd_full_remap_support(codec))
+ tlv_type = SNDRV_CTL_TLVT_CHMAP_PAIRED;
if (size < 8)
return -ENOMEM;
@@ -1620,19 +1666,35 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
return -EFAULT;
size -= 8;
dst = tlv + 2;
- for (chs = 2; chs <= spec->channels_max; chs++) {
+ for (chs = 2; chs <= spec->channels_max;
+ chs += (tlv_type == SNDRV_CTL_TLVT_CHMAP_PAIRED) ? 2 : 1) {
int i, c;
struct cea_channel_speaker_allocation *cap;
cap = channel_allocations;
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) {
int chs_bytes = chs * 4;
- if (cap->channels != chs)
+
+ if (tlv_type == SNDRV_CTL_TLVT_CHMAP_PAIRED) {
+ int chanpairs = 0;
+ /* in paired mode we need to take into account
+ * the occupied channel pairs instead of just the
+ * channel count */
+ for (c = 0; c < 7; c += 2) {
+ if (cap->speakers[c] || cap->speakers[c+1])
+ chanpairs++;
+ }
+ if (chanpairs * 2 != chs)
+ continue;
+
+ } else if (cap->channels != chs)
continue;
+
if (cap->spk_mask & ~valid_mask)
continue;
if (size < 8)
return -ENOMEM;
- if (put_user(SNDRV_CTL_TLVT_CHMAP_VAR, dst) ||
+
+ if (put_user(tlv_type, dst) ||
put_user(chs_bytes, dst + 1))
return -EFAULT;
dst += 2;
@@ -1643,10 +1705,27 @@ static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
size -= chs_bytes;
count += chs_bytes;
for (c = 7; c >= 0; c--) {
- int spk = cap->speakers[c];
- if (!spk)
- continue;
- if (put_user(spk_to_chmap(spk), dst))
+ int spk;
+ int chan = c;
+ int chpos;
+
+ if (tlv_type == SNDRV_CTL_TLVT_CHMAP_PAIRED)
+ chan = 7 - atihdmi_swap_fc_lfe(7 - chan);
+
+ spk = cap->speakers[chan];
+ if (spk)
+ chpos = spk_to_chmap(spk);
+ else {
+ /* We need to reserve an N/A channel in paired mode
+ * if the companion channel is occupied. */
+ if (tlv_type == SNDRV_CTL_TLVT_CHMAP_PAIRED
+ && cap->speakers[chan + (chan % 2 ? -1 : 1)])
+ chpos = SNDRV_CHMAP_NA;
+ else
+ continue;
+ }
+
+ if (put_user(chpos, dst))
return -EFAULT;
dst++;
}
@@ -1672,6 +1751,18 @@ static int hdmi_chmap_ctl_get(struct snd_kcontrol *kcontrol,
return 0;
}
+static int atihdmi_pairwise_chmap_check_order(struct hda_codec *codec, int ca,
+ int chs, unsigned char *map);
+
+static int hdmi_chmap_check_order(struct hda_codec *codec, int ca,
+ int chs, unsigned char *map)
+{
+ if (is_atihdmi(codec) && !has_amd_full_remap_support(codec))
+ return atihdmi_pairwise_chmap_check_order(codec, ca, chs, map);
+
+ return 0; /* anything can be remapped as needed */
+}
+
static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
@@ -1683,7 +1774,7 @@ static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
unsigned int ctl_idx;
struct snd_pcm_substream *substream;
unsigned char chmap[8];
- int i, ca, prepared = 0;
+ int i, err, ca, prepared = 0;
ctl_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
substream = snd_pcm_chmap_substream(info, ctl_idx);
@@ -1707,6 +1798,9 @@ static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap);
if (ca < 0)
return -EINVAL;
+ err = hdmi_chmap_check_order(codec, ca, ARRAY_SIZE(chmap), chmap);
+ if (err < 0)
+ return -EINVAL;
per_pin->chmap_set = true;
memcpy(per_pin->chmap, chmap, sizeof(chmap));
if (prepared)
@@ -2551,13 +2645,182 @@ static int patch_nvhdmi_8ch_7x(struct hda_codec *codec)
/*
* ATI-specific implementations
- *
- * FIXME: we may omit the whole this and use the generic code once after
- * it's confirmed to work.
*/
-#define ATIHDMI_CVT_NID 0x02 /* audio converter */
-#define ATIHDMI_PIN_NID 0x03 /* HDMI output pin */
+/* ATI/AMD specific HDA pin verbs, see the AMD HDA Verbs specification */
+#define ATI_VERB_SET_CHANNEL_ALLOCATION 0x771
+#define ATI_VERB_SET_DOWNMIX_INFO 0x772
+#define ATI_VERB_SET_MULTICHANNEL_01 0x777
+#define ATI_VERB_SET_MULTICHANNEL_23 0x778
+#define ATI_VERB_SET_MULTICHANNEL_45 0x779
+#define ATI_VERB_SET_MULTICHANNEL_67 0x77a
+#define ATI_VERB_SET_HBR_CONTROL 0x77c
+#define ATI_VERB_SET_MULTICHANNEL_1 0x785
+#define ATI_VERB_SET_MULTICHANNEL_3 0x786
+#define ATI_VERB_SET_MULTICHANNEL_5 0x787
+#define ATI_VERB_SET_MULTICHANNEL_7 0x788
+#define ATI_VERB_SET_MULTICHANNEL_MODE 0x789
+#define ATI_VERB_GET_CHANNEL_ALLOCATION 0xf71
+#define ATI_VERB_GET_DOWNMIX_INFO 0xf72
+#define ATI_VERB_GET_MULTICHANNEL_01 0xf77
+#define ATI_VERB_GET_MULTICHANNEL_23 0xf78
+#define ATI_VERB_GET_MULTICHANNEL_45 0xf79
+#define ATI_VERB_GET_MULTICHANNEL_67 0xf7a
+#define ATI_VERB_GET_HBR_CONTROL 0xf7c
+#define ATI_VERB_GET_MULTICHANNEL_1 0xf85
+#define ATI_VERB_GET_MULTICHANNEL_3 0xf86
+#define ATI_VERB_GET_MULTICHANNEL_5 0xf87
+#define ATI_VERB_GET_MULTICHANNEL_7 0xf88
+#define ATI_VERB_GET_MULTICHANNEL_MODE 0xf89
+
+/* AMD specific HDA cvt verbs */
+#define ATI_VERB_SET_RAMP_RATE 0x770
+#define ATI_VERB_GET_RAMP_RATE 0xf70
+
+#define ATI_OUT_ENABLE 0x1
+
+#define ATI_HBR_CAPABLE 0x01
+#define ATI_HBR_ENABLE 0x10
+
+static void atihdmi_set_ca(struct hda_codec *codec, hda_nid_t pin_nid, int ca)
+{
+ printk("ATI: setting ca %d\n", ca);
+ snd_hda_codec_write(codec, pin_nid, 0, ATI_VERB_SET_CHANNEL_ALLOCATION, ca);
+}
+
+static int atihdmi_swap_fc_lfe(int pos)
+{
+ /*
+ * Older ATI/AMD without channel-wise mapping
+ * have automatic FC/LFE swap built-in.
+ */
+
+ switch (pos) {
+ /* see channel_allocations[].speakers[] */
+ case 2: return 3;
+ case 3: return 2;
+ default: break;
+ }
+
+ return pos;
+}
+
+static int atihdmi_pairwise_chmap_check_order(struct hda_codec *codec, int ca,
+ int chs, unsigned char *map)
+{
+ struct cea_channel_speaker_allocation *cap;
+ int i, j;
+
+ /* check that only channel pairs need to be remapped on old ATI/AMD */
+
+ cap = &channel_allocations[get_channel_allocation_order(ca)];
+ for (i = 0; i < chs; ++i) {
+ int mask = to_spk_mask(map[i]);
+ bool ok = false;
+ bool companion_ok = false;
+
+ if (!mask)
+ continue;
+
+ for (j = 0 + i % 2; j < 8; j += 2) {
+ int chan_idx = 7 - atihdmi_swap_fc_lfe(j);
+ if (cap->speakers[chan_idx] == mask) {
+ /* channel is in a supported position */
+ ok = true;
+
+ if (i % 2 == 0 && i + 1 < chs) {
+ /* even channel, check the odd companion */
+ int comp_chan_idx = 7 - atihdmi_swap_fc_lfe(j + 1);
+ int comp_mask_req = to_spk_mask(map[i+1]);
+ int comp_mask_act = cap->speakers[comp_chan_idx];
+
+ if (comp_mask_req == comp_mask_act)
+ companion_ok = true;
+ else
+ return -EINVAL;
+ }
+ break;
+ }
+ }
+
+ if (!ok)
+ return -EINVAL;
+
+ if (companion_ok)
+ i++; /* companion channel already checked */
+ }
+
+ return 0;
+}
+
+static int atihdmi_set_chan_slot(struct hda_codec *codec, hda_nid_t pin_nid,
+ int chanslot_setup)
+{
+ int hdmi_slot = chanslot_setup & 0xf;
+ int stream_channel = chanslot_setup >> 4;
+ int verb;
+ int ati_channel_setup = 0;
+
+ if (hdmi_slot > 7)
+ return -EINVAL;
+
+ if (!has_amd_full_remap_support(codec)) {
+ hdmi_slot = atihdmi_swap_fc_lfe(hdmi_slot);
+
+ /* In case this is an odd slot but without stream channel, do not
+ * disable the slot since the corresponding even slot could have a
+ * channel. In case neither have a channel, the slot pair will be
+ * disabled when this function is called for the even slot. */
+ if (hdmi_slot % 2 != 0 && stream_channel == 0xf)
+ return 0;
+
+ hdmi_slot -= hdmi_slot % 2;
+
+ if (stream_channel != 0xf)
+ stream_channel -= stream_channel % 2;
+ }
+
+ verb = ATI_VERB_SET_MULTICHANNEL_01 + hdmi_slot/2 + (hdmi_slot % 2) * 0x00e;
+
+ /* ati_channel_setup format: [7..4] = stream_channel_id, [1] = mute, [0] = enable */
+
+ if (stream_channel != 0xf)
+ ati_channel_setup = (stream_channel << 4) | ATI_OUT_ENABLE;
+
+ return snd_hda_codec_write(codec, pin_nid, 0, verb, ati_channel_setup);
+}
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+static int atihdmi_get_chan_slot(struct hda_codec *codec, hda_nid_t pin_nid, int asp_slot)
+{
+ bool was_odd = false;
+ int ati_asp_slot = asp_slot;
+ int verb;
+ int ati_channel_setup;
+
+ /* emulate AC_VERB_GET_HDMI_CHAN_SLOT */
+
+ if (asp_slot > 7)
+ return -EINVAL;
+
+ if (!has_amd_full_remap_support(codec)) {
+ ati_asp_slot = atihdmi_swap_fc_lfe(asp_slot);
+ if (ati_asp_slot % 2 != 0) {
+ ati_asp_slot -= 1;
+ was_odd = true;
+ }
+ }
+
+ verb = ATI_VERB_GET_MULTICHANNEL_01 + ati_asp_slot/2 + (ati_asp_slot % 2) * 0x00e;
+
+ ati_channel_setup = snd_hda_codec_read(codec, pin_nid, 0, verb, 0);
+
+ if (!(ati_channel_setup & ATI_OUT_ENABLE))
+ return (0xf << 4) | asp_slot;
+
+ return ((ati_channel_setup & 0xf0) + ((!!was_odd) << 4)) | asp_slot;
+}
+#endif
static int atihdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
struct hda_codec *codec,
@@ -2565,34 +2828,117 @@ static int atihdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
unsigned int format,
struct snd_pcm_substream *substream)
{
+ hda_nid_t cvt_nid = hinfo->nid;
struct hdmi_spec *spec = codec->spec;
- struct hdmi_spec_per_cvt *per_cvt = get_cvt(spec, 0);
- int chans = substream->runtime->channels;
- int i, err;
+ int pin_idx = hinfo_to_pin_index(spec, hinfo);
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+ hda_nid_t pin_nid = per_pin->pin_nid;
+ int hbr_ctl, hbr_ctl_new;
- err = simple_playback_pcm_prepare(hinfo, codec, stream_tag, format,
- substream);
- if (err < 0)
+ hbr_ctl = snd_hda_codec_read(codec, pin_nid, 0, ATI_VERB_GET_HBR_CONTROL, 0);
+ if (hbr_ctl & ATI_HBR_CAPABLE) {
+ if (is_hbr_format(format))
+ hbr_ctl_new = hbr_ctl | ATI_HBR_ENABLE;
+ else
+ hbr_ctl_new = hbr_ctl & ~ATI_HBR_ENABLE;
+
+ snd_printdd("atihdmi_playback_pcm_prepare: "
+ "NID=0x%x, %shbr-ctl=0x%x\n",
+ pin_nid,
+ hbr_ctl == hbr_ctl_new ? "" : "new-",
+ hbr_ctl_new);
+
+ if (hbr_ctl != hbr_ctl_new)
+ snd_hda_codec_write(codec, pin_nid, 0,
+ ATI_VERB_SET_HBR_CONTROL,
+ hbr_ctl_new);
+
+ } else if (is_hbr_format(format)) {
+ snd_printdd("atihdmi_playback_pcm_prepare: HBR is not supported\n");
+ return -EINVAL;
+ }
+
+ if (is_amdhdmi_rev3(codec)) {
+ int ramp_rate = 180; /* default as per spec */
+ /* disable ramp-up/down for non-pcm as per spec */
+ if (format & AC_FMT_TYPE_NON_PCM)
+ ramp_rate = 0;
+
+ snd_hda_codec_write(codec, cvt_nid, 0, ATI_VERB_SET_RAMP_RATE, ramp_rate);
+ }
+
+ return generic_hdmi_playback_pcm_prepare(hinfo, codec, stream_tag, format, substream);
+}
+
+static int atihdmi_build_pcms(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int err, pin_idx;
+
+ err = generic_hdmi_build_pcms(codec);
+
+ if (err)
return err;
- snd_hda_codec_write(codec, per_cvt->cvt_nid, 0,
- AC_VERB_SET_CVT_CHAN_COUNT, chans - 1);
- /* FIXME: XXX */
- for (i = 0; i < chans; i++) {
- snd_hda_codec_write(codec, per_cvt->cvt_nid, 0,
- AC_VERB_SET_HDMI_CHAN_SLOT,
- (i << 4) | i);
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hda_pcm *info = get_pcm_rec(spec, pin_idx);
+
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].ops.prepare = atihdmi_playback_pcm_prepare;
}
+
+ return 0;
+}
+
+static int atihdmi_init(struct hda_codec *codec)
+{
+ struct hdmi_spec *spec = codec->spec;
+ int pin_idx, err;
+
+ err = generic_hdmi_init(codec);
+
+ if (err)
+ return err;
+
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
+ struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
+
+ /* make sure downmix information in infoframe is zero */
+ snd_hda_codec_write(codec, per_pin->pin_nid, 0, ATI_VERB_SET_DOWNMIX_INFO, 0);
+
+ /* enable channel-wise remap mode if supported */
+ if (has_amd_full_remap_support(codec))
+ snd_hda_codec_write(codec, per_pin->pin_nid, 0, ATI_VERB_SET_MULTICHANNEL_MODE, 1);
+ }
+
return 0;
}
static int patch_atihdmi(struct hda_codec *codec)
{
struct hdmi_spec *spec;
- int err = patch_simple_hdmi(codec, ATIHDMI_CVT_NID, ATIHDMI_PIN_NID);
- if (err < 0)
+ struct hdmi_spec_per_cvt *per_cvt;
+ int err, cvt_idx;
+
+ err = patch_generic_hdmi(codec);
+
+ if (err)
return err;
+
+ codec->patch_ops.init = atihdmi_init;
+ codec->patch_ops.build_pcms = atihdmi_build_pcms;
+
+ /* ATI/AMD converters do not advertise all of their capabilities */
spec = codec->spec;
- spec->pcm_playback.ops.prepare = atihdmi_playback_pcm_prepare;
+ for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) {
+ per_cvt = get_cvt(spec, cvt_idx);
+ per_cvt->channels_max = max(per_cvt->channels_max, 8u);
+ per_cvt->rates |= SUPPORTED_RATES;
+ per_cvt->formats |= SUPPORTED_FORMATS;
+ per_cvt->maxbps = max(per_cvt->maxbps, 24u);
+ }
+
+ spec->channels_max = max(spec->channels_max, 8u);
+
return 0;
}
@@ -2612,7 +2958,7 @@ static const struct hda_codec_preset snd_hda_preset_hdmi[] = {
{ .id = 0x1002793c, .name = "RS600 HDMI", .patch = patch_atihdmi },
{ .id = 0x10027919, .name = "RS600 HDMI", .patch = patch_atihdmi },
{ .id = 0x1002791a, .name = "RS690/780 HDMI", .patch = patch_atihdmi },
-{ .id = 0x1002aa01, .name = "R6xx HDMI", .patch = patch_generic_hdmi },
+{ .id = 0x1002aa01, .name = "R6xx HDMI", .patch = patch_atihdmi },
{ .id = 0x10951390, .name = "SiI1390 HDMI", .patch = patch_generic_hdmi },
{ .id = 0x10951392, .name = "SiI1392 HDMI", .patch = patch_generic_hdmi },
{ .id = 0x17e80047, .name = "Chrontel HDMI", .patch = patch_generic_hdmi },

View File

@ -23,10 +23,7 @@
</network>
<!-- audio workaround for fusion -->
<audiooutput>
<dtshdpassthrough>false</dtshdpassthrough>
<multichannellpcm>false</multichannellpcm>
<passthroughaac>false</passthroughaac>
<stereoupmix>false</stereoupmix>
<truehdpassthrough>false</truehdpassthrough>
</audiooutput>
</advancedsettings>