【ASOC全解析(四)】platform驱动解析与实践
- 一、platform概述和驱动程序内容
- 二、从零写一个虚拟平台音频驱动程序
-
- 音频DMA驱动程序
- SoC DAI驱动程序 & SoC DSP驱动程序
- 三、完整的platform驱动代码示例
-
- 如何加入dump文件功能
- 如何获取播放的数据
- 完整代码示例
/*****************************************************************************************************************/
声明: 本博客内容均由https://blog.csdn.net/weixin_47702410原创,转载or引用请注明出处,谢谢!
创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢
/*****************************************************************************************************************/
一、platform概述和驱动程序内容
ASOC中的platform驱动程序专门针对SoC(System on Chip)CPU,并且不包含任何特定于板(board-specific)的代码。其可分为音频DMA驱动程序、SoC DAI驱动程序和 DSP驱动程序。这三个驱动程序分别承担以下的作用:
-
音频DMA驱动程序:负责音频数据的直接内存访问(DMA)传输。
-
SoC DAI驱动程序:负责数字音频接口(DAI)的配置和管理。
-
SoC DSP驱动程序:负责数字信号处理器(DSP)的功能,包括混音控制、DMA IO、定义DSP前端PCM设备等。
二、从零写一个虚拟平台音频驱动程序
分成三大部分完成platform驱动程序
音频DMA驱动程序
主要在结构体“struct snd_soc_component_driver”中定义,这个结构体源码如下:
struct snd_soc_component_driver { const char *name; /* Default control and setup, added after probe() is run */ const struct snd_kcontrol_new *controls; unsigned int num_controls; const struct snd_soc_dapm_widget *dapm_widgets; unsigned int num_dapm_widgets; const struct snd_soc_dapm_route *dapm_routes; unsigned int num_dapm_routes; int (*probe)(struct snd_soc_component *component); void (*remove)(struct snd_soc_component *component); int (*suspend)(struct snd_soc_component *component); int (*resume)(struct snd_soc_component *component); unsigned int (*read)(struct snd_soc_component *component, unsigned int reg); int (*write)(struct snd_soc_component *component, unsigned int reg, unsigned int val); /* pcm creation and destruction */ int (*pcm_construct)(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd); void (*pcm_destruct)(struct snd_soc_component *component, struct snd_pcm *pcm); //... };
其中下面两个函数是定义DMA的,其中
- pcm_construct是用于分配和初始化PCM设备所需的私有数据结构。这可能包括为音频缓冲区分配内存、初始化硬件相关的数据结构等
- pcm_destruct与pcm_construct相对应,这个函数在PCM设备被移除时调用,用于释放pcm_construct中分配的所有资源。这包括释放音频缓冲区的内存、清理硬件相关的数据结构等。这个函数确保了当PCM设备不再使用时,所有的资源都被正确地释放。
相关的实现可以查看这个代码的实现:/kernel-5.10/sound/soc/pxa/pxa2xx-pcm.c(这也是Linux官方推荐看的关于platform驱动的代码)
这里也举个例子如何实现这个功能,示范源代码如下:
//使用一个结构体存储数据流的信息 struct myplat_info { unsigned int buf_max_size; unsigned int buffer_size; unsigned int period_size; unsigned int sample_rate; char *addr; unsigned int buf_pos; unsigned int is_running; struct snd_pcm_substream *substream; }; static struct myplat_info playback_info; static struct myplat_info capture_info; //设置DMA的格式 static const struct snd_pcm_hardware myplat_pcm_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 | SNDRV_PCM_FMTBIT_S32_LE, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .rate_min = 8000, .rate_max = 192000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = 1024 * 256, .period_bytes_min = 256, .period_bytes_max = 1024 * 128, .periods_min = 1, .periods_max = 8, .fifo_size = 128, }; static int myplat_pcm_new(struct snd_soc_component *component, 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; struct snd_dma_buffer *buf; int ret = 0; // 设置DMA掩码,如果尚未设置 if (!card->dev->dma_mask) card->dev->dma_mask = &dma_mask; if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = 0xffffffff; // 为播放流分配DMA缓冲区 if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { playback_info.buf_max_size = myplat_pcm_hardware.buffer_bytes_max; substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; playback_info.substream = substream; buf = &substream->dma_buffer; // 分配连贯的DMA缓冲区 buf->area = dma_alloc_coherent(pcm->card->dev, playback_info.buf_max_size, &buf->addr, GFP_KERNEL); if (!buf->area) { printk(KERN_ERR "playback alloc dma error!!! "); return -ENOMEM; } // 设置DMA缓冲区的属性 buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->bytes = playback_info.buf_max_size; playback_info.addr = buf->area; } // 为捕获流分配DMA缓冲区 if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { capture_info.buf_max_size = myplat_pcm_hardware.buffer_bytes_max; substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; capture_info.substream = substream; buf = &substream->dma_buffer; // 分配连贯的DMA缓冲区 buf->area = dma_alloc_coherent(pcm->card->dev, capture_info.buf_max_size, &buf->addr, GFP_KERNEL); if (!buf->area) { printk(KERN_ERR "capture alloc dma error!!! "); return -ENOMEM; } // 设置DMA缓冲区的属性 buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->bytes = capture_info.buf_max_size; capture_info.addr = buf->area; } return ret; } static void myplat_pcm_free_buffers(struct snd_soc_component *component, struct snd_pcm *pcm) { struct snd_pcm_substream *substream; struct snd_dma_buffer *buf; int stream; for (stream = 0; stream < 2; stream++) { substream = pcm->streams[stream].substream; if (!substream) continue; buf = &substream->dma_buffer; if (!buf->area) continue; dma_free_coherent(pcm->card->dev, buf->bytes, buf->area, buf->addr); buf->area = NULL; } } static struct snd_soc_component_driver plat_soc_drv = { //more .. .pcm_construct = myplat_pcm_new, .pcm_destruct = myplat_pcm_free_buffers, }; // 注册方式如下: ret = snd_soc_register_component(&pdev->dev,&plat_soc_drv,NULL, 0); if (ret < 0) { dev_err(&pdev->dev, "Could not register platform: %d ", ret); ret = -EBUSY; return ret; }
SoC DAI驱动程序 & SoC DSP驱动程序
这个驱动程序参考codec驱动解析与实践的内容。
主要是关于下面四个方面的实现:
-
platform DAI和PCM配置:必须能够配置platform的数字音频接口(DAI)和PCM(脉冲编码调制)音频数据的参数。每个编解码器驱动程序必须有一个 struct snd_soc_dai_driver 来定义其 DAI 和 PCM 功能和操作。
-
platform控制IO:使用RegMap API进行寄存器访问。RegMap API提供了一种简化的方式来读写platform的寄存器。
-
混音器和音频控制:提供音频路径的混合和音量控制等功能。
-
SoC DSP驱动程序 :platform音频操作功能,主要是实现platform的基本音频操作,如初始化、启动、停止等。
这里也举个例子如何实现这个功能,例子是关于platform DAI和PCM配置、SoC DSP驱动程序的实现,示范源代码如下:
static struct snd_soc_dai_driver myplat_cpudai_dai = { .name = "myplat-cpudai", .playback = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, //SoC DSP驱动程序,此处为空 .ops = NULL, }; static const struct snd_soc_component_driver myplat_cpudai_component = { .name = "myplat-cpudai", }; // 注册方式如下: ret = snd_soc_register_component(&pdev->dev, &myplat_cpudai_component, &myplat_cpudai_dai, ARRAY_SIZE(myplat_cpudai_dai));
三、完整的platform驱动代码示例
为了测试功能的可行性,本例程在Platform驱动中加入音频回环的功能,所谓的回环功能,就是指的是音频播放的数据重新流入到音频录音的数据流,这个分为软件回环和硬件回环功能,本例主要是针对软件回环方案。
另外额外引入导出音频数据流的功能(即Dump Audio文件),方便我们观察音频数据流的内容,由于录音的数据流可以使用tinycap保存为文件,本例的Dump Audio功能主要导出音频播放数据流的数据。
如何加入dump文件功能
Audio Dump功能的主要代码如下所示:
static struct file *fp; #define DUMP_DIR "/sdcard/playback.pcm" static struct file *vfs_open_file(char *file_path) { struct file *fp; //以读写且追加的方式打开,如果指定的文件不存在,那么将会创建这个文件 fp = filp_open(file_path, O_RDWR | O_APPEND | O_CREAT, 0644); if (IS_ERR(fp)) { printk(KERN_ERR"open %s failed!, ERR NO is %ld. ", file_path, (long)fp); } return fp; } static int vfs_write_file_append(struct file *fp, char *buf, size_t len) { mm_segment_t old_fs; static loff_t pos = 0; int buf_len; if (IS_ERR_OR_NULL(fp)) { printk(KERN_ERR"write file error, fp is null!"); return -1; } old_fs = get_fs(); set_fs(KERNEL_DS); buf_len = kernel_write(fp, buf, len, &pos); set_fs(old_fs); if (buf_len < 0) return -1; if (buf_len != len) printk(KERN_ERR"buf_len = %x, len = %pa ", buf_len, &len); pos += buf_len; return buf_len; } static int vfs_close_file(struct file *fp) { if (IS_ERR(fp)) { printk(KERN_ERR"colse file failed,fp is invaild! "); return -1; } else { filp_close(fp, NULL); return 0; } }
如何获取播放的数据
在结构体struct snd_soc_component_driver中有一个成员变量–.pcm_construct会创建buffer,如果音频数相关的数据传输进入驱动,那么buffer将在这里创建,随后会调用struct snd_soc_component_driver中的另一个成员变量–.trigger进行数据的传输。
我们需要对snd_soc_component_driver的两个成员进行处理便可以知道现在数据传输的位置了:
/* 针对streams创建DMA并保存相关的信息到全局变量中,以方便我们访问 */ static int myplat_pcm_new(struct snd_soc_component *component, 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; struct snd_dma_buffer *buf; int ret = 0; if (!card->dev->dma_mask) card->dev->dma_mask = &dma_mask; if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = 0xffffffff; if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { playback_info.buf_max_size = vplat_pcm_hardware.buffer_bytes_max; substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; playback_info.substream = substream; buf = &substream->dma_buffer; buf->area = dma_alloc_coherent(pcm->card->dev, playback_info.buf_max_size, &buf->addr, GFP_KERNEL); if (!buf->area) { printk(KERN_ERR"plaback alloc dma error!!! "); return -ENOMEM; } buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->bytes = playback_info.buf_max_size; playback_info.addr = buf->area; } if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { capture_info.buf_max_size = vplat_pcm_hardware.buffer_bytes_max; substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; capture_info.substream = substream; buf = &substream->dma_buffer; buf->area = dma_alloc_coherent(pcm->card->dev, capture_info.buf_max_size, &buf->addr, GFP_KERNEL); if (!buf->area) { printk(KERN_ERR"catpure alloc dma error!!! "); return -ENOMEM; } buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->bytes = capture_info.buf_max_size; capture_info.addr = buf->area; } return ret; }
在snd_soc_component_driver的成员变量.trigger中,使用一个定时器进行模拟数据传输:
/* 根据cmd启动或停止数据传输 */ static int myplat_pcm_trigger(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd) { int ret = 0; static u8 is_timer_run = 0; struct snd_pcm_runtime *runtime = substream->runtime; unsigned int rate = runtime->rate; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* 启动定时器, 模拟数据传输 */ printk("playback running... "); playback_info.is_running = 1; playback_info.sample_rate = rate; if(!is_timer_run) { is_timer_run = 1; start_timer(playback_info.sample_rate,playback_info.period_size); } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* 停止定时器 */ printk("playback stop... "); playback_info.is_running = 0; if(!capture_info.is_running){ is_timer_run = 0; del_timer(&vtimer); } break; default: ret = -EINVAL; break; } } else { switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* catpure开始接收数据 */ printk("capture running... "); capture_info.is_running = 1; capture_info.sample_rate = rate; if(!is_timer_run) { is_timer_run = 1; start_timer(capture_info.sample_rate,capture_info.period_size); } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* catpure停止接收数据 */ printk("capture stop... "); capture_info.is_running = 0; if(!playback_info.is_running){ is_timer_run = 0; del_timer(&vtimer); } break; default: ret = -EINVAL; break; } } return ret; }
定时器的目的,主要在于,当有音频录音的数据流的时候,将当前播放的数据流的内容拷贝到音频录音数据流的buffer中,如此录音获取的数据便是播放的数据了:
static struct timer_list vtimer; static void work_function(struct work_struct *work); DECLARE_WORK(myplat_work,work_function); static void vplat_timer_function(struct timer_list *t) { schedule_work(&myplat_work); } static void start_timer(unsigned int rate,unsigned long period_size) { timer_setup(&vtimer, myplat_timer_function, 0); /* 周期数/采样率 = 完成一个周期所需时间 */ vtimer.expires = jiffies + (HZ * period_size) / rate; add_timer(&vtimer); } static void work_function(struct work_struct *work){ struct snd_pcm_substream *pb_substream = playback_info.substream; //printk("%s,line:%d ",__func__,__LINE__); if (capture_info.is_running) { load_buff_period(); } // 更新状态信息 if(playback_info.is_running){ playback_info.buf_pos += playback_info.period_size; // 环形缓冲区 if (playback_info.buf_pos >= playback_info.buffer_size) playback_info.buf_pos = 0; // 更新指针位置和计算缓冲区可用空间 snd_pcm_period_elapsed(pb_substream); } if (playback_info.is_running || capture_info.is_running) { //再次启动定时器 mod_timer(&vtimer, jiffies + (HZ * playback_info.sample_rate) / playback_info.sample_rate); } }
音频拷贝数据使用函数load_buff_period,主要将capture的数据拷贝到playback的buffer中,源码如下所示:
static int load_buff_period(void) { struct snd_pcm_substream *cp_substream = capture_info.substream; int size = 0; if(capture_info.addr == NULL) { printk(KERN_ERR"catpure addr error!!! "); return -1; } if (playback_info.is_running) { if(capture_info.period_size != playback_info.period_size) { printk(KERN_ERR"capture_info.period_size(%d) != playback_info.period_size(%d) ", capture_info.period_size,playback_info.period_size); } size = capture_info.period_size <= playback_info.period_size ? capture_info.period_size : playback_info.period_size; //复制playback的一帧数据到catpure memcpy(capture_info.addr+capture_info.buf_pos, playback_info.addr+playback_info.buf_pos, size); } else { memset(capture_info.addr+capture_info.buf_pos,0x00,capture_info.period_size); } //更新capture当前buffer指针位置 capture_info.buf_pos += capture_info.period_size; if (capture_info.buf_pos >= capture_info.buffer_size) capture_info.buf_pos = 0; snd_pcm_period_elapsed(cp_substream); return 0; }
完整代码示例
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <linux/dma-mapping.h> #include <linux/timer.h> #include <asm/uaccess.h> #include <linux/workqueue.h> #include <linux/fs.h> MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); //使用一个结构体存储数据流的信息 struct myplat_info { unsigned int buf_max_size; unsigned int buffer_size; unsigned int period_size; unsigned int sample_rate; char *addr; unsigned int buf_pos; unsigned int is_running; struct snd_pcm_substream *substream; }; static struct myplat_info playback_info; static struct myplat_info capture_info; static struct timer_list vtimer; static void work_function(struct work_struct *work); DECLARE_WORK(myplat_work,work_function); #define DUMP_PLAYBACK #ifdef DUMP_PLAYBACK static struct file *fp; #define DUMP_DIR "/sdcard/playback.pcm" #endif static u64 dma_mask = DMA_BIT_MASK(32); //设置DMA的格式 static const struct snd_pcm_hardware myplat_pcm_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 | SNDRV_PCM_FMTBIT_S32_LE, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .rate_min = 8000, .rate_max = 192000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = 1024 * 256, .period_bytes_min = 256, .period_bytes_max = 1024 * 128, .periods_min = 1, .periods_max = 8, .fifo_size = 128, }; static const struct snd_soc_component_driver myplat_cpudai_component = { .name = "myplat-cpudai", }; static struct snd_soc_dai_driver myplat_cpudai_dai = { .name = "myplat-cpudai", .playback = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .ops = NULL, }; #ifdef DUMP_PLAYBACK static struct file *vfs_open_file(char *file_path) { struct file *fp; //以读写且追加的方式打开,如果指定的文件不存在,那么将会创建这个文件 fp = filp_open(file_path, O_RDWR | O_APPEND | O_CREAT, 0644); if (IS_ERR(fp)) { printk(KERN_ERR"open %s failed!, ERR NO is %ld. ", file_path, (long)fp); } return fp; } static int vfs_write_file_append(struct file *fp, char *buf, size_t len) { mm_segment_t old_fs; static loff_t pos = 0; int buf_len; if (IS_ERR_OR_NULL(fp)) { printk(KERN_ERR"write file error, fp is null!"); return -1; } old_fs = get_fs(); set_fs(KERNEL_DS); buf_len = kernel_write(fp, buf, len, &pos); set_fs(old_fs); if (buf_len < 0) return -1; if (buf_len != len) printk(KERN_ERR"buf_len = %x, len = %pa ", buf_len, &len); pos += buf_len; return buf_len; } static int vfs_close_file(struct file *fp) { if (IS_ERR(fp)) { printk(KERN_ERR"colse file failed,fp is invaild! "); return -1; } else { filp_close(fp, NULL); return 0; } } #endif static int load_buff_period(void) { struct snd_pcm_substream *cp_substream = capture_info.substream; int size = 0; if(capture_info.addr == NULL) { printk(KERN_ERR"catpure addr error!!! "); return -1; } if (playback_info.is_running) { if(capture_info.period_size != playback_info.period_size) { printk(KERN_ERR"capture_info.period_size(%d) != playback_info.period_size(%d) ", capture_info.period_size,playback_info.period_size); } size = capture_info.period_size <= playback_info.period_size ? capture_info.period_size : playback_info.period_size; //复制playback的一帧数据到catpure memcpy(capture_info.addr+capture_info.buf_pos, playback_info.addr+playback_info.buf_pos, size); } else { memset(capture_info.addr+capture_info.buf_pos,0x00,capture_info.period_size); } //更新capture当前buffer指针位置 capture_info.buf_pos += capture_info.period_size; if (capture_info.buf_pos >= capture_info.buffer_size) capture_info.buf_pos = 0; snd_pcm_period_elapsed(cp_substream); return 0; } static void work_function(struct work_struct *work){ unsigned long period_time_in_jiffies; struct snd_pcm_substream *pb_substream = playback_info.substream; //printk("%s,line:%d ",__func__,__LINE__); #ifdef DUMP_PLAYBACK if(playback_info.is_running) { fp = vfs_open_file(DUMP_DIR); vfs_write_file_append(fp, playback_info.addr+playback_info.buf_pos, playback_info.period_size); vfs_close_file(fp); } #endif if (capture_info.is_running) { load_buff_period(); } // 更新状态信息 if(playback_info.is_running){ playback_info.buf_pos += playback_info.period_size; // 环形缓冲区 if (playback_info.buf_pos >= playback_info.buffer_size) playback_info.buf_pos = 0; // 更新指针位置和计算缓冲区可用空间 snd_pcm_period_elapsed(pb_substream); } if (playback_info.is_running || capture_info.is_running) { // 为了避免精度损失,先乘以HZ,然后再除以rate。 period_time_in_jiffies = (playback_info.period_size * HZ) / playback_info.sample_rate; //再次启动定时器 mod_timer(&vtimer, jiffies + period_time_in_jiffies); } } static void myplat_timer_function(struct timer_list *t) { schedule_work(&myplat_work); } static void start_timer(unsigned int rate, unsigned long period_size) { unsigned long period_time_in_jiffies; timer_setup(&vtimer, myplat_timer_function, 0); // 使用整数运算计算周期时间(以jiffies为单位) // 例如,如果rate是Hz,period_size是样本数, // 那么一个周期的时间(以jiffies为单位)是: // (period_size * HZ) / rate // 为了避免精度损失,先乘以HZ,然后再除以rate。 period_time_in_jiffies = (period_size * HZ) / rate; // 设置定时器过期时间 vtimer.expires = jiffies + period_time_in_jiffies; add_timer(&vtimer); } static int myplat_pcm_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; printk("%s,line:%d ",__func__,__LINE__); // 设置属性 snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); snd_soc_set_runtime_hwparams(substream, &myplat_pcm_hardware); //可以在这里注册中断 return 0; } int myplat_pcm_close(struct snd_soc_component *component, struct snd_pcm_substream *substream) { printk("%s,line:%d ",__func__,__LINE__); return 0; } static int myplat_pcm_hw_params(struct snd_soc_component *component, 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); //printk("%s,line:%d ",__func__,__LINE__); /* pcm_new分配了很大的BUFFER * params决定使用多大 */ runtime->dma_bytes = totbytes; // 保存config if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { playback_info.buffer_size = totbytes; playback_info.period_size = params_period_bytes(params); } else { capture_info.buffer_size = totbytes; capture_info.period_size = params_period_bytes(params); } //设置runtime->dma_area snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); return 0; } /* 准备数据传输 */ static int myplat_pcm_prepare(struct snd_soc_component *component, struct snd_pcm_substream *substream) { //printk("%s,line:%d ",__func__,__LINE__); /* 复位各种状态信息 */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { playback_info.buf_pos = 0; playback_info.is_running = 0; } else { capture_info.buf_pos = 0; capture_info.is_running = 0; /* 加载第1个period */ load_buff_period(); } return 0; } /* 根据cmd启动或停止数据传输 */ static int myplat_pcm_trigger(struct snd_soc_component *component, struct snd_pcm_substream *substream, int cmd) { int ret = 0; static u8 is_timer_run = 0; struct snd_pcm_runtime *runtime = substream->runtime; unsigned int rate = runtime->rate; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* 启动定时器, 模拟数据传输 */ printk("playback running... "); playback_info.is_running = 1; playback_info.sample_rate = rate; if(!is_timer_run) { is_timer_run = 1; start_timer(playback_info.sample_rate,playback_info.period_size); } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* 停止定时器 */ printk("playback stop... "); playback_info.is_running = 0; if(!capture_info.is_running){ is_timer_run = 0; del_timer(&vtimer); } break; default: ret = -EINVAL; break; } } else { switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* catpure开始接收数据 */ printk("capture running... "); capture_info.is_running = 1; capture_info.sample_rate = rate; if(!is_timer_run) { is_timer_run = 1; start_timer(capture_info.sample_rate,capture_info.period_size); } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* catpure停止接收数据 */ printk("capture stop... "); capture_info.is_running = 0; if(!playback_info.is_running){ is_timer_run = 0; del_timer(&vtimer); } break; default: ret = -EINVAL; break; } } return ret; } /* 返回结果是frame */ static snd_pcm_uframes_t myplat_pcm_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) return bytes_to_frames(substream->runtime, playback_info.buf_pos); else { return bytes_to_frames(substream->runtime, capture_info.buf_pos); } } static int myplat_pcm_new(struct snd_soc_component *component, 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; struct snd_dma_buffer *buf; int ret = 0; // 设置DMA掩码,如果尚未设置 if (!card->dev->dma_mask) card->dev->dma_mask = &dma_mask; if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = 0xffffffff; // 为播放流分配DMA缓冲区 if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { playback_info.buf_max_size = myplat_pcm_hardware.buffer_bytes_max; substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; playback_info.substream = substream; buf = &substream->dma_buffer; // 分配连贯的DMA缓冲区 buf->area = dma_alloc_coherent(pcm->card->dev, playback_info.buf_max_size, &buf->addr, GFP_KERNEL); if (!buf->area) { printk(KERN_ERR "playback alloc dma error!!! "); return -ENOMEM; } // 设置DMA缓冲区的属性 buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->bytes = playback_info.buf_max_size; playback_info.addr = buf->area; } // 为捕获流分配DMA缓冲区 if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { capture_info.buf_max_size = myplat_pcm_hardware.buffer_bytes_max; substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; capture_info.substream = substream; buf = &substream->dma_buffer; // 分配连贯的DMA缓冲区 buf->area = dma_alloc_coherent(pcm->card->dev, capture_info.buf_max_size, &buf->addr, GFP_KERNEL); if (!buf->area) { printk(KERN_ERR "capture alloc dma error!!! "); return -ENOMEM; } // 设置DMA缓冲区的属性 buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->bytes = capture_info.buf_max_size; capture_info.addr = buf->area; } return ret; } static void myplat_pcm_free_buffers(struct snd_soc_component *component, struct snd_pcm *pcm) { struct snd_pcm_substream *substream; struct snd_dma_buffer *buf; int stream; for (stream = 0; stream < 2; stream++) { substream = pcm->streams[stream].substream; if (!substream) continue; buf = &substream->dma_buffer; if (!buf->area) continue; dma_free_coherent(pcm->card->dev, buf->bytes, buf->area, buf->addr); buf->area = NULL; } } static int myplat_pcm_mmap(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct vm_area_struct *vma) { struct snd_pcm_runtime *runtime = NULL; printk("%s,line:%d ",__func__,__LINE__); if (substream->runtime != NULL) { runtime = substream->runtime; return dma_mmap_wc(substream->pcm->card->dev, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); } else { return -1; } } int my_snd_pcm_lib_ioctl(struct snd_soc_component *component, struct snd_pcm_substream *substream, unsigned int cmd, void *arg) { return snd_pcm_lib_ioctl(substream,cmd, arg); } static struct snd_soc_component_driver plat_soc_drv = { .name = "myplat", .open = myplat_pcm_open, .close = myplat_pcm_close, .ioctl = my_snd_pcm_lib_ioctl, .hw_params = myplat_pcm_hw_params, .prepare = myplat_pcm_prepare, .trigger = myplat_pcm_trigger, .pointer = myplat_pcm_pointer, .mmap = myplat_pcm_mmap, .pcm_construct = myplat_pcm_new, .pcm_destruct = myplat_pcm_free_buffers, }; static int plat_probe(struct platform_device *pdev) { int ret = 0; printk("-----%s---- ",__func__); ret = snd_soc_register_component(&pdev->dev, &myplat_cpudai_component, &myplat_cpudai_dai, 1); if (ret < 0) { dev_err(&pdev->dev, "Could not register CPU DAI: %d ", ret); ret = -EBUSY; return ret; } ret = devm_snd_soc_register_component(&pdev->dev,&plat_soc_drv,NULL, 0); if (ret < 0) { dev_err(&pdev->dev, "register platform fail !ret = %d ", ret); ret = -EBUSY; return ret; } return ret; } static int plat_remove(struct platform_device *pdev){ printk("-----%s---- ",__func__); return 0; } static void plat_pdev_release(struct device *dev) { } static struct platform_device plat_pdev = { .name = "myplat", .dev.release = plat_pdev_release, }; static struct platform_driver plat_pdrv = { .probe = plat_probe, .remove = plat_remove, .driver = { .name = "myplat", }, }; static int __init plat_init(void) { int ret; ret = platform_device_register(&plat_pdev); if (ret) return ret; ret = platform_driver_register(&plat_pdrv); if (ret) platform_device_unregister(&plat_pdev); return ret; } static void __exit plat_exit(void) { platform_driver_unregister(&plat_pdrv); platform_device_unregister(&plat_pdev); } module_init(plat_init); module_exit(plat_exit); MODULE_LICENSE("GPL");