Merge branch 'topic/tlv-chmap' into for-next

This is a merge of a topic branch containing the support for the new
channel map API using control elements.
diff --git a/Documentation/sound/alsa/Channel-Mapping-API.txt b/Documentation/sound/alsa/Channel-Mapping-API.txt
new file mode 100644
index 0000000..3c43d1a
--- /dev/null
+++ b/Documentation/sound/alsa/Channel-Mapping-API.txt
@@ -0,0 +1,153 @@
+ALSA PCM channel-mapping API
+============================
+					Takashi Iwai <tiwai@suse.de>
+
+GENERAL
+-------
+
+The channel mapping API allows user to query the possible channel maps
+and the current channel map, also optionally to modify the channel map
+of the current stream.
+
+A channel map is an array of position for each PCM channel.
+Typically, a stereo PCM stream has a channel map of
+  { front_left, front_right }
+while a 4.0 surround PCM stream has a channel map of
+  { front left, front right, rear left, rear right }.
+
+The problem, so far, was that we had no standard channel map
+explicitly, and applications had no way to know which channel
+corresponds to which (speaker) position.  Thus, applications applied
+wrong channels for 5.1 outputs, and you hear suddenly strange sound
+from rear.  Or, some devices secretly assume that center/LFE is the
+third/fourth channels while others that C/LFE as 5th/6th channels.
+
+Also, some devices such as HDMI are configurable for different speaker
+positions even with the same number of total channels.  However, there
+was no way to specify this because of lack of channel map
+specification.  These are the main motivations for the new channel
+mapping API.
+
+
+DESIGN
+------
+
+Actually, "the channel mapping API" doesn't introduce anything new in
+the kernel/user-space ABI perspective.  It uses only the existing
+control element features.
+
+As a ground design, each PCM substream may contain a control element
+providing the channel mapping information and configuration.  This
+element is specified by:
+	iface = SNDRV_CTL_ELEM_IFACE_PCM
+	name = "Playback Channel Map" or "Capture Channel Map"
+	device = the same device number for the assigned PCM substream
+	index = the same index number for the assigned PCM substream
+
+Note the name is different depending on the PCM substream direction.
+
+Each control element provides at least the TLV read operation and the
+read operation.  Optionally, the write operation can be provided to
+allow user to change the channel map dynamically.
+
+* TLV
+
+The TLV operation gives the list of available channel
+maps.  A list item of a channel map is usually a TLV of
+	type data-bytes ch0 ch1 ch2...
+where type is the TLV type value, the second argument is the total
+bytes (not the numbers) of channel values, and the rest are the
+position value for each channel.
+
+As a TLV type, either SNDRV_CTL_TLVT_CHMAP_FIXED,
+SNDRV_CTL_TLV_CHMAP_VAR or SNDRV_CTL_TLVT_CHMAP_PAIRED can be used.
+The _FIXED type is for a channel map with the fixed channel position
+while the latter two are for flexible channel positions.  _VAR type is
+for a channel map where all channels are freely swappable and _PAIRED
+type is where pair-wise channels are swappable.  For example, when you
+have {FL/FR/RL/RR} channel map, _PAIRED type would allow you to swap
+only {RL/RR/FL/FR} while _VAR type would allow even swapping FL and
+RR.
+
+These new TLV types are defined in sound/tlv.h.
+
+The available channel position values are defined in sound/asound.h,
+here is a cut:
+
+/* channel positions */
+enum {
+	SNDRV_CHMAP_UNKNOWN = 0,
+	SNDRV_CHMAP_NA,		/* N/A, silent */
+	SNDRV_CHMAP_MONO,	/* mono stream */
+	/* this follows the alsa-lib mixer channel value + 3 */
+	SNDRV_CHMAP_FL,		/* front left */
+	SNDRV_CHMAP_FR,		/* front right */
+	SNDRV_CHMAP_RL,		/* rear left */
+	SNDRV_CHMAP_RR,		/* rear right */
+	SNDRV_CHMAP_FC,		/* front center */
+	SNDRV_CHMAP_LFE,	/* LFE */
+	SNDRV_CHMAP_SL,		/* side left */
+	SNDRV_CHMAP_SR,		/* side right */
+	SNDRV_CHMAP_RC,		/* rear center */
+	/* new definitions */
+	SNDRV_CHMAP_FLC,	/* front left center */
+	SNDRV_CHMAP_FRC,	/* front right center */
+	SNDRV_CHMAP_RLC,	/* rear left center */
+	SNDRV_CHMAP_RRC,	/* rear right center */
+	SNDRV_CHMAP_FLW,	/* front left wide */
+	SNDRV_CHMAP_FRW,	/* front right wide */
+	SNDRV_CHMAP_FLH,	/* front left high */
+	SNDRV_CHMAP_FCH,	/* front center high */
+	SNDRV_CHMAP_FRH,	/* front right high */
+	SNDRV_CHMAP_TC,		/* top center */
+	SNDRV_CHMAP_TFL,	/* top front left */
+	SNDRV_CHMAP_TFR,	/* top front right */
+	SNDRV_CHMAP_TFC,	/* top front center */
+	SNDRV_CHMAP_TRL,	/* top rear left */
+	SNDRV_CHMAP_TRR,	/* top rear right */
+	SNDRV_CHMAP_TRC,	/* top rear center */
+	SNDRV_CHMAP_LAST = SNDRV_CHMAP_TRC,
+};
+
+When a PCM stream can provide more than one channel map, you can
+provide multiple channel maps in a TLV container type.  The TLV data
+to be returned will contain such as:
+	SNDRV_CTL_TLVT_CONTAINER 96
+	    SNDRV_CTL_TLVT_CHMAP_FIXED 4 SNDRV_CHMAP_FC
+	    SNDRV_CTL_TLVT_CHMAP_FIXED 8 SNDRV_CHMAP_FL SNDRV_CHMAP_FR
+	    SNDRV_CTL_TLVT_CHMAP_FIXED 16 NDRV_CHMAP_FL SNDRV_CHMAP_FR \
+		SNDRV_CHMAP_RL SNDRV_CHMAP_RR
+
+The channel position is provided in LSB 16bits.  The upper bits are
+used for bit flags.
+
+#define SNDRV_CHMAP_POSITION_MASK	0xffff
+#define SNDRV_CHMAP_PHASE_INVERSE	(0x01 << 16)
+#define SNDRV_CHMAP_DRIVER_SPEC		(0x02 << 16)
+
+SNDRV_CHMAP_PHASE_INVERSE indicates the channel is phase inverted,
+(thus summing left and right channels would result in almost silence).
+Some digital mic devices have this.
+
+When SNDRV_CHMAP_DRIVER_SPEC is set, all the channel position values
+don't follow the standard definition above but driver-specific.
+
+* READ OPERATION
+
+The control read operation is for providing the current channel map of
+the given stream.  The control element returns an integer array
+containing the position of each channel.
+
+When this is performed before the number of the channel is specified
+(i.e. hw_params is set), it should return all channels set to
+UNKNOWN.
+
+* WRITE OPERATION
+
+The control write operation is optional, and only for devices that can
+change the channel configuration on the fly, such as HDMI.  User needs
+to pass an integer value containing the valid channel positions for
+all channels of the assigned PCM substream.
+
+This operation is allowed only at PCM PREPARED state.  When called in
+other states, it shall return an error.
diff --git a/include/sound/ac97_codec.h b/include/sound/ac97_codec.h
index 02cbb50..4458b87 100644
--- a/include/sound/ac97_codec.h
+++ b/include/sound/ac97_codec.h
@@ -422,6 +422,7 @@
  */
 
 struct snd_ac97;
+struct snd_pcm_chmap;
 
 struct snd_ac97_build_ops {
 	int (*build_3d) (struct snd_ac97 *ac97);
@@ -528,6 +529,8 @@
 	struct delayed_work power_work;
 #endif
 	struct device dev;
+
+	struct snd_pcm_chmap *chmaps[2]; /* channel-maps (optional) */
 };
 
 #define to_ac97_t(d) container_of(d, struct snd_ac97, dev)
diff --git a/include/sound/asound.h b/include/sound/asound.h
index 0876a1e..dfe7d44 100644
--- a/include/sound/asound.h
+++ b/include/sound/asound.h
@@ -472,6 +472,45 @@
 	SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC,
 };
 
+/* channel positions */
+enum {
+	SNDRV_CHMAP_UNKNOWN = 0,
+	SNDRV_CHMAP_NA,		/* N/A, silent */
+	SNDRV_CHMAP_MONO,	/* mono stream */
+	/* this follows the alsa-lib mixer channel value + 3 */
+	SNDRV_CHMAP_FL,		/* front left */
+	SNDRV_CHMAP_FR,		/* front right */
+	SNDRV_CHMAP_RL,		/* rear left */
+	SNDRV_CHMAP_RR,		/* rear right */
+	SNDRV_CHMAP_FC,		/* front center */
+	SNDRV_CHMAP_LFE,	/* LFE */
+	SNDRV_CHMAP_SL,		/* side left */
+	SNDRV_CHMAP_SR,		/* side right */
+	SNDRV_CHMAP_RC,		/* rear center */
+	/* new definitions */
+	SNDRV_CHMAP_FLC,	/* front left center */
+	SNDRV_CHMAP_FRC,	/* front right center */
+	SNDRV_CHMAP_RLC,	/* rear left center */
+	SNDRV_CHMAP_RRC,	/* rear right center */
+	SNDRV_CHMAP_FLW,	/* front left wide */
+	SNDRV_CHMAP_FRW,	/* front right wide */
+	SNDRV_CHMAP_FLH,	/* front left high */
+	SNDRV_CHMAP_FCH,	/* front center high */
+	SNDRV_CHMAP_FRH,	/* front right high */
+	SNDRV_CHMAP_TC,		/* top center */
+	SNDRV_CHMAP_TFL,	/* top front left */
+	SNDRV_CHMAP_TFR,	/* top front right */
+	SNDRV_CHMAP_TFC,	/* top front center */
+	SNDRV_CHMAP_TRL,	/* top rear left */
+	SNDRV_CHMAP_TRR,	/* top rear right */
+	SNDRV_CHMAP_TRC,	/* top rear center */
+	SNDRV_CHMAP_LAST = SNDRV_CHMAP_TRC,
+};
+
+#define SNDRV_CHMAP_POSITION_MASK	0xffff
+#define SNDRV_CHMAP_PHASE_INVERSE	(0x01 << 16)
+#define SNDRV_CHMAP_DRIVER_SPEC		(0x02 << 16)
+
 #define SNDRV_PCM_IOCTL_PVERSION	_IOR('A', 0x00, int)
 #define SNDRV_PCM_IOCTL_INFO		_IOR('A', 0x01, struct snd_pcm_info)
 #define SNDRV_PCM_IOCTL_TSTAMP		_IOW('A', 0x02, int)
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index cdca2ab..669c85a 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -437,6 +437,7 @@
 	struct snd_info_entry *proc_xrun_debug_entry;
 #endif
 #endif
+	struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
 };
 
 struct snd_pcm {
@@ -1086,4 +1087,51 @@
 		return "Capture";
 }
 
+/*
+ * PCM channel-mapping control API
+ */
+/* array element of channel maps */
+struct snd_pcm_chmap_elem {
+	unsigned char channels;
+	unsigned char map[15];
+};
+
+/* channel map information; retrieved via snd_kcontrol_chip() */
+struct snd_pcm_chmap {
+	struct snd_pcm *pcm;	/* assigned PCM instance */
+	int stream;		/* PLAYBACK or CAPTURE */
+	struct snd_kcontrol *kctl;
+	const struct snd_pcm_chmap_elem *chmap;
+	unsigned int max_channels;
+	unsigned int channel_mask;	/* optional: active channels bitmask */
+	void *private_data;	/* optional: private data pointer */
+};
+
+/* get the PCM substream assigned to the given chmap info */
+static inline struct snd_pcm_substream *
+snd_pcm_chmap_substream(struct snd_pcm_chmap *info, unsigned int idx)
+{
+	struct snd_pcm_substream *s;
+	for (s = info->pcm->streams[info->stream].substream; s; s = s->next)
+		if (s->number == idx)
+			return s;
+	return NULL;
+}
+
+/* ALSA-standard channel maps (RL/RR prior to C/LFE) */
+extern const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[];
+/* Other world's standard channel maps (C/LFE prior to RL/RR) */
+extern const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[];
+
+/* bit masks to be passed to snd_pcm_chmap.channel_mask field */
+#define SND_PCM_CHMAP_MASK_24	((1U << 2) | (1U << 4))
+#define SND_PCM_CHMAP_MASK_246	(SND_PCM_CHMAP_MASK_24 | (1U << 6))
+#define SND_PCM_CHMAP_MASK_2468	(SND_PCM_CHMAP_MASK_246 | (1U << 8))
+
+int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
+			   const struct snd_pcm_chmap_elem *chmap,
+			   int max_channels,
+			   unsigned long private_value,
+			   struct snd_pcm_chmap **info_ret);
+
 #endif /* __SOUND_PCM_H */
diff --git a/include/sound/tlv.h b/include/sound/tlv.h
index a64d8fe..28c65e1 100644
--- a/include/sound/tlv.h
+++ b/include/sound/tlv.h
@@ -86,4 +86,12 @@
 
 #define TLV_DB_GAIN_MUTE	-9999999
 
+/*
+ * channel-mapping TLV items
+ *  TLV length must match with num_channels
+ */
+#define SNDRV_CTL_TLVT_CHMAP_FIXED	0x101	/* fixed channel position */
+#define SNDRV_CTL_TLVT_CHMAP_VAR	0x102	/* channels freely swappable */
+#define SNDRV_CTL_TLVT_CHMAP_PAIRED	0x103	/* pair-wise swappable */
+
 #endif /* __SOUND_TLV_H */
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 1a3070b..f299194 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -1105,6 +1105,10 @@
 			break;
 		}
 		snd_unregister_device(devtype, pcm->card, pcm->device);
+		if (pcm->streams[cidx].chmap_kctl) {
+			snd_ctl_remove(pcm->card, pcm->streams[cidx].chmap_kctl);
+			pcm->streams[cidx].chmap_kctl = NULL;
+		}
 	}
  unlock:
 	mutex_unlock(&register_mutex);
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 7ae6719..f42c10a 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -26,6 +26,7 @@
 #include <linux/export.h>
 #include <sound/core.h>
 #include <sound/control.h>
+#include <sound/tlv.h>
 #include <sound/info.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -2302,3 +2303,216 @@
 }
 
 EXPORT_SYMBOL(snd_pcm_lib_readv);
+
+/*
+ * standard channel mapping helpers
+ */
+
+/* default channel maps for multi-channel playbacks, up to 8 channels */
+const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = {
+	{ .channels = 1,
+	  .map = { SNDRV_CHMAP_MONO } },
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+	{ .channels = 4,
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ .channels = 6,
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
+	{ .channels = 8,
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
+	{ }
+};
+EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps);
+
+/* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */
+const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = {
+	{ .channels = 1,
+	  .map = { SNDRV_CHMAP_MONO } },
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+	{ .channels = 4,
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ .channels = 6,
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ .channels = 8,
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
+	{ }
+};
+EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps);
+
+static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch)
+{
+	if (ch > info->max_channels)
+		return false;
+	return !info->channel_mask || (info->channel_mask & (1U << ch));
+}
+
+static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 0;
+	uinfo->count = info->max_channels;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = SNDRV_CHMAP_LAST;
+	return 0;
+}
+
+/* get callback for channel map ctl element
+ * stores the channel position firstly matching with the current channels
+ */
+static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+	unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+	struct snd_pcm_substream *substream;
+	const struct snd_pcm_chmap_elem *map;
+
+	if (snd_BUG_ON(!info->chmap))
+		return -EINVAL;
+	substream = snd_pcm_chmap_substream(info, idx);
+	if (!substream)
+		return -ENODEV;
+	memset(ucontrol->value.integer.value, 0,
+	       sizeof(ucontrol->value.integer.value));
+	if (!substream->runtime)
+		return 0; /* no channels set */
+	for (map = info->chmap; map->channels; map++) {
+		int i;
+		if (map->channels == substream->runtime->channels &&
+		    valid_chmap_channels(info, map->channels)) {
+			for (i = 0; i < map->channels; i++)
+				ucontrol->value.integer.value[i] = map->map[i];
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+/* tlv callback for channel map ctl element
+ * expands the pre-defined channel maps in a form of TLV
+ */
+static int pcm_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);
+	const struct snd_pcm_chmap_elem *map;
+	unsigned int __user *dst;
+	int c, count = 0;
+
+	if (snd_BUG_ON(!info->chmap))
+		return -EINVAL;
+	if (size < 8)
+		return -ENOMEM;
+	if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
+		return -EFAULT;
+	size -= 8;
+	dst = tlv + 2;
+	for (map = info->chmap; map->channels; map++) {
+		int chs_bytes = map->channels * 4;
+		if (!valid_chmap_channels(info, map->channels))
+			continue;
+		if (size < 8)
+			return -ENOMEM;
+		if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, 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 = 0; c < map->channels; c++) {
+			if (put_user(map->map[c], dst))
+				return -EFAULT;
+			dst++;
+		}
+	}
+	if (put_user(count, tlv + 1))
+		return -EFAULT;
+	return 0;
+}
+
+static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+	info->pcm->streams[info->stream].chmap_kctl = NULL;
+	kfree(info);
+}
+
+/**
+ * snd_pcm_add_chmap_ctls - create channel-mapping control elements
+ * @pcm: the assigned PCM instance
+ * @stream: stream direction
+ * @chmap: channel map elements (for query)
+ * @max_channels: the max number of channels for the stream
+ * @private_value: the value passed to each kcontrol's private_value field
+ * @info_ret: store struct snd_pcm_chmap instance if non-NULL
+ *
+ * Create channel-mapping control elements assigned to the given PCM stream(s).
+ * Returns zero if succeed, or a negative error value.
+ */
+int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
+			   const struct snd_pcm_chmap_elem *chmap,
+			   int max_channels,
+			   unsigned long private_value,
+			   struct snd_pcm_chmap **info_ret)
+{
+	struct snd_pcm_chmap *info;
+	struct snd_kcontrol_new knew = {
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+			SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
+		.info = pcm_chmap_ctl_info,
+		.get = pcm_chmap_ctl_get,
+		.tlv.c = pcm_chmap_ctl_tlv,
+	};
+	int err;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+	info->pcm = pcm;
+	info->stream = stream;
+	info->chmap = chmap;
+	info->max_channels = max_channels;
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		knew.name = "Playback Channel Map";
+	else
+		knew.name = "Capture Channel Map";
+	knew.device = pcm->device;
+	knew.count = pcm->streams[stream].substream_count;
+	knew.private_value = private_value;
+	info->kctl = snd_ctl_new1(&knew, info);
+	if (!info->kctl) {
+		kfree(info);
+		return -ENOMEM;
+	}
+	info->kctl->private_free = pcm_chmap_ctl_private_free;
+	err = snd_ctl_add(pcm->card, info->kctl);
+	if (err < 0)
+		return err;
+	pcm->streams[stream].chmap_kctl = info->kctl;
+	if (info_ret)
+		*info_ret = info;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls);
diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c
index a872d0a..66a3bc9 100644
--- a/sound/pci/ac97/ac97_patch.c
+++ b/sound/pci/ac97/ac97_patch.c
@@ -2595,6 +2595,21 @@
 			     shared ? 0 : 0x100);
 }
 
+static int alc650_swap_surround_put(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_ac97 *ac97 = snd_kcontrol_chip(kcontrol);
+	struct snd_pcm_chmap *map = ac97->chmaps[SNDRV_PCM_STREAM_PLAYBACK];
+
+	if (map) {
+		if (ucontrol->value.integer.value[0])
+			map->chmap = snd_pcm_std_chmaps;
+		else
+			map->chmap = snd_pcm_alt_chmaps;
+	}
+	return snd_ac97_put_volsw(kcontrol, ucontrol);
+}
+
 static const struct snd_kcontrol_new snd_ac97_controls_alc650[] = {
 	AC97_SINGLE("Duplicate Front", AC97_ALC650_MULTICH, 0, 1, 0),
 	AC97_SINGLE("Surround Down Mix", AC97_ALC650_MULTICH, 1, 1, 0),
@@ -2608,7 +2623,14 @@
 	/* 9: Line-In/Surround share */
 	/* 10: Mic/CLFE share */
 	/* 11-13: in IEC958 controls */
-	AC97_SINGLE("Swap Surround Slot", AC97_ALC650_MULTICH, 14, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Swap Surround Slot",
+		.info = snd_ac97_info_volsw,
+		.get = snd_ac97_get_volsw,
+		.put = alc650_swap_surround_put,
+		.private_value =  AC97_SINGLE_VALUE(AC97_ALC650_MULTICH, 14, 1, 0),
+	},
 #if 0 /* always set in patch_alc650 */
 	AC97_SINGLE("IEC958 Input Clock Enable", AC97_ALC650_CLOCK, 0, 1, 0),
 	AC97_SINGLE("IEC958 Input Pin Enable", AC97_ALC650_CLOCK, 1, 1, 0),
diff --git a/sound/pci/atiixp.c b/sound/pci/atiixp.c
index c744df5..368df8b 100644
--- a/sound/pci/atiixp.c
+++ b/sound/pci/atiixp.c
@@ -1250,6 +1250,7 @@
 static int __devinit snd_atiixp_pcm_new(struct atiixp *chip)
 {
 	struct snd_pcm *pcm;
+	struct snd_pcm_chmap *chmap;
 	struct snd_ac97_bus *pbus = chip->ac97_bus;
 	int err, i, num_pcms;
 
@@ -1293,6 +1294,14 @@
 					      snd_dma_pci_data(chip->pci),
 					      64*1024, 128*1024);
 
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_alt_chmaps, chip->max_channels, 0,
+				     &chmap);
+	if (err < 0)
+		return err;
+	chmap->channel_mask = SND_PCM_CHMAP_MASK_2468;
+	chip->ac97[0]->chmaps[SNDRV_PCM_STREAM_PLAYBACK] = chmap;
+
 	/* no SPDIF support on codec? */
 	if (chip->pcms[ATI_PCM_SPDIF] && ! chip->pcms[ATI_PCM_SPDIF]->rates)
 		return 0;
diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c
index fc67876..65c5591 100644
--- a/sound/pci/ca0106/ca0106_main.c
+++ b/sound/pci/ca0106/ca0106_main.c
@@ -1334,10 +1334,29 @@
 	return IRQ_HANDLED;
 }
 
+static const struct snd_pcm_chmap_elem surround_map[] = {
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ }
+};
+
+static const struct snd_pcm_chmap_elem clfe_map[] = {
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
+	{ }
+};
+
+static const struct snd_pcm_chmap_elem side_map[] = {
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
+	{ }
+};
+
 static int __devinit snd_ca0106_pcm(struct snd_ca0106 *emu, int device)
 {
 	struct snd_pcm *pcm;
 	struct snd_pcm_substream *substream;
+	const struct snd_pcm_chmap_elem *map = NULL;
 	int err;
   
 	err = snd_pcm_new(emu->card, "ca0106", device, 1, 1, &pcm);
@@ -1350,18 +1369,22 @@
 	case 0:
 	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_front_ops);
 	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_0_ops);
+	  map = snd_pcm_std_chmaps;
           break;
 	case 1:
 	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_rear_ops);
 	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_1_ops);
+	  map = surround_map;
           break;
 	case 2:
 	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_center_lfe_ops);
 	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_2_ops);
+	  map = clfe_map;
           break;
 	case 3:
 	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ca0106_playback_unknown_ops);
 	  snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ca0106_capture_3_ops);
+	  map = side_map;
           break;
         }
 
@@ -1388,6 +1411,11 @@
 			return err;
 	}
   
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, 2,
+				     1 << 2, NULL);
+	if (err < 0)
+		return err;
+
 	emu->pcm[device] = pcm;
   
 	return 0;
diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c
index 7c25906..22122ff 100644
--- a/sound/pci/cmipci.c
+++ b/sound/pci/cmipci.c
@@ -1962,6 +1962,12 @@
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
 					      snd_dma_pci_data(cm->pci), 64*1024, 128*1024);
 
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_alt_chmaps, cm->max_channels, 0,
+				     NULL);
+	if (err < 0)
+		return err;
+
 	return 0;
 }
 
diff --git a/sound/pci/ctxfi/ctpcm.c b/sound/pci/ctxfi/ctpcm.c
index d021876..e8a4feb 100644
--- a/sound/pci/ctxfi/ctpcm.c
+++ b/sound/pci/ctxfi/ctpcm.c
@@ -395,12 +395,38 @@
 	.page		= snd_pcm_sgbuf_ops_page,
 };
 
+static const struct snd_pcm_chmap_elem surround_map[] = {
+	{ .channels = 1,
+	  .map = { SNDRV_CHMAP_MONO } },
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ }
+};
+
+static const struct snd_pcm_chmap_elem clfe_map[] = {
+	{ .channels = 1,
+	  .map = { SNDRV_CHMAP_MONO } },
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
+	{ }
+};
+
+static const struct snd_pcm_chmap_elem side_map[] = {
+	{ .channels = 1,
+	  .map = { SNDRV_CHMAP_MONO } },
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
+	{ }
+};
+
 /* Create ALSA pcm device */
 int ct_alsa_pcm_create(struct ct_atc *atc,
 		       enum CTALSADEVS device,
 		       const char *device_name)
 {
 	struct snd_pcm *pcm;
+	const struct snd_pcm_chmap_elem *map;
+	int chs;
 	int err;
 	int playback_count, capture_count;
 
@@ -427,6 +453,30 @@
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
 			snd_dma_pci_data(atc->pci), 128*1024, 128*1024);
 
+	chs = 2;
+	switch (device) {
+	case FRONT:
+		chs = 8;
+		map = snd_pcm_std_chmaps;
+		break;
+	case SURROUND:
+		map = surround_map;
+		break;
+	case CLFE:
+		map = clfe_map;
+		break;
+	case SIDE:
+		map = side_map;
+		break;
+	default:
+		map = snd_pcm_std_chmaps;
+		break;
+	}
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, chs,
+				     0, NULL);
+	if (err < 0)
+		return err;
+
 #ifdef CONFIG_PM_SLEEP
 	atc->pcms[device] = pcm;
 #endif
diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c
index 5c8978b..556fd6f 100644
--- a/sound/pci/emu10k1/emu10k1x.c
+++ b/sound/pci/emu10k1/emu10k1x.c
@@ -830,9 +830,22 @@
 	return IRQ_HANDLED;
 }
 
+static const struct snd_pcm_chmap_elem surround_map[] = {
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ }
+};
+
+static const struct snd_pcm_chmap_elem clfe_map[] = {
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
+	{ }
+};
+
 static int __devinit snd_emu10k1x_pcm(struct emu10k1x *emu, int device, struct snd_pcm **rpcm)
 {
 	struct snd_pcm *pcm;
+	const struct snd_pcm_chmap_elem *map = NULL;
 	int err;
 	int capture = 0;
   
@@ -861,12 +874,15 @@
 	switch(device) {
 	case 0:
 		strcpy(pcm->name, "EMU10K1X Front");
+		map = snd_pcm_std_chmaps;
 		break;
 	case 1:
 		strcpy(pcm->name, "EMU10K1X Rear");
+		map = surround_map;
 		break;
 	case 2:
 		strcpy(pcm->name, "EMU10K1X Center/LFE");
+		map = clfe_map;
 		break;
 	}
 	emu->pcm = pcm;
@@ -875,6 +891,11 @@
 					      snd_dma_pci_data(emu->pci), 
 					      32*1024, 32*1024);
   
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, 2,
+				     1 << 2, NULL);
+	if (err < 0)
+		return err;
+
 	if (rpcm)
 		*rpcm = pcm;
   
diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c
index 2ba58d3..5674cc3 100644
--- a/sound/pci/ens1370.c
+++ b/sound/pci/ens1370.c
@@ -55,8 +55,10 @@
 
 #ifdef CHIP1370
 #define DRIVER_NAME "ENS1370"
+#define CHIP_NAME "ES1370" /* it can be ENS but just to keep compatibility... */
 #else
 #define DRIVER_NAME "ENS1371"
+#define CHIP_NAME "ES1371"
 #endif
 
 
@@ -1258,6 +1260,14 @@
 	.pointer =	snd_ensoniq_capture_pointer,
 };
 
+static const struct snd_pcm_chmap_elem surround_map[] = {
+	{ .channels = 1,
+	  .map = { SNDRV_CHMAP_MONO } },
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ }
+};
+
 static int __devinit snd_ensoniq_pcm(struct ensoniq * ensoniq, int device,
 				     struct snd_pcm ** rpcm)
 {
@@ -1266,11 +1276,7 @@
 
 	if (rpcm)
 		*rpcm = NULL;
-#ifdef CHIP1370
-	err = snd_pcm_new(ensoniq->card, "ES1370/1", device, 1, 1, &pcm);
-#else
-	err = snd_pcm_new(ensoniq->card, "ES1371/1", device, 1, 1, &pcm);
-#endif
+	err = snd_pcm_new(ensoniq->card, CHIP_NAME "/1", device, 1, 1, &pcm);
 	if (err < 0)
 		return err;
 
@@ -1283,16 +1289,22 @@
 
 	pcm->private_data = ensoniq;
 	pcm->info_flags = 0;
-#ifdef CHIP1370
-	strcpy(pcm->name, "ES1370 DAC2/ADC");
-#else
-	strcpy(pcm->name, "ES1371 DAC2/ADC");
-#endif
+	strcpy(pcm->name, CHIP_NAME " DAC2/ADC");
 	ensoniq->pcm1 = pcm;
 
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
 					      snd_dma_pci_data(ensoniq->pci), 64*1024, 128*1024);
 
+#ifdef CHIP1370
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     surround_map, 2, 0, NULL);
+#else
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_std_chmaps, 2, 0, NULL);
+#endif
+	if (err < 0)
+		return err;
+
 	if (rpcm)
 		*rpcm = pcm;
 	return 0;
@@ -1306,11 +1318,7 @@
 
 	if (rpcm)
 		*rpcm = NULL;
-#ifdef CHIP1370
-	err = snd_pcm_new(ensoniq->card, "ES1370/2", device, 1, 0, &pcm);
-#else
-	err = snd_pcm_new(ensoniq->card, "ES1371/2", device, 1, 0, &pcm);
-#endif
+	err = snd_pcm_new(ensoniq->card, CHIP_NAME "/2", device, 1, 0, &pcm);
 	if (err < 0)
 		return err;
 
@@ -1321,16 +1329,22 @@
 #endif
 	pcm->private_data = ensoniq;
 	pcm->info_flags = 0;
-#ifdef CHIP1370
-	strcpy(pcm->name, "ES1370 DAC1");
-#else
-	strcpy(pcm->name, "ES1371 DAC1");
-#endif
+	strcpy(pcm->name, CHIP_NAME " DAC1");
 	ensoniq->pcm2 = pcm;
 
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
 					      snd_dma_pci_data(ensoniq->pci), 64*1024, 128*1024);
 
+#ifdef CHIP1370
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_std_chmaps, 2, 0, NULL);
+#else
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     surround_map, 2, 0, NULL);
+#endif
+	if (err < 0)
+		return err;
+
 	if (rpcm)
 		*rpcm = pcm;
 	return 0;
@@ -1885,11 +1899,7 @@
 {
 	struct ensoniq *ensoniq = entry->private_data;
 
-#ifdef CHIP1370
-	snd_iprintf(buffer, "Ensoniq AudioPCI ES1370\n\n");
-#else
-	snd_iprintf(buffer, "Ensoniq AudioPCI ES1371\n\n");
-#endif
+	snd_iprintf(buffer, "Ensoniq AudioPCI " CHIP_NAME "\n\n");
 	snd_iprintf(buffer, "Joystick enable  : %s\n",
 		    ensoniq->ctrl & ES_JYSTK_EN ? "on" : "off");
 #ifdef CHIP1370
@@ -2361,11 +2371,7 @@
 		*rrawmidi = NULL;
 	if ((err = snd_rawmidi_new(ensoniq->card, "ES1370/1", device, 1, 1, &rmidi)) < 0)
 		return err;
-#ifdef CHIP1370
-	strcpy(rmidi->name, "ES1370");
-#else
-	strcpy(rmidi->name, "ES1371");
-#endif
+	strcpy(rmidi->name, CHIP_NAME);
 	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_ensoniq_midi_output);
 	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_ensoniq_midi_input);
 	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT |
diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c
index ce3e548..cc2e91d 100644
--- a/sound/pci/fm801.c
+++ b/sound/pci/fm801.c
@@ -711,6 +711,13 @@
 					      snd_dma_pci_data(chip->pci),
 					      chip->multichannel ? 128*1024 : 64*1024, 128*1024);
 
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_alt_chmaps,
+				     chip->multichannel ? 6 : 2, 0,
+				     NULL);
+	if (err < 0)
+		return err;
+
 	if (rpcm)
 		*rpcm = pcm;
 	return 0;
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index d71d101..960800b 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -3688,6 +3688,36 @@
 }
 EXPORT_SYMBOL_HDA(snd_hda_build_controls);
 
+/*
+ * add standard channel maps if not specified
+ */
+static int add_std_chmaps(struct hda_codec *codec)
+{
+	int i, str, err;
+
+	for (i = 0; i < codec->num_pcms; i++) {
+		for (str = 0; str < 2; str++) {
+			struct snd_pcm *pcm = codec->pcm_info[i].pcm;
+			struct hda_pcm_stream *hinfo =
+				&codec->pcm_info[i].stream[str];
+			struct snd_pcm_chmap *chmap;
+
+			if (codec->pcm_info[i].own_chmap)
+				continue;
+			if (!pcm || !hinfo->substreams)
+				continue;
+			err = snd_pcm_add_chmap_ctls(pcm, str,
+						     snd_pcm_std_chmaps,
+						     hinfo->channels_max,
+						     0, &chmap);
+			if (err < 0)
+				return err;
+			chmap->channel_mask = SND_PCM_CHMAP_MASK_2468;
+		}
+	}
+	return 0;
+}
+
 int snd_hda_codec_build_controls(struct hda_codec *codec)
 {
 	int err = 0;
@@ -3699,6 +3729,12 @@
 		err = codec->patch_ops.build_controls(codec);
 	if (err < 0)
 		return err;
+
+	/* we create chmaps here instead of build_pcms */
+	err = add_std_chmaps(codec);
+	if (err < 0)
+		return err;
+
 	snd_hda_jack_report_sync(codec); /* call at the last init point */
 	return 0;
 }
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
index ee4ae8e..507fe8a 100644
--- a/sound/pci/hda/hda_codec.h
+++ b/sound/pci/hda/hda_codec.h
@@ -776,6 +776,7 @@
 	unsigned int pcm_type;	/* HDA_PCM_TYPE_XXX */
 	int device;		/* device number to assign */
 	struct snd_pcm *pcm;	/* assigned PCM instance */
+	bool own_chmap;		/* codec driver provides own channel maps */
 };
 
 /* codec information */
diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c
index 333d533..71555cc5 100644
--- a/sound/pci/hda/patch_hdmi.c
+++ b/sound/pci/hda/patch_hdmi.c
@@ -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 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 @@
 	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_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 @@
 	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 @@
 	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 @@
 			    "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 @@
 		/* 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 @@
 
 	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 @@
 				    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 @@
 	.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 @@
 		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 @@
 		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 @@
 	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 @@
 	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 */
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index 6134999..1907dda 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -2287,6 +2287,8 @@
 			p = &alc_pcm_analog_playback;
 		info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *p;
 		info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dac_nids[0];
+		info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
+			spec->multiout.max_channels;
 	}
 	if (spec->adc_nids) {
 		p = spec->stream_analog_capture;
diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c
index 5c41152..848102e 100644
--- a/sound/pci/intel8x0.c
+++ b/sound/pci/intel8x0.c
@@ -1541,6 +1541,26 @@
 					      snd_dma_pci_data(chip->pci),
 					      rec->prealloc_size, rec->prealloc_max_size);
 
+	if (rec->ac97_idx == ICHD_PCMOUT && rec->playback_ops) {
+		struct snd_pcm_chmap *chmap;
+		int chs = 2;
+		if (rec->ac97_idx == ICHD_PCMOUT) {
+			if (chip->multi8)
+				chs = 8;
+			else if (chip->multi6)
+				chs = 6;
+			else if (chip->multi4)
+				chs = 4;
+		}
+		err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					     snd_pcm_alt_chmaps, chs, 0,
+					     &chmap);
+		if (err < 0)
+			return err;
+		chmap->channel_mask = SND_PCM_CHMAP_MASK_2468;
+		chip->ac97[0]->chmaps[SNDRV_PCM_STREAM_PLAYBACK] = chmap;
+	}
+
 	return 0;
 }
 
diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c
index e3d32e2..f0b4efd 100644
--- a/sound/pci/via82xx.c
+++ b/sound/pci/via82xx.c
@@ -1440,6 +1440,7 @@
 static int __devinit snd_via8233_pcm_new(struct via82xx *chip)
 {
 	struct snd_pcm *pcm;
+	struct snd_pcm_chmap *chmap;
 	int i, err;
 
 	chip->playback_devno = 0;	/* x 4 */
@@ -1467,6 +1468,12 @@
 					      snd_dma_pci_data(chip->pci),
 					      64*1024, VIA_MAX_BUFSIZE);
 
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_std_chmaps, 2, 0,
+				     &chmap);
+	if (err < 0)
+		return err;
+
 	/* PCM #1:  multi-channel playback and 2nd capture */
 	err = snd_pcm_new(chip->card, chip->card->shortname, 1, 1, 1, &pcm);
 	if (err < 0)
@@ -1484,6 +1491,14 @@
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
 					      snd_dma_pci_data(chip->pci),
 					      64*1024, VIA_MAX_BUFSIZE);
+
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_alt_chmaps, 6, 0,
+				     &chmap);
+	if (err < 0)
+		return err;
+	chip->ac97->chmaps[SNDRV_PCM_STREAM_PLAYBACK] = chmap;
+
 	return 0;
 }
 
@@ -1493,6 +1508,7 @@
 static int __devinit snd_via8233a_pcm_new(struct via82xx *chip)
 {
 	struct snd_pcm *pcm;
+	struct snd_pcm_chmap *chmap;
 	int err;
 
 	chip->multi_devno = 0;
@@ -1519,6 +1535,13 @@
 					      snd_dma_pci_data(chip->pci),
 					      64*1024, VIA_MAX_BUFSIZE);
 
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_alt_chmaps, 6, 0,
+				     &chmap);
+	if (err < 0)
+		return err;
+	chip->ac97->chmaps[SNDRV_PCM_STREAM_PLAYBACK] = chmap;
+
 	/* SPDIF supported? */
 	if (! ac97_can_spdif(chip->ac97))
 		return 0;
diff --git a/sound/pci/ymfpci/ymfpci_main.c b/sound/pci/ymfpci/ymfpci_main.c
index ee8b636..3a6f03f 100644
--- a/sound/pci/ymfpci/ymfpci_main.c
+++ b/sound/pci/ymfpci/ymfpci_main.c
@@ -1166,6 +1166,11 @@
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
 					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
 
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     snd_pcm_std_chmaps, 2, 0, NULL);
+	if (err < 0)
+		return err;
+
 	if (rpcm)
 		*rpcm = pcm;
 	return 0;
@@ -1257,6 +1262,14 @@
 	.pointer =		snd_ymfpci_playback_pointer,
 };
 
+static const struct snd_pcm_chmap_elem surround_map[] = {
+	{ .channels = 1,
+	  .map = { SNDRV_CHMAP_MONO } },
+	{ .channels = 2,
+	  .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ }
+};
+
 int __devinit snd_ymfpci_pcm_4ch(struct snd_ymfpci *chip, int device, struct snd_pcm ** rpcm)
 {
 	struct snd_pcm *pcm;
@@ -1278,6 +1291,11 @@
 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
 					      snd_dma_pci_data(chip->pci), 64*1024, 256*1024);
 
+	err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+				     surround_map, 2, 0, NULL);
+	if (err < 0)
+		return err;
+
 	if (rpcm)
 		*rpcm = pcm;
 	return 0;