diff options
Diffstat (limited to 'sound/soc/codecs')
39 files changed, 5854 insertions, 617 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 38a0e3b620a..c41289b5f58 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1,31 +1,40 @@ 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 + select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS + select SND_SOC_AD1980 if SND_SOC_AC97_BUS + select SND_SOC_AD73311 if I2C + select SND_SOC_AK4535 if I2C + select SND_SOC_CS4270 if I2C + select SND_SOC_PCM3008 + select SND_SOC_SSM2602 if I2C + select SND_SOC_TLV320AIC23 if I2C + select SND_SOC_TLV320AIC26 if SPI_MASTER + select SND_SOC_TLV320AIC3X if I2C + select SND_SOC_TWL4030 if TWL4030_CORE + select SND_SOC_UDA134X + select SND_SOC_UDA1380 if I2C + select SND_SOC_WM8350 if MFD_WM8350 + select SND_SOC_WM8510 if (I2C || SPI_MASTER) + select SND_SOC_WM8580 if I2C + select SND_SOC_WM8728 if (I2C || SPI_MASTER) + select SND_SOC_WM8731 if (I2C || SPI_MASTER) + select SND_SOC_WM8750 if (I2C || SPI_MASTER) + select SND_SOC_WM8753 if (I2C || SPI_MASTER) + select SND_SOC_WM8900 if I2C + select SND_SOC_WM8903 if I2C + select SND_SOC_WM8971 if I2C + select SND_SOC_WM8990 if I2C + select SND_SOC_WM9712 if SND_SOC_AC97_BUS + select SND_SOC_WM9713 if SND_SOC_AC97_BUS 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. + Support for the bus types used to access the codecs to be built must + be selected separately. + If unsure select "N". @@ -60,6 +69,12 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 +config SND_SOC_L3 + tristate + +config SND_SOC_PCM3008 + tristate + config SND_SOC_SSM2602 tristate @@ -75,15 +90,29 @@ config SND_SOC_TLV320AIC3X tristate depends on I2C +config SND_SOC_TWL4030 + tristate + depends on TWL4030_CORE + +config SND_SOC_UDA134X + tristate + select SND_SOC_L3 + config SND_SOC_UDA1380 tristate +config SND_SOC_WM8350 + tristate + config SND_SOC_WM8510 tristate config SND_SOC_WM8580 tristate +config SND_SOC_WM8728 + tristate + config SND_SOC_WM8731 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 90f0a585fc7..c4ddc9aa2bb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -3,13 +3,19 @@ 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-l3-objs := l3.o +snd-soc-pcm3008-objs := pcm3008.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-twl4030-objs := twl4030.o +snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o +snd-soc-wm8350-objs := wm8350.o snd-soc-wm8510-objs := wm8510.o snd-soc-wm8580-objs := wm8580.o +snd-soc-wm8728-objs := wm8728.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o @@ -25,13 +31,19 @@ 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_L3) += snd-soc-l3.o +obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.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_TWL4030) += snd-soc-twl4030.o +obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o +obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.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 diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index bd1ebdc6c86..fb53e6511af 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -24,7 +24,8 @@ #define AC97_VERSION "0.6" -static int ac97_prepare(struct snd_pcm_substream *substream) +static int ac97_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -42,7 +43,7 @@ static int ac97_prepare(struct snd_pcm_substream *substream) struct snd_soc_dai ac97_dai = { .name = "AC97 HiFi", - .type = SND_SOC_DAI_AC97, + .ac97_control = 1, .playback = { .stream_name = "AC97 Playback", .channels_min = 1, @@ -113,7 +114,7 @@ static int ac97_soc_probe(struct platform_device *pdev) if (ret < 0) goto bus_err; - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) goto bus_err; return 0; diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c index 1397b8e06c0..73fdbb4d4a3 100644 --- a/sound/soc/codecs/ad1980.c +++ b/sound/soc/codecs/ad1980.c @@ -85,6 +85,9 @@ 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_DOUBLE("Center/LFE Playback Volume", AC97_CENTER_LFE_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Center/LFE Playback Switch", AC97_CENTER_LFE_MASTER, 15, 7, 1, 1), + SOC_ENUM("Capture Source", ad1980_cap_src), SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0), @@ -142,10 +145,11 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, struct snd_soc_dai ad1980_dai = { .name = "AC97", + .ac97_control = 1, .playback = { .stream_name = "Playback", .channels_min = 2, - .channels_max = 2, + .channels_max = 6, .rates = SNDRV_PCM_RATE_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .capture = { @@ -192,6 +196,7 @@ static int ad1980_soc_probe(struct platform_device *pdev) struct snd_soc_codec *codec; int ret = 0; u16 vendor_id2; + u16 ext_status; printk(KERN_INFO "AD1980 SoC Audio Codec\n"); @@ -234,7 +239,7 @@ static int ad1980_soc_probe(struct platform_device *pdev) ret = ad1980_reset(codec, 0); if (ret < 0) { - printk(KERN_ERR "AC97 link error\n"); + printk(KERN_ERR "Failed to reset AD1980: AC97 link error\n"); goto reset_err; } @@ -253,12 +258,19 @@ static int ad1980_soc_probe(struct platform_device *pdev) "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 */ + /* unmute captures and playbacks volume */ + ac97_write(codec, AC97_MASTER, 0x0000); + ac97_write(codec, AC97_PCM, 0x0000); + ac97_write(codec, AC97_REC_GAIN, 0x0000); + ac97_write(codec, AC97_CENTER_LFE_MASTER, 0x0000); + ac97_write(codec, AC97_SURROUND_MASTER, 0x0000); + + /*power on LFE/CENTER/Surround DACs*/ + ext_status = ac97_read(codec, AC97_EXTENDED_STATUS); + ac97_write(codec, AC97_EXTENDED_STATUS, ext_status&~0x3800); ad1980_add_controls(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "ad1980: failed to register card\n"); goto reset_err; diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c index 37af8607b00..b09289a1e55 100644 --- a/sound/soc/codecs/ad73311.c +++ b/sound/soc/codecs/ad73311.c @@ -8,14 +8,10 @@ * 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> @@ -68,7 +64,7 @@ static int ad73311_soc_probe(struct platform_device *pdev) goto pcm_err; } - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "ad73311: failed to register card\n"); goto register_err; @@ -102,6 +98,18 @@ struct snd_soc_codec_device soc_codec_dev_ad73311 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_ad73311); +static int __init ad73311_init(void) +{ + return snd_soc_register_dai(&ad73311_dai); +} +module_init(ad73311_init); + +static void __exit ad73311_exit(void) +{ + snd_soc_unregister_dai(&ad73311_dai); +} +module_exit(ad73311_exit); + MODULE_DESCRIPTION("ASoC ad73311 driver"); MODULE_AUTHOR("Cliff Cai "); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 2a89b5888e1..81300d8d42c 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -339,7 +339,8 @@ static int ak4535_set_dai_sysclk(struct snd_soc_dai *codec_dai, } static int ak4535_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -451,8 +452,6 @@ struct snd_soc_dai ak4535_dai = { .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, @@ -513,7 +512,7 @@ static int ak4535_init(struct snd_soc_device *socdev) ak4535_add_controls(codec); ak4535_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "ak4535: failed to register card\n"); goto card_err; @@ -689,6 +688,18 @@ struct snd_soc_codec_device soc_codec_dev_ak4535 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_ak4535); +static int __init ak4535_modinit(void) +{ + return snd_soc_register_dai(&ak4535_dai); +} +module_init(ak4535_modinit); + +static void __exit ak4535_exit(void) +{ + snd_soc_unregister_dai(&ak4535_dai); +} +module_exit(ak4535_exit); + MODULE_DESCRIPTION("Soc AK4535 driver"); MODULE_AUTHOR("Richard Purdie"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 0bbd94501d7..f1aa0c34421 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -360,13 +360,14 @@ static int cs4270_i2c_write(struct snd_soc_codec *codec, unsigned int reg, /* * Program the CS4270 with the given hardware parameters. * - * The .dai_ops functions are used to provide board-specific data, like + * The .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_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -450,6 +451,19 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, return ret; } + /* Disable automatic volume control. It's enabled by default, and + * it causes volume change commands to be delayed, sometimes until + * after playback has started. + */ + + reg = cs4270_read_reg_cache(codec, CS4270_TRANS); + reg &= ~(CS4270_TRANS_SOFT | CS4270_TRANS_ZERO); + ret = cs4270_i2c_write(codec, CS4270_TRANS, reg); + if (ret < 0) { + printk(KERN_ERR "I2C write failed\n"); + return ret; + } + /* Thaw and power-up the codec */ ret = snd_soc_write(codec, CS4270_PWRCTL, 0); @@ -697,10 +711,10 @@ static int cs4270_probe(struct platform_device *pdev) 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; + cs4270_dai.ops.set_sysclk = cs4270_set_dai_sysclk; + cs4270_dai.ops.set_fmt = cs4270_set_dai_fmt; #ifdef CONFIG_SND_SOC_CS4270_HWMUTE - cs4270_dai.dai_ops.digital_mute = cs4270_mute; + cs4270_dai.ops.digital_mute = cs4270_mute; #endif } else printk(KERN_INFO "cs4270: no I2C device found, " @@ -709,7 +723,7 @@ static int cs4270_probe(struct platform_device *pdev) printk(KERN_INFO "cs4270: I2C disabled, using stand-alone mode\n"); #endif - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "cs4270: failed to register card\n"); goto error_del_driver; @@ -760,6 +774,18 @@ struct snd_soc_codec_device soc_codec_device_cs4270 = { }; EXPORT_SYMBOL_GPL(soc_codec_device_cs4270); +static int __init cs4270_init(void) +{ + return snd_soc_register_dai(&cs4270_dai); +} +module_init(cs4270_init); + +static void __exit cs4270_exit(void) +{ + snd_soc_unregister_dai(&cs4270_dai); +} +module_exit(cs4270_exit); + 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/l3.c b/sound/soc/codecs/l3.c new file mode 100644 index 00000000000..5353af58862 --- /dev/null +++ b/sound/soc/codecs/l3.c @@ -0,0 +1,91 @@ +/* + * L3 code + * + * Copyright (C) 2008, Christian Pellegrin <chripell@evolware.org> + * + * 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. + * + * + * based on: + * + * L3 bus algorithm module. + * + * Copyright (C) 2001 Russell King, All Rights Reserved. + * + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> + +#include <sound/l3.h> + +/* + * Send one byte of data to the chip. Data is latched into the chip on + * the rising edge of the clock. + */ +static void sendbyte(struct l3_pins *adap, unsigned int byte) +{ + int i; + + for (i = 0; i < 8; i++) { + adap->setclk(0); + udelay(adap->data_hold); + adap->setdat(byte & 1); + udelay(adap->data_setup); + adap->setclk(1); + udelay(adap->clock_high); + byte >>= 1; + } +} + +/* + * Send a set of bytes to the chip. We need to pulse the MODE line + * between each byte, but never at the start nor at the end of the + * transfer. + */ +static void sendbytes(struct l3_pins *adap, const u8 *buf, + int len) +{ + int i; + + for (i = 0; i < len; i++) { + if (i) { + udelay(adap->mode_hold); + adap->setmode(0); + udelay(adap->mode); + } + adap->setmode(1); + udelay(adap->mode_setup); + sendbyte(adap, buf[i]); + } +} + +int l3_write(struct l3_pins *adap, u8 addr, u8 *data, int len) +{ + adap->setclk(1); + adap->setdat(1); + adap->setmode(1); + udelay(adap->mode); + + adap->setmode(0); + udelay(adap->mode_setup); + sendbyte(adap, addr); + udelay(adap->mode_hold); + + sendbytes(adap, data, len); + + adap->setclk(1); + adap->setdat(1); + adap->setmode(0); + + return len; +} +EXPORT_SYMBOL_GPL(l3_write); + +MODULE_DESCRIPTION("L3 bit-banging driver"); +MODULE_AUTHOR("Christian Pellegrin <chripell@evolware.org>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm3008.c b/sound/soc/codecs/pcm3008.c new file mode 100644 index 00000000000..9a3e67e5319 --- /dev/null +++ b/sound/soc/codecs/pcm3008.c @@ -0,0 +1,212 @@ +/* + * ALSA Soc PCM3008 codec support + * + * Author: Hugo Villeneuve + * Copyright (C) 2008 Lyrtech inc + * + * Based on AC97 Soc codec, original copyright follow: + * Copyright 2005 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. + * + * Generic PCM3008 support. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/gpio.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "pcm3008.h" + +#define PCM3008_VERSION "0.2" + +#define PCM3008_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +struct snd_soc_dai pcm3008_dai = { + .name = "PCM3008 HiFi", + .playback = { + .stream_name = "PCM3008 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = PCM3008_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "PCM3008 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = PCM3008_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; +EXPORT_SYMBOL_GPL(pcm3008_dai); + +static void pcm3008_gpio_free(struct pcm3008_setup_data *setup) +{ + gpio_free(setup->dem0_pin); + gpio_free(setup->dem1_pin); + gpio_free(setup->pdad_pin); + gpio_free(setup->pdda_pin); +} + +static int pcm3008_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct pcm3008_setup_data *setup = socdev->codec_data; + int ret = 0; + + printk(KERN_INFO "PCM3008 SoC Audio Codec %s\n", PCM3008_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 = "PCM3008"; + codec->owner = THIS_MODULE; + codec->dai = &pcm3008_dai; + codec->num_dai = 1; + codec->write = NULL; + codec->read = NULL; + 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 "pcm3008: failed to create pcms\n"); + goto pcm_err; + } + + /* Register Card. */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "pcm3008: failed to register card\n"); + goto card_err; + } + + /* DEM1 DEM0 DE-EMPHASIS_MODE + * Low Low De-emphasis 44.1 kHz ON + * Low High De-emphasis OFF + * High Low De-emphasis 48 kHz ON + * High High De-emphasis 32 kHz ON + */ + + /* Configure DEM0 GPIO (turning OFF DAC De-emphasis). */ + ret = gpio_request(setup->dem0_pin, "codec_dem0"); + if (ret == 0) + ret = gpio_direction_output(setup->dem0_pin, 1); + if (ret != 0) + goto gpio_err; + + /* Configure DEM1 GPIO (turning OFF DAC De-emphasis). */ + ret = gpio_request(setup->dem1_pin, "codec_dem1"); + if (ret == 0) + ret = gpio_direction_output(setup->dem1_pin, 0); + if (ret != 0) + goto gpio_err; + + /* Configure PDAD GPIO. */ + ret = gpio_request(setup->pdad_pin, "codec_pdad"); + if (ret == 0) + ret = gpio_direction_output(setup->pdad_pin, 1); + if (ret != 0) + goto gpio_err; + + /* Configure PDDA GPIO. */ + ret = gpio_request(setup->pdda_pin, "codec_pdda"); + if (ret == 0) + ret = gpio_direction_output(setup->pdda_pin, 1); + if (ret != 0) + goto gpio_err; + + return ret; + +gpio_err: + pcm3008_gpio_free(setup); +card_err: + snd_soc_free_pcms(socdev); +pcm_err: + kfree(socdev->codec); + + return ret; +} + +static int pcm3008_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct pcm3008_setup_data *setup = socdev->codec_data; + + if (!codec) + return 0; + + pcm3008_gpio_free(setup); + snd_soc_free_pcms(socdev); + kfree(socdev->codec); + + return 0; +} + +#ifdef CONFIG_PM +static int pcm3008_soc_suspend(struct platform_device *pdev, pm_message_t msg) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct pcm3008_setup_data *setup = socdev->codec_data; + + gpio_set_value(setup->pdad_pin, 0); + gpio_set_value(setup->pdda_pin, 0); + + return 0; +} + +static int pcm3008_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct pcm3008_setup_data *setup = socdev->codec_data; + + gpio_set_value(setup->pdad_pin, 1); + gpio_set_value(setup->pdda_pin, 1); + + return 0; +} +#else +#define pcm3008_soc_suspend NULL +#define pcm3008_soc_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_pcm3008 = { + .probe = pcm3008_soc_probe, + .remove = pcm3008_soc_remove, + .suspend = pcm3008_soc_suspend, + .resume = pcm3008_soc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_pcm3008); + +static int __init pcm3008_init(void) +{ + return snd_soc_register_dai(&pcm3008_dai); +} +module_init(pcm3008_init); + +static void __exit pcm3008_exit(void) +{ + snd_soc_unregister_dai(&pcm3008_dai); +} +module_exit(pcm3008_exit); + +MODULE_DESCRIPTION("Soc PCM3008 driver"); +MODULE_AUTHOR("Hugo Villeneuve"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/pcm3008.h b/sound/soc/codecs/pcm3008.h new file mode 100644 index 00000000000..d04e87d3c06 --- /dev/null +++ b/sound/soc/codecs/pcm3008.h @@ -0,0 +1,25 @@ +/* + * PCM3008 ALSA SoC Layer + * + * Author: Hugo Villeneuve + * Copyright (C) 2008 Lyrtech inc + * + * 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_PCM3008_H +#define __LINUX_SND_SOC_PCM3008_H + +struct pcm3008_setup_data { + unsigned dem0_pin; + unsigned dem1_pin; + unsigned pdad_pin; + unsigned pdda_pin; +}; + +extern struct snd_soc_codec_device soc_codec_dev_pcm3008; +extern struct snd_soc_dai pcm3008_dai; + +#endif diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 44ef0dacd56..cac37361676 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -285,16 +285,23 @@ static inline int get_coeff(int mclk, int rate) } static int ssm2602_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { 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; + struct i2c_client *i2c = codec->control_data; u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3; int i = get_coeff(ssm2602->sysclk, params_rate(params)); + if (substream == ssm2602->slave_substream) { + dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n"); + return 0; + } + /*no match is found*/ if (i == ARRAY_SIZE(coeff_div)) return -EINVAL; @@ -324,19 +331,26 @@ static int ssm2602_hw_params(struct snd_pcm_substream *substream, return 0; } -static int ssm2602_startup(struct snd_pcm_substream *substream) +static int ssm2602_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->codec; struct ssm2602_priv *ssm2602 = codec->private_data; + struct i2c_client *i2c = codec->control_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. + * TODO: the ssm2602 allows pairs of non-matching PB/REC rates */ if (ssm2602->master_substream) { master_runtime = ssm2602->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, @@ -354,7 +368,8 @@ static int ssm2602_startup(struct snd_pcm_substream *substream) return 0; } -static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream) +static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -365,14 +380,21 @@ static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream) return 0; } -static void ssm2602_shutdown(struct snd_pcm_substream *substream) +static void ssm2602_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; /* deactivate */ if (!codec->active) ssm2602_write(codec, SSM2602_ACTIVE, 0); + + if (ssm2602->master_substream == substream) + ssm2602->master_substream = ssm2602->slave_substream; + + ssm2602->slave_substream = NULL; } static int ssm2602_mute(struct snd_soc_dai *dai, int mute) @@ -432,10 +454,10 @@ static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai, iface |= 0x0001; break; case SND_SOC_DAIFMT_DSP_A: - iface |= 0x0003; + iface |= 0x0013; break; case SND_SOC_DAIFMT_DSP_B: - iface |= 0x0013; + iface |= 0x0003; break; default: return -EINVAL; @@ -496,6 +518,9 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec, SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ SNDRV_PCM_RATE_96000) +#define SSM2602_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 ssm2602_dai = { .name = "SSM2602", .playback = { @@ -503,20 +528,18 @@ struct snd_soc_dai ssm2602_dai = { .channels_min = 2, .channels_max = 2, .rates = SSM2602_RATES, - .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .formats = SSM2602_FORMATS,}, .capture = { .stream_name = "Capture", .channels_min = 2, .channels_max = 2, .rates = SSM2602_RATES, - .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .formats = SSM2602_FORMATS,}, .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, @@ -601,7 +624,7 @@ static int ssm2602_init(struct snd_soc_device *socdev) ssm2602_add_controls(codec); ssm2602_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { pr_err("ssm2602: failed to register card\n"); goto card_err; @@ -770,6 +793,18 @@ struct snd_soc_codec_device soc_codec_dev_ssm2602 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2602); +static int __init ssm2602_modinit(void) +{ + return snd_soc_register_dai(&ssm2602_dai); +} +module_init(ssm2602_modinit); + +static void __exit ssm2602_exit(void) +{ + snd_soc_unregister_dai(&ssm2602_dai); +} +module_exit(ssm2602_exit); + MODULE_DESCRIPTION("ASoC ssm2602 driver"); MODULE_AUTHOR("Cliff Cai"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 44308dac9e1..cfdea007c4c 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -37,12 +37,6 @@ #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 */ @@ -261,20 +255,156 @@ static const struct snd_soc_dapm_route intercon[] = { }; -/* 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 */ +/* AIC23 driver data */ +struct aic23 { + struct snd_soc_codec codec; + int mclk; + int requested_adc; + int requested_dac; +}; + +/* + * Common Crystals used + * 11.2896 Mhz /128 = *88.2k /192 = 58.8k + * 12.0000 Mhz /125 = *96k /136 = 88.235K + * 12.2880 Mhz /128 = *96k /192 = 64k + * 16.9344 Mhz /128 = 132.3k /192 = *88.2k + * 18.4320 Mhz /128 = 144k /192 = *96k + */ + +/* + * Normal BOSR 0-256/2 = 128, 1-384/2 = 192 + * USB BOSR 0-250/2 = 125, 1-272/2 = 136 + */ +static const int bosr_usb_divisor_table[] = { + 128, 125, 192, 136 +}; +#define LOWER_GROUP ((1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<6) | (1<<7)) +#define UPPER_GROUP ((1<<8) | (1<<9) | (1<<10) | (1<<11) | (1<<15)) +static const unsigned short sr_valid_mask[] = { + LOWER_GROUP|UPPER_GROUP, /* Normal, bosr - 0*/ + LOWER_GROUP|UPPER_GROUP, /* Normal, bosr - 1*/ + LOWER_GROUP, /* Usb, bosr - 0*/ + UPPER_GROUP, /* Usb, bosr - 1*/ +}; +/* + * Every divisor is a factor of 11*12 + */ +#define SR_MULT (11*12) +#define A(x) (x) ? (SR_MULT/x) : 0 +static const unsigned char sr_adc_mult_table[] = { + A(2), A(2), A(12), A(12), A(0), A(0), A(3), A(1), + A(2), A(2), A(11), A(11), A(0), A(0), A(0), A(1) +}; +static const unsigned char sr_dac_mult_table[] = { + A(2), A(12), A(2), A(12), A(0), A(0), A(3), A(1), + A(2), A(11), A(2), A(11), A(0), A(0), A(0), A(1) }; +static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc, + int dac, int dac_l, int dac_h, int need_dac) +{ + if ((adc >= adc_l) && (adc <= adc_h) && + (dac >= dac_l) && (dac <= dac_h)) { + int diff_adc = need_adc - adc; + int diff_dac = need_dac - dac; + return abs(diff_adc) + abs(diff_dac); + } + return UINT_MAX; +} + +static int find_rate(int mclk, u32 need_adc, u32 need_dac) +{ + int i, j; + int best_i = -1; + int best_j = -1; + int best_div = 0; + unsigned best_score = UINT_MAX; + int adc_l, adc_h, dac_l, dac_h; + + need_adc *= SR_MULT; + need_dac *= SR_MULT; + /* + * rates given are +/- 1/32 + */ + adc_l = need_adc - (need_adc >> 5); + adc_h = need_adc + (need_adc >> 5); + dac_l = need_dac - (need_dac >> 5); + dac_h = need_dac + (need_dac >> 5); + for (i = 0; i < ARRAY_SIZE(bosr_usb_divisor_table); i++) { + int base = mclk / bosr_usb_divisor_table[i]; + int mask = sr_valid_mask[i]; + for (j = 0; j < ARRAY_SIZE(sr_adc_mult_table); + j++, mask >>= 1) { + int adc; + int dac; + int score; + if ((mask & 1) == 0) + continue; + adc = base * sr_adc_mult_table[j]; + dac = base * sr_dac_mult_table[j]; + score = get_score(adc, adc_l, adc_h, need_adc, + dac, dac_l, dac_h, need_dac); + if (best_score > score) { + best_score = score; + best_i = i; + best_j = j; + best_div = 0; + } + score = get_score((adc >> 1), adc_l, adc_h, need_adc, + (dac >> 1), dac_l, dac_h, need_dac); + /* prefer to have a /2 */ + if ((score != UINT_MAX) && (best_score >= score)) { + best_score = score; + best_i = i; + best_j = j; + best_div = 1; + } + } + } + return (best_j << 2) | best_i | (best_div << TLV320AIC23_CLKIN_SHIFT); +} + +#ifdef DEBUG +static void get_current_sample_rates(struct snd_soc_codec *codec, int mclk, + u32 *sample_rate_adc, u32 *sample_rate_dac) +{ + int src = tlv320aic23_read_reg_cache(codec, TLV320AIC23_SRATE); + int sr = (src >> 2) & 0x0f; + int val = (mclk / bosr_usb_divisor_table[src & 3]); + int adc = (val * sr_adc_mult_table[sr]) / SR_MULT; + int dac = (val * sr_dac_mult_table[sr]) / SR_MULT; + if (src & TLV320AIC23_CLKIN_HALF) { + adc >>= 1; + dac >>= 1; + } + *sample_rate_adc = adc; + *sample_rate_dac = dac; +} +#endif + +static int set_sample_rate_control(struct snd_soc_codec *codec, int mclk, + u32 sample_rate_adc, u32 sample_rate_dac) +{ + /* Search for the right sample rate */ + int data = find_rate(mclk, sample_rate_adc, sample_rate_dac); + if (data < 0) { + printk(KERN_ERR "%s:Invalid rate %u,%u requested\n", + __func__, sample_rate_adc, sample_rate_dac); + return -EINVAL; + } + tlv320aic23_write(codec, TLV320AIC23_SRATE, data); +#ifdef DEBUG + { + u32 adc, dac; + get_current_sample_rates(codec, mclk, &adc, &dac); + printk(KERN_DEBUG "actual samplerate = %u,%u reg=%x\n", + adc, dac, data); + } +#endif + return 0; +} + static int tlv320aic23_add_widgets(struct snd_soc_codec *codec) { snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, @@ -288,32 +418,36 @@ static int tlv320aic23_add_widgets(struct snd_soc_codec *codec) } static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->codec; - u16 iface_reg, data; - u8 count = 0; + u16 iface_reg; + int ret; + struct aic23 *aic23 = container_of(codec, struct aic23, codec); + u32 sample_rate_adc = aic23->requested_adc; + u32 sample_rate_dac = aic23->requested_dac; + u32 sample_rate = params_rate(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + aic23->requested_dac = sample_rate_dac = sample_rate; + if (!sample_rate_adc) + sample_rate_adc = sample_rate; + } else { + aic23->requested_adc = sample_rate_adc = sample_rate; + if (!sample_rate_dac) + sample_rate_dac = sample_rate; + } + ret = set_sample_rate_control(codec, aic23->mclk, sample_rate_adc, + sample_rate_dac); + if (ret < 0) + return ret; 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; @@ -332,7 +466,8 @@ static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, return 0; } -static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream) +static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -344,17 +479,23 @@ static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream) return 0; } -static void tlv320aic23_shutdown(struct snd_pcm_substream *substream) +static void tlv320aic23_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->codec; + struct aic23 *aic23 = container_of(codec, struct aic23, codec); /* deactivate */ if (!codec->active) { udelay(50); tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aic23->requested_dac = 0; + else + aic23->requested_adc = 0; } static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute) @@ -400,7 +541,7 @@ static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai, case SND_SOC_DAIFMT_I2S: iface_reg |= TLV320AIC23_FOR_I2S; break; - case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: iface_reg |= TLV320AIC23_FOR_DSP; break; case SND_SOC_DAIFMT_RIGHT_J: @@ -422,12 +563,9 @@ 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; + struct aic23 *aic23 = container_of(codec, struct aic23, codec); + aic23->mclk = freq; + return 0; } static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec, @@ -478,12 +616,10 @@ struct snd_soc_dai tlv320aic23_dai = { .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, - } + .digital_mute = tlv320aic23_mute, + .set_fmt = tlv320aic23_set_dai_fmt, + .set_sysclk = tlv320aic23_set_dai_sysclk, + } }; EXPORT_SYMBOL_GPL(tlv320aic23_dai); @@ -584,7 +720,7 @@ static int tlv320aic23_init(struct snd_soc_device *socdev) tlv320aic23_add_controls(codec); tlv320aic23_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "tlv320aic23: failed to register card\n"); goto card_err; @@ -659,14 +795,15 @@ static int tlv320aic23_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec; + struct aic23 *aic23; 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) + aic23 = kzalloc(sizeof(struct aic23), GFP_KERNEL); + if (aic23 == NULL) return -ENOMEM; - + codec = &aic23->codec; socdev->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); @@ -687,6 +824,7 @@ static int tlv320aic23_remove(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->codec; + struct aic23 *aic23 = container_of(codec, struct aic23, codec); if (codec->control_data) tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); @@ -697,7 +835,7 @@ static int tlv320aic23_remove(struct platform_device *pdev) i2c_del_driver(&tlv320aic23_i2c_driver); #endif kfree(codec->reg_cache); - kfree(codec); + kfree(aic23); return 0; } @@ -709,6 +847,18 @@ struct snd_soc_codec_device soc_codec_dev_tlv320aic23 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320aic23); +static int __init tlv320aic23_modinit(void) +{ + return snd_soc_register_dai(&tlv320aic23_dai); +} +module_init(tlv320aic23_modinit); + +static void __exit tlv320aic23_exit(void) +{ + snd_soc_unregister_dai(&tlv320aic23_dai); +} +module_exit(tlv320aic23_exit); + MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver"); MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c index bed8a9e63dd..29f2f1a017f 100644 --- a/sound/soc/codecs/tlv320aic26.c +++ b/sound/soc/codecs/tlv320aic26.c @@ -125,7 +125,8 @@ static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg, * Digital Audio Interface Operations */ static int aic26_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -287,8 +288,6 @@ struct snd_soc_dai aic26_dai = { }, .ops = { .hw_params = aic26_hw_params, - }, - .dai_ops = { .digital_mute = aic26_mute, .set_sysclk = aic26_set_sysclk, .set_fmt = aic26_set_fmt, @@ -360,7 +359,7 @@ static int aic26_probe(struct platform_device *pdev) /* CODEC is setup, we can register the card now */ dev_dbg(&pdev->dev, "Registering card\n"); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { dev_err(&pdev->dev, "aic26: failed to register card\n"); goto card_err; @@ -427,7 +426,7 @@ static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set); static int aic26_spi_probe(struct spi_device *spi) { struct aic26 *aic26; - int rc, i, reg; + int ret, i, reg; dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n"); @@ -457,6 +456,14 @@ static int aic26_spi_probe(struct spi_device *spi) aic26->codec.reg_cache_size = AIC26_NUM_REGS; aic26->codec.reg_cache = aic26->reg_cache; + aic26_dai.dev = &spi->dev; + ret = snd_soc_register_dai(&aic26_dai); + if (ret != 0) { + dev_err(&spi->dev, "Failed to register DAI: %d\n", ret); + kfree(aic26); + return ret; + } + /* Reset the codec to power on defaults */ aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00); @@ -475,8 +482,8 @@ static int aic26_spi_probe(struct spi_device *spi) /* Register the sysfs files for debugging */ /* Create SysFS files */ - rc = device_create_file(&spi->dev, &dev_attr_keyclick); - if (rc) + ret = device_create_file(&spi->dev, &dev_attr_keyclick); + if (ret) dev_info(&spi->dev, "error creating sysfs files\n"); #if defined(CONFIG_SND_SOC_OF_SIMPLE) @@ -493,6 +500,7 @@ static int aic26_spi_remove(struct spi_device *spi) { struct aic26 *aic26 = dev_get_drvdata(&spi->dev); + snd_soc_unregister_dai(&aic26_dai); kfree(aic26); return 0; diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index cff276ee261..b47a749c5ea 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -253,11 +253,17 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { 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, + SOC_SINGLE("LineL Playback Switch", LLOPM_CTRL, 3, 0x01, 0), + SOC_SINGLE("LineR Playback Switch", RLOPM_CTRL, 3, 0x01, 0), + SOC_DOUBLE_R("LineL DAC Playback Volume", DACL1_2_LLOPM_VOL, + DACR1_2_LLOPM_VOL, 0, 0x7f, 1), + SOC_SINGLE("LineL Left PGA Bypass Playback Volume", PGAL_2_LLOPM_VOL, + 0, 0x7f, 1), + SOC_SINGLE("LineR Right PGA Bypass Playback Volume", PGAR_2_RLOPM_VOL, + 0, 0x7f, 1), + SOC_DOUBLE_R("LineL Line2 Bypass Playback Volume", LINE2L_2_LLOPM_VOL, + LINE2R_2_LLOPM_VOL, 0, 0x7f, 1), + SOC_DOUBLE_R("LineR Line2 Bypass Playback Volume", LINE2L_2_RLOPM_VOL, LINE2R_2_RLOPM_VOL, 0, 0x7f, 1), SOC_DOUBLE_R("Mono DAC Playback Volume", DACL1_2_MONOLOPM_VOL, @@ -272,8 +278,12 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { 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, + SOC_DOUBLE_R("HP Right PGA Bypass Playback Volume", PGAR_2_HPLOUT_VOL, PGAR_2_HPROUT_VOL, 0, 0x7f, 1), + SOC_SINGLE("HPL PGA Bypass Playback Volume", PGAL_2_HPLOUT_VOL, + 0, 0x7f, 1), + SOC_SINGLE("HPR PGA Bypass Playback Volume", PGAL_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), @@ -281,8 +291,10 @@ static const struct snd_kcontrol_new aic3x_snd_controls[] = { 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_SINGLE("HPLCOM PGA Bypass Playback Volume", PGAL_2_HPLCOM_VOL, + 0, 0x7f, 1), + SOC_SINGLE("HPRCOM PGA Bypass Playback Volume", PGAL_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), @@ -333,7 +345,8 @@ 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("LineL Switch", DACL1_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("LineR Switch", DACL1_2_RLOPM_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), @@ -341,7 +354,8 @@ static const struct snd_kcontrol_new aic3x_left_dac_mixer_controls[] = { /* 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("LineL Switch", DACR1_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("LineR 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), @@ -350,14 +364,18 @@ static const struct snd_kcontrol_new aic3x_right_dac_mixer_controls[] = { /* 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("Line1R Switch", LINE1R_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), + SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_LADC_CTRL, 0, 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("Line1L Switch", LINE1L_2_RADC_CTRL, 3, 1, 1), SOC_DAPM_SINGLE_AIC3X("Line2R Switch", LINE2R_2_RADC_CTRL, 3, 1, 1), + SOC_DAPM_SINGLE_AIC3X("Mic3L Switch", MIC3LR_2_RADC_CTRL, 4, 1, 1), SOC_DAPM_SINGLE_AIC3X("Mic3R Switch", MIC3LR_2_RADC_CTRL, 0, 1, 1), }; @@ -379,34 +397,42 @@ 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("LineL Switch", PGAL_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("LineR Switch", PGAL_2_RLOPM_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), + SOC_DAPM_SINGLE("HPL Switch", PGAL_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPR Switch", PGAL_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPLCOM Switch", PGAL_2_HPLCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPRCOM Switch", PGAL_2_HPRCOM_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("LineL Switch", PGAR_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("LineR 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), + SOC_DAPM_SINGLE("HPL Switch", PGAR_2_HPLOUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPR Switch", PGAR_2_HPROUT_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPLCOM Switch", PGAR_2_HPLCOM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("HPRCOM 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("LineL Switch", LINE2L_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("LineR Switch", LINE2L_2_RLOPM_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), + SOC_DAPM_SINGLE("HPLCOM 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("LineL Switch", LINE2R_2_LLOPM_VOL, 7, 1, 0), + SOC_DAPM_SINGLE("LineR 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), + SOC_DAPM_SINGLE("HPRCOM Switch", LINE2R_2_HPRCOM_VOL, 7, 1, 0), }; static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { @@ -439,22 +465,26 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { /* Mono Output */ SND_SOC_DAPM_PGA("Mono Out", MONOLOPM_CTRL, 0, 0, NULL, 0), - /* Left Inputs to Left ADC */ + /* 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 Line1R 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 */ + /* 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 Line1L Mux", SND_SOC_NOPM, 0, 0, + &aic3x_right_line1_mux_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, @@ -531,7 +561,8 @@ static const struct snd_soc_dapm_route intercon[] = { {"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", "LineL Switch", "Left DAC Mux"}, + {"Left DAC_L1 Mixer", "LineR 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"}, @@ -557,7 +588,8 @@ static const struct snd_soc_dapm_route intercon[] = { {"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", "LineL Switch", "Right DAC Mux"}, + {"Right DAC_R1 Mixer", "LineR 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"}, @@ -592,8 +624,10 @@ static const struct snd_soc_dapm_route intercon[] = { {"Left Line2L Mux", "differential", "LINE2L"}, {"Left PGA Mixer", "Line1L Switch", "Left Line1L Mux"}, + {"Left PGA Mixer", "Line1R Switch", "Left Line1R Mux"}, {"Left PGA Mixer", "Line2L Switch", "Left Line2L Mux"}, {"Left PGA Mixer", "Mic3L Switch", "MIC3L"}, + {"Left PGA Mixer", "Mic3R Switch", "MIC3R"}, {"Left ADC", NULL, "Left PGA Mixer"}, {"Left ADC", NULL, "GPIO1 dmic modclk"}, @@ -605,18 +639,23 @@ static const struct snd_soc_dapm_route intercon[] = { {"Right Line2R Mux", "single-ended", "LINE2R"}, {"Right Line2R Mux", "differential", "LINE2R"}, + {"Right PGA Mixer", "Line1L Switch", "Right Line1L Mux"}, {"Right PGA Mixer", "Line1R Switch", "Right Line1R Mux"}, {"Right PGA Mixer", "Line2R Switch", "Right Line2R Mux"}, + {"Right PGA Mixer", "Mic3L Switch", "MIC3L"}, {"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", "LineL Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "LineR 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 PGA Bypass Mixer", "HPL Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "HPR Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "HPLCOM Switch", "Left PGA Mixer"}, + {"Left PGA Bypass Mixer", "HPRCOM Switch", "Left PGA Mixer"}, {"Left HPCOM Mux", "differential of HPLOUT", "Left PGA Bypass Mixer"}, {"Left HPCOM Mux", "constant VCM", "Left PGA Bypass Mixer"}, @@ -627,10 +666,13 @@ static const struct snd_soc_dapm_route intercon[] = { {"Left HP Out", NULL, "Left PGA Bypass Mixer"}, /* Right PGA Bypass */ - {"Right PGA Bypass Mixer", "Line Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "LineL Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "LineR 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 PGA Bypass Mixer", "HPL Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "HPR Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "HPLCOM Switch", "Right PGA Mixer"}, + {"Right PGA Bypass Mixer", "HPRCOM Switch", "Right PGA Mixer"}, {"Right HPCOM Mux", "differential of HPROUT", "Right PGA Bypass Mixer"}, {"Right HPCOM Mux", "constant VCM", "Right PGA Bypass Mixer"}, @@ -643,10 +685,11 @@ static const struct snd_soc_dapm_route intercon[] = { {"Right HP Out", NULL, "Right PGA Bypass Mixer"}, /* Left Line2 Bypass */ - {"Left Line2 Bypass Mixer", "Line Switch", "Left Line2L Mux"}, + {"Left Line2 Bypass Mixer", "LineL Switch", "Left Line2L Mux"}, + {"Left Line2 Bypass Mixer", "LineR 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 Line2 Bypass Mixer", "HPLCOM Switch", "Left Line2L Mux"}, {"Left HPCOM Mux", "differential of HPLOUT", "Left Line2 Bypass Mixer"}, {"Left HPCOM Mux", "constant VCM", "Left Line2 Bypass Mixer"}, @@ -657,10 +700,11 @@ static const struct snd_soc_dapm_route intercon[] = { {"Left HP Out", NULL, "Left Line2 Bypass Mixer"}, /* Right Line2 Bypass */ - {"Right Line2 Bypass Mixer", "Line Switch", "Right Line2R Mux"}, + {"Right Line2 Bypass Mixer", "LineL Switch", "Right Line2R Mux"}, + {"Right Line2 Bypass Mixer", "LineR 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 Line2 Bypass Mixer", "HPRCOM Switch", "Right Line2R Mux"}, {"Right HPCOM Mux", "differential of HPROUT", "Right Line2 Bypass Mixer"}, {"Right HPCOM Mux", "constant VCM", "Right Line2 Bypass Mixer"}, @@ -694,7 +738,8 @@ static int aic3x_add_widgets(struct snd_soc_codec *codec) } static int aic3x_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -846,6 +891,7 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; struct aic3x_priv *aic3x = codec->private_data; u8 iface_areg, iface_breg; + int delay = 0; iface_areg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLA) & 0x3f; iface_breg = aic3x_read_reg_cache(codec, AIC3X_ASD_INTF_CTRLB) & 0x3f; @@ -871,6 +917,8 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, SND_SOC_DAIFMT_INV_MASK)) { case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF): break; + case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF): + delay = 1; case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF): iface_breg |= (0x01 << 6); break; @@ -887,6 +935,7 @@ static int aic3x_set_dai_fmt(struct snd_soc_dai *codec_dai, /* set iface */ aic3x_write(codec, AIC3X_ASD_INTF_CTRLA, iface_areg); aic3x_write(codec, AIC3X_ASD_INTF_CTRLB, iface_breg); + aic3x_write(codec, AIC3X_ASD_INTF_CTRLC, delay); return 0; } @@ -981,14 +1030,41 @@ int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio) } EXPORT_SYMBOL_GPL(aic3x_get_gpio); +void aic3x_set_headset_detection(struct snd_soc_codec *codec, int detect, + int headset_debounce, int button_debounce) +{ + u8 val; + + val = ((detect & AIC3X_HEADSET_DETECT_MASK) + << AIC3X_HEADSET_DETECT_SHIFT) | + ((headset_debounce & AIC3X_HEADSET_DEBOUNCE_MASK) + << AIC3X_HEADSET_DEBOUNCE_SHIFT) | + ((button_debounce & AIC3X_BUTTON_DEBOUNCE_MASK) + << AIC3X_BUTTON_DEBOUNCE_SHIFT); + + if (detect & AIC3X_HEADSET_DETECT_MASK) + val |= AIC3X_HEADSET_DETECT_ENABLED; + + aic3x_write(codec, AIC3X_HEADSET_DETECT_CTRL_A, val); +} +EXPORT_SYMBOL_GPL(aic3x_set_headset_detection); + int aic3x_headset_detected(struct snd_soc_codec *codec) { u8 val; - aic3x_read(codec, AIC3X_RT_IRQ_FLAGS_REG, &val); - return (val >> 2) & 1; + aic3x_read(codec, AIC3X_HEADSET_DETECT_CTRL_B, &val); + return (val >> 4) & 1; } EXPORT_SYMBOL_GPL(aic3x_headset_detected); +int aic3x_button_pressed(struct snd_soc_codec *codec) +{ + u8 val; + aic3x_read(codec, AIC3X_HEADSET_DETECT_CTRL_B, &val); + return (val >> 5) & 1; +} +EXPORT_SYMBOL_GPL(aic3x_button_pressed); + #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) @@ -1009,8 +1085,6 @@ struct snd_soc_dai aic3x_dai = { .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, @@ -1152,7 +1226,7 @@ static int aic3x_init(struct snd_soc_device *socdev) aic3x_add_controls(codec); aic3x_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "aic3x: failed to register card\n"); goto card_err; @@ -1341,6 +1415,18 @@ struct snd_soc_codec_device soc_codec_dev_aic3x = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_aic3x); +static int __init aic3x_modinit(void) +{ + return snd_soc_register_dai(&aic3x_dai); +} +module_init(aic3x_modinit); + +static void __exit aic3x_exit(void) +{ + snd_soc_unregister_dai(&aic3x_dai); +} +module_exit(aic3x_exit); + 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 index 00a195aa02e..ac827e578c4 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -35,11 +35,15 @@ #define AIC3X_ASD_INTF_CTRLA 8 /* Audio serial data interface control register B */ #define AIC3X_ASD_INTF_CTRLB 9 +/* Audio serial data interface control register C */ +#define AIC3X_ASD_INTF_CTRLC 10 /* 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 - +/* Headset/button press detection register */ +#define AIC3X_HEADSET_DETECT_CTRL_A 13 +#define AIC3X_HEADSET_DETECT_CTRL_B 14 /* ADC PGA Gain control registers */ #define LADC_VOL 15 #define RADC_VOL 16 @@ -48,7 +52,9 @@ #define MIC3LR_2_RADC_CTRL 18 /* Line1 Input control registers */ #define LINE1L_2_LADC_CTRL 19 +#define LINE1R_2_LADC_CTRL 21 #define LINE1R_2_RADC_CTRL 22 +#define LINE1L_2_RADC_CTRL 24 /* Line2 Input control registers */ #define LINE2L_2_LADC_CTRL 20 #define LINE2R_2_RADC_CTRL 23 @@ -79,6 +85,8 @@ #define LINE2L_2_HPLOUT_VOL 45 #define LINE2R_2_HPROUT_VOL 62 #define PGAL_2_HPLOUT_VOL 46 +#define PGAL_2_HPROUT_VOL 60 +#define PGAR_2_HPLOUT_VOL 49 #define PGAR_2_HPROUT_VOL 63 #define DACL1_2_HPLOUT_VOL 47 #define DACR1_2_HPROUT_VOL 64 @@ -88,6 +96,8 @@ #define LINE2L_2_HPLCOM_VOL 52 #define LINE2R_2_HPRCOM_VOL 69 #define PGAL_2_HPLCOM_VOL 53 +#define PGAR_2_HPLCOM_VOL 56 +#define PGAL_2_HPRCOM_VOL 67 #define PGAR_2_HPRCOM_VOL 70 #define DACL1_2_HPLCOM_VOL 54 #define DACR1_2_HPRCOM_VOL 71 @@ -103,11 +113,17 @@ #define MONOLOPM_CTRL 79 /* Line Output Plus/Minus control registers */ #define LINE2L_2_LLOPM_VOL 80 +#define LINE2L_2_RLOPM_VOL 87 +#define LINE2R_2_LLOPM_VOL 83 #define LINE2R_2_RLOPM_VOL 90 #define PGAL_2_LLOPM_VOL 81 +#define PGAL_2_RLOPM_VOL 88 +#define PGAR_2_LLOPM_VOL 84 #define PGAR_2_RLOPM_VOL 91 #define DACL1_2_LLOPM_VOL 82 +#define DACL1_2_RLOPM_VOL 89 #define DACR1_2_RLOPM_VOL 92 +#define DACR1_2_LLOPM_VOL 85 #define LLOPM_CTRL 86 #define RLOPM_CTRL 93 /* GPIO/IRQ registers */ @@ -221,7 +237,49 @@ enum { void aic3x_set_gpio(struct snd_soc_codec *codec, int gpio, int state); int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio); + +/* headset detection / button API */ + +/* The AIC3x supports detection of stereo headsets (GND + left + right signal) + * and cellular headsets (GND + speaker output + microphone input). + * It is recommended to enable MIC bias for this function to work properly. + * For more information, please refer to the datasheet. */ +enum { + AIC3X_HEADSET_DETECT_OFF = 0, + AIC3X_HEADSET_DETECT_STEREO = 1, + AIC3X_HEADSET_DETECT_CELLULAR = 2, + AIC3X_HEADSET_DETECT_BOTH = 3 +}; + +enum { + AIC3X_HEADSET_DEBOUNCE_16MS = 0, + AIC3X_HEADSET_DEBOUNCE_32MS = 1, + AIC3X_HEADSET_DEBOUNCE_64MS = 2, + AIC3X_HEADSET_DEBOUNCE_128MS = 3, + AIC3X_HEADSET_DEBOUNCE_256MS = 4, + AIC3X_HEADSET_DEBOUNCE_512MS = 5 +}; + +enum { + AIC3X_BUTTON_DEBOUNCE_0MS = 0, + AIC3X_BUTTON_DEBOUNCE_8MS = 1, + AIC3X_BUTTON_DEBOUNCE_16MS = 2, + AIC3X_BUTTON_DEBOUNCE_32MS = 3 +}; + +#define AIC3X_HEADSET_DETECT_ENABLED 0x80 +#define AIC3X_HEADSET_DETECT_SHIFT 5 +#define AIC3X_HEADSET_DETECT_MASK 3 +#define AIC3X_HEADSET_DEBOUNCE_SHIFT 2 +#define AIC3X_HEADSET_DEBOUNCE_MASK 7 +#define AIC3X_BUTTON_DEBOUNCE_SHIFT 0 +#define AIC3X_BUTTON_DEBOUNCE_MASK 3 + +/* see the enums above for valid parameters to this function */ +void aic3x_set_headset_detection(struct snd_soc_codec *codec, int detect, + int headset_debounce, int button_debounce); int aic3x_headset_detected(struct snd_soc_codec *codec); +int aic3x_button_pressed(struct snd_soc_codec *codec); struct aic3x_setup_data { int i2c_bus; diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c new file mode 100644 index 00000000000..51848880504 --- /dev/null +++ b/sound/soc/codecs/twl4030.c @@ -0,0 +1,1317 @@ +/* + * ALSA SoC TWL4030 codec driver + * + * Author: Steve Sakoman, <steve@sakoman.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. + * + * 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, 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 <linux/i2c/twl4030.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 "twl4030.h" + +/* + * twl4030 register cache & default register settings + */ +static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { + 0x00, /* this register not used */ + 0x93, /* REG_CODEC_MODE (0x1) */ + 0xc3, /* REG_OPTION (0x2) */ + 0x00, /* REG_UNKNOWN (0x3) */ + 0x00, /* REG_MICBIAS_CTL (0x4) */ + 0x20, /* REG_ANAMICL (0x5) */ + 0x00, /* REG_ANAMICR (0x6) */ + 0x00, /* REG_AVADC_CTL (0x7) */ + 0x00, /* REG_ADCMICSEL (0x8) */ + 0x00, /* REG_DIGMIXING (0x9) */ + 0x0c, /* REG_ATXL1PGA (0xA) */ + 0x0c, /* REG_ATXR1PGA (0xB) */ + 0x00, /* REG_AVTXL2PGA (0xC) */ + 0x00, /* REG_AVTXR2PGA (0xD) */ + 0x01, /* REG_AUDIO_IF (0xE) */ + 0x00, /* REG_VOICE_IF (0xF) */ + 0x00, /* REG_ARXR1PGA (0x10) */ + 0x00, /* REG_ARXL1PGA (0x11) */ + 0x6c, /* REG_ARXR2PGA (0x12) */ + 0x6c, /* REG_ARXL2PGA (0x13) */ + 0x00, /* REG_VRXPGA (0x14) */ + 0x00, /* REG_VSTPGA (0x15) */ + 0x00, /* REG_VRX2ARXPGA (0x16) */ + 0x0c, /* REG_AVDAC_CTL (0x17) */ + 0x00, /* REG_ARX2VTXPGA (0x18) */ + 0x00, /* REG_ARXL1_APGA_CTL (0x19) */ + 0x00, /* REG_ARXR1_APGA_CTL (0x1A) */ + 0x4b, /* REG_ARXL2_APGA_CTL (0x1B) */ + 0x4b, /* REG_ARXR2_APGA_CTL (0x1C) */ + 0x00, /* REG_ATX2ARXPGA (0x1D) */ + 0x00, /* REG_BT_IF (0x1E) */ + 0x00, /* REG_BTPGA (0x1F) */ + 0x00, /* REG_BTSTPGA (0x20) */ + 0x00, /* REG_EAR_CTL (0x21) */ + 0x24, /* REG_HS_SEL (0x22) */ + 0x0a, /* REG_HS_GAIN_SET (0x23) */ + 0x00, /* REG_HS_POPN_SET (0x24) */ + 0x00, /* REG_PREDL_CTL (0x25) */ + 0x00, /* REG_PREDR_CTL (0x26) */ + 0x00, /* REG_PRECKL_CTL (0x27) */ + 0x00, /* REG_PRECKR_CTL (0x28) */ + 0x00, /* REG_HFL_CTL (0x29) */ + 0x00, /* REG_HFR_CTL (0x2A) */ + 0x00, /* REG_ALC_CTL (0x2B) */ + 0x00, /* REG_ALC_SET1 (0x2C) */ + 0x00, /* REG_ALC_SET2 (0x2D) */ + 0x00, /* REG_BOOST_CTL (0x2E) */ + 0x00, /* REG_SOFTVOL_CTL (0x2F) */ + 0x00, /* REG_DTMF_FREQSEL (0x30) */ + 0x00, /* REG_DTMF_TONEXT1H (0x31) */ + 0x00, /* REG_DTMF_TONEXT1L (0x32) */ + 0x00, /* REG_DTMF_TONEXT2H (0x33) */ + 0x00, /* REG_DTMF_TONEXT2L (0x34) */ + 0x00, /* REG_DTMF_TONOFF (0x35) */ + 0x00, /* REG_DTMF_WANONOFF (0x36) */ + 0x00, /* REG_I2S_RX_SCRAMBLE_H (0x37) */ + 0x00, /* REG_I2S_RX_SCRAMBLE_M (0x38) */ + 0x00, /* REG_I2S_RX_SCRAMBLE_L (0x39) */ + 0x16, /* REG_APLL_CTL (0x3A) */ + 0x00, /* REG_DTMF_CTL (0x3B) */ + 0x00, /* REG_DTMF_PGA_CTL2 (0x3C) */ + 0x00, /* REG_DTMF_PGA_CTL1 (0x3D) */ + 0x00, /* REG_MISC_SET_1 (0x3E) */ + 0x00, /* REG_PCMBTMUX (0x3F) */ + 0x00, /* not used (0x40) */ + 0x00, /* not used (0x41) */ + 0x00, /* not used (0x42) */ + 0x00, /* REG_RX_PATH_SEL (0x43) */ + 0x00, /* REG_VDL_APGA_CTL (0x44) */ + 0x00, /* REG_VIBRA_CTL (0x45) */ + 0x00, /* REG_VIBRA_SET (0x46) */ + 0x00, /* REG_VIBRA_PWM_SET (0x47) */ + 0x00, /* REG_ANAMIC_GAIN (0x48) */ + 0x00, /* REG_MISC_SET_2 (0x49) */ +}; + +/* + * read twl4030 register cache + */ +static inline unsigned int twl4030_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + + return cache[reg]; +} + +/* + * write twl4030 register cache + */ +static inline void twl4030_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, u8 value) +{ + u8 *cache = codec->reg_cache; + + if (reg >= TWL4030_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the twl4030 register space + */ +static int twl4030_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + twl4030_write_reg_cache(codec, reg, value); + return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); +} + +static void twl4030_clear_codecpdz(struct snd_soc_codec *codec) +{ + u8 mode; + + mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); + twl4030_write(codec, TWL4030_REG_CODEC_MODE, + mode & ~TWL4030_CODECPDZ); + + /* REVISIT: this delay is present in TI sample drivers */ + /* but there seems to be no TRM requirement for it */ + udelay(10); +} + +static void twl4030_set_codecpdz(struct snd_soc_codec *codec) +{ + u8 mode; + + mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); + twl4030_write(codec, TWL4030_REG_CODEC_MODE, + mode | TWL4030_CODECPDZ); + + /* REVISIT: this delay is present in TI sample drivers */ + /* but there seems to be no TRM requirement for it */ + udelay(10); +} + +static void twl4030_init_chip(struct snd_soc_codec *codec) +{ + int i; + + /* clear CODECPDZ prior to setting register defaults */ + twl4030_clear_codecpdz(codec); + + /* set all audio section registers to reasonable defaults */ + for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++) + twl4030_write(codec, i, twl4030_reg[i]); + +} + +/* Earpiece */ +static const char *twl4030_earpiece_texts[] = + {"Off", "DACL1", "DACL2", "Invalid", "DACR1"}; + +static const struct soc_enum twl4030_earpiece_enum = + SOC_ENUM_SINGLE(TWL4030_REG_EAR_CTL, 1, + ARRAY_SIZE(twl4030_earpiece_texts), + twl4030_earpiece_texts); + +static const struct snd_kcontrol_new twl4030_dapm_earpiece_control = +SOC_DAPM_ENUM("Route", twl4030_earpiece_enum); + +/* PreDrive Left */ +static const char *twl4030_predrivel_texts[] = + {"Off", "DACL1", "DACL2", "Invalid", "DACR2"}; + +static const struct soc_enum twl4030_predrivel_enum = + SOC_ENUM_SINGLE(TWL4030_REG_PREDL_CTL, 1, + ARRAY_SIZE(twl4030_predrivel_texts), + twl4030_predrivel_texts); + +static const struct snd_kcontrol_new twl4030_dapm_predrivel_control = +SOC_DAPM_ENUM("Route", twl4030_predrivel_enum); + +/* PreDrive Right */ +static const char *twl4030_predriver_texts[] = + {"Off", "DACR1", "DACR2", "Invalid", "DACL2"}; + +static const struct soc_enum twl4030_predriver_enum = + SOC_ENUM_SINGLE(TWL4030_REG_PREDR_CTL, 1, + ARRAY_SIZE(twl4030_predriver_texts), + twl4030_predriver_texts); + +static const struct snd_kcontrol_new twl4030_dapm_predriver_control = +SOC_DAPM_ENUM("Route", twl4030_predriver_enum); + +/* Headset Left */ +static const char *twl4030_hsol_texts[] = + {"Off", "DACL1", "DACL2"}; + +static const struct soc_enum twl4030_hsol_enum = + SOC_ENUM_SINGLE(TWL4030_REG_HS_SEL, 1, + ARRAY_SIZE(twl4030_hsol_texts), + twl4030_hsol_texts); + +static const struct snd_kcontrol_new twl4030_dapm_hsol_control = +SOC_DAPM_ENUM("Route", twl4030_hsol_enum); + +/* Headset Right */ +static const char *twl4030_hsor_texts[] = + {"Off", "DACR1", "DACR2"}; + +static const struct soc_enum twl4030_hsor_enum = + SOC_ENUM_SINGLE(TWL4030_REG_HS_SEL, 4, + ARRAY_SIZE(twl4030_hsor_texts), + twl4030_hsor_texts); + +static const struct snd_kcontrol_new twl4030_dapm_hsor_control = +SOC_DAPM_ENUM("Route", twl4030_hsor_enum); + +/* Carkit Left */ +static const char *twl4030_carkitl_texts[] = + {"Off", "DACL1", "DACL2"}; + +static const struct soc_enum twl4030_carkitl_enum = + SOC_ENUM_SINGLE(TWL4030_REG_PRECKL_CTL, 1, + ARRAY_SIZE(twl4030_carkitl_texts), + twl4030_carkitl_texts); + +static const struct snd_kcontrol_new twl4030_dapm_carkitl_control = +SOC_DAPM_ENUM("Route", twl4030_carkitl_enum); + +/* Carkit Right */ +static const char *twl4030_carkitr_texts[] = + {"Off", "DACR1", "DACR2"}; + +static const struct soc_enum twl4030_carkitr_enum = + SOC_ENUM_SINGLE(TWL4030_REG_PRECKR_CTL, 1, + ARRAY_SIZE(twl4030_carkitr_texts), + twl4030_carkitr_texts); + +static const struct snd_kcontrol_new twl4030_dapm_carkitr_control = +SOC_DAPM_ENUM("Route", twl4030_carkitr_enum); + +/* Handsfree Left */ +static const char *twl4030_handsfreel_texts[] = + {"Voice", "DACL1", "DACL2", "DACR2"}; + +static const struct soc_enum twl4030_handsfreel_enum = + SOC_ENUM_SINGLE(TWL4030_REG_HFL_CTL, 0, + ARRAY_SIZE(twl4030_handsfreel_texts), + twl4030_handsfreel_texts); + +static const struct snd_kcontrol_new twl4030_dapm_handsfreel_control = +SOC_DAPM_ENUM("Route", twl4030_handsfreel_enum); + +/* Handsfree Right */ +static const char *twl4030_handsfreer_texts[] = + {"Voice", "DACR1", "DACR2", "DACL2"}; + +static const struct soc_enum twl4030_handsfreer_enum = + SOC_ENUM_SINGLE(TWL4030_REG_HFR_CTL, 0, + ARRAY_SIZE(twl4030_handsfreer_texts), + twl4030_handsfreer_texts); + +static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control = +SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum); + +static int outmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int ret = 0; + int val; + + switch (e->reg) { + case TWL4030_REG_PREDL_CTL: + case TWL4030_REG_PREDR_CTL: + case TWL4030_REG_EAR_CTL: + val = w->value >> e->shift_l; + if (val == 3) { + printk(KERN_WARNING + "Invalid MUX setting for register 0x%02x (%d)\n", + e->reg, val); + ret = -1; + } + break; + } + + return ret; +} + +static int handsfree_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct soc_enum *e = (struct soc_enum *)w->kcontrols->private_value; + unsigned char hs_ctl; + + hs_ctl = twl4030_read_reg_cache(w->codec, e->reg); + + if (hs_ctl & TWL4030_HF_CTL_REF_EN) { + hs_ctl |= TWL4030_HF_CTL_RAMP_EN; + twl4030_write(w->codec, e->reg, hs_ctl); + hs_ctl |= TWL4030_HF_CTL_LOOP_EN; + twl4030_write(w->codec, e->reg, hs_ctl); + hs_ctl |= TWL4030_HF_CTL_HB_EN; + twl4030_write(w->codec, e->reg, hs_ctl); + } else { + hs_ctl &= ~(TWL4030_HF_CTL_RAMP_EN | TWL4030_HF_CTL_LOOP_EN + | TWL4030_HF_CTL_HB_EN); + twl4030_write(w->codec, e->reg, hs_ctl); + } + + return 0; +} + +/* + * Some of the gain controls in TWL (mostly those which are associated with + * the outputs) are implemented in an interesting way: + * 0x0 : Power down (mute) + * 0x1 : 6dB + * 0x2 : 0 dB + * 0x3 : -6 dB + * Inverting not going to help with these. + * Custom volsw and volsw_2r get/put functions to handle these gain bits. + */ +#define SOC_DOUBLE_TLV_TWL4030(xname, xreg, shift_left, shift_right, xmax,\ + xinvert, 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_twl4030, \ + .put = snd_soc_put_volsw_twl4030, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .shift = shift_left, .rshift = shift_right,\ + .max = xmax, .invert = xinvert} } +#define SOC_DOUBLE_R_TLV_TWL4030(xname, reg_left, reg_right, xshift, xmax,\ + xinvert, 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_r2_twl4030,\ + .put = snd_soc_put_volsw_r2_twl4030, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ + .rshift = xshift, .max = xmax, .invert = xinvert} } +#define SOC_SINGLE_TLV_TWL4030(xname, xreg, xshift, xmax, xinvert, tlv_array) \ + SOC_DOUBLE_TLV_TWL4030(xname, xreg, xshift, xshift, xmax, \ + xinvert, tlv_array) + +static int snd_soc_get_volsw_twl4030(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int mask = (1 << fls(max)) - 1; + + ucontrol->value.integer.value[0] = + (snd_soc_read(codec, reg) >> shift) & mask; + if (ucontrol->value.integer.value[0]) + ucontrol->value.integer.value[0] = + max + 1 - ucontrol->value.integer.value[0]; + + if (shift != rshift) { + ucontrol->value.integer.value[1] = + (snd_soc_read(codec, reg) >> rshift) & mask; + if (ucontrol->value.integer.value[1]) + ucontrol->value.integer.value[1] = + max + 1 - ucontrol->value.integer.value[1]; + } + + return 0; +} + +static int snd_soc_put_volsw_twl4030(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + int mask = (1 << fls(max)) - 1; + unsigned short val, val2, val_mask; + + val = (ucontrol->value.integer.value[0] & mask); + + val_mask = mask << shift; + if (val) + val = max + 1 - val; + val = val << shift; + if (shift != rshift) { + val2 = (ucontrol->value.integer.value[1] & mask); + val_mask |= mask << rshift; + if (val2) + val2 = max + 1 - val2; + val |= val2 << rshift; + } + return snd_soc_update_bits(codec, reg, val_mask, val); +} + +static int snd_soc_get_volsw_r2_twl4030(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + int mask = (1<<fls(max))-1; + + ucontrol->value.integer.value[0] = + (snd_soc_read(codec, reg) >> shift) & mask; + ucontrol->value.integer.value[1] = + (snd_soc_read(codec, reg2) >> shift) & mask; + + if (ucontrol->value.integer.value[0]) + ucontrol->value.integer.value[0] = + max + 1 - ucontrol->value.integer.value[0]; + if (ucontrol->value.integer.value[1]) + ucontrol->value.integer.value[1] = + max + 1 - ucontrol->value.integer.value[1]; + + return 0; +} + +static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + int mask = (1 << fls(max)) - 1; + int err; + unsigned short val, val2, val_mask; + + val_mask = mask << shift; + val = (ucontrol->value.integer.value[0] & mask); + val2 = (ucontrol->value.integer.value[1] & mask); + + if (val) + val = max + 1 - val; + if (val2) + val2 = max + 1 - val2; + + val = val << shift; + val2 = val2 << shift; + + err = snd_soc_update_bits(codec, reg, val_mask, val); + if (err < 0) + return err; + + err = snd_soc_update_bits(codec, reg2, val_mask, val2); + return err; +} + +static int twl4030_get_left_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); + int result = 0; + + /* one bit must be set a time */ + reg &= TWL4030_CKMIC_EN | TWL4030_AUXL_EN | TWL4030_HSMIC_EN + | TWL4030_MAINMIC_EN; + if (reg != 0) { + result++; + while ((reg & 1) == 0) { + result++; + reg >>= 1; + } + } + + ucontrol->value.integer.value[0] = result; + return 0; +} + +static int twl4030_put_left_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + u8 anamicl, micbias, avadc_ctl; + + anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); + anamicl &= ~(TWL4030_CKMIC_EN | TWL4030_AUXL_EN | TWL4030_HSMIC_EN + | TWL4030_MAINMIC_EN); + micbias = twl4030_read_reg_cache(codec, TWL4030_REG_MICBIAS_CTL); + micbias &= ~(TWL4030_HSMICBIAS_EN | TWL4030_MICBIAS1_EN); + avadc_ctl = twl4030_read_reg_cache(codec, TWL4030_REG_AVADC_CTL); + + switch (value) { + case 1: + anamicl |= TWL4030_MAINMIC_EN; + micbias |= TWL4030_MICBIAS1_EN; + break; + case 2: + anamicl |= TWL4030_HSMIC_EN; + micbias |= TWL4030_HSMICBIAS_EN; + break; + case 3: + anamicl |= TWL4030_AUXL_EN; + break; + case 4: + anamicl |= TWL4030_CKMIC_EN; + break; + default: + break; + } + + /* If some input is selected, enable amp and ADC */ + if (value != 0) { + anamicl |= TWL4030_MICAMPL_EN; + avadc_ctl |= TWL4030_ADCL_EN; + } else { + anamicl &= ~TWL4030_MICAMPL_EN; + avadc_ctl &= ~TWL4030_ADCL_EN; + } + + twl4030_write(codec, TWL4030_REG_ANAMICL, anamicl); + twl4030_write(codec, TWL4030_REG_MICBIAS_CTL, micbias); + twl4030_write(codec, TWL4030_REG_AVADC_CTL, avadc_ctl); + + return 1; +} + +static int twl4030_get_right_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICR); + int value = 0; + + reg &= TWL4030_SUBMIC_EN|TWL4030_AUXR_EN; + switch (reg) { + case TWL4030_SUBMIC_EN: + value = 1; + break; + case TWL4030_AUXR_EN: + value = 2; + break; + default: + break; + } + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int twl4030_put_right_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + u8 anamicr, micbias, avadc_ctl; + + anamicr = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICR); + anamicr &= ~(TWL4030_SUBMIC_EN|TWL4030_AUXR_EN); + micbias = twl4030_read_reg_cache(codec, TWL4030_REG_MICBIAS_CTL); + micbias &= ~TWL4030_MICBIAS2_EN; + avadc_ctl = twl4030_read_reg_cache(codec, TWL4030_REG_AVADC_CTL); + + switch (value) { + case 1: + anamicr |= TWL4030_SUBMIC_EN; + micbias |= TWL4030_MICBIAS2_EN; + break; + case 2: + anamicr |= TWL4030_AUXR_EN; + break; + default: + break; + } + + if (value != 0) { + anamicr |= TWL4030_MICAMPR_EN; + avadc_ctl |= TWL4030_ADCR_EN; + } else { + anamicr &= ~TWL4030_MICAMPR_EN; + avadc_ctl &= ~TWL4030_ADCR_EN; + } + + twl4030_write(codec, TWL4030_REG_ANAMICR, anamicr); + twl4030_write(codec, TWL4030_REG_MICBIAS_CTL, micbias); + twl4030_write(codec, TWL4030_REG_AVADC_CTL, avadc_ctl); + + return 1; +} + +static const char *twl4030_left_in_sel[] = { + "None", + "Main Mic", + "Headset Mic", + "Line In", + "Carkit Mic", +}; + +static const char *twl4030_right_in_sel[] = { + "None", + "Sub Mic", + "Line In", +}; + +static const struct soc_enum twl4030_left_input_mux = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(twl4030_left_in_sel), + twl4030_left_in_sel); + +static const struct soc_enum twl4030_right_input_mux = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(twl4030_right_in_sel), + twl4030_right_in_sel); + +/* + * FGAIN volume control: + * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) + */ +static DECLARE_TLV_DB_SCALE(digital_fine_tlv, -6300, 100, 1); + +/* + * CGAIN volume control: + * 0 dB to 12 dB in 6 dB steps + * value 2 and 3 means 12 dB + */ +static DECLARE_TLV_DB_SCALE(digital_coarse_tlv, 0, 600, 0); + +/* + * Analog playback gain + * -24 dB to 12 dB in 2 dB steps + */ +static DECLARE_TLV_DB_SCALE(analog_tlv, -2400, 200, 0); + +/* + * Gain controls tied to outputs + * -6 dB to 6 dB in 6 dB steps (mute instead of -12) + */ +static DECLARE_TLV_DB_SCALE(output_tvl, -1200, 600, 1); + +/* + * Capture gain after the ADCs + * from 0 dB to 31 dB in 1 dB steps + */ +static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0); + +/* + * Gain control for input amplifiers + * 0 dB to 30 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0); + +static const struct snd_kcontrol_new twl4030_snd_controls[] = { + /* Common playback gain controls */ + SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume", + TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA, + 0, 0x3f, 0, digital_fine_tlv), + SOC_DOUBLE_R_TLV("DAC2 Digital Fine Playback Volume", + TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA, + 0, 0x3f, 0, digital_fine_tlv), + + SOC_DOUBLE_R_TLV("DAC1 Digital Coarse Playback Volume", + TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA, + 6, 0x2, 0, digital_coarse_tlv), + SOC_DOUBLE_R_TLV("DAC2 Digital Coarse Playback Volume", + TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA, + 6, 0x2, 0, digital_coarse_tlv), + + SOC_DOUBLE_R_TLV("DAC1 Analog Playback Volume", + TWL4030_REG_ARXL1_APGA_CTL, TWL4030_REG_ARXR1_APGA_CTL, + 3, 0x12, 1, analog_tlv), + SOC_DOUBLE_R_TLV("DAC2 Analog Playback Volume", + TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL, + 3, 0x12, 1, analog_tlv), + SOC_DOUBLE_R("DAC1 Analog Playback Switch", + TWL4030_REG_ARXL1_APGA_CTL, TWL4030_REG_ARXR1_APGA_CTL, + 1, 1, 0), + SOC_DOUBLE_R("DAC2 Analog Playback Switch", + TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL, + 1, 1, 0), + + /* Separate output gain controls */ + SOC_DOUBLE_R_TLV_TWL4030("PreDriv Playback Volume", + TWL4030_REG_PREDL_CTL, TWL4030_REG_PREDR_CTL, + 4, 3, 0, output_tvl), + + SOC_DOUBLE_TLV_TWL4030("Headset Playback Volume", + TWL4030_REG_HS_GAIN_SET, 0, 2, 3, 0, output_tvl), + + SOC_DOUBLE_R_TLV_TWL4030("Carkit Playback Volume", + TWL4030_REG_PRECKL_CTL, TWL4030_REG_PRECKR_CTL, + 4, 3, 0, output_tvl), + + SOC_SINGLE_TLV_TWL4030("Earpiece Playback Volume", + TWL4030_REG_EAR_CTL, 4, 3, 0, output_tvl), + + /* Common capture gain controls */ + SOC_DOUBLE_R_TLV("Capture Volume", + TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA, + 0, 0x1f, 0, digital_capture_tlv), + + SOC_DOUBLE_TLV("Input Boost Volume", TWL4030_REG_ANAMIC_GAIN, + 0, 3, 5, 0, input_gain_tlv), + + /* Input source controls */ + SOC_ENUM_EXT("Left Input Source", twl4030_left_input_mux, + twl4030_get_left_input, twl4030_put_left_input), + SOC_ENUM_EXT("Right Input Source", twl4030_right_input_mux, + twl4030_get_right_input, twl4030_put_right_input), +}; + +/* add non dapm controls */ +static int twl4030_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(twl4030_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&twl4030_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("INL"), + SND_SOC_DAPM_INPUT("INR"), + + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), + SND_SOC_DAPM_OUTPUT("EARPIECE"), + SND_SOC_DAPM_OUTPUT("PREDRIVEL"), + SND_SOC_DAPM_OUTPUT("PREDRIVER"), + SND_SOC_DAPM_OUTPUT("HSOL"), + SND_SOC_DAPM_OUTPUT("HSOR"), + SND_SOC_DAPM_OUTPUT("CARKITL"), + SND_SOC_DAPM_OUTPUT("CARKITR"), + SND_SOC_DAPM_OUTPUT("HFL"), + SND_SOC_DAPM_OUTPUT("HFR"), + + /* DACs */ + SND_SOC_DAPM_DAC("DAC Right1", "Right Front Playback", + TWL4030_REG_AVDAC_CTL, 0, 0), + SND_SOC_DAPM_DAC("DAC Left1", "Left Front Playback", + TWL4030_REG_AVDAC_CTL, 1, 0), + SND_SOC_DAPM_DAC("DAC Right2", "Right Rear Playback", + TWL4030_REG_AVDAC_CTL, 2, 0), + SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback", + TWL4030_REG_AVDAC_CTL, 3, 0), + + /* Analog PGAs */ + SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ARXL1_APGA", TWL4030_REG_ARXL1_APGA_CTL, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ARXR2_APGA", TWL4030_REG_ARXR2_APGA_CTL, + 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("ARXL2_APGA", TWL4030_REG_ARXL2_APGA_CTL, + 0, 0, NULL, 0), + + /* Output MUX controls */ + /* Earpiece */ + SND_SOC_DAPM_MUX_E("Earpiece Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_earpiece_control, outmixer_event, + SND_SOC_DAPM_PRE_REG), + /* PreDrivL/R */ + SND_SOC_DAPM_MUX_E("PredriveL Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_predrivel_control, outmixer_event, + SND_SOC_DAPM_PRE_REG), + SND_SOC_DAPM_MUX_E("PredriveR Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_predriver_control, outmixer_event, + SND_SOC_DAPM_PRE_REG), + /* HeadsetL/R */ + SND_SOC_DAPM_MUX("HeadsetL Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsol_control), + SND_SOC_DAPM_MUX("HeadsetR Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsor_control), + /* CarkitL/R */ + SND_SOC_DAPM_MUX("CarkitL Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_carkitl_control), + SND_SOC_DAPM_MUX("CarkitR Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_carkitr_control), + /* HandsfreeL/R */ + SND_SOC_DAPM_MUX_E("HandsfreeL Mux", TWL4030_REG_HFL_CTL, 5, 0, + &twl4030_dapm_handsfreel_control, handsfree_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX_E("HandsfreeR Mux", TWL4030_REG_HFR_CTL, 5, 0, + &twl4030_dapm_handsfreer_control, handsfree_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_ADC("ADCL", "Left Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADCR", "Right Capture", SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"ARXL1_APGA", NULL, "DAC Left1"}, + {"ARXR1_APGA", NULL, "DAC Right1"}, + {"ARXL2_APGA", NULL, "DAC Left2"}, + {"ARXR2_APGA", NULL, "DAC Right2"}, + + /* Internal playback routings */ + /* Earpiece */ + {"Earpiece Mux", "DACL1", "ARXL1_APGA"}, + {"Earpiece Mux", "DACL2", "ARXL2_APGA"}, + {"Earpiece Mux", "DACR1", "ARXR1_APGA"}, + /* PreDrivL */ + {"PredriveL Mux", "DACL1", "ARXL1_APGA"}, + {"PredriveL Mux", "DACL2", "ARXL2_APGA"}, + {"PredriveL Mux", "DACR2", "ARXR2_APGA"}, + /* PreDrivR */ + {"PredriveR Mux", "DACR1", "ARXR1_APGA"}, + {"PredriveR Mux", "DACR2", "ARXR2_APGA"}, + {"PredriveR Mux", "DACL2", "ARXL2_APGA"}, + /* HeadsetL */ + {"HeadsetL Mux", "DACL1", "ARXL1_APGA"}, + {"HeadsetL Mux", "DACL2", "ARXL2_APGA"}, + /* HeadsetR */ + {"HeadsetR Mux", "DACR1", "ARXR1_APGA"}, + {"HeadsetR Mux", "DACR2", "ARXR2_APGA"}, + /* CarkitL */ + {"CarkitL Mux", "DACL1", "ARXL1_APGA"}, + {"CarkitL Mux", "DACL2", "ARXL2_APGA"}, + /* CarkitR */ + {"CarkitR Mux", "DACR1", "ARXR1_APGA"}, + {"CarkitR Mux", "DACR2", "ARXR2_APGA"}, + /* HandsfreeL */ + {"HandsfreeL Mux", "DACL1", "ARXL1_APGA"}, + {"HandsfreeL Mux", "DACL2", "ARXL2_APGA"}, + {"HandsfreeL Mux", "DACR2", "ARXR2_APGA"}, + /* HandsfreeR */ + {"HandsfreeR Mux", "DACR1", "ARXR1_APGA"}, + {"HandsfreeR Mux", "DACR2", "ARXR2_APGA"}, + {"HandsfreeR Mux", "DACL2", "ARXL2_APGA"}, + + /* outputs */ + {"OUTL", NULL, "ARXL2_APGA"}, + {"OUTR", NULL, "ARXR2_APGA"}, + {"EARPIECE", NULL, "Earpiece Mux"}, + {"PREDRIVEL", NULL, "PredriveL Mux"}, + {"PREDRIVER", NULL, "PredriveR Mux"}, + {"HSOL", NULL, "HeadsetL Mux"}, + {"HSOR", NULL, "HeadsetR Mux"}, + {"CARKITL", NULL, "CarkitL Mux"}, + {"CARKITR", NULL, "CarkitR Mux"}, + {"HFL", NULL, "HandsfreeL Mux"}, + {"HFR", NULL, "HandsfreeR Mux"}, + + /* inputs */ + {"ADCL", NULL, "INL"}, + {"ADCR", NULL, "INR"}, +}; + +static int twl4030_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, twl4030_dapm_widgets, + ARRAY_SIZE(twl4030_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static void twl4030_power_up(struct snd_soc_codec *codec) +{ + u8 anamicl, regmisc1, byte, popn; + int i = 0; + + /* set CODECPDZ to turn on codec */ + twl4030_set_codecpdz(codec); + + /* initiate offset cancellation */ + anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); + twl4030_write(codec, TWL4030_REG_ANAMICL, + anamicl | TWL4030_CNCL_OFFSET_START); + + /* wait for offset cancellation to complete */ + do { + /* this takes a little while, so don't slam i2c */ + udelay(2000); + twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte, + TWL4030_REG_ANAMICL); + } while ((i++ < 100) && + ((byte & TWL4030_CNCL_OFFSET_START) == + TWL4030_CNCL_OFFSET_START)); + + /* anti-pop when changing analog gain */ + regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1); + twl4030_write(codec, TWL4030_REG_MISC_SET_1, + regmisc1 | TWL4030_SMOOTH_ANAVOL_EN); + + /* toggle CODECPDZ as per TRM */ + twl4030_clear_codecpdz(codec); + twl4030_set_codecpdz(codec); + + /* program anti-pop with bias ramp delay */ + popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); + popn &= TWL4030_RAMP_DELAY; + popn |= TWL4030_RAMP_DELAY_645MS; + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); + popn |= TWL4030_VMID_EN; + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); + + /* enable anti-pop ramp */ + popn |= TWL4030_RAMP_EN; + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); +} + +static void twl4030_power_down(struct snd_soc_codec *codec) +{ + u8 popn; + + /* disable anti-pop ramp */ + popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); + popn &= ~TWL4030_RAMP_EN; + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); + + /* disable bias out */ + popn &= ~TWL4030_VMID_EN; + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn); + + /* power down */ + twl4030_clear_codecpdz(codec); +} + +static int twl4030_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + twl4030_power_up(codec); + break; + case SND_SOC_BIAS_PREPARE: + /* TODO: develop a twl4030_prepare function */ + break; + case SND_SOC_BIAS_STANDBY: + /* TODO: develop a twl4030_standby function */ + twl4030_power_down(codec); + break; + case SND_SOC_BIAS_OFF: + twl4030_power_down(codec); + break; + } + codec->bias_level = level; + + return 0; +} + +static int twl4030_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u8 mode, old_mode, format, old_format; + + + /* bit rate */ + old_mode = twl4030_read_reg_cache(codec, + TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ; + mode = old_mode & ~TWL4030_APLL_RATE; + + switch (params_rate(params)) { + case 8000: + mode |= TWL4030_APLL_RATE_8000; + break; + case 11025: + mode |= TWL4030_APLL_RATE_11025; + break; + case 12000: + mode |= TWL4030_APLL_RATE_12000; + break; + case 16000: + mode |= TWL4030_APLL_RATE_16000; + break; + case 22050: + mode |= TWL4030_APLL_RATE_22050; + break; + case 24000: + mode |= TWL4030_APLL_RATE_24000; + break; + case 32000: + mode |= TWL4030_APLL_RATE_32000; + break; + case 44100: + mode |= TWL4030_APLL_RATE_44100; + break; + case 48000: + mode |= TWL4030_APLL_RATE_48000; + break; + default: + printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n", + params_rate(params)); + return -EINVAL; + } + + if (mode != old_mode) { + /* change rate and set CODECPDZ */ + twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); + twl4030_set_codecpdz(codec); + } + + /* sample size */ + old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); + format = old_format; + format &= ~TWL4030_DATA_WIDTH; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + format |= TWL4030_DATA_WIDTH_16S_16W; + break; + case SNDRV_PCM_FORMAT_S24_LE: + format |= TWL4030_DATA_WIDTH_32S_24W; + break; + default: + printk(KERN_ERR "TWL4030 hw params: unknown format %d\n", + params_format(params)); + return -EINVAL; + } + + if (format != old_format) { + + /* clear CODECPDZ before changing format (codec requirement) */ + twl4030_clear_codecpdz(codec); + + /* change format */ + twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); + + /* set CODECPDZ afterwards */ + twl4030_set_codecpdz(codec); + } + return 0; +} + +static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 infreq; + + switch (freq) { + case 19200000: + infreq = TWL4030_APLL_INFREQ_19200KHZ; + break; + case 26000000: + infreq = TWL4030_APLL_INFREQ_26000KHZ; + break; + case 38400000: + infreq = TWL4030_APLL_INFREQ_38400KHZ; + break; + default: + printk(KERN_ERR "TWL4030 set sysclk: unknown rate %d\n", + freq); + return -EINVAL; + } + + infreq |= TWL4030_APLL_EN; + twl4030_write(codec, TWL4030_REG_APLL_CTL, infreq); + + return 0; +} + +static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 old_format, format; + + /* get format */ + old_format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); + format = old_format; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + format &= ~(TWL4030_AIF_SLAVE_EN); + format &= ~(TWL4030_CLK256FS_EN); + break; + case SND_SOC_DAIFMT_CBS_CFS: + format |= TWL4030_AIF_SLAVE_EN; + format |= TWL4030_CLK256FS_EN; + break; + default: + return -EINVAL; + } + + /* interface format */ + format &= ~TWL4030_AIF_FORMAT; + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format |= TWL4030_AIF_FORMAT_CODEC; + break; + default: + return -EINVAL; + } + + if (format != old_format) { + + /* clear CODECPDZ before changing format (codec requirement) */ + twl4030_clear_codecpdz(codec); + + /* change format */ + twl4030_write(codec, TWL4030_REG_AUDIO_IF, format); + + /* set CODECPDZ afterwards */ + twl4030_set_codecpdz(codec); + } + + return 0; +} + +#define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) +#define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_dai twl4030_dai = { + .name = "twl4030", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = TWL4030_RATES, + .formats = TWL4030_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = TWL4030_RATES, + .formats = TWL4030_FORMATS,}, + .ops = { + .hw_params = twl4030_hw_params, + .set_sysclk = twl4030_set_dai_sysclk, + .set_fmt = twl4030_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(twl4030_dai); + +static int twl4030_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; + + twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int twl4030_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + twl4030_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialize the driver + * register the mixer and dsp interfaces with the kernel + */ + +static int twl4030_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + printk(KERN_INFO "TWL4030 Audio Codec init \n"); + + codec->name = "twl4030"; + codec->owner = THIS_MODULE; + codec->read = twl4030_read_reg_cache; + codec->write = twl4030_write; + codec->set_bias_level = twl4030_set_bias_level; + codec->dai = &twl4030_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(twl4030_reg); + codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_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 "twl4030: failed to create pcms\n"); + goto pcm_err; + } + + twl4030_init_chip(codec); + + /* power on device */ + twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + twl4030_add_controls(codec); + twl4030_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "twl4030: 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 *twl4030_socdev; + +static int twl4030_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + + 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); + + twl4030_socdev = socdev; + twl4030_init(socdev); + + return 0; +} + +static int twl4030_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + printk(KERN_INFO "TWL4030 Audio Codec remove\n"); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_twl4030 = { + .probe = twl4030_probe, + .remove = twl4030_remove, + .suspend = twl4030_suspend, + .resume = twl4030_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_twl4030); + +static int __init twl4030_modinit(void) +{ + return snd_soc_register_dai(&twl4030_dai); +} +module_init(twl4030_modinit); + +static void __exit twl4030_exit(void) +{ + snd_soc_unregister_dai(&twl4030_dai); +} +module_exit(twl4030_exit); + +MODULE_DESCRIPTION("ASoC TWL4030 codec driver"); +MODULE_AUTHOR("Steve Sakoman"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h new file mode 100644 index 00000000000..54615c76802 --- /dev/null +++ b/sound/soc/codecs/twl4030.h @@ -0,0 +1,219 @@ +/* + * ALSA SoC TWL4030 codec driver + * + * Author: Steve Sakoman <steve@sakoman.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. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __TWL4030_AUDIO_H__ +#define __TWL4030_AUDIO_H__ + +#define TWL4030_REG_CODEC_MODE 0x1 +#define TWL4030_REG_OPTION 0x2 +#define TWL4030_REG_UNKNOWN 0x3 +#define TWL4030_REG_MICBIAS_CTL 0x4 +#define TWL4030_REG_ANAMICL 0x5 +#define TWL4030_REG_ANAMICR 0x6 +#define TWL4030_REG_AVADC_CTL 0x7 +#define TWL4030_REG_ADCMICSEL 0x8 +#define TWL4030_REG_DIGMIXING 0x9 +#define TWL4030_REG_ATXL1PGA 0xA +#define TWL4030_REG_ATXR1PGA 0xB +#define TWL4030_REG_AVTXL2PGA 0xC +#define TWL4030_REG_AVTXR2PGA 0xD +#define TWL4030_REG_AUDIO_IF 0xE +#define TWL4030_REG_VOICE_IF 0xF +#define TWL4030_REG_ARXR1PGA 0x10 +#define TWL4030_REG_ARXL1PGA 0x11 +#define TWL4030_REG_ARXR2PGA 0x12 +#define TWL4030_REG_ARXL2PGA 0x13 +#define TWL4030_REG_VRXPGA 0x14 +#define TWL4030_REG_VSTPGA 0x15 +#define TWL4030_REG_VRX2ARXPGA 0x16 +#define TWL4030_REG_AVDAC_CTL 0x17 +#define TWL4030_REG_ARX2VTXPGA 0x18 +#define TWL4030_REG_ARXL1_APGA_CTL 0x19 +#define TWL4030_REG_ARXR1_APGA_CTL 0x1A +#define TWL4030_REG_ARXL2_APGA_CTL 0x1B +#define TWL4030_REG_ARXR2_APGA_CTL 0x1C +#define TWL4030_REG_ATX2ARXPGA 0x1D +#define TWL4030_REG_BT_IF 0x1E +#define TWL4030_REG_BTPGA 0x1F +#define TWL4030_REG_BTSTPGA 0x20 +#define TWL4030_REG_EAR_CTL 0x21 +#define TWL4030_REG_HS_SEL 0x22 +#define TWL4030_REG_HS_GAIN_SET 0x23 +#define TWL4030_REG_HS_POPN_SET 0x24 +#define TWL4030_REG_PREDL_CTL 0x25 +#define TWL4030_REG_PREDR_CTL 0x26 +#define TWL4030_REG_PRECKL_CTL 0x27 +#define TWL4030_REG_PRECKR_CTL 0x28 +#define TWL4030_REG_HFL_CTL 0x29 +#define TWL4030_REG_HFR_CTL 0x2A +#define TWL4030_REG_ALC_CTL 0x2B +#define TWL4030_REG_ALC_SET1 0x2C +#define TWL4030_REG_ALC_SET2 0x2D +#define TWL4030_REG_BOOST_CTL 0x2E +#define TWL4030_REG_SOFTVOL_CTL 0x2F +#define TWL4030_REG_DTMF_FREQSEL 0x30 +#define TWL4030_REG_DTMF_TONEXT1H 0x31 +#define TWL4030_REG_DTMF_TONEXT1L 0x32 +#define TWL4030_REG_DTMF_TONEXT2H 0x33 +#define TWL4030_REG_DTMF_TONEXT2L 0x34 +#define TWL4030_REG_DTMF_TONOFF 0x35 +#define TWL4030_REG_DTMF_WANONOFF 0x36 +#define TWL4030_REG_I2S_RX_SCRAMBLE_H 0x37 +#define TWL4030_REG_I2S_RX_SCRAMBLE_M 0x38 +#define TWL4030_REG_I2S_RX_SCRAMBLE_L 0x39 +#define TWL4030_REG_APLL_CTL 0x3A +#define TWL4030_REG_DTMF_CTL 0x3B +#define TWL4030_REG_DTMF_PGA_CTL2 0x3C +#define TWL4030_REG_DTMF_PGA_CTL1 0x3D +#define TWL4030_REG_MISC_SET_1 0x3E +#define TWL4030_REG_PCMBTMUX 0x3F +#define TWL4030_REG_RX_PATH_SEL 0x43 +#define TWL4030_REG_VDL_APGA_CTL 0x44 +#define TWL4030_REG_VIBRA_CTL 0x45 +#define TWL4030_REG_VIBRA_SET 0x46 +#define TWL4030_REG_VIBRA_PWM_SET 0x47 +#define TWL4030_REG_ANAMIC_GAIN 0x48 +#define TWL4030_REG_MISC_SET_2 0x49 + +#define TWL4030_CACHEREGNUM (TWL4030_REG_MISC_SET_2 + 1) + +/* Bitfield Definitions */ + +/* TWL4030_CODEC_MODE (0x01) Fields */ + +#define TWL4030_APLL_RATE 0xF0 +#define TWL4030_APLL_RATE_8000 0x00 +#define TWL4030_APLL_RATE_11025 0x10 +#define TWL4030_APLL_RATE_12000 0x20 +#define TWL4030_APLL_RATE_16000 0x40 +#define TWL4030_APLL_RATE_22050 0x50 +#define TWL4030_APLL_RATE_24000 0x60 +#define TWL4030_APLL_RATE_32000 0x80 +#define TWL4030_APLL_RATE_44100 0x90 +#define TWL4030_APLL_RATE_48000 0xA0 +#define TWL4030_SEL_16K 0x04 +#define TWL4030_CODECPDZ 0x02 +#define TWL4030_OPT_MODE 0x01 + +/* TWL4030_REG_MICBIAS_CTL (0x04) Fields */ + +#define TWL4030_MICBIAS2_CTL 0x40 +#define TWL4030_MICBIAS1_CTL 0x20 +#define TWL4030_HSMICBIAS_EN 0x04 +#define TWL4030_MICBIAS2_EN 0x02 +#define TWL4030_MICBIAS1_EN 0x01 + +/* ANAMICL (0x05) Fields */ + +#define TWL4030_CNCL_OFFSET_START 0x80 +#define TWL4030_OFFSET_CNCL_SEL 0x60 +#define TWL4030_OFFSET_CNCL_SEL_ARX1 0x00 +#define TWL4030_OFFSET_CNCL_SEL_ARX2 0x20 +#define TWL4030_OFFSET_CNCL_SEL_VRX 0x40 +#define TWL4030_OFFSET_CNCL_SEL_ALL 0x60 +#define TWL4030_MICAMPL_EN 0x10 +#define TWL4030_CKMIC_EN 0x08 +#define TWL4030_AUXL_EN 0x04 +#define TWL4030_HSMIC_EN 0x02 +#define TWL4030_MAINMIC_EN 0x01 + +/* ANAMICR (0x06) Fields */ + +#define TWL4030_MICAMPR_EN 0x10 +#define TWL4030_AUXR_EN 0x04 +#define TWL4030_SUBMIC_EN 0x01 + +/* AVADC_CTL (0x07) Fields */ + +#define TWL4030_ADCL_EN 0x08 +#define TWL4030_AVADC_CLK_PRIORITY 0x04 +#define TWL4030_ADCR_EN 0x02 + +/* AUDIO_IF (0x0E) Fields */ + +#define TWL4030_AIF_SLAVE_EN 0x80 +#define TWL4030_DATA_WIDTH 0x60 +#define TWL4030_DATA_WIDTH_16S_16W 0x00 +#define TWL4030_DATA_WIDTH_32S_16W 0x40 +#define TWL4030_DATA_WIDTH_32S_24W 0x60 +#define TWL4030_AIF_FORMAT 0x18 +#define TWL4030_AIF_FORMAT_CODEC 0x00 +#define TWL4030_AIF_FORMAT_LEFT 0x08 +#define TWL4030_AIF_FORMAT_RIGHT 0x10 +#define TWL4030_AIF_FORMAT_TDM 0x18 +#define TWL4030_AIF_TRI_EN 0x04 +#define TWL4030_CLK256FS_EN 0x02 +#define TWL4030_AIF_EN 0x01 + +/* HS_GAIN_SET (0x23) Fields */ + +#define TWL4030_HSR_GAIN 0x0C +#define TWL4030_HSR_GAIN_PWR_DOWN 0x00 +#define TWL4030_HSR_GAIN_PLUS_6DB 0x04 +#define TWL4030_HSR_GAIN_0DB 0x08 +#define TWL4030_HSR_GAIN_MINUS_6DB 0x0C +#define TWL4030_HSL_GAIN 0x03 +#define TWL4030_HSL_GAIN_PWR_DOWN 0x00 +#define TWL4030_HSL_GAIN_PLUS_6DB 0x01 +#define TWL4030_HSL_GAIN_0DB 0x02 +#define TWL4030_HSL_GAIN_MINUS_6DB 0x03 + +/* HS_POPN_SET (0x24) Fields */ + +#define TWL4030_VMID_EN 0x40 +#define TWL4030_EXTMUTE 0x20 +#define TWL4030_RAMP_DELAY 0x1C +#define TWL4030_RAMP_DELAY_20MS 0x00 +#define TWL4030_RAMP_DELAY_40MS 0x04 +#define TWL4030_RAMP_DELAY_81MS 0x08 +#define TWL4030_RAMP_DELAY_161MS 0x0C +#define TWL4030_RAMP_DELAY_323MS 0x10 +#define TWL4030_RAMP_DELAY_645MS 0x14 +#define TWL4030_RAMP_DELAY_1291MS 0x18 +#define TWL4030_RAMP_DELAY_2581MS 0x1C +#define TWL4030_RAMP_EN 0x02 + +/* HFL_CTL (0x29, 0x2A) Fields */ +#define TWL4030_HF_CTL_HB_EN 0x04 +#define TWL4030_HF_CTL_LOOP_EN 0x08 +#define TWL4030_HF_CTL_RAMP_EN 0x10 +#define TWL4030_HF_CTL_REF_EN 0x20 + +/* APLL_CTL (0x3A) Fields */ + +#define TWL4030_APLL_EN 0x10 +#define TWL4030_APLL_INFREQ 0x0F +#define TWL4030_APLL_INFREQ_19200KHZ 0x05 +#define TWL4030_APLL_INFREQ_26000KHZ 0x06 +#define TWL4030_APLL_INFREQ_38400KHZ 0x0F + +/* REG_MISC_SET_1 (0x3E) Fields */ + +#define TWL4030_CLK64_EN 0x80 +#define TWL4030_SCRAMBLE_EN 0x40 +#define TWL4030_FMLOOP_EN 0x20 +#define TWL4030_SMOOTH_ANAVOL_EN 0x02 +#define TWL4030_DIGMIC_LR_SWAP_EN 0x01 + +extern struct snd_soc_dai twl4030_dai; +extern struct snd_soc_codec_device soc_codec_dev_twl4030; + +#endif /* End of __TWL4030_AUDIO_H__ */ diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c new file mode 100644 index 00000000000..a2c5064a774 --- /dev/null +++ b/sound/soc/codecs/uda134x.c @@ -0,0 +1,668 @@ +/* + * uda134x.c -- UDA134X ALSA SoC Codec driver + * + * Modifications by Christian Pellegrin <chripell@evolware.org> + * + * Copyright 2007 Dension Audio Systems Ltd. + * Author: Zoltan Devai + * + * Based on the WM87xx drivers by Liam Girdwood and 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. + */ + +#include <linux/module.h> +#include <linux/delay.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/uda134x.h> +#include <sound/l3.h> + +#include "uda134x.h" + + +#define POWER_OFF_ON_STANDBY 1 +/* + ALSA SOC usually puts the device in standby mode when it's not used + for sometime. If you define POWER_OFF_ON_STANDBY the driver will + turn off the ADC/DAC when this callback is invoked and turn it back + on when needed. Unfortunately this will result in a very light bump + (it can be audible only with good earphones). If this bothers you + just comment this line, you will have slightly higher power + consumption . Please note that sending the L3 command for ADC is + enough to make the bump, so it doesn't make difference if you + completely take off power from the codec. + */ + +#define UDA134X_RATES SNDRV_PCM_RATE_8000_48000 +#define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE) + +struct uda134x_priv { + int sysclk; + int dai_fmt; + + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +/* In-data addresses are hard-coded into the reg-cache values */ +static const char uda134x_reg[UDA134X_REGS_NUM] = { + /* Extended address registers */ + 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Status, data regs */ + 0x00, 0x83, 0x00, 0x40, 0x80, 0x00, +}; + +/* + * The codec has no support for reading its registers except for peak level... + */ +static inline unsigned int uda134x_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + + if (reg >= UDA134X_REGS_NUM) + return -1; + return cache[reg]; +} + +/* + * Write the register cache + */ +static inline void uda134x_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, unsigned int value) +{ + u8 *cache = codec->reg_cache; + + if (reg >= UDA134X_REGS_NUM) + return; + cache[reg] = value; +} + +/* + * Write to the uda134x registers + * + */ +static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + int ret; + u8 addr; + u8 data = value; + struct uda134x_platform_data *pd = codec->control_data; + + pr_debug("%s reg: %02X, value:%02X\n", __func__, reg, value); + + if (reg >= UDA134X_REGS_NUM) { + printk(KERN_ERR "%s unkown register: reg: %d", + __func__, reg); + return -EINVAL; + } + + uda134x_write_reg_cache(codec, reg, value); + + switch (reg) { + case UDA134X_STATUS0: + case UDA134X_STATUS1: + addr = UDA134X_STATUS_ADDR; + break; + case UDA134X_DATA000: + case UDA134X_DATA001: + case UDA134X_DATA010: + addr = UDA134X_DATA0_ADDR; + break; + case UDA134X_DATA1: + addr = UDA134X_DATA1_ADDR; + break; + default: + /* It's an extended address register */ + addr = (reg | UDA134X_EXTADDR_PREFIX); + + ret = l3_write(&pd->l3, + UDA134X_DATA0_ADDR, &addr, 1); + if (ret != 1) + return -EIO; + + addr = UDA134X_DATA0_ADDR; + data = (value | UDA134X_EXTDATA_PREFIX); + break; + } + + ret = l3_write(&pd->l3, + addr, &data, 1); + if (ret != 1) + return -EIO; + + return 0; +} + +static inline void uda134x_reset(struct snd_soc_codec *codec) +{ + u8 reset_reg = uda134x_read_reg_cache(codec, UDA134X_STATUS0); + uda134x_write(codec, UDA134X_STATUS0, reset_reg | (1<<6)); + msleep(1); + uda134x_write(codec, UDA134X_STATUS0, reset_reg & ~(1<<6)); +} + +static int uda134x_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u8 mute_reg = uda134x_read_reg_cache(codec, UDA134X_DATA010); + + pr_debug("%s mute: %d\n", __func__, mute); + + if (mute) + mute_reg |= (1<<2); + else + mute_reg &= ~(1<<2); + + uda134x_write(codec, UDA134X_DATA010, mute_reg & ~(1<<2)); + + return 0; +} + +static int uda134x_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct uda134x_priv *uda134x = codec->private_data; + struct snd_pcm_runtime *master_runtime; + + if (uda134x->master_substream) { + master_runtime = uda134x->master_substream->runtime; + + pr_debug("%s constraining to %d bits at %d\n", __func__, + 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); + + uda134x->slave_substream = substream; + } else + uda134x->master_substream = substream; + + return 0; +} + +static void uda134x_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct uda134x_priv *uda134x = codec->private_data; + + if (uda134x->master_substream == substream) + uda134x->master_substream = uda134x->slave_substream; + + uda134x->slave_substream = NULL; +} + +static int uda134x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct uda134x_priv *uda134x = codec->private_data; + u8 hw_params; + + if (substream == uda134x->slave_substream) { + pr_debug("%s ignoring hw_params for slave substream\n", + __func__); + return 0; + } + + hw_params = uda134x_read_reg_cache(codec, UDA134X_STATUS0); + hw_params &= STATUS0_SYSCLK_MASK; + hw_params &= STATUS0_DAIFMT_MASK; + + pr_debug("%s sysclk: %d, rate:%d\n", __func__, + uda134x->sysclk, params_rate(params)); + + /* set SYSCLK / fs ratio */ + switch (uda134x->sysclk / params_rate(params)) { + case 512: + break; + case 384: + hw_params |= (1<<4); + break; + case 256: + hw_params |= (1<<5); + break; + default: + printk(KERN_ERR "%s unsupported fs\n", __func__); + return -EINVAL; + } + + pr_debug("%s dai_fmt: %d, params_format:%d\n", __func__, + uda134x->dai_fmt, params_format(params)); + + /* set DAI format and word length */ + switch (uda134x->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + hw_params |= (1<<1); + break; + case SNDRV_PCM_FORMAT_S18_3LE: + hw_params |= (1<<2); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + hw_params |= ((1<<2) | (1<<1)); + break; + default: + printk(KERN_ERR "%s unsupported format (right)\n", + __func__); + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_LEFT_J: + hw_params |= (1<<3); + break; + default: + printk(KERN_ERR "%s unsupported format\n", __func__); + return -EINVAL; + } + + uda134x_write(codec, UDA134X_STATUS0, hw_params); + + return 0; +} + +static int uda134x_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 uda134x_priv *uda134x = codec->private_data; + + pr_debug("%s clk_id: %d, freq: %d, dir: %d\n", __func__, + clk_id, freq, dir); + + /* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable + because the codec is slave. Of course limitations of the clock + master (the IIS controller) apply. + We'll error out on set_hw_params if it's not OK */ + if ((freq >= (256 * 8000)) && (freq <= (512 * 48000))) { + uda134x->sysclk = freq; + return 0; + } + + printk(KERN_ERR "%s unsupported sysclk\n", __func__); + return -EINVAL; +} + +static int uda134x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct uda134x_priv *uda134x = codec->private_data; + + pr_debug("%s fmt: %08X\n", __func__, fmt); + + /* codec supports only full slave mode */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) { + printk(KERN_ERR "%s unsupported slave mode\n", __func__); + return -EINVAL; + } + + /* no support for clock inversion */ + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) { + printk(KERN_ERR "%s unsupported clock inversion\n", __func__); + return -EINVAL; + } + + /* We can't setup DAI format here as it depends on the word bit num */ + /* so let's just store the value for later */ + uda134x->dai_fmt = fmt; + + return 0; +} + +static int uda134x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u8 reg; + struct uda134x_platform_data *pd = codec->control_data; + int i; + u8 *cache = codec->reg_cache; + + pr_debug("%s bias level %d\n", __func__, level); + + switch (level) { + case SND_SOC_BIAS_ON: + /* ADC, DAC on */ + reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1); + uda134x_write(codec, UDA134X_STATUS1, reg | 0x03); + break; + case SND_SOC_BIAS_PREPARE: + /* power on */ + if (pd->power) { + pd->power(1); + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(uda134x_reg); i++) + codec->write(codec, i, *cache++); + } + break; + case SND_SOC_BIAS_STANDBY: + /* ADC, DAC power off */ + reg = uda134x_read_reg_cache(codec, UDA134X_STATUS1); + uda134x_write(codec, UDA134X_STATUS1, reg & ~(0x03)); + break; + case SND_SOC_BIAS_OFF: + /* power off */ + if (pd->power) + pd->power(0); + break; + } + codec->bias_level = level; + return 0; +} + +static const char *uda134x_dsp_setting[] = {"Flat", "Minimum1", + "Minimum2", "Maximum"}; +static const char *uda134x_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *uda134x_mixmode[] = {"Differential", "Analog1", + "Analog2", "Both"}; + +static const struct soc_enum uda134x_mixer_enum[] = { +SOC_ENUM_SINGLE(UDA134X_DATA010, 0, 0x04, uda134x_dsp_setting), +SOC_ENUM_SINGLE(UDA134X_DATA010, 3, 0x04, uda134x_deemph), +SOC_ENUM_SINGLE(UDA134X_EA010, 0, 0x04, uda134x_mixmode), +}; + +static const struct snd_kcontrol_new uda1341_snd_controls[] = { +SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1), +SOC_SINGLE("Capture Volume", UDA134X_EA010, 2, 0x07, 0), +SOC_SINGLE("Analog1 Volume", UDA134X_EA000, 0, 0x1F, 1), +SOC_SINGLE("Analog2 Volume", UDA134X_EA001, 0, 0x1F, 1), + +SOC_SINGLE("Mic Sensitivity", UDA134X_EA010, 2, 7, 0), +SOC_SINGLE("Mic Volume", UDA134X_EA101, 0, 0x1F, 0), + +SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0), +SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0), + +SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]), +SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), +SOC_ENUM("Input Mux", uda134x_mixer_enum[2]), + +SOC_SINGLE("AGC Switch", UDA134X_EA100, 4, 1, 0), +SOC_SINGLE("AGC Target Volume", UDA134X_EA110, 0, 0x03, 1), +SOC_SINGLE("AGC Timing", UDA134X_EA110, 2, 0x07, 0), + +SOC_SINGLE("DAC +6dB Switch", UDA134X_STATUS1, 6, 1, 0), +SOC_SINGLE("ADC +6dB Switch", UDA134X_STATUS1, 5, 1, 0), +SOC_SINGLE("ADC Polarity Switch", UDA134X_STATUS1, 4, 1, 0), +SOC_SINGLE("DAC Polarity Switch", UDA134X_STATUS1, 3, 1, 0), +SOC_SINGLE("Double Speed Playback Switch", UDA134X_STATUS1, 2, 1, 0), +SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), +}; + +static const struct snd_kcontrol_new uda1340_snd_controls[] = { +SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1), + +SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0), +SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0), + +SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]), +SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]), + +SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0), +}; + +static int uda134x_add_controls(struct snd_soc_codec *codec) +{ + int err, i, n; + const struct snd_kcontrol_new *ctrls; + struct uda134x_platform_data *pd = codec->control_data; + + switch (pd->model) { + case UDA134X_UDA1340: + case UDA134X_UDA1344: + n = ARRAY_SIZE(uda1340_snd_controls); + ctrls = uda1340_snd_controls; + break; + case UDA134X_UDA1341: + n = ARRAY_SIZE(uda1341_snd_controls); + ctrls = uda1341_snd_controls; + break; + default: + printk(KERN_ERR "%s unkown codec type: %d", + __func__, pd->model); + return -EINVAL; + } + + for (i = 0; i < n; i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ctrls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +struct snd_soc_dai uda134x_dai = { + .name = "UDA134X", + /* playback capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = UDA134X_RATES, + .formats = UDA134X_FORMATS, + }, + /* capture capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = UDA134X_RATES, + .formats = UDA134X_FORMATS, + }, + /* pcm operations */ + .ops = { + .startup = uda134x_startup, + .shutdown = uda134x_shutdown, + .hw_params = uda134x_hw_params, + .digital_mute = uda134x_mute, + .set_sysclk = uda134x_set_dai_sysclk, + .set_fmt = uda134x_set_dai_fmt, + } +}; +EXPORT_SYMBOL(uda134x_dai); + + +static int uda134x_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct uda134x_priv *uda134x; + void *codec_setup_data = socdev->codec_data; + int ret = -ENOMEM; + struct uda134x_platform_data *pd; + + printk(KERN_INFO "UDA134X SoC Audio Codec\n"); + + if (!codec_setup_data) { + printk(KERN_ERR "UDA134X SoC codec: " + "missing L3 bitbang function\n"); + return -ENODEV; + } + + pd = codec_setup_data; + switch (pd->model) { + case UDA134X_UDA1340: + case UDA134X_UDA1341: + case UDA134X_UDA1344: + break; + default: + printk(KERN_ERR "UDA134X SoC codec: " + "unsupported model %d\n", + pd->model); + return -EINVAL; + } + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return ret; + + codec = socdev->codec; + + uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL); + if (uda134x == NULL) + goto priv_err; + codec->private_data = uda134x; + + codec->reg_cache = kmemdup(uda134x_reg, sizeof(uda134x_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) + goto reg_err; + + mutex_init(&codec->mutex); + + codec->reg_cache_size = sizeof(uda134x_reg); + codec->reg_cache_step = 1; + + codec->name = "UDA134X"; + codec->owner = THIS_MODULE; + codec->dai = &uda134x_dai; + codec->num_dai = 1; + codec->read = uda134x_read_reg_cache; + codec->write = uda134x_write; +#ifdef POWER_OFF_ON_STANDBY + codec->set_bias_level = uda134x_set_bias_level; +#endif + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->control_data = codec_setup_data; + + if (pd->power) + pd->power(1); + + uda134x_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "UDA134X: failed to register pcms\n"); + goto pcm_err; + } + + ret = uda134x_add_controls(codec); + if (ret < 0) { + printk(KERN_ERR "UDA134X: failed to register controls\n"); + goto pcm_err; + } + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "UDA134X: failed to register card\n"); + goto card_err; + } + + return 0; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); +reg_err: + kfree(codec->private_data); +priv_err: + kfree(codec); + return ret; +} + +/* power down chip */ +static int uda134x_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + kfree(codec->private_data); + kfree(codec->reg_cache); + kfree(codec); + + return 0; +} + +#if defined(CONFIG_PM) +static int uda134x_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; + + uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int uda134x_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + uda134x_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + uda134x_set_bias_level(codec, SND_SOC_BIAS_ON); + return 0; +} +#else +#define uda134x_soc_suspend NULL +#define uda134x_soc_resume NULL +#endif /* CONFIG_PM */ + +struct snd_soc_codec_device soc_codec_dev_uda134x = { + .probe = uda134x_soc_probe, + .remove = uda134x_soc_remove, + .suspend = uda134x_soc_suspend, + .resume = uda134x_soc_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_uda134x); + +static int __init uda134x_init(void) +{ + return snd_soc_register_dai(&uda134x_dai); +} +module_init(uda134x_init); + +static void __exit uda134x_exit(void) +{ + snd_soc_unregister_dai(&uda134x_dai); +} +module_exit(uda134x_exit); + +MODULE_DESCRIPTION("UDA134X ALSA soc codec driver"); +MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/uda134x.h b/sound/soc/codecs/uda134x.h new file mode 100644 index 00000000000..94f440490b3 --- /dev/null +++ b/sound/soc/codecs/uda134x.h @@ -0,0 +1,36 @@ +#ifndef _UDA134X_CODEC_H +#define _UDA134X_CODEC_H + +#define UDA134X_L3ADDR 5 +#define UDA134X_DATA0_ADDR ((UDA134X_L3ADDR << 2) | 0) +#define UDA134X_DATA1_ADDR ((UDA134X_L3ADDR << 2) | 1) +#define UDA134X_STATUS_ADDR ((UDA134X_L3ADDR << 2) | 2) + +#define UDA134X_EXTADDR_PREFIX 0xC0 +#define UDA134X_EXTDATA_PREFIX 0xE0 + +/* UDA134X registers */ +#define UDA134X_EA000 0 +#define UDA134X_EA001 1 +#define UDA134X_EA010 2 +#define UDA134X_EA011 3 +#define UDA134X_EA100 4 +#define UDA134X_EA101 5 +#define UDA134X_EA110 6 +#define UDA134X_EA111 7 +#define UDA134X_STATUS0 8 +#define UDA134X_STATUS1 9 +#define UDA134X_DATA000 10 +#define UDA134X_DATA001 11 +#define UDA134X_DATA010 12 +#define UDA134X_DATA1 13 + +#define UDA134X_REGS_NUM 14 + +#define STATUS0_DAIFMT_MASK (~(7<<1)) +#define STATUS0_SYSCLK_MASK (~(3<<4)) + +extern struct snd_soc_dai uda134x_dai; +extern struct snd_soc_codec_device soc_codec_dev_uda134x; + +#endif diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index a69ee72a7af..e6bf0844fbf 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -407,7 +407,8 @@ static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai, * 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) +static int uda1380_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -439,7 +440,8 @@ static int uda1380_pcm_prepare(struct snd_pcm_substream *substream) } static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -477,7 +479,8 @@ static int uda1380_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } -static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream) +static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -560,8 +563,6 @@ struct snd_soc_dai uda1380_dai[] = { .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, }, @@ -579,8 +580,6 @@ struct snd_soc_dai uda1380_dai[] = { .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, }, @@ -598,8 +597,6 @@ struct snd_soc_dai uda1380_dai[] = { .hw_params = uda1380_pcm_hw_params, .shutdown = uda1380_pcm_shutdown, .prepare = uda1380_pcm_prepare, - }, - .dai_ops = { .set_fmt = uda1380_set_dai_fmt, }, }, @@ -680,7 +677,7 @@ static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) /* uda1380 init */ uda1380_add_controls(codec); uda1380_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { pr_err("uda1380: failed to register card\n"); goto card_err; @@ -844,6 +841,18 @@ struct snd_soc_codec_device soc_codec_dev_uda1380 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); +static int __init uda1380_modinit(void) +{ + return snd_soc_register_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); +} +module_init(uda1380_modinit); + +static void __exit uda1380_exit(void) +{ + snd_soc_unregister_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); +} +module_exit(uda1380_exit); + MODULE_AUTHOR("Giorgio Padrin"); MODULE_DESCRIPTION("Audio support for codec Philips UDA1380"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c new file mode 100644 index 00000000000..e3989d406f5 --- /dev/null +++ b/sound/soc/codecs/wm8350.c @@ -0,0 +1,1583 @@ +/* + * wm8350.c -- WM8350 ALSA SoC audio driver + * + * Copyright (C) 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood <lg@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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/mfd/wm8350/audio.h> +#include <linux/mfd/wm8350/core.h> +#include <linux/regulator/consumer.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 "wm8350.h" + +#define WM8350_OUTn_0dB 0x39 + +#define WM8350_RAMP_NONE 0 +#define WM8350_RAMP_UP 1 +#define WM8350_RAMP_DOWN 2 + +/* We only include the analogue supplies here; the digital supplies + * need to be available well before this driver can be probed. + */ +static const char *supply_names[] = { + "AVDD", + "HPVDD", +}; + +struct wm8350_output { + u16 active; + u16 left_vol; + u16 right_vol; + u16 ramp; + u16 mute; +}; + +struct wm8350_data { + struct snd_soc_codec codec; + struct wm8350_output out1; + struct wm8350_output out2; + struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)]; +}; + +static unsigned int wm8350_codec_cache_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct wm8350 *wm8350 = codec->control_data; + return wm8350->reg_cache[reg]; +} + +static unsigned int wm8350_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct wm8350 *wm8350 = codec->control_data; + return wm8350_reg_read(wm8350, reg); +} + +static int wm8350_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct wm8350 *wm8350 = codec->control_data; + return wm8350_reg_write(wm8350, reg, value); +} + +/* + * Ramp OUT1 PGA volume to minimise pops at stream startup and shutdown. + */ +static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec) +{ + struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_output *out1 = &wm8350_data->out1; + struct wm8350 *wm8350 = codec->control_data; + int left_complete = 0, right_complete = 0; + u16 reg, val; + + /* left channel */ + reg = wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME); + val = (reg & WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; + + if (out1->ramp == WM8350_RAMP_UP) { + /* ramp step up */ + if (val < out1->left_vol) { + val++; + reg &= ~WM8350_OUT1L_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, + reg | (val << WM8350_OUT1L_VOL_SHIFT)); + } else + left_complete = 1; + } else if (out1->ramp == WM8350_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val--; + reg &= ~WM8350_OUT1L_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, + reg | (val << WM8350_OUT1L_VOL_SHIFT)); + } else + left_complete = 1; + } else + return 1; + + /* right channel */ + reg = wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME); + val = (reg & WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; + if (out1->ramp == WM8350_RAMP_UP) { + /* ramp step up */ + if (val < out1->right_vol) { + val++; + reg &= ~WM8350_OUT1R_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, + reg | (val << WM8350_OUT1R_VOL_SHIFT)); + } else + right_complete = 1; + } else if (out1->ramp == WM8350_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val--; + reg &= ~WM8350_OUT1R_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, + reg | (val << WM8350_OUT1R_VOL_SHIFT)); + } else + right_complete = 1; + } + + /* only hit the update bit if either volume has changed this step */ + if (!left_complete || !right_complete) + wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU); + + return left_complete & right_complete; +} + +/* + * Ramp OUT2 PGA volume to minimise pops at stream startup and shutdown. + */ +static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec) +{ + struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_output *out2 = &wm8350_data->out2; + struct wm8350 *wm8350 = codec->control_data; + int left_complete = 0, right_complete = 0; + u16 reg, val; + + /* left channel */ + reg = wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME); + val = (reg & WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; + if (out2->ramp == WM8350_RAMP_UP) { + /* ramp step up */ + if (val < out2->left_vol) { + val++; + reg &= ~WM8350_OUT2L_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, + reg | (val << WM8350_OUT1L_VOL_SHIFT)); + } else + left_complete = 1; + } else if (out2->ramp == WM8350_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val--; + reg &= ~WM8350_OUT2L_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, + reg | (val << WM8350_OUT1L_VOL_SHIFT)); + } else + left_complete = 1; + } else + return 1; + + /* right channel */ + reg = wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME); + val = (reg & WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; + if (out2->ramp == WM8350_RAMP_UP) { + /* ramp step up */ + if (val < out2->right_vol) { + val++; + reg &= ~WM8350_OUT2R_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, + reg | (val << WM8350_OUT1R_VOL_SHIFT)); + } else + right_complete = 1; + } else if (out2->ramp == WM8350_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val--; + reg &= ~WM8350_OUT2R_VOL_MASK; + wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, + reg | (val << WM8350_OUT1R_VOL_SHIFT)); + } else + right_complete = 1; + } + + /* only hit the update bit if either volume has changed this step */ + if (!left_complete || !right_complete) + wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, WM8350_OUT2_VU); + + return left_complete & right_complete; +} + +/* + * This work ramps both output PGAs at stream start/stop time to + * minimise pop associated with DAPM power switching. + * It's best to enable Zero Cross when ramping occurs to minimise any + * zipper noises. + */ +static void wm8350_pga_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_output *out1 = &wm8350_data->out1, + *out2 = &wm8350_data->out2; + int i, out1_complete, out2_complete; + + /* do we need to ramp at all ? */ + if (out1->ramp == WM8350_RAMP_NONE && out2->ramp == WM8350_RAMP_NONE) + return; + + /* PGA volumes have 6 bits of resolution to ramp */ + for (i = 0; i <= 63; i++) { + out1_complete = 1, out2_complete = 1; + if (out1->ramp != WM8350_RAMP_NONE) + out1_complete = wm8350_out1_ramp_step(codec); + if (out2->ramp != WM8350_RAMP_NONE) + out2_complete = wm8350_out2_ramp_step(codec); + + /* ramp finished ? */ + if (out1_complete && out2_complete) + break; + + /* we need to delay longer on the up ramp */ + if (out1->ramp == WM8350_RAMP_UP || + out2->ramp == WM8350_RAMP_UP) { + /* delay is longer over 0dB as increases are larger */ + if (i >= WM8350_OUTn_0dB) + schedule_timeout_interruptible(msecs_to_jiffies + (2)); + else + schedule_timeout_interruptible(msecs_to_jiffies + (1)); + } else + udelay(50); /* doesn't matter if we delay longer */ + } + + out1->ramp = WM8350_RAMP_NONE; + out2->ramp = WM8350_RAMP_NONE; +} + +/* + * WM8350 Controls + */ + +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8350_data *wm8350_data = codec->private_data; + struct wm8350_output *out; + + switch (w->shift) { + case 0: + case 1: + out = &wm8350_data->out1; + break; + case 2: + case 3: + out = &wm8350_data->out2; + break; + + default: + BUG(); + return -1; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + out->ramp = WM8350_RAMP_UP; + out->active = 1; + + if (!delayed_work_pending(&codec->delayed_work)) + schedule_delayed_work(&codec->delayed_work, + msecs_to_jiffies(1)); + break; + + case SND_SOC_DAPM_PRE_PMD: + out->ramp = WM8350_RAMP_DOWN; + out->active = 0; + + if (!delayed_work_pending(&codec->delayed_work)) + schedule_delayed_work(&codec->delayed_work, + msecs_to_jiffies(1)); + break; + } + + return 0; +} + +static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8350_data *wm8350_priv = codec->private_data; + struct wm8350_output *out = NULL; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int ret; + unsigned int reg = mc->reg; + u16 val; + + /* For OUT1 and OUT2 we shadow the values and only actually write + * them out when active in order to ensure the amplifier comes on + * as quietly as possible. */ + switch (reg) { + case WM8350_LOUT1_VOLUME: + out = &wm8350_priv->out1; + break; + case WM8350_LOUT2_VOLUME: + out = &wm8350_priv->out2; + break; + default: + break; + } + + if (out) { + out->left_vol = ucontrol->value.integer.value[0]; + out->right_vol = ucontrol->value.integer.value[1]; + if (!out->active) + return 1; + } + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = wm8350_codec_read(codec, reg); + wm8350_codec_write(codec, reg, val | WM8350_OUT1_VU); + return 1; +} + +static int wm8350_get_volsw_2r(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8350_data *wm8350_priv = codec->private_data; + struct wm8350_output *out1 = &wm8350_priv->out1; + struct wm8350_output *out2 = &wm8350_priv->out2; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + + /* If these are cached registers use the cache */ + switch (reg) { + case WM8350_LOUT1_VOLUME: + ucontrol->value.integer.value[0] = out1->left_vol; + ucontrol->value.integer.value[1] = out1->right_vol; + return 0; + + case WM8350_LOUT2_VOLUME: + ucontrol->value.integer.value[0] = out2->left_vol; + ucontrol->value.integer.value[1] = out2->right_vol; + return 0; + + default: + break; + } + + return snd_soc_get_volsw_2r(kcontrol, ucontrol); +} + +/* double control with volume update */ +#define SOC_WM8350_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, \ + xinvert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r, \ + .get = wm8350_get_volsw_2r, .put = wm8350_put_volsw_2r_vu, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ + .rshift = xshift, .max = xmax, .invert = xinvert}, } + +static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" }; +static const char *wm8350_dacmutem[] = { "Normal", "Soft" }; +static const char *wm8350_dacmutes[] = { "Fast", "Slow" }; +static const char *wm8350_dacfilter[] = { "Normal", "Sloping" }; +static const char *wm8350_adcfilter[] = { "None", "High Pass" }; +static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" }; +static const char *wm8350_lr[] = { "Left", "Right" }; + +static const struct soc_enum wm8350_enum[] = { + SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 4, 4, wm8350_deemp), + SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol), + SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem), + SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes), + SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 12, 2, wm8350_dacfilter), + SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter), + SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp), + SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol), + SOC_ENUM_SINGLE(WM8350_INPUT_MIXER_VOLUME, 15, 2, wm8350_lr), +}; + +static DECLARE_TLV_DB_LINEAR(pre_amp_tlv, -1200, 3525); +static DECLARE_TLV_DB_LINEAR(out_pga_tlv, -5700, 600); +static DECLARE_TLV_DB_SCALE(dac_pcm_tlv, -7163, 36, 1); +static DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -12700, 50, 1); +static DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 1); + +static const unsigned int capture_sd_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 12, TLV_DB_SCALE_ITEM(-3600, 300, 1), + 13, 15, TLV_DB_SCALE_ITEM(0, 0, 0), +}; + +static const struct snd_kcontrol_new wm8350_snd_controls[] = { + SOC_ENUM("Playback Deemphasis", wm8350_enum[0]), + SOC_ENUM("Playback DAC Inversion", wm8350_enum[1]), + SOC_WM8350_DOUBLE_R_TLV("Playback PCM Volume", + WM8350_DAC_DIGITAL_VOLUME_L, + WM8350_DAC_DIGITAL_VOLUME_R, + 0, 255, 0, dac_pcm_tlv), + SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]), + SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]), + SOC_ENUM("Playback PCM Filter", wm8350_enum[4]), + SOC_ENUM("Capture PCM Filter", wm8350_enum[5]), + SOC_ENUM("Capture PCM HP Filter", wm8350_enum[6]), + SOC_ENUM("Capture ADC Inversion", wm8350_enum[7]), + SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume", + WM8350_ADC_DIGITAL_VOLUME_L, + WM8350_ADC_DIGITAL_VOLUME_R, + 0, 255, 0, adc_pcm_tlv), + SOC_DOUBLE_TLV("Capture Sidetone Volume", + WM8350_ADC_DIVIDER, + 8, 4, 15, 1, capture_sd_tlv), + SOC_WM8350_DOUBLE_R_TLV("Capture Volume", + WM8350_LEFT_INPUT_VOLUME, + WM8350_RIGHT_INPUT_VOLUME, + 2, 63, 0, pre_amp_tlv), + SOC_DOUBLE_R("Capture ZC Switch", + WM8350_LEFT_INPUT_VOLUME, + WM8350_RIGHT_INPUT_VOLUME, 13, 1, 0), + SOC_SINGLE_TLV("Left Input Left Sidetone Volume", + WM8350_OUTPUT_LEFT_MIXER_VOLUME, 1, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Left Input Right Sidetone Volume", + WM8350_OUTPUT_LEFT_MIXER_VOLUME, + 5, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Left Input Bypass Volume", + WM8350_OUTPUT_LEFT_MIXER_VOLUME, + 9, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Right Input Left Sidetone Volume", + WM8350_OUTPUT_RIGHT_MIXER_VOLUME, + 1, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Right Input Right Sidetone Volume", + WM8350_OUTPUT_RIGHT_MIXER_VOLUME, + 5, 7, 0, out_mix_tlv), + SOC_SINGLE_TLV("Right Input Bypass Volume", + WM8350_OUTPUT_RIGHT_MIXER_VOLUME, + 13, 7, 0, out_mix_tlv), + SOC_SINGLE("Left Input Mixer +20dB Switch", + WM8350_INPUT_MIXER_VOLUME_L, 0, 1, 0), + SOC_SINGLE("Right Input Mixer +20dB Switch", + WM8350_INPUT_MIXER_VOLUME_R, 0, 1, 0), + SOC_SINGLE_TLV("Out4 Capture Volume", + WM8350_INPUT_MIXER_VOLUME, + 1, 7, 0, out_mix_tlv), + SOC_WM8350_DOUBLE_R_TLV("Out1 Playback Volume", + WM8350_LOUT1_VOLUME, + WM8350_ROUT1_VOLUME, + 2, 63, 0, out_pga_tlv), + SOC_DOUBLE_R("Out1 Playback ZC Switch", + WM8350_LOUT1_VOLUME, + WM8350_ROUT1_VOLUME, 13, 1, 0), + SOC_WM8350_DOUBLE_R_TLV("Out2 Playback Volume", + WM8350_LOUT2_VOLUME, + WM8350_ROUT2_VOLUME, + 2, 63, 0, out_pga_tlv), + SOC_DOUBLE_R("Out2 Playback ZC Switch", WM8350_LOUT2_VOLUME, + WM8350_ROUT2_VOLUME, 13, 1, 0), + SOC_SINGLE("Out2 Right Invert Switch", WM8350_ROUT2_VOLUME, 10, 1, 0), + SOC_SINGLE_TLV("Out2 Beep Volume", WM8350_BEEP_VOLUME, + 5, 7, 0, out_mix_tlv), + + SOC_DOUBLE_R("Out1 Playback Switch", + WM8350_LOUT1_VOLUME, + WM8350_ROUT1_VOLUME, + 14, 1, 1), + SOC_DOUBLE_R("Out2 Playback Switch", + WM8350_LOUT2_VOLUME, + WM8350_ROUT2_VOLUME, + 14, 1, 1), +}; + +/* + * DAPM Controls + */ + +/* Left Playback Mixer */ +static const struct snd_kcontrol_new wm8350_left_play_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", + WM8350_LEFT_MIXER_CONTROL, 11, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", + WM8350_LEFT_MIXER_CONTROL, 2, 1, 0), + SOC_DAPM_SINGLE("Right Playback Switch", + WM8350_LEFT_MIXER_CONTROL, 12, 1, 0), + SOC_DAPM_SINGLE("Left Sidetone Switch", + WM8350_LEFT_MIXER_CONTROL, 0, 1, 0), + SOC_DAPM_SINGLE("Right Sidetone Switch", + WM8350_LEFT_MIXER_CONTROL, 1, 1, 0), +}; + +/* Right Playback Mixer */ +static const struct snd_kcontrol_new wm8350_right_play_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", + WM8350_RIGHT_MIXER_CONTROL, 12, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", + WM8350_RIGHT_MIXER_CONTROL, 3, 1, 0), + SOC_DAPM_SINGLE("Left Playback Switch", + WM8350_RIGHT_MIXER_CONTROL, 11, 1, 0), + SOC_DAPM_SINGLE("Left Sidetone Switch", + WM8350_RIGHT_MIXER_CONTROL, 0, 1, 0), + SOC_DAPM_SINGLE("Right Sidetone Switch", + WM8350_RIGHT_MIXER_CONTROL, 1, 1, 0), +}; + +/* Out4 Mixer */ +static const struct snd_kcontrol_new wm8350_out4_mixer_controls[] = { + SOC_DAPM_SINGLE("Right Playback Switch", + WM8350_OUT4_MIXER_CONTROL, 12, 1, 0), + SOC_DAPM_SINGLE("Left Playback Switch", + WM8350_OUT4_MIXER_CONTROL, 11, 1, 0), + SOC_DAPM_SINGLE("Right Capture Switch", + WM8350_OUT4_MIXER_CONTROL, 9, 1, 0), + SOC_DAPM_SINGLE("Out3 Playback Switch", + WM8350_OUT4_MIXER_CONTROL, 2, 1, 0), + SOC_DAPM_SINGLE("Right Mixer Switch", + WM8350_OUT4_MIXER_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("Left Mixer Switch", + WM8350_OUT4_MIXER_CONTROL, 0, 1, 0), +}; + +/* Out3 Mixer */ +static const struct snd_kcontrol_new wm8350_out3_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Playback Switch", + WM8350_OUT3_MIXER_CONTROL, 11, 1, 0), + SOC_DAPM_SINGLE("Left Capture Switch", + WM8350_OUT3_MIXER_CONTROL, 8, 1, 0), + SOC_DAPM_SINGLE("Out4 Playback Switch", + WM8350_OUT3_MIXER_CONTROL, 3, 1, 0), + SOC_DAPM_SINGLE("Left Mixer Switch", + WM8350_OUT3_MIXER_CONTROL, 0, 1, 0), +}; + +/* Left Input Mixer */ +static const struct snd_kcontrol_new wm8350_left_capt_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("L2 Capture Volume", + WM8350_INPUT_MIXER_VOLUME_L, 1, 7, 0, out_mix_tlv), + SOC_DAPM_SINGLE_TLV("L3 Capture Volume", + WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv), + SOC_DAPM_SINGLE("PGA Capture Switch", + WM8350_LEFT_INPUT_VOLUME, 14, 1, 0), +}; + +/* Right Input Mixer */ +static const struct snd_kcontrol_new wm8350_right_capt_mixer_controls[] = { + SOC_DAPM_SINGLE_TLV("L2 Capture Volume", + WM8350_INPUT_MIXER_VOLUME_R, 5, 7, 0, out_mix_tlv), + SOC_DAPM_SINGLE_TLV("L3 Capture Volume", + WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv), + SOC_DAPM_SINGLE("PGA Capture Switch", + WM8350_RIGHT_INPUT_VOLUME, 14, 1, 0), +}; + +/* Left Mic Mixer */ +static const struct snd_kcontrol_new wm8350_left_mic_mixer_controls[] = { + SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 1, 1, 0), + SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 0, 1, 0), + SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 2, 1, 0), +}; + +/* Right Mic Mixer */ +static const struct snd_kcontrol_new wm8350_right_mic_mixer_controls[] = { + SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 9, 1, 0), + SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 8, 1, 0), + SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 10, 1, 0), +}; + +/* Beep Switch */ +static const struct snd_kcontrol_new wm8350_beep_switch_controls = +SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1); + +/* Out4 Capture Mux */ +static const struct snd_kcontrol_new wm8350_out4_capture_controls = +SOC_DAPM_ENUM("Route", wm8350_enum[8]); + +static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = { + + SND_SOC_DAPM_PGA("IN3R PGA", WM8350_POWER_MGMT_2, 11, 0, NULL, 0), + SND_SOC_DAPM_PGA("IN3L PGA", WM8350_POWER_MGMT_2, 10, 0, NULL, 0), + SND_SOC_DAPM_PGA_E("Right Out2 PGA", WM8350_POWER_MGMT_3, 3, 0, NULL, + 0, pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Left Out2 PGA", WM8350_POWER_MGMT_3, 2, 0, NULL, 0, + pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Right Out1 PGA", WM8350_POWER_MGMT_3, 1, 0, NULL, + 0, pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Left Out1 PGA", WM8350_POWER_MGMT_3, 0, 0, NULL, 0, + pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_MIXER("Right Capture Mixer", WM8350_POWER_MGMT_2, + 7, 0, &wm8350_right_capt_mixer_controls[0], + ARRAY_SIZE(wm8350_right_capt_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Capture Mixer", WM8350_POWER_MGMT_2, + 6, 0, &wm8350_left_capt_mixer_controls[0], + ARRAY_SIZE(wm8350_left_capt_mixer_controls)), + + SND_SOC_DAPM_MIXER("Out4 Mixer", WM8350_POWER_MGMT_2, 5, 0, + &wm8350_out4_mixer_controls[0], + ARRAY_SIZE(wm8350_out4_mixer_controls)), + + SND_SOC_DAPM_MIXER("Out3 Mixer", WM8350_POWER_MGMT_2, 4, 0, + &wm8350_out3_mixer_controls[0], + ARRAY_SIZE(wm8350_out3_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Playback Mixer", WM8350_POWER_MGMT_2, 1, 0, + &wm8350_right_play_mixer_controls[0], + ARRAY_SIZE(wm8350_right_play_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Playback Mixer", WM8350_POWER_MGMT_2, 0, 0, + &wm8350_left_play_mixer_controls[0], + ARRAY_SIZE(wm8350_left_play_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left Mic Mixer", WM8350_POWER_MGMT_2, 8, 0, + &wm8350_left_mic_mixer_controls[0], + ARRAY_SIZE(wm8350_left_mic_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right Mic Mixer", WM8350_POWER_MGMT_2, 9, 0, + &wm8350_right_mic_mixer_controls[0], + ARRAY_SIZE(wm8350_right_mic_mixer_controls)), + + /* virtual mixer for Beep and Out2R */ + SND_SOC_DAPM_MIXER("Out2 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("Beep", WM8350_POWER_MGMT_3, 7, 0, + &wm8350_beep_switch_controls), + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", + WM8350_POWER_MGMT_4, 3, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", + WM8350_POWER_MGMT_4, 2, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", + WM8350_POWER_MGMT_4, 5, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", + WM8350_POWER_MGMT_4, 4, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8350_POWER_MGMT_1, 4, 0), + + SND_SOC_DAPM_MUX("Out4 Capture Channel", SND_SOC_NOPM, 0, 0, + &wm8350_out4_capture_controls), + + SND_SOC_DAPM_OUTPUT("OUT1R"), + SND_SOC_DAPM_OUTPUT("OUT1L"), + SND_SOC_DAPM_OUTPUT("OUT2R"), + SND_SOC_DAPM_OUTPUT("OUT2L"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("OUT4"), + + SND_SOC_DAPM_INPUT("IN1RN"), + SND_SOC_DAPM_INPUT("IN1RP"), + SND_SOC_DAPM_INPUT("IN2R"), + SND_SOC_DAPM_INPUT("IN1LP"), + SND_SOC_DAPM_INPUT("IN1LN"), + SND_SOC_DAPM_INPUT("IN2L"), + SND_SOC_DAPM_INPUT("IN3R"), + SND_SOC_DAPM_INPUT("IN3L"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + + /* left playback mixer */ + {"Left Playback Mixer", "Playback Switch", "Left DAC"}, + {"Left Playback Mixer", "Left Bypass Switch", "IN3L PGA"}, + {"Left Playback Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Playback Mixer", "Left Sidetone Switch", "Left Mic Mixer"}, + {"Left Playback Mixer", "Right Sidetone Switch", "Right Mic Mixer"}, + + /* right playback mixer */ + {"Right Playback Mixer", "Playback Switch", "Right DAC"}, + {"Right Playback Mixer", "Right Bypass Switch", "IN3R PGA"}, + {"Right Playback Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Playback Mixer", "Left Sidetone Switch", "Left Mic Mixer"}, + {"Right Playback Mixer", "Right Sidetone Switch", "Right Mic Mixer"}, + + /* out4 playback mixer */ + {"Out4 Mixer", "Right Playback Switch", "Right DAC"}, + {"Out4 Mixer", "Left Playback Switch", "Left DAC"}, + {"Out4 Mixer", "Right Capture Switch", "Right Capture Mixer"}, + {"Out4 Mixer", "Out3 Playback Switch", "Out3 Mixer"}, + {"Out4 Mixer", "Right Mixer Switch", "Right Playback Mixer"}, + {"Out4 Mixer", "Left Mixer Switch", "Left Playback Mixer"}, + {"OUT4", NULL, "Out4 Mixer"}, + + /* out3 playback mixer */ + {"Out3 Mixer", "Left Playback Switch", "Left DAC"}, + {"Out3 Mixer", "Left Capture Switch", "Left Capture Mixer"}, + {"Out3 Mixer", "Left Mixer Switch", "Left Playback Mixer"}, + {"Out3 Mixer", "Out4 Playback Switch", "Out4 Mixer"}, + {"OUT3", NULL, "Out3 Mixer"}, + + /* out2 */ + {"Right Out2 PGA", NULL, "Right Playback Mixer"}, + {"Left Out2 PGA", NULL, "Left Playback Mixer"}, + {"OUT2L", NULL, "Left Out2 PGA"}, + {"OUT2R", NULL, "Right Out2 PGA"}, + + /* out1 */ + {"Right Out1 PGA", NULL, "Right Playback Mixer"}, + {"Left Out1 PGA", NULL, "Left Playback Mixer"}, + {"OUT1L", NULL, "Left Out1 PGA"}, + {"OUT1R", NULL, "Right Out1 PGA"}, + + /* ADCs */ + {"Left ADC", NULL, "Left Capture Mixer"}, + {"Right ADC", NULL, "Right Capture Mixer"}, + + /* Left capture mixer */ + {"Left Capture Mixer", "L2 Capture Volume", "IN2L"}, + {"Left Capture Mixer", "L3 Capture Volume", "IN3L PGA"}, + {"Left Capture Mixer", "PGA Capture Switch", "Left Mic Mixer"}, + {"Left Capture Mixer", NULL, "Out4 Capture Channel"}, + + /* Right capture mixer */ + {"Right Capture Mixer", "L2 Capture Volume", "IN2R"}, + {"Right Capture Mixer", "L3 Capture Volume", "IN3R PGA"}, + {"Right Capture Mixer", "PGA Capture Switch", "Right Mic Mixer"}, + {"Right Capture Mixer", NULL, "Out4 Capture Channel"}, + + /* L3 Inputs */ + {"IN3L PGA", NULL, "IN3L"}, + {"IN3R PGA", NULL, "IN3R"}, + + /* Left Mic mixer */ + {"Left Mic Mixer", "INN Capture Switch", "IN1LN"}, + {"Left Mic Mixer", "INP Capture Switch", "IN1LP"}, + {"Left Mic Mixer", "IN2 Capture Switch", "IN2L"}, + + /* Right Mic mixer */ + {"Right Mic Mixer", "INN Capture Switch", "IN1RN"}, + {"Right Mic Mixer", "INP Capture Switch", "IN1RP"}, + {"Right Mic Mixer", "IN2 Capture Switch", "IN2R"}, + + /* out 4 capture */ + {"Out4 Capture Channel", NULL, "Out4 Mixer"}, + + /* Beep */ + {"Beep", NULL, "IN3R PGA"}, +}; + +static int wm8350_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8350_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8350_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static int wm8350_add_widgets(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_dapm_new_controls(codec, + wm8350_dapm_widgets, + ARRAY_SIZE(wm8350_dapm_widgets)); + if (ret != 0) { + dev_err(codec->dev, "dapm control register failed\n"); + return ret; + } + + /* set up audio paths */ + ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + if (ret != 0) { + dev_err(codec->dev, "DAPM route register failed\n"); + return ret; + } + + return snd_soc_dapm_new_widgets(codec); +} + +static int wm8350_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 wm8350 *wm8350 = codec->control_data; + u16 fll_4; + + switch (clk_id) { + case WM8350_MCLK_SEL_MCLK: + wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_1, + WM8350_MCLK_SEL); + break; + case WM8350_MCLK_SEL_PLL_MCLK: + case WM8350_MCLK_SEL_PLL_DAC: + case WM8350_MCLK_SEL_PLL_ADC: + case WM8350_MCLK_SEL_PLL_32K: + wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_1, + WM8350_MCLK_SEL); + fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) & + ~WM8350_FLL_CLK_SRC_MASK; + wm8350_codec_write(codec, WM8350_FLL_CONTROL_4, fll_4 | clk_id); + break; + } + + /* MCLK direction */ + if (dir == WM8350_MCLK_DIR_OUT) + wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2, + WM8350_MCLK_DIR); + else + wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2, + WM8350_MCLK_DIR); + + return 0; +} + +static int wm8350_set_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 val; + + switch (div_id) { + case WM8350_ADC_CLKDIV: + val = wm8350_codec_read(codec, WM8350_ADC_DIVIDER) & + ~WM8350_ADC_CLKDIV_MASK; + wm8350_codec_write(codec, WM8350_ADC_DIVIDER, val | div); + break; + case WM8350_DAC_CLKDIV: + val = wm8350_codec_read(codec, WM8350_DAC_CLOCK_CONTROL) & + ~WM8350_DAC_CLKDIV_MASK; + wm8350_codec_write(codec, WM8350_DAC_CLOCK_CONTROL, val | div); + break; + case WM8350_BCLK_CLKDIV: + val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) & + ~WM8350_BCLK_DIV_MASK; + wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div); + break; + case WM8350_OPCLK_CLKDIV: + val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) & + ~WM8350_OPCLK_DIV_MASK; + wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div); + break; + case WM8350_SYS_CLKDIV: + val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) & + ~WM8350_MCLK_DIV_MASK; + wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div); + break; + case WM8350_DACLR_CLKDIV: + val = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) & + ~WM8350_DACLRC_RATE_MASK; + wm8350_codec_write(codec, WM8350_DAC_LR_RATE, val | div); + break; + case WM8350_ADCLR_CLKDIV: + val = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) & + ~WM8350_ADCLRC_RATE_MASK; + wm8350_codec_write(codec, WM8350_ADC_LR_RATE, val | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int wm8350_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) & + ~(WM8350_AIF_BCLK_INV | WM8350_AIF_LRCLK_INV | WM8350_AIF_FMT_MASK); + u16 master = wm8350_codec_read(codec, WM8350_AI_DAC_CONTROL) & + ~WM8350_BCLK_MSTR; + u16 dac_lrc = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) & + ~WM8350_DACLRC_ENA; + u16 adc_lrc = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) & + ~WM8350_ADCLRC_ENA; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + master |= WM8350_BCLK_MSTR; + dac_lrc |= WM8350_DACLRC_ENA; + adc_lrc |= WM8350_ADCLRC_ENA; + 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 |= 0x2 << 8; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x1 << 8; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x3 << 8; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x3 << 8; /* lg not sure which mode */ + 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 |= WM8350_AIF_LRCLK_INV | WM8350_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= WM8350_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= WM8350_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + wm8350_codec_write(codec, WM8350_AI_FORMATING, iface); + wm8350_codec_write(codec, WM8350_AI_DAC_CONTROL, master); + wm8350_codec_write(codec, WM8350_DAC_LR_RATE, dac_lrc); + wm8350_codec_write(codec, WM8350_ADC_LR_RATE, adc_lrc); + return 0; +} + +static int wm8350_pcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *codec_dai) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int master = wm8350_codec_cache_read(codec, WM8350_AI_DAC_CONTROL) & + WM8350_BCLK_MSTR; + int enabled = 0; + + /* Check that the DACs or ADCs are enabled since they are + * required for LRC in master mode. The DACs or ADCs need a + * valid audio path i.e. pin -> ADC or DAC -> pin before + * the LRC will be enabled in master mode. */ + if (!master && cmd != SNDRV_PCM_TRIGGER_START) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + enabled = wm8350_codec_cache_read(codec, WM8350_POWER_MGMT_4) & + (WM8350_ADCR_ENA | WM8350_ADCL_ENA); + } else { + enabled = wm8350_codec_cache_read(codec, WM8350_POWER_MGMT_4) & + (WM8350_DACR_ENA | WM8350_DACL_ENA); + } + + if (!enabled) { + dev_err(codec->dev, + "%s: invalid audio path - no clocks available\n", + __func__); + return -EINVAL; + } + return 0; +} + +static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) & + ~WM8350_AIF_WL_MASK; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x1 << 10; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x2 << 10; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x3 << 10; + break; + } + + wm8350_codec_write(codec, WM8350_AI_FORMATING, iface); + return 0; +} + +static int wm8350_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8350 *wm8350 = codec->control_data; + + if (mute) + wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA); + else + wm8350_clear_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA); + return 0; +} + +/* FLL divisors */ +struct _fll_div { + int div; /* FLL_OUTDIV */ + int n; + int k; + int ratio; /* FLL_FRATIO */ +}; + +/* The size in bits of the fll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static inline int fll_factors(struct _fll_div *fll_div, unsigned int input, + unsigned int output) +{ + u64 Kpart; + unsigned int t1, t2, K, Nmod; + + if (output >= 2815250 && output <= 3125000) + fll_div->div = 0x4; + else if (output >= 5625000 && output <= 6250000) + fll_div->div = 0x3; + else if (output >= 11250000 && output <= 12500000) + fll_div->div = 0x2; + else if (output >= 22500000 && output <= 25000000) + fll_div->div = 0x1; + else { + printk(KERN_ERR "wm8350: fll freq %d out of range\n", output); + return -EINVAL; + } + + if (input > 48000) + fll_div->ratio = 1; + else + fll_div->ratio = 8; + + t1 = output * (1 << (fll_div->div + 1)); + t2 = input * fll_div->ratio; + + fll_div->n = t1 / t2; + Nmod = t1 % t2; + + if (Nmod) { + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + do_div(Kpart, t2); + 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; + fll_div->k = K; + } else + fll_div->k = 0; + + return 0; +} + +static int wm8350_set_fll(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; + struct wm8350 *wm8350 = codec->control_data; + struct _fll_div fll_div; + int ret = 0; + u16 fll_1, fll_4; + + /* power down FLL - we need to do this for reconfiguration */ + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, + WM8350_FLL_ENA | WM8350_FLL_OSC_ENA); + + if (freq_out == 0 || freq_in == 0) + return ret; + + ret = fll_factors(&fll_div, freq_in, freq_out); + if (ret < 0) + return ret; + dev_dbg(wm8350->dev, + "FLL in %d FLL out %d N 0x%x K 0x%x div %d ratio %d", + freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div, + fll_div.ratio); + + /* set up N.K & dividers */ + fll_1 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_1) & + ~(WM8350_FLL_OUTDIV_MASK | WM8350_FLL_RSP_RATE_MASK | 0xc000); + wm8350_codec_write(codec, WM8350_FLL_CONTROL_1, + fll_1 | (fll_div.div << 8) | 0x50); + wm8350_codec_write(codec, WM8350_FLL_CONTROL_2, + (fll_div.ratio << 11) | (fll_div. + n & WM8350_FLL_N_MASK)); + wm8350_codec_write(codec, WM8350_FLL_CONTROL_3, fll_div.k); + fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) & + ~(WM8350_FLL_FRAC | WM8350_FLL_SLOW_LOCK_REF); + wm8350_codec_write(codec, WM8350_FLL_CONTROL_4, + fll_4 | (fll_div.k ? WM8350_FLL_FRAC : 0) | + (fll_div.ratio == 8 ? WM8350_FLL_SLOW_LOCK_REF : 0)); + + /* power FLL on */ + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_OSC_ENA); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_ENA); + + return 0; +} + +static int wm8350_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8350 *wm8350 = codec->control_data; + struct wm8350_data *priv = codec->private_data; + struct wm8350_audio_platform_data *platform = + wm8350->codec.platform_data; + u16 pm1; + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1 | WM8350_VMID_50K | + platform->codec_current_on << 14); + break; + + case SND_SOC_BIAS_PREPARE: + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1); + pm1 &= ~WM8350_VMID_MASK; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1 | WM8350_VMID_50K); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret != 0) + return ret; + + /* Enable the system clock */ + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, + WM8350_SYSCLK_ENA); + + /* mute DAC & outputs */ + wm8350_set_bits(wm8350, WM8350_DAC_MUTE, + WM8350_DAC_MUTE_ENA); + + /* discharge cap memory */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + platform->dis_out1 | + (platform->dis_out2 << 2) | + (platform->dis_out3 << 4) | + (platform->dis_out4 << 6)); + + /* wait for discharge */ + schedule_timeout_interruptible(msecs_to_jiffies + (platform-> + cap_discharge_msecs)); + + /* enable antipop */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + (platform->vmid_s_curve << 8)); + + /* ramp up vmid */ + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + (platform-> + codec_current_charge << 14) | + WM8350_VMID_5K | WM8350_VMIDEN | + WM8350_VBUFEN); + + /* wait for vmid */ + schedule_timeout_interruptible(msecs_to_jiffies + (platform-> + vmid_charge_msecs)); + + /* turn on vmid 300k */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK); + pm1 |= WM8350_VMID_300K | + (platform->codec_current_standby << 14); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1); + + + /* enable analogue bias */ + pm1 |= WM8350_BIASEN; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); + + /* disable antipop */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0); + + } else { + /* turn on vmid 300k and reduce current */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1 | WM8350_VMID_300K | + (platform-> + codec_current_standby << 14)); + + } + break; + + case SND_SOC_BIAS_OFF: + + /* mute DAC & enable outputs */ + wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA); + + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_3, + WM8350_OUT1L_ENA | WM8350_OUT1R_ENA | + WM8350_OUT2L_ENA | WM8350_OUT2R_ENA); + + /* enable anti pop S curve */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + (platform->vmid_s_curve << 8)); + + /* turn off vmid */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~WM8350_VMIDEN; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); + + /* wait */ + schedule_timeout_interruptible(msecs_to_jiffies + (platform-> + vmid_discharge_msecs)); + + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, + (platform->vmid_s_curve << 8) | + platform->dis_out1 | + (platform->dis_out2 << 2) | + (platform->dis_out3 << 4) | + (platform->dis_out4 << 6)); + + /* turn off VBuf and drain */ + pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) & + ~(WM8350_VBUFEN | WM8350_VMID_MASK); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, + pm1 | WM8350_OUTPUT_DRAIN_EN); + + /* wait */ + schedule_timeout_interruptible(msecs_to_jiffies + (platform->drain_msecs)); + + pm1 &= ~WM8350_BIASEN; + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1); + + /* disable anti-pop */ + wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0); + + wm8350_clear_bits(wm8350, WM8350_LOUT1_VOLUME, + WM8350_OUT1L_ENA); + wm8350_clear_bits(wm8350, WM8350_ROUT1_VOLUME, + WM8350_OUT1R_ENA); + wm8350_clear_bits(wm8350, WM8350_LOUT2_VOLUME, + WM8350_OUT2L_ENA); + wm8350_clear_bits(wm8350, WM8350_ROUT2_VOLUME, + WM8350_OUT2R_ENA); + + /* disable clock gen */ + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, + WM8350_SYSCLK_ENA); + + regulator_bulk_disable(ARRAY_SIZE(priv->supplies), + priv->supplies); + break; + } + codec->bias_level = level; + return 0; +} + +static int wm8350_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; + + wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8350_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + wm8350_set_bias_level(codec, SND_SOC_BIAS_ON); + + return 0; +} + +static struct snd_soc_codec *wm8350_codec; + +static int wm8350_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct wm8350 *wm8350; + struct wm8350_data *priv; + int ret; + struct wm8350_output *out1; + struct wm8350_output *out2; + + BUG_ON(!wm8350_codec); + + socdev->codec = wm8350_codec; + codec = socdev->codec; + wm8350 = codec->control_data; + priv = codec->private_data; + + /* Enable the codec */ + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); + + /* Enable robust clocking mode in ADC */ + wm8350_codec_write(codec, WM8350_SECURITY, 0xa7); + wm8350_codec_write(codec, 0xde, 0x13); + wm8350_codec_write(codec, WM8350_SECURITY, 0); + + /* read OUT1 & OUT2 volumes */ + out1 = &priv->out1; + out2 = &priv->out2; + out1->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME) & + WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; + out1->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME) & + WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; + out2->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME) & + WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT; + out2->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME) & + WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT; + wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, 0); + wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, 0); + wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, 0); + wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, 0); + + /* Latch VU bits & mute */ + wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, + WM8350_OUT1_VU | WM8350_OUT1L_MUTE); + wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, + WM8350_OUT2_VU | WM8350_OUT2L_MUTE); + wm8350_set_bits(wm8350, WM8350_ROUT1_VOLUME, + WM8350_OUT1_VU | WM8350_OUT1R_MUTE); + wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME, + WM8350_OUT2_VU | WM8350_OUT2R_MUTE); + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create pcms\n"); + return ret; + } + + wm8350_add_controls(codec); + wm8350_add_widgets(codec); + + wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + return 0; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + return ret; +} + +static int wm8350_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm8350 *wm8350 = codec->control_data; + int ret; + + /* cancel any work waiting to be queued. */ + ret = cancel_delayed_work(&codec->delayed_work); + + /* if there was any work waiting then we run it now and + * wait for its completion */ + if (ret) { + schedule_delayed_work(&codec->delayed_work, 0); + flush_scheduled_work(); + } + + wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF); + + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); + + return 0; +} + +#define WM8350_RATES (SNDRV_PCM_RATE_8000_96000) + +#define WM8350_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8350_dai = { + .name = "WM8350", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8350_RATES, + .formats = WM8350_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8350_RATES, + .formats = WM8350_FORMATS, + }, + .ops = { + .hw_params = wm8350_pcm_hw_params, + .digital_mute = wm8350_mute, + .trigger = wm8350_pcm_trigger, + .set_fmt = wm8350_set_dai_fmt, + .set_sysclk = wm8350_set_dai_sysclk, + .set_pll = wm8350_set_fll, + .set_clkdiv = wm8350_set_clkdiv, + }, +}; +EXPORT_SYMBOL_GPL(wm8350_dai); + +struct snd_soc_codec_device soc_codec_dev_wm8350 = { + .probe = wm8350_probe, + .remove = wm8350_remove, + .suspend = wm8350_suspend, + .resume = wm8350_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8350); + +static int wm8350_codec_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct wm8350_data *priv; + struct snd_soc_codec *codec; + int ret, i; + + if (wm8350->codec.platform_data == NULL) { + dev_err(&pdev->dev, "No audio platform data supplied\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(struct wm8350_data), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(supply_names); i++) + priv->supplies[i].supply = supply_names[i]; + + ret = regulator_bulk_get(wm8350->dev, ARRAY_SIZE(priv->supplies), + priv->supplies); + if (ret != 0) + goto err_priv; + + codec = &priv->codec; + wm8350->codec.codec = codec; + + wm8350_dai.dev = &pdev->dev; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->dev = &pdev->dev; + codec->name = "WM8350"; + codec->owner = THIS_MODULE; + codec->read = wm8350_codec_read; + codec->write = wm8350_codec_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8350_set_bias_level; + codec->dai = &wm8350_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8350_MAX_REGISTER; + codec->private_data = priv; + codec->control_data = wm8350; + + /* Put the codec into reset if it wasn't already */ + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA); + + INIT_DELAYED_WORK(&codec->delayed_work, wm8350_pga_work); + ret = snd_soc_register_codec(codec); + if (ret != 0) + goto err_supply; + + wm8350_codec = codec; + + ret = snd_soc_register_dai(&wm8350_dai); + if (ret != 0) + goto err_codec; + return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err_supply: + regulator_bulk_free(ARRAY_SIZE(priv->supplies), priv->supplies); +err_priv: + kfree(priv); + wm8350_codec = NULL; + return ret; +} + +static int __devexit wm8350_codec_remove(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = wm8350->codec.codec; + struct wm8350_data *priv = codec->private_data; + + snd_soc_unregister_dai(&wm8350_dai); + snd_soc_unregister_codec(codec); + regulator_bulk_free(ARRAY_SIZE(priv->supplies), priv->supplies); + kfree(priv); + wm8350_codec = NULL; + return 0; +} + +static struct platform_driver wm8350_codec_driver = { + .driver = { + .name = "wm8350-codec", + .owner = THIS_MODULE, + }, + .probe = wm8350_codec_probe, + .remove = __devexit_p(wm8350_codec_remove), +}; + +static __init int wm8350_init(void) +{ + return platform_driver_register(&wm8350_codec_driver); +} +module_init(wm8350_init); + +static __exit void wm8350_exit(void) +{ + platform_driver_unregister(&wm8350_codec_driver); +} +module_exit(wm8350_exit); + +MODULE_DESCRIPTION("ASoC WM8350 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8350-codec"); diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h new file mode 100644 index 00000000000..cc2887aa6c3 --- /dev/null +++ b/sound/soc/codecs/wm8350.h @@ -0,0 +1,20 @@ +/* + * wm8350.h - WM8903 audio codec interface + * + * 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. + */ + +#ifndef _WM8350_H +#define _WM8350_H + +#include <sound/soc.h> + +extern struct snd_soc_dai wm8350_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8350; + +#endif diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index d8ca2da8d63..40f8238df71 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -463,7 +463,8 @@ static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai, } static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -585,8 +586,6 @@ struct snd_soc_dai wm8510_dai = { .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, @@ -659,7 +658,7 @@ static int wm8510_init(struct snd_soc_device *socdev) wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); wm8510_add_controls(codec); wm8510_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm8510: failed to register card\n"); goto card_err; @@ -890,6 +889,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8510 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8510); +static int __init wm8510_modinit(void) +{ + return snd_soc_register_dai(&wm8510_dai); +} +module_init(wm8510_modinit); + +static void __exit wm8510_exit(void) +{ + snd_soc_unregister_dai(&wm8510_dai); +} +module_exit(wm8510_exit); + MODULE_DESCRIPTION("ASoC WM8510 driver"); MODULE_AUTHOR("Liam Girdwood"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 627ebfb4209..d004e584529 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -548,13 +548,13 @@ static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, * 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_pcm_hw_params *params, + struct snd_soc_dai *dai) { 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); + u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->id); paifb &= ~WM8580_AIF_LENGTH_MASK; /* bit size */ @@ -574,7 +574,7 @@ static int wm8580_paif_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - wm8580_write(codec, WM8580_PAIF3 + dai->codec_dai->id, paifb); + wm8580_write(codec, WM8580_PAIF3 + dai->id, paifb); return 0; } @@ -798,8 +798,6 @@ struct snd_soc_dai wm8580_dai[] = { }, .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, @@ -818,8 +816,6 @@ struct snd_soc_dai wm8580_dai[] = { }, .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, @@ -873,7 +869,7 @@ static int wm8580_init(struct snd_soc_device *socdev) wm8580_add_controls(codec); wm8580_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm8580: failed to register card\n"); goto card_err; @@ -900,85 +896,85 @@ static struct snd_soc_device *wm8580_socdev; * 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) +static int wm8580_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { 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) { + 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) +static int wm8580_i2c_remove(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); -} +static const struct i2c_device_id wm8580_i2c_id[] = { + { "wm8580", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8580_i2c_id); -/* 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, + .probe = wm8580_i2c_probe, + .remove = wm8580_i2c_remove, + .id_table = wm8580_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8580", - .driver = &wm8580_i2c_driver, -}; +static int wm8580_add_i2c_device(struct platform_device *pdev, + const struct wm8580_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8580_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, "wm8580", 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(&wm8580_i2c_driver); + return -ENODEV; +} #endif static int wm8580_probe(struct platform_device *pdev) @@ -1011,11 +1007,8 @@ static int wm8580_probe(struct platform_device *pdev) #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"); + ret = wm8580_add_i2c_device(pdev, setup); } #else /* Add other interfaces here */ @@ -1034,6 +1027,7 @@ static int wm8580_remove(struct platform_device *pdev) 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(&wm8580_i2c_driver); #endif kfree(codec->private_data); @@ -1048,6 +1042,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8580 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580); +static int __init wm8580_modinit(void) +{ + return snd_soc_register_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); +} +module_init(wm8580_modinit); + +static void __exit wm8580_exit(void) +{ + snd_soc_unregister_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); +} +module_exit(wm8580_exit); + 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 index 589ddaba21d..09e4422f6f2 100644 --- a/sound/soc/codecs/wm8580.h +++ b/sound/soc/codecs/wm8580.h @@ -29,6 +29,7 @@ #define WM8580_CLKSRC_NONE 5 struct wm8580_setup_data { + int i2c_bus; unsigned short i2c_address; }; diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c new file mode 100644 index 00000000000..80b11983e13 --- /dev/null +++ b/sound/soc/codecs/wm8728.c @@ -0,0 +1,585 @@ +/* + * wm8728.c -- WM8728 ALSA SoC Audio driver + * + * 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 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 <sound/tlv.h> + +#include "wm8728.h" + +struct snd_soc_codec_device soc_codec_dev_wm8728; + +/* + * We can't read the WM8728 register space so we cache them instead. + * Note that the defaults here aren't the physical defaults, we latch + * the volume update bits, mute the output and enable infinite zero + * detect. + */ +static const u16 wm8728_reg_defaults[] = { + 0x1ff, + 0x1ff, + 0x001, + 0x100, +}; + +static inline unsigned int wm8728_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > ARRAY_SIZE(wm8728_reg_defaults)); + return cache[reg]; +} + +static inline void wm8728_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > ARRAY_SIZE(wm8728_reg_defaults)); + cache[reg] = value; +} + +/* + * write to the WM8728 register space + */ +static int wm8728_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8728 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8728_write_reg_cache(codec, reg, value); + + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1); + +static const struct snd_kcontrol_new wm8728_snd_controls[] = { + +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8728_DACLVOL, WM8728_DACRVOL, + 0, 255, 0, wm8728_tlv), + +SOC_SINGLE("Deemphasis", WM8728_DACCTL, 1, 1, 0), +}; + +static int wm8728_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8728_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8728_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* + * DAPM controls. + */ +static const struct snd_soc_dapm_widget wm8728_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("VOUTL"), +SND_SOC_DAPM_OUTPUT("VOUTR"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"VOUTL", NULL, "DAC"}, + {"VOUTR", NULL, "DAC"}, +}; + +static int wm8728_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8728_dapm_widgets, + ARRAY_SIZE(wm8728_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int wm8728_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); + + if (mute) + wm8728_write(codec, WM8728_DACCTL, mute_reg | 1); + else + wm8728_write(codec, WM8728_DACCTL, mute_reg & ~1); + + return 0; +} + +static int wm8728_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 dac = wm8728_read_reg_cache(codec, WM8728_DACCTL); + + dac &= ~0x18; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + dac |= 0x10; + break; + case SNDRV_PCM_FORMAT_S24_LE: + dac |= 0x08; + break; + default: + return -EINVAL; + } + + wm8728_write(codec, WM8728_DACCTL, dac); + + return 0; +} + +static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = wm8728_read_reg_cache(codec, WM8728_IFCTL); + + /* Currently only I2S is supported by the driver, though the + * hardware is more flexible. + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 1; + break; + default: + return -EINVAL; + } + + /* The hardware only support full slave mode */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + iface &= ~0x22; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x20; + iface &= ~0x02; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x02; + iface &= ~0x20; + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x22; + break; + default: + return -EINVAL; + } + + wm8728_write(codec, WM8728_IFCTL, iface); + return 0; +} + +static int wm8728_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + int i; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Power everything up... */ + reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); + wm8728_write(codec, WM8728_DACCTL, reg & ~0x4); + + /* ..then sync in the register cache. */ + for (i = 0; i < ARRAY_SIZE(wm8728_reg_defaults); i++) + wm8728_write(codec, i, + wm8728_read_reg_cache(codec, i)); + } + break; + + case SND_SOC_BIAS_OFF: + reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); + wm8728_write(codec, WM8728_DACCTL, reg | 0x4); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8728_RATES (SNDRV_PCM_RATE_8000_192000) + +#define WM8728_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8728_dai = { + .name = "WM8728", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8728_RATES, + .formats = WM8728_FORMATS, + }, + .ops = { + .hw_params = wm8728_hw_params, + .digital_mute = wm8728_mute, + .set_fmt = wm8728_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(wm8728_dai); + +static int wm8728_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; + + wm8728_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8728_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8728_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} + +/* + * initialise the WM8728 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8728_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8728"; + codec->owner = THIS_MODULE; + codec->read = wm8728_read_reg_cache; + codec->write = wm8728_write; + codec->set_bias_level = wm8728_set_bias_level; + codec->dai = &wm8728_dai; + codec->num_dai = 1; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->reg_cache_size = ARRAY_SIZE(wm8728_reg_defaults); + codec->reg_cache = kmemdup(wm8728_reg_defaults, + sizeof(wm8728_reg_defaults), + 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 "wm8728: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + wm8728_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8728_add_controls(codec); + wm8728_add_widgets(codec); + ret = snd_soc_init_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8728: 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 *wm8728_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8728 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ + +static int wm8728_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8728_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8728_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8728\n"); + + return ret; +} + +static int wm8728_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 wm8728_i2c_id[] = { + { "wm8728", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8728_i2c_id); + +static struct i2c_driver wm8728_i2c_driver = { + .driver = { + .name = "WM8728 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8728_i2c_probe, + .remove = wm8728_i2c_remove, + .id_table = wm8728_i2c_id, +}; + +static int wm8728_add_i2c_device(struct platform_device *pdev, + const struct wm8728_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8728_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, "wm8728", 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(&wm8728_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8728_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8728_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8728_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8728\n"); + + return ret; +} + +static int __devexit wm8728_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8728_spi_driver = { + .driver = { + .name = "wm8728", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8728_spi_probe, + .remove = __devexit_p(wm8728_spi_remove), +}; + +static int wm8728_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 wm8728_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8728_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + 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); + + wm8728_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 = wm8728_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8728_spi_write; + ret = spi_register_driver(&wm8728_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 wm8728_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) + wm8728_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(&wm8728_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8728_spi_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8728 = { + .probe = wm8728_probe, + .remove = wm8728_remove, + .suspend = wm8728_suspend, + .resume = wm8728_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8728); + +static int __init wm8728_modinit(void) +{ + return snd_soc_register_dai(&wm8728_dai); +} +module_init(wm8728_modinit); + +static void __exit wm8728_exit(void) +{ + snd_soc_unregister_dai(&wm8728_dai); +} +module_exit(wm8728_exit); + +MODULE_DESCRIPTION("ASoC WM8728 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8728.h b/sound/soc/codecs/wm8728.h new file mode 100644 index 00000000000..d269c132474 --- /dev/null +++ b/sound/soc/codecs/wm8728.h @@ -0,0 +1,30 @@ +/* + * wm8728.h -- WM8728 ASoC codec driver + * + * 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 version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8728_H +#define _WM8728_H + +#define WM8728_DACLVOL 0x00 +#define WM8728_DACRVOL 0x01 +#define WM8728_DACCTL 0x02 +#define WM8728_IFCTL 0x03 + +struct wm8728_setup_data { + int spi; + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8728_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8728; + +#endif diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 7f8a7e36b33..c444b9f2701 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -264,7 +264,8 @@ static inline int get_coeff(int mclk, int rate) } static int wm8731_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -293,7 +294,8 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream, return 0; } -static int wm8731_pcm_prepare(struct snd_pcm_substream *substream) +static int wm8731_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -305,7 +307,8 @@ static int wm8731_pcm_prepare(struct snd_pcm_substream *substream) return 0; } -static void wm8731_shutdown(struct snd_pcm_substream *substream) +static void wm8731_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -461,8 +464,6 @@ struct snd_soc_dai wm8731_dai = { .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, @@ -544,7 +545,7 @@ static int wm8731_init(struct snd_soc_device *socdev) wm8731_add_controls(codec); wm8731_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm8731: failed to register card\n"); goto card_err; @@ -792,6 +793,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8731 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8731); +static int __init wm8731_modinit(void) +{ + return snd_soc_register_dai(&wm8731_dai); +} +module_init(wm8731_modinit); + +static void __exit wm8731_exit(void) +{ + snd_soc_unregister_dai(&wm8731_dai); +} +module_exit(wm8731_exit); + MODULE_DESCRIPTION("ASoC WM8731 driver"); MODULE_AUTHOR("Richard Purdie"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index 9b7296ee5b0..5997fa68e0d 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -614,7 +614,8 @@ static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai, } static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -709,8 +710,6 @@ struct snd_soc_dai wm8750_dai = { .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, @@ -819,7 +818,7 @@ static int wm8750_init(struct snd_soc_device *socdev) wm8750_add_controls(codec); wm8750_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm8750: failed to register card\n"); goto card_err; @@ -1086,6 +1085,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8750 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8750); +static int __init wm8750_modinit(void) +{ + return snd_soc_register_dai(&wm8750_dai); +} +module_init(wm8750_modinit); + +static void __exit wm8750_exit(void) +{ + snd_soc_unregister_dai(&wm8750_dai); +} +module_exit(wm8750_exit); + MODULE_DESCRIPTION("ASoC WM8750 driver"); MODULE_AUTHOR("Liam Girdwood"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index d426eaa2218..6c21b50c937 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -922,7 +922,8 @@ static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai, * 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_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -1155,7 +1156,8 @@ static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai, * 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_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -1323,16 +1325,15 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, + .formats = WM8753_FORMATS}, .capture = { /* dummy for fast DAI switching */ .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = WM8753_RATES, - .formats = WM8753_FORMATS,}, + .formats = WM8753_FORMATS}, .ops = { - .hw_params = wm8753_i2s_hw_params,}, - .dai_ops = { + .hw_params = wm8753_i2s_hw_params, .digital_mute = wm8753_mute, .set_fmt = wm8753_mode1h_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, @@ -1356,8 +1357,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .rates = WM8753_RATES, .formats = WM8753_FORMATS,}, .ops = { - .hw_params = wm8753_pcm_hw_params,}, - .dai_ops = { + .hw_params = wm8753_pcm_hw_params, .digital_mute = wm8753_mute, .set_fmt = wm8753_mode1v_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, @@ -1385,8 +1385,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .rates = WM8753_RATES, .formats = WM8753_FORMATS,}, .ops = { - .hw_params = wm8753_pcm_hw_params,}, - .dai_ops = { + .hw_params = wm8753_pcm_hw_params, .digital_mute = wm8753_mute, .set_fmt = wm8753_mode2_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, @@ -1410,8 +1409,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .rates = WM8753_RATES, .formats = WM8753_FORMATS,}, .ops = { - .hw_params = wm8753_i2s_hw_params,}, - .dai_ops = { + .hw_params = wm8753_i2s_hw_params, .digital_mute = wm8753_mute, .set_fmt = wm8753_mode3_4_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, @@ -1439,8 +1437,7 @@ static const struct snd_soc_dai wm8753_all_dai[] = { .rates = WM8753_RATES, .formats = WM8753_FORMATS,}, .ops = { - .hw_params = wm8753_i2s_hw_params,}, - .dai_ops = { + .hw_params = wm8753_i2s_hw_params, .digital_mute = wm8753_mute, .set_fmt = wm8753_mode3_4_set_dai_fmt, .set_clkdiv = wm8753_set_dai_clkdiv, @@ -1608,7 +1605,7 @@ static int wm8753_init(struct snd_soc_device *socdev) wm8753_add_controls(codec); wm8753_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm8753: failed to register card\n"); goto card_err; @@ -1877,6 +1874,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8753 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8753); +static int __init wm8753_modinit(void) +{ + return snd_soc_register_dais(wm8753_dai, ARRAY_SIZE(wm8753_dai)); +} +module_init(wm8753_modinit); + +static void __exit wm8753_exit(void) +{ + snd_soc_unregister_dais(wm8753_dai, ARRAY_SIZE(wm8753_dai)); +} +module_exit(wm8753_exit); + MODULE_DESCRIPTION("ASoC WM8753 driver"); MODULE_AUTHOR("Liam Girdwood"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 3b326c9b558..6767de10ded 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -138,6 +138,10 @@ struct snd_soc_codec_device soc_codec_dev_wm8900; struct wm8900_priv { + struct snd_soc_codec codec; + + u16 reg_cache[WM8900_MAXREG]; + u32 fll_in; /* FLL input frequency */ u32 fll_out; /* FLL output frequency */ }; @@ -727,7 +731,8 @@ static int wm8900_add_widgets(struct snd_soc_codec *codec) } static int wm8900_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -1117,8 +1122,6 @@ struct snd_soc_dai wm8900_dai = { }, .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, @@ -1283,16 +1286,28 @@ static int wm8900_resume(struct platform_device *pdev) return 0; } -/* - * initialise the WM8900 driver - * register the mixer and dsp interfaces with the kernel - */ -static int wm8900_init(struct snd_soc_device *socdev) +static struct snd_soc_codec *wm8900_codec; + +static int wm8900_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { - struct snd_soc_codec *codec = socdev->codec; - int ret = 0; + struct wm8900_priv *wm8900; + struct snd_soc_codec *codec; unsigned int reg; - struct i2c_client *i2c_client = socdev->codec->control_data; + int ret; + + wm8900 = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL); + if (wm8900 == NULL) + return -ENOMEM; + + codec = &wm8900->codec; + codec->private_data = wm8900; + codec->reg_cache = &wm8900->reg_cache[0]; + codec->reg_cache_size = WM8900_MAXREG; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); codec->name = "WM8900"; codec->owner = THIS_MODULE; @@ -1300,33 +1315,28 @@ static int wm8900_init(struct snd_soc_device *socdev) 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; + codec->hw_write = (hw_write_t)i2c_master_send; + codec->control_data = i2c; + codec->set_bias_level = wm8900_set_bias_level; + codec->dev = &i2c->dev; 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; + dev_err(&i2c->dev, "Device is not a WM8900 - ID %x\n", reg); + ret = -ENODEV; + goto 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); + dev_info(&i2c->dev, "WM8900 revision %d\n", reg); wm8900_reset(codec); + /* Turn the chip on */ + wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + /* Latch the volume update bits */ wm8900_write(codec, WM8900_REG_LINVOL, wm8900_read(codec, WM8900_REG_LINVOL) | 0x100); @@ -1352,160 +1362,98 @@ static int wm8900_init(struct snd_soc_device *socdev) /* 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; -} + wm8900_dai.dev = &i2c->dev; -static struct snd_soc_device *wm8900_socdev; + wm8900_codec = codec; -#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); + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); goto err; } - ret = wm8900_init(socdev); - if (ret < 0) { - dev_err(&adap->dev, "failed to initialise WM8900\n"); - goto err; + ret = snd_soc_register_dai(&wm8900_dai); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register DAI: %d\n", ret); + goto err_codec; } + return ret; +err_codec: + snd_soc_unregister_codec(codec); err: - kfree(codec); - kfree(i2c); + kfree(wm8900); + wm8900_codec = NULL; return ret; } -static int wm8900_i2c_detach(struct i2c_client *client) +static int wm8900_i2c_remove(struct i2c_client *client) { - struct snd_soc_codec *codec = i2c_get_clientdata(client); - i2c_detach_client(client); - kfree(codec->reg_cache); - kfree(client); + snd_soc_unregister_dai(&wm8900_dai); + snd_soc_unregister_codec(wm8900_codec); + + wm8900_set_bias_level(wm8900_codec, SND_SOC_BIAS_OFF); + + wm8900_dai.dev = NULL; + kfree(wm8900_codec->private_data); + wm8900_codec = NULL; + return 0; } -static int wm8900_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8900_codec_probe); -} +static const struct i2c_device_id wm8900_i2c_id[] = { + { "wm8900", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8900_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver wm8900_i2c_driver = { .driver = { - .name = "WM8900 I2C codec", + .name = "WM8900", .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, + .probe = wm8900_i2c_probe, + .remove = wm8900_i2c_remove, + .id_table = wm8900_i2c_id, }; -#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); + if (!wm8900_codec) { + dev_err(&pdev->dev, "I2C client not yet instantiated\n"); + return -ENODEV; + } + codec = wm8900_codec; socdev->codec = codec; - codec->set_bias_level = wm8900_set_bias_level; + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register new PCMs\n"); + goto pcm_err; + } - 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"); + wm8900_add_controls(codec); + wm8900_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register card\n"); + goto card_err; } -#else -#error Non-I2C interfaces not yet supported -#endif + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: return ret; } @@ -1513,17 +1461,9 @@ static int wm8900_probe(struct platform_device *pdev) 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; } @@ -1536,6 +1476,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8900 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8900); +static int __init wm8900_modinit(void) +{ + return i2c_add_driver(&wm8900_i2c_driver); +} +module_init(wm8900_modinit); + +static void __exit wm8900_exit(void) +{ + i2c_del_driver(&wm8900_i2c_driver); +} +module_exit(wm8900_exit); + 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 index ba450d99e90..fd15007d10c 100644 --- a/sound/soc/codecs/wm8900.h +++ b/sound/soc/codecs/wm8900.h @@ -52,12 +52,6 @@ #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; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index ce40d787760..bde74546db4 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -33,19 +33,6 @@ #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 */ @@ -223,6 +210,23 @@ static u16 wm8903_reg_defaults[] = { 0x0000, /* R172 - Analogue Output Bias 0 */ }; +struct wm8903_priv { + struct snd_soc_codec codec; + u16 reg_cache[ARRAY_SIZE(wm8903_reg_defaults)]; + + 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; +}; + + static unsigned int wm8903_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { @@ -360,6 +364,8 @@ static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache) static void wm8903_reset(struct snd_soc_codec *codec) { wm8903_write(codec, WM8903_SW_RESET_AND_ID, 0); + memcpy(codec->reg_cache, wm8903_reg_defaults, + sizeof(wm8903_reg_defaults)); } #define WM8903_OUTPUT_SHORT 0x8 @@ -392,6 +398,7 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, break; default: BUG(); + return -EINVAL; /* Spurious warning from some compilers */ } switch (w->shift) { @@ -403,6 +410,7 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, break; default: BUG(); + return -EINVAL; /* Spurious warning from some compilers */ } if (event & SND_SOC_DAPM_PRE_PMU) { @@ -773,14 +781,14 @@ 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), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 0, 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), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 0, 1, 0), }; static const struct snd_kcontrol_new left_speaker_mixer[] = { @@ -788,7 +796,7 @@ 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), + 0, 1, 0), }; static const struct snd_kcontrol_new right_speaker_mixer[] = { @@ -797,7 +805,7 @@ 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), + 0, 1, 0), }; static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = { @@ -989,6 +997,9 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { + wm8903_write(codec, WM8903_CLOCK_RATES_2, + WM8903_CLK_SYS_ENA); + wm8903_run_sequence(codec, 0); wm8903_sync_reg_cache(codec, codec->reg_cache); @@ -1019,6 +1030,9 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_OFF: wm8903_run_sequence(codec, 32); + reg = wm8903_read(codec, WM8903_CLOCK_RATES_2); + reg &= ~WM8903_CLK_SYS_ENA; + wm8903_write(codec, WM8903_CLOCK_RATES_2, reg); break; } @@ -1257,7 +1271,8 @@ static struct { { 0, 0 }, }; -static int wm8903_startup(struct snd_pcm_substream *substream) +static int wm8903_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -1298,7 +1313,8 @@ static int wm8903_startup(struct snd_pcm_substream *substream) return 0; } -static void wm8903_shutdown(struct snd_pcm_substream *substream) +static void wm8903_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -1317,7 +1333,8 @@ static void wm8903_shutdown(struct snd_pcm_substream *substream) } static int wm8903_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -1515,8 +1532,6 @@ struct snd_soc_dai wm8903_dai = { .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 @@ -1560,39 +1575,48 @@ static int wm8903_resume(struct platform_device *pdev) return 0; } -/* - * initialise the WM8903 driver - * register the mixer and dsp interfaces with the kernel - */ -static int wm8903_init(struct snd_soc_device *socdev) +static struct snd_soc_codec *wm8903_codec; + +static int wm8903_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { - struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c = codec->control_data; - int ret = 0; + struct wm8903_priv *wm8903; + struct snd_soc_codec *codec; + int ret; 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; - } + wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL); + if (wm8903 == NULL) + return -ENOMEM; + + codec = &wm8903->codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->dev = &i2c->dev; codec->name = "WM8903"; codec->owner = THIS_MODULE; codec->read = wm8903_read; codec->write = wm8903_write; + codec->hw_write = (hw_write_t)i2c_master_send; 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; + codec->reg_cache_size = ARRAY_SIZE(wm8903->reg_cache); + codec->reg_cache = &wm8903->reg_cache[0]; + codec->private_data = wm8903; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + 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; } val = wm8903_read(codec, WM8903_REVISION_NUMBER); @@ -1601,16 +1625,6 @@ static int wm8903_init(struct snd_soc_device *socdev) 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); @@ -1645,47 +1659,45 @@ static int wm8903_init(struct snd_soc_device *socdev) 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; + wm8903_dai.dev = &i2c->dev; + wm8903_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dai(&wm8903_dai); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register DAI: %d\n", ret); + goto err_codec; } return ret; -card_err: - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); -pcm_err: - kfree(codec->reg_cache); +err_codec: + snd_soc_unregister_codec(codec); +err: + wm8903_codec = NULL; + kfree(wm8903); return ret; } -static struct snd_soc_device *wm8903_socdev; - -static int wm8903_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static int wm8903_i2c_remove(struct i2c_client *client) { - struct snd_soc_device *socdev = wm8903_socdev; - struct snd_soc_codec *codec = socdev->codec; - int ret; + struct snd_soc_codec *codec = i2c_get_clientdata(client); - i2c_set_clientdata(i2c, codec); - codec->control_data = i2c; + snd_soc_unregister_dai(&wm8903_dai); + snd_soc_unregister_codec(codec); - ret = wm8903_init(socdev); - if (ret < 0) - dev_err(&i2c->dev, "Device initialisation failed\n"); + wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); - return ret; -} + kfree(codec->private_data); + + wm8903_codec = NULL; + wm8903_dai.dev = NULL; -static int wm8903_i2c_remove(struct i2c_client *client) -{ - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); return 0; } @@ -1709,75 +1721,37 @@ static struct i2c_driver wm8903_i2c_driver = { 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; + if (!wm8903_codec) { + dev_err(&pdev->dev, "I2C device not yet probed\n"); + goto err; } - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) - return -ENOMEM; + socdev->codec = wm8903_codec; - wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL); - if (wm8903 == NULL) { - ret = -ENOMEM; - goto err_codec; + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create pcms\n"); + goto err; } - 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; + wm8903_add_controls(socdev->codec); + wm8903_add_widgets(socdev->codec); - 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; - } + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "wm8903: failed to register card\n"); + goto card_err; } return ret; -err_adapter: - i2c_del_driver(&wm8903_i2c_driver); -err_priv: - kfree(codec->private_data); -err_codec: - kfree(codec); +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +err: return ret; } @@ -1792,10 +1766,6 @@ static int wm8903_remove(struct platform_device *pdev) 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; } @@ -1808,6 +1778,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8903 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8903); +static int __init wm8903_modinit(void) +{ + return i2c_add_driver(&wm8903_i2c_driver); +} +module_init(wm8903_modinit); + +static void __exit wm8903_exit(void) +{ + i2c_del_driver(&wm8903_i2c_driver); +} +module_exit(wm8903_exit); + 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 index cec622f2f66..0ea27e2b996 100644 --- a/sound/soc/codecs/wm8903.h +++ b/sound/soc/codecs/wm8903.h @@ -18,11 +18,6 @@ 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 diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index f41a578ddd4..88ead7f8dd9 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -541,7 +541,8 @@ static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai, } static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -634,8 +635,6 @@ struct snd_soc_dai wm8971_dai = { .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, @@ -748,7 +747,7 @@ static int wm8971_init(struct snd_soc_device *socdev) wm8971_add_controls(codec); wm8971_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm8971: failed to register card\n"); goto card_err; @@ -936,6 +935,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8971 = { EXPORT_SYMBOL_GPL(soc_codec_dev_wm8971); +static int __init wm8971_modinit(void) +{ + return snd_soc_register_dai(&wm8971_dai); +} +module_init(wm8971_modinit); + +static void __exit wm8971_exit(void) +{ + snd_soc_unregister_dai(&wm8971_dai); +} +module_exit(wm8971_exit); + MODULE_DESCRIPTION("ASoC WM8971 driver"); MODULE_AUTHOR("Lab126"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 572d22b0880..5b5afc14447 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -106,6 +106,7 @@ static const u16 wm8990_reg[] = { 0x0008, /* R60 - PLL1 */ 0x0031, /* R61 - PLL2 */ 0x0026, /* R62 - PLL3 */ + 0x0000, /* R63 - Driver internal */ }; /* @@ -126,10 +127,9 @@ 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) + /* Reset register and reserved registers are uncached */ + if (reg == 0 || reg > ARRAY_SIZE(wm8990_reg) - 1) return; cache[reg] = value; @@ -1172,7 +1172,8 @@ static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai, * 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_pcm_hw_params *params, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; @@ -1222,8 +1223,14 @@ static int wm8990_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: break; + case SND_SOC_BIAS_PREPARE: + /* VMID=2*50k */ + val = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_1) & + ~WM8990_VMID_MODE_MASK; + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x2); break; + case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Enable all output discharge bits */ @@ -1272,10 +1279,17 @@ static int wm8990_set_bias_level(struct snd_soc_codec *codec, /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ wm8990_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN); - } else { - /* ON -> standby */ + /* Enable workaround for ADC clocking issue. */ + wm8990_write(codec, WM8990_EXT_ACCESS_ENA, 0x2); + wm8990_write(codec, WM8990_EXT_CTL1, 0xa003); + wm8990_write(codec, WM8990_EXT_ACCESS_ENA, 0); } + + /* VMID=2*250k */ + val = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_1) & + ~WM8990_VMID_MODE_MASK; + wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x4); break; case SND_SOC_BIAS_OFF: @@ -1349,8 +1363,7 @@ struct snd_soc_dai wm8990_dai = { .rates = WM8990_RATES, .formats = WM8990_FORMATS,}, .ops = { - .hw_params = wm8990_hw_params,}, - .dai_ops = { + .hw_params = wm8990_hw_params, .digital_mute = wm8990_mute, .set_fmt = wm8990_set_dai_fmt, .set_clkdiv = wm8990_set_dai_clkdiv, @@ -1449,7 +1462,7 @@ static int wm8990_init(struct snd_soc_device *socdev) wm8990_add_controls(codec); wm8990_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm8990: failed to register card\n"); goto card_err; @@ -1630,6 +1643,18 @@ struct snd_soc_codec_device soc_codec_dev_wm8990 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8990); +static int __init wm8990_modinit(void) +{ + return snd_soc_register_dai(&wm8990_dai); +} +module_init(wm8990_modinit); + +static void __exit wm8990_exit(void) +{ + snd_soc_unregister_dai(&wm8990_dai); +} +module_exit(wm8990_exit); + 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 index 0e192f3b078..7114ddc88b4 100644 --- a/sound/soc/codecs/wm8990.h +++ b/sound/soc/codecs/wm8990.h @@ -80,8 +80,8 @@ #define WM8990_PLL3 0x3E #define WM8990_INTDRIVBITS 0x3F -#define WM8990_REGISTER_COUNT 60 -#define WM8990_MAX_REGISTER 0x3F +#define WM8990_EXT_ACCESS_ENA 0x75 +#define WM8990_EXT_CTL1 0x7a /* * Field Definitions. diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index ffb471e420e..af83d629078 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -487,7 +487,8 @@ static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } -static int ac97_prepare(struct snd_pcm_substream *substream) +static int ac97_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -507,7 +508,8 @@ static int ac97_prepare(struct snd_pcm_substream *substream) return ac97_write(codec, reg, runtime->rate); } -static int ac97_aux_prepare(struct snd_pcm_substream *substream) +static int ac97_aux_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -533,7 +535,7 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream) struct snd_soc_dai wm9712_dai[] = { { .name = "AC97 HiFi", - .type = SND_SOC_DAI_AC97_BUS, + .ac97_control = 1, .playback = { .stream_name = "HiFi Playback", .channels_min = 1, @@ -688,7 +690,7 @@ static int wm9712_soc_probe(struct platform_device *pdev) ret = wm9712_reset(codec, 0); if (ret < 0) { - printk(KERN_ERR "AC97 link error\n"); + printk(KERN_ERR "Failed to reset WM9712: AC97 link error\n"); goto reset_err; } @@ -698,7 +700,7 @@ static int wm9712_soc_probe(struct platform_device *pdev) wm9712_set_bias_level(codec, SND_SOC_BIAS_STANDBY); wm9712_add_controls(codec); wm9712_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) { printk(KERN_ERR "wm9712: failed to register card\n"); goto reset_err; diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 945b32ed988..f3ca8aaf013 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -928,11 +928,10 @@ static int wm9713_set_dai_fmt(struct snd_soc_dai *codec_dai, } static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = dai->codec; u16 reg = ac97_read(codec, AC97_CENTER_LFE_MASTER) & 0xfff3; switch (params_format(params)) { @@ -954,11 +953,10 @@ static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } -static void wm9713_voiceshutdown(struct snd_pcm_substream *substream) +static void wm9713_voiceshutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; + struct snd_soc_codec *codec = dai->codec; u16 status; /* Gracefully shut down the voice interface. */ @@ -969,12 +967,11 @@ static void wm9713_voiceshutdown(struct snd_pcm_substream *substream) ac97_write(codec, AC97_EXTENDED_MID, status); } -static int ac97_hifi_prepare(struct snd_pcm_substream *substream) +static int ac97_hifi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + struct snd_soc_codec *codec = dai->codec; 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; @@ -989,12 +986,11 @@ static int ac97_hifi_prepare(struct snd_pcm_substream *substream) return ac97_write(codec, reg, runtime->rate); } -static int ac97_aux_prepare(struct snd_pcm_substream *substream) +static int ac97_aux_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { + struct snd_soc_codec *codec = dai->codec; 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); @@ -1028,7 +1024,7 @@ static int ac97_aux_prepare(struct snd_pcm_substream *substream) struct snd_soc_dai wm9713_dai[] = { { .name = "AC97 HiFi", - .type = SND_SOC_DAI_AC97_BUS, + .ac97_control = 1, .playback = { .stream_name = "HiFi Playback", .channels_min = 1, @@ -1042,8 +1038,7 @@ struct snd_soc_dai wm9713_dai[] = { .rates = WM9713_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, .ops = { - .prepare = ac97_hifi_prepare,}, - .dai_ops = { + .prepare = ac97_hifi_prepare, .set_clkdiv = wm9713_set_dai_clkdiv, .set_pll = wm9713_set_dai_pll,}, }, @@ -1056,8 +1051,7 @@ struct snd_soc_dai wm9713_dai[] = { .rates = WM9713_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, .ops = { - .prepare = ac97_aux_prepare,}, - .dai_ops = { + .prepare = ac97_aux_prepare, .set_clkdiv = wm9713_set_dai_clkdiv, .set_pll = wm9713_set_dai_pll,}, }, @@ -1077,8 +1071,7 @@ struct snd_soc_dai wm9713_dai[] = { .formats = WM9713_PCM_FORMATS,}, .ops = { .hw_params = wm9713_pcm_hw_params, - .shutdown = wm9713_voiceshutdown,}, - .dai_ops = { + .shutdown = wm9713_voiceshutdown, .set_clkdiv = wm9713_set_dai_clkdiv, .set_pll = wm9713_set_dai_pll, .set_fmt = wm9713_set_dai_fmt, @@ -1097,6 +1090,8 @@ int wm9713_reset(struct snd_soc_codec *codec, int try_warm) } soc_ac97_ops.reset(codec->ac97); + if (soc_ac97_ops.warm_reset) + soc_ac97_ops.warm_reset(codec->ac97); if (ac97_read(codec, 0) != wm9713_reg[0]) return -EIO; return 0; @@ -1240,7 +1235,7 @@ static int wm9713_soc_probe(struct platform_device *pdev) wm9713_reset(codec, 0); ret = wm9713_reset(codec, 1); if (ret < 0) { - printk(KERN_ERR "AC97 link error\n"); + printk(KERN_ERR "Failed to reset WM9713: AC97 link error\n"); goto reset_err; } @@ -1252,7 +1247,7 @@ static int wm9713_soc_probe(struct platform_device *pdev) wm9713_add_controls(codec); wm9713_add_widgets(codec); - ret = snd_soc_register_card(socdev); + ret = snd_soc_init_card(socdev); if (ret < 0) goto reset_err; return 0; @@ -1288,7 +1283,6 @@ static int wm9713_soc_remove(struct platform_device *pdev) snd_soc_free_ac97_codec(codec); kfree(codec->private_data); kfree(codec->reg_cache); - kfree(codec->dai); kfree(codec); return 0; } |