📄 x900-wm8976.c
字号:
/* * Driver for PhilipsWM8976 on JADE-x900 soundcard * Copyright (C) 2002 Tomas Kasparek <tomas.kasparek@seznam.cz> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License. * * History: * * * 2008-02-19 yolanda initial release * *//* $Id: x900-wm8976.c 2 2008-02-19 03:58:36Z root $ *//***************************************************************************************************** To understand what Alsa Drivers should be doing look at "Writing an Alsa Driver" by Takashi Iwai* available in the Alsa doc section on the website * * A few notes to make things clearer. The UDA1341 is hooked up to Serial port 4 on the SA1100.* We are using SSP mode to talk to the UDA1341. The UDA1341 bit & wordselect clocks are generated* by this UART. Unfortunately, the clock only runs if the transmit buffer has something in it.* So, if we are just recording, we feed the transmit DMA stream a bunch of 0x0000 so that the* transmit buffer is full and the clock keeps going. The zeroes come from FLUSH_BASE_PHYS which* is a mem loc that always decodes to 0's w/ no off chip access.** Some alsa terminology:* frame => num_channels * sample_size e.g stereo 16 bit is 2 * 16 = 32 bytes* period => the least number of bytes that will generate an interrupt e.g. we have a 1024 byte* buffer and 4 periods in the runtime structure this means we'll get an int every 256* bytes or 4 times per buffer.* A number of the sizes are in frames rather than bytes, use frames_to_bytes and* bytes_to_frames to convert. The easiest way to tell the units is to look at the* type i.e. runtime-> buffer_size is in frames and its type is snd_pcm_uframes_t* * Notes about the pointer fxn:* The pointer fxn needs to return the offset into the dma buffer in frames.* Interrupts must be blocked before calling the dma_get_pos fxn to avoid race with interrupts.** Notes about pause/resume* Implementing this would be complicated so it's skipped. The problem case is:* A full duplex connection is going, then play is paused. At this point you need to start xmitting* 0's to keep the record active which means you cant just freeze the dma and resume it later you'd* need to save off the dma info, and restore it properly on a resume. Yeach!** Notes about transfer methods:* The async write calls fail. I probably need to implement something else to support them?* ***************************************************************************************************/#include <linux/config.h>#include <sound/driver.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/errno.h>#include <linux/ioctl.h>#include <linux/delay.h>#include <linux/slab.h>#ifdef CONFIG_PM#include <linux/pm.h>#endif#include <asm/hardware.h>#include <asm/arch/platform.h>#include <asm/mach-types.h>#include <asm/dma.h>#include <asm/io.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/initval.h>#include <sound/wm8976.h>#undef DEBUG_MODE#undef DEBUG_FUNCTION_NAMES/* * FIXME: Is this enough as autodetection of 2.4.X-rmkY-hhZ kernels? * We use DMA stuff from 2.4.18-rmk3-hh24 here to be able to compile this * module for Familiar 0.6.1 *//* {{{ Type definitions */MODULE_AUTHOR("yolanda <zouzf@hotmail.com>");MODULE_LICENSE("GPL");MODULE_DESCRIPTION("x900 + WM8976driver for ALSA");MODULE_SUPPORTED_DEVICE("{{WM8976,JADE X900 WM8976}}");static char *id = NULL; /* ID for this card */#define X900_WM8976_DRIVER "x900_wm8976"module_param(id, charp, 0444);MODULE_PARM_DESC(id, "ID string for X900 + WM8976 soundcard.");#define IIS_BASE_HW 0x2002a000#define PHY_SYS_BASE 0x20031000#define IIS_CR0 ((volatile int *)(IIS_BASE + 0x000))#define IIS_CR1 ((volatile int *)(IIS_BASE + 0x004))#define IIS_SDR ((volatile int *)(IIS_BASE + 0x008))#define IIS_SSR ((volatile int *)(IIS_BASE + 0x00C))#define IIS_CPSR ((volatile int *)(IIS_BASE + 0x010))#define IIS_IMSC ((volatile int *)(IIS_BASE + 0x014))#define IIS_RIS ((volatile int *)(IIS_BASE + 0x018))#define IIS_MIS ((volatile int *)(IIS_BASE + 0x01C))#define IIS_ICR ((volatile int *)(IIS_BASE + 0x020))#define IIS_DMACR ((volatile int *)(IIS_BASE + 0x024))u32 IIS_BASE;typedef struct audio_stream{ snd_pcm_substream_t * substream; void *dma_done; spinlock_t dma_lock; struct ver_dma_param pa; int dmach; char *id; /* identification string */ int stream_id; /* numeric identification */ 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 */ int tx_spin; /* are we recoding - flag used to do DMA trans. for sync */ unsigned int old_offset; }audio_stream_t;typedef struct snd_card_x900_wm8976 { snd_card_t *card; struct wm8976_t *wm8976; snd_pcm_t *pcm; long samplerate; audio_stream_t s[2]; /* playback & capture */} x900_wm8976_t;static struct snd_card_x900_wm8976 *x900_wm8976 = NULL;static unsigned int rates[] = { 8000, 12000, 16000,22000, 24000, 32000, 48000,};static snd_pcm_hw_constraint_list_t hw_constraints_rates = { .count = ARRAY_SIZE(rates), .list = rates, .mask = 0,};/* }}} *//* {{{ Clock and sample rate stuff */static void x900_wm8976_set_audio_clock(long val){ int tmp_scr,tmp_cpsr; tmp_cpsr=(*IIS_CPSR); tmp_scr =(*IIS_CR0)& 0x00ff; switch (val) { case 8000: (*IIS_CR0) = tmp_scr | 0x0300; (*IIS_CPSR) = 0x00018; break; case 12000: (*IIS_CR0) = tmp_scr | 0x0300; (*IIS_CPSR) = 0x00010; case 16000: (*IIS_CR0) = tmp_scr | 0x0300; (*IIS_CPSR) = 0x00018; case 24000: (*IIS_CR0) = tmp_scr | 0x0300; (*IIS_CPSR) = 0x0008; case 32000: (*IIS_CR0) = tmp_scr | 0x0500; (*IIS_CPSR) = 0x0004; break; case 48000: (*IIS_CR0) = tmp_scr | 0x0300; (*IIS_CPSR) = 0x00004; break; }}static void x900_wm8976_set_samplerate(x900_wm8976_t *x900_wm8976, long rate){ unsigned char wm8976_for_samplerate[]={0x0e,0x00}; /* wait for any frame to complete */ udelay(125); if (rate >= 48000) rate = 48000; else if (rate >= 44100) rate = 44100; else if (rate >= 32000) rate = 32000; else if (rate >= 24000) rate = 24000; else if (rate >= 16000) rate = 16000; else if (rate >= 12000) rate = 12000; else rate = 8000; /* Set the external clock generator */ x900_wm8976_set_audio_clock(rate); /* Select the clock divisor */ switch (rate) { case 8000: wm8976_for_samplerate[1]=0x0a; break; case 12000: wm8976_for_samplerate[1]=0x08; case 16000: wm8976_for_samplerate[1]=0x5; case 24000: wm8976_for_samplerate[1]=0x04; case 32000: wm8976_for_samplerate[1]=0x02; case 48000: wm8976_for_samplerate[1]= 0x00; break; } /* FMT setting should be moved away when other FMTs are added (FIXME) */ jade_command(x900_wm8976->wm8976,wm8976_for_samplerate); x900_wm8976->samplerate = rate;}/* }}} *//* {{{ HW init and shutdown */static void x900_wm8976_audio_init(x900_wm8976_t *x900_wm8976){ x900_wm8976->s[SNDRV_PCM_STREAM_PLAYBACK].id = "WM8976 out"; x900_wm8976->s[SNDRV_PCM_STREAM_PLAYBACK].stream_id = SNDRV_PCM_STREAM_PLAYBACK; x900_wm8976->s[SNDRV_PCM_STREAM_CAPTURE].id = "WM8976 in"; x900_wm8976->s[SNDRV_PCM_STREAM_CAPTURE].stream_id = SNDRV_PCM_STREAM_CAPTURE; /* Initialize the wm8976 internal state */ jade_open(); }static void x900_wm8976_audio_shutdown(x900_wm8976_t *x900_wm8976){ /* disable the audio power and all signals leading to the audio chip */ //audio_stop_dma(&x900_wm8976->s[SNDRV_PCM_STREAM_PLAYBACK]); //audio_stop_dma(&x900_wm8976->s[SNDRV_PCM_STREAM_CAPTURE]); jade_close(x900_wm8976->wm8976);}/* }}} *//* * these are the address and sizes used to fill the xmit buffer * so we can get a clock in record only mode */#define FORCE_CLOCK_ADDR (dma_addr_t)FLUSH_BASE_PHYS#define FORCE_CLOCK_SIZE 4096 // was 2048#define DMA_BUF_SIZE 4096 #define DMA_LOOP_NUM 4static void audio_dma_free(audio_stream_t *s){ ver_free_dma((s)->dmach); ver_end_dma(s->dmach);}/* * this stops the dma and clears the dma ptrs */static void audio_stop_dma(audio_stream_t *s){ unsigned long flags; spin_lock_irqsave(&s->dma_lock, flags); s->active = 0; s->period = 0; /* this stops the dma channel and clears the buffer ptrs */ audio_dma_free(s); spin_unlock_irqrestore(&s->dma_lock, flags);}static int audio_dma_request(audio_stream_t *s, int play){ s->dmach=ver_request_dma(0, "iis_p", 2, 3); if(s->dmach < 0){ debug("<1>i2s dma error\n"); return -1; }else{ printk("SOUND: i2s dma chan: %d\n", s->dmach); } memset(&s->pa, 0, sizeof(s->pa)); if (play){ s->pa.SWidth = DMAC_TSIZE_WORD; s->pa.DWidth = DMAC_TSIZE_HALFWORD; s->pa.SBsize = 0x1; s->pa.DBsize = 0x4; s->pa.SIncr = 1; s->pa.DIncr = 0; }else{ s->pa.SWidth = DMAC_TSIZE_HALFWORD; s->pa.DWidth = DMAC_TSIZE_WORD; s->pa.SBsize = 0x4; s->pa.DBsize = 0x1; s->pa.SIncr = 0; s->pa.DIncr = 1; } s->pa.FlowCntrl = FLOW_MEM_PER_DMAC; if (ver_set_dma(s->dmach, &s->pa) != 0) { debug("<1>i2s write:set play dma fail:\n"); return -1; }else printk("SOUND: set dma OK\n"); return 0;}static int audio_dma_start(audio_stream_t *chip, void (*callback)(void *),int stream_id){ snd_pcm_runtime_t *runtime=chip->substream->runtime; unsigned int dma_size; unsigned int offset; int ret;// chip->dma_done=NULL; #if 0 DECLARE_COMPLETION(chip->dma_done);#endif dma_size = frames_to_bytes(runtime, runtime->period_size); offset = dma_size *chip->period; chip->period++; chip->period %= runtime->periods; chip->periods++; chip->pa.sg_len = 0; chip->pa.tsize = dma_size; if (stream_id) { chip->pa.sbuf =(void *)(0x2002a008); chip->pa.dbuf = runtime->dma_addr + offset; } else{ chip->pa.sbuf =runtime->dma_addr + offset; chip->pa.dbuf = (void *)(0x2002a008); } chip->pa.trans_done = callback; chip->pa.done_data = chip; if (ver_start_dma(chip->dmach, &chip->pa) != 0) { printk("<1>sound:start dma fail:\n"); return -1; } return 0; }static void audio_dma_play_done(void *data){ audio_stream_t *s = data; snd_pcm_substream_t * substream = s->substream; //snd_pcm_runtime_t *runtime=substream->runtime; //unsigned int dma_size,offset; spin_lock(&s->dma_lock); if (!s->tx_spin && s->periods > 0) s->periods--; snd_pcm_period_elapsed(s->substream); audio_dma_start(s,audio_dma_play_done,0); spin_unlock(&s->dma_lock);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -