diff options
87 files changed, 5633 insertions, 681 deletions
diff --git a/Documentation/devicetree/bindings/sound/soc/codecs/fsl-sgtl5000.txt b/Documentation/devicetree/bindings/sound/soc/codecs/fsl-sgtl5000.txt new file mode 100644 index 00000000000..2c3cd413f04 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/soc/codecs/fsl-sgtl5000.txt @@ -0,0 +1,11 @@ +* Freescale SGTL5000 Stereo Codec + +Required properties: +- compatible : "fsl,sgtl5000". + +Example: + +codec: sgtl5000@0a { +	compatible = "fsl,sgtl5000"; +	reg = <0x0a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8510.txt b/Documentation/devicetree/bindings/sound/wm8510.txt new file mode 100644 index 00000000000..fa1a32b8557 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8510.txt @@ -0,0 +1,18 @@ +WM8510 audio CODEC + +This device supports both I2C and SPI (configured with pin strapping +on the board). + +Required properties: + +  - compatible : "wlf,wm8510" + +  - reg : the I2C address of the device for I2C, the chip select +          number for SPI. + +Example: + +codec: wm8510@1a { +	compatible = "wlf,wm8510"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8523.txt b/Documentation/devicetree/bindings/sound/wm8523.txt new file mode 100644 index 00000000000..04746186b28 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8523.txt @@ -0,0 +1,16 @@ +WM8523 audio CODEC + +This device supports I2C only. + +Required properties: + +  - compatible : "wlf,wm8523" + +  - reg : the I2C address of the device. + +Example: + +codec: wm8523@1a { +	compatible = "wlf,wm8523"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8580.txt b/Documentation/devicetree/bindings/sound/wm8580.txt new file mode 100644 index 00000000000..7d9821f348d --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8580.txt @@ -0,0 +1,16 @@ +WM8580 audio CODEC + +This device supports I2C only. + +Required properties: + +  - compatible : "wlf,wm8580" + +  - reg : the I2C address of the device. + +Example: + +codec: wm8580@1a { +	compatible = "wlf,wm8580"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8711.txt b/Documentation/devicetree/bindings/sound/wm8711.txt new file mode 100644 index 00000000000..8ed9998cd23 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8711.txt @@ -0,0 +1,18 @@ +WM8711 audio CODEC + +This device supports both I2C and SPI (configured with pin strapping +on the board). + +Required properties: + +  - compatible : "wlf,wm8711" + +  - reg : the I2C address of the device for I2C, the chip select +          number for SPI. + +Example: + +codec: wm8711@1a { +	compatible = "wlf,wm8711"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8728.txt b/Documentation/devicetree/bindings/sound/wm8728.txt new file mode 100644 index 00000000000..a8b5c3668e6 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8728.txt @@ -0,0 +1,18 @@ +WM8728 audio CODEC + +This device supports both I2C and SPI (configured with pin strapping +on the board). + +Required properties: + +  - compatible : "wlf,wm8728" + +  - reg : the I2C address of the device for I2C, the chip select +          number for SPI. + +Example: + +codec: wm8728@1a { +	compatible = "wlf,wm8728"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8731.txt b/Documentation/devicetree/bindings/sound/wm8731.txt new file mode 100644 index 00000000000..15f70048469 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8731.txt @@ -0,0 +1,18 @@ +WM8731 audio CODEC + +This device supports both I2C and SPI (configured with pin strapping +on the board). + +Required properties: + +  - compatible : "wlf,wm8731" + +  - reg : the I2C address of the device for I2C, the chip select +          number for SPI. + +Example: + +codec: wm8731@1a { +	compatible = "wlf,wm8731"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8737.txt b/Documentation/devicetree/bindings/sound/wm8737.txt new file mode 100644 index 00000000000..4bc2cea3b14 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8737.txt @@ -0,0 +1,18 @@ +WM8737 audio CODEC + +This device supports both I2C and SPI (configured with pin strapping +on the board). + +Required properties: + +  - compatible : "wlf,wm8737" + +  - reg : the I2C address of the device for I2C, the chip select +          number for SPI. + +Example: + +codec: wm8737@1a { +	compatible = "wlf,wm8737"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8741.txt b/Documentation/devicetree/bindings/sound/wm8741.txt new file mode 100644 index 00000000000..74bda58c1bc --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8741.txt @@ -0,0 +1,18 @@ +WM8741 audio CODEC + +This device supports both I2C and SPI (configured with pin strapping +on the board). + +Required properties: + +  - compatible : "wlf,wm8741" + +  - reg : the I2C address of the device for I2C, the chip select +          number for SPI. + +Example: + +codec: wm8741@1a { +	compatible = "wlf,wm8741"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8750.txt b/Documentation/devicetree/bindings/sound/wm8750.txt new file mode 100644 index 00000000000..8db239fd5ec --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8750.txt @@ -0,0 +1,18 @@ +WM8750 and WM8987 audio CODECs + +These devices support both I2C and SPI (configured with pin strapping +on the board). + +Required properties: + +  - compatible : "wlf,wm8750" or "wlf,wm8987" + +  - reg : the I2C address of the device for I2C, the chip select +          number for SPI. + +Example: + +codec: wm8750@1a { +	compatible = "wlf,wm8750"; +	reg = <0x1a>; +}; diff --git a/Documentation/devicetree/bindings/sound/wm8753.txt b/Documentation/devicetree/bindings/sound/wm8753.txt new file mode 100644 index 00000000000..e65277a0fb6 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/wm8753.txt @@ -0,0 +1,18 @@ +WM8753 audio CODEC + +This device supports both I2C and SPI (configured with pin strapping +on the board). + +Required properties: + +  - compatible : "wlf,wm8753" + +  - reg : the I2C address of the device for I2C, the chip select +          number for SPI. + +Example: + +codec: wm8737@1a { +	compatible = "wlf,wm8753"; +	reg = <0x1a>; +}; diff --git a/arch/mips/alchemy/devboards/db1200/platform.c b/arch/mips/alchemy/devboards/db1200/platform.c index fbb55935b99..dda090bf74e 100644 --- a/arch/mips/alchemy/devboards/db1200/platform.c +++ b/arch/mips/alchemy/devboards/db1200/platform.c @@ -422,6 +422,7 @@ static struct resource au1200_psc1_res[] = {  	},  }; +/* AC97 or I2S device */  static struct platform_device db1200_audio_dev = {  	/* name assigned later based on switch setting */  	.id		= 1,	/* PSC ID */ @@ -429,19 +430,32 @@ static struct platform_device db1200_audio_dev = {  	.resource	= au1200_psc1_res,  }; +/* DB1200 ASoC card device */ +static struct platform_device db1200_sound_dev = { +	/* name assigned later based on switch setting */ +	.id		= 1,	/* PSC ID */ +}; +  static struct platform_device db1200_stac_dev = {  	.name		= "ac97-codec",  	.id		= 1,	/* on PSC1 */  }; +static struct platform_device db1200_audiodma_dev = { +	.name		= "au1xpsc-pcm", +	.id		= 1,	/* PSC ID */ +}; +  static struct platform_device *db1200_devs[] __initdata = {  	NULL,		/* PSC0, selected by S6.8 */  	&db1200_ide_dev,  	&db1200_eth_dev,  	&db1200_rtc_dev,  	&db1200_nand_dev, +	&db1200_audiodma_dev,  	&db1200_audio_dev,  	&db1200_stac_dev, +	&db1200_sound_dev,  };  static int __init db1200_dev_init(void) @@ -501,10 +515,12 @@ static int __init db1200_dev_init(void)  	if (sw == BCSR_SWITCHES_DIP_8) {  		bcsr_mod(BCSR_RESETS, 0, BCSR_RESETS_PSC1MUX);  		db1200_audio_dev.name = "au1xpsc_i2s"; +		db1200_sound_dev.name = "db1200-i2s";  		printk(KERN_INFO " S6.7 ON : PSC1 mode I2S\n");  	} else {  		bcsr_mod(BCSR_RESETS, BCSR_RESETS_PSC1MUX, 0);  		db1200_audio_dev.name = "au1xpsc_ac97"; +		db1200_sound_dev.name = "db1200-ac97";  		printk(KERN_INFO " S6.7 OFF: PSC1 mode AC97\n");  	} diff --git a/arch/mips/alchemy/devboards/db1x00/platform.c b/arch/mips/alchemy/devboards/db1x00/platform.c index 978d5ab3d67..7057d28f730 100644 --- a/arch/mips/alchemy/devboards/db1x00/platform.c +++ b/arch/mips/alchemy/devboards/db1x00/platform.c @@ -19,8 +19,11 @@   */  #include <linux/init.h> +#include <linux/interrupt.h>  #include <linux/platform_device.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1000_dma.h>  #include <asm/mach-au1x00/au1xxx.h>  #include <asm/mach-db1x00/bcsr.h>  #include "../platform.h" @@ -85,6 +88,45 @@  #endif  #endif +static struct resource alchemy_ac97c_res[] = { +	[0] = { +		.start	= AU1000_AC97_PHYS_ADDR, +		.end	= AU1000_AC97_PHYS_ADDR + 0xfff, +		.flags	= IORESOURCE_MEM, +	}, +	[1] = { +		.start	= DMA_ID_AC97C_TX, +		.end	= DMA_ID_AC97C_TX, +		.flags	= IORESOURCE_DMA, +	}, +	[2] = { +		.start	= DMA_ID_AC97C_RX, +		.end	= DMA_ID_AC97C_RX, +		.flags	= IORESOURCE_DMA, +	}, +}; + +static struct platform_device alchemy_ac97c_dev = { +	.name		= "alchemy-ac97c", +	.id		= -1, +	.resource	= alchemy_ac97c_res, +	.num_resources	= ARRAY_SIZE(alchemy_ac97c_res), +}; + +static struct platform_device alchemy_ac97c_dma_dev = { +	.name		= "alchemy-pcm-dma", +	.id		= 0, +}; + +static struct platform_device db1x00_codec_dev = { +	.name		= "ac97-codec", +	.id		= -1, +}; + +static struct platform_device db1x00_audio_dev = { +	.name		= "db1000-audio", +}; +  static int __init db1xxx_dev_init(void)  {  #ifdef DB1XXX_HAS_PCMCIA @@ -113,6 +155,12 @@ static int __init db1xxx_dev_init(void)  				    1);  #endif  	db1x_register_norflash(BOARD_FLASH_SIZE, BOARD_FLASH_WIDTH, F_SWAPPED); + +	platform_device_register(&db1x00_codec_dev); +	platform_device_register(&alchemy_ac97c_dma_dev); +	platform_device_register(&alchemy_ac97c_dev); +	platform_device_register(&db1x00_audio_dev); +  	return 0;  }  device_initcall(db1xxx_dev_init); diff --git a/include/linux/mfd/wm8994/registers.h b/include/linux/mfd/wm8994/registers.h index f3ee8428467..83ecdcd8aaf 100644 --- a/include/linux/mfd/wm8994/registers.h +++ b/include/linux/mfd/wm8994/registers.h @@ -72,6 +72,7 @@  #define WM8994_DC_SERVO_2                       0x55  #define WM8994_DC_SERVO_4                       0x57  #define WM8994_DC_SERVO_READBACK                0x58 +#define WM8994_DC_SERVO_4E			0x59  #define WM8994_ANALOGUE_HP_1                    0x60  #define WM8958_MIC_DETECT_1                     0xD0  #define WM8958_MIC_DETECT_2                     0xD1 @@ -133,6 +134,8 @@  #define WM8994_AIF1_DAC1_FILTERS_2              0x421  #define WM8994_AIF1_DAC2_FILTERS_1              0x422  #define WM8994_AIF1_DAC2_FILTERS_2              0x423 +#define WM8958_AIF1_DAC1_NOISE_GATE             0x430 +#define WM8958_AIF1_DAC2_NOISE_GATE             0x431  #define WM8994_AIF1_DRC1_1                      0x440  #define WM8994_AIF1_DRC1_2                      0x441  #define WM8994_AIF1_DRC1_3                      0x442 @@ -190,6 +193,7 @@  #define WM8994_AIF2_ADC_FILTERS                 0x510  #define WM8994_AIF2_DAC_FILTERS_1               0x520  #define WM8994_AIF2_DAC_FILTERS_2               0x521 +#define WM8958_AIF2_DAC_NOISE_GATE              0x530  #define WM8994_AIF2_DRC_1                       0x540  #define WM8994_AIF2_DRC_2                       0x541  #define WM8994_AIF2_DRC_3                       0x542 @@ -1921,6 +1925,44 @@  #define WM8994_LDO2_DISCH_WIDTH                      1  /* LDO2_DISCH */  /* + * R61 (0x3D) - MICBIAS1 + */ +#define WM8958_MICB1_RATE                       0x0020  /* MICB1_RATE */ +#define WM8958_MICB1_RATE_MASK                  0x0020  /* MICB1_RATE */ +#define WM8958_MICB1_RATE_SHIFT                      5  /* MICB1_RATE */ +#define WM8958_MICB1_RATE_WIDTH                      1  /* MICB1_RATE */ +#define WM8958_MICB1_MODE                       0x0010  /* MICB1_MODE */ +#define WM8958_MICB1_MODE_MASK                  0x0010  /* MICB1_MODE */ +#define WM8958_MICB1_MODE_SHIFT                      4  /* MICB1_MODE */ +#define WM8958_MICB1_MODE_WIDTH                      1  /* MICB1_MODE */ +#define WM8958_MICB1_LVL_MASK                   0x000E  /* MICB1_LVL - [3:1] */ +#define WM8958_MICB1_LVL_SHIFT                       1  /* MICB1_LVL - [3:1] */ +#define WM8958_MICB1_LVL_WIDTH                       3  /* MICB1_LVL - [3:1] */ +#define WM8958_MICB1_DISCH                      0x0001  /* MICB1_DISCH */ +#define WM8958_MICB1_DISCH_MASK                 0x0001  /* MICB1_DISCH */ +#define WM8958_MICB1_DISCH_SHIFT                     0  /* MICB1_DISCH */ +#define WM8958_MICB1_DISCH_WIDTH                     1  /* MICB1_DISCH */ + +/* + * R62 (0x3E) - MICBIAS2 + */ +#define WM8958_MICB2_RATE                       0x0020  /* MICB2_RATE */ +#define WM8958_MICB2_RATE_MASK                  0x0020  /* MICB2_RATE */ +#define WM8958_MICB2_RATE_SHIFT                      5  /* MICB2_RATE */ +#define WM8958_MICB2_RATE_WIDTH                      1  /* MICB2_RATE */ +#define WM8958_MICB2_MODE                       0x0010  /* MICB2_MODE */ +#define WM8958_MICB2_MODE_MASK                  0x0010  /* MICB2_MODE */ +#define WM8958_MICB2_MODE_SHIFT                      4  /* MICB2_MODE */ +#define WM8958_MICB2_MODE_WIDTH                      1  /* MICB2_MODE */ +#define WM8958_MICB2_LVL_MASK                   0x000E  /* MICB2_LVL - [3:1] */ +#define WM8958_MICB2_LVL_SHIFT                       1  /* MICB2_LVL - [3:1] */ +#define WM8958_MICB2_LVL_WIDTH                       3  /* MICB2_LVL - [3:1] */ +#define WM8958_MICB2_DISCH                      0x0001  /* MICB2_DISCH */ +#define WM8958_MICB2_DISCH_MASK                 0x0001  /* MICB2_DISCH */ +#define WM8958_MICB2_DISCH_SHIFT                     0  /* MICB2_DISCH */ +#define WM8958_MICB2_DISCH_WIDTH                     1  /* MICB2_DISCH */ + +/*   * R76 (0x4C) - Charge Pump (1)   */  #define WM8994_CP_ENA                           0x8000  /* CP_ENA */ @@ -2949,6 +2991,34 @@  #define WM8994_AIF1DAC2_3D_ENA_WIDTH                 1  /* AIF1DAC2_3D_ENA */  /* + * R1072 (0x430) - AIF1 DAC1 Noise Gate + */ +#define WM8958_AIF1DAC1_NG_HLD_MASK             0x0060  /* AIF1DAC1_NG_HLD - [6:5] */ +#define WM8958_AIF1DAC1_NG_HLD_SHIFT                 5  /* AIF1DAC1_NG_HLD - [6:5] */ +#define WM8958_AIF1DAC1_NG_HLD_WIDTH                 2  /* AIF1DAC1_NG_HLD - [6:5] */ +#define WM8958_AIF1DAC1_NG_THR_MASK             0x000E  /* AIF1DAC1_NG_THR - [3:1] */ +#define WM8958_AIF1DAC1_NG_THR_SHIFT                 1  /* AIF1DAC1_NG_THR - [3:1] */ +#define WM8958_AIF1DAC1_NG_THR_WIDTH                 3  /* AIF1DAC1_NG_THR - [3:1] */ +#define WM8958_AIF1DAC1_NG_ENA                  0x0001  /* AIF1DAC1_NG_ENA */ +#define WM8958_AIF1DAC1_NG_ENA_MASK             0x0001  /* AIF1DAC1_NG_ENA */ +#define WM8958_AIF1DAC1_NG_ENA_SHIFT                 0  /* AIF1DAC1_NG_ENA */ +#define WM8958_AIF1DAC1_NG_ENA_WIDTH                 1  /* AIF1DAC1_NG_ENA */ + +/* + * R1073 (0x431) - AIF1 DAC2 Noise Gate + */ +#define WM8958_AIF1DAC2_NG_HLD_MASK             0x0060  /* AIF1DAC2_NG_HLD - [6:5] */ +#define WM8958_AIF1DAC2_NG_HLD_SHIFT                 5  /* AIF1DAC2_NG_HLD - [6:5] */ +#define WM8958_AIF1DAC2_NG_HLD_WIDTH                 2  /* AIF1DAC2_NG_HLD - [6:5] */ +#define WM8958_AIF1DAC2_NG_THR_MASK             0x000E  /* AIF1DAC2_NG_THR - [3:1] */ +#define WM8958_AIF1DAC2_NG_THR_SHIFT                 1  /* AIF1DAC2_NG_THR - [3:1] */ +#define WM8958_AIF1DAC2_NG_THR_WIDTH                 3  /* AIF1DAC2_NG_THR - [3:1] */ +#define WM8958_AIF1DAC2_NG_ENA                  0x0001  /* AIF1DAC2_NG_ENA */ +#define WM8958_AIF1DAC2_NG_ENA_MASK             0x0001  /* AIF1DAC2_NG_ENA */ +#define WM8958_AIF1DAC2_NG_ENA_SHIFT                 0  /* AIF1DAC2_NG_ENA */ +#define WM8958_AIF1DAC2_NG_ENA_WIDTH                 1  /* AIF1DAC2_NG_ENA */ + +/*   * R1088 (0x440) - AIF1 DRC1 (1)   */  #define WM8994_AIF1DRC1_SIG_DET_RMS_MASK        0xF800  /* AIF1DRC1_SIG_DET_RMS - [15:11] */ @@ -3560,6 +3630,20 @@  #define WM8994_AIF2DAC_3D_ENA_WIDTH                  1  /* AIF2DAC_3D_ENA */  /* + * R1328 (0x530) - AIF2 DAC Noise Gate + */ +#define WM8958_AIF2DAC_NG_HLD_MASK              0x0060  /* AIF2DAC_NG_HLD - [6:5] */ +#define WM8958_AIF2DAC_NG_HLD_SHIFT                  5  /* AIF2DAC_NG_HLD - [6:5] */ +#define WM8958_AIF2DAC_NG_HLD_WIDTH                  2  /* AIF2DAC_NG_HLD - [6:5] */ +#define WM8958_AIF2DAC_NG_THR_MASK              0x000E  /* AIF2DAC_NG_THR - [3:1] */ +#define WM8958_AIF2DAC_NG_THR_SHIFT                  1  /* AIF2DAC_NG_THR - [3:1] */ +#define WM8958_AIF2DAC_NG_THR_WIDTH                  3  /* AIF2DAC_NG_THR - [3:1] */ +#define WM8958_AIF2DAC_NG_ENA                   0x0001  /* AIF2DAC_NG_ENA */ +#define WM8958_AIF2DAC_NG_ENA_MASK              0x0001  /* AIF2DAC_NG_ENA */ +#define WM8958_AIF2DAC_NG_ENA_SHIFT                  0  /* AIF2DAC_NG_ENA */ +#define WM8958_AIF2DAC_NG_ENA_WIDTH                  1  /* AIF2DAC_NG_ENA */ + +/*   * R1344 (0x540) - AIF2 DRC (1)   */  #define WM8994_AIF2DRC_SIG_DET_RMS_MASK         0xF800  /* AIF2DRC_SIG_DET_RMS - [15:11] */ diff --git a/include/sound/adau1373.h b/include/sound/adau1373.h new file mode 100644 index 00000000000..1b19c766657 --- /dev/null +++ b/include/sound/adau1373.h @@ -0,0 +1,34 @@ +/* + * Analog Devices ADAU1373 Audio Codec drive + * + * Copyright 2011 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __SOUND_ADAU1373_H__ +#define __SOUND_ADAU1373_H__ + +enum adau1373_micbias_voltage { +	ADAU1373_MICBIAS_2_9V = 0, +	ADAU1373_MICBIAS_2_2V = 1, +	ADAU1373_MICBIAS_2_6V = 2, +	ADAU1373_MICBIAS_1_8V = 3, +}; + +#define ADAU1373_DRC_SIZE 13 + +struct adau1373_platform_data { +	bool input_differential[4]; +	bool lineout_differential; +	bool lineout_ground_sense; + +	unsigned int num_drc; +	uint8_t drc_setting[3][ADAU1373_DRC_SIZE]; + +	enum adau1373_micbias_voltage micbias1; +	enum adau1373_micbias_voltage micbias2; +}; + +#endif diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index e0583b7769c..350b1b395ca 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -524,6 +524,8 @@ struct snd_soc_dapm_context {  	enum snd_soc_bias_level target_bias_level;  	struct list_head list; +	int (*stream_event)(struct snd_soc_dapm_context *dapm, int event); +  #ifdef CONFIG_DEBUG_FS  	struct dentry *debugfs_dapm;  #endif diff --git a/include/sound/soc.h b/include/sound/soc.h index aa19f5a32ba..6da55a17fcf 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -19,6 +19,7 @@  #include <linux/workqueue.h>  #include <linux/interrupt.h>  #include <linux/kernel.h> +#include <linux/regmap.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/control.h> @@ -260,6 +261,7 @@ extern struct snd_ac97_bus_ops soc_ac97_ops;  enum snd_soc_control_type {  	SND_SOC_I2C = 1,  	SND_SOC_SPI, +	SND_SOC_REGMAP,  };  enum snd_soc_compress_type { @@ -576,6 +578,7 @@ struct snd_soc_codec {  	const void *reg_def_copy;  	const struct snd_soc_cache_ops *cache_ops;  	struct mutex cache_rw_mutex; +	int val_bytes;  	/* dapm */  	struct snd_soc_dapm_context dapm; @@ -630,10 +633,14 @@ struct snd_soc_codec_driver {  	/* codec bias level */  	int (*set_bias_level)(struct snd_soc_codec *,  			      enum snd_soc_bias_level level); +	bool idle_bias_off;  	void (*seq_notifier)(struct snd_soc_dapm_context *,  			     enum snd_soc_dapm_type, int); +	/* codec stream completion event */ +	int (*stream_event)(struct snd_soc_dapm_context *dapm, int event); +  	/* probe ordering - for components with runtime dependencies */  	int probe_order;  	int remove_order; @@ -669,6 +676,9 @@ struct snd_soc_platform_driver {  	/* platform stream ops */  	struct snd_pcm_ops *ops; +	/* platform stream completion event */ +	int (*stream_event)(struct snd_soc_dapm_context *dapm, int event); +  	/* probe ordering - for components with runtime dependencies */  	int probe_order;  	int remove_order; diff --git a/sound/mips/Kconfig b/sound/mips/Kconfig index a9823fad85c..77dd0a13aec 100644 --- a/sound/mips/Kconfig +++ b/sound/mips/Kconfig @@ -23,12 +23,15 @@ config SND_SGI_HAL2  config SND_AU1X00 -	tristate "Au1x00 AC97 Port Driver" +	tristate "Au1x00 AC97 Port Driver (DEPRECATED)"  	depends on SOC_AU1000 || SOC_AU1100 || SOC_AU1500  	select SND_PCM  	select SND_AC97_CODEC  	help  	  ALSA Sound driver for the Au1x00's AC97 port. +	  Newer drivers for ASoC are available, please do not use +	  this driver as it will be removed in the future. +  endif	# SND_MIPS diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 8224db5f043..1381db853ef 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -7,6 +7,8 @@ menuconfig SND_SOC  	select SND_PCM  	select AC97_BUS if SND_SOC_AC97_BUS  	select SND_JACK if INPUT=y || INPUT=SND +	select REGMAP_I2C if I2C +	select REGMAP_SPI if SPI_MASTER  	---help---  	  If you want ASoC support, you should say Y here and also to the @@ -51,6 +53,7 @@ source "sound/soc/nuc900/Kconfig"  source "sound/soc/omap/Kconfig"  source "sound/soc/kirkwood/Kconfig"  source "sound/soc/mid-x86/Kconfig" +source "sound/soc/mxs/Kconfig"  source "sound/soc/pxa/Kconfig"  source "sound/soc/samsung/Kconfig"  source "sound/soc/s6000/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 4f913876f33..9ea8ac827ad 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SND_SOC)	+= fsl/  obj-$(CONFIG_SND_SOC)   += imx/  obj-$(CONFIG_SND_SOC)	+= jz4740/  obj-$(CONFIG_SND_SOC)	+= mid-x86/ +obj-$(CONFIG_SND_SOC)	+= mxs/  obj-$(CONFIG_SND_SOC)	+= nuc900/  obj-$(CONFIG_SND_SOC)	+= omap/  obj-$(CONFIG_SND_SOC)	+= kirkwood/ diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig index 4b67140fdec..6d592546e8f 100644 --- a/sound/soc/au1x/Kconfig +++ b/sound/soc/au1x/Kconfig @@ -18,10 +18,38 @@ config SND_SOC_AU1XPSC_AC97  	select SND_AC97_CODEC  	select SND_SOC_AC97_BUS +## +## Au1000/1500/1100 DMA + AC97C/I2SC +## +config SND_SOC_AU1XAUDIO +	tristate "SoC Audio for Au1000/Au1500/Au1100" +	depends on MIPS_ALCHEMY +	help +	  This is a driver set for the AC97 unit and the +	  old DMA controller as found on the Au1000/Au1500/Au1100 chips. + +config SND_SOC_AU1XAC97C +	tristate +	select AC97_BUS +	select SND_AC97_CODEC +	select SND_SOC_AC97_BUS + +config SND_SOC_AU1XI2SC +	tristate +  ##  ## Boards  ## +config SND_SOC_DB1000 +	tristate "DB1000 Audio support" +	depends on SND_SOC_AU1XAUDIO +	select SND_SOC_AU1XAC97C +	select SND_SOC_AC97_CODEC +	help +	  Select this option to enable AC97 audio on the early DB1x00 series +	  of boards (DB1000/DB1500/DB1100). +  config SND_SOC_DB1200  	tristate "DB1200 AC97+I2S audio support"  	depends on SND_SOC_AU1XPSC diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile index 16873076e8c..920710514ea 100644 --- a/sound/soc/au1x/Makefile +++ b/sound/soc/au1x/Makefile @@ -3,11 +3,21 @@ snd-soc-au1xpsc-dbdma-objs := dbdma2.o  snd-soc-au1xpsc-i2s-objs := psc-i2s.o  snd-soc-au1xpsc-ac97-objs := psc-ac97.o +# Au1000/1500/1100 Audio units +snd-soc-au1x-dma-objs := dma.o +snd-soc-au1x-ac97c-objs := ac97c.o +snd-soc-au1x-i2sc-objs := i2sc.o +  obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o  obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o  obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o +obj-$(CONFIG_SND_SOC_AU1XAUDIO) += snd-soc-au1x-dma.o +obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o +obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o  # Boards +snd-soc-db1000-objs := db1000.o  snd-soc-db1200-objs := db1200.o +obj-$(CONFIG_SND_SOC_DB1000) += snd-soc-db1000.o  obj-$(CONFIG_SND_SOC_DB1200) += snd-soc-db1200.o diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c new file mode 100644 index 00000000000..13802ff7cf0 --- /dev/null +++ b/sound/soc/au1x/ac97c.c @@ -0,0 +1,363 @@ +/* + * Au1000/Au1500/Au1100 AC97C controller driver for ASoC + * + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com> + * + * based on the old ALSA driver originally written by + *			Charles Eidsness <charles@cooper-street.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> + +#include "psc.h" + +/* register offsets and bits */ +#define AC97_CONFIG	0x00 +#define AC97_STATUS	0x04 +#define AC97_DATA	0x08 +#define AC97_CMDRESP	0x0c +#define AC97_ENABLE	0x10 + +#define CFG_RC(x)	(((x) & 0x3ff) << 13)	/* valid rx slots mask */ +#define CFG_XS(x)	(((x) & 0x3ff) << 3)	/* valid tx slots mask */ +#define CFG_SG		(1 << 2)	/* sync gate */ +#define CFG_SN		(1 << 1)	/* sync control */ +#define CFG_RS		(1 << 0)	/* acrst# control */ +#define STAT_XU		(1 << 11)	/* tx underflow */ +#define STAT_XO		(1 << 10)	/* tx overflow */ +#define STAT_RU		(1 << 9)	/* rx underflow */ +#define STAT_RO		(1 << 8)	/* rx overflow */ +#define STAT_RD		(1 << 7)	/* codec ready */ +#define STAT_CP		(1 << 6)	/* command pending */ +#define STAT_TE		(1 << 4)	/* tx fifo empty */ +#define STAT_TF		(1 << 3)	/* tx fifo full */ +#define STAT_RE		(1 << 1)	/* rx fifo empty */ +#define STAT_RF		(1 << 0)	/* rx fifo full */ +#define CMD_SET_DATA(x)	(((x) & 0xffff) << 16) +#define CMD_GET_DATA(x)	((x) & 0xffff) +#define CMD_READ	(1 << 7) +#define CMD_WRITE	(0 << 7) +#define CMD_IDX(x)	((x) & 0x7f) +#define EN_D		(1 << 1)	/* DISable bit */ +#define EN_CE		(1 << 0)	/* clock enable bit */ + +/* how often to retry failed codec register reads/writes */ +#define AC97_RW_RETRIES	5 + +#define AC97_RATES	\ +	SNDRV_PCM_RATE_CONTINUOUS + +#define AC97_FMTS	\ +	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE) + +/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only + * once AC97C on early Alchemy chips. The newer ones aren't so lucky. + */ +static struct au1xpsc_audio_data *ac97c_workdata; +#define ac97_to_ctx(x)		ac97c_workdata + +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg) +{ +	return __raw_readl(ctx->mmio + reg); +} + +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v) +{ +	__raw_writel(v, ctx->mmio + reg); +	wmb(); +} + +static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97, +					  unsigned short r) +{ +	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97); +	unsigned int tmo, retry; +	unsigned long data; + +	data = ~0; +	retry = AC97_RW_RETRIES; +	do { +		mutex_lock(&ctx->lock); + +		tmo = 5; +		while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--) +			udelay(21);	/* wait an ac97 frame time */ +		if (!tmo) { +			pr_debug("ac97rd timeout #1\n"); +			goto next; +		} + +		WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ); + +		/* stupid errata: data is only valid for 21us, so +		 * poll, Forrest, poll... +		 */ +		tmo = 0x10000; +		while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--) +			asm volatile ("nop"); +		data = RD(ctx, AC97_CMDRESP); + +		if (!tmo) +			pr_debug("ac97rd timeout #2\n"); + +next: +		mutex_unlock(&ctx->lock); +	} while (--retry && !tmo); + +	pr_debug("AC97RD %04x %04lx %d\n", r, data, retry); + +	return retry ? data & 0xffff : 0xffff; +} + +static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r, +				 unsigned short v) +{ +	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97); +	unsigned int tmo, retry; + +	retry = AC97_RW_RETRIES; +	do { +		mutex_lock(&ctx->lock); + +		for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--) +			udelay(21); +		if (!tmo) { +			pr_debug("ac97wr timeout #1\n"); +			goto next; +		} + +		WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v)); + +		for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--) +			udelay(21); +		if (!tmo) +			pr_debug("ac97wr timeout #2\n"); +next: +		mutex_unlock(&ctx->lock); +	} while (--retry && !tmo); + +	pr_debug("AC97WR %04x %04x %d\n", r, v, retry); +} + +static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97) +{ +	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97); + +	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN); +	msleep(20); +	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG); +	WR(ctx, AC97_CONFIG, ctx->cfg); +} + +static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97) +{ +	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97); +	int i; + +	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS); +	msleep(500); +	WR(ctx, AC97_CONFIG, ctx->cfg); + +	/* wait for codec ready */ +	i = 50; +	while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i) +		msleep(20); +	if (!i) +		printk(KERN_ERR "ac97c: codec not ready after cold reset\n"); +} + +/* AC97 controller operations */ +struct snd_ac97_bus_ops soc_ac97_ops = { +	.read		= au1xac97c_ac97_read, +	.write		= au1xac97c_ac97_write, +	.reset		= au1xac97c_ac97_cold_reset, +	.warm_reset	= au1xac97c_ac97_warm_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops);	/* globals be gone! */ + +static int alchemy_ac97c_startup(struct snd_pcm_substream *substream, +				 struct snd_soc_dai *dai) +{ +	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); +	snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]); +	return 0; +} + +static struct snd_soc_dai_ops alchemy_ac97c_ops = { +	.startup		= alchemy_ac97c_startup, +}; + +static int au1xac97c_dai_probe(struct snd_soc_dai *dai) +{ +	return ac97c_workdata ? 0 : -ENODEV; +} + +static struct snd_soc_dai_driver au1xac97c_dai_driver = { +	.name			= "alchemy-ac97c", +	.ac97_control		= 1, +	.probe			= au1xac97c_dai_probe, +	.playback = { +		.rates		= AC97_RATES, +		.formats	= AC97_FMTS, +		.channels_min	= 2, +		.channels_max	= 2, +	}, +	.capture = { +		.rates		= AC97_RATES, +		.formats	= AC97_FMTS, +		.channels_min	= 2, +		.channels_max	= 2, +	}, +	.ops			= &alchemy_ac97c_ops, +}; + +static int __devinit au1xac97c_drvprobe(struct platform_device *pdev) +{ +	int ret; +	struct resource *r; +	struct au1xpsc_audio_data *ctx; + +	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); +	if (!ctx) +		return -ENOMEM; + +	mutex_init(&ctx->lock); + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!r) { +		ret = -ENODEV; +		goto out0; +	} + +	ret = -EBUSY; +	if (!request_mem_region(r->start, resource_size(r), pdev->name)) +		goto out0; + +	ctx->mmio = ioremap_nocache(r->start, resource_size(r)); +	if (!ctx->mmio) +		goto out1; + +	r = platform_get_resource(pdev, IORESOURCE_DMA, 0); +	if (!r) +		goto out1; +	ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = r->start; + +	r = platform_get_resource(pdev, IORESOURCE_DMA, 1); +	if (!r) +		goto out1; +	ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = r->start; + +	/* switch it on */ +	WR(ctx, AC97_ENABLE, EN_D | EN_CE); +	WR(ctx, AC97_ENABLE, EN_CE); + +	ctx->cfg = CFG_RC(3) | CFG_XS(3); +	WR(ctx, AC97_CONFIG, ctx->cfg); + +	platform_set_drvdata(pdev, ctx); + +	ret = snd_soc_register_dai(&pdev->dev, &au1xac97c_dai_driver); +	if (ret) +		goto out1; + +	ac97c_workdata = ctx; +	return 0; + +out1: +	release_mem_region(r->start, resource_size(r)); +out0: +	kfree(ctx); +	return ret; +} + +static int __devexit au1xac97c_drvremove(struct platform_device *pdev) +{ +	struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev); +	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + +	snd_soc_unregister_dai(&pdev->dev); + +	WR(ctx, AC97_ENABLE, EN_D);	/* clock off, disable */ + +	iounmap(ctx->mmio); +	release_mem_region(r->start, resource_size(r)); +	kfree(ctx); + +	ac97c_workdata = NULL;	/* MDEV */ + +	return 0; +} + +#ifdef CONFIG_PM +static int au1xac97c_drvsuspend(struct device *dev) +{ +	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); + +	WR(ctx, AC97_ENABLE, EN_D);	/* clock off, disable */ + +	return 0; +} + +static int au1xac97c_drvresume(struct device *dev) +{ +	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); + +	WR(ctx, AC97_ENABLE, EN_D | EN_CE); +	WR(ctx, AC97_ENABLE, EN_CE); +	WR(ctx, AC97_CONFIG, ctx->cfg); + +	return 0; +} + +static const struct dev_pm_ops au1xpscac97_pmops = { +	.suspend	= au1xac97c_drvsuspend, +	.resume		= au1xac97c_drvresume, +}; + +#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops) + +#else + +#define AU1XPSCAC97_PMOPS NULL + +#endif + +static struct platform_driver au1xac97c_driver = { +	.driver	= { +		.name	= "alchemy-ac97c", +		.owner	= THIS_MODULE, +		.pm	= AU1XPSCAC97_PMOPS, +	}, +	.probe		= au1xac97c_drvprobe, +	.remove		= __devexit_p(au1xac97c_drvremove), +}; + +static int __init au1xac97c_load(void) +{ +	ac97c_workdata = NULL; +	return platform_driver_register(&au1xac97c_driver); +} + +static void __exit au1xac97c_unload(void) +{ +	platform_driver_unregister(&au1xac97c_driver); +} + +module_init(au1xac97c_load); +module_exit(au1xac97c_unload); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ASoC driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c new file mode 100644 index 00000000000..127477a5e0c --- /dev/null +++ b/sound/soc/au1x/db1000.c @@ -0,0 +1,75 @@ +/* + * DB1000/DB1500/DB1100 ASoC audio fabric support code. + * + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-db1x00/bcsr.h> + +#include "psc.h" + +static struct snd_soc_dai_link db1000_ac97_dai = { +	.name		= "AC97", +	.stream_name	= "AC97 HiFi", +	.codec_dai_name	= "ac97-hifi", +	.cpu_dai_name	= "alchemy-ac97c", +	.platform_name	= "alchemy-pcm-dma.0", +	.codec_name	= "ac97-codec", +}; + +static struct snd_soc_card db1000_ac97 = { +	.name		= "DB1000_AC97", +	.dai_link	= &db1000_ac97_dai, +	.num_links	= 1, +}; + +static int __devinit db1000_audio_probe(struct platform_device *pdev) +{ +	struct snd_soc_card *card = &db1000_ac97; +	card->dev = &pdev->dev; +	return snd_soc_register_card(card); +} + +static int __devexit db1000_audio_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	snd_soc_unregister_card(card); +	return 0; +} + +static struct platform_driver db1000_audio_driver = { +	.driver	= { +		.name	= "db1000-audio", +		.owner	= THIS_MODULE, +		.pm	= &snd_soc_pm_ops, +	}, +	.probe		= db1000_audio_probe, +	.remove		= __devexit_p(db1000_audio_remove), +}; + +static int __init db1000_audio_load(void) +{ +	return platform_driver_register(&db1000_audio_driver); +} + +static void __exit db1000_audio_unload(void) +{ +	platform_driver_unregister(&db1000_audio_driver); +} + +module_init(db1000_audio_load); +module_exit(db1000_audio_unload); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DB1000/DB1500/DB1100 ASoC audio"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/db1200.c b/sound/soc/au1x/db1200.c index 1d3e258c9ea..289312c14b9 100644 --- a/sound/soc/au1x/db1200.c +++ b/sound/soc/au1x/db1200.c @@ -1,7 +1,7 @@  /*   * DB1200 ASoC audio fabric support code.   * - * (c) 2008-9 Manuel Lauss <manuel.lauss@gmail.com> + * (c) 2008-2011 Manuel Lauss <manuel.lauss@googlemail.com>   *   */ @@ -21,6 +21,17 @@  #include "../codecs/wm8731.h"  #include "psc.h" +static struct platform_device_id db1200_pids[] = { +	{ +		.name		= "db1200-ac97", +		.driver_data	= 0, +	}, { +		.name		= "db1200-i2s", +		.driver_data	= 1, +	}, +	{}, +}; +  /*-------------------------  AC97 PART  ---------------------------*/  static struct snd_soc_dai_link db1200_ac97_dai = { @@ -89,36 +100,47 @@ static struct snd_soc_card db1200_i2s_machine = {  /*-------------------------  COMMON PART  ---------------------------*/ -static struct platform_device *db1200_asoc_dev; +static struct snd_soc_card *db1200_cards[] __devinitdata = { +	&db1200_ac97_machine, +	&db1200_i2s_machine, +}; -static int __init db1200_audio_load(void) +static int __devinit db1200_audio_probe(struct platform_device *pdev)  { -	int ret; +	const struct platform_device_id *pid = platform_get_device_id(pdev); +	struct snd_soc_card *card; -	ret = -ENOMEM; -	db1200_asoc_dev = platform_device_alloc("soc-audio", 1); /* PSC1 */ -	if (!db1200_asoc_dev) -		goto out; +	card = db1200_cards[pid->driver_data]; +	card->dev = &pdev->dev; +	return snd_soc_register_card(card); +} -	/* DB1200 board setup set PSC1MUX to preferred audio device */ -	if (bcsr_read(BCSR_RESETS) & BCSR_RESETS_PSC1MUX) -		platform_set_drvdata(db1200_asoc_dev, &db1200_i2s_machine); -	else -		platform_set_drvdata(db1200_asoc_dev, &db1200_ac97_machine); +static int __devexit db1200_audio_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	snd_soc_unregister_card(card); +	return 0; +} -	ret = platform_device_add(db1200_asoc_dev); +static struct platform_driver db1200_audio_driver = { +	.driver	= { +		.name	= "db1200-ac97", +		.owner	= THIS_MODULE, +		.pm	= &snd_soc_pm_ops, +	}, +	.id_table	= db1200_pids, +	.probe		= db1200_audio_probe, +	.remove		= __devexit_p(db1200_audio_remove), +}; -	if (ret) { -		platform_device_put(db1200_asoc_dev); -		db1200_asoc_dev = NULL; -	} -out: -	return ret; +static int __init db1200_audio_load(void) +{ +	return platform_driver_register(&db1200_audio_driver);  }  static void __exit db1200_audio_unload(void)  { -	platform_device_unregister(db1200_asoc_dev); +	platform_driver_unregister(&db1200_audio_driver);  }  module_init(db1200_audio_load); diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c index 20bb53a837b..d7d04e26eee 100644 --- a/sound/soc/au1x/dbdma2.c +++ b/sound/soc/au1x/dbdma2.c @@ -169,7 +169,7 @@ static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd,  	au1x_pcm_dbdma_free(pcd); -	if (stype == PCM_RX) +	if (stype == SNDRV_PCM_STREAM_CAPTURE)  		pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id,  					DSCR_CMD0_ALWAYS,  					au1x_pcm_dmarx_cb, (void *)pcd); @@ -198,7 +198,7 @@ static inline struct au1xpsc_audio_dmadata *to_dmadata(struct snd_pcm_substream  	struct snd_soc_pcm_runtime *rtd = ss->private_data;  	struct au1xpsc_audio_dmadata *pcd =  				snd_soc_platform_get_drvdata(rtd->platform); -	return &pcd[SUBSTREAM_TYPE(ss)]; +	return &pcd[ss->stream];  }  static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream, @@ -212,7 +212,7 @@ static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream,  	if (ret < 0)  		goto out; -	stype = SUBSTREAM_TYPE(substream); +	stype = substream->stream;  	pcd = to_dmadata(substream);  	DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d " @@ -255,7 +255,7 @@ static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream)  	au1xxx_dbdma_reset(pcd->ddma_chan); -	if (SUBSTREAM_TYPE(substream) == PCM_RX) { +	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {  		au1x_pcm_queue_rx(pcd);  		au1x_pcm_queue_rx(pcd);  	} else { @@ -293,6 +293,16 @@ au1xpsc_pcm_pointer(struct snd_pcm_substream *substream)  static int au1xpsc_pcm_open(struct snd_pcm_substream *substream)  { +	struct au1xpsc_audio_dmadata *pcd = to_dmadata(substream); +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	int stype = substream->stream, *dmaids; + +	dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); +	if (!dmaids) +		return -ENODEV;	/* whoa, has ordering changed? */ + +	pcd->ddma_id = dmaids[stype]; +  	snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware);  	return 0;  } @@ -340,36 +350,18 @@ struct snd_soc_platform_driver au1xpsc_soc_platform = {  static int __devinit au1xpsc_pcm_drvprobe(struct platform_device *pdev)  {  	struct au1xpsc_audio_dmadata *dmadata; -	struct resource *r;  	int ret;  	dmadata = kzalloc(2 * sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);  	if (!dmadata)  		return -ENOMEM; -	r = platform_get_resource(pdev, IORESOURCE_DMA, 0); -	if (!r) { -		ret = -ENODEV; -		goto out1; -	} -	dmadata[PCM_TX].ddma_id = r->start; - -	/* RX DMA */ -	r = platform_get_resource(pdev, IORESOURCE_DMA, 1); -	if (!r) { -		ret = -ENODEV; -		goto out1; -	} -	dmadata[PCM_RX].ddma_id = r->start; -  	platform_set_drvdata(pdev, dmadata);  	ret = snd_soc_register_platform(&pdev->dev, &au1xpsc_soc_platform); -	if (!ret) -		return ret; +	if (ret) +		kfree(dmadata); -out1: -	kfree(dmadata);  	return ret;  } @@ -405,57 +397,6 @@ static void __exit au1xpsc_audio_dbdma_unload(void)  module_init(au1xpsc_audio_dbdma_load);  module_exit(au1xpsc_audio_dbdma_unload); - -struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev) -{ -	struct resource *res, *r; -	struct platform_device *pd; -	int id[2]; -	int ret; - -	r = platform_get_resource(pdev, IORESOURCE_DMA, 0); -	if (!r) -		return NULL; -	id[0] = r->start; - -	r = platform_get_resource(pdev, IORESOURCE_DMA, 1); -	if (!r) -		return NULL; -	id[1] = r->start; - -	res = kzalloc(sizeof(struct resource) * 2, GFP_KERNEL); -	if (!res) -		return NULL; - -	res[0].start = res[0].end = id[0]; -	res[1].start = res[1].end = id[1]; -	res[0].flags = res[1].flags = IORESOURCE_DMA; - -	pd = platform_device_alloc("au1xpsc-pcm", pdev->id); -	if (!pd) -		goto out; - -	pd->resource = res; -	pd->num_resources = 2; - -	ret = platform_device_add(pd); -	if (!ret) -		return pd; - -	platform_device_put(pd); -out: -	kfree(res); -	return NULL; -} -EXPORT_SYMBOL_GPL(au1xpsc_pcm_add); - -void au1xpsc_pcm_destroy(struct platform_device *dmapd) -{ -	if (dmapd) -		platform_device_unregister(dmapd); -} -EXPORT_SYMBOL_GPL(au1xpsc_pcm_destroy); -  MODULE_LICENSE("GPL");  MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver");  MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c new file mode 100644 index 00000000000..7aa5b760677 --- /dev/null +++ b/sound/soc/au1x/dma.c @@ -0,0 +1,377 @@ +/* + * Au1000/Au1500/Au1100 Audio DMA support. + * + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com> + * + * copied almost verbatim from the old ALSA driver, written by + *			Charles Eidsness <charles@cooper-street.com> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-au1x00/au1000_dma.h> + +#include "psc.h" + +#define ALCHEMY_PCM_FMTS					\ +	(SNDRV_PCM_FMTBIT_S8     | SNDRV_PCM_FMTBIT_U8 |	\ +	 SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |	\ +	 SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE |	\ +	 SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE |	\ +	 SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE |	\ +	 0) + +struct pcm_period { +	u32 start; +	u32 relative_end;	/* relative to start of buffer */ +	struct pcm_period *next; +}; + +struct audio_stream { +	struct snd_pcm_substream *substream; +	int dma; +	struct pcm_period *buffer; +	unsigned int period_size; +	unsigned int periods; +}; + +struct alchemy_pcm_ctx { +	struct audio_stream stream[2];	/* playback & capture */ +}; + +static void au1000_release_dma_link(struct audio_stream *stream) +{ +	struct pcm_period *pointer; +	struct pcm_period *pointer_next; + +	stream->period_size = 0; +	stream->periods = 0; +	pointer = stream->buffer; +	if (!pointer) +		return; +	do { +		pointer_next = pointer->next; +		kfree(pointer); +		pointer = pointer_next; +	} while (pointer != stream->buffer); +	stream->buffer = NULL; +} + +static int au1000_setup_dma_link(struct audio_stream *stream, +				 unsigned int period_bytes, +				 unsigned int periods) +{ +	struct snd_pcm_substream *substream = stream->substream; +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct pcm_period *pointer; +	unsigned long dma_start; +	int i; + +	dma_start = virt_to_phys(runtime->dma_area); + +	if (stream->period_size == period_bytes && +	    stream->periods == periods) +		return 0; /* not changed */ + +	au1000_release_dma_link(stream); + +	stream->period_size = period_bytes; +	stream->periods = periods; + +	stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL); +	if (!stream->buffer) +		return -ENOMEM; +	pointer = stream->buffer; +	for (i = 0; i < periods; i++) { +		pointer->start = (u32)(dma_start + (i * period_bytes)); +		pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1); +		if (i < periods - 1) { +			pointer->next = kmalloc(sizeof(struct pcm_period), +						GFP_KERNEL); +			if (!pointer->next) { +				au1000_release_dma_link(stream); +				return -ENOMEM; +			} +			pointer = pointer->next; +		} +	} +	pointer->next = stream->buffer; +	return 0; +} + +static void au1000_dma_stop(struct audio_stream *stream) +{ +	if (stream->buffer) +		disable_dma(stream->dma); +} + +static void au1000_dma_start(struct audio_stream *stream) +{ +	if (!stream->buffer) +		return; + +	init_dma(stream->dma); +	if (get_dma_active_buffer(stream->dma) == 0) { +		clear_dma_done0(stream->dma); +		set_dma_addr0(stream->dma, stream->buffer->start); +		set_dma_count0(stream->dma, stream->period_size >> 1); +		set_dma_addr1(stream->dma, stream->buffer->next->start); +		set_dma_count1(stream->dma, stream->period_size >> 1); +	} else { +		clear_dma_done1(stream->dma); +		set_dma_addr1(stream->dma, stream->buffer->start); +		set_dma_count1(stream->dma, stream->period_size >> 1); +		set_dma_addr0(stream->dma, stream->buffer->next->start); +		set_dma_count0(stream->dma, stream->period_size >> 1); +	} +	enable_dma_buffers(stream->dma); +	start_dma(stream->dma); +} + +static irqreturn_t au1000_dma_interrupt(int irq, void *ptr) +{ +	struct audio_stream *stream = (struct audio_stream *)ptr; +	struct snd_pcm_substream *substream = stream->substream; + +	switch (get_dma_buffer_done(stream->dma)) { +	case DMA_D0: +		stream->buffer = stream->buffer->next; +		clear_dma_done0(stream->dma); +		set_dma_addr0(stream->dma, stream->buffer->next->start); +		set_dma_count0(stream->dma, stream->period_size >> 1); +		enable_dma_buffer0(stream->dma); +		break; +	case DMA_D1: +		stream->buffer = stream->buffer->next; +		clear_dma_done1(stream->dma); +		set_dma_addr1(stream->dma, stream->buffer->next->start); +		set_dma_count1(stream->dma, stream->period_size >> 1); +		enable_dma_buffer1(stream->dma); +		break; +	case (DMA_D0 | DMA_D1): +		pr_debug("DMA %d missed interrupt.\n", stream->dma); +		au1000_dma_stop(stream); +		au1000_dma_start(stream); +		break; +	case (~DMA_D0 & ~DMA_D1): +		pr_debug("DMA %d empty irq.\n", stream->dma); +	} +	snd_pcm_period_elapsed(substream); +	return IRQ_HANDLED; +} + +static const struct snd_pcm_hardware alchemy_pcm_hardware = { +	.info		  = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | +			    SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH, +	.formats	  = ALCHEMY_PCM_FMTS, +	.rates		  = SNDRV_PCM_RATE_8000_192000, +	.rate_min	  = SNDRV_PCM_RATE_8000, +	.rate_max	  = SNDRV_PCM_RATE_192000, +	.channels_min	  = 2, +	.channels_max	  = 2, +	.period_bytes_min = 1024, +	.period_bytes_max = 16 * 1024 - 1, +	.periods_min	  = 4, +	.periods_max	  = 255, +	.buffer_bytes_max = 128 * 1024, +	.fifo_size	  = 16, +}; + +static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss) +{ +	struct snd_soc_pcm_runtime *rtd = ss->private_data; +	return snd_soc_platform_get_drvdata(rtd->platform); +} + +static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss) +{ +	struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss); +	return &(ctx->stream[ss->stream]); +} + +static int alchemy_pcm_open(struct snd_pcm_substream *substream) +{ +	struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream); +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	int *dmaids, s = substream->stream; +	char *name; + +	dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); +	if (!dmaids) +		return -ENODEV;	/* whoa, has ordering changed? */ + +	/* DMA setup */ +	name = (s == SNDRV_PCM_STREAM_PLAYBACK) ? "audio-tx" : "audio-rx"; +	ctx->stream[s].dma = request_au1000_dma(dmaids[s], name, +					au1000_dma_interrupt, IRQF_DISABLED, +					&ctx->stream[s]); +	set_dma_mode(ctx->stream[s].dma, +		     get_dma_mode(ctx->stream[s].dma) & ~DMA_NC); + +	ctx->stream[s].substream = substream; +	ctx->stream[s].buffer = NULL; +	snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware); + +	return 0; +} + +static int alchemy_pcm_close(struct snd_pcm_substream *substream) +{ +	struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream); +	int stype = substream->stream; + +	ctx->stream[stype].substream = NULL; +	free_au1000_dma(ctx->stream[stype].dma); + +	return 0; +} + +static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream, +				 struct snd_pcm_hw_params *hw_params) +{ +	struct audio_stream *stream = ss_to_as(substream); +	int err; + +	err = snd_pcm_lib_malloc_pages(substream, +				       params_buffer_bytes(hw_params)); +	if (err < 0) +		return err; +	err = au1000_setup_dma_link(stream, +				    params_period_bytes(hw_params), +				    params_periods(hw_params)); +	if (err) +		snd_pcm_lib_free_pages(substream); + +	return err; +} + +static int alchemy_pcm_hw_free(struct snd_pcm_substream *substream) +{ +	struct audio_stream *stream = ss_to_as(substream); +	au1000_release_dma_link(stream); +	return snd_pcm_lib_free_pages(substream); +} + +static int alchemy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ +	struct audio_stream *stream = ss_to_as(substream); +	int err = 0; + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +		au1000_dma_start(stream); +		break; +	case SNDRV_PCM_TRIGGER_STOP: +		au1000_dma_stop(stream); +		break; +	default: +		err = -EINVAL; +		break; +	} +	return err; +} + +static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_pcm_substream *ss) +{ +	struct audio_stream *stream = ss_to_as(ss); +	long location; + +	location = get_dma_residue(stream->dma); +	location = stream->buffer->relative_end - location; +	if (location == -1) +		location = 0; +	return bytes_to_frames(ss->runtime, location); +} + +static struct snd_pcm_ops alchemy_pcm_ops = { +	.open			= alchemy_pcm_open, +	.close			= alchemy_pcm_close, +	.ioctl			= snd_pcm_lib_ioctl, +	.hw_params	        = alchemy_pcm_hw_params, +	.hw_free	        = alchemy_pcm_hw_free, +	.trigger		= alchemy_pcm_trigger, +	.pointer		= alchemy_pcm_pointer, +}; + +static void alchemy_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ +	snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int alchemy_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ +	struct snd_pcm *pcm = rtd->pcm; + +	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, +		snd_dma_continuous_data(GFP_KERNEL), 65536, (4096 * 1024) - 1); + +	return 0; +} + +struct snd_soc_platform_driver alchemy_pcm_soc_platform = { +	.ops		= &alchemy_pcm_ops, +	.pcm_new	= alchemy_pcm_new, +	.pcm_free	= alchemy_pcm_free_dma_buffers, +}; + +static int __devinit alchemy_pcm_drvprobe(struct platform_device *pdev) +{ +	struct alchemy_pcm_ctx *ctx; +	int ret; + +	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); +	if (!ctx) +		return -ENOMEM; + +	platform_set_drvdata(pdev, ctx); + +	ret = snd_soc_register_platform(&pdev->dev, &alchemy_pcm_soc_platform); +	if (ret) +		kfree(ctx); + +	return ret; +} + +static int __devexit alchemy_pcm_drvremove(struct platform_device *pdev) +{ +	struct alchemy_pcm_ctx *ctx = platform_get_drvdata(pdev); + +	snd_soc_unregister_platform(&pdev->dev); +	kfree(ctx); + +	return 0; +} + +static struct platform_driver alchemy_pcmdma_driver = { +	.driver	= { +		.name	= "alchemy-pcm-dma", +		.owner	= THIS_MODULE, +	}, +	.probe		= alchemy_pcm_drvprobe, +	.remove		= __devexit_p(alchemy_pcm_drvremove), +}; + +static int __init alchemy_pcmdma_load(void) +{ +	return platform_driver_register(&alchemy_pcmdma_driver); +} + +static void __exit alchemy_pcmdma_unload(void) +{ +	platform_driver_unregister(&alchemy_pcmdma_driver); +} + +module_init(alchemy_pcmdma_load); +module_exit(alchemy_pcmdma_unload); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c new file mode 100644 index 00000000000..19e0d2a9c82 --- /dev/null +++ b/sound/soc/au1x/i2sc.c @@ -0,0 +1,346 @@ +/* + * Au1000/Au1500/Au1100 I2S controller driver for ASoC + * + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com> + * + * Note: clock supplied to the I2S controller must be 256x samplerate. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> +#include <asm/mach-au1x00/au1000.h> + +#include "psc.h" + +#define I2S_RXTX	0x00 +#define I2S_CFG		0x04 +#define I2S_ENABLE	0x08 + +#define CFG_XU		(1 << 25)	/* tx underflow */ +#define CFG_XO		(1 << 24) +#define CFG_RU		(1 << 23) +#define CFG_RO		(1 << 22) +#define CFG_TR		(1 << 21) +#define CFG_TE		(1 << 20) +#define CFG_TF		(1 << 19) +#define CFG_RR		(1 << 18) +#define CFG_RF		(1 << 17) +#define CFG_ICK		(1 << 12)	/* clock invert */ +#define CFG_PD		(1 << 11)	/* set to make I2SDIO INPUT */ +#define CFG_LB		(1 << 10)	/* loopback */ +#define CFG_IC		(1 << 9)	/* word select invert */ +#define CFG_FM_I2S	(0 << 7)	/* I2S format */ +#define CFG_FM_LJ	(1 << 7)	/* left-justified */ +#define CFG_FM_RJ	(2 << 7)	/* right-justified */ +#define CFG_FM_MASK	(3 << 7) +#define CFG_TN		(1 << 6)	/* tx fifo en */ +#define CFG_RN		(1 << 5)	/* rx fifo en */ +#define CFG_SZ_8	(0x08) +#define CFG_SZ_16	(0x10) +#define CFG_SZ_18	(0x12) +#define CFG_SZ_20	(0x14) +#define CFG_SZ_24	(0x18) +#define CFG_SZ_MASK	(0x1f) +#define EN_D		(1 << 1)	/* DISable */ +#define EN_CE		(1 << 0)	/* clock enable */ + +/* only limited by clock generator and board design */ +#define AU1XI2SC_RATES \ +	SNDRV_PCM_RATE_CONTINUOUS + +#define AU1XI2SC_FMTS \ +	(SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |		\ +	SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |	\ +	SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE |	\ +	SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE |	\ +	SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE |	\ +	SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |	\ +	SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE |	\ +	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |	\ +	SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE |	\ +	0) + +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg) +{ +	return __raw_readl(ctx->mmio + reg); +} + +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v) +{ +	__raw_writel(v, ctx->mmio + reg); +	wmb(); +} + +static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ +	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai); +	unsigned long c; +	int ret; + +	ret = -EINVAL; +	c = ctx->cfg; + +	c &= ~CFG_FM_MASK; +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { +	case SND_SOC_DAIFMT_I2S: +		c |= CFG_FM_I2S; +		break; +	case SND_SOC_DAIFMT_MSB: +		c |= CFG_FM_RJ; +		break; +	case SND_SOC_DAIFMT_LSB: +		c |= CFG_FM_LJ; +		break; +	default: +		goto out; +	} + +	c &= ~(CFG_IC | CFG_ICK);		/* IB-IF */ +	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { +	case SND_SOC_DAIFMT_NB_NF: +		c |= CFG_IC | CFG_ICK; +		break; +	case SND_SOC_DAIFMT_NB_IF: +		c |= CFG_IC; +		break; +	case SND_SOC_DAIFMT_IB_NF: +		c |= CFG_ICK; +		break; +	case SND_SOC_DAIFMT_IB_IF: +		break; +	default: +		goto out; +	} + +	/* I2S controller only supports master */ +	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { +	case SND_SOC_DAIFMT_CBS_CFS:	/* CODEC slave */ +		break; +	default: +		goto out; +	} + +	ret = 0; +	ctx->cfg = c; +out: +	return ret; +} + +static int au1xi2s_trigger(struct snd_pcm_substream *substream, +			   int cmd, struct snd_soc_dai *dai) +{ +	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); +	int stype = SUBSTREAM_TYPE(substream); + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +	case SNDRV_PCM_TRIGGER_RESUME: +		/* power up */ +		WR(ctx, I2S_ENABLE, EN_D | EN_CE); +		WR(ctx, I2S_ENABLE, EN_CE); +		ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN; +		WR(ctx, I2S_CFG, ctx->cfg); +		break; +	case SNDRV_PCM_TRIGGER_STOP: +	case SNDRV_PCM_TRIGGER_SUSPEND: +		ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN); +		WR(ctx, I2S_CFG, ctx->cfg); +		WR(ctx, I2S_ENABLE, EN_D);		/* power off */ +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static unsigned long msbits_to_reg(int msbits) +{ +	switch (msbits) { +	case 8: +		return CFG_SZ_8; +	case 16: +		return CFG_SZ_16; +	case 18: +		return CFG_SZ_18; +	case 20: +		return CFG_SZ_20; +	case 24: +		return CFG_SZ_24; +	} +	return 0; +} + +static int au1xi2s_hw_params(struct snd_pcm_substream *substream, +			     struct snd_pcm_hw_params *params, +			     struct snd_soc_dai *dai) +{ +	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); +	unsigned long v; + +	v = msbits_to_reg(params->msbits); +	if (!v) +		return -EINVAL; + +	ctx->cfg &= ~CFG_SZ_MASK; +	ctx->cfg |= v; +	return 0; +} + +static int au1xi2s_startup(struct snd_pcm_substream *substream, +			   struct snd_soc_dai *dai) +{ +	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai); +	snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]); +	return 0; +} + +static const struct snd_soc_dai_ops au1xi2s_dai_ops = { +	.startup	= au1xi2s_startup, +	.trigger	= au1xi2s_trigger, +	.hw_params	= au1xi2s_hw_params, +	.set_fmt	= au1xi2s_set_fmt, +}; + +static struct snd_soc_dai_driver au1xi2s_dai_driver = { +	.symmetric_rates	= 1, +	.playback = { +		.rates		= AU1XI2SC_RATES, +		.formats	= AU1XI2SC_FMTS, +		.channels_min	= 2, +		.channels_max	= 2, +	}, +	.capture = { +		.rates		= AU1XI2SC_RATES, +		.formats	= AU1XI2SC_FMTS, +		.channels_min	= 2, +		.channels_max	= 2, +	}, +	.ops = &au1xi2s_dai_ops, +}; + +static int __devinit au1xi2s_drvprobe(struct platform_device *pdev) +{ +	int ret; +	struct resource *r; +	struct au1xpsc_audio_data *ctx; + +	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); +	if (!ctx) +		return -ENOMEM; + +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!r) { +		ret = -ENODEV; +		goto out0; +	} + +	ret = -EBUSY; +	if (!request_mem_region(r->start, resource_size(r), pdev->name)) +		goto out0; + +	ctx->mmio = ioremap_nocache(r->start, resource_size(r)); +	if (!ctx->mmio) +		goto out1; + +	r = platform_get_resource(pdev, IORESOURCE_DMA, 0); +	if (!r) +		goto out1; +	ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = r->start; + +	r = platform_get_resource(pdev, IORESOURCE_DMA, 1); +	if (!r) +		goto out1; +	ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = r->start; + +	platform_set_drvdata(pdev, ctx); + +	ret = snd_soc_register_dai(&pdev->dev, &au1xi2s_dai_driver); +	if (ret) +		goto out1; + +	return 0; + +out1: +	release_mem_region(r->start, resource_size(r)); +out0: +	kfree(ctx); +	return ret; +} + +static int __devexit au1xi2s_drvremove(struct platform_device *pdev) +{ +	struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev); +	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + +	snd_soc_unregister_dai(&pdev->dev); + +	WR(ctx, I2S_ENABLE, EN_D);	/* clock off, disable */ + +	iounmap(ctx->mmio); +	release_mem_region(r->start, resource_size(r)); +	kfree(ctx); + +	return 0; +} + +#ifdef CONFIG_PM +static int au1xi2s_drvsuspend(struct device *dev) +{ +	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev); + +	WR(ctx, I2S_ENABLE, EN_D);	/* clock off, disable */ + +	return 0; +} + +static int au1xi2s_drvresume(struct device *dev) +{ +	return 0; +} + +static const struct dev_pm_ops au1xi2sc_pmops = { +	.suspend	= au1xi2s_drvsuspend, +	.resume		= au1xi2s_drvresume, +}; + +#define AU1XI2SC_PMOPS (&au1xi2sc_pmops) + +#else + +#define AU1XI2SC_PMOPS NULL + +#endif + +static struct platform_driver au1xi2s_driver = { +	.driver	= { +		.name	= "alchemy-i2sc", +		.owner	= THIS_MODULE, +		.pm	= AU1XI2SC_PMOPS, +	}, +	.probe		= au1xi2s_drvprobe, +	.remove		= __devexit_p(au1xi2s_drvremove), +}; + +static int __init au1xi2s_load(void) +{ +	return platform_driver_register(&au1xi2s_driver); +} + +static void __exit au1xi2s_unload(void) +{ +	platform_driver_unregister(&au1xi2s_driver); +} + +module_init(au1xi2s_load); +module_exit(au1xi2s_unload); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver"); +MODULE_AUTHOR("Manuel Lauss"); diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c index d0db66f24a0..172eefd38b2 100644 --- a/sound/soc/au1x/psc-ac97.c +++ b/sound/soc/au1x/psc-ac97.c @@ -41,14 +41,14 @@  	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE)  #define AC97PCR_START(stype)	\ -	((stype) == PCM_TX ? PSC_AC97PCR_TS : PSC_AC97PCR_RS) +	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TS : PSC_AC97PCR_RS)  #define AC97PCR_STOP(stype)	\ -	((stype) == PCM_TX ? PSC_AC97PCR_TP : PSC_AC97PCR_RP) +	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TP : PSC_AC97PCR_RP)  #define AC97PCR_CLRFIFO(stype)	\ -	((stype) == PCM_TX ? PSC_AC97PCR_TC : PSC_AC97PCR_RC) +	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97PCR_TC : PSC_AC97PCR_RC)  #define AC97STAT_BUSY(stype)	\ -	((stype) == PCM_TX ? PSC_AC97STAT_TB : PSC_AC97STAT_RB) +	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_AC97STAT_TB : PSC_AC97STAT_RB)  /* instance data. There can be only one, MacLeod!!!! */  static struct au1xpsc_audio_data *au1xpsc_ac97_workdata; @@ -215,7 +215,7 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,  {  	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai);  	unsigned long r, ro, stat; -	int chans, t, stype = SUBSTREAM_TYPE(substream); +	int chans, t, stype = substream->stream;  	chans = params_channels(params); @@ -235,7 +235,7 @@ static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,  		r |= PSC_AC97CFG_SET_LEN(params->msbits);  		/* channels: enable slots for front L/R channel */ -		if (stype == PCM_TX) { +		if (stype == SNDRV_PCM_STREAM_PLAYBACK) {  			r &= ~PSC_AC97CFG_TXSLOT_MASK;  			r |= PSC_AC97CFG_TXSLOT_ENA(3);  			r |= PSC_AC97CFG_TXSLOT_ENA(4); @@ -294,7 +294,7 @@ static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,  				int cmd, struct snd_soc_dai *dai)  {  	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); -	int ret, stype = SUBSTREAM_TYPE(substream); +	int ret, stype = substream->stream;  	ret = 0; @@ -324,12 +324,21 @@ static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,  	return ret;  } +static int au1xpsc_ac97_startup(struct snd_pcm_substream *substream, +				struct snd_soc_dai *dai) +{ +	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); +	snd_soc_dai_set_dma_data(dai, substream, &pscdata->dmaids[0]); +	return 0; +} +  static int au1xpsc_ac97_probe(struct snd_soc_dai *dai)  {  	return au1xpsc_ac97_workdata ? 0 : -ENODEV;  }  static struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = { +	.startup	= au1xpsc_ac97_startup,  	.trigger	= au1xpsc_ac97_trigger,  	.hw_params	= au1xpsc_ac97_hw_params,  }; @@ -379,6 +388,16 @@ static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)  	if (!wd->mmio)  		goto out1; +	r = platform_get_resource(pdev, IORESOURCE_DMA, 0); +	if (!r) +		goto out2; +	wd->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = r->start; + +	r = platform_get_resource(pdev, IORESOURCE_DMA, 1); +	if (!r) +		goto out2; +	wd->dmaids[SNDRV_PCM_STREAM_CAPTURE] = r->start; +  	/* configuration: max dma trigger threshold, enable ac97 */  	wd->cfg = PSC_AC97CFG_RT_FIFO8 | PSC_AC97CFG_TT_FIFO8 |  		  PSC_AC97CFG_DE_ENABLE; @@ -401,15 +420,13 @@ static int __devinit au1xpsc_ac97_drvprobe(struct platform_device *pdev)  	ret = snd_soc_register_dai(&pdev->dev, &wd->dai_drv);  	if (ret) -		goto out1; +		goto out2; -	wd->dmapd = au1xpsc_pcm_add(pdev); -	if (wd->dmapd) { -		au1xpsc_ac97_workdata = wd; -		return 0; -	} +	au1xpsc_ac97_workdata = wd; +	return 0; -	snd_soc_unregister_dai(&pdev->dev); +out2: +	iounmap(wd->mmio);  out1:  	release_mem_region(r->start, resource_size(r));  out0: @@ -422,9 +439,6 @@ static int __devexit au1xpsc_ac97_drvremove(struct platform_device *pdev)  	struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);  	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	if (wd->dmapd) -		au1xpsc_pcm_destroy(wd->dmapd); -  	snd_soc_unregister_dai(&pdev->dev);  	/* disable PSC completely */ diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c index fca09127632..7c5ae920544 100644 --- a/sound/soc/au1x/psc-i2s.c +++ b/sound/soc/au1x/psc-i2s.c @@ -42,13 +42,13 @@  	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)  #define I2SSTAT_BUSY(stype)	\ -	((stype) == PCM_TX ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB) +	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB)  #define I2SPCR_START(stype)	\ -	((stype) == PCM_TX ? PSC_I2SPCR_TS : PSC_I2SPCR_RS) +	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TS : PSC_I2SPCR_RS)  #define I2SPCR_STOP(stype)	\ -	((stype) == PCM_TX ? PSC_I2SPCR_TP : PSC_I2SPCR_RP) +	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TP : PSC_I2SPCR_RP)  #define I2SPCR_CLRFIFO(stype)	\ -	((stype) == PCM_TX ? PSC_I2SPCR_TC : PSC_I2SPCR_RC) +	((stype) == SNDRV_PCM_STREAM_PLAYBACK ? PSC_I2SPCR_TC : PSC_I2SPCR_RC)  static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, @@ -240,7 +240,7 @@ static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,  			       struct snd_soc_dai *dai)  {  	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); -	int ret, stype = SUBSTREAM_TYPE(substream); +	int ret, stype = substream->stream;  	switch (cmd) {  	case SNDRV_PCM_TRIGGER_START: @@ -257,7 +257,16 @@ static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd,  	return ret;  } +static int au1xpsc_i2s_startup(struct snd_pcm_substream *substream, +			       struct snd_soc_dai *dai) +{ +	struct au1xpsc_audio_data *pscdata = snd_soc_dai_get_drvdata(dai); +	snd_soc_dai_set_dma_data(dai, substream, &pscdata->dmaids[0]); +	return 0; +} +  static struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = { +	.startup	= au1xpsc_i2s_startup,  	.trigger	= au1xpsc_i2s_trigger,  	.hw_params	= au1xpsc_i2s_hw_params,  	.set_fmt	= au1xpsc_i2s_set_fmt, @@ -304,6 +313,16 @@ static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev)  	if (!wd->mmio)  		goto out1; +	r = platform_get_resource(pdev, IORESOURCE_DMA, 0); +	if (!r) +		goto out2; +	wd->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = r->start; + +	r = platform_get_resource(pdev, IORESOURCE_DMA, 1); +	if (!r) +		goto out2; +	wd->dmaids[SNDRV_PCM_STREAM_CAPTURE] = r->start; +  	/* preserve PSC clock source set up by platform (dev.platform_data  	 * is already occupied by soc layer)  	 */ @@ -330,15 +349,11 @@ static int __devinit au1xpsc_i2s_drvprobe(struct platform_device *pdev)  	platform_set_drvdata(pdev, wd);  	ret = snd_soc_register_dai(&pdev->dev, &wd->dai_drv); -	if (ret) -		goto out1; - -	/* finally add the DMA device for this PSC */ -	wd->dmapd = au1xpsc_pcm_add(pdev); -	if (wd->dmapd) +	if (!ret)  		return 0; -	snd_soc_unregister_dai(&pdev->dev); +out2: +	iounmap(wd->mmio);  out1:  	release_mem_region(r->start, resource_size(r));  out0: @@ -351,9 +366,6 @@ static int __devexit au1xpsc_i2s_drvremove(struct platform_device *pdev)  	struct au1xpsc_audio_data *wd = platform_get_drvdata(pdev);  	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	if (wd->dmapd) -		au1xpsc_pcm_destroy(wd->dmapd); -  	snd_soc_unregister_dai(&pdev->dev);  	au_writel(0, I2S_CFG(wd)); diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h index b30eadd422a..b16b2e02e0c 100644 --- a/sound/soc/au1x/psc.h +++ b/sound/soc/au1x/psc.h @@ -1,7 +1,7 @@  /* - * Au12x0/Au1550 PSC ALSA ASoC audio support. + * Alchemy ALSA ASoC audio support.   * - * (c) 2007-2008 MSC Vertriebsges.m.b.H., + * (c) 2007-2011 MSC Vertriebsges.m.b.H.,   *	Manuel Lauss <manuel.lauss@gmail.com>   *   * This program is free software; you can redistribute it and/or modify @@ -13,10 +13,6 @@  #ifndef _AU1X_PCM_H  #define _AU1X_PCM_H -/* DBDMA helpers */ -extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev); -extern void au1xpsc_pcm_destroy(struct platform_device *dmapd); -  struct au1xpsc_audio_data {  	void __iomem *mmio; @@ -27,15 +23,9 @@ struct au1xpsc_audio_data {  	unsigned long pm[2];  	struct mutex lock; -	struct platform_device *dmapd; +	int dmaids[2];  }; -#define PCM_TX	0 -#define PCM_RX	1 - -#define SUBSTREAM_TYPE(substream) \ -	((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX) -  /* easy access macros */  #define PSC_CTRL(x)	((unsigned long)((x)->mmio) + PSC_CTRL_OFFSET)  #define PSC_SEL(x)	((unsigned long)((x)->mmio) + PSC_SEL_OFFSET) diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index fe9d548a683..9f6bc55fc39 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -27,6 +27,19 @@ config SND_SOC_BFIN_EVAL_ADAU1701  	  board connected to one of the Blackfin evaluation boards like the  	  BF5XX-STAMP or BF5XX-EZKIT. +config SND_SOC_BFIN_EVAL_ADAU1373 +	tristate "Support for the EVAL-ADAU1373 board on Blackfin eval boards" +	depends on SND_BF5XX_I2S && I2C +	select SND_BF5XX_SOC_I2S +	select SND_SOC_ADAU1373 +	help +	  Say Y if you want to add support for the Analog Devices EVAL-ADAU1373 +	  board connected to one of the Blackfin evaluation boards like the +	  BF5XX-STAMP or BF5XX-EZKIT. + +	  Note: This driver assumes that first ADAU1373 DAI is connected to the +	  first SPORT port on the BF5XX board. +  config SND_SOC_BFIN_EVAL_ADAV80X  	tristate "Support for the EVAL-ADAV80X boards on Blackfin eval boards"  	depends on SND_BF5XX_I2S && (SPI_MASTER || I2C) diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index 6018bf52a23..1bf86ccaa8d 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -21,6 +21,7 @@ snd-ad1980-objs := bf5xx-ad1980.o  snd-ssm2602-objs := bf5xx-ssm2602.o  snd-ad73311-objs := bf5xx-ad73311.o  snd-ad193x-objs := bf5xx-ad193x.o +snd-soc-bfin-eval-adau1373-objs := bfin-eval-adau1373.o  snd-soc-bfin-eval-adau1701-objs := bfin-eval-adau1701.o  snd-soc-bfin-eval-adav80x-objs := bfin-eval-adav80x.o @@ -29,5 +30,6 @@ obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o  obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o  obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o  obj-$(CONFIG_SND_BF5XX_SOC_AD193X) += snd-ad193x.o +obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1373) += snd-soc-bfin-eval-adau1373.o  obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAU1701) += snd-soc-bfin-eval-adau1701.o  obj-$(CONFIG_SND_SOC_BFIN_EVAL_ADAV80X) += snd-soc-bfin-eval-adav80x.o diff --git a/sound/soc/blackfin/bfin-eval-adau1373.c b/sound/soc/blackfin/bfin-eval-adau1373.c new file mode 100644 index 00000000000..8df2a3b0cb3 --- /dev/null +++ b/sound/soc/blackfin/bfin-eval-adau1373.c @@ -0,0 +1,202 @@ +/* + * Machine driver for EVAL-ADAU1373 on Analog Devices bfin + * evaluation boards. + * + * Copyright 2011 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> + +#include "../codecs/adau1373.h" + +static const struct snd_soc_dapm_widget bfin_eval_adau1373_dapm_widgets[] = { +	SND_SOC_DAPM_LINE("Line In1", NULL), +	SND_SOC_DAPM_LINE("Line In2", NULL), +	SND_SOC_DAPM_LINE("Line In3", NULL), +	SND_SOC_DAPM_LINE("Line In4", NULL), + +	SND_SOC_DAPM_LINE("Line Out1", NULL), +	SND_SOC_DAPM_LINE("Line Out2", NULL), +	SND_SOC_DAPM_LINE("Stereo Out", NULL), +	SND_SOC_DAPM_HP("Headphone", NULL), +	SND_SOC_DAPM_HP("Earpiece", NULL), +	SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route bfin_eval_adau1373_dapm_routes[] = { +	{ "AIN1L", NULL, "Line In1" }, +	{ "AIN1R", NULL, "Line In1" }, +	{ "AIN2L", NULL, "Line In2" }, +	{ "AIN2R", NULL, "Line In2" }, +	{ "AIN3L", NULL, "Line In3" }, +	{ "AIN3R", NULL, "Line In3" }, +	{ "AIN4L", NULL, "Line In4" }, +	{ "AIN4R", NULL, "Line In4" }, + +	/* MICBIAS can be connected via a jumper to the line-in jack, since w +	   don't know which one is going to be used, just power both. */ +	{ "Line In1", NULL, "MICBIAS1" }, +	{ "Line In2", NULL, "MICBIAS1" }, +	{ "Line In3", NULL, "MICBIAS1" }, +	{ "Line In4", NULL, "MICBIAS1" }, +	{ "Line In1", NULL, "MICBIAS2" }, +	{ "Line In2", NULL, "MICBIAS2" }, +	{ "Line In3", NULL, "MICBIAS2" }, +	{ "Line In4", NULL, "MICBIAS2" }, + +	{ "Line Out1", NULL, "LOUT1L" }, +	{ "Line Out1", NULL, "LOUT1R" }, +	{ "Line Out2", NULL, "LOUT2L" }, +	{ "Line Out2", NULL, "LOUT2R" }, +	{ "Headphone", NULL, "HPL" }, +	{ "Headphone", NULL, "HPR" }, +	{ "Earpiece", NULL, "EP" }, +	{ "Speaker", NULL, "SPKL" }, +	{ "Stereo Out", NULL, "SPKR" }, +}; + +static int bfin_eval_adau1373_hw_params(struct snd_pcm_substream *substream, +	struct snd_pcm_hw_params *params) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct snd_soc_dai *cpu_dai = rtd->cpu_dai; +	struct snd_soc_dai *codec_dai = rtd->codec_dai; +	int ret; +	int pll_rate; + +	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | +			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); +	if (ret) +		return ret; + +	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | +			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); +	if (ret) +		return ret; + +	switch (params_rate(params)) { +	case 48000: +	case 8000: +	case 12000: +	case 16000: +	case 24000: +	case 32000: +		pll_rate = 48000 * 1024; +		break; +	case 44100: +	case 7350: +	case 11025: +	case 14700: +	case 22050: +	case 29400: +		pll_rate = 44100 * 1024; +		break; +	default: +		return -EINVAL; +	} + +	ret = snd_soc_dai_set_pll(codec_dai, ADAU1373_PLL1, +			ADAU1373_PLL_SRC_MCLK1, 12288000, pll_rate); +	if (ret) +		return ret; + +	ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1373_CLK_SRC_PLL1, pll_rate, +			SND_SOC_CLOCK_IN); + +	return ret; +} + +static int bfin_eval_adau1373_codec_init(struct snd_soc_pcm_runtime *rtd) +{ +	struct snd_soc_dai *codec_dai = rtd->codec_dai; +	unsigned int pll_rate = 48000 * 1024; +	int ret; + +	ret = snd_soc_dai_set_pll(codec_dai, ADAU1373_PLL1, +			ADAU1373_PLL_SRC_MCLK1, 12288000, pll_rate); +	if (ret) +		return ret; + +	ret = snd_soc_dai_set_sysclk(codec_dai, ADAU1373_CLK_SRC_PLL1, pll_rate, +			SND_SOC_CLOCK_IN); + +	return ret; +} +static struct snd_soc_ops bfin_eval_adau1373_ops = { +	.hw_params = bfin_eval_adau1373_hw_params, +}; + +static struct snd_soc_dai_link bfin_eval_adau1373_dai = { +	.name = "adau1373", +	.stream_name = "adau1373", +	.cpu_dai_name = "bfin-i2s.0", +	.codec_dai_name = "adau1373-aif1", +	.platform_name = "bfin-i2s-pcm-audio", +	.codec_name = "adau1373.0-001a", +	.ops = &bfin_eval_adau1373_ops, +	.init = bfin_eval_adau1373_codec_init, +}; + +static struct snd_soc_card bfin_eval_adau1373 = { +	.name = "bfin-eval-adau1373", +	.dai_link = &bfin_eval_adau1373_dai, +	.num_links = 1, + +	.dapm_widgets		= bfin_eval_adau1373_dapm_widgets, +	.num_dapm_widgets	= ARRAY_SIZE(bfin_eval_adau1373_dapm_widgets), +	.dapm_routes		= bfin_eval_adau1373_dapm_routes, +	.num_dapm_routes	= ARRAY_SIZE(bfin_eval_adau1373_dapm_routes), +}; + +static int bfin_eval_adau1373_probe(struct platform_device *pdev) +{ +	struct snd_soc_card *card = &bfin_eval_adau1373; + +	card->dev = &pdev->dev; + +	return snd_soc_register_card(&bfin_eval_adau1373); +} + +static int __devexit bfin_eval_adau1373_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); + +	snd_soc_unregister_card(card); + +	return 0; +} + +static struct platform_driver bfin_eval_adau1373_driver = { +	.driver = { +		.name = "bfin-eval-adau1373", +		.owner = THIS_MODULE, +		.pm = &snd_soc_pm_ops, +	}, +	.probe = bfin_eval_adau1373_probe, +	.remove = __devexit_p(bfin_eval_adau1373_remove), +}; + +static int __init bfin_eval_adau1373_init(void) +{ +	return platform_driver_register(&bfin_eval_adau1373_driver); +} +module_init(bfin_eval_adau1373_init); + +static void __exit bfin_eval_adau1373_exit(void) +{ +	platform_driver_unregister(&bfin_eval_adau1373_driver); +} +module_exit(bfin_eval_adau1373_exit); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("ALSA SoC bfin adau1373 driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bfin-eval-adau1373"); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 665d9240c4a..71b46c8f70d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS  	select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI  	select SND_SOC_AD1980 if SND_SOC_AC97_BUS  	select SND_SOC_AD73311 +	select SND_SOC_ADAU1373 if I2C  	select SND_SOC_ADAV80X  	select SND_SOC_ADS117X  	select SND_SOC_AK4104 if SPI_MASTER @@ -139,6 +140,9 @@ config SND_SOC_ADAU1701  	select SIGMA  	tristate +config SND_SOC_ADAU1373 +	tristate +  config SND_SOC_ADAV80X  	tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 5119a7e2c1a..70c1769acd1 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -5,6 +5,7 @@ snd-soc-ad193x-objs := ad193x.o  snd-soc-ad1980-objs := ad1980.o  snd-soc-ad73311-objs := ad73311.o  snd-soc-adau1701-objs := adau1701.o +snd-soc-adau1373-objs := adau1373.o  snd-soc-adav80x-objs := adav80x.o  snd-soc-ads117x-objs := ads117x.o  snd-soc-ak4104-objs := ak4104.o @@ -100,6 +101,7 @@ obj-$(CONFIG_SND_SOC_AD1836)	+= snd-soc-ad1836.o  obj-$(CONFIG_SND_SOC_AD193X)	+= snd-soc-ad193x.o  obj-$(CONFIG_SND_SOC_AD1980)	+= snd-soc-ad1980.o  obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_ADAU1373)	+= snd-soc-adau1373.o  obj-$(CONFIG_SND_SOC_ADAU1701)  += snd-soc-adau1701.o  obj-$(CONFIG_SND_SOC_ADAV80X)  += snd-soc-adav80x.o  obj-$(CONFIG_SND_SOC_ADS117X)	+= snd-soc-ads117x.o diff --git a/sound/soc/codecs/adau1373.c b/sound/soc/codecs/adau1373.c new file mode 100644 index 00000000000..2aa40c3731d --- /dev/null +++ b/sound/soc/codecs/adau1373.c @@ -0,0 +1,1414 @@ +/* + * Analog Devices ADAU1373 Audio Codec drive + * + * Copyright 2011 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/gcd.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include <sound/soc.h> +#include <sound/adau1373.h> + +#include "adau1373.h" + +struct adau1373_dai { +	unsigned int clk_src; +	unsigned int sysclk; +	bool enable_src; +	bool master; +}; + +struct adau1373 { +	struct adau1373_dai dais[3]; +}; + +#define ADAU1373_INPUT_MODE	0x00 +#define ADAU1373_AINL_CTRL(x)	(0x01 + (x) * 2) +#define ADAU1373_AINR_CTRL(x)	(0x02 + (x) * 2) +#define ADAU1373_LLINE_OUT(x)	(0x9 + (x) * 2) +#define ADAU1373_RLINE_OUT(x)	(0xa + (x) * 2) +#define ADAU1373_LSPK_OUT	0x0d +#define ADAU1373_RSPK_OUT	0x0e +#define ADAU1373_LHP_OUT	0x0f +#define ADAU1373_RHP_OUT	0x10 +#define ADAU1373_ADC_GAIN	0x11 +#define ADAU1373_LADC_MIXER	0x12 +#define ADAU1373_RADC_MIXER	0x13 +#define ADAU1373_LLINE1_MIX	0x14 +#define ADAU1373_RLINE1_MIX	0x15 +#define ADAU1373_LLINE2_MIX	0x16 +#define ADAU1373_RLINE2_MIX	0x17 +#define ADAU1373_LSPK_MIX	0x18 +#define ADAU1373_RSPK_MIX	0x19 +#define ADAU1373_LHP_MIX	0x1a +#define ADAU1373_RHP_MIX	0x1b +#define ADAU1373_EP_MIX		0x1c +#define ADAU1373_HP_CTRL	0x1d +#define ADAU1373_HP_CTRL2	0x1e +#define ADAU1373_LS_CTRL	0x1f +#define ADAU1373_EP_CTRL	0x21 +#define ADAU1373_MICBIAS_CTRL1	0x22 +#define ADAU1373_MICBIAS_CTRL2	0x23 +#define ADAU1373_OUTPUT_CTRL	0x24 +#define ADAU1373_PWDN_CTRL1	0x25 +#define ADAU1373_PWDN_CTRL2	0x26 +#define ADAU1373_PWDN_CTRL3	0x27 +#define ADAU1373_DPLL_CTRL(x)	(0x28 + (x) * 7) +#define ADAU1373_PLL_CTRL1(x)	(0x29 + (x) * 7) +#define ADAU1373_PLL_CTRL2(x)	(0x2a + (x) * 7) +#define ADAU1373_PLL_CTRL3(x)	(0x2b + (x) * 7) +#define ADAU1373_PLL_CTRL4(x)	(0x2c + (x) * 7) +#define ADAU1373_PLL_CTRL5(x)	(0x2d + (x) * 7) +#define ADAU1373_PLL_CTRL6(x)	(0x2e + (x) * 7) +#define ADAU1373_PLL_CTRL7(x)	(0x2f + (x) * 7) +#define ADAU1373_HEADDECT	0x36 +#define ADAU1373_ADC_DAC_STATUS	0x37 +#define ADAU1373_ADC_CTRL	0x3c +#define ADAU1373_DAI(x)		(0x44 + (x)) +#define ADAU1373_CLK_SRC_DIV(x)	(0x40 + (x) * 2) +#define ADAU1373_BCLKDIV(x)	(0x47 + (x)) +#define ADAU1373_SRC_RATIOA(x)	(0x4a + (x) * 2) +#define ADAU1373_SRC_RATIOB(x)	(0x4b + (x) * 2) +#define ADAU1373_DEEMP_CTRL	0x50 +#define ADAU1373_SRC_DAI_CTRL(x) (0x51 + (x)) +#define ADAU1373_DIN_MIX_CTRL(x) (0x56 + (x)) +#define ADAU1373_DOUT_MIX_CTRL(x) (0x5b + (x)) +#define ADAU1373_DAI_PBL_VOL(x)	(0x62 + (x) * 2) +#define ADAU1373_DAI_PBR_VOL(x)	(0x63 + (x) * 2) +#define ADAU1373_DAI_RECL_VOL(x) (0x68 + (x) * 2) +#define ADAU1373_DAI_RECR_VOL(x) (0x69 + (x) * 2) +#define ADAU1373_DAC1_PBL_VOL	0x6e +#define ADAU1373_DAC1_PBR_VOL	0x6f +#define ADAU1373_DAC2_PBL_VOL	0x70 +#define ADAU1373_DAC2_PBR_VOL	0x71 +#define ADAU1373_ADC_RECL_VOL	0x72 +#define ADAU1373_ADC_RECR_VOL	0x73 +#define ADAU1373_DMIC_RECL_VOL	0x74 +#define ADAU1373_DMIC_RECR_VOL	0x75 +#define ADAU1373_VOL_GAIN1	0x76 +#define ADAU1373_VOL_GAIN2	0x77 +#define ADAU1373_VOL_GAIN3	0x78 +#define ADAU1373_HPF_CTRL	0x7d +#define ADAU1373_BASS1		0x7e +#define ADAU1373_BASS2		0x7f +#define ADAU1373_DRC(x)		(0x80 + (x) * 0x10) +#define ADAU1373_3D_CTRL1	0xc0 +#define ADAU1373_3D_CTRL2	0xc1 +#define ADAU1373_FDSP_SEL1	0xdc +#define ADAU1373_FDSP_SEL2	0xdd +#define ADAU1373_FDSP_SEL3	0xde +#define ADAU1373_FDSP_SEL4	0xdf +#define ADAU1373_DIGMICCTRL	0xe2 +#define ADAU1373_DIGEN		0xeb +#define ADAU1373_SOFT_RESET	0xff + + +#define ADAU1373_PLL_CTRL6_DPLL_BYPASS	BIT(1) +#define ADAU1373_PLL_CTRL6_PLL_EN	BIT(0) + +#define ADAU1373_DAI_INVERT_BCLK	BIT(7) +#define ADAU1373_DAI_MASTER		BIT(6) +#define ADAU1373_DAI_INVERT_LRCLK	BIT(4) +#define ADAU1373_DAI_WLEN_16		0x0 +#define ADAU1373_DAI_WLEN_20		0x4 +#define ADAU1373_DAI_WLEN_24		0x8 +#define ADAU1373_DAI_WLEN_32		0xc +#define ADAU1373_DAI_WLEN_MASK		0xc +#define ADAU1373_DAI_FORMAT_RIGHT_J	0x0 +#define ADAU1373_DAI_FORMAT_LEFT_J	0x1 +#define ADAU1373_DAI_FORMAT_I2S		0x2 +#define ADAU1373_DAI_FORMAT_DSP		0x3 + +#define ADAU1373_BCLKDIV_SOURCE		BIT(5) +#define ADAU1373_BCLKDIV_32		0x03 +#define ADAU1373_BCLKDIV_64		0x02 +#define ADAU1373_BCLKDIV_128		0x01 +#define ADAU1373_BCLKDIV_256		0x00 + +#define ADAU1373_ADC_CTRL_PEAK_DETECT	BIT(0) +#define ADAU1373_ADC_CTRL_RESET		BIT(1) +#define ADAU1373_ADC_CTRL_RESET_FORCE	BIT(2) + +#define ADAU1373_OUTPUT_CTRL_LDIFF	BIT(3) +#define ADAU1373_OUTPUT_CTRL_LNFBEN	BIT(2) + +#define ADAU1373_PWDN_CTRL3_PWR_EN BIT(0) + +#define ADAU1373_EP_CTRL_MICBIAS1_OFFSET 4 +#define ADAU1373_EP_CTRL_MICBIAS2_OFFSET 2 + +static const uint8_t adau1373_default_regs[] = { +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x00 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x10 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x20 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, /* 0x30 */ +	0x00, 0x00, 0x00, 0x80, 0x00, 0x01, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x00, /* 0x40 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, /* 0x50 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x78, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, /* 0x80 */ +	0x00, 0xc0, 0x88, 0x7a, 0xdf, 0x20, 0x00, 0x00, +	0x78, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, /* 0x90 */ +	0x00, 0xc0, 0x88, 0x7a, 0xdf, 0x20, 0x00, 0x00, +	0x78, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, /* 0xa0 */ +	0x00, 0xc0, 0x88, 0x7a, 0xdf, 0x20, 0x00, 0x00, +	0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0xb0 */ +	0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xc0 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xd0 */ +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, /* 0xe0 */ +	0x00, 0x1f, 0x0f, 0x00, 0x00, +}; + +static const unsigned int adau1373_out_tlv[] = { +	TLV_DB_RANGE_HEAD(4), +	0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1), +	8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0), +	16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0), +	24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0), +}; + +static const DECLARE_TLV_DB_MINMAX(adau1373_digital_tlv, -9563, 0); +static const DECLARE_TLV_DB_SCALE(adau1373_in_pga_tlv, -1300, 100, 1); +static const DECLARE_TLV_DB_SCALE(adau1373_ep_tlv, -600, 600, 1); + +static const DECLARE_TLV_DB_SCALE(adau1373_input_boost_tlv, 0, 2000, 0); +static const DECLARE_TLV_DB_SCALE(adau1373_gain_boost_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(adau1373_speaker_boost_tlv, 1200, 600, 0); + +static const char *adau1373_fdsp_sel_text[] = { +	"None", +	"Channel 1", +	"Channel 2", +	"Channel 3", +	"Channel 4", +	"Channel 5", +}; + +static const SOC_ENUM_SINGLE_DECL(adau1373_drc1_channel_enum, +	ADAU1373_FDSP_SEL1, 4, adau1373_fdsp_sel_text); +static const SOC_ENUM_SINGLE_DECL(adau1373_drc2_channel_enum, +	ADAU1373_FDSP_SEL1, 0, adau1373_fdsp_sel_text); +static const SOC_ENUM_SINGLE_DECL(adau1373_drc3_channel_enum, +	ADAU1373_FDSP_SEL2, 0, adau1373_fdsp_sel_text); +static const SOC_ENUM_SINGLE_DECL(adau1373_hpf_channel_enum, +	ADAU1373_FDSP_SEL3, 0, adau1373_fdsp_sel_text); +static const SOC_ENUM_SINGLE_DECL(adau1373_bass_channel_enum, +	ADAU1373_FDSP_SEL4, 4, adau1373_fdsp_sel_text); + +static const char *adau1373_hpf_cutoff_text[] = { +	"3.7Hz", "50Hz", "100Hz", "150Hz", "200Hz", "250Hz", "300Hz", "350Hz", +	"400Hz", "450Hz", "500Hz", "550Hz", "600Hz", "650Hz", "700Hz", "750Hz", +	"800Hz", +}; + +static const SOC_ENUM_SINGLE_DECL(adau1373_hpf_cutoff_enum, +	ADAU1373_HPF_CTRL, 3, adau1373_hpf_cutoff_text); + +static const char *adau1373_bass_lpf_cutoff_text[] = { +	"801Hz", "1001Hz", +}; + +static const char *adau1373_bass_clip_level_text[] = { +	"0.125", "0.250", "0.370", "0.500", "0.625", "0.750", "0.875", +}; + +static const unsigned int adau1373_bass_clip_level_values[] = { +	1, 2, 3, 4, 5, 6, 7, +}; + +static const char *adau1373_bass_hpf_cutoff_text[] = { +	"158Hz", "232Hz", "347Hz", "520Hz", +}; + +static const unsigned int adau1373_bass_tlv[] = { +	TLV_DB_RANGE_HEAD(4), +	0, 2, TLV_DB_SCALE_ITEM(-600, 600, 1), +	3, 4, TLV_DB_SCALE_ITEM(950, 250, 0), +	5, 7, TLV_DB_SCALE_ITEM(1400, 150, 0), +}; + +static const SOC_ENUM_SINGLE_DECL(adau1373_bass_lpf_cutoff_enum, +	ADAU1373_BASS1, 5, adau1373_bass_lpf_cutoff_text); + +static const SOC_VALUE_ENUM_SINGLE_DECL(adau1373_bass_clip_level_enum, +	ADAU1373_BASS1, 2, 7, adau1373_bass_clip_level_text, +	adau1373_bass_clip_level_values); + +static const SOC_ENUM_SINGLE_DECL(adau1373_bass_hpf_cutoff_enum, +	ADAU1373_BASS1, 0, adau1373_bass_hpf_cutoff_text); + +static const char *adau1373_3d_level_text[] = { +	"0%", "6.67%", "13.33%", "20%", "26.67%", "33.33%", +	"40%", "46.67%", "53.33%", "60%", "66.67%", "73.33%", +	"80%", "86.67", "99.33%", "100%" +}; + +static const char *adau1373_3d_cutoff_text[] = { +	"No 3D", "0.03125 fs", "0.04583 fs", "0.075 fs", "0.11458 fs", +	"0.16875 fs", "0.27083 fs" +}; + +static const SOC_ENUM_SINGLE_DECL(adau1373_3d_level_enum, +	ADAU1373_3D_CTRL1, 4, adau1373_3d_level_text); +static const SOC_ENUM_SINGLE_DECL(adau1373_3d_cutoff_enum, +	ADAU1373_3D_CTRL1, 0, adau1373_3d_cutoff_text); + +static const unsigned int adau1373_3d_tlv[] = { +	TLV_DB_RANGE_HEAD(2), +	0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), +	1, 7, TLV_DB_LINEAR_ITEM(-1800, -120), +}; + +static const char *adau1373_lr_mux_text[] = { +	"Mute", +	"Right Channel (L+R)", +	"Left Channel (L+R)", +	"Stereo", +}; + +static const SOC_ENUM_SINGLE_DECL(adau1373_lineout1_lr_mux_enum, +	ADAU1373_OUTPUT_CTRL, 4, adau1373_lr_mux_text); +static const SOC_ENUM_SINGLE_DECL(adau1373_lineout2_lr_mux_enum, +	ADAU1373_OUTPUT_CTRL, 6, adau1373_lr_mux_text); +static const SOC_ENUM_SINGLE_DECL(adau1373_speaker_lr_mux_enum, +	ADAU1373_LS_CTRL, 4, adau1373_lr_mux_text); + +static const struct snd_kcontrol_new adau1373_controls[] = { +	SOC_DOUBLE_R_TLV("AIF1 Capture Volume", ADAU1373_DAI_RECL_VOL(0), +		ADAU1373_DAI_RECR_VOL(0), 0, 0xff, 1, adau1373_digital_tlv), +	SOC_DOUBLE_R_TLV("AIF2 Capture Volume", ADAU1373_DAI_RECL_VOL(1), +		ADAU1373_DAI_RECR_VOL(1), 0, 0xff, 1, adau1373_digital_tlv), +	SOC_DOUBLE_R_TLV("AIF3 Capture Volume", ADAU1373_DAI_RECL_VOL(2), +		ADAU1373_DAI_RECR_VOL(2), 0, 0xff, 1, adau1373_digital_tlv), + +	SOC_DOUBLE_R_TLV("ADC Capture Volume", ADAU1373_ADC_RECL_VOL, +		ADAU1373_ADC_RECR_VOL, 0, 0xff, 1, adau1373_digital_tlv), +	SOC_DOUBLE_R_TLV("DMIC Capture Volume", ADAU1373_DMIC_RECL_VOL, +		ADAU1373_DMIC_RECR_VOL, 0, 0xff, 1, adau1373_digital_tlv), + +	SOC_DOUBLE_R_TLV("AIF1 Playback Volume", ADAU1373_DAI_PBL_VOL(0), +		ADAU1373_DAI_PBR_VOL(0), 0, 0xff, 1, adau1373_digital_tlv), +	SOC_DOUBLE_R_TLV("AIF2 Playback Volume", ADAU1373_DAI_PBL_VOL(1), +		ADAU1373_DAI_PBR_VOL(1), 0, 0xff, 1, adau1373_digital_tlv), +	SOC_DOUBLE_R_TLV("AIF3 Playback Volume", ADAU1373_DAI_PBL_VOL(2), +		ADAU1373_DAI_PBR_VOL(2), 0, 0xff, 1, adau1373_digital_tlv), + +	SOC_DOUBLE_R_TLV("DAC1 Playback Volume", ADAU1373_DAC1_PBL_VOL, +		ADAU1373_DAC1_PBR_VOL, 0, 0xff, 1, adau1373_digital_tlv), +	SOC_DOUBLE_R_TLV("DAC2 Playback Volume", ADAU1373_DAC2_PBL_VOL, +		ADAU1373_DAC2_PBR_VOL, 0, 0xff, 1, adau1373_digital_tlv), + +	SOC_DOUBLE_R_TLV("Lineout1 Playback Volume", ADAU1373_LLINE_OUT(0), +		ADAU1373_RLINE_OUT(0), 0, 0x1f, 0, adau1373_out_tlv), +	SOC_DOUBLE_R_TLV("Speaker Playback Volume", ADAU1373_LSPK_OUT, +		ADAU1373_RSPK_OUT, 0, 0x1f, 0, adau1373_out_tlv), +	SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1373_LHP_OUT, +		ADAU1373_RHP_OUT, 0, 0x1f, 0, adau1373_out_tlv), + +	SOC_DOUBLE_R_TLV("Input 1 Capture Volume", ADAU1373_AINL_CTRL(0), +		ADAU1373_AINR_CTRL(0), 0, 0x1f, 0, adau1373_in_pga_tlv), +	SOC_DOUBLE_R_TLV("Input 2 Capture Volume", ADAU1373_AINL_CTRL(1), +		ADAU1373_AINR_CTRL(1), 0, 0x1f, 0, adau1373_in_pga_tlv), +	SOC_DOUBLE_R_TLV("Input 3 Capture Volume", ADAU1373_AINL_CTRL(2), +		ADAU1373_AINR_CTRL(2), 0, 0x1f, 0, adau1373_in_pga_tlv), +	SOC_DOUBLE_R_TLV("Input 4 Capture Volume", ADAU1373_AINL_CTRL(3), +		ADAU1373_AINR_CTRL(3), 0, 0x1f, 0, adau1373_in_pga_tlv), + +	SOC_SINGLE_TLV("Earpiece Playback Volume", ADAU1373_EP_CTRL, 0, 3, 0, +		adau1373_ep_tlv), + +	SOC_DOUBLE_TLV("AIF3 Boost Playback Volume", ADAU1373_VOL_GAIN1, 4, 5, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("AIF2 Boost Playback Volume", ADAU1373_VOL_GAIN1, 2, 3, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("AIF1 Boost Playback Volume", ADAU1373_VOL_GAIN1, 0, 1, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("AIF3 Boost Capture Volume", ADAU1373_VOL_GAIN2, 4, 5, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("AIF2 Boost Capture Volume", ADAU1373_VOL_GAIN2, 2, 3, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("AIF1 Boost Capture Volume", ADAU1373_VOL_GAIN2, 0, 1, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("DMIC Boost Capture Volume", ADAU1373_VOL_GAIN3, 6, 7, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("ADC Boost Capture Volume", ADAU1373_VOL_GAIN3, 4, 5, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("DAC2 Boost Playback Volume", ADAU1373_VOL_GAIN3, 2, 3, +		1, 0, adau1373_gain_boost_tlv), +	SOC_DOUBLE_TLV("DAC1 Boost Playback Volume", ADAU1373_VOL_GAIN3, 0, 1, +		1, 0, adau1373_gain_boost_tlv), + +	SOC_DOUBLE_TLV("Input 1 Boost Capture Volume", ADAU1373_ADC_GAIN, 0, 4, +		1, 0, adau1373_input_boost_tlv), +	SOC_DOUBLE_TLV("Input 2 Boost Capture Volume", ADAU1373_ADC_GAIN, 1, 5, +		1, 0, adau1373_input_boost_tlv), +	SOC_DOUBLE_TLV("Input 3 Boost Capture Volume", ADAU1373_ADC_GAIN, 2, 6, +		1, 0, adau1373_input_boost_tlv), +	SOC_DOUBLE_TLV("Input 4 Boost Capture Volume", ADAU1373_ADC_GAIN, 3, 7, +		1, 0, adau1373_input_boost_tlv), + +	SOC_DOUBLE_TLV("Speaker Boost Playback Volume", ADAU1373_LS_CTRL, 2, 3, +		1, 0, adau1373_speaker_boost_tlv), + +	SOC_ENUM("Lineout1 LR Mux", adau1373_lineout1_lr_mux_enum), +	SOC_ENUM("Speaker LR Mux", adau1373_speaker_lr_mux_enum), + +	SOC_ENUM("HPF Cutoff", adau1373_hpf_cutoff_enum), +	SOC_DOUBLE("HPF Switch", ADAU1373_HPF_CTRL, 1, 0, 1, 0), +	SOC_ENUM("HPF Channel", adau1373_hpf_channel_enum), + +	SOC_ENUM("Bass HPF Cutoff", adau1373_bass_hpf_cutoff_enum), +	SOC_VALUE_ENUM("Bass Clip Level Threshold", +	    adau1373_bass_clip_level_enum), +	SOC_ENUM("Bass LPF Cutoff", adau1373_bass_lpf_cutoff_enum), +	SOC_DOUBLE("Bass Playback Switch", ADAU1373_BASS2, 0, 1, 1, 0), +	SOC_SINGLE_TLV("Bass Playback Volume", ADAU1373_BASS2, 2, 7, 0, +	    adau1373_bass_tlv), +	SOC_ENUM("Bass Channel", adau1373_bass_channel_enum), + +	SOC_ENUM("3D Freq", adau1373_3d_cutoff_enum), +	SOC_ENUM("3D Level", adau1373_3d_level_enum), +	SOC_SINGLE("3D Playback Switch", ADAU1373_3D_CTRL2, 0, 1, 0), +	SOC_SINGLE_TLV("3D Playback Volume", ADAU1373_3D_CTRL2, 2, 7, 0, +		adau1373_3d_tlv), +	SOC_ENUM("3D Channel", adau1373_bass_channel_enum), + +	SOC_SINGLE("Zero Cross Switch", ADAU1373_PWDN_CTRL3, 7, 1, 0), +}; + +static const struct snd_kcontrol_new adau1373_lineout2_controls[] = { +	SOC_DOUBLE_R_TLV("Lineout2 Playback Volume", ADAU1373_LLINE_OUT(1), +		ADAU1373_RLINE_OUT(1), 0, 0x1f, 0, adau1373_out_tlv), +	SOC_ENUM("Lineout2 LR Mux", adau1373_lineout2_lr_mux_enum), +}; + +static const struct snd_kcontrol_new adau1373_drc_controls[] = { +	SOC_ENUM("DRC1 Channel", adau1373_drc1_channel_enum), +	SOC_ENUM("DRC2 Channel", adau1373_drc2_channel_enum), +	SOC_ENUM("DRC3 Channel", adau1373_drc3_channel_enum), +}; + +static int adau1373_pll_event(struct snd_soc_dapm_widget *w, +	struct snd_kcontrol *kcontrol, int event) +{ +	struct snd_soc_codec *codec = w->codec; +	unsigned int pll_id = w->name[3] - '1'; +	unsigned int val; + +	if (SND_SOC_DAPM_EVENT_ON(event)) +		val = ADAU1373_PLL_CTRL6_PLL_EN; +	else +		val = 0; + +	snd_soc_update_bits(codec, ADAU1373_PLL_CTRL6(pll_id), +		ADAU1373_PLL_CTRL6_PLL_EN, val); + +	if (SND_SOC_DAPM_EVENT_ON(event)) +		mdelay(5); + +	return 0; +} + +static const char *adau1373_decimator_text[] = { +	"ADC", +	"DMIC1", +}; + +static const struct soc_enum adau1373_decimator_enum = +	SOC_ENUM_SINGLE(0, 0, 2, adau1373_decimator_text); + +static const struct snd_kcontrol_new adau1373_decimator_mux = +	SOC_DAPM_ENUM_VIRT("Decimator Mux", adau1373_decimator_enum); + +static const struct snd_kcontrol_new adau1373_left_adc_mixer_controls[] = { +	SOC_DAPM_SINGLE("DAC1 Switch", ADAU1373_LADC_MIXER, 4, 1, 0), +	SOC_DAPM_SINGLE("Input 4 Switch", ADAU1373_LADC_MIXER, 3, 1, 0), +	SOC_DAPM_SINGLE("Input 3 Switch", ADAU1373_LADC_MIXER, 2, 1, 0), +	SOC_DAPM_SINGLE("Input 2 Switch", ADAU1373_LADC_MIXER, 1, 1, 0), +	SOC_DAPM_SINGLE("Input 1 Switch", ADAU1373_LADC_MIXER, 0, 1, 0), +}; + +static const struct snd_kcontrol_new adau1373_right_adc_mixer_controls[] = { +	SOC_DAPM_SINGLE("DAC1 Switch", ADAU1373_RADC_MIXER, 4, 1, 0), +	SOC_DAPM_SINGLE("Input 4 Switch", ADAU1373_RADC_MIXER, 3, 1, 0), +	SOC_DAPM_SINGLE("Input 3 Switch", ADAU1373_RADC_MIXER, 2, 1, 0), +	SOC_DAPM_SINGLE("Input 2 Switch", ADAU1373_RADC_MIXER, 1, 1, 0), +	SOC_DAPM_SINGLE("Input 1 Switch", ADAU1373_RADC_MIXER, 0, 1, 0), +}; + +#define DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(_name, _reg) \ +const struct snd_kcontrol_new _name[] = { \ +	SOC_DAPM_SINGLE("Left DAC2 Switch", _reg, 7, 1, 0), \ +	SOC_DAPM_SINGLE("Right DAC2 Switch", _reg, 6, 1, 0), \ +	SOC_DAPM_SINGLE("Left DAC1 Switch", _reg, 5, 1, 0), \ +	SOC_DAPM_SINGLE("Right DAC1 Switch", _reg, 4, 1, 0), \ +	SOC_DAPM_SINGLE("Input 4 Bypass Switch", _reg, 3, 1, 0), \ +	SOC_DAPM_SINGLE("Input 3 Bypass Switch", _reg, 2, 1, 0), \ +	SOC_DAPM_SINGLE("Input 2 Bypass Switch", _reg, 1, 1, 0), \ +	SOC_DAPM_SINGLE("Input 1 Bypass Switch", _reg, 0, 1, 0), \ +} + +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_line1_mixer_controls, +	ADAU1373_LLINE1_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_line1_mixer_controls, +	ADAU1373_RLINE1_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_line2_mixer_controls, +	ADAU1373_LLINE2_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_line2_mixer_controls, +	ADAU1373_RLINE2_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_left_spk_mixer_controls, +	ADAU1373_LSPK_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_right_spk_mixer_controls, +	ADAU1373_RSPK_MIX); +static DECLARE_ADAU1373_OUTPUT_MIXER_CTRLS(adau1373_ep_mixer_controls, +	ADAU1373_EP_MIX); + +static const struct snd_kcontrol_new adau1373_left_hp_mixer_controls[] = { +	SOC_DAPM_SINGLE("Left DAC1 Switch", ADAU1373_LHP_MIX, 5, 1, 0), +	SOC_DAPM_SINGLE("Left DAC2 Switch", ADAU1373_LHP_MIX, 4, 1, 0), +	SOC_DAPM_SINGLE("Input 4 Bypass Switch", ADAU1373_LHP_MIX, 3, 1, 0), +	SOC_DAPM_SINGLE("Input 3 Bypass Switch", ADAU1373_LHP_MIX, 2, 1, 0), +	SOC_DAPM_SINGLE("Input 2 Bypass Switch", ADAU1373_LHP_MIX, 1, 1, 0), +	SOC_DAPM_SINGLE("Input 1 Bypass Switch", ADAU1373_LHP_MIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new adau1373_right_hp_mixer_controls[] = { +	SOC_DAPM_SINGLE("Right DAC1 Switch", ADAU1373_RHP_MIX, 5, 1, 0), +	SOC_DAPM_SINGLE("Right DAC2 Switch", ADAU1373_RHP_MIX, 4, 1, 0), +	SOC_DAPM_SINGLE("Input 4 Bypass Switch", ADAU1373_RHP_MIX, 3, 1, 0), +	SOC_DAPM_SINGLE("Input 3 Bypass Switch", ADAU1373_RHP_MIX, 2, 1, 0), +	SOC_DAPM_SINGLE("Input 2 Bypass Switch", ADAU1373_RHP_MIX, 1, 1, 0), +	SOC_DAPM_SINGLE("Input 1 Bypass Switch", ADAU1373_RHP_MIX, 0, 1, 0), +}; + +#define DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(_name, _reg) \ +const struct snd_kcontrol_new _name[] = { \ +	SOC_DAPM_SINGLE("DMIC2 Swapped Switch", _reg, 6, 1, 0), \ +	SOC_DAPM_SINGLE("DMIC2 Switch", _reg, 5, 1, 0), \ +	SOC_DAPM_SINGLE("ADC/DMIC1 Swapped Switch", _reg, 4, 1, 0), \ +	SOC_DAPM_SINGLE("ADC/DMIC1 Switch", _reg, 3, 1, 0), \ +	SOC_DAPM_SINGLE("AIF3 Switch", _reg, 2, 1, 0), \ +	SOC_DAPM_SINGLE("AIF2 Switch", _reg, 1, 1, 0), \ +	SOC_DAPM_SINGLE("AIF1 Switch", _reg, 0, 1, 0), \ +} + +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel1_mixer_controls, +	ADAU1373_DIN_MIX_CTRL(0)); +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel2_mixer_controls, +	ADAU1373_DIN_MIX_CTRL(1)); +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel3_mixer_controls, +	ADAU1373_DIN_MIX_CTRL(2)); +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel4_mixer_controls, +	ADAU1373_DIN_MIX_CTRL(3)); +static DECLARE_ADAU1373_DSP_CHANNEL_MIXER_CTRLS(adau1373_dsp_channel5_mixer_controls, +	ADAU1373_DIN_MIX_CTRL(4)); + +#define DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(_name, _reg) \ +const struct snd_kcontrol_new _name[] = { \ +	SOC_DAPM_SINGLE("DSP Channel5 Switch", _reg, 4, 1, 0), \ +	SOC_DAPM_SINGLE("DSP Channel4 Switch", _reg, 3, 1, 0), \ +	SOC_DAPM_SINGLE("DSP Channel3 Switch", _reg, 2, 1, 0), \ +	SOC_DAPM_SINGLE("DSP Channel2 Switch", _reg, 1, 1, 0), \ +	SOC_DAPM_SINGLE("DSP Channel1 Switch", _reg, 0, 1, 0), \ +} + +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif1_mixer_controls, +	ADAU1373_DOUT_MIX_CTRL(0)); +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif2_mixer_controls, +	ADAU1373_DOUT_MIX_CTRL(1)); +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_aif3_mixer_controls, +	ADAU1373_DOUT_MIX_CTRL(2)); +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_dac1_mixer_controls, +	ADAU1373_DOUT_MIX_CTRL(3)); +static DECLARE_ADAU1373_DSP_OUTPUT_MIXER_CTRLS(adau1373_dac2_mixer_controls, +	ADAU1373_DOUT_MIX_CTRL(4)); + +static const struct snd_soc_dapm_widget adau1373_dapm_widgets[] = { +	/* Datasheet claims Left ADC is bit 6 and Right ADC is bit 7, but that +	 * doesn't seem to be the case. */ +	SND_SOC_DAPM_ADC("Left ADC", NULL, ADAU1373_PWDN_CTRL1, 7, 0), +	SND_SOC_DAPM_ADC("Right ADC", NULL, ADAU1373_PWDN_CTRL1, 6, 0), + +	SND_SOC_DAPM_ADC("DMIC1", NULL, ADAU1373_DIGMICCTRL, 0, 0), +	SND_SOC_DAPM_ADC("DMIC2", NULL, ADAU1373_DIGMICCTRL, 2, 0), + +	SND_SOC_DAPM_VIRT_MUX("Decimator Mux", SND_SOC_NOPM, 0, 0, +		&adau1373_decimator_mux), + +	SND_SOC_DAPM_SUPPLY("MICBIAS2", ADAU1373_PWDN_CTRL1, 5, 0, NULL, 0), +	SND_SOC_DAPM_SUPPLY("MICBIAS1", ADAU1373_PWDN_CTRL1, 4, 0, NULL, 0), + +	SND_SOC_DAPM_PGA("IN4PGA", ADAU1373_PWDN_CTRL1, 3, 0, NULL, 0), +	SND_SOC_DAPM_PGA("IN3PGA", ADAU1373_PWDN_CTRL1, 2, 0, NULL, 0), +	SND_SOC_DAPM_PGA("IN2PGA", ADAU1373_PWDN_CTRL1, 1, 0, NULL, 0), +	SND_SOC_DAPM_PGA("IN1PGA", ADAU1373_PWDN_CTRL1, 0, 0, NULL, 0), + +	SND_SOC_DAPM_DAC("Left DAC2", NULL, ADAU1373_PWDN_CTRL2, 7, 0), +	SND_SOC_DAPM_DAC("Right DAC2", NULL, ADAU1373_PWDN_CTRL2, 6, 0), +	SND_SOC_DAPM_DAC("Left DAC1", NULL, ADAU1373_PWDN_CTRL2, 5, 0), +	SND_SOC_DAPM_DAC("Right DAC1", NULL, ADAU1373_PWDN_CTRL2, 4, 0), + +	SOC_MIXER_ARRAY("Left ADC Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_left_adc_mixer_controls), +	SOC_MIXER_ARRAY("Right ADC Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_right_adc_mixer_controls), + +	SOC_MIXER_ARRAY("Left Lineout2 Mixer", ADAU1373_PWDN_CTRL2, 3, 0, +		adau1373_left_line2_mixer_controls), +	SOC_MIXER_ARRAY("Right Lineout2 Mixer", ADAU1373_PWDN_CTRL2, 2, 0, +		adau1373_right_line2_mixer_controls), +	SOC_MIXER_ARRAY("Left Lineout1 Mixer", ADAU1373_PWDN_CTRL2, 1, 0, +		adau1373_left_line1_mixer_controls), +	SOC_MIXER_ARRAY("Right Lineout1 Mixer", ADAU1373_PWDN_CTRL2, 0, 0, +		adau1373_right_line1_mixer_controls), + +	SOC_MIXER_ARRAY("Earpiece Mixer", ADAU1373_PWDN_CTRL3, 4, 0, +		adau1373_ep_mixer_controls), +	SOC_MIXER_ARRAY("Left Speaker Mixer", ADAU1373_PWDN_CTRL3, 3, 0, +		adau1373_left_spk_mixer_controls), +	SOC_MIXER_ARRAY("Right Speaker Mixer", ADAU1373_PWDN_CTRL3, 2, 0, +		adau1373_right_spk_mixer_controls), +	SOC_MIXER_ARRAY("Left Headphone Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_left_hp_mixer_controls), +	SOC_MIXER_ARRAY("Right Headphone Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_right_hp_mixer_controls), +	SND_SOC_DAPM_SUPPLY("Headphone Enable", ADAU1373_PWDN_CTRL3, 1, 0, +		NULL, 0), + +	SND_SOC_DAPM_SUPPLY("AIF1 CLK", ADAU1373_SRC_DAI_CTRL(0), 0, 0, +	    NULL, 0), +	SND_SOC_DAPM_SUPPLY("AIF2 CLK", ADAU1373_SRC_DAI_CTRL(1), 0, 0, +	    NULL, 0), +	SND_SOC_DAPM_SUPPLY("AIF3 CLK", ADAU1373_SRC_DAI_CTRL(2), 0, 0, +	    NULL, 0), +	SND_SOC_DAPM_SUPPLY("AIF1 IN SRC", ADAU1373_SRC_DAI_CTRL(0), 2, 0, +	    NULL, 0), +	SND_SOC_DAPM_SUPPLY("AIF1 OUT SRC", ADAU1373_SRC_DAI_CTRL(0), 1, 0, +	    NULL, 0), +	SND_SOC_DAPM_SUPPLY("AIF2 IN SRC", ADAU1373_SRC_DAI_CTRL(1), 2, 0, +	    NULL, 0), +	SND_SOC_DAPM_SUPPLY("AIF2 OUT SRC", ADAU1373_SRC_DAI_CTRL(1), 1, 0, +	    NULL, 0), +	SND_SOC_DAPM_SUPPLY("AIF3 IN SRC", ADAU1373_SRC_DAI_CTRL(2), 2, 0, +	    NULL, 0), +	SND_SOC_DAPM_SUPPLY("AIF3 OUT SRC", ADAU1373_SRC_DAI_CTRL(2), 1, 0, +	    NULL, 0), + +	SND_SOC_DAPM_AIF_IN("AIF1 IN", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF1 OUT", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_IN("AIF2 IN", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF2 OUT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_IN("AIF3 IN", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0), +	SND_SOC_DAPM_AIF_OUT("AIF3 OUT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0), + +	SOC_MIXER_ARRAY("DSP Channel1 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_dsp_channel1_mixer_controls), +	SOC_MIXER_ARRAY("DSP Channel2 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_dsp_channel2_mixer_controls), +	SOC_MIXER_ARRAY("DSP Channel3 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_dsp_channel3_mixer_controls), +	SOC_MIXER_ARRAY("DSP Channel4 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_dsp_channel4_mixer_controls), +	SOC_MIXER_ARRAY("DSP Channel5 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_dsp_channel5_mixer_controls), + +	SOC_MIXER_ARRAY("AIF1 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_aif1_mixer_controls), +	SOC_MIXER_ARRAY("AIF2 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_aif2_mixer_controls), +	SOC_MIXER_ARRAY("AIF3 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_aif3_mixer_controls), +	SOC_MIXER_ARRAY("DAC1 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_dac1_mixer_controls), +	SOC_MIXER_ARRAY("DAC2 Mixer", SND_SOC_NOPM, 0, 0, +		adau1373_dac2_mixer_controls), + +	SND_SOC_DAPM_SUPPLY("DSP", ADAU1373_DIGEN, 4, 0, NULL, 0), +	SND_SOC_DAPM_SUPPLY("Recording Engine B", ADAU1373_DIGEN, 3, 0, NULL, 0), +	SND_SOC_DAPM_SUPPLY("Recording Engine A", ADAU1373_DIGEN, 2, 0, NULL, 0), +	SND_SOC_DAPM_SUPPLY("Playback Engine B", ADAU1373_DIGEN, 1, 0, NULL, 0), +	SND_SOC_DAPM_SUPPLY("Playback Engine A", ADAU1373_DIGEN, 0, 0, NULL, 0), + +	SND_SOC_DAPM_SUPPLY("PLL1", SND_SOC_NOPM, 0, 0, adau1373_pll_event, +		SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +	SND_SOC_DAPM_SUPPLY("PLL2", SND_SOC_NOPM, 0, 0, adau1373_pll_event, +		SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +	SND_SOC_DAPM_SUPPLY("SYSCLK1", ADAU1373_CLK_SRC_DIV(0), 7, 0, NULL, 0), +	SND_SOC_DAPM_SUPPLY("SYSCLK2", ADAU1373_CLK_SRC_DIV(1), 7, 0, NULL, 0), + +	SND_SOC_DAPM_INPUT("AIN1L"), +	SND_SOC_DAPM_INPUT("AIN1R"), +	SND_SOC_DAPM_INPUT("AIN2L"), +	SND_SOC_DAPM_INPUT("AIN2R"), +	SND_SOC_DAPM_INPUT("AIN3L"), +	SND_SOC_DAPM_INPUT("AIN3R"), +	SND_SOC_DAPM_INPUT("AIN4L"), +	SND_SOC_DAPM_INPUT("AIN4R"), + +	SND_SOC_DAPM_INPUT("DMIC1DAT"), +	SND_SOC_DAPM_INPUT("DMIC2DAT"), + +	SND_SOC_DAPM_OUTPUT("LOUT1L"), +	SND_SOC_DAPM_OUTPUT("LOUT1R"), +	SND_SOC_DAPM_OUTPUT("LOUT2L"), +	SND_SOC_DAPM_OUTPUT("LOUT2R"), +	SND_SOC_DAPM_OUTPUT("HPL"), +	SND_SOC_DAPM_OUTPUT("HPR"), +	SND_SOC_DAPM_OUTPUT("SPKL"), +	SND_SOC_DAPM_OUTPUT("SPKR"), +	SND_SOC_DAPM_OUTPUT("EP"), +}; + +static int adau1373_check_aif_clk(struct snd_soc_dapm_widget *source, +	struct snd_soc_dapm_widget *sink) +{ +	struct snd_soc_codec *codec = source->codec; +	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); +	unsigned int dai; +	const char *clk; + +	dai = sink->name[3] - '1'; + +	if (!adau1373->dais[dai].master) +		return 0; + +	if (adau1373->dais[dai].clk_src == ADAU1373_CLK_SRC_PLL1) +		clk = "SYSCLK1"; +	else +		clk = "SYSCLK2"; + +	return strcmp(source->name, clk) == 0; +} + +static int adau1373_check_src(struct snd_soc_dapm_widget *source, +	struct snd_soc_dapm_widget *sink) +{ +	struct snd_soc_codec *codec = source->codec; +	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); +	unsigned int dai; + +	dai = sink->name[3] - '1'; + +	return adau1373->dais[dai].enable_src; +} + +#define DSP_CHANNEL_MIXER_ROUTES(_sink) \ +	{ _sink, "DMIC2 Swapped Switch", "DMIC2" }, \ +	{ _sink, "DMIC2 Switch", "DMIC2" }, \ +	{ _sink, "ADC/DMIC1 Swapped Switch", "Decimator Mux" }, \ +	{ _sink, "ADC/DMIC1 Switch", "Decimator Mux" }, \ +	{ _sink, "AIF1 Switch", "AIF1 IN" }, \ +	{ _sink, "AIF2 Switch", "AIF2 IN" }, \ +	{ _sink, "AIF3 Switch", "AIF3 IN" } + +#define DSP_OUTPUT_MIXER_ROUTES(_sink) \ +	{ _sink, "DSP Channel1 Switch", "DSP Channel1 Mixer" }, \ +	{ _sink, "DSP Channel2 Switch", "DSP Channel2 Mixer" }, \ +	{ _sink, "DSP Channel3 Switch", "DSP Channel3 Mixer" }, \ +	{ _sink, "DSP Channel4 Switch", "DSP Channel4 Mixer" }, \ +	{ _sink, "DSP Channel5 Switch", "DSP Channel5 Mixer" } + +#define LEFT_OUTPUT_MIXER_ROUTES(_sink) \ +	{ _sink, "Right DAC2 Switch", "Right DAC2" }, \ +	{ _sink, "Left DAC2 Switch", "Left DAC2" }, \ +	{ _sink, "Right DAC1 Switch", "Right DAC1" }, \ +	{ _sink, "Left DAC1 Switch", "Left DAC1" }, \ +	{ _sink, "Input 1 Bypass Switch", "IN1PGA" }, \ +	{ _sink, "Input 2 Bypass Switch", "IN2PGA" }, \ +	{ _sink, "Input 3 Bypass Switch", "IN3PGA" }, \ +	{ _sink, "Input 4 Bypass Switch", "IN4PGA" } + +#define RIGHT_OUTPUT_MIXER_ROUTES(_sink) \ +	{ _sink, "Right DAC2 Switch", "Right DAC2" }, \ +	{ _sink, "Left DAC2 Switch", "Left DAC2" }, \ +	{ _sink, "Right DAC1 Switch", "Right DAC1" }, \ +	{ _sink, "Left DAC1 Switch", "Left DAC1" }, \ +	{ _sink, "Input 1 Bypass Switch", "IN1PGA" }, \ +	{ _sink, "Input 2 Bypass Switch", "IN2PGA" }, \ +	{ _sink, "Input 3 Bypass Switch", "IN3PGA" }, \ +	{ _sink, "Input 4 Bypass Switch", "IN4PGA" } + +static const struct snd_soc_dapm_route adau1373_dapm_routes[] = { +	{ "Left ADC Mixer", "DAC1 Switch", "Left DAC1" }, +	{ "Left ADC Mixer", "Input 1 Switch", "IN1PGA" }, +	{ "Left ADC Mixer", "Input 2 Switch", "IN2PGA" }, +	{ "Left ADC Mixer", "Input 3 Switch", "IN3PGA" }, +	{ "Left ADC Mixer", "Input 4 Switch", "IN4PGA" }, + +	{ "Right ADC Mixer", "DAC1 Switch", "Right DAC1" }, +	{ "Right ADC Mixer", "Input 1 Switch", "IN1PGA" }, +	{ "Right ADC Mixer", "Input 2 Switch", "IN2PGA" }, +	{ "Right ADC Mixer", "Input 3 Switch", "IN3PGA" }, +	{ "Right ADC Mixer", "Input 4 Switch", "IN4PGA" }, + +	{ "Left ADC", NULL, "Left ADC Mixer" }, +	{ "Right ADC", NULL, "Right ADC Mixer" }, + +	{ "Decimator Mux", "ADC", "Left ADC" }, +	{ "Decimator Mux", "ADC", "Right ADC" }, +	{ "Decimator Mux", "DMIC1", "DMIC1" }, + +	DSP_CHANNEL_MIXER_ROUTES("DSP Channel1 Mixer"), +	DSP_CHANNEL_MIXER_ROUTES("DSP Channel2 Mixer"), +	DSP_CHANNEL_MIXER_ROUTES("DSP Channel3 Mixer"), +	DSP_CHANNEL_MIXER_ROUTES("DSP Channel4 Mixer"), +	DSP_CHANNEL_MIXER_ROUTES("DSP Channel5 Mixer"), + +	DSP_OUTPUT_MIXER_ROUTES("AIF1 Mixer"), +	DSP_OUTPUT_MIXER_ROUTES("AIF2 Mixer"), +	DSP_OUTPUT_MIXER_ROUTES("AIF3 Mixer"), +	DSP_OUTPUT_MIXER_ROUTES("DAC1 Mixer"), +	DSP_OUTPUT_MIXER_ROUTES("DAC2 Mixer"), + +	{ "AIF1 OUT", NULL, "AIF1 Mixer" }, +	{ "AIF2 OUT", NULL, "AIF2 Mixer" }, +	{ "AIF3 OUT", NULL, "AIF3 Mixer" }, +	{ "Left DAC1", NULL, "DAC1 Mixer" }, +	{ "Right DAC1", NULL, "DAC1 Mixer" }, +	{ "Left DAC2", NULL, "DAC2 Mixer" }, +	{ "Right DAC2", NULL, "DAC2 Mixer" }, + +	LEFT_OUTPUT_MIXER_ROUTES("Left Lineout1 Mixer"), +	RIGHT_OUTPUT_MIXER_ROUTES("Right Lineout1 Mixer"), +	LEFT_OUTPUT_MIXER_ROUTES("Left Lineout2 Mixer"), +	RIGHT_OUTPUT_MIXER_ROUTES("Right Lineout2 Mixer"), +	LEFT_OUTPUT_MIXER_ROUTES("Left Speaker Mixer"), +	RIGHT_OUTPUT_MIXER_ROUTES("Right Speaker Mixer"), + +	{ "Left Headphone Mixer", "Left DAC2 Switch", "Left DAC2" }, +	{ "Left Headphone Mixer", "Left DAC1 Switch", "Left DAC1" }, +	{ "Left Headphone Mixer", "Input 1 Bypass Switch", "IN1PGA" }, +	{ "Left Headphone Mixer", "Input 2 Bypass Switch", "IN2PGA" }, +	{ "Left Headphone Mixer", "Input 3 Bypass Switch", "IN3PGA" }, +	{ "Left Headphone Mixer", "Input 4 Bypass Switch", "IN4PGA" }, +	{ "Right Headphone Mixer", "Right DAC2 Switch", "Right DAC2" }, +	{ "Right Headphone Mixer", "Right DAC1 Switch", "Right DAC1" }, +	{ "Right Headphone Mixer", "Input 1 Bypass Switch", "IN1PGA" }, +	{ "Right Headphone Mixer", "Input 2 Bypass Switch", "IN2PGA" }, +	{ "Right Headphone Mixer", "Input 3 Bypass Switch", "IN3PGA" }, +	{ "Right Headphone Mixer", "Input 4 Bypass Switch", "IN4PGA" }, + +	{ "Left Headphone Mixer", NULL, "Headphone Enable" }, +	{ "Right Headphone Mixer", NULL, "Headphone Enable" }, + +	{ "Earpiece Mixer", "Right DAC2 Switch", "Right DAC2" }, +	{ "Earpiece Mixer", "Left DAC2 Switch", "Left DAC2" }, +	{ "Earpiece Mixer", "Right DAC1 Switch", "Right DAC1" }, +	{ "Earpiece Mixer", "Left DAC1 Switch", "Left DAC1" }, +	{ "Earpiece Mixer", "Input 1 Bypass Switch", "IN1PGA" }, +	{ "Earpiece Mixer", "Input 2 Bypass Switch", "IN2PGA" }, +	{ "Earpiece Mixer", "Input 3 Bypass Switch", "IN3PGA" }, +	{ "Earpiece Mixer", "Input 4 Bypass Switch", "IN4PGA" }, + +	{ "LOUT1L", NULL, "Left Lineout1 Mixer" }, +	{ "LOUT1R", NULL, "Right Lineout1 Mixer" }, +	{ "LOUT2L", NULL, "Left Lineout2 Mixer" }, +	{ "LOUT2R", NULL, "Right Lineout2 Mixer" }, +	{ "SPKL", NULL, "Left Speaker Mixer" }, +	{ "SPKR", NULL, "Right Speaker Mixer" }, +	{ "HPL", NULL, "Left Headphone Mixer" }, +	{ "HPR", NULL, "Right Headphone Mixer" }, +	{ "EP", NULL, "Earpiece Mixer" }, + +	{ "IN1PGA", NULL, "AIN1L" }, +	{ "IN2PGA", NULL, "AIN2L" }, +	{ "IN3PGA", NULL, "AIN3L" }, +	{ "IN4PGA", NULL, "AIN4L" }, +	{ "IN1PGA", NULL, "AIN1R" }, +	{ "IN2PGA", NULL, "AIN2R" }, +	{ "IN3PGA", NULL, "AIN3R" }, +	{ "IN4PGA", NULL, "AIN4R" }, + +	{ "SYSCLK1", NULL, "PLL1" }, +	{ "SYSCLK2", NULL, "PLL2" }, + +	{ "Left DAC1", NULL, "SYSCLK1" }, +	{ "Right DAC1", NULL, "SYSCLK1" }, +	{ "Left DAC2", NULL, "SYSCLK1" }, +	{ "Right DAC2", NULL, "SYSCLK1" }, +	{ "Left ADC", NULL, "SYSCLK1" }, +	{ "Right ADC", NULL, "SYSCLK1" }, + +	{ "DSP", NULL, "SYSCLK1" }, + +	{ "AIF1 Mixer", NULL, "DSP" }, +	{ "AIF2 Mixer", NULL, "DSP" }, +	{ "AIF3 Mixer", NULL, "DSP" }, +	{ "DAC1 Mixer", NULL, "DSP" }, +	{ "DAC2 Mixer", NULL, "DSP" }, +	{ "DAC1 Mixer", NULL, "Playback Engine A" }, +	{ "DAC2 Mixer", NULL, "Playback Engine B" }, +	{ "Left ADC Mixer", NULL, "Recording Engine A" }, +	{ "Right ADC Mixer", NULL, "Recording Engine A" }, + +	{ "AIF1 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk }, +	{ "AIF2 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk }, +	{ "AIF3 CLK", NULL, "SYSCLK1", adau1373_check_aif_clk }, +	{ "AIF1 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk }, +	{ "AIF2 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk }, +	{ "AIF3 CLK", NULL, "SYSCLK2", adau1373_check_aif_clk }, + +	{ "AIF1 IN", NULL, "AIF1 CLK" }, +	{ "AIF1 OUT", NULL, "AIF1 CLK" }, +	{ "AIF2 IN", NULL, "AIF2 CLK" }, +	{ "AIF2 OUT", NULL, "AIF2 CLK" }, +	{ "AIF3 IN", NULL, "AIF3 CLK" }, +	{ "AIF3 OUT", NULL, "AIF3 CLK" }, +	{ "AIF1 IN", NULL, "AIF1 IN SRC", adau1373_check_src }, +	{ "AIF1 OUT", NULL, "AIF1 OUT SRC", adau1373_check_src }, +	{ "AIF2 IN", NULL, "AIF2 IN SRC", adau1373_check_src }, +	{ "AIF2 OUT", NULL, "AIF2 OUT SRC", adau1373_check_src }, +	{ "AIF3 IN", NULL, "AIF3 IN SRC", adau1373_check_src }, +	{ "AIF3 OUT", NULL, "AIF3 OUT SRC", adau1373_check_src }, + +	{ "DMIC1", NULL, "DMIC1DAT" }, +	{ "DMIC1", NULL, "SYSCLK1" }, +	{ "DMIC1", NULL, "Recording Engine A" }, +	{ "DMIC2", NULL, "DMIC2DAT" }, +	{ "DMIC2", NULL, "SYSCLK1" }, +	{ "DMIC2", NULL, "Recording Engine B" }, +}; + +static int adau1373_hw_params(struct snd_pcm_substream *substream, +	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ +	struct snd_soc_codec *codec = dai->codec; +	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); +	struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id]; +	unsigned int div; +	unsigned int freq; +	unsigned int ctrl; + +	freq = adau1373_dai->sysclk; + +	if (freq % params_rate(params) != 0) +		return -EINVAL; + +	switch (freq / params_rate(params)) { +	case 1024: /* sysclk / 256 */ +		div = 0; +		break; +	case 1536: /* 2/3 sysclk / 256 */ +		div = 1; +		break; +	case 2048: /* 1/2 sysclk / 256 */ +		div = 2; +		break; +	case 3072: /* 1/3 sysclk / 256 */ +		div = 3; +		break; +	case 4096: /* 1/4 sysclk / 256 */ +		div = 4; +		break; +	case 6144: /* 1/6 sysclk / 256 */ +		div = 5; +		break; +	case 5632: /* 2/11 sysclk / 256 */ +		div = 6; +		break; +	default: +		return -EINVAL; +	} + +	adau1373_dai->enable_src = (div != 0); + +	snd_soc_update_bits(codec, ADAU1373_BCLKDIV(dai->id), +		~ADAU1373_BCLKDIV_SOURCE, (div << 2) | ADAU1373_BCLKDIV_64); + +	switch (params_format(params)) { +	case SNDRV_PCM_FORMAT_S16_LE: +		ctrl = ADAU1373_DAI_WLEN_16; +		break; +	case SNDRV_PCM_FORMAT_S20_3LE: +		ctrl = ADAU1373_DAI_WLEN_20; +		break; +	case SNDRV_PCM_FORMAT_S24_LE: +		ctrl = ADAU1373_DAI_WLEN_24; +		break; +	case SNDRV_PCM_FORMAT_S32_LE: +		ctrl = ADAU1373_DAI_WLEN_32; +		break; +	default: +		return -EINVAL; +	} + +	return snd_soc_update_bits(codec, ADAU1373_DAI(dai->id), +			ADAU1373_DAI_WLEN_MASK, ctrl); +} + +static int adau1373_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ +	struct snd_soc_codec *codec = dai->codec; +	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(codec); +	struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id]; +	unsigned int ctrl; + +	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { +	case SND_SOC_DAIFMT_CBM_CFM: +		ctrl = ADAU1373_DAI_MASTER; +		adau1373_dai->master = true; +		break; +	case SND_SOC_DAIFMT_CBS_CFS: +		ctrl = 0; +		adau1373_dai->master = true; +		break; +	default: +		return -EINVAL; +	} + +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { +	case SND_SOC_DAIFMT_I2S: +		ctrl |= ADAU1373_DAI_FORMAT_I2S; +		break; +	case SND_SOC_DAIFMT_LEFT_J: +		ctrl |= ADAU1373_DAI_FORMAT_LEFT_J; +		break; +	case SND_SOC_DAIFMT_RIGHT_J: +		ctrl |= ADAU1373_DAI_FORMAT_RIGHT_J; +		break; +	case SND_SOC_DAIFMT_DSP_B: +		ctrl |= ADAU1373_DAI_FORMAT_DSP; +		break; +	default: +		return -EINVAL; +	} + +	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { +	case SND_SOC_DAIFMT_NB_NF: +		break; +	case SND_SOC_DAIFMT_IB_NF: +		ctrl |= ADAU1373_DAI_INVERT_BCLK; +		break; +	case SND_SOC_DAIFMT_NB_IF: +		ctrl |= ADAU1373_DAI_INVERT_LRCLK; +		break; +	case SND_SOC_DAIFMT_IB_IF: +		ctrl |= ADAU1373_DAI_INVERT_LRCLK | ADAU1373_DAI_INVERT_BCLK; +		break; +	default: +		return -EINVAL; +	} + +	snd_soc_update_bits(codec, ADAU1373_DAI(dai->id), +		~ADAU1373_DAI_WLEN_MASK, ctrl); + +	return 0; +} + +static int adau1373_set_dai_sysclk(struct snd_soc_dai *dai, +	int clk_id, unsigned int freq, int dir) +{ +	struct adau1373 *adau1373 = snd_soc_codec_get_drvdata(dai->codec); +	struct adau1373_dai *adau1373_dai = &adau1373->dais[dai->id]; + +	switch (clk_id) { +	case ADAU1373_CLK_SRC_PLL1: +	case ADAU1373_CLK_SRC_PLL2: +		break; +	default: +		return -EINVAL; +	} + +	adau1373_dai->sysclk = freq; +	adau1373_dai->clk_src = clk_id; + +	snd_soc_update_bits(dai->codec, ADAU1373_BCLKDIV(dai->id), +		ADAU1373_BCLKDIV_SOURCE, clk_id << 5); + +	return 0; +} + +static const struct snd_soc_dai_ops adau1373_dai_ops = { +	.hw_params	= adau1373_hw_params, +	.set_sysclk	= adau1373_set_dai_sysclk, +	.set_fmt	= adau1373_set_dai_fmt, +}; + +#define ADAU1373_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ +	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver adau1373_dai_driver[] = { +	{ +		.id = 0, +		.name = "adau1373-aif1", +		.playback = { +			.stream_name = "AIF1 Playback", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_48000, +			.formats = ADAU1373_FORMATS, +		}, +		.capture = { +			.stream_name = "AIF1 Capture", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_48000, +			.formats = ADAU1373_FORMATS, +		}, +		.ops = &adau1373_dai_ops, +		.symmetric_rates = 1, +	}, +	{ +		.id = 1, +		.name = "adau1373-aif2", +		.playback = { +			.stream_name = "AIF2 Playback", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_48000, +			.formats = ADAU1373_FORMATS, +		}, +		.capture = { +			.stream_name = "AIF2 Capture", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_48000, +			.formats = ADAU1373_FORMATS, +		}, +		.ops = &adau1373_dai_ops, +		.symmetric_rates = 1, +	}, +	{ +		.id = 2, +		.name = "adau1373-aif3", +		.playback = { +			.stream_name = "AIF3 Playback", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_48000, +			.formats = ADAU1373_FORMATS, +		}, +		.capture = { +			.stream_name = "AIF3 Capture", +			.channels_min = 2, +			.channels_max = 2, +			.rates = SNDRV_PCM_RATE_8000_48000, +			.formats = ADAU1373_FORMATS, +		}, +		.ops = &adau1373_dai_ops, +		.symmetric_rates = 1, +	}, +}; + +static int adau1373_set_pll(struct snd_soc_codec *codec, int pll_id, +	int source, unsigned int freq_in, unsigned int freq_out) +{ +	unsigned int dpll_div = 0; +	unsigned int x, r, n, m, i, j, mode; + +	switch (pll_id) { +	case ADAU1373_PLL1: +	case ADAU1373_PLL2: +		break; +	default: +		return -EINVAL; +	} + +	switch (source) { +	case ADAU1373_PLL_SRC_BCLK1: +	case ADAU1373_PLL_SRC_BCLK2: +	case ADAU1373_PLL_SRC_BCLK3: +	case ADAU1373_PLL_SRC_LRCLK1: +	case ADAU1373_PLL_SRC_LRCLK2: +	case ADAU1373_PLL_SRC_LRCLK3: +	case ADAU1373_PLL_SRC_MCLK1: +	case ADAU1373_PLL_SRC_MCLK2: +	case ADAU1373_PLL_SRC_GPIO1: +	case ADAU1373_PLL_SRC_GPIO2: +	case ADAU1373_PLL_SRC_GPIO3: +	case ADAU1373_PLL_SRC_GPIO4: +		break; +	default: +		return -EINVAL; +	} + +	if (freq_in < 7813 || freq_in > 27000000) +		return -EINVAL; + +	if (freq_out < 45158000 || freq_out > 49152000) +		return -EINVAL; + +	/* APLL input needs to be >= 8Mhz, so in case freq_in is less we use the +	 * DPLL to get it there. DPLL_out = (DPLL_in / div) * 1024 */ +	while (freq_in < 8000000) { +		freq_in *= 2; +		dpll_div++; +	} + +	if (freq_out % freq_in != 0) { +		/* fout = fin * (r + (n/m)) / x */ +		x = DIV_ROUND_UP(freq_in, 13500000); +		freq_in /= x; +		r = freq_out / freq_in; +		i = freq_out % freq_in; +		j = gcd(i, freq_in); +		n = i / j; +		m = freq_in / j; +		x--; +		mode = 1; +	} else { +		/* fout = fin / r */ +		r = freq_out / freq_in; +		n = 0; +		m = 0; +		x = 0; +		mode = 0; +	} + +	if (r < 2 || r > 8 || x > 3 || m > 0xffff || n > 0xffff) +		return -EINVAL; + +	if (dpll_div) { +		dpll_div = 11 - dpll_div; +		snd_soc_update_bits(codec, ADAU1373_PLL_CTRL6(pll_id), +			ADAU1373_PLL_CTRL6_DPLL_BYPASS, 0); +	} else { +		snd_soc_update_bits(codec, ADAU1373_PLL_CTRL6(pll_id), +			ADAU1373_PLL_CTRL6_DPLL_BYPASS, +			ADAU1373_PLL_CTRL6_DPLL_BYPASS); +	} + +	snd_soc_write(codec, ADAU1373_DPLL_CTRL(pll_id), +		(source << 4) | dpll_div); +	snd_soc_write(codec, ADAU1373_PLL_CTRL1(pll_id), (m >> 8) & 0xff); +	snd_soc_write(codec, ADAU1373_PLL_CTRL2(pll_id), m & 0xff); +	snd_soc_write(codec, ADAU1373_PLL_CTRL3(pll_id), (n >> 8) & 0xff); +	snd_soc_write(codec, ADAU1373_PLL_CTRL4(pll_id), n & 0xff); +	snd_soc_write(codec, ADAU1373_PLL_CTRL5(pll_id), +		(r << 3) | (x << 1) | mode); + +	/* Set sysclk to pll_rate / 4 */ +	snd_soc_update_bits(codec, ADAU1373_CLK_SRC_DIV(pll_id), 0x3f, 0x09); + +	return 0; +} + +static void adau1373_load_drc_settings(struct snd_soc_codec *codec, +	unsigned int nr, uint8_t *drc) +{ +	unsigned int i; + +	for (i = 0; i < ADAU1373_DRC_SIZE; ++i) +		snd_soc_write(codec, ADAU1373_DRC(nr) + i, drc[i]); +} + +static bool adau1373_valid_micbias(enum adau1373_micbias_voltage micbias) +{ +	switch (micbias) { +	case ADAU1373_MICBIAS_2_9V: +	case ADAU1373_MICBIAS_2_2V: +	case ADAU1373_MICBIAS_2_6V: +	case ADAU1373_MICBIAS_1_8V: +		return true; +	default: +		break; +	} +	return false; +} + +static int adau1373_probe(struct snd_soc_codec *codec) +{ +	struct adau1373_platform_data *pdata = codec->dev->platform_data; +	bool lineout_differential = false; +	unsigned int val; +	int ret; +	int i; + +	ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); +	if (ret) { +		dev_err(codec->dev, "failed to set cache I/O: %d\n", ret); +		return ret; +	} + +	codec->dapm.idle_bias_off = true; + +	if (pdata) { +		if (pdata->num_drc > ARRAY_SIZE(pdata->drc_setting)) +			return -EINVAL; + +		if (!adau1373_valid_micbias(pdata->micbias1) || +			!adau1373_valid_micbias(pdata->micbias2)) +			return -EINVAL; + +		for (i = 0; i < pdata->num_drc; ++i) { +			adau1373_load_drc_settings(codec, i, +				pdata->drc_setting[i]); +		} + +		snd_soc_add_controls(codec, adau1373_drc_controls, +			pdata->num_drc); + +		val = 0; +		for (i = 0; i < 4; ++i) { +			if (pdata->input_differential[i]) +				val |= BIT(i); +		} +		snd_soc_write(codec, ADAU1373_INPUT_MODE, val); + +		val = 0; +		if (pdata->lineout_differential) +			val |= ADAU1373_OUTPUT_CTRL_LDIFF; +		if (pdata->lineout_ground_sense) +			val |= ADAU1373_OUTPUT_CTRL_LNFBEN; +		snd_soc_write(codec, ADAU1373_OUTPUT_CTRL, val); + +		lineout_differential = pdata->lineout_differential; + +		snd_soc_write(codec, ADAU1373_EP_CTRL, +			(pdata->micbias1 << ADAU1373_EP_CTRL_MICBIAS1_OFFSET) | +			(pdata->micbias2 << ADAU1373_EP_CTRL_MICBIAS2_OFFSET)); +	} + +	if (!lineout_differential) { +		snd_soc_add_controls(codec, adau1373_lineout2_controls, +			ARRAY_SIZE(adau1373_lineout2_controls)); +	} + +	snd_soc_write(codec, ADAU1373_ADC_CTRL, +	    ADAU1373_ADC_CTRL_RESET_FORCE | ADAU1373_ADC_CTRL_PEAK_DETECT); + +	return 0; +} + +static int adau1373_set_bias_level(struct snd_soc_codec *codec, +	enum snd_soc_bias_level level) +{ +	switch (level) { +	case SND_SOC_BIAS_ON: +		break; +	case SND_SOC_BIAS_PREPARE: +		break; +	case SND_SOC_BIAS_STANDBY: +		snd_soc_update_bits(codec, ADAU1373_PWDN_CTRL3, +			ADAU1373_PWDN_CTRL3_PWR_EN, ADAU1373_PWDN_CTRL3_PWR_EN); +		break; +	case SND_SOC_BIAS_OFF: +		snd_soc_update_bits(codec, ADAU1373_PWDN_CTRL3, +			ADAU1373_PWDN_CTRL3_PWR_EN, 0); +		break; +	} +	codec->dapm.bias_level = level; +	return 0; +} + +static int adau1373_remove(struct snd_soc_codec *codec) +{ +	adau1373_set_bias_level(codec, SND_SOC_BIAS_OFF); +	return 0; +} + +static int adau1373_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ +	return adau1373_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int adau1373_resume(struct snd_soc_codec *codec) +{ +	adau1373_set_bias_level(codec, SND_SOC_BIAS_STANDBY); +	snd_soc_cache_sync(codec); + +	return 0; +} + +static struct snd_soc_codec_driver adau1373_codec_driver = { +	.probe =	adau1373_probe, +	.remove =	adau1373_remove, +	.suspend =	adau1373_suspend, +	.resume =	adau1373_resume, +	.set_bias_level = adau1373_set_bias_level, +	.reg_cache_size = ARRAY_SIZE(adau1373_default_regs), +	.reg_cache_default = adau1373_default_regs, +	.reg_word_size = sizeof(uint8_t), + +	.set_pll = adau1373_set_pll, + +	.controls = adau1373_controls, +	.num_controls = ARRAY_SIZE(adau1373_controls), +	.dapm_widgets = adau1373_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(adau1373_dapm_widgets), +	.dapm_routes = adau1373_dapm_routes, +	.num_dapm_routes = ARRAY_SIZE(adau1373_dapm_routes), +}; + +static int __devinit adau1373_i2c_probe(struct i2c_client *client, +	const struct i2c_device_id *id) +{ +	struct adau1373 *adau1373; +	int ret; + +	adau1373 = kzalloc(sizeof(*adau1373), GFP_KERNEL); +	if (!adau1373) +		return -ENOMEM; + +	dev_set_drvdata(&client->dev, adau1373); + +	ret = snd_soc_register_codec(&client->dev, &adau1373_codec_driver, +			adau1373_dai_driver, ARRAY_SIZE(adau1373_dai_driver)); +	if (ret < 0) +		kfree(adau1373); + +	return ret; +} + +static int __devexit adau1373_i2c_remove(struct i2c_client *client) +{ +	snd_soc_unregister_codec(&client->dev); +	kfree(dev_get_drvdata(&client->dev)); +	return 0; +} + +static const struct i2c_device_id adau1373_i2c_id[] = { +	{ "adau1373", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, adau1373_i2c_id); + +static struct i2c_driver adau1373_i2c_driver = { +	.driver = { +		.name = "adau1373", +		.owner = THIS_MODULE, +	}, +	.probe = adau1373_i2c_probe, +	.remove = __devexit_p(adau1373_i2c_remove), +	.id_table = adau1373_i2c_id, +}; + +static int __init adau1373_init(void) +{ +	return i2c_add_driver(&adau1373_i2c_driver); +} +module_init(adau1373_init); + +static void __exit adau1373_exit(void) +{ +	i2c_del_driver(&adau1373_i2c_driver); +} +module_exit(adau1373_exit); + +MODULE_DESCRIPTION("ASoC ADAU1373 driver"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1373.h b/sound/soc/codecs/adau1373.h new file mode 100644 index 00000000000..c6ab5530760 --- /dev/null +++ b/sound/soc/codecs/adau1373.h @@ -0,0 +1,29 @@ +#ifndef __ADAU1373_H__ +#define __ADAU1373_H__ + +enum adau1373_pll_src { +	ADAU1373_PLL_SRC_MCLK1 = 0, +	ADAU1373_PLL_SRC_BCLK1 = 1, +	ADAU1373_PLL_SRC_BCLK2 = 2, +	ADAU1373_PLL_SRC_BCLK3 = 3, +	ADAU1373_PLL_SRC_LRCLK1 = 4, +	ADAU1373_PLL_SRC_LRCLK2 = 5, +	ADAU1373_PLL_SRC_LRCLK3 = 6, +	ADAU1373_PLL_SRC_GPIO1 = 7, +	ADAU1373_PLL_SRC_GPIO2 = 8, +	ADAU1373_PLL_SRC_GPIO3 = 9, +	ADAU1373_PLL_SRC_GPIO4 = 10, +	ADAU1373_PLL_SRC_MCLK2 = 11, +}; + +enum adau1373_pll { +	ADAU1373_PLL1 = 0, +	ADAU1373_PLL2 = 1, +}; + +enum adau1373_clk_src { +	ADAU1373_CLK_SRC_PLL1 = 0, +	ADAU1373_CLK_SRC_PLL2 = 1, +}; + +#endif diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 7e4066e131e..91130fbc691 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -20,6 +20,7 @@  #include <linux/regulator/driver.h>  #include <linux/regulator/machine.h>  #include <linux/regulator/consumer.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/tlv.h>  #include <sound/pcm.h> @@ -1436,10 +1437,17 @@ static const struct i2c_device_id sgtl5000_id[] = {  MODULE_DEVICE_TABLE(i2c, sgtl5000_id); +static const struct of_device_id sgtl5000_dt_ids[] = { +	{ .compatible = "fsl,sgtl5000", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sgtl5000_dt_ids); +  static struct i2c_driver sgtl5000_i2c_driver = {  	.driver = {  		   .name = "sgtl5000",  		   .owner = THIS_MODULE, +		   .of_match_table = sgtl5000_dt_ids,  		   },  	.probe = sgtl5000_i2c_probe,  	.remove = __devexit_p(sgtl5000_i2c_remove), diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c index fbd7eb9e61c..5c7def3979c 100644 --- a/sound/soc/codecs/sta32x.c +++ b/sound/soc/codecs/sta32x.c @@ -524,13 +524,17 @@ static int sta32x_hw_params(struct snd_pcm_substream *substream,  	rate = params_rate(params);  	pr_debug("rate: %u\n", rate);  	for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++) -		if (interpolation_ratios[i].fs == rate) +		if (interpolation_ratios[i].fs == rate) {  			ir = interpolation_ratios[i].ir; +			break; +		}  	if (ir < 0)  		return -EINVAL;  	for (i = 0; mclk_ratios[ir][i].ratio; i++) -		if (mclk_ratios[ir][i].ratio * rate == sta32x->mclk) +		if (mclk_ratios[ir][i].ratio * rate == sta32x->mclk) {  			mcs = mclk_ratios[ir][i].mcs; +			break; +		}  	if (mcs < 0)  		return -EINVAL; @@ -808,6 +812,7 @@ static int sta32x_remove(struct snd_soc_codec *codec)  {  	struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); +	sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF);  	regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies);  	regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); @@ -867,18 +872,8 @@ static __devinit int sta32x_i2c_probe(struct i2c_client *i2c,  static __devexit int sta32x_i2c_remove(struct i2c_client *client)  {  	struct sta32x_priv *sta32x = i2c_get_clientdata(client); -	struct snd_soc_codec *codec = sta32x->codec; - -	if (codec) -		sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF); - -	regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); - -	if (codec) { -		snd_soc_unregister_codec(&client->dev); -		snd_soc_codec_set_drvdata(codec, NULL); -	} +	snd_soc_unregister_codec(&client->dev);  	kfree(sta32x);  	return 0;  } diff --git a/sound/soc/codecs/wm1250-ev1.c b/sound/soc/codecs/wm1250-ev1.c index bcc20896791..4523c4cec02 100644 --- a/sound/soc/codecs/wm1250-ev1.c +++ b/sound/soc/codecs/wm1250-ev1.c @@ -56,8 +56,26 @@ static struct snd_soc_codec_driver soc_codec_dev_wm1250_ev1 = {  };  static int __devinit wm1250_ev1_probe(struct i2c_client *i2c, -				      const struct i2c_device_id *id) +				      const struct i2c_device_id *i2c_id)  { +	int id, board, rev; + +	board = i2c_smbus_read_byte_data(i2c, 0); +	if (board < 0) { +		dev_err(&i2c->dev, "Failed to read ID: %d\n", board); +		return board; +	} + +	id = (board & 0xfe) >> 2; +	rev = board & 0x3; + +	if (id != 1) { +		dev_err(&i2c->dev, "Unknown board ID %d\n", id); +		return -ENODEV; +	} + +	dev_info(&i2c->dev, "revision %d\n", rev + 1); +  	return snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm1250_ev1,  				      &wm1250_ev1_dai, 1);  } diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index db0dced7484..55a4c830e11 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -20,6 +20,7 @@  #include <linux/platform_device.h>  #include <linux/spi/spi.h>  #include <linux/slab.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -598,6 +599,11 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8510 = {  	.reg_cache_default =wm8510_reg,  }; +static const struct of_device_id wm8510_of_match[] = { +	{ .compatible = "wlf,wm8510" }, +	{ }, +}; +  #if defined(CONFIG_SPI_MASTER)  static int __devinit wm8510_spi_probe(struct spi_device *spi)  { @@ -628,6 +634,7 @@ static struct spi_driver wm8510_spi_driver = {  	.driver = {  		.name	= "wm8510",  		.owner	= THIS_MODULE, +		.of_match_table = wm8510_of_match,  	},  	.probe		= wm8510_spi_probe,  	.remove		= __devexit_p(wm8510_spi_remove), @@ -671,6 +678,7 @@ static struct i2c_driver wm8510_i2c_driver = {  	.driver = {  		.name = "wm8510-codec",  		.owner = THIS_MODULE, +		.of_match_table = wm8510_of_match,  	},  	.probe =    wm8510_i2c_probe,  	.remove =   __devexit_p(wm8510_i2c_remove), diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index 4fd4d8dca0f..5355a7a944f 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -20,6 +20,7 @@  #include <linux/platform_device.h>  #include <linux/regulator/consumer.h>  #include <linux/slab.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -84,7 +85,7 @@ static const char *wm8523_zd_count_text[] = {  static const struct soc_enum wm8523_zc_count =  	SOC_ENUM_SINGLE(WM8523_ZERO_DETECT, 0, 2, wm8523_zd_count_text); -static const struct snd_kcontrol_new wm8523_snd_controls[] = { +static const struct snd_kcontrol_new wm8523_controls[] = {  SOC_DOUBLE_R_TLV("Playback Volume", WM8523_DAC_GAINL, WM8523_DAC_GAINR,  		 0, 448, 0, dac_tlv),  SOC_SINGLE("ZC Switch", WM8523_DAC_CTRL3, 4, 1, 0), @@ -101,22 +102,11 @@ SND_SOC_DAPM_OUTPUT("LINEVOUTL"),  SND_SOC_DAPM_OUTPUT("LINEVOUTR"),  }; -static const struct snd_soc_dapm_route intercon[] = { +static const struct snd_soc_dapm_route wm8523_dapm_routes[] = {  	{ "LINEVOUTL", NULL, "DAC" },  	{ "LINEVOUTR", NULL, "DAC" },  }; -static int wm8523_add_widgets(struct snd_soc_codec *codec) -{ -	struct snd_soc_dapm_context *dapm = &codec->dapm; - -	snd_soc_dapm_new_controls(dapm, wm8523_dapm_widgets, -				  ARRAY_SIZE(wm8523_dapm_widgets)); -	snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon)); - -	return 0; -} -  static struct {  	int value;  	int ratio; @@ -479,10 +469,6 @@ static int wm8523_probe(struct snd_soc_codec *codec)  	/* Bias level configuration will have done an extra enable */  	regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies); -	snd_soc_add_controls(codec, wm8523_snd_controls, -			     ARRAY_SIZE(wm8523_snd_controls)); -	wm8523_add_widgets(codec); -  	return 0;  err_enable: @@ -512,6 +498,18 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8523 = {  	.reg_word_size = sizeof(u16),  	.reg_cache_default = wm8523_reg,  	.volatile_register = wm8523_volatile_register, + +	.controls = wm8523_controls, +	.num_controls = ARRAY_SIZE(wm8523_controls), +	.dapm_widgets = wm8523_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(wm8523_dapm_widgets), +	.dapm_routes = wm8523_dapm_routes, +	.num_dapm_routes = ARRAY_SIZE(wm8523_dapm_routes), +}; + +static const struct of_device_id wm8523_of_match[] = { +	{ .compatible = "wlf,wm8523" }, +	{ },  };  #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) @@ -551,8 +549,9 @@ MODULE_DEVICE_TABLE(i2c, wm8523_i2c_id);  static struct i2c_driver wm8523_i2c_driver = {  	.driver = { -		.name = "wm8523-codec", +		.name = "wm8523",  		.owner = THIS_MODULE, +		.of_match_table = wm8523_of_match,  	},  	.probe =    wm8523_i2c_probe,  	.remove =   __devexit_p(wm8523_i2c_remove), diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 4bbc0a79f01..4664c3a76c7 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -26,6 +26,7 @@  #include <linux/platform_device.h>  #include <linux/regulator/consumer.h>  #include <linux/slab.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h> @@ -907,6 +908,11 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8580 = {  	.reg_cache_default = wm8580_reg,  }; +static const struct of_device_id wm8580_of_match[] = { +	{ .compatible = "wlf,wm8580" }, +	{ }, +}; +  #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)  static int wm8580_i2c_probe(struct i2c_client *i2c,  			    const struct i2c_device_id *id) @@ -943,8 +949,9 @@ MODULE_DEVICE_TABLE(i2c, wm8580_i2c_id);  static struct i2c_driver wm8580_i2c_driver = {  	.driver = { -		.name = "wm8580-codec", +		.name = "wm8580",  		.owner = THIS_MODULE, +		.of_match_table = wm8580_of_match,  	},  	.probe =    wm8580_i2c_probe,  	.remove =   wm8580_i2c_remove, diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c index a537e4af6ae..8457d3cb596 100644 --- a/sound/soc/codecs/wm8711.c +++ b/sound/soc/codecs/wm8711.c @@ -21,6 +21,7 @@  #include <linux/platform_device.h>  #include <linux/spi/spi.h>  #include <linux/slab.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -414,6 +415,12 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8711 = {  	.num_dapm_routes = ARRAY_SIZE(wm8711_intercon),  }; +static const struct of_device_id wm8711_of_match[] = { +	{ .compatible = "wlf,wm8711", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, wm8711_of_match); +  #if defined(CONFIG_SPI_MASTER)  static int __devinit wm8711_spi_probe(struct spi_device *spi)  { @@ -443,8 +450,9 @@ static int __devexit wm8711_spi_remove(struct spi_device *spi)  static struct spi_driver wm8711_spi_driver = {  	.driver = { -		.name	= "wm8711-codec", +		.name	= "wm8711",  		.owner	= THIS_MODULE, +		.of_match_table = wm8711_of_match,  	},  	.probe		= wm8711_spi_probe,  	.remove		= __devexit_p(wm8711_spi_remove), @@ -487,8 +495,9 @@ MODULE_DEVICE_TABLE(i2c, wm8711_i2c_id);  static struct i2c_driver wm8711_i2c_driver = {  	.driver = { -		.name = "wm8711-codec", +		.name = "wm8711",  		.owner = THIS_MODULE, +		.of_match_table = wm8711_of_match,  	},  	.probe =    wm8711_i2c_probe,  	.remove =   __devexit_p(wm8711_i2c_remove), diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 86d4718d3a7..04b027efd5c 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -19,6 +19,7 @@  #include <linux/platform_device.h>  #include <linux/spi/spi.h>  #include <linux/slab.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -269,6 +270,12 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8728 = {  	.num_dapm_routes = ARRAY_SIZE(wm8728_intercon),  }; +static const struct of_device_id wm8728_of_match[] = { +	{ .compatible = "wlf,wm8728", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, wm8728_of_match); +  #if defined(CONFIG_SPI_MASTER)  static int __devinit wm8728_spi_probe(struct spi_device *spi)  { @@ -298,8 +305,9 @@ static int __devexit wm8728_spi_remove(struct spi_device *spi)  static struct spi_driver wm8728_spi_driver = {  	.driver = { -		.name	= "wm8728-codec", +		.name	= "wm8728",  		.owner	= THIS_MODULE, +		.of_match_table = wm8728_of_match,  	},  	.probe		= wm8728_spi_probe,  	.remove		= __devexit_p(wm8728_spi_remove), @@ -342,8 +350,9 @@ MODULE_DEVICE_TABLE(i2c, wm8728_i2c_id);  static struct i2c_driver wm8728_i2c_driver = {  	.driver = { -		.name = "wm8728-codec", +		.name = "wm8728",  		.owner = THIS_MODULE, +		.of_match_table = wm8728_of_match,  	},  	.probe =    wm8728_i2c_probe,  	.remove =   __devexit_p(wm8728_i2c_remove), diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 76b4361e9b8..f76b6fc6766 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -22,6 +22,7 @@  #include <linux/platform_device.h>  #include <linux/regulator/consumer.h>  #include <linux/spi/spi.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -607,6 +608,13 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8731 = {  	.num_dapm_routes = ARRAY_SIZE(wm8731_intercon),  }; +static const struct of_device_id wm8731_of_match[] = { +	{ .compatible = "wlf,wm8731", }, +	{ } +}; + +MODULE_DEVICE_TABLE(of, wm8731_of_match); +  #if defined(CONFIG_SPI_MASTER)  static int __devinit wm8731_spi_probe(struct spi_device *spi)  { @@ -638,6 +646,7 @@ static struct spi_driver wm8731_spi_driver = {  	.driver = {  		.name	= "wm8731",  		.owner	= THIS_MODULE, +		.of_match_table = wm8731_of_match,  	},  	.probe		= wm8731_spi_probe,  	.remove		= __devexit_p(wm8731_spi_remove), @@ -682,6 +691,7 @@ static struct i2c_driver wm8731_i2c_driver = {  	.driver = {  		.name = "wm8731",  		.owner = THIS_MODULE, +		.of_match_table = wm8731_of_match,  	},  	.probe =    wm8731_i2c_probe,  	.remove =   __devexit_p(wm8731_i2c_remove), diff --git a/sound/soc/codecs/wm8737.c b/sound/soc/codecs/wm8737.c index 30c67d06a90..f6aef58845c 100644 --- a/sound/soc/codecs/wm8737.c +++ b/sound/soc/codecs/wm8737.c @@ -20,6 +20,7 @@  #include <linux/regulator/consumer.h>  #include <linux/spi/spi.h>  #include <linux/slab.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -634,6 +635,13 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8737 = {  	.reg_cache_default = wm8737_reg,  }; +static const struct of_device_id wm8737_of_match[] = { +	{ .compatible = "wlf,wm8737", }, +	{ } +}; + +MODULE_DEVICE_TABLE(of, wm8737_of_match); +  #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)  static __devinit int wm8737_i2c_probe(struct i2c_client *i2c,  				      const struct i2c_device_id *id) @@ -673,6 +681,7 @@ static struct i2c_driver wm8737_i2c_driver = {  	.driver = {  		.name = "wm8737",  		.owner = THIS_MODULE, +		.of_match_table = wm8737_of_match,  	},  	.probe =    wm8737_i2c_probe,  	.remove =   __devexit_p(wm8737_i2c_remove), @@ -711,6 +720,7 @@ static struct spi_driver wm8737_spi_driver = {  	.driver = {  		.name	= "wm8737",  		.owner	= THIS_MODULE, +		.of_match_table = wm8737_of_match,  	},  	.probe		= wm8737_spi_probe,  	.remove		= __devexit_p(wm8737_spi_remove), diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c index 25af901fe81..78c9e5ab3fa 100644 --- a/sound/soc/codecs/wm8741.c +++ b/sound/soc/codecs/wm8741.c @@ -17,9 +17,11 @@  #include <linux/delay.h>  #include <linux/pm.h>  #include <linux/i2c.h> +#include <linux/spi/spi.h>  #include <linux/platform_device.h>  #include <linux/regulator/consumer.h>  #include <linux/slab.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -422,17 +424,35 @@ static int wm8741_probe(struct snd_soc_codec *codec)  {  	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);  	int ret = 0; +	int i; + +	for (i = 0; i < ARRAY_SIZE(wm8741->supplies); i++) +		wm8741->supplies[i].supply = wm8741_supply_names[i]; + +	ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8741->supplies), +				 wm8741->supplies); +	if (ret != 0) { +		dev_err(codec->dev, "Failed to request supplies: %d\n", ret); +		goto err; +	} + +	ret = regulator_bulk_enable(ARRAY_SIZE(wm8741->supplies), +				    wm8741->supplies); +	if (ret != 0) { +		dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); +		goto err_get; +	}  	ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8741->control_type);  	if (ret != 0) {  		dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); -		return ret; +		goto err_enable;  	}  	ret = wm8741_reset(codec);  	if (ret < 0) {  		dev_err(codec->dev, "Failed to issue reset\n"); -		return ret; +		goto err_enable;  	}  	/* Change some default settings - latch VU */ @@ -451,58 +471,61 @@ static int wm8741_probe(struct snd_soc_codec *codec)  	dev_dbg(codec->dev, "Successful registration\n");  	return ret; + +err_enable: +	regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); +err_get: +	regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); +err: +	return ret; +} + +static int wm8741_remove(struct snd_soc_codec *codec) +{ +	struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); + +	regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); +	regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); + +	return 0;  }  static struct snd_soc_codec_driver soc_codec_dev_wm8741 = {  	.probe =	wm8741_probe, +	.remove =	wm8741_remove,  	.resume =	wm8741_resume,  	.reg_cache_size = ARRAY_SIZE(wm8741_reg_defaults),  	.reg_word_size = sizeof(u16),  	.reg_cache_default = wm8741_reg_defaults,  }; +static const struct of_device_id wm8741_of_match[] = { +	{ .compatible = "wlf,wm8741", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, wm8741_of_match); +  #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)  static int wm8741_i2c_probe(struct i2c_client *i2c,  			    const struct i2c_device_id *id)  {  	struct wm8741_priv *wm8741; -	int ret, i; +	int ret;  	wm8741 = kzalloc(sizeof(struct wm8741_priv), GFP_KERNEL);  	if (wm8741 == NULL)  		return -ENOMEM; -	for (i = 0; i < ARRAY_SIZE(wm8741->supplies); i++) -		wm8741->supplies[i].supply = wm8741_supply_names[i]; - -	ret = regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8741->supplies), -				 wm8741->supplies); -	if (ret != 0) { -		dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); -		goto err; -	} - -	ret = regulator_bulk_enable(ARRAY_SIZE(wm8741->supplies), -				    wm8741->supplies); -	if (ret != 0) { -		dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); -		goto err_get; -	} -  	i2c_set_clientdata(i2c, wm8741);  	wm8741->control_type = SND_SOC_I2C; -	ret =  snd_soc_register_codec(&i2c->dev, -			&soc_codec_dev_wm8741, &wm8741_dai, 1); -	if (ret < 0) -		goto err_enable; -	return ret; +	ret = snd_soc_register_codec(&i2c->dev, +				     &soc_codec_dev_wm8741, &wm8741_dai, 1); +	if (ret != 0) +		goto err; -err_enable: -	regulator_bulk_disable(ARRAY_SIZE(wm8741->supplies), wm8741->supplies); +	return ret; -err_get: -	regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);  err:  	kfree(wm8741);  	return ret; @@ -510,10 +533,7 @@ err:  static int wm8741_i2c_remove(struct i2c_client *client)  { -	struct wm8741_priv *wm8741 = i2c_get_clientdata(client); -  	snd_soc_unregister_codec(&client->dev); -	regulator_bulk_free(ARRAY_SIZE(wm8741->supplies), wm8741->supplies);  	kfree(i2c_get_clientdata(client));  	return 0;  } @@ -526,8 +546,9 @@ MODULE_DEVICE_TABLE(i2c, wm8741_i2c_id);  static struct i2c_driver wm8741_i2c_driver = {  	.driver = { -		.name = "wm8741-codec", +		.name = "wm8741",  		.owner = THIS_MODULE, +		.of_match_table = wm8741_of_match,  	},  	.probe =    wm8741_i2c_probe,  	.remove =   wm8741_i2c_remove, @@ -535,6 +556,44 @@ static struct i2c_driver wm8741_i2c_driver = {  };  #endif +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8741_spi_probe(struct spi_device *spi) +{ +	struct wm8741_priv *wm8741; +	int ret; + +	wm8741 = kzalloc(sizeof(struct wm8741_priv), GFP_KERNEL); +	if (wm8741 == NULL) +		return -ENOMEM; + +	wm8741->control_type = SND_SOC_SPI; +	spi_set_drvdata(spi, wm8741); + +	ret = snd_soc_register_codec(&spi->dev, +			&soc_codec_dev_wm8741, &wm8741_dai, 1); +	if (ret < 0) +		kfree(wm8741); +	return ret; +} + +static int __devexit wm8741_spi_remove(struct spi_device *spi) +{ +	snd_soc_unregister_codec(&spi->dev); +	kfree(spi_get_drvdata(spi)); +	return 0; +} + +static struct spi_driver wm8741_spi_driver = { +	.driver = { +		.name	= "wm8741", +		.owner	= THIS_MODULE, +		.of_match_table = wm8741_of_match, +	}, +	.probe		= wm8741_spi_probe, +	.remove		= __devexit_p(wm8741_spi_remove), +}; +#endif /* CONFIG_SPI_MASTER */ +  static int __init wm8741_modinit(void)  {  	int ret = 0; @@ -544,6 +603,13 @@ static int __init wm8741_modinit(void)  	if (ret != 0)  		pr_err("Failed to register WM8741 I2C driver: %d\n", ret);  #endif +#if defined(CONFIG_SPI_MASTER) +	ret = spi_register_driver(&wm8741_spi_driver); +	if (ret != 0) { +		printk(KERN_ERR "Failed to register wm8741 SPI driver: %d\n", +		       ret); +	} +#endif  	return ret;  } @@ -551,6 +617,9 @@ module_init(wm8741_modinit);  static void __exit wm8741_exit(void)  { +#if defined(CONFIG_SPI_MASTER) +	spi_unregister_driver(&wm8741_spi_driver); +#endif  #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)  	i2c_del_driver(&wm8741_i2c_driver);  #endif diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index d0003cc3bcd..15f03721ec6 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -21,6 +21,7 @@  #include <linux/platform_device.h>  #include <linux/spi/spi.h>  #include <linux/slab.h> +#include <linux/of_device.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -751,6 +752,13 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8750 = {  	.reg_cache_default = wm8750_reg,  }; +static const struct of_device_id wm8750_of_match[] = { +	{ .compatible = "wlf,wm8750", }, +	{ .compatible = "wlf,wm8987", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, wm8750_of_match); +  #if defined(CONFIG_SPI_MASTER)  static int __devinit wm8750_spi_probe(struct spi_device *spi)  { @@ -787,8 +795,9 @@ MODULE_DEVICE_TABLE(spi, wm8750_spi_ids);  static struct spi_driver wm8750_spi_driver = {  	.driver = { -		.name	= "wm8750-codec", +		.name	= "wm8750",  		.owner	= THIS_MODULE, +		.of_match_table = wm8750_of_match,  	},  	.id_table	= wm8750_spi_ids,  	.probe		= wm8750_spi_probe, @@ -833,8 +842,9 @@ MODULE_DEVICE_TABLE(i2c, wm8750_i2c_id);  static struct i2c_driver wm8750_i2c_driver = {  	.driver = { -		.name = "wm8750-codec", +		.name = "wm8750",  		.owner = THIS_MODULE, +		.of_match_table = wm8750_of_match,  	},  	.probe =    wm8750_i2c_probe,  	.remove =   __devexit_p(wm8750_i2c_remove), diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index ffa2ffe5ec1..fe04a101d65 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -38,6 +38,7 @@  #include <linux/delay.h>  #include <linux/pm.h>  #include <linux/i2c.h> +#include <linux/of_device.h>  #include <linux/platform_device.h>  #include <linux/spi/spi.h>  #include <linux/slab.h> @@ -1490,6 +1491,12 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8753 = {  	.reg_cache_default = wm8753_reg,  }; +static const struct of_device_id wm8753_of_match[] = { +	{ .compatible = "wlf,wm8753", }, +	{ } +}; +MODULE_DEVICE_TABLE(of, wm8753_of_match); +  #if defined(CONFIG_SPI_MASTER)  static int __devinit wm8753_spi_probe(struct spi_device *spi)  { @@ -1519,8 +1526,9 @@ static int __devexit wm8753_spi_remove(struct spi_device *spi)  static struct spi_driver wm8753_spi_driver = {  	.driver = { -		.name	= "wm8753-codec", +		.name	= "wm8753",  		.owner	= THIS_MODULE, +		.of_match_table = wm8753_of_match,  	},  	.probe		= wm8753_spi_probe,  	.remove		= __devexit_p(wm8753_spi_remove), @@ -1563,8 +1571,9 @@ MODULE_DEVICE_TABLE(i2c, wm8753_i2c_id);  static struct i2c_driver wm8753_i2c_driver = {  	.driver = { -		.name = "wm8753-codec", +		.name = "wm8753",  		.owner = THIS_MODULE, +		.of_match_table = wm8753_of_match,  	},  	.probe =    wm8753_i2c_probe,  	.remove =   __devexit_p(wm8753_i2c_remove), diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 1725550c293..8e397b286aa 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -63,6 +63,8 @@ struct wm8962_priv {  	int fll_fref;  	int fll_fout; +	u16 dsp2_ena; +  	struct delayed_work mic_work;  	struct snd_soc_jack *jack; @@ -837,7 +839,7 @@ static const struct wm8962_reg_access {  	[40] = { 0x00FF, 0x01FF, 0x0000 }, /* R40    - SPKOUTL volume */  	[41] = { 0x00FF, 0x01FF, 0x0000 }, /* R41    - SPKOUTR volume */ -	[47] = { 0x000F, 0x0000, 0x0000 }, /* R47    - Thermal Shutdown Status */ +	[47] = { 0x000F, 0x0000, 0xFFFF }, /* R47    - Thermal Shutdown Status */  	[48] = { 0x7EC7, 0x7E07, 0xFFFF }, /* R48    - Additional Control (4) */  	[49] = { 0x00D3, 0x00D7, 0xFFFF }, /* R49    - Class D Control 1 */  	[51] = { 0x0047, 0x0047, 0x0000 }, /* R51    - Class D Control 2 */ @@ -965,7 +967,7 @@ static const struct wm8962_reg_access {  	[584] = { 0x002D, 0x002D, 0x0000 }, /* R584   - IRQ Debounce */  	[586] = { 0xC000, 0xC000, 0x0000 }, /* R586   -  MICINT Source Pol */  	[768] = { 0x0001, 0x0001, 0x0000 }, /* R768   - DSP2 Power Management */ -	[1037] = { 0x0000, 0x003F, 0x0000 }, /* R1037  - DSP2_ExecControl */ +	[1037] = { 0x0000, 0x003F, 0xFFFF }, /* R1037  - DSP2_ExecControl */  	[4096] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4096  - Write Sequencer 0 */  	[4097] = { 0x00FF, 0x00FF, 0x0000 }, /* R4097  - Write Sequencer 1 */  	[4098] = { 0x070F, 0x070F, 0x0000 }, /* R4098  - Write Sequencer 2 */ @@ -1986,6 +1988,122 @@ static const unsigned int classd_tlv[] = {  };  static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static int wm8962_dsp2_write_config(struct snd_soc_codec *codec) +{ +	return 0; +} + +static int wm8962_dsp2_set_enable(struct snd_soc_codec *codec, u16 val) +{ +	u16 adcl = snd_soc_read(codec, WM8962_LEFT_ADC_VOLUME); +	u16 adcr = snd_soc_read(codec, WM8962_RIGHT_ADC_VOLUME); +	u16 dac = snd_soc_read(codec, WM8962_ADC_DAC_CONTROL_1); + +	/* Mute the ADCs and DACs */ +	snd_soc_write(codec, WM8962_LEFT_ADC_VOLUME, 0); +	snd_soc_write(codec, WM8962_RIGHT_ADC_VOLUME, WM8962_ADC_VU); +	snd_soc_update_bits(codec, WM8962_ADC_DAC_CONTROL_1, +			    WM8962_DAC_MUTE, WM8962_DAC_MUTE); + +	snd_soc_write(codec, WM8962_SOUNDSTAGE_ENABLES_0, val); + +	/* Restore the ADCs and DACs */ +	snd_soc_write(codec, WM8962_LEFT_ADC_VOLUME, adcl); +	snd_soc_write(codec, WM8962_RIGHT_ADC_VOLUME, adcr); +	snd_soc_update_bits(codec, WM8962_ADC_DAC_CONTROL_1, +			    WM8962_DAC_MUTE, dac); + +	return 0; +} + +static int wm8962_dsp2_start(struct snd_soc_codec *codec) +{ +	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + +	wm8962_dsp2_write_config(codec); + +	snd_soc_write(codec, WM8962_DSP2_EXECCONTROL, WM8962_DSP2_RUNR); + +	wm8962_dsp2_set_enable(codec, wm8962->dsp2_ena); + +	return 0; +} + +static int wm8962_dsp2_stop(struct snd_soc_codec *codec) +{ +	wm8962_dsp2_set_enable(codec, 0); + +	snd_soc_write(codec, WM8962_DSP2_EXECCONTROL, WM8962_DSP2_STOP); + +	return 0; +} + +#define WM8962_DSP2_ENABLE(xname, xshift) \ +{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ +	.info = wm8962_dsp2_ena_info, \ +	.get = wm8962_dsp2_ena_get, .put = wm8962_dsp2_ena_put, \ +	.private_value = xshift } + +static int wm8962_dsp2_ena_info(struct snd_kcontrol *kcontrol, +				struct snd_ctl_elem_info *uinfo) +{ +	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + +	uinfo->count = 1; +	uinfo->value.integer.min = 0; +	uinfo->value.integer.max = 1; + +	return 0; +} + +static int wm8962_dsp2_ena_get(struct snd_kcontrol *kcontrol, +			       struct snd_ctl_elem_value *ucontrol) +{ +	int shift = kcontrol->private_value; +	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); +	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + +	ucontrol->value.integer.value[0] = !!(wm8962->dsp2_ena & 1 << shift); + +	return 0; +} + +static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, +			       struct snd_ctl_elem_value *ucontrol) +{ +	int shift = kcontrol->private_value; +	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); +	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); +	int old = wm8962->dsp2_ena; +	int ret = 0; +	int dsp2_running = snd_soc_read(codec, WM8962_DSP2_POWER_MANAGEMENT) & +		WM8962_DSP2_ENA; + +	mutex_lock(&codec->mutex); + +	if (ucontrol->value.integer.value[0]) +		wm8962->dsp2_ena |= 1 << shift; +	else +		wm8962->dsp2_ena &= ~(1 << shift); + +	if (wm8962->dsp2_ena == old) +		goto out; + +	ret = 1; + +	if (dsp2_running) { +		if (wm8962->dsp2_ena) +			wm8962_dsp2_set_enable(codec, wm8962->dsp2_ena); +		else +			wm8962_dsp2_stop(codec); +	} + +out: +	mutex_unlock(&codec->mutex); + +	return ret; +} +  /* The VU bits for the headphones are in a different register to the mute   * bits and only take effect on the PGA if it is actually powered.   */ @@ -2049,6 +2167,14 @@ static const char *cap_hpf_mode_text[] = {  static const struct soc_enum cap_hpf_mode =  	SOC_ENUM_SINGLE(WM8962_ADC_DAC_CONTROL_2, 10, 2, cap_hpf_mode_text); + +static const char *cap_lhpf_mode_text[] = { +	"LPF", "HPF" +}; + +static const struct soc_enum cap_lhpf_mode = +	SOC_ENUM_SINGLE(WM8962_LHPF1, 1, 2, cap_lhpf_mode_text); +  static const struct snd_kcontrol_new wm8962_snd_controls[] = {  SOC_DOUBLE("Input Mixer Switch", WM8962_INPUT_MIXER_CONTROL_1, 3, 2, 1, 1), @@ -2077,6 +2203,8 @@ SOC_DOUBLE_R("Capture ZC Switch", WM8962_LEFT_INPUT_VOLUME,  SOC_SINGLE("Capture HPF Switch", WM8962_ADC_DAC_CONTROL_1, 0, 1, 1),  SOC_ENUM("Capture HPF Mode", cap_hpf_mode),  SOC_SINGLE("Capture HPF Cutoff", WM8962_ADC_DAC_CONTROL_2, 7, 7, 0), +SOC_SINGLE("Capture LHPF Switch", WM8962_LHPF1, 0, 1, 0), +SOC_ENUM("Capture LHPF Mode", cap_lhpf_mode),  SOC_DOUBLE_R_TLV("Sidetone Volume", WM8962_DAC_DSP_MIXING_1,  		 WM8962_DAC_DSP_MIXING_2, 4, 12, 0, st_tlv), @@ -2134,6 +2262,11 @@ SOC_DOUBLE_R_TLV("EQ4 Volume", WM8962_EQ3, WM8962_EQ23,  		 WM8962_EQL_B4_GAIN_SHIFT, 31, 0, eq_tlv),  SOC_DOUBLE_R_TLV("EQ5 Volume", WM8962_EQ3, WM8962_EQ23,  		 WM8962_EQL_B5_GAIN_SHIFT, 31, 0, eq_tlv), + +WM8962_DSP2_ENABLE("VSS Switch", WM8962_VSS_ENA_SHIFT), +WM8962_DSP2_ENABLE("HPF1 Switch", WM8962_HPF1_ENA_SHIFT), +WM8962_DSP2_ENABLE("HPF2 Switch", WM8962_HPF2_ENA_SHIFT), +WM8962_DSP2_ENABLE("HD Bass Switch", WM8962_HDBASS_ENA_SHIFT),  };  static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = { @@ -2395,6 +2528,31 @@ static int out_pga_event(struct snd_soc_dapm_widget *w,  	}  } +static int dsp2_event(struct snd_soc_dapm_widget *w, +		      struct snd_kcontrol *kcontrol, int event) +{ +	struct snd_soc_codec *codec = w->codec; +	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + +	switch (event) { +	case SND_SOC_DAPM_POST_PMU: +		if (wm8962->dsp2_ena) +			wm8962_dsp2_start(codec); +		break; + +	case SND_SOC_DAPM_PRE_PMD: +		if (wm8962->dsp2_ena) +			wm8962_dsp2_stop(codec); +		break; + +	default: +		BUG(); +		return -EINVAL; +	} + +	return 0; +} +  static const char *st_text[] = { "None", "Right", "Left" };  static const struct soc_enum str_enum = @@ -2517,6 +2675,9 @@ SND_SOC_DAPM_SUPPLY("SYSCLK", WM8962_CLOCKING2, 5, 0, sysclk_event,  SND_SOC_DAPM_SUPPLY("Charge Pump", WM8962_CHARGE_PUMP_1, 0, 0, cp_event,  		    SND_SOC_DAPM_POST_PMU),  SND_SOC_DAPM_SUPPLY("TOCLK", WM8962_ADDITIONAL_CONTROL_1, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY_S("DSP2", 1, WM8962_DSP2_POWER_MANAGEMENT, +		      WM8962_DSP2_ENA_SHIFT, 0, dsp2_event, +		      SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),  SND_SOC_DAPM_MIXER("INPGAL", WM8962_LEFT_INPUT_PGA_CONTROL, 4, 0,  		   inpgal, ARRAY_SIZE(inpgal)), @@ -2612,11 +2773,13 @@ static const struct snd_soc_dapm_route wm8962_intercon[] = {  	{ "ADCL", NULL, "TOCLK" },  	{ "ADCL", NULL, "MIXINL" },  	{ "ADCL", NULL, "DMIC" }, +	{ "ADCL", NULL, "DSP2" },  	{ "ADCR", NULL, "SYSCLK" },  	{ "ADCR", NULL, "TOCLK" },  	{ "ADCR", NULL, "MIXINR" },  	{ "ADCR", NULL, "DMIC" }, +	{ "ADCR", NULL, "DSP2" },  	{ "STL", "Left", "ADCL" },  	{ "STL", "Right", "ADCR" }, @@ -2628,11 +2791,13 @@ static const struct snd_soc_dapm_route wm8962_intercon[] = {  	{ "DACL", NULL, "TOCLK" },  	{ "DACL", NULL, "Beep" },  	{ "DACL", NULL, "STL" }, +	{ "DACL", NULL, "DSP2" },  	{ "DACR", NULL, "SYSCLK" },  	{ "DACR", NULL, "TOCLK" },  	{ "DACR", NULL, "Beep" },  	{ "DACR", NULL, "STR" }, +	{ "DACR", NULL, "DSP2" },  	{ "HPMIXL", "IN4L Switch", "IN4L" },  	{ "HPMIXL", "IN4R Switch", "IN4R" }, @@ -3403,12 +3568,16 @@ static irqreturn_t wm8962_irq(int irq, void *data)  	struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);  	int mask;  	int active; +	int reg;  	mask = snd_soc_read(codec, WM8962_INTERRUPT_STATUS_2_MASK);  	active = snd_soc_read(codec, WM8962_INTERRUPT_STATUS_2);  	active &= ~mask; +	if (!active) +		return IRQ_NONE; +  	/* Acknowledge the interrupts */  	snd_soc_write(codec, WM8962_INTERRUPT_STATUS_2, active); @@ -3420,9 +3589,21 @@ static irqreturn_t wm8962_irq(int irq, void *data)  	if (active & WM8962_FIFOS_ERR_EINT)  		dev_err(codec->dev, "FIFO error\n"); -	if (active & WM8962_TEMP_SHUT_EINT) +	if (active & WM8962_TEMP_SHUT_EINT) {  		dev_crit(codec->dev, "Thermal shutdown\n"); +		reg = snd_soc_read(codec, WM8962_THERMAL_SHUTDOWN_STATUS); + +		if (reg & WM8962_TEMP_ERR_HP) +			dev_crit(codec->dev, "Headphone thermal error\n"); +		if (reg & WM8962_TEMP_WARN_HP) +			dev_crit(codec->dev, "Headphone thermal warning\n"); +		if (reg & WM8962_TEMP_ERR_SPK) +			dev_crit(codec->dev, "Speaker thermal error\n"); +		if (reg & WM8962_TEMP_WARN_SPK) +			dev_crit(codec->dev, "Speaker thermal warning\n"); +	} +  	if (active & (WM8962_MICSCD_EINT | WM8962_MICD_EINT)) {  		dev_dbg(codec->dev, "Microphone event detected\n"); diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 6e85b8869af..eec8e143511 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -847,6 +847,7 @@ SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8993_BUS_CONTROL_1, 1, 0, clk_sys_event,  		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),  SND_SOC_DAPM_SUPPLY("TOCLK", WM8993_CLOCKING_1, 14, 0, NULL, 0),  SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8993_CLOCKING_3, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, NULL, 0),  SND_SOC_DAPM_ADC("ADCL", NULL, WM8993_POWER_MANAGEMENT_2, 1, 0),  SND_SOC_DAPM_ADC("ADCR", NULL, WM8993_POWER_MANAGEMENT_2, 0, 0), @@ -880,6 +881,9 @@ SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),  };  static const struct snd_soc_dapm_route routes[] = { +	{ "MICBIAS1", NULL, "VMID" }, +	{ "MICBIAS2", NULL, "VMID" }, +  	{ "ADCL", NULL, "CLK_SYS" },  	{ "ADCL", NULL, "CLK_DSP" },  	{ "ADCR", NULL, "CLK_SYS" }, @@ -1433,7 +1437,8 @@ static int wm8993_probe(struct snd_soc_codec *codec)  	int ret, i, val;  	wm8993->hubs_data.hp_startup_mode = 1; -	wm8993->hubs_data.dcs_codes = -2; +	wm8993->hubs_data.dcs_codes_l = -2; +	wm8993->hubs_data.dcs_codes_r = -2;  	wm8993->hubs_data.series_startup = 1;  	ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); diff --git a/sound/soc/codecs/wm8994-tables.c b/sound/soc/codecs/wm8994-tables.c index a87adbd05ee..df5a8b9a250 100644 --- a/sound/soc/codecs/wm8994-tables.c +++ b/sound/soc/codecs/wm8994-tables.c @@ -1073,8 +1073,8 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = {  	{ 0x0000, 0x0000 }, /* R1069 */  	{ 0x0000, 0x0000 }, /* R1070 */  	{ 0x0000, 0x0000 }, /* R1071 */ -	{ 0x0000, 0x0000 }, /* R1072 */ -	{ 0x0000, 0x0000 }, /* R1073 */ +	{ 0x006F, 0x006F }, /* R1072  - AIF1 DAC1 Noise Gate */ +	{ 0x006F, 0x006F }, /* R1073  - AIF1 DAC2 Noise Gate */  	{ 0x0000, 0x0000 }, /* R1074 */  	{ 0x0000, 0x0000 }, /* R1075 */  	{ 0x0000, 0x0000 }, /* R1076 */ @@ -1329,7 +1329,7 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = {  	{ 0x0000, 0x0000 }, /* R1325 */  	{ 0x0000, 0x0000 }, /* R1326 */  	{ 0x0000, 0x0000 }, /* R1327 */ -	{ 0x0000, 0x0000 }, /* R1328 */ +	{ 0x006F, 0x006F }, /* R1328  - AIF2 DAC Noise Gate */  	{ 0x0000, 0x0000 }, /* R1329 */  	{ 0x0000, 0x0000 }, /* R1330 */  	{ 0x0000, 0x0000 }, /* R1331 */ @@ -1635,8 +1635,8 @@ const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = {  	0x0000,     /* R58    - MICBIAS */  	0x000D,     /* R59    - LDO 1 */  	0x0003,     /* R60    - LDO 2 */ -	0x0000,     /* R61 */ -	0x0000,     /* R62 */ +	0x0039,     /* R61    - MICBIAS1 */ +	0x0039,     /* R62    - MICBIAS2 */  	0x0000,     /* R63 */  	0x0000,     /* R64 */  	0x0000,     /* R65 */ @@ -2646,8 +2646,8 @@ const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = {  	0x0000,     /* R1069 */  	0x0000,     /* R1070 */  	0x0000,     /* R1071 */ -	0x0000,     /* R1072 */ -	0x0000,     /* R1073 */ +	0x0068,     /* R1072  - AIF1 DAC1 Noise Gate */ +	0x0068,     /* R1073  - AIF1 DAC2 Noise Gate */  	0x0000,     /* R1074 */  	0x0000,     /* R1075 */  	0x0000,     /* R1076 */ @@ -2902,7 +2902,7 @@ const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = {  	0x0000,     /* R1325 */  	0x0000,     /* R1326 */  	0x0000,     /* R1327 */ -	0x0000,     /* R1328 */ +	0x0068,     /* R1328  - AIF2 DAC Noise Gate */  	0x0000,     /* R1329 */  	0x0000,     /* R1330 */  	0x0000,     /* R1331 */ diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index b393f9fac97..e5372675123 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -107,6 +107,7 @@ static int wm8994_volatile(struct snd_soc_codec *codec, unsigned int reg)  	case WM8994_LDO_2:  	case WM8958_DSP2_EXECCONTROL:  	case WM8958_MIC_DETECT_3: +	case WM8994_DC_SERVO_4E:  		return 1;  	default:  		return 0; @@ -281,6 +282,7 @@ static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);  static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0);  static const DECLARE_TLV_DB_SCALE(wm8994_3d_tlv, -1600, 183, 0);  static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0);  #define WM8994_DRC_SWITCH(xname, reg, shift) \  {	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ @@ -660,8 +662,45 @@ SOC_SINGLE_TLV("AIF2 EQ5 Volume", WM8994_AIF2_EQ_GAINS_2, 6, 31, 0,  	       eq_tlv),  }; +static const char *wm8958_ng_text[] = { +	"30ms", "125ms", "250ms", "500ms", +}; + +static const struct soc_enum wm8958_aif1dac1_ng_hold = +	SOC_ENUM_SINGLE(WM8958_AIF1_DAC1_NOISE_GATE, +			WM8958_AIF1DAC1_NG_THR_SHIFT, 4, wm8958_ng_text); + +static const struct soc_enum wm8958_aif1dac2_ng_hold = +	SOC_ENUM_SINGLE(WM8958_AIF1_DAC2_NOISE_GATE, +			WM8958_AIF1DAC2_NG_THR_SHIFT, 4, wm8958_ng_text); + +static const struct soc_enum wm8958_aif2dac_ng_hold = +	SOC_ENUM_SINGLE(WM8958_AIF2_DAC_NOISE_GATE, +			WM8958_AIF2DAC_NG_THR_SHIFT, 4, wm8958_ng_text); +  static const struct snd_kcontrol_new wm8958_snd_controls[] = {  SOC_SINGLE_TLV("AIF3 Boost Volume", WM8958_AIF3_CONTROL_2, 10, 3, 0, aif_tlv), + +SOC_SINGLE("AIF1DAC1 Noise Gate Switch", WM8958_AIF1_DAC1_NOISE_GATE, +	   WM8958_AIF1DAC1_NG_ENA_SHIFT, 1, 0), +SOC_ENUM("AIF1DAC1 Noise Gate Hold Time", wm8958_aif1dac1_ng_hold), +SOC_SINGLE_TLV("AIF1DAC1 Noise Gate Threshold Volume", +	       WM8958_AIF1_DAC1_NOISE_GATE, WM8958_AIF1DAC1_NG_THR_SHIFT, +	       7, 1, ng_tlv), + +SOC_SINGLE("AIF1DAC2 Noise Gate Switch", WM8958_AIF1_DAC2_NOISE_GATE, +	   WM8958_AIF1DAC2_NG_ENA_SHIFT, 1, 0), +SOC_ENUM("AIF1DAC2 Noise Gate Hold Time", wm8958_aif1dac2_ng_hold), +SOC_SINGLE_TLV("AIF1DAC2 Noise Gate Threshold Volume", +	       WM8958_AIF1_DAC2_NOISE_GATE, WM8958_AIF1DAC2_NG_THR_SHIFT, +	       7, 1, ng_tlv), + +SOC_SINGLE("AIF2DAC Noise Gate Switch", WM8958_AIF2_DAC_NOISE_GATE, +	   WM8958_AIF2DAC_NG_ENA_SHIFT, 1, 0), +SOC_ENUM("AIF2DAC Noise Gate Hold Time", wm8958_aif2dac_ng_hold), +SOC_SINGLE_TLV("AIF2DAC Noise Gate Threshold Volume", +	       WM8958_AIF2_DAC_NOISE_GATE, WM8958_AIF2DAC_NG_THR_SHIFT, +	       7, 1, ng_tlv),  };  static int clk_sys_event(struct snd_soc_dapm_widget *w, @@ -681,6 +720,97 @@ static int clk_sys_event(struct snd_soc_dapm_widget *w,  	return 0;  } +static void vmid_reference(struct snd_soc_codec *codec) +{ +	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + +	wm8994->vmid_refcount++; + +	dev_dbg(codec->dev, "Referencing VMID, refcount is now %d\n", +		wm8994->vmid_refcount); + +	if (wm8994->vmid_refcount == 1) { +		/* Startup bias, VMID ramp & buffer */ +		snd_soc_update_bits(codec, WM8994_ANTIPOP_2, +				    WM8994_STARTUP_BIAS_ENA | +				    WM8994_VMID_BUF_ENA | +				    WM8994_VMID_RAMP_MASK, +				    WM8994_STARTUP_BIAS_ENA | +				    WM8994_VMID_BUF_ENA | +				    (0x11 << WM8994_VMID_RAMP_SHIFT)); + +		/* Main bias enable, VMID=2x40k */ +		snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, +				    WM8994_BIAS_ENA | +				    WM8994_VMID_SEL_MASK, +				    WM8994_BIAS_ENA | 0x2); + +		msleep(20); +	} +} + +static void vmid_dereference(struct snd_soc_codec *codec) +{ +	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + +	wm8994->vmid_refcount--; + +	dev_dbg(codec->dev, "Dereferencing VMID, refcount is now %d\n", +		wm8994->vmid_refcount); + +	if (wm8994->vmid_refcount == 0) { +		/* Switch over to startup biases */ +		snd_soc_update_bits(codec, WM8994_ANTIPOP_2, +				    WM8994_BIAS_SRC | +				    WM8994_STARTUP_BIAS_ENA | +				    WM8994_VMID_BUF_ENA | +				    WM8994_VMID_RAMP_MASK, +				    WM8994_BIAS_SRC | +				    WM8994_STARTUP_BIAS_ENA | +				    WM8994_VMID_BUF_ENA | +				    (1 << WM8994_VMID_RAMP_SHIFT)); + +		/* Disable main biases */ +		snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, +				    WM8994_BIAS_ENA | +				    WM8994_VMID_SEL_MASK, 0); + +		/* Discharge line */ +		snd_soc_update_bits(codec, WM8994_ANTIPOP_1, +				    WM8994_LINEOUT1_DISCH | +				    WM8994_LINEOUT2_DISCH, +				    WM8994_LINEOUT1_DISCH | +				    WM8994_LINEOUT2_DISCH); + +		msleep(5); + +		/* Switch off startup biases */ +		snd_soc_update_bits(codec, WM8994_ANTIPOP_2, +				    WM8994_BIAS_SRC | +				    WM8994_STARTUP_BIAS_ENA | +				    WM8994_VMID_BUF_ENA | +				    WM8994_VMID_RAMP_MASK, 0); +	} +} + +static int vmid_event(struct snd_soc_dapm_widget *w, +		      struct snd_kcontrol *kcontrol, int event) +{ +	struct snd_soc_codec *codec = w->codec; + +	switch (event) { +	case SND_SOC_DAPM_PRE_PMU: +		vmid_reference(codec); +		break; + +	case SND_SOC_DAPM_POST_PMD: +		vmid_dereference(codec); +		break; +	} + +	return 0; +} +  static void wm8994_update_class_w(struct snd_soc_codec *codec)  {  	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); @@ -1208,6 +1338,8 @@ SND_SOC_DAPM_INPUT("Clock"),  SND_SOC_DAPM_SUPPLY_S("MICBIAS Supply", 1, SND_SOC_NOPM, 0, 0, micbias_ev,  		      SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, vmid_event, +		    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),  SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,  		    SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), @@ -1525,6 +1657,8 @@ static const struct snd_soc_dapm_route wm8994_revd_intercon[] = {  static const struct snd_soc_dapm_route wm8994_intercon[] = {  	{ "AIF2DACL", NULL, "AIF2DAC Mux" },  	{ "AIF2DACR", NULL, "AIF2DAC Mux" }, +	{ "MICBIAS1", NULL, "VMID" }, +	{ "MICBIAS2", NULL, "VMID" },  };  static const struct snd_soc_dapm_route wm8958_intercon[] = { @@ -1629,10 +1763,12 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,  			  unsigned int freq_in, unsigned int freq_out)  {  	struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); +	struct wm8994 *control = codec->control_data;  	int reg_offset, ret;  	struct fll_div fll;  	u16 reg, aif1, aif2;  	unsigned long timeout; +	bool was_enabled;  	aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1)  		& WM8994_AIF1CLK_ENA; @@ -1653,6 +1789,9 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,  		return -EINVAL;  	} +	reg = snd_soc_read(codec, WM8994_FLL1_CONTROL_1 + reg_offset); +	was_enabled = reg & WM8994_FLL1_ENA; +  	switch (src) {  	case 0:  		/* Allow no source specification when stopping */ @@ -1719,6 +1858,21 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,  	/* Enable (with fractional mode if required) */  	if (freq_out) { +		/* Enable VMID if we need it */ +		if (!was_enabled) { +			switch (control->type) { +			case WM8994: +				vmid_reference(codec); +				break; +			case WM8958: +				if (wm8994->revision < 1) +					vmid_reference(codec); +				break; +			default: +				break; +			} +		} +  		if (fll.k)  			reg = WM8994_FLL1_ENA | WM8994_FLL1_FRAC;  		else @@ -1736,6 +1890,20 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,  		} else {  			msleep(5);  		} +	} else { +		if (was_enabled) { +			switch (control->type) { +			case WM8994: +				vmid_dereference(codec); +				break; +			case WM8958: +				if (wm8994->revision < 1) +					vmid_dereference(codec); +				break; +			default: +				break; +			} +		}  	}  	wm8994->fll[id].in = freq_in; @@ -1852,9 +2020,6 @@ static int wm8994_set_bias_level(struct snd_soc_codec *codec,  		break;  	case SND_SOC_BIAS_PREPARE: -		/* VMID=2x40k */ -		snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, -				    WM8994_VMID_SEL_MASK, 0x2);  		break;  	case SND_SOC_BIAS_STANDBY: @@ -1896,65 +2061,13 @@ static int wm8994_set_bias_level(struct snd_soc_codec *codec,  					    WM8994_LINEOUT2_DISCH,  					    WM8994_LINEOUT1_DISCH |  					    WM8994_LINEOUT2_DISCH); - -			/* Startup bias, VMID ramp & buffer */ -			snd_soc_update_bits(codec, WM8994_ANTIPOP_2, -					    WM8994_STARTUP_BIAS_ENA | -					    WM8994_VMID_BUF_ENA | -					    WM8994_VMID_RAMP_MASK, -					    WM8994_STARTUP_BIAS_ENA | -					    WM8994_VMID_BUF_ENA | -					    (0x11 << WM8994_VMID_RAMP_SHIFT)); - -			/* Main bias enable, VMID=2x40k */ -			snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, -					    WM8994_BIAS_ENA | -					    WM8994_VMID_SEL_MASK, -					    WM8994_BIAS_ENA | 0x2); - -			msleep(20);  		} -		/* VMID=2x500k */ -		snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, -				    WM8994_VMID_SEL_MASK, 0x4);  		break;  	case SND_SOC_BIAS_OFF:  		if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) { -			/* Switch over to startup biases */ -			snd_soc_update_bits(codec, WM8994_ANTIPOP_2, -					    WM8994_BIAS_SRC | -					    WM8994_STARTUP_BIAS_ENA | -					    WM8994_VMID_BUF_ENA | -					    WM8994_VMID_RAMP_MASK, -					    WM8994_BIAS_SRC | -					    WM8994_STARTUP_BIAS_ENA | -					    WM8994_VMID_BUF_ENA | -					    (1 << WM8994_VMID_RAMP_SHIFT)); - -			/* Disable main biases */ -			snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1, -					    WM8994_BIAS_ENA | -					    WM8994_VMID_SEL_MASK, 0); - -			/* Discharge line */ -			snd_soc_update_bits(codec, WM8994_ANTIPOP_1, -					    WM8994_LINEOUT1_DISCH | -					    WM8994_LINEOUT2_DISCH, -					    WM8994_LINEOUT1_DISCH | -					    WM8994_LINEOUT2_DISCH); - -			msleep(5); - -			/* Switch off startup biases */ -			snd_soc_update_bits(codec, WM8994_ANTIPOP_2, -					    WM8994_BIAS_SRC | -					    WM8994_STARTUP_BIAS_ENA | -					    WM8994_VMID_BUF_ENA | -					    WM8994_VMID_RAMP_MASK, 0); -  			wm8994->cur_fw = NULL;  			pm_runtime_put(codec->dev); @@ -2384,6 +2497,21 @@ static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate)  	return snd_soc_update_bits(codec, reg, mask, val);  } +static int wm8994_aif2_probe(struct snd_soc_dai *dai) +{ +	struct snd_soc_codec *codec = dai->codec; + +	/* Disable the pulls on the AIF if we're using it to save power. */ +	snd_soc_update_bits(codec, WM8994_GPIO_3, +			    WM8994_GPN_PU | WM8994_GPN_PD, 0); +	snd_soc_update_bits(codec, WM8994_GPIO_4, +			    WM8994_GPN_PU | WM8994_GPN_PD, 0); +	snd_soc_update_bits(codec, WM8994_GPIO_5, +			    WM8994_GPN_PU | WM8994_GPN_PD, 0); + +	return 0; +} +  #define WM8994_RATES SNDRV_PCM_RATE_8000_96000  #define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ @@ -2451,6 +2579,7 @@ static struct snd_soc_dai_driver wm8994_dai[] = {  			.rates = WM8994_RATES,  			.formats = WM8994_FORMATS,  		}, +		.probe = wm8994_aif2_probe,  		.ops = &wm8994_aif2_dai_ops,  	},  	{ @@ -2916,6 +3045,24 @@ static irqreturn_t wm8994_fifo_error(int irq, void *data)  	return IRQ_HANDLED;  } +static irqreturn_t wm8994_temp_warn(int irq, void *data) +{ +	struct snd_soc_codec *codec = data; + +	dev_err(codec->dev, "Thermal warning\n"); + +	return IRQ_HANDLED; +} + +static irqreturn_t wm8994_temp_shut(int irq, void *data) +{ +	struct snd_soc_codec *codec = data; + +	dev_crit(codec->dev, "Thermal shutdown\n"); + +	return IRQ_HANDLED; +} +  static int wm8994_codec_probe(struct snd_soc_codec *codec)  {  	struct wm8994 *control; @@ -2972,13 +3119,14 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)  		switch (wm8994->revision) {  		case 2:  		case 3: -			wm8994->hubs.dcs_codes = -5; +			wm8994->hubs.dcs_codes_l = -5; +			wm8994->hubs.dcs_codes_r = -5;  			wm8994->hubs.hp_startup_mode = 1;  			wm8994->hubs.dcs_readback_mode = 1;  			wm8994->hubs.series_startup = 1;  			break;  		default: -			wm8994->hubs.dcs_readback_mode = 1; +			wm8994->hubs.dcs_readback_mode = 2;  			break;  		}  		break; @@ -2993,6 +3141,10 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)  	wm8994_request_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR,  			   wm8994_fifo_error, "FIFO error", codec); +	wm8994_request_irq(wm8994->control_data, WM8994_IRQ_TEMP_WARN, +			   wm8994_temp_warn, "Thermal warning", codec); +	wm8994_request_irq(wm8994->control_data, WM8994_IRQ_TEMP_SHUT, +			   wm8994_temp_shut, "Thermal shutdown", codec);  	ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_DCS_DONE,  				 wm_hubs_dcs_done, "DC servo done", @@ -3257,6 +3409,8 @@ err_irq:  	wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE,  			&wm8994->hubs);  	wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec); +	wm8994_free_irq(codec->control_data, WM8994_IRQ_TEMP_SHUT, codec); +	wm8994_free_irq(codec->control_data, WM8994_IRQ_TEMP_WARN, codec);  err:  	kfree(wm8994);  	return ret; @@ -3279,6 +3433,8 @@ static int  wm8994_codec_remove(struct snd_soc_codec *codec)  	wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE,  			&wm8994->hubs);  	wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec); +	wm8994_free_irq(codec->control_data, WM8994_IRQ_TEMP_SHUT, codec); +	wm8994_free_irq(codec->control_data, WM8994_IRQ_TEMP_WARN, codec);  	switch (control->type) {  	case WM8994: diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 1ab2266039f..f4f1355efc8 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -83,6 +83,8 @@ struct wm8994_priv {  	struct completion fll_locked[2];  	bool fll_locked_irq; +	int vmid_refcount; +  	int dac_rates[2];  	int lrclk_shared[2]; diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c index 0cdb9d10567..e5e46075c36 100644 --- a/sound/soc/codecs/wm8996.c +++ b/sound/soc/codecs/wm8996.c @@ -414,6 +414,7 @@ static const DECLARE_TLV_DB_SCALE(out_digital_tlv, -1200, 150, 0);  static const DECLARE_TLV_DB_SCALE(out_tlv, -900, 75, 0);  static const DECLARE_TLV_DB_SCALE(spk_tlv, -900, 150, 0);  static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(threedstereo_tlv, -1600, 183, 1);  static const char *sidetone_hpf_text[] = {  	"2.9kHz", "1.5kHz", "735Hz", "403Hz", "196Hz", "98Hz", "49Hz" @@ -608,6 +609,14 @@ SOC_SINGLE("DAC High Performance Switch", WM8996_OVERSAMPLING, 0, 1, 0),  SOC_SINGLE("DAC Soft Mute Switch", WM8996_DAC_SOFTMUTE, 1, 1, 0),  SOC_SINGLE("DAC Slow Soft Mute Switch", WM8996_DAC_SOFTMUTE, 0, 1, 0), +SOC_SINGLE("DSP1 3D Stereo Switch", WM8996_DSP1_RX_FILTERS_2, 8, 1, 0), +SOC_SINGLE("DSP2 3D Stereo Switch", WM8996_DSP2_RX_FILTERS_2, 8, 1, 0), + +SOC_SINGLE_TLV("DSP1 3D Stereo Volume", WM8996_DSP1_RX_FILTERS_2, 10, 15, +		0, threedstereo_tlv), +SOC_SINGLE_TLV("DSP2 3D Stereo Volume", WM8996_DSP2_RX_FILTERS_2, 10, 15, +		0, threedstereo_tlv), +  SOC_DOUBLE_TLV("Digital Output 1 Volume", WM8996_DAC1_HPOUT1_VOLUME, 0, 4,  	       8, 0, out_digital_tlv),  SOC_DOUBLE_TLV("Digital Output 2 Volume", WM8996_DAC2_HPOUT2_VOLUME, 0, 4, @@ -982,6 +991,8 @@ SND_SOC_DAPM_SUPPLY_S("Charge Pump", 2, WM8996_CHARGE_PUMP_1, 15, 0, cp_event,  		      SND_SOC_DAPM_POST_PMU),  SND_SOC_DAPM_SUPPLY("LDO2", WM8996_POWER_MANAGEMENT_2, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICB1 Audio", WM8996_MICBIAS_1, 4, 1, NULL, 0), +SND_SOC_DAPM_SUPPLY("MICB2 Audio", WM8996_MICBIAS_2, 4, 1, NULL, 0),  SND_SOC_DAPM_MICBIAS("MICB2", WM8996_POWER_MANAGEMENT_1, 9, 0),  SND_SOC_DAPM_MICBIAS("MICB1", WM8996_POWER_MANAGEMENT_1, 8, 0), @@ -1137,7 +1148,9 @@ static const struct snd_soc_dapm_route wm8996_dapm_routes[] = {  	{ "Charge Pump", NULL, "SYSCLK" },  	{ "MICB1", NULL, "LDO2" }, +	{ "MICB1", NULL, "MICB1 Audio" },  	{ "MICB2", NULL, "LDO2" }, +	{ "MICB2", NULL, "MICB2 Audio" },  	{ "IN1L PGA", NULL, "IN2LN" },  	{ "IN1L PGA", NULL, "IN2LP" }, @@ -2412,6 +2425,9 @@ static irqreturn_t wm8996_irq(int irq, void *data)  	}  	irq_val &= ~snd_soc_read(codec, WM8996_INTERRUPT_STATUS_2_MASK); +	if (!irq_val) +		return IRQ_NONE; +  	snd_soc_write(codec, WM8996_INTERRUPT_STATUS_2, irq_val);  	if (irq_val & (WM8996_DCS_DONE_01_EINT | WM8996_DCS_DONE_23_EINT)) { @@ -2430,10 +2446,7 @@ static irqreturn_t wm8996_irq(int irq, void *data)  	if (irq_val & WM8996_MICD_EINT)  		wm8996_micd(codec); -	if (irq_val) -		return IRQ_HANDLED; -	else -		return IRQ_NONE; +	return IRQ_HANDLED;  }  static irqreturn_t wm8996_edge_irq(int irq, void *data) diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index e763c54c55d..ca8ce03510f 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -18,6 +18,7 @@  #include <linux/pm.h>  #include <linux/i2c.h>  #include <linux/platform_device.h> +#include <linux/mfd/wm8994/registers.h>  #include <sound/core.h>  #include <sound/pcm.h>  #include <sound/pcm_params.h> @@ -116,14 +117,23 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)  {  	struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec);  	s8 offset; -	u16 reg, reg_l, reg_r, dcs_cfg; +	u16 reg, reg_l, reg_r, dcs_cfg, dcs_reg; + +	switch (hubs->dcs_readback_mode) { +	case 2: +		dcs_reg = WM8994_DC_SERVO_4E; +		break; +	default: +		dcs_reg = WM8993_DC_SERVO_3; +		break; +	}  	/* If we're using a digital only path and have a previously  	 * callibrated DC servo offset stored then use that. */  	if (hubs->class_w && hubs->class_w_dcs) {  		dev_dbg(codec->dev, "Using cached DC servo offset %x\n",  			hubs->class_w_dcs); -		snd_soc_write(codec, WM8993_DC_SERVO_3, hubs->class_w_dcs); +		snd_soc_write(codec, dcs_reg, hubs->class_w_dcs);  		wait_for_dc_servo(codec,  				  WM8993_DCS_TRIG_DAC_WR_0 |  				  WM8993_DCS_TRIG_DAC_WR_1); @@ -154,8 +164,9 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)  		reg_r = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_2)  			& WM8993_DCS_INTEG_CHAN_1_MASK;  		break; +	case 2:  	case 1: -		reg = snd_soc_read(codec, WM8993_DC_SERVO_3); +		reg = snd_soc_read(codec, dcs_reg);  		reg_r = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK)  			>> WM8993_DCS_DAC_WR_VAL_1_SHIFT;  		reg_l = reg & WM8993_DCS_DAC_WR_VAL_0_MASK; @@ -168,24 +179,25 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec)  	dev_dbg(codec->dev, "DCS input: %x %x\n", reg_l, reg_r);  	/* Apply correction to DC servo result */ -	if (hubs->dcs_codes) { -		dev_dbg(codec->dev, "Applying %d code DC servo correction\n", -			hubs->dcs_codes); +	if (hubs->dcs_codes_l || hubs->dcs_codes_r) { +		dev_dbg(codec->dev, +			"Applying %d/%d code DC servo correction\n", +			hubs->dcs_codes_l, hubs->dcs_codes_r);  		/* HPOUT1R */  		offset = reg_r; -		offset += hubs->dcs_codes; +		offset += hubs->dcs_codes_r;  		dcs_cfg = (u8)offset << WM8993_DCS_DAC_WR_VAL_1_SHIFT;  		/* HPOUT1L */  		offset = reg_l; -		offset += hubs->dcs_codes; +		offset += hubs->dcs_codes_l;  		dcs_cfg |= (u8)offset;  		dev_dbg(codec->dev, "DCS result: %x\n", dcs_cfg);  		/* Do it */ -		snd_soc_write(codec, WM8993_DC_SERVO_3, dcs_cfg); +		snd_soc_write(codec, dcs_reg, dcs_cfg);  		wait_for_dc_servo(codec,  				  WM8993_DCS_TRIG_DAC_WR_0 |  				  WM8993_DCS_TRIG_DAC_WR_1); @@ -217,7 +229,7 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol,  	/* If we're applying an offset correction then updating the  	 * callibration would be likely to introduce further offsets. */ -	if (hubs->dcs_codes || hubs->no_series_update) +	if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update)  		return ret;  	/* Only need to do this if the outputs are active */ @@ -699,6 +711,11 @@ static const struct snd_soc_dapm_route analogue_routes[] = {  	{ "IN1L PGA", "IN1LP Switch", "IN1LP" },  	{ "IN1L PGA", "IN1LN Switch", "IN1LN" }, +	{ "IN1L PGA", NULL, "VMID" }, +	{ "IN1R PGA", NULL, "VMID" }, +	{ "IN2L PGA", NULL, "VMID" }, +	{ "IN2R PGA", NULL, "VMID" }, +  	{ "IN1R PGA", "IN1RP Switch", "IN1RP" },  	{ "IN1R PGA", "IN1RN Switch", "IN1RN" }, @@ -716,12 +733,14 @@ static const struct snd_soc_dapm_route analogue_routes[] = {  	{ "MIXINL", NULL, "Direct Voice" },  	{ "MIXINL", NULL, "IN1LP" },  	{ "MIXINL", NULL, "Left Output Mixer" }, +	{ "MIXINL", NULL, "VMID" },  	{ "MIXINR", "IN1R Switch", "IN1R PGA" },  	{ "MIXINR", "IN2R Switch", "IN2R PGA" },  	{ "MIXINR", NULL, "Direct Voice" },  	{ "MIXINR", NULL, "IN1RP" },  	{ "MIXINR", NULL, "Right Output Mixer" }, +	{ "MIXINR", NULL, "VMID" },  	{ "ADCL", NULL, "MIXINL" },  	{ "ADCR", NULL, "MIXINR" }, @@ -752,6 +771,7 @@ static const struct snd_soc_dapm_route analogue_routes[] = {  	{ "Earpiece Mixer", "Left Output Switch", "Left Output PGA" },  	{ "Earpiece Mixer", "Right Output Switch", "Right Output PGA" }, +	{ "Earpiece Driver", NULL, "VMID" },  	{ "Earpiece Driver", NULL, "Earpiece Mixer" },  	{ "HPOUT2N", NULL, "Earpiece Driver" },  	{ "HPOUT2P", NULL, "Earpiece Driver" }, @@ -774,9 +794,11 @@ static const struct snd_soc_dapm_route analogue_routes[] = {  	{ "SPKR Boost", "SPKR Switch", "SPKR" },  	{ "SPKR Boost", "SPKL Switch", "SPKL" }, +	{ "SPKL Driver", NULL, "VMID" },  	{ "SPKL Driver", NULL, "SPKL Boost" },  	{ "SPKL Driver", NULL, "CLK_SYS" }, +	{ "SPKR Driver", NULL, "VMID" },  	{ "SPKR Driver", NULL, "SPKR Boost" },  	{ "SPKR Driver", NULL, "CLK_SYS" }, @@ -790,12 +812,18 @@ static const struct snd_soc_dapm_route analogue_routes[] = {  	{ "Headphone PGA", NULL, "Left Headphone Mux" },  	{ "Headphone PGA", NULL, "Right Headphone Mux" }, +	{ "Headphone PGA", NULL, "VMID" },  	{ "Headphone PGA", NULL, "CLK_SYS" },  	{ "Headphone PGA", NULL, "Headphone Supply" },  	{ "HPOUT1L", NULL, "Headphone PGA" },  	{ "HPOUT1R", NULL, "Headphone PGA" }, +	{ "LINEOUT1N Driver", NULL, "VMID" }, +	{ "LINEOUT1P Driver", NULL, "VMID" }, +	{ "LINEOUT2N Driver", NULL, "VMID" }, +	{ "LINEOUT2P Driver", NULL, "VMID" }, +  	{ "LINEOUT1N", NULL, "LINEOUT1N Driver" },  	{ "LINEOUT1P", NULL, "LINEOUT1P Driver" },  	{ "LINEOUT2N", NULL, "LINEOUT2N Driver" }, diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h index 676b1252ab9..c674c7a502a 100644 --- a/sound/soc/codecs/wm_hubs.h +++ b/sound/soc/codecs/wm_hubs.h @@ -23,7 +23,8 @@ extern const unsigned int wm_hubs_spkmix_tlv[];  /* This *must* be the first element of the codec->private_data struct */  struct wm_hubs_data { -	int dcs_codes; +	int dcs_codes_l; +	int dcs_codes_r;  	int dcs_readback_mode;  	int hp_startup_mode;  	int series_startup; diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 8566238db2a..7173df254a9 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -732,16 +732,19 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream,  		davinci_hw_param(dev, substream->stream);  	switch (params_format(params)) { +	case SNDRV_PCM_FORMAT_U8:  	case SNDRV_PCM_FORMAT_S8:  		dma_params->data_type = 1;  		word_length = DAVINCI_AUDIO_WORD_8;  		break; +	case SNDRV_PCM_FORMAT_U16_LE:  	case SNDRV_PCM_FORMAT_S16_LE:  		dma_params->data_type = 2;  		word_length = DAVINCI_AUDIO_WORD_16;  		break; +	case SNDRV_PCM_FORMAT_U32_LE:  	case SNDRV_PCM_FORMAT_S32_LE:  		dma_params->data_type = 4;  		word_length = DAVINCI_AUDIO_WORD_32; @@ -818,6 +821,13 @@ static struct snd_soc_dai_ops davinci_mcasp_dai_ops = {  }; +#define DAVINCI_MCASP_PCM_FMTS (SNDRV_PCM_FMTBIT_S8 | \ +				SNDRV_PCM_FMTBIT_U8 | \ +				SNDRV_PCM_FMTBIT_S16_LE | \ +				SNDRV_PCM_FMTBIT_U16_LE | \ +				SNDRV_PCM_FMTBIT_S32_LE | \ +				SNDRV_PCM_FMTBIT_U32_LE) +  static struct snd_soc_dai_driver davinci_mcasp_dai[] = {  	{  		.name		= "davinci-mcasp.0", @@ -825,17 +835,13 @@ static struct snd_soc_dai_driver davinci_mcasp_dai[] = {  			.channels_min	= 2,  			.channels_max 	= 2,  			.rates 		= DAVINCI_MCASP_RATES, -			.formats 	= SNDRV_PCM_FMTBIT_S8 | -						SNDRV_PCM_FMTBIT_S16_LE | -						SNDRV_PCM_FMTBIT_S32_LE, +			.formats	= DAVINCI_MCASP_PCM_FMTS,  		},  		.capture 	= {  			.channels_min 	= 2,  			.channels_max 	= 2,  			.rates 		= DAVINCI_MCASP_RATES, -			.formats	= SNDRV_PCM_FMTBIT_S8 | -						SNDRV_PCM_FMTBIT_S16_LE | -						SNDRV_PCM_FMTBIT_S32_LE, +			.formats	= DAVINCI_MCASP_PCM_FMTS,  		},  		.ops 		= &davinci_mcasp_dai_ops, @@ -846,7 +852,7 @@ static struct snd_soc_dai_driver davinci_mcasp_dai[] = {  			.channels_min	= 1,  			.channels_max	= 384,  			.rates		= DAVINCI_MCASP_RATES, -			.formats	= SNDRV_PCM_FMTBIT_S16_LE, +			.formats	= DAVINCI_MCASP_PCM_FMTS,  		},  		.ops 		= &davinci_mcasp_dai_ops,  	}, diff --git a/sound/soc/fsl/fsl_dma.c b/sound/soc/fsl/fsl_dma.c index cb50598338e..ef15402a3bc 100644 --- a/sound/soc/fsl/fsl_dma.c +++ b/sound/soc/fsl/fsl_dma.c @@ -297,7 +297,6 @@ static irqreturn_t fsl_dma_isr(int irq, void *dev_id)  static int fsl_dma_new(struct snd_soc_pcm_runtime *rtd)  {  	struct snd_card *card = rtd->card->snd_card; -	struct snd_soc_dai *dai = rtd->cpu_dai;  	struct snd_pcm *pcm = rtd->pcm;  	static u64 fsl_dma_dmamask = DMA_BIT_MASK(36);  	int ret; diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index d48afea5d93..06ac2b92faf 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -289,16 +289,6 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,  	 */  	if (!ssi_private->playback && !ssi_private->capture) {  		struct ccsr_ssi __iomem *ssi = ssi_private->ssi; -		int ret; - -		/* The 'name' should not have any slashes in it. */ -		ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0, -				  ssi_private->name, ssi_private); -		if (ret < 0) { -			dev_err(substream->pcm->card->dev, -				"could not claim irq %u\n", ssi_private->irq); -			return ret; -		}  		/*  		 * Section 16.5 of the MPC8610 reference manual says that the @@ -522,15 +512,12 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,  	ssi_private->second_stream = NULL;  	/* -	 * If this is the last active substream, disable the SSI and release -	 * the IRQ. +	 * If this is the last active substream, disable the SSI.  	 */  	if (!ssi_private->playback && !ssi_private->capture) {  		struct ccsr_ssi __iomem *ssi = ssi_private->ssi;  		clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); - -		free_irq(ssi_private->irq, ssi_private);  	}  } @@ -675,17 +662,30 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)  	ret = of_address_to_resource(np, 0, &res);  	if (ret) {  		dev_err(&pdev->dev, "could not determine device resources\n"); -		kfree(ssi_private); -		return ret; +		goto error_kmalloc;  	}  	ssi_private->ssi = of_iomap(np, 0);  	if (!ssi_private->ssi) {  		dev_err(&pdev->dev, "could not map device resources\n"); -		kfree(ssi_private); -		return -ENOMEM; +		ret = -ENOMEM; +		goto error_kmalloc;  	}  	ssi_private->ssi_phys = res.start; +  	ssi_private->irq = irq_of_parse_and_map(np, 0); +	if (ssi_private->irq == NO_IRQ) { +		dev_err(&pdev->dev, "no irq for node %s\n", np->full_name); +		ret = -ENXIO; +		goto error_iomap; +	} + +	/* The 'name' should not have any slashes in it. */ +	ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0, ssi_private->name, +			  ssi_private); +	if (ret < 0) { +		dev_err(&pdev->dev, "could not claim irq %u\n", ssi_private->irq); +		goto error_irqmap; +	}  	/* Are the RX and the TX clocks locked? */  	if (of_find_property(np, "fsl,ssi-asynchronous", NULL)) @@ -711,7 +711,7 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)  	if (ret) {  		dev_err(&pdev->dev, "could not create sysfs %s file\n",  			ssi_private->dev_attr.attr.name); -		goto error; +		goto error_irq;  	}  	/* Register with ASoC */ @@ -720,7 +720,7 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)  	ret = snd_soc_register_dai(&pdev->dev, &ssi_private->cpu_dai_drv);  	if (ret) {  		dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); -		goto error; +		goto error_dev;  	}  	/* Trigger the machine driver's probe function.  The platform driver @@ -741,18 +741,28 @@ static int __devinit fsl_ssi_probe(struct platform_device *pdev)  	if (IS_ERR(ssi_private->pdev)) {  		ret = PTR_ERR(ssi_private->pdev);  		dev_err(&pdev->dev, "failed to register platform: %d\n", ret); -		goto error; +		goto error_dai;  	}  	return 0; -error: +error_dai:  	snd_soc_unregister_dai(&pdev->dev); + +error_dev:  	dev_set_drvdata(&pdev->dev, NULL); -	if (dev_attr) -		device_remove_file(&pdev->dev, dev_attr); +	device_remove_file(&pdev->dev, dev_attr); + +error_irq: +	free_irq(ssi_private->irq, ssi_private); + +error_irqmap:  	irq_dispose_mapping(ssi_private->irq); + +error_iomap:  	iounmap(ssi_private->ssi); + +error_kmalloc:  	kfree(ssi_private);  	return ret; @@ -766,6 +776,9 @@ static int fsl_ssi_remove(struct platform_device *pdev)  	snd_soc_unregister_dai(&pdev->dev);  	device_remove_file(&pdev->dev, &ssi_private->dev_attr); +	free_irq(ssi_private->irq, ssi_private); +	irq_dispose_mapping(ssi_private->irq); +  	kfree(ssi_private);  	dev_set_drvdata(&pdev->dev, NULL); diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c index 309c59e6fb6..ac790e87e23 100644 --- a/sound/soc/imx/imx-pcm-fiq.c +++ b/sound/soc/imx/imx-pcm-fiq.c @@ -243,23 +243,22 @@ static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd)  	struct snd_card *card = rtd->card->snd_card;  	struct snd_soc_dai *dai = rtd->cpu_dai;  	struct snd_pcm *pcm = rtd->pcm; +	struct snd_pcm_substream *substream;  	int ret;  	ret = imx_pcm_new(rtd);  	if (ret)  		return ret; -	if (dai->driver->playback.channels_min) { -		struct snd_pcm_substream *substream = -			pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; +	substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; +	if (substream) {  		struct snd_dma_buffer *buf = &substream->dma_buffer;  		imx_ssi_fiq_tx_buffer = (unsigned long)buf->area;  	} -	if (dai->driver->capture.channels_min) { -		struct snd_pcm_substream *substream = -			pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; +	substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; +	if (substream) {  		struct snd_dma_buffer *buf = &substream->dma_buffer;  		imx_ssi_fiq_rx_buffer = (unsigned long)buf->area; diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c index 10a8e278375..4297cb6af42 100644 --- a/sound/soc/imx/imx-ssi.c +++ b/sound/soc/imx/imx-ssi.c @@ -357,8 +357,8 @@ int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,  	struct snd_pcm_runtime *runtime = substream->runtime;  	int ret; -	ret = dma_mmap_coherent(NULL, vma, runtime->dma_area, -			runtime->dma_addr, runtime->dma_bytes); +	ret = dma_mmap_writecombine(substream->pcm->card->dev, vma, +		runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);  	pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret,  			runtime->dma_area, @@ -399,14 +399,14 @@ int imx_pcm_new(struct snd_soc_pcm_runtime *rtd)  		card->dev->dma_mask = &imx_pcm_dmamask;  	if (!card->dev->coherent_dma_mask)  		card->dev->coherent_dma_mask = DMA_BIT_MASK(32); -	if (dai->driver->playback.channels_min) { +	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {  		ret = imx_pcm_preallocate_dma_buffer(pcm,  			SNDRV_PCM_STREAM_PLAYBACK);  		if (ret)  			goto out;  	} -	if (dai->driver->capture.channels_min) { +	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {  		ret = imx_pcm_preallocate_dma_buffer(pcm,  			SNDRV_PCM_STREAM_CAPTURE);  		if (ret) diff --git a/sound/soc/mxs/Kconfig b/sound/soc/mxs/Kconfig new file mode 100644 index 00000000000..e4ba8d5f25f --- /dev/null +++ b/sound/soc/mxs/Kconfig @@ -0,0 +1,20 @@ +menuconfig SND_MXS_SOC +	tristate "SoC Audio for Freescale MXS CPUs" +	depends on ARCH_MXS +	select SND_PCM +	help +	  Say Y or M if you want to add support for codecs attached to +	  the MXS SAIF interface. + + +if SND_MXS_SOC + +config SND_SOC_MXS_SGTL5000 +	tristate "SoC Audio support for i.MX boards with sgtl5000" +	depends on I2C +	select SND_SOC_SGTL5000 +	help +	  Say Y if you want to add support for SoC audio on an MXS board with +	  a sgtl5000 codec. + +endif	# SND_MXS_SOC diff --git a/sound/soc/mxs/Makefile b/sound/soc/mxs/Makefile new file mode 100644 index 00000000000..565b5b51e8b --- /dev/null +++ b/sound/soc/mxs/Makefile @@ -0,0 +1,10 @@ +# MXS Platform Support +snd-soc-mxs-objs := mxs-saif.o +snd-soc-mxs-pcm-objs := mxs-pcm.o + +obj-$(CONFIG_SND_MXS_SOC) += snd-soc-mxs.o snd-soc-mxs-pcm.o + +# i.MX Machine Support +snd-soc-mxs-sgtl5000-objs := mxs-sgtl5000.o + +obj-$(CONFIG_SND_SOC_MXS_SGTL5000) += snd-soc-mxs-sgtl5000.o diff --git a/sound/soc/mxs/mxs-pcm.c b/sound/soc/mxs/mxs-pcm.c new file mode 100644 index 00000000000..dea5aa4aa64 --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * Based on sound/soc/imx/imx-pcm-dma-mx2.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dmaengine.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma.h> +#include "mxs-pcm.h" + +static struct snd_pcm_hardware snd_mxs_hardware = { +	.info			= SNDRV_PCM_INFO_MMAP | +				  SNDRV_PCM_INFO_MMAP_VALID | +				  SNDRV_PCM_INFO_PAUSE | +				  SNDRV_PCM_INFO_RESUME | +				  SNDRV_PCM_INFO_INTERLEAVED, +	.formats		= SNDRV_PCM_FMTBIT_S16_LE | +				  SNDRV_PCM_FMTBIT_S20_3LE | +				  SNDRV_PCM_FMTBIT_S24_LE, +	.channels_min		= 2, +	.channels_max		= 2, +	.period_bytes_min	= 32, +	.period_bytes_max	= 8192, +	.periods_min		= 1, +	.periods_max		= 52, +	.buffer_bytes_max	= 64 * 1024, +	.fifo_size		= 32, + +}; + +static void audio_dma_irq(void *data) +{ +	struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data; +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + +	iprtd->offset += iprtd->period_bytes; +	iprtd->offset %= iprtd->period_bytes * iprtd->periods; +	snd_pcm_period_elapsed(substream); +} + +static bool filter(struct dma_chan *chan, void *param) +{ +	struct mxs_pcm_runtime_data *iprtd = param; +	struct mxs_pcm_dma_params *dma_params = iprtd->dma_params; + +	if (!mxs_dma_is_apbx(chan)) +		return false; + +	if (chan->chan_id != dma_params->chan_num) +		return false; + +	chan->private = &iprtd->dma_data; + +	return true; +} + +static int mxs_dma_alloc(struct snd_pcm_substream *substream, +				struct snd_pcm_hw_params *params) +{ +	struct snd_soc_pcm_runtime *rtd = substream->private_data; +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct mxs_pcm_runtime_data *iprtd = runtime->private_data; +	dma_cap_mask_t mask; + +	iprtd->dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + +	dma_cap_zero(mask); +	dma_cap_set(DMA_SLAVE, mask); +	iprtd->dma_data.chan_irq = iprtd->dma_params->chan_irq; +	iprtd->dma_chan = dma_request_channel(mask, filter, iprtd); +	if (!iprtd->dma_chan) +		return -EINVAL; + +	return 0; +} + +static int snd_mxs_pcm_hw_params(struct snd_pcm_substream *substream, +				struct snd_pcm_hw_params *params) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct mxs_pcm_runtime_data *iprtd = runtime->private_data; +	unsigned long dma_addr; +	struct dma_chan *chan; +	int ret; + +	ret = mxs_dma_alloc(substream, params); +	if (ret) +		return ret; +	chan = iprtd->dma_chan; + +	iprtd->size = params_buffer_bytes(params); +	iprtd->periods = params_periods(params); +	iprtd->period_bytes = params_period_bytes(params); +	iprtd->offset = 0; +	iprtd->period_time = HZ / (params_rate(params) / +			params_period_size(params)); + +	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + +	dma_addr = runtime->dma_addr; + +	iprtd->buf = substream->dma_buffer.area; + +	iprtd->desc = chan->device->device_prep_dma_cyclic(chan, dma_addr, +			iprtd->period_bytes * iprtd->periods, +			iprtd->period_bytes, +			substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? +			DMA_TO_DEVICE : DMA_FROM_DEVICE); +	if (!iprtd->desc) { +		dev_err(&chan->dev->device, "cannot prepare slave dma\n"); +		return -EINVAL; +	} + +	iprtd->desc->callback = audio_dma_irq; +	iprtd->desc->callback_param = substream; + +	return 0; +} + +static int snd_mxs_pcm_hw_free(struct snd_pcm_substream *substream) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + +	if (iprtd->dma_chan) { +		dma_release_channel(iprtd->dma_chan); +		iprtd->dma_chan = NULL; +	} + +	return 0; +} + +static int snd_mxs_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +	case SNDRV_PCM_TRIGGER_RESUME: +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +		dmaengine_submit(iprtd->desc); + +		break; +	case SNDRV_PCM_TRIGGER_STOP: +	case SNDRV_PCM_TRIGGER_SUSPEND: +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +		dmaengine_terminate_all(iprtd->dma_chan); + +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static snd_pcm_uframes_t snd_mxs_pcm_pointer( +		struct snd_pcm_substream *substream) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + +	return bytes_to_frames(substream->runtime, iprtd->offset); +} + +static int snd_mxs_open(struct snd_pcm_substream *substream) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct mxs_pcm_runtime_data *iprtd; +	int ret; + +	iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); +	if (iprtd == NULL) +		return -ENOMEM; +	runtime->private_data = iprtd; + +	ret = snd_pcm_hw_constraint_integer(substream->runtime, +			SNDRV_PCM_HW_PARAM_PERIODS); +	if (ret < 0) { +		kfree(iprtd); +		return ret; +	} + +	snd_soc_set_runtime_hwparams(substream, &snd_mxs_hardware); + +	return 0; +} + +static int snd_mxs_close(struct snd_pcm_substream *substream) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct mxs_pcm_runtime_data *iprtd = runtime->private_data; + +	kfree(iprtd); + +	return 0; +} + +static int snd_mxs_pcm_mmap(struct snd_pcm_substream *substream, +		struct vm_area_struct *vma) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; + +	return dma_mmap_writecombine(substream->pcm->card->dev, vma, +					runtime->dma_area, +					runtime->dma_addr, +					runtime->dma_bytes); +} + +static struct snd_pcm_ops mxs_pcm_ops = { +	.open		= snd_mxs_open, +	.close		= snd_mxs_close, +	.ioctl		= snd_pcm_lib_ioctl, +	.hw_params	= snd_mxs_pcm_hw_params, +	.hw_free	= snd_mxs_pcm_hw_free, +	.trigger	= snd_mxs_pcm_trigger, +	.pointer	= snd_mxs_pcm_pointer, +	.mmap		= snd_mxs_pcm_mmap, +}; + +static int mxs_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ +	struct snd_pcm_substream *substream = pcm->streams[stream].substream; +	struct snd_dma_buffer *buf = &substream->dma_buffer; +	size_t size = snd_mxs_hardware.buffer_bytes_max; + +	buf->dev.type = SNDRV_DMA_TYPE_DEV; +	buf->dev.dev = pcm->card->dev; +	buf->private_data = NULL; +	buf->area = dma_alloc_writecombine(pcm->card->dev, size, +					   &buf->addr, GFP_KERNEL); +	if (!buf->area) +		return -ENOMEM; +	buf->bytes = size; + +	return 0; +} + +static u64 mxs_pcm_dmamask = DMA_BIT_MASK(32); +static int mxs_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ +	struct snd_card *card = rtd->card->snd_card; +	struct snd_pcm *pcm = rtd->pcm; +	int ret = 0; + +	if (!card->dev->dma_mask) +		card->dev->dma_mask = &mxs_pcm_dmamask; +	if (!card->dev->coherent_dma_mask) +		card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + +	if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { +		ret = mxs_pcm_preallocate_dma_buffer(pcm, +			SNDRV_PCM_STREAM_PLAYBACK); +		if (ret) +			goto out; +	} + +	if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { +		ret = mxs_pcm_preallocate_dma_buffer(pcm, +			SNDRV_PCM_STREAM_CAPTURE); +		if (ret) +			goto out; +	} + +out: +	return ret; +} + +static void mxs_pcm_free(struct snd_pcm *pcm) +{ +	struct snd_pcm_substream *substream; +	struct snd_dma_buffer *buf; +	int stream; + +	for (stream = 0; stream < 2; stream++) { +		substream = pcm->streams[stream].substream; +		if (!substream) +			continue; + +		buf = &substream->dma_buffer; +		if (!buf->area) +			continue; + +		dma_free_writecombine(pcm->card->dev, buf->bytes, +				      buf->area, buf->addr); +		buf->area = NULL; +	} +} + +static struct snd_soc_platform_driver mxs_soc_platform = { +	.ops		= &mxs_pcm_ops, +	.pcm_new	= mxs_pcm_new, +	.pcm_free	= mxs_pcm_free, +}; + +static int __devinit mxs_soc_platform_probe(struct platform_device *pdev) +{ +	return snd_soc_register_platform(&pdev->dev, &mxs_soc_platform); +} + +static int __devexit mxs_soc_platform_remove(struct platform_device *pdev) +{ +	snd_soc_unregister_platform(&pdev->dev); + +	return 0; +} + +static struct platform_driver mxs_pcm_driver = { +	.driver = { +		.name = "mxs-pcm-audio", +		.owner = THIS_MODULE, +	}, +	.probe = mxs_soc_platform_probe, +	.remove = __devexit_p(mxs_soc_platform_remove), +}; + +static int __init snd_mxs_pcm_init(void) +{ +	return platform_driver_register(&mxs_pcm_driver); +} +module_init(snd_mxs_pcm_init); + +static void __exit snd_mxs_pcm_exit(void) +{ +	platform_driver_unregister(&mxs_pcm_driver); +} +module_exit(snd_mxs_pcm_exit); diff --git a/sound/soc/mxs/mxs-pcm.h b/sound/soc/mxs/mxs-pcm.h new file mode 100644 index 00000000000..f55ac4f7a76 --- /dev/null +++ b/sound/soc/mxs/mxs-pcm.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _MXS_PCM_H +#define _MXS_PCM_H + +#include <mach/dma.h> + +struct mxs_pcm_dma_params { +	int chan_irq; +	int chan_num; +}; + +struct mxs_pcm_runtime_data { +	int period_bytes; +	int periods; +	int dma; +	unsigned long offset; +	unsigned long size; +	void *buf; +	int period_time; +	struct dma_async_tx_descriptor *desc; +	struct dma_chan *dma_chan; +	struct mxs_dma_data dma_data; +	struct mxs_pcm_dma_params *dma_params; +}; + +#endif diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c new file mode 100644 index 00000000000..af5734f6dab --- /dev/null +++ b/sound/soc/mxs/mxs-saif.c @@ -0,0 +1,680 @@ +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/mxs.h> + +#include "mxs-saif.h" + +static struct mxs_saif *mxs_saif[2]; + +static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, +			int clk_id, unsigned int freq, int dir) +{ +	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + +	switch (clk_id) { +	case MXS_SAIF_MCLK: +		saif->mclk = freq; +		break; +	default: +		return -EINVAL; +	} +	return 0; +} + +/* + * Set SAIF clock and MCLK + */ +static int mxs_saif_set_clk(struct mxs_saif *saif, +				  unsigned int mclk, +				  unsigned int rate) +{ +	u32 scr; +	int ret; + +	scr = __raw_readl(saif->base + SAIF_CTRL); +	scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE; +	scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; + +	/* +	 * Set SAIF clock +	 * +	 * The SAIF clock should be either 384*fs or 512*fs. +	 * If MCLK is used, the SAIF clk ratio need to match mclk ratio. +	 *  For 32x mclk, set saif clk as 512*fs. +	 *  For 48x mclk, set saif clk as 384*fs. +	 * +	 * If MCLK is not used, we just set saif clk to 512*fs. +	 */ +	if (saif->mclk_in_use) { +		if (mclk % 32 == 0) { +			scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; +			ret = clk_set_rate(saif->clk, 512 * rate); +		} else if (mclk % 48 == 0) { +			scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE; +			ret = clk_set_rate(saif->clk, 384 * rate); +		} else { +			/* SAIF MCLK should be either 32x or 48x */ +			return -EINVAL; +		} +	} else { +		ret = clk_set_rate(saif->clk, 512 * rate); +		scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE; +	} + +	if (ret) +		return ret; + +	if (!saif->mclk_in_use) { +		__raw_writel(scr, saif->base + SAIF_CTRL); +		return 0; +	} + +	/* +	 * Program the over-sample rate for MCLK output +	 * +	 * The available MCLK range is 32x, 48x... 512x. The rate +	 * could be from 8kHz to 192kH. +	 */ +	switch (mclk / rate) { +	case 32: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(4); +		break; +	case 64: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3); +		break; +	case 128: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2); +		break; +	case 256: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1); +		break; +	case 512: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0); +		break; +	case 48: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3); +		break; +	case 96: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2); +		break; +	case 192: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1); +		break; +	case 384: +		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0); +		break; +	default: +		return -EINVAL; +	} + +	__raw_writel(scr, saif->base + SAIF_CTRL); + +	return 0; +} + +/* + * Put and disable MCLK. + */ +int mxs_saif_put_mclk(unsigned int saif_id) +{ +	struct mxs_saif *saif = mxs_saif[saif_id]; +	u32 stat; + +	if (!saif) +		return -EINVAL; + +	stat = __raw_readl(saif->base + SAIF_STAT); +	if (stat & BM_SAIF_STAT_BUSY) { +		dev_err(saif->dev, "error: busy\n"); +		return -EBUSY; +	} + +	clk_disable(saif->clk); + +	/* disable MCLK output */ +	__raw_writel(BM_SAIF_CTRL_CLKGATE, +		saif->base + SAIF_CTRL + MXS_SET_ADDR); +	__raw_writel(BM_SAIF_CTRL_RUN, +		saif->base + SAIF_CTRL + MXS_CLR_ADDR); + +	saif->mclk_in_use = 0; +	return 0; +} + +/* + * Get MCLK and set clock rate, then enable it + * + * This interface is used for codecs who are using MCLK provided + * by saif. + */ +int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk, +					unsigned int rate) +{ +	struct mxs_saif *saif = mxs_saif[saif_id]; +	u32 stat; +	int ret; + +	if (!saif) +		return -EINVAL; + +	/* Clear Reset */ +	__raw_writel(BM_SAIF_CTRL_SFTRST, +		saif->base + SAIF_CTRL + MXS_CLR_ADDR); + +	/* FIXME: need clear clk gate for register r/w */ +	__raw_writel(BM_SAIF_CTRL_CLKGATE, +		saif->base + SAIF_CTRL + MXS_CLR_ADDR); + +	stat = __raw_readl(saif->base + SAIF_STAT); +	if (stat & BM_SAIF_STAT_BUSY) { +		dev_err(saif->dev, "error: busy\n"); +		return -EBUSY; +	} + +	saif->mclk_in_use = 1; +	ret = mxs_saif_set_clk(saif, mclk, rate); +	if (ret) +		return ret; + +	ret = clk_enable(saif->clk); +	if (ret) +		return ret; + +	/* enable MCLK output */ +	__raw_writel(BM_SAIF_CTRL_RUN, +		saif->base + SAIF_CTRL + MXS_SET_ADDR); + +	return 0; +} + +/* + * SAIF DAI format configuration. + * Should only be called when port is inactive. + */ +static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ +	u32 scr, stat; +	u32 scr0; +	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + +	stat = __raw_readl(saif->base + SAIF_STAT); +	if (stat & BM_SAIF_STAT_BUSY) { +		dev_err(cpu_dai->dev, "error: busy\n"); +		return -EBUSY; +	} + +	scr0 = __raw_readl(saif->base + SAIF_CTRL); +	scr0 = scr0 & ~BM_SAIF_CTRL_BITCLK_EDGE & ~BM_SAIF_CTRL_LRCLK_POLARITY \ +		& ~BM_SAIF_CTRL_JUSTIFY & ~BM_SAIF_CTRL_DELAY; +	scr = 0; + +	/* DAI mode */ +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { +	case SND_SOC_DAIFMT_I2S: +		/* data frame low 1clk before data */ +		scr |= BM_SAIF_CTRL_DELAY; +		scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; +		break; +	case SND_SOC_DAIFMT_LEFT_J: +		/* data frame high with data */ +		scr &= ~BM_SAIF_CTRL_DELAY; +		scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; +		scr &= ~BM_SAIF_CTRL_JUSTIFY; +		break; +	default: +		return -EINVAL; +	} + +	/* DAI clock inversion */ +	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { +	case SND_SOC_DAIFMT_IB_IF: +		scr |= BM_SAIF_CTRL_BITCLK_EDGE; +		scr |= BM_SAIF_CTRL_LRCLK_POLARITY; +		break; +	case SND_SOC_DAIFMT_IB_NF: +		scr |= BM_SAIF_CTRL_BITCLK_EDGE; +		scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; +		break; +	case SND_SOC_DAIFMT_NB_IF: +		scr &= ~BM_SAIF_CTRL_BITCLK_EDGE; +		scr |= BM_SAIF_CTRL_LRCLK_POLARITY; +		break; +	case SND_SOC_DAIFMT_NB_NF: +		scr &= ~BM_SAIF_CTRL_BITCLK_EDGE; +		scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY; +		break; +	} + +	/* +	 * Note: We simply just support master mode since SAIF TX can only +	 * work as master. +	 */ +	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { +	case SND_SOC_DAIFMT_CBS_CFS: +		scr &= ~BM_SAIF_CTRL_SLAVE_MODE; +		__raw_writel(scr | scr0, saif->base + SAIF_CTRL); +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int mxs_saif_startup(struct snd_pcm_substream *substream, +			   struct snd_soc_dai *cpu_dai) +{ +	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); +	snd_soc_dai_set_dma_data(cpu_dai, substream, &saif->dma_param); + +	/* clear error status to 0 for each re-open */ +	saif->fifo_underrun = 0; +	saif->fifo_overrun = 0; + +	/* Clear Reset for normal operations */ +	__raw_writel(BM_SAIF_CTRL_SFTRST, +		saif->base + SAIF_CTRL + MXS_CLR_ADDR); + +	/* clear clock gate */ +	__raw_writel(BM_SAIF_CTRL_CLKGATE, +		saif->base + SAIF_CTRL + MXS_CLR_ADDR); + +	return 0; +} + +/* + * Should only be called when port is inactive. + * although can be called multiple times by upper layers. + */ +static int mxs_saif_hw_params(struct snd_pcm_substream *substream, +			     struct snd_pcm_hw_params *params, +			     struct snd_soc_dai *cpu_dai) +{ +	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); +	u32 scr, stat; +	int ret; + +	/* mclk should already be set */ +	if (!saif->mclk && saif->mclk_in_use) { +		dev_err(cpu_dai->dev, "set mclk first\n"); +		return -EINVAL; +	} + +	stat = __raw_readl(saif->base + SAIF_STAT); +	if (stat & BM_SAIF_STAT_BUSY) { +		dev_err(cpu_dai->dev, "error: busy\n"); +		return -EBUSY; +	} + +	/* +	 * Set saif clk based on sample rate. +	 * If mclk is used, we also set mclk, if not, saif->mclk is +	 * default 0, means not used. +	 */ +	ret = mxs_saif_set_clk(saif, saif->mclk, params_rate(params)); +	if (ret) { +		dev_err(cpu_dai->dev, "unable to get proper clk\n"); +		return ret; +	} + +	scr = __raw_readl(saif->base + SAIF_CTRL); + +	scr &= ~BM_SAIF_CTRL_WORD_LENGTH; +	scr &= ~BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; +	switch (params_format(params)) { +	case SNDRV_PCM_FORMAT_S16_LE: +		scr |= BF_SAIF_CTRL_WORD_LENGTH(0); +		break; +	case SNDRV_PCM_FORMAT_S20_3LE: +		scr |= BF_SAIF_CTRL_WORD_LENGTH(4); +		scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; +		break; +	case SNDRV_PCM_FORMAT_S24_LE: +		scr |= BF_SAIF_CTRL_WORD_LENGTH(8); +		scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE; +		break; +	default: +		return -EINVAL; +	} + +	/* Tx/Rx config */ +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +		/* enable TX mode */ +		scr &= ~BM_SAIF_CTRL_READ_MODE; +	} else { +		/* enable RX mode */ +		scr |= BM_SAIF_CTRL_READ_MODE; +	} + +	__raw_writel(scr, saif->base + SAIF_CTRL); +	return 0; +} + +static int mxs_saif_prepare(struct snd_pcm_substream *substream, +			   struct snd_soc_dai *cpu_dai) +{ +	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + +	/* enable FIFO error irqs */ +	__raw_writel(BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN, +		saif->base + SAIF_CTRL + MXS_SET_ADDR); + +	return 0; +} + +static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd, +				struct snd_soc_dai *cpu_dai) +{ +	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai); + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +	case SNDRV_PCM_TRIGGER_RESUME: +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +		dev_dbg(cpu_dai->dev, "start\n"); + +		clk_enable(saif->clk); +		if (!saif->mclk_in_use) +			__raw_writel(BM_SAIF_CTRL_RUN, +				saif->base + SAIF_CTRL + MXS_SET_ADDR); + +		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +			/* +			 * write a data to saif data register to trigger +			 * the transfer +			 */ +			__raw_writel(0, saif->base + SAIF_DATA); +		} else { +			/* +			 * read a data from saif data register to trigger +			 * the receive +			 */ +			__raw_readl(saif->base + SAIF_DATA); +		} + +		dev_dbg(cpu_dai->dev, "CTRL 0x%x STAT 0x%x\n", +			__raw_readl(saif->base + SAIF_CTRL), +			__raw_readl(saif->base + SAIF_STAT)); + +		break; +	case SNDRV_PCM_TRIGGER_SUSPEND: +	case SNDRV_PCM_TRIGGER_STOP: +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +		dev_dbg(cpu_dai->dev, "stop\n"); + +		clk_disable(saif->clk); +		if (!saif->mclk_in_use) +			__raw_writel(BM_SAIF_CTRL_RUN, +				saif->base + SAIF_CTRL + MXS_CLR_ADDR); + +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +#define MXS_SAIF_RATES		SNDRV_PCM_RATE_8000_192000 +#define MXS_SAIF_FORMATS \ +	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ +	SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops mxs_saif_dai_ops = { +	.startup = mxs_saif_startup, +	.trigger = mxs_saif_trigger, +	.prepare = mxs_saif_prepare, +	.hw_params = mxs_saif_hw_params, +	.set_sysclk = mxs_saif_set_dai_sysclk, +	.set_fmt = mxs_saif_set_dai_fmt, +}; + +static int mxs_saif_dai_probe(struct snd_soc_dai *dai) +{ +	struct mxs_saif *saif = dev_get_drvdata(dai->dev); + +	snd_soc_dai_set_drvdata(dai, saif); + +	return 0; +} + +static struct snd_soc_dai_driver mxs_saif_dai = { +	.name = "mxs-saif", +	.probe = mxs_saif_dai_probe, +	.playback = { +		.channels_min = 2, +		.channels_max = 2, +		.rates = MXS_SAIF_RATES, +		.formats = MXS_SAIF_FORMATS, +	}, +	.capture = { +		.channels_min = 2, +		.channels_max = 2, +		.rates = MXS_SAIF_RATES, +		.formats = MXS_SAIF_FORMATS, +	}, +	.ops = &mxs_saif_dai_ops, +}; + +static irqreturn_t mxs_saif_irq(int irq, void *dev_id) +{ +	struct mxs_saif *saif = dev_id; +	unsigned int stat; + +	stat = __raw_readl(saif->base + SAIF_STAT); +	if (!(stat & (BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ | +			BM_SAIF_STAT_FIFO_OVERFLOW_IRQ))) +		return IRQ_NONE; + +	if (stat & BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ) { +		dev_dbg(saif->dev, "underrun!!! %d\n", ++saif->fifo_underrun); +		__raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ, +				saif->base + SAIF_STAT + MXS_CLR_ADDR); +	} + +	if (stat & BM_SAIF_STAT_FIFO_OVERFLOW_IRQ) { +		dev_dbg(saif->dev, "overrun!!! %d\n", ++saif->fifo_overrun); +		__raw_writel(BM_SAIF_STAT_FIFO_OVERFLOW_IRQ, +				saif->base + SAIF_STAT + MXS_CLR_ADDR); +	} + +	dev_dbg(saif->dev, "SAIF_CTRL %x SAIF_STAT %x\n", +	       __raw_readl(saif->base + SAIF_CTRL), +	       __raw_readl(saif->base + SAIF_STAT)); + +	return IRQ_HANDLED; +} + +static int mxs_saif_probe(struct platform_device *pdev) +{ +	struct resource *res; +	struct mxs_saif *saif; +	int ret = 0; + +	if (pdev->id >= ARRAY_SIZE(mxs_saif)) +		return -EINVAL; + +	saif = kzalloc(sizeof(*saif), GFP_KERNEL); +	if (!saif) +		return -ENOMEM; + +	mxs_saif[pdev->id] = saif; + +	saif->clk = clk_get(&pdev->dev, NULL); +	if (IS_ERR(saif->clk)) { +		ret = PTR_ERR(saif->clk); +		dev_err(&pdev->dev, "Cannot get the clock: %d\n", +			ret); +		goto failed_clk; +	} + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		ret = -ENODEV; +		dev_err(&pdev->dev, "failed to get io resource: %d\n", +			ret); +		goto failed_get_resource; +	} + +	if (!request_mem_region(res->start, resource_size(res), "mxs-saif")) { +		dev_err(&pdev->dev, "request_mem_region failed\n"); +		ret = -EBUSY; +		goto failed_get_resource; +	} + +	saif->base = ioremap(res->start, resource_size(res)); +	if (!saif->base) { +		dev_err(&pdev->dev, "ioremap failed\n"); +		ret = -ENODEV; +		goto failed_ioremap; +	} + +	res = platform_get_resource(pdev, IORESOURCE_DMA, 0); +	if (!res) { +		ret = -ENODEV; +		dev_err(&pdev->dev, "failed to get dma resource: %d\n", +			ret); +		goto failed_ioremap; +	} +	saif->dma_param.chan_num = res->start; + +	saif->irq = platform_get_irq(pdev, 0); +	if (saif->irq < 0) { +		ret = saif->irq; +		dev_err(&pdev->dev, "failed to get irq resource: %d\n", +			ret); +		goto failed_get_irq1; +	} + +	saif->dev = &pdev->dev; +	ret = request_irq(saif->irq, mxs_saif_irq, 0, "mxs-saif", saif); +	if (ret) { +		dev_err(&pdev->dev, "failed to request irq\n"); +		goto failed_get_irq1; +	} + +	saif->dma_param.chan_irq = platform_get_irq(pdev, 1); +	if (saif->dma_param.chan_irq < 0) { +		ret = saif->dma_param.chan_irq; +		dev_err(&pdev->dev, "failed to get dma irq resource: %d\n", +			ret); +		goto failed_get_irq2; +	} + +	platform_set_drvdata(pdev, saif); + +	ret = snd_soc_register_dai(&pdev->dev, &mxs_saif_dai); +	if (ret) { +		dev_err(&pdev->dev, "register DAI failed\n"); +		goto failed_register; +	} + +	saif->soc_platform_pdev = platform_device_alloc( +					"mxs-pcm-audio", pdev->id); +	if (!saif->soc_platform_pdev) { +		ret = -ENOMEM; +		goto failed_pdev_alloc; +	} + +	platform_set_drvdata(saif->soc_platform_pdev, saif); +	ret = platform_device_add(saif->soc_platform_pdev); +	if (ret) { +		dev_err(&pdev->dev, "failed to add soc platform device\n"); +		goto failed_pdev_add; +	} + +	return 0; + +failed_pdev_add: +	platform_device_put(saif->soc_platform_pdev); +failed_pdev_alloc: +	snd_soc_unregister_dai(&pdev->dev); +failed_register: +failed_get_irq2: +	free_irq(saif->irq, saif); +failed_get_irq1: +	iounmap(saif->base); +failed_ioremap: +	release_mem_region(res->start, resource_size(res)); +failed_get_resource: +	clk_put(saif->clk); +failed_clk: +	kfree(saif); + +	return ret; +} + +static int __devexit mxs_saif_remove(struct platform_device *pdev) +{ +	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	struct mxs_saif *saif = platform_get_drvdata(pdev); + +	platform_device_unregister(saif->soc_platform_pdev); + +	snd_soc_unregister_dai(&pdev->dev); + +	iounmap(saif->base); +	release_mem_region(res->start, resource_size(res)); +	free_irq(saif->irq, saif); + +	clk_put(saif->clk); +	kfree(saif); + +	return 0; +} + +static struct platform_driver mxs_saif_driver = { +	.probe = mxs_saif_probe, +	.remove = __devexit_p(mxs_saif_remove), + +	.driver = { +		.name = "mxs-saif", +		.owner = THIS_MODULE, +	}, +}; + +static int __init mxs_saif_init(void) +{ +	return platform_driver_register(&mxs_saif_driver); +} + +static void __exit mxs_saif_exit(void) +{ +	platform_driver_unregister(&mxs_saif_driver); +} + +module_init(mxs_saif_init); +module_exit(mxs_saif_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS ASoC SAIF driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mxs/mxs-saif.h b/sound/soc/mxs/mxs-saif.h new file mode 100644 index 00000000000..0e2ff8cdbfe --- /dev/null +++ b/sound/soc/mxs/mxs-saif.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#ifndef _MXS_SAIF_H +#define _MXS_SAIF_H + +#define SAIF_CTRL	0x0 +#define SAIF_STAT	0x10 +#define SAIF_DATA	0x20 +#define SAIF_VERSION	0X30 + +/* SAIF_CTRL */ +#define BM_SAIF_CTRL_SFTRST		0x80000000 +#define BM_SAIF_CTRL_CLKGATE		0x40000000 +#define BP_SAIF_CTRL_BITCLK_MULT_RATE	27 +#define BM_SAIF_CTRL_BITCLK_MULT_RATE	0x38000000 +#define BF_SAIF_CTRL_BITCLK_MULT_RATE(v) \ +		(((v) << 27) & BM_SAIF_CTRL_BITCLK_MULT_RATE) +#define BM_SAIF_CTRL_BITCLK_BASE_RATE	0x04000000 +#define BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN	0x02000000 +#define BM_SAIF_CTRL_FIFO_SERVICE_IRQ_EN	0x01000000 +#define BP_SAIF_CTRL_RSRVD2		21 +#define BM_SAIF_CTRL_RSRVD2		0x00E00000 + +#define BP_SAIF_CTRL_DMAWAIT_COUNT	16 +#define BM_SAIF_CTRL_DMAWAIT_COUNT	0x001F0000 +#define BF_SAIF_CTRL_DMAWAIT_COUNT(v) \ +		(((v) << 16) & BM_SAIF_CTRL_DMAWAIT_COUNT) +#define BP_SAIF_CTRL_CHANNEL_NUM_SELECT 14 +#define BM_SAIF_CTRL_CHANNEL_NUM_SELECT 0x0000C000 +#define BF_SAIF_CTRL_CHANNEL_NUM_SELECT(v) \ +		(((v) << 14) & BM_SAIF_CTRL_CHANNEL_NUM_SELECT) +#define BM_SAIF_CTRL_LRCLK_PULSE	0x00002000 +#define BM_SAIF_CTRL_BIT_ORDER		0x00001000 +#define BM_SAIF_CTRL_DELAY		0x00000800 +#define BM_SAIF_CTRL_JUSTIFY		0x00000400 +#define BM_SAIF_CTRL_LRCLK_POLARITY	0x00000200 +#define BM_SAIF_CTRL_BITCLK_EDGE	0x00000100 +#define BP_SAIF_CTRL_WORD_LENGTH	4 +#define BM_SAIF_CTRL_WORD_LENGTH	0x000000F0 +#define BF_SAIF_CTRL_WORD_LENGTH(v) \ +		(((v) << 4) & BM_SAIF_CTRL_WORD_LENGTH) +#define BM_SAIF_CTRL_BITCLK_48XFS_ENABLE	0x00000008 +#define BM_SAIF_CTRL_SLAVE_MODE		0x00000004 +#define BM_SAIF_CTRL_READ_MODE		0x00000002 +#define BM_SAIF_CTRL_RUN		0x00000001 + +/* SAIF_STAT */ +#define BM_SAIF_STAT_PRESENT		0x80000000 +#define BP_SAIF_STAT_RSRVD2		17 +#define BM_SAIF_STAT_RSRVD2		0x7FFE0000 +#define BF_SAIF_STAT_RSRVD2(v) \ +		(((v) << 17) & BM_SAIF_STAT_RSRVD2) +#define BM_SAIF_STAT_DMA_PREQ		0x00010000 +#define BP_SAIF_STAT_RSRVD1		7 +#define BM_SAIF_STAT_RSRVD1		0x0000FF80 +#define BF_SAIF_STAT_RSRVD1(v) \ +		(((v) << 7) & BM_SAIF_STAT_RSRVD1) + +#define BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ 0x00000040 +#define BM_SAIF_STAT_FIFO_OVERFLOW_IRQ	0x00000020 +#define BM_SAIF_STAT_FIFO_SERVICE_IRQ	0x00000010 +#define BP_SAIF_STAT_RSRVD0		1 +#define BM_SAIF_STAT_RSRVD0		0x0000000E +#define BF_SAIF_STAT_RSRVD0(v) \ +		(((v) << 1) & BM_SAIF_STAT_RSRVD0) +#define BM_SAIF_STAT_BUSY		0x00000001 + +/* SAFI_DATA */ +#define BP_SAIF_DATA_PCM_RIGHT		16 +#define BM_SAIF_DATA_PCM_RIGHT		0xFFFF0000 +#define BF_SAIF_DATA_PCM_RIGHT(v) \ +		(((v) << 16) & BM_SAIF_DATA_PCM_RIGHT) +#define BP_SAIF_DATA_PCM_LEFT		0 +#define BM_SAIF_DATA_PCM_LEFT		0x0000FFFF +#define BF_SAIF_DATA_PCM_LEFT(v)	\ +		(((v) << 0) & BM_SAIF_DATA_PCM_LEFT) + +/* SAIF_VERSION */ +#define BP_SAIF_VERSION_MAJOR		24 +#define BM_SAIF_VERSION_MAJOR		0xFF000000 +#define BF_SAIF_VERSION_MAJOR(v) \ +		(((v) << 24) & BM_SAIF_VERSION_MAJOR) +#define BP_SAIF_VERSION_MINOR		16 +#define BM_SAIF_VERSION_MINOR		0x00FF0000 +#define BF_SAIF_VERSION_MINOR(v) \ +		(((v) << 16) & BM_SAIF_VERSION_MINOR) +#define BP_SAIF_VERSION_STEP		0 +#define BM_SAIF_VERSION_STEP		0x0000FFFF +#define BF_SAIF_VERSION_STEP(v) \ +		(((v) << 0) & BM_SAIF_VERSION_STEP) + +#define MXS_SAIF_MCLK		0 + +#include "mxs-pcm.h" + +struct mxs_saif { +	struct device *dev; +	struct clk *clk; +	unsigned int mclk; +	unsigned int mclk_in_use; +	void __iomem *base; +	int irq; +	struct mxs_pcm_dma_params dma_param; + +	struct platform_device *soc_platform_pdev; +	u32 fifo_underrun; +	u32 fifo_overrun; +}; + +extern int mxs_saif_put_mclk(unsigned int saif_id); +extern int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk, +					unsigned int rate); +#endif diff --git a/sound/soc/mxs/mxs-sgtl5000.c b/sound/soc/mxs/mxs-sgtl5000.c new file mode 100644 index 00000000000..7fbeaec06eb --- /dev/null +++ b/sound/soc/mxs/mxs-sgtl5000.c @@ -0,0 +1,173 @@ +/* + * Copyright 2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/jack.h> +#include <sound/soc-dapm.h> +#include <asm/mach-types.h> + +#include "../codecs/sgtl5000.h" +#include "mxs-saif.h" + +static int mxs_sgtl5000_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->codec_dai; +	struct snd_soc_dai *cpu_dai = rtd->cpu_dai; +	unsigned int rate = params_rate(params); +	u32 dai_format, mclk; +	int ret; + +	/* sgtl5000 does not support 512*rate when in 96000 fs */ +	switch (rate) { +	case 96000: +		mclk = 256 * rate; +		break; +	default: +		mclk = 512 * rate; +		break; +	} + +	/* Sgtl5000 sysclk should be >= 8MHz and <= 27M */ +	if (mclk < 8000000 || mclk > 27000000) +		return -EINVAL; + +	/* Set SGTL5000's SYSCLK (provided by SAIF MCLK) */ +	ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, mclk, 0); +	if (ret) +		return ret; + +	/* The SAIF MCLK should be the same as SGTL5000_SYSCLK */ +	ret = snd_soc_dai_set_sysclk(cpu_dai, MXS_SAIF_MCLK, mclk, 0); +	if (ret) +		return ret; + +	/* set codec to slave mode */ +	dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | +			SND_SOC_DAIFMT_CBS_CFS; + +	/* set codec DAI configuration */ +	ret = snd_soc_dai_set_fmt(codec_dai, dai_format); +	if (ret) +		return ret; + +	/* set cpu DAI configuration */ +	ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); +	if (ret) +		return ret; + +	return 0; +} + +static struct snd_soc_ops mxs_sgtl5000_hifi_ops = { +	.hw_params = mxs_sgtl5000_hw_params, +}; + +static struct snd_soc_dai_link mxs_sgtl5000_dai[] = { +	{ +		.name		= "HiFi Tx", +		.stream_name	= "HiFi Playback", +		.codec_dai_name	= "sgtl5000", +		.codec_name	= "sgtl5000.0-000a", +		.cpu_dai_name	= "mxs-saif.0", +		.platform_name	= "mxs-pcm-audio.0", +		.ops		= &mxs_sgtl5000_hifi_ops, +	}, { +		.name		= "HiFi Rx", +		.stream_name	= "HiFi Capture", +		.codec_dai_name	= "sgtl5000", +		.codec_name	= "sgtl5000.0-000a", +		.cpu_dai_name	= "mxs-saif.1", +		.platform_name	= "mxs-pcm-audio.1", +		.ops		= &mxs_sgtl5000_hifi_ops, +	}, +}; + +static struct snd_soc_card mxs_sgtl5000 = { +	.name		= "mxs_sgtl5000", +	.dai_link	= mxs_sgtl5000_dai, +	.num_links	= ARRAY_SIZE(mxs_sgtl5000_dai), +}; + +static int __devinit mxs_sgtl5000_probe(struct platform_device *pdev) +{ +	struct snd_soc_card *card = &mxs_sgtl5000; +	int ret; + +	/* +	 * Set an init clock(11.28Mhz) for sgtl5000 initialization(i2c r/w). +	 * The Sgtl5000 sysclk is derived from saif0 mclk and it's range +	 * should be >= 8MHz and <= 27M. +	 */ +	ret = mxs_saif_get_mclk(0, 44100 * 256, 44100); +	if (ret) +		return ret; + +	card->dev = &pdev->dev; +	platform_set_drvdata(pdev, card); + +	ret = snd_soc_register_card(card); +	if (ret) { +		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", +			ret); +		return ret; +	} + +	return 0; +} + +static int __devexit mxs_sgtl5000_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); + +	mxs_saif_put_mclk(0); + +	snd_soc_unregister_card(card); + +	return 0; +} + +static struct platform_driver mxs_sgtl5000_audio_driver = { +	.driver = { +		.name = "mxs-sgtl5000", +		.owner = THIS_MODULE, +	}, +	.probe = mxs_sgtl5000_probe, +	.remove = __devexit_p(mxs_sgtl5000_remove), +}; + +static int __init mxs_sgtl5000_init(void) +{ +	return platform_driver_register(&mxs_sgtl5000_audio_driver); +} +module_init(mxs_sgtl5000_init); + +static void __exit mxs_sgtl5000_exit(void) +{ +	platform_driver_unregister(&mxs_sgtl5000_audio_driver); +} +module_exit(mxs_sgtl5000_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXS ALSA SoC Machine driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/nuc900/nuc900-pcm.c b/sound/soc/nuc900/nuc900-pcm.c index d589ef14e91..e46d5516e00 100644 --- a/sound/soc/nuc900/nuc900-pcm.c +++ b/sound/soc/nuc900/nuc900-pcm.c @@ -318,7 +318,6 @@ static u64 nuc900_pcm_dmamask = DMA_BIT_MASK(32);  static int nuc900_dma_new(struct snd_soc_pcm_runtime *rtd)  {  	struct snd_card *card = rtd->card->snd_card; -	struct snd_soc_dai *dai = rtd->cpu_dai;  	struct snd_pcm *pcm = rtd->pcm;  	if (!card->dev->dma_mask) diff --git a/sound/soc/pxa/spitz.c b/sound/soc/pxa/spitz.c index b253d864868..ce920e3cfea 100644 --- a/sound/soc/pxa/spitz.c +++ b/sound/soc/pxa/spitz.c @@ -312,7 +312,7 @@ static struct snd_soc_dai_link spitz_dai = {  	.cpu_dai_name = "pxa2xx-i2s",  	.codec_dai_name = "wm8750-hifi",  	.platform_name = "pxa-pcm-audio", -	.codec_name = "wm8750-codec.0-001b", +	.codec_name = "wm8750.0-001b",  	.init = spitz_wm8750_init,  	.ops = &spitz_ops,  }; diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c index d69d9fc3223..4b81ffd8756 100644 --- a/sound/soc/pxa/z2.c +++ b/sound/soc/pxa/z2.c @@ -198,7 +198,7 @@ static struct snd_soc_dai_link z2_dai = {  	.cpu_dai_name	= "pxa2xx-i2s",  	.codec_dai_name	= "wm8750-hifi",  	.platform_name = "pxa-pcm-audio", -	.codec_name	= "wm8750-codec.0-001b", +	.codec_name	= "wm8750.0-001b",  	.init		= z2_wm8750_init,  	.ops		= &z2_ops,  }; diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c index 80c85fd64e1..55efc2bdf0b 100644 --- a/sound/soc/s6000/s6000-pcm.c +++ b/sound/soc/s6000/s6000-pcm.c @@ -446,7 +446,6 @@ static u64 s6000_pcm_dmamask = DMA_BIT_MASK(32);  static int s6000_pcm_new(struct snd_soc_pcm_runtime *runtime)  {  	struct snd_card *card = runtime->card->snd_card; -	struct snd_soc_dai *dai = runtime->cpu_dai;  	struct snd_pcm *pcm = runtime->pcm;  	struct s6000_pcm_dma_params *params;  	int res; diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index 65f980ef287..dd3b3eac080 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -63,7 +63,7 @@ config SND_SOC_SAMSUNG_SMDK_WM8580  config SND_SOC_SAMSUNG_SMDK_WM8994  	tristate "SoC I2S Audio support for WM8994 on SMDK" -	depends on SND_SOC_SAMSUNG && (MACH_SMDKV310 || MACH_SMDKC210) +	depends on SND_SOC_SAMSUNG && (MACH_SMDKV310 || MACH_SMDKC210 || MACH_SMDK4212)  	select SND_SOC_WM8994  	select SND_SAMSUNG_I2S  	help @@ -158,7 +158,7 @@ config SND_SOC_GONI_AQUILA_WM8994  config SND_SOC_SAMSUNG_SMDK_SPDIF  	tristate "SoC S/PDIF Audio support for SMDK" -	depends on SND_SOC_SAMSUNG && (MACH_SMDKC100 || MACH_SMDKC110 || MACH_SMDKV210 || MACH_SMDKV310) +	depends on SND_SOC_SAMSUNG && (MACH_SMDKC100 || MACH_SMDKC110 || MACH_SMDKV210 || MACH_SMDKV310 || MACH_SMDK4212)  	select SND_SAMSUNG_SPDIF  	help  	  Say Y if you want to add support for SoC S/PDIF audio on the SMDK. @@ -173,7 +173,7 @@ config SND_SOC_SMDK_WM8580_PCM  config SND_SOC_SMDK_WM8994_PCM  	tristate "SoC PCM Audio support for WM8994 on SMDK" -	depends on SND_SOC_SAMSUNG && (MACH_SMDKC210 || MACH_SMDKV310) +	depends on SND_SOC_SAMSUNG && (MACH_SMDKC210 || MACH_SMDKV310 || MACH_SMDK4212)  	select SND_SOC_WM8994  	select SND_SAMSUNG_PCM  	help diff --git a/sound/soc/samsung/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c index 14eb6ea69e7..ed8f13a29c8 100644 --- a/sound/soc/samsung/jive_wm8750.c +++ b/sound/soc/samsung/jive_wm8750.c @@ -131,7 +131,7 @@ static struct snd_soc_dai_link jive_dai = {  	.cpu_dai_name	= "s3c2412-i2s",  	.codec_dai_name = "wm8750-hifi",  	.platform_name	= "samsung-audio", -	.codec_name	= "wm8750-codec.0-001a", +	.codec_name	= "wm8750.0-001a",  	.init		= jive_wm8750_init,  	.ops		= &jive_ops,  }; diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c index 0a2c4f22303..bbd14768ecd 100644 --- a/sound/soc/samsung/smartq_wm8987.c +++ b/sound/soc/samsung/smartq_wm8987.c @@ -207,7 +207,7 @@ static struct snd_soc_dai_link smartq_dai[] = {  		.cpu_dai_name	= "samsung-i2s.0",  		.codec_dai_name	= "wm8750-hifi",  		.platform_name	= "samsung-audio", -		.codec_name	= "wm8750-codec.0-0x1a", +		.codec_name	= "wm8750.0-0x1a",  		.init		= smartq_wm8987_init,  		.ops		= &smartq_hifi_ops,  	}, diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c index 3d26f6607aa..20deecf3b24 100644 --- a/sound/soc/samsung/smdk_wm8580.c +++ b/sound/soc/samsung/smdk_wm8580.c @@ -210,7 +210,7 @@ static struct snd_soc_dai_link smdk_dai[] = {  		.cpu_dai_name = "samsung-i2s.0",  		.codec_dai_name = "wm8580-hifi-playback",  		.platform_name = "samsung-audio", -		.codec_name = "wm8580-codec.0-001b", +		.codec_name = "wm8580.0-001b",  		.init = smdk_wm8580_init_paifrx,  		.ops = &smdk_ops,  	}, @@ -220,7 +220,7 @@ static struct snd_soc_dai_link smdk_dai[] = {  		.cpu_dai_name = "samsung-i2s.0",  		.codec_dai_name = "wm8580-hifi-capture",  		.platform_name = "samsung-audio", -		.codec_name = "wm8580-codec.0-001b", +		.codec_name = "wm8580.0-001b",  		.init = smdk_wm8580_init_paiftx,  		.ops = &smdk_ops,  	}, @@ -230,7 +230,7 @@ static struct snd_soc_dai_link smdk_dai[] = {  		.cpu_dai_name = "samsung-i2s.x",  		.codec_dai_name = "wm8580-hifi-playback",  		.platform_name = "samsung-audio", -		.codec_name = "wm8580-codec.0-001b", +		.codec_name = "wm8580.0-001b",  		.init = smdk_wm8580_init_paifrx,  		.ops = &smdk_ops,  	}, diff --git a/sound/soc/samsung/smdk_wm8580pcm.c b/sound/soc/samsung/smdk_wm8580pcm.c index 0d12092df16..4b9c73477ce 100644 --- a/sound/soc/samsung/smdk_wm8580pcm.c +++ b/sound/soc/samsung/smdk_wm8580pcm.c @@ -127,7 +127,7 @@ static struct snd_soc_dai_link smdk_dai[] = {  		.cpu_dai_name = "samsung-pcm.0",  		.codec_dai_name = "wm8580-hifi-playback",  		.platform_name = "samsung-audio", -		.codec_name = "wm8580-codec.0-001b", +		.codec_name = "wm8580.0-001b",  		.ops = &smdk_wm8580_pcm_ops,  	}, {  		.name = "WM8580 PAIF PCM TX", @@ -135,7 +135,7 @@ static struct snd_soc_dai_link smdk_dai[] = {  		.cpu_dai_name = "samsung-pcm.0",  		.codec_dai_name = "wm8580-hifi-capture",  		.platform_name = "samsung-audio", -		.codec_name = "wm8580-codec.0-001b", +		.codec_name = "wm8580.0-001b",  		.ops = &smdk_wm8580_pcm_ops,  	},  }; diff --git a/sound/soc/samsung/speyside.c b/sound/soc/samsung/speyside.c index 590e9274b06..bfed1ff7093 100644 --- a/sound/soc/samsung/speyside.c +++ b/sound/soc/samsung/speyside.c @@ -125,10 +125,6 @@ static struct snd_soc_jack_pin speyside_headset_pins[] = {  		.pin = "Headset Mic",  		.mask = SND_JACK_MICROPHONE,  	}, -	{ -		.pin = "Headphone", -		.mask = SND_JACK_HEADPHONE, -	},  };  /* Default the headphone selection to active high */ @@ -252,6 +248,7 @@ static const struct snd_kcontrol_new controls[] = {  	SOC_DAPM_PIN_SWITCH("Main AMIC"),  	SOC_DAPM_PIN_SWITCH("WM1250 Input"),  	SOC_DAPM_PIN_SWITCH("WM1250 Output"), +	SOC_DAPM_PIN_SWITCH("Headphone"),  };  static struct snd_soc_dapm_widget widgets[] = { diff --git a/sound/soc/samsung/speyside_wm8962.c b/sound/soc/samsung/speyside_wm8962.c index 72535f2daaf..3820a6b057d 100644 --- a/sound/soc/samsung/speyside_wm8962.c +++ b/sound/soc/samsung/speyside_wm8962.c @@ -31,13 +31,13 @@ static int speyside_wm8962_set_bias_level(struct snd_soc_card *card,  		if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {  			ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,  						  WM8962_FLL_MCLK, 32768, -						  44100 * 256); +						  44100 * 512);  			if (ret < 0)  				pr_err("Failed to start FLL: %d\n", ret);  			ret = snd_soc_dai_set_sysclk(codec_dai,  						     WM8962_SYSCLK_FLL, -						     44100 * 256, +						     44100 * 512,  						     SND_SOC_CLOCK_IN);  			if (ret < 0) {  				pr_err("Failed to set SYSCLK: %d\n", ret); diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 20b7f3b003a..143c705ac27 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -548,9 +548,6 @@ static inline int snd_soc_lzo_get_blkpos(struct snd_soc_codec *codec,  static inline int snd_soc_lzo_get_blksize(struct snd_soc_codec *codec)  { -	const struct snd_soc_codec_driver *codec_drv; - -	codec_drv = codec->driver;  	return DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count());  } @@ -868,10 +865,6 @@ static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec)  static int snd_soc_flat_cache_init(struct snd_soc_codec *codec)  { -	const struct snd_soc_codec_driver *codec_drv; - -	codec_drv = codec->driver; -  	if (codec->reg_def_copy)  		codec->reg_cache = kmemdup(codec->reg_def_copy,  					   codec->reg_size, GFP_KERNEL); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index d2ef014af21..4065d4e84ea 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -105,7 +105,7 @@ static int format_register_str(struct snd_soc_codec *codec,  	if (wordsize + regsize + 2 + 1 != len)  		return -EINVAL; -	ret = snd_soc_read(codec , reg); +	ret = snd_soc_read(codec, reg);  	if (ret < 0) {  		memset(regbuf, 'X', regsize);  		regbuf[regsize] = '\0'; @@ -956,6 +956,8 @@ static int soc_probe_codec(struct snd_soc_card *card,  		snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets,  					  driver->num_dapm_widgets); +	codec->dapm.idle_bias_off = driver->idle_bias_off; +  	if (driver->probe) {  		ret = driver->probe(codec);  		if (ret < 0) { @@ -3141,6 +3143,7 @@ int snd_soc_register_platform(struct device *dev,  	platform->driver = platform_drv;  	platform->dapm.dev = dev;  	platform->dapm.platform = platform; +	platform->dapm.stream_event = platform_drv->stream_event;  	mutex_lock(&client_mutex);  	list_add(&platform->list, &platform_list); @@ -3253,6 +3256,7 @@ int snd_soc_register_codec(struct device *dev,  	codec->dapm.dev = dev;  	codec->dapm.codec = codec;  	codec->dapm.seq_notifier = codec_drv->seq_notifier; +	codec->dapm.stream_event = codec_drv->stream_event;  	codec->dev = dev;  	codec->driver = codec_drv;  	codec->num_dai = num_dai; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index d67c637557a..4859ad77eac 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -443,6 +443,11 @@ static int dapm_new_mixer(struct snd_soc_dapm_widget *w)  			if (path->name != (char *)w->kcontrol_news[i].name)  				continue; +			if (w->kcontrols[i]) { +				path->kcontrol = w->kcontrols[i]; +				continue; +			} +  			wlistsize = sizeof(struct snd_soc_dapm_widget_list) +  				    sizeof(struct snd_soc_dapm_widget *),  			wlist = kzalloc(wlistsize, GFP_KERNEL); @@ -1556,7 +1561,6 @@ static int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,  		/* found, now check type */  		found = 1;  		path->connect = connect; -		break;  	}  	if (found) @@ -2584,7 +2588,7 @@ static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm,  	{  		if (!w->sname || w->dapm != dapm)  			continue; -		dev_dbg(w->dapm->dev, "widget %s\n %s stream %s event %d\n", +		dev_vdbg(w->dapm->dev, "widget %s\n %s stream %s event %d\n",  			w->name, w->sname, stream, event);  		if (strstr(w->sname, stream)) {  			switch(event) { @@ -2604,6 +2608,10 @@ static void soc_dapm_stream_event(struct snd_soc_dapm_context *dapm,  	}  	dapm_power_widgets(dapm, event); + +	/* do we need to notify any clients that DAPM stream is complete */ +	if (dapm->stream_event) +		dapm->stream_event(dapm, event);  }  /** diff --git a/sound/soc/soc-io.c b/sound/soc/soc-io.c index a62f7dd4ba9..66fcccd79ef 100644 --- a/sound/soc/soc-io.c +++ b/sound/soc/soc-io.c @@ -13,26 +13,14 @@  #include <linux/i2c.h>  #include <linux/spi/spi.h> +#include <linux/regmap.h>  #include <sound/soc.h>  #include <trace/events/asoc.h> -#ifdef CONFIG_SPI_MASTER -static int do_spi_write(void *control, const char *data, int len) -{ -	struct spi_device *spi = control; -	int ret; - -	ret = spi_write(spi, data, len); -	if (ret < 0) -		return ret; - -	return len; -} -#endif - -static int do_hw_write(struct snd_soc_codec *codec, unsigned int reg, -		       unsigned int value, const void *data, int len) +#ifdef CONFIG_REGMAP +static int hw_write(struct snd_soc_codec *codec, unsigned int reg, +		    unsigned int value)  {  	int ret; @@ -49,13 +37,7 @@ static int do_hw_write(struct snd_soc_codec *codec, unsigned int reg,  		return 0;  	} -	ret = codec->hw_write(codec->control_data, data, len); -	if (ret == len) -		return 0; -	if (ret < 0) -		return ret; -	else -		return -EIO; +	return regmap_write(codec->control_data, reg, value);  }  static unsigned int hw_read(struct snd_soc_codec *codec, unsigned int reg) @@ -69,8 +51,11 @@ static unsigned int hw_read(struct snd_soc_codec *codec, unsigned int reg)  		if (codec->cache_only)  			return -1; -		BUG_ON(!codec->hw_read); -		return codec->hw_read(codec, reg); +		ret = regmap_read(codec->control_data, reg, &val); +		if (ret == 0) +			return val; +		else +			return ret;  	}  	ret = snd_soc_cache_read(codec, reg, &val); @@ -79,202 +64,18 @@ static unsigned int hw_read(struct snd_soc_codec *codec, unsigned int reg)  	return val;  } -static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg, -			      unsigned int value) -{ -	u16 data; - -	data = cpu_to_be16((reg << 12) | (value & 0xffffff)); - -	return do_hw_write(codec, reg, value, &data, 2); -} - -static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, -			     unsigned int value) -{ -	u16 data; - -	data = cpu_to_be16((reg << 9) | (value & 0x1ff)); - -	return do_hw_write(codec, reg, value, &data, 2); -} - -static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, -			     unsigned int value) -{ -	u8 data[2]; - -	reg &= 0xff; -	data[0] = reg; -	data[1] = value & 0xff; - -	return do_hw_write(codec, reg, value, data, 2); -} - -static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, -			      unsigned int value) -{ -	u8 data[3]; -	u16 val = cpu_to_be16(value); - -	data[0] = reg; -	memcpy(&data[1], &val, sizeof(val)); - -	return do_hw_write(codec, reg, value, data, 3); -} - -#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) -static unsigned int do_i2c_read(struct snd_soc_codec *codec, -				void *reg, int reglen, -				void *data, int datalen) -{ -	struct i2c_msg xfer[2]; -	int ret; -	struct i2c_client *client = codec->control_data; - -	/* Write register */ -	xfer[0].addr = client->addr; -	xfer[0].flags = 0; -	xfer[0].len = reglen; -	xfer[0].buf = reg; - -	/* Read data */ -	xfer[1].addr = client->addr; -	xfer[1].flags = I2C_M_RD; -	xfer[1].len = datalen; -	xfer[1].buf = data; - -	ret = i2c_transfer(client->adapter, xfer, 2); -	if (ret == 2) -		return 0; -	else if (ret < 0) -		return ret; -	else -		return -EIO; -} -#endif - -#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) -static unsigned int snd_soc_8_8_read_i2c(struct snd_soc_codec *codec, -					 unsigned int r) -{ -	u8 reg = r; -	u8 data; -	int ret; - -	ret = do_i2c_read(codec, ®, 1, &data, 1); -	if (ret < 0) -		return 0; -	return data; -} -#else -#define snd_soc_8_8_read_i2c NULL -#endif - -#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) -static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec, -					  unsigned int r) -{ -	u8 reg = r; -	u16 data; -	int ret; - -	ret = do_i2c_read(codec, ®, 1, &data, 2); -	if (ret < 0) -		return 0; -	return (data >> 8) | ((data & 0xff) << 8); -} -#else -#define snd_soc_8_16_read_i2c NULL -#endif - -#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) -static unsigned int snd_soc_16_8_read_i2c(struct snd_soc_codec *codec, -					  unsigned int r) -{ -	u16 reg = r; -	u8 data; -	int ret; - -	ret = do_i2c_read(codec, ®, 2, &data, 1); -	if (ret < 0) -		return 0; -	return data; -} -#else -#define snd_soc_16_8_read_i2c NULL -#endif - -#if defined(CONFIG_SPI_MASTER) -static unsigned int snd_soc_16_8_read_spi(struct snd_soc_codec *codec, -		                          unsigned int r) -{ -	struct spi_device *spi = codec->control_data; - -	const u16 reg = cpu_to_be16(r | 0x100); -	u8 data; -	int ret; - -	ret = spi_write_then_read(spi, ®, 2, &data, 1); -	if (ret < 0) -		return 0; -	return data; -} -#else -#define snd_soc_16_8_read_spi NULL -#endif - -static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg, -			      unsigned int value) -{ -	u8 data[3]; -	u16 rval = cpu_to_be16(reg); - -	memcpy(data, &rval, sizeof(rval)); -	data[2] = value; - -	return do_hw_write(codec, reg, value, data, 3); -} - -#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) -static unsigned int snd_soc_16_16_read_i2c(struct snd_soc_codec *codec, -					   unsigned int r) -{ -	u16 reg = cpu_to_be16(r); -	u16 data; -	int ret; - -	ret = do_i2c_read(codec, ®, 2, &data, 2); -	if (ret < 0) -		return 0; -	return be16_to_cpu(data); -} -#else -#define snd_soc_16_16_read_i2c NULL -#endif - -static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg, -			       unsigned int value) -{ -	u16 data[2]; - -	data[0] = cpu_to_be16(reg); -	data[1] = cpu_to_be16(value); - -	return do_hw_write(codec, reg, value, data, sizeof(data)); -} -  /* Primitive bulk write support for soc-cache.  The data pointed to by - * `data' needs to already be in the form the hardware expects - * including any leading register specific data.  Any data written - * through this function will not go through the cache as it only - * handles writing to volatile or out of bounds registers. + * `data' needs to already be in the form the hardware expects.  Any + * data written through this function will not go through the cache as + * it only handles writing to volatile or out of bounds registers. + * + * This is currently only supported for devices using the regmap API + * wrappers.   */ -static int snd_soc_hw_bulk_write_raw(struct snd_soc_codec *codec, unsigned int reg, +static int snd_soc_hw_bulk_write_raw(struct snd_soc_codec *codec, +				     unsigned int reg,  				     const void *data, size_t len)  { -	int ret; -  	/* To ensure that we don't get out of sync with the cache, check  	 * whether the base register is volatile or if we've directly asked  	 * to bypass the cache.  Out of bounds registers are considered @@ -285,68 +86,9 @@ static int snd_soc_hw_bulk_write_raw(struct snd_soc_codec *codec, unsigned int r  	    && reg < codec->driver->reg_cache_size)  		return -EINVAL; -	switch (codec->control_type) { -#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) -	case SND_SOC_I2C: -		ret = i2c_master_send(to_i2c_client(codec->dev), data, len); -		break; -#endif -#if defined(CONFIG_SPI_MASTER) -	case SND_SOC_SPI: -		ret = spi_write(to_spi_device(codec->dev), data, len); -		break; -#endif -	default: -		BUG(); -	} - -	if (ret == len) -		return 0; -	if (ret < 0) -		return ret; -	else -		return -EIO; +	return regmap_raw_write(codec->control_data, reg, data, len);  } -static struct { -	int addr_bits; -	int data_bits; -	int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int); -	unsigned int (*read)(struct snd_soc_codec *, unsigned int); -	unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int); -	unsigned int (*spi_read)(struct snd_soc_codec *, unsigned int); -} io_types[] = { -	{ -		.addr_bits = 4, .data_bits = 12, -		.write = snd_soc_4_12_write, -	}, -	{ -		.addr_bits = 7, .data_bits = 9, -		.write = snd_soc_7_9_write, -	}, -	{ -		.addr_bits = 8, .data_bits = 8, -		.write = snd_soc_8_8_write, -		.i2c_read = snd_soc_8_8_read_i2c, -	}, -	{ -		.addr_bits = 8, .data_bits = 16, -		.write = snd_soc_8_16_write, -		.i2c_read = snd_soc_8_16_read_i2c, -	}, -	{ -		.addr_bits = 16, .data_bits = 8, -		.write = snd_soc_16_8_write, -		.i2c_read = snd_soc_16_8_read_i2c, -		.spi_read = snd_soc_16_8_read_spi, -	}, -	{ -		.addr_bits = 16, .data_bits = 16, -		.write = snd_soc_16_16_write, -		.i2c_read = snd_soc_16_16_read_i2c, -	}, -}; -  /**   * snd_soc_codec_set_cache_io: Set up standard I/O functions.   * @@ -370,50 +112,51 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,  			       int addr_bits, int data_bits,  			       enum snd_soc_control_type control)  { -	int i; - -	for (i = 0; i < ARRAY_SIZE(io_types); i++) -		if (io_types[i].addr_bits == addr_bits && -		    io_types[i].data_bits == data_bits) -			break; -	if (i == ARRAY_SIZE(io_types)) { -		printk(KERN_ERR -		       "No I/O functions for %d bit address %d bit data\n", -		       addr_bits, data_bits); -		return -EINVAL; -	} +	struct regmap_config config; -	codec->write = io_types[i].write; +	memset(&config, 0, sizeof(config)); +	codec->write = hw_write;  	codec->read = hw_read;  	codec->bulk_write_raw = snd_soc_hw_bulk_write_raw; +	config.reg_bits = addr_bits; +	config.val_bits = data_bits; +  	switch (control) { +#if defined(CONFIG_REGMAP_I2C) || defined(CONFIG_REGMAP_I2C_MODULE)  	case SND_SOC_I2C: -#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) -		codec->hw_write = (hw_write_t)i2c_master_send; -#endif -		if (io_types[i].i2c_read) -			codec->hw_read = io_types[i].i2c_read; - -		codec->control_data = container_of(codec->dev, -						   struct i2c_client, -						   dev); +		codec->control_data = regmap_init_i2c(to_i2c_client(codec->dev), +						      &config);  		break; +#endif +#if defined(CONFIG_REGMAP_SPI) || defined(CONFIG_REGMAP_SPI_MODULE)  	case SND_SOC_SPI: -#ifdef CONFIG_SPI_MASTER -		codec->hw_write = do_spi_write; +		codec->control_data = regmap_init_spi(to_spi_device(codec->dev), +						      &config); +		break;  #endif -		if (io_types[i].spi_read) -			codec->hw_read = io_types[i].spi_read; -		codec->control_data = container_of(codec->dev, -						   struct spi_device, -						   dev); +	case SND_SOC_REGMAP: +		/* Device has made its own regmap arrangements */  		break; + +	default: +		return -EINVAL;  	} +	if (IS_ERR(codec->control_data)) +		return PTR_ERR(codec->control_data); +  	return 0;  }  EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io); - +#else +int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, +			       int addr_bits, int data_bits, +			       enum snd_soc_control_type control) +{ +	return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io); +#endif diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 2879c883eeb..1aee9fcdf65 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -27,8 +27,6 @@  #include <sound/soc.h>  #include <sound/initval.h> -static DEFINE_MUTEX(pcm_mutex); -  static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream)  {  	struct snd_soc_pcm_runtime *rtd = substream->private_data;  |