diff options
author | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
---|---|---|
committer | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
commit | fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204 (patch) | |
tree | 22962a4387943edc841c72a4e636a068c66d58fd /sound/soc/codecs | |
download | ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.zip ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.tar.gz |
Initial import of modified Linux 2.6.28 tree
Original upstream URL:
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git | branch linux-2.6.28.y
Diffstat (limited to 'sound/soc/codecs')
44 files changed, 23886 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig new file mode 100644 index 0000000..38a0e3b --- /dev/null +++ b/sound/soc/codecs/Kconfig @@ -0,0 +1,112 @@ +config SND_SOC_ALL_CODECS + tristate "Build all ASoC CODEC drivers" + depends on I2C + select SPI + select SPI_MASTER + select SND_SOC_AD73311 + select SND_SOC_AK4535 + select SND_SOC_CS4270 + select SND_SOC_SSM2602 + select SND_SOC_TLV320AIC23 + select SND_SOC_TLV320AIC26 + select SND_SOC_TLV320AIC3X + select SND_SOC_UDA1380 + select SND_SOC_WM8510 + select SND_SOC_WM8580 + select SND_SOC_WM8731 + select SND_SOC_WM8750 + select SND_SOC_WM8753 + select SND_SOC_WM8900 + select SND_SOC_WM8903 + select SND_SOC_WM8971 + select SND_SOC_WM8990 + help + Normally ASoC codec drivers are only built if a machine driver which + uses them is also built since they are only usable with a machine + driver. Selecting this option will allow these drivers to be built + without an explicit machine driver for test and development purposes. + + If unsure select "N". + + +config SND_SOC_AC97_CODEC + tristate + select SND_AC97_CODEC + +config SND_SOC_AD1980 + tristate + +config SND_SOC_AD73311 + tristate + +config SND_SOC_AK4535 + tristate + +# Cirrus Logic CS4270 Codec +config SND_SOC_CS4270 + tristate + +# Cirrus Logic CS4270 Codec Hardware Mute Support +# Select if you have external muting circuitry attached to your CS4270. +config SND_SOC_CS4270_HWMUTE + bool + depends on SND_SOC_CS4270 + +# 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 +# not select any sample rates that require MCLK to be divided by 1.5. +config SND_SOC_CS4270_VD33_ERRATA + bool + depends on SND_SOC_CS4270 + +config SND_SOC_SSM2602 + tristate + +config SND_SOC_TLV320AIC23 + tristate + depends on I2C + +config SND_SOC_TLV320AIC26 + tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE + depends on SPI + +config SND_SOC_TLV320AIC3X + tristate + depends on I2C + +config SND_SOC_UDA1380 + tristate + +config SND_SOC_WM8510 + tristate + +config SND_SOC_WM8580 + tristate + +config SND_SOC_WM8731 + tristate + +config SND_SOC_WM8750 + tristate + +config SND_SOC_WM8753 + tristate + +config SND_SOC_WM8900 + tristate + +config SND_SOC_WM8903 + tristate + +config SND_SOC_WM8971 + tristate + +config SND_SOC_WM8990 + tristate + +config SND_SOC_WM9712 + tristate + +config SND_SOC_WM9713 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile new file mode 100644 index 0000000..90f0a58 --- /dev/null +++ b/sound/soc/codecs/Makefile @@ -0,0 +1,43 @@ +snd-soc-ac97-objs := ac97.o +snd-soc-ad1980-objs := ad1980.o +snd-soc-ad73311-objs := ad73311.o +snd-soc-ak4535-objs := ak4535.o +snd-soc-cs4270-objs := cs4270.o +snd-soc-ssm2602-objs := ssm2602.o +snd-soc-tlv320aic23-objs := tlv320aic23.o +snd-soc-tlv320aic26-objs := tlv320aic26.o +snd-soc-tlv320aic3x-objs := tlv320aic3x.o +snd-soc-uda1380-objs := uda1380.o +snd-soc-wm8510-objs := wm8510.o +snd-soc-wm8580-objs := wm8580.o +snd-soc-wm8731-objs := wm8731.o +snd-soc-wm8750-objs := wm8750.o +snd-soc-wm8753-objs := wm8753.o +snd-soc-wm8900-objs := wm8900.o +snd-soc-wm8903-objs := wm8903.o +snd-soc-wm8971-objs := wm8971.o +snd-soc-wm8990-objs := wm8990.o +snd-soc-wm9712-objs := wm9712.o +snd-soc-wm9713-objs := wm9713.o + +obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o +obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o +obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o +obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o +obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o +obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o +obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o +obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o +obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o +obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o +obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o +obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o +obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o +obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o +obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c new file mode 100644 index 0000000..bd1ebdc --- /dev/null +++ b/sound/soc/codecs/ac97.c @@ -0,0 +1,179 @@ +/* + * ac97.c -- ALSA Soc AC97 codec support + * + * Copyright 2005 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * 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. + * + * Generic AC97 support. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include "ac97.h" + +#define AC97_VERSION "0.6" + +static int ac97_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE; + return snd_ac97_set_rate(codec->ac97, reg, runtime->rate); +} + +#define STD_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) + +struct snd_soc_dai ac97_dai = { + .name = "AC97 HiFi", + .type = SND_SOC_DAI_AC97, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = STD_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = STD_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_prepare,}, +}; +EXPORT_SYMBOL_GPL(ac97_dai); + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + return soc_ac97_ops.read(codec->ac97, reg); +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + soc_ac97_ops.write(codec->ac97, reg, val); + return 0; +} + +static int ac97_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97_template ac97_template; + int ret = 0; + + printk(KERN_INFO "AC97 SoC Audio Codec %s\n", AC97_VERSION); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (!socdev->codec) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->name = "AC97"; + codec->owner = THIS_MODULE; + codec->dai = &ac97_dai; + codec->num_dai = 1; + codec->write = ac97_write; + codec->read = ac97_read; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto err; + + /* add codec as bus device for standard ac97 */ + ret = snd_ac97_bus(codec->card, 0, &soc_ac97_ops, NULL, &ac97_bus); + if (ret < 0) + goto bus_err; + + memset(&ac97_template, 0, sizeof(struct snd_ac97_template)); + ret = snd_ac97_mixer(ac97_bus, &ac97_template, &codec->ac97); + if (ret < 0) + goto bus_err; + + ret = snd_soc_register_card(socdev); + if (ret < 0) + goto bus_err; + return 0; + +bus_err: + snd_soc_free_pcms(socdev); + +err: + kfree(socdev->codec->reg_cache); + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int ac97_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (!codec) + return 0; + + snd_soc_free_pcms(socdev); + kfree(socdev->codec->reg_cache); + kfree(socdev->codec); + + return 0; +} + +#ifdef CONFIG_PM +static int ac97_soc_suspend(struct platform_device *pdev, pm_message_t msg) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_ac97_suspend(socdev->codec->ac97); + + return 0; +} + +static int ac97_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_ac97_resume(socdev->codec->ac97); + + return 0; +} +#else +#define ac97_soc_suspend NULL +#define ac97_soc_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_ac97 = { + .probe = ac97_soc_probe, + .remove = ac97_soc_remove, + .suspend = ac97_soc_suspend, + .resume = ac97_soc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ac97); + +MODULE_DESCRIPTION("Soc Generic AC97 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ac97.h b/sound/soc/codecs/ac97.h new file mode 100644 index 0000000..281aa42 --- /dev/null +++ b/sound/soc/codecs/ac97.h @@ -0,0 +1,19 @@ +/* + * linux/sound/codecs/ac97.h -- ALSA SoC Layer + * + * Author: Liam Girdwood + * Created: Dec 1st 2005 + * Copyright: 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_SOC_AC97_H +#define __LINUX_SND_SOC_AC97_H + +extern struct snd_soc_codec_device soc_codec_dev_ac97; +extern struct snd_soc_dai ac97_dai; + +#endif diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c new file mode 100644 index 0000000..1397b8e --- /dev/null +++ b/sound/soc/codecs/ad1980.c @@ -0,0 +1,308 @@ +/* + * ad1980.c -- ALSA Soc AD1980 codec support + * + * Copyright: Analog Device Inc. + * Author: Roy Huang <roy.huang@analog.com> + * Cliff Cai <cliff.cai@analog.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "ad1980.h" + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg); +static int ac97_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val); + +/* + * AD1980 register cache + */ +static const u16 ad1980_reg[] = { + 0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */ + 0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */ + 0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */ + 0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */ + 0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */ + 0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */ + 0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */ + 0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */ +}; + +static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line", + "Stereo Mix", "Mono Mix", "Phone"}; + +static const struct soc_enum ad1980_cap_src = + SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 7, ad1980_rec_sel); + +static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = { +SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1), + +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + +SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), +SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + +SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0), +SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1), + +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + +SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1), +SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1), + +SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1), +SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), + +SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0), +SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0), + +SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1), + +SOC_ENUM("Capture Source", ad1980_cap_src), + +SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0), +}; + +/* add non dapm controls */ +static int ad1980_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ad1980_snd_ac97_controls); i++) { + err = snd_ctl_add(codec->card, snd_soc_cnew( + &ad1980_snd_ac97_controls[i], codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + switch (reg) { + case AC97_RESET: + case AC97_INT_PAGING: + case AC97_POWERDOWN: + case AC97_EXTENDED_STATUS: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return soc_ac97_ops.read(codec->ac97, reg); + default: + reg = reg >> 1; + + if (reg >= (ARRAY_SIZE(ad1980_reg))) + return -EINVAL; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg < (ARRAY_SIZE(ad1980_reg))) + cache[reg] = val; + + return 0; +} + +struct snd_soc_dai ad1980_dai = { + .name = "AC97", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(ad1980_dai); + +static int ad1980_reset(struct snd_soc_codec *codec, int try_warm) +{ + u16 retry_cnt = 0; + +retry: + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (ac97_read(codec, AC97_RESET) == 0x0090) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + /* Set bit 16slot in register 74h, then every slot will has only 16 + * bits. This command is sent out in 20bit mode, in which case the + * first nibble of data is eaten by the addr. (Tag is always 16 bit)*/ + ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900); + + if (ac97_read(codec, AC97_RESET) != 0x0090) + goto err; + return 0; + +err: + while (retry_cnt++ < 10) + goto retry; + + printk(KERN_ERR "AD1980 AC97 reset failed\n"); + return -EIO; +} + +static int ad1980_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + u16 vendor_id2; + + printk(KERN_INFO "AD1980 SoC Audio Codec\n"); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(ad1980_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + memcpy(codec->reg_cache, ad1980_reg, sizeof(u16) * \ + ARRAY_SIZE(ad1980_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(ad1980_reg); + codec->reg_cache_step = 2; + codec->name = "AD1980"; + codec->owner = THIS_MODULE; + codec->dai = &ad1980_dai; + codec->num_dai = 1; + codec->write = ac97_write; + codec->read = ac97_read; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) { + printk(KERN_ERR "ad1980: failed to register AC97 codec\n"); + goto codec_err; + } + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + + ret = ad1980_reset(codec, 0); + if (ret < 0) { + printk(KERN_ERR "AC97 link error\n"); + goto reset_err; + } + + /* Read out vendor ID to make sure it is ad1980 */ + if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144) + goto reset_err; + + vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2); + + if (vendor_id2 != 0x5370) { + if (vendor_id2 != 0x5374) + goto reset_err; + else + printk(KERN_WARNING "ad1980: " + "Found AD1981 - only 2/2 IN/OUT Channels " + "supported\n"); + } + + ac97_write(codec, AC97_MASTER, 0x0000); /* unmute line out volume */ + ac97_write(codec, AC97_PCM, 0x0000); /* unmute PCM out volume */ + ac97_write(codec, AC97_REC_GAIN, 0x0000);/* unmute record volume */ + + ad1980_add_controls(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ad1980: failed to register card\n"); + goto reset_err; + } + + return 0; + +reset_err: + snd_soc_free_pcms(socdev); + +pcm_err: + snd_soc_free_ac97_codec(codec); + +codec_err: + kfree(codec->reg_cache); + +cache_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int ad1980_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->reg_cache); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ad1980 = { + .probe = ad1980_soc_probe, + .remove = ad1980_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad1980); + +MODULE_DESCRIPTION("ASoC ad1980 driver"); +MODULE_AUTHOR("Roy Huang, Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1980.h b/sound/soc/codecs/ad1980.h new file mode 100644 index 0000000..db6c850 --- /dev/null +++ b/sound/soc/codecs/ad1980.h @@ -0,0 +1,23 @@ +/* + * ad1980.h -- ad1980 Soc Audio driver + */ + +#ifndef _AD1980_H +#define _AD1980_H +/* Bit definition of Power-Down Control/Status Register */ +#define ADC 0x0001 +#define DAC 0x0002 +#define ANL 0x0004 +#define REF 0x0008 +#define PR0 0x0100 +#define PR1 0x0200 +#define PR2 0x0400 +#define PR3 0x0800 +#define PR4 0x1000 +#define PR5 0x2000 +#define PR6 0x4000 + +extern struct snd_soc_dai ad1980_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad1980; + +#endif diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c new file mode 100644 index 0000000..37af860 --- /dev/null +++ b/sound/soc/codecs/ad73311.c @@ -0,0 +1,107 @@ +/* + * ad73311.c -- ALSA Soc AD73311 codec support + * + * Copyright: Analog Device Inc. + * Author: Cliff Cai <cliff.cai@analog.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 25th Sep 2008 Initial version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "ad73311.h" + +struct snd_soc_dai ad73311_dai = { + .name = "AD73311", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(ad73311_dai); + +static int ad73311_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + mutex_init(&codec->mutex); + codec->name = "AD73311"; + codec->owner = THIS_MODULE; + codec->dai = &ad73311_dai; + codec->num_dai = 1; + socdev->codec = codec; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ad73311: failed to create pcms\n"); + goto pcm_err; + } + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ad73311: failed to register card\n"); + goto register_err; + } + + return ret; + +register_err: + snd_soc_free_pcms(socdev); +pcm_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int ad73311_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + snd_soc_free_pcms(socdev); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ad73311 = { + .probe = ad73311_soc_probe, + .remove = ad73311_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad73311); + +MODULE_DESCRIPTION("ASoC ad73311 driver"); +MODULE_AUTHOR("Cliff Cai "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h new file mode 100644 index 0000000..507ce0c --- /dev/null +++ b/sound/soc/codecs/ad73311.h @@ -0,0 +1,90 @@ +/* + * File: sound/soc/codec/ad73311.h + * Based on: + * Author: Cliff Cai <cliff.cai@analog.com> + * + * Created: Thur Sep 25, 2008 + * Description: definitions for AD73311 registers + * + * + * Modified: + * Copyright 2006 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AD73311_H__ +#define __AD73311_H__ + +#define AD_CONTROL 0x8000 +#define AD_DATA 0x0000 +#define AD_READ 0x4000 +#define AD_WRITE 0x0000 + +/* Control register A */ +#define CTRL_REG_A (0 << 8) + +#define REGA_MODE_PRO 0x00 +#define REGA_MODE_DATA 0x01 +#define REGA_MODE_MIXED 0x03 +#define REGA_DLB 0x04 +#define REGA_SLB 0x08 +#define REGA_DEVC(x) ((x & 0x7) << 4) +#define REGA_RESET 0x80 + +/* Control register B */ +#define CTRL_REG_B (1 << 8) + +#define REGB_DIRATE(x) (x & 0x3) +#define REGB_SCDIV(x) ((x & 0x3) << 2) +#define REGB_MCDIV(x) ((x & 0x7) << 4) +#define REGB_CEE (1 << 7) + +/* Control register C */ +#define CTRL_REG_C (2 << 8) + +#define REGC_PUDEV (1 << 0) +#define REGC_PUADC (1 << 3) +#define REGC_PUDAC (1 << 4) +#define REGC_PUREF (1 << 5) +#define REGC_REFUSE (1 << 6) + +/* Control register D */ +#define CTRL_REG_D (3 << 8) + +#define REGD_IGS(x) (x & 0x7) +#define REGD_RMOD (1 << 3) +#define REGD_OGS(x) ((x & 0x7) << 4) +#define REGD_MUTE (x << 7) + +/* Control register E */ +#define CTRL_REG_E (4 << 8) + +#define REGE_DA(x) (x & 0x1f) +#define REGE_IBYP (1 << 5) + +/* Control register F */ +#define CTRL_REG_F (5 << 8) + +#define REGF_SEEN (1 << 5) +#define REGF_INV (1 << 6) +#define REGF_ALB (1 << 7) + +extern struct snd_soc_dai ad73311_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad73311; +#endif diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c new file mode 100644 index 0000000..2a89b58 --- /dev/null +++ b/sound/soc/codecs/ak4535.c @@ -0,0 +1,694 @@ +/* + * ak4535.c -- AK4535 ALSA Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "ak4535.h" + +#define AK4535_VERSION "0.3" + +struct snd_soc_codec_device soc_codec_dev_ak4535; + +/* codec private data */ +struct ak4535_priv { + unsigned int sysclk; +}; + +/* + * ak4535 register cache + */ +static const u16 ak4535_reg[AK4535_CACHEREGNUM] = { + 0x0000, 0x0080, 0x0000, 0x0003, + 0x0002, 0x0000, 0x0011, 0x0001, + 0x0000, 0x0040, 0x0036, 0x0010, + 0x0000, 0x0000, 0x0057, 0x0000, +}; + +/* + * read ak4535 register cache + */ +static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return -1; + return cache[reg]; +} + +static inline unsigned int ak4535_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 data; + data = reg; + + if (codec->hw_write(codec->control_data, &data, 1) != 1) + return -EIO; + + if (codec->hw_read(codec->control_data, &data, 1) != 1) + return -EIO; + + return data; +}; + +/* + * write ak4535 register cache + */ +static inline void ak4535_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= AK4535_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the AK4535 register space + */ +static int ak4535_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D8 AK4535 register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + ak4535_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static int ak4535_sync(struct snd_soc_codec *codec) +{ + u16 *cache = codec->reg_cache; + int i, r = 0; + + for (i = 0; i < AK4535_CACHEREGNUM; i++) + r |= ak4535_write(codec, i, cache[i]); + + return r; +}; + +static const char *ak4535_mono_gain[] = {"+6dB", "-17dB"}; +static const char *ak4535_mono_out[] = {"(L + R)/2", "Hi-Z"}; +static const char *ak4535_hp_out[] = {"Stereo", "Mono"}; +static const char *ak4535_deemp[] = {"44.1kHz", "Off", "48kHz", "32kHz"}; +static const char *ak4535_mic_select[] = {"Internal", "External"}; + +static const struct soc_enum ak4535_enum[] = { + SOC_ENUM_SINGLE(AK4535_SIG1, 7, 2, ak4535_mono_gain), + SOC_ENUM_SINGLE(AK4535_SIG1, 6, 2, ak4535_mono_out), + SOC_ENUM_SINGLE(AK4535_MODE2, 2, 2, ak4535_hp_out), + SOC_ENUM_SINGLE(AK4535_DAC, 0, 4, ak4535_deemp), + SOC_ENUM_SINGLE(AK4535_MIC, 1, 2, ak4535_mic_select), +}; + +static const struct snd_kcontrol_new ak4535_snd_controls[] = { + SOC_SINGLE("ALC2 Switch", AK4535_SIG1, 1, 1, 0), + SOC_ENUM("Mono 1 Output", ak4535_enum[1]), + SOC_ENUM("Mono 1 Gain", ak4535_enum[0]), + SOC_ENUM("Headphone Output", ak4535_enum[2]), + SOC_ENUM("Playback Deemphasis", ak4535_enum[3]), + SOC_SINGLE("Bass Volume", AK4535_DAC, 2, 3, 0), + SOC_SINGLE("Mic Boost (+20dB) Switch", AK4535_MIC, 0, 1, 0), + SOC_ENUM("Mic Select", ak4535_enum[4]), + SOC_SINGLE("ALC Operation Time", AK4535_TIMER, 0, 3, 0), + SOC_SINGLE("ALC Recovery Time", AK4535_TIMER, 2, 3, 0), + SOC_SINGLE("ALC ZC Time", AK4535_TIMER, 4, 3, 0), + SOC_SINGLE("ALC 1 Switch", AK4535_ALC1, 5, 1, 0), + SOC_SINGLE("ALC 2 Switch", AK4535_ALC1, 6, 1, 0), + SOC_SINGLE("ALC Volume", AK4535_ALC2, 0, 127, 0), + SOC_SINGLE("Capture Volume", AK4535_PGA, 0, 127, 0), + SOC_SINGLE("Left Playback Volume", AK4535_LATT, 0, 127, 1), + SOC_SINGLE("Right Playback Volume", AK4535_RATT, 0, 127, 1), + SOC_SINGLE("AUX Bypass Volume", AK4535_VOL, 0, 15, 0), + SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0), +}; + +/* add non dapm controls */ +static int ak4535_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ak4535_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Mono 1 Mixer */ +static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0), + SOC_DAPM_SINGLE("Mono Playback Switch", AK4535_SIG1, 5, 1, 0), +}; + +/* Stereo Mixer */ +static const struct snd_kcontrol_new ak4535_stereo_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG2, 4, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", AK4535_SIG2, 7, 1, 0), + SOC_DAPM_SINGLE("Aux Bypass Switch", AK4535_SIG2, 5, 1, 0), +}; + +/* Input Mixer */ +static const struct snd_kcontrol_new ak4535_input_mixer_controls[] = { + SOC_DAPM_SINGLE("Mic Capture Switch", AK4535_MIC, 2, 1, 0), + SOC_DAPM_SINGLE("Aux Capture Switch", AK4535_MIC, 5, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ak4535_input_mux_control = + SOC_DAPM_ENUM("Input Select", ak4535_enum[4]); + +/* HP L switch */ +static const struct snd_kcontrol_new ak4535_hpl_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 1, 1, 1); + +/* HP R switch */ +static const struct snd_kcontrol_new ak4535_hpr_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 0, 1, 1); + +/* mono 2 switch */ +static const struct snd_kcontrol_new ak4535_mono2_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG1, 0, 1, 0); + +/* Line out switch */ +static const struct snd_kcontrol_new ak4535_line_control = + SOC_DAPM_SINGLE("Switch", AK4535_SIG2, 6, 1, 0); + +/* ak4535 dapm widgets */ +static const struct snd_soc_dapm_widget ak4535_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Stereo Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_stereo_mixer_controls[0], + ARRAY_SIZE(ak4535_stereo_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono1 Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_mono1_mixer_controls[0], + ARRAY_SIZE(ak4535_mono1_mixer_controls)), + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, + &ak4535_input_mixer_controls[0], + ARRAY_SIZE(ak4535_input_mixer_controls)), + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &ak4535_input_mux_control), + SND_SOC_DAPM_DAC("DAC", "Playback", AK4535_PM2, 0, 0), + SND_SOC_DAPM_SWITCH("Mono 2 Enable", SND_SOC_NOPM, 0, 0, + &ak4535_mono2_control), + /* speaker powersave bit */ + SND_SOC_DAPM_PGA("Speaker Enable", AK4535_MODE2, 0, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("Line Out Enable", SND_SOC_NOPM, 0, 0, + &ak4535_line_control), + SND_SOC_DAPM_SWITCH("Left HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpl_control), + SND_SOC_DAPM_SWITCH("Right HP Enable", SND_SOC_NOPM, 0, 0, + &ak4535_hpr_control), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPP"), + SND_SOC_DAPM_OUTPUT("SPN"), + SND_SOC_DAPM_OUTPUT("MOUT1"), + SND_SOC_DAPM_OUTPUT("MOUT2"), + SND_SOC_DAPM_OUTPUT("MICOUT"), + SND_SOC_DAPM_ADC("ADC", "Capture", AK4535_PM1, 0, 0), + SND_SOC_DAPM_PGA("Spk Amp", AK4535_PM2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP R Amp", AK4535_PM2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("HP L Amp", AK4535_PM2, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic", AK4535_PM1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Line Out", AK4535_PM1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", AK4535_PM1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("AUX In", AK4535_PM1, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Int Bias", AK4535_MIC, 3, 0), + SND_SOC_DAPM_MICBIAS("Mic Ext Bias", AK4535_MIC, 4, 0), + SND_SOC_DAPM_INPUT("MICIN"), + SND_SOC_DAPM_INPUT("MICEXT"), + SND_SOC_DAPM_INPUT("AUX"), + SND_SOC_DAPM_INPUT("MIN"), + SND_SOC_DAPM_INPUT("AIN"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /*stereo mixer */ + {"Stereo Mixer", "Playback Switch", "DAC"}, + {"Stereo Mixer", "Mic Sidetone Switch", "Mic"}, + {"Stereo Mixer", "Aux Bypass Switch", "AUX In"}, + + /* mono1 mixer */ + {"Mono1 Mixer", "Mic Sidetone Switch", "Mic"}, + {"Mono1 Mixer", "Mono Playback Switch", "DAC"}, + + /* Mic */ + {"Mic", NULL, "AIN"}, + {"Input Mux", "Internal", "Mic Int Bias"}, + {"Input Mux", "External", "Mic Ext Bias"}, + {"Mic Int Bias", NULL, "MICIN"}, + {"Mic Ext Bias", NULL, "MICEXT"}, + {"MICOUT", NULL, "Input Mux"}, + + /* line out */ + {"LOUT", NULL, "Line Out Enable"}, + {"ROUT", NULL, "Line Out Enable"}, + {"Line Out Enable", "Switch", "Line Out"}, + {"Line Out", NULL, "Stereo Mixer"}, + + /* mono1 out */ + {"MOUT1", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono1 Mixer"}, + + /* left HP */ + {"HPL", NULL, "Left HP Enable"}, + {"Left HP Enable", "Switch", "HP L Amp"}, + {"HP L Amp", NULL, "Stereo Mixer"}, + + /* right HP */ + {"HPR", NULL, "Right HP Enable"}, + {"Right HP Enable", "Switch", "HP R Amp"}, + {"HP R Amp", NULL, "Stereo Mixer"}, + + /* speaker */ + {"SPP", NULL, "Speaker Enable"}, + {"SPN", NULL, "Speaker Enable"}, + {"Speaker Enable", "Switch", "Spk Amp"}, + {"Spk Amp", NULL, "MIN"}, + + /* mono 2 */ + {"MOUT2", NULL, "Mono 2 Enable"}, + {"Mono 2 Enable", "Switch", "Stereo Mixer"}, + + /* Aux In */ + {"Aux In", NULL, "AUX"}, + + /* ADC */ + {"ADC", NULL, "Input Mixer"}, + {"Input Mixer", "Mic Capture Switch", "Mic"}, + {"Input Mixer", "Aux Capture Switch", "Aux In"}, +}; + +static int ak4535_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ak4535_dapm_widgets, + ARRAY_SIZE(ak4535_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int ak4535_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ak4535_priv *ak4535 = codec->private_data; + + ak4535->sysclk = freq; + return 0; +} + +static int ak4535_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ak4535_priv *ak4535 = codec->private_data; + u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5); + int rate = params_rate(params), fs = 256; + + if (rate) + fs = ak4535->sysclk / rate; + + /* set fs */ + switch (fs) { + case 1024: + mode2 |= (0x2 << 5); + break; + case 512: + mode2 |= (0x1 << 5); + break; + case 256: + break; + } + + /* set rate */ + ak4535_write(codec, AK4535_MODE2, mode2); + return 0; +} + +static int ak4535_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 mode1 = 0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + mode1 = 0x0002; + break; + case SND_SOC_DAIFMT_LEFT_J: + mode1 = 0x0001; + break; + default: + return -EINVAL; + } + + /* use 32 fs for BCLK to save power */ + mode1 |= 0x4; + + ak4535_write(codec, AK4535_MODE1, mode1); + return 0; +} + +static int ak4535_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ak4535_read_reg_cache(codec, AK4535_DAC) & 0xffdf; + if (!mute) + ak4535_write(codec, AK4535_DAC, mute_reg); + else + ak4535_write(codec, AK4535_DAC, mute_reg | 0x20); + return 0; +} + +static int ak4535_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 i; + + switch (level) { + case SND_SOC_BIAS_ON: + ak4535_mute(codec->dai, 0); + break; + case SND_SOC_BIAS_PREPARE: + ak4535_mute(codec->dai, 1); + break; + case SND_SOC_BIAS_STANDBY: + i = ak4535_read_reg_cache(codec, AK4535_PM1); + ak4535_write(codec, AK4535_PM1, i | 0x80); + i = ak4535_read_reg_cache(codec, AK4535_PM2); + ak4535_write(codec, AK4535_PM2, i & (~0x80)); + break; + case SND_SOC_BIAS_OFF: + i = ak4535_read_reg_cache(codec, AK4535_PM1); + ak4535_write(codec, AK4535_PM1, i & (~0x80)); + break; + } + codec->bias_level = level; + return 0; +} + +#define AK4535_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_dai ak4535_dai = { + .name = "AK4535", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4535_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = ak4535_hw_params, + }, + .dai_ops = { + .set_fmt = ak4535_set_dai_fmt, + .digital_mute = ak4535_mute, + .set_sysclk = ak4535_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(ak4535_dai); + +static int ak4535_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ak4535_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + ak4535_sync(codec); + ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ak4535_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the AK4535 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ak4535_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "AK4535"; + codec->owner = THIS_MODULE; + codec->read = ak4535_read_reg_cache; + codec->write = ak4535_write; + codec->set_bias_level = ak4535_set_bias_level; + codec->dai = &ak4535_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(ak4535_reg); + codec->reg_cache = kmemdup(ak4535_reg, sizeof(ak4535_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ak4535_add_controls(codec); + ak4535_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ak4535: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + + return ret; +} + +static struct snd_soc_device *ak4535_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static int ak4535_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = ak4535_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = ak4535_init(socdev); + if (ret < 0) + printk(KERN_ERR "failed to initialise AK4535\n"); + + return ret; +} + +static int ak4535_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id ak4535_i2c_id[] = { + { "ak4535", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4535_i2c_id); + +static struct i2c_driver ak4535_i2c_driver = { + .driver = { + .name = "AK4535 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = ak4535_i2c_probe, + .remove = ak4535_i2c_remove, + .id_table = ak4535_i2c_id, +}; + +static int ak4535_add_i2c_device(struct platform_device *pdev, + const struct ak4535_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&ak4535_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "ak4535", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&ak4535_i2c_driver); + return -ENODEV; +} +#endif + +static int ak4535_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ak4535_setup_data *setup; + struct snd_soc_codec *codec; + struct ak4535_priv *ak4535; + int ret; + + printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ak4535 = kzalloc(sizeof(struct ak4535_priv), GFP_KERNEL); + if (ak4535 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ak4535; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ak4535_socdev = socdev; + ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + codec->hw_read = (hw_read_t)i2c_master_recv; + ret = ak4535_add_i2c_device(pdev, setup); + } +#endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } + return ret; +} + +/* power down chip */ +static int ak4535_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&ak4535_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4535 = { + .probe = ak4535_probe, + .remove = ak4535_remove, + .suspend = ak4535_suspend, + .resume = ak4535_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4535); + +MODULE_DESCRIPTION("Soc AK4535 driver"); +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h new file mode 100644 index 0000000..c7a5870 --- /dev/null +++ b/sound/soc/codecs/ak4535.h @@ -0,0 +1,47 @@ +/* + * ak4535.h -- AK4535 Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on wm8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _AK4535_H +#define _AK4535_H + +/* AK4535 register space */ + +#define AK4535_PM1 0x0 +#define AK4535_PM2 0x1 +#define AK4535_SIG1 0x2 +#define AK4535_SIG2 0x3 +#define AK4535_MODE1 0x4 +#define AK4535_MODE2 0x5 +#define AK4535_DAC 0x6 +#define AK4535_MIC 0x7 +#define AK4535_TIMER 0x8 +#define AK4535_ALC1 0x9 +#define AK4535_ALC2 0xa +#define AK4535_PGA 0xb +#define AK4535_LATT 0xc +#define AK4535_RATT 0xd +#define AK4535_VOL 0xe +#define AK4535_STATUS 0xf + +#define AK4535_CACHEREGNUM 0x10 + +struct ak4535_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ak4535_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak4535; + +#endif diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c new file mode 100644 index 0000000..0bbd945 --- /dev/null +++ b/sound/soc/codecs/cs4270.c @@ -0,0 +1,765 @@ +/* + * CS4270 ALSA SoC (ASoC) codec driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007 Freescale Semiconductor, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + * + * This is an ASoC device driver for the Cirrus Logic CS4270 codec. + * + * Current features/limitations: + * + * 1) Software mode is supported. Stand-alone mode is automatically + * selected if I2C is disabled or if a CS4270 is not found on the I2C + * bus. However, stand-alone mode is only partially implemented because + * there is no mechanism yet for this driver and the machine driver to + * communicate the values of the M0, M1, MCLK1, and MCLK2 pins. + * 2) Only I2C is supported, not SPI + * 3) Only Master mode is supported, not Slave. + * 4) The machine driver's 'startup' function must call + * cs4270_set_dai_sysclk() with the value of MCLK. + * 5) Only I2S and left-justified modes are supported + * 6) Power management is not supported + * 7) The only supported control is volume and hardware mute (if enabled) + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <linux/i2c.h> + +#include "cs4270.h" + +/* If I2C is defined, then we support software mode. However, if we're + not compiled as module but I2C is, then we can't use I2C calls. */ +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) +#define USE_I2C +#endif + +/* Private data for the CS4270 */ +struct cs4270_private { + unsigned int mclk; /* Input frequency of the MCLK pin */ + unsigned int mode; /* The mode (I2S or left-justified) */ +}; + +/* + * The codec isn't really big-endian or little-endian, since the I2S + * interface requires data to be sent serially with the MSbit first. + * However, to support BE and LE I2S devices, we specify both here. That + * way, ALSA will always match the bit patterns. + */ +#define CS4270_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +#ifdef USE_I2C + +/* CS4270 registers addresses */ +#define CS4270_CHIPID 0x01 /* Chip ID */ +#define CS4270_PWRCTL 0x02 /* Power Control */ +#define CS4270_MODE 0x03 /* Mode Control */ +#define CS4270_FORMAT 0x04 /* Serial Format, ADC/DAC Control */ +#define CS4270_TRANS 0x05 /* Transition Control */ +#define CS4270_MUTE 0x06 /* Mute Control */ +#define CS4270_VOLA 0x07 /* DAC Channel A Volume Control */ +#define CS4270_VOLB 0x08 /* DAC Channel B Volume Control */ + +#define CS4270_FIRSTREG 0x01 +#define CS4270_LASTREG 0x08 +#define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1) + +/* Bit masks for the CS4270 registers */ +#define CS4270_CHIPID_ID 0xF0 +#define CS4270_CHIPID_REV 0x0F +#define CS4270_PWRCTL_FREEZE 0x80 +#define CS4270_PWRCTL_PDN_ADC 0x20 +#define CS4270_PWRCTL_PDN_DAC 0x02 +#define CS4270_PWRCTL_PDN 0x01 +#define CS4270_MODE_SPEED_MASK 0x30 +#define CS4270_MODE_1X 0x00 +#define CS4270_MODE_2X 0x10 +#define CS4270_MODE_4X 0x20 +#define CS4270_MODE_SLAVE 0x30 +#define CS4270_MODE_DIV_MASK 0x0E +#define CS4270_MODE_DIV1 0x00 +#define CS4270_MODE_DIV15 0x02 +#define CS4270_MODE_DIV2 0x04 +#define CS4270_MODE_DIV3 0x06 +#define CS4270_MODE_DIV4 0x08 +#define CS4270_MODE_POPGUARD 0x01 +#define CS4270_FORMAT_FREEZE_A 0x80 +#define CS4270_FORMAT_FREEZE_B 0x40 +#define CS4270_FORMAT_LOOPBACK 0x20 +#define CS4270_FORMAT_DAC_MASK 0x18 +#define CS4270_FORMAT_DAC_LJ 0x00 +#define CS4270_FORMAT_DAC_I2S 0x08 +#define CS4270_FORMAT_DAC_RJ16 0x18 +#define CS4270_FORMAT_DAC_RJ24 0x10 +#define CS4270_FORMAT_ADC_MASK 0x01 +#define CS4270_FORMAT_ADC_LJ 0x00 +#define CS4270_FORMAT_ADC_I2S 0x01 +#define CS4270_TRANS_ONE_VOL 0x80 +#define CS4270_TRANS_SOFT 0x40 +#define CS4270_TRANS_ZERO 0x20 +#define CS4270_TRANS_INV_ADC_A 0x08 +#define CS4270_TRANS_INV_ADC_B 0x10 +#define CS4270_TRANS_INV_DAC_A 0x02 +#define CS4270_TRANS_INV_DAC_B 0x04 +#define CS4270_TRANS_DEEMPH 0x01 +#define CS4270_MUTE_AUTO 0x20 +#define CS4270_MUTE_ADC_A 0x08 +#define CS4270_MUTE_ADC_B 0x10 +#define CS4270_MUTE_POLARITY 0x04 +#define CS4270_MUTE_DAC_A 0x01 +#define CS4270_MUTE_DAC_B 0x02 + +/* + * Clock Ratio Selection for Master Mode with I2C enabled + * + * The data for this chart is taken from Table 5 of the CS4270 reference + * manual. + * + * This table is used to determine how to program the Mode Control register. + * It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling + * rates the CS4270 currently supports. + * + * Each element in this array corresponds to the ratios in mclk_ratios[]. + * These two arrays need to be in sync. + * + * 'speed_mode' is the corresponding bit pattern to be written to the + * MODE bits of the Mode Control Register + * + * 'mclk' is the corresponding bit pattern to be wirten to the MCLK bits of + * the Mode Control Register. + * + * In situations where a single ratio is represented by multiple speed + * modes, we favor the slowest speed. E.g, for a ratio of 128, we pick + * double-speed instead of quad-speed. However, the CS4270 errata states + * that Divide-By-1.5 can cause failures, so we avoid that mode where + * possible. + * + * ERRATA: There is an errata for the CS4270 where divide-by-1.5 does not + * work if VD = 3.3V. If this effects you, select the + * CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will + * never select any sample rates that require divide-by-1.5. + */ +static struct { + unsigned int ratio; + u8 speed_mode; + u8 mclk; +} cs4270_mode_ratios[] = { + {64, CS4270_MODE_4X, CS4270_MODE_DIV1}, +#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA + {96, CS4270_MODE_4X, CS4270_MODE_DIV15}, +#endif + {128, CS4270_MODE_2X, CS4270_MODE_DIV1}, + {192, CS4270_MODE_4X, CS4270_MODE_DIV3}, + {256, CS4270_MODE_1X, CS4270_MODE_DIV1}, + {384, CS4270_MODE_2X, CS4270_MODE_DIV3}, + {512, CS4270_MODE_1X, CS4270_MODE_DIV2}, + {768, CS4270_MODE_1X, CS4270_MODE_DIV3}, + {1024, CS4270_MODE_1X, CS4270_MODE_DIV4} +}; + +/* The number of MCLK/LRCK ratios supported by the CS4270 */ +#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios) + +/* + * Determine the CS4270 samples rates. + * + * 'freq' is the input frequency to MCLK. The other parameters are ignored. + * + * The value of MCLK is used to determine which sample rates are supported + * by the CS4270. The ratio of MCLK / Fs must be equal to one of nine + * support values: 64, 96, 128, 192, 256, 384, 512, 768, and 1024. + * + * This function calculates the nine ratios and determines which ones match + * a standard sample rate. If there's a match, then it is added to the list + * of support sample rates. + * + * This function must be called by the machine driver's 'startup' function, + * otherwise the list of supported sample rates will not be available in + * time for ALSA. + * + * Note that in stand-alone mode, the sample rate is determined by input + * pins M0, M1, MDIV1, and MDIV2. Also in stand-alone mode, divide-by-3 + * is not a programmable option. However, divide-by-3 is not an available + * option in stand-alone mode. This cases two problems: a ratio of 768 is + * not available (it requires divide-by-3) and B) ratios 192 and 384 can + * only be selected with divide-by-1.5, but there is an errate that make + * this selection difficult. + * + * In addition, there is no mechanism for communicating with the machine + * driver what the input settings can be. This would need to be implemented + * for stand-alone mode to work. + */ +static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs4270_private *cs4270 = codec->private_data; + unsigned int rates = 0; + unsigned int rate_min = -1; + unsigned int rate_max = 0; + unsigned int i; + + cs4270->mclk = freq; + + for (i = 0; i < NUM_MCLK_RATIOS; i++) { + unsigned int rate = freq / cs4270_mode_ratios[i].ratio; + rates |= snd_pcm_rate_to_rate_bit(rate); + if (rate < rate_min) + rate_min = rate; + if (rate > rate_max) + rate_max = rate; + } + /* FIXME: soc should support a rate list */ + rates &= ~SNDRV_PCM_RATE_KNOT; + + if (!rates) { + printk(KERN_ERR "cs4270: could not find a valid sample rate\n"); + return -EINVAL; + } + + codec_dai->playback.rates = rates; + codec_dai->playback.rate_min = rate_min; + codec_dai->playback.rate_max = rate_max; + + codec_dai->capture.rates = rates; + codec_dai->capture.rate_min = rate_min; + codec_dai->capture.rate_max = rate_max; + + return 0; +} + +/* + * Configure the codec for the selected audio format + * + * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the + * codec accordingly. + * + * Currently, this function only supports SND_SOC_DAIFMT_I2S and + * SND_SOC_DAIFMT_LEFT_J. The CS4270 codec also supports right-justified + * data for playback only, but ASoC currently does not support different + * formats for playback vs. record. + */ +static int cs4270_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs4270_private *cs4270 = codec->private_data; + int ret = 0; + + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_LEFT_J: + cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + printk(KERN_ERR "cs4270: invalid DAI format\n"); + ret = -EINVAL; + } + + return ret; +} + +/* + * A list of addresses on which this CS4270 could use. I2C addresses are + * 7 bits. For the CS4270, the upper four bits are always 1001, and the + * lower three bits are determined via the AD2, AD1, and AD0 pins + * (respectively). + */ +static const unsigned short normal_i2c[] = { + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, I2C_CLIENT_END +}; +I2C_CLIENT_INSMOD; + +/* + * Pre-fill the CS4270 register cache. + * + * We use the auto-increment feature of the CS4270 to read all registers in + * one shot. + */ +static int cs4270_fill_cache(struct snd_soc_codec *codec) +{ + u8 *cache = codec->reg_cache; + struct i2c_client *i2c_client = codec->control_data; + s32 length; + + length = i2c_smbus_read_i2c_block_data(i2c_client, + CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache); + + if (length != CS4270_NUMREGS) { + printk(KERN_ERR "cs4270: I2C read failure, addr=0x%x\n", + i2c_client->addr); + return -EIO; + } + + return 0; +} + +/* + * Read from the CS4270 register cache. + * + * This CS4270 registers are cached to avoid excessive I2C I/O operations. + * After the initial read to pre-fill the cache, the CS4270 never updates + * the register values, so we won't have a cache coherncy problem. + */ +static unsigned int cs4270_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + + if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG)) + return -EIO; + + return cache[reg - CS4270_FIRSTREG]; +} + +/* + * Write to a CS4270 register via the I2C bus. + * + * This function writes the given value to the given CS4270 register, and + * also updates the register cache. + * + * Note that we don't use the hw_write function pointer of snd_soc_codec. + * That's because it's too clunky: the hw_write_t prototype does not match + * i2c_smbus_write_byte_data(), and it's just another layer of overhead. + */ +static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 *cache = codec->reg_cache; + + if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG)) + return -EIO; + + /* Only perform an I2C operation if the new value is different */ + if (cache[reg - CS4270_FIRSTREG] != value) { + struct i2c_client *client = codec->control_data; + if (i2c_smbus_write_byte_data(client, reg, value)) { + printk(KERN_ERR "cs4270: I2C write failed\n"); + return -EIO; + } + + /* We've written to the hardware, so update the cache */ + cache[reg - CS4270_FIRSTREG] = value; + } + + return 0; +} + +/* + * Program the CS4270 with the given hardware parameters. + * + * The .dai_ops functions are used to provide board-specific data, like + * input frequencies, to this driver. This function takes that information, + * combines it with the hardware parameters provided, and programs the + * hardware accordingly. + */ +static int cs4270_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct cs4270_private *cs4270 = codec->private_data; + int ret; + unsigned int i; + unsigned int rate; + unsigned int ratio; + int reg; + + /* Figure out which MCLK/LRCK ratio to use */ + + rate = params_rate(params); /* Sampling rate, in Hz */ + ratio = cs4270->mclk / rate; /* MCLK/LRCK ratio */ + + for (i = 0; i < NUM_MCLK_RATIOS; i++) { + if (cs4270_mode_ratios[i].ratio == ratio) + break; + } + + if (i == NUM_MCLK_RATIOS) { + /* We did not find a matching ratio */ + printk(KERN_ERR "cs4270: could not find matching ratio\n"); + return -EINVAL; + } + + /* Freeze and power-down the codec */ + + ret = snd_soc_write(codec, CS4270_PWRCTL, CS4270_PWRCTL_FREEZE | + CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | + CS4270_PWRCTL_PDN); + if (ret < 0) { + printk(KERN_ERR "cs4270: I2C write failed\n"); + return ret; + } + + /* Program the mode control register */ + + reg = snd_soc_read(codec, CS4270_MODE); + reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK); + reg |= cs4270_mode_ratios[i].speed_mode | cs4270_mode_ratios[i].mclk; + + ret = snd_soc_write(codec, CS4270_MODE, reg); + if (ret < 0) { + printk(KERN_ERR "cs4270: I2C write failed\n"); + return ret; + } + + /* Program the format register */ + + reg = snd_soc_read(codec, CS4270_FORMAT); + reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK); + + switch (cs4270->mode) { + case SND_SOC_DAIFMT_I2S: + reg |= CS4270_FORMAT_DAC_I2S | CS4270_FORMAT_ADC_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ; + break; + default: + printk(KERN_ERR "cs4270: unknown format\n"); + return -EINVAL; + } + + ret = snd_soc_write(codec, CS4270_FORMAT, reg); + if (ret < 0) { + printk(KERN_ERR "cs4270: I2C write failed\n"); + return ret; + } + + /* Disable auto-mute. This feature appears to be buggy, because in + some situations, auto-mute will not deactivate when it should. */ + + reg = snd_soc_read(codec, CS4270_MUTE); + reg &= ~CS4270_MUTE_AUTO; + ret = snd_soc_write(codec, CS4270_MUTE, reg); + if (ret < 0) { + printk(KERN_ERR "cs4270: I2C write failed\n"); + return ret; + } + + /* Thaw and power-up the codec */ + + ret = snd_soc_write(codec, CS4270_PWRCTL, 0); + if (ret < 0) { + printk(KERN_ERR "cs4270: I2C write failed\n"); + return ret; + } + + return ret; +} + +#ifdef CONFIG_SND_SOC_CS4270_HWMUTE + +/* + * Set the CS4270 external mute + * + * This function toggles the mute bits in the MUTE register. The CS4270's + * mute capability is intended for external muting circuitry, so if the + * board does not have the MUTEA or MUTEB pins connected to such circuitry, + * then this function will do nothing. + */ +static int cs4270_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int reg6; + + reg6 = snd_soc_read(codec, CS4270_MUTE); + + if (mute) + reg6 |= CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B | + CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B; + else + reg6 &= ~(CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B | + CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B); + + return snd_soc_write(codec, CS4270_MUTE, reg6); +} + +#endif + +static int cs4270_i2c_probe(struct i2c_client *, const struct i2c_device_id *); + +/* A list of non-DAPM controls that the CS4270 supports */ +static const struct snd_kcontrol_new cs4270_snd_controls[] = { + SOC_DOUBLE_R("Master Playback Volume", + CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1) +}; + +static const struct i2c_device_id cs4270_id[] = { + {"cs4270", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cs4270_id); + +static struct i2c_driver cs4270_i2c_driver = { + .driver = { + .name = "CS4270 I2C", + .owner = THIS_MODULE, + }, + .id_table = cs4270_id, + .probe = cs4270_i2c_probe, +}; + +/* + * Global variable to store socdev for i2c probe function. + * + * If struct i2c_driver had a private_data field, we wouldn't need to use + * cs4270_socdec. This is the only way to pass the socdev structure to + * cs4270_i2c_probe(). + * + * The real solution to cs4270_socdev is to create a mechanism + * that maps I2C addresses to snd_soc_device structures. Perhaps the + * creation of the snd_soc_device object should be moved out of + * cs4270_probe() and into cs4270_i2c_probe(), but that would make this + * driver dependent on I2C. The CS4270 supports "stand-alone" mode, whereby + * the chip is *not* connected to the I2C bus, but is instead configured via + * input pins. + */ +static struct snd_soc_device *cs4270_socdev; + +/* + * Initialize the I2C interface of the CS4270 + * + * This function is called for whenever the I2C subsystem finds a device + * at a particular address. + * + * Note: snd_soc_new_pcms() must be called before this function can be called, + * because of snd_ctl_add(). + */ +static int cs4270_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = cs4270_socdev; + struct snd_soc_codec *codec = socdev->codec; + int i; + int ret = 0; + + /* Probing all possible addresses has one drawback: if there are + multiple CS4270s on the bus, then you cannot specify which + socdev is matched with which CS4270. For now, we just reject + this I2C device if the socdev already has one attached. */ + if (codec->control_data) + return -ENODEV; + + /* Note: codec_dai->codec is NULL here */ + + codec->reg_cache = kzalloc(CS4270_NUMREGS, GFP_KERNEL); + if (!codec->reg_cache) { + printk(KERN_ERR "cs4270: could not allocate register cache\n"); + ret = -ENOMEM; + goto error; + } + + /* Verify that we have a CS4270 */ + + ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID); + if (ret < 0) { + printk(KERN_ERR "cs4270: failed to read I2C\n"); + goto error; + } + /* The top four bits of the chip ID should be 1100. */ + if ((ret & 0xF0) != 0xC0) { + /* The device at this address is not a CS4270 codec */ + ret = -ENODEV; + goto error; + } + + printk(KERN_INFO "cs4270: found device at I2C address %X\n", + i2c_client->addr); + printk(KERN_INFO "cs4270: hardware revision %X\n", ret & 0xF); + + codec->control_data = i2c_client; + codec->read = cs4270_read_reg_cache; + codec->write = cs4270_i2c_write; + codec->reg_cache_size = CS4270_NUMREGS; + + /* The I2C interface is set up, so pre-fill our register cache */ + + ret = cs4270_fill_cache(codec); + if (ret < 0) { + printk(KERN_ERR "cs4270: failed to fill register cache\n"); + goto error; + } + + /* Add the non-DAPM controls */ + + for (i = 0; i < ARRAY_SIZE(cs4270_snd_controls); i++) { + struct snd_kcontrol *kctrl = + snd_soc_cnew(&cs4270_snd_controls[i], codec, NULL); + + ret = snd_ctl_add(codec->card, kctrl); + if (ret < 0) + goto error; + } + + i2c_set_clientdata(i2c_client, codec); + + return 0; + +error: + codec->control_data = NULL; + + kfree(codec->reg_cache); + codec->reg_cache = NULL; + codec->reg_cache_size = 0; + + return ret; +} + +#endif /* USE_I2C*/ + +struct snd_soc_dai cs4270_dai = { + .name = "CS4270", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = 0, + .formats = CS4270_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = 0, + .formats = CS4270_FORMATS, + }, +}; +EXPORT_SYMBOL_GPL(cs4270_dai); + +/* + * ASoC probe function + * + * This function is called when the machine driver calls + * platform_device_add(). + */ +static int cs4270_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + printk(KERN_INFO "CS4270 ALSA SoC Codec\n"); + + /* Allocate enough space for the snd_soc_codec structure + and our private data together. */ + codec = kzalloc(ALIGN(sizeof(struct snd_soc_codec), 4) + + sizeof(struct cs4270_private), GFP_KERNEL); + if (!codec) { + printk(KERN_ERR "cs4270: Could not allocate codec structure\n"); + return -ENOMEM; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "CS4270"; + codec->owner = THIS_MODULE; + codec->dai = &cs4270_dai; + codec->num_dai = 1; + codec->private_data = (void *) codec + + ALIGN(sizeof(struct snd_soc_codec), 4); + + socdev->codec = codec; + + /* Register PCMs */ + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "cs4270: failed to create PCMs\n"); + goto error_free_codec; + } + +#ifdef USE_I2C + cs4270_socdev = socdev; + + ret = i2c_add_driver(&cs4270_i2c_driver); + if (ret) { + printk(KERN_ERR "cs4270: failed to attach driver"); + goto error_free_pcms; + } + + /* Did we find a CS4270 on the I2C bus? */ + if (codec->control_data) { + /* Initialize codec ops */ + cs4270_dai.ops.hw_params = cs4270_hw_params; + cs4270_dai.dai_ops.set_sysclk = cs4270_set_dai_sysclk; + cs4270_dai.dai_ops.set_fmt = cs4270_set_dai_fmt; +#ifdef CONFIG_SND_SOC_CS4270_HWMUTE + cs4270_dai.dai_ops.digital_mute = cs4270_mute; +#endif + } else + printk(KERN_INFO "cs4270: no I2C device found, " + "using stand-alone mode\n"); +#else + printk(KERN_INFO "cs4270: I2C disabled, using stand-alone mode\n"); +#endif + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "cs4270: failed to register card\n"); + goto error_del_driver; + } + + return 0; + +error_del_driver: +#ifdef USE_I2C + i2c_del_driver(&cs4270_i2c_driver); + +error_free_pcms: +#endif + snd_soc_free_pcms(socdev); + +error_free_codec: + kfree(socdev->codec); + socdev->codec = NULL; + + return ret; +} + +static int cs4270_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + +#ifdef USE_I2C + i2c_del_driver(&cs4270_i2c_driver); +#endif + + kfree(socdev->codec); + socdev->codec = NULL; + + return 0; +} + +/* + * ASoC codec device structure + * + * Assign this variable to the codec_dev field of the machine driver's + * snd_soc_device structure. + */ +struct snd_soc_codec_device soc_codec_device_cs4270 = { + .probe = cs4270_probe, + .remove = cs4270_remove +}; +EXPORT_SYMBOL_GPL(soc_codec_device_cs4270); + +MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); +MODULE_DESCRIPTION("Cirrus Logic CS4270 ALSA SoC Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4270.h b/sound/soc/codecs/cs4270.h new file mode 100644 index 0000000..adc6cd9 --- /dev/null +++ b/sound/soc/codecs/cs4270.h @@ -0,0 +1,28 @@ +/* + * Cirrus Logic CS4270 ALSA SoC Codec Driver + * + * Author: Timur Tabi <timur@freescale.com> + * + * Copyright 2007 Freescale Semiconductor, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ + +#ifndef _CS4270_H +#define _CS4270_H + +/* + * The ASoC codec DAI structure for the CS4270. Assign this structure to + * the .codec_dai field of your machine driver's snd_soc_dai_link structure. + */ +extern struct snd_soc_dai cs4270_dai; + +/* + * The ASoC codec device structure for the CS4270. Assign this structure + * to the .codec_dev field of your machine driver's snd_soc_device + * structure. + */ +extern struct snd_soc_codec_device soc_codec_device_cs4270; + +#endif diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c new file mode 100644 index 0000000..44ef0da --- /dev/null +++ b/sound/soc/codecs/ssm2602.c @@ -0,0 +1,775 @@ +/* + * File: sound/soc/codecs/ssm2602.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: Driver for ssm2602 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "ssm2602.h" + +#define SSM2602_VERSION "0.1" + +struct snd_soc_codec_device soc_codec_dev_ssm2602; + +/* codec private data */ +struct ssm2602_priv { + unsigned int sysclk; + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +/* + * ssm2602 register cache + * We can't read the ssm2602 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = { + 0x0017, 0x0017, 0x0079, 0x0079, + 0x0000, 0x0000, 0x0000, 0x000a, + 0x0000, 0x0000 +}; + +/* + * read ssm2602 register cache + */ +static inline unsigned int ssm2602_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == SSM2602_RESET) + return 0; + if (reg >= SSM2602_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write ssm2602 register cache + */ +static inline void ssm2602_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= SSM2602_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the ssm2602 register space + */ +static int ssm2602_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 ssm2602 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + ssm2602_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define ssm2602_reset(c) ssm2602_write(c, SSM2602_RESET, 0) + +/*Appending several "None"s just for OSS mixer use*/ +static const char *ssm2602_input_select[] = { + "Line", "Mic", "None", "None", "None", + "None", "None", "None", +}; + +static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum ssm2602_enum[] = { + SOC_ENUM_SINGLE(SSM2602_APANA, 2, 2, ssm2602_input_select), + SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph), +}; + +static const struct snd_kcontrol_new ssm2602_snd_controls[] = { + +SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V, + 7, 1, 0), + +SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), +SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), + +SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), +SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), + +SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), + +SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0), + +SOC_ENUM("Capture Source", ssm2602_enum[0]), + +SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), +}; + +/* add non dapm controls */ +static int ssm2602_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ssm2602_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ssm2602_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Output Mixer */ +static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ssm2602_input_mux_controls = +SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]); + +static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1, + &ssm2602_output_mixer_controls[0], + ARRAY_SIZE(ssm2602_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1), +SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls), +SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1), +SND_SOC_DAPM_INPUT("MICIN"), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static const struct snd_soc_dapm_route audio_conn[] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* input mux */ + {"Input Mux", "Line", "Line Input"}, + {"Input Mux", "Mic", "Mic Bias"}, + {"ADC", NULL, "Input Mux"}, + + /* inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Bias", NULL, "MICIN"}, +}; + +static int ssm2602_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ssm2602_dapm_widgets, + ARRAY_SIZE(ssm2602_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_conn, ARRAY_SIZE(audio_conn)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return i; +} + +static int ssm2602_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + u16 srate; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3; + int i = get_coeff(ssm2602->sysclk, params_rate(params)); + + /*no match is found*/ + if (i == ARRAY_SIZE(coeff_div)) + return -EINVAL; + + srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_write(codec, SSM2602_SRATE, srate); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + ssm2602_write(codec, SSM2602_IFACE, iface); + ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); + return 0; +} + +static int ssm2602_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + struct snd_pcm_runtime *master_runtime; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + */ + if (ssm2602->master_substream) { + master_runtime = ssm2602->master_substream->runtime; + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + ssm2602->slave_substream = substream; + } else + ssm2602->master_substream = substream; + + return 0; +} + +static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + /* set active */ + ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); + + return 0; +} + +static void ssm2602_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + /* deactivate */ + if (!codec->active) + ssm2602_write(codec, SSM2602_ACTIVE, 0); +} + +static int ssm2602_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ssm2602_read_reg_cache(codec, SSM2602_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE; + if (mute) + ssm2602_write(codec, SSM2602_APDIGI, + mute_reg | APDIGI_ENABLE_DAC_MUTE); + else + ssm2602_write(codec, SSM2602_APDIGI, mute_reg); + return 0; +} + +static int ssm2602_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 ssm2602_priv *ssm2602 = codec->private_data; + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + ssm2602->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + 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 |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + ssm2602_write(codec, SSM2602_IFACE, iface); + return 0; +} + +static int ssm2602_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = ssm2602_read_reg_cache(codec, SSM2602_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + ssm2602_write(codec, SSM2602_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + ssm2602_write(codec, SSM2602_PWR, reg | PWR_CLK_OUT_PDN); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_write(codec, SSM2602_PWR, 0xffff); + break; + + } + codec->bias_level = level; + return 0; +} + +#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +struct snd_soc_dai ssm2602_dai = { + .name = "SSM2602", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .ops = { + .startup = ssm2602_startup, + .prepare = ssm2602_pcm_prepare, + .hw_params = ssm2602_hw_params, + .shutdown = ssm2602_shutdown, + }, + .dai_ops = { + .digital_mute = ssm2602_mute, + .set_sysclk = ssm2602_set_dai_sysclk, + .set_fmt = ssm2602_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(ssm2602_dai); + +static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ssm2602_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(ssm2602_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ssm2602_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the ssm2602 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ssm2602_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "SSM2602"; + codec->owner = THIS_MODULE; + codec->read = ssm2602_read_reg_cache; + codec->write = ssm2602_write; + codec->set_bias_level = ssm2602_set_bias_level; + codec->dai = &ssm2602_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(ssm2602_reg); + codec->reg_cache = kmemdup(ssm2602_reg, sizeof(ssm2602_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + ssm2602_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("ssm2602: failed to create pcms\n"); + goto pcm_err; + } + /*power on device*/ + ssm2602_write(codec, SSM2602_ACTIVE, 0); + /* set the update bits */ + reg = ssm2602_read_reg_cache(codec, SSM2602_LINVOL); + ssm2602_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_RINVOL); + ssm2602_write(codec, SSM2602_RINVOL, reg | RINVOL_RLIN_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_LOUT1V); + ssm2602_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V); + ssm2602_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH); + /*select Line in as default input*/ + ssm2602_write(codec, SSM2602_APANA, + APANA_ENABLE_MIC_BOOST2 | APANA_SELECT_DAC | + APANA_ENABLE_MIC_BOOST); + ssm2602_write(codec, SSM2602_PWR, 0); + + ssm2602_add_controls(codec); + ssm2602_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + pr_err("ssm2602: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *ssm2602_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + * ssm2602 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static int ssm2602_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = ssm2602_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = ssm2602_init(socdev); + if (ret < 0) + pr_err("failed to initialise SSM2602\n"); + + return ret; +} + +static int ssm2602_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id ssm2602_i2c_id[] = { + { "ssm2602", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id); +/* corgi i2c codec control layer */ +static struct i2c_driver ssm2602_i2c_driver = { + .driver = { + .name = "SSM2602 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = ssm2602_i2c_probe, + .remove = ssm2602_i2c_remove, + .id_table = ssm2602_i2c_id, +}; + +static int ssm2602_add_i2c_device(struct platform_device *pdev, + const struct ssm2602_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&ssm2602_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "ssm2602", I2C_NAME_SIZE); + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + return 0; +err_driver: + i2c_del_driver(&ssm2602_i2c_driver); + return -ENODEV; +} +#endif + +static int ssm2602_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ssm2602_setup_data *setup; + struct snd_soc_codec *codec; + struct ssm2602_priv *ssm2602; + int ret = 0; + + pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL); + if (ssm2602 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ssm2602; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ssm2602_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = ssm2602_add_i2c_device(pdev, setup); + } +#else + /* other interfaces */ +#endif + return ret; +} + +/* remove everything here */ +static int ssm2602_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&ssm2602_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ssm2602 = { + .probe = ssm2602_probe, + .remove = ssm2602_remove, + .suspend = ssm2602_suspend, + .resume = ssm2602_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2602); + +MODULE_DESCRIPTION("ASoC ssm2602 driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h new file mode 100644 index 0000000..f344e6d --- /dev/null +++ b/sound/soc/codecs/ssm2602.h @@ -0,0 +1,130 @@ +/* + * File: sound/soc/codecs/ssm2602.h + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SSM2602_H +#define _SSM2602_H + +/* SSM2602 Codec Register definitions */ + +#define SSM2602_LINVOL 0x00 +#define SSM2602_RINVOL 0x01 +#define SSM2602_LOUT1V 0x02 +#define SSM2602_ROUT1V 0x03 +#define SSM2602_APANA 0x04 +#define SSM2602_APDIGI 0x05 +#define SSM2602_PWR 0x06 +#define SSM2602_IFACE 0x07 +#define SSM2602_SRATE 0x08 +#define SSM2602_ACTIVE 0x09 +#define SSM2602_RESET 0x0f + +/*SSM2602 Codec Register Field definitions + *(Mask value to extract the corresponding Register field) + */ + +/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/ +#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */ +#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */ +#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */ + +/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/ +#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */ +#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */ +#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */ + +/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/ +#define LOUT1V_LHP_VOL 0x07F /* Left Channel Headphone volume control */ +#define LOUT1V_ENABLE_LZC 0x080 /* Left Channel Zero cross detect enable */ +#define LOUT1V_LRHP_BOTH 0x100 /* Left Channel Headphone volume update */ + +/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/ +#define ROUT1V_RHP_VOL 0x07F /* Right Channel Headphone volume control */ +#define ROUT1V_ENABLE_RZC 0x080 /* Right Channel Zero cross detect enable */ +#define ROUT1V_RLHP_BOTH 0x100 /* Right Channel Headphone volume update */ + +/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/ +#define APANA_ENABLE_MIC_BOOST 0x001 /* Primary Microphone Amplifier gain booster control */ +#define APANA_ENABLE_MIC_MUTE 0x002 /* Microphone Mute Control */ +#define APANA_ADC_IN_SELECT 0x004 /* Microphone/Line IN select to ADC (1=MIC, 0=Line In) */ +#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */ +#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */ +#define APANA_ENABLE_SIDETONE 0x020 /* Enable/Disable Side Tone */ +#define APANA_SIDETONE_ATTN 0x0C0 /* Side Tone Attenuation */ +#define APANA_ENABLE_MIC_BOOST2 0x100 /* Secondary Microphone Amplifier gain booster control */ + +/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/ +#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */ +#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */ +#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */ +#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */ + +/*Power Down Control (SSM2602_REG_POWER) + *(1=Enable PowerDown, 0=Disable PowerDown) + */ +#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */ +#define PWR_MIC_PDN 0x002 /* Microphone Input & Bias Power Down */ +#define PWR_ADC_PDN 0x004 /* ADC Power Down */ +#define PWR_DAC_PDN 0x008 /* DAC Power Down */ +#define PWR_OUT_PDN 0x010 /* Outputs Power Down */ +#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */ +#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */ +#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */ + +/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/ +#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */ +#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */ +#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */ +#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */ +#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */ +#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */ + +/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/ +#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */ +#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */ +#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */ +#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */ +#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */ + +/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/ +#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */ + +/*********************************************************************/ + +#define SSM2602_CACHEREGNUM 10 + +#define SSM2602_SYSCLK 0 +#define SSM2602_DAI 0 + +struct ssm2602_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ssm2602_dai; +extern struct snd_soc_codec_device soc_codec_dev_ssm2602; + +#endif diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c new file mode 100644 index 0000000..44308da --- /dev/null +++ b/sound/soc/codecs/tlv320aic23.c @@ -0,0 +1,714 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, <arunks@mistralsolutions.com> + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd., + * + * Based on sound/soc/codecs/wm8731.c by Richard Purdie + * + * 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. + * + * Notes: + * The AIC23 is a driver for a low power stereo audio + * codec tlv320aic23 + * + * The machine layer should disable unsupported inputs/outputs by + * snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/initval.h> + +#include "tlv320aic23.h" + +#define AIC23_VERSION "0.1" + +struct tlv320aic23_srate_reg_info { + u32 sample_rate; + u8 control; /* SR3, SR2, SR1, SR0 and BOSR */ + u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */ +}; + +/* + * AIC23 register cache + */ +static const u16 tlv320aic23_reg[] = { + 0x0097, 0x0097, 0x00F9, 0x00F9, /* 0 */ + 0x001A, 0x0004, 0x0007, 0x0001, /* 4 */ + 0x0020, 0x0000, 0x0000, 0x0000, /* 8 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 12 */ +}; + +/* + * read tlv320aic23 register cache + */ +static inline unsigned int tlv320aic23_read_reg_cache(struct snd_soc_codec + *codec, unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= ARRAY_SIZE(tlv320aic23_reg)) + return -1; + return cache[reg]; +} + +/* + * write tlv320aic23 register cache + */ +static inline void tlv320aic23_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, u16 value) +{ + u16 *cache = codec->reg_cache; + if (reg >= ARRAY_SIZE(tlv320aic23_reg)) + return; + cache[reg] = value; +} + +/* + * write to the tlv320aic23 register space + */ +static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + + u8 data[2]; + + /* TLV320AIC23 has 7 bit address and 9 bits of data + * so we need to switch one data bit into reg and rest + * of data into val + */ + + if ((reg < 0 || reg > 9) && (reg != 15)) { + printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg); + return -1; + } + + data[0] = (reg << 1) | (value >> 8 & 0x01); + data[1] = value & 0xff; + + tlv320aic23_write_reg_cache(codec, reg, value); + + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + + printk(KERN_ERR "%s cannot write %03x to register R%d\n", __func__, + value, reg); + + return -EIO; +} + +static const char *rec_src_text[] = { "Line", "Mic" }; +static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum rec_src_enum = + SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text); + +static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls = +SOC_DAPM_ENUM("Input Select", rec_src_enum); + +static const struct soc_enum tlv320aic23_rec_src = + SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text); +static const struct soc_enum tlv320aic23_deemph = + SOC_ENUM_SINGLE(TLV320AIC23_DIGT, 1, 4, deemph_text); + +static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0); +static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0); + +static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 val, reg; + + val = (ucontrol->value.integer.value[0] & 0x07); + + /* linear conversion to userspace + * 000 = -6db + * 001 = -9db + * 010 = -12db + * 011 = -18db (Min) + * 100 = 0db (Max) + */ + val = (val >= 4) ? 4 : (3 - val); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (~0x1C0); + tlv320aic23_write(codec, TLV320AIC23_ANLG, reg | (val << 6)); + + return 0; +} + +static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 val; + + val = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (0x1C0); + val = val >> 6; + val = (val >= 4) ? 4 : (3 - val); + ucontrol->value.integer.value[0] = val; + return 0; + +} + +#define SOC_TLV320AIC23_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, .get = snd_soc_tlv320aic23_get_volsw,\ + .put = snd_soc_tlv320aic23_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + +static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL, + TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv), + SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1), + SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 7, 1, 0), + SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv), + SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1), + SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0), + SOC_TLV320AIC23_SINGLE_TLV("Sidetone Volume", TLV320AIC23_ANLG, + 6, 4, 0, sidetone_vol_tlv), + SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph), +}; + +/* add non dapm controls */ +static int tlv320aic23_add_controls(struct snd_soc_codec *codec) +{ + + int err, i; + + for (i = 0; i < ARRAY_SIZE(tlv320aic23_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&tlv320aic23_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; + +} + +/* PGA Mixer controls for Line and Mic switch */ +static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1), + SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0, + &tlv320aic23_rec_src_mux_controls), + SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1, + &tlv320aic23_output_mixer_controls[0], + ARRAY_SIZE(tlv320aic23_output_mixer_controls)), + SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LHPOUT"), + SND_SOC_DAPM_OUTPUT("RHPOUT"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("LLINEIN"), + SND_SOC_DAPM_INPUT("RLINEIN"), + + SND_SOC_DAPM_INPUT("MICIN"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Output Mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Input"}, + + /* Outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + + /* Inputs */ + {"Line Input", "NULL", "LLINEIN"}, + {"Line Input", "NULL", "RLINEIN"}, + + {"Mic Input", "NULL", "MICIN"}, + + /* input mux */ + {"Capture Source", "Line", "Line Input"}, + {"Capture Source", "Mic", "Mic Input"}, + {"ADC", NULL, "Capture Source"}, + +}; + +/* tlv320aic23 related */ +static const struct tlv320aic23_srate_reg_info srate_reg_info[] = { + {4000, 0x06, 1}, /* 4000 */ + {8000, 0x06, 0}, /* 8000 */ + {16000, 0x0C, 1}, /* 16000 */ + {22050, 0x11, 1}, /* 22050 */ + {24000, 0x00, 1}, /* 24000 */ + {32000, 0x0C, 0}, /* 32000 */ + {44100, 0x11, 0}, /* 44100 */ + {48000, 0x00, 0}, /* 48000 */ + {88200, 0x1F, 0}, /* 88200 */ + {96000, 0x0E, 0}, /* 96000 */ +}; + +static int tlv320aic23_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface_reg, data; + u8 count = 0; + + iface_reg = + tlv320aic23_read_reg_cache(codec, + TLV320AIC23_DIGT_FMT) & ~(0x03 << 2); + + /* Search for the right sample rate */ + /* Verify what happens if the rate is not supported + * now it goes to 96Khz */ + while ((srate_reg_info[count].sample_rate != params_rate(params)) && + (count < ARRAY_SIZE(srate_reg_info))) { + count++; + } + + data = (srate_reg_info[count].divider << TLV320AIC23_CLKIN_SHIFT) | + (srate_reg_info[count]. control << TLV320AIC23_BOSR_SHIFT) | + TLV320AIC23_USB_CLK_ON; + + tlv320aic23_write(codec, TLV320AIC23_SRATE, data); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface_reg |= (0x01 << 2); + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface_reg |= (0x02 << 2); + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface_reg |= (0x03 << 2); + break; + } + tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* set active */ + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001); + + return 0; +} + +static void tlv320aic23_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* deactivate */ + if (!codec->active) { + udelay(50); + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + } +} + +static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg; + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT); + if (mute) + reg |= TLV320AIC23_DACM_MUTE; + + else + reg &= ~TLV320AIC23_DACM_MUTE; + + tlv320aic23_write(codec, TLV320AIC23_DIGT, reg); + + return 0; +} + +static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface_reg; + + iface_reg = + tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT_FMT) & (~0x03); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface_reg |= TLV320AIC23_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface_reg |= TLV320AIC23_FOR_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg |= TLV320AIC23_FOR_DSP; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg |= TLV320AIC23_FOR_LJUST; + break; + default: + return -EINVAL; + + } + + tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_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; + + switch (freq) { + case 12000000: + return 0; + } + return -EINVAL; +} + +static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + tlv320aic23_write(codec, TLV320AIC23_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + tlv320aic23_write(codec, TLV320AIC23_PWR, reg | 0x0040); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + tlv320aic23_write(codec, TLV320AIC23_PWR, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +#define AIC23_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai tlv320aic23_dai = { + .name = "tlv320aic23", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .ops = { + .prepare = tlv320aic23_pcm_prepare, + .hw_params = tlv320aic23_hw_params, + .shutdown = tlv320aic23_shutdown, + }, + .dai_ops = { + .digital_mute = tlv320aic23_mute, + .set_fmt = tlv320aic23_set_dai_fmt, + .set_sysclk = tlv320aic23_set_dai_sysclk, + } +}; +EXPORT_SYMBOL_GPL(tlv320aic23_dai); + +static int tlv320aic23_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int tlv320aic23_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u16 reg; + + /* Sync reg_cache with the hardware */ + for (reg = 0; reg < ARRAY_SIZE(tlv320aic23_reg); i++) { + u16 val = tlv320aic23_read_reg_cache(codec, reg); + tlv320aic23_write(codec, reg, val); + } + + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + tlv320aic23_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} + +/* + * initialise the AIC23 driver + * register the mixer and dsp interfaces with the kernel + */ +static int tlv320aic23_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + u16 reg; + + codec->name = "tlv320aic23"; + codec->owner = THIS_MODULE; + codec->read = tlv320aic23_read_reg_cache; + codec->write = tlv320aic23_write; + codec->set_bias_level = tlv320aic23_set_bias_level; + codec->dai = &tlv320aic23_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(tlv320aic23_reg); + codec->reg_cache = + kmemdup(tlv320aic23_reg, sizeof(tlv320aic23_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* Reset codec */ + tlv320aic23_write(codec, TLV320AIC23_RESET, 0); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + tlv320aic23_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K); + + /* Unmute input */ + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_LINVOL); + tlv320aic23_write(codec, TLV320AIC23_LINVOL, + (reg & (~TLV320AIC23_LIM_MUTED)) | + (TLV320AIC23_LRS_ENABLED)); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_RINVOL); + tlv320aic23_write(codec, TLV320AIC23_RINVOL, + (reg & (~TLV320AIC23_LIM_MUTED)) | + TLV320AIC23_LRS_ENABLED); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG); + tlv320aic23_write(codec, TLV320AIC23_ANLG, + (reg) & (~TLV320AIC23_BYPASS_ON) & + (~TLV320AIC23_MICM_MUTED)); + + /* Default output volume */ + tlv320aic23_write(codec, TLV320AIC23_LCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & + TLV320AIC23_OUT_VOL_MASK); + tlv320aic23_write(codec, TLV320AIC23_RCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & + TLV320AIC23_OUT_VOL_MASK); + + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1); + + tlv320aic23_add_controls(codec); + tlv320aic23_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} +static struct snd_soc_device *tlv320aic23_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + * If the i2c layer weren't so broken, we could pass this kind of data + * around + */ +static int tlv320aic23_codec_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + struct snd_soc_device *socdev = tlv320aic23_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = tlv320aic23_init(socdev); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to initialise AIC23\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} +static int __exit tlv320aic23_i2c_remove(struct i2c_client *i2c) +{ + put_device(&i2c->dev); + return 0; +} + +static const struct i2c_device_id tlv320aic23_id[] = { + {"tlv320aic23", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tlv320aic23_id); + +static struct i2c_driver tlv320aic23_i2c_driver = { + .driver = { + .name = "tlv320aic23", + }, + .probe = tlv320aic23_codec_probe, + .remove = __exit_p(tlv320aic23_i2c_remove), + .id_table = tlv320aic23_id, +}; + +#endif + +static int tlv320aic23_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION); + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + tlv320aic23_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + codec->hw_write = (hw_write_t) i2c_master_send; + codec->hw_read = NULL; + ret = i2c_add_driver(&tlv320aic23_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); +#endif + return ret; +} + +static int tlv320aic23_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&tlv320aic23_i2c_driver); +#endif + kfree(codec->reg_cache); + kfree(codec); + + return 0; +} +struct snd_soc_codec_device soc_codec_dev_tlv320aic23 = { + .probe = tlv320aic23_probe, + .remove = tlv320aic23_remove, + .suspend = tlv320aic23_suspend, + .resume = tlv320aic23_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320aic23); + +MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver"); +MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic23.h b/sound/soc/codecs/tlv320aic23.h new file mode 100644 index 0000000..79d1faf --- /dev/null +++ b/sound/soc/codecs/tlv320aic23.h @@ -0,0 +1,122 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, <arunks@mistralsolutions.com> + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd + * + * 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 _TLV320AIC23_H +#define _TLV320AIC23_H + +/* Codec TLV320AIC23 */ +#define TLV320AIC23_LINVOL 0x00 +#define TLV320AIC23_RINVOL 0x01 +#define TLV320AIC23_LCHNVOL 0x02 +#define TLV320AIC23_RCHNVOL 0x03 +#define TLV320AIC23_ANLG 0x04 +#define TLV320AIC23_DIGT 0x05 +#define TLV320AIC23_PWR 0x06 +#define TLV320AIC23_DIGT_FMT 0x07 +#define TLV320AIC23_SRATE 0x08 +#define TLV320AIC23_ACTIVE 0x09 +#define TLV320AIC23_RESET 0x0F + +/* Left (right) line input volume control register */ +#define TLV320AIC23_LRS_ENABLED 0x0100 +#define TLV320AIC23_LIM_MUTED 0x0080 +#define TLV320AIC23_LIV_DEFAULT 0x0017 +#define TLV320AIC23_LIV_MAX 0x001f +#define TLV320AIC23_LIV_MIN 0x0000 + +/* Left (right) channel headphone volume control register */ +#define TLV320AIC23_LZC_ON 0x0080 +#define TLV320AIC23_LHV_DEFAULT 0x0079 +#define TLV320AIC23_LHV_MAX 0x007f +#define TLV320AIC23_LHV_MIN 0x0000 + +/* Analog audio path control register */ +#define TLV320AIC23_STA_REG(x) ((x)<<6) +#define TLV320AIC23_STE_ENABLED 0x0020 +#define TLV320AIC23_DAC_SELECTED 0x0010 +#define TLV320AIC23_BYPASS_ON 0x0008 +#define TLV320AIC23_INSEL_MIC 0x0004 +#define TLV320AIC23_MICM_MUTED 0x0002 +#define TLV320AIC23_MICB_20DB 0x0001 + +/* Digital audio path control register */ +#define TLV320AIC23_DACM_MUTE 0x0008 +#define TLV320AIC23_DEEMP_32K 0x0002 +#define TLV320AIC23_DEEMP_44K 0x0004 +#define TLV320AIC23_DEEMP_48K 0x0006 +#define TLV320AIC23_ADCHP_ON 0x0001 + +/* Power control down register */ +#define TLV320AIC23_DEVICE_PWR_OFF 0x0080 +#define TLV320AIC23_CLK_OFF 0x0040 +#define TLV320AIC23_OSC_OFF 0x0020 +#define TLV320AIC23_OUT_OFF 0x0010 +#define TLV320AIC23_DAC_OFF 0x0008 +#define TLV320AIC23_ADC_OFF 0x0004 +#define TLV320AIC23_MIC_OFF 0x0002 +#define TLV320AIC23_LINE_OFF 0x0001 + +/* Digital audio interface register */ +#define TLV320AIC23_MS_MASTER 0x0040 +#define TLV320AIC23_LRSWAP_ON 0x0020 +#define TLV320AIC23_LRP_ON 0x0010 +#define TLV320AIC23_IWL_16 0x0000 +#define TLV320AIC23_IWL_20 0x0004 +#define TLV320AIC23_IWL_24 0x0008 +#define TLV320AIC23_IWL_32 0x000C +#define TLV320AIC23_FOR_I2S 0x0002 +#define TLV320AIC23_FOR_DSP 0x0003 +#define TLV320AIC23_FOR_LJUST 0x0001 + +/* Sample rate control register */ +#define TLV320AIC23_CLKOUT_HALF 0x0080 +#define TLV320AIC23_CLKIN_HALF 0x0040 +#define TLV320AIC23_BOSR_384fs 0x0002 /* BOSR_272fs in USB mode */ +#define TLV320AIC23_USB_CLK_ON 0x0001 +#define TLV320AIC23_SR_MASK 0xf +#define TLV320AIC23_CLKOUT_SHIFT 7 +#define TLV320AIC23_CLKIN_SHIFT 6 +#define TLV320AIC23_SR_SHIFT 2 +#define TLV320AIC23_BOSR_SHIFT 1 + +/* Digital interface register */ +#define TLV320AIC23_ACT_ON 0x0001 + +/* + * AUDIO related MACROS + */ + +#define TLV320AIC23_DEFAULT_OUT_VOL 0x70 +#define TLV320AIC23_DEFAULT_IN_VOLUME 0x10 + +#define TLV320AIC23_OUT_VOL_MIN TLV320AIC23_LHV_MIN +#define TLV320AIC23_OUT_VOL_MAX TLV320AIC23_LHV_MAX +#define TLV320AIC23_OUT_VO_RANGE (TLV320AIC23_OUT_VOL_MAX - \ + TLV320AIC23_OUT_VOL_MIN) +#define TLV320AIC23_OUT_VOL_MASK TLV320AIC23_OUT_VOL_MAX + +#define TLV320AIC23_IN_VOL_MIN TLV320AIC23_LIV_MIN +#define TLV320AIC23_IN_VOL_MAX TLV320AIC23_LIV_MAX +#define TLV320AIC23_IN_VOL_RANGE (TLV320AIC23_IN_VOL_MAX - \ + TLV320AIC23_IN_VOL_MIN) +#define TLV320AIC23_IN_VOL_MASK TLV320AIC23_IN_VOL_MAX + +#define TLV320AIC23_SIDETONE_MASK 0x1c0 +#define TLV320AIC23_SIDETONE_0 0x100 +#define TLV320AIC23_SIDETONE_6 0x000 +#define TLV320AIC23_SIDETONE_9 0x040 +#define TLV320AIC23_SIDETONE_12 0x080 +#define TLV320AIC23_SIDETONE_18 0x0c0 + +extern struct snd_soc_dai tlv320aic23_dai; +extern struct snd_soc_codec_device soc_codec_dev_tlv320aic23; + +#endif /* _TLV320AIC23_H */ diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c new file mode 100644 index 0000000..bed8a9e --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.c @@ -0,0 +1,520 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * ALSA SoC CODEC driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/soc-of-simple.h> +#include <sound/initval.h> + +#include "tlv320aic26.h" + +MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver"); +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_LICENSE("GPL"); + +/* AIC26 driver private data */ +struct aic26 { + struct spi_device *spi; + struct snd_soc_codec codec; + u16 reg_cache[AIC26_NUM_REGS]; /* shadow registers */ + int master; + int datfm; + int mclk; + + /* Keyclick parameters */ + int keyclick_amplitude; + int keyclick_freq; + int keyclick_len; +}; + +/* --------------------------------------------------------------------- + * Register access routines + */ +static unsigned int aic26_reg_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd, value; + u8 buffer[2]; + int rc; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + /* Do SPI transfer; first 16bits are command; remaining is + * register contents */ + cmd = AIC26_READ_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + value = (buffer[0] << 8) | buffer[1]; + + /* Update the cache before returning with the value */ + cache[reg] = value; + return value; +} + +static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + return cache[reg]; +} + +static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd; + u8 buffer[4]; + int rc; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + /* Do SPI transfer; first 16bits are command; remaining is data + * to write into register */ + cmd = AIC26_WRITE_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + buffer[2] = value >> 8; + buffer[3] = value; + rc = spi_write(aic26->spi, buffer, 4); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + + /* update cache before returning */ + cache[reg] = value; + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Operations + */ +static int aic26_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct aic26 *aic26 = codec->private_data; + int fsref, divisor, wlen, pval, jval, dval, qval; + u16 reg; + + dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n", + substream, params); + dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params), + params_format(params)); + + switch (params_rate(params)) { + case 8000: fsref = 48000; divisor = AIC26_DIV_6; break; + case 11025: fsref = 44100; divisor = AIC26_DIV_4; break; + case 12000: fsref = 48000; divisor = AIC26_DIV_4; break; + case 16000: fsref = 48000; divisor = AIC26_DIV_3; break; + case 22050: fsref = 44100; divisor = AIC26_DIV_2; break; + case 24000: fsref = 48000; divisor = AIC26_DIV_2; break; + case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break; + case 44100: fsref = 44100; divisor = AIC26_DIV_1; break; + case 48000: fsref = 48000; divisor = AIC26_DIV_1; break; + default: + dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL; + } + + /* select data word length */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break; + case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break; + default: + dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + /* Configure PLL */ + pval = 1; + jval = (fsref == 44100) ? 7 : 8; + dval = (fsref == 44100) ? 5264 : 1920; + qval = 0; + reg = 0x8000 | qval << 11 | pval << 8 | jval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg); + reg = dval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + if (aic26->master) + reg |= 0x0800; + if (fsref == 48000) + reg |= 0x2000; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Audio Control 1 (FSref divisor) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1); + reg &= ~0x0fff; + reg |= wlen | aic26->datfm | (divisor << 3) | divisor; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg); + + return 0; +} + +/** + * aic26_mute - Mute control to reduce noise when changing audio format + */ +static int aic26_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic26 *aic26 = codec->private_data; + u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN); + + dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n", + dai, mute); + + if (mute) + reg |= 0x8080; + else + reg &= ~0x8080; + aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg); + + return 0; +} + +static int aic26_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i," + " freq=%i, dir=%i)\n", + codec_dai, clk_id, freq, dir); + + /* MCLK needs to fall between 2MHz and 50 MHz */ + if ((freq < 2000000) || (freq > 50000000)) + return -EINVAL; + + aic26->mclk = freq; + return 0; +} + +static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n", + codec_dai, fmt); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break; + case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break; + default: + dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break; + case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break; + case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break; + case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break; + default: + dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Definition + */ +#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) +#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +struct snd_soc_dai aic26_dai = { + .name = "tlv320aic26", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .ops = { + .hw_params = aic26_hw_params, + }, + .dai_ops = { + .digital_mute = aic26_mute, + .set_sysclk = aic26_set_sysclk, + .set_fmt = aic26_set_fmt, + }, +}; +EXPORT_SYMBOL_GPL(aic26_dai); + +/* --------------------------------------------------------------------- + * ALSA controls + */ +static const char *aic26_capture_src_text[] = {"Mic", "Aux"}; +static const struct soc_enum aic26_capture_src_enum = + SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12, 2, aic26_capture_src_text); + +static const struct snd_kcontrol_new aic26_snd_controls[] = { + /* Output */ + SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1), + SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1), + SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0), + SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1), + SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0), + SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0), + SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0), + SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0), + SOC_ENUM("Capture Source", aic26_capture_src_enum), +}; + +/* --------------------------------------------------------------------- + * SoC CODEC portion of driver: probe and release routines + */ +static int aic26_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct snd_kcontrol *kcontrol; + struct aic26 *aic26; + int i, ret, err; + + dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n"); + dev_dbg(&pdev->dev, "socdev=%p\n", socdev); + dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data); + + /* Fetch the relevant aic26 private data here (it's already been + * stored in the .codec pointer) */ + aic26 = socdev->codec_data; + if (aic26 == NULL) { + dev_err(&pdev->dev, "aic26: missing codec pointer\n"); + return -ENODEV; + } + codec = &aic26->codec; + socdev->codec = codec; + + dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n", + &pdev->dev, socdev->dev); + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to create pcms\n"); + return -ENODEV; + } + + /* register controls */ + dev_dbg(&pdev->dev, "Registering controls\n"); + for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) { + kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL); + err = snd_ctl_add(codec->card, kcontrol); + WARN_ON(err < 0); + } + + /* CODEC is setup, we can register the card now */ + dev_dbg(&pdev->dev, "Registering card\n"); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to register card\n"); + goto card_err; + } + return 0; + + card_err: + snd_soc_free_pcms(socdev); + return ret; +} + +static int aic26_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + snd_soc_free_pcms(socdev); + return 0; +} + +struct snd_soc_codec_device aic26_soc_codec_dev = { + .probe = aic26_probe, + .remove = aic26_remove, +}; +EXPORT_SYMBOL_GPL(aic26_soc_codec_dev); + +/* --------------------------------------------------------------------- + * SPI device portion of driver: sysfs files for debugging + */ + +static ssize_t aic26_keyclick_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val, amp, freq, len; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + amp = (val >> 12) & 0x7; + freq = (125 << ((val >> 8) & 0x7)) >> 1; + len = 2 * (1 + ((val >> 4) & 0xf)); + + return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len); +} + +/* Any write to the keyclick attribute will trigger the keyclick event */ +static ssize_t aic26_keyclick_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + val |= 0x8000; + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val); + + return count; +} + +static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set); + +/* --------------------------------------------------------------------- + * SPI device portion of driver: probe and release routines and SPI + * driver registration. + */ +static int aic26_spi_probe(struct spi_device *spi) +{ + struct aic26 *aic26; + int rc, i, reg; + + dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n"); + + /* Allocate driver data */ + aic26 = kzalloc(sizeof *aic26, GFP_KERNEL); + if (!aic26) + return -ENOMEM; + + /* Initialize the driver data */ + aic26->spi = spi; + dev_set_drvdata(&spi->dev, aic26); + + /* Setup what we can in the codec structure so that the register + * access functions will work as expected. More will be filled + * out when it is probed by the SoC CODEC part of this driver */ + aic26->codec.private_data = aic26; + aic26->codec.name = "aic26"; + aic26->codec.owner = THIS_MODULE; + aic26->codec.dai = &aic26_dai; + aic26->codec.num_dai = 1; + aic26->codec.read = aic26_reg_read; + aic26->codec.write = aic26_reg_write; + aic26->master = 1; + mutex_init(&aic26->codec.mutex); + INIT_LIST_HEAD(&aic26->codec.dapm_widgets); + INIT_LIST_HEAD(&aic26->codec.dapm_paths); + aic26->codec.reg_cache_size = AIC26_NUM_REGS; + aic26->codec.reg_cache = aic26->reg_cache; + + /* Reset the codec to power on defaults */ + aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00); + + /* Power up CODEC */ + aic26_reg_write(&aic26->codec, AIC26_REG_POWER_CTRL, 0); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read(&aic26->codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + reg |= 0x0800; /* set master mode */ + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Fill register cache */ + for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++) + aic26_reg_read(&aic26->codec, i); + + /* Register the sysfs files for debugging */ + /* Create SysFS files */ + rc = device_create_file(&spi->dev, &dev_attr_keyclick); + if (rc) + dev_info(&spi->dev, "error creating sysfs files\n"); + +#if defined(CONFIG_SND_SOC_OF_SIMPLE) + /* Tell the of_soc helper about this codec */ + of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai, + spi->dev.archdata.of_node); +#endif + + dev_dbg(&spi->dev, "SPI device initialized\n"); + return 0; +} + +static int aic26_spi_remove(struct spi_device *spi) +{ + struct aic26 *aic26 = dev_get_drvdata(&spi->dev); + + kfree(aic26); + + return 0; +} + +static struct spi_driver aic26_spi = { + .driver = { + .name = "tlv320aic26", + .owner = THIS_MODULE, + }, + .probe = aic26_spi_probe, + .remove = aic26_spi_remove, +}; + +static int __init aic26_init(void) +{ + return spi_register_driver(&aic26_spi); +} +module_init(aic26_init); + +static void __exit aic26_exit(void) +{ + spi_unregister_driver(&aic26_spi); +} +module_exit(aic26_exit); diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h new file mode 100644 index 0000000..786ba16 --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.h @@ -0,0 +1,96 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * register definitions + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#ifndef _TLV320AIC16_H_ +#define _TLV320AIC16_H_ + +/* AIC26 Registers */ +#define AIC26_READ_COMMAND_WORD(addr) ((1 << 15) | (addr << 5)) +#define AIC26_WRITE_COMMAND_WORD(addr) ((0 << 15) | (addr << 5)) +#define AIC26_PAGE_ADDR(page, offset) ((page << 6) | offset) +#define AIC26_NUM_REGS AIC26_PAGE_ADDR(3, 0) + +/* Page 0: Auxillary data registers */ +#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05) +#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06) +#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07) +#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09) +#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A) + +/* Page 1: Auxillary control registers */ +#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00) +#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01) +#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03) +#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04) + +/* Page 2: Audio control registers */ +#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00) +#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01) +#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02) +#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03) +#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04) +#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05) +#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06) + +#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07) +#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08) +#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09) +#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A) +#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B) +#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C) +#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D) +#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E) +#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F) +#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10) +#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11) +#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12) +#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13) +#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14) +#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15) +#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16) +#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17) +#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18) +#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19) +#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A) + +#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B) +#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C) +#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D) +#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E) + +/* fsref dividers; used in register 'Audio Control 1' */ +enum aic26_divisors { + AIC26_DIV_1 = 0, + AIC26_DIV_1_5 = 1, + AIC26_DIV_2 = 2, + AIC26_DIV_3 = 3, + AIC26_DIV_4 = 4, + AIC26_DIV_5 = 5, + AIC26_DIV_5_5 = 6, + AIC26_DIV_6 = 7, +}; + +/* Digital data format */ +enum aic26_datfm { + AIC26_DATFM_I2S = 0 << 8, + AIC26_DATFM_DSP = 1 << 8, + AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */ + AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */ +}; + +/* Sample word length in bits; used in register 'Audio Control 1' */ +enum aic26_wlen { + AIC26_WLEN_16 = 0 << 10, + AIC26_WLEN_20 = 1 << 10, + AIC26_WLEN_24 = 2 << 10, + AIC26_WLEN_32 = 3 << 10, +}; + +extern struct snd_soc_dai aic26_dai; +extern struct snd_soc_codec_device aic26_soc_codec_dev; + +#endif /* _TLV320AIC16_H_ */ diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c new file mode 100644 index 0000000..cff276e --- /dev/null +++ b/sound/soc/codecs/tlv320aic3x.c @@ -0,0 +1,1346 @@ +/* + * ALSA SoC TLV320AIC3X codec driver + * + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> + * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> + * + * Based on sound/soc/codecs/wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Notes: + * The AIC3X is a driver for a low power stereo audio + * codecs aic31, aic32, aic33. + * + * It supports full aic33 codec functionality. + * The compatibility with aic32, aic31 is as follows: + * aic32 | aic31 + * --------------------------------------- + * MONO_LOUT -> N/A | MONO_LOUT -> N/A + * | IN1L -> LINE1L + * | IN1R -> LINE1R + * | IN2L -> LINE2L + * | IN2R -> LINE2R + * | MIC3L/R -> N/A + * truncated internal functionality in + * accordance with documentation + * --------------------------------------- + * + * Hence the machine layer should disable unsupported inputs/outputs by + * snd_soc_dapm_disable_pin(codec, "MONO_LOUT"), etc. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "tlv320aic3x.h" + +#define AIC3X_VERSION "0.2" + +/* codec private data */ +struct aic3x_priv { + unsigned int sysclk; + int master; +}; + +/* + * AIC3X register cache + * We can't read the AIC3X register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u8 aic3x_reg[AIC3X_CACHEREGNUM] = { + 0x00, 0x00, 0x00, 0x10, /* 0 */ + 0x04, 0x00, 0x00, 0x00, /* 4 */ + 0x00, 0x00, 0x00, 0x01, /* 8 */ + 0x00, 0x00, 0x00, 0x80, /* 12 */ + 0x80, 0xff, 0xff, 0x78, /* 16 */ + 0x78, 0x78, 0x78, 0x78, /* 20 */ + 0x78, 0x00, 0x00, 0xfe, /* 24 */ + 0x00, 0x00, 0xfe, 0x00, /* 28 */ + 0x18, 0x18, 0x00, 0x00, /* 32 */ + 0x00, 0x00, 0x00, 0x00, /* 36 */ + 0x00, 0x00, 0x00, 0x80, /* 40 */ + 0x80, 0x00, 0x00, 0x00, /* 44 */ + 0x00, 0x00, 0x00, 0x04, /* 48 */ + 0x00, 0x00, 0x00, 0x00, /* 52 */ + 0x00, 0x00, 0x04, 0x00, /* 56 */ + 0x00, 0x00, 0x00, 0x00, /* 60 */ + 0x00, 0x04, 0x00, 0x00, /* 64 */ + 0x00, 0x00, 0x00, 0x00, /* 68 */ + 0x04, 0x00, 0x00, 0x00, /* 72 */ + 0x00, 0x00, 0x00, 0x00, /* 76 */ + 0x00, 0x00, 0x00, 0x00, /* 80 */ + 0x00, 0x00, 0x00, 0x00, /* 84 */ + 0x00, 0x00, 0x00, 0x00, /* 88 */ + 0x00, 0x00, 0x00, 0x00, /* 92 */ + 0x00, 0x00, 0x00, 0x00, /* 96 */ + 0x00, 0x00, 0x02, /* 100 */ +}; + +/* + * read aic3x register cache + */ +static inline unsigned int aic3x_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + if (reg >= AIC3X_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write aic3x register cache + */ +static inline void aic3x_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, u8 value) +{ + u8 *cache = codec->reg_cache; + if (reg >= AIC3X_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the aic3x register space + */ +static int aic3x_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D8 aic3x register offset + * D7...D0 register data + */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + aic3x_write_reg_cache(codec, data[0], data[1]); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +/* + * read from the aic3x register space + */ +static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg, + u8 *value) +{ + *value = reg & 0xff; + if (codec->hw_read(codec->control_data, value, 1) != 1) + return -EIO; + + aic3x_write_reg_cache(codec, reg, *value); + return 0; +} + +#define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw_aic3x, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, mask, invert) } + +/* + * All input lines are connected when !0xf and disconnected with 0xf bit field, + * so we have to use specific dapm_put call for input mixer + */ +static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0x01; + unsigned short val, val_mask; + int ret; + struct snd_soc_dapm_path *path; + int found = 0; + + val = (ucontrol->value.integer.value[0] & mask); + + mask = 0xf; + if (val) + val = mask; + + if (invert) + val = mask - val; + val_mask = mask << shift; + val = val << shift; + + mutex_lock(&widget->codec->mutex); + + if (snd_soc_test_bits(widget->codec, reg, val_mask, val)) { + /* find dapm widget path assoc with kcontrol */ + list_for_each_entry(path, &widget->codec->dapm_paths, list) { + if (path->kcontrol != kcontrol) + continue; + + /* found, now check type */ + found = 1; + if (val) + /* new connection */ + path->connect = invert ? 0 : 1; + else + /* old connection must be powered down */ + path->connect = invert ? 1 : 0; + break; + } + + if (found) + snd_soc_dapm_sync(widget->codec); + } + + ret = snd_soc_update_bits(widget->codec, reg, val_mask, val); + + mutex_unlock(&widget->codec->mutex); + return ret; +} + +static const char *aic3x_left_dac_mux[] = { "DAC_L1", "DAC_L3", "DAC_L2" }; +static const char *aic3x_right_dac_mux[] = { "DAC_R1", "DAC_R3", "DAC_R2" }; +static const char *aic3x_left_hpcom_mux[] = + { "differential of HPLOUT", "constant VCM", "single-ended" }; +static const char *aic3x_right_hpcom_mux[] = + { "differential of HPROUT", "constant VCM", "single-ended", + "differential of HPLCOM", "external feedback" }; +static const char *aic3x_linein_mode_mux[] = { "single-ended", "differential" }; +static const char *aic3x_adc_hpf[] = + { "Disabled", "0.0045xFs", "0.0125xFs", "0.025xFs" }; + +#define LDAC_ENUM 0 +#define RDAC_ENUM 1 +#define LHPCOM_ENUM 2 +#define RHPCOM_ENUM 3 +#define LINE1L_ENUM 4 +#define LINE1R_ENUM 5 +#define LINE2L_ENUM 6 +#define LINE2R_ENUM 7 +#define ADC_HPF_ENUM 8 + +static const struct soc_enum aic3x_enum[] = { + SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux), + SOC_ENUM_SINGLE(DAC_LINE_MUX, 4, 3, aic3x_right_dac_mux), + SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux), + SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux), + SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf), +}; + +static const struct snd_kcontrol_new aic3x_snd_controls[] = { + /* Output */ + SOC_DOUBLE_R("PCM Playback Volume", LDAC_VOL, RDAC_VOL, 0, 0x7f, 1), + + SOC_DOUBLE_R("Line DAC Playback Volume", DACL1_2_LLOPM_VOL, + DACR1_2_RLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("Line DAC Playback Switch", LLOPM_CTRL, RLOPM_CTRL, 3, + 0x01, 0), + SOC_DOUBLE_R("Line PGA Bypass Playback Volume", PGAL_2_LLOPM_VOL, + PGAR_2_RLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("Line Line2 Bypass Playback Volume", LINE2L_2_LLOPM_VOL, + LINE2R_2_RLOPM_VOL, 0, 0x7f, 1), + + SOC_DOUBLE_R("Mono DAC Playback Volume", DACL1_2_MONOLOPM_VOL, + DACR1_2_MONOLOPM_VOL, 0, 0x7f, 1), + SOC_SINGLE("Mono DAC Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0), + SOC_DOUBLE_R("Mono PGA Bypass Playback Volume", PGAL_2_MONOLOPM_VOL, + PGAR_2_MONOLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("Mono Line2 Bypass Playback Volume", LINE2L_2_MONOLOPM_VOL, + LINE2R_2_MONOLOPM_VOL, 0, 0x7f, 1), + + SOC_DOUBLE_R("HP DAC Playback Volume", DACL1_2_HPLOUT_VOL, + DACR1_2_HPROUT_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("HP DAC Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3, + 0x01, 0), + SOC_DOUBLE_R("HP PGA Bypass Playback Volume", PGAL_2_HPLOUT_VOL, + PGAR_2_HPROUT_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("HP Line2 Bypass Playback Volume", LINE2L_2_HPLOUT_VOL, + LINE2R_2_HPROUT_VOL, 0, 0x7f, 1), + + SOC_DOUBLE_R("HPCOM DAC Playback Volume", DACL1_2_HPLCOM_VOL, + DACR1_2_HPRCOM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("HPCOM DAC Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3, + 0x01, 0), + SOC_DOUBLE_R("HPCOM PGA Bypass Playback Volume", PGAL_2_HPLCOM_VOL, + PGAR_2_HPRCOM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("HPCOM Line2 Bypass Playback Volume", LINE2L_2_HPLCOM_VOL, + LINE2R_2_HPRCOM_VOL, 0, 0x7f, 1), + + /* + * Note: enable Automatic input Gain Controller with care. It can + * adjust PGA to max value when ADC is on and will never go back. + */ + SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0), + + /* Input */ + SOC_DOUBLE_R("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x7f, 0), + SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1), + + SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]), +}; + +/* add non dapm controls */ +static int aic3x_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(aic3x_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&aic3x_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Left DAC Mux */ +static const struct snd_kcontrol_new aic3x_left_dac_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]); + +/* Right DAC Mux */ +static const struct snd_kcontrol_new aic3x_right_dac_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[RDAC_ENUM]); + +/* Left HPCOM Mux */ +static const struct snd_kcontrol_new aic3x_left_hpcom_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LHPCOM_ENUM]); + +/* Right HPCOM Mux */ +static const struct snd_kcontrol_new aic3x_right_hpcom_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[RHPCOM_ENUM]); + +/* Left DAC_L1 Mixer */ +static const struct snd_kcontrol_new aic3x_left_dac_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", DACL1_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", DACL1_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", DACL1_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", DACL1_2_HPLCOM_VOL, 7, 1, 0), +}; + +/* Right DAC_R1 Mixer */ +static const struct snd_kcontrol_new aic3x_right_dac_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", DACR1_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", DACR1_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", DACR1_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", DACR1_2_HPRCOM_VOL, 7, 1, 0), +}; + +/* Left PGA Mixer */ +static const struct snd_kcontrol_new aic3x_left_pga_mixer_controls[] = { + SOC_DAPM_SINGLE_AIC3X("Line1L Switch", LINE1L_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line2L Switch", LINE2L_2_LADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_LADC_CTRL, 4, 1, 1), +}; + +/* Right PGA Mixer */ +static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = { + SOC_DAPM_SINGLE_AIC3X("Line1R Switch", LINE1R_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Line2R Switch", LINE2R_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1), +}; + +/* Left Line1 Mux */ +static const struct snd_kcontrol_new aic3x_left_line1_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]); + +/* Right Line1 Mux */ +static const struct snd_kcontrol_new aic3x_right_line1_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]); + +/* Left Line2 Mux */ +static const struct snd_kcontrol_new aic3x_left_line2_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE2L_ENUM]); + +/* Right Line2 Mux */ +static const struct snd_kcontrol_new aic3x_right_line2_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE2R_ENUM]); + +/* Left PGA Bypass Mixer */ +static const struct snd_kcontrol_new aic3x_left_pga_bp_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", PGAL_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", PGAL_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", PGAL_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", PGAL_2_HPLCOM_VOL, 7, 1, 0), +}; + +/* Right PGA Bypass Mixer */ +static const struct snd_kcontrol_new aic3x_right_pga_bp_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", PGAR_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", PGAR_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", PGAR_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", PGAR_2_HPRCOM_VOL, 7, 1, 0), +}; + +/* Left Line2 Bypass Mixer */ +static const struct snd_kcontrol_new aic3x_left_line2_bp_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", LINE2L_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", LINE2L_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", LINE2L_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", LINE2L_2_HPLCOM_VOL, 7, 1, 0), +}; + +/* Right Line2 Bypass Mixer */ +static const struct snd_kcontrol_new aic3x_right_line2_bp_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Switch", LINE2R_2_RLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("Mono Switch", LINE2R_2_MONOLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HP Switch", LINE2R_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPCOM Switch", LINE2R_2_HPRCOM_VOL, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { + /* Left DAC to Left Outputs */ + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", DAC_PWR, 7, 0), + SND_SOC_DAPM_MUX("Left DAC Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_dac_mux_controls), + SND_SOC_DAPM_MIXER("Left DAC_L1 Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_dac_mixer_controls[0], + ARRAY_SIZE(aic3x_left_dac_mixer_controls)), + SND_SOC_DAPM_MUX("Left HPCOM Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_hpcom_mux_controls), + SND_SOC_DAPM_PGA("Left Line Out", LLOPM_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left HP Out", HPLOUT_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left HP Com", HPLCOM_CTRL, 0, 0, NULL, 0), + + /* Right DAC to Right Outputs */ + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", DAC_PWR, 6, 0), + SND_SOC_DAPM_MUX("Right DAC Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_dac_mux_controls), + SND_SOC_DAPM_MIXER("Right DAC_R1 Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_dac_mixer_controls[0], + ARRAY_SIZE(aic3x_right_dac_mixer_controls)), + SND_SOC_DAPM_MUX("Right HPCOM Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_hpcom_mux_controls), + SND_SOC_DAPM_PGA("Right Line Out", RLOPM_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right HP Out", HPROUT_CTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right HP Com", HPRCOM_CTRL, 0, 0, NULL, 0), + + /* Mono Output */ + SND_SOC_DAPM_PGA("Mono Out", MONOLOPM_CTRL, 0, 0, NULL, 0), + + /* Left Inputs to Left ADC */ + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", LINE1L_2_LADC_CTRL, 2, 0), + SND_SOC_DAPM_MIXER("Left PGA Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_pga_mixer_controls[0], + ARRAY_SIZE(aic3x_left_pga_mixer_controls)), + SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_line1_mux_controls), + SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0, + &aic3x_left_line2_mux_controls), + + /* Right Inputs to Right ADC */ + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", + LINE1R_2_RADC_CTRL, 2, 0), + SND_SOC_DAPM_MIXER("Right PGA Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_pga_mixer_controls[0], + ARRAY_SIZE(aic3x_right_pga_mixer_controls)), + SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_line1_mux_controls), + SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_line2_mux_controls), + + /* + * Not a real mic bias widget but similar function. This is for dynamic + * control of GPIO1 digital mic modulator clock output function when + * using digital mic. + */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "GPIO1 dmic modclk", + AIC3X_GPIO1_REG, 4, 0xf, + AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK, + AIC3X_GPIO1_FUNC_DISABLED), + + /* + * Also similar function like mic bias. Selects digital mic with + * configurable oversampling rate instead of ADC converter. + */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 128", + AIC3X_ASD_INTF_CTRLA, 0, 3, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 64", + AIC3X_ASD_INTF_CTRLA, 0, 3, 2, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "DMic Rate 32", + AIC3X_ASD_INTF_CTRLA, 0, 3, 3, 0), + + /* Mic Bias */ + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2V", + MICBIAS_CTRL, 6, 3, 1, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias 2.5V", + MICBIAS_CTRL, 6, 3, 2, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_micbias, "Mic Bias AVDD", + MICBIAS_CTRL, 6, 3, 3, 0), + + /* Left PGA to Left Output bypass */ + SND_SOC_DAPM_MIXER("Left PGA Bypass Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_pga_bp_mixer_controls[0], + ARRAY_SIZE(aic3x_left_pga_bp_mixer_controls)), + + /* Right PGA to Right Output bypass */ + SND_SOC_DAPM_MIXER("Right PGA Bypass Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_pga_bp_mixer_controls[0], + ARRAY_SIZE(aic3x_right_pga_bp_mixer_controls)), + + /* Left Line2 to Left Output bypass */ + SND_SOC_DAPM_MIXER("Left Line2 Bypass Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_left_line2_bp_mixer_controls[0], + ARRAY_SIZE(aic3x_left_line2_bp_mixer_controls)), + + /* Right Line2 to Right Output bypass */ + SND_SOC_DAPM_MIXER("Right Line2 Bypass Mixer", SND_SOC_NOPM, 0, 0, + &aic3x_right_line2_bp_mixer_controls[0], + ARRAY_SIZE(aic3x_right_line2_bp_mixer_controls)), + + SND_SOC_DAPM_OUTPUT("LLOUT"), + SND_SOC_DAPM_OUTPUT("RLOUT"), + SND_SOC_DAPM_OUTPUT("MONO_LOUT"), + SND_SOC_DAPM_OUTPUT("HPLOUT"), + SND_SOC_DAPM_OUTPUT("HPROUT"), + SND_SOC_DAPM_OUTPUT("HPLCOM"), + SND_SOC_DAPM_OUTPUT("HPRCOM"), + + SND_SOC_DAPM_INPUT("MIC3L"), + SND_SOC_DAPM_INPUT("MIC3R"), + SND_SOC_DAPM_INPUT("LINE1L"), + SND_SOC_DAPM_INPUT("LINE1R"), + SND_SOC_DAPM_INPUT("LINE2L"), + SND_SOC_DAPM_INPUT("LINE2R"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Left Output */ + {"Left DAC Mux", "DAC_L1", "Left DAC"}, + {"Left DAC Mux", "DAC_L2", "Left DAC"}, + {"Left DAC Mux", "DAC_L3", "Left DAC"}, + + {"Left DAC_L1 Mixer", "Line Switch", "Left DAC Mux"}, + {"Left DAC_L1 Mixer", "Mono Switch", "Left DAC Mux"}, + {"Left DAC_L1 Mixer", "HP Switch", "Left DAC Mux"}, + {"Left DAC_L1 Mixer", "HPCOM Switch", "Left DAC Mux"}, + {"Left Line Out", NULL, "Left DAC Mux"}, + {"Left HP Out", NULL, "Left DAC Mux"}, + + {"Left HPCOM Mux", "differential of HPLOUT", "Left DAC_L1 Mixer"}, + {"Left HPCOM Mux", "constant VCM", "Left DAC_L1 Mixer"}, + {"Left HPCOM Mux", "single-ended", "Left DAC_L1 Mixer"}, + + {"Left Line Out", NULL, "Left DAC_L1 Mixer"}, + {"Mono Out", NULL, "Left DAC_L1 Mixer"}, + {"Left HP Out", NULL, "Left DAC_L1 Mixer"}, + {"Left HP Com", NULL, "Left HPCOM Mux"}, + + {"LLOUT", NULL, "Left Line Out"}, + {"LLOUT", NULL, "Left Line Out"}, + {"HPLOUT", NULL, "Left HP Out"}, + {"HPLCOM", NULL, "Left HP Com"}, + + /* Right Output */ + {"Right DAC Mux", "DAC_R1", "Right DAC"}, + {"Right DAC Mux", "DAC_R2", "Right DAC"}, + {"Right DAC Mux", "DAC_R3", "Right DAC"}, + + {"Right DAC_R1 Mixer", "Line Switch", "Right DAC Mux"}, + {"Right DAC_R1 Mixer", "Mono Switch", "Right DAC Mux"}, + {"Right DAC_R1 Mixer", "HP Switch", "Right DAC Mux"}, + {"Right DAC_R1 Mixer", "HPCOM Switch", "Right DAC Mux"}, + {"Right Line Out", NULL, "Right DAC Mux"}, + {"Right HP Out", NULL, "Right DAC Mux"}, + + {"Right HPCOM Mux", "differential of HPROUT", "Right DAC_R1 Mixer"}, + {"Right HPCOM Mux", "constant VCM", "Right DAC_R1 Mixer"}, + {"Right HPCOM Mux", "single-ended", "Right DAC_R1 Mixer"}, + {"Right HPCOM Mux", "differential of HPLCOM", "Right DAC_R1 Mixer"}, + {"Right HPCOM Mux", "external feedback", "Right DAC_R1 Mixer"}, + + {"Right Line Out", NULL, "Right DAC_R1 Mixer"}, + {"Mono Out", NULL, "Right DAC_R1 Mixer"}, + {"Right HP Out", NULL, "Right DAC_R1 Mixer"}, + {"Right HP Com", NULL, "Right HPCOM Mux"}, + + {"RLOUT", NULL, "Right Line Out"}, + {"RLOUT", NULL, "Right Line Out"}, + {"HPROUT", NULL, "Right HP Out"}, + {"HPRCOM", NULL, "Right HP Com"}, + + /* Mono Output */ + {"MONO_LOUT", NULL, "Mono Out"}, + {"MONO_LOUT", NULL, "Mono Out"}, + + /* Left Input */ + {"Left Line1L Mux", "single-ended", "LINE1L"}, + {"Left Line1L Mux", "differential", "LINE1L"}, + + {"Left Line2L Mux", "single-ended", "LINE2L"}, + {"Left Line2L Mux", "differential", "LINE2L"}, + + {"Left PGA Mixer", "Line1L Switch", "Left Line1L Mux"}, + {"Left PGA Mixer", "Line2L Switch", "Left Line2L Mux"}, + {"Left PGA Mixer", "Mic3L Switch", "MIC3L"}, + + {"Left ADC", NULL, "Left PGA Mixer"}, + {"Left ADC", NULL, "GPIO1 dmic modclk"}, + + /* Right Input */ + {"Right Line1R Mux", "single-ended", "LINE1R"}, + {"Right Line1R Mux", "differential", "LINE1R"}, + + {"Right Line2R Mux", "single-ended", "LINE2R"}, + {"Right Line2R Mux", "differential", "LINE2R"}, + + {"Right PGA Mixer", "Line1R Switch", "Right Line1R Mux"}, + {"Right PGA Mixer", "Line2R Switch", "Right Line2R Mux"}, + {"Right PGA Mixer", "Mic3R Switch", "MIC3R"}, + + {"Right ADC", NULL, "Right PGA Mixer"}, + {"Right ADC", NULL, "GPIO1 dmic modclk"}, + + /* Left PGA Bypass */ + {"Left PGA Bypass Mixer", "Line Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "Mono Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "HP Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "HPCOM Switch", "Left PGA Mixer"}, + + {"Left HPCOM Mux", "differential of HPLOUT", "Left PGA Bypass Mixer"}, + {"Left HPCOM Mux", "constant VCM", "Left PGA Bypass Mixer"}, + {"Left HPCOM Mux", "single-ended", "Left PGA Bypass Mixer"}, + + {"Left Line Out", NULL, "Left PGA Bypass Mixer"}, + {"Mono Out", NULL, "Left PGA Bypass Mixer"}, + {"Left HP Out", NULL, "Left PGA Bypass Mixer"}, + + /* Right PGA Bypass */ + {"Right PGA Bypass Mixer", "Line Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "Mono Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "HP Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "HPCOM Switch", "Right PGA Mixer"}, + + {"Right HPCOM Mux", "differential of HPROUT", "Right PGA Bypass Mixer"}, + {"Right HPCOM Mux", "constant VCM", "Right PGA Bypass Mixer"}, + {"Right HPCOM Mux", "single-ended", "Right PGA Bypass Mixer"}, + {"Right HPCOM Mux", "differential of HPLCOM", "Right PGA Bypass Mixer"}, + {"Right HPCOM Mux", "external feedback", "Right PGA Bypass Mixer"}, + + {"Right Line Out", NULL, "Right PGA Bypass Mixer"}, + {"Mono Out", NULL, "Right PGA Bypass Mixer"}, + {"Right HP Out", NULL, "Right PGA Bypass Mixer"}, + + /* Left Line2 Bypass */ + {"Left Line2 Bypass Mixer", "Line Switch", "Left Line2L Mux"}, + {"Left Line2 Bypass Mixer", "Mono Switch", "Left Line2L Mux"}, + {"Left Line2 Bypass Mixer", "HP Switch", "Left Line2L Mux"}, + {"Left Line2 Bypass Mixer", "HPCOM Switch", "Left Line2L Mux"}, + + {"Left HPCOM Mux", "differential of HPLOUT", "Left Line2 Bypass Mixer"}, + {"Left HPCOM Mux", "constant VCM", "Left Line2 Bypass Mixer"}, + {"Left HPCOM Mux", "single-ended", "Left Line2 Bypass Mixer"}, + + {"Left Line Out", NULL, "Left Line2 Bypass Mixer"}, + {"Mono Out", NULL, "Left Line2 Bypass Mixer"}, + {"Left HP Out", NULL, "Left Line2 Bypass Mixer"}, + + /* Right Line2 Bypass */ + {"Right Line2 Bypass Mixer", "Line Switch", "Right Line2R Mux"}, + {"Right Line2 Bypass Mixer", "Mono Switch", "Right Line2R Mux"}, + {"Right Line2 Bypass Mixer", "HP Switch", "Right Line2R Mux"}, + {"Right Line2 Bypass Mixer", "HPCOM Switch", "Right Line2R Mux"}, + + {"Right HPCOM Mux", "differential of HPROUT", "Right Line2 Bypass Mixer"}, + {"Right HPCOM Mux", "constant VCM", "Right Line2 Bypass Mixer"}, + {"Right HPCOM Mux", "single-ended", "Right Line2 Bypass Mixer"}, + {"Right HPCOM Mux", "differential of HPLCOM", "Right Line2 Bypass Mixer"}, + {"Right HPCOM Mux", "external feedback", "Right Line2 Bypass Mixer"}, + + {"Right Line Out", NULL, "Right Line2 Bypass Mixer"}, + {"Mono Out", NULL, "Right Line2 Bypass Mixer"}, + {"Right HP Out", NULL, "Right Line2 Bypass Mixer"}, + + /* + * Logical path between digital mic enable and GPIO1 modulator clock + * output function + */ + {"GPIO1 dmic modclk", NULL, "DMic Rate 128"}, + {"GPIO1 dmic modclk", NULL, "DMic Rate 64"}, + {"GPIO1 dmic modclk", NULL, "DMic Rate 32"}, +}; + +static int aic3x_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets, + ARRAY_SIZE(aic3x_dapm_widgets)); + + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int aic3x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct aic3x_priv *aic3x = codec->private_data; + int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; + u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; + u16 pll_d = 1; + + /* select data word length */ + data = + aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & (~(0x3 << 4)); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + data |= (0x01 << 4); + break; + case SNDRV_PCM_FORMAT_S24_LE: + data |= (0x02 << 4); + break; + case SNDRV_PCM_FORMAT_S32_LE: + data |= (0x03 << 4); + break; + } + aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, data); + + /* Fsref can be 44100 or 48000 */ + fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000; + + /* Try to find a value for Q which allows us to bypass the PLL and + * generate CODEC_CLK directly. */ + for (pll_q = 2; pll_q < 18; pll_q++) + if (aic3x->sysclk / (128 * pll_q) == fsref) { + bypass_pll = 1; + break; + } + + if (bypass_pll) { + pll_q &= 0xf; + aic3x_write(codec, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT); + aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV); + } else + aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV); + + /* Route Left DAC to left channel input and + * right DAC to right channel input */ + data = (LDAC2LCH | RDAC2RCH); + data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000; + if (params_rate(params) >= 64000) + data |= DUAL_RATE_MODE; + aic3x_write(codec, AIC3X_CODEC_DATAPATH_REG, data); + + /* codec sample rate select */ + data = (fsref * 20) / params_rate(params); + if (params_rate(params) < 64000) + data /= 2; + data /= 5; + data -= 2; + data |= (data << 4); + aic3x_write(codec, AIC3X_SAMPLE_RATE_SEL_REG, data); + + if (bypass_pll) + return 0; + + /* Use PLL + * find an apropriate setup for j, d, r and p by iterating over + * p and r - j and d are calculated for each fraction. + * Up to 128 values are probed, the closest one wins the game. + * The sysclk is divided by 1000 to prevent integer overflows. + */ + codec_clk = (2048 * fsref) / (aic3x->sysclk / 1000); + + for (r = 1; r <= 16; r++) + for (p = 1; p <= 8; p++) { + int clk, tmp = (codec_clk * pll_r * 10) / pll_p; + u8 j = tmp / 10000; + u16 d = tmp % 10000; + + if (j > 63) + continue; + + if (d != 0 && aic3x->sysclk < 10000000) + continue; + + /* This is actually 1000 * ((j + (d/10000)) * r) / p + * The term had to be converted to get rid of the + * division by 10000 */ + clk = ((10000 * j * r) + (d * r)) / (10 * p); + + /* check whether this values get closer than the best + * ones we had before */ + if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) { + pll_j = j; pll_d = d; pll_r = r; pll_p = p; + last_clk = clk; + } + + /* Early exit for exact matches */ + if (clk == codec_clk) + break; + } + + if (last_clk == 0) { + printk(KERN_ERR "%s(): unable to setup PLL\n", __func__); + return -EINVAL; + } + + data = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, data | (pll_p << PLLP_SHIFT)); + aic3x_write(codec, AIC3X_OVRF_STATUS_AND_PLLR_REG, pll_r << PLLR_SHIFT); + aic3x_write(codec, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT); + aic3x_write(codec, AIC3X_PLL_PROGC_REG, (pll_d >> 6) << PLLD_MSB_SHIFT); + aic3x_write(codec, AIC3X_PLL_PROGD_REG, + (pll_d & 0x3F) << PLLD_LSB_SHIFT); + + return 0; +} + +static int aic3x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 ldac_reg = aic3x_read_reg_cache(codec, LDAC_VOL) & ~MUTE_ON; + u8 rdac_reg = aic3x_read_reg_cache(codec, RDAC_VOL) & ~MUTE_ON; + + if (mute) { + aic3x_write(codec, LDAC_VOL, ldac_reg | MUTE_ON); + aic3x_write(codec, RDAC_VOL, rdac_reg | MUTE_ON); + } else { + aic3x_write(codec, LDAC_VOL, ldac_reg); + aic3x_write(codec, RDAC_VOL, rdac_reg); + } + + return 0; +} + +static int aic3x_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3x_priv *aic3x = codec->private_data; + + aic3x->sysclk = freq; + return 0; +} + +static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic3x_priv *aic3x = codec->private_data; + u8 iface_areg, iface_breg; + + iface_areg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f; + iface_breg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aic3x->master = 1; + iface_areg |= BIT_CLK_MASTER | WORD_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + aic3x->master = 0; + break; + default: + return -EINVAL; + } + + /* + * match both interface format and signal polarities since they + * are fixed + */ + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_INV_MASK)) { + case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): + break; + case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF): + iface_breg |= (0x01 << 6); + break; + case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF): + iface_breg |= (0x02 << 6); + break; + case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF): + iface_breg |= (0x03 << 6); + break; + default: + return -EINVAL; + } + + /* set iface */ + aic3x_write(codec, AIC3X_ASD_INTF_CTRLA, iface_areg); + aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, iface_breg); + + return 0; +} + +static int aic3x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct aic3x_priv *aic3x = codec->private_data; + u8 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + /* all power is driven by DAPM system */ + if (aic3x->master) { + /* enable pll */ + reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, + reg | PLL_ENABLE); + } + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* + * all power is driven by DAPM system, + * so output power is safe if bypass was set + */ + if (aic3x->master) { + /* disable pll */ + reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, + reg & ~PLL_ENABLE); + } + break; + case SND_SOC_BIAS_OFF: + /* force all power off */ + reg = aic3x_read_reg_cache(codec, LINE1L_2_LADC_CTRL); + aic3x_write(codec, LINE1L_2_LADC_CTRL, reg & ~LADC_PWR_ON); + reg = aic3x_read_reg_cache(codec, LINE1R_2_RADC_CTRL); + aic3x_write(codec, LINE1R_2_RADC_CTRL, reg & ~RADC_PWR_ON); + + reg = aic3x_read_reg_cache(codec, DAC_PWR); + aic3x_write(codec, DAC_PWR, reg & ~(LDAC_PWR_ON | RDAC_PWR_ON)); + + reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL); + aic3x_write(codec, HPLOUT_CTRL, reg & ~HPLOUT_PWR_ON); + reg = aic3x_read_reg_cache(codec, HPROUT_CTRL); + aic3x_write(codec, HPROUT_CTRL, reg & ~HPROUT_PWR_ON); + + reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL); + aic3x_write(codec, HPLCOM_CTRL, reg & ~HPLCOM_PWR_ON); + reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL); + aic3x_write(codec, HPRCOM_CTRL, reg & ~HPRCOM_PWR_ON); + + reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL); + aic3x_write(codec, MONOLOPM_CTRL, reg & ~MONOLOPM_PWR_ON); + + reg = aic3x_read_reg_cache(codec, LLOPM_CTRL); + aic3x_write(codec, LLOPM_CTRL, reg & ~LLOPM_PWR_ON); + reg = aic3x_read_reg_cache(codec, RLOPM_CTRL); + aic3x_write(codec, RLOPM_CTRL, reg & ~RLOPM_PWR_ON); + + if (aic3x->master) { + /* disable pll */ + reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, + reg & ~PLL_ENABLE); + } + break; + } + codec->bias_level = level; + + return 0; +} + +void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state) +{ + u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG; + u8 bit = gpio ? 3: 0; + u8 val = aic3x_read_reg_cache(codec, reg) & ~(1 << bit); + aic3x_write(codec, reg, val | (!!state << bit)); +} +EXPORT_SYMBOL_GPL(aic3x_set_gpio); + +int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio) +{ + u8 reg = gpio ? AIC3X_GPIO2_REG : AIC3X_GPIO1_REG; + u8 val, bit = gpio ? 2: 1; + + aic3x_read(codec, reg, &val); + return (val >> bit) & 1; +} +EXPORT_SYMBOL_GPL(aic3x_get_gpio); + +int aic3x_headset_detected(struct snd_soc_codec *codec) +{ + u8 val; + aic3x_read(codec, AIC3X_RT_IRQ_FLAGS_REG, &val); + return (val >> 2) & 1; +} +EXPORT_SYMBOL_GPL(aic3x_headset_detected); + +#define AIC3X_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai aic3x_dai = { + .name = "tlv320aic3x", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3X_RATES, + .formats = AIC3X_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AIC3X_RATES, + .formats = AIC3X_FORMATS,}, + .ops = { + .hw_params = aic3x_hw_params, + }, + .dai_ops = { + .digital_mute = aic3x_mute, + .set_sysclk = aic3x_set_dai_sysclk, + .set_fmt = aic3x_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(aic3x_dai); + +static int aic3x_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int aic3x_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u8 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(aic3x_reg); i++) { + data[0] = i; + data[1] = cache[i]; + codec->hw_write(codec->control_data, data, 2); + } + + aic3x_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} + +/* + * initialise the AIC3X driver + * register the mixer and dsp interfaces with the kernel + */ +static int aic3x_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + struct aic3x_setup_data *setup = socdev->codec_data; + int reg, ret = 0; + + codec->name = "tlv320aic3x"; + codec->owner = THIS_MODULE; + codec->read = aic3x_read_reg_cache; + codec->write = aic3x_write; + codec->set_bias_level = aic3x_set_bias_level; + codec->dai = &aic3x_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(aic3x_reg); + codec->reg_cache = kmemdup(aic3x_reg, sizeof(aic3x_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + aic3x_write(codec, AIC3X_PAGE_SELECT, PAGE0_SELECT); + aic3x_write(codec, AIC3X_RESET, SOFT_RESET); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "aic3x: failed to create pcms\n"); + goto pcm_err; + } + + /* DAC default volume and mute */ + aic3x_write(codec, LDAC_VOL, DEFAULT_VOL | MUTE_ON); + aic3x_write(codec, RDAC_VOL, DEFAULT_VOL | MUTE_ON); + + /* DAC to HP default volume and route to Output mixer */ + aic3x_write(codec, DACL1_2_HPLOUT_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACR1_2_HPROUT_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACL1_2_HPLCOM_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACR1_2_HPRCOM_VOL, DEFAULT_VOL | ROUTE_ON); + /* DAC to Line Out default volume and route to Output mixer */ + aic3x_write(codec, DACL1_2_LLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACR1_2_RLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + /* DAC to Mono Line Out default volume and route to Output mixer */ + aic3x_write(codec, DACL1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + aic3x_write(codec, DACR1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + + /* unmute all outputs */ + reg = aic3x_read_reg_cache(codec, LLOPM_CTRL); + aic3x_write(codec, LLOPM_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, RLOPM_CTRL); + aic3x_write(codec, RLOPM_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, MONOLOPM_CTRL); + aic3x_write(codec, MONOLOPM_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, HPLOUT_CTRL); + aic3x_write(codec, HPLOUT_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, HPROUT_CTRL); + aic3x_write(codec, HPROUT_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, HPLCOM_CTRL); + aic3x_write(codec, HPLCOM_CTRL, reg | UNMUTE); + reg = aic3x_read_reg_cache(codec, HPRCOM_CTRL); + aic3x_write(codec, HPRCOM_CTRL, reg | UNMUTE); + + /* ADC default volume and unmute */ + aic3x_write(codec, LADC_VOL, DEFAULT_GAIN); + aic3x_write(codec, RADC_VOL, DEFAULT_GAIN); + /* By default route Line1 to ADC PGA mixer */ + aic3x_write(codec, LINE1L_2_LADC_CTRL, 0x0); + aic3x_write(codec, LINE1R_2_RADC_CTRL, 0x0); + + /* PGA to HP Bypass default volume, disconnect from Output Mixer */ + aic3x_write(codec, PGAL_2_HPLOUT_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAR_2_HPROUT_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAL_2_HPLCOM_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAR_2_HPRCOM_VOL, DEFAULT_VOL); + /* PGA to Line Out default volume, disconnect from Output Mixer */ + aic3x_write(codec, PGAL_2_LLOPM_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAR_2_RLOPM_VOL, DEFAULT_VOL); + /* PGA to Mono Line Out default volume, disconnect from Output Mixer */ + aic3x_write(codec, PGAL_2_MONOLOPM_VOL, DEFAULT_VOL); + aic3x_write(codec, PGAR_2_MONOLOPM_VOL, DEFAULT_VOL); + + /* Line2 to HP Bypass default volume, disconnect from Output Mixer */ + aic3x_write(codec, LINE2L_2_HPLOUT_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2R_2_HPROUT_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2L_2_HPLCOM_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2R_2_HPRCOM_VOL, DEFAULT_VOL); + /* Line2 Line Out default volume, disconnect from Output Mixer */ + aic3x_write(codec, LINE2L_2_LLOPM_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2R_2_RLOPM_VOL, DEFAULT_VOL); + /* Line2 to Mono Out default volume, disconnect from Output Mixer */ + aic3x_write(codec, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL); + aic3x_write(codec, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL); + + /* off, with power on */ + aic3x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* setup GPIO functions */ + aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4); + aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4); + + aic3x_add_controls(codec); + aic3x_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "aic3x: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *aic3x_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + * AIC3X 2 wire address can be up to 4 devices with device addresses + * 0x18, 0x19, 0x1A, 0x1B + */ + +/* + * If the i2c layer weren't so broken, we could pass this kind of data + * around + */ +static int aic3x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = aic3x_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = aic3x_init(socdev); + if (ret < 0) + printk(KERN_ERR "aic3x: failed to initialise AIC3X\n"); + return ret; +} + +static int aic3x_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id aic3x_i2c_id[] = { + { "tlv320aic3x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id); + +/* machine i2c codec control layer */ +static struct i2c_driver aic3x_i2c_driver = { + .driver = { + .name = "aic3x I2C Codec", + .owner = THIS_MODULE, + }, + .probe = aic3x_i2c_probe, + .remove = aic3x_i2c_remove, + .id_table = aic3x_i2c_id, +}; + +static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len) +{ + value[0] = i2c_smbus_read_byte_data(client, value[0]); + return (len == 1); +} + +static int aic3x_add_i2c_device(struct platform_device *pdev, + const struct aic3x_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&aic3x_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "tlv320aic3x", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&aic3x_i2c_driver); + return -ENODEV; +} +#endif + +static int aic3x_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct aic3x_setup_data *setup; + struct snd_soc_codec *codec; + struct aic3x_priv *aic3x; + int ret = 0; + + printk(KERN_INFO "AIC3X Audio Codec %s\n", AIC3X_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); + if (aic3x == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = aic3x; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + aic3x_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t) i2c_master_send; + codec->hw_read = (hw_read_t) aic3x_i2c_read; + ret = aic3x_add_i2c_device(pdev, setup); + } +#else + /* Add other interfaces here */ +#endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } + return ret; +} + +static int aic3x_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + /* power down chip */ + if (codec->control_data) + aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&aic3x_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_aic3x = { + .probe = aic3x_probe, + .remove = aic3x_remove, + .suspend = aic3x_suspend, + .resume = aic3x_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_aic3x); + +MODULE_DESCRIPTION("ASoC TLV320AIC3X codec driver"); +MODULE_AUTHOR("Vladimir Barinov"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h new file mode 100644 index 0000000..00a195a --- /dev/null +++ b/sound/soc/codecs/tlv320aic3x.h @@ -0,0 +1,235 @@ +/* + * ALSA SoC TLV320AIC3X codec driver + * + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> + * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _AIC3X_H +#define _AIC3X_H + +/* AIC3X register space */ +#define AIC3X_CACHEREGNUM 103 + +/* Page select register */ +#define AIC3X_PAGE_SELECT 0 +/* Software reset register */ +#define AIC3X_RESET 1 +/* Codec Sample rate select register */ +#define AIC3X_SAMPLE_RATE_SEL_REG 2 +/* PLL progrramming register A */ +#define AIC3X_PLL_PROGA_REG 3 +/* PLL progrramming register B */ +#define AIC3X_PLL_PROGB_REG 4 +/* PLL progrramming register C */ +#define AIC3X_PLL_PROGC_REG 5 +/* PLL progrramming register D */ +#define AIC3X_PLL_PROGD_REG 6 +/* Codec datapath setup register */ +#define AIC3X_CODEC_DATAPATH_REG 7 +/* Audio serial data interface control register A */ +#define AIC3X_ASD_INTF_CTRLA 8 +/* Audio serial data interface control register B */ +#define AIC3X_ASD_INTF_CTRLB 9 +/* Audio overflow status and PLL R value programming register */ +#define AIC3X_OVRF_STATUS_AND_PLLR_REG 11 +/* Audio codec digital filter control register */ +#define AIC3X_CODEC_DFILT_CTRL 12 + +/* ADC PGA Gain control registers */ +#define LADC_VOL 15 +#define RADC_VOL 16 +/* MIC3 control registers */ +#define MIC3LR_2_LADC_CTRL 17 +#define MIC3LR_2_RADC_CTRL 18 +/* Line1 Input control registers */ +#define LINE1L_2_LADC_CTRL 19 +#define LINE1R_2_RADC_CTRL 22 +/* Line2 Input control registers */ +#define LINE2L_2_LADC_CTRL 20 +#define LINE2R_2_RADC_CTRL 23 +/* MICBIAS Control Register */ +#define MICBIAS_CTRL 25 + +/* AGC Control Registers A, B, C */ +#define LAGC_CTRL_A 26 +#define LAGC_CTRL_B 27 +#define LAGC_CTRL_C 28 +#define RAGC_CTRL_A 29 +#define RAGC_CTRL_B 30 +#define RAGC_CTRL_C 31 + +/* DAC Power and Left High Power Output control registers */ +#define DAC_PWR 37 +#define HPLCOM_CFG 37 +/* Right High Power Output control registers */ +#define HPRCOM_CFG 38 +/* DAC Output Switching control registers */ +#define DAC_LINE_MUX 41 +/* High Power Output Driver Pop Reduction registers */ +#define HPOUT_POP_REDUCTION 42 +/* DAC Digital control registers */ +#define LDAC_VOL 43 +#define RDAC_VOL 44 +/* High Power Output control registers */ +#define LINE2L_2_HPLOUT_VOL 45 +#define LINE2R_2_HPROUT_VOL 62 +#define PGAL_2_HPLOUT_VOL 46 +#define PGAR_2_HPROUT_VOL 63 +#define DACL1_2_HPLOUT_VOL 47 +#define DACR1_2_HPROUT_VOL 64 +#define HPLOUT_CTRL 51 +#define HPROUT_CTRL 65 +/* High Power COM control registers */ +#define LINE2L_2_HPLCOM_VOL 52 +#define LINE2R_2_HPRCOM_VOL 69 +#define PGAL_2_HPLCOM_VOL 53 +#define PGAR_2_HPRCOM_VOL 70 +#define DACL1_2_HPLCOM_VOL 54 +#define DACR1_2_HPRCOM_VOL 71 +#define HPLCOM_CTRL 58 +#define HPRCOM_CTRL 72 +/* Mono Line Output Plus/Minus control registers */ +#define LINE2L_2_MONOLOPM_VOL 73 +#define LINE2R_2_MONOLOPM_VOL 76 +#define PGAL_2_MONOLOPM_VOL 74 +#define PGAR_2_MONOLOPM_VOL 77 +#define DACL1_2_MONOLOPM_VOL 75 +#define DACR1_2_MONOLOPM_VOL 78 +#define MONOLOPM_CTRL 79 +/* Line Output Plus/Minus control registers */ +#define LINE2L_2_LLOPM_VOL 80 +#define LINE2R_2_RLOPM_VOL 90 +#define PGAL_2_LLOPM_VOL 81 +#define PGAR_2_RLOPM_VOL 91 +#define DACL1_2_LLOPM_VOL 82 +#define DACR1_2_RLOPM_VOL 92 +#define LLOPM_CTRL 86 +#define RLOPM_CTRL 93 +/* GPIO/IRQ registers */ +#define AIC3X_STICKY_IRQ_FLAGS_REG 96 +#define AIC3X_RT_IRQ_FLAGS_REG 97 +#define AIC3X_GPIO1_REG 98 +#define AIC3X_GPIO2_REG 99 +#define AIC3X_GPIOA_REG 100 +#define AIC3X_GPIOB_REG 101 +/* Clock generation control register */ +#define AIC3X_CLKGEN_CTRL_REG 102 + +/* Page select register bits */ +#define PAGE0_SELECT 0 +#define PAGE1_SELECT 1 + +/* Audio serial data interface control register A bits */ +#define BIT_CLK_MASTER 0x80 +#define WORD_CLK_MASTER 0x40 + +/* Codec Datapath setup register 7 */ +#define FSREF_44100 (1 << 7) +#define FSREF_48000 (0 << 7) +#define DUAL_RATE_MODE ((1 << 5) | (1 << 6)) +#define LDAC2LCH (0x1 << 3) +#define RDAC2RCH (0x1 << 1) + +/* PLL registers bitfields */ +#define PLLP_SHIFT 0 +#define PLLQ_SHIFT 3 +#define PLLR_SHIFT 0 +#define PLLJ_SHIFT 2 +#define PLLD_MSB_SHIFT 0 +#define PLLD_LSB_SHIFT 2 + +/* Clock generation register bits */ +#define CODEC_CLKIN_PLLDIV 0 +#define CODEC_CLKIN_CLKDIV 1 +#define PLL_CLKIN_SHIFT 4 +#define MCLK_SOURCE 0x0 +#define PLL_CLKDIV_SHIFT 0 + +/* Software reset register bits */ +#define SOFT_RESET 0x80 + +/* PLL progrramming register A bits */ +#define PLL_ENABLE 0x80 + +/* Route bits */ +#define ROUTE_ON 0x80 + +/* Mute bits */ +#define UNMUTE 0x08 +#define MUTE_ON 0x80 + +/* Power bits */ +#define LADC_PWR_ON 0x04 +#define RADC_PWR_ON 0x04 +#define LDAC_PWR_ON 0x80 +#define RDAC_PWR_ON 0x40 +#define HPLOUT_PWR_ON 0x01 +#define HPROUT_PWR_ON 0x01 +#define HPLCOM_PWR_ON 0x01 +#define HPRCOM_PWR_ON 0x01 +#define MONOLOPM_PWR_ON 0x01 +#define LLOPM_PWR_ON 0x01 +#define RLOPM_PWR_ON 0x01 + +#define INVERT_VOL(val) (0x7f - val) + +/* Default output volume (inverted) */ +#define DEFAULT_VOL INVERT_VOL(0x50) +/* Default input volume */ +#define DEFAULT_GAIN 0x20 + +/* GPIO API */ +enum { + AIC3X_GPIO1_FUNC_DISABLED = 0, + AIC3X_GPIO1_FUNC_AUDIO_WORDCLK_ADC = 1, + AIC3X_GPIO1_FUNC_CLOCK_MUX = 2, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV2 = 3, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV4 = 4, + AIC3X_GPIO1_FUNC_CLOCK_MUX_DIV8 = 5, + AIC3X_GPIO1_FUNC_SHORT_CIRCUIT_IRQ = 6, + AIC3X_GPIO1_FUNC_AGC_NOISE_IRQ = 7, + AIC3X_GPIO1_FUNC_INPUT = 8, + AIC3X_GPIO1_FUNC_OUTPUT = 9, + AIC3X_GPIO1_FUNC_DIGITAL_MIC_MODCLK = 10, + AIC3X_GPIO1_FUNC_AUDIO_WORDCLK = 11, + AIC3X_GPIO1_FUNC_BUTTON_IRQ = 12, + AIC3X_GPIO1_FUNC_HEADSET_DETECT_IRQ = 13, + AIC3X_GPIO1_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 14, + AIC3X_GPIO1_FUNC_ALL_IRQ = 16 +}; + +enum { + AIC3X_GPIO2_FUNC_DISABLED = 0, + AIC3X_GPIO2_FUNC_HEADSET_DETECT_IRQ = 2, + AIC3X_GPIO2_FUNC_INPUT = 3, + AIC3X_GPIO2_FUNC_OUTPUT = 4, + AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT = 5, + AIC3X_GPIO2_FUNC_AUDIO_BITCLK = 8, + AIC3X_GPIO2_FUNC_HEADSET_DETECT_OR_BUTTON_IRQ = 9, + AIC3X_GPIO2_FUNC_ALL_IRQ = 10, + AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_OR_AGC_IRQ = 11, + AIC3X_GPIO2_FUNC_HEADSET_OR_BUTTON_PRESS_OR_SHORT_CIRCUIT_IRQ = 12, + AIC3X_GPIO2_FUNC_SHORT_CIRCUIT_IRQ = 13, + AIC3X_GPIO2_FUNC_AGC_NOISE_IRQ = 14, + AIC3X_GPIO2_FUNC_BUTTON_PRESS_IRQ = 15 +}; + +void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state); +int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio); +int aic3x_headset_detected(struct snd_soc_codec *codec); + +struct aic3x_setup_data { + int i2c_bus; + unsigned short i2c_address; + unsigned int gpio_func[2]; +}; + +extern struct snd_soc_dai aic3x_dai; +extern struct snd_soc_codec_device soc_codec_dev_aic3x; + +#endif /* _AIC3X_H */ diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c new file mode 100644 index 0000000..a69ee72 --- /dev/null +++ b/sound/soc/codecs/uda1380.c @@ -0,0 +1,849 @@ +/* + * uda1380.c - Philips UDA1380 ALSA SoC audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com> + * Improved support for DAPM and audio routing/mixing capabilities, + * added TLV support. + * + * Modified by Richard Purdie <richard@openedhand.com> to fit into SoC + * codec model. + * + * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org> + * Copyright 2005 Openedhand Ltd. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/ioctl.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> + +#include "uda1380.h" + +#define UDA1380_VERSION "0.6" + +/* + * uda1380 register cache + */ +static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = { + 0x0502, 0x0000, 0x0000, 0x3f3f, + 0x0202, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0xff00, 0x0000, 0x4800, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x8000, 0x0002, 0x0000, +}; + +/* + * read uda1380 register cache + */ +static inline unsigned int uda1380_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == UDA1380_RESET) + return 0; + if (reg >= UDA1380_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write uda1380 register cache + */ +static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= UDA1380_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the UDA1380 register space + */ +static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + /* data is + * data[0] is register offset + * data[1] is MS byte + * data[2] is LS byte + */ + data[0] = reg; + data[1] = (value & 0xff00) >> 8; + data[2] = value & 0x00ff; + + uda1380_write_reg_cache(codec, reg, value); + + /* the interpolator & decimator regs must only be written when the + * codec DAI is active. + */ + if (!codec->active && (reg >= UDA1380_MVOL)) + return 0; + pr_debug("uda1380: hw write %x val %x\n", reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) { + unsigned int val; + i2c_master_send(codec->control_data, data, 1); + i2c_master_recv(codec->control_data, data, 2); + val = (data[0]<<8) | data[1]; + if (val != value) { + pr_debug("uda1380: READ BACK VAL %x\n", + (data[0]<<8) | data[1]); + return -EIO; + } + return 0; + } else + return -EIO; +} + +#define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0) + +/* declarations of ALSA reg_elem_REAL controls */ +static const char *uda1380_deemp[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", + "96kHz", +}; +static const char *uda1380_input_sel[] = { + "Line", + "Mic + Line R", + "Line L", + "Mic", +}; +static const char *uda1380_output_sel[] = { + "DAC", + "Analog Mixer", +}; +static const char *uda1380_spf_mode[] = { + "Flat", + "Minimum1", + "Minimum2", + "Maximum" +}; +static const char *uda1380_capture_sel[] = { + "ADC", + "Digital Mixer" +}; +static const char *uda1380_sel_ns[] = { + "3rd-order", + "5th-order" +}; +static const char *uda1380_mix_control[] = { + "off", + "PCM only", + "before sound processing", + "after sound processing" +}; +static const char *uda1380_sdet_setting[] = { + "3200", + "4800", + "9600", + "19200" +}; +static const char *uda1380_os_setting[] = { + "single-speed", + "double-speed (no mixing)", + "quad-speed (no mixing)" +}; + +static const struct soc_enum uda1380_deemp_enum[] = { + SOC_ENUM_SINGLE(UDA1380_DEEMP, 8, 5, uda1380_deemp), + SOC_ENUM_SINGLE(UDA1380_DEEMP, 0, 5, uda1380_deemp), +}; +static const struct soc_enum uda1380_input_sel_enum = + SOC_ENUM_SINGLE(UDA1380_ADC, 2, 4, uda1380_input_sel); /* SEL_MIC, SEL_LNA */ +static const struct soc_enum uda1380_output_sel_enum = + SOC_ENUM_SINGLE(UDA1380_PM, 7, 2, uda1380_output_sel); /* R02_EN_AVC */ +static const struct soc_enum uda1380_spf_enum = + SOC_ENUM_SINGLE(UDA1380_MODE, 14, 4, uda1380_spf_mode); /* M */ +static const struct soc_enum uda1380_capture_sel_enum = + SOC_ENUM_SINGLE(UDA1380_IFACE, 6, 2, uda1380_capture_sel); /* SEL_SOURCE */ +static const struct soc_enum uda1380_sel_ns_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 14, 2, uda1380_sel_ns); /* SEL_NS */ +static const struct soc_enum uda1380_mix_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 12, 4, uda1380_mix_control); /* MIX, MIX_POS */ +static const struct soc_enum uda1380_sdet_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 4, 4, uda1380_sdet_setting); /* SD_VALUE */ +static const struct soc_enum uda1380_os_enum = + SOC_ENUM_SINGLE(UDA1380_MIXER, 0, 3, uda1380_os_setting); /* OS */ + +/* + * from -48 dB in 1.5 dB steps (mute instead of -49.5 dB) + */ +static DECLARE_TLV_DB_SCALE(amix_tlv, -4950, 150, 1); + +/* + * from -78 dB in 1 dB steps (3 dB steps, really. LSB are ignored), + * from -66 dB in 0.5 dB steps (2 dB steps, really) and + * from -52 dB in 0.25 dB steps + */ +static const unsigned int mvol_tlv[] = { + TLV_DB_RANGE_HEAD(3), + 0, 15, TLV_DB_SCALE_ITEM(-8200, 100, 1), + 16, 43, TLV_DB_SCALE_ITEM(-6600, 50, 0), + 44, 252, TLV_DB_SCALE_ITEM(-5200, 25, 0), +}; + +/* + * from -72 dB in 1.5 dB steps (6 dB steps really), + * from -66 dB in 0.75 dB steps (3 dB steps really), + * from -60 dB in 0.5 dB steps (2 dB steps really) and + * from -46 dB in 0.25 dB steps + */ +static const unsigned int vc_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 7, TLV_DB_SCALE_ITEM(-7800, 150, 1), + 8, 15, TLV_DB_SCALE_ITEM(-6600, 75, 0), + 16, 43, TLV_DB_SCALE_ITEM(-6000, 50, 0), + 44, 228, TLV_DB_SCALE_ITEM(-4600, 25, 0), +}; + +/* from 0 to 6 dB in 2 dB steps if SPF mode != flat */ +static DECLARE_TLV_DB_SCALE(tr_tlv, 0, 200, 0); + +/* from 0 to 24 dB in 2 dB steps, if SPF mode == maximum, otherwise cuts + * off at 18 dB max) */ +static DECLARE_TLV_DB_SCALE(bb_tlv, 0, 200, 0); + +/* from -63 to 24 dB in 0.5 dB steps (-128...48) */ +static DECLARE_TLV_DB_SCALE(dec_tlv, -6400, 50, 1); + +/* from 0 to 24 dB in 3 dB steps */ +static DECLARE_TLV_DB_SCALE(pga_tlv, 0, 300, 0); + +/* from 0 to 30 dB in 2 dB steps */ +static DECLARE_TLV_DB_SCALE(vga_tlv, 0, 200, 0); + +static const struct snd_kcontrol_new uda1380_snd_controls[] = { + SOC_DOUBLE_TLV("Analog Mixer Volume", UDA1380_AMIX, 0, 8, 44, 1, amix_tlv), /* AVCR, AVCL */ + SOC_DOUBLE_TLV("Master Playback Volume", UDA1380_MVOL, 0, 8, 252, 1, mvol_tlv), /* MVCL, MVCR */ + SOC_SINGLE_TLV("ADC Playback Volume", UDA1380_MIXVOL, 8, 228, 1, vc_tlv), /* VC2 */ + SOC_SINGLE_TLV("PCM Playback Volume", UDA1380_MIXVOL, 0, 228, 1, vc_tlv), /* VC1 */ + SOC_ENUM("Sound Processing Filter", uda1380_spf_enum), /* M */ + SOC_DOUBLE_TLV("Tone Control - Treble", UDA1380_MODE, 4, 12, 3, 0, tr_tlv), /* TRL, TRR */ + SOC_DOUBLE_TLV("Tone Control - Bass", UDA1380_MODE, 0, 8, 15, 0, bb_tlv), /* BBL, BBR */ +/**/ SOC_SINGLE("Master Playback Switch", UDA1380_DEEMP, 14, 1, 1), /* MTM */ + SOC_SINGLE("ADC Playback Switch", UDA1380_DEEMP, 11, 1, 1), /* MT2 from decimation filter */ + SOC_ENUM("ADC Playback De-emphasis", uda1380_deemp_enum[0]), /* DE2 */ + SOC_SINGLE("PCM Playback Switch", UDA1380_DEEMP, 3, 1, 1), /* MT1, from digital data input */ + SOC_ENUM("PCM Playback De-emphasis", uda1380_deemp_enum[1]), /* DE1 */ + SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */ + SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */ + SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */ + SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */ + SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */ + SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */ + SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */ + SOC_DOUBLE_S8_TLV("ADC Capture Volume", UDA1380_DEC, -128, 48, dec_tlv), /* ML_DEC, MR_DEC */ +/**/ SOC_SINGLE("ADC Capture Switch", UDA1380_PGA, 15, 1, 1), /* MT_ADC */ + SOC_DOUBLE_TLV("Line Capture Volume", UDA1380_PGA, 0, 8, 8, 0, pga_tlv), /* PGA_GAINCTRLL, PGA_GAINCTRLR */ + SOC_SINGLE("ADC Polarity inverting Switch", UDA1380_ADC, 12, 1, 0), /* ADCPOL_INV */ + SOC_SINGLE_TLV("Mic Capture Volume", UDA1380_ADC, 8, 15, 0, vga_tlv), /* VGA_CTRL */ + SOC_SINGLE("DC Filter Bypass Switch", UDA1380_ADC, 1, 1, 0), /* SKIP_DCFIL (before decimator) */ + SOC_SINGLE("DC Filter Enable Switch", UDA1380_ADC, 0, 1, 0), /* EN_DCFIL (at output of decimator) */ + SOC_SINGLE("AGC Timing", UDA1380_AGC, 8, 7, 0), /* TODO: enum, see table 62 */ + SOC_SINGLE("AGC Target level", UDA1380_AGC, 2, 3, 1), /* AGC_LEVEL */ + /* -5.5, -8, -11.5, -14 dBFS */ + SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0), +}; + +/* add non dapm controls */ +static int uda1380_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&uda1380_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Input mux */ +static const struct snd_kcontrol_new uda1380_input_mux_control = + SOC_DAPM_ENUM("Route", uda1380_input_sel_enum); + +/* Output mux */ +static const struct snd_kcontrol_new uda1380_output_mux_control = + SOC_DAPM_ENUM("Route", uda1380_output_sel_enum); + +/* Capture mux */ +static const struct snd_kcontrol_new uda1380_capture_mux_control = + SOC_DAPM_ENUM("Route", uda1380_capture_sel_enum); + + +static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = { + SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, + &uda1380_input_mux_control), + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, 0, 0, + &uda1380_output_mux_control), + SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, + &uda1380_capture_mux_control), + SND_SOC_DAPM_PGA("Left PGA", UDA1380_PM, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right PGA", UDA1380_PM, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mic LNA", UDA1380_PM, 4, 0, NULL, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", UDA1380_PM, 2, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", UDA1380_PM, 0, 0), + SND_SOC_DAPM_INPUT("VINM"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_MIXER("Analog Mixer", UDA1380_PM, 6, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("VOUTLHP"), + SND_SOC_DAPM_OUTPUT("VOUTRHP"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_DAC("DAC", "Playback", UDA1380_PM, 10, 0), + SND_SOC_DAPM_PGA("HeadPhone Driver", UDA1380_PM, 13, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + + /* output mux */ + {"HeadPhone Driver", NULL, "Output Mux"}, + {"VOUTR", NULL, "Output Mux"}, + {"VOUTL", NULL, "Output Mux"}, + + {"Analog Mixer", NULL, "VINR"}, + {"Analog Mixer", NULL, "VINL"}, + {"Analog Mixer", NULL, "DAC"}, + + {"Output Mux", "DAC", "DAC"}, + {"Output Mux", "Analog Mixer", "Analog Mixer"}, + + /* {"DAC", "Digital Mixer", "I2S" } */ + + /* headphone driver */ + {"VOUTLHP", NULL, "HeadPhone Driver"}, + {"VOUTRHP", NULL, "HeadPhone Driver"}, + + /* input mux */ + {"Left ADC", NULL, "Input Mux"}, + {"Input Mux", "Mic", "Mic LNA"}, + {"Input Mux", "Mic + Line R", "Mic LNA"}, + {"Input Mux", "Line L", "Left PGA"}, + {"Input Mux", "Line", "Left PGA"}, + + /* right input */ + {"Right ADC", "Mic + Line R", "Right PGA"}, + {"Right ADC", "Line", "Right PGA"}, + + /* inputs */ + {"Mic LNA", NULL, "VINM"}, + {"Left PGA", NULL, "VINL"}, + {"Right PGA", NULL, "VINR"}, +}; + +static int uda1380_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, uda1380_dapm_widgets, + ARRAY_SIZE(uda1380_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int iface; + + /* set up DAI based upon fmt */ + iface = uda1380_read_reg_cache(codec, UDA1380_IFACE); + iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK); + + /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= R01_SFORI_I2S | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_LSB: + iface |= R01_SFORI_LSB16 | R01_SFORO_I2S; + break; + case SND_SOC_DAIFMT_MSB: + iface |= R01_SFORI_MSB | R01_SFORO_I2S; + } + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) + iface |= R01_SIM; + + uda1380_write(codec, UDA1380_IFACE, iface); + + return 0; +} + +/* + * Flush reg cache + * We can only write the interpolator and decimator registers + * when the DAI is being clocked by the CPU DAI. It's up to the + * machine and cpu DAI driver to do this before we are called. + */ +static int uda1380_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg, reg_start, reg_end, clk; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg_start = UDA1380_MVOL; + reg_end = UDA1380_MIXER; + } else { + reg_start = UDA1380_DEC; + reg_end = UDA1380_AGC; + } + + /* FIXME disable DAC_CLK */ + clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK); + + for (reg = reg_start; reg <= reg_end; reg++) { + pr_debug("uda1380: flush reg %x val %x:", reg, + uda1380_read_reg_cache(codec, reg)); + uda1380_write(codec, reg, uda1380_read_reg_cache(codec, reg)); + } + + /* FIXME enable DAC_CLK */ + uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK); + + return 0; +} + +static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* set WSPLL power and divider if running from this clock */ + if (clk & R00_DAC_CLK) { + int rate = params_rate(params); + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + clk &= ~0x3; /* clear SEL_LOOP_DIV */ + switch (rate) { + case 6250 ... 12500: + clk |= 0x0; + break; + case 12501 ... 25000: + clk |= 0x1; + break; + case 25001 ... 50000: + clk |= 0x2; + break; + case 50001 ... 100000: + clk |= 0x3; + break; + } + uda1380_write(codec, UDA1380_PM, R02_PON_PLL | pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk |= R00_EN_DAC | R00_EN_INT; + else + clk |= R00_EN_ADC | R00_EN_DEC; + + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + + /* shut down WSPLL power if running from this clock */ + if (clk & R00_DAC_CLK) { + u16 pm = uda1380_read_reg_cache(codec, UDA1380_PM); + uda1380_write(codec, UDA1380_PM, ~R02_PON_PLL & pm); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + clk &= ~(R00_EN_DAC | R00_EN_INT); + else + clk &= ~(R00_EN_ADC | R00_EN_DEC); + + uda1380_write(codec, UDA1380_CLK, clk); +} + +static int uda1380_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM; + + /* FIXME: mute(codec,0) is called when the magician clock is already + * set to WSPLL, but for some unknown reason writing to interpolator + * registers works only when clocked by SYSCLK */ + u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); + uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk); + if (mute) + uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM); + else + uda1380_write(codec, UDA1380_DEEMP, mute_reg); + uda1380_write(codec, UDA1380_CLK, clk); + return 0; +} + +static int uda1380_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int pm = uda1380_read_reg_cache(codec, UDA1380_PM); + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS | pm); + break; + case SND_SOC_BIAS_STANDBY: + uda1380_write(codec, UDA1380_PM, R02_PON_BIAS); + break; + case SND_SOC_BIAS_OFF: + uda1380_write(codec, UDA1380_PM, 0x0); + break; + } + codec->bias_level = level; + return 0; +} + +#define UDA1380_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +struct snd_soc_dai uda1380_dai[] = { +{ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{ /* playback only - dual interface */ + .name = "UDA1380", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .digital_mute = uda1380_mute, + .set_fmt = uda1380_set_dai_fmt, + }, +}, +{ /* capture only - dual interface*/ + .name = "UDA1380", + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA1380_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = { + .hw_params = uda1380_pcm_hw_params, + .shutdown = uda1380_pcm_shutdown, + .prepare = uda1380_pcm_prepare, + }, + .dai_ops = { + .set_fmt = uda1380_set_dai_fmt, + }, +}, +}; +EXPORT_SYMBOL_GPL(uda1380_dai); + +static int uda1380_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int uda1380_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(uda1380_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + uda1380_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the UDA1380 driver + * register mixer and dsp interfaces with the kernel + */ +static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "UDA1380"; + codec->owner = THIS_MODULE; + codec->read = uda1380_read_reg_cache; + codec->write = uda1380_write; + codec->set_bias_level = uda1380_set_bias_level; + codec->dai = uda1380_dai; + codec->num_dai = ARRAY_SIZE(uda1380_dai); + codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + codec->reg_cache_size = ARRAY_SIZE(uda1380_reg); + codec->reg_cache_step = 1; + uda1380_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("uda1380: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + /* set clock input */ + switch (dac_clk) { + case UDA1380_DAC_CLK_SYSCLK: + uda1380_write(codec, UDA1380_CLK, 0); + break; + case UDA1380_DAC_CLK_WSPLL: + uda1380_write(codec, UDA1380_CLK, R00_DAC_CLK); + break; + } + + /* uda1380 init */ + uda1380_add_controls(codec); + uda1380_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + pr_err("uda1380: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *uda1380_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static int uda1380_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = uda1380_socdev; + struct uda1380_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = uda1380_init(socdev, setup->dac_clk); + if (ret < 0) + pr_err("uda1380: failed to initialise UDA1380\n"); + + return ret; +} + +static int uda1380_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id uda1380_i2c_id[] = { + { "uda1380", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id); + +static struct i2c_driver uda1380_i2c_driver = { + .driver = { + .name = "UDA1380 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = uda1380_i2c_probe, + .remove = uda1380_i2c_remove, + .id_table = uda1380_i2c_id, +}; + +static int uda1380_add_i2c_device(struct platform_device *pdev, + const struct uda1380_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&uda1380_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "uda1380", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&uda1380_i2c_driver); + return -ENODEV; +} +#endif + +static int uda1380_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct uda1380_setup_data *setup; + struct snd_soc_codec *codec; + int ret; + + pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + uda1380_socdev = socdev; + ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = uda1380_add_i2c_device(pdev, setup); + } +#endif + + if (ret != 0) + kfree(codec); + return ret; +} + +/* power down chip */ +static int uda1380_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&uda1380_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_uda1380 = { + .probe = uda1380_probe, + .remove = uda1380_remove, + .suspend = uda1380_suspend, + .resume = uda1380_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); + +MODULE_AUTHOR("Giorgio Padrin"); +MODULE_DESCRIPTION("Audio support for codec Philips UDA1380"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h new file mode 100644 index 0000000..c55c17a --- /dev/null +++ b/sound/soc/codecs/uda1380.h @@ -0,0 +1,90 @@ +/* + * Audio support for Philips UDA1380 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org> + */ + +#ifndef _UDA1380_H +#define _UDA1380_H + +#define UDA1380_CLK 0x00 +#define UDA1380_IFACE 0x01 +#define UDA1380_PM 0x02 +#define UDA1380_AMIX 0x03 +#define UDA1380_HP 0x04 +#define UDA1380_MVOL 0x10 +#define UDA1380_MIXVOL 0x11 +#define UDA1380_MODE 0x12 +#define UDA1380_DEEMP 0x13 +#define UDA1380_MIXER 0x14 +#define UDA1380_INTSTAT 0x18 +#define UDA1380_DEC 0x20 +#define UDA1380_PGA 0x21 +#define UDA1380_ADC 0x22 +#define UDA1380_AGC 0x23 +#define UDA1380_DECSTAT 0x28 +#define UDA1380_RESET 0x7f + +#define UDA1380_CACHEREGNUM 0x24 + +/* Register flags */ +#define R00_EN_ADC 0x0800 +#define R00_EN_DEC 0x0400 +#define R00_EN_DAC 0x0200 +#define R00_EN_INT 0x0100 +#define R00_DAC_CLK 0x0010 +#define R01_SFORI_I2S 0x0000 +#define R01_SFORI_LSB16 0x0100 +#define R01_SFORI_LSB18 0x0200 +#define R01_SFORI_LSB20 0x0300 +#define R01_SFORI_MSB 0x0500 +#define R01_SFORI_MASK 0x0700 +#define R01_SFORO_I2S 0x0000 +#define R01_SFORO_LSB16 0x0001 +#define R01_SFORO_LSB18 0x0002 +#define R01_SFORO_LSB20 0x0003 +#define R01_SFORO_LSB24 0x0004 +#define R01_SFORO_MSB 0x0005 +#define R01_SFORO_MASK 0x0007 +#define R01_SEL_SOURCE 0x0040 +#define R01_SIM 0x0010 +#define R02_PON_PLL 0x8000 +#define R02_PON_HP 0x2000 +#define R02_PON_DAC 0x0400 +#define R02_PON_BIAS 0x0100 +#define R02_EN_AVC 0x0080 +#define R02_PON_AVC 0x0040 +#define R02_PON_LNA 0x0010 +#define R02_PON_PGAL 0x0008 +#define R02_PON_ADCL 0x0004 +#define R02_PON_PGAR 0x0002 +#define R02_PON_ADCR 0x0001 +#define R13_MTM 0x4000 +#define R14_SILENCE 0x0080 +#define R14_SDET_ON 0x0040 +#define R21_MT_ADC 0x8000 +#define R22_SEL_LNA 0x0008 +#define R22_SEL_MIC 0x0004 +#define R22_SKIP_DCFIL 0x0002 +#define R23_AGC_EN 0x0001 + +struct uda1380_setup_data { + int i2c_bus; + unsigned short i2c_address; + int dac_clk; +#define UDA1380_DAC_CLK_SYSCLK 0 +#define UDA1380_DAC_CLK_WSPLL 1 +}; + +#define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */ +#define UDA1380_DAI_PLAYBACK 1 /* playback DAI */ +#define UDA1380_DAI_CAPTURE 2 /* capture DAI */ + +extern struct snd_soc_dai uda1380_dai[3]; +extern struct snd_soc_codec_device soc_codec_dev_uda1380; + +#endif /* _UDA1380_H */ diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c new file mode 100644 index 0000000..d8ca2da --- /dev/null +++ b/sound/soc/codecs/wm8510.c @@ -0,0 +1,895 @@ +/* + * wm8510.c -- WM8510 ALSA Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8510.h" + +#define WM8510_VERSION "0.6" + +struct snd_soc_codec_device soc_codec_dev_wm8510; + +/* + * wm8510 register cache + * We can't read the WM8510 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8510_reg[WM8510_CACHEREGNUM] = { + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0050, 0x0000, 0x0140, 0x0000, + 0x0000, 0x0000, 0x0000, 0x00ff, + 0x0000, 0x0000, 0x0100, 0x00ff, + 0x0000, 0x0000, 0x012c, 0x002c, + 0x002c, 0x002c, 0x002c, 0x0000, + 0x0032, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0038, 0x000b, 0x0032, 0x0000, + 0x0008, 0x000c, 0x0093, 0x00e9, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0003, 0x0010, 0x0000, 0x0000, + 0x0000, 0x0002, 0x0001, 0x0000, + 0x0000, 0x0000, 0x0039, 0x0000, + 0x0001, +}; + +#define WM8510_POWER1_BIASEN 0x08 +#define WM8510_POWER1_BUFIOEN 0x10 + +/* + * read wm8510 register cache + */ +static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8510_RESET) + return 0; + if (reg >= WM8510_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8510 register cache + */ +static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8510_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8510 register space + */ +static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8510 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8510_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0) + +static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" }; +static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8510_alc[] = { "ALC", "Limiter" }; + +static const struct soc_enum wm8510_enum[] = { + SOC_ENUM_SINGLE(WM8510_COMP, 1, 4, wm8510_companding), /* adc */ + SOC_ENUM_SINGLE(WM8510_COMP, 3, 4, wm8510_companding), /* dac */ + SOC_ENUM_SINGLE(WM8510_DAC, 4, 4, wm8510_deemp), + SOC_ENUM_SINGLE(WM8510_ALC3, 8, 2, wm8510_alc), +}; + +static const struct snd_kcontrol_new wm8510_snd_controls[] = { + +SOC_SINGLE("Digital Loopback Switch", WM8510_COMP, 0, 1, 0), + +SOC_ENUM("DAC Companding", wm8510_enum[1]), +SOC_ENUM("ADC Companding", wm8510_enum[0]), + +SOC_ENUM("Playback De-emphasis", wm8510_enum[2]), +SOC_SINGLE("DAC Inversion Switch", WM8510_DAC, 0, 1, 0), + +SOC_SINGLE("Master Playback Volume", WM8510_DACVOL, 0, 127, 0), + +SOC_SINGLE("High Pass Filter Switch", WM8510_ADC, 8, 1, 0), +SOC_SINGLE("High Pass Cut Off", WM8510_ADC, 4, 7, 0), +SOC_SINGLE("ADC Inversion Switch", WM8510_COMP, 0, 1, 0), + +SOC_SINGLE("Capture Volume", WM8510_ADCVOL, 0, 127, 0), + +SOC_SINGLE("DAC Playback Limiter Switch", WM8510_DACLIM1, 8, 1, 0), +SOC_SINGLE("DAC Playback Limiter Decay", WM8510_DACLIM1, 4, 15, 0), +SOC_SINGLE("DAC Playback Limiter Attack", WM8510_DACLIM1, 0, 15, 0), + +SOC_SINGLE("DAC Playback Limiter Threshold", WM8510_DACLIM2, 4, 7, 0), +SOC_SINGLE("DAC Playback Limiter Boost", WM8510_DACLIM2, 0, 15, 0), + +SOC_SINGLE("ALC Enable Switch", WM8510_ALC1, 8, 1, 0), +SOC_SINGLE("ALC Capture Max Gain", WM8510_ALC1, 3, 7, 0), +SOC_SINGLE("ALC Capture Min Gain", WM8510_ALC1, 0, 7, 0), + +SOC_SINGLE("ALC Capture ZC Switch", WM8510_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold", WM8510_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Capture Target", WM8510_ALC2, 0, 15, 0), + +SOC_ENUM("ALC Capture Mode", wm8510_enum[3]), +SOC_SINGLE("ALC Capture Decay", WM8510_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack", WM8510_ALC3, 0, 15, 0), + +SOC_SINGLE("ALC Capture Noise Gate Switch", WM8510_NGATE, 3, 1, 0), +SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8510_NGATE, 0, 7, 0), + +SOC_SINGLE("Capture PGA ZC Switch", WM8510_INPPGA, 7, 1, 0), +SOC_SINGLE("Capture PGA Volume", WM8510_INPPGA, 0, 63, 0), + +SOC_SINGLE("Speaker Playback ZC Switch", WM8510_SPKVOL, 7, 1, 0), +SOC_SINGLE("Speaker Playback Switch", WM8510_SPKVOL, 6, 1, 1), +SOC_SINGLE("Speaker Playback Volume", WM8510_SPKVOL, 0, 63, 0), +SOC_SINGLE("Speaker Boost", WM8510_OUTPUT, 2, 1, 0), + +SOC_SINGLE("Capture Boost(+20dB)", WM8510_ADCBOOST, 8, 1, 0), +SOC_SINGLE("Mono Playback Switch", WM8510_MONOMIX, 6, 1, 1), +}; + +/* add non dapm controls */ +static int wm8510_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8510_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8510_snd_controls[i], codec, + NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Speaker Output Mixer */ +static const struct snd_kcontrol_new wm8510_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_SPKMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_SPKMIX, 5, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_SPKMIX, 0, 1, 0), +}; + +/* Mono Output Mixer */ +static const struct snd_kcontrol_new wm8510_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8510_MONOMIX, 1, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", WM8510_MONOMIX, 2, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8510_boost_controls[] = { +SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 1), +SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0), +SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0), +}; + +static const struct snd_kcontrol_new wm8510_micpga_controls[] = { +SOC_DAPM_SINGLE("MICP Switch", WM8510_INPUT, 0, 1, 0), +SOC_DAPM_SINGLE("MICN Switch", WM8510_INPUT, 1, 1, 0), +SOC_DAPM_SINGLE("AUX Switch", WM8510_INPUT, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8510_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Speaker Mixer", WM8510_POWER3, 2, 0, + &wm8510_speaker_mixer_controls[0], + ARRAY_SIZE(wm8510_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8510_POWER3, 3, 0, + &wm8510_mono_mixer_controls[0], + ARRAY_SIZE(wm8510_mono_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8510_POWER3, 0, 0), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8510_POWER2, 0, 0), +SND_SOC_DAPM_PGA("Aux Input", WM8510_POWER1, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0, + &wm8510_micpga_controls[0], + ARRAY_SIZE(wm8510_micpga_controls)), +SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0, + &wm8510_boost_controls[0], + ARRAY_SIZE(wm8510_boost_controls)), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8510_POWER1, 4, 0), + +SND_SOC_DAPM_INPUT("MICN"), +SND_SOC_DAPM_INPUT("MICP"), +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("SPKOUTP"), +SND_SOC_DAPM_OUTPUT("SPKOUTN"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Microphone PGA */ + {"Mic PGA", "MICN Switch", "MICN"}, + {"Mic PGA", "MICP Switch", "MICP"}, + { "Mic PGA", "AUX Switch", "Aux Input" }, + + /* Boost Mixer */ + {"Boost Mixer", "Mic PGA Switch", "Mic PGA"}, + {"Boost Mixer", "Mic Volume", "MICP"}, + {"Boost Mixer", "Aux Volume", "Aux Input"}, + + {"ADC", NULL, "Boost Mixer"}, +}; + +static int wm8510_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8510_dapm_widgets, + ARRAY_SIZE(wm8510_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct pll_ { + unsigned int pre_div:4; /* prescale - 1 */ + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div.pre_div = 1; + Ndiv = target / source; + } else + pll_div.pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8510 N value %d outwith recommended range!d\n", + Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + if (freq_in == 0 || freq_out == 0) { + /* Clock CODEC directly from MCLK */ + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK); + wm8510_write(codec, WM8510_CLOCK, reg & 0x0ff); + + /* Turn off PLL */ + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg & 0x1df); + return 0; + } + + pll_factors(freq_out*8, freq_in); + + wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); + wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18); + wm8510_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff); + wm8510_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff); + reg = wm8510_read_reg_cache(codec, WM8510_POWER1); + wm8510_write(codec, WM8510_POWER1, reg | 0x020); + + /* Run CODEC from PLL instead of MCLK */ + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK); + wm8510_write(codec, WM8510_CLOCK, reg | 0x100); + + return 0; +} + +/* + * Configure WM8510 clock dividers. + */ +static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8510_OPCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_GPIO) & 0x1cf; + wm8510_write(codec, WM8510_GPIO, reg | div); + break; + case WM8510_MCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1f; + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + case WM8510_ADCCLK: + reg = wm8510_read_reg_cache(codec, WM8510_ADC) & 0x1f7; + wm8510_write(codec, WM8510_ADC, reg | div); + break; + case WM8510_DACCLK: + reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0x1f7; + wm8510_write(codec, WM8510_DAC, reg | div); + break; + case WM8510_BCLKDIV: + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1e3; + wm8510_write(codec, WM8510_CLOCK, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 0x0001; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0010; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0008; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x00018; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0180; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0100; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0080; + break; + default: + return -EINVAL; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_CLOCK, clk); + return 0; +} + +static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f; + u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0020; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0040; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x0060; + break; + } + + /* filter coefficient */ + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + adn |= 0x5 << 1; + break; + case SNDRV_PCM_RATE_11025: + adn |= 0x4 << 1; + break; + case SNDRV_PCM_RATE_16000: + adn |= 0x3 << 1; + break; + case SNDRV_PCM_RATE_22050: + adn |= 0x2 << 1; + break; + case SNDRV_PCM_RATE_32000: + adn |= 0x1 << 1; + break; + case SNDRV_PCM_RATE_44100: + case SNDRV_PCM_RATE_48000: + break; + } + + wm8510_write(codec, WM8510_IFACE, iface); + wm8510_write(codec, WM8510_ADD, adn); + return 0; +} + +static int wm8510_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf; + + if (mute) + wm8510_write(codec, WM8510_DAC, mute_reg | 0x40); + else + wm8510_write(codec, WM8510_DAC, mute_reg); + return 0; +} + +/* liam need to make this lower power with dapm */ +static int wm8510_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 power1 = wm8510_read_reg_cache(codec, WM8510_POWER1) & ~0x3; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + power1 |= 0x1; /* VMID 50k */ + wm8510_write(codec, WM8510_POWER1, power1); + break; + + case SND_SOC_BIAS_STANDBY: + power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN; + + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Initial cap charge at VMID 5k */ + wm8510_write(codec, WM8510_POWER1, power1 | 0x3); + mdelay(100); + } + + power1 |= 0x2; /* VMID 500k */ + wm8510_write(codec, WM8510_POWER1, power1); + break; + + case SND_SOC_BIAS_OFF: + wm8510_write(codec, WM8510_POWER1, 0); + wm8510_write(codec, WM8510_POWER2, 0); + wm8510_write(codec, WM8510_POWER3, 0); + break; + } + + codec->bias_level = level; + return 0; +} + +#define WM8510_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8510_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai wm8510_dai = { + .name = "WM8510 HiFi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8510_RATES, + .formats = WM8510_FORMATS,}, + .ops = { + .hw_params = wm8510_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8510_mute, + .set_fmt = wm8510_set_dai_fmt, + .set_clkdiv = wm8510_set_dai_clkdiv, + .set_pll = wm8510_set_dai_pll, + }, +}; +EXPORT_SYMBOL_GPL(wm8510_dai); + +static int wm8510_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8510_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8510_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8510_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the WM8510 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8510_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8510"; + codec->owner = THIS_MODULE; + codec->read = wm8510_read_reg_cache; + codec->write = wm8510_write; + codec->set_bias_level = wm8510_set_bias_level; + codec->dai = &wm8510_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8510_reg); + codec->reg_cache = kmemdup(wm8510_reg, sizeof(wm8510_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8510_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + codec->bias_level = SND_SOC_BIAS_OFF; + wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8510_add_controls(codec); + wm8510_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8510_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8510 2 wire address is 0x1a + */ + +static int wm8510_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8510_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8510_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8510\n"); + + return ret; +} + +static int wm8510_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8510_i2c_id[] = { + { "wm8510", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id); + +static struct i2c_driver wm8510_i2c_driver = { + .driver = { + .name = "WM8510 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8510_i2c_probe, + .remove = wm8510_i2c_remove, + .id_table = wm8510_i2c_id, +}; + +static int wm8510_add_i2c_device(struct platform_device *pdev, + const struct wm8510_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8510_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8510", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8510_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8510_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8510_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8510_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8510\n"); + + return ret; +} + +static int __devexit wm8510_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8510_spi_driver = { + .driver = { + .name = "wm8510", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8510_spi_probe, + .remove = __devexit_p(wm8510_spi_remove), +}; + +static int wm8510_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif /* CONFIG_SPI_MASTER */ + +static int wm8510_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8510_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + pr_info("WM8510 Audio Codec %s", WM8510_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8510_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8510_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8510_spi_write; + ret = spi_register_driver(&wm8510_spi_driver); + if (ret != 0) + printk(KERN_ERR "can't add spi driver"); + } +#endif + + if (ret != 0) + kfree(codec); + return ret; +} + +/* power down chip */ +static int wm8510_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8510_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8510_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8510_spi_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8510 = { + .probe = wm8510_probe, + .remove = wm8510_remove, + .suspend = wm8510_suspend, + .resume = wm8510_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8510); + +MODULE_DESCRIPTION("ASoC WM8510 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h new file mode 100644 index 0000000..bdefcf5 --- /dev/null +++ b/sound/soc/codecs/wm8510.h @@ -0,0 +1,105 @@ +/* + * wm8510.h -- WM8510 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8510_H +#define _WM8510_H + +/* WM8510 register space */ + +#define WM8510_RESET 0x0 +#define WM8510_POWER1 0x1 +#define WM8510_POWER2 0x2 +#define WM8510_POWER3 0x3 +#define WM8510_IFACE 0x4 +#define WM8510_COMP 0x5 +#define WM8510_CLOCK 0x6 +#define WM8510_ADD 0x7 +#define WM8510_GPIO 0x8 +#define WM8510_DAC 0xa +#define WM8510_DACVOL 0xb +#define WM8510_ADC 0xe +#define WM8510_ADCVOL 0xf +#define WM8510_EQ1 0x12 +#define WM8510_EQ2 0x13 +#define WM8510_EQ3 0x14 +#define WM8510_EQ4 0x15 +#define WM8510_EQ5 0x16 +#define WM8510_DACLIM1 0x18 +#define WM8510_DACLIM2 0x19 +#define WM8510_NOTCH1 0x1b +#define WM8510_NOTCH2 0x1c +#define WM8510_NOTCH3 0x1d +#define WM8510_NOTCH4 0x1e +#define WM8510_ALC1 0x20 +#define WM8510_ALC2 0x21 +#define WM8510_ALC3 0x22 +#define WM8510_NGATE 0x23 +#define WM8510_PLLN 0x24 +#define WM8510_PLLK1 0x25 +#define WM8510_PLLK2 0x26 +#define WM8510_PLLK3 0x27 +#define WM8510_ATTEN 0x28 +#define WM8510_INPUT 0x2c +#define WM8510_INPPGA 0x2d +#define WM8510_ADCBOOST 0x2f +#define WM8510_OUTPUT 0x31 +#define WM8510_SPKMIX 0x32 +#define WM8510_SPKVOL 0x36 +#define WM8510_MONOMIX 0x38 + +#define WM8510_CACHEREGNUM 57 + +/* Clock divider Id's */ +#define WM8510_OPCLKDIV 0 +#define WM8510_MCLKDIV 1 +#define WM8510_ADCCLK 2 +#define WM8510_DACCLK 3 +#define WM8510_BCLKDIV 4 + +/* DAC clock dividers */ +#define WM8510_DACCLK_F2 (1 << 3) +#define WM8510_DACCLK_F4 (0 << 3) + +/* ADC clock dividers */ +#define WM8510_ADCCLK_F2 (1 << 3) +#define WM8510_ADCCLK_F4 (0 << 3) + +/* PLL Out dividers */ +#define WM8510_OPCLKDIV_1 (0 << 4) +#define WM8510_OPCLKDIV_2 (1 << 4) +#define WM8510_OPCLKDIV_3 (2 << 4) +#define WM8510_OPCLKDIV_4 (3 << 4) + +/* BCLK clock dividers */ +#define WM8510_BCLKDIV_1 (0 << 2) +#define WM8510_BCLKDIV_2 (1 << 2) +#define WM8510_BCLKDIV_4 (2 << 2) +#define WM8510_BCLKDIV_8 (3 << 2) +#define WM8510_BCLKDIV_16 (4 << 2) +#define WM8510_BCLKDIV_32 (5 << 2) + +/* MCLK clock dividers */ +#define WM8510_MCLKDIV_1 (0 << 5) +#define WM8510_MCLKDIV_1_5 (1 << 5) +#define WM8510_MCLKDIV_2 (2 << 5) +#define WM8510_MCLKDIV_3 (3 << 5) +#define WM8510_MCLKDIV_4 (4 << 5) +#define WM8510_MCLKDIV_6 (5 << 5) +#define WM8510_MCLKDIV_8 (6 << 5) +#define WM8510_MCLKDIV_12 (7 << 5) + +struct wm8510_setup_data { + int spi; + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8510_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8510; + +#endif diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c new file mode 100644 index 0000000..627ebfb --- /dev/null +++ b/sound/soc/codecs/wm8580.c @@ -0,0 +1,1053 @@ +/* + * wm8580.c -- WM8580 ALSA Soc Audio driver + * + * Copyright 2008 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 as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Notes: + * The WM8580 is a multichannel codec with S/PDIF support, featuring six + * DAC channels and two ADC channels. + * + * Currently only the primary audio interface is supported - S/PDIF and + * the secondary audio interfaces are not. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include <asm/div64.h> + +#include "wm8580.h" + +#define WM8580_VERSION "0.1" + +struct pll_state { + unsigned int in; + unsigned int out; +}; + +/* codec private data */ +struct wm8580_priv { + struct pll_state a; + struct pll_state b; +}; + +/* WM8580 register space */ +#define WM8580_PLLA1 0x00 +#define WM8580_PLLA2 0x01 +#define WM8580_PLLA3 0x02 +#define WM8580_PLLA4 0x03 +#define WM8580_PLLB1 0x04 +#define WM8580_PLLB2 0x05 +#define WM8580_PLLB3 0x06 +#define WM8580_PLLB4 0x07 +#define WM8580_CLKSEL 0x08 +#define WM8580_PAIF1 0x09 +#define WM8580_PAIF2 0x0A +#define WM8580_SAIF1 0x0B +#define WM8580_PAIF3 0x0C +#define WM8580_PAIF4 0x0D +#define WM8580_SAIF2 0x0E +#define WM8580_DAC_CONTROL1 0x0F +#define WM8580_DAC_CONTROL2 0x10 +#define WM8580_DAC_CONTROL3 0x11 +#define WM8580_DAC_CONTROL4 0x12 +#define WM8580_DAC_CONTROL5 0x13 +#define WM8580_DIGITAL_ATTENUATION_DACL1 0x14 +#define WM8580_DIGITAL_ATTENUATION_DACR1 0x15 +#define WM8580_DIGITAL_ATTENUATION_DACL2 0x16 +#define WM8580_DIGITAL_ATTENUATION_DACR2 0x17 +#define WM8580_DIGITAL_ATTENUATION_DACL3 0x18 +#define WM8580_DIGITAL_ATTENUATION_DACR3 0x19 +#define WM8580_MASTER_DIGITAL_ATTENUATION 0x1C +#define WM8580_ADC_CONTROL1 0x1D +#define WM8580_SPDTXCHAN0 0x1E +#define WM8580_SPDTXCHAN1 0x1F +#define WM8580_SPDTXCHAN2 0x20 +#define WM8580_SPDTXCHAN3 0x21 +#define WM8580_SPDTXCHAN4 0x22 +#define WM8580_SPDTXCHAN5 0x23 +#define WM8580_SPDMODE 0x24 +#define WM8580_INTMASK 0x25 +#define WM8580_GPO1 0x26 +#define WM8580_GPO2 0x27 +#define WM8580_GPO3 0x28 +#define WM8580_GPO4 0x29 +#define WM8580_GPO5 0x2A +#define WM8580_INTSTAT 0x2B +#define WM8580_SPDRXCHAN1 0x2C +#define WM8580_SPDRXCHAN2 0x2D +#define WM8580_SPDRXCHAN3 0x2E +#define WM8580_SPDRXCHAN4 0x2F +#define WM8580_SPDRXCHAN5 0x30 +#define WM8580_SPDSTAT 0x31 +#define WM8580_PWRDN1 0x32 +#define WM8580_PWRDN2 0x33 +#define WM8580_READBACK 0x34 +#define WM8580_RESET 0x35 + +/* PLLB4 (register 7h) */ +#define WM8580_PLLB4_MCLKOUTSRC_MASK 0x60 +#define WM8580_PLLB4_MCLKOUTSRC_PLLA 0x20 +#define WM8580_PLLB4_MCLKOUTSRC_PLLB 0x40 +#define WM8580_PLLB4_MCLKOUTSRC_OSC 0x60 + +#define WM8580_PLLB4_CLKOUTSRC_MASK 0x180 +#define WM8580_PLLB4_CLKOUTSRC_PLLACLK 0x080 +#define WM8580_PLLB4_CLKOUTSRC_PLLBCLK 0x100 +#define WM8580_PLLB4_CLKOUTSRC_OSCCLK 0x180 + +/* CLKSEL (register 8h) */ +#define WM8580_CLKSEL_DAC_CLKSEL_MASK 0x03 +#define WM8580_CLKSEL_DAC_CLKSEL_PLLA 0x01 +#define WM8580_CLKSEL_DAC_CLKSEL_PLLB 0x02 + +/* AIF control 1 (registers 9h-bh) */ +#define WM8580_AIF_RATE_MASK 0x7 +#define WM8580_AIF_RATE_128 0x0 +#define WM8580_AIF_RATE_192 0x1 +#define WM8580_AIF_RATE_256 0x2 +#define WM8580_AIF_RATE_384 0x3 +#define WM8580_AIF_RATE_512 0x4 +#define WM8580_AIF_RATE_768 0x5 +#define WM8580_AIF_RATE_1152 0x6 + +#define WM8580_AIF_BCLKSEL_MASK 0x18 +#define WM8580_AIF_BCLKSEL_64 0x00 +#define WM8580_AIF_BCLKSEL_128 0x08 +#define WM8580_AIF_BCLKSEL_256 0x10 +#define WM8580_AIF_BCLKSEL_SYSCLK 0x18 + +#define WM8580_AIF_MS 0x20 + +#define WM8580_AIF_CLKSRC_MASK 0xc0 +#define WM8580_AIF_CLKSRC_PLLA 0x40 +#define WM8580_AIF_CLKSRC_PLLB 0x40 +#define WM8580_AIF_CLKSRC_MCLK 0xc0 + +/* AIF control 2 (registers ch-eh) */ +#define WM8580_AIF_FMT_MASK 0x03 +#define WM8580_AIF_FMT_RIGHTJ 0x00 +#define WM8580_AIF_FMT_LEFTJ 0x01 +#define WM8580_AIF_FMT_I2S 0x02 +#define WM8580_AIF_FMT_DSP 0x03 + +#define WM8580_AIF_LENGTH_MASK 0x0c +#define WM8580_AIF_LENGTH_16 0x00 +#define WM8580_AIF_LENGTH_20 0x04 +#define WM8580_AIF_LENGTH_24 0x08 +#define WM8580_AIF_LENGTH_32 0x0c + +#define WM8580_AIF_LRP 0x10 +#define WM8580_AIF_BCP 0x20 + +/* Powerdown Register 1 (register 32h) */ +#define WM8580_PWRDN1_PWDN 0x001 +#define WM8580_PWRDN1_ALLDACPD 0x040 + +/* Powerdown Register 2 (register 33h) */ +#define WM8580_PWRDN2_OSSCPD 0x001 +#define WM8580_PWRDN2_PLLAPD 0x002 +#define WM8580_PWRDN2_PLLBPD 0x004 +#define WM8580_PWRDN2_SPDIFPD 0x008 +#define WM8580_PWRDN2_SPDIFTXD 0x010 +#define WM8580_PWRDN2_SPDIFRXD 0x020 + +#define WM8580_DAC_CONTROL5_MUTEALL 0x10 + +/* + * wm8580 register cache + * We can't read the WM8580 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8580_reg[] = { + 0x0121, 0x017e, 0x007d, 0x0014, /*R3*/ + 0x0121, 0x017e, 0x007d, 0x0194, /*R7*/ + 0x001c, 0x0002, 0x0002, 0x00c2, /*R11*/ + 0x0182, 0x0082, 0x000a, 0x0024, /*R15*/ + 0x0009, 0x0000, 0x00ff, 0x0000, /*R19*/ + 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R23*/ + 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R27*/ + 0x01f0, 0x0040, 0x0000, 0x0000, /*R31(0x1F)*/ + 0x0000, 0x0000, 0x0031, 0x000b, /*R35*/ + 0x0039, 0x0000, 0x0010, 0x0032, /*R39*/ + 0x0054, 0x0076, 0x0098, 0x0000, /*R43(0x2B)*/ + 0x0000, 0x0000, 0x0000, 0x0000, /*R47*/ + 0x0000, 0x0000, 0x005e, 0x003e, /*R51(0x33)*/ + 0x0000, 0x0000 /*R53*/ +}; + +/* + * read wm8580 register cache + */ +static inline unsigned int wm8580_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > ARRAY_SIZE(wm8580_reg)); + return cache[reg]; +} + +/* + * write wm8580 register cache + */ +static inline void wm8580_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + cache[reg] = value; +} + +/* + * write to the WM8580 register space + */ +static int wm8580_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + BUG_ON(reg > ARRAY_SIZE(wm8580_reg)); + + /* Registers are 9 bits wide */ + value &= 0x1ff; + + switch (reg) { + case WM8580_RESET: + /* Uncached */ + break; + default: + if (value == wm8580_read_reg_cache(codec, reg)) + return 0; + } + + /* data is + * D15..D9 WM8580 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8580_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static inline unsigned int wm8580_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + default: + return wm8580_read_reg_cache(codec, reg); + } +} + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); + +static int wm8580_out_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int reg2 = (kcontrol->private_value >> 24) & 0xff; + int ret; + u16 val; + + /* Clear the register cache so we write without VU set */ + wm8580_write_reg_cache(codec, reg, 0); + wm8580_write_reg_cache(codec, reg2, 0); + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* Now write again with the volume update bit set */ + val = wm8580_read_reg_cache(codec, reg); + wm8580_write(codec, reg, val | 0x0100); + + val = wm8580_read_reg_cache(codec, reg2); + wm8580_write(codec, reg2, val | 0x0100); + + return 0; +} + +#define SOC_WM8580_OUT_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r, \ + .get = snd_soc_get_volsw_2r, .put = wm8580_out_vu, \ + .private_value = (reg_left) | ((shift) << 8) | \ + ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) } + +static const struct snd_kcontrol_new wm8580_snd_controls[] = { +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC1 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL1, + WM8580_DIGITAL_ATTENUATION_DACR1, + 0, 0xff, 0, dac_tlv), +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC2 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL2, + WM8580_DIGITAL_ATTENUATION_DACR2, + 0, 0xff, 0, dac_tlv), +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL3, + WM8580_DIGITAL_ATTENUATION_DACR3, + 0, 0xff, 0, dac_tlv), + +SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0), +SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0), +SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0), + +SOC_DOUBLE("DAC1 Invert Switch", WM8580_DAC_CONTROL4, 0, 1, 1, 0), +SOC_DOUBLE("DAC2 Invert Switch", WM8580_DAC_CONTROL4, 2, 3, 1, 0), +SOC_DOUBLE("DAC3 Invert Switch", WM8580_DAC_CONTROL4, 4, 5, 1, 0), + +SOC_SINGLE("DAC ZC Switch", WM8580_DAC_CONTROL5, 5, 1, 0), +SOC_SINGLE("DAC1 Switch", WM8580_DAC_CONTROL5, 0, 1, 0), +SOC_SINGLE("DAC2 Switch", WM8580_DAC_CONTROL5, 1, 1, 0), +SOC_SINGLE("DAC3 Switch", WM8580_DAC_CONTROL5, 2, 1, 0), + +SOC_DOUBLE("ADC Mute Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 0), +SOC_SINGLE("ADC High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0), +}; + +/* Add non-DAPM controls */ +static int wm8580_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8580_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8580_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} +static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1), +SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1), +SND_SOC_DAPM_DAC("DAC3", "Playback", WM8580_PWRDN1, 4, 1), + +SND_SOC_DAPM_OUTPUT("VOUT1L"), +SND_SOC_DAPM_OUTPUT("VOUT1R"), +SND_SOC_DAPM_OUTPUT("VOUT2L"), +SND_SOC_DAPM_OUTPUT("VOUT2R"), +SND_SOC_DAPM_OUTPUT("VOUT3L"), +SND_SOC_DAPM_OUTPUT("VOUT3R"), + +SND_SOC_DAPM_ADC("ADC", "Capture", WM8580_PWRDN1, 1, 1), + +SND_SOC_DAPM_INPUT("AINL"), +SND_SOC_DAPM_INPUT("AINR"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + { "VOUT1L", NULL, "DAC1" }, + { "VOUT1R", NULL, "DAC1" }, + + { "VOUT2L", NULL, "DAC2" }, + { "VOUT2R", NULL, "DAC2" }, + + { "VOUT3L", NULL, "DAC3" }, + { "VOUT3R", NULL, "DAC3" }, + + { "ADC", NULL, "AINL" }, + { "ADC", NULL, "AINR" }, +}; + +static int wm8580_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets, + ARRAY_SIZE(wm8580_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 prescale:1; + u32 postscale:1; + u32 freqmode:2; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide */ +#define FIXED_PLL_SIZE (1 << 22) + +/* PLL rate to output rate divisions */ +static struct { + unsigned int div; + unsigned int freqmode; + unsigned int postscale; +} post_table[] = { + { 2, 0, 0 }, + { 4, 0, 1 }, + { 4, 1, 0 }, + { 8, 1, 1 }, + { 8, 2, 0 }, + { 16, 2, 1 }, + { 12, 3, 0 }, + { 24, 3, 1 } +}; + +static int pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + int i; + + pr_debug("wm8580: PLL %dHz->%dHz\n", source, target); + + /* Scale the output frequency up; the PLL should run in the + * region of 90-100MHz. + */ + for (i = 0; i < ARRAY_SIZE(post_table); i++) { + if (target * post_table[i].div >= 90000000 && + target * post_table[i].div <= 100000000) { + pll_div->freqmode = post_table[i].freqmode; + pll_div->postscale = post_table[i].postscale; + target *= post_table[i].div; + break; + } + } + + if (i == ARRAY_SIZE(post_table)) { + printk(KERN_ERR "wm8580: Unable to scale output frequency " + "%u\n", target); + return -EINVAL; + } + + Ndiv = target / source; + + if (Ndiv < 5) { + source /= 2; + pll_div->prescale = 1; + Ndiv = target / source; + } else + pll_div->prescale = 0; + + if ((Ndiv < 5) || (Ndiv > 13)) { + printk(KERN_ERR + "WM8580 N=%d outside supported range\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + pll_div->k = K; + + pr_debug("PLL %x.%x prescale %d freqmode %d postscale %d\n", + pll_div->n, pll_div->k, pll_div->prescale, pll_div->freqmode, + pll_div->postscale); + + return 0; +} + +static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + int offset; + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8580_priv *wm8580 = codec->private_data; + struct pll_state *state; + struct _pll_div pll_div; + unsigned int reg; + unsigned int pwr_mask; + int ret; + + /* GCC isn't able to work out the ifs below for initialising/using + * pll_div so suppress warnings. + */ + memset(&pll_div, 0, sizeof(pll_div)); + + switch (pll_id) { + case WM8580_PLLA: + state = &wm8580->a; + offset = 0; + pwr_mask = WM8580_PWRDN2_PLLAPD; + break; + case WM8580_PLLB: + state = &wm8580->b; + offset = 4; + pwr_mask = WM8580_PWRDN2_PLLBPD; + break; + default: + return -ENODEV; + } + + if (freq_in && freq_out) { + ret = pll_factors(&pll_div, freq_out, freq_in); + if (ret != 0) + return ret; + } + + state->in = freq_in; + state->out = freq_out; + + /* Always disable the PLL - it is not safe to leave it running + * while reprogramming it. + */ + reg = wm8580_read(codec, WM8580_PWRDN2); + wm8580_write(codec, WM8580_PWRDN2, reg | pwr_mask); + + if (!freq_in || !freq_out) + return 0; + + wm8580_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff); + wm8580_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0xff); + wm8580_write(codec, WM8580_PLLA3 + offset, + (pll_div.k >> 18 & 0xf) | (pll_div.n << 4)); + + reg = wm8580_read(codec, WM8580_PLLA4 + offset); + reg &= ~0x3f; + reg |= pll_div.prescale | pll_div.postscale << 1 | + pll_div.freqmode << 4; + + wm8580_write(codec, WM8580_PLLA4 + offset, reg); + + /* All done, turn it on */ + reg = wm8580_read(codec, WM8580_PWRDN2); + wm8580_write(codec, WM8580_PWRDN2, reg & ~pwr_mask); + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8580_paif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *dai = rtd->dai; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->codec_dai->id); + + paifb &= ~WM8580_AIF_LENGTH_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + paifb |= WM8580_AIF_LENGTH_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + paifb |= WM8580_AIF_LENGTH_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + paifb |= WM8580_AIF_LENGTH_24; + break; + default: + return -EINVAL; + } + + wm8580_write(codec, WM8580_PAIF3 + dai->codec_dai->id, paifb); + return 0; +} + +static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int aifa; + unsigned int aifb; + int can_invert_lrclk; + + aifa = wm8580_read(codec, WM8580_PAIF1 + codec_dai->id); + aifb = wm8580_read(codec, WM8580_PAIF3 + codec_dai->id); + + aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + aifa &= ~WM8580_AIF_MS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aifa |= WM8580_AIF_MS; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_RIGHTJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_LEFTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + can_invert_lrclk = 0; + aifb |= WM8580_AIF_FMT_DSP; + break; + case SND_SOC_DAIFMT_DSP_B: + can_invert_lrclk = 0; + aifb |= WM8580_AIF_FMT_DSP; + aifb |= WM8580_AIF_LRP; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_IB_IF: + if (!can_invert_lrclk) + return -EINVAL; + aifb |= WM8580_AIF_BCP; + aifb |= WM8580_AIF_LRP; + break; + + case SND_SOC_DAIFMT_IB_NF: + aifb |= WM8580_AIF_BCP; + break; + + case SND_SOC_DAIFMT_NB_IF: + if (!can_invert_lrclk) + return -EINVAL; + aifb |= WM8580_AIF_LRP; + break; + + default: + return -EINVAL; + } + + wm8580_write(codec, WM8580_PAIF1 + codec_dai->id, aifa); + wm8580_write(codec, WM8580_PAIF3 + codec_dai->id, aifb); + + return 0; +} + +static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + switch (div_id) { + case WM8580_MCLK: + reg = wm8580_read(codec, WM8580_PLLB4); + reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK; + + switch (div) { + case WM8580_CLKSRC_MCLK: + /* Input */ + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_PLLB4_MCLKOUTSRC_PLLA; + break; + case WM8580_CLKSRC_PLLB: + reg |= WM8580_PLLB4_MCLKOUTSRC_PLLB; + break; + + case WM8580_CLKSRC_OSC: + reg |= WM8580_PLLB4_MCLKOUTSRC_OSC; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_PLLB4, reg); + break; + + case WM8580_DAC_CLKSEL: + reg = wm8580_read(codec, WM8580_CLKSEL); + reg &= ~WM8580_CLKSEL_DAC_CLKSEL_MASK; + + switch (div) { + case WM8580_CLKSRC_MCLK: + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLA; + break; + + case WM8580_CLKSRC_PLLB: + reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLB; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_CLKSEL, reg); + break; + + case WM8580_CLKOUTSRC: + reg = wm8580_read(codec, WM8580_PLLB4); + reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK; + + switch (div) { + case WM8580_CLKSRC_NONE: + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_PLLB4_CLKOUTSRC_PLLACLK; + break; + + case WM8580_CLKSRC_PLLB: + reg |= WM8580_PLLB4_CLKOUTSRC_PLLBCLK; + break; + + case WM8580_CLKSRC_OSC: + reg |= WM8580_PLLB4_CLKOUTSRC_OSCCLK; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_PLLB4, reg); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + reg = wm8580_read(codec, WM8580_DAC_CONTROL5); + + if (mute) + reg |= WM8580_DAC_CONTROL5_MUTEALL; + else + reg &= ~WM8580_DAC_CONTROL5_MUTEALL; + + wm8580_write(codec, WM8580_DAC_CONTROL5, reg); + + return 0; +} + +static int wm8580_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + reg = wm8580_read(codec, WM8580_PWRDN1); + wm8580_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai wm8580_dai[] = { + { + .name = "WM8580 PAIFRX", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8580_FORMATS, + }, + .ops = { + .hw_params = wm8580_paif_hw_params, + }, + .dai_ops = { + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, + .digital_mute = wm8580_digital_mute, + }, + }, + { + .name = "WM8580 PAIFTX", + .id = 1, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8580_FORMATS, + }, + .ops = { + .hw_params = wm8580_paif_hw_params, + }, + .dai_ops = { + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, + }, + }, +}; +EXPORT_SYMBOL_GPL(wm8580_dai); + +/* + * initialise the WM8580 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8580_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8580"; + codec->owner = THIS_MODULE; + codec->read = wm8580_read_reg_cache; + codec->write = wm8580_write; + codec->set_bias_level = wm8580_set_bias_level; + codec->dai = wm8580_dai; + codec->num_dai = ARRAY_SIZE(wm8580_dai); + codec->reg_cache_size = ARRAY_SIZE(wm8580_reg); + codec->reg_cache = kmemdup(wm8580_reg, sizeof(wm8580_reg), + GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* Get the codec into a known state */ + wm8580_write(codec, WM8580_RESET, 0); + + /* Power up and get individual control of the DACs */ + wm8580_write(codec, WM8580_PWRDN1, wm8580_read(codec, WM8580_PWRDN1) & + ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD)); + + /* Make VMID high impedence */ + wm8580_write(codec, WM8580_ADC_CONTROL1, + wm8580_read(codec, WM8580_ADC_CONTROL1) & ~0x100); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8580: failed to create pcms\n"); + goto pcm_err; + } + + wm8580_add_controls(codec); + wm8580_add_widgets(codec); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8580: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8580_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8580 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8580_i2c_driver; +static struct i2c_client client_template; + +static int wm8580_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8580_socdev; + struct wm8580_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + dev_err(&i2c->dev, "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8580_init(socdev); + if (ret < 0) { + dev_err(&i2c->dev, "failed to initialise WM8580\n"); + goto err; + } + + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8580_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8580_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8580_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8580_i2c_driver = { + .driver = { + .name = "WM8580 I2C Codec", + .owner = THIS_MODULE, + }, + .attach_adapter = wm8580_i2c_attach, + .detach_client = wm8580_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8580", + .driver = &wm8580_i2c_driver, +}; +#endif + +static int wm8580_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8580_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8580_priv *wm8580; + int ret = 0; + + pr_info("WM8580 Audio Codec %s\n", WM8580_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL); + if (wm8580 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8580; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8580_socdev = socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8580_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8580_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8580_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8580 = { + .probe = wm8580_probe, + .remove = wm8580_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580); + +MODULE_DESCRIPTION("ASoC WM8580 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h new file mode 100644 index 0000000..589ddab --- /dev/null +++ b/sound/soc/codecs/wm8580.h @@ -0,0 +1,42 @@ +/* + * wm8580.h -- audio driver for WM8580 + * + * Copyright 2008 Samsung Electronics. + * Author: Ryu Euiyoul + * ryu.real@gmail.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8580_H +#define _WM8580_H + +#define WM8580_PLLA 1 +#define WM8580_PLLB 2 + +#define WM8580_MCLK 1 +#define WM8580_DAC_CLKSEL 2 +#define WM8580_CLKOUTSRC 3 + +#define WM8580_CLKSRC_MCLK 1 +#define WM8580_CLKSRC_PLLA 2 +#define WM8580_CLKSRC_PLLB 3 +#define WM8580_CLKSRC_OSC 4 +#define WM8580_CLKSRC_NONE 5 + +struct wm8580_setup_data { + unsigned short i2c_address; +}; + +#define WM8580_DAI_PAIFRX 0 +#define WM8580_DAI_PAIFTX 1 + +extern struct snd_soc_dai wm8580_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_wm8580; + +#endif + diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c new file mode 100644 index 0000000..7f8a7e3 --- /dev/null +++ b/sound/soc/codecs/wm8731.c @@ -0,0 +1,797 @@ +/* + * wm8731.c -- WM8731 ALSA SoC Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8731.h" + +#define WM8731_VERSION "0.13" + +struct snd_soc_codec_device soc_codec_dev_wm8731; + +/* codec private data */ +struct wm8731_priv { + unsigned int sysclk; +}; + +/* + * wm8731 register cache + * We can't read the WM8731 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u16 wm8731_reg[WM8731_CACHEREGNUM] = { + 0x0097, 0x0097, 0x0079, 0x0079, + 0x000a, 0x0008, 0x009f, 0x000a, + 0x0000, 0x0000 +}; + +/* + * read wm8731 register cache + */ +static inline unsigned int wm8731_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8731_RESET) + return 0; + if (reg >= WM8731_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8731 register cache + */ +static inline void wm8731_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8731_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8731 register space + */ +static int wm8731_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8731 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8731_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8731_reset(c) wm8731_write(c, WM8731_RESET, 0) + +static const char *wm8731_input_select[] = {"Line In", "Mic"}; +static const char *wm8731_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum wm8731_enum[] = { + SOC_ENUM_SINGLE(WM8731_APANA, 2, 2, wm8731_input_select), + SOC_ENUM_SINGLE(WM8731_APDIGI, 1, 4, wm8731_deemph), +}; + +static const struct snd_kcontrol_new wm8731_snd_controls[] = { + +SOC_DOUBLE_R("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V, + 7, 1, 0), + +SOC_DOUBLE_R("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0), +SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1), + +SOC_SINGLE("Mic Boost (+20dB)", WM8731_APANA, 0, 1, 0), +SOC_SINGLE("Capture Mic Switch", WM8731_APANA, 1, 1, 1), + +SOC_SINGLE("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1), + +SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0), + +SOC_ENUM("Playback De-emphasis", wm8731_enum[1]), +}; + +/* add non dapm controls */ +static int wm8731_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8731_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8731_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Output Mixer */ +static const struct snd_kcontrol_new wm8731_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8731_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("Mic Sidetone Switch", WM8731_APANA, 5, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", WM8731_APANA, 4, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new wm8731_input_mux_controls = +SOC_DAPM_ENUM("Input Select", wm8731_enum[0]); + +static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, + &wm8731_output_mixer_controls[0], + ARRAY_SIZE(wm8731_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8731_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8731_PWR, 2, 1), +SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &wm8731_input_mux_controls), +SND_SOC_DAPM_PGA("Line Input", WM8731_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8731_PWR, 1, 1), +SND_SOC_DAPM_INPUT("MICIN"), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* input mux */ + {"Input Mux", "Line In", "Line Input"}, + {"Input Mux", "Mic", "Mic Bias"}, + {"ADC", NULL, "Input Mux"}, + + /* inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Bias", NULL, "MICIN"}, +}; + +static int wm8731_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8731_dapm_widgets, + ARRAY_SIZE(wm8731_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return 0; +} + +static int wm8731_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8731_priv *wm8731 = codec->private_data; + u16 iface = wm8731_read_reg_cache(codec, WM8731_IFACE) & 0xfff3; + int i = get_coeff(wm8731->sysclk, params_rate(params)); + u16 srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + wm8731_write(codec, WM8731_SRATE, srate); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + } + + wm8731_write(codec, WM8731_IFACE, iface); + return 0; +} + +static int wm8731_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* set active */ + wm8731_write(codec, WM8731_ACTIVE, 0x0001); + + return 0; +} + +static void wm8731_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* deactivate */ + if (!codec->active) { + udelay(50); + wm8731_write(codec, WM8731_ACTIVE, 0x0); + } +} + +static int wm8731_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8731_read_reg_cache(codec, WM8731_APDIGI) & 0xfff7; + + if (mute) + wm8731_write(codec, WM8731_APDIGI, mute_reg | 0x8); + else + wm8731_write(codec, WM8731_APDIGI, mute_reg); + return 0; +} + +static int wm8731_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8731_priv *wm8731 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8731->sysclk = freq; + return 0; + } + return -EINVAL; +} + + +static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + 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 |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + wm8731_write(codec, WM8731_IFACE, iface); + return 0; +} + +static int wm8731_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + wm8731_write(codec, WM8731_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + wm8731_write(codec, WM8731_PWR, reg | 0x0040); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + wm8731_write(codec, WM8731_ACTIVE, 0x0); + wm8731_write(codec, WM8731_PWR, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8731_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +#define WM8731_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8731_dai = { + .name = "WM8731", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8731_RATES, + .formats = WM8731_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8731_RATES, + .formats = WM8731_FORMATS,}, + .ops = { + .prepare = wm8731_pcm_prepare, + .hw_params = wm8731_hw_params, + .shutdown = wm8731_shutdown, + }, + .dai_ops = { + .digital_mute = wm8731_mute, + .set_sysclk = wm8731_set_dai_sysclk, + .set_fmt = wm8731_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(wm8731_dai); + +static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8731_write(codec, WM8731_ACTIVE, 0x0); + wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8731_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8731_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8731_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the WM8731 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8731_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8731"; + codec->owner = THIS_MODULE; + codec->read = wm8731_read_reg_cache; + codec->write = wm8731_write; + codec->set_bias_level = wm8731_set_bias_level; + codec->dai = &wm8731_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8731_reg); + codec->reg_cache = kmemdup(wm8731_reg, sizeof(wm8731_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8731_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8731: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* set the update bits */ + reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V); + wm8731_write(codec, WM8731_LOUT1V, reg & ~0x0100); + reg = wm8731_read_reg_cache(codec, WM8731_ROUT1V); + wm8731_write(codec, WM8731_ROUT1V, reg & ~0x0100); + reg = wm8731_read_reg_cache(codec, WM8731_LINVOL); + wm8731_write(codec, WM8731_LINVOL, reg & ~0x0100); + reg = wm8731_read_reg_cache(codec, WM8731_RINVOL); + wm8731_write(codec, WM8731_RINVOL, reg & ~0x0100); + + wm8731_add_controls(codec); + wm8731_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8731: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8731_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8731 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ + +static int wm8731_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8731_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8731_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8731\n"); + + return ret; +} + +static int wm8731_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8731_i2c_id[] = { + { "wm8731", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); + +static struct i2c_driver wm8731_i2c_driver = { + .driver = { + .name = "WM8731 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8731_i2c_probe, + .remove = wm8731_i2c_remove, + .id_table = wm8731_i2c_id, +}; + +static int wm8731_add_i2c_device(struct platform_device *pdev, + const struct wm8731_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8731_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8731", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8731_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8731_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8731_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8731_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8731\n"); + + return ret; +} + +static int __devexit wm8731_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8731_spi_driver = { + .driver = { + .name = "wm8731", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8731_spi_probe, + .remove = __devexit_p(wm8731_spi_remove), +}; + +static int wm8731_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif /* CONFIG_SPI_MASTER */ + +static int wm8731_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8731_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8731_priv *wm8731; + int ret = 0; + + pr_info("WM8731 Audio Codec %s", WM8731_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8731 = kzalloc(sizeof(struct wm8731_priv), GFP_KERNEL); + if (wm8731 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8731; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8731_socdev = socdev; + ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8731_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8731_spi_write; + ret = spi_register_driver(&wm8731_spi_driver); + if (ret != 0) + printk(KERN_ERR "can't add spi driver"); + } +#endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } + return ret; +} + +/* power down chip */ +static int wm8731_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8731_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8731_spi_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8731 = { + .probe = wm8731_probe, + .remove = wm8731_remove, + .suspend = wm8731_suspend, + .resume = wm8731_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8731); + +MODULE_DESCRIPTION("ASoC WM8731 driver"); +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h new file mode 100644 index 0000000..95190e9 --- /dev/null +++ b/sound/soc/codecs/wm8731.h @@ -0,0 +1,46 @@ +/* + * wm8731.h -- WM8731 Soc Audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on wm8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8731_H +#define _WM8731_H + +/* WM8731 register space */ + +#define WM8731_LINVOL 0x00 +#define WM8731_RINVOL 0x01 +#define WM8731_LOUT1V 0x02 +#define WM8731_ROUT1V 0x03 +#define WM8731_APANA 0x04 +#define WM8731_APDIGI 0x05 +#define WM8731_PWR 0x06 +#define WM8731_IFACE 0x07 +#define WM8731_SRATE 0x08 +#define WM8731_ACTIVE 0x09 +#define WM8731_RESET 0x0f + +#define WM8731_CACHEREGNUM 10 + +#define WM8731_SYSCLK 0 +#define WM8731_DAI 0 + +struct wm8731_setup_data { + int spi; + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8731_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8731; + +#endif diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c new file mode 100644 index 0000000..9b7296e --- /dev/null +++ b/sound/soc/codecs/wm8750.c @@ -0,0 +1,1091 @@ +/* + * wm8750.c -- WM8750 ALSA SoC audio driver + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on WM8753.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8750.h" + +#define WM8750_VERSION "0.12" + +/* codec private data */ +struct wm8750_priv { + unsigned int sysclk; +}; + +/* + * wm8750 register cache + * We can't read the WM8750 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8750_reg[] = { + 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */ + 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */ + 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */ + 0x0079, 0x0079, 0x0079, /* 40 */ +}; + +/* + * read wm8750 register cache + */ +static inline unsigned int wm8750_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg > WM8750_CACHE_REGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8750 register cache + */ +static inline void wm8750_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg > WM8750_CACHE_REGNUM) + return; + cache[reg] = value; +} + +static int wm8750_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8750_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8750_reset(c) wm8750_write(c, WM8750_RESET, 0) + +/* + * WM8750 Controls + */ +static const char *wm8750_bass[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm8750_bass_filter[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" }; +static const char *wm8750_treble[] = {"8kHz", "4kHz"}; +static const char *wm8750_3d_lc[] = {"200Hz", "500Hz"}; +static const char *wm8750_3d_uc[] = {"2.2kHz", "1.5kHz"}; +static const char *wm8750_3d_func[] = {"Capture", "Playback"}; +static const char *wm8750_alc_func[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8750_ng_type[] = {"Constant PGA Gain", + "Mute ADC Output"}; +static const char *wm8750_line_mux[] = {"Line 1", "Line 2", "Line 3", "PGA", + "Differential"}; +static const char *wm8750_pga_sel[] = {"Line 1", "Line 2", "Line 3", + "Differential"}; +static const char *wm8750_out3[] = {"VREF", "ROUT1 + Vol", "MonoOut", + "ROUT1"}; +static const char *wm8750_diff_sel[] = {"Line 1", "Line 2"}; +static const char *wm8750_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static const char *wm8750_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *wm8750_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; + +static const struct soc_enum wm8750_enum[] = { +SOC_ENUM_SINGLE(WM8750_BASS, 7, 2, wm8750_bass), +SOC_ENUM_SINGLE(WM8750_BASS, 6, 2, wm8750_bass_filter), +SOC_ENUM_SINGLE(WM8750_TREBLE, 6, 2, wm8750_treble), +SOC_ENUM_SINGLE(WM8750_3D, 5, 2, wm8750_3d_lc), +SOC_ENUM_SINGLE(WM8750_3D, 6, 2, wm8750_3d_uc), +SOC_ENUM_SINGLE(WM8750_3D, 7, 2, wm8750_3d_func), +SOC_ENUM_SINGLE(WM8750_ALC1, 7, 4, wm8750_alc_func), +SOC_ENUM_SINGLE(WM8750_NGATE, 1, 2, wm8750_ng_type), +SOC_ENUM_SINGLE(WM8750_LOUTM1, 0, 5, wm8750_line_mux), +SOC_ENUM_SINGLE(WM8750_ROUTM1, 0, 5, wm8750_line_mux), +SOC_ENUM_SINGLE(WM8750_LADCIN, 6, 4, wm8750_pga_sel), /* 10 */ +SOC_ENUM_SINGLE(WM8750_RADCIN, 6, 4, wm8750_pga_sel), +SOC_ENUM_SINGLE(WM8750_ADCTL2, 7, 4, wm8750_out3), +SOC_ENUM_SINGLE(WM8750_ADCIN, 8, 2, wm8750_diff_sel), +SOC_ENUM_SINGLE(WM8750_ADCDAC, 5, 4, wm8750_adcpol), +SOC_ENUM_SINGLE(WM8750_ADCDAC, 1, 4, wm8750_deemph), +SOC_ENUM_SINGLE(WM8750_ADCIN, 6, 4, wm8750_mono_mux), /* 16 */ + +}; + +static const struct snd_kcontrol_new wm8750_snd_controls[] = { + +SOC_DOUBLE_R("Capture Volume", WM8750_LINVOL, WM8750_RINVOL, 0, 63, 0), +SOC_DOUBLE_R("Capture ZC Switch", WM8750_LINVOL, WM8750_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8750_LINVOL, WM8750_RINVOL, 7, 1, 1), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8750_LOUT1V, + WM8750_ROUT1V, 7, 1, 0), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8750_LOUT2V, + WM8750_ROUT2V, 7, 1, 0), + +SOC_ENUM("Playback De-emphasis", wm8750_enum[15]), + +SOC_ENUM("Capture Polarity", wm8750_enum[14]), +SOC_SINGLE("Playback 6dB Attenuate", WM8750_ADCDAC, 7, 1, 0), +SOC_SINGLE("Capture 6dB Attenuate", WM8750_ADCDAC, 8, 1, 0), + +SOC_DOUBLE_R("PCM Volume", WM8750_LDAC, WM8750_RDAC, 0, 255, 0), + +SOC_ENUM("Bass Boost", wm8750_enum[0]), +SOC_ENUM("Bass Filter", wm8750_enum[1]), +SOC_SINGLE("Bass Volume", WM8750_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8750_TREBLE, 0, 15, 1), +SOC_ENUM("Treble Cut-off", wm8750_enum[2]), + +SOC_SINGLE("3D Switch", WM8750_3D, 0, 1, 0), +SOC_SINGLE("3D Volume", WM8750_3D, 1, 15, 0), +SOC_ENUM("3D Lower Cut-off", wm8750_enum[3]), +SOC_ENUM("3D Upper Cut-off", wm8750_enum[4]), +SOC_ENUM("3D Mode", wm8750_enum[5]), + +SOC_SINGLE("ALC Capture Target Volume", WM8750_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8750_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", wm8750_enum[6]), +SOC_SINGLE("ALC Capture ZC Switch", WM8750_ALC2, 7, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8750_ALC2, 0, 15, 0), +SOC_SINGLE("ALC Capture Decay Time", WM8750_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack Time", WM8750_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8750_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", wm8750_enum[4]), +SOC_SINGLE("ALC Capture NG Switch", WM8750_NGATE, 0, 1, 0), + +SOC_SINGLE("Left ADC Capture Volume", WM8750_LADC, 0, 255, 0), +SOC_SINGLE("Right ADC Capture Volume", WM8750_RADC, 0, 255, 0), + +SOC_SINGLE("ZC Timeout Switch", WM8750_ADCTL1, 0, 1, 0), +SOC_SINGLE("Playback Invert Switch", WM8750_ADCTL1, 1, 1, 0), + +SOC_SINGLE("Right Speaker Playback Invert Switch", WM8750_ADCTL2, 4, 1, 0), + +/* Unimplemented */ +/* ADCDAC Bit 0 - ADCHPD */ +/* ADCDAC Bit 4 - HPOR */ +/* ADCTL1 Bit 2,3 - DATSEL */ +/* ADCTL1 Bit 4,5 - DMONOMIX */ +/* ADCTL1 Bit 6,7 - VSEL */ +/* ADCTL2 Bit 2 - LRCM */ +/* ADCTL2 Bit 3 - TRI */ +/* ADCTL3 Bit 5 - HPFLREN */ +/* ADCTL3 Bit 6 - VROI */ +/* ADCTL3 Bit 7,8 - ADCLRM */ +/* ADCIN Bit 4 - LDCM */ +/* ADCIN Bit 5 - RDCM */ + +SOC_DOUBLE_R("Mic Boost", WM8750_LADCIN, WM8750_RADCIN, 4, 3, 0), + +SOC_DOUBLE_R("Bypass Left Playback Volume", WM8750_LOUTM1, + WM8750_LOUTM2, 4, 7, 1), +SOC_DOUBLE_R("Bypass Right Playback Volume", WM8750_ROUTM1, + WM8750_ROUTM2, 4, 7, 1), +SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8750_MOUTM1, + WM8750_MOUTM2, 4, 7, 1), + +SOC_SINGLE("Mono Playback ZC Switch", WM8750_MOUTV, 7, 1, 0), + +SOC_DOUBLE_R("Headphone Playback Volume", WM8750_LOUT1V, WM8750_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Speaker Playback Volume", WM8750_LOUT2V, WM8750_ROUT2V, + 0, 127, 0), + +SOC_SINGLE("Mono Playback Volume", WM8750_MOUTV, 0, 127, 0), + +}; + +/* add non dapm controls */ +static int wm8750_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8750_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8750_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8750_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8750_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8750_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8750_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8750_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8750_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm8750_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8750_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8750_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8750_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8750_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new wm8750_left_line_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new wm8750_right_line_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new wm8750_left_pga_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new wm8750_right_pga_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[11]); + +/* Out 3 Mux */ +static const struct snd_kcontrol_new wm8750_out3_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[12]); + +/* Differential Mux */ +static const struct snd_kcontrol_new wm8750_diffmux_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[13]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new wm8750_monomux_controls = +SOC_DAPM_ENUM("Route", wm8750_enum[16]); + +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8750_left_mixer_controls[0], + ARRAY_SIZE(wm8750_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8750_right_mixer_controls[0], + ARRAY_SIZE(wm8750_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8750_PWR2, 2, 0, + &wm8750_mono_mixer_controls[0], + ARRAY_SIZE(wm8750_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8750_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8750_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8750_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8750_PWR2, 6, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8750_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8750_PWR2, 8, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8750_PWR1, 1, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8750_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8750_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8750_PWR1, 5, 0, + &wm8750_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8750_PWR1, 4, 0, + &wm8750_right_pga_controls), + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8750_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8750_right_line_controls), + + SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8750_out3_controls), + SND_SOC_DAPM_PGA("Out 3", WM8750_PWR2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out 1", WM8750_PWR2, 2, 0, NULL, 0), + + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &wm8750_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8750_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8750_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO1"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("VREF"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("LINPUT3"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT3"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out 1", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out 1"}, + + /* out 3 */ + {"Out3 Mux", "VREF", "VREF"}, + {"Out3 Mux", "ROUT1 + Vol", "ROUT1"}, + {"Out3 Mux", "ROUT1", "Right Mixer"}, + {"Out3 Mux", "MonoOut", "MONO1"}, + {"Out 3", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line 1", "LINPUT1"}, + {"Left Line Mux", "Line 2", "LINPUT2"}, + {"Left Line Mux", "Line 3", "LINPUT3"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line 1", "RINPUT1"}, + {"Right Line Mux", "Line 2", "RINPUT2"}, + {"Right Line Mux", "Line 3", "RINPUT3"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line 1", "LINPUT1"}, + {"Left PGA Mux", "Line 2", "LINPUT2"}, + {"Left PGA Mux", "Line 3", "LINPUT3"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line 1", "RINPUT1"}, + {"Right PGA Mux", "Line 2", "RINPUT2"}, + {"Right PGA Mux", "Line 3", "RINPUT3"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line 1", "LINPUT1"}, + {"Differential Mux", "Line 1", "RINPUT1"}, + {"Differential Mux", "Line 2", "LINPUT2"}, + {"Differential Mux", "Line 2", "RINPUT2"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, +}; + +static int wm8750_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets, + ARRAY_SIZE(wm8750_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + + printk(KERN_ERR "wm8750: could not get coeff for mclk %d @ rate %d\n", + mclk, rate); + return -EINVAL; +} + +static int wm8750_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8750_priv *wm8750 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8750->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + 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 |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + wm8750_write(codec, WM8750_IFACE, iface); + return 0; +} + +static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8750_priv *wm8750 = codec->private_data; + u16 iface = wm8750_read_reg_cache(codec, WM8750_IFACE) & 0x1f3; + u16 srate = wm8750_read_reg_cache(codec, WM8750_SRATE) & 0x1c0; + int coeff = get_coeff(wm8750->sysclk, params_rate(params)); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + wm8750_write(codec, WM8750_IFACE, iface); + if (coeff >= 0) + wm8750_write(codec, WM8750_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8750_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8750_read_reg_cache(codec, WM8750_ADCDAC) & 0xfff7; + + if (mute) + wm8750_write(codec, WM8750_ADCDAC, mute_reg | 0x8); + else + wm8750_write(codec, WM8750_ADCDAC, mute_reg); + return 0; +} + +static int wm8750_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 pwr_reg = wm8750_read_reg_cache(codec, WM8750_PWR1) & 0xfe3e; + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x00c0); + break; + case SND_SOC_BIAS_PREPARE: + /* set vmid to 5k for quick power up */ + wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); + break; + case SND_SOC_BIAS_STANDBY: + /* mute dac and set vmid to 500k, enable VREF */ + wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x0141); + break; + case SND_SOC_BIAS_OFF: + wm8750_write(codec, WM8750_PWR1, 0x0001); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8750_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8750_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8750_dai = { + .name = "WM8750", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8750_RATES, + .formats = WM8750_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8750_RATES, + .formats = WM8750_FORMATS,}, + .ops = { + .hw_params = wm8750_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8750_mute, + .set_fmt = wm8750_set_dai_fmt, + .set_sysclk = wm8750_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(wm8750_dai); + +static void wm8750_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + wm8750_set_bias_level(codec, codec->bias_level); +} + +static int wm8750_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8750_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8750_reg); i++) { + if (i == WM8750_RESET) + continue; + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8750_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* charge wm8750 caps */ + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_ON; + schedule_delayed_work(&codec->delayed_work, + msecs_to_jiffies(1000)); + } + + return 0; +} + +/* + * initialise the WM8750 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8750_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8750"; + codec->owner = THIS_MODULE; + codec->read = wm8750_read_reg_cache; + codec->write = wm8750_write; + codec->set_bias_level = wm8750_set_bias_level; + codec->dai = &wm8750_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8750_reg); + codec->reg_cache = kmemdup(wm8750_reg, sizeof(wm8750_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8750_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8750: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps */ + wm8750_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_STANDBY; + schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); + + /* set the update bits */ + reg = wm8750_read_reg_cache(codec, WM8750_LDAC); + wm8750_write(codec, WM8750_LDAC, reg | 0x0100); + reg = wm8750_read_reg_cache(codec, WM8750_RDAC); + wm8750_write(codec, WM8750_RDAC, reg | 0x0100); + reg = wm8750_read_reg_cache(codec, WM8750_LOUT1V); + wm8750_write(codec, WM8750_LOUT1V, reg | 0x0100); + reg = wm8750_read_reg_cache(codec, WM8750_ROUT1V); + wm8750_write(codec, WM8750_ROUT1V, reg | 0x0100); + reg = wm8750_read_reg_cache(codec, WM8750_LOUT2V); + wm8750_write(codec, WM8750_LOUT2V, reg | 0x0100); + reg = wm8750_read_reg_cache(codec, WM8750_ROUT2V); + wm8750_write(codec, WM8750_ROUT2V, reg | 0x0100); + reg = wm8750_read_reg_cache(codec, WM8750_LINVOL); + wm8750_write(codec, WM8750_LINVOL, reg | 0x0100); + reg = wm8750_read_reg_cache(codec, WM8750_RINVOL); + wm8750_write(codec, WM8750_RINVOL, reg | 0x0100); + + wm8750_add_controls(codec); + wm8750_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8750: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8750_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8750 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ + +static int wm8750_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8750_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8750_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8750\n"); + + return ret; +} + +static int wm8750_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8750_i2c_id[] = { + { "wm8750", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id); + +static struct i2c_driver wm8750_i2c_driver = { + .driver = { + .name = "WM8750 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8750_i2c_probe, + .remove = wm8750_i2c_remove, + .id_table = wm8750_i2c_id, +}; + +static int wm8750_add_i2c_device(struct platform_device *pdev, + const struct wm8750_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8750_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8750", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8750_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8750_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8750_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8750_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8750\n"); + + return ret; +} + +static int __devexit wm8750_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8750_spi_driver = { + .driver = { + .name = "wm8750", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8750_spi_probe, + .remove = __devexit_p(wm8750_spi_remove), +}; + +static int wm8750_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif + +static int wm8750_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8750_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec; + struct wm8750_priv *wm8750; + int ret; + + pr_info("WM8750 Audio Codec %s", WM8750_VERSION); + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8750 = kzalloc(sizeof(struct wm8750_priv), GFP_KERNEL); + if (wm8750 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8750; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8750_socdev = socdev; + INIT_DELAYED_WORK(&codec->delayed_work, wm8750_work); + + ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8750_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8750_spi_write; + ret = spi_register_driver(&wm8750_spi_driver); + if (ret != 0) + printk(KERN_ERR "can't add spi driver"); + } +#endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } + return ret; +} + +/* + * This function forces any delayed work to be queued and run. + */ +static int run_delayed_work(struct delayed_work *dwork) +{ + int ret; + + /* cancel any work waiting to be queued. */ + ret = cancel_delayed_work(dwork); + + /* if there was any work waiting then we run it now and + * wait for it's completion */ + if (ret) { + schedule_delayed_work(dwork, 0); + flush_scheduled_work(); + } + return ret; +} + +/* power down chip */ +static int wm8750_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8750_set_bias_level(codec, SND_SOC_BIAS_OFF); + run_delayed_work(&codec->delayed_work); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8750_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8750_spi_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8750 = { + .probe = wm8750_probe, + .remove = wm8750_remove, + .suspend = wm8750_suspend, + .resume = wm8750_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750); + +MODULE_DESCRIPTION("ASoC WM8750 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8750.h b/sound/soc/codecs/wm8750.h new file mode 100644 index 0000000..1dc100e --- /dev/null +++ b/sound/soc/codecs/wm8750.h @@ -0,0 +1,69 @@ +/* + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <richard@openedhand.com> + * + * Based on WM8753.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _WM8750_H +#define _WM8750_H + +/* WM8750 register space */ + +#define WM8750_LINVOL 0x00 +#define WM8750_RINVOL 0x01 +#define WM8750_LOUT1V 0x02 +#define WM8750_ROUT1V 0x03 +#define WM8750_ADCDAC 0x05 +#define WM8750_IFACE 0x07 +#define WM8750_SRATE 0x08 +#define WM8750_LDAC 0x0a +#define WM8750_RDAC 0x0b +#define WM8750_BASS 0x0c +#define WM8750_TREBLE 0x0d +#define WM8750_RESET 0x0f +#define WM8750_3D 0x10 +#define WM8750_ALC1 0x11 +#define WM8750_ALC2 0x12 +#define WM8750_ALC3 0x13 +#define WM8750_NGATE 0x14 +#define WM8750_LADC 0x15 +#define WM8750_RADC 0x16 +#define WM8750_ADCTL1 0x17 +#define WM8750_ADCTL2 0x18 +#define WM8750_PWR1 0x19 +#define WM8750_PWR2 0x1a +#define WM8750_ADCTL3 0x1b +#define WM8750_ADCIN 0x1f +#define WM8750_LADCIN 0x20 +#define WM8750_RADCIN 0x21 +#define WM8750_LOUTM1 0x22 +#define WM8750_LOUTM2 0x23 +#define WM8750_ROUTM1 0x24 +#define WM8750_ROUTM2 0x25 +#define WM8750_MOUTM1 0x26 +#define WM8750_MOUTM2 0x27 +#define WM8750_LOUT2V 0x28 +#define WM8750_ROUT2V 0x29 +#define WM8750_MOUTV 0x2a + +#define WM8750_CACHE_REGNUM 0x2a + +#define WM8750_SYSCLK 0 + +struct wm8750_setup_data { + int spi; + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8750_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8750; + +#endif diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c new file mode 100644 index 0000000..d426eaa --- /dev/null +++ b/sound/soc/codecs/wm8753.c @@ -0,0 +1,1882 @@ +/* + * wm8753.c -- WM8753 ALSA Soc Audio driver + * + * Copyright 2003 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * 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. + * + * Notes: + * The WM8753 is a low power, high quality stereo codec with integrated PCM + * codec designed for portable digital telephony applications. + * + * Dual DAI:- + * + * This driver support 2 DAI PCM's. This makes the default PCM available for + * HiFi audio (e.g. MP3, ogg) playback/capture and the other PCM available for + * voice. + * + * Please note that the voice PCM can be connected directly to a Bluetooth + * codec or GSM modem and thus cannot be read or written to, although it is + * available to be configured with snd_hw_params(), etc and kcontrols in the + * normal alsa manner. + * + * Fast DAI switching:- + * + * The driver can now fast switch between the DAI configurations via a + * an alsa kcontrol. This allows the PCM to remain open. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <asm/div64.h> + +#include "wm8753.h" + +#define WM8753_VERSION "0.16" + +static int caps_charge = 2000; +module_param(caps_charge, int, 0); +MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); + +static void wm8753_set_dai_mode(struct snd_soc_codec *codec, + unsigned int mode); + +/* codec private data */ +struct wm8753_priv { + unsigned int sysclk; + unsigned int pcmclk; +}; + +/* + * wm8753 register cache + * We can't read the WM8753 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8753_reg[] = { + 0x0008, 0x0000, 0x000a, 0x000a, + 0x0033, 0x0000, 0x0007, 0x00ff, + 0x00ff, 0x000f, 0x000f, 0x007b, + 0x0000, 0x0032, 0x0000, 0x00c3, + 0x00c3, 0x00c0, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0055, + 0x0005, 0x0050, 0x0055, 0x0050, + 0x0055, 0x0050, 0x0055, 0x0079, + 0x0079, 0x0079, 0x0079, 0x0079, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0097, 0x0097, 0x0000, 0x0004, + 0x0000, 0x0083, 0x0024, 0x01ba, + 0x0000, 0x0083, 0x0024, 0x01ba, + 0x0000, 0x0000 +}; + +/* + * read wm8753 register cache + */ +static inline unsigned int wm8753_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg < 1 || reg > (ARRAY_SIZE(wm8753_reg) + 1)) + return -1; + return cache[reg - 1]; +} + +/* + * write wm8753 register cache + */ +static inline void wm8753_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < 1 || reg > 0x3f) + return; + cache[reg - 1] = value; +} + +/* + * write to the WM8753 register space + */ +static int wm8753_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8753_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8753_reset(c) wm8753_write(c, WM8753_RESET, 0) + +/* + * WM8753 Controls + */ +static const char *wm8753_base[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm8753_base_filter[] = + {"130Hz @ 48kHz", "200Hz @ 48kHz", "100Hz @ 16kHz", "400Hz @ 48kHz", + "100Hz @ 8kHz", "200Hz @ 8kHz"}; +static const char *wm8753_treble[] = {"8kHz", "4kHz"}; +static const char *wm8753_alc_func[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8753_ng_type[] = {"Constant PGA Gain", "Mute ADC Output"}; +static const char *wm8753_3d_func[] = {"Capture", "Playback"}; +static const char *wm8753_3d_uc[] = {"2.2kHz", "1.5kHz"}; +static const char *wm8753_3d_lc[] = {"200Hz", "500Hz"}; +static const char *wm8753_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"}; +static const char *wm8753_mono_mix[] = {"Stereo", "Left", "Right", "Mono"}; +static const char *wm8753_dac_phase[] = {"Non Inverted", "Inverted"}; +static const char *wm8753_line_mix[] = {"Line 1 + 2", "Line 1 - 2", + "Line 1", "Line 2"}; +static const char *wm8753_mono_mux[] = {"Line Mix", "Rx Mix"}; +static const char *wm8753_right_mux[] = {"Line 2", "Rx Mix"}; +static const char *wm8753_left_mux[] = {"Line 1", "Rx Mix"}; +static const char *wm8753_rxmsel[] = {"RXP - RXN", "RXP + RXN", "RXP", "RXN"}; +static const char *wm8753_sidetone_mux[] = {"Left PGA", "Mic 1", "Mic 2", + "Right PGA"}; +static const char *wm8753_mono2_src[] = {"Inverted Mono 1", "Left", "Right", + "Left + Right"}; +static const char *wm8753_out3[] = {"VREF", "ROUT2", "Left + Right"}; +static const char *wm8753_out4[] = {"VREF", "Capture ST", "LOUT2"}; +static const char *wm8753_radcsel[] = {"PGA", "Line or RXP-RXN", "Sidetone"}; +static const char *wm8753_ladcsel[] = {"PGA", "Line or RXP-RXN", "Line"}; +static const char *wm8753_mono_adc[] = {"Stereo", "Analogue Mix Left", + "Analogue Mix Right", "Digital Mono Mix"}; +static const char *wm8753_adc_hp[] = {"3.4Hz @ 48kHz", "82Hz @ 16k", + "82Hz @ 8kHz", "170Hz @ 8kHz"}; +static const char *wm8753_adc_filter[] = {"HiFi", "Voice"}; +static const char *wm8753_mic_sel[] = {"Mic 1", "Mic 2", "Mic 3"}; +static const char *wm8753_dai_mode[] = {"DAI 0", "DAI 1", "DAI 2", "DAI 3"}; +static const char *wm8753_dat_sel[] = {"Stereo", "Left ADC", "Right ADC", + "Channel Swap"}; +static const char *wm8753_rout2_phase[] = {"Non Inverted", "Inverted"}; + +static const struct soc_enum wm8753_enum[] = { +SOC_ENUM_SINGLE(WM8753_BASS, 7, 2, wm8753_base), +SOC_ENUM_SINGLE(WM8753_BASS, 4, 6, wm8753_base_filter), +SOC_ENUM_SINGLE(WM8753_TREBLE, 6, 2, wm8753_treble), +SOC_ENUM_SINGLE(WM8753_ALC1, 7, 4, wm8753_alc_func), +SOC_ENUM_SINGLE(WM8753_NGATE, 1, 2, wm8753_ng_type), +SOC_ENUM_SINGLE(WM8753_3D, 7, 2, wm8753_3d_func), +SOC_ENUM_SINGLE(WM8753_3D, 6, 2, wm8753_3d_uc), +SOC_ENUM_SINGLE(WM8753_3D, 5, 2, wm8753_3d_lc), +SOC_ENUM_SINGLE(WM8753_DAC, 1, 4, wm8753_deemp), +SOC_ENUM_SINGLE(WM8753_DAC, 4, 4, wm8753_mono_mix), +SOC_ENUM_SINGLE(WM8753_DAC, 6, 2, wm8753_dac_phase), +SOC_ENUM_SINGLE(WM8753_INCTL1, 3, 4, wm8753_line_mix), +SOC_ENUM_SINGLE(WM8753_INCTL1, 2, 2, wm8753_mono_mux), +SOC_ENUM_SINGLE(WM8753_INCTL1, 1, 2, wm8753_right_mux), +SOC_ENUM_SINGLE(WM8753_INCTL1, 0, 2, wm8753_left_mux), +SOC_ENUM_SINGLE(WM8753_INCTL2, 6, 4, wm8753_rxmsel), +SOC_ENUM_SINGLE(WM8753_INCTL2, 4, 4, wm8753_sidetone_mux), +SOC_ENUM_SINGLE(WM8753_OUTCTL, 7, 4, wm8753_mono2_src), +SOC_ENUM_SINGLE(WM8753_OUTCTL, 0, 3, wm8753_out3), +SOC_ENUM_SINGLE(WM8753_ADCTL2, 7, 3, wm8753_out4), +SOC_ENUM_SINGLE(WM8753_ADCIN, 2, 3, wm8753_radcsel), +SOC_ENUM_SINGLE(WM8753_ADCIN, 0, 3, wm8753_ladcsel), +SOC_ENUM_SINGLE(WM8753_ADCIN, 4, 4, wm8753_mono_adc), +SOC_ENUM_SINGLE(WM8753_ADC, 2, 4, wm8753_adc_hp), +SOC_ENUM_SINGLE(WM8753_ADC, 4, 2, wm8753_adc_filter), +SOC_ENUM_SINGLE(WM8753_MICBIAS, 6, 3, wm8753_mic_sel), +SOC_ENUM_SINGLE(WM8753_IOCTL, 2, 4, wm8753_dai_mode), +SOC_ENUM_SINGLE(WM8753_ADC, 7, 4, wm8753_dat_sel), +SOC_ENUM_SINGLE(WM8753_OUTCTL, 2, 2, wm8753_rout2_phase), +}; + + +static int wm8753_get_dai(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL); + + ucontrol->value.integer.value[0] = (mode & 0xc) >> 2; + return 0; +} + +static int wm8753_set_dai(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL); + + if (((mode & 0xc) >> 2) == ucontrol->value.integer.value[0]) + return 0; + + mode &= 0xfff3; + mode |= (ucontrol->value.integer.value[0] << 2); + + wm8753_write(codec, WM8753_IOCTL, mode); + wm8753_set_dai_mode(codec, ucontrol->value.integer.value[0]); + return 1; +} + +static const DECLARE_TLV_DB_SCALE(rec_mix_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(mic_preamp_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const unsigned int out_tlv[] = { + TLV_DB_RANGE_HEAD(2), + /* 0000000 - 0101111 = "Analogue mute" */ + 0, 48, TLV_DB_SCALE_ITEM(-25500, 0, 0), + 48, 127, TLV_DB_SCALE_ITEM(-7300, 100, 0), +}; +static const DECLARE_TLV_DB_SCALE(mix_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(voice_mix_tlv, -1200, 300, 0); +static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0); + +static const struct snd_kcontrol_new wm8753_snd_controls[] = { +SOC_DOUBLE_R_TLV("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0, + adc_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, + 127, 0, out_tlv), + +SOC_SINGLE_TLV("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0, out_tlv), + +SOC_DOUBLE_R_TLV("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, + 1, mix_tlv), +SOC_DOUBLE_R_TLV("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, + 7, 1, mix_tlv), +SOC_DOUBLE_R_TLV("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, + 1, voice_mix_tlv), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, + 1, 0), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, + 1, 0), + +SOC_SINGLE_TLV("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1, mix_tlv), +SOC_SINGLE_TLV("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1, + mix_tlv), +SOC_SINGLE_TLV("Mono Voice Playback Volume", WM8753_MOUTM2, 0, 7, 1, + voice_mix_tlv), +SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0), + +SOC_ENUM("Bass Boost", wm8753_enum[0]), +SOC_ENUM("Bass Filter", wm8753_enum[1]), +SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1), +SOC_ENUM("Treble Cut-off", wm8753_enum[2]), + +SOC_DOUBLE_TLV("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1, + rec_mix_tlv), +SOC_SINGLE_TLV("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1, + rec_mix_tlv), + +SOC_DOUBLE_R_TLV("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0, + pga_tlv), +SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1), + +SOC_ENUM("Capture Filter Select", wm8753_enum[23]), +SOC_ENUM("Capture Filter Cut-off", wm8753_enum[24]), +SOC_SINGLE("Capture Filter Switch", WM8753_ADC, 0, 1, 1), + +SOC_SINGLE("ALC Capture Target Volume", WM8753_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8753_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", wm8753_enum[3]), +SOC_SINGLE("ALC Capture ZC Switch", WM8753_ALC2, 8, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8753_ALC2, 0, 15, 1), +SOC_SINGLE("ALC Capture Decay Time", WM8753_ALC3, 4, 15, 1), +SOC_SINGLE("ALC Capture Attack Time", WM8753_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8753_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", wm8753_enum[4]), +SOC_SINGLE("ALC Capture NG Switch", WM8753_NGATE, 0, 1, 0), + +SOC_ENUM("3D Function", wm8753_enum[5]), +SOC_ENUM("3D Upper Cut-off", wm8753_enum[6]), +SOC_ENUM("3D Lower Cut-off", wm8753_enum[7]), +SOC_SINGLE("3D Volume", WM8753_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8753_3D, 0, 1, 0), + +SOC_SINGLE("Capture 6dB Attenuate", WM8753_ADCTL1, 2, 1, 0), +SOC_SINGLE("Playback 6dB Attenuate", WM8753_ADCTL1, 1, 1, 0), + +SOC_ENUM("De-emphasis", wm8753_enum[8]), +SOC_ENUM("Playback Mono Mix", wm8753_enum[9]), +SOC_ENUM("Playback Phase", wm8753_enum[10]), + +SOC_SINGLE_TLV("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0, mic_preamp_tlv), +SOC_SINGLE_TLV("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0, mic_preamp_tlv), + +SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai), + +SOC_ENUM("ADC Data Select", wm8753_enum[27]), +SOC_ENUM("ROUT2 Phase", wm8753_enum[28]), +}; + +/* add non dapm controls */ +static int wm8753_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8753_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8753_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * _DAPM_ Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8753_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_LOUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Left Playback Switch", WM8753_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_LOUTM1, 7, 1, 0), +}; + +/* Right mixer */ +static const struct snd_kcontrol_new wm8753_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_ROUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8753_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_ROUTM1, 7, 1, 0), +}; + +/* Mono mixer */ +static const struct snd_kcontrol_new wm8753_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8753_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8753_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_MOUTM2, 3, 1, 0), +SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_MOUTM2, 7, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_MOUTM1, 7, 1, 0), +}; + +/* Mono 2 Mux */ +static const struct snd_kcontrol_new wm8753_mono2_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[17]); + +/* Out 3 Mux */ +static const struct snd_kcontrol_new wm8753_out3_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[18]); + +/* Out 4 Mux */ +static const struct snd_kcontrol_new wm8753_out4_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[19]); + +/* ADC Mono Mix */ +static const struct snd_kcontrol_new wm8753_adc_mono_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[22]); + +/* Record mixer */ +static const struct snd_kcontrol_new wm8753_record_mixer_controls[] = { +SOC_DAPM_SINGLE("Voice Capture Switch", WM8753_RECMIX2, 3, 1, 0), +SOC_DAPM_SINGLE("Left Capture Switch", WM8753_RECMIX1, 3, 1, 0), +SOC_DAPM_SINGLE("Right Capture Switch", WM8753_RECMIX1, 7, 1, 0), +}; + +/* Left ADC mux */ +static const struct snd_kcontrol_new wm8753_adc_left_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[21]); + +/* Right ADC mux */ +static const struct snd_kcontrol_new wm8753_adc_right_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[20]); + +/* MIC mux */ +static const struct snd_kcontrol_new wm8753_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[16]); + +/* ALC mixer */ +static const struct snd_kcontrol_new wm8753_alc_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Capture Switch", WM8753_INCTL2, 3, 1, 0), +SOC_DAPM_SINGLE("Mic2 Capture Switch", WM8753_INCTL2, 2, 1, 0), +SOC_DAPM_SINGLE("Mic1 Capture Switch", WM8753_INCTL2, 1, 1, 0), +SOC_DAPM_SINGLE("Rx Capture Switch", WM8753_INCTL2, 0, 1, 0), +}; + +/* Left Line mux */ +static const struct snd_kcontrol_new wm8753_line_left_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[14]); + +/* Right Line mux */ +static const struct snd_kcontrol_new wm8753_line_right_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[13]); + +/* Mono Line mux */ +static const struct snd_kcontrol_new wm8753_line_mono_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[12]); + +/* Line mux and mixer */ +static const struct snd_kcontrol_new wm8753_line_mux_mix_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[11]); + +/* Rx mux and mixer */ +static const struct snd_kcontrol_new wm8753_rx_mux_mix_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[15]); + +/* Mic Selector Mux */ +static const struct snd_kcontrol_new wm8753_mic_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm8753_enum[25]); + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8753_PWR1, 5, 0), +SND_SOC_DAPM_MIXER("Left Mixer", WM8753_PWR4, 0, 0, + &wm8753_left_mixer_controls[0], ARRAY_SIZE(wm8753_left_mixer_controls)), +SND_SOC_DAPM_PGA("Left Out 1", WM8753_PWR3, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Out 2", WM8753_PWR3, 6, 0, NULL, 0), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", WM8753_PWR1, 3, 0), +SND_SOC_DAPM_OUTPUT("LOUT1"), +SND_SOC_DAPM_OUTPUT("LOUT2"), +SND_SOC_DAPM_MIXER("Right Mixer", WM8753_PWR4, 1, 0, + &wm8753_right_mixer_controls[0], ARRAY_SIZE(wm8753_right_mixer_controls)), +SND_SOC_DAPM_PGA("Right Out 1", WM8753_PWR3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Out 2", WM8753_PWR3, 5, 0, NULL, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", WM8753_PWR1, 2, 0), +SND_SOC_DAPM_OUTPUT("ROUT1"), +SND_SOC_DAPM_OUTPUT("ROUT2"), +SND_SOC_DAPM_MIXER("Mono Mixer", WM8753_PWR4, 2, 0, + &wm8753_mono_mixer_controls[0], ARRAY_SIZE(wm8753_mono_mixer_controls)), +SND_SOC_DAPM_PGA("Mono Out 1", WM8753_PWR3, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out 2", WM8753_PWR3, 1, 0, NULL, 0), +SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", WM8753_PWR1, 4, 0), +SND_SOC_DAPM_OUTPUT("MONO1"), +SND_SOC_DAPM_MUX("Mono 2 Mux", SND_SOC_NOPM, 0, 0, &wm8753_mono2_controls), +SND_SOC_DAPM_OUTPUT("MONO2"), +SND_SOC_DAPM_MIXER("Out3 Left + Right", -1, 0, 0, NULL, 0), +SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out3_controls), +SND_SOC_DAPM_PGA("Out 3", WM8753_PWR3, 4, 0, NULL, 0), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_MUX("Out4 Mux", SND_SOC_NOPM, 0, 0, &wm8753_out4_controls), +SND_SOC_DAPM_PGA("Out 4", WM8753_PWR3, 3, 0, NULL, 0), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_MIXER("Playback Mixer", WM8753_PWR4, 3, 0, + &wm8753_record_mixer_controls[0], + ARRAY_SIZE(wm8753_record_mixer_controls)), +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8753_PWR2, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8753_PWR2, 2, 0), +SND_SOC_DAPM_MUX("Capture Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8753_adc_mono_controls), +SND_SOC_DAPM_MUX("Capture Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8753_adc_mono_controls), +SND_SOC_DAPM_MUX("Capture Left Mux", SND_SOC_NOPM, 0, 0, + &wm8753_adc_left_controls), +SND_SOC_DAPM_MUX("Capture Right Mux", SND_SOC_NOPM, 0, 0, + &wm8753_adc_right_controls), +SND_SOC_DAPM_MUX("Mic Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm8753_mic_mux_controls), +SND_SOC_DAPM_PGA("Left Capture Volume", WM8753_PWR2, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Capture Volume", WM8753_PWR2, 4, 0, NULL, 0), +SND_SOC_DAPM_MIXER("ALC Mixer", WM8753_PWR2, 6, 0, + &wm8753_alc_mixer_controls[0], ARRAY_SIZE(wm8753_alc_mixer_controls)), +SND_SOC_DAPM_MUX("Line Left Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_left_controls), +SND_SOC_DAPM_MUX("Line Right Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_right_controls), +SND_SOC_DAPM_MUX("Line Mono Mux", SND_SOC_NOPM, 0, 0, + &wm8753_line_mono_controls), +SND_SOC_DAPM_MUX("Line Mixer", WM8753_PWR2, 0, 0, + &wm8753_line_mux_mix_controls), +SND_SOC_DAPM_MUX("Rx Mixer", WM8753_PWR2, 1, 0, + &wm8753_rx_mux_mix_controls), +SND_SOC_DAPM_PGA("Mic 1 Volume", WM8753_PWR2, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("Mic 2 Volume", WM8753_PWR2, 7, 0, NULL, 0), +SND_SOC_DAPM_MUX("Mic Selection Mux", SND_SOC_NOPM, 0, 0, + &wm8753_mic_sel_mux_controls), +SND_SOC_DAPM_INPUT("LINE1"), +SND_SOC_DAPM_INPUT("LINE2"), +SND_SOC_DAPM_INPUT("RXP"), +SND_SOC_DAPM_INPUT("RXN"), +SND_SOC_DAPM_INPUT("ACIN"), +SND_SOC_DAPM_OUTPUT("ACOP"), +SND_SOC_DAPM_INPUT("MIC1N"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2N"), +SND_SOC_DAPM_INPUT("MIC2"), +SND_SOC_DAPM_VMID("VREF"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* left mixer */ + {"Left Mixer", "Left Playback Switch", "Left DAC"}, + {"Left Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Left Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Left Mixer", "Bypass Playback Switch", "Line Left Mux"}, + + /* right mixer */ + {"Right Mixer", "Right Playback Switch", "Right DAC"}, + {"Right Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Right Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Right Mixer", "Bypass Playback Switch", "Line Right Mux"}, + + /* mono mixer */ + {"Mono Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Sidetone Playback Switch", "Mic Sidetone Mux"}, + {"Mono Mixer", "Bypass Playback Switch", "Line Mono Mux"}, + + /* left out */ + {"Left Out 1", NULL, "Left Mixer"}, + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out */ + {"Right Out 1", NULL, "Right Mixer"}, + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono 1 out */ + {"Mono Out 1", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out 1"}, + + /* mono 2 out */ + {"Mono 2 Mux", "Left + Right", "Out3 Left + Right"}, + {"Mono 2 Mux", "Inverted Mono 1", "MONO1"}, + {"Mono 2 Mux", "Left", "Left Mixer"}, + {"Mono 2 Mux", "Right", "Right Mixer"}, + {"Mono Out 2", NULL, "Mono 2 Mux"}, + {"MONO2", NULL, "Mono Out 2"}, + + /* out 3 */ + {"Out3 Left + Right", NULL, "Left Mixer"}, + {"Out3 Left + Right", NULL, "Right Mixer"}, + {"Out3 Mux", "VREF", "VREF"}, + {"Out3 Mux", "Left + Right", "Out3 Left + Right"}, + {"Out3 Mux", "ROUT2", "ROUT2"}, + {"Out 3", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3"}, + + /* out 4 */ + {"Out4 Mux", "VREF", "VREF"}, + {"Out4 Mux", "Capture ST", "Playback Mixer"}, + {"Out4 Mux", "LOUT2", "LOUT2"}, + {"Out 4", NULL, "Out4 Mux"}, + {"OUT4", NULL, "Out 4"}, + + /* record mixer */ + {"Playback Mixer", "Left Capture Switch", "Left Mixer"}, + {"Playback Mixer", "Voice Capture Switch", "Mono Mixer"}, + {"Playback Mixer", "Right Capture Switch", "Right Mixer"}, + + /* Mic/SideTone Mux */ + {"Mic Sidetone Mux", "Left PGA", "Left Capture Volume"}, + {"Mic Sidetone Mux", "Right PGA", "Right Capture Volume"}, + {"Mic Sidetone Mux", "Mic 1", "Mic 1 Volume"}, + {"Mic Sidetone Mux", "Mic 2", "Mic 2 Volume"}, + + /* Capture Left Mux */ + {"Capture Left Mux", "PGA", "Left Capture Volume"}, + {"Capture Left Mux", "Line or RXP-RXN", "Line Left Mux"}, + {"Capture Left Mux", "Line", "LINE1"}, + + /* Capture Right Mux */ + {"Capture Right Mux", "PGA", "Right Capture Volume"}, + {"Capture Right Mux", "Line or RXP-RXN", "Line Right Mux"}, + {"Capture Right Mux", "Sidetone", "Playback Mixer"}, + + /* Mono Capture mixer-mux */ + {"Capture Right Mixer", "Stereo", "Capture Right Mux"}, + {"Capture Left Mixer", "Analogue Mix Left", "Capture Left Mux"}, + {"Capture Left Mixer", "Analogue Mix Left", "Capture Right Mux"}, + {"Capture Right Mixer", "Analogue Mix Right", "Capture Left Mux"}, + {"Capture Right Mixer", "Analogue Mix Right", "Capture Right Mux"}, + {"Capture Left Mixer", "Digital Mono Mix", "Capture Left Mux"}, + {"Capture Left Mixer", "Digital Mono Mix", "Capture Right Mux"}, + {"Capture Right Mixer", "Digital Mono Mix", "Capture Left Mux"}, + {"Capture Right Mixer", "Digital Mono Mix", "Capture Right Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Capture Left Mixer"}, + {"Right ADC", NULL, "Capture Right Mixer"}, + + /* Left Capture Volume */ + {"Left Capture Volume", NULL, "ACIN"}, + + /* Right Capture Volume */ + {"Right Capture Volume", NULL, "Mic 2 Volume"}, + + /* ALC Mixer */ + {"ALC Mixer", "Line Capture Switch", "Line Mixer"}, + {"ALC Mixer", "Mic2 Capture Switch", "Mic 2 Volume"}, + {"ALC Mixer", "Mic1 Capture Switch", "Mic 1 Volume"}, + {"ALC Mixer", "Rx Capture Switch", "Rx Mixer"}, + + /* Line Left Mux */ + {"Line Left Mux", "Line 1", "LINE1"}, + {"Line Left Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Right Mux */ + {"Line Right Mux", "Line 2", "LINE2"}, + {"Line Right Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Mono Mux */ + {"Line Mono Mux", "Line Mix", "Line Mixer"}, + {"Line Mono Mux", "Rx Mix", "Rx Mixer"}, + + /* Line Mixer/Mux */ + {"Line Mixer", "Line 1 + 2", "LINE1"}, + {"Line Mixer", "Line 1 - 2", "LINE1"}, + {"Line Mixer", "Line 1 + 2", "LINE2"}, + {"Line Mixer", "Line 1 - 2", "LINE2"}, + {"Line Mixer", "Line 1", "LINE1"}, + {"Line Mixer", "Line 2", "LINE2"}, + + /* Rx Mixer/Mux */ + {"Rx Mixer", "RXP - RXN", "RXP"}, + {"Rx Mixer", "RXP + RXN", "RXP"}, + {"Rx Mixer", "RXP - RXN", "RXN"}, + {"Rx Mixer", "RXP + RXN", "RXN"}, + {"Rx Mixer", "RXP", "RXP"}, + {"Rx Mixer", "RXN", "RXN"}, + + /* Mic 1 Volume */ + {"Mic 1 Volume", NULL, "MIC1N"}, + {"Mic 1 Volume", NULL, "Mic Selection Mux"}, + + /* Mic 2 Volume */ + {"Mic 2 Volume", NULL, "MIC2N"}, + {"Mic 2 Volume", NULL, "MIC2"}, + + /* Mic Selector Mux */ + {"Mic Selection Mux", "Mic 1", "MIC1"}, + {"Mic Selection Mux", "Mic 2", "MIC2N"}, + {"Mic Selection Mux", "Mic 3", "MIC2"}, + + /* ACOP */ + {"ACOP", NULL, "ALC Mixer"}, +}; + +static int wm8753_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets, + ARRAY_SIZE(wm8753_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 div2:1; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 22) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "wm8753: unsupported N = %d\n", Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg, enable; + int offset; + struct snd_soc_codec *codec = codec_dai->codec; + + if (pll_id < WM8753_PLL1 || pll_id > WM8753_PLL2) + return -ENODEV; + + if (pll_id == WM8753_PLL1) { + offset = 0; + enable = 0x10; + reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xffef; + } else { + offset = 4; + enable = 0x8; + reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfff7; + } + + if (!freq_in || !freq_out) { + /* disable PLL */ + wm8753_write(codec, WM8753_PLL1CTL1 + offset, 0x0026); + wm8753_write(codec, WM8753_CLOCK, reg); + return 0; + } else { + u16 value = 0; + struct _pll_div pll_div; + + pll_factors(&pll_div, freq_out * 8, freq_in); + + /* set up N and K PLL divisor ratios */ + /* bits 8:5 = PLL_N, bits 3:0 = PLL_K[21:18] */ + value = (pll_div.n << 5) + ((pll_div.k & 0x3c0000) >> 18); + wm8753_write(codec, WM8753_PLL1CTL2 + offset, value); + + /* bits 8:0 = PLL_K[17:9] */ + value = (pll_div.k & 0x03fe00) >> 9; + wm8753_write(codec, WM8753_PLL1CTL3 + offset, value); + + /* bits 8:0 = PLL_K[8:0] */ + value = pll_div.k & 0x0001ff; + wm8753_write(codec, WM8753_PLL1CTL4 + offset, value); + + /* set PLL as input and enable */ + wm8753_write(codec, WM8753_PLL1CTL1 + offset, 0x0027 | + (pll_div.div2 << 3)); + wm8753_write(codec, WM8753_CLOCK, reg | enable); + } + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk (after PLL) clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 0x6, 0x0}, + {11289600, 8000, 0x16, 0x0}, + {18432000, 8000, 0x7, 0x0}, + {16934400, 8000, 0x17, 0x0}, + {12000000, 8000, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 0x18, 0x0}, + {16934400, 11025, 0x19, 0x0}, + {12000000, 11025, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 0xa, 0x0}, + {18432000, 16000, 0xb, 0x0}, + {12000000, 16000, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 0x1a, 0x0}, + {16934400, 22050, 0x1b, 0x0}, + {12000000, 22050, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 0xc, 0x0}, + {18432000, 32000, 0xd, 0x0}, + {12000000, 32000, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 0x10, 0x0}, + {16934400, 44100, 0x11, 0x0}, + {12000000, 44100, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 0x0, 0x0}, + {18432000, 48000, 0x1, 0x0}, + {12000000, 48000, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 0x1e, 0x0}, + {16934400, 88200, 0x1f, 0x0}, + {12000000, 88200, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 0xe, 0x0}, + {18432000, 96000, 0xf, 0x0}, + {12000000, 96000, 0xe, 0x1}, +}; + +static int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +/* + * Clock after PLL and dividers + */ +static int wm8753_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8753_priv *wm8753 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + if (clk_id == WM8753_MCLK) { + wm8753->sysclk = freq; + return 0; + } else if (clk_id == WM8753_PCMCLK) { + wm8753->pcmclk = freq; + return 0; + } + break; + } + return -EINVAL; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01ec; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + voice |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + voice |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + voice |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + voice |= 0x0013; + break; + default: + return -EINVAL; + } + + wm8753_write(codec, WM8753_PCM, voice); + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8753_priv *wm8753 = codec->private_data; + u16 voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x01f3; + u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x017f; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + voice |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + voice |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + voice |= 0x000c; + break; + } + + /* sample rate */ + if (params_rate(params) * 384 == wm8753->pcmclk) + srate |= 0x80; + wm8753_write(codec, WM8753_SRATE1, srate); + + wm8753_write(codec, WM8753_PCM, voice); + return 0; +} + +/* + * Set's PCM dai fmt and BCLK. + */ +static int wm8753_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 voice, ioctl; + + voice = wm8753_read_reg_cache(codec, WM8753_PCM) & 0x011f; + ioctl = wm8753_read_reg_cache(codec, WM8753_IOCTL) & 0x015d; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ioctl |= 0x2; + case SND_SOC_DAIFMT_CBM_CFS: + voice |= 0x0040; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + 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: + voice |= 0x0080; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + voice &= ~0x0010; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + voice |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + voice |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + voice |= 0x0010; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8753_write(codec, WM8753_PCM, voice); + wm8753_write(codec, WM8753_IOCTL, ioctl); + return 0; +} + +static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8753_PCMDIV: + reg = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0x003f; + wm8753_write(codec, WM8753_CLOCK, reg | div); + break; + case WM8753_BCLKDIV: + reg = wm8753_read_reg_cache(codec, WM8753_SRATE2) & 0x01c7; + wm8753_write(codec, WM8753_SRATE2, reg | div); + break; + case WM8753_VXCLKDIV: + reg = wm8753_read_reg_cache(codec, WM8753_SRATE2) & 0x003f; + wm8753_write(codec, WM8753_SRATE2, reg | div); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * Set's HiFi DAC format. + */ +static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01e0; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + hifi |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + hifi |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + hifi |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + hifi |= 0x0013; + break; + default: + return -EINVAL; + } + + wm8753_write(codec, WM8753_HIFI, hifi); + return 0; +} + +/* + * Set's I2S DAI format. + */ +static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 ioctl, hifi; + + hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x011f; + ioctl = wm8753_read_reg_cache(codec, WM8753_IOCTL) & 0x00ae; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + ioctl |= 0x1; + case SND_SOC_DAIFMT_CBM_CFS: + hifi |= 0x0040; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + 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: + hifi |= 0x0080; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + hifi &= ~0x0010; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + hifi |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + hifi |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + hifi |= 0x0010; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8753_write(codec, WM8753_HIFI, hifi); + wm8753_write(codec, WM8753_IOCTL, ioctl); + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8753_priv *wm8753 = codec->private_data; + u16 srate = wm8753_read_reg_cache(codec, WM8753_SRATE1) & 0x01c0; + u16 hifi = wm8753_read_reg_cache(codec, WM8753_HIFI) & 0x01f3; + int coeff; + + /* is digital filter coefficient valid ? */ + coeff = get_coeff(wm8753->sysclk, params_rate(params)); + if (coeff < 0) { + printk(KERN_ERR "wm8753 invalid MCLK or rate\n"); + return coeff; + } + wm8753_write(codec, WM8753_SRATE1, srate | (coeff_div[coeff].sr << 1) | + coeff_div[coeff].usb); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + hifi |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hifi |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + hifi |= 0x000c; + break; + } + + wm8753_write(codec, WM8753_HIFI, hifi); + return 0; +} + +static int wm8753_mode1v_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 clock; + + /* set clk source as pcmclk */ + clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb; + wm8753_write(codec, WM8753_CLOCK, clock); + + if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + return wm8753_pcm_set_dai_fmt(codec_dai, fmt); +} + +static int wm8753_mode1h_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + return wm8753_i2s_set_dai_fmt(codec_dai, fmt); +} + +static int wm8753_mode2_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 clock; + + /* set clk source as pcmclk */ + clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb; + wm8753_write(codec, WM8753_CLOCK, clock); + + if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + return wm8753_i2s_set_dai_fmt(codec_dai, fmt); +} + +static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 clock; + + /* set clk source as mclk */ + clock = wm8753_read_reg_cache(codec, WM8753_CLOCK) & 0xfffb; + wm8753_write(codec, WM8753_CLOCK, clock | 0x4); + + if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0) + return -EINVAL; + return wm8753_i2s_set_dai_fmt(codec_dai, fmt); +} + +static int wm8753_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8753_read_reg_cache(codec, WM8753_DAC) & 0xfff7; + + /* the digital mute covers the HiFi and Voice DAC's on the WM8753. + * make sure we check if they are not both active when we mute */ + if (mute && dai->id == 1) { + if (!wm8753_dai[WM8753_DAI_VOICE].playback.active || + !wm8753_dai[WM8753_DAI_HIFI].playback.active) + wm8753_write(codec, WM8753_DAC, mute_reg | 0x8); + } else { + if (mute) + wm8753_write(codec, WM8753_DAC, mute_reg | 0x8); + else + wm8753_write(codec, WM8753_DAC, mute_reg); + } + + return 0; +} + +static int wm8753_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 pwr_reg = wm8753_read_reg_cache(codec, WM8753_PWR1) & 0xfe3e; + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x00c0); + break; + case SND_SOC_BIAS_PREPARE: + /* set vmid to 5k for quick power up */ + wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x01c1); + break; + case SND_SOC_BIAS_STANDBY: + /* mute dac and set vmid to 500k, enable VREF */ + wm8753_write(codec, WM8753_PWR1, pwr_reg | 0x0141); + break; + case SND_SOC_BIAS_OFF: + wm8753_write(codec, WM8753_PWR1, 0x0001); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8753_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8753_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +/* + * The WM8753 supports upto 4 different and mutually exclusive DAI + * configurations. This gives 2 PCM's available for use, hifi and voice. + * NOTE: The Voice PCM cannot play or capture audio to the CPU as it's DAI + * is connected between the wm8753 and a BT codec or GSM modem. + * + * 1. Voice over PCM DAI - HIFI DAC over HIFI DAI + * 2. Voice over HIFI DAI - HIFI disabled + * 3. Voice disabled - HIFI over HIFI + * 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture + */ +static const struct snd_soc_dai wm8753_all_dai[] = { +/* DAI HiFi mode 1 */ +{ .name = "WM8753 HiFi", + .id = 1, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { /* dummy for fast DAI switching */ + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_i2s_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode1h_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI Voice mode 1 */ +{ .name = "WM8753 Voice", + .id = 1, + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_pcm_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode1v_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI HiFi mode 2 - dummy */ +{ .name = "WM8753 HiFi", + .id = 2, +}, +/* DAI Voice mode 2 */ +{ .name = "WM8753 Voice", + .id = 2, + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_pcm_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode2_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI HiFi mode 3 */ +{ .name = "WM8753 HiFi", + .id = 3, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_i2s_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode3_4_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI Voice mode 3 - dummy */ +{ .name = "WM8753 Voice", + .id = 3, +}, +/* DAI HiFi mode 4 */ +{ .name = "WM8753 HiFi", + .id = 4, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8753_RATES, + .formats = WM8753_FORMATS,}, + .ops = { + .hw_params = wm8753_i2s_hw_params,}, + .dai_ops = { + .digital_mute = wm8753_mute, + .set_fmt = wm8753_mode3_4_set_dai_fmt, + .set_clkdiv = wm8753_set_dai_clkdiv, + .set_pll = wm8753_set_dai_pll, + .set_sysclk = wm8753_set_dai_sysclk, + }, +}, +/* DAI Voice mode 4 - dummy */ +{ .name = "WM8753 Voice", + .id = 4, +}, +}; + +struct snd_soc_dai wm8753_dai[2]; +EXPORT_SYMBOL_GPL(wm8753_dai); + +static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode) +{ + if (mode < 4) { + int playback_active, capture_active, codec_active, pop_wait; + void *private_data; + + playback_active = wm8753_dai[0].playback.active; + capture_active = wm8753_dai[0].capture.active; + codec_active = wm8753_dai[0].active; + private_data = wm8753_dai[0].private_data; + pop_wait = wm8753_dai[0].pop_wait; + wm8753_dai[0] = wm8753_all_dai[mode << 1]; + wm8753_dai[0].playback.active = playback_active; + wm8753_dai[0].capture.active = capture_active; + wm8753_dai[0].active = codec_active; + wm8753_dai[0].private_data = private_data; + wm8753_dai[0].pop_wait = pop_wait; + + playback_active = wm8753_dai[1].playback.active; + capture_active = wm8753_dai[1].capture.active; + codec_active = wm8753_dai[1].active; + private_data = wm8753_dai[1].private_data; + pop_wait = wm8753_dai[1].pop_wait; + wm8753_dai[1] = wm8753_all_dai[(mode << 1) + 1]; + wm8753_dai[1].playback.active = playback_active; + wm8753_dai[1].capture.active = capture_active; + wm8753_dai[1].active = codec_active; + wm8753_dai[1].private_data = private_data; + wm8753_dai[1].pop_wait = pop_wait; + } + wm8753_dai[0].codec = codec; + wm8753_dai[1].codec = codec; +} + +static void wm8753_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + wm8753_set_bias_level(codec, codec->bias_level); +} + +static int wm8753_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + /* we only need to suspend if we are a valid card */ + if (!codec->card) + return 0; + + wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8753_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* we only need to resume if we are a valid card */ + if (!codec->card) + return 0; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8753_reg); i++) { + if (i + 1 == WM8753_RESET) + continue; + data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8753_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* charge wm8753 caps */ + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_ON; + schedule_delayed_work(&codec->delayed_work, + msecs_to_jiffies(caps_charge)); + } + + return 0; +} + +/* + * initialise the WM8753 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8753_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8753"; + codec->owner = THIS_MODULE; + codec->read = wm8753_read_reg_cache; + codec->write = wm8753_write; + codec->set_bias_level = wm8753_set_bias_level; + codec->dai = wm8753_dai; + codec->num_dai = 2; + codec->reg_cache_size = ARRAY_SIZE(wm8753_reg); + codec->reg_cache = kmemdup(wm8753_reg, sizeof(wm8753_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8753_set_dai_mode(codec, 0); + + wm8753_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8753: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps */ + wm8753_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->bias_level = SND_SOC_BIAS_STANDBY; + schedule_delayed_work(&codec->delayed_work, + msecs_to_jiffies(caps_charge)); + + /* set the update bits */ + reg = wm8753_read_reg_cache(codec, WM8753_LDAC); + wm8753_write(codec, WM8753_LDAC, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_RDAC); + wm8753_write(codec, WM8753_RDAC, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_LADC); + wm8753_write(codec, WM8753_LADC, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_RADC); + wm8753_write(codec, WM8753_RADC, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_LOUT1V); + wm8753_write(codec, WM8753_LOUT1V, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_ROUT1V); + wm8753_write(codec, WM8753_ROUT1V, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_LOUT2V); + wm8753_write(codec, WM8753_LOUT2V, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_ROUT2V); + wm8753_write(codec, WM8753_ROUT2V, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_LINVOL); + wm8753_write(codec, WM8753_LINVOL, reg | 0x0100); + reg = wm8753_read_reg_cache(codec, WM8753_RINVOL); + wm8753_write(codec, WM8753_RINVOL, reg | 0x0100); + + wm8753_add_controls(codec); + wm8753_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8753: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8753_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8753 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ + +static int wm8753_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8753_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8753_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8753\n"); + + return ret; +} + +static int wm8753_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8753_i2c_id[] = { + { "wm8753", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id); + +static struct i2c_driver wm8753_i2c_driver = { + .driver = { + .name = "WM8753 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8753_i2c_probe, + .remove = wm8753_i2c_remove, + .id_table = wm8753_i2c_id, +}; + +static int wm8753_add_i2c_device(struct platform_device *pdev, + const struct wm8753_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8753_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8753", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8753_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8753_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8753_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8753_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8753\n"); + + return ret; +} + +static int __devexit wm8753_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8753_spi_driver = { + .driver = { + .name = "wm8753", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8753_spi_probe, + .remove = __devexit_p(wm8753_spi_remove), +}; + +static int wm8753_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif + + +static int wm8753_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8753_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8753_priv *wm8753; + int ret = 0; + + pr_info("WM8753 Audio Codec %s", WM8753_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8753 = kzalloc(sizeof(struct wm8753_priv), GFP_KERNEL); + if (wm8753 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8753; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8753_socdev = socdev; + INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work); + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8753_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8753_spi_write; + ret = spi_register_driver(&wm8753_spi_driver); + if (ret != 0) + printk(KERN_ERR "can't add spi driver"); + } +#endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } + return ret; +} + +/* + * This function forces any delayed work to be queued and run. + */ +static int run_delayed_work(struct delayed_work *dwork) +{ + int ret; + + /* cancel any work waiting to be queued. */ + ret = cancel_delayed_work(dwork); + + /* if there was any work waiting then we run it now and + * wait for it's completion */ + if (ret) { + schedule_delayed_work(dwork, 0); + flush_scheduled_work(); + } + return ret; +} + +/* power down chip */ +static int wm8753_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8753_set_bias_level(codec, SND_SOC_BIAS_OFF); + run_delayed_work(&codec->delayed_work); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8753_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8753_spi_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8753 = { + .probe = wm8753_probe, + .remove = wm8753_remove, + .suspend = wm8753_suspend, + .resume = wm8753_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8753); + +MODULE_DESCRIPTION("ASoC WM8753 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h new file mode 100644 index 0000000..f55704c --- /dev/null +++ b/sound/soc/codecs/wm8753.h @@ -0,0 +1,127 @@ +/* + * wm8753.h -- audio driver for WM8753 + * + * Copyright 2003 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * 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 _WM8753_H +#define _WM8753_H + +/* WM8753 register space */ + +#define WM8753_DAC 0x01 +#define WM8753_ADC 0x02 +#define WM8753_PCM 0x03 +#define WM8753_HIFI 0x04 +#define WM8753_IOCTL 0x05 +#define WM8753_SRATE1 0x06 +#define WM8753_SRATE2 0x07 +#define WM8753_LDAC 0x08 +#define WM8753_RDAC 0x09 +#define WM8753_BASS 0x0a +#define WM8753_TREBLE 0x0b +#define WM8753_ALC1 0x0c +#define WM8753_ALC2 0x0d +#define WM8753_ALC3 0x0e +#define WM8753_NGATE 0x0f +#define WM8753_LADC 0x10 +#define WM8753_RADC 0x11 +#define WM8753_ADCTL1 0x12 +#define WM8753_3D 0x13 +#define WM8753_PWR1 0x14 +#define WM8753_PWR2 0x15 +#define WM8753_PWR3 0x16 +#define WM8753_PWR4 0x17 +#define WM8753_ID 0x18 +#define WM8753_INTPOL 0x19 +#define WM8753_INTEN 0x1a +#define WM8753_GPIO1 0x1b +#define WM8753_GPIO2 0x1c +#define WM8753_RESET 0x1f +#define WM8753_RECMIX1 0x20 +#define WM8753_RECMIX2 0x21 +#define WM8753_LOUTM1 0x22 +#define WM8753_LOUTM2 0x23 +#define WM8753_ROUTM1 0x24 +#define WM8753_ROUTM2 0x25 +#define WM8753_MOUTM1 0x26 +#define WM8753_MOUTM2 0x27 +#define WM8753_LOUT1V 0x28 +#define WM8753_ROUT1V 0x29 +#define WM8753_LOUT2V 0x2a +#define WM8753_ROUT2V 0x2b +#define WM8753_MOUTV 0x2c +#define WM8753_OUTCTL 0x2d +#define WM8753_ADCIN 0x2e +#define WM8753_INCTL1 0x2f +#define WM8753_INCTL2 0x30 +#define WM8753_LINVOL 0x31 +#define WM8753_RINVOL 0x32 +#define WM8753_MICBIAS 0x33 +#define WM8753_CLOCK 0x34 +#define WM8753_PLL1CTL1 0x35 +#define WM8753_PLL1CTL2 0x36 +#define WM8753_PLL1CTL3 0x37 +#define WM8753_PLL1CTL4 0x38 +#define WM8753_PLL2CTL1 0x39 +#define WM8753_PLL2CTL2 0x3a +#define WM8753_PLL2CTL3 0x3b +#define WM8753_PLL2CTL4 0x3c +#define WM8753_BIASCTL 0x3d +#define WM8753_ADCTL2 0x3f + +struct wm8753_setup_data { + int spi; + int i2c_bus; + unsigned short i2c_address; +}; + +#define WM8753_PLL1 0 +#define WM8753_PLL2 1 + +/* clock inputs */ +#define WM8753_MCLK 0 +#define WM8753_PCMCLK 1 + +/* clock divider id's */ +#define WM8753_PCMDIV 0 +#define WM8753_BCLKDIV 1 +#define WM8753_VXCLKDIV 2 + +/* PCM clock dividers */ +#define WM8753_PCM_DIV_1 (0 << 6) +#define WM8753_PCM_DIV_3 (2 << 6) +#define WM8753_PCM_DIV_5_5 (3 << 6) +#define WM8753_PCM_DIV_2 (4 << 6) +#define WM8753_PCM_DIV_4 (5 << 6) +#define WM8753_PCM_DIV_6 (6 << 6) +#define WM8753_PCM_DIV_8 (7 << 6) + +/* BCLK clock dividers */ +#define WM8753_BCLK_DIV_1 (0 << 3) +#define WM8753_BCLK_DIV_2 (1 << 3) +#define WM8753_BCLK_DIV_4 (2 << 3) +#define WM8753_BCLK_DIV_8 (3 << 3) +#define WM8753_BCLK_DIV_16 (4 << 3) + +/* VXCLK clock dividers */ +#define WM8753_VXCLK_DIV_1 (0 << 6) +#define WM8753_VXCLK_DIV_2 (1 << 6) +#define WM8753_VXCLK_DIV_4 (2 << 6) +#define WM8753_VXCLK_DIV_8 (3 << 6) +#define WM8753_VXCLK_DIV_16 (4 << 6) + +#define WM8753_DAI_HIFI 0 +#define WM8753_DAI_VOICE 1 + +extern struct snd_soc_dai wm8753_dai[2]; +extern struct snd_soc_codec_device soc_codec_dev_wm8753; + +#endif diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c new file mode 100644 index 0000000..3b326c9 --- /dev/null +++ b/sound/soc/codecs/wm8900.c @@ -0,0 +1,1541 @@ +/* + * wm8900.c -- WM8900 ALSA Soc Audio driver + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - Tristating. + * - TDM. + * - Jack detect. + * - FLL source configuration, currently only MCLK is supported. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "wm8900.h" + +/* WM8900 register space */ +#define WM8900_REG_RESET 0x0 +#define WM8900_REG_ID 0x0 +#define WM8900_REG_POWER1 0x1 +#define WM8900_REG_POWER2 0x2 +#define WM8900_REG_POWER3 0x3 +#define WM8900_REG_AUDIO1 0x4 +#define WM8900_REG_AUDIO2 0x5 +#define WM8900_REG_CLOCKING1 0x6 +#define WM8900_REG_CLOCKING2 0x7 +#define WM8900_REG_AUDIO3 0x8 +#define WM8900_REG_AUDIO4 0x9 +#define WM8900_REG_DACCTRL 0xa +#define WM8900_REG_LDAC_DV 0xb +#define WM8900_REG_RDAC_DV 0xc +#define WM8900_REG_SIDETONE 0xd +#define WM8900_REG_ADCCTRL 0xe +#define WM8900_REG_LADC_DV 0xf +#define WM8900_REG_RADC_DV 0x10 +#define WM8900_REG_GPIO 0x12 +#define WM8900_REG_INCTL 0x15 +#define WM8900_REG_LINVOL 0x16 +#define WM8900_REG_RINVOL 0x17 +#define WM8900_REG_INBOOSTMIX1 0x18 +#define WM8900_REG_INBOOSTMIX2 0x19 +#define WM8900_REG_ADCPATH 0x1a +#define WM8900_REG_AUXBOOST 0x1b +#define WM8900_REG_ADDCTL 0x1e +#define WM8900_REG_FLLCTL1 0x24 +#define WM8900_REG_FLLCTL2 0x25 +#define WM8900_REG_FLLCTL3 0x26 +#define WM8900_REG_FLLCTL4 0x27 +#define WM8900_REG_FLLCTL5 0x28 +#define WM8900_REG_FLLCTL6 0x29 +#define WM8900_REG_LOUTMIXCTL1 0x2c +#define WM8900_REG_ROUTMIXCTL1 0x2d +#define WM8900_REG_BYPASS1 0x2e +#define WM8900_REG_BYPASS2 0x2f +#define WM8900_REG_AUXOUT_CTL 0x30 +#define WM8900_REG_LOUT1CTL 0x33 +#define WM8900_REG_ROUT1CTL 0x34 +#define WM8900_REG_LOUT2CTL 0x35 +#define WM8900_REG_ROUT2CTL 0x36 +#define WM8900_REG_HPCTL1 0x3a +#define WM8900_REG_OUTBIASCTL 0x73 + +#define WM8900_MAXREG 0x80 + +#define WM8900_REG_ADDCTL_OUT1_DIS 0x80 +#define WM8900_REG_ADDCTL_OUT2_DIS 0x40 +#define WM8900_REG_ADDCTL_VMID_DIS 0x20 +#define WM8900_REG_ADDCTL_BIAS_SRC 0x10 +#define WM8900_REG_ADDCTL_VMID_SOFTST 0x04 +#define WM8900_REG_ADDCTL_TEMP_SD 0x02 + +#define WM8900_REG_GPIO_TEMP_ENA 0x2 + +#define WM8900_REG_POWER1_STARTUP_BIAS_ENA 0x0100 +#define WM8900_REG_POWER1_BIAS_ENA 0x0008 +#define WM8900_REG_POWER1_VMID_BUF_ENA 0x0004 +#define WM8900_REG_POWER1_FLL_ENA 0x0040 + +#define WM8900_REG_POWER2_SYSCLK_ENA 0x8000 +#define WM8900_REG_POWER2_ADCL_ENA 0x0002 +#define WM8900_REG_POWER2_ADCR_ENA 0x0001 + +#define WM8900_REG_POWER3_DACL_ENA 0x0002 +#define WM8900_REG_POWER3_DACR_ENA 0x0001 + +#define WM8900_REG_AUDIO1_AIF_FMT_MASK 0x0018 +#define WM8900_REG_AUDIO1_LRCLK_INV 0x0080 +#define WM8900_REG_AUDIO1_BCLK_INV 0x0100 + +#define WM8900_REG_CLOCKING1_BCLK_DIR 0x1 +#define WM8900_REG_CLOCKING1_MCLK_SRC 0x100 +#define WM8900_REG_CLOCKING1_BCLK_MASK (~0x01e) +#define WM8900_REG_CLOCKING1_OPCLK_MASK (~0x7000) + +#define WM8900_REG_CLOCKING2_ADC_CLKDIV 0xe0 +#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c + +#define WM8900_REG_DACCTRL_MUTE 0x004 +#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400 + +#define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800 + +#define WM8900_REG_AUDIO4_DACLRC_DIR 0x0800 + +#define WM8900_REG_FLLCTL1_OSC_ENA 0x100 + +#define WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF 0x100 + +#define WM8900_REG_HPCTL1_HP_IPSTAGE_ENA 0x80 +#define WM8900_REG_HPCTL1_HP_OPSTAGE_ENA 0x40 +#define WM8900_REG_HPCTL1_HP_CLAMP_IP 0x20 +#define WM8900_REG_HPCTL1_HP_CLAMP_OP 0x10 +#define WM8900_REG_HPCTL1_HP_SHORT 0x08 +#define WM8900_REG_HPCTL1_HP_SHORT2 0x04 + +#define WM8900_LRC_MASK 0xfc00 + +struct snd_soc_codec_device soc_codec_dev_wm8900; + +struct wm8900_priv { + u32 fll_in; /* FLL input frequency */ + u32 fll_out; /* FLL output frequency */ +}; + +/* + * wm8900 register cache. We can't read the entire register space and we + * have slow control buses so we cache the registers. + */ +static const u16 wm8900_reg_defaults[WM8900_MAXREG] = { + 0x8900, 0x0000, + 0xc000, 0x0000, + 0x4050, 0x4000, + 0x0008, 0x0000, + 0x0040, 0x0040, + 0x1004, 0x00c0, + 0x00c0, 0x0000, + 0x0100, 0x00c0, + 0x00c0, 0x0000, + 0xb001, 0x0000, + 0x0000, 0x0044, + 0x004c, 0x004c, + 0x0044, 0x0044, + 0x0000, 0x0044, + 0x0000, 0x0000, + 0x0002, 0x0000, + 0x0000, 0x0000, + 0x0000, 0x0000, + 0x0008, 0x0000, + 0x0000, 0x0008, + 0x0097, 0x0100, + 0x0000, 0x0000, + 0x0050, 0x0050, + 0x0055, 0x0055, + 0x0055, 0x0000, + 0x0000, 0x0079, + 0x0079, 0x0079, + 0x0079, 0x0000, + /* Remaining registers all zero */ +}; + +/* + * read wm8900 register cache + */ +static inline unsigned int wm8900_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= WM8900_MAXREG); + + if (reg == WM8900_REG_ID) + return 0; + + return cache[reg]; +} + +/* + * write wm8900 register cache + */ +static inline void wm8900_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= WM8900_MAXREG); + + cache[reg] = value; +} + +/* + * write to the WM8900 register space + */ +static int wm8900_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + if (value == wm8900_read_reg_cache(codec, reg)) + return 0; + + /* data is + * D15..D9 WM8900 register offset + * D8...D0 register data + */ + data[0] = reg; + data[1] = value >> 8; + data[2] = value & 0x00ff; + + wm8900_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +/* + * Read from the wm8900. + */ +static unsigned int wm8900_chip_read(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + BUG_ON(reg != WM8900_REG_ID && reg != WM8900_REG_POWER1); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + printk(KERN_CRIT "i2c_transfer returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +/* + * Read from the WM8900 register space. Most registers can't be read + * and are therefore supplied from cache. + */ +static unsigned int wm8900_read(struct snd_soc_codec *codec, unsigned int reg) +{ + switch (reg) { + case WM8900_REG_ID: + return wm8900_chip_read(codec, reg); + default: + return wm8900_read_reg_cache(codec, reg); + } +} + +static void wm8900_reset(struct snd_soc_codec *codec) +{ + wm8900_write(codec, WM8900_REG_RESET, 0); + + memcpy(codec->reg_cache, wm8900_reg_defaults, + sizeof(codec->reg_cache)); +} + +static int wm8900_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 hpctl1 = wm8900_read(codec, WM8900_REG_HPCTL1); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Clamp headphone outputs */ + hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP | + WM8900_REG_HPCTL1_HP_CLAMP_OP; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_POST_PMU: + /* Enable the input stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_IP; + hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT | + WM8900_REG_HPCTL1_HP_SHORT2 | + WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + msleep(400); + + /* Enable the output stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP; + hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Remove the shorts */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* Short the output */ + hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Disable the output stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Clamp the outputs and power down input */ + hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP | + WM8900_REG_HPCTL1_HP_CLAMP_OP; + hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Disable everything */ + wm8900_write(codec, WM8900_REG_HPCTL1, 0); + break; + + default: + BUG(); + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 100, 0); + +static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 0); + +static const DECLARE_TLV_DB_SCALE(in_boost_tlv, -1200, 600, 0); + +static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1200, 100, 0); + +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1); + +static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3600, 300, 0); + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1); + +static const char *mic_bias_level_txt[] = { "0.9*AVDD", "0.65*AVDD" }; + +static const struct soc_enum mic_bias_level = +SOC_ENUM_SINGLE(WM8900_REG_INCTL, 8, 2, mic_bias_level_txt); + +static const char *dac_mute_rate_txt[] = { "Fast", "Slow" }; + +static const struct soc_enum dac_mute_rate = +SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 7, 2, dac_mute_rate_txt); + +static const char *dac_deemphasis_txt[] = { + "Disabled", "32kHz", "44.1kHz", "48kHz" +}; + +static const struct soc_enum dac_deemphasis = +SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 4, 4, dac_deemphasis_txt); + +static const char *adc_hpf_cut_txt[] = { + "Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3" +}; + +static const struct soc_enum adc_hpf_cut = +SOC_ENUM_SINGLE(WM8900_REG_ADCCTRL, 5, 4, adc_hpf_cut_txt); + +static const char *lr_txt[] = { + "Left", "Right" +}; + +static const struct soc_enum aifl_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 15, 2, lr_txt); + +static const struct soc_enum aifr_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 14, 2, lr_txt); + +static const struct soc_enum dacl_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 15, 2, lr_txt); + +static const struct soc_enum dacr_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 14, 2, lr_txt); + +static const char *sidetone_txt[] = { + "Disabled", "Left ADC", "Right ADC" +}; + +static const struct soc_enum dacl_sidetone = +SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 2, 3, sidetone_txt); + +static const struct soc_enum dacr_sidetone = +SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 0, 3, sidetone_txt); + +static const struct snd_kcontrol_new wm8900_snd_controls[] = { +SOC_ENUM("Mic Bias Level", mic_bias_level), + +SOC_SINGLE_TLV("Left Input PGA Volume", WM8900_REG_LINVOL, 0, 31, 0, + in_pga_tlv), +SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1), +SOC_SINGLE("Left Input PGA ZC Switch", WM8900_REG_LINVOL, 7, 1, 0), + +SOC_SINGLE_TLV("Right Input PGA Volume", WM8900_REG_RINVOL, 0, 31, 0, + in_pga_tlv), +SOC_SINGLE("Right Input PGA Switch", WM8900_REG_RINVOL, 6, 1, 1), +SOC_SINGLE("Right Input PGA ZC Switch", WM8900_REG_RINVOL, 7, 1, 0), + +SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1), +SOC_ENUM("DAC Mute Rate", dac_mute_rate), +SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0), +SOC_ENUM("DAC Deemphasis", dac_deemphasis), +SOC_SINGLE("DAC Sloping Stopband Filter Switch", WM8900_REG_DACCTRL, 8, 1, 0), +SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL, + 12, 1, 0), + +SOC_SINGLE("ADC HPF Switch", WM8900_REG_ADCCTRL, 8, 1, 0), +SOC_ENUM("ADC HPF Cut-Off", adc_hpf_cut), +SOC_DOUBLE("ADC Invert Switch", WM8900_REG_ADCCTRL, 1, 0, 1, 0), +SOC_SINGLE_TLV("Left ADC Sidetone Volume", WM8900_REG_SIDETONE, 9, 12, 0, + adc_svol_tlv), +SOC_SINGLE_TLV("Right ADC Sidetone Volume", WM8900_REG_SIDETONE, 5, 12, 0, + adc_svol_tlv), +SOC_ENUM("Left Digital Audio Source", aifl_src), +SOC_ENUM("Right Digital Audio Source", aifr_src), + +SOC_SINGLE_TLV("DAC Input Boost Volume", WM8900_REG_AUDIO2, 10, 4, 0, + dac_boost_tlv), +SOC_ENUM("Left DAC Source", dacl_src), +SOC_ENUM("Right DAC Source", dacr_src), +SOC_ENUM("Left DAC Sidetone", dacl_sidetone), +SOC_ENUM("Right DAC Sidetone", dacr_sidetone), +SOC_DOUBLE("DAC Invert Switch", WM8900_REG_DACCTRL, 1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Digital Playback Volume", + WM8900_REG_LDAC_DV, WM8900_REG_RDAC_DV, + 1, 96, 0, dac_tlv), +SOC_DOUBLE_R_TLV("Digital Capture Volume", + WM8900_REG_LADC_DV, WM8900_REG_RADC_DV, 1, 119, 0, adc_tlv), + +SOC_SINGLE_TLV("LINPUT3 Bypass Volume", WM8900_REG_LOUTMIXCTL1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RINPUT3 Bypass Volume", WM8900_REG_ROUTMIXCTL1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("Left AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("Right AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 0, 7, 0, + out_mix_tlv), + +SOC_SINGLE_TLV("LeftIn to RightOut Mixer Volume", WM8900_REG_BYPASS1, 0, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("LeftIn to LeftOut Mixer Volume", WM8900_REG_BYPASS1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RightIn to LeftOut Mixer Volume", WM8900_REG_BYPASS2, 0, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RightIn to RightOut Mixer Volume", WM8900_REG_BYPASS2, 4, 7, 0, + out_mix_tlv), + +SOC_SINGLE_TLV("IN2L Boost Volume", WM8900_REG_INBOOSTMIX1, 0, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN3L Boost Volume", WM8900_REG_INBOOSTMIX1, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN2R Boost Volume", WM8900_REG_INBOOSTMIX2, 0, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN3R Boost Volume", WM8900_REG_INBOOSTMIX2, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("Left AUX Boost Volume", WM8900_REG_AUXBOOST, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("Right AUX Boost Volume", WM8900_REG_AUXBOOST, 0, 3, 0, + in_boost_tlv), + +SOC_DOUBLE_R_TLV("LINEOUT1 Volume", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 0, 63, 0, out_pga_tlv), +SOC_DOUBLE_R("LINEOUT1 Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 6, 1, 1), +SOC_DOUBLE_R("LINEOUT1 ZC Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("LINEOUT2 Volume", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, + 0, 63, 0, out_pga_tlv), +SOC_DOUBLE_R("LINEOUT2 Switch", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 6, 1, 1), +SOC_DOUBLE_R("LINEOUT2 ZC Switch", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 7, 1, 0), +SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1, + 0, 1, 1), + +}; + +/* add non dapm controls */ +static int wm8900_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8900_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8900_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static const struct snd_kcontrol_new wm8900_dapm_loutput2_control = +SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0); + +static const struct snd_kcontrol_new wm8900_dapm_routput2_control = +SOC_DAPM_SINGLE("LINEOUT2R Switch", WM8900_REG_POWER3, 5, 1, 0); + +static const struct snd_kcontrol_new wm8900_loutmix_controls[] = { +SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0), +SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0), +SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0), +SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_routmix_controls[] = { +SOC_DAPM_SINGLE("RINPUT3 Bypass Switch", WM8900_REG_ROUTMIXCTL1, 7, 1, 0), +SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 3, 1, 0), +SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 3, 1, 0), +SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 7, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8900_REG_ROUTMIXCTL1, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_linmix_controls[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INBOOSTMIX1, 2, 1, 1), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INBOOSTMIX1, 6, 1, 1), +SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 6, 1, 1), +SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 6, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_rinmix_controls[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INBOOSTMIX2, 2, 1, 1), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INBOOSTMIX2, 6, 1, 1), +SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 2, 1, 1), +SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 2, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_linpga_controls[] = { +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8900_REG_INCTL, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INCTL, 5, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INCTL, 4, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_rinpga_controls[] = { +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8900_REG_INCTL, 2, 1, 0), +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INCTL, 1, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INCTL, 0, 1, 0), +}; + +static const char *wm9700_lp_mux[] = { "Disabled", "Enabled" }; + +static const struct soc_enum wm8900_lineout2_lp_mux = +SOC_ENUM_SINGLE(WM8900_REG_LOUTMIXCTL1, 1, 2, wm9700_lp_mux); + +static const struct snd_kcontrol_new wm8900_lineout2_lp = +SOC_DAPM_ENUM("Route", wm8900_lineout2_lp_mux); + +static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = { + +/* Externally visible pins */ +SND_SOC_DAPM_OUTPUT("LINEOUT1L"), +SND_SOC_DAPM_OUTPUT("LINEOUT1R"), +SND_SOC_DAPM_OUTPUT("LINEOUT2L"), +SND_SOC_DAPM_OUTPUT("LINEOUT2R"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), + +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT3"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("AUX"), + +SND_SOC_DAPM_VMID("VMID"), + +/* Input */ +SND_SOC_DAPM_MIXER("Left Input PGA", WM8900_REG_POWER2, 3, 0, + wm8900_linpga_controls, + ARRAY_SIZE(wm8900_linpga_controls)), +SND_SOC_DAPM_MIXER("Right Input PGA", WM8900_REG_POWER2, 2, 0, + wm8900_rinpga_controls, + ARRAY_SIZE(wm8900_rinpga_controls)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0, + wm8900_linmix_controls, + ARRAY_SIZE(wm8900_linmix_controls)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8900_REG_POWER2, 4, 0, + wm8900_rinmix_controls, + ARRAY_SIZE(wm8900_rinmix_controls)), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8900_REG_POWER1, 4, 0), + +SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8900_REG_POWER2, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8900_REG_POWER2, 0, 0), + +/* Output */ +SND_SOC_DAPM_DAC("DACL", "Left HiFi Playback", WM8900_REG_POWER3, 1, 0), +SND_SOC_DAPM_DAC("DACR", "Right HiFi Playback", WM8900_REG_POWER3, 0, 0), + +SND_SOC_DAPM_PGA_E("Headphone Amplifier", WM8900_REG_POWER3, 7, 0, NULL, 0, + wm8900_hp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT1R PGA", WM8900_REG_POWER2, 7, 0, NULL, 0), + +SND_SOC_DAPM_MUX("LINEOUT2 LP", SND_SOC_NOPM, 0, 0, &wm8900_lineout2_lp), +SND_SOC_DAPM_PGA("LINEOUT2L PGA", WM8900_REG_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT2R PGA", WM8900_REG_POWER3, 5, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0, + wm8900_loutmix_controls, + ARRAY_SIZE(wm8900_loutmix_controls)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8900_REG_POWER3, 2, 0, + wm8900_routmix_controls, + ARRAY_SIZE(wm8900_routmix_controls)), +}; + +/* Target, Path, Source */ +static const struct snd_soc_dapm_route audio_map[] = { +/* Inputs */ +{"Left Input PGA", "LINPUT1 Switch", "LINPUT1"}, +{"Left Input PGA", "LINPUT2 Switch", "LINPUT2"}, +{"Left Input PGA", "LINPUT3 Switch", "LINPUT3"}, + +{"Right Input PGA", "RINPUT1 Switch", "RINPUT1"}, +{"Right Input PGA", "RINPUT2 Switch", "RINPUT2"}, +{"Right Input PGA", "RINPUT3 Switch", "RINPUT3"}, + +{"Left Input Mixer", "LINPUT2 Switch", "LINPUT2"}, +{"Left Input Mixer", "LINPUT3 Switch", "LINPUT3"}, +{"Left Input Mixer", "AUX Switch", "AUX"}, +{"Left Input Mixer", "Input PGA Switch", "Left Input PGA"}, + +{"Right Input Mixer", "RINPUT2 Switch", "RINPUT2"}, +{"Right Input Mixer", "RINPUT3 Switch", "RINPUT3"}, +{"Right Input Mixer", "AUX Switch", "AUX"}, +{"Right Input Mixer", "Input PGA Switch", "Right Input PGA"}, + +{"ADCL", NULL, "Left Input Mixer"}, +{"ADCR", NULL, "Right Input Mixer"}, + +/* Outputs */ +{"LINEOUT1L", NULL, "LINEOUT1L PGA"}, +{"LINEOUT1L PGA", NULL, "Left Output Mixer"}, +{"LINEOUT1R", NULL, "LINEOUT1R PGA"}, +{"LINEOUT1R PGA", NULL, "Right Output Mixer"}, + +{"LINEOUT2L PGA", NULL, "Left Output Mixer"}, +{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"}, +{"LINEOUT2 LP", "Enabled", "Left Output Mixer"}, +{"LINEOUT2L", NULL, "LINEOUT2 LP"}, + +{"LINEOUT2R PGA", NULL, "Right Output Mixer"}, +{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"}, +{"LINEOUT2 LP", "Enabled", "Right Output Mixer"}, +{"LINEOUT2R", NULL, "LINEOUT2 LP"}, + +{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"}, +{"Left Output Mixer", "AUX Bypass Switch", "AUX"}, +{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}, +{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"}, +{"Left Output Mixer", "DACL Switch", "DACL"}, + +{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"}, +{"Right Output Mixer", "AUX Bypass Switch", "AUX"}, +{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}, +{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"}, +{"Right Output Mixer", "DACR Switch", "DACR"}, + +/* Note that the headphone output stage needs to be connected + * externally to LINEOUT2 via DC blocking capacitors. Other + * configurations are not supported. + * + * Note also that left and right headphone paths are treated as a + * mono path. + */ +{"Headphone Amplifier", NULL, "LINEOUT2 LP"}, +{"Headphone Amplifier", NULL, "LINEOUT2 LP"}, +{"HP_L", NULL, "Headphone Amplifier"}, +{"HP_R", NULL, "Headphone Amplifier"}, +}; + +static int wm8900_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets, + ARRAY_SIZE(wm8900_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int wm8900_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 reg; + + reg = wm8900_read(codec, WM8900_REG_AUDIO1) & ~0x60; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg |= 0x20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg |= 0x40; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg |= 0x60; + break; + default: + return -EINVAL; + } + + wm8900_write(codec, WM8900_REG_AUDIO1, reg); + + return 0; +} + +/* FLL divisors */ +struct _fll_div { + u16 fll_ratio; + u16 fllclk_div; + u16 fll_slow_lock_ref; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + + BUG_ON(!Fout); + + /* The FLL must run at 90-100MHz which is then scaled down to + * the output value by FLLCLK_DIV. */ + target = Fout; + div = 1; + while (target < 90000000) { + div *= 2; + target *= 2; + } + + if (target > 100000000) + printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d" + " Fout=%d\n", target, Fref, Fout); + if (div > 32) { + printk(KERN_ERR "wm8900: Invalid FLL division rate %u, " + "Fref=%d, Fout=%d, target=%d\n", + div, Fref, Fout, target); + return -EINVAL; + } + + fll_div->fllclk_div = div >> 2; + + if (Fref < 48000) + fll_div->fll_slow_lock_ref = 1; + else + fll_div->fll_slow_lock_ref = 0; + + Ndiv = target / Fref; + + if (Fref < 1000000) + fll_div->fll_ratio = 8; + else + fll_div->fll_ratio = 1; + + fll_div->n = Ndiv / fll_div->fll_ratio; + Nmod = (target / fll_div->fll_ratio) % Fref; + + /* 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 */ + fll_div->k = K / 10; + + BUG_ON(target != Fout * (fll_div->fllclk_div << 2)); + BUG_ON(!K && target != Fref * fll_div->fll_ratio * fll_div->n); + + return 0; +} + +static int wm8900_set_fll(struct snd_soc_codec *codec, + int fll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct wm8900_priv *wm8900 = codec->private_data; + struct _fll_div fll_div; + unsigned int reg; + + if (wm8900->fll_in == freq_in && wm8900->fll_out == freq_out) + return 0; + + /* The digital side should be disabled during any change. */ + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg & (~WM8900_REG_POWER1_FLL_ENA)); + + /* Disable the FLL? */ + if (!freq_in || !freq_out) { + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + reg & (~WM8900_REG_CLOCKING1_MCLK_SRC)); + + reg = wm8900_read(codec, WM8900_REG_FLLCTL1); + wm8900_write(codec, WM8900_REG_FLLCTL1, + reg & (~WM8900_REG_FLLCTL1_OSC_ENA)); + + wm8900->fll_in = freq_in; + wm8900->fll_out = freq_out; + + return 0; + } + + if (fll_factors(&fll_div, freq_in, freq_out) != 0) + goto reenable; + + wm8900->fll_in = freq_in; + wm8900->fll_out = freq_out; + + /* The osclilator *MUST* be enabled before we enable the + * digital circuit. */ + wm8900_write(codec, WM8900_REG_FLLCTL1, + fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA); + + wm8900_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5); + wm8900_write(codec, WM8900_REG_FLLCTL5, + (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f)); + + if (fll_div.k) { + wm8900_write(codec, WM8900_REG_FLLCTL2, + (fll_div.k >> 8) | 0x100); + wm8900_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff); + } else + wm8900_write(codec, WM8900_REG_FLLCTL2, 0); + + if (fll_div.fll_slow_lock_ref) + wm8900_write(codec, WM8900_REG_FLLCTL6, + WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF); + else + wm8900_write(codec, WM8900_REG_FLLCTL6, 0); + + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg | WM8900_REG_POWER1_FLL_ENA); + +reenable: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + reg | WM8900_REG_CLOCKING1_MCLK_SRC); + + return 0; +} + +static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + return wm8900_set_fll(codec_dai->codec, pll_id, freq_in, freq_out); +} + +static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + switch (div_id) { + case WM8900_BCLK_DIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK)); + break; + case WM8900_OPCLK_DIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK)); + break; + case WM8900_DAC_LRCLK: + reg = wm8900_read(codec, WM8900_REG_AUDIO4); + wm8900_write(codec, WM8900_REG_AUDIO4, + div | (reg & WM8900_LRC_MASK)); + break; + case WM8900_ADC_LRCLK: + reg = wm8900_read(codec, WM8900_REG_AUDIO3); + wm8900_write(codec, WM8900_REG_AUDIO3, + div | (reg & WM8900_LRC_MASK)); + break; + case WM8900_DAC_CLKDIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING2); + wm8900_write(codec, WM8900_REG_CLOCKING2, + div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV)); + break; + case WM8900_ADC_CLKDIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING2); + wm8900_write(codec, WM8900_REG_CLOCKING2, + div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV)); + break; + case WM8900_LRCLK_MODE: + reg = wm8900_read(codec, WM8900_REG_DACCTRL); + wm8900_write(codec, WM8900_REG_DACCTRL, + div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE)); + break; + default: + return -EINVAL; + } + + return 0; +} + + +static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int clocking1, aif1, aif3, aif4; + + clocking1 = wm8900_read(codec, WM8900_REG_CLOCKING1); + aif1 = wm8900_read(codec, WM8900_REG_AUDIO1); + aif3 = wm8900_read(codec, WM8900_REG_AUDIO3); + aif4 = wm8900_read(codec, WM8900_REG_AUDIO4); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_I2S: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= 0x8; + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + 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: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8900_REG_AUDIO1_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: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8900_write(codec, WM8900_REG_CLOCKING1, clocking1); + wm8900_write(codec, WM8900_REG_AUDIO1, aif1); + wm8900_write(codec, WM8900_REG_AUDIO3, aif3); + wm8900_write(codec, WM8900_REG_AUDIO4, aif4); + + return 0; +} + +static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + reg = wm8900_read(codec, WM8900_REG_DACCTRL); + + if (mute) + reg |= WM8900_REG_DACCTRL_MUTE; + else + reg &= ~WM8900_REG_DACCTRL_MUTE; + + wm8900_write(codec, WM8900_REG_DACCTRL, reg); + + return 0; +} + +#define WM8900_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8900_PCM_FORMATS \ + (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_dai wm8900_dai = { + .name = "WM8900 HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8900_RATES, + .formats = WM8900_PCM_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8900_RATES, + .formats = WM8900_PCM_FORMATS, + }, + .ops = { + .hw_params = wm8900_hw_params, + }, + .dai_ops = { + .set_clkdiv = wm8900_set_dai_clkdiv, + .set_pll = wm8900_set_dai_pll, + .set_fmt = wm8900_set_dai_fmt, + .digital_mute = wm8900_digital_mute, + }, +}; +EXPORT_SYMBOL_GPL(wm8900_dai); + +static int wm8900_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + /* Enable thermal shutdown */ + reg = wm8900_read(codec, WM8900_REG_GPIO); + wm8900_write(codec, WM8900_REG_GPIO, + reg | WM8900_REG_GPIO_TEMP_ENA); + reg = wm8900_read(codec, WM8900_REG_ADDCTL); + wm8900_write(codec, WM8900_REG_ADDCTL, + reg | WM8900_REG_ADDCTL_TEMP_SD); + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + /* Charge capacitors if initial power up */ + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* STARTUP_BIAS_ENA on */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); + + /* Startup bias mode */ + wm8900_write(codec, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_BIAS_SRC | + WM8900_REG_ADDCTL_VMID_SOFTST); + + /* VMID 2x50k */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1); + + /* Allow capacitors to charge */ + schedule_timeout_interruptible(msecs_to_jiffies(400)); + + /* Enable bias */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | + WM8900_REG_POWER1_BIAS_ENA | 0x1); + + wm8900_write(codec, WM8900_REG_ADDCTL, 0); + + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_BIAS_ENA | 0x1); + } + + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + (reg & WM8900_REG_POWER1_FLL_ENA) | + WM8900_REG_POWER1_BIAS_ENA | 0x1); + wm8900_write(codec, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + wm8900_write(codec, WM8900_REG_POWER3, 0); + break; + + case SND_SOC_BIAS_OFF: + /* Startup bias enable */ + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA); + wm8900_write(codec, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_BIAS_SRC | + WM8900_REG_ADDCTL_VMID_SOFTST); + + /* Discharge caps */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); + schedule_timeout_interruptible(msecs_to_jiffies(500)); + + /* Remove clamp */ + wm8900_write(codec, WM8900_REG_HPCTL1, 0); + + /* Power down */ + wm8900_write(codec, WM8900_REG_ADDCTL, 0); + wm8900_write(codec, WM8900_REG_POWER1, 0); + wm8900_write(codec, WM8900_REG_POWER2, 0); + wm8900_write(codec, WM8900_REG_POWER3, 0); + + /* Need to let things settle before stopping the clock + * to ensure that restart works, see "Stopping the + * master clock" in the datasheet. */ + schedule_timeout_interruptible(msecs_to_jiffies(1)); + wm8900_write(codec, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + break; + } + codec->bias_level = level; + return 0; +} + +static int wm8900_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm8900_priv *wm8900 = codec->private_data; + int fll_out = wm8900->fll_out; + int fll_in = wm8900->fll_in; + int ret; + + /* Stop the FLL in an orderly fashion */ + ret = wm8900_set_fll(codec, 0, 0, 0); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to stop FLL\n"); + return ret; + } + + wm8900->fll_out = fll_out; + wm8900->fll_in = fll_in; + + wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8900_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm8900_priv *wm8900 = codec->private_data; + u16 *cache; + int i, ret; + + cache = kmemdup(codec->reg_cache, sizeof(wm8900_reg_defaults), + GFP_KERNEL); + + wm8900_reset(codec); + wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Restart the FLL? */ + if (wm8900->fll_out) { + int fll_out = wm8900->fll_out; + int fll_in = wm8900->fll_in; + + wm8900->fll_in = 0; + wm8900->fll_out = 0; + + ret = wm8900_set_fll(codec, 0, fll_in, fll_out); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to restart FLL\n"); + return ret; + } + } + + if (cache) { + for (i = 0; i < WM8900_MAXREG; i++) + wm8900_write(codec, i, cache[i]); + kfree(cache); + } else + dev_err(&pdev->dev, "Unable to allocate register cache\n"); + + return 0; +} + +/* + * initialise the WM8900 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8900_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + unsigned int reg; + struct i2c_client *i2c_client = socdev->codec->control_data; + + codec->name = "WM8900"; + codec->owner = THIS_MODULE; + codec->read = wm8900_read; + codec->write = wm8900_write; + codec->dai = &wm8900_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8900_MAXREG; + codec->reg_cache = kmemdup(wm8900_reg_defaults, + sizeof(wm8900_reg_defaults), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + reg = wm8900_read(codec, WM8900_REG_ID); + if (reg != 0x8900) { + dev_err(&i2c_client->dev, "Device is not a WM8900 - ID %x\n", + reg); + return -ENODEV; + } + + codec->private_data = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL); + if (codec->private_data == NULL) { + ret = -ENOMEM; + goto priv_err; + } + + /* Read back from the chip */ + reg = wm8900_chip_read(codec, WM8900_REG_POWER1); + reg = (reg >> 12) & 0xf; + dev_info(&i2c_client->dev, "WM8900 revision %d\n", reg); + + wm8900_reset(codec); + + /* Latch the volume update bits */ + wm8900_write(codec, WM8900_REG_LINVOL, + wm8900_read(codec, WM8900_REG_LINVOL) | 0x100); + wm8900_write(codec, WM8900_REG_RINVOL, + wm8900_read(codec, WM8900_REG_RINVOL) | 0x100); + wm8900_write(codec, WM8900_REG_LOUT1CTL, + wm8900_read(codec, WM8900_REG_LOUT1CTL) | 0x100); + wm8900_write(codec, WM8900_REG_ROUT1CTL, + wm8900_read(codec, WM8900_REG_ROUT1CTL) | 0x100); + wm8900_write(codec, WM8900_REG_LOUT2CTL, + wm8900_read(codec, WM8900_REG_LOUT2CTL) | 0x100); + wm8900_write(codec, WM8900_REG_ROUT2CTL, + wm8900_read(codec, WM8900_REG_ROUT2CTL) | 0x100); + wm8900_write(codec, WM8900_REG_LDAC_DV, + wm8900_read(codec, WM8900_REG_LDAC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_RDAC_DV, + wm8900_read(codec, WM8900_REG_RDAC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_LADC_DV, + wm8900_read(codec, WM8900_REG_LADC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_RADC_DV, + wm8900_read(codec, WM8900_REG_RADC_DV) | 0x100); + + /* Set the DAC and mixer output bias */ + wm8900_write(codec, WM8900_REG_OUTBIASCTL, 0x81); + + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to register new PCMs\n"); + goto pcm_err; + } + + /* Turn the chip on */ + codec->bias_level = SND_SOC_BIAS_OFF; + wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8900_add_controls(codec); + wm8900_add_widgets(codec); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); +priv_err: + kfree(codec->private_data); + return ret; +} + +static struct snd_soc_device *wm8900_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8900_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static int wm8900_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8900_socdev; + struct wm8900_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + dev_err(&adap->dev, "Probe on %x\n", addr); + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + dev_err(&adap->dev, + "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8900_init(socdev); + if (ret < 0) { + dev_err(&adap->dev, "failed to initialise WM8900\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8900_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8900_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8900_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8900_i2c_driver = { + .driver = { + .name = "WM8900 I2C codec", + .owner = THIS_MODULE, + }, + .attach_adapter = wm8900_i2c_attach, + .detach_client = wm8900_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8900", + .driver = &wm8900_i2c_driver, +}; +#endif + +static int wm8900_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8900_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + dev_info(&pdev->dev, "WM8900 Audio Codec\n"); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + socdev->codec = codec; + + codec->set_bias_level = wm8900_set_bias_level; + + wm8900_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8900_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else +#error Non-I2C interfaces not yet supported +#endif + return ret; +} + +/* power down chip */ +static int wm8900_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8900_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8900 = { + .probe = wm8900_probe, + .remove = wm8900_remove, + .suspend = wm8900_suspend, + .resume = wm8900_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8900); + +MODULE_DESCRIPTION("ASoC WM8900 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8900.h b/sound/soc/codecs/wm8900.h new file mode 100644 index 0000000..ba450d9 --- /dev/null +++ b/sound/soc/codecs/wm8900.h @@ -0,0 +1,64 @@ +/* + * wm8900.h -- WM890 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 _WM8900_H +#define _WM8900_H + +#define WM8900_FLL 1 + +#define WM8900_BCLK_DIV 1 +#define WM8900_ADC_CLKDIV 2 +#define WM8900_DAC_CLKDIV 3 +#define WM8900_ADC_LRCLK 4 +#define WM8900_DAC_LRCLK 5 +#define WM8900_OPCLK_DIV 6 +#define WM8900_LRCLK_MODE 7 + +#define WM8900_BCLK_DIV_1 0x00 +#define WM8900_BCLK_DIV_1_5 0x02 +#define WM8900_BCLK_DIV_2 0x04 +#define WM8900_BCLK_DIV_3 0x06 +#define WM8900_BCLK_DIV_4 0x08 +#define WM8900_BCLK_DIV_5_5 0x0a +#define WM8900_BCLK_DIV_6 0x0c +#define WM8900_BCLK_DIV_8 0x0e +#define WM8900_BCLK_DIV_11 0x10 +#define WM8900_BCLK_DIV_12 0x12 +#define WM8900_BCLK_DIV_16 0x14 +#define WM8900_BCLK_DIV_22 0x16 +#define WM8900_BCLK_DIV_24 0x18 +#define WM8900_BCLK_DIV_32 0x1a +#define WM8900_BCLK_DIV_44 0x1c +#define WM8900_BCLK_DIV_48 0x1e + +#define WM8900_ADC_CLKDIV_1 0x00 +#define WM8900_ADC_CLKDIV_1_5 0x20 +#define WM8900_ADC_CLKDIV_2 0x40 +#define WM8900_ADC_CLKDIV_3 0x60 +#define WM8900_ADC_CLKDIV_4 0x80 +#define WM8900_ADC_CLKDIV_5_5 0xa0 +#define WM8900_ADC_CLKDIV_6 0xc0 + +#define WM8900_DAC_CLKDIV_1 0x00 +#define WM8900_DAC_CLKDIV_1_5 0x04 +#define WM8900_DAC_CLKDIV_2 0x08 +#define WM8900_DAC_CLKDIV_3 0x0c +#define WM8900_DAC_CLKDIV_4 0x10 +#define WM8900_DAC_CLKDIV_5_5 0x14 +#define WM8900_DAC_CLKDIV_6 0x18 + +#define WM8900_ + +struct wm8900_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8900_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8900; + +#endif diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c new file mode 100644 index 0000000..ce40d78 --- /dev/null +++ b/sound/soc/codecs/wm8903.c @@ -0,0 +1,1813 @@ +/* + * wm8903.c -- WM8903 ALSA SoC Audio driver + * + * Copyright 2008 Wolfson Microelectronics + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - TDM mode configuration. + * - Mic detect. + * - Digital microphone support. + * - Interrupt support (mic detect and sequencer). + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8903.h" + +struct wm8903_priv { + int sysclk; + + /* Reference counts */ + int charge_pump_users; + int class_w_users; + int playback_active; + int capture_active; + + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +/* Register defaults at reset */ +static u16 wm8903_reg_defaults[] = { + 0x8903, /* R0 - SW Reset and ID */ + 0x0000, /* R1 - Revision Number */ + 0x0000, /* R2 */ + 0x0000, /* R3 */ + 0x0018, /* R4 - Bias Control 0 */ + 0x0000, /* R5 - VMID Control 0 */ + 0x0000, /* R6 - Mic Bias Control 0 */ + 0x0000, /* R7 */ + 0x0001, /* R8 - Analogue DAC 0 */ + 0x0000, /* R9 */ + 0x0001, /* R10 - Analogue ADC 0 */ + 0x0000, /* R11 */ + 0x0000, /* R12 - Power Management 0 */ + 0x0000, /* R13 - Power Management 1 */ + 0x0000, /* R14 - Power Management 2 */ + 0x0000, /* R15 - Power Management 3 */ + 0x0000, /* R16 - Power Management 4 */ + 0x0000, /* R17 - Power Management 5 */ + 0x0000, /* R18 - Power Management 6 */ + 0x0000, /* R19 */ + 0x0400, /* R20 - Clock Rates 0 */ + 0x0D07, /* R21 - Clock Rates 1 */ + 0x0000, /* R22 - Clock Rates 2 */ + 0x0000, /* R23 */ + 0x0050, /* R24 - Audio Interface 0 */ + 0x0242, /* R25 - Audio Interface 1 */ + 0x0008, /* R26 - Audio Interface 2 */ + 0x0022, /* R27 - Audio Interface 3 */ + 0x0000, /* R28 */ + 0x0000, /* R29 */ + 0x00C0, /* R30 - DAC Digital Volume Left */ + 0x00C0, /* R31 - DAC Digital Volume Right */ + 0x0000, /* R32 - DAC Digital 0 */ + 0x0000, /* R33 - DAC Digital 1 */ + 0x0000, /* R34 */ + 0x0000, /* R35 */ + 0x00C0, /* R36 - ADC Digital Volume Left */ + 0x00C0, /* R37 - ADC Digital Volume Right */ + 0x0000, /* R38 - ADC Digital 0 */ + 0x0073, /* R39 - Digital Microphone 0 */ + 0x09BF, /* R40 - DRC 0 */ + 0x3241, /* R41 - DRC 1 */ + 0x0020, /* R42 - DRC 2 */ + 0x0000, /* R43 - DRC 3 */ + 0x0085, /* R44 - Analogue Left Input 0 */ + 0x0085, /* R45 - Analogue Right Input 0 */ + 0x0044, /* R46 - Analogue Left Input 1 */ + 0x0044, /* R47 - Analogue Right Input 1 */ + 0x0000, /* R48 */ + 0x0000, /* R49 */ + 0x0008, /* R50 - Analogue Left Mix 0 */ + 0x0004, /* R51 - Analogue Right Mix 0 */ + 0x0000, /* R52 - Analogue Spk Mix Left 0 */ + 0x0000, /* R53 - Analogue Spk Mix Left 1 */ + 0x0000, /* R54 - Analogue Spk Mix Right 0 */ + 0x0000, /* R55 - Analogue Spk Mix Right 1 */ + 0x0000, /* R56 */ + 0x002D, /* R57 - Analogue OUT1 Left */ + 0x002D, /* R58 - Analogue OUT1 Right */ + 0x0039, /* R59 - Analogue OUT2 Left */ + 0x0039, /* R60 - Analogue OUT2 Right */ + 0x0100, /* R61 */ + 0x0139, /* R62 - Analogue OUT3 Left */ + 0x0139, /* R63 - Analogue OUT3 Right */ + 0x0000, /* R64 */ + 0x0000, /* R65 - Analogue SPK Output Control 0 */ + 0x0000, /* R66 */ + 0x0010, /* R67 - DC Servo 0 */ + 0x0100, /* R68 */ + 0x00A4, /* R69 - DC Servo 2 */ + 0x0807, /* R70 */ + 0x0000, /* R71 */ + 0x0000, /* R72 */ + 0x0000, /* R73 */ + 0x0000, /* R74 */ + 0x0000, /* R75 */ + 0x0000, /* R76 */ + 0x0000, /* R77 */ + 0x0000, /* R78 */ + 0x000E, /* R79 */ + 0x0000, /* R80 */ + 0x0000, /* R81 */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0006, /* R87 */ + 0x0000, /* R88 */ + 0x0000, /* R89 */ + 0x0000, /* R90 - Analogue HP 0 */ + 0x0060, /* R91 */ + 0x0000, /* R92 */ + 0x0000, /* R93 */ + 0x0000, /* R94 - Analogue Lineout 0 */ + 0x0060, /* R95 */ + 0x0000, /* R96 */ + 0x0000, /* R97 */ + 0x0000, /* R98 - Charge Pump 0 */ + 0x1F25, /* R99 */ + 0x2B19, /* R100 */ + 0x01C0, /* R101 */ + 0x01EF, /* R102 */ + 0x2B00, /* R103 */ + 0x0000, /* R104 - Class W 0 */ + 0x01C0, /* R105 */ + 0x1C10, /* R106 */ + 0x0000, /* R107 */ + 0x0000, /* R108 - Write Sequencer 0 */ + 0x0000, /* R109 - Write Sequencer 1 */ + 0x0000, /* R110 - Write Sequencer 2 */ + 0x0000, /* R111 - Write Sequencer 3 */ + 0x0000, /* R112 - Write Sequencer 4 */ + 0x0000, /* R113 */ + 0x0000, /* R114 - Control Interface */ + 0x0000, /* R115 */ + 0x00A8, /* R116 - GPIO Control 1 */ + 0x00A8, /* R117 - GPIO Control 2 */ + 0x00A8, /* R118 - GPIO Control 3 */ + 0x0220, /* R119 - GPIO Control 4 */ + 0x01A0, /* R120 - GPIO Control 5 */ + 0x0000, /* R121 - Interrupt Status 1 */ + 0xFFFF, /* R122 - Interrupt Status 1 Mask */ + 0x0000, /* R123 - Interrupt Polarity 1 */ + 0x0000, /* R124 */ + 0x0003, /* R125 */ + 0x0000, /* R126 - Interrupt Control */ + 0x0000, /* R127 */ + 0x0005, /* R128 */ + 0x0000, /* R129 - Control Interface Test 1 */ + 0x0000, /* R130 */ + 0x0000, /* R131 */ + 0x0000, /* R132 */ + 0x0000, /* R133 */ + 0x0000, /* R134 */ + 0x03FF, /* R135 */ + 0x0007, /* R136 */ + 0x0040, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0000, /* R140 */ + 0x0000, /* R141 */ + 0x0000, /* R142 */ + 0x0000, /* R143 */ + 0x0000, /* R144 */ + 0x0000, /* R145 */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x4000, /* R148 */ + 0x6810, /* R149 - Charge Pump Test 1 */ + 0x0004, /* R150 */ + 0x0000, /* R151 */ + 0x0000, /* R152 */ + 0x0000, /* R153 */ + 0x0000, /* R154 */ + 0x0000, /* R155 */ + 0x0000, /* R156 */ + 0x0000, /* R157 */ + 0x0000, /* R158 */ + 0x0000, /* R159 */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 */ + 0x0028, /* R164 - Clock Rate Test 4 */ + 0x0004, /* R165 */ + 0x0000, /* R166 */ + 0x0060, /* R167 */ + 0x0000, /* R168 */ + 0x0000, /* R169 */ + 0x0000, /* R170 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Analogue Output Bias 0 */ +}; + +static unsigned int wm8903_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults)); + + return cache[reg]; +} + +static unsigned int wm8903_hw_read(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + pr_err("i2c_transfer returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +static unsigned int wm8903_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case WM8903_SW_RESET_AND_ID: + case WM8903_REVISION_NUMBER: + case WM8903_INTERRUPT_STATUS_1: + case WM8903_WRITE_SEQUENCER_4: + return wm8903_hw_read(codec, reg); + + default: + return wm8903_read_reg_cache(codec, reg); + } +} + +static void wm8903_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults)); + + switch (reg) { + case WM8903_SW_RESET_AND_ID: + case WM8903_REVISION_NUMBER: + break; + + default: + cache[reg] = value; + break; + } +} + +static int wm8903_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + wm8903_write_reg_cache(codec, reg, value); + + /* Data format is 1 byte of address followed by 2 bytes of data */ + data[0] = reg; + data[1] = (value >> 8) & 0xff; + data[2] = value & 0xff; + + if (codec->hw_write(codec->control_data, data, 3) == 2) + return 0; + else + return -EIO; +} + +static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) +{ + u16 reg[5]; + struct i2c_client *i2c = codec->control_data; + + BUG_ON(start > 48); + + /* Enable the sequencer */ + reg[0] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_0); + reg[0] |= WM8903_WSEQ_ENA; + wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); + + dev_dbg(&i2c->dev, "Starting sequence at %d\n", start); + + wm8903_write(codec, WM8903_WRITE_SEQUENCER_3, + start | WM8903_WSEQ_START); + + /* Wait for it to complete. If we have the interrupt wired up then + * we could block waiting for an interrupt, though polling may still + * be desirable for diagnostic purposes. + */ + do { + msleep(10); + + reg[4] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_4); + } while (reg[4] & WM8903_WSEQ_BUSY); + + dev_dbg(&i2c->dev, "Sequence complete\n"); + + /* Disable the sequencer again */ + wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, + reg[0] & ~WM8903_WSEQ_ENA); + + return 0; +} + +static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache) +{ + int i; + + /* There really ought to be something better we can do here :/ */ + for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++) + cache[i] = wm8903_hw_read(codec, i); +} + +static void wm8903_reset(struct snd_soc_codec *codec) +{ + wm8903_write(codec, WM8903_SW_RESET_AND_ID, 0); +} + +#define WM8903_OUTPUT_SHORT 0x8 +#define WM8903_OUTPUT_OUT 0x4 +#define WM8903_OUTPUT_INT 0x2 +#define WM8903_OUTPUT_IN 0x1 + +/* + * Event for headphone and line out amplifier power changes. Special + * power up/down sequences are required in order to maximise pop/click + * performance. + */ +static int wm8903_output_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + u16 val; + u16 reg; + int shift; + u16 cp_reg = wm8903_read(codec, WM8903_CHARGE_PUMP_0); + + switch (w->reg) { + case WM8903_POWER_MANAGEMENT_2: + reg = WM8903_ANALOGUE_HP_0; + break; + case WM8903_POWER_MANAGEMENT_3: + reg = WM8903_ANALOGUE_LINEOUT_0; + break; + default: + BUG(); + } + + switch (w->shift) { + case 0: + shift = 0; + break; + case 1: + shift = 4; + break; + default: + BUG(); + } + + if (event & SND_SOC_DAPM_PRE_PMU) { + val = wm8903_read(codec, reg); + + /* Short the output */ + val &= ~(WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + + wm8903->charge_pump_users++; + + dev_dbg(&i2c->dev, "Charge pump use count now %d\n", + wm8903->charge_pump_users); + + if (wm8903->charge_pump_users == 1) { + dev_dbg(&i2c->dev, "Enabling charge pump\n"); + wm8903_write(codec, WM8903_CHARGE_PUMP_0, + cp_reg | WM8903_CP_ENA); + mdelay(4); + } + } + + if (event & SND_SOC_DAPM_POST_PMU) { + val = wm8903_read(codec, reg); + + val |= (WM8903_OUTPUT_IN << shift); + wm8903_write(codec, reg, val); + + val |= (WM8903_OUTPUT_INT << shift); + wm8903_write(codec, reg, val); + + /* Turn on the output ENA_OUTP */ + val |= (WM8903_OUTPUT_OUT << shift); + wm8903_write(codec, reg, val); + + /* Remove the short */ + val |= (WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + val = wm8903_read(codec, reg); + + /* Short the output */ + val &= ~(WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + + /* Then disable the intermediate and output stages */ + val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | + WM8903_OUTPUT_IN) << shift); + wm8903_write(codec, reg, val); + } + + if (event & SND_SOC_DAPM_POST_PMD) { + wm8903->charge_pump_users--; + + dev_dbg(&i2c->dev, "Charge pump use count now %d\n", + wm8903->charge_pump_users); + + if (wm8903->charge_pump_users == 0) { + dev_dbg(&i2c->dev, "Disabling charge pump\n"); + wm8903_write(codec, WM8903_CHARGE_PUMP_0, + cp_reg & ~WM8903_CP_ENA); + } + } + + return 0; +} + +/* + * When used with DAC outputs only the WM8903 charge pump supports + * operation in class W mode, providing very low power consumption + * when used with digital sources. Enable and disable this mode + * automatically depending on the mixer configuration. + * + * All the relevant controls are simple switches. + */ +static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = widget->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + u16 reg; + int ret; + + reg = wm8903_read(codec, WM8903_CLASS_W_0); + + /* Turn it off if we're about to enable bypass */ + if (ucontrol->value.integer.value[0]) { + if (wm8903->class_w_users == 0) { + dev_dbg(&i2c->dev, "Disabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg & + ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V)); + } + wm8903->class_w_users++; + } + + /* Implement the change */ + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + /* If we've just disabled the last bypass path turn Class W on */ + if (!ucontrol->value.integer.value[0]) { + if (wm8903->class_w_users == 1) { + dev_dbg(&i2c->dev, "Enabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg | + WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); + } + wm8903->class_w_users--; + } + + dev_dbg(&i2c->dev, "Bypass use count now %d\n", + wm8903->class_w_users); + + return ret; +} + +#define SOC_DAPM_SINGLE_W(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 = wm8903_class_w_put, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + + +/* ALSA can only do steps of .01dB */ +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); + +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); + +static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_amp, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_min, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_max, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_startup, -300, 50, 0); + +static const char *drc_slope_text[] = { + "1", "1/2", "1/4", "1/8", "1/16", "0" +}; + +static const struct soc_enum drc_slope_r0 = + SOC_ENUM_SINGLE(WM8903_DRC_2, 3, 6, drc_slope_text); + +static const struct soc_enum drc_slope_r1 = + SOC_ENUM_SINGLE(WM8903_DRC_2, 0, 6, drc_slope_text); + +static const char *drc_attack_text[] = { + "instantaneous", + "363us", "762us", "1.45ms", "2.9ms", "5.8ms", "11.6ms", "23.2ms", + "46.4ms", "92.8ms", "185.6ms" +}; + +static const struct soc_enum drc_attack = + SOC_ENUM_SINGLE(WM8903_DRC_1, 12, 11, drc_attack_text); + +static const char *drc_decay_text[] = { + "186ms", "372ms", "743ms", "1.49s", "2.97s", "5.94s", "11.89s", + "23.87s", "47.56s" +}; + +static const struct soc_enum drc_decay = + SOC_ENUM_SINGLE(WM8903_DRC_1, 8, 9, drc_decay_text); + +static const char *drc_ff_delay_text[] = { + "5 samples", "9 samples" +}; + +static const struct soc_enum drc_ff_delay = + SOC_ENUM_SINGLE(WM8903_DRC_0, 5, 2, drc_ff_delay_text); + +static const char *drc_qr_decay_text[] = { + "0.725ms", "1.45ms", "5.8ms" +}; + +static const struct soc_enum drc_qr_decay = + SOC_ENUM_SINGLE(WM8903_DRC_1, 4, 3, drc_qr_decay_text); + +static const char *drc_smoothing_text[] = { + "Low", "Medium", "High" +}; + +static const struct soc_enum drc_smoothing = + SOC_ENUM_SINGLE(WM8903_DRC_0, 11, 3, drc_smoothing_text); + +static const char *soft_mute_text[] = { + "Fast (fs/2)", "Slow (fs/32)" +}; + +static const struct soc_enum soft_mute = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 10, 2, soft_mute_text); + +static const char *mute_mode_text[] = { + "Hard", "Soft" +}; + +static const struct soc_enum mute_mode = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 9, 2, mute_mode_text); + +static const char *dac_deemphasis_text[] = { + "Disabled", "32kHz", "44.1kHz", "48kHz" +}; + +static const struct soc_enum dac_deemphasis = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 1, 4, dac_deemphasis_text); + +static const char *companding_text[] = { + "ulaw", "alaw" +}; + +static const struct soc_enum dac_companding = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 0, 2, companding_text); + +static const struct soc_enum adc_companding = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 2, 2, companding_text); + +static const char *input_mode_text[] = { + "Single-Ended", "Differential Line", "Differential Mic" +}; + +static const struct soc_enum linput_mode_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text); + +static const struct soc_enum rinput_mode_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text); + +static const char *linput_mux_text[] = { + "IN1L", "IN2L", "IN3L" +}; + +static const struct soc_enum linput_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 2, 3, linput_mux_text); + +static const struct soc_enum linput_inv_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 4, 3, linput_mux_text); + +static const char *rinput_mux_text[] = { + "IN1R", "IN2R", "IN3R" +}; + +static const struct soc_enum rinput_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 2, 3, rinput_mux_text); + +static const struct soc_enum rinput_inv_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text); + + +static const struct snd_kcontrol_new wm8903_snd_controls[] = { + +/* Input PGAs - No TLV since the scale depends on PGA mode */ +SOC_SINGLE("Left Input PGA Switch", WM8903_ANALOGUE_LEFT_INPUT_0, + 7, 1, 1), +SOC_SINGLE("Left Input PGA Volume", WM8903_ANALOGUE_LEFT_INPUT_0, + 0, 31, 0), +SOC_SINGLE("Left Input PGA Common Mode Switch", WM8903_ANALOGUE_LEFT_INPUT_1, + 6, 1, 0), + +SOC_SINGLE("Right Input PGA Switch", WM8903_ANALOGUE_RIGHT_INPUT_0, + 7, 1, 1), +SOC_SINGLE("Right Input PGA Volume", WM8903_ANALOGUE_RIGHT_INPUT_0, + 0, 31, 0), +SOC_SINGLE("Right Input PGA Common Mode Switch", WM8903_ANALOGUE_RIGHT_INPUT_1, + 6, 1, 0), + +/* ADCs */ +SOC_SINGLE("DRC Switch", WM8903_DRC_0, 15, 1, 0), +SOC_ENUM("DRC Compressor Slope R0", drc_slope_r0), +SOC_ENUM("DRC Compressor Slope R1", drc_slope_r1), +SOC_SINGLE_TLV("DRC Compressor Threashold Volume", WM8903_DRC_3, 5, 124, 1, + drc_tlv_thresh), +SOC_SINGLE_TLV("DRC Volume", WM8903_DRC_3, 0, 30, 1, drc_tlv_amp), +SOC_SINGLE_TLV("DRC Minimum Gain Volume", WM8903_DRC_1, 2, 3, 1, drc_tlv_min), +SOC_SINGLE_TLV("DRC Maximum Gain Volume", WM8903_DRC_1, 0, 3, 0, drc_tlv_max), +SOC_ENUM("DRC Attack Rate", drc_attack), +SOC_ENUM("DRC Decay Rate", drc_decay), +SOC_ENUM("DRC FF Delay", drc_ff_delay), +SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), +SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), +SOC_SINGLE_TLV("DRC QR Threashold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), +SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), +SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), +SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), +SOC_ENUM("DRC Smoothing Threashold", drc_smoothing), +SOC_SINGLE_TLV("DRC Startup Volume", WM8903_DRC_0, 6, 18, 0, drc_tlv_startup), + +SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT, + WM8903_ADC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv), +SOC_ENUM("ADC Companding Mode", adc_companding), +SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0), + +/* DAC */ +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT, + WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv), +SOC_ENUM("DAC Soft Mute Rate", soft_mute), +SOC_ENUM("DAC Mute Mode", mute_mode), +SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0), +SOC_ENUM("DAC De-emphasis", dac_deemphasis), +SOC_SINGLE("DAC Sloping Stopband Filter Switch", + WM8903_DAC_DIGITAL_1, 11, 1, 0), +SOC_ENUM("DAC Companding Mode", dac_companding), +SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0), + +/* Headphones */ +SOC_DOUBLE_R("Headphone Switch", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 8, 1, 1), +SOC_DOUBLE_R("Headphone ZC Switch", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R_TLV("Headphone Volume", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 0, 63, 0, out_tlv), + +/* Line out */ +SOC_DOUBLE_R("Line Out Switch", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 8, 1, 1), +SOC_DOUBLE_R("Line Out ZC Switch", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R_TLV("Line Out Volume", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 0, 63, 0, out_tlv), + +/* Speaker */ +SOC_DOUBLE_R("Speaker Switch", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 8, 1, 1), +SOC_DOUBLE_R("Speaker ZC Switch", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 6, 1, 0), +SOC_DOUBLE_R_TLV("Speaker Volume", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, + 0, 63, 0, out_tlv), +}; + +static int wm8903_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8903_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8903_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static const struct snd_kcontrol_new linput_mode_mux = + SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum); + +static const struct snd_kcontrol_new rinput_mode_mux = + SOC_DAPM_ENUM("Right Input Mode Mux", rinput_mode_enum); + +static const struct snd_kcontrol_new linput_mux = + SOC_DAPM_ENUM("Left Input Mux", linput_enum); + +static const struct snd_kcontrol_new linput_inv_mux = + SOC_DAPM_ENUM("Left Inverting Input Mux", linput_inv_enum); + +static const struct snd_kcontrol_new rinput_mux = + SOC_DAPM_ENUM("Right Input Mux", rinput_enum); + +static const struct snd_kcontrol_new rinput_inv_mux = + SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum); + +static const struct snd_kcontrol_new left_output_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), +SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0), +}; + +static const struct snd_kcontrol_new right_output_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 2, 1, 0), +SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0), +}; + +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 2, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 1, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, + 1, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 2, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, + 1, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, + 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +SND_SOC_DAPM_OUTPUT("LINEOUTL"), +SND_SOC_DAPM_OUTPUT("LINEOUTR"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8903_MIC_BIAS_CONTROL_0, 0, 0), + +SND_SOC_DAPM_MUX("Left Input Mux", SND_SOC_NOPM, 0, 0, &linput_mux), +SND_SOC_DAPM_MUX("Left Input Inverting Mux", SND_SOC_NOPM, 0, 0, + &linput_inv_mux), +SND_SOC_DAPM_MUX("Left Input Mode Mux", SND_SOC_NOPM, 0, 0, &linput_mode_mux), + +SND_SOC_DAPM_MUX("Right Input Mux", SND_SOC_NOPM, 0, 0, &rinput_mux), +SND_SOC_DAPM_MUX("Right Input Inverting Mux", SND_SOC_NOPM, 0, 0, + &rinput_inv_mux), +SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux), + +SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0), + +SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0, + left_output_mixer, ARRAY_SIZE(left_output_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8903_POWER_MANAGEMENT_1, 0, 0, + right_output_mixer, ARRAY_SIZE(right_output_mixer)), + +SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), + +SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, + 1, 0, NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, + 0, 0, NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0, + NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0, + NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, + NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, + NULL, 0), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + + { "Left Input Mux", "IN1L", "IN1L" }, + { "Left Input Mux", "IN2L", "IN2L" }, + { "Left Input Mux", "IN3L", "IN3L" }, + + { "Left Input Inverting Mux", "IN1L", "IN1L" }, + { "Left Input Inverting Mux", "IN2L", "IN2L" }, + { "Left Input Inverting Mux", "IN3L", "IN3L" }, + + { "Right Input Mux", "IN1R", "IN1R" }, + { "Right Input Mux", "IN2R", "IN2R" }, + { "Right Input Mux", "IN3R", "IN3R" }, + + { "Right Input Inverting Mux", "IN1R", "IN1R" }, + { "Right Input Inverting Mux", "IN2R", "IN2R" }, + { "Right Input Inverting Mux", "IN3R", "IN3R" }, + + { "Left Input Mode Mux", "Single-Ended", "Left Input Inverting Mux" }, + { "Left Input Mode Mux", "Differential Line", + "Left Input Mux" }, + { "Left Input Mode Mux", "Differential Line", + "Left Input Inverting Mux" }, + { "Left Input Mode Mux", "Differential Mic", + "Left Input Mux" }, + { "Left Input Mode Mux", "Differential Mic", + "Left Input Inverting Mux" }, + + { "Right Input Mode Mux", "Single-Ended", + "Right Input Inverting Mux" }, + { "Right Input Mode Mux", "Differential Line", + "Right Input Mux" }, + { "Right Input Mode Mux", "Differential Line", + "Right Input Inverting Mux" }, + { "Right Input Mode Mux", "Differential Mic", + "Right Input Mux" }, + { "Right Input Mode Mux", "Differential Mic", + "Right Input Inverting Mux" }, + + { "Left Input PGA", NULL, "Left Input Mode Mux" }, + { "Right Input PGA", NULL, "Right Input Mode Mux" }, + + { "ADCL", NULL, "Left Input PGA" }, + { "ADCR", NULL, "Right Input PGA" }, + + { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Left Output Mixer", "DACL Switch", "DACL" }, + { "Left Output Mixer", "DACR Switch", "DACR" }, + + { "Right Output Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Right Output Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Right Output Mixer", "DACL Switch", "DACL" }, + { "Right Output Mixer", "DACR Switch", "DACR" }, + + { "Left Speaker Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Left Speaker Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Left Speaker Mixer", "DACL Switch", "DACL" }, + { "Left Speaker Mixer", "DACR Switch", "DACR" }, + + { "Right Speaker Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Right Speaker Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Right Speaker Mixer", "DACL Switch", "DACL" }, + { "Right Speaker Mixer", "DACR Switch", "DACR" }, + + { "Left Line Output PGA", NULL, "Left Output Mixer" }, + { "Right Line Output PGA", NULL, "Right Output Mixer" }, + + { "Left Headphone Output PGA", NULL, "Left Output Mixer" }, + { "Right Headphone Output PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker PGA", NULL, "Left Speaker Mixer" }, + { "Right Speaker PGA", NULL, "Right Speaker Mixer" }, + + { "HPOUTL", NULL, "Left Headphone Output PGA" }, + { "HPOUTR", NULL, "Right Headphone Output PGA" }, + + { "LINEOUTL", NULL, "Left Line Output PGA" }, + { "LINEOUTR", NULL, "Right Line Output PGA" }, + + { "LOP", NULL, "Left Speaker PGA" }, + { "LON", NULL, "Left Speaker PGA" }, + + { "ROP", NULL, "Right Speaker PGA" }, + { "RON", NULL, "Right Speaker PGA" }, +}; + +static int wm8903_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8903_dapm_widgets, + ARRAY_SIZE(wm8903_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int wm8903_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct i2c_client *i2c = codec->control_data; + u16 reg, reg2; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + reg = wm8903_read(codec, WM8903_VMID_CONTROL_0); + reg &= ~(WM8903_VMID_RES_MASK); + reg |= WM8903_VMID_RES_50K; + wm8903_write(codec, WM8903_VMID_CONTROL_0, reg); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + wm8903_run_sequence(codec, 0); + wm8903_sync_reg_cache(codec, codec->reg_cache); + + /* Enable low impedence charge pump output */ + reg = wm8903_read(codec, + WM8903_CONTROL_INTERFACE_TEST_1); + wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, + reg | WM8903_TEST_KEY); + reg2 = wm8903_read(codec, WM8903_CHARGE_PUMP_TEST_1); + wm8903_write(codec, WM8903_CHARGE_PUMP_TEST_1, + reg2 | WM8903_CP_SW_KELVIN_MODE_MASK); + wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, + reg); + + /* By default no bypass paths are enabled so + * enable Class W support. + */ + dev_dbg(&i2c->dev, "Enabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg | + WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); + } + + reg = wm8903_read(codec, WM8903_VMID_CONTROL_0); + reg &= ~(WM8903_VMID_RES_MASK); + reg |= WM8903_VMID_RES_250K; + wm8903_write(codec, WM8903_VMID_CONTROL_0, reg); + break; + + case SND_SOC_BIAS_OFF: + wm8903_run_sequence(codec, 32); + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm8903_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 wm8903_priv *wm8903 = codec->private_data; + + wm8903->sysclk = freq; + + return 0; +} + +static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1); + + aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK | + WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif1 |= WM8903_LRCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif1 |= WM8903_BCLK_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x3; + break; + case SND_SOC_DAIFMT_DSP_B: + aif1 |= 0x3 | WM8903_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif1 |= 0x1; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + 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 |= WM8903_AIF_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 |= WM8903_AIF_BCLK_INV | WM8903_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8903_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8903_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); + + return 0; +} + +static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + reg = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + + if (mute) + reg |= WM8903_DAC_MUTE; + else + reg &= ~WM8903_DAC_MUTE; + + wm8903_write(codec, WM8903_DAC_DIGITAL_1, reg); + + return 0; +} + +/* Lookup table for CLK_SYS/fs ratio. 256fs or more is recommended + * for optimal performance so we list the lower rates first and match + * on the last match we find. */ +static struct { + int div; + int rate; + int mode; + int mclk_div; +} clk_sys_ratios[] = { + { 64, 0x0, 0x0, 1 }, + { 68, 0x0, 0x1, 1 }, + { 125, 0x0, 0x2, 1 }, + { 128, 0x1, 0x0, 1 }, + { 136, 0x1, 0x1, 1 }, + { 192, 0x2, 0x0, 1 }, + { 204, 0x2, 0x1, 1 }, + + { 64, 0x0, 0x0, 2 }, + { 68, 0x0, 0x1, 2 }, + { 125, 0x0, 0x2, 2 }, + { 128, 0x1, 0x0, 2 }, + { 136, 0x1, 0x1, 2 }, + { 192, 0x2, 0x0, 2 }, + { 204, 0x2, 0x1, 2 }, + + { 250, 0x2, 0x2, 1 }, + { 256, 0x3, 0x0, 1 }, + { 272, 0x3, 0x1, 1 }, + { 384, 0x4, 0x0, 1 }, + { 408, 0x4, 0x1, 1 }, + { 375, 0x4, 0x2, 1 }, + { 512, 0x5, 0x0, 1 }, + { 544, 0x5, 0x1, 1 }, + { 500, 0x5, 0x2, 1 }, + { 768, 0x6, 0x0, 1 }, + { 816, 0x6, 0x1, 1 }, + { 750, 0x6, 0x2, 1 }, + { 1024, 0x7, 0x0, 1 }, + { 1088, 0x7, 0x1, 1 }, + { 1000, 0x7, 0x2, 1 }, + { 1408, 0x8, 0x0, 1 }, + { 1496, 0x8, 0x1, 1 }, + { 1536, 0x9, 0x0, 1 }, + { 1632, 0x9, 0x1, 1 }, + { 1500, 0x9, 0x2, 1 }, + + { 250, 0x2, 0x2, 2 }, + { 256, 0x3, 0x0, 2 }, + { 272, 0x3, 0x1, 2 }, + { 384, 0x4, 0x0, 2 }, + { 408, 0x4, 0x1, 2 }, + { 375, 0x4, 0x2, 2 }, + { 512, 0x5, 0x0, 2 }, + { 544, 0x5, 0x1, 2 }, + { 500, 0x5, 0x2, 2 }, + { 768, 0x6, 0x0, 2 }, + { 816, 0x6, 0x1, 2 }, + { 750, 0x6, 0x2, 2 }, + { 1024, 0x7, 0x0, 2 }, + { 1088, 0x7, 0x1, 2 }, + { 1000, 0x7, 0x2, 2 }, + { 1408, 0x8, 0x0, 2 }, + { 1496, 0x8, 0x1, 2 }, + { 1536, 0x9, 0x0, 2 }, + { 1632, 0x9, 0x1, 2 }, + { 1500, 0x9, 0x2, 2 }, +}; + +/* CLK_SYS/BCLK ratios - multiplied by 10 due to .5s */ +static struct { + int ratio; + int div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 50, 5 }, + { 55, 6 }, + { 60, 7 }, + { 80, 8 }, + { 100, 9 }, + { 110, 10 }, + { 120, 11 }, + { 160, 12 }, + { 200, 13 }, + { 220, 14 }, + { 240, 15 }, + { 250, 16 }, + { 300, 17 }, + { 320, 18 }, + { 440, 19 }, + { 480, 20 }, +}; + +/* Sample rates for DSP */ +static struct { + int rate; + int value; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 88200, 9 }, + { 96000, 10 }, + { 0, 0 }, +}; + +static int wm8903_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + struct snd_pcm_runtime *master_runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + wm8903->playback_active++; + else + wm8903->capture_active++; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + */ + if (wm8903->master_substream) { + master_runtime = wm8903->master_substream->runtime; + + dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n", + master_runtime->sample_bits, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + wm8903->slave_substream = substream; + } else + wm8903->master_substream = substream; + + return 0; +} + +static void wm8903_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8903_priv *wm8903 = codec->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + wm8903->playback_active--; + else + wm8903->capture_active--; + + if (wm8903->master_substream == substream) + wm8903->master_substream = wm8903->slave_substream; + + wm8903->slave_substream = NULL; +} + +static int wm8903_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + int fs = params_rate(params); + int bclk; + int bclk_div; + int i; + int dsp_config; + int clk_config; + int best_val; + int cur_val; + int clk_sys; + + u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1); + u16 aif2 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_2); + u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3); + u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0); + u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1); + + if (substream == wm8903->slave_substream) { + dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n"); + return 0; + } + + /* Configure sample rate logic for DSP - choose nearest rate */ + dsp_config = 0; + best_val = abs(sample_rates[dsp_config].rate - fs); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + cur_val = abs(sample_rates[i].rate - fs); + if (cur_val <= best_val) { + dsp_config = i; + best_val = cur_val; + } + } + + /* Constraints should stop us hitting this but let's make sure */ + if (wm8903->capture_active) + switch (sample_rates[dsp_config].rate) { + case 88200: + case 96000: + dev_err(&i2c->dev, "%dHz unsupported by ADC\n", + fs); + return -EINVAL; + + default: + break; + } + + dev_dbg(&i2c->dev, "DSP fs = %dHz\n", sample_rates[dsp_config].rate); + clock1 &= ~WM8903_SAMPLE_RATE_MASK; + clock1 |= sample_rates[dsp_config].value; + + aif1 &= ~WM8903_AIF_WL_MASK; + bclk = 2 * fs; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bclk *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + bclk *= 20; + aif1 |= 0x4; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bclk *= 24; + aif1 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bclk *= 32; + aif1 |= 0xc; + break; + default: + return -EINVAL; + } + + dev_dbg(&i2c->dev, "MCLK = %dHz, target sample rate = %dHz\n", + wm8903->sysclk, fs); + + /* We may not have an MCLK which allows us to generate exactly + * the clock we want, particularly with USB derived inputs, so + * approximate. + */ + clk_config = 0; + best_val = abs((wm8903->sysclk / + (clk_sys_ratios[0].mclk_div * + clk_sys_ratios[0].div)) - fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_ratios); i++) { + cur_val = abs((wm8903->sysclk / + (clk_sys_ratios[i].mclk_div * + clk_sys_ratios[i].div)) - fs); + + if (cur_val <= best_val) { + clk_config = i; + best_val = cur_val; + } + } + + if (clk_sys_ratios[clk_config].mclk_div == 2) { + clock0 |= WM8903_MCLKDIV2; + clk_sys = wm8903->sysclk / 2; + } else { + clock0 &= ~WM8903_MCLKDIV2; + clk_sys = wm8903->sysclk; + } + + clock1 &= ~(WM8903_CLK_SYS_RATE_MASK | + WM8903_CLK_SYS_MODE_MASK); + clock1 |= clk_sys_ratios[clk_config].rate << WM8903_CLK_SYS_RATE_SHIFT; + clock1 |= clk_sys_ratios[clk_config].mode << WM8903_CLK_SYS_MODE_SHIFT; + + dev_dbg(&i2c->dev, "CLK_SYS_RATE=%x, CLK_SYS_MODE=%x div=%d\n", + clk_sys_ratios[clk_config].rate, + clk_sys_ratios[clk_config].mode, + clk_sys_ratios[clk_config].div); + + dev_dbg(&i2c->dev, "Actual CLK_SYS = %dHz\n", clk_sys); + + /* 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). + */ + bclk_div = 0; + best_val = ((clk_sys * 10) / bclk_divs[0].ratio) - bclk; + i = 1; + while (i < ARRAY_SIZE(bclk_divs)) { + cur_val = ((clk_sys * 10) / bclk_divs[i].ratio) - bclk; + if (cur_val < 0) /* BCLK table is sorted */ + break; + bclk_div = i; + best_val = cur_val; + i++; + } + + aif2 &= ~WM8903_BCLK_DIV_MASK; + aif3 &= ~WM8903_LRCLK_RATE_MASK; + + dev_dbg(&i2c->dev, "BCLK ratio %d for %dHz - actual BCLK = %dHz\n", + bclk_divs[bclk_div].ratio / 10, bclk, + (clk_sys * 10) / bclk_divs[bclk_div].ratio); + + aif2 |= bclk_divs[bclk_div].div; + aif3 |= bclk / fs; + + wm8903_write(codec, WM8903_CLOCK_RATES_0, clock0); + wm8903_write(codec, WM8903_CLOCK_RATES_1, clock1); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3); + + return 0; +} + +#define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define WM8903_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8903_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8903_dai = { + .name = "WM8903", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8903_PLAYBACK_RATES, + .formats = WM8903_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8903_CAPTURE_RATES, + .formats = WM8903_FORMATS, + }, + .ops = { + .startup = wm8903_startup, + .shutdown = wm8903_shutdown, + .hw_params = wm8903_hw_params, + }, + .dai_ops = { + .digital_mute = wm8903_digital_mute, + .set_fmt = wm8903_set_dai_fmt, + .set_sysclk = wm8903_set_dai_sysclk + } +}; +EXPORT_SYMBOL_GPL(wm8903_dai); + +static int wm8903_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8903_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c = codec->control_data; + int i; + u16 *reg_cache = codec->reg_cache; + u16 *tmp_cache = kmemdup(codec->reg_cache, sizeof(wm8903_reg_defaults), + GFP_KERNEL); + + /* Bring the codec back up to standby first to minimise pop/clicks */ + wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8903_set_bias_level(codec, codec->suspend_bias_level); + + /* Sync back everything else */ + if (tmp_cache) { + for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++) + if (tmp_cache[i] != reg_cache[i]) + wm8903_write(codec, i, tmp_cache[i]); + } else { + dev_err(&i2c->dev, "Failed to allocate temporary cache\n"); + } + + return 0; +} + +/* + * initialise the WM8903 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8903_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c = codec->control_data; + int ret = 0; + u16 val; + + val = wm8903_hw_read(codec, WM8903_SW_RESET_AND_ID); + if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) { + dev_err(&i2c->dev, + "Device with ID register %x is not a WM8903\n", val); + return -ENODEV; + } + + codec->name = "WM8903"; + codec->owner = THIS_MODULE; + codec->read = wm8903_read; + codec->write = wm8903_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8903_set_bias_level; + codec->dai = &wm8903_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8903_reg_defaults); + codec->reg_cache = kmemdup(wm8903_reg_defaults, + sizeof(wm8903_reg_defaults), + GFP_KERNEL); + if (codec->reg_cache == NULL) { + dev_err(&i2c->dev, "Failed to allocate register cache\n"); + return -ENOMEM; + } + + val = wm8903_read(codec, WM8903_REVISION_NUMBER); + dev_info(&i2c->dev, "WM8903 revision %d\n", + val & WM8903_CHIP_REV_MASK); + + wm8903_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&i2c->dev, "failed to create pcms\n"); + goto pcm_err; + } + + /* SYSCLK is required for pretty much anything */ + wm8903_write(codec, WM8903_CLOCK_RATES_2, WM8903_CLK_SYS_ENA); + + /* power on device */ + wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Latch volume update bits */ + val = wm8903_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT); + val |= WM8903_ADCVU; + wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val); + wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val); + + val = wm8903_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT); + val |= WM8903_DACVU; + wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val); + wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT1_LEFT); + val |= WM8903_HPOUTVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT2_LEFT); + val |= WM8903_LINEOUTVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT3_LEFT); + val |= WM8903_SPKVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val); + + /* Enable DAC soft mute by default */ + val = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + val |= WM8903_DAC_MUTEMODE; + wm8903_write(codec, WM8903_DAC_DIGITAL_1, val); + + wm8903_add_controls(codec); + wm8903_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&i2c->dev, "wm8903: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8903_socdev; + +static int wm8903_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8903_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8903_init(socdev); + if (ret < 0) + dev_err(&i2c->dev, "Device initialisation failed\n"); + + return ret; +} + +static int wm8903_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +/* i2c codec control layer */ +static const struct i2c_device_id wm8903_i2c_id[] = { + { "wm8903", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id); + +static struct i2c_driver wm8903_i2c_driver = { + .driver = { + .name = "WM8903", + .owner = THIS_MODULE, + }, + .probe = wm8903_i2c_probe, + .remove = wm8903_i2c_remove, + .id_table = wm8903_i2c_id, +}; + +static int wm8903_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8903_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8903_priv *wm8903; + struct i2c_board_info board_info; + struct i2c_adapter *adapter; + struct i2c_client *i2c_client; + int ret = 0; + + setup = socdev->codec_data; + + if (!setup->i2c_address) { + dev_err(&pdev->dev, "No codec address provided\n"); + return -ENODEV; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL); + if (wm8903 == NULL) { + ret = -ENOMEM; + goto err_codec; + } + + codec->private_data = wm8903; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8903_socdev = socdev; + + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8903_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + goto err_priv; + } else { + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "wm8903", I2C_NAME_SIZE); + board_info.addr = setup->i2c_address; + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "Can't get I2C bus %d\n", + setup->i2c_bus); + ret = -ENODEV; + goto err_adapter; + } + + i2c_client = i2c_new_device(adapter, &board_info); + i2c_put_adapter(adapter); + if (i2c_client == NULL) { + dev_err(&pdev->dev, + "I2C driver registration failed\n"); + ret = -ENODEV; + goto err_adapter; + } + } + + return ret; + +err_adapter: + i2c_del_driver(&wm8903_i2c_driver); +err_priv: + kfree(codec->private_data); +err_codec: + kfree(codec); + return ret; +} + +/* power down chip */ +static int wm8903_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + i2c_unregister_device(socdev->codec->control_data); + i2c_del_driver(&wm8903_i2c_driver); + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8903 = { + .probe = wm8903_probe, + .remove = wm8903_remove, + .suspend = wm8903_suspend, + .resume = wm8903_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8903); + +MODULE_DESCRIPTION("ASoC WM8903 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.cm>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h new file mode 100644 index 0000000..cec622f --- /dev/null +++ b/sound/soc/codecs/wm8903.h @@ -0,0 +1,1463 @@ +/* + * wm8903.h - WM8903 audio codec interface + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM8903_H +#define _WM8903_H + +#include <linux/i2c.h> + +extern struct snd_soc_dai wm8903_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8903; + +struct wm8903_setup_data { + int i2c_bus; + int i2c_address; +}; + +#define WM8903_MCLK_DIV_2 1 +#define WM8903_CLK_SYS 2 +#define WM8903_BCLK 3 +#define WM8903_LRCLK 4 + +/* + * Register values. + */ +#define WM8903_SW_RESET_AND_ID 0x00 +#define WM8903_REVISION_NUMBER 0x01 +#define WM8903_BIAS_CONTROL_0 0x04 +#define WM8903_VMID_CONTROL_0 0x05 +#define WM8903_MIC_BIAS_CONTROL_0 0x06 +#define WM8903_ANALOGUE_DAC_0 0x08 +#define WM8903_ANALOGUE_ADC_0 0x0A +#define WM8903_POWER_MANAGEMENT_0 0x0C +#define WM8903_POWER_MANAGEMENT_1 0x0D +#define WM8903_POWER_MANAGEMENT_2 0x0E +#define WM8903_POWER_MANAGEMENT_3 0x0F +#define WM8903_POWER_MANAGEMENT_4 0x10 +#define WM8903_POWER_MANAGEMENT_5 0x11 +#define WM8903_POWER_MANAGEMENT_6 0x12 +#define WM8903_CLOCK_RATES_0 0x14 +#define WM8903_CLOCK_RATES_1 0x15 +#define WM8903_CLOCK_RATES_2 0x16 +#define WM8903_AUDIO_INTERFACE_0 0x18 +#define WM8903_AUDIO_INTERFACE_1 0x19 +#define WM8903_AUDIO_INTERFACE_2 0x1A +#define WM8903_AUDIO_INTERFACE_3 0x1B +#define WM8903_DAC_DIGITAL_VOLUME_LEFT 0x1E +#define WM8903_DAC_DIGITAL_VOLUME_RIGHT 0x1F +#define WM8903_DAC_DIGITAL_0 0x20 +#define WM8903_DAC_DIGITAL_1 0x21 +#define WM8903_ADC_DIGITAL_VOLUME_LEFT 0x24 +#define WM8903_ADC_DIGITAL_VOLUME_RIGHT 0x25 +#define WM8903_ADC_DIGITAL_0 0x26 +#define WM8903_DIGITAL_MICROPHONE_0 0x27 +#define WM8903_DRC_0 0x28 +#define WM8903_DRC_1 0x29 +#define WM8903_DRC_2 0x2A +#define WM8903_DRC_3 0x2B +#define WM8903_ANALOGUE_LEFT_INPUT_0 0x2C +#define WM8903_ANALOGUE_RIGHT_INPUT_0 0x2D +#define WM8903_ANALOGUE_LEFT_INPUT_1 0x2E +#define WM8903_ANALOGUE_RIGHT_INPUT_1 0x2F +#define WM8903_ANALOGUE_LEFT_MIX_0 0x32 +#define WM8903_ANALOGUE_RIGHT_MIX_0 0x33 +#define WM8903_ANALOGUE_SPK_MIX_LEFT_0 0x34 +#define WM8903_ANALOGUE_SPK_MIX_LEFT_1 0x35 +#define WM8903_ANALOGUE_SPK_MIX_RIGHT_0 0x36 +#define WM8903_ANALOGUE_SPK_MIX_RIGHT_1 0x37 +#define WM8903_ANALOGUE_OUT1_LEFT 0x39 +#define WM8903_ANALOGUE_OUT1_RIGHT 0x3A +#define WM8903_ANALOGUE_OUT2_LEFT 0x3B +#define WM8903_ANALOGUE_OUT2_RIGHT 0x3C +#define WM8903_ANALOGUE_OUT3_LEFT 0x3E +#define WM8903_ANALOGUE_OUT3_RIGHT 0x3F +#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41 +#define WM8903_DC_SERVO_0 0x43 +#define WM8903_DC_SERVO_2 0x45 +#define WM8903_ANALOGUE_HP_0 0x5A +#define WM8903_ANALOGUE_LINEOUT_0 0x5E +#define WM8903_CHARGE_PUMP_0 0x62 +#define WM8903_CLASS_W_0 0x68 +#define WM8903_WRITE_SEQUENCER_0 0x6C +#define WM8903_WRITE_SEQUENCER_1 0x6D +#define WM8903_WRITE_SEQUENCER_2 0x6E +#define WM8903_WRITE_SEQUENCER_3 0x6F +#define WM8903_WRITE_SEQUENCER_4 0x70 +#define WM8903_CONTROL_INTERFACE 0x72 +#define WM8903_GPIO_CONTROL_1 0x74 +#define WM8903_GPIO_CONTROL_2 0x75 +#define WM8903_GPIO_CONTROL_3 0x76 +#define WM8903_GPIO_CONTROL_4 0x77 +#define WM8903_GPIO_CONTROL_5 0x78 +#define WM8903_INTERRUPT_STATUS_1 0x79 +#define WM8903_INTERRUPT_STATUS_1_MASK 0x7A +#define WM8903_INTERRUPT_POLARITY_1 0x7B +#define WM8903_INTERRUPT_CONTROL 0x7E +#define WM8903_CONTROL_INTERFACE_TEST_1 0x81 +#define WM8903_CHARGE_PUMP_TEST_1 0x95 +#define WM8903_CLOCK_RATE_TEST_4 0xA4 +#define WM8903_ANALOGUE_OUTPUT_BIAS_0 0xAC + +#define WM8903_REGISTER_COUNT 75 +#define WM8903_MAX_REGISTER 0xAC + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - SW Reset and ID + */ +#define WM8903_SW_RESET_DEV_ID1_MASK 0xFFFF /* SW_RESET_DEV_ID1 - [15:0] */ +#define WM8903_SW_RESET_DEV_ID1_SHIFT 0 /* SW_RESET_DEV_ID1 - [15:0] */ +#define WM8903_SW_RESET_DEV_ID1_WIDTH 16 /* SW_RESET_DEV_ID1 - [15:0] */ + +/* + * R1 (0x01) - Revision Number + */ +#define WM8903_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */ +#define WM8903_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */ +#define WM8903_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */ + +/* + * R4 (0x04) - Bias Control 0 + */ +#define WM8903_POBCTRL 0x0010 /* POBCTRL */ +#define WM8903_POBCTRL_MASK 0x0010 /* POBCTRL */ +#define WM8903_POBCTRL_SHIFT 4 /* POBCTRL */ +#define WM8903_POBCTRL_WIDTH 1 /* POBCTRL */ +#define WM8903_ISEL_MASK 0x000C /* ISEL - [3:2] */ +#define WM8903_ISEL_SHIFT 2 /* ISEL - [3:2] */ +#define WM8903_ISEL_WIDTH 2 /* ISEL - [3:2] */ +#define WM8903_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ +#define WM8903_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R5 (0x05) - VMID Control 0 + */ +#define WM8903_VMID_TIE_ENA 0x0080 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_MASK 0x0080 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_SHIFT 7 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_WIDTH 1 /* VMID_TIE_ENA */ +#define WM8903_BUFIO_ENA 0x0040 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_MASK 0x0040 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_SHIFT 6 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_WIDTH 1 /* BUFIO_ENA */ +#define WM8903_VMID_IO_ENA 0x0020 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_MASK 0x0020 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_SHIFT 5 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_WIDTH 1 /* VMID_IO_ENA */ +#define WM8903_VMID_SOFT_MASK 0x0018 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_SOFT_SHIFT 3 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_SOFT_WIDTH 2 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */ +#define WM8903_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */ +#define WM8903_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */ +#define WM8903_VMID_BUF_ENA 0x0001 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_MASK 0x0001 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_SHIFT 0 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ + +#define WM8903_VMID_RES_50K 2 +#define WM8903_VMID_RES_250K 3 +#define WM8903_VMID_RES_5K 4 + +/* + * R6 (0x06) - Mic Bias Control 0 + */ +#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ +#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ +#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ + +/* + * R8 (0x08) - Analogue DAC 0 + */ +#define WM8903_DACBIAS_SEL_MASK 0x0018 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACBIAS_SEL_SHIFT 3 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACBIAS_SEL_WIDTH 2 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACVMID_BIAS_SEL_MASK 0x0006 /* DACVMID_BIAS_SEL - [2:1] */ +#define WM8903_DACVMID_BIAS_SEL_SHIFT 1 /* DACVMID_BIAS_SEL - [2:1] */ +#define WM8903_DACVMID_BIAS_SEL_WIDTH 2 /* DACVMID_BIAS_SEL - [2:1] */ + +/* + * R10 (0x0A) - Analogue ADC 0 + */ +#define WM8903_ADC_OSR128 0x0001 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ + +/* + * R12 (0x0C) - Power Management 0 + */ +#define WM8903_INL_ENA 0x0002 /* INL_ENA */ +#define WM8903_INL_ENA_MASK 0x0002 /* INL_ENA */ +#define WM8903_INL_ENA_SHIFT 1 /* INL_ENA */ +#define WM8903_INL_ENA_WIDTH 1 /* INL_ENA */ +#define WM8903_INR_ENA 0x0001 /* INR_ENA */ +#define WM8903_INR_ENA_MASK 0x0001 /* INR_ENA */ +#define WM8903_INR_ENA_SHIFT 0 /* INR_ENA */ +#define WM8903_INR_ENA_WIDTH 1 /* INR_ENA */ + +/* + * R13 (0x0D) - Power Management 1 + */ +#define WM8903_MIXOUTL_ENA 0x0002 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_MASK 0x0002 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_SHIFT 1 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTR_ENA 0x0001 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_MASK 0x0001 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_SHIFT 0 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ + +/* + * R14 (0x0E) - Power Management 2 + */ +#define WM8903_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */ +#define WM8903_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */ + +/* + * R15 (0x0F) - Power Management 3 + */ +#define WM8903_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */ + +/* + * R16 (0x10) - Power Management 4 + */ +#define WM8903_MIXSPKL_ENA 0x0002 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_MASK 0x0002 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_SHIFT 1 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_WIDTH 1 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKR_ENA 0x0001 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_MASK 0x0001 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_SHIFT 0 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_WIDTH 1 /* MIXSPKR_ENA */ + +/* + * R17 (0x11) - Power Management 5 + */ +#define WM8903_SPKL_ENA 0x0002 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_MASK 0x0002 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_SHIFT 1 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_WIDTH 1 /* SPKL_ENA */ +#define WM8903_SPKR_ENA 0x0001 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_MASK 0x0001 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_SHIFT 0 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_WIDTH 1 /* SPKR_ENA */ + +/* + * R18 (0x12) - Power Management 6 + */ +#define WM8903_DACL_ENA 0x0008 /* DACL_ENA */ +#define WM8903_DACL_ENA_MASK 0x0008 /* DACL_ENA */ +#define WM8903_DACL_ENA_SHIFT 3 /* DACL_ENA */ +#define WM8903_DACL_ENA_WIDTH 1 /* DACL_ENA */ +#define WM8903_DACR_ENA 0x0004 /* DACR_ENA */ +#define WM8903_DACR_ENA_MASK 0x0004 /* DACR_ENA */ +#define WM8903_DACR_ENA_SHIFT 2 /* DACR_ENA */ +#define WM8903_DACR_ENA_WIDTH 1 /* DACR_ENA */ +#define WM8903_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8903_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R20 (0x14) - Clock Rates 0 + */ +#define WM8903_MCLKDIV2 0x0001 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_MASK 0x0001 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_SHIFT 0 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ + +/* + * R21 (0x15) - Clock Rates 1 + */ +#define WM8903_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_MODE_MASK 0x0300 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_CLK_SYS_MODE_SHIFT 8 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_CLK_SYS_MODE_WIDTH 2 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */ +#define WM8903_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */ +#define WM8903_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */ + +/* + * R22 (0x16) - Clock Rates 2 + */ +#define WM8903_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ +#define WM8903_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM8903_TO_ENA 0x0001 /* TO_ENA */ +#define WM8903_TO_ENA_MASK 0x0001 /* TO_ENA */ +#define WM8903_TO_ENA_SHIFT 0 /* TO_ENA */ +#define WM8903_TO_ENA_WIDTH 1 /* TO_ENA */ + +/* + * R24 (0x18) - Audio Interface 0 + */ +#define WM8903_DACL_DATINV 0x1000 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_SHIFT 12 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_WIDTH 1 /* DACL_DATINV */ +#define WM8903_DACR_DATINV 0x0800 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_SHIFT 11 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_WIDTH 1 /* DACR_DATINV */ +#define WM8903_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */ +#define WM8903_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */ +#define WM8903_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */ +#define WM8903_LOOPBACK 0x0100 /* LOOPBACK */ +#define WM8903_LOOPBACK_MASK 0x0100 /* LOOPBACK */ +#define WM8903_LOOPBACK_SHIFT 8 /* LOOPBACK */ +#define WM8903_LOOPBACK_WIDTH 1 /* LOOPBACK */ +#define WM8903_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */ +#define WM8903_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */ +#define WM8903_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */ +#define WM8903_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */ +#define WM8903_ADC_COMP 0x0008 /* ADC_COMP */ +#define WM8903_ADC_COMP_MASK 0x0008 /* ADC_COMP */ +#define WM8903_ADC_COMP_SHIFT 3 /* ADC_COMP */ +#define WM8903_ADC_COMP_WIDTH 1 /* ADC_COMP */ +#define WM8903_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */ +#define WM8903_DAC_COMP 0x0002 /* DAC_COMP */ +#define WM8903_DAC_COMP_MASK 0x0002 /* DAC_COMP */ +#define WM8903_DAC_COMP_SHIFT 1 /* DAC_COMP */ +#define WM8903_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM8903_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ + +/* + * R25 (0x19) - Audio Interface 1 + */ +#define WM8903_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFADC_TDM 0x0800 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */ +#define WM8903_LRCLK_DIR 0x0200 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_MASK 0x0200 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_SHIFT 9 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM8903_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM8903_BCLK_DIR 0x0040 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_SHIFT 6 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM8903_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM8903_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */ +#define WM8903_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */ +#define WM8903_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */ +#define WM8903_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */ +#define WM8903_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */ +#define WM8903_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */ + +/* + * R26 (0x1A) - Audio Interface 2 + */ +#define WM8903_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */ +#define WM8903_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */ +#define WM8903_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */ + +/* + * R27 (0x1B) - Audio Interface 3 + */ +#define WM8903_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM8903_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM8903_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R30 (0x1E) - DAC Digital Volume Left + */ +#define WM8903_DACVU 0x0100 /* DACVU */ +#define WM8903_DACVU_MASK 0x0100 /* DACVU */ +#define WM8903_DACVU_SHIFT 8 /* DACVU */ +#define WM8903_DACVU_WIDTH 1 /* DACVU */ +#define WM8903_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8903_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */ +#define WM8903_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */ + +/* + * R31 (0x1F) - DAC Digital Volume Right + */ +#define WM8903_DACVU 0x0100 /* DACVU */ +#define WM8903_DACVU_MASK 0x0100 /* DACVU */ +#define WM8903_DACVU_SHIFT 8 /* DACVU */ +#define WM8903_DACVU_WIDTH 1 /* DACVU */ +#define WM8903_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8903_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */ +#define WM8903_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */ + +/* + * R32 (0x20) - DAC Digital 0 + */ +#define WM8903_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */ +#define WM8903_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */ +#define WM8903_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */ + +/* + * R33 (0x21) - DAC Digital 1 + */ +#define WM8903_DAC_MONO 0x1000 /* DAC_MONO */ +#define WM8903_DAC_MONO_MASK 0x1000 /* DAC_MONO */ +#define WM8903_DAC_MONO_SHIFT 12 /* DAC_MONO */ +#define WM8903_DAC_MONO_WIDTH 1 /* DAC_MONO */ +#define WM8903_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */ +#define WM8903_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM8903_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8903_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8903_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R36 (0x24) - ADC Digital Volume Left + */ +#define WM8903_ADCVU 0x0100 /* ADCVU */ +#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8903_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8903_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8903_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8903_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */ +#define WM8903_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */ + +/* + * R37 (0x25) - ADC Digital Volume Right + */ +#define WM8903_ADCVU 0x0100 /* ADCVU */ +#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8903_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8903_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8903_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8903_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */ +#define WM8903_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */ + +/* + * R38 (0x26) - ADC Digital 0 + */ +#define WM8903_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_ENA 0x0010 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_MASK 0x0010 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_SHIFT 4 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_WIDTH 1 /* ADC_HPF_ENA */ +#define WM8903_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */ +#define WM8903_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */ + +/* + * R39 (0x27) - Digital Microphone 0 + */ +#define WM8903_DIGMIC_MODE_SEL 0x0100 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_MASK 0x0100 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_SHIFT 8 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_WIDTH 1 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_CLK_SEL_L_MASK 0x00C0 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_L_SHIFT 6 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_L_WIDTH 2 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_R_MASK 0x0030 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_R_SHIFT 4 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_R_WIDTH 2 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_RT_MASK 0x000C /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_RT_SHIFT 2 /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_RT_WIDTH 2 /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_MASK 0x0003 /* DIGMIC_CLK_SEL - [1:0] */ +#define WM8903_DIGMIC_CLK_SEL_SHIFT 0 /* DIGMIC_CLK_SEL - [1:0] */ +#define WM8903_DIGMIC_CLK_SEL_WIDTH 2 /* DIGMIC_CLK_SEL - [1:0] */ + +/* + * R40 (0x28) - DRC 0 + */ +#define WM8903_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM8903_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM8903_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM8903_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM8903_DRC_THRESH_HYST_MASK 0x1800 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_THRESH_HYST_SHIFT 11 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */ +#define WM8903_DRC_SMOOTH_ENA 0x0008 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_MASK 0x0008 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_SHIFT 3 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_QR_ENA 0x0004 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_MASK 0x0004 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_SHIFT 2 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */ +#define WM8903_DRC_ANTICLIP_ENA 0x0002 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_MASK 0x0002 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_SHIFT 1 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_HYST_ENA 0x0001 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_MASK 0x0001 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_SHIFT 0 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */ + +/* + * R41 (0x29) - DRC 1 + */ +#define WM8903_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_THRESH_QR_MASK 0x00C0 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_THRESH_QR_SHIFT 6 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_RATE_QR_MASK 0x0030 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_RATE_QR_SHIFT 4 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM8903_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM8903_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R42 (0x2A) - DRC 2 + */ +#define WM8903_DRC_R0_SLOPE_COMP_MASK 0x0038 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R0_SLOPE_COMP_SHIFT 3 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R1_SLOPE_COMP_MASK 0x0007 /* DRC_R1_SLOPE_COMP - [2:0] */ +#define WM8903_DRC_R1_SLOPE_COMP_SHIFT 0 /* DRC_R1_SLOPE_COMP - [2:0] */ +#define WM8903_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [2:0] */ + +/* + * R43 (0x2B) - DRC 3 + */ +#define WM8903_DRC_THRESH_COMP_MASK 0x07E0 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_THRESH_COMP_SHIFT 5 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_AMP_COMP_MASK 0x001F /* DRC_AMP_COMP - [4:0] */ +#define WM8903_DRC_AMP_COMP_SHIFT 0 /* DRC_AMP_COMP - [4:0] */ +#define WM8903_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [4:0] */ + +/* + * R44 (0x2C) - Analogue Left Input 0 + */ +#define WM8903_LINMUTE 0x0080 /* LINMUTE */ +#define WM8903_LINMUTE_MASK 0x0080 /* LINMUTE */ +#define WM8903_LINMUTE_SHIFT 7 /* LINMUTE */ +#define WM8903_LINMUTE_WIDTH 1 /* LINMUTE */ +#define WM8903_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */ +#define WM8903_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */ +#define WM8903_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */ + +/* + * R45 (0x2D) - Analogue Right Input 0 + */ +#define WM8903_RINMUTE 0x0080 /* RINMUTE */ +#define WM8903_RINMUTE_MASK 0x0080 /* RINMUTE */ +#define WM8903_RINMUTE_SHIFT 7 /* RINMUTE */ +#define WM8903_RINMUTE_WIDTH 1 /* RINMUTE */ +#define WM8903_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */ +#define WM8903_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */ +#define WM8903_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */ + +/* + * R46 (0x2E) - Analogue Left Input 1 + */ +#define WM8903_INL_CM_ENA 0x0040 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */ +#define WM8903_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */ +#define WM8903_L_MODE_SHIFT 0 /* L_MODE - [1:0] */ +#define WM8903_L_MODE_WIDTH 2 /* L_MODE - [1:0] */ + +/* + * R47 (0x2F) - Analogue Right Input 1 + */ +#define WM8903_INR_CM_ENA 0x0040 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */ +#define WM8903_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */ +#define WM8903_R_MODE_SHIFT 0 /* R_MODE - [1:0] */ +#define WM8903_R_MODE_WIDTH 2 /* R_MODE - [1:0] */ + +/* + * R50 (0x32) - Analogue Left Mix 0 + */ +#define WM8903_DACL_TO_MIXOUTL 0x0008 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_MASK 0x0008 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_SHIFT 3 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL 0x0004 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_MASK 0x0004 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_SHIFT 2 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_WIDTH 1 /* DACR_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL 0x0002 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_MASK 0x0002 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_SHIFT 1 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_WIDTH 1 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL 0x0001 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_MASK 0x0001 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_SHIFT 0 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_WIDTH 1 /* BYPASSR_TO_MIXOUTL */ + +/* + * R51 (0x33) - Analogue Right Mix 0 + */ +#define WM8903_DACL_TO_MIXOUTR 0x0008 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_MASK 0x0008 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_SHIFT 3 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_WIDTH 1 /* DACL_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR 0x0004 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_MASK 0x0004 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_SHIFT 2 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR 0x0002 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_MASK 0x0002 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_SHIFT 1 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_WIDTH 1 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR 0x0001 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_MASK 0x0001 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_SHIFT 0 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_WIDTH 1 /* BYPASSR_TO_MIXOUTR */ + +/* + * R52 (0x34) - Analogue Spk Mix Left 0 + */ +#define WM8903_DACL_TO_MIXSPKL 0x0008 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_MASK 0x0008 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_SHIFT 3 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_WIDTH 1 /* DACL_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL 0x0004 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_MASK 0x0004 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_SHIFT 2 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_WIDTH 1 /* DACR_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL 0x0002 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_MASK 0x0002 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_SHIFT 1 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_WIDTH 1 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL 0x0001 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_MASK 0x0001 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_SHIFT 0 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_WIDTH 1 /* BYPASSR_TO_MIXSPKL */ + +/* + * R53 (0x35) - Analogue Spk Mix Left 1 + */ +#define WM8903_DACL_MIXSPKL_VOL 0x0008 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_MASK 0x0008 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_SHIFT 3 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_WIDTH 1 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL 0x0004 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_MASK 0x0004 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_SHIFT 2 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_WIDTH 1 /* DACR_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL 0x0002 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_MASK 0x0002 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_SHIFT 1 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_WIDTH 1 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL 0x0001 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_MASK 0x0001 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_SHIFT 0 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_WIDTH 1 /* BYPASSR_MIXSPKL_VOL */ + +/* + * R54 (0x36) - Analogue Spk Mix Right 0 + */ +#define WM8903_DACL_TO_MIXSPKR 0x0008 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_MASK 0x0008 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_SHIFT 3 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_WIDTH 1 /* DACL_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR 0x0004 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_MASK 0x0004 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_SHIFT 2 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_WIDTH 1 /* DACR_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR 0x0002 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_MASK 0x0002 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_SHIFT 1 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_WIDTH 1 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR 0x0001 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_MASK 0x0001 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_SHIFT 0 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_WIDTH 1 /* BYPASSR_TO_MIXSPKR */ + +/* + * R55 (0x37) - Analogue Spk Mix Right 1 + */ +#define WM8903_DACL_MIXSPKR_VOL 0x0008 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_MASK 0x0008 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_SHIFT 3 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_WIDTH 1 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL 0x0004 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_MASK 0x0004 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_SHIFT 2 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_WIDTH 1 /* DACR_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL 0x0002 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_MASK 0x0002 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_SHIFT 1 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_WIDTH 1 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL 0x0001 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_MASK 0x0001 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_SHIFT 0 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_WIDTH 1 /* BYPASSR_MIXSPKR_VOL */ + +/* + * R57 (0x39) - Analogue OUT1 Left + */ +#define WM8903_HPL_MUTE 0x0100 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_MASK 0x0100 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_SHIFT 8 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_WIDTH 1 /* HPL_MUTE */ +#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */ +#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */ +#define WM8903_HPOUTLZC 0x0040 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_SHIFT 6 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_WIDTH 1 /* HPOUTLZC */ +#define WM8903_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */ +#define WM8903_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */ +#define WM8903_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */ + +/* + * R58 (0x3A) - Analogue OUT1 Right + */ +#define WM8903_HPR_MUTE 0x0100 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_MASK 0x0100 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_SHIFT 8 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_WIDTH 1 /* HPR_MUTE */ +#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */ +#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */ +#define WM8903_HPOUTRZC 0x0040 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_SHIFT 6 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_WIDTH 1 /* HPOUTRZC */ +#define WM8903_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */ +#define WM8903_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */ +#define WM8903_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */ + +/* + * R59 (0x3B) - Analogue OUT2 Left + */ +#define WM8903_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */ +#define WM8903_LINEOUTLZC 0x0040 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */ +#define WM8903_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */ +#define WM8903_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */ +#define WM8903_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */ + +/* + * R60 (0x3C) - Analogue OUT2 Right + */ +#define WM8903_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */ +#define WM8903_LINEOUTRZC 0x0040 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */ +#define WM8903_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */ +#define WM8903_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */ +#define WM8903_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */ + +/* + * R62 (0x3E) - Analogue OUT3 Left + */ +#define WM8903_SPKL_MUTE 0x0100 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_MASK 0x0100 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_SHIFT 8 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */ +#define WM8903_SPKVU 0x0080 /* SPKVU */ +#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */ +#define WM8903_SPKVU_SHIFT 7 /* SPKVU */ +#define WM8903_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8903_SPKLZC 0x0040 /* SPKLZC */ +#define WM8903_SPKLZC_MASK 0x0040 /* SPKLZC */ +#define WM8903_SPKLZC_SHIFT 6 /* SPKLZC */ +#define WM8903_SPKLZC_WIDTH 1 /* SPKLZC */ +#define WM8903_SPKL_VOL_MASK 0x003F /* SPKL_VOL - [5:0] */ +#define WM8903_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [5:0] */ +#define WM8903_SPKL_VOL_WIDTH 6 /* SPKL_VOL - [5:0] */ + +/* + * R63 (0x3F) - Analogue OUT3 Right + */ +#define WM8903_SPKR_MUTE 0x0100 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_MASK 0x0100 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_SHIFT 8 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */ +#define WM8903_SPKVU 0x0080 /* SPKVU */ +#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */ +#define WM8903_SPKVU_SHIFT 7 /* SPKVU */ +#define WM8903_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8903_SPKRZC 0x0040 /* SPKRZC */ +#define WM8903_SPKRZC_MASK 0x0040 /* SPKRZC */ +#define WM8903_SPKRZC_SHIFT 6 /* SPKRZC */ +#define WM8903_SPKRZC_WIDTH 1 /* SPKRZC */ +#define WM8903_SPKR_VOL_MASK 0x003F /* SPKR_VOL - [5:0] */ +#define WM8903_SPKR_VOL_SHIFT 0 /* SPKR_VOL - [5:0] */ +#define WM8903_SPKR_VOL_WIDTH 6 /* SPKR_VOL - [5:0] */ + +/* + * R65 (0x41) - Analogue SPK Output Control 0 + */ +#define WM8903_SPK_DISCHARGE 0x0002 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_MASK 0x0002 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_SHIFT 1 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_WIDTH 1 /* SPK_DISCHARGE */ +#define WM8903_VROI 0x0001 /* VROI */ +#define WM8903_VROI_MASK 0x0001 /* VROI */ +#define WM8903_VROI_SHIFT 0 /* VROI */ +#define WM8903_VROI_WIDTH 1 /* VROI */ + +/* + * R67 (0x43) - DC Servo 0 + */ +#define WM8903_DCS_MASTER_ENA 0x0010 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_MASK 0x0010 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_SHIFT 4 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_WIDTH 1 /* DCS_MASTER_ENA */ +#define WM8903_DCS_ENA_MASK 0x000F /* DCS_ENA - [3:0] */ +#define WM8903_DCS_ENA_SHIFT 0 /* DCS_ENA - [3:0] */ +#define WM8903_DCS_ENA_WIDTH 4 /* DCS_ENA - [3:0] */ + +/* + * R69 (0x45) - DC Servo 2 + */ +#define WM8903_DCS_MODE_MASK 0x0003 /* DCS_MODE - [1:0] */ +#define WM8903_DCS_MODE_SHIFT 0 /* DCS_MODE - [1:0] */ +#define WM8903_DCS_MODE_WIDTH 2 /* DCS_MODE - [1:0] */ + +/* + * R90 (0x5A) - Analogue HP 0 + */ +#define WM8903_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */ +#define WM8903_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA 0x0010 /* HPL_ENA */ +#define WM8903_HPL_ENA_MASK 0x0010 /* HPL_ENA */ +#define WM8903_HPL_ENA_SHIFT 4 /* HPL_ENA */ +#define WM8903_HPL_ENA_WIDTH 1 /* HPL_ENA */ +#define WM8903_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */ +#define WM8903_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA 0x0001 /* HPR_ENA */ +#define WM8903_HPR_ENA_MASK 0x0001 /* HPR_ENA */ +#define WM8903_HPR_ENA_SHIFT 0 /* HPR_ENA */ +#define WM8903_HPR_ENA_WIDTH 1 /* HPR_ENA */ + +/* + * R94 (0x5E) - Analogue Lineout 0 + */ +#define WM8903_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */ + +/* + * R98 (0x62) - Charge Pump 0 + */ +#define WM8903_CP_ENA 0x0001 /* CP_ENA */ +#define WM8903_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8903_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8903_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R104 (0x68) - Class W 0 + */ +#define WM8903_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_V 0x0001 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_SHIFT 0 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_WIDTH 1 /* CP_DYN_V */ + +/* + * R108 (0x6C) - Write Sequencer 0 + */ +#define WM8903_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8903_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8903_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8903_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R109 (0x6D) - Write Sequencer 1 + */ +#define WM8903_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8903_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8903_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R110 (0x6E) - Write Sequencer 2 + */ +#define WM8903_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8903_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8903_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8903_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R111 (0x6F) - Write Sequencer 3 + */ +#define WM8903_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8903_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8903_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8903_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8903_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8903_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8903_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8903_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R112 (0x70) - Write Sequencer 4 + */ +#define WM8903_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R114 (0x72) - Control Interface + */ +#define WM8903_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */ + +/* + * R116 (0x74) - GPIO Control 1 + */ +#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */ +#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */ +#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */ +#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */ +#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM8903_GP1_PD 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */ +#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM8903_GP1_PU 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */ +#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */ +#define WM8903_GP1_DB 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */ +#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */ + +/* + * R117 (0x75) - GPIO Control 2 + */ +#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */ +#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */ +#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */ +#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */ +#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM8903_GP2_PD 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */ +#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM8903_GP2_PU 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */ +#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */ +#define WM8903_GP2_DB 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */ +#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */ + +/* + * R118 (0x76) - GPIO Control 3 + */ +#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */ +#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */ +#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */ +#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */ +#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM8903_GP3_PD 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */ +#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM8903_GP3_PU 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */ +#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */ +#define WM8903_GP3_DB 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */ +#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */ + +/* + * R119 (0x77) - GPIO Control 4 + */ +#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */ +#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */ +#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */ +#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */ +#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM8903_GP4_PD 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */ +#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM8903_GP4_PU 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */ +#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */ +#define WM8903_GP4_DB 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */ +#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */ + +/* + * R120 (0x78) - GPIO Control 5 + */ +#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */ +#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */ +#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */ +#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */ +#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM8903_GP5_PD 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */ +#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM8903_GP5_PU 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */ +#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */ +#define WM8903_GP5_DB 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */ +#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ + +/* + * R121 (0x79) - Interrupt Status 1 + */ +#define WM8903_MICSHRT_EINT 0x8000 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_MASK 0x8000 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_SHIFT 15 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_WIDTH 1 /* MICSHRT_EINT */ +#define WM8903_MICDET_EINT 0x4000 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_MASK 0x4000 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_SHIFT 14 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_WIDTH 1 /* MICDET_EINT */ +#define WM8903_WSEQ_BUSY_EINT 0x2000 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_MASK 0x2000 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_SHIFT 13 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */ +#define WM8903_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM8903_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM8903_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM8903_GP5_EINT_WIDTH 1 /* GP5_EINT */ +#define WM8903_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM8903_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM8903_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM8903_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM8903_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM8903_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM8903_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM8903_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM8903_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM8903_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM8903_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM8903_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM8903_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM8903_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM8903_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM8903_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R122 (0x7A) - Interrupt Status 1 Mask + */ +#define WM8903_IM_MICSHRT_EINT 0x8000 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_MASK 0x8000 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_SHIFT 15 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_WIDTH 1 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICDET_EINT 0x4000 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_MASK 0x4000 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_SHIFT 14 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_WIDTH 1 /* IM_MICDET_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT 0x2000 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_MASK 0x2000 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_SHIFT 13 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ +#define WM8903_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM8903_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM8903_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM8903_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + +/* + * R123 (0x7B) - Interrupt Polarity 1 + */ +#define WM8903_MICSHRT_INV 0x8000 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_MASK 0x8000 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_SHIFT 15 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_WIDTH 1 /* MICSHRT_INV */ +#define WM8903_MICDET_INV 0x4000 /* MICDET_INV */ +#define WM8903_MICDET_INV_MASK 0x4000 /* MICDET_INV */ +#define WM8903_MICDET_INV_SHIFT 14 /* MICDET_INV */ +#define WM8903_MICDET_INV_WIDTH 1 /* MICDET_INV */ + +/* + * R126 (0x7E) - Interrupt Control + */ +#define WM8903_IRQ_POL 0x0001 /* IRQ_POL */ +#define WM8903_IRQ_POL_MASK 0x0001 /* IRQ_POL */ +#define WM8903_IRQ_POL_SHIFT 0 /* IRQ_POL */ +#define WM8903_IRQ_POL_WIDTH 1 /* IRQ_POL */ + +/* + * R129 (0x81) - Control Interface Test 1 + */ +#define WM8903_USER_KEY 0x0002 /* USER_KEY */ +#define WM8903_USER_KEY_MASK 0x0002 /* USER_KEY */ +#define WM8903_USER_KEY_SHIFT 1 /* USER_KEY */ +#define WM8903_USER_KEY_WIDTH 1 /* USER_KEY */ +#define WM8903_TEST_KEY 0x0001 /* TEST_KEY */ +#define WM8903_TEST_KEY_MASK 0x0001 /* TEST_KEY */ +#define WM8903_TEST_KEY_SHIFT 0 /* TEST_KEY */ +#define WM8903_TEST_KEY_WIDTH 1 /* TEST_KEY */ + +/* + * R149 (0x95) - Charge Pump Test 1 + */ +#define WM8903_CP_SW_KELVIN_MODE_MASK 0x0006 /* CP_SW_KELVIN_MODE - [2:1] */ +#define WM8903_CP_SW_KELVIN_MODE_SHIFT 1 /* CP_SW_KELVIN_MODE - [2:1] */ +#define WM8903_CP_SW_KELVIN_MODE_WIDTH 2 /* CP_SW_KELVIN_MODE - [2:1] */ + +/* + * R164 (0xA4) - Clock Rate Test 4 + */ +#define WM8903_ADC_DIG_MIC 0x0200 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_MASK 0x0200 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_SHIFT 9 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_WIDTH 1 /* ADC_DIG_MIC */ + +/* + * R172 (0xAC) - Analogue Output Bias 0 + */ +#define WM8903_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */ +#define WM8903_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */ +#define WM8903_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */ + +#endif diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c new file mode 100644 index 0000000..f41a578 --- /dev/null +++ b/sound/soc/codecs/wm8971.c @@ -0,0 +1,941 @@ +/* + * wm8971.c -- WM8971 ALSA SoC Audio driver + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly <kiraly@lab126.com> + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8971.h" + +#define WM8971_VERSION "0.9" + +#define WM8971_REG_COUNT 43 + +static struct workqueue_struct *wm8971_workq = NULL; + +/* codec private data */ +struct wm8971_priv { + unsigned int sysclk; +}; + +/* + * wm8971 register cache + * We can't read the WM8971 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8971_reg[] = { + 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */ + 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */ + 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */ + 0x0079, 0x0079, 0x0079, /* 40 */ +}; + +static inline unsigned int wm8971_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg < WM8971_REG_COUNT) + return cache[reg]; + + return -1; +} + +static inline void wm8971_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < WM8971_REG_COUNT) + cache[reg] = value; +} + +static int wm8971_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8971_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8971_reset(c) wm8971_write(c, WM8971_RESET, 0) + +/* WM8971 Controls */ +static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" }; +static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz", + "200Hz @ 48kHz" }; +static const char *wm8971_treble[] = { "8kHz", "4kHz" }; +static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" }; +static const char *wm8971_ng_type[] = { "Constant PGA Gain", + "Mute ADC Output" }; +static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" }; +static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA", + "Differential"}; +static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA", + "Differential"}; +static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"}; +static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"}; +static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; + +static const struct soc_enum wm8971_enum[] = { + SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass), /* 0 */ + SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter), + SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble), + SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func), + SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type), /* 4 */ + SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase), + SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */ + SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux), + SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel), + SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel), + SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol), /* 12 */ + SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux), +}; + +static const struct snd_kcontrol_new wm8971_snd_controls[] = { + SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0), + SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL, + 6, 1, 0), + SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1), + + SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V, + WM8971_ROUT1V, 7, 1, 0), + SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V, + WM8971_ROUT2V, 7, 1, 0), + SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0), + + SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0), + + SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1, + WM8971_LOUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1, + WM8971_ROUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1, + WM8971_MOUTM2, 4, 7, 1), + + SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V, + WM8971_ROUT1V, 0, 127, 0), + SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V, + WM8971_ROUT2V, 0, 127, 0), + + SOC_ENUM("Bass Boost", wm8971_enum[0]), + SOC_ENUM("Bass Filter", wm8971_enum[1]), + SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1), + + SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0), + SOC_ENUM("Treble Cut-off", wm8971_enum[2]), + + SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1), + + SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0), + + SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0), + SOC_ENUM("ALC Capture Function", wm8971_enum[3]), + SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0), + SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0), + SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0), + SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0), + SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0), + SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]), + SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0), + + SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0), + SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0), + + SOC_ENUM("Playback De-emphasis", wm8971_enum[5]), + SOC_ENUM("Playback Function", wm8971_enum[6]), + SOC_ENUM("Playback Phase", wm8971_enum[7]), + + SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0), +}; + +/* add non-DAPM controls */ +static int wm8971_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8971_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8971_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new wm8971_left_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new wm8971_right_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new wm8971_left_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new wm8971_right_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[11]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new wm8971_monomux_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[13]); + +static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_left_mixer_controls[0], + ARRAY_SIZE(wm8971_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_right_mixer_controls[0], + ARRAY_SIZE(wm8971_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0, + &wm8971_mono_mixer_controls[0], + ARRAY_SIZE(wm8971_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0), + SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8971_PWR1, 1, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0, + &wm8971_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0, + &wm8971_right_pga_controls), + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_right_line_controls), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("MIC"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line", "LINPUT1"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line", "RINPUT1"}, + {"Right Line Mux", "Mic", "MIC"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line", "LINPUT1"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line", "RINPUT1"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line", "LINPUT1"}, + {"Differential Mux", "Line", "RINPUT1"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, +}; + +static int wm8971_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8971_dapm_widgets, + ARRAY_SIZE(wm8971_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int wm8971_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 wm8971_priv *wm8971 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8971->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + 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 |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + wm8971_write(codec, WM8971_IFACE, iface); + return 0; +} + +static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8971_priv *wm8971 = codec->private_data; + u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3; + u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0; + int coeff = get_coeff(wm8971->sysclk, params_rate(params)); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + wm8971_write(codec, WM8971_IFACE, iface); + if (coeff >= 0) + wm8971_write(codec, WM8971_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8971_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8971_read_reg_cache(codec, WM8971_ADCDAC) & 0xfff7; + + if (mute) + wm8971_write(codec, WM8971_ADCDAC, mute_reg | 0x8); + else + wm8971_write(codec, WM8971_ADCDAC, mute_reg); + return 0; +} + +static int wm8971_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 pwr_reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x00c1); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* mute dac and set vmid to 500k, enable VREF */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x0140); + break; + case SND_SOC_BIAS_OFF: + wm8971_write(codec, WM8971_PWR1, 0x0001); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8971_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8971_dai = { + .name = "WM8971", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .ops = { + .hw_params = wm8971_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8971_mute, + .set_fmt = wm8971_set_dai_fmt, + .set_sysclk = wm8971_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(wm8971_dai); + +static void wm8971_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + wm8971_set_bias_level(codec, codec->bias_level); +} + +static int wm8971_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8971_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + u16 reg; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8971_reg); i++) { + if (i + 1 == WM8971_RESET) + continue; + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* charge wm8971 caps */ + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + wm8971_write(codec, WM8971_PWR1, reg | 0x01c0); + codec->bias_level = SND_SOC_BIAS_ON; + queue_delayed_work(wm8971_workq, &codec->delayed_work, + msecs_to_jiffies(1000)); + } + + return 0; +} + +static int wm8971_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8971"; + codec->owner = THIS_MODULE; + codec->read = wm8971_read_reg_cache; + codec->write = wm8971_write; + codec->set_bias_level = wm8971_set_bias_level; + codec->dai = &wm8971_dai; + codec->reg_cache_size = ARRAY_SIZE(wm8971_reg); + codec->num_dai = 1; + codec->reg_cache = kmemdup(wm8971_reg, sizeof(wm8971_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8971_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps - set vmid to 5k for quick power up */ + reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + wm8971_write(codec, WM8971_PWR1, reg | 0x01c0); + codec->bias_level = SND_SOC_BIAS_STANDBY; + queue_delayed_work(wm8971_workq, &codec->delayed_work, + msecs_to_jiffies(1000)); + + /* set the update bits */ + reg = wm8971_read_reg_cache(codec, WM8971_LDAC); + wm8971_write(codec, WM8971_LDAC, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_RDAC); + wm8971_write(codec, WM8971_RDAC, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LOUT1V); + wm8971_write(codec, WM8971_LOUT1V, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_ROUT1V); + wm8971_write(codec, WM8971_ROUT1V, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LOUT2V); + wm8971_write(codec, WM8971_LOUT2V, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_ROUT2V); + wm8971_write(codec, WM8971_ROUT2V, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LINVOL); + wm8971_write(codec, WM8971_LINVOL, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_RINVOL); + wm8971_write(codec, WM8971_RINVOL, reg | 0x0100); + + wm8971_add_controls(codec); + wm8971_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8971_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +static int wm8971_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8971_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + + codec->control_data = i2c; + + ret = wm8971_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8971\n"); + + return ret; +} + +static int wm8971_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8971_i2c_id[] = { + { "wm8971", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8971_i2c_id); + +static struct i2c_driver wm8971_i2c_driver = { + .driver = { + .name = "WM8971 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8971_i2c_probe, + .remove = wm8971_i2c_remove, + .id_table = wm8971_i2c_id, +}; + +static int wm8971_add_i2c_device(struct platform_device *pdev, + const struct wm8971_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8971_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8971", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8971_i2c_driver); + return -ENODEV; +} + +#endif + +static int wm8971_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8971_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8971_priv *wm8971; + int ret = 0; + + pr_info("WM8971 Audio Codec %s", WM8971_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8971 = kzalloc(sizeof(struct wm8971_priv), GFP_KERNEL); + if (wm8971 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8971; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8971_socdev = socdev; + + INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work); + wm8971_workq = create_workqueue("wm8971"); + if (wm8971_workq == NULL) { + kfree(codec->private_data); + kfree(codec); + return -ENOMEM; + } + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8971_add_i2c_device(pdev, setup); + } +#endif + /* Add other interfaces here */ + + if (ret != 0) { + destroy_workqueue(wm8971_workq); + kfree(codec->private_data); + kfree(codec); + } + + return ret; +} + +/* power down chip */ +static int wm8971_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); + if (wm8971_workq) + destroy_workqueue(wm8971_workq); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8971_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8971 = { + .probe = wm8971_probe, + .remove = wm8971_remove, + .suspend = wm8971_suspend, + .resume = wm8971_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8971); + +MODULE_DESCRIPTION("ASoC WM8971 driver"); +MODULE_AUTHOR("Lab126"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8971.h b/sound/soc/codecs/wm8971.h new file mode 100644 index 0000000..ef4f08f --- /dev/null +++ b/sound/soc/codecs/wm8971.h @@ -0,0 +1,64 @@ +/* + * wm8971.h -- audio driver for WM8971 + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly <kiraly@lab126.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8971_H +#define _WM8971_H + +#define WM8971_LINVOL 0x00 +#define WM8971_RINVOL 0x01 +#define WM8971_LOUT1V 0x02 +#define WM8971_ROUT1V 0x03 +#define WM8971_ADCDAC 0x05 +#define WM8971_IFACE 0x07 +#define WM8971_SRATE 0x08 +#define WM8971_LDAC 0x0a +#define WM8971_RDAC 0x0b +#define WM8971_BASS 0x0c +#define WM8971_TREBLE 0x0d +#define WM8971_RESET 0x0f +#define WM8971_ALC1 0x11 +#define WM8971_ALC2 0x12 +#define WM8971_ALC3 0x13 +#define WM8971_NGATE 0x14 +#define WM8971_LADC 0x15 +#define WM8971_RADC 0x16 +#define WM8971_ADCTL1 0x17 +#define WM8971_ADCTL2 0x18 +#define WM8971_PWR1 0x19 +#define WM8971_PWR2 0x1a +#define WM8971_ADCTL3 0x1b +#define WM8971_ADCIN 0x1f +#define WM8971_LADCIN 0x20 +#define WM8971_RADCIN 0x21 +#define WM8971_LOUTM1 0x22 +#define WM8971_LOUTM2 0x23 +#define WM8971_ROUTM1 0x24 +#define WM8971_ROUTM2 0x25 +#define WM8971_MOUTM1 0x26 +#define WM8971_MOUTM2 0x27 +#define WM8971_LOUT2V 0x28 +#define WM8971_ROUT2V 0x29 +#define WM8971_MOUTV 0x2A + +#define WM8971_SYSCLK 0 + +struct wm8971_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8971_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8971; + +#endif diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c new file mode 100644 index 0000000..572d22b --- /dev/null +++ b/sound/soc/codecs/wm8990.c @@ -0,0 +1,1635 @@ +/* + * wm8990.c -- WM8990 ALSA Soc Audio driver + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * lg@opensource.wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <asm/div64.h> + +#include "wm8990.h" + +#define WM8990_VERSION "0.2" + +/* codec private data */ +struct wm8990_priv { + unsigned int sysclk; + unsigned int pcmclk; +}; + +/* + * wm8990 register cache. Note that register 0 is not included in the + * cache. + */ +static const u16 wm8990_reg[] = { + 0x8990, /* R0 - Reset */ + 0x0000, /* R1 - Power Management (1) */ + 0x6000, /* R2 - Power Management (2) */ + 0x0000, /* R3 - Power Management (3) */ + 0x4050, /* R4 - Audio Interface (1) */ + 0x4000, /* R5 - Audio Interface (2) */ + 0x01C8, /* R6 - Clocking (1) */ + 0x0000, /* R7 - Clocking (2) */ + 0x0040, /* R8 - Audio Interface (3) */ + 0x0040, /* R9 - Audio Interface (4) */ + 0x0004, /* R10 - DAC CTRL */ + 0x00C0, /* R11 - Left DAC Digital Volume */ + 0x00C0, /* R12 - Right DAC Digital Volume */ + 0x0000, /* R13 - Digital Side Tone */ + 0x0100, /* R14 - ADC CTRL */ + 0x00C0, /* R15 - Left ADC Digital Volume */ + 0x00C0, /* R16 - Right ADC Digital Volume */ + 0x0000, /* R17 */ + 0x0000, /* R18 - GPIO CTRL 1 */ + 0x1000, /* R19 - GPIO1 & GPIO2 */ + 0x1010, /* R20 - GPIO3 & GPIO4 */ + 0x1010, /* R21 - GPIO5 & GPIO6 */ + 0x8000, /* R22 - GPIOCTRL 2 */ + 0x0800, /* R23 - GPIO_POL */ + 0x008B, /* R24 - Left Line Input 1&2 Volume */ + 0x008B, /* R25 - Left Line Input 3&4 Volume */ + 0x008B, /* R26 - Right Line Input 1&2 Volume */ + 0x008B, /* R27 - Right Line Input 3&4 Volume */ + 0x0000, /* R28 - Left Output Volume */ + 0x0000, /* R29 - Right Output Volume */ + 0x0066, /* R30 - Line Outputs Volume */ + 0x0022, /* R31 - Out3/4 Volume */ + 0x0079, /* R32 - Left OPGA Volume */ + 0x0079, /* R33 - Right OPGA Volume */ + 0x0003, /* R34 - Speaker Volume */ + 0x0003, /* R35 - ClassD1 */ + 0x0000, /* R36 */ + 0x0100, /* R37 - ClassD3 */ + 0x0079, /* R38 - ClassD4 */ + 0x0000, /* R39 - Input Mixer1 */ + 0x0000, /* R40 - Input Mixer2 */ + 0x0000, /* R41 - Input Mixer3 */ + 0x0000, /* R42 - Input Mixer4 */ + 0x0000, /* R43 - Input Mixer5 */ + 0x0000, /* R44 - Input Mixer6 */ + 0x0000, /* R45 - Output Mixer1 */ + 0x0000, /* R46 - Output Mixer2 */ + 0x0000, /* R47 - Output Mixer3 */ + 0x0000, /* R48 - Output Mixer4 */ + 0x0000, /* R49 - Output Mixer5 */ + 0x0000, /* R50 - Output Mixer6 */ + 0x0180, /* R51 - Out3/4 Mixer */ + 0x0000, /* R52 - Line Mixer1 */ + 0x0000, /* R53 - Line Mixer2 */ + 0x0000, /* R54 - Speaker Mixer */ + 0x0000, /* R55 - Additional Control */ + 0x0000, /* R56 - AntiPOP1 */ + 0x0000, /* R57 - AntiPOP2 */ + 0x0000, /* R58 - MICBIAS */ + 0x0000, /* R59 */ + 0x0008, /* R60 - PLL1 */ + 0x0031, /* R61 - PLL2 */ + 0x0026, /* R62 - PLL3 */ +}; + +/* + * read wm8990 register cache + */ +static inline unsigned int wm8990_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1); + return cache[reg]; +} + +/* + * write wm8990 register cache + */ +static inline void wm8990_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > (ARRAY_SIZE(wm8990_reg)) - 1); + + /* Reset register is uncached */ + if (reg == 0) + return; + + cache[reg] = value; +} + +/* + * write to the wm8990 register space + */ +static int wm8990_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + data[0] = reg & 0xFF; + data[1] = (value >> 8) & 0xFF; + data[2] = value & 0xFF; + + wm8990_write_reg_cache(codec, reg, value); + + if (codec->hw_write(codec->control_data, data, 3) == 2) + return 0; + else + return -EIO; +} + +#define wm8990_reset(c) wm8990_write(c, WM8990_RESET, 0) + +static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); + +static const DECLARE_TLV_DB_LINEAR(in_pga_tlv, -1650, 3000); + +static const DECLARE_TLV_DB_LINEAR(out_mix_tlv, 0, -2100); + +static const DECLARE_TLV_DB_LINEAR(out_pga_tlv, -7300, 600); + +static const DECLARE_TLV_DB_LINEAR(out_omix_tlv, -600, 0); + +static const DECLARE_TLV_DB_LINEAR(out_dac_tlv, -7163, 0); + +static const DECLARE_TLV_DB_LINEAR(in_adc_tlv, -7163, 1763); + +static const DECLARE_TLV_DB_LINEAR(out_sidetone_tlv, -3600, 0); + +static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = wm8990_read_reg_cache(codec, reg); + return wm8990_write(codec, reg, val | 0x0100); +} + +#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ + tlv_array) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + + +static const char *wm8990_digital_sidetone[] = + {"None", "Left ADC", "Right ADC", "Reserved"}; + +static const struct soc_enum wm8990_left_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE, + WM8990_ADC_TO_DACL_SHIFT, + WM8990_ADC_TO_DACL_MASK, + wm8990_digital_sidetone); + +static const struct soc_enum wm8990_right_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8990_DIGITAL_SIDE_TONE, + WM8990_ADC_TO_DACR_SHIFT, + WM8990_ADC_TO_DACR_MASK, + wm8990_digital_sidetone); + +static const char *wm8990_adcmode[] = + {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static const struct soc_enum wm8990_right_adcmode_enum = +SOC_ENUM_SINGLE(WM8990_ADC_CTRL, + WM8990_ADC_HPF_CUT_SHIFT, + WM8990_ADC_HPF_CUT_MASK, + wm8990_adcmode); + +static const struct snd_kcontrol_new wm8990_snd_controls[] = { +/* INMIXL */ +SOC_SINGLE("LIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L12MNBST_BIT, 1, 0), +SOC_SINGLE("LIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_L34MNBST_BIT, 1, 0), +/* INMIXR */ +SOC_SINGLE("RIN12 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R12MNBST_BIT, 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8990_INPUT_MIXER3, WM8990_R34MNBST_BIT, 1, 0), + +/* LOMIX */ +SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LLI3LOVOL_SHIFT, WM8990_LLI3LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LR12LOVOL_SHIFT, WM8990_LR12LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER3, + WM8990_LL12LOVOL_SHIFT, WM8990_LL12LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRI3LOVOL_SHIFT, WM8990_LRI3LOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER5, + WM8990_LRBLOVOL_SHIFT, WM8990_LRBLOVOL_MASK, 1, out_mix_tlv), + +/* ROMIX */ +SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RRI3ROVOL_SHIFT, WM8990_RRI3ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RL12ROVOL_SHIFT, WM8990_RL12ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8990_OUTPUT_MIXER4, + WM8990_RR12ROVOL_SHIFT, WM8990_RR12ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RLI3ROVOL_SHIFT, WM8990_RLI3ROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RLBROVOL_SHIFT, WM8990_RLBROVOL_MASK, 1, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8990_OUTPUT_MIXER6, + WM8990_RRBROVOL_SHIFT, WM8990_RRBROVOL_MASK, 1, out_mix_tlv), + +/* LOUT */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8990_LEFT_OUTPUT_VOLUME, + WM8990_LOUTVOL_SHIFT, WM8990_LOUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOUT ZC", WM8990_LEFT_OUTPUT_VOLUME, WM8990_LOZC_BIT, 1, 0), + +/* ROUT */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8990_RIGHT_OUTPUT_VOLUME, + WM8990_ROUTVOL_SHIFT, WM8990_ROUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROUT ZC", WM8990_RIGHT_OUTPUT_VOLUME, WM8990_ROZC_BIT, 1, 0), + +/* LOPGA */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8990_LEFT_OPGA_VOLUME, + WM8990_LOPGAVOL_SHIFT, WM8990_LOPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOPGA ZC Switch", WM8990_LEFT_OPGA_VOLUME, + WM8990_LOPGAZC_BIT, 1, 0), + +/* ROPGA */ +SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8990_RIGHT_OPGA_VOLUME, + WM8990_ROPGAVOL_SHIFT, WM8990_ROPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROPGA ZC Switch", WM8990_RIGHT_OPGA_VOLUME, + WM8990_ROPGAZC_BIT, 1, 0), + +SOC_SINGLE("LON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LONMUTE_BIT, 1, 0), +SOC_SINGLE("LOP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LOPMUTE_BIT, 1, 0), +SOC_SINGLE("LOP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_LOATTN_BIT, 1, 0), +SOC_SINGLE("RON Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_RONMUTE_BIT, 1, 0), +SOC_SINGLE("ROP Mute Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_ROPMUTE_BIT, 1, 0), +SOC_SINGLE("ROP Attenuation Switch", WM8990_LINE_OUTPUTS_VOLUME, + WM8990_ROATTN_BIT, 1, 0), + +SOC_SINGLE("OUT3 Mute Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT3MUTE_BIT, 1, 0), +SOC_SINGLE("OUT3 Attenuation Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT3ATTN_BIT, 1, 0), + +SOC_SINGLE("OUT4 Mute Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT4MUTE_BIT, 1, 0), +SOC_SINGLE("OUT4 Attenuation Switch", WM8990_OUT3_4_VOLUME, + WM8990_OUT4ATTN_BIT, 1, 0), + +SOC_SINGLE("Speaker Mode Switch", WM8990_CLASSD1, + WM8990_CDMODE_BIT, 1, 0), + +SOC_SINGLE("Speaker Output Attenuation Volume", WM8990_SPEAKER_VOLUME, + WM8990_SPKATTN_SHIFT, WM8990_SPKATTN_MASK, 0), +SOC_SINGLE("Speaker DC Boost Volume", WM8990_CLASSD3, + WM8990_DCGAIN_SHIFT, WM8990_DCGAIN_MASK, 0), +SOC_SINGLE("Speaker AC Boost Volume", WM8990_CLASSD3, + WM8990_ACGAIN_SHIFT, WM8990_ACGAIN_MASK, 0), +SOC_SINGLE_TLV("Speaker Volume", WM8990_CLASSD4, + WM8990_SPKVOL_SHIFT, WM8990_SPKVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("Speaker ZC Switch", WM8990_CLASSD4, + WM8990_SPKZC_SHIFT, WM8990_SPKZC_MASK, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8990_LEFT_DAC_DIGITAL_VOLUME, + WM8990_DACL_VOL_SHIFT, + WM8990_DACL_VOL_MASK, + 0, + out_dac_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8990_RIGHT_DAC_DIGITAL_VOLUME, + WM8990_DACR_VOL_SHIFT, + WM8990_DACR_VOL_MASK, + 0, + out_dac_tlv), + +SOC_ENUM("Left Digital Sidetone", wm8990_left_digital_sidetone_enum), +SOC_ENUM("Right Digital Sidetone", wm8990_right_digital_sidetone_enum), + +SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE, + WM8990_ADCL_DAC_SVOL_SHIFT, WM8990_ADCL_DAC_SVOL_MASK, 0, + out_sidetone_tlv), +SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8990_DIGITAL_SIDE_TONE, + WM8990_ADCR_DAC_SVOL_SHIFT, WM8990_ADCR_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + +SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8990_ADC_CTRL, + WM8990_ADC_HPF_ENA_BIT, 1, 0), + +SOC_ENUM("ADC HPF Mode", wm8990_right_adcmode_enum), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8990_LEFT_ADC_DIGITAL_VOLUME, + WM8990_ADCL_VOL_SHIFT, + WM8990_ADCL_VOL_MASK, + 0, + in_adc_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8990_RIGHT_ADC_DIGITAL_VOLUME, + WM8990_ADCR_VOL_SHIFT, + WM8990_ADCR_VOL_MASK, + 0, + in_adc_tlv), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LIN12VOL_SHIFT, + WM8990_LIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN12 ZC Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LI12ZC_BIT, 1, 0), + +SOC_SINGLE("LIN12 Mute Switch", WM8990_LEFT_LINE_INPUT_1_2_VOLUME, + WM8990_LI12MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LIN34VOL_SHIFT, + WM8990_LIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN34 ZC Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LI34ZC_BIT, 1, 0), + +SOC_SINGLE("LIN34 Mute Switch", WM8990_LEFT_LINE_INPUT_3_4_VOLUME, + WM8990_LI34MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RIN12VOL_SHIFT, + WM8990_RIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN12 ZC Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RI12ZC_BIT, 1, 0), + +SOC_SINGLE("RIN12 Mute Switch", WM8990_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8990_RI12MUTE_BIT, 1, 0), + +SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RIN34VOL_SHIFT, + WM8990_RIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN34 ZC Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RI34ZC_BIT, 1, 0), + +SOC_SINGLE("RIN34 Mute Switch", WM8990_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8990_RI34MUTE_BIT, 1, 0), + +}; + +/* add non dapm controls */ +static int wm8990_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8990_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8990_snd_controls[i], codec, + NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * _DAPM_ Controls + */ + +static int inmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 reg, fakepower; + + reg = wm8990_read_reg_cache(w->codec, WM8990_POWER_MANAGEMENT_2); + fakepower = wm8990_read_reg_cache(w->codec, WM8990_INTDRIVBITS); + + if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) | + (1 << WM8990_AINLMUX_PWR_BIT))) { + reg |= WM8990_AINL_ENA; + } else { + reg &= ~WM8990_AINL_ENA; + } + + if (fakepower & ((1 << WM8990_INMIXR_PWR_BIT) | + (1 << WM8990_AINRMUX_PWR_BIT))) { + reg |= WM8990_AINR_ENA; + } else { + reg &= ~WM8990_AINL_ENA; + } + wm8990_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg); + + return 0; +} + +static int outmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u32 reg_shift = kcontrol->private_value & 0xfff; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) : + reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER1); + if (reg & WM8990_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER2); + if (reg & WM8990_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER); + if (reg & WM8990_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8): + reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER); + if (reg & WM8990_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const unsigned int in_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600), +}; + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8990_dapm_lin12_pga_controls[] = { +SOC_DAPM_SINGLE("LIN1 Switch", WM8990_INPUT_MIXER2, WM8990_LMN1_BIT, 1, 0), +SOC_DAPM_SINGLE("LIN2 Switch", WM8990_INPUT_MIXER2, WM8990_LMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8990_dapm_lin34_pga_controls[] = { +SOC_DAPM_SINGLE("LIN3 Switch", WM8990_INPUT_MIXER2, WM8990_LMN3_BIT, 1, 0), +SOC_DAPM_SINGLE("LIN4 Switch", WM8990_INPUT_MIXER2, WM8990_LMP4_BIT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8990_dapm_rin12_pga_controls[] = { +SOC_DAPM_SINGLE("RIN1 Switch", WM8990_INPUT_MIXER2, WM8990_RMN1_BIT, 1, 0), +SOC_DAPM_SINGLE("RIN2 Switch", WM8990_INPUT_MIXER2, WM8990_RMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8990_dapm_rin34_pga_controls[] = { +SOC_DAPM_SINGLE("RIN3 Switch", WM8990_INPUT_MIXER2, WM8990_RMN3_BIT, 1, 0), +SOC_DAPM_SINGLE("RIN4 Switch", WM8990_INPUT_MIXER2, WM8990_RMP4_BIT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8990_dapm_inmixl_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8990_INPUT_MIXER3, + WM8990_LDBVOL_SHIFT, WM8990_LDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8990_INPUT_MIXER5, WM8990_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("LINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, + 1, 0), +SOC_DAPM_SINGLE("LINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8990_dapm_inmixr_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8990_INPUT_MIXER4, + WM8990_RDBVOL_SHIFT, WM8990_RDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8990_INPUT_MIXER6, WM8990_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8990_INPUT_MIXER3, WM8990_L12MNB_BIT, + 1, 0), +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8990_INPUT_MIXER3, WM8990_L34MNB_BIT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8990_ainlmux[] = + {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static const struct soc_enum wm8990_ainlmux_enum = +SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINLMODE_SHIFT, + ARRAY_SIZE(wm8990_ainlmux), wm8990_ainlmux); + +static const struct snd_kcontrol_new wm8990_dapm_ainlmux_controls = +SOC_DAPM_ENUM("Route", wm8990_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8990_ainrmux[] = + {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static const struct soc_enum wm8990_ainrmux_enum = +SOC_ENUM_SINGLE(WM8990_INPUT_MIXER1, WM8990_AINRMODE_SHIFT, + ARRAY_SIZE(wm8990_ainrmux), wm8990_ainrmux); + +static const struct snd_kcontrol_new wm8990_dapm_ainrmux_controls = +SOC_DAPM_ENUM("Route", wm8990_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8990_dapm_rxvoice_controls[] = { +SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8990_INPUT_MIXER5, WM8990_LR4BVOL_SHIFT, + WM8990_LR4BVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8990_INPUT_MIXER6, WM8990_RL4BVOL_SHIFT, + WM8990_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lomix_controls[] = { +SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LRBLO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LLBLO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LRI3LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LLI3LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LR12LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER1, + WM8990_LL12LO_BIT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8990_OUTPUT_MIXER1, + WM8990_LDLO_BIT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8990_dapm_romix_controls[] = { +SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RLBRO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RRBRO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RLI3RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RRI3RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RL12RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8990_OUTPUT_MIXER2, + WM8990_RR12RO_BIT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8990_OUTPUT_MIXER2, + WM8990_RDRO_BIT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lonmix_controls[] = { +SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LLOPGALON_BIT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LROPGALON_BIT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8990_LINE_MIXER1, + WM8990_LOPLON_BIT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8990_dapm_lopmix_controls[] = { +SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER1, + WM8990_LR12LOP_BIT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER1, + WM8990_LL12LOP_BIT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8990_LINE_MIXER1, + WM8990_LLOPGALOP_BIT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8990_dapm_ronmix_controls[] = { +SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RROPGARON_BIT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RLOPGARON_BIT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8990_LINE_MIXER2, + WM8990_ROPRON_BIT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8990_dapm_ropmix_controls[] = { +SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8990_LINE_MIXER2, + WM8990_RL12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8990_LINE_MIXER2, + WM8990_RR12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8990_LINE_MIXER2, + WM8990_RROPGAROP_BIT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8990_dapm_out3mix_controls[] = { +SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER, + WM8990_LI4O3_BIT, 1, 0), +SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8990_OUT3_4_MIXER, + WM8990_LPGAO3_BIT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8990_dapm_out4mix_controls[] = { +SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8990_OUT3_4_MIXER, + WM8990_RPGAO4_BIT, 1, 0), +SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8990_OUT3_4_MIXER, + WM8990_RI4O4_BIT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8990_dapm_spkmix_controls[] = { +SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_LI2SPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_LB2SPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8990_SPEAKER_MIXER, + WM8990_LOPGASPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8990_SPEAKER_MIXER, + WM8990_LDSPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8990_SPEAKER_MIXER, + WM8990_RDSPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8990_SPEAKER_MIXER, + WM8990_ROPGASPK_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_RL12ROP_BIT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8990_SPEAKER_MIXER, + WM8990_RI2SPK_BIT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8990_dapm_widgets[] = { +/* Input Side */ +/* Input Lines */ +SND_SOC_DAPM_INPUT("LIN1"), +SND_SOC_DAPM_INPUT("LIN2"), +SND_SOC_DAPM_INPUT("LIN3"), +SND_SOC_DAPM_INPUT("LIN4/RXN"), +SND_SOC_DAPM_INPUT("RIN3"), +SND_SOC_DAPM_INPUT("RIN4/RXP"), +SND_SOC_DAPM_INPUT("RIN1"), +SND_SOC_DAPM_INPUT("RIN2"), +SND_SOC_DAPM_INPUT("Internal ADC Source"), + +/* DACs */ +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8990_POWER_MANAGEMENT_2, + WM8990_ADCL_ENA_BIT, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8990_POWER_MANAGEMENT_2, + WM8990_ADCR_ENA_BIT, 0), + +/* Input PGAs */ +SND_SOC_DAPM_MIXER("LIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN12_ENA_BIT, + 0, &wm8990_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_lin12_pga_controls)), +SND_SOC_DAPM_MIXER("LIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_LIN34_ENA_BIT, + 0, &wm8990_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_lin34_pga_controls)), +SND_SOC_DAPM_MIXER("RIN12 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN12_ENA_BIT, + 0, &wm8990_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_rin12_pga_controls)), +SND_SOC_DAPM_MIXER("RIN34 PGA", WM8990_POWER_MANAGEMENT_2, WM8990_RIN34_ENA_BIT, + 0, &wm8990_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8990_dapm_rin34_pga_controls)), + +/* INMIXL */ +SND_SOC_DAPM_MIXER_E("INMIXL", WM8990_INTDRIVBITS, WM8990_INMIXL_PWR_BIT, 0, + &wm8990_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8990_dapm_inmixl_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINLMUX */ +SND_SOC_DAPM_MUX_E("AILNMUX", WM8990_INTDRIVBITS, WM8990_AINLMUX_PWR_BIT, 0, + &wm8990_dapm_ainlmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* INMIXR */ +SND_SOC_DAPM_MIXER_E("INMIXR", WM8990_INTDRIVBITS, WM8990_INMIXR_PWR_BIT, 0, + &wm8990_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8990_dapm_inmixr_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINRMUX */ +SND_SOC_DAPM_MUX_E("AIRNMUX", WM8990_INTDRIVBITS, WM8990_AINRMUX_PWR_BIT, 0, + &wm8990_dapm_ainrmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* Output Side */ +/* DACs */ +SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8990_POWER_MANAGEMENT_3, + WM8990_DACL_ENA_BIT, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8990_POWER_MANAGEMENT_3, + WM8990_DACR_ENA_BIT, 0), + +/* LOMIX */ +SND_SOC_DAPM_MIXER_E("LOMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOMIX_ENA_BIT, + 0, &wm8990_dapm_lomix_controls[0], + ARRAY_SIZE(wm8990_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LONMIX */ +SND_SOC_DAPM_MIXER("LONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LON_ENA_BIT, 0, + &wm8990_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8990_dapm_lonmix_controls)), + +/* LOPMIX */ +SND_SOC_DAPM_MIXER("LOPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_LOP_ENA_BIT, 0, + &wm8990_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8990_dapm_lopmix_controls)), + +/* OUT3MIX */ +SND_SOC_DAPM_MIXER("OUT3MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT3_ENA_BIT, 0, + &wm8990_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8990_dapm_out3mix_controls)), + +/* SPKMIX */ +SND_SOC_DAPM_MIXER_E("SPKMIX", WM8990_POWER_MANAGEMENT_1, WM8990_SPK_ENA_BIT, 0, + &wm8990_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8990_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + +/* OUT4MIX */ +SND_SOC_DAPM_MIXER("OUT4MIX", WM8990_POWER_MANAGEMENT_1, WM8990_OUT4_ENA_BIT, 0, + &wm8990_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8990_dapm_out4mix_controls)), + +/* ROPMIX */ +SND_SOC_DAPM_MIXER("ROPMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROP_ENA_BIT, 0, + &wm8990_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8990_dapm_ropmix_controls)), + +/* RONMIX */ +SND_SOC_DAPM_MIXER("RONMIX", WM8990_POWER_MANAGEMENT_3, WM8990_RON_ENA_BIT, 0, + &wm8990_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8990_dapm_ronmix_controls)), + +/* ROMIX */ +SND_SOC_DAPM_MIXER_E("ROMIX", WM8990_POWER_MANAGEMENT_3, WM8990_ROMIX_ENA_BIT, + 0, &wm8990_dapm_romix_controls[0], + ARRAY_SIZE(wm8990_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LOUT PGA */ +SND_SOC_DAPM_PGA("LOUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_LOUT_ENA_BIT, 0, + NULL, 0), + +/* ROUT PGA */ +SND_SOC_DAPM_PGA("ROUT PGA", WM8990_POWER_MANAGEMENT_1, WM8990_ROUT_ENA_BIT, 0, + NULL, 0), + +/* LOPGA */ +SND_SOC_DAPM_PGA("LOPGA", WM8990_POWER_MANAGEMENT_3, WM8990_LOPGA_ENA_BIT, 0, + NULL, 0), + +/* ROPGA */ +SND_SOC_DAPM_PGA("ROPGA", WM8990_POWER_MANAGEMENT_3, WM8990_ROPGA_ENA_BIT, 0, + NULL, 0), + +/* MICBIAS */ +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8990_POWER_MANAGEMENT_1, + WM8990_MICBIAS_ENA_BIT, 0), + +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AILNMUX */ + {"AILNMUX", "INMIXL Mix", "INMIXL"}, + {"AILNMUX", "DIFFINL Mix", "LIN12PGA"}, + {"AILNMUX", "DIFFINL Mix", "LIN34PGA"}, + {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Left ADC", NULL, "AILNMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AIRNMUX */ + {"AIRNMUX", "INMIXR Mix", "INMIXR"}, + {"AIRNMUX", "DIFFINR Mix", "RIN12PGA"}, + {"AIRNMUX", "DIFFINR Mix", "RIN34PGA"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXN"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Right ADC", NULL, "AIRNMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Left DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXP"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +static int wm8990_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8990_dapm_widgets, + ARRAY_SIZE(wm8990_dapm_widgets)); + + /* set up the WM8990 audio map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 div2; + u32 n; + u32 k; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 16) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8990 N value outwith recommended range! N = %d\n", Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + struct snd_soc_codec *codec = codec_dai->codec; + struct _pll_div pll_div; + + if (freq_in && freq_out) { + pll_factors(&pll_div, freq_out * 4, freq_in); + + /* Turn on PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + reg |= WM8990_PLL_ENA; + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg); + + /* sysclk comes from PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2); + wm8990_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC); + + /* set up N , fractional mode and pre-divisor if neccessary */ + wm8990_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM | + (pll_div.div2?WM8990_PRESCALE:0)); + wm8990_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8)); + wm8990_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF)); + } else { + /* Turn on PLL */ + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + reg &= ~WM8990_PLL_ENA; + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg); + } + return 0; +} + +/* + * Clock after PLL and dividers + */ +static int wm8990_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8990_priv *wm8990 = codec->private_data; + + wm8990->sysclk = freq; + return 0; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 audio1, audio3; + + audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); + audio3 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8990_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8990_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8990_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8990_AIF_TMF_I2S; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8990_AIF_TMF_RIGHTJ; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8990_AIF_TMF_LEFTJ; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8990_AIF_TMF_DSP; + audio1 &= ~WM8990_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8990_AIF_TMF_DSP | WM8990_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); + wm8990_write(codec, WM8990_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8990_MCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_MCLK_DIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_DACCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_DAC_CLKDIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_ADCCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + ~WM8990_ADC_CLKDIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + break; + case WM8990_BCLK_DIV: + reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_1) & + ~WM8990_BCLK_DIV_MASK; + wm8990_write(codec, WM8990_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8990_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); + + audio1 &= ~WM8990_AIF_WL_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + audio1 |= WM8990_AIF_WL_20BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + audio1 |= WM8990_AIF_WL_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio1 |= WM8990_AIF_WL_32BITS; + break; + } + + wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8990_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 val; + + val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE; + + if (mute) + wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + else + wm8990_write(codec, WM8990_DAC_CTRL, val); + + return 0; +} + +static int wm8990_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 val; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Enable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + WM8990_DIS_RLINE | WM8990_DIS_OUT3 | + WM8990_DIS_OUT4 | WM8990_DIS_LOUT | + WM8990_DIS_ROUT); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_VMIDTOG); + + /* Delay to allow output caps to discharge */ + msleep(msecs_to_jiffies(300)); + + /* Disable VMIDTOG */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL); + + /* disable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, 0); + + /* Enable outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00); + + msleep(msecs_to_jiffies(50)); + + /* Enable VMID at 2x50k */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02); + + msleep(msecs_to_jiffies(100)); + + /* Enable VREF */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); + + msleep(msecs_to_jiffies(600)); + + /* Enable BUFIOEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_BUFIOEN); + + /* Disable outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN); + } else { + /* ON -> standby */ + + } + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_POBCTRL | WM8990_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + WM8990_BUFDCOPEN | WM8990_POBCTRL | + WM8990_BUFIOEN); + + /* mute DAC */ + val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL); + wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + + /* Enable any disabled outputs */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); + + /* Disable VMID */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01); + + msleep(msecs_to_jiffies(300)); + + /* Enable all output discharge bits */ + wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + WM8990_DIS_RLINE | WM8990_DIS_OUT3 | + WM8990_DIS_OUT4 | WM8990_DIS_LOUT | + WM8990_DIS_ROUT); + + /* Disable VREF */ + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8990_write(codec, WM8990_ANTIPOP2, 0x0); + break; + } + + codec->bias_level = level; + return 0; +} + +#define WM8990_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8990_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +/* + * The WM8990 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +struct snd_soc_dai wm8990_dai = { +/* ADC/DAC on primary */ + .name = "WM8990 ADC/DAC Primary", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8990_RATES, + .formats = WM8990_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8990_RATES, + .formats = WM8990_FORMATS,}, + .ops = { + .hw_params = wm8990_hw_params,}, + .dai_ops = { + .digital_mute = wm8990_mute, + .set_fmt = wm8990_set_dai_fmt, + .set_clkdiv = wm8990_set_dai_clkdiv, + .set_pll = wm8990_set_dai_pll, + .set_sysclk = wm8990_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(wm8990_dai); + +static int wm8990_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + /* we only need to suspend if we are a valid card */ + if (!codec->card) + return 0; + + wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8990_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* we only need to resume if we are a valid card */ + if (!codec->card) + return 0; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8990_reg); i++) { + if (i + 1 == WM8990_RESET) + continue; + data[0] = ((i + 1) << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +/* + * initialise the WM8990 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8990_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + u16 reg; + int ret = 0; + + codec->name = "WM8990"; + codec->owner = THIS_MODULE; + codec->read = wm8990_read_reg_cache; + codec->write = wm8990_write; + codec->set_bias_level = wm8990_set_bias_level; + codec->dai = &wm8990_dai; + codec->num_dai = 2; + codec->reg_cache_size = ARRAY_SIZE(wm8990_reg); + codec->reg_cache = kmemdup(wm8990_reg, sizeof(wm8990_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8990_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8990: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps */ + codec->bias_level = SND_SOC_BIAS_OFF; + wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + reg = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_4); + wm8990_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1); + + reg = wm8990_read_reg_cache(codec, WM8990_GPIO1_GPIO2) & + ~WM8990_GPIO1_SEL_MASK; + wm8990_write(codec, WM8990_GPIO1_GPIO2, reg | 1); + + reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA); + + wm8990_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + wm8990_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + wm8990_add_controls(codec); + wm8990_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8990: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8990_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM891 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x34 + * high = 0x36 + */ + +static int wm8990_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8990_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8990_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8990\n"); + + return ret; +} + +static int wm8990_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8990_i2c_id[] = { + { "wm8990", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id); + +static struct i2c_driver wm8990_i2c_driver = { + .driver = { + .name = "WM8990 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8990_i2c_probe, + .remove = wm8990_i2c_remove, + .id_table = wm8990_i2c_id, +}; + +static int wm8990_add_i2c_device(struct platform_device *pdev, + const struct wm8990_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8990_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8990", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8990_i2c_driver); + return -ENODEV; +} +#endif + +static int wm8990_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8990_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8990_priv *wm8990; + int ret; + + pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8990 = kzalloc(sizeof(struct wm8990_priv), GFP_KERNEL); + if (wm8990 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8990; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8990_socdev = socdev; + + ret = -ENODEV; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8990_add_i2c_device(pdev, setup); + } +#endif + + if (ret != 0) { + kfree(codec->private_data); + kfree(codec); + } + return ret; +} + +/* power down chip */ +static int wm8990_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8990_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8990_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8990 = { + .probe = wm8990_probe, + .remove = wm8990_remove, + .suspend = wm8990_suspend, + .resume = wm8990_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8990); + +MODULE_DESCRIPTION("ASoC WM8990 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8990.h b/sound/soc/codecs/wm8990.h new file mode 100644 index 0000000..0e192f3 --- /dev/null +++ b/sound/soc/codecs/wm8990.h @@ -0,0 +1,843 @@ +/* + * wm8990.h -- audio driver for WM8990 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __WM8990REGISTERDEFS_H__ +#define __WM8990REGISTERDEFS_H__ + +/* + * Register values. + */ +#define WM8990_RESET 0x00 +#define WM8990_POWER_MANAGEMENT_1 0x01 +#define WM8990_POWER_MANAGEMENT_2 0x02 +#define WM8990_POWER_MANAGEMENT_3 0x03 +#define WM8990_AUDIO_INTERFACE_1 0x04 +#define WM8990_AUDIO_INTERFACE_2 0x05 +#define WM8990_CLOCKING_1 0x06 +#define WM8990_CLOCKING_2 0x07 +#define WM8990_AUDIO_INTERFACE_3 0x08 +#define WM8990_AUDIO_INTERFACE_4 0x09 +#define WM8990_DAC_CTRL 0x0A +#define WM8990_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8990_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8990_DIGITAL_SIDE_TONE 0x0D +#define WM8990_ADC_CTRL 0x0E +#define WM8990_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8990_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8990_GPIO_CTRL_1 0x12 +#define WM8990_GPIO1_GPIO2 0x13 +#define WM8990_GPIO3_GPIO4 0x14 +#define WM8990_GPIO5_GPIO6 0x15 +#define WM8990_GPIOCTRL_2 0x16 +#define WM8990_GPIO_POL 0x17 +#define WM8990_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8990_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8990_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8990_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8990_LEFT_OUTPUT_VOLUME 0x1C +#define WM8990_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8990_LINE_OUTPUTS_VOLUME 0x1E +#define WM8990_OUT3_4_VOLUME 0x1F +#define WM8990_LEFT_OPGA_VOLUME 0x20 +#define WM8990_RIGHT_OPGA_VOLUME 0x21 +#define WM8990_SPEAKER_VOLUME 0x22 +#define WM8990_CLASSD1 0x23 +#define WM8990_CLASSD3 0x25 +#define WM8990_CLASSD4 0x26 +#define WM8990_INPUT_MIXER1 0x27 +#define WM8990_INPUT_MIXER2 0x28 +#define WM8990_INPUT_MIXER3 0x29 +#define WM8990_INPUT_MIXER4 0x2A +#define WM8990_INPUT_MIXER5 0x2B +#define WM8990_INPUT_MIXER6 0x2C +#define WM8990_OUTPUT_MIXER1 0x2D +#define WM8990_OUTPUT_MIXER2 0x2E +#define WM8990_OUTPUT_MIXER3 0x2F +#define WM8990_OUTPUT_MIXER4 0x30 +#define WM8990_OUTPUT_MIXER5 0x31 +#define WM8990_OUTPUT_MIXER6 0x32 +#define WM8990_OUT3_4_MIXER 0x33 +#define WM8990_LINE_MIXER1 0x34 +#define WM8990_LINE_MIXER2 0x35 +#define WM8990_SPEAKER_MIXER 0x36 +#define WM8990_ADDITIONAL_CONTROL 0x37 +#define WM8990_ANTIPOP1 0x38 +#define WM8990_ANTIPOP2 0x39 +#define WM8990_MICBIAS 0x3A +#define WM8990_PLL1 0x3C +#define WM8990_PLL2 0x3D +#define WM8990_PLL3 0x3E +#define WM8990_INTDRIVBITS 0x3F + +#define WM8990_REGISTER_COUNT 60 +#define WM8990_MAX_REGISTER 0x3F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Reset + */ +#define WM8990_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8990_SPK_ENA 0x1000 /* SPK_ENA */ +#define WM8990_SPK_ENA_BIT 12 +#define WM8990_OUT3_ENA 0x0800 /* OUT3_ENA */ +#define WM8990_OUT3_ENA_BIT 11 +#define WM8990_OUT4_ENA 0x0400 /* OUT4_ENA */ +#define WM8990_OUT4_ENA_BIT 10 +#define WM8990_LOUT_ENA 0x0200 /* LOUT_ENA */ +#define WM8990_LOUT_ENA_BIT 9 +#define WM8990_ROUT_ENA 0x0100 /* ROUT_ENA */ +#define WM8990_ROUT_ENA_BIT 8 +#define WM8990_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */ +#define WM8990_MICBIAS_ENA_BIT 4 +#define WM8990_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */ +#define WM8990_VREF_ENA 0x0001 /* VREF_ENA */ +#define WM8990_VREF_ENA_BIT 0 + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8990_PLL_ENA 0x8000 /* PLL_ENA */ +#define WM8990_PLL_ENA_BIT 15 +#define WM8990_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8990_TSHUT_ENA_BIT 14 +#define WM8990_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8990_TSHUT_OPDIS_BIT 13 +#define WM8990_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8990_OPCLK_ENA_BIT 11 +#define WM8990_AINL_ENA 0x0200 /* AINL_ENA */ +#define WM8990_AINL_ENA_BIT 9 +#define WM8990_AINR_ENA 0x0100 /* AINR_ENA */ +#define WM8990_AINR_ENA_BIT 8 +#define WM8990_LIN34_ENA 0x0080 /* LIN34_ENA */ +#define WM8990_LIN34_ENA_BIT 7 +#define WM8990_LIN12_ENA 0x0040 /* LIN12_ENA */ +#define WM8990_LIN12_ENA_BIT 6 +#define WM8990_RIN34_ENA 0x0020 /* RIN34_ENA */ +#define WM8990_RIN34_ENA_BIT 5 +#define WM8990_RIN12_ENA 0x0010 /* RIN12_ENA */ +#define WM8990_RIN12_ENA_BIT 4 +#define WM8990_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8990_ADCL_ENA_BIT 1 +#define WM8990_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8990_ADCR_ENA_BIT 0 + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8990_LON_ENA 0x2000 /* LON_ENA */ +#define WM8990_LON_ENA_BIT 13 +#define WM8990_LOP_ENA 0x1000 /* LOP_ENA */ +#define WM8990_LOP_ENA_BIT 12 +#define WM8990_RON_ENA 0x0800 /* RON_ENA */ +#define WM8990_RON_ENA_BIT 11 +#define WM8990_ROP_ENA 0x0400 /* ROP_ENA */ +#define WM8990_ROP_ENA_BIT 10 +#define WM8990_LOPGA_ENA 0x0080 /* LOPGA_ENA */ +#define WM8990_LOPGA_ENA_BIT 7 +#define WM8990_ROPGA_ENA 0x0040 /* ROPGA_ENA */ +#define WM8990_ROPGA_ENA_BIT 6 +#define WM8990_LOMIX_ENA 0x0020 /* LOMIX_ENA */ +#define WM8990_LOMIX_ENA_BIT 5 +#define WM8990_ROMIX_ENA 0x0010 /* ROMIX_ENA */ +#define WM8990_ROMIX_ENA_BIT 4 +#define WM8990_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8990_DACL_ENA_BIT 1 +#define WM8990_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8990_DACR_ENA_BIT 0 + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8990_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8990_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8990_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8990_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8990_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8990_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8990_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8990_AIF_WL_16BITS (0 << 5) +#define WM8990_AIF_WL_20BITS (1 << 5) +#define WM8990_AIF_WL_24BITS (2 << 5) +#define WM8990_AIF_WL_32BITS (3 << 5) +#define WM8990_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8990_AIF_TMF_RIGHTJ (0 << 3) +#define WM8990_AIF_TMF_LEFTJ (1 << 3) +#define WM8990_AIF_TMF_I2S (2 << 3) +#define WM8990_AIF_TMF_DSP (3 << 3) + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8990_DACL_SRC 0x8000 /* DACL_SRC */ +#define WM8990_DACR_SRC 0x4000 /* DACR_SRC */ +#define WM8990_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8990_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8990_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST */ +#define WM8990_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8990_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8990_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8990_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8990_LOOPBACK 0x0001 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking (1) + */ +#define WM8990_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8990_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8990_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */ +#define WM8990_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8990_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8990_BCLK_DIV_1 (0x0 << 1) +#define WM8990_BCLK_DIV_1_5 (0x1 << 1) +#define WM8990_BCLK_DIV_2 (0x2 << 1) +#define WM8990_BCLK_DIV_3 (0x3 << 1) +#define WM8990_BCLK_DIV_4 (0x4 << 1) +#define WM8990_BCLK_DIV_5_5 (0x5 << 1) +#define WM8990_BCLK_DIV_6 (0x6 << 1) +#define WM8990_BCLK_DIV_8 (0x7 << 1) +#define WM8990_BCLK_DIV_11 (0x8 << 1) +#define WM8990_BCLK_DIV_12 (0x9 << 1) +#define WM8990_BCLK_DIV_16 (0xA << 1) +#define WM8990_BCLK_DIV_22 (0xB << 1) +#define WM8990_BCLK_DIV_24 (0xC << 1) +#define WM8990_BCLK_DIV_32 (0xD << 1) +#define WM8990_BCLK_DIV_44 (0xE << 1) +#define WM8990_BCLK_DIV_48 (0xF << 1) + +/* + * R7 (0x07) - Clocking (2) + */ +#define WM8990_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8990_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8990_CLK_FORCE 0x2000 /* CLK_FORCE */ +#define WM8990_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */ +#define WM8990_MCLK_DIV_1 (0 << 11) +#define WM8990_MCLK_DIV_2 (2 << 11) +#define WM8990_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8990_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV */ +#define WM8990_ADC_CLKDIV_1 (0 << 5) +#define WM8990_ADC_CLKDIV_1_5 (1 << 5) +#define WM8990_ADC_CLKDIV_2 (2 << 5) +#define WM8990_ADC_CLKDIV_3 (3 << 5) +#define WM8990_ADC_CLKDIV_4 (4 << 5) +#define WM8990_ADC_CLKDIV_5_5 (5 << 5) +#define WM8990_ADC_CLKDIV_6 (6 << 5) +#define WM8990_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */ +#define WM8990_DAC_CLKDIV_1 (0 << 2) +#define WM8990_DAC_CLKDIV_1_5 (1 << 2) +#define WM8990_DAC_CLKDIV_2 (2 << 2) +#define WM8990_DAC_CLKDIV_3 (3 << 2) +#define WM8990_DAC_CLKDIV_4 (4 << 2) +#define WM8990_DAC_CLKDIV_5_5 (5 << 2) +#define WM8990_DAC_CLKDIV_6 (6 << 2) + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8990_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8990_AIF_MSTR2 0x4000 /* AIF_MSTR2 */ +#define WM8990_AIF_SEL 0x2000 /* AIF_SEL */ +#define WM8990_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */ +#define WM8990_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8990_ALRCGPIO1 0x8000 /* ALRCGPIO1 */ +#define WM8990_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */ +#define WM8990_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8990_DACLRC_DIR 0x0800 /* DACLRC_DIR */ +#define WM8990_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8990_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */ +#define WM8990_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8990_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8990_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8990_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */ +#define WM8990_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */ +#define WM8990_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8990_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8990_DACR_DATINV 0x0001 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8990_DAC_VU 0x0100 /* DAC_VU */ +#define WM8990_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8990_DACL_VOL_SHIFT 0 +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8990_DAC_VU 0x0100 /* DAC_VU */ +#define WM8990_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8990_DACR_VOL_SHIFT 0 +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8990_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL */ +#define WM8990_ADCL_DAC_SVOL_SHIFT 9 +#define WM8990_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL */ +#define WM8990_ADCR_DAC_SVOL_SHIFT 5 +#define WM8990_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */ +#define WM8990_ADC_TO_DACL_SHIFT 2 +#define WM8990_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */ +#define WM8990_ADC_TO_DACR_SHIFT 0 + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8990_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */ +#define WM8990_ADC_HPF_ENA_BIT 8 +#define WM8990_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */ +#define WM8990_ADC_HPF_CUT_SHIFT 5 +#define WM8990_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8990_ADCL_DATINV_BIT 1 +#define WM8990_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8990_ADCR_DATINV_BIT 0 + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8990_ADC_VU 0x0100 /* ADC_VU */ +#define WM8990_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8990_ADCL_VOL_SHIFT 0 + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8990_ADC_VU 0x0100 /* ADC_VU */ +#define WM8990_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8990_ADCR_VOL_SHIFT 0 + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8990_IRQ 0x1000 /* IRQ */ +#define WM8990_TEMPOK 0x0800 /* TEMPOK */ +#define WM8990_MICSHRT 0x0400 /* MICSHRT */ +#define WM8990_MICDET 0x0200 /* MICDET */ +#define WM8990_PLL_LCK 0x0100 /* PLL_LCK */ +#define WM8990_GPI8_STATUS 0x0080 /* GPI8_STATUS */ +#define WM8990_GPI7_STATUS 0x0040 /* GPI7_STATUS */ +#define WM8990_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */ +#define WM8990_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */ +#define WM8990_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */ +#define WM8990_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */ +#define WM8990_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */ +#define WM8990_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */ + +/* + * R19 (0x13) - GPIO1 & GPIO2 + */ +#define WM8990_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */ +#define WM8990_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */ +#define WM8990_GPIO2_PU 0x2000 /* GPIO2_PU */ +#define WM8990_GPIO2_PD 0x1000 /* GPIO2_PD */ +#define WM8990_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */ +#define WM8990_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */ +#define WM8990_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */ +#define WM8990_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8990_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8990_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - GPIO3 & GPIO4 + */ +#define WM8990_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */ +#define WM8990_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */ +#define WM8990_GPIO4_PU 0x2000 /* GPIO4_PU */ +#define WM8990_GPIO4_PD 0x1000 /* GPIO4_PD */ +#define WM8990_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */ +#define WM8990_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */ +#define WM8990_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */ +#define WM8990_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8990_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8990_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ + +/* + * R21 (0x15) - GPIO5 & GPIO6 + */ +#define WM8990_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */ +#define WM8990_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */ +#define WM8990_GPIO6_PU 0x2000 /* GPIO6_PU */ +#define WM8990_GPIO6_PD 0x1000 /* GPIO6_PD */ +#define WM8990_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */ +#define WM8990_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */ +#define WM8990_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */ +#define WM8990_GPIO5_PU 0x0020 /* GPIO5_PU */ +#define WM8990_GPIO5_PD 0x0010 /* GPIO5_PD */ +#define WM8990_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8990_RD_3W_ENA 0x8000 /* RD_3W_ENA */ +#define WM8990_MODE_3W4W 0x4000 /* MODE_3W4W */ +#define WM8990_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */ +#define WM8990_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */ +#define WM8990_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */ +#define WM8990_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */ +#define WM8990_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */ +#define WM8990_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */ +#define WM8990_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8990_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */ +#define WM8990_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */ +#define WM8990_GPI7_ENA 0x0001 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8990_IRQ_INV 0x1000 /* IRQ_INV */ +#define WM8990_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8990_MICSHRT_POL 0x0400 /* MICSHRT_POL */ +#define WM8990_MICDET_POL 0x0200 /* MICDET_POL */ +#define WM8990_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */ +#define WM8990_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8990_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8990_GPIO6_POL 0x0020 /* GPIO6_POL */ +#define WM8990_GPIO5_POL 0x0010 /* GPIO5_POL */ +#define WM8990_GPIO4_POL 0x0008 /* GPIO4_POL */ +#define WM8990_GPIO3_POL 0x0004 /* GPIO3_POL */ +#define WM8990_GPIO2_POL 0x0002 /* GPIO2_POL */ +#define WM8990_GPIO1_POL 0x0001 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_LI12MUTE 0x0080 /* LI12MUTE */ +#define WM8990_LI12MUTE_BIT 7 +#define WM8990_LI12ZC 0x0040 /* LI12ZC */ +#define WM8990_LI12ZC_BIT 6 +#define WM8990_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */ +#define WM8990_LIN12VOL_SHIFT 0 +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_LI34MUTE 0x0080 /* LI34MUTE */ +#define WM8990_LI34MUTE_BIT 7 +#define WM8990_LI34ZC 0x0040 /* LI34ZC */ +#define WM8990_LI34ZC_BIT 6 +#define WM8990_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */ +#define WM8990_LIN34VOL_SHIFT 0 + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_RI12MUTE 0x0080 /* RI12MUTE */ +#define WM8990_RI12MUTE_BIT 7 +#define WM8990_RI12ZC 0x0040 /* RI12ZC */ +#define WM8990_RI12ZC_BIT 6 +#define WM8990_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */ +#define WM8990_RIN12VOL_SHIFT 0 + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8990_IPVU 0x0100 /* IPVU */ +#define WM8990_RI34MUTE 0x0080 /* RI34MUTE */ +#define WM8990_RI34MUTE_BIT 7 +#define WM8990_RI34ZC 0x0040 /* RI34ZC */ +#define WM8990_RI34ZC_BIT 6 +#define WM8990_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */ +#define WM8990_RIN34VOL_SHIFT 0 + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_LOZC 0x0080 /* LOZC */ +#define WM8990_LOZC_BIT 7 +#define WM8990_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8990_LOUTVOL_SHIFT 0 +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_ROZC 0x0080 /* ROZC */ +#define WM8990_ROZC_BIT 7 +#define WM8990_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8990_ROUTVOL_SHIFT 0 +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8990_LONMUTE 0x0040 /* LONMUTE */ +#define WM8990_LONMUTE_BIT 6 +#define WM8990_LOPMUTE 0x0020 /* LOPMUTE */ +#define WM8990_LOPMUTE_BIT 5 +#define WM8990_LOATTN 0x0010 /* LOATTN */ +#define WM8990_LOATTN_BIT 4 +#define WM8990_RONMUTE 0x0004 /* RONMUTE */ +#define WM8990_RONMUTE_BIT 2 +#define WM8990_ROPMUTE 0x0002 /* ROPMUTE */ +#define WM8990_ROPMUTE_BIT 1 +#define WM8990_ROATTN 0x0001 /* ROATTN */ +#define WM8990_ROATTN_BIT 0 + +/* + * R31 (0x1F) - Out3/4 Volume + */ +#define WM8990_OUT3MUTE 0x0020 /* OUT3MUTE */ +#define WM8990_OUT3MUTE_BIT 5 +#define WM8990_OUT3ATTN 0x0010 /* OUT3ATTN */ +#define WM8990_OUT3ATTN_BIT 4 +#define WM8990_OUT4MUTE 0x0002 /* OUT4MUTE */ +#define WM8990_OUT4MUTE_BIT 1 +#define WM8990_OUT4ATTN 0x0001 /* OUT4ATTN */ +#define WM8990_OUT4ATTN_BIT 0 + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_LOPGAZC 0x0080 /* LOPGAZC */ +#define WM8990_LOPGAZC_BIT 7 +#define WM8990_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */ +#define WM8990_LOPGAVOL_SHIFT 0 + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8990_OPVU 0x0100 /* OPVU */ +#define WM8990_ROPGAZC 0x0080 /* ROPGAZC */ +#define WM8990_ROPGAZC_BIT 7 +#define WM8990_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */ +#define WM8990_ROPGAVOL_SHIFT 0 +/* + * R34 (0x22) - Speaker Volume + */ +#define WM8990_SPKATTN_MASK 0x0003 /* SPKATTN - [1:0] */ +#define WM8990_SPKATTN_SHIFT 0 + +/* + * R35 (0x23) - ClassD1 + */ +#define WM8990_CDMODE 0x0100 /* CDMODE */ +#define WM8990_CDMODE_BIT 8 + +/* + * R37 (0x25) - ClassD3 + */ +#define WM8990_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */ +#define WM8990_DCGAIN_SHIFT 3 +#define WM8990_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */ +#define WM8990_ACGAIN_SHIFT 0 + +/* + * R38 (0x26) - ClassD4 + */ +#define WM8990_SPKZC_MASK 0x0001 /* SPKZC */ +#define WM8990_SPKZC_SHIFT 7 /* SPKZC */ +#define WM8990_SPKVOL_MASK 0x007F /* SPKVOL - [6:0] */ +#define WM8990_SPKVOL_SHIFT 0 /* SPKVOL - [6:0] */ + +/* + * R39 (0x27) - Input Mixer1 + */ +#define WM8990_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */ +#define WM8990_AINLMODE_SHIFT 2 +#define WM8990_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */ +#define WM8990_AINRMODE_SHIFT 0 + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8990_LMP4 0x0080 /* LMP4 */ +#define WM8990_LMP4_BIT 7 /* LMP4 */ +#define WM8990_LMN3 0x0040 /* LMN3 */ +#define WM8990_LMN3_BIT 6 /* LMN3 */ +#define WM8990_LMP2 0x0020 /* LMP2 */ +#define WM8990_LMP2_BIT 5 /* LMP2 */ +#define WM8990_LMN1 0x0010 /* LMN1 */ +#define WM8990_LMN1_BIT 4 /* LMN1 */ +#define WM8990_RMP4 0x0008 /* RMP4 */ +#define WM8990_RMP4_BIT 3 /* RMP4 */ +#define WM8990_RMN3 0x0004 /* RMN3 */ +#define WM8990_RMN3_BIT 2 /* RMN3 */ +#define WM8990_RMP2 0x0002 /* RMP2 */ +#define WM8990_RMP2_BIT 1 /* RMP2 */ +#define WM8990_RMN1 0x0001 /* RMN1 */ +#define WM8990_RMN1_BIT 0 /* RMN1 */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8990_L34MNB 0x0100 /* L34MNB */ +#define WM8990_L34MNB_BIT 8 +#define WM8990_L34MNBST 0x0080 /* L34MNBST */ +#define WM8990_L34MNBST_BIT 7 +#define WM8990_L12MNB 0x0020 /* L12MNB */ +#define WM8990_L12MNB_BIT 5 +#define WM8990_L12MNBST 0x0010 /* L12MNBST */ +#define WM8990_L12MNBST_BIT 4 +#define WM8990_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */ +#define WM8990_LDBVOL_SHIFT 0 + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8990_R34MNB 0x0100 /* R34MNB */ +#define WM8990_R34MNB_BIT 8 +#define WM8990_R34MNBST 0x0080 /* R34MNBST */ +#define WM8990_R34MNBST_BIT 7 +#define WM8990_R12MNB 0x0020 /* R12MNB */ +#define WM8990_R12MNB_BIT 5 +#define WM8990_R12MNBST 0x0010 /* R12MNBST */ +#define WM8990_R12MNBST_BIT 4 +#define WM8990_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */ +#define WM8990_RDBVOL_SHIFT 0 + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8990_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */ +#define WM8990_LI2BVOL_SHIFT 6 +#define WM8990_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */ +#define WM8990_LR4BVOL_SHIFT 3 +#define WM8990_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */ +#define WM8990_LL4BVOL_SHIFT 0 + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8990_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */ +#define WM8990_RI2BVOL_SHIFT 6 +#define WM8990_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */ +#define WM8990_RL4BVOL_SHIFT 3 +#define WM8990_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */ +#define WM8990_RR4BVOL_SHIFT 0 + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8990_LRBLO 0x0080 /* LRBLO */ +#define WM8990_LRBLO_BIT 7 +#define WM8990_LLBLO 0x0040 /* LLBLO */ +#define WM8990_LLBLO_BIT 6 +#define WM8990_LRI3LO 0x0020 /* LRI3LO */ +#define WM8990_LRI3LO_BIT 5 +#define WM8990_LLI3LO 0x0010 /* LLI3LO */ +#define WM8990_LLI3LO_BIT 4 +#define WM8990_LR12LO 0x0008 /* LR12LO */ +#define WM8990_LR12LO_BIT 3 +#define WM8990_LL12LO 0x0004 /* LL12LO */ +#define WM8990_LL12LO_BIT 2 +#define WM8990_LDLO 0x0001 /* LDLO */ +#define WM8990_LDLO_BIT 0 + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8990_RLBRO 0x0080 /* RLBRO */ +#define WM8990_RLBRO_BIT 7 +#define WM8990_RRBRO 0x0040 /* RRBRO */ +#define WM8990_RRBRO_BIT 6 +#define WM8990_RLI3RO 0x0020 /* RLI3RO */ +#define WM8990_RLI3RO_BIT 5 +#define WM8990_RRI3RO 0x0010 /* RRI3RO */ +#define WM8990_RRI3RO_BIT 4 +#define WM8990_RL12RO 0x0008 /* RL12RO */ +#define WM8990_RL12RO_BIT 3 +#define WM8990_RR12RO 0x0004 /* RR12RO */ +#define WM8990_RR12RO_BIT 2 +#define WM8990_RDRO 0x0001 /* RDRO */ +#define WM8990_RDRO_BIT 0 + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8990_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */ +#define WM8990_LLI3LOVOL_SHIFT 6 +#define WM8990_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */ +#define WM8990_LR12LOVOL_SHIFT 3 +#define WM8990_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */ +#define WM8990_LL12LOVOL_SHIFT 0 + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8990_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */ +#define WM8990_RRI3ROVOL_SHIFT 6 +#define WM8990_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */ +#define WM8990_RL12ROVOL_SHIFT 3 +#define WM8990_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */ +#define WM8990_RR12ROVOL_SHIFT 0 + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8990_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */ +#define WM8990_LRI3LOVOL_SHIFT 6 +#define WM8990_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */ +#define WM8990_LRBLOVOL_SHIFT 3 +#define WM8990_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */ +#define WM8990_LLBLOVOL_SHIFT 0 + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8990_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */ +#define WM8990_RLI3ROVOL_SHIFT 6 +#define WM8990_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */ +#define WM8990_RLBROVOL_SHIFT 3 +#define WM8990_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */ +#define WM8990_RRBROVOL_SHIFT 0 + +/* + * R51 (0x33) - Out3/4 Mixer + */ +#define WM8990_VSEL_MASK 0x0180 /* VSEL - [8:7] */ +#define WM8990_LI4O3 0x0020 /* LI4O3 */ +#define WM8990_LI4O3_BIT 5 +#define WM8990_LPGAO3 0x0010 /* LPGAO3 */ +#define WM8990_LPGAO3_BIT 4 +#define WM8990_RI4O4 0x0002 /* RI4O4 */ +#define WM8990_RI4O4_BIT 1 +#define WM8990_RPGAO4 0x0001 /* RPGAO4 */ +#define WM8990_RPGAO4_BIT 0 +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8990_LLOPGALON 0x0040 /* LLOPGALON */ +#define WM8990_LLOPGALON_BIT 6 +#define WM8990_LROPGALON 0x0020 /* LROPGALON */ +#define WM8990_LROPGALON_BIT 5 +#define WM8990_LOPLON 0x0010 /* LOPLON */ +#define WM8990_LOPLON_BIT 4 +#define WM8990_LR12LOP 0x0004 /* LR12LOP */ +#define WM8990_LR12LOP_BIT 2 +#define WM8990_LL12LOP 0x0002 /* LL12LOP */ +#define WM8990_LL12LOP_BIT 1 +#define WM8990_LLOPGALOP 0x0001 /* LLOPGALOP */ +#define WM8990_LLOPGALOP_BIT 0 +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8990_RROPGARON 0x0040 /* RROPGARON */ +#define WM8990_RROPGARON_BIT 6 +#define WM8990_RLOPGARON 0x0020 /* RLOPGARON */ +#define WM8990_RLOPGARON_BIT 5 +#define WM8990_ROPRON 0x0010 /* ROPRON */ +#define WM8990_ROPRON_BIT 4 +#define WM8990_RL12ROP 0x0004 /* RL12ROP */ +#define WM8990_RL12ROP_BIT 2 +#define WM8990_RR12ROP 0x0002 /* RR12ROP */ +#define WM8990_RR12ROP_BIT 1 +#define WM8990_RROPGAROP 0x0001 /* RROPGAROP */ +#define WM8990_RROPGAROP_BIT 0 + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8990_LB2SPK 0x0080 /* LB2SPK */ +#define WM8990_LB2SPK_BIT 7 +#define WM8990_RB2SPK 0x0040 /* RB2SPK */ +#define WM8990_RB2SPK_BIT 6 +#define WM8990_LI2SPK 0x0020 /* LI2SPK */ +#define WM8990_LI2SPK_BIT 5 +#define WM8990_RI2SPK 0x0010 /* RI2SPK */ +#define WM8990_RI2SPK_BIT 4 +#define WM8990_LOPGASPK 0x0008 /* LOPGASPK */ +#define WM8990_LOPGASPK_BIT 3 +#define WM8990_ROPGASPK 0x0004 /* ROPGASPK */ +#define WM8990_ROPGASPK_BIT 2 +#define WM8990_LDSPK 0x0002 /* LDSPK */ +#define WM8990_LDSPK_BIT 1 +#define WM8990_RDSPK 0x0001 /* RDSPK */ +#define WM8990_RDSPK_BIT 0 + +/* + * R55 (0x37) - Additional Control + */ +#define WM8990_VROI 0x0001 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8990_DIS_LLINE 0x0020 /* DIS_LLINE */ +#define WM8990_DIS_RLINE 0x0010 /* DIS_RLINE */ +#define WM8990_DIS_OUT3 0x0008 /* DIS_OUT3 */ +#define WM8990_DIS_OUT4 0x0004 /* DIS_OUT4 */ +#define WM8990_DIS_LOUT 0x0002 /* DIS_LOUT */ +#define WM8990_DIS_ROUT 0x0001 /* DIS_ROUT */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8990_SOFTST 0x0040 /* SOFTST */ +#define WM8990_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8990_BUFDCOPEN 0x0004 /* BUFDCOPEN */ +#define WM8990_POBCTRL 0x0002 /* POBCTRL */ +#define WM8990_VMIDTOG 0x0001 /* VMIDTOG */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8990_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */ +#define WM8990_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */ +#define WM8990_MCD 0x0004 /* MCD */ +#define WM8990_MBSEL 0x0001 /* MBSEL */ + +/* + * R60 (0x3C) - PLL1 + */ +#define WM8990_SDM 0x0080 /* SDM */ +#define WM8990_PRESCALE 0x0040 /* PRESCALE */ +#define WM8990_PLLN_MASK 0x000F /* PLLN - [3:0] */ + +/* + * R61 (0x3D) - PLL2 + */ +#define WM8990_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */ + +/* + * R62 (0x3E) - PLL3 + */ +#define WM8990_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */ + +/* + * R63 (0x3F) - Internal Driver Bits + */ +#define WM8990_INMIXL_PWR_BIT 0 +#define WM8990_AINLMUX_PWR_BIT 1 +#define WM8990_INMIXR_PWR_BIT 2 +#define WM8990_AINRMUX_PWR_BIT 3 + +struct wm8990_setup_data { + unsigned i2c_bus; + unsigned short i2c_address; +}; + +#define WM8990_MCLK_DIV 0 +#define WM8990_DACCLK_DIV 1 +#define WM8990_ADCCLK_DIV 2 +#define WM8990_BCLK_DIV 3 + +extern struct snd_soc_dai wm8990_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8990; + +#endif /* __WM8990REGISTERDEFS_H__ */ +/*------------------------------ END OF FILE ---------------------------------*/ diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c new file mode 100644 index 0000000..ffb471e --- /dev/null +++ b/sound/soc/codecs/wm9712.c @@ -0,0 +1,750 @@ +/* + * wm9712.c -- ALSA Soc WM9712 codec support + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include "wm9712.h" + +#define WM9712_VERSION "0.4" + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg); +static int ac97_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val); + +/* + * WM9712 register cache + */ +static const u16 wm9712_reg[] = { + 0x6174, 0x8000, 0x8000, 0x8000, /* 6 */ + 0x0f0f, 0xaaa0, 0xc008, 0x6808, /* e */ + 0xe808, 0xaaa0, 0xad00, 0x8000, /* 16 */ + 0xe808, 0x3000, 0x8000, 0x0000, /* 1e */ + 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */ + 0x0405, 0x0410, 0xbb80, 0xbb80, /* 2e */ + 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */ + 0x0000, 0x2000, 0x0000, 0x0000, /* 3e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 46 */ + 0x0000, 0x0000, 0xf83e, 0xffff, /* 4e */ + 0x0000, 0x0000, 0x0000, 0xf83e, /* 56 */ + 0x0008, 0x0000, 0x0000, 0x0000, /* 5e */ + 0xb032, 0x3e00, 0x0000, 0x0000, /* 66 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */ + 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */ + 0x0001, 0x0000, 0x574d, 0x4c12, /* 7e */ + 0x0000, 0x0000 /* virtual hp mixers */ +}; + +/* virtual HP mixers regs */ +#define HPL_MIXER 0x80 +#define HPR_MIXER 0x82 + +static const char *wm9712_alc_select[] = {"None", "Left", "Right", "Stereo"}; +static const char *wm9712_alc_mux[] = {"Stereo", "Left", "Right", "None"}; +static const char *wm9712_out3_src[] = {"Left", "VREF", "Left + Right", + "Mono"}; +static const char *wm9712_spk_src[] = {"Speaker Mix", "Headphone Mix"}; +static const char *wm9712_rec_adc[] = {"Stereo", "Left", "Right", "Mute"}; +static const char *wm9712_base[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm9712_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"}; +static const char *wm9712_mic[] = {"Mic 1", "Differential", "Mic 2", + "Stereo"}; +static const char *wm9712_rec_sel[] = {"Mic", "NC", "NC", "Speaker Mixer", + "Line", "Headphone Mixer", "Phone Mixer", "Phone"}; +static const char *wm9712_ng_type[] = {"Constant Gain", "Mute"}; +static const char *wm9712_diff_sel[] = {"Mic", "Line"}; + +static const struct soc_enum wm9712_enum[] = { +SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9712_alc_select), +SOC_ENUM_SINGLE(AC97_VIDEO, 12, 4, wm9712_alc_mux), +SOC_ENUM_SINGLE(AC97_AUX, 9, 4, wm9712_out3_src), +SOC_ENUM_SINGLE(AC97_AUX, 8, 2, wm9712_spk_src), +SOC_ENUM_SINGLE(AC97_REC_SEL, 12, 4, wm9712_rec_adc), +SOC_ENUM_SINGLE(AC97_MASTER_TONE, 15, 2, wm9712_base), +SOC_ENUM_DOUBLE(AC97_REC_GAIN, 14, 6, 2, wm9712_rec_gain), +SOC_ENUM_SINGLE(AC97_MIC, 5, 4, wm9712_mic), +SOC_ENUM_SINGLE(AC97_REC_SEL, 8, 8, wm9712_rec_sel), +SOC_ENUM_SINGLE(AC97_REC_SEL, 0, 8, wm9712_rec_sel), +SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9712_ng_type), +SOC_ENUM_SINGLE(0x5c, 8, 2, wm9712_diff_sel), +}; + +static const struct snd_kcontrol_new wm9712_snd_ac97_controls[] = { +SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1), +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), +SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), + +SOC_SINGLE("Speaker Playback ZC Switch", AC97_MASTER, 7, 1, 0), +SOC_SINGLE("Speaker Playback Invert Switch", AC97_MASTER, 6, 1, 0), +SOC_SINGLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 7, 1, 0), +SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_MONO, 7, 1, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + +SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), +SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), +SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0), +SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), +SOC_ENUM("ALC Function", wm9712_enum[0]), +SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0), +SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 1), +SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), +SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), +SOC_ENUM("ALC NG Type", wm9712_enum[10]), +SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 1), + +SOC_SINGLE("Mic Headphone Volume", AC97_VIDEO, 12, 7, 1), +SOC_SINGLE("ALC Headphone Volume", AC97_VIDEO, 7, 7, 1), + +SOC_SINGLE("Out3 Switch", AC97_AUX, 15, 1, 1), +SOC_SINGLE("Out3 ZC Switch", AC97_AUX, 7, 1, 1), +SOC_SINGLE("Out3 Volume", AC97_AUX, 0, 31, 1), + +SOC_SINGLE("PCBeep Bypass Headphone Volume", AC97_PC_BEEP, 12, 7, 1), +SOC_SINGLE("PCBeep Bypass Speaker Volume", AC97_PC_BEEP, 8, 7, 1), +SOC_SINGLE("PCBeep Bypass Phone Volume", AC97_PC_BEEP, 4, 7, 1), + +SOC_SINGLE("Aux Playback Headphone Volume", AC97_CD, 12, 7, 1), +SOC_SINGLE("Aux Playback Speaker Volume", AC97_CD, 8, 7, 1), +SOC_SINGLE("Aux Playback Phone Volume", AC97_CD, 4, 7, 1), + +SOC_SINGLE("Phone Volume", AC97_PHONE, 0, 15, 1), +SOC_DOUBLE("Line Capture Volume", AC97_LINE, 8, 0, 31, 1), + +SOC_SINGLE("Capture 20dB Boost Switch", AC97_REC_SEL, 14, 1, 0), +SOC_SINGLE("Capture to Phone 20dB Boost Switch", AC97_REC_SEL, 11, 1, 1), + +SOC_SINGLE("3D Upper Cut-off Switch", AC97_3D_CONTROL, 5, 1, 1), +SOC_SINGLE("3D Lower Cut-off Switch", AC97_3D_CONTROL, 4, 1, 1), +SOC_SINGLE("3D Playback Volume", AC97_3D_CONTROL, 0, 15, 0), + +SOC_ENUM("Bass Control", wm9712_enum[5]), +SOC_SINGLE("Bass Cut-off Switch", AC97_MASTER_TONE, 12, 1, 1), +SOC_SINGLE("Tone Cut-off Switch", AC97_MASTER_TONE, 4, 1, 1), +SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_MASTER_TONE, 6, 1, 0), +SOC_SINGLE("Bass Volume", AC97_MASTER_TONE, 8, 15, 1), +SOC_SINGLE("Treble Volume", AC97_MASTER_TONE, 0, 15, 1), + +SOC_SINGLE("Capture ADC Switch", AC97_REC_GAIN, 15, 1, 1), +SOC_ENUM("Capture Volume Steps", wm9712_enum[6]), +SOC_DOUBLE("Capture Volume", AC97_REC_GAIN, 8, 0, 63, 1), +SOC_SINGLE("Capture ZC Switch", AC97_REC_GAIN, 7, 1, 0), + +SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1), +SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), +SOC_SINGLE("Mic 20dB Boost Switch", AC97_MIC, 7, 1, 0), +}; + +/* add non dapm controls */ +static int wm9712_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm9712_snd_ac97_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm9712_snd_ac97_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* We have to create a fake left and right HP mixers because + * the codec only has a single control that is shared by both channels. + * This makes it impossible to determine the audio path. + */ +static int mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + u16 l, r, beep, line, phone, mic, pcm, aux; + + l = ac97_read(w->codec, HPL_MIXER); + r = ac97_read(w->codec, HPR_MIXER); + beep = ac97_read(w->codec, AC97_PC_BEEP); + mic = ac97_read(w->codec, AC97_VIDEO); + phone = ac97_read(w->codec, AC97_PHONE); + line = ac97_read(w->codec, AC97_LINE); + pcm = ac97_read(w->codec, AC97_PCM); + aux = ac97_read(w->codec, AC97_CD); + + if (l & 0x1 || r & 0x1) + ac97_write(w->codec, AC97_VIDEO, mic & 0x7fff); + else + ac97_write(w->codec, AC97_VIDEO, mic | 0x8000); + + if (l & 0x2 || r & 0x2) + ac97_write(w->codec, AC97_PCM, pcm & 0x7fff); + else + ac97_write(w->codec, AC97_PCM, pcm | 0x8000); + + if (l & 0x4 || r & 0x4) + ac97_write(w->codec, AC97_LINE, line & 0x7fff); + else + ac97_write(w->codec, AC97_LINE, line | 0x8000); + + if (l & 0x8 || r & 0x8) + ac97_write(w->codec, AC97_PHONE, phone & 0x7fff); + else + ac97_write(w->codec, AC97_PHONE, phone | 0x8000); + + if (l & 0x10 || r & 0x10) + ac97_write(w->codec, AC97_CD, aux & 0x7fff); + else + ac97_write(w->codec, AC97_CD, aux | 0x8000); + + if (l & 0x20 || r & 0x20) + ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff); + else + ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000); + + return 0; +} + +/* Left Headphone Mixers */ +static const struct snd_kcontrol_new wm9712_hpl_mixer_controls[] = { + SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPL_MIXER, 5, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 4, 1, 0), + SOC_DAPM_SINGLE("Phone Bypass Switch", HPL_MIXER, 3, 1, 0), + SOC_DAPM_SINGLE("Line Bypass Switch", HPL_MIXER, 2, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 1, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", HPL_MIXER, 0, 1, 0), +}; + +/* Right Headphone Mixers */ +static const struct snd_kcontrol_new wm9712_hpr_mixer_controls[] = { + SOC_DAPM_SINGLE("PCBeep Bypass Switch", HPR_MIXER, 5, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 4, 1, 0), + SOC_DAPM_SINGLE("Phone Bypass Switch", HPR_MIXER, 3, 1, 0), + SOC_DAPM_SINGLE("Line Bypass Switch", HPR_MIXER, 2, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 1, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", HPR_MIXER, 0, 1, 0), +}; + +/* Speaker Mixer */ +static const struct snd_kcontrol_new wm9712_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 11, 1, 1), + SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 11, 1, 1), + SOC_DAPM_SINGLE("Phone Bypass Switch", AC97_PHONE, 14, 1, 1), + SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 14, 1, 1), + SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 14, 1, 1), +}; + +/* Phone Mixer */ +static const struct snd_kcontrol_new wm9712_phone_mixer_controls[] = { + SOC_DAPM_SINGLE("PCBeep Bypass Switch", AC97_PC_BEEP, 7, 1, 1), + SOC_DAPM_SINGLE("Aux Playback Switch", AC97_CD, 7, 1, 1), + SOC_DAPM_SINGLE("Line Bypass Switch", AC97_LINE, 13, 1, 1), + SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PCM, 13, 1, 1), + SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_MIC, 14, 1, 1), + SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_MIC, 13, 1, 1), +}; + +/* ALC headphone mux */ +static const struct snd_kcontrol_new wm9712_alc_mux_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[1]); + +/* out 3 mux */ +static const struct snd_kcontrol_new wm9712_out3_mux_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[2]); + +/* spk mux */ +static const struct snd_kcontrol_new wm9712_spk_mux_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[3]); + +/* Capture to Phone mux */ +static const struct snd_kcontrol_new wm9712_capture_phone_mux_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[4]); + +/* Capture left select */ +static const struct snd_kcontrol_new wm9712_capture_selectl_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[8]); + +/* Capture right select */ +static const struct snd_kcontrol_new wm9712_capture_selectr_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[9]); + +/* Mic select */ +static const struct snd_kcontrol_new wm9712_mic_src_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[7]); + +/* diff select */ +static const struct snd_kcontrol_new wm9712_diff_sel_controls = +SOC_DAPM_ENUM("Route", wm9712_enum[11]); + +static const struct snd_soc_dapm_widget wm9712_dapm_widgets[] = { +SND_SOC_DAPM_MUX("ALC Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm9712_alc_mux_controls), +SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, + &wm9712_out3_mux_controls), +SND_SOC_DAPM_MUX("Speaker Mux", SND_SOC_NOPM, 0, 0, + &wm9712_spk_mux_controls), +SND_SOC_DAPM_MUX("Capture Phone Mux", SND_SOC_NOPM, 0, 0, + &wm9712_capture_phone_mux_controls), +SND_SOC_DAPM_MUX("Left Capture Select", SND_SOC_NOPM, 0, 0, + &wm9712_capture_selectl_controls), +SND_SOC_DAPM_MUX("Right Capture Select", SND_SOC_NOPM, 0, 0, + &wm9712_capture_selectr_controls), +SND_SOC_DAPM_MUX("Mic Select Source", SND_SOC_NOPM, 0, 0, + &wm9712_mic_src_controls), +SND_SOC_DAPM_MUX("Differential Source", SND_SOC_NOPM, 0, 0, + &wm9712_diff_sel_controls), +SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_INT_PAGING, 9, 1, + &wm9712_hpl_mixer_controls[0], ARRAY_SIZE(wm9712_hpl_mixer_controls), + mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_INT_PAGING, 8, 1, + &wm9712_hpr_mixer_controls[0], ARRAY_SIZE(wm9712_hpr_mixer_controls), + mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER("Phone Mixer", AC97_INT_PAGING, 6, 1, + &wm9712_phone_mixer_controls[0], ARRAY_SIZE(wm9712_phone_mixer_controls)), +SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_INT_PAGING, 7, 1, + &wm9712_speaker_mixer_controls[0], + ARRAY_SIZE(wm9712_speaker_mixer_controls)), +SND_SOC_DAPM_MIXER("Mono Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_INT_PAGING, 14, 1), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_INT_PAGING, 13, 1), +SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_INT_PAGING, 12, 1), +SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_INT_PAGING, 11, 1), +SND_SOC_DAPM_PGA("Headphone PGA", AC97_INT_PAGING, 4, 1, NULL, 0), +SND_SOC_DAPM_PGA("Speaker PGA", AC97_INT_PAGING, 3, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 3 PGA", AC97_INT_PAGING, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Line PGA", AC97_INT_PAGING, 2, 1, NULL, 0), +SND_SOC_DAPM_PGA("Phone PGA", AC97_INT_PAGING, 1, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic PGA", AC97_INT_PAGING, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_INT_PAGING, 10, 1), +SND_SOC_DAPM_OUTPUT("MONOOUT"), +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +SND_SOC_DAPM_OUTPUT("LOUT2"), +SND_SOC_DAPM_OUTPUT("ROUT2"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_INPUT("LINEINL"), +SND_SOC_DAPM_INPUT("LINEINR"), +SND_SOC_DAPM_INPUT("PHONE"), +SND_SOC_DAPM_INPUT("PCBEEP"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* virtual mixer - mixes left & right channels for spk and mono */ + {"AC97 Mixer", NULL, "Left DAC"}, + {"AC97 Mixer", NULL, "Right DAC"}, + + /* Left HP mixer */ + {"Left HP Mixer", "PCBeep Bypass Switch", "PCBEEP"}, + {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Left HP Mixer", "Phone Bypass Switch", "Phone PGA"}, + {"Left HP Mixer", "Line Bypass Switch", "Line PGA"}, + {"Left HP Mixer", "PCM Playback Switch", "Left DAC"}, + {"Left HP Mixer", "Mic Sidetone Switch", "Mic PGA"}, + {"Left HP Mixer", NULL, "ALC Sidetone Mux"}, + + /* Right HP mixer */ + {"Right HP Mixer", "PCBeep Bypass Switch", "PCBEEP"}, + {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Right HP Mixer", "Phone Bypass Switch", "Phone PGA"}, + {"Right HP Mixer", "Line Bypass Switch", "Line PGA"}, + {"Right HP Mixer", "PCM Playback Switch", "Right DAC"}, + {"Right HP Mixer", "Mic Sidetone Switch", "Mic PGA"}, + {"Right HP Mixer", NULL, "ALC Sidetone Mux"}, + + /* speaker mixer */ + {"Speaker Mixer", "PCBeep Bypass Switch", "PCBEEP"}, + {"Speaker Mixer", "Line Bypass Switch", "Line PGA"}, + {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Speaker Mixer", "Phone Bypass Switch", "Phone PGA"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"}, + + /* Phone mixer */ + {"Phone Mixer", "PCBeep Bypass Switch", "PCBEEP"}, + {"Phone Mixer", "Line Bypass Switch", "Line PGA"}, + {"Phone Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Phone Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Phone Mixer", "Mic 1 Sidetone Switch", "Mic PGA"}, + {"Phone Mixer", "Mic 2 Sidetone Switch", "Mic PGA"}, + + /* inputs */ + {"Line PGA", NULL, "LINEINL"}, + {"Line PGA", NULL, "LINEINR"}, + {"Phone PGA", NULL, "PHONE"}, + {"Mic PGA", NULL, "MIC1"}, + {"Mic PGA", NULL, "MIC2"}, + + /* left capture selector */ + {"Left Capture Select", "Mic", "MIC1"}, + {"Left Capture Select", "Speaker Mixer", "Speaker Mixer"}, + {"Left Capture Select", "Line", "LINEINL"}, + {"Left Capture Select", "Headphone Mixer", "Left HP Mixer"}, + {"Left Capture Select", "Phone Mixer", "Phone Mixer"}, + {"Left Capture Select", "Phone", "PHONE"}, + + /* right capture selector */ + {"Right Capture Select", "Mic", "MIC2"}, + {"Right Capture Select", "Speaker Mixer", "Speaker Mixer"}, + {"Right Capture Select", "Line", "LINEINR"}, + {"Right Capture Select", "Headphone Mixer", "Right HP Mixer"}, + {"Right Capture Select", "Phone Mixer", "Phone Mixer"}, + {"Right Capture Select", "Phone", "PHONE"}, + + /* ALC Sidetone */ + {"ALC Sidetone Mux", "Stereo", "Left Capture Select"}, + {"ALC Sidetone Mux", "Stereo", "Right Capture Select"}, + {"ALC Sidetone Mux", "Left", "Left Capture Select"}, + {"ALC Sidetone Mux", "Right", "Right Capture Select"}, + + /* ADC's */ + {"Left ADC", NULL, "Left Capture Select"}, + {"Right ADC", NULL, "Right Capture Select"}, + + /* outputs */ + {"MONOOUT", NULL, "Phone Mixer"}, + {"HPOUTL", NULL, "Headphone PGA"}, + {"Headphone PGA", NULL, "Left HP Mixer"}, + {"HPOUTR", NULL, "Headphone PGA"}, + {"Headphone PGA", NULL, "Right HP Mixer"}, + + /* mono mixer */ + {"Mono Mixer", NULL, "Left HP Mixer"}, + {"Mono Mixer", NULL, "Right HP Mixer"}, + + /* Out3 Mux */ + {"Out3 Mux", "Left", "Left HP Mixer"}, + {"Out3 Mux", "Mono", "Phone Mixer"}, + {"Out3 Mux", "Left + Right", "Mono Mixer"}, + {"Out 3 PGA", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3 PGA"}, + + /* speaker Mux */ + {"Speaker Mux", "Speaker Mix", "Speaker Mixer"}, + {"Speaker Mux", "Headphone Mix", "Mono Mixer"}, + {"Speaker PGA", NULL, "Speaker Mux"}, + {"LOUT2", NULL, "Speaker PGA"}, + {"ROUT2", NULL, "Speaker PGA"}, +}; + +static int wm9712_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm9712_dapm_widgets, + ARRAY_SIZE(wm9712_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || + reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 || + reg == AC97_REC_GAIN) + return soc_ac97_ops.read(codec->ac97, reg); + else { + reg = reg >> 1; + + if (reg > (ARRAY_SIZE(wm9712_reg))) + return -EIO; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg <= (ARRAY_SIZE(wm9712_reg))) + cache[reg] = val; + + return 0; +} + +static int ac97_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg; + u16 vra; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return ac97_write(codec, reg, runtime->rate); +} + +static int ac97_aux_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 vra, xsle; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + xsle = ac97_read(codec, AC97_PCI_SID); + ac97_write(codec, AC97_PCI_SID, xsle | 0x8000); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + + return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate); +} + +#define WM9712_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) + +struct snd_soc_dai wm9712_dai[] = { +{ + .name = "AC97 HiFi", + .type = SND_SOC_DAI_AC97_BUS, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9712_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9712_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_prepare,}, +}, +{ + .name = "AC97 Aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9712_AC97_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_aux_prepare,}, +} +}; +EXPORT_SYMBOL_GPL(wm9712_dai); + +static int wm9712_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + ac97_write(codec, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: + /* disable everything including AC link */ + ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); + ac97_write(codec, AC97_POWERDOWN, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +static int wm9712_reset(struct snd_soc_codec *codec, int try_warm) +{ + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (ac97_read(codec, 0) == wm9712_reg[0]) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + if (ac97_read(codec, 0) != wm9712_reg[0]) + goto err; + return 0; + +err: + printk(KERN_ERR "WM9712 AC97 reset failed\n"); + return -EIO; +} + +static int wm9712_soc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm9712_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm9712_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i, ret; + u16 *cache = codec->reg_cache; + + ret = wm9712_reset(codec, 1); + if (ret < 0) { + printk(KERN_ERR "could not reset AC97 codec\n"); + return ret; + } + + wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + if (ret == 0) { + /* Sync reg_cache with the hardware after cold reset */ + for (i = 2; i < ARRAY_SIZE(wm9712_reg) << 1; i += 2) { + if (i == AC97_INT_PAGING || i == AC97_POWERDOWN || + (i > 0x58 && i != 0x5c)) + continue; + soc_ac97_ops.write(codec->ac97, i, cache[i>>1]); + } + } + + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + wm9712_set_bias_level(codec, SND_SOC_BIAS_ON); + + return ret; +} + +static int wm9712_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + printk(KERN_INFO "WM9711/WM9712 SoC Audio Codec %s\n", WM9712_VERSION); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = kmemdup(wm9712_reg, sizeof(wm9712_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + codec->reg_cache_size = sizeof(wm9712_reg); + codec->reg_cache_step = 2; + + codec->name = "WM9712"; + codec->owner = THIS_MODULE; + codec->dai = wm9712_dai; + codec->num_dai = ARRAY_SIZE(wm9712_dai); + codec->write = ac97_write; + codec->read = ac97_read; + codec->set_bias_level = wm9712_set_bias_level; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) { + printk(KERN_ERR "wm9712: failed to register AC97 codec\n"); + goto codec_err; + } + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + ret = wm9712_reset(codec, 0); + if (ret < 0) { + printk(KERN_ERR "AC97 link error\n"); + goto reset_err; + } + + /* set alc mux to none */ + ac97_write(codec, AC97_VIDEO, ac97_read(codec, AC97_VIDEO) | 0x3000); + + wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm9712_add_controls(codec); + wm9712_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm9712: failed to register card\n"); + goto reset_err; + } + + return 0; + +reset_err: + snd_soc_free_pcms(socdev); + +pcm_err: + snd_soc_free_ac97_codec(codec); + +codec_err: + kfree(codec->reg_cache); + +cache_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int wm9712_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->reg_cache); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm9712 = { + .probe = wm9712_soc_probe, + .remove = wm9712_soc_remove, + .suspend = wm9712_soc_suspend, + .resume = wm9712_soc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9712); + +MODULE_DESCRIPTION("ASoC WM9711/WM9712 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9712.h b/sound/soc/codecs/wm9712.h new file mode 100644 index 0000000..d29e8a1 --- /dev/null +++ b/sound/soc/codecs/wm9712.h @@ -0,0 +1,14 @@ +/* + * wm9712.h -- WM9712 Soc Audio driver + */ + +#ifndef _WM9712_H +#define _WM9712_H + +#define WM9712_DAI_AC97_HIFI 0 +#define WM9712_DAI_AC97_AUX 1 + +extern struct snd_soc_dai wm9712_dai[2]; +extern struct snd_soc_codec_device soc_codec_dev_wm9712; + +#endif diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c new file mode 100644 index 0000000..945b32e --- /dev/null +++ b/sound/soc/codecs/wm9713.c @@ -0,0 +1,1306 @@ +/* + * wm9713.c -- ALSA Soc WM9713 codec support + * + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * + * 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. + * + * Features:- + * + * o Support for AC97 Codec, Voice DAC and Aux DAC + * o Support for DAPM + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "wm9713.h" + +#define WM9713_VERSION "0.15" + +struct wm9713_priv { + u32 pll_in; /* PLL input frequency */ + u32 pll_out; /* PLL output frequency */ +}; + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg); +static int ac97_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val); + +/* + * WM9713 register cache + * Reg 0x3c bit 15 is used by touch driver. + */ +static const u16 wm9713_reg[] = { + 0x6174, 0x8080, 0x8080, 0x8080, + 0xc880, 0xe808, 0xe808, 0x0808, + 0x00da, 0x8000, 0xd600, 0xaaa0, + 0xaaa0, 0xaaa0, 0x0000, 0x0000, + 0x0f0f, 0x0040, 0x0000, 0x7f00, + 0x0405, 0x0410, 0xbb80, 0xbb80, + 0x0000, 0xbb80, 0x0000, 0x4523, + 0x0000, 0x2000, 0x7eff, 0xffff, + 0x0000, 0x0000, 0x0080, 0x0000, + 0x0000, 0x0000, 0xfffe, 0xffff, + 0x0000, 0x0000, 0x0000, 0xfffe, + 0x4000, 0x0000, 0x0000, 0x0000, + 0xb032, 0x3e00, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0006, + 0x0001, 0x0000, 0x574d, 0x4c13, + 0x0000, 0x0000, 0x0000 +}; + +/* virtual HP mixers regs */ +#define HPL_MIXER 0x80 +#define HPR_MIXER 0x82 +#define MICB_MUX 0x82 + +static const char *wm9713_mic_mixer[] = {"Stereo", "Mic 1", "Mic 2", "Mute"}; +static const char *wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"}; +static const char *wm9713_rec_src[] = + {"Mic 1", "Mic 2", "Line", "Mono In", "Headphone", "Speaker", + "Mono Out", "Zh"}; +static const char *wm9713_rec_gain[] = {"+1.5dB Steps", "+0.75dB Steps"}; +static const char *wm9713_alc_select[] = {"None", "Left", "Right", "Stereo"}; +static const char *wm9713_mono_pga[] = {"Vmid", "Zh", "Mono", "Inv", + "Mono Vmid", "Inv Vmid"}; +static const char *wm9713_spk_pga[] = + {"Vmid", "Zh", "Headphone", "Speaker", "Inv", "Headphone Vmid", + "Speaker Vmid", "Inv Vmid"}; +static const char *wm9713_hp_pga[] = {"Vmid", "Zh", "Headphone", + "Headphone Vmid"}; +static const char *wm9713_out3_pga[] = {"Vmid", "Zh", "Inv 1", "Inv 1 Vmid"}; +static const char *wm9713_out4_pga[] = {"Vmid", "Zh", "Inv 2", "Inv 2 Vmid"}; +static const char *wm9713_dac_inv[] = + {"Off", "Mono", "Speaker", "Left Headphone", "Right Headphone", + "Headphone Mono", "NC", "Vmid"}; +static const char *wm9713_bass[] = {"Linear Control", "Adaptive Boost"}; +static const char *wm9713_ng_type[] = {"Constant Gain", "Mute"}; +static const char *wm9713_mic_select[] = {"Mic 1", "Mic 2 A", "Mic 2 B"}; +static const char *wm9713_micb_select[] = {"MPB", "MPA"}; + +static const struct soc_enum wm9713_enum[] = { +SOC_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer), /* record mic mixer 0 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux), /* record mux hp 1 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux), /* record mux mono 2 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src), /* record mux left 3 */ +SOC_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src), /* record mux right 4*/ +SOC_ENUM_DOUBLE(AC97_CD, 14, 6, 2, wm9713_rec_gain), /* record step size 5 */ +SOC_ENUM_SINGLE(AC97_PCI_SVID, 14, 4, wm9713_alc_select), /* alc source select 6*/ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 14, 4, wm9713_mono_pga), /* mono input select 7 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 11, 8, wm9713_spk_pga), /* speaker left input select 8 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 8, 8, wm9713_spk_pga), /* speaker right input select 9 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 6, 3, wm9713_hp_pga), /* headphone left input 10 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 4, 3, wm9713_hp_pga), /* headphone right input 11 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 2, 4, wm9713_out3_pga), /* out 3 source 12 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN, 0, 4, wm9713_out4_pga), /* out 4 source 13 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 13, 8, wm9713_dac_inv), /* dac invert 1 14 */ +SOC_ENUM_SINGLE(AC97_REC_GAIN_MIC, 10, 8, wm9713_dac_inv), /* dac invert 2 15 */ +SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, wm9713_bass), /* bass control 16 */ +SOC_ENUM_SINGLE(AC97_PCI_SVID, 5, 2, wm9713_ng_type), /* noise gate type 17 */ +SOC_ENUM_SINGLE(AC97_3D_CONTROL, 12, 3, wm9713_mic_select), /* mic selection 18 */ +SOC_ENUM_SINGLE(MICB_MUX, 0, 2, wm9713_micb_select), /* mic selection 19 */ +}; + +static const struct snd_kcontrol_new wm9713_snd_ac97_controls[] = { +SOC_DOUBLE("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Speaker Playback Switch", AC97_MASTER, 15, 7, 1, 1), +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_DOUBLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 7, 1, 1), +SOC_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1), +SOC_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1), +SOC_SINGLE("Mic 1 Volume", AC97_MIC, 8, 31, 1), +SOC_SINGLE("Mic 2 Volume", AC97_MIC, 0, 31, 1), + +SOC_SINGLE("Mic Boost (+20dB) Switch", AC97_LINE, 5, 1, 0), +SOC_SINGLE("Mic Headphone Mixer Volume", AC97_LINE, 0, 7, 1), + +SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1), +SOC_ENUM("Capture Volume Steps", wm9713_enum[5]), +SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0), +SOC_SINGLE("Capture ZC Switch", AC97_CD, 7, 1, 0), + +SOC_SINGLE("Capture to Headphone Volume", AC97_VIDEO, 11, 7, 1), +SOC_SINGLE("Capture to Mono Boost (+20dB) Switch", AC97_VIDEO, 8, 1, 0), +SOC_SINGLE("Capture ADC Boost (+20dB) Switch", AC97_VIDEO, 6, 1, 0), + +SOC_SINGLE("ALC Target Volume", AC97_CODEC_CLASS_REV, 12, 15, 0), +SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0), +SOC_SINGLE("ALC Decay Time", AC97_CODEC_CLASS_REV, 4, 15, 0), +SOC_SINGLE("ALC Attack Time", AC97_CODEC_CLASS_REV, 0, 15, 0), +SOC_ENUM("ALC Function", wm9713_enum[6]), +SOC_SINGLE("ALC Max Volume", AC97_PCI_SVID, 11, 7, 0), +SOC_SINGLE("ALC ZC Timeout", AC97_PCI_SVID, 9, 3, 0), +SOC_SINGLE("ALC ZC Switch", AC97_PCI_SVID, 8, 1, 0), +SOC_SINGLE("ALC NG Switch", AC97_PCI_SVID, 7, 1, 0), +SOC_ENUM("ALC NG Type", wm9713_enum[17]), +SOC_SINGLE("ALC NG Threshold", AC97_PCI_SVID, 0, 31, 0), + +SOC_DOUBLE("Speaker Playback ZC Switch", AC97_MASTER, 14, 6, 1, 0), +SOC_DOUBLE("Headphone Playback ZC Switch", AC97_HEADPHONE, 14, 6, 1, 0), + +SOC_SINGLE("Out4 Playback Switch", AC97_MASTER_MONO, 15, 1, 1), +SOC_SINGLE("Out4 Playback ZC Switch", AC97_MASTER_MONO, 14, 1, 0), +SOC_SINGLE("Out4 Playback Volume", AC97_MASTER_MONO, 8, 63, 1), + +SOC_SINGLE("Out3 Playback Switch", AC97_MASTER_MONO, 7, 1, 1), +SOC_SINGLE("Out3 Playback ZC Switch", AC97_MASTER_MONO, 6, 1, 0), +SOC_SINGLE("Out3 Playback Volume", AC97_MASTER_MONO, 0, 63, 1), + +SOC_SINGLE("Mono Capture Volume", AC97_MASTER_TONE, 8, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_TONE, 7, 1, 1), +SOC_SINGLE("Mono Playback ZC Switch", AC97_MASTER_TONE, 6, 1, 0), +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_TONE, 0, 31, 1), + +SOC_SINGLE("PC Beep Playback Headphone Volume", AC97_AUX, 12, 7, 1), +SOC_SINGLE("PC Beep Playback Speaker Volume", AC97_AUX, 8, 7, 1), +SOC_SINGLE("PC Beep Playback Mono Volume", AC97_AUX, 4, 7, 1), + +SOC_SINGLE("Voice Playback Headphone Volume", AC97_PCM, 12, 7, 1), +SOC_SINGLE("Voice Playback Master Volume", AC97_PCM, 8, 7, 1), +SOC_SINGLE("Voice Playback Mono Volume", AC97_PCM, 4, 7, 1), + +SOC_SINGLE("Aux Playback Headphone Volume", AC97_REC_SEL, 12, 7, 1), +SOC_SINGLE("Aux Playback Master Volume", AC97_REC_SEL, 8, 7, 1), +SOC_SINGLE("Aux Playback Mono Volume", AC97_REC_SEL, 4, 7, 1), + +SOC_ENUM("Bass Control", wm9713_enum[16]), +SOC_SINGLE("Bass Cut-off Switch", AC97_GENERAL_PURPOSE, 12, 1, 1), +SOC_SINGLE("Tone Cut-off Switch", AC97_GENERAL_PURPOSE, 4, 1, 1), +SOC_SINGLE("Playback Attenuate (-6dB) Switch", AC97_GENERAL_PURPOSE, 6, 1, 0), +SOC_SINGLE("Bass Volume", AC97_GENERAL_PURPOSE, 8, 15, 1), +SOC_SINGLE("Tone Volume", AC97_GENERAL_PURPOSE, 0, 15, 1), + +SOC_SINGLE("3D Upper Cut-off Switch", AC97_REC_GAIN_MIC, 5, 1, 0), +SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), +SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), +}; + +/* add non dapm controls */ +static int wm9713_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm9713_snd_ac97_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm9713_snd_ac97_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* We have to create a fake left and right HP mixers because + * the codec only has a single control that is shared by both channels. + * This makes it impossible to determine the audio path using the current + * register map, thus we add a new (virtual) register to help determine the + * audio route within the device. + */ +static int mixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 l, r, beep, tone, phone, rec, pcm, aux; + + l = ac97_read(w->codec, HPL_MIXER); + r = ac97_read(w->codec, HPR_MIXER); + beep = ac97_read(w->codec, AC97_PC_BEEP); + tone = ac97_read(w->codec, AC97_MASTER_TONE); + phone = ac97_read(w->codec, AC97_PHONE); + rec = ac97_read(w->codec, AC97_REC_SEL); + pcm = ac97_read(w->codec, AC97_PCM); + aux = ac97_read(w->codec, AC97_AUX); + + if (event & SND_SOC_DAPM_PRE_REG) + return 0; + if ((l & 0x1) || (r & 0x1)) + ac97_write(w->codec, AC97_PC_BEEP, beep & 0x7fff); + else + ac97_write(w->codec, AC97_PC_BEEP, beep | 0x8000); + + if ((l & 0x2) || (r & 0x2)) + ac97_write(w->codec, AC97_MASTER_TONE, tone & 0x7fff); + else + ac97_write(w->codec, AC97_MASTER_TONE, tone | 0x8000); + + if ((l & 0x4) || (r & 0x4)) + ac97_write(w->codec, AC97_PHONE, phone & 0x7fff); + else + ac97_write(w->codec, AC97_PHONE, phone | 0x8000); + + if ((l & 0x8) || (r & 0x8)) + ac97_write(w->codec, AC97_REC_SEL, rec & 0x7fff); + else + ac97_write(w->codec, AC97_REC_SEL, rec | 0x8000); + + if ((l & 0x10) || (r & 0x10)) + ac97_write(w->codec, AC97_PCM, pcm & 0x7fff); + else + ac97_write(w->codec, AC97_PCM, pcm | 0x8000); + + if ((l & 0x20) || (r & 0x20)) + ac97_write(w->codec, AC97_AUX, aux & 0x7fff); + else + ac97_write(w->codec, AC97_AUX, aux | 0x8000); + + return 0; +} + +/* Left Headphone Mixers */ +static const struct snd_kcontrol_new wm9713_hpl_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", HPL_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", HPL_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", HPL_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", HPL_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("MonoIn Playback Switch", HPL_MIXER, 1, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", HPL_MIXER, 0, 1, 0), +}; + +/* Right Headphone Mixers */ +static const struct snd_kcontrol_new wm9713_hpr_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", HPR_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Voice Playback Switch", HPR_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Aux Playback Switch", HPR_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("PCM Playback Switch", HPR_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("MonoIn Playback Switch", HPR_MIXER, 1, 1, 0), +SOC_DAPM_SINGLE("Bypass Playback Switch", HPR_MIXER, 0, 1, 0), +}; + +/* headphone capture mux */ +static const struct snd_kcontrol_new wm9713_hp_rec_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[1]); + +/* headphone mic mux */ +static const struct snd_kcontrol_new wm9713_hp_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[0]); + +/* Speaker Mixer */ +static const struct snd_kcontrol_new wm9713_speaker_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 11, 1, 1), +SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 11, 1, 1), +SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 11, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 14, 1, 1), +SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 14, 1, 1), +SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 14, 1, 1), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm9713_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("PC Beep Playback Switch", AC97_AUX, 7, 1, 1), +SOC_DAPM_SINGLE("Voice Playback Switch", AC97_PCM, 7, 1, 1), +SOC_DAPM_SINGLE("Aux Playback Switch", AC97_REC_SEL, 7, 1, 1), +SOC_DAPM_SINGLE("PCM Playback Switch", AC97_PHONE, 13, 1, 1), +SOC_DAPM_SINGLE("MonoIn Playback Switch", AC97_MASTER_TONE, 13, 1, 1), +SOC_DAPM_SINGLE("Bypass Playback Switch", AC97_PC_BEEP, 13, 1, 1), +SOC_DAPM_SINGLE("Mic 1 Sidetone Switch", AC97_LINE, 7, 1, 1), +SOC_DAPM_SINGLE("Mic 2 Sidetone Switch", AC97_LINE, 6, 1, 1), +}; + +/* mono mic mux */ +static const struct snd_kcontrol_new wm9713_mono_mic_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[2]); + +/* mono output mux */ +static const struct snd_kcontrol_new wm9713_mono_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[7]); + +/* speaker left output mux */ +static const struct snd_kcontrol_new wm9713_hp_spkl_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[8]); + +/* speaker right output mux */ +static const struct snd_kcontrol_new wm9713_hp_spkr_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[9]); + +/* headphone left output mux */ +static const struct snd_kcontrol_new wm9713_hpl_out_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[10]); + +/* headphone right output mux */ +static const struct snd_kcontrol_new wm9713_hpr_out_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[11]); + +/* Out3 mux */ +static const struct snd_kcontrol_new wm9713_out3_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[12]); + +/* Out4 mux */ +static const struct snd_kcontrol_new wm9713_out4_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[13]); + +/* DAC inv mux 1 */ +static const struct snd_kcontrol_new wm9713_dac_inv1_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[14]); + +/* DAC inv mux 2 */ +static const struct snd_kcontrol_new wm9713_dac_inv2_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[15]); + +/* Capture source left */ +static const struct snd_kcontrol_new wm9713_rec_srcl_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[3]); + +/* Capture source right */ +static const struct snd_kcontrol_new wm9713_rec_srcr_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[4]); + +/* mic source */ +static const struct snd_kcontrol_new wm9713_mic_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[18]); + +/* mic source B virtual control */ +static const struct snd_kcontrol_new wm9713_micb_sel_mux_controls = +SOC_DAPM_ENUM("Route", wm9713_enum[19]); + +static const struct snd_soc_dapm_widget wm9713_dapm_widgets[] = { +SND_SOC_DAPM_MUX("Capture Headphone Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_rec_mux_controls), +SND_SOC_DAPM_MUX("Sidetone Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_mic_mux_controls), +SND_SOC_DAPM_MUX("Capture Mono Mux", SND_SOC_NOPM, 0, 0, + &wm9713_mono_mic_mux_controls), +SND_SOC_DAPM_MUX("Mono Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_mono_mux_controls), +SND_SOC_DAPM_MUX("Left Speaker Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_spkl_mux_controls), +SND_SOC_DAPM_MUX("Right Speaker Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hp_spkr_mux_controls), +SND_SOC_DAPM_MUX("Left Headphone Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hpl_out_mux_controls), +SND_SOC_DAPM_MUX("Right Headphone Out Mux", SND_SOC_NOPM, 0, 0, + &wm9713_hpr_out_mux_controls), +SND_SOC_DAPM_MUX("Out 3 Mux", SND_SOC_NOPM, 0, 0, + &wm9713_out3_mux_controls), +SND_SOC_DAPM_MUX("Out 4 Mux", SND_SOC_NOPM, 0, 0, + &wm9713_out4_mux_controls), +SND_SOC_DAPM_MUX("DAC Inv Mux 1", SND_SOC_NOPM, 0, 0, + &wm9713_dac_inv1_mux_controls), +SND_SOC_DAPM_MUX("DAC Inv Mux 2", SND_SOC_NOPM, 0, 0, + &wm9713_dac_inv2_mux_controls), +SND_SOC_DAPM_MUX("Left Capture Source", SND_SOC_NOPM, 0, 0, + &wm9713_rec_srcl_mux_controls), +SND_SOC_DAPM_MUX("Right Capture Source", SND_SOC_NOPM, 0, 0, + &wm9713_rec_srcr_mux_controls), +SND_SOC_DAPM_MUX("Mic A Source", SND_SOC_NOPM, 0, 0, + &wm9713_mic_sel_mux_controls), +SND_SOC_DAPM_MUX("Mic B Source", SND_SOC_NOPM, 0, 0, + &wm9713_micb_sel_mux_controls), +SND_SOC_DAPM_MIXER_E("Left HP Mixer", AC97_EXTENDED_MID, 3, 1, + &wm9713_hpl_mixer_controls[0], ARRAY_SIZE(wm9713_hpl_mixer_controls), + mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER_E("Right HP Mixer", AC97_EXTENDED_MID, 2, 1, + &wm9713_hpr_mixer_controls[0], ARRAY_SIZE(wm9713_hpr_mixer_controls), + mixer_event, SND_SOC_DAPM_POST_REG), +SND_SOC_DAPM_MIXER("Mono Mixer", AC97_EXTENDED_MID, 0, 1, + &wm9713_mono_mixer_controls[0], ARRAY_SIZE(wm9713_mono_mixer_controls)), +SND_SOC_DAPM_MIXER("Speaker Mixer", AC97_EXTENDED_MID, 1, 1, + &wm9713_speaker_mixer_controls[0], + ARRAY_SIZE(wm9713_speaker_mixer_controls)), +SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", AC97_EXTENDED_MID, 7, 1), +SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", AC97_EXTENDED_MID, 6, 1), +SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1), +SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), +SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0), +SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker", AC97_EXTENDED_MSTATUS, 7, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 3", AC97_EXTENDED_MSTATUS, 11, 1, NULL, 0), +SND_SOC_DAPM_PGA("Out 4", AC97_EXTENDED_MSTATUS, 12, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mono Out", AC97_EXTENDED_MSTATUS, 13, 1, NULL, 0), +SND_SOC_DAPM_PGA("Left Line In", AC97_EXTENDED_MSTATUS, 6, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right Line In", AC97_EXTENDED_MSTATUS, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mono In", AC97_EXTENDED_MSTATUS, 4, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic A PGA", AC97_EXTENDED_MSTATUS, 3, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic B PGA", AC97_EXTENDED_MSTATUS, 2, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic A Pre Amp", AC97_EXTENDED_MSTATUS, 1, 1, NULL, 0), +SND_SOC_DAPM_PGA("Mic B Pre Amp", AC97_EXTENDED_MSTATUS, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", AC97_EXTENDED_MSTATUS, 14, 1), +SND_SOC_DAPM_OUTPUT("MONO"), +SND_SOC_DAPM_OUTPUT("HPL"), +SND_SOC_DAPM_OUTPUT("HPR"), +SND_SOC_DAPM_OUTPUT("SPKL"), +SND_SOC_DAPM_OUTPUT("SPKR"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_INPUT("LINEL"), +SND_SOC_DAPM_INPUT("LINER"), +SND_SOC_DAPM_INPUT("MONOIN"), +SND_SOC_DAPM_INPUT("PCBEEP"), +SND_SOC_DAPM_INPUT("MIC1"), +SND_SOC_DAPM_INPUT("MIC2A"), +SND_SOC_DAPM_INPUT("MIC2B"), +SND_SOC_DAPM_VMID("VMID"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* left HP mixer */ + {"Left HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Left HP Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Left HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Left HP Mixer", "Bypass Playback Switch", "Left Line In"}, + {"Left HP Mixer", "PCM Playback Switch", "Left DAC"}, + {"Left HP Mixer", "MonoIn Playback Switch", "Mono In"}, + {"Left HP Mixer", NULL, "Capture Headphone Mux"}, + + /* right HP mixer */ + {"Right HP Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Right HP Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Right HP Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Right HP Mixer", "Bypass Playback Switch", "Right Line In"}, + {"Right HP Mixer", "PCM Playback Switch", "Right DAC"}, + {"Right HP Mixer", "MonoIn Playback Switch", "Mono In"}, + {"Right HP Mixer", NULL, "Capture Headphone Mux"}, + + /* virtual mixer - mixes left & right channels for spk and mono */ + {"AC97 Mixer", NULL, "Left DAC"}, + {"AC97 Mixer", NULL, "Right DAC"}, + {"Line Mixer", NULL, "Right Line In"}, + {"Line Mixer", NULL, "Left Line In"}, + {"HP Mixer", NULL, "Left HP Mixer"}, + {"HP Mixer", NULL, "Right HP Mixer"}, + {"Capture Mixer", NULL, "Left Capture Source"}, + {"Capture Mixer", NULL, "Right Capture Source"}, + + /* speaker mixer */ + {"Speaker Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Speaker Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Speaker Mixer", "Bypass Playback Switch", "Line Mixer"}, + {"Speaker Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Speaker Mixer", "MonoIn Playback Switch", "Mono In"}, + + /* mono mixer */ + {"Mono Mixer", "PC Beep Playback Switch", "PCBEEP"}, + {"Mono Mixer", "Voice Playback Switch", "Voice DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux DAC"}, + {"Mono Mixer", "Bypass Playback Switch", "Line Mixer"}, + {"Mono Mixer", "PCM Playback Switch", "AC97 Mixer"}, + {"Mono Mixer", "Mic 1 Sidetone Switch", "Mic A PGA"}, + {"Mono Mixer", "Mic 2 Sidetone Switch", "Mic B PGA"}, + {"Mono Mixer", NULL, "Capture Mono Mux"}, + + /* DAC inv mux 1 */ + {"DAC Inv Mux 1", "Mono", "Mono Mixer"}, + {"DAC Inv Mux 1", "Speaker", "Speaker Mixer"}, + {"DAC Inv Mux 1", "Left Headphone", "Left HP Mixer"}, + {"DAC Inv Mux 1", "Right Headphone", "Right HP Mixer"}, + {"DAC Inv Mux 1", "Headphone Mono", "HP Mixer"}, + + /* DAC inv mux 2 */ + {"DAC Inv Mux 2", "Mono", "Mono Mixer"}, + {"DAC Inv Mux 2", "Speaker", "Speaker Mixer"}, + {"DAC Inv Mux 2", "Left Headphone", "Left HP Mixer"}, + {"DAC Inv Mux 2", "Right Headphone", "Right HP Mixer"}, + {"DAC Inv Mux 2", "Headphone Mono", "HP Mixer"}, + + /* headphone left mux */ + {"Left Headphone Out Mux", "Headphone", "Left HP Mixer"}, + + /* headphone right mux */ + {"Right Headphone Out Mux", "Headphone", "Right HP Mixer"}, + + /* speaker left mux */ + {"Left Speaker Out Mux", "Headphone", "Left HP Mixer"}, + {"Left Speaker Out Mux", "Speaker", "Speaker Mixer"}, + {"Left Speaker Out Mux", "Inv", "DAC Inv Mux 1"}, + + /* speaker right mux */ + {"Right Speaker Out Mux", "Headphone", "Right HP Mixer"}, + {"Right Speaker Out Mux", "Speaker", "Speaker Mixer"}, + {"Right Speaker Out Mux", "Inv", "DAC Inv Mux 2"}, + + /* mono mux */ + {"Mono Out Mux", "Mono", "Mono Mixer"}, + {"Mono Out Mux", "Inv", "DAC Inv Mux 1"}, + + /* out 3 mux */ + {"Out 3 Mux", "Inv 1", "DAC Inv Mux 1"}, + + /* out 4 mux */ + {"Out 4 Mux", "Inv 2", "DAC Inv Mux 2"}, + + /* output pga */ + {"HPL", NULL, "Left Headphone"}, + {"Left Headphone", NULL, "Left Headphone Out Mux"}, + {"HPR", NULL, "Right Headphone"}, + {"Right Headphone", NULL, "Right Headphone Out Mux"}, + {"OUT3", NULL, "Out 3"}, + {"Out 3", NULL, "Out 3 Mux"}, + {"OUT4", NULL, "Out 4"}, + {"Out 4", NULL, "Out 4 Mux"}, + {"SPKL", NULL, "Left Speaker"}, + {"Left Speaker", NULL, "Left Speaker Out Mux"}, + {"SPKR", NULL, "Right Speaker"}, + {"Right Speaker", NULL, "Right Speaker Out Mux"}, + {"MONO", NULL, "Mono Out"}, + {"Mono Out", NULL, "Mono Out Mux"}, + + /* input pga */ + {"Left Line In", NULL, "LINEL"}, + {"Right Line In", NULL, "LINER"}, + {"Mono In", NULL, "MONOIN"}, + {"Mic A PGA", NULL, "Mic A Pre Amp"}, + {"Mic B PGA", NULL, "Mic B Pre Amp"}, + + /* left capture select */ + {"Left Capture Source", "Mic 1", "Mic A Pre Amp"}, + {"Left Capture Source", "Mic 2", "Mic B Pre Amp"}, + {"Left Capture Source", "Line", "LINEL"}, + {"Left Capture Source", "Mono In", "MONOIN"}, + {"Left Capture Source", "Headphone", "Left HP Mixer"}, + {"Left Capture Source", "Speaker", "Speaker Mixer"}, + {"Left Capture Source", "Mono Out", "Mono Mixer"}, + + /* right capture select */ + {"Right Capture Source", "Mic 1", "Mic A Pre Amp"}, + {"Right Capture Source", "Mic 2", "Mic B Pre Amp"}, + {"Right Capture Source", "Line", "LINER"}, + {"Right Capture Source", "Mono In", "MONOIN"}, + {"Right Capture Source", "Headphone", "Right HP Mixer"}, + {"Right Capture Source", "Speaker", "Speaker Mixer"}, + {"Right Capture Source", "Mono Out", "Mono Mixer"}, + + /* left ADC */ + {"Left ADC", NULL, "Left Capture Source"}, + {"Left Voice ADC", NULL, "Left ADC"}, + {"Left HiFi ADC", NULL, "Left ADC"}, + + /* right ADC */ + {"Right ADC", NULL, "Right Capture Source"}, + {"Right Voice ADC", NULL, "Right ADC"}, + {"Right HiFi ADC", NULL, "Right ADC"}, + + /* mic */ + {"Mic A Pre Amp", NULL, "Mic A Source"}, + {"Mic A Source", "Mic 1", "MIC1"}, + {"Mic A Source", "Mic 2 A", "MIC2A"}, + {"Mic A Source", "Mic 2 B", "Mic B Source"}, + {"Mic B Pre Amp", "MPB", "Mic B Source"}, + {"Mic B Source", NULL, "MIC2B"}, + + /* headphone capture */ + {"Capture Headphone Mux", "Stereo", "Capture Mixer"}, + {"Capture Headphone Mux", "Left", "Left Capture Source"}, + {"Capture Headphone Mux", "Right", "Right Capture Source"}, + + /* mono capture */ + {"Capture Mono Mux", "Stereo", "Capture Mixer"}, + {"Capture Mono Mux", "Left", "Left Capture Source"}, + {"Capture Mono Mux", "Right", "Right Capture Source"}, +}; + +static int wm9713_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm9713_dapm_widgets, + ARRAY_SIZE(wm9713_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || + reg == AC97_VENDOR_ID1 || reg == AC97_VENDOR_ID2 || + reg == AC97_CD) + return soc_ac97_ops.read(codec->ac97, reg); + else { + reg = reg >> 1; + + if (reg > (ARRAY_SIZE(wm9713_reg))) + return -EIO; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + if (reg < 0x7c) + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg <= (ARRAY_SIZE(wm9713_reg))) + cache[reg] = val; + + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 divsel:1; + u32 divctl:1; + u32 lf:1; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the PLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 22) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + + /* The the PLL output is always 98.304MHz. */ + target = 98304000; + + /* If the input frequency is over 14.4MHz then scale it down. */ + if (source > 14400000) { + source >>= 1; + pll_div->divsel = 1; + + if (source > 14400000) { + source >>= 1; + pll_div->divctl = 1; + } else + pll_div->divctl = 0; + + } else { + pll_div->divsel = 0; + pll_div->divctl = 0; + } + + /* Low frequency sources require an additional divide in the + * loop. + */ + if (source < 8192000) { + pll_div->lf = 1; + target >>= 2; + } else + pll_div->lf = 0; + + Ndiv = target / source; + if ((Ndiv < 5) || (Ndiv > 12)) + printk(KERN_WARNING + "WM9713 PLL N value %d out of recommended range!\n", + Ndiv); + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; +} + +/** + * Please note that changing the PLL input frequency may require + * resynchronisation with the AC97 controller. + */ +static int wm9713_set_pll(struct snd_soc_codec *codec, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct wm9713_priv *wm9713 = codec->private_data; + u16 reg, reg2; + struct _pll_div pll_div; + + /* turn PLL off ? */ + if (freq_in == 0 || freq_out == 0) { + /* disable PLL power and select ext source */ + reg = ac97_read(codec, AC97_HANDSET_RATE); + ac97_write(codec, AC97_HANDSET_RATE, reg | 0x0080); + reg = ac97_read(codec, AC97_EXTENDED_MID); + ac97_write(codec, AC97_EXTENDED_MID, reg | 0x0200); + wm9713->pll_out = 0; + return 0; + } + + pll_factors(&pll_div, freq_in); + + if (pll_div.k == 0) { + reg = (pll_div.n << 12) | (pll_div.lf << 11) | + (pll_div.divsel << 9) | (pll_div.divctl << 8); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + } else { + /* write the fractional k to the reg 0x46 pages */ + reg2 = (pll_div.n << 12) | (pll_div.lf << 11) | (1 << 10) | + (pll_div.divsel << 9) | (pll_div.divctl << 8); + + /* K [21:20] */ + reg = reg2 | (0x5 << 4) | (pll_div.k >> 20); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + /* K [19:16] */ + reg = reg2 | (0x4 << 4) | ((pll_div.k >> 16) & 0xf); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + /* K [15:12] */ + reg = reg2 | (0x3 << 4) | ((pll_div.k >> 12) & 0xf); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + /* K [11:8] */ + reg = reg2 | (0x2 << 4) | ((pll_div.k >> 8) & 0xf); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + /* K [7:4] */ + reg = reg2 | (0x1 << 4) | ((pll_div.k >> 4) & 0xf); + ac97_write(codec, AC97_LINE1_LEVEL, reg); + + reg = reg2 | (0x0 << 4) | (pll_div.k & 0xf); /* K [3:0] */ + ac97_write(codec, AC97_LINE1_LEVEL, reg); + } + + /* turn PLL on and select as source */ + reg = ac97_read(codec, AC97_EXTENDED_MID); + ac97_write(codec, AC97_EXTENDED_MID, reg & 0xfdff); + reg = ac97_read(codec, AC97_HANDSET_RATE); + ac97_write(codec, AC97_HANDSET_RATE, reg & 0xff7f); + wm9713->pll_out = freq_out; + wm9713->pll_in = freq_in; + + /* wait 10ms AC97 link frames for the link to stabilise */ + schedule_timeout_interruptible(msecs_to_jiffies(10)); + return 0; +} + +static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + return wm9713_set_pll(codec, pll_id, freq_in, freq_out); +} + +/* + * Tristate the PCM DAI lines, tristate can be disabled by calling + * wm9713_set_dai_fmt() + */ +static int wm9713_set_dai_tristate(struct snd_soc_dai *codec_dai, + int tristate) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0x9fff; + + if (tristate) + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + + return 0; +} + +/* + * Configure WM9713 clock dividers. + * Voice DAC needs 256 FS + */ +static int wm9713_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM9713_PCMCLK_DIV: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xf0ff; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_CLKA_MULT: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffd; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_CLKB_MULT: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0xfffb; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_HIFI_DIV: + reg = ac97_read(codec, AC97_HANDSET_RATE) & 0x8fff; + ac97_write(codec, AC97_HANDSET_RATE, reg | div); + break; + case WM9713_PCMBCLK_DIV: + reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xf1ff; + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg | div); + break; + case WM9713_PCMCLK_PLL_DIV: + reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80; + ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x60 | div); + break; + case WM9713_HIFI_PLL_DIV: + reg = ac97_read(codec, AC97_LINE1_LEVEL) & 0xff80; + ac97_write(codec, AC97_LINE1_LEVEL, reg | 0x70 | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 gpio = ac97_read(codec, AC97_GPIO_CFG) & 0xffc5; + u16 reg = 0x8000; + + /* clock masters */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + reg |= 0x4000; + gpio |= 0x0010; + break; + case SND_SOC_DAIFMT_CBM_CFS: + reg |= 0x6000; + gpio |= 0x0018; + break; + case SND_SOC_DAIFMT_CBS_CFS: + reg |= 0x2000; + gpio |= 0x001a; + break; + case SND_SOC_DAIFMT_CBS_CFM: + gpio |= 0x0012; + break; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + reg |= 0x00c0; + break; + case SND_SOC_DAIFMT_IB_NF: + reg |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + reg |= 0x0040; + break; + } + + /* DAI format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + reg |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + reg |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + reg |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + reg |= 0x0043; + break; + } + + ac97_write(codec, AC97_GPIO_CFG, gpio); + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + return 0; +} + +static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg |= 0x000c; + break; + } + + /* enable PCM interface in master mode */ + ac97_write(codec, AC97_CENTER_LFE_MASTER, reg); + return 0; +} + +static void wm9713_voiceshutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 status; + + /* Gracefully shut down the voice interface. */ + status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; + ac97_write(codec, AC97_HANDSET_RATE, 0x0280); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + ac97_write(codec, AC97_HANDSET_RATE, 0x0F80); + ac97_write(codec, AC97_EXTENDED_MID, status); +} + +static int ac97_hifi_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + int reg; + u16 vra; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return ac97_write(codec, reg, runtime->rate); +} + +static int ac97_aux_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 vra, xsle; + + vra = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, vra | 0x1); + xsle = ac97_read(codec, AC97_PCI_SID); + ac97_write(codec, AC97_PCI_SID, xsle | 0x8000); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return -ENODEV; + + return ac97_write(codec, AC97_PCM_SURR_DAC_RATE, runtime->rate); +} + +#define WM9713_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM9713_PCM_RATES (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM9713_PCM_FORMATS \ + (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_dai wm9713_dai[] = { +{ + .name = "AC97 HiFi", + .type = SND_SOC_DAI_AC97_BUS, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_hifi_prepare,}, + .dai_ops = { + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll,}, + }, + { + .name = "AC97 Aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9713_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .prepare = ac97_aux_prepare,}, + .dai_ops = { + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll,}, + }, + { + .name = "WM9713 Voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = WM9713_PCM_RATES, + .formats = WM9713_PCM_FORMATS,}, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM9713_PCM_RATES, + .formats = WM9713_PCM_FORMATS,}, + .ops = { + .hw_params = wm9713_pcm_hw_params, + .shutdown = wm9713_voiceshutdown,}, + .dai_ops = { + .set_clkdiv = wm9713_set_dai_clkdiv, + .set_pll = wm9713_set_dai_pll, + .set_fmt = wm9713_set_dai_fmt, + .set_tristate = wm9713_set_dai_tristate, + }, + }, +}; +EXPORT_SYMBOL_GPL(wm9713_dai); + +int wm9713_reset(struct snd_soc_codec *codec, int try_warm) +{ + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (ac97_read(codec, 0) == wm9713_reg[0]) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + if (ac97_read(codec, 0) != wm9713_reg[0]) + return -EIO; + return 0; +} +EXPORT_SYMBOL_GPL(wm9713_reset); + +static int wm9713_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + /* enable thermal shutdown */ + reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x1bff; + ac97_write(codec, AC97_EXTENDED_MID, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* enable master bias and vmid */ + reg = ac97_read(codec, AC97_EXTENDED_MID) & 0x3bff; + ac97_write(codec, AC97_EXTENDED_MID, reg); + ac97_write(codec, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: + /* disable everything including AC link */ + ac97_write(codec, AC97_EXTENDED_MID, 0xffff); + ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); + ac97_write(codec, AC97_POWERDOWN, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +static int wm9713_soc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + u16 reg; + + /* Disable everything except touchpanel - that will be handled + * by the touch driver and left disabled if touch is not in + * use. */ + reg = ac97_read(codec, AC97_EXTENDED_MID); + ac97_write(codec, AC97_EXTENDED_MID, reg | 0x7fff); + ac97_write(codec, AC97_EXTENDED_MSTATUS, 0xffff); + ac97_write(codec, AC97_POWERDOWN, 0x6f00); + ac97_write(codec, AC97_POWERDOWN, 0xffff); + + return 0; +} + +static int wm9713_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm9713_priv *wm9713 = codec->private_data; + int i, ret; + u16 *cache = codec->reg_cache; + + ret = wm9713_reset(codec, 1); + if (ret < 0) { + printk(KERN_ERR "could not reset AC97 codec\n"); + return ret; + } + + wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* do we need to re-start the PLL ? */ + if (wm9713->pll_out) + wm9713_set_pll(codec, 0, wm9713->pll_in, wm9713->pll_out); + + /* only synchronise the codec if warm reset failed */ + if (ret == 0) { + for (i = 2; i < ARRAY_SIZE(wm9713_reg) << 1; i += 2) { + if (i == AC97_POWERDOWN || i == AC97_EXTENDED_MID || + i == AC97_EXTENDED_MSTATUS || i > 0x66) + continue; + soc_ac97_ops.write(codec->ac97, i, cache[i>>1]); + } + } + + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + wm9713_set_bias_level(codec, SND_SOC_BIAS_ON); + + return ret; +} + +static int wm9713_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0, reg; + + printk(KERN_INFO "WM9713/WM9714 SoC Audio Codec %s\n", WM9713_VERSION); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = kmemdup(wm9713_reg, sizeof(wm9713_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + codec->reg_cache_size = sizeof(wm9713_reg); + codec->reg_cache_step = 2; + + codec->private_data = kzalloc(sizeof(struct wm9713_priv), GFP_KERNEL); + if (codec->private_data == NULL) { + ret = -ENOMEM; + goto priv_err; + } + + codec->name = "WM9713"; + codec->owner = THIS_MODULE; + codec->dai = wm9713_dai; + codec->num_dai = ARRAY_SIZE(wm9713_dai); + codec->write = ac97_write; + codec->read = ac97_read; + codec->set_bias_level = wm9713_set_bias_level; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) + goto codec_err; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + /* do a cold reset for the controller and then try + * a warm reset followed by an optional cold reset for codec */ + wm9713_reset(codec, 0); + ret = wm9713_reset(codec, 1); + if (ret < 0) { + printk(KERN_ERR "AC97 link error\n"); + goto reset_err; + } + + wm9713_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* unmute the adc - move to kcontrol */ + reg = ac97_read(codec, AC97_CD) & 0x7fff; + ac97_write(codec, AC97_CD, reg); + + wm9713_add_controls(codec); + wm9713_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) + goto reset_err; + return 0; + +reset_err: + snd_soc_free_pcms(socdev); + +pcm_err: + snd_soc_free_ac97_codec(codec); + +codec_err: + kfree(codec->private_data); + +priv_err: + kfree(codec->reg_cache); + +cache_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int wm9713_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->private_data); + kfree(codec->reg_cache); + kfree(codec->dai); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm9713 = { + .probe = wm9713_soc_probe, + .remove = wm9713_soc_remove, + .suspend = wm9713_soc_suspend, + .resume = wm9713_soc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9713); + +MODULE_DESCRIPTION("ASoC WM9713/WM9714 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9713.h b/sound/soc/codecs/wm9713.h new file mode 100644 index 0000000..63b8d81 --- /dev/null +++ b/sound/soc/codecs/wm9713.h @@ -0,0 +1,53 @@ +/* + * wm9713.h -- WM9713 Soc Audio driver + */ + +#ifndef _WM9713_H +#define _WM9713_H + +/* clock inputs */ +#define WM9713_CLKA_PIN 0 +#define WM9713_CLKB_PIN 1 + +/* clock divider ID's */ +#define WM9713_PCMCLK_DIV 0 +#define WM9713_CLKA_MULT 1 +#define WM9713_CLKB_MULT 2 +#define WM9713_HIFI_DIV 3 +#define WM9713_PCMBCLK_DIV 4 +#define WM9713_PCMCLK_PLL_DIV 5 +#define WM9713_HIFI_PLL_DIV 6 + +/* Calculate the appropriate bit mask for the external PCM clock divider */ +#define WM9713_PCMDIV(x) ((x - 1) << 8) + +/* Calculate the appropriate bit mask for the external HiFi clock divider */ +#define WM9713_HIFIDIV(x) ((x - 1) << 12) + +/* MCLK clock mulitipliers */ +#define WM9713_CLKA_X1 (0 << 1) +#define WM9713_CLKA_X2 (1 << 1) +#define WM9713_CLKB_X1 (0 << 2) +#define WM9713_CLKB_X2 (1 << 2) + +/* MCLK clock MUX */ +#define WM9713_CLK_MUX_A (0 << 0) +#define WM9713_CLK_MUX_B (1 << 0) + +/* Voice DAI BCLK divider */ +#define WM9713_PCMBCLK_DIV_1 (0 << 9) +#define WM9713_PCMBCLK_DIV_2 (1 << 9) +#define WM9713_PCMBCLK_DIV_4 (2 << 9) +#define WM9713_PCMBCLK_DIV_8 (3 << 9) +#define WM9713_PCMBCLK_DIV_16 (4 << 9) + +#define WM9713_DAI_AC97_HIFI 0 +#define WM9713_DAI_AC97_AUX 1 +#define WM9713_DAI_PCM_VOICE 2 + +extern struct snd_soc_codec_device soc_codec_dev_wm9713; +extern struct snd_soc_dai wm9713_dai[3]; + +int wm9713_reset(struct snd_soc_codec *codec, int try_warm); + +#endif |