Linux驱动开发进阶(十)- I2C子系统BSP驱动
文章目录
- 1、前言
- 2、I2C总线注册
- 3、I2C设备注册
- 4、I2C驱动注册
- 总结
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_COMPATi2c_adapter_compat_class = class_compat_register("i2c-adapter");if (!i2c_adapter_compat_class) {retval = -ENOMEM;goto bus_err;}
#endifretval = 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_COMPATclass_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endifis_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_COMPATres = 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 1int 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驱动框架也是为了提高解决问题的效率。但想要继续加深理解,还是得提高驱动代码的阅读量和编写量。