diff options
54 files changed, 3874 insertions, 977 deletions
diff --git a/arch/arm/mach-pxa/include/mach/regs-ssp.h b/arch/arm/mach-pxa/include/mach/regs-ssp.h index 3c04cde2cf1f..f43905a27737 100644 --- a/arch/arm/mach-pxa/include/mach/regs-ssp.h +++ b/arch/arm/mach-pxa/include/mach/regs-ssp.h @@ -47,7 +47,7 @@ #define SSCR0_TUM (1 << 23) /* Transmit FIFO underrun interrupt mask */ #define SSCR0_FRDC (0x07000000) /* Frame rate divider control (mask) */ #define SSCR0_SlotsPerFrm(x) (((x) - 1) << 24) /* Time slots per frame [1..8] */ -#define SSCR0_ADC (1 << 30) /* Audio clock select */ +#define SSCR0_ACS (1 << 30) /* Audio clock select */ #define SSCR0_MOD (1 << 31) /* Mode (normal or network) */ #endif @@ -106,6 +106,11 @@ #define SSSR_TINT (1 << 19) /* Receiver Time-out Interrupt */ #define SSSR_PINT (1 << 18) /* Peripheral Trailing Byte Interrupt */ +#if defined(CONFIG_PXA3xx) +#define SSPSP_EDMYSTOP(x) ((x) << 28) /* Extended Dummy Stop */ +#define SSPSP_EDMYSTRT(x) ((x) << 26) /* Extended Dummy Start */ +#endif + #define SSPSP_FSRT (1 << 25) /* Frame Sync Relative Timing */ #define SSPSP_DMYSTOP(x) ((x) << 23) /* Dummy Stop */ #define SSPSP_SFRMWDTH(x) ((x) << 16) /* Serial Frame Width */ diff --git a/arch/arm/mach-s3c2410/dma.c b/arch/arm/mach-s3c2410/dma.c index 552b4c778fdc..440c014e24b3 100644 --- a/arch/arm/mach-s3c2410/dma.c +++ b/arch/arm/mach-s3c2410/dma.c @@ -28,7 +28,7 @@ #include <mach/regs-mem.h> #include <mach/regs-lcd.h> #include <mach/regs-sdi.h> -#include <asm/plat-s3c24xx/regs-iis.h> +#include <plat/regs-iis.h> #include <plat/regs-spi.h> static struct s3c24xx_dma_map __initdata s3c2410_dma_mappings[] = { diff --git a/arch/arm/mach-s3c2410/include/mach/io.h b/arch/arm/mach-s3c2410/include/mach/io.h index 9813dbf2ae4f..c477771c0924 100644 --- a/arch/arm/mach-s3c2410/include/mach/io.h +++ b/arch/arm/mach-s3c2410/include/mach/io.h @@ -9,7 +9,7 @@ #ifndef __ASM_ARM_ARCH_IO_H #define __ASM_ARM_ARCH_IO_H -#include <mach/hardware.h> +#include <mach/map.h> #define IO_SPACE_LIMIT 0xffffffff diff --git a/arch/arm/mach-s3c2412/dma.c b/arch/arm/mach-s3c2412/dma.c index 919856c9433f..9e3478506c6f 100644 --- a/arch/arm/mach-s3c2412/dma.c +++ b/arch/arm/mach-s3c2412/dma.c @@ -29,8 +29,8 @@ #include <mach/regs-mem.h> #include <mach/regs-lcd.h> #include <mach/regs-sdi.h> -#include <asm/plat-s3c24xx/regs-s3c2412-iis.h> -#include <asm/plat-s3c24xx/regs-iis.h> +#include <plat/regs-s3c2412-iis.h> +#include <plat/regs-iis.h> #include <plat/regs-spi.h> #define MAP(x) { (x)| DMA_CH_VALID, (x)| DMA_CH_VALID, (x)| DMA_CH_VALID, (x)| DMA_CH_VALID } diff --git a/arch/arm/mach-s3c2440/dma.c b/arch/arm/mach-s3c2440/dma.c index 5b5ee0b8f4e0..69b6cf34df47 100644 --- a/arch/arm/mach-s3c2440/dma.c +++ b/arch/arm/mach-s3c2440/dma.c @@ -28,7 +28,7 @@ #include <mach/regs-mem.h> #include <mach/regs-lcd.h> #include <mach/regs-sdi.h> -#include <asm/plat-s3c24xx/regs-iis.h> +#include <plat/regs-iis.h> #include <plat/regs-spi.h> static struct s3c24xx_dma_map __initdata s3c2440_dma_mappings[] = { diff --git a/arch/arm/mach-s3c2443/dma.c b/arch/arm/mach-s3c2443/dma.c index 2a58a4d5aa5a..8430e5829186 100644 --- a/arch/arm/mach-s3c2443/dma.c +++ b/arch/arm/mach-s3c2443/dma.c @@ -29,7 +29,7 @@ #include <mach/regs-mem.h> #include <mach/regs-lcd.h> #include <mach/regs-sdi.h> -#include <asm/plat-s3c24xx/regs-iis.h> +#include <plat/regs-iis.h> #include <plat/regs-spi.h> #define MAP(x) { \ diff --git a/arch/arm/mach-s3c2410/include/mach/audio.h b/arch/arm/plat-s3c/include/plat/audio.h index de0e8da48bc3..de0e8da48bc3 100644 --- a/arch/arm/mach-s3c2410/include/mach/audio.h +++ b/arch/arm/plat-s3c/include/plat/audio.h diff --git a/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h b/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h index 25d4058bcfed..0fad7571030e 100644 --- a/include/asm-arm/plat-s3c24xx/regs-s3c2412-iis.h +++ b/arch/arm/plat-s3c/include/plat/regs-s3c2412-iis.h @@ -33,6 +33,9 @@ #define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1) #define S3C2412_IISCON_IIS_ACTIVE (1 << 0) +#define S3C64XX_IISMOD_IMS_PCLK (0 << 10) +#define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10) + #define S3C2412_IISMOD_MASTER_INTERNAL (0 << 10) #define S3C2412_IISMOD_MASTER_EXTERNAL (1 << 10) #define S3C2412_IISMOD_SLAVE (2 << 10) @@ -44,8 +47,8 @@ #define S3C2412_IISMOD_LR_LLOW (0 << 7) #define S3C2412_IISMOD_LR_RLOW (1 << 7) #define S3C2412_IISMOD_SDF_IIS (0 << 5) -#define S3C2412_IISMOD_SDF_MSB (0 << 5) -#define S3C2412_IISMOD_SDF_LSB (0 << 5) +#define S3C2412_IISMOD_SDF_MSB (1 << 5) +#define S3C2412_IISMOD_SDF_LSB (2 << 5) #define S3C2412_IISMOD_SDF_MASK (3 << 5) #define S3C2412_IISMOD_RCLK_256FS (0 << 3) #define S3C2412_IISMOD_RCLK_512FS (1 << 3) diff --git a/arch/arm/plat-s3c24xx/clock-dclk.c b/arch/arm/plat-s3c24xx/clock-dclk.c index 5b75a797b5ab..35219dcf9f08 100644 --- a/arch/arm/plat-s3c24xx/clock-dclk.c +++ b/arch/arm/plat-s3c24xx/clock-dclk.c @@ -18,6 +18,7 @@ #include <mach/regs-clock.h> #include <mach/regs-gpio.h> +#include <mach/hardware.h> #include <plat/clock.h> #include <plat/cpu.h> diff --git a/include/asm-arm/plat-s3c24xx/regs-iis.h b/arch/arm/plat-s3c24xx/include/plat/regs-iis.h index a6f1d5df13b4..a6f1d5df13b4 100644 --- a/include/asm-arm/plat-s3c24xx/regs-iis.h +++ b/arch/arm/plat-s3c24xx/include/plat/regs-iis.h diff --git a/include/linux/mfd/wm8400-audio.h b/include/linux/mfd/wm8400-audio.h index b6640e018046..e06ed3eb1d0a 100644 --- a/include/linux/mfd/wm8400-audio.h +++ b/include/linux/mfd/wm8400-audio.h @@ -1181,6 +1181,7 @@ #define WM8400_FLL_OUTDIV_SHIFT 0 /* FLL_OUTDIV - [2:0] */ #define WM8400_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [2:0] */ +struct wm8400; void wm8400_reset_codec_reg_cache(struct wm8400 *wm8400); #endif diff --git a/include/sound/soc.h b/include/sound/soc.h index 0e7735264169..a40bc6f316fc 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -16,6 +16,8 @@ #include <linux/platform_device.h> #include <linux/types.h> #include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/control.h> @@ -168,6 +170,9 @@ struct soc_enum; struct snd_soc_ac97_ops; struct snd_soc_jack; struct snd_soc_jack_pin; +#ifdef CONFIG_GPIOLIB +struct snd_soc_jack_gpio; +#endif typedef int (*hw_write_t)(void *,const char* ,int); typedef int (*hw_read_t)(void *,char* ,int); @@ -194,6 +199,12 @@ int snd_soc_jack_new(struct snd_soc_card *card, const char *id, int type, void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask); int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, struct snd_soc_jack_pin *pins); +#ifdef CONFIG_GPIOLIB +int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_gpio *gpios); +void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_gpio *gpios); +#endif /* codec IO */ #define snd_soc_read(codec, reg) codec->read(codec, reg) @@ -264,6 +275,27 @@ struct snd_soc_jack_pin { bool invert; }; +/** + * struct snd_soc_jack_gpio - Describes a gpio pin for jack detection + * + * @gpio: gpio number + * @name: gpio name + * @report: value to report when jack detected + * @invert: report presence in low state + * @debouce_time: debouce time in ms + */ +#ifdef CONFIG_GPIOLIB +struct snd_soc_jack_gpio { + unsigned int gpio; + const char *name; + int report; + int invert; + int debounce_time; + struct snd_soc_jack *jack; + struct work_struct work; +}; +#endif + struct snd_soc_jack { struct snd_jack *jack; struct snd_soc_card *card; diff --git a/sound/soc/atmel/playpaq_wm8510.c b/sound/soc/atmel/playpaq_wm8510.c index 43dd8cee83c6..70657534e6b1 100644 --- a/sound/soc/atmel/playpaq_wm8510.c +++ b/sound/soc/atmel/playpaq_wm8510.c @@ -164,38 +164,38 @@ static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream, */ switch (params_rate(params)) { case 48000: - pll_out = 12288000; - mclk_div = WM8510_MCLKDIV_1; + pll_out = 24576000; + mclk_div = WM8510_MCLKDIV_2; bclk = WM8510_BCLKDIV_8; break; case 44100: - pll_out = 11289600; - mclk_div = WM8510_MCLKDIV_1; + pll_out = 22579200; + mclk_div = WM8510_MCLKDIV_2; bclk = WM8510_BCLKDIV_8; break; case 22050: - pll_out = 11289600; - mclk_div = WM8510_MCLKDIV_2; + pll_out = 22579200; + mclk_div = WM8510_MCLKDIV_4; bclk = WM8510_BCLKDIV_8; break; case 16000: - pll_out = 12288000; - mclk_div = WM8510_MCLKDIV_3; + pll_out = 24576000; + mclk_div = WM8510_MCLKDIV_6; bclk = WM8510_BCLKDIV_8; break; case 11025: - pll_out = 11289600; - mclk_div = WM8510_MCLKDIV_4; + pll_out = 22579200; + mclk_div = WM8510_MCLKDIV_8; bclk = WM8510_BCLKDIV_8; break; case 8000: - pll_out = 12288000; - mclk_div = WM8510_MCLKDIV_6; + pll_out = 24576000; + mclk_div = WM8510_MCLKDIV_12; bclk = WM8510_BCLKDIV_8; break; diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c index 5885702c78ff..8a935f2d1767 100644 --- a/sound/soc/blackfin/bf5xx-ac97.c +++ b/sound/soc/blackfin/bf5xx-ac97.c @@ -357,8 +357,8 @@ sport_config_err: sport_err: #ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM); -#endif gpio_err: +#endif peripheral_free_list(sport_req[sport_num]); peripheral_err: free_page((unsigned long)cmd_count); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 628a591c728f..b6c7f7a01cb0 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -14,6 +14,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_AD73311 if I2C + select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_PCM3008 @@ -25,6 +26,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_UDA134X select SND_SOC_UDA1380 if I2C select SND_SOC_WM8350 if MFD_WM8350 + select SND_SOC_WM8400 if MFD_WM8400 select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8580 if I2C select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI @@ -60,6 +62,9 @@ config SND_SOC_AD1980 config SND_SOC_AD73311 tristate +config SND_SOC_AK4104 + tristate + config SND_SOC_AK4535 tristate @@ -106,6 +111,9 @@ config SND_SOC_UDA1380 config SND_SOC_WM8350 tristate +config SND_SOC_WM8400 + tristate + config SND_SOC_WM8510 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3664cdc300b2..030d2454725f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,6 +1,7 @@ snd-soc-ac97-objs := ac97.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o +snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-cs4270-objs := cs4270.o snd-soc-l3-objs := l3.o @@ -13,6 +14,7 @@ snd-soc-twl4030-objs := twl4030.o snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o snd-soc-wm8350-objs := wm8350.o +snd-soc-wm8400-objs := wm8400.o snd-soc-wm8510-objs := wm8510.o snd-soc-wm8580-objs := wm8580.o snd-soc-wm8728-objs := wm8728.o @@ -30,6 +32,7 @@ snd-soc-wm9713-objs := wm9713.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o @@ -42,6 +45,7 @@ obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o +obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o diff --git a/sound/soc/codecs/ad73311.h b/sound/soc/codecs/ad73311.h index 507ce0c30edf..569573d2d4d7 100644 --- a/sound/soc/codecs/ad73311.h +++ b/sound/soc/codecs/ad73311.h @@ -70,7 +70,7 @@ #define REGD_IGS(x) (x & 0x7) #define REGD_RMOD (1 << 3) #define REGD_OGS(x) ((x & 0x7) << 4) -#define REGD_MUTE (x << 7) +#define REGD_MUTE (1 << 7) /* Control register E */ #define CTRL_REG_E (4 << 8) diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c new file mode 100644 index 000000000000..4d47bc4f7428 --- /dev/null +++ b/sound/soc/codecs/ak4104.c @@ -0,0 +1,365 @@ +/* + * AK4104 ALSA SoC (ASoC) driver + * + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + * + * 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 <sound/core.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <linux/spi/spi.h> +#include <sound/asoundef.h> + +#include "ak4104.h" + +/* AK4104 registers addresses */ +#define AK4104_REG_CONTROL1 0x00 +#define AK4104_REG_RESERVED 0x01 +#define AK4104_REG_CONTROL2 0x02 +#define AK4104_REG_TX 0x03 +#define AK4104_REG_CHN_STATUS(x) ((x) + 0x04) +#define AK4104_NUM_REGS 10 + +#define AK4104_REG_MASK 0x1f +#define AK4104_READ 0xc0 +#define AK4104_WRITE 0xe0 +#define AK4104_RESERVED_VAL 0x5b + +/* Bit masks for AK4104 registers */ +#define AK4104_CONTROL1_RSTN (1 << 0) +#define AK4104_CONTROL1_PW (1 << 1) +#define AK4104_CONTROL1_DIF0 (1 << 2) +#define AK4104_CONTROL1_DIF1 (1 << 3) + +#define AK4104_CONTROL2_SEL0 (1 << 0) +#define AK4104_CONTROL2_SEL1 (1 << 1) +#define AK4104_CONTROL2_MODE (1 << 2) + +#define AK4104_TX_TXE (1 << 0) +#define AK4104_TX_V (1 << 1) + +#define DRV_NAME "ak4104" + +struct ak4104_private { + struct snd_soc_codec codec; + u8 reg_cache[AK4104_NUM_REGS]; +}; + +static int ak4104_fill_cache(struct snd_soc_codec *codec) +{ + int i; + u8 *reg_cache = codec->reg_cache; + struct spi_device *spi = codec->control_data; + + for (i = 0; i < codec->reg_cache_size; i++) { + int ret = spi_w8r8(spi, i | AK4104_READ); + if (ret < 0) { + dev_err(&spi->dev, "SPI write failure\n"); + return ret; + } + + reg_cache[i] = ret; + } + + return 0; +} + +static unsigned int ak4104_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *reg_cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + return reg_cache[reg]; +} + +static int ak4104_spi_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 *cache = codec->reg_cache; + struct spi_device *spi = codec->control_data; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + reg &= AK4104_REG_MASK; + reg |= AK4104_WRITE; + + /* only write to the hardware if value has changed */ + if (cache[reg] != value) { + u8 tmp[2] = { reg, value }; + if (spi_write(spi, tmp, sizeof(tmp))) { + dev_err(&spi->dev, "SPI write failed\n"); + return -EIO; + } + + cache[reg] = value; + } + + return 0; +} + +static int ak4104_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int format) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int val = 0; + + val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1); + if (val < 0) + return val; + + val &= ~(AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1); + + /* set DAI format */ + switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + val |= AK4104_CONTROL1_DIF0; + break; + case SND_SOC_DAIFMT_I2S: + val |= AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1; + break; + default: + dev_err(codec->dev, "invalid dai format\n"); + return -EINVAL; + } + + /* This device can only be slave */ + if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + + return ak4104_spi_write(codec, AK4104_REG_CONTROL1, val); +} + +static int ak4104_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + int val = 0; + + /* set the IEC958 bits: consumer mode, no copyright bit */ + val |= IEC958_AES0_CON_NOT_COPYRIGHT; + ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(0), val); + + val = 0; + + switch (params_rate(params)) { + case 44100: + val |= IEC958_AES3_CON_FS_44100; + break; + case 48000: + val |= IEC958_AES3_CON_FS_48000; + break; + case 32000: + val |= IEC958_AES3_CON_FS_32000; + break; + default: + dev_err(codec->dev, "unsupported sampling rate\n"); + return -EINVAL; + } + + return ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(3), val); +} + +static struct snd_soc_dai_ops ak4101_dai_ops = { + .hw_params = ak4104_hw_params, + .set_fmt = ak4104_set_dai_fmt, +}; + +struct snd_soc_dai ak4104_dai = { + .name = DRV_NAME, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_32000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE + }, + .ops = &ak4101_dai_ops, +}; + +static struct snd_soc_codec *ak4104_codec; + +static int ak4104_spi_probe(struct spi_device *spi) +{ + struct snd_soc_codec *codec; + struct ak4104_private *ak4104; + int ret, val; + + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + ret = spi_setup(spi); + if (ret < 0) + return ret; + + ak4104 = kzalloc(sizeof(struct ak4104_private), GFP_KERNEL); + if (!ak4104) { + dev_err(&spi->dev, "could not allocate codec\n"); + return -ENOMEM; + } + + codec = &ak4104->codec; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->dev = &spi->dev; + codec->name = DRV_NAME; + codec->owner = THIS_MODULE; + codec->dai = &ak4104_dai; + codec->num_dai = 1; + codec->private_data = ak4104; + codec->control_data = spi; + codec->reg_cache = ak4104->reg_cache; + codec->reg_cache_size = AK4104_NUM_REGS; + + /* read all regs and fill the cache */ + ret = ak4104_fill_cache(codec); + if (ret < 0) { + dev_err(&spi->dev, "failed to fill register cache\n"); + return ret; + } + + /* read the 'reserved' register - according to the datasheet, it + * should contain 0x5b. Not a good way to verify the presence of + * the device, but there is no hardware ID register. */ + if (ak4104_read_reg_cache(codec, AK4104_REG_RESERVED) != + AK4104_RESERVED_VAL) { + ret = -ENODEV; + goto error_free_codec; + } + + /* set power-up and non-reset bits */ + val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1); + val |= AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN; + ret = ak4104_spi_write(codec, AK4104_REG_CONTROL1, val); + if (ret < 0) + goto error_free_codec; + + /* enable transmitter */ + val = ak4104_read_reg_cache(codec, AK4104_REG_TX); + val |= AK4104_TX_TXE; + ret = ak4104_spi_write(codec, AK4104_REG_TX, val); + if (ret < 0) + goto error_free_codec; + + ak4104_codec = codec; + ret = snd_soc_register_dai(&ak4104_dai); + if (ret < 0) { + dev_err(&spi->dev, "failed to register DAI\n"); + goto error_free_codec; + } + + spi_set_drvdata(spi, ak4104); + dev_info(&spi->dev, "SPI device initialized\n"); + return 0; + +error_free_codec: + kfree(ak4104); + ak4104_dai.dev = NULL; + return ret; +} + +static int __devexit ak4104_spi_remove(struct spi_device *spi) +{ + int ret, val; + struct ak4104_private *ak4104 = spi_get_drvdata(spi); + + val = ak4104_read_reg_cache(&ak4104->codec, AK4104_REG_CONTROL1); + if (val < 0) + return val; + + /* clear power-up and non-reset bits */ + val &= ~(AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN); + ret = ak4104_spi_write(&ak4104->codec, AK4104_REG_CONTROL1, val); + if (ret < 0) + return ret; + + ak4104_codec = NULL; + kfree(ak4104); + return 0; +} + +static int ak4104_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = ak4104_codec; + int ret; + + /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */ + socdev->card->codec = codec; + + /* Register PCMs */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms\n"); + return ret; + } + + /* Register the socdev */ + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + snd_soc_free_pcms(socdev); + return ret; + } + + return 0; +} + +static int ak4104_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 soc_codec_device_ak4104 = { + .probe = ak4104_probe, + .remove = ak4104_remove +}; +EXPORT_SYMBOL_GPL(soc_codec_device_ak4104); + +static struct spi_driver ak4104_spi_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ak4104_spi_probe, + .remove = __devexit_p(ak4104_spi_remove), +}; + +static int __init ak4104_init(void) +{ + pr_info("Asahi Kasei AK4104 ALSA SoC Codec Driver\n"); + return spi_register_driver(&ak4104_spi_driver); +} +module_init(ak4104_init); + +static void __exit ak4104_exit(void) +{ + spi_unregister_driver(&ak4104_spi_driver); +} +module_exit(ak4104_exit); + +MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); +MODULE_DESCRIPTION("Asahi Kasei AK4104 ALSA SoC driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/codecs/ak4104.h b/sound/soc/codecs/ak4104.h new file mode 100644 index 000000000000..eb88fe7e4def --- /dev/null +++ b/sound/soc/codecs/ak4104.h @@ -0,0 +1,7 @@ +#ifndef _AK4104_H +#define _AK4104_H + +extern struct snd_soc_dai ak4104_dai; +extern struct snd_soc_codec_device soc_codec_device_ak4104; + +#endif diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 7ae3d6520e3f..2137670c9b78 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -12,14 +12,13 @@ * * Current features/limitations: * - * 1) Software mode is supported. Stand-alone mode is not supported. - * 2) Only I2C is supported, not SPI - * 3) Only Master mode is supported, not Slave. - * 4) The machine driver's 'startup' function must call - * cs4270_set_dai_sysclk() with the value of MCLK. - * 5) Only I2S and left-justified modes are supported - * 6) Power management is not supported - * 7) The only supported control is volume and hardware mute (if enabled) + * - Software mode is supported. Stand-alone mode is not supported. + * - Only I2C is supported, not SPI + * - Support for master and slave mode + * - The machine driver's 'startup' function must call + * cs4270_set_dai_sysclk() with the value of MCLK. + * - Only I2S and left-justified modes are supported + * - Power management is not supported */ #include <linux/module.h> diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 535d8ce2c328..86bb15cc82ce 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -584,12 +584,11 @@ static int headsetl_event(struct snd_soc_dapm_widget *w, /* Save the current volume */ hs_gain = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_GAIN_SET); + hs_pop = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_POPN_SET); switch (event) { case SND_SOC_DAPM_POST_PMU: /* Do the anti-pop/bias ramp enable according to the TRM */ - hs_pop = TWL4030_RAMP_DELAY_645MS; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); hs_pop |= TWL4030_VMID_EN; twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); /* Is this needed? Can we just use whatever gain here? */ @@ -603,8 +602,6 @@ static int headsetl_event(struct snd_soc_dapm_widget *w, break; case SND_SOC_DAPM_POST_PMD: /* Do the anti-pop/bias ramp disable according to the TRM */ - hs_pop = twl4030_read_reg_cache(w->codec, - TWL4030_REG_HS_POPN_SET); hs_pop &= ~TWL4030_RAMP_EN; twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); /* Bypass the reg_cache to mute the headset */ @@ -847,6 +844,17 @@ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0); */ static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0); +static const char *twl4030_rampdelay_texts[] = { + "27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms", + "437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms", + "3495/2581/1748 ms" +}; + +static const struct soc_enum twl4030_rampdelay_enum = + SOC_ENUM_SINGLE(TWL4030_REG_HS_POPN_SET, 2, + ARRAY_SIZE(twl4030_rampdelay_texts), + twl4030_rampdelay_texts); + static const struct snd_kcontrol_new twl4030_snd_controls[] = { /* Common playback gain controls */ SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume", @@ -901,6 +909,8 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN, 0, 3, 5, 0, input_gain_tlv), + + SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum), }; static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index cafa7684c0e7..5b21594e0e58 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -25,6 +25,7 @@ #include <linux/ioctl.h> #include <linux/delay.h> #include <linux/i2c.h> +#include <linux/workqueue.h> #include <sound/core.h> #include <sound/control.h> #include <sound/initval.h> @@ -35,7 +36,8 @@ #include "uda1380.h" -#define UDA1380_VERSION "0.6" +static struct work_struct uda1380_work; +static struct snd_soc_codec *uda1380_codec; /* * uda1380 register cache @@ -52,6 +54,8 @@ static const u16 uda1380_reg[UDA1380_CACHEREGNUM] = { 0x0000, 0x8000, 0x0002, 0x0000, }; +static unsigned long uda1380_cache_dirty; + /* * read uda1380 register cache */ @@ -73,8 +77,11 @@ static inline void uda1380_write_reg_cache(struct snd_soc_codec *codec, u16 reg, unsigned int value) { u16 *cache = codec->reg_cache; + if (reg >= UDA1380_CACHEREGNUM) return; + if ((reg >= 0x10) && (cache[reg] != value)) + set_bit(reg - 0x10, &uda1380_cache_dirty); cache[reg] = value; } @@ -113,6 +120,8 @@ static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, (data[0]<<8) | data[1]); return -EIO; } + if (reg >= 0x10) + clear_bit(reg - 0x10, &uda1380_cache_dirty); return 0; } else return -EIO; @@ -120,6 +129,20 @@ static int uda1380_write(struct snd_soc_codec *codec, unsigned int reg, #define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0) +static void uda1380_flush_work(struct work_struct *work) +{ + int bit, reg; + + for_each_bit(bit, &uda1380_cache_dirty, UDA1380_CACHEREGNUM - 0x10) { + reg = 0x10 + bit; + pr_debug("uda1380: flush reg %x val %x:\n", reg, + uda1380_read_reg_cache(uda1380_codec, reg)); + uda1380_write(uda1380_codec, reg, + uda1380_read_reg_cache(uda1380_codec, reg)); + clear_bit(bit, &uda1380_cache_dirty); + } +} + /* declarations of ALSA reg_elem_REAL controls */ static const char *uda1380_deemp[] = { "None", @@ -254,7 +277,6 @@ static const struct snd_kcontrol_new uda1380_snd_controls[] = { SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */ SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */ SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */ - SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */ SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */ SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */ SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */ @@ -377,8 +399,9 @@ static int uda1380_set_dai_fmt_both(struct snd_soc_dai *codec_dai, iface |= R01_SFORI_MSB | R01_SFORO_MSB; } - if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) - iface |= R01_SIM; + /* DATAI is slave only, so in single-link mode, this has to be slave */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; uda1380_write(codec, UDA1380_IFACE, iface); @@ -406,6 +429,10 @@ static int uda1380_set_dai_fmt_playback(struct snd_soc_dai *codec_dai, iface |= R01_SFORI_MSB; } + /* DATAI is slave only, so this has to be slave */ + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS) + return -EINVAL; + uda1380_write(codec, UDA1380_IFACE, iface); return 0; @@ -440,41 +467,28 @@ static int uda1380_set_dai_fmt_capture(struct snd_soc_dai *codec_dai, return 0; } -/* - * Flush reg cache - * We can only write the interpolator and decimator registers - * when the DAI is being clocked by the CPU DAI. It's up to the - * machine and cpu DAI driver to do this before we are called. - */ -static int uda1380_pcm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - int reg, reg_start, reg_end, clk; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - reg_start = UDA1380_MVOL; - reg_end = UDA1380_MIXER; - } else { - reg_start = UDA1380_DEC; - reg_end = UDA1380_AGC; - } - - /* FIXME disable DAC_CLK */ - clk = uda1380_read_reg_cache(codec, UDA1380_CLK); - uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK); - - for (reg = reg_start; reg <= reg_end; reg++) { - pr_debug("uda1380: flush reg %x val %x:", reg, - uda1380_read_reg_cache(codec, reg)); - uda1380_write(codec, reg, uda1380_read_reg_cache(codec, reg)); + int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + uda1380_write_reg_cache(codec, UDA1380_MIXER, + mixer & ~R14_SILENCE); + schedule_work(&uda1380_work); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + uda1380_write_reg_cache(codec, UDA1380_MIXER, + mixer | R14_SILENCE); + schedule_work(&uda1380_work); + break; } - - /* FIXME restore DAC_CLK */ - uda1380_write(codec, UDA1380_CLK, clk); - return 0; } @@ -540,24 +554,6 @@ static void uda1380_pcm_shutdown(struct snd_pcm_substream *substream, uda1380_write(codec, UDA1380_CLK, clk); } -static int uda1380_mute(struct snd_soc_dai *codec_dai, int mute) -{ - struct snd_soc_codec *codec = codec_dai->codec; - u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM; - - /* FIXME: mute(codec,0) is called when the magician clock is already - * set to WSPLL, but for some unknown reason writing to interpolator - * registers works only when clocked by SYSCLK */ - u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK); - uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk); - if (mute) - uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM); - else - uda1380_write(codec, UDA1380_DEEMP, mute_reg); - uda1380_write(codec, UDA1380_CLK, clk); - return 0; -} - static int uda1380_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -586,23 +582,21 @@ static int uda1380_set_bias_level(struct snd_soc_codec *codec, static struct snd_soc_dai_ops uda1380_dai_ops = { .hw_params = uda1380_pcm_hw_params, .shutdown = uda1380_pcm_shutdown, - .prepare = uda1380_pcm_prepare, - .digital_mute = uda1380_mute, + .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_both, }; static struct snd_soc_dai_ops uda1380_dai_ops_playback = { .hw_params = uda1380_pcm_hw_params, .shutdown = uda1380_pcm_shutdown, - .prepare = uda1380_pcm_prepare, - .digital_mute = uda1380_mute, + .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_playback, }; static struct snd_soc_dai_ops uda1380_dai_ops_capture = { .hw_params = uda1380_pcm_hw_params, .shutdown = uda1380_pcm_shutdown, - .prepare = uda1380_pcm_prepare, + .trigger = uda1380_trigger, .set_fmt = uda1380_set_dai_fmt_capture, }; @@ -700,6 +694,9 @@ static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) codec->reg_cache_step = 1; uda1380_reset(codec); + uda1380_codec = codec; + INIT_WORK(&uda1380_work, uda1380_flush_work); + /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -832,8 +829,6 @@ static int uda1380_probe(struct platform_device *pdev) struct snd_soc_codec *codec; int ret; - pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION); - setup = socdev->codec_data; codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c new file mode 100644 index 000000000000..4e1cefff8483 --- /dev/null +++ b/sound/soc/codecs/wm8400.c @@ -0,0 +1,1481 @@ +/* + * wm8400.c -- WM8400 ALSA Soc Audio driver + * + * Copyright 2008, 2009 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. + * + */ + +#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/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/wm8400-audio.h> +#include <linux/mfd/wm8400-private.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 "wm8400.h" + +/* Fake register for internal state */ +#define WM8400_INTDRIVBITS (WM8400_REGISTER_COUNT + 1) +#define WM8400_INMIXL_PWR 0 +#define WM8400_AINLMUX_PWR 1 +#define WM8400_INMIXR_PWR 2 +#define WM8400_AINRMUX_PWR 3 + +static struct regulator_bulk_data power[] = { + { + .supply = "I2S1VDD", + }, + { + .supply = "I2S2VDD", + }, + { + .supply = "DCVDD", + }, + { + .supply = "FLLVDD", + }, + { + .supply = "HPVDD", + }, + { + .supply = "SPKVDD", + }, +}; + +/* codec private data */ +struct wm8400_priv { + struct snd_soc_codec codec; + struct wm8400 *wm8400; + u16 fake_register; + unsigned int sysclk; + unsigned int pcmclk; + struct work_struct work; +}; + +static inline unsigned int wm8400_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct wm8400_priv *wm8400 = codec->private_data; + + if (reg == WM8400_INTDRIVBITS) + return wm8400->fake_register; + else + return wm8400_reg_read(wm8400->wm8400, reg); +} + +/* + * write to the wm8400 register space + */ +static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct wm8400_priv *wm8400 = codec->private_data; + + if (reg == WM8400_INTDRIVBITS) { + wm8400->fake_register = value; + return 0; + } else + return wm8400_set_bits(wm8400->wm8400, reg, 0xffff, value); +} + +static void wm8400_codec_reset(struct snd_soc_codec *codec) +{ + struct wm8400_priv *wm8400 = codec->private_data; + + wm8400_reset_codec_reg_cache(wm8400->wm8400); +} + +static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); + +static const DECLARE_TLV_DB_LINEAR(in_pga_tlv, -1650, 3000); + +static const DECLARE_TLV_DB_LINEAR(out_mix_tlv, -2100, 0); + +static const DECLARE_TLV_DB_LINEAR(out_pga_tlv, -7300, 600); + +static const DECLARE_TLV_DB_LINEAR(out_omix_tlv, -600, 0); + +static const DECLARE_TLV_DB_LINEAR(out_dac_tlv, -7163, 0); + +static const DECLARE_TLV_DB_LINEAR(in_adc_tlv, -7163, 1763); + +static const DECLARE_TLV_DB_LINEAR(out_sidetone_tlv, -3600, 0); + +static int wm8400_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = wm8400_read(codec, reg); + return wm8400_write(codec, reg, val | 0x0100); +} + +#define WM8400_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_get_volsw, .put = wm8400_outpga_put_volsw_vu, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + + +static const char *wm8400_digital_sidetone[] = + {"None", "Left ADC", "Right ADC", "Reserved"}; + +static const struct soc_enum wm8400_left_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE, + WM8400_ADC_TO_DACL_SHIFT, 2, wm8400_digital_sidetone); + +static const struct soc_enum wm8400_right_digital_sidetone_enum = +SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE, + WM8400_ADC_TO_DACR_SHIFT, 2, wm8400_digital_sidetone); + +static const char *wm8400_adcmode[] = + {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static const struct soc_enum wm8400_right_adcmode_enum = +SOC_ENUM_SINGLE(WM8400_ADC_CTRL, WM8400_ADC_HPF_CUT_SHIFT, 3, wm8400_adcmode); + +static const struct snd_kcontrol_new wm8400_snd_controls[] = { +/* INMIXL */ +SOC_SINGLE("LIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L12MNBST_SHIFT, + 1, 0), +SOC_SINGLE("LIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L34MNBST_SHIFT, + 1, 0), +/* INMIXR */ +SOC_SINGLE("RIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R12MNBST_SHIFT, + 1, 0), +SOC_SINGLE("RIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R34MNBST_SHIFT, + 1, 0), + +/* LOMIX */ +SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LLI3LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LR12LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3, + WM8400_LL12LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRI3LOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER5, + WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv), + +/* ROMIX */ +SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RRI3ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RL12ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4, + WM8400_RR12ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RLI3ROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RLBROVOL_SHIFT, 7, 0, out_mix_tlv), +SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER6, + WM8400_RRBROVOL_SHIFT, 7, 0, out_mix_tlv), + +/* LOUT */ +WM8400_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8400_LEFT_OUTPUT_VOLUME, + WM8400_LOUTVOL_SHIFT, WM8400_LOUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOUT ZC", WM8400_LEFT_OUTPUT_VOLUME, WM8400_LOZC_SHIFT, 1, 0), + +/* ROUT */ +WM8400_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8400_RIGHT_OUTPUT_VOLUME, + WM8400_ROUTVOL_SHIFT, WM8400_ROUTVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROUT ZC", WM8400_RIGHT_OUTPUT_VOLUME, WM8400_ROZC_SHIFT, 1, 0), + +/* LOPGA */ +WM8400_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8400_LEFT_OPGA_VOLUME, + WM8400_LOPGAVOL_SHIFT, WM8400_LOPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("LOPGA ZC Switch", WM8400_LEFT_OPGA_VOLUME, + WM8400_LOPGAZC_SHIFT, 1, 0), + +/* ROPGA */ +WM8400_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8400_RIGHT_OPGA_VOLUME, + WM8400_ROPGAVOL_SHIFT, WM8400_ROPGAVOL_MASK, 0, out_pga_tlv), +SOC_SINGLE("ROPGA ZC Switch", WM8400_RIGHT_OPGA_VOLUME, + WM8400_ROPGAZC_SHIFT, 1, 0), + +SOC_SINGLE("LON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LONMUTE_SHIFT, 1, 0), +SOC_SINGLE("LOP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LOPMUTE_SHIFT, 1, 0), +SOC_SINGLE("LOP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_LOATTN_SHIFT, 1, 0), +SOC_SINGLE("RON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_RONMUTE_SHIFT, 1, 0), +SOC_SINGLE("ROP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_ROPMUTE_SHIFT, 1, 0), +SOC_SINGLE("ROP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME, + WM8400_ROATTN_SHIFT, 1, 0), + +SOC_SINGLE("OUT3 Mute Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT3MUTE_SHIFT, 1, 0), +SOC_SINGLE("OUT3 Attenuation Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT3ATTN_SHIFT, 1, 0), + +SOC_SINGLE("OUT4 Mute Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT4MUTE_SHIFT, 1, 0), +SOC_SINGLE("OUT4 Attenuation Switch", WM8400_OUT3_4_VOLUME, + WM8400_OUT4ATTN_SHIFT, 1, 0), + +SOC_SINGLE("Speaker Mode Switch", WM8400_CLASSD1, + WM8400_CDMODE_SHIFT, 1, 0), + +SOC_SINGLE("Speaker Output Attenuation Volume", WM8400_SPEAKER_VOLUME, + WM8400_SPKATTN_SHIFT, WM8400_SPKATTN_MASK, 0), +SOC_SINGLE("Speaker DC Boost Volume", WM8400_CLASSD3, + WM8400_DCGAIN_SHIFT, 6, 0), +SOC_SINGLE("Speaker AC Boost Volume", WM8400_CLASSD3, + WM8400_ACGAIN_SHIFT, 6, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8400_LEFT_DAC_DIGITAL_VOLUME, WM8400_DACL_VOL_SHIFT, + 127, 0, out_dac_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8400_RIGHT_DAC_DIGITAL_VOLUME, WM8400_DACR_VOL_SHIFT, + 127, 0, out_dac_tlv), + +SOC_ENUM("Left Digital Sidetone", wm8400_left_digital_sidetone_enum), +SOC_ENUM("Right Digital Sidetone", wm8400_right_digital_sidetone_enum), + +SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE, + WM8400_ADCL_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv), +SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE, + WM8400_ADCR_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv), + +SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8400_ADC_CTRL, + WM8400_ADC_HPF_ENA_SHIFT, 1, 0), + +SOC_ENUM("ADC HPF Mode", wm8400_right_adcmode_enum), + +WM8400_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8400_LEFT_ADC_DIGITAL_VOLUME, + WM8400_ADCL_VOL_SHIFT, + WM8400_ADCL_VOL_MASK, + 0, + in_adc_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8400_RIGHT_ADC_DIGITAL_VOLUME, + WM8400_ADCR_VOL_SHIFT, + WM8400_ADCR_VOL_MASK, + 0, + in_adc_tlv), + +WM8400_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LIN12VOL_SHIFT, + WM8400_LIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN12 ZC Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LI12ZC_SHIFT, 1, 0), + +SOC_SINGLE("LIN12 Mute Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + WM8400_LI12MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LIN34VOL_SHIFT, + WM8400_LIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("LIN34 ZC Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LI34ZC_SHIFT, 1, 0), + +SOC_SINGLE("LIN34 Mute Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME, + WM8400_LI34MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RIN12VOL_SHIFT, + WM8400_RIN12VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN12 ZC Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RI12ZC_SHIFT, 1, 0), + +SOC_SINGLE("RIN12 Mute Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8400_RI12MUTE_SHIFT, 1, 0), + +WM8400_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RIN34VOL_SHIFT, + WM8400_RIN34VOL_MASK, + 0, + in_pga_tlv), + +SOC_SINGLE("RIN34 ZC Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RI34ZC_SHIFT, 1, 0), + +SOC_SINGLE("RIN34 Mute Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8400_RI34MUTE_SHIFT, 1, 0), + +}; + +/* add non dapm controls */ +static int wm8400_add_controls(struct snd_soc_codec *codec) +{ + int err, i; + + for (i = 0; i < ARRAY_SIZE(wm8400_snd_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8400_snd_controls[i],codec, + NULL)); + if (err < 0) + return err; + } + return 0; +} + +/* + * _DAPM_ Controls + */ + +static int inmixer_event (struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 reg, fakepower; + + reg = wm8400_read(w->codec, WM8400_POWER_MANAGEMENT_2); + fakepower = wm8400_read(w->codec, WM8400_INTDRIVBITS); + + if (fakepower & ((1 << WM8400_INMIXL_PWR) | + (1 << WM8400_AINLMUX_PWR))) { + reg |= WM8400_AINL_ENA; + } else { + reg &= ~WM8400_AINL_ENA; + } + + if (fakepower & ((1 << WM8400_INMIXR_PWR) | + (1 << WM8400_AINRMUX_PWR))) { + reg |= WM8400_AINR_ENA; + } else { + reg &= ~WM8400_AINL_ENA; + } + wm8400_write(w->codec, WM8400_POWER_MANAGEMENT_2, reg); + + return 0; +} + +static int outmixer_event (struct snd_soc_dapm_widget *w, + struct snd_kcontrol * kcontrol, int event) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + u32 reg_shift = mc->shift; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8400_SPEAKER_MIXER | (WM8400_LDSPK << 8) : + reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER1); + if (reg & WM8400_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + case WM8400_SPEAKER_MIXER | (WM8400_RDSPK << 8): + reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER2); + if (reg & WM8400_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + case WM8400_OUTPUT_MIXER1 | (WM8400_LDLO << 8): + reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER); + if (reg & WM8400_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + case WM8400_OUTPUT_MIXER2 | (WM8400_RDRO << 8): + reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER); + if (reg & WM8400_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const unsigned int in_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0,7, TLV_DB_LINEAR_ITEM(-1200, 600), +}; + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8400_dapm_lin12_pga_controls[] = { +SOC_DAPM_SINGLE("LIN1 Switch", WM8400_INPUT_MIXER2, WM8400_LMN1_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LIN2 Switch", WM8400_INPUT_MIXER2, WM8400_LMP2_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8400_dapm_lin34_pga_controls[] = { +SOC_DAPM_SINGLE("LIN3 Switch", WM8400_INPUT_MIXER2, WM8400_LMN3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LIN4 Switch", WM8400_INPUT_MIXER2, WM8400_LMP4_SHIFT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8400_dapm_rin12_pga_controls[] = { +SOC_DAPM_SINGLE("RIN1 Switch", WM8400_INPUT_MIXER2, WM8400_RMN1_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RIN2 Switch", WM8400_INPUT_MIXER2, WM8400_RMP2_SHIFT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8400_dapm_rin34_pga_controls[] = { +SOC_DAPM_SINGLE("RIN3 Switch", WM8400_INPUT_MIXER2, WM8400_RMN3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RIN4 Switch", WM8400_INPUT_MIXER2, WM8400_RMP4_SHIFT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8400_dapm_inmixl_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8400_INPUT_MIXER3, + WM8400_LDBVOL_SHIFT, WM8400_LDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8400_INPUT_MIXER5, WM8400_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("LINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT, + 1, 0), +SOC_DAPM_SINGLE("LINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8400_dapm_inmixr_controls[] = { +SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8400_INPUT_MIXER4, + WM8400_RDBVOL_SHIFT, WM8400_RDBVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8400_INPUT_MIXER6, WM8400_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), +SOC_DAPM_SINGLE("RINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT, + 1, 0), +SOC_DAPM_SINGLE("RINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8400_ainlmux[] = + {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static const struct soc_enum wm8400_ainlmux_enum = +SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINLMODE_SHIFT, + ARRAY_SIZE(wm8400_ainlmux), wm8400_ainlmux); + +static const struct snd_kcontrol_new wm8400_dapm_ainlmux_controls = +SOC_DAPM_ENUM("Route", wm8400_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8400_ainrmux[] = + {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static const struct soc_enum wm8400_ainrmux_enum = +SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINRMODE_SHIFT, + ARRAY_SIZE(wm8400_ainrmux), wm8400_ainrmux); + +static const struct snd_kcontrol_new wm8400_dapm_ainrmux_controls = +SOC_DAPM_ENUM("Route", wm8400_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8400_dapm_rxvoice_controls[] = { +SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8400_INPUT_MIXER5, WM8400_LR4BVOL_SHIFT, + WM8400_LR4BVOL_MASK, 0, in_mix_tlv), +SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8400_INPUT_MIXER6, WM8400_RL4BVOL_SHIFT, + WM8400_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lomix_controls[] = { +SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LRBLO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LLBLO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LRI3LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LLI3LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LR12LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1, + WM8400_LL12LO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8400_OUTPUT_MIXER1, + WM8400_LDLO_SHIFT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8400_dapm_romix_controls[] = { +SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RLBRO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RRBRO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RLI3RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RRI3RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RL12RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2, + WM8400_RR12RO_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8400_OUTPUT_MIXER2, + WM8400_RDRO_SHIFT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lonmix_controls[] = { +SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LLOPGALON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LROPGALON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8400_LINE_MIXER1, + WM8400_LOPLON_SHIFT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8400_dapm_lopmix_controls[] = { +SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER1, + WM8400_LR12LOP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER1, + WM8400_LL12LOP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1, + WM8400_LLOPGALOP_SHIFT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8400_dapm_ronmix_controls[] = { +SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RROPGARON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RLOPGARON_SHIFT, 1, 0), +SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8400_LINE_MIXER2, + WM8400_ROPRON_SHIFT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8400_dapm_ropmix_controls[] = { +SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER2, + WM8400_RL12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER2, + WM8400_RR12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2, + WM8400_RROPGAROP_SHIFT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8400_dapm_out3mix_controls[] = { +SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER, + WM8400_LI4O3_SHIFT, 1, 0), +SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8400_OUT3_4_MIXER, + WM8400_LPGAO3_SHIFT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8400_dapm_out4mix_controls[] = { +SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8400_OUT3_4_MIXER, + WM8400_RPGAO4_SHIFT, 1, 0), +SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER, + WM8400_RI4O4_SHIFT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8400_dapm_spkmix_controls[] = { +SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_LI2SPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_LB2SPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8400_SPEAKER_MIXER, + WM8400_LOPGASPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8400_SPEAKER_MIXER, + WM8400_LDSPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8400_SPEAKER_MIXER, + WM8400_RDSPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8400_SPEAKER_MIXER, + WM8400_ROPGASPK_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_RL12ROP_SHIFT, 1, 0), +SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8400_SPEAKER_MIXER, + WM8400_RI2SPK_SHIFT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8400_dapm_widgets[] = { +/* Input Side */ +/* Input Lines */ +SND_SOC_DAPM_INPUT("LIN1"), +SND_SOC_DAPM_INPUT("LIN2"), +SND_SOC_DAPM_INPUT("LIN3"), +SND_SOC_DAPM_INPUT("LIN4/RXN"), +SND_SOC_DAPM_INPUT("RIN3"), +SND_SOC_DAPM_INPUT("RIN4/RXP"), +SND_SOC_DAPM_INPUT("RIN1"), +SND_SOC_DAPM_INPUT("RIN2"), +SND_SOC_DAPM_INPUT("Internal ADC Source"), + +/* DACs */ +SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8400_POWER_MANAGEMENT_2, + WM8400_ADCL_ENA_SHIFT, 0), +SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8400_POWER_MANAGEMENT_2, + WM8400_ADCR_ENA_SHIFT, 0), + +/* Input PGAs */ +SND_SOC_DAPM_MIXER("LIN12 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_LIN12_ENA_SHIFT, + 0, &wm8400_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_lin12_pga_controls)), +SND_SOC_DAPM_MIXER("LIN34 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_LIN34_ENA_SHIFT, + 0, &wm8400_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_lin34_pga_controls)), +SND_SOC_DAPM_MIXER("RIN12 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_RIN12_ENA_SHIFT, + 0, &wm8400_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_rin12_pga_controls)), +SND_SOC_DAPM_MIXER("RIN34 PGA", WM8400_POWER_MANAGEMENT_2, + WM8400_RIN34_ENA_SHIFT, + 0, &wm8400_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8400_dapm_rin34_pga_controls)), + +/* INMIXL */ +SND_SOC_DAPM_MIXER_E("INMIXL", WM8400_INTDRIVBITS, WM8400_INMIXL_PWR, 0, + &wm8400_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8400_dapm_inmixl_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINLMUX */ +SND_SOC_DAPM_MUX_E("AILNMUX", WM8400_INTDRIVBITS, WM8400_AINLMUX_PWR, 0, + &wm8400_dapm_ainlmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* INMIXR */ +SND_SOC_DAPM_MIXER_E("INMIXR", WM8400_INTDRIVBITS, WM8400_INMIXR_PWR, 0, + &wm8400_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8400_dapm_inmixr_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* AINRMUX */ +SND_SOC_DAPM_MUX_E("AIRNMUX", WM8400_INTDRIVBITS, WM8400_AINRMUX_PWR, 0, + &wm8400_dapm_ainrmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + +/* Output Side */ +/* DACs */ +SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8400_POWER_MANAGEMENT_3, + WM8400_DACL_ENA_SHIFT, 0), +SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8400_POWER_MANAGEMENT_3, + WM8400_DACR_ENA_SHIFT, 0), + +/* LOMIX */ +SND_SOC_DAPM_MIXER_E("LOMIX", WM8400_POWER_MANAGEMENT_3, + WM8400_LOMIX_ENA_SHIFT, + 0, &wm8400_dapm_lomix_controls[0], + ARRAY_SIZE(wm8400_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LONMIX */ +SND_SOC_DAPM_MIXER("LONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LON_ENA_SHIFT, + 0, &wm8400_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8400_dapm_lonmix_controls)), + +/* LOPMIX */ +SND_SOC_DAPM_MIXER("LOPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LOP_ENA_SHIFT, + 0, &wm8400_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8400_dapm_lopmix_controls)), + +/* OUT3MIX */ +SND_SOC_DAPM_MIXER("OUT3MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT3_ENA_SHIFT, + 0, &wm8400_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8400_dapm_out3mix_controls)), + +/* SPKMIX */ +SND_SOC_DAPM_MIXER_E("SPKMIX", WM8400_POWER_MANAGEMENT_1, WM8400_SPK_ENA_SHIFT, + 0, &wm8400_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8400_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + +/* OUT4MIX */ +SND_SOC_DAPM_MIXER("OUT4MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT4_ENA_SHIFT, + 0, &wm8400_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8400_dapm_out4mix_controls)), + +/* ROPMIX */ +SND_SOC_DAPM_MIXER("ROPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_ROP_ENA_SHIFT, + 0, &wm8400_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8400_dapm_ropmix_controls)), + +/* RONMIX */ +SND_SOC_DAPM_MIXER("RONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_RON_ENA_SHIFT, + 0, &wm8400_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8400_dapm_ronmix_controls)), + +/* ROMIX */ +SND_SOC_DAPM_MIXER_E("ROMIX", WM8400_POWER_MANAGEMENT_3, + WM8400_ROMIX_ENA_SHIFT, + 0, &wm8400_dapm_romix_controls[0], + ARRAY_SIZE(wm8400_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + +/* LOUT PGA */ +SND_SOC_DAPM_PGA("LOUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_LOUT_ENA_SHIFT, + 0, NULL, 0), + +/* ROUT PGA */ +SND_SOC_DAPM_PGA("ROUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_ROUT_ENA_SHIFT, + 0, NULL, 0), + +/* LOPGA */ +SND_SOC_DAPM_PGA("LOPGA", WM8400_POWER_MANAGEMENT_3, WM8400_LOPGA_ENA_SHIFT, 0, + NULL, 0), + +/* ROPGA */ +SND_SOC_DAPM_PGA("ROPGA", WM8400_POWER_MANAGEMENT_3, WM8400_ROPGA_ENA_SHIFT, 0, + NULL, 0), + +/* MICBIAS */ +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8400_POWER_MANAGEMENT_1, + WM8400_MIC1BIAS_ENA_SHIFT, 0), + +SND_SOC_DAPM_OUTPUT("LON"), +SND_SOC_DAPM_OUTPUT("LOP"), +SND_SOC_DAPM_OUTPUT("OUT3"), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("OUT4"), +SND_SOC_DAPM_OUTPUT("ROP"), +SND_SOC_DAPM_OUTPUT("RON"), + +SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording + * even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AILNMUX */ + {"AILNMUX", "INMIXL Mix", "INMIXL"}, + {"AILNMUX", "DIFFINL Mix", "LIN12 PGA"}, + {"AILNMUX", "DIFFINL Mix", "LIN34 PGA"}, + {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Left ADC", NULL, "AILNMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AIRNMUX */ + {"AIRNMUX", "INMIXR Mix", "INMIXR"}, + {"AIRNMUX", "DIFFINR Mix", "RIN12 PGA"}, + {"AIRNMUX", "DIFFINR Mix", "RIN34 PGA"}, + {"AIRNMUX", "RXVOICE Mix", "LIN4/RXN"}, + {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"}, + /* ADC */ + {"Right ADC", NULL, "AIRNMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AIRNMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AILNMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AIRNMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AILNMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AILNMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AIRNMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT3", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +static int wm8400_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8400_dapm_widgets, + ARRAY_SIZE(wm8400_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* + * Clock after FLL and dividers + */ +static int wm8400_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 wm8400_priv *wm8400 = codec->private_data; + + wm8400->sysclk = freq; + return 0; +} + +/* + * Sets ADC and Voice DAC format. + */ +static int wm8400_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 audio1, audio3; + + audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1); + audio3 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8400_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8400_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8400_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8400_AIF_FMT_I2S; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8400_AIF_FMT_RIGHTJ; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8400_AIF_FMT_LEFTJ; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8400_AIF_FMT_DSP; + audio1 &= ~WM8400_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8400_AIF_FMT_DSP | WM8400_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1); + wm8400_write(codec, WM8400_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8400_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8400_MCLK_DIV: + reg = wm8400_read(codec, WM8400_CLOCKING_2) & + ~WM8400_MCLK_DIV_MASK; + wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_DACCLK_DIV: + reg = wm8400_read(codec, WM8400_CLOCKING_2) & + ~WM8400_DAC_CLKDIV_MASK; + wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_ADCCLK_DIV: + reg = wm8400_read(codec, WM8400_CLOCKING_2) & + ~WM8400_ADC_CLKDIV_MASK; + wm8400_write(codec, WM8400_CLOCKING_2, reg | div); + break; + case WM8400_BCLK_DIV: + reg = wm8400_read(codec, WM8400_CLOCKING_1) & + ~WM8400_BCLK_DIV_MASK; + wm8400_write(codec, WM8400_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8400_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u16 audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1); + + audio1 &= ~WM8400_AIF_WL_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + audio1 |= WM8400_AIF_WL_20BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + audio1 |= WM8400_AIF_WL_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio1 |= WM8400_AIF_WL_32BITS; + break; + } + + wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8400_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 val = wm8400_read(codec, WM8400_DAC_CTRL) & ~WM8400_DAC_MUTE; + + if (mute) + wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); + else + wm8400_write(codec, WM8400_DAC_CTRL, val); + + return 0; +} + +/* TODO: set bias for best performance at standby */ +static int wm8400_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8400_priv *wm8400 = codec->private_data; + u16 val; + int ret; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*50k */ + val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) & + ~WM8400_VMID_MODE_MASK; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x2); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(power), + &power[0]); + if (ret != 0) { + dev_err(wm8400->wm8400->dev, + "Failed to enable regulators: %d\n", + ret); + return ret; + } + + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, + WM8400_CODEC_ENA | WM8400_SYSCLK_ENA); + + /* Enable all output discharge bits */ + wm8400_write(codec, WM8400_ANTIPOP1, WM8400_DIS_LLINE | + WM8400_DIS_RLINE | WM8400_DIS_OUT3 | + WM8400_DIS_OUT4 | WM8400_DIS_LOUT | + WM8400_DIS_ROUT); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL); + + msleep(500); + + /* Enable outputs */ + val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); + val |= WM8400_SPK_ENA | WM8400_OUT3_ENA | + WM8400_OUT4_ENA | WM8400_LOUT_ENA | + WM8400_ROUT_ENA; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + /* disable all output discharge bits */ + wm8400_write(codec, WM8400_ANTIPOP1, 0); + + /* Enable VREF & VMID at 2x50k */ + val |= 0x2 | WM8400_VREF_ENA; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + msleep(600); + + /* Enable BUFIOEN */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL | + WM8400_BUFIOEN); + + /* Disable outputs */ + val &= ~(WM8400_SPK_ENA | WM8400_OUT3_ENA | + WM8400_OUT4_ENA | WM8400_LOUT_ENA | + WM8400_ROUT_ENA); + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_BUFIOEN); + } + + /* VMID=2*300k */ + val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) & + ~WM8400_VMID_MODE_MASK; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x4); + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_POBCTRL | WM8400_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST | + WM8400_BUFDCOPEN | WM8400_POBCTRL | + WM8400_BUFIOEN); + + /* mute DAC */ + val = wm8400_read(codec, WM8400_DAC_CTRL); + wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE); + + /* Enable any disabled outputs */ + val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); + val |= WM8400_SPK_ENA | WM8400_OUT3_ENA | + WM8400_OUT4_ENA | WM8400_LOUT_ENA | + WM8400_ROUT_ENA; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + /* Disable VMID */ + val &= ~WM8400_VMID_MODE_MASK; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + msleep(300); + + /* Enable all output discharge bits */ + wm8400_write(codec, WM8400_ANTIPOP1, WM8400_DIS_LLINE | + WM8400_DIS_RLINE | WM8400_DIS_OUT3 | + WM8400_DIS_OUT4 | WM8400_DIS_LOUT | + WM8400_DIS_ROUT); + + /* Disable VREF */ + val &= ~WM8400_VREF_ENA; + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + wm8400_write(codec, WM8400_ANTIPOP2, 0x0); + + ret = regulator_bulk_disable(ARRAY_SIZE(power), + &power[0]); + if (ret != 0) + return ret; + + break; + } + + codec->bias_level = level; + return 0; +} + +#define WM8400_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8400_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8400_dai_ops = { + .hw_params = wm8400_hw_params, + .digital_mute = wm8400_mute, + .set_fmt = wm8400_set_dai_fmt, + .set_clkdiv = wm8400_set_dai_clkdiv, + .set_sysclk = wm8400_set_dai_sysclk, +}; + +/* + * The WM8400 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +struct snd_soc_dai wm8400_dai = { +/* ADC/DAC on primary */ + .name = "WM8400 ADC/DAC Primary", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8400_RATES, + .formats = WM8400_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8400_RATES, + .formats = WM8400_FORMATS, + }, + .ops = &wm8400_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm8400_dai); + +static int wm8400_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8400_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8400_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static struct snd_soc_codec *wm8400_codec; + +static int wm8400_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + if (!wm8400_codec) { + dev_err(&pdev->dev, "wm8400 not yet discovered\n"); + return -ENODEV; + } + codec = wm8400_codec; + + socdev->card->codec = codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create pcms\n"); + goto pcm_err; + } + + wm8400_add_controls(codec); + wm8400_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(&pdev->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: + return ret; +} + +/* power down chip */ +static int wm8400_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8400 = { + .probe = wm8400_probe, + .remove = wm8400_remove, + .suspend = wm8400_suspend, + .resume = wm8400_resume, +}; + +static void wm8400_probe_deferred(struct work_struct *work) +{ + struct wm8400_priv *priv = container_of(work, struct wm8400_priv, + work); + struct snd_soc_codec *codec = &priv->codec; + int ret; + + /* charge output caps */ + wm8400_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* We're done, tell the subsystem. */ + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(priv->wm8400->dev, + "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dai(&wm8400_dai); + if (ret != 0) { + dev_err(priv->wm8400->dev, + "Failed to register DAI: %d\n", ret); + goto err_codec; + } + + return; + +err_codec: + snd_soc_unregister_codec(codec); +err: + wm8400_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int wm8400_codec_probe(struct platform_device *dev) +{ + struct wm8400_priv *priv; + int ret; + u16 reg; + struct snd_soc_codec *codec; + + priv = kzalloc(sizeof(struct wm8400_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + codec = &priv->codec; + codec->private_data = priv; + codec->control_data = dev->dev.driver_data; + priv->wm8400 = dev->dev.driver_data; + + ret = regulator_bulk_get(priv->wm8400->dev, + ARRAY_SIZE(power), &power[0]); + if (ret != 0) { + dev_err(&dev->dev, "Failed to get regulators: %d\n", ret); + goto err; + } + + codec->dev = &dev->dev; + wm8400_dai.dev = &dev->dev; + + codec->name = "WM8400"; + codec->owner = THIS_MODULE; + codec->read = wm8400_read; + codec->write = wm8400_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8400_set_bias_level; + codec->dai = &wm8400_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8400_REGISTER_COUNT; + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + INIT_WORK(&priv->work, wm8400_probe_deferred); + + wm8400_codec_reset(codec); + + reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1); + wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, reg | WM8400_CODEC_ENA); + + /* Latch volume update bits */ + reg = wm8400_read(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME); + wm8400_write(codec, WM8400_LEFT_LINE_INPUT_1_2_VOLUME, + reg & WM8400_IPVU); + reg = wm8400_read(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME); + wm8400_write(codec, WM8400_RIGHT_LINE_INPUT_1_2_VOLUME, + reg & WM8400_IPVU); + + wm8400_write(codec, WM8400_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + wm8400_write(codec, WM8400_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + wm8400_codec = codec; + + if (!schedule_work(&priv->work)) { + ret = -EINVAL; + goto err_regulator; + } + + return 0; + +err_regulator: + wm8400_codec = NULL; + regulator_bulk_free(ARRAY_SIZE(power), power); +err: + kfree(priv); + return ret; +} + +static int __exit wm8400_codec_remove(struct platform_device *dev) +{ + struct wm8400_priv *priv = wm8400_codec->private_data; + u16 reg; + + snd_soc_unregister_dai(&wm8400_dai); + snd_soc_unregister_codec(wm8400_codec); + + reg = wm8400_read(wm8400_codec, WM8400_POWER_MANAGEMENT_1); + wm8400_write(wm8400_codec, WM8400_POWER_MANAGEMENT_1, + reg & (~WM8400_CODEC_ENA)); + + regulator_bulk_free(ARRAY_SIZE(power), power); + kfree(priv); + + wm8400_codec = NULL; + + return 0; +} + +static struct platform_driver wm8400_codec_driver = { + .driver = { + .name = "wm8400-codec", + .owner = THIS_MODULE, + }, + .probe = wm8400_codec_probe, + .remove = __exit_p(wm8400_codec_remove), +}; + +static int __init wm8400_codec_init(void) +{ + return platform_driver_register(&wm8400_codec_driver); +} +module_init(wm8400_codec_init); + +static void __exit wm8400_codec_exit(void) +{ + platform_driver_unregister(&wm8400_codec_driver); +} +module_exit(wm8400_codec_exit); + +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8400); + +MODULE_DESCRIPTION("ASoC WM8400 driver"); +MODULE_AUTHOR("Mark Brown"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:wm8400-codec"); diff --git a/sound/soc/codecs/wm8400.h b/sound/soc/codecs/wm8400.h new file mode 100644 index 000000000000..79c5934d4776 --- /dev/null +++ b/sound/soc/codecs/wm8400.h @@ -0,0 +1,62 @@ +/* + * wm8400.h -- audio driver for WM8400 + * + * 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 _WM8400_CODEC_H +#define _WM8400_CODEC_H + +#define WM8400_MCLK_DIV 0 +#define WM8400_DACCLK_DIV 1 +#define WM8400_ADCCLK_DIV 2 +#define WM8400_BCLK_DIV 3 + +#define WM8400_MCLK_DIV_1 0x400 +#define WM8400_MCLK_DIV_2 0x800 + +#define WM8400_DAC_CLKDIV_1 0x00 +#define WM8400_DAC_CLKDIV_1_5 0x04 +#define WM8400_DAC_CLKDIV_2 0x08 +#define WM8400_DAC_CLKDIV_3 0x0c +#define WM8400_DAC_CLKDIV_4 0x10 +#define WM8400_DAC_CLKDIV_5_5 0x14 +#define WM8400_DAC_CLKDIV_6 0x18 + +#define WM8400_ADC_CLKDIV_1 0x00 +#define WM8400_ADC_CLKDIV_1_5 0x20 +#define WM8400_ADC_CLKDIV_2 0x40 +#define WM8400_ADC_CLKDIV_3 0x60 +#define WM8400_ADC_CLKDIV_4 0x80 +#define WM8400_ADC_CLKDIV_5_5 0xa0 +#define WM8400_ADC_CLKDIV_6 0xc0 + + +#define WM8400_BCLK_DIV_1 (0x0 << 1) +#define WM8400_BCLK_DIV_1_5 (0x1 << 1) +#define WM8400_BCLK_DIV_2 (0x2 << 1) +#define WM8400_BCLK_DIV_3 (0x3 << 1) +#define WM8400_BCLK_DIV_4 (0x4 << 1) +#define WM8400_BCLK_DIV_5_5 (0x5 << 1) +#define WM8400_BCLK_DIV_6 (0x6 << 1) +#define WM8400_BCLK_DIV_8 (0x7 << 1) +#define WM8400_BCLK_DIV_11 (0x8 << 1) +#define WM8400_BCLK_DIV_12 (0x9 << 1) +#define WM8400_BCLK_DIV_16 (0xA << 1) +#define WM8400_BCLK_DIV_22 (0xB << 1) +#define WM8400_BCLK_DIV_24 (0xC << 1) +#define WM8400_BCLK_DIV_32 (0xD << 1) +#define WM8400_BCLK_DIV_44 (0xE << 1) +#define WM8400_BCLK_DIV_48 (0xF << 1) + +extern struct snd_soc_dai wm8400_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8400; + +#endif diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index cc975a62fa5c..6a4cea09c45d 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -336,7 +336,7 @@ static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, return 0; } - pll_factors(freq_out*8, freq_in); + pll_factors(freq_out*4, freq_in); wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18); @@ -367,7 +367,7 @@ static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai, wm8510_write(codec, WM8510_GPIO, reg | div); break; case WM8510_MCLKDIV: - reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1f; + reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x11f; wm8510_write(codec, WM8510_CLOCK, reg | div); break; case WM8510_ADCCLK: diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index ee0af23a1acc..27f9e231bf69 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -35,8 +35,6 @@ #include "wm8580.h" -#define WM8580_VERSION "0.1" - struct pll_state { unsigned int in; unsigned int out; @@ -976,8 +974,6 @@ static int wm8580_probe(struct platform_device *pdev) 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) diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index cc6e57f9acf8..a6e8f3f7f052 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -51,11 +51,6 @@ #include "wm8753.h" -#ifdef CONFIG_SPI_MASTER -static struct spi_driver wm8753_spi_driver; -static int wm8753_spi_write(struct spi_device *spi, const char *data, int len); -#endif - static int caps_charge = 2000; module_param(caps_charge, int, 0); MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)"); diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index b502741692d6..bd7392c9657e 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -20,7 +20,7 @@ config SND_DAVINCI_SOC_EVM config SND_DAVINCI_SOC_SFFSDR tristate "SoC Audio support for SFFSDR" - depends on SND_DAVINCI_SOC && MACH_DAVINCI_SFFSDR + depends on SND_DAVINCI_SOC && MACH_SFFSDR select SND_DAVINCI_SOC_I2S select SND_SOC_PCM3008 select SFFSDR_FPGA diff --git a/sound/soc/davinci/davinci-sffsdr.c b/sound/soc/davinci/davinci-sffsdr.c index 0bf81abba8c7..40eccfe9e358 100644 --- a/sound/soc/davinci/davinci-sffsdr.c +++ b/sound/soc/davinci/davinci-sffsdr.c @@ -36,9 +36,16 @@ #include "davinci-pcm.h" #include "davinci-i2s.h" +/* + * CLKX and CLKR are the inputs for the Sample Rate Generator. + * FSX and FSR are outputs, driven by the sample Rate Generator. + */ +#define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \ + SND_SOC_DAIFMT_CBM_CFS | \ + SND_SOC_DAIFMT_IB_NF) + static int sffsdr_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) + 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; @@ -56,13 +63,8 @@ static int sffsdr_hw_params(struct snd_pcm_substream *substream, } #endif - /* Set cpu DAI configuration: - * CLKX and CLKR are the inputs for the Sample Rate Generator. - * FSX and FSR are outputs, driven by the sample Rate Generator. */ - ret = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_RIGHT_J | - SND_SOC_DAIFMT_CBM_CFS | - SND_SOC_DAIFMT_IB_NF); + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, AUDIO_FORMAT); if (ret < 0) return ret; diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index 58a3fa497503..b3eb8570cd7b 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -142,7 +142,8 @@ static const struct snd_pcm_hardware fsl_dma_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_JOINT_DUPLEX, + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_PAUSE, .formats = FSLDMA_PCM_FORMATS, .rates = FSLDMA_PCM_RATES, .rate_min = 5512, diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 0fddd437a7c9..169bca295b78 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -72,6 +72,7 @@ * @dev: struct device pointer * @playback: the number of playback streams opened * @capture: the number of capture streams opened + * @asynchronous: 0=synchronous mode, 1=asynchronous mode * @cpu_dai: the CPU DAI for this device * @dev_attr: the sysfs device attribute structure * @stats: SSI statistics @@ -86,6 +87,7 @@ struct fsl_ssi_private { struct device *dev; unsigned int playback; unsigned int capture; + int asynchronous; struct snd_soc_dai cpu_dai; struct device_attribute dev_attr; @@ -301,9 +303,10 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, * * FIXME: Little-endian samples require a different shift dir */ - clrsetbits_be32(&ssi->scr, CCSR_SSI_SCR_I2S_MODE_MASK, - CCSR_SSI_SCR_TFR_CLK_DIS | - CCSR_SSI_SCR_I2S_MODE_SLAVE | CCSR_SSI_SCR_SYN); + clrsetbits_be32(&ssi->scr, + CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN, + CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE + | (ssi_private->asynchronous ? 0 : CCSR_SSI_SCR_SYN)); out_be32(&ssi->stcr, CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 | @@ -382,10 +385,15 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_RATE, first_runtime->rate, first_runtime->rate); - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_SAMPLE_BITS, - first_runtime->sample_bits, - first_runtime->sample_bits); + /* If we're in synchronous mode, then we need to constrain + * the sample size as well. We don't support independent sample + * rates in asynchronous mode. + */ + if (!ssi_private->asynchronous) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + first_runtime->sample_bits, + first_runtime->sample_bits); ssi_private->second_stream = substream; } @@ -421,13 +429,18 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, struct ccsr_ssi __iomem *ssi = ssi_private->ssi; unsigned int sample_size = snd_pcm_format_width(params_format(hw_params)); - u32 wl; + u32 wl = CCSR_SSI_SxCCR_WL(sample_size); /* The SSI should always be disabled at this points (SSIEN=0) */ - wl = CCSR_SSI_SxCCR_WL(sample_size); /* In synchronous mode, the SSI uses STCCR for capture */ - clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); + if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) || + !ssi_private->asynchronous) + clrsetbits_be32(&ssi->stccr, + CCSR_SSI_SxCCR_WL_MASK, wl); + else + clrsetbits_be32(&ssi->srccr, + CCSR_SSI_SxCCR_WL_MASK, wl); } return 0; @@ -451,28 +464,33 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: + clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE); } else { - clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); + long timeout = jiffies + 10; + setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE); - /* - * I think we need this delay to allow time for the SSI - * to put data into its FIFO. Without it, ALSA starts - * to complain about overruns. + /* Wait until the SSI has filled its FIFO. Without this + * delay, ALSA complains about overruns. When the FIFO + * is full, the DMA controller initiates its first + * transfer. Until then, however, the DMA's DAR + * register is zero, which translates to an + * out-of-bounds pointer. This makes ALSA think an + * overrun has occurred. */ - mdelay(1); + while (!(in_be32(&ssi->sisr) & CCSR_SSI_SISR_RFF0) && + (jiffies < timeout)); + if (!(in_be32(&ssi->sisr) & CCSR_SSI_SISR_RFF0)) + return -EIO; } break; case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) clrbits32(&ssi->scr, CCSR_SSI_SCR_TE); @@ -655,6 +673,7 @@ struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) ssi_private->ssi_phys = ssi_info->ssi_phys; ssi_private->irq = ssi_info->irq; ssi_private->dev = ssi_info->dev; + ssi_private->asynchronous = ssi_info->asynchronous; ssi_private->dev->driver_data = fsl_ssi_dai; @@ -705,6 +724,14 @@ void fsl_ssi_destroy_dai(struct snd_soc_dai *fsl_ssi_dai) } EXPORT_SYMBOL_GPL(fsl_ssi_destroy_dai); +static int __init fsl_ssi_init(void) +{ + printk(KERN_INFO "Freescale Synchronous Serial Interface (SSI) ASoC Driver\n"); + + return 0; +} +module_init(fsl_ssi_init); + MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h index 83b44d700e33..eade01feaab6 100644 --- a/sound/soc/fsl/fsl_ssi.h +++ b/sound/soc/fsl/fsl_ssi.h @@ -208,6 +208,7 @@ struct ccsr_ssi { * ssi_phys: physical address of the SSI registers * irq: IRQ of this SSI * dev: struct device, used to create the sysfs statistics file + * asynchronous: 0=synchronous mode, 1=asynchronous mode */ struct fsl_ssi_info { unsigned int id; @@ -215,6 +216,7 @@ struct fsl_ssi_info { dma_addr_t ssi_phys; unsigned int irq; struct device *dev; + int asynchronous; }; struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info); diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c index acf39a646b2f..ef67d1cdffe7 100644 --- a/sound/soc/fsl/mpc8610_hpcd.c +++ b/sound/soc/fsl/mpc8610_hpcd.c @@ -353,6 +353,11 @@ static int mpc8610_hpcd_probe(struct of_device *ofdev, } ssi_info.irq = machine_data->ssi_irq; + /* Do we want to use asynchronous mode? */ + ssi_info.asynchronous = + of_find_property(np, "fsl,ssi-asynchronous", NULL) ? 1 : 0; + if (ssi_info.asynchronous) + dev_info(&ofdev->dev, "using asynchronous mode\n"); /* Map the global utilities registers. */ guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts"); diff --git a/sound/soc/omap/osk5912.c b/sound/soc/omap/osk5912.c index cd41a948df7b..a952a4eb3361 100644 --- a/sound/soc/omap/osk5912.c +++ b/sound/soc/omap/osk5912.c @@ -186,13 +186,6 @@ static int __init osk_soc_init(void) 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. */ @@ -205,9 +198,8 @@ static int __init osk_soc_init(void) } } - printk(KERN_INFO "MCLK = %d [%d], usecount = %d\n", - (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK, - clk_get_usecount(tlv320aic23_mclk)); + printk(KERN_INFO "MCLK = %d [%d]\n", + (uint) clk_get_rate(tlv320aic23_mclk), CODEC_CLOCK); return 0; err1: diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index e226fa75669c..715c648203a4 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -28,6 +28,7 @@ #include <sound/pcm.h> #include <sound/soc.h> #include <sound/soc-dapm.h> +#include <sound/jack.h> #include <asm/mach-types.h> #include <mach/hardware.h> @@ -81,12 +82,97 @@ static struct snd_soc_ops sdp3430_ops = { .hw_params = sdp3430_hw_params, }; +/* SDP3430 machine DAPM */ +static const struct snd_soc_dapm_widget sdp3430_twl4030_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Ext Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_HP("Headset Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* External Mics: MAINMIC, SUBMIC with bias*/ + {"MAINMIC", NULL, "Mic Bias 1"}, + {"SUBMIC", NULL, "Mic Bias 2"}, + {"Mic Bias 1", NULL, "Ext Mic"}, + {"Mic Bias 2", NULL, "Ext Mic"}, + + /* External Speakers: HFL, HFR */ + {"Ext Spk", NULL, "HFL"}, + {"Ext Spk", NULL, "HFR"}, + + /* Headset: HSMIC (with bias), HSOL, HSOR */ + {"Headset Jack", NULL, "HSOL"}, + {"Headset Jack", NULL, "HSOR"}, + {"HSMIC", NULL, "Headset Mic Bias"}, + {"Headset Mic Bias", NULL, "Headset Jack"}, +}; + +static int sdp3430_twl4030_init(struct snd_soc_codec *codec) +{ + int ret; + + /* Add SDP3430 specific widgets */ + ret = snd_soc_dapm_new_controls(codec, sdp3430_twl4030_dapm_widgets, + ARRAY_SIZE(sdp3430_twl4030_dapm_widgets)); + if (ret) + return ret; + + /* Set up SDP3430 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* SDP3430 connected pins */ + snd_soc_dapm_enable_pin(codec, "Ext Mic"); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + snd_soc_dapm_disable_pin(codec, "Headset Jack"); + + /* TWL4030 not connected pins */ + snd_soc_dapm_nc_pin(codec, "AUXL"); + snd_soc_dapm_nc_pin(codec, "AUXR"); + snd_soc_dapm_nc_pin(codec, "CARKITMIC"); + snd_soc_dapm_nc_pin(codec, "DIGIMIC0"); + snd_soc_dapm_nc_pin(codec, "DIGIMIC1"); + + snd_soc_dapm_nc_pin(codec, "OUTL"); + snd_soc_dapm_nc_pin(codec, "OUTR"); + snd_soc_dapm_nc_pin(codec, "EARPIECE"); + snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); + snd_soc_dapm_nc_pin(codec, "PREDRIVER"); + snd_soc_dapm_nc_pin(codec, "CARKITL"); + snd_soc_dapm_nc_pin(codec, "CARKITR"); + + ret = snd_soc_dapm_sync(codec); + + return ret; +} + +/* Headset jack */ +static struct snd_soc_jack hs_jack; + +/* Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headset Jack", + .mask = SND_JACK_HEADSET, + }, +}; + +/* Headset jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + { + .gpio = (OMAP_MAX_GPIO_LINES + 2), + .name = "hsdet-gpio", + .report = SND_JACK_HEADSET, + .debounce_time = 200, + }, +}; + /* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link sdp3430_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], .codec_dai = &twl4030_dai, + .init = sdp3430_twl4030_init, .ops = &sdp3430_ops, }; @@ -130,7 +216,21 @@ static int __init sdp3430_soc_init(void) if (ret) goto err1; - return 0; + /* Headset jack detection */ + ret = snd_soc_jack_new(&snd_soc_sdp3430, "SDP3430 headset jack", + SND_JACK_HEADSET, &hs_jack); + if (ret) + return ret; + + ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins), + hs_jack_pins); + if (ret) + return ret; + + ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + + return ret; err1: printk(KERN_ERR "Unable to add platform device\n"); @@ -142,6 +242,9 @@ module_init(sdp3430_soc_init); static void __exit sdp3430_soc_exit(void) { + snd_soc_jack_free_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); + platform_device_unregister(sdp3430_snd_device); } module_exit(sdp3430_soc_exit); diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 3e18064e86b2..d3fa6357a9fd 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -300,7 +300,7 @@ static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int val; u32 sscr0 = ssp_read_reg(ssp, SSCR0) & - ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ADC); + ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS); dev_dbg(&ssp->pdev->dev, "pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %d\n", @@ -328,7 +328,7 @@ static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai, case PXA_SSP_CLK_AUDIO: priv->sysclk = 0; ssp_set_scr(&priv->dev, 1); - sscr0 |= SSCR0_ADC; + sscr0 |= SSCR0_ACS; break; default: return -ENODEV; @@ -522,9 +522,20 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, u32 sscr1; u32 sspsp; + /* check if we need to change anything at all */ + if (priv->dai_fmt == fmt) + return 0; + + /* we can only change the settings if the port is not in use */ + if (ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) { + dev_err(&ssp->pdev->dev, + "can't change hardware dai format: stream is in use"); + return -EINVAL; + } + /* reset port settings */ sscr0 = ssp_read_reg(ssp, SSCR0) & - (SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ADC); + (SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS); sscr1 = SSCR1_RxTresh(8) | SSCR1_TxTresh(7); sspsp = 0; @@ -644,8 +655,7 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, sscr0 |= SSCR0_FPCKE; #endif sscr0 |= SSCR0_DataSize(16); - if (params_channels(params) > 1) - sscr0 |= SSCR0_EDSS; + /* use network mode (2 slots) for 16 bit stereo */ break; case SNDRV_PCM_FORMAT_S24_LE: sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(8)); diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index 11cd0f289c16..cf809049272a 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -106,13 +106,13 @@ static int pxa2xx_ac97_resume(struct snd_soc_dai *dai) static int pxa2xx_ac97_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { - return pxa2xx_ac97_hw_probe(pdev); + return pxa2xx_ac97_hw_probe(to_platform_device(dai->dev)); } static void pxa2xx_ac97_remove(struct platform_device *pdev, struct snd_soc_dai *dai) { - pxa2xx_ac97_hw_remove(pdev); + pxa2xx_ac97_hw_remove(to_platform_device(dai->dev)); } static int pxa2xx_ac97_hw_params(struct snd_pcm_substream *substream, @@ -230,15 +230,45 @@ struct snd_soc_dai pxa_ac97_dai[] = { EXPORT_SYMBOL_GPL(pxa_ac97_dai); EXPORT_SYMBOL_GPL(soc_ac97_ops); -static int __init pxa_ac97_init(void) +static int __devinit pxa2xx_ac97_dev_probe(struct platform_device *pdev) { + int i; + + for (i = 0; i < ARRAY_SIZE(pxa_ac97_dai); i++) + pxa_ac97_dai[i].dev = &pdev->dev; + + /* Punt most of the init to the SoC probe; we may need the machine + * driver to do interesting things with the clocking to get us up + * and running. + */ return snd_soc_register_dais(pxa_ac97_dai, ARRAY_SIZE(pxa_ac97_dai)); } + +static int __devexit pxa2xx_ac97_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dais(pxa_ac97_dai, ARRAY_SIZE(pxa_ac97_dai)); + + return 0; +} + +static struct platform_driver pxa2xx_ac97_driver = { + .probe = pxa2xx_ac97_dev_probe, + .remove = __devexit_p(pxa2xx_ac97_dev_remove), + .driver = { + .name = "pxa2xx-ac97", + .owner = THIS_MODULE, + }, +}; + +static int __init pxa_ac97_init(void) +{ + return platform_driver_register(&pxa2xx_ac97_driver); +} module_init(pxa_ac97_init); static void __exit pxa_ac97_exit(void) { - snd_soc_unregister_dais(pxa_ac97_dai, ARRAY_SIZE(pxa_ac97_dai)); + platform_driver_unregister(&pxa2xx_ac97_driver); } module_exit(pxa_ac97_exit); diff --git a/sound/soc/pxa/zylonite.c b/sound/soc/pxa/zylonite.c index 0140a250db24..9f6116edbb84 100644 --- a/sound/soc/pxa/zylonite.c +++ b/sound/soc/pxa/zylonite.c @@ -127,8 +127,11 @@ static int zylonite_voice_hw_params(struct snd_pcm_substream *substream, if (ret < 0) return ret; - /* We're not really in network mode but the emulation wants this. */ - ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 1); + /* Use network mode for stereo, one slot per channel. */ + if (params_channels(params) > 1) + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 2); + else + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 1); if (ret < 0) return ret; diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index e05a71084c32..2f3a21eee051 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -1,19 +1,31 @@ config SND_S3C24XX_SOC - tristate "SoC Audio for the Samsung S3C24XX chips" - depends on ARCH_S3C2410 + tristate "SoC Audio for the Samsung S3CXXXX chips" + depends on ARCH_S3C2410 || ARCH_S3C64XX help Say Y or M if you want to add support for codecs attached to - the S3C24XX AC97, I2S or SSP interface. You will also need - to select the audio interfaces to support below. + the S3C24XX and S3C64XX AC97, I2S or SSP interface. You will + also need to select the audio interfaces to support below. config SND_S3C24XX_SOC_I2S tristate + select S3C2410_DMA + +config SND_S3C_I2SV2_SOC + tristate config SND_S3C2412_SOC_I2S tristate + select SND_S3C_I2SV2_SOC + select S3C2410_DMA + +config SND_S3C64XX_SOC_I2S + tristate + select SND_S3C_I2SV2_SOC + select S3C64XX_DMA config SND_S3C2443_SOC_AC97 tristate + select S3C2410_DMA select AC97_BUS select SND_SOC_AC97_BUS @@ -26,6 +38,14 @@ config SND_S3C24XX_SOC_NEO1973_WM8753 Say Y if you want to add support for SoC audio on smdk2440 with the WM8753. +config SND_S3C24XX_SOC_JIVE_WM8750 + tristate "SoC I2S Audio support for Jive" + depends on SND_S3C24XX_SOC && MACH_JIVE + select SND_SOC_WM8750 + select SND_S3C2412_SOC_I2S + help + Sat Y if you want to add support for SoC audio on the Jive. + config SND_S3C24XX_SOC_SMDK2443_WM9710 tristate "SoC AC97 Audio support for SMDK2443 - WM9710" depends on SND_S3C24XX_SOC && MACH_SMDK2443 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index 96b3f3f617d4..07a93a2ebe5f 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -2,19 +2,25 @@ snd-soc-s3c24xx-objs := s3c24xx-pcm.o snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o +snd-soc-s3c64xx-i2s-objs := s3c64xx-i2s.o snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o +snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.o obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o +obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += snd-soc-s3c64xx-i2s.o +obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o # S3C24XX Machine Support +snd-soc-jive-wm8750-objs := jive_wm8750.o snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o +obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o diff --git a/sound/soc/s3c24xx/jive_wm8750.c b/sound/soc/s3c24xx/jive_wm8750.c new file mode 100644 index 000000000000..32063790d95b --- /dev/null +++ b/sound/soc/s3c24xx/jive_wm8750.c @@ -0,0 +1,201 @@ +/* sound/soc/s3c24xx/jive_wm8750.c + * + * Copyright 2007,2008 Simtec Electronics + * + * Based on sound/soc/pxa/spitz.c + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand 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. +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> + +#include "s3c24xx-pcm.h" +#include "s3c2412-i2s.h" + +#include "../codecs/wm8750.h" + +static const struct snd_soc_dapm_route audio_map[] = { + { "Headphone Jack", NULL, "LOUT1" }, + { "Headphone Jack", NULL, "ROUT1" }, + { "Internal Speaker", NULL, "LOUT2" }, + { "Internal Speaker", NULL, "ROUT2" }, + { "LINPUT1", NULL, "Line Input" }, + { "RINPUT1", NULL, "Line Input" }, +}; + +static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Internal Speaker", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static int jive_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; + struct s3c_i2sv2_rate_calc div; + unsigned int clk = 0; + int ret = 0; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + s3c_i2sv2_calc_rate(&div, NULL, params_rate(params), + s3c2412_get_iisclk()); + + /* 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; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_RCLK, div.fs_div); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_PRESCALER, + div.clk_div - 1); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops jive_ops = { + .hw_params = jive_hw_params, +}; + +static int jive_wm8750_init(struct snd_soc_codec *codec) +{ + int err; + + /* These endpoints are not being used. */ + 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, "MONO"); + + /* Add jive specific widgets */ + err = snd_soc_dapm_new_controls(codec, wm8750_dapm_widgets, + ARRAY_SIZE(wm8750_dapm_widgets)); + if (err) { + printk(KERN_ERR "%s: failed to add widgets (%d)\n", + __func__, err); + return err; + } + + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link jive_dai = { + .name = "wm8750", + .stream_name = "WM8750", + .cpu_dai = &s3c2412_i2s_dai, + .codec_dai = &wm8750_dai, + .init = jive_wm8750_init, + .ops = &jive_ops, +}; + +/* jive audio machine driver */ +static struct snd_soc_machine snd_soc_machine_jive = { + .name = "Jive", + .dai_link = &jive_dai, + .num_links = 1, +}; + +/* jive audio private data */ +static struct wm8750_setup_data jive_wm8750_setup = { +}; + +/* jive audio subsystem */ +static struct snd_soc_device jive_snd_devdata = { + .machine = &snd_soc_machine_jive, + .platform = &s3c24xx_soc_platform, + .codec_dev = &soc_codec_dev_wm8750_spi, + .codec_data = &jive_wm8750_setup, +}; + +static struct platform_device *jive_snd_device; + +static int __init jive_init(void) +{ + int ret; + + if (!machine_is_jive()) + return 0; + + printk("JIVE WM8750 Audio support\n"); + + jive_snd_device = platform_device_alloc("soc-audio", -1); + if (!jive_snd_device) + return -ENOMEM; + + platform_set_drvdata(jive_snd_device, &jive_snd_devdata); + jive_snd_devdata.dev = &jive_snd_device->dev; + ret = platform_device_add(jive_snd_device); + + if (ret) + platform_device_put(jive_snd_device); + + return ret; +} + +static void __exit jive_exit(void) +{ + platform_device_unregister(jive_snd_device); +} + +module_init(jive_init); +module_exit(jive_exit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("ALSA SoC Jive Audio support"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c index 286e11ad50ea..5f6aeec0437d 100644 --- a/sound/soc/s3c24xx/neo1973_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -29,25 +29,17 @@ #include <mach/regs-clock.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> -#include <mach/audio.h> +#include <plat/audio.h> #include <linux/io.h> #include <mach/spi-gpio.h> -#include <asm/plat-s3c24xx/regs-iis.h> +#include <plat/regs-iis.h> #include "../codecs/wm8753.h" #include "lm4857.h" #include "s3c24xx-pcm.h" #include "s3c24xx-i2s.h" -/* Debugging stuff */ -#define S3C24XX_SOC_NEO1973_WM8753_DEBUG 0 -#if S3C24XX_SOC_NEO1973_WM8753_DEBUG -#define DBG(x...) printk(KERN_DEBUG "s3c24xx-soc-neo1973-wm8753: " x) -#else -#define DBG(x...) -#endif - /* define the scenarios */ #define NEO_AUDIO_OFF 0 #define NEO_GSM_CALL_AUDIO_HANDSET 1 @@ -72,7 +64,7 @@ static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, int ret = 0; unsigned long iis_clkrate; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); iis_clkrate = s3c24xx_i2s_get_clockrate(); @@ -158,7 +150,7 @@ static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); /* disable the PLL */ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0); @@ -181,7 +173,7 @@ static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, int ret = 0; unsigned long iis_clkrate; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); iis_clkrate = s3c24xx_i2s_get_clockrate(); @@ -224,7 +216,7 @@ static int neo1973_voice_hw_free(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); /* disable the PLL */ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0); @@ -246,7 +238,7 @@ static int neo1973_get_scenario(struct snd_kcontrol *kcontrol, static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); switch (neo1973_scenario) { case NEO_AUDIO_OFF: @@ -330,7 +322,7 @@ static int neo1973_set_scenario(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (neo1973_scenario == ucontrol->value.integer.value[0]) return 0; @@ -344,7 +336,7 @@ static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0}; static void lm4857_write_regs(void) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (i2c_master_send(i2c, lm4857_regs, 4) != 4) printk(KERN_ERR "lm4857: i2c write failed\n"); @@ -357,7 +349,7 @@ static int lm4857_get_reg(struct snd_kcontrol *kcontrol, int shift = (kcontrol->private_value >> 8) & 0x0F; int mask = (kcontrol->private_value >> 16) & 0xFF; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask; return 0; @@ -385,7 +377,7 @@ static int lm4857_get_mode(struct snd_kcontrol *kcontrol, { u8 value = lm4857_regs[LM4857_CTRL] & 0x0F; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (value) value -= 5; @@ -399,7 +391,7 @@ static int lm4857_set_mode(struct snd_kcontrol *kcontrol, { u8 value = ucontrol->value.integer.value[0]; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (value) value += 5; @@ -508,7 +500,7 @@ static int neo1973_wm8753_init(struct snd_soc_codec *codec) { int i, err; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); /* set up NC codec pins */ snd_soc_dapm_nc_pin(codec, "LOUT2"); @@ -593,7 +585,7 @@ static struct snd_soc_device neo1973_snd_devdata = { static int lm4857_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); i2c = client; @@ -603,7 +595,7 @@ static int lm4857_i2c_probe(struct i2c_client *client, static int lm4857_i2c_remove(struct i2c_client *client) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); i2c = NULL; @@ -614,7 +606,7 @@ static u8 lm4857_state; static int lm4857_suspend(struct i2c_client *dev, pm_message_t state) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); dev_dbg(&dev->dev, "lm4857_suspend\n"); lm4857_state = lm4857_regs[LM4857_CTRL] & 0xf; @@ -627,7 +619,7 @@ static int lm4857_suspend(struct i2c_client *dev, pm_message_t state) static int lm4857_resume(struct i2c_client *dev) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (lm4857_state) { lm4857_regs[LM4857_CTRL] |= (lm4857_state & 0x0f); @@ -638,7 +630,7 @@ static int lm4857_resume(struct i2c_client *dev) static void lm4857_shutdown(struct i2c_client *dev) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); dev_dbg(&dev->dev, "lm4857_shutdown\n"); lm4857_regs[LM4857_CTRL] &= 0xf0; @@ -669,7 +661,7 @@ static int __init neo1973_init(void) { int ret; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (!machine_is_neo1973_gta01()) { printk(KERN_INFO @@ -700,7 +692,7 @@ static int __init neo1973_init(void) static void __exit neo1973_exit(void) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); i2c_del_driver(&lm4857_i2c_driver); platform_device_unregister(neo1973_snd_device); diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c new file mode 100644 index 000000000000..295a4c910262 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -0,0 +1,638 @@ +/* sound/soc/s3c24xx/s3c-i2c-v2.c + * + * ALSA Soc Audio Layer - I2S core for newer Samsung SoCs. + * + * Copyright (c) 2006 Wolfson Microelectronics PLC. + * Graeme Gregory graeme.gregory@wolfsonmicro.com + * linux@wolfsonmicro.com + * + * Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <plat/regs-s3c2412-iis.h> + +#include <plat/audio.h> +#include <mach/dma.h> + +#include "s3c-i2s-v2.h" + +#define S3C2412_I2S_DEBUG_CON 0 + +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return cpu_dai->private_data; +} + +#define bit_set(v, b) (((v) & (b)) ? 1 : 0) + +#if S3C2412_I2S_DEBUG_CON +static void dbg_showcon(const char *fn, u32 con) +{ + printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn, + bit_set(con, S3C2412_IISCON_LRINDEX), + bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY), + bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY), + bit_set(con, S3C2412_IISCON_TXFIFO_FULL), + bit_set(con, S3C2412_IISCON_RXFIFO_FULL)); + + printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n", + fn, + bit_set(con, S3C2412_IISCON_TXDMA_PAUSE), + bit_set(con, S3C2412_IISCON_RXDMA_PAUSE), + bit_set(con, S3C2412_IISCON_TXCH_PAUSE), + bit_set(con, S3C2412_IISCON_RXCH_PAUSE)); + printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn, + bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE), + bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE), + bit_set(con, S3C2412_IISCON_IIS_ACTIVE)); +} +#else +static inline void dbg_showcon(const char *fn, u32 con) +{ +} +#endif + + +/* Turn on or off the transmission path. */ +void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) +{ + void __iomem *regs = i2s->regs; + u32 fic, con, mod; + + pr_debug("%s(%d)\n", __func__, on); + + fic = readl(regs + S3C2412_IISFIC); + con = readl(regs + S3C2412_IISCON); + mod = readl(regs + S3C2412_IISMOD); + + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); + + if (on) { + con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; + con &= ~S3C2412_IISCON_TXDMA_PAUSE; + con &= ~S3C2412_IISCON_TXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXONLY: + case S3C2412_IISMOD_MODE_TXRX: + /* do nothing, we are in the right mode */ + break; + + case S3C2412_IISMOD_MODE_RXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXRX; + break; + + default: + dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n"); + } + + writel(con, regs + S3C2412_IISCON); + writel(mod, regs + S3C2412_IISMOD); + } else { + /* Note, we do not have any indication that the FIFO problems + * tha the S3C2410/2440 had apply here, so we should be able + * to disable the DMA and TX without resetting the FIFOS. + */ + + con |= S3C2412_IISCON_TXDMA_PAUSE; + con |= S3C2412_IISCON_TXCH_PAUSE; + con &= ~S3C2412_IISCON_TXDMA_ACTIVE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXRX: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_RXONLY; + break; + + case S3C2412_IISMOD_MODE_TXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + con &= ~S3C2412_IISCON_IIS_ACTIVE; + break; + + default: + dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n"); + } + + writel(mod, regs + S3C2412_IISMOD); + writel(con, regs + S3C2412_IISCON); + } + + fic = readl(regs + S3C2412_IISFIC); + dbg_showcon(__func__, con); + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); +} +EXPORT_SYMBOL_GPL(s3c2412_snd_txctrl); + +void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) +{ + void __iomem *regs = i2s->regs; + u32 fic, con, mod; + + pr_debug("%s(%d)\n", __func__, on); + + fic = readl(regs + S3C2412_IISFIC); + con = readl(regs + S3C2412_IISCON); + mod = readl(regs + S3C2412_IISMOD); + + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); + + if (on) { + con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; + con &= ~S3C2412_IISCON_RXDMA_PAUSE; + con &= ~S3C2412_IISCON_RXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_TXRX: + case S3C2412_IISMOD_MODE_RXONLY: + /* do nothing, we are in the right mode */ + break; + + case S3C2412_IISMOD_MODE_TXONLY: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXRX; + break; + + default: + dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + } + + writel(mod, regs + S3C2412_IISMOD); + writel(con, regs + S3C2412_IISCON); + } else { + /* See txctrl notes on FIFOs. */ + + con &= ~S3C2412_IISCON_RXDMA_ACTIVE; + con |= S3C2412_IISCON_RXDMA_PAUSE; + con |= S3C2412_IISCON_RXCH_PAUSE; + + switch (mod & S3C2412_IISMOD_MODE_MASK) { + case S3C2412_IISMOD_MODE_RXONLY: + con &= ~S3C2412_IISCON_IIS_ACTIVE; + mod &= ~S3C2412_IISMOD_MODE_MASK; + break; + + case S3C2412_IISMOD_MODE_TXRX: + mod &= ~S3C2412_IISMOD_MODE_MASK; + mod |= S3C2412_IISMOD_MODE_TXONLY; + break; + + default: + dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + } + + writel(con, regs + S3C2412_IISCON); + writel(mod, regs + S3C2412_IISMOD); + } + + fic = readl(regs + S3C2412_IISFIC); + pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); +} +EXPORT_SYMBOL_GPL(s3c2412_snd_rxctrl); + +/* + * Wait for the LR signal to allow synchronisation to the L/R clock + * from the codec. May only be needed for slave mode. + */ +static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s) +{ + u32 iiscon; + unsigned long timeout = jiffies + msecs_to_jiffies(5); + + pr_debug("Entered %s\n", __func__); + + while (1) { + iiscon = readl(i2s->regs + S3C2412_IISCON); + if (iiscon & S3C2412_IISCON_LRINDEX) + break; + + if (timeout < jiffies) { + printk(KERN_ERR "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + } + + return 0; +} + +/* + * Set S3C2412 I2S DAI format + */ +static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("hw_params r: IISMOD: %x \n", iismod); + +#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) +#define IISMOD_MASTER_MASK S3C2412_IISMOD_MASTER_MASK +#define IISMOD_SLAVE S3C2412_IISMOD_SLAVE +#define IISMOD_MASTER S3C2412_IISMOD_MASTER_INTERNAL +#endif + +#if defined(CONFIG_PLAT_S3C64XX) +/* From Rev1.1 datasheet, we have two master and two slave modes: + * IMS[11:10]: + * 00 = master mode, fed from PCLK + * 01 = master mode, fed from CLKAUDIO + * 10 = slave mode, using PCLK + * 11 = slave mode, using I2SCLK + */ +#define IISMOD_MASTER_MASK (1 << 11) +#define IISMOD_SLAVE (1 << 11) +#define IISMOD_MASTER (0x0) +#endif + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->master = 0; + iismod &= ~IISMOD_MASTER_MASK; + iismod |= IISMOD_SLAVE; + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->master = 1; + iismod &= ~IISMOD_MASTER_MASK; + iismod |= IISMOD_MASTER; + break; + default: + pr_debug("unknwon master/slave format\n"); + return -EINVAL; + } + + iismod &= ~S3C2412_IISMOD_SDF_MASK; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + iismod |= S3C2412_IISMOD_SDF_MSB; + break; + case SND_SOC_DAIFMT_LEFT_J: + iismod |= S3C2412_IISMOD_SDF_LSB; + break; + case SND_SOC_DAIFMT_I2S: + iismod |= S3C2412_IISMOD_SDF_IIS; + break; + default: + pr_debug("Unknown data format\n"); + return -EINVAL; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("hw_params w: IISMOD: %x \n", iismod); + return 0; +} + +static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *socdai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *dai = rtd->dai; + struct s3c_i2sv2_info *i2s = to_info(dai->cpu_dai); + u32 iismod; + + pr_debug("Entered %s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dai->cpu_dai->dma_data = i2s->dma_playback; + else + dai->cpu_dai->dma_data = i2s->dma_capture; + + /* Working copies of register */ + iismod = readl(i2s->regs + S3C2412_IISMOD); + pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + iismod |= S3C2412_IISMOD_8BIT; + break; + case SNDRV_PCM_FORMAT_S16_LE: + iismod &= ~S3C2412_IISMOD_8BIT; + break; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); + return 0; +} + +static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s3c_i2sv2_info *i2s = to_info(rtd->dai->cpu_dai); + int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); + unsigned long irqs; + int ret = 0; + + pr_debug("Entered %s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* On start, ensure that the FIFOs are cleared and reset. */ + + writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH, + i2s->regs + S3C2412_IISFIC); + + /* clear again, just in case */ + writel(0x0, i2s->regs + S3C2412_IISFIC); + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!i2s->master) { + ret = s3c2412_snd_lrsync(i2s); + if (ret) + goto exit_err; + } + + local_irq_save(irqs); + + if (capture) + s3c2412_snd_rxctrl(i2s, 1); + else + s3c2412_snd_txctrl(i2s, 1); + + local_irq_restore(irqs); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + local_irq_save(irqs); + + if (capture) + s3c2412_snd_rxctrl(i2s, 0); + else + s3c2412_snd_txctrl(i2s, 0); + + local_irq_restore(irqs); + break; + default: + ret = -EINVAL; + break; + } + +exit_err: + return ret; +} + +/* + * Set S3C2412 Clock dividers + */ +static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 reg; + + pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div); + + switch (div_id) { + case S3C_I2SV2_DIV_BCLK: + reg = readl(i2s->regs + S3C2412_IISMOD); + reg &= ~S3C2412_IISMOD_BCLK_MASK; + writel(reg | div, i2s->regs + S3C2412_IISMOD); + + pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); + break; + + case S3C_I2SV2_DIV_RCLK: + if (div > 3) { + /* convert value to bit field */ + + switch (div) { + case 256: + div = S3C2412_IISMOD_RCLK_256FS; + break; + + case 384: + div = S3C2412_IISMOD_RCLK_384FS; + break; + + case 512: + div = S3C2412_IISMOD_RCLK_512FS; + break; + + case 768: + div = S3C2412_IISMOD_RCLK_768FS; + break; + + default: + return -EINVAL; + } + } + + reg = readl(i2s->regs + S3C2412_IISMOD); + reg &= ~S3C2412_IISMOD_RCLK_MASK; + writel(reg | div, i2s->regs + S3C2412_IISMOD); + pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); + break; + + case S3C_I2SV2_DIV_PRESCALER: + if (div >= 0) { + writel((div << 8) | S3C2412_IISPSR_PSREN, + i2s->regs + S3C2412_IISPSR); + } else { + writel(0x0, i2s->regs + S3C2412_IISPSR); + } + pr_debug("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR)); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* default table of all avaialable root fs divisors */ +static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 }; + +int s3c2412_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, + unsigned int *fstab, + unsigned int rate, struct clk *clk) +{ + unsigned long clkrate = clk_get_rate(clk); + unsigned int div; + unsigned int fsclk; + unsigned int actual; + unsigned int fs; + unsigned int fsdiv; + signed int deviation = 0; + unsigned int best_fs = 0; + unsigned int best_div = 0; + unsigned int best_rate = 0; + unsigned int best_deviation = INT_MAX; + + if (fstab == NULL) + fstab = iis_fs_tab; + + for (fs = 0; fs < ARRAY_SIZE(iis_fs_tab); fs++) { + fsdiv = iis_fs_tab[fs]; + + fsclk = clkrate / fsdiv; + div = fsclk / rate; + + if ((fsclk % rate) > (rate / 2)) + div++; + + if (div <= 1) + continue; + + actual = clkrate / (fsdiv * div); + deviation = actual - rate; + + printk(KERN_DEBUG "%dfs: div %d => result %d, deviation %d\n", + fsdiv, div, actual, deviation); + + deviation = abs(deviation); + + if (deviation < best_deviation) { + best_fs = fsdiv; + best_div = div; + best_rate = actual; + best_deviation = deviation; + } + + if (deviation == 0) + break; + } + + printk(KERN_DEBUG "best: fs=%d, div=%d, rate=%d\n", + best_fs, best_div, best_rate); + + info->fs_div = best_fs; + info->clk_div = best_div; + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2412_iis_calc_rate); + +int s3c_i2sv2_probe(struct platform_device *pdev, + struct snd_soc_dai *dai, + struct s3c_i2sv2_info *i2s, + unsigned long base) +{ + struct device *dev = &pdev->dev; + + i2s->dev = dev; + + /* record our i2s structure for later use in the callbacks */ + dai->private_data = i2s; + + i2s->regs = ioremap(base, 0x100); + if (i2s->regs == NULL) { + dev_err(dev, "cannot ioremap registers\n"); + return -ENXIO; + } + + i2s->iis_pclk = clk_get(dev, "iis"); + if (i2s->iis_pclk == NULL) { + dev_err(dev, "failed to get iis_clock\n"); + iounmap(i2s->regs); + return -ENOENT; + } + + clk_enable(i2s->iis_pclk); + + s3c2412_snd_txctrl(i2s, 0); + s3c2412_snd_rxctrl(i2s, 0); + + return 0; +} + +EXPORT_SYMBOL_GPL(s3c_i2sv2_probe); + +#ifdef CONFIG_PM +static int s3c2412_i2s_suspend(struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + u32 iismod; + + if (dai->active) { + i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD); + i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON); + i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR); + + /* some basic suspend checks */ + + iismod = readl(i2s->regs + S3C2412_IISMOD); + + if (iismod & S3C2412_IISCON_RXDMA_ACTIVE) + pr_warning("%s: RXDMA active?\n", __func__); + + if (iismod & S3C2412_IISCON_TXDMA_ACTIVE) + pr_warning("%s: TXDMA active?\n", __func__); + + if (iismod & S3C2412_IISCON_IIS_ACTIVE) + pr_warning("%s: IIS active\n", __func__); + } + + return 0; +} + +static int s3c2412_i2s_resume(struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + + pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n", + dai->active, i2s->suspend_iismod, i2s->suspend_iiscon); + + if (dai->active) { + writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON); + writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD); + writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR); + + writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH, + i2s->regs + S3C2412_IISFIC); + + ndelay(250); + writel(0x0, i2s->regs + S3C2412_IISFIC); + } + + return 0; +} +#else +#define s3c2412_i2s_suspend NULL +#define s3c2412_i2s_resume NULL +#endif + +int s3c_i2sv2_register_dai(struct snd_soc_dai *dai) +{ + dai->ops.trigger = s3c2412_i2s_trigger; + dai->ops.hw_params = s3c2412_i2s_hw_params; + dai->ops.set_fmt = s3c2412_i2s_set_fmt; + dai->ops.set_clkdiv = s3c2412_i2s_set_clkdiv; + + dai->suspend = s3c2412_i2s_suspend; + dai->resume = s3c2412_i2s_resume; + + return snd_soc_register_dai(dai); +} + +EXPORT_SYMBOL_GPL(s3c_i2sv2_register_dai); diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.h b/sound/soc/s3c24xx/s3c-i2s-v2.h new file mode 100644 index 000000000000..f66854a77fb2 --- /dev/null +++ b/sound/soc/s3c24xx/s3c-i2s-v2.h @@ -0,0 +1,90 @@ +/* sound/soc/s3c24xx/s3c-i2s-v2.h + * + * ALSA Soc Audio Layer - S3C_I2SV2 I2S driver + * + * Copyright (c) 2007 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. +*/ + +/* This code is the core support for the I2S block found in a number of + * Samsung SoC devices which is unofficially named I2S-V2. Currently the + * S3C2412 and the S3C64XX series use this block to provide 1 or 2 I2S + * channels via configurable GPIO. + */ + +#ifndef __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H +#define __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H __FILE__ + +#define S3C_I2SV2_DIV_BCLK (1) +#define S3C_I2SV2_DIV_RCLK (2) +#define S3C_I2SV2_DIV_PRESCALER (3) + +/** + * struct s3c_i2sv2_info - S3C I2S-V2 information + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device registe block. + * @master: True if the I2S core is the I2S bit clock master. + * @dma_playback: DMA information for playback channel. + * @dma_capture: DMA information for capture channel. + * @suspend_iismod: PM save for the IISMOD register. + * @suspend_iiscon: PM save for the IISCON register. + * @suspend_iispsr: PM save for the IISPSR register. + * + * This is the private codec state for the hardware associated with an + * I2S channel such as the register mappings and clock sources. + */ +struct s3c_i2sv2_info { + struct device *dev; + void __iomem *regs; + + struct clk *iis_pclk; + struct clk *iis_cclk; + struct clk *iis_clk; + + unsigned char master; + + struct s3c24xx_pcm_dma_params *dma_playback; + struct s3c24xx_pcm_dma_params *dma_capture; + + u32 suspend_iismod; + u32 suspend_iiscon; + u32 suspend_iispsr; +}; + +struct s3c_i2sv2_rate_calc { + unsigned int clk_div; /* for prescaler */ + unsigned int fs_div; /* for root frame clock */ +}; + +extern int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, + unsigned int *fstab, + unsigned int rate, struct clk *clk); + +/** + * s3c_i2sv2_probe - probe for i2s device helper + * @pdev: The platform device supplied to the original probe. + * @dai: The ASoC DAI structure supplied to the original probe. + * @i2s: Our local i2s structure to fill in. + * @base: The base address for the registers. + */ +extern int s3c_i2sv2_probe(struct platform_device *pdev, + struct snd_soc_dai *dai, + struct s3c_i2sv2_info *i2s, + unsigned long base); + +/** + * s3c_i2sv2_register_dai - register dai with soc core + * @dai: The snd_soc_dai structure to register + * + * Fill in any missing fields and then register the given dai with the + * soc core. + */ +extern int s3c_i2sv2_register_dai(struct snd_soc_dai *dai); + +#endif /* __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H */ diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index 382d7eee53ef..1ca3cdaa8213 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -22,6 +22,7 @@ #include <linux/delay.h> #include <linux/clk.h> #include <linux/kernel.h> +#include <linux/io.h> #include <sound/core.h> #include <sound/pcm.h> @@ -30,26 +31,16 @@ #include <sound/soc.h> #include <mach/hardware.h> -#include <linux/io.h> -#include <asm/dma.h> - -#include <asm/plat-s3c24xx/regs-s3c2412-iis.h> +#include <plat/regs-s3c2412-iis.h> -#include <mach/regs-gpio.h> -#include <mach/audio.h> +#include <plat/regs-gpio.h> +#include <plat/audio.h> #include <mach/dma.h> #include "s3c24xx-pcm.h" #include "s3c2412-i2s.h" #define S3C2412_I2S_DEBUG 0 -#define S3C2412_I2S_DEBUG_CON 0 - -#if S3C2412_I2S_DEBUG -#define DBG(x...) printk(KERN_INFO x) -#else -#define DBG(x...) do { } while (0) -#endif static struct s3c2410_dma_client s3c2412_dma_client_out = { .name = "I2S PCM Stereo out" @@ -73,431 +64,7 @@ static struct s3c24xx_pcm_dma_params s3c2412_i2s_pcm_stereo_in = { .dma_size = 4, }; -struct s3c2412_i2s_info { - struct device *dev; - void __iomem *regs; - struct clk *iis_clk; - struct clk *iis_pclk; - struct clk *iis_cclk; - - u32 suspend_iismod; - u32 suspend_iiscon; - u32 suspend_iispsr; -}; - -static struct s3c2412_i2s_info s3c2412_i2s; - -#define bit_set(v, b) (((v) & (b)) ? 1 : 0) - -#if S3C2412_I2S_DEBUG_CON -static void dbg_showcon(const char *fn, u32 con) -{ - printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn, - bit_set(con, S3C2412_IISCON_LRINDEX), - bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY), - bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY), - bit_set(con, S3C2412_IISCON_TXFIFO_FULL), - bit_set(con, S3C2412_IISCON_RXFIFO_FULL)); - - printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n", - fn, - bit_set(con, S3C2412_IISCON_TXDMA_PAUSE), - bit_set(con, S3C2412_IISCON_RXDMA_PAUSE), - bit_set(con, S3C2412_IISCON_TXCH_PAUSE), - bit_set(con, S3C2412_IISCON_RXCH_PAUSE)); - printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn, - bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE), - bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE), - bit_set(con, S3C2412_IISCON_IIS_ACTIVE)); -} -#else -static inline void dbg_showcon(const char *fn, u32 con) -{ -} -#endif - -/* Turn on or off the transmission path. */ -static void s3c2412_snd_txctrl(int on) -{ - struct s3c2412_i2s_info *i2s = &s3c2412_i2s; - void __iomem *regs = i2s->regs; - u32 fic, con, mod; - - DBG("%s(%d)\n", __func__, on); - - fic = readl(regs + S3C2412_IISFIC); - con = readl(regs + S3C2412_IISCON); - mod = readl(regs + S3C2412_IISMOD); - - DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); - - if (on) { - con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; - con &= ~S3C2412_IISCON_TXDMA_PAUSE; - con &= ~S3C2412_IISCON_TXCH_PAUSE; - - switch (mod & S3C2412_IISMOD_MODE_MASK) { - case S3C2412_IISMOD_MODE_TXONLY: - case S3C2412_IISMOD_MODE_TXRX: - /* do nothing, we are in the right mode */ - break; - - case S3C2412_IISMOD_MODE_RXONLY: - mod &= ~S3C2412_IISMOD_MODE_MASK; - mod |= S3C2412_IISMOD_MODE_TXRX; - break; - - default: - dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n"); - } - - writel(con, regs + S3C2412_IISCON); - writel(mod, regs + S3C2412_IISMOD); - } else { - /* Note, we do not have any indication that the FIFO problems - * tha the S3C2410/2440 had apply here, so we should be able - * to disable the DMA and TX without resetting the FIFOS. - */ - - con |= S3C2412_IISCON_TXDMA_PAUSE; - con |= S3C2412_IISCON_TXCH_PAUSE; - con &= ~S3C2412_IISCON_TXDMA_ACTIVE; - - switch (mod & S3C2412_IISMOD_MODE_MASK) { - case S3C2412_IISMOD_MODE_TXRX: - mod &= ~S3C2412_IISMOD_MODE_MASK; - mod |= S3C2412_IISMOD_MODE_RXONLY; - break; - - case S3C2412_IISMOD_MODE_TXONLY: - mod &= ~S3C2412_IISMOD_MODE_MASK; - con &= ~S3C2412_IISCON_IIS_ACTIVE; - break; - - default: - dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n"); - } - - writel(mod, regs + S3C2412_IISMOD); - writel(con, regs + S3C2412_IISCON); - } - - fic = readl(regs + S3C2412_IISFIC); - dbg_showcon(__func__, con); - DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); -} - -static void s3c2412_snd_rxctrl(int on) -{ - struct s3c2412_i2s_info *i2s = &s3c2412_i2s; - void __iomem *regs = i2s->regs; - u32 fic, con, mod; - - DBG("%s(%d)\n", __func__, on); - - fic = readl(regs + S3C2412_IISFIC); - con = readl(regs + S3C2412_IISCON); - mod = readl(regs + S3C2412_IISMOD); - - DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); - - if (on) { - con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; - con &= ~S3C2412_IISCON_RXDMA_PAUSE; - con &= ~S3C2412_IISCON_RXCH_PAUSE; - - switch (mod & S3C2412_IISMOD_MODE_MASK) { - case S3C2412_IISMOD_MODE_TXRX: - case S3C2412_IISMOD_MODE_RXONLY: - /* do nothing, we are in the right mode */ - break; - - case S3C2412_IISMOD_MODE_TXONLY: - mod &= ~S3C2412_IISMOD_MODE_MASK; - mod |= S3C2412_IISMOD_MODE_TXRX; - break; - - default: - dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); - } - - writel(mod, regs + S3C2412_IISMOD); - writel(con, regs + S3C2412_IISCON); - } else { - /* See txctrl notes on FIFOs. */ - - con &= ~S3C2412_IISCON_RXDMA_ACTIVE; - con |= S3C2412_IISCON_RXDMA_PAUSE; - con |= S3C2412_IISCON_RXCH_PAUSE; - - switch (mod & S3C2412_IISMOD_MODE_MASK) { - case S3C2412_IISMOD_MODE_RXONLY: - con &= ~S3C2412_IISCON_IIS_ACTIVE; - mod &= ~S3C2412_IISMOD_MODE_MASK; - break; - - case S3C2412_IISMOD_MODE_TXRX: - mod &= ~S3C2412_IISMOD_MODE_MASK; - mod |= S3C2412_IISMOD_MODE_TXONLY; - break; - - default: - dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); - } - - writel(con, regs + S3C2412_IISCON); - writel(mod, regs + S3C2412_IISMOD); - } - - fic = readl(regs + S3C2412_IISFIC); - DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); -} - - -/* - * Wait for the LR signal to allow synchronisation to the L/R clock - * from the codec. May only be needed for slave mode. - */ -static int s3c2412_snd_lrsync(void) -{ - u32 iiscon; - unsigned long timeout = jiffies + msecs_to_jiffies(5); - - DBG("Entered %s\n", __func__); - - while (1) { - iiscon = readl(s3c2412_i2s.regs + S3C2412_IISCON); - if (iiscon & S3C2412_IISCON_LRINDEX) - break; - - if (timeout < jiffies) { - printk(KERN_ERR "%s: timeout\n", __func__); - return -ETIMEDOUT; - } - } - - return 0; -} - -/* - * Check whether CPU is the master or slave - */ -static inline int s3c2412_snd_is_clkmaster(void) -{ - u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); - - DBG("Entered %s\n", __func__); - - iismod &= S3C2412_IISMOD_MASTER_MASK; - return !(iismod == S3C2412_IISMOD_SLAVE); -} - -/* - * Set S3C2412 I2S DAI format - */ -static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, - unsigned int fmt) -{ - u32 iismod; - - - DBG("Entered %s\n", __func__); - - iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); - DBG("hw_params r: IISMOD: %x \n", iismod); - - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - iismod &= ~S3C2412_IISMOD_MASTER_MASK; - iismod |= S3C2412_IISMOD_SLAVE; - break; - case SND_SOC_DAIFMT_CBS_CFS: - iismod &= ~S3C2412_IISMOD_MASTER_MASK; - iismod |= S3C2412_IISMOD_MASTER_INTERNAL; - break; - default: - DBG("unknwon master/slave format\n"); - return -EINVAL; - } - - iismod &= ~S3C2412_IISMOD_SDF_MASK; - - switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_RIGHT_J: - iismod |= S3C2412_IISMOD_SDF_MSB; - break; - case SND_SOC_DAIFMT_LEFT_J: - iismod |= S3C2412_IISMOD_SDF_LSB; - break; - case SND_SOC_DAIFMT_I2S: - iismod |= S3C2412_IISMOD_SDF_IIS; - break; - default: - DBG("Unknown data format\n"); - return -EINVAL; - } - - writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD); - DBG("hw_params w: IISMOD: %x \n", iismod); - return 0; -} - -static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - u32 iismod; - - DBG("Entered %s\n", __func__); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - rtd->dai->cpu_dai->dma_data = &s3c2412_i2s_pcm_stereo_out; - else - rtd->dai->cpu_dai->dma_data = &s3c2412_i2s_pcm_stereo_in; - - /* Working copies of register */ - iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); - DBG("%s: r: IISMOD: %x\n", __func__, iismod); - - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S8: - iismod |= S3C2412_IISMOD_8BIT; - break; - case SNDRV_PCM_FORMAT_S16_LE: - iismod &= ~S3C2412_IISMOD_8BIT; - break; - } - - writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD); - DBG("%s: w: IISMOD: %x\n", __func__, iismod); - return 0; -} - -static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); - unsigned long irqs; - int ret = 0; - - DBG("Entered %s\n", __func__); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - /* On start, ensure that the FIFOs are cleared and reset. */ - - writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH, - s3c2412_i2s.regs + S3C2412_IISFIC); - - /* clear again, just in case */ - writel(0x0, s3c2412_i2s.regs + S3C2412_IISFIC); - - case SNDRV_PCM_TRIGGER_RESUME: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (!s3c2412_snd_is_clkmaster()) { - ret = s3c2412_snd_lrsync(); - if (ret) - goto exit_err; - } - - local_irq_save(irqs); - - if (capture) - s3c2412_snd_rxctrl(1); - else - s3c2412_snd_txctrl(1); - - local_irq_restore(irqs); - break; - - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - local_irq_save(irqs); - - if (capture) - s3c2412_snd_rxctrl(0); - else - s3c2412_snd_txctrl(0); - - local_irq_restore(irqs); - break; - default: - ret = -EINVAL; - break; - } - -exit_err: - return ret; -} - -/* default table of all avaialable root fs divisors */ -static unsigned int s3c2412_iis_fs[] = { 256, 512, 384, 768, 0 }; - -int s3c2412_iis_calc_rate(struct s3c2412_rate_calc *info, - unsigned int *fstab, - unsigned int rate, struct clk *clk) -{ - unsigned long clkrate = clk_get_rate(clk); - unsigned int div; - unsigned int fsclk; - unsigned int actual; - unsigned int fs; - unsigned int fsdiv; - signed int deviation = 0; - unsigned int best_fs = 0; - unsigned int best_div = 0; - unsigned int best_rate = 0; - unsigned int best_deviation = INT_MAX; - - - if (fstab == NULL) - fstab = s3c2412_iis_fs; - - for (fs = 0;; fs++) { - fsdiv = s3c2412_iis_fs[fs]; - - if (fsdiv == 0) - break; - - fsclk = clkrate / fsdiv; - div = fsclk / rate; - - if ((fsclk % rate) > (rate / 2)) - div++; - - if (div <= 1) - continue; - - actual = clkrate / (fsdiv * div); - deviation = actual - rate; - - printk(KERN_DEBUG "%dfs: div %d => result %d, deviation %d\n", - fsdiv, div, actual, deviation); - - deviation = abs(deviation); - - if (deviation < best_deviation) { - best_fs = fsdiv; - best_div = div; - best_rate = actual; - best_deviation = deviation; - } - - if (deviation == 0) - break; - } - - printk(KERN_DEBUG "best: fs=%d, div=%d, rate=%d\n", - best_fs, best_div, best_rate); - - info->fs_div = best_fs; - info->clk_div = best_div; - - return 0; -} -EXPORT_SYMBOL_GPL(s3c2412_iis_calc_rate); +static struct s3c_i2sv2_info s3c2412_i2s; /* * Set S3C2412 Clock source @@ -507,15 +74,17 @@ static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, { u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD); - DBG("%s(%p, %d, %u, %d)\n", __func__, cpu_dai, clk_id, + pr_debug("%s(%p, %d, %u, %d)\n", __func__, cpu_dai, clk_id, freq, dir); switch (clk_id) { case S3C2412_CLKSRC_PCLK: + s3c2412_i2s.master = 1; iismod &= ~S3C2412_IISMOD_MASTER_MASK; iismod |= S3C2412_IISMOD_MASTER_INTERNAL; break; case S3C2412_CLKSRC_I2SCLK: + s3c2412_i2s.master = 0; iismod &= ~S3C2412_IISMOD_MASTER_MASK; iismod |= S3C2412_IISMOD_MASTER_EXTERNAL; break; @@ -527,74 +96,6 @@ static int s3c2412_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, return 0; } -/* - * Set S3C2412 Clock dividers - */ -static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, - int div_id, int div) -{ - struct s3c2412_i2s_info *i2s = &s3c2412_i2s; - u32 reg; - - DBG("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div); - - switch (div_id) { - case S3C2412_DIV_BCLK: - reg = readl(i2s->regs + S3C2412_IISMOD); - reg &= ~S3C2412_IISMOD_BCLK_MASK; - writel(reg | div, i2s->regs + S3C2412_IISMOD); - - DBG("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); - break; - - case S3C2412_DIV_RCLK: - if (div > 3) { - /* convert value to bit field */ - - switch (div) { - case 256: - div = S3C2412_IISMOD_RCLK_256FS; - break; - - case 384: - div = S3C2412_IISMOD_RCLK_384FS; - break; - - case 512: - div = S3C2412_IISMOD_RCLK_512FS; - break; - - case 768: - div = S3C2412_IISMOD_RCLK_768FS; - break; - - default: - return -EINVAL; - } - } - - reg = readl(s3c2412_i2s.regs + S3C2412_IISMOD); - reg &= ~S3C2412_IISMOD_RCLK_MASK; - writel(reg | div, i2s->regs + S3C2412_IISMOD); - DBG("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); - break; - - case S3C2412_DIV_PRESCALER: - if (div >= 0) { - writel((div << 8) | S3C2412_IISPSR_PSREN, - i2s->regs + S3C2412_IISPSR); - } else { - writel(0x0, i2s->regs + S3C2412_IISPSR); - } - DBG("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR)); - break; - - default: - return -EINVAL; - } - - return 0; -} struct clk *s3c2412_get_iisclk(void) { @@ -606,34 +107,30 @@ EXPORT_SYMBOL_GPL(s3c2412_get_iisclk); static int s3c2412_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { - DBG("Entered %s\n", __func__); + int ret; - s3c2412_i2s.dev = &pdev->dev; + pr_debug("Entered %s\n", __func__); - s3c2412_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100); - if (s3c2412_i2s.regs == NULL) - return -ENXIO; + ret = s3c_i2sv2_probe(pdev, dai, &s3c2412_i2s, S3C2410_PA_IIS); + if (ret) + return ret; - s3c2412_i2s.iis_pclk = clk_get(&pdev->dev, "iis"); - if (s3c2412_i2s.iis_pclk == NULL) { - DBG("failed to get iis_clock\n"); - iounmap(s3c2412_i2s.regs); - return -ENODEV; - } + s3c2412_i2s.dma_capture = &s3c2412_i2s_pcm_stereo_in; + s3c2412_i2s.dma_playback = &s3c2412_i2s_pcm_stereo_out; s3c2412_i2s.iis_cclk = clk_get(&pdev->dev, "i2sclk"); if (s3c2412_i2s.iis_cclk == NULL) { - DBG("failed to get i2sclk clock\n"); + pr_debug("failed to get i2sclk clock\n"); iounmap(s3c2412_i2s.regs); return -ENODEV; } - clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll")); + /* Set MPLL as the source for IIS CLK */ - clk_enable(s3c2412_i2s.iis_pclk); + clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll")); clk_enable(s3c2412_i2s.iis_cclk); - s3c2412_i2s.iis_clk = s3c2412_i2s.iis_pclk; + s3c2412_i2s.iis_cclk = s3c2412_i2s.iis_pclk; /* Configure the I2S pins in correct mode */ s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK); @@ -642,66 +139,8 @@ static int s3c2412_i2s_probe(struct platform_device *pdev, s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI); s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO); - s3c2412_snd_txctrl(0); - s3c2412_snd_rxctrl(0); - - return 0; -} - -#ifdef CONFIG_PM -static int s3c2412_i2s_suspend(struct snd_soc_dai *dai) -{ - struct s3c2412_i2s_info *i2s = &s3c2412_i2s; - u32 iismod; - - if (dai->active) { - i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD); - i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON); - i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR); - - /* some basic suspend checks */ - - iismod = readl(i2s->regs + S3C2412_IISMOD); - - if (iismod & S3C2412_IISCON_RXDMA_ACTIVE) - pr_warning("%s: RXDMA active?\n", __func__); - - if (iismod & S3C2412_IISCON_TXDMA_ACTIVE) - pr_warning("%s: TXDMA active?\n", __func__); - - if (iismod & S3C2412_IISCON_IIS_ACTIVE) - pr_warning("%s: IIS active\n", __func__); - } - - return 0; -} - -static int s3c2412_i2s_resume(struct snd_soc_dai *dai) -{ - struct s3c2412_i2s_info *i2s = &s3c2412_i2s; - - pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n", - dai->active, i2s->suspend_iismod, i2s->suspend_iiscon); - - if (dai->active) { - writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON); - writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD); - writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR); - - writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH, - i2s->regs + S3C2412_IISFIC); - - ndelay(250); - writel(0x0, i2s->regs + S3C2412_IISFIC); - - } - return 0; } -#else -#define s3c2412_i2s_suspend NULL -#define s3c2412_i2s_resume NULL -#endif /* CONFIG_PM */ #define S3C2412_I2S_RATES \ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ @@ -709,19 +148,13 @@ static int s3c2412_i2s_resume(struct snd_soc_dai *dai) SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) static struct snd_soc_dai_ops s3c2412_i2s_dai_ops = { - .trigger = s3c2412_i2s_trigger, - .hw_params = s3c2412_i2s_hw_params, - .set_fmt = s3c2412_i2s_set_fmt, - .set_clkdiv = s3c2412_i2s_set_clkdiv, .set_sysclk = s3c2412_i2s_set_sysclk, }; struct snd_soc_dai s3c2412_i2s_dai = { - .name = "s3c2412-i2s", - .id = 0, - .probe = s3c2412_i2s_probe, - .suspend = s3c2412_i2s_suspend, - .resume = s3c2412_i2s_resume, + .name = "s3c2412-i2s", + .id = 0, + .probe = s3c2412_i2s_probe, .playback = { .channels_min = 2, .channels_max = 2, @@ -740,7 +173,7 @@ EXPORT_SYMBOL_GPL(s3c2412_i2s_dai); static int __init s3c2412_i2s_init(void) { - return snd_soc_register_dai(&s3c2412_i2s_dai); + return s3c_i2sv2_register_dai(&s3c2412_i2s_dai); } module_init(s3c2412_i2s_init); @@ -750,7 +183,6 @@ static void __exit s3c2412_i2s_exit(void) } module_exit(s3c2412_i2s_exit); - /* Module information */ MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); MODULE_DESCRIPTION("S3C2412 I2S SoC Interface"); diff --git a/sound/soc/s3c24xx/s3c2412-i2s.h b/sound/soc/s3c24xx/s3c2412-i2s.h index aac08a25e541..92848e54be16 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.h +++ b/sound/soc/s3c24xx/s3c2412-i2s.h @@ -15,9 +15,11 @@ #ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H #define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__ -#define S3C2412_DIV_BCLK (1) -#define S3C2412_DIV_RCLK (2) -#define S3C2412_DIV_PRESCALER (3) +#include "s3c-i2s-v2.h" + +#define S3C2412_DIV_BCLK S3C_I2SV2_DIV_BCLK +#define S3C2412_DIV_RCLK S3C_I2SV2_DIV_RCLK +#define S3C2412_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER #define S3C2412_CLKSRC_PCLK (0) #define S3C2412_CLKSRC_I2SCLK (1) @@ -26,13 +28,4 @@ extern struct clk *s3c2412_get_iisclk(void); extern struct snd_soc_dai s3c2412_i2s_dai; -struct s3c2412_rate_calc { - unsigned int clk_div; /* for prescaler */ - unsigned int fs_div; /* for root frame clock */ -}; - -extern int s3c2412_iis_calc_rate(struct s3c2412_rate_calc *info, - unsigned int *fstab, - unsigned int rate, struct clk *clk); - #endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */ diff --git a/sound/soc/s3c24xx/s3c2443-ac97.c b/sound/soc/s3c24xx/s3c2443-ac97.c index 83ea623234e7..3698f707c44d 100644 --- a/sound/soc/s3c24xx/s3c2443-ac97.c +++ b/sound/soc/s3c24xx/s3c2443-ac97.c @@ -31,7 +31,7 @@ #include <plat/regs-ac97.h> #include <mach/regs-gpio.h> #include <mach/regs-clock.h> -#include <mach/audio.h> +#include <plat/audio.h> #include <asm/dma.h> #include <mach/dma.h> @@ -360,6 +360,11 @@ static struct snd_soc_dai_ops s3c2443_ac97_dai_ops = { .trigger = s3c2443_ac97_trigger, }; +static struct snd_soc_dai_ops s3c2443_ac97_mic_dai_ops = { + .hw_params = s3c2443_ac97_hw_mic_params, + .trigger = s3c2443_ac97_mic_trigger, +}; + struct snd_soc_dai s3c2443_ac97_dai[] = { { .name = "s3c2443-ac97", @@ -391,7 +396,7 @@ struct snd_soc_dai s3c2443_ac97_dai[] = { .channels_max = 1, .rates = s3c2443_AC97_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = &s3c2443_ac97_dai_ops, + .ops = &s3c2443_ac97_mic_dai_ops, }, }; EXPORT_SYMBOL_GPL(s3c2443_ac97_dai); diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c index 4473fb584c4c..cc066964dad6 100644 --- a/sound/soc/s3c24xx/s3c24xx-i2s.c +++ b/sound/soc/s3c24xx/s3c24xx-i2s.c @@ -30,22 +30,15 @@ #include <mach/hardware.h> #include <mach/regs-gpio.h> #include <mach/regs-clock.h> -#include <mach/audio.h> +#include <plat/audio.h> #include <asm/dma.h> #include <mach/dma.h> -#include <asm/plat-s3c24xx/regs-iis.h> +#include <plat/regs-iis.h> #include "s3c24xx-pcm.h" #include "s3c24xx-i2s.h" -#define S3C24XX_I2S_DEBUG 0 -#if S3C24XX_I2S_DEBUG -#define DBG(x...) printk(KERN_DEBUG "s3c24xx-i2s: " x) -#else -#define DBG(x...) -#endif - static struct s3c2410_dma_client s3c24xx_dma_client_out = { .name = "I2S PCM Stereo out" }; @@ -84,13 +77,13 @@ static void s3c24xx_snd_txctrl(int on) u32 iiscon; u32 iismod; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); - DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); + pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); if (on) { iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; @@ -120,7 +113,7 @@ static void s3c24xx_snd_txctrl(int on) writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); } - DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); + pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); } static void s3c24xx_snd_rxctrl(int on) @@ -129,13 +122,13 @@ static void s3c24xx_snd_rxctrl(int on) u32 iiscon; u32 iismod; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); - DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); + pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); if (on) { iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; @@ -165,7 +158,7 @@ static void s3c24xx_snd_rxctrl(int on) writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); } - DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); + pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon); } /* @@ -177,7 +170,7 @@ static int s3c24xx_snd_lrsync(void) u32 iiscon; int timeout = 50; /* 5ms */ - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); while (1) { iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); @@ -197,7 +190,7 @@ static int s3c24xx_snd_lrsync(void) */ static inline int s3c24xx_snd_is_clkmaster(void) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; } @@ -210,10 +203,10 @@ static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, { u32 iismod; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); - DBG("hw_params r: IISMOD: %lx \n", iismod); + pr_debug("hw_params r: IISMOD: %x \n", iismod); switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: @@ -238,7 +231,7 @@ static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai, } writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); - DBG("hw_params w: IISMOD: %lx \n", iismod); + pr_debug("hw_params w: IISMOD: %x \n", iismod); return 0; } @@ -249,7 +242,7 @@ static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; u32 iismod; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out; @@ -258,7 +251,7 @@ static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, /* Working copies of register */ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); - DBG("hw_params r: IISMOD: %lx\n", iismod); + pr_debug("hw_params r: IISMOD: %x\n", iismod); switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: @@ -276,7 +269,7 @@ static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, } writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); - DBG("hw_params w: IISMOD: %lx\n", iismod); + pr_debug("hw_params w: IISMOD: %x\n", iismod); return 0; } @@ -285,7 +278,7 @@ static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, { int ret = 0; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -327,7 +320,7 @@ static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, { u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); iismod &= ~S3C2440_IISMOD_MPLL; @@ -353,7 +346,7 @@ static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, { u32 reg; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); switch (div_id) { case S3C24XX_DIV_BCLK: @@ -389,7 +382,7 @@ EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); static int s3c24xx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100); if (s3c24xx_i2s.regs == NULL) @@ -397,7 +390,7 @@ static int s3c24xx_i2s_probe(struct platform_device *pdev, s3c24xx_i2s.iis_clk = clk_get(&pdev->dev, "iis"); if (s3c24xx_i2s.iis_clk == NULL) { - DBG("failed to get iis_clock\n"); + pr_err("failed to get iis_clock\n"); iounmap(s3c24xx_i2s.regs); return -ENODEV; } @@ -421,7 +414,7 @@ static int s3c24xx_i2s_probe(struct platform_device *pdev, #ifdef CONFIG_PM static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); @@ -435,7 +428,7 @@ static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai) static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai) { - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); clk_enable(s3c24xx_i2s.iis_clk); writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c index ba1ae09dfaed..a9d68fa2b34a 100644 --- a/sound/soc/s3c24xx/s3c24xx-pcm.c +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -29,17 +29,10 @@ #include <asm/dma.h> #include <mach/hardware.h> #include <mach/dma.h> -#include <mach/audio.h> +#include <plat/audio.h> #include "s3c24xx-pcm.h" -#define S3C24XX_PCM_DEBUG 0 -#if S3C24XX_PCM_DEBUG -#define DBG(x...) printk(KERN_DEBUG "s3c24xx-pcm: " x) -#else -#define DBG(x...) -#endif - static const struct snd_pcm_hardware s3c24xx_pcm_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | @@ -84,16 +77,16 @@ static void s3c24xx_pcm_enqueue(struct snd_pcm_substream *substream) dma_addr_t pos = prtd->dma_pos; int ret; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); while (prtd->dma_loaded < prtd->dma_limit) { unsigned long len = prtd->dma_period; - DBG("dma_loaded: %d\n", prtd->dma_loaded); + pr_debug("dma_loaded: %d\n", prtd->dma_loaded); if ((pos + len) > prtd->dma_end) { len = prtd->dma_end - pos; - DBG(KERN_DEBUG "%s: corrected dma len %ld\n", + pr_debug(KERN_DEBUG "%s: corrected dma len %ld\n", __func__, len); } @@ -119,7 +112,7 @@ static void s3c24xx_audio_buffdone(struct s3c2410_dma_chan *channel, struct snd_pcm_substream *substream = dev_id; struct s3c24xx_runtime_data *prtd; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (result == S3C2410_RES_ABORT || result == S3C2410_RES_ERR) return; @@ -148,7 +141,7 @@ static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream, unsigned long totbytes = params_buffer_bytes(params); int ret = 0; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); /* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */ @@ -161,14 +154,14 @@ static int s3c24xx_pcm_hw_params(struct snd_pcm_substream *substream, /* prepare DMA */ prtd->params = dma; - DBG("params %p, client %p, channel %d\n", prtd->params, + pr_debug("params %p, client %p, channel %d\n", prtd->params, prtd->params->client, prtd->params->channel); ret = s3c2410_dma_request(prtd->params->channel, prtd->params->client, NULL); if (ret < 0) { - DBG(KERN_ERR "failed to get dma channel\n"); + printk(KERN_ERR "failed to get dma channel\n"); return ret; } } @@ -196,7 +189,7 @@ static int s3c24xx_pcm_hw_free(struct snd_pcm_substream *substream) { struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); /* TODO - do we need to ensure DMA flushed */ snd_pcm_set_runtime_buffer(substream, NULL); @@ -214,7 +207,7 @@ static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream) struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; int ret = 0; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); /* return if this is a bufferless transfer e.g. * codec <--> BT codec or GSM modem -- lg FIXME */ @@ -259,7 +252,7 @@ static int s3c24xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) struct s3c24xx_runtime_data *prtd = substream->runtime->private_data; int ret = 0; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); spin_lock(&prtd->lock); @@ -297,7 +290,7 @@ s3c24xx_pcm_pointer(struct snd_pcm_substream *substream) unsigned long res; dma_addr_t src, dst; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); spin_lock(&prtd->lock); s3c2410_dma_getposition(prtd->params->channel, &src, &dst); @@ -309,7 +302,7 @@ s3c24xx_pcm_pointer(struct snd_pcm_substream *substream) spin_unlock(&prtd->lock); - DBG("Pointer %x %x\n", src, dst); + pr_debug("Pointer %x %x\n", src, dst); /* we seem to be getting the odd error from the pcm library due * to out-of-bounds pointers. this is maybe due to the dma engine @@ -330,7 +323,7 @@ static int s3c24xx_pcm_open(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct s3c24xx_runtime_data *prtd; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); snd_soc_set_runtime_hwparams(substream, &s3c24xx_pcm_hardware); @@ -349,10 +342,10 @@ static int s3c24xx_pcm_close(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct s3c24xx_runtime_data *prtd = runtime->private_data; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (!prtd) - DBG("s3c24xx_pcm_close called with prtd == NULL\n"); + pr_debug("s3c24xx_pcm_close called with prtd == NULL\n"); kfree(prtd); @@ -364,7 +357,7 @@ static int s3c24xx_pcm_mmap(struct snd_pcm_substream *substream, { struct snd_pcm_runtime *runtime = substream->runtime; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); return dma_mmap_writecombine(substream->pcm->card->dev, vma, runtime->dma_area, @@ -390,7 +383,7 @@ static int s3c24xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) struct snd_dma_buffer *buf = &substream->dma_buffer; size_t size = s3c24xx_pcm_hardware.buffer_bytes_max; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; @@ -409,7 +402,7 @@ static void s3c24xx_pcm_free_dma_buffers(struct snd_pcm *pcm) struct snd_dma_buffer *buf; int stream; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); for (stream = 0; stream < 2; stream++) { substream = pcm->streams[stream].substream; @@ -433,7 +426,7 @@ static int s3c24xx_pcm_new(struct snd_card *card, { int ret = 0; - DBG("Entered %s\n", __func__); + pr_debug("Entered %s\n", __func__); if (!card->dev->dma_mask) card->dev->dma_mask = &s3c24xx_pcm_dmamask; diff --git a/sound/soc/s3c24xx/s3c24xx_uda134x.c b/sound/soc/s3c24xx/s3c24xx_uda134x.c index a0a4d1832a14..8e79a416db57 100644 --- a/sound/soc/s3c24xx/s3c24xx_uda134x.c +++ b/sound/soc/s3c24xx/s3c24xx_uda134x.c @@ -22,7 +22,7 @@ #include <sound/s3c24xx_uda134x.h> #include <sound/uda134x.h> -#include <asm/plat-s3c24xx/regs-iis.h> +#include <plat/regs-iis.h> #include "s3c24xx-pcm.h" #include "s3c24xx-i2s.h" diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c new file mode 100644 index 000000000000..6e1e85dc1ff2 --- /dev/null +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -0,0 +1,220 @@ +/* sound/soc/s3c24xx/s3c64xx-i2s.c + * + * ALSA SoC Audio Layer - S3C64XX I2S driver + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/io.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <plat/regs-s3c2412-iis.h> +#include <plat/gpio-bank-d.h> +#include <plat/gpio-bank-e.h> +#include <plat/gpio-cfg.h> +#include <plat/audio.h> + +#include <mach/map.h> +#include <mach/dma.h> + +#include "s3c24xx-pcm.h" +#include "s3c64xx-i2s.h" + +static struct s3c2410_dma_client s3c64xx_dma_client_out = { + .name = "I2S PCM Stereo out" +}; + +static struct s3c2410_dma_client s3c64xx_dma_client_in = { + .name = "I2S PCM Stereo in" +}; + +static struct s3c24xx_pcm_dma_params s3c64xx_i2s_pcm_stereo_out[2] = { + [0] = { + .channel = DMACH_I2S0_OUT, + .client = &s3c64xx_dma_client_out, + .dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISTXD, + .dma_size = 4, + }, + [1] = { + .channel = DMACH_I2S1_OUT, + .client = &s3c64xx_dma_client_out, + .dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISTXD, + .dma_size = 4, + }, +}; + +static struct s3c24xx_pcm_dma_params s3c64xx_i2s_pcm_stereo_in[2] = { + [0] = { + .channel = DMACH_I2S0_IN, + .client = &s3c64xx_dma_client_in, + .dma_addr = S3C64XX_PA_IIS0 + S3C2412_IISRXD, + .dma_size = 4, + }, + [1] = { + .channel = DMACH_I2S1_IN, + .client = &s3c64xx_dma_client_in, + .dma_addr = S3C64XX_PA_IIS1 + S3C2412_IISRXD, + .dma_size = 4, + }, +}; + +static struct s3c_i2sv2_info s3c64xx_i2s[2]; + +static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) +{ + return cpu_dai->private_data; +} + +static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct s3c_i2sv2_info *i2s = to_info(cpu_dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); + + switch (clk_id) { + case S3C64XX_CLKSRC_PCLK: + iismod &= ~S3C64XX_IISMOD_IMS_SYSMUX; + break; + + case S3C64XX_CLKSRC_MUX: + iismod |= S3C64XX_IISMOD_IMS_SYSMUX; + break; + + default: + return -EINVAL; + } + + writel(iismod, i2s->regs + S3C2412_IISMOD); + + return 0; +} + + +unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *dai) +{ + struct s3c_i2sv2_info *i2s = to_info(dai); + + return clk_get_rate(i2s->iis_cclk); +} +EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clockrate); + +static int s3c64xx_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct device *dev = &pdev->dev; + struct s3c_i2sv2_info *i2s; + int ret; + + dev_dbg(dev, "%s: probing dai %d\n", __func__, pdev->id); + + if (pdev->id < 0 || pdev->id > ARRAY_SIZE(s3c64xx_i2s)) { + dev_err(dev, "id %d out of range\n", pdev->id); + return -EINVAL; + } + + i2s = &s3c64xx_i2s[pdev->id]; + + ret = s3c_i2sv2_probe(pdev, dai, i2s, + pdev->id ? S3C64XX_PA_IIS1 : S3C64XX_PA_IIS0); + if (ret) + return ret; + + i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id]; + i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id]; + + i2s->iis_cclk = clk_get(dev, "audio-bus"); + if (IS_ERR(i2s->iis_cclk)) { + dev_err(dev, "failed to get audio-bus"); + iounmap(i2s->regs); + return -ENODEV; + } + + /* configure GPIO for i2s port */ + switch (pdev->id) { + case 0: + s3c_gpio_cfgpin(S3C64XX_GPD(0), S3C64XX_GPD0_I2S0_CLK); + s3c_gpio_cfgpin(S3C64XX_GPD(1), S3C64XX_GPD1_I2S0_CDCLK); + s3c_gpio_cfgpin(S3C64XX_GPD(2), S3C64XX_GPD2_I2S0_LRCLK); + s3c_gpio_cfgpin(S3C64XX_GPD(3), S3C64XX_GPD3_I2S0_DI); + s3c_gpio_cfgpin(S3C64XX_GPD(4), S3C64XX_GPD4_I2S0_D0); + break; + case 1: + s3c_gpio_cfgpin(S3C64XX_GPE(0), S3C64XX_GPE0_I2S1_CLK); + s3c_gpio_cfgpin(S3C64XX_GPE(1), S3C64XX_GPE1_I2S1_CDCLK); + s3c_gpio_cfgpin(S3C64XX_GPE(2), S3C64XX_GPE2_I2S1_LRCLK); + s3c_gpio_cfgpin(S3C64XX_GPE(3), S3C64XX_GPE3_I2S1_DI); + s3c_gpio_cfgpin(S3C64XX_GPE(4), S3C64XX_GPE4_I2S1_D0); + } + + return 0; +} + + +#define S3C64XX_I2S_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 S3C64XX_I2S_FMTS \ + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE) + +struct snd_soc_dai s3c64xx_i2s_dai = { + .name = "s3c64xx-i2s", + .id = 0, + .probe = s3c64xx_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .ops = { + .set_sysclk = s3c64xx_i2s_set_sysclk, + }, +}; +EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai); + +static int __init s3c64xx_i2s_init(void) +{ + return s3c_i2sv2_register_dai(&s3c64xx_i2s_dai); +} +module_init(s3c64xx_i2s_init); + +static void __exit s3c64xx_i2s_exit(void) +{ + snd_soc_unregister_dai(&s3c64xx_i2s_dai); +} +module_exit(s3c64xx_i2s_exit); + +/* Module information */ +MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("S3C64XX I2S SoC Interface"); +MODULE_LICENSE("GPL"); + + + diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h new file mode 100644 index 000000000000..b7ffe3c38b66 --- /dev/null +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -0,0 +1,31 @@ +/* sound/soc/s3c24xx/s3c64xx-i2s.h + * + * ALSA SoC Audio Layer - S3C64XX I2S driver + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __SND_SOC_S3C24XX_S3C64XX_I2S_H +#define __SND_SOC_S3C24XX_S3C64XX_I2S_H __FILE__ + +#include "s3c-i2s-v2.h" + +#define S3C64XX_DIV_BCLK S3C_I2SV2_DIV_BCLK +#define S3C64XX_DIV_RCLK S3C_I2SV2_DIV_RCLK +#define S3C64XX_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER + +#define S3C64XX_CLKSRC_PCLK (0) +#define S3C64XX_CLKSRC_MUX (1) + +extern struct snd_soc_dai s3c64xx_i2s_dai; + +extern unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *cpu_dai); + +#endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */ diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 4b8dbbfe2efb..735903a74675 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -332,7 +332,7 @@ static int dapm_new_mixer(struct snd_soc_codec *codec, * kcontrol name. */ name_len = strlen(w->kcontrols[i].name) + 1; - if (w->id == snd_soc_dapm_mixer) + if (w->id != snd_soc_dapm_mixer_named_ctl) name_len += 1 + strlen(w->name); path->long_name = kmalloc(name_len, GFP_KERNEL); @@ -341,15 +341,14 @@ static int dapm_new_mixer(struct snd_soc_codec *codec, return -ENOMEM; switch (w->id) { - case snd_soc_dapm_mixer: default: snprintf(path->long_name, name_len, "%s %s", w->name, w->kcontrols[i].name); - break; + break; case snd_soc_dapm_mixer_named_ctl: snprintf(path->long_name, name_len, "%s", w->kcontrols[i].name); - break; + break; } path->long_name[name_len - 1] = '\0'; @@ -523,6 +522,137 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w, EXPORT_SYMBOL_GPL(dapm_reg_event); /* + * Scan a single DAPM widget for a complete audio path and update the + * power status appropriately. + */ +static int dapm_power_widget(struct snd_soc_codec *codec, int event, + struct snd_soc_dapm_widget *w) +{ + int in, out, power_change, power, ret; + + /* vmid - no action */ + if (w->id == snd_soc_dapm_vmid) + return 0; + + /* active ADC */ + if (w->id == snd_soc_dapm_adc && w->active) { + in = is_connected_input_ep(w); + dapm_clear_walk(w->codec); + w->power = (in != 0) ? 1 : 0; + dapm_update_bits(w); + return 0; + } + + /* active DAC */ + if (w->id == snd_soc_dapm_dac && w->active) { + out = is_connected_output_ep(w); + dapm_clear_walk(w->codec); + w->power = (out != 0) ? 1 : 0; + dapm_update_bits(w); + return 0; + } + + /* pre and post event widgets */ + if (w->id == snd_soc_dapm_pre) { + if (!w->event) + return 0; + + if (event == SND_SOC_DAPM_STREAM_START) { + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + return ret; + } else if (event == SND_SOC_DAPM_STREAM_STOP) { + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + return ret; + } + return 0; + } + if (w->id == snd_soc_dapm_post) { + if (!w->event) + return 0; + + if (event == SND_SOC_DAPM_STREAM_START) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + return ret; + } else if (event == SND_SOC_DAPM_STREAM_STOP) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + return ret; + } + return 0; + } + + /* all other widgets */ + in = is_connected_input_ep(w); + dapm_clear_walk(w->codec); + out = is_connected_output_ep(w); + dapm_clear_walk(w->codec); + power = (out != 0 && in != 0) ? 1 : 0; + power_change = (w->power == power) ? 0 : 1; + w->power = power; + + if (!power_change) + return 0; + + /* call any power change event handlers */ + if (w->event) + pr_debug("power %s event for %s flags %x\n", + w->power ? "on" : "off", + w->name, w->event_flags); + + /* power up pre event */ + if (power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + return ret; + } + + /* power down pre event */ + if (!power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + return ret; + } + + /* Lower PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && !power) + dapm_set_pga(w, power); + + dapm_update_bits(w); + + /* Raise PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && power) + dapm_set_pga(w, power); + + /* power up post event */ + if (power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMU)) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + return ret; + } + + /* power down post event */ + if (!power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMD)) { + ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + return ret; + } + + return 0; +} + +/* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- * @@ -534,7 +664,7 @@ EXPORT_SYMBOL_GPL(dapm_reg_event); static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { struct snd_soc_dapm_widget *w; - int in, out, i, c = 1, *seq = NULL, ret = 0, power_change, power; + int i, c = 1, *seq = NULL, ret = 0; /* do we have a sequenced stream event */ if (event == SND_SOC_DAPM_STREAM_START) { @@ -545,135 +675,20 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) seq = dapm_down_seq; } - for(i = 0; i < c; i++) { + for (i = 0; i < c; i++) { list_for_each_entry(w, &codec->dapm_widgets, list) { /* is widget in stream order */ if (seq && seq[i] && w->id != seq[i]) continue; - /* vmid - no action */ - if (w->id == snd_soc_dapm_vmid) - continue; - - /* active ADC */ - if (w->id == snd_soc_dapm_adc && w->active) { - in = is_connected_input_ep(w); - dapm_clear_walk(w->codec); - w->power = (in != 0) ? 1 : 0; - dapm_update_bits(w); - continue; - } - - /* active DAC */ - if (w->id == snd_soc_dapm_dac && w->active) { - out = is_connected_output_ep(w); - dapm_clear_walk(w->codec); - w->power = (out != 0) ? 1 : 0; - dapm_update_bits(w); - continue; - } - - /* pre and post event widgets */ - if (w->id == snd_soc_dapm_pre) { - if (!w->event) - continue; - - if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - return ret; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - return ret; - } - continue; - } - if (w->id == snd_soc_dapm_post) { - if (!w->event) - continue; - - if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - return ret; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - return ret; - } - continue; - } - - /* all other widgets */ - in = is_connected_input_ep(w); - dapm_clear_walk(w->codec); - out = is_connected_output_ep(w); - dapm_clear_walk(w->codec); - power = (out != 0 && in != 0) ? 1 : 0; - power_change = (w->power == power) ? 0: 1; - w->power = power; - - if (!power_change) - continue; - - /* call any power change event handlers */ - if (w->event) - pr_debug("power %s event for %s flags %x\n", - w->power ? "on" : "off", - w->name, w->event_flags); - - /* power up pre event */ - if (power && w->event && - (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { - ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - return ret; - } - - /* power down pre event */ - if (!power && w->event && - (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { - ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - return ret; - } - - /* Lower PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && !power) - dapm_set_pga(w, power); - - dapm_update_bits(w); - - /* Raise PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && power) - dapm_set_pga(w, power); - - /* power up post event */ - if (power && w->event && - (w->event_flags & SND_SOC_DAPM_POST_PMU)) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - return ret; - } - - /* power down post event */ - if (!power && w->event && - (w->event_flags & SND_SOC_DAPM_POST_PMD)) { - ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - return ret; - } + ret = dapm_power_widget(codec, event, w); + if (ret != 0) + return ret; } } - return ret; + return 0; } #ifdef DEBUG diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index ab64a30bedde..28346fb2e70c 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -14,6 +14,10 @@ #include <sound/jack.h> #include <sound/soc.h> #include <sound/soc-dapm.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/delay.h> /** * snd_soc_jack_new - Create a new jack @@ -136,3 +140,128 @@ int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count, return 0; } EXPORT_SYMBOL_GPL(snd_soc_jack_add_pins); + +#ifdef CONFIG_GPIOLIB +/* gpio detect */ +static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio) +{ + struct snd_soc_jack *jack = gpio->jack; + int enable; + int report; + + if (gpio->debounce_time > 0) + mdelay(gpio->debounce_time); + + enable = gpio_get_value(gpio->gpio); + if (gpio->invert) + enable = !enable; + + if (enable) + report = gpio->report; + else + report = 0; + + snd_soc_jack_report(jack, report, gpio->report); +} + +/* irq handler for gpio pin */ +static irqreturn_t gpio_handler(int irq, void *data) +{ + struct snd_soc_jack_gpio *gpio = data; + + schedule_work(&gpio->work); + + return IRQ_HANDLED; +} + +/* gpio work */ +static void gpio_work(struct work_struct *work) +{ + struct snd_soc_jack_gpio *gpio; + + gpio = container_of(work, struct snd_soc_jack_gpio, work); + snd_soc_jack_gpio_detect(gpio); +} + +/** + * snd_soc_jack_add_gpios - Associate GPIO pins with an ASoC jack + * + * @jack: ASoC jack + * @count: number of pins + * @gpios: array of gpio pins + * + * This function will request gpio, set data direction and request irq + * for each gpio in the array. + */ +int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_gpio *gpios) +{ + int i, ret; + + for (i = 0; i < count; i++) { + if (!gpio_is_valid(gpios[i].gpio)) { + printk(KERN_ERR "Invalid gpio %d\n", + gpios[i].gpio); + ret = -EINVAL; + goto undo; + } + if (!gpios[i].name) { + printk(KERN_ERR "No name for gpio %d\n", + gpios[i].gpio); + ret = -EINVAL; + goto undo; + } + + ret = gpio_request(gpios[i].gpio, gpios[i].name); + if (ret) + goto undo; + + ret = gpio_direction_input(gpios[i].gpio); + if (ret) + goto err; + + ret = request_irq(gpio_to_irq(gpios[i].gpio), + gpio_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + jack->card->dev->driver->name, + &gpios[i]); + if (ret) + goto err; + + INIT_WORK(&gpios[i].work, gpio_work); + gpios[i].jack = jack; + } + + return 0; + +err: + gpio_free(gpios[i].gpio); +undo: + snd_soc_jack_free_gpios(jack, i, gpios); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_jack_add_gpios); + +/** + * snd_soc_jack_free_gpios - Release GPIO pins' resources of an ASoC jack + * + * @jack: ASoC jack + * @count: number of pins + * @gpios: array of gpio pins + * + * Release gpio and irq resources for gpio pins associated with an ASoC jack. + */ +void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, + struct snd_soc_jack_gpio *gpios) +{ + int i; + + for (i = 0; i < count; i++) { + free_irq(gpio_to_irq(gpios[i].gpio), &gpios[i]); + gpio_free(gpios[i].gpio); + gpios[i].jack = NULL; + } +} +EXPORT_SYMBOL_GPL(snd_soc_jack_free_gpios); +#endif /* CONFIG_GPIOLIB */ |