15.Linux I2C驱动实验

I2C协议

I2C使用两条线在主控制器和从控制器之间进行数据通信。一条是SCL(串行时钟线),另一种是SDA(串行数据线),这两条数据线需要接上拉电阻,总线空闲时SCL和SDA处于高电平。
I2C是支持多从机的,就是说一个I2C控制器下可以挂载多个I2C从设备,这些不同的I2C从设备有不同的器件地址,这样I2C控制器就可以通过I2C设备的器件地址访问指定的I2C设备了。
在这里插入图片描述
上图中SDA和SCL这两根线必须要接一个上拉电阻,一般是4.7K,其余的I2C从器件都挂载到SDA和SCL这两根线上,这样就可以通过SDA和SCL这两根线来访问多个I2C设备了。

起始位

I2C起始位就是I2C通信起始标志,通过这个起始位就可以告诉I2C从机,"我"要开始进行I2C通信了。在SCL为高电平的时候,SDA出现下降沿就表示为起始位
在这里插入图片描述

停止位

停止位就是停止I2C通信的标志位,和起始位的功能相反。在SCL为高电平时,SDA出现上升沿就表示为停止位
在这里插入图片描述

数据传输

I2C总线在数据传输的时候要保证在SCL高电平期间,SDA上的数据稳定,因此SDA上的数据只能在SCL为低电平期间发生
在这里插入图片描述

应答信号

当I2C主机发送完8位数据以后会将SDA设置为输入状态,等待I2C从机应答,也就是等到I2C从机告诉主机他接收到数据了。应带信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完8位数据以后会紧跟着的一个时钟信号就是给应答信号使用的。从机通过SDA拉低来表示发出应答信号,表示通信成功,否则失败。
下图是我抓的IIC波形。
在这里插入图片描述

I2C写时序

在这里插入图片描述

  1. 开始信号
  2. 发送I2C设备地址,每个I2C器件都有一个设备地址,通过发送具体的设备地址来决定要访问哪个I2C器件。
  3. I2C器件地址后面跟着一个读写位,0表示写操作,1表示读操作。
  4. 从机发送的ACK应答信号。
  5. 发送要写入的寄存器地址
  6. 从机发送的ACK应答信号
  7. 发送要写入的寄存器数据
  8. 从机发送的ACK应答信号
  9. 停止信号

I2C读时序

在这里插入图片描述

  1. 主机发送起始信号
  2. 主机发送读取的I2C从设备地址
  3. 读写控制位,因为是向I2C从设备发送数据,因此是写信号
  4. 从机发送的ACK应答信号
  5. 主机发送要读取的寄存器地址
  6. 从机发送ACK信号
  7. 重新发送start信号
  8. 重新发送要读取的I2C从设备地址
  9. 读写控制位,这里是读信号,表示接下来是从I2C从设备里面读数据。
  10. 从机发送的ACK应答信号
  11. 从I2C器件中读取到的数据
  12. 主机发出NO ACK信号,表示读取完成,不需要从机再发送ACK信号了。
  13. 主机发出STOP信号,停止I2C通信。

linux I2C驱动框架简介

Linux I2C驱动主要分为I2C总线驱动I2C设备驱动
对于I2C总线驱动一旦编写完成就不需要再做修改了,其他的I2C设备直接调用主机驱动提供的API函数完成读写操作即可。这个正好符合Linux的驱动分离与分层的思想,因此Linux内核也将I2C驱动分为两部分:
I2C总线驱动:I2C总线驱动就是SOC的I2C控制器驱动,也叫做I2C适配器驱动/I2C主机驱动。
I2C设备驱动:I2C设备驱动就是针对具体的I2C设备二编写的驱动。
在这里插入图片描述
不同的平台(比如:IMX,瑞星微等)I2C主机驱动是不一样的,Linux针对每一种平台,都有其特定的I2C控制器驱动,I2C主机驱动是由芯片厂家编写好的,而I2C设备驱动一般是由该I2C设备厂家编写的。我们使用I2C外设时,一般只需要去移植该I2C器件的驱动就可以了。

I2C总线驱动

在这里插入图片描述
我们可以发现,I2C器件是符合平台总线驱动模型这种框架的。
事实上,I2C驱动框架是platform设备驱动框架的一种,platform可以用于没有总线概念的设备,从而虚拟出来一种总线——platform总线,从而构成平台总线模型,但是I2C是有自己实际的I2C总线的,就不需要虚拟了。

重要数据结构

i2c_adapter:Linux内核将SOC的I2C适配器(控制器)抽象成i2c_adapter结构体。

struct i2c_adapter
{
	499 struct module *owner;
	500 unsigned int class; /* classes to allow probing for */
	501 const struct i2c_algorithm *algo; /* 总线访问算法 */
	502 void *algo_data;
	503
	504 /* data fields that are valid for all devices */
	505 struct rt_mutex bus_lock;
	506
	507 int timeout; /* in jiffies */
	508 int retries;
	509 struct device dev; /* the adapter device */
	510
	511 int nr;
	512 char name[48];
	513 struct completion dev_released;
	514
	515 struct mutex userspace_clients_lock;
	516 struct list_head userspace_clients;
	517
	518 struct i2c_bus_recovery_info *bus_recovery_info;
	519 const struct i2c_adapter_quirks *quirks;
};

i2c_algorithm:上面的第501行,i2c_algorithm类型的指针变量algo,对于一个I2C适配器,重要的功能就是提供读写API函数给I2C设备驱动去完成读写操作的。i2c_algorithm就是I2C适配器与I2C设备进行通信的方法。

struct i2c_algorithm
{
......
	398 int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
	400 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
	401 unsigned short flags, char read_write,
	402 u8 command, int size, union i2c_smbus_data *data);
	403
	404 /* To determine what the adapter supports */
	405 u32 (*functionality) (struct i2c_adapter *);
......
};

master_xfer :第 398 行,master_xfer 就是 I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。
smbus_xfer :第 400 行,smbus_xfer 就是 SMBUS 总线的传输函数。

向内核添加一个I2C适配器

综上所述:I2C总线驱动的主要工作就是初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数。完成以后通过i2c_add_numbered_adapter或者i2c_add_adapter这两个函数向系统注册设置好i2c_adapter,这两个函数原型如下:

int i2c_add_adapter(struct i2c_adapter *adapter) //使用动态总线号
int i2c_add_numbered_adapter(struct i2c_adapter *adap) //使用静态总线号
参数描述
adapter 或 adap要添加到 Linux 内核中的 i2c_adapter,也就是 I2C 适配器。
返回值0,成功;负值,失败。

从内核删除一个I2C适配器

void i2c_del_adapter(struct i2c_adapter * adap)
参数描述
adap要删除的 I2C 适配器。
返回值无。

I2C设备驱动

重要数据结构

I2C 设备驱动重点关注两个数据结构:i2c_clienti2c_driver,根据总线、设备和驱动模型,I2C 总线上一小节已经讲了。还剩下设备和驱动,i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容,类似于 platform_driver
i2c_client 结构体:一个设备对应一个i2c_client,每检测到一个i2c设备就会给这个I2C设备分配一个i2c_client.

struct i2c_client
{
	218 unsigned short flags; /* 标志 */
	219 unsigned short addr; /* 芯片地址,7 位,存在低 7 位*/
	......
	222 char name[I2C_NAME_SIZE]; /* 名字 */
	223 struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
	224 struct device dev; /* 设备结构体 */
	225 int irq; /* 中断 */
	226 struct list_head detected;
	......
};

i2c_driver 结构体i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容。

struct i2c_driver
{
	162 unsigned int class;
	163
	164 /* Notifies the driver that a new bus has appeared. You should 
	165 * avoid using this, it will be removed in a near future.
	166 */
	167 int (*attach_adapter)(struct i2c_adapter *) __deprecated;
	168
	169 /* Standard driver model interfaces */
	170 int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	171 int (*remove)(struct i2c_client *);
	172
	173 /* driver model interfaces that don't relate to enumeration */
	174 void (*shutdown)(struct i2c_client *);
	175
	176 /* Alert callback, for example for the SMBus alert protocol.
	177 * The format and meaning of the data value depends on the 
	178 * protocol.For the SMBus alert protocol, there is a single bit 
	179 * of data passed as the alert response's low bit ("event 
	180 flag"). */
	181 void (*alert)(struct i2c_client *, unsigned int data);
	182
	183 /* a ioctl like command that can be used to perform specific 
	184 * functions with the device.
	185 */
	186 int (*command)(struct i2c_client *client, unsigned int cmd,
	void *arg);
	187
	188 struct device_driver driver;
	189 const struct i2c_device_id *id_table;
	190
	191 /* Device detection callback for automatic device creation */
	192 int (*detect)(struct i2c_client *, struct i2c_board_info *);
	193 const unsigned short *address_list;
	194 struct list_head clients;
};

向内核注册i2c_driver

对于我们 I2C 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driveri2c_driver 注册函数为i2c_register_driver,此函数原型如下:

int i2c_register_driver(struct module *owner,  struct i2c_driver *driver)
参数描述
owner一般为 THIS_MODULE。
driver要注册的 i2c_driver。
返回值0,成功;负值,失败。

另外 i2c_add_driver 也常常用于注册 i2c_driveri2c_add_driver 是一个宏。i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册的 i2c_driver

587 #define i2c_add_driver(driver) \
588 		i2c_register_driver(THIS_MODULE, driver)

注销i2c_driver

void i2c_del_driver(struct i2c_driver *driver)
参数描述
driver要注销的 i2c_driver。
返回值无。

I2C设备和驱动匹配的过程

I2C设备和驱动匹配的过程是由I2C核心来完成的,driver/i2c/i2c-core.c就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的API函数。
1、i2c_adapter 注册/注销函数

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)

2、i2c_driver 注册/注销函数

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)

I2C总线

设备和驱动的匹配是由I2C总线完成的,I2C总线的数据结构为i2c_bus_type,定义在driver/i2c/i2c-core.c文件。

736 struct bus_type i2c_bus_type = {
	737 .name = "i2c",
	738 .match = i2c_device_match,
	739 .probe = i2c_device_probe,
	740 .remove = i2c_device_remove,
	741 .shutdown = i2c_device_shutdown,
742 };

.match就是I2C总线的设备和驱动匹配函数,在这里就是i2c_device_match这个函数,此函数内容如下:

static int i2c_device_match(struct device *dev, struct device_driver  *drv)
{
	459 struct i2c_client *client = i2c_verify_client(dev);
	460 struct i2c_driver *driver;
	461
	462 if (!client)
	463 return 0;
	464
	465 /* Attempt an OF style match */
	466 if (of_driver_match_device(dev, drv))
	467 return 1;
	468
	469 /* Then ACPI style match */
	470 if (acpi_driver_match_device(dev, drv))
	471 return 1;
	472
	473 driver = to_i2c_driver(drv);
	474 /* match on an id table if there is one */
	475 if (driver->id_table)
	476 return i2c_match_id(driver->id_table, client) != NULL;
	477
	478 return 0;
}

驱动和设备的匹配根据compatable属性是否一致进行匹配的。
如果采用设备树的方式来描述设备信息:
在这里插入图片描述在这里插入图片描述
匹配成功之后probe函数就会调用,然后则probe函数中去获取I2C设备信息。

984 irq = platform_get_irq(pdev, 0);//数获取中断号
......
990 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//从设备树中获取 I2C1 控制器寄存器物理基地址
991 base = (&pdev->dev, res);//映射为虚拟地址
...... 
1008 i2c_imx->adapter.owner = THIS_MODULE;
1009 i2c_imx->adapter.algo = &i2c_imx_algo; //通信方法
1010 i2c_imx->adapter.dev.parent = &pdev->dev;
1011 i2c_imx->adapter.nr = pdev->id;
1012 i2c_imx->adapter.dev.of_node = pdev->dev.of_node;
1013 i2c_imx->base = base;
......
1028 ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
1029 IRQF_NO_SUSPEND, pdev->name, i2c_imx); //注册I2C控制器中断,中断服务函数为i2c_imx_isr
1054 ret = i2c_add_numbered_adapter(&i2c_imx->adapter); //向内核注册I2C控制器驱动,

主要看一下i2c_imx_xfer函数,因为最终就是通过此函数来完成与I2C设备通信的。

static int i2c_imx_xfer(struct i2c_adapter *adapter,struct i2c_msg *msgs, int num)
{
	891 unsigned int i, temp;
	892 int result;
	893 bool is_lastmsg = false;
	894 struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
	895
	896 dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);
	897
	898 /* Start I2C transfer */
	899 result = i2c_imx_start(i2c_imx);
	900 if (result)
	901 goto fail0;
	902
	903 /* read/write data */
	904 for (i = 0; i < num; i++) {
	905 if (i == num - 1)
	906 is_lastmsg = true;
	907
	908 if (i) {
	909 dev_dbg(&i2c_imx->adapter.dev,
	910 "<%s> repeated start\n", __func__);
	911 temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
	912 temp |= I2CR_RSTA;
	913 imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
	914 result = i2c_imx_bus_busy(i2c_imx, 1);
	915 if (result)
	916 goto fail0;
	917 }
	918 dev_dbg(&i2c_imx->adapter.dev,
	919 "<%s> transfer message: %d\n", __func__, i);
	920 /* write/read data */
	......
	938 if (msgs[i].flags & I2C_M_RD)
	939 result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
	940 else {
	941 if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
	942 result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
	943 else
	944 result = i2c_imx_write(i2c_imx, &msgs[i]);
	945 }
	946 if (result)
	947 goto fail0;
	948 }
	949
	950 fail0:
	951 /* Stop I2C transfer */
	952 i2c_imx_stop(i2c_imx);
	953
	954 dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n",
	__func__,
	955 (result < 0) ? "error" : "success msg",
	956 (result < 0) ? result : num);
	957 return (result < 0) ? result : num;
}

i2c_imx_start:第 899 行,调用 i2c_imx_start 函数开启 I2C 通信。
i2c_imx_read:第 939 行,如果是从 I2C 设备读数据的话就调用 i2c_imx_read 函数。
i2c_imx_dma_write/i2c_imx_write第 941~945 行,向 I2C 设备写数据,如果要用 DMA 的话就使用 i2c_imx_dma_write 函数来完成写数据。如果不使用 DMA 的话就使用 i2c_imx_write 函数完成写数据。
i2c_imx_stop :第 952 行,I2C 通信完成以后调用 i2c_imx_stop 函数停止 I2C 通信。
i2c_imx_starti2c_imx_readi2c_imx_writei2c_imx_stop 这些函数就是 I2C 寄存器的具体操作函数。

设备数据收发API

i2c_transfer

i2c_transferi2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数。

int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)
参数描述
adap所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。
msgsI2C 要发送的一个或多个消息。
num消息数量,也就是 msgs 的数量。
返回值负值,失败,其他非负值,发送的 msgs 数量。

我们重点来看一下,msgs这个参数,这是一个i2c_msg类型的指针参数,Linux内核使用i2c_msg结构体来描述一个消息。

68 struct i2c_msg 
{
		69 __u16 addr; /* 从机地址 */
		70 __u16 flags; /* 标志 */
		71 #define I2C_M_TEN 0x0010
		72 #define I2C_M_RD 0x0001
		73 #define I2C_M_STOP 0x8000
		74 #define I2C_M_NOSTART 0x4000
		75 #define I2C_M_REV_DIR_ADDR 0x2000 
		76 #define I2C_M_IGNORE_NAK 0x1000 
		77 #define I2C_M_NO_RD_ACK 0x0800
		78 #define I2C_M_RECV_LEN 0x0400
		79 __u16 len; /* 消息(本 msg)长度 */
		80 __u8 *buf; /* 消息数据 */
};

使用i2c_transfer函数发送之前要构建好I2C_msg,使用i2c_tranfer进行I2C数据收发的代码如下:

1 /* 设备结构体 */
2 struct xxx_dev {
4 void *private_data; /* 私有数据,一般会设置为 i2c_client */
5 };
6 
7 /*
8 * @description : 读取 I2C 设备多个寄存器数据
9 * @param – dev : I2C 设备
10 * @param – reg : 要读取的寄存器首地址
11 * @param – val : 读取到的数据
12 * @param – len : 要读取的数据长度
13 * @return : 操作结果
14 */
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,int len)
{
		17 int ret;
		18 struct i2c_msg msg[2];
		19 struct i2c_client *client = (struct i2c_client *)
		dev->private_data;
		20
		21 /* msg[0],第一条写消息,发送要读取的寄存器首地址 */
		22 msg[0].addr = client->addr; /* I2C 器件地址 */
		23 msg[0].flags = 0; /* 标记为发送数据 */
		24 msg[0].buf = &reg; /* 读取的首地址 */
		25 msg[0].len = 1; /* reg 长度 */
		26
		27 /* msg[1],第二条读消息,读取寄存器数据 */
		28 msg[1].addr = client->addr; /* I2C 器件地址 */
		29 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
		30 msg[1].buf = val; /* 读取数据缓冲区 */
		31 msg[1].len = len; /* 要读取的数据长度 */
		32
		33 ret = i2c_transfer(client->adapter, msg, 2);
		34 if(ret == 2) {
		35 ret = 0;
		36 } else {
		37 ret = -EREMOTEIO;
		38 }
		39 return ret;
}
		
		42 /*
		43 * @description : 向 I2C 设备多个寄存器写入数据
		44 * @param – dev : 要写入的设备结构体
		45 * @param – reg : 要写入的寄存器首地址
		46 * @param – buf : 要写入的数据缓冲区
		47 * @param – len : 要写入的数据长度
		48 * @return : 操作结果
		49 */
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,u8 len)
{
		52 u8 b[256];
		53 struct i2c_msg msg;
		54 struct i2c_client *client = (struct i2c_client *)
		dev->private_data;
		
		56 b[0] = reg; /* 寄存器首地址 */
		57 memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */
		
		59 msg.addr = client->addr; /* I2C 器件地址 */
		60 msg.flags = 0; /* 标记为写数据 */
	
		62 msg.buf = b; /* 要发送的数据缓冲区 */
		63 msg.len = len + 1; /* 要发送的数据长度 */
	
		65 return i2c_transfer(client->adapter, &msg, 1);
}

第 15~40 行,xxx_read_regs 函数用于读取 I2C 设备多个寄存器数据。第 18 行定义了一个i2c_msg 数组,2 个数组元素,因为 I2C 读取数据的时候要先发送要读取的寄存器地址,然后再读取数据,所以需要准备两个 i2c_msg一个用于发送寄存器地址,一个用于读取寄存器值。对于 msg[0],将 flags 设置为 0,表示写数据。msg[0]的 addr 是 I2C 设备的器件地址,msg[0]的 buf成员变量就是要读取的寄存器地址。对于 msg[1],将 flags 设置为 I2C_M_RD,表示读取数据。msg[1]的 buf 成员变量用于保存读取到的数据,len 成员变量就是要读取的数据长度。调用i2c_transfer 函数完成 I2C 数据读操作。
第 50~66 行,xxx_write_regs 函数用于向 I2C 设备多个寄存器写数据,I2C 写操作要比读操作简单一点,因此一个 i2c_msg 即可。数组 b 用于存放寄存器首地址和要发送的数据,第 59 行设置 msg 的 addr 为 I2C 器件地址。第 60 行设置 msg 的 flags 为 0,也就是写数据。第 62 行设置要发送的数据,也就是数组 b。第 63 行设置 msg 的 len 为 len+1,因为要加上一个字节的寄存器地址。最后通过 i2c_transfer 函数完成向 I2C 设备的写操作。

i2c_master_send和i2c_master_recv

这两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer
发送函数 i2c_master_send

int i2c_master_send(const struct i2c_client *client, const char *buf,int count)
参数描述
clientI2C 设备对应的 i2c_client。
buf要发送的数据。
count要发送的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
返回值负值,失败,其他非负值,发送的字节数。
int i2c_master_recv(const struct i2c_client *client,  char *buf,int count)
参数描述
clientI2C 设备对应的 i2c_client。
buf要接收的数据。
count要接收的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
返回值负值,失败,其他非负值,发送的字节数。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

<( ̄︶ ̄)Okay.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值