[alsa-devel] [PATCH] asoc, codec: Add SGTL5000 codec support to asoc
From: Zeng Zhaoming zhaoming.zeng@freescale.com
Add Freescale SGTL5000 codec support.
Signed-off-by: Zeng Zhaoming zhaoming.zeng@freescale.com --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sgtl5000.c | 1052 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/sgtl5000.h | 403 +++++++++++++++++ 4 files changed, 1460 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sgtl5000.c create mode 100644 sound/soc/codecs/sgtl5000.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3b5690d..f12ef39 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -318,3 +318,6 @@ config SND_SOC_WM2000
config SND_SOC_WM9090 tristate + +config SND_SOC_SGTL5000 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index f67a2d6..116ec3d 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -65,6 +65,7 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-jz4740-codec-objs := jz4740.o +snd-soc-sgtl5000-objs := sgtl5000.o
# Amp snd-soc-max9877-objs := max9877.o @@ -139,6 +140,7 @@ obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o +obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
# Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c new file mode 100644 index 0000000..a053f19 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.c @@ -0,0 +1,1052 @@ +/* + * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver + * + * Copyright 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/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.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" + +static const u16 sgtl5000_regs[] = { + 0xa011, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000, 0x0010, 0x0000, + 0x0010, 0x0000, 0x0010, 0x0000, 0x0000, 0x0000, 0x323c, 0x0000, + 0x3c3c, 0x0000, 0x3c3c, 0x0000, 0x555f, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x408c, 0x0000, 0x0008, 0x0000, + 0x0000, 0x0000, 0x1818, 0x0000, 0x0111, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0404, 0x0000, + 0x7060, 0x0000, 0x5000, 0x0000, 0x0000, 0x0000, 0x0017, 0x0000, + 0x01c0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, +}; + +enum sgtl5000_regulator_supplies { + VDDA, + VDDIO, + VDDD, + SGTL5000_SUPPLY_NUM +}; + +static const char* supply_names[SGTL5000_SUPPLY_NUM] = { + "VDDA", + "VDDIO", + "VDDD" +}; + +struct sgtl5000_priv { + int sysclk; + int master; + int fmt; + int rev; + int lrclk; + int capture_channels; + int playback_active; + int capture_active; + struct regulator *supplies[SGTL5000_SUPPLY_NUM]; + int regulator_volt[SGTL5000_SUPPLY_NUM]; + 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 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("Capture Mux", adc_enum); + +static const struct snd_kcontrol_new dac_mux = +SOC_DAPM_ENUM("Headphone 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("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux), + SND_SOC_DAPM_MUX("Headphone 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[] = { + {"Capture Mux", "LINE_IN", "LINE_IN"}, + {"Capture Mux", "MIC_IN", "MIC_IN"}, + {"ADC", NULL, "Capture Mux"}, + {"Headphone Mux", "DAC", "DAC"}, + {"Headphone Mux", "LINE_IN", "LINE_IN"}, + {"LO", NULL, "DAC"}, + {"HP", NULL, "Headphone Mux"}, + {"LINE_OUT", NULL, "LO"}, + {"HP_OUT", NULL, "HP"}, +}; + +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 = snd_soc_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; + + snd_soc_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 = "PCM 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 Playback Volume", SGTL5000_CHIP_ANA_HP_CTRL, 0, 8, 0x7f, 1), +}; + +static int __sgtl5000_digital_mute(struct snd_soc_codec *codec, int mute) +{ + u16 adcdac_ctrl = SGTL5000_DAC_MUTE_LEFT | SGTL5000_DAC_MUTE_RIGHT; + + if (mute) + snd_soc_update_bits(codec, SGTL5000_CHIP_ADCDAC_CTRL, + adcdac_ctrl, adcdac_ctrl); + else + snd_soc_update_bits(codec, SGTL5000_CHIP_ADCDAC_CTRL, + adcdac_ctrl, 0); + + return 0; +} + +static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + + return __sgtl5000_digital_mute(codec, mute); +} + +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; + + 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; + } + + snd_soc_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_codec *codec = rtd->codec; + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + int reg; + + reg = snd_soc_read(codec, SGTL5000_CHIP_DIG_POWER); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg |= SGTL5000_I2S_IN_POWERUP; + else + reg |= SGTL5000_I2S_OUT_POWERUP; + snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, reg); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + reg |= SGTL5000_ADC_POWERUP; + if (sgtl5000->capture_channels == 1) + reg &= ~SGTL5000_ADC_STEREO; + else + reg |= SGTL5000_ADC_STEREO; + snd_soc_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_codec *codec = rtd->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; + + 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_codec *codec = rtd->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 = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr &= ~(SGTL5000_ADC_POWERUP | SGTL5000_ADC_STEREO); + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); + } + + dig_pwr = snd_soc_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; + snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, dig_pwr); + + if (!sgtl5000->playback_active && !sgtl5000->capture_active) { + reg = snd_soc_read(codec, SGTL5000_CHIP_I2S_CTRL); + reg &= ~SGTL5000_I2S_MASTER; + snd_soc_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_codec *codec = rtd->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; + + if (!sgtl5000->sysclk) { + dev_err(codec->dev, "%s: set sysclk first!\n", __func__); + return -EFAULT; + } + + if (substream == sgtl5000->slave_substream) + 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: + dev_err(codec->dev, "sample rate %d not supported\n", 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 = snd_soc_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; + } + + dev_dbg(codec->dev, "fs=%d,clk_ctl=%d,pll_ctl=%d,i2s_ctl=%d,div2=%d\n", + sgtl5000->lrclk, clk_ctl, pll_ctl, i2s_ctl, div2); + + if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) { + snd_soc_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl); + reg = snd_soc_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL); + if (div2) + reg |= SGTL5000_INPUT_FREQ_DIV2; + else + reg &= ~SGTL5000_INPUT_FREQ_DIV2; + snd_soc_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg); + reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + reg |= SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP; + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + } + snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl); + snd_soc_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 = snd_soc_read(codec, SGTL5000_CHIP_MIC_CTRL); + if ((reg & SGTL5000_BIAS_R_MASK) != bias_r) { + reg &= ~SGTL5000_BIAS_R_MASK; + reg |= bias_r; + snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, reg); + } +} + +static int sgtl5000_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg, ana_pwr; + u16 *cache = codec->reg_cache; + + if (codec->bias_level == level) + return 0; + + switch (level) { + case SND_SOC_BIAS_ON: + sgtl5000_mic_bias(codec, 1); + + snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_MASK, SGTL5000_BIAS_R_MASK); + + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP); + + __sgtl5000_digital_mute(codec, 0); + break; + + case SND_SOC_BIAS_PREPARE: /* partial On */ + snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_MASK, 0); + + /* must power up hp/line out before vag & dac to + avoid pops. */ + reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + + reg &= ~SGTL5000_VAG_POWERUP; + reg |= SGTL5000_DAC_POWERUP; + reg |= SGTL5000_HP_POWERUP; + reg |= SGTL5000_LINE_OUT_POWERUP; + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + + snd_soc_update_bits(codec, SGTL5000_CHIP_DIG_POWER, + SGTL5000_DAC_EN, SGTL5000_DAC_EN); + + break; + + case SND_SOC_BIAS_STANDBY: + /* soc calls digital_mute to unmute before record but doesn't + call digital_mute to mute after record. */ + __sgtl5000_digital_mute(codec, 1); + + snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL, + SGTL5000_BIAS_R_MASK, 0); + + reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + if (reg & SGTL5000_VAG_POWERUP) { + reg &= ~SGTL5000_VAG_POWERUP; + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + } + reg &= ~SGTL5000_DAC_POWERUP; + reg &= ~SGTL5000_HP_POWERUP; + reg &= ~SGTL5000_LINE_OUT_POWERUP; + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + + reg = snd_soc_read(codec, SGTL5000_CHIP_DIG_POWER); + reg &= ~SGTL5000_DAC_EN; + snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, reg); + + break; + + case SND_SOC_BIAS_OFF: /* Off, without power */ + /* mute hp/Line_out first to avoid pops. */ + __sgtl5000_digital_mute(codec, 1); + sgtl5000_mic_bias(codec, 0); + + reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr = reg; + reg &= ~SGTL5000_VAG_POWERUP; + + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + + reg &= ~SGTL5000_HP_POWERUP; + reg &= ~SGTL5000_LINE_OUT_POWERUP; + reg &= ~SGTL5000_DAC_POWERUP; + reg &= ~SGTL5000_ADC_POWERUP; + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg); + + /* save ANA POWER register value for resume */ + cache[SGTL5000_CHIP_ANA_POWER >> 1] = ana_pwr; + 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, +}; + +static struct snd_soc_dai_driver 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, + .symmetric_rates = 1, +}; + +static int sgtl5000_volatile_register(unsigned int reg) +{ + if (reg == SGTL5000_CHIP_ID || + reg == SGTL5000_CHIP_ADCDAC_CTRL || + reg == SGTL5000_CHIP_ANA_STATUS) + return 1; + return 0; +} + +static int sgtl5000_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int sgtl5000_restore_reg(struct snd_soc_codec *codec, unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + return snd_soc_write(codec, reg, cache[reg >> 1]); +} + +static int sgtl5000_resume(struct snd_soc_codec *codec) +{ + int i; + + /* Restore refs first in same order as in sgtl5000_probe */ + 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 = 0; i < ARRAY_SIZE(sgtl5000_regs); i++) + sgtl5000_restore_reg(codec, i); + + snd_soc_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 int sgtl5000_probe(struct snd_soc_codec *codec) +{ + struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec); + u16 reg, ana_pwr, lreg_ctrl; + int vag; + int ret; + int vddd, vdda, vddio; + + ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + vddd = sgtl5000->regulator_volt[VDDD]; + vdda = sgtl5000->regulator_volt[VDDA]; + vddio = sgtl5000->regulator_volt[VDDIO]; + + reg = snd_soc_read(codec, SGTL5000_CHIP_ID); + if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) != + SGTL5000_PARTID_PART_ID) { + dev_err(codec->dev, "Device with ID register %x is not a sgtl5000\n", reg); + return -ENODEV; + } + + sgtl5000->rev = (reg & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT; + dev_info(codec->dev, "sgtl5000 revision %d\n", sgtl5000->rev); + + /* reset value */ + ana_pwr = SGTL5000_DAC_STEREO | + SGTL5000_LINREG_SIMPLE_POWERUP | + SGTL5000_STARTUP_POWERUP | + SGTL5000_ADC_STEREO | SGTL5000_REFTOP_POWERUP; + lreg_ctrl = 0; + + /* workaround for rev 0x11: use vddd linear regulator */ + if (!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 (vddio < 3100 && vdda < 3100) { + /* Enable VDDC charge pump */ + ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP; + } + + if (vddio >= 3100 && vdda >= 3100) { + /* VDDC use VDDIO rail */ + lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD; + 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 = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER); + ana_pwr |= reg & (SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP); + + /* set ADC/DAC ref voltage to vdda / 2 */ + vag = 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; + + /* set line out ref voltage to vddio / 2 */ + vag = 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; + + snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl); + snd_soc_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; + snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr); + msleep(10); + } + + snd_soc_write(codec, SGTL5000_CHIP_REF_CTRL, + vag << SGTL5000_ANA_GND_SHIFT); + + snd_soc_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL, + vag << SGTL5000_LINE_OUT_GND_SHIFT | + SGTL5000_LINE_OUT_CURRENT_360u << + SGTL5000_LINE_OUT_CURRENT_SHIFT); + + snd_soc_write(codec, SGTL5000_CHIP_SHORT_CTRL, 0); + snd_soc_write(codec, SGTL5000_CHIP_SSS_CTRL, + SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT); + snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, 0); + + snd_soc_write(codec, SGTL5000_CHIP_ADCDAC_CTRL, + SGTL5000_DAC_VOL_RAMP_EN | + SGTL5000_DAC_MUTE_RIGHT | + SGTL5000_DAC_MUTE_LEFT); + + snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f); + + reg = snd_soc_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); + snd_soc_write(codec, SGTL5000_CHIP_ANA_ADC_CTRL, reg); + + snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL, + SGTL5000_LINE_OUT_MUTE | + SGTL5000_HP_MUTE | + SGTL5000_HP_ZCD_EN | + SGTL5000_ADC_ZCD_EN); + + snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, 0); + snd_soc_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, 0); + + /* disable DAP */ + snd_soc_write(codec, SGTL5000_DAP_CTRL, 0); + + snd_soc_add_controls(codec, sgtl5000_snd_controls, + ARRAY_SIZE(sgtl5000_snd_controls)); + + 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); + + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static int sgtl5000_remove(struct snd_soc_codec *codec) +{ + if (codec->control_data) + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_dapm_free(codec); + + return 0; +} + +struct snd_soc_codec_driver sgtl5000_driver = { + .probe = sgtl5000_probe, + .remove = sgtl5000_remove, + .suspend = sgtl5000_suspend, + .resume = sgtl5000_resume, + .set_bias_level = sgtl5000_set_bias_level, + .reg_cache_size = ARRAY_SIZE(sgtl5000_regs), + .reg_word_size = sizeof(u16), + .reg_cache_default = sgtl5000_regs, + .volatile_register = sgtl5000_volatile_register, +}; + +static __devinit int sgtl5000_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sgtl5000_priv *sgtl5000; + int ret; + int i; + + sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL); + if (!sgtl5000) + return -ENOMEM; + + i2c_set_clientdata(client, sgtl5000); + + for (i = 0; i < SGTL5000_SUPPLY_NUM; i++) { + struct regulator *reg; + reg = regulator_get(&client->dev, supply_names[i]); + if (IS_ERR(reg)) + continue; + + sgtl5000->regulator_volt[i] = regulator_get_voltage(reg) / 1000; + regulator_enable(reg); + + sgtl5000->supplies[i] = reg; + } + + sgtl5000->regulator_volt[VDDA] = 3300; + sgtl5000->regulator_volt[VDDIO] = 3300; + + msleep(1); + + ret = snd_soc_register_codec(&client->dev, &sgtl5000_driver, &sgtl5000_dai, 1); + if (ret) { + dev_err(&client->dev, "Failed to register codec: %d\n", ret); + goto err_out; + } + + return 0; + +err_out: + for (i=0; i < SGTL5000_SUPPLY_NUM; i++) { + if( !sgtl5000->supplies[i] ) + continue; + + regulator_disable(sgtl5000->supplies[i]); + regulator_put(sgtl5000->supplies[i]); + } + + kfree(sgtl5000); + return ret; +} + +static __devexit int sgtl5000_i2c_remove(struct i2c_client *client) +{ + struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client); + int i; + + if (client->dev.platform_data) + clk_disable((struct clk *)client->dev.platform_data); + + snd_soc_unregister_codec(&client->dev); + + for (i = 0; i < SGTL5000_SUPPLY_NUM; i++){ + if( !sgtl5000->supplies[i] ) + continue; + + regulator_disable(sgtl5000->supplies[i]); + regulator_put(sgtl5000->supplies[i]); + } + + kfree(sgtl5000); + 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"); diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h new file mode 100644 index 0000000..7f24655 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.h @@ -0,0 +1,403 @@ +/* + * sgtl5000.h - SGTL5000 audio codec interface + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _SGTL5000_H +#define _SGTL5000_H + +#include <linux/i2c.h> + +/* + * Register values. + */ +#define SGTL5000_CHIP_ID 0x0000 +#define SGTL5000_CHIP_DIG_POWER 0x0002 +#define SGTL5000_CHIP_CLK_CTRL 0x0004 +#define SGTL5000_CHIP_I2S_CTRL 0x0006 +#define SGTL5000_CHIP_SSS_CTRL 0x000a +#define SGTL5000_CHIP_ADCDAC_CTRL 0x000e +#define SGTL5000_CHIP_DAC_VOL 0x0010 +#define SGTL5000_CHIP_PAD_STRENGTH 0x0014 +#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020 +#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022 +#define SGTL5000_CHIP_ANA_CTRL 0x0024 +#define SGTL5000_CHIP_LINREG_CTRL 0x0026 +#define SGTL5000_CHIP_REF_CTRL 0x0028 +#define SGTL5000_CHIP_MIC_CTRL 0x002a +#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002c +#define SGTL5000_CHIP_LINE_OUT_VOL 0x002e +#define SGTL5000_CHIP_ANA_POWER 0x0030 +#define SGTL5000_CHIP_PLL_CTRL 0x0032 +#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034 +#define SGTL5000_CHIP_ANA_STATUS 0x0036 +#define SGTL5000_CHIP_SHORT_CTRL 0x003c +#define SGTL5000_CHIP_ANA_TEST2 0x003a +#define SGTL5000_DAP_CTRL 0x0100 +#define SGTL5000_DAP_PEQ 0x0102 +#define SGTL5000_DAP_BASS_ENHANCE 0x0104 +#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106 +#define SGTL5000_DAP_AUDIO_EQ 0x0108 +#define SGTL5000_DAP_SURROUND 0x010a +#define SGTL5000_DAP_FLT_COEF_ACCESS 0x010c +#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010e +#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110 +#define SGTL5000_DAP_EQ_BASS_BAND0 0x0116 +#define SGTL5000_DAP_EQ_BASS_BAND1 0x0118 +#define SGTL5000_DAP_EQ_BASS_BAND2 0x011a +#define SGTL5000_DAP_EQ_BASS_BAND3 0x011c +#define SGTL5000_DAP_EQ_BASS_BAND4 0x011e +#define SGTL5000_DAP_MAIN_CHAN 0x0120 +#define SGTL5000_DAP_MIX_CHAN 0x0122 +#define SGTL5000_DAP_AVC_CTRL 0x0124 +#define SGTL5000_DAP_AVC_THRESHOLD 0x0126 +#define SGTL5000_DAP_AVC_ATTACK 0x0128 +#define SGTL5000_DAP_AVC_DECAY 0x012a +#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012c +#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012e +#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130 +#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132 +#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134 +#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136 +#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138 +#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013a + +/* + * Field Definitions. + */ + +/* + * SGTL5000_CHIP_ID + */ +#define SGTL5000_PARTID_MASK 0xff00 +#define SGTL5000_PARTID_SHIFT 8 +#define SGTL5000_PARTID_WIDTH 8 +#define SGTL5000_PARTID_PART_ID 0xa0 +#define SGTL5000_REVID_MASK 0x00ff +#define SGTL5000_REVID_SHIFT 0 +#define SGTL5000_REVID_WIDTH 8 + +/* + * SGTL5000_CHIP_DIG_POWER + */ +#define SGTL5000_ADC_EN 0x0040 +#define SGTL5000_DAC_EN 0x0020 +#define SGTL5000_DAP_POWERUP 0x0010 +#define SGTL5000_I2S_OUT_POWERUP 0x0002 +#define SGTL5000_I2S_IN_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_CLK_CTRL + */ +#define SGTL5000_RATE_MODE_MASK 0x0030 +#define SGTL5000_RATE_MODE_SHIFT 4 +#define SGTL5000_RATE_MODE_WIDTH 2 +#define SGTL5000_RATE_MODE_DIV_1 0 +#define SGTL5000_RATE_MODE_DIV_2 1 +#define SGTL5000_RATE_MODE_DIV_4 2 +#define SGTL5000_RATE_MODE_DIV_6 3 +#define SGTL5000_SYS_FS_MASK 0x000c +#define SGTL5000_SYS_FS_SHIFT 2 +#define SGTL5000_SYS_FS_WIDTH 2 +#define SGTL5000_SYS_FS_32k 0x0 +#define SGTL5000_SYS_FS_44_1k 0x1 +#define SGTL5000_SYS_FS_48k 0x2 +#define SGTL5000_SYS_FS_96k 0x3 +#define SGTL5000_MCLK_FREQ_MASK 0x0003 +#define SGTL5000_MCLK_FREQ_SHIFT 0 +#define SGTL5000_MCLK_FREQ_WIDTH 2 +#define SGTL5000_MCLK_FREQ_256FS 0x0 +#define SGTL5000_MCLK_FREQ_384FS 0x1 +#define SGTL5000_MCLK_FREQ_512FS 0x2 +#define SGTL5000_MCLK_FREQ_PLL 0x3 + +/* + * SGTL5000_CHIP_I2S_CTRL + */ +#define SGTL5000_I2S_SCLKFREQ_MASK 0x0100 +#define SGTL5000_I2S_SCLKFREQ_SHIFT 8 +#define SGTL5000_I2S_SCLKFREQ_WIDTH 1 +#define SGTL5000_I2S_SCLKFREQ_64FS 0x0 +#define SGTL5000_I2S_SCLKFREQ_32FS 0x1 /* Not for RJ mode */ +#define SGTL5000_I2S_MASTER 0x0080 +#define SGTL5000_I2S_SCLK_INV 0x0040 +#define SGTL5000_I2S_DLEN_MASK 0x0030 +#define SGTL5000_I2S_DLEN_SHIFT 4 +#define SGTL5000_I2S_DLEN_WIDTH 2 +#define SGTL5000_I2S_DLEN_32 0x0 +#define SGTL5000_I2S_DLEN_24 0x1 +#define SGTL5000_I2S_DLEN_20 0x2 +#define SGTL5000_I2S_DLEN_16 0x3 +#define SGTL5000_I2S_MODE_MASK 0x000c +#define SGTL5000_I2S_MODE_SHIFT 2 +#define SGTL5000_I2S_MODE_WIDTH 2 +#define SGTL5000_I2S_MODE_I2S_LJ 0x0 +#define SGTL5000_I2S_MODE_RJ 0x1 +#define SGTL5000_I2S_MODE_PCM 0x2 +#define SGTL5000_I2S_LRALIGN 0x0002 +#define SGTL5000_I2S_LRPOL 0x0001 /* set for which mode */ + +/* + * SGTL5000_CHIP_SSS_CTRL + */ +#define SGTL5000_DAP_MIX_LRSWAP 0x4000 +#define SGTL5000_DAP_LRSWAP 0x2000 +#define SGTL5000_DAC_LRSWAP 0x1000 +#define SGTL5000_I2S_OUT_LRSWAP 0x0400 +#define SGTL5000_DAP_MIX_SEL_MASK 0x0300 +#define SGTL5000_DAP_MIX_SEL_SHIFT 8 +#define SGTL5000_DAP_MIX_SEL_WIDTH 2 +#define SGTL5000_DAP_MIX_SEL_ADC 0x0 +#define SGTL5000_DAP_MIX_SEL_I2S_IN 0x1 +#define SGTL5000_DAP_SEL_MASK 0x00c0 +#define SGTL5000_DAP_SEL_SHIFT 6 +#define SGTL5000_DAP_SEL_WIDTH 2 +#define SGTL5000_DAP_SEL_ADC 0x0 +#define SGTL5000_DAP_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_MASK 0x0030 +#define SGTL5000_DAC_SEL_SHIFT 4 +#define SGTL5000_DAC_SEL_WIDTH 2 +#define SGTL5000_DAC_SEL_ADC 0x0 +#define SGTL5000_DAC_SEL_I2S_IN 0x1 +#define SGTL5000_DAC_SEL_DAP 0x3 +#define SGTL5000_I2S_OUT_SEL_MASK 0x0003 +#define SGTL5000_I2S_OUT_SEL_SHIFT 0 +#define SGTL5000_I2S_OUT_SEL_WIDTH 2 +#define SGTL5000_I2S_OUT_SEL_ADC 0x0 +#define SGTL5000_I2S_OUT_SEL_I2S_IN 0x1 +#define SGTL5000_I2S_OUT_SEL_DAP 0x3 + +/* + * SGTL5000_CHIP_ADCDAC_CTRL + */ +#define SGTL5000_VOL_BUSY_DAC_RIGHT 0x2000 +#define SGTL5000_VOL_BUSY_DAC_LEFT 0x1000 +#define SGTL5000_DAC_VOL_RAMP_EN 0x0200 +#define SGTL5000_DAC_VOL_RAMP_EXPO 0x0100 +#define SGTL5000_DAC_MUTE_RIGHT 0x0008 +#define SGTL5000_DAC_MUTE_LEFT 0x0004 +#define SGTL5000_ADC_HPF_FREEZE 0x0002 +#define SGTL5000_ADC_HPF_BYPASS 0x0001 + +/* + * SGTL5000_CHIP_DAC_VOL + */ +#define SGTL5000_DAC_VOL_RIGHT_MASK 0xff00 +#define SGTL5000_DAC_VOL_RIGHT_SHIFT 8 +#define SGTL5000_DAC_VOL_RIGHT_WIDTH 8 +#define SGTL5000_DAC_VOL_LEFT_MASK 0x00ff +#define SGTL5000_DAC_VOL_LEFT_SHIFT 0 +#define SGTL5000_DAC_VOL_LEFT_WIDTH 8 + +/* + * SGTL5000_CHIP_PAD_STRENGTH + */ +#define SGTL5000_PAD_I2S_LRCLK_MASK 0x0300 +#define SGTL5000_PAD_I2S_LRCLK_SHIFT 8 +#define SGTL5000_PAD_I2S_LRCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_SCLK_MASK 0x00c0 +#define SGTL5000_PAD_I2S_SCLK_SHIFT 6 +#define SGTL5000_PAD_I2S_SCLK_WIDTH 2 +#define SGTL5000_PAD_I2S_DOUT_MASK 0x0030 +#define SGTL5000_PAD_I2S_DOUT_SHIFT 4 +#define SGTL5000_PAD_I2S_DOUT_WIDTH 2 +#define SGTL5000_PAD_I2C_SDA_MASK 0x000c +#define SGTL5000_PAD_I2C_SDA_SHIFT 2 +#define SGTL5000_PAD_I2C_SDA_WIDTH 2 +#define SGTL5000_PAD_I2C_SCL_MASK 0x0003 +#define SGTL5000_PAD_I2C_SCL_SHIFT 0 +#define SGTL5000_PAD_I2C_SCL_WIDTH 2 + +/* + * SGTL5000_CHIP_ANA_ADC_CTRL + */ +#define SGTL5000_ADC_VOL_M6DB 0x0100 +#define SGTL5000_ADC_VOL_RIGHT_MASK 0x00f0 +#define SGTL5000_ADC_VOL_RIGHT_SHIFT 4 +#define SGTL5000_ADC_VOL_RIGHT_WIDTH 4 +#define SGTL5000_ADC_VOL_LEFT_MASK 0x000f +#define SGTL5000_ADC_VOL_LEFT_SHIFT 0 +#define SGTL5000_ADC_VOL_LEFT_WIDTH 4 + +/* + * SGTL5000_CHIP_ANA_HP_CTRL + */ +#define SGTL5000_HP_VOL_RIGHT_MASK 0x7f00 +#define SGTL5000_HP_VOL_RIGHT_SHIFT 8 +#define SGTL5000_HP_VOL_RIGHT_WIDTH 7 +#define SGTL5000_HP_VOL_LEFT_MASK 0x007f +#define SGTL5000_HP_VOL_LEFT_SHIFT 0 +#define SGTL5000_HP_VOL_LEFT_WIDTH 7 + +/* + * SGTL5000_CHIP_ANA_CTRL + */ +#define SGTL5000_LINE_OUT_MUTE 0x0100 +#define SGTL5000_HP_SEL_MASK 0x0040 +#define SGTL5000_HP_SEL_SHIFT 6 +#define SGTL5000_HP_SEL_WIDTH 1 +#define SGTL5000_HP_SEL_DAC 0x0 +#define SGTL5000_HP_SEL_LINE_IN 0x1 +#define SGTL5000_HP_ZCD_EN 0x0020 +#define SGTL5000_HP_MUTE 0x0010 +#define SGTL5000_ADC_SEL_MASK 0x0004 +#define SGTL5000_ADC_SEL_SHIFT 2 +#define SGTL5000_ADC_SEL_WIDTH 1 +#define SGTL5000_ADC_SEL_MIC 0x0 +#define SGTL5000_ADC_SEL_LINE_IN 0x1 +#define SGTL5000_ADC_ZCD_EN 0x0002 +#define SGTL5000_ADC_MUTE 0x0001 + +/* + * SGTL5000_CHIP_LINREG_CTRL + */ +#define SGTL5000_VDDC_MAN_ASSN_MASK 0x0040 +#define SGTL5000_VDDC_MAN_ASSN_SHIFT 6 +#define SGTL5000_VDDC_MAN_ASSN_WIDTH 1 +#define SGTL5000_VDDC_MAN_ASSN_VDDA 0x0 +#define SGTL5000_VDDC_MAN_ASSN_VDDIO 0x1 +#define SGTL5000_VDDC_ASSN_OVRD 0x0020 +#define SGTL5000_LINREG_VDDD_MASK 0x000f +#define SGTL5000_LINREG_VDDD_SHIFT 0 +#define SGTL5000_LINREG_VDDD_WIDTH 4 + +/* + * SGTL5000_CHIP_REF_CTRL + */ +#define SGTL5000_ANA_GND_MASK 0x01f0 +#define SGTL5000_ANA_GND_SHIFT 4 +#define SGTL5000_ANA_GND_WIDTH 5 +#define SGTL5000_ANA_GND_BASE 800 /* mv */ +#define SGTL5000_ANA_GND_STP 25 /*mv */ +#define SGTL5000_BIAS_CTRL_MASK 0x000e +#define SGTL5000_BIAS_CTRL_SHIFT 1 +#define SGTL5000_BIAS_CTRL_WIDTH 3 +#define SGTL5000_SMALL_POP 0x0001 + +/* + * SGTL5000_CHIP_MIC_CTRL + */ +#define SGTL5000_BIAS_R_MASK 0x0200 +#define SGTL5000_BIAS_R_SHIFT 8 +#define SGTL5000_BIAS_R_WIDTH 2 +#define SGTL5000_BIAS_R_off 0x0 +#define SGTL5000_BIAS_R_2K 0x1 +#define SGTL5000_BIAS_R_4k 0x2 +#define SGTL5000_BIAS_R_8k 0x3 +#define SGTL5000_BIAS_VOLT_MASK 0x0070 +#define SGTL5000_BIAS_VOLT_SHIFT 4 +#define SGTL5000_BIAS_VOLT_WIDTH 3 +#define SGTL5000_MIC_GAIN_MASK 0x0003 +#define SGTL5000_MIC_GAIN_SHIFT 0 +#define SGTL5000_MIC_GAIN_WIDTH 2 + +/* + * SGTL5000_CHIP_LINE_OUT_CTRL + */ +#define SGTL5000_LINE_OUT_CURRENT_MASK 0x0f00 +#define SGTL5000_LINE_OUT_CURRENT_SHIFT 8 +#define SGTL5000_LINE_OUT_CURRENT_WIDTH 4 +#define SGTL5000_LINE_OUT_CURRENT_180u 0x0 +#define SGTL5000_LINE_OUT_CURRENT_270u 0x1 +#define SGTL5000_LINE_OUT_CURRENT_360u 0x3 +#define SGTL5000_LINE_OUT_CURRENT_450u 0x7 +#define SGTL5000_LINE_OUT_CURRENT_540u 0xf +#define SGTL5000_LINE_OUT_GND_MASK 0x003f +#define SGTL5000_LINE_OUT_GND_SHIFT 0 +#define SGTL5000_LINE_OUT_GND_WIDTH 6 +#define SGTL5000_LINE_OUT_GND_BASE 800 /* mv */ +#define SGTL5000_LINE_OUT_GND_STP 25 +#define SGTL5000_LINE_OUT_GND_MAX 0x23 + +/* + * SGTL5000_CHIP_LINE_OUT_VOL + */ +#define SGTL5000_LINE_OUT_VOL_RIGHT_MASK 0x1f00 +#define SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT 8 +#define SGTL5000_LINE_OUT_VOL_RIGHT_WIDTH 5 +#define SGTL5000_LINE_OUT_VOL_LEFT_MASK 0x001f +#define SGTL5000_LINE_OUT_VOL_LEFT_SHIFT 0 +#define SGTL5000_LINE_OUT_VOL_LEFT_WIDTH 5 + +/* + * SGTL5000_CHIP_ANA_POWER + */ +#define SGTL5000_DAC_STEREO 0x4000 +#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000 +#define SGTL5000_STARTUP_POWERUP 0x1000 +#define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800 +#define SGTL5000_PLL_POWERUP 0x0400 +#define SGTL5000_LINEREG_D_POWERUP 0x0200 +#define SGTL5000_VCOAMP_POWERUP 0x0100 +#define SGTL5000_VAG_POWERUP 0x0080 +#define SGTL5000_ADC_STEREO 0x0040 +#define SGTL5000_REFTOP_POWERUP 0x0020 +#define SGTL5000_HP_POWERUP 0x0010 +#define SGTL5000_DAC_POWERUP 0x0008 +#define SGTL5000_CAPLESS_HP_POWERUP 0x0004 +#define SGTL5000_ADC_POWERUP 0x0002 +#define SGTL5000_LINE_OUT_POWERUP 0x0001 + +/* + * SGTL5000_CHIP_PLL_CTRL + */ +#define SGTL5000_PLL_INT_DIV_MASK 0xf800 +#define SGTL5000_PLL_INT_DIV_SHIFT 11 +#define SGTL5000_PLL_INT_DIV_WIDTH 5 +#define SGTL5000_PLL_FRAC_DIV_MASK 0x0700 +#define SGTL5000_PLL_FRAC_DIV_SHIFT 0 +#define SGTL5000_PLL_FRAC_DIV_WIDTH 11 + +/* + * SGTL5000_CHIP_CLK_TOP_CTRL + */ +#define SGTL5000_INT_OSC_EN 0x0800 +#define SGTL5000_INPUT_FREQ_DIV2 0x0008 + +/* + * SGTL5000_CHIP_ANA_STATUS + */ +#define SGTL5000_HP_LRSHORT 0x0200 +#define SGTL5000_CAPLESS_SHORT 0x0100 +#define SGTL5000_PLL_LOCKED 0x0010 + +/* + * SGTL5000_CHIP_SHORT_CTRL + */ +#define SGTL5000_LVLADJR_MASK 0x7000 +#define SGTL5000_LVLADJR_SHIFT 12 +#define SGTL5000_LVLADJR_WIDTH 3 +#define SGTL5000_LVLADJL_MASK 0x0700 +#define SGTL5000_LVLADJL_SHIFT 8 +#define SGTL5000_LVLADJL_WIDTH 3 +#define SGTL5000_LVLADJC_MASK 0x0070 +#define SGTL5000_LVLADJC_SHIFT 4 +#define SGTL5000_LVLADJC_WIDTH 3 +#define SGTL5000_LR_SHORT_MOD_MASK 0x000c +#define SGTL5000_LR_SHORT_MOD_SHIFT 2 +#define SGTL5000_LR_SHORT_MOD_WIDTH 2 +#define SGTL5000_CM_SHORT_MOD_MASK 0x0003 +#define SGTL5000_CM_SHORT_MOD_SHIFT 0 +#define SGTL5000_CM_SHORT_MOD_WIDTH 2 + +/* + *SGTL5000_CHIP_ANA_TEST2 + */ +#define SGTL5000_MONO_DAC 0x1000 + +/* + * SGTL5000_DAP_CTRL + */ +#define SGTL5000_DAP_MIX_EN 0x0010 +#define SGTL5000_DAP_EN 0x0001 + +#define SGTL5000_SYSCLK 0x00 +#define SGTL5000_LRCLK 0x01 + +#endif
On Wed, Dec 08, 2010 at 10:06:29AM +0800, zhaoming.zeng@freescale.com wrote:
From: Zeng Zhaoming zhaoming.zeng@freescale.com
Broadly speaking this is OK - there's quite a few comments below but they're mostly fairly small and local things rather than major structural issues. The one thing that needs real work is the power management which is fairly non standard and seems to follow a mix of hard coding and DAPM. A lot of the issues here seem to be due to some rather nasty hardware issues so there's likely to be limits on how nice the software can be.
For the subject line, please use a prefix such as "ASoC: " like other patches do - in general check the changelogs for the area you're submitting against for things like this.
config SND_SOC_WM9090 tristate
+config SND_SOC_SGTL5000
- tristate
Keep both Kconfig and Makefile sorted, and remember to add your CODEC to SND_SOC_ALL_CODECS.
- struct regulator *supplies[SGTL5000_SUPPLY_NUM];
- int regulator_volt[SGTL5000_SUPPLY_NUM];
- 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 */
You appear to be keeping two copies of the regulators and their voltages. TBH I'm not clear why you're caching the voltages - you may as well just read them when you need them, you only refer to them at probe time anyway.
+static int dac_info_volsw(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
+{
I think you're looking for SOC_DOUBLE_R_SX_TLV(), or possibly a variant of SOC_DOUBLE_S8_TLV()?
+static const char *mic_gain_text[] = {
- "0dB", "20dB", "30dB", "40dB"
+};
+static const char *adc_m6db_text[] = {
- "No Change", "Reduced by 6dB"
+};
Provide gain information using TLV data.
+static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
- SOC_ENUM("MIC GAIN", mic_gain),
Mic Volume.
- SOC_ENUM("Capture Vol Reduction", adc_m6db),
Capture Volume.
+static int __sgtl5000_digital_mute(struct snd_soc_codec *codec, int mute) +{
Using __ like this isn't idomiatic for Linux, and given that...
+static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{
- struct snd_soc_codec *codec = codec_dai->codec;
- return __sgtl5000_digital_mute(codec, mute);
+}
...this simply passes through to the above function it'd be better to just implement directly in here.
- /* 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;
- }
This looks wrong - you're claiming to support four formats with two different register settings. It looks like you can only actually invert one of the clocks so should remove the options for the other one.
+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;
LRCLK should be configured in hw_params().
+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_codec *codec = rtd->codec;
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
- int reg;
- reg = snd_soc_read(codec, SGTL5000_CHIP_DIG_POWER);
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
reg |= SGTL5000_I2S_IN_POWERUP;
- else
reg |= SGTL5000_I2S_OUT_POWERUP;
- snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
This looks like you should have AIF widgets in DAPM.
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
reg |= SGTL5000_ADC_POWERUP;
if (sgtl5000->capture_channels == 1)
reg &= ~SGTL5000_ADC_STEREO;
else
reg |= SGTL5000_ADC_STEREO;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
- }
Why is this here - it looks like it should be in hw_params()?
- /* 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) {
Just set symmetric_rates in your DAI (in fact you're doing this) and delete all this code (and associated variables and code elsewhere). The core will manage the symmetry constraints for you.
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
ana_pwr = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
ana_pwr &= ~(SGTL5000_ADC_POWERUP | SGTL5000_ADC_STEREO);
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
- }
The ADC power at least looks like it should be handled by DAPM.
+static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- 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;
A comment explaining what's going on here would be helpful - it looks like the device is running internally at 32kHz or higher and downsampling somehow to present lower output frequencies?
- default:
dev_err(codec->dev, "sample rate %d not supported\n", sgtl5000->lrclk);
return -EFAULT;
-EPARM or something. -EFAULT means a memory access error.
- /* SGTL5000 rev1 has a IC bug to prevent switching to MCLK from PLL. */
- if (!sgtl5000->master) {
I'd expect to see this code returning an error if something triggers a switch from MCLK to PLL when it's not supported.
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;
No need for \ outside of macros.
- if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
snd_soc_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl);
reg = snd_soc_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL);
if (div2)
reg |= SGTL5000_INPUT_FREQ_DIV2;
else
reg &= ~SGTL5000_INPUT_FREQ_DIV2;
snd_soc_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg);
snd_soc_update_bits().
+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 = snd_soc_read(codec, SGTL5000_CHIP_MIC_CTRL);
- if ((reg & SGTL5000_BIAS_R_MASK) != bias_r) {
reg &= ~SGTL5000_BIAS_R_MASK;
reg |= bias_r;
snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, reg);
- }
This looks a lot like snd_soc_update_bits(), and it looks a lot like the mic bias should be a DAPM widget since...
- switch (level) {
- case SND_SOC_BIAS_ON:
sgtl5000_mic_bias(codec, 1);
snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL,
SGTL5000_BIAS_R_MASK, SGTL5000_BIAS_R_MASK);
...as things stand the mic bias is being powered up even for playback.
- case SND_SOC_BIAS_PREPARE: /* partial On */
snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL,
SGTL5000_BIAS_R_MASK, 0);
/* must power up hp/line out before vag & dac to
avoid pops. */
reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
Oh dear, that sounds very buggy. I'd suggest using events on DAPM nodes for this stuff (perhaps just a big "output" widget) - it's more idiomatic if nothing else.
- case SND_SOC_BIAS_STANDBY:
/* soc calls digital_mute to unmute before record but doesn't
call digital_mute to mute after record. */
__sgtl5000_digital_mute(codec, 1);
Hrm? The mute control is for the DAC and has nothing to do with recording. What's the actual issue here - if there is an issue here it should be fixed in the framework rather than bodged in an individual driver.
/* save ANA POWER register value for resume */
cache[SGTL5000_CHIP_ANA_POWER >> 1] = ana_pwr;
Why would you do this on _BIAS_OFF? This isn't a suspend/resume thing, when the power is brought back up from _OFF the framework will make a new decision about what to power up.
+#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)
SNDRV_PCM_RATE_8000_96000.
+static int sgtl5000_volatile_register(unsigned int reg) +{
- if (reg == SGTL5000_CHIP_ID ||
reg == SGTL5000_CHIP_ADCDAC_CTRL ||
reg == SGTL5000_CHIP_ANA_STATUS)
return 1;
- return 0;
Use a switch statement for legibility.
- /* 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);
The CODEC will never be in any state above STANDBY at suspend.
+static int sgtl5000_remove(struct snd_soc_codec *codec) +{
- if (codec->control_data)
sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
You're guranteed to have control data.
- for (i = 0; i < SGTL5000_SUPPLY_NUM; i++) {
struct regulator *reg;
reg = regulator_get(&client->dev, supply_names[i]);
if (IS_ERR(reg))
continue;
sgtl5000->regulator_volt[i] = regulator_get_voltage(reg) / 1000;
regulator_enable(reg);
sgtl5000->supplies[i] = reg;
- }
- sgtl5000->regulator_volt[VDDA] = 3300;
- sgtl5000->regulator_volt[VDDIO] = 3300;
You're not actually doing anything to make that be the case... You should either set the voltage here, check the voltage and error out if it's wrong or just ignore the voltages and assume the hardware is correct.
- msleep(1);
This delay looks like it's in the wrong place?
- for (i=0; i < SGTL5000_SUPPLY_NUM; i++) {
if( !sgtl5000->supplies[i] )
continue;
Run your patch through checkpatch - the space placement here isn't the usual kernel style.
+static const struct i2c_device_id sgtl5000_id[] = {
- {"sgtl5000-i2c", 0},
No -i2c - the bus type is already encoded in the bus type.
+static struct i2c_driver sgtl5000_i2c_driver = {
- .driver = {
.name = "sgtl5000-i2c",
Likewise.
On Wed 2010-12-08 05:11:55, Mark Brown wrote:
On Wed, Dec 08, 2010 at 10:06:29AM +0800, zhaoming.zeng@freescale.com wrote:
From: Zeng Zhaoming zhaoming.zeng@freescale.com
Broadly speaking this is OK - there's quite a few comments below but they're mostly fairly small and local things rather than major structural issues. The one thing that needs real work is the power management which is fairly non standard and seems to follow a mix of hard coding and DAPM. A lot of the issues here seem to be due to some rather nasty hardware issues so there's likely to be limits on how nice the software can be.
For the subject line, please use a prefix such as "ASoC: " like other patches do - in general check the changelogs for the area you're submitting against for things like this.
hi, Mark, thanks for so carefully review, and so many suggestion.
config SND_SOC_WM9090 tristate
+config SND_SOC_SGTL5000
- tristate
Keep both Kconfig and Makefile sorted, and remember to add your CODEC to SND_SOC_ALL_CODECS.
- struct regulator *supplies[SGTL5000_SUPPLY_NUM];
- int regulator_volt[SGTL5000_SUPPLY_NUM];
- 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 */
You appear to be keeping two copies of the regulators and their voltages. TBH I'm not clear why you're caching the voltages - you may as well just read them when you need them, you only refer to them at probe time anyway.
Sorry, I forget to clean up this code.
+static int sgtl5000_pcm_hw_params(struct snd_pcm_substream
*substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- 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;
A comment explaining what's going on here would be helpful - it looks like the device is running internally at 32kHz or higher and downsampling somehow to present lower output frequencies?
Right, we need to downsampling for low frequencies.
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);
The CODEC will never be in any state above STANDBY at suspend.
Is there any documentation describe the state machine?
- for (i = 0; i < SGTL5000_SUPPLY_NUM; i++) {
struct regulator *reg;
reg = regulator_get(&client->dev, supply_names[i]);
if (IS_ERR(reg))
continue;
sgtl5000->regulator_volt[i] = regulator_get_voltage(reg)
/ 1000;
regulator_enable(reg);
sgtl5000->supplies[i] = reg;
- }
- sgtl5000->regulator_volt[VDDA] = 3300;
- sgtl5000->regulator_volt[VDDIO] = 3300;
Sorry again, forget to cleanup.
- msleep(1);
This delay looks like it's in the wrong place?
The SGTL5000 has an internal reset that is deasserted 8 SYS_MCLK cycles after all power rails have been brought up. After this time communication can start.
So I think delay 1ms is safe for the next operations.
On Thu, Dec 09, 2010 at 03:22:35AM +0800, Zeng Zhaoming wrote:
On Wed 2010-12-08 05:11:55, Mark Brown wrote:
On Wed, Dec 08, 2010 at 10:06:29AM +0800, zhaoming.zeng@freescale.com
- sgtl5000_set_bias_level(codec, codec->suspend_bias_level);
The CODEC will never be in any state above STANDBY at suspend.
Is there any documentation describe the state machine?
Not outside the source code.
- msleep(1);
This delay looks like it's in the wrong place?
The SGTL5000 has an internal reset that is deasserted 8 SYS_MCLK cycles after all power rails have been brought up. After this time communication can start.
So I think delay 1ms is safe for the next operations.
It needs to be tied much more clearly to the enables, then - that's not clear at the minute, especially given that there's no code in the same function that interacts with the hardware.
zhaoming.zeng@freescale.com writes:
Hi,
See comments inline.
From: Zeng Zhaoming zhaoming.zeng@freescale.com
Add Freescale SGTL5000 codec support.
Signed-off-by: Zeng Zhaoming zhaoming.zeng@freescale.com
sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/sgtl5000.c | 1052 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/sgtl5000.h | 403 +++++++++++++++++ 4 files changed, 1460 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/sgtl5000.c create mode 100644 sound/soc/codecs/sgtl5000.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3b5690d..f12ef39 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -318,3 +318,6 @@ config SND_SOC_WM2000
config SND_SOC_WM9090 tristate
+config SND_SOC_SGTL5000
- tristate
I think that keeping alphabetical order is welcomed
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index f67a2d6..116ec3d 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -65,6 +65,7 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-jz4740-codec-objs := jz4740.o +snd-soc-sgtl5000-objs := sgtl5000.o
# Amp snd-soc-max9877-objs := max9877.o @@ -139,6 +140,7 @@ obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o +obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
# Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c new file mode 100644 index 0000000..a053f19 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.c @@ -0,0 +1,1052 @@ +/*
- sgtl5000.c -- SGTL5000 ALSA SoC Audio driver
- Copyright 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/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.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"
+static const u16 sgtl5000_regs[] = {
- 0xa011, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000, 0x0010, 0x0000,
- 0x0010, 0x0000, 0x0010, 0x0000, 0x0000, 0x0000, 0x323c, 0x0000,
- 0x3c3c, 0x0000, 0x3c3c, 0x0000, 0x555f, 0x0000, 0x0000, 0x0000,
- 0x0000, 0x0000, 0x0000, 0x0000, 0x408c, 0x0000, 0x0008, 0x0000,
- 0x0000, 0x0000, 0x1818, 0x0000, 0x0111, 0x0000, 0x0000, 0x0000,
- 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0404, 0x0000,
- 0x7060, 0x0000, 0x5000, 0x0000, 0x0000, 0x0000, 0x0017, 0x0000,
- 0x01c0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+};
+enum sgtl5000_regulator_supplies {
- VDDA,
- VDDIO,
- VDDD,
- SGTL5000_SUPPLY_NUM
+};
+static const char* supply_names[SGTL5000_SUPPLY_NUM] = {
- "VDDA",
- "VDDIO",
- "VDDD"
+};
+struct sgtl5000_priv {
- int sysclk;
- int master;
- int fmt;
- int rev;
iirc rev is used only in sgtl5000_probe(). What about removing it from here ?
- int lrclk;
- int capture_channels;
- int playback_active;
- int capture_active;
- struct regulator *supplies[SGTL5000_SUPPLY_NUM];
- int regulator_volt[SGTL5000_SUPPLY_NUM];
- 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 */
looks like you're not using them (reg_vdd*, vdd*)
- struct snd_pcm_substream *master_substream;
- struct snd_pcm_substream *slave_substream;
+};
+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("Capture Mux", adc_enum);
+static const struct snd_kcontrol_new dac_mux = +SOC_DAPM_ENUM("Headphone 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("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux),
- SND_SOC_DAPM_MUX("Headphone 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[] = {
- {"Capture Mux", "LINE_IN", "LINE_IN"},
- {"Capture Mux", "MIC_IN", "MIC_IN"},
- {"ADC", NULL, "Capture Mux"},
- {"Headphone Mux", "DAC", "DAC"},
- {"Headphone Mux", "LINE_IN", "LINE_IN"},
- {"LO", NULL, "DAC"},
- {"HP", NULL, "Headphone Mux"},
- {"LINE_OUT", NULL, "LO"},
- {"HP_OUT", NULL, "HP"},
+};
+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 = snd_soc_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;
- snd_soc_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"
+};
What about using TLV controls instead of specifying db values like this?
+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),
"Mic Gain Volume" or "MIC GAIN Volume" please.
- SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0),
- SOC_ENUM("Capture Vol Reduction", adc_m6db),
same here. Not Vol but somethig like Capture Reduction Volume please.
- {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM 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 Playback Volume", SGTL5000_CHIP_ANA_HP_CTRL, 0, 8, 0x7f, 1),
+};
+static int __sgtl5000_digital_mute(struct snd_soc_codec *codec, int mute) +{
- u16 adcdac_ctrl = SGTL5000_DAC_MUTE_LEFT | SGTL5000_DAC_MUTE_RIGHT;
- if (mute)
snd_soc_update_bits(codec, SGTL5000_CHIP_ADCDAC_CTRL,
adcdac_ctrl, adcdac_ctrl);
- else
snd_soc_update_bits(codec, SGTL5000_CHIP_ADCDAC_CTRL,
adcdac_ctrl, 0);
- return 0;
+}
+static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{
- struct snd_soc_codec *codec = codec_dai->codec;
- return __sgtl5000_digital_mute(codec, mute);
+}
+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;
- 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;
- }
- snd_soc_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_codec *codec = rtd->codec;
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
- int reg;
- reg = snd_soc_read(codec, SGTL5000_CHIP_DIG_POWER);
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
reg |= SGTL5000_I2S_IN_POWERUP;
- else
reg |= SGTL5000_I2S_OUT_POWERUP;
- snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
reg |= SGTL5000_ADC_POWERUP;
if (sgtl5000->capture_channels == 1)
reg &= ~SGTL5000_ADC_STEREO;
else
reg |= SGTL5000_ADC_STEREO;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
- }
I was wondering about using DAPM for this. What do you think ?
- 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_codec *codec = rtd->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++;
Same here. Should dapm be used instead of all this _active++ / _active-- stuff ?
- /* 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;
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_codec *codec = rtd->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 = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
ana_pwr &= ~(SGTL5000_ADC_POWERUP | SGTL5000_ADC_STEREO);
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
- }
- dig_pwr = snd_soc_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;
- snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, dig_pwr);
- if (!sgtl5000->playback_active && !sgtl5000->capture_active) {
reg = snd_soc_read(codec, SGTL5000_CHIP_I2S_CTRL);
reg &= ~SGTL5000_I2S_MASTER;
snd_soc_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_codec *codec = rtd->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;
- if (!sgtl5000->sysclk) {
dev_err(codec->dev, "%s: set sysclk first!\n", __func__);
return -EFAULT;
- }
- if (substream == sgtl5000->slave_substream)
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:
dev_err(codec->dev, "sample rate %d not supported\n", 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 = snd_soc_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;
- }
- dev_dbg(codec->dev, "fs=%d,clk_ctl=%d,pll_ctl=%d,i2s_ctl=%d,div2=%d\n",
sgtl5000->lrclk, clk_ctl, pll_ctl, i2s_ctl, div2);
- if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
snd_soc_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl);
reg = snd_soc_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL);
if (div2)
reg |= SGTL5000_INPUT_FREQ_DIV2;
else
reg &= ~SGTL5000_INPUT_FREQ_DIV2;
snd_soc_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg);
reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
reg |= SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
- }
- snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl);
- snd_soc_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 = snd_soc_read(codec, SGTL5000_CHIP_MIC_CTRL);
- if ((reg & SGTL5000_BIAS_R_MASK) != bias_r) {
reg &= ~SGTL5000_BIAS_R_MASK;
reg |= bias_r;
snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, reg);
- }
+}
+static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
+{
- u16 reg, ana_pwr;
- u16 *cache = codec->reg_cache;
- if (codec->bias_level == level)
return 0;
- switch (level) {
- case SND_SOC_BIAS_ON:
sgtl5000_mic_bias(codec, 1);
snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL,
SGTL5000_BIAS_R_MASK, SGTL5000_BIAS_R_MASK);
snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP);
__sgtl5000_digital_mute(codec, 0);
break;
- case SND_SOC_BIAS_PREPARE: /* partial On */
snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL,
SGTL5000_BIAS_R_MASK, 0);
/* must power up hp/line out before vag & dac to
avoid pops. */
reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
reg &= ~SGTL5000_VAG_POWERUP;
reg |= SGTL5000_DAC_POWERUP;
reg |= SGTL5000_HP_POWERUP;
reg |= SGTL5000_LINE_OUT_POWERUP;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
snd_soc_update_bits(codec, SGTL5000_CHIP_DIG_POWER,
SGTL5000_DAC_EN, SGTL5000_DAC_EN);
break;
- case SND_SOC_BIAS_STANDBY:
/* soc calls digital_mute to unmute before record but doesn't
call digital_mute to mute after record. */
__sgtl5000_digital_mute(codec, 1);
snd_soc_update_bits(codec, SGTL5000_CHIP_MIC_CTRL,
SGTL5000_BIAS_R_MASK, 0);
reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
if (reg & SGTL5000_VAG_POWERUP) {
reg &= ~SGTL5000_VAG_POWERUP;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
}
reg &= ~SGTL5000_DAC_POWERUP;
reg &= ~SGTL5000_HP_POWERUP;
reg &= ~SGTL5000_LINE_OUT_POWERUP;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
reg = snd_soc_read(codec, SGTL5000_CHIP_DIG_POWER);
reg &= ~SGTL5000_DAC_EN;
snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, reg);
break;
- case SND_SOC_BIAS_OFF: /* Off, without power */
/* mute hp/Line_out first to avoid pops. */
__sgtl5000_digital_mute(codec, 1);
sgtl5000_mic_bias(codec, 0);
reg = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
ana_pwr = reg;
reg &= ~SGTL5000_VAG_POWERUP;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
reg &= ~SGTL5000_HP_POWERUP;
reg &= ~SGTL5000_LINE_OUT_POWERUP;
reg &= ~SGTL5000_DAC_POWERUP;
reg &= ~SGTL5000_ADC_POWERUP;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, reg);
/* save ANA POWER register value for resume */
cache[SGTL5000_CHIP_ANA_POWER >> 1] = ana_pwr;
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)
Looks like it the same as SNDRV_PCM_RATE_8000_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,
+};
+static struct snd_soc_dai_driver 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,
- .symmetric_rates = 1,
+};
+static int sgtl5000_volatile_register(unsigned int reg) +{
- if (reg == SGTL5000_CHIP_ID ||
reg == SGTL5000_CHIP_ADCDAC_CTRL ||
reg == SGTL5000_CHIP_ANA_STATUS)
return 1;
- return 0;
+}
+static int sgtl5000_suspend(struct snd_soc_codec *codec, pm_message_t state) +{
- sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
- return 0;
+}
+static int sgtl5000_restore_reg(struct snd_soc_codec *codec, unsigned int reg) +{
- u16 *cache = codec->reg_cache;
- return snd_soc_write(codec, reg, cache[reg >> 1]);
+}
+static int sgtl5000_resume(struct snd_soc_codec *codec) +{
- int i;
- /* Restore refs first in same order as in sgtl5000_probe */
- 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 = 0; i < ARRAY_SIZE(sgtl5000_regs); i++)
sgtl5000_restore_reg(codec, i);
- snd_soc_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 int sgtl5000_probe(struct snd_soc_codec *codec) +{
- struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
- u16 reg, ana_pwr, lreg_ctrl;
- int vag;
- int ret;
- int vddd, vdda, vddio;
- ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C);
- if (ret < 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
return ret;
- }
- vddd = sgtl5000->regulator_volt[VDDD];
- vdda = sgtl5000->regulator_volt[VDDA];
- vddio = sgtl5000->regulator_volt[VDDIO];
- reg = snd_soc_read(codec, SGTL5000_CHIP_ID);
- if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) !=
SGTL5000_PARTID_PART_ID) {
dev_err(codec->dev, "Device with ID register %x is not a sgtl5000\n", reg);
return -ENODEV;
- }
- sgtl5000->rev = (reg & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT;
- dev_info(codec->dev, "sgtl5000 revision %d\n", sgtl5000->rev);
- /* reset value */
- ana_pwr = SGTL5000_DAC_STEREO |
SGTL5000_LINREG_SIMPLE_POWERUP |
SGTL5000_STARTUP_POWERUP |
SGTL5000_ADC_STEREO | SGTL5000_REFTOP_POWERUP;
- lreg_ctrl = 0;
- /* workaround for rev 0x11: use vddd linear regulator */
- if (!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 (vddio < 3100 && vdda < 3100) {
/* Enable VDDC charge pump */
ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP;
- }
- if (vddio >= 3100 && vdda >= 3100) {
/* VDDC use VDDIO rail */
lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD;
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 = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
- ana_pwr |= reg & (SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP);
- /* set ADC/DAC ref voltage to vdda / 2 */
- vag = 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;
- /* set line out ref voltage to vddio / 2 */
- vag = 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;
- snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl);
- snd_soc_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;
snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
msleep(10);
- }
- snd_soc_write(codec, SGTL5000_CHIP_REF_CTRL,
vag << SGTL5000_ANA_GND_SHIFT);
- snd_soc_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL,
vag << SGTL5000_LINE_OUT_GND_SHIFT |
SGTL5000_LINE_OUT_CURRENT_360u <<
SGTL5000_LINE_OUT_CURRENT_SHIFT);
- snd_soc_write(codec, SGTL5000_CHIP_SHORT_CTRL, 0);
- snd_soc_write(codec, SGTL5000_CHIP_SSS_CTRL,
SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT);
- snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER, 0);
- snd_soc_write(codec, SGTL5000_CHIP_ADCDAC_CTRL,
SGTL5000_DAC_VOL_RAMP_EN |
SGTL5000_DAC_MUTE_RIGHT |
SGTL5000_DAC_MUTE_LEFT);
- snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f);
- reg = snd_soc_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);
- snd_soc_write(codec, SGTL5000_CHIP_ANA_ADC_CTRL, reg);
- snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL,
SGTL5000_LINE_OUT_MUTE |
SGTL5000_HP_MUTE |
SGTL5000_HP_ZCD_EN |
SGTL5000_ADC_ZCD_EN);
are theses volumes/mute stuff really needed ?
- snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, 0);
- snd_soc_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, 0);
- /* disable DAP */
- snd_soc_write(codec, SGTL5000_DAP_CTRL, 0);
- snd_soc_add_controls(codec, sgtl5000_snd_controls,
ARRAY_SIZE(sgtl5000_snd_controls));
- 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);
- sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- return 0;
+}
+static int sgtl5000_remove(struct snd_soc_codec *codec) +{
- if (codec->control_data)
sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
- snd_soc_dapm_free(codec);
- return 0;
+}
+struct snd_soc_codec_driver sgtl5000_driver = {
- .probe = sgtl5000_probe,
- .remove = sgtl5000_remove,
- .suspend = sgtl5000_suspend,
- .resume = sgtl5000_resume,
- .set_bias_level = sgtl5000_set_bias_level,
- .reg_cache_size = ARRAY_SIZE(sgtl5000_regs),
- .reg_word_size = sizeof(u16),
- .reg_cache_default = sgtl5000_regs,
missing ".reg_cache_step = 2,"
- .volatile_register = sgtl5000_volatile_register,
+};
+static __devinit int sgtl5000_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
+{
- struct sgtl5000_priv *sgtl5000;
- int ret;
- int i;
- sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL);
- if (!sgtl5000)
return -ENOMEM;
- i2c_set_clientdata(client, sgtl5000);
- for (i = 0; i < SGTL5000_SUPPLY_NUM; i++) {
struct regulator *reg;
reg = regulator_get(&client->dev, supply_names[i]);
if (IS_ERR(reg))
continue;
sgtl5000->regulator_volt[i] = regulator_get_voltage(reg) / 1000;
regulator_enable(reg);
sgtl5000->supplies[i] = reg;
- }
- sgtl5000->regulator_volt[VDDA] = 3300;
- sgtl5000->regulator_volt[VDDIO] = 3300;
uh ? if you have some regulator declared for vdda/vddio why overriding their value ? 1.8V is also a valid voltage value.
Arnaud
participants (4)
-
Arnaud Patard
-
Mark Brown
-
Zeng Zhaoming
-
zhaoming.zengļ¼ freescale.com