九鼎创展论坛
标题: x4412&ibox项目实战55-Linux音频驱动之I2C驱动实验 [打印本页]
作者: armeasy 时间: 2014-11-27 17:27
标题: x4412&ibox项目实战55-Linux音频驱动之I2C驱动实验
音频驱动关于I2C的源码路径:
- kernel/sound/soc/codecs/wm8960.c
- kernel/arch/arm/mach-exynos/mach-x4412.c
- kernel/sound/soc/soc-core.c
- kernel/sound/soc/soc-cache.c
复制代码 在mach-x4412.c中,通过I2C_BOARD_INFO宏指定了音频芯片wm8960的名称和i2c地址,同时,在i2c_devs0数组中,还指定了音频驱动的平台数据:
- static struct wm8960_data wm8960_pdata = {
- .capless = 0,
- .dres = WM8960_DRES_400R,
- };
-
- static struct i2c_board_info i2c_devs0[] __initdata = {
- {
- I2C_BOARD_INFO("kxud9", 0x18),
- .platform_data = &kxud9_data,
- }, {
- I2C_BOARD_INFO("wm8960", 0x1a),
- .platform_data = &wm8960_pdata,
- }
- };
复制代码 在smdk4x12_machine_init函数中,通过i2c_register_board_info函数注册相应的i2c设备:
- i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
复制代码 这时,一个名为wm8960,设备地址为0x1a的设备已经被添加到i2c总线上。
在wm8960.c的wm8960_modinit中,通过i2c_add_driver函数添加了一个名为wm8960-codec的i2c设备驱动:
- static const struct i2c_device_id wm8960_i2c_id[] = {
- { "wm8960", 0 },
- { }
- };
- MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);
-
- static struct i2c_driver wm8960_i2c_driver = {
- .driver = {
- .name = "wm8960-codec",
- .owner = THIS_MODULE,
- },
- .probe = wm8960_i2c_probe,
- .remove = __devexit_p(wm8960_i2c_remove),
- .id_table = wm8960_i2c_id,
- };
复制代码 在wm8960_i2c_id中,可以找到和I2C_BOARD_INFO宏注册的相同的i2c设备驱动名称wm8960,探测函数wm8960_i2c_probe被触发。
- static __devinit int wm8960_i2c_probe(struct i2c_client *i2c,
- const struct i2c_device_id *id)
- {
- struct wm8960_priv *wm8960;
- int ret;
-
- //在这里可以打印i2c->addr和i2c->type,表明从设备i2c_client已经传递进来
- wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL);
- if (wm8960 == NULL)
- return -ENOMEM;
-
- i2c_set_clientdata(i2c, wm8960);//保存i2c相关数据
- wm8960->control_type = SND_SOC_I2C;
- wm8960->control_data = i2c;
-
- ret = snd_soc_register_codec(&i2c->dev,
- &soc_codec_dev_wm8960, &wm8960_dai, 1);
- if (ret < 0)
- kfree(wm8960);
- return ret;
- }
复制代码 在探测函数中,通过名为i2c的形参结构体传入i2c设备的地址和名称,可以在函数入口添加如下打印语句:
- printk("%s:i2c->addr = 0x%x,i2c->name = %s\n",__FUNCTION__,i2c->addr,i2c->name);
复制代码 编译内核,更新映像后,可以看到如下打印信息:
- wm8960_i2c_probe:i2c->addr = 0x1a,i2c->name = wm8960
复制代码 后续整个i2c的读写,都离不开这个结构体。在wm8960.c中,通过snd_soc_read函数读取寄存器值,通过snd_soc_write函数写寄存器值。snd_soc_read函数原型在soc-core.c中:
- unsigned int snd_soc_read(struct snd_soc_codec *codec, unsigned int reg)
- {
- unsigned int ret;
-
- ret = codec->read(codec, reg);
- dev_dbg(codec->dev, "read %x => %x\n", reg, ret);
- trace_snd_soc_reg_read(codec, reg, ret);
-
- return ret;
- }
- EXPORT_SYMBOL_GPL(snd_soc_read);
复制代码 它通过codec->read函数,即形参结构体codec中的成员read实现I2C的读。在wm8960.c的probe函数wm8960_probe中,snd_soc_codec_set_cache_io函数实现了对结构体codec的成员赋值,snd_soc_codec_set_cache_io的函数原型在soc-cache.c中:
- int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
- int addr_bits, int data_bits,
- enum snd_soc_control_type control)
- {
- int i;
-
- for (i = 0; i < ARRAY_SIZE(io_types); i++)
- if (io_types[i].addr_bits == addr_bits &&
- io_types[i].data_bits == data_bits)
- break;
- if (i == ARRAY_SIZE(io_types)) {
- printk(KERN_ERR
- "No I/O functions for %d bit address %d bit data\n",
- addr_bits, data_bits);
- return -EINVAL;
- }
-
- codec->write = io_types[i].write;
- codec->read = io_types[i].read;
- codec->bulk_write_raw = snd_soc_hw_bulk_write_raw;
-
- switch (control) {
- case SND_SOC_I2C:
- #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
- codec->hw_write = (hw_write_t)i2c_master_send;//填充snd_soc_codec结构体,指定i2c写函数
- #endif
- if (io_types[i].i2c_read)
- codec->hw_read = io_types[i].i2c_read;
-
- codec->control_data = container_of(codec->dev,
- struct i2c_client,
- dev);
- break;
-
- case SND_SOC_SPI:
- #ifdef CONFIG_SPI_MASTER
- codec->hw_write = do_spi_write;
- #endif
-
- codec->control_data = container_of(codec->dev,
- struct spi_device,
- dev);
- break;
- }
-
- return 0;
- }
- EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
复制代码 io_types数组定义如下:
- static struct {
- int addr_bits;
- int data_bits;
- int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int);
- unsigned int (*read)(struct snd_soc_codec *, unsigned int);
- unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int);
- } io_types[] = {
- {
- .addr_bits = 4, .data_bits = 12,
- .write = snd_soc_4_12_write, .read = snd_soc_4_12_read,
- },
- {
- .addr_bits = 7, .data_bits = 9,
- .write = snd_soc_7_9_write, .read = snd_soc_7_9_read,
- },
- {
- .addr_bits = 8, .data_bits = 8,
- .write = snd_soc_8_8_write, .read = snd_soc_8_8_read,
- .i2c_read = snd_soc_8_8_read_i2c,
- },
- {
- .addr_bits = 8, .data_bits = 16,
- .write = snd_soc_8_16_write, .read = snd_soc_8_16_read,
- .i2c_read = snd_soc_8_16_read_i2c,
- },
- {
- .addr_bits = 16, .data_bits = 8,
- .write = snd_soc_16_8_write, .read = snd_soc_16_8_read,
- .i2c_read = snd_soc_16_8_read_i2c,
- },
- {
- .addr_bits = 16, .data_bits = 16,
- .write = snd_soc_16_16_write, .read = snd_soc_16_16_read,
- .i2c_read = snd_soc_16_16_read_i2c,
- },
- };
复制代码 在引用snd_soc_codec_set_cache_io函数时,传入的形参已经指定addr_bits为7,data_bits为9,故读函数对应snd_soc_7_9_read,写函数对应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);
- }
复制代码 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 ||
- snd_soc_codec_volatile_register(codec, reg) ||
- codec->cache_bypass) {
- if (codec->cache_only)
- return -1;
-
- BUG_ON(!codec->hw_read);
- return codec->hw_read(codec, reg);
- }
-
- ret = snd_soc_cache_read(codec, reg, &val);
- if (ret < 0)
- return -1;
- return val;
- }
复制代码 从这里很容易分析出,do_hw_read函数最终会调用snd_soc_cache_read函数。它的函数原型如下:
- 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) {
- ret = codec->cache_ops->read(codec, reg, value);
- mutex_unlock(&codec->cache_rw_mutex);
- return ret;
- }
-
- mutex_unlock(&codec->cache_rw_mutex);
- return -ENOSYS;
- }
- EXPORT_SYMBOL_GPL(snd_soc_cache_read);
复制代码 可见,真正的i2c读函数会调用codec->cache_ops->read()函数。在soc-core.c的probe函数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中:
- int snd_soc_cache_init(struct snd_soc_codec *codec)
- {
- int i;
-
- for (i = 0; i < ARRAY_SIZE(cache_types); ++i)
- if (cache_types[i].id == codec->compress_type)
- break;
-
- /* Fall back to flat compression */
- if (i == ARRAY_SIZE(cache_types)) {
- dev_warn(codec->dev, "Could not match compress type: %d\n",
- codec->compress_type);
- i = 0;
- }
-
- mutex_init(&codec->cache_rw_mutex);
- codec->cache_ops = &cache_types[i];//赋值cache_ops
-
- if (codec->cache_ops->init) {
- if (codec->cache_ops->name)
- dev_dbg(codec->dev, "Initializing %s cache for %s codec\n",
- codec->cache_ops->name, codec->name);
- return codec->cache_ops->init(codec);
- }
- return -ENOSYS;
- }
复制代码 可以看到,加粗部分已经给codec->cache_ops赋值了,再回到前面,真正执行i2c读的函数会调用codec->cache_ops->read()函数,也就是cache_types中的读函数。该数组的程序清单如下:
- static const struct snd_soc_cache_ops cache_types[] = {
- /* Flat *must* be the first entry for fallback */
- {
- .id = SND_SOC_FLAT_COMPRESSION,
- .name = "flat",
- .init = snd_soc_flat_cache_init,
- .exit = snd_soc_flat_cache_exit,
- .read = snd_soc_flat_cache_read,
- .write = snd_soc_flat_cache_write,
- .sync = snd_soc_flat_cache_sync
- },
- #ifdef CONFIG_SND_SOC_CACHE_LZO
- {
- .id = SND_SOC_LZO_COMPRESSION,
- .name = "LZO",
- .init = snd_soc_lzo_cache_init,
- .exit = snd_soc_lzo_cache_exit,
- .read = snd_soc_lzo_cache_read,
- .write = snd_soc_lzo_cache_write,
- .sync = snd_soc_lzo_cache_sync
- },
- #endif
- {
- .id = SND_SOC_RBTREE_COMPRESSION,
- .name = "rbtree",
- .init = snd_soc_rbtree_cache_init,
- .exit = snd_soc_rbtree_cache_exit,
- .read = snd_soc_rbtree_cache_read,
- .write = snd_soc_rbtree_cache_write,
- .sync = snd_soc_rbtree_cache_sync
- }
- };
复制代码 结合程序上下文,可以分析出codec->compress_type为SND_SOC_FLAT_COMPRESSION,即对应的读函数为snd_soc_flat_cache_read。其函数原型如下:
- static int snd_soc_flat_cache_read(struct snd_soc_codec *codec,
- unsigned int reg, unsigned int *value)
- {
- *value = snd_soc_get_cache_val(codec->reg_cache, reg,
- codec->driver->reg_word_size);
- return 0;
- }
复制代码 snd_soc_get_cache_val的函数原型如下:
- static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx,
- unsigned int word_size)
- {
- if (!base)
- return -1;
-
- switch (word_size) {
- case 1: {
- const u8 *cache = base;
- return cache[idx];
- }
- case 2: {
- const u16 *cache = base;
- return cache[idx];
- }
- default:
- BUG();
- }
- /* unreachable */
- return -1;
- }
复制代码 再来分析snd_soc_get_cache_val函数的几个重要的形参。snd_soc_flat_cache_init函数用于将codec->reg_def_copy赋给codec->reg_cache:
- static int snd_soc_flat_cache_init(struct snd_soc_codec *codec)
- {
- const struct snd_soc_codec_driver *codec_drv;
-
- codec_drv = codec->driver;
-
- if (codec->reg_def_copy)
- codec->reg_cache = kmemdup(codec->reg_def_copy,
- codec->reg_size, GFP_KERNEL);
- else
- codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL);
- if (!codec->reg_cache)
- return -ENOMEM;
-
- return 0;
- }
复制代码 同时,在snd_soc_register_codec函数中,如下程序用于将codec_drv->reg_cache_default给codec->reg_def_copy赋值:
- if (codec_drv->reg_cache_default) {
- codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,
- reg_size, GFP_KERNEL);
- if (!codec->reg_def_copy) {
- ret = -ENOMEM;
- goto fail;
- }
- }
复制代码 结合wm8960_i2c_probe中的如下语句:
- ret = snd_soc_register_codec(&i2c->dev,
- &soc_codec_dev_wm8960, &wm8960_dai, 1);
复制代码 可以分析出,codec_drv->reg_cache_default已被赋值为wm8960_reg,而wm8960_reg就是wm8960.c最前面的寄存器表:
- static const u16 wm8960_reg[WM8960_CACHEREGNUM] = {
- 0x0097, 0x0097, 0x0000, 0x0000,
- 0x0000, 0x0008, 0x0000, 0x000a,
- 0x01c0, 0x0000, 0x00ff, 0x00ff,
- 0x0000, 0x0000, 0x0000, 0x0000,
- 0x0000, 0x007b, 0x0100, 0x0032,
- 0x0000, 0x00c3, 0x00c3, 0x01c0,
- 0x0000, 0x0000, 0x0000, 0x0000,
- 0x0000, 0x0000, 0x0000, 0x0000,
- 0x0100, 0x0100, 0x0050, 0x0050,
- 0x0050, 0x0050, 0x0000, 0x0000,
- 0x0000, 0x0000, 0x0040, 0x0000,
- 0x0000, 0x0050, 0x0050, 0x0000,
- 0x0002, 0x0037, 0x004d, 0x0080,
- 0x0008, 0x0031, 0x0026, 0x00e9,
- };
复制代码 snd_soc_register_codec函数的第二个形参reg对应着该数组的第reg个数,第三个形参value返回该数组的第reg个数的值。当我们需要读取WM8960的寄存器值时,只需要调用snd_soc_read函数,传入形参codec和寄存器在该表中存在的位号即可。
可见,WM8960的I2C读,并不是直接通过I2C协议从硬件上读取寄存器的值,而是驱动中预先将寄存器的初始值填入一个名为wm8960_reg的寄存器表,当读寄存器值时,直接从该表格里面读取数值,即对应了相应寄存器的值。下面我们再来分析I2C的写。
驱动通过snd_soc_write函数实现I2C的写,该函数原型在soc-core.c中:
- unsigned int snd_soc_write(struct snd_soc_codec *codec,
- unsigned int reg, unsigned int val)
- {
- dev_dbg(codec->dev, "write %x = %x\n", reg, val);
- trace_snd_soc_reg_write(codec, reg, val);
- return codec->write(codec, reg, val);
- }
- EXPORT_SYMBOL_GPL(snd_soc_write);
复制代码 它通过调用codec->write函数实现。同样,在snd_soc_codec_set_cache_io函数中,将io_types.write赋给了codec->write,同时有如下语句:
- codec->hw_write = (hw_write_t)i2c_master_send;
复制代码 找到io_types.write函数,我们可以很轻松的分析出,codec->write函数对应snd_soc_7_9_write,其函数原型如下:
- 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);
- }
复制代码 do_hw_write函数原型如下:
- 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) &&
- reg < codec->driver->reg_cache_size &&
- !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);
- if (ret == len)
- return 0;
- if (ret < 0)
- return ret;
- else
- return -EIO;
- }
复制代码 程序首先调用snd_soc_cache_write函数,将要写入的值填入寄存器的cache表,确保下次调用读函数时其寄存器的正确性,再调用codec->hw_write函数,写入寄存器。前面已经提到,codec->hw_write对应i2c_master_send,它就是标准的i2c写函数了,是真实的硬件I2C写操作。
我们再来分析snd_soc_cache_write函数是如何填入寄存器的cache表的。它的函数原型如下:
- 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) {
- ret = codec->cache_ops->write(codec, reg, value);
- mutex_unlock(&codec->cache_rw_mutex);
- return ret;
- }
-
- mutex_unlock(&codec->cache_rw_mutex);
- return -ENOSYS;
- }
- EXPORT_SYMBOL_GPL(snd_soc_cache_write);
复制代码 程序中写函数的关键在codec->cache_ops->write函数,在snd_soc_cache_init函数中,有如下语句:
- codec->cache_ops = &cache_types[i];
复制代码 即codec->cache_ops->write对应cache_types->write,即snd_soc_flat_cache_write函数。其函数原型如下:
- static int snd_soc_flat_cache_write(struct snd_soc_codec *codec,
- unsigned int reg, unsigned int value)
- {
- snd_soc_set_cache_val(codec->reg_cache, reg, value,
- codec->driver->reg_word_size);
- return 0;
- }
复制代码snd_soc_set_cache_val函数原型如下:
- static bool snd_soc_set_cache_val(void *base, unsigned int idx,
- unsigned int val, unsigned int word_size)
- {
- switch (word_size) {
- case 1: {
- u8 *cache = base;
- if (cache[idx] == val)
- return true;
- cache[idx] = val;
- break;
- }
- case 2: {
- u16 *cache = base;
- if (cache[idx] == val)
- return true;
- cache[idx] = val;
- break;
- }
- default:
- BUG();
- }
- return false;
- }
复制代码 可见,snd_soc_set_cache_val函数的本质就是将val值填充到cache[idx]对应的地址,即wm8960.c中最前面定义的数组wm8960_reg中。
整个音频驱动的I2C工作机制到此分析完毕,驱动架构写得过于复杂,其目的在于兼容各种各样的芯片,方便音频驱动的移植。
作者: mzucore 时间: 2014-12-12 11:23
非常感谢您的分享~~~~~~~~
欢迎光临 九鼎创展论坛 (http://bbs.9tripod.com/) |
Powered by Discuz! X3.2 |