ASoC驱动开发 之 Codec芯片ALC5677 驱动代码分析

来源:互联网 发布:淘宝搜索你会感谢我的 编辑:程序博客网 时间:2024/05/22 17:12

【补充】

      关于 ASoC Codec驱动代码框架更详细的介绍可以阅读《ASoC Codec 驱动代码框架图》。(2016年9月7日 添加)


【前言】

        Linux下的音频驱动多采用 ASoC 架构。在这个架构里,驱动分 Platform、Codec、Machine 这 3 部分,相关介绍可以参见前文《Linux AsoC音频驱动架构 及 Machine驱动代码分析》。本文分析的是 Codec 部分,即音频编解码芯片的驱动代码。这里用作例子的芯片型号为 ALC5677。

       如前文《Linux I2C总线框架 学习笔记》所述,编写 I2C 驱动时会添加 2 个模块——Bus Driver 和 Device Driver。分别添加 client 结构体相关信息和实现 driver 函数如 (*probe)、(*remove) 等。

 

【client 设备注册流程】

        先来看看 client 部分的内容。在源文件 rt5677.c 末尾可以看到如下一行代码:

        device_initcall(rt5677_i2c_dev_init);

        这里的代码入口不是我们常见的 module_init(fn) 方式,他们区别在哪里呢?使用 SourceInsight 可以很方便地跳转到 device_initcall(fn) 定义处,可以看到下面这段预处理代码:

#ifndef MODULE    /* 如果没有定义 MODULE */#ifndef __ASSEMBLY__…#define fs_initcall(fn)                          __define_initcall(fn,5)#define fs_initcall_sync(fn)               __define_initcall(fn, 5s)#define rootfs_initcall(fn)                  __define_initcall(fn, rootfs)#define device_initcall(fn)                 __define_initcall(fn,6)#define device_initcall_sync(fn)       __define_initcall(fn, 6s)    /* 注意 */#define late_initcall(fn)             __define_initcall(fn, 7)#define late_initcall_sync(fn)           __define_initcall(fn, 7s)…#endif /* __ASSEMBLY__ */#define module_init(x)     __initcall(x);#define module_exit(x)    __exitcall(x);#else /* MODULE*/    /* 如果定义了 MODULE */…#define subsys_initcall(fn)                 module_init(fn)#define fs_initcall(fn)                          module_init(fn)#define rootfs_initcall(fn)                  module_init(fn)#define device_initcall(fn)                 module_init(fn)    /* 注意 */#define late_initcall(fn)             module_init(fn)#define console_initcall(fn)               module_init(fn)#define security_initcall(fn)              module_init(fn)#define module_init(initfn)                                            \         staticinline initcall_t __inittest(void)               \         {return initfn; }                                              \         intinit_module(void) __attribute__((alias(#initfn)));#define module_exit(exitfn)                                          \         staticinline exitcall_t __exittest(void)             \         {return exitfn; }                                             \         voidcleanup_module(void) __attribute__((alias(#exitfn)));…#endif

        可以看到,在定义了 MODULE 的情况下,device_inticall(fn) 就是 module_init(fn),而在另一种情况下是 __define_initcall(fn, 6)。对于后者,其中的参数值 6 表示 id,取值范围从 0~7s 共 17 种。这个 id 值越大,模块被内核加载的时间越晚。有的时候,一个模块 a 的正常工作需要依赖于模块 b,这种情况下就可以使用 __define_initcall(fn, id) 来规定模块的加载顺序。

        继续分析,进入 rt5677_i2c_dev_init() 函数查看 rt5677 驱动模块初始化流程。代码如下:

static int __init rt5677_i2c_dev_init(void){         int i2c_busnum = 0;         struct i2c_board_info i2c_info;         struct i2c_adapter *adapter;         ...         memset(&i2c_info,0, sizeof(i2c_info));         strncpy(i2c_info.type,"rt5677", strlen("rt5677"));          i2c_info.addr = 0x2c;    /* 101100X. 读周期 X 为 1, 写周期 X 为 0 */         ...         adapter = i2c_get_adapter(i2c_busnum);         if(adapter) {                   if(i2c_new_device(adapter, &i2c_info)){                            printk("addnew i2c device %s , addr 0x%x\n", "rt5677", i2c_info.addr);                            return0;                   }else{                            printk("addnew i2c device %s , addr 0x%x fail !!!\n", "rt5677",i2c_info.addr);                   }         }else{                   printk("[%s]getadapter %d fail\n",__func__, i2c_busnum);                   return-EINVAL;         }}

        代码很短,流程很清楚。首先手动将 alc5677 的 i2c_busnum 划分为 0 ,然后给板级信息结构体 i2c_info 初始化 I2C 地址,值为 0x2c。该地址是芯片的 datasheet 里给定的,其中 I2C_ADDR 位对应的引脚电平为低电平,所以值为0,如下图:

        之后,获取用于管理挂接在编号为 0 的 i2c_busnum 下的 i2c 设备的适配器(没看懂?可以参考一下《LinuxI2C总线框架 学习笔记》这篇文章)。如果这里获取适配器 adapter 失败,则直接退出,返回值为 -EINVAL; 如果获取成功,则调用 i2c_new_device(adapter, &i2c_info) 函数为该 adapter 添加一个内容与编解码芯片 ALC5677 一致的 client。相应代码如下:

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_infoconst *info){         struct i2c_client       *client;         int                       status;         client = kzalloc(sizeof *client,GFP_KERNEL);         if (!client)                   return NULL;         client->adapter = adap;    // 将 adapter 和 client 绑定         client->dev.platform_data = info->platform_data;         if (info->archdata)                   client->dev.archdata = *info->archdata;         client->flags = info->flags;    // 将板级信息结构体中的配置对应写入 client         client->addr = info->addr;         client->irq = info->irq;         client->irq_flags = info->irq_flags;         ...         strlcpy(client->name,info->type, sizeof(client->name));          /* Check for address validity */         status = i2c_check_client_addr_validity(client);         if (status) {                   dev_err(&adap->dev,"Invalid %d-bit I2C address 0x%02hx\n",                            client->flags& I2C_CLIENT_TEN ? 10 : 7, client->addr);                   goto out_err_silent;         }         /* Check for address business */         status = i2c_check_addr_busy(adap,client->addr);         if (status)                   goto out_err;          client->dev.parent = &client->adapter->dev;         client->dev.bus = &i2c_bus_type;         client->dev.type = &i2c_client_type;         client->dev.of_node = info->of_node;         ...         status = device_register(&client->dev);    /* 注册 client 设备 */         if (status)                   goto out_err;          dev_dbg(&adap->dev, "client[%s] registered with bus id %s\n",                   client->name, dev_name(&client->dev));          return client; out_err:         dev_err(&adap->dev, "Failedto register i2c client %s at 0x%02x "                   "(%d)\n",client->name, client->addr, status);out_err_silent:         kfree(client);         return NULL;}

        可以看到在这个函数里,1 个新的 client 被创建并被关联到 adapter 上,相应的 i2c 地址、中断号等信息都被赋为 info 结构体里的值。在所需 client 结构体的成员都被初始化之后,调用 device_register(&client->dev) 函数对 client 设备进行注册。至此,设备注册完成,即 client 部分代码结束。

 

【driver 驱动注册流程】

        再来看看 driver 函数相关的内容。在源文件 rt5677.c 中,我们还能看到下面这样一句代码:

        module_i2c_driver(rt5677_i2c_driver);

        这是一个宏定义,对其进行追踪可以看到如下声明:

(〇)driver 结构体定义:

struct i2c_driver rt5677_i2c_driver = {         .driver= {                   .name = "rt5677",                   .owner = THIS_MODULE,#ifdef CONFIG_PM                   .pm = &rt5677_pm_ops,#endif#ifdef RTACPI_I2C                   .acpi_match_table = ACPI_PTR(rt5677_acpi_match),#endif         },         .probe = rt5677_i2c_probe,        /* 关联 probe 函数 */         .remove = rt5677_i2c_remove,     /* 关联 remove 函数 */         .shutdown = rt5677_i2c_shutdown,  /* 关联 shutdown 函数 */         .id_table = rt5677_i2c_id,        /* 所支持设备的 ID 表 */};

(一)module_i2c_driver() 宏:

/** *module_i2c_driver() - Helper macro for registering a I2C driver *@__i2c_driver: i2c_driver struct * *Helper macro for I2C drivers which do not do anything special in module *init/exit. This eliminates a lot of boilerplate. Each module may only *use this macro once, and calling it replaces module_init() and module_exit() */#define module_i2c_driver(__i2c_driver)\         module_driver(__i2c_driver,i2c_add_driver, \                            i2c_del_driver)

(二)module_driver() 宏:

#define module_driver(__driver, __register, __unregister, ...) \static int __init__driver##_init(void) \{ \         return__register(&(__driver) ,##__VA_ARGS__); \} \module_init(__driver##_init); \    /* 模块入口声明 */static void __exit __driver##_exit(void) \{ \         __unregister(&(__driver), ##__VA_ARGS__); \} \module_exit(__driver##_exit);

(三)i2c_add_driver() 函数:

/* use a define to avoid include chaining to get THIS_MODULE */#define i2c_add_driver(driver)\         i2c_register_driver(THIS_MODULE,driver)
/* * Ani2c_driver is used with one or more i2c_client (device) nodes to access *i2c slave chips, on a bus instance associated with some i2c_adapter. */int i2c_register_driver(structmodule *owner, struct i2c_driver *driver){         int res;         /* Can't register until after driver model init */         if(unlikely(WARN_ON(!i2c_bus_type.p)))                   return-EAGAIN;          /*add the driver to the list of i2c drivers in the driver core */         driver->driver.owner = owner;         driver->driver.bus = &i2c_bus_type;          /*When registration returns, the driver core          * will have called probe() for allmatching-but-unbound devices.          */         res = driver_register(&driver->driver);    /* 驱动注册。注册成功返回 0 */         if(res)                   return res;         ...         INIT_LIST_HEAD(&driver->clients);         /* Walk the adapters that are already present */         i2c_for_each_dev(driver,__process_new_driver);          return 0;}EXPORT_SYMBOL(i2c_register_driver);

        通过层层传递,我们找到了 driver 函数注册相关代码的起始点—— i2c_register_driver() 函数。在这个函数里,我们传入了 driver 结构体并调用函数 driver_register() 对驱动进行了注册。至此,表层的驱动注册完成,即 driver 函数相关代码结束。

        实际上,源文件 rt5677.c 中还有类似 rt5677_probe()、rt5677_remove() 这类更底层一些的驱动函数,我们在这篇文章里暂时先不讨论,随着代码阅读和学习的深入,以后我再找时间单独写一篇。

 

【扩展阅读】

        [1] 《postcore_initcall(), arch_initcall(), subsys_initcall(),device_initcall() 调用顺序》

        [2] 《linux内核__define_initcall分析》

 

0 0
原创粉丝点击