[alsa-devel] [PATCHv2 0/4] ASoC: tlv320aic3x: Add dynamic regulator control
Hi
I named this set as v2 since aic3x soc-cache conversion was done between v1 and this.
Set goes on top of my previous aic3x one liner: http://mailman.alsa-project.org/pipermail/alsa-devel/2010-September/031749.h...
It will be easier to keep regulator enable/disable calls in sync when dynamic regulator management is added if regulator management is moved from aic3x_i2c_probe/_remove to aic3x_probe/_remove.
Signed-off-by: Jarkko Nikula jhnikula@gmail.com --- sound/soc/codecs/tlv320aic3x.c | 100 ++++++++++++++++++++-------------------- 1 files changed, 50 insertions(+), 50 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 6190351..c60ead9 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1266,7 +1266,7 @@ static int aic3x_init(struct snd_soc_codec *codec) static int aic3x_probe(struct snd_soc_codec *codec) { struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); - int ret; + int ret, i;
codec->control_data = aic3x->control_data;
@@ -1276,6 +1276,35 @@ static int aic3x_probe(struct snd_soc_codec *codec) return ret; }
+ if (aic3x->gpio_reset >= 0) { + ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset"); + if (ret != 0) + goto err_gpio; + gpio_direction_output(aic3x->gpio_reset, 0); + } + + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) + aic3x->supplies[i].supply = aic3x_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err_get; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_enable; + } + + if (aic3x->gpio_reset >= 0) { + udelay(1); + gpio_set_value(aic3x->gpio_reset, 1); + } + aic3x_init(codec);
if (aic3x->setup) { @@ -1294,11 +1323,29 @@ static int aic3x_probe(struct snd_soc_codec *codec) aic3x_add_widgets(codec);
return 0; + +err_enable: + regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); +err_get: + if (aic3x->gpio_reset >= 0) + gpio_free(aic3x->gpio_reset); +err_gpio: + kfree(aic3x); + return ret; }
static int aic3x_remove(struct snd_soc_codec *codec) { + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); + aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); + if (aic3x->gpio_reset >= 0) { + gpio_set_value(aic3x->gpio_reset, 0); + gpio_free(aic3x->gpio_reset); + } + regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); + regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); + return 0; }
@@ -1336,7 +1383,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, { struct aic3x_pdata *pdata = i2c->dev.platform_data; struct aic3x_priv *aic3x; - int ret, i; + int ret; const struct i2c_device_id *tbl;
aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); @@ -1356,68 +1403,21 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, aic3x->gpio_reset = -1; }
- if (aic3x->gpio_reset >= 0) { - ret = gpio_request(aic3x->gpio_reset, "tlv320aic3x reset"); - if (ret != 0) - goto err_gpio; - gpio_direction_output(aic3x->gpio_reset, 0); - } - for (tbl = aic3x_i2c_id; tbl->name[0]; tbl++) { if (!strcmp(tbl->name, id->name)) break; } aic3x->model = tbl - aic3x_i2c_id;
- for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) - aic3x->supplies[i].supply = aic3x_supply_names[i]; - - ret = regulator_bulk_get(&i2c->dev, ARRAY_SIZE(aic3x->supplies), - aic3x->supplies); - if (ret != 0) { - dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); - goto err_get; - } - - ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies), - aic3x->supplies); - if (ret != 0) { - dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); - goto err_enable; - } - - if (aic3x->gpio_reset >= 0) { - udelay(1); - gpio_set_value(aic3x->gpio_reset, 1); - } - ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_aic3x, &aic3x_dai, 1); if (ret < 0) - goto err_enable; - return ret; - -err_enable: - regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); -err_get: - if (aic3x->gpio_reset >= 0) - gpio_free(aic3x->gpio_reset); -err_gpio: - kfree(aic3x); + kfree(aic3x); return ret; }
static int aic3x_i2c_remove(struct i2c_client *client) { - struct aic3x_priv *aic3x = i2c_get_clientdata(client); - - if (aic3x->gpio_reset >= 0) { - gpio_set_value(aic3x->gpio_reset, 0); - gpio_free(aic3x->gpio_reset); - } - regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); - regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); - snd_soc_unregister_codec(&client->dev); kfree(i2c_get_clientdata(client)); return 0;
On Mon, Sep 20, 2010 at 10:39:11AM +0300, Jarkko Nikula wrote:
It will be easier to keep regulator enable/disable calls in sync when dynamic regulator management is added if regulator management is moved from aic3x_i2c_probe/_remove to aic3x_probe/_remove.
Signed-off-by: Jarkko Nikula jhnikula@gmail.com
Acked-by: Mark Brown broonie@opensource.wolfsonmicro.com
On Mon, 2010-09-20 at 10:26 +0100, Mark Brown wrote:
On Mon, Sep 20, 2010 at 10:39:11AM +0300, Jarkko Nikula wrote:
It will be easier to keep regulator enable/disable calls in sync when dynamic regulator management is added if regulator management is moved from aic3x_i2c_probe/_remove to aic3x_probe/_remove.
Signed-off-by: Jarkko Nikula jhnikula@gmail.com
Acked-by: Mark Brown broonie@opensource.wolfsonmicro.com
All Applied.
Thanks
Liam
Now all the regulators are disabled when entering into SND_SOC_BIAS_OFF and enabled when coming back to SND_SOC_BIAS_STANDBY state. Currently this runtime control happens only with suspend/resume as this patch does not change the default idle behavior.
This patch manages all the regulators and reset since it seems that register sync is needed even if only analog supplies AVDD and DRVDD are disabled. This was noted when the system was running with idle behavior changed and IOVDD and DVDD were on.
It is not known are all the registers needed to sync or only some subset of them. Therefore patch plays safe and does always full shutdown/power-up.
Signed-off-by: Jarkko Nikula jhnikula@gmail.com --- sound/soc/codecs/tlv320aic3x.c | 83 +++++++++++++++++++++++++++++++--------- 1 files changed, 65 insertions(+), 18 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index c60ead9..5f8a7c4 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -70,6 +70,7 @@ struct aic3x_priv { unsigned int sysclk; int master; int gpio_reset; + int power; #define AIC3X_MODEL_3X 0 #define AIC3X_MODEL_33 1 #define AIC3X_MODEL_3007 2 @@ -1028,6 +1029,64 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; }
+static int aic3x_init_3007(struct snd_soc_codec *codec) +{ + u8 tmp1, tmp2, *cache = codec->reg_cache; + + /* + * There is no need to cache writes to undocumented page 0xD but + * respective page 0 register cache entries must be preserved + */ + tmp1 = cache[0xD]; + tmp2 = cache[0x8]; + /* Class-D speaker driver init; datasheet p. 46 */ + snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x0D); + snd_soc_write(codec, 0xD, 0x0D); + snd_soc_write(codec, 0x8, 0x5C); + snd_soc_write(codec, 0x8, 0x5D); + snd_soc_write(codec, 0x8, 0x5C); + snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x00); + cache[0xD] = tmp1; + cache[0x8] = tmp2; + + return 0; +} + +static int aic3x_set_power(struct snd_soc_codec *codec, int power) +{ + struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); + int i, ret; + u8 *cache = codec->reg_cache; + + if (power) { + ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + if (ret) + goto out; + aic3x->power = 1; + if (aic3x->gpio_reset >= 0) { + udelay(1); + gpio_set_value(aic3x->gpio_reset, 1); + } + + /* Sync reg_cache with the hardware */ + codec->cache_only = 0; + for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++) + snd_soc_write(codec, i, cache[i]); + if (aic3x->model == AIC3X_MODEL_3007) + aic3x_init_3007(codec); + codec->cache_sync = 0; + } else { + aic3x->power = 0; + if (aic3x->gpio_reset >= 0) + gpio_set_value(aic3x->gpio_reset, 0); + ret = regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), + aic3x->supplies); + } +out: + return ret; +} + static int aic3x_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -1047,6 +1106,8 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec, } break; case SND_SOC_BIAS_STANDBY: + if (!aic3x->power) + aic3x_set_power(codec, 1); if (codec->bias_level == SND_SOC_BIAS_PREPARE && aic3x->master) { /* disable pll */ @@ -1056,6 +1117,8 @@ static int aic3x_set_bias_level(struct snd_soc_codec *codec, } break; case SND_SOC_BIAS_OFF: + if (aic3x->power) + aic3x_set_power(codec, 0); break; } codec->bias_level = level; @@ -1155,17 +1218,6 @@ static int aic3x_suspend(struct snd_soc_codec *codec, pm_message_t state)
static int aic3x_resume(struct snd_soc_codec *codec) { - int i; - u8 data[2]; - u8 *cache = codec->reg_cache; - - /* Sync reg_cache with the hardware */ - for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++) { - data[0] = i; - data[1] = cache[i]; - codec->hw_write(codec->control_data, data, 2); - } - aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0; @@ -1247,13 +1299,7 @@ static int aic3x_init(struct snd_soc_codec *codec) snd_soc_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL);
if (aic3x->model == AIC3X_MODEL_3007) { - /* Class-D speaker driver init; datasheet p. 46 */ - snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x0D); - snd_soc_write(codec, 0xD, 0x0D); - snd_soc_write(codec, 0x8, 0x5C); - snd_soc_write(codec, 0x8, 0x5D); - snd_soc_write(codec, 0x8, 0x5C); - snd_soc_write(codec, AIC3X_PAGE_SELECT, 0x00); + aic3x_init_3007(codec); snd_soc_write(codec, CLASSD_CTRL, 0); }
@@ -1299,6 +1345,7 @@ static int aic3x_probe(struct snd_soc_codec *codec) dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); goto err_enable; } + aic3x->power = 1;
if (aic3x->gpio_reset >= 0) { udelay(1);
On Mon, Sep 20, 2010 at 10:39:12AM +0300, Jarkko Nikula wrote:
Now all the regulators are disabled when entering into SND_SOC_BIAS_OFF and enabled when coming back to SND_SOC_BIAS_STANDBY state. Currently this runtime control happens only with suspend/resume as this patch does not change the default idle behavior.
Acked-by: Mark Brown broonie@opensource.wolfsonmicro.com
There is no need to reset the codec and perform cache sync if none of the supply regulators were not disabled. Patch registers a notifier callback for each supply and callback then sets a flag to indicate when cache sync is required.
HW writes are also needless when codec bias is off so cache_only flag is set independently of actual supply regulators state.
Signed-off-by: Jarkko Nikula jhnikula@gmail.com --- sound/soc/codecs/tlv320aic3x.c | 63 ++++++++++++++++++++++++++++++++++++++- 1 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 5f8a7c4..5356946 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -61,9 +61,18 @@ static const char *aic3x_supply_names[AIC3X_NUM_SUPPLIES] = { "DRVDD", /* ADC Analog and Output Driver Voltage */ };
+struct aic3x_priv; + +struct aic3x_disable_nb { + struct notifier_block nb; + struct aic3x_priv *aic3x; +}; + /* codec private data */ struct aic3x_priv { + struct snd_soc_codec *codec; struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES]; + struct aic3x_disable_nb disable_nb[AIC3X_NUM_SUPPLIES]; enum snd_soc_control_type control_type; struct aic3x_setup_data *setup; void *control_data; @@ -122,6 +131,8 @@ static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg, { u8 *cache = codec->reg_cache;
+ if (codec->cache_only) + return -EINVAL; if (reg >= AIC3X_CACHEREGNUM) return -1;
@@ -1052,6 +1063,26 @@ static int aic3x_init_3007(struct snd_soc_codec *codec) return 0; }
+static int aic3x_regulator_event(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct aic3x_disable_nb *disable_nb = + container_of(nb, struct aic3x_disable_nb, nb); + struct aic3x_priv *aic3x = disable_nb->aic3x; + + if (event & REGULATOR_EVENT_DISABLE) { + /* + * Put codec to reset and require cache sync as at least one + * of the supplies was disabled + */ + if (aic3x->gpio_reset >= 0) + gpio_set_value(aic3x->gpio_reset, 0); + aic3x->codec->cache_sync = 1; + } + + return 0; +} + static int aic3x_set_power(struct snd_soc_codec *codec, int power) { struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); @@ -1064,6 +1095,13 @@ static int aic3x_set_power(struct snd_soc_codec *codec, int power) if (ret) goto out; aic3x->power = 1; + /* + * Reset release and cache sync is necessary only if some + * supply was off or if there were cached writes + */ + if (!codec->cache_sync) + goto out; + if (aic3x->gpio_reset >= 0) { udelay(1); gpio_set_value(aic3x->gpio_reset, 1); @@ -1078,8 +1116,8 @@ static int aic3x_set_power(struct snd_soc_codec *codec, int power) codec->cache_sync = 0; } else { aic3x->power = 0; - if (aic3x->gpio_reset >= 0) - gpio_set_value(aic3x->gpio_reset, 0); + /* HW writes are needless when bias is off */ + codec->cache_only = 1; ret = regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); } @@ -1315,6 +1353,7 @@ static int aic3x_probe(struct snd_soc_codec *codec) int ret, i;
codec->control_data = aic3x->control_data; + aic3x->codec = codec;
ret = snd_soc_codec_set_cache_io(codec, 8, 8, aic3x->control_type); if (ret != 0) { @@ -1338,6 +1377,18 @@ static int aic3x_probe(struct snd_soc_codec *codec) dev_err(codec->dev, "Failed to request supplies: %d\n", ret); goto err_get; } + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) { + aic3x->disable_nb[i].nb.notifier_call = aic3x_regulator_event; + aic3x->disable_nb[i].aic3x = aic3x; + ret = regulator_register_notifier(aic3x->supplies[i].consumer, + &aic3x->disable_nb[i].nb); + if (ret) { + dev_err(codec->dev, + "Failed to request regulator notifier: %d\n", + ret); + goto err_notif; + } + }
ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); @@ -1372,6 +1423,10 @@ static int aic3x_probe(struct snd_soc_codec *codec) return 0;
err_enable: +err_notif: + while (i--) + regulator_unregister_notifier(aic3x->supplies[i].consumer, + &aic3x->disable_nb[i].nb); regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); err_get: if (aic3x->gpio_reset >= 0) @@ -1384,6 +1439,7 @@ err_gpio: static int aic3x_remove(struct snd_soc_codec *codec) { struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec); + int i;
aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); if (aic3x->gpio_reset >= 0) { @@ -1391,6 +1447,9 @@ static int aic3x_remove(struct snd_soc_codec *codec) gpio_free(aic3x->gpio_reset); } regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) + regulator_unregister_notifier(aic3x->supplies[i].consumer, + &aic3x->disable_nb[i].nb); regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies);
return 0;
On Mon, Sep 20, 2010 at 10:39:13AM +0300, Jarkko Nikula wrote:
There is no need to reset the codec and perform cache sync if none of the supply regulators were not disabled. Patch registers a notifier callback for each supply and callback then sets a flag to indicate when cache sync is required.
HW writes are also needless when codec bias is off so cache_only flag is set independently of actual supply regulators state.
Signed-off-by: Jarkko Nikula jhnikula@gmail.com
Acked-by: Mark Brown broonie@opensource.wolfsonmicro.com
Now codec hits the SND_SOC_BIAS_OFF also when it is idle. This is also the default state after probing and codec is left unconfigured and unpowered by default. Initialization will happen when the bias state changes and aic3x_set_power does power-up and cache sync.
Signed-off-by: Jarkko Nikula jhnikula@gmail.com --- sound/soc/codecs/tlv320aic3x.c | 20 ++------------------ 1 files changed, 2 insertions(+), 18 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 5356946..fc68779 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1341,9 +1341,6 @@ static int aic3x_init(struct snd_soc_codec *codec) snd_soc_write(codec, CLASSD_CTRL, 0); }
- /* off, with power on */ - aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; }
@@ -1354,6 +1351,7 @@ static int aic3x_probe(struct snd_soc_codec *codec)
codec->control_data = aic3x->control_data; aic3x->codec = codec; + codec->idle_bias_off = 1;
ret = snd_soc_codec_set_cache_io(codec, 8, 8, aic3x->control_type); if (ret != 0) { @@ -1390,19 +1388,7 @@ static int aic3x_probe(struct snd_soc_codec *codec) } }
- ret = regulator_bulk_enable(ARRAY_SIZE(aic3x->supplies), - aic3x->supplies); - if (ret != 0) { - dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); - goto err_enable; - } - aic3x->power = 1; - - if (aic3x->gpio_reset >= 0) { - udelay(1); - gpio_set_value(aic3x->gpio_reset, 1); - } - + codec->cache_only = 1; aic3x_init(codec);
if (aic3x->setup) { @@ -1422,7 +1408,6 @@ static int aic3x_probe(struct snd_soc_codec *codec)
return 0;
-err_enable: err_notif: while (i--) regulator_unregister_notifier(aic3x->supplies[i].consumer, @@ -1446,7 +1431,6 @@ static int aic3x_remove(struct snd_soc_codec *codec) gpio_set_value(aic3x->gpio_reset, 0); gpio_free(aic3x->gpio_reset); } - regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) regulator_unregister_notifier(aic3x->supplies[i].consumer, &aic3x->disable_nb[i].nb);
On Mon, Sep 20, 2010 at 10:39:14AM +0300, Jarkko Nikula wrote:
Now codec hits the SND_SOC_BIAS_OFF also when it is idle. This is also the default state after probing and codec is left unconfigured and unpowered by default. Initialization will happen when the bias state changes and aic3x_set_power does power-up and cache sync.
Signed-off-by: Jarkko Nikula jhnikula@gmail.com
Acked-by: Mark Brown broonie@opensource.wolfsonmicro.com
participants (3)
-
Jarkko Nikula
-
Liam Girdwood
-
Mark Brown