简介
在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
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