From d2ec3ababa675ffdd4c8e7272d7dbd4cbdbd12ff Mon Sep 17 00:00:00 2001 From: Sangbeom Kim Date: Mon, 20 Jun 2011 17:29:29 +0900 Subject: [PATCH 01/14] ASoC: SAMSUNG: Add WM8994 PCM Machine driver This patch add WM8994 PCM machine driver to support PCM audio on SMDKV310, SMDKC210 boards. Playback and Capture supports 8kHz sampling rates. and It is tested on SMDKV310, SMDKC210. Signed-off-by: Sangbeom Kim Acked-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/samsung/Kconfig | 8 ++ sound/soc/samsung/Makefile | 2 + sound/soc/samsung/smdk_wm8994pcm.c | 176 +++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 sound/soc/samsung/smdk_wm8994pcm.c diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index c611e45ff71d..54b0e4b7faf7 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -171,6 +171,14 @@ config SND_SOC_SMDK_WM8580_PCM help Say Y if you want to add support for SoC audio on the SMDK. +config SND_SOC_SMDK_WM8994_PCM + tristate "SoC PCM Audio support for WM8994 on SMDK" + depends on SND_SOC_SAMSUNG && (MACH_SMDKC210 || MACH_SMDKV310) + select SND_SOC_WM8994 + select SND_SAMSUNG_PCM + help + Say Y if you want to add support for SoC audio on the SMDK + config SND_SOC_SPEYSIDE tristate "Audio support for Wolfson Speyside" depends on SND_SOC_SAMSUNG && MACH_WLF_CRAGG_6410 diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index e04df65db1fc..9eb3b12eb72f 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -35,6 +35,7 @@ snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.o snd-soc-goni-wm8994-objs := goni_wm8994.o snd-soc-smdk-spdif-objs := smdk_spdif.o snd-soc-smdk-wm8580pcm-objs := smdk_wm8580pcm.o +snd-soc-smdk-wm8994pcm-objs := smdk_wm8994pcm.o snd-soc-speyside-objs := speyside.o snd-soc-speyside-wm8962-objs := speyside_wm8962.o @@ -55,5 +56,6 @@ obj-$(CONFIG_SND_SOC_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.o obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF) += snd-soc-smdk-spdif.o obj-$(CONFIG_SND_SOC_GONI_AQUILA_WM8994) += snd-soc-goni-wm8994.o obj-$(CONFIG_SND_SOC_SMDK_WM8580_PCM) += snd-soc-smdk-wm8580pcm.o +obj-$(CONFIG_SND_SOC_SMDK_WM8994_PCM) += snd-soc-smdk-wm8994pcm.o obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o obj-$(CONFIG_SND_SOC_SPEYSIDE_WM8962) += snd-soc-speyside-wm8962.o diff --git a/sound/soc/samsung/smdk_wm8994pcm.c b/sound/soc/samsung/smdk_wm8994pcm.c new file mode 100644 index 000000000000..5f2111685480 --- /dev/null +++ b/sound/soc/samsung/smdk_wm8994pcm.c @@ -0,0 +1,176 @@ +/* + * sound/soc/samsung/smdk_wm8994pcm.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd + * http://www.samsung.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include + +#include "../codecs/wm8994.h" +#include "dma.h" +#include "pcm.h" + +/* + * Board Settings: + * o '1' means 'ON' + * o '0' means 'OFF' + * o 'X' means 'Don't care' + * + * SMDKC210, SMDKV310: CFG3- 1001, CFG5-1000, CFG7-111111 + */ + +/* + * Configure audio route as :- + * $ amixer sset 'DAC1' on,on + * $ amixer sset 'Right Headphone Mux' 'DAC' + * $ amixer sset 'Left Headphone Mux' 'DAC' + * $ amixer sset 'DAC1R Mixer AIF1.1' on + * $ amixer sset 'DAC1L Mixer AIF1.1' on + * $ amixer sset 'IN2L' on + * $ amixer sset 'IN2L PGA IN2LN' on + * $ amixer sset 'MIXINL IN2L' on + * $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on + * $ amixer sset 'IN2R' on + * $ amixer sset 'IN2R PGA IN2RN' on + * $ amixer sset 'MIXINR IN2R' on + * $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on + */ + +/* SMDK has a 16.9344MHZ crystal attached to WM8994 */ +#define SMDK_WM8994_FREQ 16934400 + +static int smdk_wm8994_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned long mclk_freq; + int rfs, ret; + + switch(params_rate(params)) { + case 8000: + rfs = 512; + break; + default: + dev_err(cpu_dai->dev, "%s:%d Sampling Rate %u not supported!\n", + __func__, __LINE__, params_rate(params)); + return -EINVAL; + } + + mclk_freq = params_rate(params) * rfs; + + /* Set the codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B + | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* Set the cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_B + | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + mclk_freq, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1, + SMDK_WM8994_FREQ, mclk_freq); + if (ret < 0) + return ret; + + /* Set PCM source clock on CPU */ + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX, + mclk_freq, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* Set SCLK_DIV for making bclk */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops smdk_wm8994_pcm_ops = { + .hw_params = smdk_wm8994_pcm_hw_params, +}; + +static struct snd_soc_dai_link smdk_dai[] = { + { + .name = "WM8994 PAIF PCM", + .stream_name = "Primary PCM", + .cpu_dai_name = "samsung-pcm.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec", + .ops = &smdk_wm8994_pcm_ops, + }, +}; + +static struct snd_soc_card smdk_pcm = { + .name = "SMDK-PCM", + .dai_link = smdk_dai, + .num_links = 1, +}; + +static int __devinit snd_smdk_probe(struct platform_device *pdev) +{ + int ret = 0; + + smdk_pcm.dev = &pdev->dev; + ret = snd_soc_register_card(&smdk_pcm); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret); + return ret; + } + + return 0; +} + +static int __devexit snd_smdk_remove(struct platform_device *pdev) +{ + snd_soc_unregister_card(&smdk_pcm); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver snd_smdk_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "samsung-smdk-pcm", + }, + .probe = snd_smdk_probe, + .remove = __devexit_p(snd_smdk_remove), +}; + +static int __init smdk_audio_init(void) +{ + return platform_driver_register(&snd_smdk_driver); +} + +module_init(smdk_audio_init); + +static void __exit smdk_audio_exit(void) +{ + platform_driver_unregister(&snd_smdk_driver); +} + +module_exit(smdk_audio_exit); + +MODULE_AUTHOR("Sangbeom Kim, "); +MODULE_DESCRIPTION("ALSA SoC SMDK WM8994 for PCM"); +MODULE_LICENSE("GPL"); From 172a453dcfe7de2f50c5e2d00107a734cf1dccc8 Mon Sep 17 00:00:00 2001 From: Sangbeom Kim Date: Mon, 20 Jun 2011 16:36:18 +0900 Subject: [PATCH 02/14] ASoC: SAMSUNG: Move I2S common register definition I2S registers can be used for control idma. Previously, register is defined in i2s.c. For sharing the registers, It is moved to i2s-regs.h Signed-off-by: Sangbeom Kim Acked-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/samsung/i2s-regs.h | 123 +++++++++++++++++++++++++++++++++++ sound/soc/samsung/i2s.c | 104 +---------------------------- 2 files changed, 124 insertions(+), 103 deletions(-) create mode 100644 sound/soc/samsung/i2s-regs.h diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h new file mode 100644 index 000000000000..1fa0cd10153c --- /dev/null +++ b/sound/soc/samsung/i2s-regs.h @@ -0,0 +1,123 @@ +/* + * linux/sound/soc/samsung/i2s-regs.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung I2S driver's register header + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __SND_SOC_SAMSUNG_I2S_REGS_H +#define __SND_SOC_SAMSUNG_I2S_REGS_H + +#define I2SCON 0x0 +#define I2SMOD 0x4 +#define I2SFIC 0x8 +#define I2SPSR 0xc +#define I2STXD 0x10 +#define I2SRXD 0x14 +#define I2SFICS 0x18 +#define I2STXDS 0x1c + +#define CON_RSTCLR (1 << 31) +#define CON_FRXOFSTATUS (1 << 26) +#define CON_FRXORINTEN (1 << 25) +#define CON_FTXSURSTAT (1 << 24) +#define CON_FTXSURINTEN (1 << 23) +#define CON_TXSDMA_PAUSE (1 << 20) +#define CON_TXSDMA_ACTIVE (1 << 18) + +#define CON_FTXURSTATUS (1 << 17) +#define CON_FTXURINTEN (1 << 16) +#define CON_TXFIFO2_EMPTY (1 << 15) +#define CON_TXFIFO1_EMPTY (1 << 14) +#define CON_TXFIFO2_FULL (1 << 13) +#define CON_TXFIFO1_FULL (1 << 12) + +#define CON_LRINDEX (1 << 11) +#define CON_TXFIFO_EMPTY (1 << 10) +#define CON_RXFIFO_EMPTY (1 << 9) +#define CON_TXFIFO_FULL (1 << 8) +#define CON_RXFIFO_FULL (1 << 7) +#define CON_TXDMA_PAUSE (1 << 6) +#define CON_RXDMA_PAUSE (1 << 5) +#define CON_TXCH_PAUSE (1 << 4) +#define CON_RXCH_PAUSE (1 << 3) +#define CON_TXDMA_ACTIVE (1 << 2) +#define CON_RXDMA_ACTIVE (1 << 1) +#define CON_ACTIVE (1 << 0) + +#define MOD_OPCLK_CDCLK_OUT (0 << 30) +#define MOD_OPCLK_CDCLK_IN (1 << 30) +#define MOD_OPCLK_BCLK_OUT (2 << 30) +#define MOD_OPCLK_PCLK (3 << 30) +#define MOD_OPCLK_MASK (3 << 30) +#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */ + +#define MOD_BLCS_SHIFT 26 +#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT) +#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT) +#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT) +#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT) +#define MOD_BLCP_SHIFT 24 +#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT) +#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT) +#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT) +#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT) + +#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */ +#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */ +#define MOD_C1DD_HHALF (1 << 19) +#define MOD_C1DD_LHALF (1 << 18) +#define MOD_DC2_EN (1 << 17) +#define MOD_DC1_EN (1 << 16) +#define MOD_BLC_16BIT (0 << 13) +#define MOD_BLC_8BIT (1 << 13) +#define MOD_BLC_24BIT (2 << 13) +#define MOD_BLC_MASK (3 << 13) + +#define MOD_IMS_SYSMUX (1 << 10) +#define MOD_SLAVE (1 << 11) +#define MOD_TXONLY (0 << 8) +#define MOD_RXONLY (1 << 8) +#define MOD_TXRX (2 << 8) +#define MOD_MASK (3 << 8) +#define MOD_LR_LLOW (0 << 7) +#define MOD_LR_RLOW (1 << 7) +#define MOD_SDF_IIS (0 << 5) +#define MOD_SDF_MSB (1 << 5) +#define MOD_SDF_LSB (2 << 5) +#define MOD_SDF_MASK (3 << 5) +#define MOD_RCLK_256FS (0 << 3) +#define MOD_RCLK_512FS (1 << 3) +#define MOD_RCLK_384FS (2 << 3) +#define MOD_RCLK_768FS (3 << 3) +#define MOD_RCLK_MASK (3 << 3) +#define MOD_BCLK_32FS (0 << 1) +#define MOD_BCLK_48FS (1 << 1) +#define MOD_BCLK_16FS (2 << 1) +#define MOD_BCLK_24FS (3 << 1) +#define MOD_BCLK_MASK (3 << 1) +#define MOD_8BIT (1 << 0) + +#define MOD_CDCLKCON (1 << 12) + +#define PSR_PSREN (1 << 15) + +#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf) +#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf) + +#define FIC_TXFLUSH (1 << 15) +#define FIC_RXFLUSH (1 << 7) +#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf) +#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf) +#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f) + +#endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */ + + diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 992a732b5211..1568eea31f41 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -22,109 +22,7 @@ #include "dma.h" #include "i2s.h" - -#define I2SCON 0x0 -#define I2SMOD 0x4 -#define I2SFIC 0x8 -#define I2SPSR 0xc -#define I2STXD 0x10 -#define I2SRXD 0x14 -#define I2SFICS 0x18 -#define I2STXDS 0x1c - -#define CON_RSTCLR (1 << 31) -#define CON_FRXOFSTATUS (1 << 26) -#define CON_FRXORINTEN (1 << 25) -#define CON_FTXSURSTAT (1 << 24) -#define CON_FTXSURINTEN (1 << 23) -#define CON_TXSDMA_PAUSE (1 << 20) -#define CON_TXSDMA_ACTIVE (1 << 18) - -#define CON_FTXURSTATUS (1 << 17) -#define CON_FTXURINTEN (1 << 16) -#define CON_TXFIFO2_EMPTY (1 << 15) -#define CON_TXFIFO1_EMPTY (1 << 14) -#define CON_TXFIFO2_FULL (1 << 13) -#define CON_TXFIFO1_FULL (1 << 12) - -#define CON_LRINDEX (1 << 11) -#define CON_TXFIFO_EMPTY (1 << 10) -#define CON_RXFIFO_EMPTY (1 << 9) -#define CON_TXFIFO_FULL (1 << 8) -#define CON_RXFIFO_FULL (1 << 7) -#define CON_TXDMA_PAUSE (1 << 6) -#define CON_RXDMA_PAUSE (1 << 5) -#define CON_TXCH_PAUSE (1 << 4) -#define CON_RXCH_PAUSE (1 << 3) -#define CON_TXDMA_ACTIVE (1 << 2) -#define CON_RXDMA_ACTIVE (1 << 1) -#define CON_ACTIVE (1 << 0) - -#define MOD_OPCLK_CDCLK_OUT (0 << 30) -#define MOD_OPCLK_CDCLK_IN (1 << 30) -#define MOD_OPCLK_BCLK_OUT (2 << 30) -#define MOD_OPCLK_PCLK (3 << 30) -#define MOD_OPCLK_MASK (3 << 30) -#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */ - -#define MOD_BLCS_SHIFT 26 -#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT) -#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT) -#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT) -#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT) -#define MOD_BLCP_SHIFT 24 -#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT) -#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT) -#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT) -#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT) - -#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */ -#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */ -#define MOD_C1DD_HHALF (1 << 19) -#define MOD_C1DD_LHALF (1 << 18) -#define MOD_DC2_EN (1 << 17) -#define MOD_DC1_EN (1 << 16) -#define MOD_BLC_16BIT (0 << 13) -#define MOD_BLC_8BIT (1 << 13) -#define MOD_BLC_24BIT (2 << 13) -#define MOD_BLC_MASK (3 << 13) - -#define MOD_IMS_SYSMUX (1 << 10) -#define MOD_SLAVE (1 << 11) -#define MOD_TXONLY (0 << 8) -#define MOD_RXONLY (1 << 8) -#define MOD_TXRX (2 << 8) -#define MOD_MASK (3 << 8) -#define MOD_LR_LLOW (0 << 7) -#define MOD_LR_RLOW (1 << 7) -#define MOD_SDF_IIS (0 << 5) -#define MOD_SDF_MSB (1 << 5) -#define MOD_SDF_LSB (2 << 5) -#define MOD_SDF_MASK (3 << 5) -#define MOD_RCLK_256FS (0 << 3) -#define MOD_RCLK_512FS (1 << 3) -#define MOD_RCLK_384FS (2 << 3) -#define MOD_RCLK_768FS (3 << 3) -#define MOD_RCLK_MASK (3 << 3) -#define MOD_BCLK_32FS (0 << 1) -#define MOD_BCLK_48FS (1 << 1) -#define MOD_BCLK_16FS (2 << 1) -#define MOD_BCLK_24FS (3 << 1) -#define MOD_BCLK_MASK (3 << 1) -#define MOD_8BIT (1 << 0) - -#define MOD_CDCLKCON (1 << 12) - -#define PSR_PSREN (1 << 15) - -#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf) -#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf) - -#define FIC_TXFLUSH (1 << 15) -#define FIC_RXFLUSH (1 << 7) -#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf) -#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf) -#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f) +#include "i2s-regs.h" #define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) From 766705eef5d0faa9fc1e6a0cac95fc433bf5de70 Mon Sep 17 00:00:00 2001 From: Sangbeom Kim Date: Mon, 20 Jun 2011 16:36:19 +0900 Subject: [PATCH 03/14] ASoC: SAMSUNG: Add idma related register definition This patch add idma related register definitions to support idma. Signed-off-by: Sangbeom Kim Acked-by: Jassi Brar Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/samsung/i2s-regs.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h index 1fa0cd10153c..c0e6d9a19efc 100644 --- a/sound/soc/samsung/i2s-regs.h +++ b/sound/soc/samsung/i2s-regs.h @@ -23,6 +23,14 @@ #define I2SRXD 0x14 #define I2SFICS 0x18 #define I2STXDS 0x1c +#define I2SAHB 0x20 +#define I2SSTR0 0x24 +#define I2SSIZE 0x28 +#define I2STRNCNT 0x2c +#define I2SLVL0ADDR 0x30 +#define I2SLVL1ADDR 0x34 +#define I2SLVL2ADDR 0x38 +#define I2SLVL3ADDR 0x3c #define CON_RSTCLR (1 << 31) #define CON_FRXOFSTATUS (1 << 26) @@ -114,10 +122,22 @@ #define FIC_TXFLUSH (1 << 15) #define FIC_RXFLUSH (1 << 7) + #define FIC_TXCOUNT(x) (((x) >> 8) & 0xf) #define FIC_RXCOUNT(x) (((x) >> 0) & 0xf) #define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f) +#define AHB_INTENLVL0 (1 << 24) +#define AHB_LVL0INT (1 << 20) +#define AHB_CLRLVL0INT (1 << 16) +#define AHB_DMARLD (1 << 5) +#define AHB_INTMASK (1 << 3) +#define AHB_DMAEN (1 << 0) +#define AHB_LVLINTMASK (0xf << 20) + +#define I2SSIZE_TRNMSK (0xffff) +#define I2SSIZE_SHIFT (16) + #endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */ From cc52688a08880021d31a109f36ee4a78c10ba214 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 27 Jun 2011 17:04:01 +0200 Subject: [PATCH 04/14] ASoC: Add ADAV80x codec driver This patch adds support for the Analog Devices ADAV801 and ADAV803 audio codec. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- MAINTAINERS | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/adav80x.c | 951 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/adav80x.h | 35 ++ 5 files changed, 993 insertions(+) create mode 100644 sound/soc/codecs/adav80x.c create mode 100644 sound/soc/codecs/adav80x.h diff --git a/MAINTAINERS b/MAINTAINERS index 2406e0d8e807..7bcc3951189f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -534,6 +534,7 @@ L: alsa-devel@alsa-project.org (moderated for non-subscribers) W: http://wiki.analog.com/ S: Supported F: sound/soc/codecs/adau* +F: sound/soc/codecs/adav* F: sound/soc/codecs/ad1* F: sound/soc/codecs/ssm* diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 2998e659379c..ff43405752a1 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_ADAV80X select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C @@ -137,6 +138,9 @@ config SND_SOC_ADAU1701 select SIGMA tristate +config SND_SOC_ADAV80X + tristate + config SND_SOC_ADS117X tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 51cd3d489589..4957431e23fc 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-adav80x-objs := adav80x.o snd-soc-ads117x-objs := ads117x.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o @@ -99,6 +100,7 @@ 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_ADAU1701) += snd-soc-adau1701.o +obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c new file mode 100644 index 000000000000..e30fba62392d --- /dev/null +++ b/sound/soc/codecs/adav80x.c @@ -0,0 +1,951 @@ +/* + * ADAV80X Audio Codec driver supporting ADAV801, ADAV803 + * + * Copyright 2011 Analog Devices Inc. + * Author: Yi Li + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adav80x.h" + +#define ADAV80X_PLAYBACK_CTRL 0x04 +#define ADAV80X_AUX_IN_CTRL 0x05 +#define ADAV80X_REC_CTRL 0x06 +#define ADAV80X_AUX_OUT_CTRL 0x07 +#define ADAV80X_DPATH_CTRL1 0x62 +#define ADAV80X_DPATH_CTRL2 0x63 +#define ADAV80X_DAC_CTRL1 0x64 +#define ADAV80X_DAC_CTRL2 0x65 +#define ADAV80X_DAC_CTRL3 0x66 +#define ADAV80X_DAC_L_VOL 0x68 +#define ADAV80X_DAC_R_VOL 0x69 +#define ADAV80X_PGA_L_VOL 0x6c +#define ADAV80X_PGA_R_VOL 0x6d +#define ADAV80X_ADC_CTRL1 0x6e +#define ADAV80X_ADC_CTRL2 0x6f +#define ADAV80X_ADC_L_VOL 0x70 +#define ADAV80X_ADC_R_VOL 0x71 +#define ADAV80X_PLL_CTRL1 0x74 +#define ADAV80X_PLL_CTRL2 0x75 +#define ADAV80X_ICLK_CTRL1 0x76 +#define ADAV80X_ICLK_CTRL2 0x77 +#define ADAV80X_PLL_CLK_SRC 0x78 +#define ADAV80X_PLL_OUTE 0x7a + +#define ADAV80X_PLL_CLK_SRC_PLL_XIN(pll) 0x00 +#define ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll) (0x40 << (pll)) +#define ADAV80X_PLL_CLK_SRC_PLL_MASK(pll) (0x40 << (pll)) + +#define ADAV80X_ICLK_CTRL1_DAC_SRC(src) ((src) << 5) +#define ADAV80X_ICLK_CTRL1_ADC_SRC(src) ((src) << 2) +#define ADAV80X_ICLK_CTRL1_ICLK2_SRC(src) (src) +#define ADAV80X_ICLK_CTRL2_ICLK1_SRC(src) ((src) << 3) + +#define ADAV80X_PLL_CTRL1_PLLDIV 0x10 +#define ADAV80X_PLL_CTRL1_PLLPD(pll) (0x04 << (pll)) +#define ADAV80X_PLL_CTRL1_XTLPD 0x02 + +#define ADAV80X_PLL_CTRL2_FIELD(pll, x) ((x) << ((pll) * 4)) + +#define ADAV80X_PLL_CTRL2_FS_48(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x00) +#define ADAV80X_PLL_CTRL2_FS_32(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x08) +#define ADAV80X_PLL_CTRL2_FS_44(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0c) + +#define ADAV80X_PLL_CTRL2_SEL(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x02) +#define ADAV80X_PLL_CTRL2_DOUB(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x01) +#define ADAV80X_PLL_CTRL2_PLL_MASK(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0f) + +#define ADAV80X_ADC_CTRL1_MODULATOR_MASK 0x80 +#define ADAV80X_ADC_CTRL1_MODULATOR_128FS 0x00 +#define ADAV80X_ADC_CTRL1_MODULATOR_64FS 0x80 + +#define ADAV80X_DAC_CTRL1_PD 0x80 + +#define ADAV80X_DAC_CTRL2_DIV1 0x00 +#define ADAV80X_DAC_CTRL2_DIV1_5 0x10 +#define ADAV80X_DAC_CTRL2_DIV2 0x20 +#define ADAV80X_DAC_CTRL2_DIV3 0x30 +#define ADAV80X_DAC_CTRL2_DIV_MASK 0x30 + +#define ADAV80X_DAC_CTRL2_INTERPOL_256FS 0x00 +#define ADAV80X_DAC_CTRL2_INTERPOL_128FS 0x40 +#define ADAV80X_DAC_CTRL2_INTERPOL_64FS 0x80 +#define ADAV80X_DAC_CTRL2_INTERPOL_MASK 0xc0 + +#define ADAV80X_DAC_CTRL2_DEEMPH_NONE 0x00 +#define ADAV80X_DAC_CTRL2_DEEMPH_44 0x01 +#define ADAV80X_DAC_CTRL2_DEEMPH_32 0x02 +#define ADAV80X_DAC_CTRL2_DEEMPH_48 0x03 +#define ADAV80X_DAC_CTRL2_DEEMPH_MASK 0x01 + +#define ADAV80X_CAPTURE_MODE_MASTER 0x20 +#define ADAV80X_CAPTURE_WORD_LEN24 0x00 +#define ADAV80X_CAPTURE_WORD_LEN20 0x04 +#define ADAV80X_CAPTRUE_WORD_LEN18 0x08 +#define ADAV80X_CAPTURE_WORD_LEN16 0x0c +#define ADAV80X_CAPTURE_WORD_LEN_MASK 0x0c + +#define ADAV80X_CAPTURE_MODE_LEFT_J 0x00 +#define ADAV80X_CAPTURE_MODE_I2S 0x01 +#define ADAV80X_CAPTURE_MODE_RIGHT_J 0x03 +#define ADAV80X_CAPTURE_MODE_MASK 0x03 + +#define ADAV80X_PLAYBACK_MODE_MASTER 0x10 +#define ADAV80X_PLAYBACK_MODE_LEFT_J 0x00 +#define ADAV80X_PLAYBACK_MODE_I2S 0x01 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_24 0x04 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_20 0x05 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_18 0x06 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_16 0x07 +#define ADAV80X_PLAYBACK_MODE_MASK 0x07 + +#define ADAV80X_PLL_OUTE_SYSCLKPD(x) BIT(2 - (x)) + +static u8 adav80x_default_regs[] = { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x01, 0x80, 0x26, 0x00, 0x00, + 0x02, 0x40, 0x20, 0x00, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x92, 0xb1, 0x37, + 0x48, 0xd2, 0xfb, 0xca, 0xd2, 0x15, 0xe8, 0x29, 0xb9, 0x6a, 0xda, 0x2b, + 0xb7, 0xc0, 0x11, 0x65, 0x5c, 0xf6, 0xff, 0x8d, 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, 0xa5, 0x00, 0x00, + 0x00, 0xe8, 0x46, 0xe1, 0x5b, 0xd3, 0x43, 0x77, 0x93, 0xa7, 0x44, 0xee, + 0x32, 0x12, 0xc0, 0x11, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x3f, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, +}; + +struct adav80x { + enum snd_soc_control_type control_type; + + enum adav80x_clk_src clk_src; + unsigned int sysclk; + enum adav80x_pll_src pll_src; + + unsigned int dai_fmt[2]; + unsigned int rate; + bool deemph; + bool sysclk_pd[3]; +}; + +static const char *adav80x_mux_text[] = { + "ADC", + "Playback", + "Aux Playback", +}; + +static const unsigned int adav80x_mux_values[] = { + 0, 2, 3, +}; + +#define ADAV80X_MUX_ENUM_DECL(name, reg, shift) \ + SOC_VALUE_ENUM_DOUBLE_DECL(name, reg, shift, 7, \ + ARRAY_SIZE(adav80x_mux_text), adav80x_mux_text, \ + adav80x_mux_values) + +static ADAV80X_MUX_ENUM_DECL(adav80x_aux_capture_enum, ADAV80X_DPATH_CTRL1, 0); +static ADAV80X_MUX_ENUM_DECL(adav80x_capture_enum, ADAV80X_DPATH_CTRL1, 3); +static ADAV80X_MUX_ENUM_DECL(adav80x_dac_enum, ADAV80X_DPATH_CTRL2, 3); + +static const struct snd_kcontrol_new adav80x_aux_capture_mux_ctrl = + SOC_DAPM_VALUE_ENUM("Route", adav80x_aux_capture_enum); +static const struct snd_kcontrol_new adav80x_capture_mux_ctrl = + SOC_DAPM_VALUE_ENUM("Route", adav80x_capture_enum); +static const struct snd_kcontrol_new adav80x_dac_mux_ctrl = + SOC_DAPM_VALUE_ENUM("Route", adav80x_dac_enum); + +#define ADAV80X_MUX(name, ctrl) \ + SND_SOC_DAPM_VALUE_MUX(name, SND_SOC_NOPM, 0, 0, ctrl) + +static const struct snd_soc_dapm_widget adav80x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, ADAV80X_DAC_CTRL1, 7, 1), + SND_SOC_DAPM_ADC("ADC", NULL, ADAV80X_ADC_CTRL1, 5, 1), + + SND_SOC_DAPM_PGA("Right PGA", ADAV80X_ADC_CTRL1, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Left PGA", ADAV80X_ADC_CTRL1, 1, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT("AIFOUT", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFIN", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_OUT("AIFAUXOUT", "Aux Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFAUXIN", "Aux Playback", 0, SND_SOC_NOPM, 0, 0), + + ADAV80X_MUX("Aux Capture Select", &adav80x_aux_capture_mux_ctrl), + ADAV80X_MUX("Capture Select", &adav80x_capture_mux_ctrl), + ADAV80X_MUX("DAC Select", &adav80x_dac_mux_ctrl), + + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + + SND_SOC_DAPM_SUPPLY("SYSCLK", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", ADAV80X_PLL_CTRL1, 2, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2", ADAV80X_PLL_CTRL1, 3, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("OSC", ADAV80X_PLL_CTRL1, 1, 1, NULL, 0), +}; + +static int adav80x_dapm_sysclk_check(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_codec *codec = source->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + const char *clk; + + switch (adav80x->clk_src) { + case ADAV80X_CLK_PLL1: + clk = "PLL1"; + break; + case ADAV80X_CLK_PLL2: + clk = "PLL2"; + break; + case ADAV80X_CLK_XTAL: + clk = "OSC"; + break; + default: + return 0; + } + + return strcmp(source->name, clk) == 0; +} + +static int adav80x_dapm_pll_check(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_codec *codec = source->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + return adav80x->pll_src == ADAV80X_PLL_SRC_XTAL; +} + + +static const struct snd_soc_dapm_route adav80x_dapm_routes[] = { + { "DAC Select", "ADC", "ADC" }, + { "DAC Select", "Playback", "AIFIN" }, + { "DAC Select", "Aux Playback", "AIFAUXIN" }, + { "DAC", NULL, "DAC Select" }, + + { "Capture Select", "ADC", "ADC" }, + { "Capture Select", "Playback", "AIFIN" }, + { "Capture Select", "Aux Playback", "AIFAUXIN" }, + { "AIFOUT", NULL, "Capture Select" }, + + { "Aux Capture Select", "ADC", "ADC" }, + { "Aux Capture Select", "Playback", "AIFIN" }, + { "Aux Capture Select", "Aux Playback", "AIFAUXIN" }, + { "AIFAUXOUT", NULL, "Aux Capture Select" }, + + { "VOUTR", NULL, "DAC" }, + { "VOUTL", NULL, "DAC" }, + + { "Left PGA", NULL, "VINL" }, + { "Right PGA", NULL, "VINR" }, + { "ADC", NULL, "Left PGA" }, + { "ADC", NULL, "Right PGA" }, + + { "SYSCLK", NULL, "PLL1", adav80x_dapm_sysclk_check }, + { "SYSCLK", NULL, "PLL2", adav80x_dapm_sysclk_check }, + { "SYSCLK", NULL, "OSC", adav80x_dapm_sysclk_check }, + { "PLL1", NULL, "OSC", adav80x_dapm_pll_check }, + { "PLL2", NULL, "OSC", adav80x_dapm_pll_check }, + + { "ADC", NULL, "SYSCLK" }, + { "DAC", NULL, "SYSCLK" }, + { "AIFOUT", NULL, "SYSCLK" }, + { "AIFAUXOUT", NULL, "SYSCLK" }, + { "AIFIN", NULL, "SYSCLK" }, + { "AIFAUXIN", NULL, "SYSCLK" }, +}; + +static int adav80x_set_deemph(struct snd_soc_codec *codec) +{ + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + if (adav80x->deemph) { + switch (adav80x->rate) { + case 32000: + val = ADAV80X_DAC_CTRL2_DEEMPH_32; + break; + case 44100: + val = ADAV80X_DAC_CTRL2_DEEMPH_44; + break; + case 48000: + case 64000: + case 88200: + case 96000: + val = ADAV80X_DAC_CTRL2_DEEMPH_48; + break; + default: + val = ADAV80X_DAC_CTRL2_DEEMPH_NONE; + break; + } + } else { + val = ADAV80X_DAC_CTRL2_DEEMPH_NONE; + } + + return snd_soc_update_bits(codec, ADAV80X_DAC_CTRL2, + ADAV80X_DAC_CTRL2_DEEMPH_MASK, val); +} + +static int adav80x_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int deemph = ucontrol->value.enumerated.item[0]; + + if (deemph > 1) + return -EINVAL; + + adav80x->deemph = deemph; + + return adav80x_set_deemph(codec); +} + +static int adav80x_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = adav80x->deemph; + return 0; +}; + +static const DECLARE_TLV_DB_SCALE(adav80x_inpga_tlv, 0, 50, 0); +static const DECLARE_TLV_DB_MINMAX(adav80x_digital_tlv, -9563, 0); + +static const struct snd_kcontrol_new adav80x_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", ADAV80X_DAC_L_VOL, + ADAV80X_DAC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv), + SOC_DOUBLE_R_TLV("Master Capture Volume", ADAV80X_ADC_L_VOL, + ADAV80X_ADC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv), + + SOC_DOUBLE_R_TLV("PGA Capture Volume", ADAV80X_PGA_L_VOL, + ADAV80X_PGA_R_VOL, 0, 0x30, 0, adav80x_inpga_tlv), + + SOC_DOUBLE("Master Playback Switch", ADAV80X_DAC_CTRL1, 0, 1, 1, 0), + SOC_DOUBLE("Master Capture Switch", ADAV80X_ADC_CTRL1, 2, 3, 1, 1), + + SOC_SINGLE("ADC High Pass Filter Switch", ADAV80X_ADC_CTRL1, 6, 1, 0), + + SOC_SINGLE_BOOL_EXT("Playback De-emphasis Switch", 0, + adav80x_get_deemph, adav80x_put_deemph), +}; + +static unsigned int adav80x_port_ctrl_regs[2][2] = { + { ADAV80X_REC_CTRL, ADAV80X_PLAYBACK_CTRL, }, + { ADAV80X_AUX_OUT_CTRL, ADAV80X_AUX_IN_CTRL }, +}; + +static int adav80x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int capture = 0x00; + unsigned int playback = 0x00; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + capture |= ADAV80X_CAPTURE_MODE_MASTER; + playback |= ADAV80X_PLAYBACK_MODE_MASTER; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + capture |= ADAV80X_CAPTURE_MODE_I2S; + playback |= ADAV80X_PLAYBACK_MODE_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + capture |= ADAV80X_CAPTURE_MODE_LEFT_J; + playback |= ADAV80X_PLAYBACK_MODE_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + capture |= ADAV80X_CAPTURE_MODE_RIGHT_J; + playback |= ADAV80X_PLAYBACK_MODE_RIGHT_J_24; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][0], + ADAV80X_CAPTURE_MODE_MASK | ADAV80X_CAPTURE_MODE_MASTER, + capture); + snd_soc_write(codec, adav80x_port_ctrl_regs[dai->id][1], playback); + + adav80x->dai_fmt[dai->id] = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + return 0; +} + +static int adav80x_set_adc_clock(struct snd_soc_codec *codec, + unsigned int sample_rate) +{ + unsigned int val; + + if (sample_rate <= 48000) + val = ADAV80X_ADC_CTRL1_MODULATOR_128FS; + else + val = ADAV80X_ADC_CTRL1_MODULATOR_64FS; + + snd_soc_update_bits(codec, ADAV80X_ADC_CTRL1, + ADAV80X_ADC_CTRL1_MODULATOR_MASK, val); + + return 0; +} + +static int adav80x_set_dac_clock(struct snd_soc_codec *codec, + unsigned int sample_rate) +{ + unsigned int val; + + if (sample_rate <= 48000) + val = ADAV80X_DAC_CTRL2_DIV1 | ADAV80X_DAC_CTRL2_INTERPOL_256FS; + else + val = ADAV80X_DAC_CTRL2_DIV2 | ADAV80X_DAC_CTRL2_INTERPOL_128FS; + + snd_soc_update_bits(codec, ADAV80X_DAC_CTRL2, + ADAV80X_DAC_CTRL2_DIV_MASK | ADAV80X_DAC_CTRL2_INTERPOL_MASK, + val); + + return 0; +} + +static int adav80x_set_capture_pcm_format(struct snd_soc_codec *codec, + struct snd_soc_dai *dai, snd_pcm_format_t format) +{ + unsigned int val; + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + val = ADAV80X_CAPTURE_WORD_LEN16; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + val = ADAV80X_CAPTRUE_WORD_LEN18; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val = ADAV80X_CAPTURE_WORD_LEN20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = ADAV80X_CAPTURE_WORD_LEN24; + break; + default: + break; + } + + snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][0], + ADAV80X_CAPTURE_WORD_LEN_MASK, val); + + return 0; +} + +static int adav80x_set_playback_pcm_format(struct snd_soc_codec *codec, + struct snd_soc_dai *dai, snd_pcm_format_t format) +{ + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + if (adav80x->dai_fmt[dai->id] != SND_SOC_DAIFMT_RIGHT_J) + return 0; + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_16; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_18; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_24; + break; + default: + break; + } + + snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][1], + ADAV80X_PLAYBACK_MODE_MASK, val); + + return 0; +} + +static int adav80x_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 adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int rate = params_rate(params); + + if (rate * 256 != adav80x->sysclk) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + adav80x_set_playback_pcm_format(codec, dai, + params_format(params)); + adav80x_set_dac_clock(codec, rate); + } else { + adav80x_set_capture_pcm_format(codec, dai, + params_format(params)); + adav80x_set_adc_clock(codec, rate); + } + adav80x->rate = rate; + adav80x_set_deemph(codec); + + return 0; +} + +static int adav80x_set_sysclk(struct snd_soc_codec *codec, + int clk_id, unsigned int freq, int dir) +{ + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + if (dir == SND_SOC_CLOCK_IN) { + switch (clk_id) { + case ADAV80X_CLK_XIN: + case ADAV80X_CLK_XTAL: + case ADAV80X_CLK_MCLKI: + case ADAV80X_CLK_PLL1: + case ADAV80X_CLK_PLL2: + break; + default: + return -EINVAL; + } + + adav80x->sysclk = freq; + + if (adav80x->clk_src != clk_id) { + unsigned int iclk_ctrl1, iclk_ctrl2; + + adav80x->clk_src = clk_id; + if (clk_id == ADAV80X_CLK_XTAL) + clk_id = ADAV80X_CLK_XIN; + + iclk_ctrl1 = ADAV80X_ICLK_CTRL1_DAC_SRC(clk_id) | + ADAV80X_ICLK_CTRL1_ADC_SRC(clk_id) | + ADAV80X_ICLK_CTRL1_ICLK2_SRC(clk_id); + iclk_ctrl2 = ADAV80X_ICLK_CTRL2_ICLK1_SRC(clk_id); + + snd_soc_write(codec, ADAV80X_ICLK_CTRL1, iclk_ctrl1); + snd_soc_write(codec, ADAV80X_ICLK_CTRL2, iclk_ctrl2); + + snd_soc_dapm_sync(&codec->dapm); + } + } else { + unsigned int mask; + + switch (clk_id) { + case ADAV80X_CLK_SYSCLK1: + case ADAV80X_CLK_SYSCLK2: + case ADAV80X_CLK_SYSCLK3: + break; + default: + return -EINVAL; + } + + clk_id -= ADAV80X_CLK_SYSCLK1; + mask = ADAV80X_PLL_OUTE_SYSCLKPD(clk_id); + + if (freq == 0) { + snd_soc_update_bits(codec, ADAV80X_PLL_OUTE, mask, mask); + adav80x->sysclk_pd[clk_id] = true; + } else { + snd_soc_update_bits(codec, ADAV80X_PLL_OUTE, mask, 0); + adav80x->sysclk_pd[clk_id] = false; + } + + if (adav80x->sysclk_pd[0]) + snd_soc_dapm_disable_pin(&codec->dapm, "PLL1"); + else + snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1"); + + if (adav80x->sysclk_pd[1] || adav80x->sysclk_pd[2]) + snd_soc_dapm_disable_pin(&codec->dapm, "PLL2"); + else + snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL2"); + + snd_soc_dapm_sync(&codec->dapm); + } + + return 0; +} + +static int adav80x_set_pll(struct snd_soc_codec *codec, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int pll_ctrl1 = 0; + unsigned int pll_ctrl2 = 0; + unsigned int pll_src; + + switch (source) { + case ADAV80X_PLL_SRC_XTAL: + case ADAV80X_PLL_SRC_XIN: + case ADAV80X_PLL_SRC_MCLKI: + break; + default: + return -EINVAL; + } + + if (!freq_out) + return 0; + + switch (freq_in) { + case 27000000: + break; + case 54000000: + if (source == ADAV80X_PLL_SRC_XIN) { + pll_ctrl1 |= ADAV80X_PLL_CTRL1_PLLDIV; + break; + } + default: + return -EINVAL; + } + + if (freq_out > 12288000) { + pll_ctrl2 |= ADAV80X_PLL_CTRL2_DOUB(pll_id); + freq_out /= 2; + } + + /* freq_out = sample_rate * 256 */ + switch (freq_out) { + case 8192000: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_32(pll_id); + break; + case 11289600: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_44(pll_id); + break; + case 12288000: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_48(pll_id); + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, ADAV80X_PLL_CTRL1, ADAV80X_PLL_CTRL1_PLLDIV, + pll_ctrl1); + snd_soc_update_bits(codec, ADAV80X_PLL_CTRL2, + ADAV80X_PLL_CTRL2_PLL_MASK(pll_id), pll_ctrl2); + + if (source != adav80x->pll_src) { + if (source == ADAV80X_PLL_SRC_MCLKI) + pll_src = ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll_id); + else + pll_src = ADAV80X_PLL_CLK_SRC_PLL_XIN(pll_id); + + snd_soc_update_bits(codec, ADAV80X_PLL_CLK_SRC, + ADAV80X_PLL_CLK_SRC_PLL_MASK(pll_id), pll_src); + + adav80x->pll_src = source; + + snd_soc_dapm_sync(&codec->dapm); + } + + return 0; +} + +static int adav80x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int mask = ADAV80X_DAC_CTRL1_PD; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_update_bits(codec, ADAV80X_DAC_CTRL1, mask, 0x00); + break; + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, ADAV80X_DAC_CTRL1, mask, mask); + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +/* Enforce the same sample rate on all audio interfaces */ +static int adav80x_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + if (!codec->active || !adav80x->rate) + return 0; + + return snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, adav80x->rate, adav80x->rate); +} + +static void adav80x_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + if (!codec->active) + adav80x->rate = 0; +} + +static const struct snd_soc_dai_ops adav80x_dai_ops = { + .set_fmt = adav80x_set_dai_fmt, + .hw_params = adav80x_hw_params, + .startup = adav80x_dai_startup, + .shutdown = adav80x_dai_shutdown, +}; + +#define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define ADAV80X_CAPTURE_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) + +#define ADAV80X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver adav80x_dais[] = { + { + .name = "adav80x-hifi", + .id = 0, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_PLAYBACK_RATES, + .formats = ADAV80X_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_CAPTURE_RATES, + .formats = ADAV80X_FORMATS, + }, + .ops = &adav80x_dai_ops, + }, + { + .name = "adav80x-aux", + .id = 1, + .playback = { + .stream_name = "Aux Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_PLAYBACK_RATES, + .formats = ADAV80X_FORMATS, + }, + .capture = { + .stream_name = "Aux Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_CAPTURE_RATES, + .formats = ADAV80X_FORMATS, + }, + .ops = &adav80x_dai_ops, + }, +}; + +static int adav80x_probe(struct snd_soc_codec *codec) +{ + int ret; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, adav80x->control_type); + if (ret) { + dev_err(codec->dev, "failed to set cache I/O: %d\n", ret); + return ret; + } + + /* Force PLLs on for SYSCLK output */ + snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1"); + snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL2"); + + /* Power down S/PDIF receiver, since it is currently not supported */ + snd_soc_write(codec, ADAV80X_PLL_OUTE, 0x20); + /* Disable DAC zero flag */ + snd_soc_write(codec, ADAV80X_DAC_CTRL3, 0x6); + + return adav80x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); +} + +static int adav80x_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + return adav80x_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int adav80x_resume(struct snd_soc_codec *codec) +{ + adav80x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + codec->cache_sync = 1; + snd_soc_cache_sync(codec); + + return 0; +} + +static int adav80x_remove(struct snd_soc_codec *codec) +{ + return adav80x_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static struct snd_soc_codec_driver adav80x_codec_driver = { + .probe = adav80x_probe, + .remove = adav80x_remove, + .suspend = adav80x_suspend, + .resume = adav80x_resume, + .set_bias_level = adav80x_set_bias_level, + + .set_pll = adav80x_set_pll, + .set_sysclk = adav80x_set_sysclk, + + .reg_word_size = sizeof(u8), + .reg_cache_size = ARRAY_SIZE(adav80x_default_regs), + .reg_cache_default = adav80x_default_regs, + + .controls = adav80x_controls, + .num_controls = ARRAY_SIZE(adav80x_controls), + .dapm_widgets = adav80x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adav80x_dapm_widgets), + .dapm_routes = adav80x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adav80x_dapm_routes), +}; + +static int __devinit adav80x_bus_probe(struct device *dev, + enum snd_soc_control_type control_type) +{ + struct adav80x *adav80x; + int ret; + + adav80x = kzalloc(sizeof(*adav80x), GFP_KERNEL); + if (!adav80x) + return -ENOMEM; + + dev_set_drvdata(dev, adav80x); + adav80x->control_type = control_type; + + ret = snd_soc_register_codec(dev, &adav80x_codec_driver, + adav80x_dais, ARRAY_SIZE(adav80x_dais)); + if (ret) + kfree(adav80x); + + return ret; +} + +static int __devexit adav80x_bus_remove(struct device *dev) +{ + snd_soc_unregister_codec(dev); + kfree(dev_get_drvdata(dev)); + return 0; +} + +#if defined(CONFIG_SPI_MASTER) +static int __devinit adav80x_spi_probe(struct spi_device *spi) +{ + return adav80x_bus_probe(&spi->dev, SND_SOC_SPI); +} + +static int __devexit adav80x_spi_remove(struct spi_device *spi) +{ + return adav80x_bus_remove(&spi->dev); +} + +static struct spi_driver adav80x_spi_driver = { + .driver = { + .name = "adav801", + .owner = THIS_MODULE, + }, + .probe = adav80x_spi_probe, + .remove = __devexit_p(adav80x_spi_remove), +}; +#endif + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static const struct i2c_device_id adav80x_id[] = { + { "adav803", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adav80x_id); + +static int __devinit adav80x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return adav80x_bus_probe(&client->dev, SND_SOC_I2C); +} + +static int __devexit adav80x_i2c_remove(struct i2c_client *client) +{ + return adav80x_bus_remove(&client->dev); +} + +static struct i2c_driver adav80x_i2c_driver = { + .driver = { + .name = "adav803", + .owner = THIS_MODULE, + }, + .probe = adav80x_i2c_probe, + .remove = __devexit_p(adav80x_i2c_remove), + .id_table = adav80x_id, +}; +#endif + +static int __init adav80x_init(void) +{ + int ret = 0; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&adav80x_i2c_driver); + if (ret) + return ret; +#endif + +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&adav80x_spi_driver); +#endif + + return ret; +} +module_init(adav80x_init); + +static void __exit adav80x_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&adav80x_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&adav80x_spi_driver); +#endif +} +module_exit(adav80x_exit); + +MODULE_DESCRIPTION("ASoC ADAV80x driver"); +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_AUTHOR("Yi Li >"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adav80x.h b/sound/soc/codecs/adav80x.h new file mode 100644 index 000000000000..adb0fc76d4e3 --- /dev/null +++ b/sound/soc/codecs/adav80x.h @@ -0,0 +1,35 @@ +/* + * header file for ADAV80X parts + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _ADAV80X_H +#define _ADAV80X_H + +enum adav80x_pll_src { + ADAV80X_PLL_SRC_XIN, + ADAV80X_PLL_SRC_XTAL, + ADAV80X_PLL_SRC_MCLKI, +}; + +enum adav80x_pll { + ADAV80X_PLL1 = 0, + ADAV80X_PLL2 = 1, +}; + +enum adav80x_clk_src { + ADAV80X_CLK_XIN = 0, + ADAV80X_CLK_MCLKI = 1, + ADAV80X_CLK_PLL1 = 2, + ADAV80X_CLK_PLL2 = 3, + ADAV80X_CLK_XTAL = 6, + + ADAV80X_CLK_SYSCLK1 = 6, + ADAV80X_CLK_SYSCLK2 = 7, + ADAV80X_CLK_SYSCLK3 = 8, +}; + +#endif From aef05294df7ac2b55920dd40977bf9835d4bf3f1 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 27 Jun 2011 17:04:02 +0200 Subject: [PATCH 05/14] ASoC: Blackfin: Add machine driver for EVAL-ADAV80X boards Add a machine driver to support the EVAL-ADAV801 and EVAL-ADAV803 boards connected to a Analog Devices BF5XX evaluation board. Signed-off-by: Lars-Peter Clausen Acked-by: Mike Frysinger Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/blackfin/Kconfig | 13 ++ sound/soc/blackfin/Makefile | 2 + sound/soc/blackfin/bfin-eval-adav80x.c | 173 +++++++++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 sound/soc/blackfin/bfin-eval-adav80x.c diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index fa6a6d2abffc..fe9d548a6837 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_ADAV80X + tristate "Support for the EVAL-ADAV80X boards on Blackfin eval boards" + depends on SND_BF5XX_I2S && (SPI_MASTER || I2C) + select SND_BF5XX_SOC_I2S + select SND_SOC_ADAV80X + help + Say Y if you want to add support for the Analog Devices EVAL-ADAV801 or + EVAL-ADAV803 board connected to one of the Blackfin evaluation boards + like the BF5XX-STAMP or BF5XX-EZKIT. + + Note: This driver assumes that the ADAV80X digital record and playback + interfaces are connected to the first SPORT port on the BF5XX board. + config SND_BF5XX_SOC_AD73311 tristate "SoC AD73311 Audio support for Blackfin" depends on SND_BF5XX_I2S diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index f01bff63177d..6018bf52a234 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -22,6 +22,7 @@ snd-ssm2602-objs := bf5xx-ssm2602.o snd-ad73311-objs := bf5xx-ad73311.o snd-ad193x-objs := bf5xx-ad193x.o snd-soc-bfin-eval-adau1701-objs := bfin-eval-adau1701.o +snd-soc-bfin-eval-adav80x-objs := bfin-eval-adav80x.o obj-$(CONFIG_SND_BF5XX_SOC_AD1836) += snd-ad1836.o obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o @@ -29,3 +30,4 @@ 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_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-adav80x.c b/sound/soc/blackfin/bfin-eval-adav80x.c new file mode 100644 index 000000000000..8d014d01626e --- /dev/null +++ b/sound/soc/blackfin/bfin-eval-adav80x.c @@ -0,0 +1,173 @@ +/* + * Machine driver for EVAL-ADAV801 and EVAL-ADAV803 on Analog Devices bfin + * evaluation boards. + * + * Copyright 2011 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "../codecs/adav80x.h" + +static const struct snd_soc_dapm_widget bfin_eval_adav80x_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const struct snd_soc_dapm_route bfin_eval_adav80x_dapm_routes[] = { + { "Line Out", NULL, "VOUTL" }, + { "Line Out", NULL, "VOUTR" }, + + { "VINL", NULL, "Line In" }, + { "VINR", NULL, "Line In" }, +}; + +static int bfin_eval_adav80x_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; + + 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; + + ret = snd_soc_dai_set_pll(codec_dai, ADAV80X_PLL1, ADAV80X_PLL_SRC_XTAL, + 27000000, params_rate(params) * 256); + if (ret) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_PLL1, + params_rate(params) * 256, SND_SOC_CLOCK_IN); + + return ret; +} + +static int bfin_eval_adav80x_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK1, 0, + SND_SOC_CLOCK_OUT); + snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK2, 0, + SND_SOC_CLOCK_OUT); + snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_SYSCLK3, 0, + SND_SOC_CLOCK_OUT); + + snd_soc_dai_set_sysclk(codec_dai, ADAV80X_CLK_XTAL, 2700000, 0); + + return 0; +} + +static struct snd_soc_ops bfin_eval_adav80x_ops = { + .hw_params = bfin_eval_adav80x_hw_params, +}; + +static struct snd_soc_dai_link bfin_eval_adav80x_dais[] = { + { + .name = "adav80x", + .stream_name = "ADAV80x HiFi", + .cpu_dai_name = "bfin-i2s.0", + .codec_dai_name = "adav80x-hifi", + .platform_name = "bfin-i2s-pcm-audio", + .init = bfin_eval_adav80x_codec_init, + .ops = &bfin_eval_adav80x_ops, + }, +}; + +static struct snd_soc_card bfin_eval_adav80x = { + .name = "bfin-eval-adav80x", + .dai_link = bfin_eval_adav80x_dais, + .num_links = ARRAY_SIZE(bfin_eval_adav80x_dais), + + .dapm_widgets = bfin_eval_adav80x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(bfin_eval_adav80x_dapm_widgets), + .dapm_routes = bfin_eval_adav80x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(bfin_eval_adav80x_dapm_routes), +}; + +enum bfin_eval_adav80x_type { + BFIN_EVAL_ADAV801, + BFIN_EVAL_ADAV803, +}; + +static int bfin_eval_adav80x_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &bfin_eval_adav80x; + const char *codec_name; + + switch (platform_get_device_id(pdev)->driver_data) { + case BFIN_EVAL_ADAV801: + codec_name = "spi0.1"; + break; + case BFIN_EVAL_ADAV803: + codec_name = "adav803.0-0034"; + break; + default: + return -EINVAL; + } + + bfin_eval_adav80x_dais[0].codec_name = codec_name; + + card->dev = &pdev->dev; + + return snd_soc_register_card(&bfin_eval_adav80x); +} + +static int __devexit bfin_eval_adav80x_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_unregister_card(card); + + return 0; +} + +static const struct platform_device_id bfin_eval_adav80x_ids[] = { + { "bfin-eval-adav801", BFIN_EVAL_ADAV801 }, + { "bfin-eval-adav803", BFIN_EVAL_ADAV803 }, + { }, +}; +MODULE_DEVICE_TABLE(platform, bfin_eval_adav80x_ids); + +static struct platform_driver bfin_eval_adav80x_driver = { + .driver = { + .name = "bfin-eval-adav80x", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, + .probe = bfin_eval_adav80x_probe, + .remove = __devexit_p(bfin_eval_adav80x_remove), + .id_table = bfin_eval_adav80x_ids, +}; + +static int __init bfin_eval_adav80x_init(void) +{ + return platform_driver_register(&bfin_eval_adav80x_driver); +} +module_init(bfin_eval_adav80x_init); + +static void __exit bfin_eval_adav80x_exit(void) +{ + platform_driver_unregister(&bfin_eval_adav80x_driver); +} +module_exit(bfin_eval_adav80x_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ALSA SoC bfin adav80x driver"); +MODULE_LICENSE("GPL"); From e999dc50404d401150a5429b6459473a691fd1a0 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 13 Jun 2011 12:14:07 +0100 Subject: [PATCH 06/14] ASoC: Fix Blackfin I2S _pointer() implementation return in bounds values The Blackfin DMA controller can report one frame beyond the end of the buffer in the wraparound case but ALSA requires that the pointer always be in the buffer. Do the wraparound to handle this. A similar bug is likely to apply to the other Blackfin PCM drivers but the code is less obvious to inspection and I don't have a user to test. Reported-by: Kieran O'Leary Acked-by: Liam Girdwood Signed-off-by: Mark Brown Cc: stable@kernel.org --- sound/soc/blackfin/bf5xx-i2s-pcm.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c index b5101efd1c87..f1fd95bb6416 100644 --- a/sound/soc/blackfin/bf5xx-i2s-pcm.c +++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c @@ -138,11 +138,20 @@ static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) pr_debug("%s enter\n", __func__); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { diff = sport_curr_offset_tx(sport); - frames = bytes_to_frames(substream->runtime, diff); } else { diff = sport_curr_offset_rx(sport); - frames = bytes_to_frames(substream->runtime, diff); } + + /* + * TX at least can report one frame beyond the end of the + * buffer if we hit the wraparound case - clamp to within the + * buffer as the ALSA APIs require. + */ + if (diff == snd_pcm_lib_buffer_bytes(substream)) + diff = 0; + + frames = bytes_to_frames(substream->runtime, diff); + return frames; } From d5b040c92da5ae4d5d39987850d17304e17d8e79 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 7 Jun 2011 23:28:45 +0100 Subject: [PATCH 07/14] ASoC: Correct left/right swap in wm_hubs DC offset correction It was consistently wrong for everything except WM8993 so should be no functional change. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm_hubs.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 4ebc131b7f36..2d6c88b68a14 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -133,9 +133,9 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) break; case 1: reg = snd_soc_read(codec, WM8993_DC_SERVO_3); - reg_l = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK) + reg_r = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK) >> WM8993_DCS_DAC_WR_VAL_1_SHIFT; - reg_r = reg & WM8993_DCS_DAC_WR_VAL_0_MASK; + reg_l = reg & WM8993_DCS_DAC_WR_VAL_0_MASK; break; default: WARN(1, "Unknown DCS readback method\n"); @@ -149,13 +149,13 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) dev_dbg(codec->dev, "Applying %d code DC servo correction\n", hubs->dcs_codes); - /* HPOUT1L */ - offset = reg_l; + /* HPOUT1R */ + offset = reg_r; offset += hubs->dcs_codes; dcs_cfg = (u8)offset << WM8993_DCS_DAC_WR_VAL_1_SHIFT; - /* HPOUT1R */ - offset = reg_r; + /* HPOUT1L */ + offset = reg_l; offset += hubs->dcs_codes; dcs_cfg |= (u8)offset; @@ -167,8 +167,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) WM8993_DCS_TRIG_DAC_WR_0 | WM8993_DCS_TRIG_DAC_WR_1); } else { - dcs_cfg = reg_l << WM8993_DCS_DAC_WR_VAL_1_SHIFT; - dcs_cfg |= reg_r; + dcs_cfg = reg_r << WM8993_DCS_DAC_WR_VAL_1_SHIFT; + dcs_cfg |= reg_l; } /* Save the callibrated offset if we're in class W mode and From 4e8e78e37c615e7904f51e62b5a06cb8fa3b3b53 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 24 Jun 2011 12:44:45 +0100 Subject: [PATCH 08/14] ASoC: Change WM9081 speaker output enable to _OUT_DRV More for neatness than any actual performance improvement. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm9081.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 91c6b39de50c..a4691321f9b3 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -727,7 +727,7 @@ SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0, SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0), SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0), -SND_SOC_DAPM_PGA("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0), +SND_SOC_DAPM_OUT_DRV("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0), SND_SOC_DAPM_OUTPUT("LINEOUT"), SND_SOC_DAPM_OUTPUT("SPKN"), From 67d0c479d9392a89dd9ecf9d7b92148cc2251b00 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 29 Jun 2011 14:07:24 -0700 Subject: [PATCH 09/14] ASoC: Improve error reporting in Speyside WM8962 driver Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/samsung/speyside_wm8962.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/samsung/speyside_wm8962.c b/sound/soc/samsung/speyside_wm8962.c index c0ba0bfd7f57..8ac42bf82090 100644 --- a/sound/soc/samsung/speyside_wm8962.c +++ b/sound/soc/samsung/speyside_wm8962.c @@ -30,14 +30,16 @@ static int speyside_wm8962_set_bias_level(struct snd_soc_card *card, WM8962_FLL_MCLK, 32768, 44100 * 256); if (ret < 0) - pr_err("Failed to start FLL\n"); + pr_err("Failed to start FLL: %d\n", ret); ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_FLL, 44100 * 256, SND_SOC_CLOCK_IN); - if (ret < 0) + if (ret < 0) { + pr_err("Failed to set SYSCLK: %d\n"); return ret; + } } break; @@ -59,13 +61,15 @@ static int speyside_wm8962_set_bias_level_post(struct snd_soc_card *card, case SND_SOC_BIAS_STANDBY: ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK, 32768, SND_SOC_CLOCK_IN); - if (ret < 0) + if (ret < 0) { + pr_err("Failed to switch away from FLL: %d\n", ret); return ret; + } ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, 0, 0, 0); if (ret < 0) { - pr_err("Failed to stop FLL\n"); + pr_err("Failed to stop FLL: %d\n", ret); return ret; } break; From 956245e9cd229138ad26521c326650eed8b88333 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 1 Jul 2011 16:54:08 +0100 Subject: [PATCH 10/14] ASoC: core - Make platform probe more like codec probe. In preparation for ASoC dynamic PCM support (AKA ASoC DSP) Platform will also support DAPM so separate out the probe function to simplify the code (just like the codec probe). Signed-off-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 51 +++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 32bc50387f61..d49f0ed56f9c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -986,6 +986,39 @@ err_probe: return ret; } +static int soc_probe_platform(struct snd_soc_card *card, + struct snd_soc_platform *platform) +{ + int ret = 0; + const struct snd_soc_platform_driver *driver = platform->driver; + + platform->card = card; + + if (!try_module_get(platform->dev->driver->owner)) + return -ENODEV; + + if (driver->probe) { + ret = driver->probe(platform); + if (ret < 0) { + dev_err(platform->dev, + "asoc: failed to probe platform %s: %d\n", + platform->name, ret); + goto err_probe; + } + } + + /* mark platform as probed and add to card platform list */ + platform->probed = 1; + list_add(&platform->card_list, &card->platform_dev_list); + + return 0; + +err_probe: + module_put(platform->dev->driver->owner); + + return ret; +} + static void rtd_release(struct device *dev) {} static int soc_post_component_init(struct snd_soc_card *card, @@ -1109,21 +1142,9 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order) /* probe the platform */ if (!platform->probed && platform->driver->probe_order == order) { - if (!try_module_get(platform->dev->driver->owner)) - return -ENODEV; - - if (platform->driver->probe) { - ret = platform->driver->probe(platform); - if (ret < 0) { - printk(KERN_ERR "asoc: failed to probe platform %s\n", - platform->name); - module_put(platform->dev->driver->owner); - return ret; - } - } - /* mark platform as probed and add to card platform list */ - platform->probed = 1; - list_add(&platform->card_list, &card->platform_dev_list); + ret = soc_probe_platform(card, platform); + if (ret < 0) + return ret; } /* probe the CODEC DAI */ From 713d1369789f2a2336c3431b15276c968862bdb7 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 1 Jul 2011 13:56:13 -0600 Subject: [PATCH 11/14] ASoC: Tegra: I2S: Ensure clock is enabled when writing regs The I2S controller needs a clock to respond to register writes. Without this, register writes will at worst hang the CPU. In practice, I've only observed writes being dropped. Luckily, the dropped register writes historically had no effect: TEGRA_I2S_TIMING: The value we wrote was the reset default. TEGRA_I2S_FIFO_SCR: The default was for the FIFOs to request more data when one slot was empty. The requested value was for the FIFOs to request when four slots were empty. The DMA controller in the mainline kernel is configured to burst a single entry at a time into the FIFO, hence there was no issue. The only negative effect was on bus efficiency losses due to an increased number of arbitration attempts. However, in various non-upstream changes, the DMA controller now bursts four entries at a time into the FIFO. If there is only space for one entry, the data is simply dropped. In practice, this resulted in 3/4 of samples being dropped, and playback at 4x the expected rate and pitch. By fixing the clocking issue, this is solved. Signed-off-by: Stephen Warren Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/tegra/tegra_i2s.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c index 6b817e20548c..95f03c10b4f7 100644 --- a/sound/soc/tegra/tegra_i2s.c +++ b/sound/soc/tegra/tegra_i2s.c @@ -222,12 +222,18 @@ static int tegra_i2s_hw_params(struct snd_pcm_substream *substream, if (i2sclock % (2 * srate)) reg |= TEGRA_I2S_TIMING_NON_SYM_ENABLE; + if (!i2s->clk_refs) + clk_enable(i2s->clk_i2s); + tegra_i2s_write(i2s, TEGRA_I2S_TIMING, reg); tegra_i2s_write(i2s, TEGRA_I2S_FIFO_SCR, TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS | TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS); + if (!i2s->clk_refs) + clk_disable(i2s->clk_i2s); + return 0; } From b5f9cfed12dc639cce5a8b6e741352980e05a8a9 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Fri, 1 Jul 2011 13:56:14 -0600 Subject: [PATCH 12/14] ASoC: Tegra: I2S: s/clk_get_sys/clk_get/ The clock needed by the I2S driver is associated with the I2S device name in the standard fashion. Hence, use clk_get(dev) instead of clk_get_sys(clk_name). Signed-off-by: Stephen Warren Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/tegra/tegra_i2s.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c index 6b817e20548c..2e5685512db0 100644 --- a/sound/soc/tegra/tegra_i2s.c +++ b/sound/soc/tegra/tegra_i2s.c @@ -348,7 +348,6 @@ struct snd_soc_dai_driver tegra_i2s_dai[] = { static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev) { struct tegra_i2s * i2s; - char clk_name[12]; /* tegra-i2s.0 */ struct resource *mem, *memregion, *dmareq; int ret; @@ -383,8 +382,7 @@ static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev) } dev_set_drvdata(&pdev->dev, i2s); - snprintf(clk_name, sizeof(clk_name), DRV_NAME ".%d", pdev->id); - i2s->clk_i2s = clk_get_sys(clk_name, NULL); + i2s->clk_i2s = clk_get(&pdev->dev, NULL); if (IS_ERR(i2s->clk_i2s)) { dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); ret = PTR_ERR(i2s->clk_i2s); From 8e9ddf811ba021506d2316fcfe619faa0ab3f567 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 1 Jul 2011 17:24:46 -0700 Subject: [PATCH 13/14] ASoC: Ensure we delay long enough for WM8994 FLL to lock when starting This delay is very conservative. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Cc: stable@kernel.org --- sound/soc/codecs/wm8994.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 970a95c5360b..c2fc0356c2a4 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -1713,6 +1713,8 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src, snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset, WM8994_FLL1_ENA | WM8994_FLL1_FRAC, reg); + + msleep(5); } wm8994->fll[id].in = freq_in; From f1442bc1e9bd5ff4c2470d66075d066e535a2c86 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 4 Jul 2011 11:10:15 +0100 Subject: [PATCH 14/14] ASoC: core - Add platform read and write. In preparation for ASoC Dynamic PCM (AKA DSP) support. Allow platform driver to perform IO. Intended for platform DAPM. Signed-off-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc.h | 8 ++++++++ sound/soc/soc-core.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/include/sound/soc.h b/include/sound/soc.h index 6424b10ac29c..f30f3fe4a41b 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -312,6 +312,10 @@ int snd_soc_default_readable_register(struct snd_soc_codec *codec, unsigned int reg); int snd_soc_default_writable_register(struct snd_soc_codec *codec, unsigned int reg); +int snd_soc_platform_read(struct snd_soc_platform *platform, + unsigned int reg); +int snd_soc_platform_write(struct snd_soc_platform *platform, + unsigned int reg, unsigned int val); /* Utility functions to get clock rates from various things */ int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots); @@ -658,6 +662,10 @@ struct snd_soc_platform_driver { /* probe ordering - for components with runtime dependencies */ int probe_order; int remove_order; + + /* platform IO - used for platform DAPM */ + unsigned int (*read)(struct snd_soc_platform *, unsigned int); + int (*write)(struct snd_soc_platform *, unsigned int, unsigned int); }; struct snd_soc_platform { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index d49f0ed56f9c..d08abf4e3eb3 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1640,6 +1640,36 @@ int snd_soc_codec_writable_register(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(snd_soc_codec_writable_register); +int snd_soc_platform_read(struct snd_soc_platform *platform, + unsigned int reg) +{ + unsigned int ret; + + if (!platform->driver->read) { + dev_err(platform->dev, "platform has no read back\n"); + return -1; + } + + ret = platform->driver->read(platform, reg); + dev_dbg(platform->dev, "read %x => %x\n", reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_platform_read); + +int snd_soc_platform_write(struct snd_soc_platform *platform, + unsigned int reg, unsigned int val) +{ + if (!platform->driver->write) { + dev_err(platform->dev, "platform has no write back\n"); + return -1; + } + + dev_dbg(platform->dev, "write %x = %x\n", reg, val); + return platform->driver->write(platform, reg, val); +} +EXPORT_SYMBOL_GPL(snd_soc_platform_write); + /** * snd_soc_new_ac97_codec - initailise AC97 device * @codec: audio codec