diff options
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r-- | sound/soc/codecs/Kconfig | 24 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 12 | ||||
-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 | 147 | ||||
-rw-r--r-- | sound/soc/codecs/sgtl5000.c | 1227 | ||||
-rw-r--r-- | sound/soc/codecs/sgtl5000.h | 405 | ||||
-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 | 449 | ||||
-rw-r--r-- | sound/soc/codecs/stmp3xxx_spdif.h | 37 |
13 files changed, 5067 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index bbc97fd76648..43769145740d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -74,6 +74,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 @@ -176,3 +180,23 @@ 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_BLUETOOTH + tristate + depends on SND_SOC diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8b7530546f4d..6b75d7285191 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -3,6 +3,7 @@ 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-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o @@ -34,12 +35,18 @@ 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 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_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o @@ -71,3 +78,8 @@ 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 diff --git a/sound/soc/codecs/ak4647.c b/sound/soc/codecs/ak4647.c new file mode 100644 index 000000000000..46e2e8cbcacc --- /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 (-1 == retval) { + pr_err("%s:read reg errorr:reg=%x,val=%x\n", + __func__, reg, *value); + 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..ee6c49f67280 --- /dev/null +++ b/sound/soc/codecs/bluetooth.c @@ -0,0 +1,147 @@ +/* + * 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); + +MODULE_DESCRIPTION("ASoC bluetooth codec driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c new file mode 100644 index 000000000000..3d754dea05ba --- /dev/null +++ b/sound/soc/codecs/sgtl5000.c @@ -0,0 +1,1227 @@ +/* + * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver + * + * Copyright 2008-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 <mach/hardware.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 "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 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 int dac_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = widget->codec; + unsigned int reg; + + if (ucontrol->value.enumerated.item[0]) { + reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL); + reg |= SGTL5000_INT_OSC_EN; + sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg); + + if (codec->bias_level != SND_SOC_BIAS_ON) { + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_ON); + } else + snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + reg = sgtl5000_read(codec, SGTL5000_CHIP_ANA_CTRL); + reg &= ~(SGTL5000_LINE_OUT_MUTE | SGTL5000_HP_MUTE); + sgtl5000_write(codec, SGTL5000_CHIP_ANA_CTRL, reg); + } else { + reg = sgtl5000_read(codec, SGTL5000_CHIP_CLK_TOP_CTRL); + reg &= ~SGTL5000_INT_OSC_EN; + sgtl5000_write(codec, SGTL5000_CHIP_CLK_TOP_CTRL, reg); + + snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + } + return 0; +} + +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 = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "DAC Mux", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE + | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_soc_info_enum_double, + .get = snd_soc_dapm_get_enum_double, + .put = dac_mux_put, + .private_value = (unsigned long)&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] = l; + + 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; + + 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 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; + } + +#if 0 /* SGTL5000 rev1 has a IC bug to prevent switching to MCLK from PLL. */ + if (fs * 256 == sgtl5000->sysclk) + clk_ctl |= SGTL5000_MCLK_FREQ_256FS << SGTL5000_MCLK_FREQ_SHIFT; + else if (fs * 384 == sgtl5000->sysclk && fs != 96000) + clk_ctl |= SGTL5000_MCLK_FREQ_384FS << SGTL5000_MCLK_FREQ_SHIFT; + else if (fs * 512 == sgtl5000->sysclk && fs != 96000) + clk_ctl |= SGTL5000_MCLK_FREQ_512FS << SGTL5000_MCLK_FREQ_SHIFT; + else +#endif + { + if (!sgtl5000->master) { + pr_err("%s: PLL not supported in slave mode\n", + __func__); + return -EINVAL; + } + 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 (sgtl5000->lrclk == 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("fs=%d,clk_ctl=%d,pll_ctl=%d,i2s_ctl=%d,div2=%d\n", + sgtl5000->lrclk, clk_ctl, pll_ctl, i2s_ctl, div2); + + if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) { + 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 = 400; + 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_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 = 1, + .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 sgtl5000_platform_data *plat = socdev->codec_data; + 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 (!plat->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 (plat->vddio < 3100 && plat->vdda < 3100) { + /* Enable VDDC charge pump */ + ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP; + } + if (plat->vddio >= 3100 && plat->vdda >= 3100) { + /* VDDC use VDDIO rail */ + lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD; + if (plat->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 = plat->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 = plat->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); + + if (cpu_is_mx25()) + sgtl5000_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x01df); + else + 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; + 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; + + 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); + return -ENODEV; + } + + 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); + return ret; + } + + ret = snd_soc_register_dai(&sgtl5000_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + return ret; + } + + 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; + + snd_soc_unregister_dai(&sgtl5000_dai); + snd_soc_unregister_codec(codec); + 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..177be05b486c --- /dev/null +++ b/sound/soc/codecs/sgtl5000.h @@ -0,0 +1,405 @@ +/* + * sgtl5000.h - SGTL5000 audio codec interface + * + * Copyright 2008-2009 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _SGTL5000_H +#define _SGTL5000_H + +#include <linux/i2c.h> + +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_SYS_FS_MASK 0x00c0 +#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 + +struct sgtl5000_platform_data { + int vddio; /* voltage of VDDIO (mv) */ + int vdda; /* voltage of vdda (mv) */ + int vddd; /* voltage of vddd (mv), 0 if not connected */ +}; + +#endif diff --git a/sound/soc/codecs/stmp378x_codec.c b/sound/soc/codecs/stmp378x_codec.c new file mode 100644 index 000000000000..770163947171 --- /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_AUDIOOUT_BASE + HW_AUDIOIN_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_STAT, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ADCSRR, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ADCVOLUME, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ADCDEBUG, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ADCVOL, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_MICLINE, + REGS_AUDIOOUT_BASE + HW_AUDIOIN_ANACLKCTRL, + REGS_AUDIOOUT_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 */ + stmp3xxx_clearl(BM_AUDIOOUT_DACVOLUME_VOLUME_LEFT | + BM_AUDIOOUT_DACVOLUME_VOLUME_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + stmp3xxx_setl(reg, REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + + 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) + stmp3xxx_setl(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + else + stmp3xxx_setl(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + break; + + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + else + stmp3xxx_clearl(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + 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) { + stmp3xxx_setl(dac_mask, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + stmp3xxx_setl(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + stmp3xxx_setl(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL); + } else { + stmp3xxx_clearl(dac_mask, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + stmp3xxx_clearl(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + stmp3xxx_clearl(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL); + } + 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 */ + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + stmp3xxx_clearl(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL); + + /* 16 bit word length */ + stmp3xxx_setl(BM_AUDIOOUT_CTRL_WORD_LENGTH, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + + /* Update DAC volume over zero crossings */ + stmp3xxx_setl(BM_AUDIOOUT_DACVOLUME_EN_ZCD, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + /* Mute DAC */ + stmp3xxx_setl(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + + /* Update HP volume over zero crossings */ + stmp3xxx_setl(BM_AUDIOOUT_HPVOL_EN_MSTR_ZCD, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + + /* Prepare powering up HP output */ + stmp3xxx_setl(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + stmp3xxx_setl(BF(0x2, RTC_PERSISTENT0_SPARE_ANALOG), + REGS_RTC_BASE + HW_RTC_PERSISTENT0); + stmp3xxx_setl(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + stmp3xxx_clearl(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + /* Mute HP output */ + stmp3xxx_setl(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + + /* Mute speaker amp */ + stmp3xxx_setl(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL); +} + +static void +stmp378x_codec_dac_power_down(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Disable class AB */ + stmp3xxx_clearl(BM_AUDIOOUT_ANACTRL_HP_CLASSAB, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + + /* Set hold to ground */ + stmp3xxx_setl(BM_AUDIOOUT_ANACTRL_HP_HOLD_GND, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACTRL); + + /* Mute HP output */ + stmp3xxx_setl(BM_AUDIOOUT_HPVOL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_HPVOL); + /* Power down HP output */ + stmp3xxx_setl(BM_AUDIOOUT_PWRDN_HEADPHONE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN); + + /* Mute speaker amp */ + stmp3xxx_setl(BM_AUDIOOUT_SPEAKERCTRL_MUTE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_SPEAKERCTRL); + /* Power down speaker amp */ + stmp3xxx_setl(BM_AUDIOOUT_PWRDN_SPEAKER, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN); + + /* Mute DAC */ + stmp3xxx_setl(BM_AUDIOOUT_DACVOLUME_MUTE_LEFT | + BM_AUDIOOUT_DACVOLUME_MUTE_RIGHT, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_DACVOLUME); + /* Power down DAC */ + stmp3xxx_setl(BM_AUDIOOUT_PWRDN_DAC, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN); + + /* Gate DAC clocks */ + stmp3xxx_setl(BM_AUDIOOUT_ANACLKCTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_ANACLKCTRL); + stmp3xxx_setl(BM_AUDIOOUT_CTRL_CLKGATE, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); +} + +static void +stmp378x_codec_adc_power_on(struct stmp378x_codec_priv *stmp378x_adc) +{ + u32 reg; + + /* Ungate ADC clocks */ + stmp3xxx_clearl(BM_AUDIOIN_CTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + stmp3xxx_clearl(BM_AUDIOIN_ANACLKCTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL); + + /* 16 bit word length */ + stmp3xxx_setl(BM_AUDIOIN_CTRL_WORD_LENGTH, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + /* Unmute ADC channels */ + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_MUTE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + + /* + * The MUTE_LEFT and MUTE_RIGHT fields need to be cleared. + * They aren't presented in the datasheet, so this is hardcode. + */ + stmp3xxx_clearl(0x01000100, REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOLUME); + + /* Set the Input channel gain 3dB */ + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_GAIN_LEFT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_GAIN_RIGHT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_setl(BF(2, AUDIOIN_ADCVOL_GAIN_LEFT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_setl(BF(2, AUDIOIN_ADCVOL_GAIN_RIGHT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + + /* Select default input - Microphone */ + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_SELECT_LEFT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_clearl(BM_AUDIOIN_ADCVOL_SELECT_RIGHT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_setl(BF + (BV_AUDIOIN_ADCVOL_SELECT__MIC, + AUDIOIN_ADCVOL_SELECT_LEFT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + stmp3xxx_setl(BF + (BV_AUDIOIN_ADCVOL_SELECT__MIC, + AUDIOIN_ADCVOL_SELECT_RIGHT), + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + + /* Supply bias voltage to microphone */ + stmp3xxx_setl(BF(2, AUDIOIN_MICLINE_MIC_RESISTOR), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE); + stmp3xxx_setl(BM_AUDIOIN_MICLINE_MIC_SELECT, + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE); + + /* 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 */ + stmp3xxx_setl(BM_AUDIOIN_ADCVOL_MUTE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ADCVOL); + + /* Power Down ADC */ + stmp3xxx_setl(BM_AUDIOOUT_PWRDN_ADC | BM_AUDIOOUT_PWRDN_RIGHT_ADC, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_PWRDN); + + /* Gate ADC clocks */ + stmp3xxx_setl(BM_AUDIOIN_CTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + stmp3xxx_setl(BM_AUDIOIN_ANACLKCTRL_CLKGATE, + REGS_AUDIOIN_BASE + HW_AUDIOIN_ANACLKCTRL); + + /* Disable bias voltage to microphone */ + stmp3xxx_setl(BF(0, AUDIOIN_MICLINE_MIC_RESISTOR), + REGS_AUDIOIN_BASE + HW_AUDIOIN_MICLINE); +} + +static void stmp378x_codec_dac_enable(struct stmp378x_codec_priv *stmp378x_adc) +{ + /* Move DAC codec out of reset */ + stmp3xxx_clearl(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + + /* Reduce analog power */ + stmp3xxx_clearl(BM_AUDIOOUT_TEST_HP_I1_ADJ, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST); + stmp3xxx_setl(BF(0x1, AUDIOOUT_TEST_HP_I1_ADJ), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_TEST); + stmp3xxx_setl(BM_AUDIOOUT_REFCTRL_LOW_PWR, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + stmp3xxx_setl(BM_AUDIOOUT_REFCTRL_XTAL_BGR_BIAS, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + stmp3xxx_clearl(BM_AUDIOOUT_REFCTRL_BIAS_CTRL, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + stmp3xxx_clearl(BF(0x1, AUDIOOUT_REFCTRL_BIAS_CTRL), + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_REFCTRL); + + /* 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 */ + stmp3xxx_clearl(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + + /* 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 */ + stmp3xxx_setl(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + while (!(__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) & + BM_AUDIOOUT_CTRL_CLKGATE)); + + /* Soft reset ADC block */ + stmp3xxx_setl(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + 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 */ + stmp3xxx_setl(BM_AUDIOOUT_CTRL_SFTRST, + REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL); + while (! + (__raw_readl(REGS_AUDIOOUT_BASE + HW_AUDIOOUT_CTRL) & + BM_AUDIOOUT_CTRL_CLKGATE)) ; + + /* Soft reset ADC block */ + stmp3xxx_setl(BM_AUDIOIN_CTRL_SFTRST, + REGS_AUDIOIN_BASE + HW_AUDIOIN_CTRL); + 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..db5627e5e156 --- /dev/null +++ b/sound/soc/codecs/stmp3xxx_spdif.c @@ -0,0 +1,449 @@ +/* + * 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) + stmp3xxx_setl(BM_SPDIF_CTRL_WORD_LENGTH, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + break; + case SNDRV_PCM_FORMAT_S32_LE: + if (playback) + stmp3xxx_clearl(BM_SPDIF_CTRL_WORD_LENGTH, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + 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 */ + stmp3xxx_clearl(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + + /* Ungate SPDIF clocks */ + stmp3xxx_clearl(BM_SPDIF_CTRL_CLKGATE, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + + /* 16 bit word length */ + stmp3xxx_setl(BM_SPDIF_CTRL_WORD_LENGTH, REGS_SPDIF_BASE + HW_SPDIF_CTRL); +} + +static void +stmp3xxx_codec_spdif_disable(struct stmp3xxx_codec_priv *stmp3xxx_spdif) +{ + /* Gate SPDIF clocks */ + stmp3xxx_setl(BM_SPDIF_CTRL_CLKGATE, REGS_SPDIF_BASE + HW_SPDIF_CTRL); +} + +static void stmp3xxx_codec_init(struct snd_soc_codec *codec) +{ + struct stmp3xxx_codec_priv *stmp3xxx_spdif = codec->private_data; + + /* Soft reset SPDIF block */ + stmp3xxx_setl(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + 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 */ + stmp3xxx_setl(BM_SPDIF_CTRL_SFTRST, REGS_SPDIF_BASE + HW_SPDIF_CTRL); + 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 */ |