diff options
author | Takashi Iwai <tiwai@suse.de> | 2010-08-05 11:17:01 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2010-08-05 11:17:01 +0200 |
commit | e71981343ad29b5d929f82ac56c0b27b8ea0e540 (patch) | |
tree | 47135be4252faecbc0e5508658a58f8afd197fff /sound/soc/codecs | |
parent | 2603798070a80d76e7e6d2992ba4ec74addcec90 (diff) | |
parent | bda7d2a862e6b788bca2d02d38a07966a9c92e48 (diff) |
Merge branch 'topic/asoc' into for-linus
Diffstat (limited to 'sound/soc/codecs')
38 files changed, 3122 insertions, 358 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5da30eb6ad0..83f5c67d3c4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -22,9 +22,11 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4642 if I2C select SND_SOC_AK4671 if I2C select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC + select SND_SOC_CS42L51 if I2C select SND_SOC_CS4270 if I2C - select SND_SOC_MAX9877 if I2C select SND_SOC_DA7210 if I2C + select SND_SOC_JZ4740 if SOC_JZ4740 + select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C @@ -48,6 +50,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8727 select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8741 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI @@ -120,13 +123,13 @@ config SND_SOC_AK4671 config SND_SOC_CQ0093VC tristate +config SND_SOC_CS42L51 + tristate + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate -config SND_SOC_DA7210 - tristate - # Cirrus Logic CS4270 Codec VD = 3.3V Errata # Select if you are affected by the errata where the part will not function # if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will @@ -138,9 +141,15 @@ config SND_SOC_CS4270_VD33_ERRATA config SND_SOC_CX20442 tristate +config SND_SOC_JZ4740_CODEC + tristate + config SND_SOC_L3 tristate +config SND_SOC_DA7210 + tristate + config SND_SOC_PCM3008 tristate @@ -206,6 +215,9 @@ config SND_SOC_WM8728 config SND_SOC_WM8731 tristate +config SND_SOC_WM8741 + tristate + config SND_SOC_WM8750 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 91429eab070..53524095759 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -9,6 +9,7 @@ snd-soc-ak4535-objs := ak4535.o snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-cq93vc-objs := cq93vc.o +snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o @@ -34,6 +35,7 @@ snd-soc-wm8711-objs := wm8711.o snd-soc-wm8727-objs := wm8727.o snd-soc-wm8728-objs := wm8728.o snd-soc-wm8731-objs := wm8731.o +snd-soc-wm8741-objs := wm8741.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm8776-objs := wm8776.o @@ -56,6 +58,7 @@ snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o +snd-soc-jz4740-codec-objs := jz4740.o # Amp snd-soc-max9877-objs := max9877.o @@ -74,10 +77,12 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o +obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o +obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o @@ -99,6 +104,7 @@ obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o obj-$(CONFIG_SND_SOC_WM8727) += snd-soc-wm8727.o obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o +obj-$(CONFIG_SND_SOC_WM8741) += snd-soc-wm8741.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c index 21753842322..a01006c8c60 100644 --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -272,6 +272,7 @@ static int ad1836_register(struct ad1836_priv *ad1836) if (ad1836_codec) { dev_err(codec->dev, "Another ad1836 is registered\n"); + kfree(ad1836); return -EINVAL; } diff --git a/sound/soc/codecs/ad193x.c b/sound/soc/codecs/ad193x.c index c8ca1142b2f..1def75e4862 100644 --- a/sound/soc/codecs/ad193x.c +++ b/sound/soc/codecs/ad193x.c @@ -24,6 +24,7 @@ /* codec private data */ struct ad193x_priv { + unsigned int sysclk; struct snd_soc_codec codec; u8 reg_cache[AD193X_NUM_REGS]; }; @@ -251,15 +252,32 @@ static int ad193x_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static int ad193x_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 ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); + switch (freq) { + case 12288000: + case 18432000: + case 24576000: + case 36864000: + ad193x->sysclk = freq; + return 0; + } + return -EINVAL; +} + static int ad193x_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - int word_len = 0, reg = 0; + int word_len = 0, reg = 0, master_rate = 0; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct ad193x_priv *ad193x = snd_soc_codec_get_drvdata(codec); /* bit size */ switch (params_format(params)) { @@ -275,6 +293,25 @@ static int ad193x_hw_params(struct snd_pcm_substream *substream, break; } + switch (ad193x->sysclk) { + case 12288000: + master_rate = AD193X_PLL_INPUT_256; + break; + case 18432000: + master_rate = AD193X_PLL_INPUT_384; + break; + case 24576000: + master_rate = AD193X_PLL_INPUT_512; + break; + case 36864000: + master_rate = AD193X_PLL_INPUT_768; + break; + } + + reg = snd_soc_read(codec, AD193X_PLL_CLK_CTRL0); + reg = (reg & AD193X_PLL_INPUT_MASK) | master_rate; + snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, reg); + reg = snd_soc_read(codec, AD193X_DAC_CTRL2); reg = (reg & (~AD193X_DAC_WORD_LEN_MASK)) | word_len; snd_soc_write(codec, AD193X_DAC_CTRL2, reg); @@ -348,6 +385,7 @@ static int ad193x_bus_probe(struct device *dev, void *ctrl_data, int bus_type) /* pll input: mclki/xi */ snd_soc_write(codec, AD193X_PLL_CLK_CTRL0, 0x99); /* mclk=24.576Mhz: 0x9D; mclk=12.288Mhz: 0x99 */ snd_soc_write(codec, AD193X_PLL_CLK_CTRL1, 0x04); + ad193x->sysclk = 12288000; ret = snd_soc_register_codec(codec); if (ret != 0) { @@ -383,6 +421,7 @@ static struct snd_soc_dai_ops ad193x_dai_ops = { .hw_params = ad193x_hw_params, .digital_mute = ad193x_mute, .set_tdm_slot = ad193x_set_tdm_slot, + .set_sysclk = ad193x_set_dai_sysclk, .set_fmt = ad193x_set_dai_fmt, }; diff --git a/sound/soc/codecs/ad193x.h b/sound/soc/codecs/ad193x.h index a03c880d52f..654ba64ae04 100644 --- a/sound/soc/codecs/ad193x.h +++ b/sound/soc/codecs/ad193x.h @@ -11,6 +11,11 @@ #define AD193X_PLL_CLK_CTRL0 0x800 #define AD193X_PLL_POWERDOWN 0x01 +#define AD193X_PLL_INPUT_MASK (~0x6) +#define AD193X_PLL_INPUT_256 (0 << 1) +#define AD193X_PLL_INPUT_384 (1 << 1) +#define AD193X_PLL_INPUT_512 (2 << 1) +#define AD193X_PLL_INPUT_768 (3 << 1) #define AD193X_PLL_CLK_CTRL1 0x801 #define AD193X_DAC_CTRL0 0x802 #define AD193X_DAC_POWERDOWN 0x01 diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c index 7528a54102b..3d7dc55305e 100644 --- a/sound/soc/codecs/ak4642.c +++ b/sound/soc/codecs/ak4642.c @@ -22,20 +22,13 @@ * AK4643 is tested. */ -#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 <linux/slab.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> +#include <sound/tlv.h> #include "ak4642.h" @@ -111,6 +104,23 @@ struct snd_soc_codec_device soc_codec_dev_ak4642; +/* + * Playback Volume (table 39) + * + * max : 0x00 : +12.0 dB + * ( 0.5 dB step ) + * min : 0xFE : -115.0 dB + * mute: 0xFF + */ +static const DECLARE_TLV_DB_SCALE(out_tlv, -11500, 50, 1); + +static const struct snd_kcontrol_new ak4642_snd_controls[] = { + + SOC_DOUBLE_R_TLV("Digital Playback Volume", L_DVC, R_DVC, + 0, 0xFF, 1, out_tlv), +}; + + /* codec private data */ struct ak4642_priv { struct snd_soc_codec codec; @@ -204,7 +214,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, * * PLL, Master Mode * Audio I/F Format :MSB justified (ADC & DAC) - * Digital Volume: -8dB * Bass Boost Level : Middle * * This operation came from example code of @@ -214,8 +223,6 @@ static int ak4642_dai_startup(struct snd_pcm_substream *substream, ak4642_write(codec, 0x0e, 0x19); ak4642_write(codec, 0x09, 0x91); ak4642_write(codec, 0x0c, 0x91); - ak4642_write(codec, 0x0a, 0x28); - ak4642_write(codec, 0x0d, 0x28); ak4642_write(codec, 0x00, 0x64); snd_soc_update_bits(codec, PW_MGMT2, PMHP_MASK, PMHP); snd_soc_update_bits(codec, PW_MGMT2, HPMTN, HPMTN); @@ -491,8 +498,10 @@ static int ak4642_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; ret = ak4642_init(ak4642); - if (ret < 0) + if (ret < 0) { printk(KERN_ERR "failed to initialise AK4642\n"); + kfree(ak4642); + } return ret; } @@ -548,6 +557,9 @@ static int ak4642_probe(struct platform_device *pdev) goto pcm_err; } + snd_soc_add_controls(ak4642_codec, ak4642_snd_controls, + ARRAY_SIZE(ak4642_snd_controls)); + dev_info(&pdev->dev, "AK4642 Audio Codec %s", AK4642_VERSION); return ret; diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c new file mode 100644 index 00000000000..dd9b8550c40 --- /dev/null +++ b/sound/soc/codecs/cs42l51.c @@ -0,0 +1,763 @@ +/* + * cs42l51.c + * + * ASoC Driver for Cirrus Logic CS42L51 codecs + * + * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com> + * + * Based on cs4270.c - Copyright (c) Freescale Semiconductor + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For now: + * - Only I2C is support. Not SPI + * - master mode *NOT* supported + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> +#include <sound/pcm.h> +#include <linux/i2c.h> + +#include "cs42l51.h" + +enum master_slave_mode { + MODE_SLAVE, + MODE_SLAVE_AUTO, + MODE_MASTER, +}; + +struct cs42l51_private { + unsigned int mclk; + unsigned int audio_mode; /* The mode (I2S or left-justified) */ + enum master_slave_mode func; + struct snd_soc_codec codec; + u8 reg_cache[CS42L51_NUMREGS]; +}; + +static struct snd_soc_codec *cs42l51_codec; + +#define CS42L51_FORMATS ( \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +static int cs42l51_fill_cache(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache + 1; + struct i2c_client *i2c_client = codec->control_data; + s32 length; + + length = i2c_smbus_read_i2c_block_data(i2c_client, + CS42L51_FIRSTREG | 0x80, CS42L51_NUMREGS, cache); + if (length != CS42L51_NUMREGS) { + dev_err(&i2c_client->dev, + "I2C read failure, addr=0x%x (ret=%d vs %d)\n", + i2c_client->addr, length, CS42L51_NUMREGS); + return -EIO; + } + + return 0; +} + +static int cs42l51_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct snd_soc_codec *codec; + struct cs42l51_private *cs42l51; + int ret = 0; + int reg; + + if (cs42l51_codec) + return -EBUSY; + + /* Verify that we have a CS42L51 */ + ret = i2c_smbus_read_byte_data(i2c_client, CS42L51_CHIP_REV_ID); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to read I2C\n"); + goto error; + } + + if ((ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) && + (ret != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) { + dev_err(&i2c_client->dev, "Invalid chip id\n"); + ret = -ENODEV; + goto error; + } + + dev_info(&i2c_client->dev, "found device cs42l51 rev %d\n", + ret & 7); + + cs42l51 = kzalloc(sizeof(struct cs42l51_private), GFP_KERNEL); + if (!cs42l51) { + dev_err(&i2c_client->dev, "could not allocate codec\n"); + return -ENOMEM; + } + codec = &cs42l51->codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->dev = &i2c_client->dev; + codec->name = "CS42L51"; + codec->owner = THIS_MODULE; + codec->dai = &cs42l51_dai; + codec->num_dai = 1; + snd_soc_codec_set_drvdata(codec, cs42l51); + + codec->control_data = i2c_client; + codec->reg_cache = cs42l51->reg_cache; + codec->reg_cache_size = CS42L51_NUMREGS; + i2c_set_clientdata(i2c_client, codec); + + ret = cs42l51_fill_cache(codec); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to fill register cache\n"); + goto error_alloc; + } + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to set cache I/O: %d\n", ret); + goto error_alloc; + } + + /* + * DAC configuration + * - Use signal processor + * - auto mute + * - vol changes immediate + * - no de-emphasize + */ + reg = CS42L51_DAC_CTL_DATA_SEL(1) + | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0); + ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg); + if (ret < 0) + goto error_alloc; + + cs42l51_dai.dev = codec->dev; + cs42l51_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto error_alloc; + } + + ret = snd_soc_register_dai(&cs42l51_dai); + if (ret < 0) { + dev_err(&i2c_client->dev, "failed to register DAIe\n"); + goto error_reg; + } + + return 0; + +error_reg: + snd_soc_unregister_codec(codec); +error_alloc: + kfree(cs42l51); +error: + return ret; +} + +static int cs42l51_i2c_remove(struct i2c_client *client) +{ + struct cs42l51_private *cs42l51 = i2c_get_clientdata(client); + snd_soc_unregister_dai(&cs42l51_dai); + snd_soc_unregister_codec(cs42l51_codec); + cs42l51_codec = NULL; + kfree(cs42l51); + return 0; +} + + +static const struct i2c_device_id cs42l51_id[] = { + {"cs42l51", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs42l51_id); + +static struct i2c_driver cs42l51_i2c_driver = { + .driver = { + .name = "CS42L51 I2C", + .owner = THIS_MODULE, + }, + .id_table = cs42l51_id, + .probe = cs42l51_i2c_probe, + .remove = cs42l51_i2c_remove, +}; + +static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3; + + switch (value) { + default: + case 0: + ucontrol->value.integer.value[0] = 0; + break; + /* same value : (L+R)/2 and (R+L)/2 */ + case 1: + case 2: + ucontrol->value.integer.value[0] = 1; + break; + case 3: + ucontrol->value.integer.value[0] = 2; + break; + } + + return 0; +} + +#define CHAN_MIX_NORMAL 0x00 +#define CHAN_MIX_BOTH 0x55 +#define CHAN_MIX_SWAP 0xFF + +static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned char val; + + switch (ucontrol->value.integer.value[0]) { + default: + case 0: + val = CHAN_MIX_NORMAL; + break; + case 1: + val = CHAN_MIX_BOTH; + break; + case 2: + val = CHAN_MIX_SWAP; + break; + } + + snd_soc_write(codec, CS42L51_PCM_MIXER, val); + + return 1; +} + +static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0); +static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); +/* This is a lie. after -102 db, it stays at -102 */ +/* maybe a range would be better */ +static const DECLARE_TLV_DB_SCALE(aout_tlv, -11550, 50, 0); + +static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); +static const char *chan_mix[] = { + "L R", + "L+R", + "R L", +}; + +static const struct soc_enum cs42l51_chan_mix = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(chan_mix), chan_mix); + +static const struct snd_kcontrol_new cs42l51_snd_controls[] = { + SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", + CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, + 7, 0xffffff99, 0x18, adc_pcm_tlv), + SOC_DOUBLE_R("PCM Playback Switch", + CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), + SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", + CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, + 8, 0xffffff19, 0x18, aout_tlv), + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", + CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, + 7, 0xffffff99, 0x18, adc_pcm_tlv), + SOC_DOUBLE_R("ADC Mixer Switch", + CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), + SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), + SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0), + SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0), + SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), + SOC_DOUBLE_TLV("Mic Boost Volume", + CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), + SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), + SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), + SOC_ENUM_EXT("PCM channel mixer", + cs42l51_chan_mix, + cs42l51_get_chan_mix, cs42l51_set_chan_mix), +}; + +/* + * to power down, one must: + * 1.) Enable the PDN bit + * 2.) enable power-down for the select channels + * 3.) disable the PDN bit. + */ +static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + unsigned long value; + + value = snd_soc_read(w->codec, CS42L51_POWER_CTL1); + value &= ~CS42L51_POWER_CTL1_PDN; + + switch (event) { + case SND_SOC_DAPM_PRE_PMD: + value |= CS42L51_POWER_CTL1_PDN; + break; + default: + case SND_SOC_DAPM_POST_PMD: + break; + } + snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, + CS42L51_POWER_CTL1_PDN, value); + + return 0; +} + +static const char *cs42l51_dac_names[] = {"Direct PCM", + "DSP PCM", "ADC"}; +static const struct soc_enum cs42l51_dac_mux_enum = + SOC_ENUM_SINGLE(CS42L51_DAC_CTL, 6, 3, cs42l51_dac_names); +static const struct snd_kcontrol_new cs42l51_dac_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum); + +static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left", + "MIC Left", "MIC+preamp Left"}; +static const struct soc_enum cs42l51_adcl_mux_enum = + SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 4, 4, cs42l51_adcl_names); +static const struct snd_kcontrol_new cs42l51_adcl_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum); + +static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right", + "MIC Right", "MIC+preamp Right"}; +static const struct soc_enum cs42l51_adcr_mux_enum = + SOC_ENUM_SINGLE(CS42L51_ADC_INPUT, 6, 4, cs42l51_adcr_names); +static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = + SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); + +static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), + SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", + CS42L51_POWER_CTL1, 1, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", + CS42L51_POWER_CTL1, 2, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", + CS42L51_POWER_CTL1, 5, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", + CS42L51_POWER_CTL1, 6, 1, + cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), + + /* analog/mic */ + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("MICR"), + + SND_SOC_DAPM_MIXER("Mic Preamp Left", + CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0), + SND_SOC_DAPM_MIXER("Mic Preamp Right", + CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0), + + /* HP */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + + /* mux */ + SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, + &cs42l51_dac_mux_controls), + SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0, + &cs42l51_adcl_mux_controls), + SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0, + &cs42l51_adcr_mux_controls), +}; + +static const struct snd_soc_dapm_route cs42l51_routes[] = { + {"HPL", NULL, "Left DAC"}, + {"HPR", NULL, "Right DAC"}, + + {"Left ADC", NULL, "Left PGA"}, + {"Right ADC", NULL, "Right PGA"}, + + {"Mic Preamp Left", NULL, "MICL"}, + {"Mic Preamp Right", NULL, "MICR"}, + + {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" }, + {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" }, + {"PGA-ADC Mux Left", "MIC Left", "MICL" }, + {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" }, + {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" }, + {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" }, + {"PGA-ADC Mux Right", "MIC Right", "MICR" }, + {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" }, + + {"Left PGA", NULL, "PGA-ADC Mux Left"}, + {"Right PGA", NULL, "PGA-ADC Mux Right"}, +}; + +static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + case SND_SOC_DAIFMT_RIGHT_J: + cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + dev_err(codec->dev, "invalid DAI format\n"); + ret = -EINVAL; + } + + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cs42l51->func = MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + cs42l51->func = MODE_SLAVE_AUTO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +struct cs42l51_ratios { + unsigned int ratio; + unsigned char speed_mode; + unsigned char mclk; +}; + +static struct cs42l51_ratios slave_ratios[] = { + { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 }, + { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, + { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 }, + { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 }, + { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, + { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 }, + { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 }, + { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, + { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 }, + { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, + { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 }, +}; + |