summaryrefslogtreecommitdiff
path: root/sound/pci/hda/patch_hdmi.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2013-02-21 11:34:25 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2013-02-21 11:34:25 -0800
commit460dc1eecf37263c8e3b17685ef236f0d236facb (patch)
tree1d20e367cefccddb969b48afaab140b8125cea4e /sound/pci/hda/patch_hdmi.c
parent024e4ec1856d57bb78c06ec903d29dcf716f5f47 (diff)
parentb531f81b0d70ffbe8d70500512483227cc532608 (diff)
Merge tag 'sound-3.9' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai: "The biggest change in this update is the unification of HD-audio codec parsers. Now the HD-audio codec is parsed in a generic parser code which is invoked by each HD-audio codec driver. Some background information is found in David Henningsson's blog entry: http://voices.canonical.com/david.henningsson/2013/01/18/upcoming-changes-to-the-intel-hda-drivers/ Other than that, some random updates/fixes like USB-audio and a bunch of small AoC updates as usual. Highlights: - Unification of HD-audio parser code (aka generic parser) - Support of new Intel HD-audio controller, new IDT codecs - Fixes for HD-audio HDMI audio hotplug - Haswell HDMI audio fixup - Support of Creative CA0132 DSP code - A few fixes of HDSP driver - USB-audio fix for Roland A-PRO, M-Audio FT C600 - Support PM for aloop driver (and fixes Oops) - Compress API updates for gapless playback support For ASoC part: - Support for a wider range of hardware in the compressed stream code - The ability to mute capture streams as well as playback streams while inactive - DT support for AK4642, FSI, Samsung I2S and WM8962 - AC'97 support for Tegra - New driver for max98090, replacing the stub which was there - A new driver from Dialog Note that due to dependencies, DTification of DMA support for Samsung platforms (used only by the and I2S driver and SPI) is merged here as well." Fix up trivial conflict in drivers/spi/spi-s3c64xx.c due to removed code being changed. * tag 'sound-3.9' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (453 commits) ALSA: usb: Fix Processing Unit Descriptor parsers ALSA: hda - hdmi: Notify userspace when ELD control changes ALSA: hda - hdmi: Protect ELD buffer ALSA: hda - hdmi: Refactor hdmi_eld into parsed_hdmi_eld ALSA: hda - hdmi: Do not expose eld data when eld is invalid ALSA: hda - hdmi: ELD shouldn't be valid after unplug ALSA: hda - Fix the silent speaker output on Fujitsu S7020 laptop ALSA: hda - add quirks for mute LED on two HP machines ALSA: usb/quirks, fix out-of-bounds access ASoC: codecs: Add da7213 codec ALSA: au88x0 - Define channel map for au88x0 ALSA: compress: add support for gapless playback ALSA: hda - Remove speaker clicks on CX20549 ALSA: hda - Disable runtime PM for Intel 5 Series/3400 ALSA: hda - Increase badness for missing multi-io ASoC: arizona: Automatically manage input mutes ALSA: hda - Fix broken workaround for HDMI/SPDIF conflicts ALSA: hda/ca0132 - Add missing \n to debug prints ALSA: hda/ca0132 - Fix type of INVALID_CHIP_ADDRESS ALSA: hda - update documentation for no-primary-hp fixup ...
Diffstat (limited to 'sound/pci/hda/patch_hdmi.c')
-rw-r--r--sound/pci/hda/patch_hdmi.c227
1 files changed, 184 insertions, 43 deletions
diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index 807a2aa1ff38..21425fb51fe0 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -64,6 +64,9 @@ struct hdmi_spec_per_cvt {
unsigned int maxbps;
};
+/* max. connections to a widget */
+#define HDA_MAX_CONNECTIONS 32
+
struct hdmi_spec_per_pin {
hda_nid_t pin_nid;
int num_mux_nids;
@@ -72,6 +75,7 @@ struct hdmi_spec_per_pin {
struct hda_codec *codec;
struct hdmi_eld sink_eld;
struct delayed_work work;
+ struct snd_kcontrol *eld_ctl;
int repoll_count;
bool non_pcm;
bool chmap_set; /* channel-map override by ALSA API? */
@@ -81,12 +85,14 @@ struct hdmi_spec_per_pin {
struct hdmi_spec {
int num_cvts;
struct hdmi_spec_per_cvt cvts[MAX_HDMI_CVTS];
+ hda_nid_t cvt_nids[MAX_HDMI_CVTS];
int num_pins;
struct hdmi_spec_per_pin pins[MAX_HDMI_PINS];
struct hda_pcm pcm_rec[MAX_HDMI_PINS];
unsigned int channels_max; /* max over all cvts */
+ struct hdmi_eld temp_eld;
/*
* Non-generic ATI/NVIDIA specific
*/
@@ -339,14 +345,18 @@ static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
- struct hdmi_spec *spec;
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_eld *eld;
int pin_idx;
- spec = codec->spec;
uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
pin_idx = kcontrol->private_value;
- uinfo->count = spec->pins[pin_idx].sink_eld.eld_size;
+ eld = &spec->pins[pin_idx].sink_eld;
+
+ mutex_lock(&eld->lock);
+ uinfo->count = eld->eld_valid ? eld->eld_size : 0;
+ mutex_unlock(&eld->lock);
return 0;
}
@@ -355,14 +365,26 @@ static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
- struct hdmi_spec *spec;
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_eld *eld;
int pin_idx;
- spec = codec->spec;
pin_idx = kcontrol->private_value;
+ eld = &spec->pins[pin_idx].sink_eld;
+
+ mutex_lock(&eld->lock);
+ if (eld->eld_size > ARRAY_SIZE(ucontrol->value.bytes.data)) {
+ mutex_unlock(&eld->lock);
+ snd_BUG();
+ return -EINVAL;
+ }
- memcpy(ucontrol->value.bytes.data,
- spec->pins[pin_idx].sink_eld.eld_buffer, ELD_MAX_SIZE);
+ memset(ucontrol->value.bytes.data, 0,
+ ARRAY_SIZE(ucontrol->value.bytes.data));
+ if (eld->eld_valid)
+ memcpy(ucontrol->value.bytes.data, eld->eld_buffer,
+ eld->eld_size);
+ mutex_unlock(&eld->lock);
return 0;
}
@@ -392,6 +414,7 @@ static int hdmi_create_eld_ctl(struct hda_codec *codec, int pin_idx,
if (err < 0)
return err;
+ spec->pins[pin_idx].eld_ctl = kctl;
return 0;
}
@@ -516,7 +539,7 @@ static int hdmi_channel_allocation(struct hdmi_eld *eld, int channels)
* 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))
+ if (eld->info.spk_alloc & (1 << i))
spk_mask |= eld_speaker_allocation_bits[i];
}
@@ -530,7 +553,7 @@ static int hdmi_channel_allocation(struct hdmi_eld *eld, int channels)
}
}
- snd_print_channel_allocation(eld->spk_alloc, buf, sizeof(buf));
+ snd_print_channel_allocation(eld->info.spk_alloc, buf, sizeof(buf));
snd_printdd("HDMI: select CA 0x%x for %d-channel allocation: %s\n",
ca, channels, buf);
@@ -714,9 +737,10 @@ static void hdmi_setup_fake_chmap(unsigned char *map, int ca)
static void hdmi_setup_channel_mapping(struct hda_codec *codec,
hda_nid_t pin_nid, bool non_pcm, int ca,
- int channels, unsigned char *map)
+ int channels, unsigned char *map,
+ bool chmap_set)
{
- if (!non_pcm && map) {
+ if (!non_pcm && chmap_set) {
hdmi_manual_setup_channel_mapping(codec, pin_nid,
channels, map);
} else {
@@ -870,7 +894,7 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, int pin_idx,
ca = 0;
memset(&ai, 0, sizeof(ai));
- if (eld->conn_type == 0) { /* HDMI */
+ if (eld->info.conn_type == 0) { /* HDMI */
struct hdmi_audio_infoframe *hdmi_ai = &ai.hdmi;
hdmi_ai->type = 0x84;
@@ -879,7 +903,7 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, int pin_idx,
hdmi_ai->CC02_CT47 = channels - 1;
hdmi_ai->CA = ca;
hdmi_checksum_audio_infoframe(hdmi_ai);
- } else if (eld->conn_type == 1) { /* DisplayPort */
+ } else if (eld->info.conn_type == 1) { /* DisplayPort */
struct dp_audio_infoframe *dp_ai = &ai.dp;
dp_ai->type = 0x84;
@@ -905,7 +929,8 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, int pin_idx,
pin_nid,
channels);
hdmi_setup_channel_mapping(codec, pin_nid, non_pcm, ca,
- channels, per_pin->chmap);
+ channels, per_pin->chmap,
+ per_pin->chmap_set);
hdmi_stop_infoframe_trans(codec, pin_nid);
hdmi_fill_audio_infoframe(codec, pin_nid,
ai.bytes, sizeof(ai));
@@ -915,7 +940,8 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, int pin_idx,
* accordingly */
if (per_pin->non_pcm != non_pcm)
hdmi_setup_channel_mapping(codec, pin_nid, non_pcm, ca,
- channels, per_pin->chmap);
+ channels, per_pin->chmap,
+ per_pin->chmap_set);
}
per_pin->non_pcm = non_pcm;
@@ -1098,10 +1124,14 @@ static int hdmi_pcm_open(struct hda_pcm_stream *hinfo,
/* Restrict capabilities by ELD if this isn't disabled */
if (!static_hdmi_pcm && eld->eld_valid) {
- snd_hdmi_eld_update_pcm_info(eld, hinfo);
+ snd_hdmi_eld_update_pcm_info(&eld->info, hinfo);
if (hinfo->channels_min > hinfo->channels_max ||
- !hinfo->rates || !hinfo->formats)
+ !hinfo->rates || !hinfo->formats) {
+ per_cvt->assigned = 0;
+ hinfo->nid = 0;
+ snd_hda_spdif_ctls_unassign(codec, pin_idx);
return -ENODEV;
+ }
}
/* Store the updated parameters */
@@ -1142,7 +1172,9 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx)
static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
{
struct hda_codec *codec = per_pin->codec;
- struct hdmi_eld *eld = &per_pin->sink_eld;
+ struct hdmi_spec *spec = codec->spec;
+ struct hdmi_eld *eld = &spec->temp_eld;
+ struct hdmi_eld *pin_eld = &per_pin->sink_eld;
hda_nid_t pin_nid = per_pin->pin_nid;
/*
* Always execute a GetPinSense verb here, even when called from
@@ -1153,27 +1185,64 @@ static void hdmi_present_sense(struct hdmi_spec_per_pin *per_pin, int repoll)
* the unsolicited response to avoid custom WARs.
*/
int present = snd_hda_pin_sense(codec, pin_nid);
- bool eld_valid = false;
+ bool update_eld = false;
+ bool eld_changed = false;
- memset(eld, 0, offsetof(struct hdmi_eld, eld_buffer));
-
- eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE);
- if (eld->monitor_present)
- eld_valid = !!(present & AC_PINSENSE_ELDV);
+ pin_eld->monitor_present = !!(present & AC_PINSENSE_PRESENCE);
+ if (pin_eld->monitor_present)
+ eld->eld_valid = !!(present & AC_PINSENSE_ELDV);
+ else
+ eld->eld_valid = false;
_snd_printd(SND_PR_VERBOSE,
"HDMI status: Codec=%d Pin=%d Presence_Detect=%d ELD_Valid=%d\n",
- codec->addr, pin_nid, eld->monitor_present, eld_valid);
+ codec->addr, pin_nid, eld->monitor_present, eld->eld_valid);
+
+ if (eld->eld_valid) {
+ if (snd_hdmi_get_eld(codec, pin_nid, eld->eld_buffer,
+ &eld->eld_size) < 0)
+ eld->eld_valid = false;
+ else {
+ memset(&eld->info, 0, sizeof(struct parsed_hdmi_eld));
+ if (snd_hdmi_parse_eld(&eld->info, eld->eld_buffer,
+ eld->eld_size) < 0)
+ eld->eld_valid = false;
+ }
- if (eld_valid) {
- if (!snd_hdmi_get_eld(eld, codec, pin_nid))
- snd_hdmi_show_eld(eld);
+ if (eld->eld_valid) {
+ snd_hdmi_show_eld(&eld->info);
+ update_eld = true;
+ }
else if (repoll) {
queue_delayed_work(codec->bus->workq,
&per_pin->work,
msecs_to_jiffies(300));
+ return;
}
}
+
+ mutex_lock(&pin_eld->lock);
+ if (pin_eld->eld_valid && !eld->eld_valid) {
+ update_eld = true;
+ eld_changed = true;
+ }
+ if (update_eld) {
+ pin_eld->eld_valid = eld->eld_valid;
+ eld_changed = pin_eld->eld_size != eld->eld_size ||
+ memcmp(pin_eld->eld_buffer, eld->eld_buffer,
+ eld->eld_size) != 0;
+ if (eld_changed)
+ memcpy(pin_eld->eld_buffer, eld->eld_buffer,
+ eld->eld_size);
+ pin_eld->eld_size = eld->eld_size;
+ pin_eld->info = eld->info;
+ }
+ mutex_unlock(&pin_eld->lock);
+
+ if (eld_changed)
+ snd_ctl_notify(codec->bus->card,
+ SNDRV_CTL_EVENT_MASK_VALUE | SNDRV_CTL_EVENT_MASK_INFO,
+ &per_pin->eld_ctl->id);
}
static void hdmi_repoll_eld(struct work_struct *work)
@@ -1187,6 +1256,9 @@ static void hdmi_repoll_eld(struct work_struct *work)
hdmi_present_sense(per_pin, per_pin->repoll_count);
}
+static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
+ hda_nid_t nid);
+
static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid)
{
struct hdmi_spec *spec = codec->spec;
@@ -1206,6 +1278,9 @@ static int hdmi_add_pin(struct hda_codec *codec, hda_nid_t pin_nid)
if (snd_BUG_ON(spec->num_pins >= MAX_HDMI_PINS))
return -E2BIG;
+ if (codec->vendor_id == 0x80862807)
+ intel_haswell_fixup_connect_list(codec, pin_nid);
+
pin_idx = spec->num_pins;
per_pin = &spec->pins[pin_idx];
@@ -1253,7 +1328,7 @@ static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t cvt_nid)
if (err < 0)
return err;
- spec->num_cvts++;
+ spec->cvt_nids[spec->num_cvts++] = cvt_nid;
return 0;
}
@@ -1635,6 +1710,7 @@ static int generic_hdmi_init_per_pins(struct hda_codec *codec)
struct hdmi_eld *eld = &per_pin->sink_eld;
per_pin->codec = codec;
+ mutex_init(&eld->lock);
INIT_DELAYED_WORK(&per_pin->work, hdmi_repoll_eld);
snd_hda_eld_proc_new(codec, eld, pin_idx);
}
@@ -1681,30 +1757,92 @@ static const struct hda_codec_ops generic_hdmi_patch_ops = {
.unsol_event = hdmi_unsol_event,
};
-static void intel_haswell_fixup_connect_list(struct hda_codec *codec)
+
+static void intel_haswell_fixup_connect_list(struct hda_codec *codec,
+ hda_nid_t nid)
+{
+ struct hdmi_spec *spec = codec->spec;
+ hda_nid_t conns[4];
+ int nconns;
+
+ nconns = snd_hda_get_connections(codec, nid, conns, ARRAY_SIZE(conns));
+ if (nconns == spec->num_cvts &&
+ !memcmp(conns, spec->cvt_nids, spec->num_cvts * sizeof(hda_nid_t)))
+ return;
+
+ /* override pins connection list */
+ snd_printdd("hdmi: haswell: override pin connection 0x%x\n", nid);
+ snd_hda_override_conn_list(codec, nid, spec->num_cvts, spec->cvt_nids);
+}
+
+#define INTEL_VENDOR_NID 0x08
+#define INTEL_GET_VENDOR_VERB 0xf81
+#define INTEL_SET_VENDOR_VERB 0x781
+#define INTEL_EN_DP12 0x02 /* enable DP 1.2 features */
+#define INTEL_EN_ALL_PIN_CVTS 0x01 /* enable 2nd & 3rd pins and convertors */
+
+static void intel_haswell_enable_all_pins(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
{
unsigned int vendor_param;
- hda_nid_t list[3] = {0x2, 0x3, 0x4};
- vendor_param = snd_hda_codec_read(codec, 0x08, 0, 0xf81, 0);
- if (vendor_param == -1 || vendor_param & 0x02)
+ if (action != HDA_FIXUP_ACT_PRE_PROBE)
+ return;
+ vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
+ INTEL_GET_VENDOR_VERB, 0);
+ if (vendor_param == -1 || vendor_param & INTEL_EN_ALL_PIN_CVTS)
return;
- /* enable DP1.2 mode */
- vendor_param |= 0x02;
- snd_hda_codec_read(codec, 0x08, 0, 0x781, vendor_param);
+ vendor_param |= INTEL_EN_ALL_PIN_CVTS;
+ vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
+ INTEL_SET_VENDOR_VERB, vendor_param);
+ if (vendor_param == -1)
+ return;
+
+ snd_hda_codec_update_widgets(codec);
+ return;
+}
+
+static void intel_haswell_fixup_enable_dp12(struct hda_codec *codec)
+{
+ unsigned int vendor_param;
- vendor_param = snd_hda_codec_read(codec, 0x08, 0, 0xf81, 0);
- if (vendor_param == -1 || !(vendor_param & 0x02))
+ vendor_param = snd_hda_codec_read(codec, INTEL_VENDOR_NID, 0,
+ INTEL_GET_VENDOR_VERB, 0);
+ if (vendor_param == -1 || vendor_param & INTEL_EN_DP12)
return;
- /* override 3 pins connection list */
- snd_hda_override_conn_list(codec, 0x05, 3, list);
- snd_hda_override_conn_list(codec, 0x06, 3, list);
- snd_hda_override_conn_list(codec, 0x07, 3, list);
+ /* enable DP1.2 mode */
+ vendor_param |= INTEL_EN_DP12;
+ snd_hda_codec_write_cache(codec, INTEL_VENDOR_NID, 0,
+ INTEL_SET_VENDOR_VERB, vendor_param);
}
+
+/* available models for fixup */
+enum {
+ INTEL_HASWELL,
+};
+
+static const struct hda_model_fixup hdmi_models[] = {
+ {.id = INTEL_HASWELL, .name = "Haswell"},
+ {}
+};
+
+static const struct snd_pci_quirk hdmi_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x8086, 0x2010, "Haswell", INTEL_HASWELL),
+ {} /* terminator */
+};
+
+static const struct hda_fixup hdmi_fixups[] = {
+ [INTEL_HASWELL] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = intel_haswell_enable_all_pins,
+ },
+};
+
+
static int patch_generic_hdmi(struct hda_codec *codec)
{
struct hdmi_spec *spec;
@@ -1715,8 +1853,11 @@ static int patch_generic_hdmi(struct hda_codec *codec)
codec->spec = spec;
+ snd_hda_pick_fixup(codec, hdmi_models, hdmi_fixup_tbl, hdmi_fixups);
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
if (codec->vendor_id == 0x80862807)
- intel_haswell_fixup_connect_list(codec);
+ intel_haswell_fixup_enable_dp12(codec);
if (hdmi_parse_codec(codec) < 0) {
codec->spec = NULL;