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 | 2c3289c7071a4461dc92faac12be7097fbf3457a (patch) | |
tree | 4ae6b829fd8688641f26f93e79b1763658ee16fd | |
parent | 09714f8ea9072b750c02b0208a10c615901a42e4 (diff) |
sound: soc: rda: RDA support
-rw-r--r-- | sound/soc/rda/Kconfig | 28 | ||||
-rw-r--r-- | sound/soc/rda/Makefile | 12 | ||||
-rw-r--r-- | sound/soc/rda/rda_aif.h | 234 | ||||
-rw-r--r-- | sound/soc/rda/rda_audifc.c | 347 | ||||
-rw-r--r-- | sound/soc/rda/rda_audifc.h | 86 | ||||
-rw-r--r-- | sound/soc/rda/rda_bb_ifc.h | 120 | ||||
-rw-r--r-- | sound/soc/rda/rda_codec.c | 1061 | ||||
-rw-r--r-- | sound/soc/rda/rda_codec.h | 138 | ||||
-rw-r--r-- | sound/soc/rda/rda_codec_adp.h | 423 | ||||
-rw-r--r-- | sound/soc/rda/rda_dai.c | 471 | ||||
-rw-r--r-- | sound/soc/rda/rda_dsp_aud.c | 334 | ||||
-rw-r--r-- | sound/soc/rda/rda_dsp_aud.h | 44 | ||||
-rw-r--r-- | sound/soc/rda/rda_pcm.c | 378 | ||||
-rw-r--r-- | sound/soc/rda/rda_pcm.h | 40 | ||||
-rw-r--r-- | sound/soc/rda/rda_soundcard.c | 150 | ||||
-rw-r--r-- | sound/soc/rda/rda_voice_codec.c | 211 | ||||
-rw-r--r-- | sound/soc/rda/rda_voice_dai.c | 161 | ||||
-rw-r--r-- | sound/soc/rda/rda_voice_pcm.c | 588 | ||||
-rw-r--r-- | sound/soc/rda/rda_voice_pcm.h | 40 |
19 files changed, 4866 insertions, 0 deletions
diff --git a/sound/soc/rda/Kconfig b/sound/soc/rda/Kconfig new file mode 100644 index 000000000000..1fa4fded4bc0 --- /dev/null +++ b/sound/soc/rda/Kconfig @@ -0,0 +1,28 @@ +menu "Soc Audio for the RDA chips." + + +config SND_RDA_SOC + tristate "SoC Audio for the RDA." + depends on ARCH_RDA && !RDA_FPGA + +config SND_RDA_SOC_RDASOUNDCARD + tristate "rda CHIP sound card -- SoC Audio support for RAD chip." + depends on SND_RDA_SOC + select SND_RDA_SOC_RDASOUNDCARD_PCM + select SND_RDA_SOC_RDASOUNDCARD_DAI + select SND_RDA_SOC_RDASOUNDCARD_CODEC + help + Say Y if you want to build in to kernel, Say M for module. + +config SND_RDA_SOC_RDASOUNDCARD_PCM + tristate + +config SND_RDA_SOC_RDASOUNDCARD_DAI + tristate + +config SND_RDA_SOC_RDASOUNDCARD_CODEC + tristate + +endmenu + + diff --git a/sound/soc/rda/Makefile b/sound/soc/rda/Makefile new file mode 100644 index 000000000000..51cae8c62051 --- /dev/null +++ b/sound/soc/rda/Makefile @@ -0,0 +1,12 @@ +# rda Platform Support +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_PCM) += rda_pcm.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_PCM) += rda_dsp_aud.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_PCM) += rda_voice_pcm.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_PCM) += rda_audifc.o + +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_DAI) += rda_dai.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_DAI) += rda_voice_dai.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_CODEC) += rda_codec.o +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD_CODEC) += rda_voice_codec.o + +obj-$(CONFIG_SND_RDA_SOC_RDASOUNDCARD) += rda_soundcard.o diff --git a/sound/soc/rda/rda_aif.h b/sound/soc/rda/rda_aif.h new file mode 100644 index 000000000000..80795b33c92b --- /dev/null +++ b/sound/soc/rda/rda_aif.h @@ -0,0 +1,234 @@ +/* + * 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 _RDA_AIF_H_ +#define _RDA_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/rda/rda_audifc.c b/sound/soc/rda/rda_audifc.c new file mode 100644 index 000000000000..a239ee096296 --- /dev/null +++ b/sound/soc/rda/rda_audifc.c @@ -0,0 +1,347 @@ +/* + * 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 "rda_bb_ifc.h" +#include "rda_audifc.h" + +static HWP_BB_IFC_T __iomem *hwp_audIfc; + +static struct rda_audifc_ch *audifc_ch[RDA_AUDIFC_QTY]; + +static int irq[2]; + +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; + + if(!ptr_ch || ptr_ch->inuse == IFC_NOUSE) + return IRQ_NONE; + + reg = hwp_audIfc->ch[RDA_AUDIFC_RECORD].status + & (BB_IFC_CAUSE_IEF | BB_IFC_CAUSE_IHF | BB_IFC_CAUSE_I4F | BB_IFC_CAUSE_I3_4F); + + hwp_audIfc->ch[RDA_AUDIFC_RECORD].int_clear = reg; + + if(ptr_ch->callback == NULL) + return IRQ_NONE; + + 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; +} + +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; + + if(!ptr_ch || ptr_ch->inuse == IFC_NOUSE) + 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(ptr_ch->callback == NULL) + return IRQ_NONE; + + 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) +{ + + if (hwp_audIfc->ch[ch].status & BB_IFC_ENABLE) { + printk(KERN_INFO "%s: audio ifc busy \n", __func__); + return RDA_AUDIFC_ERROR; + } + // Assert on word alignement + if (((u32) params->src_addr) % 4 != 0) { + 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) { + 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; + hwp_audIfc->ch[ch].Fifo_Size = params->xfer_size; + + // we need this four ints !!!! + 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) +{ + return (u32) (hwp_audIfc->ch[ch].cur_ahb_addr); +} + +audifc_addr_t rda_get_audifc_dst_pos(u8 ch) +{ + return (u32) (hwp_audIfc->ch[ch].cur_ahb_addr); +} + +void rda_start_audifc(u8 ch) +{ + enable_irq(irq[ch]); + hwp_audIfc->ch[ch].control = BB_IFC_ENABLE; + return; +} + +void rda_stop_audifc(u8 ch) +{ + disable_irq_nosync(irq[ch]); + hwp_audIfc->ch[ch].control = BB_IFC_DISABLE; + return; +} + +void rda_poll_audifc(u8 ch) +{ + 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. */ + if(audifc_ch[dev_id]->inuse == IFC_INUSE) + 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[dev_id]->inuse = IFC_INUSE; + + *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; + audifc_ch[ch]->inuse = IFC_NOUSE; + + return; +} + +static int rda_audifc_probe(struct platform_device *pdev) +{ + struct rda_audifc_device_data *audifc_device; + struct resource *mem; + //int irq[RDA_AUDIFC_QTY]; + int ret; + + 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; + } + disable_irq(irq[RDA_AUDIFC_RECORD]); + + 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; + } + disable_irq(irq[RDA_AUDIFC_PLAY]); + printk(KERN_INFO"audio ifc : initialized .\n"); + + return 0; +} + +static int rda_audifc_remove(struct platform_device *pdev) +{ + 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]->inuse = IFC_NOUSE; + 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]->inuse = IFC_NOUSE; + audifc_ch[RDA_AUDIFC_PLAY] = NULL; + } + + return 0; +} + +static struct platform_driver rda_audifc_driver = { + .probe = rda_audifc_probe, + .remove = 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 RDA FPGA PCM"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_ALIAS("platform: rda-audifc"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rda/rda_audifc.h b/sound/soc/rda/rda_audifc.h new file mode 100644 index 000000000000..ee6bc079662f --- /dev/null +++ b/sound/soc/rda/rda_audifc.h @@ -0,0 +1,86 @@ + +/* + * 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 _RDA_AUDIFC_H +#define _RDA_AUDIFC_H + +#include <linux/bitops.h> + +// ============================================================================= +// MACROS +// ============================================================================= + +#define IFC_INUSE 1 +#define IFC_NOUSE 0 + +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 /* _RDA_AUDIFC_H */ diff --git a/sound/soc/rda/rda_bb_ifc.h b/sound/soc/rda/rda_bb_ifc.h new file mode 100644 index 000000000000..76fa81d27652 --- /dev/null +++ b/sound/soc/rda/rda_bb_ifc.h @@ -0,0 +1,120 @@ +//============================================================================== +// +// 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 _RDA_BB_IFC_H_ +#define _RDA_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; + +//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) + +//cur_ahb_addr +#define BB_IFC_CUR_AHB_ADDR(n) (((n)&0x3FFFFFF)<<0) + +#endif diff --git a/sound/soc/rda/rda_codec.c b/sound/soc/rda/rda_codec.c new file mode 100644 index 000000000000..c335391c93db --- /dev/null +++ b/sound/soc/rda/rda_codec.c @@ -0,0 +1,1061 @@ +/* + * ALSA SoC RDA 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: + * rda codec + * + * 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 <mach/iomap.h> +#include <asm/io.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <plat/reg_spi.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 <plat/rda_debug.h> +#include <plat/md_sys.h> + +#include <linux/gpio.h> + +#include "rda_codec.h" +#include "rda_codec_adp.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +static int gpio_audio_extpa = -1; +static int gpio_audio_extpa_1 = -1;//add for external PA + +//#define DEBUG 1 + +/* rda_codec_data driver data */ +struct rda_codec_data { + void __iomem *io_base; + struct msys_device *codec_msys; + struct snd_soc_codec* codec; + u32 in_sample_rate; + u32 in_channel_nb; + u32 out_sample_rate; + u32 out_channel_nb; + SND_ITF_T itf; + HAL_AIF_STREAM_T stream; + AUD_LEVEL_T cfg; + AUD_APP_MODE_T CodecAppMode; + u8 codec_is_open; + + // loop mode + SND_ITF_T loop_mode_itf; +}; + +#ifdef DEBUG +static void aud_Dump(SND_ITF_T itf, HAL_AIF_STREAM_T *stream, AUD_LEVEL_T *cfg) +{ + printk(KERN_INFO "############################### \n"); + printk(KERN_INFO "itf is [%d] \n", itf); + if(stream != NULL) { + printk(KERN_INFO "stream->sampleRate is [%d] \n", stream->sampleRate); + printk(KERN_INFO "stream->channelNb is [%d] \n", stream->channelNb); + } + else { + printk(KERN_INFO "stream is NULL!!! \n"); + } + if(cfg != NULL) { + printk(KERN_INFO "cfg->spkLevel is [%d] \n", cfg->spkLevel); + printk(KERN_INFO "cfg->micLevel is [%d] \n", cfg->micLevel); + } + else { + printk(KERN_INFO "cfg is NULL!!! \n"); + } + + printk(KERN_INFO "############################### \n"); +} +#endif + +static int aud_StreamStart(SND_ITF_T itf, HAL_AIF_STREAM_T *stream, AUD_LEVEL_T* cfg, + struct rda_codec_data* codec_data) +{ + int ret = 0; + u8 __dat[sizeof(SND_ITF_T) + sizeof(HAL_AIF_STREAM_T) + sizeof(AUD_LEVEL_T)] = {0}; + struct client_cmd codec_cmd; + +#ifdef DEBUG + aud_Dump(itf, stream, cfg); +#endif + + memcpy((u8 *)&__dat, &itf, + sizeof(SND_ITF_T)); + memcpy((u8 *)&__dat + sizeof(SND_ITF_T), stream, + sizeof(HAL_AIF_STREAM_T)); + memcpy((u8* )&__dat + sizeof(SND_ITF_T) + sizeof(HAL_AIF_STREAM_T), cfg, + sizeof(AUD_LEVEL_T)); + + memset(&codec_cmd, 0, sizeof(codec_cmd)); + codec_cmd.pmsys_dev = codec_data->codec_msys; + codec_cmd.mod_id = SYS_AUDIO_MOD; + codec_cmd.mesg_id = SYS_AUDIO_CMD_AUD_STREAM_START; + codec_cmd.pdata = (void *)&__dat; + codec_cmd.data_size = sizeof(__dat); + ret = rda_msys_send_cmd(&codec_cmd); + if ( ret ) + printk(KERN_INFO ">>>> [%s], ret [%d] \n", __func__, ret); + else + rda_dbg_audio(">>>> [%s], ret [%d] \n", __func__, ret); + + return ret; +} + +static int aud_StreamRecord(SND_ITF_T itf, HAL_AIF_STREAM_T *stream, AUD_LEVEL_T* cfg, + struct rda_codec_data* codec_data) +{ + int ret = 0; + u8 __dat[sizeof(SND_ITF_T) + sizeof(HAL_AIF_STREAM_T) + sizeof(AUD_LEVEL_T)] = {0}; + struct client_cmd codec_cmd; + +#ifdef DEBUG + aud_Dump(itf, stream, cfg); +#endif + + memcpy((u8 *)&__dat, &itf, sizeof(SND_ITF_T)); + memcpy((u8 *)&__dat + sizeof(SND_ITF_T), stream, + sizeof(HAL_AIF_STREAM_T)); + memcpy((u8 *)&__dat + sizeof(SND_ITF_T) + sizeof(HAL_AIF_STREAM_T), cfg, + sizeof(AUD_LEVEL_T)); + + memset(&codec_cmd, 0, sizeof(codec_cmd)); + codec_cmd.pmsys_dev = codec_data->codec_msys; + codec_cmd.mod_id = SYS_AUDIO_MOD; + codec_cmd.mesg_id = SYS_AUDIO_CMD_AUD_STREAM_RECORD; + codec_cmd.pdata = (void *)&__dat; + codec_cmd.data_size = sizeof(__dat); + ret = rda_msys_send_cmd(&codec_cmd); + if ( ret ) + printk(KERN_INFO ">>>> [%s], ret [%d] \n", __func__, ret); + else + rda_dbg_audio(">>>> [%s], ret [%d] \n", __func__, ret); + + + return ret; +} + +static int aud_StreamStop(SND_ITF_T itf, + struct rda_codec_data* codec_data) +{ + int ret = 0; + u8 __dat[sizeof(SND_ITF_T)] = {0}; + struct client_cmd codec_cmd; + + memcpy((u8 *)&__dat, &itf, sizeof(SND_ITF_T)); + + memset(&codec_cmd, 0, sizeof(codec_cmd)); + codec_cmd.pmsys_dev = codec_data->codec_msys; + codec_cmd.mod_id = SYS_AUDIO_MOD; + codec_cmd.mesg_id = SYS_AUDIO_CMD_AUD_STREAM_STOP; + codec_cmd.pdata = (void *)&__dat; + codec_cmd.data_size = sizeof(__dat); + ret = rda_msys_send_cmd(&codec_cmd); + if ( ret ) + printk(KERN_INFO ">>>> [%s], ret [%d] \n", __func__, ret); + else + rda_dbg_audio(">>>> [%s], ret [%d] \n", __func__, ret); + + return ret; +} + +static int aud_Setup(SND_ITF_T itf, AUD_LEVEL_T* cfg, + struct rda_codec_data* codec_data) +{ + int ret = 0; + u8 __dat[sizeof(SND_ITF_T) + sizeof(AUD_LEVEL_T)] = {0}; + struct client_cmd codec_cmd; + +#ifdef DEBUG + aud_Dump(itf, NULL, cfg); +#endif + + memcpy((u8 *)&__dat, &itf, sizeof(SND_ITF_T)); + memcpy((u8 *)&__dat + sizeof(SND_ITF_T), cfg, sizeof(AUD_LEVEL_T)); + + memset(&codec_cmd, 0, sizeof(codec_cmd)); + codec_cmd.pmsys_dev = codec_data->codec_msys; + codec_cmd.mod_id = SYS_AUDIO_MOD; + codec_cmd.mesg_id = SYS_AUDIO_CMD_AUD_SETUP; + codec_cmd.pdata = (void *)&__dat; + codec_cmd.data_size = sizeof(__dat); + ret = rda_msys_send_cmd(&codec_cmd); + if ( ret ) + printk(KERN_DEBUG ">>>> [%s], ret [%d] \n", __func__, ret); + else + rda_dbg_audio(">>>> [%s], ret [%d] \n", __func__, ret); + + + return ret; +} + +static int aud_LoudspeakerWithEarpiece(u8 on, + struct rda_codec_data* codec_data) +{ + int ret = 0; + u32 __dat = 0; + struct client_cmd codec_cmd; + + __dat = on; + + memset(&codec_cmd, 0, sizeof(codec_cmd)); + codec_cmd.pmsys_dev = codec_data->codec_msys; + codec_cmd.mod_id = SYS_AUDIO_MOD; + codec_cmd.mesg_id = SYS_AUDIO_CMD_AUD_LOUDSPEAKER_WITH_EARPIECE; + codec_cmd.pdata = (void *)&__dat; + codec_cmd.data_size = sizeof(__dat); + ret = rda_msys_send_cmd(&codec_cmd); + if ( ret ) + printk(KERN_INFO ">>>> [%s], ret [%d] \n", __func__, ret); + else + rda_dbg_audio(">>>> [%s], ret [%d] \n", __func__, ret); + + return ret; +} + +static int aud_ForceReceiverMicSelection(u8 on, + struct rda_codec_data* codec_data) +{ + int ret = 0; + u32 __dat = 0; + struct client_cmd codec_cmd; + + __dat = on; + + memset(&codec_cmd, 0, sizeof(codec_cmd)); + codec_cmd.pmsys_dev = codec_data->codec_msys; + codec_cmd.mod_id = SYS_AUDIO_MOD; + codec_cmd.mesg_id = SYS_AUDIO_CMD_AUD_FORCE_RECEIVER_MIC_SELECTION; + codec_cmd.pdata = (void *)&__dat; + codec_cmd.data_size = sizeof(__dat); + ret = rda_msys_send_cmd(&codec_cmd); + if ( ret ) + printk(KERN_INFO ">>>> [%s], ret [%d] \n", __func__, ret); + else + rda_dbg_audio(">>>> [%s], ret [%d] \n", __func__, ret); + + return ret; +} + +static int aud_CodecAppMode(u32 mode, + struct rda_codec_data* codec_data) +{ + int ret = 0; + u32 __dat = 0; + struct client_cmd codec_cmd; + + __dat = mode; + memset(&codec_cmd, 0, sizeof(codec_cmd)); + codec_cmd.pmsys_dev = codec_data->codec_msys; + codec_cmd.mod_id = SYS_AUDIO_MOD; + codec_cmd.mesg_id = SYS_AUDIO_CMD_AUD_CODEC_APP_MODE; + codec_cmd.pdata = (void *)&__dat; + codec_cmd.data_size = sizeof(__dat); + ret = rda_msys_send_cmd(&codec_cmd); + if ( ret ) + printk(KERN_INFO ">>>> [%s], ret [%d] \n", __func__, ret); + else + rda_dbg_audio(">>>> [%s], ret [%d] \n", __func__, ret); + + return ret; + +} + +static int aud_TestModeSetup(SND_ITF_T itf, HAL_AIF_STREAM_T *stream, AUD_LEVEL_T* cfg, + AUD_TEST_T mode, struct rda_codec_data* codec_data) +{ + int ret = 0; + u8 __dat[sizeof(SND_ITF_T) + sizeof(HAL_AIF_STREAM_T) + sizeof(AUD_LEVEL_T) + sizeof(AUD_TEST_T)] = {0}; + struct client_cmd codec_cmd; + +#ifdef DEBUG + aud_Dump(itf, stream, cfg); +#endif + + memcpy((u8 *)&__dat, &itf, sizeof(SND_ITF_T)); + memcpy((u8 *)&__dat + sizeof(SND_ITF_T), stream, + sizeof(HAL_AIF_STREAM_T)); + memcpy((u8 *)&__dat + sizeof(SND_ITF_T) + sizeof(HAL_AIF_STREAM_T), cfg, + sizeof(AUD_LEVEL_T)); + memcpy((u8 *)&__dat + sizeof(SND_ITF_T) + sizeof(HAL_AIF_STREAM_T) + sizeof(AUD_LEVEL_T), &mode, sizeof(AUD_TEST_T)); + + memset(&codec_cmd, 0, sizeof(codec_cmd)); + codec_cmd.pmsys_dev = codec_data->codec_msys; + codec_cmd.mod_id = SYS_AUDIO_MOD; + codec_cmd.mesg_id = SYS_AUDIO_CMD_AUD_TEST_MODE_SETUP; + codec_cmd.pdata = (void *)&__dat; + codec_cmd.data_size = sizeof(__dat); + ret = rda_msys_send_cmd(&codec_cmd); + if ( ret ) + printk(KERN_INFO ">>>> [%s], ret [%d] \n", __func__, ret); + else + rda_dbg_audio(">>>> [%s], ret [%d] \n", __func__, ret); + + return ret; +} +static int ctrl_ext_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} +static int rda_codec_get_open_status(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + printk(KERN_INFO"rda codec : codec_data->codec_is_open %d \n", codec_data->codec_is_open); + + ucontrol->value.integer.value[0] = codec_data->codec_is_open; + + return 0; +} +static int ctrl_ext_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} +static int rda_codec_set_playback_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 volume = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + volume = ucontrol->value.integer.value[0]; + + codec_data->cfg.spkLevel = volume; + + return 0; +} +static int rda_codec_set_capture_volume(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 volume = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + volume = ucontrol->value.integer.value[0]; + + codec_data->cfg.micLevel = volume; + + return 0; +} + +static int rda_codec_set_itf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 itf = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + itf = ucontrol->value.integer.value[0]; + + codec_data->itf = itf; + + return 0; +} + +static int rda_codec_codec_app_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + u32 mode = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + mode = ucontrol->value.integer.value[0]; + + codec_data->CodecAppMode = mode; + + ret = aud_CodecAppMode(mode, codec_data); + + return (ret!=0?-1:0); +} + +static int rda_codec_start_play(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + codec_data->stream.sampleRate = codec_data->out_sample_rate; + codec_data->stream.channelNb = codec_data->out_channel_nb; + ret = aud_StreamStart(codec_data->itf, &(codec_data->stream), + &(codec_data->cfg), codec_data); + + if(!ret) { + codec_data->codec_is_open = TRUE; + } + + return (ret!=0?-1:0); +} + +static int rda_codec_stop(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + ret = aud_StreamStop(codec_data->itf, codec_data); + + if(!ret) { + codec_data->codec_is_open = FALSE; + } + + return (ret!=0?-1:0); +} + +static int rda_codec_start_record(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + codec_data->stream.sampleRate = codec_data->in_sample_rate; + codec_data->stream.channelNb = codec_data->in_channel_nb; + ret = aud_StreamRecord(codec_data->itf, &(codec_data->stream), + &(codec_data->cfg), codec_data); + + if(!ret) { + codec_data->codec_is_open = TRUE; + } + + return (ret!=0?-1:0); +} + +static int rda_codec_set_in_channel_number(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 val = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + val = ucontrol->value.integer.value[0]; + + codec_data->in_channel_nb = val; + + return 0; +} + +static int rda_codec_set_in_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 val = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + val = ucontrol->value.integer.value[0]; + + codec_data->in_sample_rate = val; + + return 0; +} + +static int rda_codec_set_out_channel_number(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 val = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + val = ucontrol->value.integer.value[0]; + + codec_data->out_channel_nb = val; + + return 0; +} + +static int rda_codec_set_out_sample_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 val = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + val = ucontrol->value.integer.value[0]; + + codec_data->out_sample_rate = val; + + return 0; +} + +static int rda_codec_set_spksel(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + AUD_SPK_T spk = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + spk = ucontrol->value.integer.value[0]; + rda_dbg_audio("SPK = %x\n",spk); + if (spk == AUD_SPK_DISABLE) { + if (gpio_is_valid(gpio_audio_extpa)) + gpio_set_value(gpio_audio_extpa,0);//turn off + else + rda_dbg_audio("disable, gpio extpa is invalid !\n"); + if (gpio_is_valid(gpio_audio_extpa_1)) { + gpio_set_value(gpio_audio_extpa_1,0); + rda_dbg_audio("== external PA disable ==\n"); + } else + rda_dbg_audio("disable, gpio extpa 1 is invalid!\n"); + } else if ((spk == AUD_SPK_LOUD_SPEAKER_EAR_PIECE) || (spk == AUD_SPK_LOUD_SPEAKER)) { + if (gpio_is_valid(gpio_audio_extpa)) + gpio_set_value(gpio_audio_extpa,1);//turn on + else + rda_dbg_audio("gpio extpa is invalid !\n"); + if (gpio_is_valid(gpio_audio_extpa_1)) { + gpio_set_value(gpio_audio_extpa_1,1); + rda_dbg_audio("== external PA enable ==\n"); + } else + rda_dbg_audio("gpio extpa 1 is invalid !\n"); + } + + if(spk == AUD_SPK_LOUD_SPEAKER_EAR_PIECE) { + ret = aud_LoudspeakerWithEarpiece(TRUE, codec_data); + } else { + ret = aud_LoudspeakerWithEarpiece(FALSE, codec_data); + } + + return (ret!=0?-1:0); +} +static int rda_codec_force_mainmic(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0, on = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + on = ucontrol->value.integer.value[0]; + + ret = aud_ForceReceiverMicSelection(on, codec_data); + + return (ret!=0?-1:0); +} +static int rda_codec_mute_mic(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u32 mute = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + mute = ucontrol->value.integer.value[0]; + + codec_data->cfg.micLevel = mute; + + return 0; +} +static int rda_codec_set_commit_setup(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + ret = aud_Setup(codec_data->itf, &(codec_data->cfg), codec_data); + + return (ret!=0?-1:0); +} + +static int rda_audio_loop_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + AUD_TEST_T mode = AUD_TEST_NO; + SND_ITF_T itf = SND_ITF_LOUD_SPEAKER; + HAL_AIF_STREAM_T stream; + AUD_LEVEL_T cfg; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + // 1. mode + mode = ucontrol->value.integer.value[0]; + // 2. itf & cfg + switch(mode) { + case AUD_TEST_HEADSETMIC_IN_HEADSET_OUT: + itf = SND_ITF_EAR_PIECE; + mode = AUD_TEST_SIDE_TEST; + cfg.micLevel = SND_MIC_ENABLE; + cfg.spkLevel = SND_SPK_VOL_6; + cfg.toneLevel = 0; + cfg.sideLevel = 0; + break; + case AUD_TEST_RECVMIC_IN_EARPIECE_OUT: + itf = SND_ITF_EAR_PIECE; + mode = AUD_TEST_SIDE_TEST; + cfg.micLevel = SND_MIC_ENABLE; + cfg.spkLevel = SND_SPK_VOL_6; + cfg.toneLevel = 0; + cfg.sideLevel = 0; + break; + case AUD_TEST_MAINMIC_IN_RECEIVER_OUT: + itf = SND_ITF_RECEIVER; + mode = AUD_TEST_SIDE_TEST; + cfg.micLevel = SND_MIC_ENABLE; + cfg.spkLevel = SND_SPK_VOL_6; + cfg.toneLevel = 0; + cfg.sideLevel = 0; + break; + case AUD_TEST_SIDE_TEST: + itf = SND_ITF_LOUD_SPEAKER; + mode = AUD_TEST_SIDE_TEST; + cfg.micLevel = SND_MIC_ENABLE; + cfg.spkLevel = SND_SPK_VOL_1; + cfg.toneLevel = 0; + cfg.sideLevel = 0; + break; + case AUD_TEST_NO: + default: + itf = codec_data->loop_mode_itf; + cfg.micLevel = SND_MIC_MUTE; + cfg.spkLevel = SND_SPK_MUTE; + cfg.toneLevel = 0; + cfg.sideLevel = 0; + break; + } + + codec_data->loop_mode_itf = itf; + // 3. stream + stream.startAddress = NULL; + stream.length = 0; + stream.sampleRate = HAL_AIF_FREQ_8000HZ; + stream.channelNb = HAL_AIF_MONO; + stream.voiceQuality = FALSE; + stream.playSyncWithRecord = FALSE; + stream.halfHandler = NULL; + stream.endHandler = NULL; + + ret = aud_TestModeSetup(itf, &stream, &cfg, mode, codec_data); + + return (ret!=0?-1:0); +} + +/* + * rda_codec_data register cache + */ +static const u16 rda_codec_reg[] = { + 0x0097, 0x0097, 0x00F9, 0x00F9, /* 0 */ + 0x001A, 0x0004, 0x0007, 0x0001, /* 4 */ + 0x0020, 0x0000, 0x0000, 0x0000, /* 8 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 12 */ +}; + +static const struct snd_kcontrol_new rda_codec_snd_controls[] = { + // volumes ctrls + SOC_SINGLE_EXT(MIXER_PLAYBACK_VOLUME, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_playback_volume), + SOC_SINGLE_EXT(MIXER_CAPTURE_VOLUME, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_capture_volume), + // devices ctrls + SOC_SINGLE_EXT(MIXER_ITF, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_itf), + SOC_SINGLE_EXT(MIXER_SPK_SEL, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_spksel), + SOC_SINGLE_EXT(MIXER_FORCE_MAINMIC, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_force_mainmic), + SOC_SINGLE_EXT(MIXER_CODEC_APP_MODE, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_codec_app_mode), + SOC_SINGLE_EXT(MIXER_START_PLAY, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_start_play), + SOC_SINGLE_EXT(MIXER_START_RECORD, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_start_record), + SOC_SINGLE_EXT(MIXER_STOP, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_stop), + SOC_SINGLE_EXT(MIXER_OUT_SAMPLE_RATE, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_out_sample_rate), + SOC_SINGLE_EXT(MIXER_OUT_CHANNEL_NB, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_out_channel_number), + SOC_SINGLE_EXT(MIXER_IN_SAMPLE_RATE, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_in_sample_rate), + SOC_SINGLE_EXT(MIXER_IN_CHANNEL_NB, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_in_channel_number), + SOC_SINGLE_EXT(MIXER_MUTE_MIC, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_mute_mic), + SOC_SINGLE_EXT(MIXER_COMMIT_SETUP, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_codec_set_commit_setup), + // status + SOC_SINGLE_EXT(MIXER_CODEC_OPEN_STATUS, 0, 0, 0xFFFF, 0, + rda_codec_get_open_status, ctrl_ext_set_reg), + // factory mode ctrls + SOC_SINGLE_EXT(MIXER_LOOP_MODE, 0, 0, 0xFFFF, 0, + ctrl_ext_get_reg, rda_audio_loop_mode), +}; + + +static int rda_codec_dai_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; + int ret = 0; + struct rda_codec_data *codec_data = snd_soc_codec_get_drvdata(codec); + + u32 sample_rate = params_rate(params); + + codec_data->stream.sampleRate = sample_rate; + + return ret; +} + +static int rda_codec_dai_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + // do this in user space + return 0; +} + +static int rda_codec_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + break; + case SNDRV_PCM_TRIGGER_RESUME: + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + + case SNDRV_PCM_TRIGGER_STOP: + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int rda_codec_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + + return 0; +} + +static void rda_codec_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ +} + +static int rda_codec_dai_dig_mute(struct snd_soc_dai *dai, int mute) +{ + return 0; +} + +static int rda_codec_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + return 0; +} + +static int rda_codec_dai_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int rda_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + break; + case SND_SOC_BIAS_OFF: + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define RDA_CODEC_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 rda_codec_dai_ops = { + .startup = rda_codec_dai_startup, + .hw_params = rda_codec_dai_hw_params, + .prepare = rda_codec_dai_pcm_prepare, + .trigger = rda_codec_dai_trigger, + .shutdown = rda_codec_dai_shutdown, + .digital_mute = rda_codec_dai_dig_mute, + .set_fmt = rda_codec_dai_set_fmt, + .set_sysclk = rda_codec_dai_set_sysclk, +}; + +static struct snd_soc_dai_driver rda_codec_dai_driver = { + .name = "rda-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = RDA_CODEC_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = RDA_CODEC_FORMATS, + }, + .ops = &rda_codec_dai_ops, +}; + +static int rda_codec_suspend(struct snd_soc_codec *codec) +{ + rda_codec_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int rda_codec_resume(struct snd_soc_codec *codec) +{ + snd_soc_cache_sync(codec); + rda_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +// just like hw_write in soc-io.c +static int rda_codec_hw_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + return 0; +} + +// just like hw_write in soc-io.c +static unsigned int rda_codec_hw_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + return 0; +} + +static int rda_modem_codec_notify(struct notifier_block *nb, unsigned long mesg, void *data) +{ + struct msys_device *pmsys_dev = container_of(nb, struct msys_device, notifier); + struct rda_codec_data *codec_data = (struct rda_codec_data *)pmsys_dev->private; + struct client_mesg *pmesg = (struct client_mesg *)data; + struct snd_soc_codec* codec = NULL; + + if(codec_data != NULL) + codec = codec_data->codec; + + if (pmesg->mod_id != SYS_GEN_MOD) { + return NOTIFY_DONE; + } + + if (mesg != SYS_GEN_MESG_RTC_TRIGGER) { + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static int rda_codec_probe(struct snd_soc_codec *codec) +{ + struct rda_codec_data *codec_data = NULL; + + codec_data = snd_soc_codec_get_drvdata(codec); + + if(codec_data == NULL) { + printk(KERN_INFO"NULL codec_data is when probe, error \n"); + return -1; + } + + codec_data->codec = codec; + + snd_soc_add_codec_controls(codec, rda_codec_snd_controls, + ARRAY_SIZE(rda_codec_snd_controls)); + + return 0; +} + +static int rda_codec_remove(struct snd_soc_codec *codec) +{ + struct rda_codec_data* codec_data = NULL; + + codec_data = snd_soc_codec_get_drvdata(codec); + + rda_codec_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_rda_codec_driver = { + .reg_cache_size = ARRAY_SIZE(rda_codec_reg), + .reg_word_size = sizeof(u16), + .reg_cache_default = rda_codec_reg, + .read = rda_codec_hw_read, + .write = rda_codec_hw_write, + .probe = rda_codec_probe, + .remove = rda_codec_remove, + .suspend = rda_codec_suspend, + .resume = rda_codec_resume, + .set_bias_level = rda_codec_set_bias_level, + .dapm_widgets = NULL, + .num_dapm_widgets = 0, + .dapm_routes = NULL, + .num_dapm_routes = 0, +}; + +static int rda_codec_platform_probe(struct platform_device *pdev) +{ + struct rda_codec_data *codec_data = NULL; + int ret = 0; + struct resource *extpa_res,*extpa1_res; + + codec_data = devm_kzalloc(&pdev->dev, sizeof(struct rda_codec_data), GFP_KERNEL); + + if (codec_data == NULL) { + return -ENOMEM; + } + + platform_set_drvdata(pdev, codec_data); + + // ap <---> modem codec + codec_data->codec_msys = rda_msys_alloc_device(); + if (!codec_data->codec_msys) { + ret = -ENOMEM; + } + + codec_data->codec_msys->module = SYS_AUDIO_MOD; + codec_data->codec_msys->name = "rda-codec"; + codec_data->codec_msys->notifier.notifier_call = rda_modem_codec_notify; + codec_data->codec_msys->private = (void *)codec_data; + + rda_msys_register_device(codec_data->codec_msys); + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_rda_codec_driver, + &rda_codec_dai_driver, 1); + extpa_res = platform_get_resource_byname(pdev,IORESOURCE_MEM,"audio-extpa"); + if (extpa_res->start > 0) { + gpio_audio_extpa = extpa_res->start; + printk(KERN_INFO"get extpa resource succeed %d\n",gpio_audio_extpa); + ret = gpio_request(gpio_audio_extpa,"audio-extpa"); + if (ret < 0) { + printk(KERN_ERR"rda codec : gpio_request fail. "); + goto err_request_gpio; + } + ret = gpio_direction_output(gpio_audio_extpa,1); + if (ret < 0) { + printk(KERN_ERR"rda codec : gpio_direction_output fail."); + goto err_request_gpio; + } + } else + printk(KERN_INFO"FAILED TO GET EXTPA RESOURCE !"); + + extpa1_res = platform_get_resource_byname(pdev,IORESOURCE_MEM,"audio-extpa-1"); + if (extpa1_res->start > 0) { + gpio_audio_extpa_1 = extpa1_res->start; + printk(KERN_INFO"get extpa_1 resource succeed %d\n",gpio_audio_extpa_1); + ret = gpio_request(gpio_audio_extpa_1,"audio-extpa-1"); + if (ret < 0) { + printk(KERN_ERR"rda codec : gpio_request 1 fail. "); + goto err_request_gpio1; + } + ret = gpio_direction_output(gpio_audio_extpa_1,1); + if (ret < 0) { + printk(KERN_ERR"rda codec : gpio_direction_output 1 fail."); + goto err_request_gpio1; + } + } else + printk(KERN_INFO"FAILED TO GET EXTPA_1 RESOURCE !"); + + return ret; +err_request_gpio: + gpio_free(gpio_audio_extpa); +err_request_gpio1: + gpio_free(gpio_audio_extpa); + gpio_free(gpio_audio_extpa_1); + + return ret; +} + +static int __exit rda_codec_platform_remove(struct platform_device *pdev) +{ + struct rda_codec_data *codec_data = platform_get_drvdata(pdev); + + snd_soc_unregister_codec(&pdev->dev); + + rda_msys_unregister_device(codec_data->codec_msys); + rda_msys_free_device(codec_data->codec_msys); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver rda_codec_driver = { + .driver = { + .name = "rda-codec", + .owner = THIS_MODULE, + }, + + .probe = rda_codec_platform_probe, + .remove = __exit_p(rda_codec_platform_remove), +}; + +static int __init rda_codec_modinit(void) +{ + return platform_driver_register(&rda_codec_driver); +} + +static void __exit rda_codec_modexit(void) +{ + platform_driver_unregister(&rda_codec_driver); +} + +static void __exit rdafpag_pcm_modexit(void) +{ + platform_driver_unregister(&rda_codec_driver); +} + +module_init(rda_codec_modinit); +module_exit(rda_codec_modexit); + +MODULE_DESCRIPTION("ASoC RDA codec driver"); +MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rda/rda_codec.h b/sound/soc/rda/rda_codec.h new file mode 100644 index 000000000000..f1288e44585d --- /dev/null +++ b/sound/soc/rda/rda_codec.h @@ -0,0 +1,138 @@ +/* + * ALSA SoC RDA 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 _RDA_CODEC_H +#define _RDA_CODEC_H + +///////////////////////// abb spi ///////////////////////// +/////////////////////////////////////////////////////////// + +// ============================================================================= +// BOOT_ISPI_DELAY_T +// ----------------------------------------------------------------------------- +/// Delays +/// Used to define the configuration delays +// ============================================================================= +typedef enum { + /// Delay of 0 half-period + BOOT_ISPI_HALF_CLK_PERIOD_0, + /// Delay of 1 half-period + BOOT_ISPI_HALF_CLK_PERIOD_1, + /// Delay of 2 half-period + BOOT_ISPI_HALF_CLK_PERIOD_2, + /// Delay of 3 half-period + BOOT_ISPI_HALF_CLK_PERIOD_3, + + BOOT_ISPI_HALF_CLK_PERIOD_QTY +} BOOT_ISPI_DELAY_T; + +// ============================================================================= +// BOOT_ISPI_CFG_T +// ----------------------------------------------------------------------------- +/// Structure for configuration. +/// A configuration structure allows to open or change the SPI with the desired +/// parameters. +// ============================================================================= +typedef struct { + /// Polarity of the FM CS. + u8 csFmActiveLow; + + /// Polarity of the ABB CS. + u8 csAbbActiveLow; + + /// Polarity of the PMU CS. + u8 csPmuActiveLow; + + /// If the first edge after the CS activation is a falling edge, set to + /// \c 1.\n Otherwise, set to \c 0. + u8 clkFallEdge; + + /// The delay between the CS activation and the first clock edge, + /// can be 0 to 2 half clocks. + BOOT_ISPI_DELAY_T clkDelay; + + /// The delay between the CS activation and the output of the data, + /// can be 0 to 2 half clocks. + BOOT_ISPI_DELAY_T doDelay; + + /// The delay between the CS activation and the sampling of the input data, + /// can be 0 to 3 half clocks. + BOOT_ISPI_DELAY_T diDelay; + + /// The delay between the end of transfer and the CS deactivation, can be + /// 0 to 3 half clocks. + BOOT_ISPI_DELAY_T csDelay; + + /// The time when the CS must remain deactivated before a new transfer, + /// can be 0 to 3 half clocks. + BOOT_ISPI_DELAY_T csPulse; + + /// Frame size in bits + u32 frameSize; + + /// OE ratio - Value from 0 to 31 is the number of data out to transfert + /// before the SPI_DO pin switches to input. + /// Not needed in the chip, but needed for the FPGA + u8 oeRatio; + + /// SPI maximum clock frequency: the SPI clock will be the highest + /// possible value inferior to this parameter. + u32 spiFreq; + +} BOOT_ISPI_CFG_T; + +// ============================================================================= +// BOOT_ISPI_CS_T +// ----------------------------------------------------------------------------- +/// Chip Select +/// Used to select a Chip Select +// ============================================================================= +typedef enum { + /// Chip Select for the PMU analog module. + BOOT_ISPI_CS_PMU = 0, + /// Chip Select for the ABB analog module. + BOOT_ISPI_CS_ABB, + /// Chip Select for the FM analog module. + BOOT_ISPI_CS_FM, + + BOOT_ISPI_CS_QTY +} BOOT_ISPI_CS_T; + +// ============================================================================== +// Codec registers +// ============================================================================== + +// reg 0x12d - s_cnt_constant +#define RDA_CODEC_REG_SAMPLE_RATE 0x12D +#define RDA_CODEC_REG_SAMPLE_RATE_SHIFT (6) +#define RDA_CODEC_REG_SAMPLE_RATE_MASK (0x1f<<(RDA_CODEC_REG_SAMPLE_RATE_SHIFT)) + +#define RDA_CODEC_REG_DIG_MUTE 0x12A +#define RDA_CODEC_REG_DIG_MUTE_SHIFT (7) +#define RDA_CODEC_REG_DIG_MUTE_MASK (0x1<<(RDA_CODEC_REG_DIG_EN_SHIFT)) + +#define RDA_CODEC_REG_DIG_EN 0x12A +#define RDA_CODEC_REG_DIG_EN_SHIFT (2) +#define RDA_CODEC_REG_DIG_EN_MASK (0x1<<(RDA_CODEC_REG_DIG_EN_SHIFT)) + +#define RDA_CODEC_REG_SELECT_SPK_PA 0x42 +#define RDA_CODEC_REG_SELECT_SPK_PA_SHIFT (2) +#define RDA_CODEC_REG_SELECT_SPK_PA_MASK (0x1<<(RDA_CODEC_REG_SELECT_SPK_PA_SHIFT)) + +#define RDA_CODEC_REG_SELECT_HEAD_PA 0x42 +#define RDA_CODEC_REG_SELECT_HEAD_PA_SHIFT (1) +#define RDA_CODEC_REG_SELECT_HEAD_PA_MASK (0x1<<(RDA_CODEC_REG_SELECT_HEAD_PA_SHIFT)) + +#define RDA_CODEC_REG_SELECT_RECV_PA 0x42 +#define RDA_CODEC_REG_SELECT_RECV_PA_SHIFT (1) +#define RDA_CODEC_REG_SELECT_RECV_PA_MASK (0x1<<(RDA_CODEC_REG_SELECT_RECV_PA_SHIFT)) + +#endif /* _RDA_CODEC_H */ diff --git a/sound/soc/rda/rda_codec_adp.h b/sound/soc/rda/rda_codec_adp.h new file mode 100644 index 000000000000..abec1fc3f7f6 --- /dev/null +++ b/sound/soc/rda/rda_codec_adp.h @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/* + * All these are same with modem definitions + * and, from definitions of headers in modem's source codes. + * +*/ + + +#ifndef RDA8810_MODEM_CODEC_ADP_H +#define RDA8810_MODEM_CODEC_ADP_H +// ============================================================================ +// SND_SPK_LEVEL_T +// ----------------------------------------------------------------------------- +/// This type describes the possible level value for the speaker volume. +// ============================================================================= +typedef enum +{ + SND_SPK_MUTE = 0x00000000, + SND_SPK_VOL_1 = 0x00000001, + SND_SPK_VOL_2 = 0x00000002, + SND_SPK_VOL_3 = 0x00000003, + SND_SPK_VOL_4 = 0x00000004, + SND_SPK_VOL_5 = 0x00000005, + SND_SPK_VOL_6 = 0x00000006, + SND_SPK_VOL_7 = 0x00000007, + SND_SPK_VOL_QTY = 0x00000008 +} SND_SPK_LEVEL_T; + + +// ============================================================================ +// SND_MIC_LEVEL_T +// ----------------------------------------------------------------------------- +/// This type describes the possible level value for the mic: mute or enabled. +// ============================================================================= +typedef enum +{ + SND_MIC_MUTE = 0x00000000, + SND_MIC_ENABLE = 0x00000001, + SND_MIC_VOL_QTY = 0x00000002 +} SND_MIC_LEVEL_T; + + +// ============================================================================ +// SND_SIDE_LEVEL_T +// ----------------------------------------------------------------------------- +/// This type describes the possible level value for the side tone volume. The value +/// SND_SIDE_VOL_TEST is used for mic to spk simple loop back test. +// ============================================================================= +typedef enum +{ + SND_SIDE_MUTE = 0x00000000, + SND_SIDE_VOL_1 = 0x00000001, + SND_SIDE_VOL_2 = 0x00000002, + SND_SIDE_VOL_3 = 0x00000003, + SND_SIDE_VOL_4 = 0x00000004, + SND_SIDE_VOL_5 = 0x00000005, + SND_SIDE_VOL_6 = 0x00000006, + SND_SIDE_VOL_7 = 0x00000007, + SND_SIDE_VOL_TEST = 0x00000008, + SND_SIDE_VOL_QTY = 0x00000009 +} SND_SIDE_LEVEL_T; + + +// ============================================================================ +// SND_TONE_ATTENUATION_T +// ----------------------------------------------------------------------------- +/// Attenuation of the tone. The attenuation can be set to 0 dB, -3 dB, -9 dB and +/// -15dB. +// ============================================================================= +typedef enum +{ +/// No attenuation + SND_TONE_0DB = 0x00000000, +/// -3dB + SND_TONE_M3DB = 0x00000001, +/// -9db + SND_TONE_M9DB = 0x00000002, +/// -15dB + SND_TONE_M15DB = 0x00000003, + SND_TONE_VOL_QTY = 0x00000004 +} SND_TONE_ATTENUATION_T; + + +// ============================================================================ +// SND_TONE_TYPE_T +// ----------------------------------------------------------------------------- +/// Tone types. The DTMF Tones are used to inform the user that the number is being +/// composed. All the standard DTMF are available: 0 to 9, A to D, pound and star. +/// \n The Comfort Tones are used to inform the user on the current state of the +/// call: Ringing, Busy, Unavailable... All frequencies needed to do the standard +/// Comfort Tones are available: 425 Hz, 950 Hz, 1400 Hz and 1800 Hz. \n +// ============================================================================= +typedef enum +{ +/// Tone when the '0' key + SND_DTMF_0 = 0x00000000, +/// Tone when the '1' key + SND_DTMF_1 = 0x00000001, +/// Tone when the '2' key + SND_DTMF_2 = 0x00000002, +/// Tone when the '3' key + SND_DTMF_3 = 0x00000003, +/// Tone when the '4' key + SND_DTMF_4 = 0x00000004, +/// Tone when the '5' key + SND_DTMF_5 = 0x00000005, +/// Tone when the '6' key + SND_DTMF_6 = 0x00000006, +/// Tone when the '7' key + SND_DTMF_7 = 0x00000007, +/// Tone when the '8' key + SND_DTMF_8 = 0x00000008, +/// Tone when the '9' key + SND_DTMF_9 = 0x00000009, + SND_DTMF_A = 0x0000000A, + SND_DTMF_B = 0x0000000B, + SND_DTMF_C = 0x0000000C, + SND_DTMF_D = 0x0000000D, +/// Tone when the * key + SND_DTMF_S = 0x0000000E, +/// Tone when the # key + SND_DTMF_P = 0x0000000F, +/// Comfort tone at 425 Hz + SND_COMFORT_425 = 0x00000010, +/// Comfort tone at 950 Hz + SND_COMFORT_950 = 0x00000011, +/// Comfort tone at 1400 Hz + SND_COMFORT_1400 = 0x00000012, +/// Confort tone at 1800 Hz + SND_COMFORT_1800 = 0x00000013, +/// No tone is emitted + SND_NO_TONE = 0x00000014 +} SND_TONE_TYPE_T; + + +// ============================================================================ +// SND_ITF_T +// ----------------------------------------------------------------------------- +/// That type provide a way to identify the different audio interface. +// ============================================================================= +typedef enum +{ + SND_ITF_RECEIVER = 0x00000000, + SND_ITF_EAR_PIECE = 0x00000001, + SND_ITF_LOUD_SPEAKER = 0x00000002, + SND_ITF_BLUETOOTH = 0x00000003, + SND_ITF_FM = 0x00000004, + SND_ITF_TV = 0x00000005, +/// Number (max) of available interface to the SND driver + SND_ITF_QTY = 0x00000006, + SND_ITF_NONE = 0x000000FF +} SND_ITF_T; + + +// ============================================================================ +// SND_EQUALIZER_MODE_T +// ----------------------------------------------------------------------------- +/// SND equalizer modes enumerator +// ============================================================================= +typedef enum +{ + SND_EQUALIZER_OFF = 0xFFFFFFFF, + SND_EQUALIZER_NORMAL = 0x00000000, + SND_EQUALIZER_BASS = 0x00000001, + SND_EQUALIZER_DANCE = 0x00000002, + SND_EQUALIZER_CLASSICAL = 0x00000003, + SND_EQUALIZER_TREBLE = 0x00000004, + SND_EQUALIZER_PARTY = 0x00000005, + SND_EQUALIZER_POP = 0x00000006, + SND_EQUALIZER_ROCK = 0x00000007, + SND_EQUALIZER_CUSTOM = 0x00000008, + SND_EQUALIZER_QTY = 0x00000009 +} SND_EQUALIZER_MODE_T; + +////////////////////////////////////////////////////////////////////////////////// +// ============================================================================= +// HAL_AIF_HANDLER_T +// ----------------------------------------------------------------------------- +/// Type use to define the user handling function called when an interrupt +/// related to the IFC occurs. Interrupt can be generated when playing or +/// recording a buffer, when we reach the middle or the end of the buffer. +// ============================================================================= +// typedef VOID (*HAL_AIF_HANDLER_T)(VOID); +typedef void (*HAL_AIF_HANDLER_T)(void); + +// ============================================================================= +// HAL_AIF_SR_T +// ----------------------------------------------------------------------------- +/// Audio stream sample rate. +// ============================================================================= +typedef enum +{ + HAL_AIF_FREQ_8000HZ = 8000, + HAL_AIF_FREQ_11025HZ = 11025, + HAL_AIF_FREQ_12000HZ = 12000, + HAL_AIF_FREQ_16000HZ = 16000, + HAL_AIF_FREQ_22050HZ = 22050, + HAL_AIF_FREQ_24000HZ = 24000, + HAL_AIF_FREQ_32000HZ = 32000, + HAL_AIF_FREQ_44100HZ = 44100, + HAL_AIF_FREQ_48000HZ = 48000 +} HAL_AIF_FREQ_T; + + + +// ============================================================================= +// HAL_AIF_CH_NB_T +// ----------------------------------------------------------------------------- +/// This type defines the number of channels used by a stream +// ============================================================================ +typedef enum +{ + HAL_AIF_MONO = 1, + HAL_AIF_STEREO = 2, +} HAL_AIF_CH_NB_T; + +// ============================================================================= +// HAL_AIF_STREAM_T +// ----------------------------------------------------------------------------- +/// This type describes a stream, played or record. +// ============================================================================= +typedef struct +{ + // UINT32* startAddress; + unsigned int* startAddress; + /// Stream length in bytes. + /// The length of a stream must be a multiple of 16 bytes. + /// The maximum size is 32 KB. + // UINT16 length; + unsigned short length; + HAL_AIF_FREQ_T sampleRate; + HAL_AIF_CH_NB_T channelNb; + /// True if this is a voice stream, coded on 13 bits, mono, at 8kHz. + /// Voice quality streams are the only that can be output through + /// the receiver interface. + // BOOL voiceQuality; + unsigned char voiceQuality; + /// True if the play stream is started along with the record stream. + /// In this case, the play stream will be configured at first, + /// but it is not started until the record stream is ready to start. + // BOOL playSyncWithRecord; + unsigned char playSyncWithRecord; + /// Handler called when the middle of the buffer is reached. + /// If this field is left blank (NULL), no interrupt will be + /// generated. + HAL_AIF_HANDLER_T halfHandler; + /// Handler called when the end of the buffer is reached. + /// If this field is left blank (NULL), no interrupt will be + /// generated. + HAL_AIF_HANDLER_T endHandler; +} HAL_AIF_STREAM_T; + +/////////////////////////////////////////////////////////////////////////////////////// + +// ============================================================================= +// AUD_SPK_T +// ----------------------------------------------------------------------------- +/// Speaker output selection. +// ============================================================================= +typedef enum +{ + /// Output on receiver + /// This output can only use voice quality streams (Mono, 8kHz, + /// voiceQuality field set to TRUE). + AUD_SPK_RECEIVER = 0, + /// Output on ear-piece + AUD_SPK_EAR_PIECE, + /// Output on hand-free loud speaker + AUD_SPK_LOUD_SPEAKER, + /// Output on both hand-free loud speaker and ear-piece + AUD_SPK_LOUD_SPEAKER_EAR_PIECE, + + AUD_SPK_QTY, + + AUD_SPK_DISABLE=255 +} AUD_SPK_T; + + +// ============================================================================= +// AUD_MIC_T +// ----------------------------------------------------------------------------- +/// Microphone input selection. +// ============================================================================= +typedef enum +{ + /// Input from the regular microphone port + AUD_MIC_RECEIVER = 0, + /// Input from the ear-piece port, + AUD_MIC_EAR_PIECE, + /// Input from regular microphone, but for loudspeaker mode. + AUD_MIC_LOUD_SPEAKER, + + AUD_MIC_QTY, + + AUD_MIC_DISABLE = 255 +} AUD_MIC_T; + + +// ============================================================================= +// AUD_SPEAKER_TYPE_T +// ----------------------------------------------------------------------------- +/// Describes how the speaker is plugged on the stereo output: +/// - is it a stereo speaker ? (speakers ?) +/// - is it a mono speaker on the left channel ? +/// - is it a mono speaker on the right channel ? +/// - is this a mono output ? +// ============================================================================= +typedef enum +{ + AUD_SPEAKER_STEREO, + AUD_SPEAKER_MONO_RIGHT, + AUD_SPEAKER_MONO_LEFT, + /// The output is mono only. + AUD_SPEAKER_STEREO_NA, + + AUD_SPEAKER_QTY +} AUD_SPEAKER_TYPE_T; + + +// ============================================================================= +// AUD_LEVEL_T +// ----------------------------------------------------------------------------- +/// Level configuration structure. +/// +/// A level configuration structure allows to start an AUD operation (start +/// stream, start record, or start tone) with the desired gains on an interface. +// ============================================================================= +typedef struct +{ + /// Speaker level, + SND_SPK_LEVEL_T spkLevel; + + /// Microphone level: muted or enabled + SND_MIC_LEVEL_T micLevel; + + /// Sidetone + SND_SIDE_LEVEL_T sideLevel; + + SND_TONE_ATTENUATION_T toneLevel; + +} AUD_LEVEL_T; + +// ============================================================================= +// AUD_TEST_T +// ----------------------------------------------------------------------------- +/// Used to choose the audio mode: normal, test, dai's ... +// ============================================================================= +typedef enum +{ + /// No test mode. + AUD_TEST_NO, + + /// For audio test loop; analog to DAI loop + DAI to analog loop. + AUD_TEST_LOOP_ACOUSTIC, + + /// For audio test loop; radio loop on DAI interface + AUD_TEST_LOOP_RF_DAI, + + /// For audio test loop; radio loop on analog interface + AUD_TEST_LOOP_RF_ANALOG, + + /// For board check: mic input is fedback to the speaker output. + AUD_TEST_SIDE_TEST, + + /// For board check; audio loop in VOC + AUD_TEST_LOOP_VOC, + + /// For board check; regular mic input and earpiece output + AUD_TEST_RECVMIC_IN_EARPIECE_OUT, + + /// headset mic in & headset out + AUD_TEST_HEADSETMIC_IN_HEADSET_OUT, + + /// main mic in & receiver out + AUD_TEST_MAINMIC_IN_RECEIVER_OUT, + + AUD_TEST_MODE_QTY +} AUD_TEST_T; + +typedef enum +{ + AUD_APP_CODEC = 0, + AUD_APP_FM, + AUD_APP_ATV, + + AUD_APP_QTY, +} AUD_APP_MODE_T; + +/* Mixer control names */ +#define MIXER_PLAYBACK_VOLUME "Playback Volume" +#define MIXER_CAPTURE_VOLUME "Capture Volume" +#define MIXER_ITF "ITF" +#define MIXER_SPK_SEL "SpkSel" +#define MIXER_FORCE_MAINMIC "ForceMainMic" +#define MIXER_CODEC_APP_MODE "CodecAppMode" +#define MIXER_START_PLAY "StartPlay" +#define MIXER_START_RECORD "StartRecord" +#define MIXER_STOP "Stop" +#define MIXER_OUT_SAMPLE_RATE "OutSampleRate" +#define MIXER_IN_SAMPLE_RATE "InSampleRate" +#define MIXER_OUT_CHANNEL_NB "OutChannelNumber" +#define MIXER_IN_CHANNEL_NB "InChannelNumber" +#define MIXER_COMMIT_SETUP "Commit Setup" +#define MIXER_CODEC_OPEN_STATUS "CodecOpenStatus" +#define MIXER_MUTE_MIC "MuteMic" +#define MIXER_LOOP_MODE "Loop Mode" + +#endif /* RDA8810_MODEM_CODEC_ADP_H */ diff --git a/sound/soc/rda/rda_dai.c b/sound/soc/rda/rda_dai.c new file mode 100644 index 000000000000..e98f5d9aac36 --- /dev/null +++ b/sound/soc/rda/rda_dai.c @@ -0,0 +1,471 @@ +/* + * 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_aif.h" + +//#define DEBUG 1 + +#define AIF_FIX_SAMPLERATE_WHEN_CAPTURE 44100 + +#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; + +static int rda_cpu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rda_dai *aif_cfg = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + mutex_lock(&aif_cfg->mutex); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (!aif_cfg->PlayActive) { + aif_cfg->PlayActive = true; + } else { + ret = -EBUSY; + } + } else { + if (!aif_cfg->RecordActive) { + aif_cfg->RecordActive = true; + } else { + ret = -EBUSY; + } + } + + mutex_unlock(&aif_cfg->mutex); + + return ret; +} + +static void rda_cpu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rda_dai *aif_cfg = snd_soc_dai_get_drvdata(dai); + + + mutex_lock(&aif_cfg->mutex); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + aif_cfg->PlayActive = false; + } else { + aif_cfg->RecordActive = false; + } + + if (!aif_cfg->PlayActive && !aif_cfg->RecordActive) { + hwp_apAif->ctrl = 0; + hwp_apAif->serial_ctrl = AIF_MASTER_MODE_MASTER; + hwp_apAif->side_tone = 0; + hwp_apAif->Cfg_Aif_Tx_Stb = 0; + aif_cfg->OpenStatus = false; + } + mutex_unlock(&aif_cfg->mutex); +} + +static int rda_cpu_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rda_dai *aif_cfg = snd_soc_dai_get_drvdata(dai); + + u32 channel_num = 0; + u32 lrck = 0; + u32 bck = 0; + u32 bck_lrck_ratio = 0; + u32 sample_rate = 0; + u32 aif_source_clock = 0; + +#ifdef DEBUG + printk(KERN_INFO"[%s] \n", __func__); +#endif + + if (!aif_cfg->OpenStatus) { + + sample_rate = params_rate(params); + +#ifdef DEBUG + printk(KERN_INFO"sample_rate : [%d] \n", sample_rate); +#endif + // on rda : aif just config the output params + // when input sample_rate is 8000, we should still config aif to playback sample_rate + // like android-fixed 44100 +#ifdef AIF_FIX_SAMPLERATE_WHEN_CAPTURE + if(substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + printk(KERN_INFO"fix capture sample rate : [%d] \n", AIF_FIX_SAMPLERATE_WHEN_CAPTURE); + sample_rate = AIF_FIX_SAMPLERATE_WHEN_CAPTURE; + } +#endif + + lrck = sample_rate; + + switch (sample_rate) { + case 8000: + bck_lrck_ratio = 50; + break; + + case 11025: + bck_lrck_ratio = 36; + break; + + case 12000: + bck_lrck_ratio = 38; + break; + + case 16000: + bck_lrck_ratio = 50; + break; + + case 22050: + bck_lrck_ratio = 40; + break; + + case 24000: + bck_lrck_ratio = 38; + break; + + case 32000: + bck_lrck_ratio = 56; + break; + + case 44100: + bck_lrck_ratio = 62; + break; + + case 48000: + bck_lrck_ratio = 36; + break; + + default: + mutex_unlock(&aif_cfg->mutex); + return -EINVAL; + break; + } + + bck = lrck * bck_lrck_ratio; + aif_cfg->AudioBck_div = FAST_CLOCK / bck - 2; + aif_cfg->bcklrck_div = bck_lrck_ratio / 2 - 16; + + channel_num = params_channels(params); + + switch (channel_num) { + case 1: + case 2: + aif_cfg->ChannelNb = channel_num; + break; + default: + mutex_unlock(&aif_cfg->mutex); + return -EINVAL; + } + + if (sample_rate == 8000 || sample_rate == 12000 + || sample_rate == 16000 || sample_rate == 24000 + || sample_rate == 32000 || sample_rate == 48000) { + aif_source_clock = 24576000; + } else if (sample_rate == 11025 || sample_rate == 22050 + || sample_rate == 44100) { + aif_source_clock = 22579200; + } else { + aif_source_clock = 24576000; + } + + aif_cfg->TxStb_div = aif_source_clock / sample_rate - 2; + aif_cfg->MasterFlag = true; + } + + return 0; +} + +static int rda_cpu_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rda_dai *aif_cfg = snd_soc_dai_get_drvdata(dai); + + u32 serialCfgReg = 0; + + if (!aif_cfg->OpenStatus) { + + hwp_apAif->Cfg_Aif_Tx_Stb = + AIF_AIF_TX_STB_EN | AIF_AIF_TX_STB_DIV(aif_cfg->TxStb_div); + + // config - communicate with codec NOT I2S + aif_cfg->ControlReg |= + AIF_PARALLEL_OUT_SET_PARA | AIF_PARALLEL_IN_SET_PARA | + AIF_LOOP_BACK_NORMAL | AIF_TX_STB_MODE; + + // FIXME + // we assume : + // 1. playback always use channel number 2 + // 2. this config of aif (not use i2s) just affect playback not capture + // when this is called because record, we should know this configure will just used by playback + // when capture config channel number 1, we should also configure this to channel number 2, + // because playback will happen when capture + + // BUG : playback and capture interface maybe called 1 2 11 2 2 11025 + // so, aif_cfg->ChannelNb (capture set) maybe used by playback and STREO_DUPLT will be set and sound will be slower. + // TODO : different ops when different stream, now we just set all to stereo cause android use 44100 & 2 channels + // if (aif_cfg->ChannelNb == 1 && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + // serialCfgReg |= + // AIF_MASTER_MODE_MASTER | + // AIF_TX_MODE_MONO_STEREO_DUPLI; + // } else { + serialCfgReg |= + AIF_MASTER_MODE_MASTER | AIF_TX_MODE_STEREO_STEREO; + //} + + hwp_apAif->serial_ctrl = serialCfgReg; + + aif_cfg->OpenStatus = true; + } + + return 0; +} + +static int rda_cpu_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct rda_dai *aif_cfg = snd_soc_dai_get_drvdata(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + hwp_apAif->ctrl = + (aif_cfg->ControlReg | AIF_ENABLE_H_ENABLE) & + ~AIF_TX_OFF; + aif_cfg->PlayStatus = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + if (!aif_cfg->RecordStatus) { + hwp_apAif->ctrl = 0; + } + aif_cfg->PlayStatus = false; + break; + default: + break; + } + + } else { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!aif_cfg->PlayStatus) { + hwp_apAif->ctrl = + (aif_cfg->ControlReg | AIF_ENABLE_H_ENABLE) + | AIF_TX_OFF_TX_OFF; + } + aif_cfg->RecordStatus = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + if (!aif_cfg->PlayStatus) { + hwp_apAif->ctrl = 0; + } + aif_cfg->RecordStatus = false; + break; + default: + break; + } + } + + return 0; +} + +static const struct snd_soc_dai_ops rda_cpu_dai_driver_ops = { + .startup = rda_cpu_dai_startup, + .shutdown = rda_cpu_dai_shutdown, + .hw_params = rda_cpu_dai_hw_params, + .prepare = rda_cpu_dai_prepare, + .trigger = rda_cpu_dai_trigger, +}; + +static int rda_cpu_dai_driver_probe(struct snd_soc_dai *dai) +{ + struct rda_dai *dmic = snd_soc_dai_get_drvdata(dai); + + 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); + + return 0; +} + +static int rda_cpu_dai_driver_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_cpu_dai_driver = { + .name = "rda-cpu-dai-driver", + .probe = rda_cpu_dai_driver_probe, + .remove = rda_cpu_dai_driver_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_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .sig_bits = 16, + }, + .ops = &rda_cpu_dai_driver_ops, +}; + +static struct snd_soc_component_driver rda_component ={ + .name = "rda-soundcard" +}; +static int rda_cpu_dai_platform_driver_probe(struct + platform_device + *pdev) +{ + struct rda_dai *AifCfg; + struct resource *res; + int ret; + + AifCfg = + devm_kzalloc(&pdev->dev, sizeof(struct rda_dai), GFP_KERNEL); + if (!AifCfg) + return -ENOMEM; + + platform_set_drvdata(pdev, AifCfg); + AifCfg->dev = &pdev->dev; + + mutex_init(&AifCfg->mutex); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(AifCfg->dev, "invalid dma 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_component(&pdev->dev,&rda_component, &rda_cpu_dai_driver, 1); + if (ret) + goto err_put_clk; + + return 0; + +err_put_clk: + return ret; +} + +static int __exit rda_cpu_dai_platform_driver_remove(struct + platform_device + *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static struct platform_driver rda_cpu_dai_platform_driver = { + .driver = { + .name = "rda-aif", + .owner = THIS_MODULE, + }, + .probe = rda_cpu_dai_platform_driver_probe, + .remove = __exit_p(rda_cpu_dai_platform_driver_remove), +}; + +static int __init rda_cpu_dai_modinit(void) +{ + return platform_driver_register(&rda_cpu_dai_platform_driver); +} + +static void __exit rda_cpu_dai_modexit(void) +{ + platform_driver_unregister(&rda_cpu_dai_platform_driver); +} + +module_init(rda_cpu_dai_modinit); +module_exit(rda_cpu_dai_modexit); + +MODULE_DESCRIPTION("ALSA SoC for RDA CPU DAI"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rda/rda_dsp_aud.c b/sound/soc/rda/rda_dsp_aud.c new file mode 100644 index 000000000000..e3fcf3b602cd --- /dev/null +++ b/sound/soc/rda/rda_dsp_aud.c @@ -0,0 +1,334 @@ +/* + * ===================================================================================== + * + * Filename: rda_dsp_aud.c + * + * Description: RDA DSP_AUD Receiver driver for linux. + * + * Version: 1.0 + * Created: 06/12/2013 04:19:05 PM + * Revision: none + * Compiler: gcc + * + * Author: Naiquan Hu, + * Organization: RDA Microelectronics Inc. + * + * Copyright (C) 2013 RDA Microelectronics Inc. + * ===================================================================================== + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> // udelay() +#include <linux/device.h> // device_create() +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/cdev.h> +#include <linux/fs.h> +//#include <linux/version.h> /* constant of kernel version */ +#include <asm/uaccess.h> // get_user() +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/audiocontrol.h> +#include <linux/gpio.h> +#include <plat/md_sys.h> +#include <linux/leds.h> + +#include "rda_dsp_aud.h" +#include <rda/tgt_ap_board_config.h> + +struct msys_device *bp_gpioc_msys = NULL; + +/* rda_gpioc_data driver data */ +struct rda_gpioc_data { + struct msys_device *gpioc_msys; +}; + +typedef struct +{ + u8 id; + u8 value; + u8 default_value1; + u8 default_value2; +} rda_gpioc_op; + +#ifdef _TGT_AP_LED_RED_FLASH +#define LED_CAM_FLASH "red-flash" +#elif defined(_TGT_AP_LED_GREEN_FLASH) +#define LED_CAM_FLASH "green-flash" +#elif defined(_TGT_AP_LED_BLUE_FLASH) +#define LED_CAM_FLASH "blue-flash" +#endif + +#ifdef LED_CAM_FLASH +DEFINE_LED_TRIGGER(rda_sensor_led); +#endif + +static int rda_modem_gpioc_notify(struct notifier_block *nb, unsigned long mesg, void *data) +{ + struct msys_device *pmsys_dev = container_of(nb, struct msys_device, notifier); + struct client_mesg *pmesg = (struct client_mesg *)data; + + + if (pmesg->mod_id != SYS_GEN_MOD) { + return NOTIFY_DONE; + } + + if (mesg != SYS_GEN_MESG_RTC_TRIGGER) { + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +int rda_gpioc_operation(rda_gpioc_op *gpioc_op) +{ + int enable; + int ret, value; + u8 data[sizeof(rda_gpioc_op)] = { 0 }; + struct client_cmd gpioc_cmd; + + value = sizeof(rda_gpioc_op); + + memcpy(data, gpioc_op, sizeof(rda_gpioc_op)); + memset(&gpioc_cmd, 0, sizeof(gpioc_cmd)); + gpioc_cmd.pmsys_dev = bp_gpioc_msys; + gpioc_cmd.mod_id = SYS_GPIO_MOD; + gpioc_cmd.mesg_id = SYS_GPIO_CMD_OPERATION; + gpioc_cmd.pdata = (void *)&data; + gpioc_cmd.data_size = sizeof(data); + ret = rda_msys_send_cmd(&gpioc_cmd); + + printk( ">>>> [%s], ret [%d] \n", __func__, ret); + + return ret; +} + +static ssize_t rdabp_gpio_open_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int value; + + printk(KERN_DEBUG "%s, buf: %s\n", __func__, buf); + + if (sscanf(buf, "%u", &value) != 1) + return -EINVAL; + + printk("The SYSTEM value %d\n", value); + + return count; +} + +/* + * GPO set. + */ +static ssize_t OrangePi_2G_IOT_gpio_set_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int gpionum; + rda_gpioc_op gpioc_op; + + if (sscanf(buf, "%u", &gpionum) != 1) + return -EINVAL; + + gpioc_op.id = gpionum; + gpioc_op.value = 1; + gpioc_op.default_value1 = 0; + gpioc_op.default_value2 = 0; + rda_gpioc_operation(&gpioc_op); + + return 1; +} + +/* + * GPO clear. + */ +static ssize_t OrangePi_2G_IOT_gpio_clear_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int gpionum; + rda_gpioc_op gpioc_op; + + if (sscanf(buf, "%u", &gpionum) != 1) + return -EINVAL; + + gpioc_op.id = gpionum; + gpioc_op.value = 0; + gpioc_op.default_value1 = 0; + gpioc_op.default_value2 = 0; + rda_gpioc_operation(&gpioc_op); + + return 1; +} +static ssize_t rdabp_gpio_close_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + printk(KERN_DEBUG "%s, buf: %s\n", __func__, buf); + + if (sscanf(buf, "%u", &value) != 1) + return -EINVAL; + + return count; +} + + +static ssize_t rdabp_gpio_set_io_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + rda_gpioc_op gpioc_op; + + if (sscanf(buf, "%u", &value) != 1) + return -EINVAL; + gpioc_op.id = 8; + gpioc_op.value = 1; + gpioc_op.default_value1 = 0; + gpioc_op.default_value2 = 0; + rda_gpioc_operation(&gpioc_op); + + return 1; +} + + +static ssize_t rdabp_gpio_get_value_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + if (sscanf(buf, "%u", &value) != 1) + return -EINVAL; + + return count; +} + + +static ssize_t rdabp_gpio_set_value_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + if (sscanf(buf, "%u", &value) != 1) + return -EINVAL; + + return count; +} + +static ssize_t rdabp_gpio_enable_irq_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + printk(KERN_DEBUG "%s, buf: %s\n", __func__, buf); + + if (sscanf(buf, "%u", &value) != 1) + return -EINVAL; + + + return count; +} + +static DEVICE_ATTR(gpio_open, 0777, + NULL, rdabp_gpio_open_store); +static DEVICE_ATTR(gpio_close,0777, + NULL, rdabp_gpio_close_store); +static DEVICE_ATTR(gpio_set_io, 0777, + NULL,rdabp_gpio_set_io_store); +static DEVICE_ATTR(gpio_get_value, 0777, + NULL,rdabp_gpio_get_value_store); +static DEVICE_ATTR(gpio_set_value, 0777, + NULL,rdabp_gpio_set_value_store); +static DEVICE_ATTR(gpio_enable_irq, 0777, + NULL,rdabp_gpio_enable_irq_store); +static DEVICE_ATTR(gpo_set, 0777, + NULL, OrangePi_2G_IOT_gpio_set_store); +static DEVICE_ATTR(gpo_clear, 0777, + NULL, OrangePi_2G_IOT_gpio_clear_store); + + +static int rda_gpioc_platform_probe(struct platform_device *pdev) +{ + struct rda_gpioc_data *gpioc_data = NULL; + int ret = 0; + + gpioc_data = devm_kzalloc(&pdev->dev, sizeof(struct rda_gpioc_data), GFP_KERNEL); + + if (gpioc_data == NULL) { + return -ENOMEM; + } + + platform_set_drvdata(pdev, gpioc_data); + + // ap <---> modem gpioc + gpioc_data->gpioc_msys = rda_msys_alloc_device(); + if (!gpioc_data->gpioc_msys) { + ret = -ENOMEM; + } + + gpioc_data->gpioc_msys->module = SYS_GPIO_MOD; + gpioc_data->gpioc_msys->name = "rda-gpioc"; + gpioc_data->gpioc_msys->notifier.notifier_call = rda_modem_gpioc_notify; + + rda_msys_register_device(gpioc_data->gpioc_msys); + bp_gpioc_msys = gpioc_data->gpioc_msys; + + device_create_file(&pdev->dev, &dev_attr_gpio_open); + device_create_file(&pdev->dev, &dev_attr_gpio_close); + device_create_file(&pdev->dev, &dev_attr_gpio_set_io); + device_create_file(&pdev->dev, &dev_attr_gpio_get_value); + device_create_file(&pdev->dev, &dev_attr_gpio_set_value); + device_create_file(&pdev->dev, &dev_attr_gpio_enable_irq); + device_create_file(&pdev->dev, &dev_attr_gpo_set); + device_create_file(&pdev->dev, &dev_attr_gpo_clear); + + #ifdef LED_CAM_FLASH + led_trigger_register_simple(LED_CAM_FLASH, &rda_sensor_led); + mdelay(5); + led_trigger_event(rda_sensor_led, LED_HALF); + printk(" rda_gpioc_platform_probe22222222222222 \r\n "); + #endif + + return ret; +} + +static int __exit rda_gpioc_platform_remove(struct platform_device *pdev) +{ + struct rda_gpioc_data *gpioc_data = platform_get_drvdata(pdev); + + rda_msys_unregister_device(gpioc_data->gpioc_msys); + rda_msys_free_device(gpioc_data->gpioc_msys); + + platform_set_drvdata(pdev, NULL); + + #ifdef LED_CAM_FLASH + led_trigger_unregister_simple(rda_sensor_led); + #endif + + return 0; +} + +static struct platform_driver rda_gpioc_driver = { + .driver = { + .name = "rda-gpioc", + .owner = THIS_MODULE, + }, + + .probe = rda_gpioc_platform_probe, + .remove = __exit_p(rda_gpioc_platform_remove), +}; + +static int __init rda_gpioc_modinit(void) +{ + return platform_driver_register(&rda_gpioc_driver); +} + +static void __exit rda_gpioc_modexit(void) +{ + platform_driver_unregister(&rda_gpioc_driver); +} + +module_init(rda_gpioc_modinit); +module_exit(rda_gpioc_modexit); + + diff --git a/sound/soc/rda/rda_dsp_aud.h b/sound/soc/rda/rda_dsp_aud.h new file mode 100644 index 000000000000..2054cfe31039 --- /dev/null +++ b/sound/soc/rda/rda_dsp_aud.h @@ -0,0 +1,44 @@ + +#ifndef __RDA_DSP_AUD_RADIO_H__ +#define __RDA_DSP_AUD_RADIO_H__ + + +#include <linux/ioctl.h> +#include <linux/time.h> + +//scan sort algorithm +enum{ + DSP_AUD_SCAN_SORT_NON = 0, + DSP_AUD_SCAN_SORT_UP, + DSP_AUD_SCAN_SORT_DOWN, + DSP_AUD_SCAN_SORT_MAX +}; + +//scan methods +enum{ + DSP_AUD_SCAN_SEL_HW = 0, //select hardware scan, advantage: fast + DSP_AUD_SCAN_SEL_SW, //select software scan, advantage: more accurate + DSP_AUD_SCAN_SEL_MAX +}; + + + +#define DSP_AUD_NAME "rdadspAud" +#define DSP_AUD_DEVICE_NAME "/dev/rdadspAud" + +// ********** ***********DSP_AUD IOCTL define start ******************************* + +#define DSP_AUD_IOC_MAGIC 0xf9 // FIXME: any conflict? + + +//IOCTL and struct for test +#define GP0_1_LEVEL _IOWR(DSP_AUD_IOC_MAGIC, 11, uint32_t*) + +struct dsp_aud_em_parm { + uint16_t group_idx; + uint16_t item_idx; + uint32_t item_value; +}; + + +#endif // __RDA_DSP_AUD_RADIO_H__ diff --git a/sound/soc/rda/rda_pcm.c b/sound/soc/rda/rda_pcm.c new file mode 100644 index 000000000000..314d00132658 --- /dev/null +++ b/sound/soc/rda/rda_pcm.c @@ -0,0 +1,378 @@ +/* + * rda_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 <plat/pm_ddr.h> +#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 <plat/rda_debug.h> +#include "rda_audifc.h" +#include "rda_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_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME , + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = 32, + .period_bytes_max = 32 * 1024,//64 * 1024, + .periods_min = 4, + .periods_max = 4, + .buffer_bytes_max = 128 * 1024,//256 * 1024, +}; + +struct rda_runtime_data { + spinlock_t lock; + struct rda_pcm_dma_data *dma_data; + int dma_ch; +}; + +static void rda_pcm_dma_irq(int ch, int stat, void *data) +{ + struct snd_pcm_substream *substream = data; + + 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 rda_runtime_data *prtd = runtime->private_data; + int err = 0; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + 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; + + 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_audifc_chan_params 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 + */ + + 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; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (dma_data->set_threshold) + dma_data->set_threshold(substream); + + rda_dbg_audio("[start aud ifc] : substream->stream is [%d], %s, cmd is [%d] \n", + substream->stream, substream->stream == SNDRV_PCM_STREAM_PLAYBACK?"PLAYBACK":"CAPTURE", cmd); + if(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pm_ddr_get(PM_DDR_AUDIO_IFC_PLAYBACK); + else + pm_ddr_get(PM_DDR_AUDIO_IFC_CAPTURE); + rda_start_audifc(prtd->dma_ch); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + rda_dbg_audio("[stop aud ifc] : substream->stream is [%d], %s, cmd is [%d] \n", + substream->stream, substream->stream == SNDRV_PCM_STREAM_PLAYBACK?"PLAYBACK":"CAPTURE", cmd); + rda_stop_audifc(prtd->dma_ch); + if(substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pm_ddr_put(PM_DDR_AUDIO_IFC_PLAYBACK); + else + pm_ddr_put(PM_DDR_AUDIO_IFC_CAPTURE); + break; + default: + ret = -EINVAL; + } + + 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; + + 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; + 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) { + printk(KERN_INFO "[%s], snd_pcm_hw_constraint_integer < 0\n", __func__); + goto out; + } + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) { + printk(KERN_INFO "[%s], kzalloc == NULL\n", __func__); + 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; + + 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; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + + if (!buf->area) { + printk(KERN_INFO "rda audio : %s, alloc dma buffer fail. \n", __func__); + 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; + + 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; + + 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 int rda_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &rda_soc_platform); +} + +static int __exit rda_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver rda_pcm_driver = { + .driver = { + .name = "rda-pcm", + .owner = THIS_MODULE, + }, + + .probe = rda_pcm_probe, + .remove = __exit_p(rda_pcm_remove), +}; + +static int __init rda_pcm_modinit(void) +{ + return platform_driver_register(&rda_pcm_driver); +} + +static void __exit rda_pcm_modexit(void) +{ + platform_driver_unregister(&rda_pcm_driver); +} + +module_init(rda_pcm_modinit); +module_exit(rda_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/rda/rda_pcm.h b/sound/soc/rda/rda_pcm.h new file mode 100644 index 000000000000..1d46ee10f79e --- /dev/null +++ b/sound/soc/rda/rda_pcm.h @@ -0,0 +1,40 @@ +/* + * 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/rda/rda_soundcard.c b/sound/soc/rda/rda_soundcard.c new file mode 100644 index 000000000000..1ce270d55870 --- /dev/null +++ b/sound/soc/rda/rda_soundcard.c @@ -0,0 +1,150 @@ +/* + * 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_soundcard_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 ">>>> %s \n", __func__); + + /* 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_soundcard_dai_link_ops = { + .hw_params = rda_soundcard_hw_params, +}; + +static const struct snd_soc_dapm_route rda_soundcard_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_soundcard_dai_link[] = { + // audio stream + { + .name = "rda-soundcard-dai-link-aud", // machine dai link name + .stream_name = "rdaaud-stream", // stream name + + // cpu(soc)(platform) name - manage "data" and "audio ifc" + .platform_name = "rda-pcm", + // cpu dai - manage aif + // .cpu_dai_name = "rdaaud-platform-cpu-dai", + .cpu_dai_name = "rda-aif", + + // codec name + .codec_name = "rda-codec", + // codec dai name - in fact, there is no codec dai in rda + .codec_dai_name = "rda-codec-dai", + + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &rda_soundcard_dai_link_ops, + }, + // voice stream + { + .name = "rda-soundcard-dai-link-voice", // machine dai link name + .stream_name = "rdavoice-stream", // stream name + + // cpu(soc)(platform) name - comm with modem + .platform_name = "rda-voice-pcm", + // cpu dai - fake + .cpu_dai_name = "rda-voice-cpu-dai", + + // codec name - fake + .codec_name = "rda-voice-codec", + // codec dai name - fake + .codec_dai_name = "rda-voice-codec-dai", + + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, + .ops = &rda_soundcard_dai_link_ops, + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card rda_soundcard = { + .name = "rda-soundcard", // sound card name + .owner = THIS_MODULE, + .dai_link = &rda_soundcard_dai_link[0], + .num_links = ARRAY_SIZE(rda_soundcard_dai_link), + + .dapm_widgets = NULL, + .num_dapm_widgets = 0, + .dapm_routes = NULL, + .num_dapm_routes = 0, +}; + +static struct platform_device *rda_snd_device; + +static int __init rda_soundcard_modinit(void) +{ + int err = 0; + + rda_snd_device = platform_device_alloc("soc-audio", -1); + + if (!rda_snd_device) + return -ENOMEM; + + platform_set_drvdata(rda_snd_device, &rda_soundcard); + + err = platform_device_add(rda_snd_device); + if (err) + platform_device_put(rda_snd_device); + + return err; +} + +static void __exit rda_soundcard_modexit(void) +{ + platform_device_unregister(rda_snd_device); +} + +module_init(rda_soundcard_modinit); +module_exit(rda_soundcard_modexit); + +MODULE_DESCRIPTION("ALSA SoC for RDA SoundCard"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rda/rda_voice_codec.c b/sound/soc/rda/rda_voice_codec.c new file mode 100644 index 000000000000..8ab8be16058f --- /dev/null +++ b/sound/soc/rda/rda_voice_codec.c @@ -0,0 +1,211 @@ +/* + * ALSA SoC RDA 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: + * rda codec + * + * 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 <mach/iomap.h> +#include <asm/io.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <plat/reg_spi.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 <plat/rda_debug.h> +#include <plat/md_sys.h> + +static int rda_voice_codec_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int rda_voice_codec_dai_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int rda_voice_codec_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int rda_voice_codec_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +static void rda_voice_codec_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ +} + +static int rda_voice_codec_dai_dig_mute(struct snd_soc_dai *dai, int mute) +{ + return 0; +} + +static int rda_voice_codec_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + return 0; +} + +static int rda_voice_codec_dai_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int rda_voice_codec_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + return 0; +} + +#define RDA_CODEC_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 rda_voice_codec_dai_ops = { + .startup = rda_voice_codec_dai_startup, + .hw_params = rda_voice_codec_dai_hw_params, + .prepare = rda_voice_codec_dai_pcm_prepare, + .trigger = rda_voice_codec_dai_trigger, + .shutdown = rda_voice_codec_dai_shutdown, + .digital_mute = rda_voice_codec_dai_dig_mute, + .set_fmt = rda_voice_codec_dai_set_fmt, + .set_sysclk = rda_voice_codec_dai_set_sysclk, +}; + +static struct snd_soc_dai_driver rda_voice_codec_dai_driver = { + .name = "rda-voice-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = RDA_CODEC_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = RDA_CODEC_FORMATS, + }, + .ops = &rda_voice_codec_dai_ops, +}; + +static int rda_voice_codec_suspend(struct snd_soc_codec *codec) +{ + return 0; +} + +static int rda_voice_codec_resume(struct snd_soc_codec *codec) +{ + return 0; +} + +static int rda_voice_codec_probe(struct snd_soc_codec *codec) +{ + return 0; +} + +static int rda_voice_codec_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_rda_voice_codec_driver = { + .reg_cache_size = 0, + .reg_word_size = sizeof(u16), + .reg_cache_default = NULL, + .probe = rda_voice_codec_probe, + .remove = rda_voice_codec_remove, + .suspend = rda_voice_codec_suspend, + .resume = rda_voice_codec_resume, + .set_bias_level = rda_voice_codec_set_bias_level, + .dapm_widgets = NULL, + .num_dapm_widgets = 0, + .dapm_routes = NULL, + .num_dapm_routes = 0, +}; + +static int rda_voice_codec_platform_probe(struct platform_device *pdev) +{ + int ret = 0; + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_rda_voice_codec_driver, + &rda_voice_codec_dai_driver, 1); + return 0; +} + +static int __exit rda_voice_codec_platform_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver rda_voice_codec_driver = { + .driver = { + .name = "rda-voice-codec", + .owner = THIS_MODULE, + }, + + .probe = rda_voice_codec_platform_probe, + .remove = __exit_p(rda_voice_codec_platform_remove), +}; + +static struct platform_device rda_voice_codec = { + .name = "rda-voice-codec", + .id = -1, +}; + +static int __init rda_voice_codec_modinit(void) +{ + platform_device_register(&rda_voice_codec); + return platform_driver_register(&rda_voice_codec_driver); +} + +static void __exit rda_voice_codec_modexit(void) +{ + platform_driver_unregister(&rda_voice_codec_driver); +} + +static void __exit rdafpag_pcm_modexit(void) +{ + platform_driver_unregister(&rda_voice_codec_driver); +} + +module_init(rda_voice_codec_modinit); +module_exit(rda_voice_codec_modexit); + +MODULE_DESCRIPTION("ASoC RDA codec driver"); +MODULE_AUTHOR("Arun KS <arunks@mistralsolutions.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rda/rda_voice_dai.c b/sound/soc/rda/rda_voice_dai.c new file mode 100644 index 000000000000..710d3d08588a --- /dev/null +++ b/sound/soc/rda/rda_voice_dai.c @@ -0,0 +1,161 @@ +/* + * 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> + +static int rda_voice_cpu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +static void rda_voice_cpu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ +} + +static int rda_voice_cpu_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int rda_voice_cpu_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return 0; +} + +static int rda_voice_cpu_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + return 0; +} + +static const struct snd_soc_dai_ops rda_voice_cpu_dai_driver_ops = { + .startup = rda_voice_cpu_dai_startup, + .shutdown = rda_voice_cpu_dai_shutdown, + .hw_params = rda_voice_cpu_dai_hw_params, + .prepare = rda_voice_cpu_dai_prepare, + .trigger = rda_voice_cpu_dai_trigger, +}; + +static int rda_voice_cpu_dai_driver_probe(struct snd_soc_dai *dai) +{ + return 0; +} + +static int rda_voice_cpu_dai_driver_remove(struct snd_soc_dai *dai) +{ + return 0; +} + +static struct snd_soc_dai_driver rda_voice_cpu_dai_driver = { + .name = "rda-voice-cpu-dai-driver", + .probe = rda_voice_cpu_dai_driver_probe, + .remove = rda_voice_cpu_dai_driver_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_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .sig_bits = 16, + }, + .ops = &rda_voice_cpu_dai_driver_ops, +}; + +static struct snd_soc_component_driver rda_voice_component = { + .name = "rda_voice_component" +}; + +static int rda_voice_cpu_dai_platform_driver_probe(struct platform_device *pdev) +{ + int ret = 0; + + ret = snd_soc_register_component(&pdev->dev, &rda_voice_component, &rda_voice_cpu_dai_driver, 1); + return 0; +} + +static int __exit rda_voice_cpu_dai_platform_driver_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + + return 0; +} + +static struct platform_driver rda_voice_cpu_dai_platform_driver = { + .driver = { + .name = "rda-voice-cpu-dai", + .owner = THIS_MODULE, + }, + .probe = rda_voice_cpu_dai_platform_driver_probe, + .remove = __exit_p(rda_voice_cpu_dai_platform_driver_remove), +}; + +static struct platform_device rda_voice_cpu_dai = { + .name = "rda-voice-cpu-dai", + .id = -1, +}; + +static int __init rda_voice_cpu_dai_modinit(void) +{ + platform_device_register(&rda_voice_cpu_dai); + return platform_driver_register(&rda_voice_cpu_dai_platform_driver); +} + +static void __exit rda_voice_cpu_dai_modexit(void) +{ + platform_driver_unregister(&rda_voice_cpu_dai_platform_driver); +} + +module_init(rda_voice_cpu_dai_modinit); +module_exit(rda_voice_cpu_dai_modexit); + +MODULE_DESCRIPTION("ALSA SoC for RDA CPU DAI"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rda/rda_voice_pcm.c b/sound/soc/rda/rda_voice_pcm.c new file mode 100644 index 000000000000..15801123137b --- /dev/null +++ b/sound/soc/rda/rda_voice_pcm.c @@ -0,0 +1,588 @@ +/* + * rda_voice_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 <plat/md_sys.h> +#include <plat/pm_ddr.h> +#include "rda_voice_pcm.h" + +// FIXME, same as modem definition (vois_m.h) +enum { + VOIS_STATUS_MID_BUFFER_REACHED, + VOIS_STATUS_END_BUFFER_REACHED, + VOIS_STATUS_NO_MORE_DATA, + VOIS_STATUS_ERR, + + VOIS_STATUS_QTY, +}; +// FIXME, same as modem definition (Syscmds_audio.c) +#define PCM_VOICE_PERIOD_COUNT 4 +#define PCM_SHARE_BUFFER_SIZE (640*10) +#define PCM_VOICE_PERIOD_SIZE (PCM_SHARE_BUFFER_SIZE/2) +#define PCM_VOICE_PERIOD_TIME_IN_MS (PCM_VOICE_PERIOD_SIZE/2/8000) + +#define ADDR_MD2AP(addr) \ + ((addr&0x0FFFFFFF) | 0x10000000) + +static u8 g_current_period = 0; +static void __iomem * g_modem_share_pcm_buf = NULL; +static struct rda_voice_pcm_dma_data voice_dma_data; +static struct msys_device *voice_msys = NULL; +static struct timer_list period_timer; + +static const struct snd_pcm_hardware rda_voice_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME , + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = PCM_VOICE_PERIOD_SIZE, + .period_bytes_max = PCM_VOICE_PERIOD_SIZE, + .periods_min = PCM_VOICE_PERIOD_COUNT, + .periods_max = PCM_VOICE_PERIOD_COUNT, + .buffer_bytes_max = 256 * 1024, +}; + +struct rda_runtime_data { + spinlock_t lock; + struct rda_voice_pcm_dma_data *dma_data; + int dma_ch; +}; + +static struct work_struct start_work; +static struct work_struct stop_work; +static struct workqueue_struct *record_wq; +static struct mutex record_mutex; + +static void period_timer_func(unsigned long from_timer) +{ + struct snd_pcm_substream *substream = NULL; + struct snd_dma_buffer *buf = NULL; + if(voice_msys) + substream = voice_msys->private; + else { + printk(KERN_ERR"%s : voice_msys is null, not process. \n", __func__); + return ; + } + + if(substream) { + buf = &substream->dma_buffer; + } + else { + printk(KERN_ERR"%s: substream is null, not process. \n", __func__); + return ; + } + + memset(buf->area + g_current_period*PCM_VOICE_PERIOD_SIZE, 0, PCM_VOICE_PERIOD_SIZE); + snd_pcm_period_elapsed(substream); + ++g_current_period; + if(g_current_period >= PCM_VOICE_PERIOD_COUNT) + g_current_period = 0; + printk(KERN_INFO"mod_timer %d\n",g_current_period); + mod_timer(&period_timer, jiffies+msecs_to_jiffies(PCM_VOICE_PERIOD_TIME_IN_MS)); +} + +static int vois_RecordStart(u32 *buffer_address) +{ + int ret = 0; + struct client_cmd cmd; + + printk(KERN_INFO ">>>> [%s]\n", __func__); + + *buffer_address = 0; + + memset(&cmd, 0, sizeof(cmd)); + cmd.pmsys_dev = voice_msys; + cmd.mod_id = SYS_AUDIO_MOD; + cmd.mesg_id = SYS_AUDIO_CMD_AUD_VOICE_RECORD_START; + cmd.pout_data = buffer_address; + cmd.out_size = sizeof(*buffer_address); + ret = rda_msys_send_cmd(&cmd); + printk(KERN_INFO ">>>> [%s], ret [%d] addr [0x%x]\n", + __func__, ret, *buffer_address); + + return ret; +} + +static int vois_RecordStop(void) +{ + int ret = 0; + struct client_cmd cmd; + + printk(KERN_INFO ">>>> [%s]\n", __func__); + + memset(&cmd, 0, sizeof(cmd)); + cmd.pmsys_dev = voice_msys; + cmd.mod_id = SYS_AUDIO_MOD; + cmd.mesg_id = SYS_AUDIO_CMD_AUD_VOICE_RECORD_STOP; + cmd.pdata = NULL; + cmd.data_size = 0; + ret = rda_msys_send_cmd(&cmd); + printk(KERN_INFO ">>>> [%s], ret [%d] \n", __func__, ret); + + return ret; +} + +/* this may get called several times by oss emulation */ +static int rda_voice_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct rda_runtime_data *prtd = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->dma_data = &voice_dma_data; + + return 0; +} + +static int rda_voice_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; + + prtd->dma_data = NULL; + + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int rda_voice_pcm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static void rda_record_start_work(struct work_struct *work) +{ + int ret = 0; + u32 ret_addr = 0; + + mutex_lock(&record_mutex); + ret = vois_RecordStart(&ret_addr); + if (ret) { + printk(KERN_INFO "rda voice : failed to start voice record. \n"); + } + g_modem_share_pcm_buf = ioremap(ADDR_MD2AP(ret_addr), PCM_SHARE_BUFFER_SIZE); + if(g_modem_share_pcm_buf <= 0) { + printk(KERN_INFO "rda voice : remap shared buffer fail. \n"); + } + mutex_unlock(&record_mutex); +} + +static void rda_record_stop_work(struct work_struct *work) +{ + unsigned long flags; + + mutex_lock(&record_mutex); + vois_RecordStop(); + if(g_modem_share_pcm_buf) + iounmap(g_modem_share_pcm_buf); + mutex_unlock(&record_mutex); + mod_timer(&period_timer, 0); + local_irq_save(flags); + g_modem_share_pcm_buf = 0; + g_current_period = 0; + local_irq_restore(flags); + +} + +static int rda_voice_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + //u32 ret_addr = 0; + int ret = 0; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + { + printk(KERN_INFO "rda voice : [start aud pcm] : substream->stream is [%d], cmd is [%d] \n", + substream->stream, cmd); + if(voice_msys) { + voice_msys->private = (void *)substream; + // remap it every time in case of modem "malloc" the mem + /*ret = vois_RecordStart(&ret_addr); + if (ret) { + printk(KERN_INFO "rda voice : failed to start voice record. \n"); + ret = -EINVAL; + } + g_modem_share_pcm_buf = ioremap(ADDR_MD2AP(ret_addr), PCM_SHARE_BUFFER_SIZE); + if(g_modem_share_pcm_buf <= 0) { + printk(KERN_INFO "rda voice : remap shared buffer fail. \n"); + ret = -EINVAL; + }*/ + //queue start work here to avoid dead lock, since md_sys command will sleep + queue_work(record_wq, &start_work); + } + else + printk(KERN_INFO "rda voice : [start aud pcm] : BUG : no voice_msys here! \n"); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + { + printk(KERN_INFO "rda8810 voice : [stop aud pcm] : substream->stream is [%d], cmd is [%d] \n", + substream->stream, cmd); + if(voice_msys) { + /*vois_RecordStop(); + if(g_modem_share_pcm_buf) + iounmap(g_modem_share_pcm_buf); + mod_timer(&period_timer, 0); + local_irq_disable(); + g_modem_share_pcm_buf = 0; + voice_msys->private = (void *)NULL; + g_current_period = 0; + local_irq_enable();*/ + //queue stop work here same to start + queue_work(record_wq,&stop_work); + voice_msys->private = (void *)NULL; + } + else + printk(KERN_INFO "rda voice : [stop aud pcm] : BUG : no voice_msys here! \n"); + } + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t rda_voice_pcm_pointer(struct snd_pcm_substream + *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t offset; + offset = bytes_to_frames(runtime, g_current_period*PCM_VOICE_PERIOD_SIZE); + + if (offset >= runtime->buffer_size) + offset = 0; + + return offset; +} + +static int rda_voice_pcm_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct rda_runtime_data *prtd; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_soc_set_runtime_hwparams(substream, &rda_voice_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) { + printk(KERN_ERR"rda_voice_pcm_open : snd_pcm_hw_constraint_integer < 0\n"); + goto out; + } + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) { + printk(KERN_ERR"rda_voice_pcm_open : kzalloc == NULL\n"); + ret = -ENOMEM; + goto out; + } + spin_lock_init(&prtd->lock); + runtime->private_data = prtd; + pm_ddr_get(PM_DDR_AUDIO_IFC_CAPTURE); + printk(KERN_INFO"rda_voice_pcm_open get pm ddr\n"); +out: + return ret; +} + +static int rda_voice_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + kfree(runtime->private_data); + pm_ddr_put(PM_DDR_AUDIO_IFC_CAPTURE); + printk(KERN_INFO"rda_voice_pcm_close put pm ddr\n"); + return 0; +} + +static int rda_voice_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} + +static struct snd_pcm_ops rda_voice_pcm_ops = { + .open = rda_voice_pcm_open, + .close = rda_voice_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = rda_voice_pcm_hw_params, + .hw_free = rda_voice_pcm_hw_free, + .prepare = rda_voice_pcm_prepare, + .trigger = rda_voice_pcm_trigger, + .pointer = rda_voice_pcm_pointer, + .mmap = rda_voice_pcm_mmap, +}; + + + +static u64 rda_voice_pcm_dmamask = DMA_BIT_MASK(32); + +static int rda_voice_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_voice_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static void rda_voice_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream = 0; + 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_voice_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &rda_voice_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_voice_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = rda_voice_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + rda_voice_pcm_free_dma_buffers(pcm); + + return ret; +} + +static struct snd_soc_platform_driver rda_soc_platform = { + .ops = &rda_voice_pcm_ops, + .pcm_new = rda_voice_pcm_new, + .pcm_free = rda_voice_pcm_free_dma_buffers, +}; + +static int rda_modem_voice_notify(struct notifier_block *nb, unsigned long mesg, void *data) +{ + struct client_mesg *pmesg = (struct client_mesg *)data; + struct snd_pcm_runtime *runtime = NULL; + struct snd_pcm_substream *substream = NULL; + struct snd_dma_buffer *buf = NULL; + unsigned int off = 0; + int status = 0; + + if (pmesg->mod_id != SYS_AUDIO_MOD) { + // printk(KERN_ERR"rda_modem_voice_notify : not audio mod mesg \n"); + return NOTIFY_DONE; + } + + if(voice_msys) + substream = voice_msys->private; + else { + // printk(KERN_ERR"rda_modem_voice_notify : BUG : voice_msys is null. \n"); + return NOTIFY_OK; + } + + if(substream) { + runtime = substream->runtime; + buf = &substream->dma_buffer; + } + else { + printk(KERN_ERR"rda_modem_voice_notify : BUG : substream is null. \n"); + return NOTIFY_OK; + } + + if(mesg == SYS_AUDIO_MESG_VOICE_HANDLER_CALLBACK) { + status = *((unsigned int*)&(pmesg->param)); + // printk(KERN_ERR"rda_modem_voice_notify : mesg [0x%x] \n", status); + if(status == VOIS_STATUS_MID_BUFFER_REACHED || status == VOIS_STATUS_END_BUFFER_REACHED) { + if(status == VOIS_STATUS_MID_BUFFER_REACHED) + off = 0; + else + off = PCM_VOICE_PERIOD_SIZE; + + // copy buffer, buf->dma_area is virtual address of audio dma buffer we alloc. + // a period one time ( ap has 4 periods and md has 2 period ) + memcpy(buf->area + g_current_period*PCM_VOICE_PERIOD_SIZE, + g_modem_share_pcm_buf + off, PCM_VOICE_PERIOD_SIZE); + snd_pcm_period_elapsed(substream); + + ++g_current_period; + + if(g_current_period >= PCM_VOICE_PERIOD_COUNT) + g_current_period = 0; + } + else if (status == VOIS_STATUS_NO_MORE_DATA) { + // FIXME VOIS_STATUS_NO_MORE_DATA means end recording at modem side, we fill 0 for rest periods + // for up layer return from in_read cause it is blocked + // way 1 + //for(i = 0; i < PCM_VOICE_PERIOD_COUNT*5; ++i) { + // memset(buf->area + g_current_period*PCM_VOICE_PERIOD_SIZE, 0, PCM_VOICE_PERIOD_SIZE); + // snd_pcm_period_elapsed(substream); + // ++g_current_period; + // if(g_current_period >= PCM_VOICE_PERIOD_COUNT) + // g_current_period = 0; + //} + // way 2 + mod_timer(&period_timer, jiffies+msecs_to_jiffies(PCM_VOICE_PERIOD_TIME_IN_MS)); + } + + return NOTIFY_DONE; + } + else { + // printk(KERN_ERR"rda_modem_voice_notify : BUG : mesg != SYS_AUDIO_MESG_VOICE_HANDLER_CALLBACK. \n"); + } + + return NOTIFY_OK; +} + + +static int rda_voice_pcm_probe(struct platform_device *pdev) +{ + voice_msys = rda_msys_alloc_device(); + if (!voice_msys) { + printk(KERN_ERR"rda_voice_pcm_probe : rda_msys_alloc_device fail. \n"); + return -ENOMEM; + } + + voice_msys->module = SYS_AUDIO_MOD; + voice_msys->name = "rda-voice-pcm"; + voice_msys->notifier.notifier_call = rda_modem_voice_notify; + voice_msys->private = (void *)NULL; + + rda_msys_register_device(voice_msys); + + init_timer(&period_timer); + period_timer.expires = 0; + period_timer.function = period_timer_func; + period_timer.data = 0; + // add_timer(&period_timer); + + INIT_WORK(&start_work,rda_record_start_work); + INIT_WORK(&stop_work,rda_record_stop_work); + record_wq = create_singlethread_workqueue("record_wq"); + mutex_init(&record_mutex); + + return snd_soc_register_platform(&pdev->dev, &rda_soc_platform); +} + +static int __exit rda_voice_pcm_remove(struct platform_device *pdev) +{ + if(voice_msys) { + rda_msys_unregister_device(voice_msys); + rda_msys_free_device(voice_msys); + } + + del_timer_sync(&period_timer); + + snd_soc_unregister_platform(&pdev->dev); + destroy_workqueue(record_wq); + return 0; +} + +static struct platform_driver rda_voice_pcm_driver = { + .driver = { + .name = "rda-voice-pcm", + .owner = THIS_MODULE, + }, + + .probe = rda_voice_pcm_probe, + .remove = __exit_p(rda_voice_pcm_remove), +}; + +static struct platform_device rda_voice_pcm = { + .name = "rda-voice-pcm", + .id = -1, +}; + +static int __init rda_voice_pcm_modinit(void) +{ + platform_device_register(&rda_voice_pcm); + return platform_driver_register(&rda_voice_pcm_driver); +} + +static void __exit rda_voice_pcm_modexit(void) +{ + platform_driver_unregister(&rda_voice_pcm_driver); +} + +module_init(rda_voice_pcm_modinit); +module_exit(rda_voice_pcm_modexit); + +MODULE_DESCRIPTION("ALSA SoC for RDA PCM"); +MODULE_AUTHOR("Xu Mingliang <mingliangxu@rdamicro.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rda/rda_voice_pcm.h b/sound/soc/rda/rda_voice_pcm.h new file mode 100644 index 000000000000..b6c23eedaa63 --- /dev/null +++ b/sound/soc/rda/rda_voice_pcm.h @@ -0,0 +1,40 @@ +/* + * 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_VOICE_PCM_H__ +#define __RDA_VOICE_PCM_H__ + +struct snd_pcm_substream; + +struct rda_voice_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 |