[alsa-devel] [RFC/RFT PATCH] ALSA: hda - hdmi: Add ATI/AMD multi-channel audio support

Olivier Langlois olivier at trillion01.com
Wed Sep 25 06:22:36 CEST 2013


Hi,

This is just to let you know that I have applied your patch on my linux
3.11 setup and

speaker-test -D hdmi:CARD=Generic,DEV=0 -c8 -r192000 -F S16_LE

did work as expected on my 7.1 receiver with:

lano1106 at whippet2 /proc/asound/card0 :( $ cat codec#0 
Codec: ATI R6xx HDMI
Address: 0
AFG Function Id: 0x1 (unsol 0)
Vendor Id: 0x1002aa01
Subsystem Id: 0x00aa0100
Revision Id: 0x100300
No Modem Function Group found
Default PCM:
    rates [0x70]: 32000 44100 48000
    bits [0x2]: 16
    formats [0x5]: PCM AC3

>From dmesg:

lano1106 at whippet2 /etc $ dmesg | grep hdmi
[   56.836416] ALSA sound/pci/hda/patch_hdmi.c:1345 HDMI status: Codec=0
Pin=3 Presence_Detect=0 ELD_Valid=0
[   56.836446] ALSA sound/pci/hda/patch_hdmi.c:1345 HDMI status: Codec=0
Pin=5 Presence_Detect=0 ELD_Valid=0
[   56.836474] ALSA sound/pci/hda/patch_hdmi.c:1345 HDMI status: Codec=0
Pin=7 Presence_Detect=0 ELD_Valid=0
[   56.836502] ALSA sound/pci/hda/patch_hdmi.c:1345 HDMI status: Codec=0
Pin=9 Presence_Detect=0 ELD_Valid=0
[   56.836532] ALSA sound/pci/hda/patch_hdmi.c:1345 HDMI status: Codec=0
Pin=11 Presence_Detect=0 ELD_Valid=0
[   56.836563] ALSA sound/pci/hda/patch_hdmi.c:1345 HDMI status: Codec=0
Pin=13 Presence_Detect=0 ELD_Valid=0
[   79.977754] ALSA sound/pci/hda/patch_hdmi.c:1013 HDMI hot plug event:
Codec=0 Pin=3 Presence_Detect=1 ELD_Valid=0
[   79.977773] ALSA sound/pci/hda/patch_hdmi.c:1345 HDMI status: Codec=0
Pin=3 Presence_Detect=1 ELD_Valid=1
[   79.978112] ALSA sound/pci/hda/patch_hdmi.c:1013 HDMI hot plug event:
Codec=0 Pin=3 Presence_Detect=0 ELD_Valid=1
[   79.978125] ALSA sound/pci/hda/patch_hdmi.c:1345 HDMI status: Codec=0
Pin=3 Presence_Detect=1 ELD_Valid=1
[  300.375444] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[  821.665641] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1857.928517] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1901.741094] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1905.391150] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1909.039165] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1912.689156] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1916.337149] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1919.987152] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1923.635135] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1927.285133] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1930.933134] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0xb
for 6-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1937.298396] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1940.038137] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1942.776132] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1945.514135] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1948.252139] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1950.993136] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1953.731135] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1956.470138] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1959.209138] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1961.950134] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1964.690139] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1967.428135] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC
[ 1978.703563] ALSA sound/pci/hda/patch_hdmi.c:561 HDMI: select CA 0x13
for 8-channel allocation:  FL/FR LFE FC RL/RR RLC/RRC


Correct me if I am wrong but this codec should be able to output 24 bits
PCM. How one could go to test that?

Good work Anssi!

On Sun, 2013-09-22 at 20:50 +0300, Anssi Hannula wrote:
> Request for comments and testing.
> 
> AMD just released the documentation needed for multi-channel HDMI
> support, so here is the initial patch adding such support.
> 
> This has been tested on codec 0x1002aa01, revision 0x100200 with the
> exception of custom channel maps, which I intend to test later.
> 
> I'm especially interested in testers with:
> - Older codecs other than 0x1002aa01. My best guess is that the new code
>   should work on them as well, but I'm not really 100% sure.
> - Codec 0x1002aa01 revisions 0x100300 (Rev ID 3) or later (see
>   /proc/asound/cardX/codec#Y). These support individual channel
>   remapping and that code is entirely untested (even for regular
>   5.1/7.1).
> 
> I decided it was best to get this out there early even though major
> parts of it are still untested, for comments and for other people to
> test :)
> 
> Note that this recent commit is probably required for multi-channel to
> work with the open-source radeon driver:
> ALSA: hda - hdmi: Fallback to ALSA allocation when selecting CA
> https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/sound/pci/hda/patch_hdmi.c?id=18e391862cceaf43ddb8eb5cca05e1a83abdebaa
> 
> Easiest way to test basic multichannel is by speaker-test, e.g.
> speaker-test -D hdmi:CARD=Generic,DEV=0 -c8 -r192000 -F S16_LE
> for 7.1 16-bit 192kHz
> 
> 
> Detailed discussion below:
> 
> ATI/AMD codecs do not support all the standard HDA HDMI/DP functions. In
> particular, these are missing:
> - ELD support
> - standard channel mapping support
> - standard infoframe configuration support
> 
> In place of them, custom HDA pin verbs are included, that allow:
> - getting speaker mask from EDID
> - getting SADs from EDID
> - getting connection type from EDID (HDMI, DisplayPort)
> - setting CA for infoframe
> - setting down-mix information for infoframe
> - channel pair remapping
> - individual channel remapping (revision ID 3+)
> 
> The documentation for the verbs has now been released by AMD:
> http://www.x.org/docs/AMD/AMD_HDA_verbs.pdf
> 
> Also, converted nodes do not have all their supported rates, formats and
> channel counts listed.
> 
> Add support for the ATI/AMD specific verbs and use them instead of the
> generic methods on ATI/AMD codecs. This allows multi-channel audio to
> work.
> Is the way it is done OK, or should something more generic be put into
> place (custom callbacks and/or flags in struct hdmi_spec maybe)?
> 
> On the older ATI/AMD codecs that do not support individual remapping,
> channel remapping is restricted to pair remapping. This does not affect
> the standard multi-channel configurations.
> 
> Since there is no ELD support (unless out-of-band communication with the
> graphics driver is added so that they can provide it to us directly),
> ELD is currently emulated with only the basic information populated
> (speaker mask, SADs, connection type). There are some bits missing on
> the radeon driver side (the SADs etc are not populated) so currently
> the ELD emulation only works on fglrx. ELD emulation is not needed for
> multi-channel playback.
> 
> Rafał, are you interested in implementing the missing bits in the radeon
> driver, as I see you have looked into this before?
> 
> The ELD emulation was quite quickly hacked together and hasn't been
> cleaned up yet, since I'm not sure how we want to ultimately handle
> this. Is ELD emulation the way to go? If so, where does the code belong
> (the ATI specific code is in patch_hdmi.c, but the ELD parsing/defines
> etc are in hda_eld.c)?
> 
> Codec 0x1002aa01 is switched back to patch_atihdmi to support the
> ATI/AMD specific features.
> 
> HBR mode (needed for DTS-HD/TrueHD passthrough) is not working yet, and
> the released documentation does not mention it.
> 
> Signed-off-by: Anssi Hannula <anssi.hannula at iki.fi>
> Tested-by: Peter Frühberger <fritsch at xbmc.org>
> Cc: Rafał Miłecki <zajec5 at gmail.com>
> ---
>  sound/pci/hda/patch_hdmi.c | 418 ++++++++++++++++++++++++++++++++++++++++-----
>  1 file changed, 374 insertions(+), 44 deletions(-)
> 
> diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
> index 3d8cd044..741d75e 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 at nvidia.com>
> + *  Copyright (c) 2013 Anssi Hannula <anssi.hannula at iki.fi>
>   *
>   *  Authors:
>   *			Wu Fengguang <wfg at linux.intel.com>
> @@ -45,6 +46,9 @@ module_param(static_hdmi_pcm, bool, 0644);
>  MODULE_PARM_DESC(static_hdmi_pcm, "Don't restrict PCM parameters per ELD info");
>  
>  #define is_haswell(codec)  ((codec)->vendor_id == 0x80862807)
> +#define is_atihdmi(codec) (((codec)->vendor_id & 0xffff0000) == 0x10020000)
> +#define has_amd_full_remap_support(codec) \
> +	((codec)->vendor_id == 0x1002791a && ((codec)->revision_id & 0xff00) >= 0x0300)
>  
>  struct hdmi_spec_per_cvt {
>  	hda_nid_t cvt_nid;
> @@ -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;
> @@ -1274,6 +1314,9 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
>  	return 0;
>  }
>  
> +static int atihdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
> +			   unsigned char *buf, int *eld_size);
> +
>  static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
>  {
>  	struct hda_codec *codec = per_pin->codec;
> @@ -1303,8 +1346,12 @@ static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
>  		"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 (pin_eld->monitor_present && is_atihdmi(codec))
> +		eld->eld_valid = (atihdmi_get_eld(codec, pin_nid, eld->eld_buffer, &eld->eld_size) == 0);
> +
>  	if (eld->eld_valid) {
> -		if (snd_hdmi_get_eld(codec, pin_nid, eld->eld_buffer,
> +		if (!is_atihdmi(codec) &&
> +		    snd_hdmi_get_eld(codec, pin_nid, eld->eld_buffer,
>  						     &eld->eld_size) < 0)
>  			eld->eld_valid = false;
>  		else {
> @@ -1603,6 +1650,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 +1662,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 +1673,33 @@ 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 < 8; c++) {
> +					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 +1710,25 @@ 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 (tlv_type == SNDRV_CTL_TLVT_CHMAP_PAIRED)
> +						chpos = SNDRV_CHMAP_NA;
> +					else
> +						continue;
> +				}
> +
> +				if (put_user(chpos, dst))
>  					return -EFAULT;
>  				dst++;
>  			}
> @@ -1672,6 +1754,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 +1777,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 +1801,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,48 +2648,281 @@ 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_AUDIO_DESCRIPTOR	0x776
> +#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_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_SPEAKER_ALLOCATION	0xf70
> +#define ATI_VERB_GET_CHANNEL_ALLOCATION	0xf71
> +#define ATI_VERB_GET_DOWNMIX_INFO	0xf72
> +#define ATI_VERB_GET_AUDIO_DESCRIPTOR	0xf76
> +#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_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
> +
> +#define ATI_OUT_ENABLE 0x1
> +
> +/* TODO: cleanup this function */
> +static int atihdmi_get_eld(struct hda_codec *codec, hda_nid_t nid,
> +			   unsigned char *buf, int *eld_size)
> +{
> +	int spkalloc, ati_sad;
> +	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)
> +		return -EINVAL;
> +
> +	memset(buf, 0, 84);
> +
> +	/* speaker allocation from EDID */
> +	buf[7] = spkalloc & 0x00007f;
> +
> +	/* is DisplayPort? */
> +	if (spkalloc & 0x000200)
> +		buf[5] |= 0x04;
> +
> +	pos = 20;
> +	for (i = 1; i <= 14; i++) {
> +		if (i == 13) /* not handled by ATI/AMD */
> +			continue;
> +
> +		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 & 0xff00) {
> +			memcpy(buf + pos, &ati_sad, 3);
> +			pos += 3;
> +		}
> +
> +		if (i == 1 && ati_sad & 0xff000000 && (ati_sad & 0xff00) != (ati_sad & 0xff000000) >> 16) {
> +			/* for PCM there is a separate stereo rate mask */
> +			memcpy(buf + pos, &ati_sad, 3);
> +			/* max channels to 2 */
> +			buf[pos+0] = (buf[pos+0] & ~0x7) | 0x1;
> +			/* rates from extra byte */
> +			buf[pos+1] = (ati_sad & 0xff000000) >> 24;
> +			pos += 3;
> +		}
> +	}
> +
> +	if (pos == 20)
> +		return -EINVAL;
> +
> +	/* version */
> +	buf[0] = 0x10;
> +
> +	/* Baseline length */
> +	buf[2] = pos - 4;
> +
> +	/* SAD count */
> +	buf[5] |= ((pos - 20) / 3) << 4;
> +
> +	*eld_size = pos;
> +
> +	return 0;
> +}
> +
> +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 (!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 (!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;
> +		}
> +	}
>  
> -static int atihdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
> -					struct hda_codec *codec,
> -					unsigned int stream_tag,
> -					unsigned int format,
> -					struct snd_pcm_substream *substream)
> +	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_init(struct hda_codec *codec)
>  {
>  	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, err;
>  
> -	err = simple_playback_pcm_prepare(hinfo, codec, stream_tag, format,
> -					  substream);
> -	if (err < 0)
> +	err = generic_hdmi_init(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 hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);
> +
> +		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;
> +
> +	/* 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);
> +	}
> +
>  	return 0;
>  }
>  
> @@ -2612,7 +2942,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 },




More information about the Alsa-devel mailing list