Linux Alsa音频驱动框架(声卡的运行以及PCM数据流读写)

1)前一章主要讲解Linux Alsa音频驱动框架内的声卡的创建以及注册详细流程分析,本节主要深入讲解PCM数据在Alsa架构的声卡中的输入、输出流向以及codec内部pipeline。同样以三星的S5PV210/MINI210平台来分析,同样适用于三星其他平台、以及其他SOC厂商(MTK/海思/Mstar/Amlogic/SigmaStar/全志/RockChip平台)遵守Linux Alsa架构的平台。分享给将要学习或者正在学习Linux音频驱动的同学。

2)适用于对C语言有基本的认识,以及对Linux驱动知识有基本的掌握能力。

3)Linux内核版本:Linux3.0.8。

4)内容属于原创,若转载,请说明出处。

5)提供相关问题有偿答疑和支持。

上一个笔记分析了声卡的创建以及注册的过程,那么这个笔记开始就分析一下执行的过程:上一个笔记分析知道在声卡创建pcm设备的时候在pcm_native.c中会注册一个ops:struct file_operations snd_pcm_f_ops[2];此结构包含了pcm的播放以及录制的ops;
注册的接口是:
 

int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
               const struct file_operations *f_ops,
               void *private_data,
               const char *name, struct device *device)
{
   int minor;
   struct snd_minor *preg;

   if (snd_BUG_ON(!name))
       return -EINVAL;
   preg = kmalloc(sizeof *preg, GFP_KERNEL);
   if (preg == NULL)
       return -ENOMEM;
   preg->type = type;
   preg->card = card ? card->number : -1;
   preg->device = dev;
   preg->f_ops = f_ops;  //赋值
   preg->private_data = private_data;
   mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
   minor = snd_find_free_minor(type);
#else
   minor = snd_kernel_minor(type, card, dev);
   if (minor >= 0 && snd_minors[minor])
       minor = -EBUSY;
#endif
   if (minor < 0) {
       mutex_unlock(&sound_mutex);
       kfree(preg);
       return minor;
   }
   snd_minors[minor] = preg;  //将上面的f_ops保存到snd_minors中去
   preg->dev = device_create(sound_class, device, MKDEV(major, minor),
                 private_data, "%s", name); //这里会去创建设备节点,注意major, minor设备号固定,比较特殊,后面会创建同样的设备;定义:static int major = CONFIG_SND_MAJOR;
   if (IS_ERR(preg->dev)) {
       snd_minors[minor] = NULL;
       mutex_unlock(&sound_mutex);
       minor = PTR_ERR(preg->dev);
       kfree(preg);
       return minor;
   }

   mutex_unlock(&sound_mutex);
   return 0;
}

EXPORT_SYMBOL(snd_register_device_for_dev);

 在sound/core/sound.c中有一个单独的模块,专门是为了注册声卡字符设备用的:这里的设备号与上面的是同一个:static int major = CONFIG_SND_MAJOR;
75edc6e2633e41b191add2c5d4bbe1f8.png
当应用程序open设备文件/dev/snd/pcmCxDxp时,会进入snd_fops的open回调函数;

static int snd_open(struct inode *inode, struct file *file)
{
   unsigned int minor = iminor(inode);
   struct snd_minor *mptr = NULL;
   const struct file_operations *old_fops;
   int err = 0;

   if (minor >= ARRAY_SIZE(snd_minors))
       return -ENODEV;
   mutex_lock(&sound_mutex);
   mptr = snd_minors[minor]; //拿到snd_minors结构
   if (mptr == NULL) {
       mptr = autoload_device(minor);
       if (!mptr) {
           mutex_unlock(&sound_mutex);
           return -ENODEV;
       }
   }
   old_fops = file->f_op;
  file->f_op = fops_get(mptr->f_ops);  //将snd_minors中的f_ops拷贝到file的f_op中,应用程序的所有read/write/ioctl调用都会进入pcm设备自己的回调函数中,也就是上面提到的snd_pcm_f_ops结构中定义的回调。
   if (file->f_op == NULL) {
       file->f_op = old_fops;
       err = -ENODEV;
   }
   mutex_unlock(&sound_mutex);
   if (err < 0)
       return err;

   if (file->f_op->open) {
       err = file->f_op->open(inode, file);//最后调用一次open
       if (err) {
           fops_put(file->f_op);
           file->f_op = fops_get(old_fops);
       }
   }
   fops_put(old_fops);
   return err;
}

对于在codec的驱动中我们经常会看到如下设置codec的寄存器:
6076ef08c60248d18664889e60d04f63.png

下面来分析一下此接口设置寄存器的原理:前面的分析知道在bind以后会去依次调用各个driver的probe函数,对于codec的驱动也是一样:在probe的时候会传递一个snd_soc_codec参数:
89344fddddb04b338c77c25f67ed26fb.png
在soc-core中的函数soc_probe_dai_link中找到此段代码:
 

static int soc_probe_dai_link(struct snd_soc_card *card, int num) {
   struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
   struct snd_soc_codec *codec = rtd->codec;
    .................
   /* probe the CODEC */
   if (!codec->probed) {
       ret = soc_probe_codec(card, codec);
       if (ret < 0)
           return ret;
   }
   ..................
}

static int soc_probe_codec(struct snd_soc_card *card,
              struct snd_soc_codec *codec)
{
   const struct snd_soc_codec_driver *driver = codec->driver;
    ...............
   if (driver->dapm_widgets)
       snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets,
                     driver->num_dapm_widgets);

   if (driver->probe) {
       ret = driver->probe(codec); //这里调用probe,代码跟踪发现这里的codec就是在之前bind的时候codec_list中保存的codec信息
       if (ret < 0) {
           dev_err(codec->dev,
               "asoc: failed to probe CODEC %s: %d\n",
               codec->name, ret);
           goto err_probe;
       }
   }
   ..................
}

进去snd_soc_update_bits发现调用的流程是:snd_soc_update_bits(codec)  ->  snd_soc_read(codec)/ snd_soc_write(codec)  ->  codec->write(codec)/codec->read(codec);那么实际最后的读写调用codec中的read/write,那么codec中的write/read是在上面的soc_probe_codec中的codec参数,也即来自于codec_list中的codec信息,那么codec_list中的codec信息是在注册codec的时候从snd_soc_dai_driver传递过来的参数codec_drv :
f3541f379d5c4213a8d9ba2b2747926b.png
但是实际发现snd_soc_codec_driver中并未实现read和write接口:
ac89a9aaa4424d61bf00b2d62bd2d73a.png
不能理解如何调用read/write接口,因此继续分析发现,在wm8960_probe函数中调用snd_soc_update_bits之前有一个函数snd_soc_codec_set_cache_io:
..............
ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type);
..............
此函数就是设置iic/spi读写接口的,新的内核使用regmap来实现: addr_bits代表地址位数,data_bits代表数据位的长度一般都是7bit,从wm8960的文档中可以看出寄存器的位数是9bit;
13ae8bb5aaab4ac0acc79f0f1bcca742.png
下面可以看出这里在codec中保存了3个与codec读写有关的接口:codec->write/codec-read/codec/hw_write,另外最后还将IIC的client句柄保存到codec->control_data中,后面在写硬件IIC的时候会用到此句柄;
254722f63bdf4465bc10281b7618715d.jpeg
如下是对应的接口:
8f459f3cb6374eeeb6e0d9a7467d4c83.png
因此在上层调用snd_soc_update_bits的时候实际调用sound/soc/soc-cache.c中的snd_soc_7_9_write/snd_soc_7_9_read,好了接着分析这两个读写接口:
static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,unsigned int reg)
{
   return do_hw_read(codec, reg);
}
static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,unsigned int value)
{
    u8 data[2];
    data[0] = (reg << 1) | ((value >> 8) & 0x0001);
    data[1] = value & 0x00ff;
    return do_hw_write(codec, reg, value, data, 2);
}
从wm8960的数据手册中可以看到wm8960一共有56个寄存器,地址从0x00~0x37,每一个寄存器占9bit,在驱动中会把所有寄存器的数目以及每一个寄存器的默认值按顺序保存:
082b4bad171e40b3bc1839da45b64c87.png
如下寄存器编号:(0x00~0x37)
6f24b800bd164ef0a11e2ddfcaf86174.png
那么snd_soc_update_bits修改寄存器的值都是传递此编号对应的寄存器;接着上面的do_hw_read函数:

static unsigned int do_hw_read(struct snd_soc_codec *codec, unsigned int reg)
{
   int ret;
   unsigned int val;

   if (reg >= codec->driver->reg_cache_size ||    //先判断被设置的寄存器编号是否大于预置的寄存器编号数目56,正确设置的话是不会超过的
       snd_soc_codec_volatile_register(codec, reg) ||  //返回0
       codec->cache_bypass) {  // 未定义cache_bypass
       if (codec->cache_only)
           return -1;

       BUG_ON(!codec->hw_read);
       return codec->hw_read(codec, reg);  
   }   //跳过此if

   ret = snd_soc_cache_read(codec, reg, &val); //进入此函数读取默认寄存器表中的值
   if (ret < 0)
       return -1;
   return val;
}

int snd_soc_codec_volatile_register(struct snd_soc_codec *codec,
                   unsigned int reg)
{
   if (codec->volatile_register)  //在snd_soc_register_codec函数中有赋值codec_drv->volatile_register;但是volatile_register在codec_drv中未定义;
       return codec->volatile_register(codec, reg);
   else
       return 0;
}

int snd_soc_cache_read(struct snd_soc_codec *codec,
              unsigned int reg, unsigned int *value)
{  
   int ret;
   
   mutex_lock(&codec->cache_rw_mutex);
   
   if (value && codec->cache_ops && codec->cache_ops->read) {  //在上一个笔记《Linux音频驱动框架(声卡的创建以及注册)》中分析过此ops接口,因此在次处会应用到,这里read主要是获取上面的wm8960_reg的cache临时保存的寄存器中的值
       ret = codec->cache_ops->read(codec, reg, value);
       mutex_unlock(&codec->cache_rw_mutex);
       return ret;
   }
   
   mutex_unlock(&codec->cache_rw_mutex);
   return -ENOSYS;
}  

下面分析写:
static int do_hw_write(struct snd_soc_codec *codec, unsigned int reg,
              unsigned int value, const void *data, int len)
{
   int ret;
   if (!snd_soc_codec_volatile_register(codec, reg) && //上面分析snd_soc_codec_volatile_register返回0
       reg < codec->driver->reg_cache_size &&  //正常这里不会大于codec->driver->reg_cache_size
       !codec->cache_bypass) {  // codec->cache_bypass未定义
        ret = snd_soc_cache_write(codec, reg, value); //因此执行到这里,目的是更新寄存器cache中的寄存器的临时值
       if (ret < 0)
           return -1;
   }    
   if (codec->cache_only) {
       codec->cache_sync = 1;
       return 0;
   }
   ret = codec->hw_write(codec->control_data, data, len); //接着这里真正的写硬件IIC
   if (ret == len)
       return 0;
   if (ret < 0)
       return ret;
   else
       return -EIO;
}

int snd_soc_cache_write(struct snd_soc_codec *codec,
           unsigned int reg, unsigned int value)
{
   int ret;

   mutex_lock(&codec->cache_rw_mutex);

   if (codec->cache_ops && codec->cache_ops->write) {  //这个和上面的read相同,这里会更新wm8960_reg的临时cache保存的寄存器中的值
       ret = codec->cache_ops->write(codec, reg, value);
       mutex_unlock(&codec->cache_rw_mutex);
       return ret;
   }

   mutex_unlock(&codec->cache_rw_mutex);
   return -ENOSYS;
}

在上面的分析知道codec->hw_write(codec->control_data, data, len); 实际调用IIC的标准接口i2c_master_send函数(linux-smart210/drivers/i2c/i2c-core.c):
1369 int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
1370 {
1371     int ret;
1372     struct i2c_adapter *adap = client->adapter;
1373     struct i2c_msg msg;
1374
1375     msg.addr = client->addr;
1376     msg.flags = client->flags & I2C_M_TEN;
1377     msg.len = count;
1378     msg.buf = (char *)buf;
1379  
1380     ret = i2c_transfer(adap, &msg, 1);
1381  
1382     /* If everything went ok (i.e. 1 msg transmitted), return #bytes
1383        transmitted, else error code. */
1384     return (ret == 1) ? count : ret;
1385 }

至此,关于IIC的读写分析完毕:发现codec的读写为了提高效率,硬件有关的只是实现写接口,而在写硬件的同时会把寄存器的值保存到一个临时的cache中,然后在读的时候不会去直接读硬件,而是读cache中的临时保存值;另外一个问题就是在codec的设置寄存器中经常会看到mask的参数,如:
 

snd_soc_update_bits(codec, WM8960_DACCTL1, 0x6, val);
******************************************************************************************
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
               unsigned int mask, unsigned int value)
{
   int change;
   unsigned int old, new;
   int ret;

   ret = snd_soc_read(codec, reg);
   if (ret < 0)
       return ret;

   old = ret;
   new = (old & ~mask) | value;
   change = old != new;
   if (change) {
       ret = snd_soc_write(codec, reg, new);
       if (ret < 0)
           return ret;
   }    

   return change;
}
******************************************************************************************

下面来分析一下:
参数mask主要表示要写(更新)某个寄存器的哪些位:
#define WM8960_DACCTL1      0x5
从上面的cache表中可以看出默认值是8(0 0000 1000),下面是寄存器:
edf0cb28897c4c7db7c0c217a600c54c.jpeg

88dfb902eeee484fa9a9f17b04349dc8.jpeg

上面的的函数mask值是6:(0 0000 0110)
new = (old & ~mask) | value;  // new = (0 0000 1000  &  1 1111 1001) |  value
上面可以看出mask的主要作用就是标记要改变的位,如上面要改变的位是2:1即DEEMPH表示的值,其他未改变的值保持原样;

对于在machine中(mini210_wm8960.c)我们经常会看到如下初始化DAPM:
41458e163bea4e7484ef333083fe366f.png
概念PGA:PGA是Programmable Gain Amplifier (可编程增益放大器)
下面来分析一下DAPM框架:
DAPM框架中几个重要的数据结构:snd_soc_dapm_widget,snd_soc_dapm_path,snd_soc_dapm_route。其中snd_soc_dapm_path无需我们自己定义,它会在注册snd_soc_dapm_route时动态地生成,但是对于系统中的widget和route,我们是需要自己进行定义的,另外,widget所包含的kcontrol与普通的kcontrol有所不同,它们的定义方法与标准的kcontrol也有所不同。和普通的kcontrol一样,DAPM框架为我们提供了大量的辅助宏用来定义各种各样的widget控件,这些宏定义根据widget的类型,按照它们的电源所在的域,被分为了几个域,他们分别是:
codec域     比如VREF和VMID等提供参考电压的widget,这些widget通常在codec的probe/remove回调中进行控制,当然,在工作中如果没有音频流时,也可以适当地进行控制它们的开启与关闭。
platform域    位于该域上的widget通常是针对平台或板子的一些需要物理连接的输入/输出接口,例如耳机、扬声器、麦克风,因为这些接口在每块板子上都可能不一样,所以通常它们是在machine驱动中进行定义和控制,并且也可以由用户空间的应用程序通过某种方式来控制它们的打开和关闭。
音频路径域    一般是指codec内部的mixer、mux等控制音频路径的widget,这些widget可以根据用户空间的设定连接关系,自动设定他们的电源状态。
音频数据流域    是指那些需要处理音频数据流的widget,例如ADC、DAC等等。

首先看一下这3个结构:(soc-dapm.h)
struct snd_soc_dapm_widget {
   enum snd_soc_dapm_type id;
   char *name;     /* widget name */
   char *sname;    /* stream name */
   struct snd_soc_codec *codec;
   struct list_head list;
   struct snd_soc_dapm_context *dapm;

   /* dapm control */
   short reg;                      /* negative reg = no direct dapm */
   unsigned char shift;            /* bits to shift */
   unsigned int saved_value;       /* widget saved value */
   unsigned int value;             /* widget current value */
   unsigned int mask;          /* non-shifted mask */
   unsigned int on_val;            /* on state value */
   unsigned int off_val;           /* off state value */
   unsigned char power:1;          /* block power status */
   unsigned char invert:1;         /* invert the power bit */
   unsigned char active:1;         /* active stream on DAC, ADC's */
   unsigned char connected:1;      /* connected codec pin */
   unsigned char new:1;            /* cnew complete */
   unsigned char ext:1;            /* has external widgets */
   unsigned char force:1;          /* force state */
   unsigned char ignore_suspend:1;         /* kept enabled over suspend */
   int subseq;             /* sor
  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值