linux内核I2C驱动编程框架简析(一)


I2C驱动跟前面介绍的linux内核驱动的platform机制一样,也是 总线-设备-驱动模型。同样是使用内核的bus_type结构体定义了一个虚拟总线i2c_bus_type。
bus_type结构体定义如下

/**
 * struct bus_type - The bus type of the device
 *  * @name:	The name of the bus.
 * @dev_name:	Used for subsystems to enumerate devices like ("foo%u", dev->id).
 * @dev_root:	Default device to use as the parent.
 * @bus_groups:	Default attributes of the bus.
 * @dev_groups:	Default attributes of the devices on the bus.
 * @drv_groups: Default attributes of the device drivers on the bus.
 * @match:	Called, perhaps multiple times, whenever a new device or driver
 * 	is added for this bus. It should return a positive value if the
 * 	given device can be handled by the given driver and zero
 * 	otherwise. It may also return error code if determining that
 * 	the driver supports the device is not possible. In case of
 * 	-EPROBE_DEFER it will queue the device for deferred probing.
 * @uevent:	Called when a device is added, removed, or a few other things
 * 	that generate uevents to add the environment variables.
 * @probe:	Called when a new device or driver add to this bus, and callback
 * 	the specific driver's probe to initial the matched device.
 * @remove:	Called when a device removed from this bus.
 * @shutdown:	Called at shut-down time to quiesce the device.
 *  * @online:	Called to put the device back online (after offlining it).
 * @offline:	Called to put the device offline for hot-removal. May fail.
 *  * @suspend:	Called when a device on this bus wants to go to sleep mode.
 * @resume:	Called to bring a device on this bus out of sleep mode.
 * @num_vf:	Called to find out how many virtual functions a device on this
 * 	bus supports.
 * @dma_configure:	Called to setup DMA configuration on a device on
 * 		this bus.
 * @pm:		Power management operations of this bus, callback the specific
 * 	device driver's pm-ops.
 * @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU
 *              driver implementations to a bus and allow the driver to do
 *              bus-specific setup
 * @p:		The private data of the driver core, only the driver core can
 * 	touch this.
 * @lock_key:	Lock class key for use by the lock validator
 * @need_parent_lock:	When probing or removing a device on this bus, the
 * 		device core should lock the device's parent.
 *  * A bus is a channel between the processor and one or more devices. For the
 * purposes of the device model, all devices are connected via a bus, even if
 * it is an internal, virtual, "platform" bus. Buses can plug into each other.
 * A USB controller is usually a PCI device, for example. The device model
 * represents the actual connections between buses and the devices they control.
 * A bus is represented by the bus_type structure. It contains the name, the
 * default attributes, the bus' methods, PM operations, and the driver core's
 * private data.
 */
struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	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);

	int (*num_vf)(struct device *dev);

	int (*dma_configure)(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;

	bool need_parent_lock;
};

i2c_bus_type定义如下

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);

linux内核I2C驱动编程机制

在这个i2c_bus_type总线上同样维护着两个链表:dev 链表和 drv 链表。

  • dev 链表

dev 链表上每一个节点描述的 I2C 外设的纯硬件信息对应的数据结构为 struct i2c_client,每当向 dev 链表添加一个 I2C 外设的硬件信息节点时,只需用此数据结构定义初始化一个对象即可,然后向 dev 链表添加,一旦添加完毕,内核会帮你遍历 drv 链表,从 drv 链表上取出每一个软件节点跟这个要注册的硬件节点进行匹配,内核通过调用总线提供的match 函数进行比较比较 i2c_client 的 name 和 i2c_driver 的 id_table 的 name, 如果匹配成功,硬件找到了对应的软件,内核会调用 i2c_driver 的 probe 函数,并且把匹配成功的硬件节点的首地址传递给 probe 函数。 最终完成硬件和软件的再次结合。

  • drv 链表
    drv 链表上每一个节点描述的 I2C 外设的纯软件信息对应的数据结构为 struct i2c_driver,每当向drv链表添加一个I2C外设的软件信息节点时,只需用此数据结构定义初始化一个对象即可,然后向 drv 链表添加,一旦添加完毕,内核会帮你遍历 dev 链表,从 dev 链表上取出每一个硬件节点跟这个要注册的软件节点进行匹配,内核通过调用总线提供的 match 函数进行比较, 比较 i2c_client 的 name 和 i2c_driver 的 id_table 的 name, 如果匹配成功,软件找到了对应的硬件,内核会调用i2c_driver的probe函数,并且把匹配成功的硬件节点的首地址传递给 probe 函数, 最终完成硬件和软件的再次结合。如下图所示
    在这里插入图片描述
    驱动开发者要实现一个 I2C 设备驱动,只需关注以下两个数据结构:
    struct i2c_client 和 struct i2c_driver。

struct i2c_client定义

/**
 - struct i2c_client - represent an I2C slave device
 - @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;
 - I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking
 - @addr: Address used on the I2C bus connected to the parent adapter.
 - @name: Indicates the type of the device, usually a chip name that's generic enough to hide second-sourcing and compatible revisions.
 - @adapter: manages the bus segment hosting this I2C device
 - @dev: Driver model device node for the slave.
 - @irq: indicates the IRQ generated by this device (if any)
 - @detected: member of an i2c_driver.clients list or i2c-core's
   userspace_devices list
 - @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter
 - calls it to pass on slave events to the slave driver.
 - An i2c_client identifies a single device (i.e. chip) connected to an i2c bus. The behaviour exposed to Linux is defined by the driver
 - managing the device.
 */
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 init_irq;			/* irq set at initialization	*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};

功能:represent an I2C slave device,用于描述I2C外设的纯硬件信息
成员:addr: I2C 外设的设备地址,用于找外设,必须初始化!
name: 用于匹配,必须初始化!
dev: 重点关注其中的 void *platform_data 字段,此字段将来用于装载自定义的用于描述 I2C 外设的硬件信息。
irq:如果 I2C 外设和 CPU 之间有中断,此字段保存对应的中断号
切记: linux 内核对 i2c_client 的操作和 platform_device 所有区别,驱动开发者不用去自己定义初始化和注册一个struct i2c_client硬件节点对象,定义初始化和注册过程统一由内核来帮你完成
那么不用驱动开发者自己去定义初始化和注册一个struct i2c_client硬件节点,内核如何获取具体的iic硬件节点信息呢?答:使用i2c_board_info 这个结构体来给内核提供将来要初始化struct i2c_client硬件节点的具体信息

struct i2c_board_info

/**
 - struct i2c_board_info - template for device creation
 - @type: chip type, to initialize i2c_client.name
 - @flags: to initialize i2c_client.flags
 - @addr: stored in i2c_client.addr
 - @dev_name: Overrides the default <busnr>-<addr> dev_name if set
 - @platform_data: stored in i2c_client.dev.platform_data
 - @of_node: pointer to OpenFirmware device node
 - @fwnode: device node supplied by the platform firmware
 - @properties: additional device properties for the device
 - @resources: resources associated with the device
 - @num_resources: number of resources in the @resources array
 - @irq: stored in i2c_client.irq
 -  - I2C doesn't actually support hardware probing, although controllers and
 - devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's
 - a device at a given address.  Drivers commonly need more information than
 - that, such as chip type, configuration, associated IRQ, and so on.
 -  - i2c_board_info is used to build tables of information listing I2C devices
 - that are present.  This information is used to grow the driver model tree.
 - For mainboards this is done statically using i2c_register_board_info();
 - bus numbers identify adapters that aren't yet available.  For add-on boards,
 - i2c_new_device() does this dynamically with the adapter already known.
 */
struct i2c_board_info {
	char		type[I2C_NAME_SIZE];
	unsigned short	flags;
	unsigned short	addr;
	const char	*dev_name;
	void		*platform_data;
	struct device_node *of_node;
	struct fwnode_handle *fwnode;
	const struct property_entry *properties;
	const struct resource *resources;
	unsigned int	num_resources;
	int		irq;
};

该结构体功能:驱动开发者利用此数据结构将 I2C 外设的硬件信息告诉给 linux 内核,将来内核根据提供的 I2C外设的硬件信息帮你定义初始化和注册一个i2c_client硬件节点对象到 dev链表。
结构体成员

  • type:指定硬件节点的名称,此字段将来会自动的赋值给 i2c_client 的name,将来用于匹配. 所以必须初始化!
  • addr:指定 I2C 外设的设备地址,此字段将来会自动赋值给 i2c_client
    的 addr,将来用于找外设,所以必须初始化!
  • platform_data:用 于 装 载 自 定 义 的 硬 件 信 息 , 将 来 会 自 动 赋 值 给i2c_client.dev.platform_data
  • irq:如果 CPU 和外设需要中断,此字段用来指定中断号将来会赋值给 i2c_client.irq
  • 其他的成员暂时没能力解释,不知道,可以省略
    配套函数:i2c_register_board_info(int busnum,
    struct i2c_board_info const *info, unsigned len)
  • 函数功能:注册 I2C 外设硬件信息到内核,将来 linux 内核会帮你定义初始化和注册i2c_client 硬 件 节 点 到 dev 链 表 , 内 核 初 始 化 i2c_client 所 需 的 内 容 都 是 根 据i2c_board_info 来进行提供。
  • 参数:busnum:I2C 外设所在的 CPU 的 I2C 总线编号,根据原理图获取
    info:传递要注册的 I2C 硬件信息
    len:用 i2c_board_info 描述的硬件信息的个数
    注意事项:struct i2c_board_info 的定义初始化和注册不能采用 insmod/rmmod 进行,必须将代码和 uImage 写到一起,一般要写到开发板对应的平台文件中(比如内核源码/arch/arm/plat-s5p6818/x6818/device.c)!
    下面以X6818 开发板上的 mma8653三轴加速度传感器为例
    在这里插入图片描述
    在这里插入图片描述
    见上图片,struct i2c_board_info 的定义初始化和注册是在X6818 开发板的板级信息文件arch/arm/plat-s5p6818/x6818/device.c中。

struct i2c_driver

/**
 - struct i2c_driver - represent an I2C device driver
 - @class: What kind of i2c device we instantiate (for detect)
 - @probe: Callback for device binding - soon to be deprecated
 - @probe_new: New callback for device binding
 - @remove: Callback for device unbinding
 - @shutdown: Callback for device shutdown
 - @alert: Alert callback, for example for the SMBus alert protocol
 - @command: Callback for bus-wide signaling (optional)
 - @driver: Device driver model driver
 - @id_table: List of I2C devices supported by this driver
 - @detect: Callback for device detection
 - @address_list: The I2C addresses to probe (for detect)
 - @clients: List of detected clients we created (for i2c-core use only)
 - @disable_i2c_core_irq_mapping: Tell the i2c-core to not do irq-mapping
 -  - The driver.owner field should be set to the module owner of this driver.
 - The driver.name field should be set to the name of this driver.
 -  - For automatic device detection, both @detect and @address_list must
 - be defined. @class should also be set, otherwise only devices forced
 - with module parameters will be created. The detect function must
 - fill at least the name field of the i2c_board_info structure it is
 - handed upon successful detection, and possibly also the flags field.
 -  - If @detect is missing, the driver will still work fine for enumerated
 - devices. Detected devices simply won't be supported. This is expected
 - for the many I2C/SMBus devices which can't be detected reliably, and
 - the ones which can always be enumerated in practice.
 -  - The i2c_client structure which is handed to the @detect callback is
 - not a real i2c_client. It is initialized just enough so that you can
 - call i2c_smbus_read_byte_data and friends on it. Don't do anything
 - else with it. In particular, calling dev_dbg and friends on it is
 - not allowed.
 */
struct i2c_driver {
	unsigned int class;

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

	/* New driver model interface to aid the seamless removal of the
	 * current probe()'s, more commonly unused than used second parameter.
	 */
	int (*probe_new)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(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").
	 * For the SMBus Host Notify protocol, the data corresponds to the
	 * 16-bit payload data reported by the slave device acting as master.
	 */
	void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
		      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;

	bool disable_i2c_core_irq_mapping;
};

功能说明:represent an I2C device driver,描述 I2C 外设的软件信息
结构体成员

  • probe:硬件节点和软件节点匹配成功,内核调用

  • remove:卸载软件节点,内核调用此函数 (这里的硬件节点无法卸载,写在一起了跟内核)

  • id_table:重点关注其中的 name 字段,此字段将来用于匹配

struct i2c_device_id {
char name[I2C_NAME_SIZE]; //用于匹配
unsigned long driver_data //用于给 probe 函数传递参数
}

配套函数

  • i2c_add_driver(&软件节点对象)

向内核 drv 链表注册添加 I2C 外设软件节点对象
将来内核会帮你遍历,匹配,调用 probe 函数,传递参数

/* use a define to avoid include chaining to get THIS_MODULE */
#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)

i2c_register_driver()函数源码如下

/*
 * An i2c_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(struct module *owner, struct i2c_driver *driver)
{
	int res;

	/* Can't register until after driver model init */
	if (WARN_ON(!is_registered))
		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;
	INIT_LIST_HEAD(&driver->clients);

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver);
	if (res)
		return res;

	pr_debug("driver [%s] registered\n", driver->driver.name);

	/* Walk the adapters that are already present */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}
  • i2c_del_driver(&软件节点对象)

从内核 drv 链表删除软件节点对象
内核会帮你调用 remove 函数

/**
 * i2c_del_driver - unregister I2C driver
 * @driver: the driver being unregistered
 * Context: can sleep
 */
void i2c_del_driver(struct i2c_driver *driver)
{
	i2c_for_each_dev(driver, __process_removed_driver);

	driver_unregister(&driver->driver);
	pr_debug("driver [%s] unregistered\n", driver->driver.name);
}

linux内核I2C驱动编程框架举例

先看如下框架图
在这里插入图片描述

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>

static struct i2c_device_id mma8653_id[] = {
    {"mma8653", 0},
    { }
}; //"mma865"将来用于匹配

// probe函数实现
//client:指向匹配成功的硬件节点对象(由内核帮你创建)
//client->addr:获取设备地址
//client->irq:获取中断号
//client->dev.platform_data:获取自定义的硬件信息
static int mma8653_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    printk("%s\n", __func__);
    return 0;
}
//remove函数实现
static int mma8653_remove(struct i2c_client *client)
{
    printk("%s\n", __func__);
    return 0;
}

//定义初始化MMA8653软件节点对象
static struct i2c_driver mma8653_drv = {
    .driver = {
        .name = "tarena" //不重要,匹配不是靠它完成  //不写会吐核 wkx
    },
    .id_table = mma8653_id, //匹配是靠其中的name字段
    .probe = mma8653_probe,
    .remove = mma8653_remove
};
//入口函数
static int mma8653_init(void)
{
    //向内核drv链表添加软件节点对象,匹配成功,内核调用probe函数
    i2c_add_driver(&mma8653_drv);
    return 0;
}
//出口函数
static void mma8653_exit(void)
{
    //从内核中删除软件节点对象,调用remove函数
    i2c_del_driver(&mma8653_drv);
}
//各种修饰及其GPL规则
module_init(mma8653_init);
module_exit(mma8653_exit);
MODULE_LICENSE("GPL");

猜你喜欢

转载自blog.csdn.net/weixin_43326587/article/details/107089183