/*++ * linux/sound/soc/wmt/wmt-i2s.c * WonderMedia I2S audio driver for ALSA * * Copyright c 2010 WonderMedia Technologies, 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, see . * * WonderMedia Technologies, Inc. * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C --*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wmt-soc.h" #include "wmt-pcm.h" #define NULL_DMA ((dmach_t)(-1)) #define AUD_SPDIF_ENABLE 1 static int wmt_i2s_output = 0; //hdmi enable or not 2013-9-3 //maybe can remove it 2013-10-24 static int wmt_codec_wm8994 = 0; //env set 2013-9-3 static int gpio_pa = -1; static int gpio_active = 0; extern int wmt_gpio_setpull(unsigned int gpio, enum wmt_gpio_pulltype pull); /* * Debug */ #define AUDIO_NAME "WMT_I2S" //#define WMT_I2S_DEBUG 1 //#define WMT_I2S_DEBUG_DETAIL 1 #ifdef WMT_I2S_DEBUG #define DPRINTK(format, arg...) \ printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) #else #define DPRINTK(format, arg...) do {} while (0) #endif #ifdef WMT_I2S_DEBUG_DETAIL #define DBG_DETAIL(format, arg...) \ printk(KERN_INFO AUDIO_NAME ": [%s]" format "\n" , __FUNCTION__, ## arg) #else #define DBG_DETAIL(format, arg...) do {} while (0) #endif #define err(format, arg...) \ printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) #define info(format, arg...) \ printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) #define warn(format, arg...) \ printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) struct wmt_i2s_asoc_data { int stream_id; unsigned int bus_id; struct i2s_s i2s; /* * Flags indicating is the bus already activated and configured by * another substream */ unsigned char HDMI_and_DAC0; //new env set to determine unsigned char HDMI_aud_enable; //same with global wmt_i2s_output int active; int configured; unsigned char CH_SEL_NUM; unsigned char HDMI_SPDIF_STATE; /*struct timer_list delay_timer;*/ struct audio_stream_a s[2]; }; static struct timer_list pa_timer; static int audio_interface_mode = 0;//add 2014-6-24 i2s left right static int wmt_pdm_module_enable = 0; extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); #define to_i2s_data(priv) container_of((priv), struct wmt_i2s_asoc_data, bus_id) #define SNDRV_PCM_STREAM_ALL 2 static void i2s_init(int mode); static void i2s_exit(void); #ifdef CONFIG_FB_WMT extern int vpp_set_audio(int format, int sample_rate, int channel); #endif static struct wmt_i2s_asoc_data i2s_data[NUM_LINKS] = { { .bus_id = 0, //?? 2 SNDRV_PCM_STREAM_ALL .i2s = { /* interrupt counters */ {0, 0, 0, 0, 0, 0, 0, 0}, /* irq number*/ 0, /* reference counter */ 0, /* channels */ 0, /* format */ 0, /* fragment size */ 0, /* sample rate */ 0, i2s_init, i2s_exit, }, .s = { { .id = "WMT I2S out", .stream_id = SNDRV_PCM_STREAM_PLAYBACK, .dmach = NULL_DMA, .dma_dev = AHB1_AUD_DMA_REQ_1, /*.dma_cfg = dma_device_cfg_table[I2S_TX_DMA_REQ],*/ }, { .id = "WMT I2S in", .stream_id = SNDRV_PCM_STREAM_CAPTURE, .dmach = NULL_DMA, .dma_dev = AHB1_AUD_DMA_REQ_0, /*.dma_cfg = dma_device_cfg_table[I2S_RX_DMA_REQ],*/ }, }, .HDMI_aud_enable = 0, .HDMI_and_DAC0 = 0, .CH_SEL_NUM = 2, .HDMI_SPDIF_STATE = 6,//hdmi spdif play at sametime }, }; /* for HDMI Audio status */ int hd_audio_flag = 0; #ifdef CONFIG_WMT_I2S_INT /* wmt_i2s_interrupt() * * It's only interrupt counter now, might be useful to * debug or benchmark. */ static irqreturn_t wmt_i2s_interrupt(int irq, void *dev_id, struct pt_regs *regs) { DBG_DETAIL(); return IRQ_HANDLED; } #endif #if 0 static void delay_timer_handler(unsigned long data) { DBG_DETAIL(); } #endif static void pa_timer_handler(unsigned long data) { printk("%s\n", __func__); if (gpio_pa >= 0) { gpio_direction_output(gpio_pa, gpio_active); } } void wmt_i2s_ch_config(void) { /* CH_SEL_NUM = 0, mesns output L to SPDIF&DAC's L and R 1, mesns output R to SPDIF&DAC's L and R 2, means normal stereo output to SPDIF&DAC */ switch (i2s_data->CH_SEL_NUM) { case 0: ASMPFCHCFG0_VAL = 0x10100000; ASMPF2HDACHCFG_VAL = 0x76543200; break; case 1: ASMPFCHCFG0_VAL = 0x10101111; ASMPF2HDACHCFG_VAL = 0x76543211; break; case 2: ASMPFCHCFG0_VAL = 0x10101010; ASMPF2HDACHCFG_VAL = 0x76543210; break; } if (i2s_data->i2s.channels == 0x01) { ASMPFCHCFG0_VAL = 0x10100000; ASMPF2HDACHCFG_VAL = 0x76543200; } if (i2s_data->HDMI_aud_enable) { /* disable DAC#0, if HDMI Audio is enabled */ ASMPFCHCFG0_VAL &= 0xFFFF00FF; ASMPFCHCFG0_VAL |= 0x00009800; } /* HDMI_SPDIF_STATE = 3, means disable HDMI&SPDIF 4, means enable HDMI only 5, means enable SPDIF only 6, means enable HDMI&SPDIF as normal */ switch (i2s_data->HDMI_SPDIF_STATE) { case 3: ASMPFCHCFG0_VAL &= 0xFFFFFF00; ASMPFCHCFG0_VAL |= 0x00000098; ASMPF2HDACHCFG_VAL &= 0xFFFFFF00; ASMPF2HDACHCFG_VAL |= 0x00000076; break; case 4: ASMPFCHCFG0_VAL &= 0xFFFFFF00; ASMPFCHCFG0_VAL |= 0x00000098; break; case 5: ASMPF2HDACHCFG_VAL &= 0xFFFFFF00; ASMPF2HDACHCFG_VAL |= 0x00000076; break; } } void wmt_i2s_dac0_ctrl(int HDMI_audio_enable) { /* check if output to HDMI audio and DAC0 at same time */ if (i2s_data->HDMI_and_DAC0) return; if (HDMI_audio_enable) { i2s_data->HDMI_aud_enable = 1; info("HDMI Audio is enabled, disable dac0 of I2S"); } else { i2s_data->HDMI_aud_enable = 0; info("HDMI Audio is disabled, enable dac0 of I2S"); } wmt_i2s_ch_config(); info("CHCFG0=0x%x, HDACHCFG=0x%x", ASMPFCHCFG0_VAL, ASMPF2HDACHCFG_VAL); } EXPORT_SYMBOL(wmt_i2s_dac0_ctrl); void wmt_i2s_ch_sel(int ch_sel_num) { if (ch_sel_num < 3) i2s_data->CH_SEL_NUM = ch_sel_num; else i2s_data->HDMI_SPDIF_STATE = ch_sel_num; wmt_i2s_ch_config(); info("SEL: CHCFG0=0x%x, HDACHCFG=0x%x", ASMPFCHCFG0_VAL, ASMPF2HDACHCFG_VAL); } EXPORT_SYMBOL(wmt_i2s_ch_sel); static void i2s_disable(void) { DBG_DETAIL(); DACCFG_VAL &= ~DACITF_ENABLE; ASMPFCFG_VAL &=~ASMPF_ENABLE; HDACKGEN_VAL &= ~HDACKGEN_ENABLE; #ifdef AUD_SPDIF_ENABLE DGOCFG_VAL &= ~DGOITF_ENABLE; #endif AADCF0CFG_VAL &= ~(AADCITF_ENABLE | AADCF_ENABLE); } static void i2s_enable(void) { DBG_DETAIL(); AADCF0CFG_VAL |= (AADCITF_ENABLE | AADCF_ENABLE); ASMPFCFG_VAL |= (ASMPF_ENABLE); #ifdef AUD_SPDIF_ENABLE DGOCFG_VAL |= DGOITF_ENABLE; #endif DACCFG_VAL |= DACITF_ENABLE; if (hd_audio_flag) HDACKGEN_VAL |= HDACKGEN_ENABLE; } static void aud_audprf_setting(unsigned char smp_rate_index) { unsigned long cfg_tbl[] = {0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00000001, 0x00000001, 0x00000001 }; unsigned int dgo_tbl[] = {0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00}; unsigned int sysclk_tbl[] = {4096, 5632, 6144, 8192, 11264, 12288, 16384, 22579, 24576, 32768, 45056, 49152, 16384, 22528, 24576}; #ifdef AUD_SPDIF_ENABLE unsigned int dgocs_tbl[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x08, 0x0A, 0x00, 0x0C, 0x0E}; #endif //unsigned int clock = 0; unsigned long aud_reg_val; DBG_DETAIL(); ADCCFG_VAL = cfg_tbl[smp_rate_index]; aud_reg_val = dgo_tbl[smp_rate_index]; DGOCFG_VAL = aud_reg_val; HDACKGEN_VAL = aud_reg_val; #ifdef AUD_SPDIF_ENABLE /* set ADGO channel status */ aud_reg_val = dgocs_tbl[smp_rate_index] << 24; DGOCS0A_VAL = aud_reg_val; DGOCS1A_VAL = aud_reg_val; //info("%s : DGOCS01A_VAL=0x%x", __func__, (unsigned int)aud_reg_val); #endif DACCFG_VAL = cfg_tbl[smp_rate_index]; auto_pll_divisor(DEV_I2S, CLK_ENABLE , 0, 0); auto_pll_divisor(DEV_I2S, SET_PLLDIV, 1, sysclk_tbl[smp_rate_index]); /*clock = auto_pll_divisor(DEV_I2S, GET_FREQ , 0, 0); info("%s : clock=%d", __func__, clock);*/ } static unsigned char aud_smp_rate_convert(unsigned int smp_rate) { unsigned char i = 0; unsigned int smp_rate_tbl[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 128000, 176000, 192000}; DBG_DETAIL(); /* boundary checking */ if (smp_rate < 8000) { i = 0x00; } else if (smp_rate > 192000) { i = 0x0E; } else { for (i = 0; i < 0x1F; i++) { if (smp_rate == smp_rate_tbl[i]) { break; } else if (smp_rate < smp_rate_tbl[i + 1]) { if (smp_rate < (smp_rate_tbl[i] + ((smp_rate_tbl[i + 1] - smp_rate_tbl[i]) / 2))) { break; } else { i++; break; } } } } return i; } static void i2s_sample_rate(unsigned int rate) { unsigned char rate_index; DBG_DETAIL("rate=%d", rate); if (rate == i2s_data->i2s.rate) return; i2s_data->i2s.rate = rate; rate_index = aud_smp_rate_convert(rate); aud_audprf_setting(rate_index); #ifdef CONFIG_FB_WMT /* pass information of audio to HDMI Audio */ hd_audio_flag = vpp_set_audio(16, rate, 2); #endif } static int i2sdacdat_gpio = -1;//means don't touch GP10 bit0-3 i2sDacDat0-3 void wmt_set_i2s_share_pin(void) { int pwren_num,active_level,gpio_int_num; char buf[256]; int varlen = 256; static int gpio26_mux = -1; unsigned int gpval = 0x0; if(gpio26_mux == -1){ if(wmt_getsyspara("wmt.bt.mtk6622",buf,&varlen) == 0) { sscanf(buf,"%d:%d:%d",&pwren_num,&active_level,&gpio_int_num); printk("use customized value:p%d,a%d,i%d\n",pwren_num,active_level,gpio_int_num); if(pwren_num == 62){ gpio26_mux = 0x01; }else{ gpio26_mux = 0x00; } }else{ gpio26_mux = 0x00; } } printk("%s gpio26_mux:%d\n",__func__,gpio26_mux); if(!gpio26_mux){ /* disable GPIO and enable Pull Down mode */ gpval &= ~0xff; //GPIO_CTRL_GP10_I2S_BYTE_VAL &= ~0xFF; // 0xFF bit1 }else{ gpval &= ~0xF7; //GPIO_CTRL_GP10_I2S_BYTE_VAL &= ~0xF7; } // configure I2SDACDAT1/2/3 as GPIO function but mmax-wm8994 that configured as I2SADCLRC/I2SADCBLCK if (!wmt_codec_wm8994) { gpval |= (BIT1 | BIT2 | BIT3); //GPIO_CTRL_GP10_I2S_BYTE_VAL |= (BIT1 | BIT2 | BIT3); } //add for mainly i2sdacdat1 work as gpio to control led 2014-8-15 so here just keep it specific func //tp driver will use it as gpio switch (i2sdacdat_gpio) { case 1: gpval |= BIT1; //GPIO_CTRL_GP10_I2S_BYTE_VAL |= BIT1; // i2sdacdat1 work as gpio break; case 2: gpval |= BIT2; //GPIO_CTRL_GP10_I2S_BYTE_VAL |= BIT2; break; case 3: gpval |= BIT3; //GPIO_CTRL_GP10_I2S_BYTE_VAL |= BIT3; break; } GPIO_CTRL_GP10_I2S_BYTE_VAL = gpval; GPIO_CTRL_GP11_I2S_BYTE_VAL &= ~(BIT0 | BIT1 | BIT2); if(!gpio26_mux){ PULL_EN_GP10_I2S_BYTE_VAL |= 0xFF; }else{ PULL_EN_GP10_I2S_BYTE_VAL |= 0xF7; } PULL_EN_GP11_I2S_BYTE_VAL |= (BIT0 | BIT1 | BIT2); if(!gpio26_mux){ /* set to 2ch input, 2ch output */ PIN_SHARING_SEL_4BYTE_VAL &= ~(BIT15 | BIT17 | BIT19 | BIT20); PIN_SHARING_SEL_4BYTE_VAL |= (BIT1 | BIT16 | BIT18); }else{ PIN_SHARING_SEL_4BYTE_VAL &= ~(BIT15 | BIT17 | BIT20); PIN_SHARING_SEL_4BYTE_VAL |= (BIT1 | BIT16); } } EXPORT_SYMBOL(wmt_set_i2s_share_pin); static void i2s_init(int mode) { #ifdef DEBUG int ret; #endif int temp ; //unsigned int clock = 0; DPRINTK("i2s_ref = %d ", i2s_data->i2s.ref); if (++i2s_data->i2s.ref > 1) return; DBG_DETAIL(); if (!mode) { /* set to 24.576MHz */ auto_pll_divisor(DEV_I2S, CLK_ENABLE , 0, 0); auto_pll_divisor(DEV_I2S, SET_PLLDIV, 1, 24576); /*clock = auto_pll_divisor(DEV_I2S, GET_FREQ , 0, 0); info("%s : clock=%d \n" , __func__, clock);*/ /* Enable BIT4:ARFP clock, BIT3:ARF clock */ PMCEU_VAL |= (BIT4 | BIT3); /* Enable BIT2:AUD clock */ PMCE3_VAL |= BIT2; wmt_set_i2s_share_pin(); } /* connect DZDRQ8 to ADC0 FIFO, DZDRQ9 to DAC FIFO and DZDRQA to ADC1 FIFO */ DZDRQ8_CFG_VAL = 0x0; DZDRQ9_CFG_VAL = 0x1; DZDRQA_CFG_VAL = 0x2; DACCFG_VAL = 0x0; ADCCFG_VAL = 0x0; /* little endian, signed format, enable sample FIFO, 16bit sample, 2 channel */ ASMPFCFG_VAL = 0x52; /* assign ch0 to DAC#0_L, DAC#1_L, DAC#2_L, SPDIF_L, ch1 to DAC#0_R, DAC#1_R, DAC#2_R, SPDIF_R */ ASMPFCHCFG0_VAL = 0x10101010; /* assign ch0 to DAC#3_L, ch1 to DAC#3_R */ ASMPFCHCFG1_VAL = 0x10; /* select ADCDAT0, 16 bits mode, enable AADCFIFO and AADCITF */ AADCF0CFG_VAL = (AADCITF_ENABLE | AADCF16_ENABLE | AADCF_ENABLE); /* the sequence must be ARF_ADCCFG first then ARF_DGOCFG, finally ARF_DACCFG while slave mode, otherwise will generate noise when record function is active */ /* ADC slave mode, 48K sample rate, I2S mode */ ADCCFG_VAL = 0x2001; i2s_data->i2s.rate = 48000; i2s_data->i2s.channels = 2; i2s_data->i2s.format = SNDRV_PCM_FORMAT_S16_LE; #ifdef AUD_SPDIF_ENABLE /* B1: 0 -> PCM, 1 -> Bitstream B24 ~ B27: sample rate */ /* set ADGO channel status for 48K sample rate */ DGOCS0A_VAL = 0x02 << 24; DGOCS1A_VAL = 0x02 << 24; /* enable ADGOITF and ADGOCKGEN for 48K sample rate */ DGOCFG_VAL = 0x82; #endif /* enable ADACITF and ADACCKGEN for 44.1K sample rate, I2S mode */ DACCFG_VAL = 0x402001; if (audio_interface_mode == 1) { printk("<<<<<%s left justified!\n", __func__); //left justified bit20: 0->i2s, 1->l/r clock polarity DACCFG_VAL |= 0x1 << 20; //bit7:0 padding added to dac serial data output //0 = LJ //1 = I2S //others = RJ (depends on Bit 11:8) DACCFG_VAL &= ~(0x1<<0); } /* enable HD Audio clock for 48K sample rate or not*/ HDACKGEN_VAL = 0x12; #ifdef CONFIG_FB_WMT /* pass information of audio to HDMI Audio */ hd_audio_flag = vpp_set_audio(16, i2s_data->i2s.rate, i2s_data->i2s.channels); ASMPF2HDACHCFG_VAL = 0x76543210; #endif if (!hd_audio_flag) HDACKGEN_VAL = 0; /* audio peri reset */ temp = AUDPRFRST_VAL; #ifdef AUD_SPDIF_ENABLE temp |= (ASMPF_RESET | DACITF_RESET | ADCITF_RESET | DGOITF_RESET); #else temp |= (ASMPF_RESET | DACITF_RESET | ADCITF_RESET); #endif AUDPRFRST_VAL = temp; /* request irq */ #ifdef CONFIG_WMT_I2S_INT ret = request_irq(i2s_data->i2s.irq, wmt_i2s_interrupt, SA_INTERRUPT, "wmt_alsa_vt1602", NULL); if (ret) printk("%s : unable to request IRQ \n" , __func__); #endif } static void i2s_exit(void) { DBG_DETAIL(); if (--i2s_data->i2s.ref) return; DPRINTK("Do i2s_exit "); #ifdef CONFIG_WMT_I2S_INT free_irq(i2s_data->i2s.irq, NULL); #endif /* Reset counter.*/ memset(&i2s_data->i2s.ints, 0, sizeof(struct i2s_ints_s)); return; } static int wmt_i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = substream->runtime; int stream_id = substream->pstr->stream; struct audio_stream_a *s = &i2s_data->s[0]; DBG_DETAIL(); s[stream_id].stream = substream; runtime->private_data = s; return 0; } static void wmt_i2s_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { DBG_DETAIL(); } static int wmt_i2s_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { int err = 0; int stream_id = substream->pstr->stream; DBG_DETAIL(); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { ASMPFCFG_VAL |= ASMPF_ENABLE; } else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { AADCF0CFG_VAL |= AADCF_ENABLE; } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { ASMPFCFG_VAL &= ~ASMPF_ENABLE; } else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { AADCF0CFG_VAL &= ~AADCF_ENABLE; } /* mod_timer(&i2s_data->delay_timer, jiffies + HZ / 100); */ break; default: err = -EINVAL; } return err; } static int wmt_i2s_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { DBG_DETAIL(); return 0; } static int wmt_i2s_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_pcm_runtime *runtime = substream->runtime; unsigned int channel, byte; int stream_id = substream->pstr->stream; #ifdef CONFIG_SND_OSSEMUL //info("oss.rate=%d, oss.channels=%d", runtime->oss.rate, runtime->oss.channels); #else DBG_DETAIL(); #endif byte = (runtime->sample_bits)/8; channel = runtime->channels; DPRINTK("snd_wmt_alsa_prepare byte = %d, channels = %d", byte, runtime->channels); if ((runtime->rate != i2s_data->i2s.rate) || (runtime->format != i2s_data->i2s.format) || (runtime->channels != i2s_data->i2s.channels) || (stream_id != i2s_data->stream_id)) { info("*** stream_id=%d, rate=%d, format=0x%x channels=%d ***", stream_id, runtime->rate, runtime->format, runtime->channels); i2s_data->i2s.format = runtime->format; i2s_data->i2s.channels = runtime->channels; i2s_data->stream_id = stream_id; } else { return 0; } i2s_disable(); /* format setting */ if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { /* little or big endian check */ switch (runtime->format) { case SNDRV_PCM_FORMAT_S16_BE: case SNDRV_PCM_FORMAT_U16_BE: case SNDRV_PCM_FORMAT_S24_BE: case SNDRV_PCM_FORMAT_U24_BE: case SNDRV_PCM_FORMAT_S32_BE: case SNDRV_PCM_FORMAT_U32_BE: ASMPFCFG_VAL |= ASMPF_EXCH_ENDIAN; break; default: ASMPFCFG_VAL &= ~ASMPF_EXCH_ENDIAN; break; } /* unsigned or signed check */ switch (runtime->format) { case SNDRV_PCM_FORMAT_U8: case SNDRV_PCM_FORMAT_U16_LE: case SNDRV_PCM_FORMAT_U16_BE: case SNDRV_PCM_FORMAT_U24_LE: case SNDRV_PCM_FORMAT_U24_BE: case SNDRV_PCM_FORMAT_U32_LE: case SNDRV_PCM_FORMAT_U32_BE: ASMPFCFG_VAL |= ASMPF_EXCH_FMT; break; default: ASMPFCFG_VAL &= ~ASMPF_EXCH_FMT; break; } /* sample quantization check */ ASMPFCFG_VAL &= ~(BIT4 | BIT5); switch (runtime->format) { case SNDRV_PCM_FORMAT_S8: case SNDRV_PCM_FORMAT_U8: ASMPFCFG_VAL |= ASMPF_8BIT_SMP; break; case SNDRV_PCM_FORMAT_S16_LE: case SNDRV_PCM_FORMAT_S16_BE: case SNDRV_PCM_FORMAT_U16_LE: case SNDRV_PCM_FORMAT_U16_BE: ASMPFCFG_VAL |= ASMPF_16BIT_SMP; break; case SNDRV_PCM_FORMAT_S32_LE: case SNDRV_PCM_FORMAT_S32_BE: case SNDRV_PCM_FORMAT_U32_LE: case SNDRV_PCM_FORMAT_U32_BE: ASMPFCFG_VAL |= ASMPF_32BIT_SMP; break; case SNDRV_PCM_FORMAT_S24_LE: case SNDRV_PCM_FORMAT_S24_BE: case SNDRV_PCM_FORMAT_U24_LE: case SNDRV_PCM_FORMAT_U24_BE: info("*** Not Supported: fmt=24Bit ***"); default: break; } /* channel number check */ ASMPFCFG_VAL &= ~(BIT0 | BIT1 | BIT2 | BIT3); ASMPFCFG_VAL |= runtime->channels; wmt_i2s_ch_config(); info("Prepare: CHCFG0=0x%x, HDACHCFG=0x%x", ASMPFCHCFG0_VAL, ASMPF2HDACHCFG_VAL); } /* sample rate setting */ #ifdef CONFIG_SND_OSSEMUL if (runtime->oss.rate) { i2s_sample_rate(runtime->oss.rate); } else { i2s_sample_rate(runtime->rate); } #else i2s_sample_rate(runtime->rate); #endif i2s_enable(); /* printk("avail_max=%d, rate=%d, channels=%d, period_size=%d, periods=%d, buffer_size=%d, tick_time=%d, \ min_align=%d, byte_align=%d, frame_bits=%d, sample_bits=%d, sleep_min=%d, xfer_align=%d, boundary=%d\n", runtime->avail_max, runtime->rate, runtime->channels, runtime->period_size, runtime->periods, runtime->buffer_size, runtime->tick_time, runtime->min_align, runtime->byte_align, runtime->frame_bits, runtime->sample_bits, runtime->sleep_min, runtime->xfer_align, runtime->boundary); */ return 0; } /* * This must be called before _set_clkdiv and _set_sysclk since McBSP register * cache is initialized here */ static int wmt_i2s_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { DBG_DETAIL(); return 0; //add rambo 2013-3-13 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: break; default: /* Unsupported data format */ return -EINVAL; } switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: break; case SND_SOC_DAIFMT_CBM_CFM: break; default: return -EINVAL; } /* Set bit clock (CLKX/CLKR) and FS polarities */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: /* * Normal BCLK + FS. * FS active low. TX data driven on falling edge of bit clock * and RX data sampled on rising edge of bit clock. */ break; case SND_SOC_DAIFMT_NB_IF: break; case SND_SOC_DAIFMT_IB_NF: break; case SND_SOC_DAIFMT_IB_IF: break; default: return -EINVAL; } return 0; } #ifdef CONFIG_PM static int wmt_i2s_suspend(struct snd_soc_dai *cpu_dai) { printk("%s!\n", __func__); DBG_DETAIL(); i2s_data->i2s.ref = 0; #if 1 if (gpio_pa >= 0) { gpio_direction_output(gpio_pa, !gpio_active); } #endif return 0; } static int wmt_i2s_resume(struct snd_soc_dai *cpu_dai) { printk("%s!\n", __func__); DBG_DETAIL(); i2s_init(1); wmt_i2s_ch_config(); #if 1 if (gpio_pa >= 0) { gpio_direction_output(gpio_pa, gpio_active); mdelay(50); } #endif info("Resume: CHCFG0=0x%x, HDACHCFG=0x%x", ASMPFCHCFG0_VAL, ASMPF2HDACHCFG_VAL); return 0; } #else #define wmt_i2s_suspend NULL #define wmt_i2s_resume NULL #endif static struct snd_soc_dai_ops wmt_i2s_dai_ops = { .startup = wmt_i2s_dai_startup, .prepare = wmt_i2s_prepare, .shutdown = wmt_i2s_dai_shutdown, .trigger = wmt_i2s_dai_trigger, .hw_params = wmt_i2s_dai_hw_params, .set_fmt = wmt_i2s_dai_set_dai_fmt, }; struct snd_soc_dai_driver wmt_i2s_dai = { .suspend = wmt_i2s_suspend, .resume = wmt_i2s_resume, .playback = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_FLOAT, }, .capture = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .ops = &wmt_i2s_dai_ops, }; static int wmt_i2s_probe(struct platform_device *pdev) { int ret; char buf[64]; int varlen = 64; DBG_DETAIL(); ret = wmt_getsyspara("wmt.audio.pcm", buf, &varlen); if (ret == 0) { sscanf(buf, "%d", &wmt_pdm_module_enable); } ret = wmt_getsyspara("wmt.audio.i2s", buf, &varlen); if (ret == 0) { if (!strncmp(buf, "wm8994", strlen("wm8994"))) wmt_codec_wm8994 = 1; } memset(buf, 0, sizeof(buf)); ret = wmt_getsyspara("wmt.audio.codechdmi", buf, &varlen); if (ret == 0) { sscanf(buf, "%d", &i2s_data[0].HDMI_and_DAC0); printk("<<<%s codec and hdmi:%d\n", __FUNCTION__, i2s_data[0].HDMI_and_DAC0); } memset(buf, 0, sizeof(buf)); //0:i2sdacdat0(usually use i2s function!!) 1:i2sdacdat1 2:i2sdacdat2 3:i2sdacdat3 ret = wmt_getsyspara("wmt.audio.dacdat.gpio", buf, &varlen); if (ret == 0) { sscanf(buf, "%d", &i2sdacdat_gpio); printk("<<<%s i2sdacdat_gpio:%d\n", __FUNCTION__, i2sdacdat_gpio); } memset(buf, 0, sizeof(buf)); ret = wmt_getsyspara("wmt.audio.interface.mode", buf, &varlen); if (ret == 0) { // 0:i2s 1:left justified 2:right justified sscanf(buf, "%d", &audio_interface_mode); printk("<<<%s audio_interface_mode:%d\n", __FUNCTION__, audio_interface_mode); } memset(buf, 0, sizeof(buf)); ret = wmt_getsyspara("wmt.audio.pa", buf, &varlen); if (ret == 0) { // gpio nr:active---> 1:0 sscanf(buf, "%d:%d", &gpio_pa, &gpio_active); } printk("%s audio pa:%d:%d\n", __FUNCTION__, gpio_pa, gpio_active); if (gpio_pa >= 0) { ret = gpio_request(gpio_pa, "audio pa"); if (ret) { printk("%s gpio %d request error!\n", __func__, gpio_pa); return ret; } #if 0 if (gpio_active) wmt_gpio_setpull(gpio_pa, WMT_GPIO_PULL_DOWN); else wmt_gpio_setpull(gpio_pa, WMT_GPIO_PULL_UP); msleep(10); gpio_direction_output(gpio_pa, !gpio_active); //???pop??? printk("%s shutdown pa!\n", __func__); msleep(100); #endif init_timer(&pa_timer); pa_timer.function = pa_timer_handler; } i2s_data->s[0].dma_cfg = dma_device_cfg_table[AHB1_AUD_DMA_REQ_1]; i2s_data->s[1].dma_cfg = dma_device_cfg_table[AHB1_AUD_DMA_REQ_0]; /*init i2s controller*/ i2s_data->i2s.init(0); spin_lock_init(&i2s_data->s[0].dma_lock); spin_lock_init(&i2s_data->s[1].dma_lock); /*init_timer(&i2s_data->delay_timer); i2s_data->delay_timer.function = delay_timer_handler;*/ /* register with the ASoC layers */ ret = snd_soc_register_dai(&pdev->dev, &wmt_i2s_dai); if (ret) { pr_err("Failed to register DAI: %d\n", ret); return ret; } if (gpio_pa >= 0) { mod_timer(&pa_timer, jiffies+msecs_to_jiffies(10000)); } return 0; } static int __devexit wmt_i2s_remove(struct platform_device *pdev) { DBG_DETAIL(); snd_soc_unregister_dai(&pdev->dev); return 0; } static void wmt_i2s_plat_shutdown(struct platform_device *pdev) { printk("%s!\n", __func__); if (gpio_pa >= 0) { gpio_direction_output(gpio_pa, !gpio_active); } } static struct platform_driver wmt_i2s_driver = { .probe = wmt_i2s_probe, .remove = __devexit_p(wmt_i2s_remove), //.suspend = wmt_i2s_plat_suspend, //.resume = wmt_i2s_plat_resume, .shutdown = wmt_i2s_plat_shutdown, .driver = { .name = "wmt-i2s", .owner = THIS_MODULE, }, }; static int __init wmt_i2s_init(void) { DBG_DETAIL(); return platform_driver_register(&wmt_i2s_driver); } static void __exit wmt_i2s_exit(void) { DBG_DETAIL(); platform_driver_unregister(&wmt_i2s_driver); } module_init(wmt_i2s_init); module_exit(wmt_i2s_exit); MODULE_AUTHOR("WonderMedia Technologies, Inc."); MODULE_DESCRIPTION("WMT [ALSA SoC] driver"); MODULE_LICENSE("GPL");