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

68 篇文章 14 订阅
56 篇文章 6 订阅

【补充】

      关于 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分析》

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值