九鼎创展论坛中文版English
登录 | 立即注册 设为首页收藏本站 切换到宽版
查看: 6924|回复: 1
打印 上一主题 下一主题

x4412&ibox项目实战55-Linux音频驱动之I2C驱动实验

[复制链接]
跳转到指定楼层
楼主
发表于 2014-11-27 17:27:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
音频驱动关于I2C的源码路径:
  1. kernel/sound/soc/codecs/wm8960.c
  2. kernel/arch/arm/mach-exynos/mach-x4412.c
  3. kernel/sound/soc/soc-core.c
  4. kernel/sound/soc/soc-cache.c
复制代码
       mach-x4412.c中,通过I2C_BOARD_INFO宏指定了音频芯片wm8960的名称和i2c地址,同时,在i2c_devs0数组中,还指定了音频驱动的平台数据:
  1. static struct wm8960_data wm8960_pdata = {
  2.          .capless      = 0,
  3.          .dres           = WM8960_DRES_400R,
  4. };

  5. static struct i2c_board_info i2c_devs0[] __initdata = {
  6.          {
  7.                    I2C_BOARD_INFO("kxud9", 0x18),
  8.                    .platform_data     = &kxud9_data,
  9.          }, {
  10.                    I2C_BOARD_INFO("wm8960", 0x1a),
  11.                    .platform_data = &wm8960_pdata,
  12.          }
  13. };
复制代码
       smdk4x12_machine_init函数中,通过i2c_register_board_info函数注册相应的i2c设备:
  1. i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
复制代码
       这时,一个名为wm8960,设备地址为0x1a的设备已经被添加到i2c总线上。
       wm8960.cwm8960_modinit中,通过i2c_add_driver函数添加了一个名为wm8960-codeci2c设备驱动:
  1. static const struct i2c_device_id wm8960_i2c_id[] = {
  2.          { "wm8960", 0 },
  3.          { }
  4. };
  5. MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);

  6. static struct i2c_driver wm8960_i2c_driver = {
  7.          .driver = {
  8.                    .name = "wm8960-codec",
  9.                    .owner = THIS_MODULE,
  10.          },
  11.          .probe =    wm8960_i2c_probe,
  12.          .remove =   __devexit_p(wm8960_i2c_remove),
  13.          .id_table = wm8960_i2c_id,
  14. };
复制代码
       wm8960_i2c_id中,可以找到和I2C_BOARD_INFO宏注册的相同的i2c设备驱动名称wm8960,探测函数wm8960_i2c_probe被触发。
  1. static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
  2.                                            const struct i2c_device_id *id)
  3. {
  4.          struct wm8960_priv *wm8960;
  5.          int ret;

  6.          //在这里可以打印i2c->addr和i2c->type,表明从设备i2c_client已经传递进来
  7.          wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
  8.          if (wm8960 == NULL)
  9.                    return -ENOMEM;

  10.          i2c_set_clientdata(i2c, wm8960);//保存i2c相关数据
  11.          wm8960->control_type = SND_SOC_I2C;
  12.          wm8960->control_data = i2c;

  13.          ret = snd_soc_register_codec(&i2c->dev,
  14.                             &soc_codec_dev_wm8960, &wm8960_dai, 1);
  15.          if (ret < 0)
  16.                    kfree(wm8960);
  17.          return ret;
  18. }
复制代码
       在探测函数中,通过名为i2c的形参结构体传入i2c设备的地址和名称,可以在函数入口添加如下打印语句:
  1. printk("%s:i2c->addr = 0x%x,i2c->name = %s\n",__FUNCTION__,i2c->addr,i2c->name);
复制代码
       编译内核,更新映像后,可以看到如下打印信息:
  1. wm8960_i2c_probe:i2c->addr = 0x1a,i2c->name = wm8960
复制代码
       后续整个i2c的读写,都离不开这个结构体。在wm8960.c中,通过snd_soc_read函数读取寄存器值,通过snd_soc_write函数写寄存器值。snd_soc_read函数原型在soc-core.c中:
  1. unsigned int snd_soc_read(struct snd_soc_codec *codec, unsigned int reg)
  2. {
  3.          unsigned int ret;

  4.          ret = codec->read(codec, reg);
  5.          dev_dbg(codec->dev, "read %x => %x\n", reg, ret);
  6.          trace_snd_soc_reg_read(codec, reg, ret);

  7.          return ret;
  8. }
  9. EXPORT_SYMBOL_GPL(snd_soc_read);
复制代码
       它通过codec->read函数,即形参结构体codec中的成员read实现I2C的读。在wm8960.cprobe函数wm8960_probe中,snd_soc_codec_set_cache_io函数实现了对结构体codec的成员赋值,snd_soc_codec_set_cache_io的函数原型在soc-cache.c中:
  1. int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
  2.                                    int addr_bits, int data_bits,
  3.                                    enum snd_soc_control_type control)
  4. {
  5.          int i;

  6.          for (i = 0; i < ARRAY_SIZE(io_types); i++)
  7.                    if (io_types[i].addr_bits == addr_bits &&
  8.                        io_types[i].data_bits == data_bits)
  9.                             break;
  10.          if (i == ARRAY_SIZE(io_types)) {
  11.                    printk(KERN_ERR
  12.                           "No I/O functions for %d bit address %d bit data\n",
  13.                           addr_bits, data_bits);
  14.                    return -EINVAL;
  15.          }

  16.          codec->write = io_types[i].write;
  17.          codec->read = io_types[i].read;
  18.          codec->bulk_write_raw = snd_soc_hw_bulk_write_raw;

  19.          switch (control) {
  20.          case SND_SOC_I2C:
  21. #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
  22.                    codec->hw_write = (hw_write_t)i2c_master_send;//填充snd_soc_codec结构体,指定i2c写函数
  23. #endif
  24.                    if (io_types[i].i2c_read)
  25.                             codec->hw_read = io_types[i].i2c_read;

  26.                    codec->control_data = container_of(codec->dev,
  27.                                                            struct i2c_client,
  28.                                                            dev);
  29.                    break;

  30.          case SND_SOC_SPI:
  31. #ifdef CONFIG_SPI_MASTER
  32.                    codec->hw_write = do_spi_write;
  33. #endif

  34.                    codec->control_data = container_of(codec->dev,
  35.                                                            struct spi_device,
  36.                                                            dev);
  37.                    break;
  38.          }

  39.          return 0;
  40. }
  41. EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
复制代码
       io_types数组定义如下:
  1. static struct {
  2.          int addr_bits;
  3.          int data_bits;
  4.          int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int);
  5.          unsigned int (*read)(struct snd_soc_codec *, unsigned int);
  6.          unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int);
  7. } io_types[] = {
  8.          {
  9.                    .addr_bits = 4, .data_bits = 12,
  10.                    .write = snd_soc_4_12_write, .read = snd_soc_4_12_read,
  11.          },
  12.          {
  13.                    .addr_bits = 7, .data_bits = 9,
  14.                    .write = snd_soc_7_9_write, .read = snd_soc_7_9_read,
  15.          },
  16.          {
  17.                    .addr_bits = 8, .data_bits = 8,
  18.                    .write = snd_soc_8_8_write, .read = snd_soc_8_8_read,
  19.                    .i2c_read = snd_soc_8_8_read_i2c,
  20.          },
  21.          {
  22.                    .addr_bits = 8, .data_bits = 16,
  23.                    .write = snd_soc_8_16_write, .read = snd_soc_8_16_read,
  24.                    .i2c_read = snd_soc_8_16_read_i2c,
  25.          },
  26.          {
  27.                    .addr_bits = 16, .data_bits = 8,
  28.                    .write = snd_soc_16_8_write, .read = snd_soc_16_8_read,
  29.                    .i2c_read = snd_soc_16_8_read_i2c,
  30.          },
  31.          {
  32.                    .addr_bits = 16, .data_bits = 16,
  33.                    .write = snd_soc_16_16_write, .read = snd_soc_16_16_read,
  34.                    .i2c_read = snd_soc_16_16_read_i2c,
  35.          },
  36. };
复制代码
       在引用snd_soc_codec_set_cache_io函数时,传入的形参已经指定addr_bits7data_bits9,故读函数对应snd_soc_7_9_read,写函数对应snd_soc_7_9_write
       snd_soc_7_9_read函数对应函数原型如下:
  1. static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
  2.                                           unsigned int reg)
  3. {
  4.          return do_hw_read (codec, reg);
  5. }
复制代码
       do_hw_read函数原型如下:
  1. static unsigned int do_hw_read(struct snd_soc_codec *codec, unsigned int reg)
  2. {
  3.          int ret;
  4.          unsigned int val;

  5.          if (reg >= codec->driver->reg_cache_size ||
  6.              snd_soc_codec_volatile_register(codec, reg) ||
  7.              codec->cache_bypass) {
  8.                    if (codec->cache_only)
  9.                             return -1;

  10.                    BUG_ON(!codec->hw_read);
  11.                    return codec->hw_read(codec, reg);
  12.          }

  13.          ret = snd_soc_cache_read(codec, reg, &val);
  14.          if (ret < 0)
  15.                    return -1;
  16.          return val;
  17. }
复制代码
       从这里很容易分析出,do_hw_read函数最终会调用snd_soc_cache_read函数。它的函数原型如下:
  1. int snd_soc_cache_read(struct snd_soc_codec *codec,
  2.                           unsigned int reg, unsigned int *value)
  3. {
  4.          int ret;

  5.          mutex_lock(&codec->cache_rw_mutex);

  6.          if (value && codec->cache_ops && codec->cache_ops->read) {
  7.                    ret = codec->cache_ops->read(codec, reg, value);
  8.                    mutex_unlock(&codec->cache_rw_mutex);
  9.                    return ret;
  10.          }

  11.          mutex_unlock(&codec->cache_rw_mutex);
  12.          return -ENOSYS;
  13. }
  14. EXPORT_SYMBOL_GPL(snd_soc_cache_read);
复制代码
       可见,真正的i2c读函数会调用codec->cache_ops->read()函数。在soc-core.cprobe函数soc_probe中,调用了snd_soc_register_card函数,用于注册声卡,该函数会继续调用snd_soc_instantiate_cards函数初始化声卡,最终初始化声卡的函数为snd_soc_instantiate_card,它引用了snd_soc_init_codec_cache函数初始化解码芯片,snd_soc_cache_init函数被调用,它的函数原型在soc-cache.c中:

  1. int snd_soc_cache_init(struct snd_soc_codec *codec)
  2. {
  3.          int i;

  4.          for (i = 0; i < ARRAY_SIZE(cache_types); ++i)
  5.                    if (cache_types[i].id == codec->compress_type)
  6.                             break;

  7.          /* Fall back to flat compression */
  8.          if (i == ARRAY_SIZE(cache_types)) {
  9.                    dev_warn(codec->dev, "Could not match compress type: %d\n",
  10.                              codec->compress_type);
  11.                    i = 0;
  12.          }

  13.          mutex_init(&codec->cache_rw_mutex);
  14.          codec->cache_ops = &cache_types[i];//赋值cache_ops

  15.          if (codec->cache_ops->init) {
  16.                    if (codec->cache_ops->name)
  17.                             dev_dbg(codec->dev, "Initializing %s cache for %s codec\n",
  18.                                      codec->cache_ops->name, codec->name);
  19.                    return codec->cache_ops->init(codec);
  20.          }
  21.          return -ENOSYS;
  22. }
复制代码
       可以看到,加粗部分已经给codec->cache_ops赋值了,再回到前面,真正执行i2c读的函数会调用codec->cache_ops->read()函数,也就是cache_types中的读函数。该数组的程序清单如下:
  1. static const struct snd_soc_cache_ops cache_types[] = {
  2.          /* Flat *must* be the first entry for fallback */
  3.          {
  4.                    .id = SND_SOC_FLAT_COMPRESSION,
  5.                    .name = "flat",
  6.                    .init = snd_soc_flat_cache_init,
  7.                    .exit = snd_soc_flat_cache_exit,
  8.                    .read = snd_soc_flat_cache_read,
  9.                    .write = snd_soc_flat_cache_write,
  10.                    .sync = snd_soc_flat_cache_sync
  11.          },
  12. #ifdef CONFIG_SND_SOC_CACHE_LZO
  13.          {
  14.                    .id = SND_SOC_LZO_COMPRESSION,
  15.                    .name = "LZO",
  16.                    .init = snd_soc_lzo_cache_init,
  17.                    .exit = snd_soc_lzo_cache_exit,
  18.                    .read = snd_soc_lzo_cache_read,
  19.                    .write = snd_soc_lzo_cache_write,
  20.                    .sync = snd_soc_lzo_cache_sync
  21.          },
  22. #endif
  23.          {
  24.                    .id = SND_SOC_RBTREE_COMPRESSION,
  25.                    .name = "rbtree",
  26.                    .init = snd_soc_rbtree_cache_init,
  27.                    .exit = snd_soc_rbtree_cache_exit,
  28.                    .read = snd_soc_rbtree_cache_read,
  29.                    .write = snd_soc_rbtree_cache_write,
  30.                    .sync = snd_soc_rbtree_cache_sync
  31.          }
  32. };
复制代码
       结合程序上下文,可以分析出codec->compress_typeSND_SOC_FLAT_COMPRESSION,即对应的读函数为snd_soc_flat_cache_read。其函数原型如下:
  1. static int snd_soc_flat_cache_read(struct snd_soc_codec *codec,
  2.                                         unsigned int reg, unsigned int *value)
  3. {
  4.          *value = snd_soc_get_cache_val(codec->reg_cache, reg,
  5.                                             codec->driver->reg_word_size);
  6.          return 0;
  7. }
复制代码
       snd_soc_get_cache_val的函数原型如下:
  1. static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx,
  2.                    unsigned int word_size)
  3. {
  4.          if (!base)
  5.                    return -1;

  6.          switch (word_size) {
  7.          case 1: {
  8.                    const u8 *cache = base;
  9.                    return cache[idx];
  10.          }
  11.          case 2: {
  12.                    const u16 *cache = base;
  13.                    return cache[idx];
  14.          }
  15.          default:
  16.                    BUG();
  17.          }
  18.          /* unreachable */
  19.          return -1;
  20. }
复制代码
       再来分析snd_soc_get_cache_val函数的几个重要的形参。snd_soc_flat_cache_init函数用于将codec->reg_def_copy赋给codec->reg_cache
  1. static int snd_soc_flat_cache_init(struct snd_soc_codec *codec)
  2. {
  3.          const struct snd_soc_codec_driver *codec_drv;

  4.          codec_drv = codec->driver;

  5.          if (codec->reg_def_copy)
  6.                    codec->reg_cache = kmemdup(codec->reg_def_copy,
  7.                                                   codec->reg_size, GFP_KERNEL);
  8.          else
  9.                    codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL);
  10.          if (!codec->reg_cache)
  11.                    return -ENOMEM;

  12.          return 0;
  13. }
复制代码
       同时,在snd_soc_register_codec函数中,如下程序用于将codec_drv->reg_cache_defaultcodec->reg_def_copy赋值:

  1. if (codec_drv->reg_cache_default) {
  2.                             codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,
  3.                                                               reg_size, GFP_KERNEL);
  4.                             if (!codec->reg_def_copy) {
  5.                                      ret = -ENOMEM;
  6.                                      goto fail;
  7.                             }
  8.                    }
复制代码
       结合wm8960_i2c_probe中的如下语句:
  1. ret = snd_soc_register_codec(&i2c->dev,
  2.                             &soc_codec_dev_wm8960, &wm8960_dai, 1);
复制代码
       可以分析出,codec_drv->reg_cache_default已被赋值为wm8960_reg,而wm8960_reg就是wm8960.c最前面的寄存器表:
  1. static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
  2.          0x0097, 0x0097, 0x0000, 0x0000,
  3.          0x0000, 0x0008, 0x0000, 0x000a,
  4.          0x01c0, 0x0000, 0x00ff, 0x00ff,
  5.          0x0000, 0x0000, 0x0000, 0x0000,
  6.          0x0000, 0x007b, 0x0100, 0x0032,
  7.          0x0000, 0x00c3, 0x00c3, 0x01c0,
  8.          0x0000, 0x0000, 0x0000, 0x0000,
  9.          0x0000, 0x0000, 0x0000, 0x0000,
  10.          0x0100, 0x0100, 0x0050, 0x0050,
  11.          0x0050, 0x0050, 0x0000, 0x0000,
  12.          0x0000, 0x0000, 0x0040, 0x0000,
  13.          0x0000, 0x0050, 0x0050, 0x0000,
  14.          0x0002, 0x0037, 0x004d, 0x0080,
  15.          0x0008, 0x0031, 0x0026, 0x00e9,
  16. };
复制代码
       snd_soc_register_codec函数的第二个形参reg对应着该数组的第reg个数,第三个形参value返回该数组的第reg个数的值。当我们需要读取WM8960的寄存器值时,只需要调用snd_soc_read函数,传入形参codec和寄存器在该表中存在的位号即可。
       可见,WM8960I2C读,并不是直接通过I2C协议从硬件上读取寄存器的值,而是驱动中预先将寄存器的初始值填入一个名为wm8960_reg的寄存器表,当读寄存器值时,直接从该表格里面读取数值,即对应了相应寄存器的值。下面我们再来分析I2C的写。
       驱动通过snd_soc_write函数实现I2C的写,该函数原型在soc-core.c中:
  1. unsigned int snd_soc_write(struct snd_soc_codec *codec,
  2.                                unsigned int reg, unsigned int val)
  3. {
  4.          dev_dbg(codec->dev, "write %x = %x\n", reg, val);
  5.          trace_snd_soc_reg_write(codec, reg, val);
  6.          return codec->write(codec, reg, val);
  7. }
  8. EXPORT_SYMBOL_GPL(snd_soc_write);
复制代码
       它通过调用codec->write函数实现。同样,在snd_soc_codec_set_cache_io函数中,将io_types.write赋给了codec->write,同时有如下语句:
  1. codec->hw_write = (hw_write_t)i2c_master_send;
复制代码
       找到io_types.write函数,我们可以很轻松的分析出,codec->write函数对应snd_soc_7_9_write,其函数原型如下:
  1. static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
  2.                                  unsigned int value)
  3. {
  4.          u8 data[2];

  5.          data[0] = (reg << 1) | ((value >> 8) & 0x0001);
  6.          data[1] = value & 0x00ff;

  7.          return do_hw_write(codec, reg, value, data, 2);
  8. }
复制代码
       do_hw_write函数原型如下:
  1. static int do_hw_write(struct snd_soc_codec *codec, unsigned int reg,
  2.                           unsigned int value, const void *data, int len)
  3. {
  4.          int ret;

  5.          if (!snd_soc_codec_volatile_register(codec, reg) &&
  6.              reg < codec->driver->reg_cache_size &&
  7.              !codec->cache_bypass) {
  8.                    ret = snd_soc_cache_write(codec, reg, value);//将要写的值填入cache表
  9.                    if (ret < 0)
  10.                             return -1;
  11.          }

  12.          if (codec->cache_only) {
  13.                    codec->cache_sync = 1;
  14.                    return 0;
  15.          }

  16.          ret = codec->hw_write(codec->control_data, data, len);
  17.          if (ret == len)
  18.                    return 0;
  19.          if (ret < 0)
  20.                    return ret;
  21.          else
  22.                    return -EIO;
  23. }
复制代码
       程序首先调用snd_soc_cache_write函数,将要写入的值填入寄存器的cache表,确保下次调用读函数时其寄存器的正确性,再调用codec->hw_write函数,写入寄存器。前面已经提到,codec->hw_write对应i2c_master_send,它就是标准的i2c写函数了,是真实的硬件I2C写操作。
       我们再来分析snd_soc_cache_write函数是如何填入寄存器的cache表的。它的函数原型如下:
  1. int snd_soc_cache_write(struct snd_soc_codec *codec,
  2.                             unsigned int reg, unsigned int value)
  3. {
  4.          int ret;

  5.          mutex_lock(&codec->cache_rw_mutex);

  6.          if (codec->cache_ops && codec->cache_ops->write) {
  7.                    ret = codec->cache_ops->write(codec, reg, value);
  8.                    mutex_unlock(&codec->cache_rw_mutex);
  9.                    return ret;
  10.          }

  11.          mutex_unlock(&codec->cache_rw_mutex);
  12.          return -ENOSYS;
  13. }
  14. EXPORT_SYMBOL_GPL(snd_soc_cache_write);
复制代码
       程序中写函数的关键在codec->cache_ops->write函数,在snd_soc_cache_init函数中,有如下语句:
  1. codec->cache_ops = &cache_types[i];
复制代码
       codec->cache_ops->write对应cache_types->write,即snd_soc_flat_cache_write函数。其函数原型如下:
  1. static int snd_soc_flat_cache_write(struct snd_soc_codec *codec,
  2.                                          unsigned int reg, unsigned int value)
  3. {
  4.          snd_soc_set_cache_val(codec->reg_cache, reg, value,
  5.                                   codec->driver->reg_word_size);
  6.          return 0;
  7. }
复制代码
snd_soc_set_cache_val函数原型如下:

  1. static bool snd_soc_set_cache_val(void *base, unsigned int idx,
  2.                                        unsigned int val, unsigned int word_size)
  3. {
  4.          switch (word_size) {
  5.          case 1: {
  6.                    u8 *cache = base;
  7.                    if (cache[idx] == val)
  8.                             return true;
  9.                    cache[idx] = val;
  10.                    break;
  11.          }
  12.          case 2: {
  13.                    u16 *cache = base;
  14.                    if (cache[idx] == val)
  15.                             return true;
  16.                    cache[idx] = val;
  17.                    break;
  18.          }
  19.          default:
  20.                    BUG();
  21.          }
  22.          return false;
  23. }
复制代码
       可见,snd_soc_set_cache_val函数的本质就是将val值填充到cache[idx]对应的地址,即wm8960.c中最前面定义的数组wm8960_reg中。
       整个音频驱动的I2C工作机制到此分析完毕,驱动架构写得过于复杂,其目的在于兼容各种各样的芯片,方便音频驱动的移植。
回复

使用道具 举报

沙发
发表于 2014-12-12 11:23:17 | 只看该作者
非常感谢您的分享~~~~~~~~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳市九鼎创展科技官方论坛 ( 粤ICP备11028681号-2  

GMT+8, 2024-9-28 15:19 , Processed in 0.020244 second(s), 16 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表