ALSA: PCM: volume API implementation

Introduced a new helper function snd_pcm_add_volume_ctls() to
create control elements representing the volume for each PCM
(sub)stream.

This patch also squashes the logic implemented by below change-

ASoC: Fix freed memory access of pcm stream kctl

Consider sound card instantiate fails due to
audrx init failure. In such case, all dais/ctls
are de-registered and freed. But as part of it,
access to unregistered ctls for pcm_chmap and similar
controls result in crash.  Ctls are freed at disconnection
but the disconnect is called only when it was registered.

CRs-Fixed: 1038054
Change-Id: Ief8817b4ec000c058d46aa021977b7c6003c0011
Signed-off-by: Laxminath Kasam <lkasam@codeaurora.org>
Signed-off-by: Damir Didjusto <damird@codeaurora.org>
Signed-off-by: Banajit Goswami <bgoswami@codeaurora.org>
Signed-off-by: Sudheer Papothi <spapothi@codeaurora.org>
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index b4db06a..df5ca27 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -518,6 +518,7 @@
 #endif
 #endif
 	struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
+	struct snd_kcontrol *vol_kctl; /* volume controls */
 	struct device dev;
 };
 
@@ -1412,6 +1413,30 @@
 	return 1ULL << (__force int) pcm_format;
 }
 
+/*
+ * PCM Volume control API
+ */
+/* array element of volume */
+struct snd_pcm_volume_elem {
+	int volume;
+};
+
+/* pp information; retrieved via snd_kcontrol_chip() */
+struct snd_pcm_volume {
+	struct snd_pcm *pcm;	/* assigned PCM instance */
+	int stream;		/* PLAYBACK or CAPTURE */
+	struct snd_kcontrol *kctl;
+	const struct snd_pcm_volume_elem *volume;
+	int max_length;
+	void *private_data;	/* optional: private data pointer */
+};
+
+int snd_pcm_add_volume_ctls(struct snd_pcm *pcm, int stream,
+			   const struct snd_pcm_volume_elem *volume,
+			   int max_length,
+			   unsigned long private_value,
+			   struct snd_pcm_volume **info_ret);
+
 /* printk helpers */
 #define pcm_err(pcm, fmt, args...) \
 	dev_err((pcm)->card->dev, fmt, ##args)
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 8e980aa..1155ecb 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -855,6 +855,10 @@
 		snd_ctl_remove(pstr->pcm->card, pstr->chmap_kctl);
 		pstr->chmap_kctl = NULL;
 	}
+	if (pstr->vol_kctl) {
+		snd_ctl_remove(pstr->pcm->card, pstr->vol_kctl);
+		pstr->vol_kctl = NULL;
+	}
 }
 
 static void snd_pcm_free_stream(struct snd_pcm_str * pstr)
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 294230d..b57fc6e 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -41,6 +41,8 @@
 #define trace_hw_ptr_error(substream, reason)
 #endif
 
+#define STRING_LENGTH_OF_INT 12
+
 /*
  * fill ring buffer with silence
  * runtime->silence_start: starting pointer to silence area
@@ -2568,6 +2570,24 @@
 	kfree(info);
 }
 
+static int pcm_volume_ctl_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 = 0;
+	uinfo->value.integer.max = 0x2000;
+	return 0;
+}
+
+static void pcm_volume_ctl_private_free(struct snd_kcontrol *kcontrol)
+{
+	struct snd_pcm_volume *info = snd_kcontrol_chip(kcontrol);
+
+	info->pcm->streams[info->stream].vol_kctl = NULL;
+	kfree(info);
+}
+
 /**
  * snd_pcm_add_chmap_ctls - create channel-mapping control elements
  * @pcm: the assigned PCM instance
@@ -2629,3 +2649,74 @@
 	return 0;
 }
 EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls);
+
+/**
+ * snd_pcm_add_volume_ctls - create volume control elements
+ * @pcm: the assigned PCM instance
+ * @stream: stream direction
+ * @max_length: the max length of the volume parameter of stream
+ * @private_value: the value passed to each kcontrol's private_value field
+ * @info_ret: store struct snd_pcm_volume instance if non-NULL
+ *
+ * Create volume control elements assigned to the given PCM stream(s).
+ * Returns zero if succeed, or a negative error value.
+ */
+int snd_pcm_add_volume_ctls(struct snd_pcm *pcm, int stream,
+			   const struct snd_pcm_volume_elem *volume,
+			   int max_length,
+			   unsigned long private_value,
+			   struct snd_pcm_volume **info_ret)
+{
+	struct snd_pcm_volume *info;
+	struct snd_kcontrol_new knew = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+			SNDRV_CTL_ELEM_ACCESS_READWRITE,
+		.info = pcm_volume_ctl_info,
+	};
+	int err;
+	int size;
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+	info->pcm = pcm;
+	info->stream = stream;
+	info->volume = volume;
+	info->max_length = max_length;
+	size = sizeof("Playback ") + sizeof(" Volume") +
+		STRING_LENGTH_OF_INT*sizeof(char) + 1;
+	knew.name = kzalloc(size, GFP_KERNEL);
+	if (!knew.name) {
+		kfree(info);
+		return -ENOMEM;
+	}
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		snprintf((char *)knew.name, size, "%s %d %s",
+			"Playback", pcm->device, "Volume");
+	else
+		snprintf((char *)knew.name, size, "%s %d %s",
+			"Capture", pcm->device, "Volume");
+	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);
+		kfree(knew.name);
+		return -ENOMEM;
+	}
+	info->kctl->private_free = pcm_volume_ctl_private_free;
+	err = snd_ctl_add(pcm->card, info->kctl);
+	if (err < 0) {
+		kfree(info);
+		kfree(knew.name);
+		return -ENOMEM;
+	}
+	pcm->streams[stream].vol_kctl = info->kctl;
+	if (info_ret)
+		*info_ret = info;
+	kfree(knew.name);
+	return 0;
+}
+EXPORT_SYMBOL(snd_pcm_add_volume_ctls);