/* * Copyright (c) sitek hengke elec .Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include //#include"mx27_wm9712.h" #define DEBUG_MX27_AC97_CAPTURE 1 //julian workaround //#define TALK 5 #if defined (TALK) #define DBG(l,f...) do{ if (l <= TALK) printk (f); }while(0) #define ENTRY(l) DBG(l, "%s: entry/n", __FUNCTION__) #else #define ENTRY(l) do {} while (0) #define DBG(l,f...) do {} while (0) #endif #define MAX_BUFFER_SIZE (32 * 1024) #define DMA_BUF_SIZE (8 * 1024) #define MIN_PERIOD_SIZE 64 #define MIN_PERIOD 2 #define MAX_PERIOD 255 extern void gpio_ssi_active(int ssi_num); extern void gpio_ssi_inactive(int ssi_nim); extern unsigned short ssi_ac97_read(unsigned short reg); extern void ssi_ac97_write(unsigned short reg, unsigned short val); static char* id = "mx27-wm9712"; #define SSI_CODEC SSI1//0 //Julian, NOTE: use SSI1 directly somewhere. #if 1 //#define _reg_value(addr,offset) *((volatile unsigned int *)((IO_ADDRESS(addr)+offset))) #define TX_WATERMARK 0x4 //set tx_fifo watermark #define RX_WATERMARK 0x6 // set rx_fifo watermark #define AC97_TAG_READ 0xc000 #define AC97_TAG_WRITE 0xe000 #define AC97_TAG_DISABLE 0x8000 #define AC97_TAG_PCM 0x9800 #endif typedef struct audio_stream{ char* id; int stream_id; int dma_channel; //we are using this stream for transfer now int active; // are we recording - flag used to do DMA trans. for sync int tx_spin; //for locking in DMA operations spinlock_t dma_lock; //int mode; int period; /* Index of current period */ int periods; /* Number of periods buffered to DMA */ // unsigned int old_offset; snd_pcm_substream_t* substream; }audio_stream_t; typedef struct mx27_codec_t{ snd_card_t* card; snd_pcm_t *pcm; audio_stream_t s[2]; /* [0], playback; [1], capture */ ac97_bus_t* ac97_bus; ac97_t* ac97; unsigned int ac97_clock; }mx27_codec_t; static mx27_codec_t* mx27_codec = NULL; static snd_pcm_hardware_t mx27_codec_playback_hw = { .info = ( SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID // | sNDRV_PCM_INFO_PAUsE // | sNDRV_PCM_INFO_REsUME ), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = (0 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 ), .rate_min = 8000, .rate_max = 48000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = MAX_BUFFER_SIZE, .period_bytes_min = MIN_PERIOD_SIZE, .period_bytes_max = DMA_BUF_SIZE, .periods_min = MIN_PERIOD, .periods_max = MAX_PERIOD, .fifo_size = 0, }; static snd_pcm_hardware_t mx27_codec_capture_hw = { .info = ( SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID // | SNDRV_PCM_INFO_PAUsE // | SNDRV_PCM_INFO_REsUME ), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = (0 | SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 ), .rate_min = 8000, .rate_max = 48000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = MAX_BUFFER_SIZE, .period_bytes_min = MIN_PERIOD_SIZE, .period_bytes_max = DMA_BUF_SIZE, .periods_min = MIN_PERIOD, .periods_max = MAX_PERIOD, .fifo_size = 0, }; static unsigned int rates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, }; static snd_pcm_hw_constraint_list_t hw_constraints_rates = { .count = ARRAY_SIZE (rates), .list = rates, .mask = 0, }; #if DEBUG_MX27_AC97_CAPTURE struct debug_capture_dma{ //use 2 buffers. dma_addr_t addr_phys, addr_phys2;//u32 void* addr_virt; void* addr_virt2; unsigned int length;//buf len unsigned int trans; unsigned short actual_rate; unsigned char doing_buf1, trigger;//flags }; static struct debug_capture_dma debug_cap; #endif static unsigned short mx27_wm9712_read (ac97_t* ac97, unsigned short reg) { return ssi_ac97_read (reg); } static void mx27_wm9712_write(ac97_t* ac97, unsigned short reg,unsigned short val) { ssi_ac97_write (reg, val); } static unsigned int adjust_speed(int speed) { // speeds from 8k to 48 if (speed >= (44100 + 48000) / 2) { speed = 48000; } else if (speed >= (32000 + 44100) / 2) { speed = 44100; } else if (speed >= (24000 + 32000) / 2) { speed = 32000; } else if (speed >= (22050 + 24000) / 2) { speed = 24000; } else if (speed >= (16000 + 22050) / 2) { speed = 22050; } else if (speed >= (12000 + 16000) / 2) { speed = 16000; } else if (speed >= (11025 + 12000) / 2) { speed = 12000; } else if (speed >= (8000 + 11025) / 2) { speed = 11025; } else { speed = 8000; } return speed; } static inline void set_audiodac_rate (mx27_codec_t* codec, int frequency) { unsigned int v = adjust_speed(frequency); DBG (2,"adjust %d Hz to %d(DAC rate)/n", frequency, v); snd_ac97_set_rate(codec->ac97, AC97_PCM_FRONT_DAC_RATE, v); } static inline void set_audioadc_rate (mx27_codec_t* codec, int frequency) { unsigned int v; v = adjust_speed(frequency); DBG (2,"adjust %d Hz to %d(ADC rate)/n", frequency, v); snd_ac97_set_rate(codec->ac97, AC97_PCM_LR_ADC_RATE, v); } static void wm9712_init(void) { unsigned short id1, id2, reg; #if 0 int i; for(i=0;i<100000;i++){ udelay(30); id1=ssi_ac97_read(AC97_VENDOR_ID1); id2=ssi_ac97_read(AC97_VENDOR_ID2); if((id1 !=0x574d) || (id2 != 0x4c12)) printk("E%d ",i); } #endif id1=ssi_ac97_read(AC97_VENDOR_ID1); id2=ssi_ac97_read(AC97_VENDOR_ID2); printk("codec's id is (0x%0x,0x%0x)/n",id1, id2); if((id1 ==0x574d) && (id2 == 0x4c12)) { printk("Wolfson wm9712/9715./n"); } else if ((id1 ==0x4352) && (id2 == 0x595d)) { printk("Cirrus Logic CS4205 rev 5/n"); } //set codec var reg = ssi_ac97_read(AC97_EXTENDED_STATUS); reg |= 0x1; ssi_ac97_write(AC97_EXTENDED_STATUS,reg); } #define CCM_PCDR0 0x18 static void mx27_hw_config(void) { //julian, in ssi_ac97.c //set GPIO pins(gpioc-20~gpioc23) as SSI1 module function for primary //gpio_ssi_active(SSI2);//external ssi2-port //mxc_clks_enable(SSI1_BAUD); //mx27_audmux_config(); //mx27_ssi_ac97_config(); } /* mx27_codec_init, called from the driver initialization function to initialize the ALsA structures. */ static void mx27_codec_init (mx27_codec_t* codec) { ENTRY (0); /* setup DMA stuff */ codec->s[SNDRV_PCM_STREAM_PLAYBACK].id = "WM9712 out"; codec->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id = SNDRV_PCM_STREAM_PLAYBACK; codec->s[SNDRV_PCM_STREAM_CAPTURE].id = "WM9712 in"; codec->s[SNDRV_PCM_STREAM_CAPTURE].stream_id = SNDRV_PCM_STREAM_CAPTURE; } /* mx27_codec_ac97_init*/ static int mx27_codec_ac97_init (mx27_codec_t* codec) { ac97_template_t ac97; int result; static ac97_bus_ops_t ops = { //.reset =wm9712_reset, .read = mx27_wm9712_read, .write = mx27_wm9712_write, //.init =wm9712_init, }; printk("%s entry/n",__FUNCTION__); result = snd_ac97_bus (codec->card, 0, &ops, codec, &codec->ac97_bus); if (result < 0) return result; memset (&ac97, 0, sizeof(ac97)); ac97.private_data = codec; result = snd_ac97_mixer (codec->ac97_bus, &ac97, &codec->ac97); return result; } /*! * This function configures the DMA channel used to transfer audio from MCU to wm9712 * * @param substream pointer to the structure of the current stream. * @param callback pointer to function that will be called when a sDMA TX transfer finishes. * @return 0 on success, -1 otherwise. */ static int configure_write_channel(audio_stream_t * s, mxc_dma_callback_t callback) { int ret = -1; int channel = -1; ENTRY(0); channel = mxc_dma_request(MXC_DMA_SSI1_16BIT_TX0, "ALSA TX DMA"); if (channel < 0) { printk("error requesting a write dma channel/n"); return -1; } s->dma_channel = channel; ret = mxc_dma_callback_set(s->dma_channel, (mxc_dma_callback_t) callback,(void *)s); return ret; } //config for sitek.cn xtp-d501 board,using ssi1. static int configure_read_channel(audio_stream_t * s, mxc_dma_callback_t callback) { int ret = -1; int channel = -1; channel = mxc_dma_request(MXC_DMA_SSI1_16BIT_RX0, "ALSA RX DMA"); if (channel < 0) { printk("error requesting a read dma channel/n"); return -1; } s->dma_channel = channel; ret = mxc_dma_callback_set(s->dma_channel, (mxc_dma_callback_t) callback,(void *)s); return ret; } /*! * This function gets the dma pointer position during record. * Our DMA implementation does not allow to retrieve this position * when a transfert is active, so, it answers the middle of * the current period beeing transfered * * @param s pointer to the structure of the current stream. * */ static unsigned int audio_get_capture_dma_pos(audio_stream_t *s) { snd_pcm_substream_t *substream; snd_pcm_runtime_t *runtime; unsigned int offset; substream = s->substream; runtime = substream->runtime; offset = 0; /* tx_spin value is used here to check if a transfert is active */ if (s->tx_spin) { offset = (runtime->period_size * (s->periods)) + (runtime->period_size >> 1); if (offset >= runtime->buffer_size) offset = runtime->period_size >> 1; //printk("MXC: audio_get_dma_pos offset %d/n", offset); } else { offset = (runtime->period_size * (s->periods)); if (offset >= runtime->buffer_size) offset = 0; //printk("MXC: audio_get_dma_pos BIs offset %d/n", offset); } return offset; } /*! * This function gets the dma pointer position during playback. * Our DMA implementation does not allow to retrieve this position * when a transfert is active, so, it answers the middle of * the current period beeing transfered * * @param s pointer to the structure of the current stream. * */ static unsigned int audio_get_playback_dma_pos(audio_stream_t * s) { snd_pcm_substream_t *substream; snd_pcm_runtime_t *runtime; unsigned int offset; substream = s->substream; runtime = substream->runtime; offset = 0; /* tx_spin value is used here to check if a transfert is active */ if (s->tx_spin) { offset = runtime->period_size * (s->periods); if (offset >= runtime->buffer_size) offset = 0; DBG(1,"MXC: audio_get_dma_pos offset %d/n", offset); } else { offset = (runtime->period_size * (s->periods)); if (offset >= runtime->buffer_size) offset = 0; DBG(1,"MXC: audio_get_dma_pos BIs offset %d/n", offset); } return offset; } /*! * This function stops the current dma transfert for playback * and clears the dma pointers. * @param substream pointer to the structure of the current stream. */ static void audio_playback_stop_dma(audio_stream_t * s) { unsigned long flags; snd_pcm_substream_t *substream; snd_pcm_runtime_t *runtime; unsigned int dma_size; unsigned int offset; substream = s->substream; runtime = substream->runtime; dma_size = frames_to_bytes(runtime, runtime->period_size); offset = dma_size * s->periods; spin_lock_irqsave(&s->dma_lock, flags); printk("MXC : audio_stop_dma active = 0/n"); s->active = 0; s->period = 0; s->periods = 0; /* this stops the dma channel and clears the buffer ptrs */ mxc_dma_disable(s->dma_channel); dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, DMA_TO_DEVICE); spin_unlock_irqrestore(&s->dma_lock, flags); } /*! * This function stops the current dma transfer for capture * and clears the dma pointers. * @param substream pointer to the structure of the current stream. */ static void audio_capture_stop_dma(audio_stream_t * s) { unsigned long flags; snd_pcm_substream_t *substream; snd_pcm_runtime_t *runtime; unsigned int dma_size; unsigned int offset; substream = s->substream; runtime = substream->runtime; dma_size = frames_to_bytes(runtime, runtime->period_size); offset = dma_size * s->periods; spin_lock_irqsave(&s->dma_lock, flags); printk("MXC : audio_stop_dma active = 0/n"); s->active = 0; s->period = 0; s->periods = 0; /* this stops the dma channel and clears the buffer ptrs */ mxc_dma_disable(s->dma_channel); #if DEBUG_MX27_AC97_CAPTURE #else dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, DMA_FROM_DEVICE); #endif spin_unlock_irqrestore(&s->dma_lock, flags); } /*! * This function is called whenever a new audio block needs to be * transferred to wm9712. The function receives the address and the size * of the new block and start a new DMA transfer. * * @param substream pointer to the structure of the current stream. * */ static void audio_playback_dma(audio_stream_t * s) { snd_pcm_substream_t *substream; snd_pcm_runtime_t *runtime; unsigned int dma_size; unsigned int offset; int ret = 0; mxc_dma_requestbuf_t dma_request; substream = s->substream; runtime = substream->runtime; DBG(1,"/nDMA is playback/n"); memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); if (s->active) { dma_size = frames_to_bytes(runtime, runtime->period_size); DBG(1,"s->period (%x) runtime->periods (%d)/n", s->period, runtime->periods); DBG(1,"runtime->period_size (%d) dma_bytes (%d)/n", (unsigned int)runtime->period_size, runtime->dma_bytes); offset = dma_size * s->period; snd_assert(dma_size <= DMA_BUF_SIZE,); dma_request.src_addr = (dma_addr_t) (dma_map_single(NULL, runtime->dma_area+ offset, dma_size, DMA_TO_DEVICE)); dma_request.dst_addr = (dma_addr_t) (SSI1_BASE_ADDR + MXC_SSI1STX0); dma_request.num_of_bytes = dma_size; DBG(1,"MXC: start DMA offset=%d size=%d/n", offset,runtime->dma_bytes); DBG(1,"dst=%x,src=%x/n", dma_request.dst_addr,dma_request.src_addr); mxc_dma_config(s->dma_channel, &dma_request, 1, MXC_DMA_MODE_WRITE); ret = mxc_dma_enable(s->dma_channel); //ssi_transmit_enable(SSI_CODEC, 1); //printk("dma_size is 0x%x/n",dma_size); s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ if (ret) { printk("audio_process_dma: cannot queue DMA buffer (%i)/n", ret); return; } s->period++; s->period %= runtime->periods; } } #if DEBUG_MX27_AC97_CAPTURE static inline void capture_fifo_align(void) { //we need that the 1st data in fifo is slot3. julian. int i,j,k; unsigned int tmp[20],fcs,dum; unsigned char check_n, equal_n; unsigned char brk_flag; //int align_rd_num = 0; check_n = 10; udelay(200); //make sure slot3,4 is valid in 1st frame. //dummy read to clear fifo for(i=0;i<8;i++){ dum = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SRX0); } //assume slot12 is the 1st data brk_flag = 0; equal_n = 0; for(j=0;j> 12) & 0xf) >= 3) break; udelay(1); k--; } tmp[j] = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SRX0); if(tmp[0] == tmp[j]) equal_n++; else brk_flag = 1; if(equal_n == check_n) return; dum = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SRX0); dum = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SRX0); if(brk_flag){ break; } } //assume slot12 is the 2nd data brk_flag = 0; equal_n = 0; for(j=0;j> 12) & 0xf) >= 3) break; udelay(1); k--; } dum = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SRX0); tmp[j] = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SRX0); if(tmp[0] == tmp[j]) equal_n++; else brk_flag = 1; if(equal_n == check_n) return; dum = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SRX0); if(brk_flag){ break; } } //assume slot12 is the 3rd data //do nothing #if 0//test for(j=0;j<3;j++){ k = 2000; while(k){ fcs = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SFCSR); if(((fcs >> 12) & 0xf) >= 3) break; udelay(1); k--; } for(i=0;i<3;i++){ tmp[i + j*3] = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSI1SRX0); } } for(i=0;i<8;i++){ printk("%x ",tmp[i]);} printk("/nRX FIFO./n"); #endif } #endif /*! * This function is called whenever a new audio block needs to be * transferred from wm9712. The function receives the address and the size * of the block that will store the audio samples and start a new DMA transfer. * * @param substream pointer to the structure of the current stream. * */ static void audio_capture_dma(audio_stream_t * s) { snd_pcm_substream_t *substream; snd_pcm_runtime_t *runtime; unsigned int dma_size; unsigned int offset; int ret = 0; mxc_dma_requestbuf_t dma_request; substream = s->substream; runtime = substream->runtime; memset(&dma_request, 0, sizeof(mxc_dma_requestbuf_t)); if (s->active) { dma_size = frames_to_bytes(runtime, runtime->period_size); offset = dma_size * s->period; snd_assert(dma_size <= DMA_BUF_SIZE,); #if DEBUG_MX27_AC97_CAPTURE //printk("s->period=%x,periods=%d/n",s->period, runtime->periods); //printk("period_size=%d,dma_bytes=%d/n", // (unsigned int)runtime->period_size,runtime->dma_bytes); //printk("period_step=%d,rate=%d/n", // (unsigned int)runtime->period_step,runtime->rate); //printk("dma addr=%x,num=%d/n", // dma_request.dst_addr, dma_request.num_of_bytes); debug_cap.trans = (dma_size * 72000) /debug_cap.actual_rate; //== dma_size*(48000/actual_rate)*(3/2). //FIXME:coeffcient is 48k relative. julian. if(debug_cap.doing_buf1 == 0){ dma_request.dst_addr = debug_cap.addr_phys; }else{ dma_request.dst_addr = debug_cap.addr_phys2; } dma_request.src_addr = (dma_addr_t) (SSI1_BASE_ADDR + MXC_SSI1SRX0); dma_request.num_of_bytes = debug_cap.trans; mxc_dma_config(s->dma_channel, &dma_request, 1, MXC_DMA_MODE_READ); if(debug_cap.trigger) capture_fifo_align(); ////// #else dma_request.dst_addr = (dma_addr_t) (dma_map_single(NULL, runtime->dma_area + offset, dma_size, DMA_FROM_DEVICE)); dma_request.src_addr = (dma_addr_t) (SSI1_BASE_ADDR + MXC_SSI1SRX0); dma_request.num_of_bytes = dma_size; mxc_dma_config(s->dma_channel, &dma_request, 1, MXC_DMA_MODE_READ); #endif ret = mxc_dma_enable(s->dma_channel); s->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ if (ret) { printk("audio_process_dma: cannot queue DMA buffer (%i)/n", ret); return; } s->period++; s->period %= runtime->periods; } } /*! * This is a callback which will be called when a TX transfer finishes. * The call occurs in interrupt context. * @param dat pointer to the structure of the current stream. */ static void audio_playback_dma_callback(void *data, int error, unsigned int count) { audio_stream_t *s; snd_pcm_substream_t *substream; snd_pcm_runtime_t *runtime; unsigned int dma_size; unsigned int previous_period; unsigned int offset; ENTRY(0); s = data; substream = s->substream; runtime = substream->runtime; previous_period = s->periods; dma_size = frames_to_bytes(runtime, runtime->period_size); offset = dma_size * previous_period; s->tx_spin = 0; s->periods++; s->periods %= runtime->periods; /* * Give back to the CPU the access to the non cached memory */ dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, DMA_TO_DEVICE); /* * 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->substream); spin_lock(&s->dma_lock); /* * Trig next DMA transfer */ audio_playback_dma(s); spin_unlock(&s->dma_lock); } /*! * This is a callback which will be called when a RX transfer finishes. * The call occurs in interrupt context. * @param substream pointer to the structure of the current stream. */ static void audio_capture_dma_callback(void *data, int error, unsigned int count) { audio_stream_t *s; snd_pcm_substream_t *substream; snd_pcm_runtime_t *runtime; unsigned int dma_size; unsigned int previous_period; unsigned int offset; #if DEBUG_MX27_AC97_CAPTURE int i,j; unsigned int total_length, debug_step; unsigned char *p, *dbg; #endif s = data; substream = s->substream; runtime = substream->runtime; previous_period = s->periods; dma_size = frames_to_bytes(runtime, runtime->period_size); offset = dma_size * previous_period; s->tx_spin = 0; s->periods++; s->periods %= runtime->periods; #if DEBUG_MX27_AC97_CAPTURE //printk("s->period=%x,bufsiz=%d/n",s->period, runtime->buffer_size); //printk("addr=%x,num=%d,====/n",runtime->dma_addr, dma_size); total_length = debug_cap.trans; p = runtime->dma_area + offset; if(debug_cap.doing_buf1 == 0){ dbg = (unsigned char *)debug_cap.addr_virt; debug_cap.doing_buf1 = 1; }else{ dbg = (unsigned char *)debug_cap.addr_virt2; debug_cap.doing_buf1 = 0; } //Trig next DMA transfer, using ping-pong buffer debug_cap.trigger = 0; spin_lock(&s->dma_lock); audio_capture_dma(s); spin_unlock(&s->dma_lock); //re-order data //re-order after trigger so that pcm-data will not lost. debug_step = (288000/debug_cap.actual_rate); //== (48000/actual_rate)*(3/2)*4 //transform for(i=0,j=0;iactive) snd_pcm_period_elapsed(s->substream); #else /* * Give back to the CPU the access to the non cached memory */ dma_unmap_single(NULL, runtime->dma_addr + offset, dma_size, DMA_FROM_DEVICE); /* * 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->substream); spin_lock(&s->dma_lock); /* * Trig next DMA transfer */ audio_capture_dma(s); spin_unlock(&s->dma_lock); #endif } static int mx27_codec_open(snd_pcm_substream_t * substream) { mx27_codec_t* codec = snd_pcm_substream_chip (substream); snd_pcm_runtime_t* runtime = substream->runtime; int stream_id = substream->pstr->stream; int result; int err; ENTRY (0); codec->s[stream_id].substream = substream; if (stream_id == SNDRV_PCM_STREAM_PLAYBACK){ runtime->hw = mx27_codec_playback_hw; if ((err = configure_write_channel(&codec->s[SNDRV_PCM_STREAM_PLAYBACK], audio_playback_dma_callback)) < 0) return err; } else if(stream_id == SNDRV_PCM_STREAM_CAPTURE){ runtime->hw = mx27_codec_capture_hw; if ((err = configure_read_channel(&codec->s[SNDRV_PCM_STREAM_CAPTURE], audio_capture_dma_callback)) < 0) return err; } else return -ENODEV; result = snd_pcm_hw_constraint_integer(runtime,SNDRV_PCM_HW_PARAM_PERIODS); if (result < 0) return result; result = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); return result < 0 ? result : 0; } static int mx27_codec_close (snd_pcm_substream_t * substream) { mx27_codec_t* codec = snd_pcm_substream_chip (substream); int stream_id = substream->pstr->stream; int channel=-1; int result ; ENTRY(1); codec->s[substream->pstr->stream].substream = NULL; channel=codec->s[substream->pstr->stream].dma_channel; result=mxc_dma_free(channel); if (stream_id == SNDRV_PCM_STREAM_PLAYBACK){ ssi_interrupt_disable(SSI_CODEC, ssi_tx_dma_interrupt_enable); ssi_interrupt_disable(SSI_CODEC, ssi_tx_fifo_0_empty); } if (stream_id == SNDRV_PCM_STREAM_CAPTURE){ ssi_interrupt_disable(SSI_CODEC, ssi_rx_dma_interrupt_enable); ssi_interrupt_disable(SSI_CODEC, ssi_rx_fifo_0_full); } //ssi_transmit_enable(SSI_CODEC, 0); //ssi_enable(SSI_CODEC, 0); return result < 0 ?result:0; } static int mx27_codec_hw_params (snd_pcm_substream_t* substream, snd_pcm_hw_params_t* hw_params) { snd_pcm_runtime_t *runtime; int result; ENTRY (0); runtime=substream->runtime; //printk("dma_addr=%x/n",substream->runtime->dma_addr); result = snd_pcm_lib_malloc_pages (substream, params_buffer_bytes (hw_params)); if(result<0) return result; runtime->dma_addr = virt_to_phys(runtime->dma_area); DBG(1,"%s: dma_area=%x ",__FUNCTION__, (int)substream->runtime->dma_area); DBG(1,"dma_addr=%x,dma_bytes=%d/n", substream->runtime->dma_addr,substream->runtime->dma_bytes); return result; } static int mx27_codec_hw_free (snd_pcm_substream_t* substream) { ENTRY (0); return snd_pcm_lib_free_pages (substream); } //start the codec. static int mx27_codec_trigger(snd_pcm_substream_t* substream, int cmd) { mx27_codec_t* codec = snd_pcm_substream_chip (substream); int stream_id = substream->pstr->stream; audio_stream_t* s= &codec->s[stream_id]; int result = 0; //int i, j; //unsigned int tmp[30]; DBG(0, "%s: trigger id %d cmd %d/n", __FUNCTION__, stream_id, cmd); if(stream_id==0) switch (cmd) { case SNDRV_PCM_TRIGGER_START: DBG (1, "trigger start stream %d/n", stream_id); s->tx_spin = 0; s->active = 1; audio_playback_dma(s); break; case SNDRV_PCM_TRIGGER_STOP: DBG (1, "trigger stop entry/n"); s->tx_spin=0; s->active=0; audio_playback_stop_dma(s); break; case SNDRV_PCM_TRIGGER_SUSPEND: break; case SNDRV_PCM_TRIGGER_RESUME: break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: break; default: result = -EINVAL; break; } if(stream_id==1) switch (cmd) { case SNDRV_PCM_TRIGGER_START: DBG (1, "trigger start stream %d/n", stream_id); s->tx_spin = 0; s->active = 1; #if DEBUG_MX27_AC97_CAPTURE debug_cap.doing_buf1 = 0; //use 2 buf debug_cap.trigger = 1; //do some align //ssi_interrupt_enable(SSI_CODEC, ssi_rx_dma_interrupt_enable); //ssi_interrupt_enable(SSI_CODEC, ssi_rx_fifo_0_full); #endif audio_capture_dma(s); break; case SNDRV_PCM_TRIGGER_STOP: DBG (1, "trigger stop/n"); s->tx_spin=0; s->active=0; audio_capture_stop_dma(s); break; case SNDRV_PCM_TRIGGER_SUSPEND: break; case SNDRV_PCM_TRIGGER_RESUME: break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: break; default: result = -EINVAL; break; } return result; } static int mx27_codec_prepare (snd_pcm_substream_t* substream) { mx27_codec_t* codec = snd_pcm_substream_chip (substream); snd_pcm_runtime_t* runtime = substream->runtime; audio_stream_t* s = &codec->s[substream->pstr->stream]; int id=substream->pstr->stream; //unsigned int reg; ENTRY (0); s->period = 0; s->periods = 0; //printk ("runtime rate %d Hz/n", runtime->rate); if(id==0){ //ssi_enable(SSI_CODEC, 1); //ssi_transmit_enable(SSI_CODEC, 1); ssi_interrupt_enable(SSI_CODEC, ssi_tx_dma_interrupt_enable); ssi_interrupt_enable(SSI_CODEC, ssi_tx_fifo_0_empty); set_audiodac_rate(codec,runtime->rate); } if(id==1){ #if 0 printk("stmsk=0x%x/n", __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSISTMSK)); printk("srmsk=0x%x/n", __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSISRMSK)); printk("saccst=0x%x/n", __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+0x50)); reg = __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+0x10);//disable __raw_writel(reg &(~(1<<0)),IO_ADDRESS(SSI1_BASE_ADDR)+0x10); __raw_writel(0x001,IO_ADDRESS(SSI1_BASE_ADDR)+0x58); __raw_writel(0xffffffe0,IO_ADDRESS(SSI1_BASE_ADDR)+0x4c); __raw_writel(0xffffffe0,IO_ADDRESS(SSI1_BASE_ADDR)+0x48); __raw_writel(reg |(1<<0),IO_ADDRESS(SSI1_BASE_ADDR)+0x10);//enable printk("stmsk=0x%x/n", __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSISTMSK)); printk("srmsk=0x%x/n", __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSISRMSK)); printk("saccst=0x%x/n", __raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+0x50)); #endif #if DEBUG_MX27_AC97_CAPTURE ssi_interrupt_enable(SSI_CODEC, ssi_rx_dma_interrupt_enable); ssi_interrupt_enable(SSI_CODEC, ssi_rx_fifo_0_full); //this 2 irq will be freed in codec_clode(). debug_cap.actual_rate = runtime->rate; printk("actual sample rate is %d Hz./n",runtime->rate); if( (48000 % runtime->rate) != 0){ printk("---DEBUG_MX27_AC97_CAPTURE---/n"); printk("This driver support sample rate(kHz) lists:/n"); printk("48, 24, 12 and 8./n");//??16 cannot //printk("test=%d./n",(48000 % runtime->rate)); return -1; } printk("workaroud:always set codec ADC sample rate as 48kHz./n"); set_audioadc_rate(codec,48000); #else ssi_interrupt_enable(SSI_CODEC, ssi_rx_dma_interrupt_enable); ssi_interrupt_enable(SSI_CODEC, ssi_rx_fifo_0_full); set_audioadc_rate(codec,runtime->rate); #endif } return 0; } static snd_pcm_uframes_t mx27_codec_pointer (snd_pcm_substream_t* substream) { mx27_codec_t* codec = snd_pcm_substream_chip (substream); audio_stream_t* s = (audio_stream_t *)(&(codec->s[substream->pstr->stream])); int stream_id=substream->pstr->stream; unsigned int c=0; ENTRY (3); //printk("sisr=0x%x/n",__raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSISISR)); //printk("satag=0x%x/n",__raw_readl(IO_ADDRESS(SSI1_BASE_ADDR)+MXC_SSISATAG)); if(stream_id==0){ c = audio_get_playback_dma_pos(s); DBG (3,"pos c=0x%x/n", c); } if(stream_id==1){ c = audio_get_capture_dma_pos(s); DBG(3,"pos c=%x/n", c); } return c; } static snd_pcm_ops_t mx27_codec_playback_ops = { .open = mx27_codec_open, .close = mx27_codec_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = mx27_codec_hw_params, .hw_free = mx27_codec_hw_free, .prepare = mx27_codec_prepare, .trigger = mx27_codec_trigger, .pointer = mx27_codec_pointer, }; static snd_pcm_ops_t mx27_codec_capture_ops = { .open = mx27_codec_open, .close = mx27_codec_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = mx27_codec_hw_params, .hw_free = mx27_codec_hw_free, .prepare = mx27_codec_prepare, .trigger = mx27_codec_trigger, .pointer = mx27_codec_pointer, }; /* mx27_codec_pcm_init performs the hardware initialization for the PCM device. */ static int mx27_codec_pcm_init (mx27_codec_t* codec, int device) { snd_pcm_t* pcm; int result = 0; ENTRY (0); result = snd_pcm_new (codec->card, "mx27 PCM", device, 1, 1, &pcm); if (result) goto done; snd_pcm_set_ops (pcm, SNDRV_PCM_STREAM_PLAYBACK, &mx27_codec_playback_ops); snd_pcm_set_ops (pcm, SNDRV_PCM_STREAM_CAPTURE, &mx27_codec_capture_ops); snd_pcm_lib_preallocate_pages_for_all (pcm, SNDRV_DMA_TYPE_CONTINUOUS, //((struct device *)(unsigned long)(GFP_KERNEL)), snd_dma_continuous_data(GFP_KERNEL), MAX_BUFFER_SIZE * 2 , MAX_BUFFER_SIZE * 2 ); pcm->private_data = codec; pcm->info_flags = 0; strcpy (pcm->name, "mx27 PCM"); mx27_codec_init (codec); codec->pcm = pcm; done: return result; } static void mx27_codec_free (snd_card_t *card) { mx27_codec_t* codec = card->private_data; ENTRY (0); card->private_data = NULL; mx27_codec = NULL; kfree (codec); printk("ac97 codec free done./n"); } static int __init mx27_codec_driver_init (void) { int result; snd_card_t* card; ENTRY (0); udelay(10); mx27_hw_config(); wm9712_init();//julian #if DEBUG_MX27_AC97_CAPTURE debug_cap.length = 10 * DMA_BUF_SIZE; //when rate =48k, we get 2 useful data in 3 received data. (*1.5) //when rate =8k, we get 2 useful data in 18 received data. (*9) debug_cap.addr_virt = dma_alloc_coherent(NULL, debug_cap.length, &debug_cap.addr_phys, GFP_DMA | GFP_KERNEL); debug_cap.addr_virt2 = dma_alloc_coherent(NULL, debug_cap.length, &debug_cap.addr_phys2, GFP_DMA | GFP_KERNEL); #endif card = snd_card_new (-1, id, THIS_MODULE, sizeof (mx27_codec_t*)); if (card == NULL) return -ENOMEM; mx27_codec = kcalloc (1, sizeof (mx27_codec_t), GFP_KERNEL); if (mx27_codec == NULL) return -ENOMEM; card->private_data = (void*) mx27_codec; card->private_free = mx27_codec_free; mx27_codec->card = card; result = mx27_codec_pcm_init (mx27_codec, 0); if (result < 0) goto fail; result = mx27_codec_ac97_init (mx27_codec); if (result < 0) goto fail; strcpy(card->driver, "MXC"); strcpy(card->shortname, "wm9712-alsa"); sprintf(card->longname, "MX27 with wm9712"); result = snd_card_register(card); if (result < 0) goto fail; return 0; fail: snd_card_free (card); #if DEBUG_MX27_AC97_CAPTURE dma_free_coherent(NULL, debug_cap.length, debug_cap.addr_virt, debug_cap.addr_phys); dma_free_coherent(NULL, debug_cap.length, debug_cap.addr_virt2, debug_cap.addr_phys2); #endif return result; } static void __exit mx27_codec_driver_exit (void) { ENTRY (0); snd_card_free (mx27_codec->card); #if DEBUG_MX27_AC97_CAPTURE dma_free_coherent(NULL, debug_cap.length, debug_cap.addr_virt, debug_cap.addr_phys); dma_free_coherent(NULL, debug_cap.length, debug_cap.addr_virt2, debug_cap.addr_phys2); #endif } module_init(mx27_codec_driver_init); module_exit(mx27_codec_driver_exit); MODULE_AUTHOR("zhong.py&deng.tj"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("mx27-wm9712/9715 driver for ALSA");