linux内核驱动:I2C子系统总结

前言

本笔记基于linux5.10.xxx内核源码版本,结合i2c控制器designware的ip核,结合at24系列eeprom从设备,总结i2c内核驱动学习;

一、概述

IIC(Inter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I²C的正确读法为“I平方C”(“I-squared-C”),而“I二C”(“I-two-C”)则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。
—来自百度百科

二、硬件特性

硬件连线
I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上,拓扑图如下
在这里插入图片描述

传输时序

在这里插入图片描述
以上为数据传输开始和结束时的时钟线和数据线构造的起始和结束信号bit电平

在这里插入图片描述
以上为实际数据载荷数据传输时SDA时钟线和SCL时钟线的配合要求

在这里插入图片描述
以上为ack数据bit位的传输时序
在这里插入图片描述
以上为主机向从机写数据时从机的ack情况,以7bit地址为例
在这里插入图片描述
以上为主机从从机读数据时主机的ack的情况,以7bit地址为例

器件地址

包含10bit和7bit器件地址,器件地址是指的数据手册中包含bit0(读写方向位)的数据向右移动一位后的数据,在设备树中配置的也是右移一位的数据,注意区分;

传输速率

标准模式:最高传输速率为100kb/s。
快速模式:最高传输速率为400kb/s。
高速模式:最高传输速率为3.4Mb/s。
实际使用的速率和主机和从机支持的速率是否匹配有关系

三、主要驱动文件

文件说明
drivers/i2c/i2c-core-base.c: i2c核心层文件,i2c总线注册,i2c控制器注册,i2c设备、驱动注册,通用数据传输接口在此文件定义;

drivers/i2c/i2c-core-of.c: 操作i2c控制器、i2c设备的设备树信息到控制器、i2c设备信息的操作函数;

drivers/i2c/i2c-dev.c: 封装了i2c协调器设备(实际的i2c控制器和虚拟的i2c控制器)内核驱动操作集struct file_operations i2cdev_fops,对应设备节点/dev/i2c-xx在用户空间open之后的用户空间write、read、ioctl函数在内核态的驱动实现;
i2c_adapter注册到总线后,触发通知事件,实现i2c_adapter和i2c_dev的绑定;

drivers/i2c/i2c-core-slave.c: i2c设备器件i2c_client的slave_cb成员注册接口文件;

drivers/i2c/i2c-core-smbus.c:支持smbus协议的函数接口文件,与i2c在时序上有些差异,硬件接口类似;

drivers/i2c/i2c-mux.c: i2c外设接口扩展芯片核心层驱动;

drivers/i2c/muxes/i2c-mux-xxxx.c: 一些i2c外设接口扩展芯片的驱动文件,如pca954x系列芯片;

drivers/i2c/busses/i2c-xxx.c: 具体SOC芯片厂家i2c外设ip的驱动实现,如i2c-s3c2410.c、i2c-rk3x.c等;

drivers/misc/eeprom/at24.c: 具体的i2c设备器件驱动,eeprom at24c的驱动文件;

主要文件关系示意图
在这里插入图片描述

四、主要数据结构

struct i2c_adapter

定义于include/linux/i2c.h;
i2c外设控制器的数据表示,一个SOC芯片不只一个控制器,实际驱动中会通过i2c_adapter生成i2c_dev将它挂载到i2c_dev_list(i2c-dev.c)链表中;
单个i2c外设控制器可能具备master和slave双模式配置,多大数SOC的控制器主要用来做master接从设备的i2c设备

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;  //适配器的id号,对应 /dev/i2c-号码
	char name[48]; //适配器的名字 ,在属性文件中定义 /sys/devices/platform/soc/4860000.i2c/i2c-0/i2c-dev/i2c-0/name
	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 i2c_algorithm

定义于include/linux/i2c.h;
表示i2c数据传输的函数方法集合,由于控制器可能具备master和slave两种角色功能,对于master角色需要实现master_xfer和functionality,对于slave角色需要实现functionality、reg_slave和unreg_slave;

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

struct i2c_client

定义于include/linux/i2c.h;
i2c从设备的数据表示,如eeprom的at24c从设备树节点解析后会创建成此结构;
关注addr地址成员,name成员,adapter成员为适配器的指针,一个适配器下可能有多个i2c设备,i2c设备的指针adapter会指向同一个适配器表示;

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]; //设备名 ,如at24c04 的/sys/bus/i2c/devices/0-0056/name 的内容为24c04
	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
};

struct i2c_driver

定义于include/linux/i2c.h;
i2c设备的驱动表示,关注probe和probe_new两个函数,probe函数适用于id_table成员的已经初始化,probe_new适用于成员driver.of_match_table已经初始化;

struct i2c_driver {
	unsigned int class;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);//probe函数
	int (*remove)(struct i2c_client *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 *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;
};

struct i2c_msg

定义于include/uapi/linux/i2c.h;
i2c读写消息的表示;
addr为器件地址,flags为读写表示;
len表示buf包含实际传输的字节数据,这里len不包括器件地址的长度;

struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__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_DMA_SAFE		0x0200	/* the buffer of this message is DMA safe */
					/* makes only sense in kernelspace */
					/* userspace buffers are copied anyway */
#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;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};

struct file_operations i2cdev_fops

定义drivers/i2c/i2c-dev.c;
用户空间打开/dev/i2c-0等设备节点时,对应到内核空间的驱动函数

static const struct file_operations i2cdev_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= i2cdev_read,
	.write		= i2cdev_write,
	.unlocked_ioctl	= i2cdev_ioctl,
	.compat_ioctl	= compat_i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

五、初始化和读写流程

初始化流程

i2c控制器初始化(master为例)
在这里插入图片描述

i2c设备初始化(at24c eeprom为例)
如上图
读写流程
在这里插入图片描述

六、应用使用

1、打开设备

int fd = open("/dev/i2c-0", O_RDWR);

2、写数据

int i2c_write(uint8_t slave, uint8_t reg, uint8_t * data, int len)
{
  unsigned char buf[1024];
  struct i2c_rdwr_ioctl_data i2c_data;
  struct i2c_msg i2c_msg;
  i2c_data.nmsgs = 1;
  i2c_data.msgs = &i2c_msg ;
  ioctl(_fd, I2C_TIMEOUT, 1);
  ioctl(_fd, I2C_RETRIES, 2);
  memset(buf, 0, 1024);
  buf[0] = reg;
  memcpy(&buf[1], buf, len);
  i2c_data.msgs[0].addr = slave;
  i2c_data.msgs[0].flags = 0; 
  i2c_data.msgs[0].buf = &buf[0];
  i2c_data.msgs[0].len = len+1;
  int ret = ioctl(fd, I2C_RDWR, (unsigned long)&i2c_data);
  if (ret < 0)
  {
    printf("write error\r\rn");
    return -1;
  }
 return 0;
}

3、读数据

int i2c_read(int8_t slave, int8_t reg, uint8_t * data, int len)
    unsigned char buf[2];
    struct i2c_rdwr_ioctl_data i2c_data;
    struct i2c_msg i2c_msg[2] ;
    i2c_data.nmsgs = 2;
    i2c_data.msgs = &i2c_msg[0] ;
    ioctl(_fd, I2C_TIMEOUT, 1);
    ioctl(_fd, I2C_RETRIES, 2);
    
    buf[0] = reg ;
    i2c_data.msgs[0].addr = slave;
    i2c_data.msgs[0].flags = 0;     
    i2c_data.msgs[0].buf = &buf[0];
    i2c_data.msgs[0].len = 1;

    i2c_data.msgs[1].addr = slave;
    i2c_data.msgs[1].flags = 1;    
    i2c_data.msgs[1].buf = data;
    i2c_data.msgs[1].len = len;

    int ret = ioctl(_fd, I2C_RDWR, (unsigned long)&i2c_data);
    if (ret < 0)
    {
        printf("read error\n");
        return -1;
    }
    return 0 ;
}

4、关闭设备

close(fd);

七、调试方法

1、确认设备节点信息
/dev/i2c-x,是否和设备树配置的匹配
/sys/devices/下 查找 i2c-dev 文件可以看到控制器信息、控制器下属对应的设备关系;
2、调试工具
i2cdetect – 用来列举 I2C 外设上的设备
如i2cdetect -y 0 ,显示i2c外设0上的i2c设备信息

i2cdump – 显示 i2c 设备所有 register 的值
i2cdump -y 0 0x40 , 显示外设0上 地址0x40器件的寄存器的值

i2cget – 获取 i2c 设备某个 register 的值
i2cget -y 0 0x40 0x01, 获取i2c外设0上0x40地址器件的0x01寄存器的值

i2cset – 设置i2c 设备某个 register 的值
i2cset -y 0 0x40 0x01 0x02, 设置i2c外设0上0x40地址器件的0x01寄存器的值为0x01

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值