summaryrefslogtreecommitdiff
path: root/sound/soc/codecs
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r--sound/soc/codecs/sgtl5000.c96
-rw-r--r--sound/soc/codecs/sgtl5000.h4
2 files changed, 100 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