简介
在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);elsestatus = 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_COMPATres = 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;elsebri->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 ithere 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);elsedev_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;elseclient->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;elseclient->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 DEBUGfor (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) ? "+" : "");}
#endifif (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