linux驱动:i2c设备总线驱动

Posted 超凡东皇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux驱动:i2c设备总线驱动相关的知识,希望对你有一定的参考价值。

前言

linux下设备跟驱动是分开的,他们通过总线进行匹配,设备由设备树负责,在设备树中添加相应的结点,系统会自动向总线注册相应的设备,而驱动开发需要负责的主要就是驱动的编写,向总线注册驱动,如果通过of_device_id中的.compatible跟设备匹配成功进执行其中的probe函数。

i2c结构体

对于i2c设备我们都不陌生,i2c是一种实际存在的物理总线,不像有些字符设备,使用的是虚拟的platform总线,实际上是不存在的,是虚拟出来的总线。在linux下写i2c驱动,掌握4个结构体就够了,其定义都在/linux/include/i2c.h下

/*
 * i2c_adapter is the structure used to identify a physical i2c bus along
 * with the access algorithms necessary to access it.
 */
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	*/
	struct rt_mutex bus_lock;

	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */

	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 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 (*smbus_xfer) (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 *);
};

struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared. You should avoid
	 * using this, it will be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#ifdef CONFIG_MTK_I2C_EXTENSION
	__u32 timing;			/* parameters of timings		*/
	__u32 ext_flag;
#endif
};

①i2c_adapter

对应物理上的一个适配器,这是跟i2c总线相关的结构体

②i2c_algorithm

对应相应的通信方法,与适配器进行绑定

③i2c_driver

描述驱动内容,对应一套驱动方法,i2c_device_id是该驱动所支持的I2C设备的ID表。

④i2c_client

对应真实的物理设备,并且一个设备对应一个i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。

i2c核心代码

i2c总线的核心代码就在i2c-core.c中,包含了i2c总线的注册,没错,总线也是一种结构体(bus_type),它是通过bus_register接口向系统中注册的

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

每一种总线(bus)都包含两条链(device与driver),设备与驱动,当他们注册的时候,match函数中会挨个进行匹配,匹配上了就执行probe函数

i2c总线上也有2条链,i2c_client链和i2c_driver链,当任何一个driver或者client去注册时,i2c总线都会调用match函数去对client.name与driver.id_table.name进行循环匹配

struct bus_type i2c_bus_type = {
	.name		= "i2c",//在/sys/bus/下显示的名字
	.match		= i2c_device_match,//作用:负责driver与device进行匹配(一般用名字进行匹配)
	.probe		= i2c_device_probe,//一个驱动安装或者设备安装被匹配上的时候就执行probe函数
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);

如果driver.id_table.name中所有的id都匹配不上则说明client并没有driver,就结束了;

如果匹配上了,则标明client和driver是适用的,那么i2c总线会调用自身的probe函数,自身的函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作

static int __init i2c_init(void)
{
	int retval;

	retval = bus_register(&i2c_bus_type);//注册i2c总线,总线也是一个结构体
	if (retval)
		return retval;
#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;
	return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
	bus_unregister(&i2c_bus_type);
	return retval;
}

static void __exit i2c_exit(void)
{
	i2c_del_driver(&dummy_driver);
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
#endif
	bus_unregister(&i2c_bus_type);
	tracepoint_synchronize_unregister();
}

注:如果同样的设备有多个,可以用同一份驱动,在设备树中添加多个节点,使用不同的i2c地址,而i2c地址包含在设备结构体中(即i2c_client),在/sys/bus/i2c/devices/下根据不同地址进行访问,那么有人问了,能不能将多个i2c设备的地址弄成相同的,这种情况就像一个班里有好几个重名的同学,重名可以吗?是可以的,比如对他们说下课了,大家都一样收到信息知道下课了,这就是广播,只是某些情况有些麻烦,比如需要找其中某一个,就不好找了,如果是这种情况,i2c设备地址就应该区分,所以具体得看应用的场景。

以上是关于linux驱动:i2c设备总线驱动的主要内容,如果未能解决你的问题,请参考以下文章

:Linux I2C核心总线与设备驱动

linux驱动:i2c设备总线驱动

Smart210学习记录-----Linux i2c驱动

Linux+I2C总线分析(主要是probe的方式)

九i2c设备驱动

九i2c设备驱动