I2C驱动

I2C中主要包含5个管脚,包括:复位信号,中断信号,I2C时钟信号,I2C数据信号,触摸屏供电信号(我的电路供电没有控制信号)。设备树相对简单了很多。

SCL和SDA管脚是需要在i2c的主控侧配置的,管脚类型配置为i2c功能,中断脚和rst脚是放在设备的DTS树中配置的,管脚配置为gpio功能。

触摸屏使用的是i2c总线:

麒麟适配mysql_麒麟适配mysql

 数据线和时钟线

这两个管脚需要在i2c主控侧配置,在rk3568.dtsi文件中对数据线和时钟线的配置如下:

麒麟适配mysql_初始化_02

 在rk3568-pinctrl.dtsi中的管脚配置如下:

麒麟适配mysql_麒麟适配mysql_03

中断脚和rst脚的配置在客户端设备中,在rk3568-evb.dtsi文件中配置如下:

麒麟适配mysql_嵌入式硬件_04

中断脚和rst脚

这两个脚在客户端的设备树中配置,在rk3568-evb.dtsi中配置如下:

麒麟适配mysql_嵌入式硬件_05

麒麟适配mysql_设备驱动_06

  设备节点调试

我们触摸屏是接在i2c1上,i2c主控节点如下:

麒麟适配mysql_单片机_07

I2C体系结构

由于现在引入了设备树这种机制,所以I2C以及不需要自己手动注册了,开发板一上电,i2c总线就注册好了,而设备树又是和platform平台总线相配合的,设备树中的节点会被解析为platform的平台设备。所以我们需要将平台总线设备转换为i2c设备,再注册到I2C总线里。

Linux驱动分为三部分:I2C核心,I2C总线驱动和I2C设备驱动。

麒麟适配mysql_单片机_08

I2C核心

I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,这部分主要是一些与硬件按无关的接口函数,这部分代码一般不用我们普通开发者进行开发和修改,但是理解这部分的代码逻辑和接口还是非常必要的。

I2C核心中的主要函数如下:

注册/注销 适配器(adapter)
int i2c_add_adapter(struct i2c_adapter *adap);
int i2c_del_adapter(struct i2c_adapter *adap);

注册/注销 I2C设备驱动程序
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);

创建并注册一个新的I2C设备
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);

I2C 传输、发送和接收
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);

上面最后的三个函数用于实现I2C设备之间的数据交换。i2c_transfer函数可以进行复杂的多消息传输 ,而i2c_master_send和i2c_master_recv函数用于单个数据消息的发送和接收。

这些函数提供了对I2C总线读写操作的基本支持,简化了I2C设备驱动的开发,有了这些接口我们就不用关注I2C协议方面的代码了,只需要调用该接口即可完成数据的传输。

注意:I2C_transfer函数本身不具备驱动适配器物理硬件完成消息交互的能i,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer函数真正驱动硬件流程。

i2c适配器驱动

通过上面的介绍我们知道了i2c驱动主要分为三部分,上面我们已经介绍了i2c核心这一部分,现在我们来介绍一下i2c适配器驱动,我们知道i2c驱动和其他的那些字符设备驱动有所不同,I2C驱动中维持着一套自己的总线。

I2C适配器驱动是Linux内核中的一个核心模块,总线层负责管理所有注册到系统的I2C总线适配器和设备,并提供与设备通信的API函数。它提供了一些基本的操作函数,如启动总线、停止总线、发送起始信号、发送停止信号等。但是这部分是由Linux内核完成的,并不需要我们开发者进行修改或添加,所以了解即可。

主要包含I2C硬件体系结构体中适配器的控制,用于I2C读写时序,主要数据结构:I2C_Adapter,I2C_algorithm

下面我们用一张图来看一下上面描述的这个过程:

麒麟适配mysql_麒麟适配mysql_09

I2c设备驱动

I2C设备驱动要使用i2c_driver和i2c_client数据结构并填充其中的成员函数。 i2c_client一般被包含在设备的私有信息结构体yyy_data中,而i2c_driver则适合被定义为全局变量并初始化。

看到i2c设备驱动的两个结构体大家是不是很熟悉了,I2C设备驱动是针对特定类型的i2c设备编写的驱动程序。它包含了对具体设备的操作和控制逻辑,通过调用I2C总线核心驱动提供的API函数与设备进行通信。设备驱动的主要任务包括初始化设备、读写数据、配置设备参数等。

通过I2C适配器与CPU交换数据。主要数据结构:i2c_driver和i2c_client

因为这部分是针对特定类型的I2C设备编写的驱动程序,所以这部分才是要我们开发人员来完成编写的,我们如果需要在自己的开发板上添加一个新的I2C模块,就要首先编写I2C设备驱动这部分,这部分的编写需要调用上面我们介绍的i2c核心和i2c总线中接口函数来完成模块的初始化。

i2c驱动总结

总线核心驱动: Linux内核中的一个核心模块,负责管理所有注册到系统的i2c总线适配器和设备,并提供与设备通信的API函数。它提供了一些基本的操作函数,如启动总线、停止总线、发送起始信号、发送停止信号等。

适配器驱动:i2c适配器驱动负责与硬件的I2C控制器进行交互,完成硬件层面的初始化、配置和操作。它将底层硬件的特定接口与I2C总线核心驱动进行连接,使得核心驱动能够通过适配器驱动来访问硬件。

设备驱动: i2c设备驱动是针对特定类型的I2C设备编写的驱动程序。它包含了对具体设备的操作和控制逻辑,通过调用I2C总线核心驱动提供的API函数与设备进行通信。设备驱动的主要任务包括初始化设备、读写数据、配置设备参数等。

三部分之间的关系如下:

I2C核心层驱动作为顶层驱动,管理整个I2C子系统,并提供了基本的I2C操作接口;

I2C适配器驱动负责与底层硬件的I2C控制器进行交互,通过适配器驱动,I2C总线核心驱动能够与硬件进行通信。

I2C设备驱动则针对具体的I2C设备编写,实现了对设备的初始化、读写数据等操作。

麒麟适配mysql_单片机_10

具体设备驱动分析

当我们需编写具体的I2C设备驱动程序时,我们需要编写以下内容:probe函数、remove函数、操作函数以及数据传输与处理,下面将对每部分进行详细介绍。

probe函数

具体设备中的probe函数是I2C设备驱动中最重要的函数之一,用于在I2C设备与驱动匹配成功后进行初始化和注册设备。在probe函数中,可以执行以下任务:

  • 进行设备的特定初始化操作,例如配置设备寄存器、申请内存资源等;
  • 注册字符设备、输入设备或其他设备类别,使系统能够识别和使用该设备;
  • 存储设备私有数据,通过使用i2c_set_clientdata函数将私有数据与i2c_client相关联,方便后续的操作函数访问。

我们在学习其他设备驱动的时候就知道了probe函数是设备与驱动匹配成功后被调用执行的。它的原型通常如下所示:

static int i2c_device_probe(struct i2c_client *client, const struct i2c_device_id *id);

下面我们就找一个设备驱动来分析一下我们应该如何编写,这里以rk3x_i2c_probe为例给大家进行分析:

static int rk3x_i2c_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	const struct of_device_id *match;
	struct rk3x_i2c *i2c;
	struct resource *mem;
	int ret = 0;
	int bus_nr;
	u32 value;
	int irq;
	unsigned long clk_rate;
 
	i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
	if (!i2c)
		return -ENOMEM;
 
	match = of_match_node(rk3x_i2c_match, np);
	i2c->soc_data = (struct rk3x_i2c_soc_data *)match->data;
 
	/* use common interface to get I2C timing properties */
	i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);
 
	strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));
	i2c->adap.owner = THIS_MODULE;
	i2c->adap.algo = &rk3x_i2c_algorithm;
	i2c->adap.retries = 3;
	i2c->adap.dev.of_node = np;
	i2c->adap.algo_data = i2c;
	i2c->adap.dev.parent = &pdev->dev;
 
	i2c->dev = &pdev->dev;
 
	spin_lock_init(&i2c->lock);
	init_waitqueue_head(&i2c->wait);
 
	i2c->i2c_restart_nb.notifier_call = rk3x_i2c_restart_notify;
	i2c->i2c_restart_nb.priority = 128;
	ret = register_i2c_restart_handler(&i2c->i2c_restart_nb);
	if (ret) {
		dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
		return ret;
	}
 
	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
	if (IS_ERR(i2c->regs))
		return PTR_ERR(i2c->regs);
 
	/* Try to set the I2C adapter number from dt */
	bus_nr = of_alias_get_id(np, "i2c");
 
	/*
	 * Switch to new interface if the SoC also offers the old one.
	 * The control bit is located in the GRF register space.
	 */
	if (i2c->soc_data->grf_offset >= 0) {
		struct regmap *grf;
 
		grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
		if (IS_ERR(grf)) {
			dev_err(&pdev->dev,
				"rk3x-i2c needs 'rockchip,grf' property\n");
			return PTR_ERR(grf);
		}
 
		if (bus_nr < 0) {
			dev_err(&pdev->dev, "rk3x-i2c needs i2cX alias");
			return -EINVAL;
		}
 
		/* 27+i: write mask, 11+i: value */
		value = BIT(27 + bus_nr) | BIT(11 + bus_nr);
 
		ret = regmap_write(grf, i2c->soc_data->grf_offset, value);
		if (ret != 0) {
			dev_err(i2c->dev, "Could not write to GRF: %d\n", ret);
			return ret;
		}
	}
 
	/* IRQ setup */
	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(&pdev->dev, "cannot find rk3x IRQ\n");
		return irq;
	}
 
	ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
			       0, dev_name(&pdev->dev), i2c);
	if (ret < 0) {
		dev_err(&pdev->dev, "cannot request IRQ\n");
		return ret;
	}
 
	platform_set_drvdata(pdev, i2c);
 
	if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
		/* Only one clock to use for bus clock and peripheral clock */
		i2c->clk = devm_clk_get(&pdev->dev, NULL);
		i2c->pclk = i2c->clk;
	} else {
		i2c->clk = devm_clk_get(&pdev->dev, "i2c");
		i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
	}
 
	if (IS_ERR(i2c->clk)) {
		ret = PTR_ERR(i2c->clk);
		if (ret != -EPROBE_DEFER)
			dev_err(&pdev->dev, "Can't get bus clk: %d\n", ret);
		return ret;
	}
	if (IS_ERR(i2c->pclk)) {
		ret = PTR_ERR(i2c->pclk);
		if (ret != -EPROBE_DEFER)
			dev_err(&pdev->dev, "Can't get periph clk: %d\n", ret);
		return ret;
	}
 
	ret = clk_prepare(i2c->clk);
	if (ret < 0) {
		dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
		return ret;
	}
	ret = clk_prepare(i2c->pclk);
	if (ret < 0) {
		dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
		goto err_clk;
	}
 
	i2c->clk_rate_nb.notifier_call = rk3x_i2c_clk_notifier_cb;
	ret = clk_notifier_register(i2c->clk, &i2c->clk_rate_nb);
	if (ret != 0) {
		dev_err(&pdev->dev, "Unable to register clock notifier\n");
		goto err_pclk;
	}
 
	clk_rate = clk_get_rate(i2c->clk);
	rk3x_i2c_adapt_div(i2c, clk_rate);
 
	ret = i2c_add_adapter(&i2c->adap);
	if (ret < 0) {
		dev_err(&pdev->dev, "Could not register adapter\n");
		goto err_clk_notifier;
	}
 
	dev_info(&pdev->dev, "Initialized RK3xxx I2C bus at %p\n", i2c->regs);
 
	return 0;
 
err_clk_notifier:
	clk_notifier_unregister(i2c->clk, &i2c->clk_rate_nb);
err_pclk:
	clk_unprepare(i2c->pclk);
err_clk:
	clk_unprepare(i2c->clk);
	return ret;
}

 从上面的代码我们可以发现,rk3x_i2c_probe主要做了以下几件事情:

1,通过devm_kzalloc函数为rk3x_i2c结构体分配内存空间;

2,从设备树中获取I2C设备信息并填充rk3x_i2c结构体;

3,使用devm_platform_ioremap_resource函数来映射设备的寄存器资源到内存中;

4,获取并配置中断;

5,使用i2c_add_adapter注册设备;

读写函数

由于rk3x_i2c中的读写函数和该设备关联性较大,不具备通用性,这里以sx1_i2c_write_bye和sx1_i2c_read_byte来给大家进行分析,该函数更具有通用性。

/* Write to I2C device */
int sx1_i2c_write_byte(u8 devaddr, u8 regoffset, u8 value)
{
	struct i2c_adapter *adap;
	int err;
	struct i2c_msg msg[1];
	unsigned char data[2];

	adap = i2c_get_adapter(0);
	if (!adap)
		return -ENODEV;
	msg->addr = devaddr;	/* I2C address of chip */
	msg->flags = 0;
	msg->len = 2;
	msg->buf = data;
	data[0] = regoffset;	/* register num */
	data[1] = value;		/* register data */
	err = i2c_transfer(adap, msg, 1);
	i2c_put_adapter(adap);
	if (err >= 0)
		return 0;
	return err;
}

/* Read from I2C device */
int sx1_i2c_read_byte(u8 devaddr, u8 regoffset, u8 *value)
{
	struct i2c_adapter *adap;
	int err;
	struct i2c_msg msg[1];
	unsigned char data[2];

	adap = i2c_get_adapter(0);
	if (!adap)
		return -ENODEV;

	msg->addr = devaddr;	/* I2C address of chip */
	msg->flags = 0;
	msg->len = 1;
	msg->buf = data;
	data[0] = regoffset;	/* register num */
	err = i2c_transfer(adap, msg, 1);

	msg->addr = devaddr;	/* I2C address */
	msg->flags = I2C_M_RD;
	msg->len = 1;
	msg->buf = data;
	err = i2c_transfer(adap, msg, 1);
	*value = data[0];
	i2c_put_adapter(adap);

	if (err >= 0)
		return 0;
	return err;
}

从上面的代码可以看出,sx1_i2c_write_byte主要完成了以下功能:

1,通过调用i2c_get_adapter(0)函数获取指定索引的I2C适配器对象并赋值给adap变量;

2,初始化一个struct i2c_msg类型的数组msg,该数组包含一个元素用于I2C消息的传输;

3,设置msg结构体中的字段:

    addr: 设备的I2C地址

    flags:传输标志位,此处为0表示写操作;

    len: 要传输的字节数,此处设置为2,即1字节的寄存器地址和1字节的寄存器数据;

    buf:数据缓冲区的指针,用于存储要发送的数据。

4,将要写入的设备寄存器地址和数据分别存储在data数组的第一个和第二个元素中,即data[0]=regoffset;和data[1]=value

5,调用i2c_transfer()函数进行I2C消息传输,将数据写入设备寄存器;

6,使用i2c_put_adapter()函数释放先前获取的I2C适配器对象;

sx1_i2c_read_byte主要完成了以下功能:

1,通过调用i2c_get_adapter(0)函数获取指定索引的I2C适配器对象并赋值给adap变量;

2,初始化一个struct i2c_msg类型的数组msg,该数组包含一个元素用于I2C消息的传输;

3,设置msg结构体中的字段:

  addr: 设备的i2c地址;

  flags:传输标志位,此除位0表示写操作

  len:要传输或接收的字节数;

  buf:数据缓冲区的指针,用于存储要发送或接收的数据

4,将要读取的设备寄存器地址存储在data数组的第一个元素中,即data[0]=regoffset

5,调用i2c_transfer()函数进行I2C消息传输,将数据写入设备寄存器;

6,更改flags字段位I2C_M_RD,表示接收模式(读操作);

7,再次调用i2c_transfer()函数进行I2C消息传输,从设备中读取数据

8,将读取到的数存储在data数组的第一个元素中,即*value=data[0]

9,使用i2c_put_adapter()函数释放先前获取的I2C适配器对象。

对比i2c读和写的过程,大家可能会发现I2C读的过程为什么调用了两次i2c_transfer函数呢?多调用了一次i2c_transfer函数是因为我们在调用i2c_transfer读取数据时,需要先发送要读取的寄存器地址给设备,然后再从设备读取实际的数据。所以第一次使用i2c_transfer发送的信息为要读取的地址信息,第二次将标志位改为读,然后再使用i2c_transfer将从设备返回的信息存储到i2c_adapter中。

I2C驱动中几个重要的结构体

在I2C驱动中,有三个比较重要的结构体用于描述和管理I2C设备和传输操作,下面就这三个结构体的成员以及作用来给大家讲解一下。

i2c_adapter结构体

定义位置:i2c.h

结构体原型:

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	1

	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 irq_domain *host_notify_domain;
	struct regulator *bus_regulator;
};

几个重要的成员:

name:适配器的名称
nr:适配器的编号;
bus_lock和bus_unlock: 用于保护对适配器的并发访问的锁机制;
algo:指向I2C算法结构体的指针,包含了适配器的通信算法,如标准模式、快速模式、高速模式等;

一个i2c_adapter结构体对应一个I2C控制器。相关API:

int i2c_add_adapter(struct i2c_adapter *adapter)----注册一个i2c_adaper,系统分配编号;

int i2c_add_numbered_adapter(struct i2c_adapter *adapter)----注册一个i2c_adapter,自己制定编号

void i2c_del_adapter(struct i2c_adapter *adap)----注销一个i2c_adapter 

i2c_algorithm

位置:include/linux/i2c.h

```
struct i2c_algorithm {
    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 *);
 
#if IS_ENABLED(CONFIG_I2C_SLAVE)
    int (*reg_slave)(struct i2c_client *client);
    int (*unreg_slave)(struct i2c_client *client);
#endif
};

对应一套具体的通信方法,master_xfer: 产生I2C通信时序 

I2C_client结构体

定义位置:i2c.h

结构体原型:

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
#define I2C_CLIENT_PEC		0x04	/* Use Packet Error Checking */
#define I2C_CLIENT_TEN		0x10	/* we have a ten bit chip address */
					/* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE	0x20	/* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY	0x40	/* We want to use I2C host notify */
#define I2C_CLIENT_WAKE		0x80	/* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB		0x9000	/* Use Omnivision SCCB protocol */
					/* Must match I2C_M_STOP|IGNORE_NAK */

	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
	void *devres_group_id;		/* ID of probe devres group	*/
};

几个重要的成员:

flags:标志位,用于指定设备的特性和行为;
addr:设备的I2C地址
adapter:指向i2c_adapter的指针,表示所属的I2C适配器;
driver:指向设备驱动程序的指针,表示设备所使用的驱动;

 i2c_driver结构体

定义位置:i2c.h

结构体原型

struct i2c_driver {
	unsigned int class;

	union {
	/* Standard driver model interfaces */
		int (*probe)(struct i2c_client *client);
		/*
		 * Legacy callback that was part of a conversion of .probe().
		 * Today it has the same semantic as .probe(). Don't use for new
		 * code.
		 */
		int (*probe_new)(struct i2c_client *client);
	};
	void (*remove)(struct i2c_client *client);


	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *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 *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 *client, struct i2c_board_info *info);
	const unsigned short *address_list;
	struct list_head clients;

	u32 flags;
};

几个重要的成员:

driver:是一个struct device_driver结构体,用于向Linux设备模型注册驱动程序;
probe和remove:指向探测和移除设备的函数指针,通过这两个函数,驱动程序可以在发现匹配的设备时执行初始化操作,并在设备被移除时执行清理操作
id_table:用于指定驱动程序支持的I2C设备ID列表,以便匹配对应的设备。

int i2c_add_driver(struct i2c_driver *driver)---注册一个i2c_driver

void i2c_del_driver(struct i2c_driver *driver)---注销一个i2c_driver 

 这些结构体共同构成了Linux内核中的I2C驱动框架,提供了对I2C总线、适配器和设备的抽象和管理功能。开发者可以基于这些结构体来编写自己的I2C驱动程序,并实现与I2C设备的通信和控制。所以我们的工作就是填充这些结构体,然后调用对应的接口把我们填充好的结构体传递给I2C设备器驱动和核心驱动,从而完成设备的初始化和读写操作。

I2C总线驱动分析

嵌入式Linux驱动开发——IIC驱动(基于野火I.MX6ULL)_带iic的led driver- 

i2c总线注册

drivers/i2c/i2c-core-base.c

static int __init i2c_init(void)
{
    int retval;
    ...
    retval = bus_register(&i2c_bus_type);
    if (retval)
        return retval;
 
    is_registered = true;
    ...
    retval = 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;
    ...
}

I2C总线定义

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

 I2C设备和I2C驱动匹配规则

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    struct i2c_client    *client = i2c_verify_client(dev);
    struct i2c_driver    *driver;
 
 
    /* Attempt an OF style match */
    if (i2c_of_match_device(drv->of_match_table, client))
        return 1;
 
    /* Then ACPI style match */
    if (acpi_driver_match_device(dev, drv))
        return 1;
 
    driver = to_i2c_driver(drv);
 
    /* Finally an I2C match */
    if (i2c_match_id(driver->id_table, client))
        return 1;
 
    return 0;
}

of_driver_match_device:设备树匹配方式,比较I2C设备节点的compatible属性和of_device_id中的compatible属性。

acpi_driver_match_device:ACPI匹配方式

i2c_match_id:i2c总线传统匹配方式,比较i2c设备名字和i2c驱动的id_table->name字段是否相等。 

总结

I2C驱动的学习有一个特点:弄懂比较难,会用比较简单,这是因为有很多的有难度的内容以及和协议相关的内容都以及被Linux或者芯片厂封装好了,我们需要做的就是使用他们提供的这些接口完成指定设备的读写操作,但我们的学习不能止步于此,所以我们不但要会用,还要知其然知其所以然。

PCIE驱动

PCIE总线主控端需要配置一个reset脚用来开启PCIE设备:

麒麟适配mysql_单片机_11

一般PCIE的发送脚和接收脚芯片厂家都已经配置好了,例如:

麒麟适配mysql_麒麟适配mysql_12

PCI和PCIE的区别

SDIO总线

MMC总线