diff mbox

ASoC: dapm: add kcontrol to switch regulator to regulated/bypass state

Message ID 1441707249-11259-1-git-send-email-nikesh@opensource.wolfsonmicro.com (mailing list archive)
State New, archived
Headers show

Commit Message

nikesh@opensource.wolfsonmicro.com Sept. 8, 2015, 10:14 a.m. UTC
When regulator is defined with SND_SOC_DAPM_REGULATOR_CONTROL_BYPASS
flag, then a kcontrol will be created which can be used to switch
regulator to regulated/bypass state. This will help to control the
behaviour of the regulator based on a usecase. For example voice call
may need a regulated voltage to acheive higher quality whereas voice
trigger may need bypass voltage so as to save on power.

Signed-off-by: Nikesh Oswal <nikesh@opensource.wolfsonmicro.com>
---
 include/sound/soc-dapm.h |    7 +-
 sound/soc/soc-dapm.c     |  177 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 183 insertions(+), 1 deletion(-)

Comments

Mark Brown Sept. 8, 2015, 1:22 p.m. UTC | #1
On Tue, Sep 08, 2015 at 11:14:09AM +0100, Nikesh Oswal wrote:
> When regulator is defined with SND_SOC_DAPM_REGULATOR_CONTROL_BYPASS
> flag, then a kcontrol will be created which can be used to switch
> regulator to regulated/bypass state. This will help to control the
> behaviour of the regulator based on a usecase. For example voice call
> may need a regulated voltage to acheive higher quality whereas voice
> trigger may need bypass voltage so as to save on power.

This is really not a good idea, moving a regulator from regulated to
bypass without coordination with the driver is a recipie for bugs at
best and physical damage at worst.  It's something that should be being
done by the device driver based on the current state of the device, the
general model is that we always drive to the lowest power state possible
based on what the device is currently doing.  If the device is set up to
do something that can use an unregulated supply then it should put the
supply into bypass mode without any help from userspace.

As I think we went through the last time you submitted code to the core
it is very important that we have coherent and safe abstractions that
result in code which does what it says.  The code in the core has to
work coherently for everyone, just randomly punching holes through
abstractions for system specific hacks from userspace is not going to do
that.
diff mbox

Patch

diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h
index 15717b4..778b847 100644
--- a/include/sound/soc-dapm.h
+++ b/include/sound/soc-dapm.h
@@ -343,7 +343,12 @@  struct device;
 	(e & (SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD))
 
 /* regulator widget flags */
-#define SND_SOC_DAPM_REGULATOR_BYPASS     0x1     /* bypass when disabled */
+/* bypass when disabled and regulated when enabled */
+#define SND_SOC_DAPM_REGULATOR_BYPASS		0x1
+/* bypass when disabled and regulated when enable by default and a
+   kcontrol is created to explicitly switch between bypass/regulated */
+#define SND_SOC_DAPM_REGULATOR_CONTROL_BYPASS \
+	(SND_SOC_DAPM_REGULATOR_BYPASS | 0x2)
 
 struct snd_soc_dapm_widget;
 enum snd_soc_dapm_type;
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 36ab9cb..2d77eb9 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -965,6 +965,180 @@  static int dapm_new_mux(struct snd_soc_dapm_widget *w)
 	return 0;
 }
 
+static int snd_soc_dapm_regulator_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget_list *wlist =
+		dapm_kcontrol_get_wlist(kcontrol);
+	struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+	struct snd_soc_card *card = widget->dapm->card;
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	unsigned int *item = ucontrol->value.enumerated.item;
+	unsigned int new_val, val;
+	int ret;
+	bool bypass;
+
+	if (item[0] >= e->items)
+		return -EINVAL;
+
+	val = dapm_kcontrol_get_value(kcontrol);
+	new_val = item[0] == 0 ? SND_SOC_DAPM_REGULATOR_BYPASS : 0;
+	bypass = new_val == SND_SOC_DAPM_REGULATOR_BYPASS ? false : true;
+
+	if (new_val != val) {
+		mutex_lock_nested(&card->dapm_mutex,
+				  SND_SOC_DAPM_CLASS_RUNTIME);
+		if (regulator_is_enabled(widget->regulator)) {
+			ret = regulator_allow_bypass(widget->regulator, bypass);
+			if (ret != 0)
+				dev_warn(widget->dapm->dev,
+					"ASoC: Failed to change bypass %s: %d\n",
+					widget->name, ret);
+		}
+		dapm_kcontrol_set_value(kcontrol, new_val);
+		widget->on_val = new_val;
+		mutex_unlock(&card->dapm_mutex);
+	}
+
+	return 0;
+}
+
+static int snd_soc_dapm_regulator_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int val;
+
+	val = dapm_kcontrol_get_value(kcontrol);
+
+	if (val == SND_SOC_DAPM_REGULATOR_BYPASS)
+		ucontrol->value.enumerated.item[0] = 0;
+	else
+		ucontrol->value.enumerated.item[0] = 1;
+
+	return 0;
+}
+
+static const char * const dapm_regulator_texts[] = {
+	"Regulated",
+	"Bypass",
+};
+
+/* create new dapm regulator control */
+static int dapm_new_regulator(struct snd_soc_dapm_widget *w)
+{
+	int ret = 0;
+	struct snd_soc_card *card = w->dapm->card;
+	unsigned long private_value;
+	struct snd_kcontrol *kcontrol;
+	struct snd_soc_dapm_path *path;
+	struct soc_enum regulator_enum[] = {
+		SOC_ENUM_SINGLE_VIRT(ARRAY_SIZE(dapm_regulator_texts),
+			dapm_regulator_texts),
+	};
+	struct snd_kcontrol_new kcontrol_regulator[] = {
+		SOC_ENUM_EXT(NULL, regulator_enum[0],
+			snd_soc_dapm_regulator_get,
+			snd_soc_dapm_regulator_put),
+	};
+
+
+	/* kcontrol creation is done only if client requests it */
+	if (w->on_val != SND_SOC_DAPM_REGULATOR_CONTROL_BYPASS)
+		return 0;
+
+
+	/* create a kcontrol only if somebody is sourcing
+	   from this regulator widget */
+	if (list_empty(&w->edges[SND_SOC_DAPM_DIR_IN])) {
+		dev_err(w->dapm->dev, "ASoC: %s has no sinks\n", w->name);
+		return -EINVAL;
+	}
+
+	w->num_kcontrols = 1;
+
+	private_value = (unsigned long) devm_kmemdup(card->dev,
+			(void *)(kcontrol_regulator[0].private_value),
+			sizeof(struct soc_enum), GFP_KERNEL);
+	if (!private_value) {
+		dev_err(card->dev, "ASoC: Failed to create control for %s widget\n",
+			w->name);
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	kcontrol_regulator[0].private_value = private_value;
+
+	w->kcontrol_news = devm_kmemdup(card->dev, &kcontrol_regulator[0],
+		sizeof(struct snd_kcontrol_new), GFP_KERNEL);
+	if (!(w->kcontrol_news)) {
+		dev_err(card->dev, "ASoC: Failed to create control for %s widget\n",
+			w->name);
+		ret = -ENOMEM;
+		goto err_private;
+	}
+
+
+	kcontrol = snd_soc_cnew(&w->kcontrol_news[0], NULL,
+		w->name, NULL);
+
+	if (!kcontrol) {
+		ret = -ENOMEM;
+		goto err_kcontrol_news;
+	}
+
+	kcontrol->private_free = dapm_kcontrol_free;
+
+	ret = dapm_kcontrol_data_alloc(w, kcontrol);
+	if (ret)
+		goto err_kcontrol;
+
+
+	ret = snd_ctl_add(card->snd_card, kcontrol);
+	if (ret < 0) {
+		dev_err(w->dapm->dev,
+			"ASoC: failed to add widget %s dapm kcontrol %s: %d\n",
+			w->name, w->kcontrol_news[0].name, ret);
+		goto err_kcontrol;
+	}
+
+	ret = dapm_kcontrol_add_widget(kcontrol, w);
+	if (ret)
+		goto err_kcontrol;
+
+
+	/* change on_val to remove the kcontrol creation bit
+	   as kcontrol is already created */
+	w->on_val = SND_SOC_DAPM_REGULATOR_BYPASS;
+	/* update the kcontrol value to reflect the initial value */
+	dapm_kcontrol_set_value(kcontrol, w->on_val);
+
+	w->kcontrols = kzalloc(w->num_kcontrols *
+				sizeof(struct snd_kcontrol *),
+				GFP_KERNEL);
+	if (!w->kcontrols) {
+		ret = -ENOMEM;
+		goto err_kcontrol;
+	}
+
+	w->kcontrols[0] = kcontrol;
+
+	snd_soc_dapm_widget_for_each_path(w, SND_SOC_DAPM_DIR_IN, path) {
+		if (path->name)
+			dapm_kcontrol_add_path(w->kcontrols[0], path);
+	}
+
+	return 0;
+
+err_kcontrol:
+	snd_ctl_free_one(kcontrol);
+err_kcontrol_news:
+	devm_kfree(card->dev, (void *)w->kcontrol_news);
+err_private:
+	devm_kfree(card->dev, (void *)private_value);
+err_out:
+	return ret;
+}
+
 /* create new dapm volume control */
 static int dapm_new_pga(struct snd_soc_dapm_widget *w)
 {
@@ -2922,6 +3096,9 @@  int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
 		case snd_soc_dapm_dai_link:
 			dapm_new_dai_link(w);
 			break;
+		case snd_soc_dapm_regulator_supply:
+			dapm_new_regulator(w);
+			break;
 		default:
 			break;
 		}