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 <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <asm/uaccess.h>
#include <linux/platform_data/epit-imx.h>
#include <linux/platform_data/dma-imx-sdma.h>
模块参数
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文件系统中所对应的文件节点的属性可以使用<linux/stat.h>中定义的权限值;
当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 - DMA
static 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 functions
static 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 setup
static 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/remove
static 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/exit
static 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. <opensource@glowforge.com>");
MODULE_DESCRIPTION("Freescale i.MX6 PWM audio interface");