Linux驱动学习--I2C驱动(二)

一、修改设备树

1、IO修改

首先要修改IO,我们使用的是I2C1接口,而I2C1接口使用到了UART4_TXD 和 UART4_RXD
,所以我们需要在设备树里面设置UART4_TXD 和 UART4_RXD 这两个 IO,我们需要修改的内容为:

pinctrl_i2c1: i2c1grp {
     fsl,pins = <
         MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
         MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
     >;
};

pinctrl_i2c1就是I2C1的IO节点,将UART4_TXD和UART4_RXD这两个IO复用为I2C1_SCL和I2C1_SDA,电气属性都设置为0x4001b8b0。

2、在I2C1节点下加入ap3216c子节点

我们要在i2c1节点中添加ap3216c子节点,修改以后如下所示:

&i2c1 {
     clock-frequency = <100000>;
     pinctrl-names = "default";
     pinctrl-0 = <&pinctrl_i2c1>;
     status = "okay";

    ap3216c@1e {
         compatible = "alientek,ap3216c";
         reg = <0x1e>;
    };
};

在ap3216c子节点,@后面的“1e”是ap3216c的器件地址。设置compatible值为“alientek,ap3216c”。reg属性也是设置ap3216c器件地址的,所以reg设置为0x1e。修改完成后,我们怎么检测我们的设备树有没有修改好呢?

使用新的设备树启动Linux内核。进入sys/bus/i2c/devices目录,然后输入ls查看会有一个0-001e的子目录,然后使用cat  0-001e/name命令,我们会看到设备的名字。

二、驱动的编写

I2C总线-设备-驱动模型

1、i2c_driver表明能支持哪些设备:

  • 使用of_match_table来判断
    • 设备树中,某个I2C控制器节点下可以创建I2C设备节点
      • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功
    • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功
  • 使用id_table来判断
    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功

i2c_driver跟i2c_client匹配成功后,就调用i2c_probe函数。

2、i2c_client设备

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

  • 通过I2C bus number来创建
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len);
  • 通过设备树来创建

该方法的具体过程上面一节已经详细讲解(我们的代码也是基于这个方法来构建的)

有时候不知道该设备挂载在那个I2C bus下,无法知道它对应的I2C bus number,但是可以通过其他方法知道对应的i2c_adapter结构体。

可以使用下面两个函数来创建i2c_client:

  •   i2c_new_device
 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_new_probed_device
  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_put_adapter(i2c_adap);
	(...)
  }

差别:

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

方法3:由i2c_driver.detect函数来判断是否有对应的I2C设备并生成i2c_client

方法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

以上是i2c_client的四种创建方法,

接下来我们来看看i2c_driver程序的套路,都是I2C总线-设备-驱动模型,分配、设置、注册一个I2C_driver结构体:

在probe函数中,分配、设置、注册file_operations结构体。

在file_operations的函数中,使用i2c_transfer等函数来发起I2C传输

那我们来看下具体是怎么实现的呢?

//驱动入口函数
static int __init ap3216c_init(void)
{
    int ret = 0;

    ret = i2c_add_driver(&i2c_ap3216c_driver);
    return ret;
}

首先驱动函数中的i2c_add_driver是向内核中注册i2c_driver,该结构体如下:

/*设备树匹配列表*/
static const struct of_device_id ap3216c_of_match[] = {
    {.compatible = "alientek, ap3216c"},
    {/*Sentinel*/}
};

//传统匹配方式ID列表
static const struct i2c_device_id ap3216c_id[] = {
    {"alientek, ap3216c", 0},
    {}
};

/*i2c驱动结构体*/
static struct i2c_driver i2c_ap3216c_driver = {
    .driver = {
        .name = "ap3216c",
        .of_match_table = ap3216c_of_match,
        .owner = THIS_MODULE,
    },
    .probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .id_table = ap3216c_id,
};

ap3216c_of_match 匹配表, of_device_id 类型,用于设备树设备和驱动匹配。这里只写了一个 compatible 属性,值为“alientek,ap3216c”。而ap3216c_id 匹配表, i2c_device_id 类型。用于传统的设备和驱动匹配,也就是没有使用设备树的时候。ap3216c_probe 函数,当 I2C 设备和驱动匹配成功以后此函数就会执行,和platform 驱动框架一样。此函数前面都是标准的字符设备注册代码,最后面会将此函数的第一个参数 client 传递给 ap3216cdev 的 private_data 成员变量。该函数具体内容如下:
 

/**
 * i2c驱动的probe函数,当驱动与设备匹配以后此函数就会执行
 * client:i2c设备
 * id:i2c设备ID
 * 返回值:0成功;其他,失败
 *
*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    //1、构建设备号
    if(ap3216cdev.major){
        ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
        register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
    }else{
        alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
        ap3216cdev.major = MAJOR(ap3216cdev.devid);
    }

    //2、注册设备
    cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
    cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
    //3、创建类
    ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
    //4、创建设备
    ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);

    ap3216cdev.private_data = client;

    return 0;
}

注册设备是会需要两个参数一个是cdev,另一个是file_operations结构体,而在这个结构体中包含了open,read等和上层APP交互的函数。接下来就是对具体的IIC设备ap3216c的寄存器进行读写操作了,我就不贴代码了,需要翻阅数据手册来具体编写,我们主要是通过调用i2c_transfer函数来传递数据。到此我们已经可以使用系统的IIC总线来读写我们的设备了。

这只是一种I2C的简单实现,后面有时间我们再详细看看i2c_transfer是怎么实现的,一个比较重要的一个叫I2C_Adapter的东西

  • 32
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值