imx_pwm_audio分析

imx_pwm_audio分析

​ 该代码声称大部分是从i.MX6 PWM驱动(drivers/pwm/pwm-imx.c)改编的,但实际上pwm-imx.c并没有使用DMA。

​ 更新于2018年3月21日,针对kernel版本为L3.14.28。

文章目录

引入头文件

使用到sDMA和EPIT定时器,均为NPX的IMX系列所特有的。

​ sDMA:Smart Direct Memory Access Controller

https://github.com/freebsd/freebsd/blob/3799d78beb4cf81baac99c1256126e10696fc4e3/sys/arm/freescale/imx/imx6_sdma.c

https://github.com/freebsd/freebsd/blob/1d6e4247415d264485ee94b59fdbc12e0c566fd0/sys/arm/freescale/imx/imx6_sdma.h

EPIT:Enhanced Programmable Interval Timerimx。增强型可编程间隔计时器的驱动程序,它是一种简单的自由运行的计数器设备,可用作系统计时器。 在imx5上,设备的第二个实例用作系统事件计时器。

https://github.com/freebsd/freebsd/blob/1d6e4247415d264485ee94b59fdbc12e0c566fd0/sys/arm/freescale/imx/imx_epit.c

​ imx-sdma在kernel 4.19提交了一个增加virt-dma支持的change,virt-dma为对DMAengine的虚拟DMA通道支持。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #include 
#include 

模块参数

static u32 sample_rate        = 22000; /* 采样率22kHz */
static u32 bits_per_sample    = 8;  /* 每个样本8bits */
static int signed_samples     = 0;  /* 1 if samples are signed, 0 if unsigned */
module_param(sample_rate,     uint, 0); 
/* 设置参数访问权限为0,即不可传参给模块 */
module_param(bits_per_sample, uint, 0);
module_param(signed_samples,  int, 0);
static u32 sample_offset = 0;#define AUDIO_DEVICE_NAME "pwm_audio"
#define AUDIO_DEVICE_PATH "/dev/" AUDIO_DEVICE_NAME  
/* 设备节点为/dev/pwm_audio */

​ module_param(name, type, perm);

​ perm表示该参数在sysfs文件系统中所对应的文件节点的属性可以使用中定义的权限值;

​ 当perm为0时,表示此参数不存在sysfs文件系统下对应的文件节点;否则,模块被加载后,在/sys/module/目录下将会出现以此模块名命名的目录,带有给定的权限。

分配次设备号

​ 为128,通常用于蜂鸣器/dev/beep

/* I've seen cases where the kernel hands out the same minor number */
/* to multiple modules that use MISC_DYNAMIC_MINOR. */
/* The Linux Allocated Devices list assigns 128 to "/dev/beep", */
/* a.k.a. "fancy beep device," a.k.a. the IBM PC speaker. */
/* So it's somewhat appropriate. :) */
#define AUDIO_DEVICE_MINOR_NUMBER 128

SDMA脚本部分

​ 在load_sdma_script()、load_script_context()和imx_pwm_audio_probe()中被调用。

static const u32 sdma_script[] = {0x0a000901, 0x69c80400, 0x69c86a2b, 0x620002df, 0x7d090568, 0x7d0b6209, 0x02a602bd, 0x6a2b0400,0x01607df2, 0x03000400, 0x01607dee, 0x620a7df4
};

PWM相关寄存器设置。

这部分需要做针对性修改。

/* PWM Control Register */
#define PWMCR   0x00
/* PWM Status Register */
#define PWMSR   0x04
/* PWM Interrupt Register */
#define PWMIR   0x08
/* PWM Sample Register */
#define PWMSAR  0x0c
/* PWM Period Register */
#define PWMPR   0x10
/* PWM Counter Register */
#define PWMCNR  0x14#define PWMCR_FWM(x)          ((((x)-1) & 0x3) << 26)
#define PWMCR_DOZEEN          (1 << 24)
#define PWMCR_WAITEN          (1 << 23)
#define PWMCR_DBGEN           (1 << 22)
#define PWMCR_CLKSRC_IPG_HIGH (2 << 16)
#define PWMCR_CLKSRC_IPG      (1 << 16)
#define PWMCR_PRESCALER(x)    ((((x) - 1) & 0xFFF) << 4)
#define PWMCR_SWR             (1 << 3)
#define PWMCR_REPEAT_1        (0 << 1)
#define PWMCR_REPEAT_2        (1 << 1)
#define PWMCR_REPEAT_4        (2 << 1)
#define PWMCR_REPEAT_8        (3 << 1)
#define PWMCR_EN              (1 << 0)#define PWMSR_FWE             (1 << 6)
#define PWMSR_CMP             (1 << 5)
#define PWMSR_ROV             (1 << 4)
#define PWMSR_FE              (1 << 3)
#define PWMSR_FIFOAV_MASK     0x7#define PWMIR_CIE             (1 << 2)
#define PWMIR_RIE             (1 << 1)
#define PWMIR_FIE             (1 << 0)#define RES_TO_PWMPR(bits)    ((1<<(bits))-3)
/** bits_per_sample/8后向上取整,变为以字节为单位。默认每个样本8bits,即每个样本1字节 */
#define BYTES_PER_SAMPLE      DIV_ROUND_UP(bits_per_sample, 8) #define BYTES_PER_SECOND      ((sample_rate)*(BYTES_PER_SAMPLE))
#define SAMPLE_BUF_SIZE       (10*BYTES_PER_SECOND) /** 缓冲区为10秒的数据大小 *//** 当有这么多字节的数据(1秒的数据)入队时,开始播放. */
#define START_THRESHOLD       (1*BYTES_PER_SECOND) 

IMX PWM特定的音频数据结构体

​ linux中,提供了miscdevice这种杂项设备,指定主设备号为10。

​ “ ipg”时钟驱动对设备映射寄存器的访问,而“ per”时钟驱动设备本身。这两个也是IMX系列特有的。

struct imx_pwm_audio_data {/**指向父设备的指针 */struct device *dev;/** 锁定以确保每次只能通过一个进程打开设备 */struct mutex lock;   /** 用于PWM音频设备的open/close/probe/remove函数 *//** 外设时钟 */struct clk *clk_per;/** IPG时钟 */struct clk *clk_ipg;/** PWM寄存器的基地址 */void __iomem *mmio_base;/** PWM寄存器基地址的物理地址 */u32 mmio_base_phys;/** 用户空间接口 */struct miscdevice audiodev;/** SPKR_EN GPIO的数量 */int enable_gpio;/** 指向用于驱动DMA引擎的计时器的指针 */struct epit *epit;/** SDMA脚本加载地址(以字节为单位的偏移量) */u32 sdma_script_origin;/** SDMA通道号 */u32 sdma_ch_num;/** 指向SDMA引擎的指针 */struct sdma_engine *sdma;/** 指向驱动程序使用的SDMA通道的指针 */struct sdma_channel *sdmac;/** 指向样本缓冲区开头的指针 */void *sample_buf;/** 样本缓冲区开头的物理地址 */dma_addr_t sample_buf_phys;/** 进入队列中的样本数据的字节数 */u32 sample_buf_len;
};

DMA

load_sdma_script

#pragma mark - DMAstatic int load_sdma_script(struct imx_pwm_audio_data *self)
{int ret;const u32 *script = sdma_script;size_t script_len = sizeof(sdma_script);struct sdma_context_data initial_context = {{0}};/* 将脚本代码写入SDMA RAM */dev_dbg(self->dev, "loading SDMA script (%d bytes)...", script_len);ret = sdma_write_datamem(self->sdma, (void *)script, script_len,self->sdma_script_origin);if (ret) {dev_err(self->dev, "failed to load script");return ret;}/* 加载初始context */ret = sdma_load_partial_context(self->sdmac, &initial_context, 0,sizeof(initial_context));dev_dbg(self->dev, "script loaded");return ret;
}

load_script_context

设置脚本程序计数器和参数

static int load_script_context(struct imx_pwm_audio_data *self)
{int ret;struct sdma_context_data context = {{0}};/* 设置脚本参数和初始PC *//* 有关参数要求,请参见特定的asm文件 */context.channel_state.pc = self->sdma_script_origin * 2; /* 在程序空间中寻址 */context.gReg[5] = (1<<bits_per_sample)-1;context.gReg[6] = sample_offset;context.msa = self->sample_buf_phys;context.mda = self->mmio_base_phys+PWMSAR;context.ms = 0x00100000; /* 冻结目标地址;源地址后递增;以读取模式启动 */context.pda = epit_status_register_address(self->epit);context.ps = 0x000c0400; /* 冻结目标地址;32位写大小;以写模式启动 *//* 获取通道并加载其context; *//* 由EPIT在外部触发 */sdma_setup_channel(self->sdmac, true);ret = sdma_load_partial_context(self->sdmac, &context, 0, sizeof(context));if (ret) {dev_err(self->dev, "failed to set up channel");return ret;}return 0;
}

pwm_audio_set_sample_buf_len

​ PWM音频样本缓冲区长度设置。

​ 在pwm_audio_enqueue_data和pwm_audio_stop_and_clear_data函数中被调用,

​ 对sdma_context_data结构体变量context的gReg[7]、mda、msa进行设置。

static int pwm_audio_set_sample_buf_len(struct imx_pwm_audio_data *self,size_t new_len)
{int context_len = 0;struct sdma_context_data context = {{0}};dev_dbg(self->dev, "%s: %u", __func__, new_len);if (new_len > SAMPLE_BUF_SIZE) {return -EINVAL;}self->sample_buf_len = new_len;context.gReg[7] = self->sample_buf_phys+self->sample_buf_len;context.mda = self->mmio_base_phys+PWMSAR;context.msa = self->sample_buf_phys;/* 如果有数据,则仅将指针更新到缓冲区的末尾(r7) */if (new_len > 0) {context_len = 4;}/* 如果清除数据,请更新r7和MSA(因为MDA在中间,所以也更新)*/else {context_len = 12;}/*** 此操作由通道0运行,该通道与我们脚本的通道互斥。* 由于一次只能运行一个通道,并且通道不能相互抢占,因此可以确保仅在脚本的迭代之间更新context。*(即,我们的脚本运行时不会突然更新寄存器)*/return sdma_load_partial_context(self->sdmac,(struct sdma_context_data *)&context.gReg[7],offsetof(struct sdma_context_data, gReg[7]),context_len);
}

Audio functions

pwm_audio_play

#pragma mark - Audio functionsstatic int pwm_audio_play(struct imx_pwm_audio_data *self)
{/* 如果没有数据播放,则返回错误 */if (self->sample_buf_len == 0) {return -ENODATA;}/* 如果已经在播放则什么都不做 */if (epit_is_running(self->epit)) {return 0;}dev_dbg(self->dev, "%s", __func__);/* 打开扬声器 */gpio_set_value(self->enable_gpio, 1);/* 使能timer events */sdma_event_enable(self->sdmac, epit_sdma_event(self->epit));epit_start_hz(self->epit, sample_rate); /* 开始生成periodic events *//* 设置非零优先级以启动脚本 */sdma_set_channel_priority(self->sdmac, 5);return 0;
}

pwm_audio_stop

static int pwm_audio_stop(struct imx_pwm_audio_data *self)
{dev_dbg(self->dev, "%s", __func__);/* 停止timer并禁用sdma通道 */epit_stop(self->epit);sdma_event_disable(self->sdmac, epit_sdma_event(self->epit));/* 关闭扬声器 */gpio_set_value(self->enable_gpio, 0);return 0;
}

pwm_audio_stop_and_clear_data

​ 调用pwm_audio_stop,并将样本缓冲区长度设为0。

static int pwm_audio_stop_and_clear_data(struct imx_pwm_audio_data *self)
{pwm_audio_stop(self);return pwm_audio_set_sample_buf_len(self, 0);
}

pwm_audio_enqueue_data

static ssize_t pwm_audio_enqueue_data(struct imx_pwm_audio_data *self,const char __user *data, size_t count)
{size_t bytes_free = 0, nbytes = 0;int ret;dev_dbg(self->dev, "%s: %u", __func__, count);if (self->sample_buf_len >= SAMPLE_BUF_SIZE) {return -EPERM;}bytes_free = SAMPLE_BUF_SIZE - self->sample_buf_len;nbytes = min(count, bytes_free);if (copy_from_user(self->sample_buf+self->sample_buf_len, data, nbytes)) {dev_err(self->dev, "unable to copy data from userspace");return -EBADF;}ret = pwm_audio_set_sample_buf_len(self, self->sample_buf_len+nbytes);return (ret < 0) ? ret : nbytes;
}

pwm_audio_sdma_interrupt

​ 当SDMA引擎执行“done 3”指令,为我们的通道设置中断标志时调用。此回调在tasklet context中执行。

static void pwm_audio_sdma_interrupt(void *param)
{struct imx_pwm_audio_data *self = (struct imx_pwm_audio_data *)param;pwm_audio_stop(self);
}

PWM setup

pwm_enable

#pragma mark - PWM setupstatic int pwm_enable(struct imx_pwm_audio_data *self)
{u32 period = RES_TO_PWMPR(bits_per_sample);u32 cr;/* 使能clocks */int ret = 0;if ((ret = clk_prepare_enable(self->clk_per))) {return ret;}if ((ret = clk_prepare_enable(self->clk_ipg))) {return ret;}/* TODO: PWMCR_REPEAT 需要基于采样率 *//* 对于更高的sample rates/sizes,可能需要为PWMCR_REPEAT_4、2或1 */cr = PWMCR_REPEAT_8 | PWMCR_DOZEEN | PWMCR_WAITEN | PWMCR_DBGEN | PWMCR_CLKSRC_IPG_HIGH |PWMCR_EN;/* 软件reset */__raw_writel(PWMCR_SWR, self->mmio_base+PWMCR);udelay(10);/* 激活和停用PWM(reset后似乎是必需的) */__raw_writel(PWMCR_EN, self->mmio_base+PWMCR);__raw_writel(0, self->mmio_base+PWMCR);/* Silence沉默 */__raw_writel(0, self->mmio_base+PWMSAR);/* 设置period,增加一些余量以防止高振幅失真。 *//* 增加周期会增加最大PWM计数器值。由于信号值不变,因此具有减小输出信号幅度的作用。 *//* 实验确定25%的系数是clarity和amplitude之间的最佳平衡,同时保留了完整的动态范围。 */period += period >> 2;dev_dbg(self->dev, "setting PWMPR: %08x (%lu Hz)", period,clk_get_rate(self->clk_ipg)/(period+2));__raw_writel(period, self->mmio_base+PWMPR);/* 配置并启用 */dev_dbg(self->dev, "setting PWMCR: %08x", cr);__raw_writel(cr, self->mmio_base+PWMCR);return 0;
}

pwm_disable

static int pwm_disable(struct imx_pwm_audio_data *self)
{u32 cr = 0;/* 禁用PWM */__raw_writel(cr, self->mmio_base+PWMCR);/* 释放clocks */clk_disable_unprepare(self->clk_per);clk_disable_unprepare(self->clk_ipg);return 0;
}

Userspace API

#pragma mark - Userspace API/* miscdevice将filp的private_data指针设置为其自身 */
#define DEV_SELF_FROM_FILP(filp) \struct device *dev = ((struct miscdevice *)filp->private_data)->parent; \struct imx_pwm_audio_data *self = dev_get_drvdata(dev)

imx_pwm_audio_dev_open

static int imx_pwm_audio_dev_open(struct inode *inode, struct file *filp)
{/* 打开时,静音并清除所有数据 *//* 一次只能打开一个/dev/pwm_audio 进程 *//* 不支持mixing */DEV_SELF_FROM_FILP(filp);if (!mutex_trylock(&self->lock)) {dev_warn(dev, AUDIO_DEVICE_PATH " is in use");return -EBUSY;} else {dev_dbg(dev, AUDIO_DEVICE_PATH " opened");}return pwm_audio_stop_and_clear_data(self);
}

imx_pwm_audio_dev_close

static int imx_pwm_audio_dev_close(struct inode *inode, struct file *filp)
{DEV_SELF_FROM_FILP(filp);dev_dbg(dev, AUDIO_DEVICE_PATH " closed");/* 如果进程写入的数据少于START_THRESHOLD的数据量, *//* 请确保在close后我们正在播放任何队列中的数据。 *//* (例如,“ cat”制作非常短的音频文件) */if (self->sample_buf_len > 0) {pwm_audio_play(self);}mutex_unlock(&self->lock);return 0;
}

imx_pwm_audio_dev_read

static ssize_t imx_pwm_audio_dev_read(struct file *filp, char __user *data,size_t count, loff_t *offp)
{/* 不支持从 /dev/pwm_audio 读取 */return 0;
}

imx_pwm_audio_dev_write

static ssize_t imx_pwm_audio_dev_write(struct file *filp,const char __user *data, size_t count, loff_t *offp)
{DEV_SELF_FROM_FILP(filp);ssize_t ret = pwm_audio_enqueue_data(self, data, count);/* 如果我们有足够的数据在队列中,就开始播放 */if (self->sample_buf_len >= START_THRESHOLD) {pwm_audio_play(self);}return ret;
}

imx_pwm_audio_dev_fsync

static int imx_pwm_audio_dev_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
{DEV_SELF_FROM_FILP(filp);/* 如果我们有数据进入队列就开始播放 */return pwm_audio_play(self);
}

imx_pwm_audio_dev_llseek

static loff_t imx_pwm_audio_dev_llseek(struct file *filp, loff_t off, int whence)
{DEV_SELF_FROM_FILP(filp);/* 将lseek()设置为0可以停止音频并清除缓冲区。 */if (off != 0 || whence != SEEK_SET) {return -EINVAL;}return pwm_audio_stop_and_clear_data(self);
}

audio_dev_fops

const struct file_operations audio_dev_fops = {.owner    = THIS_MODULE,.open     = imx_pwm_audio_dev_open,.read     = imx_pwm_audio_dev_read,.write    = imx_pwm_audio_dev_write,.fsync    = imx_pwm_audio_dev_fsync,.llseek   = imx_pwm_audio_dev_llseek,.release  = imx_pwm_audio_dev_close,
};

​ 结构体中用“.”是结构体的初始化方式中的一种。.owner = THIS_MODULE,指的是对dev_fops 结构体中的owner成员进行初始化,赋值为THIS_MODULE。

platform_device probe/remove

of_device_id imx_pwm_audio_dt_ids

#pragma mark - platform_device probe/removestatic const struct of_device_id imx_pwm_audio_dt_ids[] = {{ .compatible = "glowforge,imx-pwm-audio" },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_pwm_audio_dt_ids);

​ MODULE_DEVICE_TABLE 为内核宏,第一个参数是设备的类型,第二个参数是设备表。

​ 一般用在热插拔的设备驱动中。上述xx_driver_ids结构,是此驱动所支持的设备列表。

​ 作用是:将xx_driver_ids结构输出到用户空间,这样模块加载系统在加载模块时,就知道了什么模块对应什么硬件设备。

​ 用法是:MODULE_DEVICE_TABLE(设备类型,设备表),其中,设备类型,包括USB,PCI等,也可以自己起名字,上述代码中是针对不同的平台分的类;设备表也是自己定义的,它的最后一项必须是空,用来标识结束。

​ 会生成一个名为__mod_of_device_table的局部变量,该变量指向imx_pwm_audio_dt_ids。

imx_pwm_audio_probe

static int imx_pwm_audio_probe(struct platform_device *pdev)
{const struct of_device_id *of_id = of_match_device(imx_pwm_audio_dt_ids,&pdev->dev);struct imx_pwm_audio_data *self;struct resource *r;int ret = 0;struct device_node *epit_np = NULL;u32 sdma_params[2];if (!of_id) {return -ENODEV;}self = devm_kzalloc(&pdev->dev, sizeof(*self), GFP_KERNEL);if (self == NULL) {dev_err(&pdev->dev, "failed to allocate memory");return -ENOMEM;}self->clk_per = devm_clk_get(&pdev->dev, "per");if (IS_ERR(self->clk_per)) {dev_err(&pdev->dev, "getting per clock failed with %ld",PTR_ERR(self->clk_per));return PTR_ERR(self->clk_per);}dev_dbg(&pdev->dev, "clk_per rate: %lu", clk_get_rate(self->clk_per));self->clk_ipg = devm_clk_get(&pdev->dev, "ipg");if (IS_ERR(self->clk_ipg)) {dev_err(&pdev->dev, "getting ipg clock failed with %ld",PTR_ERR(self->clk_ipg));return PTR_ERR(self->clk_ipg);}dev_dbg(&pdev->dev, "clk_ipg rate: %lu", clk_get_rate(self->clk_ipg));r = platform_get_resource(pdev, IORESOURCE_MEM, 0);self->mmio_base = devm_ioremap_resource(&pdev->dev, r);if (IS_ERR(self->mmio_base)) {return PTR_ERR(self->mmio_base);}self->mmio_base_phys = r->start;self->enable_gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0);if (self->enable_gpio < 0) {dev_err(&pdev->dev, "no enable-gpio specified");return -EINVAL;}ret = devm_gpio_request_one(&pdev->dev, self->enable_gpio, 0, "spkr_en");if (ret) {dev_err(&pdev->dev, "cannot reserve spkr_en gpio");return ret;}self->dev = &pdev->dev;platform_set_drvdata(pdev, self);/* 为样本缓冲区分配连续内存 */self->sample_buf = dma_zalloc_coherent(&pdev->dev, SAMPLE_BUF_SIZE,&self->sample_buf_phys, GFP_KERNEL);if (!self->sample_buf) {dev_err(&pdev->dev, "failed to allocate sample buffer");return -ENOMEM;}dev_dbg(&pdev->dev, "allocated %d byte sample buffer at 0x%08x",SAMPLE_BUF_SIZE, self->sample_buf_phys);/* Set up timer */epit_np = of_parse_phandle(pdev->dev.of_node, "timer", 0);if (IS_ERR(epit_np)) {dev_err(&pdev->dev, "no timer specified");ret = -ENODEV;goto failed_epit_init;}self->epit = epit_get(epit_np);of_node_put(epit_np);if (!self->epit) {dev_err(&pdev->dev, "failed to get timer");ret = -ENODEV;goto failed_epit_init;}ret = epit_init_freerunning(self->epit, NULL, NULL);if (ret) {dev_err(&pdev->dev, "failed to initialize timer");goto failed_epit_init;}/* 读取SDMA通道号和加载地址 */if (of_property_read_u32_array(pdev->dev.of_node, "sdma-params",sdma_params, ARRAY_SIZE(sdma_params)) == 0) {self->sdma_ch_num = sdma_params[0];self->sdma_script_origin = sdma_params[1];} else {dev_err(&pdev->dev, "sdma-params property not specified");goto failed_sdma_init;}/* 设置SDMA并获取通道参考 */self->sdma = sdma_engine_get();if (IS_ERR(self->sdma)) {dev_err(&pdev->dev, "failed to get sdma engine");ret = -ENODEV;goto failed_sdma_init;}self->sdmac = sdma_get_channel(self->sdma, self->sdma_ch_num);if (IS_ERR(self->sdmac)) {dev_err(&pdev->dev, "failed to get sdma channel");ret = -ENODEV;goto failed_sdma_init;}/* 加载SDMA脚本 */ret = load_sdma_script(self);if (ret) {goto failed_load_sdma_script;}ret = load_script_context(self);if (ret) {goto failed_load_sdma_script;}sdma_set_channel_interrupt_callback(self->sdmac, pwm_audio_sdma_interrupt,self);mutex_init(&self->lock);self->audiodev.minor = AUDIO_DEVICE_MINOR_NUMBER;self->audiodev.name = AUDIO_DEVICE_NAME;self->audiodev.fops = &audio_dev_fops;self->audiodev.parent = &pdev->dev;ret = misc_register(&self->audiodev);if (ret) {dev_err(&pdev->dev, "unable to register " AUDIO_DEVICE_PATH);goto failed_dev_register;}return pwm_enable(self);failed_dev_register:mutex_destroy(&self->lock);
failed_load_sdma_script:
failed_sdma_init:epit_stop(self->epit);
failed_epit_init:dma_free_coherent(&pdev->dev, SAMPLE_BUF_SIZE, self->sample_buf,self->sample_buf_phys);return ret;
}

imx_pwm_audio_remove

static int imx_pwm_audio_remove(struct platform_device *pdev)
{struct imx_pwm_audio_data *self = platform_get_drvdata(pdev);misc_deregister(&self->audiodev);sdma_set_channel_interrupt_callback(self->sdmac, NULL, NULL);pwm_audio_stop(self);pwm_disable(self);dma_free_coherent(&pdev->dev, SAMPLE_BUF_SIZE, self->sample_buf,self->sample_buf_phys);mutex_destroy(&self->lock);return 0;
}

platform_driver imx_pwm_audio_driver

static struct platform_driver imx_pwm_audio_driver = {.driver = {.name = "imx-pwm-audio",.owner = THIS_MODULE,.of_match_table = imx_pwm_audio_dt_ids,},.probe = imx_pwm_audio_probe,.remove = imx_pwm_audio_remove,
};

Module init/exit

imx_pwm_audio_init

#pragma mark - Module init/exitstatic int __init imx_pwm_audio_init(void)
{int ret = 0;pr_info("%s: %u bits/sample%s, %u Hz\n", THIS_MODULE->name,bits_per_sample, (signed_samples) ? " (signed)" : "", sample_rate);/* 拒绝无意义参数 */if (bits_per_sample < 4 || bits_per_sample > 16) {pr_err("value %u is invalid for bits_per_sample param\n", bits_per_sample);return -EINVAL;}if (sample_rate < 1000 || sample_rate > 48000) {pr_err("value %u is invalid for sample_rate param\n", sample_rate);return -EINVAL;}/* 计算样本偏移量 */if (!signed_samples) {u32 bits_per_sample_rounded_up = BYTES_PER_SAMPLE*8;sample_offset = (1 << (bits_per_sample_rounded_up-1)) -(1 << (bits_per_sample-1));} else {sample_offset = -(1 << (bits_per_sample-1));}/* TODO: 如果采样率高于给定比特率(bps)的PWM频率,则发出警报 */ret = platform_driver_register(&imx_pwm_audio_driver);if (ret < 0) {pr_err("failed to initialize audio driver\n");}return ret;
}
module_init(imx_pwm_audio_init);

imx_pwm_audio_exit

static void __exit imx_pwm_audio_exit(void)
{platform_driver_unregister(&imx_pwm_audio_driver);pr_info("%s: unloaded\n", THIS_MODULE->name);
}
module_exit(imx_pwm_audio_exit);

LICENSE、AUTHOR、DESCRIPTION

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Glowforge, Inc. ");
MODULE_DESCRIPTION("Freescale i.MX6 PWM audio interface");


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部