首页 > 其他分享 >【驱动】I2C驱动分析(四)-关键API解析

【驱动】I2C驱动分析(四)-关键API解析

时间:2024-01-18 23:02:30浏览次数:33  
标签:API i2c adapter driver dev client 驱动 I2C

简介

 在Linux内核源代码中的driver目录下包含一个i2c目录

  i2c-core.c这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
  i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。

  busses文件夹这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c.
  algos文件夹实现了一些I2C总线适配器的algorithm.

I2C Core

i2c_new_device

i2c_new_device用于创建一个新的I2C设备,这个函数将会使用info提供的信息建立一个i2c_client并与第一个参数指向的i2c_adapter绑定。返回的参数是一个i2c_client指针。驱动中可以直接使用i2c_client指针和设备通信了。

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
	struct i2c_client	*client;
	int			status;

	client = kzalloc(sizeof *client, GFP_KERNEL);
	if (!client)
		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_addr_validity(client->addr, client->flags);
	if (status) {
		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;
	}

	/* Check for address business */
	status = i2c_check_addr_ex(adap, i2c_encode_flags_to_addr(client));
	if (status != 0)
		dev_err(&adap->dev, "%d i2c clients have been registered at 0x%02x",
			status, client->addr);
	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;
	client->dev.fwnode = info->fwnode;

	i2c_dev_set_name(adap, client, status);
	status = device_register(&client->dev);
	if (status)
		goto out_err;

	dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
		client->name, dev_name(&client->dev));

	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;
}
  • 函数通过调用kzalloc函数为client变量分配了一个大小为sizeof *client的内存块,并将其初始化为零。
  • adap参数赋值给client->adapter,表示新创建的设备将使用该I2C适配器。
  • 板级信息中的平台数据,标志位,I2C设备地址,中断号等传给client结构体。
  • 调用i2c_check_addr_validity函数检查I2C设备地址的有效性
  • 调用i2c_check_addr_ex函数检查地址的有效性,如果返回值不为0,表示已经有其他I2C设备注册在相同的地址上。
  • 设置新创建的设备的父设备为适配器的设备。将设备总线类型设置为I2C总线类型,并将设备类型设置为I2C客户端类型。
  • 将与设备树相关的节点of_node,与固件节点相关的节点fwnode传递给新创建的设备。
  • 调用i2c_dev_set_name函数为设备设置名称。
  • 最后会调用device_register函数注册新创建的设备。返回指向新创建的I2C设备的指针。

i2c_device_match

i2c_device_match 函数根据设备和设备驱动程序之间的不同匹配方式,检查它们之间是否存在匹配关系。这个函数通常在 I2C 子系统的设备驱动程序注册过程中使用,以确定哪个驱动程序适用于给定的设备。

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;
}
  • i2c_verify_client 函数用于验证设备是否是有效的 I2C 客户端设备。

  • 进行 OF (Open Firmware) 风格的匹配,通过调用 of_driver_match_device 函数来检查设备和驱动程序之间是否存在匹配关系。

  • 如果 OF 风格的匹配失败,则尝试进行 ACPI (Advanced Configuration and Power Interface) 风格的匹配,通过调用 acpi_driver_match_device 函数来检查设备和驱动程序之间是否存在匹配关系。

  • 如果 OF 和 ACPI 风格的匹配都失败了,代码将继续执行。调用to_i2c_driver 宏将其转换为 `struct i2c_driver 结构体指针。

  • 代码检查 driverid_table 字段是否为空。id_table 是一个指向 I2C 驱动程序支持的设备 ID 表的指针。如果 id_table 不为空,则调用 i2c_match_id 函数,将 driver->id_tableclient 作为参数进行匹配。如果匹配成功,即找到了匹配的设备 ID,函数返回的结果不为空,表示找到了匹配的设备驱动程序。

i2c_device_probe

i2c_device_probe 函数执行了 I2C 设备的探测操作。它设置中断信息、处理唤醒功能、设置时钟、关联功耗域,并调用驱动程序的 probe 函数进行设备特定的探测操作。

static int i2c_device_probe(struct device *dev)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;
	int status;

	if (!client)
		return 0;

	if (!client->irq) {
		int irq = -ENOENT;

		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);
		} else if (ACPI_COMPANION(dev)) {
			irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);
		}
		if (irq == -EPROBE_DEFER)
			return irq;
		if (irq < 0)
			irq = 0;

		client->irq = irq;
	}

	driver = to_i2c_driver(dev->driver);
	if (!driver->probe || !driver->id_table)
		return -ENODEV;

	if (client->flags & I2C_CLIENT_WAKE) {
		int wakeirq = -ENOENT;

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

	dev_dbg(dev, "probe\n");

	status = of_clk_set_defaults(dev->of_node, false);
	if (status < 0)
		goto err_clear_wakeup_irq;

	status = dev_pm_domain_attach(&client->dev, true);
	if (status == -EPROBE_DEFER)
		goto err_clear_wakeup_irq;

	status = driver->probe(client, i2c_match_id(driver->id_table, client));
	if (status)
		goto err_detach_pm_domain;

	return 0;

err_detach_pm_domain:
	dev_pm_domain_detach(&client->dev, true);
err_clear_wakeup_irq:
	dev_pm_clear_wake_irq(&client->dev);
	device_init_wakeup(&client->dev, false);
	return status;
}
  • i2c_verify_client 函数用于验证设备是否是有效的 I2C 客户端设备。如果 client 为空,则说明设备不是有效的 I2C 客户端设备。

  • 检查 client 的中断(IRQ)是否已经设置。如果 client->irq 为 0,说明中断尚未设置。代码尝试从设备的设备树(device tree)或 ACPI 中获取中断信息,并将其赋值给 client->irq

  • 获取设备的驱动程序,并将其赋值给 driver 变量。这里使用了 to_i2c_driver 宏将 dev->driver 转换为 struct i2c_driver 结构体指针。

  • 检查 driverprobeid_table 字段是否为空。如果其中任何一个为空,表示驱动程序不支持探测操作或设备 ID 表,函数返回 -ENODEV(设备不存在)。

  • 如果 I2C 客户端设备的标志(client->flags)中包含 I2C_CLIENT_WAKE,表示该设备支持唤醒功能。代码尝试获取唤醒中断(wakeup IRQ)并进行设置。首先,通过设备的设备树或 ACPI 获取唤醒中断信息。然后,使用 dev_pm_set_dedicated_wake_irqdev_pm_set_wake_irq 函数设置唤醒中断。如果设置失败,会发出警告信息。

  • 调用 of_clk_set_defaults 函数为设备的时钟设置默认值。如果设置失败,跳转到 err_clear_wakeup_irq 标签处进行清理操作。

  • 调用 dev_pm_domain_attach 函数将设备与功耗域(power domain)关联起来。如果探测操作被推迟(deferred),跳转到 err_clear_wakeup_irq 标签处进行清理操作。

  • 调用驱动程序的 probe 函数,并传递 clienti2c_match_id(driver->id_table, client) 作为参数。i2c_match_id 函数用于在设备 ID 表中查找与给定设备匹配的条目。如果 probe 函数返回非零值,表示探测操作失败,跳转到 err_detach_pm_domain 标签处进行清理操作。

i2c_device_remove

i2c_device_remove 函数执行了 I2C 设备的移除操作。它调用驱动程序的 remove 函数,并进行功耗域的分离、唤醒中断的清除以及设备唤醒状态的设置。

static int i2c_device_remove(struct device *dev)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;
	int status = 0;

	if (!client || !dev->driver)
		return 0;

	driver = to_i2c_driver(dev->driver);
	if (driver->remove) {
		dev_dbg(dev, "remove\n");
		status = driver->remove(client);
	}

	dev_pm_domain_detach(&client->dev, true);

	dev_pm_clear_wake_irq(&client->dev);
	device_init_wakeup(&client->dev, false);

	return status;
}
  • i2c_verify_client 函数用于验证设备是否是有效的 I2C 客户端设备。如果 client 为空,则说明设备不是有效的 I2C 客户端设备。

  • 使用 to_i2c_driver 宏将 dev->driver 转换为 struct i2c_driver 结构体指针。

  • 如果驱动程序的 remove 字段不为空,表示驱动程序支持移除操作。开始进行移除操作,并调用驱动程序的 remove 函数。

  • 调用 dev_pm_domain_detach 函数分离设备和功耗域的关联。

  • 调用 dev_pm_clear_wake_irq 函数清除唤醒中断设置。

  • 调用 device_init_wakeup 函数将设备的唤醒状态设置为 false。

i2c_register_adapter

i2c_register_adapter 函数用于注册一个 I2C 适配器。它进行了一系列的完整性检查和初始化操作,并注册适配器设备。然后,注册与适配器相关的设备节点、ACPI 设备和空间处理器。最后,遍历所有的 I2C 驱动程序,并通知它们有新的适配器注册了。

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	int res = 0;

	/* Can't register until after driver model init */
	if (unlikely(WARN_ON(!i2c_bus_type.p))) {
		res = -EAGAIN;
		goto out_list;
	}

	/* Sanity checks */
	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);

	/* Set default timeout to 1 second if not already set */
	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);

	pm_runtime_no_callbacks(&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

	/* bus recovery specific initialization */
	if (adap->bus_recovery_info) {
		struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;

		if (!bri->recover_bus) {
			dev_err(&adap->dev, "No recover_bus() found, not using recovery\n");
			adap->bus_recovery_info = NULL;
			goto exit_recovery;
		}

		/* Generic GPIO recovery */
		if (bri->recover_bus == i2c_generic_gpio_recovery) {
			if (!gpio_is_valid(bri->scl_gpio)) {
				dev_err(&adap->dev, "Invalid SCL gpio, not using recovery\n");
				adap->bus_recovery_info = NULL;
				goto exit_recovery;
			}

			if (gpio_is_valid(bri->sda_gpio))
				bri->get_sda = get_sda_gpio_value;
			else
				bri->get_sda = NULL;

			bri->get_scl = get_scl_gpio_value;
			bri->set_scl = set_scl_gpio_value;
		} else if (!bri->set_scl || !bri->get_scl) {
			/* Generic SCL recovery */
			dev_err(&adap->dev, "No {get|set}_gpio() found, not using recovery\n");
			adap->bus_recovery_info = NULL;
		}
	}

exit_recovery:
	/* create pre-declared device nodes */
	of_i2c_register_devices(adap);
	acpi_i2c_register_devices(adap);
	acpi_i2c_install_space_handler(adap);

	if (adap->nr < __i2c_first_dynamic_bus_num)
		i2c_scan_static_board_info(adap);

	/* Notify drivers */
	mutex_lock(&core_lock);
	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
	mutex_unlock(&core_lock);

	return 0;

out_list:
	mutex_lock(&core_lock);
	idr_remove(&i2c_adapter_idr, adap->nr);
	mutex_unlock(&core_lock);
	return res;
}
  • 检查 i2c_bus_type 的状态,确保驱动模型已经初始化。

  • 完整性检查。它检查适配器的名称 adap->name 和适配器的算法 adap->algo 是否为空。

  • 初始化适配器的互斥锁 adap->bus_lock、用户空间客户端锁 adap->userspace_clients_lock,以及用户空间客户端列表 adap->userspace_clients

  • 如果适配器的超时时间 adap->timeout 为 0,则将其设置为默认值 1 秒(HZ 表示秒数)。

  • 设置适配器设备的名称为 "i2c-%d",其中 %d 会被适配器的编号 adap->nr 替换。然后,它设置适配器设备的总线类型为 &i2c_bus_type,设备类型为 &i2c_adapter_type

  • 代码调用 device_register 函数注册适配器设备。如果注册失败,会返回相应的错误码,并进行清理操作。

  • 代码调用 pm_runtime_no_callbacks 函数设置适配器设备的电源管理运行时回调。

  • 如果定义了宏 CONFIG_I2C_COMPAT,代码调用 class_compat_create_link 函数创建适配器设备与兼容性类之间的链接。

  • 如果适配器具有 bus_recovery_info 字段,表示支持总线恢复功能。代码进行一些适配器恢复功能相关的初始化,包括检查恢复函数的有效性以及设置 GPIO 相关的函数。

  • 如果适配器的编号小于预定义的动态总线编号 __i2c_first_dynamic_bus_num,则调用 i2c_scan_static_board_info 函数扫描静态板级信息。

  • 代码通过调用 bus_for_each_drv 函数遍历所有注册的 I2C 驱动程序,并调用 __process_new_adapter 函数处理每个驱动程序。

  • 最后,返回 0 表示注册成功。

i2c_add_adapter

i2c_add_adapter 函数用于添加一个新的 I2C 适配器。它先尝试从设备树节点中获取适配器的编号,如果成功则使用指定的编号添加适配器。如果没有相关的设备树节点或获取编号失败,函数会在动态范围内分配一个适配器 ID,并将适配器与该 ID 相关联。然后,函数调用 i2c_register_adapter 函数注册适配器,并返回注册函数的返回值。

int i2c_add_adapter(struct i2c_adapter *adapter)
{
	struct device *dev = &adapter->dev;
	int id;

	if (dev->of_node) {
		id = of_alias_get_id(dev->of_node, "i2c");
		if (id >= 0) {
			adapter->nr = id;
			return __i2c_add_numbered_adapter(adapter);
		}
	}

	mutex_lock(&core_lock);
	id = idr_alloc(&i2c_adapter_idr, adapter,
		       __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
	mutex_unlock(&core_lock);
	if (id < 0)
		return id;

	adapter->nr = id;

	return i2c_register_adapter(adapter);
}
  • 首先,代码获取适配器的设备结构体指针 dev,并将其赋值给变量 dev

  • 如果适配器设备具有 of_node 字段,表示设备与设备树节点相关联。代码通过调用 of_alias_get_id 函数从设备树节点中获取 "i2c" 别名的 ID。如果获取的 ID 大于等于 0,则将其赋值给适配器的编号 adapter->nr,并调用 __i2c_add_numbered_adapter 函数以指定的编号添加适配器。然后,函数返回该函数的返回值。

  • 如果设备没有相关的设备树节点或没有获取到别名的 ID,代码通过调用 mutex_lock 函数锁定 core_lock 互斥锁,以确保在添加适配器时不会发生竞争条件。

  • 代码调用 idr_alloc 函数从 i2c_adapter_idr 中分配一个适配器 ID,并将适配器结构体指针 adapter 与该 ID 相关联。参数 __i2c_first_dynamic_bus_num 表示分配的 ID 必须大于或等于该值。如果分配失败,即返回的 ID 小于 0,代码将返回该错误码。

  • 如果成功分配了适配器 ID,代码将该 ID 赋值给适配器的编号 adapter->nr

  • 代码通过调用 i2c_register_adapter 函数注册适配器并返回注册函数 i2c_register_adapter 的返回值。

i2c_detect_address

i2c_detect_address 函数用于检测指定地址上是否存在 I2C 设备,并执行自定义的设备检测函数。它会进行一系列的检查,包括地址的有效性、地址是否已被占用以及地址上是否存在设备。如果检测成功,函数会调用自定义的检测函数并根据检测结果进行相应的处理,包括创建新的设备实例并添加到驱动程序的客户端列表中。

static int i2c_detect_address(struct i2c_client *temp_client,
			      struct i2c_driver *driver)
{
	struct i2c_board_info info;
	struct i2c_adapter *adapter = temp_client->adapter;
	int addr = temp_client->addr;
	int err;

	/* Make sure the address is valid */
	err = i2c_check_7bit_addr_validity_strict(addr);
	if (err) {
		dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
			 addr);
		return err;
	}

	/* Skip if already in use (7 bit, no need to encode flags) */
	if (i2c_check_addr_busy(adapter, addr))
		return 0;

	/* Make sure there is something at this address */
	if (!i2c_default_probe(adapter, addr))
		return 0;

	/* Finally call the custom detection function */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;
	err = driver->detect(temp_client, &info);
	if (err) {
		/* -ENODEV is returned if the detection fails. We catch it
		   here as this isn't an error. */
		return err == -ENODEV ? 0 : err;
	}

	/* Consistency check */
	if (info.type[0] == '\0') {
		dev_err(&adapter->dev, "%s detection function provided "
			"no name for 0x%x\n", driver->driver.name,
			addr);
	} else {
		struct i2c_client *client;

		/* Detection succeeded, instantiate the device */
		if (adapter->class & I2C_CLASS_DEPRECATED)
			dev_warn(&adapter->dev,
				"This adapter will soon drop class based instantiation of devices. "
				"Please make sure client 0x%02x gets instantiated by other means. "
				"Check 'Documentation/i2c/instantiating-devices' for details.\n",
				info.addr);

		dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n",
			info.type, info.addr);
		client = i2c_new_device(adapter, &info);
		if (client)
			list_add_tail(&client->detected, &driver->clients);
		else
			dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
				info.type, info.addr);
	}
	return 0;
}
  • 调用 i2c_check_7bit_addr_validity_strict 函数检查地址的有效性。

  • 代码调用 i2c_check_addr_busy 函数检查地址是否已经在使用。

  • 代码调用 i2c_default_probe 函数检查该地址上是否存在设备。

  • 代码初始化一个 struct i2c_board_info 结构体变量 info,并将地址 addr 赋值给 info.addr 字段。

  • 代码调用设备驱动程序的 detect 函数,传入临时客户端 temp_clientinfo 结构体变量。这个自定义的检测函数用于检测设备是否存在,并填充 info 结构体中的其他字段。如果检测函数返回错误码,函数会检查错误码是否为 -ENODEV(表示设备检测失败),如果是则返回 0 表示不需要进行进一步的设备检测。

  • 如果设备检测成功,函数会检查 info 结构体中的设备名称字段 info.type 是否为空。如果为空,表示自定义的检测函数没有提供设备名称。否则,函数会创建一个新的 I2C 客户端设备,并将其添加到驱动程序的客户端列表中。

  • 如果适配器的 class 字段包含 I2C_CLASS_DEPRECATED 标志,表示该适配器将来会停止使用基于类的设备实例化方法。函数会打印警告信息,提醒开发者使用其他方式实例化设备。

i2c_detect

i2c_detect函数根据给定的适配器和驱动程序,通过遍历地址列表并调用i2c_detect_address函数,检测I2C适配器上连接的设备是否存在。

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
	const unsigned short *address_list;
	struct i2c_client *temp_client;
	int i, err = 0;
	int adap_id = i2c_adapter_id(adapter);

	address_list = driver->address_list;
	if (!driver->detect || !address_list)
		return 0;

	/* Warn that the adapter lost class based instantiation */
	if (adapter->class == I2C_CLASS_DEPRECATED) {
		dev_dbg(&adapter->dev,
			"This adapter dropped support for I2C classes and "
			"won't auto-detect %s devices anymore. If you need it, check "
			"'Documentation/i2c/instantiating-devices' for alternatives.\n",
			driver->driver.name);
		return 0;
	}

	/* Stop here if the classes do not match */
	if (!(adapter->class & driver->class))
		return 0;

	/* Set up a temporary client to help detect callback */
	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!temp_client)
		return -ENOMEM;
	temp_client->adapter = adapter;

	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
		dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
			"addr 0x%02x\n", adap_id, address_list[i]);
		temp_client->addr = address_list[i];
		err = i2c_detect_address(temp_client, driver);
		if (unlikely(err))
			break;
	}

	kfree(temp_client);
	return err;
}

这段代码是一个用于检测I2C适配器上连接的设备的函数。下面是对代码的详细解释:

  • i2c_adapter_id(adapter)用于获取适配器的ID,并将其赋值给adap_id变量。

  • 如果adapterclassdriverclass不匹配,则返回0,表示不进行设备检测。

  • 创建一个临时的i2c_client结构体对象temp_client,并将其分配到内存中。

  • 使用一个循环遍历address_list数组中的地址,直到遇到I2C_CLIENT_END为止。在循环中,打印适配器的ID和当前地址,然后将当前地址赋值给temp_clientaddr成员。

  • 调用i2c_detect_address函数来检测指定地址上是否存在设备。

I2C dev

i2c_dev_init

i2c_dev_init执行了一系列操作,包括注册字符设备、创建设备类、注册总线通知器以及绑定已经存在的适配器。它在初始化过程中处理了可能发生的错误,并返回相应的错误码。

static int __init i2c_dev_init(void)
{
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");

	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;

	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class)) {
		res = PTR_ERR(i2c_dev_class);
		goto out_unreg_chrdev;
	}
	i2c_dev_class->dev_groups = i2c_groups;

	/* Keep track of adapters which will be added or removed later */
	res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
	if (res)
		goto out_unreg_class;

	/* Bind to already existing adapters right away */
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}
  • 调用register_chrdev函数注册字符设备,使用I2C_MAJOR作为主设备号,"i2c"作为设备名称,&i2cdev_fops是指向字符设备操作函数的指针。如果注册失败,跳转到out标签处。

  • 使用class_create函数创建一个设备类对象i2c_dev_class,"i2c-dev"作为类名称。如果创建失败,跳转到out_unreg_chrdev标签处。

  • 使用bus_register_notifier函数注册总线通知器,将i2cdev_notifier作为通知回调函数,以便跟踪稍后要添加或删除的适配器。如果注册失败,跳转到out_unreg_class标签处。

  • 调用i2c_for_each_dev函数来绑定已经存在的适配器。该函数使用i2cdev_attach_adapter作为回调函数,在设备上执行绑定操作。

  • 如果有任何错误发生,跳转到out_unreg_classout_unreg_chrdevout标签处执行相应的清理操作。

i2cdev_attach_adapter

i2cdev_attach_adapter作用是将I2C适配器注册到Linux内核中,以便在系统中使用I2C总线。它会获取一个空闲的struct i2c_dev结构体,然后使用device_create函数创建一个I2C设备,并将其与驱动核心相关联。

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;
	int res;

	if (dev->type != &i2c_adapter_type)
		return 0;
	adap = to_i2c_adapter(dev);

	i2c_dev = get_free_i2c_dev(adap);
	if (IS_ERR(i2c_dev))
		return PTR_ERR(i2c_dev);

	/* register this i2c device with the driver core */
	i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
				     MKDEV(I2C_MAJOR, adap->nr), NULL,
				     "i2c-%d", adap->nr);
	if (IS_ERR(i2c_dev->dev)) {
		res = PTR_ERR(i2c_dev->dev);
		goto error;
	}

	pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
		 adap->name, adap->nr);
	return 0;
error:
	return_i2c_dev(i2c_dev);
	return res;
}
  • 检查传入的设备是否为I2C适配器类型。如果不是,则直接返回0。

  • 调用get_free_i2c_dev函数获取一个空闲的i2c_dev结构体,并将其赋值给i2c_dev变量。

  • device_create创建一个I2C设备,并将其与驱动核心相关联。设备的主设备号为I2C_MAJOR,次设备号为adap->nr,设备名为i2c-%d,其中%d会被替换为适配器的编号。

  • 检查设备创建过程是否出错。如果出错,则将错误码赋值给res变量,并跳转到error标签处进行错误处理。

i2cdev_open

i2cdev_open通过次设备号获取对应的i2c_dev结构体和适配器,然后分配并初始化一个i2c_client结构体,最后将其赋值给文件的私有数据。

static int i2cdev_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);
	struct i2c_client *client;
	struct i2c_adapter *adap;
	struct i2c_dev *i2c_dev;

	i2c_dev = i2c_dev_get_by_minor(minor);
	if (!i2c_dev)
		return -ENODEV;

	adap = i2c_get_adapter(i2c_dev->adap->nr);
	if (!adap)
		return -ENODEV;

	/* This creates an anonymous i2c_client, which may later be
	 * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
	 *
	 * This client is ** NEVER REGISTERED ** with the driver model
	 * or I2C core code!!  It just holds private copies of addressing
	 * information and maybe a PEC flag.
	 */
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client) {
		i2c_put_adapter(adap);
		return -ENOMEM;
	}
	snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

	client->adapter = adap;
	file->private_data = client;

	return 0;
}
  • 通过次设备号获取相应的i2c_dev结构体,该结构体表示I2C设备。
  • 通过适配器编号获取相应的i2c_adapter结构体,该结构体表示I2C适配器。
  • 使用kzalloc函数分配一块内存,大小为sizeof(*client),用于存储i2c_client结构体的信息。
  • 使用snprintf函数将适配器编号格式化为字符串,存储在client->name中。
  • i2c_adapter结构体赋值给i2c_client结构体的adapter成员变量。将client指针赋值给文件的私有数据,以便在后续的文件操作中使用。

i2cdev_write

i2cdev_write函数将用户空间的数据复制到内核空间,并使用i2c_master_send函数将数据发送到之前打开的I2C设备中。

static ssize_t i2cdev_write(struct file *file, const char __user *buf,
		size_t count, loff_t *offset)
{
	int ret;
	char *tmp;
	struct i2c_client *client = file->private_data;

	if (count > 8192)
		count = 8192;

	tmp = memdup_user(buf, count);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
		iminor(file_inode(file)), count);

	ret = i2c_master_send(client, tmp, count);
	kfree(tmp);
	return ret;
}
  • 首先从文件的私有数据中获取之前打开的I2C设备的i2c_client结构体指针。

  • 限制写入的数据长度不超过8192字节,如果超过了则将其截断为8192字节。

  • 使用memdup_user函数将用户空间的buf中的数据复制到内核空间,并将复制后的数据的指针赋值给tmp

  • 使用i2c_master_send函数将数据发送到I2C设备,返回值存储在ret中。

i2cdev_read

i2cdev_read函数在内核中分配一个缓冲区,使用i2c_master_recv函数从I2C设备中接收数据,并将接收到的数据复制到用户空间。

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
		loff_t *offset)
{
	char *tmp;
	int ret;

	struct i2c_client *client = file->private_data;

	if (count > 8192)
		count = 8192;

	tmp = kmalloc(count, GFP_KERNEL);
	if (tmp == NULL)
		return -ENOMEM;

	pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
		iminor(file_inode(file)), count);

	ret = i2c_master_recv(client, tmp, count);
	if (ret >= 0)
		ret = copy_to_user(buf, tmp, count) ? -EFAULT : ret;
	kfree(tmp);
	return ret;
}
  • 从文件的私有数据中获取之前打开的I2C设备的i2c_client结构体指针。

  • 限制读取的数据长度不超过8192字节,如果超过了则将其截断为8192字节。

  • 在内核中分配一个大小为count字节的缓冲区,用于存储从I2C设备读取的数据。

  • 使用i2c_master_recv函数从I2C设备中接收数据,返回值存储在ret中。

  • 如果数据接收成功,使用copy_to_user将数据从内核空间复制到用户空间的buf中。

i2cdev_ioctl

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = file->private_data;
	unsigned long funcs;

	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
		cmd, arg);

	switch (cmd) {
	case I2C_SLAVE:
	case I2C_SLAVE_FORCE:
		if ((arg > 0x3ff) ||
		    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
			return -EINVAL;
		if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
			return -EBUSY;
		/* REVISIT: address could become busy later */
		client->addr = arg;
		return 0;
	case I2C_TENBIT:
		if (arg)
			client->flags |= I2C_M_TEN;
		else
			client->flags &= ~I2C_M_TEN;
		return 0;
	case I2C_PEC:
		/*
		 * Setting the PEC flag here won't affect kernel drivers,
		 * which will be using the i2c_client node registered with
		 * the driver model core.  Likewise, when that client has
		 * the PEC flag already set, the i2c-dev driver won't see
		 * (or use) this setting.
		 */
		if (arg)
			client->flags |= I2C_CLIENT_PEC;
		else
			client->flags &= ~I2C_CLIENT_PEC;
		return 0;
	case I2C_FUNCS:
		funcs = i2c_get_functionality(client->adapter);
		return put_user(funcs, (unsigned long __user *)arg);

	case I2C_RDWR:
		return i2cdev_ioctl_rdwr(client, arg);

	case I2C_SMBUS:
		return i2cdev_ioctl_smbus(client, arg);

	case I2C_RETRIES:
		if (arg > INT_MAX)
			return -EINVAL;

		client->adapter->retries = arg;
		break;
	case I2C_TIMEOUT:
		if (arg > INT_MAX)
			return -EINVAL;

		/* For historical reasons, user-space sets the timeout
		 * value in units of 10 ms.
		 */
		client->adapter->timeout = msecs_to_jiffies(arg * 10);
		break;
	default:
		/* NOTE:  returning a fault code here could cause trouble
		 * in buggy userspace code.  Some old kernel bugs returned
		 * zero in this case, and userspace code might accidentally
		 * have depended on that bug.
		 */
		return -ENOTTY;
	}
	return 0;
}
  • I2C_SLAVEI2C_SLAVE_FORCE:设置I2C设备的从设备地址。首先检查地址是否合法,如果地址超出范围或者设备不支持10位地址且地址超过7位,则返回错误码-EINVAL。如果是I2C_SLAVE命令并且地址已经被占用,则返回错误码-EBUSY。将client->addr设置为参数arg的值,表示从设备地址已经设置成功。
  • I2C_TENBIT:设置I2C设备是否使用10位地址。如果参数arg为非零值,则设置client->flagsI2C_M_TEN标志位,表示使用10位地址。否则,清除该标志位,表示使用7位地址。
  • I2C_PEC:设置I2C设备是否启动PEC(奇偶校验)。如果参数arg为非零值,则设置client->flagsI2C_CLIENT_PEC标志位,表示启用PEC。否则,清除该标志位,表示禁用PEC。
  • I2C_FUNCS:获取I2C设备支持的功能并将其值存储在funcs变量中。然后使用put_user函数将funcs的值复制到用户空间的arg指定的地址上,并返回操作结果。
  • I2C_RDWR:调用i2cdev_ioctl_rdwr函数处理I2C读写操作。
  • I2C_SMBUS:调用i2cdev_ioctl_smbus函数处理I2C SMBus操作。
  • I2C_RETRIES:设置I2C总线的重试次数。首先检查参数arg是否超过了整型的最大值,如果超过则返回错误码-EINVAL。然后将client->adapter->retries设置为参数arg的值,表示重试次数已经设置成功。
  • I2C_TIMEOUT:设置I2C总线的超时时间。首先检查参数arg是否超过了整型的最大值,如果超过则返回错误码-EINVAL。然后将client->adapter->timeout设置为参数arg乘以10得到的值(以毫秒为单位),表示超时时间已经设置成功。

i2c_driver

i2c_register_driver

i2c_register_driver将驱动程序注册到I2C驱动核心,并在注册完成后处理所有已经存在的适配器。注册完成后,驱动核心会调用probe()函数来匹配并初始化所有匹配的但未绑定的设备。

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	/* Can't register until after driver model init */
	if (unlikely(WARN_ON(!i2c_bus_type.p)))
		return -EAGAIN;

	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;
	driver->driver.bus = &i2c_bus_type;
	INIT_LIST_HEAD(&driver->clients);

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver);
	if (res)
		return res;

	pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

	/* Walk the adapters that are already present */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
  • 检查是否可以在驱动模型初始化之后进行注册。如果驱动总线类型i2c_bus_type未初始化(i2c_bus_type.p为空),则打印警告信息,并返回错误码-EAGAIN

  • 将驱动程序的所有者(owner)设置为参数owner指定的模块,并将驱动程序的总线(bus)设置为&i2c_bus_type,表示使用I2C总线。

  • 初始化驱动程序的clients链表头。

  • 调用driver_register函数将驱动程序注册到驱动核心(driver core)。注册完成后,驱动核心会为所有匹配但未绑定的设备调用probe()函数。

  • 遍历已经存在的适配器(adapters),对每个适配器调用__process_new_driver函数进行处理。

I2C 传输

i2c_transfer

i2c_transfer用于执行I2C传输操作。它首先检查是否支持主控制器,如果支持,则打印调试信息,尝试对适配器进行锁定,然后调用__i2c_transfer函数执行传输操作,并在完成后解锁适配器并返回传输的结果。如果不支持主控制器,则返回不支持的错误码。

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int ret;

	/* REVISIT the fault reporting model here is weak:
	 *
	 *  - When we get an error after receiving N bytes from a slave,
	 *    there is no way to report "N".
	 *
	 *  - When we get a NAK after transmitting N bytes to a slave,
	 *    there is no way to report "N" ... or to let the master
	 *    continue executing the rest of this combined message, if
	 *    that's the appropriate response.
	 *
	 *  - When for example "num" is two and we successfully complete
	 *    the first message but get an error part way through the
	 *    second, it's unclear whether that should be reported as
	 *    one (discarding status on the second message) or errno
	 *    (discarding status on the first one).
	 */

	if (adap->algo->master_xfer) {
#ifdef DEBUG
		for (ret = 0; ret < num; ret++) {
			dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
				"len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
				? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
				(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
		}
#endif

		if (in_atomic() || irqs_disabled()) {
			ret = i2c_trylock_adapter(adap);
			if (!ret)
				/* I2C activity is ongoing. */
				return -EAGAIN;
		} else {
			i2c_lock_adapter(adap);
		}

		ret = __i2c_transfer(adap, msgs, num);
		i2c_unlock_adapter(adap);

		return ret;
	} else {
		dev_dbg(&adap->dev, "I2C level transfers not supported\n");
		return -EOPNOTSUPP;
	}
}
  • 检查是否支持master_xfer函数指针,即I2C适配器的主控制器(master controller)是否存在。

  • 如果支持主控制器,则打印调试信息,显示每个消息的属性,如读写方向、地址、长度等。

  • 如果当前处于原子操作或中断被禁止的情况下,尝试对适配器进行非阻塞锁定。如果锁定失败,表示I2C活动正在进行中,函数返回错误码-EAGAIN

  • 如果不处于原子操作或中断被禁止的情况下,对适配器进行阻塞锁定。

  • 调用__i2c_transfer函数执行实际的I2C传输。

  • 解锁适配器,返回传输的结果。

  • 如果不支持主控制器,则打印调试信息,表示不支持I2C级别的传输。

i2c_master_send

i2c_master_send通过I2C主控制器向从设备发送数据。它构建一个i2c_msg结构,设置消息的地址、标志、长度和缓冲区,并将其传递给i2c_transfer函数执行实际的传输操作。函数的返回值是发送的字节数或错误码,用于指示传输是否成功。

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
	int ret;
	struct i2c_adapter *adap = client->adapter;
	struct i2c_msg msg;

	msg.addr = client->addr;
	msg.flags = client->flags & I2C_M_TEN;
	msg.len = count;
	msg.buf = (char *)buf;

	ret = i2c_transfer(adap, &msg, 1);

	/*
	 * If everything went ok (i.e. 1 msg transmitted), return #bytes
	 * transmitted, else error code.
	 */
	return (ret == 1) ? count : ret;
}
  • client结构中获取I2C适配器指针adap

  • 定义i2c_msg结构变量msg,并设置其成员变量:

    • addr:设置为从设备的地址。

    • flags:设置为client结构中的flags成员与I2C_M_TEN按位与的结果。

    • len:设置为要发送的数据字节数count

    • buf:设置为要发送的数据缓冲区指针buf

  • 调用i2c_transfer函数执行I2C传输,将适配器指针和msg结构的地址作为参数传递。

  • 根据返回值判断传输是否成功:

  • 如果返回值为1,表示成功传输了1个消息,返回发送的字节数count

  • 否则,返回错误码。

i2c_master_recv

i2c_master_recv通过I2C主控制器从从设备接收数据。它构建一个i2c_msg结构,设置消息的地址、标志、长度和缓冲区,并将其传递给i2c_transfer函数执行实际的传输操作。函数的返回值是接收的字节数或错误码,用于指示传输是否成功。

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
	struct i2c_adapter *adap = client->adapter;
	struct i2c_msg msg;
	int ret;

	msg.addr = client->addr;
	msg.flags = client->flags & I2C_M_TEN;
	msg.flags |= I2C_M_RD;
	msg.len = count;
	msg.buf = buf;

	ret = i2c_transfer(adap, &msg, 1);

	/*
	 * If everything went ok (i.e. 1 msg received), return #bytes received,
	 * else error code.
	 */
	return (ret == 1) ? count : ret;
}
  • client结构中获取I2C适配器指针adap

  • 定义i2c_msg结构变量msg,并设置其成员变量:

    • addr:设置为从设备的地址。

    • flags:设置为client结构中的flags成员与I2C_M_TEN按位与的结果,表示读取数据。

    • flags还通过按位或操作符|=设置为I2C_M_RD,以指示读取操作。

    • len:设置为要接收的数据字节数count

    • buf:设置为用于接收数据的缓冲区指针buf

  • 调用i2c_transfer函数执行I2C传输,将适配器指针和msg结构的地址作为参数传递。

  • 根据返回值判断传输是否成功:返回值是接收的字节数或错误码,用于指示传输是否成功。

本文参考

https://blog.csdn.net/qq_45172832/article/details/131221971

https://hello2mao.github.io/2015/12/02/Linux_I2C_driver/

https://blog.csdn.net/m0_46577050/article/details/122315569

标签:API,i2c,adapter,driver,dev,client,驱动,I2C
From: https://www.cnblogs.com/dongxb/p/17973616

相关文章

  • 【驱动】I2C驱动分析(六)-I2C驱动模板
    前言LinuxI2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。下面我们看下如何写一个基本的I2C驱动。内......
  • 【驱动】I2C驱动分析(一)-I2C驱协议简介
    什么是I²CI²C叫集成电路总线它是一种串行通信接口,具有双向两线同步串行总线,通常由两根线组成——SDA(串行数据线)和SCL(串行时钟线)和上拉电阻。它们用于需要许多不同部件(例如传感器、引脚、扩展和驱动程序)协同工作的项目,因为它们可以将多达128个设备连接到主板,同时保持清晰......
  • CPLEX通过Python API获取Gap值的方法
    写在前面最近在使用Cplex求解模型,尽管Cplex的PythonAPI会自动输出引擎日志,但在多次求解中一次次看引擎日志找Gap值并做实验记录很麻烦,所以需要找到获取Gap值的方法。然而我在Cplex的官方文档中并没有找到这个方法,然后我就一个个去试这些方法,可算是给我试出来了。解决方法在Cpl......
  • 精品IDEA插件推荐:Apipost-Helper
    Apipost-Helper是由Apipost推出的IDEA插件,写完接口可以进行快速调试,且支持搜索接口、根据method跳转接口,还支持生成标准的API文档,注意:这些操作都可以在代码编辑器内独立完成,非常好用!这里给大家介绍一下Apipost-Helper的安装和使用安装在IDEA编辑器插件中心输入Apipost搜索安装:......
  • 开发者的API利器:Apipost
    在当今的数字化时代,数据流通是推动社会进步的关键因素之一。其中,API(应用编程接口)已经成为跨平台数据交互的标准。然而,API开发和管理并非易事,Apipost一体化研发协作赋能平台,支持从API设计到API调试再到API测试覆盖整个API生命周期的API管理平台,一起来看看Apipost有什么不同吧。一......
  • 世微AP2121太阳能草坪灯驱动芯片
    概述AP2121是一款专为太阳能LED草坪灯设计的专用集成电路。AP2121仅需一个外接电感即可组成太阳能照明装置。AP2121由开关型驱动电路、光控开关电路、过放保护电路、内部集成的肖特基二极管等电路组成。AP2121采用专利技术,使得欠压关断时LED灯无闪烁AP2121工......
  • Flink DataStream API 编程模型
    Flink系列文章第01讲:Flink的应用场景和架构模型第02讲:Flink入门程序WordCount和SQL实现第03讲:Flink的编程模型与其他框架比较第04讲:Flink常用的DataSet和DataStreamAPI第05讲:FlinkSQL&Table编程和案例第06讲:Flink集群安装部署和HA配置第07讲:Flink常见......
  • 解决vue用axiso两次访问后台api,发现后台的sessionID不一致
    我用的是ASP.NETCore7.0做的后台api,在解决了跨域问题(这个问题在官网上就有答案https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?view=aspnetcore-7.0)为了方便阅读,我再讲一下在里progam里面增加代码(黄色代码),代码格式我就把不变的放到一起了解决完这个之后,因为要......
  • 基于java调用stable diffusion api
    基于Java调用StableDiffusionAPI在现代的信息社会中,数据的传输和处理变得越来越重要。在这个过程中,有时候我们需要将数据稳定地传输给多个接收方。为了满足这个需求,StableDiffusionAPI应运而生。本文将介绍如何使用Java调用StableDiffusionAPI,并提供相应的代码示例。首先,我......
  • java 调用 stable diffusion api
    Java调用StableDiffusionAPI引言在现代软件开发中,大多数应用程序都需要与其他系统进行数据交换。为了实现这一目标,我们需要使用API(ApplicationProgrammingInterface)来与其他系统进行通信。API可以是Web服务、库或其他类型的接口。在本文中,我们将探讨如何使用Java编......