diff options
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r-- | sound/soc/codecs/Kconfig | 22 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 8 | ||||
-rw-r--r-- | sound/soc/codecs/ac97.c | 31 | ||||
-rw-r--r-- | sound/soc/codecs/ac97.h | 2 | ||||
-rw-r--r-- | sound/soc/codecs/ak4535.c | 696 | ||||
-rw-r--r-- | sound/soc/codecs/ak4535.h | 46 | ||||
-rw-r--r-- | sound/soc/codecs/cs4270.c | 8 | ||||
-rw-r--r-- | sound/soc/codecs/cs4270.h | 2 | ||||
-rw-r--r-- | sound/soc/codecs/tlv320aic3x.c | 384 | ||||
-rw-r--r-- | sound/soc/codecs/tlv320aic3x.h | 55 | ||||
-rw-r--r-- | sound/soc/codecs/uda1380.c | 852 | ||||
-rw-r--r-- | sound/soc/codecs/uda1380.h | 89 | ||||
-rw-r--r-- | sound/soc/codecs/wm8510.c | 817 | ||||
-rw-r--r-- | sound/soc/codecs/wm8510.h | 103 | ||||
-rw-r--r-- | sound/soc/codecs/wm8731.c | 79 | ||||
-rw-r--r-- | sound/soc/codecs/wm8731.h | 2 | ||||
-rw-r--r-- | sound/soc/codecs/wm8750.c | 87 | ||||
-rw-r--r-- | sound/soc/codecs/wm8750.h | 2 | ||||
-rw-r--r-- | sound/soc/codecs/wm8753.c | 183 | ||||
-rw-r--r-- | sound/soc/codecs/wm8753.h | 2 | ||||
-rw-r--r-- | sound/soc/codecs/wm8990.c | 1626 | ||||
-rw-r--r-- | sound/soc/codecs/wm8990.h | 832 | ||||
-rw-r--r-- | sound/soc/codecs/wm9712.c | 53 | ||||
-rw-r--r-- | sound/soc/codecs/wm9712.h | 2 | ||||
-rw-r--r-- | sound/soc/codecs/wm9713.c | 79 | ||||
-rw-r--r-- | sound/soc/codecs/wm9713.h | 2 |
26 files changed, 5596 insertions, 468 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 3903ab7..1db04a2 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1,31 +1,37 @@ config SND_SOC_AC97_CODEC tristate - depends on SND_SOC + select SND_AC97_CODEC + +config SND_SOC_AK4535 + tristate + +config SND_SOC_UDA1380 + tristate + +config SND_SOC_WM8510 + tristate config SND_SOC_WM8731 tristate - depends on SND_SOC config SND_SOC_WM8750 tristate - depends on SND_SOC config SND_SOC_WM8753 tristate - depends on SND_SOC + +config SND_SOC_WM8990 + tristate config SND_SOC_WM9712 tristate - depends on SND_SOC config SND_SOC_WM9713 tristate - depends on SND_SOC # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate - depends on SND_SOC # Cirrus Logic CS4270 Codec Hardware Mute Support # Select if you have external muting circuitry attached to your CS4270. @@ -43,4 +49,4 @@ config SND_SOC_CS4270_VD33_ERRATA config SND_SOC_TLV320AIC3X tristate - depends on SND_SOC && I2C + depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4e1314c..d7b97ab 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,16 +1,24 @@ snd-soc-ac97-objs := ac97.o +snd-soc-ak4535-objs := ak4535.o +snd-soc-uda1380-objs := uda1380.o +snd-soc-wm8510-objs := wm8510.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o +snd-soc-wm8990-objs := wm8990.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-cs4270-objs := cs4270.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o +obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index 2a1ffe3..61fd96c 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -10,9 +10,6 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 17th Oct 2005 Initial version. - * * Generic AC97 support. */ @@ -24,6 +21,7 @@ #include <sound/ac97_codec.h> #include <sound/initval.h> #include <sound/soc.h> +#include "ac97.h" #define AC97_VERSION "0.6" @@ -43,7 +41,7 @@ static int ac97_prepare(struct snd_pcm_substream *substream) SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ SNDRV_PCM_RATE_48000) -struct snd_soc_codec_dai ac97_dai = { +struct snd_soc_dai ac97_dai = { .name = "AC97 HiFi", .type = SND_SOC_DAI_AC97, .playback = { @@ -146,9 +144,34 @@ static int ac97_soc_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int ac97_soc_suspend(struct platform_device *pdev, pm_message_t msg) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_ac97_suspend(socdev->codec->ac97); + + return 0; +} + +static int ac97_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_ac97_resume(socdev->codec->ac97); + + return 0; +} +#else +#define ac97_soc_suspend NULL +#define ac97_soc_resume NULL +#endif + struct snd_soc_codec_device soc_codec_dev_ac97 = { .probe = ac97_soc_probe, .remove = ac97_soc_remove, + .suspend = ac97_soc_suspend, + .resume = ac97_soc_resume, }; EXPORT_SYMBOL_GPL(soc_codec_dev_ac97); diff --git a/sound/soc/codecs/ac97.h b/sound/soc/codecs/ac97.h index 2bf6d69..281aa42 100644 --- a/sound/soc/codecs/ac97.h +++ b/sound/soc/codecs/ac97.h @@ -14,6 +14,6 @@ #define __LINUX_SND_SOC_AC97_H extern struct snd_soc_codec_device soc_codec_dev_ac97; -extern struct snd_soc_codec_dai ac97_dai; +extern struct snd_soc_dai ac97_dai; #endif diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c new file mode 100644 index 0000000..b26003c --- /dev/null +++ b/sound/soc/codecs/ak4535.c @@ -0,0 +1,696 @@ +/* + * ak4535.c -- AK4535 ALSA Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on wm8753.c by Liam Girdwood + * + * 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 "ak4535.h" + +#define AUDIO_NAME "ak4535" +#define AK4535_VERSION "0.3" + +struct snd_soc_codec_device soc_codec_dev_ak4535; + +/* codec private data */ +struct ak4535_priv { + unsigned int sysclk; +}; + +/* + * ak4535 register cache + */ +static const u16 ak4535_reg[AK4535_CACHEREGNUM] = { + 0x0000, 0x0080, 0x0000, 0x0003, + 0x0002, 0x0000, 0x0011, 0x0001, + 0x0000, 0x0040, 0x0036, 0x0010, + 0x0000, 0x0000, 0x0057, 0x0000, +}; + +/* + * read ak4535 register cache + */ +static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return -1; + return cache[reg]; +} + +static inline unsigned int ak4535_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 ak4535 register cache + */ +static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the AK4535 register space + */ +static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D8 AK4535 register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + ak4535_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static int ak4535_sync(struct snd_soc_codec *codec) +{ + u16 *cache = codec->reg_cache; + int i, r = 0; + + for (i = 0; i < AK4535_CACHEREGNUM; i++) + r |= ak4535_write(codec, i, cache[i]); + + return r; +}; + +static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"}; +static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"}; +static const char *ak4535_hp_out[] = {"Stereo", "Mono"}; +static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"}; +static const char *ak4535_mic_select[] = {"Internal", "External"}; + +static const struct soc_enum ak4535_enum[] = { + SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain), + SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out), + SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out), + SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp), + SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select), +}; + +static const struct snd_kcontrol_new ak4535_snd_controls[] = { + SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0), + SOC_ENUM("Mono 1 Output", ak4535_enum[1]), + SOC_ENUM("Mono 1 Gain", ak4535_enum[0]), + SOC_ENUM("Headphone Output", ak4535_enum[2]), + SOC_ENUM("Playback Deemphasis", ak4535_enum[3]), + SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0), + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0), + SOC_ENUM("Mic Select", ak4535_enum[4]), + SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0), + SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0), + SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0), + SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0), + SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0), + SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0), + SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1), + SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1), + SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0), + SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0), +}; + +/* add non dapm controls */ +static int ak4535_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ak4535_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Mono 1 Mixer */ +static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0), +}; + +/* Stereo Mixer */ +static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0), + SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0), +}; + +/* Input Mixer */ +static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0), + SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ak4535_input_mux_control = + SOC_DAPM_ENUM("Input Select", ak4535_enum[4]); + +/* HP L switch */ +static const struct snd_kcontrol_new ak4535_hpl_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1); + +/* HP R switch */ +static const struct snd_kcontrol_new ak4535_hpr_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1); + +/* mono 2 switch */ +static const struct snd_kcontrol_new ak4535_mono2_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0); + +/* Line out switch */ +static const struct snd_kcontrol_new ak4535_line_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0); + +/* ak4535 dapm widgets */ +static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_stereo_mixer_controls[0], + ARRAY_SIZE(ak4535_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_mono1_mixer_controls[0], + ARRAY_SIZE(ak4535_mono1_mixer_controls)), + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_input_mixer_controls[0], + ARRAY_SIZE(ak4535_input_mixer_controls)), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ak4535_input_mux_control), + SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0), + SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0, + &ak4535_mono2_control), + /* speaker powersave bit */ + SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0, + &ak4535_line_control), + SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpl_control), + SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpr_control), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPP"), + SND_SOC_DAPM_OUTPUT("SPN"), + SND_SOC_DAPM_OUTPUT("MOUT1"), + SND_SOC_DAPM_OUTPUT("MOUT2"), + SND_SOC_DAPM_OUTPUT("MICOUT"), + SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0), + SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0), + SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0), + SND_SOC_DAPM_INPUT("MICIN"), + SND_SOC_DAPM_INPUT("MICEXT"), + SND_SOC_DAPM_INPUT("AUX"), + SND_SOC_DAPM_INPUT("MIN"), + SND_SOC_DAPM_INPUT("AIN"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /*stereo mixer */ + {"Stereo Mixer", "Playback Switch", "DAC"}, + {"Stereo Mixer", "Mic Sidetone Switch", "Mic"}, + {"Stereo Mixer", "Aux Bypass Switch", "AUX In"}, + + /* mono1 mixer */ + {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"}, + {"Mono1 Mixer", "Mono Playback Switch", "DAC"}, + + /* Mic */ + {"Mic", NULL, "AIN"}, + {"Input Mux", "Internal", "Mic Int Bias"}, + {"Input Mux", "External", "Mic Ext Bias"}, + {"Mic Int Bias", NULL, "MICIN"}, + {"Mic Ext Bias", NULL, "MICEXT"}, + {"MICOUT", NULL, "Input Mux"}, + + /* line out */ + {"LOUT", NULL, "Line Out Enable"}, + {"ROUT", NULL, "Line Out Enable"}, + {"Line Out Enable", "Switch", "Line Out"}, + {"Line Out", NULL, "Stereo Mixer"}, + + /* mono1 out */ + {"MOUT1", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono1 Mixer"}, + + /* left HP */ + {"HPL", NULL, "Left HP Enable"}, + {"Left HP Enable", "Switch", "HP L Amp"}, + {"HP L Amp", NULL, "Stereo Mixer"}, + + /* right HP */ + {"HPR", NULL, "Right HP Enable"}, + {"Right HP Enable", "Switch", "HP R Amp"}, + {"HP R Amp", NULL, "Stereo Mixer"}, + + /* speaker */ + {"SPP", NULL, "Speaker Enable"}, + {"SPN", NULL, "Speaker Enable"}, + {"Speaker Enable", "Switch", "Spk Amp"}, + {"Spk Amp", NULL, "MIN"}, + + /* mono 2 */ + {"MOUT2", NULL, "Mono 2 Enable"}, + {"Mono 2 Enable", "Switch", "Stereo Mixer"}, + + /* Aux In */ + {"Aux In", NULL, "AUX"}, + + /* ADC */ + {"ADC", NULL, "Input Mixer"}, + {"Input Mixer", "Mic Capture Switch", "Mic"}, + {"Input Mixer", "Aux Capture Switch", "Aux In"}, +}; + +static int ak4535_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ak4535_dapm_widgets, + ARRAY_SIZE(ak4535_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int ak4535_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 ak4535_priv *ak4535 = codec->private_data; + + ak4535->sysclk = freq; + return 0; +} + +static int ak4535_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ak4535_priv *ak4535 = codec->private_data; + u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5); + int rate = params_rate(params), fs = 256; + + if (rate) + fs = ak4535->sysclk / rate; + + /* set fs */ + switch (fs) { + case 1024: + mode2 |= (0x2 << 5); + break; + case 512: + mode2 |= (0x1 << 5); + break; + case 256: + break; + } + + /* set rate */ + ak4535_write(codec, AK4535_MODE2, mode2); + return 0; +} + +static int ak4535_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 mode1 = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode1 = 0x0002; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode1 = 0x0001; + break; + default: + return -EINVAL; + } + + /* use 32 fs for BCLK to save power */ + mode1 |= 0x4; + + ak4535_write(codec, AK4535_MODE1, mode1); + return 0; +} + +static int ak4535_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf; + if (!mute) + ak4535_write(codec, AK4535_DAC, mute_reg); + else + ak4535_write(codec, AK4535_DAC, mute_reg | 0x20); + return 0; +} + +static int ak4535_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 i; + + switch (level) { + case SND_SOC_BIAS_ON: + ak4535_mute(codec->dai, 0); + break; + case SND_SOC_BIAS_PREPARE: + ak4535_mute(codec->dai, 1); + break; + case SND_SOC_BIAS_STANDBY: + i = ak4535_read_reg_cache(codec, AK4535_PM1); + ak4535_write(codec, AK4535_PM1, i | 0x80); + i = ak4535_read_reg_cache(codec, AK4535_PM2); + ak4535_write(codec, AK4535_PM2, i & (~0x80)); + break; + case SND_SOC_BIAS_OFF: + i = ak4535_read_reg_cache(codec, AK4535_PM1); + ak4535_write(codec, AK4535_PM1, i & (~0x80)); + break; + } + codec->bias_level = level; + return 0; +} + +#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_dai ak4535_dai = { + .name = "AK4535", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = ak4535_hw_params, + }, + .dai_ops = { + .set_fmt = ak4535_set_dai_fmt, + .digital_mute = ak4535_mute, + .set_sysclk = ak4535_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(ak4535_dai); + +static int ak4535_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ak4535_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + ak4535_sync(codec); + ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ak4535_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the AK4535 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ak4535_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "AK4535"; + codec->owner = THIS_MODULE; + codec->read = ak4535_read_reg_cache; + codec->write = ak4535_write; + codec->set_bias_level = ak4535_set_bias_level; + codec->dai = &ak4535_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(ak4535_reg); + codec->reg_cache = kmemdup(ak4535_reg, sizeof(ak4535_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ak4535_add_controls(codec); + ak4535_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ak4535: 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; +} + +static struct snd_soc_device *ak4535_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +#define I2C_DRIVERID_AK4535 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver ak4535_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static int ak4535_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = ak4535_socdev; + struct ak4535_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + printk(KERN_ERR "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = ak4535_init(socdev); + if (ret < 0) { + printk(KERN_ERR "failed to initialise AK4535\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int ak4535_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int ak4535_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, ak4535_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver ak4535_i2c_driver = { + .driver = { + .name = "AK4535 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_AK4535, + .attach_adapter = ak4535_i2c_attach, + .detach_client = ak4535_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "AK4535", + .driver = &ak4535_i2c_driver, +}; +#endif + +static int ak4535_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ak4535_setup_data *setup; + struct snd_soc_codec *codec; + struct ak4535_priv *ak4535; + int ret = 0; + + printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL); + if (ak4535 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ak4535; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ak4535_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + codec->hw_read = (hw_read_t)i2c_master_recv; + ret = i2c_add_driver(&ak4535_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int ak4535_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + ak4535_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(&ak4535_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4535 = { + .probe = ak4535_probe, + .remove = ak4535_remove, + .suspend = ak4535_suspend, + .resume = ak4535_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4535); + +MODULE_DESCRIPTION("Soc AK4535 driver"); +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h new file mode 100644 index 0000000..e9fe30e --- /dev/null +++ b/sound/soc/codecs/ak4535.h @@ -0,0 +1,46 @@ +/* + * ak4535.h -- AK4535 Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on wm8753.h + * + * 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 _AK4535_H +#define _AK4535_H + +/* AK4535 register space */ + +#define AK4535_PM1 0x0 +#define AK4535_PM2 0x1 +#define AK4535_SIG1 0x2 +#define AK4535_SIG2 0x3 +#define AK4535_MODE1 0x4 +#define AK4535_MODE2 0x5 +#define AK4535_DAC 0x6 +#define AK4535_MIC 0x7 +#define AK4535_TIMER 0x8 +#define AK4535_ALC1 0x9 +#define AK4535_ALC2 0xa +#define AK4535_PGA 0xb +#define AK4535_LATT 0xc +#define AK4535_RATT 0xd +#define AK4535_VOL 0xe +#define AK4535_STATUS 0xf + +#define AK4535_CACHEREGNUM 0x10 + +struct ak4535_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ak4535_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak4535; + +#endif diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index e73fcfd..9deb8c7 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -201,7 +201,7 @@ static struct { * driver what the input settings can be. This would need to be implemented * for stand-alone mode to work. */ -static int cs4270_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int cs4270_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; @@ -251,7 +251,7 @@ static int cs4270_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, * data for playback only, but ASoC currently does not support different * formats for playback vs. record. */ -static int cs4270_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int format) { struct snd_soc_codec *codec = codec_dai->codec; @@ -471,7 +471,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, * board does not have the MUTEA or MUTEB pins connected to such circuitry, * then this function will do nothing. */ -static int cs4270_mute(struct snd_soc_codec_dai *dai, int mute) +static int cs4270_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; int reg6; @@ -667,7 +667,7 @@ error: #endif /* USE_I2C*/ -struct snd_soc_codec_dai cs4270_dai = { +struct snd_soc_dai cs4270_dai = { .name = "CS4270", .playback = { .stream_name = "Playback", diff --git a/sound/soc/codecs/cs4270.h b/sound/soc/codecs/cs4270.h index 0ced49b..adc6cd9 100644 --- a/sound/soc/codecs/cs4270.h +++ b/sound/soc/codecs/cs4270.h @@ -16,7 +16,7 @@ * The ASoC codec DAI structure for the CS4270. Assign this structure to * the .codec_dai field of your machine driver's snd_soc_dai_link structure. */ -extern struct snd_soc_codec_dai cs4270_dai; +extern struct snd_soc_dai cs4270_dai; /* * The ASoC codec device structure for the CS4270. Assign this structure diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 09b1661..b1dce5f 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -29,7 +29,7 @@ * --------------------------------------- * * Hence the machine layer should disable unsupported inputs/outputs by - * snd_soc_dapm_set_endpoint(codec, "MONO_LOUT", 0), etc. + * snd_soc_dapm_disable_pin(codec, "MONO_LOUT"), etc. */ #include <linux/module.h> @@ -49,7 +49,7 @@ #include "tlv320aic3x.h" #define AUDIO_NAME "aic3x" -#define AIC3X_VERSION "0.1" +#define AIC3X_VERSION "0.2" /* codec private data */ struct aic3x_priv { @@ -138,6 +138,20 @@ static int aic3x_write(struct snd_soc_codec *codec, unsigned int reg, return -EIO; } +/* + * read from the aic3x register space + */ +static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg, + u8 *value) +{ + *value = reg & 0xff; + if (codec->hw_read(codec->control_data, value, 1) != 1) + return -EIO; + + aic3x_write_reg_cache(codec, reg, *value); + return 0; +} + #define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_volsw, \ @@ -192,7 +206,7 @@ static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol, } if (found) - snd_soc_dapm_sync_endpoints(widget->codec); + snd_soc_dapm_sync(widget->codec); } ret = snd_soc_update_bits(widget->codec, reg, val_mask, val); @@ -209,6 +223,8 @@ static const char *aic3x_right_hpcom_mux[] = { "differential of HPROUT", "constant VCM", "single-ended", "differential of HPLCOM", "external feedback" }; static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" }; +static const char *aic3x_adc_hpf[] = + { "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" }; #define LDAC_ENUM 0 #define RDAC_ENUM 1 @@ -218,6 +234,7 @@ static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" }; #define LINE1R_ENUM 5 #define LINE2L_ENUM 6 #define LINE2R_ENUM 7 +#define ADC_HPF_ENUM 8 static const struct soc_enum aic3x_enum[] = { SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux), @@ -228,6 +245,7 @@ static const struct soc_enum aic3x_enum[] = { SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf), }; static const struct snd_kcontrol_new aic3x_snd_controls[] = { @@ -278,6 +296,8 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { /* Input */ SOC_DOUBLE_R("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x7f, 0), SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1), + + SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]), }; /* add non dapm controls */ @@ -441,11 +461,34 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0, &aic3x_right_line2_mux_controls), + /* + * Not a real mic bias widget but similar function. This is for dynamic + * control of GPIO1 digital mic modulator clock output function when + * using digital mic. + */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "GPIO1 dmic modclk", + AIC3X_GPIO1_REG, 4, 0xf, + AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK, + AIC3X_GPIO1_FUNC_DISABLED), + + /* + * Also similar function like mic bias. Selects digital mic with + * configurable oversampling rate instead of ADC converter. + */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 128", + AIC3X_ASD_INTF_CTRLA, 0, 3, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 64", + AIC3X_ASD_INTF_CTRLA, 0, 3, 2, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 32", + AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0), + /* Mic Bias */ - SND_SOC_DAPM_MICBIAS("Mic Bias 2V", MICBIAS_CTRL, 6, 0), - SND_SOC_DAPM_MICBIAS("Mic Bias 2.5V", MICBIAS_CTRL, 7, 0), - SND_SOC_DAPM_MICBIAS("Mic Bias AVDD", MICBIAS_CTRL, 6, 0), - SND_SOC_DAPM_MICBIAS("Mic Bias AVDD", MICBIAS_CTRL, 7, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2V", + MICBIAS_CTRL, 6, 3, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2.5V", + MICBIAS_CTRL, 6, 3, 2, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias AVDD", + MICBIAS_CTRL, 6, 3, 3, 0), /* Left PGA to Left Output bypass */ SND_SOC_DAPM_MIXER("Left PGA Bypass Mixer", SND_SOC_NOPM, 0, 0, @@ -483,7 +526,7 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINE2R"), }; -static const char *intercon[][3] = { +static const struct snd_soc_dapm_route intercon[] = { /* Left Output */ {"Left DAC Mux", "DAC_L1", "Left DAC"}, {"Left DAC Mux", "DAC_L2", "Left DAC"}, @@ -554,6 +597,7 @@ static const char *intercon[][3] = { {"Left PGA Mixer", "Mic3L Switch", "MIC3L"}, {"Left ADC", NULL, "Left PGA Mixer"}, + {"Left ADC", NULL, "GPIO1 dmic modclk"}, /* Right Input */ {"Right Line1R Mux", "single-ended", "LINE1R"}, @@ -567,6 +611,7 @@ static const char *intercon[][3] = { {"Right PGA Mixer", "Mic3R Switch", "MIC3R"}, {"Right ADC", NULL, "Right PGA Mixer"}, + {"Right ADC", NULL, "GPIO1 dmic modclk"}, /* Left PGA Bypass */ {"Left PGA Bypass Mixer", "Line Switch", "Left PGA Mixer"}, @@ -628,101 +673,27 @@ static const char *intercon[][3] = { {"Mono Out", NULL, "Right Line2 Bypass Mixer"}, {"Right HP Out", NULL, "Right Line2 Bypass Mixer"}, - /* terminator */ - {NULL, NULL, NULL}, + /* + * Logical path between digital mic enable and GPIO1 modulator clock + * output function + */ + {"GPIO1 dmic modclk", NULL, "DMic Rate 128"}, + {"GPIO1 dmic modclk", NULL, "DMic Rate 64"}, + {"GPIO1 dmic modclk", NULL, "DMic Rate 32"}, }; static int aic3x_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(aic3x_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &aic3x_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets, + ARRAY_SIZE(aic3x_dapm_widgets)); /* set up audio path interconnects */ - for (i = 0; intercon[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, intercon[i][0], - intercon[i][1], intercon[i][2]); + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); snd_soc_dapm_new_widgets(codec); return 0; } -struct aic3x_rate_divs { - u32 mclk; - u32 rate; - u32 fsref_reg; - u8 sr_reg:4; - u8 pllj_reg; - u16 plld_reg; -}; - -/* AIC3X codec mclk clock divider coefficients */ -static const struct aic3x_rate_divs aic3x_divs[] = { - /* 8k */ - {12000000, 8000, 48000, 0xa, 16, 3840}, - {19200000, 8000, 48000, 0xa, 10, 2400}, - {22579200, 8000, 48000, 0xa, 8, 7075}, - {33868800, 8000, 48000, 0xa, 5, 8049}, - /* 11.025k */ - {12000000, 11025, 44100, 0x6, 15, 528}, - {19200000, 11025, 44100, 0x6, 9, 4080}, - {22579200, 11025, 44100, 0x6, 8, 0}, - {33868800, 11025, 44100, 0x6, 5, 3333}, - /* 16k */ - {12000000, 16000, 48000, 0x4, 16, 3840}, - {19200000, 16000, 48000, 0x4, 10, 2400}, - {22579200, 16000, 48000, 0x4, 8, 7075}, - {33868800, 16000, 48000, 0x4, 5, 8049}, - /* 22.05k */ - {12000000, 22050, 44100, 0x2, 15, 528}, - {19200000, 22050, 44100, 0x2, 9, 4080}, - {22579200, 22050, 44100, 0x2, 8, 0}, - {33868800, 22050, 44100, 0x2, 5, 3333}, - /* 32k */ - {12000000, 32000, 48000, 0x1, 16, 3840}, - {19200000, 32000, 48000, 0x1, 10, 2400}, - {22579200, 32000, 48000, 0x1, 8, 7075}, - {33868800, 32000, 48000, 0x1, 5, 8049}, - /* 44.1k */ - {12000000, 44100, 44100, 0x0, 15, 528}, - {19200000, 44100, 44100, 0x0, 9, 4080}, - {22579200, 44100, 44100, 0x0, 8, 0}, - {33868800, 44100, 44100, 0x0, 5, 3333}, - /* 48k */ - {12000000, 48000, 48000, 0x0, 16, 3840}, - {19200000, 48000, 48000, 0x0, 10, 2400}, - {22579200, 48000, 48000, 0x0, 8, 7075}, - {33868800, 48000, 48000, 0x0, 5, 8049}, - /* 64k */ - {12000000, 64000, 96000, 0x1, 16, 3840}, - {19200000, 64000, 96000, 0x1, 10, 2400}, - {22579200, 64000, 96000, 0x1, 8, 7075}, - {33868800, 64000, 96000, 0x1, 5, 8049}, - /* 88.2k */ - {12000000, 88200, 88200, 0x0, 15, 528}, - {19200000, 88200, 88200, 0x0, 9, 4080}, - {22579200, 88200, 88200, 0x0, 8, 0}, - {33868800, 88200, 88200, 0x0, 5, 3333}, - /* 96k */ - {12000000, 96000, 96000, 0x0, 16, 3840}, - {19200000, 96000, 96000, 0x0, 10, 2400}, - {22579200, 96000, 96000, 0x0, 8, 7075}, - {33868800, 96000, 96000, 0x0, 5, 8049}, -}; - -static inline int aic3x_get_divs(int mclk, int rate) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(aic3x_divs); i++) { - if (aic3x_divs[i].rate == rate && aic3x_divs[i].mclk == mclk) - return i; - } - - return 0; -} - static int aic3x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -730,49 +701,107 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->codec; struct aic3x_priv *aic3x = codec->private_data; - int i; - u8 data, pll_p, pll_r, pll_j; - u16 pll_d; - - i = aic3x_get_divs(aic3x->sysclk, params_rate(params)); + int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; + u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; + u16 pll_d = 1; - /* Route Left DAC to left channel input and - * right DAC to right channel input */ - data = (LDAC2LCH | RDAC2RCH); - switch (aic3x_divs[i].fsref_reg) { - case 44100: - data |= FSREF_44100; + /* select data word length */ + data = + aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4)); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: break; - case 48000: - data |= FSREF_48000; + case SNDRV_PCM_FORMAT_S20_3LE: + data |= (0x01 << 4); break; - case 88200: - data |= FSREF_44100 | DUAL_RATE_MODE; + case SNDRV_PCM_FORMAT_S24_LE: + data |= (0x02 << 4); break; - case 96000: - data |= FSREF_48000 | DUAL_RATE_MODE; + case SNDRV_PCM_FORMAT_S32_LE: + data |= (0x03 << 4); break; } + aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, data); + + /* Fsref can be 44100 or 48000 */ + fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000; + + /* Try to find a value for Q which allows us to bypass the PLL and + * generate CODEC_CLK directly. */ + for (pll_q = 2; pll_q < 18; pll_q++) + if (aic3x->sysclk / (128 * pll_q) == fsref) { + bypass_pll = 1; + break; + } + + if (bypass_pll) { + pll_q &= 0xf; + aic3x_write(codec, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT); + aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV); + } else + aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV); + + /* Route Left DAC to left channel input and + * right DAC to right channel input */ + data = (LDAC2LCH | RDAC2RCH); + data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000; + if (params_rate(params) >= 64000) + data |= DUAL_RATE_MODE; aic3x_write(codec, AIC3X_CODEC_DATAPATH_REG, data); /* codec sample rate select */ - data = aic3x_divs[i].sr_reg; + data = (fsref * 20) / params_rate(params); + if (params_rate(params) < 64000) + data /= 2; + data /= 5; + data -= 2; data |= (data << 4); aic3x_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data); - /* Use PLL for generation Fsref by equation: - * Fsref = (MCLK * K * R)/(2048 * P); - * Fix P = 2 and R = 1 and calculate K, if - * K = J.D, i.e. J - an interger portion of K and D is the fractional - * one with 4 digits of precision; - * Example: - * For MCLK = 22.5792 MHz and Fsref = 48kHz: - * Select P = 2, R= 1, K = 8.7074, which results in J = 8, D = 7074 + if (bypass_pll) + return 0; + + /* Use PLL + * find an apropriate setup for j, d, r and p by iterating over + * p and r - j and d are calculated for each fraction. + * Up to 128 values are probed, the closest one wins the game. + * The sysclk is divided by 1000 to prevent integer overflows. */ - pll_p = 2; - pll_r = 1; - pll_j = aic3x_divs[i].pllj_reg; - pll_d = aic3x_divs[i].plld_reg; + codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000); + + for (r = 1; r <= 16; r++) + for (p = 1; p <= 8; p++) { + int clk, tmp = (codec_clk * pll_r * 10) / pll_p; + u8 j = tmp / 10000; + u16 d = tmp % 10000; + + if (j > 63) + continue; + + if (d != 0 && aic3x->sysclk < 10000000) + continue; + + /* This is actually 1000 * ((j + (d/10000)) * r) / p + * The term had to be converted to get rid of the + * division by 10000 */ + clk = ((10000 * j * r) + (d * r)) / (10 * p); + + /* check whether this values get closer than the best + * ones we had before */ + if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) { + pll_j = j; pll_d = d; pll_r = r; pll_p = p; + last_clk = clk; + } + + /* Early exit for exact matches */ + if (clk == codec_clk) + break; + } + + if (last_clk == 0) { + printk(KERN_ERR "%s(): unable to setup PLL\n", __func__); + return -EINVAL; + } data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT)); @@ -782,28 +811,10 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, aic3x_write(codec, AIC3X_PLL_PROGD_REG, (pll_d & 0x3F) << PLLD_LSB_SHIFT); - /* select data word length */ - data = - aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4)); - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - break; - case SNDRV_PCM_FORMAT_S20_3LE: - data |= (0x01 << 4); - break; - case SNDRV_PCM_FORMAT_S24_LE: - data |= (0x02 << 4); - break; - case SNDRV_PCM_FORMAT_S32_LE: - data |= (0x03 << 4); - break; - } - aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, data); - return 0; } -static int aic3x_mute(struct snd_soc_codec_dai *dai, int mute) +static int aic3x_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u8 ldac_reg = aic3x_read_reg_cache(codec, LDAC_VOL) & ~MUTE_ON; @@ -820,31 +831,25 @@ static int aic3x_mute(struct snd_soc_codec_dai *dai, int mute) return 0; } -static int aic3x_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int aic3x_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 aic3x_priv *aic3x = codec->private_data; - switch (freq) { - case 12000000: - case 19200000: - case 22579200: - case 33868800: - aic3x->sysclk = freq; - return 0; - } - - return -EINVAL; + aic3x->sysclk = freq; + return 0; } -static int aic3x_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; struct aic3x_priv *aic3x = codec->private_data; - u8 iface_areg = 0; - u8 iface_breg = 0; + u8 iface_areg, iface_breg; + + iface_areg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f; + iface_breg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -883,13 +888,14 @@ static int aic3x_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return 0; } -static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) +static int aic3x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { struct aic3x_priv *aic3x = codec->private_data; u8 reg; - switch (event) { - case SNDRV_CTL_POWER_D0: + switch (level) { + case SND_SOC_BIAS_ON: /* all power is driven by DAPM system */ if (aic3x->master) { /* enable pll */ @@ -898,10 +904,9 @@ static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) reg | PLL_ENABLE); } break; - case SNDRV_CTL_POWER_D1: - case SNDRV_CTL_POWER_D2: + case SND_SOC_BIAS_PREPARE: break; - case SNDRV_CTL_POWER_D3hot: + case SND_SOC_BIAS_STANDBY: /* * all power is driven by DAPM system, * so output power is safe if bypass was set @@ -913,7 +918,7 @@ static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) reg & ~PLL_ENABLE); } break; - case SNDRV_CTL_POWER_D3cold: + case SND_SOC_BIAS_OFF: /* force all power off */ reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL); aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON); @@ -949,16 +954,43 @@ static int aic3x_dapm_event(struct snd_soc_codec *codec, int event) } break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } +void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state) +{ + u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG; + u8 bit = gpio ? 3: 0; + u8 val = aic3x_read_reg_cache(codec, reg) & ~(1 << bit); + aic3x_write(codec, reg, val | (!!state << bit)); +} +EXPORT_SYMBOL_GPL(aic3x_set_gpio); + +int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio) +{ + u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG; + u8 val, bit = gpio ? 2: 1; + + aic3x_read(codec, reg, &val); + return (val >> bit) & 1; +} +EXPORT_SYMBOL_GPL(aic3x_get_gpio); + +int aic3x_headset_detected(struct snd_soc_codec *codec) +{ + u8 val; + aic3x_read(codec, AIC3X_RT_IRQ_FLAGS_REG, &val); + return (val >> 2) & 1; +} +EXPORT_SYMBOL_GPL(aic3x_headset_detected); + #define AIC3X_RATES SNDRV_PCM_RATE_8000_96000 #define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) -struct snd_soc_codec_dai aic3x_dai = { +struct snd_soc_dai aic3x_dai = { .name = "aic3x", .playback = { .stream_name = "Playback", @@ -988,7 +1020,7 @@ static int aic3x_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; - aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -1008,7 +1040,7 @@ static int aic3x_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - aic3x_dapm_event(codec, codec->suspend_dapm_state); + aic3x_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -1020,16 +1052,17 @@ static int aic3x_resume(struct platform_device *pdev) static int aic3x_init(struct snd_soc_device *socdev) { struct snd_soc_codec *codec = socdev->codec; + struct aic3x_setup_data *setup = socdev->codec_data; int reg, ret = 0; codec->name = "aic3x"; codec->owner = THIS_MODULE; codec->read = aic3x_read_reg_cache; codec->write = aic3x_write; - codec->dapm_event = aic3x_dapm_event; + codec->set_bias_level = aic3x_set_bias_level; codec->dai = &aic3x_dai; codec->num_dai = 1; - codec->reg_cache_size = sizeof(aic3x_reg); + codec->reg_cache_size = ARRAY_SIZE(aic3x_reg); codec->reg_cache = kmemdup(aic3x_reg, sizeof(aic3x_reg), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; @@ -1108,7 +1141,11 @@ static int aic3x_init(struct snd_soc_device *socdev) aic3x_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL); /* off, with power on */ - aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* setup GPIO functions */ + aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4); + aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4); aic3x_add_controls(codec); aic3x_add_widgets(codec); @@ -1217,6 +1254,12 @@ static struct i2c_client client_template = { .name = "AIC3X", .driver = &aic3x_i2c_driver, }; + +static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len) +{ + value[0] = i2c_smbus_read_byte_data(client, value[0]); + return (len == 1); +} #endif static int aic3x_probe(struct platform_device *pdev) @@ -1251,6 +1294,7 @@ static int aic3x_probe(struct platform_device *pdev) if (setup->i2c_address) { normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t) i2c_master_send; + codec->hw_read = (hw_read_t) aic3x_i2c_read; ret = i2c_add_driver(&aic3x_i2c_driver); if (ret != 0) printk(KERN_ERR "can't add i2c driver"); @@ -1268,7 +1312,7 @@ static int aic3x_remove(struct platform_device *pdev) /* power down chip */ if (codec->control_data) - aic3x_dapm_event(codec, SNDRV_CTL_POWER_D3); + aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index d0cdeeb..d76c079 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -37,6 +37,8 @@ #define AIC3X_ASD_INTF_CTRLB 9 /* Audio overflow status and PLL R value programming register */ #define AIC3X_OVRF_STATUS_AND_PLLR_REG 11 +/* Audio codec digital filter control register */ +#define AIC3X_CODEC_DFILT_CTRL 12 /* ADC PGA Gain control registers */ #define LADC_VOL 15 @@ -108,6 +110,13 @@ #define DACR1_2_RLOPM_VOL 92 #define LLOPM_CTRL 86 #define RLOPM_CTRL 93 +/* GPIO/IRQ registers */ +#define AIC3X_STICKY_IRQ_FLAGS_REG 96 +#define AIC3X_RT_IRQ_FLAGS_REG 97 +#define AIC3X_GPIO1_REG 98 +#define AIC3X_GPIO2_REG 99 +#define AIC3X_GPIOA_REG 100 +#define AIC3X_GPIOB_REG 101 /* Clock generation control register */ #define AIC3X_CLKGEN_CTRL_REG 102 @@ -128,12 +137,15 @@ /* PLL registers bitfields */ #define PLLP_SHIFT 0 +#define PLLQ_SHIFT 3 #define PLLR_SHIFT 0 #define PLLJ_SHIFT 2 #define PLLD_MSB_SHIFT 0 #define PLLD_LSB_SHIFT 2 /* Clock generation register bits */ +#define CODEC_CLKIN_PLLDIV 0 +#define CODEC_CLKIN_CLKDIV 1 #define PLL_CLKIN_SHIFT 4 #define MCLK_SOURCE 0x0 #define PLL_CLKDIV_SHIFT 0 @@ -171,11 +183,52 @@ /* Default input volume */ #define DEFAULT_GAIN 0x20 +/* GPIO API */ +enum { + AIC3X_GPIO1_FUNC_DISABLED = 0, + AIC3X_GPIO1_FUNC_AUDIO_WORDCLK_ADC = 1, + AIC3X_GPIO1_FUNC_CLOCK_MUX = 2, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV2 = 3, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV4 = 4, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV8 = 5, + AIC3X_GPIO1_FUNC_SHORT_CIRCUIT_IRQ = 6, + AIC3X_GPIO1_FUNC_AGC_NOISE_IRQ = 7, + AIC3X_GPIO1_FUNC_INPUT = 8, + AIC3X_GPIO1_FUNC_OUTPUT = 9, + AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK = 10, + AIC3X_GPIO1_FUNC_AUDIO_WORDCLK = 11, + AIC3X_GPIO1_FUNC_BUTTON_IRQ = 12, + AIC3X_GPIO1_FUNC_HEADSET_DETECT_IRQ = 13, + AIC3X_GPIO1_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 14, + AIC3X_GPIO1_FUNC_ALL_IRQ = 16 +}; + +enum { + AIC3X_GPIO2_FUNC_DISABLED = 0, + AIC3X_GPIO2_FUNC_HEADSET_DETECT_IRQ = 2, + AIC3X_GPIO2_FUNC_INPUT = 3, + AIC3X_GPIO2_FUNC_OUTPUT = 4, + AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT = 5, + AIC3X_GPIO2_FUNC_AUDIO_BITCLK = 8, + AIC3X_GPIO2_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 9, + AIC3X_GPIO2_FUNC_ALL_IRQ = 10, + AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_OR_AGC_IRQ = 11, + AIC3X_GPIO2_FUNC_HEADSET_OR_BUTTON_PRESS_OR_SHORT_CIRCUIT_IRQ = 12, + AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_IRQ = 13, + AIC3X_GPIO2_FUNC_AGC_NOISE_IRQ = 14, + AIC3X_GPIO2_FUNC_BUTTON_PRESS_IRQ = 15 +}; + +void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state); +int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio); +int aic3x_headset_detected(struct snd_soc_codec *codec); + struct aic3x_setup_data { unsigned short i2c_address; + unsigned int gpio_func[2]; }; -extern struct snd_soc_codec_dai aic3x_dai; +extern struct snd_soc_dai aic3x_dai; extern struct snd_soc_codec_device soc_codec_dev_aic3x; #endif /* _AIC3X_H */ diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c new file mode 100644 index 0000000..a52d6d9 --- /dev/null +++ b/sound/soc/codecs/uda1380.c @@ -0,0 +1,852 @@ +/* + * uda1380.c - Philips UDA1380 ALSA SoC audio driver + * + * 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. + * + * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com> + * Improved support for DAPM and audio routing/mixing capabilities, + * added TLV support. + * + * Modified by Richard Purdie <richard@openedhand.com> to fit into SoC + * codec model. + * + * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org> + * Copyright 2005 Openedhand Ltd. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +#include "uda1380.h" + +#define UDA1380_VERSION "0.6" +#define AUDIO_NAME "uda1380" + +/* + * uda1380 register cache + */ +static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = { + 0x0502, 0x0000, 0x0000, 0x3f3f, + 0x0202, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0xff00, 0x0000, 0x4800, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x8000, 0x0002, 0x0000, +}; + +/* + * read uda1380 register cache + */ +static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == UDA1380_RESET) + return 0; + if (reg >= UDA1380_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write uda1380 register cache + */ +static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= UDA1380_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the UDA1380 register space + */ +static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + /* data is + * data[0] is register offset + * data[1] is MS byte + * data[2] is LS byte + */ + data[0] = reg; + data[1] = (value & 0xff00) >> 8; + data[2] = value & 0x00ff; + + uda1380_write_reg_cache(codec, reg, value); + + /* the interpolator & decimator regs must only be written when the + * codec DAI is active. + */ + if (!codec->active && (reg >= UDA1380_MVOL)) + return 0; + pr_debug("uda1380: hw write %x val %x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) { + unsigned int val; + i2c_master_send(codec->control_data, data, 1); + i2c_master_recv(codec->control_data, data, 2); + val = (data[0]<<8) | data[1]; + if (val != value) { + pr_debug("uda1380: READ BACK VAL %x\n", + (data[0]<<8) | data[1]); + return -EIO; + } + return 0; + } else + return -EIO; +} + +#define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0) + +/* declarations of ALSA reg_elem_REAL controls */ +static const char *uda1380_deemp[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", + "96kHz", +}; +static const char *uda1380_input_sel[] = { + "Line", + "Mic + Line R", + "Line L", + "Mic", +}; +static const char *uda1380_output_sel[] = { + "DAC", + "Analog Mixer", +}; +static const char *uda1380_spf_mode[] = { + "Flat", + "Minimum1", + "Minimum2", + "Maximum" +}; +static const char *uda1380_capture_sel[] = { + "ADC", + "Digital Mixer" +}; +static const char *uda1380_sel_ns[] = { + "3rd-order", + "5th-order" +}; +static const char *uda1380_mix_control[] = { + "off", + "PCM only", + "before sound processing", + "after sound processing" +}; +static const char *uda1380_sdet_setting[] = { + "3200", + "4800", + "9600", + "19200" +}; +static const char *uda1380_os_setting[] = { + "single-speed", + "double-speed (no mixing)", + "quad-speed (no mixing)" +}; + +static const struct soc_enum uda1380_deemp_enum[] = { + SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, 5, uda1380_deemp), + SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, 5, uda1380_deemp), +}; +static const struct soc_enum uda1380_input_sel_enum = + SOC_ENUM_SINGLE(UDA1380_ADC, 2, 4, uda1380_input_sel); /* SEL_MIC, SEL_LNA */ +static const struct soc_enum uda1380_output_sel_enum = + SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel); /* R02_EN_AVC */ +static const struct soc_enum uda1380_spf_enum = + SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode); /* M */ +static const struct soc_enum uda1380_capture_sel_enum = + SOC_ENUM_SINGLE(UDA1380_IFACE, 6, 2, uda1380_capture_sel); /* SEL_SOURCE */ +static const struct soc_enum uda1380_sel_ns_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 14, 2, uda1380_sel_ns); /* SEL_NS */ +static const struct soc_enum uda1380_mix_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 12, 4, uda1380_mix_control); /* MIX, MIX_POS */ +static const struct soc_enum uda1380_sdet_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 4, 4, uda1380_sdet_setting); /* SD_VALUE */ +static const struct soc_enum uda1380_os_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 0, 3, uda1380_os_setting); /* OS */ + +/* + * from -48 dB in 1.5 dB steps (mute instead of -49.5 dB) + */ +static DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1); + +/* + * from -78 dB in 1 dB steps (3 dB steps, really. LSB are ignored), + * from -66 dB in 0.5 dB steps (2 dB steps, really) and + * from -52 dB in 0.25 dB steps + */ +static const unsigned int mvol_tlv[] = { + TLV_DB_RANGE_HEAD(3), + 0, 15, TLV_DB_SCALE_ITEM(-8200, 100, 1), + 16, 43, TLV_DB_SCALE_ITEM(-6600, 50, 0), + 44, 252, TLV_DB_SCALE_ITEM(-5200, 25, 0), +}; + +/* + * from -72 dB in 1.5 dB steps (6 dB steps really), + * from -66 dB in 0.75 dB steps (3 dB steps really), + * from -60 dB in 0.5 dB steps (2 dB steps really) and + * from -46 dB in 0.25 dB steps + */ +static const unsigned int vc_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 7, TLV_DB_SCALE_ITEM(-7800, 150, 1), + 8, 15, TLV_DB_SCALE_ITEM(-6600, 75, 0), + 16, 43, TLV_DB_SCALE_ITEM(-6000, 50, 0), + 44, 228, TLV_DB_SCALE_ITEM(-4600, 25, 0), +}; + +/* from 0 to 6 dB in 2 dB steps if SPF mode != flat */ +static DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0); + +/* from 0 to 24 dB in 2 dB steps, if SPF mode == maximum, otherwise cuts + * off at 18 dB max) */ +static DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0); + +/* from -63 to 24 dB in 0.5 dB steps (-128...48) */ +static DECLARE_TLV_DB_SCALE(dec_tlv, -6400, 50, 1); + +/* from 0 to 24 dB in 3 dB steps */ +static DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0); + +/* from 0 to 30 dB in 2 dB steps */ +static DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0); + +static const struct snd_kcontrol_new uda1380_snd_controls[] = { + SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv), /* AVCR, AVCL */ + SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv), /* MVCL, MVCR */ + SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv), /* VC2 */ + SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv), /* VC1 */ + SOC_ENUM("Sound Processing Filter", uda1380_spf_enum), /* M */ + SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), /* TRL, TRR */ + SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv), /* BBL, BBR */ +/**/ SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1), /* MTM */ + SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1), /* MT2 from decimation filter */ + SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]), /* DE2 */ + SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1), /* MT1, from digital data input */ + SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]), /* DE1 */ + SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */ + SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */ + SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */ + SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */ + SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */ + SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */ + SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */ + SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv), /* ML_DEC, MR_DEC */ +/**/ SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1), /* MT_ADC */ + SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */ + SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0), /* ADCPOL_INV */ + SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv), /* VGA_CTRL */ + SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0), /* SKIP_DCFIL (before decimator) */ + SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0), /* EN_DCFIL (at output of decimator) */ + SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), /* TODO: enum, see table 62 */ + SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), /* AGC_LEVEL */ + /* -5.5, -8, -11.5, -14 dBFS */ + SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0), +}; + +/* add non dapm controls */ +static int uda1380_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&uda1380_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Input mux */ +static const struct snd_kcontrol_new uda1380_input_mux_control = + SOC_DAPM_ENUM("Route", uda1380_input_sel_enum); + +/* Output mux */ +static const struct snd_kcontrol_new uda1380_output_mux_control = + SOC_DAPM_ENUM("Route", uda1380_output_sel_enum); + +/* Capture mux */ +static const struct snd_kcontrol_new uda1380_capture_mux_control = + SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum); + + +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &uda1380_input_mux_control), + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0, + &uda1380_output_mux_control), + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, + &uda1380_capture_mux_control), + SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0), + SND_SOC_DAPM_INPUT("VINM"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("VOUTLHP"), + SND_SOC_DAPM_OUTPUT("VOUTRHP"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0), + SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + + /* output mux */ + {"HeadPhone Driver", NULL, "Output Mux"}, + {"VOUTR", NULL, "Output Mux"}, + {"VOUTL", NULL, "Output Mux"}, + + {"Analog Mixer", NULL, "VINR"}, + {"Analog Mixer", NULL, "VINL"}, + {"Analog Mixer", NULL, "DAC"}, + + {"Output Mux", "DAC", "DAC"}, + {"Output Mux", "Analog Mixer", "Analog Mixer"}, + + /* {"DAC", "Digital Mixer", "I2S" } */ + + /* headphone driver */ + {"VOUTLHP", NULL, "HeadPhone Driver"}, + {"VOUTRHP", NULL, "HeadPhone Driver"}, + + /* input mux */ + {"Left ADC", NULL, "Input Mux"}, + {"Input Mux", "Mic", "Mic LNA"}, + {"Input Mux", "Mic + Line R", "Mic LNA"}, + {"Input Mux", "Line L", "Left PGA"}, + {"Input Mux", "Line", "Left PGA"}, + + /* right input */ + {"Right ADC", "Mic + Line R", "Right PGA"}, + {"Right ADC", "Line", "Right PGA"}, + + /* inputs */ + {"Mic LNA", NULL, "VINM"}, + {"Left PGA", NULL, "VINL"}, + {"Right PGA", NULL, "VINR"}, +}; + +static int uda1380_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets, + ARRAY_SIZE(uda1380_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int iface; + + /* set up DAI based upon fmt */ + iface = uda1380_read_reg_cache(codec, UDA1380_IFACE); + iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK); + + /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORI_I2S | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORI_LSB16 | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORI_MSB | R01_SFORO_I2S; + } + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) + iface |= R01_SIM; + + uda1380_write(codec, UDA1380_IFACE, iface); + + return 0; +} + +/* + * Flush reg cache + * We can only write the interpolator and decimator registers + * when the DAI is being clocked by the CPU DAI. It's up to the + * machine and cpu DAI driver to do this before we are called. + */ +static int uda1380_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg, reg_start, reg_end, clk; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg_start = UDA1380_MVOL; + reg_end = UDA1380_MIXER; + } else { + reg_start = UDA1380_DEC; + reg_end = UDA1380_AGC; + } + + /* FIXME disable DAC_CLK */ + clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK); + + for (reg = reg_start; reg <= reg_end; reg++) { + pr_debug("uda1380: flush reg %x val %x:", reg, + uda1380_read_reg_cache(codec, reg)); + uda1380_write(codec, reg, uda1380_read_reg_cache(codec, reg)); + } + + /* FIXME enable DAC_CLK */ + uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK); + + return 0; +} + +static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* set WSPLL power and divider if running from this clock */ + if (clk & R00_DAC_CLK) { + int rate = params_rate(params); + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + clk &= ~0x3; /* clear SEL_LOOP_DIV */ + switch (rate) { + case 6250 ... 12500: + clk |= 0x0; + break; + case 12501 ... 25000: + clk |= 0x1; + break; + case 25001 ... 50000: + clk |= 0x2; + break; + case 50001 ... 100000: + clk |= 0x3; + break; + } + uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk |= R00_EN_DAC | R00_EN_INT; + else + clk |= R00_EN_ADC | R00_EN_DEC; + + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* shut down WSPLL power if running from this clock */ + if (clk & R00_DAC_CLK) { + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk &= ~(R00_EN_DAC | R00_EN_INT); + else + clk &= ~(R00_EN_ADC | R00_EN_DEC); + + uda1380_write(codec, UDA1380_CLK, clk); +} + +static int uda1380_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM; + + /* FIXME: mute(codec,0) is called when the magician clock is already + * set to WSPLL, but for some unknown reason writing to interpolator + * registers works only when clocked by SYSCLK */ + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk); + if (mute) + uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM); + else + uda1380_write(codec, UDA1380_DEEMP, mute_reg); + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static int uda1380_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int pm = uda1380_read_reg_cache(codec, UDA1380_PM); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm); + break; + case SND_SOC_BIAS_STANDBY: + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS); + break; + case SND_SOC_BIAS_OFF: + uda1380_write(codec, UDA1380_PM, 0x0); + break; + } + codec->bias_level = level; + return 0; +} + +#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_dai uda1380_dai[] = { +{ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{ /* playback only - dual interface */ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{ /* capture only - dual interface*/ + .name = "UDA1380", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .set_fmt = uda1380_set_dai_fmt, + }, +}, +}; +EXPORT_SYMBOL_GPL(uda1380_dai); + +static int uda1380_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int uda1380_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(uda1380_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + uda1380_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the UDA1380 driver + * register mixer and dsp interfaces with the kernel + */ +static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "UDA1380"; + codec->owner = THIS_MODULE; + codec->read = uda1380_read_reg_cache; + codec->write = uda1380_write; + codec->set_bias_level = uda1380_set_bias_level; + codec->dai = uda1380_dai; + codec->num_dai = ARRAY_SIZE(uda1380_dai); + codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + codec->reg_cache_size = ARRAY_SIZE(uda1380_reg); + codec->reg_cache_step = 1; + uda1380_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("uda1380: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + /* set clock input */ + switch (dac_clk) { + case UDA1380_DAC_CLK_SYSCLK: + uda1380_write(codec, UDA1380_CLK, 0); + break; + case UDA1380_DAC_CLK_WSPLL: + uda1380_write(codec, UDA1380_CLK, R00_DAC_CLK); + break; + } + + /* uda1380 init */ + uda1380_add_controls(codec); + uda1380_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + pr_err("uda1380: 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; +} + +static struct snd_soc_device *uda1380_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +#define I2C_DRIVERID_UDA1380 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver uda1380_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int uda1380_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = uda1380_socdev; + struct uda1380_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + pr_err("uda1380: failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = uda1380_init(socdev, setup->dac_clk); + if (ret < 0) { + pr_err("uda1380: failed to initialise UDA1380\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int uda1380_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int uda1380_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, uda1380_codec_probe); +} + +static struct i2c_driver uda1380_i2c_driver = { + .driver = { + .name = "UDA1380 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_UDA1380, + .attach_adapter = uda1380_i2c_attach, + .detach_client = uda1380_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "UDA1380", + .driver = &uda1380_i2c_driver, +}; +#endif + +static int uda1380_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct uda1380_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + uda1380_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&uda1380_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int uda1380_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + uda1380_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(&uda1380_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_uda1380 = { + .probe = uda1380_probe, + .remove = uda1380_remove, + .suspend = uda1380_suspend, + .resume = uda1380_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); + +MODULE_AUTHOR("Giorgio Padrin"); +MODULE_DESCRIPTION("Audio support for codec Philips UDA1380"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h new file mode 100644 index 0000000..50c603e --- /dev/null +++ b/sound/soc/codecs/uda1380.h @@ -0,0 +1,89 @@ +/* + * Audio support for Philips UDA1380 + * + * 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. + * + * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org> + */ + +#ifndef _UDA1380_H +#define _UDA1380_H + +#define UDA1380_CLK 0x00 +#define UDA1380_IFACE 0x01 +#define UDA1380_PM 0x02 +#define UDA1380_AMIX 0x03 +#define UDA1380_HP 0x04 +#define UDA1380_MVOL 0x10 +#define UDA1380_MIXVOL 0x11 +#define UDA1380_MODE 0x12 +#define UDA1380_DEEMP 0x13 +#define UDA1380_MIXER 0x14 +#define UDA1380_INTSTAT 0x18 +#define UDA1380_DEC 0x20 +#define UDA1380_PGA 0x21 +#define UDA1380_ADC 0x22 +#define UDA1380_AGC 0x23 +#define UDA1380_DECSTAT 0x28 +#define UDA1380_RESET 0x7f + +#define UDA1380_CACHEREGNUM 0x24 + +/* Register flags */ +#define R00_EN_ADC 0x0800 +#define R00_EN_DEC 0x0400 +#define R00_EN_DAC 0x0200 +#define R00_EN_INT 0x0100 +#define R00_DAC_CLK 0x0010 +#define R01_SFORI_I2S 0x0000 +#define R01_SFORI_LSB16 0x0100 +#define R01_SFORI_LSB18 0x0200 +#define R01_SFORI_LSB20 0x0300 +#define R01_SFORI_MSB 0x0500 +#define R01_SFORI_MASK 0x0700 +#define R01_SFORO_I2S 0x0000 +#define R01_SFORO_LSB16 0x0001 +#define R01_SFORO_LSB18 0x0002 +#define R01_SFORO_LSB20 0x0003 +#define R01_SFORO_LSB24 0x0004 +#define R01_SFORO_MSB 0x0005 +#define R01_SFORO_MASK 0x0007 +#define R01_SEL_SOURCE 0x0040 +#define R01_SIM 0x0010 +#define R02_PON_PLL 0x8000 +#define R02_PON_HP 0x2000 +#define R02_PON_DAC 0x0400 +#define R02_PON_BIAS 0x0100 +#define R02_EN_AVC 0x0080 +#define R02_PON_AVC 0x0040 +#define R02_PON_LNA 0x0010 +#define R02_PON_PGAL 0x0008 +#define R02_PON_ADCL 0x0004 +#define R02_PON_PGAR 0x0002 +#define R02_PON_ADCR 0x0001 +#define R13_MTM 0x4000 +#define R14_SILENCE 0x0080 +#define R14_SDET_ON 0x0040 +#define R21_MT_ADC 0x8000 +#define R22_SEL_LNA 0x0008 +#define R22_SEL_MIC 0x0004 +#define R22_SKIP_DCFIL 0x0002 +#define R23_AGC_EN 0x0001 + +struct uda1380_setup_data { + unsigned short i2c_address; + int dac_clk; +#define UDA1380_DAC_CLK_SYSCLK 0 +#define UDA1380_DAC_CLK_WSPLL 1 +}; + +#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */ +#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */ +#define UDA1380_DAI_CAPTURE 2 /* capture DAI */ + +extern struct snd_soc_dai uda1380_dai[3]; +extern struct snd_soc_codec_device soc_codec_dev_uda1380; + +#endif /* _UDA1380_H */ diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c new file mode 100644 index 0000000..67325fd --- /dev/null +++ b/sound/soc/codecs/wm8510.c @@ -0,0 +1,817 @@ +/* + * wm8510.c -- WM8510 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * + * 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/kernel.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 "wm8510.h" + +#define AUDIO_NAME "wm8510" +#define WM8510_VERSION "0.6" + +struct snd_soc_codec_device soc_codec_dev_wm8510; + +/* + * wm8510 register cache + * We can't read the WM8510 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8510_reg[WM8510_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0050, 0x0000, 0x0140, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ff, + 0x0000, 0x0000, 0x0100, 0x00ff, + 0x0000, 0x0000, 0x012c, 0x002c, + 0x002c, 0x002c, 0x002c, 0x0000, + 0x0032, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0038, 0x000b, 0x0032, 0x0000, + 0x0008, 0x000c, 0x0093, 0x00e9, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0003, 0x0010, 0x0000, 0x0000, + 0x0000, 0x0002, 0x0001, 0x0000, + 0x0000, 0x0000, 0x0039, 0x0000, + 0x0001, +}; + +/* + * read wm8510 register cache + */ +static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8510_RESET) + return 0; + if (reg >= WM8510_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8510 register cache + */ +static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8510_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8510 register space + */ +static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8510 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8510_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0) + +static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" }; +static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8510_alc[] = { "ALC", "Limiter" }; + +static const struct soc_enum wm8510_enum[] = { + SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */ + SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */ + SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp), + SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc), +}; + +static const struct snd_kcontrol_new wm8510_snd_controls[] = { + +SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0), + +SOC_ENUM("DAC Companding", wm8510_enum[1]), +SOC_ENUM("ADC Companding", wm8510_enum[0]), + +SOC_ENUM("Playback De-emphasis", wm8510_enum[2]), +SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0), + +SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), + +SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8510_enum[3]), +SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0), +SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0), + +SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0), +SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1), +SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0), +SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0), + +SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0), +SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1), +}; + +/* add non dapm controls */ +static int wm8510_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8510_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8510_snd_controls[i], codec, + NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8510_boost_controls[] = { +SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 0), +SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0), +SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0), +}; + +static const struct snd_kcontrol_new wm8510_micpga_controls[] = { +SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0), +SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0), +SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0, + &wm8510_speaker_mixer_controls[0], + ARRAY_SIZE(wm8510_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0, + &wm8510_mono_mixer_controls[0], + ARRAY_SIZE(wm8510_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Mic PGA", WM8510_POWER2, 2, 0, + &wm8510_micpga_controls[0], + ARRAY_SIZE(wm8510_micpga_controls)), +SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0, + &wm8510_boost_controls[0], + ARRAY_SIZE(wm8510_boost_controls)), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Microphone PGA */ + {"Mic PGA", "MICN Switch", "MICN"}, + {"Mic PGA", "MICP Switch", "MICP"}, + { "Mic PGA", "AUX Switch", "Aux Input" }, + + /* Boost Mixer */ + {"Boost Mixer", "Mic PGA Switch", "Mic PGA"}, + {"Boost Mixer", "Mic Volume", "MICP"}, + {"Boost Mixer", "Aux Volume", "Aux Input"}, + + {"ADC", NULL, "Boost Mixer"}, +}; + +static int wm8510_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8510_dapm_widgets, + ARRAY_SIZE(wm8510_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct pll_ { + unsigned int pre_div:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div.pre_div = 1; + Ndiv = target / source; + } else + pll_div.pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8510 N value %d outwith recommended range!d\n", + Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +static int wm8510_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; + u16 reg; + + if (freq_in == 0 || freq_out == 0) { + /* Clock CODEC directly from MCLK */ + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK); + wm8510_write(codec, WM8510_CLOCK, reg & 0x0ff); + + /* Turn off PLL */ + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg & 0x1df); + return 0; + } + + pll_factors(freq_out*8, freq_in); + + wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); + wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18); + wm8510_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff); + wm8510_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff); + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg | 0x020); + + /* Run CODEC from PLL instead of MCLK */ + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK); + wm8510_write(codec, WM8510_CLOCK, reg | 0x100); + + return 0; +} + +/* + * Configure WM8510 clock dividers. + */ +static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8510_OPCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_GPIO) & 0x1cf; + wm8510_write(codec, WM8510_GPIO, reg | div); + break; + case WM8510_MCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1f; + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + case WM8510_ADCCLK: + reg = wm8510_read_reg_cache(codec, WM8510_ADC) & 0x1f7; + wm8510_write(codec, WM8510_ADC, reg | div); + break; + case WM8510_DACCLK: + reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0x1f7; + wm8510_write(codec, WM8510_DAC, reg | div); + break; + case WM8510_BCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1e3; + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_CLOCK, clk); + return 0; +} + +static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f; + u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0040; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x0060; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + adn |= 0x5 << 1; + break; + case SNDRV_PCM_RATE_11025: + adn |= 0x4 << 1; + break; + case SNDRV_PCM_RATE_16000: + adn |= 0x3 << 1; + break; + case SNDRV_PCM_RATE_22050: + adn |= 0x2 << 1; + break; + case SNDRV_PCM_RATE_32000: + adn |= 0x1 << 1; + break; + case SNDRV_PCM_RATE_44100: + case SNDRV_PCM_RATE_48000: + break; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_ADD, adn); + return 0; +} + +static int wm8510_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf; + + if (mute) + wm8510_write(codec, WM8510_DAC, mute_reg | 0x40); + else + wm8510_write(codec, WM8510_DAC, mute_reg); + return 0; +} + +/* liam need to make this lower power with dapm */ +static int wm8510_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + + switch (level) { + case SND_SOC_BIAS_ON: + wm8510_write(codec, WM8510_POWER1, 0x1ff); + wm8510_write(codec, WM8510_POWER2, 0x1ff); + wm8510_write(codec, WM8510_POWER3, 0x1ff); + break; + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + wm8510_write(codec, WM8510_POWER1, 0x0); + wm8510_write(codec, WM8510_POWER2, 0x0); + wm8510_write(codec, WM8510_POWER3, 0x0); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai wm8510_dai = { + .name = "WM8510 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .ops = { + .hw_params = wm8510_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8510_mute, + .set_fmt = wm8510_set_dai_fmt, + .set_clkdiv = wm8510_set_dai_clkdiv, + .set_pll = wm8510_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8510_dai); + +static int wm8510_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8510_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8510_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the WM8510 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8510_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8510"; + codec->owner = THIS_MODULE; + codec->read = wm8510_read_reg_cache; + codec->write = wm8510_write; + codec->set_bias_level = wm8510_set_bias_level; + codec->dai = &wm8510_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8510_reg); + codec->reg_cache = kmemdup(wm8510_reg, sizeof(wm8510_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8510_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8510_add_controls(codec); + wm8510_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8510: 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; +} + +static struct snd_soc_device *wm8510_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8510 2 wire address is 0x1a + */ +#define I2C_DRIVERID_WM8510 0xfefe /* liam - need a proper id */ + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8510_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ + +static int wm8510_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8510_socdev; + struct wm8510_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + pr_err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8510_init(socdev); + if (ret < 0) { + pr_err("failed to initialise WM8510\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8510_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8510_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8510_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8510_i2c_driver = { + .driver = { + .name = "WM8510 I2C Codec", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8510, + .attach_adapter = wm8510_i2c_attach, + .detach_client = wm8510_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8510", + .driver = &wm8510_i2c_driver, +}; +#endif + +static int wm8510_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8510_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + pr_info("WM8510 Audio Codec %s", WM8510_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8510_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8510_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8510_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8510_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(&wm8510_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8510 = { + .probe = wm8510_probe, + .remove = wm8510_remove, + .suspend = wm8510_suspend, + .resume = wm8510_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8510); + +MODULE_DESCRIPTION("ASoC WM8510 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h new file mode 100644 index 0000000..f5d2e42 --- /dev/null +++ b/sound/soc/codecs/wm8510.h @@ -0,0 +1,103 @@ +/* + * wm8510.h -- WM8510 Soc Audio driver + * + * 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 _WM8510_H +#define _WM8510_H + +/* WM8510 register space */ + +#define WM8510_RESET 0x0 +#define WM8510_POWER1 0x1 +#define WM8510_POWER2 0x2 +#define WM8510_POWER3 0x3 +#define WM8510_IFACE 0x4 +#define WM8510_COMP 0x5 +#define WM8510_CLOCK 0x6 +#define WM8510_ADD 0x7 +#define WM8510_GPIO 0x8 +#define WM8510_DAC 0xa +#define WM8510_DACVOL 0xb +#define WM8510_ADC 0xe +#define WM8510_ADCVOL 0xf +#define WM8510_EQ1 0x12 +#define WM8510_EQ2 0x13 +#define WM8510_EQ3 0x14 +#define WM8510_EQ4 0x15 +#define WM8510_EQ5 0x16 +#define WM8510_DACLIM1 0x18 +#define WM8510_DACLIM2 0x19 +#define WM8510_NOTCH1 0x1b +#define WM8510_NOTCH2 0x1c +#define WM8510_NOTCH3 0x1d +#define WM8510_NOTCH4 0x1e +#define WM8510_ALC1 0x20 +#define WM8510_ALC2 0x21 +#define WM8510_ALC3 0x22 +#define WM8510_NGATE 0x23 +#define WM8510_PLLN 0x24 +#define WM8510_PLLK1 0x25 +#define WM8510_PLLK2 0x26 +#define WM8510_PLLK3 0x27 +#define WM8510_ATTEN 0x28 +#define WM8510_INPUT 0x2c +#define WM8510_INPPGA 0x2d +#define WM8510_ADCBOOST 0x2f +#define WM8510_OUTPUT 0x31 +#define WM8510_SPKMIX 0x32 +#define WM8510_SPKVOL 0x36 +#define WM8510_MONOMIX 0x38 + +#define WM8510_CACHEREGNUM 57 + +/* Clock divider Id's */ +#define WM8510_OPCLKDIV 0 +#define WM8510_MCLKDIV 1 +#define WM8510_ADCCLK 2 +#define WM8510_DACCLK 3 +#define WM8510_BCLKDIV 4 + +/* DAC clock dividers */ +#define WM8510_DACCLK_F2 (1 << 3) +#define WM8510_DACCLK_F4 (0 << 3) + +/* ADC clock dividers */ +#define WM8510_ADCCLK_F2 (1 << 3) +#define WM8510_ADCCLK_F4 (0 << 3) + +/* PLL Out dividers */ +#define WM8510_OPCLKDIV_1 (0 << 4) +#define WM8510_OPCLKDIV_2 (1 << 4) +#define WM8510_OPCLKDIV_3 (2 << 4) +#define WM8510_OPCLKDIV_4 (3 << 4) + +/* BCLK clock dividers */ +#define WM8510_BCLKDIV_1 (0 << 2) +#define WM8510_BCLKDIV_2 (1 << 2) +#define WM8510_BCLKDIV_4 (2 << 2) +#define WM8510_BCLKDIV_8 (3 << 2) +#define WM8510_BCLKDIV_16 (4 << 2) +#define WM8510_BCLKDIV_32 (5 << 2) + +/* MCLK clock dividers */ +#define WM8510_MCLKDIV_1 (0 << 5) +#define WM8510_MCLKDIV_1_5 (1 << 5) +#define WM8510_MCLKDIV_2 (2 << 5) +#define WM8510_MCLKDIV_3 (3 << 5) +#define WM8510_MCLKDIV_4 (4 << 5) +#define WM8510_MCLKDIV_6 (5 << 5) +#define WM8510_MCLKDIV_8 (6 << 5) +#define WM8510_MCLKDIV_12 (7 << 5) + +struct wm8510_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8510_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8510; + +#endif diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 0cf9265..369d39c 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -31,25 +31,6 @@ #define AUDIO_NAME "wm8731" #define WM8731_VERSION "0.13" -/* - * Debug - */ - -#define WM8731_DEBUG 0 - -#ifdef WM8731_DEBUG -#define dbg(format, arg...) \ - printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) -#else -#define dbg(format, arg...) do {} while (0) -#endif -#define err(format, arg...) \ - printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) -#define info(format, arg...) \ - printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) -#define warn(format, arg...) \ - printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) - struct snd_soc_codec_device soc_codec_dev_wm8731; /* codec private data */ @@ -193,7 +174,7 @@ SND_SOC_DAPM_INPUT("RLINEIN"), SND_SOC_DAPM_INPUT("LLINEIN"), }; -static const char *intercon[][3] = { +static const struct snd_soc_dapm_route intercon[] = { /* output mixer */ {"Output Mixer", "Line Bypass Switch", "Line Input"}, {"Output Mixer", "HiFi Playback Switch", "DAC"}, @@ -214,22 +195,14 @@ static const char *intercon[][3] = { {"Line Input", NULL, "LLINEIN"}, {"Line Input", NULL, "RLINEIN"}, {"Mic Bias", NULL, "MICIN"}, - - /* terminator */ - {NULL, NULL, NULL}, }; static int wm8731_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(wm8731_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8731_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets, + ARRAY_SIZE(wm8731_dapm_widgets)); - /* set up audio path interconnects */ - for (i = 0; intercon[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, intercon[i][0], - intercon[i][1], intercon[i][2]); + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); snd_soc_dapm_new_widgets(codec); return 0; @@ -345,7 +318,7 @@ static void wm8731_shutdown(struct snd_pcm_substream *substream) } } -static int wm8731_mute(struct snd_soc_codec_dai *dai, int mute) +static int wm8731_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u16 mute_reg = wm8731_read_reg_cache(codec, WM8731_APDIGI) & 0xfff7; @@ -357,7 +330,7 @@ static int wm8731_mute(struct snd_soc_codec_dai *dai, int mute) return 0; } -static int wm8731_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int wm8731_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; @@ -376,7 +349,7 @@ static int wm8731_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, } -static int wm8731_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -435,29 +408,29 @@ static int wm8731_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return 0; } -static int wm8731_dapm_event(struct snd_soc_codec *codec, int event) +static int wm8731_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { u16 reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f; - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ + switch (level) { + case SND_SOC_BIAS_ON: /* vref/mid, osc on, dac unmute */ wm8731_write(codec, WM8731_PWR, reg); break; - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + case SND_SOC_BIAS_PREPARE: break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: /* everything off except vref/vmid, */ wm8731_write(codec, WM8731_PWR, reg | 0x0040); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: /* everything off, dac mute, inactive */ wm8731_write(codec, WM8731_ACTIVE, 0x0); wm8731_write(codec, WM8731_PWR, 0xffff); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -470,7 +443,7 @@ static int wm8731_dapm_event(struct snd_soc_codec *codec, int event) #define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) -struct snd_soc_codec_dai wm8731_dai = { +struct snd_soc_dai wm8731_dai = { .name = "WM8731", .playback = { .stream_name = "Playback", @@ -503,7 +476,7 @@ static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_codec *codec = socdev->codec; wm8731_write(codec, WM8731_ACTIVE, 0x0); - wm8731_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -521,8 +494,8 @@ static int wm8731_resume(struct platform_device *pdev) data[1] = cache[i] & 0x00ff; codec->hw_write(codec->control_data, data, 2); } - wm8731_dapm_event(codec, SNDRV_CTL_POWER_D3hot); - wm8731_dapm_event(codec, codec->suspend_dapm_state); + wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8731_set_bias_level(codec, codec->suspend_bias_level); return 0; } @@ -539,10 +512,10 @@ static int wm8731_init(struct snd_soc_device *socdev) codec->owner = THIS_MODULE; codec->read = wm8731_read_reg_cache; codec->write = wm8731_write; - codec->dapm_event = wm8731_dapm_event; + codec->set_bias_level = wm8731_set_bias_level; codec->dai = &wm8731_dai; codec->num_dai = 1; - codec->reg_cache_size = sizeof(wm8731_reg); + codec->reg_cache_size = ARRAY_SIZE(wm8731_reg); codec->reg_cache = kmemdup(wm8731_reg, sizeof(wm8731_reg), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; @@ -557,7 +530,7 @@ static int wm8731_init(struct snd_soc_device *socdev) } /* power on device */ - wm8731_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* set the update bits */ reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V); @@ -632,13 +605,13 @@ static int wm8731_codec_probe(struct i2c_adapter *adap, int addr, int kind) ret = i2c_attach_client(i2c); if (ret < 0) { - err("failed to attach codec at addr %x\n", addr); + pr_err("failed to attach codec at addr %x\n", addr); goto err; } ret = wm8731_init(socdev); if (ret < 0) { - err("failed to initialise WM8731\n"); + pr_err("failed to initialise WM8731\n"); goto err; } return ret; @@ -689,7 +662,7 @@ static int wm8731_probe(struct platform_device *pdev) struct wm8731_priv *wm8731; int ret = 0; - info("WM8731 Audio Codec %s", WM8731_VERSION); + pr_info("WM8731 Audio Codec %s", WM8731_VERSION); setup = socdev->codec_data; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); @@ -730,7 +703,7 @@ static int wm8731_remove(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->codec; if (codec->control_data) - wm8731_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h index 5bcab6a..99f2e3c 100644 --- a/sound/soc/codecs/wm8731.h +++ b/sound/soc/codecs/wm8731.h @@ -38,7 +38,7 @@ struct wm8731_setup_data { unsigned short i2c_address; }; -extern struct snd_soc_codec_dai wm8731_dai; +extern struct snd_soc_dai wm8731_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8731; #endif diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 16cd5d4..e23cb09 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -31,25 +31,6 @@ #define AUDIO_NAME "WM8750" #define WM8750_VERSION "0.12" -/* - * Debug - */ - -#define WM8750_DEBUG 0 - -#ifdef WM8750_DEBUG -#define dbg(format, arg...) \ - printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) -#else -#define dbg(format, arg...) do {} while (0) -#endif -#define err(format, arg...) \ - printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) -#define info(format, arg...) \ - printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) -#define warn(format, arg...) \ - printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) - /* codec private data */ struct wm8750_priv { unsigned int sysclk; @@ -378,7 +359,7 @@ static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { SND_SOC_DAPM_INPUT("RINPUT3"), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* left mixer */ {"Left Mixer", "Playback Switch", "Left DAC"}, {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, @@ -470,22 +451,14 @@ static const char *audio_map[][3] = { /* ADC */ {"Left ADC", NULL, "Left ADC Mux"}, {"Right ADC", NULL, "Right ADC Mux"}, - - /* terminator */ - {NULL, NULL, NULL}, }; static int wm8750_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(wm8750_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8750_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets, + ARRAY_SIZE(wm8750_dapm_widgets)); - /* set up audio path audio_mapnects */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(codec); return 0; @@ -563,7 +536,7 @@ static inline int get_coeff(int mclk, int rate) return -EINVAL; } -static int wm8750_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int wm8750_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; @@ -581,7 +554,7 @@ static int wm8750_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, return -EINVAL; } -static int wm8750_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -674,7 +647,7 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } -static int wm8750_mute(struct snd_soc_codec_dai *dai, int mute) +static int wm8750_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u16 mute_reg = wm8750_read_reg_cache(codec, WM8750_ADCDAC) & 0xfff7; @@ -686,29 +659,29 @@ static int wm8750_mute(struct snd_soc_codec_dai *dai, int mute) return 0; } -static int wm8750_dapm_event(struct snd_soc_codec *codec, int event) +static int wm8750_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { u16 pwr_reg = wm8750_read_reg_cache(codec, WM8750_PWR1) & 0xfe3e; - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ + switch (level) { + case SND_SOC_BIAS_ON: /* set vmid to 50k and unmute dac */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x00c0); break; - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + case SND_SOC_BIAS_PREPARE: /* set vmid to 5k for quick power up */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: /* mute dac and set vmid to 500k, enable VREF */ wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x0141); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: wm8750_write(codec, WM8750_PWR1, 0x0001); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -719,7 +692,7 @@ static int wm8750_dapm_event(struct snd_soc_codec *codec, int event) #define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE) -struct snd_soc_codec_dai wm8750_dai = { +struct snd_soc_dai wm8750_dai = { .name = "WM8750", .playback = { .stream_name = "Playback", @@ -748,7 +721,7 @@ static void wm8750_work(struct work_struct *work) { struct snd_soc_codec *codec = container_of(work, struct snd_soc_codec, delayed_work.work); - wm8750_dapm_event(codec, codec->dapm_state); + wm8750_set_bias_level(codec, codec->bias_level); } static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) @@ -756,7 +729,7 @@ static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -777,12 +750,12 @@ static int wm8750_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* charge wm8750 caps */ - if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) { - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D2); - codec->dapm_state = SNDRV_CTL_POWER_D0; + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_ON; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); } @@ -803,10 +776,10 @@ static int wm8750_init(struct snd_soc_device *socdev) codec->owner = THIS_MODULE; codec->read = wm8750_read_reg_cache; codec->write = wm8750_write; - codec->dapm_event = wm8750_dapm_event; + codec->set_bias_level = wm8750_set_bias_level; codec->dai = &wm8750_dai; codec->num_dai = 1; - codec->reg_cache_size = sizeof(wm8750_reg); + codec->reg_cache_size = ARRAY_SIZE(wm8750_reg); codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL); if (codec->reg_cache == NULL) return -ENOMEM; @@ -821,8 +794,8 @@ static int wm8750_init(struct snd_soc_device *socdev) } /* charge output caps */ - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D2); - codec->dapm_state = SNDRV_CTL_POWER_D3hot; + wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_STANDBY; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); /* set the update bits */ @@ -904,13 +877,13 @@ static int wm8750_codec_probe(struct i2c_adapter *adap, int addr, int kind) ret = i2c_attach_client(i2c); if (ret < 0) { - err("failed to attach codec at addr %x\n", addr); + pr_err("failed to attach codec at addr %x\n", addr); goto err; } ret = wm8750_init(socdev); if (ret < 0) { - err("failed to initialise WM8750\n"); + pr_err("failed to initialise WM8750\n"); goto err; } return ret; @@ -961,7 +934,7 @@ static int wm8750_probe(struct platform_device *pdev) struct wm8750_priv *wm8750; int ret = 0; - info("WM8750 Audio Codec %s", WM8750_VERSION); + pr_info("WM8750 Audio Codec %s", WM8750_VERSION); codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) return -ENOMEM; @@ -1021,7 +994,7 @@ static int wm8750_remove(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->codec; if (codec->control_data) - wm8750_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); run_delayed_work(&codec->delayed_work); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); diff --git a/sound/soc/codecs/wm8750.h b/sound/soc/codecs/wm8750.h index a97a54a..8ef30e6 100644 --- a/sound/soc/codecs/wm8750.h +++ b/sound/soc/codecs/wm8750.h @@ -61,7 +61,7 @@ struct wm8750_setup_data { unsigned short i2c_address; }; -extern struct snd_soc_codec_dai wm8750_dai; +extern struct snd_soc_dai wm8750_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8750; #endif diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index fb41826..8604809 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -55,25 +55,6 @@ #define AUDIO_NAME "wm8753" #define WM8753_VERSION "0.16" -/* - * Debug - */ - -#define WM8753_DEBUG 0 - -#ifdef WM8753_DEBUG -#define dbg(format, arg...) \ - printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg) -#else -#define dbg(format, arg...) do {} while (0) -#endif -#define err(format, arg...) \ - printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) -#define info(format, arg...) \ - printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) -#define warn(format, arg...) \ - printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) - static int caps_charge = 2000; module_param(caps_charge, int, 0); MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); @@ -260,28 +241,50 @@ static int wm8753_set_dai(struct snd_kcontrol *kcontrol, return 1; } -static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); +static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(mic_preamp_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const unsigned int out_tlv[] = { + TLV_DB_RANGE_HEAD(2), + /* 0000000 - 0101111 = "Analogue mute" */ + 0, 48, TLV_DB_SCALE_ITEM(-25500, 0, 0), + 48, 127, TLV_DB_SCALE_ITEM(-7300, 100, 0), +}; +static const DECLARE_TLV_DB_SCALE(mix_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(voice_mix_tlv, -1200, 300, 0); +static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0); static const struct snd_kcontrol_new wm8753_snd_controls[] = { -SOC_DOUBLE_R("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0), - -SOC_DOUBLE_R("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0), - -SOC_DOUBLE_R("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, 0, 127, 0), -SOC_DOUBLE_R("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, 127, 0), - -SOC_SINGLE("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0), - -SOC_DOUBLE_R("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, 1), -SOC_DOUBLE_R("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, 7, 1), -SOC_DOUBLE_R("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, 1), - -SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, 1, 0), -SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, 1, 0), - -SOC_SINGLE("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1), -SOC_SINGLE("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1), -SOC_SINGLE("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1), +SOC_DOUBLE_R_TLV("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0, + adc_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, + 127, 0, out_tlv), + +SOC_SINGLE_TLV("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0, out_tlv), + +SOC_DOUBLE_R_TLV("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, + 1, mix_tlv), +SOC_DOUBLE_R_TLV("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, + 7, 1, mix_tlv), +SOC_DOUBLE_R_TLV("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, + 1, voice_mix_tlv), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, + 1, 0), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, + 1, 0), + +SOC_SINGLE_TLV("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1, mix_tlv), +SOC_SINGLE_TLV("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1, + mix_tlv), +SOC_SINGLE_TLV("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1, + voice_mix_tlv), SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0), SOC_ENUM("Bass Boost", wm8753_enum[0]), @@ -291,10 +294,13 @@ SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1), SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1), SOC_ENUM("Treble Cut-off", wm8753_enum[2]), -SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, rec_mix_tlv), -SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, rec_mix_tlv), +SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, + rec_mix_tlv), +SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, + rec_mix_tlv), -SOC_DOUBLE_R("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0), +SOC_DOUBLE_R_TLV("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0, + pga_tlv), SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0), SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1), @@ -326,8 +332,8 @@ SOC_ENUM("De-emphasis", wm8753_enum[8]), SOC_ENUM("Playback Mono Mix", wm8753_enum[9]), SOC_ENUM("Playback Phase", wm8753_enum[10]), -SOC_SINGLE("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0), -SOC_SINGLE("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0), +SOC_SINGLE_TLV("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0, mic_preamp_tlv), +SOC_SINGLE_TLV("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0, mic_preamp_tlv), SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai), @@ -523,7 +529,7 @@ SND_SOC_DAPM_INPUT("MIC2"), SND_SOC_DAPM_VMID("VREF"), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* left mixer */ {"Left Mixer", "Left Playback Switch", "Left DAC"}, {"Left Mixer", "Voice Playback Switch", "Voice DAC"}, @@ -674,23 +680,14 @@ static const char *audio_map[][3] = { /* ACOP */ {"ACOP", NULL, "ALC Mixer"}, - - /* terminator */ - {NULL, NULL, NULL}, }; static int wm8753_add_widgets(struct snd_soc_codec *codec) { - int i; + snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets, + ARRAY_SIZE(wm8753_dapm_widgets)); - for (i = 0; i < ARRAY_SIZE(wm8753_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm8753_dapm_widgets[i]); - - /* set up the WM8753 audio map */ - for (i = 0; audio_map[i][0] != NULL; i++) { - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); - } + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(codec); return 0; @@ -743,7 +740,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target, pll_div->k = K; } -static int wm8753_set_dai_pll(struct snd_soc_codec_dai *codec_dai, +static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, unsigned int freq_in, unsigned int freq_out) { u16 reg, enable; @@ -866,7 +863,7 @@ static int get_coeff(int mclk, int rate) /* * Clock after PLL and dividers */ -static int wm8753_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, +static int wm8753_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; @@ -893,7 +890,7 @@ static int wm8753_set_dai_sysclk(struct snd_soc_codec_dai *codec_dai, /* * Set's ADC and Voice DAC format. */ -static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -963,7 +960,7 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, /* * Set's PCM dai fmt and BCLK. */ -static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1029,7 +1026,7 @@ static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return 0; } -static int wm8753_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, +static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1057,7 +1054,7 @@ static int wm8753_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, /* * Set's HiFi DAC format. */ -static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1090,7 +1087,7 @@ static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, /* * Set's I2S DAI format. */ -static int wm8753_i2s_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1198,7 +1195,7 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, return 0; } -static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_mode1v_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1213,7 +1210,7 @@ static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return wm8753_pcm_set_dai_fmt(codec_dai, fmt); } -static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_mode1h_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) @@ -1221,7 +1218,7 @@ static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return wm8753_i2s_set_dai_fmt(codec_dai, fmt); } -static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_mode2_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1236,7 +1233,7 @@ static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return wm8753_i2s_set_dai_fmt(codec_dai, fmt); } -static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1253,7 +1250,7 @@ static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, return wm8753_i2s_set_dai_fmt(codec_dai, fmt); } -static int wm8753_mute(struct snd_soc_codec_dai *dai, int mute) +static int wm8753_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; u16 mute_reg = wm8753_read_reg_cache(codec, WM8753_DAC) & 0xfff7; @@ -1274,29 +1271,29 @@ static int wm8753_mute(struct snd_soc_codec_dai *dai, int mute) return 0; } -static int wm8753_dapm_event(struct snd_soc_codec *codec, int event) +static int wm8753_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { u16 pwr_reg = wm8753_read_reg_cache(codec, WM8753_PWR1) & 0xfe3e; - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ + switch (level) { + case SND_SOC_BIAS_ON: /* set vmid to 50k and unmute dac */ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0); break; - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + case SND_SOC_BIAS_PREPARE: /* set vmid to 5k for quick power up */ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x01c1); break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: /* mute dac and set vmid to 500k, enable VREF */ wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x0141); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: wm8753_write(codec, WM8753_PWR1, 0x0001); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -1319,7 +1316,7 @@ static int wm8753_dapm_event(struct snd_soc_codec *codec, int event) * 3. Voice disabled - HIFI over HIFI * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture */ -static const struct snd_soc_codec_dai wm8753_all_dai[] = { +static const struct snd_soc_dai wm8753_all_dai[] = { /* DAI HiFi mode 1 */ { .name = "WM8753 HiFi", .id = 1, @@ -1459,7 +1456,7 @@ static const struct snd_soc_codec_dai wm8753_all_dai[] = { }, }; -struct snd_soc_codec_dai wm8753_dai[2]; +struct snd_soc_dai wm8753_dai[2]; EXPORT_SYMBOL_GPL(wm8753_dai); static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode) @@ -1500,7 +1497,7 @@ static void wm8753_work(struct work_struct *work) { struct snd_soc_codec *codec = container_of(work, struct snd_soc_codec, delayed_work.work); - wm8753_dapm_event(codec, codec->dapm_state); + wm8753_set_bias_level(codec, codec->bias_level); } static int wm8753_suspend(struct platform_device *pdev, pm_message_t state) @@ -1512,7 +1509,7 @@ static int wm8753_suspend(struct platform_device *pdev, pm_message_t state) if (!codec->card) return 0; - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -1537,12 +1534,12 @@ static int wm8753_resume(struct platform_device *pdev) codec->hw_write(codec->control_data, data, 2); } - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* charge wm8753 caps */ - if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) { - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D2); - codec->dapm_state = SNDRV_CTL_POWER_D0; + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_ON; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(caps_charge)); } @@ -1563,10 +1560,10 @@ static int wm8753_init(struct snd_soc_device *socdev) codec->owner = THIS_MODULE; codec->read = wm8753_read_reg_cache; codec->write = wm8753_write; - codec->dapm_event = wm8753_dapm_event; + codec->set_bias_level = wm8753_set_bias_level; codec->dai = wm8753_dai; codec->num_dai = 2; - codec->reg_cache_size = sizeof(wm8753_reg); + codec->reg_cache_size = ARRAY_SIZE(wm8753_reg); codec->reg_cache = kmemdup(wm8753_reg, sizeof(wm8753_reg), GFP_KERNEL); if (codec->reg_cache == NULL) @@ -1584,8 +1581,8 @@ static int wm8753_init(struct snd_soc_device *socdev) } /* charge output caps */ - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D2); - codec->dapm_state = SNDRV_CTL_POWER_D3hot; + wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_STANDBY; schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(caps_charge)); @@ -1673,13 +1670,13 @@ static int wm8753_codec_probe(struct i2c_adapter *adap, int addr, int kind) ret = i2c_attach_client(i2c); if (ret < 0) { - err("failed to attach codec at addr %x\n", addr); + pr_err("failed to attach codec at addr %x\n", addr); goto err; } ret = wm8753_init(socdev); if (ret < 0) { - err("failed to initialise WM8753\n"); + pr_err("failed to initialise WM8753\n"); goto err; } @@ -1731,7 +1728,7 @@ static int wm8753_probe(struct platform_device *pdev) struct wm8753_priv *wm8753; int ret = 0; - info("WM8753 Audio Codec %s", WM8753_VERSION); + pr_info("WM8753 Audio Codec %s", WM8753_VERSION); setup = socdev->codec_data; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); @@ -1792,7 +1789,7 @@ static int wm8753_remove(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->codec; if (codec->control_data) - wm8753_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); run_delayed_work(&codec->delayed_work); snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h index 95e2a1f..44f5f1f 100644 --- a/sound/soc/codecs/wm8753.h +++ b/sound/soc/codecs/wm8753.h @@ -120,7 +120,7 @@ struct wm8753_setup_data { #define WM8753_DAI_HIFI 0 #define WM8753_DAI_VOICE 1 -extern struct snd_soc_codec_dai wm8753_dai[2]; +extern struct snd_soc_dai wm8753_dai[2]; extern struct snd_soc_codec_device soc_codec_dev_wm8753; #endif diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c new file mode 100644 index 0000000..3ecce51 --- /dev/null +++ b/sound/soc/codecs/wm8990.c @@ -0,0 +1,1626 @@ +/* + * wm8990.c -- WM8990 ALSA Soc Audio driver + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * lg@opensource.wolfsonmicro.com or linux@wolfsonmicro.com + * + * 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/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> +#include <asm/div64.h> + +#include "wm8990.h" + +#define AUDIO_NAME "wm8990" +#define WM8990_VERSION "0.2" + +/* codec private data */ +struct wm8990_priv { + unsigned int sysclk; + unsigned int pcmclk; +}; + +/* + * wm8990 register cache. Note that register 0 is not included in the + * cache. + */ +static const u16 wm8990_reg[] = { + 0x8990, /* R0 - Reset */ + 0x0000, /* R1 - Power Management (1) */ + 0x6000, /* R2 - Power Management (2) */ + 0x0000, /* R3 - Power Management (3) */ + 0x4050, /* R4 - Audio Interface (1) */ + 0x4000, /* R5 - Audio Interface (2) */ + 0x01C8, /* R6 - Clocking (1) */ + 0x0000, /* R7 - Clocking (2) */ + 0x0040, /* R8 - Audio Interface (3) */ + 0x0040, /* R9 - Audio Interface (4) */ + 0x0004, /* R10 - DAC CTRL */ + 0x00C0, /* R11 - Left DAC Digital Volume */ + 0x00C0, /* R12 - Right DAC Digital Volume */ + 0x0000, /* R13 - Digital Side Tone */ + 0x0100, /* R14 - ADC CTRL */ + 0x00C0, /* R15 - Left ADC Digital Volume */ + 0x00C0, /* R16 - Right ADC Digital Volume */ + 0x0000, /* R17 */ + 0x0000, /* R18 - GPIO CTRL 1 */ + 0x1000, /* R19 - GPIO1 & GPIO2 */ + 0x1010, /* R20 - GPIO3 & GPIO4 */ + 0x1010, /* R21 - GPIO5 & GPIO6 */ + 0x8000, /* R22 - GPIOCTRL 2 */ + 0x0800, /* R23 - GPIO_POL */ + 0x008B, /* R24 - Left Line Input 1&2 Volume */ + 0x008B, /* R25 - Left Line Input 3&4 Volume */ + 0x008B, /* R26 - Right Line Input 1&2 Volume */ + 0x008B, /* R27 - Right Line Input 3&4 Volume */ + 0x0000, /* R28 - Left Output Volume */ + 0x0000, /* R29 - Right Output Volume */ + 0x0066, /* R30 - Line Outputs Volume */ + 0x0022, /* R31 - Out3/4 Volume */ + 0x0079, /* R32 - Left OPGA Volume */ + 0x0079, /* R33 - Right OPGA Volume */ + 0x0003, /* R34 - Speaker Volume */ + 0x0003, /* R35 - ClassD1 */ + 0x0000, /* R36 */ + 0x0100, /* R37 - ClassD3 */ + 0x0000, /* R38 */ + 0x0000, /* R39 - Input Mixer1 */ + 0x0000, /* R40 - Input Mixer2 */ + 0x0000, /* R41 - Input Mixer3 */ + 0x0000, /* R42 - Input Mixer4 */ + 0x0000, /* R43 - Input Mixer5 */ + 0x0000, /* R44 - Input Mixer6 */ + 0x0000, /* R45 - Output Mixer1 */ + 0x0000, /* R46 - Output Mixer2 */ + 0x0000, /* R47 - Output Mixer3 */ + 0x0000, /* R48 - Output Mixer4 */ + 0x0000, /* R49 - Output Mixer5 */ + 0x0000, /* R50 - Output Mixer6 */ + 0x0180, /* R51 - Out3/4 Mixer */ + 0x0000, /* R52 - Line Mixer1 */ + 0x0000, /* R53 - Line Mixer2 */ + 0x0000, /* R54 - Speaker Mixer */ + 0x0000, /* R55 - Additional Control */ + 0x0000, /* R56 - AntiPOP1 */ + 0x0000, /* R57 - AntiPOP2 */ + 0x0000, /* R58 - MICBIAS */ + 0x0000, /* R59 */ + 0x0008, /* R60 - PLL1 */ + 0x0031, /* R61 - PLL2 */ + 0x0026, /* R62 - PLL3 */ +}; + +/* + * read wm8990 register cache + */ +static inline unsigned int wm8990_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1); + return cache[reg]; +} + +/* + * write wm8990 register cache + */ +static inline void wm8990_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1); + + /* Reset register is uncached */ + if (reg == 0) + return; + + cache[reg] = value; +} + +/* + * write to the wm8990 register space + */ +static int wm8990_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + data[0] = reg & 0xFF; + data[1] = (value >> 8) & 0xFF; + data[2] = value & 0xFF; + + wm8990_write_reg_cache(codec, reg, value); + + if (codec->hw_write(codec->control_data, data, 3) == 2) + return 0; + else + return -EIO; +} + +#define wm8990_reset(c) wm8990_write(c, WM8990_RESET, 0) + +static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); + +static const DECLARE_TLV_DB_LINEAR(in_pga_tlv, -1650, 3000); + +static const DECLARE_TLV_DB_LINEAR(out_mix_tlv, 0, -2100); + +static const DECLARE_TLV_DB_LINEAR(out_pga_tlv, -7300, 600); + +static const DECLARE_TLV_DB_LINEAR(out_omix_tlv, -600, 0); + +static const DECLARE_TLV_DB_LINEAR(out_dac_tlv, -7163, 0); + +static const DECLARE_TLV_DB_LINEAR(in_adc_tlv, -7163, 1763); + +static const DECLARE_TLV_DB_LINEAR(out_sidetone_tlv, -3600, 0); + +static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = wm8990_read_reg_cache(codec, reg); + return wm8990_write(codec, reg, val | 0x0100); +} + +#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ + tlv_array) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + + +static const char *wm8990_digital_sidetone[] = + {"None", "Left ADC", "Right ADC", "Reserved"}; + +static const struct soc_enum wm8990_left_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE, + WM8990_ADC_TO_DACL_SHIFT, + WM8990_ADC_TO_DACL_MASK, + wm8990_digital_sidetone); + +static const struct soc_enum wm8990_right_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE, + WM8990_ADC_TO_DACR_SHIFT, + WM8990_ADC_TO_DACR_MASK, + wm8990_digital_sidetone); + +static const char *wm8990_adcmode[] = + {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static const struct soc_enum wm8990_right_adcmode_enum = +SOC_ENUM_SINGLE(WM8990_ADC_CTRL, + WM8990_ADC_HPF_CUT_SHIFT, + WM8990_ADC_HPF_CUT_MASK, + wm8990_adcmode); + +static const struct snd_kcontrol_new wm8990_snd_controls[] = { +/* INMIXL */ +SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0), +SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0), +/* INMIXR */ +SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0), + +/* LOMIX */ +SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), + +/* ROMIX */ +SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv), + +/* LOUT */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME, + WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0), + +/* ROUT */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME, + WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0), + +/* LOPGA */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME, + WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME, + WM8990_LOPGAZC_BIT, 1, 0), + +/* ROPGA */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME, + WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME, + WM8990_ROPGAZC_BIT, 1, 0), + +SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LONMUTE_BIT, 1, 0), +SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LOPMUTE_BIT, 1, 0), +SOC_SINGLE("LOP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LOATTN_BIT, 1, 0), +SOC_SINGLE("RON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_RONMUTE_BIT, 1, 0), +SOC_SINGLE("ROP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_ROPMUTE_BIT, 1, 0), +SOC_SINGLE("ROP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_ROATTN_BIT, 1, 0), + +SOC_SINGLE("OUT3 Mute Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT3MUTE_BIT, 1, 0), +SOC_SINGLE("OUT3 Attenuation Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT3ATTN_BIT, 1, 0), + +SOC_SINGLE("OUT4 Mute Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT4MUTE_BIT, 1, 0), +SOC_SINGLE("OUT4 Attenuation Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT4ATTN_BIT, 1, 0), + +SOC_SINGLE("Speaker Mode Switch", WM8990_CLASSD1, + WM8990_CDMODE_BIT, 1, 0), + +SOC_SINGLE("Speaker Output Attenuation Volume", WM8990_SPEAKER_VOLUME, + WM8990_SPKVOL_SHIFT, WM8990_SPKVOL_MASK, 0), +SOC_SINGLE("Speaker DC Boost Volume", WM8990_CLASSD3, + WM8990_DCGAIN_SHIFT, WM8990_DCGAIN_MASK, 0), +SOC_SINGLE("Speaker AC Boost Volume", WM8990_CLASSD3, + WM8990_ACGAIN_SHIFT, WM8990_ACGAIN_MASK, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8990_LEFT_DAC_DIGITAL_VOLUME, + WM8990_DACL_VOL_SHIFT, + WM8990_DACL_VOL_MASK, + 0, + out_dac_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8990_RIGHT_DAC_DIGITAL_VOLUME, + WM8990_DACR_VOL_SHIFT, + WM8990_DACR_VOL_MASK, + 0, + out_dac_tlv), + +SOC_ENUM("Left Digital Sidetone", wm8990_left_digital_sidetone_enum), +SOC_ENUM("Right Digital Sidetone", wm8990_right_digital_sidetone_enum), + +SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE, + WM8990_ADCL_DAC_SVOL_SHIFT, WM8990_ADCL_DAC_SVOL_MASK, 0, + out_sidetone_tlv), +SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE, + WM8990_ADCR_DAC_SVOL_SHIFT, WM8990_ADCR_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + +SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8990_ADC_CTRL, + WM8990_ADC_HPF_ENA_BIT, 1, 0), + +SOC_ENUM("ADC HPF Mode", wm8990_right_adcmode_enum), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8990_LEFT_ADC_DIGITAL_VOLUME, + WM8990_ADCL_VOL_SHIFT, + WM8990_ADCL_VOL_MASK, + 0, + in_adc_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8990_RIGHT_ADC_DIGITAL_VOLUME, + WM8990_ADCR_VOL_SHIFT, + WM8990_ADCR_VOL_MASK, + 0, + in_adc_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LIN12VOL_SHIFT, + WM8990_LIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN12 ZC Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LI12ZC_BIT, 1, 0), + +SOC_SINGLE("LIN12 Mute Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LI12MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LIN34VOL_SHIFT, + WM8990_LIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN34 ZC Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LI34ZC_BIT, 1, 0), + +SOC_SINGLE("LIN34 Mute Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LI34MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RIN12VOL_SHIFT, + WM8990_RIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN12 ZC Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RI12ZC_BIT, 1, 0), + +SOC_SINGLE("RIN12 Mute Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RI12MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RIN34VOL_SHIFT, + WM8990_RIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN34 ZC Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RI34ZC_BIT, 1, 0), + +SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RI34MUTE_BIT, 1, 0), + +}; + +/* add non dapm controls */ +static int wm8990_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8990_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8990_snd_controls[i], codec, + NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * _DAPM_ Controls + */ + +static int inmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 reg, fakepower; + + reg = wm8990_read_reg_cache(w->codec, WM8990_POWER_MANAGEMENT_2); + fakepower = wm8990_read_reg_cache(w->codec, WM8990_INTDRIVBITS); + + if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) | + (1 << WM8990_AINLMUX_PWR_BIT))) { + reg |= WM8990_AINL_ENA; + } else { + reg &= ~WM8990_AINL_ENA; + } + + if (fakepower & ((1 << WM8990_INMIXR_PWR_BIT) | + (1 << WM8990_AINRMUX_PWR_BIT))) { + reg |= WM8990_AINR_ENA; + } else { + reg &= ~WM8990_AINL_ENA; + } + wm8990_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg); + + return 0; +} + +static int outmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u32 reg_shift = kcontrol->private_value & 0xfff; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) : + reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER1); + if (reg & WM8990_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER2); + if (reg & WM8990_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER); + if (reg & WM8990_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER); + if (reg & WM8990_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const unsigned int in_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600), +}; + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8990_dapm_lin12_pga_controls[] = { +SOC_DAPM_SINGLE("LIN1 Switch", WM8990_INPUT_MIXER2, WM8990_LMN1_BIT, 1, 0), +SOC_DAPM_SINGLE("LIN2 Switch", WM8990_INPUT_MIXER2, WM8990_LMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8990_dapm_lin34_pga_controls[] = { +SOC_DAPM_SINGLE("LIN3 Switch", WM8990_INPUT_MIXER2, WM8990_LMN3_BIT, 1, 0), +SOC_DAPM_SINGLE("LIN4 Switch", WM8990_INPUT_MIXER2, WM8990_LMP4_BIT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8990_dapm_rin12_pga_controls[] = { +SOC_DAPM_SINGLE("RIN1 Switch", WM8990_INPUT_MIXER2, WM8990_RMN1_BIT, 1, 0), +SOC_DAPM_SINGLE("RIN2 Switch", WM8990_INPUT_MIXER2, WM8990_RMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8990_dapm_rin34_pga_controls[] = { +SOC_DAPM_SINGLE("RIN3 Switch", WM8990_INPUT_MIXER2, WM8990_RMN3_BIT, 1, 0), +SOC_DAPM_SINGLE("RIN4 Switch", WM8990_INPUT_MIXER2, WM8990_RMP4_BIT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8990_dapm_inmixl_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8990_INPUT_MIXER3, + WM8990_LDBVOL_SHIFT, WM8990_LDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8990_INPUT_MIXER5, WM8990_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("LINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, + 1, 0), +SOC_DAPM_SINGLE("LINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8990_dapm_inmixr_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4, + WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, + 1, 0), +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8990_ainlmux[] = + {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static const struct soc_enum wm8990_ainlmux_enum = +SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINLMODE_SHIFT, + ARRAY_SIZE(wm8990_ainlmux), wm8990_ainlmux); + +static const struct snd_kcontrol_new wm8990_dapm_ainlmux_controls = +SOC_DAPM_ENUM("Route", wm8990_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8990_ainrmux[] = + {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static const struct soc_enum wm8990_ainrmux_enum = +SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINRMODE_SHIFT, + ARRAY_SIZE(wm8990_ainrmux), wm8990_ainrmux); + +static const struct snd_kcontrol_new wm8990_dapm_ainrmux_controls = +SOC_DAPM_ENUM("Route", wm8990_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8990_dapm_rxvoice_controls[] = { +SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8990_INPUT_MIXER5, WM8990_LR4BVOL_SHIFT, + WM8990_LR4BVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8990_INPUT_MIXER6, WM8990_RL4BVOL_SHIFT, + WM8990_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lomix_controls[] = { +SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LRBLO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LLBLO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LRI3LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LLI3LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LR12LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LL12LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8990_OUTPUT_MIXER1, + WM8990_LDLO_BIT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8990_dapm_romix_controls[] = { +SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RLBRO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RRBRO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RLI3RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RRI3RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RL12RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RR12RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8990_OUTPUT_MIXER2, + WM8990_RDRO_BIT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lonmix_controls[] = { +SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LLOPGALON_BIT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LROPGALON_BIT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8990_LINE_MIXER1, + WM8990_LOPLON_BIT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lopmix_controls[] = { +SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER1, + WM8990_LR12LOP_BIT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER1, + WM8990_LL12LOP_BIT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LLOPGALOP_BIT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8990_dapm_ronmix_controls[] = { +SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RROPGARON_BIT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RLOPGARON_BIT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8990_LINE_MIXER2, + WM8990_ROPRON_BIT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8990_dapm_ropmix_controls[] = { +SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER2, + WM8990_RL12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER2, + WM8990_RR12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RROPGAROP_BIT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8990_dapm_out3mix_controls[] = { +SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER, + WM8990_LI4O3_BIT, 1, 0), +SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8990_OUT3_4_MIXER, + WM8990_LPGAO3_BIT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8990_dapm_out4mix_controls[] = { +SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8990_OUT3_4_MIXER, + WM8990_RPGAO4_BIT, 1, 0), +SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER, + WM8990_RI4O4_BIT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8990_dapm_spkmix_controls[] = { +SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_LI2SPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_LB2SPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8990_SPEAKER_MIXER, + WM8990_LOPGASPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8990_SPEAKER_MIXER, + WM8990_LDSPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8990_SPEAKER_MIXER, + WM8990_RDSPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8990_SPEAKER_MIXER, + WM8990_ROPGASPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_RL12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_RI2SPK_BIT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8990_dapm_widgets[] = { +/* Input Side */ +/* Input Lines */ +SND_SOC_DAPM_INPUT("LIN1"), +SND_SOC_DAPM_INPUT("LIN2"), +SND_SOC_DAPM_INPUT("LIN3"), +SND_SOC_DAPM_INPUT("LIN4/RXN"), +SND_SOC_DAPM_INPUT("RIN3"), +SND_SOC_DAPM_INPUT("RIN4/RXP"), +SND_SOC_DAPM_INPUT("RIN1"), +SND_SOC_DAPM_INPUT("RIN2"), +SND_SOC_DAPM_INPUT("Internal ADC Source"), + +/* DACs */ +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2, + WM8990_ADCL_ENA_BIT, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2, + WM8990_ADCR_ENA_BIT, 0), + +/* Input PGAs */ +SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT, + 0, &wm8990_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)), +SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT, + 0, &wm8990_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)), +SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT, + 0, &wm8990_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)), +SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT, + 0, &wm8990_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)), + +/* INMIXL */ +SND_SOC_DAPM_MIXER_E("INMIXL", WM8990_INTDRIVBITS, WM8990_INMIXL_PWR_BIT, 0, + &wm8990_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8990_dapm_inmixl_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINLMUX */ +SND_SOC_DAPM_MUX_E("AILNMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0, + &wm8990_dapm_ainlmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* INMIXR */ +SND_SOC_DAPM_MIXER_E("INMIXR", WM8990_INTDRIVBITS, WM8990_INMIXR_PWR_BIT, 0, + &wm8990_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8990_dapm_inmixr_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINRMUX */ +SND_SOC_DAPM_MUX_E("AIRNMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0, + &wm8990_dapm_ainrmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* Output Side */ +/* DACs */ +SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3, + WM8990_DACL_ENA_BIT, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3, + WM8990_DACR_ENA_BIT, 0), + +/* LOMIX */ +SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT, + 0, &wm8990_dapm_lomix_controls[0], + ARRAY_SIZE(wm8990_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LONMIX */ +SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0, + &wm8990_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8990_dapm_lonmix_controls)), + +/* LOPMIX */ +SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0, + &wm8990_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8990_dapm_lopmix_controls)), + +/* OUT3MIX */ +SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0, + &wm8990_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8990_dapm_out3mix_controls)), + +/* SPKMIX */ +SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0, + &wm8990_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + +/* OUT4MIX */ +SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0, + &wm8990_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8990_dapm_out4mix_controls)), + +/* ROPMIX */ +SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0, + &wm8990_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8990_dapm_ropmix_controls)), + +/* RONMIX */ +SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0, + &wm8990_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8990_dapm_ronmix_controls)), + +/* ROMIX */ +SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT, + 0, &wm8990_dapm_romix_controls[0], + ARRAY_SIZE(wm8990_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LOUT PGA */ +SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0, + NULL, 0), + +/* ROUT PGA */ +SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0, + NULL, 0), + +/* LOPGA */ +SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0, + NULL, 0), + +/* ROPGA */ +SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0, + NULL, 0), + +/* MICBIAS */ +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8990_POWER_MANAGEMENT_1, + WM8990_MICBIAS_ENA_BIT, 0), + +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AILNMUX */ + {"AILNMUX", "INMIXL Mix", "INMIXL"}, + {"AILNMUX", "DIFFINL Mix", "LIN12PGA"}, + {"AILNMUX", "DIFFINL Mix", "LIN34PGA"}, + {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Left ADC", NULL, "AILNMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AIRNMUX */ + {"AIRNMUX", "INMIXR Mix", "INMIXR"}, + {"AIRNMUX", "DIFFINR Mix", "RIN12PGA"}, + {"AIRNMUX", "DIFFINR Mix", "RIN34PGA"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXN"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Right ADC", NULL, "AIRNMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXP"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +static int wm8990_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8990_dapm_widgets, + ARRAY_SIZE(wm8990_dapm_widgets)); + + /* set up the WM8990 audio map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 div2; + u32 n; + u32 k; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 16) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8990 N value outwith recommended range! N = %d\n", Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + struct snd_soc_codec *codec = codec_dai->codec; + struct _pll_div pll_div; + + if (freq_in && freq_out) { + pll_factors(&pll_div, freq_out * 4, freq_in); + + /* Turn on PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + reg |= WM8990_PLL_ENA; + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg); + + /* sysclk comes from PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2); + wm8990_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC); + + /* set up N , fractional mode and pre-divisor if neccessary */ + wm8990_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM | + (pll_div.div2?WM8990_PRESCALE:0)); + wm8990_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8)); + wm8990_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF)); + } else { + /* Turn on PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + reg &= ~WM8990_PLL_ENA; + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg); + } + return 0; +} + +/* + * Clock after PLL and dividers + */ +static int wm8990_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 wm8990_priv *wm8990 = codec->private_data; + + wm8990->sysclk = freq; + return 0; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 audio1, audio3; + + audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); + audio3 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8990_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8990_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8990_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8990_AIF_TMF_I2S; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8990_AIF_TMF_RIGHTJ; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8990_AIF_TMF_LEFTJ; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8990_AIF_TMF_DSP; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); + wm8990_write(codec, WM8990_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8990_MCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_MCLK_DIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_DACCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_DAC_CLKDIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_ADCCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_ADC_CLKDIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_BCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_1) & + ~WM8990_BCLK_DIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8990_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); + + audio1 &= ~WM8990_AIF_WL_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + audio1 |= WM8990_AIF_WL_20BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + audio1 |= WM8990_AIF_WL_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio1 |= WM8990_AIF_WL_32BITS; + break; + } + + wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8990_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 val; + + val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE; + + if (mute) + wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + else + wm8990_write(codec, WM8990_DAC_CTRL, val); + + return 0; +} + +static int wm8990_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 val; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Enable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + WM8990_DIS_RLINE | WM8990_DIS_OUT3 | + WM8990_DIS_OUT4 | WM8990_DIS_LOUT | + WM8990_DIS_ROUT); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_VMIDTOG); + + /* Delay to allow output caps to discharge */ + msleep(msecs_to_jiffies(300)); + + /* Disable VMIDTOG */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL); + + /* disable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, 0); + + /* Enable outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00); + + msleep(msecs_to_jiffies(50)); + + /* Enable VMID at 2x50k */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02); + + msleep(msecs_to_jiffies(100)); + + /* Enable VREF */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); + + msleep(msecs_to_jiffies(600)); + + /* Enable BUFIOEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_BUFIOEN); + + /* Disable outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN); + } else { + /* ON -> standby */ + + } + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_POBCTRL | WM8990_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_BUFIOEN); + + /* mute DAC */ + val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL); + wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + + /* Enable any disabled outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); + + /* Disable VMID */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01); + + msleep(msecs_to_jiffies(300)); + + /* Enable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + WM8990_DIS_RLINE | WM8990_DIS_OUT3 | + WM8990_DIS_OUT4 | WM8990_DIS_LOUT | + WM8990_DIS_ROUT); + + /* Disable VREF */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, 0x0); + break; + } + + codec->bias_level = level; + return 0; +} + +#define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* + * The WM8990 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +struct snd_soc_dai wm8990_dai = { +/* ADC/DAC on primary */ + .name = "WM8990 ADC/DAC Primary", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8990_RATES, + .formats = WM8990_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8990_RATES, + .formats = WM8990_FORMATS,}, + .ops = { + .hw_params = wm8990_hw_params,}, + .dai_ops = { + .digital_mute = wm8990_mute, + .set_fmt = wm8990_set_dai_fmt, + .set_clkdiv = wm8990_set_dai_clkdiv, + .set_pll = wm8990_set_dai_pll, + .set_sysclk = wm8990_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(wm8990_dai); + +static int wm8990_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + /* we only need to suspend if we are a valid card */ + if (!codec->card) + return 0; + + wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8990_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* we only need to resume if we are a valid card */ + if (!codec->card) + return 0; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8990_reg); i++) { + if (i + 1 == WM8990_RESET) + continue; + data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +/* + * initialise the WM8990 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8990_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + u16 reg; + int ret = 0; + + codec->name = "WM8990"; + codec->owner = THIS_MODULE; + codec->read = wm8990_read_reg_cache; + codec->write = wm8990_write; + codec->set_bias_level = wm8990_set_bias_level; + codec->dai = &wm8990_dai; + codec->num_dai = 2; + codec->reg_cache_size = ARRAY_SIZE(wm8990_reg); + codec->reg_cache = kmemdup(wm8990_reg, sizeof(wm8990_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8990_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8990: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps */ + codec->bias_level = SND_SOC_BIAS_OFF; + wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + reg = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_4); + wm8990_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1); + + reg = wm8990_read_reg_cache(codec, WM8990_GPIO1_GPIO2) & + ~WM8990_GPIO1_SEL_MASK; + wm8990_write(codec, WM8990_GPIO1_GPIO2, reg | 1); + + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA); + + wm8990_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + wm8990_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + wm8990_add_controls(codec); + wm8990_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8990: 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 the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8990_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM891 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x34 + * high = 0x36 + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8990_i2c_driver; +static struct i2c_client client_template; + +static int wm8990_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8990_socdev; + struct wm8990_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + pr_err("failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8990_init(socdev); + if (ret < 0) { + pr_err("failed to initialise WM8990\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8990_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8990_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8990_codec_probe); +} + +static struct i2c_driver wm8990_i2c_driver = { + .driver = { + .name = "WM8990 I2C Codec", + .owner = THIS_MODULE, + }, + .attach_adapter = wm8990_i2c_attach, + .detach_client = wm8990_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8990", + .driver = &wm8990_i2c_driver, +}; +#endif + +static int wm8990_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8990_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8990_priv *wm8990; + int ret = 0; + + pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8990 = kzalloc(sizeof(struct wm8990_priv), GFP_KERNEL); + if (wm8990 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8990; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8990_socdev = socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8990_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8990_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8990_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(&wm8990_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8990 = { + .probe = wm8990_probe, + .remove = wm8990_remove, + .suspend = wm8990_suspend, + .resume = wm8990_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8990); + +MODULE_DESCRIPTION("ASoC WM8990 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8990.h b/sound/soc/codecs/wm8990.h new file mode 100644 index 0000000..6bea574 --- /dev/null +++ b/sound/soc/codecs/wm8990.h @@ -0,0 +1,832 @@ +/* + * wm8990.h -- audio driver for WM8990 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * 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 __WM8990REGISTERDEFS_H__ +#define __WM8990REGISTERDEFS_H__ + +/* + * Register values. + */ +#define WM8990_RESET 0x00 +#define WM8990_POWER_MANAGEMENT_1 0x01 +#define WM8990_POWER_MANAGEMENT_2 0x02 +#define WM8990_POWER_MANAGEMENT_3 0x03 +#define WM8990_AUDIO_INTERFACE_1 0x04 +#define WM8990_AUDIO_INTERFACE_2 0x05 +#define WM8990_CLOCKING_1 0x06 +#define WM8990_CLOCKING_2 0x07 +#define WM8990_AUDIO_INTERFACE_3 0x08 +#define WM8990_AUDIO_INTERFACE_4 0x09 +#define WM8990_DAC_CTRL 0x0A +#define WM8990_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8990_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8990_DIGITAL_SIDE_TONE 0x0D +#define WM8990_ADC_CTRL 0x0E +#define WM8990_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8990_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8990_GPIO_CTRL_1 0x12 +#define WM8990_GPIO1_GPIO2 0x13 +#define WM8990_GPIO3_GPIO4 0x14 +#define WM8990_GPIO5_GPIO6 0x15 +#define WM8990_GPIOCTRL_2 0x16 +#define WM8990_GPIO_POL 0x17 +#define WM8990_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8990_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8990_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8990_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8990_LEFT_OUTPUT_VOLUME 0x1C +#define WM8990_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8990_LINE_OUTPUTS_VOLUME 0x1E +#define WM8990_OUT3_4_VOLUME 0x1F +#define WM8990_LEFT_OPGA_VOLUME 0x20 +#define WM8990_RIGHT_OPGA_VOLUME 0x21 +#define WM8990_SPEAKER_VOLUME 0x22 +#define WM8990_CLASSD1 0x23 +#define WM8990_CLASSD3 0x25 +#define WM8990_INPUT_MIXER1 0x27 +#define WM8990_INPUT_MIXER2 0x28 +#define WM8990_INPUT_MIXER3 0x29 +#define WM8990_INPUT_MIXER4 0x2A +#define WM8990_INPUT_MIXER5 0x2B +#define WM8990_INPUT_MIXER6 0x2C +#define WM8990_OUTPUT_MIXER1 0x2D +#define WM8990_OUTPUT_MIXER2 0x2E +#define WM8990_OUTPUT_MIXER3 0x2F +#define WM8990_OUTPUT_MIXER4 0x30 +#define WM8990_OUTPUT_MIXER5 0x31 +#define WM8990_OUTPUT_MIXER6 0x32 +#define WM8990_OUT3_4_MIXER 0x33 +#define WM8990_LINE_MIXER1 0x34 +#define WM8990_LINE_MIXER2 0x35 +#define WM8990_SPEAKER_MIXER 0x36 +#define WM8990_ADDITIONAL_CONTROL 0x37 +#define WM8990_ANTIPOP1 0x38 +#define WM8990_ANTIPOP2 0x39 +#define WM8990_MICBIAS 0x3A +#define WM8990_PLL1 0x3C +#define WM8990_PLL2 0x3D +#define WM8990_PLL3 0x3E +#define WM8990_INTDRIVBITS 0x3F + +#define WM8990_REGISTER_COUNT 60 +#define WM8990_MAX_REGISTER 0x3F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Reset + */ +#define WM8990_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8990_SPK_ENA 0x1000 /* SPK_ENA */ +#define WM8990_SPK_ENA_BIT 12 +#define WM8990_OUT3_ENA 0x0800 /* OUT3_ENA */ +#define WM8990_OUT3_ENA_BIT 11 +#define WM8990_OUT4_ENA 0x0400 /* OUT4_ENA */ +#define WM8990_OUT4_ENA_BIT 10 +#define WM8990_LOUT_ENA 0x0200 /* LOUT_ENA */ +#define WM8990_LOUT_ENA_BIT 9 +#define WM8990_ROUT_ENA 0x0100 /* ROUT_ENA */ +#define WM8990_ROUT_ENA_BIT 8 +#define WM8990_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */ +#define WM8990_MICBIAS_ENA_BIT 4 +#define WM8990_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */ +#define WM8990_VREF_ENA 0x0001 /* VREF_ENA */ +#define WM8990_VREF_ENA_BIT 0 + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8990_PLL_ENA 0x8000 /* PLL_ENA */ +#define WM8990_PLL_ENA_BIT 15 +#define WM8990_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8990_TSHUT_ENA_BIT 14 +#define WM8990_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8990_TSHUT_OPDIS_BIT 13 +#define WM8990_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8990_OPCLK_ENA_BIT 11 +#define WM8990_AINL_ENA 0x0200 /* AINL_ENA */ +#define WM8990_AINL_ENA_BIT 9 +#define WM8990_AINR_ENA 0x0100 /* AINR_ENA */ +#define WM8990_AINR_ENA_BIT 8 +#define WM8990_LIN34_ENA 0x0080 /* LIN34_ENA */ +#define WM8990_LIN34_ENA_BIT 7 +#define WM8990_LIN12_ENA 0x0040 /* LIN12_ENA */ +#define WM8990_LIN12_ENA_BIT 6 +#define WM8990_RIN34_ENA 0x0020 /* RIN34_ENA */ +#define WM8990_RIN34_ENA_BIT 5 +#define WM8990_RIN12_ENA 0x0010 /* RIN12_ENA */ +#define WM8990_RIN12_ENA_BIT 4 +#define WM8990_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8990_ADCL_ENA_BIT 1 +#define WM8990_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8990_ADCR_ENA_BIT 0 + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8990_LON_ENA 0x2000 /* LON_ENA */ +#define WM8990_LON_ENA_BIT 13 +#define WM8990_LOP_ENA 0x1000 /* LOP_ENA */ +#define WM8990_LOP_ENA_BIT 12 +#define WM8990_RON_ENA 0x0800 /* RON_ENA */ +#define WM8990_RON_ENA_BIT 11 +#define WM8990_ROP_ENA 0x0400 /* ROP_ENA */ +#define WM8990_ROP_ENA_BIT 10 +#define WM8990_LOPGA_ENA 0x0080 /* LOPGA_ENA */ +#define WM8990_LOPGA_ENA_BIT 7 +#define WM8990_ROPGA_ENA 0x0040 /* ROPGA_ENA */ +#define WM8990_ROPGA_ENA_BIT 6 +#define WM8990_LOMIX_ENA 0x0020 /* LOMIX_ENA */ +#define WM8990_LOMIX_ENA_BIT 5 +#define WM8990_ROMIX_ENA 0x0010 /* ROMIX_ENA */ +#define WM8990_ROMIX_ENA_BIT 4 +#define WM8990_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8990_DACL_ENA_BIT 1 +#define WM8990_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8990_DACR_ENA_BIT 0 + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8990_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8990_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8990_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8990_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8990_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8990_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8990_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8990_AIF_WL_16BITS (0 << 5) +#define WM8990_AIF_WL_20BITS (1 << 5) +#define WM8990_AIF_WL_24BITS (2 << 5) +#define WM8990_AIF_WL_32BITS (3 << 5) +#define WM8990_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8990_AIF_TMF_RIGHTJ (0 << 3) +#define WM8990_AIF_TMF_LEFTJ (1 << 3) +#define WM8990_AIF_TMF_I2S (2 << 3) +#define WM8990_AIF_TMF_DSP (3 << 3) + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8990_DACL_SRC 0x8000 /* DACL_SRC */ +#define WM8990_DACR_SRC 0x4000 /* DACR_SRC */ +#define WM8990_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8990_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8990_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST */ +#define WM8990_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8990_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8990_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8990_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8990_LOOPBACK 0x0001 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking (1) + */ +#define WM8990_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8990_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8990_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */ +#define WM8990_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8990_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8990_BCLK_DIV_1 (0x0 << 1) +#define WM8990_BCLK_DIV_1_5 (0x1 << 1) +#define WM8990_BCLK_DIV_2 (0x2 << 1) +#define WM8990_BCLK_DIV_3 (0x3 << 1) +#define WM8990_BCLK_DIV_4 (0x4 << 1) +#define WM8990_BCLK_DIV_5_5 (0x5 << 1) +#define WM8990_BCLK_DIV_6 (0x6 << 1) +#define WM8990_BCLK_DIV_8 (0x7 << 1) +#define WM8990_BCLK_DIV_11 (0x8 << 1) +#define WM8990_BCLK_DIV_12 (0x9 << 1) +#define WM8990_BCLK_DIV_16 (0xA << 1) +#define WM8990_BCLK_DIV_22 (0xB << 1) +#define WM8990_BCLK_DIV_24 (0xC << 1) +#define WM8990_BCLK_DIV_32 (0xD << 1) +#define WM8990_BCLK_DIV_44 (0xE << 1) +#define WM8990_BCLK_DIV_48 (0xF << 1) + +/* + * R7 (0x07) - Clocking (2) + */ +#define WM8990_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8990_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8990_CLK_FORCE 0x2000 /* CLK_FORCE */ +#define WM8990_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */ +#define WM8990_MCLK_DIV_1 (0 << 11) +#define WM8990_MCLK_DIV_2 (2 << 11) +#define WM8990_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8990_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV */ +#define WM8990_ADC_CLKDIV_1 (0 << 5) +#define WM8990_ADC_CLKDIV_1_5 (1 << 5) +#define WM8990_ADC_CLKDIV_2 (2 << 5) +#define WM8990_ADC_CLKDIV_3 (3 << 5) +#define WM8990_ADC_CLKDIV_4 (4 << 5) +#define WM8990_ADC_CLKDIV_5_5 (5 << 5) +#define WM8990_ADC_CLKDIV_6 (6 << 5) +#define WM8990_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */ +#define WM8990_DAC_CLKDIV_1 (0 << 2) +#define WM8990_DAC_CLKDIV_1_5 (1 << 2) +#define WM8990_DAC_CLKDIV_2 (2 << 2) +#define WM8990_DAC_CLKDIV_3 (3 << 2) +#define WM8990_DAC_CLKDIV_4 (4 << 2) +#define WM8990_DAC_CLKDIV_5_5 (5 << 2) +#define WM8990_DAC_CLKDIV_6 (6 << 2) + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8990_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8990_AIF_MSTR2 0x4000 /* AIF_MSTR2 */ +#define WM8990_AIF_SEL 0x2000 /* AIF_SEL */ +#define WM8990_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */ +#define WM8990_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8990_ALRCGPIO1 0x8000 /* ALRCGPIO1 */ +#define WM8990_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */ +#define WM8990_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8990_DACLRC_DIR 0x0800 /* DACLRC_DIR */ +#define WM8990_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8990_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */ +#define WM8990_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8990_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8990_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8990_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */ +#define WM8990_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */ +#define WM8990_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8990_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8990_DACR_DATINV 0x0001 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8990_DAC_VU 0x0100 /* DAC_VU */ +#define WM8990_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8990_DACL_VOL_SHIFT 0 +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8990_DAC_VU 0x0100 /* DAC_VU */ +#define WM8990_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8990_DACR_VOL_SHIFT 0 +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8990_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL */ +#define WM8990_ADCL_DAC_SVOL_SHIFT 9 +#define WM8990_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL */ +#define WM8990_ADCR_DAC_SVOL_SHIFT 5 +#define WM8990_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */ +#define WM8990_ADC_TO_DACL_SHIFT 2 +#define WM8990_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */ +#define WM8990_ADC_TO_DACR_SHIFT 0 + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8990_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */ +#define WM8990_ADC_HPF_ENA_BIT 8 +#define WM8990_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */ +#define WM8990_ADC_HPF_CUT_SHIFT 5 +#define WM8990_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8990_ADCL_DATINV_BIT 1 +#define WM8990_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8990_ADCR_DATINV_BIT 0 + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8990_ADC_VU 0x0100 /* ADC_VU */ +#define WM8990_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8990_ADCL_VOL_SHIFT 0 + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8990_ADC_VU 0x0100 /* ADC_VU */ +#define WM8990_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8990_ADCR_VOL_SHIFT 0 + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8990_IRQ 0x1000 /* IRQ */ +#define WM8990_TEMPOK 0x0800 /* TEMPOK */ +#define WM8990_MICSHRT 0x0400 /* MICSHRT */ +#define WM8990_MICDET 0x0200 /* MICDET */ +#define WM8990_PLL_LCK 0x0100 /* PLL_LCK */ +#define WM8990_GPI8_STATUS 0x0080 /* GPI8_STATUS */ +#define WM8990_GPI7_STATUS 0x0040 /* GPI7_STATUS */ +#define WM8990_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */ +#define WM8990_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */ +#define WM8990_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */ +#define WM8990_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */ +#define WM8990_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */ +#define WM8990_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */ + +/* + * R19 (0x13) - GPIO1 & GPIO2 + */ +#define WM8990_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */ +#define WM8990_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */ +#define WM8990_GPIO2_PU 0x2000 /* GPIO2_PU */ +#define WM8990_GPIO2_PD 0x1000 /* GPIO2_PD */ +#define WM8990_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */ +#define WM8990_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */ +#define WM8990_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */ +#define WM8990_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8990_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8990_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - GPIO3 & GPIO4 + */ +#define WM8990_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */ +#define WM8990_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */ +#define WM8990_GPIO4_PU 0x2000 /* GPIO4_PU */ +#define WM8990_GPIO4_PD 0x1000 /* GPIO4_PD */ +#define WM8990_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */ +#define WM8990_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */ +#define WM8990_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */ +#define WM8990_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8990_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8990_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ + +/* + * R21 (0x15) - GPIO5 & GPIO6 + */ +#define WM8990_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */ +#define WM8990_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */ +#define WM8990_GPIO6_PU 0x2000 /* GPIO6_PU */ +#define WM8990_GPIO6_PD 0x1000 /* GPIO6_PD */ +#define WM8990_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */ +#define WM8990_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */ +#define WM8990_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */ +#define WM8990_GPIO5_PU 0x0020 /* GPIO5_PU */ +#define WM8990_GPIO5_PD 0x0010 /* GPIO5_PD */ +#define WM8990_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8990_RD_3W_ENA 0x8000 /* RD_3W_ENA */ +#define WM8990_MODE_3W4W 0x4000 /* MODE_3W4W */ +#define WM8990_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */ +#define WM8990_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */ +#define WM8990_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */ +#define WM8990_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */ +#define WM8990_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */ +#define WM8990_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */ +#define WM8990_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8990_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */ +#define WM8990_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */ +#define WM8990_GPI7_ENA 0x0001 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8990_IRQ_INV 0x1000 /* IRQ_INV */ +#define WM8990_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8990_MICSHRT_POL 0x0400 /* MICSHRT_POL */ +#define WM8990_MICDET_POL 0x0200 /* MICDET_POL */ +#define WM8990_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */ +#define WM8990_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8990_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8990_GPIO6_POL 0x0020 /* GPIO6_POL */ +#define WM8990_GPIO5_POL 0x0010 /* GPIO5_POL */ +#define WM8990_GPIO4_POL 0x0008 /* GPIO4_POL */ +#define WM8990_GPIO3_POL 0x0004 /* GPIO3_POL */ +#define WM8990_GPIO2_POL 0x0002 /* GPIO2_POL */ +#define WM8990_GPIO1_POL 0x0001 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_LI12MUTE 0x0080 /* LI12MUTE */ +#define WM8990_LI12MUTE_BIT 7 +#define WM8990_LI12ZC 0x0040 /* LI12ZC */ +#define WM8990_LI12ZC_BIT 6 +#define WM8990_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */ +#define WM8990_LIN12VOL_SHIFT 0 +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_LI34MUTE 0x0080 /* LI34MUTE */ +#define WM8990_LI34MUTE_BIT 7 +#define WM8990_LI34ZC 0x0040 /* LI34ZC */ +#define WM8990_LI34ZC_BIT 6 +#define WM8990_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */ +#define WM8990_LIN34VOL_SHIFT 0 + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_RI12MUTE 0x0080 /* RI12MUTE */ +#define WM8990_RI12MUTE_BIT 7 +#define WM8990_RI12ZC 0x0040 /* RI12ZC */ +#define WM8990_RI12ZC_BIT 6 +#define WM8990_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */ +#define WM8990_RIN12VOL_SHIFT 0 + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_RI34MUTE 0x0080 /* RI34MUTE */ +#define WM8990_RI34MUTE_BIT 7 +#define WM8990_RI34ZC 0x0040 /* RI34ZC */ +#define WM8990_RI34ZC_BIT 6 +#define WM8990_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */ +#define WM8990_RIN34VOL_SHIFT 0 + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_LOZC 0x0080 /* LOZC */ +#define WM8990_LOZC_BIT 7 +#define WM8990_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8990_LOUTVOL_SHIFT 0 +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_ROZC 0x0080 /* ROZC */ +#define WM8990_ROZC_BIT 7 +#define WM8990_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8990_ROUTVOL_SHIFT 0 +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8990_LONMUTE 0x0040 /* LONMUTE */ +#define WM8990_LONMUTE_BIT 6 +#define WM8990_LOPMUTE 0x0020 /* LOPMUTE */ +#define WM8990_LOPMUTE_BIT 5 +#define WM8990_LOATTN 0x0010 /* LOATTN */ +#define WM8990_LOATTN_BIT 4 +#define WM8990_RONMUTE 0x0004 /* RONMUTE */ +#define WM8990_RONMUTE_BIT 2 +#define WM8990_ROPMUTE 0x0002 /* ROPMUTE */ +#define WM8990_ROPMUTE_BIT 1 +#define WM8990_ROATTN 0x0001 /* ROATTN */ +#define WM8990_ROATTN_BIT 0 + +/* + * R31 (0x1F) - Out3/4 Volume + */ +#define WM8990_OUT3MUTE 0x0020 /* OUT3MUTE */ +#define WM8990_OUT3MUTE_BIT 5 +#define WM8990_OUT3ATTN 0x0010 /* OUT3ATTN */ +#define WM8990_OUT3ATTN_BIT 4 +#define WM8990_OUT4MUTE 0x0002 /* OUT4MUTE */ +#define WM8990_OUT4MUTE_BIT 1 +#define WM8990_OUT4ATTN 0x0001 /* OUT4ATTN */ +#define WM8990_OUT4ATTN_BIT 0 + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_LOPGAZC 0x0080 /* LOPGAZC */ +#define WM8990_LOPGAZC_BIT 7 +#define WM8990_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */ +#define WM8990_LOPGAVOL_SHIFT 0 + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_ROPGAZC 0x0080 /* ROPGAZC */ +#define WM8990_ROPGAZC_BIT 7 +#define WM8990_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */ +#define WM8990_ROPGAVOL_SHIFT 0 +/* + * R34 (0x22) - Speaker Volume + */ +#define WM8990_SPKVOL_MASK 0x0003 /* SPKVOL - [1:0] */ +#define WM8990_SPKVOL_SHIFT 0 + +/* + * R35 (0x23) - ClassD1 + */ +#define WM8990_CDMODE 0x0100 /* CDMODE */ +#define WM8990_CDMODE_BIT 8 + +/* + * R37 (0x25) - ClassD3 + */ +#define WM8990_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */ +#define WM8990_DCGAIN_SHIFT 3 +#define WM8990_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */ +#define WM8990_ACGAIN_SHIFT 0 +/* + * R39 (0x27) - Input Mixer1 + */ +#define WM8990_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */ +#define WM8990_AINLMODE_SHIFT 2 +#define WM8990_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */ +#define WM8990_AINRMODE_SHIFT 0 + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8990_LMP4 0x0080 /* LMP4 */ +#define WM8990_LMP4_BIT 7 /* LMP4 */ +#define WM8990_LMN3 0x0040 /* LMN3 */ +#define WM8990_LMN3_BIT 6 /* LMN3 */ +#define WM8990_LMP2 0x0020 /* LMP2 */ +#define WM8990_LMP2_BIT 5 /* LMP2 */ +#define WM8990_LMN1 0x0010 /* LMN1 */ +#define WM8990_LMN1_BIT 4 /* LMN1 */ +#define WM8990_RMP4 0x0008 /* RMP4 */ +#define WM8990_RMP4_BIT 3 /* RMP4 */ +#define WM8990_RMN3 0x0004 /* RMN3 */ +#define WM8990_RMN3_BIT 2 /* RMN3 */ +#define WM8990_RMP2 0x0002 /* RMP2 */ +#define WM8990_RMP2_BIT 1 /* RMP2 */ +#define WM8990_RMN1 0x0001 /* RMN1 */ +#define WM8990_RMN1_BIT 0 /* RMN1 */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8990_L34MNB 0x0100 /* L34MNB */ +#define WM8990_L34MNB_BIT 8 +#define WM8990_L34MNBST 0x0080 /* L34MNBST */ +#define WM8990_L34MNBST_BIT 7 +#define WM8990_L12MNB 0x0020 /* L12MNB */ +#define WM8990_L12MNB_BIT 5 +#define WM8990_L12MNBST 0x0010 /* L12MNBST */ +#define WM8990_L12MNBST_BIT 4 +#define WM8990_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */ +#define WM8990_LDBVOL_SHIFT 0 + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8990_R34MNB 0x0100 /* R34MNB */ +#define WM8990_R34MNB_BIT 8 +#define WM8990_R34MNBST 0x0080 /* R34MNBST */ +#define WM8990_R34MNBST_BIT 7 +#define WM8990_R12MNB 0x0020 /* R12MNB */ +#define WM8990_R12MNB_BIT 5 +#define WM8990_R12MNBST 0x0010 /* R12MNBST */ +#define WM8990_R12MNBST_BIT 4 +#define WM8990_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */ +#define WM8990_RDBVOL_SHIFT 0 + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8990_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */ +#define WM8990_LI2BVOL_SHIFT 6 +#define WM8990_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */ +#define WM8990_LR4BVOL_SHIFT 3 +#define WM8990_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */ +#define WM8990_LL4BVOL_SHIFT 0 + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8990_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */ +#define WM8990_RI2BVOL_SHIFT 6 +#define WM8990_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */ +#define WM8990_RL4BVOL_SHIFT 3 +#define WM8990_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */ +#define WM8990_RR4BVOL_SHIFT 0 + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8990_LRBLO 0x0080 /* LRBLO */ +#define WM8990_LRBLO_BIT 7 +#define WM8990_LLBLO 0x0040 /* LLBLO */ +#define WM8990_LLBLO_BIT 6 +#define WM8990_LRI3LO 0x0020 /* LRI3LO */ +#define WM8990_LRI3LO_BIT 5 +#define WM8990_LLI3LO 0x0010 /* LLI3LO */ +#define WM8990_LLI3LO_BIT 4 +#define WM8990_LR12LO 0x0008 /* LR12LO */ +#define WM8990_LR12LO_BIT 3 +#define WM8990_LL12LO 0x0004 /* LL12LO */ +#define WM8990_LL12LO_BIT 2 +#define WM8990_LDLO 0x0001 /* LDLO */ +#define WM8990_LDLO_BIT 0 + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8990_RLBRO 0x0080 /* RLBRO */ +#define WM8990_RLBRO_BIT 7 +#define WM8990_RRBRO 0x0040 /* RRBRO */ +#define WM8990_RRBRO_BIT 6 +#define WM8990_RLI3RO 0x0020 /* RLI3RO */ +#define WM8990_RLI3RO_BIT 5 +#define WM8990_RRI3RO 0x0010 /* RRI3RO */ +#define WM8990_RRI3RO_BIT 4 +#define WM8990_RL12RO 0x0008 /* RL12RO */ +#define WM8990_RL12RO_BIT 3 +#define WM8990_RR12RO 0x0004 /* RR12RO */ +#define WM8990_RR12RO_BIT 2 +#define WM8990_RDRO 0x0001 /* RDRO */ +#define WM8990_RDRO_BIT 0 + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8990_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */ +#define WM8990_LLI3LOVOL_SHIFT 6 +#define WM8990_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */ +#define WM8990_LR12LOVOL_SHIFT 3 +#define WM8990_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */ +#define WM8990_LL12LOVOL_SHIFT 0 + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8990_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */ +#define WM8990_RRI3ROVOL_SHIFT 6 +#define WM8990_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */ +#define WM8990_RL12ROVOL_SHIFT 3 +#define WM8990_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */ +#define WM8990_RR12ROVOL_SHIFT 0 + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8990_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */ +#define WM8990_LRI3LOVOL_SHIFT 6 +#define WM8990_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */ +#define WM8990_LRBLOVOL_SHIFT 3 +#define WM8990_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */ +#define WM8990_LLBLOVOL_SHIFT 0 + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8990_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */ +#define WM8990_RLI3ROVOL_SHIFT 6 +#define WM8990_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */ +#define WM8990_RLBROVOL_SHIFT 3 +#define WM8990_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */ +#define WM8990_RRBROVOL_SHIFT 0 + +/* + * R51 (0x33) - Out3/4 Mixer + */ +#define WM8990_VSEL_MASK 0x0180 /* VSEL - [8:7] */ +#define WM8990_LI4O3 0x0020 /* LI4O3 */ +#define WM8990_LI4O3_BIT 5 +#define WM8990_LPGAO3 0x0010 /* LPGAO3 */ +#define WM8990_LPGAO3_BIT 4 +#define WM8990_RI4O4 0x0002 /* RI4O4 */ +#define WM8990_RI4O4_BIT 1 +#define WM8990_RPGAO4 0x0001 /* RPGAO4 */ +#define WM8990_RPGAO4_BIT 0 +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8990_LLOPGALON 0x0040 /* LLOPGALON */ +#define WM8990_LLOPGALON_BIT 6 +#define WM8990_LROPGALON 0x0020 /* LROPGALON */ +#define WM8990_LROPGALON_BIT 5 +#define WM8990_LOPLON 0x0010 /* LOPLON */ +#define WM8990_LOPLON_BIT 4 +#define WM8990_LR12LOP 0x0004 /* LR12LOP */ +#define WM8990_LR12LOP_BIT 2 +#define WM8990_LL12LOP 0x0002 /* LL12LOP */ +#define WM8990_LL12LOP_BIT 1 +#define WM8990_LLOPGALOP 0x0001 /* LLOPGALOP */ +#define WM8990_LLOPGALOP_BIT 0 +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8990_RROPGARON 0x0040 /* RROPGARON */ +#define WM8990_RROPGARON_BIT 6 +#define WM8990_RLOPGARON 0x0020 /* RLOPGARON */ +#define WM8990_RLOPGARON_BIT 5 +#define WM8990_ROPRON 0x0010 /* ROPRON */ +#define WM8990_ROPRON_BIT 4 +#define WM8990_RL12ROP 0x0004 /* RL12ROP */ +#define WM8990_RL12ROP_BIT 2 +#define WM8990_RR12ROP 0x0002 /* RR12ROP */ +#define WM8990_RR12ROP_BIT 1 +#define WM8990_RROPGAROP 0x0001 /* RROPGAROP */ +#define WM8990_RROPGAROP_BIT 0 + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8990_LB2SPK 0x0080 /* LB2SPK */ +#define WM8990_LB2SPK_BIT 7 +#define WM8990_RB2SPK 0x0040 /* RB2SPK */ +#define WM8990_RB2SPK_BIT 6 +#define WM8990_LI2SPK 0x0020 /* LI2SPK */ +#define WM8990_LI2SPK_BIT 5 +#define WM8990_RI2SPK 0x0010 /* RI2SPK */ +#define WM8990_RI2SPK_BIT 4 +#define WM8990_LOPGASPK 0x0008 /* LOPGASPK */ +#define WM8990_LOPGASPK_BIT 3 +#define WM8990_ROPGASPK 0x0004 /* ROPGASPK */ +#define WM8990_ROPGASPK_BIT 2 +#define WM8990_LDSPK 0x0002 /* LDSPK */ +#define WM8990_LDSPK_BIT 1 +#define WM8990_RDSPK 0x0001 /* RDSPK */ +#define WM8990_RDSPK_BIT 0 + +/* + * R55 (0x37) - Additional Control + */ +#define WM8990_VROI 0x0001 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8990_DIS_LLINE 0x0020 /* DIS_LLINE */ +#define WM8990_DIS_RLINE 0x0010 /* DIS_RLINE */ +#define WM8990_DIS_OUT3 0x0008 /* DIS_OUT3 */ +#define WM8990_DIS_OUT4 0x0004 /* DIS_OUT4 */ +#define WM8990_DIS_LOUT 0x0002 /* DIS_LOUT */ +#define WM8990_DIS_ROUT 0x0001 /* DIS_ROUT */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8990_SOFTST 0x0040 /* SOFTST */ +#define WM8990_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8990_BUFDCOPEN 0x0004 /* BUFDCOPEN */ +#define WM8990_POBCTRL 0x0002 /* POBCTRL */ +#define WM8990_VMIDTOG 0x0001 /* VMIDTOG */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8990_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */ +#define WM8990_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */ +#define WM8990_MCD 0x0004 /* MCD */ +#define WM8990_MBSEL 0x0001 /* MBSEL */ + +/* + * R60 (0x3C) - PLL1 + */ +#define WM8990_SDM 0x0080 /* SDM */ +#define WM8990_PRESCALE 0x0040 /* PRESCALE */ +#define WM8990_PLLN_MASK 0x000F /* PLLN - [3:0] */ + +/* + * R61 (0x3D) - PLL2 + */ +#define WM8990_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */ + +/* + * R62 (0x3E) - PLL3 + */ +#define WM8990_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */ + +/* + * R63 (0x3F) - Internal Driver Bits + */ +#define WM8990_INMIXL_PWR_BIT 0 +#define WM8990_AINLMUX_PWR_BIT 1 +#define WM8990_INMIXR_PWR_BIT 2 +#define WM8990_AINRMUX_PWR_BIT 3 + +struct wm8990_setup_data { + unsigned short i2c_address; +}; + +#define WM8990_MCLK_DIV 0 +#define WM8990_DACCLK_DIV 1 +#define WM8990_ADCCLK_DIV 2 +#define WM8990_BCLK_DIV 3 + +extern struct snd_soc_dai wm8990_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8990; + +#endif /* __WM8990REGISTERDEFS_H__ */ +/*------------------------------ END OF FILE ---------------------------------*/ diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 76c1e2d..9fc8edd 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -9,9 +9,6 @@ * 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. - * - * Revision history - * 4th Feb 2006 Initial version. */ #include <linux/init.h> @@ -25,6 +22,7 @@ #include <sound/initval.h> #include <sound/soc.h> #include <sound/soc-dapm.h> +#include "wm9712.h" #define WM9712_VERSION "0.4" @@ -351,7 +349,7 @@ SND_SOC_DAPM_INPUT("MIC1"), SND_SOC_DAPM_INPUT("MIC2"), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* virtual mixer - mixes left & right channels for spk and mono */ {"AC97 Mixer", NULL, "Left DAC"}, {"AC97 Mixer", NULL, "Right DAC"}, @@ -446,21 +444,14 @@ static const char *audio_map[][3] = { {"Speaker PGA", NULL, "Speaker Mux"}, {"LOUT2", NULL, "Speaker PGA"}, {"ROUT2", NULL, "Speaker PGA"}, - - {NULL, NULL, NULL}, }; static int wm9712_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(wm9712_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm9712_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm9712_dapm_widgets, + ARRAY_SIZE(wm9712_dapm_widgets)); - /* set up audio path connects */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(codec); return 0; @@ -541,7 +532,7 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream) SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ SNDRV_PCM_RATE_48000) -struct snd_soc_codec_dai wm9712_dai[] = { +struct snd_soc_dai wm9712_dai[] = { { .name = "AC97 HiFi", .type = SND_SOC_DAI_AC97_BUS, @@ -574,23 +565,23 @@ struct snd_soc_codec_dai wm9712_dai[] = { }; EXPORT_SYMBOL_GPL(wm9712_dai); -static int wm9712_dapm_event(struct snd_soc_codec *codec, int event) +static int wm9712_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: ac97_write(codec, AC97_POWERDOWN, 0x0000); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: /* disable everything including AC link */ ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); ac97_write(codec, AC97_POWERDOWN, 0xffff); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -598,12 +589,12 @@ static int wm9712_reset(struct snd_soc_codec *codec, int try_warm) { if (try_warm && soc_ac97_ops.warm_reset) { soc_ac97_ops.warm_reset(codec->ac97); - if (!(ac97_read(codec, 0) & 0x8000)) + if (ac97_read(codec, 0) == wm9712_reg[0]) return 1; } soc_ac97_ops.reset(codec->ac97); - if (ac97_read(codec, 0) & 0x8000) + if (ac97_read(codec, 0) != wm9712_reg[0]) goto err; return 0; @@ -618,7 +609,7 @@ static int wm9712_soc_suspend(struct platform_device *pdev, struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; - wm9712_dapm_event(codec, SNDRV_CTL_POWER_D3cold); + wm9712_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -635,7 +626,7 @@ static int wm9712_soc_resume(struct platform_device *pdev) return ret; } - wm9712_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret == 0) { /* Sync reg_cache with the hardware after cold reset */ @@ -647,8 +638,8 @@ static int wm9712_soc_resume(struct platform_device *pdev) } } - if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) - wm9712_dapm_event(codec, SNDRV_CTL_POWER_D0); + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + wm9712_set_bias_level(codec, SND_SOC_BIAS_ON); return ret; } @@ -682,7 +673,7 @@ static int wm9712_soc_probe(struct platform_device *pdev) codec->num_dai = ARRAY_SIZE(wm9712_dai); codec->write = ac97_write; codec->read = ac97_read; - codec->dapm_event = wm9712_dapm_event; + codec->set_bias_level = wm9712_set_bias_level; INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -706,7 +697,7 @@ static int wm9712_soc_probe(struct platform_device *pdev) /* set alc mux to none */ ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000); - wm9712_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); wm9712_add_controls(codec); wm9712_add_widgets(codec); ret = snd_soc_register_card(socdev); diff --git a/sound/soc/codecs/wm9712.h b/sound/soc/codecs/wm9712.h index 719105d..d29e8a1 100644 --- a/sound/soc/codecs/wm9712.h +++ b/sound/soc/codecs/wm9712.h @@ -8,7 +8,7 @@ #define WM9712_DAI_AC97_HIFI 0 #define WM9712_DAI_AC97_AUX 1 -extern struct snd_soc_codec_dai wm9712_dai[2]; +extern struct snd_soc_dai wm9712_dai[2]; extern struct snd_soc_codec_device soc_codec_dev_wm9712; #endif diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 1f24116..38d1fe0 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -10,9 +10,6 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * - * Revision history - * 4th Feb 2006 Initial version. - * * Features:- * * o Support for AC97 Codec, Voice DAC and Aux DAC @@ -456,7 +453,7 @@ SND_SOC_DAPM_INPUT("MIC2B"), SND_SOC_DAPM_VMID("VMID"), }; -static const char *audio_map[][3] = { +static const struct snd_soc_dapm_route audio_map[] = { /* left HP mixer */ {"Left HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"}, @@ -607,21 +604,14 @@ static const char *audio_map[][3] = { {"Capture Mono Mux", "Stereo", "Capture Mixer"}, {"Capture Mono Mux", "Left", "Left Capture Source"}, {"Capture Mono Mux", "Right", "Right Capture Source"}, - - {NULL, NULL, NULL}, }; static int wm9713_add_widgets(struct snd_soc_codec *codec) { - int i; - - for (i = 0; i < ARRAY_SIZE(wm9713_dapm_widgets); i++) - snd_soc_dapm_new_control(codec, &wm9713_dapm_widgets[i]); + snd_soc_dapm_new_controls(codec, wm9713_dapm_widgets, + ARRAY_SIZE(wm9713_dapm_widgets)); - /* set up audio path audio_mapnects */ - for (i = 0; audio_map[i][0] != NULL; i++) - snd_soc_dapm_connect_input(codec, audio_map[i][0], - audio_map[i][1], audio_map[i][2]); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); snd_soc_dapm_new_widgets(codec); return 0; @@ -799,7 +789,7 @@ static int wm9713_set_pll(struct snd_soc_codec *codec, return 0; } -static int wm9713_set_dai_pll(struct snd_soc_codec_dai *codec_dai, +static int wm9713_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; @@ -810,7 +800,7 @@ static int wm9713_set_dai_pll(struct snd_soc_codec_dai *codec_dai, * Tristate the PCM DAI lines, tristate can be disabled by calling * wm9713_set_dai_fmt() */ -static int wm9713_set_dai_tristate(struct snd_soc_codec_dai *codec_dai, +static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai, int tristate) { struct snd_soc_codec *codec = codec_dai->codec; @@ -826,7 +816,7 @@ static int wm9713_set_dai_tristate(struct snd_soc_codec_dai *codec_dai, * Configure WM9713 clock dividers. * Voice DAC needs 256 FS */ -static int wm9713_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, +static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) { struct snd_soc_codec *codec = codec_dai->codec; @@ -868,7 +858,7 @@ static int wm9713_set_dai_clkdiv(struct snd_soc_codec_dai *codec_dai, return 0; } -static int wm9713_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, +static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; @@ -886,7 +876,7 @@ static int wm9713_set_dai_fmt(struct snd_soc_codec_dai *codec_dai, gpio |= 0x0018; break; case SND_SOC_DAIFMT_CBS_CFS: - reg |= 0x0200; + reg |= 0x2000; gpio |= 0x001a; break; case SND_SOC_DAIFMT_CBS_CFM: @@ -1011,15 +1001,24 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream) return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate); } -#define WM9713_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ - SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ - SNDRV_PCM_RATE_48000) +#define WM9713_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) #define WM9713_PCM_FORMATS \ (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ SNDRV_PCM_FORMAT_S24_LE) -struct snd_soc_codec_dai wm9713_dai[] = { +struct snd_soc_dai wm9713_dai[] = { { .name = "AC97 HiFi", .type = SND_SOC_DAI_AC97_BUS, @@ -1061,13 +1060,13 @@ struct snd_soc_codec_dai wm9713_dai[] = { .stream_name = "Voice Playback", .channels_min = 1, .channels_max = 1, - .rates = WM9713_RATES, + .rates = WM9713_PCM_RATES, .formats = WM9713_PCM_FORMATS,}, .capture = { .stream_name = "Voice Capture", .channels_min = 1, .channels_max = 2, - .rates = WM9713_RATES, + .rates = WM9713_PCM_RATES, .formats = WM9713_PCM_FORMATS,}, .ops = { .hw_params = wm9713_pcm_hw_params, @@ -1086,44 +1085,44 @@ int wm9713_reset(struct snd_soc_codec *codec, int try_warm) { if (try_warm && soc_ac97_ops.warm_reset) { soc_ac97_ops.warm_reset(codec->ac97); - if (!(ac97_read(codec, 0) & 0x8000)) + if (ac97_read(codec, 0) == wm9713_reg[0]) return 1; } soc_ac97_ops.reset(codec->ac97); - if (ac97_read(codec, 0) & 0x8000) + if (ac97_read(codec, 0) != wm9713_reg[0]) return -EIO; return 0; } EXPORT_SYMBOL_GPL(wm9713_reset); -static int wm9713_dapm_event(struct snd_soc_codec *codec, int event) +static int wm9713_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) { u16 reg; - switch (event) { - case SNDRV_CTL_POWER_D0: /* full On */ + switch (level) { + case SND_SOC_BIAS_ON: /* enable thermal shutdown */ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff; ac97_write(codec, AC97_EXTENDED_MID, reg); break; - case SNDRV_CTL_POWER_D1: /* partial On */ - case SNDRV_CTL_POWER_D2: /* partial On */ + case SND_SOC_BIAS_PREPARE: break; - case SNDRV_CTL_POWER_D3hot: /* Off, with power */ + case SND_SOC_BIAS_STANDBY: /* enable master bias and vmid */ reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff; ac97_write(codec, AC97_EXTENDED_MID, reg); ac97_write(codec, AC97_POWERDOWN, 0x0000); break; - case SNDRV_CTL_POWER_D3cold: /* Off, without power */ + case SND_SOC_BIAS_OFF: /* disable everything including AC link */ ac97_write(codec, AC97_EXTENDED_MID, 0xffff); ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); ac97_write(codec, AC97_POWERDOWN, 0xffff); break; } - codec->dapm_state = event; + codec->bias_level = level; return 0; } @@ -1160,7 +1159,7 @@ static int wm9713_soc_resume(struct platform_device *pdev) return ret; } - wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* do we need to re-start the PLL ? */ if (wm9713->pll_out) @@ -1176,8 +1175,8 @@ static int wm9713_soc_resume(struct platform_device *pdev) } } - if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) - wm9713_dapm_event(codec, SNDRV_CTL_POWER_D0); + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + wm9713_set_bias_level(codec, SND_SOC_BIAS_ON); return ret; } @@ -1216,7 +1215,7 @@ static int wm9713_soc_probe(struct platform_device *pdev) codec->num_dai = ARRAY_SIZE(wm9713_dai); codec->write = ac97_write; codec->read = ac97_read; - codec->dapm_event = wm9713_dapm_event; + codec->set_bias_level = wm9713_set_bias_level; INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); @@ -1238,7 +1237,7 @@ static int wm9713_soc_probe(struct platform_device *pdev) goto reset_err; } - wm9713_dapm_event(codec, SNDRV_CTL_POWER_D3hot); + wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* unmute the adc - move to kcontrol */ reg = ac97_read(codec, AC97_CD) & 0x7fff; diff --git a/sound/soc/codecs/wm9713.h b/sound/soc/codecs/wm9713.h index d357b6c..63b8d81 100644 --- a/sound/soc/codecs/wm9713.h +++ b/sound/soc/codecs/wm9713.h @@ -46,7 +46,7 @@ #define WM9713_DAI_PCM_VOICE 2 extern struct snd_soc_codec_device soc_codec_dev_wm9713; -extern struct snd_soc_codec_dai wm9713_dai[3]; +extern struct snd_soc_dai wm9713_dai[3]; int wm9713_reset(struct snd_soc_codec *codec, int try_warm); |