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 <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");
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值