summaryrefslogtreecommitdiff
path: root/sound/soc/codecs/sgtl5000.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs/sgtl5000.c')
-rw-r--r--sound/soc/codecs/sgtl5000.c1394
1 files changed, 1394 insertions, 0 deletions
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
new file mode 100644
index 000000000000..bb94c65244f1
--- /dev/null
+++ b/sound/soc/codecs/sgtl5000.c
@@ -0,0 +1,1394 @@
+/*
+ * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver
+ *
+ * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <mach/hardware.h>
+
+#include "sgtl5000.h"
+
+struct sgtl5000_priv {
+ int sysclk;
+ int master;
+ int fmt;
+ int rev;
+ int lrclk;
+ 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;
+ int vddio; /* voltage of VDDIO (mv) */
+ int vdda; /* voltage of vdda (mv) */
+ int vddd; /* voltage of vddd (mv), 0 if not connected */
+ struct snd_pcm_substream *master_substream;
+ struct snd_pcm_substream *slave_substream;
+};
+
+static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level);
+
+#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)
+{
+ u16 *cache = codec->reg_cache;
+ unsigned int offset = reg >> 1;
+ if (offset >= ARRAY_SIZE(sgtl5000_regs))
+ return -EINVAL;
+ pr_debug("r r:%02x,v:%04x\n", reg, cache[offset]);
+ return cache[offset];
+}
+
+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;
+ u8 buf0[2], buf1[2];
+ u16 addr = client->addr;
+ u16 flags = client->flags;
+ struct i2c_msg msg[2] = {
+ {addr, flags, 2, buf0},
+ {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;
+ }
+
+ value = buf1[0] << 8 | buf1[1];
+
+ pr_debug("r r:%02x,v:%04x\n", reg, value);
+ return value;
+}
+
+static unsigned int sgtl5000_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ if ((reg == SGTL5000_CHIP_ID) ||
+ (reg == SGTL5000_CHIP_ADCDAC_CTRL) ||
+ (reg == SGTL5000_CHIP_ANA_STATUS) ||
+ (reg > SGTL5000_MAX_CACHED_REG))
+ return sgtl5000_hw_read(codec, reg);
+ else
+ return sgtl5000_read_reg_cache(codec, reg);
+}
+
+static inline void sgtl5000_write_reg_cache(struct snd_soc_codec *codec,
+ u16 reg, unsigned int value)
+{
+ u16 *cache = codec->reg_cache;
+ unsigned int offset = reg >> 1;
+ if (offset < ARRAY_SIZE(sgtl5000_regs))
+ cache[offset] = value;
+}
+
+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;
+ u8 buf[4];
+ 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;
+ buf[1] = reg & 0xff;
+ buf[2] = (value & 0xff00) >> 8;
+ 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);
+ return -EIO;
+ }
+
+ return i2c_ret;
+}
+
+static void sgtl5000_sync_reg_cache(struct snd_soc_codec *codec)
+{
+ int reg;
+ for (reg = 0; reg <= SGTL5000_MAX_CACHED_REG; reg += 2)
+ sgtl5000_write_reg_cache(codec, reg,
+ sgtl5000_hw_read(codec, reg));
+}
+
+static int sgtl5000_restore_reg(struct snd_soc_codec *codec, unsigned int reg)
+{
+ unsigned int cached_val, hw_val;
+
+ cached_val = sgtl5000_read_reg_cache(codec, reg);
+ hw_val = sgtl5000_hw_read(codec, reg);
+
+ if (hw_val != cached_val)
+ return sgtl5000_write(codec, reg, cached_val);
+
+ return 0;
+}
+
+static int all_reg[] = {
+ SGTL5000_CHIP_ID,
+ SGTL5000_CHIP_DIG_POWER,
+ SGTL5000_CHIP_CLK_CTRL,
+ SGTL5000_CHIP_I2S_CTRL,
+ SGTL5000_CHIP_SSS_CTRL,
+ SGTL5000_CHIP_ADCDAC_CTRL,
+ SGTL5000_CHIP_DAC_VOL,
+ SGTL5000_CHIP_PAD_STRENGTH,
+ SGTL5000_CHIP_ANA_ADC_CTRL,
+ SGTL5000_CHIP_ANA_HP_CTRL,
+ SGTL5000_CHIP_ANA_CTRL,
+ SGTL5000_CHIP_LINREG_CTRL,
+ SGTL5000_CHIP_REF_CTRL,
+ SGTL5000_CHIP_MIC_CTRL,
+ SGTL5000_CHIP_LINE_OUT_CTRL,
+ SGTL5000_CHIP_LINE_OUT_VOL,
+ SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_CHIP_PLL_CTRL,
+ SGTL5000_CHIP_CLK_TOP_CTRL,
+ SGTL5000_CHIP_ANA_STATUS,
+ SGTL5000_CHIP_SHORT_CTRL,
+};
+
+#ifdef DEBUG
+static void dump_reg(struct snd_soc_codec *codec)
+{
+ int i, reg;
+ printk(KERN_DEBUG "dump begin\n");
+ for (i = 0; i < 21; i++) {
+ reg = sgtl5000_read(codec, all_reg[i]);
+ printk(KERN_DEBUG "d r %04x, v %04x\n", all_reg[i], reg);
+ }
+ printk(KERN_DEBUG "dump end\n");
+}
+#else
+static void dump_reg(struct snd_soc_codec *codec)
+{
+}
+#endif
+
+static const char *adc_mux_text[] = {
+ "MIC_IN", "LINE_IN"
+};
+
+static const char *dac_mux_text[] = {
+ "DAC", "LINE_IN"
+};
+
+static const struct soc_enum adc_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 2, 2, adc_mux_text);
+
+static const struct soc_enum dac_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, dac_mux_text);
+
+static const struct snd_kcontrol_new adc_mux =
+SOC_DAPM_ENUM("ADC Mux", adc_enum);
+
+static const struct snd_kcontrol_new dac_mux =
+SOC_DAPM_ENUM("DAC Mux", dac_enum);
+
+static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("LINE_IN"),
+ SND_SOC_DAPM_INPUT("MIC_IN"),
+
+ SND_SOC_DAPM_OUTPUT("HP_OUT"),
+ SND_SOC_DAPM_OUTPUT("LINE_OUT"),
+
+ SND_SOC_DAPM_PGA("HP", SGTL5000_CHIP_ANA_CTRL, 4, 1, NULL, 0),
+ SND_SOC_DAPM_PGA("LO", SGTL5000_CHIP_ANA_CTRL, 8, 1, NULL, 0),
+
+ SND_SOC_DAPM_MUX("ADC Mux", SND_SOC_NOPM, 0, 0, &adc_mux),
+ SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, &dac_mux),
+
+ SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_DIG_POWER, 6, 0),
+ SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"ADC Mux", "LINE_IN", "LINE_IN"},
+ {"ADC Mux", "MIC_IN", "MIC_IN"},
+ {"ADC", NULL, "ADC Mux"},
+ {"DAC Mux", "DAC", "DAC"},
+ {"DAC Mux", "LINE_IN", "LINE_IN"},
+ {"LO", NULL, "DAC"},
+ {"HP", NULL, "DAC Mux"},
+ {"LINE_OUT", NULL, "LO"},
+ {"HP_OUT", NULL, "HP"},
+};
+
+static int sgtl5000_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, sgtl5000_dapm_widgets,
+ ARRAY_SIZE(sgtl5000_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int dac_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xfc - 0x3c;
+ return 0;
+}
+
+static int dac_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg, l, r;
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_DAC_VOL);
+ l = (reg & SGTL5000_DAC_VOL_LEFT_MASK) >> SGTL5000_DAC_VOL_LEFT_SHIFT;
+ r = (reg & SGTL5000_DAC_VOL_RIGHT_MASK) >> SGTL5000_DAC_VOL_RIGHT_SHIFT;
+ l = l < 0x3c ? 0x3c : l;
+ l = l > 0xfc ? 0xfc : l;
+ r = r < 0x3c ? 0x3c : r;
+ r = r > 0xfc ? 0xfc : r;
+ l = 0xfc - l;
+ r = 0xfc - r;
+
+ ucontrol->value.integer.value[0] = l;
+ ucontrol->value.integer.value[1] = r;
+
+ return 0;
+}
+
+static int dac_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg, l, r;
+
+ l = ucontrol->value.integer.value[0];
+ r = ucontrol->value.integer.value[1];
+
+ l = l < 0 ? 0 : l;
+ l = l > 0xfc - 0x3c ? 0xfc - 0x3c : l;
+ r = r < 0 ? 0 : r;
+ r = r > 0xfc - 0x3c ? 0xfc - 0x3c : r;
+ l = 0xfc - l;
+ r = 0xfc - r;
+
+ reg = l << SGTL5000_DAC_VOL_LEFT_SHIFT |
+ r << SGTL5000_DAC_VOL_RIGHT_SHIFT;
+
+ sgtl5000_write(codec, SGTL5000_CHIP_DAC_VOL, reg);
+
+ return 0;
+}
+
+static const char *mic_gain_text[] = {
+ "0dB", "20dB", "30dB", "40dB"
+};
+
+static const char *adc_m6db_text[] = {
+ "No Change", "Reduced by 6dB"
+};
+
+static const struct soc_enum mic_gain =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_MIC_CTRL, 0, 4, mic_gain_text);
+
+static const struct soc_enum adc_m6db =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_ADC_CTRL, 8, 2, adc_m6db_text);
+
+static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
+ SOC_ENUM("MIC GAIN", mic_gain),
+ SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0),
+ SOC_ENUM("Capture Vol Reduction", adc_m6db),
+ {.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = dac_info_volsw,
+ .get = dac_get_volsw,
+ .put = dac_put_volsw,
+ },
+ SOC_DOUBLE("Headphone Volume", SGTL5000_CHIP_ANA_HP_CTRL, 0, 8, 0x7f,
+ 1),
+};
+
+static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 adcdac_ctrl;
+
+ adcdac_ctrl = sgtl5000_read(codec, SGTL5000_CHIP_ADCDAC_CTRL);
+
+ if (mute) {
+ adcdac_ctrl |= SGTL5000_DAC_MUTE_LEFT;
+ adcdac_ctrl |= SGTL5000_DAC_MUTE_RIGHT;
+ } else {
+ adcdac_ctrl &= ~SGTL5000_DAC_MUTE_LEFT;
+ adcdac_ctrl &= ~SGTL5000_DAC_MUTE_RIGHT;
+ }
+
+ sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, adcdac_ctrl);
+ if (!mute)
+ dump_reg(codec);
+ return 0;
+}
+
+static int sgtl5000_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ u16 i2sctl = 0;
+ pr_debug("%s:fmt=%08x\n", __func__, fmt);
+ sgtl5000->master = 0;
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ i2sctl |= SGTL5000_I2S_MASTER;
+ sgtl5000->master = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ i2sctl |= SGTL5000_I2S_MODE_PCM;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ i2sctl |= SGTL5000_I2S_MODE_PCM;
+ i2sctl |= SGTL5000_I2S_LRALIGN;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ i2sctl |= SGTL5000_I2S_MODE_I2S_LJ;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ i2sctl |= SGTL5000_I2S_MODE_RJ;
+ i2sctl |= SGTL5000_I2S_LRPOL;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ i2sctl |= SGTL5000_I2S_MODE_I2S_LJ;
+ i2sctl |= SGTL5000_I2S_LRALIGN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ sgtl5000->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_NB_IF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ case SND_SOC_DAIFMT_IB_NF:
+ i2sctl |= SGTL5000_I2S_SCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+ sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, i2sctl);
+
+ return 0;
+}
+
+static int sgtl5000_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case SGTL5000_SYSCLK:
+ sgtl5000->sysclk = freq;
+ break;
+ case SGTL5000_LRCLK:
+ sgtl5000->lrclk = freq;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int sgtl5000_pcm_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ int reg;
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg |= SGTL5000_I2S_IN_POWERUP;
+ else
+ reg |= SGTL5000_I2S_OUT_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ reg |= SGTL5000_ADC_POWERUP;
+ if (sgtl5000->capture_channels == 1)
+ reg &= ~SGTL5000_ADC_STEREO;
+ else
+ reg |= SGTL5000_ADC_STEREO;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ }
+
+ return 0;
+}
+
+static int sgtl5000_pcm_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ struct snd_pcm_runtime *master_runtime;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sgtl5000->playback_active++;
+ else
+ sgtl5000->capture_active++;
+
+ /* The DAI has shared clocks so if we already have a playback or
+ * capture going then constrain this substream to match it.
+ */
+ if (sgtl5000->master_substream) {
+ master_runtime = sgtl5000->master_substream->runtime;
+
+ if (master_runtime->rate != 0) {
+ pr_debug("Constraining to %dHz\n",
+ master_runtime->rate);
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ master_runtime->rate,
+ master_runtime->rate);
+ }
+
+ if (master_runtime->sample_bits != 0) {
+ pr_debug("Constraining to %d bits\n",
+ master_runtime->sample_bits);
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ master_runtime->sample_bits,
+ master_runtime->sample_bits);
+ }
+
+ sgtl5000->slave_substream = substream;
+ } else
+ sgtl5000->master_substream = substream;
+
+ return 0;
+}
+
+static void sgtl5000_pcm_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ int reg, dig_pwr, ana_pwr;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ sgtl5000->playback_active--;
+ else
+ sgtl5000->capture_active--;
+
+ if (sgtl5000->master_substream == substream)
+ sgtl5000->master_substream = sgtl5000->slave_substream;
+
+ sgtl5000->slave_substream = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ ana_pwr = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ ana_pwr &= ~(SGTL5000_ADC_POWERUP | SGTL5000_ADC_STEREO);
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+ }
+
+ dig_pwr = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dig_pwr &= ~SGTL5000_I2S_IN_POWERUP;
+ else
+ dig_pwr &= ~SGTL5000_I2S_OUT_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, dig_pwr);
+
+ if (!sgtl5000->playback_active && !sgtl5000->capture_active) {
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_I2S_CTRL);
+ reg &= ~SGTL5000_I2S_MASTER;
+ sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, reg);
+ }
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ * input: params_rate, params_fmt
+ */
+static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ int channels = params_channels(params);
+ int clk_ctl = 0;
+ int pll_ctl = 0;
+ int i2s_ctl;
+ int div2 = 0;
+ int reg;
+ int sys_fs;
+
+ pr_debug("%s channels=%d\n", __func__, channels);
+
+ if (!sgtl5000->sysclk) {
+ pr_err("%s: set sysclk first!\n", __func__);
+ return -EFAULT;
+ }
+
+ if (substream == sgtl5000->slave_substream) {
+ pr_debug("Ignoring hw_params for slave substream\n");
+ return 0;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ sgtl5000->capture_channels = channels;
+
+ switch (sgtl5000->lrclk) {
+ case 8000:
+ case 16000:
+ sys_fs = 32000;
+ break;
+ case 11025:
+ case 22050:
+ sys_fs = 44100;
+ break;
+ default:
+ sys_fs = sgtl5000->lrclk;
+ break;
+ }
+
+ switch (sys_fs / sgtl5000->lrclk) {
+ case 4:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_4 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ case 2:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_2 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ default:
+ break;
+ }
+
+ switch (sys_fs) {
+ case 32000:
+ clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 44100:
+ clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 48000:
+ clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 96000:
+ clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ default:
+ pr_err("%s: sample rate %d not supported\n", __func__,
+ sgtl5000->lrclk);
+ return -EFAULT;
+ }
+ /* SGTL5000 rev1 has a IC bug to prevent switching to MCLK from PLL. */
+ if (!sgtl5000->master) {
+ sys_fs = sgtl5000->lrclk;
+ clk_ctl = SGTL5000_RATE_MODE_DIV_1 << SGTL5000_RATE_MODE_SHIFT;
+ if (sys_fs * 256 == sgtl5000->sysclk)
+ clk_ctl |= SGTL5000_MCLK_FREQ_256FS << \
+ SGTL5000_MCLK_FREQ_SHIFT;
+ else if (sys_fs * 384 == sgtl5000->sysclk && sys_fs != 96000)
+ clk_ctl |= SGTL5000_MCLK_FREQ_384FS << \
+ SGTL5000_MCLK_FREQ_SHIFT;
+ else if (sys_fs * 512 == sgtl5000->sysclk && sys_fs != 96000)
+ clk_ctl |= SGTL5000_MCLK_FREQ_512FS << \
+ SGTL5000_MCLK_FREQ_SHIFT;
+ else {
+ pr_err("%s: PLL not supported in slave mode\n",
+ __func__);
+ return -EINVAL;
+ }
+ } else
+ clk_ctl |= SGTL5000_MCLK_FREQ_PLL << SGTL5000_MCLK_FREQ_SHIFT;
+
+ if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
+ u64 out, t;
+ unsigned int in, int_div, frac_div;
+ if (sgtl5000->sysclk > 17000000) {
+ div2 = 1;
+ in = sgtl5000->sysclk / 2;
+ } else {
+ div2 = 0;
+ in = sgtl5000->sysclk;
+ }
+ if (sys_fs == 44100)
+ out = 180633600;
+ else
+ out = 196608000;
+ t = do_div(out, in);
+ int_div = out;
+ t *= 2048;
+ do_div(t, in);
+ frac_div = t;
+ pll_ctl = int_div << SGTL5000_PLL_INT_DIV_SHIFT |
+ frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT;
+ }
+
+ i2s_ctl = sgtl5000_read(codec, SGTL5000_CHIP_I2S_CTRL);
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ return -EINVAL;
+ i2s_ctl |= SGTL5000_I2S_DLEN_16 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_32FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ i2s_ctl |= SGTL5000_I2S_DLEN_20 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ i2s_ctl |= SGTL5000_I2S_DLEN_24 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ return -EINVAL;
+ i2s_ctl |= SGTL5000_I2S_DLEN_32 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pr_debug("\nfs=%d,clk_ctl=%04x,pll_ctl=%04x,i2s_ctl=%04x,div2=%d\n",
+ sgtl5000->lrclk, clk_ctl, pll_ctl, i2s_ctl, div2);
+
+ if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
+ sgtl5000_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl);
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL);
+ if (div2)
+ reg |= SGTL5000_INPUT_FREQ_DIV2;
+ else
+ reg &= ~SGTL5000_INPUT_FREQ_DIV2;
+ sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg);
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ reg |= SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ }
+ sgtl5000_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl);
+ sgtl5000_write(codec, SGTL5000_CHIP_I2S_CTRL, i2s_ctl);
+
+ return 0;
+}
+
+static void sgtl5000_mic_bias(struct snd_soc_codec *codec, int enable)
+{
+ int reg, bias_r = 0;
+ if (enable)
+ bias_r = SGTL5000_BIAS_R_4k << SGTL5000_BIAS_R_SHIFT;
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_MIC_CTRL);
+ if ((reg & SGTL5000_BIAS_R_MASK) != bias_r) {
+ reg &= ~SGTL5000_BIAS_R_MASK;
+ reg |= bias_r;
+ sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, reg);
+ }
+}
+
+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);
+ switch (level) {
+ case SND_SOC_BIAS_ON: /* full On */
+ if (codec->bias_level == SND_SOC_BIAS_ON)
+ break;
+
+ sgtl5000_mic_bias(codec, 1);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ reg |= SGTL5000_VAG_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ msleep(400);
+
+ 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;
+
+ sgtl5000_mic_bias(codec, 0);
+
+ /* must power up hp/line out before vag & dac to
+ avoid pops. */
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ if (reg & SGTL5000_VAG_POWERUP)
+ delay = 600;
+ reg &= ~SGTL5000_VAG_POWERUP;
+ reg |= SGTL5000_DAC_POWERUP;
+ reg |= SGTL5000_HP_POWERUP;
+ reg |= SGTL5000_LINE_OUT_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ if (delay)
+ msleep(delay);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER);
+ reg |= SGTL5000_DAC_EN;
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
+
+ break;
+
+ case SND_SOC_BIAS_STANDBY: /* Off, with power */
+ /* soc doesn't do PREPARE state after record so make sure
+ that anything that needs to be turned OFF gets turned off. */
+ if (codec->bias_level == SND_SOC_BIAS_STANDBY)
+ break;
+
+ /* soc calls digital_mute to unmute before record but doesn't
+ call digital_mute to mute after record. */
+ sgtl5000_digital_mute(&sgtl5000_dai, 1);
+
+ sgtl5000_mic_bias(codec, 0);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ if (reg & SGTL5000_VAG_POWERUP) {
+ reg &= ~SGTL5000_VAG_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ msleep(400);
+ }
+ reg &= ~SGTL5000_DAC_POWERUP;
+ reg &= ~SGTL5000_HP_POWERUP;
+ reg &= ~SGTL5000_LINE_OUT_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_DIG_POWER);
+ 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);
+ ana_pwr = reg;
+ reg &= ~SGTL5000_VAG_POWERUP;
+
+ /* Workaround for sgtl5000 rev 0x11 chip audio suspend failure
+ issue on mx25 */
+ /* reg &= ~SGTL5000_REFTOP_POWERUP; */
+
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+ msleep(600);
+
+ reg &= ~SGTL5000_HP_POWERUP;
+ reg &= ~SGTL5000_LINE_OUT_POWERUP;
+ reg &= ~SGTL5000_DAC_POWERUP;
+ reg &= ~SGTL5000_ADC_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
+
+ /* 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;
+ return 0;
+}
+
+#define SGTL5000_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 |\
+ SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_96000)
+
+#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+struct snd_soc_dai_ops sgtl5000_ops = {
+ .prepare = sgtl5000_pcm_prepare,
+ .startup = sgtl5000_pcm_startup,
+ .shutdown = sgtl5000_pcm_shutdown,
+ .hw_params = sgtl5000_pcm_hw_params,
+ .digital_mute = sgtl5000_digital_mute,
+ .set_fmt = sgtl5000_set_dai_fmt,
+ .set_sysclk = sgtl5000_set_dai_sysclk
+};
+
+struct snd_soc_dai sgtl5000_dai = {
+ .name = "SGTL5000",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SGTL5000_RATES,
+ .formats = SGTL5000_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SGTL5000_RATES,
+ .formats = SGTL5000_FORMATS,
+ },
+ .ops = &sgtl5000_ops,
+};
+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);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int sgtl5000_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ unsigned int i;
+
+ /* Restore refs first in same order as in sgtl5000_init */
+ sgtl5000_restore_reg(codec, SGTL5000_CHIP_LINREG_CTRL);
+ sgtl5000_restore_reg(codec, SGTL5000_CHIP_ANA_POWER);
+ msleep(10);
+ sgtl5000_restore_reg(codec, SGTL5000_CHIP_REF_CTRL);
+ sgtl5000_restore_reg(codec, SGTL5000_CHIP_LINE_OUT_CTRL);
+
+ /* Restore everythine else */
+ for (i = 1; i < sizeof(all_reg) / sizeof(int); i++)
+ sgtl5000_restore_reg(codec, all_reg[i]);
+
+ sgtl5000_write(codec, SGTL5000_DAP_CTRL, 0);
+
+ /* Bring the codec back up to standby first to minimise pop/clicks */
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (codec->suspend_bias_level == SND_SOC_BIAS_ON)
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+ sgtl5000_set_bias_level(codec, codec->suspend_bias_level);
+
+ return 0;
+}
+
+static struct snd_soc_codec *sgtl5000_codec;
+
+/*
+ * initialise the SGTL5000 driver
+ * register the mixer and dsp interfaces with the kernel
+ */
+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) {
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ /* reset value */
+ ana_pwr = SGTL5000_DAC_STEREO |
+ SGTL5000_LINREG_SIMPLE_POWERUP |
+ SGTL5000_STARTUP_POWERUP |
+ SGTL5000_ADC_STEREO | SGTL5000_REFTOP_POWERUP;
+ lreg_ctrl = 0;
+ ref_ctrl = 0;
+ lo_ctrl = 0;
+ short_ctrl = 0;
+ sss = SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT;
+
+ /* workaround for rev 0x11: use vddd linear regulator */
+ if (!sgtl5000->vddd || (sgtl5000->rev >= 0x11)) {
+ /* set VDDD to 1.2v */
+ lreg_ctrl |= 0x8 << SGTL5000_LINREG_VDDD_SHIFT;
+ /* power internal linear regulator */
+ ana_pwr |= SGTL5000_LINEREG_D_POWERUP;
+ } else {
+ /* turn of startup power */
+ ana_pwr &= ~SGTL5000_STARTUP_POWERUP;
+ ana_pwr &= ~SGTL5000_LINREG_SIMPLE_POWERUP;
+ }
+ if (sgtl5000->vddio < 3100 && sgtl5000->vdda < 3100) {
+ /* Enable VDDC charge pump */
+ ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP;
+ }
+ if (sgtl5000->vddio >= 3100 && sgtl5000->vdda >= 3100) {
+ /* VDDC use VDDIO rail */
+ lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD;
+ if (sgtl5000->vddio >= 3100)
+ lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO <<
+ SGTL5000_VDDC_MAN_ASSN_SHIFT;
+ }
+ /* If PLL is powered up (such as on power cycle) leave it on. */
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_POWER);
+ ana_pwr |= reg & (SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP);
+
+ /* set ADC/DAC ref voltage to vdda/2 */
+ vag = sgtl5000->vdda / 2;
+ if (vag <= SGTL5000_ANA_GND_BASE)
+ vag = 0;
+ else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP *
+ (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT))
+ vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT;
+ else
+ vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP;
+ ref_ctrl |= vag << SGTL5000_ANA_GND_SHIFT;
+
+ /* set line out ref voltage to vddio/2 */
+ vag = sgtl5000->vddio / 2;
+ if (vag <= SGTL5000_LINE_OUT_GND_BASE)
+ vag = 0;
+ else if (vag >= SGTL5000_LINE_OUT_GND_BASE + SGTL5000_LINE_OUT_GND_STP *
+ SGTL5000_LINE_OUT_GND_MAX)
+ vag = SGTL5000_LINE_OUT_GND_MAX;
+ else
+ vag = (vag - SGTL5000_LINE_OUT_GND_BASE) /
+ SGTL5000_LINE_OUT_GND_STP;
+ lo_ctrl |= vag << SGTL5000_LINE_OUT_GND_SHIFT;
+
+ /* enable small pop */
+ ref_ctrl |= SGTL5000_SMALL_POP;
+
+ /* Controls the output bias current for the lineout */
+ lo_ctrl |=
+ (SGTL5000_LINE_OUT_CURRENT_360u << SGTL5000_LINE_OUT_CURRENT_SHIFT);
+
+ /* set short detect */
+ /* keep default */
+
+ /* set routing */
+ /* keep default, bypass DAP */
+
+ sgtl5000_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl);
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+ msleep(10);
+
+ /* For rev 0x11, if vddd linear reg has been enabled, we have
+ to disable simple reg to get proper VDDD voltage. */
+ if ((ana_pwr & SGTL5000_LINEREG_D_POWERUP) && (sgtl5000->rev >= 0x11)) {
+ ana_pwr &= ~SGTL5000_LINREG_SIMPLE_POWERUP;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+ msleep(10);
+ }
+
+ sgtl5000_write(codec, SGTL5000_CHIP_REF_CTRL, ref_ctrl);
+ sgtl5000_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL, lo_ctrl);
+ sgtl5000_write(codec, SGTL5000_CHIP_SHORT_CTRL, short_ctrl);
+ sgtl5000_write(codec, SGTL5000_CHIP_SSS_CTRL, sss);
+ sgtl5000_write(codec, SGTL5000_CHIP_DIG_POWER, 0);
+
+ reg = SGTL5000_DAC_VOL_RAMP_EN |
+ SGTL5000_DAC_MUTE_RIGHT | SGTL5000_DAC_MUTE_LEFT;
+ sgtl5000_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, reg);
+
+#ifdef CONFIG_ARCH_MXC
+ if (cpu_is_mx25())
+ sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x01df);
+ else
+#endif
+ sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f);
+
+ reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_ADC_CTRL);
+ reg &= ~SGTL5000_ADC_VOL_M6DB;
+ reg &= ~(SGTL5000_ADC_VOL_LEFT_MASK | SGTL5000_ADC_VOL_RIGHT_MASK);
+ reg |= (0xf << SGTL5000_ADC_VOL_LEFT_SHIFT)
+ | (0xf << SGTL5000_ADC_VOL_RIGHT_SHIFT);
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_ADC_CTRL, reg);
+
+ reg = SGTL5000_LINE_OUT_MUTE | SGTL5000_HP_MUTE |
+ SGTL5000_HP_ZCD_EN | SGTL5000_ADC_ZCD_EN;
+ sgtl5000_write(codec, SGTL5000_CHIP_ANA_CTRL, reg);
+
+ sgtl5000_write(codec, SGTL5000_CHIP_MIC_CTRL, 0);
+ sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, 0);
+ /* disable DAP */
+ sgtl5000_write(codec, SGTL5000_DAP_CTRL, 0);
+ /* TODO: initialize DAP */
+
+ snd_soc_add_controls(codec, sgtl5000_snd_controls,
+ ARRAY_SIZE(sgtl5000_snd_controls));
+ sgtl5000_add_widgets(codec);
+
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ 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)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ 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);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_sgtl5000 = {
+ .probe = sgtl5000_probe,
+ .remove = sgtl5000_remove,
+ .suspend = sgtl5000_suspend,
+ .resume = sgtl5000_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_sgtl5000);
+
+static __devinit int sgtl5000_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sgtl5000_priv *sgtl5000;
+ struct snd_soc_codec *codec;
+ struct regulator *reg;
+ int ret = 0;
+ u32 val;
+
+ if (sgtl5000_codec) {
+ dev_err(&client->dev,
+ "Multiple SGTL5000 devices not supported\n");
+ return -ENOMEM;
+ }
+
+ codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (codec == NULL)
+ return -ENOMEM;
+
+ sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL);
+ if (sgtl5000 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ snd_soc_codec_set_drvdata(codec, sgtl5000);
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ i2c_set_clientdata(client, codec);
+ codec->control_data = client;
+
+ reg = regulator_get(&client->dev, "VDDIO");
+ if (!IS_ERR(reg))
+ sgtl5000->reg_vddio = reg;
+
+ reg = regulator_get(&client->dev, "VDDA");
+ if (!IS_ERR(reg))
+ sgtl5000->reg_vdda = reg;
+
+ reg = regulator_get(&client->dev, "VDDD");
+ if (!IS_ERR(reg))
+ sgtl5000->reg_vddd = reg;
+
+ if (sgtl5000->reg_vdda) {
+ sgtl5000->vdda =
+ regulator_get_voltage(sgtl5000->reg_vdda) / 1000;
+ regulator_enable(sgtl5000->reg_vdda);
+ }
+ if (sgtl5000->reg_vddio) {
+ sgtl5000->vddio =
+ regulator_get_voltage(sgtl5000->reg_vddio) / 1000;
+ regulator_enable(sgtl5000->reg_vddio);
+ }
+ if (sgtl5000->reg_vddd) {
+ sgtl5000->vddd =
+ regulator_get_voltage(sgtl5000->reg_vddd) / 1000;
+ regulator_enable(sgtl5000->reg_vddd);
+ } else {
+ sgtl5000->vddd = 0; /* use internal regulator */
+ }
+
+ msleep(1);
+
+ val = sgtl5000_read(codec, SGTL5000_CHIP_ID);
+ if (((val & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) !=
+ SGTL5000_PARTID_PART_ID) {
+ pr_err("Device with ID register %x is not a SGTL5000\n", val);
+ ret = -ENODEV;
+ goto err_codec_reg;
+ }
+
+ sgtl5000->rev = (val & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT;
+ dev_info(&client->dev, "SGTL5000 revision %d\n", sgtl5000->rev);
+
+ codec->dev = &client->dev;
+ codec->name = "SGTL5000";
+ codec->owner = THIS_MODULE;
+ codec->read = sgtl5000_read_reg_cache;
+ codec->write = sgtl5000_write;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = sgtl5000_set_bias_level;
+ codec->dai = &sgtl5000_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = sizeof(sgtl5000_regs);
+ codec->reg_cache_step = 2;
+ codec->reg_cache = (void *)&sgtl5000_regs;
+
+ sgtl5000_sync_reg_cache(codec);
+
+ sgtl5000_codec = codec;
+ sgtl5000_dai.dev = &client->dev;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ goto err_codec_reg;
+ }
+
+ ret = snd_soc_register_dai(&sgtl5000_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
+ goto err_codec_reg;
+ }
+
+ return 0;
+
+err_codec_reg:
+ if (sgtl5000->reg_vddd)
+ regulator_disable(sgtl5000->reg_vddd);
+ if (sgtl5000->reg_vdda)
+ regulator_disable(sgtl5000->reg_vdda);
+ if (sgtl5000->reg_vddio)
+ regulator_disable(sgtl5000->reg_vddio);
+ if (sgtl5000->reg_vddd)
+ regulator_put(sgtl5000->reg_vddd);
+ if (sgtl5000->reg_vdda)
+ regulator_put(sgtl5000->reg_vdda);
+ if (sgtl5000->reg_vddio)
+ regulator_put(sgtl5000->reg_vddio);
+ kfree(sgtl5000);
+ kfree(codec);
+ return ret;
+}
+
+static __devexit int sgtl5000_i2c_remove(struct i2c_client *client)
+{
+ struct snd_soc_codec *codec = i2c_get_clientdata(client);
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ if (client->dev.platform_data)
+ clk_disable((struct clk *)client->dev.platform_data);
+
+ snd_soc_unregister_dai(&sgtl5000_dai);
+ snd_soc_unregister_codec(codec);
+
+ if (sgtl5000->reg_vddio) {
+ regulator_disable(sgtl5000->reg_vddio);
+ regulator_put(sgtl5000->reg_vddio);
+ }
+ if (sgtl5000->reg_vddd) {
+ regulator_disable(sgtl5000->reg_vddd);
+ regulator_put(sgtl5000->reg_vddd);
+ }
+ if (sgtl5000->reg_vdda) {
+ regulator_disable(sgtl5000->reg_vdda);
+ regulator_put(sgtl5000->reg_vdda);
+ }
+
+ kfree(codec);
+ kfree(sgtl5000);
+ sgtl5000_codec = NULL;
+ return 0;
+}
+
+static const struct i2c_device_id sgtl5000_id[] = {
+ {"sgtl5000-i2c", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, sgtl5000_id);
+
+static struct i2c_driver sgtl5000_i2c_driver = {
+ .driver = {
+ .name = "sgtl5000-i2c",
+ .owner = THIS_MODULE,
+ },
+ .probe = sgtl5000_i2c_probe,
+ .remove = __devexit_p(sgtl5000_i2c_remove),
+ .id_table = sgtl5000_id,
+};
+
+static int __init sgtl5000_modinit(void)
+{
+ return i2c_add_driver(&sgtl5000_i2c_driver);
+}
+module_init(sgtl5000_modinit);
+
+static void __exit sgtl5000_exit(void)
+{
+ i2c_del_driver(&sgtl5000_i2c_driver);
+}
+module_exit(sgtl5000_exit);
+
+MODULE_DESCRIPTION("ASoC SGTL5000 driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");