diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h
index 8f2d3064b520..18cdc59df9aa 100644
--- a/include/sound/soc-usb.h
+++ b/include/sound/soc-usb.h
@@ -6,6 +6,12 @@
#ifndef __LINUX_SND_SOC_USB_H
#define __LINUX_SND_SOC_USB_H
+enum snd_soc_usb_kctl {
+ SND_SOC_USB_KCTL_CARD_ROUTE,
+ SND_SOC_USB_KCTL_PCM_ROUTE,
+ SND_SOC_USB_KCTL_MAX,
+};
+
/**
* struct snd_soc_usb_device
* @card_idx - sound card index associated with USB device@@ -24,16 +30,26 @@ struct snd_soc_usb_device {
* struct snd_soc_usb
* @list - list head for SND SOC struct list
* @component - reference to ASoC component
+ * @kctl - list of kcontrols created
* @num_supported_streams - number of supported concurrent sessions
* @connection_status_cb - callback to notify connection events
+ * @put_offload_dev - callback to select USB sound card/PCM device
+ * @get_offload_dev - callback to fetch selected USB sound card/PCM device
* @priv_data - driver data
**/
struct snd_soc_usb {
struct list_head list;
struct snd_soc_component *component;
+ struct snd_kcontrol *kctl[SND_SOC_USB_KCTL_MAX];
unsigned int num_supported_streams;
int (*connection_status_cb)(struct snd_soc_usb *usb,
struct snd_soc_usb_device *sdev, bool connected);
+ int (*put_offload_dev)(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ enum snd_soc_usb_kctl type);
+ int (*get_offload_dev)(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol,
+ enum snd_soc_usb_kctl type);
void *priv_data;
};
diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c
index bc77204fd2db..ade09b416d45 100644
--- a/sound/soc/soc-usb.c
+++ b/sound/soc/soc-usb.c
@@ -15,6 +15,9 @@ static struct device_node *snd_soc_find_phandle(struct device *dev)
{
struct device_node *node;
+ if (!dev)
+ return ERR_PTR(-ENODEV);
+
node = of_parse_phandle(dev->of_node, "usb-soc-be", 0);
if (!node)
return ERR_PTR(-ENODEV);@@ -38,6 +41,152 @@ static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device_node *node)
return NULL;
}
+/* SOC USB sound kcontrols */
+static int soc_usb_put_offload_pcm_dev(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node);
+ int ret = 0;
+
+ mutex_lock(&ctx_mutex);
+ if (ctx && ctx->put_offload_dev)
+ ret = ctx->put_offload_dev(kcontrol, ucontrol,
+ SND_SOC_USB_KCTL_PCM_ROUTE);
+ mutex_unlock(&ctx_mutex);
+
+ return ret;
+}
+
+static int soc_usb_get_offload_pcm_dev(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node);
+ int ret = 0;
+
+ ucontrol->value.integer.value[0] = -1;
+
+ mutex_lock(&ctx_mutex);
+ if (ctx && ctx->get_offload_dev)
+ ret = ctx->get_offload_dev(kcontrol, ucontrol,
+ SND_SOC_USB_KCTL_PCM_ROUTE);
+ mutex_unlock(&ctx_mutex);
+
+ return ret;
+
+}
+
+static int soc_usb_put_offload_card_dev(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node);
+ int ret = 0;
+
+ mutex_lock(&ctx_mutex);
+ if (ctx && ctx->put_offload_dev)
+ ret = ctx->put_offload_dev(kcontrol, ucontrol,
+ SND_SOC_USB_KCTL_CARD_ROUTE);
+ mutex_unlock(&ctx_mutex);
+
+ return ret;
+}
+
+static int soc_usb_get_offload_card_dev(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct snd_soc_usb *ctx = snd_soc_find_usb_ctx(component->dev->of_node);
+ int ret = 0;
+
+ ucontrol->value.integer.value[0] = -1;
+
+ mutex_lock(&ctx_mutex);
+ if (ctx && ctx->get_offload_dev)
+ ret = ctx->get_offload_dev(kcontrol, ucontrol,
+ SND_SOC_USB_KCTL_CARD_ROUTE);
+ mutex_unlock(&ctx_mutex);
+
+ return ret;
+
+}
+
+static int soc_usb_offload_pcm_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = -1;
+ /* Arbitrary max value, as there is no 'limit' on number of PCM devices */
+ uinfo->value.integer.max = 0xff;
+
+ return 0;
+}
+
+static int soc_usb_offload_card_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = -1;
+ uinfo->value.integer.max = SNDRV_CARDS;
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new soc_usb_kcontrols[] = {
+ [SND_SOC_USB_KCTL_CARD_ROUTE] = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .name = "USB Offload Playback Route Card Select",
+ .info = soc_usb_offload_card_info,
+ .get = soc_usb_get_offload_card_dev,
+ .put = soc_usb_put_offload_card_dev,
+ },
+ [SND_SOC_USB_KCTL_PCM_ROUTE] = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .name = "USB Offload Playback Route PCM Select",
+ .info = soc_usb_offload_pcm_info,
+ .get = soc_usb_get_offload_pcm_dev,
+ .put = soc_usb_put_offload_pcm_dev,
+ },
+};
+
+static int snd_soc_usb_control_remove(struct snd_soc_usb *usb)
+{
+ struct snd_soc_component *component = usb->component;
+ int i;
+
+ for (i = 0; i < SND_SOC_USB_KCTL_MAX; i++) {
+ if (usb->kctl[i]) {
+ snd_ctl_remove(component->card->snd_card,
+ usb->kctl[i]);
+ snd_ctl_free_one(usb->kctl[i]);
+ usb->kctl[i] = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static int snd_soc_usb_control_init(struct snd_soc_usb *usb)
+{
+ struct snd_soc_component *component = usb->component;
+ int ret;
+ int i;
+
+ for (i = 0; i < SND_SOC_USB_KCTL_MAX; i++) {
+ usb->kctl[i] = snd_ctl_new1(&soc_usb_kcontrols[i], component);
+ ret = snd_ctl_add(component->card->snd_card, usb->kctl[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
/**
* snd_soc_usb_get_components_tag() - Retrieve SOC USB component tag
* @playback: direction of audio stream@@ -158,6 +307,12 @@ EXPORT_SYMBOL_GPL(snd_soc_usb_free_port);
*/
int snd_soc_usb_add_port(struct snd_soc_usb *usb)
{
+ int ret;
+
+ ret = snd_soc_usb_control_init(usb);
+ if (ret < 0)
+ return ret;
+
mutex_lock(&ctx_mutex);
list_add_tail(&usb->list, &usb_ctx_list);
mutex_unlock(&ctx_mutex);@@ -187,6 +342,8 @@ int snd_soc_usb_remove_port(struct snd_soc_usb *usb)
}
mutex_unlock(&ctx_mutex);
+ snd_soc_usb_control_remove(usb);
+
return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port);