RK3568驱动指南|第十五篇 I2C-第178章 i2c_client结构体分析

发布于:2024-07-03 ⋅ 阅读:(12) ⋅ 点赞:(0)

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十五篇 I2C_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第178章 i2c_client结构体分析

在编写FT5X06触摸芯片驱动时,是通过i2c_client结构体来获取到I2C外设地址,而在i2c_client结构体中还包括中断号、I2C控制器等信息,该结构体定义在“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;		/* 设备结构体 */
	int init_irq;			/* 初始化时设置的中断号 */
	int irq;			/* 设备产生的中断号 */
	struct list_head detected;	/* 检测到的设备列表 */
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* 从模式下的回调函数 */
#endif
};

在编写I2C client代码时提到了两种方法,第一种是在设备树中的I2C节点中追加对应的设备节点,可以回顾第170章内容,第二种方法是编写platform device C程序,可以回顾第172章内容,但是在C程序中并不是直接创建的i2c_client结构体,而是创建了i2c_board_info结构体,i2c_board_info和i2c_client两个结构体内容是相似的,最大的区别在于i2c_board_info没有I2C设备具体挂载到哪一个I2C 适配器的结构体i2c_adapter,那Linux在没有使用设备树的前提下是怎样解决i2c_board_info无法描述挂载的哪一个I2C 适配器这一问题的呢?

在本章的第一小节中将会对非设备树源码的I2C client进行分析,而在第二小节中将会对RK3568源码的I2C client进行分析。

178.1 非设备树I2C client分析

由于RK3568的源码已经不再使用平台文件对设备进行描述,而是使用设备树进行了取代,所以要想对非设备树的I2C client进行分析,需要使用较早一些的内核源码,这里使用的是4412开发板3.0版本的内核源码,具体存放路径为“iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\107_i2c_client

首先找到itop-4412开发板的平台文件,具体路径为“arch/arm/mach-exynos/mach-itop4412.c”,在该平台文件中使用了i2c_register_board_info函数,注册了board_info,具体内容如下所示:

	s3c_i2c0_set_platdata(NULL);
	i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));

	s3c_i2c1_set_platdata(NULL);
	i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));

	s3c_i2c2_set_platdata(NULL);
	i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));

	init_lcd_type();
	get_lcd_type();
	setup_ft5x_width_height();
		
	s3c_i2c3_set_platdata(NULL);
	i2c_register_board_info(3, i2c_devs3, ARRAY_SIZE(i2c_devs3));

	s3c_i2c4_set_platdata(NULL);
	i2c_register_board_info(4, i2c_devs4, ARRAY_SIZE(i2c_devs4));

#ifdef CONFIG_MPU_SENSORS_MPU6050B1
	sensor_hw_init();
#endif

	s3c_i2c5_set_platdata(NULL);
	i2c_register_board_info(5, i2c_devs5, ARRAY_SIZE(i2c_devs5));

	//For S5K4EC (using i2c6)
//#ifndef CONFIG_CAN_MCP251X
#if !defined(CONFIG_CAN_MCP251X) && !defined(CONFIG_SPI_RC522)
	s3c_i2c6_set_platdata(NULL);
	i2c_register_board_info(6, i2c_devs6, ARRAY_SIZE(i2c_devs6));
#endif


	s3c_i2c7_set_platdata(NULL);
	i2c_register_board_info(7, i2c_devs7, ARRAY_SIZE(i2c_devs7));

i2c_register_board_info函数定义在“drivers/i2c/i2c-boardinfo.c”文件中,具体内容如下所示:

/*
 * 注册 I2C 设备到内核的 I2C 子系统中。
 *
 * @busnum: I2C 总线号
 * @info: I2C 设备信息数组
 * @len: 设备信息数组的长度
 *
 * 返回值:
 * 成功时返回 0,失败时返回负值错误码。
*/
int __init
i2c_register_board_info(int busnum,
	struct i2c_board_info const *info, unsigned len)
{
	int status;

	// 获取写锁以保护全局 I2C 设备列表
	down_write(&__i2c_board_lock);

	// 如果总线号大于等于第一个动态分配的总线号,更新第一个动态分配的总线号
	if (busnum >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = busnum + 1;

	// 遍历设备信息数组
	for (status = 0; len; len--, info++) {
		// 为每个设备分配 i2c_devinfo 结构体
		struct i2c_devinfo	*devinfo;
		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
		if (!devinfo) {
			// 内存分配失败,打印调试信息并退出
			pr_debug("i2c-core: can't register boardinfo!\n");
			status = -ENOMEM;
			break;
		}

		// 填充 i2c_devinfo 结构体
		devinfo->busnum = busnum;
		devinfo->board_info = *info;
		list_add_tail(&devinfo->list, &__i2c_board_list);
	}

	// 释放写锁
	up_write(&__i2c_board_lock);

	return status;
}

该函数有三个参数其中busnum表示I2C的总线编号,例如I2C0、I2C1中的0和1就属于总线编号,第二个参数info表示I2C设备信息数组,第三个参数len:表示设备信息数组的长度,这个函数用于在内核的 I2C 子系统中注册 I2C 设备信息。函数分析如下:

第18行:获取全局 I2C 设备列表的写锁,以保护并发访问。

第21-22行:如果指定的总线号大于等于第一个动态分配的总线号,则更新第一个动态分配的总线号。

第25-34行:遍历传入的 i2c_board_info 数组,为每个设备分配一个 i2c_devinfo 结构体,填充 i2c_devinfo 结构体的成员,包括总线号和设备信息,将新分配的 i2c_devinfo 添加到全局 I2C 设备列表的末尾。

对该函数进行分析之后发现并没有寻找i2c_adapter以及创建i2c_client相关的操作,那上面创建的 i2c_devinfo是如何挂到i2c_adapter上的呢?这就需要找到4412的I2C控制器驱动了,也就是“drivers/i2c/busses/i2c-s3c2410.c”文件,在I2C控制器驱动的probe函数中调用了i2c_add_numbered_adapter函数来注册一个I2C适配器,并分配指定的总线号,该函数定义在“drivers/i2c/i2c-core.c”文件中,具体内容如下所示:

/*
 * 向 I2C 核心子系统注册一个 I2C 适配器,并分配一个指定的总线号。
 *
 * @adap: 要注册的 I2C 适配器
 *
 * 返回值:
 * 成功时返回 0,失败时返回负值错误码。
 */
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
	int	id;
	int	status;

	// 检查总线号是否超出了允许的范围
	if (adap->nr & ~MAX_ID_MASK)
		return -EINVAL;

retry:
	// 尝试获取 i2c_adapter_idr IDR 的预分配资源
	if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
		return -ENOMEM;

	// 获取写锁,保护对全局 i2c_adapter_idr IDR 的访问
	mutex_lock(&core_lock);

	// 从 i2c_adapter_idr IDR 中获取一个新 ID,此 ID 应该大于或等于指定的总线号
	status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
	if (status == 0 && id != adap->nr) {
		// 如果获取的 ID 不等于指定的总线号,说明该总线号已被占用
		status = -EBUSY;
		// 从 i2c_adapter_idr IDR 中移除该 ID
		idr_remove(&i2c_adapter_idr, id);
	}

	// 释放写锁
	mutex_unlock(&core_lock);

	if (status == -EAGAIN)
		// 如果获取 IDR 资源失败,则重试
		goto retry;

	if (status == 0)
		// 如果获取 ID 成功,则注册 I2C 适配器
		status = i2c_register_adapter(adap);

	return status;
}

这个函数的具体的工作流程如下:

第15-16行:检查传入的 i2c_adapter 结构体的总线号是否超出了允许的范围。

第18-21行:尝试获取 i2c_adapter_idr IDR 的预分配资源,如果失败则返回 -ENOMEM 错误。

第24行:获取写锁 core_lock,保护对全局 i2c_adapter_idr IDR 的访问。

第27行:从 i2c_adapter_idr IDR 中获取一个新的 ID,此 ID 应该大于或等于指定的总线号。

第28-33行:如果获取的 ID 不等于指定的总线号,说明该总线号已被占用,此时返回 -EBUSY 错误,并从 IDR 中移除该 ID。

第36行:释放写锁 core_lock。

第38-40行:如果在获取 IDR 资源时出现 -EAGAIN 错误,则重试。

第42行:如果获取 ID 成功,则调用 i2c_register_adapter() 函数注册I2C适配器。

该函数的重点为第42行调用的 i2c_register_adapter() 函数,通过 i2c_register_adapter() 函数注册了I2C适配器,该函数的具体内容如下所示:

/*
 * 将一个 I2C 适配器注册到 I2C 核心子系统中。
 *
 * @adap: 要注册的 I2C 适配器
 *
 * 返回值:
 * 成功时返回 0,失败时返回负值错误码。
 */
static int i2c_register_adapter(struct i2c_adapter *adap)
{
	int res = 0;

	// 检查 I2C 总线类型是否已初始化完成
	if (unlikely(WARN_ON(!i2c_bus_type.p))) {
		res = -EAGAIN;
		goto out_list;
	}

	// 检查适配器名称是否为空
	if (unlikely(adap->name[0] == '\0')) {
		pr_err("i2c-core: Attempt to register an adapter with "
		       "no name!\n");
		return -EINVAL;
	}
	// 检查适配器是否有算法
	if (unlikely(!adap->algo)) {
		pr_err("i2c-core: Attempt to register adapter '%s' with "
		       "no algo!\n", adap->name);
		return -EINVAL;
	}

	// 初始化适配器的总线锁和用户空间客户端锁
	rt_mutex_init(&adap->bus_lock);
	mutex_init(&adap->userspace_clients_lock);
	INIT_LIST_HEAD(&adap->userspace_clients);

	// 如果未设置超时时间,则默认为 1 秒
	if (adap->timeout == 0)
		adap->timeout = HZ;

	// 设置适配器的设备名称
	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);
	if (res)
		goto out_list;

	dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

#ifdef CONFIG_I2C_COMPAT
	// 创建兼容性类链接
	res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
				       adap->dev.parent);
	if (res)
		dev_warn(&adap->dev,
			 "Failed to create compatibility class link\n");
#endif

	// 扫描静态的 I2C 设备信息
	if (adap->nr < __i2c_first_dynamic_bus_num)
		i2c_scan_static_board_info(adap);

	// 通知所有的 I2C 驱动程序有新的适配器注册
	mutex_lock(&core_lock);
	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
	mutex_unlock(&core_lock);

	return 0;

out_list:
	// 如果注册失败,则从 i2c_adapter_idr 中移除该适配器
	mutex_lock(&core_lock);
	idr_remove(&i2c_adapter_idr, adap->nr);
	mutex_unlock(&core_lock);
	return res;
}

第13-17行:检查 i2c_bus_type 是否已经初始化完成,如果未初始化则返回 -EAGAIN 错误。

第20-24行:检查适配器的名称是否为空,如果为空则返回 -EINVAL 错误。

第26-30行:检查适配器是否有算法,如果没有则返回 -EINVAL 错误。

第33-35行:初始化适配器的总线锁和用户空间客户端锁。

第38-39行:如果未设置超时时间,则默认为 1 秒。

第42-49行:设置适配器的设备名称,并注册设备。

第63-64行:如果适配器的总线号小于 __i2c_first_dynamic_bus_num,则扫描静态的 I2C 设备信息。

第66-69行:通知所有的 I2C 驱动程序有新的适配器注册。

第73-78行:如果注册失败,则从 i2c_adapter_idr 中移除该适配器。

该函数的重点为第64行调用的i2c_scan_static_board_info函数,该函数的主要作用是扫描当前i2c适配器上的静态板载设备信息,函数的具体内容如下所示:

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    // 定义一个i2c_devinfo结构体指针
    struct i2c_devinfo *devinfo;

    // 获取__i2c_board_lock读锁
    down_read(&__i2c_board_lock);

    // 遍历__i2c_board_list链表
    list_for_each_entry(devinfo, &__i2c_board_list, list) {
        // 检查当前设备是否在当前适配器上
        if (devinfo->busnum == adapter->nr
                && !i2c_new_device(adapter,
                        &devinfo->board_info))
            // 如果添加设备失败,打印错误信息
            dev_err(&adapter->dev,
                "Can't create device at 0x%02x\n",
                devinfo->board_info.addr);
    }

    // 释放__i2c_board_lock读锁
    up_read(&__i2c_board_lock);
}

该函数的重点在10-19行,通过遍历__i2c_board_list链表最后调用第13行的i2c_new_device函数用于创建一个新的i2c client设备客户端,该函数的具体内容为:

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    // 定义一个i2c_client结构体指针
    struct i2c_client *client;
    // 定义一个状态变量
    int status;

    // 为i2c_client分配内存
    client = kzalloc(sizeof *client, GFP_KERNEL);
    if (!client)
        // 如果内存分配失败,返回NULL
        return NULL;

    // 将适配器赋值给客户端
    client->adapter = adap;

    // 将平台数据赋值给客户端
    client->dev.platform_data = info->platform_data;

    // 如果有架构相关的数据,也赋值给客户端
    if (info->archdata)
        client->dev.archdata = *info->archdata;

    // 设置客户端的其他属性
    client->flags = info->flags;
    client->addr = info->addr;
    client->irq = info->irq;

    // 复制设备类型到客户端名称
    strlcpy(client->name, info->type, sizeof(client->name));

    // 检查客户端地址的有效性
    status = i2c_check_client_addr_validity(client);
    if (status) {
        // 如果地址无效,打印错误信息并返回NULL
        dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
            client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
        goto out_err_silent;
    }

    // 检查地址是否正在被使用
    status = i2c_check_addr_busy(adap, client->addr);
    if (status)
        // 如果地址正在被使用,打印错误信息并返回NULL
        goto out_err;

    // 设置客户端的设备属性
    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;
    client->dev.type = &i2c_client_type;
    client->dev.of_node = info->of_node;

    // 设置客户端的设备名称
    dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
                 client->addr);
    // 注册设备
    status = device_register(&client->dev);
    if (status)
        // 如果注册失败,打印错误信息并返回NULL
        goto out_err;

    // 打印设备注册信息
    dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
        client->name, dev_name(&client->dev));

    // 返回新创建的i2c_client
    return client;

out_err:
    // 如果发生错误,打印错误信息并释放内存
    dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
        "(%d)\n", client->name, client->addr, status);
out_err_silent:
    kfree(client);
    return NULL;
}

至此,关于非设备树中的i2c client就分析完成了,当然现在的源码已经不再使用平台文件了,而是使用设备树对设备进行描述,本小节的知识只是为了更好的理解I2C框架,在下个小节中将对设备树中的i2c client进行分析。

178.2 设备树I2C client分析

FT5X06触摸芯片完整的设备树如下所示:

i2c1: i2c@fe5a0000 {
	compatible = "rockchip,rk3399-i2c";
	reg = <0x0 0xfe5a0000 0x0 0x1000>;
	clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
	clock-names = "i2c", "pclk";
	interrupts = <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>;
	pinctrl-names = "default";
	pinctrl-0 = <&i2c1_xfer>;
	#address-cells = <1>;
	#size-cells = <0>;
	status = "disabled";

	myft5x06: my-ft5x06@38 {
		compatible = "my-ft5x06";
		reg = <0x38>;
		reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&gpio3>;
		interrupts-gpio = <&gpio3 RK_PA5 GPIO_ACTIVE_LOW>;
		interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;
		pinctrl-names = "default";
		pinctrl-0 = <&myft5x06_pins>;
	};
};

根据设备树讲解的相关知识,i2c1节点会转换成为platform device,但由于在该节点中并没有特殊的属性值,所以它的子节点myft5x06并不会被转换成platform device,这时候就出现问题了,既然触摸节点myft5x06不会被转换成platform device,那最后是如何解析的呢?

在上一小节关于平台文件中的I2C client的分析中,需要先让I2C 控制器工作起来,然后再添加具体的I2C设备信息,这里的设备树文件也同样如此,先让I2C 控制器工作起来,再解析对应的设备树节点。所以先打开RK3568对应的I2C控制器驱动文件“drivers/i2c/busses/i2c-rk3x.c”找到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;
    u32 value;
    int irq;
    unsigned long clk_rate;

    // 分配 rk3x_i2c 结构体
    i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
    if (!i2c)
        return -ENOMEM;

    // 获取匹配的 SoC 数据
    match = of_match_node(rk3x_i2c_match, np);
    i2c->soc_data = match->data;

    // 解析 I2C 时序属性
    i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);

    // 初始化 I2C 适配器
    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->i2c_restart_nb.notifier_call = rk3x_i2c_restart_notify;
    i2c->i2c_restart_nb.priority = 128;
    ret = register_pre_restart_handler(&i2c->i2c_restart_nb);
    if (ret) {
        dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
        return ret;
    }

    // 映射 I2C 寄存器
    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);

    // 切换到新的 I2C 接口
    if (i2c->soc_data->grf_offset >= 0) {
        struct regmap *grf;
        grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
        if (!IS_ERR(grf)) {
            int bus_nr;
            bus_nr = of_alias_get_id(np, "i2c");
            if (bus_nr < 0) {
                dev_err(&pdev->dev, "rk3x-i2c needs i2cX alias");
                return -EINVAL;
            }
            // 配置 GRF 寄存器以切换到新的 I2C 接口
            if (i2c->soc_data == &rv1108_soc_data && bus_nr == 2)
                value = BIT(26) | BIT(10);
            else if (i2c->soc_data == &rv1126_soc_data && bus_nr == 2)
                value = BIT(20) | BIT(4);
            else
                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;
            }
        }
    }

    // 请求 I2C 中断
    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;
    }

    // 获取 I2C 时钟
    if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
        // 只有一个时钟用于总线时钟和外设时钟
        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");
    }

    // 准备 I2C 时钟
    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;
    }

    // 计算 I2C 时钟分频
    clk_rate = clk_get_rate(i2c->clk);
    rk3x_i2c_adapt_div(i2c, clk_rate);

    // 注册 I2C 适配器
    ret = i2c_add_adapter(&i2c->adap);
    if (ret < 0)
        goto err_clk_notifier;

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

该probe函数最重要的部分就是第128行,调用i2c_add_adapter函数注册I2C适配器,该

函数定义在“drivers/i2c/i2c-core-base.c”文件中,具体内容如下所示:

// 添加I2C适配器的函数
int i2c_add_adapter(struct i2c_adapter *adapter)
{
    // 获取适配器的设备
    struct device *dev = &adapter->dev;
    // 声明一个ID变量
    int id;

    // 如果设备节点存在
    if (dev->of_node) {
        // 从设备树别名中获取I2C总线号
        id = of_alias_get_id(dev->of_node, "i2c");
        // 如果总线号有效
        if (id >= 0) {
            // 设置适配器编号为获取的总线号
            adapter->nr = id;
            // 添加编号的I2C适配器
            return __i2c_add_numbered_adapter(adapter);
        }
    }

    // 加锁,防止并发访问
    mutex_lock(&core_lock);
    // 在i2c_adapter_idr动态ID池中分配一个ID
    id = idr_alloc(&i2c_adapter_idr, adapter,
                   __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
    // 解锁
    mutex_unlock(&core_lock);
    // 如果分配ID失败
    if (WARN(id < 0, "couldn't get idr"))
        // 返回错误码
        return id;

    // 设置适配器编号为分配的ID
    adapter->nr = id;

    // 注册I2C适配器
    return i2c_register_adapter(adapter);
}

第10-20行:首先尝试从设备树别名中获取I2C总线号,如果获取成功,则直接使用这个总线号并使用__i2c_add_numbered_adapter函数添加编号的I2C适配器。

第25-26行:如果没有在设备树中找到总线号,则从动态ID池中分配一个ID作为适配器编号。

第38行:最后调用i2c_register_adapter函数注册I2C适配器。

I2C总线号就是I2C0、I2C1的0和1,获取到总线号之后会调用__i2c_add_numbered_adapter函数来添加对应的I2C适配器,该函数的具体内容如下所示:

// 添加编号的I2C适配器的内部函数
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    // 声明一个ID变量
    int id;

    // 加锁,防止并发访问
    mutex_lock(&core_lock);
    // 在i2c_adapter_idr动态ID池中分配一个从adap->nr开始的ID
    id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL);
    // 解锁
    mutex_unlock(&core_lock);
    // 如果分配ID失败
    if (WARN(id < 0, "couldn't get idr"))
        // 如果是因为空间不足,-ENOSPC,返回-EBUSY表示设备忙;否则返回错误码
        return id == -ENOSPC ? -EBUSY : id;

    // 注册I2C适配器
    return i2c_register_adapter(adap);
}

在该函数的最后会使用i2c_register_adapter函数注册I2C适配器,i2c_register_adapter函数的具体内容如下所示:

// I2C适配器注册函数
static int i2c_register_adapter(struct i2c_adapter *adap)
{
    // 初始化返回值为无效
    int res = -EINVAL;

    // 如果驱动模型还没有初始化完成,返回-EAGAIN表示再次尝试
    if (WARN_ON(!is_registered)) {
        res = -EAGAIN;
        goto out_list;
    }

    // 进行一些基本检查
    // 如果适配器名称为空,返回错误
    if (WARN(!adap->name[0], "i2c adapter has no name"))
        goto out_list;
    // 如果没有提供算法,返回错误
    if (!adap->algo) {
        pr_err("adapter '%s': no algo supplied!\n", adap->name);
        goto out_list;
    }

    // 如果没有提供锁操作,使用默认的i2c_adapter_lock_ops
    if (!adap->lock_ops)
        adap->lock_ops = &i2c_adapter_lock_ops;

    // 初始化适配器总线锁和复用锁
    rt_mutex_init(&adap->bus_lock);
    rt_mutex_init(&adap->mux_lock);
    mutex_init(&adap->userspace_clients_lock);
    INIT_LIST_HEAD(&adap->userspace_clients);

    // 如果没有设置超时时间,设置为1秒
    if (adap->timeout == 0)
        adap->timeout = HZ;

    // 设置Host Notify中断域
    res = i2c_setup_host_notify_irq_domain(adap);
    if (res) {
        pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",
               adap->name, res);
        goto out_list;
    }

    // 设置设备名称和总线类型
    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);
    if (res) {
        pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
        goto out_list;
    }

    // 设置SMBUS警报
    res = of_i2c_setup_smbus_alert(adap);
    if (res)
        goto out_reg;

    // 输出调试信息
    dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

    // 设置电源管理相关属性
    pm_runtime_no_callbacks(&adap->dev);
    pm_suspend_ignore_children(&adap->dev, true);
    pm_runtime_enable(&adap->dev);

    // 创建兼容性类链接
#ifdef CONFIG_I2C_COMPAT
    res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
                                   adap->dev.parent);
    if (res)
        dev_warn(&adap->dev,
                 "Failed to create compatibility class link\n");
#endif

    // 初始化恢复机制
    i2c_init_recovery(adap);

    // 注册设备节点和ACPI设备
    of_i2c_register_devices(adap);
    i2c_acpi_install_space_handler(adap);
    i2c_acpi_register_devices(adap);

    // 扫描静态板载设备
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);

    // 通知驱动
    mutex_lock(&core_lock);
    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
    mutex_unlock(&core_lock);

    return 0;

out_reg:
    init_completion(&adap->dev_released);
    device_unregister(&adap->dev);
    wait_for_completion(&adap->dev_released);
out_list:
    mutex_lock(&core_lock);
    idr_remove(&i2c_adapter_idr, adap->nr);
    mutex_unlock(&core_lock);
    return res;
}

在该函数的第82行调用了of_i2c_register_devices函数扫描设备树中定义的I2C从设备并注册他们,该函数的具体内容如下所示

void of_i2c_register_devices(struct i2c_adapter *adap)
{
    // 声明两个设备树节点指针变量,分别用于表示I2C总线节点和I2C从设备节点
    struct device_node *bus, *node;
    // 声明一个I2C从设备客户端指针变量
    struct i2c_client *client;

    // 只有当I2C适配器有关联的设备树节点时,才继续注册从设备
    if (!adap->dev.of_node)
        return;

    // 打印调试信息,表示正在遍历I2C从设备节点
    dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");

    // 尝试从I2C适配器的设备树节点中获取名为"i2c-bus"的子节点
    // 如果没有找到该子节点,则直接使用I2C适配器的设备树节点作为I2C总线节点
    bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
    if (!bus)
        bus = of_node_get(adap->dev.of_node);

    // 遍历I2C总线节点的所有可用子节点
    for_each_available_child_of_node(bus, node) {
        // 检查该I2C从设备节点是否已被标记为已注册
        // 如果已被标记,则跳过该节点,避免重复注册
        if (of_node_test_and_set_flag(node, OF_POPULATED))
            continue;

        // 使用of_i2c_register_device函数注册I2C从设备
        // 该函数根据设备树节点的属性创建并注册i2c_client结构体
        client = of_i2c_register_device(adap, node);
        if (IS_ERR(client)) {
            // 如果注册失败,打印错误消息,并清除节点的"已注册"标志,以便下次可以重新尝试注册
            dev_err(&adap->dev,
                "Failed to create I2C device for %pOF\n",
                node);
            of_node_clear_flag(node, OF_POPULATED);
        }
    }

    // 释放对I2C总线节点的引用
    of_node_put(bus);
}

第9-10行:检查 adap->dev.of_node 是否为空。如果为空,意味着 I2C 适配器没有关联的设备树节点,函数直接返回。

第17-19行:尝试从 I2C 适配器的设备树节点中获取名为 "i2c-bus" 的子节点。如果找不到,则直接使用 I2C 适配器的设备树节点作为 I2C 总线节点。

第22-26行遍历 I2C 总线节点的所有可用子节点。对于每个子节点:

(1)检查该节点是否已被标记为已注册(OF_POPULATED)。如果已被标记,则跳过该节点,避免重复注册。

(2)使用 of_i2c_register_device() 函数注册 I2C 从设备。该函数根据设备树节点的属性创建并注册 i2c_client 结构体。

(3)如果注册失败,打印错误消息,并清除节点的 "已注册" 标志,以便下次可以重新尝试注册。

该函数的关注点在第30行的of_i2c_register_device函数,通过该函数注册所有可用子节点,该函数的具体内容为

static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
						 struct device_node *node)
{
	// 声明i2c_client和i2c_board_info结构体指针变量
	struct i2c_client *client;
	struct i2c_board_info info;
	int ret;

	// 打印调试信息,表示正在注册设备树节点
	dev_dbg(&adap->dev, "of_i2c: register %pOF\n", node);

	// 根据设备树节点信息填充i2c_board_info结构体
	ret = of_i2c_get_board_info(&adap->dev, node, &info);
	if (ret)
		// 如果获取i2c_board_info信息失败,返回错误指针
		return ERR_PTR(ret);

	// 使用i2c_new_device()函数注册i2c_client设备
	client = i2c_new_device(adap, &info);
	if (!client) {
		// 如果注册失败,打印错误信息并返回错误指针
		dev_err(&adap->dev, "of_i2c: Failure registering %pOF\n", node);
		return ERR_PTR(-EINVAL);
	}
	// 返回成功注册的i2c_client指针
	return client;
}

这个函数的主要作用是根据提供的设备树节点信息,自动创建并注册一个I2C从设备。它使用of_i2c_get_board_info()函数从设备树节点中获取必要的信息,of_i2c_get_board_info()函数的具体内容如下所示:

int of_i2c_get_board_info(struct device *dev, struct device_node *node,
			  struct i2c_board_info *info)
{
	// 声明地址和返回值变量
	u32 addr;
	int ret;

	// 清空i2c_board_info结构体
	memset(info, 0, sizeof(*info));

	// 从设备树节点获取modalias字符串
	if (of_modalias_node(node, info->type, sizeof(info->type)) < 0) {
		// 如果获取modalias失败,打印错误信息并返回
		dev_err(dev, "of_i2c: modalias failure on %pOF\n", node);
		return -EINVAL;
	}

	// 从设备树节点获取reg属性(设备地址)
	ret = of_property_read_u32(node, "reg", &addr);
	if (ret) {
		// 如果获取地址失败,打印错误信息并返回
		dev_err(dev, "of_i2c: invalid reg on %pOF\n", node);
		return ret;
	}

	// 检查地址是否为10位地址模式
	if (addr & I2C_TEN_BIT_ADDRESS) {
		// 清除高位标志,并设置i2c_board_info的flags
		addr &= ~I2C_TEN_BIT_ADDRESS;
		info->flags |= I2C_CLIENT_TEN;
	}

	// 检查地址是否为从设备地址模式
	if (addr & I2C_OWN_SLAVE_ADDRESS) {
		// 清除高位标志,并设置i2c_board_info的flags
		addr &= ~I2C_OWN_SLAVE_ADDRESS;
		info->flags |= I2C_CLIENT_SLAVE;
	}

	// 填充i2c_board_info结构体的addr、of_node和fwnode字段
	info->addr = addr;
	info->of_node = node;
	info->fwnode = of_fwnode_handle(node);

	// 检查设备树节点是否有"host-notify"属性
	if (of_property_read_bool(node, "host-notify"))
		// 如果有,设置i2c_board_info的flags
		info->flags |= I2C_CLIENT_HOST_NOTIFY;

	// 检查设备树节点是否有"wakeup-source"属性
	if (of_get_property(node, "wakeup-source", NULL))
		// 如果有,设置i2c_board_info的flags
		info->flags |= I2C_CLIENT_WAKE;

	// 返回0,表示成功获取并填充i2c_board_info结构体
	return 0;
}

这个函数的主要作用是根据提供的设备树节点信息,填充i2c_board_info结构体。它从设备树节点中获取设备的modalias、地址、总线标志等信息,并将其填充到i2c_board_info结构体中。

在of_i2c_register_device函数的最后会调用i2c_new_device()函数进行实际设备的注册,至此对于设备树中的I2C client的分析就结束了。

但还有一个地方需要注意,在上面的函数分析中并没有涉及到中断相关内容,而前面章节编写的FT5X06驱动中有这样的代码描述,这里的中断号是在哪里进行的赋值呢?

    ret = request_irq(client->irq, ft5x06_handler,
                     IRQ_TYPE_EDGE_FALLING | IRQF_ONESHOT, "ft5x06 irq", NULL);

实际上在执行驱动的probe函数之前,会先执行“drivers/i2c/i2c-core-base.c”文件中的i2c_device_probe函数,该函数的具体内容如下所示:

static int i2c_device_probe(struct device *dev)
{
    // 从 device 结构体中获取 i2c_client 结构体
    struct i2c_client    *client = i2c_verify_client(dev);
    // 从 device 结构体中获取 i2c_driver 结构体
    struct i2c_driver    *driver;
    int status;

    // 如果 client 不存在,则返回 0
    if (!client)
        return 0;

    // 将 dev->driver 转换为 i2c_driver 类型
    driver = to_i2c_driver(dev->driver);

    // 如果 client 没有中断号且 driver 没有禁用 i2c 核心中断映射,则尝试获取中断号
    if (!client->irq && !driver->disable_i2c_core_irq_mapping) {
        int irq = -ENOENT;

        // 如果 client 使用 Host Notify 中断,则使用 i2c_smbus_host_notify_to_irq 获取中断号
        if (client->flags & I2C_CLIENT_HOST_NOTIFY) {
            dev_dbg(dev, "Using Host Notify IRQ\n");
            // 保持适配器处于活动状态,因为需要 Host Notify 中断
            pm_runtime_get_sync(&client->adapter->dev);
            irq = i2c_smbus_host_notify_to_irq(client);
        } 
        // 如果设备有 DT 节点,则尝试从 DT 节点获取中断号
        else if (dev->of_node) {
            irq = of_irq_get_byname(dev->of_node, "irq");
            if (irq == -EINVAL || irq == -ENODATA)
                irq = of_irq_get(dev->of_node, 0);
        } 
        // 如果设备有 ACPI 关联,则尝试从 ACPI 获取中断号
        else if (ACPI_COMPANION(dev)) {
            irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);
        }

        // 如果获取中断号失败,则设置为 0
        if (irq == -EPROBE_DEFER)
            return irq;
        if (irq < 0)
            irq = 0;

        // 将获取到的中断号设置到 client 结构体中
        client->irq = irq;
    }

    // 如果 driver 没有 ID 表,且设备也没有匹配的 OF 或 ACPI ID 表,则返回 -ENODEV
    if (!driver->id_table &&
        !i2c_acpi_match_device(dev->driver->acpi_match_table, client) &&
        !i2c_of_match_device(dev->driver->of_match_table, client))
        return -ENODEV;

    // 如果 client 需要唤醒功能,则尝试设置唤醒中断
    if (client->flags & I2C_CLIENT_WAKE) {
        int wakeirq = -ENOENT;

        // 如果设备有 DT 节点,则尝试从 DT 节点获取唤醒中断号
        if (dev->of_node) {
            wakeirq = of_irq_get_byname(dev->of_node, "wakeup");
            if (wakeirq == -EPROBE_DEFER)
                return wakeirq;
        }

        // 启用设备的唤醒功能
        device_init_wakeup(&client->dev, true);

        // 如果获取到了唤醒中断号,且与普通中断号不同,则设置专用唤醒中断
        if (wakeirq > 0 && wakeirq != client->irq)
            status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);
        // 否则,使用普通中断作为唤醒中断
        else if (client->irq > 0)
            status = dev_pm_set_wake_irq(dev, client->irq);
        else
            status = 0;

        // 如果设置唤醒中断失败,则输出警告
        if (status)
            dev_warn(&client->dev, "failed to set up wakeup irq\n");
    }

    dev_dbg(dev, "probe\n");

    // 设置设备的时钟默认值
    status = of_clk_set_defaults(dev->of_node, false);
    if (status < 0)
        goto err_clear_wakeup_irq;

    // 附加 PM 域
    status = dev_pm_domain_attach(&client->dev, true);
    if (status)
        goto err_clear_wakeup_irq;

    // 调用 driver 的 probe_new 或 probe 函数
    if (driver->probe_new)
        status = driver->probe_new(client);
    else if (driver->probe)
        status = driver->probe(client,
                               i2c_match_id(driver->id_table, client));
    else
        status = -EINVAL;

    // 如果 probe 函数失败,则清除唤醒中断并分离 PM 域
    if (status) {
        dev_pm_domain_detach(&client->dev, true);
        goto err_clear_wakeup_irq;
    }

    return 0;

err_clear_wakeup_irq:
    dev_pm_clear_wake_irq(&client->dev);
    device_init_wakeup(&client->dev, false);
    return status;
}

第16行-第46行都是关于中断相关的操作,在第45行将获取到的中断号设置到 client 结构体中,所以在FT5X06的驱动中可以直接调用client->irq,至此关于设备树的I2C client的分析就完成了,在下个章节将会对应用程序中使用I2C进行讲解。