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

发布时间:2024年01月24日

/*****************************************************************************************************************/

声明: 本博客内容均由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的一帧数据到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");

文章来源:https://blog.csdn.net/weixin_47702410/article/details/135789845
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。