Linux的I2C子系统

这篇博客详细介绍了Linux内核中的I2C子系统,包括i2c_client(设备结构体)、i2c_driver(驱动结构体)和i2c_adapter(适配器结构体)对象。这些对象分别代表了I2C设备、驱动程序和控制器,并通过注册机制相互配合工作。博客还阐述了I2C程序框架、核心i2c-core的初始化以及I2C设备和驱动的注册过程。内容深入浅出,有助于理解Linux系统中I2C通信的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux的I2C子系统

i2c子系统中内容很多,相互交织,一下子讲不清楚。下面使用对象的思想,把一些硬件,算法,驱动等抽象为对象,使用对象来表达,然后由不同的对象组成一个整体。
下面先介绍各个部分抽象出来的对象。

对象结构体列举


1、设备结构体对象 i2c_client

这是设备信息对象描述的结构体,即设备i2c_device,它是对设备的硬件信息记录和描述,是要注册到系统中的,和i2c_driver 配对,即把硬件描述信息交给驱动,由驱动操作硬件完成功能。

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 i2c_driver *driver;	   /* and our access routines */
	struct device  dev;		       /* the device structure	 */
	int irq;			           /* irq issued by device	*/
	struct list_head  detected;
};

2、驱动结构体对象 i2c_driver

从机设备驱动。和 i2c_client 匹配,通过硬件提供的信息操作硬件。这样做就是把驱动和设备进行分离。

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 if you can, it will probably
	 * be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *);
	int (*detach_adapter)(struct i2c_adapter *);

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

3、适配器结构体对象 i2c_adapter

这个结构体描述了SOC内部的I2C控制器对象的数据和操作方法。即控制器的控制代码,寄存器操作等。

struct i2c_adapter 
{
	struct module *owner;
	unsigned int id;
	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];  //适配器名称,可通过/sys/bus/i2c/devices/i2c-x/name访问
	struct completion dev_released;
	struct list_head userspace_clients;
};

4、算法结构体对象 i2c_algorithm

这个结构体对象描述了I2C算法,如时序的控制,通讯算法等。它一般是包含在 i2c_adapter 内部的。

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 *);//当前的i2c控制器支持的功能
};

I2C 程序框架

经过抽象出来的对象,他们可以通过下面的框图进行组织。

在这里插入图片描述

I2C核心 i2c-core分析

I2C核心的代码在 /deivers/i2c/i2c-core.c。核心代码在系统初始化时,作为一个模块加载。

postcore_initcall(i2c_init);
module_exit(i2c_exit);

i2c_init 代码如下:

static int __init i2c_init(void)
{
	retval = bus_register(&i2c_bus_type);//I2C总线注册

#ifdef CONFIG_I2C_COMPAT
	i2c_adapter_compat_class = class_compat_register("i2c-adapter");
#endif
	retval = i2c_add_driver(&dummy_driver);
}

bus_register(&i2c_bus_type) 是在注册I2C总线,结构体 i2c_bus_type 中有操作总线的函数。注册后就在 /sys/bus 目录下出现 i2c 目录。这个初始化主要任务就是注册总线。
总线的结构体定义如下:

struct bus_type i2c_bus_type = 
{
	.name		= "i2c",  //总线名称,/sys/bus/下出现这个名称
	.match		= i2c_device_match,  //匹配函数
	.probe		= i2c_device_probe,  //探测函数
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};

i2c_add_driver(&dummy_driver) 注册驱动,这里了注册了一个空驱动 dummy。驱动出现在在/sys/bus/ 的dummy。

static struct i2c_driver dummy_driver = 
{
	.driver.name	= "dummy",  //空的驱动
	.probe		= dummy_probe,
	.remove		= dummy_remove,
	.id_table	= dummy_id,
};

下面对 i2c_bus_type 中的实现函数解析:

i2c_device_match

  • 将设备和驱动进行匹配。使用设备和驱动的名字进行匹配。驱动维护了一个链表,存储所有的I2C驱动,设备就名称和驱动数组中的驱动名称进行配对,名称相同就匹配成功。

i2c_device_probe

  • 当设备和驱动匹配成功了,就执行这个函数,这个函数的功能是探测设备的,对设备初始化设置,如果成功设置,就探测成功。
  • 注意,当调用这个函数时,在它内部实际上是调用了 driver中的 probe函数,在这里是总线中的 probe。

i2c_device_remove

i2c_device_shutdown


I2C-client 和 I2C-driver 的注册函数

I2C核心提供了对I2C-client 和 I2C-driver 的注册函数;,面这些对象在不同的时期进行注册到系统中才能使用。注册函数如下。

I2C-driver 的注册函数:
static inline int i2c_add_driver(struct i2c_driver *driver)
{
	return i2c_register_driver(THIS_MODULE, driver);//
}
I2C设备注册函数:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    status = device_register(&client->dev);//设备注册
}
i2c_adapter的注册:

i2c_add_adapter
i2c_add_numbered_adapter


I2C_adapter 分析

i2c-adapter 是SOC内部的i2c控制器,i2c属于一种总线,所以下面的代码使用了平台总线组织i2c控制器。平台总线用来管理驱动和设备的匹配等任务的。下面是i2c适配器驱动代码,文件名称i2c-s3c2410.c,它作为模块注册到系统中的,它属于平台总线驱动代码。

subsys_initcall(i2c_adap_s3c_init);

i2c_adap_s3c_init函数:

static int __init i2c_adap_s3c_init(void)
{
	return platform_driver_register(&s3c24xx_i2c_driver);//用平台总线注册的
}

s3c24xx_i2c_driver结构体:

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,
	},
};

上面的s3c24xx_i2c_probe初始化函数,是总线驱动和设备(I2C控制器 即device)进行匹配成功后执行的。执行probe函数的参数是匹配(mach)时传递过来的。这里的驱动是adapter总线 驱动,设备是adapter 设备。

s3c24xx_i2c_probe 主要完成的工作是adapter结构体的赋值和adapter接口注册。

下面是s3c24xx_i2c_probe大概的分析:

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
	struct s3c24xx_i2c *i2c;  //定义s3c24xx_i2c类型的指针,这个类型就包含了adapter结构体。
	struct s3c2410_platform_i2c *pdata;
	struct resource *res;


    pdata = pdev->dev.platform_data;//设备具体的信息取出来赋值给pdata
    
    i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);//分配内存,填充
	strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
	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;
    ......
    
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//对资源的申请,后面还有设置等。

    ret = i2c_add_numbered_adapter(&i2c->adap);//注册adapter函数

}

s3c24xx_i2c_algorithm 时序控制结构体的实例化

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,//
	.functionality		= s3c24xx_i2c_func,//所支持的功能列举
};

I2C-client 和 I2C-driver 实例化

以触摸屏680为例,说明实例化。

  • 驱动注册
static int __init gsl_ts_init(void)
{
	ret = i2c_add_driver(&gsl_ts_driver);//注册驱动
}

驱动结构体:

static struct i2c_driver gsl_ts_driver =
 {
	.driver = {
		.name = GSLX680_I2C_NAME,
		.owner = THIS_MODULE,
	},
#ifndef CONFIG_HAS_EARLYSUSPEND
	.suspend	= gsl_ts_suspend,
	.resume	= gsl_ts_resume,
#endif
	.probe		= gsl_ts_probe,
	.remove		= __devexit_p(gsl_ts_remove),
	.id_table	= gsl_ts_id,
};
  • 设备注册

smdkc110_machine_init()中对设备结构体信息填充,在 i2c_register_adapter 中完成注册设备。

static void __init smdkc110_machine_init(void)
{
	i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
	i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
	i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
}

i2c_devs0i2c_devs1

/* I2C0 */
static struct i2c_board_info i2c_devs0[] __initdata = {
#ifdef CONFIG_SND_SOC_WM8580
	{
		I2C_BOARD_INFO("wm8580", 0x1b),
	},
#endif
};

/* I2C1 */
static struct i2c_board_info i2c_devs1[] __initdata = {
#ifdef CONFIG_VIDEO_TV20
	{
		I2C_BOARD_INFO("s5p_ddc", (0x74>>1)),
	},
#endif
};

i2c_board_info

struct i2c_board_info {
	char		type[I2C_NAME_SIZE];
	unsigned short	flags;
	unsigned short	addr;      //i2c设备从从地址
	void		*platform_data;   //i2c私有数据
	struct dev_archdata	*archdata;
#ifdef CONFIG_OF
	struct device_node *of_node;
#endif
	int		irq;                   //外部中断, 和i2c中的中断没关系,即发生触摸动作时,通知主机,i2c取数据吧
};
如何注册device
int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
{
    devinfo->busnum = busnum;
	devinfo->board_info = *info;
	list_add_tail(&devinfo->list, &__i2c_board_list);//这里仅仅是维护了一个设备链表结构,还没有真正填充内容。
}

在 i2c_register_adapter 中真正创建了设备,调用关系如下:

i2c_register_adapter 
—> i2c_scan_static_board_info //遍历设备链表,找出注册的代表设备的那个结构体
—>i2c_new_device
—>device_register

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值