summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorAlan Tull <r80115@freescale.com>2010-10-06 15:04:28 -0500
committerAlan Tull <alan.tull@freescale.com>2010-10-15 11:23:52 -0500
commita10c73bc908a89540fab88aaf5d59751cc6ec177 (patch)
tree88801e0dfa542b4e95c2e1777beef895961005e0 /sound
parent19c511da7cc64d552483d5f5ca4872c607408d44 (diff)
ENGR00132525-3 sgtl5000: audio clock gating
Turn off audio clock when possible. Empirical data says that we need to leave the clocks on for 300 mSec after all codec writes are done so schedule work to do that. Signed-off-by: Alan Tull <alan.tull@freescale.com> audio clock gating debug
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/sgtl5000.c96
-rw-r--r--sound/soc/codecs/sgtl5000.h4
-rw-r--r--sound/soc/imx/imx-3stack-sgtl5000.c11
3 files changed, 111 insertions, 0 deletions
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
index 178678d03d74..6e4bf1e3cd63 100644
--- a/sound/soc/codecs/sgtl5000.c
+++ b/sound/soc/codecs/sgtl5000.c
@@ -38,6 +38,10 @@ struct sgtl5000_priv {
int capture_channels;
int playback_active;
int capture_active;
+ int clock_on; /* clock enable status */
+ int need_clk_for_access; /* need clock on because doing access */
+ int need_clk_for_bias; /* need clock on due to bias level */
+ int (*clock_enable) (int enable);
struct regulator *reg_vddio;
struct regulator *reg_vdda;
struct regulator *reg_vddd;
@@ -54,6 +58,29 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
#define SGTL5000_MAX_CACHED_REG SGTL5000_CHIP_SHORT_CTRL
static u16 sgtl5000_regs[(SGTL5000_MAX_CACHED_REG >> 1) + 1];
+/*
+ * Schedule clock to be turned off or turn clock on.
+ */
+static void sgtl5000_clock_gating(struct snd_soc_codec *codec, int enable)
+{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ if (sgtl5000->clock_enable == NULL)
+ return;
+
+ if (enable == 0) {
+ if (!sgtl5000->need_clk_for_access &&
+ !sgtl5000->need_clk_for_bias)
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(300));
+ } else {
+ if (!sgtl5000->clock_on) {
+ sgtl5000->clock_enable(1);
+ sgtl5000->clock_on = 1;
+ }
+ }
+}
+
static unsigned int sgtl5000_read_reg_cache(struct snd_soc_codec *codec,
unsigned int reg)
{
@@ -68,6 +95,7 @@ static unsigned int sgtl5000_read_reg_cache(struct snd_soc_codec *codec,
static unsigned int sgtl5000_hw_read(struct snd_soc_codec *codec,
unsigned int reg)
{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
struct i2c_client *client = codec->control_data;
int i2c_ret;
u16 value;
@@ -79,9 +107,13 @@ static unsigned int sgtl5000_hw_read(struct snd_soc_codec *codec,
{addr, flags | I2C_M_RD, 2, buf1},
};
+ sgtl5000->need_clk_for_access = 1;
+ sgtl5000_clock_gating(codec, 1);
buf0[0] = (reg & 0xff00) >> 8;
buf0[1] = reg & 0xff;
i2c_ret = i2c_transfer(client->adapter, msg, 2);
+ sgtl5000->need_clk_for_access = 0;
+ sgtl5000_clock_gating(codec, 0);
if (i2c_ret < 0) {
pr_err("%s: read reg error : Reg 0x%02x\n", __func__, reg);
return 0;
@@ -116,6 +148,7 @@ static inline void sgtl5000_write_reg_cache(struct snd_soc_codec *codec,
static int sgtl5000_write(struct snd_soc_codec *codec, unsigned int reg,
unsigned int value)
{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
struct i2c_client *client = codec->control_data;
u16 addr = client->addr;
u16 flags = client->flags;
@@ -123,6 +156,8 @@ static int sgtl5000_write(struct snd_soc_codec *codec, unsigned int reg,
int i2c_ret;
struct i2c_msg msg = { addr, flags, 4, buf };
+ sgtl5000->need_clk_for_access = 1;
+ sgtl5000_clock_gating(codec, 1);
sgtl5000_write_reg_cache(codec, reg, value);
pr_debug("w r:%02x,v:%04x\n", reg, value);
buf[0] = (reg & 0xff00) >> 8;
@@ -131,6 +166,8 @@ static int sgtl5000_write(struct snd_soc_codec *codec, unsigned int reg,
buf[3] = value & 0xff;
i2c_ret = i2c_transfer(client->adapter, &msg, 1);
+ sgtl5000->need_clk_for_access = 0;
+ sgtl5000_clock_gating(codec, 0);
if (i2c_ret < 0) {
pr_err("%s: write reg error : Reg 0x%02x = 0x%04x\n",
__func__, reg, value);
@@ -743,6 +780,7 @@ static void sgtl5000_mic_bias(struct snd_soc_codec *codec, int enable)
static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
u16 reg, ana_pwr;
int delay = 0;
pr_debug("dapm level %d\n", level);
@@ -761,6 +799,10 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
break;
case SND_SOC_BIAS_PREPARE: /* partial On */
+ /* Keep clock on while in PREPARE or BIAS_ON state */
+ sgtl5000->need_clk_for_bias = 1;
+ sgtl5000_clock_gating(codec, 1);
+
if (codec->bias_level == SND_SOC_BIAS_PREPARE)
break;
@@ -812,9 +854,15 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
reg &= ~SGTL5000_DAC_EN;
sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
+ sgtl5000->need_clk_for_bias = 0;
+ sgtl5000_clock_gating(codec, 0);
+
break;
case SND_SOC_BIAS_OFF: /* Off, without power */
+ sgtl5000->need_clk_for_bias = 1;
+ sgtl5000_clock_gating(codec, 1);
+
/* must power down hp/line out after vag & dac to
avoid pops. */
reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
@@ -837,6 +885,10 @@ static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
/* save ANA POWER register value for resume */
sgtl5000_write_reg_cache(codec, SGTL5000_CHIP_ANA_POWER,
ana_pwr);
+
+ sgtl5000->need_clk_for_bias = 0;
+ sgtl5000_clock_gating(codec, 0);
+
break;
}
codec->bias_level = level;
@@ -887,6 +939,23 @@ struct snd_soc_dai sgtl5000_dai = {
};
EXPORT_SYMBOL_GPL(sgtl5000_dai);
+/*
+ * Delayed work that turns off the audio clock after a delay.
+ */
+static void sgtl5000_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec =
+ container_of(work, struct snd_soc_codec, delayed_work.work);
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ if (!sgtl5000->need_clk_for_access &&
+ !sgtl5000->need_clk_for_bias &&
+ sgtl5000->clock_on) {
+ sgtl5000->clock_enable(0);
+ sgtl5000->clock_on = 0;
+ }
+}
+
static int sgtl5000_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
@@ -936,12 +1005,19 @@ static int sgtl5000_probe(struct platform_device *pdev)
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec = sgtl5000_codec;
struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ struct sgtl5000_setup_data *setup = socdev->codec_data;
u16 reg, ana_pwr, lreg_ctrl, ref_ctrl, lo_ctrl, short_ctrl, sss;
int vag;
int ret = 0;
socdev->card->codec = sgtl5000_codec;
+ if ((setup != NULL) && (setup->clock_enable != NULL)) {
+ sgtl5000->clock_enable = setup->clock_enable;
+ sgtl5000->need_clk_for_bias = 1;
+ INIT_DELAYED_WORK(&codec->delayed_work, sgtl5000_work);
+ }
+
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
@@ -1077,6 +1153,25 @@ static int sgtl5000_probe(struct platform_device *pdev)
return 0;
}
+/*
+ * This function forces any delayed work to be queued and run.
+ */
+static int run_delayed_work(struct delayed_work *dwork)
+{
+ int ret;
+
+ /* cancel any work waiting to be queued. */
+ ret = cancel_delayed_work(dwork);
+
+ /* if there was any work waiting then we run it now and
+ * wait for it's completion */
+ if (ret) {
+ schedule_delayed_work(dwork, 0);
+ flush_scheduled_work();
+ }
+ return ret;
+}
+
/* power down chip */
static int sgtl5000_remove(struct platform_device *pdev)
{
@@ -1085,6 +1180,7 @@ static int sgtl5000_remove(struct platform_device *pdev)
if (codec->control_data)
sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ run_delayed_work(&codec->delayed_work);
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h
index b1a755e29d57..49b444cd070d 100644
--- a/sound/soc/codecs/sgtl5000.h
+++ b/sound/soc/codecs/sgtl5000.h
@@ -403,4 +403,8 @@ extern struct snd_soc_codec_device soc_codec_dev_sgtl5000;
#define SGTL5000_SYSCLK 0x00
#define SGTL5000_LRCLK 0x01
+struct sgtl5000_setup_data {
+ int (*clock_enable) (int enable);
+};
+
#endif
diff --git a/sound/soc/imx/imx-3stack-sgtl5000.c b/sound/soc/imx/imx-3stack-sgtl5000.c
index e73ec62f3251..c1b7d401fbcd 100644
--- a/sound/soc/imx/imx-3stack-sgtl5000.c
+++ b/sound/soc/imx/imx-3stack-sgtl5000.c
@@ -25,6 +25,7 @@
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/fsl_devices.h>
+#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -573,6 +574,8 @@ static int __devinit imx_3stack_sgtl5000_probe(struct platform_device *pdev)
struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
struct imx_3stack_priv *priv = &card_priv;
struct snd_soc_dai *sgtl5000_cpu_dai;
+ struct sgtl5000_setup_data *setup;
+
int ret = 0;
priv->pdev = pdev;
@@ -616,6 +619,14 @@ static int __devinit imx_3stack_sgtl5000_probe(struct platform_device *pdev)
goto err_card_reg;
}
+ setup = kzalloc(sizeof(struct sgtl5000_setup_data), GFP_KERNEL);
+ if (!setup) {
+ pr_err("%s: kzalloc sgtl5000_setup_data failed\n", __func__);
+ goto err_card_reg;
+ }
+ setup->clock_enable = plat->clock_enable;
+ imx_3stack_snd_devdata.codec_data = setup;
+
sgtl5000_jack_func = 1;
sgtl5000_spk_func = 1;
sgtl5000_line_in_func = 0;