嵌入式系统与单片机|技术阅读
登录|注册

您现在的位置是:嵌入式系统与单片机 > 技术阅读 > 实例化i2c设备的4种方式

实例化i2c设备的4种方式

前篇关于i2c驱动的架构,, 中介绍了kernel提供的i2c framework。但是没有写完i2c 设备驱动。

如何实例化i2c设备?4种方式如下:

  • 方法1:静态声明I2C设备

    • 通过devicetree声明I2C设备

    • 在板级文件中声明I2C设备

  • 方法2:显式实例化设备

  • 方法3:对某些设备的I2C总线进行探测

  • 方法4:从用户空间实例化


在本篇中,主要看一下方法1,3和方法4。对于方法2,在下一篇的一个I2C ADC/DAC的misc设备驱动实例中再加以详述。

通过devicetree声明I2C设备

翻出很久之前的一篇博客,如何通过 DeviceTree Overlays来实例化rtc ds3231, 

https://blog.csdn.net/feiwatson/article/details/81072640

/dts-v1/;/plugin/;/ { compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709";

fragment@0 { target = <&i2c1>; __overlay__ { #address-cells = <1>; #size-cells = <0>; rtc@68 { compatible = "maxim,ds3231"; reg = <0x68>; #address-cells = <2>; #size-cells = <1>; }; }; }; };首先,写一个dts。编译,并将生成的.dtbo到/boot/overlays重启后,查看/dev可以看到rtc

通过板级文件中声明I2C设备

在架构板级文件中添加i2c设备信息,并注册到特定位置。看一个nxp imx开发板的电源芯片的实例,arch/arm/mach-imx/mach-mx35_3ds.c

static struct mc13xxx_platform_data mx35_3ds_mc13892_data = { .flags = MC13XXX_USE_RTC | MC13XXX_USE_TOUCHSCREEN, .regulators = { .num_regulators = ARRAY_SIZE(mx35_3ds_regulators), .regulators = mx35_3ds_regulators, },};

#define GPIO_PMIC_INT IMX_GPIO_NR(2, 0)

static struct i2c_board_info mx35_3ds_i2c_mc13892 = {

I2C_BOARD_INFO("mc13892", 0x08), .platform_data = &mx35_3ds_mc13892_data, /* irq number is run-time assigned */};

static void __init imx35_3ds_init_mc13892(void){ int ret = gpio_request_one(GPIO_PMIC_INT, GPIOF_DIR_IN, "pmic irq");

if (ret) { pr_err("failed to get pmic irq: %d\n", ret); return; }

mx35_3ds_i2c_mc13892.irq = gpio_to_irq(GPIO_PMIC_INT); i2c_register_board_info(0, &mx35_3ds_i2c_mc13892, 1);}

MC13892是面向i.MX51、i.MX37、i.MX3和i.MX27应用处理器的PMIC。在板级文件中定义了一个 i2c_board_info 结构体,使用i2c_register_board_info函数将i2c设备信息添加到特定链表。

i2c_register_board_info在/drivers/i2c/i2c-boardinfo.c中:

/*** i2c_register_board_info - statically declare I2C devices* @busnum: identifies the bus to which these devices belong* @info: vector of i2c device descriptors* @len: how many descriptors in the vector; may be zero to reserve* the specified bus number.** Systems using the Linux I2C driver stack can declare tables of board info* while they initialize. This should be done in board-specific init code* near arch_initcall() time, or equivalent, before any I2C adapter driver is* registered. For example, mainboard init code could define several devices,* as could the init code for each daughtercard in a board stack.** The I2C devices will be created later, after the adapter for the relevant* bus has been registered. After that moment, standard driver model tools* are used to bind "new style" I2C drivers to the devices. The bus number* for any device declared using this routine is not available for dynamic* allocation.** The board info passed can safely be __initdata, but be careful of embedded* pointers (for platform_data, functions, etc) since that won't be copied.* Device properties are deep-copied though.*/int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)


对某些设备的I2C总线进行探测

有时没有关于I2C设备的足够信息,甚至无法调用i2c_new_scanned_device()。典型的例子是PC主板上的硬件监控芯片。有几十个模型,可能被分配在25个不同的地址。考虑到主板的庞大数量,几乎不可能建立一个正在使用的硬件监控芯片的详尽列表。幸运的是,大多数这些芯片都有制造商和设备ID寄存器,因此可以通过探测来识别它们。

在这种情况下,I2C设备既没有显式声明也没有实例化。相反,一旦这些设备的驱动程序被加载,I2C -core就会探测它们,如果找到了,就会自动实例化一个I2C设备。为了防止此机制的任何不当行为,适用以下限制:

  • I2C设备驱动程序必须实现detect()方法,该方法通过从任意寄存器读取来识别受支持的设备。

  • 只有那些可能有支持设备并同意被探测的总线才会被探测。例如,这避免了探测电视适配器上的硬件监控芯片。


detect何时调用?

i2c_driver注册的时候,i2c_core会在所有已经注册的i2c_adapter上探测address_list中的所有地址,硬件探测成功之后后调用i2c_driver的detect成员,然后根据detect填充的info建立一个i2c_client。

例如,MAX1668温度传感器的驱动实现,/drivers/hwmon/max1668.c

static struct i2c_driver max1668_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "max1668", }, .probe = max1668_probe, .id_table = max1668_id, .detect = max1668_detect, .address_list = max1668_addr_list,};

module_i2c_driver(max1668_driver);

其中,detect函数

/* Return 0 if detection is successful, -ENODEV otherwise */static int max1668_detect(struct i2c_client *client, struct i2c_board_info *info){ struct i2c_adapter *adapter = client->adapter; const char *type_name; int man_id, dev_id;

if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -ENODEV;

/* Check for unsupported part */ man_id = i2c_smbus_read_byte_data(client, MAX1668_REG_MAN_ID); if (man_id != MAN_ID_MAXIM) return -ENODEV;

dev_id = i2c_smbus_read_byte_data(client, MAX1668_REG_DEV_ID); if (dev_id < 0) return -ENODEV;

type_name = NULL; if (dev_id == DEV_ID_MAX1668) type_name = "max1668"; else if (dev_id == DEV_ID_MAX1805) type_name = "max1805"; else if (dev_id == DEV_ID_MAX1989) type_name = "max1989";

if (!type_name) return -ENODEV;

strlcpy(info->type, type_name, I2C_NAME_SIZE);

return 0;}

以树莓派为例,i2c_add_adapter会在bcm2835_i2c_probe调用

static int bcm2835_i2c_probe(struct platform_device *pdev)

在i2c_register_adapter 中会调用,bus_for_each_drv来通知所有总线类型是i2c_bus_type的driver。

/* Notify drivers */ mutex_lock(&core_lock); bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter); mutex_unlock(&core_lock);
static int __process_new_adapter(struct device_driver *d, void *data){ return i2c_do_add_adapter(to_i2c_driver(d), data);}

i2c_do_add_adapter->i2c_detect->i2c_detect_address 中有一段关键code

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


从用户空间实例化

之前的一篇博客,https://blog.csdn.net/feiwatson/article/details/81048616

用户空间通过两个sysfs属性文件来建立和删除i2c_client:new_device和delete_device。
  • new_device有两个参数:i2c设备的名字(字符串)和地址(以0x开头的16进制数)。

  • delete_device只有一个参数:设备的地址。


pi@raspberrypi:/sys/class/i2c-adapter/i2c-1 $ lsdelete_device device i2c-dev name new_device of_node power subsystem uevent

添加rtc设备

pi@raspberrypi:/sys/class/i2c-adapter/i2c-1 $ echo ds3231 0x68 | sudo tee new_device ds3231 0x68


方法2:显式实例化设备,在下一篇"在Linux系统里如何编写一个PCF8591的驱动,完成ADC数据采集,DAC数据输出"中再详述。

PCF8591 是一个单片集成、单独供电、低功耗、8-bit CMOS数据获取器件。PCF8591 具有 4 个模拟输入、1 个模拟输出和 1个串行 I2C 总线接口。PCF8591 的 3 个地址引脚 A0, A1 和 A2 可用于硬件地址编程,允许在同个 I2C 总线上接入 8 个 PCF8591 器件,而无需额外的硬件。在 PCF8591 器件上输入输出的地址、控制和数据信号都是通过双线双向 I2C 总线以串行的方式进行传输。

文末附上之前的文章: