1、前言
- 学习参考书籍以及本文涉及的示例程序:李山文的《Linux驱动开发进阶》
- 本文属于个人学习后的总结,不太具备教学功能。
2、I2C总线注册
和其它总线驱动一样,I2C驱动的注册也依赖I2C总线的注册。下图i2c_init是i2c总线驱动的入口:
static int __init i2c_init(void)
{
int retval;
retval = of_alias_get_highest_id("i2c");
down_write(&__i2c_board_lock);
if (retval >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = retval + 1;
up_write(&__i2c_board_lock);
retval = bus_register(&i2c_bus_type);
if (retval)
return retval;
is_registered = true;
#ifdef CONFIG_I2C_COMPAT
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
if (!i2c_adapter_compat_class) {
retval = -ENOMEM;
goto bus_err;
}
#endif
retval = i2c_add_driver(&dummy_driver);
if (retval)
goto class_err;
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
if (IS_ENABLED(CONFIG_ACPI))
WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));
return 0;
class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
is_registered = false;
bus_unregister(&i2c_bus_type);
return retval;
}
of_alias_get_highest_id函数用于找到设备树中的aliases表,例如:
aliases {
gpio0 = &gpio0;
gpio1 = &gpio1;
gpio2 = &gpio2;
gpio3 = &gpio3;
gpio4 = &gpio4;
i2c0 = &i2c0;
i2c1 = &i2c1;
i2c2 = &i2c2;
i2c3 = &i2c3;
i2c4 = &i2c4;
i2c5 = &i2c5;
};
aliases表指定了相关外设的别名,例如&i2c5的别名为i2c5,通过of_alias_get_highest_id函数,查找i2c最高的alias,即i2c5这个ID,很显然这个ID就是1。
接着就是bus_register(&i2c_bus_type),该函数完成i2c总线的注册,将总线注册到内核中,此时设备和驱动就可以引用i2c_bus_type总线了。接着class_compat_register(“i2c-adapter”)是注册设备类。
接下来再看i2c_bus_type结构体,但这里不写介绍了,主要就是match匹配函数:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);
3、I2C设备注册
一样的,i2c设备分为i2c控制器设备和挂载在i2c控制器下的i2c子设备。i2c控制器设备通过i2c_add_adapter()函数来注册:
int i2c_add_adapter(struct i2c_adapter *adapter)
{
struct device *dev = &adapter->dev;
int id;
if (dev->of_node) {
id = of_alias_get_id(dev->of_node, "i2c");
if (id >= 0) {
adapter->nr = id;
return __i2c_add_numbered_adapter(adapter);
}
}
mutex_lock(&core_lock);
id = idr_alloc(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
mutex_unlock(&core_lock);
if (WARN(id < 0, "couldn't get idr"))
return id;
adapter->nr = id;
return i2c_register_adapter(adapter);
}
EXPORT_SYMBOL(i2c_add_adapter);
重点是__i2c_add_numbered_adapter()函数的实现,这里只截取了一部分:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
if (res) {
pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
goto out_list;
}
res = of_i2c_setup_smbus_alert(adap);
if (res)
goto out_reg;
pm_runtime_no_callbacks(&adap->dev);
pm_suspend_ignore_children(&adap->dev, true);
pm_runtime_enable(&adap->dev);
res = i2c_init_recovery(adap);
if (res == -EPROBE_DEFER)
goto out_reg;
dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
#ifdef CONFIG_I2C_COMPAT
res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
if (res)
dev_warn(&adap->dev,
"Failed to create compatibility class link\n");
#endif
/* create pre-declared device nodes */
of_i2c_register_devices(adap);
i2c_acpi_install_space_handler(adap);
i2c_acpi_register_devices(adap);
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
/* Notify drivers */
mutex_lock(&core_lock);
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
mutex_unlock(&core_lock);
...
}
dev_set_name()用于设置i2c控制器设备的名称,然后初始化i2c控制器设备的bus和type。然后调用device_register()函数向内核注册i2c控制器设备。接着调用i2c_setup_smbus_alert()函数用于初始化SMBus总线接口。然后调用of_i2c_register_devices()函数用于将设备树中的I2C控制器设备下的所有子设备节点全部注册到内核。
i2c_add_adapter函数一般在i2c控制器驱动程序中调用,也就是说,一般在注册控制器驱动时才去注册I2C控制器设备和I2c从设备。
4、I2C驱动注册
i2c控制器抽象出了一个结构体,为:
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
unsigned long locked_flags; /* owned by the I2C core */
#define I2C_ALF_IS_SUSPENDED 0
#define I2C_ALF_SUSPEND_REPORTED 1
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
struct irq_domain *host_notify_domain;
};
重点关注algo函数,algo用于I2C底层的数据传输,必须要初始化。向内核注册控制器驱动很简单,就是调用i2c_add_adapter()。上面也介绍了这个函数,该函数会将I2C控制器设备和I2C从设备全部注册到内核中,并且注册一个i2c_adapter。
下面展示如何注册一个i2c控制器:
重点看一下foo_i2cadapter_algo,这是一个i2c_algorithm结构体:
struct i2c_algorithm {
/*
* If an adapter algorithm can't do I2C-level access, set master_xfer
* to NULL. If an adapter algorithm can do SMBus access, set
* smbus_xfer. If set to NULL, the SMBus protocol is simulated
* using common I2C messages.
*
* master_xfer should return the number of messages successfully
* processed, or a negative value on error
*/
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*master_xfer_atomic)(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num);
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality)(struct i2c_adapter *adap);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
static int dummy_i2cadapter_xfer (struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
int i = 0, index;
int wr_flag, stop;
for (index = 0; index < num; index++) {
//发生错误,无回应,I2C_M_TEN表示支持10bit地址,这里不支持10bit地址
if( (msgs[index].flags & (I2C_M_NO_RD_ACK | I2C_M_IGNORE_NAK | I2C_M_TEN)) || msgs[i].len == 0) {
return ENOTSUPP;
}
//发送开始信号,从设备地址
if (!(msgs[index].flags & I2C_M_NOSTART)) {
wr_flag = (msgs[index].flags & I2C_M_RD ? 0x01 : 0x00);
printk(KERN_INFO "TX: START %x\n", (msgs[index].addr<<1) | wr_flag );
}
//读取数据
if(msgs[index].flags & I2C_M_RD) {
for(i = 0; i < msgs[index].len; i++) {
stop = (index+1 == num && i+1 == msgs[index].len) ? 0x01 : 0x00;
msgs[index].buf[i] = 'A';
if(stop)
printk(KERN_INFO "TX: STOP\n");
}
}
//发送数据
else {
for(i = 0; i < msgs[index].len; i++) {
stop = (index+1 == num && i+1 == msgs[index].len) ? 0x01 : 0x00;
printk(KERN_INFO "TX: %c \n", msgs[index].buf[i]);
if(stop)
printk(KERN_INFO "TX: STOP\n");
}
}
}
return 0;
}
static u32 dummy_i2cadapter_functionality (struct i2c_adapter * adap)
{
return I2C_FUNC_I2C;
}
static struct i2c_algorithm dummy_i2cadapter_algo =
{
.master_xfer = dummy_i2cadapter_xfer,
.functionality = dummy_i2cadapter_functionality
};
在编写i2c设备驱动程序时,调用的i2c_transfer,实际调用的就是bsp驱动中的i2c_algorithm结构体中的master_xfer指向的函数,这也是Linux中使用c语言实现多态的一种表现。
完整的i2c bsp驱动程序可以参考:李山文的《Linux驱动开发进阶》i2c bsp驱动示例
总结
关于《Linux驱动开放进阶》的学习,暂时告一段落。通过该书籍的学习,加强了对Linux驱动的理解。但Linux内核驱动不仅仅只有这些,目前熟悉Linux驱动框架也是为了提高解决问题的效率。但想要继续加深理解,还是得提高驱动代码的阅读量和编写量。