/*****************************************************************************************************************/
声明: 本博客内容均由https://blog.csdn.net/weixin_47702410原创,转载or引用请注明出处,谢谢!
创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢
/*****************************************************************************************************************/
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驱动程序
主要在结构体“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的,其中
相关的实现可以查看这个代码的实现:/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!!!\n");
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!!!\n");
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\n", ret);
ret = -EBUSY;
return ret;
}
这个驱动程序参考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驱动中加入音频回环的功能,所谓的回环功能,就是指的是音频播放的数据重新流入到音频录音的数据流,这个分为软件回环和硬件回环功能,本例主要是针对软件回环方案。
另外额外引入导出音频数据流的功能(即Dump Audio文件),方便我们观察音频数据流的内容,由于录音的数据流可以使用tinycap保存为文件,本例的Dump Audio功能主要导出音频播放数据流的数据。
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.\n", 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\n", 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!\n");
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!!!\n");
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!!!\n");
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...\n");
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...\n");
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...\n");
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...\n");
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\n",__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!!!\n");
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)\n",
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.\n", 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\n", 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!\n");
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!!!\n");
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)\n",
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\n",__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\n",__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\n",__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\n",__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\n",__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...\n");
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...\n");
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...\n");
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...\n");
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!!!\n");
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!!!\n");
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\n",__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----\n",__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\n", 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\n", ret);
ret = -EBUSY;
return ret;
}
return ret;
}
static int plat_remove(struct platform_device *pdev){
printk("-----%s----\n",__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");