[alsa-devel] [PATCH 1/2] regulator: Implement deferred disable support
It is a reasonably common pattern for hardware to require some delay after being quiesced before the disable has finalised, especially in mixed signal devices. For example, an active discharge may be required to ensure that the circuit starts up again in a known state. Avoid having to implement such delays in the regulator API by providing regulator_deferred_disable() which will do a regulator_disable() a specified number of milliseconds after it is called.
Due to the reference counting done on regulators a deferred disable can be cancelled by doing another regulator_enable().
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- drivers/regulator/core.c | 59 ++++++++++++++++++++++++++++++++++++ include/linux/regulator/consumer.h | 7 ++++ include/linux/regulator/driver.h | 3 ++ 3 files changed, 69 insertions(+), 0 deletions(-)
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 9a33fe2..026f3d4 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1552,6 +1552,63 @@ int regulator_force_disable(struct regulator *regulator) } EXPORT_SYMBOL_GPL(regulator_force_disable);
+static void regulator_disable_work(struct work_struct *work) +{ + struct regulator_dev *rdev = container_of(work, struct regulator_dev, + disable_work.work); + int count, i, ret; + + mutex_lock(&rdev->mutex); + + BUG_ON(!rdev->deferred_disables); + + count = rdev->deferred_disables; + rdev->deferred_disables = 0; + + for (i = 0; i < count; i++) { + ret = _regulator_disable(rdev); + if (ret != 0) + rdev_err(rdev, "Deferred disable failed: %d\n", ret); + } + + mutex_unlock(&rdev->mutex); + + if (rdev->supply) { + for (i = 0; i < count; i++) { + ret = regulator_disable(rdev->supply); + if (ret != 0) { + rdev_err(rdev, + "Supply disable failed: %d\n", ret); + } + } + } +} + +/** + * regulator_disable_deferred - disable regulator output with delay + * @regulator: regulator source + * @ms: miliseconds until the regulator is disabled + * + * Execute regulator_disable() on the regulator after a delay. This + * is intended for use with devices that require some time to quiesce. + * + * NOTE: this will only disable the regulator output if no other consumer + * devices have it enabled, the regulator device supports disabling and + * machine constraints permit this operation. + */ +int regulator_disable_deferred(struct regulator *regulator, int ms) +{ + struct regulator_dev *rdev = regulator->rdev; + + mutex_lock(&rdev->mutex); + rdev->deferred_disables++; + mutex_unlock(&rdev->mutex); + + return schedule_delayed_work(&rdev->disable_work, + msecs_to_jiffies(ms)); +} +EXPORT_SYMBOL_GPL(regulator_disable_deferred); + static int _regulator_is_enabled(struct regulator_dev *rdev) { /* If we don't know then assume that the regulator is always on */ @@ -2622,6 +2679,7 @@ struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc, INIT_LIST_HEAD(&rdev->consumer_list); INIT_LIST_HEAD(&rdev->list); BLOCKING_INIT_NOTIFIER_HEAD(&rdev->notifier); + INIT_DELAYED_WORK(&rdev->disable_work, regulator_disable_work);
/* preform any regulator specific init */ if (init_data->regulator_init) { @@ -2729,6 +2787,7 @@ void regulator_unregister(struct regulator_dev *rdev) #ifdef CONFIG_DEBUG_FS debugfs_remove_recursive(rdev->debugfs); #endif + flush_work_sync(&rdev->disable_work.work); WARN_ON(rdev->open_count); unset_regulator_supplies(rdev); list_del(&rdev->list); diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h index b47771a..f7756d1 100644 --- a/include/linux/regulator/consumer.h +++ b/include/linux/regulator/consumer.h @@ -141,6 +141,7 @@ int regulator_enable(struct regulator *regulator); int regulator_disable(struct regulator *regulator); int regulator_force_disable(struct regulator *regulator); int regulator_is_enabled(struct regulator *regulator); +int regulator_disable_deferred(struct regulator *regulator, int ms);
int regulator_bulk_get(struct device *dev, int num_consumers, struct regulator_bulk_data *consumers); @@ -211,6 +212,12 @@ static inline int regulator_disable(struct regulator *regulator) return 0; }
+static inline int regulator_disable_deferred(struct regulator *regulator, + int ms) +{ + return 0; +} + static inline int regulator_is_enabled(struct regulator *regulator) { return 1; diff --git a/include/linux/regulator/driver.h b/include/linux/regulator/driver.h index 03a1798..52c89ae 100644 --- a/include/linux/regulator/driver.h +++ b/include/linux/regulator/driver.h @@ -200,6 +200,9 @@ struct regulator_dev { struct regulation_constraints *constraints; struct regulator *supply; /* for tree */
+ struct delayed_work disable_work; + int deferred_disables; + void *reg_data; /* regulator_dev data */
#ifdef CONFIG_DEBUG_FS
The WM8996 only requires CPVDD when the charge pump is active so control it separately to the other supplies, only enabling it when the charge pump is active. This will result in a small power saving on systems which are able to provide independent software control of the supply.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/wm8996.c | 38 ++++++++++++++++++++++++++++++-------- 1 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index b3b0a9d..0fed436 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -41,12 +41,11 @@ #define HPOUT2L 4 #define HPOUT2R 8
-#define WM8996_NUM_SUPPLIES 4 +#define WM8996_NUM_SUPPLIES 3 static const char *wm8996_supply_names[WM8996_NUM_SUPPLIES] = { "DBVDD", "AVDD1", "AVDD2", - "CPVDD", };
struct wm8996_priv { @@ -71,6 +70,7 @@ struct wm8996_priv {
struct regulator_bulk_data supplies[WM8996_NUM_SUPPLIES]; struct notifier_block disable_nb[WM8996_NUM_SUPPLIES]; + struct regulator *cpvdd;
struct wm8996_pdata pdata;
@@ -112,7 +112,6 @@ static int wm8996_regulator_event_##n(struct notifier_block *nb, \ WM8996_REGULATOR_EVENT(0) WM8996_REGULATOR_EVENT(1) WM8996_REGULATOR_EVENT(2) -WM8996_REGULATOR_EVENT(3)
static const u16 wm8996_reg[WM8996_MAX_REGISTER] = { [WM8996_SOFTWARE_RESET] = 0x8996, @@ -670,16 +669,29 @@ SOC_SINGLE_TLV("DSP2 EQ B5 Volume", WM8996_DSP2_RX_EQ_GAINS_2, 6, 31, 0, static int cp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { + struct snd_soc_codec *codec = w->codec; + struct wm8996_priv *wm8996 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ret = regulator_enable(wm8996->cpvdd); + if (ret != 0) + dev_err(codec->dev, "Failed to enable CPVDD: %d\n", + ret); + break; case SND_SOC_DAPM_POST_PMU: msleep(5); break; + case SND_SOC_DAPM_POST_PMD: + regulator_disable_deferred(wm8996->cpvdd, 20); + break; default: BUG(); - return -EINVAL; + ret = -EINVAL; }
- return 0; + return ret; }
static int rmv_short_event(struct snd_soc_dapm_widget *w, @@ -988,7 +1000,8 @@ SND_SOC_DAPM_SUPPLY_S("SYSCLK", 1, WM8996_AIF_CLOCKING_1, 0, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("SYSDSPCLK", 2, WM8996_CLOCKING_1, 1, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("AIFCLK", 2, WM8996_CLOCKING_1, 2, 0, NULL, 0), SND_SOC_DAPM_SUPPLY_S("Charge Pump", 2, WM8996_CHARGE_PUMP_1, 15, 0, cp_event, - SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_SUPPLY("LDO2", WM8996_POWER_MANAGEMENT_2, 1, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("MICB1 Audio", WM8996_MICBIAS_1, 4, 1, NULL, 0), @@ -2639,7 +2652,13 @@ static int wm8996_probe(struct snd_soc_codec *codec) wm8996->disable_nb[0].notifier_call = wm8996_regulator_event_0; wm8996->disable_nb[1].notifier_call = wm8996_regulator_event_1; wm8996->disable_nb[2].notifier_call = wm8996_regulator_event_2; - wm8996->disable_nb[3].notifier_call = wm8996_regulator_event_3; + + wm8996->cpvdd = regulator_get(&i2c->dev, "CPVDD"); + if (IS_ERR(wm8996->cpvdd)) { + ret = PTR_ERR(wm8996->cpvdd); + dev_err(&i2c->dev, "Failed to get CPVDD: %d\n", ret); + goto err_get; + }
/* This should really be moved into the regulator core */ for (i = 0; i < ARRAY_SIZE(wm8996->supplies); i++) { @@ -2656,7 +2675,7 @@ static int wm8996_probe(struct snd_soc_codec *codec) wm8996->supplies); if (ret != 0) { dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); - goto err_get; + goto err_cpvdd; }
if (wm8996->pdata.ldo_ena >= 0) { @@ -2899,6 +2918,8 @@ err_enable: gpio_set_value_cansleep(wm8996->pdata.ldo_ena, 0);
regulator_bulk_disable(ARRAY_SIZE(wm8996->supplies), wm8996->supplies); +err_cpvdd: + regulator_put(wm8996->cpvdd); err_get: regulator_bulk_free(ARRAY_SIZE(wm8996->supplies), wm8996->supplies); err: @@ -2922,6 +2943,7 @@ static int wm8996_remove(struct snd_soc_codec *codec) for (i = 0; i < ARRAY_SIZE(wm8996->supplies); i++) regulator_unregister_notifier(wm8996->supplies[i].consumer, &wm8996->disable_nb[i]); + regulator_put(wm8996->cpvdd); regulator_bulk_free(ARRAY_SIZE(wm8996->supplies), wm8996->supplies);
return 0;
On Mon, 2011-09-12 at 17:59 +0100, Mark Brown wrote:
The WM8996 only requires CPVDD when the charge pump is active so control it separately to the other supplies, only enabling it when the charge pump is active. This will result in a small power saving on systems which are able to provide independent software control of the supply.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com
sound/soc/codecs/wm8996.c | 38 ++++++++++++++++++++++++++++++-------- 1 files changed, 30 insertions(+), 8 deletions(-)
Acked-by: Liam Girdwood lrg@ti.com
On Tue, Sep 13, 2011 at 03:53:20PM +0100, Liam Girdwood wrote:
On Mon, 2011-09-12 at 17:59 +0100, Mark Brown wrote:
sound/soc/codecs/wm8996.c | 38 ++++++++++++++++++++++++++++++-------- 1 files changed, 30 insertions(+), 8 deletions(-)
Acked-by: Liam Girdwood lrg@ti.com
Should I apply the regulator change to ASoC as well? Obviously this won't build without that. Providing there aren't any merge issues this could go via either tree (or I could just hang onto this until after the merge window, it's not exactly urgent).
On Tue, 2011-09-13 at 16:15 +0100, Mark Brown wrote:
On Tue, Sep 13, 2011 at 03:53:20PM +0100, Liam Girdwood wrote:
On Mon, 2011-09-12 at 17:59 +0100, Mark Brown wrote:
sound/soc/codecs/wm8996.c | 38 ++++++++++++++++++++++++++++++-------- 1 files changed, 30 insertions(+), 8 deletions(-)
Acked-by: Liam Girdwood lrg@ti.com
Should I apply the regulator change to ASoC as well? Obviously this won't build without that. Providing there aren't any merge issues this could go via either tree (or I could just hang onto this until after the merge window, it's not exactly urgent).
Yeah, lets take 1/2 via ASoC. It has my Ack.
participants (2)
-
Liam Girdwood
-
Mark Brown