Hi I am writing my audio driver for my board: TMPA910 CPU and WM8960 codec. when I run pcm_min.c, there is an error: "Input/Output error". I find that error because no DMA interrupt appear, printk() in DMA interrupt program no appear I have already set bit IE and ITC in DMA Configuaration register but no change. Here is my source code. Any idea, what could be the reason ?
#include <linux/slab.h> #include <linux/delay.h> #include <linux/dma-mapping.h> #include <asm/bug.h> #include <asm/dma.h> #include <asm/cacheflush.h> #include <asm/arch/dma.h> #include <asm/arch/tmpa910-regs.h> #include "tmpa910_i2s.h" #define TMPA910_I2S_DEBUG #ifdef TMPA910_I2S_DEBUG #define i2s_printd(level, format, arg...) \ printk(level "i2s: " format, ## arg) #define I2S_ASSERT(expr) \ do { \ if (unlikely(!(expr))) { \ printk(KERN_ERR "%s: %d, bug\n", __FUNCTION__, __LINE__); \ } \ } while(0) #else #define i2s_printd(level, format, arg...) #define I2S_ASSERT(expr) #endif int tmpa910_i2s_config_tx(struct tmpa910_i2s *i2s) { return 0; } static void setup_tx_desc(struct scatter_dma_t *desc, unsigned int phydesc, unsigned int phy_buf, int fragcount, size_t fragsize) { int i; for (i=0; i<fragcount; i++) { desc[i].lli = (unsigned long)(phydesc + (i + 1) * sizeof(struct scatter_dma_t)); desc[i].srcaddr = (unsigned long)(phy_buf + i*fragsize); //Phy Addr desc[i].dstaddr = (unsigned long)I2STDAT_ADR; desc[i].control = 0x84492000 + (unsigned long)(fragsize >> 2); } /* make circular */ desc[fragcount-1].lli = (unsigned long)phydesc; printk("\nphydesc= %x\nphy_buff=%x\nragcount=%x\nfragsize=%x\n",phydesc,phy_buf,fragcount,fragsize); } static int i2s_start(struct tmpa910_i2s *i2s) { int i; tmpa910_dma_enable(i2s->dma_tx_ch); /* I2S DMA set complete */ I2STDMA1 = 0x0001; /* I2S transfer start */ I2STSLVON = 0x0001; //for(i=0;i<=10000;i++) //{ // printk("\nI2STST = %d\n",I2STST); // printk("\nI2SRST = %d\n",I2SRST); //} printk("====> Start I2S\n"); return 0; } static int i2s_stop(struct tmpa910_i2s *i2s) { I2STDMA1 = 0x0000; I2STSLVON = 0x0000; tmpa910_dma_disable(i2s->dma_tx_ch); printk("====> Stop I2S\n"); return 0; } static inline int i2s_tx_dma_start(struct tmpa910_i2s *i2s) { int dma_ch = i2s->dma_tx_ch; struct scatter_dma_t *dma_desc; i2s->curr_tx_desc = i2s->dma_tx_desc; dma_desc = i2s->curr_tx_desc; DMA_SRC_ADDR(dma_ch) = dma_desc->srcaddr; DMA_DEST_ADDR(dma_ch) = dma_desc->dstaddr; DMA_LLI(dma_ch) = dma_desc->lli; DMA_CONTROL(dma_ch) = dma_desc->control; //DMA_CONFIG(dma_ch) = 0x00008a81; DMA_CONFIG(dma_ch) = 0x0000Ca81; printk("\nstart dma: srcaddr = %x",DMA_SRC_ADDR(dma_ch)); printk("\nstart dma: dstaddr = %x",DMA_DEST_ADDR(dma_ch)); printk("\nstart dma: lli = %x",DMA_LLI(dma_ch)); printk("\nstart dma: control = %x",DMA_CONTROL(dma_ch)); printk("\nstart dma: config = %x\n",DMA_CONFIG(dma_ch)); return 0; } int tmpa910_i2s_tx_start(struct tmpa910_i2s *i2s) { i2s_printd(KERN_INFO, "%s: tx_run:%d\n", __FUNCTION__, i2s->tx_run); printk("%s: tx_run:%d\n", __FUNCTION__, i2s->tx_run); if (i2s->tx_run) return -EBUSY; i2s_tx_dma_start(i2s); i2s_start(i2s); i2s->tx_run = 1; /* int dma_ch = i2s->dma_tx_ch; struct scatter_dma_t *dma_desc; I2SCOMMON = 0x19; // IISSCLK = Fosch(X1), Set SCK/WS/CLKO of Tx and Rx as Common I2STCON = 0x00; // IIS Standard Format I2SRCON = 0x00; // IIS Standard Format I2SRMS = 0x00; // Slave I2STMS = 0x01; // Master I2STMCON = 0x04; // I2SMCLK = Fosch/4 = 11.2896MHz // I2SSCLK = 11.2896MHz/8 = 1411.2KHz // I2SWS = 1411.2KHz/32 = 44.1KHz //I2SRMCON = 0x04; //I2STCON = 0x00; // IIS Standard Format I2STFCLR = 0x01; // Clear FIFO DMA_SRC_ADDR(dma_ch) = dma_desc[0]->srcaddr; DMA_DEST_ADDR(dma_ch) = dma_desc[0]->dstaddr; //DMA_LLI(dma_ch) = dma_desc->lli; DMA_CONTROL(dma_ch) = dma_desc[0]->control; DMA_CONFIG(dma_ch) = 0x00008a81; I2STSLVON = 0x0000 */ return 0; } int tmpa910_i2s_tx_stop(struct tmpa910_i2s *i2s) { if (!i2s->tx_run) return 0; /* Both rx and tx dma stopped */ i2s_stop(i2s); i2s->curr_tx_desc = NULL; i2s->tx_run = 0; return 0; } int tmpa910_i2s_config_tx_dma(struct tmpa910_i2s *i2s, unsigned char *cpu_buf, unsigned int phy_buf, int fragcount, size_t fragsize, size_t size) { unsigned int count; dma_addr_t addr; u32 *map_cpu = (u32 *)cpu_buf; int i; i2s_printd(KERN_INFO, "%s( %p, %X, %d, %x, %x )\n", __FUNCTION__, cpu_buf, phy_buf, fragcount, fragsize, size); printk("%s( %p, %X, %d, %x, %x )\n", __FUNCTION__, cpu_buf, phy_buf, fragcount, fragsize, size); count = fragsize / size; /* for fragments larger than 16k words we use 2d dma, * denote fragecount as two numbers' mutliply and both of them * are less than 64k.*/ if (count >= 0x1000) { printk("Error: tx dma size too large %d\n", count); return -EINVAL; } if (i2s->dma_tx_desc) { dma_free_coherent(NULL, i2s->tx_desc_bytes, i2s->dma_tx_desc, 0); } i2s->dma_tx_desc = dma_alloc_coherent(NULL, fragcount * sizeof(struct scatter_dma_t), &addr, 0); i2s->tx_desc_bytes = fragcount * sizeof(struct scatter_dma_t); i2s->dma_tx_phydesc = addr; i2s->dma_tx_buf = phy_buf; if (!i2s->dma_tx_desc) { return -ENOMEM; } setup_tx_desc(i2s->dma_tx_desc, addr, phy_buf, fragcount, fragsize); //²âÊÔ³ÌÐò //memset(cpu_buf, 0x50, fragcount * fragsize); for(i=0; i<fragcount*fragsize/4; i++) { map_cpu[i] = (i << 2); //printk("\nmap_cpu[%x]= %d\n",i,map_cpu[i]); } printk("End i2s config tx dma "); return 0; } unsigned int tmpa910_i2s_curr_offset_tx(struct tmpa910_i2s *i2s) { int dma_ch = i2s->dma_tx_ch; unsigned int addr, size; addr = DMA_SRC_ADDR(dma_ch); size = addr - i2s->dma_tx_buf; printk("size[%d]", size); return size; } static int i2s_check_status(struct tmpa910_i2s *i2s, unsigned int *i2s_stat, unsigned int *rx_stat, unsigned int *tx_stat) { int status = 0; return status; } static void tx_handler(int dma_ch, void *dev_id) { unsigned int tx_stat; struct tmpa910_i2s *i2s = dev_id; printk("DMA tx interrupt handler"); printk("%s\n", __FUNCTION__); i2s_check_status(i2s, NULL, NULL, &tx_stat); if (i2s->tx_callback) { i2s->tx_callback(i2s->data); } } static void err_handler(int dma_ch, void *dev_id) { unsigned int status; struct tmpa910_i2s *i2s = dev_id; printk("\nDMA error handler\n"); i2s_printd(KERN_INFO, "%s\n", __FUNCTION__); printk("%s\n", __FUNCTION__); if (i2s_check_status(i2s, &status, NULL, NULL)) { printk(KERN_ERR "error checking status ??"); printk("error checking status ??"); return; } if (i2s->err_callback) i2s->err_callback(i2s->data); } struct tmpa910_i2s *tmpa910_i2s_init( int dma_rx, void (*rx_callback)(void*), int dma_tx, void (*tx_callback)(void*), int err_irq, void (*err_callback)(void*), void *data) { struct tmpa910_i2s *i2s; i2s = kmalloc(sizeof(struct tmpa910_i2s), GFP_KERNEL); if (i2s == NULL) return NULL; memset(i2s, 0, sizeof(struct tmpa910_i2s)); i2s->dma_tx_ch = tmpa910_dma_request("I2S TX", 1, tx_handler, err_handler, i2s); if (i2s->dma_tx_ch < 0) { printk(KERN_ERR "unable to tx audio dma 0x%x\n", dma_tx); printk("unable to tx audio dma 0x%x\n", dma_tx); goto __init_err2; } printk("dma_tx_ch = %d\n", i2s->dma_tx_ch); i2s->err_irq = err_irq; i2s->rx_callback = rx_callback; i2s->tx_callback = tx_callback; i2s->err_callback = err_callback; i2s->data = data; //printk("dma tx: %p\n", i2s->dma_tx_ch); return i2s; __init_err2: kfree(i2s); return NULL; } void tmpa910_i2s_free(struct tmpa910_i2s *i2s) { if (i2s == NULL) return; i2s_stop(i2s); if (i2s->dma_tx_desc) { dma_free_coherent(NULL, i2s->tx_desc_bytes, i2s->dma_tx_desc, 0); } tmpa910_dma_free(i2s->dma_tx_ch); kfree(i2s); }
#include <linux/init.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/dma-mapping.h> #include <linux/platform_device.h> #include <asm/irq.h> #include <asm/delay.h> #include <sound/driver.h> #include <sound/core.h> #include <sound/info.h> #include <sound/control.h> #include <sound/pcm.h> #define SNDRV_GET_ID #include <sound/initval.h> #include <asm/arch/dma.h> #include <asm/arch/irqs.h> #include <asm/arch/tmpa910-regs.h> #include "tmpa910_i2s.h" #include "wm8976.h" #define I2S_DMA_RX I2S0 #define I2S_DMA_TX I2S1 #define I2S_IRQ_ERR I2S_INT #define CONFIG_SND_DEBUG //Debug #ifdef CONFIG_SND_DEBUG #define snd_printk_marker() snd_printk(KERN_INFO "\n%s\n", __FUNCTION__) #else #define snd_printk_marker() #endif #undef CONFIG_SND_DEBUG_CURRPTR /* causes output every frame! */ //#define CONFIG_SND_DEBUG_CURRPTR #undef NOCONTROLS /* define this to omit all the ALSA controls */ #define DRIVER_NAME "WM8976-I2S" #define CHIP_NAME "Wolfson WM8976" #define PCM_NAME "WM8976_PCM" /* Only one WM8976 soundcard is supported */ static struct platform_device *device = NULL; /* Chip level */ #define WM8976_BUF_SZ 0x10000 /* 64kb */ #define PCM_BUFFER_MAX (WM8976_BUF_SZ / 2) #define CHANNELS_OUTPUT 2 #define CHANNELS_INPUT 2 #define FRAGMENTS_MIN 2 #define FRAGMENTS_MAX 32 #define AUDIO_RATE_DEFAULT 44100 typedef struct snd_wm8976 wm8976_t; typedef struct snd_pcm_substream snd_pcm_substream_t; typedef struct snd_pcm_hardware snd_pcm_hardware_t; typedef struct snd_pcm_hw_params snd_pcm_hw_params_t; typedef struct snd_pcm_runtime snd_pcm_runtime_t; typedef struct snd_pcm_ops snd_pcm_ops_t; struct snd_wm8976 { struct snd_card *card; struct tmpa910_i2s *i2s; spinlock_t wm8976_lock; struct snd_pcm *pcm; int poll_reg; /* index of the wm8976 register last queried */ /* if non-null, current subtream running */ snd_pcm_substream_t *rx_substream; /* if non-null, current subtream running */ snd_pcm_substream_t *tx_substream; }; /*======================================================*/ /* WM8976 I2C INTERFACE DEFINE */ /*======================================================*/ #define I2C0SR_BUS_CHECK 0x00000020 #define I2C0SR_BUS_FREE 0x00000000 #define I2C0SR_ACK_CHECK 0x00000010 #define I2C0SR_ACK_ENABLE 0x00000000 static void i2c_bus_free_chk(void) { u32 reg_data; do { reg_data = I2C0SR; }while( (reg_data & I2C0SR_BUS_CHECK) != I2C0SR_BUS_FREE ); } static void i2c_ack_wait(void) { u32 reg_data; printk("\ni2c: wait .... "); do { reg_data = I2C0SR; }while( (reg_data & I2C0SR_ACK_CHECK) != I2C0SR_ACK_ENABLE ); } static void i2c_packet_send(u8 sub_addr, u8 data) { printk("\ni2c: begin send packet"); I2C0DBR = 0x34; I2C0CR2 = 0xf8; i2c_ack_wait(); I2C0DBR = sub_addr; i2c_ack_wait(); I2C0DBR = data; i2c_ack_wait(); I2C0CR2 = 0xd8; i2c_bus_free_chk(); printk("\ni2c: stop send packet"); } static void init_wm8976_i2c(void) { unsigned long flags; local_irq_save(flags); printk("\nInit I2S\n"); /* I2S Register Set */ GPIOLFR1 = 0x1f; // Config IIS Function Port: I2SSCLK,I2S0MCLK,I2S0DATI,I2S0CLK,I2S0WS GPIOMFR1 = 0x04; // Config IIS Function Port: I2S1DATO I2SCOMMON = 0x19; // IISSCLK = Fosch(X1), Set SCK/WS/CLKO of Tx and Rx as Common I2STMCON = 0x04; // I2SMCLK = Fosch/4 = 11.2896MHz // I2SSCLK = 11.2896MHz/8 = 1411.2KHz // I2SWS = 1411.2KHz/32 = 44.1KHz I2SRMCON = 0x04; I2STCON = 0x00; // IIS Standard Format I2STFCLR = 0x01; // Clear FIFO I2SRMS = 0x00; // Slave I2STMS = 0x00; // Slave printk("\nInit I2C0\n"); /* GPIO Setting for I2C0 Pin */ GPIOCODE = 0xC0; // Open-Drain Enable GPIOCFR1 = 0xC0; // 1 GPIOCFR2 = 0x00; // 0 GPIOCIE = 0x00; // 0 I2C Interrupt Disable /* Power On WM8976: PR2 = 0*/ GPIORDIR = 0x07; GPIORFR1 = 0x00; GPIORFR2 = 0x00; GPIORDATA = 0x00; /* GPIO Setting for I2C1 Pin */ GPIOFODE = 0xC0; GPIOFFR1 = 0xC0; GPIOFFR2 = 0x00; GPIOFDIR = 0x00; GPIOFIE = 0x00; local_irq_restore(flags); i2c_bus_free_chk(); I2C0CR2 = 0xc8; I2C0PRS = 0x19; I2C0CR1 = 0x13; printk("\nInit WM8960 codec\n"); i2c_packet_send(0x00, 0x00); //R0 = 0x00 i2c_packet_send(0x02, 0x2d); //R1 = 0x02d i2c_packet_send(0x04, 0x3f); //R2 = 0x03f i2c_packet_send(0x07, 0xef); //R3 = 0x1ef i2c_packet_send(0x05, 0x90); //R2 = 0x190 i2c_packet_send(0x14, 0x80); //R10 = 0x080 i2c_packet_send(0x17, 0xff); //R11 = 0x1ff i2c_packet_send(0x19, 0xff); //R12 = 0x1ff i2c_packet_send(0x30, 0x32); //R24 = 0x032 //sample rate is 44.1KHz i2c_packet_send(0x0d, 0x49); //R6 = 0x149 i2c_packet_send(0x48, 0x07); //R36 = 0x007 i2c_packet_send(0x4a, 0x21); //R37 = 0x021 i2c_packet_send(0x4d, 0x61); //R38 = 0x161 i2c_packet_send(0x4e, 0x27); //R39 = 0x027 } static void release_wm8976_i2c(void) { } /*======================================*/ /* AUDIO CLOCK INTERFACE */ static void enable_audio_sysclk(void) { } static void disable_audio_sysclk(void) { } /************************************************************* * pcm methods *************************************************************/ static snd_pcm_hardware_t snd_wm8976_playback_hw = { .info = ( SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER ), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_44100, .rate_min = 44100, .rate_max = 44100, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = PCM_BUFFER_MAX, .period_bytes_min = 0x1000, //4KB .period_bytes_max = 0x3000, //8KB .periods_min = FRAGMENTS_MIN, .periods_max = FRAGMENTS_MAX, }; static snd_pcm_hardware_t snd_wm8976_capture_hw = { .info = ( SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER ), .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_44100, .rate_min = 44100, .rate_max = 44100, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = PCM_BUFFER_MAX, .period_bytes_min = 0x1000, //4KB .period_bytes_max = 0x3000, //8KB .periods_min = FRAGMENTS_MIN, .periods_max = FRAGMENTS_MAX, }; static int snd_wm8976_playback_open(snd_pcm_substream_t *substream) { wm8976_t *chip = snd_pcm_substream_chip(substream); printk("\nsnd playback open\n"); //snd_printk_marker(); chip->tx_substream = substream; //printk("\n1232321321\n"); substream->runtime->hw = snd_wm8976_playback_hw; //printk("\n1232321321\n"); return 0; } static int snd_wm8976_capture_open(snd_pcm_substream_t *substream) { wm8976_t *chip = snd_pcm_substream_chip(substream); printk("\nsnd capture open\n"); //snd_printk_marker(); substream->runtime->hw = snd_wm8976_capture_hw; chip->rx_substream = substream; return 0; } static int snd_wm8976_playback_close(snd_pcm_substream_t *substream) { wm8976_t *chip = snd_pcm_substream_chip(substream); //snd_printk_marker(); printk("\nsnd playback close\n"); chip->tx_substream = NULL; return 0; } static int snd_wm8976_capture_close(snd_pcm_substream_t *substream) { wm8976_t *chip = snd_pcm_substream_chip(substream); //snd_printk_marker(); printk("\nsnd capture close\n"); chip->rx_substream = NULL; return 0; } //I2S in following static int snd_wm8976_hw_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hwparams) { printk("\nsnd hw params\n"); //snd_printk_marker(); /* * Allocate all available memory for our DMA buffer. * Necessary because we get a 4x increase in bytes for the 2 channel mode. * (we lie to the ALSA midlayer through the hwparams data) * We're relying on the driver not supporting full duplex mode * to allow us to grab all the memory. */ //printk("params_buffer_bytes return %d\n", params_buffer_bytes(hwparams)); if( snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hwparams)) < 0 ) return -ENOMEM; return 0; } static int snd_wm8976_hw_free(snd_pcm_substream_t * substream) { printk("\nsnd hw free\n"); //snd_printk_marker(); snd_pcm_lib_free_pages(substream); return 0; } static int snd_wm8976_playback_prepare(snd_pcm_substream_t *substream) { wm8976_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; printk("\nsnd playback prepare\n"); int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size); int err = 0; int word_len = 4; //snd_assert((substream == chip->tx_substream), return -EINVAL); printk("\n%s channels:%d, period_bytes:0x%lx, periods:%d\n", __FUNCTION__, runtime->channels, frames_to_bytes(runtime, runtime->period_size), runtime->periods); //snd_printd(KERN_INFO "%s channels:%d, period_bytes:0x%lx, periods:%d\n", // __FUNCTION__, runtime->channels, // frames_to_bytes(runtime, runtime->period_size), // runtime->periods); err = tmpa910_i2s_config_tx_dma(chip->i2s, runtime->dma_area, runtime->dma_addr, runtime->periods, fragsize_bytes, word_len); printk("\n%x %x %x %x %x\n",runtime->dma_area, runtime->dma_addr, runtime->periods, fragsize_bytes, word_len); return err; } static int snd_wm8976_capture_prepare(snd_pcm_substream_t *substream) { printk(" snd_wm8976_capture_prepare\n"); return 0; } static int snd_wm8976_playback_trigger(snd_pcm_substream_t *substream, int cmd) { wm8976_t *chip = snd_pcm_substream_chip(substream); snd_printk_marker(); spin_lock(&chip->wm8976_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: printk(" SNDRV_PCM_TRIGGER_START\n"); tmpa910_i2s_tx_start(chip->i2s); break; case SNDRV_PCM_TRIGGER_STOP: tmpa910_i2s_tx_stop(chip->i2s); printk(" SNDRV_PCM_TRIGGER_STOP\n"); break; default: spin_unlock(&chip->wm8976_lock); return -EINVAL; } spin_unlock(&chip->wm8976_lock); //snd_printd(KERN_INFO"playback cmd:%s\n", cmd?"start":"stop"); printk("\nplayback cmd:%s\n", cmd?"start":"stop"); return 0; } static int snd_wm8976_capture_trigger(snd_pcm_substream_t *substream, int cmd) { wm8976_t *chip = snd_pcm_substream_chip(substream); snd_printk_marker(); spin_lock(&chip->wm8976_lock); snd_assert(substream == chip->rx_substream, return -EINVAL); switch (cmd) { case SNDRV_PCM_TRIGGER_START: printk(" SNDRV_PCM_TRIGGER_START\n"); break; case SNDRV_PCM_TRIGGER_STOP: printk(" SNDRV_PCM_TRIGGER_STOP\n"); break; default: spin_unlock(&chip->wm8976_lock); return -EINVAL; } spin_unlock(&chip->wm8976_lock); //snd_printd(KERN_ERR"capture cmd:%s\n", cmd ? "start" : "stop"); printk("capture cmd:%s\n", cmd ? "start" : "stop"); return 0; } static snd_pcm_uframes_t snd_wm8976_playback_pointer(snd_pcm_substream_t *substream) { printk("\nsnd playback pointer\n"); wm8976_t *chip = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; int dma_ch = chip->i2s->dma_tx_ch; unsigned int offset; //offset = tmpa910_i2s_curr_offset_tx(chip->i2s); //offset = bytes_to_frames(runtime, offset); offset = bytes_to_frames(runtime, DMA_SRC_ADDR(dma_ch)-runtime->dma_addr); if (offset >= runtime->buffer_size) offset = 0; printk("\n(%x)\n", offset); printk("\nruntime dma addr = %x\n", runtime->dma_addr); return offset; } static snd_pcm_uframes_t snd_wm8976_capture_pointer(snd_pcm_substream_t *substream) { printk(" snd_wm8976_capture_pointer\n"); return 0; } /* pcm method tables */ static snd_pcm_ops_t snd_wm8976_playback_ops = { .open = snd_wm8976_playback_open, .close = snd_wm8976_playback_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_wm8976_hw_params, .hw_free = snd_wm8976_hw_free, .prepare = snd_wm8976_playback_prepare, .trigger = snd_wm8976_playback_trigger, .pointer = snd_wm8976_playback_pointer, }; static snd_pcm_ops_t snd_wm8976_capture_ops = { .open = snd_wm8976_capture_open, .close = snd_wm8976_capture_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_wm8976_hw_params, .hw_free = snd_wm8976_hw_free, .prepare = snd_wm8976_capture_prepare, .trigger = snd_wm8976_capture_trigger, .pointer = snd_wm8976_capture_pointer, }; /************************************************************* * card and device *************************************************************/ static int snd_wm8976_stop(struct snd_wm8976 *chip) { printk("\nsnd card stop\n"); snd_printk_marker(); return 0; } static int snd_wm8976_dev_free(struct snd_device *device) { struct snd_wm8976 *chip = (wm8976_t *)device->device_data; snd_printk_marker(); return snd_wm8976_stop(chip); } static struct snd_device_ops snd_wm8976_ops = { .dev_free = snd_wm8976_dev_free, }; static int snd_bf53x_wm8976_reset(wm8976_t *chip) { return 0; } static int snd_wm8976_configure(wm8976_t *chip) { int err = 0; struct tmpa910_i2s *i2s= chip->i2s; snd_printk_marker(); snd_bf53x_wm8976_reset(chip); err = err || tmpa910_i2s_config_tx(i2s); if (err) { snd_printk(KERN_ERR "Unable to set i2s configuration\n"); printk("Unable to set i2s configuration\n"); } return err; } static void snd_wm8976_dma_rx(void *data) { struct snd_wm8976 *wm8976 = data; snd_printk_marker(); if (wm8976->rx_substream) { snd_pcm_period_elapsed(wm8976->rx_substream); } } static void snd_wm8976_dma_tx(void *data) { struct snd_wm8976 *wm8976 = data; //snd_printk_marker(); printk("\nsnd_pcm_period_elapsed\n"); if (wm8976->tx_substream) { snd_pcm_period_elapsed(wm8976->tx_substream); } } static void snd_wm8976_i2s_err(void *data) { printk(KERN_ERR DRIVER_NAME ":%s: err happened on i2s\n", __FUNCTION__); printk(":%s: err happened on i2s\n", __FUNCTION__); } static int __devinit snd_wm8976_pcm(struct snd_wm8976 *wm8976) { struct snd_pcm *pcm; int err = 0; printk("\nsnd pcm new\n"); //snd_printk_marker(); /* 1 playback and 1 capture substream, of 2-8 channels each */ if((err = snd_pcm_new(wm8976->card, PCM_NAME, 0, 1, 1, &pcm)) < 0) { return err; } /* * this sets up our initial buffers and sets the dma_type to isa. * isa works but I'm not sure why (or if) it's the right choice * this may be too large, trying it for now */ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_isa_data(), WM8976_BUF_SZ, WM8976_BUF_SZ); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_wm8976_playback_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_wm8976_capture_ops); wm8976->pcm = pcm; pcm->info_flags = 0; strcpy(pcm->name, PCM_NAME); pcm->private_data = wm8976; return 0; } static int __devinit snd_wm8976_probe(struct platform_device *pdev) { int err = 0; struct snd_card *card; struct snd_wm8976 *wm8976; struct tmpa910_i2s *i2s; char * id = "ID string for TMPA910 + WM8976 soundcard."; snd_printk_marker(); if (device != NULL) return -ENOENT; card = snd_card_new(-1, id, THIS_MODULE, sizeof(struct snd_wm8976)); if (card == NULL) { snd_printdd(KERN_DEBUG "%s: snd_card_new() failed\n", __FUNCTION__); printk("%s: snd_card_new() failed\n", __FUNCTION__); return -ENOMEM; } wm8976 = card->private_data; wm8976->card = card; //wm8976->samplerate = AUDIO_RATE_DEFAULT; if ((i2s = tmpa910_i2s_init(I2S_DMA_RX, snd_wm8976_dma_rx, I2S_DMA_TX, snd_wm8976_dma_tx, I2S_IRQ_ERR, snd_wm8976_i2s_err, wm8976)) == NULL) { printk(KERN_ERR DRIVER_NAME ": Failed to find device on i2s\n"); printk(": Failed to find device on i2s\n"); err = -ENODEV; goto __i2s_err; } wm8976->i2s = i2s; err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, wm8976, &snd_wm8976_ops); if (err) { goto __nodev; } if ((err = snd_wm8976_pcm(wm8976)) < 0) { goto __nodev; } if ((err = snd_wm8976_configure(wm8976)) < 0) { printk("snd_wm8976_configure faild.\n"); goto __nodev; } strcpy(card->driver, DRIVER_NAME); strcpy(card->shortname, CHIP_NAME); sprintf(card->longname, "%s at I2S rx/tx dma %d/%d err irq %d", card->shortname, I2S_DMA_RX, I2S_DMA_TX, I2S_IRQ_ERR); printk(card->longname, "%s at I2S rx/tx dma %d/%d err irq %d", card->shortname, I2S_DMA_RX, I2S_DMA_TX, I2S_IRQ_ERR); snd_card_set_dev(card, &pdev->dev); if ((err = snd_card_register(card)) < 0) { printk("snd_card_register faild.\n"); goto __nodev; } printk("platform_set_drvdata card=%p\n", card); platform_set_drvdata(pdev, card); return 0; __nodev: tmpa910_i2s_free(i2s); __i2s_err: snd_card_free(card); return err; } static int __devexit snd_wm8976_remove(struct platform_device *pdev) { struct snd_card *card; struct snd_wm8976 *wm8976; printk(" -> snd_wm8976_remove\n"); card = platform_get_drvdata(pdev); wm8976 = card->private_data; snd_wm8976_stop(wm8976); tmpa910_i2s_free(wm8976->i2s); snd_card_free(card); platform_set_drvdata(pdev, NULL); return 0; } #define TMPA910_WM8976_DRIVER "tmpa910_wm8976" static struct platform_driver snd_wm8976_driver = { .probe = snd_wm8976_probe, .remove = __devexit_p(snd_wm8976_remove), .driver = { .name = DRIVER_NAME, }, }; static int __init snd_wm8976_init(void) { int err; init_wm8976_i2c(); enable_audio_sysclk(); if ((err = platform_driver_register(&snd_wm8976_driver)) < 0) return err; #if 0 device = platform_device_register_simple(TMPA910_WM8976_DRIVER, -1, NULL, 0); if (!IS_ERR(device)) { if (platform_get_drvdata(device)) { printk("\nsnd_wm8976_init ok\n"); return 0; } printk("\nsnd_wm8976_init error\n"); platform_device_unregister(device); err = -ENODEV; } else { err = PTR_ERR(device); } platform_driver_unregister(&snd_wm8976_driver); #endif printk("\nsnd_wm8976_init err = %d\n", err); return err; } static void __exit snd_wm8976_exit(void) { platform_device_unregister(device); platform_driver_unregister(&snd_wm8976_driver); release_wm8976_i2c(); disable_audio_sysclk(); } MODULE_AUTHOR("TOSHIBA <tmpa910@xxxxxxxxxxx>"); MODULE_DESCRIPTION("TMPA910/WM8976"); MODULE_LICENSE("GPL"); module_init(snd_wm8976_init); module_exit(snd_wm8976_exit);
_______________________________________________ Alsa-devel mailing list Alsa-devel@xxxxxxxxxxxxxxxx http://mailman.alsa-project.org/mailman/listinfo/alsa-devel