【ASOC全解析(四)】platform驱动解析与实践

2024-01-24 01:20

本文主要是介绍【ASOC全解析(四)】platform驱动解析与实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【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!!!\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;
}

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.\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的一帧数据到catpurememcpy(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"
#endifstatic 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;}
}
#endifstatic 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的一帧数据到catpurememcpy(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_PLAYBACKif(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);}
#endifif (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;// 保存configif (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_areasnd_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");

这篇关于【ASOC全解析(四)】platform驱动解析与实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/638129

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro