diff options
Diffstat (limited to 'sound/soc')
91 files changed, 15961 insertions, 1703 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index f743530..4dfda66 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -5,6 +5,7 @@ menuconfig SND_SOC tristate "ALSA for SoC audio support" select SND_PCM + select AC97_BUS if SND_SOC_AC97_BUS ---help--- If you want ASoC support, you should say Y here and also to the @@ -31,6 +32,7 @@ source "sound/soc/sh/Kconfig" source "sound/soc/fsl/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/omap/Kconfig" +source "sound/soc/blackfin/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 933a66d..d849349 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -2,4 +2,4 @@ snd-soc-core-objs := soc-core.o soc-dapm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o obj-$(CONFIG_SND_SOC) += codecs/ at32/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ -obj-$(CONFIG_SND_SOC) += omap/ au1x/ +obj-$(CONFIG_SND_SOC) += omap/ au1x/ blackfin/ diff --git a/sound/soc/at32/playpaq_wm8510.c b/sound/soc/at32/playpaq_wm8510.c index 3f32621..98a2d58 100644 --- a/sound/soc/at32/playpaq_wm8510.c +++ b/sound/soc/at32/playpaq_wm8510.c @@ -377,6 +377,7 @@ static struct snd_soc_machine snd_soc_machine_playpaq = { static struct wm8510_setup_data playpaq_wm8510_setup = { + .i2c_bus = 0, .i2c_address = 0x1a, }; @@ -405,7 +406,6 @@ static int __init playpaq_asoc_init(void) ssc = ssc_request(0); if (IS_ERR(ssc)) { ret = PTR_ERR(ssc); - ssc = NULL; goto err_ssc; } ssc_p->ssc = ssc; @@ -476,10 +476,7 @@ err_pll0: _gclk0 = NULL; } err_gclk0: - if (ssc != NULL) { - ssc_free(ssc); - ssc = NULL; - } + ssc_free(ssc); err_ssc: return ret; } diff --git a/sound/soc/at91/Kconfig b/sound/soc/at91/Kconfig index 9051865..85a8832 100644 --- a/sound/soc/at91/Kconfig +++ b/sound/soc/at91/Kconfig @@ -8,20 +8,3 @@ config SND_AT91_SOC config SND_AT91_SOC_SSC tristate - -config SND_AT91_SOC_ETI_B1_WM8731 - tristate "SoC Audio support for WM8731-based Endrelia ETI-B1 boards" - depends on SND_AT91_SOC && (MACH_ETI_B1 || MACH_ETI_C1) - select SND_AT91_SOC_SSC - select SND_SOC_WM8731 - help - Say Y if you want to add support for SoC audio on WM8731-based - Endrelia Technologies Inc ETI-B1 or ETI-C1 boards. - -config SND_AT91_SOC_ETI_SLAVE - bool "Run codec in slave Mode on Endrelia boards" - depends on SND_AT91_SOC_ETI_B1_WM8731 - default n - help - Say Y if you want to run with the AT91 SSC generating the BCLK - and LRC signals on Endrelia boards. diff --git a/sound/soc/at91/Makefile b/sound/soc/at91/Makefile index f23da17..b817f11 100644 --- a/sound/soc/at91/Makefile +++ b/sound/soc/at91/Makefile @@ -4,8 +4,3 @@ snd-soc-at91-ssc-objs := at91-ssc.o obj-$(CONFIG_SND_AT91_SOC) += snd-soc-at91.o obj-$(CONFIG_SND_AT91_SOC_SSC) += snd-soc-at91-ssc.o - -# AT91 Machine Support -snd-soc-eti-b1-wm8731-objs := eti_b1_wm8731.o - -obj-$(CONFIG_SND_AT91_SOC_ETI_B1_WM8731) += snd-soc-eti-b1-wm8731.o diff --git a/sound/soc/at91/at91-ssc.c b/sound/soc/at91/at91-ssc.c index 5d44515..1b61cc4 100644 --- a/sound/soc/at91/at91-ssc.c +++ b/sound/soc/at91/at91-ssc.c @@ -5,7 +5,7 @@ * Endrelia Technologies Inc. * * Based on pxa2xx Platform drivers by - * Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -408,7 +408,7 @@ static int at91_ssc_hw_params(struct snd_pcm_substream *substream, dma_params->pdc_xfer_size = 4; break; default: - printk(KERN_WARNING "at91-ssc: unsupported PCM format"); + printk(KERN_WARNING "at91-ssc: unsupported PCM format\n"); return -EINVAL; } diff --git a/sound/soc/at91/eti_b1_wm8731.c b/sound/soc/at91/eti_b1_wm8731.c deleted file mode 100644 index b81d6b2..0000000 --- a/sound/soc/at91/eti_b1_wm8731.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - * eti_b1_wm8731 -- SoC audio for AT91RM9200-based Endrelia ETI_B1 board. - * - * Author: Frank Mandarino <fmandarino@endrelia.com> - * Endrelia Technologies Inc. - * Created: Mar 29, 2006 - * - * Based on corgi.c by: - * - * Copyright 2005 Wolfson Microelectronics PLC. - * Copyright 2005 Openedhand Ltd. - * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> - * Richard Purdie <richard@openedhand.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/kernel.h> -#include <linux/clk.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/soc.h> -#include <sound/soc-dapm.h> - -#include <mach/hardware.h> -#include <mach/gpio.h> - -#include "../codecs/wm8731.h" -#include "at91-pcm.h" -#include "at91-ssc.h" - -#if 0 -#define DBG(x...) printk(KERN_INFO "eti_b1_wm8731: " x) -#else -#define DBG(x...) -#endif - -static struct clk *pck1_clk; -static struct clk *pllb_clk; - - -static int eti_b1_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - int ret; - - /* cpu clock is the AT91 master clock sent to the SSC */ - ret = snd_soc_dai_set_sysclk(cpu_dai, AT91_SYSCLK_MCK, - 60000000, SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - /* codec system clock is supplied by PCK1, set to 12MHz */ - ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, - 12000000, SND_SOC_CLOCK_IN); - if (ret < 0) - return ret; - - /* Start PCK1 clock. */ - clk_enable(pck1_clk); - DBG("pck1 started\n"); - - return 0; -} - -static void eti_b1_shutdown(struct snd_pcm_substream *substream) -{ - /* Stop PCK1 clock. */ - clk_disable(pck1_clk); - DBG("pck1 stopped\n"); -} - -static int eti_b1_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - int ret; - -#ifdef CONFIG_SND_AT91_SOC_ETI_SLAVE - unsigned int rate; - int cmr_div, period; - - /* set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); - if (ret < 0) - return ret; - - /* set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); - if (ret < 0) - return ret; - - /* - * The SSC clock dividers depend on the sample rate. The CMR.DIV - * field divides the system master clock MCK to drive the SSC TK - * signal which provides the codec BCLK. The TCMR.PERIOD and - * RCMR.PERIOD fields further divide the BCLK signal to drive - * the SSC TF and RF signals which provide the codec DACLRC and - * ADCLRC clocks. - * - * The dividers were determined through trial and error, where a - * CMR.DIV value is chosen such that the resulting BCLK value is - * divisible, or almost divisible, by (2 * sample rate), and then - * the TCMR.PERIOD or RCMR.PERIOD is BCLK / (2 * sample rate) - 1. - */ - rate = params_rate(params); - - switch (rate) { - case 8000: - cmr_div = 25; /* BCLK = 60MHz/(2*25) = 1.2MHz */ - period = 74; /* LRC = BCLK/(2*(74+1)) = 8000Hz */ - break; - case 32000: - cmr_div = 7; /* BCLK = 60MHz/(2*7) ~= 4.28571428MHz */ - period = 66; /* LRC = BCLK/(2*(66+1)) = 31982.942Hz */ - break; - case 48000: - cmr_div = 13; /* BCLK = 60MHz/(2*13) ~= 2.3076923MHz */ - period = 23; /* LRC = BCLK/(2*(23+1)) = 48076.923Hz */ - break; - default: - printk(KERN_WARNING "unsupported rate %d on ETI-B1 board\n", rate); - return -EINVAL; - } - - /* set the MCK divider for BCLK */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, AT91SSC_CMR_DIV, cmr_div); - if (ret < 0) - return ret; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - /* set the BCLK divider for DACLRC */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, - AT91SSC_TCMR_PERIOD, period); - } else { - /* set the BCLK divider for ADCLRC */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, - AT91SSC_RCMR_PERIOD, period); - } - if (ret < 0) - return ret; - -#else /* CONFIG_SND_AT91_SOC_ETI_SLAVE */ - /* - * Codec in Master Mode. - */ - - /* set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - - /* set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); - if (ret < 0) - return ret; - -#endif /* CONFIG_SND_AT91_SOC_ETI_SLAVE */ - - return 0; -} - -static struct snd_soc_ops eti_b1_ops = { - .startup = eti_b1_startup, - .hw_params = eti_b1_hw_params, - .shutdown = eti_b1_shutdown, -}; - - -static const struct snd_soc_dapm_widget eti_b1_dapm_widgets[] = { - SND_SOC_DAPM_MIC("Int Mic", NULL), - SND_SOC_DAPM_SPK("Ext Spk", NULL), -}; - -static const struct snd_soc_dapm_route intercon[] = { - - /* speaker connected to LHPOUT */ - {"Ext Spk", NULL, "LHPOUT"}, - - /* mic is connected to Mic Jack, with WM8731 Mic Bias */ - {"MICIN", NULL, "Mic Bias"}, - {"Mic Bias", NULL, "Int Mic"}, -}; - -/* - * Logic for a wm8731 as connected on a Endrelia ETI-B1 board. - */ -static int eti_b1_wm8731_init(struct snd_soc_codec *codec) -{ - DBG("eti_b1_wm8731_init() called\n"); - - /* Add specific widgets */ - snd_soc_dapm_new_controls(codec, eti_b1_dapm_widgets, - ARRAY_SIZE(eti_b1_dapm_widgets)); - - /* Set up specific audio path interconnects */ - snd_soc_dapm_add_route(codec, intercon, ARRAY_SIZE(intercon)); - - /* not connected */ - snd_soc_dapm_disable_pin(codec, "RLINEIN"); - snd_soc_dapm_disable_pin(codec, "LLINEIN"); - - /* always connected */ - snd_soc_dapm_enable_pin(codec, "Int Mic"); - snd_soc_dapm_enable_pin(codec, "Ext Spk"); - - snd_soc_dapm_sync(codec); - - return 0; -} - -static struct snd_soc_dai_link eti_b1_dai = { - .name = "WM8731", - .stream_name = "WM8731 PCM", - .cpu_dai = &at91_ssc_dai[1], - .codec_dai = &wm8731_dai, - .init = eti_b1_wm8731_init, - .ops = &eti_b1_ops, -}; - -static struct snd_soc_machine snd_soc_machine_eti_b1 = { - .name = "ETI_B1_WM8731", - .dai_link = &eti_b1_dai, - .num_links = 1, -}; - -static struct wm8731_setup_data eti_b1_wm8731_setup = { - .i2c_address = 0x1a, -}; - -static struct snd_soc_device eti_b1_snd_devdata = { - .machine = &snd_soc_machine_eti_b1, - .platform = &at91_soc_platform, - .codec_dev = &soc_codec_dev_wm8731, - .codec_data = &eti_b1_wm8731_setup, -}; - -static struct platform_device *eti_b1_snd_device; - -static int __init eti_b1_init(void) -{ - int ret; - struct at91_ssc_periph *ssc = eti_b1_dai.cpu_dai->private_data; - - if (!request_mem_region(AT91RM9200_BASE_SSC1, SZ_16K, "soc-audio")) { - DBG("SSC1 memory region is busy\n"); - return -EBUSY; - } - - ssc->base = ioremap(AT91RM9200_BASE_SSC1, SZ_16K); - if (!ssc->base) { - DBG("SSC1 memory ioremap failed\n"); - ret = -ENOMEM; - goto fail_release_mem; - } - - ssc->pid = AT91RM9200_ID_SSC1; - - eti_b1_snd_device = platform_device_alloc("soc-audio", -1); - if (!eti_b1_snd_device) { - DBG("platform device allocation failed\n"); - ret = -ENOMEM; - goto fail_io_unmap; - } - - platform_set_drvdata(eti_b1_snd_device, &eti_b1_snd_devdata); - eti_b1_snd_devdata.dev = &eti_b1_snd_device->dev; - - ret = platform_device_add(eti_b1_snd_device); - if (ret) { - DBG("platform device add failed\n"); - platform_device_put(eti_b1_snd_device); - goto fail_io_unmap; - } - - at91_set_A_periph(AT91_PIN_PB6, 0); /* TF1 */ - at91_set_A_periph(AT91_PIN_PB7, 0); /* TK1 */ - at91_set_A_periph(AT91_PIN_PB8, 0); /* TD1 */ - at91_set_A_periph(AT91_PIN_PB9, 0); /* RD1 */ -/* at91_set_A_periph(AT91_PIN_PB10, 0);*/ /* RK1 */ - at91_set_A_periph(AT91_PIN_PB11, 0); /* RF1 */ - - /* - * Set PCK1 parent to PLLB and its rate to 12 Mhz. - */ - pllb_clk = clk_get(NULL, "pllb"); - pck1_clk = clk_get(NULL, "pck1"); - - clk_set_parent(pck1_clk, pllb_clk); - clk_set_rate(pck1_clk, 12000000); - - DBG("MCLK rate %luHz\n", clk_get_rate(pck1_clk)); - - /* assign the GPIO pin to PCK1 */ - at91_set_B_periph(AT91_PIN_PA24, 0); - -#ifdef CONFIG_SND_AT91_SOC_ETI_SLAVE - printk(KERN_INFO "eti_b1_wm8731: Codec in Slave Mode\n"); -#else - printk(KERN_INFO "eti_b1_wm8731: Codec in Master Mode\n"); -#endif - return ret; - -fail_io_unmap: - iounmap(ssc->base); -fail_release_mem: - release_mem_region(AT91RM9200_BASE_SSC1, SZ_16K); - return ret; -} - -static void __exit eti_b1_exit(void) -{ - struct at91_ssc_periph *ssc = eti_b1_dai.cpu_dai->private_data; - - clk_put(pck1_clk); - clk_put(pllb_clk); - - platform_device_unregister(eti_b1_snd_device); - - iounmap(ssc->base); - release_mem_region(AT91RM9200_BASE_SSC1, SZ_16K); -} - -module_init(eti_b1_init); -module_exit(eti_b1_exit); - -/* Module information */ -MODULE_AUTHOR("Frank Mandarino <fmandarino@endrelia.com>"); -MODULE_DESCRIPTION("ALSA SoC ETI-B1-WM8731"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig new file mode 100644 index 0000000..dc00620 --- /dev/null +++ b/sound/soc/blackfin/Kconfig @@ -0,0 +1,101 @@ +config SND_BF5XX_I2S + tristate "SoC I2S Audio for the ADI BF5xx chip" + depends on BLACKFIN && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the Blackfin SPORT (synchronous serial ports) interface in I2S + mode (supports single stereo In/Out). + You will also need to select the audio interfaces to support below. + +config SND_BF5XX_SOC_SSM2602 + tristate "SoC SSM2602 Audio support for BF52x ezkit" + depends on SND_BF5XX_I2S + select SND_BF5XX_SOC_I2S + select SND_SOC_SSM2602 + select I2C + select I2C_BLACKFIN_TWI + help + Say Y if you want to add support for SoC audio on BF527-EZKIT. + +config SND_BF5XX_SOC_AD73311 + tristate "SoC AD73311 Audio support for Blackfin" + depends on SND_BF5XX_I2S + select SND_BF5XX_SOC_I2S + select SND_SOC_AD73311 + help + Say Y if you want to add support for AD73311 codec on Blackfin. + +config SND_BFIN_AD73311_SE + int "PF pin for AD73311L Chip Select" + depends on SND_BF5XX_SOC_AD73311 + default 4 + help + Enter the GPIO used to control AD73311's SE pin. Acceptable + values are 0 to 7 + +config SND_BF5XX_AC97 + tristate "SoC AC97 Audio for the ADI BF5xx chip" + depends on BLACKFIN && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the Blackfin SPORT (synchronous serial ports) interface in slot 16 + mode (pseudo AC97 interface). + You will also need to select the audio interfaces to support below. + + Note: + AC97 codecs which do not implment the slot-16 mode will not function + properly with this driver. This driver is known to work with the + Analog Devices line of AC97 codecs. + +config SND_MMAP_SUPPORT + bool "Enable MMAP Support" + depends on SND_BF5XX_AC97 + default y + help + Say y if you want AC97 driver to support mmap mode. + We introduce an intermediate buffer to simulate mmap. + +config SND_BF5XX_SOC_SPORT + tristate + +config SND_BF5XX_SOC_I2S + tristate + select SND_BF5XX_SOC_SPORT + +config SND_BF5XX_SOC_AC97 + tristate + select AC97_BUS + select SND_SOC_AC97_BUS + select SND_BF5XX_SOC_SPORT + +config SND_BF5XX_SOC_AD1980 + tristate "SoC AD1980/1 Audio support for BF5xx" + depends on SND_BF5XX_AC97 + select SND_BF5XX_SOC_AC97 + select SND_SOC_AD1980 + help + Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT. + +config SND_BF5XX_SPORT_NUM + int "Set a SPORT for Sound chip" + depends on (SND_BF5XX_I2S || SND_BF5XX_AC97) + range 0 3 if BF54x + range 0 1 if (BF53x || BF561) + default 0 + help + Set the correct SPORT for sound chip. + +config SND_BF5XX_HAVE_COLD_RESET + bool "BOARD has COLD Reset GPIO" + depends on SND_BF5XX_AC97 + default y if BFIN548_EZKIT + default n if !BFIN548_EZKIT + +config SND_BF5XX_RESET_GPIO_NUM + int "Set a GPIO for cold reset" + depends on SND_BF5XX_HAVE_COLD_RESET + range 0 159 + default 19 if BFIN548_EZKIT + default 5 if BFIN537_STAMP + help + Set the correct GPIO for RESET the sound chip. diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile new file mode 100644 index 0000000..97bb37a --- /dev/null +++ b/sound/soc/blackfin/Makefile @@ -0,0 +1,21 @@ +# Blackfin Platform Support +snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o +snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o +snd-soc-bf5xx-sport-objs := bf5xx-sport.o +snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o +snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o + +obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o +obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o +obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o +obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o +obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o + +# Blackfin Machine Support +snd-ad1980-objs := bf5xx-ad1980.o +snd-ssm2602-objs := bf5xx-ssm2602.o +snd-ad73311-objs := bf5xx-ad73311.o + +obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o +obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o +obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.c b/sound/soc/blackfin/bf5xx-ac97-pcm.c new file mode 100644 index 0000000..25e50d2 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ac97-pcm.c @@ -0,0 +1,457 @@ +/* + * File: sound/soc/blackfin/bf5xx-ac97-pcm.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: DMA Driver for AC97 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/dma.h> + +#include "bf5xx-ac97-pcm.h" +#include "bf5xx-ac97.h" +#include "bf5xx-sport.h" + +#if defined(CONFIG_SND_MMAP_SUPPORT) +static void bf5xx_mmap_copy(struct snd_pcm_substream *substream, + snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + bf5xx_pcm_to_ac97( + (struct ac97_frame *)sport->tx_dma_buf + sport->tx_pos, + (__u32 *)runtime->dma_area + sport->tx_pos, count); + sport->tx_pos += runtime->period_size; + if (sport->tx_pos >= runtime->buffer_size) + sport->tx_pos %= runtime->buffer_size; + sport->tx_delay_pos = sport->tx_pos; + } else { + bf5xx_ac97_to_pcm( + (struct ac97_frame *)sport->rx_dma_buf + sport->rx_pos, + (__u32 *)runtime->dma_area + sport->rx_pos, count); + sport->rx_pos += runtime->period_size; + if (sport->rx_pos >= runtime->buffer_size) + sport->rx_pos %= runtime->buffer_size; + } +} +#endif + +static void bf5xx_dma_irq(void *data) +{ + struct snd_pcm_substream *pcm = data; +#if defined(CONFIG_SND_MMAP_SUPPORT) + struct snd_pcm_runtime *runtime = pcm->runtime; + struct sport_device *sport = runtime->private_data; + bf5xx_mmap_copy(pcm, runtime->period_size); + if (pcm->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (sport->once == 0) { + snd_pcm_period_elapsed(pcm); + bf5xx_mmap_copy(pcm, runtime->period_size); + sport->once = 1; + } + } +#endif + snd_pcm_period_elapsed(pcm); +} + +/* The memory size for pure pcm data is 128*1024 = 0x20000 bytes. + * The total rx/tx buffer is for ac97 frame to hold all pcm data + * is 0x20000 * sizeof(struct ac97_frame) / 4. + */ +#ifdef CONFIG_SND_MMAP_SUPPORT +static const struct snd_pcm_hardware bf5xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, +#else +static const struct snd_pcm_hardware bf5xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, +#endif + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = 32, + .period_bytes_max = 0x10000, + .periods_min = 1, + .periods_max = PAGE_SIZE/32, + .buffer_bytes_max = 0x20000, /* 128 kbytes */ + .fifo_size = 16, +}; + +static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + size_t size = bf5xx_pcm_hardware.buffer_bytes_max + * sizeof(struct ac97_frame) / 4; + + snd_pcm_lib_malloc_pages(substream, size); + + return 0; +} + +static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + memset(runtime->dma_area, 0, runtime->buffer_size); + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + + /* An intermediate buffer is introduced for implementing mmap for + * SPORT working in TMD mode(include AC97). + */ +#if defined(CONFIG_SND_MMAP_SUPPORT) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport_set_tx_callback(sport, bf5xx_dma_irq, substream); + sport_config_tx_dma(sport, sport->tx_dma_buf, runtime->periods, + runtime->period_size * sizeof(struct ac97_frame)); + } else { + sport_set_rx_callback(sport, bf5xx_dma_irq, substream); + sport_config_rx_dma(sport, sport->rx_dma_buf, runtime->periods, + runtime->period_size * sizeof(struct ac97_frame)); + } +#else + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport_set_tx_callback(sport, bf5xx_dma_irq, substream); + sport_config_tx_dma(sport, runtime->dma_area, runtime->periods, + runtime->period_size * sizeof(struct ac97_frame)); + } else { + sport_set_rx_callback(sport, bf5xx_dma_irq, substream); + sport_config_rx_dma(sport, runtime->dma_area, runtime->periods, + runtime->period_size * sizeof(struct ac97_frame)); + } +#endif + return 0; +} + +static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int ret = 0; + + pr_debug("%s enter\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + bf5xx_mmap_copy(substream, runtime->period_size); + snd_pcm_period_elapsed(substream); + sport->tx_delay_pos = 0; + sport_tx_start(sport); + } + else + sport_rx_start(sport); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +#if defined(CONFIG_SND_MMAP_SUPPORT) + sport->tx_pos = 0; +#endif + sport_tx_stop(sport); + } else { +#if defined(CONFIG_SND_MMAP_SUPPORT) + sport->rx_pos = 0; +#endif + sport_rx_stop(sport); + } + break; + default: + ret = -EINVAL; + } + return ret; +} + +static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + unsigned int curr; + +#if defined(CONFIG_SND_MMAP_SUPPORT) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + curr = sport->tx_delay_pos; + else + curr = sport->rx_pos; +#else + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + curr = sport_curr_offset_tx(sport) / sizeof(struct ac97_frame); + else + curr = sport_curr_offset_rx(sport) / sizeof(struct ac97_frame); + +#endif + return curr; +} + +static int bf5xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + pr_debug("%s enter\n", __func__); + snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + if (sport_handle != NULL) + runtime->private_data = sport_handle; + else { + pr_err("sport_handle is NULL\n"); + return -1; + } + return 0; + + out: + return ret; +} + +static int bf5xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + + pr_debug("%s enter\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport->once = 0; + memset(sport->tx_dma_buf, 0, runtime->buffer_size * sizeof(struct ac97_frame)); + } else + memset(sport->rx_dma_buf, 0, runtime->buffer_size * sizeof(struct ac97_frame)); + + return 0; +} + +#ifdef CONFIG_SND_MMAP_SUPPORT +static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + size_t size = vma->vm_end - vma->vm_start; + vma->vm_start = (unsigned long)runtime->dma_area; + vma->vm_end = vma->vm_start + size; + vma->vm_flags |= VM_SHARED; + return 0 ; +} +#else +static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, + void __user *buf, snd_pcm_uframes_t count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + pr_debug("%s copy pos:0x%lx count:0x%lx\n", + substream->stream ? "Capture" : "Playback", pos, count); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + bf5xx_pcm_to_ac97( + (struct ac97_frame *)runtime->dma_area + pos, + buf, count); + else + bf5xx_ac97_to_pcm( + (struct ac97_frame *)runtime->dma_area + pos, + buf, count); + return 0; +} +#endif + +struct snd_pcm_ops bf5xx_pcm_ac97_ops = { + .open = bf5xx_pcm_open, + .close = bf5xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = bf5xx_pcm_hw_params, + .hw_free = bf5xx_pcm_hw_free, + .prepare = bf5xx_pcm_prepare, + .trigger = bf5xx_pcm_trigger, + .pointer = bf5xx_pcm_pointer, +#ifdef CONFIG_SND_MMAP_SUPPORT + .mmap = bf5xx_pcm_mmap, +#else + .copy = bf5xx_pcm_copy, +#endif +}; + +static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = bf5xx_pcm_hardware.buffer_bytes_max + * sizeof(struct ac97_frame) / 4; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + pr_err("Failed to allocate dma memory\n"); + pr_err("Please increase uncached DMA memory region\n"); + return -ENOMEM; + } + buf->bytes = size; + + pr_debug("%s, area:%p, size:0x%08lx\n", __func__, + buf->area, buf->bytes); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_handle->tx_buf = buf->area; + else + sport_handle->rx_buf = buf->area; + +/* + * Need to allocate local buffer when enable + * MMAP for SPORT working in TMD mode (include AC97). + */ +#if defined(CONFIG_SND_MMAP_SUPPORT) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (!sport_handle->tx_dma_buf) { + sport_handle->tx_dma_buf = dma_alloc_coherent(NULL, \ + size, &sport_handle->tx_dma_phy, GFP_KERNEL); + if (!sport_handle->tx_dma_buf) { + pr_err("Failed to allocate memory for tx dma \ + buf - Please increase uncached DMA \ + memory region\n"); + return -ENOMEM; + } else + memset(sport_handle->tx_dma_buf, 0, size); + } else + memset(sport_handle->tx_dma_buf, 0, size); + } else { + if (!sport_handle->rx_dma_buf) { + sport_handle->rx_dma_buf = dma_alloc_coherent(NULL, \ + size, &sport_handle->rx_dma_phy, GFP_KERNEL); + if (!sport_handle->rx_dma_buf) { + pr_err("Failed to allocate memory for rx dma \ + buf - Please increase uncached DMA \ + memory region\n"); + return -ENOMEM; + } else + memset(sport_handle->rx_dma_buf, 0, size); + } else + memset(sport_handle->rx_dma_buf, 0, size); + } +#endif + return 0; +} + +static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; +#if defined(CONFIG_SND_MMAP_SUPPORT) + size_t size = bf5xx_pcm_hardware.buffer_bytes_max * + sizeof(struct ac97_frame) / 4; +#endif + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(NULL, buf->bytes, buf->area, 0); + buf->area = NULL; +#if defined(CONFIG_SND_MMAP_SUPPORT) + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (sport_handle->tx_dma_buf) + dma_free_coherent(NULL, size, \ + sport_handle->tx_dma_buf, 0); + sport_handle->tx_dma_buf = NULL; + } else { + + if (sport_handle->rx_dma_buf) + dma_free_coherent(NULL, size, \ + sport_handle->rx_dma_buf, 0); + sport_handle->rx_dma_buf = NULL; + } +#endif + } + if (sport_handle) + sport_done(sport_handle); +} + +static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; + +int bf5xx_pcm_ac97_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + pr_debug("%s enter\n", __func__); + if (!card->dev->dma_mask) + card->dev->dma_mask = &bf5xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (dai->playback.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform bf5xx_ac97_soc_platform = { + .name = "bf5xx-audio", + .pcm_ops = &bf5xx_pcm_ac97_ops, + .pcm_new = bf5xx_pcm_ac97_new, + .pcm_free = bf5xx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(bf5xx_ac97_soc_platform); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.h b/sound/soc/blackfin/bf5xx-ac97-pcm.h new file mode 100644 index 0000000..350125a --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ac97-pcm.h @@ -0,0 +1,29 @@ +/* + * linux/sound/arm/bf5xx-ac97-pcm.h -- ALSA PCM interface for the Blackfin + * + * Copyright 2007 Analog Device 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 _BF5XX_AC97_PCM_H +#define _BF5XX_AC97_PCM_H + +struct bf5xx_pcm_dma_params { + char *name; /* stream identifier */ +}; + +struct bf5xx_gpio { + u32 sys; + u32 rx; + u32 tx; + u32 clk; + u32 frm; +}; + +/* platform data */ +extern struct snd_soc_platform bf5xx_ac97_soc_platform; + +#endif diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c new file mode 100644 index 0000000..5e5aafb --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ac97.c @@ -0,0 +1,406 @@ +/* + * bf5xx-ac97.c -- AC97 support for the ADI blackfin chip. + * + * Author: Roy Huang + * Created: 11th. June 2007 + * Copyright: Analog Device 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. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/delay.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/irq.h> +#include <asm/portmux.h> +#include <linux/mutex.h> +#include <linux/gpio.h> + +#include "bf5xx-sport.h" +#include "bf5xx-ac97.h" + +#if defined(CONFIG_BF54x) +#define PIN_REQ_SPORT_0 {P_SPORT0_TFS, P_SPORT0_DTPRI, P_SPORT0_TSCLK, \ + P_SPORT0_RFS, P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0} + +#define PIN_REQ_SPORT_1 {P_SPORT1_TFS, P_SPORT1_DTPRI, P_SPORT1_TSCLK, \ + P_SPORT1_RFS, P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0} + +#define PIN_REQ_SPORT_2 {P_SPORT2_TFS, P_SPORT2_DTPRI, P_SPORT2_TSCLK, \ + P_SPORT2_RFS, P_SPORT2_DRPRI, P_SPORT2_RSCLK, 0} + +#define PIN_REQ_SPORT_3 {P_SPORT3_TFS, P_SPORT3_DTPRI, P_SPORT3_TSCLK, \ + P_SPORT3_RFS, P_SPORT3_DRPRI, P_SPORT3_RSCLK, 0} +#else +#define PIN_REQ_SPORT_0 {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, \ + P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0} + +#define PIN_REQ_SPORT_1 {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, \ + P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0} +#endif + +static int *cmd_count; +static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; + +#if defined(CONFIG_BF54x) +static struct sport_param sport_params[4] = { + { + .dma_rx_chan = CH_SPORT0_RX, + .dma_tx_chan = CH_SPORT0_TX, + .err_irq = IRQ_SPORT0_ERR, + .regs = (struct sport_register *)SPORT0_TCR1, + }, + { + .dma_rx_chan = CH_SPORT1_RX, + .dma_tx_chan = CH_SPORT1_TX, + .err_irq = IRQ_SPORT1_ERR, + .regs = (struct sport_register *)SPORT1_TCR1, + }, + { + .dma_rx_chan = CH_SPORT2_RX, + .dma_tx_chan = CH_SPORT2_TX, + .err_irq = IRQ_SPORT2_ERR, + .regs = (struct sport_register *)SPORT2_TCR1, + }, + { + .dma_rx_chan = CH_SPORT3_RX, + .dma_tx_chan = CH_SPORT3_TX, + .err_irq = IRQ_SPORT3_ERR, + .regs = (struct sport_register *)SPORT3_TCR1, + } +}; +#else +static struct sport_param sport_params[2] = { + { + .dma_rx_chan = CH_SPORT0_RX, + .dma_tx_chan = CH_SPORT0_TX, + .err_irq = IRQ_SPORT0_ERROR, + .regs = (struct sport_register *)SPORT0_TCR1, + }, + { + .dma_rx_chan = CH_SPORT1_RX, + .dma_tx_chan = CH_SPORT1_TX, + .err_irq = IRQ_SPORT1_ERROR, + .regs = (struct sport_register *)SPORT1_TCR1, + } +}; +#endif + +void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u32 *src, \ + size_t count) +{ + while (count--) { + dst->ac97_tag = TAG_VALID | TAG_PCM; + (dst++)->ac97_pcm = *src++; + } +} +EXPORT_SYMBOL(bf5xx_pcm_to_ac97); + +void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u32 *dst, \ + size_t count) +{ + while (count--) + *(dst++) = (src++)->ac97_pcm; +} +EXPORT_SYMBOL(bf5xx_ac97_to_pcm); + +static unsigned int sport_tx_curr_frag(struct sport_device *sport) +{ + return sport->tx_curr_frag = sport_curr_offset_tx(sport) / \ + sport->tx_fragsize; +} + +static void enqueue_cmd(struct snd_ac97 *ac97, __u16 addr, __u16 data) +{ + struct sport_device *sport = sport_handle; + int nextfrag = sport_tx_curr_frag(sport); + struct ac97_frame *nextwrite; + + sport_incfrag(sport, &nextfrag, 1); + + nextwrite = (struct ac97_frame *)(sport->tx_buf + \ + nextfrag * sport->tx_fragsize); + pr_debug("sport->tx_buf:%p, nextfrag:0x%x nextwrite:%p, cmd_count:%d\n", + sport->tx_buf, nextfrag, nextwrite, cmd_count[nextfrag]); + nextwrite[cmd_count[nextfrag]].ac97_tag |= TAG_CMD; + nextwrite[cmd_count[nextfrag]].ac97_addr = addr; + nextwrite[cmd_count[nextfrag]].ac97_data = data; + ++cmd_count[nextfrag]; + pr_debug("ac97_sport: Inserting %02x/%04x into fragment %d\n", + addr >> 8, data, nextfrag); +} + +static unsigned short bf5xx_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct ac97_frame out_frame[2], in_frame[2]; + + pr_debug("%s enter 0x%x\n", __func__, reg); + + /* When dma descriptor is enabled, the register should not be read */ + if (sport_handle->tx_run || sport_handle->rx_run) { + pr_err("Could you send a mail to cliff.cai@analog.com " + "to report this?\n"); + return -EFAULT; + } + + memset(&out_frame, 0, 2 * sizeof(struct ac97_frame)); + memset(&in_frame, 0, 2 * sizeof(struct ac97_frame)); + out_frame[0].ac97_tag = TAG_VALID | TAG_CMD; + out_frame[0].ac97_addr = ((reg << 8) | 0x8000); + sport_send_and_recv(sport_handle, (unsigned char *)&out_frame, + (unsigned char *)&in_frame, + 2 * sizeof(struct ac97_frame)); + return in_frame[1].ac97_data; +} + +void bf5xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + pr_debug("%s enter 0x%x:0x%04x\n", __func__, reg, val); + + if (sport_handle->tx_run) { + enqueue_cmd(ac97, (reg << 8), val); /* write */ + enqueue_cmd(ac97, (reg << 8) | 0x8000, 0); /* read back */ + } else { + struct ac97_frame frame; + memset(&frame, 0, sizeof(struct ac97_frame)); + frame.ac97_tag = TAG_VALID | TAG_CMD; + frame.ac97_addr = (reg << 8); + frame.ac97_data = val; + sport_send_and_recv(sport_handle, (unsigned char *)&frame, \ + NULL, sizeof(struct ac97_frame)); + } +} + +static void bf5xx_ac97_warm_reset(struct snd_ac97 *ac97) +{ +#if defined(CONFIG_BF54x) || defined(CONFIG_BF561) || \ + (defined(BF537_FAMILY) && (CONFIG_SND_BF5XX_SPORT_NUM == 1)) + +#define CONCAT(a, b, c) a ## b ## c +#define BFIN_SPORT_RFS(x) CONCAT(P_SPORT, x, _RFS) + + u16 per = BFIN_SPORT_RFS(CONFIG_SND_BF5XX_SPORT_NUM); + u16 gpio = P_IDENT(BFIN_SPORT_RFS(CONFIG_SND_BF5XX_SPORT_NUM)); + + pr_debug("%s enter\n", __func__); + + peripheral_free(per); + gpio_request(gpio, "bf5xx-ac97"); + gpio_direction_output(gpio, 1); + udelay(2); + gpio_set_value(gpio, 0); + udelay(1); + gpio_free(gpio); + peripheral_request(per, "soc-audio"); +#else + pr_info("%s: Not implemented\n", __func__); +#endif +} + +static void bf5xx_ac97_cold_reset(struct snd_ac97 *ac97) +{ +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + pr_debug("%s enter\n", __func__); + + /* It is specified for bf548-ezkit */ + gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 0); + /* Keep reset pin low for 1 ms */ + mdelay(1); + gpio_set_value(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1); + /* Wait for bit clock recover */ + mdelay(1); +#else + pr_info("%s: Not implemented\n", __func__); +#endif +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = bf5xx_ac97_read, + .write = bf5xx_ac97_write, + .warm_reset = bf5xx_ac97_warm_reset, + .reset = bf5xx_ac97_cold_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +#ifdef CONFIG_PM +static int bf5xx_ac97_suspend(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + pr_debug("%s : sport %d\n", __func__, dai->id); + if (!dai->active) + return 0; + if (dai->capture.active) + sport_rx_stop(sport); + if (dai->playback.active) + sport_tx_stop(sport); + return 0; +} + +static int bf5xx_ac97_resume(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + int ret; + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + pr_debug("%s : sport %d\n", __func__, dai->id); + if (!dai->active) + return 0; + + ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1)); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1)); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + if (dai->capture.active) + sport_rx_start(sport); + if (dai->playback.active) + sport_tx_start(sport); + return 0; +} + +#else +#define bf5xx_ac97_suspend NULL +#define bf5xx_ac97_resume NULL +#endif + +static int bf5xx_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + int ret; +#if defined(CONFIG_BF54x) + u16 sport_req[][7] = {PIN_REQ_SPORT_0, PIN_REQ_SPORT_1, + PIN_REQ_SPORT_2, PIN_REQ_SPORT_3}; +#else + u16 sport_req[][7] = {PIN_REQ_SPORT_0, PIN_REQ_SPORT_1}; +#endif + cmd_count = (int *)get_zeroed_page(GFP_KERNEL); + if (cmd_count == NULL) + return -ENOMEM; + + if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { + pr_err("Requesting Peripherals failed\n"); + return -EFAULT; + } + +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + /* Request PB3 as reset pin */ + if (gpio_request(CONFIG_SND_BF5XX_RESET_GPIO_NUM, "SND_AD198x RESET")) { + pr_err("Failed to request GPIO_%d for reset\n", + CONFIG_SND_BF5XX_RESET_GPIO_NUM); + peripheral_free_list(&sport_req[sport_num][0]); + return -1; + } + gpio_direction_output(CONFIG_SND_BF5XX_RESET_GPIO_NUM, 1); +#endif + sport_handle = sport_init(&sport_params[sport_num], 2, \ + sizeof(struct ac97_frame), NULL); + if (!sport_handle) { + peripheral_free_list(&sport_req[sport_num][0]); +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif + return -ENODEV; + } + /*SPORT works in TDM mode to simulate AC97 transfers*/ + ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1); + if (ret) { + pr_err("SPORT is busy!\n"); + kfree(sport_handle); + peripheral_free_list(&sport_req[sport_num][0]); +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif + return -EBUSY; + } + + ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1)); + if (ret) { + pr_err("SPORT is busy!\n"); + kfree(sport_handle); + peripheral_free_list(&sport_req[sport_num][0]); +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1)); + if (ret) { + pr_err("SPORT is busy!\n"); + kfree(sport_handle); + peripheral_free_list(&sport_req[sport_num][0]); +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif + return -EBUSY; + } + return 0; +} + +static void bf5xx_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + free_page((unsigned long)cmd_count); + cmd_count = NULL; +#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET + gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); +#endif +} + +struct snd_soc_dai bfin_ac97_dai = { + .name = "bf5xx-ac97", + .id = 0, + .type = SND_SOC_DAI_AC97, + .probe = bf5xx_ac97_probe, + .remove = bf5xx_ac97_remove, + .suspend = bf5xx_ac97_suspend, + .resume = bf5xx_ac97_resume, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "AC97 Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(bfin_ac97_dai); + +MODULE_AUTHOR("Roy Huang"); +MODULE_DESCRIPTION("AC97 driver for ADI Blackfin"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-ac97.h b/sound/soc/blackfin/bf5xx-ac97.h new file mode 100644 index 0000000..3f77cc5 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ac97.h @@ -0,0 +1,36 @@ +/* + * linux/sound/arm/bf5xx-ac97.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BF5XX_AC97_H +#define _BF5XX_AC97_H + +extern struct snd_ac97_bus_ops bf5xx_ac97_ops; +extern struct snd_ac97 *ac97; +/* Frame format in memory, only support stereo currently */ +struct ac97_frame { + u16 ac97_tag; /* slot 0 */ + u16 ac97_addr; /* slot 1 */ + u16 ac97_data; /* slot 2 */ + u32 ac97_pcm; /* slot 3 and 4: left and right pcm data */ +} __attribute__ ((packed)); + +#define TAG_VALID 0x8000 +#define TAG_CMD 0x6000 +#define TAG_PCM_LEFT 0x1000 +#define TAG_PCM_RIGHT 0x0800 +#define TAG_PCM (TAG_PCM_LEFT | TAG_PCM_RIGHT) + +extern struct snd_soc_dai bfin_ac97_dai; + +void bf5xx_pcm_to_ac97(struct ac97_frame *dst, const __u32 *src, \ + size_t count); + +void bf5xx_ac97_to_pcm(const struct ac97_frame *src, __u32 *dst, \ + size_t count); + +#endif diff --git a/sound/soc/blackfin/bf5xx-ad1980.c b/sound/soc/blackfin/bf5xx-ad1980.c new file mode 100644 index 0000000..124425d --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ad1980.c @@ -0,0 +1,113 @@ +/* + * File: sound/soc/blackfin/bf5xx-ad1980.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: Board driver for AD1980/1 audio codec + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <asm/dma.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <linux/gpio.h> +#include <asm/portmux.h> + +#include "../codecs/ad1980.h" +#include "bf5xx-sport.h" +#include "bf5xx-ac97-pcm.h" +#include "bf5xx-ac97.h" + +static struct snd_soc_machine bf5xx_board; + +static int bf5xx_board_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + pr_debug("%s enter\n", __func__); + cpu_dai->private_data = sport_handle; + return 0; +} + +static struct snd_soc_ops bf5xx_board_ops = { + .startup = bf5xx_board_startup, +}; + +static struct snd_soc_dai_link bf5xx_board_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &bfin_ac97_dai, + .codec_dai = &ad1980_dai, + .ops = &bf5xx_board_ops, +}; + +static struct snd_soc_machine bf5xx_board = { + .name = "bf5xx-board", + .dai_link = &bf5xx_board_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_board_snd_devdata = { + .machine = &bf5xx_board, + .platform = &bf5xx_ac97_soc_platform, + .codec_dev = &soc_codec_dev_ad1980, +}; + +static struct platform_device *bf5xx_board_snd_device; + +static int __init bf5xx_board_init(void) +{ + int ret; + + bf5xx_board_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf5xx_board_snd_device) + return -ENOMEM; + + platform_set_drvdata(bf5xx_board_snd_device, &bf5xx_board_snd_devdata); + bf5xx_board_snd_devdata.dev = &bf5xx_board_snd_device->dev; + ret = platform_device_add(bf5xx_board_snd_device); + + if (ret) + platform_device_put(bf5xx_board_snd_device); + + return ret; +} + +static void __exit bf5xx_board_exit(void) +{ + platform_device_unregister(bf5xx_board_snd_device); +} + +module_init(bf5xx_board_init); +module_exit(bf5xx_board_exit); + +/* Module information */ +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ALSA SoC AD1980/1 BF5xx board"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-ad73311.c b/sound/soc/blackfin/bf5xx-ad73311.c new file mode 100644 index 0000000..622c9b9 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ad73311.c @@ -0,0 +1,240 @@ +/* + * File: sound/soc/blackfin/bf5xx-ad73311.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Thur Sep 25 2008 + * Description: Board driver for ad73311 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> + +#include <asm/blackfin.h> +#include <asm/cacheflush.h> +#include <asm/irq.h> +#include <asm/dma.h> +#include <asm/portmux.h> + +#include "../codecs/ad73311.h" +#include "bf5xx-sport.h" +#include "bf5xx-i2s-pcm.h" +#include "bf5xx-i2s.h" + +#if CONFIG_SND_BF5XX_SPORT_NUM == 0 +#define bfin_write_SPORT_TCR1 bfin_write_SPORT0_TCR1 +#define bfin_read_SPORT_TCR1 bfin_read_SPORT0_TCR1 +#define bfin_write_SPORT_TCR2 bfin_write_SPORT0_TCR2 +#define bfin_write_SPORT_TX16 bfin_write_SPORT0_TX16 +#define bfin_read_SPORT_STAT bfin_read_SPORT0_STAT +#else +#define bfin_write_SPORT_TCR1 bfin_write_SPORT1_TCR1 +#define bfin_read_SPORT_TCR1 bfin_read_SPORT1_TCR1 +#define bfin_write_SPORT_TCR2 bfin_write_SPORT1_TCR2 +#define bfin_write_SPORT_TX16 bfin_write_SPORT1_TX16 +#define bfin_read_SPORT_STAT bfin_read_SPORT1_STAT +#endif + +#define GPIO_SE CONFIG_SND_BFIN_AD73311_SE + +static struct snd_soc_machine bf5xx_ad73311; + +static int snd_ad73311_startup(void) +{ + pr_debug("%s enter\n", __func__); + + /* Pull up SE pin on AD73311L */ + gpio_set_value(GPIO_SE, 1); + return 0; +} + +static int snd_ad73311_configure(void) +{ + unsigned short ctrl_regs[6]; + unsigned short status = 0; + int count = 0; + + /* DMCLK = MCLK = 16.384 MHz + * SCLK = DMCLK/8 = 2.048 MHz + * Sample Rate = DMCLK/2048 = 8 KHz + */ + ctrl_regs[0] = AD_CONTROL | AD_WRITE | CTRL_REG_B | REGB_MCDIV(0) | \ + REGB_SCDIV(0) | REGB_DIRATE(0); + ctrl_regs[1] = AD_CONTROL | AD_WRITE | CTRL_REG_C | REGC_PUDEV | \ + REGC_PUADC | REGC_PUDAC | REGC_PUREF | REGC_REFUSE ; + ctrl_regs[2] = AD_CONTROL | AD_WRITE | CTRL_REG_D | REGD_OGS(2) | \ + REGD_IGS(2); + ctrl_regs[3] = AD_CONTROL | AD_WRITE | CTRL_REG_E | REGE_DA(0x1f); + ctrl_regs[4] = AD_CONTROL | AD_WRITE | CTRL_REG_F | REGF_SEEN ; + ctrl_regs[5] = AD_CONTROL | AD_WRITE | CTRL_REG_A | REGA_MODE_DATA; + + local_irq_disable(); + snd_ad73311_startup(); + udelay(1); + + bfin_write_SPORT_TCR1(TFSR); + bfin_write_SPORT_TCR2(0xF); + SSYNC(); + + /* SPORT Tx Register is a 8 x 16 FIFO, all the data can be put to + * FIFO before enable SPORT to transfer the data + */ + for (count = 0; count < 6; count++) + bfin_write_SPORT_TX16(ctrl_regs[count]); + SSYNC(); + bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() | TSPEN); + SSYNC(); + + /* When TUVF is set, the data is already send out */ + while (!(status & TUVF) && count++ < 10000) { + udelay(1); + status = bfin_read_SPORT_STAT(); + SSYNC(); + } + bfin_write_SPORT_TCR1(bfin_read_SPORT_TCR1() & ~TSPEN); + SSYNC(); + local_irq_enable(); + + if (count == 10000) { + printk(KERN_ERR "ad73311: failed to configure codec\n"); + return -1; + } + return 0; +} + +static int bf5xx_probe(struct platform_device *pdev) +{ + int err; + if (gpio_request(GPIO_SE, "AD73311_SE")) { + printk(KERN_ERR "%s: Failed ro request GPIO_%d\n", __func__, GPIO_SE); + return -EBUSY; + } + + gpio_direction_output(GPIO_SE, 0); + + err = snd_ad73311_configure(); + if (err < 0) + return -EFAULT; + + return 0; +} + +static int bf5xx_ad73311_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + pr_debug("%s enter\n", __func__); + cpu_dai->private_data = sport_handle; + return 0; +} + +static int bf5xx_ad73311_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + pr_debug("%s rate %d format %x\n", __func__, params_rate(params), + params_format(params)); + + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + return 0; +} + + +static struct snd_soc_ops bf5xx_ad73311_ops = { + .startup = bf5xx_ad73311_startup, + .hw_params = bf5xx_ad73311_hw_params, +}; + +static struct snd_soc_dai_link bf5xx_ad73311_dai = { + .name = "ad73311", + .stream_name = "AD73311", + .cpu_dai = &bf5xx_i2s_dai, + .codec_dai = &ad73311_dai, + .ops = &bf5xx_ad73311_ops, +}; + +static struct snd_soc_machine bf5xx_ad73311 = { + .name = "bf5xx_ad73311", + .probe = bf5xx_probe, + .dai_link = &bf5xx_ad73311_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_ad73311_snd_devdata = { + .machine = &bf5xx_ad73311, + .platform = &bf5xx_i2s_soc_platform, + .codec_dev = &soc_codec_dev_ad73311, +}; + +static struct platform_device *bf52x_ad73311_snd_device; + +static int __init bf5xx_ad73311_init(void) +{ + int ret; + + pr_debug("%s enter\n", __func__); + bf52x_ad73311_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf52x_ad73311_snd_device) + return -ENOMEM; + + platform_set_drvdata(bf52x_ad73311_snd_device, &bf5xx_ad73311_snd_devdata); + bf5xx_ad73311_snd_devdata.dev = &bf52x_ad73311_snd_device->dev; + ret = platform_device_add(bf52x_ad73311_snd_device); + + if (ret) + platform_device_put(bf52x_ad73311_snd_device); + + return ret; +} + +static void __exit bf5xx_ad73311_exit(void) +{ + pr_debug("%s enter\n", __func__); + platform_device_unregister(bf52x_ad73311_snd_device); +} + +module_init(bf5xx_ad73311_init); +module_exit(bf5xx_ad73311_exit); + +/* Module information */ +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ALSA SoC AD73311 Blackfin"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c new file mode 100644 index 0000000..61fccf9 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c @@ -0,0 +1,288 @@ +/* + * File: sound/soc/blackfin/bf5xx-i2s-pcm.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: DMA driver for i2s codec + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/dma.h> + +#include "bf5xx-i2s-pcm.h" +#include "bf5xx-i2s.h" +#include "bf5xx-sport.h" + +static void bf5xx_dma_irq(void *data) +{ + struct snd_pcm_substream *pcm = data; + snd_pcm_period_elapsed(pcm); +} + +static const struct snd_pcm_hardware bf5xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 32, + .period_bytes_max = 0x10000, + .periods_min = 1, + .periods_max = PAGE_SIZE/32, + .buffer_bytes_max = 0x20000, /* 128 kbytes */ + .fifo_size = 16, +}; + +static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + size_t size = bf5xx_pcm_hardware.buffer_bytes_max; + snd_pcm_lib_malloc_pages(substream, size); + + return 0; +} + +static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + + return 0; +} + +static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int period_bytes = frames_to_bytes(runtime, runtime->period_size); + + pr_debug("%s enter\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport_set_tx_callback(sport, bf5xx_dma_irq, substream); + sport_config_tx_dma(sport, runtime->dma_area, + runtime->periods, period_bytes); + } else { + sport_set_rx_callback(sport, bf5xx_dma_irq, substream); + sport_config_rx_dma(sport, runtime->dma_area, + runtime->periods, period_bytes); + } + + return 0; +} + +static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int ret = 0; + + pr_debug("%s enter\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_tx_start(sport); + else + sport_rx_start(sport); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_tx_stop(sport); + else + sport_rx_stop(sport); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + unsigned int diff; + snd_pcm_uframes_t frames; + pr_debug("%s enter\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + diff = sport_curr_offset_tx(sport); + frames = bytes_to_frames(substream->runtime, diff); + } else { + diff = sport_curr_offset_rx(sport); + frames = bytes_to_frames(substream->runtime, diff); + } + return frames; +} + +static int bf5xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + pr_debug("%s enter\n", __func__); + snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, \ + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + if (sport_handle != NULL) + runtime->private_data = sport_handle; + else { + pr_err("sport_handle is NULL\n"); + return -1; + } + return 0; + + out: + return ret; +} + +static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + size_t size = vma->vm_end - vma->vm_start; + vma->vm_start = (unsigned long)runtime->dma_area; + vma->vm_end = vma->vm_start + size; + vma->vm_flags |= VM_SHARED; + + return 0 ; +} + +struct snd_pcm_ops bf5xx_pcm_i2s_ops = { + .open = bf5xx_pcm_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = bf5xx_pcm_hw_params, + .hw_free = bf5xx_pcm_hw_free, + .prepare = bf5xx_pcm_prepare, + .trigger = bf5xx_pcm_trigger, + .pointer = bf5xx_pcm_pointer, + .mmap = bf5xx_pcm_mmap, +}; + +static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = bf5xx_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + pr_err("Failed to allocate dma memory \ + Please increase uncached DMA memory region\n"); + return -ENOMEM; + } + buf->bytes = size; + + pr_debug("%s, area:%p, size:0x%08lx\n", __func__, + buf->area, buf->bytes); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_handle->tx_buf = buf->area; + else + sport_handle->rx_buf = buf->area; + + return 0; +} + +static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(NULL, buf->bytes, buf->area, 0); + buf->area = NULL; + } + if (sport_handle) + sport_done(sport_handle); +} + +static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; + +int bf5xx_pcm_i2s_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + pr_debug("%s enter\n", __func__); + if (!card->dev->dma_mask) + card->dev->dma_mask = &bf5xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (dai->playback.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + out: + return ret; +} + +struct snd_soc_platform bf5xx_i2s_soc_platform = { + .name = "bf5xx-audio", + .pcm_ops = &bf5xx_pcm_i2s_ops, + .pcm_new = bf5xx_pcm_i2s_new, + .pcm_free = bf5xx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(bf5xx_i2s_soc_platform); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.h b/sound/soc/blackfin/bf5xx-i2s-pcm.h new file mode 100644 index 0000000..4d4609a --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s-pcm.h @@ -0,0 +1,29 @@ +/* + * linux/sound/arm/bf5xx-i2s-pcm.h -- ALSA PCM interface for the Blackfin + * + * Copyright 2007 Analog Device 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 _BF5XX_I2S_PCM_H +#define _BF5XX_I2S_PCM_H + +struct bf5xx_pcm_dma_params { + char *name; /* stream identifier */ +}; + +struct bf5xx_gpio { + u32 sys; + u32 rx; + u32 tx; + u32 clk; + u32 frm; +}; + +/* platform data */ +extern struct snd_soc_platform bf5xx_i2s_soc_platform; + +#endif diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c new file mode 100644 index 0000000..827587f --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s.c @@ -0,0 +1,311 @@ +/* + * File: sound/soc/blackfin/bf5xx-i2s.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: Blackfin I2S CPU DAI driver + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/irq.h> +#include <asm/portmux.h> +#include <linux/mutex.h> +#include <linux/gpio.h> + +#include "bf5xx-sport.h" +#include "bf5xx-i2s.h" + +struct bf5xx_i2s_port { + u16 tcr1; + u16 rcr1; + u16 tcr2; + u16 rcr2; + int counter; +}; + +static struct bf5xx_i2s_port bf5xx_i2s; +static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; + +static struct sport_param sport_params[2] = { + { + .dma_rx_chan = CH_SPORT0_RX, + .dma_tx_chan = CH_SPORT0_TX, + .err_irq = IRQ_SPORT0_ERROR, + .regs = (struct sport_register *)SPORT0_TCR1, + }, + { + .dma_rx_chan = CH_SPORT1_RX, + .dma_tx_chan = CH_SPORT1_TX, + .err_irq = IRQ_SPORT1_ERROR, + .regs = (struct sport_register *)SPORT1_TCR1, + } +}; + +static u16 sport_req[][7] = { + { P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, + P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0}, + { P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, + P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0}, +}; + +static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + int ret = 0; + + /* interface format:support I2S,slave mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + bf5xx_i2s.tcr1 |= TFSR | TCKFE; + bf5xx_i2s.rcr1 |= RFSR | RCKFE; + bf5xx_i2s.tcr2 |= TSFSE; + bf5xx_i2s.rcr2 |= RSFSE; + break; + case SND_SOC_DAIFMT_DSP_A: + bf5xx_i2s.tcr1 |= TFSR; + bf5xx_i2s.rcr1 |= RFSR; + break; + case SND_SOC_DAIFMT_LEFT_J: + ret = -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBM_CFS: + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFM: + ret = -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int bf5xx_i2s_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s enter\n", __func__); + + /*this counter is used for counting how many pcm streams are opened*/ + bf5xx_i2s.counter++; + return 0; +} + +static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int ret = 0; + + bf5xx_i2s.tcr2 &= ~0x1f; + bf5xx_i2s.rcr2 &= ~0x1f; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bf5xx_i2s.tcr2 |= 15; + bf5xx_i2s.rcr2 |= 15; + sport_handle->wdsize = 2; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bf5xx_i2s.tcr2 |= 23; + bf5xx_i2s.rcr2 |= 23; + sport_handle->wdsize = 3; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bf5xx_i2s.tcr2 |= 31; + bf5xx_i2s.rcr2 |= 31; + sport_handle->wdsize = 4; + break; + } + + if (bf5xx_i2s.counter == 1) { + /* + * TX and RX are not independent,they are enabled at the + * same time, even if only one side is running. So, we + * need to configure both of them at the time when the first + * stream is opened. + * + * CPU DAI:slave mode. + */ + ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1, + bf5xx_i2s.rcr2, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, bf5xx_i2s.tcr1, + bf5xx_i2s.tcr2, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + } + + return 0; +} + +static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%s enter\n", __func__); + bf5xx_i2s.counter--; +} + +static int bf5xx_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + pr_debug("%s enter\n", __func__); + if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { + pr_err("Requesting Peripherals failed\n"); + return -EFAULT; + } + + /* request DMA for SPORT */ + sport_handle = sport_init(&sport_params[sport_num], 4, \ + 2 * sizeof(u32), NULL); + if (!sport_handle) { + peripheral_free_list(&sport_req[sport_num][0]); + return -ENODEV; + } + + return 0; +} + +static void bf5xx_i2s_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + pr_debug("%s enter\n", __func__); + peripheral_free_list(&sport_req[sport_num][0]); +} + +#ifdef CONFIG_PM +static int bf5xx_i2s_suspend(struct platform_device *dev, + struct snd_soc_dai *dai) +{ + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + pr_debug("%s : sport %d\n", __func__, dai->id); + if (!dai->active) + return 0; + if (dai->capture.active) + sport_rx_stop(sport); + if (dai->playback.active) + sport_tx_stop(sport); + return 0; +} + +static int bf5xx_i2s_resume(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + int ret; + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + pr_debug("%s : sport %d\n", __func__, dai->id); + if (!dai->active) + return 0; + + ret = sport_config_rx(sport_handle, RFSR | RCKFE, RSFSE|0x1f, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, TFSR | TCKFE, TSFSE|0x1f, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + if (dai->capture.active) + sport_rx_start(sport); + if (dai->playback.active) + sport_tx_start(sport); + return 0; +} + +#else +#define bf5xx_i2s_suspend NULL +#define bf5xx_i2s_resume NULL +#endif + +#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000) + +#define BF5XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai bf5xx_i2s_dai = { + .name = "bf5xx-i2s", + .id = 0, + .type = SND_SOC_DAI_I2S, + .probe = bf5xx_i2s_probe, + .remove = bf5xx_i2s_remove, + .suspend = bf5xx_i2s_suspend, + .resume = bf5xx_i2s_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = BF5XX_I2S_RATES, + .formats = BF5XX_I2S_FORMATS,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = BF5XX_I2S_RATES, + .formats = BF5XX_I2S_FORMATS,}, + .ops = { + .startup = bf5xx_i2s_startup, + .shutdown = bf5xx_i2s_shutdown, + .hw_params = bf5xx_i2s_hw_params,}, + .dai_ops = { + .set_fmt = bf5xx_i2s_set_dai_fmt, + }, +}; +EXPORT_SYMBOL_GPL(bf5xx_i2s_dai); + +/* Module information */ +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("I2S driver for ADI Blackfin"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-i2s.h b/sound/soc/blackfin/bf5xx-i2s.h new file mode 100644 index 0000000..7107d1a --- /dev/null +++ b/sound/soc/blackfin/bf5xx-i2s.h @@ -0,0 +1,14 @@ +/* + * linux/sound/arm/bf5xx-i2s.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _BF5XX_I2S_H +#define _BF5XX_I2S_H + +extern struct snd_soc_dai bf5xx_i2s_dai; + +#endif diff --git a/sound/soc/blackfin/bf5xx-sport.c b/sound/soc/blackfin/bf5xx-sport.c new file mode 100644 index 0000000..3b99e48 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-sport.c @@ -0,0 +1,1032 @@ +/* + * File: bf5xx_sport.c + * Based on: + * Author: Roy Huang <roy.huang@analog.com> + * + * Created: Tue Sep 21 10:52:42 CEST 2004 + * Description: + * Blackfin SPORT Driver + * + * Copyright 2004-2007 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/gpio.h> +#include <linux/bug.h> +#include <asm/portmux.h> +#include <asm/dma.h> +#include <asm/blackfin.h> +#include <asm/cacheflush.h> + +#include "bf5xx-sport.h" +/* delay between frame sync pulse and first data bit in multichannel mode */ +#define FRAME_DELAY (1<<12) + +struct sport_device *sport_handle; +EXPORT_SYMBOL(sport_handle); +/* note: multichannel is in units of 8 channels, + * tdm_count is # channels NOT / 8 ! */ +int sport_set_multichannel(struct sport_device *sport, + int tdm_count, u32 mask, int packed) +{ + pr_debug("%s tdm_count=%d mask:0x%08x packed=%d\n", __func__, + tdm_count, mask, packed); + + if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN)) + return -EBUSY; + + if (tdm_count & 0x7) + return -EINVAL; + + if (tdm_count > 32) + return -EINVAL; /* Only support less than 32 channels now */ + + if (tdm_count) { + sport->regs->mcmc1 = ((tdm_count>>3)-1) << 12; + sport->regs->mcmc2 = FRAME_DELAY | MCMEN | \ + (packed ? (MCDTXPE|MCDRXPE) : 0); + + sport->regs->mtcs0 = mask; + sport->regs->mrcs0 = mask; + sport->regs->mtcs1 = 0; + sport->regs->mrcs1 = 0; + sport->regs->mtcs2 = 0; + sport->regs->mrcs2 = 0; + sport->regs->mtcs3 = 0; + sport->regs->mrcs3 = 0; + } else { + sport->regs->mcmc1 = 0; + sport->regs->mcmc2 = 0; + + sport->regs->mtcs0 = 0; + sport->regs->mrcs0 = 0; + } + + sport->regs->mtcs1 = 0; sport->regs->mtcs2 = 0; sport->regs->mtcs3 = 0; + sport->regs->mrcs1 = 0; sport->regs->mrcs2 = 0; sport->regs->mrcs3 = 0; + + SSYNC(); + + return 0; +} +EXPORT_SYMBOL(sport_set_multichannel); + +int sport_config_rx(struct sport_device *sport, unsigned int rcr1, + unsigned int rcr2, unsigned int clkdiv, unsigned int fsdiv) +{ + if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN)) + return -EBUSY; + + sport->regs->rcr1 = rcr1; + sport->regs->rcr2 = rcr2; + sport->regs->rclkdiv = clkdiv; + sport->regs->rfsdiv = fsdiv; + + SSYNC(); + + return 0; +} +EXPORT_SYMBOL(sport_config_rx); + +int sport_config_tx(struct sport_device *sport, unsigned int tcr1, + unsigned int tcr2, unsigned int clkdiv, unsigned int fsdiv) +{ + if ((sport->regs->tcr1 & TSPEN) || (sport->regs->rcr1 & RSPEN)) + return -EBUSY; + + sport->regs->tcr1 = tcr1; + sport->regs->tcr2 = tcr2; + sport->regs->tclkdiv = clkdiv; + sport->regs->tfsdiv = fsdiv; + + SSYNC(); + + return 0; +} +EXPORT_SYMBOL(sport_config_tx); + +static void setup_desc(struct dmasg *desc, void *buf, int fragcount, + size_t fragsize, unsigned int cfg, + unsigned int x_count, unsigned int ycount, size_t wdsize) +{ + + int i; + + for (i = 0; i < fragcount; ++i) { + desc[i].next_desc_addr = (unsigned long)&(desc[i + 1]); + desc[i].start_addr = (unsigned long)buf + i*fragsize; + desc[i].cfg = cfg; + desc[i].x_count = x_count; + desc[i].x_modify = wdsize; + desc[i].y_count = ycount; + desc[i].y_modify = wdsize; + } + + /* make circular */ + desc[fragcount-1].next_desc_addr = (unsigned long)desc; + + pr_debug("setup desc: desc0=%p, next0=%lx, desc1=%p," + "next1=%lx\nx_count=%x,y_count=%x,addr=0x%lx,cfs=0x%x\n", + &(desc[0]), desc[0].next_desc_addr, + &(desc[1]), desc[1].next_desc_addr, + desc[0].x_count, desc[0].y_count, + desc[0].start_addr, desc[0].cfg); +} + +static int sport_start(struct sport_device *sport) +{ + enable_dma(sport->dma_rx_chan); + enable_dma(sport->dma_tx_chan); + sport->regs->rcr1 |= RSPEN; + sport->regs->tcr1 |= TSPEN; + SSYNC(); + + return 0; +} + +static int sport_stop(struct sport_device *sport) +{ + sport->regs->tcr1 &= ~TSPEN; + sport->regs->rcr1 &= ~RSPEN; + SSYNC(); + + disable_dma(sport->dma_rx_chan); + disable_dma(sport->dma_tx_chan); + return 0; +} + +static inline int sport_hook_rx_dummy(struct sport_device *sport) +{ + struct dmasg *desc, temp_desc; + unsigned long flags; + + BUG_ON(sport->dummy_rx_desc == NULL); + BUG_ON(sport->curr_rx_desc == sport->dummy_rx_desc); + + /* Maybe the dummy buffer descriptor ring is damaged */ + sport->dummy_rx_desc->next_desc_addr = \ + (unsigned long)(sport->dummy_rx_desc+1); + + local_irq_save(flags); + desc = (struct dmasg *)get_dma_next_desc_ptr(sport->dma_rx_chan); + /* Copy the descriptor which will be damaged to backup */ + temp_desc = *desc; + desc->x_count = 0xa; + desc->y_count = 0; + desc->next_desc_addr = (unsigned long)(sport->dummy_rx_desc); + local_irq_restore(flags); + /* Waiting for dummy buffer descriptor is already hooked*/ + while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) - + sizeof(struct dmasg)) != + (unsigned long)sport->dummy_rx_desc) + ; + sport->curr_rx_desc = sport->dummy_rx_desc; + /* Restore the damaged descriptor */ + *desc = temp_desc; + + return 0; +} + +static inline int sport_rx_dma_start(struct sport_device *sport, int dummy) +{ + if (dummy) { + sport->dummy_rx_desc->next_desc_addr = \ + (unsigned long) sport->dummy_rx_desc; + sport->curr_rx_desc = sport->dummy_rx_desc; + } else + sport->curr_rx_desc = sport->dma_rx_desc; + + set_dma_next_desc_addr(sport->dma_rx_chan, \ + (unsigned long)(sport->curr_rx_desc)); + set_dma_x_count(sport->dma_rx_chan, 0); + set_dma_x_modify(sport->dma_rx_chan, 0); + set_dma_config(sport->dma_rx_chan, (DMAFLOW_LARGE | NDSIZE_9 | \ + WDSIZE_32 | WNR)); + set_dma_curr_addr(sport->dma_rx_chan, sport->curr_rx_desc->start_addr); + SSYNC(); + + return 0; +} + +static inline int sport_tx_dma_start(struct sport_device *sport, int dummy) +{ + if (dummy) { + sport->dummy_tx_desc->next_desc_addr = \ + (unsigned long) sport->dummy_tx_desc; + sport->curr_tx_desc = sport->dummy_tx_desc; + } else + sport->curr_tx_desc = sport->dma_tx_desc; + + set_dma_next_desc_addr(sport->dma_tx_chan, \ + (unsigned long)(sport->curr_tx_desc)); + set_dma_x_count(sport->dma_tx_chan, 0); + set_dma_x_modify(sport->dma_tx_chan, 0); + set_dma_config(sport->dma_tx_chan, + (DMAFLOW_LARGE | NDSIZE_9 | WDSIZE_32)); + set_dma_curr_addr(sport->dma_tx_chan, sport->curr_tx_desc->start_addr); + SSYNC(); + + return 0; +} + +int sport_rx_start(struct sport_device *sport) +{ + unsigned long flags; + pr_debug("%s enter\n", __func__); + if (sport->rx_run) + return -EBUSY; + if (sport->tx_run) { + /* tx is running, rx is not running */ + BUG_ON(sport->dma_rx_desc == NULL); + BUG_ON(sport->curr_rx_desc != sport->dummy_rx_desc); + local_irq_save(flags); + while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) - + sizeof(struct dmasg)) != + (unsigned long)sport->dummy_rx_desc) + ; + sport->dummy_rx_desc->next_desc_addr = + (unsigned long)(sport->dma_rx_desc); + local_irq_restore(flags); + sport->curr_rx_desc = sport->dma_rx_desc; + } else { + sport_tx_dma_start(sport, 1); + sport_rx_dma_start(sport, 0); + sport_start(sport); + } + + sport->rx_run = 1; + + return 0; +} +EXPORT_SYMBOL(sport_rx_start); + +int sport_rx_stop(struct sport_device *sport) +{ + pr_debug("%s enter\n", __func__); + + if (!sport->rx_run) + return 0; + if (sport->tx_run) { + /* TX dma is still running, hook the dummy buffer */ + sport_hook_rx_dummy(sport); + } else { + /* Both rx and tx dma will be stopped */ + sport_stop(sport); + sport->curr_rx_desc = NULL; + sport->curr_tx_desc = NULL; + } + + sport->rx_run = 0; + + return 0; +} +EXPORT_SYMBOL(sport_rx_stop); + +static inline int sport_hook_tx_dummy(struct sport_device *sport) +{ + struct dmasg *desc, temp_desc; + unsigned long flags; + + BUG_ON(sport->dummy_tx_desc == NULL); + BUG_ON(sport->curr_tx_desc == sport->dummy_tx_desc); + + sport->dummy_tx_desc->next_desc_addr = \ + (unsigned long)(sport->dummy_tx_desc+1); + + /* Shorten the time on last normal descriptor */ + local_irq_save(flags); + desc = (struct dmasg *)get_dma_next_desc_ptr(sport->dma_tx_chan); + /* Store the descriptor which will be damaged */ + temp_desc = *desc; + desc->x_count = 0xa; + desc->y_count = 0; + desc->next_desc_addr = (unsigned long)(sport->dummy_tx_desc); + local_irq_restore(flags); + /* Waiting for dummy buffer descriptor is already hooked*/ + while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - \ + sizeof(struct dmasg)) != \ + (unsigned long)sport->dummy_tx_desc) + ; + sport->curr_tx_desc = sport->dummy_tx_desc; + /* Restore the damaged descriptor */ + *desc = temp_desc; + + return 0; +} + +int sport_tx_start(struct sport_device *sport) +{ + unsigned flags; + pr_debug("%s: tx_run:%d, rx_run:%d\n", __func__, + sport->tx_run, sport->rx_run); + if (sport->tx_run) + return -EBUSY; + if (sport->rx_run) { + BUG_ON(sport->dma_tx_desc == NULL); + BUG_ON(sport->curr_tx_desc != sport->dummy_tx_desc); + /* Hook the normal buffer descriptor */ + local_irq_save(flags); + while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - + sizeof(struct dmasg)) != + (unsigned long)sport->dummy_tx_desc) + ; + sport->dummy_tx_desc->next_desc_addr = + (unsigned long)(sport->dma_tx_desc); + local_irq_restore(flags); + sport->curr_tx_desc = sport->dma_tx_desc; + } else { + + sport_tx_dma_start(sport, 0); + /* Let rx dma run the dummy buffer */ + sport_rx_dma_start(sport, 1); + sport_start(sport); + } + sport->tx_run = 1; + return 0; +} +EXPORT_SYMBOL(sport_tx_start); + +int sport_tx_stop(struct sport_device *sport) +{ + if (!sport->tx_run) + return 0; + if (sport->rx_run) { + /* RX is still running, hook the dummy buffer */ + sport_hook_tx_dummy(sport); + } else { + /* Both rx and tx dma stopped */ + sport_stop(sport); + sport->curr_rx_desc = NULL; + sport->curr_tx_desc = NULL; + } + + sport->tx_run = 0; + + return 0; +} +EXPORT_SYMBOL(sport_tx_stop); + +static inline int compute_wdsize(size_t wdsize) +{ + switch (wdsize) { + case 1: + return WDSIZE_8; + case 2: + return WDSIZE_16; + case 4: + default: + return WDSIZE_32; + } +} + +int sport_config_rx_dma(struct sport_device *sport, void *buf, + int fragcount, size_t fragsize) +{ + unsigned int x_count; + unsigned int y_count; + unsigned int cfg; + dma_addr_t addr; + + pr_debug("%s buf:%p, frag:%d, fragsize:0x%lx\n", __func__, \ + buf, fragcount, fragsize); + + x_count = fragsize / sport->wdsize; + y_count = 0; + + /* for fragments larger than 64k words we use 2d dma, + * denote fragecount as two numbers' mutliply and both of them + * are less than 64k.*/ + if (x_count >= 0x10000) { + int i, count = x_count; + + for (i = 16; i > 0; i--) { + x_count = 1 << i; + if ((count & (x_count - 1)) == 0) { + y_count = count >> i; + if (y_count < 0x10000) + break; + } + } + if (i == 0) + return -EINVAL; + } + pr_debug("%s(x_count:0x%x, y_count:0x%x)\n", __func__, + x_count, y_count); + + if (sport->dma_rx_desc) + dma_free_coherent(NULL, sport->rx_desc_bytes, + sport->dma_rx_desc, 0); + + /* Allocate a new descritor ring as current one. */ + sport->dma_rx_desc = dma_alloc_coherent(NULL, \ + fragcount * sizeof(struct dmasg), &addr, 0); + sport->rx_desc_bytes = fragcount * sizeof(struct dmasg); + + if (!sport->dma_rx_desc) { + pr_err("Failed to allocate memory for rx desc\n"); + return -ENOMEM; + } + + sport->rx_buf = buf; + sport->rx_fragsize = fragsize; + sport->rx_frags = fragcount; + + cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | WNR | \ + (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */ + + if (y_count != 0) + cfg |= DMA2D; + + setup_desc(sport->dma_rx_desc, buf, fragcount, fragsize, + cfg|DMAEN, x_count, y_count, sport->wdsize); + + return 0; +} +EXPORT_SYMBOL(sport_config_rx_dma); + +int sport_config_tx_dma(struct sport_device *sport, void *buf, \ + int fragcount, size_t fragsize) +{ + unsigned int x_count; + unsigned int y_count; + unsigned int cfg; + dma_addr_t addr; + + pr_debug("%s buf:%p, fragcount:%d, fragsize:0x%lx\n", + __func__, buf, fragcount, fragsize); + + x_count = fragsize/sport->wdsize; + y_count = 0; + + /* for fragments larger than 64k words we use 2d dma, + * denote fragecount as two numbers' mutliply and both of them + * are less than 64k.*/ + if (x_count >= 0x10000) { + int i, count = x_count; + + for (i = 16; i > 0; i--) { + x_count = 1 << i; + if ((count & (x_count - 1)) == 0) { + y_count = count >> i; + if (y_count < 0x10000) + break; + } + } + if (i == 0) + return -EINVAL; + } + pr_debug("%s x_count:0x%x, y_count:0x%x\n", __func__, + x_count, y_count); + + + if (sport->dma_tx_desc) { + dma_free_coherent(NULL, sport->tx_desc_bytes, \ + sport->dma_tx_desc, 0); + } + + sport->dma_tx_desc = dma_alloc_coherent(NULL, \ + fragcount * sizeof(struct dmasg), &addr, 0); + sport->tx_desc_bytes = fragcount * sizeof(struct dmasg); + if (!sport->dma_tx_desc) { + pr_err("Failed to allocate memory for tx desc\n"); + return -ENOMEM; + } + + sport->tx_buf = buf; + sport->tx_fragsize = fragsize; + sport->tx_frags = fragcount; + cfg = 0x7000 | DI_EN | compute_wdsize(sport->wdsize) | \ + (DESC_ELEMENT_COUNT << 8); /* large descriptor mode */ + + if (y_count != 0) + cfg |= DMA2D; + + setup_desc(sport->dma_tx_desc, buf, fragcount, fragsize, + cfg|DMAEN, x_count, y_count, sport->wdsize); + + return 0; +} +EXPORT_SYMBOL(sport_config_tx_dma); + +/* setup dummy dma descriptor ring, which don't generate interrupts, + * the x_modify is set to 0 */ +static int sport_config_rx_dummy(struct sport_device *sport) +{ + struct dmasg *desc; + unsigned config; + + pr_debug("%s entered\n", __func__); +#if L1_DATA_A_LENGTH != 0 + desc = (struct dmasg *) l1_data_sram_alloc(2 * sizeof(*desc)); +#else + { + dma_addr_t addr; + desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0); + } +#endif + if (desc == NULL) { + pr_err("Failed to allocate memory for dummy rx desc\n"); + return -ENOMEM; + } + memset(desc, 0, 2 * sizeof(*desc)); + sport->dummy_rx_desc = desc; + desc->start_addr = (unsigned long)sport->dummy_buf; + config = DMAFLOW_LARGE | NDSIZE_9 | compute_wdsize(sport->wdsize) + | WNR | DMAEN; + desc->cfg = config; + desc->x_count = sport->dummy_count/sport->wdsize; + desc->x_modify = sport->wdsize; + desc->y_count = 0; + desc->y_modify = 0; + memcpy(desc+1, desc, sizeof(*desc)); + desc->next_desc_addr = (unsigned long)(desc+1); + desc[1].next_desc_addr = (unsigned long)desc; + return 0; +} + +static int sport_config_tx_dummy(struct sport_device *sport) +{ + struct dmasg *desc; + unsigned int config; + + pr_debug("%s entered\n", __func__); + +#if L1_DATA_A_LENGTH != 0 + desc = (struct dmasg *) l1_data_sram_alloc(2 * sizeof(*desc)); +#else + { + dma_addr_t addr; + desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0); + } +#endif + if (!desc) { + pr_err("Failed to allocate memory for dummy tx desc\n"); + return -ENOMEM; + } + memset(desc, 0, 2 * sizeof(*desc)); + sport->dummy_tx_desc = desc; + desc->start_addr = (unsigned long)sport->dummy_buf + \ + sport->dummy_count; + config = DMAFLOW_LARGE | NDSIZE_9 | + compute_wdsize(sport->wdsize) | DMAEN; + desc->cfg = config; + desc->x_count = sport->dummy_count/sport->wdsize; + desc->x_modify = sport->wdsize; + desc->y_count = 0; + desc->y_modify = 0; + memcpy(desc+1, desc, sizeof(*desc)); + desc->next_desc_addr = (unsigned long)(desc+1); + desc[1].next_desc_addr = (unsigned long)desc; + return 0; +} + +unsigned long sport_curr_offset_rx(struct sport_device *sport) +{ + unsigned long curr = get_dma_curr_addr(sport->dma_rx_chan); + + return (unsigned char *)curr - sport->rx_buf; +} +EXPORT_SYMBOL(sport_curr_offset_rx); + +unsigned long sport_curr_offset_tx(struct sport_device *sport) +{ + unsigned long curr = get_dma_curr_addr(sport->dma_tx_chan); + + return (unsigned char *)curr - sport->tx_buf; +} +EXPORT_SYMBOL(sport_curr_offset_tx); + +void sport_incfrag(struct sport_device *sport, int *frag, int tx) +{ + ++(*frag); + if (tx == 1 && *frag == sport->tx_frags) + *frag = 0; + + if (tx == 0 && *frag == sport->rx_frags) + *frag = 0; +} +EXPORT_SYMBOL(sport_incfrag); + +void sport_decfrag(struct sport_device *sport, int *frag, int tx) +{ + --(*frag); + if (tx == 1 && *frag == 0) + *frag = sport->tx_frags; + + if (tx == 0 && *frag == 0) + *frag = sport->rx_frags; +} +EXPORT_SYMBOL(sport_decfrag); + +static int sport_check_status(struct sport_device *sport, + unsigned int *sport_stat, + unsigned int *rx_stat, + unsigned int *tx_stat) +{ + int status = 0; + + if (sport_stat) { + SSYNC(); + status = sport->regs->stat; + if (status & (TOVF|TUVF|ROVF|RUVF)) + sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF)); + SSYNC(); + *sport_stat = status; + } + + if (rx_stat) { + SSYNC(); + status = get_dma_curr_irqstat(sport->dma_rx_chan); + if (status & (DMA_DONE|DMA_ERR)) + clear_dma_irqstat(sport->dma_rx_chan); + SSYNC(); + *rx_stat = status; + } + + if (tx_stat) { + SSYNC(); + status = get_dma_curr_irqstat(sport->dma_tx_chan); + if (status & (DMA_DONE|DMA_ERR)) + clear_dma_irqstat(sport->dma_tx_chan); + SSYNC(); + *tx_stat = status; + } + + return 0; +} + +int sport_dump_stat(struct sport_device *sport, char *buf, size_t len) +{ + int ret; + + ret = snprintf(buf, len, + "sts: 0x%04x\n" + "rx dma %d sts: 0x%04x tx dma %d sts: 0x%04x\n", + sport->regs->stat, + sport->dma_rx_chan, + get_dma_curr_irqstat(sport->dma_rx_chan), + sport->dma_tx_chan, + get_dma_curr_irqstat(sport->dma_tx_chan)); + buf += ret; + len -= ret; + + ret += snprintf(buf, len, + "curr_rx_desc:0x%p, curr_tx_desc:0x%p\n" + "dma_rx_desc:0x%p, dma_tx_desc:0x%p\n" + "dummy_rx_desc:0x%p, dummy_tx_desc:0x%p\n", + sport->curr_rx_desc, sport->curr_tx_desc, + sport->dma_rx_desc, sport->dma_tx_desc, + sport->dummy_rx_desc, sport->dummy_tx_desc); + + return ret; +} + +static irqreturn_t rx_handler(int irq, void *dev_id) +{ + unsigned int rx_stat; + struct sport_device *sport = dev_id; + + pr_debug("%s enter\n", __func__); + sport_check_status(sport, NULL, &rx_stat, NULL); + if (!(rx_stat & DMA_DONE)) + pr_err("rx dma is already stopped\n"); + + if (sport->rx_callback) { + sport->rx_callback(sport->rx_data); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t tx_handler(int irq, void *dev_id) +{ + unsigned int tx_stat; + struct sport_device *sport = dev_id; + pr_debug("%s enter\n", __func__); + sport_check_status(sport, NULL, NULL, &tx_stat); + if (!(tx_stat & DMA_DONE)) { + pr_err("tx dma is already stopped\n"); + return IRQ_HANDLED; + } + if (sport->tx_callback) { + sport->tx_callback(sport->tx_data); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static irqreturn_t err_handler(int irq, void *dev_id) +{ + unsigned int status = 0; + struct sport_device *sport = dev_id; + + pr_debug("%s\n", __func__); + if (sport_check_status(sport, &status, NULL, NULL)) { + pr_err("error checking status ??"); + return IRQ_NONE; + } + + if (status & (TOVF|TUVF|ROVF|RUVF)) { + pr_info("sport status error:%s%s%s%s\n", + status & TOVF ? " TOVF" : "", + status & TUVF ? " TUVF" : "", + status & ROVF ? " ROVF" : "", + status & RUVF ? " RUVF" : ""); + if (status & TOVF || status & TUVF) { + disable_dma(sport->dma_tx_chan); + if (sport->tx_run) + sport_tx_dma_start(sport, 0); + else + sport_tx_dma_start(sport, 1); + enable_dma(sport->dma_tx_chan); + } else { + disable_dma(sport->dma_rx_chan); + if (sport->rx_run) + sport_rx_dma_start(sport, 0); + else + sport_rx_dma_start(sport, 1); + enable_dma(sport->dma_rx_chan); + } + } + status = sport->regs->stat; + if (status & (TOVF|TUVF|ROVF|RUVF)) + sport->regs->stat = (status & (TOVF|TUVF|ROVF|RUVF)); + SSYNC(); + + if (sport->err_callback) + sport->err_callback(sport->err_data); + + return IRQ_HANDLED; +} + +int sport_set_rx_callback(struct sport_device *sport, + void (*rx_callback)(void *), void *rx_data) +{ + BUG_ON(rx_callback == NULL); + sport->rx_callback = rx_callback; + sport->rx_data = rx_data; + + return 0; +} +EXPORT_SYMBOL(sport_set_rx_callback); + +int sport_set_tx_callback(struct sport_device *sport, + void (*tx_callback)(void *), void *tx_data) +{ + BUG_ON(tx_callback == NULL); + sport->tx_callback = tx_callback; + sport->tx_data = tx_data; + + return 0; +} +EXPORT_SYMBOL(sport_set_tx_callback); + +int sport_set_err_callback(struct sport_device *sport, + void (*err_callback)(void *), void *err_data) +{ + BUG_ON(err_callback == NULL); + sport->err_callback = err_callback; + sport->err_data = err_data; + + return 0; +} +EXPORT_SYMBOL(sport_set_err_callback); + +struct sport_device *sport_init(struct sport_param *param, unsigned wdsize, + unsigned dummy_count, void *private_data) +{ + int ret; + struct sport_device *sport; + pr_debug("%s enter\n", __func__); + BUG_ON(param == NULL); + BUG_ON(wdsize == 0 || dummy_count == 0); + sport = kmalloc(sizeof(struct sport_device), GFP_KERNEL); + if (!sport) { + pr_err("Failed to allocate for sport device\n"); + return NULL; + } + + memset(sport, 0, sizeof(struct sport_device)); + sport->dma_rx_chan = param->dma_rx_chan; + sport->dma_tx_chan = param->dma_tx_chan; + sport->err_irq = param->err_irq; + sport->regs = param->regs; + sport->private_data = private_data; + + if (request_dma(sport->dma_rx_chan, "SPORT RX Data") == -EBUSY) { + pr_err("Failed to request RX dma %d\n", \ + sport->dma_rx_chan); + goto __init_err1; + } + if (set_dma_callback(sport->dma_rx_chan, rx_handler, sport) != 0) { + pr_err("Failed to request RX irq %d\n", \ + sport->dma_rx_chan); + goto __init_err2; + } + + if (request_dma(sport->dma_tx_chan, "SPORT TX Data") == -EBUSY) { + pr_err("Failed to request TX dma %d\n", \ + sport->dma_tx_chan); + goto __init_err2; + } + + if (set_dma_callback(sport->dma_tx_chan, tx_handler, sport) != 0) { + pr_err("Failed to request TX irq %d\n", \ + sport->dma_tx_chan); + goto __init_err3; + } + + if (request_irq(sport->err_irq, err_handler, IRQF_SHARED, "SPORT err", + sport) < 0) { + pr_err("Failed to request err irq:%d\n", \ + sport->err_irq); + goto __init_err3; + } + + pr_err("dma rx:%d tx:%d, err irq:%d, regs:%p\n", + sport->dma_rx_chan, sport->dma_tx_chan, + sport->err_irq, sport->regs); + + sport->wdsize = wdsize; + sport->dummy_count = dummy_count; + +#if L1_DATA_A_LENGTH != 0 + sport->dummy_buf = l1_data_sram_alloc(dummy_count * 2); +#else + sport->dummy_buf = kmalloc(dummy_count * 2, GFP_KERNEL); +#endif + if (sport->dummy_buf == NULL) { + pr_err("Failed to allocate dummy buffer\n"); + goto __error; + } + + memset(sport->dummy_buf, 0, dummy_count * 2); + ret = sport_config_rx_dummy(sport); + if (ret) { + pr_err("Failed to config rx dummy ring\n"); + goto __error; + } + ret = sport_config_tx_dummy(sport); + if (ret) { + pr_err("Failed to config tx dummy ring\n"); + goto __error; + } + + return sport; +__error: + free_irq(sport->err_irq, sport); +__init_err3: + free_dma(sport->dma_tx_chan); +__init_err2: + free_dma(sport->dma_rx_chan); +__init_err1: + kfree(sport); + return NULL; +} +EXPORT_SYMBOL(sport_init); + +void sport_done(struct sport_device *sport) +{ + if (sport == NULL) + return; + + sport_stop(sport); + if (sport->dma_rx_desc) + dma_free_coherent(NULL, sport->rx_desc_bytes, + sport->dma_rx_desc, 0); + if (sport->dma_tx_desc) + dma_free_coherent(NULL, sport->tx_desc_bytes, + sport->dma_tx_desc, 0); + +#if L1_DATA_A_LENGTH != 0 + l1_data_sram_free(sport->dummy_rx_desc); + l1_data_sram_free(sport->dummy_tx_desc); + l1_data_sram_free(sport->dummy_buf); +#else + dma_free_coherent(NULL, 2*sizeof(struct dmasg), + sport->dummy_rx_desc, 0); + dma_free_coherent(NULL, 2*sizeof(struct dmasg), + sport->dummy_tx_desc, 0); + kfree(sport->dummy_buf); +#endif + free_dma(sport->dma_rx_chan); + free_dma(sport->dma_tx_chan); + free_irq(sport->err_irq, sport); + + kfree(sport); + sport = NULL; +} +EXPORT_SYMBOL(sport_done); +/* +* It is only used to send several bytes when dma is not enabled + * sport controller is configured but not enabled. + * Multichannel cannot works with pio mode */ +/* Used by ac97 to write and read codec register */ +int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \ + u8 *in_data, int len) +{ + unsigned short dma_config; + unsigned short status; + unsigned long flags; + unsigned long wait = 0; + + pr_debug("%s enter, out_data:%p, in_data:%p len:%d\n", \ + __func__, out_data, in_data, len); + pr_debug("tcr1:0x%04x, tcr2:0x%04x, tclkdiv:0x%04x, tfsdiv:0x%04x\n" + "mcmc1:0x%04x, mcmc2:0x%04x\n", + sport->regs->tcr1, sport->regs->tcr2, + sport->regs->tclkdiv, sport->regs->tfsdiv, + sport->regs->mcmc1, sport->regs->mcmc2); + flush_dcache_range((unsigned)out_data, (unsigned)(out_data + len)); + + /* Enable tx dma */ + dma_config = (RESTART | WDSIZE_16 | DI_EN); + set_dma_start_addr(sport->dma_tx_chan, (unsigned long)out_data); + set_dma_x_count(sport->dma_tx_chan, len/2); + set_dma_x_modify(sport->dma_tx_chan, 2); + set_dma_config(sport->dma_tx_chan, dma_config); + enable_dma(sport->dma_tx_chan); + + if (in_data != NULL) { + invalidate_dcache_range((unsigned)in_data, \ + (unsigned)(in_data + len)); + /* Enable rx dma */ + dma_config = (RESTART | WDSIZE_16 | WNR | DI_EN); + set_dma_start_addr(sport->dma_rx_chan, (unsigned long)in_data); + set_dma_x_count(sport->dma_rx_chan, len/2); + set_dma_x_modify(sport->dma_rx_chan, 2); + set_dma_config(sport->dma_rx_chan, dma_config); + enable_dma(sport->dma_rx_chan); + } + + local_irq_save(flags); + sport->regs->tcr1 |= TSPEN; + sport->regs->rcr1 |= RSPEN; + SSYNC(); + + status = get_dma_curr_irqstat(sport->dma_tx_chan); + while (status & DMA_RUN) { + udelay(1); + status = get_dma_curr_irqstat(sport->dma_tx_chan); + pr_debug("DMA status:0x%04x\n", status); + if (wait++ > 100) + goto __over; + } + status = sport->regs->stat; + wait = 0; + + while (!(status & TXHRE)) { + pr_debug("sport status:0x%04x\n", status); + udelay(1); + status = *(unsigned short *)&sport->regs->stat; + if (wait++ > 1000) + goto __over; + } + /* Wait for the last byte sent out */ + udelay(20); + pr_debug("sport status:0x%04x\n", status); + +__over: + sport->regs->tcr1 &= ~TSPEN; + sport->regs->rcr1 &= ~RSPEN; + SSYNC(); + disable_dma(sport->dma_tx_chan); + /* Clear the status */ + clear_dma_irqstat(sport->dma_tx_chan); + if (in_data != NULL) { + disable_dma(sport->dma_rx_chan); + clear_dma_irqstat(sport->dma_rx_chan); + } + SSYNC(); + local_irq_restore(flags); + + return 0; +} +EXPORT_SYMBOL(sport_send_and_recv); + +MODULE_AUTHOR("Roy Huang"); +MODULE_DESCRIPTION("SPORT driver for ADI Blackfin"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-sport.h b/sound/soc/blackfin/bf5xx-sport.h new file mode 100644 index 0000000..fcadcc0 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-sport.h @@ -0,0 +1,194 @@ +/* + * File: bf5xx_ac97_sport.h + * Based on: + * Author: Roy Huang <roy.huang@analog.com> + * + * Created: + * Description: + * + * Copyright 2004-2007 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef __BF5XX_SPORT_H__ +#define __BF5XX_SPORT_H__ + +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <asm/dma.h> + +struct sport_register { + u16 tcr1; u16 reserved0; + u16 tcr2; u16 reserved1; + u16 tclkdiv; u16 reserved2; + u16 tfsdiv; u16 reserved3; + u32 tx; + u32 reserved_l0; + u32 rx; + u32 reserved_l1; + u16 rcr1; u16 reserved4; + u16 rcr2; u16 reserved5; + u16 rclkdiv; u16 reserved6; + u16 rfsdiv; u16 reserved7; + u16 stat; u16 reserved8; + u16 chnl; u16 reserved9; + u16 mcmc1; u16 reserved10; + u16 mcmc2; u16 reserved11; + u32 mtcs0; + u32 mtcs1; + u32 mtcs2; + u32 mtcs3; + u32 mrcs0; + u32 mrcs1; + u32 mrcs2; + u32 mrcs3; +}; + +#define DESC_ELEMENT_COUNT 9 + +struct sport_device { + int dma_rx_chan; + int dma_tx_chan; + int err_irq; + struct sport_register *regs; + + unsigned char *rx_buf; + unsigned char *tx_buf; + unsigned int rx_fragsize; + unsigned int tx_fragsize; + unsigned int rx_frags; + unsigned int tx_frags; + unsigned int wdsize; + + /* for dummy dma transfer */ + void *dummy_buf; + unsigned int dummy_count; + + /* DMA descriptor ring head of current audio stream*/ + struct dmasg *dma_rx_desc; + struct dmasg *dma_tx_desc; + unsigned int rx_desc_bytes; + unsigned int tx_desc_bytes; + + unsigned int rx_run:1; /* rx is running */ + unsigned int tx_run:1; /* tx is running */ + + struct dmasg *dummy_rx_desc; + struct dmasg *dummy_tx_desc; + + struct dmasg *curr_rx_desc; + struct dmasg *curr_tx_desc; + + int rx_curr_frag; + int tx_curr_frag; + + unsigned int rcr1; + unsigned int rcr2; + int rx_tdm_count; + + unsigned int tcr1; + unsigned int tcr2; + int tx_tdm_count; + + void (*rx_callback)(void *data); + void *rx_data; + void (*tx_callback)(void *data); + void *tx_data; + void (*err_callback)(void *data); + void *err_data; + unsigned char *tx_dma_buf; + unsigned char *rx_dma_buf; +#ifdef CONFIG_SND_MMAP_SUPPORT + dma_addr_t tx_dma_phy; + dma_addr_t rx_dma_phy; + int tx_pos;/*pcm sample count*/ + int rx_pos; + unsigned int tx_buffer_size; + unsigned int rx_buffer_size; + int tx_delay_pos; + int once; +#endif + void *private_data; +}; + +extern struct sport_device *sport_handle; + +struct sport_param { + int dma_rx_chan; + int dma_tx_chan; + int err_irq; + struct sport_register *regs; +}; + +struct sport_device *sport_init(struct sport_param *param, unsigned wdsize, + unsigned dummy_count, void *private_data); + +void sport_done(struct sport_device *sport); + +/* first use these ...*/ + +/* note: multichannel is in units of 8 channels, tdm_count is number of channels + * NOT / 8 ! all channels are enabled by default */ +int sport_set_multichannel(struct sport_device *sport, int tdm_count, + u32 mask, int packed); + +int sport_config_rx(struct sport_device *sport, + unsigned int rcr1, unsigned int rcr2, + unsigned int clkdiv, unsigned int fsdiv); + +int sport_config_tx(struct sport_device *sport, + unsigned int tcr1, unsigned int tcr2, + unsigned int clkdiv, unsigned int fsdiv); + +/* ... then these: */ + +/* buffer size (in bytes) == fragcount * fragsize_bytes */ + +/* this is not a very general api, it sets the dma to 2d autobuffer mode */ + +int sport_config_rx_dma(struct sport_device *sport, void *buf, + int fragcount, size_t fragsize_bytes); + +int sport_config_tx_dma(struct sport_device *sport, void *buf, + int fragcount, size_t fragsize_bytes); + +int sport_tx_start(struct sport_device *sport); +int sport_tx_stop(struct sport_device *sport); +int sport_rx_start(struct sport_device *sport); +int sport_rx_stop(struct sport_device *sport); + +/* for use in interrupt handler */ +unsigned long sport_curr_offset_rx(struct sport_device *sport); +unsigned long sport_curr_offset_tx(struct sport_device *sport); + +void sport_incfrag(struct sport_device *sport, int *frag, int tx); +void sport_decfrag(struct sport_device *sport, int *frag, int tx); + +int sport_set_rx_callback(struct sport_device *sport, + void (*rx_callback)(void *), void *rx_data); +int sport_set_tx_callback(struct sport_device *sport, + void (*tx_callback)(void *), void *tx_data); +int sport_set_err_callback(struct sport_device *sport, + void (*err_callback)(void *), void *err_data); + +int sport_send_and_recv(struct sport_device *sport, u8 *out_data, \ + u8 *in_data, int len); +#endif /* BF53X_SPORT_H */ diff --git a/sound/soc/blackfin/bf5xx-ssm2602.c b/sound/soc/blackfin/bf5xx-ssm2602.c new file mode 100644 index 0000000..e15f67f --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ssm2602.c @@ -0,0 +1,186 @@ +/* + * File: sound/soc/blackfin/bf5xx-ssm2602.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: board driver for SSM2602 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm_params.h> + +#include <asm/dma.h> +#include <asm/portmux.h> +#include <linux/gpio.h> +#include "../codecs/ssm2602.h" +#include "bf5xx-sport.h" +#include "bf5xx-i2s-pcm.h" +#include "bf5xx-i2s.h" + +static struct snd_soc_machine bf5xx_ssm2602; + +static int bf5xx_ssm2602_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + pr_debug("%s enter\n", __func__); + cpu_dai->private_data = sport_handle; + return 0; +} + +static int bf5xx_ssm2602_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int clk = 0; + int ret = 0; + + pr_debug("%s rate %d format %x\n", __func__, params_rate(params), + params_format(params)); + /* + * If you are using a crystal source which frequency is not 12MHz + * then modify the below case statement with frequency of the crystal. + * + * If you are using the SPORT to generate clocking then this is + * where to do it. + */ + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + case 11025: + case 22050: + case 44100: + clk = 12000000; + break; + } + + /* + * CODEC is master for BCLK and LRC in this configuration. + */ + + /* set codec DAI configuration */ + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + /* set cpu DAI configuration */ + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = codec_dai->dai_ops.set_sysclk(codec_dai, SSM2602_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops bf5xx_ssm2602_ops = { + .startup = bf5xx_ssm2602_startup, + .hw_params = bf5xx_ssm2602_hw_params, +}; + +static struct snd_soc_dai_link bf5xx_ssm2602_dai = { + .name = "ssm2602", + .stream_name = "SSM2602", + .cpu_dai = &bf5xx_i2s_dai, + .codec_dai = &ssm2602_dai, + .ops = &bf5xx_ssm2602_ops, +}; + +/* + * SSM2602 2 wire address is determined by CSB + * state during powerup. + * low = 0x1a + * high = 0x1b + */ + +static struct ssm2602_setup_data bf5xx_ssm2602_setup = { + .i2c_bus = 0, + .i2c_address = 0x1b, +}; + +static struct snd_soc_machine bf5xx_ssm2602 = { + .name = "bf5xx_ssm2602", + .dai_link = &bf5xx_ssm2602_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_ssm2602_snd_devdata = { + .machine = &bf5xx_ssm2602, + .platform = &bf5xx_i2s_soc_platform, + .codec_dev = &soc_codec_dev_ssm2602, + .codec_data = &bf5xx_ssm2602_setup, +}; + +static struct platform_device *bf52x_ssm2602_snd_device; + +static int __init bf5xx_ssm2602_init(void) +{ + int ret; + + pr_debug("%s enter\n", __func__); + bf52x_ssm2602_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf52x_ssm2602_snd_device) + return -ENOMEM; + + platform_set_drvdata(bf52x_ssm2602_snd_device, + &bf5xx_ssm2602_snd_devdata); + bf5xx_ssm2602_snd_devdata.dev = &bf52x_ssm2602_snd_device->dev; + ret = platform_device_add(bf52x_ssm2602_snd_device); + + if (ret) + platform_device_put(bf52x_ssm2602_snd_device); + + return ret; +} + +static void __exit bf5xx_ssm2602_exit(void) +{ + pr_debug("%s enter\n", __func__); + platform_device_unregister(bf52x_ssm2602_snd_device); +} + +module_init(bf5xx_ssm2602_init); +module_exit(bf5xx_ssm2602_exit); + +/* Module information */ +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("ALSA SoC SSM2602 BF527-EZKIT"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 1db04a2..4975d85 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1,32 +1,45 @@ -config SND_SOC_AC97_CODEC - tristate - select SND_AC97_CODEC - -config SND_SOC_AK4535 - tristate - -config SND_SOC_UDA1380 - tristate +config SND_SOC_ALL_CODECS + tristate "Build all ASoC CODEC drivers" + depends on I2C + select SPI + select SPI_MASTER + select SND_SOC_AD73311 + select SND_SOC_AK4535 + select SND_SOC_CS4270 + select SND_SOC_SSM2602 + select SND_SOC_TLV320AIC23 + select SND_SOC_TLV320AIC26 + select SND_SOC_TLV320AIC3X + select SND_SOC_UDA1380 + select SND_SOC_WM8510 + select SND_SOC_WM8580 + select SND_SOC_WM8731 + select SND_SOC_WM8750 + select SND_SOC_WM8753 + select SND_SOC_WM8900 + select SND_SOC_WM8903 + select SND_SOC_WM8971 + select SND_SOC_WM8990 + help + Normally ASoC codec drivers are only built if a machine driver which + uses them is also built since they are only usable with a machine + driver. Selecting this option will allow these drivers to be built + without an explicit machine driver for test and development purposes. -config SND_SOC_WM8510 - tristate + If unsure select "N". -config SND_SOC_WM8731 - tristate -config SND_SOC_WM8750 - tristate - -config SND_SOC_WM8753 +config SND_SOC_AC97_CODEC tristate + select SND_AC97_CODEC -config SND_SOC_WM8990 +config SND_SOC_AD1980 tristate -config SND_SOC_WM9712 +config SND_SOC_AD73311 tristate -config SND_SOC_WM9713 +config SND_SOC_AK4535 tristate # Cirrus Logic CS4270 Codec @@ -47,6 +60,53 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 +config SND_SOC_SSM2602 + tristate + +config SND_SOC_TLV320AIC23 + tristate + depends on I2C + +config SND_SOC_TLV320AIC26 + tristate "TI TLV320AIC26 Codec support" + depends on SPI + config SND_SOC_TLV320AIC3X tristate depends on I2C + +config SND_SOC_UDA1380 + tristate + +config SND_SOC_WM8510 + tristate + +config SND_SOC_WM8580 + tristate + +config SND_SOC_WM8731 + tristate + +config SND_SOC_WM8750 + tristate + +config SND_SOC_WM8753 + tristate + +config SND_SOC_WM8900 + tristate + +config SND_SOC_WM8903 + tristate + +config SND_SOC_WM8971 + tristate + +config SND_SOC_WM8990 + tristate + +config SND_SOC_WM9712 + tristate + +config SND_SOC_WM9713 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index d7b97ab..90f0a58 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,25 +1,43 @@ snd-soc-ac97-objs := ac97.o +snd-soc-ad1980-objs := ad1980.o +snd-soc-ad73311-objs := ad73311.o snd-soc-ak4535-objs := ak4535.o +snd-soc-cs4270-objs := cs4270.o +snd-soc-ssm2602-objs := ssm2602.o +snd-soc-tlv320aic23-objs := tlv320aic23.o +snd-soc-tlv320aic26-objs := tlv320aic26.o +snd-soc-tlv320aic3x-objs := tlv320aic3x.o snd-soc-uda1380-objs := uda1380.o snd-soc-wm8510-objs := wm8510.o +snd-soc-wm8580-objs := wm8580.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o +snd-soc-wm8900-objs := wm8900.o +snd-soc-wm8903-objs := wm8903.o +snd-soc-wm8971-objs := wm8971.o snd-soc-wm8990-objs := wm8990.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o -snd-soc-cs4270-objs := cs4270.o -snd-soc-tlv320aic3x-objs := tlv320aic3x.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o +obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o +obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o +obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o +obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o +obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o +obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o +obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o +obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o -obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o -obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index 61fd96c..bd1ebdc 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -2,8 +2,7 @@ * ac97.c -- ALSA Soc AC97 codec support * * Copyright 2005 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c new file mode 100644 index 0000000..1397b8e --- /dev/null +++ b/sound/soc/codecs/ad1980.c @@ -0,0 +1,308 @@ +/* + * ad1980.c -- ALSA Soc AD1980 codec support + * + * Copyright: Analog Device Inc. + * Author: Roy Huang <roy.huang@analog.com> + * Cliff Cai <cliff.cai@analog.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "ad1980.h" + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg); +static int ac97_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int val); + +/* + * AD1980 register cache + */ +static const u16 ad1980_reg[] = { + 0x0090, 0x8000, 0x8000, 0x8000, /* 0 - 6 */ + 0x0000, 0x0000, 0x8008, 0x8008, /* 8 - e */ + 0x8808, 0x8808, 0x0000, 0x8808, /* 10 - 16 */ + 0x8808, 0x0000, 0x8000, 0x0000, /* 18 - 1e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 20 - 26 */ + 0x03c7, 0x0000, 0xbb80, 0xbb80, /* 28 - 2e */ + 0xbb80, 0xbb80, 0x0000, 0x8080, /* 30 - 36 */ + 0x8080, 0x2000, 0x0000, 0x0000, /* 38 - 3e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x8080, 0x0000, 0x0000, 0x0000, /* 60 - 66 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* reserved */ + 0x0000, 0x0000, 0x1001, 0x0000, /* 70 - 76 */ + 0x0000, 0x0000, 0x4144, 0x5370 /* 78 - 7e */ +}; + +static const char *ad1980_rec_sel[] = {"Mic", "CD", "NC", "AUX", "Line", + "Stereo Mix", "Mono Mix", "Phone"}; + +static const struct soc_enum ad1980_cap_src = + SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 7, ad1980_rec_sel); + +static const struct snd_kcontrol_new ad1980_snd_ac97_controls[] = { +SOC_DOUBLE("Master Playback Volume", AC97_MASTER, 8, 0, 31, 1), +SOC_SINGLE("Master Playback Switch", AC97_MASTER, 15, 1, 1), + +SOC_DOUBLE("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1), +SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + +SOC_DOUBLE("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1), +SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + +SOC_DOUBLE("PCM Capture Volume", AC97_REC_GAIN, 8, 0, 31, 0), +SOC_SINGLE("PCM Capture Switch", AC97_REC_GAIN, 15, 1, 1), + +SOC_SINGLE("Mono Playback Volume", AC97_MASTER_MONO, 0, 31, 1), +SOC_SINGLE("Mono Playback Switch", AC97_MASTER_MONO, 15, 1, 1), + +SOC_SINGLE("Phone Capture Volume", AC97_PHONE, 0, 31, 1), +SOC_SINGLE("Phone Capture Switch", AC97_PHONE, 15, 1, 1), + +SOC_SINGLE("Mic Volume", AC97_MIC, 0, 31, 1), +SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), + +SOC_SINGLE("Stereo Mic Switch", AC97_AD_MISC, 6, 1, 0), +SOC_DOUBLE("Line HP Swap Switch", AC97_AD_MISC, 10, 5, 1, 0), + +SOC_DOUBLE("Surround Playback Volume", AC97_SURROUND_MASTER, 8, 0, 31, 1), +SOC_DOUBLE("Surround Playback Switch", AC97_SURROUND_MASTER, 15, 7, 1, 1), + +SOC_ENUM("Capture Source", ad1980_cap_src), + +SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0), +}; + +/* add non dapm controls */ +static int ad1980_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ad1980_snd_ac97_controls); i++) { + err = snd_ctl_add(codec->card, snd_soc_cnew( + &ad1980_snd_ac97_controls[i], codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +static unsigned int ac97_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + switch (reg) { + case AC97_RESET: + case AC97_INT_PAGING: + case AC97_POWERDOWN: + case AC97_EXTENDED_STATUS: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return soc_ac97_ops.read(codec->ac97, reg); + default: + reg = reg >> 1; + + if (reg >= (ARRAY_SIZE(ad1980_reg))) + return -EINVAL; + + return cache[reg]; + } +} + +static int ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + + soc_ac97_ops.write(codec->ac97, reg, val); + reg = reg >> 1; + if (reg < (ARRAY_SIZE(ad1980_reg))) + cache[reg] = val; + + return 0; +} + +struct snd_soc_dai ad1980_dai = { + .name = "AC97", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(ad1980_dai); + +static int ad1980_reset(struct snd_soc_codec *codec, int try_warm) +{ + u16 retry_cnt = 0; + +retry: + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (ac97_read(codec, AC97_RESET) == 0x0090) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + /* Set bit 16slot in register 74h, then every slot will has only 16 + * bits. This command is sent out in 20bit mode, in which case the + * first nibble of data is eaten by the addr. (Tag is always 16 bit)*/ + ac97_write(codec, AC97_AD_SERIAL_CFG, 0x9900); + + if (ac97_read(codec, AC97_RESET) != 0x0090) + goto err; + return 0; + +err: + while (retry_cnt++ < 10) + goto retry; + + printk(KERN_ERR "AD1980 AC97 reset failed\n"); + return -EIO; +} + +static int ad1980_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + u16 vendor_id2; + + printk(KERN_INFO "AD1980 SoC Audio Codec\n"); + + socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->codec == NULL) + return -ENOMEM; + codec = socdev->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = + kzalloc(sizeof(u16) * ARRAY_SIZE(ad1980_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + memcpy(codec->reg_cache, ad1980_reg, sizeof(u16) * \ + ARRAY_SIZE(ad1980_reg)); + codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(ad1980_reg); + codec->reg_cache_step = 2; + codec->name = "AD1980"; + codec->owner = THIS_MODULE; + codec->dai = &ad1980_dai; + codec->num_dai = 1; + codec->write = ac97_write; + codec->read = ac97_read; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) { + printk(KERN_ERR "ad1980: failed to register AC97 codec\n"); + goto codec_err; + } + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + + ret = ad1980_reset(codec, 0); + if (ret < 0) { + printk(KERN_ERR "AC97 link error\n"); + goto reset_err; + } + + /* Read out vendor ID to make sure it is ad1980 */ + if (ac97_read(codec, AC97_VENDOR_ID1) != 0x4144) + goto reset_err; + + vendor_id2 = ac97_read(codec, AC97_VENDOR_ID2); + + if (vendor_id2 != 0x5370) { + if (vendor_id2 != 0x5374) + goto reset_err; + else + printk(KERN_WARNING "ad1980: " + "Found AD1981 - only 2/2 IN/OUT Channels " + "supported\n"); + } + + ac97_write(codec, AC97_MASTER, 0x0000); /* unmute line out volume */ + ac97_write(codec, AC97_PCM, 0x0000); /* unmute PCM out volume */ + ac97_write(codec, AC97_REC_GAIN, 0x0000);/* unmute record volume */ + + ad1980_add_controls(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ad1980: failed to register card\n"); + goto reset_err; + } + + return 0; + +reset_err: + snd_soc_free_pcms(socdev); + +pcm_err: + snd_soc_free_ac97_codec(codec); + +codec_err: + kfree(codec->reg_cache); + +cache_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int ad1980_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + + snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->reg_cache); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ad1980 = { + .probe = ad1980_soc_probe, + .remove = ad1980_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad1980); + +MODULE_DESCRIPTION("ASoC ad1980 driver"); +MODULE_AUTHOR("Roy Huang, Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1980.h b/sound/soc/codecs/ad1980.h new file mode 100644 index 0000000..db6c850 --- /dev/null +++ b/sound/soc/codecs/ad1980.h @@ -0,0 +1,23 @@ +/* + * ad1980.h -- ad1980 Soc Audio driver + */ + +#ifndef _AD1980_H +#define _AD1980_H +/* Bit definition of Power-Down Control/Status Register */ +#define ADC 0x0001 +#define DAC 0x0002 +#define ANL 0x0004 +#define REF 0x0008 +#define PR0 0x0100 +#define PR1 0x0200 +#define PR2 0x0400 +#define PR3 0x0800 +#define PR4 0x1000 +#define PR5 0x2000 +#define PR6 0x4000 + +extern struct snd_soc_dai ad1980_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad1980; + +#endif diff --git a/sound/soc/codecs/ad73311.c b/sound/soc/codecs/ad73311.c new file mode 100644 index 0000000..37af860 --- /dev/null +++ b/sound/soc/codecs/ad73311.c @@ -0,0 +1,107 @@ +/* + * ad73311.c -- ALSA Soc AD73311 codec support + * + * Copyright: Analog Device Inc. + * Author: Cliff Cai <cliff.cai@analog.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Revision history + * 25th Sep 2008 Initial version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "ad73311.h" + +struct snd_soc_dai ad73311_dai = { + .name = "AD73311", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, }, +}; +EXPORT_SYMBOL_GPL(ad73311_dai); + +static int ad73311_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + mutex_init(&codec->mutex); + codec->name = "AD73311"; + codec->owner = THIS_MODULE; + codec->dai = &ad73311_dai; + codec->num_dai = 1; + socdev->codec = codec; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "ad73311: failed to create pcms\n"); + goto pcm_err; + } + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "ad73311: failed to register card\n"); + goto register_err; + } + + return ret; + +register_err: + snd_soc_free_pcms(socdev); +pcm_err: + kfree(socdev->codec); + socdev->codec = NULL; + return ret; +} + +static int ad73311_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec == NULL) + return 0; + snd_soc_free_pcms(socdev); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ad73311 = { + .probe = ad73311_soc_probe, + .remove = ad73311_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad73311); + +MODULE_DESCRIPTION("ASoC ad73311 driver"); +MODULE_AUTHOR("Cliff Cai "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h new file mode 100644 index 0000000..507ce0c --- /dev/null +++ b/sound/soc/codecs/ad73311.h @@ -0,0 +1,90 @@ +/* + * File: sound/soc/codec/ad73311.h + * Based on: + * Author: Cliff Cai <cliff.cai@analog.com> + * + * Created: Thur Sep 25, 2008 + * Description: definitions for AD73311 registers + * + * + * Modified: + * Copyright 2006 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AD73311_H__ +#define __AD73311_H__ + +#define AD_CONTROL 0x8000 +#define AD_DATA 0x0000 +#define AD_READ 0x4000 +#define AD_WRITE 0x0000 + +/* Control register A */ +#define CTRL_REG_A (0 << 8) + +#define REGA_MODE_PRO 0x00 +#define REGA_MODE_DATA 0x01 +#define REGA_MODE_MIXED 0x03 +#define REGA_DLB 0x04 +#define REGA_SLB 0x08 +#define REGA_DEVC(x) ((x & 0x7) << 4) +#define REGA_RESET 0x80 + +/* Control register B */ +#define CTRL_REG_B (1 << 8) + +#define REGB_DIRATE(x) (x & 0x3) +#define REGB_SCDIV(x) ((x & 0x3) << 2) +#define REGB_MCDIV(x) ((x & 0x7) << 4) +#define REGB_CEE (1 << 7) + +/* Control register C */ +#define CTRL_REG_C (2 << 8) + +#define REGC_PUDEV (1 << 0) +#define REGC_PUADC (1 << 3) +#define REGC_PUDAC (1 << 4) +#define REGC_PUREF (1 << 5) +#define REGC_REFUSE (1 << 6) + +/* Control register D */ +#define CTRL_REG_D (3 << 8) + +#define REGD_IGS(x) (x & 0x7) +#define REGD_RMOD (1 << 3) +#define REGD_OGS(x) ((x & 0x7) << 4) +#define REGD_MUTE (x << 7) + +/* Control register E */ +#define CTRL_REG_E (4 << 8) + +#define REGE_DA(x) (x & 0x1f) +#define REGE_IBYP (1 << 5) + +/* Control register F */ +#define CTRL_REG_F (5 << 8) + +#define REGF_SEEN (1 << 5) +#define REGF_INV (1 << 6) +#define REGF_ALB (1 << 7) + +extern struct snd_soc_dai ad73311_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad73311; +#endif diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index 7da9f467..2a89b58 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -28,7 +28,6 @@ #include "ak4535.h" -#define AUDIO_NAME "ak4535" #define AK4535_VERSION "0.3" struct snd_soc_codec_device soc_codec_dev_ak4535; @@ -535,87 +534,85 @@ static struct snd_soc_device *ak4535_socdev; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) -#define I2C_DRIVERID_AK4535 0xfefe /* liam - need a proper id */ - -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; - -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static struct i2c_driver ak4535_i2c_driver; -static struct i2c_client client_template; - -/* If the i2c layer weren't so broken, we could pass this kind of data - around */ -static int ak4535_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int ak4535_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = ak4535_socdev; - struct ak4535_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c; int ret; - if (addr != setup->i2c_address) - return -ENODEV; - - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (i2c == NULL) - return -ENOMEM; - i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - printk(KERN_ERR "failed to attach codec at addr %x\n", addr); - goto err; - } - ret = ak4535_init(socdev); - if (ret < 0) { + if (ret < 0) printk(KERN_ERR "failed to initialise AK4535\n"); - goto err; - } - return ret; -err: - kfree(i2c); return ret; } -static int ak4535_i2c_detach(struct i2c_client *client) +static int ak4535_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 ak4535_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, ak4535_codec_probe); -} +static const struct i2c_device_id ak4535_i2c_id[] = { + { "ak4535", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4535_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver ak4535_i2c_driver = { .driver = { .name = "AK4535 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_AK4535, - .attach_adapter = ak4535_i2c_attach, - .detach_client = ak4535_i2c_detach, - .command = NULL, + .probe = ak4535_i2c_probe, + .remove = ak4535_i2c_remove, + .id_table = ak4535_i2c_id, }; -static struct i2c_client client_template = { - .name = "AK4535", - .driver = &ak4535_i2c_driver, -}; +static int ak4535_add_i2c_device(struct platform_device *pdev, + const struct ak4535_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&ak4535_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "ak4535", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&ak4535_i2c_driver); + return -ENODEV; +} #endif static int ak4535_probe(struct platform_device *pdev) @@ -624,7 +621,7 @@ static int ak4535_probe(struct platform_device *pdev) struct ak4535_setup_data *setup; struct snd_soc_codec *codec; struct ak4535_priv *ak4535; - int ret = 0; + int ret; printk(KERN_INFO "AK4535 Audio Codec %s", AK4535_VERSION); @@ -646,17 +643,14 @@ static int ak4535_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_paths); ak4535_socdev = socdev; + ret = -ENODEV; + #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; codec->hw_read = (hw_read_t)i2c_master_recv; - ret = i2c_add_driver(&ak4535_i2c_driver); - if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + ret = ak4535_add_i2c_device(pdev, setup); } -#else - /* Add other interfaces here */ #endif if (ret != 0) { @@ -678,6 +672,7 @@ static int ak4535_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(&ak4535_i2c_driver); #endif kfree(codec->private_data); diff --git a/sound/soc/codecs/ak4535.h b/sound/soc/codecs/ak4535.h index e9fe30e..c7a5870 100644 --- a/sound/soc/codecs/ak4535.h +++ b/sound/soc/codecs/ak4535.h @@ -37,6 +37,7 @@ #define AK4535_CACHEREGNUM 0x10 struct ak4535_setup_data { + int i2c_bus; unsigned short i2c_address; }; diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c new file mode 100644 index 0000000..44ef0da --- /dev/null +++ b/sound/soc/codecs/ssm2602.c @@ -0,0 +1,775 @@ +/* + * File: sound/soc/codecs/ssm2602.c + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * Description: Driver for ssm2602 sound chip + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "ssm2602.h" + +#define SSM2602_VERSION "0.1" + +struct snd_soc_codec_device soc_codec_dev_ssm2602; + +/* codec private data */ +struct ssm2602_priv { + unsigned int sysclk; + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +/* + * ssm2602 register cache + * We can't read the ssm2602 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = { + 0x0017, 0x0017, 0x0079, 0x0079, + 0x0000, 0x0000, 0x0000, 0x000a, + 0x0000, 0x0000 +}; + +/* + * read ssm2602 register cache + */ +static inline unsigned int ssm2602_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == SSM2602_RESET) + return 0; + if (reg >= SSM2602_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write ssm2602 register cache + */ +static inline void ssm2602_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= SSM2602_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the ssm2602 register space + */ +static int ssm2602_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 ssm2602 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + ssm2602_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define ssm2602_reset(c) ssm2602_write(c, SSM2602_RESET, 0) + +/*Appending several "None"s just for OSS mixer use*/ +static const char *ssm2602_input_select[] = { + "Line", "Mic", "None", "None", "None", + "None", "None", "None", +}; + +static const char *ssm2602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum ssm2602_enum[] = { + SOC_ENUM_SINGLE(SSM2602_APANA, 2, 2, ssm2602_input_select), + SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph), +}; + +static const struct snd_kcontrol_new ssm2602_snd_controls[] = { + +SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V, + 7, 1, 0), + +SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), +SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), + +SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), +SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), + +SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), + +SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1), +SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0), + +SOC_ENUM("Capture Source", ssm2602_enum[0]), + +SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), +}; + +/* add non dapm controls */ +static int ssm2602_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(ssm2602_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&ssm2602_snd_controls[i], codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +/* Output Mixer */ +static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0), +}; + +/* Input mux */ +static const struct snd_kcontrol_new ssm2602_input_mux_controls = +SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]); + +static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1, + &ssm2602_output_mixer_controls[0], + ARRAY_SIZE(ssm2602_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1), +SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls), +SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1), +SND_SOC_DAPM_INPUT("MICIN"), +SND_SOC_DAPM_INPUT("RLINEIN"), +SND_SOC_DAPM_INPUT("LLINEIN"), +}; + +static const struct snd_soc_dapm_route audio_conn[] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + + /* input mux */ + {"Input Mux", "Line", "Line Input"}, + {"Input Mux", "Mic", "Mic Bias"}, + {"ADC", NULL, "Input Mux"}, + + /* inputs */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, + {"Mic Bias", NULL, "MICIN"}, +}; + +static int ssm2602_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ssm2602_dapm_widgets, + ARRAY_SIZE(ssm2602_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_conn, ARRAY_SIZE(audio_conn)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return i; +} + +static int ssm2602_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + u16 srate; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3; + int i = get_coeff(ssm2602->sysclk, params_rate(params)); + + /*no match is found*/ + if (i == ARRAY_SIZE(coeff_div)) + return -EINVAL; + + srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_write(codec, SSM2602_SRATE, srate); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + ssm2602_write(codec, SSM2602_IFACE, iface); + ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); + return 0; +} + +static int ssm2602_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + struct snd_pcm_runtime *master_runtime; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + */ + if (ssm2602->master_substream) { + master_runtime = ssm2602->master_substream->runtime; + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + ssm2602->slave_substream = substream; + } else + ssm2602->master_substream = substream; + + return 0; +} + +static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + /* set active */ + ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); + + return 0; +} + +static void ssm2602_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + /* deactivate */ + if (!codec->active) + ssm2602_write(codec, SSM2602_ACTIVE, 0); +} + +static int ssm2602_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = ssm2602_read_reg_cache(codec, SSM2602_APDIGI) & ~APDIGI_ENABLE_DAC_MUTE; + if (mute) + ssm2602_write(codec, SSM2602_APDIGI, + mute_reg | APDIGI_ENABLE_DAC_MUTE); + else + ssm2602_write(codec, SSM2602_APDIGI, mute_reg); + return 0; +} + +static int ssm2602_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct ssm2602_priv *ssm2602 = codec->private_data; + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + ssm2602->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int ssm2602_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + ssm2602_write(codec, SSM2602_IFACE, iface); + return 0; +} + +static int ssm2602_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = ssm2602_read_reg_cache(codec, SSM2602_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + ssm2602_write(codec, SSM2602_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + ssm2602_write(codec, SSM2602_PWR, reg | PWR_CLK_OUT_PDN); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + ssm2602_write(codec, SSM2602_ACTIVE, 0); + ssm2602_write(codec, SSM2602_PWR, 0xffff); + break; + + } + codec->bias_level = level; + return 0; +} + +#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +struct snd_soc_dai ssm2602_dai = { + .name = "SSM2602", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SSM2602_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .ops = { + .startup = ssm2602_startup, + .prepare = ssm2602_pcm_prepare, + .hw_params = ssm2602_hw_params, + .shutdown = ssm2602_shutdown, + }, + .dai_ops = { + .digital_mute = ssm2602_mute, + .set_sysclk = ssm2602_set_dai_sysclk, + .set_fmt = ssm2602_set_dai_fmt, + } +}; +EXPORT_SYMBOL_GPL(ssm2602_dai); + +static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ssm2602_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(ssm2602_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + ssm2602_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + ssm2602_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +/* + * initialise the ssm2602 driver + * register the mixer and dsp interfaces with the kernel + */ +static int ssm2602_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "SSM2602"; + codec->owner = THIS_MODULE; + codec->read = ssm2602_read_reg_cache; + codec->write = ssm2602_write; + codec->set_bias_level = ssm2602_set_bias_level; + codec->dai = &ssm2602_dai; + codec->num_dai = 1; + codec->reg_cache_size = sizeof(ssm2602_reg); + codec->reg_cache = kmemdup(ssm2602_reg, sizeof(ssm2602_reg), + GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + ssm2602_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + pr_err("ssm2602: failed to create pcms\n"); + goto pcm_err; + } + /*power on device*/ + ssm2602_write(codec, SSM2602_ACTIVE, 0); + /* set the update bits */ + reg = ssm2602_read_reg_cache(codec, SSM2602_LINVOL); + ssm2602_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_RINVOL); + ssm2602_write(codec, SSM2602_RINVOL, reg | RINVOL_RLIN_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_LOUT1V); + ssm2602_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH); + reg = ssm2602_read_reg_cache(codec, SSM2602_ROUT1V); + ssm2602_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH); + /*select Line in as default input*/ + ssm2602_write(codec, SSM2602_APANA, + APANA_ENABLE_MIC_BOOST2 | APANA_SELECT_DAC | + APANA_ENABLE_MIC_BOOST); + ssm2602_write(codec, SSM2602_PWR, 0); + + ssm2602_add_controls(codec); + ssm2602_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + pr_err("ssm2602: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *ssm2602_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + * ssm2602 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static int ssm2602_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = ssm2602_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = ssm2602_init(socdev); + if (ret < 0) + pr_err("failed to initialise SSM2602\n"); + + return ret; +} + +static int ssm2602_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id ssm2602_i2c_id[] = { + { "ssm2602", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id); +/* corgi i2c codec control layer */ +static struct i2c_driver ssm2602_i2c_driver = { + .driver = { + .name = "SSM2602 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = ssm2602_i2c_probe, + .remove = ssm2602_i2c_remove, + .id_table = ssm2602_i2c_id, +}; + +static int ssm2602_add_i2c_device(struct platform_device *pdev, + const struct ssm2602_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&ssm2602_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "ssm2602", I2C_NAME_SIZE); + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + return 0; +err_driver: + i2c_del_driver(&ssm2602_i2c_driver); + return -ENODEV; +} +#endif + +static int ssm2602_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct ssm2602_setup_data *setup; + struct snd_soc_codec *codec; + struct ssm2602_priv *ssm2602; + int ret = 0; + + pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + ssm2602 = kzalloc(sizeof(struct ssm2602_priv), GFP_KERNEL); + if (ssm2602 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = ssm2602; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ssm2602_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = ssm2602_add_i2c_device(pdev, setup); + } +#else + /* other interfaces */ +#endif + return ret; +} + +/* remove everything here */ +static int ssm2602_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&ssm2602_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ssm2602 = { + .probe = ssm2602_probe, + .remove = ssm2602_remove, + .suspend = ssm2602_suspend, + .resume = ssm2602_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ssm2602); + +MODULE_DESCRIPTION("ASoC ssm2602 driver"); +MODULE_AUTHOR("Cliff Cai"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h new file mode 100644 index 0000000..f344e6d --- /dev/null +++ b/sound/soc/codecs/ssm2602.h @@ -0,0 +1,130 @@ +/* + * File: sound/soc/codecs/ssm2602.h + * Author: Cliff Cai <Cliff.Cai@analog.com> + * + * Created: Tue June 06 2008 + * + * Modified: + * Copyright 2008 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SSM2602_H +#define _SSM2602_H + +/* SSM2602 Codec Register definitions */ + +#define SSM2602_LINVOL 0x00 +#define SSM2602_RINVOL 0x01 +#define SSM2602_LOUT1V 0x02 +#define SSM2602_ROUT1V 0x03 +#define SSM2602_APANA 0x04 +#define SSM2602_APDIGI 0x05 +#define SSM2602_PWR 0x06 +#define SSM2602_IFACE 0x07 +#define SSM2602_SRATE 0x08 +#define SSM2602_ACTIVE 0x09 +#define SSM2602_RESET 0x0f + +/*SSM2602 Codec Register Field definitions + *(Mask value to extract the corresponding Register field) + */ + +/*Left ADC Volume Control (SSM2602_REG_LEFT_ADC_VOL)*/ +#define LINVOL_LIN_VOL 0x01F /* Left Channel PGA Volume control */ +#define LINVOL_LIN_ENABLE_MUTE 0x080 /* Left Channel Input Mute */ +#define LINVOL_LRIN_BOTH 0x100 /* Left Channel Line Input Volume update */ + +/*Right ADC Volume Control (SSM2602_REG_RIGHT_ADC_VOL)*/ +#define RINVOL_RIN_VOL 0x01F /* Right Channel PGA Volume control */ +#define RINVOL_RIN_ENABLE_MUTE 0x080 /* Right Channel Input Mute */ +#define RINVOL_RLIN_BOTH 0x100 /* Right Channel Line Input Volume update */ + +/*Left DAC Volume Control (SSM2602_REG_LEFT_DAC_VOL)*/ +#define LOUT1V_LHP_VOL 0x07F /* Left Channel Headphone volume control */ +#define LOUT1V_ENABLE_LZC 0x080 /* Left Channel Zero cross detect enable */ +#define LOUT1V_LRHP_BOTH 0x100 /* Left Channel Headphone volume update */ + +/*Right DAC Volume Control (SSM2602_REG_RIGHT_DAC_VOL)*/ +#define ROUT1V_RHP_VOL 0x07F /* Right Channel Headphone volume control */ +#define ROUT1V_ENABLE_RZC 0x080 /* Right Channel Zero cross detect enable */ +#define ROUT1V_RLHP_BOTH 0x100 /* Right Channel Headphone volume update */ + +/*Analogue Audio Path Control (SSM2602_REG_ANALOGUE_PATH)*/ +#define APANA_ENABLE_MIC_BOOST 0x001 /* Primary Microphone Amplifier gain booster control */ +#define APANA_ENABLE_MIC_MUTE 0x002 /* Microphone Mute Control */ +#define APANA_ADC_IN_SELECT 0x004 /* Microphone/Line IN select to ADC (1=MIC, 0=Line In) */ +#define APANA_ENABLE_BYPASS 0x008 /* Line input bypass to line output */ +#define APANA_SELECT_DAC 0x010 /* Select DAC (1=Select DAC, 0=Don't Select DAC) */ +#define APANA_ENABLE_SIDETONE 0x020 /* Enable/Disable Side Tone */ +#define APANA_SIDETONE_ATTN 0x0C0 /* Side Tone Attenuation */ +#define APANA_ENABLE_MIC_BOOST2 0x100 /* Secondary Microphone Amplifier gain booster control */ + +/*Digital Audio Path Control (SSM2602_REG_DIGITAL_PATH)*/ +#define APDIGI_ENABLE_ADC_HPF 0x001 /* Enable/Disable ADC Highpass Filter */ +#define APDIGI_DE_EMPHASIS 0x006 /* De-Emphasis Control */ +#define APDIGI_ENABLE_DAC_MUTE 0x008 /* DAC Mute Control */ +#define APDIGI_STORE_OFFSET 0x010 /* Store/Clear DC offset when HPF is disabled */ + +/*Power Down Control (SSM2602_REG_POWER) + *(1=Enable PowerDown, 0=Disable PowerDown) + */ +#define PWR_LINE_IN_PDN 0x001 /* Line Input Power Down */ +#define PWR_MIC_PDN 0x002 /* Microphone Input & Bias Power Down */ +#define PWR_ADC_PDN 0x004 /* ADC Power Down */ +#define PWR_DAC_PDN 0x008 /* DAC Power Down */ +#define PWR_OUT_PDN 0x010 /* Outputs Power Down */ +#define PWR_OSC_PDN 0x020 /* Oscillator Power Down */ +#define PWR_CLK_OUT_PDN 0x040 /* CLKOUT Power Down */ +#define PWR_POWER_OFF 0x080 /* POWEROFF Mode */ + +/*Digital Audio Interface Format (SSM2602_REG_DIGITAL_IFACE)*/ +#define IFACE_IFACE_FORMAT 0x003 /* Digital Audio input format control */ +#define IFACE_AUDIO_DATA_LEN 0x00C /* Audio Data word length control */ +#define IFACE_DAC_LR_POLARITY 0x010 /* Polarity Control for clocks in RJ,LJ and I2S modes */ +#define IFACE_DAC_LR_SWAP 0x020 /* Swap DAC data control */ +#define IFACE_ENABLE_MASTER 0x040 /* Enable/Disable Master Mode */ +#define IFACE_BCLK_INVERT 0x080 /* Bit Clock Inversion control */ + +/*Sampling Control (SSM2602_REG_SAMPLING_CTRL)*/ +#define SRATE_ENABLE_USB_MODE 0x001 /* Enable/Disable USB Mode */ +#define SRATE_BOS_RATE 0x002 /* Base Over-Sampling rate */ +#define SRATE_SAMPLE_RATE 0x03C /* Clock setting condition (Sampling rate control) */ +#define SRATE_CORECLK_DIV2 0x040 /* Core Clock divider select */ +#define SRATE_CLKOUT_DIV2 0x080 /* Clock Out divider select */ + +/*Active Control (SSM2602_REG_ACTIVE_CTRL)*/ +#define ACTIVE_ACTIVATE_CODEC 0x001 /* Activate Codec Digital Audio Interface */ + +/*********************************************************************/ + +#define SSM2602_CACHEREGNUM 10 + +#define SSM2602_SYSCLK 0 +#define SSM2602_DAI 0 + +struct ssm2602_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai ssm2602_dai; +extern struct snd_soc_codec_device soc_codec_dev_ssm2602; + +#endif diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c new file mode 100644 index 0000000..bac7815 --- /dev/null +++ b/sound/soc/codecs/tlv320aic23.c @@ -0,0 +1,714 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, <arunks@mistralsolutions.com> + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd., + * + * Based on sound/soc/codecs/wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Notes: + * The AIC23 is a driver for a low power stereo audio + * codec tlv320aic23 + * + * The machine layer should disable unsupported inputs/outputs by + * snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/initval.h> + +#include "tlv320aic23.h" + +#define AIC23_VERSION "0.1" + +struct tlv320aic23_srate_reg_info { + u32 sample_rate; + u8 control; /* SR3, SR2, SR1, SR0 and BOSR */ + u8 divider; /* if 0 CLKIN = MCLK, if 1 CLKIN = MCLK/2 */ +}; + +/* + * AIC23 register cache + */ +static const u16 tlv320aic23_reg[] = { + 0x0097, 0x0097, 0x00F9, 0x00F9, /* 0 */ + 0x001A, 0x0004, 0x0007, 0x0001, /* 4 */ + 0x0020, 0x0000, 0x0000, 0x0000, /* 8 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 12 */ +}; + +/* + * read tlv320aic23 register cache + */ +static inline unsigned int tlv320aic23_read_reg_cache(struct snd_soc_codec + *codec, unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= ARRAY_SIZE(tlv320aic23_reg)) + return -1; + return cache[reg]; +} + +/* + * write tlv320aic23 register cache + */ +static inline void tlv320aic23_write_reg_cache(struct snd_soc_codec *codec, + u8 reg, u16 value) +{ + u16 *cache = codec->reg_cache; + if (reg >= ARRAY_SIZE(tlv320aic23_reg)) + return; + cache[reg] = value; +} + +/* + * write to the tlv320aic23 register space + */ +static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + + u8 data; + + /* TLV320AIC23 has 7 bit address and 9 bits of data + * so we need to switch one data bit into reg and rest + * of data into val + */ + + if ((reg < 0 || reg > 9) && (reg != 15)) { + printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg); + return -1; + } + + data = (reg << 1) | (value >> 8 & 0x01); + + tlv320aic23_write_reg_cache(codec, reg, value); + + if (codec->hw_write(codec->control_data, data, + (value & 0xff)) == 0) + return 0; + + printk(KERN_ERR "%s cannot write %03x to register R%d\n", __func__, + value, reg); + + return -EIO; +} + +static const char *rec_src_text[] = { "Line", "Mic" }; +static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum rec_src_enum = + SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text); + +static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls = +SOC_DAPM_ENUM("Input Select", rec_src_enum); + +static const struct soc_enum tlv320aic23_rec_src = + SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text); +static const struct soc_enum tlv320aic23_deemph = + SOC_ENUM_SINGLE(TLV320AIC23_DIGT, 1, 4, deemph_text); + +static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0); +static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0); + +static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 val, reg; + + val = (ucontrol->value.integer.value[0] & 0x07); + + /* linear conversion to userspace + * 000 = -6db + * 001 = -9db + * 010 = -12db + * 011 = -18db (Min) + * 100 = 0db (Max) + */ + val = (val >= 4) ? 4 : (3 - val); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (~0x1C0); + tlv320aic23_write(codec, TLV320AIC23_ANLG, reg | (val << 6)); + + return 0; +} + +static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 val; + + val = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG) & (0x1C0); + val = val >> 6; + val = (val >= 4) ? 4 : (3 - val); + ucontrol->value.integer.value[0] = val; + return 0; + +} + +#define SOC_TLV320AIC23_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, .get = snd_soc_tlv320aic23_get_volsw,\ + .put = snd_soc_tlv320aic23_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + +static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL, + TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv), + SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1), + SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 7, 1, 0), + SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv), + SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1), + SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0), + SOC_TLV320AIC23_SINGLE_TLV("Sidetone Volume", TLV320AIC23_ANLG, + 6, 4, 0, sidetone_vol_tlv), + SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph), +}; + +/* add non dapm controls */ +static int tlv320aic23_add_controls(struct snd_soc_codec *codec) +{ + + int err, i; + + for (i = 0; i < ARRAY_SIZE(tlv320aic23_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&tlv320aic23_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; + +} + +/* PGA Mixer controls for Line and Mic switch */ +static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1), + SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0, + &tlv320aic23_rec_src_mux_controls), + SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1, + &tlv320aic23_output_mixer_controls[0], + ARRAY_SIZE(tlv320aic23_output_mixer_controls)), + SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LHPOUT"), + SND_SOC_DAPM_OUTPUT("RHPOUT"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("LLINEIN"), + SND_SOC_DAPM_INPUT("RLINEIN"), + + SND_SOC_DAPM_INPUT("MICIN"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Output Mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Input"}, + + /* Outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + + /* Inputs */ + {"Line Input", "NULL", "LLINEIN"}, + {"Line Input", "NULL", "RLINEIN"}, + + {"Mic Input", "NULL", "MICIN"}, + + /* input mux */ + {"Capture Source", "Line", "Line Input"}, + {"Capture Source", "Mic", "Mic Input"}, + {"ADC", NULL, "Capture Source"}, + +}; + +/* tlv320aic23 related */ +static const struct tlv320aic23_srate_reg_info srate_reg_info[] = { + {4000, 0x06, 1}, /* 4000 */ + {8000, 0x06, 0}, /* 8000 */ + {16000, 0x0C, 1}, /* 16000 */ + {22050, 0x11, 1}, /* 22050 */ + {24000, 0x00, 1}, /* 24000 */ + {32000, 0x0C, 0}, /* 32000 */ + {44100, 0x11, 0}, /* 44100 */ + {48000, 0x00, 0}, /* 48000 */ + {88200, 0x1F, 0}, /* 88200 */ + {96000, 0x0E, 0}, /* 96000 */ +}; + +static int tlv320aic23_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 iface_reg, data; + u8 count = 0; + + iface_reg = + tlv320aic23_read_reg_cache(codec, + TLV320AIC23_DIGT_FMT) & ~(0x03 << 2); + + /* Search for the right sample rate */ + /* Verify what happens if the rate is not supported + * now it goes to 96Khz */ + while ((srate_reg_info[count].sample_rate != params_rate(params)) && + (count < ARRAY_SIZE(srate_reg_info))) { + count++; + } + + data = (srate_reg_info[count].divider << TLV320AIC23_CLKIN_SHIFT) | + (srate_reg_info[count]. control << TLV320AIC23_BOSR_SHIFT) | + TLV320AIC23_USB_CLK_ON; + + tlv320aic23_write(codec, TLV320AIC23_SRATE, data); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface_reg |= (0x01 << 2); + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface_reg |= (0x02 << 2); + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface_reg |= (0x03 << 2); + break; + } + tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* set active */ + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001); + + return 0; +} + +static void tlv320aic23_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + + /* deactivate */ + if (!codec->active) { + udelay(50); + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + } +} + +static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg; + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT); + if (mute) + reg |= TLV320AIC23_DACM_MUTE; + + else + reg &= ~TLV320AIC23_DACM_MUTE; + + tlv320aic23_write(codec, TLV320AIC23_DIGT, reg); + + return 0; +} + +static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface_reg; + + iface_reg = + tlv320aic23_read_reg_cache(codec, TLV320AIC23_DIGT_FMT) & (~0x03); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface_reg |= TLV320AIC23_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface_reg |= TLV320AIC23_FOR_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg |= TLV320AIC23_FOR_DSP; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg |= TLV320AIC23_FOR_LJUST; + break; + default: + return -EINVAL; + + } + + tlv320aic23_write(codec, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + + switch (freq) { + case 12000000: + return 0; + } + return -EINVAL; +} + +static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + tlv320aic23_write(codec, TLV320AIC23_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + tlv320aic23_write(codec, TLV320AIC23_PWR, reg | 0x0040); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + tlv320aic23_write(codec, TLV320AIC23_PWR, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +#define AIC23_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai tlv320aic23_dai = { + .name = "tlv320aic23", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .ops = { + .prepare = tlv320aic23_pcm_prepare, + .hw_params = tlv320aic23_hw_params, + .shutdown = tlv320aic23_shutdown, + }, + .dai_ops = { + .digital_mute = tlv320aic23_mute, + .set_fmt = tlv320aic23_set_dai_fmt, + .set_sysclk = tlv320aic23_set_dai_sysclk, + } +}; +EXPORT_SYMBOL_GPL(tlv320aic23_dai); + +static int tlv320aic23_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0); + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int tlv320aic23_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u16 reg; + + /* Sync reg_cache with the hardware */ + for (reg = 0; reg < ARRAY_SIZE(tlv320aic23_reg); i++) { + u16 val = tlv320aic23_read_reg_cache(codec, reg); + tlv320aic23_write(codec, reg, val); + } + + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + tlv320aic23_set_bias_level(codec, codec->suspend_bias_level); + + return 0; +} + +/* + * initialise the AIC23 driver + * register the mixer and dsp interfaces with the kernel + */ +static int tlv320aic23_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + u16 reg; + + codec->name = "tlv320aic23"; + codec->owner = THIS_MODULE; + codec->read = tlv320aic23_read_reg_cache; + codec->write = tlv320aic23_write; + codec->set_bias_level = tlv320aic23_set_bias_level; + codec->dai = &tlv320aic23_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(tlv320aic23_reg); + codec->reg_cache = + kmemdup(tlv320aic23_reg, sizeof(tlv320aic23_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* Reset codec */ + tlv320aic23_write(codec, TLV320AIC23_RESET, 0); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to create pcms\n"); + goto pcm_err; + } + + /* power on device */ + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + tlv320aic23_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K); + + /* Unmute input */ + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_LINVOL); + tlv320aic23_write(codec, TLV320AIC23_LINVOL, + (reg & (~TLV320AIC23_LIM_MUTED)) | + (TLV320AIC23_LRS_ENABLED)); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_RINVOL); + tlv320aic23_write(codec, TLV320AIC23_RINVOL, + (reg & (~TLV320AIC23_LIM_MUTED)) | + TLV320AIC23_LRS_ENABLED); + + reg = tlv320aic23_read_reg_cache(codec, TLV320AIC23_ANLG); + tlv320aic23_write(codec, TLV320AIC23_ANLG, + (reg) & (~TLV320AIC23_BYPASS_ON) & + (~TLV320AIC23_MICM_MUTED)); + + /* Default output volume */ + tlv320aic23_write(codec, TLV320AIC23_LCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & + TLV320AIC23_OUT_VOL_MASK); + tlv320aic23_write(codec, TLV320AIC23_RCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & + TLV320AIC23_OUT_VOL_MASK); + + tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1); + + tlv320aic23_add_controls(codec); + tlv320aic23_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} +static struct snd_soc_device *tlv320aic23_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +/* + * If the i2c layer weren't so broken, we could pass this kind of data + * around + */ +static int tlv320aic23_codec_probe(struct i2c_client *i2c, + const struct i2c_device_id *i2c_id) +{ + struct snd_soc_device *socdev = tlv320aic23_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EINVAL; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = tlv320aic23_init(socdev); + if (ret < 0) { + printk(KERN_ERR "tlv320aic23: failed to initialise AIC23\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} +static int __exit tlv320aic23_i2c_remove(struct i2c_client *i2c) +{ + put_device(&i2c->dev); + return 0; +} + +static const struct i2c_device_id tlv320aic23_id[] = { + {"tlv320aic23", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tlv320aic23_id); + +static struct i2c_driver tlv320aic23_i2c_driver = { + .driver = { + .name = "tlv320aic23", + }, + .probe = tlv320aic23_codec_probe, + .remove = __exit_p(tlv320aic23_i2c_remove), + .id_table = tlv320aic23_id, +}; + +#endif + +static int tlv320aic23_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION); + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + tlv320aic23_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + codec->hw_write = (hw_write_t) i2c_smbus_write_byte_data; + codec->hw_read = NULL; + ret = i2c_add_driver(&tlv320aic23_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); +#endif + return ret; +} + +static int tlv320aic23_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&tlv320aic23_i2c_driver); +#endif + kfree(codec->reg_cache); + kfree(codec); + + return 0; +} +struct snd_soc_codec_device soc_codec_dev_tlv320aic23 = { + .probe = tlv320aic23_probe, + .remove = tlv320aic23_remove, + .suspend = tlv320aic23_suspend, + .resume = tlv320aic23_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320aic23); + +MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver"); +MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tlv320aic23.h b/sound/soc/codecs/tlv320aic23.h new file mode 100644 index 0000000..79d1faf --- /dev/null +++ b/sound/soc/codecs/tlv320aic23.h @@ -0,0 +1,122 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, <arunks@mistralsolutions.com> + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _TLV320AIC23_H +#define _TLV320AIC23_H + +/* Codec TLV320AIC23 */ +#define TLV320AIC23_LINVOL 0x00 +#define TLV320AIC23_RINVOL 0x01 +#define TLV320AIC23_LCHNVOL 0x02 +#define TLV320AIC23_RCHNVOL 0x03 +#define TLV320AIC23_ANLG 0x04 +#define TLV320AIC23_DIGT 0x05 +#define TLV320AIC23_PWR 0x06 +#define TLV320AIC23_DIGT_FMT 0x07 +#define TLV320AIC23_SRATE 0x08 +#define TLV320AIC23_ACTIVE 0x09 +#define TLV320AIC23_RESET 0x0F + +/* Left (right) line input volume control register */ +#define TLV320AIC23_LRS_ENABLED 0x0100 +#define TLV320AIC23_LIM_MUTED 0x0080 +#define TLV320AIC23_LIV_DEFAULT 0x0017 +#define TLV320AIC23_LIV_MAX 0x001f +#define TLV320AIC23_LIV_MIN 0x0000 + +/* Left (right) channel headphone volume control register */ +#define TLV320AIC23_LZC_ON 0x0080 +#define TLV320AIC23_LHV_DEFAULT 0x0079 +#define TLV320AIC23_LHV_MAX 0x007f +#define TLV320AIC23_LHV_MIN 0x0000 + +/* Analog audio path control register */ +#define TLV320AIC23_STA_REG(x) ((x)<<6) +#define TLV320AIC23_STE_ENABLED 0x0020 +#define TLV320AIC23_DAC_SELECTED 0x0010 +#define TLV320AIC23_BYPASS_ON 0x0008 +#define TLV320AIC23_INSEL_MIC 0x0004 +#define TLV320AIC23_MICM_MUTED 0x0002 +#define TLV320AIC23_MICB_20DB 0x0001 + +/* Digital audio path control register */ +#define TLV320AIC23_DACM_MUTE 0x0008 +#define TLV320AIC23_DEEMP_32K 0x0002 +#define TLV320AIC23_DEEMP_44K 0x0004 +#define TLV320AIC23_DEEMP_48K 0x0006 +#define TLV320AIC23_ADCHP_ON 0x0001 + +/* Power control down register */ +#define TLV320AIC23_DEVICE_PWR_OFF 0x0080 +#define TLV320AIC23_CLK_OFF 0x0040 +#define TLV320AIC23_OSC_OFF 0x0020 +#define TLV320AIC23_OUT_OFF 0x0010 +#define TLV320AIC23_DAC_OFF 0x0008 +#define TLV320AIC23_ADC_OFF 0x0004 +#define TLV320AIC23_MIC_OFF 0x0002 +#define TLV320AIC23_LINE_OFF 0x0001 + +/* Digital audio interface register */ +#define TLV320AIC23_MS_MASTER 0x0040 +#define TLV320AIC23_LRSWAP_ON 0x0020 +#define TLV320AIC23_LRP_ON 0x0010 +#define TLV320AIC23_IWL_16 0x0000 +#define TLV320AIC23_IWL_20 0x0004 +#define TLV320AIC23_IWL_24 0x0008 +#define TLV320AIC23_IWL_32 0x000C +#define TLV320AIC23_FOR_I2S 0x0002 +#define TLV320AIC23_FOR_DSP 0x0003 +#define TLV320AIC23_FOR_LJUST 0x0001 + +/* Sample rate control register */ +#define TLV320AIC23_CLKOUT_HALF 0x0080 +#define TLV320AIC23_CLKIN_HALF 0x0040 +#define TLV320AIC23_BOSR_384fs 0x0002 /* BOSR_272fs in USB mode */ +#define TLV320AIC23_USB_CLK_ON 0x0001 +#define TLV320AIC23_SR_MASK 0xf +#define TLV320AIC23_CLKOUT_SHIFT 7 +#define TLV320AIC23_CLKIN_SHIFT 6 +#define TLV320AIC23_SR_SHIFT 2 +#define TLV320AIC23_BOSR_SHIFT 1 + +/* Digital interface register */ +#define TLV320AIC23_ACT_ON 0x0001 + +/* + * AUDIO related MACROS + */ + +#define TLV320AIC23_DEFAULT_OUT_VOL 0x70 +#define TLV320AIC23_DEFAULT_IN_VOLUME 0x10 + +#define TLV320AIC23_OUT_VOL_MIN TLV320AIC23_LHV_MIN +#define TLV320AIC23_OUT_VOL_MAX TLV320AIC23_LHV_MAX +#define TLV320AIC23_OUT_VO_RANGE (TLV320AIC23_OUT_VOL_MAX - \ + TLV320AIC23_OUT_VOL_MIN) +#define TLV320AIC23_OUT_VOL_MASK TLV320AIC23_OUT_VOL_MAX + +#define TLV320AIC23_IN_VOL_MIN TLV320AIC23_LIV_MIN +#define TLV320AIC23_IN_VOL_MAX TLV320AIC23_LIV_MAX +#define TLV320AIC23_IN_VOL_RANGE (TLV320AIC23_IN_VOL_MAX - \ + TLV320AIC23_IN_VOL_MIN) +#define TLV320AIC23_IN_VOL_MASK TLV320AIC23_IN_VOL_MAX + +#define TLV320AIC23_SIDETONE_MASK 0x1c0 +#define TLV320AIC23_SIDETONE_0 0x100 +#define TLV320AIC23_SIDETONE_6 0x000 +#define TLV320AIC23_SIDETONE_9 0x040 +#define TLV320AIC23_SIDETONE_12 0x080 +#define TLV320AIC23_SIDETONE_18 0x0c0 + +extern struct snd_soc_dai tlv320aic23_dai; +extern struct snd_soc_codec_device soc_codec_dev_tlv320aic23; + +#endif /* _TLV320AIC23_H */ diff --git a/sound/soc/codecs/tlv320aic26.c b/sound/soc/codecs/tlv320aic26.c new file mode 100644 index 0000000..bed8a9e --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.c @@ -0,0 +1,520 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * ALSA SoC CODEC driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/sysfs.h> +#include <linux/spi/spi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/soc-of-simple.h> +#include <sound/initval.h> + +#include "tlv320aic26.h" + +MODULE_DESCRIPTION("ASoC TLV320AIC26 codec driver"); +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_LICENSE("GPL"); + +/* AIC26 driver private data */ +struct aic26 { + struct spi_device *spi; + struct snd_soc_codec codec; + u16 reg_cache[AIC26_NUM_REGS]; /* shadow registers */ + int master; + int datfm; + int mclk; + + /* Keyclick parameters */ + int keyclick_amplitude; + int keyclick_freq; + int keyclick_len; +}; + +/* --------------------------------------------------------------------- + * Register access routines + */ +static unsigned int aic26_reg_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd, value; + u8 buffer[2]; + int rc; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + /* Do SPI transfer; first 16bits are command; remaining is + * register contents */ + cmd = AIC26_READ_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + rc = spi_write_then_read(aic26->spi, buffer, 2, buffer, 2); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + value = (buffer[0] << 8) | buffer[1]; + + /* Update the cache before returning with the value */ + cache[reg] = value; + return value; +} + +static unsigned int aic26_reg_read_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return 0; + } + + return cache[reg]; +} + +static int aic26_reg_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct aic26 *aic26 = codec->private_data; + u16 *cache = codec->reg_cache; + u16 cmd; + u8 buffer[4]; + int rc; + + if (reg >= AIC26_NUM_REGS) { + WARN_ON_ONCE(1); + return -EINVAL; + } + + /* Do SPI transfer; first 16bits are command; remaining is data + * to write into register */ + cmd = AIC26_WRITE_COMMAND_WORD(reg); + buffer[0] = (cmd >> 8) & 0xff; + buffer[1] = cmd & 0xff; + buffer[2] = value >> 8; + buffer[3] = value; + rc = spi_write(aic26->spi, buffer, 4); + if (rc) { + dev_err(&aic26->spi->dev, "AIC26 reg read error\n"); + return -EIO; + } + + /* update cache before returning */ + cache[reg] = value; + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Operations + */ +static int aic26_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct aic26 *aic26 = codec->private_data; + int fsref, divisor, wlen, pval, jval, dval, qval; + u16 reg; + + dev_dbg(&aic26->spi->dev, "aic26_hw_params(substream=%p, params=%p)\n", + substream, params); + dev_dbg(&aic26->spi->dev, "rate=%i format=%i\n", params_rate(params), + params_format(params)); + + switch (params_rate(params)) { + case 8000: fsref = 48000; divisor = AIC26_DIV_6; break; + case 11025: fsref = 44100; divisor = AIC26_DIV_4; break; + case 12000: fsref = 48000; divisor = AIC26_DIV_4; break; + case 16000: fsref = 48000; divisor = AIC26_DIV_3; break; + case 22050: fsref = 44100; divisor = AIC26_DIV_2; break; + case 24000: fsref = 48000; divisor = AIC26_DIV_2; break; + case 32000: fsref = 48000; divisor = AIC26_DIV_1_5; break; + case 44100: fsref = 44100; divisor = AIC26_DIV_1; break; + case 48000: fsref = 48000; divisor = AIC26_DIV_1; break; + default: + dev_dbg(&aic26->spi->dev, "bad rate\n"); return -EINVAL; + } + + /* select data word length */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S16_BE: wlen = AIC26_WLEN_16; break; + case SNDRV_PCM_FORMAT_S24_BE: wlen = AIC26_WLEN_24; break; + case SNDRV_PCM_FORMAT_S32_BE: wlen = AIC26_WLEN_32; break; + default: + dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + /* Configure PLL */ + pval = 1; + jval = (fsref == 44100) ? 7 : 8; + dval = (fsref == 44100) ? 5264 : 1920; + qval = 0; + reg = 0x8000 | qval << 11 | pval << 8 | jval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG1, reg); + reg = dval << 2; + aic26_reg_write(codec, AIC26_REG_PLL_PROG2, reg); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + if (aic26->master) + reg |= 0x0800; + if (fsref == 48000) + reg |= 0x2000; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Audio Control 1 (FSref divisor) */ + reg = aic26_reg_read_cache(codec, AIC26_REG_AUDIO_CTRL1); + reg &= ~0x0fff; + reg |= wlen | aic26->datfm | (divisor << 3) | divisor; + aic26_reg_write(codec, AIC26_REG_AUDIO_CTRL1, reg); + + return 0; +} + +/** + * aic26_mute - Mute control to reduce noise when changing audio format + */ +static int aic26_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct aic26 *aic26 = codec->private_data; + u16 reg = aic26_reg_read_cache(codec, AIC26_REG_DAC_GAIN); + + dev_dbg(&aic26->spi->dev, "aic26_mute(dai=%p, mute=%i)\n", + dai, mute); + + if (mute) + reg |= 0x8080; + else + reg &= ~0x8080; + aic26_reg_write(codec, AIC26_REG_DAC_GAIN, reg); + + return 0; +} + +static int aic26_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_sysclk(dai=%p, clk_id==%i," + " freq=%i, dir=%i)\n", + codec_dai, clk_id, freq, dir); + + /* MCLK needs to fall between 2MHz and 50 MHz */ + if ((freq < 2000000) || (freq > 50000000)) + return -EINVAL; + + aic26->mclk = freq; + return 0; +} + +static int aic26_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct aic26 *aic26 = codec->private_data; + + dev_dbg(&aic26->spi->dev, "aic26_set_fmt(dai=%p, fmt==%i)\n", + codec_dai, fmt); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: aic26->master = 1; break; + case SND_SOC_DAIFMT_CBS_CFS: aic26->master = 0; break; + default: + dev_dbg(&aic26->spi->dev, "bad master\n"); return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: aic26->datfm = AIC26_DATFM_I2S; break; + case SND_SOC_DAIFMT_DSP_A: aic26->datfm = AIC26_DATFM_DSP; break; + case SND_SOC_DAIFMT_RIGHT_J: aic26->datfm = AIC26_DATFM_RIGHTJ; break; + case SND_SOC_DAIFMT_LEFT_J: aic26->datfm = AIC26_DATFM_LEFTJ; break; + default: + dev_dbg(&aic26->spi->dev, "bad format\n"); return -EINVAL; + } + + return 0; +} + +/* --------------------------------------------------------------------- + * Digital Audio Interface Definition + */ +#define AIC26_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) +#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) + +struct snd_soc_dai aic26_dai = { + .name = "tlv320aic26", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC26_RATES, + .formats = AIC26_FORMATS, + }, + .ops = { + .hw_params = aic26_hw_params, + }, + .dai_ops = { + .digital_mute = aic26_mute, + .set_sysclk = aic26_set_sysclk, + .set_fmt = aic26_set_fmt, + }, +}; +EXPORT_SYMBOL_GPL(aic26_dai); + +/* --------------------------------------------------------------------- + * ALSA controls + */ +static const char *aic26_capture_src_text[] = {"Mic", "Aux"}; +static const struct soc_enum aic26_capture_src_enum = + SOC_ENUM_SINGLE(AIC26_REG_AUDIO_CTRL1, 12, 2, aic26_capture_src_text); + +static const struct snd_kcontrol_new aic26_snd_controls[] = { + /* Output */ + SOC_DOUBLE("PCM Playback Volume", AIC26_REG_DAC_GAIN, 8, 0, 0x7f, 1), + SOC_DOUBLE("PCM Playback Switch", AIC26_REG_DAC_GAIN, 15, 7, 1, 1), + SOC_SINGLE("PCM Capture Volume", AIC26_REG_ADC_GAIN, 8, 0x7f, 0), + SOC_SINGLE("PCM Capture Mute", AIC26_REG_ADC_GAIN, 15, 1, 1), + SOC_SINGLE("Keyclick activate", AIC26_REG_AUDIO_CTRL2, 15, 0x1, 0), + SOC_SINGLE("Keyclick amplitude", AIC26_REG_AUDIO_CTRL2, 12, 0x7, 0), + SOC_SINGLE("Keyclick frequency", AIC26_REG_AUDIO_CTRL2, 8, 0x7, 0), + SOC_SINGLE("Keyclick period", AIC26_REG_AUDIO_CTRL2, 4, 0xf, 0), + SOC_ENUM("Capture Source", aic26_capture_src_enum), +}; + +/* --------------------------------------------------------------------- + * SoC CODEC portion of driver: probe and release routines + */ +static int aic26_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct snd_kcontrol *kcontrol; + struct aic26 *aic26; + int i, ret, err; + + dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n"); + dev_dbg(&pdev->dev, "socdev=%p\n", socdev); + dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data); + + /* Fetch the relevant aic26 private data here (it's already been + * stored in the .codec pointer) */ + aic26 = socdev->codec_data; + if (aic26 == NULL) { + dev_err(&pdev->dev, "aic26: missing codec pointer\n"); + return -ENODEV; + } + codec = &aic26->codec; + socdev->codec = codec; + + dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n", + &pdev->dev, socdev->dev); + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to create pcms\n"); + return -ENODEV; + } + + /* register controls */ + dev_dbg(&pdev->dev, "Registering controls\n"); + for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) { + kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL); + err = snd_ctl_add(codec->card, kcontrol); + WARN_ON(err < 0); + } + + /* CODEC is setup, we can register the card now */ + dev_dbg(&pdev->dev, "Registering card\n"); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "aic26: failed to register card\n"); + goto card_err; + } + return 0; + + card_err: + snd_soc_free_pcms(socdev); + return ret; +} + +static int aic26_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + snd_soc_free_pcms(socdev); + return 0; +} + +struct snd_soc_codec_device aic26_soc_codec_dev = { + .probe = aic26_probe, + .remove = aic26_remove, +}; +EXPORT_SYMBOL_GPL(aic26_soc_codec_dev); + +/* --------------------------------------------------------------------- + * SPI device portion of driver: sysfs files for debugging + */ + +static ssize_t aic26_keyclick_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val, amp, freq, len; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + amp = (val >> 12) & 0x7; + freq = (125 << ((val >> 8) & 0x7)) >> 1; + len = 2 * (1 + ((val >> 4) & 0xf)); + + return sprintf(buf, "amp=%x freq=%iHz len=%iclks\n", amp, freq, len); +} + +/* Any write to the keyclick attribute will trigger the keyclick event */ +static ssize_t aic26_keyclick_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct aic26 *aic26 = dev_get_drvdata(dev); + int val; + + val = aic26_reg_read_cache(&aic26->codec, AIC26_REG_AUDIO_CTRL2); + val |= 0x8000; + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL2, val); + + return count; +} + +static DEVICE_ATTR(keyclick, 0644, aic26_keyclick_show, aic26_keyclick_set); + +/* --------------------------------------------------------------------- + * SPI device portion of driver: probe and release routines and SPI + * driver registration. + */ +static int aic26_spi_probe(struct spi_device *spi) +{ + struct aic26 *aic26; + int rc, i, reg; + + dev_dbg(&spi->dev, "probing tlv320aic26 spi device\n"); + + /* Allocate driver data */ + aic26 = kzalloc(sizeof *aic26, GFP_KERNEL); + if (!aic26) + return -ENOMEM; + + /* Initialize the driver data */ + aic26->spi = spi; + dev_set_drvdata(&spi->dev, aic26); + + /* Setup what we can in the codec structure so that the register + * access functions will work as expected. More will be filled + * out when it is probed by the SoC CODEC part of this driver */ + aic26->codec.private_data = aic26; + aic26->codec.name = "aic26"; + aic26->codec.owner = THIS_MODULE; + aic26->codec.dai = &aic26_dai; + aic26->codec.num_dai = 1; + aic26->codec.read = aic26_reg_read; + aic26->codec.write = aic26_reg_write; + aic26->master = 1; + mutex_init(&aic26->codec.mutex); + INIT_LIST_HEAD(&aic26->codec.dapm_widgets); + INIT_LIST_HEAD(&aic26->codec.dapm_paths); + aic26->codec.reg_cache_size = AIC26_NUM_REGS; + aic26->codec.reg_cache = aic26->reg_cache; + + /* Reset the codec to power on defaults */ + aic26_reg_write(&aic26->codec, AIC26_REG_RESET, 0xBB00); + + /* Power up CODEC */ + aic26_reg_write(&aic26->codec, AIC26_REG_POWER_CTRL, 0); + + /* Audio Control 3 (master mode, fsref rate) */ + reg = aic26_reg_read(&aic26->codec, AIC26_REG_AUDIO_CTRL3); + reg &= ~0xf800; + reg |= 0x0800; /* set master mode */ + aic26_reg_write(&aic26->codec, AIC26_REG_AUDIO_CTRL3, reg); + + /* Fill register cache */ + for (i = 0; i < ARRAY_SIZE(aic26->reg_cache); i++) + aic26_reg_read(&aic26->codec, i); + + /* Register the sysfs files for debugging */ + /* Create SysFS files */ + rc = device_create_file(&spi->dev, &dev_attr_keyclick); + if (rc) + dev_info(&spi->dev, "error creating sysfs files\n"); + +#if defined(CONFIG_SND_SOC_OF_SIMPLE) + /* Tell the of_soc helper about this codec */ + of_snd_soc_register_codec(&aic26_soc_codec_dev, aic26, &aic26_dai, + spi->dev.archdata.of_node); +#endif + + dev_dbg(&spi->dev, "SPI device initialized\n"); + return 0; +} + +static int aic26_spi_remove(struct spi_device *spi) +{ + struct aic26 *aic26 = dev_get_drvdata(&spi->dev); + + kfree(aic26); + + return 0; +} + +static struct spi_driver aic26_spi = { + .driver = { + .name = "tlv320aic26", + .owner = THIS_MODULE, + }, + .probe = aic26_spi_probe, + .remove = aic26_spi_remove, +}; + +static int __init aic26_init(void) +{ + return spi_register_driver(&aic26_spi); +} +module_init(aic26_init); + +static void __exit aic26_exit(void) +{ + spi_unregister_driver(&aic26_spi); +} +module_exit(aic26_exit); diff --git a/sound/soc/codecs/tlv320aic26.h b/sound/soc/codecs/tlv320aic26.h new file mode 100644 index 0000000..786ba16 --- /dev/null +++ b/sound/soc/codecs/tlv320aic26.h @@ -0,0 +1,96 @@ +/* + * Texas Instruments TLV320AIC26 low power audio CODEC + * register definitions + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#ifndef _TLV320AIC16_H_ +#define _TLV320AIC16_H_ + +/* AIC26 Registers */ +#define AIC26_READ_COMMAND_WORD(addr) ((1 << 15) | (addr << 5)) +#define AIC26_WRITE_COMMAND_WORD(addr) ((0 << 15) | (addr << 5)) +#define AIC26_PAGE_ADDR(page, offset) ((page << 6) | offset) +#define AIC26_NUM_REGS AIC26_PAGE_ADDR(3, 0) + +/* Page 0: Auxillary data registers */ +#define AIC26_REG_BAT1 AIC26_PAGE_ADDR(0, 0x05) +#define AIC26_REG_BAT2 AIC26_PAGE_ADDR(0, 0x06) +#define AIC26_REG_AUX AIC26_PAGE_ADDR(0, 0x07) +#define AIC26_REG_TEMP1 AIC26_PAGE_ADDR(0, 0x09) +#define AIC26_REG_TEMP2 AIC26_PAGE_ADDR(0, 0x0A) + +/* Page 1: Auxillary control registers */ +#define AIC26_REG_AUX_ADC AIC26_PAGE_ADDR(1, 0x00) +#define AIC26_REG_STATUS AIC26_PAGE_ADDR(1, 0x01) +#define AIC26_REG_REFERENCE AIC26_PAGE_ADDR(1, 0x03) +#define AIC26_REG_RESET AIC26_PAGE_ADDR(1, 0x04) + +/* Page 2: Audio control registers */ +#define AIC26_REG_AUDIO_CTRL1 AIC26_PAGE_ADDR(2, 0x00) +#define AIC26_REG_ADC_GAIN AIC26_PAGE_ADDR(2, 0x01) +#define AIC26_REG_DAC_GAIN AIC26_PAGE_ADDR(2, 0x02) +#define AIC26_REG_SIDETONE AIC26_PAGE_ADDR(2, 0x03) +#define AIC26_REG_AUDIO_CTRL2 AIC26_PAGE_ADDR(2, 0x04) +#define AIC26_REG_POWER_CTRL AIC26_PAGE_ADDR(2, 0x05) +#define AIC26_REG_AUDIO_CTRL3 AIC26_PAGE_ADDR(2, 0x06) + +#define AIC26_REG_FILTER_COEFF_L_N0 AIC26_PAGE_ADDR(2, 0x07) +#define AIC26_REG_FILTER_COEFF_L_N1 AIC26_PAGE_ADDR(2, 0x08) +#define AIC26_REG_FILTER_COEFF_L_N2 AIC26_PAGE_ADDR(2, 0x09) +#define AIC26_REG_FILTER_COEFF_L_N3 AIC26_PAGE_ADDR(2, 0x0A) +#define AIC26_REG_FILTER_COEFF_L_N4 AIC26_PAGE_ADDR(2, 0x0B) +#define AIC26_REG_FILTER_COEFF_L_N5 AIC26_PAGE_ADDR(2, 0x0C) +#define AIC26_REG_FILTER_COEFF_L_D1 AIC26_PAGE_ADDR(2, 0x0D) +#define AIC26_REG_FILTER_COEFF_L_D2 AIC26_PAGE_ADDR(2, 0x0E) +#define AIC26_REG_FILTER_COEFF_L_D4 AIC26_PAGE_ADDR(2, 0x0F) +#define AIC26_REG_FILTER_COEFF_L_D5 AIC26_PAGE_ADDR(2, 0x10) +#define AIC26_REG_FILTER_COEFF_R_N0 AIC26_PAGE_ADDR(2, 0x11) +#define AIC26_REG_FILTER_COEFF_R_N1 AIC26_PAGE_ADDR(2, 0x12) +#define AIC26_REG_FILTER_COEFF_R_N2 AIC26_PAGE_ADDR(2, 0x13) +#define AIC26_REG_FILTER_COEFF_R_N3 AIC26_PAGE_ADDR(2, 0x14) +#define AIC26_REG_FILTER_COEFF_R_N4 AIC26_PAGE_ADDR(2, 0x15) +#define AIC26_REG_FILTER_COEFF_R_N5 AIC26_PAGE_ADDR(2, 0x16) +#define AIC26_REG_FILTER_COEFF_R_D1 AIC26_PAGE_ADDR(2, 0x17) +#define AIC26_REG_FILTER_COEFF_R_D2 AIC26_PAGE_ADDR(2, 0x18) +#define AIC26_REG_FILTER_COEFF_R_D4 AIC26_PAGE_ADDR(2, 0x19) +#define AIC26_REG_FILTER_COEFF_R_D5 AIC26_PAGE_ADDR(2, 0x1A) + +#define AIC26_REG_PLL_PROG1 AIC26_PAGE_ADDR(2, 0x1B) +#define AIC26_REG_PLL_PROG2 AIC26_PAGE_ADDR(2, 0x1C) +#define AIC26_REG_AUDIO_CTRL4 AIC26_PAGE_ADDR(2, 0x1D) +#define AIC26_REG_AUDIO_CTRL5 AIC26_PAGE_ADDR(2, 0x1E) + +/* fsref dividers; used in register 'Audio Control 1' */ +enum aic26_divisors { + AIC26_DIV_1 = 0, + AIC26_DIV_1_5 = 1, + AIC26_DIV_2 = 2, + AIC26_DIV_3 = 3, + AIC26_DIV_4 = 4, + AIC26_DIV_5 = 5, + AIC26_DIV_5_5 = 6, + AIC26_DIV_6 = 7, +}; + +/* Digital data format */ +enum aic26_datfm { + AIC26_DATFM_I2S = 0 << 8, + AIC26_DATFM_DSP = 1 << 8, + AIC26_DATFM_RIGHTJ = 2 << 8, /* right justified */ + AIC26_DATFM_LEFTJ = 3 << 8, /* left justified */ +}; + +/* Sample word length in bits; used in register 'Audio Control 1' */ +enum aic26_wlen { + AIC26_WLEN_16 = 0 << 10, + AIC26_WLEN_20 = 1 << 10, + AIC26_WLEN_24 = 2 << 10, + AIC26_WLEN_32 = 3 << 10, +}; + +extern struct snd_soc_dai aic26_dai; +extern struct snd_soc_codec_device aic26_soc_codec_dev; + +#endif /* _TLV320AIC16_H_ */ diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 5f9abb1..05336ed 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1,7 +1,7 @@ /* * ALSA SoC TLV320AIC3X codec driver * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * Based on sound/soc/codecs/wm8753.c by Liam Girdwood @@ -48,7 +48,6 @@ #include "tlv320aic3x.h" -#define AUDIO_NAME "aic3x" #define AIC3X_VERSION "0.2" /* codec private data */ @@ -991,7 +990,7 @@ EXPORT_SYMBOL_GPL(aic3x_headset_detected); SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) struct snd_soc_dai aic3x_dai = { - .name = "aic3x", + .name = "tlv320aic3x", .playback = { .stream_name = "Playback", .channels_min = 1, @@ -1055,7 +1054,7 @@ static int aic3x_init(struct snd_soc_device *socdev) struct aic3x_setup_data *setup = socdev->codec_data; int reg, ret = 0; - codec->name = "aic3x"; + codec->name = "tlv320aic3x"; codec->owner = THIS_MODULE; codec->read = aic3x_read_reg_cache; codec->write = aic3x_write; @@ -1172,71 +1171,39 @@ static struct snd_soc_device *aic3x_socdev; * AIC3X 2 wire address can be up to 4 devices with device addresses * 0x18, 0x19, 0x1A, 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 aic3x_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 aic3x_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int aic3x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = aic3x_socdev; - struct aic3x_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) - return -ENOMEM; - i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - printk(KERN_ERR "aic3x: failed to attach codec at addr %x\n", - addr); - goto err; - } - ret = aic3x_init(socdev); - if (ret < 0) { + if (ret < 0) printk(KERN_ERR "aic3x: failed to initialise AIC3X\n"); - goto err; - } - return ret; - -err: - kfree(i2c); return ret; } -static int aic3x_i2c_detach(struct i2c_client *client) +static int aic3x_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 aic3x_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, aic3x_codec_probe); -} +static const struct i2c_device_id aic3x_i2c_id[] = { + { "tlv320aic3x", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aic3x_i2c_id); /* machine i2c codec control layer */ static struct i2c_driver aic3x_i2c_driver = { @@ -1244,13 +1211,9 @@ static struct i2c_driver aic3x_i2c_driver = { .name = "aic3x I2C Codec", .owner = THIS_MODULE, }, - .attach_adapter = aic3x_i2c_attach, - .detach_client = aic3x_i2c_detach, -}; - -static struct i2c_client client_template = { - .name = "AIC3X", - .driver = &aic3x_i2c_driver, + .probe = aic3x_i2c_probe, + .remove = aic3x_i2c_remove, + .id_table = aic3x_i2c_id, }; static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len) @@ -1258,6 +1221,46 @@ static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len) value[0] = i2c_smbus_read_byte_data(client, value[0]); return (len == 1); } + +static int aic3x_add_i2c_device(struct platform_device *pdev, + const struct aic3x_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&aic3x_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "tlv320aic3x", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&aic3x_i2c_driver); + return -ENODEV; +} #endif static int aic3x_probe(struct platform_device *pdev) @@ -1290,12 +1293,9 @@ static int aic3x_probe(struct platform_device *pdev) aic3x_socdev = socdev; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t) i2c_master_send; codec->hw_read = (hw_read_t) aic3x_i2c_read; - ret = i2c_add_driver(&aic3x_i2c_driver); - if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + ret = aic3x_add_i2c_device(pdev, setup); } #else /* Add other interfaces here */ @@ -1320,6 +1320,7 @@ static int aic3x_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(&aic3x_i2c_driver); #endif kfree(codec->private_data); diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h index d76c079..00a195a 100644 --- a/sound/soc/codecs/tlv320aic3x.h +++ b/sound/soc/codecs/tlv320aic3x.h @@ -1,7 +1,7 @@ /* * ALSA SoC TLV320AIC3X codec driver * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify @@ -224,6 +224,7 @@ int aic3x_get_gpio(struct snd_soc_codec *codec, int gpio); int aic3x_headset_detected(struct snd_soc_codec *codec); struct aic3x_setup_data { + int i2c_bus; unsigned short i2c_address; unsigned int gpio_func[2]; }; diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 807318f..a69ee72 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -36,7 +36,6 @@ #include "uda1380.h" #define UDA1380_VERSION "0.6" -#define AUDIO_NAME "uda1380" /* * uda1380 register cache @@ -701,87 +700,86 @@ static struct snd_soc_device *uda1380_socdev; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) -#define I2C_DRIVERID_UDA1380 0xfefe /* liam - need a proper id */ - -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; - -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static struct i2c_driver uda1380_i2c_driver; -static struct i2c_client client_template; - -/* If the i2c layer weren't so broken, we could pass this kind of data - around */ - -static int uda1380_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int uda1380_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = uda1380_socdev; struct uda1380_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; - 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) - return -ENOMEM; - i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - pr_err("uda1380: failed to attach codec at addr %x\n", addr); - goto err; - } - ret = uda1380_init(socdev, setup->dac_clk); - if (ret < 0) { + if (ret < 0) pr_err("uda1380: failed to initialise UDA1380\n"); - goto err; - } - return ret; -err: - kfree(i2c); return ret; } -static int uda1380_i2c_detach(struct i2c_client *client) +static int uda1380_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 uda1380_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, uda1380_codec_probe); -} +static const struct i2c_device_id uda1380_i2c_id[] = { + { "uda1380", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id); static struct i2c_driver uda1380_i2c_driver = { .driver = { .name = "UDA1380 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_UDA1380, - .attach_adapter = uda1380_i2c_attach, - .detach_client = uda1380_i2c_detach, - .command = NULL, + .probe = uda1380_i2c_probe, + .remove = uda1380_i2c_remove, + .id_table = uda1380_i2c_id, }; -static struct i2c_client client_template = { - .name = "UDA1380", - .driver = &uda1380_i2c_driver, -}; +static int uda1380_add_i2c_device(struct platform_device *pdev, + const struct uda1380_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&uda1380_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "uda1380", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&uda1380_i2c_driver); + return -ENODEV; +} #endif static int uda1380_probe(struct platform_device *pdev) @@ -789,7 +787,7 @@ static int uda1380_probe(struct platform_device *pdev) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct uda1380_setup_data *setup; struct snd_soc_codec *codec; - int ret = 0; + int ret; pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION); @@ -804,16 +802,13 @@ static int uda1380_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_paths); uda1380_socdev = socdev; + ret = -ENODEV; + #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; - ret = i2c_add_driver(&uda1380_i2c_driver); - if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + ret = uda1380_add_i2c_device(pdev, setup); } -#else - /* Add other interfaces here */ #endif if (ret != 0) @@ -833,6 +828,7 @@ static int uda1380_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(&uda1380_i2c_driver); #endif kfree(codec); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h index 50c603e..c55c17a 100644 --- a/sound/soc/codecs/uda1380.h +++ b/sound/soc/codecs/uda1380.h @@ -73,6 +73,7 @@ #define R23_AGC_EN 0x0001 struct uda1380_setup_data { + int i2c_bus; unsigned short i2c_address; int dac_clk; #define UDA1380_DAC_CLK_SYSCLK 0 diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 3d998e6..d8ca2da 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -3,7 +3,7 @@ * * Copyright 2006 Wolfson Microelectronics PLC. * - * Author: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -18,6 +18,7 @@ #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> @@ -27,7 +28,6 @@ #include "wm8510.h" -#define AUDIO_NAME "wm8510" #define WM8510_VERSION "0.6" struct snd_soc_codec_device soc_codec_dev_wm8510; @@ -55,6 +55,9 @@ static const u16 wm8510_reg[WM8510_CACHEREGNUM] = { 0x0001, }; +#define WM8510_POWER1_BIASEN 0x08 +#define WM8510_POWER1_BUFIOEN 0x10 + /* * read wm8510 register cache */ @@ -199,7 +202,7 @@ SOC_DAPM_SINGLE("PCM Playback Switch", WM8510_MONOMIX, 0, 1, 0), }; static const struct snd_kcontrol_new wm8510_boost_controls[] = { -SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 0), +SOC_DAPM_SINGLE("Mic PGA Switch", WM8510_INPPGA, 6, 1, 1), SOC_DAPM_SINGLE("Aux Volume", WM8510_ADCBOOST, 0, 7, 0), SOC_DAPM_SINGLE("Mic Volume", WM8510_ADCBOOST, 4, 7, 0), }; @@ -224,9 +227,9 @@ SND_SOC_DAPM_PGA("SpkN Out", WM8510_POWER3, 5, 0, NULL, 0), SND_SOC_DAPM_PGA("SpkP Out", WM8510_POWER3, 6, 0, NULL, 0), SND_SOC_DAPM_PGA("Mono Out", WM8510_POWER3, 7, 0, NULL, 0), -SND_SOC_DAPM_PGA("Mic PGA", WM8510_POWER2, 2, 0, - &wm8510_micpga_controls[0], - ARRAY_SIZE(wm8510_micpga_controls)), +SND_SOC_DAPM_MIXER("Mic PGA", WM8510_POWER2, 2, 0, + &wm8510_micpga_controls[0], + ARRAY_SIZE(wm8510_micpga_controls)), SND_SOC_DAPM_MIXER("Boost Mixer", WM8510_POWER2, 4, 0, &wm8510_boost_controls[0], ARRAY_SIZE(wm8510_boost_controls)), @@ -526,23 +529,35 @@ static int wm8510_mute(struct snd_soc_dai *dai, int mute) static int wm8510_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + u16 power1 = wm8510_read_reg_cache(codec, WM8510_POWER1) & ~0x3; switch (level) { case SND_SOC_BIAS_ON: - wm8510_write(codec, WM8510_POWER1, 0x1ff); - wm8510_write(codec, WM8510_POWER2, 0x1ff); - wm8510_write(codec, WM8510_POWER3, 0x1ff); - break; case SND_SOC_BIAS_PREPARE: + power1 |= 0x1; /* VMID 50k */ + wm8510_write(codec, WM8510_POWER1, power1); + break; + case SND_SOC_BIAS_STANDBY: + power1 |= WM8510_POWER1_BIASEN | WM8510_POWER1_BUFIOEN; + + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Initial cap charge at VMID 5k */ + wm8510_write(codec, WM8510_POWER1, power1 | 0x3); + mdelay(100); + } + + power1 |= 0x2; /* VMID 500k */ + wm8510_write(codec, WM8510_POWER1, power1); break; + case SND_SOC_BIAS_OFF: - /* everything off, dac mute, inactive */ - wm8510_write(codec, WM8510_POWER1, 0x0); - wm8510_write(codec, WM8510_POWER2, 0x0); - wm8510_write(codec, WM8510_POWER3, 0x0); + wm8510_write(codec, WM8510_POWER1, 0); + wm8510_write(codec, WM8510_POWER2, 0); + wm8510_write(codec, WM8510_POWER3, 0); break; } + codec->bias_level = level; return 0; } @@ -640,6 +655,7 @@ static int wm8510_init(struct snd_soc_device *socdev) } /* power on device */ + codec->bias_level = SND_SOC_BIAS_OFF; wm8510_set_bias_level(codec, SND_SOC_BIAS_STANDBY); wm8510_add_controls(codec); wm8510_add_widgets(codec); @@ -665,90 +681,144 @@ static struct snd_soc_device *wm8510_socdev; /* * WM8510 2 wire address is 0x1a */ -#define I2C_DRIVERID_WM8510 0xfefe /* liam - need a proper id */ - -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static struct i2c_driver wm8510_i2c_driver; -static struct i2c_client client_template; - -/* If the i2c layer weren't so broken, we could pass this kind of data - around */ - -static int wm8510_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int wm8510_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8510_socdev; - struct wm8510_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c; int ret; - if (addr != setup->i2c_address) - return -ENODEV; - - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (i2c == NULL) - return -ENOMEM; - i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - pr_err("failed to attach codec at addr %x\n", addr); - goto err; - } - ret = wm8510_init(socdev); - if (ret < 0) { + if (ret < 0) pr_err("failed to initialise WM8510\n"); - goto err; - } - return ret; -err: - kfree(i2c); return ret; } -static int wm8510_i2c_detach(struct i2c_client *client) +static int wm8510_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 wm8510_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8510_codec_probe); -} +static const struct i2c_device_id wm8510_i2c_id[] = { + { "wm8510", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8510_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver wm8510_i2c_driver = { .driver = { .name = "WM8510 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_WM8510, - .attach_adapter = wm8510_i2c_attach, - .detach_client = wm8510_i2c_detach, - .command = NULL, + .probe = wm8510_i2c_probe, + .remove = wm8510_i2c_remove, + .id_table = wm8510_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8510", - .driver = &wm8510_i2c_driver, -}; +static int wm8510_add_i2c_device(struct platform_device *pdev, + const struct wm8510_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8510_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8510", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8510_i2c_driver); + return -ENODEV; +} #endif +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8510_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8510_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8510_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8510\n"); + + return ret; +} + +static int __devexit wm8510_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8510_spi_driver = { + .driver = { + .name = "wm8510", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8510_spi_probe, + .remove = __devexit_p(wm8510_spi_remove), +}; + +static int wm8510_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif /* CONFIG_SPI_MASTER */ + static int wm8510_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -771,14 +841,17 @@ static int wm8510_probe(struct platform_device *pdev) wm8510_socdev = socdev; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; - ret = i2c_add_driver(&wm8510_i2c_driver); + ret = wm8510_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8510_spi_write; + ret = spi_register_driver(&wm8510_spi_driver); if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + printk(KERN_ERR "can't add spi driver"); } -#else - /* Add other interfaces here */ #endif if (ret != 0) @@ -798,8 +871,12 @@ static int wm8510_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(&wm8510_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8510_spi_driver); +#endif kfree(codec); return 0; diff --git a/sound/soc/codecs/wm8510.h b/sound/soc/codecs/wm8510.h index f5d2e42..bdefcf5 100644 --- a/sound/soc/codecs/wm8510.h +++ b/sound/soc/codecs/wm8510.h @@ -94,6 +94,8 @@ #define WM8510_MCLKDIV_12 (7 << 5) struct wm8510_setup_data { + int spi; + int i2c_bus; unsigned short i2c_address; }; diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c new file mode 100644 index 0000000..627ebfb --- /dev/null +++ b/sound/soc/codecs/wm8580.c @@ -0,0 +1,1053 @@ +/* + * wm8580.c -- WM8580 ALSA Soc Audio driver + * + * Copyright 2008 Wolfson Microelectronics PLC. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Notes: + * The WM8580 is a multichannel codec with S/PDIF support, featuring six + * DAC channels and two ADC channels. + * + * Currently only the primary audio interface is supported - S/PDIF and + * the secondary audio interfaces are not. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include <asm/div64.h> + +#include "wm8580.h" + +#define WM8580_VERSION "0.1" + +struct pll_state { + unsigned int in; + unsigned int out; +}; + +/* codec private data */ +struct wm8580_priv { + struct pll_state a; + struct pll_state b; +}; + +/* WM8580 register space */ +#define WM8580_PLLA1 0x00 +#define WM8580_PLLA2 0x01 +#define WM8580_PLLA3 0x02 +#define WM8580_PLLA4 0x03 +#define WM8580_PLLB1 0x04 +#define WM8580_PLLB2 0x05 +#define WM8580_PLLB3 0x06 +#define WM8580_PLLB4 0x07 +#define WM8580_CLKSEL 0x08 +#define WM8580_PAIF1 0x09 +#define WM8580_PAIF2 0x0A +#define WM8580_SAIF1 0x0B +#define WM8580_PAIF3 0x0C +#define WM8580_PAIF4 0x0D +#define WM8580_SAIF2 0x0E +#define WM8580_DAC_CONTROL1 0x0F +#define WM8580_DAC_CONTROL2 0x10 +#define WM8580_DAC_CONTROL3 0x11 +#define WM8580_DAC_CONTROL4 0x12 +#define WM8580_DAC_CONTROL5 0x13 +#define WM8580_DIGITAL_ATTENUATION_DACL1 0x14 +#define WM8580_DIGITAL_ATTENUATION_DACR1 0x15 +#define WM8580_DIGITAL_ATTENUATION_DACL2 0x16 +#define WM8580_DIGITAL_ATTENUATION_DACR2 0x17 +#define WM8580_DIGITAL_ATTENUATION_DACL3 0x18 +#define WM8580_DIGITAL_ATTENUATION_DACR3 0x19 +#define WM8580_MASTER_DIGITAL_ATTENUATION 0x1C +#define WM8580_ADC_CONTROL1 0x1D +#define WM8580_SPDTXCHAN0 0x1E +#define WM8580_SPDTXCHAN1 0x1F +#define WM8580_SPDTXCHAN2 0x20 +#define WM8580_SPDTXCHAN3 0x21 +#define WM8580_SPDTXCHAN4 0x22 +#define WM8580_SPDTXCHAN5 0x23 +#define WM8580_SPDMODE 0x24 +#define WM8580_INTMASK 0x25 +#define WM8580_GPO1 0x26 +#define WM8580_GPO2 0x27 +#define WM8580_GPO3 0x28 +#define WM8580_GPO4 0x29 +#define WM8580_GPO5 0x2A +#define WM8580_INTSTAT 0x2B +#define WM8580_SPDRXCHAN1 0x2C +#define WM8580_SPDRXCHAN2 0x2D +#define WM8580_SPDRXCHAN3 0x2E +#define WM8580_SPDRXCHAN4 0x2F +#define WM8580_SPDRXCHAN5 0x30 +#define WM8580_SPDSTAT 0x31 +#define WM8580_PWRDN1 0x32 +#define WM8580_PWRDN2 0x33 +#define WM8580_READBACK 0x34 +#define WM8580_RESET 0x35 + +/* PLLB4 (register 7h) */ +#define WM8580_PLLB4_MCLKOUTSRC_MASK 0x60 +#define WM8580_PLLB4_MCLKOUTSRC_PLLA 0x20 +#define WM8580_PLLB4_MCLKOUTSRC_PLLB 0x40 +#define WM8580_PLLB4_MCLKOUTSRC_OSC 0x60 + +#define WM8580_PLLB4_CLKOUTSRC_MASK 0x180 +#define WM8580_PLLB4_CLKOUTSRC_PLLACLK 0x080 +#define WM8580_PLLB4_CLKOUTSRC_PLLBCLK 0x100 +#define WM8580_PLLB4_CLKOUTSRC_OSCCLK 0x180 + +/* CLKSEL (register 8h) */ +#define WM8580_CLKSEL_DAC_CLKSEL_MASK 0x03 +#define WM8580_CLKSEL_DAC_CLKSEL_PLLA 0x01 +#define WM8580_CLKSEL_DAC_CLKSEL_PLLB 0x02 + +/* AIF control 1 (registers 9h-bh) */ +#define WM8580_AIF_RATE_MASK 0x7 +#define WM8580_AIF_RATE_128 0x0 +#define WM8580_AIF_RATE_192 0x1 +#define WM8580_AIF_RATE_256 0x2 +#define WM8580_AIF_RATE_384 0x3 +#define WM8580_AIF_RATE_512 0x4 +#define WM8580_AIF_RATE_768 0x5 +#define WM8580_AIF_RATE_1152 0x6 + +#define WM8580_AIF_BCLKSEL_MASK 0x18 +#define WM8580_AIF_BCLKSEL_64 0x00 +#define WM8580_AIF_BCLKSEL_128 0x08 +#define WM8580_AIF_BCLKSEL_256 0x10 +#define WM8580_AIF_BCLKSEL_SYSCLK 0x18 + +#define WM8580_AIF_MS 0x20 + +#define WM8580_AIF_CLKSRC_MASK 0xc0 +#define WM8580_AIF_CLKSRC_PLLA 0x40 +#define WM8580_AIF_CLKSRC_PLLB 0x40 +#define WM8580_AIF_CLKSRC_MCLK 0xc0 + +/* AIF control 2 (registers ch-eh) */ +#define WM8580_AIF_FMT_MASK 0x03 +#define WM8580_AIF_FMT_RIGHTJ 0x00 +#define WM8580_AIF_FMT_LEFTJ 0x01 +#define WM8580_AIF_FMT_I2S 0x02 +#define WM8580_AIF_FMT_DSP 0x03 + +#define WM8580_AIF_LENGTH_MASK 0x0c +#define WM8580_AIF_LENGTH_16 0x00 +#define WM8580_AIF_LENGTH_20 0x04 +#define WM8580_AIF_LENGTH_24 0x08 +#define WM8580_AIF_LENGTH_32 0x0c + +#define WM8580_AIF_LRP 0x10 +#define WM8580_AIF_BCP 0x20 + +/* Powerdown Register 1 (register 32h) */ +#define WM8580_PWRDN1_PWDN 0x001 +#define WM8580_PWRDN1_ALLDACPD 0x040 + +/* Powerdown Register 2 (register 33h) */ +#define WM8580_PWRDN2_OSSCPD 0x001 +#define WM8580_PWRDN2_PLLAPD 0x002 +#define WM8580_PWRDN2_PLLBPD 0x004 +#define WM8580_PWRDN2_SPDIFPD 0x008 +#define WM8580_PWRDN2_SPDIFTXD 0x010 +#define WM8580_PWRDN2_SPDIFRXD 0x020 + +#define WM8580_DAC_CONTROL5_MUTEALL 0x10 + +/* + * wm8580 register cache + * We can't read the WM8580 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8580_reg[] = { + 0x0121, 0x017e, 0x007d, 0x0014, /*R3*/ + 0x0121, 0x017e, 0x007d, 0x0194, /*R7*/ + 0x001c, 0x0002, 0x0002, 0x00c2, /*R11*/ + 0x0182, 0x0082, 0x000a, 0x0024, /*R15*/ + 0x0009, 0x0000, 0x00ff, 0x0000, /*R19*/ + 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R23*/ + 0x00ff, 0x00ff, 0x00ff, 0x00ff, /*R27*/ + 0x01f0, 0x0040, 0x0000, 0x0000, /*R31(0x1F)*/ + 0x0000, 0x0000, 0x0031, 0x000b, /*R35*/ + 0x0039, 0x0000, 0x0010, 0x0032, /*R39*/ + 0x0054, 0x0076, 0x0098, 0x0000, /*R43(0x2B)*/ + 0x0000, 0x0000, 0x0000, 0x0000, /*R47*/ + 0x0000, 0x0000, 0x005e, 0x003e, /*R51(0x33)*/ + 0x0000, 0x0000 /*R53*/ +}; + +/* + * read wm8580 register cache + */ +static inline unsigned int wm8580_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > ARRAY_SIZE(wm8580_reg)); + return cache[reg]; +} + +/* + * write wm8580 register cache + */ +static inline void wm8580_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + cache[reg] = value; +} + +/* + * write to the WM8580 register space + */ +static int wm8580_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + BUG_ON(reg > ARRAY_SIZE(wm8580_reg)); + + /* Registers are 9 bits wide */ + value &= 0x1ff; + + switch (reg) { + case WM8580_RESET: + /* Uncached */ + break; + default: + if (value == wm8580_read_reg_cache(codec, reg)) + return 0; + } + + /* data is + * D15..D9 WM8580 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8580_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static inline unsigned int wm8580_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + default: + return wm8580_read_reg_cache(codec, reg); + } +} + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); + +static int wm8580_out_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int reg2 = (kcontrol->private_value >> 24) & 0xff; + int ret; + u16 val; + + /* Clear the register cache so we write without VU set */ + wm8580_write_reg_cache(codec, reg, 0); + wm8580_write_reg_cache(codec, reg2, 0); + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* Now write again with the volume update bit set */ + val = wm8580_read_reg_cache(codec, reg); + wm8580_write(codec, reg, val | 0x0100); + + val = wm8580_read_reg_cache(codec, reg2); + wm8580_write(codec, reg2, val | 0x0100); + + return 0; +} + +#define SOC_WM8580_OUT_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r, \ + .get = snd_soc_get_volsw_2r, .put = wm8580_out_vu, \ + .private_value = (reg_left) | ((shift) << 8) | \ + ((max) << 12) | ((invert) << 20) | ((reg_right) << 24) } + +static const struct snd_kcontrol_new wm8580_snd_controls[] = { +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC1 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL1, + WM8580_DIGITAL_ATTENUATION_DACR1, + 0, 0xff, 0, dac_tlv), +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC2 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL2, + WM8580_DIGITAL_ATTENUATION_DACR2, + 0, 0xff, 0, dac_tlv), +SOC_WM8580_OUT_DOUBLE_R_TLV("DAC3 Playback Volume", + WM8580_DIGITAL_ATTENUATION_DACL3, + WM8580_DIGITAL_ATTENUATION_DACR3, + 0, 0xff, 0, dac_tlv), + +SOC_SINGLE("DAC1 Deemphasis Switch", WM8580_DAC_CONTROL3, 0, 1, 0), +SOC_SINGLE("DAC2 Deemphasis Switch", WM8580_DAC_CONTROL3, 1, 1, 0), +SOC_SINGLE("DAC3 Deemphasis Switch", WM8580_DAC_CONTROL3, 2, 1, 0), + +SOC_DOUBLE("DAC1 Invert Switch", WM8580_DAC_CONTROL4, 0, 1, 1, 0), +SOC_DOUBLE("DAC2 Invert Switch", WM8580_DAC_CONTROL4, 2, 3, 1, 0), +SOC_DOUBLE("DAC3 Invert Switch", WM8580_DAC_CONTROL4, 4, 5, 1, 0), + +SOC_SINGLE("DAC ZC Switch", WM8580_DAC_CONTROL5, 5, 1, 0), +SOC_SINGLE("DAC1 Switch", WM8580_DAC_CONTROL5, 0, 1, 0), +SOC_SINGLE("DAC2 Switch", WM8580_DAC_CONTROL5, 1, 1, 0), +SOC_SINGLE("DAC3 Switch", WM8580_DAC_CONTROL5, 2, 1, 0), + +SOC_DOUBLE("ADC Mute Switch", WM8580_ADC_CONTROL1, 0, 1, 1, 0), +SOC_SINGLE("ADC High-Pass Filter Switch", WM8580_ADC_CONTROL1, 4, 1, 0), +}; + +/* Add non-DAPM controls */ +static int wm8580_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8580_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8580_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} +static const struct snd_soc_dapm_widget wm8580_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC1", "Playback", WM8580_PWRDN1, 2, 1), +SND_SOC_DAPM_DAC("DAC2", "Playback", WM8580_PWRDN1, 3, 1), +SND_SOC_DAPM_DAC("DAC3", "Playback", WM8580_PWRDN1, 4, 1), + +SND_SOC_DAPM_OUTPUT("VOUT1L"), +SND_SOC_DAPM_OUTPUT("VOUT1R"), +SND_SOC_DAPM_OUTPUT("VOUT2L"), +SND_SOC_DAPM_OUTPUT("VOUT2R"), +SND_SOC_DAPM_OUTPUT("VOUT3L"), +SND_SOC_DAPM_OUTPUT("VOUT3R"), + +SND_SOC_DAPM_ADC("ADC", "Capture", WM8580_PWRDN1, 1, 1), + +SND_SOC_DAPM_INPUT("AINL"), +SND_SOC_DAPM_INPUT("AINR"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + { "VOUT1L", NULL, "DAC1" }, + { "VOUT1R", NULL, "DAC1" }, + + { "VOUT2L", NULL, "DAC2" }, + { "VOUT2R", NULL, "DAC2" }, + + { "VOUT3L", NULL, "DAC3" }, + { "VOUT3R", NULL, "DAC3" }, + + { "ADC", NULL, "AINL" }, + { "ADC", NULL, "AINR" }, +}; + +static int wm8580_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets, + ARRAY_SIZE(wm8580_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 prescale:1; + u32 postscale:1; + u32 freqmode:2; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide */ +#define FIXED_PLL_SIZE (1 << 22) + +/* PLL rate to output rate divisions */ +static struct { + unsigned int div; + unsigned int freqmode; + unsigned int postscale; +} post_table[] = { + { 2, 0, 0 }, + { 4, 0, 1 }, + { 4, 1, 0 }, + { 8, 1, 1 }, + { 8, 2, 0 }, + { 16, 2, 1 }, + { 12, 3, 0 }, + { 24, 3, 1 } +}; + +static int pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + int i; + + pr_debug("wm8580: PLL %dHz->%dHz\n", source, target); + + /* Scale the output frequency up; the PLL should run in the + * region of 90-100MHz. + */ + for (i = 0; i < ARRAY_SIZE(post_table); i++) { + if (target * post_table[i].div >= 90000000 && + target * post_table[i].div <= 100000000) { + pll_div->freqmode = post_table[i].freqmode; + pll_div->postscale = post_table[i].postscale; + target *= post_table[i].div; + break; + } + } + + if (i == ARRAY_SIZE(post_table)) { + printk(KERN_ERR "wm8580: Unable to scale output frequency " + "%u\n", target); + return -EINVAL; + } + + Ndiv = target / source; + + if (Ndiv < 5) { + source /= 2; + pll_div->prescale = 1; + Ndiv = target / source; + } else + pll_div->prescale = 0; + + if ((Ndiv < 5) || (Ndiv > 13)) { + printk(KERN_ERR + "WM8580 N=%d outside supported range\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + pll_div->k = K; + + pr_debug("PLL %x.%x prescale %d freqmode %d postscale %d\n", + pll_div->n, pll_div->k, pll_div->prescale, pll_div->freqmode, + pll_div->postscale); + + return 0; +} + +static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + int offset; + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8580_priv *wm8580 = codec->private_data; + struct pll_state *state; + struct _pll_div pll_div; + unsigned int reg; + unsigned int pwr_mask; + int ret; + + /* GCC isn't able to work out the ifs below for initialising/using + * pll_div so suppress warnings. + */ + memset(&pll_div, 0, sizeof(pll_div)); + + switch (pll_id) { + case WM8580_PLLA: + state = &wm8580->a; + offset = 0; + pwr_mask = WM8580_PWRDN2_PLLAPD; + break; + case WM8580_PLLB: + state = &wm8580->b; + offset = 4; + pwr_mask = WM8580_PWRDN2_PLLBPD; + break; + default: + return -ENODEV; + } + + if (freq_in && freq_out) { + ret = pll_factors(&pll_div, freq_out, freq_in); + if (ret != 0) + return ret; + } + + state->in = freq_in; + state->out = freq_out; + + /* Always disable the PLL - it is not safe to leave it running + * while reprogramming it. + */ + reg = wm8580_read(codec, WM8580_PWRDN2); + wm8580_write(codec, WM8580_PWRDN2, reg | pwr_mask); + + if (!freq_in || !freq_out) + return 0; + + wm8580_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff); + wm8580_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0xff); + wm8580_write(codec, WM8580_PLLA3 + offset, + (pll_div.k >> 18 & 0xf) | (pll_div.n << 4)); + + reg = wm8580_read(codec, WM8580_PLLA4 + offset); + reg &= ~0x3f; + reg |= pll_div.prescale | pll_div.postscale << 1 | + pll_div.freqmode << 4; + + wm8580_write(codec, WM8580_PLLA4 + offset, reg); + + /* All done, turn it on */ + reg = wm8580_read(codec, WM8580_PWRDN2); + wm8580_write(codec, WM8580_PWRDN2, reg & ~pwr_mask); + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8580_paif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *dai = rtd->dai; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->codec_dai->id); + + paifb &= ~WM8580_AIF_LENGTH_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + paifb |= WM8580_AIF_LENGTH_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + paifb |= WM8580_AIF_LENGTH_24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + paifb |= WM8580_AIF_LENGTH_24; + break; + default: + return -EINVAL; + } + + wm8580_write(codec, WM8580_PAIF3 + dai->codec_dai->id, paifb); + return 0; +} + +static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int aifa; + unsigned int aifb; + int can_invert_lrclk; + + aifa = wm8580_read(codec, WM8580_PAIF1 + codec_dai->id); + aifb = wm8580_read(codec, WM8580_PAIF3 + codec_dai->id); + + aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + aifa &= ~WM8580_AIF_MS; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aifa |= WM8580_AIF_MS; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_RIGHTJ; + break; + case SND_SOC_DAIFMT_LEFT_J: + can_invert_lrclk = 1; + aifb |= WM8580_AIF_FMT_LEFTJ; + break; + case SND_SOC_DAIFMT_DSP_A: + can_invert_lrclk = 0; + aifb |= WM8580_AIF_FMT_DSP; + break; + case SND_SOC_DAIFMT_DSP_B: + can_invert_lrclk = 0; + aifb |= WM8580_AIF_FMT_DSP; + aifb |= WM8580_AIF_LRP; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + + case SND_SOC_DAIFMT_IB_IF: + if (!can_invert_lrclk) + return -EINVAL; + aifb |= WM8580_AIF_BCP; + aifb |= WM8580_AIF_LRP; + break; + + case SND_SOC_DAIFMT_IB_NF: + aifb |= WM8580_AIF_BCP; + break; + + case SND_SOC_DAIFMT_NB_IF: + if (!can_invert_lrclk) + return -EINVAL; + aifb |= WM8580_AIF_LRP; + break; + + default: + return -EINVAL; + } + + wm8580_write(codec, WM8580_PAIF1 + codec_dai->id, aifa); + wm8580_write(codec, WM8580_PAIF3 + codec_dai->id, aifb); + + return 0; +} + +static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + switch (div_id) { + case WM8580_MCLK: + reg = wm8580_read(codec, WM8580_PLLB4); + reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK; + + switch (div) { + case WM8580_CLKSRC_MCLK: + /* Input */ + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_PLLB4_MCLKOUTSRC_PLLA; + break; + case WM8580_CLKSRC_PLLB: + reg |= WM8580_PLLB4_MCLKOUTSRC_PLLB; + break; + + case WM8580_CLKSRC_OSC: + reg |= WM8580_PLLB4_MCLKOUTSRC_OSC; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_PLLB4, reg); + break; + + case WM8580_DAC_CLKSEL: + reg = wm8580_read(codec, WM8580_CLKSEL); + reg &= ~WM8580_CLKSEL_DAC_CLKSEL_MASK; + + switch (div) { + case WM8580_CLKSRC_MCLK: + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLA; + break; + + case WM8580_CLKSRC_PLLB: + reg |= WM8580_CLKSEL_DAC_CLKSEL_PLLB; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_CLKSEL, reg); + break; + + case WM8580_CLKOUTSRC: + reg = wm8580_read(codec, WM8580_PLLB4); + reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK; + + switch (div) { + case WM8580_CLKSRC_NONE: + break; + + case WM8580_CLKSRC_PLLA: + reg |= WM8580_PLLB4_CLKOUTSRC_PLLACLK; + break; + + case WM8580_CLKSRC_PLLB: + reg |= WM8580_PLLB4_CLKOUTSRC_PLLBCLK; + break; + + case WM8580_CLKSRC_OSC: + reg |= WM8580_PLLB4_CLKOUTSRC_OSCCLK; + break; + + default: + return -EINVAL; + } + wm8580_write(codec, WM8580_PLLB4, reg); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + reg = wm8580_read(codec, WM8580_DAC_CONTROL5); + + if (mute) + reg |= WM8580_DAC_CONTROL5_MUTEALL; + else + reg &= ~WM8580_DAC_CONTROL5_MUTEALL; + + wm8580_write(codec, WM8580_DAC_CONTROL5, reg); + + return 0; +} + +static int wm8580_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + reg = wm8580_read(codec, WM8580_PWRDN1); + wm8580_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8580_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai wm8580_dai[] = { + { + .name = "WM8580 PAIFRX", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8580_FORMATS, + }, + .ops = { + .hw_params = wm8580_paif_hw_params, + }, + .dai_ops = { + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, + .digital_mute = wm8580_digital_mute, + }, + }, + { + .name = "WM8580 PAIFTX", + .id = 1, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = WM8580_FORMATS, + }, + .ops = { + .hw_params = wm8580_paif_hw_params, + }, + .dai_ops = { + .set_fmt = wm8580_set_paif_dai_fmt, + .set_clkdiv = wm8580_set_dai_clkdiv, + .set_pll = wm8580_set_dai_pll, + }, + }, +}; +EXPORT_SYMBOL_GPL(wm8580_dai); + +/* + * initialise the WM8580 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8580_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + + codec->name = "WM8580"; + codec->owner = THIS_MODULE; + codec->read = wm8580_read_reg_cache; + codec->write = wm8580_write; + codec->set_bias_level = wm8580_set_bias_level; + codec->dai = wm8580_dai; + codec->num_dai = ARRAY_SIZE(wm8580_dai); + codec->reg_cache_size = ARRAY_SIZE(wm8580_reg); + codec->reg_cache = kmemdup(wm8580_reg, sizeof(wm8580_reg), + GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + /* Get the codec into a known state */ + wm8580_write(codec, WM8580_RESET, 0); + + /* Power up and get individual control of the DACs */ + wm8580_write(codec, WM8580_PWRDN1, wm8580_read(codec, WM8580_PWRDN1) & + ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD)); + + /* Make VMID high impedence */ + wm8580_write(codec, WM8580_ADC_CONTROL1, + wm8580_read(codec, WM8580_ADC_CONTROL1) & ~0x100); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8580: failed to create pcms\n"); + goto pcm_err; + } + + wm8580_add_controls(codec); + wm8580_add_widgets(codec); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8580: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8580_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +/* + * WM8580 2 wire address is determined by GPIO5 + * state during powerup. + * low = 0x1a + * high = 0x1b + */ +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8580_i2c_driver; +static struct i2c_client client_template; + +static int wm8580_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8580_socdev; + struct wm8580_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + dev_err(&i2c->dev, "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8580_init(socdev); + if (ret < 0) { + dev_err(&i2c->dev, "failed to initialise WM8580\n"); + goto err; + } + + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8580_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8580_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8580_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8580_i2c_driver = { + .driver = { + .name = "WM8580 I2C Codec", + .owner = THIS_MODULE, + }, + .attach_adapter = wm8580_i2c_attach, + .detach_client = wm8580_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8580", + .driver = &wm8580_i2c_driver, +}; +#endif + +static int wm8580_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8580_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8580_priv *wm8580; + int ret = 0; + + pr_info("WM8580 Audio Codec %s\n", WM8580_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8580 = kzalloc(sizeof(struct wm8580_priv), GFP_KERNEL); + if (wm8580 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8580; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8580_socdev = socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8580_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else + /* Add other interfaces here */ +#endif + return ret; +} + +/* power down chip */ +static int wm8580_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8580_set_bias_level(codec, SND_SOC_BIAS_OFF); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8580_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8580 = { + .probe = wm8580_probe, + .remove = wm8580_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580); + +MODULE_DESCRIPTION("ASoC WM8580 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8580.h b/sound/soc/codecs/wm8580.h new file mode 100644 index 0000000..589ddab --- /dev/null +++ b/sound/soc/codecs/wm8580.h @@ -0,0 +1,42 @@ +/* + * wm8580.h -- audio driver for WM8580 + * + * Copyright 2008 Samsung Electronics. + * Author: Ryu Euiyoul + * ryu.real@gmail.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8580_H +#define _WM8580_H + +#define WM8580_PLLA 1 +#define WM8580_PLLB 2 + +#define WM8580_MCLK 1 +#define WM8580_DAC_CLKSEL 2 +#define WM8580_CLKOUTSRC 3 + +#define WM8580_CLKSRC_MCLK 1 +#define WM8580_CLKSRC_PLLA 2 +#define WM8580_CLKSRC_PLLB 3 +#define WM8580_CLKSRC_OSC 4 +#define WM8580_CLKSRC_NONE 5 + +struct wm8580_setup_data { + unsigned short i2c_address; +}; + +#define WM8580_DAI_PAIFRX 0 +#define WM8580_DAI_PAIFTX 1 + +extern struct snd_soc_dai wm8580_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_wm8580; + +#endif + diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 9402fca..7f8a7e3 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -19,6 +19,7 @@ #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> @@ -28,7 +29,6 @@ #include "wm8731.h" -#define AUDIO_NAME "wm8731" #define WM8731_VERSION "0.13" struct snd_soc_codec_device soc_codec_dev_wm8731; @@ -570,88 +570,144 @@ static struct snd_soc_device *wm8731_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 wm8731_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 wm8731_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int wm8731_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8731_socdev; - struct wm8731_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) - return -ENOMEM; - i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - pr_err("failed to attach codec at addr %x\n", addr); - goto err; - } - ret = wm8731_init(socdev); - if (ret < 0) { + if (ret < 0) pr_err("failed to initialise WM8731\n"); - goto err; - } - return ret; -err: - kfree(i2c); return ret; } -static int wm8731_i2c_detach(struct i2c_client *client) +static int wm8731_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 wm8731_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8731_codec_probe); -} +static const struct i2c_device_id wm8731_i2c_id[] = { + { "wm8731", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8731_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver wm8731_i2c_driver = { .driver = { .name = "WM8731 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_WM8731, - .attach_adapter = wm8731_i2c_attach, - .detach_client = wm8731_i2c_detach, - .command = NULL, + .probe = wm8731_i2c_probe, + .remove = wm8731_i2c_remove, + .id_table = wm8731_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8731", - .driver = &wm8731_i2c_driver, -}; +static int wm8731_add_i2c_device(struct platform_device *pdev, + const struct wm8731_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8731_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8731", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8731_i2c_driver); + return -ENODEV; +} #endif +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8731_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8731_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8731_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8731\n"); + + return ret; +} + +static int __devexit wm8731_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8731_spi_driver = { + .driver = { + .name = "wm8731", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8731_spi_probe, + .remove = __devexit_p(wm8731_spi_remove), +}; + +static int wm8731_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#endif /* CONFIG_SPI_MASTER */ + static int wm8731_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -680,16 +736,21 @@ static int wm8731_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_paths); wm8731_socdev = socdev; + ret = -ENODEV; + #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(&wm8731_i2c_driver); + ret = wm8731_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8731_spi_write; + ret = spi_register_driver(&wm8731_spi_driver); if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + printk(KERN_ERR "can't add spi driver"); } -#else - /* Add other interfaces here */ #endif if (ret != 0) { @@ -711,8 +772,12 @@ static int wm8731_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(&wm8731_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8731_spi_driver); +#endif kfree(codec->private_data); kfree(codec); diff --git a/sound/soc/codecs/wm8731.h b/sound/soc/codecs/wm8731.h index 99f2e3c..95190e9 100644 --- a/sound/soc/codecs/wm8731.h +++ b/sound/soc/codecs/wm8731.h @@ -35,6 +35,8 @@ #define WM8731_DAI 0 struct wm8731_setup_data { + int spi; + int i2c_bus; unsigned short i2c_address; }; diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index dd1f554..9b7296e 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -19,6 +19,7 @@ #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> @@ -28,7 +29,6 @@ #include "wm8750.h" -#define AUDIO_NAME "WM8750" #define WM8750_VERSION "0.12" /* codec private data */ @@ -841,88 +841,147 @@ static struct snd_soc_device *wm8750_socdev; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) /* - * WM8731 2 wire address is determined by GPIO5 + * WM8750 2 wire address is determined by GPIO5 * state during powerup. * low = 0x1a * high = 0x1b */ -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static struct i2c_driver wm8750_i2c_driver; -static struct i2c_client client_template; - -static int wm8750_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int wm8750_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8750_socdev; - struct wm8750_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) - return -ENOMEM; - i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - pr_err("failed to attach codec at addr %x\n", addr); - goto err; - } - ret = wm8750_init(socdev); - if (ret < 0) { + if (ret < 0) pr_err("failed to initialise WM8750\n"); - goto err; - } - return ret; -err: - kfree(i2c); return ret; } -static int wm8750_i2c_detach(struct i2c_client *client) +static int wm8750_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 wm8750_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8750_codec_probe); -} +static const struct i2c_device_id wm8750_i2c_id[] = { + { "wm8750", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver wm8750_i2c_driver = { .driver = { .name = "WM8750 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_WM8750, - .attach_adapter = wm8750_i2c_attach, - .detach_client = wm8750_i2c_detach, - .command = NULL, + .probe = wm8750_i2c_probe, + .remove = wm8750_i2c_remove, + .id_table = wm8750_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8750", - .driver = &wm8750_i2c_driver, +static int wm8750_add_i2c_device(struct platform_device *pdev, + const struct wm8750_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8750_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8750", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8750_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8750_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8750_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8750_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8750\n"); + + return ret; +} + +static int __devexit wm8750_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8750_spi_driver = { + .driver = { + .name = "wm8750", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8750_spi_probe, + .remove = __devexit_p(wm8750_spi_remove), }; + +static int wm8750_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} #endif static int wm8750_probe(struct platform_device *pdev) @@ -931,7 +990,7 @@ static int wm8750_probe(struct platform_device *pdev) struct wm8750_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec; struct wm8750_priv *wm8750; - int ret = 0; + int ret; pr_info("WM8750 Audio Codec %s", WM8750_VERSION); codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); @@ -952,16 +1011,21 @@ static int wm8750_probe(struct platform_device *pdev) wm8750_socdev = socdev; INIT_DELAYED_WORK(&codec->delayed_work, wm8750_work); + ret = -ENODEV; + #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; - ret = i2c_add_driver(&wm8750_i2c_driver); + ret = wm8750_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8750_spi_write; + ret = spi_register_driver(&wm8750_spi_driver); if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + printk(KERN_ERR "can't add spi driver"); } -#else - /* Add other interfaces here */ #endif if (ret != 0) { @@ -1002,8 +1066,12 @@ static int wm8750_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(&wm8750_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8750_spi_driver); +#endif kfree(codec->private_data); kfree(codec); diff --git a/sound/soc/codecs/wm8750.h b/sound/soc/codecs/wm8750.h index 8ef30e6..1dc100e 100644 --- a/sound/soc/codecs/wm8750.h +++ b/sound/soc/codecs/wm8750.h @@ -58,6 +58,8 @@ #define WM8750_SYSCLK 0 struct wm8750_setup_data { + int spi; + int i2c_bus; unsigned short i2c_address; }; diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index e873414..d426eaa 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -2,8 +2,7 @@ * wm8753.c -- WM8753 ALSA Soc Audio driver * * Copyright 2003 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -40,6 +39,7 @@ #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> @@ -51,7 +51,6 @@ #include "wm8753.h" -#define AUDIO_NAME "wm8753" #define WM8753_VERSION "0.16" static int caps_charge = 2000; @@ -1637,86 +1636,145 @@ static struct snd_soc_device *wm8753_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 wm8753_i2c_driver; -static struct i2c_client client_template; - -static int wm8753_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int wm8753_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8753_socdev; - struct wm8753_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) - return -ENOMEM; - i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - pr_err("failed to attach codec at addr %x\n", addr); - goto err; - } - ret = wm8753_init(socdev); - if (ret < 0) { + if (ret < 0) pr_err("failed to initialise WM8753\n"); - goto err; - } - - return ret; -err: - kfree(i2c); return ret; } -static int wm8753_i2c_detach(struct i2c_client *client) +static int wm8753_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 wm8753_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8753_codec_probe); -} +static const struct i2c_device_id wm8753_i2c_id[] = { + { "wm8753", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id); -/* corgi i2c codec control layer */ static struct i2c_driver wm8753_i2c_driver = { .driver = { .name = "WM8753 I2C Codec", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_WM8753, - .attach_adapter = wm8753_i2c_attach, - .detach_client = wm8753_i2c_detach, - .command = NULL, + .probe = wm8753_i2c_probe, + .remove = wm8753_i2c_remove, + .id_table = wm8753_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8753", - .driver = &wm8753_i2c_driver, +static int wm8753_add_i2c_device(struct platform_device *pdev, + const struct wm8753_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8753_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8753", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8753_i2c_driver); + return -ENODEV; +} +#endif + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8753_spi_probe(struct spi_device *spi) +{ + struct snd_soc_device *socdev = wm8753_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + codec->control_data = spi; + + ret = wm8753_init(socdev); + if (ret < 0) + dev_err(&spi->dev, "failed to initialise WM8753\n"); + + return ret; +} + +static int __devexit wm8753_spi_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver wm8753_spi_driver = { + .driver = { + .name = "wm8753", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8753_spi_probe, + .remove = __devexit_p(wm8753_spi_remove), }; + +static int wm8753_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} #endif + static int wm8753_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -1748,14 +1806,17 @@ static int wm8753_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(&wm8753_i2c_driver); + ret = wm8753_add_i2c_device(pdev, setup); + } +#endif +#if defined(CONFIG_SPI_MASTER) + if (setup->spi) { + codec->hw_write = (hw_write_t)wm8753_spi_write; + ret = spi_register_driver(&wm8753_spi_driver); if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + printk(KERN_ERR "can't add spi driver"); } -#else - /* Add other interfaces here */ #endif if (ret != 0) { @@ -1796,8 +1857,12 @@ static int wm8753_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(&wm8753_i2c_driver); #endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8753_spi_driver); +#endif kfree(codec->private_data); kfree(codec); diff --git a/sound/soc/codecs/wm8753.h b/sound/soc/codecs/wm8753.h index 44f5f1f..f55704c 100644 --- a/sound/soc/codecs/wm8753.h +++ b/sound/soc/codecs/wm8753.h @@ -2,8 +2,7 @@ * wm8753.h -- audio driver for WM8753 * * Copyright 2003 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -79,6 +78,8 @@ #define WM8753_ADCTL2 0x3f struct wm8753_setup_data { + int spi; + int i2c_bus; unsigned short i2c_address; }; diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c new file mode 100644 index 0000000..3b326c9 --- /dev/null +++ b/sound/soc/codecs/wm8900.c @@ -0,0 +1,1541 @@ +/* + * wm8900.c -- WM8900 ALSA Soc Audio driver + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - Tristating. + * - TDM. + * - Jack detect. + * - FLL source configuration, currently only MCLK is supported. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "wm8900.h" + +/* WM8900 register space */ +#define WM8900_REG_RESET 0x0 +#define WM8900_REG_ID 0x0 +#define WM8900_REG_POWER1 0x1 +#define WM8900_REG_POWER2 0x2 +#define WM8900_REG_POWER3 0x3 +#define WM8900_REG_AUDIO1 0x4 +#define WM8900_REG_AUDIO2 0x5 +#define WM8900_REG_CLOCKING1 0x6 +#define WM8900_REG_CLOCKING2 0x7 +#define WM8900_REG_AUDIO3 0x8 +#define WM8900_REG_AUDIO4 0x9 +#define WM8900_REG_DACCTRL 0xa +#define WM8900_REG_LDAC_DV 0xb +#define WM8900_REG_RDAC_DV 0xc +#define WM8900_REG_SIDETONE 0xd +#define WM8900_REG_ADCCTRL 0xe +#define WM8900_REG_LADC_DV 0xf +#define WM8900_REG_RADC_DV 0x10 +#define WM8900_REG_GPIO 0x12 +#define WM8900_REG_INCTL 0x15 +#define WM8900_REG_LINVOL 0x16 +#define WM8900_REG_RINVOL 0x17 +#define WM8900_REG_INBOOSTMIX1 0x18 +#define WM8900_REG_INBOOSTMIX2 0x19 +#define WM8900_REG_ADCPATH 0x1a +#define WM8900_REG_AUXBOOST 0x1b +#define WM8900_REG_ADDCTL 0x1e +#define WM8900_REG_FLLCTL1 0x24 +#define WM8900_REG_FLLCTL2 0x25 +#define WM8900_REG_FLLCTL3 0x26 +#define WM8900_REG_FLLCTL4 0x27 +#define WM8900_REG_FLLCTL5 0x28 +#define WM8900_REG_FLLCTL6 0x29 +#define WM8900_REG_LOUTMIXCTL1 0x2c +#define WM8900_REG_ROUTMIXCTL1 0x2d +#define WM8900_REG_BYPASS1 0x2e +#define WM8900_REG_BYPASS2 0x2f +#define WM8900_REG_AUXOUT_CTL 0x30 +#define WM8900_REG_LOUT1CTL 0x33 +#define WM8900_REG_ROUT1CTL 0x34 +#define WM8900_REG_LOUT2CTL 0x35 +#define WM8900_REG_ROUT2CTL 0x36 +#define WM8900_REG_HPCTL1 0x3a +#define WM8900_REG_OUTBIASCTL 0x73 + +#define WM8900_MAXREG 0x80 + +#define WM8900_REG_ADDCTL_OUT1_DIS 0x80 +#define WM8900_REG_ADDCTL_OUT2_DIS 0x40 +#define WM8900_REG_ADDCTL_VMID_DIS 0x20 +#define WM8900_REG_ADDCTL_BIAS_SRC 0x10 +#define WM8900_REG_ADDCTL_VMID_SOFTST 0x04 +#define WM8900_REG_ADDCTL_TEMP_SD 0x02 + +#define WM8900_REG_GPIO_TEMP_ENA 0x2 + +#define WM8900_REG_POWER1_STARTUP_BIAS_ENA 0x0100 +#define WM8900_REG_POWER1_BIAS_ENA 0x0008 +#define WM8900_REG_POWER1_VMID_BUF_ENA 0x0004 +#define WM8900_REG_POWER1_FLL_ENA 0x0040 + +#define WM8900_REG_POWER2_SYSCLK_ENA 0x8000 +#define WM8900_REG_POWER2_ADCL_ENA 0x0002 +#define WM8900_REG_POWER2_ADCR_ENA 0x0001 + +#define WM8900_REG_POWER3_DACL_ENA 0x0002 +#define WM8900_REG_POWER3_DACR_ENA 0x0001 + +#define WM8900_REG_AUDIO1_AIF_FMT_MASK 0x0018 +#define WM8900_REG_AUDIO1_LRCLK_INV 0x0080 +#define WM8900_REG_AUDIO1_BCLK_INV 0x0100 + +#define WM8900_REG_CLOCKING1_BCLK_DIR 0x1 +#define WM8900_REG_CLOCKING1_MCLK_SRC 0x100 +#define WM8900_REG_CLOCKING1_BCLK_MASK (~0x01e) +#define WM8900_REG_CLOCKING1_OPCLK_MASK (~0x7000) + +#define WM8900_REG_CLOCKING2_ADC_CLKDIV 0xe0 +#define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c + +#define WM8900_REG_DACCTRL_MUTE 0x004 +#define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400 + +#define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800 + +#define WM8900_REG_AUDIO4_DACLRC_DIR 0x0800 + +#define WM8900_REG_FLLCTL1_OSC_ENA 0x100 + +#define WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF 0x100 + +#define WM8900_REG_HPCTL1_HP_IPSTAGE_ENA 0x80 +#define WM8900_REG_HPCTL1_HP_OPSTAGE_ENA 0x40 +#define WM8900_REG_HPCTL1_HP_CLAMP_IP 0x20 +#define WM8900_REG_HPCTL1_HP_CLAMP_OP 0x10 +#define WM8900_REG_HPCTL1_HP_SHORT 0x08 +#define WM8900_REG_HPCTL1_HP_SHORT2 0x04 + +#define WM8900_LRC_MASK 0xfc00 + +struct snd_soc_codec_device soc_codec_dev_wm8900; + +struct wm8900_priv { + u32 fll_in; /* FLL input frequency */ + u32 fll_out; /* FLL output frequency */ +}; + +/* + * wm8900 register cache. We can't read the entire register space and we + * have slow control buses so we cache the registers. + */ +static const u16 wm8900_reg_defaults[WM8900_MAXREG] = { + 0x8900, 0x0000, + 0xc000, 0x0000, + 0x4050, 0x4000, + 0x0008, 0x0000, + 0x0040, 0x0040, + 0x1004, 0x00c0, + 0x00c0, 0x0000, + 0x0100, 0x00c0, + 0x00c0, 0x0000, + 0xb001, 0x0000, + 0x0000, 0x0044, + 0x004c, 0x004c, + 0x0044, 0x0044, + 0x0000, 0x0044, + 0x0000, 0x0000, + 0x0002, 0x0000, + 0x0000, 0x0000, + 0x0000, 0x0000, + 0x0008, 0x0000, + 0x0000, 0x0008, + 0x0097, 0x0100, + 0x0000, 0x0000, + 0x0050, 0x0050, + 0x0055, 0x0055, + 0x0055, 0x0000, + 0x0000, 0x0079, + 0x0079, 0x0079, + 0x0079, 0x0000, + /* Remaining registers all zero */ +}; + +/* + * read wm8900 register cache + */ +static inline unsigned int wm8900_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= WM8900_MAXREG); + + if (reg == WM8900_REG_ID) + return 0; + + return cache[reg]; +} + +/* + * write wm8900 register cache + */ +static inline void wm8900_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= WM8900_MAXREG); + + cache[reg] = value; +} + +/* + * write to the WM8900 register space + */ +static int wm8900_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + if (value == wm8900_read_reg_cache(codec, reg)) + return 0; + + /* data is + * D15..D9 WM8900 register offset + * D8...D0 register data + */ + data[0] = reg; + data[1] = value >> 8; + data[2] = value & 0x00ff; + + wm8900_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +/* + * Read from the wm8900. + */ +static unsigned int wm8900_chip_read(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + BUG_ON(reg != WM8900_REG_ID && reg != WM8900_REG_POWER1); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + printk(KERN_CRIT "i2c_transfer returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +/* + * Read from the WM8900 register space. Most registers can't be read + * and are therefore supplied from cache. + */ +static unsigned int wm8900_read(struct snd_soc_codec *codec, unsigned int reg) +{ + switch (reg) { + case WM8900_REG_ID: + return wm8900_chip_read(codec, reg); + default: + return wm8900_read_reg_cache(codec, reg); + } +} + +static void wm8900_reset(struct snd_soc_codec *codec) +{ + wm8900_write(codec, WM8900_REG_RESET, 0); + + memcpy(codec->reg_cache, wm8900_reg_defaults, + sizeof(codec->reg_cache)); +} + +static int wm8900_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 hpctl1 = wm8900_read(codec, WM8900_REG_HPCTL1); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Clamp headphone outputs */ + hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP | + WM8900_REG_HPCTL1_HP_CLAMP_OP; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_POST_PMU: + /* Enable the input stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_IP; + hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT | + WM8900_REG_HPCTL1_HP_SHORT2 | + WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + msleep(400); + + /* Enable the output stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP; + hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Remove the shorts */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_PRE_PMD: + /* Short the output */ + hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Disable the output stage */ + hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + + /* Clamp the outputs and power down input */ + hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP | + WM8900_REG_HPCTL1_HP_CLAMP_OP; + hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; + wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Disable everything */ + wm8900_write(codec, WM8900_REG_HPCTL1, 0); + break; + + default: + BUG(); + } + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -5700, 100, 0); + +static const DECLARE_TLV_DB_SCALE(out_mix_tlv, -1500, 300, 0); + +static const DECLARE_TLV_DB_SCALE(in_boost_tlv, -1200, 600, 0); + +static const DECLARE_TLV_DB_SCALE(in_pga_tlv, -1200, 100, 0); + +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1); + +static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3600, 300, 0); + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1); + +static const char *mic_bias_level_txt[] = { "0.9*AVDD", "0.65*AVDD" }; + +static const struct soc_enum mic_bias_level = +SOC_ENUM_SINGLE(WM8900_REG_INCTL, 8, 2, mic_bias_level_txt); + +static const char *dac_mute_rate_txt[] = { "Fast", "Slow" }; + +static const struct soc_enum dac_mute_rate = +SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 7, 2, dac_mute_rate_txt); + +static const char *dac_deemphasis_txt[] = { + "Disabled", "32kHz", "44.1kHz", "48kHz" +}; + +static const struct soc_enum dac_deemphasis = +SOC_ENUM_SINGLE(WM8900_REG_DACCTRL, 4, 4, dac_deemphasis_txt); + +static const char *adc_hpf_cut_txt[] = { + "Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3" +}; + +static const struct soc_enum adc_hpf_cut = +SOC_ENUM_SINGLE(WM8900_REG_ADCCTRL, 5, 4, adc_hpf_cut_txt); + +static const char *lr_txt[] = { + "Left", "Right" +}; + +static const struct soc_enum aifl_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 15, 2, lr_txt); + +static const struct soc_enum aifr_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO1, 14, 2, lr_txt); + +static const struct soc_enum dacl_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 15, 2, lr_txt); + +static const struct soc_enum dacr_src = +SOC_ENUM_SINGLE(WM8900_REG_AUDIO2, 14, 2, lr_txt); + +static const char *sidetone_txt[] = { + "Disabled", "Left ADC", "Right ADC" +}; + +static const struct soc_enum dacl_sidetone = +SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 2, 3, sidetone_txt); + +static const struct soc_enum dacr_sidetone = +SOC_ENUM_SINGLE(WM8900_REG_SIDETONE, 0, 3, sidetone_txt); + +static const struct snd_kcontrol_new wm8900_snd_controls[] = { +SOC_ENUM("Mic Bias Level", mic_bias_level), + +SOC_SINGLE_TLV("Left Input PGA Volume", WM8900_REG_LINVOL, 0, 31, 0, + in_pga_tlv), +SOC_SINGLE("Left Input PGA Switch", WM8900_REG_LINVOL, 6, 1, 1), +SOC_SINGLE("Left Input PGA ZC Switch", WM8900_REG_LINVOL, 7, 1, 0), + +SOC_SINGLE_TLV("Right Input PGA Volume", WM8900_REG_RINVOL, 0, 31, 0, + in_pga_tlv), +SOC_SINGLE("Right Input PGA Switch", WM8900_REG_RINVOL, 6, 1, 1), +SOC_SINGLE("Right Input PGA ZC Switch", WM8900_REG_RINVOL, 7, 1, 0), + +SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1), +SOC_ENUM("DAC Mute Rate", dac_mute_rate), +SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0), +SOC_ENUM("DAC Deemphasis", dac_deemphasis), +SOC_SINGLE("DAC Sloping Stopband Filter Switch", WM8900_REG_DACCTRL, 8, 1, 0), +SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL, + 12, 1, 0), + +SOC_SINGLE("ADC HPF Switch", WM8900_REG_ADCCTRL, 8, 1, 0), +SOC_ENUM("ADC HPF Cut-Off", adc_hpf_cut), +SOC_DOUBLE("ADC Invert Switch", WM8900_REG_ADCCTRL, 1, 0, 1, 0), +SOC_SINGLE_TLV("Left ADC Sidetone Volume", WM8900_REG_SIDETONE, 9, 12, 0, + adc_svol_tlv), +SOC_SINGLE_TLV("Right ADC Sidetone Volume", WM8900_REG_SIDETONE, 5, 12, 0, + adc_svol_tlv), +SOC_ENUM("Left Digital Audio Source", aifl_src), +SOC_ENUM("Right Digital Audio Source", aifr_src), + +SOC_SINGLE_TLV("DAC Input Boost Volume", WM8900_REG_AUDIO2, 10, 4, 0, + dac_boost_tlv), +SOC_ENUM("Left DAC Source", dacl_src), +SOC_ENUM("Right DAC Source", dacr_src), +SOC_ENUM("Left DAC Sidetone", dacl_sidetone), +SOC_ENUM("Right DAC Sidetone", dacr_sidetone), +SOC_DOUBLE("DAC Invert Switch", WM8900_REG_DACCTRL, 1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Digital Playback Volume", + WM8900_REG_LDAC_DV, WM8900_REG_RDAC_DV, + 1, 96, 0, dac_tlv), +SOC_DOUBLE_R_TLV("Digital Capture Volume", + WM8900_REG_LADC_DV, WM8900_REG_RADC_DV, 1, 119, 0, adc_tlv), + +SOC_SINGLE_TLV("LINPUT3 Bypass Volume", WM8900_REG_LOUTMIXCTL1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RINPUT3 Bypass Volume", WM8900_REG_ROUTMIXCTL1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("Left AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("Right AUX Bypass Volume", WM8900_REG_AUXOUT_CTL, 0, 7, 0, + out_mix_tlv), + +SOC_SINGLE_TLV("LeftIn to RightOut Mixer Volume", WM8900_REG_BYPASS1, 0, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("LeftIn to LeftOut Mixer Volume", WM8900_REG_BYPASS1, 4, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RightIn to LeftOut Mixer Volume", WM8900_REG_BYPASS2, 0, 7, 0, + out_mix_tlv), +SOC_SINGLE_TLV("RightIn to RightOut Mixer Volume", WM8900_REG_BYPASS2, 4, 7, 0, + out_mix_tlv), + +SOC_SINGLE_TLV("IN2L Boost Volume", WM8900_REG_INBOOSTMIX1, 0, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN3L Boost Volume", WM8900_REG_INBOOSTMIX1, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN2R Boost Volume", WM8900_REG_INBOOSTMIX2, 0, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("IN3R Boost Volume", WM8900_REG_INBOOSTMIX2, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("Left AUX Boost Volume", WM8900_REG_AUXBOOST, 4, 3, 0, + in_boost_tlv), +SOC_SINGLE_TLV("Right AUX Boost Volume", WM8900_REG_AUXBOOST, 0, 3, 0, + in_boost_tlv), + +SOC_DOUBLE_R_TLV("LINEOUT1 Volume", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 0, 63, 0, out_pga_tlv), +SOC_DOUBLE_R("LINEOUT1 Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 6, 1, 1), +SOC_DOUBLE_R("LINEOUT1 ZC Switch", WM8900_REG_LOUT1CTL, WM8900_REG_ROUT1CTL, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("LINEOUT2 Volume", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, + 0, 63, 0, out_pga_tlv), +SOC_DOUBLE_R("LINEOUT2 Switch", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 6, 1, 1), +SOC_DOUBLE_R("LINEOUT2 ZC Switch", + WM8900_REG_LOUT2CTL, WM8900_REG_ROUT2CTL, 7, 1, 0), +SOC_SINGLE("LINEOUT2 LP -12dB", WM8900_REG_LOUTMIXCTL1, + 0, 1, 1), + +}; + +/* add non dapm controls */ +static int wm8900_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8900_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8900_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static const struct snd_kcontrol_new wm8900_dapm_loutput2_control = +SOC_DAPM_SINGLE("LINEOUT2L Switch", WM8900_REG_POWER3, 6, 1, 0); + +static const struct snd_kcontrol_new wm8900_dapm_routput2_control = +SOC_DAPM_SINGLE("LINEOUT2R Switch", WM8900_REG_POWER3, 5, 1, 0); + +static const struct snd_kcontrol_new wm8900_loutmix_controls[] = { +SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0), +SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0), +SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0), +SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_routmix_controls[] = { +SOC_DAPM_SINGLE("RINPUT3 Bypass Switch", WM8900_REG_ROUTMIXCTL1, 7, 1, 0), +SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 3, 1, 0), +SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 3, 1, 0), +SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 7, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8900_REG_ROUTMIXCTL1, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_linmix_controls[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INBOOSTMIX1, 2, 1, 1), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INBOOSTMIX1, 6, 1, 1), +SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 6, 1, 1), +SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 6, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_rinmix_controls[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INBOOSTMIX2, 2, 1, 1), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INBOOSTMIX2, 6, 1, 1), +SOC_DAPM_SINGLE("AUX Switch", WM8900_REG_AUXBOOST, 2, 1, 1), +SOC_DAPM_SINGLE("Input PGA Switch", WM8900_REG_ADCPATH, 2, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_linpga_controls[] = { +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8900_REG_INCTL, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8900_REG_INCTL, 5, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8900_REG_INCTL, 4, 1, 0), +}; + +static const struct snd_kcontrol_new wm8900_rinpga_controls[] = { +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8900_REG_INCTL, 2, 1, 0), +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8900_REG_INCTL, 1, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8900_REG_INCTL, 0, 1, 0), +}; + +static const char *wm9700_lp_mux[] = { "Disabled", "Enabled" }; + +static const struct soc_enum wm8900_lineout2_lp_mux = +SOC_ENUM_SINGLE(WM8900_REG_LOUTMIXCTL1, 1, 2, wm9700_lp_mux); + +static const struct snd_kcontrol_new wm8900_lineout2_lp = +SOC_DAPM_ENUM("Route", wm8900_lineout2_lp_mux); + +static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = { + +/* Externally visible pins */ +SND_SOC_DAPM_OUTPUT("LINEOUT1L"), +SND_SOC_DAPM_OUTPUT("LINEOUT1R"), +SND_SOC_DAPM_OUTPUT("LINEOUT2L"), +SND_SOC_DAPM_OUTPUT("LINEOUT2R"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), + +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT3"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("AUX"), + +SND_SOC_DAPM_VMID("VMID"), + +/* Input */ +SND_SOC_DAPM_MIXER("Left Input PGA", WM8900_REG_POWER2, 3, 0, + wm8900_linpga_controls, + ARRAY_SIZE(wm8900_linpga_controls)), +SND_SOC_DAPM_MIXER("Right Input PGA", WM8900_REG_POWER2, 2, 0, + wm8900_rinpga_controls, + ARRAY_SIZE(wm8900_rinpga_controls)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0, + wm8900_linmix_controls, + ARRAY_SIZE(wm8900_linmix_controls)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8900_REG_POWER2, 4, 0, + wm8900_rinmix_controls, + ARRAY_SIZE(wm8900_rinmix_controls)), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8900_REG_POWER1, 4, 0), + +SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8900_REG_POWER2, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8900_REG_POWER2, 0, 0), + +/* Output */ +SND_SOC_DAPM_DAC("DACL", "Left HiFi Playback", WM8900_REG_POWER3, 1, 0), +SND_SOC_DAPM_DAC("DACR", "Right HiFi Playback", WM8900_REG_POWER3, 0, 0), + +SND_SOC_DAPM_PGA_E("Headphone Amplifier", WM8900_REG_POWER3, 7, 0, NULL, 0, + wm8900_hp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA("LINEOUT1L PGA", WM8900_REG_POWER2, 8, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT1R PGA", WM8900_REG_POWER2, 7, 0, NULL, 0), + +SND_SOC_DAPM_MUX("LINEOUT2 LP", SND_SOC_NOPM, 0, 0, &wm8900_lineout2_lp), +SND_SOC_DAPM_PGA("LINEOUT2L PGA", WM8900_REG_POWER3, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT2R PGA", WM8900_REG_POWER3, 5, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0, + wm8900_loutmix_controls, + ARRAY_SIZE(wm8900_loutmix_controls)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8900_REG_POWER3, 2, 0, + wm8900_routmix_controls, + ARRAY_SIZE(wm8900_routmix_controls)), +}; + +/* Target, Path, Source */ +static const struct snd_soc_dapm_route audio_map[] = { +/* Inputs */ +{"Left Input PGA", "LINPUT1 Switch", "LINPUT1"}, +{"Left Input PGA", "LINPUT2 Switch", "LINPUT2"}, +{"Left Input PGA", "LINPUT3 Switch", "LINPUT3"}, + +{"Right Input PGA", "RINPUT1 Switch", "RINPUT1"}, +{"Right Input PGA", "RINPUT2 Switch", "RINPUT2"}, +{"Right Input PGA", "RINPUT3 Switch", "RINPUT3"}, + +{"Left Input Mixer", "LINPUT2 Switch", "LINPUT2"}, +{"Left Input Mixer", "LINPUT3 Switch", "LINPUT3"}, +{"Left Input Mixer", "AUX Switch", "AUX"}, +{"Left Input Mixer", "Input PGA Switch", "Left Input PGA"}, + +{"Right Input Mixer", "RINPUT2 Switch", "RINPUT2"}, +{"Right Input Mixer", "RINPUT3 Switch", "RINPUT3"}, +{"Right Input Mixer", "AUX Switch", "AUX"}, +{"Right Input Mixer", "Input PGA Switch", "Right Input PGA"}, + +{"ADCL", NULL, "Left Input Mixer"}, +{"ADCR", NULL, "Right Input Mixer"}, + +/* Outputs */ +{"LINEOUT1L", NULL, "LINEOUT1L PGA"}, +{"LINEOUT1L PGA", NULL, "Left Output Mixer"}, +{"LINEOUT1R", NULL, "LINEOUT1R PGA"}, +{"LINEOUT1R PGA", NULL, "Right Output Mixer"}, + +{"LINEOUT2L PGA", NULL, "Left Output Mixer"}, +{"LINEOUT2 LP", "Disabled", "LINEOUT2L PGA"}, +{"LINEOUT2 LP", "Enabled", "Left Output Mixer"}, +{"LINEOUT2L", NULL, "LINEOUT2 LP"}, + +{"LINEOUT2R PGA", NULL, "Right Output Mixer"}, +{"LINEOUT2 LP", "Disabled", "LINEOUT2R PGA"}, +{"LINEOUT2 LP", "Enabled", "Right Output Mixer"}, +{"LINEOUT2R", NULL, "LINEOUT2 LP"}, + +{"Left Output Mixer", "LINPUT3 Bypass Switch", "LINPUT3"}, +{"Left Output Mixer", "AUX Bypass Switch", "AUX"}, +{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}, +{"Left Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"}, +{"Left Output Mixer", "DACL Switch", "DACL"}, + +{"Right Output Mixer", "RINPUT3 Bypass Switch", "RINPUT3"}, +{"Right Output Mixer", "AUX Bypass Switch", "AUX"}, +{"Right Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}, +{"Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer"}, +{"Right Output Mixer", "DACR Switch", "DACR"}, + +/* Note that the headphone output stage needs to be connected + * externally to LINEOUT2 via DC blocking capacitors. Other + * configurations are not supported. + * + * Note also that left and right headphone paths are treated as a + * mono path. + */ +{"Headphone Amplifier", NULL, "LINEOUT2 LP"}, +{"Headphone Amplifier", NULL, "LINEOUT2 LP"}, +{"HP_L", NULL, "Headphone Amplifier"}, +{"HP_R", NULL, "Headphone Amplifier"}, +}; + +static int wm8900_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets, + ARRAY_SIZE(wm8900_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int wm8900_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 reg; + + reg = wm8900_read(codec, WM8900_REG_AUDIO1) & ~0x60; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg |= 0x20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg |= 0x40; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg |= 0x60; + break; + default: + return -EINVAL; + } + + wm8900_write(codec, WM8900_REG_AUDIO1, reg); + + return 0; +} + +/* FLL divisors */ +struct _fll_div { + u16 fll_ratio; + u16 fllclk_div; + u16 fll_slow_lock_ref; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + + BUG_ON(!Fout); + + /* The FLL must run at 90-100MHz which is then scaled down to + * the output value by FLLCLK_DIV. */ + target = Fout; + div = 1; + while (target < 90000000) { + div *= 2; + target *= 2; + } + + if (target > 100000000) + printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d" + " Fout=%d\n", target, Fref, Fout); + if (div > 32) { + printk(KERN_ERR "wm8900: Invalid FLL division rate %u, " + "Fref=%d, Fout=%d, target=%d\n", + div, Fref, Fout, target); + return -EINVAL; + } + + fll_div->fllclk_div = div >> 2; + + if (Fref < 48000) + fll_div->fll_slow_lock_ref = 1; + else + fll_div->fll_slow_lock_ref = 0; + + Ndiv = target / Fref; + + if (Fref < 1000000) + fll_div->fll_ratio = 8; + else + fll_div->fll_ratio = 1; + + fll_div->n = Ndiv / fll_div->fll_ratio; + Nmod = (target / fll_div->fll_ratio) % Fref; + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + BUG_ON(target != Fout * (fll_div->fllclk_div << 2)); + BUG_ON(!K && target != Fref * fll_div->fll_ratio * fll_div->n); + + return 0; +} + +static int wm8900_set_fll(struct snd_soc_codec *codec, + int fll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct wm8900_priv *wm8900 = codec->private_data; + struct _fll_div fll_div; + unsigned int reg; + + if (wm8900->fll_in == freq_in && wm8900->fll_out == freq_out) + return 0; + + /* The digital side should be disabled during any change. */ + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg & (~WM8900_REG_POWER1_FLL_ENA)); + + /* Disable the FLL? */ + if (!freq_in || !freq_out) { + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + reg & (~WM8900_REG_CLOCKING1_MCLK_SRC)); + + reg = wm8900_read(codec, WM8900_REG_FLLCTL1); + wm8900_write(codec, WM8900_REG_FLLCTL1, + reg & (~WM8900_REG_FLLCTL1_OSC_ENA)); + + wm8900->fll_in = freq_in; + wm8900->fll_out = freq_out; + + return 0; + } + + if (fll_factors(&fll_div, freq_in, freq_out) != 0) + goto reenable; + + wm8900->fll_in = freq_in; + wm8900->fll_out = freq_out; + + /* The osclilator *MUST* be enabled before we enable the + * digital circuit. */ + wm8900_write(codec, WM8900_REG_FLLCTL1, + fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA); + + wm8900_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5); + wm8900_write(codec, WM8900_REG_FLLCTL5, + (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f)); + + if (fll_div.k) { + wm8900_write(codec, WM8900_REG_FLLCTL2, + (fll_div.k >> 8) | 0x100); + wm8900_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff); + } else + wm8900_write(codec, WM8900_REG_FLLCTL2, 0); + + if (fll_div.fll_slow_lock_ref) + wm8900_write(codec, WM8900_REG_FLLCTL6, + WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF); + else + wm8900_write(codec, WM8900_REG_FLLCTL6, 0); + + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg | WM8900_REG_POWER1_FLL_ENA); + +reenable: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + reg | WM8900_REG_CLOCKING1_MCLK_SRC); + + return 0; +} + +static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + return wm8900_set_fll(codec_dai->codec, pll_id, freq_in, freq_out); +} + +static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + switch (div_id) { + case WM8900_BCLK_DIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK)); + break; + case WM8900_OPCLK_DIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING1); + wm8900_write(codec, WM8900_REG_CLOCKING1, + div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK)); + break; + case WM8900_DAC_LRCLK: + reg = wm8900_read(codec, WM8900_REG_AUDIO4); + wm8900_write(codec, WM8900_REG_AUDIO4, + div | (reg & WM8900_LRC_MASK)); + break; + case WM8900_ADC_LRCLK: + reg = wm8900_read(codec, WM8900_REG_AUDIO3); + wm8900_write(codec, WM8900_REG_AUDIO3, + div | (reg & WM8900_LRC_MASK)); + break; + case WM8900_DAC_CLKDIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING2); + wm8900_write(codec, WM8900_REG_CLOCKING2, + div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV)); + break; + case WM8900_ADC_CLKDIV: + reg = wm8900_read(codec, WM8900_REG_CLOCKING2); + wm8900_write(codec, WM8900_REG_CLOCKING2, + div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV)); + break; + case WM8900_LRCLK_MODE: + reg = wm8900_read(codec, WM8900_REG_DACCTRL); + wm8900_write(codec, WM8900_REG_DACCTRL, + div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE)); + break; + default: + return -EINVAL; + } + + return 0; +} + + +static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int clocking1, aif1, aif3, aif4; + + clocking1 = wm8900_read(codec, WM8900_REG_CLOCKING1); + aif1 = wm8900_read(codec, WM8900_REG_AUDIO1); + aif3 = wm8900_read(codec, WM8900_REG_AUDIO3); + aif4 = wm8900_read(codec, WM8900_REG_AUDIO4); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + clocking1 &= ~WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 |= WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 |= WM8900_REG_AUDIO4_DACLRC_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + clocking1 |= WM8900_REG_CLOCKING1_BCLK_DIR; + aif3 &= ~WM8900_REG_AUDIO3_ADCLRC_DIR; + aif4 &= ~WM8900_REG_AUDIO4_DACLRC_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + aif1 |= WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_I2S: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 &= ~WM8900_REG_AUDIO1_AIF_FMT_MASK; + aif1 |= 0x8; + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8900_REG_AUDIO1_BCLK_INV; + aif1 &= ~WM8900_REG_AUDIO1_LRCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 &= ~WM8900_REG_AUDIO1_BCLK_INV; + aif1 |= WM8900_REG_AUDIO1_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8900_write(codec, WM8900_REG_CLOCKING1, clocking1); + wm8900_write(codec, WM8900_REG_AUDIO1, aif1); + wm8900_write(codec, WM8900_REG_AUDIO3, aif3); + wm8900_write(codec, WM8900_REG_AUDIO4, aif4); + + return 0; +} + +static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + reg = wm8900_read(codec, WM8900_REG_DACCTRL); + + if (mute) + reg |= WM8900_REG_DACCTRL_MUTE; + else + reg &= ~WM8900_REG_DACCTRL_MUTE; + + wm8900_write(codec, WM8900_REG_DACCTRL, reg); + + return 0; +} + +#define WM8900_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) + +#define WM8900_PCM_FORMATS \ + (SNDRV_PCM_FORMAT_S16_LE | SNDRV_PCM_FORMAT_S20_3LE | \ + SNDRV_PCM_FORMAT_S24_LE) + +struct snd_soc_dai wm8900_dai = { + .name = "WM8900 HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8900_RATES, + .formats = WM8900_PCM_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8900_RATES, + .formats = WM8900_PCM_FORMATS, + }, + .ops = { + .hw_params = wm8900_hw_params, + }, + .dai_ops = { + .set_clkdiv = wm8900_set_dai_clkdiv, + .set_pll = wm8900_set_dai_pll, + .set_fmt = wm8900_set_dai_fmt, + .digital_mute = wm8900_digital_mute, + }, +}; +EXPORT_SYMBOL_GPL(wm8900_dai); + +static int wm8900_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + /* Enable thermal shutdown */ + reg = wm8900_read(codec, WM8900_REG_GPIO); + wm8900_write(codec, WM8900_REG_GPIO, + reg | WM8900_REG_GPIO_TEMP_ENA); + reg = wm8900_read(codec, WM8900_REG_ADDCTL); + wm8900_write(codec, WM8900_REG_ADDCTL, + reg | WM8900_REG_ADDCTL_TEMP_SD); + break; + + case SND_SOC_BIAS_PREPARE: + break; + + case SND_SOC_BIAS_STANDBY: + /* Charge capacitors if initial power up */ + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* STARTUP_BIAS_ENA on */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); + + /* Startup bias mode */ + wm8900_write(codec, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_BIAS_SRC | + WM8900_REG_ADDCTL_VMID_SOFTST); + + /* VMID 2x50k */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1); + + /* Allow capacitors to charge */ + schedule_timeout_interruptible(msecs_to_jiffies(400)); + + /* Enable bias */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA | + WM8900_REG_POWER1_BIAS_ENA | 0x1); + + wm8900_write(codec, WM8900_REG_ADDCTL, 0); + + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_BIAS_ENA | 0x1); + } + + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + (reg & WM8900_REG_POWER1_FLL_ENA) | + WM8900_REG_POWER1_BIAS_ENA | 0x1); + wm8900_write(codec, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + wm8900_write(codec, WM8900_REG_POWER3, 0); + break; + + case SND_SOC_BIAS_OFF: + /* Startup bias enable */ + reg = wm8900_read(codec, WM8900_REG_POWER1); + wm8900_write(codec, WM8900_REG_POWER1, + reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA); + wm8900_write(codec, WM8900_REG_ADDCTL, + WM8900_REG_ADDCTL_BIAS_SRC | + WM8900_REG_ADDCTL_VMID_SOFTST); + + /* Discharge caps */ + wm8900_write(codec, WM8900_REG_POWER1, + WM8900_REG_POWER1_STARTUP_BIAS_ENA); + schedule_timeout_interruptible(msecs_to_jiffies(500)); + + /* Remove clamp */ + wm8900_write(codec, WM8900_REG_HPCTL1, 0); + + /* Power down */ + wm8900_write(codec, WM8900_REG_ADDCTL, 0); + wm8900_write(codec, WM8900_REG_POWER1, 0); + wm8900_write(codec, WM8900_REG_POWER2, 0); + wm8900_write(codec, WM8900_REG_POWER3, 0); + + /* Need to let things settle before stopping the clock + * to ensure that restart works, see "Stopping the + * master clock" in the datasheet. */ + schedule_timeout_interruptible(msecs_to_jiffies(1)); + wm8900_write(codec, WM8900_REG_POWER2, + WM8900_REG_POWER2_SYSCLK_ENA); + break; + } + codec->bias_level = level; + return 0; +} + +static int wm8900_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm8900_priv *wm8900 = codec->private_data; + int fll_out = wm8900->fll_out; + int fll_in = wm8900->fll_in; + int ret; + + /* Stop the FLL in an orderly fashion */ + ret = wm8900_set_fll(codec, 0, 0, 0); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to stop FLL\n"); + return ret; + } + + wm8900->fll_out = fll_out; + wm8900->fll_in = fll_in; + + wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8900_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct wm8900_priv *wm8900 = codec->private_data; + u16 *cache; + int i, ret; + + cache = kmemdup(codec->reg_cache, sizeof(wm8900_reg_defaults), + GFP_KERNEL); + + wm8900_reset(codec); + wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Restart the FLL? */ + if (wm8900->fll_out) { + int fll_out = wm8900->fll_out; + int fll_in = wm8900->fll_in; + + wm8900->fll_in = 0; + wm8900->fll_out = 0; + + ret = wm8900_set_fll(codec, 0, fll_in, fll_out); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to restart FLL\n"); + return ret; + } + } + + if (cache) { + for (i = 0; i < WM8900_MAXREG; i++) + wm8900_write(codec, i, cache[i]); + kfree(cache); + } else + dev_err(&pdev->dev, "Unable to allocate register cache\n"); + + return 0; +} + +/* + * initialise the WM8900 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8900_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int ret = 0; + unsigned int reg; + struct i2c_client *i2c_client = socdev->codec->control_data; + + codec->name = "WM8900"; + codec->owner = THIS_MODULE; + codec->read = wm8900_read; + codec->write = wm8900_write; + codec->dai = &wm8900_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8900_MAXREG; + codec->reg_cache = kmemdup(wm8900_reg_defaults, + sizeof(wm8900_reg_defaults), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + reg = wm8900_read(codec, WM8900_REG_ID); + if (reg != 0x8900) { + dev_err(&i2c_client->dev, "Device is not a WM8900 - ID %x\n", + reg); + return -ENODEV; + } + + codec->private_data = kzalloc(sizeof(struct wm8900_priv), GFP_KERNEL); + if (codec->private_data == NULL) { + ret = -ENOMEM; + goto priv_err; + } + + /* Read back from the chip */ + reg = wm8900_chip_read(codec, WM8900_REG_POWER1); + reg = (reg >> 12) & 0xf; + dev_info(&i2c_client->dev, "WM8900 revision %d\n", reg); + + wm8900_reset(codec); + + /* Latch the volume update bits */ + wm8900_write(codec, WM8900_REG_LINVOL, + wm8900_read(codec, WM8900_REG_LINVOL) | 0x100); + wm8900_write(codec, WM8900_REG_RINVOL, + wm8900_read(codec, WM8900_REG_RINVOL) | 0x100); + wm8900_write(codec, WM8900_REG_LOUT1CTL, + wm8900_read(codec, WM8900_REG_LOUT1CTL) | 0x100); + wm8900_write(codec, WM8900_REG_ROUT1CTL, + wm8900_read(codec, WM8900_REG_ROUT1CTL) | 0x100); + wm8900_write(codec, WM8900_REG_LOUT2CTL, + wm8900_read(codec, WM8900_REG_LOUT2CTL) | 0x100); + wm8900_write(codec, WM8900_REG_ROUT2CTL, + wm8900_read(codec, WM8900_REG_ROUT2CTL) | 0x100); + wm8900_write(codec, WM8900_REG_LDAC_DV, + wm8900_read(codec, WM8900_REG_LDAC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_RDAC_DV, + wm8900_read(codec, WM8900_REG_RDAC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_LADC_DV, + wm8900_read(codec, WM8900_REG_LADC_DV) | 0x100); + wm8900_write(codec, WM8900_REG_RADC_DV, + wm8900_read(codec, WM8900_REG_RADC_DV) | 0x100); + + /* Set the DAC and mixer output bias */ + wm8900_write(codec, WM8900_REG_OUTBIASCTL, 0x81); + + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to register new PCMs\n"); + goto pcm_err; + } + + /* Turn the chip on */ + codec->bias_level = SND_SOC_BIAS_OFF; + wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8900_add_controls(codec); + wm8900_add_widgets(codec); + + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); +priv_err: + kfree(codec->private_data); + return ret; +} + +static struct snd_soc_device *wm8900_socdev; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + +static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8900_i2c_driver; +static struct i2c_client client_template; + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static int wm8900_codec_probe(struct i2c_adapter *adap, int addr, int kind) +{ + struct snd_soc_device *socdev = wm8900_socdev; + struct wm8900_setup_data *setup = socdev->codec_data; + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c; + int ret; + + if (addr != setup->i2c_address) + return -ENODEV; + + dev_err(&adap->dev, "Probe on %x\n", addr); + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); + if (i2c == NULL) { + kfree(codec); + return -ENOMEM; + } + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = i2c_attach_client(i2c); + if (ret < 0) { + dev_err(&adap->dev, + "failed to attach codec at addr %x\n", addr); + goto err; + } + + ret = wm8900_init(socdev); + if (ret < 0) { + dev_err(&adap->dev, "failed to initialise WM8900\n"); + goto err; + } + return ret; + +err: + kfree(codec); + kfree(i2c); + return ret; +} + +static int wm8900_i2c_detach(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + i2c_detach_client(client); + kfree(codec->reg_cache); + kfree(client); + return 0; +} + +static int wm8900_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8900_codec_probe); +} + +/* corgi i2c codec control layer */ +static struct i2c_driver wm8900_i2c_driver = { + .driver = { + .name = "WM8900 I2C codec", + .owner = THIS_MODULE, + }, + .attach_adapter = wm8900_i2c_attach, + .detach_client = wm8900_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8900", + .driver = &wm8900_i2c_driver, +}; +#endif + +static int wm8900_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8900_setup_data *setup; + struct snd_soc_codec *codec; + int ret = 0; + + dev_info(&pdev->dev, "WM8900 Audio Codec\n"); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + socdev->codec = codec; + + codec->set_bias_level = wm8900_set_bias_level; + + wm8900_socdev = socdev; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + if (setup->i2c_address) { + normal_i2c[0] = setup->i2c_address; + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8900_i2c_driver); + if (ret != 0) + printk(KERN_ERR "can't add i2c driver"); + } +#else +#error Non-I2C interfaces not yet supported +#endif + return ret; +} + +/* power down chip */ +static int wm8900_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8900_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8900_i2c_driver); +#endif + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8900 = { + .probe = wm8900_probe, + .remove = wm8900_remove, + .suspend = wm8900_suspend, + .resume = wm8900_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8900); + +MODULE_DESCRIPTION("ASoC WM8900 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfonmicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8900.h b/sound/soc/codecs/wm8900.h new file mode 100644 index 0000000..ba450d9 --- /dev/null +++ b/sound/soc/codecs/wm8900.h @@ -0,0 +1,64 @@ +/* + * wm8900.h -- WM890 Soc Audio driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8900_H +#define _WM8900_H + +#define WM8900_FLL 1 + +#define WM8900_BCLK_DIV 1 +#define WM8900_ADC_CLKDIV 2 +#define WM8900_DAC_CLKDIV 3 +#define WM8900_ADC_LRCLK 4 +#define WM8900_DAC_LRCLK 5 +#define WM8900_OPCLK_DIV 6 +#define WM8900_LRCLK_MODE 7 + +#define WM8900_BCLK_DIV_1 0x00 +#define WM8900_BCLK_DIV_1_5 0x02 +#define WM8900_BCLK_DIV_2 0x04 +#define WM8900_BCLK_DIV_3 0x06 +#define WM8900_BCLK_DIV_4 0x08 +#define WM8900_BCLK_DIV_5_5 0x0a +#define WM8900_BCLK_DIV_6 0x0c +#define WM8900_BCLK_DIV_8 0x0e +#define WM8900_BCLK_DIV_11 0x10 +#define WM8900_BCLK_DIV_12 0x12 +#define WM8900_BCLK_DIV_16 0x14 +#define WM8900_BCLK_DIV_22 0x16 +#define WM8900_BCLK_DIV_24 0x18 +#define WM8900_BCLK_DIV_32 0x1a +#define WM8900_BCLK_DIV_44 0x1c +#define WM8900_BCLK_DIV_48 0x1e + +#define WM8900_ADC_CLKDIV_1 0x00 +#define WM8900_ADC_CLKDIV_1_5 0x20 +#define WM8900_ADC_CLKDIV_2 0x40 +#define WM8900_ADC_CLKDIV_3 0x60 +#define WM8900_ADC_CLKDIV_4 0x80 +#define WM8900_ADC_CLKDIV_5_5 0xa0 +#define WM8900_ADC_CLKDIV_6 0xc0 + +#define WM8900_DAC_CLKDIV_1 0x00 +#define WM8900_DAC_CLKDIV_1_5 0x04 +#define WM8900_DAC_CLKDIV_2 0x08 +#define WM8900_DAC_CLKDIV_3 0x0c +#define WM8900_DAC_CLKDIV_4 0x10 +#define WM8900_DAC_CLKDIV_5_5 0x14 +#define WM8900_DAC_CLKDIV_6 0x18 + +#define WM8900_ + +struct wm8900_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8900_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8900; + +#endif diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c new file mode 100644 index 0000000..ce40d78 --- /dev/null +++ b/sound/soc/codecs/wm8903.c @@ -0,0 +1,1813 @@ +/* + * wm8903.c -- WM8903 ALSA SoC Audio driver + * + * Copyright 2008 Wolfson Microelectronics + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: + * - TDM mode configuration. + * - Mic detect. + * - Digital microphone support. + * - Interrupt support (mic detect and sequencer). + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8903.h" + +struct wm8903_priv { + int sysclk; + + /* Reference counts */ + int charge_pump_users; + int class_w_users; + int playback_active; + int capture_active; + + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +/* Register defaults at reset */ +static u16 wm8903_reg_defaults[] = { + 0x8903, /* R0 - SW Reset and ID */ + 0x0000, /* R1 - Revision Number */ + 0x0000, /* R2 */ + 0x0000, /* R3 */ + 0x0018, /* R4 - Bias Control 0 */ + 0x0000, /* R5 - VMID Control 0 */ + 0x0000, /* R6 - Mic Bias Control 0 */ + 0x0000, /* R7 */ + 0x0001, /* R8 - Analogue DAC 0 */ + 0x0000, /* R9 */ + 0x0001, /* R10 - Analogue ADC 0 */ + 0x0000, /* R11 */ + 0x0000, /* R12 - Power Management 0 */ + 0x0000, /* R13 - Power Management 1 */ + 0x0000, /* R14 - Power Management 2 */ + 0x0000, /* R15 - Power Management 3 */ + 0x0000, /* R16 - Power Management 4 */ + 0x0000, /* R17 - Power Management 5 */ + 0x0000, /* R18 - Power Management 6 */ + 0x0000, /* R19 */ + 0x0400, /* R20 - Clock Rates 0 */ + 0x0D07, /* R21 - Clock Rates 1 */ + 0x0000, /* R22 - Clock Rates 2 */ + 0x0000, /* R23 */ + 0x0050, /* R24 - Audio Interface 0 */ + 0x0242, /* R25 - Audio Interface 1 */ + 0x0008, /* R26 - Audio Interface 2 */ + 0x0022, /* R27 - Audio Interface 3 */ + 0x0000, /* R28 */ + 0x0000, /* R29 */ + 0x00C0, /* R30 - DAC Digital Volume Left */ + 0x00C0, /* R31 - DAC Digital Volume Right */ + 0x0000, /* R32 - DAC Digital 0 */ + 0x0000, /* R33 - DAC Digital 1 */ + 0x0000, /* R34 */ + 0x0000, /* R35 */ + 0x00C0, /* R36 - ADC Digital Volume Left */ + 0x00C0, /* R37 - ADC Digital Volume Right */ + 0x0000, /* R38 - ADC Digital 0 */ + 0x0073, /* R39 - Digital Microphone 0 */ + 0x09BF, /* R40 - DRC 0 */ + 0x3241, /* R41 - DRC 1 */ + 0x0020, /* R42 - DRC 2 */ + 0x0000, /* R43 - DRC 3 */ + 0x0085, /* R44 - Analogue Left Input 0 */ + 0x0085, /* R45 - Analogue Right Input 0 */ + 0x0044, /* R46 - Analogue Left Input 1 */ + 0x0044, /* R47 - Analogue Right Input 1 */ + 0x0000, /* R48 */ + 0x0000, /* R49 */ + 0x0008, /* R50 - Analogue Left Mix 0 */ + 0x0004, /* R51 - Analogue Right Mix 0 */ + 0x0000, /* R52 - Analogue Spk Mix Left 0 */ + 0x0000, /* R53 - Analogue Spk Mix Left 1 */ + 0x0000, /* R54 - Analogue Spk Mix Right 0 */ + 0x0000, /* R55 - Analogue Spk Mix Right 1 */ + 0x0000, /* R56 */ + 0x002D, /* R57 - Analogue OUT1 Left */ + 0x002D, /* R58 - Analogue OUT1 Right */ + 0x0039, /* R59 - Analogue OUT2 Left */ + 0x0039, /* R60 - Analogue OUT2 Right */ + 0x0100, /* R61 */ + 0x0139, /* R62 - Analogue OUT3 Left */ + 0x0139, /* R63 - Analogue OUT3 Right */ + 0x0000, /* R64 */ + 0x0000, /* R65 - Analogue SPK Output Control 0 */ + 0x0000, /* R66 */ + 0x0010, /* R67 - DC Servo 0 */ + 0x0100, /* R68 */ + 0x00A4, /* R69 - DC Servo 2 */ + 0x0807, /* R70 */ + 0x0000, /* R71 */ + 0x0000, /* R72 */ + 0x0000, /* R73 */ + 0x0000, /* R74 */ + 0x0000, /* R75 */ + 0x0000, /* R76 */ + 0x0000, /* R77 */ + 0x0000, /* R78 */ + 0x000E, /* R79 */ + 0x0000, /* R80 */ + 0x0000, /* R81 */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 */ + 0x0000, /* R85 */ + 0x0000, /* R86 */ + 0x0006, /* R87 */ + 0x0000, /* R88 */ + 0x0000, /* R89 */ + 0x0000, /* R90 - Analogue HP 0 */ + 0x0060, /* R91 */ + 0x0000, /* R92 */ + 0x0000, /* R93 */ + 0x0000, /* R94 - Analogue Lineout 0 */ + 0x0060, /* R95 */ + 0x0000, /* R96 */ + 0x0000, /* R97 */ + 0x0000, /* R98 - Charge Pump 0 */ + 0x1F25, /* R99 */ + 0x2B19, /* R100 */ + 0x01C0, /* R101 */ + 0x01EF, /* R102 */ + 0x2B00, /* R103 */ + 0x0000, /* R104 - Class W 0 */ + 0x01C0, /* R105 */ + 0x1C10, /* R106 */ + 0x0000, /* R107 */ + 0x0000, /* R108 - Write Sequencer 0 */ + 0x0000, /* R109 - Write Sequencer 1 */ + 0x0000, /* R110 - Write Sequencer 2 */ + 0x0000, /* R111 - Write Sequencer 3 */ + 0x0000, /* R112 - Write Sequencer 4 */ + 0x0000, /* R113 */ + 0x0000, /* R114 - Control Interface */ + 0x0000, /* R115 */ + 0x00A8, /* R116 - GPIO Control 1 */ + 0x00A8, /* R117 - GPIO Control 2 */ + 0x00A8, /* R118 - GPIO Control 3 */ + 0x0220, /* R119 - GPIO Control 4 */ + 0x01A0, /* R120 - GPIO Control 5 */ + 0x0000, /* R121 - Interrupt Status 1 */ + 0xFFFF, /* R122 - Interrupt Status 1 Mask */ + 0x0000, /* R123 - Interrupt Polarity 1 */ + 0x0000, /* R124 */ + 0x0003, /* R125 */ + 0x0000, /* R126 - Interrupt Control */ + 0x0000, /* R127 */ + 0x0005, /* R128 */ + 0x0000, /* R129 - Control Interface Test 1 */ + 0x0000, /* R130 */ + 0x0000, /* R131 */ + 0x0000, /* R132 */ + 0x0000, /* R133 */ + 0x0000, /* R134 */ + 0x03FF, /* R135 */ + 0x0007, /* R136 */ + 0x0040, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0000, /* R140 */ + 0x0000, /* R141 */ + 0x0000, /* R142 */ + 0x0000, /* R143 */ + 0x0000, /* R144 */ + 0x0000, /* R145 */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x4000, /* R148 */ + 0x6810, /* R149 - Charge Pump Test 1 */ + 0x0004, /* R150 */ + 0x0000, /* R151 */ + 0x0000, /* R152 */ + 0x0000, /* R153 */ + 0x0000, /* R154 */ + 0x0000, /* R155 */ + 0x0000, /* R156 */ + 0x0000, /* R157 */ + 0x0000, /* R158 */ + 0x0000, /* R159 */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 */ + 0x0028, /* R164 - Clock Rate Test 4 */ + 0x0004, /* R165 */ + 0x0000, /* R166 */ + 0x0060, /* R167 */ + 0x0000, /* R168 */ + 0x0000, /* R169 */ + 0x0000, /* R170 */ + 0x0000, /* R171 */ + 0x0000, /* R172 - Analogue Output Bias 0 */ +}; + +static unsigned int wm8903_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults)); + + return cache[reg]; +} + +static unsigned int wm8903_hw_read(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + pr_err("i2c_transfer returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +static unsigned int wm8903_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case WM8903_SW_RESET_AND_ID: + case WM8903_REVISION_NUMBER: + case WM8903_INTERRUPT_STATUS_1: + case WM8903_WRITE_SEQUENCER_4: + return wm8903_hw_read(codec, reg); + + default: + return wm8903_read_reg_cache(codec, reg); + } +} + +static void wm8903_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults)); + + switch (reg) { + case WM8903_SW_RESET_AND_ID: + case WM8903_REVISION_NUMBER: + break; + + default: + cache[reg] = value; + break; + } +} + +static int wm8903_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[3]; + + wm8903_write_reg_cache(codec, reg, value); + + /* Data format is 1 byte of address followed by 2 bytes of data */ + data[0] = reg; + data[1] = (value >> 8) & 0xff; + data[2] = value & 0xff; + + if (codec->hw_write(codec->control_data, data, 3) == 2) + return 0; + else + return -EIO; +} + +static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) +{ + u16 reg[5]; + struct i2c_client *i2c = codec->control_data; + + BUG_ON(start > 48); + + /* Enable the sequencer */ + reg[0] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_0); + reg[0] |= WM8903_WSEQ_ENA; + wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); + + dev_dbg(&i2c->dev, "Starting sequence at %d\n", start); + + wm8903_write(codec, WM8903_WRITE_SEQUENCER_3, + start | WM8903_WSEQ_START); + + /* Wait for it to complete. If we have the interrupt wired up then + * we could block waiting for an interrupt, though polling may still + * be desirable for diagnostic purposes. + */ + do { + msleep(10); + + reg[4] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_4); + } while (reg[4] & WM8903_WSEQ_BUSY); + + dev_dbg(&i2c->dev, "Sequence complete\n"); + + /* Disable the sequencer again */ + wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, + reg[0] & ~WM8903_WSEQ_ENA); + + return 0; +} + +static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache) +{ + int i; + + /* There really ought to be something better we can do here :/ */ + for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++) + cache[i] = wm8903_hw_read(codec, i); +} + +static void wm8903_reset(struct snd_soc_codec *codec) +{ + wm8903_write(codec, WM8903_SW_RESET_AND_ID, 0); +} + +#define WM8903_OUTPUT_SHORT 0x8 +#define WM8903_OUTPUT_OUT 0x4 +#define WM8903_OUTPUT_INT 0x2 +#define WM8903_OUTPUT_IN 0x1 + +/* + * Event for headphone and line out amplifier power changes. Special + * power up/down sequences are required in order to maximise pop/click + * performance. + */ +static int wm8903_output_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + u16 val; + u16 reg; + int shift; + u16 cp_reg = wm8903_read(codec, WM8903_CHARGE_PUMP_0); + + switch (w->reg) { + case WM8903_POWER_MANAGEMENT_2: + reg = WM8903_ANALOGUE_HP_0; + break; + case WM8903_POWER_MANAGEMENT_3: + reg = WM8903_ANALOGUE_LINEOUT_0; + break; + default: + BUG(); + } + + switch (w->shift) { + case 0: + shift = 0; + break; + case 1: + shift = 4; + break; + default: + BUG(); + } + + if (event & SND_SOC_DAPM_PRE_PMU) { + val = wm8903_read(codec, reg); + + /* Short the output */ + val &= ~(WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + + wm8903->charge_pump_users++; + + dev_dbg(&i2c->dev, "Charge pump use count now %d\n", + wm8903->charge_pump_users); + + if (wm8903->charge_pump_users == 1) { + dev_dbg(&i2c->dev, "Enabling charge pump\n"); + wm8903_write(codec, WM8903_CHARGE_PUMP_0, + cp_reg | WM8903_CP_ENA); + mdelay(4); + } + } + + if (event & SND_SOC_DAPM_POST_PMU) { + val = wm8903_read(codec, reg); + + val |= (WM8903_OUTPUT_IN << shift); + wm8903_write(codec, reg, val); + + val |= (WM8903_OUTPUT_INT << shift); + wm8903_write(codec, reg, val); + + /* Turn on the output ENA_OUTP */ + val |= (WM8903_OUTPUT_OUT << shift); + wm8903_write(codec, reg, val); + + /* Remove the short */ + val |= (WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + val = wm8903_read(codec, reg); + + /* Short the output */ + val &= ~(WM8903_OUTPUT_SHORT << shift); + wm8903_write(codec, reg, val); + + /* Then disable the intermediate and output stages */ + val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | + WM8903_OUTPUT_IN) << shift); + wm8903_write(codec, reg, val); + } + + if (event & SND_SOC_DAPM_POST_PMD) { + wm8903->charge_pump_users--; + + dev_dbg(&i2c->dev, "Charge pump use count now %d\n", + wm8903->charge_pump_users); + + if (wm8903->charge_pump_users == 0) { + dev_dbg(&i2c->dev, "Disabling charge pump\n"); + wm8903_write(codec, WM8903_CHARGE_PUMP_0, + cp_reg & ~WM8903_CP_ENA); + } + } + + return 0; +} + +/* + * When used with DAC outputs only the WM8903 charge pump supports + * operation in class W mode, providing very low power consumption + * when used with digital sources. Enable and disable this mode + * automatically depending on the mixer configuration. + * + * All the relevant controls are simple switches. + */ +static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = widget->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + u16 reg; + int ret; + + reg = wm8903_read(codec, WM8903_CLASS_W_0); + + /* Turn it off if we're about to enable bypass */ + if (ucontrol->value.integer.value[0]) { + if (wm8903->class_w_users == 0) { + dev_dbg(&i2c->dev, "Disabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg & + ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V)); + } + wm8903->class_w_users++; + } + + /* Implement the change */ + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + /* If we've just disabled the last bypass path turn Class W on */ + if (!ucontrol->value.integer.value[0]) { + if (wm8903->class_w_users == 1) { + dev_dbg(&i2c->dev, "Enabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg | + WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); + } + wm8903->class_w_users--; + } + + dev_dbg(&i2c->dev, "Bypass use count now %d\n", + wm8903->class_w_users); + + return ret; +} + +#define SOC_DAPM_SINGLE_W(xname, reg, shift, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw, .put = wm8903_class_w_put, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + + +/* ALSA can only do steps of .01dB */ +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); + +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); + +static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_amp, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_min, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_max, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_tlv_startup, -300, 50, 0); + +static const char *drc_slope_text[] = { + "1", "1/2", "1/4", "1/8", "1/16", "0" +}; + +static const struct soc_enum drc_slope_r0 = + SOC_ENUM_SINGLE(WM8903_DRC_2, 3, 6, drc_slope_text); + +static const struct soc_enum drc_slope_r1 = + SOC_ENUM_SINGLE(WM8903_DRC_2, 0, 6, drc_slope_text); + +static const char *drc_attack_text[] = { + "instantaneous", + "363us", "762us", "1.45ms", "2.9ms", "5.8ms", "11.6ms", "23.2ms", + "46.4ms", "92.8ms", "185.6ms" +}; + +static const struct soc_enum drc_attack = + SOC_ENUM_SINGLE(WM8903_DRC_1, 12, 11, drc_attack_text); + +static const char *drc_decay_text[] = { + "186ms", "372ms", "743ms", "1.49s", "2.97s", "5.94s", "11.89s", + "23.87s", "47.56s" +}; + +static const struct soc_enum drc_decay = + SOC_ENUM_SINGLE(WM8903_DRC_1, 8, 9, drc_decay_text); + +static const char *drc_ff_delay_text[] = { + "5 samples", "9 samples" +}; + +static const struct soc_enum drc_ff_delay = + SOC_ENUM_SINGLE(WM8903_DRC_0, 5, 2, drc_ff_delay_text); + +static const char *drc_qr_decay_text[] = { + "0.725ms", "1.45ms", "5.8ms" +}; + +static const struct soc_enum drc_qr_decay = + SOC_ENUM_SINGLE(WM8903_DRC_1, 4, 3, drc_qr_decay_text); + +static const char *drc_smoothing_text[] = { + "Low", "Medium", "High" +}; + +static const struct soc_enum drc_smoothing = + SOC_ENUM_SINGLE(WM8903_DRC_0, 11, 3, drc_smoothing_text); + +static const char *soft_mute_text[] = { + "Fast (fs/2)", "Slow (fs/32)" +}; + +static const struct soc_enum soft_mute = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 10, 2, soft_mute_text); + +static const char *mute_mode_text[] = { + "Hard", "Soft" +}; + +static const struct soc_enum mute_mode = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 9, 2, mute_mode_text); + +static const char *dac_deemphasis_text[] = { + "Disabled", "32kHz", "44.1kHz", "48kHz" +}; + +static const struct soc_enum dac_deemphasis = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_1, 1, 4, dac_deemphasis_text); + +static const char *companding_text[] = { + "ulaw", "alaw" +}; + +static const struct soc_enum dac_companding = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 0, 2, companding_text); + +static const struct soc_enum adc_companding = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 2, 2, companding_text); + +static const char *input_mode_text[] = { + "Single-Ended", "Differential Line", "Differential Mic" +}; + +static const struct soc_enum linput_mode_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 0, 3, input_mode_text); + +static const struct soc_enum rinput_mode_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 0, 3, input_mode_text); + +static const char *linput_mux_text[] = { + "IN1L", "IN2L", "IN3L" +}; + +static const struct soc_enum linput_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 2, 3, linput_mux_text); + +static const struct soc_enum linput_inv_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_LEFT_INPUT_1, 4, 3, linput_mux_text); + +static const char *rinput_mux_text[] = { + "IN1R", "IN2R", "IN3R" +}; + +static const struct soc_enum rinput_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 2, 3, rinput_mux_text); + +static const struct soc_enum rinput_inv_enum = + SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text); + + +static const struct snd_kcontrol_new wm8903_snd_controls[] = { + +/* Input PGAs - No TLV since the scale depends on PGA mode */ +SOC_SINGLE("Left Input PGA Switch", WM8903_ANALOGUE_LEFT_INPUT_0, + 7, 1, 1), +SOC_SINGLE("Left Input PGA Volume", WM8903_ANALOGUE_LEFT_INPUT_0, + 0, 31, 0), +SOC_SINGLE("Left Input PGA Common Mode Switch", WM8903_ANALOGUE_LEFT_INPUT_1, + 6, 1, 0), + +SOC_SINGLE("Right Input PGA Switch", WM8903_ANALOGUE_RIGHT_INPUT_0, + 7, 1, 1), +SOC_SINGLE("Right Input PGA Volume", WM8903_ANALOGUE_RIGHT_INPUT_0, + 0, 31, 0), +SOC_SINGLE("Right Input PGA Common Mode Switch", WM8903_ANALOGUE_RIGHT_INPUT_1, + 6, 1, 0), + +/* ADCs */ +SOC_SINGLE("DRC Switch", WM8903_DRC_0, 15, 1, 0), +SOC_ENUM("DRC Compressor Slope R0", drc_slope_r0), +SOC_ENUM("DRC Compressor Slope R1", drc_slope_r1), +SOC_SINGLE_TLV("DRC Compressor Threashold Volume", WM8903_DRC_3, 5, 124, 1, + drc_tlv_thresh), +SOC_SINGLE_TLV("DRC Volume", WM8903_DRC_3, 0, 30, 1, drc_tlv_amp), +SOC_SINGLE_TLV("DRC Minimum Gain Volume", WM8903_DRC_1, 2, 3, 1, drc_tlv_min), +SOC_SINGLE_TLV("DRC Maximum Gain Volume", WM8903_DRC_1, 0, 3, 0, drc_tlv_max), +SOC_ENUM("DRC Attack Rate", drc_attack), +SOC_ENUM("DRC Decay Rate", drc_decay), +SOC_ENUM("DRC FF Delay", drc_ff_delay), +SOC_SINGLE("DRC Anticlip Switch", WM8903_DRC_0, 1, 1, 0), +SOC_SINGLE("DRC QR Switch", WM8903_DRC_0, 2, 1, 0), +SOC_SINGLE_TLV("DRC QR Threashold Volume", WM8903_DRC_0, 6, 3, 0, drc_tlv_max), +SOC_ENUM("DRC QR Decay Rate", drc_qr_decay), +SOC_SINGLE("DRC Smoothing Switch", WM8903_DRC_0, 3, 1, 0), +SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8903_DRC_0, 0, 1, 0), +SOC_ENUM("DRC Smoothing Threashold", drc_smoothing), +SOC_SINGLE_TLV("DRC Startup Volume", WM8903_DRC_0, 6, 18, 0, drc_tlv_startup), + +SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT, + WM8903_ADC_DIGITAL_VOLUME_RIGHT, 1, 96, 0, digital_tlv), +SOC_ENUM("ADC Companding Mode", adc_companding), +SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0), + +/* DAC */ +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT, + WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv), +SOC_ENUM("DAC Soft Mute Rate", soft_mute), +SOC_ENUM("DAC Mute Mode", mute_mode), +SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0), +SOC_ENUM("DAC De-emphasis", dac_deemphasis), +SOC_SINGLE("DAC Sloping Stopband Filter Switch", + WM8903_DAC_DIGITAL_1, 11, 1, 0), +SOC_ENUM("DAC Companding Mode", dac_companding), +SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0), + +/* Headphones */ +SOC_DOUBLE_R("Headphone Switch", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 8, 1, 1), +SOC_DOUBLE_R("Headphone ZC Switch", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R_TLV("Headphone Volume", + WM8903_ANALOGUE_OUT1_LEFT, WM8903_ANALOGUE_OUT1_RIGHT, + 0, 63, 0, out_tlv), + +/* Line out */ +SOC_DOUBLE_R("Line Out Switch", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 8, 1, 1), +SOC_DOUBLE_R("Line Out ZC Switch", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R_TLV("Line Out Volume", + WM8903_ANALOGUE_OUT2_LEFT, WM8903_ANALOGUE_OUT2_RIGHT, + 0, 63, 0, out_tlv), + +/* Speaker */ +SOC_DOUBLE_R("Speaker Switch", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 8, 1, 1), +SOC_DOUBLE_R("Speaker ZC Switch", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, 6, 1, 0), +SOC_DOUBLE_R_TLV("Speaker Volume", + WM8903_ANALOGUE_OUT3_LEFT, WM8903_ANALOGUE_OUT3_RIGHT, + 0, 63, 0, out_tlv), +}; + +static int wm8903_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8903_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8903_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + return 0; +} + +static const struct snd_kcontrol_new linput_mode_mux = + SOC_DAPM_ENUM("Left Input Mode Mux", linput_mode_enum); + +static const struct snd_kcontrol_new rinput_mode_mux = + SOC_DAPM_ENUM("Right Input Mode Mux", rinput_mode_enum); + +static const struct snd_kcontrol_new linput_mux = + SOC_DAPM_ENUM("Left Input Mux", linput_enum); + +static const struct snd_kcontrol_new linput_inv_mux = + SOC_DAPM_ENUM("Left Inverting Input Mux", linput_inv_enum); + +static const struct snd_kcontrol_new rinput_mux = + SOC_DAPM_ENUM("Right Input Mux", rinput_enum); + +static const struct snd_kcontrol_new rinput_inv_mux = + SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum); + +static const struct snd_kcontrol_new left_output_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), +SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_LEFT_MIX_0, 1, 1, 0), +}; + +static const struct snd_kcontrol_new right_output_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 2, 1, 0), +SOC_DAPM_SINGLE_W("Left Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0), +SOC_DAPM_SINGLE_W("Right Bypass Switch", WM8903_ANALOGUE_RIGHT_MIX_0, 1, 1, 0), +}; + +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 2, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, 1, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_LEFT_0, + 1, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, 2, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, + 1, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8903_ANALOGUE_SPK_MIX_RIGHT_0, + 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1L"), +SND_SOC_DAPM_INPUT("IN1R"), +SND_SOC_DAPM_INPUT("IN2L"), +SND_SOC_DAPM_INPUT("IN2R"), +SND_SOC_DAPM_INPUT("IN3L"), +SND_SOC_DAPM_INPUT("IN3R"), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +SND_SOC_DAPM_OUTPUT("LINEOUTL"), +SND_SOC_DAPM_OUTPUT("LINEOUTR"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_MICBIAS("Mic Bias", WM8903_MIC_BIAS_CONTROL_0, 0, 0), + +SND_SOC_DAPM_MUX("Left Input Mux", SND_SOC_NOPM, 0, 0, &linput_mux), +SND_SOC_DAPM_MUX("Left Input Inverting Mux", SND_SOC_NOPM, 0, 0, + &linput_inv_mux), +SND_SOC_DAPM_MUX("Left Input Mode Mux", SND_SOC_NOPM, 0, 0, &linput_mode_mux), + +SND_SOC_DAPM_MUX("Right Input Mux", SND_SOC_NOPM, 0, 0, &rinput_mux), +SND_SOC_DAPM_MUX("Right Input Inverting Mux", SND_SOC_NOPM, 0, 0, + &rinput_inv_mux), +SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux), + +SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0), + +SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0, + left_output_mixer, ARRAY_SIZE(left_output_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8903_POWER_MANAGEMENT_1, 0, 0, + right_output_mixer, ARRAY_SIZE(right_output_mixer)), + +SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), + +SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, + 1, 0, NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, + 0, 0, NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0, + NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0, + NULL, 0, wm8903_output_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, + NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, + NULL, 0), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + + { "Left Input Mux", "IN1L", "IN1L" }, + { "Left Input Mux", "IN2L", "IN2L" }, + { "Left Input Mux", "IN3L", "IN3L" }, + + { "Left Input Inverting Mux", "IN1L", "IN1L" }, + { "Left Input Inverting Mux", "IN2L", "IN2L" }, + { "Left Input Inverting Mux", "IN3L", "IN3L" }, + + { "Right Input Mux", "IN1R", "IN1R" }, + { "Right Input Mux", "IN2R", "IN2R" }, + { "Right Input Mux", "IN3R", "IN3R" }, + + { "Right Input Inverting Mux", "IN1R", "IN1R" }, + { "Right Input Inverting Mux", "IN2R", "IN2R" }, + { "Right Input Inverting Mux", "IN3R", "IN3R" }, + + { "Left Input Mode Mux", "Single-Ended", "Left Input Inverting Mux" }, + { "Left Input Mode Mux", "Differential Line", + "Left Input Mux" }, + { "Left Input Mode Mux", "Differential Line", + "Left Input Inverting Mux" }, + { "Left Input Mode Mux", "Differential Mic", + "Left Input Mux" }, + { "Left Input Mode Mux", "Differential Mic", + "Left Input Inverting Mux" }, + + { "Right Input Mode Mux", "Single-Ended", + "Right Input Inverting Mux" }, + { "Right Input Mode Mux", "Differential Line", + "Right Input Mux" }, + { "Right Input Mode Mux", "Differential Line", + "Right Input Inverting Mux" }, + { "Right Input Mode Mux", "Differential Mic", + "Right Input Mux" }, + { "Right Input Mode Mux", "Differential Mic", + "Right Input Inverting Mux" }, + + { "Left Input PGA", NULL, "Left Input Mode Mux" }, + { "Right Input PGA", NULL, "Right Input Mode Mux" }, + + { "ADCL", NULL, "Left Input PGA" }, + { "ADCR", NULL, "Right Input PGA" }, + + { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Left Output Mixer", "DACL Switch", "DACL" }, + { "Left Output Mixer", "DACR Switch", "DACR" }, + + { "Right Output Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Right Output Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Right Output Mixer", "DACL Switch", "DACL" }, + { "Right Output Mixer", "DACR Switch", "DACR" }, + + { "Left Speaker Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Left Speaker Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Left Speaker Mixer", "DACL Switch", "DACL" }, + { "Left Speaker Mixer", "DACR Switch", "DACR" }, + + { "Right Speaker Mixer", "Left Bypass Switch", "Left Input PGA" }, + { "Right Speaker Mixer", "Right Bypass Switch", "Right Input PGA" }, + { "Right Speaker Mixer", "DACL Switch", "DACL" }, + { "Right Speaker Mixer", "DACR Switch", "DACR" }, + + { "Left Line Output PGA", NULL, "Left Output Mixer" }, + { "Right Line Output PGA", NULL, "Right Output Mixer" }, + + { "Left Headphone Output PGA", NULL, "Left Output Mixer" }, + { "Right Headphone Output PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker PGA", NULL, "Left Speaker Mixer" }, + { "Right Speaker PGA", NULL, "Right Speaker Mixer" }, + + { "HPOUTL", NULL, "Left Headphone Output PGA" }, + { "HPOUTR", NULL, "Right Headphone Output PGA" }, + + { "LINEOUTL", NULL, "Left Line Output PGA" }, + { "LINEOUTR", NULL, "Right Line Output PGA" }, + + { "LOP", NULL, "Left Speaker PGA" }, + { "LON", NULL, "Left Speaker PGA" }, + + { "ROP", NULL, "Right Speaker PGA" }, + { "RON", NULL, "Right Speaker PGA" }, +}; + +static int wm8903_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8903_dapm_widgets, + ARRAY_SIZE(wm8903_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +static int wm8903_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct i2c_client *i2c = codec->control_data; + u16 reg, reg2; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + reg = wm8903_read(codec, WM8903_VMID_CONTROL_0); + reg &= ~(WM8903_VMID_RES_MASK); + reg |= WM8903_VMID_RES_50K; + wm8903_write(codec, WM8903_VMID_CONTROL_0, reg); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + wm8903_run_sequence(codec, 0); + wm8903_sync_reg_cache(codec, codec->reg_cache); + + /* Enable low impedence charge pump output */ + reg = wm8903_read(codec, + WM8903_CONTROL_INTERFACE_TEST_1); + wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, + reg | WM8903_TEST_KEY); + reg2 = wm8903_read(codec, WM8903_CHARGE_PUMP_TEST_1); + wm8903_write(codec, WM8903_CHARGE_PUMP_TEST_1, + reg2 | WM8903_CP_SW_KELVIN_MODE_MASK); + wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, + reg); + + /* By default no bypass paths are enabled so + * enable Class W support. + */ + dev_dbg(&i2c->dev, "Enabling Class W\n"); + wm8903_write(codec, WM8903_CLASS_W_0, reg | + WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); + } + + reg = wm8903_read(codec, WM8903_VMID_CONTROL_0); + reg &= ~(WM8903_VMID_RES_MASK); + reg |= WM8903_VMID_RES_250K; + wm8903_write(codec, WM8903_VMID_CONTROL_0, reg); + break; + + case SND_SOC_BIAS_OFF: + wm8903_run_sequence(codec, 32); + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm8903_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8903_priv *wm8903 = codec->private_data; + + wm8903->sysclk = freq; + + return 0; +} + +static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1); + + aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK | + WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif1 |= WM8903_LRCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif1 |= WM8903_BCLK_DIR; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x3; + break; + case SND_SOC_DAIFMT_DSP_B: + aif1 |= 0x3 | WM8903_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + aif1 |= 0x1; + break; + case SND_SOC_DAIFMT_LEFT_J: + break; + default: + return -EINVAL; + } + + /* Clock inversion */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8903_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8903_AIF_BCLK_INV | WM8903_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8903_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8903_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); + + return 0; +} + +static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + reg = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + + if (mute) + reg |= WM8903_DAC_MUTE; + else + reg &= ~WM8903_DAC_MUTE; + + wm8903_write(codec, WM8903_DAC_DIGITAL_1, reg); + + return 0; +} + +/* Lookup table for CLK_SYS/fs ratio. 256fs or more is recommended + * for optimal performance so we list the lower rates first and match + * on the last match we find. */ +static struct { + int div; + int rate; + int mode; + int mclk_div; +} clk_sys_ratios[] = { + { 64, 0x0, 0x0, 1 }, + { 68, 0x0, 0x1, 1 }, + { 125, 0x0, 0x2, 1 }, + { 128, 0x1, 0x0, 1 }, + { 136, 0x1, 0x1, 1 }, + { 192, 0x2, 0x0, 1 }, + { 204, 0x2, 0x1, 1 }, + + { 64, 0x0, 0x0, 2 }, + { 68, 0x0, 0x1, 2 }, + { 125, 0x0, 0x2, 2 }, + { 128, 0x1, 0x0, 2 }, + { 136, 0x1, 0x1, 2 }, + { 192, 0x2, 0x0, 2 }, + { 204, 0x2, 0x1, 2 }, + + { 250, 0x2, 0x2, 1 }, + { 256, 0x3, 0x0, 1 }, + { 272, 0x3, 0x1, 1 }, + { 384, 0x4, 0x0, 1 }, + { 408, 0x4, 0x1, 1 }, + { 375, 0x4, 0x2, 1 }, + { 512, 0x5, 0x0, 1 }, + { 544, 0x5, 0x1, 1 }, + { 500, 0x5, 0x2, 1 }, + { 768, 0x6, 0x0, 1 }, + { 816, 0x6, 0x1, 1 }, + { 750, 0x6, 0x2, 1 }, + { 1024, 0x7, 0x0, 1 }, + { 1088, 0x7, 0x1, 1 }, + { 1000, 0x7, 0x2, 1 }, + { 1408, 0x8, 0x0, 1 }, + { 1496, 0x8, 0x1, 1 }, + { 1536, 0x9, 0x0, 1 }, + { 1632, 0x9, 0x1, 1 }, + { 1500, 0x9, 0x2, 1 }, + + { 250, 0x2, 0x2, 2 }, + { 256, 0x3, 0x0, 2 }, + { 272, 0x3, 0x1, 2 }, + { 384, 0x4, 0x0, 2 }, + { 408, 0x4, 0x1, 2 }, + { 375, 0x4, 0x2, 2 }, + { 512, 0x5, 0x0, 2 }, + { 544, 0x5, 0x1, 2 }, + { 500, 0x5, 0x2, 2 }, + { 768, 0x6, 0x0, 2 }, + { 816, 0x6, 0x1, 2 }, + { 750, 0x6, 0x2, 2 }, + { 1024, 0x7, 0x0, 2 }, + { 1088, 0x7, 0x1, 2 }, + { 1000, 0x7, 0x2, 2 }, + { 1408, 0x8, 0x0, 2 }, + { 1496, 0x8, 0x1, 2 }, + { 1536, 0x9, 0x0, 2 }, + { 1632, 0x9, 0x1, 2 }, + { 1500, 0x9, 0x2, 2 }, +}; + +/* CLK_SYS/BCLK ratios - multiplied by 10 due to .5s */ +static struct { + int ratio; + int div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 50, 5 }, + { 55, 6 }, + { 60, 7 }, + { 80, 8 }, + { 100, 9 }, + { 110, 10 }, + { 120, 11 }, + { 160, 12 }, + { 200, 13 }, + { 220, 14 }, + { 240, 15 }, + { 250, 16 }, + { 300, 17 }, + { 320, 18 }, + { 440, 19 }, + { 480, 20 }, +}; + +/* Sample rates for DSP */ +static struct { + int rate; + int value; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 88200, 9 }, + { 96000, 10 }, + { 0, 0 }, +}; + +static int wm8903_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + struct snd_pcm_runtime *master_runtime; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + wm8903->playback_active++; + else + wm8903->capture_active++; + + /* The DAI has shared clocks so if we already have a playback or + * capture going then constrain this substream to match it. + */ + if (wm8903->master_substream) { + master_runtime = wm8903->master_substream->runtime; + + dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n", + master_runtime->sample_bits, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); + + wm8903->slave_substream = substream; + } else + wm8903->master_substream = substream; + + return 0; +} + +static void wm8903_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8903_priv *wm8903 = codec->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + wm8903->playback_active--; + else + wm8903->capture_active--; + + if (wm8903->master_substream == substream) + wm8903->master_substream = wm8903->slave_substream; + + wm8903->slave_substream = NULL; +} + +static int wm8903_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8903_priv *wm8903 = codec->private_data; + struct i2c_client *i2c = codec->control_data; + int fs = params_rate(params); + int bclk; + int bclk_div; + int i; + int dsp_config; + int clk_config; + int best_val; + int cur_val; + int clk_sys; + + u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1); + u16 aif2 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_2); + u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3); + u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0); + u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1); + + if (substream == wm8903->slave_substream) { + dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n"); + return 0; + } + + /* Configure sample rate logic for DSP - choose nearest rate */ + dsp_config = 0; + best_val = abs(sample_rates[dsp_config].rate - fs); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + cur_val = abs(sample_rates[i].rate - fs); + if (cur_val <= best_val) { + dsp_config = i; + best_val = cur_val; + } + } + + /* Constraints should stop us hitting this but let's make sure */ + if (wm8903->capture_active) + switch (sample_rates[dsp_config].rate) { + case 88200: + case 96000: + dev_err(&i2c->dev, "%dHz unsupported by ADC\n", + fs); + return -EINVAL; + + default: + break; + } + + dev_dbg(&i2c->dev, "DSP fs = %dHz\n", sample_rates[dsp_config].rate); + clock1 &= ~WM8903_SAMPLE_RATE_MASK; + clock1 |= sample_rates[dsp_config].value; + + aif1 &= ~WM8903_AIF_WL_MASK; + bclk = 2 * fs; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + bclk *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + bclk *= 20; + aif1 |= 0x4; + break; + case SNDRV_PCM_FORMAT_S24_LE: + bclk *= 24; + aif1 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S32_LE: + bclk *= 32; + aif1 |= 0xc; + break; + default: + return -EINVAL; + } + + dev_dbg(&i2c->dev, "MCLK = %dHz, target sample rate = %dHz\n", + wm8903->sysclk, fs); + + /* We may not have an MCLK which allows us to generate exactly + * the clock we want, particularly with USB derived inputs, so + * approximate. + */ + clk_config = 0; + best_val = abs((wm8903->sysclk / + (clk_sys_ratios[0].mclk_div * + clk_sys_ratios[0].div)) - fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_ratios); i++) { + cur_val = abs((wm8903->sysclk / + (clk_sys_ratios[i].mclk_div * + clk_sys_ratios[i].div)) - fs); + + if (cur_val <= best_val) { + clk_config = i; + best_val = cur_val; + } + } + + if (clk_sys_ratios[clk_config].mclk_div == 2) { + clock0 |= WM8903_MCLKDIV2; + clk_sys = wm8903->sysclk / 2; + } else { + clock0 &= ~WM8903_MCLKDIV2; + clk_sys = wm8903->sysclk; + } + + clock1 &= ~(WM8903_CLK_SYS_RATE_MASK | + WM8903_CLK_SYS_MODE_MASK); + clock1 |= clk_sys_ratios[clk_config].rate << WM8903_CLK_SYS_RATE_SHIFT; + clock1 |= clk_sys_ratios[clk_config].mode << WM8903_CLK_SYS_MODE_SHIFT; + + dev_dbg(&i2c->dev, "CLK_SYS_RATE=%x, CLK_SYS_MODE=%x div=%d\n", + clk_sys_ratios[clk_config].rate, + clk_sys_ratios[clk_config].mode, + clk_sys_ratios[clk_config].div); + + dev_dbg(&i2c->dev, "Actual CLK_SYS = %dHz\n", clk_sys); + + /* We may not get quite the right frequency if using + * approximate clocks so look for the closest match that is + * higher than the target (we need to ensure that there enough + * BCLKs to clock out the samples). + */ + bclk_div = 0; + best_val = ((clk_sys * 10) / bclk_divs[0].ratio) - bclk; + i = 1; + while (i < ARRAY_SIZE(bclk_divs)) { + cur_val = ((clk_sys * 10) / bclk_divs[i].ratio) - bclk; + if (cur_val < 0) /* BCLK table is sorted */ + break; + bclk_div = i; + best_val = cur_val; + i++; + } + + aif2 &= ~WM8903_BCLK_DIV_MASK; + aif3 &= ~WM8903_LRCLK_RATE_MASK; + + dev_dbg(&i2c->dev, "BCLK ratio %d for %dHz - actual BCLK = %dHz\n", + bclk_divs[bclk_div].ratio / 10, bclk, + (clk_sys * 10) / bclk_divs[bclk_div].ratio); + + aif2 |= bclk_divs[bclk_div].div; + aif3 |= bclk / fs; + + wm8903_write(codec, WM8903_CLOCK_RATES_0, clock0); + wm8903_write(codec, WM8903_CLOCK_RATES_1, clock1); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2); + wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3); + + return 0; +} + +#define WM8903_PLAYBACK_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define WM8903_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define WM8903_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8903_dai = { + .name = "WM8903", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8903_PLAYBACK_RATES, + .formats = WM8903_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8903_CAPTURE_RATES, + .formats = WM8903_FORMATS, + }, + .ops = { + .startup = wm8903_startup, + .shutdown = wm8903_shutdown, + .hw_params = wm8903_hw_params, + }, + .dai_ops = { + .digital_mute = wm8903_digital_mute, + .set_fmt = wm8903_set_dai_fmt, + .set_sysclk = wm8903_set_dai_sysclk + } +}; +EXPORT_SYMBOL_GPL(wm8903_dai); + +static int wm8903_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8903_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c = codec->control_data; + int i; + u16 *reg_cache = codec->reg_cache; + u16 *tmp_cache = kmemdup(codec->reg_cache, sizeof(wm8903_reg_defaults), + GFP_KERNEL); + + /* Bring the codec back up to standby first to minimise pop/clicks */ + wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8903_set_bias_level(codec, codec->suspend_bias_level); + + /* Sync back everything else */ + if (tmp_cache) { + for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++) + if (tmp_cache[i] != reg_cache[i]) + wm8903_write(codec, i, tmp_cache[i]); + } else { + dev_err(&i2c->dev, "Failed to allocate temporary cache\n"); + } + + return 0; +} + +/* + * initialise the WM8903 driver + * register the mixer and dsp interfaces with the kernel + */ +static int wm8903_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + struct i2c_client *i2c = codec->control_data; + int ret = 0; + u16 val; + + val = wm8903_hw_read(codec, WM8903_SW_RESET_AND_ID); + if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) { + dev_err(&i2c->dev, + "Device with ID register %x is not a WM8903\n", val); + return -ENODEV; + } + + codec->name = "WM8903"; + codec->owner = THIS_MODULE; + codec->read = wm8903_read; + codec->write = wm8903_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8903_set_bias_level; + codec->dai = &wm8903_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8903_reg_defaults); + codec->reg_cache = kmemdup(wm8903_reg_defaults, + sizeof(wm8903_reg_defaults), + GFP_KERNEL); + if (codec->reg_cache == NULL) { + dev_err(&i2c->dev, "Failed to allocate register cache\n"); + return -ENOMEM; + } + + val = wm8903_read(codec, WM8903_REVISION_NUMBER); + dev_info(&i2c->dev, "WM8903 revision %d\n", + val & WM8903_CHIP_REV_MASK); + + wm8903_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&i2c->dev, "failed to create pcms\n"); + goto pcm_err; + } + + /* SYSCLK is required for pretty much anything */ + wm8903_write(codec, WM8903_CLOCK_RATES_2, WM8903_CLK_SYS_ENA); + + /* power on device */ + wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Latch volume update bits */ + val = wm8903_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT); + val |= WM8903_ADCVU; + wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val); + wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val); + + val = wm8903_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT); + val |= WM8903_DACVU; + wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val); + wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT1_LEFT); + val |= WM8903_HPOUTVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT2_LEFT); + val |= WM8903_LINEOUTVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val); + + val = wm8903_read(codec, WM8903_ANALOGUE_OUT3_LEFT); + val |= WM8903_SPKVU; + wm8903_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val); + wm8903_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val); + + /* Enable DAC soft mute by default */ + val = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + val |= WM8903_DAC_MUTEMODE; + wm8903_write(codec, WM8903_DAC_DIGITAL_1, val); + + wm8903_add_controls(codec); + wm8903_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + dev_err(&i2c->dev, "wm8903: failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +static struct snd_soc_device *wm8903_socdev; + +static int wm8903_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8903_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + codec->control_data = i2c; + + ret = wm8903_init(socdev); + if (ret < 0) + dev_err(&i2c->dev, "Device initialisation failed\n"); + + return ret; +} + +static int wm8903_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +/* i2c codec control layer */ +static const struct i2c_device_id wm8903_i2c_id[] = { + { "wm8903", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id); + +static struct i2c_driver wm8903_i2c_driver = { + .driver = { + .name = "WM8903", + .owner = THIS_MODULE, + }, + .probe = wm8903_i2c_probe, + .remove = wm8903_i2c_remove, + .id_table = wm8903_i2c_id, +}; + +static int wm8903_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8903_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8903_priv *wm8903; + struct i2c_board_info board_info; + struct i2c_adapter *adapter; + struct i2c_client *i2c_client; + int ret = 0; + + setup = socdev->codec_data; + + if (!setup->i2c_address) { + dev_err(&pdev->dev, "No codec address provided\n"); + return -ENODEV; + } + + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8903 = kzalloc(sizeof(struct wm8903_priv), GFP_KERNEL); + if (wm8903 == NULL) { + ret = -ENOMEM; + goto err_codec; + } + + codec->private_data = wm8903; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + wm8903_socdev = socdev; + + codec->hw_write = (hw_write_t)i2c_master_send; + ret = i2c_add_driver(&wm8903_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + goto err_priv; + } else { + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "wm8903", I2C_NAME_SIZE); + board_info.addr = setup->i2c_address; + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "Can't get I2C bus %d\n", + setup->i2c_bus); + ret = -ENODEV; + goto err_adapter; + } + + i2c_client = i2c_new_device(adapter, &board_info); + i2c_put_adapter(adapter); + if (i2c_client == NULL) { + dev_err(&pdev->dev, + "I2C driver registration failed\n"); + ret = -ENODEV; + goto err_adapter; + } + } + + return ret; + +err_adapter: + i2c_del_driver(&wm8903_i2c_driver); +err_priv: + kfree(codec->private_data); +err_codec: + kfree(codec); + return ret; +} + +/* power down chip */ +static int wm8903_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + i2c_unregister_device(socdev->codec->control_data); + i2c_del_driver(&wm8903_i2c_driver); + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8903 = { + .probe = wm8903_probe, + .remove = wm8903_remove, + .suspend = wm8903_suspend, + .resume = wm8903_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8903); + +MODULE_DESCRIPTION("ASoC WM8903 driver"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.cm>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h new file mode 100644 index 0000000..cec622f --- /dev/null +++ b/sound/soc/codecs/wm8903.h @@ -0,0 +1,1463 @@ +/* + * wm8903.h - WM8903 audio codec interface + * + * Copyright 2008 Wolfson Microelectronics PLC. + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _WM8903_H +#define _WM8903_H + +#include <linux/i2c.h> + +extern struct snd_soc_dai wm8903_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8903; + +struct wm8903_setup_data { + int i2c_bus; + int i2c_address; +}; + +#define WM8903_MCLK_DIV_2 1 +#define WM8903_CLK_SYS 2 +#define WM8903_BCLK 3 +#define WM8903_LRCLK 4 + +/* + * Register values. + */ +#define WM8903_SW_RESET_AND_ID 0x00 +#define WM8903_REVISION_NUMBER 0x01 +#define WM8903_BIAS_CONTROL_0 0x04 +#define WM8903_VMID_CONTROL_0 0x05 +#define WM8903_MIC_BIAS_CONTROL_0 0x06 +#define WM8903_ANALOGUE_DAC_0 0x08 +#define WM8903_ANALOGUE_ADC_0 0x0A +#define WM8903_POWER_MANAGEMENT_0 0x0C +#define WM8903_POWER_MANAGEMENT_1 0x0D +#define WM8903_POWER_MANAGEMENT_2 0x0E +#define WM8903_POWER_MANAGEMENT_3 0x0F +#define WM8903_POWER_MANAGEMENT_4 0x10 +#define WM8903_POWER_MANAGEMENT_5 0x11 +#define WM8903_POWER_MANAGEMENT_6 0x12 +#define WM8903_CLOCK_RATES_0 0x14 +#define WM8903_CLOCK_RATES_1 0x15 +#define WM8903_CLOCK_RATES_2 0x16 +#define WM8903_AUDIO_INTERFACE_0 0x18 +#define WM8903_AUDIO_INTERFACE_1 0x19 +#define WM8903_AUDIO_INTERFACE_2 0x1A +#define WM8903_AUDIO_INTERFACE_3 0x1B +#define WM8903_DAC_DIGITAL_VOLUME_LEFT 0x1E +#define WM8903_DAC_DIGITAL_VOLUME_RIGHT 0x1F +#define WM8903_DAC_DIGITAL_0 0x20 +#define WM8903_DAC_DIGITAL_1 0x21 +#define WM8903_ADC_DIGITAL_VOLUME_LEFT 0x24 +#define WM8903_ADC_DIGITAL_VOLUME_RIGHT 0x25 +#define WM8903_ADC_DIGITAL_0 0x26 +#define WM8903_DIGITAL_MICROPHONE_0 0x27 +#define WM8903_DRC_0 0x28 +#define WM8903_DRC_1 0x29 +#define WM8903_DRC_2 0x2A +#define WM8903_DRC_3 0x2B +#define WM8903_ANALOGUE_LEFT_INPUT_0 0x2C +#define WM8903_ANALOGUE_RIGHT_INPUT_0 0x2D +#define WM8903_ANALOGUE_LEFT_INPUT_1 0x2E +#define WM8903_ANALOGUE_RIGHT_INPUT_1 0x2F +#define WM8903_ANALOGUE_LEFT_MIX_0 0x32 +#define WM8903_ANALOGUE_RIGHT_MIX_0 0x33 +#define WM8903_ANALOGUE_SPK_MIX_LEFT_0 0x34 +#define WM8903_ANALOGUE_SPK_MIX_LEFT_1 0x35 +#define WM8903_ANALOGUE_SPK_MIX_RIGHT_0 0x36 +#define WM8903_ANALOGUE_SPK_MIX_RIGHT_1 0x37 +#define WM8903_ANALOGUE_OUT1_LEFT 0x39 +#define WM8903_ANALOGUE_OUT1_RIGHT 0x3A +#define WM8903_ANALOGUE_OUT2_LEFT 0x3B +#define WM8903_ANALOGUE_OUT2_RIGHT 0x3C +#define WM8903_ANALOGUE_OUT3_LEFT 0x3E +#define WM8903_ANALOGUE_OUT3_RIGHT 0x3F +#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41 +#define WM8903_DC_SERVO_0 0x43 +#define WM8903_DC_SERVO_2 0x45 +#define WM8903_ANALOGUE_HP_0 0x5A +#define WM8903_ANALOGUE_LINEOUT_0 0x5E +#define WM8903_CHARGE_PUMP_0 0x62 +#define WM8903_CLASS_W_0 0x68 +#define WM8903_WRITE_SEQUENCER_0 0x6C +#define WM8903_WRITE_SEQUENCER_1 0x6D +#define WM8903_WRITE_SEQUENCER_2 0x6E +#define WM8903_WRITE_SEQUENCER_3 0x6F +#define WM8903_WRITE_SEQUENCER_4 0x70 +#define WM8903_CONTROL_INTERFACE 0x72 +#define WM8903_GPIO_CONTROL_1 0x74 +#define WM8903_GPIO_CONTROL_2 0x75 +#define WM8903_GPIO_CONTROL_3 0x76 +#define WM8903_GPIO_CONTROL_4 0x77 +#define WM8903_GPIO_CONTROL_5 0x78 +#define WM8903_INTERRUPT_STATUS_1 0x79 +#define WM8903_INTERRUPT_STATUS_1_MASK 0x7A +#define WM8903_INTERRUPT_POLARITY_1 0x7B +#define WM8903_INTERRUPT_CONTROL 0x7E +#define WM8903_CONTROL_INTERFACE_TEST_1 0x81 +#define WM8903_CHARGE_PUMP_TEST_1 0x95 +#define WM8903_CLOCK_RATE_TEST_4 0xA4 +#define WM8903_ANALOGUE_OUTPUT_BIAS_0 0xAC + +#define WM8903_REGISTER_COUNT 75 +#define WM8903_MAX_REGISTER 0xAC + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - SW Reset and ID + */ +#define WM8903_SW_RESET_DEV_ID1_MASK 0xFFFF /* SW_RESET_DEV_ID1 - [15:0] */ +#define WM8903_SW_RESET_DEV_ID1_SHIFT 0 /* SW_RESET_DEV_ID1 - [15:0] */ +#define WM8903_SW_RESET_DEV_ID1_WIDTH 16 /* SW_RESET_DEV_ID1 - [15:0] */ + +/* + * R1 (0x01) - Revision Number + */ +#define WM8903_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */ +#define WM8903_CHIP_REV_SHIFT 0 /* CHIP_REV - [3:0] */ +#define WM8903_CHIP_REV_WIDTH 4 /* CHIP_REV - [3:0] */ + +/* + * R4 (0x04) - Bias Control 0 + */ +#define WM8903_POBCTRL 0x0010 /* POBCTRL */ +#define WM8903_POBCTRL_MASK 0x0010 /* POBCTRL */ +#define WM8903_POBCTRL_SHIFT 4 /* POBCTRL */ +#define WM8903_POBCTRL_WIDTH 1 /* POBCTRL */ +#define WM8903_ISEL_MASK 0x000C /* ISEL - [3:2] */ +#define WM8903_ISEL_SHIFT 2 /* ISEL - [3:2] */ +#define WM8903_ISEL_WIDTH 2 /* ISEL - [3:2] */ +#define WM8903_STARTUP_BIAS_ENA 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_MASK 0x0002 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_SHIFT 1 /* STARTUP_BIAS_ENA */ +#define WM8903_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ +#define WM8903_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM8903_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R5 (0x05) - VMID Control 0 + */ +#define WM8903_VMID_TIE_ENA 0x0080 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_MASK 0x0080 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_SHIFT 7 /* VMID_TIE_ENA */ +#define WM8903_VMID_TIE_ENA_WIDTH 1 /* VMID_TIE_ENA */ +#define WM8903_BUFIO_ENA 0x0040 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_MASK 0x0040 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_SHIFT 6 /* BUFIO_ENA */ +#define WM8903_BUFIO_ENA_WIDTH 1 /* BUFIO_ENA */ +#define WM8903_VMID_IO_ENA 0x0020 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_MASK 0x0020 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_SHIFT 5 /* VMID_IO_ENA */ +#define WM8903_VMID_IO_ENA_WIDTH 1 /* VMID_IO_ENA */ +#define WM8903_VMID_SOFT_MASK 0x0018 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_SOFT_SHIFT 3 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_SOFT_WIDTH 2 /* VMID_SOFT - [4:3] */ +#define WM8903_VMID_RES_MASK 0x0006 /* VMID_RES - [2:1] */ +#define WM8903_VMID_RES_SHIFT 1 /* VMID_RES - [2:1] */ +#define WM8903_VMID_RES_WIDTH 2 /* VMID_RES - [2:1] */ +#define WM8903_VMID_BUF_ENA 0x0001 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_MASK 0x0001 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_SHIFT 0 /* VMID_BUF_ENA */ +#define WM8903_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ + +#define WM8903_VMID_RES_50K 2 +#define WM8903_VMID_RES_250K 3 +#define WM8903_VMID_RES_5K 4 + +/* + * R6 (0x06) - Mic Bias Control 0 + */ +#define WM8903_MICDET_HYST_ENA 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_MASK 0x0080 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_SHIFT 7 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_HYST_ENA_WIDTH 1 /* MICDET_HYST_ENA */ +#define WM8903_MICDET_THR_MASK 0x0070 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_SHIFT 4 /* MICDET_THR - [6:4] */ +#define WM8903_MICDET_THR_WIDTH 3 /* MICDET_THR - [6:4] */ +#define WM8903_MICSHORT_THR_MASK 0x000C /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_SHIFT 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICSHORT_THR_WIDTH 2 /* MICSHORT_THR - [3:2] */ +#define WM8903_MICDET_ENA 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_MASK 0x0002 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_SHIFT 1 /* MICDET_ENA */ +#define WM8903_MICDET_ENA_WIDTH 1 /* MICDET_ENA */ +#define WM8903_MICBIAS_ENA 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_MASK 0x0001 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_SHIFT 0 /* MICBIAS_ENA */ +#define WM8903_MICBIAS_ENA_WIDTH 1 /* MICBIAS_ENA */ + +/* + * R8 (0x08) - Analogue DAC 0 + */ +#define WM8903_DACBIAS_SEL_MASK 0x0018 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACBIAS_SEL_SHIFT 3 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACBIAS_SEL_WIDTH 2 /* DACBIAS_SEL - [4:3] */ +#define WM8903_DACVMID_BIAS_SEL_MASK 0x0006 /* DACVMID_BIAS_SEL - [2:1] */ +#define WM8903_DACVMID_BIAS_SEL_SHIFT 1 /* DACVMID_BIAS_SEL - [2:1] */ +#define WM8903_DACVMID_BIAS_SEL_WIDTH 2 /* DACVMID_BIAS_SEL - [2:1] */ + +/* + * R10 (0x0A) - Analogue ADC 0 + */ +#define WM8903_ADC_OSR128 0x0001 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_MASK 0x0001 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_SHIFT 0 /* ADC_OSR128 */ +#define WM8903_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ + +/* + * R12 (0x0C) - Power Management 0 + */ +#define WM8903_INL_ENA 0x0002 /* INL_ENA */ +#define WM8903_INL_ENA_MASK 0x0002 /* INL_ENA */ +#define WM8903_INL_ENA_SHIFT 1 /* INL_ENA */ +#define WM8903_INL_ENA_WIDTH 1 /* INL_ENA */ +#define WM8903_INR_ENA 0x0001 /* INR_ENA */ +#define WM8903_INR_ENA_MASK 0x0001 /* INR_ENA */ +#define WM8903_INR_ENA_SHIFT 0 /* INR_ENA */ +#define WM8903_INR_ENA_WIDTH 1 /* INR_ENA */ + +/* + * R13 (0x0D) - Power Management 1 + */ +#define WM8903_MIXOUTL_ENA 0x0002 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_MASK 0x0002 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_SHIFT 1 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM8903_MIXOUTR_ENA 0x0001 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_MASK 0x0001 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_SHIFT 0 /* MIXOUTR_ENA */ +#define WM8903_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ + +/* + * R14 (0x0E) - Power Management 2 + */ +#define WM8903_HPL_PGA_ENA 0x0002 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_MASK 0x0002 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_SHIFT 1 /* HPL_PGA_ENA */ +#define WM8903_HPL_PGA_ENA_WIDTH 1 /* HPL_PGA_ENA */ +#define WM8903_HPR_PGA_ENA 0x0001 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_MASK 0x0001 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_SHIFT 0 /* HPR_PGA_ENA */ +#define WM8903_HPR_PGA_ENA_WIDTH 1 /* HPR_PGA_ENA */ + +/* + * R15 (0x0F) - Power Management 3 + */ +#define WM8903_LINEOUTL_PGA_ENA 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_MASK 0x0002 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_SHIFT 1 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTL_PGA_ENA_WIDTH 1 /* LINEOUTL_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_MASK 0x0001 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_SHIFT 0 /* LINEOUTR_PGA_ENA */ +#define WM8903_LINEOUTR_PGA_ENA_WIDTH 1 /* LINEOUTR_PGA_ENA */ + +/* + * R16 (0x10) - Power Management 4 + */ +#define WM8903_MIXSPKL_ENA 0x0002 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_MASK 0x0002 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_SHIFT 1 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKL_ENA_WIDTH 1 /* MIXSPKL_ENA */ +#define WM8903_MIXSPKR_ENA 0x0001 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_MASK 0x0001 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_SHIFT 0 /* MIXSPKR_ENA */ +#define WM8903_MIXSPKR_ENA_WIDTH 1 /* MIXSPKR_ENA */ + +/* + * R17 (0x11) - Power Management 5 + */ +#define WM8903_SPKL_ENA 0x0002 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_MASK 0x0002 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_SHIFT 1 /* SPKL_ENA */ +#define WM8903_SPKL_ENA_WIDTH 1 /* SPKL_ENA */ +#define WM8903_SPKR_ENA 0x0001 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_MASK 0x0001 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_SHIFT 0 /* SPKR_ENA */ +#define WM8903_SPKR_ENA_WIDTH 1 /* SPKR_ENA */ + +/* + * R18 (0x12) - Power Management 6 + */ +#define WM8903_DACL_ENA 0x0008 /* DACL_ENA */ +#define WM8903_DACL_ENA_MASK 0x0008 /* DACL_ENA */ +#define WM8903_DACL_ENA_SHIFT 3 /* DACL_ENA */ +#define WM8903_DACL_ENA_WIDTH 1 /* DACL_ENA */ +#define WM8903_DACR_ENA 0x0004 /* DACR_ENA */ +#define WM8903_DACR_ENA_MASK 0x0004 /* DACR_ENA */ +#define WM8903_DACR_ENA_SHIFT 2 /* DACR_ENA */ +#define WM8903_DACR_ENA_WIDTH 1 /* DACR_ENA */ +#define WM8903_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8903_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8903_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8903_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R20 (0x14) - Clock Rates 0 + */ +#define WM8903_MCLKDIV2 0x0001 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_MASK 0x0001 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_SHIFT 0 /* MCLKDIV2 */ +#define WM8903_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ + +/* + * R21 (0x15) - Clock Rates 1 + */ +#define WM8903_CLK_SYS_RATE_MASK 0x3C00 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_RATE_SHIFT 10 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [13:10] */ +#define WM8903_CLK_SYS_MODE_MASK 0x0300 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_CLK_SYS_MODE_SHIFT 8 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_CLK_SYS_MODE_WIDTH 2 /* CLK_SYS_MODE - [9:8] */ +#define WM8903_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */ +#define WM8903_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */ +#define WM8903_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */ + +/* + * R22 (0x16) - Clock Rates 2 + */ +#define WM8903_CLK_SYS_ENA 0x0004 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_MASK 0x0004 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_SHIFT 2 /* CLK_SYS_ENA */ +#define WM8903_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ +#define WM8903_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */ +#define WM8903_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM8903_TO_ENA 0x0001 /* TO_ENA */ +#define WM8903_TO_ENA_MASK 0x0001 /* TO_ENA */ +#define WM8903_TO_ENA_SHIFT 0 /* TO_ENA */ +#define WM8903_TO_ENA_WIDTH 1 /* TO_ENA */ + +/* + * R24 (0x18) - Audio Interface 0 + */ +#define WM8903_DACL_DATINV 0x1000 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_MASK 0x1000 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_SHIFT 12 /* DACL_DATINV */ +#define WM8903_DACL_DATINV_WIDTH 1 /* DACL_DATINV */ +#define WM8903_DACR_DATINV 0x0800 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_MASK 0x0800 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_SHIFT 11 /* DACR_DATINV */ +#define WM8903_DACR_DATINV_WIDTH 1 /* DACR_DATINV */ +#define WM8903_DAC_BOOST_MASK 0x0600 /* DAC_BOOST - [10:9] */ +#define WM8903_DAC_BOOST_SHIFT 9 /* DAC_BOOST - [10:9] */ +#define WM8903_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [10:9] */ +#define WM8903_LOOPBACK 0x0100 /* LOOPBACK */ +#define WM8903_LOOPBACK_MASK 0x0100 /* LOOPBACK */ +#define WM8903_LOOPBACK_SHIFT 8 /* LOOPBACK */ +#define WM8903_LOOPBACK_WIDTH 1 /* LOOPBACK */ +#define WM8903_AIFADCL_SRC 0x0080 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_MASK 0x0080 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_SHIFT 7 /* AIFADCL_SRC */ +#define WM8903_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */ +#define WM8903_AIFADCR_SRC 0x0040 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_MASK 0x0040 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_SHIFT 6 /* AIFADCR_SRC */ +#define WM8903_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */ +#define WM8903_AIFDACL_SRC 0x0020 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_MASK 0x0020 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_SHIFT 5 /* AIFDACL_SRC */ +#define WM8903_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */ +#define WM8903_AIFDACR_SRC 0x0010 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_MASK 0x0010 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_SHIFT 4 /* AIFDACR_SRC */ +#define WM8903_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */ +#define WM8903_ADC_COMP 0x0008 /* ADC_COMP */ +#define WM8903_ADC_COMP_MASK 0x0008 /* ADC_COMP */ +#define WM8903_ADC_COMP_SHIFT 3 /* ADC_COMP */ +#define WM8903_ADC_COMP_WIDTH 1 /* ADC_COMP */ +#define WM8903_ADC_COMPMODE 0x0004 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_MASK 0x0004 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_SHIFT 2 /* ADC_COMPMODE */ +#define WM8903_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */ +#define WM8903_DAC_COMP 0x0002 /* DAC_COMP */ +#define WM8903_DAC_COMP_MASK 0x0002 /* DAC_COMP */ +#define WM8903_DAC_COMP_SHIFT 1 /* DAC_COMP */ +#define WM8903_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM8903_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */ +#define WM8903_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ + +/* + * R25 (0x19) - Audio Interface 1 + */ +#define WM8903_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */ +#define WM8903_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */ +#define WM8903_AIFADC_TDM 0x0800 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_MASK 0x0800 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_SHIFT 11 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */ +#define WM8903_AIFADC_TDM_CHAN 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_MASK 0x0400 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_SHIFT 10 /* AIFADC_TDM_CHAN */ +#define WM8903_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */ +#define WM8903_LRCLK_DIR 0x0200 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_MASK 0x0200 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_SHIFT 9 /* LRCLK_DIR */ +#define WM8903_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM8903_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */ +#define WM8903_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM8903_BCLK_DIR 0x0040 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_SHIFT 6 /* BCLK_DIR */ +#define WM8903_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM8903_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */ +#define WM8903_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM8903_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */ +#define WM8903_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */ +#define WM8903_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */ +#define WM8903_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */ +#define WM8903_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */ +#define WM8903_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */ + +/* + * R26 (0x1A) - Audio Interface 2 + */ +#define WM8903_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */ +#define WM8903_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */ +#define WM8903_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */ + +/* + * R27 (0x1B) - Audio Interface 3 + */ +#define WM8903_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM8903_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM8903_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R30 (0x1E) - DAC Digital Volume Left + */ +#define WM8903_DACVU 0x0100 /* DACVU */ +#define WM8903_DACVU_MASK 0x0100 /* DACVU */ +#define WM8903_DACVU_SHIFT 8 /* DACVU */ +#define WM8903_DACVU_WIDTH 1 /* DACVU */ +#define WM8903_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8903_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */ +#define WM8903_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */ + +/* + * R31 (0x1F) - DAC Digital Volume Right + */ +#define WM8903_DACVU 0x0100 /* DACVU */ +#define WM8903_DACVU_MASK 0x0100 /* DACVU */ +#define WM8903_DACVU_SHIFT 8 /* DACVU */ +#define WM8903_DACVU_WIDTH 1 /* DACVU */ +#define WM8903_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8903_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */ +#define WM8903_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */ + +/* + * R32 (0x20) - DAC Digital 0 + */ +#define WM8903_ADCL_DAC_SVOL_MASK 0x0F00 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCL_DAC_SVOL_SHIFT 8 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [11:8] */ +#define WM8903_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8903_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ +#define WM8903_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */ +#define WM8903_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */ +#define WM8903_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */ + +/* + * R33 (0x21) - DAC Digital 1 + */ +#define WM8903_DAC_MONO 0x1000 /* DAC_MONO */ +#define WM8903_DAC_MONO_MASK 0x1000 /* DAC_MONO */ +#define WM8903_DAC_MONO_SHIFT 12 /* DAC_MONO */ +#define WM8903_DAC_MONO_WIDTH 1 /* DAC_MONO */ +#define WM8903_DAC_SB_FILT 0x0800 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_MASK 0x0800 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_SHIFT 11 /* DAC_SB_FILT */ +#define WM8903_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */ +#define WM8903_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM8903_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */ +#define WM8903_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM8903_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM8903_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8903_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8903_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R36 (0x24) - ADC Digital Volume Left + */ +#define WM8903_ADCVU 0x0100 /* ADCVU */ +#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8903_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8903_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8903_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8903_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */ +#define WM8903_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */ + +/* + * R37 (0x25) - ADC Digital Volume Right + */ +#define WM8903_ADCVU 0x0100 /* ADCVU */ +#define WM8903_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8903_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8903_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8903_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8903_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */ +#define WM8903_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */ + +/* + * R38 (0x26) - ADC Digital 0 + */ +#define WM8903_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */ +#define WM8903_ADC_HPF_ENA 0x0010 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_MASK 0x0010 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_SHIFT 4 /* ADC_HPF_ENA */ +#define WM8903_ADC_HPF_ENA_WIDTH 1 /* ADC_HPF_ENA */ +#define WM8903_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */ +#define WM8903_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */ +#define WM8903_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */ +#define WM8903_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */ + +/* + * R39 (0x27) - Digital Microphone 0 + */ +#define WM8903_DIGMIC_MODE_SEL 0x0100 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_MASK 0x0100 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_SHIFT 8 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_MODE_SEL_WIDTH 1 /* DIGMIC_MODE_SEL */ +#define WM8903_DIGMIC_CLK_SEL_L_MASK 0x00C0 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_L_SHIFT 6 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_L_WIDTH 2 /* DIGMIC_CLK_SEL_L - [7:6] */ +#define WM8903_DIGMIC_CLK_SEL_R_MASK 0x0030 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_R_SHIFT 4 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_R_WIDTH 2 /* DIGMIC_CLK_SEL_R - [5:4] */ +#define WM8903_DIGMIC_CLK_SEL_RT_MASK 0x000C /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_RT_SHIFT 2 /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_RT_WIDTH 2 /* DIGMIC_CLK_SEL_RT - [3:2] */ +#define WM8903_DIGMIC_CLK_SEL_MASK 0x0003 /* DIGMIC_CLK_SEL - [1:0] */ +#define WM8903_DIGMIC_CLK_SEL_SHIFT 0 /* DIGMIC_CLK_SEL - [1:0] */ +#define WM8903_DIGMIC_CLK_SEL_WIDTH 2 /* DIGMIC_CLK_SEL - [1:0] */ + +/* + * R40 (0x28) - DRC 0 + */ +#define WM8903_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM8903_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM8903_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM8903_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM8903_DRC_THRESH_HYST_MASK 0x1800 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_THRESH_HYST_SHIFT 11 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [12:11] */ +#define WM8903_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM8903_DRC_FF_DELAY 0x0020 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_MASK 0x0020 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_SHIFT 5 /* DRC_FF_DELAY */ +#define WM8903_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */ +#define WM8903_DRC_SMOOTH_ENA 0x0008 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_MASK 0x0008 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_SHIFT 3 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */ +#define WM8903_DRC_QR_ENA 0x0004 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_MASK 0x0004 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_SHIFT 2 /* DRC_QR_ENA */ +#define WM8903_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */ +#define WM8903_DRC_ANTICLIP_ENA 0x0002 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_MASK 0x0002 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_SHIFT 1 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */ +#define WM8903_DRC_HYST_ENA 0x0001 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_MASK 0x0001 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_SHIFT 0 /* DRC_HYST_ENA */ +#define WM8903_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */ + +/* + * R41 (0x29) - DRC 1 + */ +#define WM8903_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8903_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */ +#define WM8903_DRC_THRESH_QR_MASK 0x00C0 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_THRESH_QR_SHIFT 6 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [7:6] */ +#define WM8903_DRC_RATE_QR_MASK 0x0030 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_RATE_QR_SHIFT 4 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [5:4] */ +#define WM8903_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM8903_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM8903_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM8903_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R42 (0x2A) - DRC 2 + */ +#define WM8903_DRC_R0_SLOPE_COMP_MASK 0x0038 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R0_SLOPE_COMP_SHIFT 3 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [5:3] */ +#define WM8903_DRC_R1_SLOPE_COMP_MASK 0x0007 /* DRC_R1_SLOPE_COMP - [2:0] */ +#define WM8903_DRC_R1_SLOPE_COMP_SHIFT 0 /* DRC_R1_SLOPE_COMP - [2:0] */ +#define WM8903_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [2:0] */ + +/* + * R43 (0x2B) - DRC 3 + */ +#define WM8903_DRC_THRESH_COMP_MASK 0x07E0 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_THRESH_COMP_SHIFT 5 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [10:5] */ +#define WM8903_DRC_AMP_COMP_MASK 0x001F /* DRC_AMP_COMP - [4:0] */ +#define WM8903_DRC_AMP_COMP_SHIFT 0 /* DRC_AMP_COMP - [4:0] */ +#define WM8903_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [4:0] */ + +/* + * R44 (0x2C) - Analogue Left Input 0 + */ +#define WM8903_LINMUTE 0x0080 /* LINMUTE */ +#define WM8903_LINMUTE_MASK 0x0080 /* LINMUTE */ +#define WM8903_LINMUTE_SHIFT 7 /* LINMUTE */ +#define WM8903_LINMUTE_WIDTH 1 /* LINMUTE */ +#define WM8903_LIN_VOL_MASK 0x001F /* LIN_VOL - [4:0] */ +#define WM8903_LIN_VOL_SHIFT 0 /* LIN_VOL - [4:0] */ +#define WM8903_LIN_VOL_WIDTH 5 /* LIN_VOL - [4:0] */ + +/* + * R45 (0x2D) - Analogue Right Input 0 + */ +#define WM8903_RINMUTE 0x0080 /* RINMUTE */ +#define WM8903_RINMUTE_MASK 0x0080 /* RINMUTE */ +#define WM8903_RINMUTE_SHIFT 7 /* RINMUTE */ +#define WM8903_RINMUTE_WIDTH 1 /* RINMUTE */ +#define WM8903_RIN_VOL_MASK 0x001F /* RIN_VOL - [4:0] */ +#define WM8903_RIN_VOL_SHIFT 0 /* RIN_VOL - [4:0] */ +#define WM8903_RIN_VOL_WIDTH 5 /* RIN_VOL - [4:0] */ + +/* + * R46 (0x2E) - Analogue Left Input 1 + */ +#define WM8903_INL_CM_ENA 0x0040 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_MASK 0x0040 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_SHIFT 6 /* INL_CM_ENA */ +#define WM8903_INL_CM_ENA_WIDTH 1 /* INL_CM_ENA */ +#define WM8903_L_IP_SEL_N_MASK 0x0030 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_N_SHIFT 4 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_N_WIDTH 2 /* L_IP_SEL_N - [5:4] */ +#define WM8903_L_IP_SEL_P_MASK 0x000C /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_IP_SEL_P_SHIFT 2 /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_IP_SEL_P_WIDTH 2 /* L_IP_SEL_P - [3:2] */ +#define WM8903_L_MODE_MASK 0x0003 /* L_MODE - [1:0] */ +#define WM8903_L_MODE_SHIFT 0 /* L_MODE - [1:0] */ +#define WM8903_L_MODE_WIDTH 2 /* L_MODE - [1:0] */ + +/* + * R47 (0x2F) - Analogue Right Input 1 + */ +#define WM8903_INR_CM_ENA 0x0040 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_MASK 0x0040 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_SHIFT 6 /* INR_CM_ENA */ +#define WM8903_INR_CM_ENA_WIDTH 1 /* INR_CM_ENA */ +#define WM8903_R_IP_SEL_N_MASK 0x0030 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_N_SHIFT 4 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_N_WIDTH 2 /* R_IP_SEL_N - [5:4] */ +#define WM8903_R_IP_SEL_P_MASK 0x000C /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_IP_SEL_P_SHIFT 2 /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_IP_SEL_P_WIDTH 2 /* R_IP_SEL_P - [3:2] */ +#define WM8903_R_MODE_MASK 0x0003 /* R_MODE - [1:0] */ +#define WM8903_R_MODE_SHIFT 0 /* R_MODE - [1:0] */ +#define WM8903_R_MODE_WIDTH 2 /* R_MODE - [1:0] */ + +/* + * R50 (0x32) - Analogue Left Mix 0 + */ +#define WM8903_DACL_TO_MIXOUTL 0x0008 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_MASK 0x0008 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_SHIFT 3 /* DACL_TO_MIXOUTL */ +#define WM8903_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL 0x0004 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_MASK 0x0004 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_SHIFT 2 /* DACR_TO_MIXOUTL */ +#define WM8903_DACR_TO_MIXOUTL_WIDTH 1 /* DACR_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL 0x0002 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_MASK 0x0002 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_SHIFT 1 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSL_TO_MIXOUTL_WIDTH 1 /* BYPASSL_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL 0x0001 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_MASK 0x0001 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_SHIFT 0 /* BYPASSR_TO_MIXOUTL */ +#define WM8903_BYPASSR_TO_MIXOUTL_WIDTH 1 /* BYPASSR_TO_MIXOUTL */ + +/* + * R51 (0x33) - Analogue Right Mix 0 + */ +#define WM8903_DACL_TO_MIXOUTR 0x0008 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_MASK 0x0008 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_SHIFT 3 /* DACL_TO_MIXOUTR */ +#define WM8903_DACL_TO_MIXOUTR_WIDTH 1 /* DACL_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR 0x0004 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_MASK 0x0004 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_SHIFT 2 /* DACR_TO_MIXOUTR */ +#define WM8903_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR 0x0002 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_MASK 0x0002 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_SHIFT 1 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSL_TO_MIXOUTR_WIDTH 1 /* BYPASSL_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR 0x0001 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_MASK 0x0001 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_SHIFT 0 /* BYPASSR_TO_MIXOUTR */ +#define WM8903_BYPASSR_TO_MIXOUTR_WIDTH 1 /* BYPASSR_TO_MIXOUTR */ + +/* + * R52 (0x34) - Analogue Spk Mix Left 0 + */ +#define WM8903_DACL_TO_MIXSPKL 0x0008 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_MASK 0x0008 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_SHIFT 3 /* DACL_TO_MIXSPKL */ +#define WM8903_DACL_TO_MIXSPKL_WIDTH 1 /* DACL_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL 0x0004 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_MASK 0x0004 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_SHIFT 2 /* DACR_TO_MIXSPKL */ +#define WM8903_DACR_TO_MIXSPKL_WIDTH 1 /* DACR_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL 0x0002 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_MASK 0x0002 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_SHIFT 1 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSL_TO_MIXSPKL_WIDTH 1 /* BYPASSL_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL 0x0001 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_MASK 0x0001 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_SHIFT 0 /* BYPASSR_TO_MIXSPKL */ +#define WM8903_BYPASSR_TO_MIXSPKL_WIDTH 1 /* BYPASSR_TO_MIXSPKL */ + +/* + * R53 (0x35) - Analogue Spk Mix Left 1 + */ +#define WM8903_DACL_MIXSPKL_VOL 0x0008 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_MASK 0x0008 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_SHIFT 3 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACL_MIXSPKL_VOL_WIDTH 1 /* DACL_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL 0x0004 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_MASK 0x0004 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_SHIFT 2 /* DACR_MIXSPKL_VOL */ +#define WM8903_DACR_MIXSPKL_VOL_WIDTH 1 /* DACR_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL 0x0002 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_MASK 0x0002 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_SHIFT 1 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSL_MIXSPKL_VOL_WIDTH 1 /* BYPASSL_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL 0x0001 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_MASK 0x0001 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_SHIFT 0 /* BYPASSR_MIXSPKL_VOL */ +#define WM8903_BYPASSR_MIXSPKL_VOL_WIDTH 1 /* BYPASSR_MIXSPKL_VOL */ + +/* + * R54 (0x36) - Analogue Spk Mix Right 0 + */ +#define WM8903_DACL_TO_MIXSPKR 0x0008 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_MASK 0x0008 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_SHIFT 3 /* DACL_TO_MIXSPKR */ +#define WM8903_DACL_TO_MIXSPKR_WIDTH 1 /* DACL_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR 0x0004 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_MASK 0x0004 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_SHIFT 2 /* DACR_TO_MIXSPKR */ +#define WM8903_DACR_TO_MIXSPKR_WIDTH 1 /* DACR_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR 0x0002 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_MASK 0x0002 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_SHIFT 1 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSL_TO_MIXSPKR_WIDTH 1 /* BYPASSL_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR 0x0001 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_MASK 0x0001 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_SHIFT 0 /* BYPASSR_TO_MIXSPKR */ +#define WM8903_BYPASSR_TO_MIXSPKR_WIDTH 1 /* BYPASSR_TO_MIXSPKR */ + +/* + * R55 (0x37) - Analogue Spk Mix Right 1 + */ +#define WM8903_DACL_MIXSPKR_VOL 0x0008 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_MASK 0x0008 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_SHIFT 3 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACL_MIXSPKR_VOL_WIDTH 1 /* DACL_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL 0x0004 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_MASK 0x0004 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_SHIFT 2 /* DACR_MIXSPKR_VOL */ +#define WM8903_DACR_MIXSPKR_VOL_WIDTH 1 /* DACR_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL 0x0002 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_MASK 0x0002 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_SHIFT 1 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSL_MIXSPKR_VOL_WIDTH 1 /* BYPASSL_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL 0x0001 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_MASK 0x0001 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_SHIFT 0 /* BYPASSR_MIXSPKR_VOL */ +#define WM8903_BYPASSR_MIXSPKR_VOL_WIDTH 1 /* BYPASSR_MIXSPKR_VOL */ + +/* + * R57 (0x39) - Analogue OUT1 Left + */ +#define WM8903_HPL_MUTE 0x0100 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_MASK 0x0100 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_SHIFT 8 /* HPL_MUTE */ +#define WM8903_HPL_MUTE_WIDTH 1 /* HPL_MUTE */ +#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */ +#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */ +#define WM8903_HPOUTLZC 0x0040 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_MASK 0x0040 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_SHIFT 6 /* HPOUTLZC */ +#define WM8903_HPOUTLZC_WIDTH 1 /* HPOUTLZC */ +#define WM8903_HPOUTL_VOL_MASK 0x003F /* HPOUTL_VOL - [5:0] */ +#define WM8903_HPOUTL_VOL_SHIFT 0 /* HPOUTL_VOL - [5:0] */ +#define WM8903_HPOUTL_VOL_WIDTH 6 /* HPOUTL_VOL - [5:0] */ + +/* + * R58 (0x3A) - Analogue OUT1 Right + */ +#define WM8903_HPR_MUTE 0x0100 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_MASK 0x0100 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_SHIFT 8 /* HPR_MUTE */ +#define WM8903_HPR_MUTE_WIDTH 1 /* HPR_MUTE */ +#define WM8903_HPOUTVU 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_MASK 0x0080 /* HPOUTVU */ +#define WM8903_HPOUTVU_SHIFT 7 /* HPOUTVU */ +#define WM8903_HPOUTVU_WIDTH 1 /* HPOUTVU */ +#define WM8903_HPOUTRZC 0x0040 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_MASK 0x0040 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_SHIFT 6 /* HPOUTRZC */ +#define WM8903_HPOUTRZC_WIDTH 1 /* HPOUTRZC */ +#define WM8903_HPOUTR_VOL_MASK 0x003F /* HPOUTR_VOL - [5:0] */ +#define WM8903_HPOUTR_VOL_SHIFT 0 /* HPOUTR_VOL - [5:0] */ +#define WM8903_HPOUTR_VOL_WIDTH 6 /* HPOUTR_VOL - [5:0] */ + +/* + * R59 (0x3B) - Analogue OUT2 Left + */ +#define WM8903_LINEOUTL_MUTE 0x0100 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_MASK 0x0100 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_SHIFT 8 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTL_MUTE_WIDTH 1 /* LINEOUTL_MUTE */ +#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */ +#define WM8903_LINEOUTLZC 0x0040 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_MASK 0x0040 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_SHIFT 6 /* LINEOUTLZC */ +#define WM8903_LINEOUTLZC_WIDTH 1 /* LINEOUTLZC */ +#define WM8903_LINEOUTL_VOL_MASK 0x003F /* LINEOUTL_VOL - [5:0] */ +#define WM8903_LINEOUTL_VOL_SHIFT 0 /* LINEOUTL_VOL - [5:0] */ +#define WM8903_LINEOUTL_VOL_WIDTH 6 /* LINEOUTL_VOL - [5:0] */ + +/* + * R60 (0x3C) - Analogue OUT2 Right + */ +#define WM8903_LINEOUTR_MUTE 0x0100 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_MASK 0x0100 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_SHIFT 8 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTR_MUTE_WIDTH 1 /* LINEOUTR_MUTE */ +#define WM8903_LINEOUTVU 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_MASK 0x0080 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_SHIFT 7 /* LINEOUTVU */ +#define WM8903_LINEOUTVU_WIDTH 1 /* LINEOUTVU */ +#define WM8903_LINEOUTRZC 0x0040 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_MASK 0x0040 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_SHIFT 6 /* LINEOUTRZC */ +#define WM8903_LINEOUTRZC_WIDTH 1 /* LINEOUTRZC */ +#define WM8903_LINEOUTR_VOL_MASK 0x003F /* LINEOUTR_VOL - [5:0] */ +#define WM8903_LINEOUTR_VOL_SHIFT 0 /* LINEOUTR_VOL - [5:0] */ +#define WM8903_LINEOUTR_VOL_WIDTH 6 /* LINEOUTR_VOL - [5:0] */ + +/* + * R62 (0x3E) - Analogue OUT3 Left + */ +#define WM8903_SPKL_MUTE 0x0100 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_MASK 0x0100 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_SHIFT 8 /* SPKL_MUTE */ +#define WM8903_SPKL_MUTE_WIDTH 1 /* SPKL_MUTE */ +#define WM8903_SPKVU 0x0080 /* SPKVU */ +#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */ +#define WM8903_SPKVU_SHIFT 7 /* SPKVU */ +#define WM8903_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8903_SPKLZC 0x0040 /* SPKLZC */ +#define WM8903_SPKLZC_MASK 0x0040 /* SPKLZC */ +#define WM8903_SPKLZC_SHIFT 6 /* SPKLZC */ +#define WM8903_SPKLZC_WIDTH 1 /* SPKLZC */ +#define WM8903_SPKL_VOL_MASK 0x003F /* SPKL_VOL - [5:0] */ +#define WM8903_SPKL_VOL_SHIFT 0 /* SPKL_VOL - [5:0] */ +#define WM8903_SPKL_VOL_WIDTH 6 /* SPKL_VOL - [5:0] */ + +/* + * R63 (0x3F) - Analogue OUT3 Right + */ +#define WM8903_SPKR_MUTE 0x0100 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_MASK 0x0100 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_SHIFT 8 /* SPKR_MUTE */ +#define WM8903_SPKR_MUTE_WIDTH 1 /* SPKR_MUTE */ +#define WM8903_SPKVU 0x0080 /* SPKVU */ +#define WM8903_SPKVU_MASK 0x0080 /* SPKVU */ +#define WM8903_SPKVU_SHIFT 7 /* SPKVU */ +#define WM8903_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8903_SPKRZC 0x0040 /* SPKRZC */ +#define WM8903_SPKRZC_MASK 0x0040 /* SPKRZC */ +#define WM8903_SPKRZC_SHIFT 6 /* SPKRZC */ +#define WM8903_SPKRZC_WIDTH 1 /* SPKRZC */ +#define WM8903_SPKR_VOL_MASK 0x003F /* SPKR_VOL - [5:0] */ +#define WM8903_SPKR_VOL_SHIFT 0 /* SPKR_VOL - [5:0] */ +#define WM8903_SPKR_VOL_WIDTH 6 /* SPKR_VOL - [5:0] */ + +/* + * R65 (0x41) - Analogue SPK Output Control 0 + */ +#define WM8903_SPK_DISCHARGE 0x0002 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_MASK 0x0002 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_SHIFT 1 /* SPK_DISCHARGE */ +#define WM8903_SPK_DISCHARGE_WIDTH 1 /* SPK_DISCHARGE */ +#define WM8903_VROI 0x0001 /* VROI */ +#define WM8903_VROI_MASK 0x0001 /* VROI */ +#define WM8903_VROI_SHIFT 0 /* VROI */ +#define WM8903_VROI_WIDTH 1 /* VROI */ + +/* + * R67 (0x43) - DC Servo 0 + */ +#define WM8903_DCS_MASTER_ENA 0x0010 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_MASK 0x0010 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_SHIFT 4 /* DCS_MASTER_ENA */ +#define WM8903_DCS_MASTER_ENA_WIDTH 1 /* DCS_MASTER_ENA */ +#define WM8903_DCS_ENA_MASK 0x000F /* DCS_ENA - [3:0] */ +#define WM8903_DCS_ENA_SHIFT 0 /* DCS_ENA - [3:0] */ +#define WM8903_DCS_ENA_WIDTH 4 /* DCS_ENA - [3:0] */ + +/* + * R69 (0x45) - DC Servo 2 + */ +#define WM8903_DCS_MODE_MASK 0x0003 /* DCS_MODE - [1:0] */ +#define WM8903_DCS_MODE_SHIFT 0 /* DCS_MODE - [1:0] */ +#define WM8903_DCS_MODE_WIDTH 2 /* DCS_MODE - [1:0] */ + +/* + * R90 (0x5A) - Analogue HP 0 + */ +#define WM8903_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */ +#define WM8903_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */ +#define WM8903_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */ +#define WM8903_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */ +#define WM8903_HPL_ENA 0x0010 /* HPL_ENA */ +#define WM8903_HPL_ENA_MASK 0x0010 /* HPL_ENA */ +#define WM8903_HPL_ENA_SHIFT 4 /* HPL_ENA */ +#define WM8903_HPL_ENA_WIDTH 1 /* HPL_ENA */ +#define WM8903_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */ +#define WM8903_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */ +#define WM8903_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */ +#define WM8903_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */ +#define WM8903_HPR_ENA 0x0001 /* HPR_ENA */ +#define WM8903_HPR_ENA_MASK 0x0001 /* HPR_ENA */ +#define WM8903_HPR_ENA_SHIFT 0 /* HPR_ENA */ +#define WM8903_HPR_ENA_WIDTH 1 /* HPR_ENA */ + +/* + * R94 (0x5E) - Analogue Lineout 0 + */ +#define WM8903_LINEOUTL_RMV_SHORT 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_MASK 0x0080 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_SHIFT 7 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_RMV_SHORT_WIDTH 1 /* LINEOUTL_RMV_SHORT */ +#define WM8903_LINEOUTL_ENA_OUTP 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_MASK 0x0040 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_SHIFT 6 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_OUTP_WIDTH 1 /* LINEOUTL_ENA_OUTP */ +#define WM8903_LINEOUTL_ENA_DLY 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_MASK 0x0020 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_SHIFT 5 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA_DLY_WIDTH 1 /* LINEOUTL_ENA_DLY */ +#define WM8903_LINEOUTL_ENA 0x0010 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_MASK 0x0010 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_SHIFT 4 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTL_ENA_WIDTH 1 /* LINEOUTL_ENA */ +#define WM8903_LINEOUTR_RMV_SHORT 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_MASK 0x0008 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_SHIFT 3 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_RMV_SHORT_WIDTH 1 /* LINEOUTR_RMV_SHORT */ +#define WM8903_LINEOUTR_ENA_OUTP 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_MASK 0x0004 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_SHIFT 2 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_OUTP_WIDTH 1 /* LINEOUTR_ENA_OUTP */ +#define WM8903_LINEOUTR_ENA_DLY 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_MASK 0x0002 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_SHIFT 1 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA_DLY_WIDTH 1 /* LINEOUTR_ENA_DLY */ +#define WM8903_LINEOUTR_ENA 0x0001 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_MASK 0x0001 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_SHIFT 0 /* LINEOUTR_ENA */ +#define WM8903_LINEOUTR_ENA_WIDTH 1 /* LINEOUTR_ENA */ + +/* + * R98 (0x62) - Charge Pump 0 + */ +#define WM8903_CP_ENA 0x0001 /* CP_ENA */ +#define WM8903_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8903_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8903_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R104 (0x68) - Class W 0 + */ +#define WM8903_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */ +#define WM8903_CP_DYN_V 0x0001 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_SHIFT 0 /* CP_DYN_V */ +#define WM8903_CP_DYN_V_WIDTH 1 /* CP_DYN_V */ + +/* + * R108 (0x6C) - Write Sequencer 0 + */ +#define WM8903_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM8903_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8903_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8903_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8903_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R109 (0x6D) - Write Sequencer 1 + */ +#define WM8903_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8903_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM8903_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8903_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8903_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R110 (0x6E) - Write Sequencer 2 + */ +#define WM8903_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM8903_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8903_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM8903_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8903_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8903_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R111 (0x6F) - Write Sequencer 3 + */ +#define WM8903_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8903_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8903_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8903_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8903_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8903_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8903_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8903_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8903_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R112 (0x70) - Write Sequencer 4 + */ +#define WM8903_WSEQ_CURRENT_INDEX_MASK 0x03F0 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [9:4] */ +#define WM8903_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8903_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R114 (0x72) - Control Interface + */ +#define WM8903_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */ +#define WM8903_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */ + +/* + * R116 (0x74) - GPIO Control 1 + */ +#define WM8903_GP1_FN_MASK 0x1F00 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_SHIFT 8 /* GP1_FN - [12:8] */ +#define WM8903_GP1_FN_WIDTH 5 /* GP1_FN - [12:8] */ +#define WM8903_GP1_DIR 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_MASK 0x0080 /* GP1_DIR */ +#define WM8903_GP1_DIR_SHIFT 7 /* GP1_DIR */ +#define WM8903_GP1_DIR_WIDTH 1 /* GP1_DIR */ +#define WM8903_GP1_OP_CFG 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_MASK 0x0040 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_SHIFT 6 /* GP1_OP_CFG */ +#define WM8903_GP1_OP_CFG_WIDTH 1 /* GP1_OP_CFG */ +#define WM8903_GP1_IP_CFG 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_MASK 0x0020 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_SHIFT 5 /* GP1_IP_CFG */ +#define WM8903_GP1_IP_CFG_WIDTH 1 /* GP1_IP_CFG */ +#define WM8903_GP1_LVL 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_MASK 0x0010 /* GP1_LVL */ +#define WM8903_GP1_LVL_SHIFT 4 /* GP1_LVL */ +#define WM8903_GP1_LVL_WIDTH 1 /* GP1_LVL */ +#define WM8903_GP1_PD 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_MASK 0x0008 /* GP1_PD */ +#define WM8903_GP1_PD_SHIFT 3 /* GP1_PD */ +#define WM8903_GP1_PD_WIDTH 1 /* GP1_PD */ +#define WM8903_GP1_PU 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_MASK 0x0004 /* GP1_PU */ +#define WM8903_GP1_PU_SHIFT 2 /* GP1_PU */ +#define WM8903_GP1_PU_WIDTH 1 /* GP1_PU */ +#define WM8903_GP1_INTMODE 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_MASK 0x0002 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_SHIFT 1 /* GP1_INTMODE */ +#define WM8903_GP1_INTMODE_WIDTH 1 /* GP1_INTMODE */ +#define WM8903_GP1_DB 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_MASK 0x0001 /* GP1_DB */ +#define WM8903_GP1_DB_SHIFT 0 /* GP1_DB */ +#define WM8903_GP1_DB_WIDTH 1 /* GP1_DB */ + +/* + * R117 (0x75) - GPIO Control 2 + */ +#define WM8903_GP2_FN_MASK 0x1F00 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_SHIFT 8 /* GP2_FN - [12:8] */ +#define WM8903_GP2_FN_WIDTH 5 /* GP2_FN - [12:8] */ +#define WM8903_GP2_DIR 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_MASK 0x0080 /* GP2_DIR */ +#define WM8903_GP2_DIR_SHIFT 7 /* GP2_DIR */ +#define WM8903_GP2_DIR_WIDTH 1 /* GP2_DIR */ +#define WM8903_GP2_OP_CFG 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_MASK 0x0040 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_SHIFT 6 /* GP2_OP_CFG */ +#define WM8903_GP2_OP_CFG_WIDTH 1 /* GP2_OP_CFG */ +#define WM8903_GP2_IP_CFG 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_MASK 0x0020 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_SHIFT 5 /* GP2_IP_CFG */ +#define WM8903_GP2_IP_CFG_WIDTH 1 /* GP2_IP_CFG */ +#define WM8903_GP2_LVL 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_MASK 0x0010 /* GP2_LVL */ +#define WM8903_GP2_LVL_SHIFT 4 /* GP2_LVL */ +#define WM8903_GP2_LVL_WIDTH 1 /* GP2_LVL */ +#define WM8903_GP2_PD 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_MASK 0x0008 /* GP2_PD */ +#define WM8903_GP2_PD_SHIFT 3 /* GP2_PD */ +#define WM8903_GP2_PD_WIDTH 1 /* GP2_PD */ +#define WM8903_GP2_PU 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_MASK 0x0004 /* GP2_PU */ +#define WM8903_GP2_PU_SHIFT 2 /* GP2_PU */ +#define WM8903_GP2_PU_WIDTH 1 /* GP2_PU */ +#define WM8903_GP2_INTMODE 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_MASK 0x0002 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_SHIFT 1 /* GP2_INTMODE */ +#define WM8903_GP2_INTMODE_WIDTH 1 /* GP2_INTMODE */ +#define WM8903_GP2_DB 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_MASK 0x0001 /* GP2_DB */ +#define WM8903_GP2_DB_SHIFT 0 /* GP2_DB */ +#define WM8903_GP2_DB_WIDTH 1 /* GP2_DB */ + +/* + * R118 (0x76) - GPIO Control 3 + */ +#define WM8903_GP3_FN_MASK 0x1F00 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_SHIFT 8 /* GP3_FN - [12:8] */ +#define WM8903_GP3_FN_WIDTH 5 /* GP3_FN - [12:8] */ +#define WM8903_GP3_DIR 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_MASK 0x0080 /* GP3_DIR */ +#define WM8903_GP3_DIR_SHIFT 7 /* GP3_DIR */ +#define WM8903_GP3_DIR_WIDTH 1 /* GP3_DIR */ +#define WM8903_GP3_OP_CFG 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_MASK 0x0040 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_SHIFT 6 /* GP3_OP_CFG */ +#define WM8903_GP3_OP_CFG_WIDTH 1 /* GP3_OP_CFG */ +#define WM8903_GP3_IP_CFG 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_MASK 0x0020 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_SHIFT 5 /* GP3_IP_CFG */ +#define WM8903_GP3_IP_CFG_WIDTH 1 /* GP3_IP_CFG */ +#define WM8903_GP3_LVL 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_MASK 0x0010 /* GP3_LVL */ +#define WM8903_GP3_LVL_SHIFT 4 /* GP3_LVL */ +#define WM8903_GP3_LVL_WIDTH 1 /* GP3_LVL */ +#define WM8903_GP3_PD 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_MASK 0x0008 /* GP3_PD */ +#define WM8903_GP3_PD_SHIFT 3 /* GP3_PD */ +#define WM8903_GP3_PD_WIDTH 1 /* GP3_PD */ +#define WM8903_GP3_PU 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_MASK 0x0004 /* GP3_PU */ +#define WM8903_GP3_PU_SHIFT 2 /* GP3_PU */ +#define WM8903_GP3_PU_WIDTH 1 /* GP3_PU */ +#define WM8903_GP3_INTMODE 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_MASK 0x0002 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_SHIFT 1 /* GP3_INTMODE */ +#define WM8903_GP3_INTMODE_WIDTH 1 /* GP3_INTMODE */ +#define WM8903_GP3_DB 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_MASK 0x0001 /* GP3_DB */ +#define WM8903_GP3_DB_SHIFT 0 /* GP3_DB */ +#define WM8903_GP3_DB_WIDTH 1 /* GP3_DB */ + +/* + * R119 (0x77) - GPIO Control 4 + */ +#define WM8903_GP4_FN_MASK 0x1F00 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_SHIFT 8 /* GP4_FN - [12:8] */ +#define WM8903_GP4_FN_WIDTH 5 /* GP4_FN - [12:8] */ +#define WM8903_GP4_DIR 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_MASK 0x0080 /* GP4_DIR */ +#define WM8903_GP4_DIR_SHIFT 7 /* GP4_DIR */ +#define WM8903_GP4_DIR_WIDTH 1 /* GP4_DIR */ +#define WM8903_GP4_OP_CFG 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_MASK 0x0040 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_SHIFT 6 /* GP4_OP_CFG */ +#define WM8903_GP4_OP_CFG_WIDTH 1 /* GP4_OP_CFG */ +#define WM8903_GP4_IP_CFG 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_MASK 0x0020 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_SHIFT 5 /* GP4_IP_CFG */ +#define WM8903_GP4_IP_CFG_WIDTH 1 /* GP4_IP_CFG */ +#define WM8903_GP4_LVL 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_MASK 0x0010 /* GP4_LVL */ +#define WM8903_GP4_LVL_SHIFT 4 /* GP4_LVL */ +#define WM8903_GP4_LVL_WIDTH 1 /* GP4_LVL */ +#define WM8903_GP4_PD 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_MASK 0x0008 /* GP4_PD */ +#define WM8903_GP4_PD_SHIFT 3 /* GP4_PD */ +#define WM8903_GP4_PD_WIDTH 1 /* GP4_PD */ +#define WM8903_GP4_PU 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_MASK 0x0004 /* GP4_PU */ +#define WM8903_GP4_PU_SHIFT 2 /* GP4_PU */ +#define WM8903_GP4_PU_WIDTH 1 /* GP4_PU */ +#define WM8903_GP4_INTMODE 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_MASK 0x0002 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_SHIFT 1 /* GP4_INTMODE */ +#define WM8903_GP4_INTMODE_WIDTH 1 /* GP4_INTMODE */ +#define WM8903_GP4_DB 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_MASK 0x0001 /* GP4_DB */ +#define WM8903_GP4_DB_SHIFT 0 /* GP4_DB */ +#define WM8903_GP4_DB_WIDTH 1 /* GP4_DB */ + +/* + * R120 (0x78) - GPIO Control 5 + */ +#define WM8903_GP5_FN_MASK 0x1F00 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_SHIFT 8 /* GP5_FN - [12:8] */ +#define WM8903_GP5_FN_WIDTH 5 /* GP5_FN - [12:8] */ +#define WM8903_GP5_DIR 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_MASK 0x0080 /* GP5_DIR */ +#define WM8903_GP5_DIR_SHIFT 7 /* GP5_DIR */ +#define WM8903_GP5_DIR_WIDTH 1 /* GP5_DIR */ +#define WM8903_GP5_OP_CFG 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_MASK 0x0040 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_SHIFT 6 /* GP5_OP_CFG */ +#define WM8903_GP5_OP_CFG_WIDTH 1 /* GP5_OP_CFG */ +#define WM8903_GP5_IP_CFG 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_MASK 0x0020 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_SHIFT 5 /* GP5_IP_CFG */ +#define WM8903_GP5_IP_CFG_WIDTH 1 /* GP5_IP_CFG */ +#define WM8903_GP5_LVL 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_MASK 0x0010 /* GP5_LVL */ +#define WM8903_GP5_LVL_SHIFT 4 /* GP5_LVL */ +#define WM8903_GP5_LVL_WIDTH 1 /* GP5_LVL */ +#define WM8903_GP5_PD 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_MASK 0x0008 /* GP5_PD */ +#define WM8903_GP5_PD_SHIFT 3 /* GP5_PD */ +#define WM8903_GP5_PD_WIDTH 1 /* GP5_PD */ +#define WM8903_GP5_PU 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_MASK 0x0004 /* GP5_PU */ +#define WM8903_GP5_PU_SHIFT 2 /* GP5_PU */ +#define WM8903_GP5_PU_WIDTH 1 /* GP5_PU */ +#define WM8903_GP5_INTMODE 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_MASK 0x0002 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_SHIFT 1 /* GP5_INTMODE */ +#define WM8903_GP5_INTMODE_WIDTH 1 /* GP5_INTMODE */ +#define WM8903_GP5_DB 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_MASK 0x0001 /* GP5_DB */ +#define WM8903_GP5_DB_SHIFT 0 /* GP5_DB */ +#define WM8903_GP5_DB_WIDTH 1 /* GP5_DB */ + +/* + * R121 (0x79) - Interrupt Status 1 + */ +#define WM8903_MICSHRT_EINT 0x8000 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_MASK 0x8000 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_SHIFT 15 /* MICSHRT_EINT */ +#define WM8903_MICSHRT_EINT_WIDTH 1 /* MICSHRT_EINT */ +#define WM8903_MICDET_EINT 0x4000 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_MASK 0x4000 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_SHIFT 14 /* MICDET_EINT */ +#define WM8903_MICDET_EINT_WIDTH 1 /* MICDET_EINT */ +#define WM8903_WSEQ_BUSY_EINT 0x2000 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_MASK 0x2000 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_SHIFT 13 /* WSEQ_BUSY_EINT */ +#define WM8903_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */ +#define WM8903_GP5_EINT 0x0010 /* GP5_EINT */ +#define WM8903_GP5_EINT_MASK 0x0010 /* GP5_EINT */ +#define WM8903_GP5_EINT_SHIFT 4 /* GP5_EINT */ +#define WM8903_GP5_EINT_WIDTH 1 /* GP5_EINT */ +#define WM8903_GP4_EINT 0x0008 /* GP4_EINT */ +#define WM8903_GP4_EINT_MASK 0x0008 /* GP4_EINT */ +#define WM8903_GP4_EINT_SHIFT 3 /* GP4_EINT */ +#define WM8903_GP4_EINT_WIDTH 1 /* GP4_EINT */ +#define WM8903_GP3_EINT 0x0004 /* GP3_EINT */ +#define WM8903_GP3_EINT_MASK 0x0004 /* GP3_EINT */ +#define WM8903_GP3_EINT_SHIFT 2 /* GP3_EINT */ +#define WM8903_GP3_EINT_WIDTH 1 /* GP3_EINT */ +#define WM8903_GP2_EINT 0x0002 /* GP2_EINT */ +#define WM8903_GP2_EINT_MASK 0x0002 /* GP2_EINT */ +#define WM8903_GP2_EINT_SHIFT 1 /* GP2_EINT */ +#define WM8903_GP2_EINT_WIDTH 1 /* GP2_EINT */ +#define WM8903_GP1_EINT 0x0001 /* GP1_EINT */ +#define WM8903_GP1_EINT_MASK 0x0001 /* GP1_EINT */ +#define WM8903_GP1_EINT_SHIFT 0 /* GP1_EINT */ +#define WM8903_GP1_EINT_WIDTH 1 /* GP1_EINT */ + +/* + * R122 (0x7A) - Interrupt Status 1 Mask + */ +#define WM8903_IM_MICSHRT_EINT 0x8000 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_MASK 0x8000 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_SHIFT 15 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICSHRT_EINT_WIDTH 1 /* IM_MICSHRT_EINT */ +#define WM8903_IM_MICDET_EINT 0x4000 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_MASK 0x4000 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_SHIFT 14 /* IM_MICDET_EINT */ +#define WM8903_IM_MICDET_EINT_WIDTH 1 /* IM_MICDET_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT 0x2000 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_MASK 0x2000 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_SHIFT 13 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */ +#define WM8903_IM_GP5_EINT 0x0010 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_MASK 0x0010 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_SHIFT 4 /* IM_GP5_EINT */ +#define WM8903_IM_GP5_EINT_WIDTH 1 /* IM_GP5_EINT */ +#define WM8903_IM_GP4_EINT 0x0008 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_MASK 0x0008 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_SHIFT 3 /* IM_GP4_EINT */ +#define WM8903_IM_GP4_EINT_WIDTH 1 /* IM_GP4_EINT */ +#define WM8903_IM_GP3_EINT 0x0004 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_MASK 0x0004 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_SHIFT 2 /* IM_GP3_EINT */ +#define WM8903_IM_GP3_EINT_WIDTH 1 /* IM_GP3_EINT */ +#define WM8903_IM_GP2_EINT 0x0002 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_MASK 0x0002 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_SHIFT 1 /* IM_GP2_EINT */ +#define WM8903_IM_GP2_EINT_WIDTH 1 /* IM_GP2_EINT */ +#define WM8903_IM_GP1_EINT 0x0001 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_MASK 0x0001 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_SHIFT 0 /* IM_GP1_EINT */ +#define WM8903_IM_GP1_EINT_WIDTH 1 /* IM_GP1_EINT */ + +/* + * R123 (0x7B) - Interrupt Polarity 1 + */ +#define WM8903_MICSHRT_INV 0x8000 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_MASK 0x8000 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_SHIFT 15 /* MICSHRT_INV */ +#define WM8903_MICSHRT_INV_WIDTH 1 /* MICSHRT_INV */ +#define WM8903_MICDET_INV 0x4000 /* MICDET_INV */ +#define WM8903_MICDET_INV_MASK 0x4000 /* MICDET_INV */ +#define WM8903_MICDET_INV_SHIFT 14 /* MICDET_INV */ +#define WM8903_MICDET_INV_WIDTH 1 /* MICDET_INV */ + +/* + * R126 (0x7E) - Interrupt Control + */ +#define WM8903_IRQ_POL 0x0001 /* IRQ_POL */ +#define WM8903_IRQ_POL_MASK 0x0001 /* IRQ_POL */ +#define WM8903_IRQ_POL_SHIFT 0 /* IRQ_POL */ +#define WM8903_IRQ_POL_WIDTH 1 /* IRQ_POL */ + +/* + * R129 (0x81) - Control Interface Test 1 + */ +#define WM8903_USER_KEY 0x0002 /* USER_KEY */ +#define WM8903_USER_KEY_MASK 0x0002 /* USER_KEY */ +#define WM8903_USER_KEY_SHIFT 1 /* USER_KEY */ +#define WM8903_USER_KEY_WIDTH 1 /* USER_KEY */ +#define WM8903_TEST_KEY 0x0001 /* TEST_KEY */ +#define WM8903_TEST_KEY_MASK 0x0001 /* TEST_KEY */ +#define WM8903_TEST_KEY_SHIFT 0 /* TEST_KEY */ +#define WM8903_TEST_KEY_WIDTH 1 /* TEST_KEY */ + +/* + * R149 (0x95) - Charge Pump Test 1 + */ +#define WM8903_CP_SW_KELVIN_MODE_MASK 0x0006 /* CP_SW_KELVIN_MODE - [2:1] */ +#define WM8903_CP_SW_KELVIN_MODE_SHIFT 1 /* CP_SW_KELVIN_MODE - [2:1] */ +#define WM8903_CP_SW_KELVIN_MODE_WIDTH 2 /* CP_SW_KELVIN_MODE - [2:1] */ + +/* + * R164 (0xA4) - Clock Rate Test 4 + */ +#define WM8903_ADC_DIG_MIC 0x0200 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_MASK 0x0200 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_SHIFT 9 /* ADC_DIG_MIC */ +#define WM8903_ADC_DIG_MIC_WIDTH 1 /* ADC_DIG_MIC */ + +/* + * R172 (0xAC) - Analogue Output Bias 0 + */ +#define WM8903_PGA_BIAS_MASK 0x0070 /* PGA_BIAS - [6:4] */ +#define WM8903_PGA_BIAS_SHIFT 4 /* PGA_BIAS - [6:4] */ +#define WM8903_PGA_BIAS_WIDTH 3 /* PGA_BIAS - [6:4] */ + +#endif diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c new file mode 100644 index 0000000..f41a578 --- /dev/null +++ b/sound/soc/codecs/wm8971.c @@ -0,0 +1,941 @@ +/* + * wm8971.c -- WM8971 ALSA SoC Audio driver + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly <kiraly@lab126.com> + * + * Based on wm8753.c by Liam Girdwood + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> + +#include "wm8971.h" + +#define WM8971_VERSION "0.9" + +#define WM8971_REG_COUNT 43 + +static struct workqueue_struct *wm8971_workq = NULL; + +/* codec private data */ +struct wm8971_priv { + unsigned int sysclk; +}; + +/* + * wm8971 register cache + * We can't read the WM8971 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8971_reg[] = { + 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */ + 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */ + 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */ + 0x0079, 0x0079, 0x0079, /* 40 */ +}; + +static inline unsigned int wm8971_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg < WM8971_REG_COUNT) + return cache[reg]; + + return -1; +} + +static inline void wm8971_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg < WM8971_REG_COUNT) + cache[reg] = value; +} + +static int wm8971_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8971_write_reg_cache (codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8971_reset(c) wm8971_write(c, WM8971_RESET, 0) + +/* WM8971 Controls */ +static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" }; +static const char *wm8971_bass_filter[] = { "130Hz @ 48kHz", + "200Hz @ 48kHz" }; +static const char *wm8971_treble[] = { "8kHz", "4kHz" }; +static const char *wm8971_alc_func[] = { "Off", "Right", "Left", "Stereo" }; +static const char *wm8971_ng_type[] = { "Constant PGA Gain", + "Mute ADC Output" }; +static const char *wm8971_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; +static const char *wm8971_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static const char *wm8971_dac_phase[] = { "Non Inverted", "Inverted" }; +static const char *wm8971_lline_mux[] = {"Line", "NC", "NC", "PGA", + "Differential"}; +static const char *wm8971_rline_mux[] = {"Line", "Mic", "NC", "PGA", + "Differential"}; +static const char *wm8971_lpga_sel[] = {"Line", "NC", "NC", "Differential"}; +static const char *wm8971_rpga_sel[] = {"Line", "Mic", "NC", "Differential"}; +static const char *wm8971_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; + +static const struct soc_enum wm8971_enum[] = { + SOC_ENUM_SINGLE(WM8971_BASS, 7, 2, wm8971_bass), /* 0 */ + SOC_ENUM_SINGLE(WM8971_BASS, 6, 2, wm8971_bass_filter), + SOC_ENUM_SINGLE(WM8971_TREBLE, 6, 2, wm8971_treble), + SOC_ENUM_SINGLE(WM8971_ALC1, 7, 4, wm8971_alc_func), + SOC_ENUM_SINGLE(WM8971_NGATE, 1, 2, wm8971_ng_type), /* 4 */ + SOC_ENUM_SINGLE(WM8971_ADCDAC, 1, 4, wm8971_deemp), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 4, 4, wm8971_mono_mux), + SOC_ENUM_SINGLE(WM8971_ADCTL1, 1, 2, wm8971_dac_phase), + SOC_ENUM_SINGLE(WM8971_LOUTM1, 0, 5, wm8971_lline_mux), /* 8 */ + SOC_ENUM_SINGLE(WM8971_ROUTM1, 0, 5, wm8971_rline_mux), + SOC_ENUM_SINGLE(WM8971_LADCIN, 6, 4, wm8971_lpga_sel), + SOC_ENUM_SINGLE(WM8971_RADCIN, 6, 4, wm8971_rpga_sel), + SOC_ENUM_SINGLE(WM8971_ADCDAC, 5, 4, wm8971_adcpol), /* 12 */ + SOC_ENUM_SINGLE(WM8971_ADCIN, 6, 4, wm8971_mono_mux), +}; + +static const struct snd_kcontrol_new wm8971_snd_controls[] = { + SOC_DOUBLE_R("Capture Volume", WM8971_LINVOL, WM8971_RINVOL, 0, 63, 0), + SOC_DOUBLE_R("Capture ZC Switch", WM8971_LINVOL, WM8971_RINVOL, + 6, 1, 0), + SOC_DOUBLE_R("Capture Switch", WM8971_LINVOL, WM8971_RINVOL, 7, 1, 1), + + SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8971_LOUT1V, + WM8971_ROUT1V, 7, 1, 0), + SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8971_LOUT2V, + WM8971_ROUT2V, 7, 1, 0), + SOC_SINGLE("Mono Playback ZC Switch", WM8971_MOUTV, 7, 1, 0), + + SOC_DOUBLE_R("PCM Volume", WM8971_LDAC, WM8971_RDAC, 0, 255, 0), + + SOC_DOUBLE_R("Bypass Left Playback Volume", WM8971_LOUTM1, + WM8971_LOUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Right Playback Volume", WM8971_ROUTM1, + WM8971_ROUTM2, 4, 7, 1), + SOC_DOUBLE_R("Bypass Mono Playback Volume", WM8971_MOUTM1, + WM8971_MOUTM2, 4, 7, 1), + + SOC_DOUBLE_R("Headphone Playback Volume", WM8971_LOUT1V, + WM8971_ROUT1V, 0, 127, 0), + SOC_DOUBLE_R("Speaker Playback Volume", WM8971_LOUT2V, + WM8971_ROUT2V, 0, 127, 0), + + SOC_ENUM("Bass Boost", wm8971_enum[0]), + SOC_ENUM("Bass Filter", wm8971_enum[1]), + SOC_SINGLE("Bass Volume", WM8971_BASS, 0, 7, 1), + + SOC_SINGLE("Treble Volume", WM8971_TREBLE, 0, 7, 0), + SOC_ENUM("Treble Cut-off", wm8971_enum[2]), + + SOC_SINGLE("Capture Filter Switch", WM8971_ADCDAC, 0, 1, 1), + + SOC_SINGLE("ALC Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Max Volume", WM8971_ALC1, 4, 7, 0), + + SOC_SINGLE("ALC Capture Target Volume", WM8971_ALC1, 0, 7, 0), + SOC_SINGLE("ALC Capture Max Volume", WM8971_ALC1, 4, 7, 0), + SOC_ENUM("ALC Capture Function", wm8971_enum[3]), + SOC_SINGLE("ALC Capture ZC Switch", WM8971_ALC2, 7, 1, 0), + SOC_SINGLE("ALC Capture Hold Time", WM8971_ALC2, 0, 15, 0), + SOC_SINGLE("ALC Capture Decay Time", WM8971_ALC3, 4, 15, 0), + SOC_SINGLE("ALC Capture Attack Time", WM8971_ALC3, 0, 15, 0), + SOC_SINGLE("ALC Capture NG Threshold", WM8971_NGATE, 3, 31, 0), + SOC_ENUM("ALC Capture NG Type", wm8971_enum[4]), + SOC_SINGLE("ALC Capture NG Switch", WM8971_NGATE, 0, 1, 0), + + SOC_SINGLE("Capture 6dB Attenuate", WM8971_ADCDAC, 8, 1, 0), + SOC_SINGLE("Playback 6dB Attenuate", WM8971_ADCDAC, 7, 1, 0), + + SOC_ENUM("Playback De-emphasis", wm8971_enum[5]), + SOC_ENUM("Playback Function", wm8971_enum[6]), + SOC_ENUM("Playback Phase", wm8971_enum[7]), + + SOC_DOUBLE_R("Mic Boost", WM8971_LADCIN, WM8971_RADCIN, 4, 3, 0), +}; + +/* add non-DAPM controls */ +static int wm8971_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8971_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8971_snd_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8971_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", WM8971_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8971_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM8971_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new wm8971_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", WM8971_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", WM8971_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", WM8971_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", WM8971_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new wm8971_left_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new wm8971_right_line_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new wm8971_left_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new wm8971_right_pga_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[11]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new wm8971_monomux_controls = +SOC_DAPM_ENUM("Route", wm8971_enum[13]); + +static const struct snd_soc_dapm_widget wm8971_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_left_mixer_controls[0], + ARRAY_SIZE(wm8971_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8971_right_mixer_controls[0], + ARRAY_SIZE(wm8971_right_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8971_PWR2, 2, 0, + &wm8971_mono_mixer_controls[0], + ARRAY_SIZE(wm8971_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8971_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8971_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8971_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8971_PWR2, 6, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8971_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8971_PWR2, 8, 0), + SND_SOC_DAPM_PGA("Mono Out 1", WM8971_PWR2, 2, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8971_PWR1, 1, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8971_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8971_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8971_PWR1, 5, 0, + &wm8971_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8971_PWR1, 4, 0, + &wm8971_right_pga_controls), + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8971_right_line_controls), + + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8971_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("MIC"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line", "LINPUT1"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line", "RINPUT1"}, + {"Right Line Mux", "Mic", "MIC"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line", "LINPUT1"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line", "RINPUT1"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line", "LINPUT1"}, + {"Differential Mux", "Line", "RINPUT1"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, +}; + +static int wm8971_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8971_dapm_widgets, + ARRAY_SIZE(wm8971_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int wm8971_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8971_priv *wm8971 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8971->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + wm8971_write(codec, WM8971_IFACE, iface); + return 0; +} + +static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + struct wm8971_priv *wm8971 = codec->private_data; + u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3; + u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0; + int coeff = get_coeff(wm8971->sysclk, params_rate(params)); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + wm8971_write(codec, WM8971_IFACE, iface); + if (coeff >= 0) + wm8971_write(codec, WM8971_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8971_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8971_read_reg_cache(codec, WM8971_ADCDAC) & 0xfff7; + + if (mute) + wm8971_write(codec, WM8971_ADCDAC, mute_reg | 0x8); + else + wm8971_write(codec, WM8971_ADCDAC, mute_reg); + return 0; +} + +static int wm8971_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 pwr_reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x00c1); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* mute dac and set vmid to 500k, enable VREF */ + wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x0140); + break; + case SND_SOC_BIAS_OFF: + wm8971_write(codec, WM8971_PWR1, 0x0001); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8971_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) + +#define WM8971_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +struct snd_soc_dai wm8971_dai = { + .name = "WM8971", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8971_RATES, + .formats = WM8971_FORMATS,}, + .ops = { + .hw_params = wm8971_pcm_hw_params, + }, + .dai_ops = { + .digital_mute = wm8971_mute, + .set_fmt = wm8971_set_dai_fmt, + .set_sysclk = wm8971_set_dai_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(wm8971_dai); + +static void wm8971_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, delayed_work.work); + wm8971_set_bias_level(codec, codec->bias_level); +} + +static int wm8971_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8971_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + u16 reg; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8971_reg); i++) { + if (i + 1 == WM8971_RESET) + continue; + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8971_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* charge wm8971 caps */ + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { + reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + wm8971_write(codec, WM8971_PWR1, reg | 0x01c0); + codec->bias_level = SND_SOC_BIAS_ON; + queue_delayed_work(wm8971_workq, &codec->delayed_work, + msecs_to_jiffies(1000)); + } + + return 0; +} + +static int wm8971_init(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->codec; + int reg, ret = 0; + + codec->name = "WM8971"; + codec->owner = THIS_MODULE; + codec->read = wm8971_read_reg_cache; + codec->write = wm8971_write; + codec->set_bias_level = wm8971_set_bias_level; + codec->dai = &wm8971_dai; + codec->reg_cache_size = ARRAY_SIZE(wm8971_reg); + codec->num_dai = 1; + codec->reg_cache = kmemdup(wm8971_reg, sizeof(wm8971_reg), GFP_KERNEL); + + if (codec->reg_cache == NULL) + return -ENOMEM; + + wm8971_reset(codec); + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to create pcms\n"); + goto pcm_err; + } + + /* charge output caps - set vmid to 5k for quick power up */ + reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + wm8971_write(codec, WM8971_PWR1, reg | 0x01c0); + codec->bias_level = SND_SOC_BIAS_STANDBY; + queue_delayed_work(wm8971_workq, &codec->delayed_work, + msecs_to_jiffies(1000)); + + /* set the update bits */ + reg = wm8971_read_reg_cache(codec, WM8971_LDAC); + wm8971_write(codec, WM8971_LDAC, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_RDAC); + wm8971_write(codec, WM8971_RDAC, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LOUT1V); + wm8971_write(codec, WM8971_LOUT1V, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_ROUT1V); + wm8971_write(codec, WM8971_ROUT1V, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LOUT2V); + wm8971_write(codec, WM8971_LOUT2V, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_ROUT2V); + wm8971_write(codec, WM8971_ROUT2V, reg | 0x0100); + + reg = wm8971_read_reg_cache(codec, WM8971_LINVOL); + wm8971_write(codec, WM8971_LINVOL, reg | 0x0100); + reg = wm8971_read_reg_cache(codec, WM8971_RINVOL); + wm8971_write(codec, WM8971_RINVOL, reg | 0x0100); + + wm8971_add_controls(codec); + wm8971_add_widgets(codec); + ret = snd_soc_register_card(socdev); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to register card\n"); + goto card_err; + } + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + kfree(codec->reg_cache); + return ret; +} + +/* If the i2c layer weren't so broken, we could pass this kind of data + around */ +static struct snd_soc_device *wm8971_socdev; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + +static int wm8971_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_device *socdev = wm8971_socdev; + struct snd_soc_codec *codec = socdev->codec; + int ret; + + i2c_set_clientdata(i2c, codec); + + codec->control_data = i2c; + + ret = wm8971_init(socdev); + if (ret < 0) + pr_err("failed to initialise WM8971\n"); + + return ret; +} + +static int wm8971_i2c_remove(struct i2c_client *client) +{ + struct snd_soc_codec *codec = i2c_get_clientdata(client); + kfree(codec->reg_cache); + return 0; +} + +static const struct i2c_device_id wm8971_i2c_id[] = { + { "wm8971", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8971_i2c_id); + +static struct i2c_driver wm8971_i2c_driver = { + .driver = { + .name = "WM8971 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8971_i2c_probe, + .remove = wm8971_i2c_remove, + .id_table = wm8971_i2c_id, +}; + +static int wm8971_add_i2c_device(struct platform_device *pdev, + const struct wm8971_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8971_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8971", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8971_i2c_driver); + return -ENODEV; +} + +#endif + +static int wm8971_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct wm8971_setup_data *setup; + struct snd_soc_codec *codec; + struct wm8971_priv *wm8971; + int ret = 0; + + pr_info("WM8971 Audio Codec %s", WM8971_VERSION); + + setup = socdev->codec_data; + codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + + wm8971 = kzalloc(sizeof(struct wm8971_priv), GFP_KERNEL); + if (wm8971 == NULL) { + kfree(codec); + return -ENOMEM; + } + + codec->private_data = wm8971; + socdev->codec = codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + wm8971_socdev = socdev; + + INIT_DELAYED_WORK(&codec->delayed_work, wm8971_work); + wm8971_workq = create_workqueue("wm8971"); + if (wm8971_workq == NULL) { + kfree(codec->private_data); + kfree(codec); + return -ENOMEM; + } + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + if (setup->i2c_address) { + codec->hw_write = (hw_write_t)i2c_master_send; + ret = wm8971_add_i2c_device(pdev, setup); + } +#endif + /* Add other interfaces here */ + + if (ret != 0) { + destroy_workqueue(wm8971_workq); + kfree(codec->private_data); + kfree(codec); + } + + return ret; +} + +/* power down chip */ +static int wm8971_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->codec; + + if (codec->control_data) + wm8971_set_bias_level(codec, SND_SOC_BIAS_OFF); + if (wm8971_workq) + destroy_workqueue(wm8971_workq); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) + i2c_unregister_device(codec->control_data); + i2c_del_driver(&wm8971_i2c_driver); +#endif + kfree(codec->private_data); + kfree(codec); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8971 = { + .probe = wm8971_probe, + .remove = wm8971_remove, + .suspend = wm8971_suspend, + .resume = wm8971_resume, +}; + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8971); + +MODULE_DESCRIPTION("ASoC WM8971 driver"); +MODULE_AUTHOR("Lab126"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8971.h b/sound/soc/codecs/wm8971.h new file mode 100644 index 0000000..ef4f08f --- /dev/null +++ b/sound/soc/codecs/wm8971.h @@ -0,0 +1,64 @@ +/* + * wm8971.h -- audio driver for WM8971 + * + * Copyright 2005 Lab126, Inc. + * + * Author: Kenneth Kiraly <kiraly@lab126.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _WM8971_H +#define _WM8971_H + +#define WM8971_LINVOL 0x00 +#define WM8971_RINVOL 0x01 +#define WM8971_LOUT1V 0x02 +#define WM8971_ROUT1V 0x03 +#define WM8971_ADCDAC 0x05 +#define WM8971_IFACE 0x07 +#define WM8971_SRATE 0x08 +#define WM8971_LDAC 0x0a +#define WM8971_RDAC 0x0b +#define WM8971_BASS 0x0c +#define WM8971_TREBLE 0x0d +#define WM8971_RESET 0x0f +#define WM8971_ALC1 0x11 +#define WM8971_ALC2 0x12 +#define WM8971_ALC3 0x13 +#define WM8971_NGATE 0x14 +#define WM8971_LADC 0x15 +#define WM8971_RADC 0x16 +#define WM8971_ADCTL1 0x17 +#define WM8971_ADCTL2 0x18 +#define WM8971_PWR1 0x19 +#define WM8971_PWR2 0x1a +#define WM8971_ADCTL3 0x1b +#define WM8971_ADCIN 0x1f +#define WM8971_LADCIN 0x20 +#define WM8971_RADCIN 0x21 +#define WM8971_LOUTM1 0x22 +#define WM8971_LOUTM2 0x23 +#define WM8971_ROUTM1 0x24 +#define WM8971_ROUTM2 0x25 +#define WM8971_MOUTM1 0x26 +#define WM8971_MOUTM2 0x27 +#define WM8971_LOUT2V 0x28 +#define WM8971_ROUT2V 0x29 +#define WM8971_MOUTV 0x2A + +#define WM8971_SYSCLK 0 + +struct wm8971_setup_data { + int i2c_bus; + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8971_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8971; + +#endif diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index dd995ef..572d22b 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -30,7 +30,6 @@ #include "wm8990.h" -#define AUDIO_NAME "wm8990" #define WM8990_VERSION "0.2" /* codec private data */ @@ -1477,81 +1476,86 @@ static struct snd_soc_device *wm8990_socdev; * low = 0x34 * high = 0x36 */ -static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END }; -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static struct i2c_driver wm8990_i2c_driver; -static struct i2c_client client_template; - -static int wm8990_codec_probe(struct i2c_adapter *adap, int addr, int kind) +static int wm8990_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) { struct snd_soc_device *socdev = wm8990_socdev; - struct wm8990_setup_data *setup = socdev->codec_data; struct snd_soc_codec *codec = socdev->codec; - struct i2c_client *i2c; int ret; - if (addr != setup->i2c_address) - return -ENODEV; - - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (i2c == NULL) - return -ENOMEM; - i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = i2c_attach_client(i2c); - if (ret < 0) { - pr_err("failed to attach codec at addr %x\n", addr); - goto err; - } - ret = wm8990_init(socdev); - if (ret < 0) { + if (ret < 0) pr_err("failed to initialise WM8990\n"); - goto err; - } - return ret; -err: - kfree(i2c); return ret; } -static int wm8990_i2c_detach(struct i2c_client *client) +static int wm8990_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 wm8990_i2c_attach(struct i2c_adapter *adap) -{ - return i2c_probe(adap, &addr_data, wm8990_codec_probe); -} +static const struct i2c_device_id wm8990_i2c_id[] = { + { "wm8990", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8990_i2c_id); static struct i2c_driver wm8990_i2c_driver = { .driver = { .name = "WM8990 I2C Codec", .owner = THIS_MODULE, }, - .attach_adapter = wm8990_i2c_attach, - .detach_client = wm8990_i2c_detach, - .command = NULL, + .probe = wm8990_i2c_probe, + .remove = wm8990_i2c_remove, + .id_table = wm8990_i2c_id, }; -static struct i2c_client client_template = { - .name = "WM8990", - .driver = &wm8990_i2c_driver, -}; +static int wm8990_add_i2c_device(struct platform_device *pdev, + const struct wm8990_setup_data *setup) +{ + struct i2c_board_info info; + struct i2c_adapter *adapter; + struct i2c_client *client; + int ret; + + ret = i2c_add_driver(&wm8990_i2c_driver); + if (ret != 0) { + dev_err(&pdev->dev, "can't add i2c driver\n"); + return ret; + } + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = setup->i2c_address; + strlcpy(info.type, "wm8990", I2C_NAME_SIZE); + + adapter = i2c_get_adapter(setup->i2c_bus); + if (!adapter) { + dev_err(&pdev->dev, "can't get i2c adapter %d\n", + setup->i2c_bus); + goto err_driver; + } + + client = i2c_new_device(adapter, &info); + i2c_put_adapter(adapter); + if (!client) { + dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", + (unsigned int)info.addr); + goto err_driver; + } + + return 0; + +err_driver: + i2c_del_driver(&wm8990_i2c_driver); + return -ENODEV; +} #endif static int wm8990_probe(struct platform_device *pdev) @@ -1560,7 +1564,7 @@ static int wm8990_probe(struct platform_device *pdev) struct wm8990_setup_data *setup; struct snd_soc_codec *codec; struct wm8990_priv *wm8990; - int ret = 0; + int ret; pr_info("WM8990 Audio Codec %s\n", WM8990_VERSION); @@ -1582,16 +1586,13 @@ static int wm8990_probe(struct platform_device *pdev) INIT_LIST_HEAD(&codec->dapm_paths); wm8990_socdev = socdev; + ret = -ENODEV; + #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - normal_i2c[0] = setup->i2c_address; codec->hw_write = (hw_write_t)i2c_master_send; - ret = i2c_add_driver(&wm8990_i2c_driver); - if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + ret = wm8990_add_i2c_device(pdev, setup); } -#else - /* Add other interfaces here */ #endif if (ret != 0) { @@ -1612,6 +1613,7 @@ static int wm8990_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(&wm8990_i2c_driver); #endif kfree(codec->private_data); diff --git a/sound/soc/codecs/wm8990.h b/sound/soc/codecs/wm8990.h index 0a08325..0e192f3 100644 --- a/sound/soc/codecs/wm8990.h +++ b/sound/soc/codecs/wm8990.h @@ -827,6 +827,7 @@ #define WM8990_AINRMUX_PWR_BIT 3 struct wm8990_setup_data { + unsigned i2c_bus; unsigned short i2c_address; }; diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 2f1c91b..ffb471e 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -2,8 +2,7 @@ * wm9712.c -- ALSA Soc WM9712 codec support * * Copyright 2006 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 38d1fe0..aba402b 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -2,8 +2,7 @@ * wm9713.c -- ALSA Soc WM9713 codec support * * Copyright 2006 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -419,8 +418,12 @@ SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1), SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), -SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture", AC97_EXTENDED_MID, 5, 1), -SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture", AC97_EXTENDED_MID, 4, 1), +SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0), +SND_SOC_DAPM_ADC("Left HiFi ADC", "Left HiFi Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Right HiFi ADC", "Right HiFi Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Left Voice ADC", "Left Voice Capture", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_ADC("Right Voice ADC", "Right Voice Capture", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_PGA("Left Headphone", AC97_EXTENDED_MSTATUS, 10, 1, NULL, 0), SND_SOC_DAPM_PGA("Right Headphone", AC97_EXTENDED_MSTATUS, 9, 1, NULL, 0), SND_SOC_DAPM_PGA("Left Speaker", AC97_EXTENDED_MSTATUS, 8, 1, NULL, 0), @@ -583,9 +586,13 @@ static const struct snd_soc_dapm_route audio_map[] = { /* left ADC */ {"Left ADC", NULL, "Left Capture Source"}, + {"Left Voice ADC", NULL, "Left ADC"}, + {"Left HiFi ADC", NULL, "Left ADC"}, /* right ADC */ {"Right ADC", NULL, "Right Capture Source"}, + {"Right Voice ADC", NULL, "Right ADC"}, + {"Right HiFi ADC", NULL, "Right ADC"}, /* mic */ {"Mic A Pre Amp", NULL, "Mic A Source"}, @@ -949,17 +956,17 @@ static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, static void wm9713_voiceshutdown(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_device *socdev = rtd->socdev; - struct snd_soc_codec *codec = socdev->codec; - u16 status; - - /* Gracefully shut down the voice interface. */ - status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; - ac97_write(codec, AC97_HANDSET_RATE, 0x0280); - schedule_timeout_interruptible(msecs_to_jiffies(1)); - ac97_write(codec, AC97_HANDSET_RATE, 0x0F80); - ac97_write(codec, AC97_EXTENDED_MID, status); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->codec; + u16 status; + + /* Gracefully shut down the voice interface. */ + status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; + ac97_write(codec, AC97_HANDSET_RATE, 0x0280); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + ac97_write(codec, AC97_HANDSET_RATE, 0x0F80); + ac97_write(codec, AC97_EXTENDED_MID, status); } static int ac97_hifi_prepare(struct snd_pcm_substream *substream) diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index 65fdbd8..9e6062c 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -1,7 +1,7 @@ /* * ASoC driver for TI DAVINCI EVM platform * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify @@ -136,6 +136,7 @@ static struct snd_soc_machine snd_soc_machine_evm = { /* evm audio private data */ static struct aic3x_setup_data evm_aic3x_setup = { + .i2c_bus = 0, .i2c_address = 0x1b, }; diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 5ebf1ff..abb5fed 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -1,7 +1,7 @@ /* * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify @@ -256,7 +256,7 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, mcbsp_word_length = DAVINCI_MCBSP_WORD_32; break; default: - printk(KERN_WARNING "davinci-i2s: unsupported PCM format"); + printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n"); return -EINVAL; } diff --git a/sound/soc/davinci/davinci-i2s.h b/sound/soc/davinci/davinci-i2s.h index c5b0918..241648c 100644 --- a/sound/soc/davinci/davinci-i2s.h +++ b/sound/soc/davinci/davinci-i2s.h @@ -1,7 +1,7 @@ /* * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index 6a5e56a..76feaa6 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -1,7 +1,7 @@ /* * ALSA PCM interface for the TI DAVINCI processor * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h index 8d6a45e..62cb4eb 100644 --- a/sound/soc/davinci/davinci-pcm.h +++ b/sound/soc/davinci/davinci-pcm.h @@ -1,7 +1,7 @@ /* * ALSA PCM interface for the TI DAVINCI processor * - * Author: Vladimir Barinov, <vbarinov@ru.mvista.com> + * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> * * This program is free software; you can redistribute it and/or modify diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 3368ace..bba9546 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,3 +1,6 @@ +config SND_SOC_OF_SIMPLE + tristate + config SND_SOC_MPC8610 bool "ALSA SoC support for the MPC8610 SOC" depends on MPC8610_HPCD @@ -14,3 +17,10 @@ config SND_SOC_MPC8610_HPCD default y if MPC8610_HPCD help Say Y if you want to enable audio on the Freescale MPC8610 HPCD. + +config SND_SOC_MPC5200_I2S + tristate "Freescale MPC5200 PSC in I2S mode driver" + select SND_SOC_OF_SIMPLE + depends on SND_SOC && PPC_MPC52xx + help + Say Y here to support the MPC5200 PSCs in I2S mode. diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 62f680a..035da4a 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -1,6 +1,11 @@ +# Simple machine driver that extracts configuration from the OF device tree +obj-$(CONFIG_SND_SOC_OF_SIMPLE) += soc-of-simple.o + # MPC8610 HPCD Machine Support obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o # MPC8610 Platform Support obj-$(CONFIG_SND_SOC_MPC8610) += fsl_ssi.o fsl_dma.o +obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o + diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c new file mode 100644 index 0000000..8692329 --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -0,0 +1,884 @@ +/* + * Freescale MPC5200 PSC in I2S mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <sound/soc-of-simple.h> + +#include <sysdev/bestcomm/bestcomm.h> +#include <sysdev/bestcomm/gen_bd.h> +#include <asm/mpc52xx_psc.h> + +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); +MODULE_LICENSE("GPL"); + +/** + * PSC_I2S_RATES: sample rates supported by the I2S + * + * This driver currently only supports the PSC running in I2S slave mode, + * which means the codec determines the sample rate. Therefore, we tell + * ALSA that we support all rates and let the codec driver decide what rates + * are really supported. + */ +#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \ + SNDRV_PCM_RATE_CONTINUOUS) + +/** + * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode + */ +#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_S32_BE) + +/** + * psc_i2s_stream - Data specific to a single stream (playback or capture) + * @active: flag indicating if the stream is active + * @psc_i2s: pointer back to parent psc_i2s data structure + * @bcom_task: bestcomm task structure + * @irq: irq number for bestcomm task + * @period_start: physical address of start of DMA region + * @period_end: physical address of end of DMA region + * @period_next_pt: physical address of next DMA buffer to enqueue + * @period_bytes: size of DMA period in bytes + */ +struct psc_i2s_stream { + int active; + struct psc_i2s *psc_i2s; + struct bcom_task *bcom_task; + int irq; + struct snd_pcm_substream *stream; + dma_addr_t period_start; + dma_addr_t period_end; + dma_addr_t period_next_pt; + dma_addr_t period_current_pt; + int period_bytes; +}; + +/** + * psc_i2s - Private driver data + * @name: short name for this device ("PSC0", "PSC1", etc) + * @psc_regs: pointer to the PSC's registers + * @fifo_regs: pointer to the PSC's FIFO registers + * @irq: IRQ of this PSC + * @dev: struct device pointer + * @dai: the CPU DAI for this device + * @sicr: Base value used in serial interface control register; mode is ORed + * with this value. + * @playback: Playback stream context data + * @capture: Capture stream context data + */ +struct psc_i2s { + char name[32]; + struct mpc52xx_psc __iomem *psc_regs; + struct mpc52xx_psc_fifo __iomem *fifo_regs; + unsigned int irq; + struct device *dev; + struct snd_soc_dai dai; + spinlock_t lock; + u32 sicr; + + /* per-stream data */ + struct psc_i2s_stream playback; + struct psc_i2s_stream capture; + + /* Statistics */ + struct { + int overrun_count; + int underrun_count; + } stats; +}; + +/* + * Interrupt handlers + */ +static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) +{ + struct psc_i2s *psc_i2s = _psc_i2s; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + u16 isr; + + isr = in_be16(®s->mpc52xx_psc_isr); + + /* Playback underrun error */ + if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) + psc_i2s->stats.underrun_count++; + + /* Capture overrun error */ + if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) + psc_i2s->stats.overrun_count++; + + out_8(®s->command, 4 << 4); /* reset the error status */ + + return IRQ_HANDLED; +} + +/** + * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer + * @s: pointer to stream private data structure + * + * Enqueues another audio period buffer into the bestcomm queue. + * + * Note: The routine must only be called when there is space available in + * the queue. Otherwise the enqueue will fail and the audio ring buffer + * will get out of sync + */ +static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) +{ + struct bcom_bd *bd; + + /* Prepare and enqueue the next buffer descriptor */ + bd = bcom_prepare_next_buffer(s->bcom_task); + bd->status = s->period_bytes; + bd->data[0] = s->period_next_pt; + bcom_submit_next_buffer(s->bcom_task, NULL); + + /* Update for next period */ + s->period_next_pt += s->period_bytes; + if (s->period_next_pt >= s->period_end) + s->period_next_pt = s->period_start; +} + +/* Bestcomm DMA irq handler */ +static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) +{ + struct psc_i2s_stream *s = _psc_i2s_stream; + + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + s->period_current_pt += s->period_bytes; + if (s->period_current_pt >= s->period_end) + s->period_current_pt = s->period_start; + psc_i2s_bcom_enqueue_next_buffer(s); + bcom_enable(s->bcom_task); + } + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; +} + +/** + * psc_i2s_startup: create a new substream + * + * This is the first function called when a stream is opened. + * + * If this is the first stream open, then grab the IRQ and program most of + * the PSC registers. + */ +static int psc_i2s_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + int rc; + + dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); + + if (!psc_i2s->playback.active && + !psc_i2s->capture.active) { + /* Setup the IRQs */ + rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED, + "psc-i2s-status", psc_i2s); + rc |= request_irq(psc_i2s->capture.irq, + &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-capture", &psc_i2s->capture); + rc |= request_irq(psc_i2s->playback.irq, + &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-playback", &psc_i2s->playback); + if (rc) { + free_irq(psc_i2s->irq, psc_i2s); + free_irq(psc_i2s->capture.irq, + &psc_i2s->capture); + free_irq(psc_i2s->playback.irq, + &psc_i2s->playback); + return -ENODEV; + } + } + + return 0; +} + +static int psc_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + u32 mode; + + dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params)); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + mode = MPC52xx_PSC_SICR_SIM_CODEC_8; + break; + case SNDRV_PCM_FORMAT_S16_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_16; + break; + case SNDRV_PCM_FORMAT_S24_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_24; + break; + case SNDRV_PCM_FORMAT_S32_BE: + mode = MPC52xx_PSC_SICR_SIM_CODEC_32; + break; + default: + dev_dbg(psc_i2s->dev, "invalid format\n"); + return -EINVAL; + } + out_be32(&psc_i2s->psc_regs->sicr, psc_i2s->sicr | mode); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int psc_i2s_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +/** + * psc_i2s_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + */ +static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct psc_i2s_stream *s; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + u16 imr; + u8 psc_cmd; + long flags; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" + " stream_id=%i\n", + substream, cmd, substream->pstr->stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + s->period_bytes = frames_to_bytes(runtime, + runtime->period_size); + s->period_start = virt_to_phys(runtime->dma_area); + s->period_end = s->period_start + + (s->period_bytes * runtime->periods); + s->period_next_pt = s->period_start; + s->period_current_pt = s->period_start; + s->active = 1; + + /* First; reset everything */ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + out_8(®s->command, MPC52xx_PSC_RST_RX); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + } else { + out_8(®s->command, MPC52xx_PSC_RST_TX); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + } + + /* Next, fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. */ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + while (!bcom_queue_full(s->bcom_task)) + psc_i2s_bcom_enqueue_next_buffer(s); + bcom_enable(s->bcom_task); + + /* Due to errata in the i2s mode; need to line up enabling + * the transmitter with a transition on the frame sync + * line */ + + spin_lock_irqsave(&psc_i2s->lock, flags); + /* first make sure it is low */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) + ; + /* then wait for the transition to high */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) + ; + /* Finally, enable the PSC. + * Receiver must always be enabled; even when we only want + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ + psc_cmd = MPC52xx_PSC_RX_ENABLE; + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) + psc_cmd |= MPC52xx_PSC_TX_ENABLE; + out_8(®s->command, psc_cmd); + spin_unlock_irqrestore(&psc_i2s->lock, flags); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* Turn off the PSC */ + s->active = 0; + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (!psc_i2s->playback.active) { + out_8(®s->command, 2 << 4); /* reset rx */ + out_8(®s->command, 3 << 4); /* reset tx */ + out_8(®s->command, 4 << 4); /* reset err */ + } + } else { + out_8(®s->command, 3 << 4); /* reset tx */ + out_8(®s->command, 4 << 4); /* reset err */ + if (!psc_i2s->capture.active) + out_8(®s->command, 2 << 4); /* reset rx */ + } + + bcom_disable(s->bcom_task); + while (!bcom_queue_empty(s->bcom_task)) + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + + break; + + default: + dev_dbg(psc_i2s->dev, "invalid command\n"); + return -EINVAL; + } + + /* Update interrupt enable settings */ + imr = 0; + if (psc_i2s->playback.active) + imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_i2s->capture.active) + imr |= MPC52xx_PSC_IMR_ORERR; + out_be16(®s->isr_imr.imr, imr); + + return 0; +} + +/** + * psc_i2s_shutdown: shutdown the data transfer on a stream + * + * Shutdown the PSC if there are no other substreams open. + */ +static void psc_i2s_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + + dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); + + /* + * If this is the last active substream, disable the PSC and release + * the IRQ. + */ + if (!psc_i2s->playback.active && + !psc_i2s->capture.active) { + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); + out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ + out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ + out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + + /* Release irqs */ + free_irq(psc_i2s->irq, psc_i2s); + free_irq(psc_i2s->capture.irq, &psc_i2s->capture); + free_irq(psc_i2s->playback.irq, &psc_i2s->playback); + } +} + +/** + * psc_i2s_set_sysclk: set the clock frequency and direction + * + * This function is called by the machine driver to tell us what the clock + * frequency and direction are. + * + * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN), + * and we don't care about the frequency. Return an error if the direction + * is not SND_SOC_CLOCK_IN. + * + * @clk_id: reserved, should be zero + * @freq: the frequency of the given clock ID, currently ignored + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) + */ +static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct psc_i2s *psc_i2s = cpu_dai->private_data; + dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", + cpu_dai, dir); + return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; +} + +/** + * psc_i2s_set_fmt: set the serial format. + * + * This function is called by the machine driver to tell us what serial + * format to use. + * + * This driver only supports I2S mode. Return an error if the format is + * not SND_SOC_DAIFMT_I2S. + * + * @format: one of SND_SOC_DAIFMT_xxx + */ +static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) +{ + struct psc_i2s *psc_i2s = cpu_dai->private_data; + dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", + cpu_dai, format); + return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_i2s_dai_template: template CPU Digital Audio Interface + */ +static struct snd_soc_dai psc_i2s_dai_template = { + .type = SND_SOC_DAI_I2S, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = PSC_I2S_RATES, + .formats = PSC_I2S_FORMATS, + }, + .ops = { + .startup = psc_i2s_startup, + .hw_params = psc_i2s_hw_params, + .hw_free = psc_i2s_hw_free, + .shutdown = psc_i2s_shutdown, + .trigger = psc_i2s_trigger, + }, + .dai_ops = { + .set_sysclk = psc_i2s_set_sysclk, + .set_fmt = psc_i2s_set_fmt, + }, +}; + +/* --------------------------------------------------------------------- + * The PSC I2S 'ASoC platform' driver + * + * Can be referenced by an 'ASoC machine' driver + * This driver only deals with the audio bus; it doesn't have any + * interaction with the attached codec + */ + +static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .period_bytes_max = 1024 * 1024, + .period_bytes_min = 32, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 2 * 1024 * 1024, + .fifo_size = 0, +}; + +static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); + + s->stream = substream; + return 0; +} + +static int psc_i2s_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + s->stream = NULL; + return 0; +} + +static snd_pcm_uframes_t +psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + dma_addr_t count; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + count = s->period_current_pt - s->period_start; + + return bytes_to_frames(substream->runtime, count); +} + +static struct snd_pcm_ops psc_i2s_pcm_ops = { + .open = psc_i2s_pcm_open, + .close = psc_i2s_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .pointer = psc_i2s_pcm_pointer, +}; + +static u64 psc_i2s_pcm_dmamask = 0xffffffff; +static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; + int rc = 0; + + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n", + card, dai, pcm); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &psc_i2s_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (pcm->streams[0].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, + &pcm->streams[0].substream->dma_buffer); + if (rc) + goto playback_alloc_err; + } + + if (pcm->streams[1].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, + &pcm->streams[1].substream->dma_buffer); + if (rc) + goto capture_alloc_err; + } + + return 0; + + capture_alloc_err: + if (pcm->streams[0].substream) + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + playback_alloc_err: + dev_err(card->dev, "Cannot allocate buffer(s)\n"); + return -ENOMEM; +} + +static void psc_i2s_pcm_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_pcm_substream *substream; + int stream; + + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +struct snd_soc_platform psc_i2s_pcm_soc_platform = { + .name = "mpc5200-psc-audio", + .pcm_ops = &psc_i2s_pcm_ops, + .pcm_new = &psc_i2s_pcm_new, + .pcm_free = &psc_i2s_pcm_free, +}; + +/* --------------------------------------------------------------------- + * Sysfs attributes for debugging + */ + +static ssize_t psc_i2s_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + + return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x " + "tfnum=%i tfstat=0x%.4x\n", + in_be16(&psc_i2s->psc_regs->sr_csr.status), + in_be32(&psc_i2s->psc_regs->sicr), + in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff, + in_be16(&psc_i2s->fifo_regs->rfstat), + in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff, + in_be16(&psc_i2s->fifo_regs->tfstat)); +} + +static int *psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, const char *name) +{ + if (strcmp(name, "playback_underrun") == 0) + return &psc_i2s->stats.underrun_count; + if (strcmp(name, "capture_overrun") == 0) + return &psc_i2s->stats.overrun_count; + + return NULL; +} + +static ssize_t psc_i2s_stat_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + int *attrib; + + attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); + if (!attrib) + return 0; + + return sprintf(buf, "%i\n", *attrib); +} + +static ssize_t psc_i2s_stat_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + int *attrib; + + attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); + if (!attrib) + return 0; + + *attrib = simple_strtoul(buf, NULL, 0); + return count; +} + +DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL); +DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store); +DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store); + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int __devinit psc_i2s_of_probe(struct of_device *op, + const struct of_device_id *match) +{ + phys_addr_t fifo; + struct psc_i2s *psc_i2s; + struct resource res; + int size, psc_id, irq, rc; + const __be32 *prop; + void __iomem *regs; + + dev_dbg(&op->dev, "probing psc i2s device\n"); + + /* Get the PSC ID */ + prop = of_get_property(op->node, "cell-index", &size); + if (!prop || size < sizeof *prop) + return -ENODEV; + psc_id = be32_to_cpu(*prop); + + /* Fetch the registers and IRQ of the PSC */ + irq = irq_of_parse_and_map(op->node, 0); + if (of_address_to_resource(op->node, 0, &res)) { + dev_err(&op->dev, "Missing reg property\n"); + return -ENODEV; + } + regs = ioremap(res.start, 1 + res.end - res.start); + if (!regs) { + dev_err(&op->dev, "Could not map registers\n"); + return -ENODEV; + } + + /* Allocate and initialize the driver private data */ + psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL); + if (!psc_i2s) { + iounmap(regs); + return -ENOMEM; + } + spin_lock_init(&psc_i2s->lock); + psc_i2s->irq = irq; + psc_i2s->psc_regs = regs; + psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs; + psc_i2s->dev = &op->dev; + psc_i2s->playback.psc_i2s = psc_i2s; + psc_i2s->capture.psc_i2s = psc_i2s; + snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1); + + /* Fill out the CPU DAI structure */ + memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai); + psc_i2s->dai.private_data = psc_i2s; + psc_i2s->dai.name = psc_i2s->name; + psc_i2s->dai.id = psc_id; + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ + fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_i2s->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512); + psc_i2s->playback.bcom_task = + bcom_psc_gen_bd_tx_init(psc_id, 10, fifo); + if (!psc_i2s->capture.bcom_task || + !psc_i2s->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); + iounmap(regs); + kfree(psc_i2s); + return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); + out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset transmitter */ + out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset receiver */ + out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + + /* Configure the serial interface mode; defaulting to CODEC8 mode */ + psc_i2s->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | + MPC52xx_PSC_SICR_CLKPOL; + if (of_get_property(op->node, "fsl,cellslave", NULL)) + psc_i2s->sicr |= MPC52xx_PSC_SICR_CELLSLAVE | + MPC52xx_PSC_SICR_GENCLK; + out_be32(&psc_i2s->psc_regs->sicr, + psc_i2s->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); + + /* Check for the codec handle. If it is not present then we + * are done */ + if (!of_get_property(op->node, "codec-handle", NULL)) + return 0; + + /* Set up mode register; + * First write: RxRdy (FIFO Alarm) generates rx FIFO irq + * Second write: register Normal mode for non loopback + */ + out_8(&psc_i2s->psc_regs->mode, 0); + out_8(&psc_i2s->psc_regs->mode, 0); + + /* Set the TX and RX fifo alarm thresholds */ + out_be16(&psc_i2s->fifo_regs->rfalarm, 0x100); + out_8(&psc_i2s->fifo_regs->rfcntl, 0x4); + out_be16(&psc_i2s->fifo_regs->tfalarm, 0x100); + out_8(&psc_i2s->fifo_regs->tfcntl, 0x7); + + /* Lookup the IRQ numbers */ + psc_i2s->playback.irq = + bcom_get_task_irq(psc_i2s->playback.bcom_task); + psc_i2s->capture.irq = + bcom_get_task_irq(psc_i2s->capture.bcom_task); + + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_i2s); + + /* Register the SYSFS files */ + rc = device_create_file(psc_i2s->dev, &dev_attr_status); + rc = device_create_file(psc_i2s->dev, &dev_attr_capture_overrun); + rc = device_create_file(psc_i2s->dev, &dev_attr_playback_underrun); + if (rc) + dev_info(psc_i2s->dev, "error creating sysfs files\n"); + + /* Tell the ASoC OF helpers about it */ + of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node, + &psc_i2s->dai); + + return 0; +} + +static int __devexit psc_i2s_of_remove(struct of_device *op) +{ + struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev); + + dev_dbg(&op->dev, "psc_i2s_remove()\n"); + + bcom_gen_bd_rx_release(psc_i2s->capture.bcom_task); + bcom_gen_bd_tx_release(psc_i2s->playback.bcom_task); + + iounmap(psc_i2s->psc_regs); + iounmap(psc_i2s->fifo_regs); + kfree(psc_i2s); + dev_set_drvdata(&op->dev, NULL); + + return 0; +} + +/* Match table for of_platform binding */ +static struct of_device_id psc_i2s_match[] __devinitdata = { + { .compatible = "fsl,mpc5200-psc-i2s", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_i2s_match); + +static struct of_platform_driver psc_i2s_driver = { + .match_table = psc_i2s_match, + .probe = psc_i2s_of_probe, + .remove = __devexit_p(psc_i2s_of_remove), + .driver = { + .name = "mpc5200-psc-i2s", + .owner = THIS_MODULE, + }, +}; + +/* --------------------------------------------------------------------- + * Module setup and teardown; simply register the of_platform driver + * for the PSC in I2S mode. + */ +static int __init psc_i2s_init(void) +{ + return of_register_platform_driver(&psc_i2s_driver); +} +module_init(psc_i2s_init); + +static void __exit psc_i2s_exit(void) +{ + of_unregister_platform_driver(&psc_i2s_driver); +} +module_exit(psc_i2s_exit); + + diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index 4bdc9d8..94f89de 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -68,10 +68,6 @@ static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device) guts_set_pmuxcr_dma(machine_data->guts, machine_data->dma_id, machine_data->dma_channel_id[1], 0); - guts_set_pmuxcr_dma(machine_data->guts, 1, 0, 0); - guts_set_pmuxcr_dma(machine_data->guts, 1, 3, 0); - guts_set_pmuxcr_dma(machine_data->guts, 0, 3, 0); - switch (machine_data->ssi_id) { case 0: clrsetbits_be32(&machine_data->guts->pmuxcr, @@ -230,6 +226,8 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, struct fsl_ssi_info ssi_info; struct fsl_dma_info dma_info; int ret = -ENODEV; + unsigned int playback_dma_channel; + unsigned int capture_dma_channel; machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL); if (!machine_data) @@ -381,8 +379,9 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, goto error; } - /* Find the DMA channels to use. For now, we always use the first DMA - controller. */ + /* Find the DMA channels to use. Both SSIs need to use the same DMA + * controller, so let's use DMA#1. + */ for_each_compatible_node(dma_np, NULL, "fsl,mpc8610-dma") { iprop = of_get_property(dma_np, "cell-index", NULL); if (iprop && (*iprop == 0)) { @@ -397,14 +396,19 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, } machine_data->dma_id = *iprop; + /* SSI1 needs to use DMA Channels 0 and 1, and SSI2 needs to use DMA + * channels 2 and 3. This is just how the MPC8610 is wired + * internally. + */ + playback_dma_channel = (machine_data->ssi_id == 0) ? 0 : 2; + capture_dma_channel = (machine_data->ssi_id == 0) ? 1 : 3; + /* - * Find the DMA channels to use. For now, we always use DMA channel 0 - * for playback, and DMA channel 1 for capture. + * Find the DMA channels to use. */ while ((dma_channel_np = of_get_next_child(dma_np, dma_channel_np))) { iprop = of_get_property(dma_channel_np, "cell-index", NULL); - /* Is it DMA channel 0? */ - if (iprop && (*iprop == 0)) { + if (iprop && (*iprop == playback_dma_channel)) { /* dma_channel[0] and dma_irq[0] are for playback */ dma_info.dma_channel[0] = of_iomap(dma_channel_np, 0); dma_info.dma_irq[0] = @@ -412,7 +416,7 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, machine_data->dma_channel_id[0] = *iprop; continue; } - if (iprop && (*iprop == 1)) { + if (iprop && (*iprop == capture_dma_channel)) { /* dma_channel[1] and dma_irq[1] are for capture */ dma_info.dma_channel[1] = of_iomap(dma_channel_np, 0); dma_info.dma_irq[1] = diff --git a/sound/soc/fsl/soc-of-simple.c b/sound/soc/fsl/soc-of-simple.c new file mode 100644 index 0000000..0382fda --- /dev/null +++ b/sound/soc/fsl/soc-of-simple.c @@ -0,0 +1,171 @@ +/* + * OF helpers for ALSA SoC Layer + * + * Copyright (C) 2008, Secret Lab Technologies Ltd. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-of-simple.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALSA SoC OpenFirmware bindings"); + +static DEFINE_MUTEX(of_snd_soc_mutex); +static LIST_HEAD(of_snd_soc_device_list); +static int of_snd_soc_next_index; + +struct of_snd_soc_device { + int id; + struct list_head list; + struct snd_soc_device device; + struct snd_soc_machine machine; + struct snd_soc_dai_link dai_link; + struct platform_device *pdev; + struct device_node *platform_node; + struct device_node *codec_node; +}; + +static struct snd_soc_ops of_snd_soc_ops = { +}; + +static struct of_snd_soc_device * +of_snd_soc_get_device(struct device_node *codec_node) +{ + struct of_snd_soc_device *of_soc; + + list_for_each_entry(of_soc, &of_snd_soc_device_list, list) { + if (of_soc->codec_node == codec_node) + return of_soc; + } + + of_soc = kzalloc(sizeof(struct of_snd_soc_device), GFP_KERNEL); + if (!of_soc) + return NULL; + + /* Initialize the structure and add it to the global list */ + of_soc->codec_node = codec_node; + of_soc->id = of_snd_soc_next_index++; + of_soc->machine.dai_link = &of_soc->dai_link; + of_soc->machine.num_links = 1; + of_soc->device.machine = &of_soc->machine; + of_soc->dai_link.ops = &of_snd_soc_ops; + list_add(&of_soc->list, &of_snd_soc_device_list); + + return of_soc; +} + +static void of_snd_soc_register_device(struct of_snd_soc_device *of_soc) +{ + struct platform_device *pdev; + int rc; + + /* Only register the device if both the codec and platform have + * been registered */ + if ((!of_soc->device.codec_data) || (!of_soc->platform_node)) + return; + + pr_info("platform<-->codec match achieved; registering machine\n"); + + pdev = platform_device_alloc("soc-audio", of_soc->id); + if (!pdev) { + pr_err("of_soc: platform_device_alloc() failed\n"); + return; + } + + pdev->dev.platform_data = of_soc; + platform_set_drvdata(pdev, &of_soc->device); + of_soc->device.dev = &pdev->dev; + + /* The ASoC device is complete; register it */ + rc = platform_device_add(pdev); + if (rc) { + pr_err("of_soc: platform_device_add() failed\n"); + return; + } + +} + +int of_snd_soc_register_codec(struct snd_soc_codec_device *codec_dev, + void *codec_data, struct snd_soc_dai *dai, + struct device_node *node) +{ + struct of_snd_soc_device *of_soc; + int rc = 0; + + pr_info("registering ASoC codec driver: %s\n", node->full_name); + + mutex_lock(&of_snd_soc_mutex); + of_soc = of_snd_soc_get_device(node); + if (!of_soc) { + rc = -ENOMEM; + goto out; + } + + /* Store the codec data */ + of_soc->device.codec_data = codec_data; + of_soc->device.codec_dev = codec_dev; + of_soc->dai_link.name = (char *)node->name; + of_soc->dai_link.stream_name = (char *)node->name; + of_soc->dai_link.codec_dai = dai; + + /* Now try to register the SoC device */ + of_snd_soc_register_device(of_soc); + + out: + mutex_unlock(&of_snd_soc_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(of_snd_soc_register_codec); + +int of_snd_soc_register_platform(struct snd_soc_platform *platform, + struct device_node *node, + struct snd_soc_dai *cpu_dai) +{ + struct of_snd_soc_device *of_soc; + struct device_node *codec_node; + const phandle *handle; + int len, rc = 0; + + pr_info("registering ASoC platform driver: %s\n", node->full_name); + + handle = of_get_property(node, "codec-handle", &len); + if (!handle || len < sizeof(handle)) + return -ENODEV; + codec_node = of_find_node_by_phandle(*handle); + if (!codec_node) + return -ENODEV; + pr_info("looking for codec: %s\n", codec_node->full_name); + + mutex_lock(&of_snd_soc_mutex); + of_soc = of_snd_soc_get_device(codec_node); + if (!of_soc) { + rc = -ENOMEM; + goto out; + } + + of_soc->platform_node = node; + of_soc->dai_link.cpu_dai = cpu_dai; + of_soc->device.platform = platform; + of_soc->machine.name = of_soc->dai_link.cpu_dai->name; + + /* Now try to register the SoC device */ + of_snd_soc_register_device(of_soc); + + out: + mutex_unlock(&of_snd_soc_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(of_snd_soc_register_platform); diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index aea27e7..8b7766b 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -13,3 +13,11 @@ config SND_OMAP_SOC_N810 select SND_SOC_TLV320AIC3X help Say Y if you want to add support for SoC audio on Nokia N810. + +config SND_OMAP_SOC_OSK5912 + tristate "SoC Audio support for omap osk5912" + depends on SND_OMAP_SOC && MACH_OMAP_OSK + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC23 + help + Say Y if you want to add support for SoC audio on osk5912. diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index d8d8d58..e09d1f2 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -7,5 +7,7 @@ obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o # OMAP Machine Support snd-soc-n810-objs := n810.o +snd-soc-osk5912-objs := osk5912.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index 87d0ed0..fae3ad3 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -247,9 +247,9 @@ static int n810_aic33_init(struct snd_soc_codec *codec) int i, err; /* Not connected */ - snd_soc_dapm_disable_pin(codec, "MONO_LOUT"); - snd_soc_dapm_disable_pin(codec, "HPLCOM"); - snd_soc_dapm_disable_pin(codec, "HPRCOM"); + snd_soc_dapm_nc_pin(codec, "MONO_LOUT"); + snd_soc_dapm_nc_pin(codec, "HPLCOM"); + snd_soc_dapm_nc_pin(codec, "HPRCOM"); /* Add N810 specific controls */ for (i = 0; i < ARRAY_SIZE(aic33_n810_controls); i++) { @@ -290,6 +290,7 @@ static struct snd_soc_machine snd_soc_machine_n810 = { /* Audio private data */ static struct aic3x_setup_data n810_aic33_setup = { + .i2c_bus = 2, .i2c_address = 0x18, .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED, .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT, diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 35310e1..0a063a9 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -59,12 +59,7 @@ static struct omap_mcbsp_data mcbsp_data[NUM_LINKS]; * Stream DMA parameters. DMA request line and port address are set runtime * since they are different between OMAP1 and later OMAPs */ -static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params[NUM_LINKS][2] = { -{ - { .name = "I2S PCM Stereo out", }, - { .name = "I2S PCM Stereo in", }, -}, -}; +static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params[NUM_LINKS][2]; #if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX) static const int omap1_dma_reqs[][2] = { @@ -84,11 +79,22 @@ static const unsigned long omap1_mcbsp_port[][2] = { static const int omap1_dma_reqs[][2] = {}; static const unsigned long omap1_mcbsp_port[][2] = {}; #endif -#if defined(CONFIG_ARCH_OMAP2420) -static const int omap2420_dma_reqs[][2] = { + +#if defined(CONFIG_ARCH_OMAP24XX) || defined(CONFIG_ARCH_OMAP34XX) +static const int omap24xx_dma_reqs[][2] = { { OMAP24XX_DMA_MCBSP1_TX, OMAP24XX_DMA_MCBSP1_RX }, { OMAP24XX_DMA_MCBSP2_TX, OMAP24XX_DMA_MCBSP2_RX }, +#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX) + { OMAP24XX_DMA_MCBSP3_TX, OMAP24XX_DMA_MCBSP3_RX }, + { OMAP24XX_DMA_MCBSP4_TX, OMAP24XX_DMA_MCBSP4_RX }, + { OMAP24XX_DMA_MCBSP5_TX, OMAP24XX_DMA_MCBSP5_RX }, +#endif }; +#else +static const int omap24xx_dma_reqs[][2] = {}; +#endif + +#if defined(CONFIG_ARCH_OMAP2420) static const unsigned long omap2420_mcbsp_port[][2] = { { OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1, OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 }, @@ -96,10 +102,43 @@ static const unsigned long omap2420_mcbsp_port[][2] = { OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 }, }; #else -static const int omap2420_dma_reqs[][2] = {}; static const unsigned long omap2420_mcbsp_port[][2] = {}; #endif +#if defined(CONFIG_ARCH_OMAP2430) +static const unsigned long omap2430_mcbsp_port[][2] = { + { OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR, + OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR, + OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DXR, + OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DXR, + OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DXR, + OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DRR }, +}; +#else +static const unsigned long omap2430_mcbsp_port[][2] = {}; +#endif + +#if defined(CONFIG_ARCH_OMAP34XX) +static const unsigned long omap34xx_mcbsp_port[][2] = { + { OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DRR }, + { OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DXR, + OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DRR }, +}; +#else +static const unsigned long omap34xx_mcbsp_port[][2] = {}; +#endif + static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -167,14 +206,19 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, dma = omap1_dma_reqs[bus_id][substream->stream]; port = omap1_mcbsp_port[bus_id][substream->stream]; } else if (cpu_is_omap2420()) { - dma = omap2420_dma_reqs[bus_id][substream->stream]; + dma = omap24xx_dma_reqs[bus_id][substream->stream]; port = omap2420_mcbsp_port[bus_id][substream->stream]; + } else if (cpu_is_omap2430()) { + dma = omap24xx_dma_reqs[bus_id][substream->stream]; + port = omap2430_mcbsp_port[bus_id][substream->stream]; + } else if (cpu_is_omap343x()) { + dma = omap24xx_dma_reqs[bus_id][substream->stream]; + port = omap34xx_mcbsp_port[bus_id][substream->stream]; } else { - /* - * TODO: Add support for 2430 and 3430 - */ return -ENODEV; } + omap_mcbsp_dai_dma_params[id][substream->stream].name = + substream->stream ? "Audio Capture" : "Audio Playback"; omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma; omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port; cpu_dai->dma_data = &omap_mcbsp_dai_dma_params[id][substream->stream]; @@ -245,6 +289,11 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); break; + case SND_SOC_DAIFMT_DSP_A: + /* 0-bit data delay */ + regs->rcr2 |= RDATDLY(0); + regs->xcr2 |= XDATDLY(0); + break; default: /* Unsupported data format */ return -EINVAL; @@ -310,7 +359,7 @@ static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data, int clk_id) { int sel_bit; - u16 reg; + u16 reg, reg_devconf1 = OMAP243X_CONTROL_DEVCONF1; if (cpu_class_is_omap1()) { /* OMAP1's can use only external source clock */ @@ -320,6 +369,12 @@ static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data, return 0; } + if (cpu_is_omap2420() && mcbsp_data->bus_id > 1) + return -EINVAL; + + if (cpu_is_omap343x()) + reg_devconf1 = OMAP343X_CONTROL_DEVCONF1; + switch (mcbsp_data->bus_id) { case 0: reg = OMAP2_CONTROL_DEVCONF0; @@ -329,20 +384,26 @@ static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data, reg = OMAP2_CONTROL_DEVCONF0; sel_bit = 6; break; - /* TODO: Support for ports 3 - 5 in OMAP2430 and OMAP34xx */ + case 2: + reg = reg_devconf1; + sel_bit = 0; + break; + case 3: + reg = reg_devconf1; + sel_bit = 2; + break; + case 4: + reg = reg_devconf1; + sel_bit = 4; + break; default: return -EINVAL; } - if (cpu_class_is_omap2()) { - if (clk_id == OMAP_MCBSP_SYSCLK_CLKS_FCLK) { - omap_ctrl_writel(omap_ctrl_readl(reg) & - ~(1 << sel_bit), reg); - } else { - omap_ctrl_writel(omap_ctrl_readl(reg) | - (1 << sel_bit), reg); - } - } + if (clk_id == OMAP_MCBSP_SYSCLK_CLKS_FCLK) + omap_ctrl_writel(omap_ctrl_readl(reg) & ~(1 << sel_bit), reg); + else + omap_ctrl_writel(omap_ctrl_readl(reg) | (1 << sel_bit), reg); return 0; } @@ -376,37 +437,49 @@ static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, return err; } -struct snd_soc_dai omap_mcbsp_dai[NUM_LINKS] = { -{ - .name = "omap-mcbsp-dai", - .id = 0, - .type = SND_SOC_DAI_I2S, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = OMAP_MCBSP_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = OMAP_MCBSP_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - }, - .ops = { - .startup = omap_mcbsp_dai_startup, - .shutdown = omap_mcbsp_dai_shutdown, - .trigger = omap_mcbsp_dai_trigger, - .hw_params = omap_mcbsp_dai_hw_params, - }, - .dai_ops = { - .set_fmt = omap_mcbsp_dai_set_dai_fmt, - .set_clkdiv = omap_mcbsp_dai_set_clkdiv, - .set_sysclk = omap_mcbsp_dai_set_dai_sysclk, - }, - .private_data = &mcbsp_data[0].bus_id, -}, +#define OMAP_MCBSP_DAI_BUILDER(link_id) \ +{ \ + .name = "omap-mcbsp-dai-(link_id)", \ + .id = (link_id), \ + .type = SND_SOC_DAI_I2S, \ + .playback = { \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = OMAP_MCBSP_RATES, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .capture = { \ + .channels_min = 2, \ + .channels_max = 2, \ + .rates = OMAP_MCBSP_RATES, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + }, \ + .ops = { \ + .startup = omap_mcbsp_dai_startup, \ + .shutdown = omap_mcbsp_dai_shutdown, \ + .trigger = omap_mcbsp_dai_trigger, \ + .hw_params = omap_mcbsp_dai_hw_params, \ + }, \ + .dai_ops = { \ + .set_fmt = omap_mcbsp_dai_set_dai_fmt, \ + .set_clkdiv = omap_mcbsp_dai_set_clkdiv, \ + .set_sysclk = omap_mcbsp_dai_set_dai_sysclk, \ + }, \ + .private_data = &mcbsp_data[(link_id)].bus_id, \ +} + +struct snd_soc_dai omap_mcbsp_dai[] = { + OMAP_MCBSP_DAI_BUILDER(0), + OMAP_MCBSP_DAI_BUILDER(1), +#if NUM_LINKS >= 3 + OMAP_MCBSP_DAI_BUILDER(2), +#endif +#if NUM_LINKS == 5 + OMAP_MCBSP_DAI_BUILDER(3), + OMAP_MCBSP_DAI_BUILDER(4), +#endif }; + EXPORT_SYMBOL_GPL(omap_mcbsp_dai); MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>"); diff --git a/sound/soc/omap/omap-mcbsp.h b/sound/soc/omap/omap-mcbsp.h index ed8afb5..df7ad13 100644 --- a/sound/soc/omap/omap-mcbsp.h +++ b/sound/soc/omap/omap-mcbsp.h @@ -38,11 +38,17 @@ enum omap_mcbsp_div { OMAP_MCBSP_CLKGDV, /* Sample rate generator divider */ }; -/* - * REVISIT: Preparation for the ASoC v2. Let the number of available links to - * be same than number of McBSP ports found in OMAP(s) we are compiling for. - */ -#define NUM_LINKS 1 +#if defined(CONFIG_ARCH_OMAP2420) +#define NUM_LINKS 2 +#endif +#if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX) +#undef NUM_LINKS +#define NUM_LINKS 3 +#endif +#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX) +#undef NUM_LINKS +#define NUM_LINKS 5 +#endif extern struct snd_soc_dai omap_mcbsp_dai[NUM_LINKS]; diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 690bfea..e9084fd 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -97,7 +97,7 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, prtd->dma_data = dma_data; err = omap_request_dma(dma_data->dma_req, dma_data->name, omap_pcm_dma_irq, substream, &prtd->dma_ch); - if (!cpu_is_omap1510()) { + if (!err & !cpu_is_omap1510()) { /* * Link channel with itself so DMA doesn't need any * reprogramming while looping the buffer @@ -147,12 +147,14 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream) dma_params.src_or_dst_synch = OMAP_DMA_DST_SYNC; dma_params.src_start = runtime->dma_addr; dma_params.dst_start = dma_data->port_addr; + dma_params.dst_port = OMAP_DMA_PORT_MPUI; } else { dma_params.src_amode = OMAP_DMA_AMODE_CONSTANT; dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC; dma_params.src_or_dst_synch = OMAP_DMA_SRC_SYNC; dma_params.src_start = dma_data->port_addr; dma_params.dst_start = runtime->dma_addr; + dma_params.src_port = OMAP_DMA_PORT_MPUI; } /* * Set DMA transfer frame size equal to ALSA period size and frame diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c new file mode 100644 index 0000000..0fe7337 --- /dev/null +++ b/sound/soc/omap/osk5912.c @@ -0,0 +1,232 @@ +/* + * osk5912.c -- SoC audio for OSK 5912 + * + * Copyright (C) 2008 Mistral Solutions + * + * Contact: Arun KS <arunks@mistralsolutions.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/clk.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <linux/gpio.h> +#include <mach/mcbsp.h> + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/tlv320aic23.h" + +#define CODEC_CLOCK 12000000 + +static struct clk *tlv320aic23_mclk; + +static int osk_startup(struct snd_pcm_substream *substream) +{ + return clk_enable(tlv320aic23_mclk); +} + +static void osk_shutdown(struct snd_pcm_substream *substream) +{ + clk_disable(tlv320aic23_mclk); +} + +static int osk_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int err; + + /* Set codec DAI configuration */ + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return err; + } + + /* Set cpu DAI configuration */ + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return err; + } + + /* Set the codec system clock for DAC and ADC */ + err = + snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN); + + if (err < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return err; + } + + return err; +} + +static struct snd_soc_ops osk_ops = { + .startup = osk_startup, + .hw_params = osk_hw_params, + .shutdown = osk_shutdown, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +static int osk_tlv320aic23_init(struct snd_soc_codec *codec) +{ + + /* Add osk5912 specific widgets */ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + /* Set up osk5912 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Line In"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link osk_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &tlv320aic23_dai, + .init = osk_tlv320aic23_init, + .ops = &osk_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_machine snd_soc_machine_osk = { + .name = "OSK5912", + .dai_link = &osk_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device osk_snd_devdata = { + .machine = &snd_soc_machine_osk, + .platform = &omap_soc_platform, + .codec_dev = &soc_codec_dev_tlv320aic23, +}; + +static struct platform_device *osk_snd_device; + +static int __init osk_soc_init(void) +{ + int err; + u32 curRate; + struct device *dev; + + if (!(machine_is_omap_osk())) + return -ENODEV; + + osk_snd_device = platform_device_alloc("soc-audio", -1); + if (!osk_snd_device) + return -ENOMEM; + + platform_set_drvdata(osk_snd_device, &osk_snd_devdata); + osk_snd_devdata.dev = &osk_snd_device->dev; + *(unsigned int *)osk_dai.cpu_dai->private_data = 0; /* McBSP1 */ + err = platform_device_add(osk_snd_device); + if (err) + goto err1; + + dev = &osk_snd_device->dev; + + tlv320aic23_mclk = clk_get(dev, "mclk"); + if (IS_ERR(tlv320aic23_mclk)) { + printk(KERN_ERR "Could not get mclk clock\n"); + return -ENODEV; + } + + if (clk_get_usecount(tlv320aic23_mclk) > 0) { + /* MCLK is already in use */ + printk(KERN_WARNING + "MCLK in use at %d Hz. We change it to %d Hz\n", + (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK); + } + + /* + * Configure 12 MHz output on MCLK. + */ + curRate = (uint) clk_get_rate(tlv320aic23_mclk); + if (curRate != CODEC_CLOCK) { + if (clk_set_rate(tlv320aic23_mclk, CODEC_CLOCK)) { + printk(KERN_ERR "Cannot set MCLK for AIC23 CODEC\n"); + err = -ECANCELED; + goto err1; + } + } + + printk(KERN_INFO "MCLK = %d [%d], usecount = %d\n", + (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK, + clk_get_usecount(tlv320aic23_mclk)); + + return 0; +err1: + clk_put(tlv320aic23_mclk); + platform_device_del(osk_snd_device); + platform_device_put(osk_snd_device); + + return err; + +} + +static void __exit osk_soc_exit(void) +{ + platform_device_unregister(osk_snd_device); +} + +module_init(osk_soc_init); +module_exit(osk_soc_exit); + +MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); +MODULE_DESCRIPTION("ALSA SoC OSK 5912"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 9212c37..f8c1cdd 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -1,6 +1,7 @@ config SND_PXA2XX_SOC tristate "SoC Audio for the Intel PXA2xx chip" depends on ARCH_PXA + select SND_PXA2XX_LIB help Say Y or M if you want to add support for codecs attached to the PXA2xx AC97, I2S or SSP interface. You will also need @@ -13,6 +14,8 @@ config SND_PXA2XX_AC97 config SND_PXA2XX_SOC_AC97 tristate select AC97_BUS + select SND_ARM + select SND_PXA2XX_LIB_AC97 select SND_SOC_AC97_BUS config SND_PXA2XX_SOC_I2S diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c index fa69faa..2718eaf 100644 --- a/sound/soc/pxa/corgi.c +++ b/sound/soc/pxa/corgi.c @@ -4,7 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it @@ -281,8 +281,8 @@ static int corgi_wm8731_init(struct snd_soc_codec *codec) { int i, err; - snd_soc_dapm_disable_pin(codec, "LLINEIN"); - snd_soc_dapm_disable_pin(codec, "RLINEIN"); + snd_soc_dapm_nc_pin(codec, "LLINEIN"); + snd_soc_dapm_nc_pin(codec, "RLINEIN"); /* Add corgi specific controls */ for (i = 0; i < ARRAY_SIZE(wm8731_corgi_controls); i++) { @@ -322,6 +322,7 @@ static struct snd_soc_machine snd_soc_machine_corgi = { /* corgi audio private data */ static struct wm8731_setup_data corgi_wm8731_setup = { + .i2c_bus = 0, .i2c_address = 0x1b, }; diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c index d9c3f7b..e6ff692 100644 --- a/sound/soc/pxa/em-x270.c +++ b/sound/soc/pxa/em-x270.c @@ -9,7 +9,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c index a4697f7..4d9930c 100644 --- a/sound/soc/pxa/poodle.c +++ b/sound/soc/pxa/poodle.c @@ -4,7 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it @@ -242,8 +242,8 @@ static int poodle_wm8731_init(struct snd_soc_codec *codec) { int i, err; - snd_soc_dapm_disable_pin(codec, "LLINEIN"); - snd_soc_dapm_disable_pin(codec, "RLINEIN"); + snd_soc_dapm_nc_pin(codec, "LLINEIN"); + snd_soc_dapm_nc_pin(codec, "RLINEIN"); snd_soc_dapm_enable_pin(codec, "MICIN"); /* Add poodle specific controls */ @@ -284,6 +284,7 @@ static struct snd_soc_machine snd_soc_machine_poodle = { /* poodle audio private data */ static struct wm8731_setup_data poodle_wm8731_setup = { + .i2c_bus = 0, .i2c_address = 0x1b, }; diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index ac8f227..a7a3a9c 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -13,225 +13,30 @@ #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/interrupt.h> -#include <linux/wait.h> -#include <linux/clk.h> -#include <linux/delay.h> #include <sound/core.h> -#include <sound/pcm.h> #include <sound/ac97_codec.h> -#include <sound/initval.h> #include <sound/soc.h> +#include <sound/pxa2xx-lib.h> -#include <asm/irq.h> -#include <linux/mutex.h> #include <mach/hardware.h> #include <mach/pxa-regs.h> -#include <mach/pxa2xx-gpio.h> -#include <mach/audio.h> #include "pxa2xx-pcm.h" #include "pxa2xx-ac97.h" -static DEFINE_MUTEX(car_mutex); -static DECLARE_WAIT_QUEUE_HEAD(gsr_wq); -static volatile long gsr_bits; -static struct clk *ac97_clk; -#ifdef CONFIG_PXA27x -static struct clk *ac97conf_clk; -#endif - -/* - * Beware PXA27x bugs: - * - * o Slot 12 read from modem space will hang controller. - * o CDONE, SDONE interrupt fails after any slot 12 IO. - * - * We therefore have an hybrid approach for waiting on SDONE (interrupt or - * 1 jiffy timeout if interrupt never comes). - */ - -static unsigned short pxa2xx_ac97_read(struct snd_ac97 *ac97, - unsigned short reg) -{ - unsigned short val = -1; - volatile u32 *reg_addr; - - mutex_lock(&car_mutex); - - /* set up primary or secondary codec/modem space */ -#if defined(CONFIG_PXA27x) || defined(CONFIG_PXA3xx) - reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE; -#else - if (reg == AC97_GPIO_STATUS) - reg_addr = ac97->num ? &SMC_REG_BASE : &PMC_REG_BASE; - else - reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE; -#endif - reg_addr += (reg >> 1); - -#ifndef CONFIG_PXA27x - if (reg == AC97_GPIO_STATUS) { - /* read from controller cache */ - val = *reg_addr; - goto out; - } -#endif - - /* start read access across the ac97 link */ - GSR = GSR_CDONE | GSR_SDONE; - gsr_bits = 0; - val = *reg_addr; - - wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1); - if (!((GSR | gsr_bits) & GSR_SDONE)) { - printk(KERN_ERR "%s: read error (ac97_reg=%x GSR=%#lx)\n", - __func__, reg, GSR | gsr_bits); - val = -1; - goto out; - } - - /* valid data now */ - GSR = GSR_CDONE | GSR_SDONE; - gsr_bits = 0; - val = *reg_addr; - /* but we've just started another cycle... */ - wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_SDONE, 1); - -out: mutex_unlock(&car_mutex); - return val; -} - -static void pxa2xx_ac97_write(struct snd_ac97 *ac97, unsigned short reg, - unsigned short val) -{ - volatile u32 *reg_addr; - - mutex_lock(&car_mutex); - - /* set up primary or secondary codec/modem space */ -#if defined(CONFIG_PXA27x) || defined(CONFIG_PXA3xx) - reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE; -#else - if (reg == AC97_GPIO_STATUS) - reg_addr = ac97->num ? &SMC_REG_BASE : &PMC_REG_BASE; - else - reg_addr = ac97->num ? &SAC_REG_BASE : &PAC_REG_BASE; -#endif - reg_addr += (reg >> 1); - - GSR = GSR_CDONE | GSR_SDONE; - gsr_bits = 0; - *reg_addr = val; - wait_event_timeout(gsr_wq, (GSR | gsr_bits) & GSR_CDONE, 1); - if (!((GSR | gsr_bits) & GSR_CDONE)) - printk(KERN_ERR "%s: write error (ac97_reg=%x GSR=%#lx)\n", - __func__, reg, GSR | gsr_bits); - - mutex_unlock(&car_mutex); -} - static void pxa2xx_ac97_warm_reset(struct snd_ac97 *ac97) { -#ifdef CONFIG_PXA3xx - int timeout = 100; -#endif - gsr_bits = 0; - -#ifdef CONFIG_PXA27x - /* warm reset broken on Bulverde, - so manually keep AC97 reset high */ - pxa_gpio_mode(113 | GPIO_OUT | GPIO_DFLT_HIGH); - udelay(10); - GCR |= GCR_WARM_RST; - pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT); - udelay(500); -#elif defined(CONFIG_PXA3xx) - /* Can't use interrupts */ - GCR |= GCR_WARM_RST; - while (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--) - mdelay(1); -#else - GCR |= GCR_WARM_RST | GCR_PRIRDY_IEN | GCR_SECRDY_IEN; - wait_event_timeout(gsr_wq, gsr_bits & (GSR_PCR | GSR_SCR), 1); -#endif - - if (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR))) - printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n", - __func__, gsr_bits); + pxa2xx_ac97_try_warm_reset(ac97); - GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); - GCR |= GCR_SDONE_IE|GCR_CDONE_IE; + pxa2xx_ac97_finish_reset(ac97); } static void pxa2xx_ac97_cold_reset(struct snd_ac97 *ac97) { -#ifdef CONFIG_PXA3xx - int timeout = 1000; - - /* Hold CLKBPB for 100us */ - GCR = 0; - GCR = GCR_CLKBPB; - udelay(100); - GCR = 0; -#endif - - GCR &= GCR_COLD_RST; /* clear everything but nCRST */ - GCR &= ~GCR_COLD_RST; /* then assert nCRST */ - - gsr_bits = 0; -#ifdef CONFIG_PXA27x - /* PXA27x Developers Manual section 13.5.2.2.1 */ - clk_enable(ac97conf_clk); - udelay(5); - clk_disable(ac97conf_clk); - GCR = GCR_COLD_RST; - udelay(50); -#elif defined(CONFIG_PXA3xx) - /* Can't use interrupts on PXA3xx */ - GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); - - GCR = GCR_WARM_RST | GCR_COLD_RST; - while (!(GSR & (GSR_PCR | GSR_SCR)) && timeout--) - mdelay(10); -#else - GCR = GCR_COLD_RST; - GCR |= GCR_CDONE_IE|GCR_SDONE_IE; - wait_event_timeout(gsr_wq, gsr_bits & (GSR_PCR | GSR_SCR), 1); -#endif - - if (!((GSR | gsr_bits) & (GSR_PCR | GSR_SCR))) - printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n", - __func__, gsr_bits); - - GCR &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); - GCR |= GCR_SDONE_IE|GCR_CDONE_IE; -} - -static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id) -{ - long status; - - status = GSR; - if (status) { - GSR = status; - gsr_bits |= status; - wake_up(&gsr_wq); - -#ifdef CONFIG_PXA27x - /* Although we don't use those we still need to clear them - since they tend to spuriously trigger when MMC is used - (hardware bug? go figure)... */ - MISR = MISR_EOC; - PISR = PISR_EOC; - MCSR = MCSR_EOC; -#endif - - return IRQ_HANDLED; - } + pxa2xx_ac97_try_cold_reset(ac97); - return IRQ_NONE; + pxa2xx_ac97_finish_reset(ac97); } struct snd_ac97_bus_ops soc_ac97_ops = { @@ -285,16 +90,13 @@ static struct pxa2xx_pcm_dma_params pxa2xx_ac97_pcm_mic_mono_in = { static int pxa2xx_ac97_suspend(struct platform_device *pdev, struct snd_soc_dai *dai) { - GCR |= GCR_ACLINK_OFF; - clk_disable(ac97_clk); - return 0; + return pxa2xx_ac97_hw_suspend(); } static int pxa2xx_ac97_resume(struct platform_device *pdev, struct snd_soc_dai *dai) { - clk_enable(ac97_clk); - return 0; + return pxa2xx_ac97_hw_resume(); } #else @@ -305,61 +107,13 @@ static int pxa2xx_ac97_resume(struct platform_device *pdev, static int pxa2xx_ac97_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { - int ret; - - ret = request_irq(IRQ_AC97, pxa2xx_ac97_irq, IRQF_DISABLED, "AC97", NULL); - if (ret < 0) - goto err; - - pxa_gpio_mode(GPIO31_SYNC_AC97_MD); - pxa_gpio_mode(GPIO30_SDATA_OUT_AC97_MD); - pxa_gpio_mode(GPIO28_BITCLK_AC97_MD); - pxa_gpio_mode(GPIO29_SDATA_IN_AC97_MD); -#ifdef CONFIG_PXA27x - /* Use GPIO 113 as AC97 Reset on Bulverde */ - pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT); - - ac97conf_clk = clk_get(&pdev->dev, "AC97CONFCLK"); - if (IS_ERR(ac97conf_clk)) { - ret = PTR_ERR(ac97conf_clk); - ac97conf_clk = NULL; - goto err_irq; - } -#endif - ac97_clk = clk_get(&pdev->dev, "AC97CLK"); - if (IS_ERR(ac97_clk)) { - ret = PTR_ERR(ac97_clk); - ac97_clk = NULL; - goto err_irq; - } - clk_enable(ac97_clk); - return 0; - - err_irq: - GCR |= GCR_ACLINK_OFF; -#ifdef CONFIG_PXA27x - if (ac97conf_clk) { - clk_put(ac97conf_clk); - ac97conf_clk = NULL; - } -#endif - free_irq(IRQ_AC97, NULL); - err: - return ret; + return pxa2xx_ac97_hw_probe(pdev); } static void pxa2xx_ac97_remove(struct platform_device *pdev, struct snd_soc_dai *dai) { - GCR |= GCR_ACLINK_OFF; - free_irq(IRQ_AC97, NULL); -#ifdef CONFIG_PXA27x - clk_put(ac97conf_clk); - ac97conf_clk = NULL; -#endif - clk_disable(ac97_clk); - clk_put(ac97_clk); - ac97_clk = NULL; + pxa2xx_ac97_hw_remove(pdev); } static int pxa2xx_ac97_hw_params(struct snd_pcm_substream *substream, diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index ad4c31d..e758034 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -3,7 +3,7 @@ * * Copyright 2005 Wolfson Microelectronics PLC. * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * lrg@slimlogic.co.uk * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -21,6 +21,7 @@ #include <sound/pcm.h> #include <sound/initval.h> #include <sound/soc.h> +#include <sound/pxa2xx-lib.h> #include <mach/hardware.h> #include <mach/pxa-regs.h> @@ -30,6 +31,14 @@ #include "pxa2xx-pcm.h" #include "pxa2xx-i2s.h" +struct pxa2xx_gpio { + u32 sys; + u32 rx; + u32 tx; + u32 clk; + u32 frm; +}; + /* * I2S Controller Register and Bit Definitions */ @@ -105,11 +114,6 @@ static struct pxa2xx_gpio gpio_bus[] = { .frm = GPIO31_SYNC_I2S_MD, }, { /* I2S SoC Master */ -#ifdef CONFIG_PXA27x - .sys = GPIO113_I2S_SYSCLK_MD, -#else - .sys = GPIO32_SYSCLK_I2S_MD, -#endif .rx = GPIO29_SDATA_IN_I2S_MD, .tx = GPIO30_SDATA_OUT_I2S_MD, .clk = GPIO28_BITCLK_OUT_I2S_MD, @@ -383,6 +387,11 @@ static struct platform_driver pxa2xx_i2s_driver = { static int __init pxa2xx_i2s_init(void) { + if (cpu_is_pxa27x()) + gpio_bus[1].sys = GPIO113_I2S_SYSCLK_MD; + else + gpio_bus[1].sys = GPIO32_SYSCLK_I2S_MD; + clk_i2s = ERR_PTR(-ENOENT); return platform_driver_register(&pxa2xx_i2s_driver); } @@ -396,6 +405,6 @@ module_init(pxa2xx_i2s_init); module_exit(pxa2xx_i2s_exit); /* Module information */ -MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("pxa2xx I2S SoC Interface"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/pxa/pxa2xx-pcm.c b/sound/soc/pxa/pxa2xx-pcm.c index 4345f38..afcd892 100644 --- a/sound/soc/pxa/pxa2xx-pcm.c +++ b/sound/soc/pxa/pxa2xx-pcm.c @@ -10,64 +10,14 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/slab.h> #include <linux/dma-mapping.h> #include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> - -#include <asm/dma.h> -#include <mach/hardware.h> -#include <mach/pxa-regs.h> -#include <mach/audio.h> +#include <sound/pxa2xx-lib.h> #include "pxa2xx-pcm.h" - -static const struct snd_pcm_hardware pxa2xx_pcm_hardware = { - .info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE | - SNDRV_PCM_FMTBIT_S32_LE, - .period_bytes_min = 32, - .period_bytes_max = 8192 - 32, - .periods_min = 1, - .periods_max = PAGE_SIZE/sizeof(pxa_dma_desc), - .buffer_bytes_max = 128 * 1024, - .fifo_size = 32, -}; - -struct pxa2xx_runtime_data { - int dma_ch; - struct pxa2xx_pcm_dma_params *params; - pxa_dma_desc *dma_desc_array; - dma_addr_t dma_desc_array_phys; -}; - -static void pxa2xx_pcm_dma_irq(int dma_ch, void *dev_id) -{ - struct snd_pcm_substream *substream = dev_id; - struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; - int dcsr; - - dcsr = DCSR(dma_ch); - DCSR(dma_ch) = dcsr & ~DCSR_STOPIRQEN; - - if (dcsr & DCSR_ENDINTR) { - snd_pcm_period_elapsed(substream); - } else { - printk(KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n", - prtd->params->name, dma_ch, dcsr); - } -} +#include "../../arm/pxa2xx-pcm.h" static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) @@ -76,10 +26,6 @@ static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, struct pxa2xx_runtime_data *prtd = runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct pxa2xx_pcm_dma_params *dma = rtd->dai->cpu_dai->dma_data; - size_t totsize = params_buffer_bytes(params); - size_t period = params_period_bytes(params); - pxa_dma_desc *dma_desc; - dma_addr_t dma_buff_phys, next_desc_phys; int ret; /* return if this is a bufferless transfer e.g. @@ -106,42 +52,16 @@ static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, prtd->dma_ch = ret; } - snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); - runtime->dma_bytes = totsize; - - dma_desc = prtd->dma_desc_array; - next_desc_phys = prtd->dma_desc_array_phys; - dma_buff_phys = runtime->dma_addr; - do { - next_desc_phys += sizeof(pxa_dma_desc); - dma_desc->ddadr = next_desc_phys; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - dma_desc->dsadr = dma_buff_phys; - dma_desc->dtadr = prtd->params->dev_addr; - } else { - dma_desc->dsadr = prtd->params->dev_addr; - dma_desc->dtadr = dma_buff_phys; - } - if (period > totsize) - period = totsize; - dma_desc->dcmd = prtd->params->dcmd | period | DCMD_ENDIRQEN; - dma_desc++; - dma_buff_phys += period; - } while (totsize -= period); - dma_desc[-1].ddadr = prtd->dma_desc_array_phys; - - return 0; + return __pxa2xx_pcm_hw_params(substream, params); } static int pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream) { struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; - if (prtd && prtd->params) - *prtd->params->drcmr = 0; + __pxa2xx_pcm_hw_free(substream); if (prtd->dma_ch) { - snd_pcm_set_runtime_buffer(substream, NULL); pxa_free_dma(prtd->dma_ch); prtd->dma_ch = 0; } @@ -149,188 +69,21 @@ static int pxa2xx_pcm_hw_free(struct snd_pcm_substream *substream) return 0; } -static int pxa2xx_pcm_prepare(struct snd_pcm_substream *substream) -{ - struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; - - DCSR(prtd->dma_ch) &= ~DCSR_RUN; - DCSR(prtd->dma_ch) = 0; - DCMD(prtd->dma_ch) = 0; - *prtd->params->drcmr = prtd->dma_ch | DRCMR_MAPVLD; - - return 0; -} - -static int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct pxa2xx_runtime_data *prtd = substream->runtime->private_data; - int ret = 0; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys; - DCSR(prtd->dma_ch) = DCSR_RUN; - break; - - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - DCSR(prtd->dma_ch) &= ~DCSR_RUN; - break; - - case SNDRV_PCM_TRIGGER_RESUME: - DCSR(prtd->dma_ch) |= DCSR_RUN; - break; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - DDADR(prtd->dma_ch) = prtd->dma_desc_array_phys; - DCSR(prtd->dma_ch) |= DCSR_RUN; - break; - - default: - ret = -EINVAL; - } - - return ret; -} - -static snd_pcm_uframes_t -pxa2xx_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct pxa2xx_runtime_data *prtd = runtime->private_data; - - dma_addr_t ptr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? - DSADR(prtd->dma_ch) : DTADR(prtd->dma_ch); - snd_pcm_uframes_t x = bytes_to_frames(runtime, ptr - runtime->dma_addr); - - if (x == runtime->buffer_size) - x = 0; - return x; -} - -static int pxa2xx_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct pxa2xx_runtime_data *prtd; - int ret; - - snd_soc_set_runtime_hwparams(substream, &pxa2xx_pcm_hardware); - - /* - * For mysterious reasons (and despite what the manual says) - * playback samples are lost if the DMA count is not a multiple - * of the DMA burst size. Let's add a rule to enforce that. - */ - ret = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); - if (ret) - goto out; - - ret = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); - if (ret) - goto out; - - ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) - goto out; - - prtd = kzalloc(sizeof(struct pxa2xx_runtime_data), GFP_KERNEL); - if (prtd == NULL) { - ret = -ENOMEM; - goto out; - } - - prtd->dma_desc_array = - dma_alloc_writecombine(substream->pcm->card->dev, PAGE_SIZE, - &prtd->dma_desc_array_phys, GFP_KERNEL); - if (!prtd->dma_desc_array) { - ret = -ENOMEM; - goto err1; - } - - runtime->private_data = prtd; - return 0; - - err1: - kfree(prtd); - out: - return ret; -} - -static int pxa2xx_pcm_close(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct pxa2xx_runtime_data *prtd = runtime->private_data; - - dma_free_writecombine(substream->pcm->card->dev, PAGE_SIZE, - prtd->dma_desc_array, prtd->dma_desc_array_phys); - kfree(prtd); - return 0; -} - -static int pxa2xx_pcm_mmap(struct snd_pcm_substream *substream, - struct vm_area_struct *vma) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - return dma_mmap_writecombine(substream->pcm->card->dev, vma, - runtime->dma_area, - runtime->dma_addr, - runtime->dma_bytes); -} - struct snd_pcm_ops pxa2xx_pcm_ops = { - .open = pxa2xx_pcm_open, - .close = pxa2xx_pcm_close, + .open = __pxa2xx_pcm_open, + .close = __pxa2xx_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = pxa2xx_pcm_hw_params, .hw_free = pxa2xx_pcm_hw_free, - .prepare = pxa2xx_pcm_prepare, + .prepare = __pxa2xx_pcm_prepare, .trigger = pxa2xx_pcm_trigger, .pointer = pxa2xx_pcm_pointer, .mmap = pxa2xx_pcm_mmap, }; -static int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) -{ - struct snd_pcm_substream *substream = pcm->streams[stream].substream; - struct snd_dma_buffer *buf = &substream->dma_buffer; - size_t size = pxa2xx_pcm_hardware.buffer_bytes_max; - buf->dev.type = SNDRV_DMA_TYPE_DEV; - buf->dev.dev = pcm->card->dev; - buf->private_data = NULL; - buf->area = dma_alloc_writecombine(pcm->card->dev, size, - &buf->addr, GFP_KERNEL); - if (!buf->area) - return -ENOMEM; - buf->bytes = size; - return 0; -} - -static void pxa2xx_pcm_free_dma_buffers(struct snd_pcm *pcm) -{ - struct snd_pcm_substream *substream; - struct snd_dma_buffer *buf; - int stream; - - for (stream = 0; stream < 2; stream++) { - substream = pcm->streams[stream].substream; - if (!substream) - continue; - - buf = &substream->dma_buffer; - if (!buf->area) - continue; - - dma_free_writecombine(pcm->card->dev, buf->bytes, - buf->area, buf->addr); - buf->area = NULL; - } -} - static u64 pxa2xx_pcm_dmamask = DMA_32BIT_MASK; -int pxa2xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, +static int pxa2xx_soc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret = 0; @@ -360,7 +113,7 @@ int pxa2xx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_soc_platform pxa2xx_soc_platform = { .name = "pxa2xx-audio", .pcm_ops = &pxa2xx_pcm_ops, - .pcm_new = pxa2xx_pcm_new, + .pcm_new = pxa2xx_soc_pcm_new, .pcm_free = pxa2xx_pcm_free_dma_buffers, }; EXPORT_SYMBOL_GPL(pxa2xx_soc_platform); diff --git a/sound/soc/pxa/pxa2xx-pcm.h b/sound/soc/pxa/pxa2xx-pcm.h index 54c9c75..60c3b20 100644 --- a/sound/soc/pxa/pxa2xx-pcm.h +++ b/sound/soc/pxa/pxa2xx-pcm.h @@ -13,21 +13,6 @@ #ifndef _PXA2XX_PCM_H #define _PXA2XX_PCM_H -struct pxa2xx_pcm_dma_params { - char *name; /* stream identifier */ - u32 dcmd; /* DMA descriptor dcmd field */ - volatile u32 *drcmr; /* the DMA request channel to use */ - u32 dev_addr; /* device physical address for DMA */ -}; - -struct pxa2xx_gpio { - u32 sys; - u32 rx; - u32 tx; - u32 clk; - u32 frm; -}; - /* platform data */ extern struct snd_soc_platform pxa2xx_soc_platform; diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index b89a3ed..d307b67 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -4,7 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it @@ -281,13 +281,13 @@ static int spitz_wm8750_init(struct snd_soc_codec *codec) int i, err; /* NC codec pins */ - snd_soc_dapm_disable_pin(codec, "RINPUT1"); - snd_soc_dapm_disable_pin(codec, "LINPUT2"); - snd_soc_dapm_disable_pin(codec, "RINPUT2"); - snd_soc_dapm_disable_pin(codec, "LINPUT3"); - snd_soc_dapm_disable_pin(codec, "RINPUT3"); - snd_soc_dapm_disable_pin(codec, "OUT3"); - snd_soc_dapm_disable_pin(codec, "MONO1"); + snd_soc_dapm_nc_pin(codec, "RINPUT1"); + snd_soc_dapm_nc_pin(codec, "LINPUT2"); + snd_soc_dapm_nc_pin(codec, "RINPUT2"); + snd_soc_dapm_nc_pin(codec, "LINPUT3"); + snd_soc_dapm_nc_pin(codec, "RINPUT3"); + snd_soc_dapm_nc_pin(codec, "OUT3"); + snd_soc_dapm_nc_pin(codec, "MONO1"); /* Add spitz specific controls */ for (i = 0; i < ARRAY_SIZE(wm8750_spitz_controls); i++) { @@ -327,6 +327,7 @@ static struct snd_soc_machine snd_soc_machine_spitz = { /* spitz audio private data */ static struct wm8750_setup_data spitz_wm8750_setup = { + .i2c_bus = 0, .i2c_address = 0x1b, }; diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c index 2baaa75..afefe41 100644 --- a/sound/soc/pxa/tosa.c +++ b/sound/soc/pxa/tosa.c @@ -4,7 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * Authors: Liam Girdwood <lrg@slimlogic.co.uk> * Richard Purdie <richard@openedhand.com> * * This program is free software; you can redistribute it and/or modify it @@ -190,8 +190,8 @@ static int tosa_ac97_init(struct snd_soc_codec *codec) { int i, err; - snd_soc_dapm_disable_pin(codec, "OUT3"); - snd_soc_dapm_disable_pin(codec, "MONOOUT"); + snd_soc_dapm_nc_pin(codec, "OUT3"); + snd_soc_dapm_nc_pin(codec, "MONOOUT"); /* add tosa specific controls */ for (i = 0; i < ARRAY_SIZE(tosa_controls); i++) { diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c index 8089f8e..87ddfef 100644 --- a/sound/soc/s3c24xx/neo1973_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -24,6 +24,7 @@ #include <sound/soc-dapm.h> #include <sound/tlv.h> +#include <asm/mach-types.h> #include <asm/hardware/scoop.h> #include <mach/regs-clock.h> #include <mach/regs-gpio.h> @@ -510,21 +511,20 @@ static int neo1973_wm8753_init(struct snd_soc_codec *codec) DBG("Entered %s\n", __func__); /* set up NC codec pins */ - snd_soc_dapm_disable_pin(codec, "LOUT2"); - snd_soc_dapm_disable_pin(codec, "ROUT2"); - snd_soc_dapm_disable_pin(codec, "OUT3"); - snd_soc_dapm_disable_pin(codec, "OUT4"); - snd_soc_dapm_disable_pin(codec, "LINE1"); - snd_soc_dapm_disable_pin(codec, "LINE2"); - - - /* set endpoints to default mode */ - set_scenario_endpoints(codec, NEO_AUDIO_OFF); + snd_soc_dapm_nc_pin(codec, "LOUT2"); + snd_soc_dapm_nc_pin(codec, "ROUT2"); + snd_soc_dapm_nc_pin(codec, "OUT3"); + snd_soc_dapm_nc_pin(codec, "OUT4"); + snd_soc_dapm_nc_pin(codec, "LINE1"); + snd_soc_dapm_nc_pin(codec, "LINE2"); /* Add neo1973 specific widgets */ snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets, ARRAY_SIZE(wm8753_dapm_widgets)); + /* set endpoints to default mode */ + set_scenario_endpoints(codec, NEO_AUDIO_OFF); + /* add neo1973 specific controls */ for (i = 0; i < ARRAY_SIZE(wm8753_neo1973_controls); i++) { err = snd_ctl_add(codec->card, @@ -586,6 +586,7 @@ static struct snd_soc_machine neo1973 = { }; static struct wm8753_setup_data neo1973_wm8753_setup = { + .i2c_bus = 0, .i2c_address = 0x1a, }; @@ -596,54 +597,24 @@ static struct snd_soc_device neo1973_snd_devdata = { .codec_data = &neo1973_wm8753_setup, }; -static struct i2c_client client_template; - -static const unsigned short normal_i2c[] = { 0x7C, I2C_CLIENT_END }; - -/* Magic definition of all other variables and things */ -I2C_CLIENT_INSMOD; - -static int lm4857_amp_probe(struct i2c_adapter *adap, int addr, int kind) +static int lm4857_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) { - int ret; - DBG("Entered %s\n", __func__); - client_template.adapter = adap; - client_template.addr = addr; - - i2c = kmemdup(&client_template, sizeof(client_template), GFP_KERNEL); - if (i2c == NULL) - return -ENOMEM; - - ret = i2c_attach_client(i2c); - if (ret < 0) { - printk(KERN_ERR "LM4857 failed to attach at addr %x\n", addr); - goto exit_err; - } + i2c = client; lm4857_write_regs(); - return ret; - -exit_err: - kfree(i2c); - return ret; -} - -static int lm4857_i2c_detach(struct i2c_client *client) -{ - DBG("Entered %s\n", __func__); - - i2c_detach_client(client); - kfree(client); return 0; } -static int lm4857_i2c_attach(struct i2c_adapter *adap) +static int lm4857_i2c_remove(struct i2c_client *client) { DBG("Entered %s\n", __func__); - return i2c_probe(adap, &addr_data, lm4857_amp_probe); + i2c = NULL; + + return 0; } static u8 lm4857_state; @@ -681,24 +652,22 @@ static void lm4857_shutdown(struct i2c_client *dev) lm4857_write_regs(); } -/* corgi i2c codec control layer */ +static const struct i2c_device_id lm4857_i2c_id[] = { + { "neo1973_lm4857", 0 }, + { } +}; + static struct i2c_driver lm4857_i2c_driver = { .driver = { .name = "LM4857 I2C Amp", .owner = THIS_MODULE, }, - .id = I2C_DRIVERID_LM4857, .suspend = lm4857_suspend, .resume = lm4857_resume, .shutdown = lm4857_shutdown, - .attach_adapter = lm4857_i2c_attach, - .detach_client = lm4857_i2c_detach, - .command = NULL, -}; - -static struct i2c_client client_template = { - .name = "LM4857", - .driver = &lm4857_i2c_driver, + .probe = lm4857_i2c_probe, + .remove = lm4857_i2c_remove, + .id_table = lm4857_i2c_id, }; static struct platform_device *neo1973_snd_device; @@ -709,6 +678,12 @@ static int __init neo1973_init(void) DBG("Entered %s\n", __func__); + if (!machine_is_neo1973_gta01()) { + printk(KERN_INFO + "Only GTA01 hardware supported by ASoC driver\n"); + return -ENODEV; + } + neo1973_snd_device = platform_device_alloc("soc-audio", -1); if (!neo1973_snd_device) return -ENOMEM; @@ -717,12 +692,15 @@ static int __init neo1973_init(void) neo1973_snd_devdata.dev = &neo1973_snd_device->dev; ret = platform_device_add(neo1973_snd_device); - if (ret) + if (ret) { platform_device_put(neo1973_snd_device); + return ret; + } ret = i2c_add_driver(&lm4857_i2c_driver); + if (ret != 0) - printk(KERN_ERR "can't add i2c driver"); + platform_device_unregister(neo1973_snd_device); return ret; } diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 83f1190..462e635d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -4,8 +4,7 @@ * Copyright 2005 Wolfson Microelectronics PLC. * Copyright 2005 Openedhand Ltd. * - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * with code, comments and ideas from :- * Richard Purdie <richard@openedhand.com> * @@ -340,6 +339,12 @@ static int soc_codec_close(struct snd_pcm_substream *substream) } codec->active--; + /* Muting the DAC suppresses artifacts caused during digital + * shutdown, for example from stopping clocks. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dai_digital_mute(codec_dai, 1); + if (cpu_dai->ops.shutdown) cpu_dai->ops.shutdown(substream); @@ -970,9 +975,29 @@ static ssize_t codec_reg_show(struct device *dev, step = codec->reg_cache_step; count += sprintf(buf, "%s registers\n", codec->name); - for (i = 0; i < codec->reg_cache_size; i += step) - count += sprintf(buf + count, "%2x: %4x\n", i, - codec->read(codec, i)); + for (i = 0; i < codec->reg_cache_size; i += step) { + count += sprintf(buf + count, "%2x: ", i); + if (count >= PAGE_SIZE - 1) + break; + + if (codec->display_register) + count += codec->display_register(codec, buf + count, + PAGE_SIZE - count, i); + else + count += snprintf(buf + count, PAGE_SIZE - count, + "%4x", codec->read(codec, i)); + + if (count >= PAGE_SIZE - 1) + break; + + count += snprintf(buf + count, PAGE_SIZE - count, "\n"); + if (count >= PAGE_SIZE - 1) + break; + } + + /* Truncate count; min() would cause a warning */ + if (count >= PAGE_SIZE) + count = PAGE_SIZE - 1; return count; } @@ -1296,10 +1321,10 @@ int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = e->shift_l == e->shift_r ? 1 : 2; - uinfo->value.enumerated.items = e->mask; + uinfo->value.enumerated.items = e->max; - if (uinfo->value.enumerated.item > e->mask - 1) - uinfo->value.enumerated.item = e->mask - 1; + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, e->texts[uinfo->value.enumerated.item]); return 0; @@ -1322,7 +1347,7 @@ int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned short val, bitmask; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(codec, e->reg); ucontrol->value.enumerated.item[0] @@ -1352,14 +1377,14 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, unsigned short val; unsigned short mask, bitmask; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; - if (ucontrol->value.enumerated.item[0] > e->mask - 1) + if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; val = ucontrol->value.enumerated.item[0] << e->shift_l; mask = (bitmask - 1) << e->shift_l; if (e->shift_l != e->shift_r) { - if (ucontrol->value.enumerated.item[1] > e->mask - 1) + if (ucontrol->value.enumerated.item[1] > e->max - 1) return -EINVAL; val |= ucontrol->value.enumerated.item[1] << e->shift_r; mask |= (bitmask - 1) << e->shift_r; @@ -1386,10 +1411,10 @@ int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol, uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; - uinfo->value.enumerated.items = e->mask; + uinfo->value.enumerated.items = e->max; - if (uinfo->value.enumerated.item > e->mask - 1) - uinfo->value.enumerated.item = e->mask - 1; + if (uinfo->value.enumerated.item > e->max - 1) + uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name, e->texts[uinfo->value.enumerated.item]); return 0; @@ -1434,9 +1459,11 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_ext); int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int max = (kcontrol->private_value >> 16) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + unsigned int shift = mc->min; + unsigned int rshift = mc->rshift; if (max == 1) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1462,13 +1489,15 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw); int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask; @@ -1499,13 +1528,15 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw); int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; unsigned short val, val2, val_mask; val = (ucontrol->value.integer.value[0] & mask); @@ -1537,7 +1568,9 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw); int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int max = (kcontrol->private_value >> 12) & 0xff; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; if (max == 1) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1563,13 +1596,15 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_2r); int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int reg2 = (kcontrol->private_value >> 24) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int max = (kcontrol->private_value >> 12) & 0xff; - int mask = (1<<fls(max))-1; - int invert = (kcontrol->private_value >> 20) & 0x01; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1<<fls(max))-1; + unsigned int invert = mc->invert; ucontrol->value.integer.value[0] = (snd_soc_read(codec, reg) >> shift) & mask; @@ -1598,13 +1633,15 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw_2r); int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int reg2 = (kcontrol->private_value >> 24) & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int max = (kcontrol->private_value >> 12) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 20) & 0x01; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; int err; unsigned short val, val2, val_mask; @@ -1641,8 +1678,10 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw_2r); int snd_soc_info_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - int max = (signed char)((kcontrol->private_value >> 16) & 0xff); - int min = (signed char)((kcontrol->private_value >> 24) & 0xff); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int max = mc->max; + int min = mc->min; uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 2; @@ -1664,9 +1703,11 @@ EXPORT_SYMBOL_GPL(snd_soc_info_volsw_s8); int snd_soc_get_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int min = (signed char)((kcontrol->private_value >> 24) & 0xff); + unsigned int reg = mc->reg; + int min = mc->min; int val = snd_soc_read(codec, reg); ucontrol->value.integer.value[0] = @@ -1689,9 +1730,11 @@ EXPORT_SYMBOL_GPL(snd_soc_get_volsw_s8); int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int min = (signed char)((kcontrol->private_value >> 24) & 0xff); + unsigned int reg = mc->reg; + int min = mc->min; unsigned short val; val = (ucontrol->value.integer.value[0]+min) & 0xff; @@ -1842,7 +1885,7 @@ module_init(snd_soc_init); module_exit(snd_soc_exit); /* Module information */ -MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("ALSA SoC Core"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:soc-audio"); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index f9d100b..efbd0b3 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2,8 +2,7 @@ * soc-dapm.c -- ALSA SoC Dynamic Audio Power Management * * Copyright 2005 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * Author: Liam Girdwood <lrg@slimlogic.co.uk> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -38,6 +37,7 @@ #include <linux/bitops.h> #include <linux/platform_device.h> #include <linux/jiffies.h> +#include <linux/debugfs.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -67,7 +67,9 @@ static int dapm_status = 1; module_param(dapm_status, int, 0); MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries"); -static unsigned int pop_time; +static struct dentry *asoc_debugfs; + +static u32 pop_time; static void pop_wait(void) { @@ -104,10 +106,13 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_switch: case snd_soc_dapm_mixer: { int val; - int reg = w->kcontrols[i].private_value & 0xff; - int shift = (w->kcontrols[i].private_value >> 8) & 0x0f; - int mask = (w->kcontrols[i].private_value >> 16) & 0xff; - int invert = (w->kcontrols[i].private_value >> 24) & 0x01; + struct soc_mixer_control *mc = (struct soc_mixer_control *) + w->kcontrols[i].private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; val = snd_soc_read(w->codec, reg); val = (val >> shift) & mask; @@ -122,13 +127,13 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value; int val, item, bitmask; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(w->codec, e->reg); item = (val >> e->shift_l) & (bitmask - 1); p->connect = 0; - for (i = 0; i < e->mask; i++) { + for (i = 0; i < e->max; i++) { if (!(strcmp(p->name, e->texts[i])) && item == i) p->connect = 1; } @@ -165,7 +170,7 @@ static int dapm_connect_mux(struct snd_soc_codec *codec, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; int i; - for (i = 0; i < e->mask; i++) { + for (i = 0; i < e->max; i++) { if (!(strcmp(control_name, e->texts[i]))) { list_add(&path->list, &codec->dapm_paths); list_add(&path->list_sink, &dest->sources); @@ -247,16 +252,19 @@ static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power) return 0; if (widget->num_kcontrols && k) { - int reg = k->private_value & 0xff; - int shift = (k->private_value >> 8) & 0x0f; - int mask = (k->private_value >> 16) & 0xff; - int invert = (k->private_value >> 24) & 0x01; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)k->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; if (power) { int i; /* power up has happended, increase volume to last level */ if (invert) { - for (i = mask; i > widget->saved_value; i--) + for (i = max; i > widget->saved_value; i--) snd_soc_update_bits(widget->codec, reg, mask, i); } else { for (i = 0; i < widget->saved_value; i++) @@ -684,7 +692,7 @@ static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action) /* test and update the power status of a mux widget */ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, struct snd_kcontrol *kcontrol, int mask, - int val, struct soc_enum* e) + int mux, int val, struct soc_enum *e) { struct snd_soc_dapm_path *path; int found = 0; @@ -700,12 +708,12 @@ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, if (path->kcontrol != kcontrol) continue; - if (!path->name || ! e->texts[val]) + if (!path->name || !e->texts[mux]) continue; found = 1; /* we now need to match the string in the enum to the path */ - if (!(strcmp(path->name, e->texts[val]))) + if (!(strcmp(path->name, e->texts[mux]))) path->connect = 1; /* new connection */ else path->connect = 0; /* old connection must be powered down */ @@ -811,51 +819,35 @@ static ssize_t dapm_widget_show(struct device *dev, static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL); -/* pop/click delay times */ -static ssize_t dapm_pop_time_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return sprintf(buf, "%d\n", pop_time); -} - -static ssize_t dapm_pop_time_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) - -{ - unsigned long val; - - if (strict_strtoul(buf, 10, &val) >= 0) - pop_time = val; - else - printk(KERN_ERR "Unable to parse pop_time setting\n"); - - return count; -} - -static DEVICE_ATTR(dapm_pop_time, 0744, dapm_pop_time_show, - dapm_pop_time_store); - int snd_soc_dapm_sys_add(struct device *dev) { int ret = 0; - if (dapm_status) { - ret = device_create_file(dev, &dev_attr_dapm_widget); + if (!dapm_status) + return 0; - if (ret == 0) - ret = device_create_file(dev, &dev_attr_dapm_pop_time); - } + ret = device_create_file(dev, &dev_attr_dapm_widget); + if (ret != 0) + return ret; - return ret; + asoc_debugfs = debugfs_create_dir("asoc", NULL); + if (!IS_ERR(asoc_debugfs)) + debugfs_create_u32("dapm_pop_time", 0744, asoc_debugfs, + &pop_time); + else + asoc_debugfs = NULL; + + return 0; } static void snd_soc_dapm_sys_remove(struct device *dev) { if (dapm_status) { - device_remove_file(dev, &dev_attr_dapm_pop_time); device_remove_file(dev, &dev_attr_dapm_widget); } + + if (asoc_debugfs) + debugfs_remove_recursive(asoc_debugfs); } /* free all dapm widgets and resources */ @@ -1133,12 +1125,14 @@ int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int invert = (kcontrol->private_value >> 24) & 0x01; - int mask = (1 << fls(max)) - 1; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + unsigned int invert = mc->invert; + unsigned int mask = (1 << fls(max)) - 1; /* return the saved value if we are powered down */ if (widget->id == snd_soc_dapm_pga && !widget->power) { @@ -1176,12 +1170,14 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); - int reg = kcontrol->private_value & 0xff; - int shift = (kcontrol->private_value >> 8) & 0x0f; - int rshift = (kcontrol->private_value >> 12) & 0x0f; - int max = (kcontrol->private_value >> 16) & 0xff; - int mask = (1 << fls(max)) - 1; - int invert = (kcontrol->private_value >> 24) & 0x01; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int rshift = mc->rshift; + int max = mc->max; + unsigned int mask = (1 << fls(max)) - 1; + unsigned int invert = mc->invert; unsigned short val, val2, val_mask; int ret; @@ -1248,7 +1244,7 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned short val, bitmask; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; val = snd_soc_read(widget->codec, e->reg); ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1); @@ -1278,15 +1274,15 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, unsigned short mask, bitmask; int ret = 0; - for (bitmask = 1; bitmask < e->mask; bitmask <<= 1) + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; - if (ucontrol->value.enumerated.item[0] > e->mask - 1) + if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; mux = ucontrol->value.enumerated.item[0]; val = mux << e->shift_l; mask = (bitmask - 1) << e->shift_l; if (e->shift_l != e->shift_r) { - if (ucontrol->value.enumerated.item[1] > e->mask - 1) + if (ucontrol->value.enumerated.item[1] > e->max - 1) return -EINVAL; val |= ucontrol->value.enumerated.item[1] << e->shift_r; mask |= (bitmask - 1) << e->shift_r; @@ -1294,7 +1290,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, mutex_lock(&widget->codec->mutex); widget->value = val; - dapm_mux_update_power(widget, kcontrol, mask, mux, e); + dapm_mux_update_power(widget, kcontrol, mask, mux, val, e); if (widget->event) { if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { ret = widget->event(widget, @@ -1487,6 +1483,26 @@ int snd_soc_dapm_disable_pin(struct snd_soc_codec *codec, char *pin) EXPORT_SYMBOL_GPL(snd_soc_dapm_disable_pin); /** + * snd_soc_dapm_nc_pin - permanently disable pin. + * @codec: SoC codec + * @pin: pin name + * + * Marks the specified pin as being not connected, disabling it along + * any parent or child widgets. At present this is identical to + * snd_soc_dapm_disable_pin() but in future it will be extended to do + * additional things such as disabling controls which only affect + * paths through the pin. + * + * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to + * do any widget power switching. + */ +int snd_soc_dapm_nc_pin(struct snd_soc_codec *codec, char *pin) +{ + return snd_soc_dapm_set_pin(codec, pin, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_nc_pin); + +/** * snd_soc_dapm_get_pin_status - get audio pin status * @codec: audio codec * @pin: audio signal pin endpoint (or start point) @@ -1524,6 +1540,6 @@ void snd_soc_dapm_free(struct snd_soc_device *socdev) EXPORT_SYMBOL_GPL(snd_soc_dapm_free); /* Module information */ -MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); MODULE_LICENSE("GPL"); |