diff options
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r-- | sound/soc/codecs/Kconfig | 37 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 18 | ||||
-rw-r--r-- | sound/soc/codecs/ak4647.c | 799 | ||||
-rw-r--r-- | sound/soc/codecs/ak4647.h | 92 | ||||
-rw-r--r-- | sound/soc/codecs/ak5702.c | 607 | ||||
-rw-r--r-- | sound/soc/codecs/ak5702.h | 130 | ||||
-rw-r--r-- | sound/soc/codecs/bluetooth.c | 161 | ||||
-rw-r--r-- | sound/soc/codecs/cs42888.c | 1196 | ||||
-rw-r--r-- | sound/soc/codecs/cs42888.h | 31 | ||||
-rw-r--r-- | sound/soc/codecs/mxs-adc-codec.c | 1220 | ||||
-rw-r--r-- | sound/soc/codecs/mxs-adc-codec.h | 87 | ||||
-rw-r--r-- | sound/soc/codecs/mxs_spdif.c | 454 | ||||
-rw-r--r-- | sound/soc/codecs/mxs_spdif.h | 172 | ||||
-rw-r--r-- | sound/soc/codecs/sgtl5000.c | 1295 | ||||
-rw-r--r-- | sound/soc/codecs/sgtl5000.h | 406 | ||||
-rw-r--r-- | sound/soc/codecs/stmp378x_codec.c | 1051 | ||||
-rw-r--r-- | sound/soc/codecs/stmp378x_codec.h | 87 | ||||
-rw-r--r-- | sound/soc/codecs/stmp3xxx_spdif.c | 455 | ||||
-rw-r--r-- | sound/soc/codecs/stmp3xxx_spdif.h | 37 | ||||
-rw-r--r-- | sound/soc/codecs/wm8350.c | 39 | ||||
-rw-r--r-- | sound/soc/codecs/wm8580.c | 166 | ||||
-rw-r--r-- | sound/soc/codecs/wm8580.h | 9 |
22 files changed, 8530 insertions, 19 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index bbc97fd76648..dd28cad09934 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_CS4270 if I2C + select SND_SOC_CS42888 if I2C select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C @@ -74,6 +75,10 @@ config SND_SOC_AK4104 config SND_SOC_AK4535 tristate +config SND_SOC_AK5702 + tristate + depends on I2C + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate @@ -86,6 +91,9 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 +config SND_SOC_CS42888 + tristate + config SND_SOC_L3 tristate @@ -176,3 +184,32 @@ config SND_SOC_WM9712 config SND_SOC_WM9713 tristate + +config SND_SOC_SGTL5000 + tristate + depends on I2C + +config SND_SOC_AK4647 + tristate + depends on I2C + +config SND_SOC_STMP378X_CODEC + tristate + depends on SND_SOC + +config SND_SOC_STMP3XXX_SPDIF + tristate + depends on SND_SOC + +config SND_SOC_MXS_ADC_CODEC + tristate + depends on SND_SOC + +config SND_SOC_BLUETOOTH + tristate + depends on SND_SOC + +config SND_SOC_MXS_SPDIF + tristate + depends on SND_SOC + diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8b7530546f4d..8fd4da08392c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -3,7 +3,9 @@ snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o +snd-soc-ak5702-objs := ak5702.o snd-soc-cs4270-objs := cs4270.o +snd-soc-cs42888-objs := cs42888.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-spdif-objs := spdif_transciever.o @@ -34,13 +36,22 @@ snd-soc-wm9081-objs := wm9081.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o +snd-soc-sgtl5000-objs := sgtl5000.o +snd-soc-ak4647-objs := ak4647.o +snd-soc-stmp378x-codec-objs := stmp378x_codec.o +snd-soc-stmp3xxx-spdif-objs := stmp3xxx_spdif.o +snd-soc-bluetooth-objs := bluetooth.o +snd-soc-mxs-spdif-objs := mxs_spdif.o +snd-soc-mxs-adc-objs := mxs-adc-codec.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_AK5702) += snd-soc-ak5702.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_CS42888) += snd-soc-cs42888.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o @@ -71,3 +82,10 @@ obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o 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_SGTL5000) += snd-soc-sgtl5000.o +obj-$(CONFIG_SND_SOC_AK4647) += snd-soc-ak4647.o +obj-$(CONFIG_SND_SOC_STMP378X_CODEC) += snd-soc-stmp378x-codec.o +obj-$(CONFIG_SND_SOC_STMP3XXX_SPDIF) += snd-soc-stmp3xxx-spdif.o +obj-$(CONFIG_SND_SOC_BLUETOOTH) += snd-soc-bluetooth.o +obj-$(CONFIG_SND_SOC_MXS_SPDIF) += snd-soc-mxs-spdif.o +obj-$(CONFIG_SND_SOC_MXS_ADC_CODEC) += snd-soc-mxs-adc.o diff --git a/sound/soc/codecs/ak4647.c b/sound/soc/codecs/ak4647.c new file mode 100644 index 000000000000..c3d14e99be0e --- /dev/null +++ b/sound/soc/codecs/ak4647.c @@ -0,0 +1,799 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ak4647.c + * @brief Driver for AK4647 + * + * @ingroup Sound + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/i2c.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 <sound/tlv.h> + +#include "ak4647.h" + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +static struct i2c_client *ak4647_i2c_client; + +int ak4647_read_reg(unsigned int reg, u8 *value) +{ + s32 retval; + retval = i2c_smbus_read_byte_data(ak4647_i2c_client, reg); + if (retval < 0) { + pr_err("%s:read reg errorr:reg=%x,val=%x\n", + __func__, reg, retval); + return -1; + } else { + *value = (u8) retval; + return 0; + } +} + +int ak4647_write_reg(unsigned int reg, u8 value) +{ + if (i2c_smbus_write_byte_data(ak4647_i2c_client, reg, value) < 0) { + pr_err("%s:write reg errorr:reg=%x,val=%x\n", + __func__, reg, value); + return -1; + } + return 0; +} + +static unsigned int ak4647_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 value; + ak4647_read_reg(reg, &value); + return value; +} + +static int ak4647_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + return ak4647_write_reg(reg, value); +} + +#define DEBUG_AK4647 0 + +#if DEBUG_AK4647 + +static char *ak4647_reg_names[] = { + "AK4647_PM1", + "AK4647_PM2", + "AK4647_SIG1", + "AK4647_SIG2", + "AK4647_MODE1", + "AK4647_MODE2", + "AK4647_TIMER", + "AK4647_ALC1", + "AK4647_ALC2", + "AK4647_LEFT_INPUT_VOLUME", + "AK4647_LEFT_DGT_VOLUME", + "AK4647_ALC3", + "AK4647_RIGHT_INPUT_VOLUME", + "AK4647_RIGHT_DGT_VOLUME", + "AK4647_MODE3", + "AK4647_MODE4", + "AK4647_PM3", + "AK4647_DGT_FIL_SEL", + "AK4647_FIL3_COEF0", + "AK4647_FIL3_COEF1", + "AK4647_FIL3_COEF2", + "AK4647_FIL3_COEF3", + "AK4647_EQ_COEF0", + "AK4647_EQ_COEF1", + "AK4647_EQ_COEF2", + "AK4647_EQ_COEF3", + "AK4647_EQ_COEF4", + "AK4647_EQ_COEF5", + "AK4647_FIL1_COEF0", + "AK4647_FIL1_COEF1", + "AK4647_FIL1_COEF2", + "AK4647_FIL1_COEF3", +}; + +static void dump_all_regs(void) +{ + int i; + u8 value; + + for (i = AK4647_REG_START; i < AK4647_REG_NUMBER; i++) { + ak4647_read_reg(i, &value); + pr_info("%s = 0x%x\n", ak4647_reg_names[i], value); + } +} +#endif + +static int loopback_xhandle_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int loopback_xhandle_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +static const char *ak4647_hp_out[] = { "Stereo", "Mono" }; + +static const char *ak4647_left_in[] = { "LIN1", "LIN2" }; + +static const char *ak4647_right_in[] = { "RIN1", "RIN2" }; + +static const char *ak4647_deemp[] = { "44.1kHz", "Off", "48kHz", "32kHz" }; + +static const struct soc_enum ak4647_enum[] = { + SOC_ENUM_SINGLE(AK4647_MODE4, 2, 2, ak4647_hp_out), + SOC_ENUM_SINGLE(AK4647_MODE3, 0, 4, ak4647_deemp), + SOC_ENUM_SINGLE(AK4647_PM3, 1, 2, ak4647_left_in), + SOC_ENUM_SINGLE(AK4647_PM3, 2, 2, ak4647_right_in), +}; + +#undef snd_soc_info_bool_ext +#define snd_soc_info_bool_ext snd_ctl_boolean_mono_info +static const struct snd_kcontrol_new ak4647_snd_controls[] = { + SOC_ENUM("Headphone Output", ak4647_enum[0]), + SOC_ENUM("Playback Deemphasis", ak4647_enum[1]), + SOC_ENUM("Left Capture Select", ak4647_enum[2]), + SOC_ENUM("Right Capture Select", ak4647_enum[3]), + SOC_SINGLE("Bass Volume", AK4647_MODE3, 2, 3, 0), + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4647_SIG1, 0, 1, 0), + SOC_SINGLE("Mic Bias", AK4647_SIG1, 2, 1, 0), + SOC_SINGLE("ALC Switch", AK4647_ALC1, 5, 1, 0), + SOC_SINGLE("ALC Recovery Time", AK4647_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4647_TIMER, 4, 3, 0), + SOC_SINGLE("ALC Volume", AK4647_ALC2, 0, 127, 0), + SOC_SINGLE("Left Capture Volume", AK4647_LEFT_INPUT_VOLUME, 0, 242, 0), + SOC_SINGLE("Right Capture Volume", + AK4647_RIGHT_INPUT_VOLUME, 0, 242, 0), + SOC_SINGLE("Left Playback Volume", AK4647_LEFT_DGT_VOLUME, 0, 255, 1), + SOC_SINGLE("Right Playback Volume", AK4647_RIGHT_DGT_VOLUME, 0, 255, 1), + SOC_SINGLE_BOOL_EXT("Loopback Line-in", 0, + loopback_xhandle_get, loopback_xhandle_put), +}; + +/* Stereo Mixer for HP*/ +static const struct snd_kcontrol_new ak4647_hp_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", AK4647_MODE4, 0, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4647_MODE4, 1, 1, 0), +}; + +/* Stereo Mixer for Line out*/ +static const struct snd_kcontrol_new ak4647_line_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", AK4647_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4647_SIG2, 2, 1, 0), +}; + +/* ak4647 dapm widgets */ +static const struct snd_soc_dapm_widget ak4647_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Headphone Mixer", SND_SOC_NOPM, 0, 0, + &ak4647_hp_stereo_mixer_controls[0], + ARRAY_SIZE(ak4647_hp_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Lineout Mixer", SND_SOC_NOPM, 0, 0, + &ak4647_line_stereo_mixer_controls[0], + ARRAY_SIZE(ak4647_line_stereo_mixer_controls)), + SND_SOC_DAPM_DAC("DAC", "Playback", AK4647_PM1, 2, 0), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("LOUT"), + + SND_SOC_DAPM_ADC("Left ADC", "Capture", AK4647_PM1, 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Capture", AK4647_PM3, 0, 0), + SND_SOC_DAPM_PGA("HP R Amp", AK4647_PM2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP L Amp", AK4647_PM2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic", AK4647_PM1, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out Amp", AK4647_PM1, 3, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", AK4647_SIG1, 2, 0), + SND_SOC_DAPM_INPUT("Left Input"), + SND_SOC_DAPM_INPUT("Right Input"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /*headphone stereo mixer */ + {"Headphone Mixer", "Playback Switch", "DAC"}, + {"Headphone Mixer", "Mic Sidetone Switch", "Mic"}, + + /*lineout stereo mixer */ + {"Lineout Mixer", "Playback Switch", "DAC"}, + {"Lineout Mixer", "Mic Sidetone Switch", "Mic"}, + + /* headphone amp */ + {"HP R Amp", NULL, "Headphone Mixer"}, + {"HP L Amp", NULL, "Headphone Mixer"}, + + /* headphone */ + {"HPR", NULL, "HP R Amp"}, + {"HPL", NULL, "HP L Amp"}, + + /* line out */ + {"Line Out Amp", NULL, "Lineout Mixer"}, + {"LOUT", NULL, "Line Out Amp"}, + + /* ADC */ + {"Left ADC", NULL, "Left Input"}, + {"Right ADC", NULL, "Right Input"}, + +}; + +static int ak4647_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ak4647_dapm_widgets, + ARRAY_SIZE(ak4647_dapm_widgets)); + + /* set up audio path audio_mapnects */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +#define AK4647_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_48000) + +#define AK4647_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static int ak4647_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + u8 value; + u8 fs = 0; + + /* FS3 is on D5 */ + switch (freq) { + case 8000: + fs = 0x0; + break; + case 11025: + fs = 0x5; + break; + case 16000: + fs = 0x2; + break; + case 22050: + fs = 0x7; + break; + case 32000: + fs = 0x22; + break; + case 44100: + fs = 0x27; + break; + case 48000: + fs = 0x23; + break; + default: + pr_err("unsupported sample rate"); + return -1; + } + + ak4647_read_reg(AK4647_MODE2, &value); + value &= 0xC0; + value |= fs; + ak4647_write_reg(AK4647_MODE2, value); + return 0; +} + +static int ak4647_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + u8 reg_mode1, reg_pm2; + + /* get current values */ + ak4647_read_reg(AK4647_MODE1, ®_mode1); + ak4647_read_reg(AK4647_PM2, ®_pm2); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + SET_BIT_IN_BYTE(reg_pm2, 3); + break; + case SND_SOC_DAIFMT_CBS_CFS: + CLEAR_BIT_IN_BYTE(reg_pm2, 3); + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + SET_BIT_IN_BYTE(reg_mode1, 0); + SET_BIT_IN_BYTE(reg_mode1, 1); + break; + case SND_SOC_DAIFMT_RIGHT_J: + SET_BIT_IN_BYTE(reg_mode1, 0); + CLEAR_BIT_IN_BYTE(reg_mode1, 1); + break; + case SND_SOC_DAIFMT_LEFT_J: + CLEAR_BIT_IN_BYTE(reg_mode1, 0); + SET_BIT_IN_BYTE(reg_mode1, 1); + break; + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + default: + pr_err("dai format %d not supported", fmt); + return -EINVAL; + } + + ak4647_write_reg(AK4647_MODE1, reg_mode1); + ak4647_write_reg(AK4647_PM2, reg_pm2); + return 0; +} + +static int ak4647_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) +{ + int retval = 0; + u8 value; + if (AK4647_BCLK_CLKDIV == div_id) { + ak4647_read_reg(AK4647_MODE1, &value); + switch (div) { + case AK4647_BCLK_DIV_32: + CLEAR_BIT_IN_BYTE(value, 3); + ak4647_write_reg(AK4647_MODE1, value); + break; + case AK4647_BCLK_DIV_64: + SET_BIT_IN_BYTE(value, 3); + ak4647_write_reg(AK4647_MODE1, value); + break; + default: + retval = -1; + pr_err("wrong div value for divid %d", div_id); + break; + } + } else if (AK4647_MCLK_CLKDIV == div_id) { + ak4647_read_reg(AK4647_MODE2, &value); + switch (div) { + case AK4647_MCLK_DIV_32: + SET_BIT_IN_BYTE(value, 7); + SET_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_MODE2, value); + break; + case AK4647_MCLK_DIV_64: + SET_BIT_IN_BYTE(value, 7); + CLEAR_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_MODE2, value); + break; + case AK4647_MCLK_DIV_128: + CLEAR_BIT_IN_BYTE(value, 7); + SET_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_MODE2, value); + break; + case AK4647_MCLK_DIV_256: + CLEAR_BIT_IN_BYTE(value, 7); + CLEAR_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_MODE2, value); + break; + default: + retval = -1; + pr_err("wrong div value for div id %d", div_id); + break; + } + } else { + retval = -1; + pr_err("wrong div id"); + } + + return retval; +} + +static int ak4647_digital_mute(struct snd_soc_dai *dai, int mute) +{ + u8 value; + + ak4647_read_reg(AK4647_MODE3, &value); + + if (mute) + SET_BIT_IN_BYTE(value, 5); + else + CLEAR_BIT_IN_BYTE(value, 5); + ak4647_write_reg(AK4647_MODE3, value); + return 0; +} + +struct ak4647_state_data { + u8 left_capture_vol; + u8 right_capture_vol; +}; +static struct ak4647_state_data ak4647_state; + +static int ak4647_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 value; + /* for playback, save down capture volume */ + if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { + ak4647_read_reg(AK4647_LEFT_INPUT_VOLUME, &value); + ak4647_state.left_capture_vol = value; + ak4647_read_reg(AK4647_RIGHT_INPUT_VOLUME, &value); + ak4647_state.right_capture_vol = value; + + /* when PMADL=PMADR=0 set IVL &IVR to be 0x91 (0db) */ + ak4647_write_reg(AK4647_LEFT_INPUT_VOLUME, 0x91); + ak4647_write_reg(AK4647_RIGHT_INPUT_VOLUME, 0x91); + } + + /* output digital volume independent */ + ak4647_read_reg(AK4647_MODE3, &value); + CLEAR_BIT_IN_BYTE(value, 4); + ak4647_write_reg(AK4647_MODE3, value); + return 0; +} + +static void ak4647_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 value; + ak4647_read_reg(AK4647_PM2, &value); + /* mute */ + CLEAR_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_PM2, value); + + /* after playback, restore capture volume */ + if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { + ak4647_write_reg(AK4647_LEFT_INPUT_VOLUME, + ak4647_state.left_capture_vol); + ak4647_write_reg(AK4647_RIGHT_INPUT_VOLUME, + ak4647_state.right_capture_vol); + } +} + +static int ak4647_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + u8 value; + + /* VCOM power on */ + ak4647_write_reg(AK4647_PM1, 0x44); + msleep(30); + + ak4647_read_reg(AK4647_PM2, &value); + /* PLL enabled */ + SET_BIT_IN_BYTE(value, 0); + + ak4647_write_reg(AK4647_PM2, value); + /* wait for PLL locked */ + msleep(40); + + /* don't mute */ + SET_BIT_IN_BYTE(value, 6); + ak4647_write_reg(AK4647_PM2, value); + return 0; +} + +struct snd_soc_dai_ops ak4647_ops = { + .prepare = ak4647_pcm_prepare, + .startup = ak4647_pcm_startup, + .shutdown = ak4647_pcm_shutdown, + .digital_mute = ak4647_digital_mute, + .set_fmt = ak4647_set_dai_fmt, + .set_sysclk = ak4647_set_dai_sysclk, + .set_clkdiv = ak4647_set_clkdiv, +}; + +struct snd_soc_dai ak4647_hifi_dai = { + .name = "ak4647", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AK4647_RATES, + .formats = AK4647_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4647_RATES, + .formats = AK4647_FORMATS, + }, + .ops = &ak4647_ops, +}; +EXPORT_SYMBOL_GPL(ak4647_hifi_dai); + +static struct snd_soc_codec *ak4647_codec; + +static int ak4647_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = ak4647_codec; + int ret = 0; + + socdev->card->codec = ak4647_codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&ak4647_i2c_client->dev, "failed to create pcms\n"); + return ret; + } + + /* setup init value for audio path controls here */ + /* enable DAC to headphone */ + ak4647_write_reg(AK4647_MODE4, 0x9); + /* capture source to be LIN2 and RIN2 */ + ak4647_write_reg(AK4647_PM3, 0x6); + /* MPWR pin up */ + ak4647_write_reg(AK4647_SIG1, 0x5); + + /* MCKI = 12M, default audio interface format as "left-justified" */ + ak4647_write_reg(AK4647_MODE1, 0x62); + + /* ALC disabled */ + ak4647_write_reg(AK4647_ALC1, 0x0); + + ak4647_state.left_capture_vol = 0x91; + ak4647_state.right_capture_vol = 0x91; + + snd_soc_add_controls(codec, ak4647_snd_controls, + ARRAY_SIZE(ak4647_snd_controls)); + ak4647_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + pr_err("ak4647: failed to register card\n"); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; + } + + return 0; +} + +static __devexit int ak4647_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + + snd_soc_unregister_dai(&ak4647_hifi_dai); + snd_soc_unregister_codec(codec); + kfree(codec); + ak4647_codec = NULL; + return 0; +} + +static int ak4647_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct snd_soc_codec *codec; + int ret; + u8 val; + + if (ak4647_codec) { + dev_err(&client->dev, + "Multiple AK4647 devices not supported\n"); + return -ENOMEM; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + i2c_set_clientdata(client, codec); + ak4647_i2c_client = client; + codec->control_data = client; + ret = ak4647_read_reg(AK4647_REG_START, &val); + if (ret < 0) { + pr_err("Device with ID register %x is not a AK4647\n", val); + return -ENODEV; + } + + codec->dev = &client->dev; + codec->name = "ak4647", codec->owner = THIS_MODULE; + codec->owner = THIS_MODULE; + codec->read = ak4647_codec_read; + codec->write = ak4647_codec_write; + codec->dai = &ak4647_hifi_dai; + codec->num_dai = 1; + + ak4647_codec = codec; + ak4647_hifi_dai.dev = &client->dev; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&ak4647_hifi_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + return ret; + } + + return ret; +} + +static const struct i2c_device_id ak4647_id[] = { + {"ak4647-i2c", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, ak4647_id); + +static struct i2c_driver ak4647_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ak4647-i2c", + }, + .probe = ak4647_i2c_probe, + .remove = __devexit_p(ak4647_i2c_remove), + .id_table = ak4647_id, +}; + +int pmic_audio_fm_output_enable(bool enable) +{ + u8 val; + if (enable) { + ak4647_set_dai_fmt(NULL, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + ak4647_set_dai_sysclk(NULL, 0, 44100, 0); + ak4647_set_clkdiv(NULL, 0, 0); + /* VCOM power on */ + ak4647_write_reg(AK4647_PM1, 0x44); + msleep(30); + + ak4647_read_reg(AK4647_PM2, &val); + /* PLL enabled */ + SET_BIT_IN_BYTE(val, 0); + + ak4647_write_reg(AK4647_PM2, val); + /* wait for PLL locked */ + msleep(40); + + /* don't mute */ + SET_BIT_IN_BYTE(val, 6); + ak4647_write_reg(AK4647_PM2, val); + + /* loopback STDO to DAC */ + ak4647_read_reg(AK4647_MODE3, &val); + SET_BIT_IN_BYTE(val, 6); + ak4647_write_reg(AK4647_MODE3, val); + + /* switch to R/L 1 */ + ak4647_read_reg(AK4647_PM3, &val); + CLEAR_BIT_IN_BYTE(val, 1); + CLEAR_BIT_IN_BYTE(val, 2); + ak4647_write_reg(AK4647_PM3, val); + + /* power up ADC */ + ak4647_read_reg(AK4647_PM1, &val); + SET_BIT_IN_BYTE(val, 0); + ak4647_write_reg(AK4647_PM1, val); + ak4647_read_reg(AK4647_PM3, &val); + SET_BIT_IN_BYTE(val, 0); + ak4647_write_reg(AK4647_PM3, val); + + /* power up DAC */ + ak4647_read_reg(AK4647_PM1, &val); + SET_BIT_IN_BYTE(val, 2); + ak4647_write_reg(AK4647_PM1, val); + + msleep(30); + + /* headphone output switch on */ + ak4647_read_reg(AK4647_MODE4, &val); + SET_BIT_IN_BYTE(val, 0); + ak4647_write_reg(AK4647_MODE4, val); + + /* power on headphone amp */ + ak4647_read_reg(AK4647_PM2, &val); + SET_BIT_IN_BYTE(val, 4); + SET_BIT_IN_BYTE(val, 5); + ak4647_write_reg(AK4647_PM2, val); + + ak4647_digital_mute(NULL, 0); + } else { + ak4647_digital_mute(NULL, 1); + + /* disbale loopback */ + ak4647_read_reg(AK4647_MODE3, &val); + CLEAR_BIT_IN_BYTE(val, 6); + SET_BIT_IN_BYTE(val, 5); + ak4647_write_reg(AK4647_MODE3, val); + + /* switch to R/L 2 */ + ak4647_read_reg(AK4647_PM3, &val); + SET_BIT_IN_BYTE(val, 1); + SET_BIT_IN_BYTE(val, 2); + ak4647_write_reg(AK4647_PM3, val); + } + return 0; +} + +static int loopback_xhandle_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value; + return 0; +} + +static int loopback_xhandle_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int changed; + long flag = ucontrol->value.integer.value[0]; + changed = + (ucontrol->value.integer.value[0] == + kcontrol->private_value) ? 0 : 1; + kcontrol->private_value = flag; + if (flag) + pmic_audio_fm_output_enable(true); + else + pmic_audio_fm_output_enable(false); + + return changed; +} + +/* power down chip */ +static int ak4647_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + i2c_del_driver(&ak4647_i2c_driver); + kfree(codec); + + return 0; +} + +static int ak4647_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int ak4647_resume(struct platform_device *pdev) +{ + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4647 = { + .probe = ak4647_probe, + .remove = ak4647_remove, + .suspend = ak4647_suspend, + .resume = ak4647_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4647); + +static int __init ak4647_modinit(void) +{ + return i2c_add_driver(&ak4647_i2c_driver); +} +module_init(ak4647_modinit); + +static void __exit ak4647_exit(void) +{ + i2c_del_driver(&ak4647_i2c_driver); +} +module_exit(ak4647_exit); + +MODULE_DESCRIPTION("ASoC ak4647 driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4647.h b/sound/soc/codecs/ak4647.h new file mode 100644 index 000000000000..d88832b1dd09 --- /dev/null +++ b/sound/soc/codecs/ak4647.h @@ -0,0 +1,92 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file ak4647.h + * @brief Driver for AK4647 + * + * @ingroup Sound + */ +#ifndef _AK4647_H_ +#define _AK4647_H_ + +#ifdef __KERNEL__ + +/*! + * AK4647 registers + */ + +#define AK4647_PM1 0x00 +#define AK4647_PM2 0x01 +#define AK4647_SIG1 0x02 +#define AK4647_SIG2 0x03 +#define AK4647_MODE1 0x04 +#define AK4647_MODE2 0x05 +#define AK4647_TIMER 0x06 +#define AK4647_ALC1 0x07 +#define AK4647_ALC2 0x08 + +#define AK4647_LEFT_INPUT_VOLUME 0x09 +#define AK4647_LEFT_DGT_VOLUME 0x0A +#define AK4647_ALC3 0x0B +#define AK4647_RIGHT_INPUT_VOLUME 0x0C +#define AK4647_RIGHT_DGT_VOLUME 0x0D +#define AK4647_MODE3 0x0E +#define AK4647_MODE4 0x0F +#define AK4647_PM3 0x10 +#define AK4647_DGT_FIL_SEL 0x11 + +/* filter 3 coeffecient*/ + +#define AK4647_FIL3_COEF0 0x12 +#define AK4647_FIL3_COEF1 0x13 +#define AK4647_FIL3_COEF2 0x14 +#define AK4647_FIL3_COEF3 0x15 + +/* eq coeffecient*/ + +#define AK4647_EQ_COEF0 0x16 +#define AK4647_EQ_COEF1 0x17 +#define AK4647_EQ_COEF2 0x18 +#define AK4647_EQ_COEF3 0x19 +#define AK4647_EQ_COEF4 0x1A +#define AK4647_EQ_COEF5 0x1B + +/* filter 3 coeffecient*/ + +#define AK4647_FIL1_COEF0 0x1C +#define AK4647_FIL1_COEF1 0x1D +#define AK4647_FIL1_COEF2 0x1E +#define AK4647_FIL1_COEF3 0x1F + +#define AK4647_REG_START 0x00 +#define AK4647_REG_END 0x1F +#define AK4647_REG_NUMBER 0x20 + +/* clock divider id's */ +#define AK4647_BCLK_CLKDIV 0 +#define AK4647_MCLK_CLKDIV 1 + +/* bit clock div values (AK4647_BCLK_CLKDIV)*/ +#define AK4647_BCLK_DIV_32 0 +#define AK4647_BCLK_DIV_64 1 + +/* m clock div values (AK4647_MCLK_CLKDIV)*/ +#define AK4647_MCLK_DIV_32 0 +#define AK4647_MCLK_DIV_64 1 +#define AK4647_MCLK_DIV_128 2 +#define AK4647_MCLK_DIV_256 3 + +#endif /* __KERNEL__ */ + +#endif /* _AK4647_H_ */ diff --git a/sound/soc/codecs/ak5702.c b/sound/soc/codecs/ak5702.c new file mode 100644 index 000000000000..a34f48e52b3f --- /dev/null +++ b/sound/soc/codecs/ak5702.c @@ -0,0 +1,607 @@ +/* + * ak5702.c -- AK5702 Soc Audio driver + * + * Copyright 2009 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/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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 "ak5702.h" + +#define AK5702_VERSION "0.1" + +/* codec private data */ +struct ak5702_priv { + unsigned int sysclk; +}; + +/* + * ak5702 register cache + */ +static const u16 ak5702_reg[AK5702_CACHEREGNUM] = { + 0x0000, 0x0024, 0x0000, 0x0001, 0x0023, 0x001f, + 0x0000, 0x0001, 0x0091, 0x0000, 0x00e1, 0x0000, + 0x00a0, 0x0000, 0x0000, 0x0000, 0x0001, 0x0020, + 0x0000, 0x0000, 0x0001, 0x0091, 0x0000, 0x00e1, + 0x0000, +}; + +/* + * read ak5702 register cache + */ +static inline unsigned int ak5702_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= AK5702_CACHEREGNUM) + return -1; + return cache[reg]; +} + +static inline unsigned int ak5702_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 data; + data = reg; + + if (codec->hw_write(codec->control_data, &data, 1) != 1) + return -EIO; + if (codec->hw_read(codec->control_data, &data, 1) != 1) + return -EIO; + + return data; +}; + +/* + * write ak5702 register cache + */ +static inline void ak5702_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + if (reg >= AK5702_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the AK5702 register space + */ +static int ak5702_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + data[0] = reg & 0xff; + data[1] = value & 0xff; + + ak5702_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static const char *ak5702_mic_gain[] = { "0dB", "+15dB", "+30dB", "+36dB" }; +static const char *ak5702_adca_left_type[] = + { "Single-ended", "Full-differential" }; +static const char *ak5702_adca_right_type[] = + { "Single-ended", "Full-differential" }; +static const char *ak5702_adcb_left_type[] = + { "Single-ended", "Full-differential" }; +static const char *ak5702_adcb_right_type[] = + { "Single-ended", "Full-differential" }; +static const char *ak5702_adca_left_input[] = { "LIN1", "LIN2" }; +static const char *ak5702_adca_right_input[] = { "RIN1", "RIN2" }; +static const char *ak5702_adcb_left_input[] = { "LIN3", "LIN4" }; +static const char *ak5702_adcb_right_input[] = { "RIN3", "RIN4" }; + +static const struct soc_enum ak5702_enum[] = { + SOC_ENUM_SINGLE(AK5702_MICG1, 0, 4, ak5702_mic_gain), + SOC_ENUM_SINGLE(AK5702_MICG2, 0, 4, ak5702_mic_gain), + + SOC_ENUM_SINGLE(AK5702_SIG1, 0, 2, ak5702_adca_left_input), + SOC_ENUM_SINGLE(AK5702_SIG1, 1, 2, ak5702_adca_right_input), + SOC_ENUM_SINGLE(AK5702_SIG2, 0, 2, ak5702_adcb_left_input), + SOC_ENUM_SINGLE(AK5702_SIG1, 1, 2, ak5702_adcb_right_input), + + SOC_ENUM_SINGLE(AK5702_SIG1, 2, 2, ak5702_adca_left_type), + SOC_ENUM_SINGLE(AK5702_SIG1, 3, 2, ak5702_adca_right_type), + SOC_ENUM_SINGLE(AK5702_SIG2, 2, 2, ak5702_adcb_left_type), + SOC_ENUM_SINGLE(AK5702_SIG2, 3, 2, ak5702_adcb_right_type), +}; + +static const struct snd_kcontrol_new ak5702_snd_controls[] = { + SOC_SINGLE("ADCA Left Vol", AK5702_LVOL1, 0, 242, 0), + SOC_SINGLE("ADCA Right Vol", AK5702_RVOL1, 0, 242, 0), + SOC_SINGLE("ADCB Left Vol", AK5702_LVOL2, 0, 242, 0), + SOC_SINGLE("ADCB Right Vol", AK5702_RVOL2, 0, 242, 0), + + SOC_ENUM("MIC-AmpA Gain", ak5702_enum[0]), + SOC_ENUM("MIC-AmpB Gain", ak5702_enum[1]), + + SOC_ENUM("ADCA Left Source", ak5702_enum[2]), + SOC_ENUM("ADCA Right Source", ak5702_enum[3]), + SOC_ENUM("ADCB Left Source", ak5702_enum[4]), + SOC_ENUM("ADCB Right Source", ak5702_enum[5]), + + SOC_ENUM("ADCA Left Type", ak5702_enum[6]), + SOC_ENUM("ADCA Right Type", ak5702_enum[7]), + SOC_ENUM("ADCB Left Type", ak5702_enum[8]), + SOC_ENUM("ADCB Right Type", ak5702_enum[9]), +}; + +/* ak5702 dapm widgets */ +static const struct snd_soc_dapm_widget ak5702_dapm_widgets[] = { + SND_SOC_DAPM_ADC("ADCA Left", "Capture", AK5702_PM1, 0, 0), + SND_SOC_DAPM_ADC("ADCA Right", "Capture", AK5702_PM1, 1, 0), + SND_SOC_DAPM_ADC("ADCB Left", "Capture", AK5702_PM2, 0, 0), + SND_SOC_DAPM_ADC("ADCB Right", "Capture", AK5702_PM2, 1, 0), + + SND_SOC_DAPM_INPUT("ADCA Left Input"), + SND_SOC_DAPM_INPUT("ADCA Right Input"), + SND_SOC_DAPM_INPUT("ADCB Left Input"), + SND_SOC_DAPM_INPUT("ADCB Right Input"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"ADCA Left", NULL, "ADCA Left Input"}, + {"ADCA Right", NULL, "ADCA Right Input"}, + {"ADCB Left", NULL, "ADCB Left Input"}, + {"ADCB Right", NULL, "ADCB Right Input"}, +}; + +static int ak5702_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ak5702_dapm_widgets, + ARRAY_SIZE(ak5702_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int ak5702_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; + u8 fs = 0; + u8 value; + + switch (freq) { + case 8000: + fs = 0x0; + break; + case 11025: + fs = 0x05; + break; + case 12000: + fs = 0x01; + break; + case 16000: + fs = 0x02; + break; + case 22050: + fs = 0x07; + break; + case 24000: + fs = 0x03; + break; + case 32000: + fs = 0x0a; + break; + case 44100: + fs = 0x0f; + break; + case 48000: + fs = 0x0b; + break; + default: + return -EINVAL; + } + + value = ak5702_read_reg_cache(codec, AK5702_FS1); + value &= (~AK5702_FS1_FS_MASK); + value |= fs; + ak5702_write(codec, AK5702_FS1, value); + return 0; +} + +static int ak5702_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 fmt1 = 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + fmt1 = AK5702_FMT1_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + fmt1 = AK5702_FMT1_MSB; + break; + default: + return -EINVAL; + } + + ak5702_write(codec, AK5702_FMT1, fmt1); + ak5702_write(codec, AK5702_FMT2, AK5702_FMT2_STEREO); + return 0; +} + +static int ak5702_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 reg = 0; + + reg = ak5702_read_reg_cache(codec, AK5702_PLL1); + switch (pll_id) { + case AK5702_PLL_POWERDOWN: + reg &= (~AK5702_PLL1_PM_MASK); + reg |= AK5702_PLL1_POWERDOWN; + break; + case AK5702_PLL_MASTER: + reg &= (~AK5702_PLL1_MODE_MASK); + reg |= AK5702_PLL1_MASTER; + reg |= AK5702_PLL1_POWERUP; + break; + case AK5702_PLL_SLAVE: + reg &= (~AK5702_PLL1_MODE_MASK); + reg |= AK5702_PLL1_SLAVE; + reg |= AK5702_PLL1_POWERUP; + break; + default: + return -ENODEV; + } + + switch (freq_in) { + case 11289600: + reg &= (~AK5702_PLL1_PLL_MASK); + reg |= AK5702_PLL1_11289600; + break; + case 12000000: + reg &= (~AK5702_PLL1_PLL_MASK); + reg |= AK5702_PLL1_12000000; + break; + case 12288000: + reg &= (~AK5702_PLL1_PLL_MASK); + reg |= AK5702_PLL1_12288000; + break; + case 19200000: + reg &= (~AK5702_PLL1_PLL_MASK); + reg |= AK5702_PLL1_19200000; + break; + default: + return -ENODEV; + } + + ak5702_write(codec, AK5702_PLL1, reg); + return 0; +} + +static int ak5702_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 reg = 0; + + if (div_id == AK5702_BCLK_CLKDIV) { + reg = ak5702_read_reg_cache(codec, AK5702_FS1); + switch (div) { + case AK5702_BCLK_DIV_32: + reg &= (~AK5702_FS1_BCKO_MASK); + reg |= AK5702_FS1_BCKO_32FS; + ak5702_write(codec, AK5702_FS1, reg); + break; + case AK5702_BCLK_DIV_64: + reg &= (~AK5702_FS1_BCKO_MASK); + reg |= AK5702_FS1_BCKO_64FS; + ak5702_write(codec, AK5702_FS1, reg); + break; + default: + return -EINVAL; + } + } + return 0; +} + +static int ak5702_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u8 reg = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + ak5702_write(codec, AK5702_PM1, reg | AK5702_PM1_PMVCM); + reg = ak5702_read_reg_cache(codec, AK5702_PLL1); + reg = reg | AK5702_PLL1_POWERUP | AK5702_PLL1_MASTER; + ak5702_write(codec, AK5702_PLL1, reg); + break; + case SND_SOC_BIAS_STANDBY: + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + ak5702_write(codec, AK5702_PM1, reg | AK5702_PM1_PMVCM); + reg = ak5702_read_reg_cache(codec, AK5702_PLL1); + ak5702_write(codec, AK5702_PLL1, reg & (~AK5702_PLL1_POWERUP)); + break; + case SND_SOC_BIAS_OFF: + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + ak5702_write(codec, AK5702_PM1, reg & (~AK5702_PM1_PMVCM)); + break; + } + + codec->bias_level = level; + return 0; +} + +static int ak5702_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; + + ak5702_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ak5702_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + + /* Bring the codec back up to standby first to minimise pop/clicks */ + ak5702_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ak5702_set_bias_level(codec, codec->suspend_bias_level); + + /* Sync back everything else */ + for (i = 0; i < ARRAY_SIZE(ak5702_reg); i++) + ak5702_write(codec, i, ak5702_reg[i]); + + return 0; +} + +#define AK5702_RATES SNDRV_PCM_RATE_8000_48000 +#define AK5702_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_dai_ops ak5702_ops = { + .set_fmt = ak5702_set_dai_fmt, + .set_sysclk = ak5702_set_dai_sysclk, + .set_clkdiv = ak5702_set_dai_clkdiv, + .set_pll = ak5702_set_dai_pll, +}; + +struct snd_soc_dai ak5702_dai = { + .name = "AK5702", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = AK5702_RATES, + .formats = AK5702_FORMATS, + }, + .ops = &ak5702_ops, +}; +EXPORT_SYMBOL_GPL(ak5702_dai); + +static struct snd_soc_codec *ak5702_codec; + +static int ak5702_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = ak5702_codec; + int ret = 0; + u8 reg = 0; + + socdev->card->codec = ak5702_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ak5702: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + reg |= AK5702_PM1_PMVCM; + ak5702_write(codec, AK5702_PM1, reg); + + /* initialize ADC */ + reg = AK5702_SIG1_L_LIN1 | AK5702_SIG1_R_RIN2; + ak5702_write(codec, AK5702_SIG1, reg); + reg = AK5702_SIG2_L_LIN3 | AK5702_SIG2_R_RIN4; + ak5702_write(codec, AK5702_SIG2, reg); + + reg = ak5702_read_reg_cache(codec, AK5702_PM1); + reg = reg | AK5702_PM1_PMADAL | AK5702_PM1_PMADAR; + ak5702_write(codec, AK5702_PM1, reg); + reg = ak5702_read_reg_cache(codec, AK5702_PM2); + reg = reg | AK5702_PM2_PMADBL | AK5702_PM2_PMADBR; + ak5702_write(codec, AK5702_PM2, reg); + + /* initialize volume */ + ak5702_write(codec, AK5702_MICG1, AK5702_MICG1_INIT); + ak5702_write(codec, AK5702_MICG2, AK5702_MICG2_INIT); + ak5702_write(codec, AK5702_VOL1, AK5702_VOL1_IVOLAC); + ak5702_write(codec, AK5702_VOL2, AK5702_VOL2_IVOLBC); + ak5702_write(codec, AK5702_LVOL1, AK5702_LVOL1_INIT); + ak5702_write(codec, AK5702_RVOL1, AK5702_RVOL1_INIT); + ak5702_write(codec, AK5702_LVOL2, AK5702_LVOL2_INIT); + ak5702_write(codec, AK5702_RVOL2, AK5702_RVOL2_INIT); + + snd_soc_add_controls(codec, ak5702_snd_controls, + ARRAY_SIZE(ak5702_snd_controls)); + ak5702_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ak5702: failed to register card\n"); + goto card_err; + } + + return ret; +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int ak5702_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ak5702_priv *ak5702; + struct snd_soc_codec *codec; + int ret; + + if (ak5702_codec) { + dev_err(&client->dev, + "Multiple AK5702 devices not supported\n"); + return -ENOMEM; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ak5702 = kzalloc(sizeof(struct ak5702_priv), GFP_KERNEL); + if (ak5702 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ak5702; + 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; + + codec->dev = &client->dev; + codec->name = "AK5702"; + codec->owner = THIS_MODULE; + codec->read = ak5702_read_reg_cache; + codec->write = ak5702_write; + codec->set_bias_level = ak5702_set_bias_level; + codec->dai = &ak5702_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(ak5702_reg); + codec->reg_cache = (void *)&ak5702_reg; + if (codec->reg_cache == NULL) + return -ENOMEM; + + codec->hw_write = (hw_write_t) i2c_master_send; + codec->hw_read = (hw_read_t) i2c_master_recv; + + ak5702_codec = codec; + ak5702_dai.dev = &client->dev; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&ak5702_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + return ret; + } + + return ret; +} + +static __devexit int ak5702_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + struct ak5702_priv *ak5702 = codec->private_data; + + snd_soc_unregister_dai(&ak5702_dai); + snd_soc_unregister_codec(codec); + kfree(codec); + kfree(ak5702); + ak5702_codec = NULL; + return 0; +} + +static const struct i2c_device_id ak5702_i2c_id[] = { + {"ak5702-i2c", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ak5702_i2c_id); + +static struct i2c_driver ak5702_i2c_driver = { + .driver = { + .name = "ak5702-i2c", + .owner = THIS_MODULE, + }, + .probe = ak5702_i2c_probe, + .remove = __devexit_p(ak5702_i2c_remove), + .id_table = ak5702_i2c_id, +}; +#endif + +static int ak5702_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) + ak5702_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&ak5702_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak5702 = { + .probe = ak5702_probe, + .remove = ak5702_remove, + .suspend = ak5702_suspend, + .resume = ak5702_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak5702); + +static int __init ak5702_modinit(void) +{ + return i2c_add_driver(&ak5702_i2c_driver); +} +module_init(ak5702_modinit); + +static void __exit ak5702_exit(void) +{ + i2c_del_driver(&ak5702_i2c_driver); +} +module_exit(ak5702_exit); + +MODULE_DESCRIPTION("Soc AK5702 driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak5702.h b/sound/soc/codecs/ak5702.h new file mode 100644 index 000000000000..97af6e45e498 --- /dev/null +++ b/sound/soc/codecs/ak5702.h @@ -0,0 +1,130 @@ +/* + * ak5702.h -- AK5702 Soc Audio driver + * + * Copyright 2009 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. + */ + +#ifndef _AK5702_H +#define _AK5702_H + +/* AK5702 register space */ + +#define AK5702_PM1 0x00 +#define AK5702_PLL1 0x01 +#define AK5702_SIG1 0x02 +#define AK5702_MICG1 0x03 +#define AK5702_FMT1 0x04 +#define AK5702_FS1 0x05 +#define AK5702_CLK1 0x06 +#define AK5702_VOL1 0x07 +#define AK5702_LVOL1 0x08 +#define AK5702_RVOL1 0x09 +#define AK5702_TIMER1 0x0a +#define AK5702_ALC11 0x0b +#define AK5702_ALC12 0x0c +#define AK5702_MODE11 0x0d +#define AK5702_MODE12 0x0e +#define AK5702_MODE13 0x0f + +#define AK5702_PM2 0x10 +#define AK5702_PLL2 0x11 +#define AK5702_SIG2 0x12 +#define AK5702_MICG2 0x13 +#define AK5702_FMT2 0x14 +#define AK5702_FS2 0x15 +#define AK5702_CLK2 0x16 +#define AK5702_VOL2 0x17 +#define AK5702_LVOL2 0x18 +#define AK5702_RVOL2 0x19 +#define AK5702_TIMER2 0x1a +#define AK5702_ALC21 0x1b +#define AK5702_ALC22 0x1c +#define AK5702_MODE21 0x1d +#define AK5702_MODE22 0x1e + +#define AK5702_CACHEREGNUM 0x1F + +#define AK5702_PM1_PMADAL 0x01 +#define AK5702_PM1_PMADAR 0x02 +#define AK5702_PM1_PMVCM 0x04 +#define AK5702_PM2_PMADBL 0x01 +#define AK5702_PM2_PMADBR 0x02 + +#define AK5702_PLL1_POWERDOWN 0x0 +#define AK5702_PLL1_POWERUP 0x01 +#define AK5702_PLL1_MASTER 0x02 +#define AK5702_PLL1_SLAVE 0x0 +#define AK5702_PLL1_11289600 0x10 +#define AK5702_PLL1_12000000 0x24 +#define AK5702_PLL1_12288000 0x14 +#define AK5702_PLL1_19200000 0x20 + +#define AK5702_SIG1_L_LIN1 0x0 +#define AK5702_SIG1_L_LIN2 0x01 +#define AK5702_SIG1_R_RIN1 0x0 +#define AK5702_SIG1_R_RIN2 0x02 +#define AK5702_SIG1_PMMPA 0x10 +#define AK5702_SIG2_L_LIN3 0x0 +#define AK5702_SIG2_L_LIN4 0x01 +#define AK5702_SIG2_R_RIN3 0x0 +#define AK5702_SIG2_R_RIN4 0x02 +#define AK5702_SIG2_PMMPB 0x10 + +#define AK5702_MICG1_INIT 0x0 +#define AK5702_MICG2_INIT 0x0 + +#define AK5702_FMT1_I2S 0x23 +#define AK5702_FMT1_MSB 0x22 +#define AK5702_FMT2_STEREO 0x20 +#define AK5702_FS1_BCKO_32FS 0x10 +#define AK5702_FS1_BCKO_64FS 0x20 +#define AK5702_CLK1_PS_256FS 0x0 +#define AK5702_CLK1_PS_128FS 0x01 +#define AK5702_CLK1_PS_64FS 0x02 +#define AK5702_CLK1_PS_32FS 0x03 +#define AK5702_VOL1_IVOLAC 0x01 +#define AK5702_VOL2_IVOLBC 0x01 +#define AK5702_LVOL1_INIT 0x91 +#define AK5702_RVOL1_INIT 0x91 +#define AK5702_LVOL2_INIT 0x91 +#define AK5702_RVOL2_INIT 0x91 + +#define AK5702_PLL1_PM_MASK 0x01 +#define AK5702_PLL1_MODE_MASK 0x02 +#define AK5702_PLL1_PLL_MASK 0x3c +#define AK5702_FS1_BCKO_MASK 0x30 +#define AK5702_FS1_FS_MASK 0x0f +#define AK5702_CLK1_PS_MASK 0x03 + +/* clock divider id */ +#define AK5702_BCLK_CLKDIV 0 +#define AK5702_MCLK_CLKDIV 1 + +/* bit clock div values */ +#define AK5702_BCLK_DIV_32 0 +#define AK5702_BCLK_DIV_64 1 + +/* m clock div values */ +#define AK5702_MCLK_DIV_32 0 +#define AK5702_MCLK_DIV_64 1 +#define AK5702_MCLK_DIV_128 2 +#define AK5702_MCLK_DIV_256 3 + +/* PLL master and slave modes */ +#define AK5702_PLL_POWERDOWN 0 +#define AK5702_PLL_MASTER 1 +#define AK5702_PLL_SLAVE 2 + +struct ak5702_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ak5702_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak5702; + +#endif diff --git a/sound/soc/codecs/bluetooth.c b/sound/soc/codecs/bluetooth.c new file mode 100644 index 000000000000..2d4c13b4f0af --- /dev/null +++ b/sound/soc/codecs/bluetooth.c @@ -0,0 +1,161 @@ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file bluetooth.c + * @brief Driver for bluetooth PCM interface + * + * @ingroup Sound + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.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 <sound/tlv.h> + +#define BLUETOOTH_RATES SNDRV_PCM_RATE_8000 + +#define BLUETOOTH_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +struct snd_soc_dai bt_dai = { + .name = "bluetooth", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = BLUETOOTH_RATES, + .formats = BLUETOOTH_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = BLUETOOTH_RATES, + .formats = BLUETOOTH_FORMATS, + }, +}; +EXPORT_SYMBOL_GPL(bt_dai); + +static int bt_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->card->codec; + int ret = 0; + + codec->name = "bluetooth"; + codec->owner = THIS_MODULE; + codec->dai = &bt_dai; + codec->num_dai = 1; + + snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("failed to create bluetooth pcms\n"); + return ret; + } + + ret = snd_soc_init_card(socdev); + strcpy(codec->card->id, "bluetooth"); + + if (ret < 0) { + pr_err("bluetooth: failed to register card\n"); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; + } + return 0; +} + +static struct snd_soc_device *bt_socdev; + +static int bt_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int ret = 0; + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->card->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + bt_socdev = socdev; + + ret = bt_init(socdev); + if (ret < 0) { + pr_err("Bluetooth codec initialisation failed\n"); + kfree(codec); + } + + return ret; +} + +/* power down chip */ +static int bt_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + kfree(codec); + + return 0; +} + +static int bt_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int bt_resume(struct platform_device *pdev) +{ + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_bt = { + .probe = bt_probe, + .remove = bt_remove, + .suspend = bt_suspend, + .resume = bt_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_bt); + +static int __init bluetooth_modinit(void) +{ + return snd_soc_register_dai(&bt_dai); +} + +module_init(bluetooth_modinit); + +static void __exit bluetooth_exit(void) +{ + snd_soc_unregister_dai(&bt_dai); +} + +module_exit(bluetooth_exit); + +MODULE_DESCRIPTION("ASoC bluetooth codec driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42888.c b/sound/soc/codecs/cs42888.c new file mode 100644 index 000000000000..e9288cc9a1ca --- /dev/null +++ b/sound/soc/codecs/cs42888.c @@ -0,0 +1,1196 @@ +/* + * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/spi/spi.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/tlv.h> +#include <sound/initval.h> +#include <asm/div64.h> + +#include "cs42888.h" + +#define CS42888_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* CS42888 registers addresses */ +#define CS42888_CHIPID 0x01 /* Chip ID */ +#define CS42888_PWRCTL 0x02 /* Power Control */ +#define CS42888_MODE 0x03 /* Functional Mode */ +#define CS42888_FORMAT 0x04 /* Interface Formats */ +#define CS42888_ADCCTL 0x05 /* ADC Control */ +#define CS42888_TRANS 0x06 /* Transition Control */ +#define CS42888_MUTE 0x07 /* Mute Control */ +#define CS42888_VOLAOUT1 0x08 /* Volume Control AOUT1*/ +#define CS42888_VOLAOUT2 0x09 /* Volume Control AOUT2*/ +#define CS42888_VOLAOUT3 0x0A /* Volume Control AOUT3*/ +#define CS42888_VOLAOUT4 0x0B /* Volume Control AOUT4*/ +#define CS42888_VOLAOUT5 0x0C /* Volume Control AOUT5*/ +#define CS42888_VOLAOUT6 0x0D /* Volume Control AOUT6*/ +#define CS42888_VOLAOUT7 0x0E /* Volume Control AOUT7*/ +#define CS42888_VOLAOUT8 0x0F /* Volume Control AOUT8*/ +#define CS42888_DACINV 0x10 /* DAC Channel Invert */ +#define CS42888_VOLAIN1 0x11 /* Volume Control AIN1 */ +#define CS42888_VOLAIN2 0x12 /* Volume Control AIN2 */ +#define CS42888_VOLAIN3 0x13 /* Volume Control AIN3 */ +#define CS42888_VOLAIN4 0x14 /* Volume Control AIN4 */ +#define CS42888_ADCINV 0x17 /* ADC Channel Invert */ +#define CS42888_STATUSCTL 0x18 /* Status Control */ +#define CS42888_STATUS 0x19 /* Status */ +#define CS42888_STATUSMASK 0x1A /* Status Mask */ + +#define CS42888_FIRSTREG 0x01 +#define CS42888_LASTREG 0x1A +#define CS42888_NUMREGS (CS42888_LASTREG - CS42888_FIRSTREG + 1) +#define CS42888_I2C_INCR 0x80 + +/* Bit masks for the CS42888 registers */ +#define CS42888_CHIPID_ID_MASK 0xF0 +#define CS42888_CHIPID_REV 0x0F +#define CS42888_PWRCTL_PDN_ADC2_OFFSET 6 +#define CS42888_PWRCTL_PDN_ADC1_OFFSET 5 +#define CS42888_PWRCTL_PDN_DAC4_OFFSET 4 +#define CS42888_PWRCTL_PDN_DAC3_OFFSET 3 +#define CS42888_PWRCTL_PDN_DAC2_OFFSET 2 +#define CS42888_PWRCTL_PDN_DAC1_OFFSET 1 +#define CS42888_PWRCTL_PDN_OFFSET 0 +#define CS42888_PWRCTL_PDN_ADC2_MASK (1 << CS42888_PWRCTL_PDN_ADC2_OFFSET) +#define CS42888_PWRCTL_PDN_ADC1_MASK (1 << CS42888_PWRCTL_PDN_ADC1_OFFSET) +#define CS42888_PWRCTL_PDN_DAC4_MASK (1 << CS42888_PWRCTL_PDN_DAC4_OFFSET) +#define CS42888_PWRCTL_PDN_DAC3_MASK (1 << CS42888_PWRCTL_PDN_DAC3_OFFSET) +#define CS42888_PWRCTL_PDN_DAC2_MASK (1 << CS42888_PWRCTL_PDN_DAC2_OFFSET) +#define CS42888_PWRCTL_PDN_DAC1_MASK (1 << CS42888_PWRCTL_PDN_DAC1_OFFSET) +#define CS42888_PWRCTL_PDN_MASK (1 << CS42888_PWRCTL_PDN_OFFSET) + +#define CS42888_MODE_SPEED_MASK 0xF0 +#define CS42888_MODE_1X 0x00 +#define CS42888_MODE_2X 0x50 +#define CS42888_MODE_4X 0xA0 +#define CS42888_MODE_SLAVE 0xF0 +#define CS42888_MODE_DIV_MASK 0x0E +#define CS42888_MODE_DIV1 0x00 +#define CS42888_MODE_DIV2 0x02 +#define CS42888_MODE_DIV3 0x04 +#define CS42888_MODE_DIV4 0x06 +#define CS42888_MODE_DIV5 0x08 + +#define CS42888_FORMAT_FREEZE_OFFSET 7 +#define CS42888_FORMAT_AUX_DIF_OFFSET 6 +#define CS42888_FORMAT_DAC_DIF_OFFSET 3 +#define CS42888_FORMAT_ADC_DIF_OFFSET 0 +#define CS42888_FORMAT_FREEZE_MASK (1 << CS42888_FORMAT_FREEZE_OFFSET) +#define CS42888_FORMAT_AUX_DIF_MASK (1 << CS42888_FORMAT_AUX_DIF_OFFSET) +#define CS42888_FORMAT_DAC_DIF_MASK (7 << CS42888_FORMAT_DAC_DIF_OFFSET) +#define CS42888_FORMAT_ADC_DIF_MASK (7 << CS42888_FORMAT_ADC_DIF_OFFSET) + +#define CS42888_TRANS_DAC_SNGVOL_OFFSET 7 +#define CS42888_TRANS_DAC_SZC_OFFSET 5 +#define CS42888_TRANS_AMUTE_OFFSET 4 +#define CS42888_TRANS_MUTE_ADC_SP_OFFSET 3 +#define CS42888_TRANS_ADC_SNGVOL_OFFSET 2 +#define CS42888_TRANS_ADC_SZC_OFFSET 0 +#define CS42888_TRANS_DAC_SNGVOL_MASK (1 << CS42888_TRANS_DAC_SNGVOL_OFFSET) +#define CS42888_TRANS_DAC_SZC_MASK (3 << CS42888_TRANS_DAC_SZC_OFFSET) +#define CS42888_TRANS_AMUTE_MASK (1 << CS42888_TRANS_AMUTE_OFFSET) +#define CS42888_TRANS_MUTE_ADC_SP_MASK (1 << CS42888_TRANS_MUTE_ADC_SP_OFFSET) +#define CS42888_TRANS_ADC_SNGVOL_MASK (1 << CS42888_TRANS_ADC_SNGVOL_OFFSET) +#define CS42888_TRANS_ADC_SZC_MASK (3 << CS42888_TRANS_ADC_SZC_OFFSET) + +#define CS42888_MUTE_AOUT8 (0x1 << 7) +#define CS42888_MUTE_AOUT7 (0x1 << 6) +#define CS42888_MUTE_AOUT6 (0x1 << 5) +#define CS42888_MUTE_AOUT5 (0x1 << 4) +#define CS42888_MUTE_AOUT4 (0x1 << 3) +#define CS42888_MUTE_AOUT3 (0x1 << 2) +#define CS42888_MUTE_AOUT2 (0x1 << 1) +#define CS42888_MUTE_AOUT1 (0x1 << 0) +#define CS42888_MUTE_ALL (CS42888_MUTE_AOUT1 | CS42888_MUTE_AOUT2 | \ + CS42888_MUTE_AOUT3 | CS42888_MUTE_AOUT4 | \ + CS42888_MUTE_AOUT5 | CS42888_MUTE_AOUT6 | \ + CS42888_MUTE_AOUT7 | CS42888_MUTE_AOUT8) + +#define DIF_LEFT_J 0 +#define DIF_I2S 1 +#define DIF_RIGHT_J 2 +#define DIF_TDM 6 + +/* Private data for the CS42888 */ +struct cs42888_private { + struct snd_soc_codec codec; + u8 reg_cache[CS42888_NUMREGS]; + unsigned int mclk; /* Input frequency of the MCLK pin */ + unsigned int mode; /* The mode (I2S or left-justified) */ + unsigned int slave_mode; + unsigned int manual_mute; + struct regulator *regulator_vsd; +}; + +static struct i2c_client *cs42888_i2c_client; + +int cs42888_read_reg(unsigned int reg, u8 *value) +{ + s32 retval; + retval = i2c_smbus_read_byte_data(cs42888_i2c_client, reg); + if (retval < 0) { + pr_err("%s:read reg errorr:reg=%x,val=%x\n", + __func__, reg, retval); + return -1; + } else { + *value = (u8) retval; + return 0; + } +} + +int cs42888_write_reg(unsigned int reg, u8 value) +{ + if (i2c_smbus_write_byte_data(cs42888_i2c_client, reg, value) < 0) { + pr_err("%s:write reg errorr:reg=%x,val=%x\n", + __func__, reg, value); + return -1; + } + return 0; +} +/** + * cs42888_fill_cache - pre-fill the CS42888 register cache. + * @codec: the codec for this CS42888 + * + * This function fills in the CS42888 register cache by reading the register + * values from the hardware. + * + * This CS42888 registers are cached to avoid excessive I2C I/O operations. + * After the initial read to pre-fill the cache, the CS42888 never updates + * the register values, so we won't have a cache coherency problem. + * + * We use the auto-increment feature of the CS42888 to read all registers in + * one shot. + */ +static int cs42888_fill_cache(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache; + struct i2c_client *i2c_client = codec->control_data; + s32 length; + + length = i2c_smbus_read_i2c_block_data(i2c_client, + CS42888_FIRSTREG | CS42888_I2C_INCR, CS42888_NUMREGS, cache); + + if (length != CS42888_NUMREGS) { + dev_err(codec->dev, "i2c read failure, addr=0x%x\n", + i2c_client->addr); + return -EIO; + } + + return 0; +} + +/** + * cs42888_read_reg_cache - read from the CS42888 register cache. + * @codec: the codec for this CS42888 + * @reg: the register to read + * + * This function returns the value for a given register. It reads only from + * the register cache, not the hardware itself. + * + * This CS42888 registers are cached to avoid excessive I2C I/O operations. + * After the initial read to pre-fill the cache, the CS42888 never updates + * the register values, so we won't have a cache coherency problem. + */ +static u8 cs42888_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + + if ((reg < CS42888_FIRSTREG) || (reg > CS42888_LASTREG)) + return -EIO; + + return cache[reg - CS42888_FIRSTREG]; +} + +/** + * cs42888_i2c_write - write to a CS42888 register via the I2C bus. + * @codec: the codec for this CS42888 + * @reg: the register to write + * @value: the value to write to the register + * + * This function writes the given value to the given CS42888 register, and + * also updates the register cache. + * + * Note that we don't use the hw_write function pointer of snd_soc_codec. + * That's because it's too clunky: the hw_write_t prototype does not match + * i2c_smbus_write_byte_data(), and it's just another layer of overhead. + */ +static int cs42888_i2c_write(struct snd_soc_codec *codec, unsigned int reg, + u8 value) +{ + u8 *cache = codec->reg_cache; + + if ((reg < CS42888_FIRSTREG) || (reg > CS42888_LASTREG)) + return -EIO; + + /* Only perform an I2C operation if the new value is different */ + if (cache[reg - CS42888_FIRSTREG] != value) { + if (i2c_smbus_write_byte_data(cs42888_i2c_client, reg, value) + < 0) { + dev_err(codec->dev, "i2c write failed\n"); + return -EIO; + } + + /* We've written to the hardware, so update the cache */ + cache[reg - CS42888_FIRSTREG] = value; + } + + return 0; +} + +#ifdef CS42888_DEBUG +static void dump_reg(struct snd_soc_codec *codec) +{ + int i, reg; + int ret; + printk(KERN_DEBUG "dump begin\n"); + printk(KERN_DEBUG "reg value in cache\n"); + for (i = 0; i < CS42888_NUMREGS; i++) + printk(KERN_DEBUG "reg[%d] = 0x%x\n", i, cache[i]); + + printk(KERN_DEBUG "real reg value\n"); + ret = cs42888_fill_cache(codec); + if (ret < 0) { + pr_err("failed to fill register cache\n"); + return ret; + } + for (i = 0; i < CS42888_NUMREGS; i++) + printk(KERN_DEBUG "reg[%d] = 0x%x\n", i, cache[i]); + + printk(KERN_DEBUG "dump end\n"); +} +#else +static void dump_reg(struct snd_soc_codec *codec) +{ +} +#endif + +/* -127.5dB to 0dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +/* -64dB to 24dB with step of 0.5dB */ +static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 1); + +static int cs42888_out_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int ret; + u16 val; + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* Now write again with the volume update bit set */ + val = cs42888_read_reg_cache(codec, reg); + ret = cs42888_i2c_write(codec, reg, val); + + val = cs42888_read_reg_cache(codec, reg2); + ret = cs42888_i2c_write(codec, reg2, val); + return 0; +} + +int cs42888_info_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + int min = mc->min; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = max-min; + return 0; +} + +int cs42888_get_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int min = mc->min; + int val = cs42888_read_reg_cache(codec, reg); + + ucontrol->value.integer.value[0] = + ((signed char)(val))-min; + + val = cs42888_read_reg_cache(codec, reg2); + ucontrol->value.integer.value[1] = + ((signed char)(val))-min; + return 0; +} + +int cs42888_put_volsw_s8(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + int min = mc->min; + unsigned short val; + int ret; + + val = (ucontrol->value.integer.value[0]+min); + ret = cs42888_i2c_write(codec, reg, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + val = ((ucontrol->value.integer.value[1]+min)); + ret = cs42888_i2c_write(codec, reg2, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + return 0; +} + +#define SOC_CS42888_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \ + xinvert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r, \ + .get = snd_soc_get_volsw_2r, \ + .put = cs42888_out_vu, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, \ + .rreg = reg_right, \ + .shift = xshift, \ + .max = xmax, \ + .invert = xinvert} \ +} + +#define SOC_CS42888_DOUBLE_R_S8_TLV(xname, reg_left, reg_right, xmin, xmax, \ + tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = cs42888_info_volsw_s8, .get = cs42888_get_volsw_s8, \ + .put = cs42888_put_volsw_s8, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, \ + .rreg = reg_right, \ + .min = xmin, \ + .max = xmax} \ +} + +static const char *cs42888_adcfilter[] = { "None", "High Pass" }; +static const char *cs42888_dacinvert[] = { "Disabled", "Enabled" }; +static const char *cs42888_adcinvert[] = { "Disabled", "Enabled" }; +static const char *cs42888_dacamute[] = { "Disabled", "AutoMute" }; +static const char *cs42888_dac_sngvol[] = { "Disabled", "Enabled" }; +static const char *cs42888_dac_szc[] = { "Immediate Change", "Zero Cross", + "Soft Ramp", "Soft Ramp on Zero Cross" }; +static const char *cs42888_mute_adc[] = { "UnMute", "Mute" }; +static const char *cs42888_adc_sngvol[] = { "Disabled", "Enabled" }; +static const char *cs42888_adc_szc[] = { "Immediate Change", "Zero Cross", + "Soft Ramp", "Soft Ramp on Zero Cross" }; +static const char *cs42888_dac_dem[] = { "No-De-Emphasis", "De-Emphasis" }; +static const char *cs42888_adc_single[] = { "Differential", "Single-Ended" }; + +static const struct soc_enum cs42888_enum[] = { + SOC_ENUM_SINGLE(CS42888_ADCCTL, 7, 2, cs42888_adcfilter), + SOC_ENUM_DOUBLE(CS42888_DACINV, 0, 1, 2, cs42888_dacinvert), + SOC_ENUM_DOUBLE(CS42888_DACINV, 2, 3, 2, cs42888_dacinvert), + SOC_ENUM_DOUBLE(CS42888_DACINV, 4, 5, 2, cs42888_dacinvert), + SOC_ENUM_DOUBLE(CS42888_DACINV, 6, 7, 2, cs42888_dacinvert), + SOC_ENUM_DOUBLE(CS42888_ADCINV, 0, 1, 2, cs42888_adcinvert), + SOC_ENUM_DOUBLE(CS42888_ADCINV, 2, 3, 2, cs42888_adcinvert), + SOC_ENUM_SINGLE(CS42888_TRANS, 4, 2, cs42888_dacamute), + SOC_ENUM_SINGLE(CS42888_TRANS, 7, 2, cs42888_dac_sngvol), + SOC_ENUM_SINGLE(CS42888_TRANS, 5, 4, cs42888_dac_szc), + SOC_ENUM_SINGLE(CS42888_TRANS, 3, 2, cs42888_mute_adc), + SOC_ENUM_SINGLE(CS42888_TRANS, 2, 2, cs42888_adc_sngvol), + SOC_ENUM_SINGLE(CS42888_TRANS, 0, 4, cs42888_adc_szc), + SOC_ENUM_SINGLE(CS42888_ADCCTL, 5, 2, cs42888_dac_dem), + SOC_ENUM_SINGLE(CS42888_ADCCTL, 4, 2, cs42888_adc_single), + SOC_ENUM_SINGLE(CS42888_ADCCTL, 3, 2, cs42888_adc_single), +}; + +static const struct snd_kcontrol_new cs42888_snd_controls[] = { +SOC_CS42888_DOUBLE_R_TLV("DAC1 Playback Volume", + CS42888_VOLAOUT1, + CS42888_VOLAOUT2, + 0, 0xff, 1, dac_tlv), +SOC_CS42888_DOUBLE_R_TLV("DAC2 Playback Volume", + CS42888_VOLAOUT3, + CS42888_VOLAOUT4, + 0, 0xff, 1, dac_tlv), +SOC_CS42888_DOUBLE_R_TLV("DAC3 Playback Volume", + CS42888_VOLAOUT5, + CS42888_VOLAOUT6, + 0, 0xff, 1, dac_tlv), +SOC_CS42888_DOUBLE_R_TLV("DAC4 Playback Volume", + CS42888_VOLAOUT7, + CS42888_VOLAOUT8, + 0, 0xff, 1, dac_tlv), +SOC_CS42888_DOUBLE_R_S8_TLV("ADC1 Capture Volume", + CS42888_VOLAIN1, + CS42888_VOLAIN2, + -128, 48, adc_tlv), +SOC_CS42888_DOUBLE_R_S8_TLV("ADC2 Capture Volume", + CS42888_VOLAIN3, + CS42888_VOLAIN4, + -128, 48, adc_tlv), +SOC_ENUM("ADC High-Pass Filter Switch", cs42888_enum[0]), +SOC_ENUM("DAC1 Invert Switch", cs42888_enum[1]), +SOC_ENUM("DAC2 Invert Switch", cs42888_enum[2]), +SOC_ENUM("DAC3 Invert Switch", cs42888_enum[3]), +SOC_ENUM("DAC4 Invert Switch", cs42888_enum[4]), +SOC_ENUM("ADC1 Invert Switch", cs42888_enum[5]), +SOC_ENUM("ADC2 Invert Switch", cs42888_enum[6]), +SOC_ENUM("DAC Auto Mute Switch", cs42888_enum[7]), +SOC_ENUM("DAC Single Volume Control Switch", cs42888_enum[8]), +SOC_ENUM("DAC Soft Ramp and Zero Cross Control Switch", cs42888_enum[9]), +SOC_ENUM("Mute ADC Serial Port Switch", cs42888_enum[10]), +SOC_ENUM("ADC Single Volume Control Switch", cs42888_enum[11]), +SOC_ENUM("ADC Soft Ramp and Zero Cross Control Switch", cs42888_enum[12]), +SOC_ENUM("DAC Deemphasis Switch", cs42888_enum[13]), +SOC_ENUM("ADC1 Single Ended Mode Switch", cs42888_enum[14]), +SOC_ENUM("ADC2 Single Ended Mode Switch", cs42888_enum[15]), +}; + + +static const struct snd_soc_dapm_widget cs42888_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC1", "Playback", CS42888_PWRCTL, 1, 1), +SND_SOC_DAPM_DAC("DAC2", "Playback", CS42888_PWRCTL, 2, 1), +SND_SOC_DAPM_DAC("DAC3", "Playback", CS42888_PWRCTL, 3, 1), +SND_SOC_DAPM_DAC("DAC4", "Playback", CS42888_PWRCTL, 4, 1), + +SND_SOC_DAPM_OUTPUT("AOUT1L"), +SND_SOC_DAPM_OUTPUT("AOUT1R"), +SND_SOC_DAPM_OUTPUT("AOUT2L"), +SND_SOC_DAPM_OUTPUT("AOUT2R"), +SND_SOC_DAPM_OUTPUT("AOUT3L"), +SND_SOC_DAPM_OUTPUT("AOUT3R"), +SND_SOC_DAPM_OUTPUT("AOUT4L"), +SND_SOC_DAPM_OUTPUT("AOUT4R"), + +SND_SOC_DAPM_ADC("ADC1", "Capture", CS42888_PWRCTL, 5, 1), +SND_SOC_DAPM_ADC("ADC2", "Capture", CS42888_PWRCTL, 6, 1), + +SND_SOC_DAPM_INPUT("AIN1L"), +SND_SOC_DAPM_INPUT("AIN1R"), +SND_SOC_DAPM_INPUT("AIN2L"), +SND_SOC_DAPM_INPUT("AIN2R"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Playback */ + { "AOUT1L", NULL, "DAC1" }, + { "AOUT1R", NULL, "DAC1" }, + + { "AOUT2L", NULL, "DAC2" }, + { "AOUT2R", NULL, "DAC2" }, + + { "AOUT3L", NULL, "DAC3" }, + { "AOUT3R", NULL, "DAC3" }, + + { "AOUT4L", NULL, "DAC4" }, + { "AOUT4R", NULL, "DAC4" }, + + /* Capture */ + { "ADC1", NULL, "AIN1L" }, + { "ADC1", NULL, "AIN1R" }, + + { "ADC2", NULL, "AIN2L" }, + { "ADC2", NULL, "AIN2R" }, +}; + + +static int ca42888_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, cs42888_dapm_widgets, + ARRAY_SIZE(cs42888_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/** + * struct cs42888_mode_ratios - clock ratio tables + * @ratio: the ratio of MCLK to the sample rate + * @speed_mode: the Speed Mode bits to set in the Mode Control register for + * this ratio + * @mclk: the Ratio Select bits to set in the Mode Control register for this + * ratio + * + * The data for this chart is taken from Table 10 of the CS42888 reference + * manual. + * + * This table is used to determine how to program the Functional Mode register. + * It is also used by cs42888_set_dai_sysclk() to tell ALSA which sampling + * rates the CS42888 currently supports. + * + * @speed_mode is the corresponding bit pattern to be written to the + * MODE bits of the Mode Control Register + * + * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of + * the Mode Control Register. + * + */ +struct cs42888_mode_ratios { + unsigned int ratio; + u8 speed_mode; + u8 mclk; +}; + +static struct cs42888_mode_ratios cs42888_mode_ratios[] = { + {64, CS42888_MODE_4X, CS42888_MODE_DIV1}, + {96, CS42888_MODE_4X, CS42888_MODE_DIV2}, + {128, CS42888_MODE_2X, CS42888_MODE_DIV1}, + {192, CS42888_MODE_2X, CS42888_MODE_DIV2}, + {256, CS42888_MODE_1X, CS42888_MODE_DIV1}, + {384, CS42888_MODE_2X, CS42888_MODE_DIV4}, + {512, CS42888_MODE_1X, CS42888_MODE_DIV3}, + {768, CS42888_MODE_1X, CS42888_MODE_DIV4}, + {1024, CS42888_MODE_1X, CS42888_MODE_DIV5} +}; + +/* The number of MCLK/LRCK ratios supported by the CS42888 */ +#define NUM_MCLK_RATIOS ARRAY_SIZE(cs42888_mode_ratios) + +/** + * cs42888_set_dai_sysclk - determine the CS42888 samples rates. + * @codec_dai: the codec DAI + * @clk_id: the clock ID (ignored) + * @freq: the MCLK input frequency + * @dir: the clock direction (ignored) + * + * This function is used to tell the codec driver what the input MCLK + * frequency is. + * + */ +static int cs42888_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 cs42888_private *cs42888 = codec->private_data; + + cs42888->mclk = freq; + + return 0; +} + +/** + * cs42888_set_dai_fmt - configure the codec for the selected audio format + * @codec_dai: the codec DAI + * @format: a SND_SOC_DAIFMT_x value indicating the data format + * + * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the + * codec accordingly. + * + * Currently, this function only supports SND_SOC_DAIFMT_I2S and + * SND_SOC_DAIFMT_LEFT_J. The CS42888 codec also supports right-justified + * data for playback only, but ASoC currently does not support different + * formats for playback vs. record. + */ +static int cs42888_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42888_private *cs42888 = codec->private_data; + int ret = 0; + u8 val; + val = cs42888_read_reg_cache(codec, CS42888_FORMAT); + val &= ~CS42888_FORMAT_DAC_DIF_MASK; + val &= ~CS42888_FORMAT_ADC_DIF_MASK; + /* set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_LEFT_J: + val |= DIF_LEFT_J << CS42888_FORMAT_DAC_DIF_OFFSET; + val |= DIF_LEFT_J << CS42888_FORMAT_ADC_DIF_OFFSET; + break; + case SND_SOC_DAIFMT_I2S: + val |= DIF_I2S << CS42888_FORMAT_DAC_DIF_OFFSET; + val |= DIF_I2S << CS42888_FORMAT_ADC_DIF_OFFSET; + break; + case SND_SOC_DAIFMT_RIGHT_J: + val |= DIF_RIGHT_J << CS42888_FORMAT_DAC_DIF_OFFSET; + val |= DIF_RIGHT_J << CS42888_FORMAT_ADC_DIF_OFFSET; + break; + default: + dev_err(codec->dev, "invalid dai format\n"); + ret = -EINVAL; + return ret; + } + + ret = cs42888_i2c_write(codec, CS42888_FORMAT, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + val = cs42888_read_reg_cache(codec, CS42888_MODE); + /* set master/slave audio interface */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + cs42888->slave_mode = 1; + val &= ~CS42888_MODE_SPEED_MASK; + val |= CS42888_MODE_SLAVE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + cs42888->slave_mode = 0; + break; + default: + /* all other modes are unsupported by the hardware */ + ret = -EINVAL; + return ret; + } + + ret = cs42888_i2c_write(codec, CS42888_MODE, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + return ret; +} + +/** + * cs42888_hw_params - program the CS42888 with the given hardware parameters. + * @substream: the audio stream + * @params: the hardware parameters to set + + * @dai: the SOC DAI (ignored) + * + * This function programs the hardware with the values provided. + * Specifically, the sample rate and the data format. + * + * The .ops functions are used to provide board-specific data, like input + * frequencies, to this driver. This function takes that information, + * combines it with the hardware parameters provided, and programs the + * hardware accordingly. + */ +static int cs42888_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 cs42888_private *cs42888 = codec->private_data; + int ret; + unsigned int i; + unsigned int rate; + unsigned int ratio; + u8 val; + + rate = params_rate(params); /* Sampling rate, in Hz */ + ratio = cs42888->mclk / rate; /* MCLK/LRCK ratio */ + + for (i = 0; i < NUM_MCLK_RATIOS; i++) { + if (cs42888_mode_ratios[i].ratio == ratio) + break; + } + + if (i == NUM_MCLK_RATIOS) { + /* We did not find a matching ratio */ + dev_err(codec->dev, "could not find matching ratio\n"); + return -EINVAL; + } + + if (!cs42888->slave_mode) { + val = cs42888_read_reg_cache(codec, CS42888_MODE); + val &= ~CS42888_MODE_SPEED_MASK; + val |= cs42888_mode_ratios[i].speed_mode; + val &= ~CS42888_MODE_DIV_MASK; + val |= cs42888_mode_ratios[i].mclk; + } else { + val = cs42888_read_reg_cache(codec, CS42888_MODE); + val &= ~CS42888_MODE_SPEED_MASK; + val |= CS42888_MODE_SLAVE; + } + ret = cs42888_i2c_write(codec, CS42888_MODE, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + /* Out of low power state */ + val = cs42888_read_reg_cache(codec, CS42888_PWRCTL); + val &= ~CS42888_PWRCTL_PDN_MASK; + ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + /* Unmute all the channels */ + val = cs42888_read_reg_cache(codec, CS42888_MUTE); + val &= ~CS42888_MUTE_ALL; + ret = cs42888_i2c_write(codec, CS42888_MUTE, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + ret = cs42888_fill_cache(codec); + if (ret < 0) { + pr_err("failed to fill register cache\n"); + return ret; + } + + return ret; +} + +/** + * cs42888_shutdown - cs42888 enters into low power mode again. + * @substream: the audio stream + * @dai: the SOC DAI (ignored) + * + * The .ops functions are used to provide board-specific data, like input + * frequencies, to this driver. This function takes that information, + * combines it with the hardware parameters provided, and programs the + * hardware accordingly. + */ +static void cs42888_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; + int ret; + u8 val; + + /* Mute all the channels */ + val = cs42888_read_reg_cache(codec, CS42888_MUTE); + val |= CS42888_MUTE_ALL; + ret = cs42888_i2c_write(codec, CS42888_MUTE, val); + if (ret < 0) + pr_err("i2c write failed\n"); + + /* Enter low power state */ + val = cs42888_read_reg_cache(codec, CS42888_PWRCTL); + val |= CS42888_PWRCTL_PDN_MASK; + ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val); + if (ret < 0) + pr_err("i2c write failed\n"); +} + +/* + * cs42888_codec - global variable to store codec for the ASoC probe function + * + * If struct i2c_driver had a private_data field, we wouldn't need to use + * cs42888_codec. This is the only way to pass the codec structure from + * cs42888_i2c_probe() to cs42888_probe(). Unfortunately, there is no good + * way to synchronize these two functions. cs42888_i2c_probe() can be called + * multiple times before cs42888_probe() is called even once. So for now, we + * also only allow cs42888_i2c_probe() to be run once. That means that we do + * not support more than one cs42888 device in the system, at least for now. + */ +static struct snd_soc_codec *cs42888_codec; + +static struct snd_soc_dai_ops cs42888_dai_ops = { + .set_fmt = cs42888_set_dai_fmt, + .set_sysclk = cs42888_set_dai_sysclk, + .hw_params = cs42888_hw_params, + .shutdown = cs42888_shutdown, +}; + +struct snd_soc_dai cs42888_dai = { + .name = "CS42888", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_176400), + .formats = CS42888_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_176400), + .formats = CS42888_FORMATS, + }, + .ops = &cs42888_dai_ops, +}; +EXPORT_SYMBOL_GPL(cs42888_dai); + +/** + * cs42888_probe - ASoC probe function + * @pdev: platform device + * + * This function is called when ASoC has all the pieces it needs to + * instantiate a sound driver. + */ +static int cs42888_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = cs42888_codec; + int ret; + + /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */ + socdev->card->codec = codec; + + /* 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; + } + + /* Add the non-DAPM controls */ + ret = snd_soc_add_controls(codec, cs42888_snd_controls, + ARRAY_SIZE(cs42888_snd_controls)); + if (ret < 0) { + dev_err(codec->dev, "failed to add controls\n"); + goto error_free_pcms; + } + + /* Add DAPM controls */ + ca42888_add_widgets(codec); + + /* And finally, register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + goto error_free_pcms; + } + + return 0; + +error_free_pcms: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return ret; +} + +/** + * cs42888_remove - ASoC remove function + * @pdev: platform device + * + * This function is the counterpart to cs42888_probe(). + */ +static int cs42888_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +}; + + +/** + * cs42888_i2c_probe - initialize the I2C interface of the CS42888 + * @i2c_client: the I2C client object + * @id: the I2C device ID (ignored) + * + * This function is called whenever the I2C subsystem finds a device that + * matches the device ID given via a prior call to i2c_add_driver(). + */ +static int cs42888_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct snd_soc_codec *codec; + struct cs42888_private *cs42888; + int ret; + struct regulator *regulator_vsd; + u8 val; + + if (cs42888_codec) { + dev_err(&i2c_client->dev, + "Multiple CS42888 devices not supported\n"); + return -ENOMEM; + } + + cs42888_i2c_client = i2c_client; + + /* Allocate enough space for the snd_soc_codec structure + and our private data together. */ + cs42888 = kzalloc(sizeof(struct cs42888_private), GFP_KERNEL); + if (!cs42888) { + dev_err(&i2c_client->dev, "could not allocate codec\n"); + return -ENOMEM; + } + + /* hold on reset */ + gpio_cs42888_pdwn(1); + + regulator_vsd = regulator_get(&i2c_client->dev, "VSD"); + if (!IS_ERR(regulator_vsd)) + cs42888->regulator_vsd = regulator_vsd; + + if (cs42888->regulator_vsd) { + regulator_set_voltage(cs42888->regulator_vsd, + 2800000, 2800000); + if (regulator_enable(cs42888->regulator_vsd) != 0) { + pr_err("%s:VSD set voltage error\n", __func__); + } else { + dev_dbg(&i2c_client->dev, + "%s:io set voltage ok\n", __func__); + } + } + + msleep(1); + /* out of reset state */ + gpio_cs42888_pdwn(0); + + /* Verify that we have a CS42888 */ + ret = cs42888_read_reg(CS42888_CHIPID, &val); + if (ret < 0) { + pr_err("Device with ID register %x is not a CS42888", val); + return -ENODEV; + } + /* The top four bits of the chip ID should be 0000. */ + if ((val & CS42888_CHIPID_ID_MASK) != 0x00) { + dev_err(&i2c_client->dev, "device is not a CS42888\n"); + return -ENODEV; + } + + dev_info(&i2c_client->dev, "found device at i2c address %X\n", + i2c_client->addr); + dev_info(&i2c_client->dev, "hardware revision %X\n", val & 0xF); + + codec = &cs42888->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c_client, cs42888); + codec->control_data = i2c_client; + + codec->dev = &i2c_client->dev; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = cs42888; + codec->name = "CS42888"; + codec->owner = THIS_MODULE; + codec->read = cs42888_read_reg_cache; + codec->write = cs42888_i2c_write; + codec->dai = &cs42888_dai; + codec->num_dai = 1; + codec->reg_cache = cs42888->reg_cache; + codec->reg_cache_size = ARRAY_SIZE(cs42888->reg_cache); + + /* The I2C interface is set up, so pre-fill our register cache */ + ret = cs42888_fill_cache(codec); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to fill register cache\n"); + goto error_free_codec; + } + + /* Enter low power state */ + val = cs42888_read_reg_cache(codec, CS42888_PWRCTL); + val |= CS42888_PWRCTL_PDN_MASK; + ret = cs42888_i2c_write(codec, CS42888_PWRCTL, val); + if (ret < 0) { + dev_err(&i2c_client->dev, "i2c write failed\n"); + return ret; + } + + /* Disable auto-mute */ + val = cs42888_read_reg_cache(codec, CS42888_TRANS); + val &= ~CS42888_TRANS_AMUTE_MASK; + ret = cs42888_i2c_write(codec, CS42888_TRANS, val); + if (ret < 0) { + pr_err("i2c write failed\n"); + return ret; + } + + cs42888_dai.dev = &i2c_client->dev; + + cs42888_codec = codec; + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to register codec: %d\n", ret); + goto error_free_codec; + } + + ret = snd_soc_register_dai(&cs42888_dai); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to register DAIe\n"); + goto error_codec; + } + + return 0; + +error_codec: + snd_soc_unregister_codec(codec); +error_free_codec: + kfree(cs42888); + cs42888_codec = NULL; + cs42888_dai.dev = NULL; + + return ret; +} + +/** + * cs42888_i2c_remove - remove an I2C device + * @i2c_client: the I2C client object + * + * This function is the counterpart to cs42888_i2c_probe(). + */ +static int cs42888_i2c_remove(struct i2c_client *i2c_client) +{ + struct cs42888_private *cs42888 = i2c_get_clientdata(i2c_client); + + snd_soc_unregister_dai(&cs42888_dai); + snd_soc_unregister_codec(&cs42888->codec); + kfree(cs42888); + cs42888_codec = NULL; + cs42888_dai.dev = NULL; + + return 0; +} + +/* + * cs42888_i2c_id - I2C device IDs supported by this driver + */ +static struct i2c_device_id cs42888_i2c_id[] = { + {"cs42888", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs42888_i2c_id); + +#ifdef CONFIG_PM + +/* This suspend/resume implementation can handle both - a simple standby + * where the codec remains powered, and a full suspend, where the voltage + * domain the codec is connected to is teared down and/or any other hardware + * reset condition is asserted. + * + * The codec's own power saving features are enabled in the suspend callback, + * and all registers are written back to the hardware when resuming. + */ + +static int cs42888_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cs42888_private *cs42888 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = &cs42888->codec; + int reg = snd_soc_read(codec, CS42888_PWRCTL) | CS42888_PWRCTL_PDN_MASK; + + return snd_soc_write(codec, CS42888_PWRCTL, reg); +} + +static int cs42888_i2c_resume(struct i2c_client *client) +{ + struct cs42888_private *cs42888 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = &cs42888->codec; + int reg; + + /* In case the device was put to hard reset during sleep, we need to + * wait 500ns here before any I2C communication. */ + ndelay(500); + + /* first restore the entire register cache ... */ + for (reg = CS42888_FIRSTREG; reg <= CS42888_LASTREG; reg++) { + u8 val = snd_soc_read(codec, reg); + + if (i2c_smbus_write_byte_data(client, reg, val)) { + dev_err(codec->dev, "i2c write failed\n"); + return -EIO; + } + } + + /* ... then disable the power-down bits */ + reg = snd_soc_read(codec, CS42888_PWRCTL); + reg &= ~CS42888_PWRCTL_PDN_MASK; + + return snd_soc_write(codec, CS42888_PWRCTL, reg); +} +#else +#define cs42888_i2c_suspend NULL +#define cs42888_i2c_resume NULL +#endif /* CONFIG_PM */ + +/* + * cs42888_i2c_driver - I2C device identification + * + * This structure tells the I2C subsystem how to identify and support a + * given I2C device type. + */ +static struct i2c_driver cs42888_i2c_driver = { + .driver = { + .name = "cs42888", + .owner = THIS_MODULE, + }, + .id_table = cs42888_i2c_id, + .probe = cs42888_i2c_probe, + .remove = cs42888_i2c_remove, + .suspend = cs42888_i2c_suspend, + .resume = cs42888_i2c_resume, +}; + +/* + * ASoC codec device structure + * + * Assign this variable to the codec_dev field of the machine driver's + * snd_soc_device structure. + */ +struct snd_soc_codec_device soc_codec_device_cs42888 = { + .probe = cs42888_probe, + .remove = cs42888_remove +}; +EXPORT_SYMBOL_GPL(soc_codec_device_cs42888); + +static int __init cs42888_init(void) +{ + pr_info("Cirrus Logic CS42888 ALSA SoC Codec Driver\n"); + + return i2c_add_driver(&cs42888_i2c_driver); +} +module_init(cs42888_init); + +static void __exit cs42888_exit(void) +{ + i2c_del_driver(&cs42888_i2c_driver); +} +module_exit(cs42888_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Cirrus Logic CS42888 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42888.h b/sound/soc/codecs/cs42888.h new file mode 100644 index 000000000000..a2c8562c17c5 --- /dev/null +++ b/sound/soc/codecs/cs42888.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _CS42888_H +#define _CS42888_H + +/* + * The ASoC codec DAI structure for the CS42888. Assign this structure to + * the .codec_dai field of your machine driver's snd_soc_dai_link structure. + */ +extern struct snd_soc_dai cs42888_dai; + +/* + * The ASoC codec device structure for the CS42888. Assign this structure + * to the .codec_dev field of your machine driver's snd_soc_device + * structure. + */ +extern struct snd_soc_codec_device soc_codec_device_cs42888; + +extern void gpio_cs42888_pdwn(int pdwn); +#endif diff --git a/sound/soc/codecs/mxs-adc-codec.c b/sound/soc/codecs/mxs-adc-codec.c new file mode 100644 index 000000000000..f8f10619731a --- /dev/null +++ b/sound/soc/codecs/mxs-adc-codec.c @@ -0,0 +1,1220 @@ +/* + * ALSA codec for Freescale MXS ADC/DAC Audio + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.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 <asm/dma.h> + +#include <mach/dma.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/regs-audioin.h> +#include <mach/regs-audioout.h> +#include <mach/regs-rtc.h> + +#include "mxs-adc-codec.h" + +#define BV_AUDIOIN_ADCVOL_SELECT__MIC 0x00 /* missing define */ + +#ifndef BF +#define BF(value, field) (((value) << BP_##field) & BM_##field) +#endif + +#define BM_RTC_PERSISTENT0_RELEASE_GND BF(0x2, RTC_PERSISTENT0_SPARE_ANALOG) + +#define REGS_RTC_BASE (IO_ADDRESS(RTC_PHYS_ADDR)) + +struct mxs_codec_priv { + struct clk *clk; + struct snd_soc_codec codec; +}; + +/* + * ALSA API + */ +static void __iomem *adc_regmap[] = { + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_STAT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACDEBUG, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT0, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT1, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_VERSION, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL, + REGS_AUDIOIN_BASE + HW_AUDIOIN_STAT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCDEBUG, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL, + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL, + REGS_AUDIOIN_BASE + HW_AUDIOIN_DATA, +}; + +static u16 mxs_audio_regs[ADC_REGNUM]; + +static u8 dac_volumn_control_word[] = { + 0x37, 0x5e, 0x7e, 0x8e, + 0x9e, 0xae, 0xb6, 0xbe, + 0xc6, 0xce, 0xd6, 0xde, + 0xe6, 0xee, 0xf6, 0xfe, +}; + +/* + * ALSA core supports only 16 bit registers. It means we have to simulate it + * by virtually splitting a 32bit ADC/DAC registers into two halves + * high (bits 31:16) and low (bits 15:0). The routins abow detects which part + * of 32bit register is accessed. + */ +static void mxs_codec_write_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < ADC_REGNUM) + cache[reg] = value; +} + +static int mxs_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned int reg_val; + unsigned int mask = 0xffff; + + if (reg >= ADC_REGNUM) + return -EIO; + + mxs_codec_write_cache(codec, reg, value); + + if (reg & 0x1) { + mask <<= 16; + value <<= 16; + } + + reg_val = __raw_readl(adc_regmap[reg >> 1]); + reg_val = (reg_val & ~mask) | value; + __raw_writel(reg_val, adc_regmap[reg >> 1]); + + return 0; +} + +static unsigned int mxs_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int reg_val; + + if (reg >= ADC_REGNUM) + return -1; + + reg_val = __raw_readl(adc_regmap[reg >> 1]); + if (reg & 1) + reg_val >>= 16; + + return reg_val & 0xffff; +} + +static unsigned int mxs_codec_read_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= ADC_REGNUM) + return -EINVAL; + return cache[reg]; +} + +static void mxs_codec_sync_reg_cache(struct snd_soc_codec *codec) +{ + int reg; + for (reg = 0; reg < ADC_REGNUM; reg += 1) + mxs_codec_write_cache(codec, reg, + mxs_codec_read(codec, reg)); +} + +static int mxs_codec_restore_reg(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int cached_val, hw_val; + + cached_val = mxs_codec_read_cache(codec, reg); + hw_val = mxs_codec_read(codec, reg); + + if (hw_val != cached_val) + return mxs_codec_write(codec, reg, cached_val); + + 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 = 0xf; + return 0; +} + +static int dac_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg, l, r; + int i; + + reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + + l = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT; + r = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT; + /*Left channel */ + i = 0; + while (i < 16) { + if (l == dac_volumn_control_word[i]) { + ucontrol->value.integer.value[0] = i; + break; + } + i++; + } + if (i == 16) + ucontrol->value.integer.value[0] = i; + /*Right channel */ + i = 0; + while (i < 16) { + if (r == dac_volumn_control_word[i]) { + ucontrol->value.integer.value[1] = i; + break; + } + i++; + } + if (i == 16) + ucontrol->value.integer.value[1] = i; + + return 0; +} + +static int dac_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg, l, r; + int i; + + i = ucontrol->value.integer.value[0]; + l = dac_volumn_control_word[i]; + /*Get dac volume for left channel */ + reg = BF(l, AUDIOOUT_DACVOLUME_VOLUME_LEFT); + + i = ucontrol->value.integer.value[1]; + r = dac_volumn_control_word[i]; + /*Get dac volume for right channel */ + reg = reg | BF(r, AUDIOOUT_DACVOLUME_VOLUME_RIGHT); + + /*Clear left/right dac volume */ + __raw_writel(BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT | + BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR); + __raw_writel(reg, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + + return 0; +} + +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Prepare powering up HP and SPEAKER output */ + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND, + REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET); + msleep(100); + break; + case SND_SOC_DAPM_POST_PMU: + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + break; + case SND_SOC_DAPM_POST_PMD: + __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND, + REGS_RTC_BASE + HW_RTC_PERSISTENT0_CLR); + break; + } + return 0; +} + +static int adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND, + REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET); + msleep(100); + break; + case SND_SOC_DAPM_POST_PMD: + __raw_writel(BM_RTC_PERSISTENT0_RELEASE_GND, + REGS_RTC_BASE + HW_RTC_PERSISTENT0_CLR); + break; + } + return 0; +} + +static const char *mxs_codec_adc_input_sel[] = + { "Mic", "Line In 1", "Head Phone", "Line In 2" }; + +static const char *mxs_codec_hp_output_sel[] = { "DAC Out", "Line In 1" }; + +static const char *mxs_codec_adc_3d_sel[] = + { "Off", "Low", "Medium", "High" }; + +static const struct soc_enum mxs_codec_enum[] = { + SOC_ENUM_SINGLE(ADC_ADCVOL_L, 12, 4, mxs_codec_adc_input_sel), + SOC_ENUM_SINGLE(ADC_ADCVOL_L, 4, 4, mxs_codec_adc_input_sel), + SOC_ENUM_SINGLE(DAC_HPVOL_H, 0, 2, mxs_codec_hp_output_sel), + SOC_ENUM_SINGLE(DAC_CTRL_L, 8, 4, mxs_codec_adc_3d_sel), +}; + +/* Codec controls */ +static const struct snd_kcontrol_new mxs_snd_controls[] = { + /* Playback Volume */ + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC 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_R("DAC Playback Switch", + DAC_VOLUME_H, DAC_VOLUME_L, 8, 0x01, 1), + SOC_DOUBLE("HP Playback Volume", DAC_HPVOL_L, 8, 0, 0x7F, 1), + + /* Capture Volume */ + SOC_DOUBLE_R("ADC Capture Volume", + ADC_VOLUME_H, ADC_VOLUME_L, 0, 0xFF, 0), + SOC_DOUBLE("ADC PGA Capture Volume", ADC_ADCVOL_L, 8, 0, 0x0F, 0), + SOC_SINGLE("ADC PGA Capture Switch", ADC_ADCVOL_H, 8, 0x1, 1), + SOC_SINGLE("Mic PGA Capture Volume", ADC_MICLINE_L, 0, 0x03, 0), + + /* Virtual 3D effect */ + SOC_ENUM("3D effect", mxs_codec_enum[3]), +}; + +/* Left ADC Mux */ +static const struct snd_kcontrol_new mxs_left_adc_controls = +SOC_DAPM_ENUM("Route", mxs_codec_enum[0]); + +/* Right ADC Mux */ +static const struct snd_kcontrol_new mxs_right_adc_controls = +SOC_DAPM_ENUM("Route", mxs_codec_enum[1]); + +/* Head Phone Mux */ +static const struct snd_kcontrol_new mxs_hp_controls = +SOC_DAPM_ENUM("Route", mxs_codec_enum[2]); + +static const struct snd_soc_dapm_widget mxs_codec_widgets[] = { + + SND_SOC_DAPM_ADC_E("ADC", "Capture", DAC_PWRDN_L, 8, 1, adc_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_DAC("DAC", "Playback", DAC_PWRDN_L, 12, 1), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &mxs_left_adc_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &mxs_right_adc_controls), + SND_SOC_DAPM_MUX("HP Mux", SND_SOC_NOPM, 0, 0, + &mxs_hp_controls), + SND_SOC_DAPM_PGA_E("HP AMP", DAC_PWRDN_L, 0, 1, NULL, 0, pga_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA("SPEAKER AMP", DAC_PWRDN_H, 8, 1, NULL, 0), + SND_SOC_DAPM_INPUT("LINE1L"), + SND_SOC_DAPM_INPUT("LINE1R"), + SND_SOC_DAPM_INPUT("LINE2L"), + SND_SOC_DAPM_INPUT("LINE2R"), + SND_SOC_DAPM_INPUT("MIC"), + + SND_SOC_DAPM_OUTPUT("SPEAKER"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + + /* Left ADC Mux */ + {"Left ADC Mux", "Mic", "MIC"}, + {"Left ADC Mux", "Line In 1", "LINE1L"}, + {"Left ADC Mux", "Line In 2", "LINE2L"}, + {"Left ADC Mux", "Head Phone", "HPL"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Mic", "MIC"}, + {"Right ADC Mux", "Line In 1", "LINE1R"}, + {"Right ADC Mux", "Line In 2", "LINE2R"}, + {"Right ADC Mux", "Head Phone", "HPR"}, + + /* ADC */ + {"ADC", NULL, "Left ADC Mux"}, + {"ADC", NULL, "Right ADC Mux"}, + + /* HP Mux */ + {"HP Mux", "DAC Out", "DAC"}, + {"HP Mux", "Line In 1", "LINE1L"}, + {"HP Mux", "Line In 1", "LINE1R"}, + + /* HP amp */ + {"HP AMP", NULL, "HP Mux"}, + /* HP output */ + {"HPR", NULL, "HP AMP"}, + {"HPL", NULL, "HP AMP"}, + + /* Speaker amp */ + {"SPEAKER AMP", NULL, "DAC"}, + {"SPEAKER", NULL, "SPEAKER AMP"}, +}; + +static int mxs_codec_add_widgets(struct snd_soc_codec *codec) +{ + int ret = 0; + + snd_soc_dapm_new_controls(codec, mxs_codec_widgets, + ARRAY_SIZE(mxs_codec_widgets)); + + if (ret) { + dev_err(codec->dev, "dapm control register failed\n"); + return ret; + } + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + if (ret) { + dev_err(codec->dev, "DAPM route register failed\n"); + return ret; + } + + return snd_soc_dapm_new_widgets(codec); +} + +struct dac_srr { + u32 rate; + u32 basemult; + u32 src_hold; + u32 src_int; + u32 src_frac; +}; + +static struct dac_srr srr_values[] = { + {192000, 0x4, 0x0, 0x0F, 0x13FF}, + {176400, 0x4, 0x0, 0x11, 0x0037}, + {128000, 0x4, 0x0, 0x17, 0x0E00}, + {96000, 0x2, 0x0, 0x0F, 0x13FF}, + {88200, 0x2, 0x0, 0x11, 0x0037}, + {64000, 0x2, 0x0, 0x17, 0x0E00}, + {48000, 0x1, 0x0, 0x0F, 0x13FF}, + {44100, 0x1, 0x0, 0x11, 0x0037}, + {32000, 0x1, 0x0, 0x17, 0x0E00}, + {24000, 0x1, 0x1, 0x0F, 0x13FF}, + {22050, 0x1, 0x1, 0x11, 0x0037}, + {16000, 0x1, 0x1, 0x17, 0x0E00}, + {12000, 0x1, 0x3, 0x0F, 0x13FF}, + {11025, 0x1, 0x3, 0x11, 0x0037}, + {8000, 0x1, 0x3, 0x17, 0x0E00} +}; + +static inline int get_srr_values(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(srr_values); i++) + if (srr_values[i].rate == rate) + return i; + + return -1; +} + +static int mxs_codec_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; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int i; + u32 srr_value = 0; + u32 src_hold = 0; + + i = get_srr_values(params_rate(params)); + if (i < 0) + dev_warn(socdev->dev, "%s doesn't support rate %d\n", + codec->name, params_rate(params)); + else { + src_hold = srr_values[i].src_hold; + + srr_value = + BF(srr_values[i].basemult, AUDIOOUT_DACSRR_BASEMULT) | + BF(srr_values[i].src_int, AUDIOOUT_DACSRR_SRC_INT) | + BF(srr_values[i].src_frac, AUDIOOUT_DACSRR_SRC_FRAC) | + BF(src_hold, AUDIOOUT_DACSRR_SRC_HOLD); + + if (playback) + __raw_writel(srr_value, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR); + else + __raw_writel(srr_value, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (playback) + __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + else + __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + + break; + + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + else + __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + + break; + + default: + dev_warn(socdev->dev, "%s doesn't support format %d\n", + codec->name, params_format(params)); + + } + + return 0; +} + +static int mxs_codec_dig_mute(struct snd_soc_dai *dai, int mute) +{ + int l, r; + int ll, rr; + u32 reg, reg1, reg2; + u32 dac_mask = BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT; + + if (mute) { + reg = __raw_readl(REGS_AUDIOOUT_BASE + \ + HW_AUDIOOUT_DACVOLUME); + + reg1 = reg & ~BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT; + reg1 = reg1 & ~BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT; + + l = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT; + r = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT; + + /* fade out dac vol */ + while ((l > DAC_VOLUME_MIN) || (r > DAC_VOLUME_MIN)) { + l -= 0x8; + r -= 0x8; + ll = l > DAC_VOLUME_MIN ? l : DAC_VOLUME_MIN; + rr = r > DAC_VOLUME_MIN ? r : DAC_VOLUME_MIN; + reg2 = reg1 | BF_AUDIOOUT_DACVOLUME_VOLUME_LEFT(ll) + | BF_AUDIOOUT_DACVOLUME_VOLUME_RIGHT(rr); + __raw_writel(reg2, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + msleep(1); + } + + __raw_writel(dac_mask, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + reg = reg | dac_mask; + __raw_writel(reg, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + } else + __raw_writel(dac_mask, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR); + + return 0; +} + +static int mxs_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + 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; + break; + + case SND_SOC_BIAS_PREPARE: /* partial On */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE) + break; + /* Set Capless mode */ + __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_CLR); + break; + + case SND_SOC_BIAS_STANDBY: /* Off, with power */ + if (codec->bias_level == SND_SOC_BIAS_STANDBY) + break; + /* Unset Capless mode */ + __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + break; + + case SND_SOC_BIAS_OFF: /* Off, without power */ + if (codec->bias_level == SND_SOC_BIAS_OFF) + break; + /* Unset Capless mode */ + __raw_writel(BM_AUDIOOUT_PWRDN_CAPLESS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + break; + } + codec->bias_level = level; + return 0; +} + +/* + * Codec initialization + */ +#define VAG_BASE_VALUE ((1400/2 - 625)/25) +static void mxs_codec_dac_set_vag(void) +{ + u32 refctrl_val = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + + refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VAG_VAL); + refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VBG_ADJ); + refctrl_val |= BF(VAG_BASE_VALUE, AUDIOOUT_REFCTRL_VAG_VAL) | + BM_AUDIOOUT_REFCTRL_ADJ_VAG | + BF(0xF, AUDIOOUT_REFCTRL_ADC_REFVAL) | + BM_AUDIOOUT_REFCTRL_ADJ_ADC | + BF(0x3, AUDIOOUT_REFCTRL_VBG_ADJ) | BM_AUDIOOUT_REFCTRL_RAISE_REF; + + __raw_writel(refctrl_val, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); +} + +static bool mxs_codec_dac_is_capless() +{ + if ((__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN) + & BM_AUDIOOUT_PWRDN_CAPLESS) == 0) + return false; + else + return true; +} +static void mxs_codec_dac_arm_short_cm(bool bShort) +{ + __raw_writel(BF(3, AUDIOOUT_ANACTRL_SHORTMODE_CM), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_CM_STS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + if (bShort) + __raw_writel(BF(1, AUDIOOUT_ANACTRL_SHORTMODE_CM), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); +} +static void mxs_codec_dac_arm_short_lr(bool bShort) +{ + __raw_writel(BF(3, AUDIOOUT_ANACTRL_SHORTMODE_LR), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACTRL_SHORT_LR_STS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + if (bShort) + __raw_writel(BF(1, AUDIOOUT_ANACTRL_SHORTMODE_LR), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); +} +static void mxs_codec_dac_set_short_trip_level(u8 u8level) +{ + __raw_writel(__raw_readl(REGS_AUDIOOUT_BASE + + HW_AUDIOOUT_ANACTRL) + & (~BM_AUDIOOUT_ANACTRL_SHORT_LVLADJL) + & (~BM_AUDIOOUT_ANACTRL_SHORT_LVLADJR) + | BF(u8level, AUDIOOUT_ANACTRL_SHORT_LVLADJL) + | BF(u8level, AUDIOOUT_ANACTRL_SHORT_LVLADJR), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); +} +static void mxs_codec_dac_arm_short(bool bLatchCM, bool bLatchLR) +{ + if (bLatchCM) { + if (mxs_codec_dac_is_capless()) + mxs_codec_dac_arm_short_cm(true); + } else + mxs_codec_dac_arm_short_cm(false); + + if (bLatchLR) + mxs_codec_dac_arm_short_lr(true); + else + mxs_codec_dac_arm_short_lr(false); + +} +static void +mxs_codec_dac_power_on(struct mxs_codec_priv *mxs_adc) +{ + /* Ungate DAC clocks */ + __raw_writel(BM_AUDIOOUT_CTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_CLR); + + /* 16 bit word length */ + __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + + /* Arm headphone LR short protect */ + mxs_codec_dac_set_short_trip_level(0); + mxs_codec_dac_arm_short(false, true); + + /* Update DAC volume over zero crossings */ + __raw_writel(BM_AUDIOOUT_DACVOLUME_EN_ZCD, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + /* Mute DAC */ + __raw_writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + + /* Update HP volume over zero crossings */ + __raw_writel(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + /* Mute HP output */ + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + /* Mute speaker amp */ + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); + /* Enable the audioout */ + __raw_writel(BM_AUDIOOUT_CTRL_RUN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); +} + +static void +mxs_codec_dac_power_down(struct mxs_codec_priv *mxs_adc) +{ + /* Disable the audioout */ + __raw_writel(BM_AUDIOOUT_CTRL_RUN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + /* Disable class AB */ + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + + /* Set hold to ground */ + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + /* Mute HP output */ + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + /* Power down HP output */ + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + + /* Mute speaker amp */ + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); + /* Power down speaker amp */ + __raw_writel(BM_AUDIOOUT_PWRDN_SPEAKER, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + + /* Mute DAC */ + __raw_writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + /* Power down DAC */ + __raw_writel(BM_AUDIOOUT_PWRDN_DAC, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + + /* Gate DAC clocks */ + __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_SET); + __raw_writel(BM_AUDIOOUT_CTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); +} + +static void +mxs_codec_adc_power_on(struct mxs_codec_priv *mxs_adc) +{ + u32 reg; + + /* Ungate ADC clocks */ + __raw_writel(BM_AUDIOIN_CTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + __raw_writel(BM_AUDIOIN_ANACLKCTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL_CLR); + + /* 16 bit word length */ + __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + + /* Unmute ADC channels */ + __raw_writel(BM_AUDIOIN_ADCVOL_MUTE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + + /* + * The MUTE_LEFT and MUTE_RIGHT fields need to be cleared. + * They aren't presented in the datasheet, so this is hardcode. + */ + __raw_writel(0x01000100, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME_CLR); + + /* Set the Input channel gain 3dB */ + __raw_writel(BM_AUDIOIN_ADCVOL_GAIN_LEFT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + __raw_writel(BM_AUDIOIN_ADCVOL_GAIN_RIGHT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + __raw_writel(BF(2, AUDIOIN_ADCVOL_GAIN_LEFT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + __raw_writel(BF(2, AUDIOIN_ADCVOL_GAIN_RIGHT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + + /* Select default input - Microphone */ + __raw_writel(BM_AUDIOIN_ADCVOL_SELECT_LEFT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + __raw_writel(BM_AUDIOIN_ADCVOL_SELECT_RIGHT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + __raw_writel(BF + (BV_AUDIOIN_ADCVOL_SELECT__MIC, + AUDIOIN_ADCVOL_SELECT_LEFT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + __raw_writel(BF + (BV_AUDIOIN_ADCVOL_SELECT__MIC, + AUDIOIN_ADCVOL_SELECT_RIGHT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + + /* Supply bias voltage to microphone */ + __raw_writel(BF(1, AUDIOIN_MICLINE_MIC_RESISTOR), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET); + __raw_writel(BM_AUDIOIN_MICLINE_MIC_SELECT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET); + __raw_writel(BF(1, AUDIOIN_MICLINE_MIC_GAIN), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET); + __raw_writel(BF(7, AUDIOIN_MICLINE_MIC_BIAS), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET); + + /* Set max ADC volume */ + reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT; + reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT; + reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_LEFT); + reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_RIGHT); + __raw_writel(reg, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); +} + +static void +mxs_codec_adc_power_down(struct mxs_codec_priv *mxs_adc) +{ + /* Mute ADC channels */ + __raw_writel(BM_AUDIOIN_ADCVOL_MUTE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + + /* Power Down ADC */ + __raw_writel(BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + + /* Gate ADC clocks */ + __raw_writel(BM_AUDIOIN_CTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + __raw_writel(BM_AUDIOIN_ANACLKCTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL_SET); + + /* Disable bias voltage to microphone */ + __raw_writel(BF(0, AUDIOIN_MICLINE_MIC_RESISTOR), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET); +} + +static void mxs_codec_dac_enable(struct mxs_codec_priv *mxs_adc) +{ + /* Move DAC codec out of reset */ + __raw_writel(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + + /* Reduce analog power */ + __raw_writel(BM_AUDIOOUT_TEST_HP_I1_ADJ, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_CLR); + __raw_writel(BF(0x1, AUDIOOUT_TEST_HP_I1_ADJ), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_SET); + __raw_writel(BM_AUDIOOUT_REFCTRL_LOW_PWR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_SET); + __raw_writel(BM_AUDIOOUT_REFCTRL_XTAL_BGR_BIAS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_SET); + __raw_writel(BM_AUDIOOUT_REFCTRL_BIAS_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_CLR); + __raw_writel(BF(0x1, AUDIOOUT_REFCTRL_BIAS_CTRL), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_CLR); + + /* Set Vag value */ + mxs_codec_dac_set_vag(); + + /* Power on DAC codec */ + mxs_codec_dac_power_on(mxs_adc); +} + +static void mxs_codec_dac_disable(struct mxs_codec_priv *mxs_adc) +{ + mxs_codec_dac_power_down(mxs_adc); +} + +static void mxs_codec_adc_enable(struct mxs_codec_priv *mxs_adc) +{ + /* Move ADC codec out of reset */ + __raw_writel(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + + /* Power on ADC codec */ + mxs_codec_adc_power_on(mxs_adc); +} + +static void mxs_codec_adc_disable(struct mxs_codec_priv *mxs_adc) +{ + mxs_codec_adc_power_down(mxs_adc); +} + +static void mxs_codec_startup(struct snd_soc_codec *codec) +{ + struct mxs_codec_priv *mxs_adc = codec->private_data; + + /* Soft reset DAC block */ + __raw_writel(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + while (!(__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) + & BM_AUDIOOUT_CTRL_CLKGATE)){ + } + + /* Soft reset ADC block */ + __raw_writel(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + while (!(__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL) + & BM_AUDIOIN_CTRL_CLKGATE)){ + } + + mxs_codec_dac_enable(mxs_adc); + mxs_codec_adc_enable(mxs_adc); + + /*Sync regs and cache */ + mxs_codec_sync_reg_cache(codec); + + snd_soc_add_controls(codec, mxs_snd_controls, + ARRAY_SIZE(mxs_snd_controls)); + + mxs_codec_add_widgets(codec); +} + +static void mxs_codec_stop(struct snd_soc_codec *codec) +{ + struct mxs_codec_priv *mxs_adc = codec->private_data; + mxs_codec_dac_disable(mxs_adc); + mxs_codec_adc_disable(mxs_adc); +} + +#define MXS_ADC_RATES SNDRV_PCM_RATE_8000_192000 +#define MXS_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops mxs_dai_ops = { + .hw_params = mxs_codec_hw_params, + .digital_mute = mxs_codec_dig_mute, +}; + +struct snd_soc_dai mxs_codec_dai = { + .name = "mxs adc/dac", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = MXS_ADC_RATES, + .formats = MXS_ADC_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = MXS_ADC_RATES, + .formats = MXS_ADC_FORMATS, + }, + .ops = &mxs_dai_ops, +}; +EXPORT_SYMBOL_GPL(mxs_codec_dai); + +static struct snd_soc_codec *mxs_codec; + +static int mxs_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + socdev->card->codec = mxs_codec; + codec = mxs_codec; + + /* 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; + } + + mxs_codec_startup(codec); + + /* Register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + return ret; + } + /* Set default bias level*/ + mxs_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +static int mxs_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + mxs_codec_stop(codec); + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int mxs_codec_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; + struct mxs_codec_priv *mxs_adc; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + mxs_adc = codec->private_data; + + mxs_codec_dac_disable(mxs_adc); + mxs_codec_adc_disable(mxs_adc); + clk_disable(mxs_adc->clk); + ret = 0; + +out: + return ret; +} + +static int mxs_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct mxs_codec_priv *mxs_adc; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + mxs_adc = codec->private_data; + clk_enable(mxs_adc->clk); + + /* Soft reset DAC block */ + __raw_writel(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + while (! + (__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) + & BM_AUDIOOUT_CTRL_CLKGATE)){ + } + + /* Soft reset ADC block */ + __raw_writel(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + while (! + (__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL) + & BM_AUDIOIN_CTRL_CLKGATE)){ + } + + mxs_codec_dac_enable(mxs_adc); + mxs_codec_adc_enable(mxs_adc); + + /*restore registers relevant to amixer controls */ + mxs_codec_restore_reg(codec, DAC_CTRL_L); + mxs_codec_restore_reg(codec, DAC_VOLUME_L); + mxs_codec_restore_reg(codec, DAC_VOLUME_H); + mxs_codec_restore_reg(codec, DAC_HPVOL_L); + mxs_codec_restore_reg(codec, DAC_HPVOL_H); + mxs_codec_restore_reg(codec, DAC_SPEAKERCTRL_H); + mxs_codec_restore_reg(codec, ADC_VOLUME_L); + mxs_codec_restore_reg(codec, ADC_VOLUME_H); + mxs_codec_restore_reg(codec, ADC_ADCVOL_L); + mxs_codec_restore_reg(codec, ADC_ADCVOL_H); + mxs_codec_restore_reg(codec, ADC_MICLINE_L); + + ret = 0; + +out: + return ret; +} +#else +#define mxs_codec_suspend NULL +#define mxs_codec_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_codec_dev_mxs = { + .probe = mxs_codec_probe, + .remove = mxs_codec_remove, + .suspend = mxs_codec_suspend, + .resume = mxs_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_mxs); + +/* codec register, unregister function */ +static int __init mxs_codec_audio_probe(struct platform_device *pdev) +{ + struct mxs_codec_priv *mxs_adc; + struct snd_soc_codec *codec; + int ret = 0; + + dev_info(&pdev->dev, + "MXS ADC/DAC Audio Codec \n"); + + mxs_adc = kzalloc(sizeof(struct mxs_codec_priv), GFP_KERNEL); + if (mxs_adc == NULL) + return -ENOMEM; + + codec = &mxs_adc->codec; + codec->dev = &pdev->dev; + codec->name = "mxs adc/dac"; + codec->owner = THIS_MODULE; + codec->private_data = mxs_adc; + codec->read = mxs_codec_read; + codec->write = mxs_codec_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = mxs_codec_set_bias_level; + codec->dai = &mxs_codec_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(mxs_audio_regs) >> 1; + codec->reg_cache_step = 1; + codec->reg_cache = (void *)&mxs_audio_regs; + + platform_set_drvdata(pdev, mxs_adc); + + mxs_codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* Turn on audio clock */ + mxs_adc->clk = clk_get(&pdev->dev, "audio"); + if (IS_ERR(mxs_adc->clk)) { + ret = PTR_ERR(mxs_adc->clk); + dev_err(&pdev->dev, "%s: Clocks initialization failed\n", + __func__); + goto clk_err; + } + clk_enable(mxs_adc->clk); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + ret = snd_soc_register_dai(&mxs_codec_dai); + if (ret) { + dev_err(&pdev->dev, "failed to register codec dai\n"); + goto dai_err; + } + + return 0; + +dai_err: + snd_soc_unregister_codec(codec); +card_err: + clk_disable(mxs_adc->clk); + clk_put(mxs_adc->clk); +clk_err: + kfree(mxs_adc); + return ret; +} + +static int __devexit mxs_codec_audio_remove(struct platform_device *pdev) +{ + struct mxs_codec_priv *mxs_adc = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = &mxs_adc->codec; + + snd_soc_unregister_codec(codec); + + clk_disable(mxs_adc->clk); + clk_put(mxs_adc->clk); + + kfree(mxs_adc); + + return 0; +} + +struct platform_driver mxs_codec_audio_driver = { + .driver = { + .name = "mxs-adc-audio", + }, + .probe = mxs_codec_audio_probe, + .remove = __devexit_p(mxs_codec_audio_remove), +}; + +static int __init mxs_codec_init(void) +{ + return platform_driver_register(&mxs_codec_audio_driver); +} + +static void __exit mxs_codec_exit(void) +{ + return platform_driver_unregister(&mxs_codec_audio_driver); +} + +module_init(mxs_codec_init); +module_exit(mxs_codec_exit); + +MODULE_DESCRIPTION("MXS ADC/DAC codec"); +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/mxs-adc-codec.h b/sound/soc/codecs/mxs-adc-codec.h new file mode 100644 index 000000000000..129c1c7bdf66 --- /dev/null +++ b/sound/soc/codecs/mxs-adc-codec.h @@ -0,0 +1,87 @@ +/* + * ALSA codec for Freescale MXS ADC/DAC Audio + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __MXS_ADC_CODEC_H +#define __MXS_ADC_CODEC_H + +#define DAC_CTRL_L 0 +#define DAC_CTRL_H 1 +#define DAC_STAT_L 2 +#define DAC_STAT_H 3 +#define DAC_SRR_L 4 +#define DAC_VOLUME_L 6 +#define DAC_VOLUME_H 7 +#define DAC_DEBUG_L 8 +#define DAC_DEBUG_H 9 +#define DAC_HPVOL_L 10 +#define DAC_HPVOL_H 11 +#define DAC_PWRDN_L 12 +#define DAC_PWRDN_H 13 +#define DAC_REFCTRL_L 14 +#define DAC_REFCTRL_H 15 +#define DAC_ANACTRL_L 16 +#define DAC_ANACTRL_H 17 +#define DAC_TEST_L 18 +#define DAC_TEST_H 19 +#define DAC_BISTCTRL_L 20 +#define DAC_BISTCTRL_H 21 +#define DAC_BISTSTAT0_L 22 +#define DAC_BISTSTAT0_H 23 +#define DAC_BISTSTAT1_L 24 +#define DAC_BISTSTAT1_H 25 +#define DAC_ANACLKCTRL_L 26 +#define DAC_ANACLKCTRL_H 27 +#define DAC_DATA_L 28 +#define DAC_DATA_H 29 +#define DAC_SPEAKERCTRL_L 30 +#define DAC_SPEAKERCTRL_H 31 +#define DAC_VERSION_L 32 +#define DAC_VERSION_H 33 +#define ADC_CTRL_L 34 +#define ADC_CTRL_H 35 +#define ADC_STAT_L 36 +#define ADC_STAT_H 37 +#define ADC_SRR_L 38 +#define ADC_SRR_H 39 +#define ADC_VOLUME_L 40 +#define ADC_VOLUME_H 41 +#define ADC_DEBUG_L 42 +#define ADC_DEBUG_H 43 +#define ADC_ADCVOL_L 44 +#define ADC_ADCVOL_H 45 +#define ADC_MICLINE_L 46 +#define ADC_MICLINE_H 47 +#define ADC_ANACLKCTRL_L 48 +#define ADC_ANACLKCTRL_H 49 +#define ADC_DATA_L 50 +#define ADC_DATA_H 51 + +#define ADC_REGNUM 52 + +#define DAC_VOLUME_MIN 0x37 +#define DAC_VOLUME_MAX 0xFE +#define ADC_VOLUME_MIN 0x37 +#define ADC_VOLUME_MAX 0xFE +#define HP_VOLUME_MAX 0x0 +#define HP_VOLUME_MIN 0x7F +#define LO_VOLUME_MAX 0x0 +#define LO_VOLUME_MIN 0x1F + +extern struct snd_soc_dai mxs_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_mxs; + +#endif /* __MXS_ADC_CODEC_H */ diff --git a/sound/soc/codecs/mxs_spdif.c b/sound/soc/codecs/mxs_spdif.c new file mode 100644 index 000000000000..af47dcb547cf --- /dev/null +++ b/sound/soc/codecs/mxs_spdif.c @@ -0,0 +1,454 @@ +/* + * ALSA SoC MXS SPDIF codec driver + * + * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. + * + * Based on stmp3xxx_spdif.h by: + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.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 <asm/dma.h> +#include <mach/hardware.h> + +#include "mxs_spdif.h" + +#define REGS_SPDIF_BASE IO_ADDRESS(SPDIF_PHYS_ADDR) + +#define BF(value, field) (((value) << BP_##field) & BM_##field) + +struct mxs_codec_priv { + struct clk *clk; + struct snd_soc_codec codec; +}; + +/* + * ALSA API + */ +static void __iomem *spdif_regmap[] = { + REGS_SPDIF_BASE + HW_SPDIF_CTRL, + REGS_SPDIF_BASE + HW_SPDIF_STAT, + REGS_SPDIF_BASE + HW_SPDIF_FRAMECTRL, + REGS_SPDIF_BASE + HW_SPDIF_SRR, + REGS_SPDIF_BASE + HW_SPDIF_DEBUG, + REGS_SPDIF_BASE + HW_SPDIF_DATA, + REGS_SPDIF_BASE + HW_SPDIF_VERSION, +}; + +/* + * ALSA core supports only 16 bit registers. It means we have to simulate it + * by virtually splitting a 32bit SPDIF registers into two halves + * high (bits 31:16) and low (bits 15:0). The routins abow detects which part + * of 32bit register is accessed. + */ +static int mxs_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned int reg_val; + unsigned int mask = 0xffff; + + if (reg >= SPDIF_REGNUM) + return -EIO; + + if (reg & 0x1) { + mask <<= 16; + value <<= 16; + } + + reg_val = __raw_readl(spdif_regmap[reg >> 1]); + reg_val = (reg_val & ~mask) | value; + __raw_writel(reg_val, spdif_regmap[reg >> 1]); + + return 0; +} + +static unsigned int mxs_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int reg_val; + + if (reg >= SPDIF_REGNUM) + return -1; + + reg_val = __raw_readl(spdif_regmap[reg >> 1]); + if (reg & 1) + reg_val >>= 16; + + return reg_val & 0xffff; +} + +/* Codec controls */ +static const struct snd_kcontrol_new mxs_snd_controls[] = { + SOC_SINGLE("PRO", SPDIF_FRAMECTRL_L, 0, 0x1, 0), + SOC_SINGLE("AUDIO", SPDIF_FRAMECTRL_L, 1, 0x1, 0), + SOC_SINGLE("COPY", SPDIF_FRAMECTRL_L, 2, 0x1, 0), + SOC_SINGLE("PRE", SPDIF_FRAMECTRL_L, 3, 0x1, 0), + SOC_SINGLE("CC", SPDIF_FRAMECTRL_L, 4, 0x7F, 0), + SOC_SINGLE("L", SPDIF_FRAMECTRL_L, 12, 0x1, 0), + SOC_SINGLE("V", SPDIF_FRAMECTRL_L, 13, 0x1, 0), + SOC_SINGLE("USER DATA", SPDIF_FRAMECTRL_L, 14, 0x1, 0), + SOC_SINGLE("AUTO MUTE", SPDIF_FRAMECTRL_H, 16, 0x1, 0), + SOC_SINGLE("V CONFIG", SPDIF_FRAMECTRL_H, 17, 0x1, 0), +}; + +struct spdif_srr { + u32 rate; + u32 basemult; + u32 rate_factor; +}; + +static struct spdif_srr srr_values[] = { + {96000, 0x2, 0x0BB80}, + {88200, 0x2, 0x0AC44}, + {64000, 0x2, 0x07D00}, + {48000, 0x1, 0x0BB80}, + {44100, 0x1, 0x0AC44}, + {32000, 0x1, 0x07D00}, +}; + +static inline int get_srr_values(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(srr_values); i++) + if (srr_values[i].rate == rate) + return i; + + return -1; +} + +static int mxs_codec_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; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int i; + u32 srr_value = 0; + u32 basemult; + + i = get_srr_values(params_rate(params)); + if (i < 0) + printk(KERN_WARNING "%s doesn't support rate %d\n", + codec->name, params_rate(params)); + else { + basemult = srr_values[i].basemult; + + srr_value = BF(basemult, SPDIF_SRR_BASEMULT) | + BF(srr_values[i].rate_factor, SPDIF_SRR_RATE); + + if (playback) + __raw_writel(srr_value, REGS_SPDIF_BASE + HW_SPDIF_SRR); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (playback) + __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + break; + default: + printk(KERN_WARNING "%s doesn't support format %d\n", + codec->name, params_format(params)); + } + + return 0; +} + +static void mxs_codec_spdif_enable(struct mxs_codec_priv *mxs_spdif) +{ + /* Move SPDIF codec out of reset */ + __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + + /* Ungate SPDIF clocks */ + __raw_writel(BM_SPDIF_CTRL_CLKGATE, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + + /* 16 bit word length */ + __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); +} + +static void mxs_codec_spdif_disable(struct mxs_codec_priv *mxs_spdif) +{ + /* Gate SPDIF clocks */ + __raw_writel(BM_SPDIF_CTRL_CLKGATE, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); +} + +static void mxs_codec_init(struct snd_soc_codec *codec) +{ + struct mxs_codec_priv *mxs_spdif = codec->private_data; + + /* Soft reset SPDIF block */ + __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); + + while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL) + & BM_SPDIF_CTRL_CLKGATE)) + ; + + mxs_codec_spdif_enable(mxs_spdif); + + snd_soc_add_controls(codec, mxs_snd_controls, + ARRAY_SIZE(mxs_snd_controls)); +} + +static void mxs_codec_exit(struct snd_soc_codec *codec) +{ + struct mxs_codec_priv *mxs_spdif = codec->private_data; + + mxs_codec_spdif_disable(mxs_spdif); +} + +struct snd_soc_dai_ops mxs_spdif_codec_dai_ops = { + .hw_params = mxs_codec_hw_params, +}; + +struct snd_soc_dai mxs_spdif_codec_dai = { + .name = "mxs spdif", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = MXS_SPDIF_RATES, + .formats = MXS_SPDIF_FORMATS, + }, + .ops = &mxs_spdif_codec_dai_ops, +}; +EXPORT_SYMBOL_GPL(mxs_spdif_codec_dai); + +static struct snd_soc_codec *mxs_spdif_codec; + +static int mxs_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + socdev->card->codec = mxs_spdif_codec; + codec = mxs_spdif_codec; + + /* 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; + } + + mxs_codec_init(codec); + + /* Register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + return ret; + } + + return 0; +} + +static int mxs_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + mxs_codec_exit(codec); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int mxs_codec_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; + struct mxs_codec_priv *mxs_spdif; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + mxs_spdif = codec->private_data; + + mxs_codec_spdif_disable(mxs_spdif); + clk_disable(mxs_spdif->clk); + ret = 0; + +out: + return ret; +} + +static int mxs_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct mxs_codec_priv *mxs_spdif; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + mxs_spdif = codec->private_data; + clk_enable(mxs_spdif->clk); + + /* Soft reset SPDIF block */ + __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); + while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL) + & BM_SPDIF_CTRL_CLKGATE)) + ; + + mxs_codec_spdif_enable(mxs_spdif); + + ret = 0; + +out: + return ret; +} +#else +#define mxs_codec_suspend NULL +#define mxs_codec_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_spdif_codec_dev_mxs = { + .probe = mxs_codec_probe, + .remove = mxs_codec_remove, + .suspend = mxs_codec_suspend, + .resume = mxs_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_spdif_codec_dev_mxs); + +static int __init mxs_spdif_probe(struct platform_device *pdev) +{ + struct snd_soc_codec *codec; + struct mxs_codec_priv *mxs_spdif; + int ret = 0; + + dev_info(&pdev->dev, "MXS SPDIF Audio Transmitter\n"); + + mxs_spdif = kzalloc(sizeof(struct mxs_codec_priv), GFP_KERNEL); + if (mxs_spdif == NULL) + return -ENOMEM; + + codec = &mxs_spdif->codec; + codec->name = "mxs spdif"; + codec->owner = THIS_MODULE; + codec->private_data = mxs_spdif; + codec->read = mxs_codec_read; + codec->write = mxs_codec_write; + codec->dai = &mxs_spdif_codec_dai; + codec->num_dai = 1; + + platform_set_drvdata(pdev, mxs_spdif); + mxs_spdif_codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* Turn on audio clock */ + mxs_spdif->clk = clk_get(&pdev->dev, "spdif"); + if (IS_ERR(mxs_spdif->clk)) { + ret = PTR_ERR(mxs_spdif->clk); + dev_err(&pdev->dev, "Clocks initialization failed\n"); + goto clk_err; + } + clk_enable(mxs_spdif->clk); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + ret = snd_soc_register_dai(&mxs_spdif_codec_dai); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto dai_err; + } + + return 0; + +dai_err: + snd_soc_unregister_codec(codec); +card_err: + clk_disable(mxs_spdif->clk); + clk_put(mxs_spdif->clk); +clk_err: + kfree(mxs_spdif); + return ret; +} + +static int __devexit mxs_spdif_remove(struct platform_device *pdev) +{ + struct mxs_codec_priv *mxs_spdif = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = &mxs_spdif->codec; + + snd_soc_unregister_codec(codec); + + clk_disable(mxs_spdif->clk); + clk_put(mxs_spdif->clk); + + kfree(mxs_spdif); + + return 0; +} + +struct platform_driver mxs_spdif_driver = { + .driver = { + .name = "mxs-spdif", + }, + .probe = mxs_spdif_probe, + .remove = __devexit_p(mxs_spdif_remove), +}; + +static int __init mxs_spdif_init(void) +{ + return platform_driver_register(&mxs_spdif_driver); +} + +static void __exit mxs_spdif_exit(void) +{ + return platform_driver_unregister(&mxs_spdif_driver); +} + +module_init(mxs_spdif_init); +module_exit(mxs_spdif_exit); + +MODULE_DESCRIPTION("MXS SPDIF transmitter"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/mxs_spdif.h b/sound/soc/codecs/mxs_spdif.h new file mode 100644 index 000000000000..6bce044d683e --- /dev/null +++ b/sound/soc/codecs/mxs_spdif.h @@ -0,0 +1,172 @@ +/* + * ALSA SoC MXS SPDIF codec driver + * + * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. + * + * Based on stmp3xxx_spdif.h by: + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#ifndef __MXS_SPDIF_CODEC_H +#define __MXS_SPDIF_CODEC_H + +#define SPDIF_CTRL_L 0 +#define SPDIF_CTRL_H 1 +#define SPDIF_STAT_L 2 +#define SPDIF_STAT_H 3 +#define SPDIF_FRAMECTRL_L 4 +#define SPDIF_FRAMECTRL_H 5 +#define SPDIF_SRR_L 6 +#define SPDIF_SRR_H 7 +#define SPDIF_DEBUG_L 8 +#define SPDIF_DEBUG_H 9 +#define SPDIF_DATA_L 10 +#define SPDIF_DATA_H 11 +#define SPDIF_VERSION_L 12 +#define SPDIF_VERSION_H 13 + +#define SPDIF_REGNUM 14 + +#define HW_SPDIF_CTRL (0x00000000) +#define HW_SPDIF_CTRL_SET (0x00000004) +#define HW_SPDIF_CTRL_CLR (0x00000008) +#define HW_SPDIF_CTRL_TOG (0x0000000c) + +#define BM_SPDIF_CTRL_SFTRST 0x80000000 +#define BM_SPDIF_CTRL_CLKGATE 0x40000000 +#define BP_SPDIF_CTRL_RSRVD1 21 +#define BM_SPDIF_CTRL_RSRVD1 0x3FE00000 +#define BF_SPDIF_CTRL_RSRVD1(v) \ + (((v) << 21) & BM_SPDIF_CTRL_RSRVD1) +#define BP_SPDIF_CTRL_DMAWAIT_COUNT 16 +#define BM_SPDIF_CTRL_DMAWAIT_COUNT 0x001F0000 +#define BF_SPDIF_CTRL_DMAWAIT_COUNT(v) \ + (((v) << 16) & BM_SPDIF_CTRL_DMAWAIT_COUNT) +#define BP_SPDIF_CTRL_RSRVD0 6 +#define BM_SPDIF_CTRL_RSRVD0 0x0000FFC0 +#define BF_SPDIF_CTRL_RSRVD0(v) \ + (((v) << 6) & BM_SPDIF_CTRL_RSRVD0) +#define BM_SPDIF_CTRL_WAIT_END_XFER 0x00000020 +#define BM_SPDIF_CTRL_WORD_LENGTH 0x00000010 +#define BM_SPDIF_CTRL_FIFO_UNDERFLOW_IRQ 0x00000008 +#define BM_SPDIF_CTRL_FIFO_OVERFLOW_IRQ 0x00000004 +#define BM_SPDIF_CTRL_FIFO_ERROR_IRQ_EN 0x00000002 +#define BM_SPDIF_CTRL_RUN 0x00000001 + +#define HW_SPDIF_STAT (0x00000010) +#define HW_SPDIF_STAT_SET (0x00000014) +#define HW_SPDIF_STAT_CLR (0x00000018) +#define HW_SPDIF_STAT_TOG (0x0000001c) + +#define BM_SPDIF_STAT_PRESENT 0x80000000 +#define BP_SPDIF_STAT_RSRVD1 1 +#define BM_SPDIF_STAT_RSRVD1 0x7FFFFFFE +#define BF_SPDIF_STAT_RSRVD1(v) \ + (((v) << 1) & BM_SPDIF_STAT_RSRVD1) +#define BM_SPDIF_STAT_END_XFER 0x00000001 + +#define HW_SPDIF_FRAMECTRL (0x00000020) +#define HW_SPDIF_FRAMECTRL_SET (0x00000024) +#define HW_SPDIF_FRAMECTRL_CLR (0x00000028) +#define HW_SPDIF_FRAMECTRL_TOG (0x0000002c) + +#define BP_SPDIF_FRAMECTRL_RSRVD2 18 +#define BM_SPDIF_FRAMECTRL_RSRVD2 0xFFFC0000 +#define BF_SPDIF_FRAMECTRL_RSRVD2(v) \ + (((v) << 18) & BM_SPDIF_FRAMECTRL_RSRVD2) +#define BM_SPDIF_FRAMECTRL_V_CONFIG 0x00020000 +#define BM_SPDIF_FRAMECTRL_AUTO_MUTE 0x00010000 +#define BM_SPDIF_FRAMECTRL_RSRVD1 0x00008000 +#define BM_SPDIF_FRAMECTRL_USER_DATA 0x00004000 +#define BM_SPDIF_FRAMECTRL_V 0x00002000 +#define BM_SPDIF_FRAMECTRL_L 0x00001000 +#define BM_SPDIF_FRAMECTRL_RSRVD0 0x00000800 +#define BP_SPDIF_FRAMECTRL_CC 4 +#define BM_SPDIF_FRAMECTRL_CC 0x000007F0 +#define BF_SPDIF_FRAMECTRL_CC(v) \ + (((v) << 4) & BM_SPDIF_FRAMECTRL_CC) +#define BM_SPDIF_FRAMECTRL_PRE 0x00000008 +#define BM_SPDIF_FRAMECTRL_COPY 0x00000004 +#define BM_SPDIF_FRAMECTRL_AUDIO 0x00000002 +#define BM_SPDIF_FRAMECTRL_PRO 0x00000001 + +#define HW_SPDIF_SRR (0x00000030) +#define HW_SPDIF_SRR_SET (0x00000034) +#define HW_SPDIF_SRR_CLR (0x00000038) +#define HW_SPDIF_SRR_TOG (0x0000003c) + +#define BM_SPDIF_SRR_RSRVD1 0x80000000 +#define BP_SPDIF_SRR_BASEMULT 28 +#define BM_SPDIF_SRR_BASEMULT 0x70000000 +#define BF_SPDIF_SRR_BASEMULT(v) \ + (((v) << 28) & BM_SPDIF_SRR_BASEMULT) +#define BP_SPDIF_SRR_RSRVD0 20 +#define BM_SPDIF_SRR_RSRVD0 0x0FF00000 +#define BF_SPDIF_SRR_RSRVD0(v) \ + (((v) << 20) & BM_SPDIF_SRR_RSRVD0) +#define BP_SPDIF_SRR_RATE 0 +#define BM_SPDIF_SRR_RATE 0x000FFFFF +#define BF_SPDIF_SRR_RATE(v) \ + (((v) << 0) & BM_SPDIF_SRR_RATE) + +#define HW_SPDIF_DEBUG (0x00000040) +#define HW_SPDIF_DEBUG_SET (0x00000044) +#define HW_SPDIF_DEBUG_CLR (0x00000048) +#define HW_SPDIF_DEBUG_TOG (0x0000004c) + +#define BP_SPDIF_DEBUG_RSRVD1 2 +#define BM_SPDIF_DEBUG_RSRVD1 0xFFFFFFFC +#define BF_SPDIF_DEBUG_RSRVD1(v) \ + (((v) << 2) & BM_SPDIF_DEBUG_RSRVD1) +#define BM_SPDIF_DEBUG_DMA_PREQ 0x00000002 +#define BM_SPDIF_DEBUG_FIFO_STATUS 0x00000001 + +#define HW_SPDIF_DATA (0x00000050) +#define HW_SPDIF_DATA_SET (0x00000054) +#define HW_SPDIF_DATA_CLR (0x00000058) +#define HW_SPDIF_DATA_TOG (0x0000005c) + +#define BP_SPDIF_DATA_HIGH 16 +#define BM_SPDIF_DATA_HIGH 0xFFFF0000 +#define BF_SPDIF_DATA_HIGH(v) \ + (((v) << 16) & BM_SPDIF_DATA_HIGH) +#define BP_SPDIF_DATA_LOW 0 +#define BM_SPDIF_DATA_LOW 0x0000FFFF +#define BF_SPDIF_DATA_LOW(v) \ + (((v) << 0) & BM_SPDIF_DATA_LOW) + +#define HW_SPDIF_VERSION (0x00000060) + +#define BP_SPDIF_VERSION_MAJOR 24 +#define BM_SPDIF_VERSION_MAJOR 0xFF000000 +#define BF_SPDIF_VERSION_MAJOR(v) \ + (((v) << 24) & BM_SPDIF_VERSION_MAJOR) +#define BP_SPDIF_VERSION_MINOR 16 +#define BM_SPDIF_VERSION_MINOR 0x00FF0000 +#define BF_SPDIF_VERSION_MINOR(v) \ + (((v) << 16) & BM_SPDIF_VERSION_MINOR) +#define BP_SPDIF_VERSION_STEP 0 +#define BM_SPDIF_VERSION_STEP 0x0000FFFF +#define BF_SPDIF_VERSION_STEP(v) \ + (((v) << 0) & BM_SPDIF_VERSION_STEP) + +#define MXS_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define MXS_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +extern struct snd_soc_dai mxs_spdif_codec_dai; +extern struct snd_soc_codec_device soc_spdif_codec_dev_mxs; + +#endif /* __MXS_SPDIF_CODEC_H */ diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c new file mode 100644 index 000000000000..e1d5ef73f4d1 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.c @@ -0,0 +1,1295 @@ +/* + * 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/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 <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; + 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]; + +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 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}, + }; + + buf0[0] = (reg & 0xff00) >> 8; + buf0[1] = reg & 0xff; + i2c_ret = i2c_transfer(client->adapter, msg, 2); + 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 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_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); + 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 = codec->private_data; + 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 = codec->private_data; + + 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 = codec->private_data; + 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 = codec->private_data; + 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; + + 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 = codec->private_data; + 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 = codec->private_data; + 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) +{ + 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 */ + 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); + + break; + + case SND_SOC_BIAS_OFF: /* Off, without power */ + /* 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); + 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, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(sgtl5000_dai); + +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 = codec->private_data; + u16 reg, ana_pwr, lreg_ctrl, ref_ctrl, lo_ctrl, short_ctrl, sss; + int vag; + int ret = 0; + + socdev->card->codec = sgtl5000_codec; + + /* 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); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "sgtl5000: failed to register card\n"); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; + } + + return 0; +} + +/* 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); + 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; + } + + codec->private_data = 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 = codec->private_data; + + 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"); diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h new file mode 100644 index 000000000000..b1a755e29d57 --- /dev/null +++ b/sound/soc/codecs/sgtl5000.h @@ -0,0 +1,406 @@ +/* + * sgtl5000.h - SGTL5000 audio codec interface + * + * Copyright (C) 2008-2010 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> + +extern struct snd_soc_dai sgtl5000_dai; +extern struct snd_soc_codec_device soc_codec_dev_sgtl5000; + +/* + * 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 diff --git a/sound/soc/codecs/stmp378x_codec.c b/sound/soc/codecs/stmp378x_codec.c new file mode 100644 index 000000000000..b8463f58d18d --- /dev/null +++ b/sound/soc/codecs/stmp378x_codec.c @@ -0,0 +1,1051 @@ +/* + * ALSA codec for Freescale STMP378X ADC/DAC + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.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 <asm/dma.h> + +#include <mach/regs-apbx.h> +#include <mach/regs-audioin.h> +#include <mach/regs-audioout.h> +#include <mach/regs-rtc.h> +#include <mach/platform.h> + +#include "stmp378x_codec.h" + +#define BV_AUDIOIN_ADCVOL_SELECT__MIC 0x00 /* missing define */ + +#define STMP378X_VERSION "0.1" +struct stmp378x_codec_priv { + struct clk *clk; + struct snd_soc_codec codec; +}; + +/* + * ALSA API + */ +static void __iomem *adc_regmap[] = { + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_STAT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACDEBUG, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT0, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_BISTSTAT1, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DATA, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_VERSION, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL, + REGS_AUDIOIN_BASE + HW_AUDIOIN_STAT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCDEBUG, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL, + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL, + REGS_AUDIOIN_BASE + HW_AUDIOIN_DATA, +}; + +static u16 stmp378x_audio_regs[ADC_REGNUM]; + +static u8 dac_volumn_control_word[] = { + 0x37, 0x5e, 0x7e, 0x8e, + 0x9e, 0xae, 0xb6, 0xbe, + 0xc6, 0xce, 0xd6, 0xde, + 0xe6, 0xee, 0xf6, 0xfe, +}; + +/* + * ALSA core supports only 16 bit registers. It means we have to simulate it + * by virtually splitting a 32bit ADC/DAC registers into two halves + * high (bits 31:16) and low (bits 15:0). The routins abow detects which part + * of 32bit register is accessed. + */ +static void stmp378x_codec_write_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < ADC_REGNUM) + cache[reg] = value; +} + +static int stmp378x_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned int reg_val; + unsigned int mask = 0xffff; + + if (reg >= ADC_REGNUM) + return -EIO; + + stmp378x_codec_write_cache(codec, reg, value); + + if (reg & 0x1) { + mask <<= 16; + value <<= 16; + } + + reg_val = __raw_readl(adc_regmap[reg >> 1]); + reg_val = (reg_val & ~mask) | value; + __raw_writel(reg_val, adc_regmap[reg >> 1]); + + return 0; +} + +static unsigned int stmp378x_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int reg_val; + + if (reg >= ADC_REGNUM) + return -1; + + reg_val = __raw_readl(adc_regmap[reg >> 1]); + if (reg & 1) + reg_val >>= 16; + + return reg_val & 0xffff; +} + +static unsigned int stmp378x_codec_read_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= ADC_REGNUM) + return -EINVAL; + return cache[reg]; +} + +static void stmp378x_codec_sync_reg_cache(struct snd_soc_codec *codec) +{ + int reg; + for (reg = 0; reg < ADC_REGNUM; reg += 1) + stmp378x_codec_write_cache(codec, reg, + stmp378x_codec_read(codec, reg)); +} + +static int stmp378x_codec_restore_reg(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int cached_val, hw_val; + + cached_val = stmp378x_codec_read_cache(codec, reg); + hw_val = stmp378x_codec_read(codec, reg); + + if (hw_val != cached_val) + return stmp378x_codec_write(codec, reg, cached_val); + + 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 = 0xf; + return 0; +} + +static int dac_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg, l, r; + int i; + + reg = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + + l = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_LEFT; + r = (reg & BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT) >> + BP_AUDIOOUT_DACVOLUME_VOLUME_RIGHT; + /*Left channel */ + i = 0; + while (i < 16) { + if (l == dac_volumn_control_word[i]) { + ucontrol->value.integer.value[0] = i; + break; + } + i++; + } + if (i == 16) + ucontrol->value.integer.value[0] = i; + /*Right channel */ + i = 0; + while (i < 16) { + if (r == dac_volumn_control_word[i]) { + ucontrol->value.integer.value[1] = i; + break; + } + i++; + } + if (i == 16) + ucontrol->value.integer.value[1] = i; + + return 0; +} + +static int dac_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int reg, l, r; + int i; + + i = ucontrol->value.integer.value[0]; + l = dac_volumn_control_word[i]; + /*Get dac volume for left channel */ + reg = BF(l, AUDIOOUT_DACVOLUME_VOLUME_LEFT); + + i = ucontrol->value.integer.value[1]; + r = dac_volumn_control_word[i]; + /*Get dac volume for right channel */ + reg = reg | BF(r, AUDIOOUT_DACVOLUME_VOLUME_RIGHT); + + /*Clear left/right dac volume */ + __raw_writel(BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT | + BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR); + __raw_writel(reg, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + + return 0; +} + +static const char *stmp378x_codec_adc_input_sel[] = + { "Mic", "Line In 1", "Head Phone", "Line In 2" }; + +static const char *stmp378x_codec_hp_output_sel[] = { "DAC Out", "Line In 1" }; + +static const char *stmp378x_codec_adc_3d_sel[] = + { "Off", "Low", "Medium", "High" }; + +static const struct soc_enum stmp378x_codec_enum[] = { + SOC_ENUM_SINGLE(ADC_ADCVOL_L, 12, 4, stmp378x_codec_adc_input_sel), + SOC_ENUM_SINGLE(ADC_ADCVOL_L, 4, 4, stmp378x_codec_adc_input_sel), + SOC_ENUM_SINGLE(DAC_HPVOL_H, 0, 2, stmp378x_codec_hp_output_sel), + SOC_ENUM_SINGLE(DAC_CTRL_L, 8, 4, stmp378x_codec_adc_3d_sel), +}; + +/* Codec controls */ +static const struct snd_kcontrol_new stmp378x_snd_controls[] = { + /* Playback Volume */ + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC 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_R("DAC Playback Switch", + DAC_VOLUME_H, DAC_VOLUME_L, 8, 0x01, 1), + SOC_DOUBLE("HP Playback Volume", DAC_HPVOL_L, 8, 0, 0x7F, 1), + SOC_SINGLE("HP Playback Switch", DAC_HPVOL_H, 8, 0x1, 1), + SOC_SINGLE("Speaker Playback Switch", DAC_SPEAKERCTRL_H, 8, 0x1, 1), + + /* Capture Volume */ + SOC_DOUBLE_R("ADC Capture Volume", + ADC_VOLUME_H, ADC_VOLUME_L, 0, 0xFF, 0), + SOC_DOUBLE("ADC PGA Capture Volume", ADC_ADCVOL_L, 8, 0, 0x0F, 0), + SOC_SINGLE("ADC PGA Capture Switch", ADC_ADCVOL_H, 8, 0x1, 1), + SOC_SINGLE("Mic PGA Capture Volume", ADC_MICLINE_L, 0, 0x03, 0), + + /* Virtual 3D effect */ + SOC_ENUM("3D effect", stmp378x_codec_enum[3]), +}; + +/* Left ADC Mux */ +static const struct snd_kcontrol_new stmp378x_left_adc_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[0]); + +/* Right ADC Mux */ +static const struct snd_kcontrol_new stmp378x_right_adc_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[1]); + +/* Head Phone Mux */ +static const struct snd_kcontrol_new stmp378x_hp_controls = +SOC_DAPM_ENUM("Route", stmp378x_codec_enum[2]); + +static const struct snd_soc_dapm_widget stmp378x_codec_widgets[] = { + + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", DAC_PWRDN_L, 8, 1), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", DAC_PWRDN_H, 0, 1), + + SND_SOC_DAPM_DAC("DAC", "Playback", DAC_PWRDN_L, 12, 1), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_left_adc_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_right_adc_controls), + SND_SOC_DAPM_MUX("HP Mux", SND_SOC_NOPM, 0, 0, + &stmp378x_hp_controls), + + SND_SOC_DAPM_PGA("HP_AMP", DAC_PWRDN_L, 0, 1, NULL, 0), + + SND_SOC_DAPM_PGA("HP_CAPLESS", DAC_PWRDN_L, 4, 1, NULL, 0), + + SND_SOC_DAPM_PGA("SPK_AMP", DAC_PWRDN_H, 8, 1, NULL, 0), + + SND_SOC_DAPM_INPUT("LINE1L"), + SND_SOC_DAPM_INPUT("LINE1R"), + SND_SOC_DAPM_INPUT("LINE2L"), + SND_SOC_DAPM_INPUT("LINE2R"), + SND_SOC_DAPM_INPUT("MIC"), + + SND_SOC_DAPM_OUTPUT("SPEAKER"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + + /* Left ADC Mux */ + {"Left ADC Mux", "Mic", "MIC"}, + {"Left ADC Mux", "Line In 1", "LINE1L"}, + {"Left ADC Mux", "Line In 2", "LINE2L"}, + {"Left ADC Mux", "Head Phone", "HPL"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Mic", "MIC"}, + {"Right ADC Mux", "Line In 1", "LINE1R"}, + {"Right ADC Mux", "Line In 2", "LINE2R"}, + {"Right ADC Mux", "Head Phone", "HPR"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, + + /* HP Mux */ + {"HP Mux", "DAC Out", "DAC"}, + {"HP Mux", "Line In 1", "LINE1L"}, + {"HP Mux", "Line In 1", "LINE1R"}, + + /* HP output */ + {"HP_CAPLESS", NULL, "HP Mux"}, + {"HP_AMP", NULL, "HP_CAPLESS"}, + {"HPR", NULL, "HP_AMP"}, + {"HPL", NULL, "HP_AMP"}, + + /* Speaker amp */ + {"SPK_AMP", NULL, "DAC"}, + {"SPEAKER", NULL, "SPK_AMP"}, +}; + +static int stmp378x_codec_add_widgets(struct snd_soc_codec *codec) +{ + int ret = 0; + + snd_soc_dapm_new_controls(codec, stmp378x_codec_widgets, + ARRAY_SIZE(stmp378x_codec_widgets)); + + if (ret) { + dev_err(codec->dev, "dapm control register failed\n"); + return ret; + } + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + if (ret) { + dev_err(codec->dev, "DAPM route register failed\n"); + return ret; + } + + return snd_soc_dapm_new_widgets(codec); +} + +struct dac_srr { + u32 rate; + u32 basemult; + u32 src_hold; + u32 src_int; + u32 src_frac; +}; + +static struct dac_srr srr_values[] = { + {192000, 0x4, 0x0, 0x0F, 0x13FF}, + {176400, 0x4, 0x0, 0x11, 0x0037}, + {128000, 0x4, 0x0, 0x17, 0x0E00}, + {96000, 0x2, 0x0, 0x0F, 0x13FF}, + {88200, 0x2, 0x0, 0x11, 0x0037}, + {64000, 0x2, 0x0, 0x17, 0x0E00}, + {48000, 0x1, 0x0, 0x0F, 0x13FF}, + {44100, 0x1, 0x0, 0x11, 0x0037}, + {32000, 0x1, 0x0, 0x17, 0x0E00}, + {24000, 0x1, 0x1, 0x0F, 0x13FF}, + {22050, 0x1, 0x1, 0x11, 0x0037}, + {16000, 0x1, 0x1, 0x17, 0x0E00}, + {12000, 0x1, 0x3, 0x0F, 0x13FF}, + {11025, 0x1, 0x3, 0x11, 0x0037}, + {8000, 0x1, 0x3, 0x17, 0x0E00} +}; + +static inline int get_srr_values(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(srr_values); i++) + if (srr_values[i].rate == rate) + return i; + + return -1; +} + +static int stmp378x_codec_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; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int i; + u32 srr_value = 0; + u32 src_hold = 0; + + i = get_srr_values(params_rate(params)); + if (i < 0) + dev_warn(socdev->dev, "%s doesn't support rate %d\n", + codec->name, params_rate(params)); + else { + src_hold = srr_values[i].src_hold; + + srr_value = + BF(srr_values[i].basemult, AUDIOOUT_DACSRR_BASEMULT) | + BF(srr_values[i].src_int, AUDIOOUT_DACSRR_SRC_INT) | + BF(srr_values[i].src_frac, AUDIOOUT_DACSRR_SRC_FRAC) | + BF(src_hold, AUDIOOUT_DACSRR_SRC_HOLD); + + if (playback) + __raw_writel(srr_value, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACSRR); + else + __raw_writel(srr_value, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCSRR); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (playback) + __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + else + __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + + break; + + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + else + __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + + break; + + default: + dev_warn(socdev->dev, "%s doesn't support format %d\n", + codec->name, params_format(params)); + + } + + return 0; +} + +static int stmp378x_codec_dig_mute(struct snd_soc_dai *dai, int mute) +{ + u32 dac_mask = BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT; + + if (mute) { + __raw_writel(dac_mask, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); + } else { + __raw_writel(dac_mask, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_CLR); + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_CLR); + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_CLR); + } + return 0; +} + +/* + * Codec initialization + */ +#define VAG_BASE_VALUE ((1400/2 - 625)/25) +static void stmp378x_codec_dac_set_vag(void) +{ + u32 refctrl_val = __raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + + refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VAG_VAL); + refctrl_val &= ~(BM_AUDIOOUT_REFCTRL_VBG_ADJ); + refctrl_val |= BF(VAG_BASE_VALUE, AUDIOOUT_REFCTRL_VAG_VAL) | + BM_AUDIOOUT_REFCTRL_ADJ_VAG | + BF(0xF, AUDIOOUT_REFCTRL_ADC_REFVAL) | + BM_AUDIOOUT_REFCTRL_ADJ_ADC | + BF(0x3, AUDIOOUT_REFCTRL_VBG_ADJ) | BM_AUDIOOUT_REFCTRL_RAISE_REF; + + __raw_writel(refctrl_val, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); +} + +static void +stmp378x_codec_dac_power_on(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Ungate DAC clocks */ + __raw_writel(BM_AUDIOOUT_CTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_CLR); + + /* 16 bit word length */ + __raw_writel(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + + /* Update DAC volume over zero crossings */ + __raw_writel(BM_AUDIOOUT_DACVOLUME_EN_ZCD, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + /* Mute DAC */ + __raw_writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + + /* Update HP volume over zero crossings */ + __raw_writel(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + + /* Prepare powering up HP output */ + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + __raw_writel(BF(0x2, RTC_PERSISTENT0_SPARE_ANALOG), + REGS_RTC_BASE + HW_RTC_PERSISTENT0_SET); + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + /* Mute HP output */ + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + + /* Mute speaker amp */ + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); +} + +static void +stmp378x_codec_dac_power_down(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Disable class AB */ + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_CLR); + + /* Set hold to ground */ + __raw_writel(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL_SET); + + /* Mute HP output */ + __raw_writel(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL_SET); + /* Power down HP output */ + __raw_writel(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + + /* Mute speaker amp */ + __raw_writel(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL_SET); + /* Power down speaker amp */ + __raw_writel(BM_AUDIOOUT_PWRDN_SPEAKER, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + + /* Mute DAC */ + __raw_writel(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME_SET); + /* Power down DAC */ + __raw_writel(BM_AUDIOOUT_PWRDN_DAC, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + + /* Gate DAC clocks */ + __raw_writel(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL_SET); + __raw_writel(BM_AUDIOOUT_CTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); +} + +static void +stmp378x_codec_adc_power_on(struct stmp378x_codec_priv *stmp378x_adc) +{ + u32 reg; + + /* Ungate ADC clocks */ + __raw_writel(BM_AUDIOIN_CTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + __raw_writel(BM_AUDIOIN_ANACLKCTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL_CLR); + + /* 16 bit word length */ + __raw_writel(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + + /* Unmute ADC channels */ + __raw_writel(BM_AUDIOIN_ADCVOL_MUTE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + + /* + * The MUTE_LEFT and MUTE_RIGHT fields need to be cleared. + * They aren't presented in the datasheet, so this is hardcode. + */ + __raw_writel(0x01000100, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME_CLR); + + /* Set the Input channel gain 3dB */ + __raw_writel(BM_AUDIOIN_ADCVOL_GAIN_LEFT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + __raw_writel(BM_AUDIOIN_ADCVOL_GAIN_RIGHT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + __raw_writel(BF(2, AUDIOIN_ADCVOL_GAIN_LEFT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + __raw_writel(BF(2, AUDIOIN_ADCVOL_GAIN_RIGHT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + + /* Select default input - Microphone */ + __raw_writel(BM_AUDIOIN_ADCVOL_SELECT_LEFT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + __raw_writel(BM_AUDIOIN_ADCVOL_SELECT_RIGHT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_CLR); + __raw_writel(BF + (BV_AUDIOIN_ADCVOL_SELECT__MIC, + AUDIOIN_ADCVOL_SELECT_LEFT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + __raw_writel(BF + (BV_AUDIOIN_ADCVOL_SELECT__MIC, + AUDIOIN_ADCVOL_SELECT_RIGHT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + + /* Supply bias voltage to microphone */ + __raw_writel(BF(2, AUDIOIN_MICLINE_MIC_RESISTOR), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET); + __raw_writel(BM_AUDIOIN_MICLINE_MIC_SELECT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET); + + /* Set max ADC volume */ + reg = __raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_LEFT; + reg &= ~BM_AUDIOIN_ADCVOLUME_VOLUME_RIGHT; + reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_LEFT); + reg |= BF(ADC_VOLUME_MAX, AUDIOIN_ADCVOLUME_VOLUME_RIGHT); + __raw_writel(reg, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); +} + +static void +stmp378x_codec_adc_power_down(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Mute ADC channels */ + __raw_writel(BM_AUDIOIN_ADCVOL_MUTE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL_SET); + + /* Power Down ADC */ + __raw_writel(BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN_SET); + + /* Gate ADC clocks */ + __raw_writel(BM_AUDIOIN_CTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + __raw_writel(BM_AUDIOIN_ANACLKCTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL_SET); + + /* Disable bias voltage to microphone */ + __raw_writel(BF(0, AUDIOIN_MICLINE_MIC_RESISTOR), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE_SET); +} + +static void stmp378x_codec_dac_enable(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Move DAC codec out of reset */ + __raw_writel(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_CLR); + + /* Reduce analog power */ + __raw_writel(BM_AUDIOOUT_TEST_HP_I1_ADJ, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_CLR); + __raw_writel(BF(0x1, AUDIOOUT_TEST_HP_I1_ADJ), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST_SET); + __raw_writel(BM_AUDIOOUT_REFCTRL_LOW_PWR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_SET); + __raw_writel(BM_AUDIOOUT_REFCTRL_XTAL_BGR_BIAS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_SET); + __raw_writel(BM_AUDIOOUT_REFCTRL_BIAS_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_CLR); + __raw_writel(BF(0x1, AUDIOOUT_REFCTRL_BIAS_CTRL), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL_CLR); + + /* Set Vag value */ + stmp378x_codec_dac_set_vag(); + + /* Power on DAC codec */ + stmp378x_codec_dac_power_on(stmp378x_adc); +} + +static void stmp378x_codec_dac_disable(struct stmp378x_codec_priv *stmp378x_adc) +{ + stmp378x_codec_dac_power_down(stmp378x_adc); +} + +static void stmp378x_codec_adc_enable(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Move ADC codec out of reset */ + __raw_writel(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_CLR); + + /* Power on ADC codec */ + stmp378x_codec_adc_power_on(stmp378x_adc); +} + +static void stmp378x_codec_adc_disable(struct stmp378x_codec_priv *stmp378x_adc) +{ + stmp378x_codec_adc_power_down(stmp378x_adc); +} + +static void stmp378x_codec_startup(struct snd_soc_codec *codec) +{ + struct stmp378x_codec_priv *stmp378x_adc = codec->private_data; + + /* Soft reset DAC block */ + __raw_writel(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + while (!(__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) & + BM_AUDIOOUT_CTRL_CLKGATE)); + + /* Soft reset ADC block */ + __raw_writel(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + while (!(__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL) & + BM_AUDIOIN_CTRL_CLKGATE)) ; + + stmp378x_codec_dac_enable(stmp378x_adc); + stmp378x_codec_adc_enable(stmp378x_adc); + + /*Sync regs and cache */ + stmp378x_codec_sync_reg_cache(codec); + + snd_soc_add_controls(codec, stmp378x_snd_controls, + ARRAY_SIZE(stmp378x_snd_controls)); + + stmp378x_codec_add_widgets(codec); +} + +static void stmp378x_codec_stop(struct snd_soc_codec *codec) +{ + struct stmp378x_codec_priv *stmp378x_adc = codec->private_data; + stmp378x_codec_dac_disable(stmp378x_adc); + stmp378x_codec_adc_disable(stmp378x_adc); +} + +#define STMP378X_ADC_RATES SNDRV_PCM_RATE_8000_192000 +#define STMP378X_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops stmp378x_dai_ops = { + .hw_params = stmp378x_codec_hw_params, + .digital_mute = stmp378x_codec_dig_mute, +}; + +struct snd_soc_dai stmp378x_codec_dai = { + .name = "stmp378x adc/dac", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STMP378X_ADC_RATES, + .formats = STMP378X_ADC_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = STMP378X_ADC_RATES, + .formats = STMP378X_ADC_FORMATS, + }, + .ops = &stmp378x_dai_ops, +}; + +EXPORT_SYMBOL_GPL(stmp378x_codec_dai); + +static struct snd_soc_codec *stmp378x_codec; + +static int stmp378x_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + socdev->card->codec = stmp378x_codec; + codec = stmp378x_codec; + + /* 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; + } + + stmp378x_codec_startup(codec); + + /* Register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + return ret; + } + + return 0; +} + +static int stmp378x_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + stmp378x_codec_stop(codec); + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int stmp378x_codec_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; + struct stmp378x_codec_priv *stmp378x_adc; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp378x_adc = codec->private_data; + + stmp378x_codec_dac_disable(stmp378x_adc); + stmp378x_codec_adc_disable(stmp378x_adc); + clk_disable(stmp378x_adc->clk); + ret = 0; + +out: + return ret; +} + +static int stmp378x_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct stmp378x_codec_priv *stmp378x_adc; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp378x_adc = codec->private_data; + clk_enable(stmp378x_adc->clk); + + /* Soft reset DAC block */ + __raw_writel(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL_SET); + while (! + (__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) & + BM_AUDIOOUT_CTRL_CLKGATE)) ; + + /* Soft reset ADC block */ + __raw_writel(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL_SET); + while (! + (__raw_readl(REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL) & + BM_AUDIOIN_CTRL_CLKGATE)) ; + + stmp378x_codec_dac_enable(stmp378x_adc); + stmp378x_codec_adc_enable(stmp378x_adc); + + /*restore registers relevant to amixer controls */ + stmp378x_codec_restore_reg(codec, DAC_CTRL_L); + stmp378x_codec_restore_reg(codec, DAC_VOLUME_L); + stmp378x_codec_restore_reg(codec, DAC_VOLUME_H); + stmp378x_codec_restore_reg(codec, DAC_HPVOL_L); + stmp378x_codec_restore_reg(codec, DAC_HPVOL_H); + stmp378x_codec_restore_reg(codec, DAC_SPEAKERCTRL_H); + stmp378x_codec_restore_reg(codec, ADC_VOLUME_L); + stmp378x_codec_restore_reg(codec, ADC_VOLUME_H); + stmp378x_codec_restore_reg(codec, ADC_ADCVOL_L); + stmp378x_codec_restore_reg(codec, ADC_ADCVOL_H); + stmp378x_codec_restore_reg(codec, ADC_MICLINE_L); + + ret = 0; + +out: + return ret; +} +#else +#define stmp378x_codec_suspend NULL +#define stmp378x_codec_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_codec_dev_stmp378x = { + .probe = stmp378x_codec_probe, + .remove = stmp378x_codec_remove, + .suspend = stmp378x_codec_suspend, + .resume = stmp378x_codec_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_stmp378x); + +/* codec register, unregister function */ +static int __init stmp378x_audio_probe(struct platform_device *pdev) +{ + struct stmp378x_codec_priv *stmp378x_adc; + struct snd_soc_codec *codec; + int ret = 0; + + dev_info(&pdev->dev, + "STMP378X ADC/DAC Audio Codec %s\n", STMP378X_VERSION); + + stmp378x_adc = kzalloc(sizeof(struct stmp378x_codec_priv), GFP_KERNEL); + if (stmp378x_adc == NULL) + return -ENOMEM; + + codec = &stmp378x_adc->codec; + codec->dev = &pdev->dev; + codec->name = "stmp378x adc/dac"; + codec->owner = THIS_MODULE; + codec->private_data = stmp378x_adc; + codec->read = stmp378x_codec_read; + codec->write = stmp378x_codec_write; + codec->dai = &stmp378x_codec_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(stmp378x_audio_regs) >> 1; + codec->reg_cache_step = 1; + codec->reg_cache = (void *)&stmp378x_audio_regs; + + platform_set_drvdata(pdev, stmp378x_adc); + + stmp378x_codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* Turn on audio clock */ + stmp378x_adc->clk = clk_get(&pdev->dev, "audio"); + if (IS_ERR(stmp378x_adc->clk)) { + ret = PTR_ERR(stmp378x_adc->clk); + dev_err(&pdev->dev, "%s: Clocks initialization failed\n", __func__); + goto clk_err; + } + clk_enable(stmp378x_adc->clk); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + ret = snd_soc_register_dai(&stmp378x_codec_dai); + if (ret) { + dev_err(&pdev->dev, "failed to register codec dai\n"); + goto dai_err; + } + + return 0; + +dai_err: + snd_soc_unregister_codec(codec); +card_err: + clk_disable(stmp378x_adc->clk); + clk_put(stmp378x_adc->clk); +clk_err: + kfree(stmp378x_adc); + return ret; +} + +static int __devexit stmp378x_audio_remove(struct platform_device *pdev) +{ + struct stmp378x_codec_priv *stmp378x_adc = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = &stmp378x_adc->codec; + + snd_soc_unregister_codec(codec); + + clk_disable(stmp378x_adc->clk); + clk_put(stmp378x_adc->clk); + + kfree(stmp378x_adc); + + return 0; +} + +struct platform_driver stmp378x_audio_driver = { + .driver = { + .name = "stmp378x-audio", + }, + .probe = stmp378x_audio_probe, + .remove = __devexit_p(stmp378x_audio_remove), +}; + +static int __init stmp378x_codec_init(void) +{ + return platform_driver_register(&stmp378x_audio_driver); +} + +static void __exit stmp378x_codec_exit(void) +{ + return platform_driver_unregister(&stmp378x_audio_driver); +} + +module_init(stmp378x_codec_init); +module_exit(stmp378x_codec_exit); + +MODULE_DESCRIPTION("STMP378X ADC/DAC codec"); +MODULE_AUTHOR("Vladislav Buzov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stmp378x_codec.h b/sound/soc/codecs/stmp378x_codec.h new file mode 100644 index 000000000000..80fce7273126 --- /dev/null +++ b/sound/soc/codecs/stmp378x_codec.h @@ -0,0 +1,87 @@ +/* + * ALSA codec for Freescale STMP378X + * + * Author: Vladislav Buzov <vbuzov@embeddedalley.com> + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#ifndef __STMP378X_CODEC_H +#define __STMP378X_CODEC_H + +#define DAC_CTRL_L 0 +#define DAC_CTRL_H 1 +#define DAC_STAT_L 2 +#define DAC_STAT_H 3 +#define DAC_SRR_L 4 +#define DAC_VOLUME_L 6 +#define DAC_VOLUME_H 7 +#define DAC_DEBUG_L 8 +#define DAC_DEBUG_H 9 +#define DAC_HPVOL_L 10 +#define DAC_HPVOL_H 11 +#define DAC_PWRDN_L 12 +#define DAC_PWRDN_H 13 +#define DAC_REFCTRL_L 14 +#define DAC_REFCTRL_H 15 +#define DAC_ANACTRL_L 16 +#define DAC_ANACTRL_H 17 +#define DAC_TEST_L 18 +#define DAC_TEST_H 19 +#define DAC_BISTCTRL_L 20 +#define DAC_BISTCTRL_H 21 +#define DAC_BISTSTAT0_L 22 +#define DAC_BISTSTAT0_H 23 +#define DAC_BISTSTAT1_L 24 +#define DAC_BISTSTAT1_H 25 +#define DAC_ANACLKCTRL_L 26 +#define DAC_ANACLKCTRL_H 27 +#define DAC_DATA_L 28 +#define DAC_DATA_H 29 +#define DAC_SPEAKERCTRL_L 30 +#define DAC_SPEAKERCTRL_H 31 +#define DAC_VERSION_L 32 +#define DAC_VERSION_H 33 +#define ADC_CTRL_L 34 +#define ADC_CTRL_H 35 +#define ADC_STAT_L 36 +#define ADC_STAT_H 37 +#define ADC_SRR_L 38 +#define ADC_SRR_H 39 +#define ADC_VOLUME_L 40 +#define ADC_VOLUME_H 41 +#define ADC_DEBUG_L 42 +#define ADC_DEBUG_H 43 +#define ADC_ADCVOL_L 44 +#define ADC_ADCVOL_H 45 +#define ADC_MICLINE_L 46 +#define ADC_MICLINE_H 47 +#define ADC_ANACLKCTRL_L 48 +#define ADC_ANACLKCTRL_H 49 +#define ADC_DATA_L 50 +#define ADC_DATA_H 51 + +#define ADC_REGNUM 52 + +#define DAC_VOLUME_MIN 0x37 +#define DAC_VOLUME_MAX 0xFE +#define ADC_VOLUME_MIN 0x37 +#define ADC_VOLUME_MAX 0xFE +#define HP_VOLUME_MAX 0x0 +#define HP_VOLUME_MIN 0x7F +#define LO_VOLUME_MAX 0x0 +#define LO_VOLUME_MIN 0x1F + +extern struct snd_soc_dai stmp378x_codec_dai; +extern struct snd_soc_codec_device soc_codec_dev_stmp378x; + +#endif /* __STMP378X_CODEC_H */ diff --git a/sound/soc/codecs/stmp3xxx_spdif.c b/sound/soc/codecs/stmp3xxx_spdif.c new file mode 100644 index 000000000000..afbcfa6c9f8c --- /dev/null +++ b/sound/soc/codecs/stmp3xxx_spdif.c @@ -0,0 +1,455 @@ +/* + * ALSA SoC STMP3xxx SPDIF transmitter driver + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.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 <asm/dma.h> + +#include <mach/regs-spdif.h> +#include <mach/platform.h> + +#include "stmp3xxx_spdif.h" + +#define STMP3XXX_VERSION "0.1" +struct stmp3xxx_codec_priv { + struct clk *clk; + struct snd_soc_codec codec; +}; + +/* + * ALSA API + */ +static void __iomem *spdif_regmap[] = { + REGS_SPDIF_BASE + HW_SPDIF_CTRL, + REGS_SPDIF_BASE + HW_SPDIF_STAT, + REGS_SPDIF_BASE + HW_SPDIF_FRAMECTRL, + REGS_SPDIF_BASE + HW_SPDIF_SRR, + REGS_SPDIF_BASE + HW_SPDIF_DEBUG, + REGS_SPDIF_BASE + HW_SPDIF_DATA, + REGS_SPDIF_BASE + HW_SPDIF_VERSION, +}; + +/* + * ALSA core supports only 16 bit registers. It means we have to simulate it + * by virtually splitting a 32bit SPDIF registers into two halves + * high (bits 31:16) and low (bits 15:0). The routins abow detects which part + * of 32bit register is accessed. + */ +static int stmp3xxx_codec_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + unsigned int reg_val; + unsigned int mask = 0xffff; + + if (reg >= SPDIF_REGNUM) + return -EIO; + + if (reg & 0x1) { + mask <<= 16; + value <<= 16; + } + + reg_val = __raw_readl(spdif_regmap[reg >> 1]); + reg_val = (reg_val & ~mask) | value; + __raw_writel(reg_val, spdif_regmap[reg >> 1]); + + return 0; +} + +static unsigned int stmp3xxx_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int reg_val; + + if (reg >= SPDIF_REGNUM) + return -1; + + reg_val = __raw_readl(spdif_regmap[reg >> 1]); + if (reg & 1) + reg_val >>= 16; + + return reg_val & 0xffff; +} + +/* Codec controls */ +static const struct snd_kcontrol_new stmp3xxx_snd_controls[] = { + SOC_SINGLE("PRO", SPDIF_FRAMECTRL_L, 0, 0x1, 0), + SOC_SINGLE("AUDIO", SPDIF_FRAMECTRL_L, 1, 0x1, 0), + SOC_SINGLE("COPY", SPDIF_FRAMECTRL_L, 2, 0x1, 0), + SOC_SINGLE("PRE", SPDIF_FRAMECTRL_L, 3, 0x1, 0), + SOC_SINGLE("CC", SPDIF_FRAMECTRL_L, 4, 0x7F, 0), + SOC_SINGLE("L", SPDIF_FRAMECTRL_L, 12, 0x1, 0), + SOC_SINGLE("V", SPDIF_FRAMECTRL_L, 13, 0x1, 0), + SOC_SINGLE("USER DATA", SPDIF_FRAMECTRL_L, 14, 0x1, 0), + SOC_SINGLE("AUTO MUTE", SPDIF_FRAMECTRL_H, 16, 0x1, 0), + SOC_SINGLE("V CONFIG", SPDIF_FRAMECTRL_H, 17, 0x1, 0), +}; + +struct spdif_srr { + u32 rate; + u32 basemult; + u32 rate_factor; +}; + +static struct spdif_srr srr_values[] = { + {96000, 0x2, 0x0BB80}, + {88200, 0x2, 0x0AC44}, + {64000, 0x2, 0x07D00}, + {48000, 0x1, 0x0BB80}, + {44100, 0x1, 0x0AC44}, + {32000, 0x1, 0x07D00}, +}; + +static inline int get_srr_values(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(srr_values); i++) + if (srr_values[i].rate == rate) + return i; + + return -1; +} + +static int stmp3xxx_codec_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; + int playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0; + int i; + u32 srr_value = 0; + u32 basemult; + + i = get_srr_values(params_rate(params)); + if (i < 0) + printk(KERN_WARNING "%s doesn't support rate %d\n", + codec->name, params_rate(params)); + else { + basemult = srr_values[i].basemult; + + srr_value = BF(basemult, SPDIF_SRR_BASEMULT) | + BF(srr_values[i].rate_factor, SPDIF_SRR_RATE); + + if (playback) + __raw_writel(srr_value, REGS_SPDIF_BASE + HW_SPDIF_SRR); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + if (playback) + __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); + break; + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + break; + default: + printk(KERN_WARNING "%s doesn't support format %d\n", + codec->name, params_format(params)); + } + + return 0; +} + +static void +stmp3xxx_codec_spdif_enable(struct stmp3xxx_codec_priv *stmp3xxx_spdif) +{ + /* Move SPDIF codec out of reset */ + __raw_writel(BM_SPDIF_CTRL_SFTRST, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + + /* Ungate SPDIF clocks */ + __raw_writel(BM_SPDIF_CTRL_CLKGATE, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_CLR); + + /* 16 bit word length */ + __raw_writel(BM_SPDIF_CTRL_WORD_LENGTH, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); +} + +static void +stmp3xxx_codec_spdif_disable(struct stmp3xxx_codec_priv *stmp3xxx_spdif) +{ + /* Gate SPDIF clocks */ + __raw_writel(BM_SPDIF_CTRL_CLKGATE, + REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); +} + +static void stmp3xxx_codec_init(struct snd_soc_codec *codec) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data; + + /* Soft reset SPDIF block */ + __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); + while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL) & BM_SPDIF_CTRL_CLKGATE)); + + stmp3xxx_codec_spdif_enable(stmp3xxx_spdif); + + snd_soc_add_controls(codec, stmp3xxx_snd_controls, + ARRAY_SIZE(stmp3xxx_snd_controls)); +} + +static void stmp3xxx_codec_exit(struct snd_soc_codec *codec) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data; + + stmp3xxx_codec_spdif_disable(stmp3xxx_spdif); +} + +#define STMP3XXX_SPDIF_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define STMP3XXX_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai_ops stmp3xxx_spdif_codec_dai_ops = { + .hw_params = stmp3xxx_codec_hw_params, +}; + +struct snd_soc_dai stmp3xxx_spdif_codec_dai = { + .name = "stmp3xxx spdif", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STMP3XXX_SPDIF_RATES, + .formats = STMP3XXX_SPDIF_FORMATS, + }, + .ops = &stmp3xxx_spdif_codec_dai_ops, +}; +EXPORT_SYMBOL_GPL(stmp3xxx_spdif_codec_dai); + +static struct snd_soc_codec *stmp3xxx_spdif_codec; + +static int stmp3xxx_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + socdev->card->codec = stmp3xxx_spdif_codec; + codec = stmp3xxx_spdif_codec; + + /* 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; + } + + stmp3xxx_codec_init(codec); + + /* Register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + return ret; + } + + return 0; +} + +static int stmp3xxx_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + stmp3xxx_codec_exit(codec); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int stmp3xxx_codec_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; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp3xxx_spdif = codec->private_data; + + stmp3xxx_codec_spdif_disable(stmp3xxx_spdif); + clk_disable(stmp3xxx_spdif->clk); + ret = 0; + +out: + return ret; +} + +static int stmp3xxx_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = -EINVAL; + + if (codec == NULL) + goto out; + + stmp3xxx_spdif = codec->private_data; + clk_enable(stmp3xxx_spdif->clk); + + /* Soft reset SPDIF block */ + __raw_writel(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL_SET); + while (!(__raw_readl(REGS_SPDIF_BASE + HW_SPDIF_CTRL) & BM_SPDIF_CTRL_CLKGATE)); + + stmp3xxx_codec_spdif_enable(stmp3xxx_spdif); + + ret = 0; + +out: + return ret; +} +#else +#define stmp3xxx_codec_suspend NULL +#define stmp3xxx_codec_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx = { + .probe = stmp3xxx_codec_probe, + .remove = stmp3xxx_codec_remove, + .suspend = stmp3xxx_codec_suspend, + .resume = stmp3xxx_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_spdif_codec_dev_stmp3xxx); + +static int __init stmp3xxx_spdif_probe(struct platform_device *pdev) +{ + struct snd_soc_codec *codec; + struct stmp3xxx_codec_priv *stmp3xxx_spdif; + int ret = 0; + + dev_info(&pdev->dev, + "STMP3XXX SPDIF Audio Transmitter %s\n", STMP3XXX_VERSION); + + stmp3xxx_spdif = + kzalloc(sizeof(struct stmp3xxx_codec_priv), GFP_KERNEL); + if (stmp3xxx_spdif == NULL) + return -ENOMEM; + + codec = &stmp3xxx_spdif->codec; + codec->name = "stmp3xxx spdif"; + codec->owner = THIS_MODULE; + codec->private_data = stmp3xxx_spdif; + codec->read = stmp3xxx_codec_read; + codec->write = stmp3xxx_codec_write; + codec->dai = &stmp3xxx_spdif_codec_dai; + codec->num_dai = 1; + + platform_set_drvdata(pdev, stmp3xxx_spdif); + stmp3xxx_spdif_codec = codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* Turn on audio clock */ + stmp3xxx_spdif->clk = clk_get(&pdev->dev, "spdif"); + if (IS_ERR(stmp3xxx_spdif->clk)) { + ret = PTR_ERR(stmp3xxx_spdif->clk); + dev_err(&pdev->dev, "Clocks initialization failed\n"); + goto clk_err; + } + clk_enable(stmp3xxx_spdif->clk); + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + ret = snd_soc_register_dai(&stmp3xxx_spdif_codec_dai); + if (ret) { + dev_err(&pdev->dev, "failed to register card\n"); + goto dai_err; + } + + return 0; + +dai_err: + snd_soc_unregister_codec(codec); +card_err: + clk_disable(stmp3xxx_spdif->clk); + clk_put(stmp3xxx_spdif->clk); +clk_err: + kfree(stmp3xxx_spdif); + return ret; +} +static int __devexit stmp3xxx_spdif_remove(struct platform_device *pdev) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = &stmp3xxx_spdif->codec; + + snd_soc_unregister_codec(codec); + + clk_disable(stmp3xxx_spdif->clk); + clk_put(stmp3xxx_spdif->clk); + + kfree(stmp3xxx_spdif); + + return 0; +} + +struct platform_driver stmp3xxx_spdif_driver = { + .driver = { + .name = "stmp3xxx-spdif", + }, + .probe = stmp3xxx_spdif_probe, + .remove = __devexit_p(stmp3xxx_spdif_remove), +}; + +static int __init stmp3xxx_spdif_init(void) +{ + return platform_driver_register(&stmp3xxx_spdif_driver); +} + +static void __exit stmp3xxx_spdif_exit(void) +{ + return platform_driver_unregister(&stmp3xxx_spdif_driver); +} + +module_init(stmp3xxx_spdif_init); +module_exit(stmp3xxx_spdif_exit); + +MODULE_DESCRIPTION("STMP3XXX SPDIF transmitter"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stmp3xxx_spdif.h b/sound/soc/codecs/stmp3xxx_spdif.h new file mode 100644 index 000000000000..0ae20a7e8cc6 --- /dev/null +++ b/sound/soc/codecs/stmp3xxx_spdif.h @@ -0,0 +1,37 @@ +/* + * ALSA SoC STMP378x SPDIF codec driver + * + * Vladimir Barinov <vbarinov@embeddedalley.com> + * + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ +#ifndef __STMP3XXX_SPDIF_CODEC_H +#define __STMP3XXX_SPDIF_CODEC_H + +#define SPDIF_CTRL_L 0 +#define SPDIF_CTRL_H 1 +#define SPDIF_STAT_L 2 +#define SPDIF_STAT_H 3 +#define SPDIF_FRAMECTRL_L 4 +#define SPDIF_FRAMECTRL_H 5 +#define SPDIF_SRR_L 6 +#define SPDIF_SRR_H 7 +#define SPDIF_DEBUG_L 8 +#define SPDIF_DEBUG_H 9 +#define SPDIF_DATA_L 10 +#define SPDIF_DATA_H 11 +#define SPDIF_VERSION_L 12 +#define SPDIF_VERSION_H 13 + +#define SPDIF_REGNUM 14 + +extern struct snd_soc_dai stmp3xxx_spdif_codec_dai; +extern struct snd_soc_codec_device soc_spdif_codec_dev_stmp3xxx; + +#endif /* __STMP3XXX_SPDIF_CODEC_H */ diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index e7348d341b76..fa91d844e646 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -406,7 +406,6 @@ static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" }; static const char *wm8350_dacmutem[] = { "Normal", "Soft" }; static const char *wm8350_dacmutes[] = { "Fast", "Slow" }; -static const char *wm8350_dacfilter[] = { "Normal", "Sloping" }; static const char *wm8350_adcfilter[] = { "None", "High Pass" }; static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" }; static const char *wm8350_lr[] = { "Left", "Right" }; @@ -416,7 +415,6 @@ static const struct soc_enum wm8350_enum[] = { SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol), SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem), SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes), - SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 12, 2, wm8350_dacfilter), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol), @@ -444,10 +442,9 @@ static const struct snd_kcontrol_new wm8350_snd_controls[] = { 0, 255, 0, dac_pcm_tlv), SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]), SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]), - SOC_ENUM("Playback PCM Filter", wm8350_enum[4]), - SOC_ENUM("Capture PCM Filter", wm8350_enum[5]), - SOC_ENUM("Capture PCM HP Filter", wm8350_enum[6]), - SOC_ENUM("Capture ADC Inversion", wm8350_enum[7]), + SOC_ENUM("Capture PCM Filter", wm8350_enum[4]), + SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]), + SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]), SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume", WM8350_ADC_DIGITAL_VOLUME_L, WM8350_ADC_DIGITAL_VOLUME_R, @@ -580,7 +577,7 @@ static const struct snd_kcontrol_new wm8350_left_capt_mixer_controls[] = { SOC_DAPM_SINGLE_TLV("L3 Capture Volume", WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv), SOC_DAPM_SINGLE("PGA Capture Switch", - WM8350_LEFT_INPUT_VOLUME, 14, 1, 0), + WM8350_LEFT_INPUT_VOLUME, 14, 1, 1), }; /* Right Input Mixer */ @@ -590,7 +587,7 @@ static const struct snd_kcontrol_new wm8350_right_capt_mixer_controls[] = { SOC_DAPM_SINGLE_TLV("L3 Capture Volume", WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv), SOC_DAPM_SINGLE("PGA Capture Switch", - WM8350_RIGHT_INPUT_VOLUME, 14, 1, 0), + WM8350_RIGHT_INPUT_VOLUME, 14, 1, 1), }; /* Left Mic Mixer */ @@ -613,7 +610,7 @@ SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1); /* Out4 Capture Mux */ static const struct snd_kcontrol_new wm8350_out4_capture_controls = -SOC_DAPM_ENUM("Route", wm8350_enum[8]); +SOC_DAPM_ENUM("Route", wm8350_enum[7]); static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = { @@ -993,6 +990,7 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai) { struct snd_soc_codec *codec = codec_dai->codec; + struct wm8350 *wm8350 = codec->control_data; u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) & ~WM8350_AIF_WL_MASK; @@ -1012,6 +1010,19 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, } wm8350_codec_write(codec, WM8350_AI_FORMATING, iface); + + /* The sloping stopband filter is recommended for use with + * lower sample rates to improve performance. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (params_rate(params) < 24000) + wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME, + WM8350_DAC_SB_FILT); + else + wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME, + WM8350_DAC_SB_FILT); + } + return 0; } @@ -1116,7 +1127,7 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai, fll_1 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_1) & ~(WM8350_FLL_OUTDIV_MASK | WM8350_FLL_RSP_RATE_MASK | 0xc000); wm8350_codec_write(codec, WM8350_FLL_CONTROL_1, - fll_1 | (fll_div.div << 8) | 0x50); + fll_1 | (fll_div.div << 8)); wm8350_codec_write(codec, WM8350_FLL_CONTROL_2, (fll_div.ratio << 11) | (fll_div. n & WM8350_FLL_N_MASK)); @@ -1454,13 +1465,13 @@ static int wm8350_probe(struct platform_device *pdev) /* Latch VU bits & mute */ wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, - WM8350_OUT1_VU | WM8350_OUT1L_MUTE); + WM8350_OUT1_VU); wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, - WM8350_OUT2_VU | WM8350_OUT2L_MUTE); + WM8350_OUT2_VU); wm8350_set_bits(wm8350, WM8350_ROUT1_VOLUME, - WM8350_OUT1_VU | WM8350_OUT1R_MUTE); + WM8350_OUT1_VU); wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME, - WM8350_OUT2_VU | WM8350_OUT2R_MUTE); + WM8350_OUT2_VU); wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L); wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 86c4b24db817..f6627793d98b 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -23,7 +23,10 @@ #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> +#include <linux/spi/spi.h> #include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -160,6 +163,8 @@ #define WM8580_DAC_CONTROL5_MUTEALL 0x10 +#define WM8580_REG_DATA_LENGTH 9 + /* * wm8580 register cache * We can't read the WM8580 register space when we @@ -187,15 +192,19 @@ struct pll_state { unsigned int out; }; +#define WM8580_NUM_SUPPLIES 0 +static const char *wm8580_supply_names[WM8580_NUM_SUPPLIES] = { +}; + /* codec private data */ struct wm8580_priv { struct snd_soc_codec codec; + struct regulator_bulk_data supplies[WM8580_NUM_SUPPLIES]; u16 reg_cache[WM8580_MAX_REGISTER + 1]; struct pll_state a; struct pll_state b; }; - /* * read wm8580 register cache */ @@ -319,7 +328,6 @@ SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume", WM8580_DIGITAL_ATTENUATION_DACL3, WM8580_DIGITAL_ATTENUATION_DACR3, 0, 0xff, 0, dac_tlv), - SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0), SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0), SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0), @@ -524,7 +532,7 @@ static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, (pll_div.k >> 18 & 0xf) | (pll_div.n << 4)); reg = wm8580_read(codec, WM8580_PLLA4 + offset); - reg &= ~0x3f; + reg &= ~0x3b; reg |= pll_div.prescale | pll_div.postscale << 1 | pll_div.freqmode << 3; @@ -740,6 +748,33 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, return 0; } +static int wm8580_set_paif_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + switch (clk_id) { + case WM8580_BCLK_CLKDIV: + reg = wm8580_read(codec, WM8580_PAIF1) & + ~WM8580_AIF_BCLKSEL_MASK; + wm8580_write(codec, WM8580_PAIF1, reg | freq); + reg = wm8580_read(codec, WM8580_PAIF2) & + ~WM8580_AIF_BCLKSEL_MASK; + wm8580_write(codec, WM8580_PAIF2, reg | freq); + break; + case WM8580_LRCLK_CLKDIV: + reg = wm8580_read(codec, WM8580_PAIF1) & ~0x07; + wm8580_write(codec, WM8580_PAIF1, reg | freq); + break; + + default: + return -EINVAL; + } + + return 0; +} + static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute) { struct snd_soc_codec *codec = codec_dai->codec; @@ -795,6 +830,7 @@ static int wm8580_set_bias_level(struct snd_soc_codec *codec, static struct snd_soc_dai_ops wm8580_dai_ops_playback = { .hw_params = wm8580_paif_hw_params, .set_fmt = wm8580_set_paif_dai_fmt, + .set_sysclk = wm8580_set_paif_dai_sysclk, .set_clkdiv = wm8580_set_dai_clkdiv, .set_pll = wm8580_set_dai_pll, .digital_mute = wm8580_digital_mute, @@ -922,11 +958,28 @@ static int wm8580_register(struct wm8580_priv *wm8580) memcpy(codec->reg_cache, wm8580_reg, sizeof(wm8580_reg)); + for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++) + wm8580->supplies[i].supply = wm8580_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8580->supplies), + wm8580->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8580->supplies), + wm8580->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_regulator_get; + } + /* Get the codec into a known state */ ret = wm8580_write(codec, WM8580_RESET, 0); if (ret != 0) { dev_err(codec->dev, "Failed to reset codec: %d\n", ret); - goto err; + goto err_regulator_enable; } for (i = 0; i < ARRAY_SIZE(wm8580_dai); i++) @@ -939,7 +992,7 @@ static int wm8580_register(struct wm8580_priv *wm8580) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - goto err; + goto err_regulator_enable; } ret = snd_soc_register_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); @@ -952,6 +1005,10 @@ static int wm8580_register(struct wm8580_priv *wm8580) err_codec: snd_soc_unregister_codec(codec); +err_regulator_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); +err_regulator_get: + regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); err: kfree(wm8580); return ret; @@ -962,6 +1019,8 @@ static void wm8580_unregister(struct wm8580_priv *wm8580) wm8580_set_bias_level(&wm8580->codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); snd_soc_unregister_codec(&wm8580->codec); + regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); + regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); kfree(wm8580); wm8580_codec = NULL; } @@ -1012,6 +1071,92 @@ static struct i2c_driver wm8580_i2c_driver = { }; #endif +#if defined(CONFIG_SPI) || defined(CONFIG_SPI_MODULE) + /*! + * This function is called to transfer data to WM8580 on SPI. + * + * @param spi the SPI slave device(WM8580) + * @param buf the pointer to the data buffer + * @param len the length of the data to be transferred + * + * @return Returns 0 on success -1 on failure. + */ +static inline int spi_rw(void *control_data, char *data, int length) +{ + int ret; + struct spi_transfer t = { + .tx_buf = (const void *)data, + .rx_buf = (void *)data, + .len = length / 2, + .cs_change = 0, + .delay_usecs = 0, + }; + struct spi_message m; + u8 temp = data[0]; + data[0] = data[1]; + data[1] = temp; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync((struct spi_device *)control_data, &m); + if (unlikely(ret < 0)) + return ret; + else + return length; + +} + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit wm8580_spi_probe(struct spi_device *spi) +{ + struct wm8580_priv *wm8580; + struct snd_soc_codec *codec; + + wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL); + if (wm8580 == NULL) + return -ENOMEM; + + codec = &wm8580->codec; + + spi->bits_per_word = 16; + spi->mode = SPI_MODE_2; + + spi_setup(spi); + + spi_set_drvdata(spi, wm8580); + codec->hw_write = (hw_write_t) spi_rw; + codec->hw_read = (hw_read_t) spi_rw; + codec->control_data = spi; + + codec->dev = &spi->dev; + + return wm8580_register(wm8580); +} + +static int __devinit wm8580_spi_remove(struct spi_device *spi) +{ + struct wm8580_priv *wm8580 = spi_get_drvdata(spi); + wm8580_unregister(wm8580); + return 0; +} + +static struct spi_driver wm8580_spi_driver = { + .driver = { + .name = "wm8580_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8580_spi_probe, + .remove = __devexit_p(wm8580_spi_remove), +}; +#endif + static int __init wm8580_modinit(void) { int ret; @@ -1023,6 +1168,13 @@ static int __init wm8580_modinit(void) } #endif +#if defined(CONFIG_SPI) || defined(CONFIG_SPI_MODULE) + spi_register_driver(&wm8580_spi_driver); + if (ret != 0) { + pr_err("Failed to register WM8580 SPI driver: %d\n", ret); + } +#endif + return 0; } module_init(wm8580_modinit); @@ -1032,6 +1184,10 @@ static void __exit wm8580_exit(void) #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) i2c_del_driver(&wm8580_i2c_driver); #endif + +#if defined(CONFIG_SPI) || defined(CONFIG_SPI_MODULE) + spi_unregister_driver(&wm8580_spi_driver); +#endif } module_exit(wm8580_exit); diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h index 0dfb5ddde6a2..0c51ede6b13b 100644 --- a/sound/soc/codecs/wm8580.h +++ b/sound/soc/codecs/wm8580.h @@ -28,6 +28,15 @@ #define WM8580_CLKSRC_OSC 4 #define WM8580_CLKSRC_NONE 5 +/*clock divider id's */ +#define WM8580_BCLK_CLKDIV 0 +#define WM8580_LRCLK_CLKDIV 1 + +struct wm8580_setup_data { + unsigned short spi; + unsigned short i2c_address; +}; + #define WM8580_DAI_PAIFRX 0 #define WM8580_DAI_PAIFTX 1 |