i2c系统驱动程序模型

I2C系统驱动模型

参考资料

  • Linux内核文档:
    • Documentation\i2c\instantiating-devices.rst
    • Documentation\i2c\writing-clients.rst
  • Linux内核驱动程序示例:
    • drivers/eeprom/at24.c

1.I2C驱动程序的层次

在这里插入图片描述
I2C Core就是I2c核心层,它的作用:

  • 提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等
  • 实现I2C总线-设备-驱动模型,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)

2. I2C总线 - 设备 - 驱动模型

平台总线说明
之前用过Linux的平台设备总线(platform_bus_type),对于上面的图左边,平台设备总线应该是platform_device。保存了的一些设备相关的信息。右边会是platform_driver,保存了一些代码,根据左边的信息操作。左边的platform_device是经常容易变化的部分, 而右边则是稳定的部分。
代码的稳定性
如果修改了硬件结构,那么只需要修改左边的platform_device就够了。新版本中platform_device会来自设备树,这样的话,比如修改了引脚,只需要修改设备树就好了。内核启动时根据设备树的信息生成device。整个修改过程不需要改内核,不需要改驱动。
设备匹配
platform_driver通过probe函数、device_table表明支持哪些设备。当platform_device注册时,回去platform_driver一一比较,如果匹配成功就会去调用probe函数。匹配函数就是platform_bus提供的match函数。
返过来,如果已经有了platform_device,而新增加了platform_driver。同样会调用match函数,去一个一个比较platform_device。匹配的话就会调用probe函数。

I2C总线
在这里插入图片描述

对于平台总线,它是一条抽象的虚拟总线,而I2C总线则时一条真实的总线。在i2c_bus_type的两边也有两个链表,一个链表会挂有一个或多个i2c_client(I2C设备),另一个链表会挂有一个或多个i2c_driver (I2C驱动)。i2c_bus_type里面有match函数,用来检测左右两边是否匹配。
左边的i2c_client用来描述硬件信息,比如i2c的地址、名字、对应的adapter(i2c总线)。右边有各种操作函数。如果左右两边匹配的话,那么就会调用probe_new函数来注册。

//i2c-core.c
struct bus_type i2c_bus_type = {
    .name       = "i2c",
    .match      = i2c_device_match,
    .probe      = i2c_device_probe,
    .remove     = i2c_device_remove,
    .shutdown   = i2c_device_shutdown,
};

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 进行of风格(设备树)的类型比较  */ 
    if (of_driver_match_device(dev, drv)) 		//这里把drv->of_match_table做匹配
        return 1; 
    
    /* Then ACPI style match */
    if (acpi_driver_match_device(dev, drv))		//匹配acpi_device_id
        return 1;

    driver = to_i2c_driver(drv);
    /* match on an id table if there is one 对于i2c_client无论时设备树还是代码里生成的,都会有一个name用来比较 */
    if (driver->id_table)
        return i2c_match_id(driver->id_table, client) != NULL;			//匹配id_table和i2c_client的name

    return 0;
}

//drivers/misc/eeprom/at24.c
static struct i2c_driver at24_driver = {
    .driver = {
        .name = "at24",
        .of_match_table = at24_of_match,
        .acpi_match_table = ACPI_PTR(at24_acpi_ids),
    },
    .probe = at24_probe,
    .remove = at24_remove,
    .id_table = at24_ids,
};


static const struct of_device_id at24_of_match[] = {
		{ .compatible = "atmel,24c00",		.data = &at24_data_24c00 },
		{ .compatible = "atmel,24c01",		.data = &at24_data_24c01 },
		{ .compatible = "atmel,24cs01",		.data = &at24_data_24cs01 },
 		//... 此处省略了一部分
		{ .compatible = "atmel,24c256",		.data = &at24_data_24c256 },
		{ .compatible = "atmel,24c512",		.data = &at24_data_24c512 },
		{ .compatible = "atmel,24c1024",	.data = &at24_data_24c1024 },
		{ /* END OF LIST */ },
};

static const struct acpi_device_id at24_acpi_ids[] = {
    { "INT3499", AT24_DEVICE_MAGIC(8192 / 8, 0) },
    { }
};

static const struct i2c_device_id at24_ids[] = {
    /* needs 8 addresses as A0-A2 are ignored */
    { "24c00",  AT24_DEVICE_MAGIC(128 / 8,  AT24_FLAG_TAKE8ADDR) },
    /* old variants can't be handled with this generic entry! */
    { "24c01",  AT24_DEVICE_MAGIC(1024 / 8, 0) },
    { "24cs01", AT24_DEVICE_MAGIC(16,
                AT24_FLAG_SERIAL | AT24_FLAG_READONLY) },
   	//... 此处省略了一部分
    { "24c512", AT24_DEVICE_MAGIC(524288 / 8,   AT24_FLAG_ADDR16) },
    { "24c1024",    AT24_DEVICE_MAGIC(1048576 / 8,  AT24_FLAG_ADDR16) },
    { "at24", 0 },
    { /* END OF LIST */ }
};
设备树中如何配置i2c
//Example:
    i2c1: i2c@400a0000 {					/* 第一条i2c总线 */
        /* ... master properties skipped ... */
        clock-frequency = <100000>;			//

        flash@50 {										//第一个设备,会生成一个i2c_client
            compatible = "atmel,24c256";		//这个compatible会和i2c_driver里的of_match_table来比较
            reg = <0x50>;
        };

        pca9532: gpio@60 {						//第二个设备,会生成一个i2c_client
            compatible = "nxp,pca9532";
            gpio-controller;
            #gpio-cells = <2>;
            reg = <0x60>;
        };
    };

2.1 i2c_driver

i2c_driver在结构体中表明能支持哪些设备

  • 使用of_match_table来判断:设备树中i2c设备节点会生成i2c_client结构体,其中有compatible属性和of_match_table的某项兼容,就匹配成功
  • 使用id_table来判断:i2c_client的name属性会和id_table[i].name匹配比较,如果相同则匹配成功

2.2 i2c_client

i2c_client表示一个I2C设备,创建i2c_client的方法有4种

  1. 方法1
  • 通过i2c bus number来创建
    int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
  • 通过设备树来创建
	i2c1: i2c@400a0000 {
		/* ... master properties skipped ... */
		clock-frequency = <100000>;

		flash@50 {
			compatible = "atmel,24c256";
			reg = <0x50>;
		};

		pca9532: gpio@60 {
			compatible = "nxp,pca9532";
			gpio-controller;
			#gpio-cells = <2>;
			reg = <0x60>;
		};
	};
  1. 方法2
    有时候无法知道设备在哪个i2c bus下,不知道对应的i2c bus number。但是可以通过其他方法知道对应的i2c_adapter结构体。
    可以使用下面两个函数来创建i2c_client:
  static struct i2c_board_info sfe4001_hwmon_info = {
	I2C_BOARD_INFO("max6647", 0x4e),
  };

  int sfe4001_init(struct efx_nic *efx)
  {
	(...)
	efx->board_info.hwmon_client =
		i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);			//使用i2c_adapter创建i2c_client

	(...)
  }
//-------------------------------------------------------------------------------------------
  static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };

  static int usb_hcd_nxp_probe(struct platform_device *pdev)
  {
	(...)
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info i2c_info;

	(...)
	i2c_adap = i2c_get_adapter(2);
	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
	strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));
	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
						   normal_i2c, NULL);		//使用i2c_new_probed_device来创建i2c_client
	i2c_put_adapter(i2c_adap);
	(...)
  }

两个函数的区别:
i2c_new_device会创建i2c_client,即使该设备并不存在。
i2c_new_probed_device 它成功的话,会创建i2c_client,并且表示这个设备肯定存在。I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样。可以罗列出可能的地址,i2c_new_probed_device使用这些地址判断设备是否存在。

  1. 方法3(不推荐)
    由i2c_driver.detect函数来判断是否有对应的i2c设备并生成i2c_client

  2. 方法4
    通过用户空间(user-space)生成调试时、或者不方便通过代码明确地生成i2c_client时,可以通过用户空间来生成。

 	// 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
	# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
  
	// 删除一个i2c_client
	# echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

习惯就好zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值