常用驱动适配总结

news2024/11/18 6:44:26

I2C驱动

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

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

触摸屏使用的是i2c总线:

 数据线和时钟线

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

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

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

中断脚和rst脚

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

  设备节点调试

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

I2C体系结构

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

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

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

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

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设备编写,实现了对设备的初始化、读写数据等操作。

具体设备驱动分析

当我们需编写具体的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-CSDN博客

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设备:

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

PCI和PCIE的区别

SDIO总线

MMC总线

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1462412.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WebRTC最新版报错解决:city.wav:missing and no known rule to make it (二十六)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只…

Chrome关闭时出现弹窗runtime error c++R6052,且无法关闭

环境&#xff1a; Chrome 版本121 Win10专业版 问题描述&#xff1a; Chrome关闭时出现弹窗runtime error cR6052&#xff0c;且无法关闭 解决方案&#xff1a; 1.任务管理器打开&#xff0c;强制结束进程 2.再次打开谷歌浏览器&#xff0c;打开设置关于Chrome&#xff0…

Chrome插件精选 — 缓存清理

Chrome实现同一功能的插件往往有多款产品&#xff0c;逐一去安装试用耗时又费力&#xff0c;在此为某一类型插件挑选出比较好用的一款或几款&#xff0c;尽量满足界面精致、功能齐全、设置选项丰富的使用要求&#xff0c;便于节省一个个去尝试的时间和精力。 1. Chrome清理大师…

MySQL|MySQL基础(求知讲堂-学习笔记【详】)

MySQL基础 目录 MySQL基础一、 MySQL的结构二、 管理数据库1&#xff09;查询所有的数据库2&#xff09;创建数据库3&#xff09;修改数据库的字符编码4&#xff09;删除数据库5&#xff09;切换操作的数据库 三、表的概念四、字段的数据类型4.1 整型4.2 浮点型(float和double)…

机试笔记-害死人不偿命的(3n+1)猜想

来自 “胡凡《算法笔记》” 比较容易理解的题目 应该要想到用while&#xff08;&#xff09;循环&#xff0c;因为题设中给出了当 n 1的时候脱离计算的要求&#xff0c;所以以后计算的过程中应该想到用while&#xff08;&#xff09;的循环作为测试条件

【linux】【Shell】Linux基础命令集

目录 一、常用操作命令 二、编辑命令 三、挂载磁盘命令 一、常用操作命令 1、cd :更改文件目录命令 1.切换到主目录cd2.切换到目录/tmpcd /tmp3.切换到当前目录 dir目录cd dir4.切换到根目录cd /5.切换到上一级目录cd ..6.切换到二级目录cd ../..7.切换到主目录&#xff0…

打造纯Lua组件化开发模式:Unity xLua框架详解

在传统的Unity开发中&#xff0c;通常会使用C#来编写游戏逻辑和组件。但是&#xff0c;随着Lua在游戏开发中的应用越来越广泛&#xff0c;我们可以将游戏逻辑和组件完全用Lua来实现&#xff0c;实现纯Lua的组件化开发模式。这样做的好处是可以更加灵活地修改游戏逻辑&#xff0…

【算法与数据结构】1020、130、LeetCode飞地的数量 被围绕的区域

文章目录 一、1020、飞地的数量二、130、被围绕的区域三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、1020、飞地的数量 思路分析&#xff1a;博主认为题目很抽象&#xff0c;非常难理解。想了好久&#xff0c;要理解…

行测:国考省考行测:图形推理,四面体,正六面体的图形推理方法,箭头唯一法

国考省考行测&#xff1a;图形推理 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要的还是申论和行测&#xff0c;所以大家认真准备吧&#xff0c;我讲一起屡屡申论和行测的重要知识点 遇到…

(十二)【Jmeter】线程(Threads(Users))之tearDown 线程组

简述 操作路径如下: 作用:在正式测试结束后执行清理操作,如关闭连接、释放资源等。配置:设置清理操作的采样器、执行顺序等参数。使用场景:确保在测试结束后应用程序恢复到正常状态,避免资源泄漏或对其他测试的影响。优点:提供清理操作,确保测试环境的整洁和可重复性…

水经注下载注记地图, mars3d加载底图

使用 水经微图 &#xff08;公司提供的&#xff0c;需付费&#xff0c;我也没有这个东西&#xff09;下载注记地图&#xff1b; 1、选择下载 选择区域&#xff1a; 根据需求进行选择&#xff0c;两边都可以选择&#xff0c;看个人喜欢&#xff1b;这里以澳门为演示 选择地图…

从零开始手写mmo游戏从框架到爆炸(二十一)— 战斗系统二

导航&#xff1a;从零开始手写mmo游戏从框架到爆炸&#xff08;零&#xff09;—— 导航-CSDN博客 上一章&#xff08;从零开始手写mmo游戏从框架到爆炸&#xff08;二十&#xff09;— 战斗系统一-CSDN博客&#xff09;我们只是完成了基本的战斗&#xff0c;速度属性并没有…

学习数仓工具 dbt

DBT 是一个有趣的工具&#xff0c;它通过一种结构化的方式定义了数仓中各种表、视图的构建和填充方式。 dbt 面相的对象是数据开发团队&#xff0c;提供了如下几个最有价值的能力&#xff1a; 支持多种数据库通过 select 来定义数据&#xff0c;无需编写 DML构建数据时&#…

Facebook的未来蓝图:数字社交的下一个篇章

在数字化时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分。而在众多的社交媒体平台中&#xff0c;Facebook一直处于领先地位&#xff0c;不断探索着数字社交的新领域和新形式。随着科技的不断发展和社会的不断变革&#xff0c;Facebook正在谱写着数字社交的未…

Vue路由缓存问题

路由缓存问题的产生 VueRouter允许用户在页面中创建多个视图&#xff08;多级路由&#xff09;&#xff0c;并根据路由参数来动态的切换视图。使用带参数的路由时&#xff0c;相同的组件实例将被重复使用。因为两个路由都渲染同一个组件&#xff0c;比起销毁再创建&#xff0c;…

谷歌掀桌子!开源Gemma:可商用,性能超过Llama 2!

2月22日&#xff0c;谷歌在官网宣布&#xff0c;开源大语言模型Gemma。 Gemma与谷歌最新发布的Gemini 使用了同一架构&#xff0c;有20亿、70亿两种参数&#xff0c;每种参数都有预训练和指令调优两个版本。 根据谷歌公布的测试显示&#xff0c;在MMLU、BBH、GSM8K等主流测试…

数据结构·顺序表

1数据结构简介 学习数据结构与算法之前&#xff0c;一般是先学数据结构&#xff0c;方便之后学习算法&#xff0c;那么数据结构拆开介绍&#xff0c;就是数据 和 结构&#xff0c;数据&#xff0c;生活中到处都是&#xff0c;结构&#xff0c;就是数据存储的方式&#xff0c;即…

React 事件处理 ( this问题 参数传递 ref)

React事件的命名采用小驼峰方式&#xff08;cameCase&#xff09;,而不是小写 使用JSX语法时你需要传入一个函数作为事件处理函数&#xff0c;而不是一个字符串 你不能通过返回false 的方式阻止默认行为。你必须显示式的使用preventDefault 1 this 需要谨慎对待JSX回调函数中的…

第13讲实现自定义logout处理

默认logout请求实现是有状态的&#xff0c;返回到login请求页面&#xff1b;我们现在是前后端分离处理&#xff0c;所以需要自定义实现logout 新建JwtLogoutSuccessHandler /*** 自定义Logout处理* author java1234_小锋 &#xff08;公众号&#xff1a;java1234&#xff09;…

camunda源代码编译运行(二):构建并运行camunda源代码工程

接上一篇文章&#xff1a;camunda源代码编译运行&#xff08;一&#xff09;&#xff1a;下载编译camunda源代码 Camunda 7.19源代码一共有178个maven工程和1个angular前端工程&#xff0c;这么多工程中包括了大量的QA测试包、JDK不同版本适配&#xff08;比如&#xff1a;Jav…