ZYNQ Linux I2C总线框架笔记


IIC简介

1.起始位

在 SCL 为高电平的时候, SDA 出现下降沿就表示为起始位
在这里插入图片描述

2.停止位

在 SCL 位高电平的时候, SDA出现上升沿就表示为停止位
在这里插入图片描述

3.数据传输

要保证在 SCL 高电平期间, SDA 上的数据稳定,因此 SDA 上的数据变化只能在 SCL 低电平期间发生
在这里插入图片描述

4.应答信号

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

IIC写时许

在这里插入图片描述
1)、开始信号。
2)、发送 I2C 设备地址,每个 I2C 器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个 I2C 器件。这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为1 的话表示这是一个读操作,为 0 的话表示这是一个写操作。
3)、 I2C 器件地址后面跟着一个读写位,为 0 表示写操作,为 1 表示读操作。
4)、从机发送的 ACK 应答信号。
5)、重新发送开始信号。
6)、发送要写写入数据的寄存器地址。
7)、从机发送的 ACK 应答信号。
8)、发送要写入寄存器的数据。
9)、从机发送的 ACK 应答信号。
10)、停止信号。

IIC读时许

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

Linux I2C 总线框架简介

I2C 主机驱动,I2C 设备驱动
I2C 主机驱动就是 SoC 的 I2C 控制器对应的驱动程序
设备驱动就是挂在总线下的具体设备对应的驱动程序,比如eeprom、触摸IC、传感器IC
于主机驱动来说,一旦编写完成就不需要再做修改,其他的 I2C 设备直接调用主机驱动提供的 API 函数完成
读写操作即可。


一、Linux I2C驱动

I2C 总线驱动

一般 SoC 的 I2C 总线驱动都是由半导体厂商编写的,不需要用户去编写。因此只要专注于 I2C设备驱动。

I2C 设备驱动

总线、设备、驱动模型

设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver

i2c_client 用于描述 I2C 总线下的设备
2c_driver 用于描述 I2C 总线下的设备驱动

i2c_client 结构体

一个 I2C 总线下的从设备对应一个 i2c_client 结构体变量
每检测到一个 I2C 从设备就会给这个设备分配一个 i2c_client。
i2c_client 结构体定义在 include/linux/i2c.h 文件中

struct i2c_client {
	unsigned short flags; // 标志
	unsigned short addr; // 芯片地址,7 位,存在低 7 位

	char	 name[I2C_NAME_SIZE]; // 设备名字
	struct	 i2c_adapter *adapter; // 对应的 I2C 适配器
	struct	 device dev; // 内置 device 结构体
	int		 irq; // 中断
	struct	 list_head detected;
	
	#if IS_ENABLED(CONFIG_I2C_SLAVE)
		i2c_slave_cb_t slave_cb; /* callback for slave mode */
	#endif
}

i2c_driver 结构体
i2c_driver 结构体定义在 include/linux/i2c.h 文件中

struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared. You should avoid
	* using this, it will be removed in a near future.
	*/
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;

183	/* Standard driver model interfaces */
184	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
185	int (*remove)(struct i2c_client *);

	/* New driver model interface to aid the seamless removal of the
	* current probe()'s, more commonly unused than used second parameter.
	*/
	int (*probe_new)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration */
	void (*shutdown)(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").
	* 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 *, 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);

210	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;

	bool disable_i2c_core_irq_mapping;
}

184 行,当 I2C 设备和驱动匹配成功以后 probe 函数就会执行
210 行, device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。

当构建完成以后需要向 I2C 子系统注册这个 i2c_driver。 i2c_driver 注册使用
i2c_add_driver

 /* use a define to avoid include chaining to get THIS_MODULE */
 #define i2c_add_driver(driver) \
 			i2c_register_driver(THIS_MODULE, driver)

只有一个参数,就是要注册的 i2c_driver

i2c_add_driver 是一个宏,调用了i2c_register_drive

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

注销 I2C 设备驱动 i2c_del_driver 函数

void i2c_del_driver(struct i2c_driver *driver)
driver:要注销的 i2c_driver

i2c_driver 注册示例代

/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 函数具体程序 */
	return 0;
}

/* i2c 驱动的 remove 函数 */
static int ap3216c_remove(struct i2c_client *client)
{
	/* 函数具体程序 */
	return 0;
}

/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx" },
	{ /* Sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
	.probe = xxx_probe,
	.remove = xxx_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "xxx",
		.of_match_table = xxx_of_match,
	},
	.id_table = xxx_id,
	};

/* 驱动入口函数 */
static int __init xxx_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&xxx_driver);
	return ret;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
	i2c_del_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

ZYNQ I2C 适配器驱动

在内核源码 arch/arm/boot/dts/zynq-7000.dtsi 设备树中I2C节点

i2c0: i2c@e0004000 {
	compatible = "cdns,i2c-r1p10";
	status = "disabled";
	clocks = <&clkc 38>;
	interrupt-parent = <&intc>;
	interrupts = <0 25 4>;
	reg = <0xe0004000 0x1000>;
	#address-cells = <1>;
	#size-cells = <0>;
};

I2C 设备驱动编写流程

在设备树下创建相应节点

&i2c0 {
	clock-frequency = <100000>;

	rtc@51 {
		compatible = "nxp,pcf8563";
		reg = <0x51>;
	};
};

&i2c0 引用 i2c节点,clock-frequency通讯速率 100KHz
rtc@51 在i2c节点下添加了一个rtc@51的子节点
compatible 匹配驱动 reg 设置器件地址

I2C 设备数据收发处理

I2C 设备寄存器进行读写操作 i2c_transfer
i2c_transfer函数最终会调用I2C 适配器中 cdns_i2c_algo

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

Linux 内核使用 i2c_msg 在这里插入代码片结构体来描述一个消息。 i2c_msg 结构体定义在 include/uapi/linux/i2c.h

struct i2c_msg {
	__u16 addr; /* 从机地址 */
	__u16 flags; /* 标志 */
	#define I2C_M_RD 0x0001 /* read data, from slave to master */
	/* I2C_M_RD is guaranteed to be 0x0001! */
	#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
	#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
	#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
	#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
	#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
	__u16 len; /* 消息(数据)长度 */
	__u8 *buf; /* 消息(数据)地址 */
};

在使用i2c_transfer 函数发送数据之前要先构建好 i2c_msg

/* 设备结构体 */
struct xxx_dev {
 ......
 void *private_data; /* 私有数据,一般会设置为 i2c_client */
};

static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	/* msg[0] 发送要读取的寄存器首地址 */
	msg[0].addr = client->addr; /* I2C 器件地址 */
	msg[0].flags = 0; /* 标记为发送数据 */
	msg[0].buf = &reg; /* 读取的首地址 */
	msg[0].len = 1; /* reg 长度 */
	
	/* msg[1] 读取寄存器数据 */
	msg[1].addr = client->addr; /* I2C 器件地址 */
	msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
	msg[1].buf = val; /* 读取数据缓冲区 */
	msg[1].len = len; /* 要读取的数据长度 */
	
	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		ret = -EREMOTEIO;
	}
	return ret;
}

static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_
	b[0] = reg; /* 寄存器首地址 */
	memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到*/
	msg.addr = client->addr; /* I2C 器件地址 */
	msg.flags = 0; /* 标记为写数据 */
	msg.buf = b; /* 要发送的数据缓冲区 */
	msg.len = len + 1; /* 要发送的数据长度 */
	
	return i2c_transfer(client->adapter, &msg, 1);
}

xxx_read_regs 函数用于读取 I2C 设备多个寄存器数据
两个 i2c_msg 一个用来发送寄存器地址,一个用于读取寄存器值
msg[0].flags = 0 表示写数据,msg[1].flags = I2C_M_RD; 表示读数据

xxx_write_regs 函数用于向 I2C 设备多个寄存器写数据
msg 的 len 为 len+1,因为要加上一个字节的寄存器地址。

I2C 数据的收发操作最终都会调用i2c_transfer

I2C 数据发送函数 i2c_master_send

int i2c_master_send(const struct i2c_client *client,
					const char *buf,
					int count)
client: I2C 设备对应的 i2c_client。
buf:要发送的数据。
count: 要发送的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无
符号 16)类型的数据。
返回值: 负值,失败,其他非负值,发送的字节数。

I2C 数据接收函数为 i2c_master_recv

int i2c_master_recv(const struct i2c_client *client,
					char *buf,
					int count)
client: I2C 设备对应的 i2c_client。
buf:要接收的数据。
count: 要接收的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16)类型的数据。
返回值: 负值,失败,其他非负值,发送的字节数。

ZYNQ I2C驱动PCF8563设备

1.修改设备树 添加设备节点

打开system-top.dts 文件,追加节点,在i2c0 节点中添加“ rtc@51”子节点

&i2c0 {
	clock-frequency = <100000>;

	rtc@51 {
		compatible = "zynq-pcf8563";
		reg = <0x51>;
	};
};

编译成.dtb文件后,在/sys/bus/i2c/devices 目录下会存放着I2C设备

2.驱动编写

/*
 * 自定义结构体,用于表示时间和日期信息
 */
struct pcf8563_time {
	int sec;	// 秒
	int min;	// 分
	int hour;	// 小时
	int day;	// 日
	int wday;	// 星期
	int mon;	// 月份
	int year;	// 年
};
/*
 * 自定义结构体pcf8563_dev
 * 用于描述pcf8563设备
 */
struct pcf8563_dev {
	struct i2c_client *client;		/* i2c次设备 */
	dev_t devid;					/* 设备号 */
	struct cdev cdev;				/* cdev结构体 */
	struct class *class;			/* 类 */
	struct device *device;			/* 设备 */
};

static struct pcf8563_dev pcf8563;	// 定义一个pcf8563设备


static ssize_t pcf8563_read(struct file *filp, char __user *buf,
			size_t cnt, loff_t *off)
{
	struct pcf8563_dev *dev = filp->private_data;
	struct i2c_client *client = dev->client;
	struct pcf8563_time time = {0};
	u8 read_buf[9] = {0};
	int ret;

	/* 读寄存器数据 */
	ret = pcf8563_read_reg(dev, PCF8563_CTL_STATUS_1,
				read_buf, 9);
	if (ret)
		return ret;

	/* 校验时钟完整性 */
	if (read_buf[PCF8563_VL_SECONDS] & 0x80) {
		dev_err(&client->dev,
					"low voltage detected, date/time is not reliable.\n");
		return -EINVAL;
	}

	/* 将BCD码转换为数据得到时间、日期 */
	time.sec = bcd2bin(read_buf[PCF8563_VL_SECONDS] & 0x7F);		// 秒
	time.min = bcd2bin(read_buf[PCF8563_MINUTES] & 0x7F);			// 分
	time.hour = bcd2bin(read_buf[PCF8563_HOURS] & 0x3F);			// 小时
	time.day = bcd2bin(read_buf[PCF8563_DAYS] & 0x3F);				// 日
	time.wday = read_buf[PCF8563_WEEKDAYS] & 0x07;					// 星期
	time.mon = bcd2bin(read_buf[PCF8563_CENTURY_MONTHS] & 0x1F);	// 月
	time.year = bcd2bin(read_buf[PCF8563_YEARS]) + YEAR_BASE;		// 年

	/* 将数据拷贝到用户空间 */
	return copy_to_user(buf, &time, sizeof(struct pcf8563_time));
}


static ssize_t pcf8563_write(struct file *filp, const char __user *buf,
			size_t cnt, loff_t *offt)
{
	struct pcf8563_dev *dev = filp->private_data;
	struct pcf8563_time time = {0};
	u8 write_buf[9] = {0};
	int ret;

	ret = copy_from_user(&time, buf, cnt);	// 得到应用层传递过来的数据
	if(0 > ret)
		return -EFAULT;

	/* 将数据转换为BCD码 */
	write_buf[PCF8563_VL_SECONDS] = bin2bcd(time.sec);		// 秒
	write_buf[PCF8563_MINUTES] = bin2bcd(time.min);			// 分
	write_buf[PCF8563_HOURS] = bin2bcd(time.hour);			// 小时
	write_buf[PCF8563_DAYS] = bin2bcd(time.day);			// 日
	write_buf[PCF8563_WEEKDAYS] = time.wday & 0x07;			// 星期
	write_buf[PCF8563_CENTURY_MONTHS] = bin2bcd(time.mon);	// 月
	write_buf[PCF8563_YEARS] = bin2bcd(time.year % 100);	// 年

	/* 将数据写入寄存器 */
	ret = pcf8563_write_reg(dev, PCF8563_VL_SECONDS,
				&write_buf[PCF8563_VL_SECONDS], 7);
	if (ret)
		return ret;

	return cnt;
}

读函数用到了pcf8563_read_reg
写函数用到了pcf8563_write_reg

static int pcf8563_read_reg(struct pcf8563_dev *dev, u8 reg, u8 *buf, u8 len)
{
	struct i2c_client *client = dev->client;
	struct i2c_msg msg[2];
	int ret;

	/* msg[0]: 发送消息 */
	msg[0].addr = client->addr;		// pcf8563从机地址
	msg[0].flags = client->flags;	// 标记为写数据
	msg[0].buf = &reg;				// 要写入的数据缓冲区
	msg[0].len = 1;					// 要写入的数据长度

	/* msg[1]: 接收消息 */
	msg[1].addr = client->addr;		// pcf8563从机地址
	msg[1].flags = client->flags | I2C_M_RD;// 标记为读数据
	msg[1].buf = buf;				// 存放读数据的缓冲区
	msg[1].len = len;				// 读取的字节长度

	ret = i2c_transfer(client->adapter, msg, 2);
	if (2 != ret) {
		dev_err(&client->dev, "%s: error: reg=0x%x, len=0x%x\n",
					__func__, reg, len);
		return -EIO;
	}

	return 0;
}

static int pcf8563_write_reg(struct pcf8563_dev *dev, u8 reg, u8 *buf, u8 len)
{
	struct i2c_client *client = dev->client;
	struct i2c_msg msg;
	u8 send_buf[17] = {0};
	int ret;

	if (16 < len) {
		dev_err(&client->dev, "%s: error: Invalid transfer byte length %d\n",
					__func__, len);
		return -EINVAL;
	}

	send_buf[0] = reg;				// 寄存器首地址
	memcpy(&send_buf[1], buf, len);	// 将要写入的数据存放到数组send_buf后面

	msg.addr = client->addr;		// pcf8563从机地址
	msg.flags = client->flags;		// 标记为写数据
	msg.buf = send_buf;				// 要写入的数据缓冲区
	msg.len = len + 1;				// 要写入的数据长度

	ret = i2c_transfer(client->adapter, &msg, 1);
	if (1 != ret) {
		dev_err(&client->dev, "%s: error: reg=0x%x, len=0x%x\n",
					__func__, reg, len);
		return -EIO;
	}

	return 0;
}

pcf8563_read_reg函数中 msg[0]为读取的寄存器地址,msg[1]为要读取寄存器数据 最后用收发函数i2c_transfer
pcf8563_write_reg函数中 send_buf为要写入的数据

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值