From a91eb199e4dc8a2ab3fb7a53f1a23ce82b29fc04 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 26 Nov 2009 11:56:07 +0000 Subject: ASoC: Initial WM8904 CODEC driver The WM8904 is a high performance ultra-low power stereo CODEC optimised for portable audio applications, with features including a class W amplifier, FLL with free running mode, Mobile ReTune and ground referenced headphone and line outputs. Support for some features, most particularly the digital microphone interface, is not yet present. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/Kconfig | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'sound/soc/codecs/Kconfig') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 52b005f8fed4..011d3ab7e64a 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -49,6 +49,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8900 if I2C select SND_SOC_WM8903 if I2C + select SND_SOC_WM8904 if I2C select SND_SOC_WM8940 if I2C select SND_SOC_WM8960 if I2C select SND_SOC_WM8961 if I2C @@ -203,6 +204,9 @@ config SND_SOC_WM8900 config SND_SOC_WM8903 tristate +config SND_SOC_WM8904 + tristate + config SND_SOC_WM8940 tristate -- cgit v1.2.3 From 98615454f66175e923f239ab1d1bd85cd618363e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 14 Dec 2009 13:21:56 +0900 Subject: ASoC: Add DA7210 codec device support for ALSA This original driver was created by Dialog Semiconductor, and cleanuped by Kuninori Morimoto. Special thanks to David Chen. This became very simple ASoC codec driver, and it is tested by EcoVec24 board. Signed-off-by: David Chen Signed-off-by: Kuninori Morimoto Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/da7210.c | 586 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/da7210.h | 24 ++ 4 files changed, 616 insertions(+) create mode 100644 sound/soc/codecs/da7210.c create mode 100644 sound/soc/codecs/da7210.h (limited to 'sound/soc/codecs/Kconfig') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 011d3ab7e64a..691abe7df087 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -23,6 +23,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4671 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_MAX9877 if I2C + select SND_SOC_DA7210 if I2C select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C @@ -113,6 +114,9 @@ config SND_SOC_AK4671 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 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 0471d9044205..b328f293be65 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -10,6 +10,7 @@ snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o +snd-soc-da7210-objs := da7210.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-spdif-objs := spdif_transciever.o @@ -67,6 +68,7 @@ obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.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_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c new file mode 100644 index 000000000000..14f5f344b1d5 --- /dev/null +++ b/sound/soc/codecs/da7210.c @@ -0,0 +1,586 @@ +/* + * DA7210 ALSA Soc codec driver + * + * Copyright (c) 2009 Dialog Semiconductor + * Written by David Chen + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Cleanups by Kuninori Morimoto + * + * Tested on SuperH Ecovec24 board with S16/S24 LE in 48KHz using I2S + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "da7210.h" + +/* DA7210 register space */ +#define DA7210_STATUS 0x02 +#define DA7210_STARTUP1 0x03 +#define DA7210_MIC_L 0x07 +#define DA7210_MIC_R 0x08 +#define DA7210_INMIX_L 0x0D +#define DA7210_INMIX_R 0x0E +#define DA7210_ADC_HPF 0x0F +#define DA7210_ADC 0x10 +#define DA7210_DAC_HPF 0x14 +#define DA7210_DAC_L 0x15 +#define DA7210_DAC_R 0x16 +#define DA7210_DAC_SEL 0x17 +#define DA7210_OUTMIX_L 0x1C +#define DA7210_OUTMIX_R 0x1D +#define DA7210_HP_L_VOL 0x21 +#define DA7210_HP_R_VOL 0x22 +#define DA7210_HP_CFG 0x23 +#define DA7210_DAI_SRC_SEL 0x25 +#define DA7210_DAI_CFG1 0x26 +#define DA7210_DAI_CFG3 0x28 +#define DA7210_PLL_DIV3 0x2B +#define DA7210_PLL 0x2C + +/* STARTUP1 bit fields */ +#define DA7210_SC_MST_EN (1 << 0) + +/* MIC_L bit fields */ +#define DA7210_MICBIAS_EN (1 << 6) +#define DA7210_MIC_L_EN (1 << 7) + +/* MIC_R bit fields */ +#define DA7210_MIC_R_EN (1 << 7) + +/* INMIX_L bit fields */ +#define DA7210_IN_L_EN (1 << 7) + +/* INMIX_R bit fields */ +#define DA7210_IN_R_EN (1 << 7) + +/* ADC_HPF bit fields */ +#define DA7210_ADC_VOICE_EN (1 << 7) + +/* ADC bit fields */ +#define DA7210_ADC_L_EN (1 << 3) +#define DA7210_ADC_R_EN (1 << 7) + +/* DAC_SEL bit fields */ +#define DA7210_DAC_L_SRC_DAI_L (4 << 0) +#define DA7210_DAC_L_EN (1 << 3) +#define DA7210_DAC_R_SRC_DAI_R (5 << 4) +#define DA7210_DAC_R_EN (1 << 7) + +/* OUTMIX_L bit fields */ +#define DA7210_OUT_L_EN (1 << 7) + +/* OUTMIX_R bit fields */ +#define DA7210_OUT_R_EN (1 << 7) + +/* HP_CFG bit fields */ +#define DA7210_HP_2CAP_MODE (1 << 1) +#define DA7210_HP_SENSE_EN (1 << 2) +#define DA7210_HP_L_EN (1 << 3) +#define DA7210_HP_MODE (1 << 6) +#define DA7210_HP_R_EN (1 << 7) + +/* DAI_SRC_SEL bit fields */ +#define DA7210_DAI_OUT_L_SRC (6 << 0) +#define DA7210_DAI_OUT_R_SRC (7 << 4) + +/* DAI_CFG1 bit fields */ +#define DA7210_DAI_WORD_S16_LE (0 << 0) +#define DA7210_DAI_WORD_S24_LE (2 << 0) +#define DA7210_DAI_FLEN_64BIT (1 << 2) +#define DA7210_DAI_MODE_MASTER (1 << 7) + +/* DAI_CFG3 bit fields */ +#define DA7210_DAI_FORMAT_I2SMODE (0 << 0) +#define DA7210_DAI_OE (1 << 3) +#define DA7210_DAI_EN (1 << 7) + +/*PLL_DIV3 bit fields */ +#define DA7210_MCLK_RANGE_10_20_MHZ (1 << 4) +#define DA7210_PLL_BYP (1 << 6) + +/* PLL bit fields */ +#define DA7210_PLL_FS_48000 (11 << 0) + +#define DA7210_VERSION "0.0.1" + +/* Codec private data */ +struct da7210_priv { + struct snd_soc_codec codec; +}; + +static struct snd_soc_codec *da7210_codec; + +/* + * Register cache + */ +static const u8 da7210_reg[] = { + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R0 - R7 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, /* R8 - RF */ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x10, 0x54, /* R10 - R17 */ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R18 - R1F */ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, /* R20 - R27 */ + 0x04, 0x00, 0x00, 0x30, 0x2A, 0x00, 0x40, 0x00, /* R28 - R2F */ + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, /* R30 - R37 */ + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, /* R38 - R3F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R40 - R4F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R48 - R4F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R50 - R57 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R58 - R5F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R60 - R67 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R68 - R6F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R70 - R77 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0x00, /* R78 - R7F */ + 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, /* R80 - R87 */ + 0x00, /* R88 */ +}; + +/* + * Read da7210 register cache + */ +static inline u32 da7210_read_reg_cache(struct snd_soc_codec *codec, u32 reg) +{ + u8 *cache = codec->reg_cache; + BUG_ON(reg > ARRAY_SIZE(da7210_reg)); + return cache[reg]; +} + +/* + * Write to the da7210 register space + */ +static int da7210_write(struct snd_soc_codec *codec, u32 reg, u32 value) +{ + u8 *cache = codec->reg_cache; + u8 data[2]; + + BUG_ON(codec->volatile_register); + + data[0] = reg & 0xff; + data[1] = value & 0xff; + + if (reg >= codec->reg_cache_size) + return -EIO; + + if (2 != codec->hw_write(codec->control_data, data, 2)) + return -EIO; + + cache[reg] = value; + return 0; +} + +/* + * Read from the da7210 register space. + */ +static inline u32 da7210_read(struct snd_soc_codec *codec, u32 reg) +{ + if (DA7210_STATUS == reg) + return i2c_smbus_read_byte_data(codec->control_data, reg); + + return da7210_read_reg_cache(codec, reg); +} + +static int da7210_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_codec *codec = dai->codec; + + if (is_play) { + /* PlayBack Volume 40 */ + snd_soc_update_bits(codec, DA7210_HP_L_VOL, 0x3F, 40); + snd_soc_update_bits(codec, DA7210_HP_R_VOL, 0x3F, 40); + + /* Enable Out */ + snd_soc_update_bits(codec, DA7210_OUTMIX_L, 0x1F, 0x10); + snd_soc_update_bits(codec, DA7210_OUTMIX_R, 0x1F, 0x10); + + } else { + /* Volume 7 */ + snd_soc_update_bits(codec, DA7210_MIC_L, 0x7, 0x7); + snd_soc_update_bits(codec, DA7210_MIC_R, 0x7, 0x7); + + /* Enable Mic */ + snd_soc_update_bits(codec, DA7210_INMIX_L, 0x1F, 0x1); + snd_soc_update_bits(codec, DA7210_INMIX_R, 0x1F, 0x1); + } + + return 0; +} + +/* + * Set PCM DAI word length. + */ +static int da7210_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u32 dai_cfg1; + u32 reg, mask; + + /* set DAI source to Left and Right ADC */ + da7210_write(codec, DA7210_DAI_SRC_SEL, + DA7210_DAI_OUT_R_SRC | DA7210_DAI_OUT_L_SRC); + + /* Enable DAI */ + da7210_write(codec, DA7210_DAI_CFG3, DA7210_DAI_OE | DA7210_DAI_EN); + + dai_cfg1 = 0xFC & da7210_read(codec, DA7210_DAI_CFG1); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + dai_cfg1 |= DA7210_DAI_WORD_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24_LE: + dai_cfg1 |= DA7210_DAI_WORD_S24_LE; + break; + default: + return -EINVAL; + } + + da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1); + + /* FIXME + * + * It support 48K only now + */ + switch (params_rate(params)) { + case 48000: + if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { + reg = DA7210_DAC_HPF; + mask = DA7210_DAC_VOICE_EN; + } else { + reg = DA7210_ADC_HPF; + mask = DA7210_ADC_VOICE_EN; + } + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, reg, mask, 0); + + return 0; +} + +/* + * Set DAI mode and Format + */ +static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u32 dai_cfg1; + u32 dai_cfg3; + + dai_cfg1 = 0x7f & da7210_read(codec, DA7210_DAI_CFG1); + dai_cfg3 = 0xfc & da7210_read(codec, DA7210_DAI_CFG3); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + dai_cfg1 |= DA7210_DAI_MODE_MASTER; + break; + default: + return -EINVAL; + } + + /* FIXME + * + * It support I2S only now + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dai_cfg3 |= DA7210_DAI_FORMAT_I2SMODE; + break; + default: + return -EINVAL; + } + + /* FIXME + * + * It support 64bit data transmission only now + */ + dai_cfg1 |= DA7210_DAI_FLEN_64BIT; + + da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1); + da7210_write(codec, DA7210_DAI_CFG3, dai_cfg3); + + return 0; +} + +#define DA7210_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +/* DAI operations */ +static struct snd_soc_dai_ops da7210_dai_ops = { + .startup = da7210_startup, + .hw_params = da7210_hw_params, + .set_fmt = da7210_set_dai_fmt, +}; + +struct snd_soc_dai da7210_dai = { + .name = "DA7210 IIS", + .id = 0, + /* playback capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7210_FORMATS, + }, + /* capture capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7210_FORMATS, + }, + .ops = &da7210_dai_ops, +}; +EXPORT_SYMBOL_GPL(da7210_dai); + +/* + * Initialize the DA7210 driver + * register the mixer and dsp interfaces with the kernel + */ +static int da7210_init(struct da7210_priv *da7210) +{ + struct snd_soc_codec *codec = &da7210->codec; + int ret = 0; + + if (da7210_codec) { + dev_err(codec->dev, "Another da7210 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = da7210; + codec->name = "DA7210"; + codec->owner = THIS_MODULE; + codec->read = da7210_read; + codec->write = da7210_write; + codec->dai = &da7210_dai; + codec->num_dai = 1; + codec->hw_write = (hw_write_t)i2c_master_send; + codec->reg_cache_size = ARRAY_SIZE(da7210_reg); + codec->reg_cache = kmemdup(da7210_reg, + sizeof(da7210_reg), GFP_KERNEL); + + if (!codec->reg_cache) + return -ENOMEM; + + da7210_dai.dev = codec->dev; + da7210_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(codec->dev, "Failed to register CODEC: %d\n", ret); + goto init_err; + } + + ret = snd_soc_register_dai(&da7210_dai); + if (ret) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + goto init_err; + } + + /* FIXME + * + * This driver use fixed value here + */ + + /* + * ADC settings + */ + + /* Enable Left & Right MIC PGA and Mic Bias */ + da7210_write(codec, DA7210_MIC_L, DA7210_MIC_L_EN | DA7210_MICBIAS_EN); + da7210_write(codec, DA7210_MIC_R, DA7210_MIC_R_EN); + + /* Enable Left and Right input PGA */ + da7210_write(codec, DA7210_INMIX_L, DA7210_IN_L_EN); + da7210_write(codec, DA7210_INMIX_R, DA7210_IN_R_EN); + + /* Enable Left and Right ADC */ + da7210_write(codec, DA7210_ADC, DA7210_ADC_L_EN | DA7210_ADC_R_EN); + + /* + * DAC settings + */ + + /* Enable Left and Right DAC */ + da7210_write(codec, DA7210_DAC_SEL, + DA7210_DAC_L_SRC_DAI_L | DA7210_DAC_L_EN | + DA7210_DAC_R_SRC_DAI_R | DA7210_DAC_R_EN); + + /* Enable Left and Right out PGA */ + da7210_write(codec, DA7210_OUTMIX_L, DA7210_OUT_L_EN); + da7210_write(codec, DA7210_OUTMIX_R, DA7210_OUT_R_EN); + + /* Enable Left and Right HeadPhone PGA */ + da7210_write(codec, DA7210_HP_CFG, + DA7210_HP_2CAP_MODE | DA7210_HP_SENSE_EN | + DA7210_HP_L_EN | DA7210_HP_MODE | DA7210_HP_R_EN); + + /* Diable PLL and bypass it */ + da7210_write(codec, DA7210_PLL, DA7210_PLL_FS_48000); + + /* Bypass PLL and set MCLK freq rang to 10-20MHz */ + da7210_write(codec, DA7210_PLL_DIV3, + DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); + + /* Activate all enabled subsystem */ + da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN); + + return ret; + +init_err: + kfree(codec->reg_cache); + codec->reg_cache = NULL; + + return ret; + +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int da7210_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da7210_priv *da7210; + struct snd_soc_codec *codec; + int ret; + + da7210 = kzalloc(sizeof(struct da7210_priv), GFP_KERNEL); + if (!da7210) + return -ENOMEM; + + codec = &da7210->codec; + codec->dev = &i2c->dev; + + i2c_set_clientdata(i2c, da7210); + codec->control_data = i2c; + + ret = da7210_init(da7210); + if (ret < 0) + pr_err("Failed to initialise da7210 audio codec\n"); + + return ret; +} + +static int da7210_i2c_remove(struct i2c_client *client) +{ + struct da7210_priv *da7210 = i2c_get_clientdata(client); + + snd_soc_unregister_dai(&da7210_dai); + kfree(da7210->codec.reg_cache); + kfree(da7210); + da7210_codec = NULL; + + return 0; +} + +static const struct i2c_device_id da7210_i2c_id[] = { + { "da7210", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da7210_i2c_id); + +/* I2C codec control layer */ +static struct i2c_driver da7210_i2c_driver = { + .driver = { + .name = "DA7210 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = da7210_i2c_probe, + .remove = __devexit_p(da7210_i2c_remove), + .id_table = da7210_i2c_id, +}; +#endif + +static int da7210_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + if (!da7210_codec) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = da7210_codec; + codec = da7210_codec; + + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + dev_info(&pdev->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION); + +pcm_err: + return ret; +} + +static int da7210_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_da7210 = { + .probe = da7210_probe, + .remove = da7210_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_da7210); + +static int __init da7210_modinit(void) +{ + int ret = 0; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&da7210_i2c_driver); +#endif + return ret; +} +module_init(da7210_modinit); + +static void __exit da7210_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&da7210_i2c_driver); +#endif +} +module_exit(da7210_exit); + +MODULE_DESCRIPTION("ASoC DA7210 driver"); +MODULE_AUTHOR("David Chen, Kuninori Morimoto"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/da7210.h b/sound/soc/codecs/da7210.h new file mode 100644 index 000000000000..390d621eb742 --- /dev/null +++ b/sound/soc/codecs/da7210.h @@ -0,0 +1,24 @@ +/* + * da7210.h -- audio driver for da7210 + * + * Copyright (c) 2009 Dialog Semiconductor + * Written by David Chen + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Cleanups by Kuninori Morimoto + * + * 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 _DA7210_H +#define _DA7210_H + +extern struct snd_soc_dai da7210_dai; +extern struct snd_soc_codec_device soc_codec_dev_da7210; + +#endif + -- cgit v1.2.3 From b35a28af0a64a1e8e389bc009b76253256d8fe7b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 18 Dec 2009 12:00:22 +0000 Subject: ASoC: Add initial WM8955 CODEC driver The WM8955 is a low power, high quality stereo DAC with integrated headphone and loudspeaker amplifiers, designed to reduce external component requirements in portable digital audio applications. This is an initial driver implementing support for the majority of the functionality in the device, currently OUT3 is not supported. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm8955.h | 26 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8955.c | 1151 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8955.h | 489 +++++++++++++++++++ 5 files changed, 1672 insertions(+) create mode 100644 include/sound/wm8955.h create mode 100644 sound/soc/codecs/wm8955.c create mode 100644 sound/soc/codecs/wm8955.h (limited to 'sound/soc/codecs/Kconfig') diff --git a/include/sound/wm8955.h b/include/sound/wm8955.h new file mode 100644 index 000000000000..5074ef499f40 --- /dev/null +++ b/include/sound/wm8955.h @@ -0,0 +1,26 @@ +/* + * Platform data for WM8955 + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * 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 __WM8955_PDATA_H__ +#define __WM8955_PDATA_H__ + +struct wm8955_pdata { + /* Configure LOUT2/ROUT2 to drive a speaker */ + unsigned int out2_speaker:1; + + /* Configure MONOIN+/- in differential mode */ + unsigned int monoin_diff:1; +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 691abe7df087..62ff26a08a2f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -52,6 +52,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8903 if I2C select SND_SOC_WM8904 if I2C select SND_SOC_WM8940 if I2C + select SND_SOC_WM8955 if I2C select SND_SOC_WM8960 if I2C select SND_SOC_WM8961 if I2C select SND_SOC_WM8971 if I2C @@ -214,6 +215,9 @@ config SND_SOC_WM8904 config SND_SOC_WM8940 tristate +config SND_SOC_WM8955 + tristate + config SND_SOC_WM8960 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index c0fd3c86edad..ea9835412e6a 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -39,6 +39,7 @@ snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o snd-soc-wm8904-objs := wm8904.o snd-soc-wm8940-objs := wm8940.o +snd-soc-wm8955-objs := wm8955.o snd-soc-wm8960-objs := wm8960.o snd-soc-wm8961-objs := wm8961.o snd-soc-wm8971-objs := wm8971.o @@ -97,6 +98,7 @@ obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8904) += snd-soc-wm8904.o obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o +obj-$(CONFIG_SND_SOC_WM8955) += snd-soc-wm8955.o obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c new file mode 100644 index 000000000000..615dab2b62ef --- /dev/null +++ b/sound/soc/codecs/wm8955.c @@ -0,0 +1,1151 @@ +/* + * wm8955.c -- WM8955 ALSA SoC Audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8955.h" + +static struct snd_soc_codec *wm8955_codec; +struct snd_soc_codec_device soc_codec_dev_wm8955; + +#define WM8955_NUM_SUPPLIES 4 +static const char *wm8955_supply_names[WM8955_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "HPVDD", + "AVDD", +}; + +/* codec private data */ +struct wm8955_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM8955_MAX_REGISTER + 1]; + + unsigned int mclk_rate; + + int deemph; + int fs; + + struct regulator_bulk_data supplies[WM8955_NUM_SUPPLIES]; + + struct wm8955_pdata *pdata; +}; + +static const u16 wm8955_reg[WM8955_MAX_REGISTER + 1] = { + 0x0000, /* R0 */ + 0x0000, /* R1 */ + 0x0079, /* R2 - LOUT1 volume */ + 0x0079, /* R3 - ROUT1 volume */ + 0x0000, /* R4 */ + 0x0008, /* R5 - DAC Control */ + 0x0000, /* R6 */ + 0x000A, /* R7 - Audio Interface */ + 0x0000, /* R8 - Sample Rate */ + 0x0000, /* R9 */ + 0x00FF, /* R10 - Left DAC volume */ + 0x00FF, /* R11 - Right DAC volume */ + 0x000F, /* R12 - Bass control */ + 0x000F, /* R13 - Treble control */ + 0x0000, /* R14 */ + 0x0000, /* R15 - Reset */ + 0x0000, /* R16 */ + 0x0000, /* R17 */ + 0x0000, /* R18 */ + 0x0000, /* R19 */ + 0x0000, /* R20 */ + 0x0000, /* R21 */ + 0x0000, /* R22 */ + 0x00C1, /* R23 - Additional control (1) */ + 0x0000, /* R24 - Additional control (2) */ + 0x0000, /* R25 - Power Management (1) */ + 0x0000, /* R26 - Power Management (2) */ + 0x0000, /* R27 - Additional Control (3) */ + 0x0000, /* R28 */ + 0x0000, /* R29 */ + 0x0000, /* R30 */ + 0x0000, /* R31 */ + 0x0000, /* R32 */ + 0x0000, /* R33 */ + 0x0050, /* R34 - Left out Mix (1) */ + 0x0050, /* R35 - Left out Mix (2) */ + 0x0050, /* R36 - Right out Mix (1) */ + 0x0050, /* R37 - Right Out Mix (2) */ + 0x0050, /* R38 - Mono out Mix (1) */ + 0x0050, /* R39 - Mono out Mix (2) */ + 0x0079, /* R40 - LOUT2 volume */ + 0x0079, /* R41 - ROUT2 volume */ + 0x0079, /* R42 - MONOOUT volume */ + 0x0000, /* R43 - Clocking / PLL */ + 0x0103, /* R44 - PLL Control 1 */ + 0x0024, /* R45 - PLL Control 2 */ + 0x01BA, /* R46 - PLL Control 3 */ + 0x0000, /* R47 */ + 0x0000, /* R48 */ + 0x0000, /* R49 */ + 0x0000, /* R50 */ + 0x0000, /* R51 */ + 0x0000, /* R52 */ + 0x0000, /* R53 */ + 0x0000, /* R54 */ + 0x0000, /* R55 */ + 0x0000, /* R56 */ + 0x0000, /* R57 */ + 0x0000, /* R58 */ + 0x0000, /* R59 - PLL Control 4 */ +}; + +static int wm8955_reset(struct snd_soc_codec *codec) +{ + return snd_soc_write(codec, WM8955_RESET, 0); +} + +struct pll_factors { + int n; + int k; + int outdiv; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 22) * 10) + +static int wm8995_pll_factors(struct device *dev, + int Fref, int Fout, struct pll_factors *pll) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + + dev_dbg(dev, "Fref=%u Fout=%u\n", Fref, Fout); + + /* The oscilator should run at should be 90-100MHz, and + * there's a divide by 4 plus an optional divide by 2 in the + * output path to generate the system clock. The clock table + * is sortd so we should always generate a suitable target. */ + target = Fout * 4; + if (target < 90000000) { + pll->outdiv = 1; + target *= 2; + } else { + pll->outdiv = 0; + } + + WARN_ON(target < 90000000 || target > 100000000); + + dev_dbg(dev, "Fvco=%dHz\n", target); + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + pll->n = Ndiv; + Nmod = target % Fref; + dev_dbg(dev, "Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + pll->k = K / 10; + + dev_dbg(dev, "N=%x K=%x OUTDIV=%x\n", pll->n, pll->k, pll->outdiv); + + return 0; +} + +/* Lookup table specifiying SRATE (table 25 in datasheet); some of the + * output frequencies have been rounded to the standard frequencies + * they are intended to match where the error is slight. */ +static struct { + int mclk; + int fs; + int usb; + int sr; +} clock_cfgs[] = { + { 18432000, 8000, 0, 3, }, + { 18432000, 12000, 0, 9, }, + { 18432000, 16000, 0, 11, }, + { 18432000, 24000, 0, 29, }, + { 18432000, 32000, 0, 13, }, + { 18432000, 48000, 0, 1, }, + { 18432000, 96000, 0, 15, }, + + { 16934400, 8018, 0, 19, }, + { 16934400, 11025, 0, 25, }, + { 16934400, 22050, 0, 27, }, + { 16934400, 44100, 0, 17, }, + { 16934400, 88200, 0, 31, }, + + { 12000000, 8000, 1, 2, }, + { 12000000, 11025, 1, 25, }, + { 12000000, 12000, 1, 8, }, + { 12000000, 16000, 1, 10, }, + { 12000000, 22050, 1, 27, }, + { 12000000, 24000, 1, 28, }, + { 12000000, 32000, 1, 12, }, + { 12000000, 44100, 1, 17, }, + { 12000000, 48000, 1, 0, }, + { 12000000, 88200, 1, 31, }, + { 12000000, 96000, 1, 14, }, + + { 12288000, 8000, 0, 2, }, + { 12288000, 12000, 0, 8, }, + { 12288000, 16000, 0, 10, }, + { 12288000, 24000, 0, 28, }, + { 12288000, 32000, 0, 12, }, + { 12288000, 48000, 0, 0, }, + { 12288000, 96000, 0, 14, }, + + { 12289600, 8018, 0, 18, }, + { 12289600, 11025, 0, 24, }, + { 12289600, 22050, 0, 26, }, + { 11289600, 44100, 0, 16, }, + { 11289600, 88200, 0, 31, }, +}; + +static int wm8955_configure_clocking(struct snd_soc_codec *codec) +{ + struct wm8955_priv *wm8955 = codec->private_data; + int i, ret, val; + int clocking = 0; + int srate = 0; + int sr = -1; + struct pll_factors pll; + + /* If we're not running a sample rate currently just pick one */ + if (wm8955->fs == 0) + wm8955->fs = 8000; + + /* Can we generate an exact output? */ + for (i = 0; i < ARRAY_SIZE(clock_cfgs); i++) { + if (wm8955->fs != clock_cfgs[i].fs) + continue; + sr = i; + + if (wm8955->mclk_rate == clock_cfgs[i].mclk) + break; + } + + /* We should never get here with an unsupported sample rate */ + if (sr == -1) { + dev_err(codec->dev, "Sample rate %dHz unsupported\n", + wm8955->fs); + WARN_ON(sr == -1); + return -EINVAL; + } + + if (i == ARRAY_SIZE(clock_cfgs)) { + /* If we can't generate the right clock from MCLK then + * we should configure the PLL to supply us with an + * appropriate clock. + */ + clocking |= WM8955_MCLKSEL; + + /* Use the last divider configuration we saw for the + * sample rate. */ + ret = wm8995_pll_factors(codec->dev, wm8955->mclk_rate, + clock_cfgs[sr].mclk, &pll); + if (ret != 0) { + dev_err(codec->dev, + "Unable to generate %dHz from %dHz MCLK\n", + wm8955->fs, wm8955->mclk_rate); + return -EINVAL; + } + + snd_soc_update_bits(codec, WM8955_PLL_CONTROL_1, + WM8955_N_MASK | WM8955_K_21_18_MASK, + (pll.n << WM8955_N_SHIFT) | + pll.k >> 18); + snd_soc_update_bits(codec, WM8955_PLL_CONTROL_2, + WM8955_K_17_9_MASK, + (pll.k >> 9) & WM8955_K_17_9_MASK); + snd_soc_update_bits(codec, WM8955_PLL_CONTROL_2, + WM8955_K_8_0_MASK, + pll.k & WM8955_K_8_0_MASK); + if (pll.k) + snd_soc_update_bits(codec, WM8955_PLL_CONTROL_4, + WM8955_KEN, WM8955_KEN); + else + snd_soc_update_bits(codec, WM8955_PLL_CONTROL_4, + WM8955_KEN, 0); + + if (pll.outdiv) + val = WM8955_PLL_RB | WM8955_PLLOUTDIV2; + else + val = WM8955_PLL_RB; + + /* Now start the PLL running */ + snd_soc_update_bits(codec, WM8955_CLOCKING_PLL, + WM8955_PLL_RB | WM8955_PLLOUTDIV2, val); + snd_soc_update_bits(codec, WM8955_CLOCKING_PLL, + WM8955_PLLEN, WM8955_PLLEN); + } + + srate = clock_cfgs[sr].usb | (clock_cfgs[sr].sr << WM8955_SR_SHIFT); + + snd_soc_update_bits(codec, WM8955_SAMPLE_RATE, + WM8955_USB | WM8955_SR_MASK, srate); + snd_soc_update_bits(codec, WM8955_CLOCKING_PLL, + WM8955_MCLKSEL, clocking); + + return 0; +} + +static int wm8955_sysclk(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + int ret = 0; + + /* Always disable the clocks - if we're doing reconfiguration this + * avoids misclocking. + */ + snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1, + WM8955_DIGENB, 0); + snd_soc_update_bits(codec, WM8955_CLOCKING_PLL, + WM8955_PLL_RB | WM8955_PLLEN, 0); + + switch (event) { + case SND_SOC_DAPM_POST_PMD: + break; + case SND_SOC_DAPM_PRE_PMU: + ret = wm8955_configure_clocking(codec); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8955_set_deemph(struct snd_soc_codec *codec) +{ + struct wm8955_priv *wm8955 = codec->private_data; + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8955->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8955->fs) < + abs(deemph_settings[best] - wm8955->fs)) + best = i; + } + + val = best << WM8955_DEEMPH_SHIFT; + } else { + val = 0; + } + + dev_dbg(codec->dev, "Set deemphasis %d\n", val); + + return snd_soc_update_bits(codec, WM8955_DAC_CONTROL, + WM8955_DEEMPH_MASK, val); +} + +static int wm8955_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8955_priv *wm8955 = codec->private_data; + + return wm8955->deemph; +} + +static int wm8955_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8955_priv *wm8955 = codec->private_data; + int deemph = ucontrol->value.enumerated.item[0]; + + if (deemph > 1) + return -EINVAL; + + wm8955->deemph = deemph; + + return wm8955_set_deemph(codec); +} + +static const char *bass_mode_text[] = { + "Linear", "Adaptive", +}; + +static const struct soc_enum bass_mode = + SOC_ENUM_SINGLE(WM8955_BASS_CONTROL, 7, 2, bass_mode_text); + +static const char *bass_cutoff_text[] = { + "Low", "High" +}; + +static const struct soc_enum bass_cutoff = + SOC_ENUM_SINGLE(WM8955_BASS_CONTROL, 6, 2, bass_cutoff_text); + +static const char *treble_cutoff_text[] = { + "High", "Low" +}; + +static const struct soc_enum treble_cutoff = + SOC_ENUM_SINGLE(WM8955_TREBLE_CONTROL, 6, 2, treble_cutoff_text); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(atten_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(mono_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(treble_tlv, -1200, 150, 1); + +static const struct snd_kcontrol_new wm8955_snd_controls[] = { +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8955_LEFT_DAC_VOLUME, + WM8955_RIGHT_DAC_VOLUME, 0, 255, 0, digital_tlv), +SOC_SINGLE_TLV("Playback Attenuation Volume", WM8955_DAC_CONTROL, 7, 1, 1, + atten_tlv), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8955_get_deemph, wm8955_put_deemph), + +SOC_ENUM("Bass Mode", bass_mode), +SOC_ENUM("Bass Cutoff", bass_cutoff), +SOC_SINGLE("Bass Volume", WM8955_BASS_CONTROL, 0, 15, 1), + +SOC_ENUM("Treble Cutoff", treble_cutoff), +SOC_SINGLE_TLV("Treble Volume", WM8955_TREBLE_CONTROL, 0, 14, 1, treble_tlv), + +SOC_SINGLE_TLV("Left Bypass Volume", WM8955_LEFT_OUT_MIX_1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Left Mono Volume", WM8955_LEFT_OUT_MIX_2, 4, 7, 1, + bypass_tlv), + +SOC_SINGLE_TLV("Right Mono Volume", WM8955_RIGHT_OUT_MIX_1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Right Bypass Volume", WM8955_RIGHT_OUT_MIX_2, 4, 7, 1, + bypass_tlv), + +/* Not a stereo pair so they line up with the DAPM switches */ +SOC_SINGLE_TLV("Mono Left Bypass Volume", WM8955_MONO_OUT_MIX_1, 4, 7, 1, + mono_tlv), +SOC_SINGLE_TLV("Mono Right Bypass Volume", WM8955_MONO_OUT_MIX_2, 4, 7, 1, + mono_tlv), + +SOC_DOUBLE_R_TLV("Headphone Volume", WM8955_LOUT1_VOLUME, + WM8955_ROUT1_VOLUME, 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Headphone ZC Switch", WM8955_LOUT1_VOLUME, + WM8955_ROUT1_VOLUME, 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Volume", WM8955_LOUT2_VOLUME, + WM8955_ROUT2_VOLUME, 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker ZC Switch", WM8955_LOUT2_VOLUME, + WM8955_ROUT2_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("Mono Volume", WM8955_MONOOUT_VOLUME, 0, 127, 0, out_tlv), +SOC_SINGLE("Mono ZC Switch", WM8955_MONOOUT_VOLUME, 7, 1, 0), +}; + +static const struct snd_kcontrol_new lmixer[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8955_LEFT_OUT_MIX_1, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Switch", WM8955_LEFT_OUT_MIX_1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8955_LEFT_OUT_MIX_2, 8, 1, 0), +SOC_DAPM_SINGLE("Mono Switch", WM8955_LEFT_OUT_MIX_2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new rmixer[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8955_RIGHT_OUT_MIX_1, 8, 1, 0), +SOC_DAPM_SINGLE("Mono Switch", WM8955_RIGHT_OUT_MIX_1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8955_RIGHT_OUT_MIX_2, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Switch", WM8955_RIGHT_OUT_MIX_2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new mmixer[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8955_MONO_OUT_MIX_1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8955_MONO_OUT_MIX_1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8955_MONO_OUT_MIX_2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8955_MONO_OUT_MIX_2, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8955_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("MONOIN-"), +SND_SOC_DAPM_INPUT("MONOIN+"), +SND_SOC_DAPM_INPUT("LINEINR"), +SND_SOC_DAPM_INPUT("LINEINL"), + +SND_SOC_DAPM_PGA("Mono Input", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_SUPPLY("SYSCLK", WM8955_POWER_MANAGEMENT_1, 0, 1, wm8955_sysclk, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("TSDEN", WM8955_ADDITIONAL_CONTROL_1, 8, 0, NULL, 0), + +SND_SOC_DAPM_DAC("DACL", "Playback", WM8955_POWER_MANAGEMENT_2, 8, 0), +SND_SOC_DAPM_DAC("DACR", "Playback", WM8955_POWER_MANAGEMENT_2, 7, 0), + +SND_SOC_DAPM_PGA("LOUT1 PGA", WM8955_POWER_MANAGEMENT_2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT1 PGA", WM8955_POWER_MANAGEMENT_2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("LOUT2 PGA", WM8955_POWER_MANAGEMENT_2, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT2 PGA", WM8955_POWER_MANAGEMENT_2, 3, 0, NULL, 0), +SND_SOC_DAPM_PGA("MOUT PGA", WM8955_POWER_MANAGEMENT_2, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("OUT3 PGA", WM8955_POWER_MANAGEMENT_2, 1, 0, NULL, 0), + +/* The names are chosen to make the control names nice */ +SND_SOC_DAPM_MIXER("Left", SND_SOC_NOPM, 0, 0, + lmixer, ARRAY_SIZE(lmixer)), +SND_SOC_DAPM_MIXER("Right", SND_SOC_NOPM, 0, 0, + rmixer, ARRAY_SIZE(rmixer)), +SND_SOC_DAPM_MIXER("Mono", SND_SOC_NOPM, 0, 0, + mmixer, ARRAY_SIZE(mmixer)), + +SND_SOC_DAPM_OUTPUT("LOUT1"), +SND_SOC_DAPM_OUTPUT("ROUT1"), +SND_SOC_DAPM_OUTPUT("LOUT2"), +SND_SOC_DAPM_OUTPUT("ROUT2"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("OUT3"), +}; + +static const struct snd_soc_dapm_route wm8955_intercon[] = { + { "DACL", NULL, "SYSCLK" }, + { "DACR", NULL, "SYSCLK" }, + + { "Mono Input", NULL, "MONOIN-" }, + { "Mono Input", NULL, "MONOIN+" }, + + { "Left", "Playback Switch", "DACL" }, + { "Left", "Right Playback Switch", "DACR" }, + { "Left", "Bypass Switch", "LINEINL" }, + { "Left", "Mono Switch", "Mono Input" }, + + { "Right", "Playback Switch", "DACR" }, + { "Right", "Left Playback Switch", "DACL" }, + { "Right", "Bypass Switch", "LINEINR" }, + { "Right", "Mono Switch", "Mono Input" }, + + { "Mono", "Left Playback Switch", "DACL" }, + { "Mono", "Right Playback Switch", "DACR" }, + { "Mono", "Left Bypass Switch", "LINEINL" }, + { "Mono", "Right Bypass Switch", "LINEINR" }, + + { "LOUT1 PGA", NULL, "Left" }, + { "LOUT1", NULL, "TSDEN" }, + { "LOUT1", NULL, "LOUT1 PGA" }, + + { "ROUT1 PGA", NULL, "Right" }, + { "ROUT1", NULL, "TSDEN" }, + { "ROUT1", NULL, "ROUT1 PGA" }, + + { "LOUT2 PGA", NULL, "Left" }, + { "LOUT2", NULL, "TSDEN" }, + { "LOUT2", NULL, "LOUT2 PGA" }, + + { "ROUT2 PGA", NULL, "Right" }, + { "ROUT2", NULL, "TSDEN" }, + { "ROUT2", NULL, "ROUT2 PGA" }, + + { "MOUT PGA", NULL, "Mono" }, + { "MONOOUT", NULL, "MOUT PGA" }, + + /* OUT3 not currently implemented */ + { "OUT3", NULL, "OUT3 PGA" }, +}; + +static int wm8955_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_add_controls(codec, wm8955_snd_controls, + ARRAY_SIZE(wm8955_snd_controls)); + + snd_soc_dapm_new_controls(codec, wm8955_dapm_widgets, + ARRAY_SIZE(wm8955_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, wm8955_intercon, + ARRAY_SIZE(wm8955_intercon)); + + return 0; +} + +static int wm8955_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8955_priv *wm8955 = codec->private_data; + int ret; + int wl; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + wl = 0; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + wl = 0x4; + break; + case SNDRV_PCM_FORMAT_S24_LE: + wl = 0x8; + break; + case SNDRV_PCM_FORMAT_S32_LE: + wl = 0xc; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(codec, WM8955_AUDIO_INTERFACE, + WM8955_WL_MASK, wl); + + wm8955->fs = params_rate(params); + wm8955_set_deemph(codec); + + /* If the chip is clocked then disable the clocks and force a + * reconfiguration, otherwise DAPM will power up the + * clocks for us later. */ + ret = snd_soc_read(codec, WM8955_POWER_MANAGEMENT_1); + if (ret < 0) + return ret; + if (ret & WM8955_DIGENB) { + snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1, + WM8955_DIGENB, 0); + snd_soc_update_bits(codec, WM8955_CLOCKING_PLL, + WM8955_PLL_RB | WM8955_PLLEN, 0); + + wm8955_configure_clocking(codec); + } + + return 0; +} + + +static int wm8955_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8955_priv *priv = codec->private_data; + int div; + + switch (clk_id) { + case WM8955_CLK_MCLK: + if (freq > 15000000) { + priv->mclk_rate = freq /= 2; + div = WM8955_MCLKDIV2; + } else { + priv->mclk_rate = freq; + div = 0; + } + + snd_soc_update_bits(codec, WM8955_SAMPLE_RATE, + WM8955_MCLKDIV2, div); + break; + + default: + return -EINVAL; + } + + dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); + + return 0; +} + +static int wm8955_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + u16 aif = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif |= WM8955_MS; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif |= WM8955_LRP; + case SND_SOC_DAIFMT_DSP_A: + aif |= 0x3; + break; + case SND_SOC_DAIFMT_I2S: + aif |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif |= 0x1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8955_BCLKINV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif |= WM8955_BCLKINV | WM8955_LRP; + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8955_BCLKINV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif |= WM8955_LRP; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, WM8955_AUDIO_INTERFACE, + WM8955_MS | WM8955_FORMAT_MASK | WM8955_BCLKINV | + WM8955_LRP, aif); + + return 0; +} + + +static int wm8955_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int val; + + if (mute) + val = WM8955_DACMU; + else + val = 0; + + snd_soc_update_bits(codec, WM8955_DAC_CONTROL, WM8955_DACMU, val); + + return 0; +} + +static int wm8955_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8955_priv *wm8955 = codec->private_data; + int ret, i; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID resistance 2*50k */ + snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1, + WM8955_VMIDSEL_MASK, + 0x1 << WM8955_VMIDSEL_SHIFT); + + /* Default bias current */ + snd_soc_update_bits(codec, WM8955_ADDITIONAL_CONTROL_1, + WM8955_VSEL_MASK, + 0x2 << WM8955_VSEL_SHIFT); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(codec->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + /* Sync back cached values if they're + * different from the hardware default. + */ + for (i = 0; i < ARRAY_SIZE(wm8955->reg_cache); i++) { + if (i == WM8955_RESET) + continue; + + if (wm8955->reg_cache[i] == wm8955_reg[i]) + continue; + + snd_soc_write(codec, i, wm8955->reg_cache[i]); + } + + /* Enable VREF and VMID */ + snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1, + WM8955_VREF | + WM8955_VMIDSEL_MASK, + WM8955_VREF | + 0x3 << WM8955_VREF_SHIFT); + + /* Let VMID ramp */ + msleep(500); + + /* High resistance VROI to maintain outputs */ + snd_soc_update_bits(codec, + WM8955_ADDITIONAL_CONTROL_3, + WM8955_VROI, WM8955_VROI); + } + + /* Maintain VMID with 2*250k */ + snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1, + WM8955_VMIDSEL_MASK, + 0x2 << WM8955_VMIDSEL_SHIFT); + + /* Minimum bias current */ + snd_soc_update_bits(codec, WM8955_ADDITIONAL_CONTROL_1, + WM8955_VSEL_MASK, 0); + break; + + case SND_SOC_BIAS_OFF: + /* Low resistance VROI to help discharge */ + snd_soc_update_bits(codec, + WM8955_ADDITIONAL_CONTROL_3, + WM8955_VROI, 0); + + /* Turn off VMID and VREF */ + snd_soc_update_bits(codec, WM8955_POWER_MANAGEMENT_1, + WM8955_VREF | + WM8955_VMIDSEL_MASK, 0); + + regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8955_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8955_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops wm8955_dai_ops = { + .set_sysclk = wm8955_set_sysclk, + .set_fmt = wm8955_set_fmt, + .hw_params = wm8955_hw_params, + .digital_mute = wm8955_digital_mute, +}; + +struct snd_soc_dai wm8955_dai = { + .name = "WM8955", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8955_RATES, + .formats = WM8955_FORMATS, + }, + .ops = &wm8955_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm8955_dai); + +#ifdef CONFIG_PM +static int wm8955_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8955_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8955_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm8955_suspend NULL +#define wm8955_resume NULL +#endif + +static int wm8955_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8955_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8955_codec; + codec = wm8955_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + wm8955_add_widgets(codec); + + return ret; + +pcm_err: + return ret; +} + +static int wm8955_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8955 = { + .probe = wm8955_probe, + .remove = wm8955_remove, + .suspend = wm8955_suspend, + .resume = wm8955_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8955); + +static int wm8955_register(struct wm8955_priv *wm8955, + enum snd_soc_control_type control) +{ + int ret; + struct snd_soc_codec *codec = &wm8955->codec; + int i; + + if (wm8955_codec) { + dev_err(codec->dev, "Another WM8955 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8955; + codec->name = "WM8955"; + codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8955_set_bias_level; + codec->dai = &wm8955_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8955_MAX_REGISTER; + codec->reg_cache = &wm8955->reg_cache; + + memcpy(codec->reg_cache, wm8955_reg, sizeof(wm8955_reg)); + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++) + wm8955->supplies[i].supply = wm8955_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies), + wm8955->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_get; + } + + ret = wm8955_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset: %d\n", ret); + goto err_enable; + } + + wm8955_dai.dev = codec->dev; + + /* Change some default settings - latch VU and enable ZC */ + wm8955->reg_cache[WM8955_LEFT_DAC_VOLUME] |= WM8955_LDVU; + wm8955->reg_cache[WM8955_RIGHT_DAC_VOLUME] |= WM8955_RDVU; + wm8955->reg_cache[WM8955_LOUT1_VOLUME] |= WM8955_LO1VU | WM8955_LO1ZC; + wm8955->reg_cache[WM8955_ROUT1_VOLUME] |= WM8955_RO1VU | WM8955_RO1ZC; + wm8955->reg_cache[WM8955_LOUT2_VOLUME] |= WM8955_LO2VU | WM8955_LO2ZC; + wm8955->reg_cache[WM8955_ROUT2_VOLUME] |= WM8955_RO2VU | WM8955_RO2ZC; + wm8955->reg_cache[WM8955_MONOOUT_VOLUME] |= WM8955_MOZC; + + /* Also enable adaptive bass boost by default */ + wm8955->reg_cache[WM8955_BASS_CONTROL] |= WM8955_BB; + + /* Set platform data values */ + if (wm8955->pdata) { + if (wm8955->pdata->out2_speaker) + wm8955->reg_cache[WM8955_ADDITIONAL_CONTROL_2] + |= WM8955_ROUT2INV; + + if (wm8955->pdata->monoin_diff) + wm8955->reg_cache[WM8955_MONO_OUT_MIX_1] + |= WM8955_DMEN; + } + + wm8955_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Bias level configuration will have done an extra enable */ + regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); + + wm8955_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8955_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); +err_get: + regulator_bulk_free(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); +err: + kfree(wm8955); + return ret; +} + +static void wm8955_unregister(struct wm8955_priv *wm8955) +{ + wm8955_set_bias_level(&wm8955->codec, SND_SOC_BIAS_OFF); + regulator_bulk_free(ARRAY_SIZE(wm8955->supplies), wm8955->supplies); + snd_soc_unregister_dai(&wm8955_dai); + snd_soc_unregister_codec(&wm8955->codec); + kfree(wm8955); + wm8955_codec = NULL; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static __devinit int wm8955_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8955_priv *wm8955; + struct snd_soc_codec *codec; + + wm8955 = kzalloc(sizeof(struct wm8955_priv), GFP_KERNEL); + if (wm8955 == NULL) + return -ENOMEM; + + codec = &wm8955->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8955); + codec->control_data = i2c; + wm8955->pdata = i2c->dev.platform_data; + + codec->dev = &i2c->dev; + + return wm8955_register(wm8955, SND_SOC_I2C); +} + +static __devexit int wm8955_i2c_remove(struct i2c_client *client) +{ + struct wm8955_priv *wm8955 = i2c_get_clientdata(client); + wm8955_unregister(wm8955); + return 0; +} + +static const struct i2c_device_id wm8955_i2c_id[] = { + { "wm8955", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8955_i2c_id); + +static struct i2c_driver wm8955_i2c_driver = { + .driver = { + .name = "wm8955", + .owner = THIS_MODULE, + }, + .probe = wm8955_i2c_probe, + .remove = __devexit_p(wm8955_i2c_remove), + .id_table = wm8955_i2c_id, +}; +#endif + +static int __init wm8955_modinit(void) +{ + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8955_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8955 I2C driver: %d\n", + ret); + } +#endif + return 0; +} +module_init(wm8955_modinit); + +static void __exit wm8955_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8955_i2c_driver); +#endif +} +module_exit(wm8955_exit); + +MODULE_DESCRIPTION("ASoC WM8955 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8955.h b/sound/soc/codecs/wm8955.h new file mode 100644 index 000000000000..ae349c8531f6 --- /dev/null +++ b/sound/soc/codecs/wm8955.h @@ -0,0 +1,489 @@ +/* + * wm8955.h -- WM8904 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics, plc + * + * Author: Mark Brown + * + * 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 _WM8955_H +#define _WM8955_H + +#define WM8955_CLK_MCLK 1 + +extern struct snd_soc_dai wm8955_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8955; + +/* + * Register values. + */ +#define WM8955_LOUT1_VOLUME 0x02 +#define WM8955_ROUT1_VOLUME 0x03 +#define WM8955_DAC_CONTROL 0x05 +#define WM8955_AUDIO_INTERFACE 0x07 +#define WM8955_SAMPLE_RATE 0x08 +#define WM8955_LEFT_DAC_VOLUME 0x0A +#define WM8955_RIGHT_DAC_VOLUME 0x0B +#define WM8955_BASS_CONTROL 0x0C +#define WM8955_TREBLE_CONTROL 0x0D +#define WM8955_RESET 0x0F +#define WM8955_ADDITIONAL_CONTROL_1 0x17 +#define WM8955_ADDITIONAL_CONTROL_2 0x18 +#define WM8955_POWER_MANAGEMENT_1 0x19 +#define WM8955_POWER_MANAGEMENT_2 0x1A +#define WM8955_ADDITIONAL_CONTROL_3 0x1B +#define WM8955_LEFT_OUT_MIX_1 0x22 +#define WM8955_LEFT_OUT_MIX_2 0x23 +#define WM8955_RIGHT_OUT_MIX_1 0x24 +#define WM8955_RIGHT_OUT_MIX_2 0x25 +#define WM8955_MONO_OUT_MIX_1 0x26 +#define WM8955_MONO_OUT_MIX_2 0x27 +#define WM8955_LOUT2_VOLUME 0x28 +#define WM8955_ROUT2_VOLUME 0x29 +#define WM8955_MONOOUT_VOLUME 0x2A +#define WM8955_CLOCKING_PLL 0x2B +#define WM8955_PLL_CONTROL_1 0x2C +#define WM8955_PLL_CONTROL_2 0x2D +#define WM8955_PLL_CONTROL_3 0x2E +#define WM8955_PLL_CONTROL_4 0x3B + +#define WM8955_REGISTER_COUNT 29 +#define WM8955_MAX_REGISTER 0x3B + +/* + * Field Definitions. + */ + +/* + * R2 (0x02) - LOUT1 volume + */ +#define WM8955_LO1VU 0x0100 /* LO1VU */ +#define WM8955_LO1VU_MASK 0x0100 /* LO1VU */ +#define WM8955_LO1VU_SHIFT 8 /* LO1VU */ +#define WM8955_LO1VU_WIDTH 1 /* LO1VU */ +#define WM8955_LO1ZC 0x0080 /* LO1ZC */ +#define WM8955_LO1ZC_MASK 0x0080 /* LO1ZC */ +#define WM8955_LO1ZC_SHIFT 7 /* LO1ZC */ +#define WM8955_LO1ZC_WIDTH 1 /* LO1ZC */ +#define WM8955_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8955_LOUTVOL_SHIFT 0 /* LOUTVOL - [6:0] */ +#define WM8955_LOUTVOL_WIDTH 7 /* LOUTVOL - [6:0] */ + +/* + * R3 (0x03) - ROUT1 volume + */ +#define WM8955_RO1VU 0x0100 /* RO1VU */ +#define WM8955_RO1VU_MASK 0x0100 /* RO1VU */ +#define WM8955_RO1VU_SHIFT 8 /* RO1VU */ +#define WM8955_RO1VU_WIDTH 1 /* RO1VU */ +#define WM8955_RO1ZC 0x0080 /* RO1ZC */ +#define WM8955_RO1ZC_MASK 0x0080 /* RO1ZC */ +#define WM8955_RO1ZC_SHIFT 7 /* RO1ZC */ +#define WM8955_RO1ZC_WIDTH 1 /* RO1ZC */ +#define WM8955_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8955_ROUTVOL_SHIFT 0 /* ROUTVOL - [6:0] */ +#define WM8955_ROUTVOL_WIDTH 7 /* ROUTVOL - [6:0] */ + +/* + * R5 (0x05) - DAC Control + */ +#define WM8955_DAT 0x0080 /* DAT */ +#define WM8955_DAT_MASK 0x0080 /* DAT */ +#define WM8955_DAT_SHIFT 7 /* DAT */ +#define WM8955_DAT_WIDTH 1 /* DAT */ +#define WM8955_DACMU 0x0008 /* DACMU */ +#define WM8955_DACMU_MASK 0x0008 /* DACMU */ +#define WM8955_DACMU_SHIFT 3 /* DACMU */ +#define WM8955_DACMU_WIDTH 1 /* DACMU */ +#define WM8955_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8955_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8955_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R7 (0x07) - Audio Interface + */ +#define WM8955_BCLKINV 0x0080 /* BCLKINV */ +#define WM8955_BCLKINV_MASK 0x0080 /* BCLKINV */ +#define WM8955_BCLKINV_SHIFT 7 /* BCLKINV */ +#define WM8955_BCLKINV_WIDTH 1 /* BCLKINV */ +#define WM8955_MS 0x0040 /* MS */ +#define WM8955_MS_MASK 0x0040 /* MS */ +#define WM8955_MS_SHIFT 6 /* MS */ +#define WM8955_MS_WIDTH 1 /* MS */ +#define WM8955_LRSWAP 0x0020 /* LRSWAP */ +#define WM8955_LRSWAP_MASK 0x0020 /* LRSWAP */ +#define WM8955_LRSWAP_SHIFT 5 /* LRSWAP */ +#define WM8955_LRSWAP_WIDTH 1 /* LRSWAP */ +#define WM8955_LRP 0x0010 /* LRP */ +#define WM8955_LRP_MASK 0x0010 /* LRP */ +#define WM8955_LRP_SHIFT 4 /* LRP */ +#define WM8955_LRP_WIDTH 1 /* LRP */ +#define WM8955_WL_MASK 0x000C /* WL - [3:2] */ +#define WM8955_WL_SHIFT 2 /* WL - [3:2] */ +#define WM8955_WL_WIDTH 2 /* WL - [3:2] */ +#define WM8955_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */ +#define WM8955_FORMAT_SHIFT 0 /* FORMAT - [1:0] */ +#define WM8955_FORMAT_WIDTH 2 /* FORMAT - [1:0] */ + +/* + * R8 (0x08) - Sample Rate + */ +#define WM8955_BCLKDIV2 0x0080 /* BCLKDIV2 */ +#define WM8955_BCLKDIV2_MASK 0x0080 /* BCLKDIV2 */ +#define WM8955_BCLKDIV2_SHIFT 7 /* BCLKDIV2 */ +#define WM8955_BCLKDIV2_WIDTH 1 /* BCLKDIV2 */ +#define WM8955_MCLKDIV2 0x0040 /* MCLKDIV2 */ +#define WM8955_MCLKDIV2_MASK 0x0040 /* MCLKDIV2 */ +#define WM8955_MCLKDIV2_SHIFT 6 /* MCLKDIV2 */ +#define WM8955_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ +#define WM8955_SR_MASK 0x003E /* SR - [5:1] */ +#define WM8955_SR_SHIFT 1 /* SR - [5:1] */ +#define WM8955_SR_WIDTH 5 /* SR - [5:1] */ +#define WM8955_USB 0x0001 /* USB */ +#define WM8955_USB_MASK 0x0001 /* USB */ +#define WM8955_USB_SHIFT 0 /* USB */ +#define WM8955_USB_WIDTH 1 /* USB */ + +/* + * R10 (0x0A) - Left DAC volume + */ +#define WM8955_LDVU 0x0100 /* LDVU */ +#define WM8955_LDVU_MASK 0x0100 /* LDVU */ +#define WM8955_LDVU_SHIFT 8 /* LDVU */ +#define WM8955_LDVU_WIDTH 1 /* LDVU */ +#define WM8955_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */ +#define WM8955_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */ +#define WM8955_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */ + +/* + * R11 (0x0B) - Right DAC volume + */ +#define WM8955_RDVU 0x0100 /* RDVU */ +#define WM8955_RDVU_MASK 0x0100 /* RDVU */ +#define WM8955_RDVU_SHIFT 8 /* RDVU */ +#define WM8955_RDVU_WIDTH 1 /* RDVU */ +#define WM8955_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */ +#define WM8955_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */ +#define WM8955_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */ + +/* + * R12 (0x0C) - Bass control + */ +#define WM8955_BB 0x0080 /* BB */ +#define WM8955_BB_MASK 0x0080 /* BB */ +#define WM8955_BB_SHIFT 7 /* BB */ +#define WM8955_BB_WIDTH 1 /* BB */ +#define WM8955_BC 0x0040 /* BC */ +#define WM8955_BC_MASK 0x0040 /* BC */ +#define WM8955_BC_SHIFT 6 /* BC */ +#define WM8955_BC_WIDTH 1 /* BC */ +#define WM8955_BASS_MASK 0x000F /* BASS - [3:0] */ +#define WM8955_BASS_SHIFT 0 /* BASS - [3:0] */ +#define WM8955_BASS_WIDTH 4 /* BASS - [3:0] */ + +/* + * R13 (0x0D) - Treble control + */ +#define WM8955_TC 0x0040 /* TC */ +#define WM8955_TC_MASK 0x0040 /* TC */ +#define WM8955_TC_SHIFT 6 /* TC */ +#define WM8955_TC_WIDTH 1 /* TC */ +#define WM8955_TRBL_MASK 0x000F /* TRBL - [3:0] */ +#define WM8955_TRBL_SHIFT 0 /* TRBL - [3:0] */ +#define WM8955_TRBL_WIDTH 4 /* TRBL - [3:0] */ + +/* + * R15 (0x0F) - Reset + */ +#define WM8955_RESET_MASK 0x01FF /* RESET - [8:0] */ +#define WM8955_RESET_SHIFT 0 /* RESET - [8:0] */ +#define WM8955_RESET_WIDTH 9 /* RESET - [8:0] */ + +/* + * R23 (0x17) - Additional control (1) + */ +#define WM8955_TSDEN 0x0100 /* TSDEN */ +#define WM8955_TSDEN_MASK 0x0100 /* TSDEN */ +#define WM8955_TSDEN_SHIFT 8 /* TSDEN */ +#define WM8955_TSDEN_WIDTH 1 /* TSDEN */ +#define WM8955_VSEL_MASK 0x00C0 /* VSEL - [7:6] */ +#define WM8955_VSEL_SHIFT 6 /* VSEL - [7:6] */ +#define WM8955_VSEL_WIDTH 2 /* VSEL - [7:6] */ +#define WM8955_DMONOMIX_MASK 0x0030 /* DMONOMIX - [5:4] */ +#define WM8955_DMONOMIX_SHIFT 4 /* DMONOMIX - [5:4] */ +#define WM8955_DMONOMIX_WIDTH 2 /* DMONOMIX - [5:4] */ +#define WM8955_DACINV 0x0002 /* DACINV */ +#define WM8955_DACINV_MASK 0x0002 /* DACINV */ +#define WM8955_DACINV_SHIFT 1 /* DACINV */ +#define WM8955_DACINV_WIDTH 1 /* DACINV */ +#define WM8955_TOEN 0x0001 /* TOEN */ +#define WM8955_TOEN_MASK 0x0001 /* TOEN */ +#define WM8955_TOEN_SHIFT 0 /* TOEN */ +#define WM8955_TOEN_WIDTH 1 /* TOEN */ + +/* + * R24 (0x18) - Additional control (2) + */ +#define WM8955_OUT3SW_MASK 0x0180 /* OUT3SW - [8:7] */ +#define WM8955_OUT3SW_SHIFT 7 /* OUT3SW - [8:7] */ +#define WM8955_OUT3SW_WIDTH 2 /* OUT3SW - [8:7] */ +#define WM8955_ROUT2INV 0x0010 /* ROUT2INV */ +#define WM8955_ROUT2INV_MASK 0x0010 /* ROUT2INV */ +#define WM8955_ROUT2INV_SHIFT 4 /* ROUT2INV */ +#define WM8955_ROUT2INV_WIDTH 1 /* ROUT2INV */ +#define WM8955_DACOSR 0x0001 /* DACOSR */ +#define WM8955_DACOSR_MASK 0x0001 /* DACOSR */ +#define WM8955_DACOSR_SHIFT 0 /* DACOSR */ +#define WM8955_DACOSR_WIDTH 1 /* DACOSR */ + +/* + * R25 (0x19) - Power Management (1) + */ +#define WM8955_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */ +#define WM8955_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */ +#define WM8955_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */ +#define WM8955_VREF 0x0040 /* VREF */ +#define WM8955_VREF_MASK 0x0040 /* VREF */ +#define WM8955_VREF_SHIFT 6 /* VREF */ +#define WM8955_VREF_WIDTH 1 /* VREF */ +#define WM8955_DIGENB 0x0001 /* DIGENB */ +#define WM8955_DIGENB_MASK 0x0001 /* DIGENB */ +#define WM8955_DIGENB_SHIFT 0 /* DIGENB */ +#define WM8955_DIGENB_WIDTH 1 /* DIGENB */ + +/* + * R26 (0x1A) - Power Management (2) + */ +#define WM8955_DACL 0x0100 /* DACL */ +#define WM8955_DACL_MASK 0x0100 /* DACL */ +#define WM8955_DACL_SHIFT 8 /* DACL */ +#define WM8955_DACL_WIDTH 1 /* DACL */ +#define WM8955_DACR 0x0080 /* DACR */ +#define WM8955_DACR_MASK 0x0080 /* DACR */ +#define WM8955_DACR_SHIFT 7 /* DACR */ +#define WM8955_DACR_WIDTH 1 /* DACR */ +#define WM8955_LOUT1 0x0040 /* LOUT1 */ +#define WM8955_LOUT1_MASK 0x0040 /* LOUT1 */ +#define WM8955_LOUT1_SHIFT 6 /* LOUT1 */ +#define WM8955_LOUT1_WIDTH 1 /* LOUT1 */ +#define WM8955_ROUT1 0x0020 /* ROUT1 */ +#define WM8955_ROUT1_MASK 0x0020 /* ROUT1 */ +#define WM8955_ROUT1_SHIFT 5 /* ROUT1 */ +#define WM8955_ROUT1_WIDTH 1 /* ROUT1 */ +#define WM8955_LOUT2 0x0010 /* LOUT2 */ +#define WM8955_LOUT2_MASK 0x0010 /* LOUT2 */ +#define WM8955_LOUT2_SHIFT 4 /* LOUT2 */ +#define WM8955_LOUT2_WIDTH 1 /* LOUT2 */ +#define WM8955_ROUT2 0x0008 /* ROUT2 */ +#define WM8955_ROUT2_MASK 0x0008 /* ROUT2 */ +#define WM8955_ROUT2_SHIFT 3 /* ROUT2 */ +#define WM8955_ROUT2_WIDTH 1 /* ROUT2 */ +#define WM8955_MONO 0x0004 /* MONO */ +#define WM8955_MONO_MASK 0x0004 /* MONO */ +#define WM8955_MONO_SHIFT 2 /* MONO */ +#define WM8955_MONO_WIDTH 1 /* MONO */ +#define WM8955_OUT3 0x0002 /* OUT3 */ +#define WM8955_OUT3_MASK 0x0002 /* OUT3 */ +#define WM8955_OUT3_SHIFT 1 /* OUT3 */ +#define WM8955_OUT3_WIDTH 1 /* OUT3 */ + +/* + * R27 (0x1B) - Additional Control (3) + */ +#define WM8955_VROI 0x0040 /* VROI */ +#define WM8955_VROI_MASK 0x0040 /* VROI */ +#define WM8955_VROI_SHIFT 6 /* VROI */ +#define WM8955_VROI_WIDTH 1 /* VROI */ + +/* + * R34 (0x22) - Left out Mix (1) + */ +#define WM8955_LD2LO 0x0100 /* LD2LO */ +#define WM8955_LD2LO_MASK 0x0100 /* LD2LO */ +#define WM8955_LD2LO_SHIFT 8 /* LD2LO */ +#define WM8955_LD2LO_WIDTH 1 /* LD2LO */ +#define WM8955_LI2LO 0x0080 /* LI2LO */ +#define WM8955_LI2LO_MASK 0x0080 /* LI2LO */ +#define WM8955_LI2LO_SHIFT 7 /* LI2LO */ +#define WM8955_LI2LO_WIDTH 1 /* LI2LO */ +#define WM8955_LI2LOVOL_MASK 0x0070 /* LI2LOVOL - [6:4] */ +#define WM8955_LI2LOVOL_SHIFT 4 /* LI2LOVOL - [6:4] */ +#define WM8955_LI2LOVOL_WIDTH 3 /* LI2LOVOL - [6:4] */ + +/* + * R35 (0x23) - Left out Mix (2) + */ +#define WM8955_RD2LO 0x0100 /* RD2LO */ +#define WM8955_RD2LO_MASK 0x0100 /* RD2LO */ +#define WM8955_RD2LO_SHIFT 8 /* RD2LO */ +#define WM8955_RD2LO_WIDTH 1 /* RD2LO */ +#define WM8955_RI2LO 0x0080 /* RI2LO */ +#define WM8955_RI2LO_MASK 0x0080 /* RI2LO */ +#define WM8955_RI2LO_SHIFT 7 /* RI2LO */ +#define WM8955_RI2LO_WIDTH 1 /* RI2LO */ +#define WM8955_RI2LOVOL_MASK 0x0070 /* RI2LOVOL - [6:4] */ +#define WM8955_RI2LOVOL_SHIFT 4 /* RI2LOVOL - [6:4] */ +#define WM8955_RI2LOVOL_WIDTH 3 /* RI2LOVOL - [6:4] */ + +/* + * R36 (0x24) - Right out Mix (1) + */ +#define WM8955_LD2RO 0x0100 /* LD2RO */ +#define WM8955_LD2RO_MASK 0x0100 /* LD2RO */ +#define WM8955_LD2RO_SHIFT 8 /* LD2RO */ +#define WM8955_LD2RO_WIDTH 1 /* LD2RO */ +#define WM8955_LI2RO 0x0080 /* LI2RO */ +#define WM8955_LI2RO_MASK 0x0080 /* LI2RO */ +#define WM8955_LI2RO_SHIFT 7 /* LI2RO */ +#define WM8955_LI2RO_WIDTH 1 /* LI2RO */ +#define WM8955_LI2ROVOL_MASK 0x0070 /* LI2ROVOL - [6:4] */ +#define WM8955_LI2ROVOL_SHIFT 4 /* LI2ROVOL - [6:4] */ +#define WM8955_LI2ROVOL_WIDTH 3 /* LI2ROVOL - [6:4] */ + +/* + * R37 (0x25) - Right Out Mix (2) + */ +#define WM8955_RD2RO 0x0100 /* RD2RO */ +#define WM8955_RD2RO_MASK 0x0100 /* RD2RO */ +#define WM8955_RD2RO_SHIFT 8 /* RD2RO */ +#define WM8955_RD2RO_WIDTH 1 /* RD2RO */ +#define WM8955_RI2RO 0x0080 /* RI2RO */ +#define WM8955_RI2RO_MASK 0x0080 /* RI2RO */ +#define WM8955_RI2RO_SHIFT 7 /* RI2RO */ +#define WM8955_RI2RO_WIDTH 1 /* RI2RO */ +#define WM8955_RI2ROVOL_MASK 0x0070 /* RI2ROVOL - [6:4] */ +#define WM8955_RI2ROVOL_SHIFT 4 /* RI2ROVOL - [6:4] */ +#define WM8955_RI2ROVOL_WIDTH 3 /* RI2ROVOL - [6:4] */ + +/* + * R38 (0x26) - Mono out Mix (1) + */ +#define WM8955_LD2MO 0x0100 /* LD2MO */ +#define WM8955_LD2MO_MASK 0x0100 /* LD2MO */ +#define WM8955_LD2MO_SHIFT 8 /* LD2MO */ +#define WM8955_LD2MO_WIDTH 1 /* LD2MO */ +#define WM8955_LI2MO 0x0080 /* LI2MO */ +#define WM8955_LI2MO_MASK 0x0080 /* LI2MO */ +#define WM8955_LI2MO_SHIFT 7 /* LI2MO */ +#define WM8955_LI2MO_WIDTH 1 /* LI2MO */ +#define WM8955_LI2MOVOL_MASK 0x0070 /* LI2MOVOL - [6:4] */ +#define WM8955_LI2MOVOL_SHIFT 4 /* LI2MOVOL - [6:4] */ +#define WM8955_LI2MOVOL_WIDTH 3 /* LI2MOVOL - [6:4] */ +#define WM8955_DMEN 0x0001 /* DMEN */ +#define WM8955_DMEN_MASK 0x0001 /* DMEN */ +#define WM8955_DMEN_SHIFT 0 /* DMEN */ +#define WM8955_DMEN_WIDTH 1 /* DMEN */ + +/* + * R39 (0x27) - Mono out Mix (2) + */ +#define WM8955_RD2MO 0x0100 /* RD2MO */ +#define WM8955_RD2MO_MASK 0x0100 /* RD2MO */ +#define WM8955_RD2MO_SHIFT 8 /* RD2MO */ +#define WM8955_RD2MO_WIDTH 1 /* RD2MO */ +#define WM8955_RI2MO 0x0080 /* RI2MO */ +#define WM8955_RI2MO_MASK 0x0080 /* RI2MO */ +#define WM8955_RI2MO_SHIFT 7 /* RI2MO */ +#define WM8955_RI2MO_WIDTH 1 /* RI2MO */ +#define WM8955_RI2MOVOL_MASK 0x0070 /* RI2MOVOL - [6:4] */ +#define WM8955_RI2MOVOL_SHIFT 4 /* RI2MOVOL - [6:4] */ +#define WM8955_RI2MOVOL_WIDTH 3 /* RI2MOVOL - [6:4] */ + +/* + * R40 (0x28) - LOUT2 volume + */ +#define WM8955_LO2VU 0x0100 /* LO2VU */ +#define WM8955_LO2VU_MASK 0x0100 /* LO2VU */ +#define WM8955_LO2VU_SHIFT 8 /* LO2VU */ +#define WM8955_LO2VU_WIDTH 1 /* LO2VU */ +#define WM8955_LO2ZC 0x0080 /* LO2ZC */ +#define WM8955_LO2ZC_MASK 0x0080 /* LO2ZC */ +#define WM8955_LO2ZC_SHIFT 7 /* LO2ZC */ +#define WM8955_LO2ZC_WIDTH 1 /* LO2ZC */ +#define WM8955_LOUT2VOL_MASK 0x007F /* LOUT2VOL - [6:0] */ +#define WM8955_LOUT2VOL_SHIFT 0 /* LOUT2VOL - [6:0] */ +#define WM8955_LOUT2VOL_WIDTH 7 /* LOUT2VOL - [6:0] */ + +/* + * R41 (0x29) - ROUT2 volume + */ +#define WM8955_RO2VU 0x0100 /* RO2VU */ +#define WM8955_RO2VU_MASK 0x0100 /* RO2VU */ +#define WM8955_RO2VU_SHIFT 8 /* RO2VU */ +#define WM8955_RO2VU_WIDTH 1 /* RO2VU */ +#define WM8955_RO2ZC 0x0080 /* RO2ZC */ +#define WM8955_RO2ZC_MASK 0x0080 /* RO2ZC */ +#define WM8955_RO2ZC_SHIFT 7 /* RO2ZC */ +#define WM8955_RO2ZC_WIDTH 1 /* RO2ZC */ +#define WM8955_ROUT2VOL_MASK 0x007F /* ROUT2VOL - [6:0] */ +#define WM8955_ROUT2VOL_SHIFT 0 /* ROUT2VOL - [6:0] */ +#define WM8955_ROUT2VOL_WIDTH 7 /* ROUT2VOL - [6:0] */ + +/* + * R42 (0x2A) - MONOOUT volume + */ +#define WM8955_MOZC 0x0080 /* MOZC */ +#define WM8955_MOZC_MASK 0x0080 /* MOZC */ +#define WM8955_MOZC_SHIFT 7 /* MOZC */ +#define WM8955_MOZC_WIDTH 1 /* MOZC */ +#define WM8955_MOUTVOL_MASK 0x007F /* MOUTVOL - [6:0] */ +#define WM8955_MOUTVOL_SHIFT 0 /* MOUTVOL - [6:0] */ +#define WM8955_MOUTVOL_WIDTH 7 /* MOUTVOL - [6:0] */ + +/* + * R43 (0x2B) - Clocking / PLL + */ +#define WM8955_MCLKSEL 0x0100 /* MCLKSEL */ +#define WM8955_MCLKSEL_MASK 0x0100 /* MCLKSEL */ +#define WM8955_MCLKSEL_SHIFT 8 /* MCLKSEL */ +#define WM8955_MCLKSEL_WIDTH 1 /* MCLKSEL */ +#define WM8955_PLLOUTDIV2 0x0020 /* PLLOUTDIV2 */ +#define WM8955_PLLOUTDIV2_MASK 0x0020 /* PLLOUTDIV2 */ +#define WM8955_PLLOUTDIV2_SHIFT 5 /* PLLOUTDIV2 */ +#define WM8955_PLLOUTDIV2_WIDTH 1 /* PLLOUTDIV2 */ +#define WM8955_PLL_RB 0x0010 /* PLL_RB */ +#define WM8955_PLL_RB_MASK 0x0010 /* PLL_RB */ +#define WM8955_PLL_RB_SHIFT 4 /* PLL_RB */ +#define WM8955_PLL_RB_WIDTH 1 /* PLL_RB */ +#define WM8955_PLLEN 0x0008 /* PLLEN */ +#define WM8955_PLLEN_MASK 0x0008 /* PLLEN */ +#define WM8955_PLLEN_SHIFT 3 /* PLLEN */ +#define WM8955_PLLEN_WIDTH 1 /* PLLEN */ + +/* + * R44 (0x2C) - PLL Control 1 + */ +#define WM8955_N_MASK 0x01E0 /* N - [8:5] */ +#define WM8955_N_SHIFT 5 /* N - [8:5] */ +#define WM8955_N_WIDTH 4 /* N - [8:5] */ +#define WM8955_K_21_18_MASK 0x000F /* K(21:18) - [3:0] */ +#define WM8955_K_21_18_SHIFT 0 /* K(21:18) - [3:0] */ +#define WM8955_K_21_18_WIDTH 4 /* K(21:18) - [3:0] */ + +/* + * R45 (0x2D) - PLL Control 2 + */ +#define WM8955_K_17_9_MASK 0x01FF /* K(17:9) - [8:0] */ +#define WM8955_K_17_9_SHIFT 0 /* K(17:9) - [8:0] */ +#define WM8955_K_17_9_WIDTH 9 /* K(17:9) - [8:0] */ + +/* + * R46 (0x2E) - PLL Control 3 + */ +#define WM8955_K_8_0_MASK 0x01FF /* K(8:0) - [8:0] */ +#define WM8955_K_8_0_SHIFT 0 /* K(8:0) - [8:0] */ +#define WM8955_K_8_0_WIDTH 9 /* K(8:0) - [8:0] */ + +/* + * R59 (0x3B) - PLL Control 4 + */ +#define WM8955_KEN 0x0080 /* KEN */ +#define WM8955_KEN_MASK 0x0080 /* KEN */ +#define WM8955_KEN_SHIFT 7 /* KEN */ +#define WM8955_KEN_WIDTH 1 /* KEN */ + +#endif -- cgit v1.2.3 From 0d34e91596ef537c2893a031f0483014bb82adf3 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Wed, 27 Jan 2010 18:56:23 +0100 Subject: ASoC: add a WM8978 codec driver The WM8978 codec from Wolfson Microelectronics is very similar to wm8974, but is stereo and also has some differences in pin configuration and internal signal routing. This driver is based on wm8974 and takes the differences into account. Signed-off-by: Guennadi Liakhovetski Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8978.c | 1124 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8978.h | 89 ++++ 4 files changed, 1219 insertions(+) create mode 100644 sound/soc/codecs/wm8978.c create mode 100644 sound/soc/codecs/wm8978.h (limited to 'sound/soc/codecs/Kconfig') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 62ff26a08a2f..0aad72fc1961 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -57,6 +57,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8961 if I2C select SND_SOC_WM8971 if I2C select SND_SOC_WM8974 if I2C + select SND_SOC_WM8978 if I2C select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C select SND_SOC_WM8993 if I2C @@ -230,6 +231,9 @@ config SND_SOC_WM8971 config SND_SOC_WM8974 tristate +config SND_SOC_WM8978 + tristate + config SND_SOC_WM8988 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index ea9835412e6a..fbd290e41e9e 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -44,6 +44,7 @@ snd-soc-wm8960-objs := wm8960.o snd-soc-wm8961-objs := wm8961.o snd-soc-wm8971-objs := wm8971.o snd-soc-wm8974-objs := wm8974.o +snd-soc-wm8978-objs := wm8978.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o snd-soc-wm8993-objs := wm8993.o @@ -103,6 +104,7 @@ obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o obj-$(CONFIG_SND_SOC_WM8974) += snd-soc-wm8974.o +obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c new file mode 100644 index 000000000000..d9d4e9dd1adb --- /dev/null +++ b/sound/soc/codecs/wm8978.c @@ -0,0 +1,1124 @@ +/* + * wm8978.c -- WM8978 ALSA SoC Audio Codec driver + * + * Copyright (C) 2009-2010 Guennadi Liakhovetski + * Copyright (C) 2007 Carlos Munoz + * Copyright 2006-2009 Wolfson Microelectronics PLC. + * Based on wm8974 and wm8990 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8978.h" + +static struct snd_soc_codec *wm8978_codec; + +/* wm8978 register cache. Note that register 0 is not included in the cache. */ +static const u16 wm8978_reg[WM8978_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x00...0x03 */ + 0x0050, 0x0000, 0x0140, 0x0000, /* 0x04...0x07 */ + 0x0000, 0x0000, 0x0000, 0x00ff, /* 0x08...0x0b */ + 0x00ff, 0x0000, 0x0100, 0x00ff, /* 0x0c...0x0f */ + 0x00ff, 0x0000, 0x012c, 0x002c, /* 0x10...0x13 */ + 0x002c, 0x002c, 0x002c, 0x0000, /* 0x14...0x17 */ + 0x0032, 0x0000, 0x0000, 0x0000, /* 0x18...0x1b */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x1c...0x1f */ + 0x0038, 0x000b, 0x0032, 0x0000, /* 0x20...0x23 */ + 0x0008, 0x000c, 0x0093, 0x00e9, /* 0x24...0x27 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 0x28...0x2b */ + 0x0033, 0x0010, 0x0010, 0x0100, /* 0x2c...0x2f */ + 0x0100, 0x0002, 0x0001, 0x0001, /* 0x30...0x33 */ + 0x0039, 0x0039, 0x0039, 0x0039, /* 0x34...0x37 */ + 0x0001, 0x0001, /* 0x38...0x3b */ +}; + +/* codec private data */ +struct wm8978_priv { + struct snd_soc_codec codec; + unsigned int f_pllout; + unsigned int f_mclk; + unsigned int f_256fs; + unsigned int f_opclk; + enum wm8978_sysclk_src sysclk; + u16 reg_cache[WM8978_CACHEREGNUM]; +}; + +static const char *wm8978_companding[] = {"Off", "NC", "u-law", "A-law"}; +static const char *wm8978_eqmode[] = {"Capture", "Playback"}; +static const char *wm8978_bw[] = {"Narrow", "Wide"}; +static const char *wm8978_eq1[] = {"80Hz", "105Hz", "135Hz", "175Hz"}; +static const char *wm8978_eq2[] = {"230Hz", "300Hz", "385Hz", "500Hz"}; +static const char *wm8978_eq3[] = {"650Hz", "850Hz", "1.1kHz", "1.4kHz"}; +static const char *wm8978_eq4[] = {"1.8kHz", "2.4kHz", "3.2kHz", "4.1kHz"}; +static const char *wm8978_eq5[] = {"5.3kHz", "6.9kHz", "9kHz", "11.7kHz"}; +static const char *wm8978_alc3[] = {"ALC", "Limiter"}; +static const char *wm8978_alc1[] = {"Off", "Right", "Left", "Both"}; + +static const SOC_ENUM_SINGLE_DECL(adc_compand, WM8978_COMPANDING_CONTROL, 1, + wm8978_companding); +static const SOC_ENUM_SINGLE_DECL(dac_compand, WM8978_COMPANDING_CONTROL, 3, + wm8978_companding); +static const SOC_ENUM_SINGLE_DECL(eqmode, WM8978_EQ1, 8, wm8978_eqmode); +static const SOC_ENUM_SINGLE_DECL(eq1, WM8978_EQ1, 5, wm8978_eq1); +static const SOC_ENUM_SINGLE_DECL(eq2bw, WM8978_EQ2, 8, wm8978_bw); +static const SOC_ENUM_SINGLE_DECL(eq2, WM8978_EQ2, 5, wm8978_eq2); +static const SOC_ENUM_SINGLE_DECL(eq3bw, WM8978_EQ3, 8, wm8978_bw); +static const SOC_ENUM_SINGLE_DECL(eq3, WM8978_EQ3, 5, wm8978_eq3); +static const SOC_ENUM_SINGLE_DECL(eq4bw, WM8978_EQ4, 8, wm8978_bw); +static const SOC_ENUM_SINGLE_DECL(eq4, WM8978_EQ4, 5, wm8978_eq4); +static const SOC_ENUM_SINGLE_DECL(eq5, WM8978_EQ5, 5, wm8978_eq5); +static const SOC_ENUM_SINGLE_DECL(alc3, WM8978_ALC_CONTROL_3, 8, wm8978_alc3); +static const SOC_ENUM_SINGLE_DECL(alc1, WM8978_ALC_CONTROL_1, 7, wm8978_alc1); + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0); +static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0); +static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 1); + +static const struct snd_kcontrol_new wm8978_snd_controls[] = { + + SOC_SINGLE("Digital Loopback Switch", + WM8978_COMPANDING_CONTROL, 0, 1, 0), + + SOC_ENUM("ADC Companding", adc_compand), + SOC_ENUM("DAC Companding", dac_compand), + + SOC_DOUBLE("DAC Inversion Switch", WM8978_DAC_CONTROL, 0, 1, 1, 0), + + SOC_DOUBLE_R_TLV("PCM Volume", + WM8978_LEFT_DAC_DIGITAL_VOLUME, WM8978_RIGHT_DAC_DIGITAL_VOLUME, + 0, 255, 0, digital_tlv), + + SOC_SINGLE("High Pass Filter Switch", WM8978_ADC_CONTROL, 8, 1, 0), + SOC_SINGLE("High Pass Cut Off", WM8978_ADC_CONTROL, 4, 7, 0), + SOC_DOUBLE("ADC Inversion Switch", WM8978_ADC_CONTROL, 0, 1, 1, 0), + + SOC_DOUBLE_R_TLV("ADC Volume", + WM8978_LEFT_ADC_DIGITAL_VOLUME, WM8978_RIGHT_ADC_DIGITAL_VOLUME, + 0, 255, 0, digital_tlv), + + SOC_ENUM("Equaliser Function", eqmode), + SOC_ENUM("EQ1 Cut Off", eq1), + SOC_SINGLE_TLV("EQ1 Volume", WM8978_EQ1, 0, 24, 1, eq_tlv), + + SOC_ENUM("Equaliser EQ2 Bandwith", eq2bw), + SOC_ENUM("EQ2 Cut Off", eq2), + SOC_SINGLE_TLV("EQ2 Volume", WM8978_EQ2, 0, 24, 1, eq_tlv), + + SOC_ENUM("Equaliser EQ3 Bandwith", eq3bw), + SOC_ENUM("EQ3 Cut Off", eq3), + SOC_SINGLE_TLV("EQ3 Volume", WM8978_EQ3, 0, 24, 1, eq_tlv), + + SOC_ENUM("Equaliser EQ4 Bandwith", eq4bw), + SOC_ENUM("EQ4 Cut Off", eq4), + SOC_SINGLE_TLV("EQ4 Volume", WM8978_EQ4, 0, 24, 1, eq_tlv), + + SOC_ENUM("EQ5 Cut Off", eq5), + SOC_SINGLE_TLV("EQ5 Volume", WM8978_EQ5, 0, 24, 1, eq_tlv), + + SOC_SINGLE("DAC Playback Limiter Switch", + WM8978_DAC_LIMITER_1, 8, 1, 0), + SOC_SINGLE("DAC Playback Limiter Decay", + WM8978_DAC_LIMITER_1, 4, 15, 0), + SOC_SINGLE("DAC Playback Limiter Attack", + WM8978_DAC_LIMITER_1, 0, 15, 0), + + SOC_SINGLE("DAC Playback Limiter Threshold", + WM8978_DAC_LIMITER_2, 4, 7, 0), + SOC_SINGLE("DAC Playback Limiter Boost", + WM8978_DAC_LIMITER_2, 0, 15, 0), + + SOC_ENUM("ALC Enable Switch", alc1), + SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 7, 0), + SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0), + + SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 7, 0), + SOC_SINGLE("ALC Capture Target", WM8978_ALC_CONTROL_2, 0, 15, 0), + + SOC_ENUM("ALC Capture Mode", alc3), + SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 15, 0), + SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 15, 0), + + SOC_SINGLE("ALC Capture Noise Gate Switch", WM8978_NOISE_GATE, 3, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Threshold", + WM8978_NOISE_GATE, 0, 7, 0), + + SOC_DOUBLE_R("Capture PGA ZC Switch", + WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL, + 7, 1, 0), + + /* OUT1 - Headphones */ + SOC_DOUBLE_R("Headphone Playback ZC Switch", + WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 7, 1, 0), + + SOC_DOUBLE_R_TLV("Headphone Playback Volume", + WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, + 0, 63, 0, spk_tlv), + + /* OUT2 - Speakers */ + SOC_DOUBLE_R("Speaker Playback ZC Switch", + WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 7, 1, 0), + + SOC_DOUBLE_R_TLV("Speaker Playback Volume", + WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, + 0, 63, 0, spk_tlv), + + /* OUT3/4 - Line Output */ + SOC_DOUBLE_R("Line Playback Switch", + WM8978_OUT3_MIXER_CONTROL, WM8978_OUT4_MIXER_CONTROL, 6, 1, 1), + + /* Mixer #3: Boost (Input) mixer */ + SOC_DOUBLE_R("PGA Boost (+20dB)", + WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL, + 8, 1, 0), + SOC_DOUBLE_R_TLV("L2/R2 Boost Volume", + WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL, + 4, 7, 0, boost_tlv), + SOC_DOUBLE_R_TLV("Aux Boost Volume", + WM8978_LEFT_ADC_BOOST_CONTROL, WM8978_RIGHT_ADC_BOOST_CONTROL, + 0, 7, 0, boost_tlv), + + /* Input PGA volume */ + SOC_DOUBLE_R_TLV("Input PGA Volume", + WM8978_LEFT_INP_PGA_CONTROL, WM8978_RIGHT_INP_PGA_CONTROL, + 0, 63, 0, inpga_tlv), + + /* Headphone */ + SOC_DOUBLE_R("Headphone Switch", + WM8978_LOUT1_HP_CONTROL, WM8978_ROUT1_HP_CONTROL, 6, 1, 1), + + /* Speaker */ + SOC_DOUBLE_R("Speaker Switch", + WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 6, 1, 1), +}; + +/* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */ +static const struct snd_kcontrol_new wm8978_left_out_mixer[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_LEFT_MIXER_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_LEFT_MIXER_CONTROL, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_LEFT_MIXER_CONTROL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8978_right_out_mixer[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8978_RIGHT_MIXER_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8978_RIGHT_MIXER_CONTROL, 0, 1, 0), +}; + +/* OUT3/OUT4 Mixer not implemented */ + +/* Mixer #2: Input PGA Mute */ +static const struct snd_kcontrol_new wm8978_left_input_mixer[] = { + SOC_DAPM_SINGLE("L2 Switch", WM8978_INPUT_CONTROL, 2, 1, 0), + SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 0, 1, 0), +}; +static const struct snd_kcontrol_new wm8978_right_input_mixer[] = { + SOC_DAPM_SINGLE("R2 Switch", WM8978_INPUT_CONTROL, 6, 1, 0), + SOC_DAPM_SINGLE("MicN Switch", WM8978_INPUT_CONTROL, 5, 1, 0), + SOC_DAPM_SINGLE("MicP Switch", WM8978_INPUT_CONTROL, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8978_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", + WM8978_POWER_MANAGEMENT_3, 0, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", + WM8978_POWER_MANAGEMENT_3, 1, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", + WM8978_POWER_MANAGEMENT_2, 0, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", + WM8978_POWER_MANAGEMENT_2, 1, 0), + + /* Mixer #1: OUT1,2 */ + SOC_MIXER_ARRAY("Left Output Mixer", WM8978_POWER_MANAGEMENT_3, + 2, 0, wm8978_left_out_mixer), + SOC_MIXER_ARRAY("Right Output Mixer", WM8978_POWER_MANAGEMENT_3, + 3, 0, wm8978_right_out_mixer), + + SOC_MIXER_ARRAY("Left Input Mixer", WM8978_POWER_MANAGEMENT_2, + 2, 0, wm8978_left_input_mixer), + SOC_MIXER_ARRAY("Right Input Mixer", WM8978_POWER_MANAGEMENT_2, + 3, 0, wm8978_right_input_mixer), + + SND_SOC_DAPM_PGA("Left Boost Mixer", WM8978_POWER_MANAGEMENT_2, + 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Boost Mixer", WM8978_POWER_MANAGEMENT_2, + 5, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Left Capture PGA", WM8978_LEFT_INP_PGA_CONTROL, + 6, 1, NULL, 0), + SND_SOC_DAPM_PGA("Right Capture PGA", WM8978_RIGHT_INP_PGA_CONTROL, + 6, 1, NULL, 0), + + SND_SOC_DAPM_PGA("Left Headphone Out", WM8978_POWER_MANAGEMENT_2, + 7, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Headphone Out", WM8978_POWER_MANAGEMENT_2, + 8, 0, NULL, 0), + + SND_SOC_DAPM_PGA("Left Speaker Out", WM8978_POWER_MANAGEMENT_3, + 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Speaker Out", WM8978_POWER_MANAGEMENT_3, + 5, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("OUT4 VMID", WM8978_POWER_MANAGEMENT_3, + 8, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8978_POWER_MANAGEMENT_1, 4, 0), + + SND_SOC_DAPM_INPUT("LMICN"), + SND_SOC_DAPM_INPUT("LMICP"), + SND_SOC_DAPM_INPUT("RMICN"), + SND_SOC_DAPM_INPUT("RMICP"), + SND_SOC_DAPM_INPUT("LAUX"), + SND_SOC_DAPM_INPUT("RAUX"), + SND_SOC_DAPM_INPUT("L2"), + SND_SOC_DAPM_INPUT("R2"), + SND_SOC_DAPM_OUTPUT("LHP"), + SND_SOC_DAPM_OUTPUT("RHP"), + SND_SOC_DAPM_OUTPUT("LSPK"), + SND_SOC_DAPM_OUTPUT("RSPK"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Output mixer */ + {"Right Output Mixer", "PCM Playback Switch", "Right DAC"}, + {"Right Output Mixer", "Aux Playback Switch", "RAUX"}, + {"Right Output Mixer", "Line Bypass Switch", "Right Boost Mixer"}, + + {"Left Output Mixer", "PCM Playback Switch", "Left DAC"}, + {"Left Output Mixer", "Aux Playback Switch", "LAUX"}, + {"Left Output Mixer", "Line Bypass Switch", "Left Boost Mixer"}, + + /* Outputs */ + {"Right Headphone Out", NULL, "Right Output Mixer"}, + {"RHP", NULL, "Right Headphone Out"}, + + {"Left Headphone Out", NULL, "Left Output Mixer"}, + {"LHP", NULL, "Left Headphone Out"}, + + {"Right Speaker Out", NULL, "Right Output Mixer"}, + {"RSPK", NULL, "Right Speaker Out"}, + + {"Left Speaker Out", NULL, "Left Output Mixer"}, + {"LSPK", NULL, "Left Speaker Out"}, + + /* Boost Mixer */ + {"Right ADC", NULL, "Right Boost Mixer"}, + + {"Right Boost Mixer", NULL, "RAUX"}, + {"Right Boost Mixer", NULL, "Right Capture PGA"}, + {"Right Boost Mixer", NULL, "R2"}, + + {"Left ADC", NULL, "Left Boost Mixer"}, + + {"Left Boost Mixer", NULL, "LAUX"}, + {"Left Boost Mixer", NULL, "Left Capture PGA"}, + {"Left Boost Mixer", NULL, "L2"}, + + /* Input PGA */ + {"Right Capture PGA", NULL, "Right Input Mixer"}, + {"Left Capture PGA", NULL, "Left Input Mixer"}, + + {"Right Input Mixer", "R2 Switch", "R2"}, + {"Right Input Mixer", "MicN Switch", "RMICN"}, + {"Right Input Mixer", "MicP Switch", "RMICP"}, + + {"Left Input Mixer", "L2 Switch", "L2"}, + {"Left Input Mixer", "MicN Switch", "LMICN"}, + {"Left Input Mixer", "MicP Switch", "LMICP"}, +}; + +static int wm8978_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8978_dapm_widgets, + ARRAY_SIZE(wm8978_dapm_widgets)); + + /* set up the WM8978 audio map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + return 0; +} + +/* PLL divisors */ +struct wm8978_pll_div { + u32 k; + u8 n; + u8 div2; +}; + +#define FIXED_PLL_SIZE (1 << 24) + +static void pll_factors(struct wm8978_pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 k_part; + unsigned int k, n_div, n_mod; + + n_div = target / source; + if (n_div < 6) { + source >>= 1; + pll_div->div2 = 1; + n_div = target / source; + } else { + pll_div->div2 = 0; + } + + if (n_div < 6 || n_div > 12) + dev_warn(wm8978_codec->dev, + "WM8978 N value exceeds recommended range! N = %u\n", + n_div); + + pll_div->n = n_div; + n_mod = target - source * n_div; + k_part = FIXED_PLL_SIZE * (long long)n_mod + source / 2; + + do_div(k_part, source); + + k = k_part & 0xFFFFFFFF; + + pll_div->k = k; +} +/* + * Calculate internal frequencies and dividers, according to Figure 40 + * "PLL and Clock Select Circuit" in WM8978 datasheet Rev. 2.6 + */ +static int wm8978_configure_pll(struct snd_soc_codec *codec) +{ + struct wm8978_priv *wm8978 = codec->private_data; + struct wm8978_pll_div pll_div; + unsigned int f_opclk = wm8978->f_opclk, f_mclk = wm8978->f_mclk, + f_256fs = wm8978->f_256fs; + unsigned int f2, opclk_div; + + if (!f_mclk) + return -EINVAL; + + if (f_opclk) { + /* + * The user needs OPCLK. Choose OPCLKDIV to put + * 6 <= R = f2 / f1 < 13, 1 <= OPCLKDIV <= 4. + * f_opclk = f_mclk * prescale * R / 4 / OPCLKDIV, where + * prescale = 1, or prescale = 2. Prescale is calculated inside + * pll_factors(). We have to select f_PLLOUT, such that + * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be + * f_mclk * 3 / 16 <= f_opclk < f_mclk * 13 / 4. + */ + if (16 * f_opclk < 3 * f_mclk || 4 * f_opclk >= 13 * f_mclk) + return -EINVAL; + + if (4 * f_opclk < 3 * f_mclk) + /* Have to use OPCLKDIV */ + opclk_div = (3 * f_mclk / 4 + f_opclk - 1) / f_opclk; + else + opclk_div = 1; + + dev_dbg(codec->dev, "%s: OPCLKDIV=%d\n", __func__, opclk_div); + + snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 0x30, + (opclk_div - 1) << 4); + + wm8978->f_pllout = f_opclk * opclk_div; + } else if (f_256fs) { + /* + * Not using OPCLK, choose R: + * 6 <= R = f2 / f1 < 13, to put 1 <= MCLKDIV <= 12. + * f_256fs = f_mclk * prescale * R / 4 / MCLKDIV, where + * prescale = 1, or prescale = 2. Prescale is calculated inside + * pll_factors(). We have to select f_PLLOUT, such that + * f_mclk * 3 / 4 <= f_PLLOUT < f_mclk * 13 / 4. Must be + * f_mclk * 3 / 48 <= f_256fs < f_mclk * 13 / 4. This means MCLK + * must be 3.781MHz <= f_MCLK <= 32.768MHz + */ + if (48 * f_256fs < 3 * f_mclk || 4 * f_256fs >= 13 * f_mclk) + return -EINVAL; + + /* + * MCLKDIV will be selected in .hw_params(), just choose a + * suitable f_PLLOUT + */ + if (4 * f_256fs < 3 * f_mclk) + /* Will have to use MCLKDIV */ + wm8978->f_pllout = wm8978->f_mclk * 3 / 4; + else + wm8978->f_pllout = f_256fs; + + /* GPIO1 into default mode as input - before configuring PLL */ + snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0); + } else { + return -EINVAL; + } + + f2 = wm8978->f_pllout * 4; + + dev_dbg(codec->dev, "%s: f_MCLK=%uHz, f_PLLOUT=%uHz\n", __func__, + wm8978->f_mclk, wm8978->f_pllout); + + pll_factors(&pll_div, f2, wm8978->f_mclk); + + dev_dbg(codec->dev, "%s: calculated PLL N=0x%x, K=0x%x, div2=%d\n", + __func__, pll_div.n, pll_div.k, pll_div.div2); + + /* Turn PLL off for configuration... */ + snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0); + + snd_soc_write(codec, WM8978_PLL_N, (pll_div.div2 << 4) | pll_div.n); + snd_soc_write(codec, WM8978_PLL_K1, pll_div.k >> 18); + snd_soc_write(codec, WM8978_PLL_K2, (pll_div.k >> 9) & 0x1ff); + snd_soc_write(codec, WM8978_PLL_K3, pll_div.k & 0x1ff); + + /* ...and on again */ + snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20); + + if (f_opclk) + /* Output PLL (OPCLK) to GPIO1 */ + snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 4); + + return 0; +} + +/* + * Configure WM8978 clock dividers. + */ +static int wm8978_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8978_priv *wm8978 = codec->private_data; + int ret = 0; + + switch (div_id) { + case WM8978_OPCLKRATE: + wm8978->f_opclk = div; + + if (wm8978->f_mclk) + ret = wm8978_configure_pll(codec); + break; + case WM8978_MCLKDIV: + if (div & ~0xe0) + return -EINVAL; + snd_soc_update_bits(codec, WM8978_CLOCKING, 0xe0, div); + break; + case WM8978_ADCCLK: + if (div & ~8) + return -EINVAL; + snd_soc_update_bits(codec, WM8978_ADC_CONTROL, 8, div); + break; + case WM8978_DACCLK: + if (div & ~8) + return -EINVAL; + snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 8, div); + break; + case WM8978_BCLKDIV: + if (div & ~0x1c) + return -EINVAL; + snd_soc_update_bits(codec, WM8978_CLOCKING, 0x1c, div); + break; + default: + return -EINVAL; + } + + dev_dbg(codec->dev, "%s: ID %d, value %u\n", __func__, div_id, div); + + return ret; +} + +/* + * @freq: when .set_pll() us not used, freq is codec MCLK input frequency + */ +static int wm8978_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 wm8978_priv *wm8978 = codec->private_data; + int ret = 0; + + dev_dbg(codec->dev, "%s: ID %d, freq %u\n", __func__, clk_id, freq); + + if (freq) { + wm8978->f_mclk = freq; + + /* Even if MCLK is used for system clock, might have to drive OPCLK */ + if (wm8978->f_opclk) + ret = wm8978_configure_pll(codec); + + /* Our sysclk is fixed to 256 * fs, will configure in .hw_params() */ + + if (!ret) + wm8978->sysclk = clk_id; + } + + if (wm8978->sysclk == WM8978_PLL && (!freq || clk_id == WM8978_MCLK)) { + /* Clock CODEC directly from MCLK */ + snd_soc_update_bits(codec, WM8978_CLOCKING, 0x100, 0); + + /* GPIO1 into default mode as input - before configuring PLL */ + snd_soc_update_bits(codec, WM8978_GPIO_CONTROL, 7, 0); + + /* Turn off PLL */ + snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0); + wm8978->sysclk = WM8978_MCLK; + wm8978->f_pllout = 0; + wm8978->f_opclk = 0; + } + + return ret; +} + +/* + * Set ADC and Voice DAC format. + */ +static int wm8978_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + /* + * BCLK polarity mask = 0x100, LRC clock polarity mask = 0x80, + * Data Format mask = 0x18: all will be calculated anew + */ + u16 iface = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x198; + u16 clk = snd_soc_read(codec, WM8978_CLOCKING); + + dev_dbg(codec->dev, "%s\n", __func__); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + clk &= ~1; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x8; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x18; + 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 |= 0x180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x80; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface); + snd_soc_write(codec, WM8978_CLOCKING, clk); + + return 0; +} + +/* MCLK dividers */ +static const int mclk_numerator[] = {1, 3, 2, 3, 4, 6, 8, 12}; +static const int mclk_denominator[] = {1, 2, 1, 1, 1, 1, 1, 1}; + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8978_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8978_priv *wm8978 = codec->private_data; + /* Word length mask = 0x60 */ + u16 iface_ctl = snd_soc_read(codec, WM8978_AUDIO_INTERFACE) & ~0x60; + /* Sampling rate mask = 0xe (for filters) */ + u16 add_ctl = snd_soc_read(codec, WM8978_ADDITIONAL_CONTROL) & ~0xe; + u16 clking = snd_soc_read(codec, WM8978_CLOCKING); + enum wm8978_sysclk_src current_clk_id = clking & 0x100 ? + WM8978_PLL : WM8978_MCLK; + unsigned int f_sel, diff, diff_best = INT_MAX; + int i, best = 0; + + if (!wm8978->f_mclk) + return -EINVAL; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface_ctl |= 0x20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface_ctl |= 0x40; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface_ctl |= 0x60; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case 8000: + add_ctl |= 0x5 << 1; + break; + case 11025: + add_ctl |= 0x4 << 1; + break; + case 16000: + add_ctl |= 0x3 << 1; + break; + case 22050: + add_ctl |= 0x2 << 1; + break; + case 32000: + add_ctl |= 0x1 << 1; + break; + case 44100: + case 48000: + break; + } + + /* Sampling rate is known now, can configure the MCLK divider */ + wm8978->f_256fs = params_rate(params) * 256; + + if (wm8978->sysclk == WM8978_MCLK) { + f_sel = wm8978->f_mclk; + } else { + if (!wm8978->f_pllout) { + int ret = wm8978_configure_pll(codec); + if (ret < 0) + return ret; + } + f_sel = wm8978->f_pllout; + } + + /* + * In some cases it is possible to reconfigure PLL to a higher frequency + * by raising OPCLKDIV, but normally OPCLK is configured to 256 * fs or + * 512 * fs, so, we should be fine. + */ + if (f_sel < wm8978->f_256fs || f_sel > 12 * wm8978->f_256fs) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mclk_numerator); i++) { + diff = abs(wm8978->f_256fs * 3 - + f_sel * 3 * mclk_denominator[i] / mclk_numerator[i]); + + if (diff < diff_best) { + diff_best = diff; + best = i; + } + + if (!diff) + break; + } + + if (diff) + dev_warn(codec->dev, "Imprecise clock: %u%s\n", + f_sel * mclk_denominator[best] / mclk_numerator[best], + wm8978->sysclk == WM8978_MCLK ? + ", consider using PLL" : ""); + + dev_dbg(codec->dev, "%s: fmt %d, rate %u, MCLK divisor #%d\n", __func__, + params_format(params), params_rate(params), best); + + /* MCLK divisor mask = 0xe0 */ + snd_soc_update_bits(codec, WM8978_CLOCKING, 0xe0, best << 5); + + snd_soc_write(codec, WM8978_AUDIO_INTERFACE, iface_ctl); + snd_soc_write(codec, WM8978_ADDITIONAL_CONTROL, add_ctl); + + if (wm8978->sysclk != current_clk_id) { + if (wm8978->sysclk == WM8978_PLL) + /* Run CODEC from PLL instead of MCLK */ + snd_soc_update_bits(codec, WM8978_CLOCKING, + 0x100, 0x100); + else + /* Clock CODEC directly from MCLK */ + snd_soc_update_bits(codec, WM8978_CLOCKING, 0x100, 0); + } + + return 0; +} + +static int wm8978_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + dev_dbg(codec->dev, "%s: %d\n", __func__, mute); + + if (mute) + snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 0x40, 0x40); + else + snd_soc_update_bits(codec, WM8978_DAC_CONTROL, 0x40, 0); + + return 0; +} + +static int wm8978_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 power1 = snd_soc_read(codec, WM8978_POWER_MANAGEMENT_1) & ~3; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + power1 |= 1; /* VMID 75k */ + snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, power1); + break; + case SND_SOC_BIAS_STANDBY: + /* bit 3: enable bias, bit 2: enable I/O tie off buffer */ + power1 |= 0xc; + + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Initial cap charge at VMID 5k */ + snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, + power1 | 0x3); + mdelay(100); + } + + power1 |= 0x2; /* VMID 500k */ + snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, power1); + break; + case SND_SOC_BIAS_OFF: + /* Preserve PLL - OPCLK may be used by someone */ + snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, ~0x20, 0); + snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0); + snd_soc_write(codec, WM8978_POWER_MANAGEMENT_3, 0); + break; + } + + dev_dbg(codec->dev, "%s: %d, %x\n", __func__, level, power1); + + codec->bias_level = level; + return 0; +} + +#define WM8978_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops wm8978_dai_ops = { + .hw_params = wm8978_hw_params, + .digital_mute = wm8978_mute, + .set_fmt = wm8978_set_dai_fmt, + .set_clkdiv = wm8978_set_dai_clkdiv, + .set_sysclk = wm8978_set_dai_sysclk, +}; + +/* Also supports 12kHz */ +struct snd_soc_dai wm8978_dai = { + .name = "WM8978 HiFi", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8978_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = WM8978_FORMATS, + }, + .ops = &wm8978_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm8978_dai); + +static int wm8978_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8978_set_bias_level(codec, SND_SOC_BIAS_OFF); + /* Also switch PLL off */ + snd_soc_write(codec, WM8978_POWER_MANAGEMENT_1, 0); + /* Put to sleep */ + snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0x40); + + return 0; +} + +static int wm8978_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8978_priv *wm8978 = codec->private_data; + int i; + u16 *cache = codec->reg_cache; + + /* Wake up the codec */ + snd_soc_write(codec, WM8978_POWER_MANAGEMENT_2, 0); + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8978_reg); i++) { + if (i == WM8978_RESET) + continue; + if (cache[i] != wm8978_reg[i]) + snd_soc_write(codec, i, cache[i]); + } + + wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + if (wm8978->f_pllout) + /* Switch PLL on */ + snd_soc_update_bits(codec, WM8978_POWER_MANAGEMENT_1, 0x20, 0x20); + + return 0; +} + +static int wm8978_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8978_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8978_codec; + codec = wm8978_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8978_snd_controls, + ARRAY_SIZE(wm8978_snd_controls)); + wm8978_add_widgets(codec); + +pcm_err: + return ret; +} + +/* power down chip */ +static int wm8978_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8978 = { + .probe = wm8978_probe, + .remove = wm8978_remove, + .suspend = wm8978_suspend, + .resume = wm8978_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8978); + +/* + * These registers contain an "update" bit - bit 8. This means, for example, + * that one can write new DAC digital volume for both channels, but only when + * the update bit is set, will also the volume be updated - simultaneously for + * both channels. + */ +static const int update_reg[] = { + WM8978_LEFT_DAC_DIGITAL_VOLUME, + WM8978_RIGHT_DAC_DIGITAL_VOLUME, + WM8978_LEFT_ADC_DIGITAL_VOLUME, + WM8978_RIGHT_ADC_DIGITAL_VOLUME, + WM8978_LEFT_INP_PGA_CONTROL, + WM8978_RIGHT_INP_PGA_CONTROL, + WM8978_LOUT1_HP_CONTROL, + WM8978_ROUT1_HP_CONTROL, + WM8978_LOUT2_SPK_CONTROL, + WM8978_ROUT2_SPK_CONTROL, +}; + +static __devinit int wm8978_register(struct wm8978_priv *wm8978) +{ + int ret, i; + struct snd_soc_codec *codec = &wm8978->codec; + + if (wm8978_codec) { + dev_err(codec->dev, "Another WM8978 is registered\n"); + return -EINVAL; + } + + /* + * Set default system clock to PLL, it is more precise, this is also the + * default hardware setting + */ + wm8978->sysclk = WM8978_PLL; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8978; + codec->name = "WM8978"; + codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8978_set_bias_level; + codec->dai = &wm8978_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8978_CACHEREGNUM; + codec->reg_cache = &wm8978->reg_cache; + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_I2C); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + memcpy(codec->reg_cache, wm8978_reg, sizeof(wm8978_reg)); + + /* + * Set the update bit in all registers, that have one. This way all + * writes to those registers will also cause the update bit to be + * written. + */ + for (i = 0; i < ARRAY_SIZE(update_reg); i++) + ((u16 *)codec->reg_cache)[update_reg[i]] |= 0x100; + + /* Reset the codec */ + ret = snd_soc_write(codec, WM8978_RESET, 0); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + goto err; + } + + wm8978_dai.dev = codec->dev; + + wm8978_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8978_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dai(&wm8978_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + goto err_codec; + } + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(wm8978); + return ret; +} + +static __devexit void wm8978_unregister(struct wm8978_priv *wm8978) +{ + wm8978_set_bias_level(&wm8978->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8978_dai); + snd_soc_unregister_codec(&wm8978->codec); + kfree(wm8978); + wm8978_codec = NULL; +} + +static __devinit int wm8978_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8978_priv *wm8978; + struct snd_soc_codec *codec; + + wm8978 = kzalloc(sizeof(struct wm8978_priv), GFP_KERNEL); + if (wm8978 == NULL) + return -ENOMEM; + + codec = &wm8978->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8978); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8978_register(wm8978); +} + +static __devexit int wm8978_i2c_remove(struct i2c_client *client) +{ + struct wm8978_priv *wm8978 = i2c_get_clientdata(client); + wm8978_unregister(wm8978); + return 0; +} + +static const struct i2c_device_id wm8978_i2c_id[] = { + { "wm8978", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id); + +static struct i2c_driver wm8978_i2c_driver = { + .driver = { + .name = "WM8978", + .owner = THIS_MODULE, + }, + .probe = wm8978_i2c_probe, + .remove = __devexit_p(wm8978_i2c_remove), + .id_table = wm8978_i2c_id, +}; + +static int __init wm8978_modinit(void) +{ + return i2c_add_driver(&wm8978_i2c_driver); +} +module_init(wm8978_modinit); + +static void __exit wm8978_exit(void) +{ + i2c_del_driver(&wm8978_i2c_driver); +} +module_exit(wm8978_exit); + +MODULE_DESCRIPTION("ASoC WM8978 codec driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8978.h b/sound/soc/codecs/wm8978.h new file mode 100644 index 000000000000..b58f0bf947e7 --- /dev/null +++ b/sound/soc/codecs/wm8978.h @@ -0,0 +1,89 @@ +/* + * wm8978.h -- codec driver for WM8978 + * + * Copyright 2009 Guennadi Liakhovetski + * + * 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 __WM8978_H__ +#define __WM8978_H__ + +/* + * Register values. + */ +#define WM8978_RESET 0x00 +#define WM8978_POWER_MANAGEMENT_1 0x01 +#define WM8978_POWER_MANAGEMENT_2 0x02 +#define WM8978_POWER_MANAGEMENT_3 0x03 +#define WM8978_AUDIO_INTERFACE 0x04 +#define WM8978_COMPANDING_CONTROL 0x05 +#define WM8978_CLOCKING 0x06 +#define WM8978_ADDITIONAL_CONTROL 0x07 +#define WM8978_GPIO_CONTROL 0x08 +#define WM8978_JACK_DETECT_CONTROL_1 0x09 +#define WM8978_DAC_CONTROL 0x0A +#define WM8978_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8978_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8978_JACK_DETECT_CONTROL_2 0x0D +#define WM8978_ADC_CONTROL 0x0E +#define WM8978_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8978_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8978_EQ1 0x12 +#define WM8978_EQ2 0x13 +#define WM8978_EQ3 0x14 +#define WM8978_EQ4 0x15 +#define WM8978_EQ5 0x16 +#define WM8978_DAC_LIMITER_1 0x18 +#define WM8978_DAC_LIMITER_2 0x19 +#define WM8978_NOTCH_FILTER_1 0x1b +#define WM8978_NOTCH_FILTER_2 0x1c +#define WM8978_NOTCH_FILTER_3 0x1d +#define WM8978_NOTCH_FILTER_4 0x1e +#define WM8978_ALC_CONTROL_1 0x20 +#define WM8978_ALC_CONTROL_2 0x21 +#define WM8978_ALC_CONTROL_3 0x22 +#define WM8978_NOISE_GATE 0x23 +#define WM8978_PLL_N 0x24 +#define WM8978_PLL_K1 0x25 +#define WM8978_PLL_K2 0x26 +#define WM8978_PLL_K3 0x27 +#define WM8978_3D_CONTROL 0x29 +#define WM8978_BEEP_CONTROL 0x2b +#define WM8978_INPUT_CONTROL 0x2c +#define WM8978_LEFT_INP_PGA_CONTROL 0x2d +#define WM8978_RIGHT_INP_PGA_CONTROL 0x2e +#define WM8978_LEFT_ADC_BOOST_CONTROL 0x2f +#define WM8978_RIGHT_ADC_BOOST_CONTROL 0x30 +#define WM8978_OUTPUT_CONTROL 0x31 +#define WM8978_LEFT_MIXER_CONTROL 0x32 +#define WM8978_RIGHT_MIXER_CONTROL 0x33 +#define WM8978_LOUT1_HP_CONTROL 0x34 +#define WM8978_ROUT1_HP_CONTROL 0x35 +#define WM8978_LOUT2_SPK_CONTROL 0x36 +#define WM8978_ROUT2_SPK_CONTROL 0x37 +#define WM8978_OUT3_MIXER_CONTROL 0x38 +#define WM8978_OUT4_MIXER_CONTROL 0x39 + +#define WM8978_CACHEREGNUM 58 + +/* Clock divider Id's */ +enum wm8978_clk_id { + WM8978_OPCLKRATE, + WM8978_MCLKDIV, + WM8978_ADCCLK, + WM8978_DACCLK, + WM8978_BCLKDIV, +}; + +enum wm8978_sysclk_src { + WM8978_PLL, + WM8978_MCLK +}; + +extern struct snd_soc_dai wm8978_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8978; + +#endif /* __WM8978_H__ */ -- cgit v1.2.3 From 9e6e96a197a03752d39a63e4f83e0b707ccedad7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 29 Jan 2010 17:47:12 +0000 Subject: ASoC: Add WM8994 CODEC driver The WM8994 is a highly integrated ultra-low power hi-fi audio subsystem designed for smartphones and other portable devices rich in multimedia features. It provides advanced digital mixing facilities enabling low power high quality interconnection of CPU, baseband and other audio sources through flexible digital and analogue routing, and integrates a class W headphone driver and stereo class D speaker drivers. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8994.c | 3870 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8994.h | 26 + 4 files changed, 3902 insertions(+) create mode 100644 sound/soc/codecs/wm8994.c create mode 100644 sound/soc/codecs/wm8994.h (limited to 'sound/soc/codecs/Kconfig') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0aad72fc1961..6b8a10120f9c 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -61,6 +61,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C select SND_SOC_WM8993 if I2C + select SND_SOC_WM8994 if I2C select SND_SOC_WM9081 if I2C select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS @@ -243,6 +244,9 @@ config SND_SOC_WM8990 config SND_SOC_WM8993 tristate +config SND_SOC_WM8994 + tristate + config SND_SOC_WM9081 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fbd290e41e9e..209dd6c7c254 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -48,6 +48,7 @@ snd-soc-wm8978-objs := wm8978.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o snd-soc-wm8993-objs := wm8993.o +snd-soc-wm8994-objs := wm8994.o snd-soc-wm9081-objs := wm9081.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o @@ -108,6 +109,7 @@ obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o +obj-$(CONFIG_SND_SOC_WM8994) += snd-soc-wm8994.o obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c new file mode 100644 index 000000000000..5dd4b299f69e --- /dev/null +++ b/sound/soc/codecs/wm8994.c @@ -0,0 +1,3870 @@ +/* + * wm8994.c -- WM8994 ALSA SoC Audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "wm8994.h" +#include "wm_hubs.h" + +static struct snd_soc_codec *wm8994_codec; +struct snd_soc_codec_device soc_codec_dev_wm8994; + +struct fll_config { + int src; + int in; + int out; +}; + +#define WM8994_NUM_DRC 3 +#define WM8994_NUM_EQ 3 + +static int wm8994_drc_base[] = { + WM8994_AIF1_DRC1_1, + WM8994_AIF1_DRC2_1, + WM8994_AIF2_DRC_1, +}; + +static int wm8994_retune_mobile_base[] = { + WM8994_AIF1_DAC1_EQ_GAINS_1, + WM8994_AIF1_DAC2_EQ_GAINS_1, + WM8994_AIF2_EQ_GAINS_1, +}; + +#define WM8994_REG_CACHE_SIZE 0x621 + +/* codec private data */ +struct wm8994_priv { + struct wm_hubs_data hubs; + struct snd_soc_codec codec; + u16 reg_cache[WM8994_REG_CACHE_SIZE + 1]; + int sysclk[2]; + int sysclk_rate[2]; + int mclk[2]; + int aifclk[2]; + struct fll_config fll[2], fll_suspend[2]; + + int dac_rates[2]; + int lrclk_shared[2]; + + /* Platform dependant DRC configuration */ + const char **drc_texts; + int drc_cfg[WM8994_NUM_DRC]; + struct soc_enum drc_enum; + + /* Platform dependant ReTune mobile configuration */ + int num_retune_mobile_texts; + const char **retune_mobile_texts; + int retune_mobile_cfg[WM8994_NUM_EQ]; + struct soc_enum retune_mobile_enum; + + struct wm8994_pdata *pdata; +}; + +static struct { + unsigned short readable; /* Mask of readable bits */ + unsigned short writable; /* Mask of writable bits */ + unsigned short vol; /* Mask of volatile bits */ +} access_masks[] = { + { 0xFFFF, 0xFFFF, 0x0000 }, /* R0 - Software Reset */ + { 0x3B37, 0x3B37, 0x0000 }, /* R1 - Power Management (1) */ + { 0x6BF0, 0x6BF0, 0x0000 }, /* R2 - Power Management (2) */ + { 0x3FF0, 0x3FF0, 0x0000 }, /* R3 - Power Management (3) */ + { 0x3F3F, 0x3F3F, 0x0000 }, /* R4 - Power Management (4) */ + { 0x3F0F, 0x3F0F, 0x0000 }, /* R5 - Power Management (5) */ + { 0x003F, 0x003F, 0x0000 }, /* R6 - Power Management (6) */ + { 0x0000, 0x0000, 0x0000 }, /* R7 */ + { 0x0000, 0x0000, 0x0000 }, /* R8 */ + { 0x0000, 0x0000, 0x0000 }, /* R9 */ + { 0x0000, 0x0000, 0x0000 }, /* R10 */ + { 0x0000, 0x0000, 0x0000 }, /* R11 */ + { 0x0000, 0x0000, 0x0000 }, /* R12 */ + { 0x0000, 0x0000, 0x0000 }, /* R13 */ + { 0x0000, 0x0000, 0x0000 }, /* R14 */ + { 0x0000, 0x0000, 0x0000 }, /* R15 */ + { 0x0000, 0x0000, 0x0000 }, /* R16 */ + { 0x0000, 0x0000, 0x0000 }, /* R17 */ + { 0x0000, 0x0000, 0x0000 }, /* R18 */ + { 0x0000, 0x0000, 0x0000 }, /* R19 */ + { 0x0000, 0x0000, 0x0000 }, /* R20 */ + { 0x01C0, 0x01C0, 0x0000 }, /* R21 - Input Mixer (1) */ + { 0x0000, 0x0000, 0x0000 }, /* R22 */ + { 0x0000, 0x0000, 0x0000 }, /* R23 */ + { 0x00DF, 0x01DF, 0x0000 }, /* R24 - Left Line Input 1&2 Volume */ + { 0x00DF, 0x01DF, 0x0000 }, /* R25 - Left Line Input 3&4 Volume */ + { 0x00DF, 0x01DF, 0x0000 }, /* R26 - Right Line Input 1&2 Volume */ + { 0x00DF, 0x01DF, 0x0000 }, /* R27 - Right Line Input 3&4 Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R28 - Left Output Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R29 - Right Output Volume */ + { 0x0077, 0x0077, 0x0000 }, /* R30 - Line Outputs Volume */ + { 0x0030, 0x0030, 0x0000 }, /* R31 - HPOUT2 Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R32 - Left OPGA Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R33 - Right OPGA Volume */ + { 0x007F, 0x007F, 0x0000 }, /* R34 - SPKMIXL Attenuation */ + { 0x017F, 0x017F, 0x0000 }, /* R35 - SPKMIXR Attenuation */ + { 0x003F, 0x003F, 0x0000 }, /* R36 - SPKOUT Mixers */ + { 0x003F, 0x003F, 0x0000 }, /* R37 - ClassD */ + { 0x00FF, 0x01FF, 0x0000 }, /* R38 - Speaker Volume Left */ + { 0x00FF, 0x01FF, 0x0000 }, /* R39 - Speaker Volume Right */ + { 0x00FF, 0x00FF, 0x0000 }, /* R40 - Input Mixer (2) */ + { 0x01B7, 0x01B7, 0x0000 }, /* R41 - Input Mixer (3) */ + { 0x01B7, 0x01B7, 0x0000 }, /* R42 - Input Mixer (4) */ + { 0x01C7, 0x01C7, 0x0000 }, /* R43 - Input Mixer (5) */ + { 0x01C7, 0x01C7, 0x0000 }, /* R44 - Input Mixer (6) */ + { 0x01FF, 0x01FF, 0x0000 }, /* R45 - Output Mixer (1) */ + { 0x01FF, 0x01FF, 0x0000 }, /* R46 - Output Mixer (2) */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R47 - Output Mixer (3) */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R48 - Output Mixer (4) */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R49 - Output Mixer (5) */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R50 - Output Mixer (6) */ + { 0x0038, 0x0038, 0x0000 }, /* R51 - HPOUT2 Mixer */ + { 0x0077, 0x0077, 0x0000 }, /* R52 - Line Mixer (1) */ + { 0x0077, 0x0077, 0x0000 }, /* R53 - Line Mixer (2) */ + { 0x03FF, 0x03FF, 0x0000 }, /* R54 - Speaker Mixer */ + { 0x00C1, 0x00C1, 0x0000 }, /* R55 - Additional Control */ + { 0x00F0, 0x00F0, 0x0000 }, /* R56 - AntiPOP (1) */ + { 0x01EF, 0x01EF, 0x0000 }, /* R57 - AntiPOP (2) */ + { 0x00FF, 0x00FF, 0x0000 }, /* R58 - MICBIAS */ + { 0x000F, 0x000F, 0x0000 }, /* R59 - LDO 1 */ + { 0x0007, 0x0007, 0x0000 }, /* R60 - LDO 2 */ + { 0x0000, 0x0000, 0x0000 }, /* R61 */ + { 0x0000, 0x0000, 0x0000 }, /* R62 */ + { 0x0000, 0x0000, 0x0000 }, /* R63 */ + { 0x0000, 0x0000, 0x0000 }, /* R64 */ + { 0x0000, 0x0000, 0x0000 }, /* R65 */ + { 0x0000, 0x0000, 0x0000 }, /* R66 */ + { 0x0000, 0x0000, 0x0000 }, /* R67 */ + { 0x0000, 0x0000, 0x0000 }, /* R68 */ + { 0x0000, 0x0000, 0x0000 }, /* R69 */ + { 0x0000, 0x0000, 0x0000 }, /* R70 */ + { 0x0000, 0x0000, 0x0000 }, /* R71 */ + { 0x0000, 0x0000, 0x0000 }, /* R72 */ + { 0x0000, 0x0000, 0x0000 }, /* R73 */ + { 0x0000, 0x0000, 0x0000 }, /* R74 */ + { 0x0000, 0x0000, 0x0000 }, /* R75 */ + { 0x8000, 0x8000, 0x0000 }, /* R76 - Charge Pump (1) */ + { 0x0000, 0x0000, 0x0000 }, /* R77 */ + { 0x0000, 0x0000, 0x0000 }, /* R78 */ + { 0x0000, 0x0000, 0x0000 }, /* R79 */ + { 0x0000, 0x0000, 0x0000 }, /* R80 */ + { 0x0301, 0x0301, 0x0000 }, /* R81 - Class W (1) */ + { 0x0000, 0x0000, 0x0000 }, /* R82 */ + { 0x0000, 0x0000, 0x0000 }, /* R83 */ + { 0x333F, 0x333F, 0x0000 }, /* R84 - DC Servo (1) */ + { 0x0FEF, 0x0FEF, 0x0000 }, /* R85 - DC Servo (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R86 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R87 - DC Servo (4) */ + { 0x0333, 0x0000, 0x0000 }, /* R88 - DC Servo Readback */ + { 0x0000, 0x0000, 0x0000 }, /* R89 */ + { 0x0000, 0x0000, 0x0000 }, /* R90 */ + { 0x0000, 0x0000, 0x0000 }, /* R91 */ + { 0x0000, 0x0000, 0x0000 }, /* R92 */ + { 0x0000, 0x0000, 0x0000 }, /* R93 */ + { 0x0000, 0x0000, 0x0000 }, /* R94 */ + { 0x0000, 0x0000, 0x0000 }, /* R95 */ + { 0x00EE, 0x00EE, 0x0000 }, /* R96 - Analogue HP (1) */ + { 0x0000, 0x0000, 0x0000 }, /* R97 */ + { 0x0000, 0x0000, 0x0000 }, /* R98 */ + { 0x0000, 0x0000, 0x0000 }, /* R99 */ + { 0x0000, 0x0000, 0x0000 }, /* R100 */ + { 0x0000, 0x0000, 0x0000 }, /* R101 */ + { 0x0000, 0x0000, 0x0000 }, /* R102 */ + { 0x0000, 0x0000, 0x0000 }, /* R103 */ + { 0x0000, 0x0000, 0x0000 }, /* R104 */ + { 0x0000, 0x0000, 0x0000 }, /* R105 */ + { 0x0000, 0x0000, 0x0000 }, /* R106 */ + { 0x0000, 0x0000, 0x0000 }, /* R107 */ + { 0x0000, 0x0000, 0x0000 }, /* R108 */ + { 0x0000, 0x0000, 0x0000 }, /* R109 */ + { 0x0000, 0x0000, 0x0000 }, /* R110 */ + { 0x0000, 0x0000, 0x0000 }, /* R111 */ + { 0x0000, 0x0000, 0x0000 }, /* R112 */ + { 0x0000, 0x0000, 0x0000 }, /* R113 */ + { 0x0000, 0x0000, 0x0000 }, /* R114 */ + { 0x0000, 0x0000, 0x0000 }, /* R115 */ + { 0x0000, 0x0000, 0x0000 }, /* R116 */ + { 0x0000, 0x0000, 0x0000 }, /* R117 */ + { 0x0000, 0x0000, 0x0000 }, /* R118 */ + { 0x0000, 0x0000, 0x0000 }, /* R119 */ + { 0x0000, 0x0000, 0x0000 }, /* R120 */ + { 0x0000, 0x0000, 0x0000 }, /* R121 */ + { 0x0000, 0x0000, 0x0000 }, /* R122 */ + { 0x0000, 0x0000, 0x0000 }, /* R123 */ + { 0x0000, 0x0000, 0x0000 }, /* R124 */ + { 0x0000, 0x0000, 0x0000 }, /* R125 */ + { 0x0000, 0x0000, 0x0000 }, /* R126 */ + { 0x0000, 0x0000, 0x0000 }, /* R127 */ + { 0x0000, 0x0000, 0x0000 }, /* R128 */ + { 0x0000, 0x0000, 0x0000 }, /* R129 */ + { 0x0000, 0x0000, 0x0000 }, /* R130 */ + { 0x0000, 0x0000, 0x0000 }, /* R131 */ + { 0x0000, 0x0000, 0x0000 }, /* R132 */ + { 0x0000, 0x0000, 0x0000 }, /* R133 */ + { 0x0000, 0x0000, 0x0000 }, /* R134 */ + { 0x0000, 0x0000, 0x0000 }, /* R135 */ + { 0x0000, 0x0000, 0x0000 }, /* R136 */ + { 0x0000, 0x0000, 0x0000 }, /* R137 */ + { 0x0000, 0x0000, 0x0000 }, /* R138 */ + { 0x0000, 0x0000, 0x0000 }, /* R139 */ + { 0x0000, 0x0000, 0x0000 }, /* R140 */ + { 0x0000, 0x0000, 0x0000 }, /* R141 */ + { 0x0000, 0x0000, 0x0000 }, /* R142 */ + { 0x0000, 0x0000, 0x0000 }, /* R143 */ + { 0x0000, 0x0000, 0x0000 }, /* R144 */ + { 0x0000, 0x0000, 0x0000 }, /* R145 */ + { 0x0000, 0x0000, 0x0000 }, /* R146 */ + { 0x0000, 0x0000, 0x0000 }, /* R147 */ + { 0x0000, 0x0000, 0x0000 }, /* R148 */ + { 0x0000, 0x0000, 0x0000 }, /* R149 */ + { 0x0000, 0x0000, 0x0000 }, /* R150 */ + { 0x0000, 0x0000, 0x0000 }, /* R151 */ + { 0x0000, 0x0000, 0x0000 }, /* R152 */ + { 0x0000, 0x0000, 0x0000 }, /* R153 */ + { 0x0000, 0x0000, 0x0000 }, /* R154 */ + { 0x0000, 0x0000, 0x0000 }, /* R155 */ + { 0x0000, 0x0000, 0x0000 }, /* R156 */ + { 0x0000, 0x0000, 0x0000 }, /* R157 */ + { 0x0000, 0x0000, 0x0000 }, /* R158 */ + { 0x0000, 0x0000, 0x0000 }, /* R159 */ + { 0x0000, 0x0000, 0x0000 }, /* R160 */ + { 0x0000, 0x0000, 0x0000 }, /* R161 */ + { 0x0000, 0x0000, 0x0000 }, /* R162 */ + { 0x0000, 0x0000, 0x0000 }, /* R163 */ + { 0x0000, 0x0000, 0x0000 }, /* R164 */ + { 0x0000, 0x0000, 0x0000 }, /* R165 */ + { 0x0000, 0x0000, 0x0000 }, /* R166 */ + { 0x0000, 0x0000, 0x0000 }, /* R167 */ + { 0x0000, 0x0000, 0x0000 }, /* R168 */ + { 0x0000, 0x0000, 0x0000 }, /* R169 */ + { 0x0000, 0x0000, 0x0000 }, /* R170 */ + { 0x0000, 0x0000, 0x0000 }, /* R171 */ + { 0x0000, 0x0000, 0x0000 }, /* R172 */ + { 0x0000, 0x0000, 0x0000 }, /* R173 */ + { 0x0000, 0x0000, 0x0000 }, /* R174 */ + { 0x0000, 0x0000, 0x0000 }, /* R175 */ + { 0x0000, 0x0000, 0x0000 }, /* R176 */ + { 0x0000, 0x0000, 0x0000 }, /* R177 */ + { 0x0000, 0x0000, 0x0000 }, /* R178 */ + { 0x0000, 0x0000, 0x0000 }, /* R179 */ + { 0x0000, 0x0000, 0x0000 }, /* R180 */ + { 0x0000, 0x0000, 0x0000 }, /* R181 */ + { 0x0000, 0x0000, 0x0000 }, /* R182 */ + { 0x0000, 0x0000, 0x0000 }, /* R183 */ + { 0x0000, 0x0000, 0x0000 }, /* R184 */ + { 0x0000, 0x0000, 0x0000 }, /* R185 */ + { 0x0000, 0x0000, 0x0000 }, /* R186 */ + { 0x0000, 0x0000, 0x0000 }, /* R187 */ + { 0x0000, 0x0000, 0x0000 }, /* R188 */ + { 0x0000, 0x0000, 0x0000 }, /* R189 */ + { 0x0000, 0x0000, 0x0000 }, /* R190 */ + { 0x0000, 0x0000, 0x0000 }, /* R191 */ + { 0x0000, 0x0000, 0x0000 }, /* R192 */ + { 0x0000, 0x0000, 0x0000 }, /* R193 */ + { 0x0000, 0x0000, 0x0000 }, /* R194 */ + { 0x0000, 0x0000, 0x0000 }, /* R195 */ + { 0x0000, 0x0000, 0x0000 }, /* R196 */ + { 0x0000, 0x0000, 0x0000 }, /* R197 */ + { 0x0000, 0x0000, 0x0000 }, /* R198 */ + { 0x0000, 0x0000, 0x0000 }, /* R199 */ + { 0x0000, 0x0000, 0x0000 }, /* R200 */ + { 0x0000, 0x0000, 0x0000 }, /* R201 */ + { 0x0000, 0x0000, 0x0000 }, /* R202 */ + { 0x0000, 0x0000, 0x0000 }, /* R203 */ + { 0x0000, 0x0000, 0x0000 }, /* R204 */ + { 0x0000, 0x0000, 0x0000 }, /* R205 */ + { 0x0000, 0x0000, 0x0000 }, /* R206 */ + { 0x0000, 0x0000, 0x0000 }, /* R207 */ + { 0x0000, 0x0000, 0x0000 }, /* R208 */ + { 0x0000, 0x0000, 0x0000 }, /* R209 */ + { 0x0000, 0x0000, 0x0000 }, /* R210 */ + { 0x0000, 0x0000, 0x0000 }, /* R211 */ + { 0x0000, 0x0000, 0x0000 }, /* R212 */ + { 0x0000, 0x0000, 0x0000 }, /* R213 */ + { 0x0000, 0x0000, 0x0000 }, /* R214 */ + { 0x0000, 0x0000, 0x0000 }, /* R215 */ + { 0x0000, 0x0000, 0x0000 }, /* R216 */ + { 0x0000, 0x0000, 0x0000 }, /* R217 */ + { 0x0000, 0x0000, 0x0000 }, /* R218 */ + { 0x0000, 0x0000, 0x0000 }, /* R219 */ + { 0x0000, 0x0000, 0x0000 }, /* R220 */ + { 0x0000, 0x0000, 0x0000 }, /* R221 */ + { 0x0000, 0x0000, 0x0000 }, /* R222 */ + { 0x0000, 0x0000, 0x0000 }, /* R223 */ + { 0x0000, 0x0000, 0x0000 }, /* R224 */ + { 0x0000, 0x0000, 0x0000 }, /* R225 */ + { 0x0000, 0x0000, 0x0000 }, /* R226 */ + { 0x0000, 0x0000, 0x0000 }, /* R227 */ + { 0x0000, 0x0000, 0x0000 }, /* R228 */ + { 0x0000, 0x0000, 0x0000 }, /* R229 */ + { 0x0000, 0x0000, 0x0000 }, /* R230 */ + { 0x0000, 0x0000, 0x0000 }, /* R231 */ + { 0x0000, 0x0000, 0x0000 }, /* R232 */ + { 0x0000, 0x0000, 0x0000 }, /* R233 */ + { 0x0000, 0x0000, 0x0000 }, /* R234 */ + { 0x0000, 0x0000, 0x0000 }, /* R235 */ + { 0x0000, 0x0000, 0x0000 }, /* R236 */ + { 0x0000, 0x0000, 0x0000 }, /* R237 */ + { 0x0000, 0x0000, 0x0000 }, /* R238 */ + { 0x0000, 0x0000, 0x0000 }, /* R239 */ + { 0x0000, 0x0000, 0x0000 }, /* R240 */ + { 0x0000, 0x0000, 0x0000 }, /* R241 */ + { 0x0000, 0x0000, 0x0000 }, /* R242 */ + { 0x0000, 0x0000, 0x0000 }, /* R243 */ + { 0x0000, 0x0000, 0x0000 }, /* R244 */ + { 0x0000, 0x0000, 0x0000 }, /* R245 */ + { 0x0000, 0x0000, 0x0000 }, /* R246 */ + { 0x0000, 0x0000, 0x0000 }, /* R247 */ + { 0x0000, 0x0000, 0x0000 }, /* R248 */ + { 0x0000, 0x0000, 0x0000 }, /* R249 */ + { 0x0000, 0x0000, 0x0000 }, /* R250 */ + { 0x0000, 0x0000, 0x0000 }, /* R251 */ + { 0x0000, 0x0000, 0x0000 }, /* R252 */ + { 0x0000, 0x0000, 0x0000 }, /* R253 */ + { 0x0000, 0x0000, 0x0000 }, /* R254 */ + { 0x0000, 0x0000, 0x0000 }, /* R255 */ + { 0x000F, 0x0000, 0x0000 }, /* R256 - Chip Revision */ + { 0x0074, 0x0074, 0x0000 }, /* R257 - Control Interface */ + { 0x0000, 0x0000, 0x0000 }, /* R258 */ + { 0x0000, 0x0000, 0x0000 }, /* R259 */ + { 0x0000, 0x0000, 0x0000 }, /* R260 */ + { 0x0000, 0x0000, 0x0000 }, /* R261 */ + { 0x0000, 0x0000, 0x0000 }, /* R262 */ + { 0x0000, 0x0000, 0x0000 }, /* R263 */ + { 0x0000, 0x0000, 0x0000 }, /* R264 */ + { 0x0000, 0x0000, 0x0000 }, /* R265 */ + { 0x0000, 0x0000, 0x0000 }, /* R266 */ + { 0x0000, 0x0000, 0x0000 }, /* R267 */ + { 0x0000, 0x0000, 0x0000 }, /* R268 */ + { 0x0000, 0x0000, 0x0000 }, /* R269 */ + { 0x0000, 0x0000, 0x0000 }, /* R270 */ + { 0x0000, 0x0000, 0x0000 }, /* R271 */ + { 0x807F, 0x837F, 0x0000 }, /* R272 - Write Sequencer Ctrl (1) */ + { 0x017F, 0x0000, 0x0000 }, /* R273 - Write Sequencer Ctrl (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R274 */ + { 0x0000, 0x0000, 0x0000 }, /* R275 */ + { 0x0000, 0x0000, 0x0000 }, /* R276 */ + { 0x0000, 0x0000, 0x0000 }, /* R277 */ + { 0x0000, 0x0000, 0x0000 }, /* R278 */ + { 0x0000, 0x0000, 0x0000 }, /* R279 */ + { 0x0000, 0x0000, 0x0000 }, /* R280 */ + { 0x0000, 0x0000, 0x0000 }, /* R281 */ + { 0x0000, 0x0000, 0x0000 }, /* R282 */ + { 0x0000, 0x0000, 0x0000 }, /* R283 */ + { 0x0000, 0x0000, 0x0000 }, /* R284 */ + { 0x0000, 0x0000, 0x0000 }, /* R285 */ + { 0x0000, 0x0000, 0x0000 }, /* R286 */ + { 0x0000, 0x0000, 0x0000 }, /* R287 */ + { 0x0000, 0x0000, 0x0000 }, /* R288 */ + { 0x0000, 0x0000, 0x0000 }, /* R289 */ + { 0x0000, 0x0000, 0x0000 }, /* R290 */ + { 0x0000, 0x0000, 0x0000 }, /* R291 */ + { 0x0000, 0x0000, 0x0000 }, /* R292 */ + { 0x0000, 0x0000, 0x0000 }, /* R293 */ + { 0x0000, 0x0000, 0x0000 }, /* R294 */ + { 0x0000, 0x0000, 0x0000 }, /* R295 */ + { 0x0000, 0x0000, 0x0000 }, /* R296 */ + { 0x0000, 0x0000, 0x0000 }, /* R297 */ + { 0x0000, 0x0000, 0x0000 }, /* R298 */ + { 0x0000, 0x0000, 0x0000 }, /* R299 */ + { 0x0000, 0x0000, 0x0000 }, /* R300 */ + { 0x0000, 0x0000, 0x0000 }, /* R301 */ + { 0x0000, 0x0000, 0x0000 }, /* R302 */ + { 0x0000, 0x0000, 0x0000 }, /* R303 */ + { 0x0000, 0x0000, 0x0000 }, /* R304 */ + { 0x0000, 0x0000, 0x0000 }, /* R305 */ + { 0x0000, 0x0000, 0x0000 }, /* R306 */ + { 0x0000, 0x0000, 0x0000 }, /* R307 */ + { 0x0000, 0x0000, 0x0000 }, /* R308 */ + { 0x0000, 0x0000, 0x0000 }, /* R309 */ + { 0x0000, 0x0000, 0x0000 }, /* R310 */ + { 0x0000, 0x0000, 0x0000 }, /* R311 */ + { 0x0000, 0x0000, 0x0000 }, /* R312 */ + { 0x0000, 0x0000, 0x0000 }, /* R313 */ + { 0x0000, 0x0000, 0x0000 }, /* R314 */ + { 0x0000, 0x0000, 0x0000 }, /* R315 */ + { 0x0000, 0x0000, 0x0000 }, /* R316 */ + { 0x0000, 0x0000, 0x0000 }, /* R317 */ + { 0x0000, 0x0000, 0x0000 }, /* R318 */ + { 0x0000, 0x0000, 0x0000 }, /* R319 */ + { 0x0000, 0x0000, 0x0000 }, /* R320 */ + { 0x0000, 0x0000, 0x0000 }, /* R321 */ + { 0x0000, 0x0000, 0x0000 }, /* R322 */ + { 0x0000, 0x0000, 0x0000 }, /* R323 */ + { 0x0000, 0x0000, 0x0000 }, /* R324 */ + { 0x0000, 0x0000, 0x0000 }, /* R325 */ + { 0x0000, 0x0000, 0x0000 }, /* R326 */ + { 0x0000, 0x0000, 0x0000 }, /* R327 */ + { 0x0000, 0x0000, 0x0000 }, /* R328 */ + { 0x0000, 0x0000, 0x0000 }, /* R329 */ + { 0x0000, 0x0000, 0x0000 }, /* R330 */ + { 0x0000, 0x0000, 0x0000 }, /* R331 */ + { 0x0000, 0x0000, 0x0000 }, /* R332 */ + { 0x0000, 0x0000, 0x0000 }, /* R333 */ + { 0x0000, 0x0000, 0x0000 }, /* R334 */ + { 0x0000, 0x0000, 0x0000 }, /* R335 */ + { 0x0000, 0x0000, 0x0000 }, /* R336 */ + { 0x0000, 0x0000, 0x0000 }, /* R337 */ + { 0x0000, 0x0000, 0x0000 }, /* R338 */ + { 0x0000, 0x0000, 0x0000 }, /* R339 */ + { 0x0000, 0x0000, 0x0000 }, /* R340 */ + { 0x0000, 0x0000, 0x0000 }, /* R341 */ + { 0x0000, 0x0000, 0x0000 }, /* R342 */ + { 0x0000, 0x0000, 0x0000 }, /* R343 */ + { 0x0000, 0x0000, 0x0000 }, /* R344 */ + { 0x0000, 0x0000, 0x0000 }, /* R345 */ + { 0x0000, 0x0000, 0x0000 }, /* R346 */ + { 0x0000, 0x0000, 0x0000 }, /* R347 */ + { 0x0000, 0x0000, 0x0000 }, /* R348 */ + { 0x0000, 0x0000, 0x0000 }, /* R349 */ + { 0x0000, 0x0000, 0x0000 }, /* R350 */ + { 0x0000, 0x0000, 0x0000 }, /* R351 */ + { 0x0000, 0x0000, 0x0000 }, /* R352 */ + { 0x0000, 0x0000, 0x0000 }, /* R353 */ + { 0x0000, 0x0000, 0x0000 }, /* R354 */ + { 0x0000, 0x0000, 0x0000 }, /* R355 */ + { 0x0000, 0x0000, 0x0000 }, /* R356 */ + { 0x0000, 0x0000, 0x0000 }, /* R357 */ + { 0x0000, 0x0000, 0x0000 }, /* R358 */ + { 0x0000, 0x0000, 0x0000 }, /* R359 */ + { 0x0000, 0x0000, 0x0000 }, /* R360 */ + { 0x0000, 0x0000, 0x0000 }, /* R361 */ + { 0x0000, 0x0000, 0x0000 }, /* R362 */ + { 0x0000, 0x0000, 0x0000 }, /* R363 */ + { 0x0000, 0x0000, 0x0000 }, /* R364 */ + { 0x0000, 0x0000, 0x0000 }, /* R365 */ + { 0x0000, 0x0000, 0x0000 }, /* R366 */ + { 0x0000, 0x0000, 0x0000 }, /* R367 */ + { 0x0000, 0x0000, 0x0000 }, /* R368 */ + { 0x0000, 0x0000, 0x0000 }, /* R369 */ + { 0x0000, 0x0000, 0x0000 }, /* R370 */ + { 0x0000, 0x0000, 0x0000 }, /* R371 */ + { 0x0000, 0x0000, 0x0000 }, /* R372 */ + { 0x0000, 0x0000, 0x0000 }, /* R373 */ + { 0x0000, 0x0000, 0x0000 }, /* R374 */ + { 0x0000, 0x0000, 0x0000 }, /* R375 */ + { 0x0000, 0x0000, 0x0000 }, /* R376 */ + { 0x0000, 0x0000, 0x0000 }, /* R377 */ + { 0x0000, 0x0000, 0x0000 }, /* R378 */ + { 0x0000, 0x0000, 0x0000 }, /* R379 */ + { 0x0000, 0x0000, 0x0000 }, /* R380 */ + { 0x0000, 0x0000, 0x0000 }, /* R381 */ + { 0x0000, 0x0000, 0x0000 }, /* R382 */ + { 0x0000, 0x0000, 0x0000 }, /* R383 */ + { 0x0000, 0x0000, 0x0000 }, /* R384 */ + { 0x0000, 0x0000, 0x0000 }, /* R385 */ + { 0x0000, 0x0000, 0x0000 }, /* R386 */ + { 0x0000, 0x0000, 0x0000 }, /* R387 */ + { 0x0000, 0x0000, 0x0000 }, /* R388 */ + { 0x0000, 0x0000, 0x0000 }, /* R389 */ + { 0x0000, 0x0000, 0x0000 }, /* R390 */ + { 0x0000, 0x0000, 0x0000 }, /* R391 */ + { 0x0000, 0x0000, 0x0000 }, /* R392 */ + { 0x0000, 0x0000, 0x0000 }, /* R393 */ + { 0x0000, 0x0000, 0x0000 }, /* R394 */ + { 0x0000, 0x0000, 0x0000 }, /* R395 */ + { 0x0000, 0x0000, 0x0000 }, /* R396 */ + { 0x0000, 0x0000, 0x0000 }, /* R397 */ + { 0x0000, 0x0000, 0x0000 }, /* R398 */ + { 0x0000, 0x0000, 0x0000 }, /* R399 */ + { 0x0000, 0x0000, 0x0000 }, /* R400 */ + { 0x0000, 0x0000, 0x0000 }, /* R401 */ + { 0x0000, 0x0000, 0x0000 }, /* R402 */ + { 0x0000, 0x0000, 0x0000 }, /* R403 */ + { 0x0000, 0x0000, 0x0000 }, /* R404 */ + { 0x0000, 0x0000, 0x0000 }, /* R405 */ + { 0x0000, 0x0000, 0x0000 }, /* R406 */ + { 0x0000, 0x0000, 0x0000 }, /* R407 */ + { 0x0000, 0x0000, 0x0000 }, /* R408 */ + { 0x0000, 0x0000, 0x0000 }, /* R409 */ + { 0x0000, 0x0000, 0x0000 }, /* R410 */ + { 0x0000, 0x0000, 0x0000 }, /* R411 */ + { 0x0000, 0x0000, 0x0000 }, /* R412 */ + { 0x0000, 0x0000, 0x0000 }, /* R413 */ + { 0x0000, 0x0000, 0x0000 }, /* R414 */ + { 0x0000, 0x0000, 0x0000 }, /* R415 */ + { 0x0000, 0x0000, 0x0000 }, /* R416 */ + { 0x0000, 0x0000, 0x0000 }, /* R417 */ + { 0x0000, 0x0000, 0x0000 }, /* R418 */ + { 0x0000, 0x0000, 0x0000 }, /* R419 */ + { 0x0000, 0x0000, 0x0000 }, /* R420 */ + { 0x0000, 0x0000, 0x0000 }, /* R421 */ + { 0x0000, 0x0000, 0x0000 }, /* R422 */ + { 0x0000, 0x0000, 0x0000 }, /* R423 */ + { 0x0000, 0x0000, 0x0000 }, /* R424 */ + { 0x0000, 0x0000, 0x0000 }, /* R425 */ + { 0x0000, 0x0000, 0x0000 }, /* R426 */ + { 0x0000, 0x0000, 0x0000 }, /* R427 */ + { 0x0000, 0x0000, 0x0000 }, /* R428 */ + { 0x0000, 0x0000, 0x0000 }, /* R429 */ + { 0x0000, 0x0000, 0x0000 }, /* R430 */ + { 0x0000, 0x0000, 0x0000 }, /* R431 */ + { 0x0000, 0x0000, 0x0000 }, /* R432 */ + { 0x0000, 0x0000, 0x0000 }, /* R433 */ + { 0x0000, 0x0000, 0x0000 }, /* R434 */ + { 0x0000, 0x0000, 0x0000 }, /* R435 */ + { 0x0000, 0x0000, 0x0000 }, /* R436 */ + { 0x0000, 0x0000, 0x0000 }, /* R437 */ + { 0x0000, 0x0000, 0x0000 }, /* R438 */ + { 0x0000, 0x0000, 0x0000 }, /* R439 */ + { 0x0000, 0x0000, 0x0000 }, /* R440 */ + { 0x0000, 0x0000, 0x0000 }, /* R441 */ + { 0x0000, 0x0000, 0x0000 }, /* R442 */ + { 0x0000, 0x0000, 0x0000 }, /* R443 */ + { 0x0000, 0x0000, 0x0000 }, /* R444 */ + { 0x0000, 0x0000, 0x0000 }, /* R445 */ + { 0x0000, 0x0000, 0x0000 }, /* R446 */ + { 0x0000, 0x0000, 0x0000 }, /* R447 */ + { 0x0000, 0x0000, 0x0000 }, /* R448 */ + { 0x0000, 0x0000, 0x0000 }, /* R449 */ + { 0x0000, 0x0000, 0x0000 }, /* R450 */ + { 0x0000, 0x0000, 0x0000 }, /* R451 */ + { 0x0000, 0x0000, 0x0000 }, /* R452 */ + { 0x0000, 0x0000, 0x0000 }, /* R453 */ + { 0x0000, 0x0000, 0x0000 }, /* R454 */ + { 0x0000, 0x0000, 0x0000 }, /* R455 */ + { 0x0000, 0x0000, 0x0000 }, /* R456 */ + { 0x0000, 0x0000, 0x0000 }, /* R457 */ + { 0x0000, 0x0000, 0x0000 }, /* R458 */ + { 0x0000, 0x0000, 0x0000 }, /* R459 */ + { 0x0000, 0x0000, 0x0000 }, /* R460 */ + { 0x0000, 0x0000, 0x0000 }, /* R461 */ + { 0x0000, 0x0000, 0x0000 }, /* R462 */ + { 0x0000, 0x0000, 0x0000 }, /* R463 */ + { 0x0000, 0x0000, 0x0000 }, /* R464 */ + { 0x0000, 0x0000, 0x0000 }, /* R465 */ + { 0x0000, 0x0000, 0x0000 }, /* R466 */ + { 0x0000, 0x0000, 0x0000 }, /* R467 */ + { 0x0000, 0x0000, 0x0000 }, /* R468 */ + { 0x0000, 0x0000, 0x0000 }, /* R469 */ + { 0x0000, 0x0000, 0x0000 }, /* R470 */ + { 0x0000, 0x0000, 0x0000 }, /* R471 */ + { 0x0000, 0x0000, 0x0000 }, /* R472 */ + { 0x0000, 0x0000, 0x0000 }, /* R473 */ + { 0x0000, 0x0000, 0x0000 }, /* R474 */ + { 0x0000, 0x0000, 0x0000 }, /* R475 */ + { 0x0000, 0x0000, 0x0000 }, /* R476 */ + { 0x0000, 0x0000, 0x0000 }, /* R477 */ + { 0x0000, 0x0000, 0x0000 }, /* R478 */ + { 0x0000, 0x0000, 0x0000 }, /* R479 */ + { 0x0000, 0x0000, 0x0000 }, /* R480 */ + { 0x0000, 0x0000, 0x0000 }, /* R481 */ + { 0x0000, 0x0000, 0x0000 }, /* R482 */ + { 0x0000, 0x0000, 0x0000 }, /* R483 */ + { 0x0000, 0x0000, 0x0000 }, /* R484 */ + { 0x0000, 0x0000, 0x0000 }, /* R485 */ + { 0x0000, 0x0000, 0x0000 }, /* R486 */ + { 0x0000, 0x0000, 0x0000 }, /* R487 */ + { 0x0000, 0x0000, 0x0000 }, /* R488 */ + { 0x0000, 0x0000, 0x0000 }, /* R489 */ + { 0x0000, 0x0000, 0x0000 }, /* R490 */ + { 0x0000, 0x0000, 0x0000 }, /* R491 */ + { 0x0000, 0x0000, 0x0000 }, /* R492 */ + { 0x0000, 0x0000, 0x0000 }, /* R493 */ + { 0x0000, 0x0000, 0x0000 }, /* R494 */ + { 0x0000, 0x0000, 0x0000 }, /* R495 */ + { 0x0000, 0x0000, 0x0000 }, /* R496 */ + { 0x0000, 0x0000, 0x0000 }, /* R497 */ + { 0x0000, 0x0000, 0x0000 }, /* R498 */ + { 0x0000, 0x0000, 0x0000 }, /* R499 */ + { 0x0000, 0x0000, 0x0000 }, /* R500 */ + { 0x0000, 0x0000, 0x0000 }, /* R501 */ + { 0x0000, 0x0000, 0x0000 }, /* R502 */ + { 0x0000, 0x0000, 0x0000 }, /* R503 */ + { 0x0000, 0x0000, 0x0000 }, /* R504 */ + { 0x0000, 0x0000, 0x0000 }, /* R505 */ + { 0x0000, 0x0000, 0x0000 }, /* R506 */ + { 0x0000, 0x0000, 0x0000 }, /* R507 */ + { 0x0000, 0x0000, 0x0000 }, /* R508 */ + { 0x0000, 0x0000, 0x0000 }, /* R509 */ + { 0x0000, 0x0000, 0x0000 }, /* R510 */ + { 0x0000, 0x0000, 0x0000 }, /* R511 */ + { 0x001F, 0x001F, 0x0000 }, /* R512 - AIF1 Clocking (1) */ + { 0x003F, 0x003F, 0x0000 }, /* R513 - AIF1 Clocking (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R514 */ + { 0x0000, 0x0000, 0x0000 }, /* R515 */ + { 0x001F, 0x001F, 0x0000 }, /* R516 - AIF2 Clocking (1) */ + { 0x003F, 0x003F, 0x0000 }, /* R517 - AIF2 Clocking (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R518 */ + { 0x0000, 0x0000, 0x0000 }, /* R519 */ + { 0x001F, 0x001F, 0x0000 }, /* R520 - Clocking (1) */ + { 0x0777, 0x0777, 0x0000 }, /* R521 - Clocking (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R522 */ + { 0x0000, 0x0000, 0x0000 }, /* R523 */ + { 0x0000, 0x0000, 0x0000 }, /* R524 */ + { 0x0000, 0x0000, 0x0000 }, /* R525 */ + { 0x0000, 0x0000, 0x0000 }, /* R526 */ + { 0x0000, 0x0000, 0x0000 }, /* R527 */ + { 0x00FF, 0x00FF, 0x0000 }, /* R528 - AIF1 Rate */ + { 0x00FF, 0x00FF, 0x0000 }, /* R529 - AIF2 Rate */ + { 0x000F, 0x0000, 0x0000 }, /* R530 - Rate Status */ + { 0x0000, 0x0000, 0x0000 }, /* R531 */ + { 0x0000, 0x0000, 0x0000 }, /* R532 */ + { 0x0000, 0x0000, 0x0000 }, /* R533 */ + { 0x0000, 0x0000, 0x0000 }, /* R534 */ + { 0x0000, 0x0000, 0x0000 }, /* R535 */ + { 0x0000, 0x0000, 0x0000 }, /* R536 */ + { 0x0000, 0x0000, 0x0000 }, /* R537 */ + { 0x0000, 0x0000, 0x0000 }, /* R538 */ + { 0x0000, 0x0000, 0x0000 }, /* R539 */ + { 0x0000, 0x0000, 0x0000 }, /* R540 */ + { 0x0000, 0x0000, 0x0000 }, /* R541 */ + { 0x0000, 0x0000, 0x0000 }, /* R542 */ + { 0x0000, 0x0000, 0x0000 }, /* R543 */ + { 0x0007, 0x0007, 0x0000 }, /* R544 - FLL1 Control (1) */ + { 0x3F77, 0x3F77, 0x0000 }, /* R545 - FLL1 Control (2) */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R546 - FLL1 Control (3) */ + { 0x7FEF, 0x7FEF, 0x0000 }, /* R547 - FLL1 Control (4) */ + { 0x1FDB, 0x1FDB, 0x0000 }, /* R548 - FLL1 Control (5) */ + { 0x0000, 0x0000, 0x0000 }, /* R549 */ + { 0x0000, 0x0000, 0x0000 }, /* R550 */ + { 0x0000, 0x0000, 0x0000 }, /* R551 */ + { 0x0000, 0x0000, 0x0000 }, /* R552 */ + { 0x0000, 0x0000, 0x0000 }, /* R553 */ + { 0x0000, 0x0000, 0x0000 }, /* R554 */ + { 0x0000, 0x0000, 0x0000 }, /* R555 */ + { 0x0000, 0x0000, 0x0000 }, /* R556 */ + { 0x0000, 0x0000, 0x0000 }, /* R557 */ + { 0x0000, 0x0000, 0x0000 }, /* R558 */ + { 0x0000, 0x0000, 0x0000 }, /* R559 */ + { 0x0000, 0x0000, 0x0000 }, /* R560 */ + { 0x0000, 0x0000, 0x0000 }, /* R561 */ + { 0x0000, 0x0000, 0x0000 }, /* R562 */ + { 0x0000, 0x0000, 0x0000 }, /* R563 */ + { 0x0000, 0x0000, 0x0000 }, /* R564 */ + { 0x0000, 0x0000, 0x0000 }, /* R565 */ + { 0x0000, 0x0000, 0x0000 }, /* R566 */ + { 0x0000, 0x0000, 0x0000 }, /* R567 */ + { 0x0000, 0x0000, 0x0000 }, /* R568 */ + { 0x0000, 0x0000, 0x0000 }, /* R569 */ + { 0x0000, 0x0000, 0x0000 }, /* R570 */ + { 0x0000, 0x0000, 0x0000 }, /* R571 */ + { 0x0000, 0x0000, 0x0000 }, /* R572 */ + { 0x0000, 0x0000, 0x0000 }, /* R573 */ + { 0x0000, 0x0000, 0x0000 }, /* R574 */ + { 0x0000, 0x0000, 0x0000 }, /* R575 */ + { 0x0007, 0x0007, 0x0000 }, /* R576 - FLL2 Control (1) */ + { 0x3F77, 0x3F77, 0x0000 }, /* R577 - FLL2 Control (2) */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R578 - FLL2 Control (3) */ + { 0x7FEF, 0x7FEF, 0x0000 }, /* R579 - FLL2 Control (4) */ + { 0x1FDB, 0x1FDB, 0x0000 }, /* R580 - FLL2 Control (5) */ + { 0x0000, 0x0000, 0x0000 }, /* R581 */ + { 0x0000, 0x0000, 0x0000 }, /* R582 */ + { 0x0000, 0x0000, 0x0000 }, /* R583 */ + { 0x0000, 0x0000, 0x0000 }, /* R584 */ + { 0x0000, 0x0000, 0x0000 }, /* R585 */ + { 0x0000, 0x0000, 0x0000 }, /* R586 */ + { 0x0000, 0x0000, 0x0000 }, /* R587 */ + { 0x0000, 0x0000, 0x0000 }, /* R588 */ + { 0x0000, 0x0000, 0x0000 }, /* R589 */ + { 0x0000, 0x0000, 0x0000 }, /* R590 */ + { 0x0000, 0x0000, 0x0000 }, /* R591 */ + { 0x0000, 0x0000, 0x0000 }, /* R592 */ + { 0x0000, 0x0000, 0x0000 }, /* R593 */ + { 0x0000, 0x0000, 0x0000 }, /* R594 */ + { 0x0000, 0x0000, 0x0000 }, /* R595 */ + { 0x0000, 0x0000, 0x0000 }, /* R596 */ + { 0x0000, 0x0000, 0x0000 }, /* R597 */ + { 0x0000, 0x0000, 0x0000 }, /* R598 */ + { 0x0000, 0x0000, 0x0000 }, /* R599 */ + { 0x0000, 0x0000, 0x0000 }, /* R600 */ + { 0x0000, 0x0000, 0x0000 }, /* R601 */ + { 0x0000, 0x0000, 0x0000 }, /* R602 */ + { 0x0000, 0x0000, 0x0000 }, /* R603 */ + { 0x0000, 0x0000, 0x0000 }, /* R604 */ + { 0x0000, 0x0000, 0x0000 }, /* R605 */ + { 0x0000, 0x0000, 0x0000 }, /* R606 */ + { 0x0000, 0x0000, 0x0000 }, /* R607 */ + { 0x0000, 0x0000, 0x0000 }, /* R608 */ + { 0x0000, 0x0000, 0x0000 }, /* R609 */ + { 0x0000, 0x0000, 0x0000 }, /* R610 */ + { 0x0000, 0x0000, 0x0000 }, /* R611 */ + { 0x0000, 0x0000, 0x0000 }, /* R612 */ + { 0x0000, 0x0000, 0x0000 }, /* R613 */ + { 0x0000, 0x0000, 0x0000 }, /* R614 */ + { 0x0000, 0x0000, 0x0000 }, /* R615 */ + { 0x0000, 0x0000, 0x0000 }, /* R616 */ + { 0x0000, 0x0000, 0x0000 }, /* R617 */ + { 0x0000, 0x0000, 0x0000 }, /* R618 */ + { 0x0000, 0x0000, 0x0000 }, /* R619 */ + { 0x0000, 0x0000, 0x0000 }, /* R620 */ + { 0x0000, 0x0000, 0x0000 }, /* R621 */ + { 0x0000, 0x0000, 0x0000 }, /* R622 */ + { 0x0000, 0x0000, 0x0000 }, /* R623 */ + { 0x0000, 0x0000, 0x0000 }, /* R624 */ + { 0x0000, 0x0000, 0x0000 }, /* R625 */ + { 0x0000, 0x0000, 0x0000 }, /* R626 */ + { 0x0000, 0x0000, 0x0000 }, /* R627 */ + { 0x0000, 0x0000, 0x0000 }, /* R628 */ + { 0x0000, 0x0000, 0x0000 }, /* R629 */ + { 0x0000, 0x0000, 0x0000 }, /* R630 */ + { 0x0000, 0x0000, 0x0000 }, /* R631 */ + { 0x0000, 0x0000, 0x0000 }, /* R632 */ + { 0x0000, 0x0000, 0x0000 }, /* R633 */ + { 0x0000, 0x0000, 0x0000 }, /* R634 */ + { 0x0000, 0x0000, 0x0000 }, /* R635 */ + { 0x0000, 0x0000, 0x0000 }, /* R636 */ + { 0x0000, 0x0000, 0x0000 }, /* R637 */ + { 0x0000, 0x0000, 0x0000 }, /* R638 */ + { 0x0000, 0x0000, 0x0000 }, /* R639 */ + { 0x0000, 0x0000, 0x0000 }, /* R640 */ + { 0x0000, 0x0000, 0x0000 }, /* R641 */ + { 0x0000, 0x0000, 0x0000 }, /* R642 */ + { 0x0000, 0x0000, 0x0000 }, /* R643 */ + { 0x0000, 0x0000, 0x0000 }, /* R644 */ + { 0x0000, 0x0000, 0x0000 }, /* R645 */ + { 0x0000, 0x0000, 0x0000 }, /* R646 */ + { 0x0000, 0x0000, 0x0000 }, /* R647 */ + { 0x0000, 0x0000, 0x0000 }, /* R648 */ + { 0x0000, 0x0000, 0x0000 }, /* R649 */ + { 0x0000, 0x0000, 0x0000 }, /* R650 */ + { 0x0000, 0x0000, 0x0000 }, /* R651 */ + { 0x0000, 0x0000, 0x0000 }, /* R652 */ + { 0x0000, 0x0000, 0x0000 }, /* R653 */ + { 0x0000, 0x0000, 0x0000 }, /* R654 */ + { 0x0000, 0x0000, 0x0000 }, /* R655 */ + { 0x0000, 0x0000, 0x0000 }, /* R656 */ + { 0x0000, 0x0000, 0x0000 }, /* R657 */ + { 0x0000, 0x0000, 0x0000 }, /* R658 */ + { 0x0000, 0x0000, 0x0000 }, /* R659 */ + { 0x0000, 0x0000, 0x0000 }, /* R660 */ + { 0x0000, 0x0000, 0x0000 }, /* R661 */ + { 0x0000, 0x0000, 0x0000 }, /* R662 */ + { 0x0000, 0x0000, 0x0000 }, /* R663 */ + { 0x0000, 0x0000, 0x0000 }, /* R664 */ + { 0x0000, 0x0000, 0x0000 }, /* R665 */ + { 0x0000, 0x0000, 0x0000 }, /* R666 */ + { 0x0000, 0x0000, 0x0000 }, /* R667 */ + { 0x0000, 0x0000, 0x0000 }, /* R668 */ + { 0x0000, 0x0000, 0x0000 }, /* R669 */ + { 0x0000, 0x0000, 0x0000 }, /* R670 */ + { 0x0000, 0x0000, 0x0000 }, /* R671 */ + { 0x0000, 0x0000, 0x0000 }, /* R672 */ + { 0x0000, 0x0000, 0x0000 }, /* R673 */ + { 0x0000, 0x0000, 0x0000 }, /* R674 */ + { 0x0000, 0x0000, 0x0000 }, /* R675 */ + { 0x0000, 0x0000, 0x0000 }, /* R676 */ + { 0x0000, 0x0000, 0x0000 }, /* R677 */ + { 0x0000, 0x0000, 0x0000 }, /* R678 */ + { 0x0000, 0x0000, 0x0000 }, /* R679 */ + { 0x0000, 0x0000, 0x0000 }, /* R680 */ + { 0x0000, 0x0000, 0x0000 }, /* R681 */ + { 0x0000, 0x0000, 0x0000 }, /* R682 */ + { 0x0000, 0x0000, 0x0000 }, /* R683 */ + { 0x0000, 0x0000, 0x0000 }, /* R684 */ + { 0x0000, 0x0000, 0x0000 }, /* R685 */ + { 0x0000, 0x0000, 0x0000 }, /* R686 */ + { 0x0000, 0x0000, 0x0000 }, /* R687 */ + { 0x0000, 0x0000, 0x0000 }, /* R688 */ + { 0x0000, 0x0000, 0x0000 }, /* R689 */ + { 0x0000, 0x0000, 0x0000 }, /* R690 */ + { 0x0000, 0x0000, 0x0000 }, /* R691 */ + { 0x0000, 0x0000, 0x0000 }, /* R692 */ + { 0x0000, 0x0000, 0x0000 }, /* R693 */ + { 0x0000, 0x0000, 0x0000 }, /* R694 */ + { 0x0000, 0x0000, 0x0000 }, /* R695 */ + { 0x0000, 0x0000, 0x0000 }, /* R696 */ + { 0x0000, 0x0000, 0x0000 }, /* R697 */ + { 0x0000, 0x0000, 0x0000 }, /* R698 */ + { 0x0000, 0x0000, 0x0000 }, /* R699 */ + { 0x0000, 0x0000, 0x0000 }, /* R700 */ + { 0x0000, 0x0000, 0x0000 }, /* R701 */ + { 0x0000, 0x0000, 0x0000 }, /* R702 */ + { 0x0000, 0x0000, 0x0000 }, /* R703 */ + { 0x0000, 0x0000, 0x0000 }, /* R704 */ + { 0x0000, 0x0000, 0x0000 }, /* R705 */ + { 0x0000, 0x0000, 0x0000 }, /* R706 */ + { 0x0000, 0x0000, 0x0000 }, /* R707 */ + { 0x0000, 0x0000, 0x0000 }, /* R708 */ + { 0x0000, 0x0000, 0x0000 }, /* R709 */ + { 0x0000, 0x0000, 0x0000 }, /* R710 */ + { 0x0000, 0x0000, 0x0000 }, /* R711 */ + { 0x0000, 0x0000, 0x0000 }, /* R712 */ + { 0x0000, 0x0000, 0x0000 }, /* R713 */ + { 0x0000, 0x0000, 0x0000 }, /* R714 */ + { 0x0000, 0x0000, 0x0000 }, /* R715 */ + { 0x0000, 0x0000, 0x0000 }, /* R716 */ + { 0x0000, 0x0000, 0x0000 }, /* R717 */ + { 0x0000, 0x0000, 0x0000 }, /* R718 */ + { 0x0000, 0x0000, 0x0000 }, /* R719 */ + { 0x0000, 0x0000, 0x0000 }, /* R720 */ + { 0x0000, 0x0000, 0x0000 }, /* R721 */ + { 0x0000, 0x0000, 0x0000 }, /* R722 */ + { 0x0000, 0x0000, 0x0000 }, /* R723 */ + { 0x0000, 0x0000, 0x0000 }, /* R724 */ + { 0x0000, 0x0000, 0x0000 }, /* R725 */ + { 0x0000, 0x0000, 0x0000 }, /* R726 */ + { 0x0000, 0x0000, 0x0000 }, /* R727 */ + { 0x0000, 0x0000, 0x0000 }, /* R728 */ + { 0x0000, 0x0000, 0x0000 }, /* R729 */ + { 0x0000, 0x0000, 0x0000 }, /* R730 */ + { 0x0000, 0x0000, 0x0000 }, /* R731 */ + { 0x0000, 0x0000, 0x0000 }, /* R732 */ + { 0x0000, 0x0000, 0x0000 }, /* R733 */ + { 0x0000, 0x0000, 0x0000 }, /* R734 */ + { 0x0000, 0x0000, 0x0000 }, /* R735 */ + { 0x0000, 0x0000, 0x0000 }, /* R736 */ + { 0x0000, 0x0000, 0x0000 }, /* R737 */ + { 0x0000, 0x0000, 0x0000 }, /* R738 */ + { 0x0000, 0x0000, 0x0000 }, /* R739 */ + { 0x0000, 0x0000, 0x0000 }, /* R740 */ + { 0x0000, 0x0000, 0x0000 }, /* R741 */ + { 0x0000, 0x0000, 0x0000 }, /* R742 */ + { 0x0000, 0x0000, 0x0000 }, /* R743 */ + { 0x0000, 0x0000, 0x0000 }, /* R744 */ + { 0x0000, 0x0000, 0x0000 }, /* R745 */ + { 0x0000, 0x0000, 0x0000 }, /* R746 */ + { 0x0000, 0x0000, 0x0000 }, /* R747 */ + { 0x0000, 0x0000, 0x0000 }, /* R748 */ + { 0x0000, 0x0000, 0x0000 }, /* R749 */ + { 0x0000, 0x0000, 0x0000 }, /* R750 */ + { 0x0000, 0x0000, 0x0000 }, /* R751 */ + { 0x0000, 0x0000, 0x0000 }, /* R752 */ + { 0x0000, 0x0000, 0x0000 }, /* R753 */ + { 0x0000, 0x0000, 0x0000 }, /* R754 */ + { 0x0000, 0x0000, 0x0000 }, /* R755 */ + { 0x0000, 0x0000, 0x0000 }, /* R756 */ + { 0x0000, 0x0000, 0x0000 }, /* R757 */ + { 0x0000, 0x0000, 0x0000 }, /* R758 */ + { 0x0000, 0x0000, 0x0000 }, /* R759 */ + { 0x0000, 0x0000, 0x0000 }, /* R760 */ + { 0x0000, 0x0000, 0x0000 }, /* R761 */ + { 0x0000, 0x0000, 0x0000 }, /* R762 */ + { 0x0000, 0x0000, 0x0000 }, /* R763 */ + { 0x0000, 0x0000, 0x0000 }, /* R764 */ + { 0x0000, 0x0000, 0x0000 }, /* R765 */ + { 0x0000, 0x0000, 0x0000 }, /* R766 */ + { 0x0000, 0x0000, 0x0000 }, /* R767 */ + { 0xE1F8, 0xE1F8, 0x0000 }, /* R768 - AIF1 Control (1) */ + { 0xCD1F, 0xCD1F, 0x0000 }, /* R769 - AIF1 Control (2) */ + { 0xF000, 0xF000, 0x0000 }, /* R770 - AIF1 Master/Slave */ + { 0x01F0, 0x01F0, 0x0000 }, /* R771 - AIF1 BCLK */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R772 - AIF1ADC LRCLK */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R773 - AIF1DAC LRCLK */ + { 0x0003, 0x0003, 0x0000 }, /* R774 - AIF1DAC Data */ + { 0x0003, 0x0003, 0x0000 }, /* R775 - AIF1ADC Data */ + { 0x0000, 0x0000, 0x0000 }, /* R776 */ + { 0x0000, 0x0000, 0x0000 }, /* R777 */ + { 0x0000, 0x0000, 0x0000 }, /* R778 */ + { 0x0000, 0x0000, 0x0000 }, /* R779 */ + { 0x0000, 0x0000, 0x0000 }, /* R780 */ + { 0x0000, 0x0000, 0x0000 }, /* R781 */ + { 0x0000, 0x0000, 0x0000 }, /* R782 */ + { 0x0000, 0x0000, 0x0000 }, /* R783 */ + { 0xF1F8, 0xF1F8, 0x0000 }, /* R784 - AIF2 Control (1) */ + { 0xFD1F, 0xFD1F, 0x0000 }, /* R785 - AIF2 Control (2) */ + { 0xF000, 0xF000, 0x0000 }, /* R786 - AIF2 Master/Slave */ + { 0x01F0, 0x01F0, 0x0000 }, /* R787 - AIF2 BCLK */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R788 - AIF2ADC LRCLK */ + { 0x0FFF, 0x0FFF, 0x0000 }, /* R789 - AIF2DAC LRCLK */ + { 0x0003, 0x0003, 0x0000 }, /* R790 - AIF2DAC Data */ + { 0x0003, 0x0003, 0x0000 }, /* R791 - AIF2ADC Data */ + { 0x0000, 0x0000, 0x0000 }, /* R792 */ + { 0x0000, 0x0000, 0x0000 }, /* R793 */ + { 0x0000, 0x0000, 0x0000 }, /* R794 */ + { 0x0000, 0x0000, 0x0000 }, /* R795 */ + { 0x0000, 0x0000, 0x0000 }, /* R796 */ + { 0x0000, 0x0000, 0x0000 }, /* R797 */ + { 0x0000, 0x0000, 0x0000 }, /* R798 */ + { 0x0000, 0x0000, 0x0000 }, /* R799 */ + { 0x0000, 0x0000, 0x0000 }, /* R800 */ + { 0x0000, 0x0000, 0x0000 }, /* R801 */ + { 0x0000, 0x0000, 0x0000 }, /* R802 */ + { 0x0000, 0x0000, 0x0000 }, /* R803 */ + { 0x0000, 0x0000, 0x0000 }, /* R804 */ + { 0x0000, 0x0000, 0x0000 }, /* R805 */ + { 0x0000, 0x0000, 0x0000 }, /* R806 */ + { 0x0000, 0x0000, 0x0000 }, /* R807 */ + { 0x0000, 0x0000, 0x0000 }, /* R808 */ + { 0x0000, 0x0000, 0x0000 }, /* R809 */ + { 0x0000, 0x0000, 0x0000 }, /* R810 */ + { 0x0000, 0x0000, 0x0000 }, /* R811 */ + { 0x0000, 0x0000, 0x0000 }, /* R812 */ + { 0x0000, 0x0000, 0x0000 }, /* R813 */ + { 0x0000, 0x0000, 0x0000 }, /* R814 */ + { 0x0000, 0x0000, 0x0000 }, /* R815 */ + { 0x0000, 0x0000, 0x0000 }, /* R816 */ + { 0x0000, 0x0000, 0x0000 }, /* R817 */ + { 0x0000, 0x0000, 0x0000 }, /* R818 */ + { 0x0000, 0x0000, 0x0000 }, /* R819 */ + { 0x0000, 0x0000, 0x0000 }, /* R820 */ + { 0x0000, 0x0000, 0x0000 }, /* R821 */ + { 0x0000, 0x0000, 0x0000 }, /* R822 */ + { 0x0000, 0x0000, 0x0000 }, /* R823 */ + { 0x0000, 0x0000, 0x0000 }, /* R824 */ + { 0x0000, 0x0000, 0x0000 }, /* R825 */ + { 0x0000, 0x0000, 0x0000 }, /* R826 */ + { 0x0000, 0x0000, 0x0000 }, /* R827 */ + { 0x0000, 0x0000, 0x0000 }, /* R828 */ + { 0x0000, 0x0000, 0x0000 }, /* R829 */ + { 0x0000, 0x0000, 0x0000 }, /* R830 */ + { 0x0000, 0x0000, 0x0000 }, /* R831 */ + { 0x0000, 0x0000, 0x0000 }, /* R832 */ + { 0x0000, 0x0000, 0x0000 }, /* R833 */ + { 0x0000, 0x0000, 0x0000 }, /* R834 */ + { 0x0000, 0x0000, 0x0000 }, /* R835 */ + { 0x0000, 0x0000, 0x0000 }, /* R836 */ + { 0x0000, 0x0000, 0x0000 }, /* R837 */ + { 0x0000, 0x0000, 0x0000 }, /* R838 */ + { 0x0000, 0x0000, 0x0000 }, /* R839 */ + { 0x0000, 0x0000, 0x0000 }, /* R840 */ + { 0x0000, 0x0000, 0x0000 }, /* R841 */ + { 0x0000, 0x0000, 0x0000 }, /* R842 */ + { 0x0000, 0x0000, 0x0000 }, /* R843 */ + { 0x0000, 0x0000, 0x0000 }, /* R844 */ + { 0x0000, 0x0000, 0x0000 }, /* R845 */ + { 0x0000, 0x0000, 0x0000 }, /* R846 */ + { 0x0000, 0x0000, 0x0000 }, /* R847 */ + { 0x0000, 0x0000, 0x0000 }, /* R848 */ + { 0x0000, 0x0000, 0x0000 }, /* R849 */ + { 0x0000, 0x0000, 0x0000 }, /* R850 */ + { 0x0000, 0x0000, 0x0000 }, /* R851 */ + { 0x0000, 0x0000, 0x0000 }, /* R852 */ + { 0x0000, 0x0000, 0x0000 }, /* R853 */ + { 0x0000, 0x0000, 0x0000 }, /* R854 */ + { 0x0000, 0x0000, 0x0000 }, /* R855 */ + { 0x0000, 0x0000, 0x0000 }, /* R856 */ + { 0x0000, 0x0000, 0x0000 }, /* R857 */ + { 0x0000, 0x0000, 0x0000 }, /* R858 */ + { 0x0000, 0x0000, 0x0000 }, /* R859 */ + { 0x0000, 0x0000, 0x0000 }, /* R860 */ + { 0x0000, 0x0000, 0x0000 }, /* R861 */ + { 0x0000, 0x0000, 0x0000 }, /* R862 */ + { 0x0000, 0x0000, 0x0000 }, /* R863 */ + { 0x0000, 0x0000, 0x0000 }, /* R864 */ + { 0x0000, 0x0000, 0x0000 }, /* R865 */ + { 0x0000, 0x0000, 0x0000 }, /* R866 */ + { 0x0000, 0x0000, 0x0000 }, /* R867 */ + { 0x0000, 0x0000, 0x0000 }, /* R868 */ + { 0x0000, 0x0000, 0x0000 }, /* R869 */ + { 0x0000, 0x0000, 0x0000 }, /* R870 */ + { 0x0000, 0x0000, 0x0000 }, /* R871 */ + { 0x0000, 0x0000, 0x0000 }, /* R872 */ + { 0x0000, 0x0000, 0x0000 }, /* R873 */ + { 0x0000, 0x0000, 0x0000 }, /* R874 */ + { 0x0000, 0x0000, 0x0000 }, /* R875 */ + { 0x0000, 0x0000, 0x0000 }, /* R876 */ + { 0x0000, 0x0000, 0x0000 }, /* R877 */ + { 0x0000, 0x0000, 0x0000 }, /* R878 */ + { 0x0000, 0x0000, 0x0000 }, /* R879 */ + { 0x0000, 0x0000, 0x0000 }, /* R880 */ + { 0x0000, 0x0000, 0x0000 }, /* R881 */ + { 0x0000, 0x0000, 0x0000 }, /* R882 */ + { 0x0000, 0x0000, 0x0000 }, /* R883 */ + { 0x0000, 0x0000, 0x0000 }, /* R884 */ + { 0x0000, 0x0000, 0x0000 }, /* R885 */ + { 0x0000, 0x0000, 0x0000 }, /* R886 */ + { 0x0000, 0x0000, 0x0000 }, /* R887 */ + { 0x0000, 0x0000, 0x0000 }, /* R888 */ + { 0x0000, 0x0000, 0x0000 }, /* R889 */ + { 0x0000, 0x0000, 0x0000 }, /* R890 */ + { 0x0000, 0x0000, 0x0000 }, /* R891 */ + { 0x0000, 0x0000, 0x0000 }, /* R892 */ + { 0x0000, 0x0000, 0x0000 }, /* R893 */ + { 0x0000, 0x0000, 0x0000 }, /* R894 */ + { 0x0000, 0x0000, 0x0000 }, /* R895 */ + { 0x0000, 0x0000, 0x0000 }, /* R896 */ + { 0x0000, 0x0000, 0x0000 }, /* R897 */ + { 0x0000, 0x0000, 0x0000 }, /* R898 */ + { 0x0000, 0x0000, 0x0000 }, /* R899 */ + { 0x0000, 0x0000, 0x0000 }, /* R900 */ + { 0x0000, 0x0000, 0x0000 }, /* R901 */ + { 0x0000, 0x0000, 0x0000 }, /* R902 */ + { 0x0000, 0x0000, 0x0000 }, /* R903 */ + { 0x0000, 0x0000, 0x0000 }, /* R904 */ + { 0x0000, 0x0000, 0x0000 }, /* R905 */ + { 0x0000, 0x0000, 0x0000 }, /* R906 */ + { 0x0000, 0x0000, 0x0000 }, /* R907 */ + { 0x0000, 0x0000, 0x0000 }, /* R908 */ + { 0x0000, 0x0000, 0x0000 }, /* R909 */ + { 0x0000, 0x0000, 0x0000 }, /* R910 */ + { 0x0000, 0x0000, 0x0000 }, /* R911 */ + { 0x0000, 0x0000, 0x0000 }, /* R912 */ + { 0x0000, 0x0000, 0x0000 }, /* R913 */ + { 0x0000, 0x0000, 0x0000 }, /* R914 */ + { 0x0000, 0x0000, 0x0000 }, /* R915 */ + { 0x0000, 0x0000, 0x0000 }, /* R916 */ + { 0x0000, 0x0000, 0x0000 }, /* R917 */ + { 0x0000, 0x0000, 0x0000 }, /* R918 */ + { 0x0000, 0x0000, 0x0000 }, /* R919 */ + { 0x0000, 0x0000, 0x0000 }, /* R920 */ + { 0x0000, 0x0000, 0x0000 }, /* R921 */ + { 0x0000, 0x0000, 0x0000 }, /* R922 */ + { 0x0000, 0x0000, 0x0000 }, /* R923 */ + { 0x0000, 0x0000, 0x0000 }, /* R924 */ + { 0x0000, 0x0000, 0x0000 }, /* R925 */ + { 0x0000, 0x0000, 0x0000 }, /* R926 */ + { 0x0000, 0x0000, 0x0000 }, /* R927 */ + { 0x0000, 0x0000, 0x0000 }, /* R928 */ + { 0x0000, 0x0000, 0x0000 }, /* R929 */ + { 0x0000, 0x0000, 0x0000 }, /* R930 */ + { 0x0000, 0x0000, 0x0000 }, /* R931 */ + { 0x0000, 0x0000, 0x0000 }, /* R932 */ + { 0x0000, 0x0000, 0x0000 }, /* R933 */ + { 0x0000, 0x0000, 0x0000 }, /* R934 */ + { 0x0000, 0x0000, 0x0000 }, /* R935 */ + { 0x0000, 0x0000, 0x0000 }, /* R936 */ + { 0x0000, 0x0000, 0x0000 }, /* R937 */ + { 0x0000, 0x0000, 0x0000 }, /* R938 */ + { 0x0000, 0x0000, 0x0000 }, /* R939 */ + { 0x0000, 0x0000, 0x0000 }, /* R940 */ + { 0x0000, 0x0000, 0x0000 }, /* R941 */ + { 0x0000, 0x0000, 0x0000 }, /* R942 */ + { 0x0000, 0x0000, 0x0000 }, /* R943 */ + { 0x0000, 0x0000, 0x0000 }, /* R944 */ + { 0x0000, 0x0000, 0x0000 }, /* R945 */ + { 0x0000, 0x0000, 0x0000 }, /* R946 */ + { 0x0000, 0x0000, 0x0000 }, /* R947 */ + { 0x0000, 0x0000, 0x0000 }, /* R948 */ + { 0x0000, 0x0000, 0x0000 }, /* R949 */ + { 0x0000, 0x0000, 0x0000 }, /* R950 */ + { 0x0000, 0x0000, 0x0000 }, /* R951 */ + { 0x0000, 0x0000, 0x0000 }, /* R952 */ + { 0x0000, 0x0000, 0x0000 }, /* R953 */ + { 0x0000, 0x0000, 0x0000 }, /* R954 */ + { 0x0000, 0x0000, 0x0000 }, /* R955 */ + { 0x0000, 0x0000, 0x0000 }, /* R956 */ + { 0x0000, 0x0000, 0x0000 }, /* R957 */ + { 0x0000, 0x0000, 0x0000 }, /* R958 */ + { 0x0000, 0x0000, 0x0000 }, /* R959 */ + { 0x0000, 0x0000, 0x0000 }, /* R960 */ + { 0x0000, 0x0000, 0x0000 }, /* R961 */ + { 0x0000, 0x0000, 0x0000 }, /* R962 */ + { 0x0000, 0x0000, 0x0000 }, /* R963 */ + { 0x0000, 0x0000, 0x0000 }, /* R964 */ + { 0x0000, 0x0000, 0x0000 }, /* R965 */ + { 0x0000, 0x0000, 0x0000 }, /* R966 */ + { 0x0000, 0x0000, 0x0000 }, /* R967 */ + { 0x0000, 0x0000, 0x0000 }, /* R968 */ + { 0x0000, 0x0000, 0x0000 }, /* R969 */ + { 0x0000, 0x0000, 0x0000 }, /* R970 */ + { 0x0000, 0x0000, 0x0000 }, /* R971 */ + { 0x0000, 0x0000, 0x0000 }, /* R972 */ + { 0x0000, 0x0000, 0x0000 }, /* R973 */ + { 0x0000, 0x0000, 0x0000 }, /* R974 */ + { 0x0000, 0x0000, 0x0000 }, /* R975 */ + { 0x0000, 0x0000, 0x0000 }, /* R976 */ + { 0x0000, 0x0000, 0x0000 }, /* R977 */ + { 0x0000, 0x0000, 0x0000 }, /* R978 */ + { 0x0000, 0x0000, 0x0000 }, /* R979 */ + { 0x0000, 0x0000, 0x0000 }, /* R980 */ + { 0x0000, 0x0000, 0x0000 }, /* R981 */ + { 0x0000, 0x0000, 0x0000 }, /* R982 */ + { 0x0000, 0x0000, 0x0000 }, /* R983 */ + { 0x0000, 0x0000, 0x0000 }, /* R984 */ + { 0x0000, 0x0000, 0x0000 }, /* R985 */ + { 0x0000, 0x0000, 0x0000 }, /* R986 */ + { 0x0000, 0x0000, 0x0000 }, /* R987 */ + { 0x0000, 0x0000, 0x0000 }, /* R988 */ + { 0x0000, 0x0000, 0x0000 }, /* R989 */ + { 0x0000, 0x0000, 0x0000 }, /* R990 */ + { 0x0000, 0x0000, 0x0000 }, /* R991 */ + { 0x0000, 0x0000, 0x0000 }, /* R992 */ + { 0x0000, 0x0000, 0x0000 }, /* R993 */ + { 0x0000, 0x0000, 0x0000 }, /* R994 */ + { 0x0000, 0x0000, 0x0000 }, /* R995 */ + { 0x0000, 0x0000, 0x0000 }, /* R996 */ + { 0x0000, 0x0000, 0x0000 }, /* R997 */ + { 0x0000, 0x0000, 0x0000 }, /* R998 */ + { 0x0000, 0x0000, 0x0000 }, /* R999 */ + { 0x0000, 0x0000, 0x0000 }, /* R1000 */ + { 0x0000, 0x0000, 0x0000 }, /* R1001 */ + { 0x0000, 0x0000, 0x0000 }, /* R1002 */ + { 0x0000, 0x0000, 0x0000 }, /* R1003 */ + { 0x0000, 0x0000, 0x0000 }, /* R1004 */ + { 0x0000, 0x0000, 0x0000 }, /* R1005 */ + { 0x0000, 0x0000, 0x0000 }, /* R1006 */ + { 0x0000, 0x0000, 0x0000 }, /* R1007 */ + { 0x0000, 0x0000, 0x0000 }, /* R1008 */ + { 0x0000, 0x0000, 0x0000 }, /* R1009 */ + { 0x0000, 0x0000, 0x0000 }, /* R1010 */ + { 0x0000, 0x0000, 0x0000 }, /* R1011 */ + { 0x0000, 0x0000, 0x0000 }, /* R1012 */ + { 0x0000, 0x0000, 0x0000 }, /* R1013 */ + { 0x0000, 0x0000, 0x0000 }, /* R1014 */ + { 0x0000, 0x0000, 0x0000 }, /* R1015 */ + { 0x0000, 0x0000, 0x0000 }, /* R1016 */ + { 0x0000, 0x0000, 0x0000 }, /* R1017 */ + { 0x0000, 0x0000, 0x0000 }, /* R1018 */ + { 0x0000, 0x0000, 0x0000 }, /* R1019 */ + { 0x0000, 0x0000, 0x0000 }, /* R1020 */ + { 0x0000, 0x0000, 0x0000 }, /* R1021 */ + { 0x0000, 0x0000, 0x0000 }, /* R1022 */ + { 0x0000, 0x0000, 0x0000 }, /* R1023 */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1024 - AIF1 ADC1 Left Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1025 - AIF1 ADC1 Right Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1026 - AIF1 DAC1 Left Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1027 - AIF1 DAC1 Right Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1028 - AIF1 ADC2 Left Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1029 - AIF1 ADC2 Right Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1030 - AIF1 DAC2 Left Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1031 - AIF1 DAC2 Right Volume */ + { 0x0000, 0x0000, 0x0000 }, /* R1032 */ + { 0x0000, 0x0000, 0x0000 }, /* R1033 */ + { 0x0000, 0x0000, 0x0000 }, /* R1034 */ + { 0x0000, 0x0000, 0x0000 }, /* R1035 */ + { 0x0000, 0x0000, 0x0000 }, /* R1036 */ + { 0x0000, 0x0000, 0x0000 }, /* R1037 */ + { 0x0000, 0x0000, 0x0000 }, /* R1038 */ + { 0x0000, 0x0000, 0x0000 }, /* R1039 */ + { 0xF800, 0xF800, 0x0000 }, /* R1040 - AIF1 ADC1 Filters */ + { 0x7800, 0x7800, 0x0000 }, /* R1041 - AIF1 ADC2 Filters */ + { 0x0000, 0x0000, 0x0000 }, /* R1042 */ + { 0x0000, 0x0000, 0x0000 }, /* R1043 */ + { 0x0000, 0x0000, 0x0000 }, /* R1044 */ + { 0x0000, 0x0000, 0x0000 }, /* R1045 */ + { 0x0000, 0x0000, 0x0000 }, /* R1046 */ + { 0x0000, 0x0000, 0x0000 }, /* R1047 */ + { 0x0000, 0x0000, 0x0000 }, /* R1048 */ + { 0x0000, 0x0000, 0x0000 }, /* R1049 */ + { 0x0000, 0x0000, 0x0000 }, /* R1050 */ + { 0x0000, 0x0000, 0x0000 }, /* R1051 */ + { 0x0000, 0x0000, 0x0000 }, /* R1052 */ + { 0x0000, 0x0000, 0x0000 }, /* R1053 */ + { 0x0000, 0x0000, 0x0000 }, /* R1054 */ + { 0x0000, 0x0000, 0x0000 }, /* R1055 */ + { 0x02B6, 0x02B6, 0x0000 }, /* R1056 - AIF1 DAC1 Filters (1) */ + { 0x3F00, 0x3F00, 0x0000 }, /* R1057 - AIF1 DAC1 Filters (2) */ + { 0x02B6, 0x02B6, 0x0000 }, /* R1058 - AIF1 DAC2 Filters (1) */ + { 0x3F00, 0x3F00, 0x0000 }, /* R1059 - AIF1 DAC2 Filters (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R1060 */ + { 0x0000, 0x0000, 0x0000 }, /* R1061 */ + { 0x0000, 0x0000, 0x0000 }, /* R1062 */ + { 0x0000, 0x0000, 0x0000 }, /* R1063 */ + { 0x0000, 0x0000, 0x0000 }, /* R1064 */ + { 0x0000, 0x0000, 0x0000 }, /* R1065 */ + { 0x0000, 0x0000, 0x0000 }, /* R1066 */ + { 0x0000, 0x0000, 0x0000 }, /* R1067 */ + { 0x0000, 0x0000, 0x0000 }, /* R1068 */ + { 0x0000, 0x0000, 0x0000 }, /* R1069 */ + { 0x0000, 0x0000, 0x0000 }, /* R1070 */ + { 0x0000, 0x0000, 0x0000 }, /* R1071 */ + { 0x0000, 0x0000, 0x0000 }, /* R1072 */ + { 0x0000, 0x0000, 0x0000 }, /* R1073 */ + { 0x0000, 0x0000, 0x0000 }, /* R1074 */ + { 0x0000, 0x0000, 0x0000 }, /* R1075 */ + { 0x0000, 0x0000, 0x0000 }, /* R1076 */ + { 0x0000, 0x0000, 0x0000 }, /* R1077 */ + { 0x0000, 0x0000, 0x0000 }, /* R1078 */ + { 0x0000, 0x0000, 0x0000 }, /* R1079 */ + { 0x0000, 0x0000, 0x0000 }, /* R1080 */ + { 0x0000, 0x0000, 0x0000 }, /* R1081 */ + { 0x0000, 0x0000, 0x0000 }, /* R1082 */ + { 0x0000, 0x0000, 0x0000 }, /* R1083 */ + { 0x0000, 0x0000, 0x0000 }, /* R1084 */ + { 0x0000, 0x0000, 0x0000 }, /* R1085 */ + { 0x0000, 0x0000, 0x0000 }, /* R1086 */ + { 0x0000, 0x0000, 0x0000 }, /* R1087 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1088 - AIF1 DRC1 (1) */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R1089 - AIF1 DRC1 (2) */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1090 - AIF1 DRC1 (3) */ + { 0x07FF, 0x07FF, 0x0000 }, /* R1091 - AIF1 DRC1 (4) */ + { 0x03FF, 0x03FF, 0x0000 }, /* R1092 - AIF1 DRC1 (5) */ + { 0x0000, 0x0000, 0x0000 }, /* R1093 */ + { 0x0000, 0x0000, 0x0000 }, /* R1094 */ + { 0x0000, 0x0000, 0x0000 }, /* R1095 */ + { 0x0000, 0x0000, 0x0000 }, /* R1096 */ + { 0x0000, 0x0000, 0x0000 }, /* R1097 */ + { 0x0000, 0x0000, 0x0000 }, /* R1098 */ + { 0x0000, 0x0000, 0x0000 }, /* R1099 */ + { 0x0000, 0x0000, 0x0000 }, /* R1100 */ + { 0x0000, 0x0000, 0x0000 }, /* R1101 */ + { 0x0000, 0x0000, 0x0000 }, /* R1102 */ + { 0x0000, 0x0000, 0x0000 }, /* R1103 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1104 - AIF1 DRC2 (1) */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R1105 - AIF1 DRC2 (2) */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1106 - AIF1 DRC2 (3) */ + { 0x07FF, 0x07FF, 0x0000 }, /* R1107 - AIF1 DRC2 (4) */ + { 0x03FF, 0x03FF, 0x0000 }, /* R1108 - AIF1 DRC2 (5) */ + { 0x0000, 0x0000, 0x0000 }, /* R1109 */ + { 0x0000, 0x0000, 0x0000 }, /* R1110 */ + { 0x0000, 0x0000, 0x0000 }, /* R1111 */ + { 0x0000, 0x0000, 0x0000 }, /* R1112 */ + { 0x0000, 0x0000, 0x0000 }, /* R1113 */ + { 0x0000, 0x0000, 0x0000 }, /* R1114 */ + { 0x0000, 0x0000, 0x0000 }, /* R1115 */ + { 0x0000, 0x0000, 0x0000 }, /* R1116 */ + { 0x0000, 0x0000, 0x0000 }, /* R1117 */ + { 0x0000, 0x0000, 0x0000 }, /* R1118 */ + { 0x0000, 0x0000, 0x0000 }, /* R1119 */ + { 0x0000, 0x0000, 0x0000 }, /* R1120 */ + { 0x0000, 0x0000, 0x0000 }, /* R1121 */ + { 0x0000, 0x0000, 0x0000 }, /* R1122 */ + { 0x0000, 0x0000, 0x0000 }, /* R1123 */ + { 0x0000, 0x0000, 0x0000 }, /* R1124 */ + { 0x0000, 0x0000, 0x0000 }, /* R1125 */ + { 0x0000, 0x0000, 0x0000 }, /* R1126 */ + { 0x0000, 0x0000, 0x0000 }, /* R1127 */ + { 0x0000, 0x0000, 0x0000 }, /* R1128 */ + { 0x0000, 0x0000, 0x0000 }, /* R1129 */ + { 0x0000, 0x0000, 0x0000 }, /* R1130 */ + { 0x0000, 0x0000, 0x0000 }, /* R1131 */ + { 0x0000, 0x0000, 0x0000 }, /* R1132 */ + { 0x0000, 0x0000, 0x0000 }, /* R1133 */ + { 0x0000, 0x0000, 0x0000 }, /* R1134 */ + { 0x0000, 0x0000, 0x0000 }, /* R1135 */ + { 0x0000, 0x0000, 0x0000 }, /* R1136 */ + { 0x0000, 0x0000, 0x0000 }, /* R1137 */ + { 0x0000, 0x0000, 0x0000 }, /* R1138 */ + { 0x0000, 0x0000, 0x0000 }, /* R1139 */ + { 0x0000, 0x0000, 0x0000 }, /* R1140 */ + { 0x0000, 0x0000, 0x0000 }, /* R1141 */ + { 0x0000, 0x0000, 0x0000 }, /* R1142 */ + { 0x0000, 0x0000, 0x0000 }, /* R1143 */ + { 0x0000, 0x0000, 0x0000 }, /* R1144 */ + { 0x0000, 0x0000, 0x0000 }, /* R1145 */ + { 0x0000, 0x0000, 0x0000 }, /* R1146 */ + { 0x0000, 0x0000, 0x0000 }, /* R1147 */ + { 0x0000, 0x0000, 0x0000 }, /* R1148 */ + { 0x0000, 0x0000, 0x0000 }, /* R1149 */ + { 0x0000, 0x0000, 0x0000 }, /* R1150 */ + { 0x0000, 0x0000, 0x0000 }, /* R1151 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1152 - AIF1 DAC1 EQ Gains (1) */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R1153 - AIF1 DAC1 EQ Gains (2) */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1154 - AIF1 DAC1 EQ Band 1 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1155 - AIF1 DAC1 EQ Band 1 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1156 - AIF1 DAC1 EQ Band 1 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1157 - AIF1 DAC1 EQ Band 2 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1158 - AIF1 DAC1 EQ Band 2 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1159 - AIF1 DAC1 EQ Band 2 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1160 - AIF1 DAC1 EQ Band 2 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1161 - AIF1 DAC1 EQ Band 3 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1162 - AIF1 DAC1 EQ Band 3 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1163 - AIF1 DAC1 EQ Band 3 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1164 - AIF1 DAC1 EQ Band 3 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1165 - AIF1 DAC1 EQ Band 4 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1166 - AIF1 DAC1 EQ Band 4 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1167 - AIF1 DAC1 EQ Band 4 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1168 - AIF1 DAC1 EQ Band 4 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1169 - AIF1 DAC1 EQ Band 5 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1170 - AIF1 DAC1 EQ Band 5 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1171 - AIF1 DAC1 EQ Band 5 PG */ + { 0x0000, 0x0000, 0x0000 }, /* R1172 */ + { 0x0000, 0x0000, 0x0000 }, /* R1173 */ + { 0x0000, 0x0000, 0x0000 }, /* R1174 */ + { 0x0000, 0x0000, 0x0000 }, /* R1175 */ + { 0x0000, 0x0000, 0x0000 }, /* R1176 */ + { 0x0000, 0x0000, 0x0000 }, /* R1177 */ + { 0x0000, 0x0000, 0x0000 }, /* R1178 */ + { 0x0000, 0x0000, 0x0000 }, /* R1179 */ + { 0x0000, 0x0000, 0x0000 }, /* R1180 */ + { 0x0000, 0x0000, 0x0000 }, /* R1181 */ + { 0x0000, 0x0000, 0x0000 }, /* R1182 */ + { 0x0000, 0x0000, 0x0000 }, /* R1183 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1184 - AIF1 DAC2 EQ Gains (1) */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R1185 - AIF1 DAC2 EQ Gains (2) */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1186 - AIF1 DAC2 EQ Band 1 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1187 - AIF1 DAC2 EQ Band 1 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1188 - AIF1 DAC2 EQ Band 1 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1189 - AIF1 DAC2 EQ Band 2 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1190 - AIF1 DAC2 EQ Band 2 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1191 - AIF1 DAC2 EQ Band 2 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1192 - AIF1 DAC2 EQ Band 2 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1193 - AIF1 DAC2 EQ Band 3 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1194 - AIF1 DAC2 EQ Band 3 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1195 - AIF1 DAC2 EQ Band 3 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1196 - AIF1 DAC2 EQ Band 3 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1197 - AIF1 DAC2 EQ Band 4 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1198 - AIF1 DAC2 EQ Band 4 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1199 - AIF1 DAC2 EQ Band 4 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1200 - AIF1 DAC2 EQ Band 4 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1201 - AIF1 DAC2 EQ Band 5 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1202 - AIF1 DAC2 EQ Band 5 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1203 - AIF1 DAC2 EQ Band 5 PG */ + { 0x0000, 0x0000, 0x0000 }, /* R1204 */ + { 0x0000, 0x0000, 0x0000 }, /* R1205 */ + { 0x0000, 0x0000, 0x0000 }, /* R1206 */ + { 0x0000, 0x0000, 0x0000 }, /* R1207 */ + { 0x0000, 0x0000, 0x0000 }, /* R1208 */ + { 0x0000, 0x0000, 0x0000 }, /* R1209 */ + { 0x0000, 0x0000, 0x0000 }, /* R1210 */ + { 0x0000, 0x0000, 0x0000 }, /* R1211 */ + { 0x0000, 0x0000, 0x0000 }, /* R1212 */ + { 0x0000, 0x0000, 0x0000 }, /* R1213 */ + { 0x0000, 0x0000, 0x0000 }, /* R1214 */ + { 0x0000, 0x0000, 0x0000 }, /* R1215 */ + { 0x0000, 0x0000, 0x0000 }, /* R1216 */ + { 0x0000, 0x0000, 0x0000 }, /* R1217 */ + { 0x0000, 0x0000, 0x0000 }, /* R1218 */ + { 0x0000, 0x0000, 0x0000 }, /* R1219 */ + { 0x0000, 0x0000, 0x0000 }, /* R1220 */ + { 0x0000, 0x0000, 0x0000 }, /* R1221 */ + { 0x0000, 0x0000, 0x0000 }, /* R1222 */ + { 0x0000, 0x0000, 0x0000 }, /* R1223 */ + { 0x0000, 0x0000, 0x0000 }, /* R1224 */ + { 0x0000, 0x0000, 0x0000 }, /* R1225 */ + { 0x0000, 0x0000, 0x0000 }, /* R1226 */ + { 0x0000, 0x0000, 0x0000 }, /* R1227 */ + { 0x0000, 0x0000, 0x0000 }, /* R1228 */ + { 0x0000, 0x0000, 0x0000 }, /* R1229 */ + { 0x0000, 0x0000, 0x0000 }, /* R1230 */ + { 0x0000, 0x0000, 0x0000 }, /* R1231 */ + { 0x0000, 0x0000, 0x0000 }, /* R1232 */ + { 0x0000, 0x0000, 0x0000 }, /* R1233 */ + { 0x0000, 0x0000, 0x0000 }, /* R1234 */ + { 0x0000, 0x0000, 0x0000 }, /* R1235 */ + { 0x0000, 0x0000, 0x0000 }, /* R1236 */ + { 0x0000, 0x0000, 0x0000 }, /* R1237 */ + { 0x0000, 0x0000, 0x0000 }, /* R1238 */ + { 0x0000, 0x0000, 0x0000 }, /* R1239 */ + { 0x0000, 0x0000, 0x0000 }, /* R1240 */ + { 0x0000, 0x0000, 0x0000 }, /* R1241 */ + { 0x0000, 0x0000, 0x0000 }, /* R1242 */ + { 0x0000, 0x0000, 0x0000 }, /* R1243 */ + { 0x0000, 0x0000, 0x0000 }, /* R1244 */ + { 0x0000, 0x0000, 0x0000 }, /* R1245 */ + { 0x0000, 0x0000, 0x0000 }, /* R1246 */ + { 0x0000, 0x0000, 0x0000 }, /* R1247 */ + { 0x0000, 0x0000, 0x0000 }, /* R1248 */ + { 0x0000, 0x0000, 0x0000 }, /* R1249 */ + { 0x0000, 0x0000, 0x0000 }, /* R1250 */ + { 0x0000, 0x0000, 0x0000 }, /* R1251 */ + { 0x0000, 0x0000, 0x0000 }, /* R1252 */ + { 0x0000, 0x0000, 0x0000 }, /* R1253 */ + { 0x0000, 0x0000, 0x0000 }, /* R1254 */ + { 0x0000, 0x0000, 0x0000 }, /* R1255 */ + { 0x0000, 0x0000, 0x0000 }, /* R1256 */ + { 0x0000, 0x0000, 0x0000 }, /* R1257 */ + { 0x0000, 0x0000, 0x0000 }, /* R1258 */ + { 0x0000, 0x0000, 0x0000 }, /* R1259 */ + { 0x0000, 0x0000, 0x0000 }, /* R1260 */ + { 0x0000, 0x0000, 0x0000 }, /* R1261 */ + { 0x0000, 0x0000, 0x0000 }, /* R1262 */ + { 0x0000, 0x0000, 0x0000 }, /* R1263 */ + { 0x0000, 0x0000, 0x0000 }, /* R1264 */ + { 0x0000, 0x0000, 0x0000 }, /* R1265 */ + { 0x0000, 0x0000, 0x0000 }, /* R1266 */ + { 0x0000, 0x0000, 0x0000 }, /* R1267 */ + { 0x0000, 0x0000, 0x0000 }, /* R1268 */ + { 0x0000, 0x0000, 0x0000 }, /* R1269 */ + { 0x0000, 0x0000, 0x0000 }, /* R1270 */ + { 0x0000, 0x0000, 0x0000 }, /* R1271 */ + { 0x0000, 0x0000, 0x0000 }, /* R1272 */ + { 0x0000, 0x0000, 0x0000 }, /* R1273 */ + { 0x0000, 0x0000, 0x0000 }, /* R1274 */ + { 0x0000, 0x0000, 0x0000 }, /* R1275 */ + { 0x0000, 0x0000, 0x0000 }, /* R1276 */ + { 0x0000, 0x0000, 0x0000 }, /* R1277 */ + { 0x0000, 0x0000, 0x0000 }, /* R1278 */ + { 0x0000, 0x0000, 0x0000 }, /* R1279 */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1280 - AIF2 ADC Left Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1281 - AIF2 ADC Right Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1282 - AIF2 DAC Left Volume */ + { 0x00FF, 0x01FF, 0x0000 }, /* R1283 - AIF2 DAC Right Volume */ + { 0x0000, 0x0000, 0x0000 }, /* R1284 */ + { 0x0000, 0x0000, 0x0000 }, /* R1285 */ + { 0x0000, 0x0000, 0x0000 }, /* R1286 */ + { 0x0000, 0x0000, 0x0000 }, /* R1287 */ + { 0x0000, 0x0000, 0x0000 }, /* R1288 */ + { 0x0000, 0x0000, 0x0000 }, /* R1289 */ + { 0x0000, 0x0000, 0x0000 }, /* R1290 */ + { 0x0000, 0x0000, 0x0000 }, /* R1291 */ + { 0x0000, 0x0000, 0x0000 }, /* R1292 */ + { 0x0000, 0x0000, 0x0000 }, /* R1293 */ + { 0x0000, 0x0000, 0x0000 }, /* R1294 */ + { 0x0000, 0x0000, 0x0000 }, /* R1295 */ + { 0xF800, 0xF800, 0x0000 }, /* R1296 - AIF2 ADC Filters */ + { 0x0000, 0x0000, 0x0000 }, /* R1297 */ + { 0x0000, 0x0000, 0x0000 }, /* R1298 */ + { 0x0000, 0x0000, 0x0000 }, /* R1299 */ + { 0x0000, 0x0000, 0x0000 }, /* R1300 */ + { 0x0000, 0x0000, 0x0000 }, /* R1301 */ + { 0x0000, 0x0000, 0x0000 }, /* R1302 */ + { 0x0000, 0x0000, 0x0000 }, /* R1303 */ + { 0x0000, 0x0000, 0x0000 }, /* R1304 */ + { 0x0000, 0x0000, 0x0000 }, /* R1305 */ + { 0x0000, 0x0000, 0x0000 }, /* R1306 */ + { 0x0000, 0x0000, 0x0000 }, /* R1307 */ + { 0x0000, 0x0000, 0x0000 }, /* R1308 */ + { 0x0000, 0x0000, 0x0000 }, /* R1309 */ + { 0x0000, 0x0000, 0x0000 }, /* R1310 */ + { 0x0000, 0x0000, 0x0000 }, /* R1311 */ + { 0x02B6, 0x02B6, 0x0000 }, /* R1312 - AIF2 DAC Filters (1) */ + { 0x3F00, 0x3F00, 0x0000 }, /* R1313 - AIF2 DAC Filters (2) */ + { 0x0000, 0x0000, 0x0000 }, /* R1314 */ + { 0x0000, 0x0000, 0x0000 }, /* R1315 */ + { 0x0000, 0x0000, 0x0000 }, /* R1316 */ + { 0x0000, 0x0000, 0x0000 }, /* R1317 */ + { 0x0000, 0x0000, 0x0000 }, /* R1318 */ + { 0x0000, 0x0000, 0x0000 }, /* R1319 */ + { 0x0000, 0x0000, 0x0000 }, /* R1320 */ + { 0x0000, 0x0000, 0x0000 }, /* R1321 */ + { 0x0000, 0x0000, 0x0000 }, /* R1322 */ + { 0x0000, 0x0000, 0x0000 }, /* R1323 */ + { 0x0000, 0x0000, 0x0000 }, /* R1324 */ + { 0x0000, 0x0000, 0x0000 }, /* R1325 */ + { 0x0000, 0x0000, 0x0000 }, /* R1326 */ + { 0x0000, 0x0000, 0x0000 }, /* R1327 */ + { 0x0000, 0x0000, 0x0000 }, /* R1328 */ + { 0x0000, 0x0000, 0x0000 }, /* R1329 */ + { 0x0000, 0x0000, 0x0000 }, /* R1330 */ + { 0x0000, 0x0000, 0x0000 }, /* R1331 */ + { 0x0000, 0x0000, 0x0000 }, /* R1332 */ + { 0x0000, 0x0000, 0x0000 }, /* R1333 */ + { 0x0000, 0x0000, 0x0000 }, /* R1334 */ + { 0x0000, 0x0000, 0x0000 }, /* R1335 */ + { 0x0000, 0x0000, 0x0000 }, /* R1336 */ + { 0x0000, 0x0000, 0x0000 }, /* R1337 */ + { 0x0000, 0x0000, 0x0000 }, /* R1338 */ + { 0x0000, 0x0000, 0x0000 }, /* R1339 */ + { 0x0000, 0x0000, 0x0000 }, /* R1340 */ + { 0x0000, 0x0000, 0x0000 }, /* R1341 */ + { 0x0000, 0x0000, 0x0000 }, /* R1342 */ + { 0x0000, 0x0000, 0x0000 }, /* R1343 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1344 - AIF2 DRC (1) */ + { 0x1FFF, 0x1FFF, 0x0000 }, /* R1345 - AIF2 DRC (2) */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1346 - AIF2 DRC (3) */ + { 0x07FF, 0x07FF, 0x0000 }, /* R1347 - AIF2 DRC (4) */ + { 0x03FF, 0x03FF, 0x0000 }, /* R1348 - AIF2 DRC (5) */ + { 0x0000, 0x0000, 0x0000 }, /* R1349 */ + { 0x0000, 0x0000, 0x0000 }, /* R1350 */ + { 0x0000, 0x0000, 0x0000 }, /* R1351 */ + { 0x0000, 0x0000, 0x0000 }, /* R1352 */ + { 0x0000, 0x0000, 0x0000 }, /* R1353 */ + { 0x0000, 0x0000, 0x0000 }, /* R1354 */ + { 0x0000, 0x0000, 0x0000 }, /* R1355 */ + { 0x0000, 0x0000, 0x0000 }, /* R1356 */ + { 0x0000, 0x0000, 0x0000 }, /* R1357 */ + { 0x0000, 0x0000, 0x0000 }, /* R1358 */ + { 0x0000, 0x0000, 0x0000 }, /* R1359 */ + { 0x0000, 0x0000, 0x0000 }, /* R1360 */ + { 0x0000, 0x0000, 0x0000 }, /* R1361 */ + { 0x0000, 0x0000, 0x0000 }, /* R1362 */ + { 0x0000, 0x0000, 0x0000 }, /* R1363 */ + { 0x0000, 0x0000, 0x0000 }, /* R1364 */ + { 0x0000, 0x0000, 0x0000 }, /* R1365 */ + { 0x0000, 0x0000, 0x0000 }, /* R1366 */ + { 0x0000, 0x0000, 0x0000 }, /* R1367 */ + { 0x0000, 0x0000, 0x0000 }, /* R1368 */ + { 0x0000, 0x0000, 0x0000 }, /* R1369 */ + { 0x0000, 0x0000, 0x0000 }, /* R1370 */ + { 0x0000, 0x0000, 0x0000 }, /* R1371 */ + { 0x0000, 0x0000, 0x0000 }, /* R1372 */ + { 0x0000, 0x0000, 0x0000 }, /* R1373 */ + { 0x0000, 0x0000, 0x0000 }, /* R1374 */ + { 0x0000, 0x0000, 0x0000 }, /* R1375 */ + { 0x0000, 0x0000, 0x0000 }, /* R1376 */ + { 0x0000, 0x0000, 0x0000 }, /* R1377 */ + { 0x0000, 0x0000, 0x0000 }, /* R1378 */ + { 0x0000, 0x0000, 0x0000 }, /* R1379 */ + { 0x0000, 0x0000, 0x0000 }, /* R1380 */ + { 0x0000, 0x0000, 0x0000 }, /* R1381 */ + { 0x0000, 0x0000, 0x0000 }, /* R1382 */ + { 0x0000, 0x0000, 0x0000 }, /* R1383 */ + { 0x0000, 0x0000, 0x0000 }, /* R1384 */ + { 0x0000, 0x0000, 0x0000 }, /* R1385 */ + { 0x0000, 0x0000, 0x0000 }, /* R1386 */ + { 0x0000, 0x0000, 0x0000 }, /* R1387 */ + { 0x0000, 0x0000, 0x0000 }, /* R1388 */ + { 0x0000, 0x0000, 0x0000 }, /* R1389 */ + { 0x0000, 0x0000, 0x0000 }, /* R1390 */ + { 0x0000, 0x0000, 0x0000 }, /* R1391 */ + { 0x0000, 0x0000, 0x0000 }, /* R1392 */ + { 0x0000, 0x0000, 0x0000 }, /* R1393 */ + { 0x0000, 0x0000, 0x0000 }, /* R1394 */ + { 0x0000, 0x0000, 0x0000 }, /* R1395 */ + { 0x0000, 0x0000, 0x0000 }, /* R1396 */ + { 0x0000, 0x0000, 0x0000 }, /* R1397 */ + { 0x0000, 0x0000, 0x0000 }, /* R1398 */ + { 0x0000, 0x0000, 0x0000 }, /* R1399 */ + { 0x0000, 0x0000, 0x0000 }, /* R1400 */ + { 0x0000, 0x0000, 0x0000 }, /* R1401 */ + { 0x0000, 0x0000, 0x0000 }, /* R1402 */ + { 0x0000, 0x0000, 0x0000 }, /* R1403 */ + { 0x0000, 0x0000, 0x0000 }, /* R1404 */ + { 0x0000, 0x0000, 0x0000 }, /* R1405 */ + { 0x0000, 0x0000, 0x0000 }, /* R1406 */ + { 0x0000, 0x0000, 0x0000 }, /* R1407 */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1408 - AIF2 EQ Gains (1) */ + { 0xFFC0, 0xFFC0, 0x0000 }, /* R1409 - AIF2 EQ Gains (2) */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1410 - AIF2 EQ Band 1 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1411 - AIF2 EQ Band 1 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1412 - AIF2 EQ Band 1 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1413 - AIF2 EQ Band 2 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1414 - AIF2 EQ Band 2 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1415 - AIF2 EQ Band 2 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1416 - AIF2 EQ Band 2 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1417 - AIF2 EQ Band 3 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1418 - AIF2 EQ Band 3 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1419 - AIF2 EQ Band 3 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1420 - AIF2 EQ Band 3 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1421 - AIF2 EQ Band 4 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1422 - AIF2 EQ Band 4 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1423 - AIF2 EQ Band 4 C */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1424 - AIF2 EQ Band 4 PG */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1425 - AIF2 EQ Band 5 A */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1426 - AIF2 EQ Band 5 B */ + { 0xFFFF, 0xFFFF, 0x0000 }, /* R1427 - AIF2 EQ Band 5 PG */ + { 0x0000, 0x0000, 0x0000 }, /* R1428 */ + { 0x0000, 0x0000, 0x0000 }, /* R1429 */ + { 0x0000, 0x0000, 0x0000 }, /* R1430 */ + { 0x0000, 0x0000, 0x0000 }, /* R1431 */ + { 0x0000, 0x0000, 0x0000 }, /* R1432 */ + { 0x0000, 0x0000, 0x0000 }, /* R1433 */ + { 0x0000, 0x0000, 0x0000 }, /* R1434 */ + { 0x0000, 0x0000, 0x0000 }, /* R1435 */ + { 0x0000, 0x0000, 0x0000 }, /* R1436 */ + { 0x0000, 0x0000, 0x0000 }, /* R1437 */ + { 0x0000, 0x0000, 0x0000 }, /* R1438 */ + { 0x0000, 0x0000, 0x0000 }, /* R1439 */ + { 0x0000, 0x0000, 0x0000 }, /* R1440 */ + { 0x0000, 0x0000, 0x0000 }, /* R1441 */ + { 0x0000, 0x0000, 0x0000 }, /* R1442 */ + { 0x0000, 0x0000, 0x0000 }, /* R1443 */ + { 0x0000, 0x0000, 0x0000 }, /* R1444 */ + { 0x0000, 0x0000, 0x0000 }, /* R1445 */ + { 0x0000, 0x0000, 0x0000 }, /* R1446 */ + { 0x0000, 0x0000, 0x0000 }, /* R1447 */ + { 0x0000, 0x0000, 0x0000 }, /* R1448 */ + { 0x0000, 0x0000, 0x0000 }, /* R1449 */ + { 0x0000, 0x0000, 0x0000 }, /* R1450 */ + { 0x0000, 0x0000, 0x0000 }, /* R1451 */ + { 0x0000, 0x0000, 0x0000 }, /* R1452 */ + { 0x0000, 0x0000, 0x0000 }, /* R1453 */ + { 0x0000, 0x0000, 0x0000 }, /* R1454 */ + { 0x0000, 0x0000, 0x0000 }, /* R1455 */ + { 0x0000, 0x0000, 0x0000 }, /* R1456 */ + { 0x0000, 0x0000, 0x0000 }, /* R1457 */ + { 0x0000, 0x0000, 0x0000 }, /* R1458 */ + { 0x0000, 0x0000, 0x0000 }, /* R1459 */ + { 0x0000, 0x0000, 0x0000 }, /* R1460 */ + { 0x0000, 0x0000, 0x0000 }, /* R1461 */ + { 0x0000, 0x0000, 0x0000 }, /* R1462 */ + { 0x0000, 0x0000, 0x0000 }, /* R1463 */ + { 0x0000, 0x0000, 0x0000 }, /* R1464 */ + { 0x0000, 0x0000, 0x0000 }, /* R1465 */ + { 0x0000, 0x0000, 0x0000 }, /* R1466 */ + { 0x0000, 0x0000, 0x0000 }, /* R1467 */ + { 0x0000, 0x0000, 0x0000 }, /* R1468 */ + { 0x0000, 0x0000, 0x0000 }, /* R1469 */ + { 0x0000, 0x0000, 0x0000 }, /* R1470 */ + { 0x0000, 0x0000, 0x0000 }, /* R1471 */ + { 0x0000, 0x0000, 0x0000 }, /* R1472 */ + { 0x0000, 0x0000, 0x0000 }, /* R1473 */ + { 0x0000, 0x0000, 0x0000 }, /* R1474 */ + { 0x0000, 0x0000, 0x0000 }, /* R1475 */ + { 0x0000, 0x0000, 0x0000 }, /* R1476 */ + { 0x0000, 0x0000, 0x0000 }, /* R1477 */ + { 0x0000, 0x0000, 0x0000 }, /* R1478 */ + { 0x0000, 0x0000, 0x0000 }, /* R1479 */ + { 0x0000, 0x0000, 0x0000 }, /* R1480 */ + { 0x0000, 0x0000, 0x0000 }, /* R1481 */ + { 0x0000, 0x0000, 0x0000 }, /* R1482 */ + { 0x0000, 0x0000, 0x0000 }, /* R1483 */ + { 0x0000, 0x0000, 0x0000 }, /* R1484 */ + { 0x0000, 0x0000, 0x0000 }, /* R1485 */ + { 0x0000, 0x0000, 0x0000 }, /* R1486 */ + { 0x0000, 0x0000, 0x0000 }, /* R1487 */ + { 0x0000, 0x0000, 0x0000 }, /* R1488 */ + { 0x0000, 0x0000, 0x0000 }, /* R1489 */ + { 0x0000, 0x0000, 0x0000 }, /* R1490 */ + { 0x0000, 0x0000, 0x0000 }, /* R1491 */ + { 0x0000, 0x0000, 0x0000 }, /* R1492 */ + { 0x0000, 0x0000, 0x0000 }, /* R1493 */ + { 0x0000, 0x0000, 0x0000 }, /* R1494 */ + { 0x0000, 0x0000, 0x0000 }, /* R1495 */ + { 0x0000, 0x0000, 0x0000 }, /* R1496 */ + { 0x0000, 0x0000, 0x0000 }, /* R1497 */ + { 0x0000, 0x0000, 0x0000 }, /* R1498 */ + { 0x0000, 0x0000, 0x0000 }, /* R1499 */ + { 0x0000, 0x0000, 0x0000 }, /* R1500 */ + { 0x0000, 0x0000, 0x0000 }, /* R1501 */ + { 0x0000, 0x0000, 0x0000 }, /* R1502 */ + { 0x0000, 0x0000, 0x0000 }, /* R1503 */ + { 0x0000, 0x0000, 0x0000 }, /* R1504 */ + { 0x0000, 0x0000, 0x0000 }, /* R1505 */ + { 0x0000, 0x0000, 0x0000 }, /* R1506 */ + { 0x0000, 0x0000, 0x0000 }, /* R1507 */ + { 0x0000, 0x0000, 0x0000 }, /* R1508 */ + { 0x0000, 0x0000, 0x0000 }, /* R1509 */ + { 0x0000, 0x0000, 0x0000 }, /* R1510 */ + { 0x0000, 0x0000, 0x0000 }, /* R1511 */ + { 0x0000, 0x0000, 0x0000 }, /* R1512 */ + { 0x0000, 0x0000, 0x0000 }, /* R1513 */ + { 0x0000, 0x0000, 0x0000 }, /* R1514 */ + { 0x0000, 0x0000, 0x0000 }, /* R1515 */ + { 0x0000, 0x0000, 0x0000 }, /* R1516 */ + { 0x0000, 0x0000, 0x0000 }, /* R1517 */ + { 0x0000, 0x0000, 0x0000 }, /* R1518 */ + { 0x0000, 0x0000, 0x0000 }, /* R1519 */ + { 0x0000, 0x0000, 0x0000 }, /* R1520 */ + { 0x0000, 0x0000, 0x0000 }, /* R1521 */ + { 0x0000, 0x0000, 0x0000 }, /* R1522 */ + { 0x0000, 0x0000, 0x0000 }, /* R1523 */ + { 0x0000, 0x0000, 0x0000 }, /* R1524 */ + { 0x0000, 0x0000, 0x0000 }, /* R1525 */ + { 0x0000, 0x0000, 0x0000 }, /* R1526 */ + { 0x0000, 0x0000, 0x0000 }, /* R1527 */ + { 0x0000, 0x0000, 0x0000 }, /* R1528 */ + { 0x0000, 0x0000, 0x0000 }, /* R1529 */ + { 0x0000, 0x0000, 0x0000 }, /* R1530 */ + { 0x0000, 0x0000, 0x0000 }, /* R1531 */ + { 0x0000, 0x0000, 0x0000 }, /* R1532 */ + { 0x0000, 0x0000, 0x0000 }, /* R1533 */ + { 0x0000, 0x0000, 0x0000 }, /* R1534 */ + { 0x0000, 0x0000, 0x0000 }, /* R1535 */ + { 0x01EF, 0x01EF, 0x0000 }, /* R1536 - DAC1 Mixer Volumes */ + { 0x0037, 0x0037, 0x0000 }, /* R1537 - DAC1 Left Mixer Routing */ + { 0x0037, 0x0037, 0x0000 }, /* R1538 - DAC1 Right Mixer Routing */ + { 0x01EF, 0x01EF, 0x0000 }, /* R1539 - DAC2 Mixer Volumes */ + { 0x0037, 0x0037, 0x0000 }, /* R1540 - DAC2 Left Mixer Routing */ + { 0x0037, 0x0037, 0x0000 }, /* R1541 - DAC2 Right Mixer Routing */ + { 0x0003, 0x0003, 0x0000 }, /* R1542 - AIF1 ADC1 Left Mixer Routing */ + { 0x0003, 0x0003, 0x0000 }, /* R1543 - AIF1 ADC1 Right Mixer Routing */ + { 0x0003, 0x0003, 0x0000 }, /* R1544 - AIF1 ADC2 Left Mixer Routing */ + { 0x0003, 0x0003, 0x0000 }, /* R1545 - AIF1 ADC2 Right mixer Routing */ + { 0x0000, 0x0000, 0x0000 }, /* R1546 */ + { 0x0000, 0x0000, 0x0000 }, /* R1547 */ + { 0x0000, 0x0000, 0x0000 }, /* R1548 */ + { 0x0000, 0x0000, 0x0000 }, /* R1549 */ + { 0x0000, 0x0000, 0x0000 }, /* R1550 */ + { 0x0000, 0x0000, 0x0000 }, /* R1551 */ + { 0x02FF, 0x03FF, 0x0000 }, /* R1552 - DAC1 Left Volume */ + { 0x02FF, 0x03FF, 0x0000 }, /* R1553 - DAC1 Right Volume */ + { 0x02FF, 0x03FF, 0x0000 }, /* R1554 - DAC2 Left Volume */ + { 0x02FF, 0x03FF, 0x0000 }, /* R1555 - DAC2 Right Volume */ + { 0x0003, 0x0003, 0x0000 }, /* R1556 - DAC Softmute */ + { 0x0000, 0x0000, 0x0000 }, /* R1557 */ + { 0x0000, 0x0000, 0x0000 }, /* R1558 */ + { 0x0000, 0x0000, 0x0000 }, /* R1559 */ + { 0x0000, 0x0000, 0x0000 }, /* R1560 */ + { 0x0000, 0x0000, 0x0000 }, /* R1561 */ + { 0x0000, 0x0000, 0x0000 }, /* R1562 */ + { 0x0000, 0x0000, 0x0000 }, /* R1563 */ + { 0x0000, 0x0000, 0x0000 }, /* R1564 */ + { 0x0000, 0x0000, 0x0000 }, /* R1565 */ + { 0x0000, 0x0000, 0x0000 }, /* R1566 */ + { 0x0000, 0x0000, 0x0000 }, /* R1567 */ + { 0x0003, 0x0003, 0x0000 }, /* R1568 - Oversampling */ + { 0x03C3, 0x03C3, 0x0000 }, /* R1569 - Sidetone */ +}; + +static int wm8994_readable(unsigned int reg) +{ + if (reg >= ARRAY_SIZE(access_masks)) + return 0; + return access_masks[reg].readable != 0; +} + +static int wm8994_volatile(unsigned int reg) +{ + if (reg >= WM8994_REG_CACHE_SIZE) + return 1; + + switch (reg) { + case WM8994_SOFTWARE_RESET: + case WM8994_CHIP_REVISION: + case WM8994_DC_SERVO_1: + case WM8994_DC_SERVO_READBACK: + case WM8994_RATE_STATUS: + case WM8994_LDO_1: + case WM8994_LDO_2: + return 1; + default: + return 0; + } +} + +static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct wm8994_priv *wm8994 = codec->private_data; + + BUG_ON(reg > WM8994_MAX_REGISTER); + + if (!wm8994_volatile(reg)) + wm8994->reg_cache[reg] = value; + + return wm8994_reg_write(codec->control_data, reg, value); +} + +static unsigned int wm8994_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *reg_cache = codec->reg_cache; + + BUG_ON(reg > WM8994_MAX_REGISTER); + + if (wm8994_volatile(reg)) + return wm8994_reg_read(codec->control_data, reg); + else + return reg_cache[reg]; +} + +static int configure_aif_clock(struct snd_soc_codec *codec, int aif) +{ + struct wm8994_priv *wm8994 = codec->private_data; + int rate; + int reg1 = 0; + int offset; + + if (aif) + offset = 4; + else + offset = 0; + + switch (wm8994->sysclk[aif]) { + case WM8994_SYSCLK_MCLK1: + rate = wm8994->mclk[0]; + break; + + case WM8994_SYSCLK_MCLK2: + reg1 |= 0x8; + rate = wm8994->mclk[1]; + break; + + case WM8994_SYSCLK_FLL1: + reg1 |= 0x10; + rate = wm8994->fll[0].out; + break; + + case WM8994_SYSCLK_FLL2: + reg1 |= 0x18; + rate = wm8994->fll[1].out; + break; + + default: + return -EINVAL; + } + + if (rate >= 13500000) { + rate /= 2; + reg1 |= WM8994_AIF1CLK_DIV; + + dev_dbg(codec->dev, "Dividing AIF%d clock to %dHz\n", + aif + 1, rate); + } + wm8994->aifclk[aif] = rate; + + snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset, + WM8994_AIF1CLK_SRC_MASK | WM8994_AIF1CLK_DIV, + reg1); + + return 0; +} + +static int configure_clock(struct snd_soc_codec *codec) +{ + struct wm8994_priv *wm8994 = codec->private_data; + int old, new; + + /* Bring up the AIF clocks first */ + configure_aif_clock(codec, 0); + configure_aif_clock(codec, 1); + + /* Then switch CLK_SYS over to the higher of them; a change + * can only happen as a result of a clocking change which can + * only be made outside of DAPM so we can safely redo the + * clocking. + */ + + /* If they're equal it doesn't matter which is used */ + if (wm8994->aifclk[0] == wm8994->aifclk[1]) + return 0; + + if (wm8994->aifclk[0] < wm8994->aifclk[1]) + new = WM8994_SYSCLK_SRC; + else + new = 0; + + old = snd_soc_read(codec, WM8994_CLOCKING_1) & WM8994_SYSCLK_SRC; + + /* If there's no change then we're done. */ + if (old == new) + return 0; + + snd_soc_update_bits(codec, WM8994_CLOCKING_1, WM8994_SYSCLK_SRC, new); + + snd_soc_dapm_sync(codec); + + return 0; +} + +static int check_clk_sys(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + int reg = snd_soc_read(source->codec, WM8994_CLOCKING_1); + const char *clk; + + /* Check what we're currently using for CLK_SYS */ + if (reg & WM8994_SYSCLK_SRC) + clk = "AIF2CLK"; + else + clk = "AIF1CLK"; + + return strcmp(source->name, clk) == 0; +} + +static const char *sidetone_hpf_text[] = { + "2.7kHz", "1.35kHz", "675Hz", "370Hz", "180Hz", "90Hz", "45Hz" +}; + +static const struct soc_enum sidetone_hpf = + SOC_ENUM_SINGLE(WM8994_SIDETONE, 7, 7, sidetone_hpf_text); + +static const DECLARE_TLV_DB_SCALE(aif_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0); +static const DECLARE_TLV_DB_SCALE(wm8994_3d_tlv, -1600, 183, 0); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); + +#define WM8994_DRC_SWITCH(xname, reg, shift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\ + .put = wm8994_put_drc_sw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, 1, 0) } + +static int wm8994_put_drc_sw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int mask, ret; + + /* Can't enable both ADC and DAC paths simultaneously */ + if (mc->shift == WM8994_AIF1DAC1_DRC_ENA_SHIFT) + mask = WM8994_AIF1ADC1L_DRC_ENA_MASK | + WM8994_AIF1ADC1R_DRC_ENA_MASK; + else + mask = WM8994_AIF1DAC1_DRC_ENA_MASK; + + ret = snd_soc_read(codec, mc->reg); + if (ret < 0) + return ret; + if (ret & mask) + return -EINVAL; + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + + + +static void wm8994_set_drc(struct snd_soc_codec *codec, int drc) +{ + struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_pdata *pdata = wm8994->pdata; + int base = wm8994_drc_base[drc]; + int cfg = wm8994->drc_cfg[drc]; + int save, i; + + /* Save any enables; the configuration should clear them. */ + save = snd_soc_read(codec, base); + save &= WM8994_AIF1DAC1_DRC_ENA | WM8994_AIF1ADC1L_DRC_ENA | + WM8994_AIF1ADC1R_DRC_ENA; + + for (i = 0; i < WM8994_DRC_REGS; i++) + snd_soc_update_bits(codec, base + i, 0xffff, + pdata->drc_cfgs[cfg].regs[i]); + + snd_soc_update_bits(codec, base, WM8994_AIF1DAC1_DRC_ENA | + WM8994_AIF1ADC1L_DRC_ENA | + WM8994_AIF1ADC1R_DRC_ENA, save); +} + +/* Icky as hell but saves code duplication */ +static int wm8994_get_drc(const char *name) +{ + if (strcmp(name, "AIF1DRC1 Mode") == 0) + return 0; + if (strcmp(name, "AIF1DRC2 Mode") == 0) + return 1; + if (strcmp(name, "AIF2DRC Mode") == 0) + return 2; + return -EINVAL; +} + +static int wm8994_put_drc_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_pdata *pdata = wm8994->pdata; + int drc = wm8994_get_drc(kcontrol->id.name); + int value = ucontrol->value.integer.value[0]; + + if (drc < 0) + return drc; + + if (value >= pdata->num_drc_cfgs) + return -EINVAL; + + wm8994->drc_cfg[drc] = value; + + wm8994_set_drc(codec, drc); + + return 0; +} + +static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8994_priv *wm8994 = codec->private_data; + int drc = wm8994_get_drc(kcontrol->id.name); + + ucontrol->value.enumerated.item[0] = wm8994->drc_cfg[drc]; + + return 0; +} + +static void wm8994_set_retune_mobile(struct snd_soc_codec *codec, int block) +{ + struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_pdata *pdata = wm8994->pdata; + int base = wm8994_retune_mobile_base[block]; + int iface, best, best_val, save, i, cfg; + + if (!pdata || !wm8994->num_retune_mobile_texts) + return; + + switch (block) { + case 0: + case 1: + iface = 0; + break; + case 2: + iface = 1; + break; + default: + return; + } + + /* Find the version of the currently selected configuration + * with the nearest sample rate. */ + cfg = wm8994->retune_mobile_cfg[block]; + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { + if (strcmp(pdata->retune_mobile_cfgs[i].name, + wm8994->retune_mobile_texts[cfg]) == 0 && + abs(pdata->retune_mobile_cfgs[i].rate + - wm8994->dac_rates[iface]) < best_val) { + best = i; + best_val = abs(pdata->retune_mobile_cfgs[i].rate + - wm8994->dac_rates[iface]); + } + } + + dev_dbg(codec->dev, "ReTune Mobile %d %s/%dHz for %dHz sample rate\n", + block, + pdata->retune_mobile_cfgs[best].name, + pdata->retune_mobile_cfgs[best].rate, + wm8994->dac_rates[iface]); + + /* The EQ will be disabled while reconfiguring it, remember the + * current configuration. + */ + save = snd_soc_read(codec, base); + save &= WM8994_AIF1DAC1_EQ_ENA; + + for (i = 0; i < WM8994_EQ_REGS; i++) + snd_soc_update_bits(codec, base + i, 0xffff, + pdata->retune_mobile_cfgs[best].regs[i]); + + snd_soc_update_bits(codec, base, WM8994_AIF1DAC1_EQ_ENA, save); +} + +/* Icky as hell but saves code duplication */ +static int wm8994_get_retune_mobile_block(const char *name) +{ + if (strcmp(name, "AIF1.1 EQ Mode") == 0) + return 0; + if (strcmp(name, "AIF1.2 EQ Mode") == 0) + return 1; + if (strcmp(name, "AIF2 EQ Mode") == 0) + return 2; + return -EINVAL; +} + +static int wm8994_put_retune_mobile_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8994_priv *wm8994 = codec->private_data; + struct wm8994_pdata *pdata = wm8994->pdata; + int block = wm8994_get_retune_mobile_block(kcontrol->id.name); + int value = ucontrol->value.integer.value[0]; + + if (block < 0) + return block; + + if (value >= pdata->num_retune_mobile_cfgs) + return -EINVAL; + + wm8994->retune_mobile_cfg[block] = value; + + wm8994_set_retune_mobile(codec, block); + + return 0; +} + +static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8994_priv *wm8994 = codec->private_data; + int block = wm8994_get_retune_mobile_block(kcontrol->id.name); + + ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block]; + + return 0; +} + +static const struct snd_kcontrol_new wm8994_snd_controls[] = { +SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8994_AIF1_ADC1_LEFT_VOLUME, + WM8994_AIF1_ADC1_RIGHT_VOLUME, + 1, 119, 0, digital_tlv), +SOC_DOUBLE_R_TLV("AIF1ADC2 Volume", WM8994_AIF1_ADC2_LEFT_VOLUME, + WM8994_AIF1_ADC2_RIGHT_VOLUME, + 1, 119, 0, digital_tlv), +SOC_DOUBLE_R_TLV("AIF2ADC Volume", WM8994_AIF2_ADC_LEFT_VOLUME, + WM8994_AIF2_ADC_RIGHT_VOLUME, + 1, 119, 0, digital_tlv), + +SOC_DOUBLE_R_TLV("AIF1DAC1 Volume", WM8994_AIF1_DAC1_LEFT_VOLUME, + WM8994_AIF1_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv), +SOC_DOUBLE_R_TLV("AIF1DAC2 Volume", WM8994_AIF1_DAC2_LEFT_VOLUME, + WM8994_AIF1_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv), +SOC_DOUBLE_R_TLV("AIF2DAC Volume", WM8994_AIF2_DAC_LEFT_VOLUME, + WM8994_AIF2_DAC_RIGHT_VOLUME, 1, 96, 0, digital_tlv), + +SOC_SINGLE_TLV("AIF1 Boost Volume", WM8994_AIF1_CONTROL_2, 10, 3, 0, aif_tlv), +SOC_SINGLE_TLV("AIF2 Boost Volume", WM8994_AIF2_CONTROL_2, 10, 3, 0, aif_tlv), + +SOC_SINGLE("AIF1DAC1 EQ Switch", WM8994_AIF1_DAC1_EQ_GAINS_1, 0, 1, 0), +SOC_SINGLE("AIF1DAC2 EQ Switch", WM8994_AIF1_DAC2_EQ_GAINS_1, 0, 1, 0), +SOC_SINGLE("AIF2 EQ Switch", WM8994_AIF2_EQ_GAINS_1, 0, 1, 0), + +WM8994_DRC_SWITCH("AIF1DAC1 DRC Switch", WM8994_AIF1_DRC1_1, 2), +WM8994_DRC_SWITCH("AIF1ADC1L DRC Switch", WM8994_AIF1_DRC1_1, 1), +WM8994_DRC_SWITCH("AIF1ADC1R DRC Switch", WM8994_AIF1_DRC1_1, 0), + +WM8994_DRC_SWITCH("AIF1DAC2 DRC Switch", WM8994_AIF1_DRC2_1, 2), +WM8994_DRC_SWITCH("AIF1ADC2L DRC Switch", WM8994_AIF1_DRC2_1, 1), +WM8994_DRC_SWITCH("AIF1ADC2R DRC Switch", WM8994_AIF1_DRC2_1, 0), + +WM8994_DRC_SWITCH("AIF2DAC DRC Switch", WM8994_AIF2_DRC_1, 2), +WM8994_DRC_SWITCH("AIF2ADCL DRC Switch", WM8994_AIF2_DRC_1, 1), +WM8994_DRC_SWITCH("AIF2ADCR DRC Switch", WM8994_AIF2_DRC_1, 0), + +SOC_SINGLE_TLV("DAC1 Right Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES, + 5, 12, 0, st_tlv), +SOC_SINGLE_TLV("DAC1 Left Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES, + 0, 12, 0, st_tlv), +SOC_SINGLE_TLV("DAC2 Right Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES, + 5, 12, 0, st_tlv), +SOC_SINGLE_TLV("DAC2 Left Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES, + 0, 12, 0, st_tlv), +SOC_ENUM("Sidetone HPF Mux", sidetone_hpf), +SOC_SINGLE("Sidetone HPF Switch", WM8994_SIDETONE, 6, 1, 0), + +SOC_DOUBLE_R_TLV("DAC1 Volume", WM8994_DAC1_LEFT_VOLUME, + WM8994_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv), +SOC_DOUBLE_R("DAC1 Switch", WM8994_DAC1_LEFT_VOLUME, + WM8994_DAC1_RIGHT_VOLUME, 9, 1, 1), + +SOC_DOUBLE_R_TLV("DAC2 Volume", WM8994_DAC2_LEFT_VOLUME, + WM8994_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv), +SOC_DOUBLE_R("DAC2 Switch", WM8994_DAC2_LEFT_VOLUME, + WM8994_DAC2_RIGHT_VOLUME, 9, 1, 1), + +SOC_SINGLE_TLV("SPKL DAC2 Volume", WM8994_SPKMIXL_ATTENUATION, + 6, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKL DAC1 Volume", WM8994_SPKMIXL_ATTENUATION, + 2, 1, 1, wm_hubs_spkmix_tlv), + +SOC_SINGLE_TLV("SPKR DAC2 Volume", WM8994_SPKMIXR_ATTENUATION, + 6, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKR DAC1 Volume", WM8994_SPKMIXR_ATTENUATION, + 2, 1, 1, wm_hubs_spkmix_tlv), + +SOC_SINGLE_TLV("AIF1DAC1 3D Stereo Volume", WM8994_AIF1_DAC1_FILTERS_2, + 10, 15, 0, wm8994_3d_tlv), +SOC_SINGLE("AIF1DAC1 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2, + 8, 1, 0), +SOC_SINGLE_TLV("AIF1DAC2 3D Stereo Volume", WM8994_AIF1_DAC2_FILTERS_2, + 10, 15, 0, wm8994_3d_tlv), +SOC_SINGLE("AIF1DAC2 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2, + 8, 1, 0), +SOC_SINGLE_TLV("AIF2DAC 3D Stereo Volume", WM8994_AIF1_DAC1_FILTERS_2, + 10, 15, 0, wm8994_3d_tlv), +SOC_SINGLE("AIF2DAC 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2, + 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8994_eq_controls[] = { +SOC_SINGLE_TLV("AIF1DAC1 EQ1 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC1 EQ2 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 6, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC1 EQ3 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 1, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC1 EQ4 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC1 EQ5 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 6, 31, 0, + eq_tlv), + +SOC_SINGLE_TLV("AIF1DAC2 EQ1 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC2 EQ2 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 6, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC2 EQ3 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 1, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC2 EQ4 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF1DAC2 EQ5 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 6, 31, 0, + eq_tlv), + +SOC_SINGLE_TLV("AIF2 EQ1 Volume", WM8994_AIF2_EQ_GAINS_1, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF2 EQ2 Volume", WM8994_AIF2_EQ_GAINS_1, 6, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF2 EQ3 Volume", WM8994_AIF2_EQ_GAINS_1, 1, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF2 EQ4 Volume", WM8994_AIF2_EQ_GAINS_2, 11, 31, 0, + eq_tlv), +SOC_SINGLE_TLV("AIF2 EQ5 Volume", WM8994_AIF2_EQ_GAINS_2, 6, 31, 0, + eq_tlv), +}; + +static int clk_sys_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return configure_clock(codec); + + case SND_SOC_DAPM_POST_PMD: + configure_clock(codec); + break; + } + + return 0; +} + +static void wm8994_update_class_w(struct snd_soc_codec *codec) +{ + int enable = 1; + int source = 0; /* GCC flow analysis can't track enable */ + int reg, reg_r; + + /* Only support direct DAC->headphone paths */ + reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1); + if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) { + dev_dbg(codec->dev, "HPL connected to output mixer\n"); + enable = 0; + } + + reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2); + if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) { + dev_dbg(codec->dev, "HPR connected to output mixer\n"); + enable = 0; + } + + /* We also need the same setting for L/R and only one path */ + reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING); + switch (reg) { + case WM8994_AIF2DACL_TO_DAC1L: + dev_dbg(codec->dev, "Class W source AIF2DAC\n"); + source = 2 << WM8994_CP_DYN_SRC_SEL_SHIFT; + break; + case WM8994_AIF1DAC2L_TO_DAC1L: + dev_dbg(codec->dev, "Class W source AIF1DAC2\n"); + source = 1 << WM8994_CP_DYN_SRC_SEL_SHIFT; + break; + case WM8994_AIF1DAC1L_TO_DAC1L: + dev_dbg(codec->dev, "Class W source AIF1DAC1\n"); + source = 0 << WM8994_CP_DYN_SRC_SEL_SHIFT; + break; + default: + dev_dbg(codec->dev, "DAC mixer setting: %x\n", reg); + enable = 0; + break; + } + + reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING); + if (reg_r != reg) { + dev_dbg(codec->dev, "Left and right DAC mixers different\n"); + enable = 0; + } + + if (enable) { + dev_dbg(codec->dev, "Class W enabled\n"); + snd_soc_update_bits(codec, WM8994_CLASS_W_1, + WM8994_CP_DYN_PWR | + WM8994_CP_DYN_SRC_SEL_MASK, + source | WM8994_CP_DYN_PWR); + + } else { + dev_dbg(codec->dev, "Class W disabled\n"); + snd_soc_update_bits(codec, WM8994_CLASS_W_1, + WM8994_CP_DYN_PWR, 0); + } +} + +static const char *hp_mux_text[] = { + "Mixer", + "DAC", +}; + +#define WM8994_HP_ENUM(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_enum_double, \ + .get = snd_soc_dapm_get_enum_double, \ + .put = wm8994_put_hp_enum, \ + .private_value = (unsigned long)&xenum } + +static int wm8994_put_hp_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = w->codec; + int ret; + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + wm8994_update_class_w(codec); + + return ret; +} + +static const struct soc_enum hpl_enum = + SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_1, 8, 2, hp_mux_text); + +static const struct snd_kcontrol_new hpl_mux = + WM8994_HP_ENUM("Left Headphone Mux", hpl_enum); + +static const struct soc_enum hpr_enum = + SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_2, 8, 2, hp_mux_text); + +static const struct snd_kcontrol_new hpr_mux = + WM8994_HP_ENUM("Right Headphone Mux", hpr_enum); + +static const char *adc_mux_text[] = { + "ADC", + "DMIC", +}; + +static const struct soc_enum adc_enum = + SOC_ENUM_SINGLE(0, 0, 2, adc_mux_text); + +static const struct snd_kcontrol_new adcl_mux = + SOC_DAPM_ENUM_VIRT("ADCL Mux", adc_enum); + +static const struct snd_kcontrol_new adcr_mux = + SOC_DAPM_ENUM_VIRT("ADCR Mux", adc_enum); + +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0), +SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0), +SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 8, 1, 0), +SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 6, 1, 0), +SOC_DAPM_SINGLE("IN1RP Switch", WM8994_SPEAKER_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 0, 1, 0), +}; + +/* Debugging; dump chip status after DAPM transitions */ +static int post_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + dev_dbg(codec->dev, "SRC status: %x\n", + snd_soc_read(codec, + WM8994_RATE_STATUS)); + return 0; +} + +static const struct snd_kcontrol_new aif1adc1l_mix[] = { +SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif1adc1r_mix[] = { +SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif2dac2l_mix[] = { +SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 5, 1, 0), +SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 4, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 2, 1, 0), +SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new aif2dac2r_mix[] = { +SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 5, 1, 0), +SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 4, 1, 0), +SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 2, 1, 0), +SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 1, 1, 0), +SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +#define WM8994_CLASS_W_SWITCH(xname, reg, shift, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw, .put = wm8994_put_class_w, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + +static int wm8994_put_class_w(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *w = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = w->codec; + int ret; + + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + wm8994_update_class_w(codec); + + return ret; +} + +static const struct snd_kcontrol_new dac1l_mix[] = { +WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 5, 1, 0), +WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 4, 1, 0), +WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 2, 1, 0), +WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 1, 1, 0), +WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const struct snd_kcontrol_new dac1r_mix[] = { +WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 5, 1, 0), +WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 4, 1, 0), +WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 2, 1, 0), +WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 1, 1, 0), +WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING, + 0, 1, 0), +}; + +static const char *sidetone_text[] = { + "ADC/DMIC1", "DMIC2", +}; + +static const struct soc_enum sidetone1_enum = + SOC_ENUM_SINGLE(WM8994_SIDETONE, 0, 2, sidetone_text); + +static const struct snd_kcontrol_new sidetone1_mux = + SOC_DAPM_ENUM("Left Sidetone Mux", sidetone1_enum); + +static const struct soc_enum sidetone2_enum = + SOC_ENUM_SINGLE(WM8994_SIDETONE, 1, 2, sidetone_text); + +static const struct snd_kcontrol_new sidetone2_mux = + SOC_DAPM_ENUM("Right Sidetone Mux", sidetone2_enum); + +static const char *aif1dac_text[] = { + "AIF1DACDAT", "AIF3DACDAT", +}; + +static const struct soc_enum aif1dac_enum = + SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 0, 2, aif1dac_text); + +static const struct snd_kcontrol_new aif1dac_mux = + SOC_DAPM_ENUM("AIF1DAC Mux", aif1dac_enum); + +static const char *aif2dac_text[] = { + "AIF2DACDAT", "AIF3DACDAT", +}; + +static const struct soc_enum aif2dac_enum = + SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 1, 2, aif2dac_text); + +static const struct snd_kcontrol_new aif2dac_mux = + SOC_DAPM_ENUM("AIF2DAC Mux", aif2dac_enum); + +static const char *aif2adc_text[] = { + "AIF2ADCDAT", "AIF3DACDAT", +}; + +static const struct soc_enum aif2adc_enum = + SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 2, 2, aif2adc_text); + +static const struct snd_kcontrol_new aif2adc_mux = + SOC_DAPM_ENUM("AIF2ADC Mux", aif2adc_enum); + +static const char *aif3adc_text[] = { + "AIF1ADCDAT", "AIF2ADCDAT", "AIF2DACDAT", +}; + +static const struct soc_enum aif3adc_enum = + SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 3, 3, aif3adc_text); + +static const struct snd_kcontrol_new aif3adc_mux = + SOC_DAPM_ENUM("AIF3ADC Mux", aif3adc_enum); + +static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("DMIC1DAT"), +SND_SOC_DAPM_INPUT("DMIC2DAT"), + +SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_SUPPLY("DSP1CLK", WM8994_CLOCKING_1, 3, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("DSP2CLK", WM8994_CLOCKING_1, 2, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("DSPINTCLK", WM8994_CLOCKING_1, 1, 0, NULL, 0), + +SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0), + +SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", "AIF1 Capture", + 0, WM8994_POWER_MANAGEMENT_4, 9, 0), +SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", "AIF1 Capture", + 0, WM8994_POWER_MANAGEMENT_4, 8, 0), +SND_SOC_DAPM_AIF_IN("AIF1DAC1L", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 9, 0), +SND_SOC_DAPM_AIF_IN("AIF1DAC1R", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 8, 0), + +SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", "AIF1 Capture", + 0, WM8994_POWER_MANAGEMENT_4, 11, 0), +SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", "AIF1 Capture", + 0, WM8994_POWER_MANAGEMENT_4, 10, 0), +SND_SOC_DAPM_AIF_IN("AIF1DAC2L", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 11, 0), +SND_SOC_DAPM_AIF_IN("AIF1DAC2R", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 10, 0), + +SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0, + aif1adc1l_mix, ARRAY_SIZE(aif1adc1l_mix)), +SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0, + aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)), + +SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0, + aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)), +SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0, + aif2dac2r_mix, ARRAY_SIZE(aif2dac2r_mix)), + +SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &sidetone1_mux), +SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &sidetone2_mux), + +SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0, + dac1l_mix, ARRAY_SIZE(dac1l_mix)), +SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0, + dac1r_mix, ARRAY_SIZE(dac1r_mix)), + +SND_SOC_DAPM_AIF_OUT("AIF2ADCL", NULL, 0, + WM8994_POWER_MANAGEMENT_4, 13, 0), +SND_SOC_DAPM_AIF_OUT("AIF2ADCR", NULL, 0, + WM8994_POWER_MANAGEMENT_4, 12, 0), +SND_SOC_DAPM_AIF_IN("AIF2DACL", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 13, 0), +SND_SOC_DAPM_AIF_IN("AIF2DACR", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 12, 0), + +SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIF2DACDAT", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIF2ADCDAT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("AIF1DAC Mux", SND_SOC_NOPM, 0, 0, &aif1dac_mux), +SND_SOC_DAPM_MUX("AIF2DAC Mux", SND_SOC_NOPM, 0, 0, &aif2dac_mux), +SND_SOC_DAPM_MUX("AIF2ADC Mux", SND_SOC_NOPM, 0, 0, &aif2adc_mux), +SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &aif3adc_mux), + +SND_SOC_DAPM_AIF_IN("AIF3DACDAT", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIF3ADCDAT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_SUPPLY("TOCLK", WM8994_CLOCKING_1, 4, 0, NULL, 0), + +SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8994_POWER_MANAGEMENT_4, 5, 0), +SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8994_POWER_MANAGEMENT_4, 4, 0), +SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8994_POWER_MANAGEMENT_4, 3, 0), +SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0), + +/* Power is done with the muxes since the ADC power also controls the + * downsampling chain, the chip will automatically manage the analogue + * specific portions. + */ +SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux), +SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux), + +SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0), +SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0), +SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0), +SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0), + +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), + +SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), + +SND_SOC_DAPM_POST("Debug log", post_ev), +}; + +static const struct snd_soc_dapm_route intercon[] = { + + { "CLK_SYS", NULL, "AIF1CLK", check_clk_sys }, + { "CLK_SYS", NULL, "AIF2CLK", check_clk_sys }, + + { "DSP1CLK", NULL, "CLK_SYS" }, + { "DSP2CLK", NULL, "CLK_SYS" }, + { "DSPINTCLK", NULL, "CLK_SYS" }, + + { "AIF1ADC1L", NULL, "AIF1CLK" }, + { "AIF1ADC1L", NULL, "DSP1CLK" }, + { "AIF1ADC1R", NULL, "AIF1CLK" }, + { "AIF1ADC1R", NULL, "DSP1CLK" }, + { "AIF1ADC1R", NULL, "DSPINTCLK" }, + + { "AIF1DAC1L", NULL, "AIF1CLK" }, + { "AIF1DAC1L", NULL, "DSP1CLK" }, + { "AIF1DAC1R", NULL, "AIF1CLK" }, + { "AIF1DAC1R", NULL, "DSP1CLK" }, + { "AIF1DAC1R", NULL, "DSPINTCLK" }, + + { "AIF1ADC2L", NULL, "AIF1CLK" }, + { "AIF1ADC2L", NULL, "DSP1CLK" }, + { "AIF1ADC2R", NULL, "AIF1CLK" }, + { "AIF1ADC2R", NULL, "DSP1CLK" }, + { "AIF1ADC2R", NULL, "DSPINTCLK" }, + + { "AIF1DAC2L", NULL, "AIF1CLK" }, + { "AIF1DAC2L", NULL, "DSP1CLK" }, + { "AIF1DAC2R", NULL, "AIF1CLK" }, + { "AIF1DAC2R", NULL, "DSP1CLK" }, + { "AIF1DAC2R", NULL, "DSPINTCLK" }, + + { "AIF2ADCL", NULL, "AIF2CLK" }, + { "AIF2ADCL", NULL, "DSP2CLK" }, + { "AIF2ADCR", NULL, "AIF2CLK" }, + { "AIF2ADCR", NULL, "DSP2CLK" }, + { "AIF2ADCR", NULL, "DSPINTCLK" }, + + { "AIF2DACL", NULL, "AIF2CLK" }, + { "AIF2DACL", NULL, "DSP2CLK" }, + { "AIF2DACR", NULL, "AIF2CLK" }, + { "AIF2DACR", NULL, "DSP2CLK" }, + { "AIF2DACR", NULL, "DSPINTCLK" }, + + { "DMIC1L", NULL, "DMIC1DAT" }, + { "DMIC1L", NULL, "CLK_SYS" }, + { "DMIC1R", NULL, "DMIC1DAT" }, + { "DMIC1R", NULL, "CLK_SYS" }, + { "DMIC2L", NULL, "DMIC2DAT" }, + { "DMIC2L", NULL, "CLK_SYS" }, + { "DMIC2R", NULL, "DMIC2DAT" }, + { "DMIC2R", NULL, "CLK_SYS" }, + + { "ADCL", NULL, "AIF1CLK" }, + { "ADCL", NULL, "DSP1CLK" }, + { "ADCL", NULL, "DSPINTCLK" }, + + { "ADCR", NULL, "AIF1CLK" }, + { "ADCR", NULL, "DSP1CLK" }, + { "ADCR", NULL, "DSPINTCLK" }, + + { "ADCL Mux", "ADC", "ADCL" }, + { "ADCL Mux", "DMIC", "DMIC1L" }, + { "ADCR Mux", "ADC", "ADCR" }, + { "ADCR Mux", "DMIC", "DMIC1R" }, + + { "DAC1L", NULL, "AIF1CLK" }, + { "DAC1L", NULL, "DSP1CLK" }, + { "DAC1L", NULL, "DSPINTCLK" }, + + { "DAC1R", NULL, "AIF1CLK" }, + { "DAC1R", NULL, "DSP1CLK" }, + { "DAC1R", NULL, "DSPINTCLK" }, + + { "DAC2L", NULL, "AIF2CLK" }, + { "DAC2L", NULL, "DSP2CLK" }, + { "DAC2L", NULL, "DSPINTCLK" }, + + { "DAC2R", NULL, "AIF2DACR" }, + { "DAC2R", NULL, "AIF2CLK" }, + { "DAC2R", NULL, "DSP2CLK" }, + { "DAC2R", NULL, "DSPINTCLK" }, + + { "TOCLK", NULL, "CLK_SYS" }, + + /* AIF1 outputs */ + { "AIF1ADC1L", NULL, "AIF1ADC1L Mixer" }, + { "AIF1ADC1L Mixer", "ADC/DMIC Switch", "ADCL Mux" }, + { "AIF1ADC1L Mixer", "AIF2 Switch", "AIF2DACL" }, + + { "AIF1ADC1R", NULL, "AIF1ADC1R Mixer" }, + { "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" }, + { "AIF1ADC1R Mixer", "AIF2 Switch", "AIF2DACR" }, + + /* Pin level routing for AIF3 */ + { "AIF1DAC1L", NULL, "AIF1DAC Mux" }, + { "AIF1DAC1R", NULL, "AIF1DAC Mux" }, + { "AIF1DAC2L", NULL, "AIF1DAC Mux" }, + { "AIF1DAC2R", NULL, "AIF1DAC Mux" }, + + { "AIF2DACL", NULL, "AIF2DAC Mux" }, + { "AIF2DACR", NULL, "AIF2DAC Mux" }, + + { "AIF1DAC Mux", "AIF1DACDAT", "AIF1DACDAT" }, + { "AIF1DAC Mux", "AIF3DACDAT", "AIF3DACDAT" }, + { "AIF2DAC Mux", "AIF2DACDAT", "AIF2DACDAT" }, + { "AIF2DAC Mux", "AIF3DACDAT", "AIF3DACDAT" }, + { "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCL" }, + { "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCR" }, + { "AIF2ADC Mux", "AIF3DACDAT", "AIF3ADCDAT" }, + + /* DAC1 inputs */ + { "DAC1L", NULL, "DAC1L Mixer" }, + { "DAC1L Mixer", "AIF2 Switch", "AIF2DACL" }, + { "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, + { "DAC1L Mixer", "AIF1.1 Switch", "AIF1DAC1L" }, + { "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + { "DAC1R", NULL, "DAC1R Mixer" }, + { "DAC1R Mixer", "AIF2 Switch", "AIF2DACR" }, + { "DAC1R Mixer", "AIF1.2 Switch", "AIF1DAC2R" }, + { "DAC1R Mixer", "AIF1.1 Switch", "AIF1DAC1R" }, + { "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + /* DAC2/AIF2 outputs */ + { "AIF2ADCL", NULL, "AIF2DAC2L Mixer" }, + { "DAC2L", NULL, "AIF2DAC2L Mixer" }, + { "AIF2DAC2L Mixer", "AIF2 Switch", "AIF2DACL" }, + { "AIF2DAC2L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, + { "AIF2DAC2L Mixer", "AIF1.1 Switch", "AIF1DAC1L" }, + { "AIF2DAC2L Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "AIF2DAC2L Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + { "AIF2ADCR", NULL, "AIF2DAC2R Mixer" }, + { "DAC2R", NULL, "AIF2DAC2R Mixer" }, + { "AIF2DAC2R Mixer", "AIF2 Switch", "AIF2DACR" }, + { "AIF2DAC2R Mixer", "AIF1.2 Switch", "AIF1DAC2R" }, + { "AIF2DAC2R Mixer", "AIF1.1 Switch", "AIF1DAC1R" }, + { "AIF2DAC2R Mixer", "Left Sidetone Switch", "Left Sidetone" }, + { "AIF2DAC2R Mixer", "Right Sidetone Switch", "Right Sidetone" }, + + { "AIF2ADCDAT", NULL, "AIF2ADC Mux" }, + + /* AIF3 output */ + { "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC1L" }, + { "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC1R" }, + { "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC2L" }, + { "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC2R" }, + { "AIF3ADCDAT", "AIF2ADCDAT", "AIF2ADCL" }, + { "AIF3ADCDAT", "AIF2ADCDAT", "AIF2ADCR" }, + { "AIF3ADCDAT", "AIF2DACDAT", "AIF2DACL" }, + { "AIF3ADCDAT", "AIF2DACDAT", "AIF2DACR" }, + + /* Sidetone */ + { "Left Sidetone", "ADC/DMIC1", "ADCL Mux" }, + { "Left Sidetone", "DMIC2", "DMIC2L" }, + { "Right Sidetone", "ADC/DMIC1", "ADCR Mux" }, + { "Right Sidetone", "DMIC2", "DMIC2R" }, + + /* Output stages */ + { "Left Output Mixer", "DAC Switch", "DAC1L" }, + { "Right Output Mixer", "DAC Switch", "DAC1R" }, + + { "SPKL", "DAC1 Switch", "DAC1L" }, + { "SPKL", "DAC2 Switch", "DAC2L" }, + + { "SPKR", "DAC1 Switch", "DAC1R" }, + { "SPKR", "DAC2 Switch", "DAC2R" }, + + { "Left Headphone Mux", "DAC", "DAC1L" }, + { "Right Headphone Mux", "DAC", "DAC1R" }, +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +struct fll_div { + u16 outdiv; + u16 n; + u16 k; + u16 clk_ref_div; + u16 fll_fratio; +}; + +static int wm8994_get_fll_config(struct fll_div *fll, + int freq_in, int freq_out) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + pr_debug("FLL input=%dHz, output=%dHz\n", freq_in, freq_out); + + /* Scale the input frequency down to <= 13.5MHz */ + fll->clk_ref_div = 0; + while (freq_in > 13500000) { + fll->clk_ref_div++; + freq_in /= 2; + + if (fll->clk_ref_div > 3) + return -EINVAL; + } + pr_debug("CLK_REF_DIV=%d, Fref=%dHz\n", fll->clk_ref_div, freq_in); + + /* Scale the output to give 90MHz<=Fvco<=100MHz */ + fll->outdiv = 3; + while (freq_out * (fll->outdiv + 1) < 90000000) { + fll->outdiv++; + if (fll->outdiv > 63) + return -EINVAL; + } + freq_out *= fll->outdiv + 1; + pr_debug("OUTDIV=%d, Fvco=%dHz\n", fll->outdiv, freq_out); + + if (freq_in > 1000000) { + fll->fll_fratio = 0; + } else { + fll->fll_fratio = 3; + freq_in *= 8; + } + pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in); + + /* Now, calculate N.K */ + Ndiv = freq_out / freq_in; + + fll->n = Ndiv; + Nmod = freq_out % freq_in; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, freq_in); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll->k = K / 10; + + pr_debug("N=%x K=%x\n", fll->n, fll->k); + + return 0; +} + +static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, + unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8994_priv *wm8994 = codec->private_data; + int reg_offset, ret; + struct fll_div fll; + u16 reg, aif1, aif2; + + aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1) + & WM8994_AIF1CLK_ENA; + + aif2 = snd_soc_read(codec, WM8994_AIF2_CLOCKING_1) + & WM8994_AIF2CLK_ENA; + + switch (id) { + case WM8994_FLL1: + reg_offset = 0; + id = 0; + break; + case WM8994_FLL2: + reg_offset = 0x20; + id = 1; + break; + default: + return -EINVAL; + } + + /* Are we changing anything? */ + if (wm8994->fll[id].src == src && + wm8994->fll[id].in == freq_in && wm8994->fll[id].out == freq_out) + return 0; + + /* If we're stopping the FLL redo the old config - no + * registers will actually be written but we avoid GCC flow + * analysis bugs spewing warnings. + */ + if (freq_out) + ret = wm8994_get_fll_config(&fll, freq_in, freq_out); + else + ret = wm8994_get_fll_config(&fll, wm8994->fll[id].in, + wm8994->fll[id].out); + if (ret < 0) + return ret; + + /* Gate the AIF clocks while we reclock */ + snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, + WM8994_AIF1CLK_ENA, 0); + snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1, + WM8994_AIF2CLK_ENA, 0); + + /* We always need to disable the FLL while reconfiguring */ + snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset, + WM8994_FLL1_ENA, 0); + + reg = (fll.outdiv << WM8994_FLL1_OUTDIV_SHIFT) | + (fll.fll_fratio << WM8994_FLL1_FRATIO_SHIFT); + snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_2 + reg_offset, + WM8994_FLL1_OUTDIV_MASK | + WM8994_FLL1_FRATIO_MASK, reg); + + snd_soc_write(codec, WM8994_FLL1_CONTROL_3 + reg_offset, fll.k); + + snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_4 + reg_offset, + WM8994_FLL1_N_MASK, + fll.n << WM8994_FLL1_N_SHIFT); + + snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset, + WM8994_FLL1_REFCLK_DIV_MASK, + fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT); + + /* Enable (with fractional mode if required) */ + if (freq_out) { + if (fll.k) + reg = WM8994_FLL1_ENA | WM8994_FLL1_FRAC; + else + reg = WM8994_FLL1_ENA; + snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset, + WM8994_FLL1_ENA | WM8994_FLL1_FRAC, + reg); + } + + wm8994->fll[id].in = freq_in; + wm8994->fll[id].out = freq_out; + + /* Enable any gated AIF clocks */ + snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, + WM8994_AIF1CLK_ENA, aif1); + snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1, + WM8994_AIF2CLK_ENA, aif2); + + configure_clock(codec); + + return 0; +} + +static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8994_priv *wm8994 = codec->private_data; + + switch (dai->id) { + case 1: + case 2: + break; + + default: + /* AIF3 shares clocking with AIF1/2 */ + return -EINVAL; + } + + switch (clk_id) { + case WM8994_SYSCLK_MCLK1: + wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK1; + wm8994->mclk[0] = freq; + dev_dbg(dai->dev, "AIF%d using MCLK1 at %uHz\n", + dai->id, freq); + break; + + case WM8994_SYSCLK_MCLK2: + /* TODO: Set GPIO AF */ + wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK2; + wm8994->mclk[1] = freq; + dev_dbg(dai->dev, "AIF%d using MCLK2 at %uHz\n", + dai->id, freq); + break; + + case WM8994_SYSCLK_FLL1: + wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL1; + dev_dbg(dai->dev, "AIF%d using FLL1\n", dai->id); + break; + + case WM8994_SYSCLK_FLL2: + wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL2; + dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id); + break; + + default: + return -EINVAL; + } + + configure_clock(codec); + + return 0; +} + +static int wm8994_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2x40k */ + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, + WM8994_VMID_SEL_MASK, 0x2); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Tweak DC servo configuration for improved + * performance. */ + snd_soc_write(codec, 0x102, 0x3); + snd_soc_write(codec, 0x56, 0x3); + snd_soc_write(codec, 0x102, 0); + + /* Discharge LINEOUT1 & 2 */ + snd_soc_update_bits(codec, WM8994_ANTIPOP_1, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH); + + /* Startup bias, VMID ramp & buffer */ + snd_soc_update_bits(codec, WM8994_ANTIPOP_2, + WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + WM8994_VMID_RAMP_MASK, + WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + (0x11 << WM8994_VMID_RAMP_SHIFT)); + + /* Main bias enable, VMID=2x40k */ + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, + WM8994_BIAS_ENA | + WM8994_VMID_SEL_MASK, + WM8994_BIAS_ENA | 0x2); + + msleep(20); + } + + /* VMID=2x500k */ + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, + WM8994_VMID_SEL_MASK, 0x4); + + break; + + case SND_SOC_BIAS_OFF: + /* Switch over to startup biases */ + snd_soc_update_bits(codec, WM8994_ANTIPOP_2, + WM8994_BIAS_SRC | WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + WM8994_VMID_RAMP_MASK, + WM8994_BIAS_SRC | WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + (1 << WM8994_VMID_RAMP_SHIFT)); + + /* Disable main biases */ + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, + WM8994_BIAS_ENA | WM8994_VMID_SEL_MASK, 0); + + /* Discharge line */ + snd_soc_update_bits(codec, WM8994_ANTIPOP_1, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH, + WM8994_LINEOUT1_DISCH | + WM8994_LINEOUT2_DISCH); + + msleep(5); + + /* Switch off startup biases */ + snd_soc_update_bits(codec, WM8994_ANTIPOP_2, + WM8994_BIAS_SRC | WM8994_STARTUP_BIAS_ENA | + WM8994_VMID_BUF_ENA | + WM8994_VMID_RAMP_MASK, 0); + + break; + } + codec->bias_level = level; + return 0; +} + +static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + int ms_reg; + int aif1_reg; + int ms = 0; + int aif1 = 0; + + switch (dai->id) { + case 1: + ms_reg = WM8994_AIF1_MASTER_SLAVE; + aif1_reg = WM8994_AIF1_CONTROL_1; + break; + case 2: + ms_reg = WM8994_AIF2_MASTER_SLAVE; + aif1_reg = WM8994_AIF2_CONTROL_1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ms = WM8994_AIF1_MSTR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif1 |= WM8994_AIF1_LRCLK_INV; + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x18; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 |= 0x8; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8994_AIF1_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8994_AIF1_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8994_AIF1_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, aif1_reg, + WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV | + WM8994_AIF1_FMT_MASK, + aif1); + snd_soc_update_bits(codec, ms_reg, WM8994_AIF1_MSTR, + ms); + + return 0; +} + +static struct { + int val, rate; +} srs[] = { + { 0, 8000 }, + { 1, 11025 }, + { 2, 12000 }, + { 3, 16000 }, + { 4, 22050 }, + { 5, 24000 }, + { 6, 32000 }, + { 7, 44100 }, + { 8, 48000 }, + { 9, 88200 }, + { 10, 96000 }, +}; + +static int fs_ratios[] = { + 64, 128, 192, 256, 348, 512, 768, 1024, 1408, 1536 +}; + +static int bclk_divs[] = { + 10, 15, 20, 30, 40, 50, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480, + 640, 880, 960, 1280, 1760, 1920 +}; + +static int wm8994_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8994_priv *wm8994 = codec->private_data; + int aif1_reg; + int bclk_reg; + int lrclk_reg; + int rate_reg; + int aif1 = 0; + int bclk = 0; + int lrclk = 0; + int rate_val = 0; + int id = dai->id - 1; + + int i, cur_val, best_val, bclk_rate, best; + + switch (dai->id) { + case 1: + aif1_reg = WM8994_AIF1_CONTROL_1; + bclk_reg = WM8994_AIF1_BCLK; + rate_reg = WM8994_AIF1_RATE; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + wm8994->lrclk_shared[0]) + lrclk_reg = WM8994_AIF1DAC_LRCLK; + else + lrclk_reg = WM8994_AIF1ADC_LRCLK; + break; + case 2: + aif1_reg = WM8994_AIF2_CONTROL_1; + bclk_reg = WM8994_AIF2_BCLK; + rate_reg = WM8994_AIF2_RATE; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK || + wm8994->lrclk_shared[1]) + lrclk_reg = WM8994_AIF2DAC_LRCLK; + else + lrclk_reg = WM8994_AIF2ADC_LRCLK; + break; + default: + return -EINVAL; + } + + bclk_rate = params_rate(params) * 2; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bclk_rate *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + bclk_rate *= 20; + aif1 |= 0x20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bclk_rate *= 24; + aif1 |= 0x40; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bclk_rate *= 32; + aif1 |= 0x60; + break; + default: + return -EINVAL; + } + + /* Try to find an appropriate sample rate; look for an exact match. */ + for (i = 0; i < ARRAY_SIZE(srs); i++) + if (srs[i].rate == params_rate(params)) + break; + if (i == ARRAY_SIZE(srs)) + return -EINVAL; + rate_val |= srs[i].val << WM8994_AIF1_SR_SHIFT; + + dev_dbg(dai->dev, "Sample rate is %dHz\n", srs[i].rate); + dev_dbg(dai->dev, "AIF%dCLK is %dHz, target BCLK %dHz\n", + dai->id, wm8994->aifclk[id], bclk_rate); + + if (wm8994->aifclk[id] == 0) { + dev_err(dai->dev, "AIF%dCLK not configured\n", dai->id); + return -EINVAL; + } + + /* AIFCLK/fs ratio; look for a close match in either direction */ + best = 0; + best_val = abs((fs_ratios[0] * params_rate(params)) + - wm8994->aifclk[id]); + for (i = 1; i < ARRAY_SIZE(fs_ratios); i++) { + cur_val = abs((fs_ratios[i] * params_rate(params)) + - wm8994->aifclk[id]); + if (cur_val >= best_val) + continue; + best = i; + best_val = cur_val; + } + dev_dbg(dai->dev, "Selected AIF%dCLK/fs = %d\n", + dai->id, fs_ratios[best]); + rate_val |= best; + + /* We may not get quite the right frequency if using + * approximate clocks so look for the closest match that is + * higher than the target (we need to ensure that there enough + * BCLKs to clock out the samples). + */ + best = 0; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + if (bclk_divs[i] < 0) + continue; + cur_val = (wm8994->aifclk[id] * 10 / bclk_divs[i]) + - bclk_rate * 10; + if (cur_val < 0) /* BCLK table is sorted */ + break; + best = i; + } + bclk_rate = wm8994->aifclk[id] / bclk_divs[best]; + dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n", + bclk_divs[best], bclk_rate); + bclk |= best << WM8994_AIF1_BCLK_DIV_SHIFT; + + lrclk = bclk_rate / params_rate(params); + dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n", + lrclk, bclk_rate / lrclk); + + snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1); + snd_soc_update_bits(codec, bclk_reg, WM8994_AIF1_BCLK_DIV_MASK, bclk); + snd_soc_update_bits(codec, lrclk_reg, WM8994_AIF1DAC_RATE_MASK, + lrclk); + snd_soc_update_bits(codec, rate_reg, WM8994_AIF1_SR_MASK | + WM8994_AIF1CLK_RATE_MASK, rate_val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (dai->id) { + case 1: + wm8994->dac_rates[0] = params_rate(params); + wm8994_set_retune_mobile(codec, 0); + wm8994_set_retune_mobile(codec, 1); + break; + case 2: + wm8994->dac_rates[1] = params_rate(params); + wm8994_set_retune_mobile(codec, 2); + break; + } + } + + return 0; +} + +static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int mute_reg; + int reg; + + switch (codec_dai->id) { + case 1: + mute_reg = WM8994_AIF1_DAC1_FILTERS_1; + break; + case 2: + mute_reg = WM8994_AIF2_DAC_FILTERS_1; + break; + default: + return -EINVAL; + } + + if (mute) + reg = WM8994_AIF1DAC1_MUTE; + else + reg = 0; + + snd_soc_update_bits(codec, mute_reg, WM8994_AIF1DAC1_MUTE, reg); + + return 0; +} + +#define WM8994_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8994_aif1_dai_ops = { + .set_sysclk = wm8994_set_dai_sysclk, + .set_fmt = wm8994_set_dai_fmt, + .hw_params = wm8994_hw_params, + .digital_mute = wm8994_aif_mute, + .set_pll = wm8994_set_fll, +}; + +static struct snd_soc_dai_ops wm8994_aif2_dai_ops = { + .set_sysclk = wm8994_set_dai_sysclk, + .set_fmt = wm8994_set_dai_fmt, + .hw_params = wm8994_hw_params, + .digital_mute = wm8994_aif_mute, + .set_pll = wm8994_set_fll, +}; + +struct snd_soc_dai wm8994_dai[] = { + { + .name = "WM8994 AIF1", + .id = 1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + }, + .ops = &wm8994_aif1_dai_ops, + }, + { + .name = "WM8994 AIF2", + .id = 2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + }, + .ops = &wm8994_aif2_dai_ops, + }, + { + .name = "WM8994 AIF3", + .playback = { + .stream_name = "AIF3 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + }, + .playback = { + .stream_name = "AIF3 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8994_RATES, + .formats = WM8994_FORMATS, + }, + } +}; +EXPORT_SYMBOL_GPL(wm8994_dai); + +#ifdef CONFIG_PM +static int wm8994_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8994_priv *wm8994 = codec->private_data; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { + memcpy(&wm8994->fll_suspend[i], &wm8994->fll[i], + sizeof(struct fll_config)); + ret = wm8994_set_fll(&codec->dai[0], i + 1, 0, 0, 0); + if (ret < 0) + dev_warn(codec->dev, "Failed to stop FLL%d: %d\n", + i + 1, ret); + } + + wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8994_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8994_priv *wm8994 = codec->private_data; + u16 *reg_cache = codec->reg_cache; + int i, ret; + + /* Restore the registers */ + for (i = 1; i < ARRAY_SIZE(wm8994->reg_cache); i++) { + switch (i) { + case WM8994_LDO_1: + case WM8994_LDO_2: + case WM8994_SOFTWARE_RESET: + /* Handled by other MFD drivers */ + continue; + default: + break; + } + + if (!access_masks[i].writable) + continue; + + wm8994_reg_write(codec->control_data, i, reg_cache[i]); + } + + wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) { + ret = wm8994_set_fll(&codec->dai[0], i + 1, + wm8994->fll_suspend[i].src, + wm8994->fll_suspend[i].in, + wm8994->fll_suspend[i].out); + if (ret < 0) + dev_warn(codec->dev, "Failed to restore FLL%d: %d\n", + i + 1, ret); + } + + return 0; +} +#else +#define wm8994_suspend NULL +#define wm8994_resume NULL +#endif + +static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994) +{ + struct snd_soc_codec *codec = &wm8994->codec; + struct wm8994_pdata *pdata = wm8994->pdata; + struct snd_kcontrol_new controls[] = { + SOC_ENUM_EXT("AIF1.1 EQ Mode", + wm8994->retune_mobile_enum, + wm8994_get_retune_mobile_enum, + wm8994_put_retune_mobile_enum), + SOC_ENUM_EXT("AIF1.2 EQ Mode", + wm8994->retune_mobile_enum, + wm8994_get_retune_mobile_enum, + wm8994_put_retune_mobile_enum), + SOC_ENUM_EXT("AIF2 EQ Mode", + wm8994->retune_mobile_enum, + wm8994_get_retune_mobile_enum, + wm8994_put_retune_mobile_enum), + }; + int ret, i, j; + const char **t; + + /* We need an array of texts for the enum API but the number + * of texts is likely to be less than the number of + * configurations due to the sample rate dependency of the + * configurations. */ + wm8994->num_retune_mobile_texts = 0; + wm8994->retune_mobile_texts = NULL; + for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) { + for (j = 0; j < wm8994->num_retune_mobile_texts; j++) { + if (strcmp(pdata->retune_mobile_cfgs[i].name, + wm8994->retune_mobile_texts[j]) == 0) + break; + } + + if (j != wm8994->num_retune_mobile_texts) + continue; + + /* Expand the array... */ + t = krealloc(wm8994->retune_mobile_texts, + sizeof(char *) * + (wm8994->num_retune_mobile_texts + 1), + GFP_KERNEL); + if (t == NULL) + continue; + + /* ...store the new entry... */ + t[wm8994->num_retune_mobile_texts] = + pdata->retune_mobile_cfgs[i].name; + + /* ...and remember the new version. */ + wm8994->num_retune_mobile_texts++; + wm8994->retune_mobile_texts = t; + } + + dev_dbg(codec->dev, "Allocated %d unique ReTune Mobile names\n", + wm8994->num_retune_mobile_texts); + + wm8994->retune_mobile_enum.max = wm8994->num_retune_mobile_texts; + wm8994->retune_mobile_enum.texts = wm8994->retune_mobile_texts; + + ret = snd_soc_add_controls(&wm8994->codec, controls, + ARRAY_SIZE(controls)); + if (ret != 0) + dev_err(wm8994->codec.dev, + "Failed to add ReTune Mobile controls: %d\n", ret); +} + +static void wm8994_handle_pdata(struct wm8994_priv *wm8994) +{ + struct snd_soc_codec *codec = &wm8994->codec; + struct wm8994_pdata *pdata = wm8994->pdata; + int ret, i; + + if (!pdata) + return; + + wm_hubs_handle_analogue_pdata(codec, pdata->lineout1_diff, + pdata->lineout2_diff, + pdata->lineout1fb, + pdata->lineout2fb, + pdata->jd_scthr, + pdata->jd_thr, + pdata->micbias1_lvl, + pdata->micbias2_lvl); + + dev_dbg(codec->dev, "%d DRC configurations\n", pdata->num_drc_cfgs); + + if (pdata->num_drc_cfgs) { + struct snd_kcontrol_new controls[] = { + SOC_ENUM_EXT("AIF1DRC1 Mode", wm8994->drc_enum, + wm8994_get_drc_enum, wm8994_put_drc_enum), + SOC_ENUM_EXT("AIF1DRC2 Mode", wm8994->drc_enum, + wm8994_get_drc_enum, wm8994_put_drc_enum), + SOC_ENUM_EXT("AIF2DRC Mode", wm8994->drc_enum, + wm8994_get_drc_enum, wm8994_put_drc_enum), + }; + + /* We need an array of texts for the enum API */ + wm8994->drc_texts = kmalloc(sizeof(char *) + * pdata->num_drc_cfgs, GFP_KERNEL); + if (!wm8994->drc_texts) { + dev_err(wm8994->codec.dev, + "Failed to allocate %d DRC config texts\n", + pdata->num_drc_cfgs); + return; + } + + for (i = 0; i < pdata->num_drc_cfgs; i++) + wm8994->drc_texts[i] = pdata->drc_cfgs[i].name; + + wm8994->drc_enum.max = pdata->num_drc_cfgs; + wm8994->drc_enum.texts = wm8994->drc_texts; + + ret = snd_soc_add_controls(&wm8994->codec, controls, + ARRAY_SIZE(controls)); + if (ret != 0) + dev_err(wm8994->codec.dev, + "Failed to add DRC mode controls: %d\n", ret); + + for (i = 0; i < WM8994_NUM_DRC; i++) + wm8994_set_drc(codec, i); + } + + dev_dbg(codec->dev, "%d ReTune Mobile configurations\n", + pdata->num_retune_mobile_cfgs); + + if (pdata->num_retune_mobile_cfgs) + wm8994_handle_retune_mobile_pdata(wm8994); + else + snd_soc_add_controls(&wm8994->codec, wm8994_eq_controls, + ARRAY_SIZE(wm8994_eq_controls)); +} + +static int wm8994_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8994_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8994_codec; + codec = wm8994_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + return ret; + } + + wm8994_handle_pdata(codec->private_data); + + wm_hubs_add_analogue_controls(codec); + snd_soc_add_controls(codec, wm8994_snd_controls, + ARRAY_SIZE(wm8994_snd_controls)); + snd_soc_dapm_new_controls(codec, wm8994_dapm_widgets, + ARRAY_SIZE(wm8994_dapm_widgets)); + wm_hubs_add_analogue_routes(codec, 0, 0); + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + return 0; +} + +static int wm8994_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8994 = { + .probe = wm8994_probe, + .remove = wm8994_remove, + .suspend = wm8994_suspend, + .resume = wm8994_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8994); + +static int wm8994_codec_probe(struct platform_device *pdev) +{ + int ret; + struct wm8994_priv *wm8994; + struct snd_soc_codec *codec; + int i; + u16 rev; + + if (wm8994_codec) { + dev_err(&pdev->dev, "Another WM8994 is registered\n"); + return -EINVAL; + } + + wm8994 = kzalloc(sizeof(struct wm8994_priv), GFP_KERNEL); + if (!wm8994) { + dev_err(&pdev->dev, "Failed to allocate private data\n"); + return -ENOMEM; + } + + codec = &wm8994->codec; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8994; + codec->control_data = dev_get_drvdata(pdev->dev.parent); + codec->name = "WM8994"; + codec->owner = THIS_MODULE; + codec->read = wm8994_read; + codec->write = wm8994_write; + codec->readable_register = wm8994_readable; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8994_set_bias_level; + codec->dai = &wm8994_dai[0]; + codec->num_dai = 3; + codec->reg_cache_size = WM8994_MAX_REGISTER; + codec->reg_cache = &wm8994->reg_cache; + codec->dev = &pdev->dev; + + wm8994->pdata = pdev->dev.parent->platform_data; + + /* Fill the cache with physical values we inherited; don't reset */ + ret = wm8994_bulk_read(codec->control_data, 0, + ARRAY_SIZE(wm8994->reg_cache) - 1, + codec->reg_cache); + if (ret < 0) { + dev_err(codec->dev, "Failed to fill register cache: %d\n", + ret); + goto err; + } + + /* Clear the cached values for unreadable/volatile registers to + * avoid potential confusion. + */ + for (i = 0; i < ARRAY_SIZE(wm8994->reg_cache); i++) + if (wm8994_volatile(i) || !wm8994_readable(i)) + wm8994->reg_cache[i] = 0; + + /* Set revision-specific configuration */ + rev = snd_soc_read(codec, WM8994_CHIP_REVISION); + switch (rev) { + case 2: + case 3: + wm8994->hubs.dcs_codes = -5; + wm8994->hubs.hp_startup_mode = 1; + break; + default: + break; + } + + + /* Remember if AIFnLRCLK is configured as a GPIO. This should be + * configured on init - if a system wants to do this dynamically + * at runtime we can deal with that then. + */ + ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_1); + if (ret < 0) { + dev_err(codec->dev, "Failed to read GPIO1 state: %d\n", ret); + goto err; + } + if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { + wm8994->lrclk_shared[0] = 1; + wm8994_dai[0].symmetric_rates = 1; + } else { + wm8994->lrclk_shared[0] = 0; + } + + ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_6); + if (ret < 0) { + dev_err(codec->dev, "Failed to read GPIO6 state: %d\n", ret); + goto err; + } + if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) { + wm8994->lrclk_shared[1] = 1; + wm8994_dai[1].symmetric_rates = 1; + } else { + wm8994->lrclk_shared[1] = 0; + } + + for (i = 0; i < ARRAY_SIZE(wm8994_dai); i++) + wm8994_dai[i].dev = codec->dev; + + wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8994_codec = codec; + + /* Latch volume updates (right only; we always do left then right). */ + snd_soc_update_bits(codec, WM8994_AIF1_DAC1_RIGHT_VOLUME, + WM8994_AIF1DAC1_VU, WM8994_AIF1DAC1_VU); + snd_soc_update_bits(codec, WM8994_AIF1_DAC2_RIGHT_VOLUME, + WM8994_AIF1DAC2_VU, WM8994_AIF1DAC2_VU); + snd_soc_update_bits(codec, WM8994_AIF2_DAC_RIGHT_VOLUME, + WM8994_AIF2DAC_VU, WM8994_AIF2DAC_VU); + snd_soc_update_bits(codec, WM8994_AIF1_ADC1_RIGHT_VOLUME, + WM8994_AIF1ADC1_VU, WM8994_AIF1ADC1_VU); + snd_soc_update_bits(codec, WM8994_AIF1_ADC2_RIGHT_VOLUME, + WM8994_AIF1ADC2_VU, WM8994_AIF1ADC2_VU); + snd_soc_update_bits(codec, WM8994_AIF2_ADC_RIGHT_VOLUME, + WM8994_AIF2ADC_VU, WM8994_AIF1ADC2_VU); + snd_soc_update_bits(codec, WM8994_DAC1_RIGHT_VOLUME, + WM8994_DAC1_VU, WM8994_DAC1_VU); + snd_soc_update_bits(codec, WM8994_DAC2_RIGHT_VOLUME, + WM8994_DAC2_VU, WM8994_DAC2_VU); + + /* Set the low bit of the 3D stereo depth so TLV matches */ + snd_soc_update_bits(codec, WM8994_AIF1_DAC1_FILTERS_2, + 1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT, + 1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT); + snd_soc_update_bits(codec, WM8994_AIF1_DAC2_FILTERS_2, + 1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT, + 1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT); + snd_soc_update_bits(codec, WM8994_AIF2_DAC_FILTERS_2, + 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT, + 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT); + + wm8994_update_class_w(codec); + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dais(wm8994_dai, ARRAY_SIZE(wm8994_dai)); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + goto err_codec; + } + + platform_set_drvdata(pdev, wm8994); + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(wm8994); + return ret; +} + +static int __devexit wm8994_codec_remove(struct platform_device *pdev) +{ + struct wm8994_priv *wm8994 = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = &wm8994->codec; + + wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dais(wm8994_dai, ARRAY_SIZE(wm8994_dai)); + snd_soc_unregister_codec(&wm8994->codec); + kfree(wm8994); + wm8994_codec = NULL; + + return 0; +} + +static struct platform_driver wm8994_codec_driver = { + .driver = { + .name = "wm8994-codec", + .owner = THIS_MODULE, + }, + .probe = wm8994_codec_probe, + .remove = __devexit_p(wm8994_codec_remove), +}; + +static __init int wm8994_init(void) +{ + return platform_driver_register(&wm8994_codec_driver); +} +module_init(wm8994_init); + +static __exit void wm8994_exit(void) +{ + platform_driver_unregister(&wm8994_codec_driver); +} +module_exit(wm8994_exit); + + +MODULE_DESCRIPTION("ASoC WM8994 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8994-codec"); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h new file mode 100644 index 000000000000..0a5e1424dea0 --- /dev/null +++ b/sound/soc/codecs/wm8994.h @@ -0,0 +1,26 @@ +/* + * wm8994.h -- WM8994 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 _WM8994_H +#define _WM8994_H + +#include + +extern struct snd_soc_codec_device soc_codec_dev_wm8994; +extern struct snd_soc_dai wm8994_dai[]; + +/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */ +#define WM8994_SYSCLK_MCLK1 1 +#define WM8994_SYSCLK_MCLK2 2 +#define WM8994_SYSCLK_FLL1 3 +#define WM8994_SYSCLK_FLL2 4 + +#define WM8994_FLL1 1 +#define WM8994_FLL2 2 + +#endif -- cgit v1.2.3 From fead215d1c0a385fc27a1fa96b7abbc4d66fb4c6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 2 Feb 2010 10:06:55 +0000 Subject: ASoC: Fix WM8994 dependency The dependency on MFD_WM8994 rather than I2C went awry. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc/codecs/Kconfig') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6b8a10120f9c..5ab59219a8de 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -61,7 +61,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C select SND_SOC_WM8993 if I2C - select SND_SOC_WM8994 if I2C + select SND_SOC_WM8994 if MFD_WM8994 select SND_SOC_WM9081 if I2C select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS -- cgit v1.2.3 From 3a66d3877eaa4ab9818000a15c07326adaa9ca79 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 11 Feb 2010 13:27:19 +0000 Subject: ASoC: Add WM2000 driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WM2000 is a low power, high quality handset receiver speaker driver with Wolfson myZone™ Ambient Noise Cancellation (ANC). It provides enhanced voice communication quality in a noisy environment if the handset acoustics are designed appropriately. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- include/sound/wm2000.h | 26 ++ sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm2000.c | 888 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm2000.h | 79 +++++ 5 files changed, 999 insertions(+) create mode 100644 include/sound/wm2000.h create mode 100644 sound/soc/codecs/wm2000.c create mode 100644 sound/soc/codecs/wm2000.h (limited to 'sound/soc/codecs/Kconfig') diff --git a/include/sound/wm2000.h b/include/sound/wm2000.h new file mode 100644 index 000000000000..aa388ca9ec64 --- /dev/null +++ b/include/sound/wm2000.h @@ -0,0 +1,26 @@ +/* + * linux/sound/wm2000.h -- Platform data for WM2000 + * + * Copyright 2010 Wolfson Microelectronics. PLC. + * + * 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 __LINUX_SND_WM2000_H +#define __LINUX_SND_WM2000_H + +struct wm2000_platform_data { + /** Filename for system-specific image to download to device. */ + const char *download_file; + + /** Divide MCLK by 2 for system clock? */ + unsigned int mclkdiv2:1; + + /** Disable speech clarity enhancement, for use when an + * external algorithm is used. */ + unsigned int speech_enh_disable:1; +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 5ab59219a8de..1743d565e996 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -36,6 +36,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TWL4030 if TWL4030_CORE select SND_SOC_UDA134X select SND_SOC_UDA1380 if I2C + select SND_SOC_WM2000 if I2C select SND_SOC_WM8350 if MFD_WM8350 select SND_SOC_WM8400 if MFD_WM8400 select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI @@ -265,3 +266,6 @@ config SND_SOC_MAX9877 config SND_SOC_TPA6130A2 tristate + +config SND_SOC_WM2000 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 209dd6c7c254..dd5ce6df6292 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -58,6 +58,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o # Amp snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o +snd-soc-wm2000-objs := wm2000.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o @@ -119,3 +120,4 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o +obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c new file mode 100644 index 000000000000..217b02680597 --- /dev/null +++ b/sound/soc/codecs/wm2000.c @@ -0,0 +1,888 @@ +/* + * wm2000.c -- WM2000 ALSA Soc Audio driver + * + * Copyright 2008-2010 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * 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. + * + * The download image for the WM2000 will be requested as + * 'wm2000_anc.bin' by default (overridable via platform data) at + * runtime and is expected to be in flat binary format. This is + * generated by Wolfson configuration tools and includes + * system-specific callibration information. If supplied as a + * sequence of ASCII-encoded hexidecimal bytes this can be converted + * into a flat binary with a command such as this on the command line: + * + * perl -e 'while (<>) { s/[\r\n]+// ; printf("%c", hex($_)); }' + * < file > wm2000_anc.bin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wm2000.h" + +enum wm2000_anc_mode { + ANC_ACTIVE = 0, + ANC_BYPASS = 1, + ANC_STANDBY = 2, + ANC_OFF = 3, +}; + +struct wm2000_priv { + struct i2c_client *i2c; + + enum wm2000_anc_mode anc_mode; + + unsigned int anc_active:1; + unsigned int anc_eng_ena:1; + unsigned int spk_ena:1; + + unsigned int mclk_div:1; + unsigned int speech_clarity:1; + + int anc_download_size; + char *anc_download; +}; + +static struct i2c_client *wm2000_i2c; + +static int wm2000_write(struct i2c_client *i2c, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + int ret; + + data[0] = (reg >> 8) & 0xff; + data[1] = reg & 0xff; + data[2] = value & 0xff; + + dev_vdbg(&i2c->dev, "write %x = %x\n", reg, value); + + ret = i2c_master_send(i2c, data, 3); + if (ret == 3) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + +static unsigned int wm2000_read(struct i2c_client *i2c, unsigned int r) +{ + struct i2c_msg xfer[2]; + u8 reg[2]; + u8 data; + int ret; + + /* Write register */ + reg[0] = (r >> 8) & 0xff; + reg[1] = r & 0xff; + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = sizeof(reg); + xfer[0].buf = ®[0]; + + /* Read data */ + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 1; + xfer[1].buf = &data; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret != 2) { + dev_err(&i2c->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + dev_vdbg(&i2c->dev, "read %x from %x\n", data, r); + + return data; +} + +static void wm2000_reset(struct wm2000_priv *wm2000) +{ + struct i2c_client *i2c = wm2000->i2c; + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR); + wm2000_write(i2c, WM2000_REG_ID1, 0); + + wm2000->anc_mode = ANC_OFF; +} + +static int wm2000_poll_bit(struct i2c_client *i2c, + unsigned int reg, u8 mask, int timeout) +{ + int val; + + val = wm2000_read(i2c, reg); + + while (!(val & mask) && --timeout) { + msleep(1); + val = wm2000_read(i2c, reg); + } + + if (timeout == 0) + return 0; + else + return 1; +} + +static int wm2000_power_up(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + int ret, timeout; + + BUG_ON(wm2000->anc_mode != ANC_OFF); + + dev_dbg(&i2c->dev, "Beginning power up\n"); + + if (!wm2000->mclk_div) { + dev_dbg(&i2c->dev, "Disabling MCLK divider\n"); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, + WM2000_MCLK_DIV2_ENA_CLR); + } else { + dev_dbg(&i2c->dev, "Enabling MCLK divider\n"); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, + WM2000_MCLK_DIV2_ENA_SET); + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_CLR); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_ENG_SET); + + /* Wait for ANC engine to become ready */ + if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, + WM2000_ANC_ENG_IDLE, 1)) { + dev_err(&i2c->dev, "ANC engine failed to reset\n"); + return -ETIMEDOUT; + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_BOOT_COMPLETE, 1)) { + dev_err(&i2c->dev, "ANC engine failed to initialise\n"); + return -ETIMEDOUT; + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET); + + /* Open code download of the data since it is the only bulk + * write we do. */ + dev_dbg(&i2c->dev, "Downloading %d bytes\n", + wm2000->anc_download_size - 2); + + ret = i2c_master_send(i2c, wm2000->anc_download, + wm2000->anc_download_size); + if (ret < 0) { + dev_err(&i2c->dev, "i2c_transfer() failed: %d\n", ret); + return ret; + } + if (ret != wm2000->anc_download_size) { + dev_err(&i2c->dev, "i2c_transfer() failed, %d != %d\n", + ret, wm2000->anc_download_size); + return -EIO; + } + + dev_dbg(&i2c->dev, "Download complete\n"); + + if (analogue) { + timeout = 248; + wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, timeout / 4); + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_MOUSE_ENABLE | + WM2000_MODE_THERMAL_ENABLE); + } else { + timeout = 10; + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_MOUSE_ENABLE | + WM2000_MODE_THERMAL_ENABLE); + } + + ret = wm2000_read(i2c, WM2000_REG_SPEECH_CLARITY); + if (wm2000->speech_clarity) + ret &= ~WM2000_SPEECH_CLARITY; + else + ret |= WM2000_SPEECH_CLARITY; + wm2000_write(i2c, WM2000_REG_SPEECH_CLARITY, ret); + + wm2000_write(i2c, WM2000_REG_SYS_START0, 0x33); + wm2000_write(i2c, WM2000_REG_SYS_START1, 0x02); + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR); + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_MOUSE_ACTIVE, timeout)) { + dev_err(&i2c->dev, "Timed out waiting for device after %dms\n", + timeout * 10); + return -ETIMEDOUT; + } + + dev_dbg(&i2c->dev, "ANC active\n"); + if (analogue) + dev_dbg(&i2c->dev, "Analogue active\n"); + wm2000->anc_mode = ANC_ACTIVE; + + return 0; +} + +static int wm2000_power_down(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + int timeout; + + if (analogue) { + timeout = 248; + wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, timeout / 4); + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_POWER_DOWN); + } else { + timeout = 10; + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_POWER_DOWN); + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_POWER_DOWN_COMPLETE, timeout)) { + dev_err(&i2c->dev, "Timeout waiting for ANC power down\n"); + return -ETIMEDOUT; + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, + WM2000_ANC_ENG_IDLE, 1)) { + dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n"); + return -ETIMEDOUT; + } + + dev_dbg(&i2c->dev, "powered off\n"); + wm2000->anc_mode = ANC_OFF; + + return 0; +} + +static int wm2000_enter_bypass(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + BUG_ON(wm2000->anc_mode != ANC_ACTIVE); + + if (analogue) { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_BYPASS_ENTRY); + } else { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_BYPASS_ENTRY); + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_ANC_DISABLED, 10)) { + dev_err(&i2c->dev, "Timeout waiting for ANC disable\n"); + return -ETIMEDOUT; + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, + WM2000_ANC_ENG_IDLE, 1)) { + dev_err(&i2c->dev, "Timeout waiting for ANC engine idle\n"); + return -ETIMEDOUT; + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR); + + wm2000->anc_mode = ANC_BYPASS; + dev_dbg(&i2c->dev, "bypass enabled\n"); + + return 0; +} + +static int wm2000_exit_bypass(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + BUG_ON(wm2000->anc_mode != ANC_BYPASS); + + wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0); + + if (analogue) { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_MOUSE_ENABLE | + WM2000_MODE_THERMAL_ENABLE); + } else { + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_MOUSE_ENABLE | + WM2000_MODE_THERMAL_ENABLE); + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR); + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_MOUSE_ACTIVE, 10)) { + dev_err(&i2c->dev, "Timed out waiting for MOUSE\n"); + return -ETIMEDOUT; + } + + wm2000->anc_mode = ANC_ACTIVE; + dev_dbg(&i2c->dev, "MOUSE active\n"); + + return 0; +} + +static int wm2000_enter_standby(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + int timeout; + + BUG_ON(wm2000->anc_mode != ANC_ACTIVE); + + if (analogue) { + timeout = 248; + wm2000_write(i2c, WM2000_REG_ANA_VMID_PD_TIME, timeout / 4); + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_STANDBY_ENTRY); + } else { + timeout = 10; + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_STANDBY_ENTRY); + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_ANC_DISABLED, timeout)) { + dev_err(&i2c->dev, + "Timed out waiting for ANC disable after 1ms\n"); + return -ETIMEDOUT; + } + + if (!wm2000_poll_bit(i2c, WM2000_REG_ANC_STAT, WM2000_ANC_ENG_IDLE, + 1)) { + dev_err(&i2c->dev, + "Timed out waiting for standby after %dms\n", + timeout * 10); + return -ETIMEDOUT; + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL1, WM2000_SYS_STBY); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_CLR); + + wm2000->anc_mode = ANC_STANDBY; + dev_dbg(&i2c->dev, "standby\n"); + if (analogue) + dev_dbg(&i2c->dev, "Analogue disabled\n"); + + return 0; +} + +static int wm2000_exit_standby(struct i2c_client *i2c, int analogue) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + int timeout; + + BUG_ON(wm2000->anc_mode != ANC_STANDBY); + + wm2000_write(i2c, WM2000_REG_SYS_CTL1, 0); + + if (analogue) { + timeout = 248; + wm2000_write(i2c, WM2000_REG_ANA_VMID_PU_TIME, timeout / 4); + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_ANA_SEQ_INCLUDE | + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_MOUSE_ENABLE); + } else { + timeout = 10; + + wm2000_write(i2c, WM2000_REG_SYS_MODE_CNTRL, + WM2000_MODE_THERMAL_ENABLE | + WM2000_MODE_MOUSE_ENABLE); + } + + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_RAM_SET); + wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_ANC_INT_N_CLR); + + if (!wm2000_poll_bit(i2c, WM2000_REG_SYS_STATUS, + WM2000_STATUS_MOUSE_ACTIVE, timeout)) { + dev_err(&i2c->dev, "Timed out waiting for MOUSE after %dms\n", + timeout * 10); + return -ETIMEDOUT; + } + + wm2000->anc_mode = ANC_ACTIVE; + dev_dbg(&i2c->dev, "MOUSE active\n"); + if (analogue) + dev_dbg(&i2c->dev, "Analogue enabled\n"); + + return 0; +} + +typedef int (*wm2000_mode_fn)(struct i2c_client *i2c, int analogue); + +static struct { + enum wm2000_anc_mode source; + enum wm2000_anc_mode dest; + int analogue; + wm2000_mode_fn step[2]; +} anc_transitions[] = { + { + .source = ANC_OFF, + .dest = ANC_ACTIVE, + .analogue = 1, + .step = { + wm2000_power_up, + }, + }, + { + .source = ANC_OFF, + .dest = ANC_STANDBY, + .step = { + wm2000_power_up, + wm2000_enter_standby, + }, + }, + { + .source = ANC_OFF, + .dest = ANC_BYPASS, + .analogue = 1, + .step = { + wm2000_power_up, + wm2000_enter_bypass, + }, + }, + { + .source = ANC_ACTIVE, + .dest = ANC_BYPASS, + .analogue = 1, + .step = { + wm2000_enter_bypass, + }, + }, + { + .source = ANC_ACTIVE, + .dest = ANC_STANDBY, + .analogue = 1, + .step = { + wm2000_enter_standby, + }, + }, + { + .source = ANC_ACTIVE, + .dest = ANC_OFF, + .analogue = 1, + .step = { + wm2000_power_down, + }, + }, + { + .source = ANC_BYPASS, + .dest = ANC_ACTIVE, + .analogue = 1, + .step = { + wm2000_exit_bypass, + }, + }, + { + .source = ANC_BYPASS, + .dest = ANC_STANDBY, + .analogue = 1, + .step = { + wm2000_exit_bypass, + wm2000_enter_standby, + }, + }, + { + .source = ANC_BYPASS, + .dest = ANC_OFF, + .step = { + wm2000_exit_bypass, + wm2000_power_down, + }, + }, + { + .source = ANC_STANDBY, + .dest = ANC_ACTIVE, + .analogue = 1, + .step = { + wm2000_exit_standby, + }, + }, + { + .source = ANC_STANDBY, + .dest = ANC_BYPASS, + .analogue = 1, + .step = { + wm2000_exit_standby, + wm2000_enter_bypass, + }, + }, + { + .source = ANC_STANDBY, + .dest = ANC_OFF, + .step = { + wm2000_exit_standby, + wm2000_power_down, + }, + }, +}; + +static int wm2000_anc_transition(struct wm2000_priv *wm2000, + enum wm2000_anc_mode mode) +{ + struct i2c_client *i2c = wm2000->i2c; + int i, j; + int ret; + + if (wm2000->anc_mode == mode) + return 0; + + for (i = 0; i < ARRAY_SIZE(anc_transitions); i++) + if (anc_transitions[i].source == wm2000->anc_mode && + anc_transitions[i].dest == mode) + break; + if (i == ARRAY_SIZE(anc_transitions)) { + dev_err(&i2c->dev, "No transition for %d->%d\n", + wm2000->anc_mode, mode); + return -EINVAL; + } + + for (j = 0; j < ARRAY_SIZE(anc_transitions[j].step); j++) { + if (!anc_transitions[i].step[j]) + break; + ret = anc_transitions[i].step[j](i2c, + anc_transitions[i].analogue); + if (ret != 0) + return ret; + } + + return 0; +} + +static int wm2000_anc_set_mode(struct wm2000_priv *wm2000) +{ + struct i2c_client *i2c = wm2000->i2c; + enum wm2000_anc_mode mode; + + if (wm2000->anc_eng_ena && wm2000->spk_ena) + if (wm2000->anc_active) + mode = ANC_ACTIVE; + else + mode = ANC_BYPASS; + else + mode = ANC_STANDBY; + + dev_dbg(&i2c->dev, "Set mode %d (enabled %d, mute %d, active %d)\n", + mode, wm2000->anc_eng_ena, !wm2000->spk_ena, + wm2000->anc_active); + + return wm2000_anc_transition(wm2000, mode); +} + +static int wm2000_anc_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev); + + ucontrol->value.enumerated.item[0] = wm2000->anc_active; + + return 0; +} + +static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev); + int anc_active = ucontrol->value.enumerated.item[0]; + + if (anc_active > 1) + return -EINVAL; + + wm2000->anc_active = anc_active; + + return wm2000_anc_set_mode(wm2000); +} + +static int wm2000_speaker_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev); + + ucontrol->value.enumerated.item[0] = wm2000->spk_ena; + + return 0; +} + +static int wm2000_speaker_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev); + int val = ucontrol->value.enumerated.item[0]; + + if (val > 1) + return -EINVAL; + + wm2000->spk_ena = val; + + return wm2000_anc_set_mode(wm2000); +} + +static const struct snd_kcontrol_new wm2000_controls[] = { + SOC_SINGLE_BOOL_EXT("WM2000 ANC Switch", 0, + wm2000_anc_mode_get, + wm2000_anc_mode_put), + SOC_SINGLE_BOOL_EXT("WM2000 Switch", 0, + wm2000_speaker_get, + wm2000_speaker_put), +}; + +static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&wm2000_i2c->dev); + + if (SND_SOC_DAPM_EVENT_ON(event)) + wm2000->anc_eng_ena = 1; + + if (SND_SOC_DAPM_EVENT_OFF(event)) + wm2000->anc_eng_ena = 0; + + return wm2000_anc_set_mode(wm2000); +} + +static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = { +/* Externally visible pins */ +SND_SOC_DAPM_OUTPUT("WM2000 SPKN"), +SND_SOC_DAPM_OUTPUT("WM2000 SPKP"), + +SND_SOC_DAPM_INPUT("WM2000 LINN"), +SND_SOC_DAPM_INPUT("WM2000 LINP"), + +SND_SOC_DAPM_PGA_E("ANC Engine", SND_SOC_NOPM, 0, 0, NULL, 0, + wm2000_anc_power_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +}; + +/* Target, Path, Source */ +static const struct snd_soc_dapm_route audio_map[] = { + { "WM2000 SPKN", NULL, "ANC Engine" }, + { "WM2000 SPKP", NULL, "ANC Engine" }, + { "ANC Engine", NULL, "WM2000 LINN" }, + { "ANC Engine", NULL, "WM2000 LINP" }, +}; + +/* Called from the machine driver */ +int wm2000_add_controls(struct snd_soc_codec *codec) +{ + int ret; + + if (!wm2000_i2c) { + pr_err("WM2000 not yet probed\n"); + return -ENODEV; + } + + ret = snd_soc_dapm_new_controls(codec, wm2000_dapm_widgets, + ARRAY_SIZE(wm2000_dapm_widgets)); + if (ret < 0) + return ret; + + ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + if (ret < 0) + return ret; + + return snd_soc_add_controls(codec, wm2000_controls, + ARRAY_SIZE(wm2000_controls)); +} +EXPORT_SYMBOL_GPL(wm2000_add_controls); + +static int __devinit wm2000_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + struct wm2000_priv *wm2000; + struct wm2000_platform_data *pdata; + const char *filename; + const struct firmware *fw; + int reg, ret; + u16 id; + + if (wm2000_i2c) { + dev_err(&i2c->dev, "Another WM2000 is already registered\n"); + return -EINVAL; + } + + wm2000 = kzalloc(sizeof(struct wm2000_priv), GFP_KERNEL); + if (wm2000 == NULL) { + dev_err(&i2c->dev, "Unable to allocate private data\n"); + return -ENOMEM; + } + + /* Verify that this is a WM2000 */ + reg = wm2000_read(i2c, WM2000_REG_ID1); + id = reg << 8; + reg = wm2000_read(i2c, WM2000_REG_ID2); + id |= reg & 0xff; + + if (id != 0x2000) { + dev_err(&i2c->dev, "Device is not a WM2000 - ID %x\n", id); + ret = -ENODEV; + goto err; + } + + reg = wm2000_read(i2c, WM2000_REG_REVISON); + dev_info(&i2c->dev, "revision %c\n", reg + 'A'); + + filename = "wm2000_anc.bin"; + pdata = dev_get_platdata(&i2c->dev); + if (pdata) { + wm2000->mclk_div = pdata->mclkdiv2; + wm2000->speech_clarity = !pdata->speech_enh_disable; + + if (pdata->download_file) + filename = pdata->download_file; + } + + ret = request_firmware(&fw, filename, &i2c->dev); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to acquire ANC data: %d\n", ret); + goto err; + } + + /* Pre-cook the concatenation of the register address onto the image */ + wm2000->anc_download_size = fw->size + 2; + wm2000->anc_download = kmalloc(wm2000->anc_download_size, GFP_KERNEL); + if (wm2000->anc_download == NULL) { + dev_err(&i2c->dev, "Out of memory\n"); + ret = -ENOMEM; + goto err_fw; + } + + wm2000->anc_download[0] = 0x80; + wm2000->anc_download[1] = 0x00; + memcpy(wm2000->anc_download + 2, fw->data, fw->size); + + release_firmware(fw); + + dev_set_drvdata(&i2c->dev, wm2000); + wm2000->anc_eng_ena = 1; + wm2000->i2c = i2c; + + wm2000_reset(wm2000); + + /* This will trigger a transition to standby mode by default */ + wm2000_anc_set_mode(wm2000); + + wm2000_i2c = i2c; + + return 0; + +err_fw: + release_firmware(fw); +err: + kfree(wm2000); + return ret; +} + +static __devexit int wm2000_i2c_remove(struct i2c_client *i2c) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + wm2000_anc_transition(wm2000, ANC_OFF); + + wm2000_i2c = NULL; + kfree(wm2000->anc_download); + kfree(wm2000); + + return 0; +} + +static void wm2000_i2c_shutdown(struct i2c_client *i2c) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + wm2000_anc_transition(wm2000, ANC_OFF); +} + +#ifdef CONFIG_PM +static int wm2000_i2c_suspend(struct i2c_client *i2c, pm_message_t mesg) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + return wm2000_anc_transition(wm2000, ANC_OFF); +} + +static int wm2000_i2c_resume(struct i2c_client *i2c) +{ + struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + + return wm2000_anc_set_mode(wm2000); +} +#else +#define wm2000_i2c_suspend NULL +#define wm2000_i2c_resume NULL +#endif + +static const struct i2c_device_id wm2000_i2c_id[] = { + { "wm2000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm2000_i2c_id); + +static struct i2c_driver wm2000_i2c_driver = { + .driver = { + .name = "wm2000", + .owner = THIS_MODULE, + }, + .probe = wm2000_i2c_probe, + .remove = __devexit_p(wm2000_i2c_remove), + .suspend = wm2000_i2c_suspend, + .resume = wm2000_i2c_resume, + .shutdown = wm2000_i2c_shutdown, + .id_table = wm2000_i2c_id, +}; + +static int __init wm2000_init(void) +{ + return i2c_add_driver(&wm2000_i2c_driver); +} +module_init(wm2000_init); + +static void __exit wm2000_exit(void) +{ + i2c_del_driver(&wm2000_i2c_driver); +} +module_exit(wm2000_exit); + +MODULE_DESCRIPTION("ASoC WM2000 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm2000.h b/sound/soc/codecs/wm2000.h new file mode 100644 index 000000000000..c18e261c3c7f --- /dev/null +++ b/sound/soc/codecs/wm2000.h @@ -0,0 +1,79 @@ +/* + * wm2000.h -- WM2000 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 _WM2000_H +#define _WM2000_H + +struct wm2000_setup_data { + unsigned short i2c_address; + int mclk_div; /* Set to a non-zero value if MCLK_DIV_2 required */ +}; + +extern int wm2000_add_controls(struct snd_soc_codec *codec); + +extern struct snd_soc_dai wm2000_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm2000; + +#define WM2000_REG_SYS_START 0x8000 +#define WM2000_REG_SPEECH_CLARITY 0x8fef +#define WM2000_REG_SYS_WATCHDOG 0x8ff6 +#define WM2000_REG_ANA_VMID_PD_TIME 0x8ff7 +#define WM2000_REG_ANA_VMID_PU_TIME 0x8ff8 +#define WM2000_REG_CAT_FLTR_INDX 0x8ff9 +#define WM2000_REG_CAT_GAIN_0 0x8ffa +#define WM2000_REG_SYS_STATUS 0x8ffc +#define WM2000_REG_SYS_MODE_CNTRL 0x8ffd +#define WM2000_REG_SYS_START0 0x8ffe +#define WM2000_REG_SYS_START1 0x8fff +#define WM2000_REG_ID1 0xf000 +#define WM2000_REG_ID2 0xf001 +#define WM2000_REG_REVISON 0xf002 +#define WM2000_REG_SYS_CTL1 0xf003 +#define WM2000_REG_SYS_CTL2 0xf004 +#define WM2000_REG_ANC_STAT 0xf005 +#define WM2000_REG_IF_CTL 0xf006 + +/* SPEECH_CLARITY */ +#define WM2000_SPEECH_CLARITY 0x01 + +/* SYS_STATUS */ +#define WM2000_STATUS_MOUSE_ACTIVE 0x40 +#define WM2000_STATUS_CAT_FREQ_COMPLETE 0x20 +#define WM2000_STATUS_CAT_GAIN_COMPLETE 0x10 +#define WM2000_STATUS_THERMAL_SHUTDOWN_COMPLETE 0x08 +#define WM2000_STATUS_ANC_DISABLED 0x04 +#define WM2000_STATUS_POWER_DOWN_COMPLETE 0x02 +#define WM2000_STATUS_BOOT_COMPLETE 0x01 + +/* SYS_MODE_CNTRL */ +#define WM2000_MODE_ANA_SEQ_INCLUDE 0x80 +#define WM2000_MODE_MOUSE_ENABLE 0x40 +#define WM2000_MODE_CAT_FREQ_ENABLE 0x20 +#define WM2000_MODE_CAT_GAIN_ENABLE 0x10 +#define WM2000_MODE_BYPASS_ENTRY 0x08 +#define WM2000_MODE_STANDBY_ENTRY 0x04 +#define WM2000_MODE_THERMAL_ENABLE 0x02 +#define WM2000_MODE_POWER_DOWN 0x01 + +/* SYS_CTL1 */ +#define WM2000_SYS_STBY 0x01 + +/* SYS_CTL2 */ +#define WM2000_MCLK_DIV2_ENA_CLR 0x80 +#define WM2000_MCLK_DIV2_ENA_SET 0x40 +#define WM2000_ANC_ENG_CLR 0x20 +#define WM2000_ANC_ENG_SET 0x10 +#define WM2000_ANC_INT_N_CLR 0x08 +#define WM2000_ANC_INT_N_SET 0x04 +#define WM2000_RAM_CLR 0x02 +#define WM2000_RAM_SET 0x01 + +/* ANC_STAT */ +#define WM2000_ANC_ENG_IDLE 0x01 + +#endif -- cgit v1.2.3