【驱动】I2C驱动分析(四)-关键API解析
简介
在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 结构体指针。 -
代码检查
driver
的id_table
字段是否为空。id_table
是一个指向 I2C 驱动程序支持的设备 ID 表的指针。如果id_table
不为空,则调用i2c_match_id
函数,将driver->id_table
和client
作为参数进行匹配。如果匹配成功,即找到了匹配的设备 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
结构体指针。 -
检查
driver
的probe
和id_table
字段是否为空。如果其中任何一个为空,表示驱动程序不支持探测操作或设备 ID 表,函数返回 -ENODEV(设备不存在)。 -
如果 I2C 客户端设备的标志(
client->flags
)中包含I2C_CLIENT_WAKE
,表示该设备支持唤醒功能。代码尝试获取唤醒中断(wakeup IRQ)并进行设置。首先,通过设备的设备树或 ACPI 获取唤醒中断信息。然后,使用dev_pm_set_dedicated_wake_irq
或dev_pm_set_wake_irq
函数设置唤醒中断。如果设置失败,会发出警告信息。 -
调用
of_clk_set_defaults
函数为设备的时钟设置默认值。如果设置失败,跳转到err_clear_wakeup_irq
标签处进行清理操作。 -
调用
dev_pm_domain_attach
函数将设备与功耗域(power domain)关联起来。如果探测操作被推迟(deferred),跳转到err_clear_wakeup_irq
标签处进行清理操作。 -
调用驱动程序的
probe
函数,并传递client
和i2c_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_client
和info
结构体变量。这个自定义的检测函数用于检测设备是否存在,并填充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
变量。 -
如果
adapter
的class
与driver
的class
不匹配,则返回0,表示不进行设备检测。 -
创建一个临时的
i2c_client
结构体对象temp_client
,并将其分配到内存中。 -
使用一个循环遍历
address_list
数组中的地址,直到遇到I2C_CLIENT_END
为止。在循环中,打印适配器的ID和当前地址,然后将当前地址赋值给temp_client
的addr
成员。 -
调用
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_class
、out_unreg_chrdev
或out
标签处执行相应的清理操作。
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_SLAVE
和I2C_SLAVE_FORCE
:设置I2C设备的从设备地址。首先检查地址是否合法,如果地址超出范围或者设备不支持10位地址且地址超过7位,则返回错误码-EINVAL
。如果是I2C_SLAVE
命令并且地址已经被占用,则返回错误码-EBUSY
。将client->addr
设置为参数arg
的值,表示从设备地址已经设置成功。I2C_TENBIT
:设置I2C设备是否使用10位地址。如果参数arg
为非零值,则设置client->flags
的I2C_M_TEN
标志位,表示使用10位地址。否则,清除该标志位,表示使用7位地址。I2C_PEC
:设置I2C设备是否启动PEC(奇偶校验)。如果参数arg
为非零值,则设置client->flags
的I2C_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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架