Linux 驱动开发(二)IIC

根据Linux驱动的分离与分层的思想,Linux内核将 IIC 驱动分成 IIC 总线驱动和 IIC 设备驱动
IIC 总线驱动就是 SOC 的 IIC 控制器驱动,也叫做 IIC 适配器驱动
IIC 设备驱动就是针对具体的 IIC 设备而编写的驱动

1 IIC 总线驱动

I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到两个重要的数据结构: i2c_adapteri2c_algorithm, Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter,结构体定义如下:

struct i2c_adapter {
	struct module *owner;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
	void *algo_data;
	/* data fields that are valid for all devices	*/
	struct rt_mutex bus_lock;
	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */
	int nr;
	char name[48];
	struct completion dev_released;
	struct mutex userspace_clients_lock;
	struct list_head userspace_clients;
	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;
};

i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。i2c_algorithm 结构体定义如下:

struct i2c_algorithm {

	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);
};

master_xfer 就是 IIC 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。smbus_xfer 就是 SMBUS 总线的传输函数。

I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapte

2 IIC 设备驱动

I2C 设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver,根据总线、设备和驱动模型,I2C 总线上一小节已经讲了。还剩下设备和驱动, i2c_client 就是描述设备信息的, i2c_driver 描述驱动内容。

不需要我们自己创建i2c_client,我们一般在设备树里面添加具体的 I2C 芯片,系统在解析设备树的时候就会知道有这个I2C设备,然后会创建对应的i2c_client, i2c_client 结构体定义如下:

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
};

i2c 设备驱动框架,i2c_driver 初始化与注册,需要 I2C设备驱动编写人员编写的,IIC 驱动程序就是初始化 i2c_driver,然后向系统注册。注册使用 i2c_register_driver、i2c_add_driver,如果注销i2c_driver 使用 i2c_del_driver。 i2c_driver 结构体定义如下:

struct i2c_driver {
	unsigned int class;
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);------1int (*remove)(struct i2c_client *);
	void (*shutdown)(struct i2c_client *);
	void (*alert)(struct i2c_client *, unsigned int data);
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
	struct device_driver driver;						-----------------2const struct i2c_device_id *id_table;
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

当 I2C 设备和驱动匹配成功以后(1)处的 probe 函数就会执行,(2)处的device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver 的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。

i2c_driver 的注册示例代码如下:

/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 函数具体程序 */
  	return 0;
}

/* i2c 驱动的 remove 函数 */
static int xxx_remove(struct i2c_client *client)
{
	/* 函数具体程序 */
	return 0;
}

 /* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
	{"xxx", 0},
	{}
};

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

/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
	.probe = xxx_probe,
	.remove = xxx_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "xxx",
		.of_match_table = xxx_of_match,
	},
	.id_table = xxx_id,
};

/* 驱动入口函数 */
static int __init xxx_init(void)
{
 	int ret = 0;
	ret = i2c_add_driver(&xxx_driver);
 	return ret;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
	i2c_del_driver(&xxx_driver);
 }

 module_init(xxx_init);
 module_exit(xxx_exit);

3 设备与驱动匹配的过程

设备和驱动的匹配过程也是由 I2C 总线完成的, I2C 总线的数据结构为 i2c_bus_type,定义如下:

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

其中,i2c_device_match 函数完成的就是设备与驱动匹配的过程,函数内容如下:

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

其中,of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C
设备和驱动匹配。

4 以 ap3216c 为例构建一个 Linux 下 IIC 的驱动框架

  • 修改设备树
    首先肯定是要修改 IO, AP3216C 用到了 I2C1 接口, I.MX6U-ALPHA 开发板上的 I2C1 接口使用到了 UART4_TXD 和 UART4_RXD,因此肯定要在设备树里面设置这两个 IO。将 UART4_TXD 和 UART4_RXD 这两个 IO 分别复用为 I2C1_SCL 和 I2C1_SDA,电气属性都设置为 0x4001b8b0
pinctrl_i2c1: i2c1grp {
			fsl,pins = <
				MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
				MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
			>;
		};

接着在 i2c1 节点追加 ap3216c 子节点,@后面的“1e”是 ap3216c 的器件地址

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

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

};

接着我要开始编写驱动程序了

  • 首先我们构建一个设备驱动框架
    (1)驱动入口与出口,入口注册,出口注销
static int __init ap3216c_init(void)
{
    int ret = 0;
    ret = i2c_add_driver(&ap3216c_driver);
    return ret;   
}

static void __exit ap3216c_exit(void)
{
    i2c_del_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jimmy");

(2)添加一个结构体

/* ap3216c_driver 结构体变量, i2c_driver 类型 */
static struct i2c_driver ap3216c_driver = {
	.probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .driver = {
        .name = "ap3216c",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(ap3216c_of_match),
    },
    .id_table = ap3216c_id,
};

其中 of_match_table 和 id_table 函数是设备和驱动之间的匹配函数,分别对应两种不同的匹配方式,即有设备树和没有设备树的时候,我们需要定义两个匹配表结构体。同时还要定义一个 probe 函数和一个 remove 函数, probe 函数的内容就是我们字符设备驱动那一套,remove 函数的内容主要对 probe 函数中的资源申请做一个释放。

/* 传统的匹配表 */
static struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c", 0},
    {}
};
/* 设备树的匹配表 */
static struct of_device_id ap3216c_of_match[] = {
    {.compatible = "alientek,ap3216c"},
    {}
};
  • 接着我们构建一个字符设备驱动模型
    定义一个ap3216c设备结构体、设备操作集并创建相应的函数,在 probe 函数中我们主要构建了字符设备的框架,首先创建设备号,如果没有定义设备号我们直接申请设备号,如果没有定义我们通过函数向内核申请设备号。接着初始化 cdev 并添加一个 cdev,接着创建类和设备。
/* ap3216c 设备结构体 
   private_data成员变量用于存放ap3216c对应的i2c_client */  
struct ap3216c_dev {
    struct cdev cdev;       // 字符设备
    dev_t devid;            // 设备号
    int major;              // 主设备号 
    int minor;              // 次设备号
    struct class *class;    // 类
    struct device *device;  // 设备
    void *private_data;     //私有数据
    unsigned short ir, als, ps;  // 获取的传感器的数据
};
struct ap3216c_dev ap3216cdev;

static const struct file_operations ap3216c_fops = {
    .owner = THIS_MODULE,
    .open = ap3216c_open,
    .read = ap3216c_read,
    .release = ap3216c_release,
};

/* ap3216c_probe 函数,当 I2C 设备和驱动匹配成功以后此函数就会执行,和
   platform 驱动框架一样。此函数前面都是标准的字符设备注册代码,最后面会
   将此函数的第一个参数client传递给ap3216cdev的private_data 成员变量。*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = 0;
    /* 搭建字符设备驱动框架 */
	/*1、创建设备号 */
    ap3216cdev.major = 0;
	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);	/* 获取分配号的主设备号 */
        ap3216cdev.minor = MINOR(ap3216cdev.devid); /* 获取分配号的次设备号 */
	}
    if(ret < 0)
    {
        printk("ap3216cdev chrdev_region error!\r\n");
        ret = -EINVAL;
        goto fail_devid;
    }
    printk("major = %d, monor = % d\r\n", ap3216cdev.major, ap3216cdev.minor);
	  
	/* 2、初始化cdev */
	ap3216cdev.cdev.owner = THIS_MODULE;
	cdev_init(&ap3216cdev.cdev, &ap3216c_fops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
    if(ret < 0){
        ret = -EINVAL;
        goto fail_cdev;
    }

	/* 4、创建类 */
	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.class)) {
		ret =  PTR_ERR(ap3216cdev.class);
        goto fail_class;
	}

	/* 5、创建设备 */
	ap3216cdev.device = device_create(ap3216cdev.class, NULL, 
										ap3216cdev.devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.device)) {
		ret = PTR_ERR(ap3216cdev.device);
        goto fail_device;
	}
	
    ap3216cdev.private_data = client;
    
    return 0;
fail_device:
    class_destroy(ap3216cdev.class);
fail_class:
    cdev_del(&ap3216cdev.cdev);
fail_cdev:
    unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
fail_devid:
    return ret;
}

static int ap3216c_remove(struct i2c_client *client)
{
    /* 删除字符设备 */
    cdev_del(&ap3216cdev.cdev);
    /* 注销设备号 */
    unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);  
    /* 摧毁设备 */
    device_destroy(ap3216cdev.class, ap3216cdev.devid);  
    /* 摧毁类 */
    class_destroy(ap3216cdev.class);
    return 0;
}
  • 最后我们初始化 AP3216C
/* 读取AP3216c的N个寄存器值 */
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len) 
{
    struct i2c_msg msg[2];
    struct i2c_client * client = (struct i2c_client *)dev->private_data;
    
    /* iic 协议中读操作第一阶段是主机向从机写一个地址(要读的寄存器地址),第二阶段发送一个读信号开始读数据*/
    /* msg[0]发送要读取的首地址 */
    msg[0].addr = client->addr;    // 从机地址,也就是ap3216c地址
    msg[0].flags = 0;              // 表示为要发送的数据
    msg[0].buf = &reg;             // 要发送的数据也就是寄存器地址(iic协议第一阶段要发送的是			
    							   // 要读的寄存器地址)
    msg[0].len = 1;                // 要发送的寄存器地址长度为1

    /* msg[1]读取数据 */
    msg[1].addr = client->addr;    // 从机地址,也就是ap3216c地址
    msg[1].flags = I2C_M_RD;       // 表示读数据
    msg[1].buf = val;              // 接收到的从机发送的数据
    msg[1].len = len;              // 要读取的寄存器地址长度

    return i2c_transfer(client->adapter, msg, 2);
}

/* 向AP3216c写N个寄存器的的数据 */
static int ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len) 
{   
    u8 buff[256];
    struct i2c_msg msg;
    struct i2c_client * client = (struct i2c_client *)dev->private_data;
    
    /* 构建要发送的数据,也就是寄存器首地址+寄存器数据 */
    buff[0] = reg;
    memcpy(&buff[1], buf, len);

    /* iic 协议中写操作是主机向从机写一个地址(要写的寄存器地址),接着就是要写的数据 */
    msg.addr = client->addr;    // 从机地址,也就是ap3216c地址
    msg.flags = 0;              // 表示为要发送的数据
    msg.buf = buff;            // 要发送的数据也就是寄存器的地址+实际要发送的数据
    msg.len = len +1;           // 要发送的寄存器地址长度(1)+实际数据长度

    return i2c_transfer(client->adapter, &msg, 1);

}

/* 读取ap3216c一个寄存器的值*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
    u8 data = 0;
    ap3216c_read_regs(dev, reg, &data, 1);
    return data;
}

/* 向ap3216c一个寄存器写数据*/
static unsigned char ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
    u8 buf = 0;
    buf = data;
    ap3216c_write_regs(dev, reg, &buf, 1);
    return 0;
}

/* 读取AP3216C的数据 */
void ap3216c_readdata(struct ap3216c_dev *dev)
{
    unsigned char buf[6];
    unsigned char i;

	/* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++)	
    {
        buf[i] = ap3216c_read_reg(&ap3216cdev, AP3216C_IRDATALOW + i);	
    }
	
    if(buf[0] & 0X80) 	/* IR_OF位为1,则数据无效 */
		dev->ir = 0;					
	else 				/* 读取IR传感器的数据 */
		dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); 			
	
	dev->als = ((unsigned short)buf[3] << 8) | buf[2];	/* 读取ALS传感器的数据 */  
	
    if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效*/
		dev->ps = 0;    													
	else 				/* 读取PS传感器的数据 */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 	
}

static int ap3216c_open(struct inode *node, struct file *filp)
{
    unsigned char value = 0;
    filp->private_data = &ap3216cdev;

    printk("open\r\n");

    /* 初始化ap3216c */
    ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X04);   /* 复位AP3216C */
    mdelay(50);
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);	/* 开启ALS、PS+IR */
	value = ap3216c_read_reg(&ap3216cdev, AP3216C_SYSTEMCONG);	/* 读取刚刚写进去的0X03 */
    printk("AP3216C_SYSTEMCONG = %#x\r\n", value);

    return 0;
}


static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt,
								 loff_t *offset)
{
    short data[3];
    int err = 0;

    struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
    
    /* 向应用返回ap3216c的原始数据 */
    ap3216c_readdata(dev);

    data[0] = dev->ir;
    data[1] = dev->als;
    data[2] = dev->ps;

    err = copy_to_user(buf, data, sizeof(data));
    if(err < 0) {
        printk("error copying\r\n");
    }

    return 0;
}

首先我们在 open 函数中初始化 ap3216c
ap3216c_read_regs 函数实现多字节读取
ap3216c_write_regs 函数实现连续多字节写操作
ap3216c_write_reg 函数用于向 AP3216C 的指定寄存器写入数据,用于一个寄存器的数据写操作
ap3216c_read_reg 函数用于读取 AP3216C 的指定寄存器数据,用于一个寄存器的数据读取
ap3216c_readdata 函数用于读取 AP3216C 的数据,读取原始数据,包括 ALS,PS 和 IR
在 read 函数中通过 copy_to_user 函数将读取到的数据发送到用户空间

这里我们重要说一个函数那就是 i2c_transfer
前面我们说到 IIC 设备驱动首先要做的就是初始化 i2c_driver 并向 Linux 内核注册。当设备和驱动匹配以后 i2c_driver 里面的 probe 函数就会执行, probe 函数里面所做的就是字符设备驱动那一套了。 一般需要在 probe 函数里面初始化 I2C 设备,要初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作,这里就要用到 i2c_transfer 函数了。i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数。函数原型如下:

int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)
/* 	adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的 i2c_adapter。
	msgs: I2C 要发送的一个或多个消息。
	num: 消息数量,也就是 msgs 的数量。*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值