diff options
54 files changed, 3873 insertions, 976 deletions
diff --git a/arch/arm/mach-pxa/include/mach/regs-ssp.h b/arch/arm/mach-pxa/include/mach/regs-ssp.h index 3c04cde2cf1..f43905a2773 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 552b4c778fd..440c014e24b 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 9813dbf2ae4..c477771c092 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 919856c9433..9e3478506c6 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 5b5ee0b8f4e..69b6cf34df4 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 2a58a4d5aa5..8430e582918 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 de0e8da48bc..de0e8da48bc 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 25d4058bcfe..0fad7571030 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 5b75a797b5a..35219dcf9f0 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 a6f1d5df13b..a6f1d5df13b 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 b6640e01804..e06ed3eb1d0 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 0e773526416..a40bc6f316f 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 43dd8cee83c..70657534e6b 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 5885702c78f..8a935f2d176 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 628a591c728..b6c7f7a01cb 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 3664cdc300b..030d2454725 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 507ce0c30ed..569573d2d4d 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 00000000000..4d47bc4f742 --- /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 00000000000..eb88fe7e4de --- /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 7ae3d6520e3..2137670c9b7 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 535d8ce2c32..86bb15cc82c 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 cafa7684c0e..5b21594e0e5 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; +	int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER); -	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)); +	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 00000000000..4e1cefff848 --- /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 00000000000..79c5934d477 --- /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 cc975a62fa5..6a4cea09c45 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 ee0af23a1ac..27f9e231bf6 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 cc6e57f9acf..a6e8f3f7f05 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 b502741692d..bd7392c9657 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 0bf81abba8c..40eccfe9e35 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 58a3fa49750..b3eb8570cd7 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 0fddd437a7c..169bca295b7 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 83b44d700e3..eade01feaab 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 acf39a646b2..ef67d1cdffe 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 cd41a948df7..a952a4eb336 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 e226fa75669..715c648203a 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 3e18064e86b..d3fa6357a9f 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 11cd0f289c1..cf809049272 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 0140a250db2..9f6116edbb8 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 e05a71084c3..2f3a21eee05 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 96b3f3f617d..07a93a2ebe5 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 00000000000..32063790d95 --- /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 286e11ad50e..5f6aeec0437 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 00000000000..295a4c91026 --- /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 00000000000..f66854a77fb --- /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 382d7eee53e..1ca3cdaa821 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,86 +139,22 @@ 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 | \  	SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \  	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 aac08a25e54..92848e54be1 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 83ea623234e..3698f707c44 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 4473fb584c4..cc066964dad 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 ba1ae09dfae..a9d68fa2b34 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 a0a4d1832a1..8e79a416db5 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 00000000000..6e1e85dc1ff --- /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 00000000000..b7ffe3c38b6 --- /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 4b8dbbfe2ef..735903a7467 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 ab64a30bedd..28346fb2e70 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 */  |