i2c_driver

AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:

  • 复位:往寄存器0写入0x4

  • 使能:往寄存器0写入0x3

  • 读红外:读寄存器0xA、0xB得到2字节的红外数据

  • 读光强:读寄存器0xC、0xD得到2字节的光强

  • 读距离:读寄存器0xE、0xF得到2字节的距离值

AP3216C的设备地址是0x1E。本章以AP3216C为基础,介绍i2c驱动的写法。

基本框架

  1. 构建自己的i2c_driver结构体,里面有匹配规则 和 probe 函数

    static struct i2c_driver i2c_ap3216c_driver = {
    	.driver = {
    		.name = "ap3216c",
    		.of_match_table = of_match_ids_ap3216c,	/* a:设备树匹配 b:i2c_client.name匹配 */
    	},
    	.probe_new = ap3216c_probe,
    	.remove = ap3216c_remove,
    	.id_table = ap3216c_ids,					/* c:i2c_client.name匹配*/
    };
    
  2. 注册自己的i2c_driver 到内核的i2c总线,然后到内核上的client总线上,按照a、b和c的匹配规则如果匹配到自己的client,就调用 .probe_new函数

    static int __init i2c_driver_ap3216c_init(void)
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    	return i2c_add_driver(&i2c_ap3216c_driver);
    }
    
  3. 在probe函数里面注册自己的字符设备驱动

  • a:register_chrdev,注册file_operations 在里面有自己的read open函数

  • b:class_create创建类

  • c: device_create在类型创建设备节点/dev/ap3216c

static int ap3216c_probe(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	ap3216c_client = client;
	
	/* register_chrdev */
	major = register_chrdev(0, "ap3216c", &ap3216c_ops);

	ap3216c_class = class_create(THIS_MODULE, "ap3216c_class");
	device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c"); /* /dev/ap3216c */

	return 0;
}
  1. open函数里面执行硬件操作,比如ap3216c复位

    static int ap3216c_open (struct inode *node, struct file *file)
    {
    	/*
    	 *  复位操作,ap3216c要求上电复位的
    	 */
    	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);
    	/* delay for reset */
    	mdelay(20);
    	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);
    	return 0;
    }
    
  2. 读寄存器数据

    static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
    {
    	/*
    	 *  读寄存器数据
    	 */
    	int err;
    	char kernel_buf[6];
    	int val;
    
    	if (size != 6)
    		return -EINVAL;
    
    	val = i2c_smbus_read_word_data(ap3216c_client, 0xA); /* read IR */
    	kernel_buf[0] = val & 0xff;
    	kernel_buf[1] = (val>>8) & 0xff;
    
    	val = i2c_smbus_read_word_data(ap3216c_client, 0xC); /* read 光强 */
    	kernel_buf[2] = val & 0xff;
    	kernel_buf[3] = (val>>8) & 0xff;
    
    	val = i2c_smbus_read_word_data(ap3216c_client, 0xE); /* read 距离 */
    	kernel_buf[4] = val & 0xff;
    	kernel_buf[5] = (val>>8) & 0xff;
    
    	err = copy_to_user(buf, kernel_buf, size);
    	return size;
    }
    
  3. 出口函数,反着来

    static void __exit i2c_driver_ap3216c_exit(void)
    {
    	i2c_del_driver(&i2c_ap3216c_driver);
    }
    

完整的【a3216c_drv.c】代碼如下:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>

static int major = 0;
static struct class *ap3216c_class;
static struct i2c_client *ap3216c_client;
/*
* 5:读寄存器数据
*/
static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	/*
	 *  读寄存器数据
	 */
	int err;
	char kernel_buf[6];
	int val;
	
	if (size != 6)
		return -EINVAL;

	val = i2c_smbus_read_word_data(ap3216c_client, 0xA); /* read IR */
	kernel_buf[0] = val & 0xff;
	kernel_buf[1] = (val>>8) & 0xff;
	
	val = i2c_smbus_read_word_data(ap3216c_client, 0xC); /* read 光强 */
	kernel_buf[2] = val & 0xff;
	kernel_buf[3] = (val>>8) & 0xff;

	val = i2c_smbus_read_word_data(ap3216c_client, 0xE); /* read 距离 */
	kernel_buf[4] = val & 0xff;
	kernel_buf[5] = (val>>8) & 0xff;
	
	err = copy_to_user(buf, kernel_buf, size);
	return size;
}

/*
* 4:复位操作,ap3216c要求上电复位的
*/
static int ap3216c_open (struct inode *node, struct file *file)
{
	/*
	 *  复位操作,ap3216c要求上电复位的
	 */
	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);
	/* delay for reset */
	mdelay(20);
	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);
	return 0;
}


static struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open  = ap3216c_open,
	.read  = ap3216c_read,
};

static const struct of_device_id of_match_ids_ap3216c[] = {
	{ .compatible = "com_name,chip_name",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct i2c_device_id ap3216c_ids[] = {
	{ "chip_name",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};
/* 
* 3:在probe函数里面注册自己的字符设备驱动
*   a:register_chrdev,注册file_operations 在里面有自己的read open函数
*   b:class_create创建类
*   c: device_create在类型创建设备节点/dev/ap3216c
*/
static int ap3216c_probe(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	ap3216c_client = client;
	
	/* register_chrdev */
	major = register_chrdev(0, "ap3216c", &ap3216c_ops);

	ap3216c_class = class_create(THIS_MODULE, "ap3216c_class");
	device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c"); /* /dev/ap3216c */

	return 0;
}

static int ap3216c_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(ap3216c_class, MKDEV(major, 0));
	class_destroy(ap3216c_class);
	
	/* unregister_chrdev */
	unregister_chrdev(major, "ap3216c");

	return 0;
}
/* 
* 1:构建自己的i2c_driver结构体,里面有匹配规则 和 probe 函数
*/
static struct i2c_driver i2c_ap3216c_driver = {
	.driver = {
		.name = "ap3216c",
		.of_match_table = of_match_ids_ap3216c,	/* a:设备树匹配 b:i2c_client.name匹配 */
	},
	.probe_new = ap3216c_probe,
	.remove = ap3216c_remove,
	.id_table = ap3216c_ids,					/* c:i2c_client.name匹配*/
};

/* 
* 2:注册自己的i2c_driver 到内核的i2c总线,然后到内核上的client总线上,按照a、b和c的匹配规则
*   如果匹配到自己的client,就调用 .probe_new函数,
*/
static int __init i2c_driver_ap3216c_init(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return i2c_add_driver(&i2c_ap3216c_driver);
}
module_init(i2c_driver_ap3216c_init);
/* 
* 6:出口函数,反着来
*/
static void __exit i2c_driver_ap3216c_exit(void)
{
	i2c_del_driver(&i2c_ap3216c_driver);
}
module_exit(i2c_driver_ap3216c_exit);

MODULE_AUTHOR("www.100ask.net");
MODULE_LICENSE("GPL");