From: Takashi Iwai Subject: ALSA: Add Intel HDMI support Patch-mainline: 2.6.29 References: bnc#485768 Add the support for Intel HDMI devices. Signed-off-by: Takashi Iwai --- sound/pci/Kconfig | 12 sound/pci/hda/Makefile | 2 sound/pci/hda/hda_codec.c | 87 +++++ sound/pci/hda/hda_codec.h | 7 sound/pci/hda/hda_eld.c | 592 ++++++++++++++++++++++++++++++++++ sound/pci/hda/hda_intel.c | 18 - sound/pci/hda/hda_local.h | 68 +++ sound/pci/hda/hda_patch.h | 2 sound/pci/hda/hda_proc.c | 75 ---- sound/pci/hda/patch_atihdmi.c | 9 sound/pci/hda/patch_intelhdmi.c | 694 ++++++++++++++++++++++++++++++++++++++++ sound/pci/hda/patch_nvhdmi.c | 7 12 files changed, 1504 insertions(+), 69 deletions(-) --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -573,6 +573,18 @@ config SND_HDA_CODEC_NVHDMI Say Y here to include NVIDIA HDMI HD-audio codec support in snd-hda-intel driver, such as NVIDIA MCP78 HDMI. +config SND_HDA_CODEC_INTELHDMI + bool "Build INTEL HDMI HD-audio codec support" + depends on SND_HDA_INTEL + default y + help + Say Y here to include INTEL HDMI HD-audio codec support in + snd-hda-intel driver, such as Eaglelake integrated HDMI. + +config SND_HDA_ELD + def_bool y + depends on SND_HDA_CODEC_INTELHDMI + config SND_HDA_CODEC_CONEXANT bool "Build Conexant HD-audio codec support" depends on SND_HDA_INTEL --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -16,5 +16,7 @@ snd-hda-intel-$(CONFIG_SND_HDA_CODEC_ATI snd-hda-intel-$(CONFIG_SND_HDA_CODEC_CONEXANT) += patch_conexant.o snd-hda-intel-$(CONFIG_SND_HDA_CODEC_VIA) += patch_via.o snd-hda-intel-$(CONFIG_SND_HDA_CODEC_NVHDMI) += patch_nvhdmi.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_INTELHDMI) += patch_intelhdmi.o +snd-hda-intel-$(CONFIG_SND_HDA_ELD) += hda_eld.o obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -55,6 +55,7 @@ static struct hda_vendor_id hda_vendor_i { 0x1002, "ATI" }, { 0x1057, "Motorola" }, { 0x1095, "Silicon Image" }, + { 0x10de, "Nvidia" }, { 0x10ec, "Realtek" }, { 0x1106, "VIA" }, { 0x111d, "IDT" }, @@ -64,7 +65,9 @@ static struct hda_vendor_id hda_vendor_i { 0x14f1, "Conexant" }, { 0x17e8, "Chrontel" }, { 0x1854, "LG" }, + { 0x1aec, "Wolfson Microelectronics" }, { 0x434d, "C-Media" }, + { 0x8086, "Intel" }, { 0x8384, "SigmaTel" }, {} /* terminator */ }; @@ -97,6 +100,9 @@ static const struct hda_codec_preset *hd #ifdef CONFIG_SND_HDA_CODEC_NVHDMI snd_hda_preset_nvhdmi, #endif +#ifdef CONFIG_SND_HDA_CODEC_INTELHDMI + snd_hda_preset_intelhdmi, +#endif NULL }; @@ -124,6 +130,52 @@ make_codec_cmd(struct hda_codec *codec, return val; } +const char *snd_hda_get_jack_location(u32 cfg) +{ + static char *bases[7] = { + "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom", + }; + static unsigned char specials_idx[] = { + 0x07, 0x08, + 0x17, 0x18, 0x19, + 0x37, 0x38 + }; + static char *specials[] = { + "Rear Panel", "Drive Bar", + "Riser", "HDMI", "ATAPI", + "Mobile-In", "Mobile-Out" + }; + int i; + cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT; + if ((cfg & 0x0f) < 7) + return bases[cfg & 0x0f]; + for (i = 0; i < ARRAY_SIZE(specials_idx); i++) { + if (cfg == specials_idx[i]) + return specials[i]; + } + return "UNKNOWN"; +} + +const char *snd_hda_get_jack_connectivity(u32 cfg) +{ + static char *jack_locations[4] = { "Ext", "Int", "Sep", "Oth" }; + + return jack_locations[(cfg >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3]; +} + +const char *snd_hda_get_jack_type(u32 cfg) +{ + static char *jack_types[16] = { + "Line Out", "Speaker", "HP Out", "CD", + "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand", + "Line In", "Aux", "Mic", "Telephony", + "SPDIF In", "Digitial In", "Reserved", "Other" + }; + + return jack_types[(cfg & AC_DEFCFG_DEVICE) + >> AC_DEFCFG_DEVICE_SHIFT]; +} + /** * snd_hda_codec_read - send a command and get the response * @codec: the HDA codec @@ -1636,6 +1688,8 @@ int snd_hda_create_spdif_out_ctls(struct } for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) { kctl = snd_ctl_new1(dig_mix, codec); + if (!kctl) + return -ENOMEM; kctl->id.index = idx; kctl->private_value = nid; err = snd_ctl_add(codec->bus->card, kctl); @@ -1783,6 +1837,8 @@ int snd_hda_create_spdif_in_ctls(struct } for (dig_mix = dig_in_ctls; dig_mix->name; dig_mix++) { kctl = snd_ctl_new1(dig_mix, codec); + if (!kctl) + return -ENOMEM; kctl->private_value = nid; err = snd_ctl_add(codec->bus->card, kctl); if (err < 0) @@ -3263,3 +3319,34 @@ int snd_hda_codecs_inuse(struct hda_bus } #endif #endif + +/* + * used by hda_proc.c and hda_eld.c + */ +void snd_print_pcm_rates(int pcm, char *buf, int buflen) +{ + static unsigned int rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 384000 + }; + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(rates); i++) + if (pcm & (1 << i)) + j += snprintf(buf + j, buflen - j, " %d", rates[i]); + + buf[j] = '\0'; /* necessary when j == 0 */ +} + +void snd_print_pcm_bits(int pcm, char *buf, int buflen) +{ + static unsigned int bits[] = { 8, 16, 20, 24, 32 }; + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(bits); i++) + if (pcm & (AC_SUPPCM_BITS_8 << i)) + j += snprintf(buf + j, buflen - j, " %d", bits[i]); + + buf[j] = '\0'; /* necessary when j == 0 */ +} + --- a/sound/pci/hda/hda_codec.h +++ b/sound/pci/hda/hda_codec.h @@ -832,6 +832,13 @@ int snd_hda_resume(struct hda_bus *bus); #endif /* + * get widget information + */ +const char *snd_hda_get_jack_connectivity(u32 cfg); +const char *snd_hda_get_jack_type(u32 cfg); +const char *snd_hda_get_jack_location(u32 cfg); + +/* * power saving */ #ifdef CONFIG_SND_HDA_POWER_SAVE --- /dev/null +++ b/sound/pci/hda/hda_eld.c @@ -0,0 +1,592 @@ +/* + * Generic routines and proc interface for ELD(EDID Like Data) information + * + * Copyright(c) 2008 Intel Corporation. + * + * Authors: + * Wu Fengguang + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include "hda_codec.h" +#include "hda_local.h" + +enum eld_versions { + ELD_VER_CEA_861D = 2, + ELD_VER_PARTIAL = 31, +}; + +enum cea_edid_versions { + CEA_EDID_VER_NONE = 0, + CEA_EDID_VER_CEA861 = 1, + CEA_EDID_VER_CEA861A = 2, + CEA_EDID_VER_CEA861BCD = 3, + CEA_EDID_VER_RESERVED = 4, +}; + +static char *cea_speaker_allocation_names[] = { + /* 0 */ "FL/FR", + /* 1 */ "LFE", + /* 2 */ "FC", + /* 3 */ "RL/RR", + /* 4 */ "RC", + /* 5 */ "FLC/FRC", + /* 6 */ "RLC/RRC", + /* 7 */ "FLW/FRW", + /* 8 */ "FLH/FRH", + /* 9 */ "TC", + /* 10 */ "FCH", +}; + +static char *eld_connection_type_names[4] = { + "HDMI", + "DisplayPort", + "2-reserved", + "3-reserved" +}; + +enum cea_audio_coding_types { + AUDIO_CODING_TYPE_REF_STREAM_HEADER = 0, + AUDIO_CODING_TYPE_LPCM = 1, + AUDIO_CODING_TYPE_AC3 = 2, + AUDIO_CODING_TYPE_MPEG1 = 3, + AUDIO_CODING_TYPE_MP3 = 4, + AUDIO_CODING_TYPE_MPEG2 = 5, + AUDIO_CODING_TYPE_AACLC = 6, + AUDIO_CODING_TYPE_DTS = 7, + AUDIO_CODING_TYPE_ATRAC = 8, + AUDIO_CODING_TYPE_SACD = 9, + AUDIO_CODING_TYPE_EAC3 = 10, + AUDIO_CODING_TYPE_DTS_HD = 11, + AUDIO_CODING_TYPE_MLP = 12, + AUDIO_CODING_TYPE_DST = 13, + AUDIO_CODING_TYPE_WMAPRO = 14, + AUDIO_CODING_TYPE_REF_CXT = 15, + /* also include valid xtypes below */ + AUDIO_CODING_TYPE_HE_AAC = 15, + AUDIO_CODING_TYPE_HE_AAC2 = 16, + AUDIO_CODING_TYPE_MPEG_SURROUND = 17, +}; + +enum cea_audio_coding_xtypes { + AUDIO_CODING_XTYPE_HE_REF_CT = 0, + AUDIO_CODING_XTYPE_HE_AAC = 1, + AUDIO_CODING_XTYPE_HE_AAC2 = 2, + AUDIO_CODING_XTYPE_MPEG_SURROUND = 3, + AUDIO_CODING_XTYPE_FIRST_RESERVED = 4, +}; + +static char *cea_audio_coding_type_names[] = { + /* 0 */ "undefined", + /* 1 */ "LPCM", + /* 2 */ "AC-3", + /* 3 */ "MPEG1", + /* 4 */ "MP3", + /* 5 */ "MPEG2", + /* 6 */ "AAC-LC", + /* 7 */ "DTS", + /* 8 */ "ATRAC", + /* 9 */ "DSD (One Bit Audio)", + /* 10 */ "E-AC-3/DD+ (Dolby Digital Plus)", + /* 11 */ "DTS-HD", + /* 12 */ "MLP (Dolby TrueHD)", + /* 13 */ "DST", + /* 14 */ "WMAPro", + /* 15 */ "HE-AAC", + /* 16 */ "HE-AACv2", + /* 17 */ "MPEG Surround", +}; + +/* + * The following two lists are shared between + * - HDMI audio InfoFrame (source to sink) + * - CEA E-EDID Extension (sink to source) + */ + +/* + * SS1:SS0 index => sample size + */ +static int cea_sample_sizes[4] = { + 0, /* 0: Refer to Stream Header */ + AC_SUPPCM_BITS_16, /* 1: 16 bits */ + AC_SUPPCM_BITS_20, /* 2: 20 bits */ + AC_SUPPCM_BITS_24, /* 3: 24 bits */ +}; + +/* + * SF2:SF1:SF0 index => sampling frequency + */ +static int cea_sampling_frequencies[8] = { + 0, /* 0: Refer to Stream Header */ + SNDRV_PCM_RATE_32000, /* 1: 32000Hz */ + SNDRV_PCM_RATE_44100, /* 2: 44100Hz */ + SNDRV_PCM_RATE_48000, /* 3: 48000Hz */ + SNDRV_PCM_RATE_88200, /* 4: 88200Hz */ + SNDRV_PCM_RATE_96000, /* 5: 96000Hz */ + SNDRV_PCM_RATE_176400, /* 6: 176400Hz */ + SNDRV_PCM_RATE_192000, /* 7: 192000Hz */ +}; + +static unsigned char hdmi_get_eld_byte(struct hda_codec *codec, hda_nid_t nid, + int byte_index) +{ + unsigned int val; + + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_HDMI_ELDD, byte_index); + +#ifdef BE_PARANOID + printk(KERN_INFO "HDMI: ELD data byte %d: 0x%x\n", byte_index, val); +#endif + + if ((val & AC_ELDD_ELD_VALID) == 0) { + snd_printd(KERN_INFO "HDMI: invalid ELD data byte %d\n", + byte_index); + val = 0; + } + + return val & AC_ELDD_ELD_DATA; +} + +#define GRAB_BITS(buf, byte, lowbit, bits) \ +({ \ + BUILD_BUG_ON(lowbit > 7); \ + BUILD_BUG_ON(bits > 8); \ + BUILD_BUG_ON(bits <= 0); \ + \ + (buf[byte] >> (lowbit)) & ((1 << (bits)) - 1); \ +}) + +static void hdmi_update_short_audio_desc(struct cea_sad *a, + const unsigned char *buf) +{ + int i; + int val; + + val = GRAB_BITS(buf, 1, 0, 7); + a->rates = 0; + for (i = 0; i < 7; i++) + if (val & (1 << i)) + a->rates |= cea_sampling_frequencies[i + 1]; + + a->channels = GRAB_BITS(buf, 0, 0, 3); + a->channels++; + + a->format = GRAB_BITS(buf, 0, 3, 4); + switch (a->format) { + case AUDIO_CODING_TYPE_REF_STREAM_HEADER: + snd_printd(KERN_INFO + "HDMI: audio coding type 0 not expected\n"); + break; + + case AUDIO_CODING_TYPE_LPCM: + val = GRAB_BITS(buf, 2, 0, 3); + a->sample_bits = 0; + for (i = 0; i < 3; i++) + if (val & (1 << i)) + a->sample_bits |= cea_sample_sizes[i + 1]; + break; + + case AUDIO_CODING_TYPE_AC3: + case AUDIO_CODING_TYPE_MPEG1: + case AUDIO_CODING_TYPE_MP3: + case AUDIO_CODING_TYPE_MPEG2: + case AUDIO_CODING_TYPE_AACLC: + case AUDIO_CODING_TYPE_DTS: + case AUDIO_CODING_TYPE_ATRAC: + a->max_bitrate = GRAB_BITS(buf, 2, 0, 8); + a->max_bitrate *= 8000; + break; + + case AUDIO_CODING_TYPE_SACD: + break; + + case AUDIO_CODING_TYPE_EAC3: + break; + + case AUDIO_CODING_TYPE_DTS_HD: + break; + + case AUDIO_CODING_TYPE_MLP: + break; + + case AUDIO_CODING_TYPE_DST: + break; + + case AUDIO_CODING_TYPE_WMAPRO: + a->profile = GRAB_BITS(buf, 2, 0, 3); + break; + + case AUDIO_CODING_TYPE_REF_CXT: + a->format = GRAB_BITS(buf, 2, 3, 5); + if (a->format == AUDIO_CODING_XTYPE_HE_REF_CT || + a->format >= AUDIO_CODING_XTYPE_FIRST_RESERVED) { + snd_printd(KERN_INFO + "HDMI: audio coding xtype %d not expected\n", + a->format); + a->format = 0; + } else + a->format += AUDIO_CODING_TYPE_HE_AAC - + AUDIO_CODING_XTYPE_HE_AAC; + break; + } +} + +/* + * Be careful, ELD buf could be totally rubbish! + */ +static int hdmi_update_eld(struct hdmi_eld *e, + const unsigned char *buf, int size) +{ + int mnl; + int i; + + e->eld_ver = GRAB_BITS(buf, 0, 3, 5); + if (e->eld_ver != ELD_VER_CEA_861D && + e->eld_ver != ELD_VER_PARTIAL) { + snd_printd(KERN_INFO "HDMI: Unknown ELD version %d\n", + e->eld_ver); + goto out_fail; + } + + e->eld_size = size; + e->baseline_len = GRAB_BITS(buf, 2, 0, 8); + mnl = GRAB_BITS(buf, 4, 0, 5); + e->cea_edid_ver = GRAB_BITS(buf, 4, 5, 3); + + e->support_hdcp = GRAB_BITS(buf, 5, 0, 1); + e->support_ai = GRAB_BITS(buf, 5, 1, 1); + e->conn_type = GRAB_BITS(buf, 5, 2, 2); + e->sad_count = GRAB_BITS(buf, 5, 4, 4); + + e->aud_synch_delay = GRAB_BITS(buf, 6, 0, 8) * 2; + e->spk_alloc = GRAB_BITS(buf, 7, 0, 7); + + e->port_id = get_unaligned_le64(buf + 8); + + /* not specified, but the spec's tendency is little endian */ + e->manufacture_id = get_unaligned_le16(buf + 16); + e->product_id = get_unaligned_le16(buf + 18); + + if (mnl > ELD_MAX_MNL) { + snd_printd(KERN_INFO "HDMI: MNL is reserved value %d\n", mnl); + goto out_fail; + } else if (ELD_FIXED_BYTES + mnl > size) { + snd_printd(KERN_INFO "HDMI: out of range MNL %d\n", mnl); + goto out_fail; + } else + strlcpy(e->monitor_name, buf + ELD_FIXED_BYTES, mnl); + + for (i = 0; i < e->sad_count; i++) { + if (ELD_FIXED_BYTES + mnl + 3 * (i + 1) > size) { + snd_printd(KERN_INFO "HDMI: out of range SAD %d\n", i); + goto out_fail; + } + hdmi_update_short_audio_desc(e->sad + i, + buf + ELD_FIXED_BYTES + mnl + 3 * i); + } + + return 0; + +out_fail: + e->eld_ver = 0; + return -EINVAL; +} + +static int hdmi_present_sense(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_SENSE, 0); +} + +static int hdmi_eld_valid(struct hda_codec *codec, hda_nid_t nid) +{ + int eldv; + int present; + + present = hdmi_present_sense(codec, nid); + eldv = (present & AC_PINSENSE_ELDV); + present = (present & AC_PINSENSE_PRESENCE); + +#ifdef CONFIG_SND_DEBUG_VERBOSE + printk(KERN_INFO "HDMI: sink_present = %d, eld_valid = %d\n", + !!present, !!eldv); +#endif + + return eldv && present; +} + +int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid) +{ + return snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_HDMI_DIP_SIZE, + AC_DIPSIZE_ELD_BUF); +} + +int snd_hdmi_get_eld(struct hdmi_eld *eld, + struct hda_codec *codec, hda_nid_t nid) +{ + int i; + int ret; + int size; + unsigned char *buf; + + if (!hdmi_eld_valid(codec, nid)) + return -ENOENT; + + size = snd_hdmi_get_eld_size(codec, nid); + if (size == 0) { + /* wfg: workaround for ASUS P5E-VM HDMI board */ + snd_printd(KERN_INFO "HDMI: ELD buf size is 0, force 128\n"); + size = 128; + } + if (size < ELD_FIXED_BYTES || size > PAGE_SIZE) { + snd_printd(KERN_INFO "HDMI: invalid ELD buf size %d\n", size); + return -ERANGE; + } + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < size; i++) + buf[i] = hdmi_get_eld_byte(codec, nid, i); + + ret = hdmi_update_eld(eld, buf, size); + + kfree(buf); + return ret; +} + +static void hdmi_show_short_audio_desc(struct cea_sad *a) +{ + char buf[SND_PRINT_RATES_ADVISED_BUFSIZE]; + char buf2[8 + SND_PRINT_BITS_ADVISED_BUFSIZE] = ", bits ="; + + if (!a->format) + return; + + snd_print_pcm_rates(a->rates, buf, sizeof(buf)); + + if (a->format == AUDIO_CODING_TYPE_LPCM) + snd_print_pcm_bits(a->sample_bits, buf2 + 8, sizeof(buf2 - 8)); + else if (a->max_bitrate) + snprintf(buf2, sizeof(buf2), + ", max bitrate = %d", a->max_bitrate); + else + buf2[0] = '\0'; + + printk(KERN_INFO "HDMI: supports coding type %s:" + " channels = %d, rates =%s%s\n", + cea_audio_coding_type_names[a->format], + a->channels, + buf, + buf2); +} + +void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen) +{ + int i, j; + + for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) { + if (spk_alloc & (1 << i)) + j += snprintf(buf + j, buflen - j, " %s", + cea_speaker_allocation_names[i]); + } + buf[j] = '\0'; /* necessary when j == 0 */ +} + +void snd_hdmi_show_eld(struct hdmi_eld *e) +{ + int i; + + printk(KERN_INFO "HDMI: detected monitor %s at connection type %s\n", + e->monitor_name, + eld_connection_type_names[e->conn_type]); + + if (e->spk_alloc) { + char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE]; + snd_print_channel_allocation(e->spk_alloc, buf, sizeof(buf)); + printk(KERN_INFO "HDMI: available speakers:%s\n", buf); + } + + for (i = 0; i < e->sad_count; i++) + hdmi_show_short_audio_desc(e->sad + i); +} + +#ifdef CONFIG_PROC_FS + +static void hdmi_print_sad_info(int i, struct cea_sad *a, + struct snd_info_buffer *buffer) +{ + char buf[SND_PRINT_RATES_ADVISED_BUFSIZE]; + + snd_iprintf(buffer, "sad%d_coding_type\t[0x%x] %s\n", + i, a->format, cea_audio_coding_type_names[a->format]); + snd_iprintf(buffer, "sad%d_channels\t\t%d\n", i, a->channels); + + snd_print_pcm_rates(a->rates, buf, sizeof(buf)); + snd_iprintf(buffer, "sad%d_rates\t\t[0x%x]%s\n", i, a->rates, buf); + + if (a->format == AUDIO_CODING_TYPE_LPCM) { + snd_print_pcm_bits(a->sample_bits, buf, sizeof(buf)); + snd_iprintf(buffer, "sad%d_bits\t\t[0x%x]%s\n", + i, a->sample_bits, buf); + } + + if (a->max_bitrate) + snd_iprintf(buffer, "sad%d_max_bitrate\t%d\n", + i, a->max_bitrate); + + if (a->profile) + snd_iprintf(buffer, "sad%d_profile\t\t%d\n", i, a->profile); +} + +static void hdmi_print_eld_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdmi_eld *e = entry->private_data; + char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE]; + int i; + static char *eld_versoin_names[32] = { + "reserved", + "reserved", + "CEA-861D or below", + [3 ... 30] = "reserved", + [31] = "partial" + }; + static char *cea_edid_version_names[8] = { + "no CEA EDID Timing Extension block present", + "CEA-861", + "CEA-861-A", + "CEA-861-B, C or D", + [4 ... 7] = "reserved" + }; + + snd_iprintf(buffer, "monitor_name\t\t%s\n", e->monitor_name); + snd_iprintf(buffer, "connection_type\t\t%s\n", + eld_connection_type_names[e->conn_type]); + snd_iprintf(buffer, "eld_version\t\t[0x%x] %s\n", e->eld_ver, + eld_versoin_names[e->eld_ver]); + snd_iprintf(buffer, "edid_version\t\t[0x%x] %s\n", e->cea_edid_ver, + cea_edid_version_names[e->cea_edid_ver]); + snd_iprintf(buffer, "manufacture_id\t\t0x%x\n", e->manufacture_id); + snd_iprintf(buffer, "product_id\t\t0x%x\n", e->product_id); + snd_iprintf(buffer, "port_id\t\t\t0x%llx\n", (long long)e->port_id); + snd_iprintf(buffer, "support_hdcp\t\t%d\n", e->support_hdcp); + snd_iprintf(buffer, "support_ai\t\t%d\n", e->support_ai); + snd_iprintf(buffer, "audio_sync_delay\t%d\n", e->aud_synch_delay); + + snd_print_channel_allocation(e->spk_alloc, buf, sizeof(buf)); + snd_iprintf(buffer, "speakers\t\t[0x%x]%s\n", e->spk_alloc, buf); + + snd_iprintf(buffer, "sad_count\t\t%d\n", e->sad_count); + + for (i = 0; i < e->sad_count; i++) + hdmi_print_sad_info(i, e->sad + i, buffer); +} + +static void hdmi_write_eld_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct hdmi_eld *e = entry->private_data; + char line[64]; + char name[64]; + char *sname; + long long val; + int n; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + if (sscanf(line, "%s %llx", name, &val) != 2) + continue; + /* + * We don't allow modification to these fields: + * monitor_name manufacture_id product_id + * eld_version edid_version + */ + if (!strcmp(name, "connection_type")) + e->conn_type = val; + else if (!strcmp(name, "port_id")) + e->port_id = val; + else if (!strcmp(name, "support_hdcp")) + e->support_hdcp = val; + else if (!strcmp(name, "support_ai")) + e->support_ai = val; + else if (!strcmp(name, "audio_sync_delay")) + e->aud_synch_delay = val; + else if (!strcmp(name, "speakers")) + e->spk_alloc = val; + else if (!strcmp(name, "sad_count")) + e->sad_count = val; + else if (!strncmp(name, "sad", 3)) { + sname = name + 4; + n = name[3] - '0'; + if (name[4] >= '0' && name[4] <= '9') { + sname++; + n = 10 * n + name[4] - '0'; + } + if (n < 0 || n > 31) /* double the CEA limit */ + continue; + if (!strcmp(sname, "_coding_type")) + e->sad[n].format = val; + else if (!strcmp(sname, "_channels")) + e->sad[n].channels = val; + else if (!strcmp(sname, "_rates")) + e->sad[n].rates = val; + else if (!strcmp(sname, "_bits")) + e->sad[n].sample_bits = val; + else if (!strcmp(sname, "_max_bitrate")) + e->sad[n].max_bitrate = val; + else if (!strcmp(sname, "_profile")) + e->sad[n].profile = val; + if (n >= e->sad_count) + e->sad_count = n + 1; + } + } +} + + +int snd_hda_eld_proc_new(struct hda_codec *codec, struct hdmi_eld *eld) +{ + char name[32]; + struct snd_info_entry *entry; + int err; + + snprintf(name, sizeof(name), "eld#%d", codec->addr); + err = snd_card_proc_new(codec->bus->card, name, &entry); + if (err < 0) + return err; + + snd_info_set_text_ops(entry, eld, hdmi_print_eld_info); + entry->c.text.write = hdmi_write_eld_info; + entry->mode |= S_IWUSR; + eld->proc_entry = entry; + + return 0; +} + +void snd_hda_eld_proc_free(struct hda_codec *codec, struct hdmi_eld *eld) +{ +#if 0 /* we don't support hwdep reconfig yet */ + if (!codec->bus->shutdown && eld->proc_entry) { + snd_device_free(codec->bus->card, eld->proc_entry); + eld->proc_entry = NULL; + } +#endif +} + +#endif /* CONFIG_PROC_FS */ --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -292,6 +292,8 @@ enum { /* Define VIA HD Audio Device ID*/ #define VIA_HDAC_DEVICE_ID 0x3288 +/* HD Audio class code */ +#define PCI_CLASS_MULTIMEDIA_HD_AUDIO 0x0403 /* */ @@ -418,6 +420,7 @@ enum { AZX_DRIVER_ULI, AZX_DRIVER_NVIDIA, AZX_DRIVER_TERA, + AZX_DRIVER_GENERIC, AZX_NUM_DRIVERS, /* keep this as last entry */ }; @@ -431,6 +434,7 @@ static char *driver_short_names[] __devi [AZX_DRIVER_ULI] = "HDA ULI M5461", [AZX_DRIVER_NVIDIA] = "HDA NVidia", [AZX_DRIVER_TERA] = "HDA Teradici", + [AZX_DRIVER_GENERIC] = "HD-Audio Generic", }; /* @@ -2321,6 +2325,7 @@ static int __devinit azx_create(struct s chip->playback_streams = ATIHDMI_NUM_PLAYBACK; chip->capture_streams = ATIHDMI_NUM_CAPTURE; break; + case AZX_DRIVER_GENERIC: default: chip->playback_streams = ICH6_NUM_PLAYBACK; chip->capture_streams = ICH6_NUM_CAPTURE; @@ -2530,12 +2535,17 @@ static struct pci_device_id azx_ids[] = { PCI_DEVICE(0x10de, 0x0ac1), .driver_data = AZX_DRIVER_NVIDIA }, { PCI_DEVICE(0x10de, 0x0ac2), .driver_data = AZX_DRIVER_NVIDIA }, { PCI_DEVICE(0x10de, 0x0ac3), .driver_data = AZX_DRIVER_NVIDIA }, - { PCI_DEVICE(0x10de, 0x0bd4), .driver_data = AZX_DRIVER_NVIDIA }, - { PCI_DEVICE(0x10de, 0x0bd5), .driver_data = AZX_DRIVER_NVIDIA }, - { PCI_DEVICE(0x10de, 0x0bd6), .driver_data = AZX_DRIVER_NVIDIA }, - { PCI_DEVICE(0x10de, 0x0bd7), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0d94), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0d95), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0d96), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0d97), .driver_data = AZX_DRIVER_NVIDIA }, /* Teradici */ { PCI_DEVICE(0x6549, 0x1200), .driver_data = AZX_DRIVER_TERA }, + /* AMD Generic, PCI class code and Vendor ID for HD Audio */ + { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_ANY_ID), + .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8, + .class_mask = 0xffffff, + .driver_data = AZX_DRIVER_GENERIC }, { 0, } }; MODULE_DEVICE_TABLE(pci, azx_ids); --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -284,6 +284,12 @@ int snd_hda_codec_proc_new(struct hda_co static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; } #endif +#define SND_PRINT_RATES_ADVISED_BUFSIZE 80 +void snd_print_pcm_rates(int pcm, char *buf, int buflen); + +#define SND_PRINT_BITS_ADVISED_BUFSIZE 16 +void snd_print_pcm_bits(int pcm, char *buf, int buflen); + /* * Misc */ @@ -436,4 +442,66 @@ int snd_hda_check_amp_list_power(struct #define get_amp_index(kc) (((kc)->private_value >> 19) & 0xf) #define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f) +/* + * CEA Short Audio Descriptor data + */ +struct cea_sad { + int channels; + int format; /* (format == 0) indicates invalid SAD */ + int rates; + int sample_bits; /* for LPCM */ + int max_bitrate; /* for AC3...ATRAC */ + int profile; /* for WMAPRO */ +}; + +#define ELD_FIXED_BYTES 20 +#define ELD_MAX_MNL 16 +#define ELD_MAX_SAD 16 + +/* + * ELD: EDID Like Data + */ +struct hdmi_eld { + int eld_size; + int baseline_len; + int eld_ver; /* (eld_ver == 0) indicates invalid ELD */ + int cea_edid_ver; + char monitor_name[ELD_MAX_MNL + 1]; + int manufacture_id; + int product_id; + u64 port_id; + int support_hdcp; + int support_ai; + int conn_type; + int aud_synch_delay; + int spk_alloc; + int sad_count; + struct cea_sad sad[ELD_MAX_SAD]; +#ifdef CONFIG_PROC_FS + struct snd_info_entry *proc_entry; +#endif +}; + +int snd_hdmi_get_eld_size(struct hda_codec *codec, hda_nid_t nid); +int snd_hdmi_get_eld(struct hdmi_eld *, struct hda_codec *, hda_nid_t); +void snd_hdmi_show_eld(struct hdmi_eld *eld); + +#ifdef CONFIG_PROC_FS +int snd_hda_eld_proc_new(struct hda_codec *codec, struct hdmi_eld *eld); +void snd_hda_eld_proc_free(struct hda_codec *codec, struct hdmi_eld *eld); +#else +static inline int snd_hda_eld_proc_new(struct hda_codec *codec, + struct hdmi_eld *eld) +{ + return 0; +} +static inline void snd_hda_eld_proc_free(struct hda_codec *codec, + struct hdmi_eld *eld) +{ +} +#endif + +#define SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE 80 +void snd_print_channel_allocation(int spk_alloc, char *buf, int buflen); + #endif /* __SOUND_HDA_LOCAL_H */ --- a/sound/pci/hda/hda_patch.h +++ b/sound/pci/hda/hda_patch.h @@ -20,3 +20,5 @@ extern struct hda_codec_preset snd_hda_p extern struct hda_codec_preset snd_hda_preset_via[]; /* NVIDIA HDMI codecs */ extern struct hda_codec_preset snd_hda_preset_nvhdmi[]; +/* Intel HDMI codecs */ +extern struct hda_codec_preset snd_hda_preset_intelhdmi[]; --- a/sound/pci/hda/hda_proc.c +++ b/sound/pci/hda/hda_proc.c @@ -91,31 +91,21 @@ static void print_amp_vals(struct snd_in static void print_pcm_rates(struct snd_info_buffer *buffer, unsigned int pcm) { - static unsigned int rates[] = { - 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, - 96000, 176400, 192000, 384000 - }; - int i; + char buf[SND_PRINT_RATES_ADVISED_BUFSIZE]; pcm &= AC_SUPPCM_RATES; snd_iprintf(buffer, " rates [0x%x]:", pcm); - for (i = 0; i < ARRAY_SIZE(rates); i++) - if (pcm & (1 << i)) - snd_iprintf(buffer, " %d", rates[i]); - snd_iprintf(buffer, "\n"); + snd_print_pcm_rates(pcm, buf, sizeof(buf)); + snd_iprintf(buffer, "%s\n", buf); } static void print_pcm_bits(struct snd_info_buffer *buffer, unsigned int pcm) { - static unsigned int bits[] = { 8, 16, 20, 24, 32 }; - int i; + char buf[SND_PRINT_BITS_ADVISED_BUFSIZE]; - pcm = (pcm >> 16) & 0xff; - snd_iprintf(buffer, " bits [0x%x]:", pcm); - for (i = 0; i < ARRAY_SIZE(bits); i++) - if (pcm & (1 << i)) - snd_iprintf(buffer, " %d", bits[i]); - snd_iprintf(buffer, "\n"); + snd_iprintf(buffer, " bits [0x%x]:", (pcm >> 16) & 0xff); + snd_print_pcm_bits(pcm, buf, sizeof(buf)); + snd_iprintf(buffer, "%s\n", buf); } static void print_pcm_formats(struct snd_info_buffer *buffer, @@ -145,32 +135,6 @@ static void print_pcm_caps(struct snd_in print_pcm_formats(buffer, stream); } -static const char *get_jack_location(u32 cfg) -{ - static char *bases[7] = { - "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom", - }; - static unsigned char specials_idx[] = { - 0x07, 0x08, - 0x17, 0x18, 0x19, - 0x37, 0x38 - }; - static char *specials[] = { - "Rear Panel", "Drive Bar", - "Riser", "HDMI", "ATAPI", - "Mobile-In", "Mobile-Out" - }; - int i; - cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT; - if ((cfg & 0x0f) < 7) - return bases[cfg & 0x0f]; - for (i = 0; i < ARRAY_SIZE(specials_idx); i++) { - if (cfg == specials_idx[i]) - return specials[i]; - } - return "UNKNOWN"; -} - static const char *get_jack_connection(u32 cfg) { static char *names[16] = { @@ -206,13 +170,6 @@ static void print_pin_caps(struct snd_in int *supports_vref) { static char *jack_conns[4] = { "Jack", "N/A", "Fixed", "Both" }; - static char *jack_types[16] = { - "Line Out", "Speaker", "HP Out", "CD", - "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand", - "Line In", "Aux", "Mic", "Telephony", - "SPDIF In", "Digitial In", "Reserved", "Other" - }; - static char *jack_locations[4] = { "Ext", "Int", "Sep", "Oth" }; unsigned int caps, val; caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); @@ -236,8 +193,6 @@ static void print_pin_caps(struct snd_in else snd_iprintf(buffer, " HDMI"); } - if (caps & AC_PINCAP_LR_SWAP) - snd_iprintf(buffer, " R/L"); if (caps & AC_PINCAP_TRIG_REQ) snd_iprintf(buffer, " Trigger"); if (caps & AC_PINCAP_IMP_SENSE) @@ -276,9 +231,9 @@ static void print_pin_caps(struct snd_in caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); snd_iprintf(buffer, " Pin Default 0x%08x: [%s] %s at %s %s\n", caps, jack_conns[(caps & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT], - jack_types[(caps & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT], - jack_locations[(caps >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3], - get_jack_location(caps)); + snd_hda_get_jack_type(caps), + snd_hda_get_jack_connectivity(caps), + snd_hda_get_jack_location(caps)); snd_iprintf(buffer, " Conn = %s, Color = %s\n", get_jack_connection(caps), get_jack_color(caps)); @@ -444,7 +399,10 @@ static void print_conn_list(struct snd_i { int c, curr = -1; - if (conn_len > 1 && wid_type != AC_WID_AUD_MIX) + if (conn_len > 1 && + wid_type != AC_WID_AUD_MIX && + wid_type != AC_WID_VOL_KNB && + wid_type != AC_WID_POWER) curr = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_SEL, 0); snd_iprintf(buffer, " Connection: %d\n", conn_len); @@ -502,12 +460,13 @@ static void print_gpio(struct snd_info_b for (i = 0; i < max; ++i) snd_iprintf(buffer, " IO[%d]: enable=%d, dir=%d, wake=%d, " - "sticky=%d, data=%d\n", i, + "sticky=%d, data=%d, unsol=%d\n", i, (enable & (1< + * Wu Fengguang + * + * Maintained by: + * Wu Fengguang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include "hda_codec.h" +#include "hda_local.h" +#include "hda_patch.h" + +#define CVT_NID 0x02 /* audio converter */ +#define PIN_NID 0x03 /* HDMI output pin */ + +#define INTEL_HDMI_EVENT_TAG 0x08 + +struct intel_hdmi_spec { + struct hda_multi_out multiout; + struct hda_pcm pcm_rec; + struct hdmi_eld sink_eld; +}; + +static struct hda_verb pinout_enable_verb[] = { + {PIN_NID, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {} /* terminator */ +}; + +static struct hda_verb unsolicited_response_verb[] = { + {PIN_NID, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | + INTEL_HDMI_EVENT_TAG}, + {} +}; + +static struct hda_verb def_chan_map[] = { + {CVT_NID, AC_VERB_SET_HDMI_CHAN_SLOT, 0x00}, + {CVT_NID, AC_VERB_SET_HDMI_CHAN_SLOT, 0x11}, + {CVT_NID, AC_VERB_SET_HDMI_CHAN_SLOT, 0x22}, + {CVT_NID, AC_VERB_SET_HDMI_CHAN_SLOT, 0x33}, + {CVT_NID, AC_VERB_SET_HDMI_CHAN_SLOT, 0x44}, + {CVT_NID, AC_VERB_SET_HDMI_CHAN_SLOT, 0x55}, + {CVT_NID, AC_VERB_SET_HDMI_CHAN_SLOT, 0x66}, + {CVT_NID, AC_VERB_SET_HDMI_CHAN_SLOT, 0x77}, + {} +}; + + +struct hdmi_audio_infoframe { + u8 type; /* 0x84 */ + u8 ver; /* 0x01 */ + u8 len; /* 0x0a */ + + u8 checksum; /* PB0 */ + u8 CC02_CT47; /* CC in bits 0:2, CT in 4:7 */ + u8 SS01_SF24; + u8 CXT04; + u8 CA; + u8 LFEPBL01_LSV36_DM_INH7; + u8 reserved[5]; /* PB6 - PB10 */ +}; + +/* + * CEA speaker placement: + * + * FLH FCH FRH + * FLW FL FLC FC FRC FR FRW + * + * LFE + * TC + * + * RL RLC RC RRC RR + * + * The Left/Right Surround channel _notions_ LS/RS in SMPTE 320M corresponds to + * CEA RL/RR; The SMPTE channel _assignment_ C/LFE is swapped to CEA LFE/FC. + */ +enum cea_speaker_placement { + FL = (1 << 0), /* Front Left */ + FC = (1 << 1), /* Front Center */ + FR = (1 << 2), /* Front Right */ + FLC = (1 << 3), /* Front Left Center */ + FRC = (1 << 4), /* Front Right Center */ + RL = (1 << 5), /* Rear Left */ + RC = (1 << 6), /* Rear Center */ + RR = (1 << 7), /* Rear Right */ + RLC = (1 << 8), /* Rear Left Center */ + RRC = (1 << 9), /* Rear Right Center */ + LFE = (1 << 10), /* Low Frequency Effect */ + FLW = (1 << 11), /* Front Left Wide */ + FRW = (1 << 12), /* Front Right Wide */ + FLH = (1 << 13), /* Front Left High */ + FCH = (1 << 14), /* Front Center High */ + FRH = (1 << 15), /* Front Right High */ + TC = (1 << 16), /* Top Center */ +}; + +/* + * ELD SA bits in the CEA Speaker Allocation data block + */ +static int eld_speaker_allocation_bits[] = { + [0] = FL | FR, + [1] = LFE, + [2] = FC, + [3] = RL | RR, + [4] = RC, + [5] = FLC | FRC, + [6] = RLC | RRC, + /* the following are not defined in ELD yet */ + [7] = FLW | FRW, + [8] = FLH | FRH, + [9] = TC, + [10] = FCH, +}; + +struct cea_channel_speaker_allocation { + int ca_index; + int speakers[8]; + + /* derived values, just for convenience */ + int channels; + int spk_mask; +}; + +/* + * This is an ordered list! + * + * The preceding ones have better chances to be selected by + * hdmi_setup_channel_allocation(). + */ +static struct cea_channel_speaker_allocation channel_allocations[] = { +/* channel: 8 7 6 5 4 3 2 1 */ +{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL } }, + /* 2.1 */ +{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL } }, + /* Dolby Surround */ +{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL } }, +{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL } }, +{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL } }, +{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL } }, +{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL } }, +{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL } }, +{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL } }, + /* 5.1 */ +{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL } }, + /* 6.1 */ +{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL } }, + /* 7.1 */ +{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL } }, +{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL } }, +{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL } }, +{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL } }, +{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL } }, +{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL } }, +{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL } }, +{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL } }, +{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL } }, +{ .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL } }, +{ .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL } }, +{ .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL } }, +{ .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL } }, +}; + +/* + * HDMI routines + */ + +#ifdef BE_PARANOID +static void hdmi_get_dip_index(struct hda_codec *codec, hda_nid_t nid, + int *packet_index, int *byte_index) +{ + int val; + + val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_HDMI_DIP_INDEX, 0); + + *packet_index = val >> 5; + *byte_index = val & 0x1f; +} +#endif + +static void hdmi_set_dip_index(struct hda_codec *codec, hda_nid_t nid, + int packet_index, int byte_index) +{ + int val; + + val = (packet_index << 5) | (byte_index & 0x1f); + + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_HDMI_DIP_INDEX, val); +} + +static void hdmi_write_dip_byte(struct hda_codec *codec, hda_nid_t nid, + unsigned char val) +{ + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_HDMI_DIP_DATA, val); +} + +static void hdmi_enable_output(struct hda_codec *codec) +{ + /* Unmute */ + if (get_wcaps(codec, PIN_NID) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, PIN_NID, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + /* Enable pin out */ + snd_hda_sequence_write(codec, pinout_enable_verb); +} + +/* + * Enable Audio InfoFrame Transmission + */ +static void hdmi_start_infoframe_trans(struct hda_codec *codec) +{ + hdmi_set_dip_index(codec, PIN_NID, 0x0, 0x0); + snd_hda_codec_write(codec, PIN_NID, 0, AC_VERB_SET_HDMI_DIP_XMIT, + AC_DIPXMIT_BEST); +} + +/* + * Disable Audio InfoFrame Transmission + */ +static void hdmi_stop_infoframe_trans(struct hda_codec *codec) +{ + hdmi_set_dip_index(codec, PIN_NID, 0x0, 0x0); + snd_hda_codec_write(codec, PIN_NID, 0, AC_VERB_SET_HDMI_DIP_XMIT, + AC_DIPXMIT_DISABLE); +} + +static int hdmi_get_channel_count(struct hda_codec *codec) +{ + return 1 + snd_hda_codec_read(codec, CVT_NID, 0, + AC_VERB_GET_CVT_CHAN_COUNT, 0); +} + +static void hdmi_set_channel_count(struct hda_codec *codec, int chs) +{ + snd_hda_codec_write(codec, CVT_NID, 0, + AC_VERB_SET_CVT_CHAN_COUNT, chs - 1); + + if (chs != hdmi_get_channel_count(codec)) + snd_printd(KERN_INFO "HDMI channel count: expect %d, get %d\n", + chs, hdmi_get_channel_count(codec)); +} + +static void hdmi_debug_channel_mapping(struct hda_codec *codec) +{ +#ifdef CONFIG_SND_DEBUG_VERBOSE + int i; + int slot; + + for (i = 0; i < 8; i++) { + slot = snd_hda_codec_read(codec, CVT_NID, 0, + AC_VERB_GET_HDMI_CHAN_SLOT, i); + printk(KERN_DEBUG "HDMI: ASP channel %d => slot %d\n", + slot >> 4, slot & 0x7); + } +#endif +} + +static void hdmi_parse_eld(struct hda_codec *codec) +{ + struct intel_hdmi_spec *spec = codec->spec; + struct hdmi_eld *eld = &spec->sink_eld; + + if (!snd_hdmi_get_eld(eld, codec, PIN_NID)) + snd_hdmi_show_eld(eld); +} + + +/* + * Audio InfoFrame routines + */ + +static void hdmi_debug_dip_size(struct hda_codec *codec) +{ +#ifdef CONFIG_SND_DEBUG_VERBOSE + int i; + int size; + + size = snd_hdmi_get_eld_size(codec, PIN_NID); + printk(KERN_DEBUG "HDMI: ELD buf size is %d\n", size); + + for (i = 0; i < 8; i++) { + size = snd_hda_codec_read(codec, PIN_NID, 0, + AC_VERB_GET_HDMI_DIP_SIZE, i); + printk(KERN_DEBUG "HDMI: DIP GP[%d] buf size is %d\n", i, size); + } +#endif +} + +static void hdmi_clear_dip_buffers(struct hda_codec *codec) +{ +#ifdef BE_PARANOID + int i, j; + int size; + int pi, bi; + for (i = 0; i < 8; i++) { + size = snd_hda_codec_read(codec, PIN_NID, 0, + AC_VERB_GET_HDMI_DIP_SIZE, i); + if (size == 0) + continue; + + hdmi_set_dip_index(codec, PIN_NID, i, 0x0); + for (j = 1; j < 1000; j++) { + hdmi_write_dip_byte(codec, PIN_NID, 0x0); + hdmi_get_dip_index(codec, PIN_NID, &pi, &bi); + if (pi != i) + snd_printd(KERN_INFO "dip index %d: %d != %d\n", + bi, pi, i); + if (bi == 0) /* byte index wrapped around */ + break; + } + snd_printd(KERN_INFO + "HDMI: DIP GP[%d] buf reported size=%d, written=%d\n", + i, size, j); + } +#endif +} + +static void hdmi_fill_audio_infoframe(struct hda_codec *codec, + struct hdmi_audio_infoframe *ai) +{ + u8 *params = (u8 *)ai; + u8 sum = 0; + int i; + + hdmi_debug_dip_size(codec); + hdmi_clear_dip_buffers(codec); /* be paranoid */ + + for (i = 0; i < sizeof(ai); i++) + sum += params[i]; + ai->checksum = - sum; + + hdmi_set_dip_index(codec, PIN_NID, 0x0, 0x0); + for (i = 0; i < sizeof(ai); i++) + hdmi_write_dip_byte(codec, PIN_NID, params[i]); +} + +/* + * Compute derived values in channel_allocations[]. + */ +static void init_channel_allocations(void) +{ + int i, j; + struct cea_channel_speaker_allocation *p; + + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + p = channel_allocations + i; + p->channels = 0; + p->spk_mask = 0; + for (j = 0; j < ARRAY_SIZE(p->speakers); j++) + if (p->speakers[j]) { + p->channels++; + p->spk_mask |= p->speakers[j]; + } + } +} + +/* + * The transformation takes two steps: + * + * eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask + * spk_mask => (channel_allocations[]) => ai->CA + * + * TODO: it could select the wrong CA from multiple candidates. +*/ +static int hdmi_setup_channel_allocation(struct hda_codec *codec, + struct hdmi_audio_infoframe *ai) +{ + struct intel_hdmi_spec *spec = codec->spec; + struct hdmi_eld *eld = &spec->sink_eld; + int i; + int spk_mask = 0; + int channels = 1 + (ai->CC02_CT47 & 0x7); + char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE]; + + /* + * CA defaults to 0 for basic stereo audio + */ + if (channels <= 2) + return 0; + + /* + * HDMI sink's ELD info cannot always be retrieved for now, e.g. + * in console or for audio devices. Assume the highest speakers + * configuration, to _not_ prohibit multi-channel audio playback. + */ + if (!eld->spk_alloc) + eld->spk_alloc = 0xffff; + + /* + * expand ELD's speaker allocation mask + * + * ELD tells the speaker mask in a compact(paired) form, + * expand ELD's notions to match the ones used by Audio InfoFrame. + */ + for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) { + if (eld->spk_alloc & (1 << i)) + spk_mask |= eld_speaker_allocation_bits[i]; + } + + /* search for the first working match in the CA table */ + for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) { + if (channels == channel_allocations[i].channels && + (spk_mask & channel_allocations[i].spk_mask) == + channel_allocations[i].spk_mask) { + ai->CA = channel_allocations[i].ca_index; + break; + } + } + + snd_print_channel_allocation(eld->spk_alloc, buf, sizeof(buf)); + snd_printdd(KERN_INFO + "HDMI: select CA 0x%x for %d-channel allocation: %s\n", + ai->CA, channels, buf); + + return ai->CA; +} + +static void hdmi_setup_channel_mapping(struct hda_codec *codec, + struct hdmi_audio_infoframe *ai) +{ + if (!ai->CA) + return; + + /* + * TODO: adjust channel mapping if necessary + * ALSA sequence is front/surr/clfe/side? + */ + + snd_hda_sequence_write(codec, def_chan_map); + hdmi_debug_channel_mapping(codec); +} + + +static void hdmi_setup_audio_infoframe(struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hdmi_audio_infoframe ai = { + .type = 0x84, + .ver = 0x01, + .len = 0x0a, + .CC02_CT47 = substream->runtime->channels - 1, + }; + + hdmi_setup_channel_allocation(codec, &ai); + hdmi_setup_channel_mapping(codec, &ai); + + hdmi_fill_audio_infoframe(codec, &ai); + hdmi_start_infoframe_trans(codec); +} + + +/* + * Unsolicited events + */ + +static void hdmi_intrinsic_event(struct hda_codec *codec, unsigned int res) +{ + int pind = !!(res & AC_UNSOL_RES_PD); + int eldv = !!(res & AC_UNSOL_RES_ELDV); + + printk(KERN_INFO + "HDMI hot plug event: Presence_Detect=%d ELD_Valid=%d\n", + pind, eldv); + + if (pind && eldv) { + hdmi_parse_eld(codec); + /* TODO: do real things about ELD */ + } +} + +static void hdmi_non_intrinsic_event(struct hda_codec *codec, unsigned int res) +{ + int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT; + int cp_state = !!(res & AC_UNSOL_RES_CP_STATE); + int cp_ready = !!(res & AC_UNSOL_RES_CP_READY); + + printk(KERN_INFO + "HDMI content protection event: SUBTAG=0x%x CP_STATE=%d CP_READY=%d\n", + subtag, + cp_state, + cp_ready); + + /* TODO */ + if (cp_state) + ; + if (cp_ready) + ; +} + + +static void intel_hdmi_unsol_event(struct hda_codec *codec, unsigned int res) +{ + int tag = res >> AC_UNSOL_RES_TAG_SHIFT; + int subtag = (res & AC_UNSOL_RES_SUBTAG) >> AC_UNSOL_RES_SUBTAG_SHIFT; + + if (tag != INTEL_HDMI_EVENT_TAG) { + snd_printd(KERN_INFO "Unexpected HDMI event tag 0x%x\n", tag); + return; + } + + if (subtag == 0) + hdmi_intrinsic_event(codec, res); + else + hdmi_non_intrinsic_event(codec, res); +} + +/* + * Callbacks + */ + +static int intel_hdmi_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct intel_hdmi_spec *spec = codec->spec; + + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int intel_hdmi_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct intel_hdmi_spec *spec = codec->spec; + + hdmi_stop_infoframe_trans(codec); + + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int intel_hdmi_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct intel_hdmi_spec *spec = codec->spec; + + snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag, + format, substream); + + hdmi_set_channel_count(codec, substream->runtime->channels); + + hdmi_setup_audio_infoframe(codec, substream); + + return 0; +} + +static struct hda_pcm_stream intel_hdmi_pcm_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 8, + .nid = CVT_NID, /* NID to query formats and rates and setup streams */ + .ops = { + .open = intel_hdmi_playback_pcm_open, + .close = intel_hdmi_playback_pcm_close, + .prepare = intel_hdmi_playback_pcm_prepare + }, +}; + +static int intel_hdmi_build_pcms(struct hda_codec *codec) +{ + struct intel_hdmi_spec *spec = codec->spec; + struct hda_pcm *info = &spec->pcm_rec; + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "INTEL HDMI"; + info->pcm_type = HDA_PCM_TYPE_HDMI; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = intel_hdmi_pcm_playback; + + return 0; +} + +static int intel_hdmi_build_controls(struct hda_codec *codec) +{ + struct intel_hdmi_spec *spec = codec->spec; + int err; + + err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid); + if (err < 0) + return err; + + return 0; +} + +static int intel_hdmi_init(struct hda_codec *codec) +{ + hdmi_enable_output(codec); + + snd_hda_sequence_write(codec, unsolicited_response_verb); + + return 0; +} + +static void intel_hdmi_free(struct hda_codec *codec) +{ + struct intel_hdmi_spec *spec = codec->spec; + + snd_hda_eld_proc_free(codec, &spec->sink_eld); + kfree(spec); +} + +static struct hda_codec_ops intel_hdmi_patch_ops = { + .init = intel_hdmi_init, + .free = intel_hdmi_free, + .build_pcms = intel_hdmi_build_pcms, + .build_controls = intel_hdmi_build_controls, + .unsol_event = intel_hdmi_unsol_event, +}; + +static int patch_intel_hdmi(struct hda_codec *codec) +{ + struct intel_hdmi_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + spec->multiout.num_dacs = 0; /* no analog */ + spec->multiout.max_channels = 8; + spec->multiout.dig_out_nid = CVT_NID; + + codec->spec = spec; + codec->patch_ops = intel_hdmi_patch_ops; + + snd_hda_eld_proc_new(codec, &spec->sink_eld); + + init_channel_allocations(); + + return 0; +} + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_intelhdmi[] = { + { .id = 0x808629fb, .name = "G45 DEVCL", .patch = patch_intel_hdmi }, + { .id = 0x80862801, .name = "G45 DEVBLC", .patch = patch_intel_hdmi }, + { .id = 0x80862802, .name = "G45 DEVCTG", .patch = patch_intel_hdmi }, + { .id = 0x80862803, .name = "G45 DEVELK", .patch = patch_intel_hdmi }, + { .id = 0x80862804, .name = "G45 DEVIBX", .patch = patch_intel_hdmi }, + { .id = 0x10951392, .name = "SiI1392 HDMI", .patch = patch_intel_hdmi }, + {} /* terminator */ +}; --- a/sound/pci/hda/patch_nvhdmi.c +++ b/sound/pci/hda/patch_nvhdmi.c @@ -159,7 +159,10 @@ static int patch_nvhdmi(struct hda_codec * patch entries */ struct hda_codec_preset snd_hda_preset_nvhdmi[] = { - { .id = 0x10de0002, .name = "NVIDIA MCP78 HDMI", .patch = patch_nvhdmi }, - { .id = 0x10de0007, .name = "NVIDIA MCP7A HDMI", .patch = patch_nvhdmi }, + { .id = 0x10de0002, .name = "MCP78 HDMI", .patch = patch_nvhdmi }, + { .id = 0x10de0006, .name = "MCP78 HDMI", .patch = patch_nvhdmi }, + { .id = 0x10de0007, .name = "MCP7A HDMI", .patch = patch_nvhdmi }, + { .id = 0x10de0067, .name = "MCP67 HDMI", .patch = patch_nvhdmi }, + { .id = 0x10de8001, .name = "MCP73 HDMI", .patch = patch_nvhdmi }, {} /* terminator */ };