|
@@ -35,6 +35,7 @@
|
|
|
#include <sound/core.h>
|
|
|
#include <sound/jack.h>
|
|
|
#include <sound/asoundef.h>
|
|
|
+#include <sound/tlv.h>
|
|
|
#include "hda_codec.h"
|
|
|
#include "hda_local.h"
|
|
|
#include "hda_jack.h"
|
|
@@ -73,6 +74,8 @@ struct hdmi_spec_per_pin {
|
|
|
struct delayed_work work;
|
|
|
int repoll_count;
|
|
|
bool non_pcm;
|
|
|
+ bool chmap_set; /* channel-map override by ALSA API? */
|
|
|
+ unsigned char chmap[8]; /* ALSA API channel-map */
|
|
|
};
|
|
|
|
|
|
struct hdmi_spec {
|
|
@@ -82,6 +85,7 @@ struct hdmi_spec {
|
|
|
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 */
|
|
|
|
|
|
/*
|
|
|
* Non-generic ATI/NVIDIA specific
|
|
@@ -548,7 +552,7 @@ static void hdmi_debug_channel_mapping(struct hda_codec *codec,
|
|
|
}
|
|
|
|
|
|
|
|
|
-static void hdmi_setup_channel_mapping(struct hda_codec *codec,
|
|
|
+static void hdmi_std_setup_channel_mapping(struct hda_codec *codec,
|
|
|
hda_nid_t pin_nid,
|
|
|
bool non_pcm,
|
|
|
int ca)
|
|
@@ -588,6 +592,136 @@ static void hdmi_setup_channel_mapping(struct hda_codec *codec,
|
|
|
hdmi_debug_channel_mapping(codec, pin_nid);
|
|
|
}
|
|
|
|
|
|
+struct channel_map_table {
|
|
|
+ unsigned char map; /* ALSA API channel map position */
|
|
|
+ unsigned char cea_slot; /* CEA slot value */
|
|
|
+ int spk_mask; /* speaker position bit mask */
|
|
|
+};
|
|
|
+
|
|
|
+static struct channel_map_table map_tables[] = {
|
|
|
+ { SNDRV_CHMAP_FL, 0x00, FL },
|
|
|
+ { SNDRV_CHMAP_FR, 0x01, FR },
|
|
|
+ { SNDRV_CHMAP_RL, 0x04, RL },
|
|
|
+ { SNDRV_CHMAP_RR, 0x05, RR },
|
|
|
+ { SNDRV_CHMAP_LFE, 0x02, LFE },
|
|
|
+ { SNDRV_CHMAP_FC, 0x03, FC },
|
|
|
+ { SNDRV_CHMAP_RLC, 0x06, RLC },
|
|
|
+ { SNDRV_CHMAP_RRC, 0x07, RRC },
|
|
|
+ {} /* terminator */
|
|
|
+};
|
|
|
+
|
|
|
+/* from ALSA API channel position to speaker bit mask */
|
|
|
+static int to_spk_mask(unsigned char c)
|
|
|
+{
|
|
|
+ struct channel_map_table *t = map_tables;
|
|
|
+ for (; t->map; t++) {
|
|
|
+ if (t->map == c)
|
|
|
+ return t->spk_mask;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* from ALSA API channel position to CEA slot */
|
|
|
+static int to_cea_slot(unsigned char c)
|
|
|
+{
|
|
|
+ struct channel_map_table *t = map_tables;
|
|
|
+ for (; t->map; t++) {
|
|
|
+ if (t->map == c)
|
|
|
+ return t->cea_slot;
|
|
|
+ }
|
|
|
+ return 0x0f;
|
|
|
+}
|
|
|
+
|
|
|
+/* from CEA slot to ALSA API channel position */
|
|
|
+static int from_cea_slot(unsigned char c)
|
|
|
+{
|
|
|
+ struct channel_map_table *t = map_tables;
|
|
|
+ for (; t->map; t++) {
|
|
|
+ if (t->cea_slot == c)
|
|
|
+ return t->map;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* from speaker bit mask to ALSA API channel position */
|
|
|
+static int spk_to_chmap(int spk)
|
|
|
+{
|
|
|
+ struct channel_map_table *t = map_tables;
|
|
|
+ for (; t->map; t++) {
|
|
|
+ if (t->spk_mask == spk)
|
|
|
+ return t->map;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* get the CA index corresponding to the given ALSA API channel map */
|
|
|
+static int hdmi_manual_channel_allocation(int chs, unsigned char *map)
|
|
|
+{
|
|
|
+ int i, spks = 0, spk_mask = 0;
|
|
|
+
|
|
|
+ for (i = 0; i < chs; i++) {
|
|
|
+ int mask = to_spk_mask(map[i]);
|
|
|
+ if (mask) {
|
|
|
+ spk_mask |= mask;
|
|
|
+ spks++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
|
|
|
+ if ((chs == channel_allocations[i].channels ||
|
|
|
+ spks == channel_allocations[i].channels) &&
|
|
|
+ (spk_mask & channel_allocations[i].spk_mask) ==
|
|
|
+ channel_allocations[i].spk_mask)
|
|
|
+ return channel_allocations[i].ca_index;
|
|
|
+ }
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+/* set up the channel slots for the given ALSA API channel map */
|
|
|
+static int hdmi_manual_setup_channel_mapping(struct hda_codec *codec,
|
|
|
+ hda_nid_t pin_nid,
|
|
|
+ int chs, unsigned char *map)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ for (i = 0; i < 8; i++) {
|
|
|
+ int val, err;
|
|
|
+ if (i < chs)
|
|
|
+ val = to_cea_slot(map[i]);
|
|
|
+ else
|
|
|
+ val = 0xf;
|
|
|
+ val |= (i << 4);
|
|
|
+ err = snd_hda_codec_write(codec, pin_nid, 0,
|
|
|
+ AC_VERB_SET_HDMI_CHAN_SLOT, val);
|
|
|
+ if (err)
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* store ALSA API channel map from the current default map */
|
|
|
+static void hdmi_setup_fake_chmap(unsigned char *map, int ca)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+ for (i = 0; i < 8; i++) {
|
|
|
+ if (i < channel_allocations[ca].channels)
|
|
|
+ map[i] = from_cea_slot((hdmi_channel_mapping[ca][i] >> 4) & 0x0f);
|
|
|
+ else
|
|
|
+ map[i] = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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)
|
|
|
+{
|
|
|
+ if (!non_pcm && map) {
|
|
|
+ hdmi_manual_setup_channel_mapping(codec, pin_nid,
|
|
|
+ channels, map);
|
|
|
+ } else {
|
|
|
+ hdmi_std_setup_channel_mapping(codec, pin_nid, non_pcm, ca);
|
|
|
+ hdmi_setup_fake_chmap(map, ca);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
/*
|
|
|
* Audio InfoFrame routines
|
|
@@ -726,7 +860,12 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, int pin_idx,
|
|
|
if (!eld->monitor_present)
|
|
|
return;
|
|
|
|
|
|
- ca = hdmi_channel_allocation(eld, channels);
|
|
|
+ if (!non_pcm && per_pin->chmap_set)
|
|
|
+ ca = hdmi_manual_channel_allocation(channels, per_pin->chmap);
|
|
|
+ else
|
|
|
+ ca = hdmi_channel_allocation(eld, channels);
|
|
|
+ if (ca < 0)
|
|
|
+ ca = 0;
|
|
|
|
|
|
memset(&ai, 0, sizeof(ai));
|
|
|
if (eld->conn_type == 0) { /* HDMI */
|
|
@@ -763,7 +902,8 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, int pin_idx,
|
|
|
"pin=%d channels=%d\n",
|
|
|
pin_nid,
|
|
|
channels);
|
|
|
- hdmi_setup_channel_mapping(codec, pin_nid, non_pcm, ca);
|
|
|
+ hdmi_setup_channel_mapping(codec, pin_nid, non_pcm, ca,
|
|
|
+ channels, per_pin->chmap);
|
|
|
hdmi_stop_infoframe_trans(codec, pin_nid);
|
|
|
hdmi_fill_audio_infoframe(codec, pin_nid,
|
|
|
ai.bytes, sizeof(ai));
|
|
@@ -772,7 +912,8 @@ static void hdmi_setup_audio_infoframe(struct hda_codec *codec, int pin_idx,
|
|
|
/* For non-pcm audio switch, setup new channel mapping
|
|
|
* accordingly */
|
|
|
if (per_pin->non_pcm != non_pcm)
|
|
|
- hdmi_setup_channel_mapping(codec, pin_nid, non_pcm, ca);
|
|
|
+ hdmi_setup_channel_mapping(codec, pin_nid, non_pcm, ca,
|
|
|
+ channels, per_pin->chmap);
|
|
|
}
|
|
|
|
|
|
per_pin->non_pcm = non_pcm;
|
|
@@ -1098,8 +1239,11 @@ static int hdmi_add_cvt(struct hda_codec *codec, hda_nid_t cvt_nid)
|
|
|
|
|
|
per_cvt->cvt_nid = cvt_nid;
|
|
|
per_cvt->channels_min = 2;
|
|
|
- if (chans <= 16)
|
|
|
+ if (chans <= 16) {
|
|
|
per_cvt->channels_max = chans;
|
|
|
+ if (chans > spec->channels_max)
|
|
|
+ spec->channels_max = chans;
|
|
|
+ }
|
|
|
|
|
|
err = snd_hda_query_supported_pcm(codec, cvt_nid,
|
|
|
&per_cvt->rates,
|
|
@@ -1250,7 +1394,10 @@ static int hdmi_pcm_close(struct hda_pcm_stream *hinfo,
|
|
|
AC_VERB_SET_PIN_WIDGET_CONTROL,
|
|
|
pinctl & ~PIN_OUT);
|
|
|
snd_hda_spdif_ctls_unassign(codec, pin_idx);
|
|
|
+ per_pin->chmap_set = false;
|
|
|
+ memset(per_pin->chmap, 0, sizeof(per_pin->chmap));
|
|
|
}
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1261,6 +1408,135 @@ static const struct hda_pcm_ops generic_ops = {
|
|
|
.cleanup = generic_hdmi_playback_pcm_cleanup,
|
|
|
};
|
|
|
|
|
|
+/*
|
|
|
+ * ALSA API channel-map control callbacks
|
|
|
+ */
|
|
|
+static int hdmi_chmap_ctl_info(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_info *uinfo)
|
|
|
+{
|
|
|
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
|
+ struct hda_codec *codec = info->private_data;
|
|
|
+ struct hdmi_spec *spec = codec->spec;
|
|
|
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
|
+ uinfo->count = spec->channels_max;
|
|
|
+ uinfo->value.integer.min = 0;
|
|
|
+ uinfo->value.integer.max = SNDRV_CHMAP_LAST;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
|
|
|
+ unsigned int size, unsigned int __user *tlv)
|
|
|
+{
|
|
|
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
|
+ struct hda_codec *codec = info->private_data;
|
|
|
+ struct hdmi_spec *spec = codec->spec;
|
|
|
+ const unsigned int valid_mask =
|
|
|
+ FL | FR | RL | RR | LFE | FC | RLC | RRC;
|
|
|
+ unsigned int __user *dst;
|
|
|
+ int chs, count = 0;
|
|
|
+
|
|
|
+ if (size < 8)
|
|
|
+ return -ENOMEM;
|
|
|
+ if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
|
|
|
+ return -EFAULT;
|
|
|
+ size -= 8;
|
|
|
+ dst = tlv + 2;
|
|
|
+ for (chs = 2; chs <= spec->channels_max; chs++) {
|
|
|
+ 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)
|
|
|
+ continue;
|
|
|
+ if (cap->spk_mask & ~valid_mask)
|
|
|
+ continue;
|
|
|
+ if (size < 8)
|
|
|
+ return -ENOMEM;
|
|
|
+ if (put_user(SNDRV_CTL_TLVT_CHMAP_VAR, dst) ||
|
|
|
+ put_user(chs_bytes, dst + 1))
|
|
|
+ return -EFAULT;
|
|
|
+ dst += 2;
|
|
|
+ size -= 8;
|
|
|
+ count += 8;
|
|
|
+ if (size < chs_bytes)
|
|
|
+ return -ENOMEM;
|
|
|
+ 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))
|
|
|
+ return -EFAULT;
|
|
|
+ dst++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (put_user(count, tlv + 1))
|
|
|
+ return -EFAULT;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int hdmi_chmap_ctl_get(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
+{
|
|
|
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
|
+ struct hda_codec *codec = info->private_data;
|
|
|
+ struct hdmi_spec *spec = codec->spec;
|
|
|
+ int pin_idx = kcontrol->private_value;
|
|
|
+ struct hdmi_spec_per_pin *per_pin = &spec->pins[pin_idx];
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(per_pin->chmap); i++)
|
|
|
+ ucontrol->value.integer.value[i] = per_pin->chmap[i];
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
+{
|
|
|
+ struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
|
|
|
+ struct hda_codec *codec = info->private_data;
|
|
|
+ struct hdmi_spec *spec = codec->spec;
|
|
|
+ int pin_idx = kcontrol->private_value;
|
|
|
+ struct hdmi_spec_per_pin *per_pin = &spec->pins[pin_idx];
|
|
|
+ unsigned int ctl_idx;
|
|
|
+ struct snd_pcm_substream *substream;
|
|
|
+ unsigned char chmap[8];
|
|
|
+ int i, ca, prepared = 0;
|
|
|
+
|
|
|
+ ctl_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
|
|
|
+ substream = snd_pcm_chmap_substream(info, ctl_idx);
|
|
|
+ if (!substream || !substream->runtime)
|
|
|
+ return -EBADFD;
|
|
|
+ switch (substream->runtime->status->state) {
|
|
|
+ case SNDRV_PCM_STATE_OPEN:
|
|
|
+ case SNDRV_PCM_STATE_SETUP:
|
|
|
+ break;
|
|
|
+ case SNDRV_PCM_STATE_PREPARED:
|
|
|
+ prepared = 1;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+ memset(chmap, 0, sizeof(chmap));
|
|
|
+ for (i = 0; i < ARRAY_SIZE(chmap); i++)
|
|
|
+ chmap[i] = ucontrol->value.integer.value[i];
|
|
|
+ if (!memcmp(chmap, per_pin->chmap, sizeof(chmap)))
|
|
|
+ return 0;
|
|
|
+ ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap);
|
|
|
+ if (ca < 0)
|
|
|
+ return -EINVAL;
|
|
|
+ per_pin->chmap_set = true;
|
|
|
+ memcpy(per_pin->chmap, chmap, sizeof(chmap));
|
|
|
+ if (prepared)
|
|
|
+ hdmi_setup_audio_infoframe(codec, pin_idx, per_pin->non_pcm,
|
|
|
+ substream);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int generic_hdmi_build_pcms(struct hda_codec *codec)
|
|
|
{
|
|
|
struct hdmi_spec *spec = codec->spec;
|
|
@@ -1273,6 +1549,7 @@ static int generic_hdmi_build_pcms(struct hda_codec *codec)
|
|
|
info = &spec->pcm_rec[pin_idx];
|
|
|
info->name = get_hdmi_pcm_name(pin_idx);
|
|
|
info->pcm_type = HDA_PCM_TYPE_HDMI;
|
|
|
+ info->own_chmap = true;
|
|
|
|
|
|
pstr = &info->stream[SNDRV_PCM_STREAM_PLAYBACK];
|
|
|
pstr->substreams = 1;
|
|
@@ -1330,6 +1607,27 @@ static int generic_hdmi_build_controls(struct hda_codec *codec)
|
|
|
hdmi_present_sense(per_pin, 0);
|
|
|
}
|
|
|
|
|
|
+ /* add channel maps */
|
|
|
+ for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
|
|
|
+ struct snd_pcm_chmap *chmap;
|
|
|
+ struct snd_kcontrol *kctl;
|
|
|
+ int i;
|
|
|
+ err = snd_pcm_add_chmap_ctls(codec->pcm_info[pin_idx].pcm,
|
|
|
+ SNDRV_PCM_STREAM_PLAYBACK,
|
|
|
+ NULL, 0, pin_idx, &chmap);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
+ /* override handlers */
|
|
|
+ chmap->private_data = codec;
|
|
|
+ kctl = chmap->kctl;
|
|
|
+ for (i = 0; i < kctl->count; i++)
|
|
|
+ kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
|
|
|
+ kctl->info = hdmi_chmap_ctl_info;
|
|
|
+ kctl->get = hdmi_chmap_ctl_get;
|
|
|
+ kctl->put = hdmi_chmap_ctl_put;
|
|
|
+ kctl->tlv.c = hdmi_chmap_ctl_tlv;
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1857,6 +2155,43 @@ static int patch_nvhdmi_2ch(struct hda_codec *codec)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int nvhdmi_7x_8ch_build_pcms(struct hda_codec *codec)
|
|
|
+{
|
|
|
+ struct hdmi_spec *spec = codec->spec;
|
|
|
+ int err = simple_playback_build_pcms(codec);
|
|
|
+ spec->pcm_rec[0].own_chmap = true;
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int nvhdmi_7x_8ch_build_controls(struct hda_codec *codec)
|
|
|
+{
|
|
|
+ struct hdmi_spec *spec = codec->spec;
|
|
|
+ struct snd_pcm_chmap *chmap;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ err = simple_playback_build_controls(codec);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
+
|
|
|
+ /* add channel maps */
|
|
|
+ err = snd_pcm_add_chmap_ctls(spec->pcm_rec[0].pcm,
|
|
|
+ SNDRV_PCM_STREAM_PLAYBACK,
|
|
|
+ snd_pcm_alt_chmaps, 8, 0, &chmap);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
+ switch (codec->preset->id) {
|
|
|
+ case 0x10de0002:
|
|
|
+ case 0x10de0003:
|
|
|
+ case 0x10de0005:
|
|
|
+ case 0x10de0006:
|
|
|
+ chmap->channel_mask = (1U << 2) | (1U << 8);
|
|
|
+ break;
|
|
|
+ case 0x10de0007:
|
|
|
+ chmap->channel_mask = (1U << 2) | (1U << 6) | (1U << 8);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int patch_nvhdmi_8ch_7x(struct hda_codec *codec)
|
|
|
{
|
|
|
struct hdmi_spec *spec;
|
|
@@ -1867,6 +2202,8 @@ static int patch_nvhdmi_8ch_7x(struct hda_codec *codec)
|
|
|
spec->multiout.max_channels = 8;
|
|
|
spec->pcm_playback = nvhdmi_pcm_playback_8ch_7x;
|
|
|
codec->patch_ops.init = nvhdmi_7x_init_8ch;
|
|
|
+ codec->patch_ops.build_pcms = nvhdmi_7x_8ch_build_pcms;
|
|
|
+ codec->patch_ops.build_controls = nvhdmi_7x_8ch_build_controls;
|
|
|
|
|
|
/* Initialize the audio infoframe channel mask and checksum to something
|
|
|
* valid */
|