aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars-Peter Clausen <lars@metafoo.de>2015-03-30 15:04:48 -0400
committerMark Brown <broonie@kernel.org>2015-04-01 16:27:44 -0400
commit35afd9221b301d1959eadab2d45a2cb94dcb7d30 (patch)
tree6b0daf8eebe5a257f29d26c4abc2ac2e0014a345
parentc59e6abba9dd7bc273c3dd389ae9927d1da88f35 (diff)
ASoC: wm8753: Integrate capacitor charging into the DAPM sequence
When being powered on, either initially on probe or when resuming from suspend, the wm8971 configures the device for quick output capacitor charging. Since the charging can take a rather long time (up to multiple seconds) it is done asynchronously without blocking. A delayed work item is run once the charging is finished and the device is switched to the target bias level. This all done asynchronously to the regular DAPM sequence accessing the same data structures and registers without any looking, which can lead to race conditions. Furthermore this potentially delays the start of stream on the CODEC while the rest of the system is already up and running, meaning the first bytes of audio are lost. It also does no comply with the assumption of the DAPM core that if set_bias_level() returned successfully the device will be at the requested bias level. This patch slightly refactors things and makes sure that the caps charging is properly integrated into the DAPM sequence. When transitioning from SND_SOC_BIAS_OFF to SND_SOC_BIAS_STANDBY the part will be put into fast charging mode and a work item will be scheduled that puts it back into standby charging once the charging period has elapsed. If a playback or capture stream is started while charging is in progress the driver will now wait in SND_SOC_BIAS_PREPARE until the charging is done. This makes sure that charging is done asynchronously in the background when the chip is idle, but at the same time makes sure that playback/capture is not started before the charging is done. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Acked-by: Charles Keepax <ckeepax@opensource.wolfsonmicro.com> Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r--sound/soc/codecs/wm8753.c54
1 files changed, 24 insertions, 30 deletions
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c
index 21ca3a94fc96..176fcb1530c3 100644
--- a/sound/soc/codecs/wm8753.c
+++ b/sound/soc/codecs/wm8753.c
@@ -153,6 +153,7 @@ struct wm8753_priv {
153 unsigned int hifi_fmt; 153 unsigned int hifi_fmt;
154 154
155 int dai_func; 155 int dai_func;
156 struct delayed_work charge_work;
156}; 157};
157 158
158#define wm8753_reset(c) snd_soc_write(c, WM8753_RESET, 0) 159#define wm8753_reset(c) snd_soc_write(c, WM8753_RESET, 0)
@@ -1326,9 +1327,19 @@ static int wm8753_mute(struct snd_soc_dai *dai, int mute)
1326 return 0; 1327 return 0;
1327} 1328}
1328 1329
1330static void wm8753_charge_work(struct work_struct *work)
1331{
1332 struct wm8753_priv *wm8753 =
1333 container_of(work, struct wm8753_priv, charge_work.work);
1334
1335 /* Set to 500k */
1336 regmap_update_bits(wm8753->regmap, WM8753_PWR1, 0x0180, 0x0100);
1337}
1338
1329static int wm8753_set_bias_level(struct snd_soc_codec *codec, 1339static int wm8753_set_bias_level(struct snd_soc_codec *codec,
1330 enum snd_soc_bias_level level) 1340 enum snd_soc_bias_level level)
1331{ 1341{
1342 struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
1332 u16 pwr_reg = snd_soc_read(codec, WM8753_PWR1) & 0xfe3e; 1343 u16 pwr_reg = snd_soc_read(codec, WM8753_PWR1) & 0xfe3e;
1333 1344
1334 switch (level) { 1345 switch (level) {
@@ -1337,14 +1348,22 @@ static int wm8753_set_bias_level(struct snd_soc_codec *codec,
1337 snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x00c0); 1348 snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x00c0);
1338 break; 1349 break;
1339 case SND_SOC_BIAS_PREPARE: 1350 case SND_SOC_BIAS_PREPARE:
1340 /* set vmid to 5k for quick power up */ 1351 /* Wait until fully charged */
1341 snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x01c1); 1352 flush_delayed_work(&wm8753->charge_work);
1342 break; 1353 break;
1343 case SND_SOC_BIAS_STANDBY: 1354 case SND_SOC_BIAS_STANDBY:
1344 /* mute dac and set vmid to 500k, enable VREF */ 1355 if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
1345 snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x0141); 1356 /* set vmid to 5k for quick power up */
1357 snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x01c1);
1358 schedule_delayed_work(&wm8753->charge_work,
1359 msecs_to_jiffies(caps_charge));
1360 } else {
1361 /* mute dac and set vmid to 500k, enable VREF */
1362 snd_soc_write(codec, WM8753_PWR1, pwr_reg | 0x0141);
1363 }
1346 break; 1364 break;
1347 case SND_SOC_BIAS_OFF: 1365 case SND_SOC_BIAS_OFF:
1366 cancel_delayed_work_sync(&wm8753->charge_work);
1348 snd_soc_write(codec, WM8753_PWR1, 0x0001); 1367 snd_soc_write(codec, WM8753_PWR1, 0x0001);
1349 break; 1368 break;
1350 } 1369 }
@@ -1428,15 +1447,6 @@ static struct snd_soc_dai_driver wm8753_dai[] = {
1428}, 1447},
1429}; 1448};
1430 1449
1431static void wm8753_work(struct work_struct *work)
1432{
1433 struct snd_soc_dapm_context *dapm =
1434 container_of(work, struct snd_soc_dapm_context,
1435 delayed_work.work);
1436 struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
1437 wm8753_set_bias_level(codec, dapm->bias_level);
1438}
1439
1440static int wm8753_suspend(struct snd_soc_codec *codec) 1450static int wm8753_suspend(struct snd_soc_codec *codec)
1441{ 1451{
1442 wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); 1452 wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
@@ -1450,16 +1460,6 @@ static int wm8753_resume(struct snd_soc_codec *codec)
1450 regcache_sync(wm8753->regmap); 1460 regcache_sync(wm8753->regmap);
1451 1461
1452 wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 1462 wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
1453
1454 /* charge wm8753 caps */
1455 if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) {
1456 wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
1457 codec->dapm.bias_level = SND_SOC_BIAS_ON;
1458 queue_delayed_work(system_power_efficient_wq,
1459 &codec->dapm.delayed_work,
1460 msecs_to_jiffies(caps_charge));
1461 }
1462
1463 return 0; 1463 return 0;
1464} 1464}
1465 1465
@@ -1468,7 +1468,7 @@ static int wm8753_probe(struct snd_soc_codec *codec)
1468 struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec); 1468 struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
1469 int ret; 1469 int ret;
1470 1470
1471 INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8753_work); 1471 INIT_DELAYED_WORK(&wm8753->charge_work, wm8753_charge_work);
1472 1472
1473 ret = wm8753_reset(codec); 1473 ret = wm8753_reset(codec);
1474 if (ret < 0) { 1474 if (ret < 0) {
@@ -1479,11 +1479,6 @@ static int wm8753_probe(struct snd_soc_codec *codec)
1479 wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 1479 wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
1480 wm8753->dai_func = 0; 1480 wm8753->dai_func = 0;
1481 1481
1482 /* charge output caps */
1483 wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
1484 schedule_delayed_work(&codec->dapm.delayed_work,
1485 msecs_to_jiffies(caps_charge));
1486
1487 /* set the update bits */ 1482 /* set the update bits */
1488 snd_soc_update_bits(codec, WM8753_LDAC, 0x0100, 0x0100); 1483 snd_soc_update_bits(codec, WM8753_LDAC, 0x0100, 0x0100);
1489 snd_soc_update_bits(codec, WM8753_RDAC, 0x0100, 0x0100); 1484 snd_soc_update_bits(codec, WM8753_RDAC, 0x0100, 0x0100);
@@ -1502,7 +1497,6 @@ static int wm8753_probe(struct snd_soc_codec *codec)
1502/* power down chip */ 1497/* power down chip */
1503static int wm8753_remove(struct snd_soc_codec *codec) 1498static int wm8753_remove(struct snd_soc_codec *codec)
1504{ 1499{
1505 flush_delayed_work(&codec->dapm.delayed_work);
1506 wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); 1500 wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF);
1507 1501
1508 return 0; 1502 return 0;