|
@@ -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 @@ snd_pcm_sframes_t snd_pcm_lib_readv(struct snd_pcm_substream *substream,
|
|
|
}
|
|
|
|
|
|
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);
|