资讯详情

I2C 子系统(三):I2C Driver

I2C driver 分四部分写

I2C SW Architecture I2C Data Structure I2C Register Flow I2C Data Transfer 

文章以 MTK 平台为例,code 来自小米开源项目,每次小米做手机项目,都会 kernel 由于必须遵循部分开源,因此必须遵循 GPL 协议

https://github.com/MiCode/Xiaomi_Kernel_OpenSource 

I2C driver 源码目录

/kernel-4.14/drivers/i2c/i2c-core-base.c //Linux common 驱动 /kernel-4.14/drivers/i2c/i2c-core.h /kernel-4.14/include/linux/i2c.h  /kernel-4.14/drivers/i2c/busses/i2c-mt65xx.c //i2c 控制器驱动 /kernel-4.14/arch/arm64/boot/dts/  demo /kernel-4.14/drivers/input/touchscreen/ 

【driver 由普通驱动工程师负责,【i2c 核心层】由 Linux 提供,【i2c 芯片原厂负责以下内容。

I2C 子系统通过 i2c-core 将 i2c 设备驱动和 i2c 分离总线驱动,使总线驱动 i2c 不要在意设备驱动 i2c 以总线传输细节为重点 i2c 实现设备逻辑。

抽象如下:

I2C 总线驱动的重点是 I2C 这里有两个重要的数据结构:i2c_adapter和i2c_algorithm。其中,Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter,i2c_algorithm 只有一些传输实现函数集。

在 Linux 以下节点将出现在系统中:

我们必须理解一个 Linux 子系统,必须研究它的数据结构,搞懂每个结构体存储了什么东西,才能梳理清该子系统的架构。

I2C 子系统有几个主要结构:

I2C 控制器端:i2c_adapter、i2c_algorithm、mtk_i2c  I2C 设备驱动端:i2c_client、i2c_driver  传输端:i2c_msg 

i2c_adapter:i2c-core 层描述一个 I2C 如果一个芯片有控制器, 8 路 I2C bus,则有 8 个 i2c_adapter

struct i2c_adapter { 
         struct module *owner;  unsigned int class; /* 该 I2C bus 支持哪些类型的设备来自设备 */  const struct i2c_algorithm *algo; /* the algorithm to access the bus */  void *algo_data;   /* data fields that ar valid for all devices */
	const struct i2c_lock_operations *lock_ops;
	struct rt_mutex bus_lock;
	struct rt_mutex mux_lock;

	int timeout;/* 超过该时间无法重发 */
	int retries;/* I2C发送失败重试次数 */
	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;/*I2C bus id*/
	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;
};

i2c_algorithm:I2C 传输函数合集,其中 master_xfer 是真正的传输函数,芯片原厂写 I2C 控制器驱动时必须实现。functionality 函数会返回该 I2C 控制器支持什么通信协议,也需要实现,其他的函数即便 Linux 规定了,芯片原厂也可以不实现。

struct i2c_algorithm { 
       
	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
};

MTK 只实现了其中两个

i2c_client:描述设备信息的

struct i2c_client { 
       
	unsigned short flags;/* I2C 传输标志位如下*/
#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;/* 所处的那一路 I2C bus */
	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 */
};

i2c_driver:普通驱动工程师写驱动时,必须实现其中的 probe 函数和 remove 函数。

struct i2c_driver { 
       
	unsigned int class;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
	int (*remove)(struct i2c_client *client);

	int (*probe_new)(struct i2c_client *client);

	void (*shutdown)(struct i2c_client *client);

	void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol,unsigned int data);

	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	int (*detect)(struct i2c_client *client, struct i2c_board_info *info);
	const unsigned short *address_list;
	struct list_head clients;
};

mtk_i2c:MTK 平台用该结构体表示 I2C 控制器,定义在/kernel-4.14/drivers/i2c/busses/i2c-mt65xx.c

struct mtk_i2c { 
       
	struct i2c_adapter adap;	/* i2c host adapter */
	struct device *dev;
	struct completion msg_complete;

	/* set in i2c probe */
	void __iomem *base;		/* i2c base addr */
	void __iomem *pdmabase;		/* dma base address*/
	struct clk *clk_main;		/* main clock for i2c bus */
	struct clk *clk_dma;		/* DMA clock for i2c via DMA */
	struct clk *clk_pmic;		/* PMIC clock for i2c from PMIC */
	bool have_pmic;			/* can use i2c pins from PMIC */
	bool use_push_pull;		/* IO config push-pull mode */

	u16 irq_stat;			/* interrupt status */
	unsigned int clk_src_div;
	unsigned int speed_hz;		/* The speed in transfer */
	enum mtk_trans_op op;
	u16 timing_reg;
	u16 high_speed_reg;
	unsigned char auto_restart;
	bool ignore_restart_irq;
	const struct mtk_i2c_compatible *dev_comp;
};

i2c_msg:I2C 读写必须填充 i2c_msg,写为 0 ,读为 I2C_M_RD,其他的 flag 大家可以参考

struct i2c_msg { 
       
	__u16 addr;
	__u16 flags;
#define I2C_M_RD 0x0001 /* guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* use only if I2C_FUNC_10BIT_ADDR */
#define I2C_M_DMA_SAFE 0x0200 /* use only in kernel space */
#define I2C_M_RECV_LEN 0x0400 /* use only if I2C_FUNC_SMBUS_READ_BLOCK_DATA */
#define I2C_M_NO_RD_ACK 0x0800 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* use only if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* use only if I2C_FUNC_PROTOCOL_MANGLING */
	__u16 len;//无符号16位,65536个byte,一次 I2C 传输最大 64KB
	__u8 *buf;
};

I2C_M_NO_RD_ACK:忽略所有 ACK/NACK 一直读
I2C_M_IGNORE_NAK:忽略所有的 NACK 继续读
I2C_M_NOSTART:没有 START 信号

/kernel-5.10/drivers/i2c/i2c-core-base.c 是 I2C 的核心部分,I2C 核心提供了一些与具体硬件无关的 API 函数

1、i2c_adapter 注册/注销函数

int i2c_add_adapter(struct i2c_adapter *adapter)//自动分配 adapter ID
int i2c_add_numbered_adapter(struct i2c_adapter *adap)//指定 ID
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)

上述 API 一般需要在 init/exit 或者 probe/remove 函数中成对使用。

设备和驱动的匹配过程也是由 I2C 总线完成的,I2C 总线的数据结构为 i2c_bus_type,定义在 /kernel-5.10/drivers/i2c/i2c-core-base.c 文件,i2c_bus_type 内容如下:

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

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

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{ 
       
	struct i2c_client *client = i2c_verify_client(dev);
	struct i2c_driver *driver;

	if (!client)
	return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
	return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
	return 1;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
	return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C 设备和驱动匹配。

acpi_driver_match_device 函数用于 ACPI 形式的匹配。

i2c_match_id 函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C 设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。

控制器驱动

I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到两个重要的数据结构:i2c_adapter 和 i2c_algorithm。其中,Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter。

对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。i2c_algorithm 就是 I2C 适配器与 I2C 设备进行通信的方法。

I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter 或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter。

I2C 控制器驱动加载

设备树 mt6885.dts

驱动

驱动和设备树匹配以后,probe 函数开始执行,重要的地方博主都进行了注释,不重要的部分进行了删除。

static int mt_i2c_probe(struct platform_device *pdev)
{ 
        
	int ret = 0;
	struct mt_i2c *i2c; //控制器结构体
	unsigned int clk_src_in_hz;
	struct resource *res;
	const struct of_device_id *of_id;
  
  //申请内存
	i2c = devm_kzalloc(&pdev->dev, sizeof(struct mt_i2c), GFP_KERNEL);

  //获取设备树节点
	ret = mt_i2c_parse_dt(pdev->dev.of_node, i2c);

  //从设备树获取 I2C 控制器寄存器物理基地址
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

  //进行内存映射,得到 Linux 内核使用的虚拟地址
	i2c->base = devm_ioremap_resource(&pdev->dev, res);

  .....
  
  //获取中断号
	i2c->irqnr = platform_get_irq(pdev, 0);

	init_waitqueue_head(&i2c->wait);

  //请求中断,中断服务函数为 mt_i2c_irq
	ret = devm_request_irq(&pdev->dev, i2c->irqnr, mt_i2c_irq,
		IRQF_NO_SUSPEND | IRQF_TRIGGER_NONE, I2C_DRV_NAME, i2c);

	of_id = of_match_node(mtk_i2c_of_match, pdev->dev.of_node);

  //填充 adapter 结构体各个参数
	i2c->dev_comp = of_id->data;
	i2c->i2c_pll_info = &i2c_pll_info;
	i2c->adap.dev.of_node = pdev->dev.of_node;
	i2c->dev = &i2c->adap.dev;
	i2c->adap.dev.parent = &pdev->dev;
	i2c->adap.owner = THIS_MODULE;
	i2c->adap.algo = &mt_i2c_algorithm;
	i2c->adap.algo_data = NULL;
	i2c->adap.timeout = 2 * HZ;
	i2c->adap.retries = 1;
	i2c->adap.nr = i2c->id;
	spin_lock_init(&i2c->cg_lock);

  ......

	mutex_init(&i2c->i2c_mutex);
	ret = i2c_set_speed(i2c, clk_src_in_hz);

	ret = mt_i2c_clock_enable(i2c);

	mt_i2c_init_hw(i2c);

	mt_i2c_clock_disable(i2c);
  
  // DMA 相关
	if (i2c->ch_offset_default)
		i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
			(PAGE_SIZE * 2), &i2c->dma_buf.paddr, GFP_KERNEL);
	else
		i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
			PAGE_SIZE, &i2c->dma_buf.paddr, GFP_KERNEL);

	if (i2c->dma_buf.vaddr == NULL) { 
        
		dev_info(&pdev->dev, "dma_alloc_coherent fail\n");
		return -ENOMEM;
	}
	i2c_set_adapdata(&i2c->adap, i2c);
  
  //向 Linux 内核注册 i2c_adapter
	ret = i2c_add_numbered_adapter(&i2c->adap);

	platform_set_drvdata(pdev, i2c);

	return 0;
}

节点创建

i2c_add_adapter、i2c_add_numbered_adapter 是注册 i2c_adapter,这两个 API 最终是调用 i2c_register_adapter。

static int i2c_register_adapter(struct i2c_adapter *adap)
{ 
        
	......

	dev_set_name(&adap->dev, "i2c-%d", adap->nr);
	adap->dev.bus = &i2c_bus_type;
	adap->dev.type = &i2c_adapter_type;
	res = device_register(&adap->dev);

	/* create pre-declared device nodes */
	of_i2c_register_devices(adap);
	i2c_acpi_install_space_handler(adap);
	i2c_acpi_register_devices(adap);

  .....
}

dev_set_name(&adap->dev, “i2c-%d”, adap->nr);会在Linux 中自动创建 /sys/devices/platform/11f00000.i2c6/i2c-6 节点。

of_i2c_register_devices 会调用 i2c_new_client_device

struct i2c_client *i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{ 
        
  ......

	client->dev.parent = &client->adapter->dev;
	client->dev.bus = &i2c_bus_type;
	client->dev.type = &i2c_client_type;
	client->dev.of_node = of_node_get(info->of_node);
	client->dev.fwnode = info->fwnode;

	device_enable_async_suspend(&client->dev);
	i2c_dev_set_name(adap, client, info);

  ......

	status = device_register(&client->dev);

  ......
}

i2c_dev_set_name 会在 Linux 系统中自动创建 /sys/devices/platform/11f00000.i2c6/i2c-6/6-0058 ,这里是挂在某个 i2c bus 上的,从机地址为 0x58 的设备节点。

到这里,大家应该能看懂下图,这些节点在 I2C 控制器驱动加载时,自动创建的。大家在 I2C 控制器的子节点中声明的 I2C 从机设备,也会在这一步创建节点。

设备驱动

I2C 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver。i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容。

一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client。

i2c_driver 中包含 probe 函数和 device_driver 结构体。如果使用设备树的话,需要设置 device_driver 的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。

当 I2C 设备和驱动匹配以后,probe 函数就会执行。

因此,对于 Linux 来讲,不区分 I2C 控制器和 I2C 从机设备的不同,用的都是同一套东西,即【总线、设备、驱动】框架,都有 probe 函数。

设备树

注意,I2C 设备在设备树中必须挂到对应的总线下,如图是在 &i2c0 下。

驱动

当驱动和设备匹配时,probe 函数开始执行,重要的地方博主都写了注释

static int goodix_i2c_probe(struct i2c_client *client,
	const struct i2c_device_id *dev_id)
{ 
        
	struct goodix_ts_device *ts_device = NULL;
	struct goodix_ts_board_data *ts_bdata = NULL;
	int r = 0;

  //检查 I2C 控制器支持是否支持标准 I2C 协议
	r = i2c_check_functionality(client->adapter,I2C_FUNC_I2C);

	/* 板级信息分配

标签: 传感器pec09opb620传感器opb615传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台