aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars-Peter Clausen <lars@metafoo.de>2015-03-30 15:04:46 -0400
committerMark Brown <broonie@kernel.org>2015-04-01 16:27:36 -0400
commit643518403c3fdecd40d9edfd50f4bafcebeda79b (patch)
treef20d1acd286d69cf171be8c1dfc263d9fd71fb54
parentab87ce1d9bb0501fccfc00d5e5ce7c16cd2bcc3e (diff)
ASoC: wm8971: 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/wm8971.c71
1 files changed, 31 insertions, 40 deletions
diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c
index 44baacd33252..4ab034d48474 100644
--- a/sound/soc/codecs/wm8971.c
+++ b/sound/soc/codecs/wm8971.c
@@ -34,6 +34,8 @@
34/* codec private data */ 34/* codec private data */
35struct wm8971_priv { 35struct wm8971_priv {
36 unsigned int sysclk; 36 unsigned int sysclk;
37 struct delayed_work charge_work;
38 struct regmap *regmap;
37}; 39};
38 40
39/* 41/*
@@ -550,9 +552,19 @@ static int wm8971_mute(struct snd_soc_dai *dai, int mute)
550 return 0; 552 return 0;
551} 553}
552 554
555static void wm8971_charge_work(struct work_struct *work)
556{
557 struct wm8971_priv *wm8971 =
558 container_of(work, struct wm8971_priv, charge_work.work);
559
560 /* Set to 500k */
561 regmap_update_bits(wm8971->regmap, WM8971_PWR1, 0x0180, 0x0100);
562}
563
553static int wm8971_set_bias_level(struct snd_soc_codec *codec, 564static int wm8971_set_bias_level(struct snd_soc_codec *codec,
554 enum snd_soc_bias_level level) 565 enum snd_soc_bias_level level)
555{ 566{
567 struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec);
556 u16 pwr_reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; 568 u16 pwr_reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
557 569
558 switch (level) { 570 switch (level) {
@@ -561,15 +573,24 @@ static int wm8971_set_bias_level(struct snd_soc_codec *codec,
561 snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x00c1); 573 snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x00c1);
562 break; 574 break;
563 case SND_SOC_BIAS_PREPARE: 575 case SND_SOC_BIAS_PREPARE:
576 /* Wait until fully charged */
577 flush_delayed_work(&wm8971->charge_work);
564 break; 578 break;
565 case SND_SOC_BIAS_STANDBY: 579 case SND_SOC_BIAS_STANDBY:
566 if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) 580 if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
567 snd_soc_cache_sync(codec); 581 snd_soc_cache_sync(codec);
582 /* charge output caps - set vmid to 5k for quick power up */
583 snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x01c0);
584 queue_delayed_work(system_power_efficient_wq,
585 &wm8971->charge_work, msecs_to_jiffies(1000));
586 } else {
587 /* mute dac and set vmid to 500k, enable VREF */
588 snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x0140);
589 }
568 590
569 /* mute dac and set vmid to 500k, enable VREF */
570 snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x0140);
571 break; 591 break;
572 case SND_SOC_BIAS_OFF: 592 case SND_SOC_BIAS_OFF:
593 cancel_delayed_work_sync(&wm8971->charge_work);
573 snd_soc_write(codec, WM8971_PWR1, 0x0001); 594 snd_soc_write(codec, WM8971_PWR1, 0x0001);
574 break; 595 break;
575 } 596 }
@@ -608,15 +629,6 @@ static struct snd_soc_dai_driver wm8971_dai = {
608 .ops = &wm8971_dai_ops, 629 .ops = &wm8971_dai_ops,
609}; 630};
610 631
611static void wm8971_work(struct work_struct *work)
612{
613 struct snd_soc_dapm_context *dapm =
614 container_of(work, struct snd_soc_dapm_context,
615 delayed_work.work);
616 struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm);
617 wm8971_set_bias_level(codec, codec->dapm.bias_level);
618}
619
620static int wm8971_suspend(struct snd_soc_codec *codec) 632static int wm8971_suspend(struct snd_soc_codec *codec)
621{ 633{
622 wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); 634 wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF);
@@ -625,39 +637,19 @@ static int wm8971_suspend(struct snd_soc_codec *codec)
625 637
626static int wm8971_resume(struct snd_soc_codec *codec) 638static int wm8971_resume(struct snd_soc_codec *codec)
627{ 639{
628 u16 reg;
629
630 wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY); 640 wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
631
632 /* charge wm8971 caps */
633 if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) {
634 reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
635 snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0);
636 codec->dapm.bias_level = SND_SOC_BIAS_ON;
637 queue_delayed_work(system_power_efficient_wq,
638 &codec->dapm.delayed_work,
639 msecs_to_jiffies(1000));
640 }
641
642 return 0; 641 return 0;
643} 642}
644 643
645static int wm8971_probe(struct snd_soc_codec *codec) 644static int wm8971_probe(struct snd_soc_codec *codec)
646{ 645{
647 int ret = 0; 646 struct wm8971_priv *wm8971 = snd_soc_codec_get_drvdata(codec);
648 u16 reg;
649 647
650 INIT_DELAYED_WORK(&codec->dapm.delayed_work, wm8971_work); 648 INIT_DELAYED_WORK(&wm8971->charge_work, wm8971_charge_work);
651 649
652 wm8971_reset(codec); 650 wm8971_reset(codec);
653 651
654 /* charge output caps - set vmid to 5k for quick power up */ 652 wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
655 reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e;
656 snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0);
657 codec->dapm.bias_level = SND_SOC_BIAS_STANDBY;
658 queue_delayed_work(system_power_efficient_wq,
659 &codec->dapm.delayed_work,
660 msecs_to_jiffies(1000));
661 653
662 /* set the update bits */ 654 /* set the update bits */
663 snd_soc_update_bits(codec, WM8971_LDAC, 0x0100, 0x0100); 655 snd_soc_update_bits(codec, WM8971_LDAC, 0x0100, 0x0100);
@@ -669,7 +661,7 @@ static int wm8971_probe(struct snd_soc_codec *codec)
669 snd_soc_update_bits(codec, WM8971_LINVOL, 0x0100, 0x0100); 661 snd_soc_update_bits(codec, WM8971_LINVOL, 0x0100, 0x0100);
670 snd_soc_update_bits(codec, WM8971_RINVOL, 0x0100, 0x0100); 662 snd_soc_update_bits(codec, WM8971_RINVOL, 0x0100, 0x0100);
671 663
672 return ret; 664 return 0;
673} 665}
674 666
675 667
@@ -710,7 +702,6 @@ static int wm8971_i2c_probe(struct i2c_client *i2c,
710 const struct i2c_device_id *id) 702 const struct i2c_device_id *id)
711{ 703{
712 struct wm8971_priv *wm8971; 704 struct wm8971_priv *wm8971;
713 struct regmap *regmap;
714 int ret; 705 int ret;
715 706
716 wm8971 = devm_kzalloc(&i2c->dev, sizeof(struct wm8971_priv), 707 wm8971 = devm_kzalloc(&i2c->dev, sizeof(struct wm8971_priv),
@@ -718,9 +709,9 @@ static int wm8971_i2c_probe(struct i2c_client *i2c,
718 if (wm8971 == NULL) 709 if (wm8971 == NULL)
719 return -ENOMEM; 710 return -ENOMEM;
720 711
721 regmap = devm_regmap_init_i2c(i2c, &wm8971_regmap); 712 wm8971->regmap = devm_regmap_init_i2c(i2c, &wm8971_regmap);
722 if (IS_ERR(regmap)) 713 if (IS_ERR(wm8971->regmap))
723 return PTR_ERR(regmap); 714 return PTR_ERR(wm8971->regmap);
724 715
725 i2c_set_clientdata(i2c, wm8971); 716 i2c_set_clientdata(i2c, wm8971);
726 717