一、實驗環境
1.1 虛擬機環境
a) Vmware版本:Vmware Workstation 12.5.7
b) Ubuntu版本:9.10
c) 內核版本:2.6.31.14
d) toolchain版本:arm-linux-gcc 4.3.2
1.2 開發板
優龍FS2410開發板,UDA1341聲卡
內核版本:3.4.2
二、聲卡數據傳輸的原理(以播放為例)
(1) 驅動程序分配一個buffer:s2c2440_dma_new
(2) app不斷寫一個個period數據到buffer(appl_ptr以frame為單位) 。一個period包含多個frame,一個frame就是一個采樣數據
(3) 驅動不斷從buffer里取出一個period:load_dma_period,啟動DMA傳輸:s3c2440_dma_start,發送給聲卡
(4)傳輸完畢,產生中斷):s3c2440_dma2_irq ,更新狀態(hw_ptr,以frame為單位)
三、具體實現(s3c2440_dma.c)
注:內核中關于s3c24xx的DMA操作的代碼框架非常復雜,暫時未仔細研究(可參考:李蘭溪 S3C24XX DMA框架源碼分析)。而我們自制的驅動,則簡化了很多,但基本思想和流程是和內核一致的。
準備工作
1. 定義好DMA操作相關的寄存器,并進行ioremap,以便后續的訪問
#define DMA0_BASE_ADDR 0x4B000000
#define DMA1_BASE_ADDR 0x4B000040
#define DMA2_BASE_ADDR 0x4B000080
#define DMA3_BASE_ADDR 0x4B0000C0
struct s3c_dma_regs {
unsigned long disrc;
unsigned long disrcc;
unsigned long didst;
unsigned long didstc;
unsigned long dcon;
unsigned long dstat;
unsigned long dcsrc;
unsigned long dcdst;
unsigned long dmasktrig;
};
static volatile struct s3c_dma_regs *dma_regs;
static int s3c2440_dma_init(void)
{
dma_regs = ioremap(DMA2_BASE_ADDR, sizeof(struct s3c_dma_regs));
platform_device_register(&s3c2440_dma_dev);
platform_driver_register(&s3c2440_dma_drv);
return 0;
}
static void s3c2440_dma_exit(void)
{
platform_device_unregister(&s3c2440_dma_dev);
platform_driver_unregister(&s3c2440_dma_drv);
iounmap(dma_regs);
}
2. 實現幾個基礎函數,供后續s3c2440_dma_prepare、s3c2440_dma2_irq和s3c2440_dma_trigger調用
/* 數據傳輸: 源,目的,長度 */
static void load_dma_period(void)
{
/* 把源,目的,長度告訴DMA */
dma_regs->disrc = playback_dma_info.phy_addr + playback_dma_info.dma_ofs; /* 源的物理地址 */
dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB總線, 源地址遞增 */
dma_regs->didst = 0x55000010; /* 目的的物理地址 IIS fifo entry*/
dma_regs->didstc = (0<<2) | (1<<1) | (1<<0); /* 目的位于APB總線, 目的地址不變 */
/*
handshake mode
DACK and DREQ are synchronized to PCLK
Enable/Disable the interrupt setting for CURR_TC
A unit transfer
single service mode
select I2SSDO of DCON2 as DMA request source
hardware trigger DMA request
datasize to be transfered: half word
2(bytes)*1(unit)*initial transfer_count = len
==>transfer_count=len/2
*/
/* bit22: 1-noreload */
//傳輸的長度(datasize是half word即2個字節):playback_dma_info.period_size/2
dma_regs->dcon = (1<<31)|(0<<30)|(1<<29)|(0<<28)|(0<<27)|(0<<24)|(1<<23)|(1<<22)|(1<<20)|(playback_dma_info.period_size/2);
/* 使能中斷,單個傳輸,硬件觸發 */
}
static void s3c2440_dma_start(void)
{
/* 啟動DMA */
dma_regs->dmasktrig = (1<<1);
}
static void s3c2440_dma_stop(void)
{
/* 停止DMA */
dma_regs->dmasktrig &= ~(1<<1);
}
3.1 實現s3c2440_dma_platform.pcm_new(即s3c2440_dma_new)
(參考 soundsocsamsungdma.c 的 dma_new)
static int s3c2440_dma_new(struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
struct snd_pcm *pcm = rtd->pcm;
struct snd_pcm_substream *substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
/*
snd_dma_buffer的作用:
在hw_params階段,snd_soc_platform_driver的ops->hw_params會被調用,通常會使用snd_pcm_set_runtime_buffer()
把substream->dma_buffer的值拷貝到substream->runtime的相關字段中(.dma_area, .dma_addr, .dma_bytes),
這樣以后就可以通過substream->runtime獲得這些地址和大小信息了。因為有播放和錄音兩個substream,而runtime始終指向當前使用的substream,
所以便于跟蹤substream。
*/
int ret = 0;
/* 1. 分配DMA BUFFER */
if (!card->dev->dma_mask) //這段代碼,是后來調試時發現,必須要加的
card->dev->dma_mask = &dma_mask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
playback_dma_info.virt_addr = (unsigned int)dma_alloc_writecombine(pcm->card->dev, s3c2440_dma_hardware.buffer_bytes_max,
&playback_dma_info.phy_addr, GFP_KERNEL);
if (!playback_dma_info.virt_addr)
{
return -ENOMEM;
}
playback_dma_info.buf_max_size = s3c2440_dma_hardware.buffer_bytes_max;
buf->dev.type = SNDRV_DMA_TYPE_DEV;
buf->dev.dev = pcm->card->dev;
buf->private_data = NULL;
buf->area = (unsigned char *)playback_dma_info.virt_addr; //這句話是后來調試時,才發現需要加的
buf->bytes = playback_dma_info.buf_max_size;
buf->addr = playback_dma_info.phy_addr;
}
return ret;
//為了簡化,先去掉錄音功能
}
3.2 實現s3c2440_dma_platform.ops.open(即s3c2440_dma_open)
(參考 soundsocsamsungdma.c 的 dma_open)
static const struct snd_pcm_hardware s3c2440_dma_hardware = {
.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 |
SNDRV_PCM_FMTBIT_U16_LE |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S8,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 128*1024, //在s3c2440_dma_new里被用于指定dma_alloc_writecombine的size參數
.period_bytes_min = PAGE_SIZE,
.period_bytes_max = PAGE_SIZE*2,
.periods_min = 2,
.periods_max = 128,
.fifo_size = 32,
};
//目前只支持播放
static int s3c2440_dma_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
int ret;
/* 設置屬性 */
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); //約束:periods必須是整數
snd_soc_set_runtime_hwparams(substream, &s3c2440_dma_hardware);
/*
snd_soc_set_runtime_hwparams的作用: 把s3c2440_dma_hardware的各個屬性賦給substream->runtime->hw,
后續在snd_pcm_open_file==>snd_pcm_open_substream==>snd_pcm_hw_constraints_complete==>
snd_pcm_hw_constraint_minmax里會調用諸如snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_CHANNELS, hw->channels_min, hw->channels_max);
*/
/* 注冊中斷 */
ret = request_irq(IRQ_DMA2, s3c2440_dma2_irq, IRQF_DISABLED, 'myalsa for playback', substream);
if (ret)
{
printk('request_irq error!n');
return -EIO;
}
return 0;
}
static int s3c2440_dma_close(struct snd_pcm_substream *substream)
{
free_irq(IRQ_DMA2, substream);
return 0;
}
3.3 實現s3c2440_dma_platform.ops. hw_params(即s3c2440_dma_hw_params)
(參考 soundsocsamsungdma.c 的 dma_hw_params)
static int s3c2440_dma_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
{
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long totbytes = params_buffer_bytes(params);
/* 根據params設置DMA */
/* 關于snd_pcm_set_runtime_buffer的作用,可看上文s3c2440_dma_new 關于snd_dma_buffer的注釋*/
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
/*
s3c2440_dma_new分配了很大的DMA BUFFER,而dma_bytes表明app決定使用多大
runtime->dma_bytes被snd_pcm_lib_readv_transfer和snd_pcm_lib_writev_transfer用來和App之間傳輸數據
*/
runtime->dma_bytes = totbytes;
playback_dma_info.buffer_size = totbytes;
playback_dma_info.period_size = params_period_bytes(params); //記錄了app在每個period里傳輸的數據大小(單位:byte),一個period里包含多個frame
return 0;
}
3.4 實現s3c2440_dma_prepare
(參考 soundsocsamsungdma.c 的 dma_prepare)
static int s3c2440_dma_prepare(struct snd_pcm_substream *substream)
{
/* 準備DMA傳輸 */
/* 復位各種狀態信息 */
playback_dma_info.dma_ofs = 0;
playback_dma_info.be_running = 0;
/* 加載第1個period */
load_dma_period(); //仿照裸板程序的dma_init()
return 0;
}
3.5 實現s3c2440_dma_trigger
(參考 soundsocsamsungdma.c 的 dma_trigger)
static int s3c2440_dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
int ret = 0;
/* 根據cmd啟動或停止DMA傳輸 */
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/* 啟動DMA傳輸 */
playback_dma_info.be_running = 1;
s3c2440_dma_start();
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
/* 停止DMA傳輸 */
playback_dma_info.be_running = 0;
s3c2440_dma_stop();
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
3.6 實現s3c2440_dma2_irq
(參考 soundsocsamsungdma.c 的 audio_buffdone)
static irqreturn_t s3c2440_dma2_irq(int irq, void *devid)
{
struct snd_pcm_substream *substream = devid;
/* 更新狀態信息 */
playback_dma_info.dma_ofs += playback_dma_info.period_size;
if (playback_dma_info.dma_ofs >= playback_dma_info.buffer_size) // buffer_size來自于params_buffer_bytes(params),即App需要使用的緩沖區大小
playback_dma_info.dma_ofs = 0; //如果當前DMA緩沖區中已傳輸的位置,超出了playback_dma_info.buffer_size,那么回零
/* 更新hw_ptr等信息,
* 并且判斷:如果buffer里沒有數據了,則調用trigger來停止DMA
*/
snd_pcm_period_elapsed(substream);
/*
snd_pcm_period_elapsed為了查詢當前已傳輸的DMA數據在ring_buffer中的位置,會調用snd_pcm_update_hw_ptr0==>substream->ops->pointer(即soc_pcm_pointer) ==>
上一篇:U-Boot移植
下一篇:ALSA聲卡_從零編寫之調試(基于優龍FS2410開發板,UDA1341聲卡)
推薦閱讀最新更新時間:2025-05-01 08:51







設計資源 培訓 開發板 精華推薦
- LT8331EMSE 4.5V 至 80V 輸入、-5V 反相轉換器的典型應用電路
- usb2lpt usb轉并口工具
- 使用 MaxLinear, Inc 的 SPX385AN-1.2/TR 的參考設計
- MIC2182,適用于多種應用的高效同步降壓穩壓器 DC/DC 轉換評估板
- 具有集成終端的 LTC2872 RS232/RS485 雙路多協議收發器的典型應用
- 32U2最小系統,附帶一個參考用的雙空格小鍵盤
- DC2395A-F,基于 LTC2325-14 四通道、14 位、5Msps/ch 同步采樣 SAR ADC 的演示板
- 使用 STMicroelectronics 的 STUSB4500 的參考設計
- EP53A7LQI 1A 同步降壓穩壓器使用外分壓器的典型應用電路
- OP295GSZ-REEL7 方波振蕩器典型應用 不論電源變化,頻率穩定