diff options
author | Kevin | 2014-11-15 10:00:36 +0800 |
---|---|---|
committer | Kevin | 2014-11-15 10:00:36 +0800 |
commit | 9d40ac5867b9aefe0722bc1f110b965ff294d30d (patch) | |
tree | de942df665fac4bac0d9cb7ae86910fe937b0c1a /ANDROID_3.4.5/sound/soc/wmt | |
parent | 392e8802486cb573b916e746010e141a75f507e6 (diff) | |
download | FOSSEE-netbook-kernel-source-9d40ac5867b9aefe0722bc1f110b965ff294d30d.tar.gz FOSSEE-netbook-kernel-source-9d40ac5867b9aefe0722bc1f110b965ff294d30d.tar.bz2 FOSSEE-netbook-kernel-source-9d40ac5867b9aefe0722bc1f110b965ff294d30d.zip |
add via modify part source code for wm8880 4.4 kitkat
Diffstat (limited to 'ANDROID_3.4.5/sound/soc/wmt')
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/Kconfig | 65 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/Makefile | 12 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-i2s.c | 1105 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.c | 419 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.h | 122 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.c | 597 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.h | 35 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.c | 767 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.h | 46 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-soc.c | 321 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt-soc.h | 53 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.c | 259 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.h | 55 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.c | 101 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.h | 33 | ||||
-rwxr-xr-x | ANDROID_3.4.5/sound/soc/wmt/wmt_wm8994.c | 304 |
16 files changed, 4294 insertions, 0 deletions
diff --git a/ANDROID_3.4.5/sound/soc/wmt/Kconfig b/ANDROID_3.4.5/sound/soc/wmt/Kconfig new file mode 100755 index 00000000..344ec73d --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/Kconfig @@ -0,0 +1,65 @@ +config SND_WMT_SOC + bool "SoC Audio for the WMT chip" + depends on ARCH_WMT && SND && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the WMT AC97, I2S interface. You will also need + to select the audio interfaces to support below. + +# +# WMT ALSA I2S driver +# +config SND_WMT_SOC_I2S + bool "SoC I2S Audio support for WMT" + depends on SND_WMT_SOC + ---help--- + Say Y or M if you want to add support for codecs attached to + the WMT I2S interface. + +config SND_WMT_SOC_PDM + bool "SoC PDM Audio Interface support for WMT" + depends on SND_WMT_SOC + ---help--- + Say Y or M if you want to add support for codecs attached to + the WMT PDM interface. + +config I2S_HW_DAC + bool "HW_DAC" + depends on SND_WMT_SOC_I2S + select SND_SOC_HWDAC + ---help--- + Say Y here if you want to use HW DAC. + +config I2S_CODEC_VT1602 + bool "VT1602" + depends on SND_WMT_SOC_I2S + select SND_SOC_VT1602 + ---help--- + Say Y here if you want to use VT1602 as the I2S Codec. + +config I2S_CODEC_WM8900 + bool "WM8900" + depends on SND_WMT_SOC_I2S + select SND_SOC_WM8900 + ---help--- + Say Y here if you want to use WM8900 as the I2S Codec. + +config I2S_CODEC_WM8994 + bool "WM8994" + depends on SND_WMT_SOC_I2S + select SND_SOC_WM8994 + ---help--- + Say Y here if you want to use WM8994 as the I2S Codec. + +config I2S_CODEC_VT1603 + bool "VT1603" + depends on SND_WMT_SOC_I2S + select SND_SOC_VT1603 + ---help--- + Say Y here if you want to use VT1603 as the I2S Codec. + +config ECHO_CANCELLATION_FM34 + tristate "FM34" + select SND_SOC_WMT_FM34 + ---help--- + Say Y here if you want to use fm34 to echo cancallation. diff --git a/ANDROID_3.4.5/sound/soc/wmt/Makefile b/ANDROID_3.4.5/sound/soc/wmt/Makefile new file mode 100755 index 00000000..bf4a07ef --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/Makefile @@ -0,0 +1,12 @@ +# +## Makefile for ALSA WMT +# +# +# WMT SOC Support +snd-soc-wmt-i2s-objs := wmt-i2s.o wmt_swmixer.o +snd-soc-wmt-objs := wmt-soc.o wmt-pcm.o wmt_wm8994.o wmt_hwdep.o + +snd-soc-wmt-pdm-objs := wmt-pcm-controller.o wmt-pcm-dma.o +obj-$(CONFIG_SND_WMT_SOC_I2S) += snd-soc-wmt-i2s.o +obj-$(CONFIG_SND_WMT_SOC) += snd-soc-wmt.o +obj-$(CONFIG_SND_WMT_SOC_PDM) += snd-soc-wmt-pdm.o diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-i2s.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-i2s.c new file mode 100755 index 00000000..643d25e2 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-i2s.c @@ -0,0 +1,1105 @@ +/*++ + * 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <linux/gpio.h> +#include <mach/wmt_iomux.h> +#include <linux/suspend.h> + +#include <mach/hardware.h> +#include <asm/dma.h> +#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"); + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.c new file mode 100755 index 00000000..dccb82c6 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.c @@ -0,0 +1,419 @@ +/*++ + * linux/sound/soc/wmt/wmt-pdm-if.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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <mach/hardware.h> +#include <asm/dma.h> +#include "wmt-soc.h" +#include "wmt-pcm-controller.h" + +#define PCM_IS_MASTER_MODE +#define NULL_DMA ((dmach_t)(-1)) + +static int wmt_pcm_module_enable = 0; +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +static struct audio_stream_a wmt_pcm_controller_data[] = { + { + .id = "WMT PCM out", + .stream_id = SNDRV_PCM_STREAM_PLAYBACK, + .dmach = NULL_DMA, + .dma_dev = PCM_TX_DMA_REQ, + /*.dma_cfg = dma_device_cfg_table[I2S_TX_DMA_REQ],*/ + }, + { + .id = "WMT PCM in", + .stream_id = SNDRV_PCM_STREAM_CAPTURE, + .dmach = NULL_DMA, + .dma_dev = PCM_RX_DMA_REQ, + /*.dma_cfg = dma_device_cfg_table[I2S_RX_DMA_REQ],*/ + }, +}; + +struct wmt_pcm_controller +{ + int irq_no; + int pcm_clk_src; + int pcm_enable; +}; + +static struct wmt_pcm_controller wmt_pcm_controller; + +static irqreturn_t +wmt_pcm_controller_irq_handler(int irq, void *dev_id) +{ + if (PCMSR_VAL & PCMSR_TXUND) { + printk("-->PCMSR_TXUND\n"); + } + else if (PCMSR_VAL & PCMSR_RXOVR) { + printk("-->PCMSR_RXOVR\n"); + } + + PCMSR_VAL = 0x7F; //write clear all intr + return IRQ_HANDLED; +} + +static void wmt_pcm_controller_enable(void) +{ + PCMCR_VAL |= (PCMCR_PCM_ENABLE | PCMCR_DMA_EN); +} + +static void wmt_pcm_controller_disable(void) +{ + PCMCR_VAL &= ~(PCMCR_PCM_ENABLE | PCMCR_DMA_EN); +} + +static int wmt_pcm_controller_init(void) +{ + wmt_pcm_controller.irq_no = IRQ_PCM; + + // Before control pcm-module, enable pcm clock first + CLOCKEN(27); + + // set pcm_clk_source = 62464khz + //auto_pll_divisor(DEV_PCM0, CLK_ENABLE, 0, 0); + //auto_pll_divisor(DEV_PCM0, SET_PLLDIV, 1, 83333); + + wmt_pcm_controller.pcm_clk_src = auto_pll_divisor(DEV_PCM0, GET_FREQ, 0, 0); + wmt_pcm_controller.pcm_clk_src /= 1000; + wmt_pcm_controller.pcm_enable = 0; + printk("wmt_pcm_controller_init: pcm_clk_src=%d \n\r", wmt_pcm_controller.pcm_clk_src); + + // Note: you should config PIN_SHARING_SEL_4BYTE_VAL if using pcm function!!! Loon mark at 2013/4/10 + if (wmt_pcm_module_enable) { + printk("begin to configure pcm pin\n"); + /* disable GPIO and Pull Down mode */ + /* Bit1:I2SDACDAT1=PCMSYNC, Bit2:I2SDACDAT2=PCMCLK, Bit3:I2SDACDAT3=PCMIN, Bit4:I2SADCMCLK=PCMOUT */ + GPIO_CTRL_GP10_I2S_BYTE_VAL &= ~(BIT1 | BIT2 | BIT3 | BIT4); + GPIO_CTRL_GP11_I2S_BYTE_VAL &= ~(BIT1); + /*disable pull enable*/ + PULL_EN_GP10_I2S_BYTE_VAL &= ~(BIT1 | BIT2 | BIT3 | BIT4); + PULL_EN_GP11_I2S_BYTE_VAL &= ~(BIT1); + + /* set to pcm mode */ + // select PCMMCLK[bit0], PCMSYNC[bit17:16], PCMCLK[19:18], PCMIN[20], PCMOUT[22:21] + //GPIO_PIN_SHARING_SEL_4BYTE_VAL &= ~(BIT21); + GPIO_PIN_SHARING_SEL_4BYTE_VAL |= (BIT15 | BIT16 | BIT17 | BIT18 | BIT19 |BIT20); + } + + // set pcm control register + PCMCR_VAL |= (PCMCR_TXFF_RST | PCMCR_RXFF_RST); + //PCMCR_VAL |= 0x00880000; // TX/RX Fifo Threshold A + PCMCR_VAL &= ~(PCMCR_BCLK_SEL); + PCMCR_VAL &= ~PCMCR_SLAVE; // master mode + PCMCR_VAL |= PCMCR_SYNC_MODE;//short frame sync + + // set pcm format register + PCMDFCR_VAL = 0; + PCMDFCR_VAL |= (PCMDFCR_WR_AL | PCMDFCR_TX_AL | PCMDFCR_RX_AL | PCMDFCR_RD_AL); + PCMDFCR_VAL |= (PCMDFCR_TX_SZ_14 | PCMDFCR_RX_SZ_14); + //PCMDFCR_VAL |= (PCMDFCR_TX_SZ_08 | PCMDFCR_RX_SZ_08); + + // + // request irq + // + /*if (request_irq(wmt_pcm_controller.irq_no, &wmt_pcm_controller_irq_handler, IRQF_DISABLED, "wmt_pcm_controller", NULL)){ + printk(KERN_ERR "PCM_IRQ Request Failed!\n"); + } + PCMCR_VAL |= (PCMCR_IRQ_EN | PCMCR_TXUND_EN | PCMCR_RXOVR_EN); + */ + printk("fun:%s,line:%d\n",__func__,__LINE__); + return 0 ; +} + + +static int wmt_pcm_controller_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 = &wmt_pcm_controller_data[0]; + //dump_stack(); + s[stream_id].stream = substream; + runtime->private_data = s; + + return 0; +} + +static void wmt_pcm_controller_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + //dump_stack(); +} + +static int wmt_pcm_controller_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int err = 0; + int stream_id = substream->pstr->stream; + //dump_stack(); + 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) { + PCMCR_VAL |= PCMCR_TXFF_RST; // reset txfifo + PCMDFCR_VAL |= PCMDFCR_TXFM_EN; + } + else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + PCMCR_VAL |= PCMCR_RXFF_RST; // reset rxfifo + PCMDFCR_VAL |= PCMDFCR_RXFM_EN; + } + //wmt_pcm_controller_enable(); + wmt_pcm_controller.pcm_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) { + PCMDFCR_VAL &= ~PCMDFCR_TXFM_EN; + } + else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + PCMDFCR_VAL &= ~PCMDFCR_RXFM_EN; + } + //wmt_pcm_controller_disable(); + wmt_pcm_controller.pcm_enable--; + break; + default: + err = -EINVAL; + break; + } + + if (wmt_pcm_controller.pcm_enable) + wmt_pcm_controller_enable(); + else + wmt_pcm_controller_disable(); + + return err; +} + +static int wmt_pcm_controller_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + //dump_stack(); + return 0; +} + +static int wmt_pcm_controller_dai_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; + + byte = (runtime->sample_bits)/8; + channel = runtime->channels; + + printk(KERN_INFO "wmt_pcm_controller_dai_prepare byte = %d, channels = %d\n", byte, runtime->channels); + + /* format setting */ + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + /* little or big endian check */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + break; + } + + /* channel number check */ + switch (runtime->channels) { + case 1: + break; + case 2: + break; + default: + break; + } + } + else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + /* little or big endian check */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + break; + } + + /* channel number check */ + switch (runtime->channels) { + case 1: + break; + case 2: + break; + default: + break; + } + } + + switch (runtime->rate) { + case 16000: + PCMDIVR_VAL &= ~PCMCLK_DIV_MASK; + PCMDIVR_VAL |= (wmt_pcm_controller.pcm_clk_src / PCMCLK_256K); + break; + case 8000: + PCMDIVR_VAL &= ~PCMCLK_DIV_MASK; + PCMDIVR_VAL |= (wmt_pcm_controller.pcm_clk_src / PCMCLK_128K); + break; + default : + printk(KERN_ERR "not supported fs: %d \n\r", runtime->rate); + break; + } + + return 0; +} + +/* + * This must be called before _set_clkdiv and _set_sysclk since McBSP register + * cache is initialized here + */ +static int wmt_pcm_controller_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + return 0; +} + +#ifdef CONFIG_PM +static int wmt_pcm_controller_suspend(struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int wmt_pcm_controller_resume(struct snd_soc_dai *cpu_dai) +{ + int ret = wmt_pcm_controller_init(); + if (ret) { + pr_err("Failed to init pcm module: %d\n", ret); + return ret; + } + return 0; +} +#else +#define wmt_pcm_controller_suspend NULL +#define wmt_pcm_controller_resume NULL +#endif + +static struct snd_soc_dai_ops wmt_pcm_controller_dai_ops = { + .startup = wmt_pcm_controller_dai_startup, + .prepare = wmt_pcm_controller_dai_prepare, + .shutdown = wmt_pcm_controller_dai_shutdown, + .trigger = wmt_pcm_controller_dai_trigger, + .hw_params = wmt_pcm_controller_dai_hw_params, + .set_fmt = wmt_pcm_controller_dai_set_dai_fmt, +}; + +struct snd_soc_dai_driver wmt_pcm_controller_dai = { + .suspend = wmt_pcm_controller_suspend, + .resume = wmt_pcm_controller_resume, + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &wmt_pcm_controller_dai_ops, +}; + +static int wmt_pcm_controller_probe(struct platform_device *pdev) +{ + int ret = 0; + char buf[64]; + int varlen = 64; + + ret = wmt_getsyspara("wmt.audio.pcm", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", &wmt_pcm_module_enable); + } + + ret = wmt_pcm_controller_init(); + if (ret) { + pr_err("Failed to init pcm module: %d\n", ret); + return ret; + } + + /* register with the ASoC layers */ + ret = snd_soc_register_dai(&pdev->dev, &wmt_pcm_controller_dai); + if (ret) { + pr_err("Failed to register DAI: %d\n", ret); + return ret; + } + + wmt_pcm_controller_data[0].dma_cfg = dma_device_cfg_table[PCM_TX_DMA_REQ]; + wmt_pcm_controller_data[1].dma_cfg = dma_device_cfg_table[PCM_RX_DMA_REQ]; + + spin_lock_init(&wmt_pcm_controller_data[0].dma_lock); + spin_lock_init(&wmt_pcm_controller_data[1].dma_lock); + + return 0; +} + +static int __devexit wmt_pcm_controller_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver wmt_pcm_controller_driver = { + .probe = wmt_pcm_controller_probe, + .remove = __devexit_p(wmt_pcm_controller_remove), + .driver = { + .name = "wmt-pcm-controller", + .owner = THIS_MODULE, + }, +}; + + +static int __init wmt_pcm_controller_module_init(void) +{ + return platform_driver_register(&wmt_pcm_controller_driver); +} + +static void __exit wmt_pcm_controller_module_exit(void) +{ + platform_driver_unregister(&wmt_pcm_controller_driver); +} + +module_init(wmt_pcm_controller_module_init); +module_exit(wmt_pcm_controller_module_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC] driver"); +MODULE_LICENSE("GPL"); + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.h b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.h new file mode 100755 index 00000000..58e94f23 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.h @@ -0,0 +1,122 @@ +/*++ +linux/include/asm-arm/arch-wmt/wmt_pcm.h + +Copyright (c) 2008 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 <http://www.gnu.org/licenses/>. + +WonderMedia Technologies, Inc. +10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ + +/* Be sure that virtual mapping is defined right */ + +#ifndef __WMT_PCM_CONTROLLER_H +#define __WMT_PCM_CONTROLLER_H + +/* + * Address + */ +#define PCM_CR_ADDR (0x0000+PCM_BASE_ADDR) +#define PCM_SR_ADDR (0x0004+PCM_BASE_ADDR) +/* Reserved 0x0008 ~ 0x000F */ +#define PCM_DFCR_ADDR (0x0008+PCM_BASE_ADDR) +#define PCM_DIVR_ADDR (0x000C+PCM_BASE_ADDR) +/* Reserved 0x0020 ~ 0x007F */ +#define PCM_TFIFO_ADDR (0x0010+PCM_BASE_ADDR) +#define PCM_TFIFO_1_ADDR (0x0014+PCM_BASE_ADDR) + +#define PCM_RFIFO_ADDR (0x0030+PCM_BASE_ADDR) +#define PCM_RFIFO_1_ADDR (0x0034+PCM_BASE_ADDR) +/* Reserved 0x0100 ~ 0xFFFF */ + +/* + * Value + */ +#define PCMCR_VAL (REG32_VAL(PCM_CR_ADDR)) +#define PCMSR_VAL (REG32_VAL(PCM_SR_ADDR)) +#define PCMDFCR_VAL (REG32_VAL(PCM_DFCR_ADDR)) +#define PCMDIVR_VAL (REG32_VAL(PCM_DIVR_ADDR)) +#define GPIO_PIN_SHARING_SEL_4BYTE_VAL (REG32_VAL(SHARE_PIN_SELEC)) + +#define PCMCLK_DIV_MASK 0x7FF +#define PLL_B_DIVF_MASK 0xFF0000 +#define PLL_B_DIVR_MASK 0x1F00 +#define PLL_B_DIVQ_MASK 0x07 +#define OSC_25M 25000 +#define PCMCLK_128K 128 +#define PCMCLK_256K 256 + + +//============================================================================= +// +// PCMCR PCM Control Register +// +//============================================================================= +#define PCMCR_PCM_ENABLE (BIT0) /* PCM Interface Enable */ +#define PCMCR_SLAVE (BIT1) /* Master/Slave Mode */ + +#define PCMCR_BCLK_SEL (BIT3) /* PCM_CLK Select */ +#define PCMCR_SYNC_MODE (BIT4) /* Frame Sync Mode: 0-long, 1-Short */ +#define PCMCR_DMA_EN (BIT5) /* DMA Enable */ +#define PCMCR_SYNC_OFF (BIT6) /* Sync Disable */ +#define PCMCR_MUTE (BIT7) /* Mute Enable */ +#define PCMCR_IRQ_EN (BIT8) /* Interrupt Enable */ +#define PCMCR_DMA_IRQ_SEL (BIT9) /* Threshold DMA/IRQ Select */ +#define PCMCR_RXFE_EN (BIT10) /* RX FIFO Empty Interrupt Enable */ +#define PCMCR_RXFF_EN (BIT11) /* RX FIFO Full Interrupt Enable */ +#define PCMCR_RXOVR_EN (BIT12) /* RX FIFO Overrun Interrupt Enable */ +#define PCMCR_TXFE_EN (BIT13) /* TX FIFO Empty Interrupt Enable */ +#define PCMCR_TXUND_EN (BIT14) /* TX FIFO Underrun Interrupt Enable */ + +#define PCMCR_TXFF_RST (BIT24) /* TX FIFO Reset */ +#define PCMCR_RXFF_RST (BIT25) /* RX FIFO Reset */ + + +//============================================================================= +// +// PCMSR PCM Status Register +// +//============================================================================= +#define PCMSR_RXFE (BIT0) /* RX FIFO Empty Status */ +#define PCMSR_RXFF (BIT1) /* RX FIFO Full Status */ +#define PCMSR_RXOVR (BIT2) /* RX FIFO Overrun Status */ +#define PCMSR_TXFE (BIT3) /* TX FIFO Empty Status */ +#define PCMSR_TXUND (BIT4) /* TX FIFO Underrun Status */ +#define PCMSR_TXFAE (BIT5) /* TX FIFO Almost Empty Status */ +#define PCMSR_RXFAF (BIT6) /* RX FIFO Almost Full Status */ + + +//============================================================================= +// +// PCMDFCR PCM Data Format Control Register +// +//============================================================================= +#define PCMDFCR_TXFM_EN (BIT0) /* TX Data Format Enable */ +#define PCMDFCR_TX_SZ_13 0x00 /* TX Input Data Size 13 bits wide */ +#define PCMDFCR_TX_SZ_14 0x02 /* TX Input Data Size 14 bits wide */ +#define PCMDFCR_TX_SZ_08 0x04 /* TX Input Data Size 8 bits wide */ +#define PCMDFCR_WR_AL (BIT3) /* TX Write Data Alignment Setup */ +#define PCMDFCR_TX_AL (BIT4) /* PCM_OUT Alignment Control */ +#define PCMDFCR_TX_PAD (BIT5) /* PCM_OUT Padding Control */ +#define PCMDFCR_TX_MSB (BIT6) /* PCM_OUT First Bit Select */ + +#define PCMDFCR_RXFM_EN (BIT8) /* RX Data Format Enable */ +#define PCMDFCR_RX_SZ_13 0x0000 /* RCM_IN Data Size 13 bits wide */ +#define PCMDFCR_RX_SZ_14 0x0200 /* RCM_IN Data Size 14 bits wide */ +#define PCMDFCR_RX_SZ_08 0x0400 /* RCM_IN Data Size 8 bits wide */ +#define PCMDFCR_RX_AL (BIT11) /* RCM_IN Data Alignment Setup */ +#define PCMDFCR_RD_AL (BIT12) /* PX FIFO Alignment Control */ +#define PCMDFCR_RX_PAD (BIT13) /* PX FIFO Padding Control */ +#define PCMDFCR_RX_MSB (BIT14) /* RX FIFO First Bit Select */ + + +#endif /* __WMT_PCM_CONTROLLER_H */ diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.c new file mode 100755 index 00000000..27a66bea --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.c @@ -0,0 +1,597 @@ +/*++ + * linux/sound/soc/wmt/wmt-pcm.c + * WonderMedia 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> + +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/delay.h> + +#include <asm/dma.h> +#include "wmt-pcm-dma.h" +#include "wmt-soc.h" + +#define NULL_DMA ((dmach_t)(-1)) + +/* + * Debug + */ +#define AUDIO_NAME "WMT_PCM_DMA" +//#define WMT_PCM_DEBUG 1 +//#define WMT_PCM_DEBUG_DETAIL 1 + +#ifdef WMT_PCM_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_PCM_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) + +static const struct snd_pcm_hardware wmt_pcm_dma_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 16000, + .period_bytes_min = 32, + .period_bytes_max = 4 * 1024, + .periods_min = 1, + .periods_max = 32, + .buffer_bytes_max = 16 * 1024, +}; + +static int audio_dma_free(struct audio_stream_a *s); + +/* + * Main dma routine, requests dma according where you are in main alsa buffer + */ +static void audio_process_dma(struct audio_stream_a *s) +{ + struct snd_pcm_substream *substream = s->stream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + dma_addr_t dma_base; + int ret = 0; + + DPRINTK("s: %d, dmach: %d. active: %d", (int)s, s->dmach, s->active); + + if (s->active) { + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + + if (dma_size > MAX_DMA_SIZE) + dma_size = CUT_DMA_SIZE; + offset = dma_size * s->period; + + dma_base = __virt_to_phys((dma_addr_t)runtime->dma_area); + + if ((runtime->channels == 2 || runtime->channels == 1) && + (runtime->format == SNDRV_PCM_FORMAT_S16_LE)) { + ret = wmt_start_dma(s->dmach, runtime->dma_addr + offset, 0, dma_size); + } + + if (ret) { + printk(KERN_ERR "audio_process_dma: cannot queue DMA buffer (%i) \n", ret); + return; + } + + s->period++; + s->period %= runtime->periods; + s->periods++; + s->offset = offset; + } +} + +/* + * This is called when dma IRQ occurs at the end of each transmited block + */ +static void audio_dma_callback(void *data) +{ + struct audio_stream_a *s = data; + + //DBG_DETAIL(); + + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + if (s->periods > 0) + s->periods--; + + audio_process_dma(s); + spin_unlock(&s->dma_lock); +} + +static int audio_dma_request(struct audio_stream_a *s, void (*callback) (void *)) +{ + int err; + err = 0; + + DBG_DETAIL(); + + //DPRINTK("s pointer: %d, dmach: %d, id: %s, dma_dev: %d", (int)s, s->dmach, s->id, s->dma_dev); + err = wmt_request_dma(&s->dmach, s->id, s->dma_dev, callback, s); + if (err < 0) + printk(KERN_ERR "Unable to grab audio dma 0x%x\n", s->dmach); + + return err; +} + +static void audio_setup_dma(struct audio_stream_a *s, int stream_id) +{ + struct snd_pcm_runtime *runtime = s->stream->runtime; + + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + /* From memory to device */ + switch (runtime->channels * runtime->format) { + case 1: + s->dma_cfg.DefaultCCR = PCM_TX_DMA_8BITS_CFG; /* setup 1 bytes*/ + break ; + case 2: + s->dma_cfg.DefaultCCR = PCM_TX_DMA_16BITS_CFG; /* setup 2 bytes*/ + break ; + case 4: + s->dma_cfg.DefaultCCR = PCM_TX_DMA_32BITS_CFG; /* setup 4 byte*/ + break ; + } + } + else { + /* From device to memory */ + switch (runtime->channels * runtime->format) { + case 1: + s->dma_cfg.DefaultCCR = PCM_RX_DMA_8BITS_CFG ; /* setup 1 bytes*/ + break ; + case 2: + s->dma_cfg.DefaultCCR = PCM_RX_DMA_16BITS_CFG ; /* setup 2 bytes*/ + break ; + case 4: + s->dma_cfg.DefaultCCR = PCM_RX_DMA_32BITS_CFG ; /* setup 4 byte*/ + break ; + } + } + + s->dma_cfg.ChunkSize = 1; + + wmt_setup_dma(s->dmach, s->dma_cfg) ; +} + +static int audio_dma_free(struct audio_stream_a *s) +{ + int err = 0; + DBG_DETAIL(); + wmt_free_dma(s->dmach); + s->dmach = NULL_DMA; + return err; +} + +/* + * this stops the dma and clears the dma ptrs + */ +static void audio_stop_dma(struct audio_stream_a *s) +{ + //dump_stack(); + unsigned long flags; + DBG_DETAIL(); + local_irq_save(flags); + s->active = 0; + s->period = 0; + s->periods = 0; + s->offset = 0; + wmt_stop_dma(s->dmach); + wmt_clear_dma(s->dmach); + local_irq_restore(flags); +} + +/* this may get called several times by oss emulation */ +static int wmt_pcm_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + //dump_stack(); + struct snd_pcm_runtime *runtime = substream->runtime; + int err = 0; + DBG_DETAIL(); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + return err; +} + +static int wmt_pcm_dma_hw_free(struct snd_pcm_substream *substream) +{ + DBG_DETAIL(); + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int wmt_pcm_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *prtd = runtime->private_data; + struct audio_stream_a *s = &prtd[stream_id]; + //dump_stack(); + DBG_DETAIL(); + + s->period = 0; + s->periods = 0; + s->offset = 0; + audio_setup_dma(s, stream_id); + + return 0; +} + +static int wmt_pcm_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *prtd = runtime->private_data; + struct audio_stream_a *s = &prtd[stream_id]; + int ret = 0; + + DPRINTK("Enter, cmd=%d", cmd); + //dump_stack(); + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->active = 1; + audio_process_dma(s); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + audio_stop_dma(s); + break; + default: + ret = -EINVAL; + } + spin_unlock(&s->dma_lock); + + return ret; +} + +static snd_pcm_uframes_t wmt_pcm_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *prtd = runtime->private_data; + int stream_id = substream->pstr->stream; + struct audio_stream_a *s = &prtd[stream_id]; + dma_addr_t ptr; + snd_pcm_uframes_t offset = 0; + //dump_stack(); + ptr = wmt_get_dma_pos(s->dmach); + + if ((runtime->channels == 1 || runtime->channels == 2) && (runtime->format == SNDRV_PCM_FORMAT_S16_LE)) { + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } + + if (offset >= runtime->buffer_size) + offset = 0; + + spin_lock(&s->dma_lock); + + if (s->periods > 0 && s->periods < 2) { + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + if (snd_pcm_playback_hw_avail(runtime) >= 2 * runtime->period_size) + audio_process_dma(s); + } + else { + if (snd_pcm_capture_hw_avail(runtime) >= 2* runtime->period_size) + audio_process_dma(s); + } + + } + spin_unlock(&s->dma_lock); + + return offset; +} + +static int wmt_pcm_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *s = runtime->private_data; + int ret; + + DBG_DETAIL(); + + if (!cpu_dai->active) { + audio_dma_request(&s[0], audio_dma_callback); + audio_dma_request(&s[1], audio_dma_callback); + } + //dump_stack(); + snd_soc_set_runtime_hwparams(substream, &wmt_pcm_dma_hardware); + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + +out: + return ret; +} + +static int wmt_pcm_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *s = runtime->private_data; + + DBG_DETAIL(); + //dump_stack(); + if (!cpu_dai->active) { + audio_dma_free(&s[0]); + audio_dma_free(&s[1]); + } + + return 0; +} + +static int wmt_pcm_dma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + DBG_DETAIL(); + //dump_stack(); + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops wmt_pcm_dma_ops = { + .open = wmt_pcm_dma_open, + .close = wmt_pcm_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = wmt_pcm_dma_hw_params, + .hw_free = wmt_pcm_dma_hw_free, + .prepare = wmt_pcm_dma_prepare, + .trigger = wmt_pcm_dma_trigger, + .pointer = wmt_pcm_dma_pointer, + .mmap = wmt_pcm_dma_mmap, +}; + +static u64 wmt_pcm_dma_dmamask = DMA_BIT_MASK(32); + +static int wmt_pcm_dma_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 = wmt_pcm_dma_hardware.buffer_bytes_max; + + DBG_DETAIL(); + + 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); + + DPRINTK("buf_area = %x, buf_addr = %x", (unsigned int)buf->area, buf->addr); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + + return 0; +} + +static void wmt_pcm_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + DBG_DETAIL(); + + 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 wmt_pcm_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret = 0; + + DBG_DETAIL(); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &wmt_pcm_dma_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 = wmt_pcm_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = wmt_pcm_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + wmt_pcm_dma_free_dma_buffers(pcm); + + return ret; +} + +#ifdef CONFIG_PM +static int wmt_pcm_dma_suspend(struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct audio_stream_a *prtd; + struct audio_stream_a *s; + + DBG_DETAIL(); + + if (!runtime) + return 0; + + prtd = runtime->private_data; + s = &prtd[SNDRV_PCM_STREAM_PLAYBACK]; + + if (s->active) { + udelay(5); + wmt_stop_dma(s->dmach); + /* + wmt_clear_dma(s->dmach); + audio_stop_dma(s); + */ + } + + s = &prtd[SNDRV_PCM_STREAM_CAPTURE]; + + if (s->active) { + udelay(5); + wmt_stop_dma(s->dmach); + /* + wmt_clear_dma(s->dmach); + audio_stop_dma(s); + */ + } + + return 0; +} + +static int wmt_pcm_dma_resume(struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct audio_stream_a *prtd; + struct audio_stream_a *s; + + DBG_DETAIL(); + + if (!runtime) + return 0; + + prtd = runtime->private_data; + s = &prtd[SNDRV_PCM_STREAM_PLAYBACK]; + audio_setup_dma(s, SNDRV_PCM_STREAM_PLAYBACK); + + if (s->active) { + wmt_resume_dma(s->dmach) ; + } + + s = &prtd[SNDRV_PCM_STREAM_CAPTURE]; + audio_setup_dma(s, SNDRV_PCM_STREAM_CAPTURE); + + if (s->active) { + wmt_resume_dma(s->dmach) ; + } + + return 0; +} +#else +#define wmt_pcm_dma_suspend NULL +#define wmt_pcm_dma_resume NULL +#endif + +static struct snd_soc_platform_driver wmt_soc_platform = { + .ops = &wmt_pcm_dma_ops, + .pcm_new = wmt_pcm_dma_new, + .pcm_free = wmt_pcm_dma_free_dma_buffers, + .suspend = wmt_pcm_dma_suspend, + .resume = wmt_pcm_dma_resume, +}; + +static int __devinit wmt_pcm_dma_platform_probe(struct platform_device *pdev) +{ + DBG_DETAIL(); + return snd_soc_register_platform(&pdev->dev, &wmt_soc_platform); +} + +static int __devexit wmt_pcm_dma_platform_remove(struct platform_device *pdev) +{ + DBG_DETAIL(); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver wmt_pcm_dma_driver = { + .driver = { + .name = "wmt-pcm-dma", + .owner = THIS_MODULE, + }, + + .probe = wmt_pcm_dma_platform_probe, + .remove = __devexit_p(wmt_pcm_dma_platform_remove), +}; + +module_platform_driver(wmt_pcm_dma_driver); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC/pcm dma] driver"); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.h b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.h new file mode 100755 index 00000000..88f246b2 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.h @@ -0,0 +1,35 @@ +/*++ + * linux/sound/soc/wmt/wmt-pcm.h + * WonderMedia 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#ifndef __WMT_PCM_DMA_H__ +#define __WMT_PCM_DMA_H__ + +struct wmt_pcm_dma_data { + char *name; /* stream identifier */ + int dma_req; /* DMA request line */ + unsigned long port_addr; /* transmit/receive register */ + struct dma_device_cfg_s *dma_cfg; +}; + +#endif /* __WMT_PDM_PCM_H__ */ diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.c new file mode 100755 index 00000000..848ea5a1 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.c @@ -0,0 +1,767 @@ +/*++ + * linux/sound/soc/wmt/wmt-pcm.c + * WonderMedia 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> + +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/delay.h> + +#include <asm/dma.h> +#include "wmt-pcm.h" +#include "wmt-soc.h" + +#define NULL_DMA ((dmach_t)(-1)) + +#define AUDIO_NAME "WMT_PCM" +//#define WMT_PCM_DEBUG 1 +//#define WMT_PCM_DEBUG_DETAIL 1 + +#ifdef WMT_PCM_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_PCM_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) + +static const struct snd_pcm_hardware wmt_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 96000, + .period_bytes_min = 4*1024,//32, + .period_bytes_max = 8*1024,//4 * 1024, + .periods_min = 1,//2, + .periods_max = 16,//128, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32,// +}; + +struct wmt_runtime_data { + spinlock_t lock; + struct wmt_pcm_dma_data *dma_data; + int dma_ch; + int period_index; +}; + +struct snd_wfd_buffer { + struct device *dev; + unsigned char *area; /* virtual pointer */ + dma_addr_t addr; /* physical address */ + size_t bytes; /* buffer size in bytes */ + size_t valuable_sz; /* valuable size in wfd buffer */ + unsigned char *wr_ptr; + unsigned char *rd_ptr; + int enable; +}; +static struct snd_wfd_buffer wfd_audbuf; + +static int audio_dma_free(struct audio_stream_a *s); + +dmach_t pcm_out_dmach = 0xFF; +struct dma_device_cfg_s *pcm_out_dma_cfg = NULL; + +void wmt_pcm_wfd_start(void) +{ + /* allocate buffer for WFD support */ + wfd_audbuf.area = dma_alloc_writecombine(wfd_audbuf.dev, wfd_audbuf.bytes, + &wfd_audbuf.addr, GFP_KERNEL); + + if (!wfd_audbuf.area) { + err("WFD_Aud allocate buffer fail"); + } + else { + info("*WFD_Aud enable"); + wfd_audbuf.enable = 1; + } + + memset(wfd_audbuf.area, 0x0, wfd_audbuf.bytes); + //info("&wfd_audbuf.addr=0x%x wfd_audbuf.addr=0x%x", &wfd_audbuf.addr, (unsigned int)wfd_audbuf.addr); +} +EXPORT_SYMBOL(wmt_pcm_wfd_start); + +unsigned int wmt_pcm_wfd_get_buf(void) +{ + wfd_audbuf.valuable_sz = 0; + wfd_audbuf.wr_ptr = wfd_audbuf.rd_ptr = wfd_audbuf.area; + return (unsigned int)&wfd_audbuf.addr; +} +EXPORT_SYMBOL(wmt_pcm_wfd_get_buf); + +void wmt_pcm_wfd_stop(void) +{ + wfd_audbuf.wr_ptr = wfd_audbuf.area; + wfd_audbuf.enable = 0; + + dma_free_writecombine(wfd_audbuf.dev, wfd_audbuf.bytes, + wfd_audbuf.area, wfd_audbuf.addr); + info("*WFD_Aud disable"); + return; +} +EXPORT_SYMBOL(wmt_pcm_wfd_stop); + +int wmt_pcm_wfd_get_strm(WFDStrmInfo_t *info) +{ + if ((info->req_sz > wfd_audbuf.bytes) || (!wfd_audbuf.valuable_sz)) { + //info("WFD read size=%d, Too Large!", info->req_sz); + info->avail_sz = 0; + return (int)info; + } + else if (wfd_audbuf.valuable_sz > info->req_sz) { + info->avail_sz = info->req_sz; + wfd_audbuf.valuable_sz -= info->req_sz; + } + else { + info->avail_sz = wfd_audbuf.valuable_sz; + wfd_audbuf.valuable_sz = 0; + } + + info->buf_offset = wfd_audbuf.rd_ptr - wfd_audbuf.area; + wfd_audbuf.rd_ptr += info->avail_sz; + + if (wfd_audbuf.rd_ptr >= wfd_audbuf.area + wfd_audbuf.bytes) + wfd_audbuf.rd_ptr = wfd_audbuf.rd_ptr - wfd_audbuf.bytes; + + return (int)info; +} +EXPORT_SYMBOL(wmt_pcm_wfd_get_strm); + +void wmt_pcm_wfd_update(char *src_buf, unsigned int chunksize) +{ + //info("wmt_pcm_wfd_update, 0x%x", src_buf[1024]); + memcpy(wfd_audbuf.wr_ptr, src_buf, chunksize); + wfd_audbuf.wr_ptr += chunksize; + + if (wfd_audbuf.wr_ptr == wfd_audbuf.area + wfd_audbuf.bytes) + wfd_audbuf.wr_ptr = wfd_audbuf.area; + + wfd_audbuf.valuable_sz += chunksize; + if (wfd_audbuf.valuable_sz >= wfd_audbuf.bytes) { + wfd_audbuf.valuable_sz = wfd_audbuf.bytes; + wfd_audbuf.rd_ptr = wfd_audbuf.wr_ptr; + } +} + +/* + * Main dma routine, requests dma according where you are in main alsa buffer + */ +static void audio_process_dma(struct audio_stream_a *s) +{ + struct snd_pcm_substream *substream = s->stream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + dma_addr_t dma_base; + int ret = 0; + + //DBG_DETAIL(); + DPRINTK("s: %d, dmach: %d. active: %d", (int)s, s->dmach, s->active); + + if (s->active) { + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + /*DPRINTK("frame_bits=%d, period_size=%d, dma_size 1=%d", + runtime->frame_bits, (int)runtime->period_size, dma_size);*/ + if (dma_size > MAX_DMA_SIZE) + dma_size = CUT_DMA_SIZE; + offset = dma_size * s->period; + + /*DPRINTK("offset: 0x%x, ->dma_area: 0x%x, ->dma_addr: 0x%x, final addr: 0x%x", + offset, (unsigned int)runtime->dma_area, runtime->dma_addr, runtime->dma_addr+offset);*/ + + dma_base = __virt_to_phys((dma_addr_t)runtime->dma_area); + + //DPRINTK("dma address: 0x%x", dma_base+offset); + /*DPRINTK("hw_ptr_interrupt: 0x%x, state: %d, hwptr: %u, applptr: %u, avail_min: %u", + (unsigned int)runtime->hw_ptr_interrupt, runtime->status->state, (unsigned int)runtime->status->hw_ptr, + (unsigned int)runtime->control->appl_ptr, (unsigned int)runtime->control->avail_min);*/ + //DPRINTK("dmach: %u, dma_addr: %x, dma_size: %u", s->dmach, dma_base+offset, dma_size); + + if ((runtime->channels == 2 || runtime->channels == 1) && + (runtime->format == SNDRV_PCM_FORMAT_S16_LE)) { + ret = wmt_start_dma(s->dmach, runtime->dma_addr + offset, 0, dma_size); + } + + if (ret) { + printk(KERN_ERR "audio_process_dma: cannot queue DMA buffer (%i) \n", ret); + return; + } + + s->period++; + s->period %= runtime->periods; + s->periods++; + s->offset = offset; + } +} + +/* + * This is called when dma IRQ occurs at the end of each transmited block + */ +static void audio_dma_callback(void *data) +{ + struct audio_stream_a *s = data; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + int stream_id; + + //DBG_DETAIL(); + + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + stream_id = substream->pstr->stream; + + if (s->period > 0) + offset = dma_size * (s->period - 1); + else + offset = dma_size * (s->periods - 1); + + if ((stream_id == SNDRV_PCM_STREAM_PLAYBACK) && (wfd_audbuf.enable)) { + wmt_pcm_wfd_update(runtime->dma_area + offset, dma_size); + } + + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + if (s->periods > 0) + s->periods--; + + audio_process_dma(s); + spin_unlock(&s->dma_lock); +} + +static int audio_dma_request(struct audio_stream_a *s, void (*callback) (void *)) +{ + int err; + err = 0; + + DBG_DETAIL(); + + //DPRINTK("s pointer: %d, dmach: %d, id: %s, dma_dev: %d", (int)s, s->dmach, s->id, s->dma_dev); + err = wmt_request_dma(&s->dmach, s->id, s->dma_dev, callback, s); + if (err < 0) + printk(KERN_ERR "Unable to grab audio dma 0x%x\n", s->dmach); + + if (!strcmp(s->id, "WMT I2S out")) { + pcm_out_dmach = s->dmach; + } + return err; +} + +static void audio_setup_dma(struct audio_stream_a *s, int stream_id) +{ + struct snd_pcm_runtime *runtime = s->stream->runtime; + + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + /* From memory to device */ + switch (runtime->channels * runtime->format) { + #if 0 //comment 2014-1-3 tinyplay mono pattern abnormal + case 1: + s->dma_cfg.DefaultCCR = I2S_TX_DMA_8BITS_CFG; /* setup 1 bytes*/ + break ; + case 2: + s->dma_cfg.DefaultCCR = I2S_TX_DMA_16BITS_CFG; /* setup 2 bytes*/ + break ; + case 4: + #endif + default : + s->dma_cfg.DefaultCCR = I2S_TX_DMA_32BITS_CFG; /* setup 4 byte*/ + break ; + } + } + else { + /* From device to memory */ + switch (runtime->channels * runtime->format) { + case 1: + s->dma_cfg.DefaultCCR = I2S_RX_DMA_8BITS_CFG ; /* setup 1 bytes*/ + break ; + case 2: + s->dma_cfg.DefaultCCR = I2S_RX_DMA_16BITS_CFG ; /* setup 2 bytes*/ + break ; + case 4: + s->dma_cfg.DefaultCCR = I2S_RX_DMA_32BITS_CFG ; /* setup 4 byte*/ + break ; + } + } + + s->dma_cfg.ChunkSize = 1; + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + pcm_out_dma_cfg = &s->dma_cfg; + } + + //DPRINTK("s pointer: %d. audio dma %d cfg.DefaultCCR 0x%x ", (int)s, s->dmach, (unsigned int)s->dma_cfg.DefaultCCR); + //DPRINTK("cfg.ChunkSize 0x%x ", s->dma_cfg.ChunkSize); + wmt_setup_dma(s->dmach, s->dma_cfg) ; +} + +static int audio_dma_free(struct audio_stream_a *s) +{ + int err = 0; + DBG_DETAIL(); + wmt_free_dma(s->dmach); + s->dmach = NULL_DMA; + pcm_out_dma_cfg = NULL; + return err; +} + +/* + * this stops the dma and clears the dma ptrs + */ +static void audio_stop_dma(struct audio_stream_a *s) +{ + unsigned long flags; + DBG_DETAIL(); + local_irq_save(flags); + s->active = 0; + s->period = 0; + s->periods = 0; + s->offset = 0; + s->last_offset = 0; + wmt_stop_dma(s->dmach); + wmt_clear_dma(s->dmach); + local_irq_restore(flags); +} + +/* this may get called several times by oss emulation */ +static int wmt_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err = 0; + DBG_DETAIL(); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + return err; +} + +static int wmt_pcm_hw_free(struct snd_pcm_substream *substream) +{ + DBG_DETAIL(); + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int wmt_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *prtd = runtime->private_data; + struct audio_stream_a *s = &prtd[stream_id]; + + DBG_DETAIL(); + + s->period = 0; + s->periods = 0; + s->offset = 0; + s->last_offset = 0; + audio_setup_dma(s, stream_id); + + return 0; +} + +static int wmt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *prtd = runtime->private_data; + struct audio_stream_a *s = &prtd[stream_id]; + int ret = 0; + + DPRINTK("wmt_pcm_trigger Enter, cmd=%d", cmd); + + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->active = 1; + audio_process_dma(s); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + audio_stop_dma(s); + break; + default: + ret = -EINVAL; + } + spin_unlock(&s->dma_lock); + + return ret; +} + +static snd_pcm_uframes_t wmt_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *prtd = runtime->private_data; + int stream_id = substream->pstr->stream; + struct audio_stream_a *s = &prtd[stream_id]; + dma_addr_t ptr; + snd_pcm_uframes_t offset = 0; + + ptr = wmt_get_dma_pos(s->dmach); + + if ((runtime->channels == 1 || runtime->channels == 2) && (runtime->format == SNDRV_PCM_FORMAT_S16_LE)) { + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } + + if ((offset < s->last_offset) && ((s->last_offset - offset) < runtime->period_size) && + (s->last_offset != runtime->buffer_size)) { + snd_pcm_uframes_t old_offset = offset; + if (s->last_offset < runtime->period_size) + offset = runtime->period_size; + else { + offset = runtime->period_size * + ((s->last_offset / runtime->period_size) + 1); + } + printk("last_offset %d, old offset %d, new offset %d\n", (int)s->last_offset, (int)old_offset, (int)offset); + } + s->last_offset = offset; + + if (offset >= runtime->buffer_size) + offset = 0; + + spin_lock(&s->dma_lock); + + if (s->periods > 0 && s->periods < 3) { + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + if (snd_pcm_playback_hw_avail(runtime) >= 3 * runtime->period_size) + audio_process_dma(s); + } + else { + if (snd_pcm_capture_hw_avail(runtime) >= 3* runtime->period_size) + audio_process_dma(s); + } + + } + spin_unlock(&s->dma_lock); + + //DPRINTK("offset = %x", (unsigned int)offset); + return offset; +} + +static int wmt_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *s = runtime->private_data; + int ret; + + DBG_DETAIL(); + + if (!cpu_dai->active) { + audio_dma_request(&s[0], audio_dma_callback); + audio_dma_request(&s[1], audio_dma_callback); + } + + snd_soc_set_runtime_hwparams(substream, &wmt_pcm_hardware); + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + +out: + return ret; +} + +static int wmt_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *s = runtime->private_data; + + DBG_DETAIL(); + + if (!cpu_dai->active) { + audio_dma_free(&s[0]); + audio_dma_free(&s[1]); + } + + return 0; +} + +static int wmt_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + DBG_DETAIL(); + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops wmt_pcm_ops = { + .open = wmt_pcm_open, + .close = wmt_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = wmt_pcm_hw_params, + .hw_free = wmt_pcm_hw_free, + .prepare = wmt_pcm_prepare, + .trigger = wmt_pcm_trigger, + .pointer = wmt_pcm_pointer, + .mmap = wmt_pcm_mmap, +}; + +static u64 wmt_pcm_dmamask = DMA_BIT_MASK(32); + +static int wmt_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 = wmt_pcm_hardware.buffer_bytes_max; + + DBG_DETAIL(); + + 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 (stream == SNDRV_PCM_STREAM_PLAYBACK) { + info("stream = %d, buf_addr = %x", stream, buf->addr); + + wfd_audbuf.bytes = size; + wfd_audbuf.dev = pcm->card->dev; + wfd_audbuf.enable = 0; + } + + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + + return 0; +} + +static void wmt_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + DBG_DETAIL(); + + 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; + } +} + +int wmt_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; + + DBG_DETAIL(); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &wmt_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 = wmt_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = wmt_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + wmt_pcm_free_dma_buffers(pcm); + + return ret; +} + +#ifdef CONFIG_PM +static int wmt_pcm_suspend(struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct audio_stream_a *prtd; + struct audio_stream_a *s; + + DBG_DETAIL(); + + if (!runtime) + return 0; + + prtd = runtime->private_data; + s = &prtd[SNDRV_PCM_STREAM_PLAYBACK]; + + if (s->active) { + udelay(5); + wmt_stop_dma(s->dmach); + /* + wmt_clear_dma(s->dmach); + audio_stop_dma(s); + */ + } + + s = &prtd[SNDRV_PCM_STREAM_CAPTURE]; + + if (s->active) { + udelay(5); + wmt_stop_dma(s->dmach); + /* + wmt_clear_dma(s->dmach); + audio_stop_dma(s); + */ + } + + return 0; +} + +static int wmt_pcm_resume(struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct audio_stream_a *prtd; + struct audio_stream_a *s; + + DBG_DETAIL(); + + if (!runtime) { + if ((pcm_out_dmach != 0xFF) && (pcm_out_dma_cfg != NULL)) { + wmt_setup_dma(pcm_out_dmach, *pcm_out_dma_cfg); + } + return 0; + } + + prtd = runtime->private_data; + s = &prtd[SNDRV_PCM_STREAM_PLAYBACK]; + audio_setup_dma(s, SNDRV_PCM_STREAM_PLAYBACK); + + if (s->active) { + wmt_resume_dma(s->dmach) ; + } + + s = &prtd[SNDRV_PCM_STREAM_CAPTURE]; + audio_setup_dma(s, SNDRV_PCM_STREAM_CAPTURE); + + if (s->active) { + wmt_resume_dma(s->dmach) ; + } + + return 0; +} +#else +#define wmt_pcm_suspend NULL +#define wmt_pcm_resume NULL +#endif + +static struct snd_soc_platform_driver wmt_soc_platform = { + .ops = &wmt_pcm_ops, + .pcm_new = wmt_pcm_new, + .pcm_free = wmt_pcm_free_dma_buffers, + .suspend = wmt_pcm_suspend, + .resume = wmt_pcm_resume, +}; + +static int __devinit wmt_pcm_platform_probe(struct platform_device *pdev) +{ + DBG_DETAIL(); + + return snd_soc_register_platform(&pdev->dev, &wmt_soc_platform); +} + +static int __devexit wmt_pcm_platform_remove(struct platform_device *pdev) +{ + DBG_DETAIL(); + + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver wmt_pcm_driver = { + .driver = { + .name = "wmt-audio-pcm", + .owner = THIS_MODULE, + }, + + .probe = wmt_pcm_platform_probe, + .remove = __devexit_p(wmt_pcm_platform_remove), +}; + +module_platform_driver(wmt_pcm_driver); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC/pcm] driver"); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.h b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.h new file mode 100755 index 00000000..90ad7747 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.h @@ -0,0 +1,46 @@ +/*++ + * linux/sound/soc/wmt/wmt-pcm.h + * WonderMedia 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#ifndef __WMT_PCM_H__ +#define __WMT_PCM_H__ + +struct wmt_pcm_dma_data { + char *name; /* stream identifier */ + int dma_req; /* DMA request line */ + unsigned long port_addr; /* transmit/receive register */ + struct dma_device_cfg_s *dma_cfg; +}; + +typedef struct WFDStrmInfo { + unsigned int req_sz; + unsigned int avail_sz; + unsigned int buf_offset; +} WFDStrmInfo_t; + +extern void wmt_pcm_wfd_start(void); +extern unsigned int wmt_pcm_wfd_get_buf(void); +extern void wmt_pcm_wfd_stop(void); +extern int wmt_pcm_wfd_get_strm(WFDStrmInfo_t *info); + +#endif diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.c new file mode 100755 index 00000000..f62e993d --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.c @@ -0,0 +1,321 @@ +/*++ + * linux/sound/soc/wmt/wmt-soc.c + * WonderMedia 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/hwdep.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "wmt-soc.h" +#include "wmt-pcm.h" +#include "wmt_hwdep.h" +#include "../codecs/wmt_vt1602.h" +#include "../codecs/vt1603.h" + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); +extern void wmt_set_i2s_share_pin(); +char wmt_codec_name[80]; +char wmt_dai_name[80]; +char wmt_rate[10]; + +#define AUDIO_NAME "WMT_SOC" +//#define WMT_SOC_DEBUG 1 +//#define WMT_SOC_DEBUG_DETAIL 1 + +#ifdef WMT_SOC_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_SOC_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) + +#define WMT_I2S_RATES SNDRV_PCM_RATE_8000_96000 + +static struct snd_soc_card snd_soc_machine_wmt; + +static int wmt_soc_primary_startup(struct snd_pcm_substream *substream) +{ + DBG_DETAIL(); + return 0; +} + +static void wmt_soc_primary_shutdown(struct snd_pcm_substream *substream) +{ + DBG_DETAIL(); +} + +static int wmt_soc_primary_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + DBG_DETAIL(); + + if (strcmp(wmt_codec_name, "hwdac")) { + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_I2S |SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + } + + + /* Set cpu DAI configuration for I2S */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + |SND_SOC_DAIFMT_MASTER_MASK | SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + + if ((!strcmp(wmt_codec_name, "vt1602")) || (!strcmp(wmt_codec_name, "vt1603"))) { + /* Set the codec system clock for DAC and ADC */ + if (!(params_rate(params) % 11025)) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 11289600, + SND_SOC_CLOCK_IN); + } + else { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 12288000, + SND_SOC_CLOCK_IN); + } + } + + return ret; +} + +static int wmt_soc_second_startup(struct snd_pcm_substream *substream) +{ + //dump_stack(); + DBG_DETAIL(); + return 0; +} + +static void wmt_soc_second_shutdown(struct snd_pcm_substream *substream) +{ + //dump_stack(); + DBG_DETAIL(); +} + +static int wmt_soc_second_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + //dump_stack(); + DBG_DETAIL(); + return 0; +} + +static int wmt_soc_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + DBG_DETAIL(); + wmt_soc_hwdep_new(rtd->codec); + return 0; +} + +static int wmt_suspend_pre(struct snd_soc_card *card) +{ + snd_soc_dapm_disable_pin(&card->rtd->codec->dapm, "Left HP"); + snd_soc_dapm_disable_pin(&card->rtd->codec->dapm, "Right HP"); + snd_soc_dapm_disable_pin(&card->rtd->codec->dapm, "Left SPK"); + snd_soc_dapm_disable_pin(&card->rtd->codec->dapm, "Right SPK"); +} + +static int wmt_suspend_post(struct snd_soc_card *card) +{ + DBG_DETAIL(); + + /* Disable BIT15:I2S clock, BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL &= ~(BIT15 | BIT4 | BIT3); + + snd_soc_dapm_enable_pin(&card->rtd->codec->dapm, "Left HP"); + snd_soc_dapm_enable_pin(&card->rtd->codec->dapm, "Right HP"); + snd_soc_dapm_enable_pin(&card->rtd->codec->dapm, "Left SPK"); + snd_soc_dapm_enable_pin(&card->rtd->codec->dapm, "Right SPK"); + + return 0; +} + +static int wmt_resume_pre(struct snd_soc_card *card) +{ + /* Enable MCLK before VT1602 codec enable, otherwise the codec will be disabled. */ + + /* set to 24.576MHz */ + auto_pll_divisor(DEV_I2S, CLK_ENABLE , 0, 0); + auto_pll_divisor(DEV_I2S, SET_PLLDIV, 1, 24576); + /* Enable BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL |= (BIT4 | BIT3); + /* Enable BIT2:AUD clock */ + PMCE3_VAL |= BIT2; + + wmt_set_i2s_share_pin(); + return 0; +} + +static struct snd_soc_ops wmt_soc_primary_ops = { + .startup = wmt_soc_primary_startup, + .hw_params = wmt_soc_primary_hw_params, + .shutdown = wmt_soc_primary_shutdown, +}; + +static struct snd_soc_ops wmt_soc_second_ops = { + .startup = wmt_soc_second_startup, + .hw_params = wmt_soc_second_hw_params, + .shutdown = wmt_soc_second_shutdown, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link wmt_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .platform_name = "wmt-audio-pcm.0", + .init = wmt_soc_dai_init, + .ops = &wmt_soc_primary_ops, + }, + { + .name = "Voice", + .stream_name = "Voice", + .platform_name = "wmt-pcm-dma.0", + .cpu_dai_name = "wmt-pcm-controller.0", + .codec_dai_name = "HWDAC", + .codec_name = "wmt-i2s-hwdac.0", + .ops = &wmt_soc_second_ops, + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_machine_wmt = { + .name = "WMT_VT1609", + .dai_link = wmt_dai, + .num_links = ARRAY_SIZE(wmt_dai), + .suspend_pre = wmt_suspend_pre, + .suspend_post = wmt_suspend_post, + .resume_pre = wmt_resume_pre, +}; + +static struct platform_device *wmt_snd_device; + +static int __init wmt_soc_init(void) +{ + int ret, i; + char buf[64]; + int len = ARRAY_SIZE(buf); + + if (wmt_getsyspara("wmt.audio.i2s", buf, &len) != 0) { + strcpy(wmt_dai_name, "null"); + strcpy(wmt_codec_name, "null"); + } + else { + strcpy(wmt_dai_name, "i2s"); + } + + if (strcmp(wmt_dai_name, "null")) { + for (i = 0; i < 80; ++i) { + if (buf[i] == ':') + break; + else + wmt_codec_name[i] = buf[i]; + } + } + else { + return -EINVAL; + } + + // is wm8994, return and load wmt_wm8994 module + if (strcmp(wmt_codec_name, "wm8994") == 0) + return -ENODEV; + + info("dai_name=%s, codec_name=%s", wmt_dai_name, wmt_codec_name); + + + wmt_i2s_dai.playback.rates = (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000); + wmt_i2s_dai.capture.rates = (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000); + wmt_dai[0].cpu_dai_name = "wmt-i2s.0"; + + if (!strcmp(wmt_codec_name, "vt1602")) { + wmt_dai[0].codec_dai_name = "VT1602"; + wmt_dai[0].codec_name = "vt1602.1-001a"; + } + else if (!strcmp(wmt_codec_name, "hwdac")) { + wmt_dai[0].codec_dai_name = "HWDAC"; + wmt_dai[0].codec_name = "wmt-i2s-hwdac.0"; + } + else if (!strcmp(wmt_codec_name, "vt1603")) { + wmt_dai[0].codec_dai_name = "VT1603"; + wmt_dai[0].codec_name = "vt1603-codec"; + } + + /* Doing register process after plug-in */ + wmt_snd_device = platform_device_alloc("soc-audio", -1); + if (!wmt_snd_device) + return -ENOMEM; + + platform_set_drvdata(wmt_snd_device, &snd_soc_machine_wmt); + + ret = platform_device_add(wmt_snd_device); + if (ret) + goto err1; + + return 0; + +err1: + platform_device_put(wmt_snd_device); + return ret; + +} + +static void __exit wmt_soc_exit(void) +{ + DBG_DETAIL(); + + platform_device_unregister(wmt_snd_device); +} + +module_init(wmt_soc_init); +module_exit(wmt_soc_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC/Machine] driver"); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.h b/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.h new file mode 100755 index 00000000..f49a2764 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.h @@ -0,0 +1,53 @@ +/*++ + * linux/sound/soc/wmt/wmt-soc.h + * WonderMedia 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#ifndef __WMT_ASOC_H__ +#define __WMT_ASOC_H__ +#include <mach/dma.h> + +struct audio_stream_a { + char *id; /* identification string */ + int stream_id; /* numeric identification */ + dmach_t dmach; /* DMA channel number */ + struct dma_device_cfg_s dma_cfg; /* DMA device config */ + int dma_dev; /* dma number of that device */ + int dma_q_head; /* DMA Channel Q Head */ + int dma_q_tail; /* DMA Channel Q Tail */ + char dma_q_count; /* DMA Channel Q Count */ + int active:1; /* we are using this stream for transfer now */ + int period; /* current transfer period */ + int periods; /* current count of periods registerd in the DMA engine */ + spinlock_t dma_lock; /* for locking in DMA operations */ + struct snd_pcm_substream *stream; /* the pcm stream */ + unsigned linked:1; /* dma channels linked */ + int offset; /* store start position of the last period in the alsa buffer */ + snd_pcm_uframes_t last_offset; +}; + +#define NUM_LINKS 1 + +extern struct snd_soc_dai_driver wmt_i2s_dai; + + +#endif diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.c b/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.c new file mode 100755 index 00000000..8a6d374e --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.c @@ -0,0 +1,259 @@ +/*++ + * linux/sound/soc/wmt/wmt_hwdep.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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <sound/soc.h> +#include <sound/jack.h> + +#include <asm/mach-types.h> +#include <mach/gpio.h> + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> +#include <sound/hwdep.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "wmt_hwdep.h" +#include "wmt-pcm.h" + +int WFD_flag = 0; +static int gmode = 2; +static char gstring[3] = "LR"; + +static char gSpdHdm[6] = "BOTH"; +static int gSpHd = 6; + +extern void wmt_i2s_dac0_ctrl(int HDMI_audio_enable); + +static int wmt_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + if ((file->f_flags & O_RDWR) && (WFD_flag)) { + return -EBUSY; + } + else if (file->f_flags & O_SYNC) { + WFD_flag = 1; + } + return 0; +} + +static int wmt_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + WFD_flag = 0; + return 0; +} + +static int wmt_hwdep_mmap(struct snd_hwdep *hw, struct file *file, struct vm_area_struct *vma) +{ + vma->vm_flags |= VM_IO | VM_RESERVED; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + if (remap_pfn_range(vma, vma->vm_start, (vma->vm_pgoff), + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + printk("*E* remap page range failed: vm_pgoff=0x%x ", (unsigned int)vma->vm_pgoff); + return -EAGAIN; + } + return 0; +} + +static int wmt_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg) +{ + int *value; + WFDStrmInfo_t *info; + struct wmt_soc_vt1603_info vt1603_info; + int ret = 0; + + switch (cmd) { + case WMT_SOC_IOCTL_HDMI: + value = (int __user *)arg; + + if (*value > 1) { + printk("Not supported status for HDMI Audio %d", *value); + return 0; + } + wmt_i2s_dac0_ctrl(*value); + return 0; + + case WMT_SOC_IOCTL_WFD_START: + wmt_pcm_wfd_start(); + return copy_to_user( (void *)arg, (const void __user *) wmt_pcm_wfd_get_buf(), sizeof(unsigned int)); + + case WMT_SOC_IOCTL_GET_STRM: + info = (WFDStrmInfo_t *)wmt_pcm_wfd_get_strm((WFDStrmInfo_t *)arg); + return __put_user((int)info, (unsigned int __user *) arg); + + case WMT_SOC_IOCTL_WFD_STOP: + wmt_pcm_wfd_stop(); + return 0; + + case WMT_SOC_IOCTL_VT1603_RD: + ret = copy_from_user(&vt1603_info, (void __user *)arg, sizeof(vt1603_info)); + + if (ret == 0) { + vt1603_info.reg_value = vt1603_hwdep_ioctl(0, vt1603_info.reg_offset, vt1603_info.reg_value); + printk("<<<%s read reg 0x%x val 0x%x\n", __FUNCTION__, vt1603_info.reg_offset, vt1603_info.reg_value); + ret = copy_to_user((void __user *)arg, &vt1603_info, sizeof(vt1603_info)); + } + return ret; + case WMT_SOC_IOCTL_VT1603_WR: + ret = copy_from_user(&vt1603_info, (void __user *)arg, sizeof(vt1603_info)); + printk("<<<%s write reg 0x%x val 0x%x\n", __FUNCTION__, vt1603_info.reg_offset, vt1603_info.reg_value); + if (ret == 0) + vt1603_hwdep_ioctl(1, vt1603_info.reg_offset, vt1603_info.reg_value); + return ret; + + case WMT_SOC_IOCTL_CH_SEL: + value = (int __user *)arg; + + if (*value > 2) { + printk("Not supported for CH select %d", *value); + return 0; + } + wmt_i2s_ch_sel(*value); + return 0; + + default: + break; + } + + printk("Not supported ioctl for WMT-HWDEP"); + return -ENOIOCTLCMD; +} + +static long wmt_hwdep_write(struct snd_hwdep *hw, const char __user *buf, + long count, loff_t *offset) +{ + char string[3]; + //int mode; + memset(string, 0, sizeof(string)); + copy_from_user(&string, buf, sizeof(string)); + printk("<<<%s %s\n", __FUNCTION__, string); + if (!memcmp(string, "LL", 2)) { + gmode = 0; + } + else if (!memcmp(string, "RR", 2)) { + gmode = 1; + } + else if (!memcmp(string, "LR", 2)) { + gmode = 2; + } + else { + printk("Not supported for CH select"); + return count; + } + + memset(gstring, 0, sizeof(gstring)); + strncpy(gstring, string, sizeof(string)); + wmt_i2s_ch_sel(gmode); + return count; +} + +static long wmt_hwdep_read(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + int len = 0; + printk("%s string %s --> mode %d\n", __FUNCTION__, gstring, gmode); + len = copy_to_user(buf, gstring, sizeof(gstring)); + + return sizeof(gstring); +} + +static long wmt_hwdep_write_1(struct snd_hwdep *hw, const char __user *buf, + long count, loff_t *offset) +{ + char string[5]; + //int mode; + + copy_from_user(&string, buf, sizeof(string)); + printk("<<<%s %s\n", __FUNCTION__, string); + if (!memcmp(string, "NONE", 4)) { + gSpHd = 3; + } + else if (!memcmp(string, "HDMI", 4)) { + gSpHd = 4; + } + else if (!memcmp(string, "SPDIF", 5)) { + gSpHd = 5; + } + else if (!memcmp(string, "BOTH", 4)) { + gSpHd = 6; + } + else { + printk("Not supported for SPDIF/HDMI switch"); + return count; + } + + memset(gSpdHdm, 0, sizeof(gSpdHdm)); + strncpy(gSpdHdm, string, sizeof(string)); + + wmt_i2s_ch_sel(gSpHd); + return count; +} + + +static long wmt_hwdep_read_1(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + int len = 0; + printk("%s string %s --> mode %d\n", __FUNCTION__, gSpdHdm, gSpHd); + len = copy_to_user(buf, gSpdHdm, sizeof(gSpdHdm)); + + return sizeof(gstring); +} + +void wmt_soc_hwdep_new(struct snd_soc_codec *codec) +{ + struct snd_hwdep *hwdep; + struct snd_hwdep *hwdep_1; + if (snd_hwdep_new(codec->card->snd_card, "WMT-HWDEP", 0, &hwdep) < 0) { + printk("create WMT-HWDEP_0 fail"); + return; + } + + sprintf(hwdep->name, "WMT-HWDEP %d", 0); + + hwdep->iface = SNDRV_HWDEP_IFACE_WMT; + hwdep->ops.open = wmt_hwdep_open; + hwdep->ops.ioctl = wmt_hwdep_ioctl; + hwdep->ops.release = wmt_hwdep_release; + hwdep->ops.mmap = wmt_hwdep_mmap; + hwdep->ops.write = wmt_hwdep_write; + hwdep->ops.read = wmt_hwdep_read; + + if (snd_hwdep_new(codec->card->snd_card, "WMT-HWDEP", 1, &hwdep_1) < 0) { + printk("create WMT-HWDEP_1 fail"); + return; + } + + sprintf(hwdep_1->name, "WMT-HWDEP %d", 1); + printk("create %s success", hwdep_1->name); + + hwdep_1->iface = SNDRV_HWDEP_IFACE_WMT; + hwdep_1->ops.write = wmt_hwdep_write_1; + hwdep_1->ops.read = wmt_hwdep_read_1; +} + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.h b/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.h new file mode 100755 index 00000000..c95d5e9e --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.h @@ -0,0 +1,55 @@ +/*++ + * linux/sound/soc/wmt/wmt_hwdep.h + * 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +/* + * ioctls for Hardware Dependant Interface + */ +#ifndef __WMT_HWDEP_H__ +#define __WMT_HWDEP_H__ + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/hwdep.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +struct wmt_soc_vt1603_info { + u16 reg_offset; + u16 reg_value; +};//add 2013-9-2 for vt1603 eq apk + +#define WMT_SOC_IOCTL_HDMI _IOWR('H', 0x10, int) +#define WMT_SOC_IOCTL_WFD_START _IOWR('H', 0x20, int) +#define WMT_SOC_IOCTL_GET_STRM _IOWR('H', 0x30, int) +#define WMT_SOC_IOCTL_WFD_STOP _IOWR('H', 0x40, int) +#define WMT_SOC_IOCTL_VT1603_RD _IOWR('H', 0x50, int) +#define WMT_SOC_IOCTL_VT1603_WR _IOWR('H', 0x60, int) +#define WMT_SOC_IOCTL_CH_SEL _IOWR('H', 0x70, int) +void wmt_soc_hwdep_new(struct snd_soc_codec *codec); + +extern int vt1603_hwdep_ioctl(u8 rw_flag, u16 offset, u16 value); +extern void wmt_i2s_ch_sel(int ch_sel_num); +#endif diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.c b/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.c new file mode 100755 index 00000000..4d92a81f --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.c @@ -0,0 +1,101 @@ +/*++ + * linux/sound/soc/wmt/wmt_swmixer.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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#include "wmt_swmixer.h" +#include <sound/asound.h> + + + +void wmt_sw_u2s(int fmt, char *buffer, unsigned int chunksize) +{ + unsigned int index; + if (fmt == SNDRV_PCM_FORMAT_U8) { + for (index = 0; index < chunksize; ++index) + *(buffer + index) ^= 0x80; + } + +} + + +void wmt_pcm_fmt_trans(int fmt, int channel, char *src_buf, char *dst_buf, unsigned int chunksize) +{ + unsigned int index = 0; + float_data_t f_data; + unsigned short data; + + /* always convert to 2ch, s16le (4 bytes) */ + if ((fmt == SNDRV_PCM_FORMAT_S16_LE) && (channel == 1)) { + /* transfer from 1ch s16le(2 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < (chunksize / 2); ++index) { + *((unsigned int *)dst_buf + index) = (*((unsigned short *)src_buf + index) << 16 | + *((unsigned short *)src_buf + index)); + } + } + else if ((fmt == SNDRV_PCM_FORMAT_U8) && (channel == 1)) { + /* transfer from 1ch U8(1 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < chunksize; ++index) { + /* padding zero to byte 0 & byte 2 */ + *((unsigned int *)dst_buf + index) = (*((unsigned char *)src_buf + index) << 24 | + *((unsigned char *)src_buf + index) << 8); + } + } + else if ((fmt == SNDRV_PCM_FORMAT_U8) && (channel == 2)) { + /* transfer from 2ch U8(2 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < chunksize; ++index) { + /* padding zero to byte 0 */ + *((unsigned short *)dst_buf + index) = *((unsigned char *)src_buf + index) << 8; + } + } + else if ((fmt == SNDRV_PCM_FORMAT_FLOAT) && (channel == 2)) { + /* transfer from 2ch float(8 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < (chunksize / 4); ++index) { + f_data = *((float_data_t *)src_buf + index); + + if (!f_data.sign) { + data = (f_data.frac + 0x800000) >> (8 - (f_data.exp - 127)); + } + else { + data = ~((f_data.frac + 0x800000) >> (8 - (f_data.exp - 127))) + 1; + } + + *((unsigned short *)dst_buf + index) = data; + } + } + else if ((fmt == SNDRV_PCM_FORMAT_FLOAT) && (channel == 1)) { + /* transfer from 1ch float(4 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < (chunksize / 4); ++index) { + f_data = *((float_data_t *)src_buf + index); + + if (!f_data.sign) { + data = (unsigned short)((f_data.frac + 0x800000) >> (8 - (f_data.exp - 127))); + } + else { + data = (unsigned short)(~((f_data.frac + 0x800000) >> (8 - (f_data.exp - 127))) + 1); + } + + *((unsigned int *)dst_buf + index) = (data << 16) | data; + } + } +} + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.h b/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.h new file mode 100755 index 00000000..3163e5af --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.h @@ -0,0 +1,33 @@ +/*++ + * linux/sound/soc/wmt/wmt_swmixer.h + * 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 <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +typedef struct float_data { + unsigned long frac : 23; + unsigned long exp : 8; + unsigned long sign : 1; +} float_data_t; + +void wmt_sw_u2s(int fmt, char *buffer, unsigned int chunksize); +void wmt_pcm_fmt_trans(int fmt, int channel, char *src_buf, char *dst_buf, unsigned int chunksize); + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_wm8994.c b/ANDROID_3.4.5/sound/soc/wmt/wmt_wm8994.c new file mode 100755 index 00000000..8d4cebf9 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_wm8994.c @@ -0,0 +1,304 @@ +/* + * wmt_wm8994.c + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: Chanwoo Choi <cw00.choi@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <sound/soc.h> +#include <sound/jack.h> + +#include <asm/mach-types.h> +#include <mach/gpio.h> + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> +#include <sound/hwdep.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "wmt-soc.h" +#include "wmt-pcm.h" +#include "wmt_hwdep.h" +#include "../codecs/wm8994.h" +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/wm8994/core.h> + +#include <linux/i2c.h> + +static struct snd_soc_card wmt; +static struct platform_device *wmt_snd_device; +static int wmt_incall = 0; + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); +extern void wmt_set_i2s_share_pin(void); + +static const struct snd_pcm_hardware wmt_voice_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 8000, + .period_bytes_min = 16, + .period_bytes_max = 4 * 1024, + .periods_min = 2, + .periods_max = 16, + .buffer_bytes_max = 16 * 1024, +}; + +static int wmt_snd_suspend_post(struct snd_soc_card *card) +{ + /* Disable BIT15:I2S clock, BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL &= ~(BIT15 | BIT4 | BIT3); + return 0; +} + +static int wmt_snd_resume_pre(struct snd_soc_card *card) +{ + /* Enable MCLK before VT1602 codec enable, otherwise the codec will be disabled. */ + + /* set to 24.576MHz */ + auto_pll_divisor(DEV_I2S, CLK_ENABLE , 0, 0); + auto_pll_divisor(DEV_I2S, SET_PLLDIV, 1, 24576); + /* Enable BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL |= (BIT4 | BIT3); + /* Enable BIT2:AUD clock */ + PMCE3_VAL |= BIT2; + + wmt_set_i2s_share_pin(); + + return 0; +} + +static int wmt_wm8994_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret = 0; + unsigned int pll_in = 48000; + unsigned int pll_out = 12000000; + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_LRCLK, + pll_in, pll_out); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, pll_out, + SND_SOC_CLOCK_IN); + + wmt_soc_hwdep_new(rtd->codec); + + return 0; +} + +static int wmt_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int pll_in = 48000; + unsigned int pll_out = 12000000; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF); + if (ret< 0) { + printk("<<ret:%d snd_soc_dai_set_fmt(codec) hifi\n", ret); + return ret; + } + + /* Set cpu DAI configuration for I2S */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S); + if (ret < 0) { + printk("<<ret:%d snd_soc_dai_set_fmt(cpu) hifi\n", ret); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_LRCLK, + pll_in, pll_out); + if (ret < 0) { + printk("<<ret:%d snd_soc_dai_set_pll hifi\n", ret); + return ret; + } + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + printk("<<ret:%d snd_soc_dai_set_sysclk hifi\n", ret); + + if (!wmt_incall) + snd_soc_update_bits(codec_dai->codec, WM8994_CLOCKING_1, WM8994_SYSCLK_SRC, 0); + + return 0; +} + +static int wmt_voice_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = snd_soc_set_runtime_hwparams(substream, &wmt_voice_hardware); + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime,SNDRV_PCM_HW_PARAM_PERIODS); + return ret; +} + +static int wmt_voice_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 ret = 0; + unsigned int pll_in = 2048000; + unsigned int pll_out = 12288000; + + if (params_rate(params) != 8000) + return -EINVAL; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + printk("<<ret:%d snd_soc_dai_set_fmt voice\n", ret); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, WM8994_FLL_SRC_BCLK, + pll_in, pll_out); + if (ret < 0) { + printk("<<ret:%d snd_soc_dai_set_pll voice\n", ret); + return ret; + } + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + printk("<<ret:%d snd_soc_dai_set_sysclk voice\n", ret); + + wmt_incall = 1; + return ret; +} + +static int wmt_voice_hw_free(struct snd_pcm_substream *substream) +{ + wmt_incall = 0; + return 0; +} + +static struct snd_soc_ops wmt_hifi_ops = { + .hw_params = wmt_hifi_hw_params, +}; + +static struct snd_soc_ops wmt_voice_ops = { + .startup = wmt_voice_startup, + .hw_params = wmt_voice_hw_params, + .hw_free = wmt_voice_hw_free, +}; + +static struct snd_soc_dai_driver voice_dai[] = { + { + .name = "wmt-voice-dai", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + }, +}; + +static struct snd_soc_dai_link wmt_dai[] = { + { + .name = "WM8994", + .stream_name = "WM8994 HiFi", + .cpu_dai_name = "wmt-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "wmt-audio-pcm.0", + .codec_name = "wm8994-codec", + .init = wmt_wm8994_init, + .ops = &wmt_hifi_ops, + }, + { + .name = "WM8994 Voice", + .stream_name = "Voice", + .cpu_dai_name = "wmt-voice-dai", + .codec_dai_name = "wm8994-aif2", + .codec_name = "wm8994-codec", + .ops = &wmt_voice_ops, + }, +}; + +static struct snd_soc_card wmt = { + .name = "WMT_WM8994", + .dai_link = wmt_dai, + .num_links = ARRAY_SIZE(wmt_dai), + .suspend_post = wmt_snd_suspend_post, + .resume_pre = wmt_snd_resume_pre, +}; + +static int __init wmt_init(void) +{ + int ret; + char buf[128]; + int len = ARRAY_SIZE(buf); + + if (wmt_getsyspara("wmt.audio.i2s", buf, &len) != 0) + return -EINVAL; + + if (strncmp(buf, "wm8994", strlen("wm8994"))) + return -ENODEV; + + wmt_snd_device = platform_device_alloc("soc-audio", -1); + if (!wmt_snd_device) + return -ENOMEM; + + /* register voice DAI here */ + ret = snd_soc_register_dais(&wmt_snd_device->dev, voice_dai, ARRAY_SIZE(voice_dai)); + if (ret) { + platform_device_put(wmt_snd_device); + return ret; + } + + platform_set_drvdata(wmt_snd_device, &wmt); + ret = platform_device_add(wmt_snd_device); + + if (ret) { + snd_soc_unregister_dai(&wmt_snd_device->dev); + platform_device_put(wmt_snd_device); + } + + return ret; +} + +static void __exit wmt_exit(void) +{ + snd_soc_unregister_dai(&wmt_snd_device->dev); + platform_device_unregister(wmt_snd_device); +} + +module_init(wmt_init); +module_exit(wmt_exit); + +MODULE_DESCRIPTION("ALSA SoC WM8994 WMT(wm8950)"); +MODULE_AUTHOR("Loonzhong <Loonzhong@wondermedia.com.cn>"); +MODULE_LICENSE("GPL"); |