diff options
author | Daniel Thompson <daniel.thompson@linaro.org> | 2017-07-30 08:09:38 +0100 |
---|---|---|
committer | Daniel Thompson <daniel.thompson@linaro.org> | 2017-07-31 14:14:34 +0100 |
commit | 40600bf3a14bda555279b08f3d4f71d696097dfc (patch) | |
tree | ef2a81b3783225f7dbade370907cf432bc9727da | |
parent | 2c3289c7071a4461dc92faac12be7097fbf3457a (diff) |
sound: soc: rdafpga: RDA support
-rw-r--r-- | sound/soc/rdafpga/Kconfig | 28 | ||||
-rw-r--r-- | sound/soc/rdafpga/Makefile | 14 | ||||
-rw-r--r-- | sound/soc/rdafpga/aif.h | 244 | ||||
-rw-r--r-- | sound/soc/rdafpga/aif_asm.h | 211 | ||||
-rw-r--r-- | sound/soc/rdafpga/aud_ifc.c | 369 | ||||
-rw-r--r-- | sound/soc/rdafpga/aud_ifc.h | 99 | ||||
-rw-r--r-- | sound/soc/rdafpga/bb_ifc.h | 209 | ||||
-rw-r--r-- | sound/soc/rdafpga/bb_ifc_asm.h | 202 | ||||
-rw-r--r-- | sound/soc/rdafpga/rdafpga_dai.c | 536 | ||||
-rw-r--r-- | sound/soc/rdafpga/rdafpga_pcm.c | 410 | ||||
-rw-r--r-- | sound/soc/rdafpga/rdafpga_pcm.h | 41 | ||||
-rw-r--r-- | sound/soc/rdafpga/rdafpga_soundcard.c | 129 | ||||
-rw-r--r-- | sound/soc/rdafpga/tlv320aic23_spi.c | 861 | ||||
-rw-r--r-- | sound/soc/rdafpga/tlv320aic23_spi.h | 119 |
14 files changed, 3472 insertions, 0 deletions
diff --git a/sound/soc/rdafpga/Kconfig b/sound/soc/rdafpga/Kconfig new file mode 100644 index 000000000000..9e5fa9ee2bb7 --- /dev/null +++ b/sound/soc/rdafpga/Kconfig @@ -0,0 +1,28 @@ +menu "Soc Audio for the RDA FPGA chips." + + +config SND_RDA_SOC_FPGA + tristate "SoC Audio for the RDA FPGA" + depends on ARCH_RDA + +config SND_RDA_SOC_RDASOUNDCARD_FPGA + tristate "rda FPGA sound card -- SoC Audio support for RAD FPGA boards." + depends on SND_RDA_SOC_FPGA + select SND_RDA_SOC_RDASOUNDCARD_FPGA_PCM + select SND_RDA_SOC_RDASOUNDCARD_FPGA_DAI + select SND_SOC_TLV320AIC23 + help + Say Y if you want to build in to kernel, Say M for module. + +config SND_RDA_SOC_RDASOUNDCARD_FPGA_PCM + tristate + + +config SND_RDA_SOC_RDASOUNDCARD_FPGA_DAI + tristate + +config SND_SOC_TLV320AIC23 + tristate + + +endmenu diff --git a/sound/soc/rdafpga/Makefile b/sound/soc/rdafpga/Makefile new file mode 100644 index 000000000000..f2406df531a3 --- /dev/null +++ b/sound/soc/rdafpga/Makefile @@ -0,0 +1,14 @@ +# rda Platform Support + +rda-audio-ifc-objs := aud_ifc.o +audio-codec-spi-objs := tlv320aic23_spi.o +snd-rda-rdasoundcard-fpba-objs := rdafpga_soundcard.o +snd-rda-rdasoundcard-fpba-pcm-objs := rdafpga_pcm.o +snd-rda-rdasoundcard-fpba-dai-objs := rdafpga_dai.o + +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_FPGA) += rda-audio-ifc.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_FPGA) += audio-codec-spi.o + +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_FPGA) += snd-rda-rdasoundcard-fpba.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_FPGA_PCM) += snd-rda-rdasoundcard-fpba-pcm.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_FPGA_DAI) += snd-rda-rdasoundcard-fpba-dai.o diff --git a/sound/soc/rdafpga/aif.h b/sound/soc/rdafpga/aif.h new file mode 100644 index 000000000000..283c70ed9a5b --- /dev/null +++ b/sound/soc/rdafpga/aif.h @@ -0,0 +1,244 @@ +/* + * aif.h- RDA audio interface + * + * Copyright (C) 2013 RDA Microelectronics 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#ifndef _AIF_H_ +#define _AIF_H_ + +#ifdef CT_ASM +#error "You are trying to use in an assembly code the normal H description of 'aif'." +#endif + + + +// ============================================================================= +// MACROS +// ============================================================================= + +// ============================================================================ +// AIF_SAMPLING_RATE_T +// ----------------------------------------------------------------------------- +/// +// ============================================================================= +typedef enum +{ + AIF_8K = 0x00000000, + AIF_11K025 = 0x00000001, + AIF_12K = 0x00000002, + AIF_16K = 0x00000003, + AIF_22K05 = 0x00000004, + AIF_24K = 0x00000005, + AIF_32K = 0x00000006, + AIF_44K1 = 0x00000007, + AIF_48K = 0x00000008 +} AIF_SAMPLING_RATE_T; + +#define AIF_RX_FIFO_SIZE (4) +#define AIF_TX_FIFO_SIZE (4) + +// ============================================================================= +// TYPES +// ============================================================================= + +// ============================================================================ +// AIF_T +// ----------------------------------------------------------------------------- +/// +// ============================================================================= +#define REG_AP_AIF_BASE 0x009E0000 + +typedef volatile struct +{ + REG32 data; //0x00000000 + REG32 ctrl; //0x00000004 + REG32 serial_ctrl; //0x00000008 + REG32 tone; //0x0000000C + REG32 side_tone; //0x00000010 + REG32 Cfg_Clk_AudioBCK; //0x00000014 + REG32 Cfg_Aif_Tx_Stb; //0x00000018 +} HWP_AIF_T; + +//#define hwp_apAif ((HWP_AIF_T*) KSEG1(REG_AP_AIF_BASE)) + + +//data +#define AIF_DATA0(n) (((n)&0xFFFF)<<0) +#define AIF_DATA1(n) (((n)&0xFFFF)<<16) + +//ctrl +#define AIF_ENABLE (1<<0) +#define AIF_ENABLE_ENABLE (1<<0) +#define AIF_ENABLE_DISABLE (0<<0) +#define AIF_TX_OFF (1<<4) +#define AIF_TX_OFF_TX_ON (0<<4) +#define AIF_TX_OFF_TX_OFF (1<<4) +#define AIF_PARALLEL_OUT_SET (1<<8) +#define AIF_PARALLEL_OUT_SET_SERL (0<<8) +#define AIF_PARALLEL_OUT_SET_PARA (1<<8) +#define AIF_PARALLEL_OUT_CLR (1<<9) +#define AIF_PARALLEL_OUT_CLR_SERL (0<<9) +#define AIF_PARALLEL_OUT_CLR_PARA (1<<9) +#define AIF_PARALLEL_IN_SET (1<<10) +#define AIF_PARALLEL_IN_SET_SERL (0<<10) +#define AIF_PARALLEL_IN_SET_PARA (1<<10) +#define AIF_PARALLEL_IN_CLR (1<<11) +#define AIF_PARALLEL_IN_CLR_SERL (0<<11) +#define AIF_PARALLEL_IN_CLR_PARA (1<<11) +#define AIF_TX_STB_MODE (1<<12) +#define AIF_PARALLEL_IN2_EN (1<<13) +#define AIF_PARALLEL_IN2_EN_DISABLE (0<<13) +#define AIF_PARALLEL_IN2_EN_ENABLE (1<<13) +#define AIF_OUT_UNDERFLOW (1<<16) +#define AIF_IN_OVERFLOW (1<<17) +#define AIF_LOOP_BACK (1<<31) +#define AIF_LOOP_BACK_NORMAL (0<<31) +#define AIF_LOOP_BACK_LOOPBACK (1<<31) + +//serial_ctrl +#define AIF_SERIAL_MODE(n) (((n)&3)<<0) +#define AIF_SERIAL_MODE_I2S_PCM (0<<0) +#define AIF_SERIAL_MODE_VOICE (1<<0) +#define AIF_SERIAL_MODE_DAI (2<<0) +#define AIF_I2S_IN_SEL(n) (((n)&3)<<2) +#define AIF_I2S_IN_SEL_I2S_IN_0 (0<<2) +#define AIF_I2S_IN_SEL_I2S_IN_1 (1<<2) +#define AIF_I2S_IN_SEL_I2S_IN_2 (2<<2) +#define AIF_MASTER_MODE (1<<4) +#define AIF_MASTER_MODE_SLAVE (0<<4) +#define AIF_MASTER_MODE_MASTER (1<<4) +#define AIF_LSB (1<<5) +#define AIF_LSB_MSB (0<<5) +#define AIF_LSB_LSB (1<<5) +#define AIF_LRCK_POL (1<<6) +#define AIF_LRCK_POL_LEFT_H_RIGHT_L (0<<6) +#define AIF_LRCK_POL_LEFT_L_RIGHT_H (1<<6) +#define AIF_RX_DLY(n) (((n)&3)<<8) +#define AIF_RX_DLY_ALIGN (0<<8) +#define AIF_RX_DLY_DLY_1 (1<<8) +#define AIF_RX_DLY_DLY_2 (2<<8) +#define AIF_RX_DLY_DLY_3 (3<<8) +#define AIF_TX_DLY (1<<10) +#define AIF_TX_DLY_ALIGN (0<<10) +#define AIF_TX_DLY_DLY_1 (1<<10) +#define AIF_TX_DLY_S (1<<11) +#define AIF_TX_DLY_S_NO_DLY (0<<11) +#define AIF_TX_DLY_S_DLY (1<<11) +#define AIF_TX_MODE(n) (((n)&3)<<12) +#define AIF_TX_MODE_STEREO_STEREO (0<<12) +#define AIF_TX_MODE_MONO_STEREO_CHAN_L (1<<12) +#define AIF_TX_MODE_MONO_STEREO_DUPLI (2<<12) +#define AIF_TX_MODE_STEREO_TO_MONO (3<<12) +#define AIF_RX_MODE (1<<14) +#define AIF_RX_MODE_STEREO_STEREO (0<<14) +#define AIF_RX_MODE_STEREO_MONO_FROM_L (1<<14) +#define AIF_BCK_LRCK(n) (((n)&31)<<16) +#define AIF_BCK_LRCK_BCK_LRCK_16 (0<<16) +#define AIF_BCK_LRCK_BCK_LRCK_17 (1<<16) +#define AIF_BCK_LRCK_BCK_LRCK_18 (2<<16) +#define AIF_BCK_LRCK_BCK_LRCK_19 (3<<16) +#define AIF_BCK_LRCK_BCK_LRCK_20 (4<<16) +#define AIF_BCK_LRCK_BCK_LRCK_21 (5<<16) +#define AIF_BCK_LRCK_BCK_LRCK_22 (6<<16) +#define AIF_BCK_LRCK_BCK_LRCK_23 (7<<16) +#define AIF_BCK_LRCK_BCK_LRCK_24 (8<<16) +#define AIF_BCK_LRCK_BCK_LRCK_25 (9<<16) +#define AIF_BCK_LRCK_BCK_LRCK_26 (10<<16) +#define AIF_BCK_LRCK_BCK_LRCK_27 (11<<16) +#define AIF_BCK_LRCK_BCK_LRCK_28 (12<<16) +#define AIF_BCK_LRCK_BCK_LRCK_29 (13<<16) +#define AIF_BCK_LRCK_BCK_LRCK_30 (14<<16) +#define AIF_BCK_LRCK_BCK_LRCK_31 (15<<16) +#define AIF_BCK_LRCK_BCK_LRCK_32 (16<<16) +#define AIF_BCK_LRCK_BCK_LRCK_33 (17<<16) +#define AIF_BCK_LRCK_BCK_LRCK_34 (18<<16) +#define AIF_BCK_LRCK_BCK_LRCK_35 (19<<16) +#define AIF_BCK_LRCK_BCK_LRCK_36 (20<<16) +#define AIF_BCK_LRCK_BCK_LRCK_37 (21<<16) +#define AIF_BCK_LRCK_BCK_LRCK_38 (22<<16) +#define AIF_BCK_LRCK_BCK_LRCK_39 (23<<16) +#define AIF_BCK_LRCK_BCK_LRCK_40 (24<<16) +#define AIF_BCK_LRCK_BCK_LRCK_41 (25<<16) +#define AIF_BCK_LRCK_BCK_LRCK_42 (26<<16) +#define AIF_BCK_LRCK_BCK_LRCK_43 (27<<16) +#define AIF_BCK_LRCK_BCK_LRCK_44 (28<<16) +#define AIF_BCK_LRCK_BCK_LRCK_45 (29<<16) +#define AIF_BCK_LRCK_BCK_LRCK_46 (30<<16) +#define AIF_BCK_LRCK_BCK_LRCK_47 (31<<16) +#define AIF_OUTPUT_HALF_CYCLE_DLY (1<<25) +#define AIF_OUTPUT_HALF_CYCLE_DLY_NO_DLY (0<<25) +#define AIF_OUTPUT_HALF_CYCLE_DLY_DLY (1<<25) +#define AIF_INPUT_HALF_CYCLE_DLY (1<<26) +#define AIF_INPUT_HALF_CYCLE_DLY_NO_DLY (0<<26) +#define AIF_INPUT_HALF_CYCLE_DLY_DLY (1<<26) +#define AIF_BCKOUT_GATE (1<<28) +#define AIF_BCKOUT_GATE_NO_GATE (0<<28) +#define AIF_BCKOUT_GATE_GATED (1<<28) + +//tone +#define AIF_ENABLE_H (1<<0) +#define AIF_ENABLE_H_DISABLE (0<<0) +#define AIF_ENABLE_H_ENABLE (1<<0) +#define AIF_TONE_SELECT (1<<1) +#define AIF_TONE_SELECT_DTMF (0<<1) +#define AIF_TONE_SELECT_COMFORT_TONE (1<<1) +#define AIF_DTMF_FREQ_COL(n) (((n)&3)<<4) +#define AIF_DTMF_FREQ_COL_1209_HZ (0<<4) +#define AIF_DTMF_FREQ_COL_1336_HZ (1<<4) +#define AIF_DTMF_FREQ_COL_1477_HZ (2<<4) +#define AIF_DTMF_FREQ_COL_1633_HZ (3<<4) +#define AIF_DTMF_FREQ_ROW(n) (((n)&3)<<6) +#define AIF_DTMF_FREQ_ROW_697_HZ (0<<6) +#define AIF_DTMF_FREQ_ROW_770_HZ (1<<6) +#define AIF_DTMF_FREQ_ROW_852_HZ (2<<6) +#define AIF_DTMF_FREQ_ROW_941_HZ (3<<6) +#define AIF_COMFORT_FREQ(n) (((n)&3)<<8) +#define AIF_COMFORT_FREQ_425_HZ (0<<8) +#define AIF_COMFORT_FREQ_950_HZ (1<<8) +#define AIF_COMFORT_FREQ_1400_HZ (2<<8) +#define AIF_COMFORT_FREQ_1800_HZ (3<<8) +#define AIF_TONE_GAIN(n) (((n)&3)<<12) +#define AIF_TONE_GAIN_0_DB (0<<12) +#define AIF_TONE_GAIN_M3_DB (1<<12) +#define AIF_TONE_GAIN_M9_DB (2<<12) +#define AIF_TONE_GAIN_M15_DB (3<<12) + +//side_tone +#define AIF_SIDE_TONE_GAIN(n) (((n)&15)<<0) + +//Cfg_Clk_AudioBCK +#define AIF_AUDIOBCK_DIVIDER(n) (((n)&0x7FF)<<0) +#define AIF_BCK_POL (1<<16) +#define AIF_BCK_POL_NORMAL (0<<16) +#define AIF_BCK_POL_INVERT (1<<16) +#define AIF_BCK_SEL_PLL (1<<20) +#define AIF_BCK_PLL_SOURCE (1<<21) +#define AIF_BCK_PLL_SOURCE_PLL_150M (0<<21) +#define AIF_BCK_PLL_SOURCE_PLL_CODEC (1<<21) + +//Cfg_Aif_Tx_Stb +#define AIF_AIF_TX_STB_DIV(n) (((n)&0x3FFF)<<0) +#define AIF_AIF_TX_STB_26M_EN (1<<30) +#define AIF_AIF_TX_STB_EN (1<<31) + + + + + +#endif + diff --git a/sound/soc/rdafpga/aif_asm.h b/sound/soc/rdafpga/aif_asm.h new file mode 100644 index 000000000000..3076f1ab6095 --- /dev/null +++ b/sound/soc/rdafpga/aif_asm.h @@ -0,0 +1,211 @@ +#ifndef _AIF_ASM_H_ +#define _AIF_ASM_H_ + +//THIS FILE HAS BEEN GENERATED WITH COOLWATCHER. PLEASE EDIT WITH CARE ! + +#ifndef CT_ASM +#error "You are trying to use in a normal C code the assembly H description of 'aif'." +#endif + + +#define AIF_8K (0) +#define AIF_11K025 (1) +#define AIF_12K (2) +#define AIF_16K (3) +#define AIF_22K05 (4) +#define AIF_24K (5) +#define AIF_32K (6) +#define AIF_44K1 (7) +#define AIF_48K (8) +#define AIF_RX_FIFO_SIZE (4) +#define AIF_TX_FIFO_SIZE (4) + +//============================================================================== +// AIF_Sampling_Rate +//------------------------------------------------------------------------------ +/// +//============================================================================== + + + +//============================================================================== +// aif +//------------------------------------------------------------------------------ +/// +//============================================================================== +#define REG_AP_AIF_BASE 0x009E0000 + +#define REG_AP_AIF_BASE_HI BASE_HI(REG_AP_AIF_BASE) +#define REG_AP_AIF_BASE_LO BASE_LO(REG_AP_AIF_BASE) + +#define REG_AP_AIF_DATA REG_AP_AIF_BASE_LO + 0x00000000 +#define REG_AP_AIF_CTRL REG_AP_AIF_BASE_LO + 0x00000004 +#define REG_AP_AIF_SERIAL_CTRL REG_AP_AIF_BASE_LO + 0x00000008 +#define REG_AP_AIF_TONE REG_AP_AIF_BASE_LO + 0x0000000C +#define REG_AP_AIF_SIDE_TONE REG_AP_AIF_BASE_LO + 0x00000010 +#define REG_AP_AIF_CFG_CLK_AUDIOBCK REG_AP_AIF_BASE_LO + 0x00000014 +#define REG_AP_AIF_CFG_AIF_TX_STB REG_AP_AIF_BASE_LO + 0x00000018 + +//data +#define AIF_DATA0(n) (((n)&0xFFFF)<<0) +#define AIF_DATA1(n) (((n)&0xFFFF)<<16) + +//ctrl +#define AIF_ENABLE (1<<0) +#define AIF_ENABLE_ENABLE (1<<0) +#define AIF_ENABLE_DISABLE (0<<0) +#define AIF_TX_OFF (1<<4) +#define AIF_TX_OFF_TX_ON (0<<4) +#define AIF_TX_OFF_TX_OFF (1<<4) +#define AIF_PARALLEL_OUT_SET (1<<8) +#define AIF_PARALLEL_OUT_SET_SERL (0<<8) +#define AIF_PARALLEL_OUT_SET_PARA (1<<8) +#define AIF_PARALLEL_OUT_CLR (1<<9) +#define AIF_PARALLEL_OUT_CLR_SERL (0<<9) +#define AIF_PARALLEL_OUT_CLR_PARA (1<<9) +#define AIF_PARALLEL_IN_SET (1<<10) +#define AIF_PARALLEL_IN_SET_SERL (0<<10) +#define AIF_PARALLEL_IN_SET_PARA (1<<10) +#define AIF_PARALLEL_IN_CLR (1<<11) +#define AIF_PARALLEL_IN_CLR_SERL (0<<11) +#define AIF_PARALLEL_IN_CLR_PARA (1<<11) +#define AIF_TX_STB_MODE (1<<12) +#define AIF_PARALLEL_IN2_EN (1<<13) +#define AIF_PARALLEL_IN2_EN_DISABLE (0<<13) +#define AIF_PARALLEL_IN2_EN_ENABLE (1<<13) +#define AIF_OUT_UNDERFLOW (1<<16) +#define AIF_IN_OVERFLOW (1<<17) +#define AIF_LOOP_BACK (1<<31) +#define AIF_LOOP_BACK_NORMAL (0<<31) +#define AIF_LOOP_BACK_LOOPBACK (1<<31) + +//serial_ctrl +#define AIF_SERIAL_MODE(n) (((n)&3)<<0) +#define AIF_SERIAL_MODE_I2S_PCM (0<<0) +#define AIF_SERIAL_MODE_VOICE (1<<0) +#define AIF_SERIAL_MODE_DAI (2<<0) +#define AIF_I2S_IN_SEL(n) (((n)&3)<<2) +#define AIF_I2S_IN_SEL_I2S_IN_0 (0<<2) +#define AIF_I2S_IN_SEL_I2S_IN_1 (1<<2) +#define AIF_I2S_IN_SEL_I2S_IN_2 (2<<2) +#define AIF_MASTER_MODE (1<<4) +#define AIF_MASTER_MODE_SLAVE (0<<4) +#define AIF_MASTER_MODE_MASTER (1<<4) +#define AIF_LSB (1<<5) +#define AIF_LSB_MSB (0<<5) +#define AIF_LSB_LSB (1<<5) +#define AIF_LRCK_POL (1<<6) +#define AIF_LRCK_POL_LEFT_H_RIGHT_L (0<<6) +#define AIF_LRCK_POL_LEFT_L_RIGHT_H (1<<6) +#define AIF_RX_DLY(n) (((n)&3)<<8) +#define AIF_RX_DLY_ALIGN (0<<8) +#define AIF_RX_DLY_DLY_1 (1<<8) +#define AIF_RX_DLY_DLY_2 (2<<8) +#define AIF_RX_DLY_DLY_3 (3<<8) +#define AIF_TX_DLY (1<<10) +#define AIF_TX_DLY_ALIGN (0<<10) +#define AIF_TX_DLY_DLY_1 (1<<10) +#define AIF_TX_DLY_S (1<<11) +#define AIF_TX_DLY_S_NO_DLY (0<<11) +#define AIF_TX_DLY_S_DLY (1<<11) +#define AIF_TX_MODE(n) (((n)&3)<<12) +#define AIF_TX_MODE_STEREO_STEREO (0<<12) +#define AIF_TX_MODE_MONO_STEREO_CHAN_L (1<<12) +#define AIF_TX_MODE_MONO_STEREO_DUPLI (2<<12) +#define AIF_TX_MODE_STEREO_TO_MONO (3<<12) +#define AIF_RX_MODE (1<<14) +#define AIF_RX_MODE_STEREO_STEREO (0<<14) +#define AIF_RX_MODE_STEREO_MONO_FROM_L (1<<14) +#define AIF_BCK_LRCK(n) (((n)&31)<<16) +#define AIF_BCK_LRCK_BCK_LRCK_16 (0<<16) +#define AIF_BCK_LRCK_BCK_LRCK_17 (1<<16) +#define AIF_BCK_LRCK_BCK_LRCK_18 (2<<16) +#define AIF_BCK_LRCK_BCK_LRCK_19 (3<<16) +#define AIF_BCK_LRCK_BCK_LRCK_20 (4<<16) +#define AIF_BCK_LRCK_BCK_LRCK_21 (5<<16) +#define AIF_BCK_LRCK_BCK_LRCK_22 (6<<16) +#define AIF_BCK_LRCK_BCK_LRCK_23 (7<<16) +#define AIF_BCK_LRCK_BCK_LRCK_24 (8<<16) +#define AIF_BCK_LRCK_BCK_LRCK_25 (9<<16) +#define AIF_BCK_LRCK_BCK_LRCK_26 (10<<16) +#define AIF_BCK_LRCK_BCK_LRCK_27 (11<<16) +#define AIF_BCK_LRCK_BCK_LRCK_28 (12<<16) +#define AIF_BCK_LRCK_BCK_LRCK_29 (13<<16) +#define AIF_BCK_LRCK_BCK_LRCK_30 (14<<16) +#define AIF_BCK_LRCK_BCK_LRCK_31 (15<<16) +#define AIF_BCK_LRCK_BCK_LRCK_32 (16<<16) +#define AIF_BCK_LRCK_BCK_LRCK_33 (17<<16) +#define AIF_BCK_LRCK_BCK_LRCK_34 (18<<16) +#define AIF_BCK_LRCK_BCK_LRCK_35 (19<<16) +#define AIF_BCK_LRCK_BCK_LRCK_36 (20<<16) +#define AIF_BCK_LRCK_BCK_LRCK_37 (21<<16) +#define AIF_BCK_LRCK_BCK_LRCK_38 (22<<16) +#define AIF_BCK_LRCK_BCK_LRCK_39 (23<<16) +#define AIF_BCK_LRCK_BCK_LRCK_40 (24<<16) +#define AIF_BCK_LRCK_BCK_LRCK_41 (25<<16) +#define AIF_BCK_LRCK_BCK_LRCK_42 (26<<16) +#define AIF_BCK_LRCK_BCK_LRCK_43 (27<<16) +#define AIF_BCK_LRCK_BCK_LRCK_44 (28<<16) +#define AIF_BCK_LRCK_BCK_LRCK_45 (29<<16) +#define AIF_BCK_LRCK_BCK_LRCK_46 (30<<16) +#define AIF_BCK_LRCK_BCK_LRCK_47 (31<<16) +#define AIF_OUTPUT_HALF_CYCLE_DLY (1<<25) +#define AIF_OUTPUT_HALF_CYCLE_DLY_NO_DLY (0<<25) +#define AIF_OUTPUT_HALF_CYCLE_DLY_DLY (1<<25) +#define AIF_INPUT_HALF_CYCLE_DLY (1<<26) +#define AIF_INPUT_HALF_CYCLE_DLY_NO_DLY (0<<26) +#define AIF_INPUT_HALF_CYCLE_DLY_DLY (1<<26) +#define AIF_BCKOUT_GATE (1<<28) +#define AIF_BCKOUT_GATE_NO_GATE (0<<28) +#define AIF_BCKOUT_GATE_GATED (1<<28) + +//tone +#define AIF_ENABLE_H (1<<0) +#define AIF_ENABLE_H_DISABLE (0<<0) +#define AIF_ENABLE_H_ENABLE (1<<0) +#define AIF_TONE_SELECT (1<<1) +#define AIF_TONE_SELECT_DTMF (0<<1) +#define AIF_TONE_SELECT_COMFORT_TONE (1<<1) +#define AIF_DTMF_FREQ_COL(n) (((n)&3)<<4) +#define AIF_DTMF_FREQ_COL_1209_HZ (0<<4) +#define AIF_DTMF_FREQ_COL_1336_HZ (1<<4) +#define AIF_DTMF_FREQ_COL_1477_HZ (2<<4) +#define AIF_DTMF_FREQ_COL_1633_HZ (3<<4) +#define AIF_DTMF_FREQ_ROW(n) (((n)&3)<<6) +#define AIF_DTMF_FREQ_ROW_697_HZ (0<<6) +#define AIF_DTMF_FREQ_ROW_770_HZ (1<<6) +#define AIF_DTMF_FREQ_ROW_852_HZ (2<<6) +#define AIF_DTMF_FREQ_ROW_941_HZ (3<<6) +#define AIF_COMFORT_FREQ(n) (((n)&3)<<8) +#define AIF_COMFORT_FREQ_425_HZ (0<<8) +#define AIF_COMFORT_FREQ_950_HZ (1<<8) +#define AIF_COMFORT_FREQ_1400_HZ (2<<8) +#define AIF_COMFORT_FREQ_1800_HZ (3<<8) +#define AIF_TONE_GAIN(n) (((n)&3)<<12) +#define AIF_TONE_GAIN_0_DB (0<<12) +#define AIF_TONE_GAIN_M3_DB (1<<12) +#define AIF_TONE_GAIN_M9_DB (2<<12) +#define AIF_TONE_GAIN_M15_DB (3<<12) + +//side_tone +#define AIF_SIDE_TONE_GAIN(n) (((n)&15)<<0) + +//Cfg_Clk_AudioBCK +#define AIF_AUDIOBCK_DIVIDER(n) (((n)&0x7FF)<<0) +#define AIF_BCK_POL (1<<16) +#define AIF_BCK_POL_NORMAL (0<<16) +#define AIF_BCK_POL_INVERT (1<<16) +#define AIF_BCK_SEL_PLL (1<<20) +#define AIF_BCK_PLL_SOURCE (1<<21) +#define AIF_BCK_PLL_SOURCE_PLL_150M (0<<21) +#define AIF_BCK_PLL_SOURCE_PLL_CODEC (1<<21) + +//Cfg_Aif_Tx_Stb +#define AIF_AIF_TX_STB_DIV(n) (((n)&0x3FFF)<<0) +#define AIF_AIF_TX_STB_26M_EN (1<<30) +#define AIF_AIF_TX_STB_EN (1<<31) + + + + +#endif diff --git a/sound/soc/rdafpga/aud_ifc.c b/sound/soc/rdafpga/aud_ifc.c new file mode 100644 index 000000000000..9649297ce1cd --- /dev/null +++ b/sound/soc/rdafpga/aud_ifc.c @@ -0,0 +1,369 @@ +/* + * aud_ifc.c -- AUDIO IFC interface for the RDA SoC + * + * Copyright (C) 2012 RDA Microelectronics (Beijing) Co., Ltd. + * + * Contact: Xu Mingliang <mingliangxu@rdamicro.com> + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> + +#include <plat/devices.h> + + +#include "bb_ifc.h" +#include "aud_ifc.h" + + +static HWP_BB_IFC_T __iomem *hwp_audIfc; + +static struct rda_audifc_ch *audifc_ch[RDA_AUDIFC_QTY]; + + +static irqreturn_t rda_audifc_record_irq_handler(int irq, void *channel) +{ + + struct rda_audifc_ch *ptr_ch = (struct rda_audifc_ch *)channel; + u32 reg; + + printk(KERN_INFO "record\n"); + + reg = hwp_audIfc->ch[RDA_AUDIFC_RECORD].status; + + if (reg & BB_IFC_I4F) { + ptr_ch->callback(ptr_ch->id, RDA_AUDIFC_QUARTER_IRQ, ptr_ch->data); + return IRQ_HANDLED; + }else if (reg & BB_IFC_IHF) { + ptr_ch->callback(ptr_ch->id, RDA_AUDIFC_HALF_IRQ, ptr_ch->data); + return IRQ_HANDLED; + }else if (reg & BB_IFC_I3_4F) { + ptr_ch->callback(ptr_ch->id, RDA_AUDIFC_THREE_QUARTER_IRQ, ptr_ch->data); + return IRQ_HANDLED; + }else if (reg & BB_IFC_IEF) { + ptr_ch->callback(ptr_ch->id, RDA_AUDIFC_END_IRQ, ptr_ch->data); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static irqreturn_t rda_audifc_play_irq_handler(int irq, void *channel) +{ + + + struct rda_audifc_ch *ptr_ch = (struct rda_audifc_ch *)channel; + u32 reg; + //printk(KERN_INFO "play\n"); + + if (ptr_ch->callback==NULL) + return IRQ_NONE; + + reg = hwp_audIfc->ch[RDA_AUDIFC_PLAY].status&(BB_IFC_CAUSE_IEF|BB_IFC_CAUSE_IHF|BB_IFC_CAUSE_I4F|BB_IFC_CAUSE_I3_4F); + + hwp_audIfc->ch[RDA_AUDIFC_PLAY].int_clear=reg; + + if (reg & BB_IFC_CAUSE_I4F) { + ptr_ch->callback(ptr_ch->id, RDA_AUDIFC_QUARTER_IRQ, ptr_ch->data); + return IRQ_HANDLED; + }else if (reg & BB_IFC_CAUSE_IHF) { + ptr_ch->callback(ptr_ch->id, RDA_AUDIFC_HALF_IRQ, ptr_ch->data); + return IRQ_HANDLED; + }else if (reg & BB_IFC_CAUSE_I3_4F) { + ptr_ch->callback(ptr_ch->id, RDA_AUDIFC_THREE_QUARTER_IRQ, ptr_ch->data); + return IRQ_HANDLED; + }else if (reg & BB_IFC_CAUSE_IEF) { + ptr_ch->callback(ptr_ch->id, RDA_AUDIFC_END_IRQ, ptr_ch->data); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +int rda_set_audifc_params(u8 ch, struct rda_audifc_chan_params *params) +{ + + printk(KERN_INFO "rda_set_audifc_params:%d\n",ch); + printk(KERN_INFO "params->src_addr:0x%x\n",params->src_addr); + printk(KERN_INFO "params->xfer_size:%ld\n",params->xfer_size); + + if (hwp_audIfc->ch[ch].status & BB_IFC_ENABLE){ + //dev_err(&pdev->dev, "%s: audio ifc busy \n", __func__); + printk(KERN_INFO "%s: audio ifc busy \n", __func__); + return RDA_AUDIFC_ERROR; + } + + // Assert on word alignement + if (((u32)params->src_addr)%4 != 0){ + //dev_err(&pdev->dev, "%s: AUDIO IFC transfer start address not aligned: 0x%x", __func__ , (u32)params->src_addr); + printk(KERN_INFO "%s: AUDIO IFC transfer start address not aligned: 0x%x", __func__ , (u32)params->src_addr); + return RDA_AUDIFC_ERROR; + } + +#if 0 + // Size must be a multiple of 32 bytes + if (((u32)params->xfer_size)%32 != 0){ + //dev_err(&pdev->dev, "%s: AUDIO IFC transfer size not mult. of 32-bytes: 0x%x", __func__ , (u32)params->xfer_size); + printk(KERN_INFO "%s: AUDIO IFC transfer size not mult. of 32-bytes: 0x%x", __func__ , (u32)params->xfer_size); + return RDA_AUDIFC_ERROR; + } +#endif + + hwp_audIfc->ch[ch].start_addr = (u32)params->src_addr;//(u32)0x100000+16*1024;// (u32)params->src_addr; + + hwp_audIfc->ch[ch].Fifo_Size =params->xfer_size;// 16*1024;//params->xfer_size; + + hwp_audIfc->ch[ch].int_mask = BB_IFC_QUARTER_FIFO| + BB_IFC_HALF_FIFO| + BB_IFC_THREE_QUARTER_FIFO| + BB_IFC_END_FIFO; + + return RDA_AUDIFC_NOERR; +} + + + +audifc_addr_t rda_get_audifc_src_pos(u8 ch) +{ + +// printk(KERN_INFO "rda_get_audifc_src_pos ch:0x%x\n",(u32)(hwp_audIfc->ch[ch].cur_ahb_addr)); + + return (u32)(hwp_audIfc->ch[ch].cur_ahb_addr); +} + + + +audifc_addr_t rda_get_audifc_dst_pos(u8 ch) +{ +// printk(KERN_INFO "rda_get_audifc_src_pos ch:0x%x\n",(u32)(hwp_audIfc->ch[ch].cur_ahb_addr)); + return (u32)(hwp_audIfc->ch[ch].cur_ahb_addr); +} + + + + +void rda_start_audifc(u8 ch) +{ + + printk(KERN_INFO "rda_start_audifc ch:%d\n",ch); + + hwp_audIfc->ch[ch].control = BB_IFC_ENABLE; + + return; +} + + + +void rda_stop_audifc(u8 ch) +{ + printk(KERN_INFO "rda_stop_audifc ch:%d\n",ch); + + hwp_audIfc->ch[ch].control = BB_IFC_DISABLE; + + return; +} + + + +void rda_poll_audifc(u8 ch) +{ +//TO BE DONG with TIME CONTROLLING...... +// while (!(hwp_audIfc->ch[ch].status & BB_IFC_FIFO_EMPTY)) { + /* Nothing to do */ +// }; + + return; +} + + + +int rda_request_audifc(int dev_id, const char *dev_name, + void (*callback) (int ch, int state, void *data), void *data, + int * audifc_ch_out) +{ + /* Clear interrupt flag and disble audifc. */ + rda_stop_audifc(dev_id); + + audifc_ch[dev_id]->id=dev_id; + audifc_ch[dev_id]->dev_name = dev_name; + audifc_ch[dev_id]->callback = callback; + audifc_ch[dev_id]->data = data; + + *audifc_ch_out = dev_id; + + return 0; +} + + + +void rda_free_audifc(u8 ch) +{ + audifc_ch[ch]->dev_name = NULL; + audifc_ch[ch]->callback = NULL; + audifc_ch[ch]->data = NULL; + + return; +} + + + +static int __devinit rda_audifc_probe(struct platform_device *pdev) +{ + struct rda_audifc_device_data *audifc_device; + struct resource *mem; + int irq[RDA_AUDIFC_QTY]; + int ret; + dev_err(&pdev->dev, "rda_audifc_probe\n"); + + audifc_device = pdev->dev.platform_data; + if (!audifc_device) { + dev_err(&pdev->dev, "%s: rda audifc initialized without platform data\n", __func__); + return -EINVAL; + } + + audifc_ch[RDA_AUDIFC_RECORD] = &audifc_device->chan[RDA_AUDIFC_RECORD]; + audifc_ch[RDA_AUDIFC_RECORD]->id = RDA_AUDIFC_RECORD; + audifc_ch[RDA_AUDIFC_RECORD]->dev_name = NULL; + audifc_ch[RDA_AUDIFC_RECORD]->callback = NULL; + audifc_ch[RDA_AUDIFC_RECORD]->data = NULL; + + audifc_ch[RDA_AUDIFC_PLAY] = &audifc_device->chan[RDA_AUDIFC_PLAY]; + audifc_ch[RDA_AUDIFC_PLAY]->id = RDA_AUDIFC_PLAY; + audifc_ch[RDA_AUDIFC_PLAY]->dev_name = NULL; + audifc_ch[RDA_AUDIFC_PLAY]->callback = NULL; + audifc_ch[RDA_AUDIFC_PLAY]->data = NULL; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "%s: no mem resource\n", __func__); + return -EINVAL; + } + + irq[RDA_AUDIFC_RECORD] = platform_get_irq(pdev, RDA_AUDIFC_RECORD); + if (irq[RDA_AUDIFC_RECORD] < 0) { + return irq[RDA_AUDIFC_RECORD]; + } + + irq[RDA_AUDIFC_PLAY] = platform_get_irq(pdev, RDA_AUDIFC_PLAY); + if (irq[RDA_AUDIFC_PLAY] < 0) { + return irq[RDA_AUDIFC_PLAY]; + } + + hwp_audIfc = ioremap(mem->start, resource_size(mem)); + if (!hwp_audIfc) { + dev_err(&pdev->dev, "%s: ioremap fail\n", __func__); + return -ENOMEM; + } + /* + * The flag, IRQF_TRIGGER_RISING, is not for device. + * It's only for calling mask_irq function to enable interrupt. + */ + ret = request_irq(irq[RDA_AUDIFC_RECORD], rda_audifc_record_irq_handler, + 0, "rda-audifc", (void *)audifc_ch[RDA_AUDIFC_RECORD]); + if (ret < 0) { + dev_err(&pdev->dev, "%s: request record irq fail\n", __func__); + iounmap(hwp_audIfc); + return ret; + } + ret = request_irq(irq[RDA_AUDIFC_PLAY], rda_audifc_play_irq_handler, + 0, "rda-audifc", (void *)audifc_ch[RDA_AUDIFC_PLAY]); + if (ret < 0) { + dev_err(&pdev->dev, "%s: request play irq fail\n", __func__); + iounmap(hwp_audIfc); + return ret; + } + printk(KERN_INFO "rda audifc is initialized!\n"); + + return 0; +} + +static int __devexit rda_audifc_remove(struct platform_device *pdev) +{ + //struct rda_audifc_device_data *audifc_device = pdev->dev.platform_data; + struct resource *io; + int irq[2]; + + iounmap(hwp_audIfc); + hwp_audIfc=NULL; + + + io = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(io->start, resource_size(io)); + + //free record irq + irq[RDA_AUDIFC_RECORD] = platform_get_irq(pdev, RDA_AUDIFC_RECORD); + free_irq(irq[RDA_AUDIFC_RECORD], (void *)audifc_ch[RDA_AUDIFC_RECORD]); + + if(audifc_ch[RDA_AUDIFC_RECORD]!= NULL){ + audifc_ch[RDA_AUDIFC_RECORD]->id = RDA_AUDIFC_RECORD; + audifc_ch[RDA_AUDIFC_RECORD]->dev_name = NULL; + audifc_ch[RDA_AUDIFC_RECORD]->callback = NULL; + audifc_ch[RDA_AUDIFC_RECORD]->data = NULL; + audifc_ch[RDA_AUDIFC_RECORD] = NULL; + + } + + //free record irq + irq[RDA_AUDIFC_PLAY] = platform_get_irq(pdev, RDA_AUDIFC_PLAY); + free_irq(irq[RDA_AUDIFC_PLAY], (void *)audifc_ch[RDA_AUDIFC_PLAY]); + + if(audifc_ch[RDA_AUDIFC_PLAY]!= NULL){ + + audifc_ch[RDA_AUDIFC_PLAY]->id = RDA_AUDIFC_PLAY; + audifc_ch[RDA_AUDIFC_PLAY]->dev_name = NULL; + audifc_ch[RDA_AUDIFC_PLAY]->callback = NULL; + audifc_ch[RDA_AUDIFC_PLAY]->data = NULL; + audifc_ch[RDA_AUDIFC_PLAY] = NULL; + } + + return 0; +} + +static struct platform_driver rda_audifc_driver = { + .probe = rda_audifc_probe, + .remove = __devexit_p(rda_audifc_remove), + .driver = { + .name = "rda-audifc"}, +}; + +static int __init rda_audifc_init(void) +{ + return platform_driver_register(&rda_audifc_driver); +} + +module_init(rda_audifc_init); + +static void __exit rda_audifc_exit(void) +{ + platform_driver_unregister(&rda_audifc_driver); +} + +module_exit(rda_audifc_exit); + + +MODULE_DESCRIPTION("AUDIO IFC for RDA8810 FPGA PCM"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_ALIAS("platform: rda-audifc"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/rdafpga/aud_ifc.h b/sound/soc/rdafpga/aud_ifc.h new file mode 100644 index 000000000000..b00d6364f3a8 --- /dev/null +++ b/sound/soc/rdafpga/aud_ifc.h @@ -0,0 +1,99 @@ + +/* + * aud_ifc.c -- audiodma is the dma only for audio. + * + * Copyright (C) 2012 RDA Microelectronics (Beijing) Co., Ltd. + * + * Contact: Xu Mingliang <mingliangxu@rdamicro.com> + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __ASM_AUDIFC_H +#define __ASM_AUDIFC_H + + +#include <linux/bitops.h> + + +// ============================================================================= +// MACROS +// ============================================================================= + + +typedef enum { + RDA_AUDIFC_QUARTER_IRQ=0, + RDA_AUDIFC_HALF_IRQ=1, + RDA_AUDIFC_THREE_QUARTER_IRQ=2, + RDA_AUDIFC_END_IRQ=3, + + RDA_AUDIFC_QTY_IRQ +} RDA_AUDIFC_HANDLE_ID_T; + + + +typedef enum { + RDA_AUDIFC_RECORD=0, + RDA_AUDIFC_PLAY=1, + + RDA_AUDIFC_QTY +} RDA_AUDIFC_REQUEST_ID_T; + + + + +typedef enum { + RDA_AUDIFC_NOERR=0, + RDA_AUDIFC_ERROR=1, + +} RDA_AUDIFC_ERROR_T; + + + +typedef unsigned int audifc_addr_t; + +struct rda_audifc_chan_params { + audifc_addr_t src_addr; + audifc_addr_t dst_addr; + unsigned long xfer_size; + unsigned long audifc_mode; +}; + + +int rda_set_audifc_params(u8 ch, struct rda_audifc_chan_params *params); +audifc_addr_t rda_get_audifc_src_pos(u8 ch); + +audifc_addr_t rda_get_audifc_dst_pos(u8 ch); + +void rda_start_audifc(u8 ch); + +void rda_stop_audifc(u8 ch); + +void rda_poll_audifc(u8 ch); +; +int rda_request_audifc(int dev_id, const char *dev_name, + void (*callback) (int ch, int state, void *data), void *data, + int * audifc_ch_out); + +void rda_free_audifc(u8 ch); + +#endif /* __ASM_AUDIFC_H */ + + + + + diff --git a/sound/soc/rdafpga/bb_ifc.h b/sound/soc/rdafpga/bb_ifc.h new file mode 100644 index 000000000000..ed75c1a8d3ea --- /dev/null +++ b/sound/soc/rdafpga/bb_ifc.h @@ -0,0 +1,209 @@ +//============================================================================== +// +// Copyright (C) 2003-2007, Coolsand Technologies, Inc. +// All Rights Reserved +// +// This source code is the property of Coolsand Technologies and is +// confidential. Any modification, distribution, reproduction or +// exploitation of any content of this file is totally forbidden, +// except with the written permission of Coolsand Technologies. +// +//============================================================================== +// +// THIS FILE WAS GENERATED FROM ITS CORRESPONDING XML VERSION WITH COOLXML. +// +// !!! PLEASE DO NOT EDIT !!! +// +// $HeadURL$ +// $Author$ +// $Date$ +// $Revision$ +// +//============================================================================== +// +/// @file +// +//============================================================================== + +#ifndef _BB_IFC_H_ +#define _BB_IFC_H_ + +#ifdef CT_ASM +#error "You are trying to use in an assembly code the normal H description of 'bb_ifc'." +#endif + + +//#include "globals.h" + +// ============================================================================= +// MACROS +// ============================================================================= +#define BB_IFC_ADDR_LEN (15) +#define BB_IFC_ADDR_ALIGN (2) +#define BB_IFC_TC_LEN (8) + +// ============================================================================= +// TYPES +// ============================================================================= + +// ============================================================================ +// BB_IFC_T +// ----------------------------------------------------------------------------- +/// +// ============================================================================= +#define REG_AU_IFC_BASE 0x009F0000 +#define REG_BB_IFC_BASE 0x01901000 + +typedef volatile struct +{ + /// The Channel 0 conveys data from the AIF to the memory. + /// The Channel 1 conveys data from the memory to the AIF. + /// These Channels only exist with Voice Option. + struct + { + REG32 control; //0x00000000 + REG32 status; //0x00000004 + REG32 start_addr; //0x00000008 + REG32 Fifo_Size; //0x0000000C + REG32 Reserved_00000010; //0x00000010 + REG32 int_mask; //0x00000014 + REG32 int_clear; //0x00000018 + REG32 cur_ahb_addr; //0x0000001C + } ch[2]; + REG32 ch2_control; //0x00000040 + REG32 ch2_status; //0x00000044 + REG32 ch2_start_addr; //0x00000048 + REG32 ch2_end_addr; //0x0000004C + REG32 ch2_tc; //0x00000050 + REG32 ch2_int_mask; //0x00000054 + REG32 ch2_int_clear; //0x00000058 + REG32 ch2_cur_ahb_addr; //0x0000005C + REG32 ch3_control; //0x00000060 + REG32 ch3_status; //0x00000064 + REG32 ch3_start_addr; //0x00000068 + REG32 Reserved_0000006C; //0x0000006C + REG32 ch3_tc; //0x00000070 + REG32 ch3_int_mask; //0x00000074 + REG32 ch3_int_clear; //0x00000078 + REG32 ch3_cur_ahb_addr; //0x0000007C +} HWP_BB_IFC_T; + +//#define hwp_auIfc ((HWP_BB_IFC_T*) KSEG1(REG_AU_IFC_BASE)) +//#define hwp_bbIfc ((HWP_BB_IFC_T*) KSEG1(REG_BB_IFC_BASE)) + + +//control +#define BB_IFC_ENABLE (1<<0) +#define BB_IFC_DISABLE (1<<1) +#define BB_IFC_AUTO_DISABLE (1<<4) + +//status +//#define BB_IFC_ENABLE (1<<0) +#define BB_IFC_FIFO_EMPTY (1<<4) +#define BB_IFC_CAUSE_IEF (1<<8) +#define BB_IFC_CAUSE_IHF (1<<9) +#define BB_IFC_CAUSE_I4F (1<<10) +#define BB_IFC_CAUSE_I3_4F (1<<11) +#define BB_IFC_IEF (1<<16) +#define BB_IFC_IHF (1<<17) +#define BB_IFC_I4F (1<<18) +#define BB_IFC_I3_4F (1<<19) + +//start_addr +#define BB_IFC_START_ADDR(n) (((n)&0xFFFFFF)<<2) + +//Fifo_Size +#define BB_IFC_FIFO_SIZE(n) (((n)&0x7FF)<<4) + +//int_mask +#define BB_IFC_END_FIFO (1<<8) +#define BB_IFC_HALF_FIFO (1<<9) +#define BB_IFC_QUARTER_FIFO (1<<10) +#define BB_IFC_THREE_QUARTER_FIFO (1<<11) + +//int_clear +//#define BB_IFC_END_FIFO (1<<8) +//#define BB_IFC_HALF_FIFO (1<<9) +//#define BB_IFC_QUARTER_FIFO (1<<10) +//#define BB_IFC_THREE_QUARTER_FIFO (1<<11) + +//cur_ahb_addr +#define BB_IFC_CUR_AHB_ADDR(n) (((n)&0x3FFFFFF)<<0) + +//ch2_control +//#define BB_IFC_ENABLE (1<<0) +//#define BB_IFC_DISABLE (1<<1) +#define BB_IFC_BURST_SIZE (1<<16) +#define BB_IFC_FIFO_MODE (1<<17) + +//ch2_status +//#define BB_IFC_ENABLE (1<<0) +#define BB_IFC_ENABLE_DISABLE (0<<0) +#define BB_IFC_ENABLE_ENABLE (1<<0) +//#define BB_IFC_FIFO_EMPTY (1<<4) +#define BB_IFC_CAUSE_ITC (1<<8) +//#define BB_IFC_CAUSE_IEF (1<<9) +#define BB_IFC_CAUSE_IHTC (1<<10) +#define BB_IFC_ITC (1<<12) +//#define BB_IFC_IEF (1<<13) +#define BB_IFC_IHTC (1<<14) +#define BB_IFC_CUR_TC(n) (((n)&0xFF)<<16) + +//ch2_start_addr +//#define BB_IFC_START_ADDR(n) (((n)&0x1FFF)<<2) +#define BB_IFC_PAGE_ADDR(n) (((n)&0x7FF)<<15) + +//ch2_end_addr +#define BB_IFC_END_ADDR(n) (((n)&0x1FFF)<<2) +//#define BB_IFC_PAGE_ADDR(n) (((n)&0x7FF)<<15) + +//ch2_tc +#define BB_IFC_TC(n) (((n)&0xFF)<<0) + +//ch2_int_mask +#define BB_IFC_END_TC (1<<0) +//#define BB_IFC_END_FIFO (1<<1) +#define BB_IFC_HALF_TC (1<<2) + +//ch2_int_clear +//#define BB_IFC_END_TC (1<<0) +//#define BB_IFC_END_FIFO (1<<1) +//#define BB_IFC_HALF_FIFO (1<<2) + +//ch2_cur_ahb_addr +//#define BB_IFC_CUR_AHB_ADDR(n) (((n)&0x3FFFFFF)<<0) + +//ch3_control +//#define BB_IFC_ENABLE (1<<0) +//#define BB_IFC_DISABLE (1<<1) + +//ch3_status +//#define BB_IFC_ENABLE (1<<0) +//#define BB_IFC_ENABLE_DISABLE (0<<0) +//#define BB_IFC_ENABLE_ENABLE (1<<0) +//#define BB_IFC_FIFO_EMPTY (1<<4) +//#define BB_IFC_CAUSE_ITC (1<<8) +//#define BB_IFC_ITC (1<<12) +//#define BB_IFC_CUR_TC(n) (((n)&0xFF)<<16) + +//ch3_start_addr +//#define BB_IFC_START_ADDR(n) (((n)&0x1FFF)<<2) + +//ch3_tc +//#define BB_IFC_TC(n) (((n)&0xFF)<<0) + +//ch3_int_mask +//#define BB_IFC_END_TC (1<<0) + +//ch3_int_clear +//#define BB_IFC_END_TC (1<<0) + +//ch3_cur_ahb_addr +//#define BB_IFC_CUR_AHB_ADDR(n) (((n)&0x3FFFFFF)<<0) + + + + + +#endif + diff --git a/sound/soc/rdafpga/bb_ifc_asm.h b/sound/soc/rdafpga/bb_ifc_asm.h new file mode 100644 index 000000000000..3d111950e15a --- /dev/null +++ b/sound/soc/rdafpga/bb_ifc_asm.h @@ -0,0 +1,202 @@ +#ifndef _BB_IFC_ASM_H_ +#define _BB_IFC_ASM_H_ + +//THIS FILE HAS BEEN GENERATED WITH COOLWATCHER. PLEASE EDIT WITH CARE ! + +#ifndef CT_ASM +#error "You are trying to use in a normal C code the assembly H description of 'bb_ifc'." +#endif + +#include "globals_asm.h" + +#define BB_IFC_ADDR_LEN (15) +#define BB_IFC_ADDR_ALIGN (2) +#define BB_IFC_TC_LEN (8) + +//============================================================================== +// bb_ifc +//------------------------------------------------------------------------------ +/// +//============================================================================== +#define REG_AU_IFC_BASE 0x009F0000 +#define REG_BB_IFC_BASE 0x01901000 + +#define REG_AU_IFC_BASE_HI BASE_HI(REG_AU_IFC_BASE) +#define REG_AU_IFC_BASE_LO BASE_LO(REG_AU_IFC_BASE) + +#define REG_AU_IFC_CH_0_CONTROL REG_AU_IFC_BASE_LO + 0x00000000 +#define REG_AU_IFC_CH_0_STATUS REG_AU_IFC_BASE_LO + 0x00000004 +#define REG_AU_IFC_CH_0_START_ADDR REG_AU_IFC_BASE_LO + 0x00000008 +#define REG_AU_IFC_CH_0_FIFO_SIZE REG_AU_IFC_BASE_LO + 0x0000000C +#define REG_AU_IFC_CH_0_INT_MASK REG_AU_IFC_BASE_LO + 0x00000014 +#define REG_AU_IFC_CH_0_INT_CLEAR REG_AU_IFC_BASE_LO + 0x00000018 +#define REG_AU_IFC_CH_0_CUR_AHB_ADDR REG_AU_IFC_BASE_LO + 0x0000001C +#define REG_AU_IFC_CH_1_CONTROL REG_AU_IFC_BASE_LO + 0x00000020 +#define REG_AU_IFC_CH_1_STATUS REG_AU_IFC_BASE_LO + 0x00000024 +#define REG_AU_IFC_CH_1_START_ADDR REG_AU_IFC_BASE_LO + 0x00000028 +#define REG_AU_IFC_CH_1_FIFO_SIZE REG_AU_IFC_BASE_LO + 0x0000002C +#define REG_AU_IFC_CH_1_INT_MASK REG_AU_IFC_BASE_LO + 0x00000034 +#define REG_AU_IFC_CH_1_INT_CLEAR REG_AU_IFC_BASE_LO + 0x00000038 +#define REG_AU_IFC_CH_1_CUR_AHB_ADDR REG_AU_IFC_BASE_LO + 0x0000003C +#define REG_AU_IFC_CH2_CONTROL REG_AU_IFC_BASE_LO + 0x00000040 +#define REG_AU_IFC_CH2_STATUS REG_AU_IFC_BASE_LO + 0x00000044 +#define REG_AU_IFC_CH2_START_ADDR REG_AU_IFC_BASE_LO + 0x00000048 +#define REG_AU_IFC_CH2_END_ADDR REG_AU_IFC_BASE_LO + 0x0000004C +#define REG_AU_IFC_CH2_TC REG_AU_IFC_BASE_LO + 0x00000050 +#define REG_AU_IFC_CH2_INT_MASK REG_AU_IFC_BASE_LO + 0x00000054 +#define REG_AU_IFC_CH2_INT_CLEAR REG_AU_IFC_BASE_LO + 0x00000058 +#define REG_AU_IFC_CH2_CUR_AHB_ADDR REG_AU_IFC_BASE_LO + 0x0000005C +#define REG_AU_IFC_CH3_CONTROL REG_AU_IFC_BASE_LO + 0x00000060 +#define REG_AU_IFC_CH3_STATUS REG_AU_IFC_BASE_LO + 0x00000064 +#define REG_AU_IFC_CH3_START_ADDR REG_AU_IFC_BASE_LO + 0x00000068 +#define REG_AU_IFC_CH3_TC REG_AU_IFC_BASE_LO + 0x00000070 +#define REG_AU_IFC_CH3_INT_MASK REG_AU_IFC_BASE_LO + 0x00000074 +#define REG_AU_IFC_CH3_INT_CLEAR REG_AU_IFC_BASE_LO + 0x00000078 +#define REG_AU_IFC_CH3_CUR_AHB_ADDR REG_AU_IFC_BASE_LO + 0x0000007C + +#define REG_BB_IFC_BASE_HI BASE_HI(REG_BB_IFC_BASE) +#define REG_BB_IFC_BASE_LO BASE_LO(REG_BB_IFC_BASE) + +#define REG_BB_IFC_CH_0_CONTROL REG_BB_IFC_BASE_LO + 0x00000000 +#define REG_BB_IFC_CH_0_STATUS REG_BB_IFC_BASE_LO + 0x00000004 +#define REG_BB_IFC_CH_0_START_ADDR REG_BB_IFC_BASE_LO + 0x00000008 +#define REG_BB_IFC_CH_0_FIFO_SIZE REG_BB_IFC_BASE_LO + 0x0000000C +#define REG_BB_IFC_CH_0_INT_MASK REG_BB_IFC_BASE_LO + 0x00000014 +#define REG_BB_IFC_CH_0_INT_CLEAR REG_BB_IFC_BASE_LO + 0x00000018 +#define REG_BB_IFC_CH_0_CUR_AHB_ADDR REG_BB_IFC_BASE_LO + 0x0000001C +#define REG_BB_IFC_CH_1_CONTROL REG_BB_IFC_BASE_LO + 0x00000020 +#define REG_BB_IFC_CH_1_STATUS REG_BB_IFC_BASE_LO + 0x00000024 +#define REG_BB_IFC_CH_1_START_ADDR REG_BB_IFC_BASE_LO + 0x00000028 +#define REG_BB_IFC_CH_1_FIFO_SIZE REG_BB_IFC_BASE_LO + 0x0000002C +#define REG_BB_IFC_CH_1_INT_MASK REG_BB_IFC_BASE_LO + 0x00000034 +#define REG_BB_IFC_CH_1_INT_CLEAR REG_BB_IFC_BASE_LO + 0x00000038 +#define REG_BB_IFC_CH_1_CUR_AHB_ADDR REG_BB_IFC_BASE_LO + 0x0000003C +#define REG_BB_IFC_CH2_CONTROL REG_BB_IFC_BASE_LO + 0x00000040 +#define REG_BB_IFC_CH2_STATUS REG_BB_IFC_BASE_LO + 0x00000044 +#define REG_BB_IFC_CH2_START_ADDR REG_BB_IFC_BASE_LO + 0x00000048 +#define REG_BB_IFC_CH2_END_ADDR REG_BB_IFC_BASE_LO + 0x0000004C +#define REG_BB_IFC_CH2_TC REG_BB_IFC_BASE_LO + 0x00000050 +#define REG_BB_IFC_CH2_INT_MASK REG_BB_IFC_BASE_LO + 0x00000054 +#define REG_BB_IFC_CH2_INT_CLEAR REG_BB_IFC_BASE_LO + 0x00000058 +#define REG_BB_IFC_CH2_CUR_AHB_ADDR REG_BB_IFC_BASE_LO + 0x0000005C +#define REG_BB_IFC_CH3_CONTROL REG_BB_IFC_BASE_LO + 0x00000060 +#define REG_BB_IFC_CH3_STATUS REG_BB_IFC_BASE_LO + 0x00000064 +#define REG_BB_IFC_CH3_START_ADDR REG_BB_IFC_BASE_LO + 0x00000068 +#define REG_BB_IFC_CH3_TC REG_BB_IFC_BASE_LO + 0x00000070 +#define REG_BB_IFC_CH3_INT_MASK REG_BB_IFC_BASE_LO + 0x00000074 +#define REG_BB_IFC_CH3_INT_CLEAR REG_BB_IFC_BASE_LO + 0x00000078 +#define REG_BB_IFC_CH3_CUR_AHB_ADDR REG_BB_IFC_BASE_LO + 0x0000007C + +//control +#define BB_IFC_ENABLE (1<<0) +#define BB_IFC_DISABLE (1<<1) +#define BB_IFC_AUTO_DISABLE (1<<4) + +//status +//#define BB_IFC_ENABLE (1<<0) +#define BB_IFC_FIFO_EMPTY (1<<4) +#define BB_IFC_CAUSE_IEF (1<<8) +#define BB_IFC_CAUSE_IHF (1<<9) +#define BB_IFC_CAUSE_I4F (1<<10) +#define BB_IFC_CAUSE_I3_4F (1<<11) +#define BB_IFC_IEF (1<<16) +#define BB_IFC_IHF (1<<17) +#define BB_IFC_I4F (1<<18) +#define BB_IFC_I3_4F (1<<19) + +//start_addr +#define BB_IFC_START_ADDR(n) (((n)&0xFFFFFF)<<2) + +//Fifo_Size +#define BB_IFC_FIFO_SIZE(n) (((n)&0x7FF)<<4) + +//int_mask +#define BB_IFC_END_FIFO (1<<8) +#define BB_IFC_HALF_FIFO (1<<9) +#define BB_IFC_QUARTER_FIFO (1<<10) +#define BB_IFC_THREE_QUARTER_FIFO (1<<11) + +//int_clear +//#define BB_IFC_END_FIFO (1<<8) +//#define BB_IFC_HALF_FIFO (1<<9) +//#define BB_IFC_QUARTER_FIFO (1<<10) +//#define BB_IFC_THREE_QUARTER_FIFO (1<<11) + +//cur_ahb_addr +#define BB_IFC_CUR_AHB_ADDR(n) (((n)&0x3FFFFFF)<<0) + +//ch2_control +//#define BB_IFC_ENABLE (1<<0) +//#define BB_IFC_DISABLE (1<<1) +#define BB_IFC_BURST_SIZE (1<<16) +#define BB_IFC_FIFO_MODE (1<<17) + +//ch2_status +//#define BB_IFC_ENABLE (1<<0) +#define BB_IFC_ENABLE_DISABLE (0<<0) +#define BB_IFC_ENABLE_ENABLE (1<<0) +//#define BB_IFC_FIFO_EMPTY (1<<4) +#define BB_IFC_CAUSE_ITC (1<<8) +//#define BB_IFC_CAUSE_IEF (1<<9) +#define BB_IFC_CAUSE_IHTC (1<<10) +#define BB_IFC_ITC (1<<12) +//#define BB_IFC_IEF (1<<13) +#define BB_IFC_IHTC (1<<14) +#define BB_IFC_CUR_TC(n) (((n)&0xFF)<<16) + +//ch2_start_addr +//#define BB_IFC_START_ADDR(n) (((n)&0x1FFF)<<2) +#define BB_IFC_PAGE_ADDR(n) (((n)&0x7FF)<<15) + +//ch2_end_addr +#define BB_IFC_END_ADDR(n) (((n)&0x1FFF)<<2) +//#define BB_IFC_PAGE_ADDR(n) (((n)&0x7FF)<<15) + +//ch2_tc +#define BB_IFC_TC(n) (((n)&0xFF)<<0) + +//ch2_int_mask +#define BB_IFC_END_TC (1<<0) +//#define BB_IFC_END_FIFO (1<<1) +#define BB_IFC_HALF_TC (1<<2) + +//ch2_int_clear +//#define BB_IFC_END_TC (1<<0) +//#define BB_IFC_END_FIFO (1<<1) +//#define BB_IFC_HALF_FIFO (1<<2) + +//ch2_cur_ahb_addr +//#define BB_IFC_CUR_AHB_ADDR(n) (((n)&0x3FFFFFF)<<0) + +//ch3_control +//#define BB_IFC_ENABLE (1<<0) +//#define BB_IFC_DISABLE (1<<1) + +//ch3_status +//#define BB_IFC_ENABLE (1<<0) +//#define BB_IFC_ENABLE_DISABLE (0<<0) +//#define BB_IFC_ENABLE_ENABLE (1<<0) +//#define BB_IFC_FIFO_EMPTY (1<<4) +//#define BB_IFC_CAUSE_ITC (1<<8) +//#define BB_IFC_ITC (1<<12) +//#define BB_IFC_CUR_TC(n) (((n)&0xFF)<<16) + +//ch3_start_addr +//#define BB_IFC_START_ADDR(n) (((n)&0x1FFF)<<2) + +//ch3_tc +//#define BB_IFC_TC(n) (((n)&0xFF)<<0) + +//ch3_int_mask +//#define BB_IFC_END_TC (1<<0) + +//ch3_int_clear +//#define BB_IFC_END_TC (1<<0) + +//ch3_cur_ahb_addr +//#define BB_IFC_CUR_AHB_ADDR(n) (((n)&0x3FFFFFF)<<0) + + + + +#endif diff --git a/sound/soc/rdafpga/rdafpga_dai.c b/sound/soc/rdafpga/rdafpga_dai.c new file mode 100644 index 000000000000..72ac1d1e2cdb --- /dev/null +++ b/sound/soc/rdafpga/rdafpga_dai.c @@ -0,0 +1,536 @@ +/* + * omap-dmic.c -- OMAP ASoC DMIC DAI driver + * + * Copyright (C) 2010 - 2011 Texas Instruments + * + * Author: David Lambert <dlambert@ti.com> + * Misael Lopez Cruz <misael.lopez@ti.com> + * Liam Girdwood <lrg@ti.com> + * Peter Ujfalusi <peter.ujfalusi@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <plat/dma.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +//#include "rda_dai.h" +#include "aif.h" + + + +#define FAST_CLOCK 26000000 //50M + +#define AIF_SOURCE_CLOCK 48000000 //50M + +struct rda_dai { + struct device *dev; + + bool PlayActive; + bool RecordActive; + + struct mutex mutex; + + u32 TxStb_div; + u32 AudioBck_div; + u32 bcklrck_div; + u32 ChannelNb; + + u32 ControlReg; + //u32 SerialMode; + bool MasterFlag; + + bool OpenStatus; + + bool PlayStatus; + bool RecordStatus; + +}; + + + +static HWP_AIF_T __iomem *hwp_apAif; +/* + * Stream DMA parameters + +static struct omap_pcm_dma_data rda_dai_dai_dma_params = { + .name = "DMIC capture", + .data_type = OMAP_DMA_DATA_TYPE_S32, + .sync_mode = OMAP_DMA_SYNC_PACKET, +}; + +*/ + +static int rda_dai_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rda_dai *AifCfg = snd_soc_dai_get_drvdata(dai); + int ret = 0; + printk(KERN_INFO "rda_dai_dai_startup"); + mutex_lock(&AifCfg->mutex); + if(substream->stream==SNDRV_PCM_STREAM_PLAYBACK) + { + if (!AifCfg->PlayActive) + AifCfg->PlayActive = 1; + else + ret = -EBUSY; + }else{ + + if (!AifCfg->RecordActive) + AifCfg->RecordActive = 1; + else + ret = -EBUSY; + } + mutex_unlock(&AifCfg->mutex); + + return ret; +} + +static void rda_dai_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rda_dai *AifCfg = snd_soc_dai_get_drvdata(dai); + printk(KERN_INFO "rda_dai_dai_shutdown"); + mutex_lock(&AifCfg->mutex); + if(substream->stream==SNDRV_PCM_STREAM_PLAYBACK) + { + AifCfg->PlayActive = 0; + }else{ + AifCfg->RecordActive = 0; + } + + //close + if(AifCfg->PlayActive == 0&&AifCfg->RecordActive == 0){ + + hwp_apAif->ctrl=0; + hwp_apAif->serial_ctrl=AIF_MASTER_MODE_MASTER; + hwp_apAif->side_tone=0; + hwp_apAif->Cfg_Aif_Tx_Stb=0; + AifCfg->OpenStatus=0; + } + mutex_unlock(&AifCfg->mutex); +} + + + +static int rda_dai_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rda_dai *AifCfg = snd_soc_dai_get_drvdata(dai); + + u32 ChannelNb; + u32 Lrck; + u32 Bck; + u32 BckLrckRatio; + + printk(KERN_INFO "rda_dai_dai_hw_params"); + + if(AifCfg->OpenStatus==0){ + u32 SampleRate=params_rate(params); + + AifCfg->TxStb_div=AIF_SOURCE_CLOCK/SampleRate-2; + + //LRCK frequency + Lrck=SampleRate; + + + switch(SampleRate){ + case 8000: + BckLrckRatio=50; + break; + + case 11025: + BckLrckRatio=36; + break; + + case 12000: + BckLrckRatio=38; + break; + + case 16000: + BckLrckRatio=50; + break; + + case 22050: + BckLrckRatio=40; + break; + + case 24000: + BckLrckRatio=38; + break; + + case 32000: + BckLrckRatio=56; + break; + + case 44100: + BckLrckRatio=62; + break; + + case 48000: + BckLrckRatio=36; + break; + + default: + dev_err(AifCfg->dev,"Improper stream frequency.\n"); + return -EINVAL; + + break; + } + + Bck=Lrck*BckLrckRatio; + AifCfg->AudioBck_div=FAST_CLOCK/Bck-2; + AifCfg->bcklrck_div=BckLrckRatio/2-16; + + + ChannelNb = params_channels(params); + + switch (ChannelNb) { + case 1: + case 2: + AifCfg->ChannelNb=ChannelNb; + break; + default: + dev_err(AifCfg->dev, "invalid number of legacy channels\n"); + return -EINVAL; + } + + AifCfg->MasterFlag=true; + + + /* packet size is threshold * channels */ + //snd_soc_dai_set_dma_data(dai, substream, &rda_dai_dai_dma_params); + + } + + + return 0; +} + +static int rda_dai_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rda_dai *AifCfg = snd_soc_dai_get_drvdata(dai); + u32 SerialCfgReg=0; + + printk(KERN_INFO "rda_dai_dai_prepare:%p\n",hwp_apAif); + + if(AifCfg->OpenStatus == 0) + { + hwp_apAif->Cfg_Aif_Tx_Stb=AIF_AIF_TX_STB_EN | AIF_AIF_TX_STB_DIV(AifCfg->TxStb_div); + + // Serial in and serial out + AifCfg->ControlReg = AIF_PARALLEL_OUT_CLR_PARA | + AIF_PARALLEL_IN_CLR_PARA | + AIF_LOOP_BACK_NORMAL; + + //SerialMode + SerialCfgReg |=AIF_SERIAL_MODE_I2S_PCM; + + //Master + SerialCfgReg |=AifCfg->MasterFlag?AIF_MASTER_MODE_MASTER:AIF_MASTER_MODE_SLAVE; + + //LSB first + SerialCfgReg |=AIF_LSB_MSB; + + //LRCK polarity + SerialCfgReg |=AIF_LRCK_POL_LEFT_H_RIGHT_L; + + //Rx delay + SerialCfgReg |=AIF_RX_DLY_DLY_2; + + //Tx delay + SerialCfgReg |=AIF_TX_DLY_DLY_1; + + //Rx Tx Mode + if(AifCfg->ChannelNb==1) + { + //Rx mode + SerialCfgReg |=AIF_RX_MODE_STEREO_MONO_FROM_L; + //Tx mode + SerialCfgReg |=AIF_TX_MODE_MONO_STEREO_DUPLI; + }else{ + //Rx mode + SerialCfgReg |=AIF_RX_MODE_STEREO_STEREO; + //Tx mode + SerialCfgReg |=AIF_TX_MODE_STEREO_STEREO; + } + + + //we get the Bck from the audio clock + hwp_apAif->Cfg_Clk_AudioBCK=AIF_AUDIOBCK_DIVIDER(AifCfg->AudioBck_div); + + SerialCfgReg |= AIF_BCK_LRCK(AifCfg->bcklrck_div); + + //BCK polarity + SerialCfgReg |=AifCfg->MasterFlag?AIF_BCK_POL_NORMAL:AIF_BCK_POL_INVERT; + + // Output Half Cycle Delay + SerialCfgReg |=AifCfg->MasterFlag?AIF_OUTPUT_HALF_CYCLE_DLY_DLY:AIF_OUTPUT_HALF_CYCLE_DLY_NO_DLY; + + // Input Half Cycle Delay + SerialCfgReg |=AIF_INPUT_HALF_CYCLE_DLY_NO_DLY; + + //BckOut gating + SerialCfgReg |=AIF_BCKOUT_GATE_NO_GATE; + + hwp_apAif->serial_ctrl=SerialCfgReg; + + AifCfg->OpenStatus=1; + + } + + printk(KERN_INFO "rda_dai_dai_prepare end:%p\n",hwp_apAif); + + return 0; +} + +static int rda_dai_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct rda_dai *AifCfg = snd_soc_dai_get_drvdata(dai); + printk(KERN_INFO "rda_dai_dai_trigger"); + if(substream->stream==SNDRV_PCM_STREAM_PLAYBACK) + { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + printk(KERN_INFO "rda_dai_dai_trigger start"); + hwp_apAif->ctrl=(AifCfg->ControlReg|AIF_ENABLE_H_ENABLE)&~AIF_TX_OFF; + AifCfg->PlayStatus=1; + break; + case SNDRV_PCM_TRIGGER_STOP: + printk(KERN_INFO "rda_dai_dai_trigger try stop"); + if(AifCfg->RecordStatus==0){ + printk(KERN_INFO "rda_dai_dai_trigger stop"); + // To have the clock allowing the disabling. + hwp_apAif->ctrl=0; + } + AifCfg->PlayStatus=0; + break; + default: + break; + } + + }else{ + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if(AifCfg->PlayStatus==0){ + hwp_apAif->ctrl=(AifCfg->ControlReg|AIF_ENABLE_H_ENABLE)|AIF_TX_OFF_TX_OFF; + } + AifCfg->RecordStatus=1; + break; + case SNDRV_PCM_TRIGGER_STOP: + if(AifCfg->PlayStatus==0){ + // Disable the AIF if not recording. + hwp_apAif->ctrl=0; + } + AifCfg->RecordStatus=0; + break; + default: + break; + } + + } +// printk(KERN_INFO "rda_dai_dai_trigger end"); + + return 0; +} + + +static const struct snd_soc_dai_ops rda_dai_dai_ops = { + .startup = rda_dai_dai_startup, + .shutdown = rda_dai_dai_shutdown, + .hw_params = rda_dai_dai_hw_params, + .prepare = rda_dai_dai_prepare, + .trigger = rda_dai_dai_trigger, +// .set_sysclk = rda_dai_set_dai_sysclk, +}; + +static int rda_dai_probe(struct snd_soc_dai *dai) +{ + struct rda_dai *dmic = snd_soc_dai_get_drvdata(dai); + printk(KERN_INFO "rda_dai_probe"); + + pm_runtime_enable(dmic->dev); + + /* Disable lines while request is ongoing */ + pm_runtime_get_sync(dmic->dev); + //omap_dmic_write(dmic, rda_dai_CTRL_REG, 0x00); + pm_runtime_put_sync(dmic->dev); + + /* Configure DMIC threshold value */ + //dmic->threshold = rda_dai_THRES_MAX - 3; + return 0; +} + +static int rda_dai_remove(struct snd_soc_dai *dai) +{ + struct rda_dai *dmic = snd_soc_dai_get_drvdata(dai); + + pm_runtime_disable(dmic->dev); + + return 0; +} + +static struct snd_soc_dai_driver rda_dai_dai = { + .name = "rda-aif", + .probe = rda_dai_probe, + .remove = rda_dai_remove, + .playback={ + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .sig_bits = 16, + }, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .sig_bits = 16, + }, + .ops = &rda_dai_dai_ops, +}; + +static __devinit int asoc_dmic_probe(struct platform_device *pdev) +{ + struct rda_dai *AifCfg; + struct resource *res; + int ret; + + printk(KERN_INFO "asoc_dmic_probe"); + + AifCfg = devm_kzalloc(&pdev->dev, sizeof(struct rda_dai), GFP_KERNEL); + if (!AifCfg) + return -ENOMEM; + + platform_set_drvdata(pdev, AifCfg); + AifCfg->dev = &pdev->dev; +// dmic->sysclk = rda_dai_SYSCLK_SYNC_MUX_CLKS; + + + mutex_init(&AifCfg->mutex); + +// dmic->fclk = clk_get(dmic->dev, "dmic_fck"); +// if (IS_ERR(dmic->fclk)) { +// dev_err(dmic->dev, "cant get dmic_fck\n"); +// return -ENODEV; +// } + +// res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); +// if (!res) { +// dev_err(dmic->dev, "invalid dma memory resource\n"); +// ret = -ENODEV; +// goto err_put_clk; +// } +// omap_dmic_dai_dma_params.port_addr = res->start + rda_dai_DATA_REG; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(AifCfg->dev, "invalid dma resource\n"); + ret = -ENODEV; + goto err_put_clk; + } +/* rda_dai_dai_dma_params.dma_req = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); + if (!res) { + dev_err(dmic->dev, "invalid memory resource\n"); + ret = -ENODEV; + goto err_put_clk; + } +*/ + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) { + dev_err(AifCfg->dev, "memory region already claimed\n"); + ret = -ENODEV; + goto err_put_clk; + } + + hwp_apAif = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!hwp_apAif) { + ret = -ENOMEM; + goto err_put_clk; + } + + ret = snd_soc_register_dai(&pdev->dev, &rda_dai_dai); + if (ret) + goto err_put_clk; + + return 0; + +err_put_clk: + //clk_put(dmic->fclk); + return ret; +} + +static int __devexit asoc_dmic_remove(struct platform_device *pdev) +{ +// struct rda_dai *AifCfg = platform_get_drvdata(pdev); + + snd_soc_unregister_dai(&pdev->dev); + //clk_put(dmic->fclk); + + return 0; +} + +static struct platform_driver rda_dai_driver = { + .driver = { + .name = "rda-aif", + .owner = THIS_MODULE, + }, + .probe = asoc_dmic_probe, + .remove = __devexit_p(asoc_dmic_remove), +}; + + + +static int __init rdafpag_dai_modinit(void) +{ + return platform_driver_register(&rda_dai_driver); +} + +static void __exit rdafpag_dai_modexit(void) +{ + platform_driver_unregister(&rda_dai_driver); +} + + +module_init(rdafpag_dai_modinit); +module_exit(rdafpag_dai_modexit); + + + +MODULE_DESCRIPTION("ALSA SoC for RDA FPGA DAI"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rdafpga/rdafpga_pcm.c b/sound/soc/rdafpga/rdafpga_pcm.c new file mode 100644 index 000000000000..bd4361110fed --- /dev/null +++ b/sound/soc/rdafpga/rdafpga_pcm.c @@ -0,0 +1,410 @@ +/* + * rdafpga_pcm.c -- ALSA PCM interface for the RDA SoC + * + * Copyright (C) 2012 RDA Microelectronics (Beijing) Co., Ltd. + * + * Contact: Xu Mingliang <mingliangxu@rdamicro.com> + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "aud_ifc.h" +#include "rdafpga_pcm.h" + +struct rda_pcm_dma_data dma_data; + +static const struct snd_pcm_hardware rda_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + .formats = SNDRV_PCM_FMTBIT_S16_LE , + .period_bytes_min = 32, + .period_bytes_max = 128 * 1024, + .periods_min = 4, + .periods_max = 4, + .buffer_bytes_max = 512 * 1024, +}; + +struct rda_runtime_data { + spinlock_t lock; + struct rda_pcm_dma_data *dma_data; + int dma_ch; + int period_index; +}; + +static void rda_pcm_dma_irq(int ch, int stat, void *data) +{ + struct snd_pcm_substream *substream = data; + +// printk(KERN_INFO "rda_pcm_dma_irq\n"); + + snd_pcm_period_elapsed(substream); +} + +/* this may get called several times by oss emulation */ +static int rda_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + //struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct rda_runtime_data *prtd = runtime->private_data; + + int err = 0; + + printk(KERN_INFO "rda_pcm_hw_params\n"); + +// dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ +// if (!dma_data) +// return 0; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + //if (prtd->dma_data) + // return 0; + prtd->dma_data = &dma_data; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_data.dma_req=1; + dma_data.name="play"; + + } else { + dma_data.dma_req=0; + dma_data.name="record"; + } + + + err = rda_request_audifc(dma_data.dma_req, dma_data.name, + rda_pcm_dma_irq, substream, &prtd->dma_ch); + + return err; +} + +static int rda_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rda_runtime_data *prtd = runtime->private_data; + + if (prtd->dma_data == NULL) + return 0; + printk(KERN_INFO "rda_pcm_hw_free\n"); + + rda_free_audifc(prtd->dma_ch); + prtd->dma_data = NULL; + + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int rda_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rda_runtime_data *prtd = runtime->private_data; + //struct rda_pcm_dma_data *dma_data = prtd->dma_data; + struct rda_audifc_chan_params dma_params; + int bytes; + printk(KERN_INFO "rda_pcm_prepare\n"); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + //if (!prtd->dma_data) + // return 0; + + //memset(&dma_params, 0, sizeof(dma_params)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_params.src_addr = runtime->dma_addr; + dma_params.dst_addr = 0; + dma_params.xfer_size = runtime->dma_bytes; + dma_params.audifc_mode = 0; + + } else { + dma_params.src_addr = runtime->dma_addr; + dma_params.dst_addr = 0; + dma_params.xfer_size = runtime->dma_bytes; + dma_params.audifc_mode = 0; + } + + /* + * Set DMA transfer frame size equal to ALSA period size and frame + * count as no. of ALSA periods. Then with DMA frame interrupt enabled, + * we can transfer the whole ALSA buffer with single DMA transfer but + * still can get an interrupt at each period bounary + */ + bytes = snd_pcm_lib_period_bytes(substream); + + printk(KERN_INFO "prtd->dma_ch %d\n",prtd->dma_ch); + + rda_set_audifc_params(prtd->dma_ch, &dma_params); + + return 0; +} + +static int rda_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rda_runtime_data *prtd = runtime->private_data; + struct rda_pcm_dma_data *dma_data = prtd->dma_data; +// unsigned long flags; + int ret = 0; + printk(KERN_INFO "rda_pcm_trigger\n"); + + //spin_lock_irqsave(&prtd->lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + prtd->period_index = 0; + /* Configure McBSP internal buffer usage */ + if (dma_data->set_threshold) + dma_data->set_threshold(substream); + + rda_start_audifc(prtd->dma_ch); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + prtd->period_index = -1; + rda_stop_audifc(prtd->dma_ch); + break; + default: + ret = -EINVAL; + } + //spin_unlock_irqrestore(&prtd->lock, flags); + + return ret; +} + +static snd_pcm_uframes_t rda_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rda_runtime_data *prtd = runtime->private_data; + audifc_addr_t ptr; + snd_pcm_uframes_t offset; +//printk(KERN_INFO "rda_pcm_pointer\n"); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ptr = rda_get_audifc_dst_pos(prtd->dma_ch); + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } else { + ptr = rda_get_audifc_src_pos(prtd->dma_ch); + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } + + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +static int rda_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rda_runtime_data *prtd; + int ret; + printk(KERN_INFO "rda_pcm_open\n"); + snd_soc_set_runtime_hwparams(substream, &rda_pcm_hardware); + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + goto out; + } + spin_lock_init(&prtd->lock); + runtime->private_data = prtd; + +out: + return ret; +} + +static int rda_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + kfree(runtime->private_data); + return 0; +} + +static int rda_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; +printk(KERN_INFO "rda_pcm_mmap\n"); + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops rda_pcm_ops = { + .open = rda_pcm_open, + .close = rda_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = rda_pcm_hw_params, + .hw_free = rda_pcm_hw_free, + .prepare = rda_pcm_prepare, + .trigger = rda_pcm_trigger, + .pointer = rda_pcm_pointer, + .mmap = rda_pcm_mmap, +}; + +static u64 rda_pcm_dmamask = DMA_BIT_MASK(32); + +static int rda_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 = rda_pcm_hardware.buffer_bytes_max; + printk(KERN_INFO "rda_pcm_preallocate_dma_buffer\n"); + 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); + printk(KERN_INFO "buf->area:%p buf->bytes;%d\n",buf->area,size); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void rda_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + printk(KERN_INFO "rda_pcm_free_dma_buffers\n"); + 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 int rda_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; + printk(KERN_INFO "rda_pcm_new\n"); + if (!card->dev->dma_mask) + card->dev->dma_mask = &rda_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 = rda_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = rda_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + rda_pcm_free_dma_buffers(pcm); + + return ret; +} + +static struct snd_soc_platform_driver rda_soc_platform = { + .ops = &rda_pcm_ops, + .pcm_new = rda_pcm_new, + .pcm_free = rda_pcm_free_dma_buffers, +}; + +static __devinit int rda_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, + &rda_soc_platform); +} + +static int __devexit rda_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver rda_pcm_driver = { + .driver = { + .name = "rdafpga-pcm-audio", + .owner = THIS_MODULE, + }, + + .probe = rda_pcm_probe, + .remove = __exit_p(rda_pcm_remove), +}; + +static int __init rdafpag_pcm_modinit(void) +{ + return platform_driver_register(&rda_pcm_driver); +} + +static void __exit rdafpag_pcm_modexit(void) +{ + platform_driver_unregister(&rda_pcm_driver); +} + + +module_init(rdafpag_pcm_modinit); +module_exit(rdafpag_pcm_modexit); + + + +MODULE_DESCRIPTION("ALSA SoC for RDA FPGA PCM"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_LICENSE("GPL"); + + diff --git a/sound/soc/rdafpga/rdafpga_pcm.h b/sound/soc/rdafpga/rdafpga_pcm.h new file mode 100644 index 000000000000..e9f02c34b067 --- /dev/null +++ b/sound/soc/rdafpga/rdafpga_pcm.h @@ -0,0 +1,41 @@ +/* + * rda-pcm.h + * + * Copyright (C) 2012 RDA Microelectronics (Beijing) Co., Ltd. + * + * Contact: Xu Mingliang <mingliangxu@rdamicro.com> + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __RDA_PCM_H__ +#define __RDA_PCM_H__ + +struct snd_pcm_substream; + +struct rda_pcm_dma_data { + char *name; /* stream identifier */ + int dma_req; /* DMA request line */ + unsigned long port_addr; /* transmit/receive register */ + void (*set_threshold)(struct snd_pcm_substream *substream); + int data_type; /* data type 8,16,32 */ + int sync_mode; /* DMA sync mode */ + int packet_size; /* packet size only in PACKET mode */ +}; + +#endif + diff --git a/sound/soc/rdafpga/rdafpga_soundcard.c b/sound/soc/rdafpga/rdafpga_soundcard.c new file mode 100644 index 000000000000..45c29a32e0ac --- /dev/null +++ b/sound/soc/rdafpga/rdafpga_soundcard.c @@ -0,0 +1,129 @@ +/* + * sound/soc/rda/soundcard_rdafpga.c + * + * Copyright (C) 2012 RDA. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <linux/gpio.h> +#include <linux/module.h> + +#define CODEC_CLOCK 12000000 + + +static int rda_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; + int err=0; + + + printk(KERN_INFO "rda_hw_params\n"); + + /* Set the codec system clock for DAC and ADC */ + err = + snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN); + + if (err < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return err; + } + + return err; +} + +static struct snd_soc_ops rda_ops = { + .hw_params = rda_hw_params, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rda_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .cpu_dai_name = "rda-aif",//"rdafpga-dai-i2s", + .codec_dai_name = "tlv320aic23-hifi", + .platform_name = "rdafpga-pcm-audio", + .codec_name ="spi0.0",// "tlv320aic23-spi", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &rda_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_card_rda = { + .name = "rdafpga_soundcard", + .owner = THIS_MODULE, + .dai_link = &rda_dai, + .num_links = 1, + + .dapm_widgets = tlv320aic23_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static struct platform_device *rda_snd_device; + +static int __init rdasoundcard_modinit(void) +{ + int err; + + printk(KERN_INFO "rdasoundcard_modinit\n"); + + rda_snd_device = platform_device_alloc("soc-audio", -1); + if (!rda_snd_device) + return -ENOMEM; + + platform_set_drvdata(rda_snd_device, &snd_soc_card_rda); + err = platform_device_add(rda_snd_device); + if (err) + platform_device_put(rda_snd_device); + + return err; + +} + +static void __exit rdasoundcard_modexit(void) +{ + //clk_put(tlv320aic23_mclk); + platform_device_unregister(rda_snd_device); +} +module_init(rdasoundcard_modinit); +module_exit(rdasoundcard_modexit); + +MODULE_DESCRIPTION("ALSA SoC for RDA FPGA tlv320aic23"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rdafpga/tlv320aic23_spi.c b/sound/soc/rdafpga/tlv320aic23_spi.c new file mode 100644 index 000000000000..772cc81c81d1 --- /dev/null +++ b/sound/soc/rdafpga/tlv320aic23_spi.c @@ -0,0 +1,861 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, <arunks@mistralsolutions.com> + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd., + * + * Based on sound/soc/codecs/wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Notes: + * The AIC23 is a driver for a low power stereo audio + * codec tlv320aic23 + * + * The machine layer should disable unsupported inputs/outputs by + * snd_soc_dapm_disable_pin(codec, "LHPOUT"), etc. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/tlv.h> +#include <sound/initval.h> + +#include "tlv320aic23_spi.h" + + + +// ============================================================================= +// MACROS +// ============================================================================= +/// Address on the I2C bus. +#define TI_ADDR 0x1a // 0x1a // 0x1b in case of CS=1 + +/// Ti's register Map +#define REG_TI_LINE_VOL_L 0x00 + #define TI_LRS_ON (1<<8) + #define TI_LRS_OFF (0<<8) + #define TI_LIM_ON (1<<7) + #define TI_LIM_OFF (0<<7) + #define LIV_VALUE(v) ((v)&0x1f) + +#define REG_TI_LINE_VOL_R 0x01 + #define TI_RLS_ON (1<<8) + #define TI_RLS_OFF (0<<8) + #define TI_RIM_ON (1<<7) + #define TI_RIM_OFF (0<<7) + #define RIV_VALUE(v) ((v)&0x1f) + +#define REG_TI_HEAD_VOL_L 0x02 + #define TI_LZC_ON (1<<7) + #define TI_LZC_OFF (0<<7) + #define TI_LHV_VALUE(v) ((v)&0x7f) + #define TI_LHV_MUTE 0x30 + #define TI_LHV_0dB 0x79 + +#define REG_TI_HEAD_VOL_R 0x03 + #define TI_RZC_ON (1<<7) + #define TI_RZC_OFF (0<<7) + #define TI_RHV_VALUE(v) ((v)&0x7f) + #define TI_RHV_MUTE 0x30 + +#define REG_TI_ANA_AUD_CTRL 0x04 + #define TI_SIDETONE_0DB 0x120 + #define TI_SIDETONE_MINUS_6DB 0x20 + #define TI_SIDETONE_MINUS_9DB 0x60 + #define TI_SIDETONE_MINUS_12DB 0xa0 + #define TI_SIDETONE_MINUS_18DB 0xe0 + #define TI_SIDETONE_OFF (0<<5) + #define TI_ANA_DAC_ON (1<<4) + #define TI_ANA_DAC_OFF (0<<4) + #define TI_ANA_BYP_ON (1<<3) + #define TI_ANA_BYP_OFF (0<<3) + #define TI_INSEL_MIC (1<<2) + #define TI_INSEL_LINE (0<<2) + #define TI_MICM_ON (1<<1) + #define TI_MICM_OFF (0<<1) + #define TI_MICB_ON (1<<0) + #define TI_MICB_OFF (0<<0) + +#define REG_TI_DIG_AUD_CTRL 0x05 + #define TI_DACM_ON (1<<3) + #define TI_DACM_OFF (0<<3) + #define TI_DEEMP_OFF (0<<1) + #define TI_DEEMP_32KHZ (1<<1) + #define TI_DEEMP_44KHZ (2<<1) + #define TI_DEEMP_48KHZ (3<<1) + #define TI_ADCHP_ON (0<<0) + #define TI_ADCHP_OFF (1<<0) + +#define REG_TI_POWER_CTRL 0x06 + #define TI_POWER_OFF (1<<7) + #define TI_POWER_ON (0<<7) + #define TI_CLK_OFF (1<<6) + #define TI_CLK_ON (0<<6) + #define TI_OSC_OFF (1<<5) + #define TI_OSC_ON (0<<5) + #define TI_OUT_OFF (1<<4) + #define TI_OUT_ON (0<<4) + #define TI_DAC_OFF (1<<3) + #define TI_DAC_ON (0<<3) + #define TI_ADC_OFF (1<<2) + #define TI_ADC_ON (0<<2) + #define TI_MIC_OFF (1<<1) + #define TI_MIC_ON (0<<1) + #define TI_LINE_OFF (1<<0) + #define TI_LINE_ON (0<<0) + #define TI_POWER_ALL_OFF 0xff + +#define REG_TI_DIG_FORMAT 0x07 + #define TI_MODE_MASTER (1<<6) + #define TI_MODE_SLAVE (0<<6) + #define TI_LRSWAP_ON (1<<5) + #define TI_LRSWAP_OFF (0<<5) + #define TI_LRP_R_LRCIN_LOW (1<<4) + #define TI_LRP_R_LRCIN_HIGH (0<<4) + #define TI_LRP_MSB_ON_2BLCK (1<<4) + #define TI_LRP_MSB_ON_1BLCK (0<<4) + #define TI_IWL_16BIT (0<<2) + #define TI_IWL_20BIT (1<<2) + #define TI_IWL_24BIT (2<<2) + #define TI_IWL_32BIT (3<<2) + #define TI_FOR_DSP (3<<0) + #define TI_FOR_I2S (2<<0) + #define TI_FOR_L_ALIGN (1<<0) + #define TI_FOR_R_ALIGN (0<<0) + +#define REG_TI_SAMPLE_RATE 0x08 + #define TI_CLKIN_DIV_ON (1<<6) + #define TI_CLKIN_DIV_OFF (0<<6) + #define TI_CLKOUT_DIV_ON (1<<7) + #define TI_CLKOUT_DIV_OFF (0<<7) + #define TI_SR_VALUE(v) (((v)&0xf)<<2) + #define TI_BOSR_FAST (1<<1) + #define TI_BOSR_SLOW (0<<1) + #define TI_CLK_MODE_USB (1<<0) + #define TI_CLK_MODE_NORM (0<<0) + +#define REG_TI_DIG_ACT 0x09 + #define TI_ACT_ON (1<<0) + #define TI_ACT_OFF (0<<0) + +#define REG_TI_RESET 0x0f + #define TI_RESET 0 + +#define AIC23_VERSION "0.1" + +/* + * AIC23 register cache + */ +static const u16 tlv320aic23_reg[] = { + 0x0097, 0x0097, 0x00F9, 0x00F9, /* 0 */ + 0x001A, 0x0004, 0x0007, 0x0001, /* 4 */ + 0x0020, 0x0000, 0x0000, 0x0000, /* 8 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 12 */ +}; + +static const char *rec_src_text[] = { "Line", "Mic" }; +static const char *deemph_text[] = {"None", "32Khz", "44.1Khz", "48Khz"}; + +static const struct soc_enum rec_src_enum = + SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text); + +static const struct snd_kcontrol_new tlv320aic23_rec_src_mux_controls = +SOC_DAPM_ENUM("Input Select", rec_src_enum); + +static const struct soc_enum tlv320aic23_rec_src = + SOC_ENUM_SINGLE(TLV320AIC23_ANLG, 2, 2, rec_src_text); +static const struct soc_enum tlv320aic23_deemph = + SOC_ENUM_SINGLE(TLV320AIC23_DIGT, 1, 4, deemph_text); + +static const DECLARE_TLV_DB_SCALE(out_gain_tlv, -12100, 100, 0); +static const DECLARE_TLV_DB_SCALE(input_gain_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_vol_tlv, -1800, 300, 0); + +static int snd_soc_tlv320aic23_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 val, reg; + + val = (ucontrol->value.integer.value[0] & 0x07); + + /* linear conversion to userspace + * 000 = -6db + * 001 = -9db + * 010 = -12db + * 011 = -18db (Min) + * 100 = 0db (Max) + */ + val = (val >= 4) ? 4 : (3 - val); + + reg = snd_soc_read(codec, TLV320AIC23_ANLG) & (~0x1C0); + snd_soc_write(codec, TLV320AIC23_ANLG, reg | (val << 6)); + + return 0; +} + +static int snd_soc_tlv320aic23_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 val; + + val = snd_soc_read(codec, TLV320AIC23_ANLG) & (0x1C0); + val = val >> 6; + val = (val >= 4) ? 4 : (3 - val); + ucontrol->value.integer.value[0] = val; + return 0; + +} + +static const struct snd_kcontrol_new tlv320aic23_snd_controls[] = { + SOC_DOUBLE_R_TLV("Digital Playback Volume", TLV320AIC23_LCHNVOL, + TLV320AIC23_RCHNVOL, 0, 127, 0, out_gain_tlv), + SOC_SINGLE("Digital Playback Switch", TLV320AIC23_DIGT, 3, 1, 1), + SOC_DOUBLE_R("Line Input Switch", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 7, 1, 0), + SOC_DOUBLE_R_TLV("Line Input Volume", TLV320AIC23_LINVOL, + TLV320AIC23_RINVOL, 0, 31, 0, input_gain_tlv), + SOC_SINGLE("Mic Input Switch", TLV320AIC23_ANLG, 1, 1, 1), + SOC_SINGLE("Mic Booster Switch", TLV320AIC23_ANLG, 0, 1, 0), + SOC_SINGLE_EXT_TLV("Sidetone Volume", TLV320AIC23_ANLG, 6, 4, 0, + snd_soc_tlv320aic23_get_volsw, + snd_soc_tlv320aic23_put_volsw, sidetone_vol_tlv), + SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph), +}; + +/* PGA Mixer controls for Line and Mic switch */ +static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0), + SOC_DAPM_SINGLE("Mic Sidetone Switch", TLV320AIC23_ANLG, 5, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", TLV320AIC23_ANLG, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", TLV320AIC23_PWR, 3, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", TLV320AIC23_PWR, 2, 1), + SND_SOC_DAPM_MUX("Capture Source", SND_SOC_NOPM, 0, 0, + &tlv320aic23_rec_src_mux_controls), + SND_SOC_DAPM_MIXER("Output Mixer", TLV320AIC23_PWR, 4, 1, + &tlv320aic23_output_mixer_controls[0], + ARRAY_SIZE(tlv320aic23_output_mixer_controls)), + SND_SOC_DAPM_PGA("Line Input", TLV320AIC23_PWR, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Mic Input", TLV320AIC23_PWR, 1, 1, NULL, 0), + + SND_SOC_DAPM_OUTPUT("LHPOUT"), + SND_SOC_DAPM_OUTPUT("RHPOUT"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("ROUT"), + + SND_SOC_DAPM_INPUT("LLINEIN"), + SND_SOC_DAPM_INPUT("RLINEIN"), + + SND_SOC_DAPM_INPUT("MICIN"), +}; + +static const struct snd_soc_dapm_route tlv320aic23_intercon[] = { + /* Output Mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "Playback Switch", "DAC"}, + {"Output Mixer", "Mic Sidetone Switch", "Mic Input"}, + + /* Outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + + /* Inputs */ + {"Line Input", "NULL", "LLINEIN"}, + {"Line Input", "NULL", "RLINEIN"}, + + {"Mic Input", "NULL", "MICIN"}, + + /* input mux */ + {"Capture Source", "Line", "Line Input"}, + {"Capture Source", "Mic", "Mic Input"}, + {"ADC", NULL, "Capture Source"}, + +}; + +/* AIC23 driver data */ +struct aic23 { + enum snd_soc_control_type control_type; + int mclk; + int requested_adc; + int requested_dac; +}; + +/* + * Common Crystals used + * 11.2896 Mhz /128 = *88.2k /192 = 58.8k + * 12.0000 Mhz /125 = *96k /136 = 88.235K + * 12.2880 Mhz /128 = *96k /192 = 64k + * 16.9344 Mhz /128 = 132.3k /192 = *88.2k + * 18.4320 Mhz /128 = 144k /192 = *96k + */ + +/* + * Normal BOSR 0-256/2 = 128, 1-384/2 = 192 + * USB BOSR 0-250/2 = 125, 1-272/2 = 136 + */ +static const int bosr_usb_divisor_table[] = { + 128, 125, 192, 136 +}; +#define LOWER_GROUP ((1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<6) | (1<<7)) +#define UPPER_GROUP ((1<<8) | (1<<9) | (1<<10) | (1<<11) | (1<<15)) +static const unsigned short sr_valid_mask[] = { + LOWER_GROUP|UPPER_GROUP, /* Normal, bosr - 0*/ + LOWER_GROUP, /* Usb, bosr - 0*/ + LOWER_GROUP|UPPER_GROUP, /* Normal, bosr - 1*/ + UPPER_GROUP, /* Usb, bosr - 1*/ +}; +/* + * Every divisor is a factor of 11*12 + */ +#define SR_MULT (11*12) +#define A(x) (SR_MULT/x) +static const unsigned char sr_adc_mult_table[] = { + A(2), A(2), A(12), A(12), 0, 0, A(3), A(1), + A(2), A(2), A(11), A(11), 0, 0, 0, A(1) +}; +static const unsigned char sr_dac_mult_table[] = { + A(2), A(12), A(2), A(12), 0, 0, A(3), A(1), + A(2), A(11), A(2), A(11), 0, 0, 0, A(1) +}; + +static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc, + int dac, int dac_l, int dac_h, int need_dac) +{ + if ((adc >= adc_l) && (adc <= adc_h) && + (dac >= dac_l) && (dac <= dac_h)) { + int diff_adc = need_adc - adc; + int diff_dac = need_dac - dac; + return abs(diff_adc) + abs(diff_dac); + } + return UINT_MAX; +} + +static int find_rate(int mclk, u32 need_adc, u32 need_dac) +{ + int i, j; + int best_i = -1; + int best_j = -1; + int best_div = 0; + unsigned best_score = UINT_MAX; + int adc_l, adc_h, dac_l, dac_h; + + need_adc *= SR_MULT; + need_dac *= SR_MULT; + /* + * rates given are +/- 1/32 + */ + adc_l = need_adc - (need_adc >> 5); + adc_h = need_adc + (need_adc >> 5); + dac_l = need_dac - (need_dac >> 5); + dac_h = need_dac + (need_dac >> 5); + for (i = 0; i < ARRAY_SIZE(bosr_usb_divisor_table); i++) { + int base = mclk / bosr_usb_divisor_table[i]; + int mask = sr_valid_mask[i]; + for (j = 0; j < ARRAY_SIZE(sr_adc_mult_table); + j++, mask >>= 1) { + int adc; + int dac; + int score; + if ((mask & 1) == 0) + continue; + adc = base * sr_adc_mult_table[j]; + dac = base * sr_dac_mult_table[j]; + score = get_score(adc, adc_l, adc_h, need_adc, + dac, dac_l, dac_h, need_dac); + if (best_score > score) { + best_score = score; + best_i = i; + best_j = j; + best_div = 0; + } + score = get_score((adc >> 1), adc_l, adc_h, need_adc, + (dac >> 1), dac_l, dac_h, need_dac); + /* prefer to have a /2 */ + if ((score != UINT_MAX) && (best_score >= score)) { + best_score = score; + best_i = i; + best_j = j; + best_div = 1; + } + } + } + return (best_j << 2) | best_i | (best_div << TLV320AIC23_CLKIN_SHIFT); +} + +#ifdef DEBUG +static void get_current_sample_rates(struct snd_soc_codec *codec, int mclk, + u32 *sample_rate_adc, u32 *sample_rate_dac) +{ + int src = snd_soc_read(codec, TLV320AIC23_SRATE); + int sr = (src >> 2) & 0x0f; + int val = (mclk / bosr_usb_divisor_table[src & 3]); + int adc = (val * sr_adc_mult_table[sr]) / SR_MULT; + int dac = (val * sr_dac_mult_table[sr]) / SR_MULT; + if (src & TLV320AIC23_CLKIN_HALF) { + adc >>= 1; + dac >>= 1; + } + *sample_rate_adc = adc; + *sample_rate_dac = dac; +} +#endif + +static int set_sample_rate_control(struct snd_soc_codec *codec, int mclk, + u32 sample_rate_adc, u32 sample_rate_dac) +{ + /* Search for the right sample rate */ + int data = find_rate(mclk, sample_rate_adc, sample_rate_dac); + if (data < 0) { + printk(KERN_ERR "%s:Invalid rate %u,%u requested\n", + __func__, sample_rate_adc, sample_rate_dac); + return -EINVAL; + } + snd_soc_write(codec, TLV320AIC23_SRATE, data); +#ifdef DEBUG + { + u32 adc, dac; + get_current_sample_rates(codec, mclk, &adc, &dac); + printk(KERN_DEBUG "actual samplerate = %u,%u reg=%x\n", + adc, dac, data); + } +#endif + return 0; +} + +static int tlv320aic23_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + u16 iface_reg; + int ret; + struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec); + u32 sample_rate_adc = aic23->requested_adc; + u32 sample_rate_dac = aic23->requested_dac; + u32 sample_rate = params_rate(params); + + printk(KERN_INFO "tlv320aic23_hw_params\n"); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + aic23->requested_dac = sample_rate_dac = sample_rate; + if (!sample_rate_adc) + sample_rate_adc = sample_rate; + } else { + aic23->requested_adc = sample_rate_adc = sample_rate; + if (!sample_rate_dac) + sample_rate_dac = sample_rate; + } + ret = set_sample_rate_control(codec, aic23->mclk, sample_rate_adc, + sample_rate_dac); + if (ret < 0) + return ret; + + iface_reg = snd_soc_read(codec, TLV320AIC23_DIGT_FMT) & ~(0x03 << 2); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface_reg |= (0x01 << 2); + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface_reg |= (0x02 << 2); + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface_reg |= (0x03 << 2); + break; + } + snd_soc_write(codec, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + + printk(KERN_INFO "tlv320aic23_pcm_prepare\n"); + + /* set active */ + snd_soc_write(codec, TLV320AIC23_ACTIVE, 0x0001); + + return 0; +} + +static void tlv320aic23_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec); +printk(KERN_INFO "tlv320aic23_shutdown\n"); + /* deactivate */ + if (!codec->active) { + udelay(50); + snd_soc_write(codec, TLV320AIC23_ACTIVE, 0x0); + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + aic23->requested_dac = 0; + else + aic23->requested_adc = 0; +} + +static int tlv320aic23_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg; + + printk(KERN_INFO "tlv320aic23_mute:%d\n",mute); + + + + reg = snd_soc_read(codec, TLV320AIC23_DIGT); + if (mute) + reg |= TLV320AIC23_DACM_MUTE; + + else + reg &= ~TLV320AIC23_DACM_MUTE; + + snd_soc_write(codec, TLV320AIC23_DIGT, reg); + + return 0; +} + +static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface_reg; + + printk(KERN_INFO "tlv320aic23_set_dai_fmt\n"); + + iface_reg = snd_soc_read(codec, TLV320AIC23_DIGT_FMT) & (~0x03); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface_reg |= TLV320AIC23_MS_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface_reg &= ~TLV320AIC23_MS_MASTER; + break; + default: + return -EINVAL; + + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface_reg |= TLV320AIC23_FOR_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg |= TLV320AIC23_LRP_ON; + case SND_SOC_DAIFMT_DSP_B: + iface_reg |= TLV320AIC23_FOR_DSP; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface_reg |= TLV320AIC23_FOR_LJUST; + break; + default: + return -EINVAL; + + } + + snd_soc_write(codec, TLV320AIC23_DIGT_FMT, iface_reg); + + return 0; +} + +static int tlv320aic23_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct aic23 *aic23 = snd_soc_dai_get_drvdata(codec_dai); + + printk(KERN_INFO "tlv320aic23_set_dai_sysclk\n"); + + aic23->mclk = freq; + return 0; +} + +static int tlv320aic23_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = snd_soc_read(codec, TLV320AIC23_PWR) & 0x17f; + + + printk(KERN_INFO "tlv320aic23_set_bias_level:%d\n",level); + + + switch (level) { + case SND_SOC_BIAS_ON: + /* vref/mid, osc on, dac unmute */ + reg &= ~(TLV320AIC23_DEVICE_PWR_OFF | TLV320AIC23_OSC_OFF | \ + TLV320AIC23_DAC_OFF|TLV320AIC23_CLK_OFF); + snd_soc_write(codec, TLV320AIC23_PWR, reg); + + +/*ss + snd_soc_write(codec,REG_TI_RESET, TI_RESET); + + snd_soc_write(codec,REG_TI_LINE_VOL_L, TI_LRS_ON|TI_LIM_ON); + + snd_soc_write(codec,REG_TI_LINE_VOL_R, TI_LRS_ON|TI_LIM_ON); + + + // Avoid explosive voice pulse when turning on audio chip + snd_soc_write(codec,REG_TI_HEAD_VOL_L, TI_LRS_ON|TI_LHV_VALUE(0x79)); + snd_soc_write(codec,REG_TI_HEAD_VOL_R, TI_LRS_ON|TI_LHV_VALUE(0x79)); + + + snd_soc_write(codec,REG_TI_ANA_AUD_CTRL, TI_SIDETONE_OFF|TI_ANA_DAC_ON|TI_ANA_BYP_OFF|TI_INSEL_MIC|TI_MICM_ON|TI_MICB_OFF); + + snd_soc_write(codec,REG_TI_DIG_AUD_CTRL, TI_DACM_OFF|TI_DEEMP_OFF|TI_ADCHP_OFF); + + snd_soc_write(codec,REG_TI_DIG_FORMAT, TI_LRSWAP_OFF|TI_IWL_16BIT|TI_MODE_SLAVE|TI_LRP_R_LRCIN_HIGH|TI_FOR_I2S); + + snd_soc_write(codec,REG_TI_SAMPLE_RATE, TI_CLKIN_DIV_OFF|TI_CLKOUT_DIV_OFF|TI_SR_VALUE(3) | TI_BOSR_SLOW|TI_CLK_MODE_USB); + + snd_soc_write(codec,REG_TI_DIG_ACT, TI_ACT_ON); + + snd_soc_write(codec,REG_TI_POWER_CTRL, TI_POWER_ON|TI_CLK_ON|TI_OSC_ON|TI_OUT_ON|TI_DAC_ON|TI_ADC_ON|TI_MIC_ON|TI_LINE_OFF); + + snd_soc_write(codec,REG_TI_ANA_AUD_CTRL, TI_SIDETONE_OFF|TI_ANA_DAC_ON|TI_ANA_BYP_OFF|TI_INSEL_MIC|TI_MICB_OFF|TI_MICM_OFF); + + + +*/ + + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* everything off except vref/vmid, */ + snd_soc_write(codec, TLV320AIC23_PWR, + reg | TLV320AIC23_CLK_OFF); + break; + case SND_SOC_BIAS_OFF: + /* everything off, dac mute, inactive */ + snd_soc_write(codec, TLV320AIC23_ACTIVE, 0x0); + snd_soc_write(codec, TLV320AIC23_PWR, 0x1ff); + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define AIC23_RATES SNDRV_PCM_RATE_8000_96000 +#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops tlv320aic23_dai_ops = { + .prepare = tlv320aic23_pcm_prepare, + .hw_params = tlv320aic23_hw_params, + .shutdown = tlv320aic23_shutdown, + .digital_mute = tlv320aic23_mute, + .set_fmt = tlv320aic23_set_dai_fmt, + .set_sysclk = tlv320aic23_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver tlv320aic23_dai = { + .name = "tlv320aic23-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = AIC23_RATES, + .formats = AIC23_FORMATS,}, + .ops = &tlv320aic23_dai_ops, +}; + +static int tlv320aic23_suspend(struct snd_soc_codec *codec) +{ + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int tlv320aic23_resume(struct snd_soc_codec *codec) +{ + snd_soc_cache_sync(codec); + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static int tlv320aic23_probe(struct snd_soc_codec *codec) +{ + struct aic23 *aic23 = snd_soc_codec_get_drvdata(codec); + int ret; + + printk(KERN_INFO "AIC23 Audio Codec %s\n", AIC23_VERSION); + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, aic23->control_type); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + /* Reset codec */ + snd_soc_write(codec, TLV320AIC23_RESET, 0); + + /* Write the register default value to cache for reserved registers, + * so the write to the these registers are suppressed by the cache + * restore code when it skips writes of default registers. + */ + snd_soc_cache_write(codec, 0x0A, 0); + snd_soc_cache_write(codec, 0x0B, 0); + snd_soc_cache_write(codec, 0x0C, 0); + snd_soc_cache_write(codec, 0x0D, 0); + snd_soc_cache_write(codec, 0x0E, 0); + + /* power on device */ + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + snd_soc_write(codec, TLV320AIC23_DIGT, TLV320AIC23_DEEMP_44K); + + /* Unmute input */ + snd_soc_update_bits(codec, TLV320AIC23_LINVOL, + TLV320AIC23_LIM_MUTED, TLV320AIC23_LRS_ENABLED); + + snd_soc_update_bits(codec, TLV320AIC23_RINVOL, + TLV320AIC23_LIM_MUTED, TLV320AIC23_LRS_ENABLED); + + snd_soc_update_bits(codec, TLV320AIC23_ANLG, + TLV320AIC23_BYPASS_ON | TLV320AIC23_MICM_MUTED, + 0); + + /* Default output volume */ + snd_soc_write(codec, TLV320AIC23_LCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & TLV320AIC23_OUT_VOL_MASK); + snd_soc_write(codec, TLV320AIC23_RCHNVOL, + TLV320AIC23_DEFAULT_OUT_VOL & TLV320AIC23_OUT_VOL_MASK); + + snd_soc_write(codec, TLV320AIC23_ACTIVE, 0x1); + + snd_soc_add_codec_controls(codec, tlv320aic23_snd_controls, + ARRAY_SIZE(tlv320aic23_snd_controls)); + + return 0; +} + +static int tlv320aic23_remove(struct snd_soc_codec *codec) +{ + tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_tlv320aic23 = { + .reg_cache_size = ARRAY_SIZE(tlv320aic23_reg), + .reg_word_size = sizeof(u16), + .reg_cache_default = tlv320aic23_reg, + .probe = tlv320aic23_probe, + .remove = tlv320aic23_remove, + .suspend = tlv320aic23_suspend, + .resume = tlv320aic23_resume, + .set_bias_level = tlv320aic23_set_bias_level, + .dapm_widgets = tlv320aic23_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tlv320aic23_dapm_widgets), + .dapm_routes = tlv320aic23_intercon, + .num_dapm_routes = ARRAY_SIZE(tlv320aic23_intercon), +}; + + +#if defined(CONFIG_SPI_MASTER) +static int tlv320aic23_spi_probe(struct spi_device *spi) +{ + struct aic23 *aic23; + int ret; + printk(KERN_INFO "tlv320aic23_spi_probe\n"); + + aic23 = devm_kzalloc(&spi->dev, sizeof(struct aic23), GFP_KERNEL); + if (aic23 == NULL) { + printk(KERN_INFO "tlv320aic23_spi_probe error!"); + return -ENOMEM; + } + spi->mode=SPI_MODE_2; + + spi_set_drvdata(spi, aic23); + aic23->control_type = SND_SOC_SPI; + + ret = snd_soc_register_codec(&spi->dev, + &soc_codec_dev_tlv320aic23, &tlv320aic23_dai, 1); + return ret; +} + +static int __exit tlv320aic23_spi_remove(struct spi_device *spi) +{ + snd_soc_unregister_codec(&spi->dev); + return 0; +} + +static const struct spi_device_id tlv320aic23_spi_id[] = { + {"tlv320aic23-spi", 0}, + {} +}; + +static struct spi_driver tlv320aic23_spi_driver = { + .driver = { + .name = "tlv320aic23-spi", + .owner = THIS_MODULE, + }, + .probe = tlv320aic23_spi_probe, + .remove = __devexit_p(tlv320aic23_spi_remove), + .id_table = tlv320aic23_spi_id, +}; +#endif +static int __init tlv320aic23_modinit(void) +{ + int ret; + +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&tlv320aic23_spi_driver); +#endif + + return ret; +} +module_init(tlv320aic23_modinit); + +static void __exit tlv320aic23_exit(void) +{ + +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&tlv320aic23_spi_driver); +#endif + +} +module_exit(tlv320aic23_exit); + +MODULE_DESCRIPTION("ASoC TLV320AIC23 codec driver"); +MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rdafpga/tlv320aic23_spi.h b/sound/soc/rdafpga/tlv320aic23_spi.h new file mode 100644 index 000000000000..e804120bd3da --- /dev/null +++ b/sound/soc/rdafpga/tlv320aic23_spi.h @@ -0,0 +1,119 @@ +/* + * ALSA SoC TLV320AIC23 codec driver + * + * Author: Arun KS, <arunks@mistralsolutions.com> + * Copyright: (C) 2008 Mistral Solutions Pvt Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _TLV320AIC23_H +#define _TLV320AIC23_H + +/* Codec TLV320AIC23 */ +#define TLV320AIC23_LINVOL 0x00 +#define TLV320AIC23_RINVOL 0x01 +#define TLV320AIC23_LCHNVOL 0x02 +#define TLV320AIC23_RCHNVOL 0x03 +#define TLV320AIC23_ANLG 0x04 +#define TLV320AIC23_DIGT 0x05 +#define TLV320AIC23_PWR 0x06 +#define TLV320AIC23_DIGT_FMT 0x07 +#define TLV320AIC23_SRATE 0x08 +#define TLV320AIC23_ACTIVE 0x09 +#define TLV320AIC23_RESET 0x0F + +/* Left (right) line input volume control register */ +#define TLV320AIC23_LRS_ENABLED 0x0100 +#define TLV320AIC23_LIM_MUTED 0x0080 +#define TLV320AIC23_LIV_DEFAULT 0x0017 +#define TLV320AIC23_LIV_MAX 0x001f +#define TLV320AIC23_LIV_MIN 0x0000 + +/* Left (right) channel headphone volume control register */ +#define TLV320AIC23_LZC_ON 0x0080 +#define TLV320AIC23_LHV_DEFAULT 0x0079 +#define TLV320AIC23_LHV_MAX 0x007f +#define TLV320AIC23_LHV_MIN 0x0000 + +/* Analog audio path control register */ +#define TLV320AIC23_STA_REG(x) ((x)<<6) +#define TLV320AIC23_STE_ENABLED 0x0020 +#define TLV320AIC23_DAC_SELECTED 0x0010 +#define TLV320AIC23_BYPASS_ON 0x0008 +#define TLV320AIC23_INSEL_MIC 0x0004 +#define TLV320AIC23_MICM_MUTED 0x0002 +#define TLV320AIC23_MICB_20DB 0x0001 + +/* Digital audio path control register */ +#define TLV320AIC23_DACM_MUTE 0x0008 +#define TLV320AIC23_DEEMP_32K 0x0002 +#define TLV320AIC23_DEEMP_44K 0x0004 +#define TLV320AIC23_DEEMP_48K 0x0006 +#define TLV320AIC23_ADCHP_ON 0x0001 + +/* Power control down register */ +#define TLV320AIC23_DEVICE_PWR_OFF 0x0080 +#define TLV320AIC23_CLK_OFF 0x0040 +#define TLV320AIC23_OSC_OFF 0x0020 +#define TLV320AIC23_OUT_OFF 0x0010 +#define TLV320AIC23_DAC_OFF 0x0008 +#define TLV320AIC23_ADC_OFF 0x0004 +#define TLV320AIC23_MIC_OFF 0x0002 +#define TLV320AIC23_LINE_OFF 0x0001 + +/* Digital audio interface register */ +#define TLV320AIC23_MS_MASTER 0x0040 +#define TLV320AIC23_LRSWAP_ON 0x0020 +#define TLV320AIC23_LRP_ON 0x0010 +#define TLV320AIC23_IWL_16 0x0000 +#define TLV320AIC23_IWL_20 0x0004 +#define TLV320AIC23_IWL_24 0x0008 +#define TLV320AIC23_IWL_32 0x000C +#define TLV320AIC23_FOR_I2S 0x0002 +#define TLV320AIC23_FOR_DSP 0x0003 +#define TLV320AIC23_FOR_LJUST 0x0001 + +/* Sample rate control register */ +#define TLV320AIC23_CLKOUT_HALF 0x0080 +#define TLV320AIC23_CLKIN_HALF 0x0040 +#define TLV320AIC23_BOSR_384fs 0x0002 /* BOSR_272fs in USB mode */ +#define TLV320AIC23_USB_CLK_ON 0x0001 +#define TLV320AIC23_SR_MASK 0xf +#define TLV320AIC23_CLKOUT_SHIFT 7 +#define TLV320AIC23_CLKIN_SHIFT 6 +#define TLV320AIC23_SR_SHIFT 2 +#define TLV320AIC23_BOSR_SHIFT 1 + +/* Digital interface register */ +#define TLV320AIC23_ACT_ON 0x0001 + +/* + * AUDIO related MACROS + */ + +#define TLV320AIC23_DEFAULT_OUT_VOL 0x70 +#define TLV320AIC23_DEFAULT_IN_VOLUME 0x10 + +#define TLV320AIC23_OUT_VOL_MIN TLV320AIC23_LHV_MIN +#define TLV320AIC23_OUT_VOL_MAX TLV320AIC23_LHV_MAX +#define TLV320AIC23_OUT_VO_RANGE (TLV320AIC23_OUT_VOL_MAX - \ + TLV320AIC23_OUT_VOL_MIN) +#define TLV320AIC23_OUT_VOL_MASK TLV320AIC23_OUT_VOL_MAX + +#define TLV320AIC23_IN_VOL_MIN TLV320AIC23_LIV_MIN +#define TLV320AIC23_IN_VOL_MAX TLV320AIC23_LIV_MAX +#define TLV320AIC23_IN_VOL_RANGE (TLV320AIC23_IN_VOL_MAX - \ + TLV320AIC23_IN_VOL_MIN) +#define TLV320AIC23_IN_VOL_MASK TLV320AIC23_IN_VOL_MAX + +#define TLV320AIC23_SIDETONE_MASK 0x1c0 +#define TLV320AIC23_SIDETONE_0 0x100 +#define TLV320AIC23_SIDETONE_6 0x000 +#define TLV320AIC23_SIDETONE_9 0x040 +#define TLV320AIC23_SIDETONE_12 0x080 +#define TLV320AIC23_SIDETONE_18 0x0c0 + +#endif /* _TLV320AIC23_H */ |