45.Linux I2C 驱动

45.Linux I2C 驱动

正如前一章那样,将直接驱动硬件的IIC作为IIC控制器,使用IIC来操作设备的作为设备驱动,该设备驱动直接调用IIC控制器里面的API函数即可。

i2c内核中的i2c

内容在include/linux/i2c.h中

IIC总线

对应IIC控制器这层,linux为其抽象为i2c_adapter结构体,也就i2c设配置驱动

c 复制代码
/*
 * i2c_adapter is the structure used to identify a physical i2c bus along
 * with the access algorithms necessary to access it.
 */
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_adapter越需要1.申请,2.初始化,3.注册等步骤来向linux内核进行注册。

注册函数

c 复制代码
 int i2c_add_adapter(struct i2c_adapter *);
int i2c_add_numbered_adapter(struct i2c_adapter *);

//移除使用
i2c_del_adapter(struct i2c_adapter *);

*其中最重要的是注册i2c_adapter结构体中的const struct i2c_algorithm algo;

c 复制代码
struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	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 *);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};

此变量包含了IIC控制器访问IIC设备的api接口函数

其中master_xfer函数就是IIC控制器最终进行收发的函数。

IIC设备驱动
i2c_client

i2c_client 就是描述设备信息的,

c 复制代码
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;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};

i2c_client:表示i2c设备

不需要我们自己定义,来自设备树种的节点,我们只需要在设备树中添加具体的i2c节点,解析设备树时,内核就会自己创建一个i2c_client

c 复制代码
&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

    codec: wm8960@1a {
            compatible = "wlf,wm8960";
            reg = <0x1a>;
            clocks = <&clks IMX6UL_CLK_SAI2>;
            clock-names = "mclk";
            wlf,shared-lrclk;
    };


	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
		status = "disabled";
	};

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

i2c_driver 描述驱动内容,类似于 platform_driver。

c 复制代码
struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared. You should avoid
	 * using this, it will be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};
c 复制代码
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

当设备和驱动匹配之后,就会自动执行probe函数!

i2c初始化与注册,需要i2c设备驱动编写,就是要初始化i2c_driver,然后向系统注册。

注册使用

c 复制代码
int i2c_register_driver(struct module *owner,
					struct i2c_driver *driver)

owner: 一般为 THIS_MODULE。

driver:要注册的 i2c_driver。返回值: 0,成功;负值,失败。

另外 i2c_add_driver 也常常用于注册 i2c_driver, i2c_add_driver 是一个宏,定义如下:

c 复制代码
#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)

注销 I2C 设备驱动的时候需要将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:

c 复制代码
void i2c_del_driver(struct i2c_driver *driver)

driver:要注销的 i2c_driver。

i2c_driver 的注册示例代码如下:

c 复制代码
/* 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);

NXP实现的I2C驱动

i2c驱动

通过设备树中的i2c来找到i2c驱动文件

c 复制代码
			i2c1: i2c@021a0000 {
				#address-cells = <1>;
				#size-cells = <0>;
				compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
				reg = <0x021a0000 0x4000>;
				interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_I2C1>;
				status = "disabled";
			};

			i2c2: i2c@021a4000 {
				#address-cells = <1>;
				#size-cells = <0>;
				compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
				reg = <0x021a4000 0x4000>;
				interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_I2C2>;
				status = "disabled";
			};

			i2c3: i2c@021a8000 {
				#address-cells = <1>;
				#size-cells = <0>;
				compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
				reg = <0x021a8000 0x4000>;
				interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_I2C3>;
				status = "disabled";
			};

找到drivers/i2c/busses/i2c-imx.c

文件最后

c 复制代码
static struct platform_driver i2c_imx_driver = {
	.probe = i2c_imx_probe,
	.remove = i2c_imx_remove,
	.driver	= {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = i2c_imx_dt_ids,
		.pm = IMX_I2C_PM,
	},
	.id_table	= imx_i2c_devtype,
};

static int __init i2c_adap_imx_init(void)
{
	return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);

static void __exit i2c_adap_imx_exit(void)
{
	platform_driver_unregister(&i2c_imx_driver);
}
module_exit(i2c_adap_imx_exit);

通过platform来引入i2c总线设备

c 复制代码
.probe = i2c_imx_probe,
.remove = i2c_imx_remove,

NXP为i2c进行封装为了imx_i2c_struct

c 复制代码
struct imx_i2c_struct {
	struct i2c_adapter	adapter;   //内核的i2c_adapter
	struct clk		*clk;
	void __iomem		*base;
	wait_queue_head_t	queue;
	unsigned long		i2csr;
	unsigned int		disable_delay;
	int			stopped;
	unsigned int		ifdr; /* IMX_I2C_IFDR */
	unsigned int		cur_clk;
	unsigned int		bitrate;
	const struct imx_i2c_hwdata	*hwdata;

	struct imx_i2c_dma	*dma;
};

i2c_imx_probe主要步骤

1.创建imx_i2c_struct结构体

2.从设备树中获取irq、resource、reg等信息

3.在内核中为imx_i2c_struct申请内存。

4.初始化imx_i2c_struct,主要是i2c_adapter等成员变量(包括i2c_algorithm)

5.使能时钟

6.申请IRQ

7.各种i2c其余初始化、设置默认值

8.向内核中申请i2c_adapter ,i2c_add_numbered_adapter

第四步中

c 复制代码
static struct i2c_algorithm i2c_imx_algo = {
	.master_xfer	= i2c_imx_xfer,
	.functionality	= i2c_imx_func,
};
...

		i2c_imx->adapter.algo		= &i2c_imx_algo;

static struct i2c_algorithm的原型为:

c 复制代码
struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	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 *);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};
c 复制代码
.master_xfer	= i2c_imx_xfer,

i2c_imx_xfer就是控制i2c的寄存器进行传输,会直接对i2c的寄存器进行操作.

c 复制代码
.functionality	= i2c_imx_func,

返回i2c可以操作的设备

I2C 适配器驱动 SOC 厂商已经替我们编写好了,我们需要做的就是编写具体的设备驱动,本小节我们就来学习一下 I2C 设备驱动的详细编写流程。

设备驱动

不使用设备树时

使用设备树时

1.将需要添加的i2c设备添加具体的i2c控制器下

​ 具体信息打开绑定文档查看

c 复制代码
&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

    codec: wm8960@1a {
            compatible = "wlf,wm8960";
            reg = <0x1a>;
            clocks = <&clks IMX6UL_CLK_SAI2>;
            clock-names = "mclk";
            wlf,shared-lrclk;
    };


	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
		status = "disabled";
	};

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

添加设备格式

c 复制代码
设备名称@设备地址 {
		compatible = "XXX"; 	// 必须
		reg = <0x0e>;		//设备地址 必须
		position = <2>;
		status = "disabled";
    	interrupt-parent = <&gpio5>;
    	interrupts  = <0 8>;
	};

i2c_driver也有device_driver结构体,实现匹配的关键如39章节如出一辙

使用IIC的AP3216C环境光传感器进行驱动

实战

接到了I2C1上,使用了uart4_rxd和uart4_txd

而且还有一个AP_INT作为中断

  1. 修改设备树

    一个i2c1下面不能出现同一个器件地址的设备

c 复制代码
&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

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

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

修改完毕编译,复制拷贝。

可以看到0-001e这个设备

c 复制代码
ls /sys/bus/i2c/devices/
0-001a  0-001e  1-0014  1-001a  1-0038  1-003c  i2c-0   i2c-1

框架

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include "ap3216c_reg.h"
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id){

    printk("ap3216c_probe\r\n");

    return 0;
}

static int ap3216c_remove(struct i2c_client *client){


    
    return 0;
}
//设备树匹配
const struct of_device_id ap3216c_of_match[]= {
    { .compatible = "alientek,ap3216c"},
    {},
};

//传统匹配表
const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c",0},
    {},
};
struct i2c_driver ap3216c_driver= {
    .probe = ap3216c_probe,
    .remove =  ap3216c_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "ap3216c",
        .of_match_table = ap3216c_of_match,
    },
    .id_table = ap3216c_id,

};
static int __init ap3216c_init(void){
    int ret = 0;
    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");

模板加载后板子输出

c 复制代码
/lib/modules/4.1.15 # modprobe ap3216c.ko
ap3216c_probe
/lib/modules/4.1.15 # ls /sys/bus/i2c/drivers
ap3216c             mc13xxx             stmpe-i2c
at24                mma8450             tlv320aic23-codec
da9052              ov2640              tsc2007
dummy               pca953x             vtl_ts
egalax_ts           pfuze100-regulator  wm8962
ir-kbd-i2c          sgtl5000

我们先简单搭建一个字符设备框架

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include "ap3216c_reg.h"
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define AP3216C_CNT      1
#define AP3216C_NAME     "ap3216c"


struct ap3216c_dev
{
    dev_t devid;
    int MAJOR;
    int MINOR;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_id;
};
struct ap3216c_dev ap3216c;



static int ap3216c_open (struct inode *inode, struct file *file){
    file->private_data = &ap3216c;
    printk("ap3216c_open\r\n");    
    return 0; 
}  

static int ap3216c_close(struct inode *inode, struct file *file){
    struct ap3216c_dev *dev = file->private_data;
    printk("ap3216c_close\r\n");
    return 0;
}

static ssize_t ap3216c_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
    struct ap3216c_dev *dev = file->private_data;
    printk("ap3216c_read\r\n");

    //读取ap3216c的数据



    
    return 0;

}
const struct file_operations ap3216c_opts={
    .owner = THIS_MODULE,
    .open  = ap3216c_open,
    .release = ap3216c_close,
    .read = ap3216c_read,
};

static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id){

    printk("ap3216c_probe\r\n");
    //搭建字符设备驱动框架

    int ret = 0;
    //注册设备号
    ap3216c.MAJOR = 0;
    if ( ap3216c.MAJOR )
    {
        ap3216c.devid = MKDEV(ap3216c.MAJOR,0);
        ret  = register_chrdev_region(ap3216c.devid ,AP3216C_CNT,AP3216C_NAME);
    }else{
        ret = alloc_chrdev_region(&ap3216c.devid,0,AP3216C_CNT,AP3216C_NAME);
         ap3216c.MAJOR  = MAJOR(ap3216c.devid);
         ap3216c.MINOR  = MINOR(ap3216c.devid);
    }
    if (ret<0)
    {
        printk("ap3216c chrdev_region error\r\n");
        goto fail_devid;
        
    }

    //注册设备
    ap3216c.cdev.owner = THIS_MODULE;
    cdev_init(&ap3216c.cdev,&ap3216c_opts);


    ret  = cdev_add(&ap3216c.cdev, ap3216c.devid, AP3216C_CNT);
    if (ret < 0 )
    {
        goto fail_cdev;
    }
    

    //自动创建设备节点
    ap3216c.class = class_create(THIS_MODULE,AP3216C_NAME);
    if (IS_ERR(ap3216c.class))
    {
        ret = PTR_ERR(ap3216c.class);
        goto fail_class;
    }
    
    ap3216c.device = device_create(ap3216c.class,NULL,ap3216c.devid,NULL,AP3216C_NAME);
    if (IS_ERR(ap3216c.device ))
    {
        ret  = PTR_ERR(ap3216c.device); 
        goto fail_device;
    }



    return 0;
fail_device:
    class_destroy(ap3216c.class);
fail_class:
    cdev_del(&ap3216c.cdev);
fail_cdev:
    unregister_chrdev_region(ap3216c.devid,AP3216C_CNT);
fail_devid:  
    return ret;
}

static int ap3216c_remove(struct i2c_client *client){

    cdev_del(&ap3216c.cdev);

    
    unregister_chrdev_region(ap3216c.devid,AP3216C_CNT);
    device_destroy(ap3216c.class,ap3216c.devid);
    class_destroy(ap3216c.class);

    
    return 0;

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

//传统匹配表
const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c",0},
    {},
};
struct i2c_driver ap3216c_driver= {
    .probe = ap3216c_probe,
    .remove =  ap3216c_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "ap3216c",
        .of_match_table = ap3216c_of_match,
    },
    .id_table = ap3216c_id,

};
static int __init ap3216c_init(void){
    int ret = 0;
    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");

测试app

c 复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include "linux/input.h"





int main(int argc, char *argv[])
{
	int fd;
	int ret = 0;
	char *filename;
	unsigned char data;
	
	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	read(fd,&data,sizeof(data));
	
	close(fd);
	return ret;
}
  1. 编写i2c设备驱动框架 字符设备驱动框架(不是必要的,只是为了测试驱动)

如果编写电容触摸屏需要 i2c设备驱动框架 + pintcl(输入)框架

  1. 初始化ap3216c,实现ap3216c_read;

这里使用i2c_transfer函数来实现数据的传输

c 复制代码
int i2c_transfer(struct i2c_adapter *adap,
				struct i2c_msg *msgs,
					int num)

adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的i2c_adapter。

msgs: I2C 要发送的一个或多个消息。

num: 消息数量,也就是 msgs 的数量。

返回值: 负值,失败,其他非负值,发送的 msgs 数量。

adap是当设备和驱动匹配之后

c 复制代码
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)

会将匹配的i2c_client传递给client

i2c_client的结构体中有一个成员变量i2c_adapter就是i2c的适配器,就是我需要的参数

c 复制代码
struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RD		0x0001	/* read data, from slave to master */
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};

完整代码:

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "ap3216c_reg.h"
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define AP3216C_CNT      1
#define AP3216C_NAME     "ap3216c"




struct ap3216c_dev
{
    dev_t devid;
    int MAJOR;
    int MINOR;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_id;

    void *private_data;

    unsigned short ir;
    unsigned short ps;
    unsigned short als;
};
struct ap3216c_dev ap3216c;


//读取ap3216c的N个寄存器的值
//从哪个设备的哪个reg读取多长的数据到哪个地址
static int ap3216c_read_regs(struct ap3216c_dev *dev,u8 reg,void *val,int len){
    
    struct i2c_msg ap3216c_msg[2];
    struct i2c_client *client =  dev->private_data;
    //client中保存着大量的句柄,包括从机地址,i2c适配器...


    //1.想哪个从机发送什么数据
    ap3216c_msg[0].addr =client->addr  ;  //丛机地址
    ap3216c_msg[0].flags =0 ;             //表示发送
    ap3216c_msg[0].buf =&reg ;          //发送的数据
    ap3216c_msg[0].len =sizeof(reg) ;          //发送的长度



    //2.读取数据
    ap3216c_msg[1].addr =client->addr  ;  //丛机地址
    ap3216c_msg[1].flags =I2C_M_RD ;             //表示接受
    ap3216c_msg[1].buf = val;          //接收的数据
    ap3216c_msg[1].len = len;          //发送的长度
    
    return  i2c_transfer(client->adapter,ap3216c_msg,2);
}


//想ap3216c写入N个寄存器的值
static int ap3216c_write_regs(struct ap3216c_dev *dev,u8 reg,u8 *buf,u8 len){
    u8 b[256];
    struct i2c_msg msg;
    struct i2c_client *client =  dev->private_data;
    //client中保存着大量的句柄,包括从机地址,i2c适配器...
    b[0] = reg;

    memcpy(&b[1],buf,len);
    //1.想哪个从机发送什么数据
    msg.addr =client->addr  ;  //丛机地址
    msg.flags =0 ;             //表示发送
    msg.buf = b ;          //发送的数据
    msg.len =len+1 ;          //发送的长度

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


//读取单个寄存器
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev,u8 reg){
    u8 data = 0;
    ap3216c_read_regs(dev,reg,&data,1);
    return data;
}
//写入单个寄存器
static void ap3216c_write_reg(struct ap3216c_dev *dev,u8 reg,u8 data){
    u8 buf = 0;
    buf = data;
    ap3216c_write_regs(dev,reg,&buf,1);
}

void ap3216c_readdata(struct ap3216c_dev *dev){
    unsigned char buf[6];
    unsigned char i = 0;
    for ( i = 0; i < 6; i++)
    {
        buf[i] = ap3216c_read_reg(dev,AP3216C_IRDATALOW+i);
    }

    if (buf[0] & 0x80)
    {
       dev->ir = 0;
       dev->ps = 0;
    }else{
        dev->ir= ((unsigned short)buf[1]<<2)| (buf[0]&0x03);
        dev->ps = (((unsigned short)buf[5]&(0x3f)) <<4)| (buf[4]&0x0F);
    }

    dev->als = ((unsigned short)buf[3]<<8)| buf[2];
}

static int ap3216c_open (struct inode *inode, struct file *file){
    file->private_data = &ap3216c;
    printk("ap3216c_open\r\n");   
    unsigned char  value;
    
    //初始化 AP3216C
    ap3216c_write_reg(&ap3216c,AP3216C_SYSTEMCONG,0x4);  //复位
    mdelay(50);

    
    ap3216c_write_reg(&ap3216c,AP3216C_SYSTEMCONG,0x3);
    value = ap3216c_read_reg(&ap3216c,AP3216C_SYSTEMCONG);
    printk("AP3216C_SYSTEMCONG:%#x\r\n",value);
    

    return 0; 
}  

static int ap3216c_close(struct inode *inode, struct file *file){
    struct ap3216c_dev *dev = file->private_data;
    printk("ap3216c_close\r\n");
    return 0;
}

static ssize_t ap3216c_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
    unsigned char err;
    short data[3];
    struct ap3216c_dev *dev = file->private_data;
    printk("ap3216c_read!\r\n");
    //读取ap3216c的数据
    ap3216c_readdata(dev);
    data[0] = dev->ir;
    data[1] = dev->ps;
    data[2] = dev->als;
    
    err = copy_to_user(buf,data,sizeof(data));
    printk("ap3216c_read\r\n");
    return 0;
}
const struct file_operations ap3216c_opts={
    .owner = THIS_MODULE,
    .open  = ap3216c_open,
    .release = ap3216c_close,
    .read = ap3216c_read,
};

static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id){

    printk("ap3216c_probe\r\n");
    //搭建字符设备驱动框架

    int ret = 0;
    //注册设备号
    ap3216c.MAJOR = 0;
    if ( ap3216c.MAJOR )
    {
        ap3216c.devid = MKDEV(ap3216c.MAJOR,0);
        ret  = register_chrdev_region(ap3216c.devid ,AP3216C_CNT,AP3216C_NAME);
    }else{
        ret = alloc_chrdev_region(&ap3216c.devid,0,AP3216C_CNT,AP3216C_NAME);
         ap3216c.MAJOR  = MAJOR(ap3216c.devid);
         ap3216c.MINOR  = MINOR(ap3216c.devid);
    }
    if (ret<0)
    {
        printk("ap3216c chrdev_region error\r\n");
        goto fail_devid;
        
    }

    //注册设备
    ap3216c.cdev.owner = THIS_MODULE;
    cdev_init(&ap3216c.cdev,&ap3216c_opts);


    ret  = cdev_add(&ap3216c.cdev, ap3216c.devid, AP3216C_CNT);
    if (ret < 0 )
    {
        goto fail_cdev;
    }
    

    //自动创建设备节点
    ap3216c.class = class_create(THIS_MODULE,AP3216C_NAME);
    if (IS_ERR(ap3216c.class))
    {
        ret = PTR_ERR(ap3216c.class);
        goto fail_class;
    }
    
    ap3216c.device = device_create(ap3216c.class,NULL,ap3216c.devid,NULL,AP3216C_NAME);
    if (IS_ERR(ap3216c.device ))
    {
        ret  = PTR_ERR(ap3216c.device); 
        goto fail_device;
    }

    ap3216c.private_data =  client ;  //将i2c_client传递给设备结构体中的指针。里面保存着i2c的大量句柄

    return 0;
fail_device:
    class_destroy(ap3216c.class);
fail_class:
    cdev_del(&ap3216c.cdev);
fail_cdev:
    unregister_chrdev_region(ap3216c.devid,AP3216C_CNT);
fail_devid:  
    return ret;
}

static int ap3216c_remove(struct i2c_client *client){

    cdev_del(&ap3216c.cdev);

    
    unregister_chrdev_region(ap3216c.devid,AP3216C_CNT);
    device_destroy(ap3216c.class,ap3216c.devid);
    class_destroy(ap3216c.class);

    
    return 0;

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

//传统匹配表
const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c",0},
    {},
};
struct i2c_driver ap3216c_driver= {
    .probe = ap3216c_probe,
    .remove =  ap3216c_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "ap3216c",
        .of_match_table = ap3216c_of_match,
    },
    .id_table = ap3216c_id,

};
static int __init ap3216c_init(void){
    int ret = 0;
    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");

对于新老驱动匹配of_device_id和device_id做兼容,只需要一个宏定义即可。

c 复制代码
#if IS_ENABLED(CONFIG_OF)
const struct of_device_id imx415_of_match[] = {
    {
        .compatible = "sony,imx415" ,
    },
    {},
}
#endif
相关推荐
k***81721 小时前
使用Canal将MySQL数据同步到ES(Linux)
linux·mysql·elasticsearch
8K超高清1 小时前
超高清科技引爆中国电影向“新”力
大数据·运维·服务器·网络·人工智能·科技
CIb0la1 小时前
Google 将用 Aluminium OS 取代 ChromeOS
运维·生活·媒体
至此流年莫相忘1 小时前
第二版:Windows 服务器上私有化部署 Qwen/Qwen3-Embedding-0.6B 模型
服务器·windows·embedding
last demo1 小时前
Linux 正则表达式
linux·mysql·正则表达式
fiveym1 小时前
CentOS 7 部署 isc-dhcp-server(PXE 场景专属,含完整配置 + 避坑指南)
linux·运维·centos
执笔论英雄2 小时前
【RL】 ROLL Generate Scheduler
java·服务器·数据库
Ghost Face...2 小时前
V4L2架构与硬件适配全解析
linux·架构
繁华似锦respect2 小时前
C++ 设计模式之工厂模式详细介绍
java·linux·c++·网络协议·设计模式