Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: wm8804: Enable runtime PM

Currently both the oscillator and the PLL are powered up in
set_bias_level. This can be problematic when using output clocks from
the wm8804 for other devices. The snd_soc_codec_set_pll API defines that
a clock should be available once the call returns, however, with all the
clocking controlled in set_bias_level this is not currently the case.

This patch enables pm_runtime for the wm8804, enabling both the
regulators and the oscillator when the chip resumes, and enabling the
PLL in the snd_soc_codec_set_pll call. Naturally the enabling the PLL
will also cause the chip to resume.

Signed-off-by: Charles Keepax <ckeepax@opensource.wolfsonmicro.com>
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Charles Keepax and committed by
Mark Brown
1a60667f 5631f187

+62 -50
+1
sound/soc/codecs/wm8804-i2c.c
··· 50 50 .driver = { 51 51 .name = "wm8804", 52 52 .owner = THIS_MODULE, 53 + .pm = &wm8804_pm, 53 54 .of_match_table = wm8804_of_match, 54 55 }, 55 56 .probe = wm8804_i2c_probe,
+1
sound/soc/codecs/wm8804-spi.c
··· 43 43 .driver = { 44 44 .name = "wm8804", 45 45 .owner = THIS_MODULE, 46 + .pm = &wm8804_pm, 46 47 .of_match_table = wm8804_of_match, 47 48 }, 48 49 .probe = wm8804_spi_probe,
+59 -50
sound/soc/codecs/wm8804.c
··· 16 16 #include <linux/gpio/consumer.h> 17 17 #include <linux/delay.h> 18 18 #include <linux/pm.h> 19 + #include <linux/pm_runtime.h> 19 20 #include <linux/of_device.h> 20 21 #include <linux/regulator/consumer.h> 21 22 #include <linux/slab.h> ··· 60 59 }; 61 60 62 61 struct wm8804_priv { 62 + struct device *dev; 63 63 struct regmap *regmap; 64 64 struct regulator_bulk_data supplies[WM8804_NUM_SUPPLIES]; 65 65 struct notifier_block disable_nb[WM8804_NUM_SUPPLIES]; ··· 405 403 int source, unsigned int freq_in, 406 404 unsigned int freq_out) 407 405 { 408 - struct snd_soc_codec *codec; 406 + struct snd_soc_codec *codec = dai->codec; 407 + struct wm8804_priv *wm8804 = snd_soc_codec_get_drvdata(codec); 408 + bool change; 409 409 410 - codec = dai->codec; 411 410 if (!freq_in || !freq_out) { 412 411 /* disable the PLL */ 413 - snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1); 414 - return 0; 412 + regmap_update_bits_check(wm8804->regmap, WM8804_PWRDN, 413 + 0x1, 0x1, &change); 414 + if (change) 415 + pm_runtime_put(wm8804->dev); 415 416 } else { 416 417 int ret; 417 418 struct pll_div pll_div; 418 - struct wm8804_priv *wm8804; 419 - 420 - wm8804 = snd_soc_codec_get_drvdata(codec); 421 419 422 420 ret = pll_factors(&pll_div, freq_out, freq_in, 423 421 wm8804->mclk_div); ··· 425 423 return ret; 426 424 427 425 /* power down the PLL before reprogramming it */ 428 - snd_soc_update_bits(codec, WM8804_PWRDN, 0x1, 0x1); 426 + regmap_update_bits_check(wm8804->regmap, WM8804_PWRDN, 427 + 0x1, 0x1, &change); 428 + if (!change) 429 + pm_runtime_get_sync(wm8804->dev); 429 430 430 431 /* set PLLN and PRESCALE */ 431 432 snd_soc_update_bits(codec, WM8804_PLL4, 0xf | 0x10, ··· 506 501 return 0; 507 502 } 508 503 509 - static int wm8804_set_bias_level(struct snd_soc_codec *codec, 510 - enum snd_soc_bias_level level) 511 - { 512 - int ret; 513 - struct wm8804_priv *wm8804; 514 - 515 - wm8804 = snd_soc_codec_get_drvdata(codec); 516 - switch (level) { 517 - case SND_SOC_BIAS_ON: 518 - break; 519 - case SND_SOC_BIAS_PREPARE: 520 - /* power up the OSC and the PLL */ 521 - snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0); 522 - break; 523 - case SND_SOC_BIAS_STANDBY: 524 - if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { 525 - ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies), 526 - wm8804->supplies); 527 - if (ret) { 528 - dev_err(codec->dev, 529 - "Failed to enable supplies: %d\n", 530 - ret); 531 - return ret; 532 - } 533 - regcache_sync(wm8804->regmap); 534 - } 535 - /* power down the OSC and the PLL */ 536 - snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9); 537 - break; 538 - case SND_SOC_BIAS_OFF: 539 - /* power down the OSC and the PLL */ 540 - snd_soc_update_bits(codec, WM8804_PWRDN, 0x9, 0x9); 541 - regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), 542 - wm8804->supplies); 543 - break; 544 - } 545 - 546 - codec->dapm.bias_level = level; 547 - return 0; 548 - } 549 - 550 504 static const struct snd_soc_dai_ops wm8804_dai_ops = { 551 505 .hw_params = wm8804_hw_params, 552 506 .set_fmt = wm8804_set_fmt, ··· 543 579 }; 544 580 545 581 static const struct snd_soc_codec_driver soc_codec_dev_wm8804 = { 546 - .set_bias_level = wm8804_set_bias_level, 547 582 .idle_bias_off = true, 548 583 549 584 .dapm_widgets = wm8804_dapm_widgets, ··· 576 613 577 614 dev_set_drvdata(dev, wm8804); 578 615 616 + wm8804->dev = dev; 579 617 wm8804->regmap = regmap; 580 618 581 619 wm8804->reset = devm_gpiod_get_optional(dev, "wlf,reset", ··· 667 703 goto err_reg_enable; 668 704 } 669 705 706 + pm_runtime_set_active(dev); 707 + pm_runtime_enable(dev); 708 + pm_runtime_idle(dev); 709 + 670 710 return 0; 671 711 672 712 err_reg_enable: ··· 681 713 682 714 void wm8804_remove(struct device *dev) 683 715 { 716 + pm_runtime_disable(dev); 684 717 snd_soc_unregister_codec(dev); 685 718 } 686 719 EXPORT_SYMBOL_GPL(wm8804_remove); 720 + 721 + #if IS_ENABLED(CONFIG_PM) 722 + static int wm8804_runtime_resume(struct device *dev) 723 + { 724 + struct wm8804_priv *wm8804 = dev_get_drvdata(dev); 725 + int ret; 726 + 727 + ret = regulator_bulk_enable(ARRAY_SIZE(wm8804->supplies), 728 + wm8804->supplies); 729 + if (ret) { 730 + dev_err(wm8804->dev, "Failed to enable supplies: %d\n", ret); 731 + return ret; 732 + } 733 + 734 + regcache_sync(wm8804->regmap); 735 + 736 + /* Power up OSCCLK */ 737 + regmap_update_bits(wm8804->regmap, WM8804_PWRDN, 0x8, 0x0); 738 + 739 + return 0; 740 + } 741 + 742 + static int wm8804_runtime_suspend(struct device *dev) 743 + { 744 + struct wm8804_priv *wm8804 = dev_get_drvdata(dev); 745 + 746 + /* Power down OSCCLK */ 747 + regmap_update_bits(wm8804->regmap, WM8804_PWRDN, 0x8, 0x8); 748 + 749 + regulator_bulk_disable(ARRAY_SIZE(wm8804->supplies), 750 + wm8804->supplies); 751 + 752 + return 0; 753 + } 754 + #endif 755 + 756 + const struct dev_pm_ops wm8804_pm = { 757 + SET_RUNTIME_PM_OPS(wm8804_runtime_suspend, wm8804_runtime_resume, NULL) 758 + }; 759 + EXPORT_SYMBOL_GPL(wm8804_pm); 687 760 688 761 MODULE_DESCRIPTION("ASoC WM8804 driver"); 689 762 MODULE_AUTHOR("Dimitris Papastamos <dp@opensource.wolfsonmicro.com>");
+1
sound/soc/codecs/wm8804.h
··· 65 65 #define WM8804_MCLKDIV_128FS 1 66 66 67 67 extern const struct regmap_config wm8804_regmap_config; 68 + extern const struct dev_pm_ops wm8804_pm; 68 69 69 70 int wm8804_probe(struct device *dev, struct regmap *regmap); 70 71 void wm8804_remove(struct device *dev);