Linux IIC 驱动分析(1) — 框架分析

  • Post author:
  • Post category:linux




Linux IIC 驱动分析(1) — 框架分析


目录


1、框架


2、数据结构&软件层次


2.1、i2c_adatper


2.2、i2c_algorithm


2.3、i2c_bus_type


2.4、i2c_client


2.5、i2c_board_info


2.6、i2c_driver


2.7、i2c_msg


2.8、i2c_dev


3、小结


IIC 属于较为常用的总线,一般会集成到 SoC 上,作为一个通用外设而存在,IIC 总线会接有类似 EEPROM、TP (触摸屏)等设备,他们通过 IIC 协议与 SoC 进行通信,将数据传输/接收到 SoC。关于总线协议部分参考

IIC 总线协议详解

,这里不在多说协议部分,着重关注 Linux 的 IIC 驱动

框架

部分;



1、框架

img

大的软件框架如上图所示:

1、应用层通过标准的 open 调用进行 IIC 设备的操作;

2、每一个 i2c_client 对应一个实际硬件上的 IIC device(比如 EEPROM)

3、每一个 i2c_driver 描述一种 IIC 设备的驱动

4、i2c_dev 是注册的字符类型的设备

5、i2c_core 是 IIC 核心层,提供了总线、驱动、通信方式等的注册和钩子函数的设置

6、i2c_adapter 用于描述一个 SoC 的一个 IIC 控制器

7、i2c_algorithm 用于底层对接实际的控制器,产生 IIC 硬件波形的函数

8、下面对接的就是实际的 SoC 的 IIC 控制器(寄存器)和硬件

IIC 的 Linux 软件结构与 Linux SPI 部分(

Linux SPI 驱动分析(1)— 结构框架

)的基本一致,主要的代码实现集中在:***drivers/i2c/***目录下,首先我们先认识一下 Linux 是怎么抽象 IIC 的,这对于更好的理解代码,至关重要:

img

这张图,是在描述软件对硬件的抽象过程,非红的部分,是硬件,SoC 上挂了 N 个 IIC 的控制器,每个控制器上,挂了 M 个设备。红色部分,是 Linux 针对硬件抽象出来的软件结构,由此可以看到,Linux 驱动中:

1、**i2c_adatper:**描述一个实际的 IIC 物理硬件

2、**i2c_algorithm:**函数指针集,钩子函数,用于描述特定 SoC 硬件的 IIC 模块产生通信波形的方法

3、**i2c_client:**描述一个挂接到 IIC 总线上的具体物理设备

4、**i2c_driver:**用于描述一个 IIC 设备的驱动

有了抽象出来的

数据结构

后,在结合 Linux 设备驱动的框架(Bus、Device、Driver),我们来看看在软件层次上,每个层次与上下层之间的关系,以及抽象出来的接口。



2、数据结构&软件层次

有了第一章的部分,我们来看看围绕着几个数据结构划分的几个软件层次



2.1、

i2c_adatper



i2c_adatper


用于描述一个实际的 IIC 控制器,它的定义在 *

include/linux/i2c.h*

文件,如下:

struct i2c_adapter {
	struct module *owner;    // 所属模块
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; // 总线通信方法结构体指针
	void *algo_data;         // algorithm数据
 
	/* data fields that are valid for all devices	*/
	struct rt_mutex bus_lock;  //控制并发访问的自旋锁
 
	int timeout;			/* in jiffies */
	int retries;            // 重试次数
	struct device dev;		// 适配器设备
 
	int nr;                
	char name[48];          // 适配器名称
	struct completion dev_released;
 
	struct mutex userspace_clients_lock;
	struct list_head userspace_clients;  // client 链表头
};

它定义了一些基本的成员,核心成员 *algo,它包含了操作这个 IIC 控制器的函数集,也就是直接对接到实际的 SoC 的 IIC 控制器的操作(寄存器配置);针对每款 CPU 来说,IIC 控制器可能不止一个,所以 i2c_adapter 也可能不止一个,他们都需要在系统上电的时候,进行初始化,所以,这些 i2c_adapter 结构,是通过 platform 总线连接,并在上电的时候通过定义多个 platform device 设备和 platform_driver,靠 platform_bus 连接起来,并进行多次的 probe 调用,进行初始化的!

比如:

static struct platform_device_id s3c24xx_driver_ids[] = {
	{
		.name		= "s3c2410-i2c",
		.driver_data	= TYPE_S3C2410,
	}, {
		.name		= "s3c2440-i2c",
		.driver_data	= TYPE_S3C2440,
	}, {
		.name		= "s3c2440-hdmiphy-i2c",
		.driver_data	= TYPE_S3C2440_HDMIPHY,
	}, { },
};
MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
 
static struct platform_driver s3c24xx_i2c_driver = {
	.probe		= s3c24xx_i2c_probe,
	.remove		= s3c24xx_i2c_remove,
	.id_table	= s3c24xx_driver_ids,
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c-i2c",
		.pm	= S3C24XX_DEV_PM_OPS,
	},
};
 
static int __init i2c_adap_s3c_init(void)
{
	return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);

这里定义并注册了针对该款芯片的的 platform driver,同时:

void __init exynos4_map_io(void)
{
....
	/* The I2C bus controllers are directly compatible with s3c2440 */
	s3c_i2c0_setname("s3c2440-i2c");
	s3c_i2c1_setname("s3c2440-i2c");
	s3c_i2c2_setname("s3c2440-i2c");
....
}

所以这款 CPU 有 3 个 IIC 硬件控制器,这个 s3c24xx_i2c_probe 会被匹配 3 次,也就是调用 3 次;

既然 i2c_adapter 结构和具体的硬件相关,那么必然的,不同 CPU 需要有不同的实例化,所以,在每个 CPU 的 xxx_i2c_probe 函数中,就是对 i2c_adapter 最好的实例化的地方;

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
	struct s3c24xx_i2c *i2c;
	struct s3c2410_platform_i2c *pdata;
	struct resource *res;
	int ret;
....
	i2c->adap.owner   = THIS_MODULE;
	i2c->adap.algo    = &s3c24xx_i2c_algorithm;
	i2c->adap.retries = 2;
	i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	i2c->tx_setup     = 50;
 
	spin_lock_init(&i2c->lock);
	init_waitqueue_head(&i2c->wait);
....
	ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,
			  dev_name(&pdev->dev), i2c);
....
	i2c->adap.nr = pdata->bus_num;
 
	ret = i2c_add_numbered_adapter(&i2c->adap);
	if (ret < 0) {
		dev_err(&pdev->dev, "failed to add bus to i2c core\n");
		goto err_cpufreq;
	}
	platform_set_drvdata(pdev, i2c);
....
	return ret;
}

在 probe 中,一般的,需要申请 IRQ 资源,获取寄存器资源,这里主要关注的是,


初始化了一个 i2c_adapter 结构,并将它注册到了 i2c 核心


img



2.2、

i2c_algorithm



i2c_algorithm


代表了和硬件对接的一组 IIC 控制器操作集合:

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

可以看到,master_xfer 函数用于数据传送和读取,functionality 用来获取 IIC 控制器支持情况,根据 xxx_i2c_probe 分析可知,在调用这个 xxx_i2c_probe 的时候,已经将其操作集 i2c_algorithm 挂接到了 i2c_adapter 结构,一并注册到了 i2c 核心。

/* i2c bus registration info */
 
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};
 
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
...
	i2c->adap.owner   = THIS_MODULE;
	i2c->adap.algo    = &s3c24xx_i2c_algorithm;
	i2c->adap.retries = 2;
	i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	i2c->tx_setup     = 50;
...
	return ret;
}



2.3、i2c_bus_type

现在我们有了对接底层的那套东西,并已经注册到了 i2c 核心,现在我们转战 i2c 核心;

根据 Linux 的设备、驱动、总线的思想,i2c 设备应该通过 Linux 的 i2c 总线(bus)挂接上去,并和其固定的 driver 进行匹配,所以在 i2c 核心初始化的时候,必须先要注册 i2c bus,在 i2c-core.c 中:

static int __init i2c_init(void)
{
	int retval;
 
	retval = bus_register(&i2c_bus_type);
	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);
}
 
/* We must initialize early, because some subsystems register i2c drivers
 * in subsys_initcall() code, but are linked (and initialized) before i2c.
 */
postcore_initcall(i2c_init);
module_exit(i2c_exit);

这里建立起了 i2c bus:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};

i2c bus 定义 match 的规则:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;
 
	if (!client)
		return 0;
 
	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;
 
	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;
 
	return 0;
}

可以看到,这里是通过 match 它的 id_table 来进行匹配的。

到这里,i2c 核心,建立起来了 i2c bus,就等着挂接 device 和 driver 上去了:

img



2.4、i2c_client

现在 i2c 控制器,i2c 操作硬件函数,i2c 总线(软件抽象的)已经准备完毕,接下来就需要添加 i2c 设备进总线,i2c 设备使用 *

i2c_client*

结构来描述:

struct i2c_client { 
	unsigned int flags; /* 标志 */ 
	unsigned short addr; /* 低 7 位为芯片地址 */
	char name[I2C_NAME_SIZE]; /* 设备名称 */ 
 
	struct i2c_adapter *adapter; /*依附的 i2c_adapter*/ 
	struct i2c_driver *driver; /*依附的 i2c_driver */ 
 
	int irq;
	struct device dev; /* 设备结构体 */ 
	struct list_head detected; /* 链表头 */
};

比如一个 EEPROM 的 IIC 设备,那么它就需要首先实现这个 *

i2c_client*

结构,并将其注册(添加)到 i2c 核心,添加的函数为:

1、i2c_new_device

2、i2c_register_board_info

3、i2c_scan_static_board_info

4、i2c_detect_address

四种方式各有千秋,下一篇文章分析其用法

img



2.5、i2c_board_info

用于描述具体的 IIC 设备,和上面一个差不多,不再多讲:

struct i2c_board_info {
	char		type[I2C_NAME_SIZE];
	unsigned short	flags;
	unsigned short	addr;
	void		*platform_data;
	struct dev_archdata	*archdata;
	struct device_node *of_node;
	int		irq;
};



2.6、i2c_driver

现在就剩下 driver 了,一个 IIC 外设的驱动用一个 *

i2c_driver*

结构描述:

struct i2c_driver {
	unsigned int class;
 
	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this, it will be removed in a
	 * near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated; /*依附 i2c_adapter 函数指针 */
	int (*detach_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;
};

具体的设备,需要实现对应的 i2c_driver,并注册到 i2c 核心,比如 EEPROM 设备,在 drivers/misc/eeprom.c:

static const struct i2c_device_id eeprom_id[] = {
	{ "eeprom", 0 },
	{ }
};
 
static struct i2c_driver eeprom_driver = {
	.driver = {
		.name	= "eeprom",
	},
	.probe		= eeprom_probe,
	.remove		= eeprom_remove,
	.id_table	= eeprom_id,
 
	.class		= I2C_CLASS_DDC | I2C_CLASS_SPD,
	.detect		= eeprom_detect,
	.address_list	= normal_i2c,
};
 
static int __init eeprom_init(void)
{
	return i2c_add_driver(&eeprom_driver);
}

通过


i2c_add_driver


函数注册到 i2c 核心

img



2.7、i2c_msg

万事俱备只欠东风,东西都准备好了,那么开始传输数据吧,内核中使用 *

i2c_msg*

来描述一个 i2c 数据构成:

struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RD		0x0001	/* read data, from slave to master */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};

数据传输,主要就是指定设备地址,数据长度,以及数据 buffer,flags 标记了传输的一些属性。

当数据准备好了,即填充好 i2c_msg 后,可以调用 i2c 核心层提供的函数进行数据的收发:

1、**i2c_transfer:**调用 adap->algo->master_xfer 进行数据收发

2、**i2c_master_send:**包装了 i2c_transfer 调用,填充 i2c_msg 进行数据发送一次

3、**i2c_master_recv:**包装了 i2c_transfer 调用,填充 i2c_msg 进行数据接收一次



2.8、i2c_dev

虽然万事 OK,但是还是需要给驱动层留一个访问 i2c 的口子吧?所以有了 i2c_dev,i2c 核心只是做了一些管理和提供接口的工作,那么具体的支撑用户层进行访问的算是这个 i2c_dev 了,在 i2c_dev.c 文件中,首先进行 i2c-dev 的分配和注册:

static int __init i2c_dev_init(void)
{
	int res;
 
	printk(KERN_INFO "i2c /dev entries driver\n");
 
	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;
 
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
 
	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;
 
	/* Bind to already existing adapters right away */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);
 
	return 0;
 
out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

同时提供了用户层的操作集合 *

i2cdev_fops*

static const struct file_operations i2cdev_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= i2cdev_read,
	.write		= i2cdev_write,
	.unlocked_ioctl	= i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

用户层调用的 open、read、write、ioclt,都会先走到这里,我们看一个 i2cdev_read:

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
		loff_t *offset)
{
	char *tmp;
	int ret;
 
	struct i2c_client *client = file->private_data;
 
	if (count > 8192)
		count = 8192;
 
	tmp = kmalloc(count, GFP_KERNEL);
	if (tmp == NULL)
		return -ENOMEM;
 
	pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
		iminor(file->f_path.dentry->d_inode), count);
 
	ret = i2c_master_recv(client, tmp, count);
	if (ret >= 0)
		ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
	kfree(tmp);
	return ret;
}

调用到和 i2c 核心层的


i2c_master_recv


函数后,将结果通过


copy_to_user


返回给用户层



3、小结

至此,i2c 的基本结构和最基本的流程分析差不多了,数据结构之间的关系为:

img

大致的结构之间的注册和初始化为:

img

用户层调用的流程大致为:

img

参考文档:

https://www.cnblogs.com/lifexy/p/7816324.html

https://blog.csdn.net/tech_pro/article/details/72902883

https://blog.csdn.net/m0_37661202/article/details/75949790

https://blog.csdn.net/shenlong1356/article/details/89212234

https://blog.csdn.net/fulinus/article/details/9008191

https://blog.csdn.net/W1107101310/article/details/79871029

https://blog.csdn.net/weixin_43542305/article/details/86479106