linux deeply understands I2C kernel driver

Posted by vinylblare on Thu, 10 Feb 2022 07:01:42 +0100

Series articles

1. Mx6ull manual to find the use method and light the LED in practice (register version)
1. Mx6ull manual to find out how to use actual LED (firmware library version)
Actual combat of linux character device driver
linux LED device driver file
Practical analysis of linux device tree (. dts)
linux uses the device tree to light up the LED actual combat
Concurrency and competition in linux driver
linux kernel timer
linux kernel interrupt understanding
linux driver blocking and non blocking
linux kernel asynchronous notification
linux platform driver framework
LED driver of linux kernel
linux misc device driver
linux input subsystem


I2C is a very common serial communication interface, which is used to connect various peripherals, sensors and other devices. This paper understands the implementation of I2C kernel and the work that developers need to develop, and does not involve the physical characteristics, timing and other information of I2C.

The I2C driver is divided into two parts by the kernel:

I2C bus driver, I2C bus driver is the I2C controller driver of SOC, also known as I2C adapter driver;
I2C device driver. I2C device driver is a driver written for specific I2C devices.

1. I2C bus driver

I2C bus driver focuses on I2C adapter driver. There are two important data structures: i2c_adapter and i2c_algorithm.


 * 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;
        struct device dev;              /* the adapter device */
        char name[48];\


 * struct i2c_algorithm - represent I2C transfer method
 * @master_xfer: Issue a set of i2c transactions to the given I2C adapter
 *   defined by the msgs array, with num messages available to transfer via
 *   the adapter specified by adap.
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 *);

i2c_adapter corresponds to a physical adapter, while i2c_algorithm corresponds to a set of communication methods.
i2c_ Key function master in algorithm_ Used to generate ixfer function_ MSG is the unit (transmission address, direction, buffer, length and other information)

master_xfer is the transfer function of I2C adapter; smbus_xfer is the transfer function of SMBUS bus.

Through i2c_add_numbered_adapter or I2C_ add_ The two functions adapter register the set I2C with the system_ Adapter, the prototypes of these two functions are as follows:

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)

Remove I2C adapter

void i2c_del_adapter(struct i2c_adapter * adap)

2. I2C device driver

I2C device driver focuses on two data structures: i2c_client and i2c_driver.

i2c_client describes the device information, corresponding to the device tree. One device corresponds to one i2c_client

struct i2c_client {
        unsigned short flags;           /* div., see below              */
        unsigned short addr;            /*Chip address, 7 bits, with lower 7 bits   */                                        
        char name[I2C_NAME_SIZE];
        struct i2c_adapter *adapter;    /* Corresponding adapter */
        struct device dev;              /*Equipment structure  */
        int irq;                        /* interrupt */
        struct list_head detected;

i2c_driver describes the driver content (specific programming implementation) and writes the key content of I2C device driver

struct i2c_driver {
        unsigned int class;
        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 *);
        void (*shutdown)(struct i2c_client *);

        struct device_driver driver;
        const struct i2c_device_id *id_table;

i2c_driver: corresponding to a set of driving methods, it provides functions such as probe(),remove(),suspend().
i2c_client: corresponding to real physical devices, usually i2c_board_info, or use the device tree description.

i2c_driver and i2c_client relationship is one to many, one i2c_driver supports multiple I2C of the same type at the same time_ client.
i2c_client attached to i2c_adpater. An I2C_ Adpatter, which can be used by multiple i2c_client attachment.

Register I2C with the Linux kernel_ Driver, the registration function is int i2c_register_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

i2c_add_driver is also often used to register i2c_driver

#define i2c_add_driver(driver) \
					i2c_register_driver(THIS_MODULE, driver)


void i2c_del_driver(struct i2c_driver *driver)

3. I2C core

The matching process between I2C device and driver is completed by I2C core, and the data structure is i2c_bus_type, defined in drivers / I2C / I2C core C Documents:

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 matching process, three matching methods

static int i2c_device_match(struct device *dev, struct device_driver *drv)
        struct i2c_client       *client = i2c_verify_client(dev);
        struct i2c_driver       *driver;

        /* 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;

1) Device tree matching: of_ driver_ match_ The device function is used to complete the device tree device and driver matching. Compare the compatible attribute and of of of the I2C device node_ device_ Whether the compatible attribute in ID is equal. If so, it indicates that I2C device and driver match.

i2c_ bus_ When is type used? First, let's take a look at what the I2C framework does

4. I2C framework, what does the system do for you?

The bus is registered to the structure involved in the system i2c_adapter,i2c_algorithm

In imx6ull Locate the I2C1 controller node of I.MX6U in dtsi file.

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

Under the kernel source folder, search the attribute value "imx6ul-i2c", grep -r "imx6ul-i2c". /, In the driver file drivers / I2C / busses / I2C IMX Found in C

static const struct of_device_id i2c_imx_dt_ids[] = {
        { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
        { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
        { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
        { /* sentinel */ }
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);

1) The I2C adapter driver is a standard platform driver
2) The attribute value of "fsl,imx21-i2c" is consistent with the compatible attribute value of i2c1 node in the device tree
3)platform_driver_register registers the driver on the I2C bus. How to execute the match function is analyzed below:

#define platform_driver_register(drv) \
        __platform_driver_register(drv, THIS_MODULE)

//Check__ platform_driver_register function
int __platform_driver_register(struct platform_driver *drv,
                                struct module *owner)
        drv->driver.owner = owner;
        drv->driver.bus = &platform_bus_type;//Important: bind bus
        return driver_register(&drv->driver);
//Then look at the driver_register function
int driver_register(struct device_driver *drv)
        other = driver_find(drv->name, drv->bus);
        ret = bus_add_driver(drv);
        ret = driver_add_groups(drv, drv->groups);

int bus_add_driver(struct device_driver *drv)
        bus = bus_get(drv->bus);
        if (drv->bus->p->drivers_autoprobe) {
                error = driver_attach(drv);
        module_add_driver(drv->owner, drv);
        error = driver_create_file(drv, &driver_attr_uevent);

int driver_attach(struct device_driver *drv)
        return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
#__ driver_attach function is a parameter
static int __driver_attach(struct device *dev, void *data)
        if (!driver_match_device(drv, dev))
                return 0;
        if (!dev->driver)
                driver_probe_device(drv, dev);

static inline int driver_match_device(struct device_driver *drv, struct device *dev)
        return drv->bus->match ? drv->bus->match(dev, drv) : 1;
Final call bus match function
drv->bus->match Which function is it? drv->driver.bus = &platform_bus_type
drv->bus->match Namely platform_bus_type.match

struct bus_type platform_bus_type = {
        .name           = "platform",
        .dev_groups     = platform_dev_groups,
        .match          = platform_match,
        .uevent         = platform_uevent,
        .pm             = &platform_dev_pm_ops,

static int platform_match(struct device *dev, struct device_driver *drv)
        struct platform_device *pdev = to_platform_device(dev);
        struct platform_driver *pdrv = to_platform_driver(drv);
        if (pdev->driver_override)
                return !strcmp(pdev->driver_override, drv->name);
        //Device tree matching function        
        if (of_driver_match_device(dev, drv))
                return 1;
        if (acpi_driver_match_device(dev, drv))
                return 1;
        if (pdrv->id_table)
                return platform_match_id(pdrv->id_table, pdev) != NULL;
        return (strcmp(pdev->name, drv->name) == 0);

Summary: equipment and platform_ Binding of driver

When driving registration platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev()
For each virtual platform bus Equipment operation match Operation;
If it matches, call platform_drv_probe()->driver->probe(),If probe If successful, bind the device to the driver.

After match ing, execute the probe function. The execution process is as follows

int driver_probe_device(struct device_driver *drv, struct device *dev)
        if (!device_is_registered(dev))
                return -ENODEV;
        ret = really_probe(dev, drv);

static int really_probe(struct device *dev, struct device_driver *drv)
	 if (dev->bus->probe) {
                ret = dev->bus->probe(dev);
        } else if (drv->probe) {
                ret = drv->probe(dev);

DRV - > probe I2C_ imx_ The probe function executes.

i2c_ When did the adapter register? In I2C_ imx_ In probe function

static int i2c_imx_probe(struct platform_device *pdev)
		//i2c_imx is the adapter structure
        i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);
        /* Setup i2c_imx driver structure Assign value to structure */
        strlcpy(i2c_imx->, pdev->name, sizeof(i2c_imx->;
        i2c_imx->adapter.owner          = THIS_MODULE;
        i2c_imx->adapter.algo           = &i2c_imx_algo;
        i2c_imx->     = &pdev->dev;
        i2c_imx->             = pdev->id;
        i2c_imx->    = pdev->dev.of_node;
        i2c_imx->base                   = base;
        ret = i2c_add_numbered_adapter(&i2c_imx->adapter);

Summary: work done in probe function

  1. Initialize i2c_adapter, setting I2C_ The algorithm is i2c_imx_algo, and finally register I2C with the Linux kernel_ adapter.
  2. Initialize the relevant registers of I2C1 controller. i2c_imx_algo contains the communication function master between I2C1 adapter and I2C device_ xfer,i2c_imx_algo structure determination

The functions analyzed above include driver and the functions analyzed below include device

i2c_ What did the adapter do? Bind device to driver

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
        if (adap->nr == -1) /* -1 means dynamically assign bus id */
                return i2c_add_adapter(adap);
        return __i2c_add_numbered_adapter(adap);

int i2c_add_adapter(struct i2c_adapter *adapter)
        if (dev->of_node) {
                id = of_alias_get_id(dev->of_node, "i2c");
                if (id >= 0) {
                        adapter->nr = id;
                        return __i2c_add_numbered_adapter(adapter);
        return i2c_register_adapter(adapter);

static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
        return i2c_register_adapter(adap);
static int i2c_register_adapter(struct i2c_adapter *adap)
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);
        adap->dev.bus = &i2c_bus_type;
        adap->dev.type = &i2c_adapter_type;
        res = device_register(&adap->dev,)
//With device
int device_register(struct device *dev)
        return device_add(dev);

int device_add(struct device *dev)
        dev = get_device(dev);
		bus_probe_device(dev);//Start looking for the driver corresponding to the device

//Find a driver for the device  
void bus_probe_device(struct device *dev)  
    struct bus_type *bus = dev->bus;//Obtain the bus to which the device belongs, which is set during device initialization  
    if (bus->p->drivers_autoprobe) {  
        ret = device_attach(dev);

int device_attach(struct device *dev)  
    if (dev->driver) {//If the device already has a driver  
    } else {//The device has no driver  
        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);-------Traverse the on the bus driver Linked list-------  
//__ device_attach function as argument
static int __device_attach(struct device_driver *drv, void *data)  
    if (!driver_match_device(drv, dev))//Whether the device and driver match the function,
        return 0;  
    return driver_probe_device(drv, dev);//If the device and driver match successfully, call the probe function
int driver_probe_device(struct device_driver *drv, struct device *dev)  
    ret = really_probe(dev, drv);//Continue calling really_probe function

static int really_probe(struct device *dev, struct device_driver *drv)  
    dev->driver = drv;//After matching, record the drive information into the equipment  
    if (dev->bus->probe) {//If there is a probe function on the bus, the probe function of the bus is called  
        ret = dev->bus->probe(dev);  

    } else if (drv->probe) {  
        ret = drv->probe(dev);//If there is no probe function in the bus, the driven probe function is called  

In the previous summary, the device and driver in the kernel have been bound, and the probe function is executed. In this function, the adapter structure is registered and the adapter is registered to i2c_bus_type. This process needs to be further understood

i2c_imx_algo contains the communication function master between I2C1 adapter and I2C device_ xfer

static struct i2c_algorithm i2c_imx_algo = {
	.master_xfer = i2c_imx_xfer,
	.functionality = i2c_imx_func,
static int i2c_imx_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
        struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);

        /* Start I2C transfer */
        result = i2c_imx_start(i2c_imx);
        if (result)
                if (msgs[i].flags & I2C_M_RD)
                        result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
                else {
                        if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
                                result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
                                result = i2c_imx_write(i2c_imx, &msgs[i]);
                if (result)
                        goto fail0;

        /* Stop I2C transfer */

Summary: I2C sequential reading and writing information

  1. Call I2C_ imx_ The start function starts I2C communication
  2. Call I2C_ imx_ The stop function stops I2C communication
  3. If you are reading data from an I2C device, call i2c_imx_read function.
  4. Write data to I2C device. If you want to use DMA, use i2c_imx_dma_write function to write data. If you don't use DMA, use I2C_ imx_ The write function completes writing data

So I2C_ imx_ When is xfer called?
When the driver sends information to the adapter, I2C_ When the transfer function is called

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
 	if (adap->algo->master_xfer) {

5. I2C framework, what do developers need to do?

5.1 I2C data communication mode

Core function: i2c_transfer function. Eventually, I2C in the I2C adapter will be called_ Master in algorithm_ Xfer function.

int i2c_transfer(struct i2c_adapter *adap,
						struct i2c_msg *msgs,
						int num)

adap: I2C adapter used, I2C_ The client will save its corresponding i2c_adapter.
msgs: one or more messages to be sent by I2C.
num: the number of messages, that is, the number of msgs

i2c_msg structure

struct i2c_msg {
	__u16 addr; /* Slave address */
	__u16 flags; /* sign */
	#define I2C_M_TEN 0x0010
	#define I2C_M_RD 0x0001
	#define I2C_M_STOP 0x8000
	#define I2C_M_NOSTART 0x4000
	#define I2C_M_REV_DIR_ADDR 0x2000
	#define I2C_M_IGNORE_NAK 0x1000
	#define I2C_M_NO_RD_ACK 0x0800
	#define I2C_M_RECV_LEN 0x0400
	__u16 len; /* Message (msg) length */
	__u8 *buf; /* Message data */

1) Read data example

static int xxx_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	/* msg[0]Send the first address to read for */
	msg[0].addr = client->addr;		/* I2C Device address */
	msg[0].flags = 0;					/* Mark as send data */
	msg[0].buf = &reg;					/* First address read */
	msg[0].len = 1;						/* reg length*/

	/* msg[1]Read data */
	msg[1].addr = client->addr;		/* I2C Device address */
	msg[1].flags = I2C_M_RD;		/* Mark as read data*/
	msg[1].buf = val;					/* Read data buffer */
	msg[1].len = len;					/* Length of data to read*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
		ret = -EREMOTEIO;
	return ret;

When I2C reads data, it needs to send the register address to be read first, and then read the data, so it needs to prepare two i2c_msg. One is used to send the register address and the other is used to read the register value.

msg[0], set flags to 0 to write data
msg[1], set flags to I2C_M_RD, read data

2) Write data example

static int xxx_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	b[0] = reg;					/* Register header address */
	memcpy(&b[1],buf,len);		/* Copy the data to be written into array b */
	msg.addr = client->addr;	/*I2C Device address */
	msg.flags = 0;				/* Mark as write data */

	msg.buf = b;				/* Data buffer to write */
	msg.len = len + 1;			/* Length of data to write */

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

I2C write operation is a little simpler than read operation. An i2c_msg is enough.
1) Array b is used to store the first address of the register and the data to be sent
2) Len is the register address of len+1 plus one byte

5.2 preparation of I2C device driver

The above knowledge can only be considered when doing SOC development. The chips used have basically helped us do a good job. Driver developers only need to write I2C_ And I2C driver_ client

1, I2C device information description (i2c_client)

1. Unused device tree
When the device tree is not used, I2C needs to be used in BSP_ board_ Info structure to describe a specific I2C device

struct i2c_board_info {
	char type[I2C_NAME_SIZE]; /* I2C Device name */
	unsigned short flags; /* sign */
	unsigned short addr; /* I2C Device address */
	void *platform_data;
	struct dev_archdata *archdata;
	struct device_node *of_node;
	struct fwnode_handle *fwnode;
	int irq;

Two member variables, type and addr, must be set. One is the name of the I2C device and the other is the name of the I2C device
Device address

static struct i2c_board_info mx27_3ds_i2c_camera = {
		I2C_BOARD_INFO("ov2640", 0x30),

I2C_BOARD_INFO is a macro, which is defined as follows

#define I2C_BOARD_INFO(dev_type, dev_addr) \
            .type = dev_type, .addr = (dev_addr)

2. Use device tree

1. IO modify or add
Set pin reuse and open imx6ull alientek EMMC dts

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

pinctrl_i2c1 is the IO node of I2C1. Uart4 will be used here_ TXD and UART4_RXD these two IOS are
Multiplexed to I2C1_SCL and I2C1_SDA and electrical properties are set to 0x4001b8b0.

2. Add ap3216c child node to i2c1 node

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";
	ap3216c@1e {
	compatible = "alientek,ap3216c";
	reg = <0x1e>;
  1. Ap3216c child node, "1e" after @ is the device address of ap3216c
  2. Set the compatible value to "alientek,ap3216c";
  3. The reg attribute also sets the ap3216c device address, so reg is set to 0x1e
  4. I2C address can be found in the manual, and the excerpts in the manual are as follows:
    I2C Slave Address
    The slave addresses have 7 bits. A read/write bit should be appended to the slave address by the master device to properly communicate with the device. The slave address of this device is 0x1E

2, i2c_driver driver template:

/* AP3216C Operation function */
static const struct file_operations xxx_ops = {
        .owner = THIS_MODULE,
        .open = xxx_open,
        .read = xxx_read,
        .release = xxx_release,
//Read function
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)

static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
	//Register ops read-write function
static const struct i2c_device_id xxx_id[] = {
        {"alientek,ap3216c", 0},

/* Device tree matching list */
static const struct of_device_id xxx_of_match[] = {
        { .compatible = "alientek,ap3216c" },
        { /* Sentinel */ }

/* i2c Drive structure */
static struct i2c_driver xxx_driver = {
        .probe = xxx_probe,
        .remove = xxx_remove,
        .driver = {
                        .owner = THIS_MODULE,
                        .name = "ap3216c",
                        .of_match_table = xxx_of_match,
        .id_table = ap3216c_id,

static int __init xxx_init(void)
        int ret = 0;

        ret = i2c_add_driver(&xxx_driver);
        return ret;

static void __exit xxx_exit(void)


Analyze i2c_del_driver

i2c_ del_ The driver implementation relationship is very complex, and the function call relationship

				 bus_add_driver(struct device_driver *drv)
				 		driver_attach(struct device_driver *drv) 
				 			   bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
				 			            driver_probe_device(structdevice_driver *drv, struct device *dev) 

Focus on the next two functions

int driver_probe_device(structdevice_driver *drv, struct device *dev) 
	if (drv->bus->match && !drv->bus->match(dev, drv)) 
	   goto done; 
	ret = really_probe(dev, drv); 
//Execute probe function
static int really_probe(struct device *dev, struct device_driver *drv) 
//Call the probe function of the bus to which the driver belongs:
    if (dev->bus->probe) { 
        ret = dev->bus->probe(dev); 
//probe function in called driver: 
    else if (drv->probe) { 
        ret = drv->probe(dev); 

2. According to the above template, AP3216C driver is realized, focusing on the implementation

Examples are as follows:
Command code in manual:

Header file ap3216creg H information is defined according to the address in the manual

#define AP3216C_ADDR     	 0X1E 	/*  Ap3216c address*/

/* AP3316C register */
#define AP3216C_SYSTEMCONG 	 0x00 	/*  Configuration register*/
#define AP3216C_INTSTATUS 	 0X01 	/*  Interrupt status register*/
#define AP3216C_INTCLEAR 	 0X02 	/*  Interrupt clear register*/
#define AP3216C_IRDATALOW 	 0x0A 	/*  IR data low byte*/
#define AP3216C_IRDATAHIGH 	 0x0B 	/*  IR data high byte*/
#define AP3216C_ALSDATALOW 	 0x0C 	/*  ALS data low byte*/
#define AP3216C_ALSDATAHIGH 	 0X0D 	/*  ALS data high byte*/
#define AP3216C_PSDATALOW 	 0X0E 	/*  PS data low byte*/
#define AP3216C_PSDATAHIGH 	 0X0F 	/*  PS data high byte*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"

#define AP3216C_CNT	1
#define AP3216C_NAME	"ap3216c"

struct ap3216c_dev {
	dev_t devid;			/* Equipment number 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* class 		*/
	struct device *device;	/* equipment 	 */
	struct device_node	*nd; /* Device node */
	int major;			/* Main equipment No */
	void *private_data;	/* Private data */
	unsigned short ir, als, ps;		/* Three optical sensor data */

static struct ap3216c_dev ap3216cdev;

//Read the value in the register, two i2c_msg writes the address flags = 0 before reading, and flags = I2C when reading_ M_ RD
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	/* msg[0]Send the first address to read for */
	msg[0].addr = client->addr;			/* ap3216c Which I2C device does the address represent*/
	msg[0].flags = 0;					/* send data */
	msg[0].buf = &reg;					/* First address read */
	msg[0].len = 1;						/* reg length*/

	/* msg[1]Read data */
	msg[1].addr = client->addr;			/* ap3216c Which I2C device does the address represent*/
	msg[1].flags = I2C_M_RD;			/* Read data*/
	msg[1].buf = val;					/* Read data buffer */
	msg[1].len = len;					/* Length of data to read*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		printk("i2c msg failed=%d\n",ret);
		ret = -EREMOTEIO;
	return ret;

//It's easy to write. The value should be an i2c_msg, array b, register address + data
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	b[0] = reg;					
	msg.addr = client->addr;	/* ap3216c Which I2C device does the address represent*/
	msg.flags = 0;				

	msg.buf = b;				
	msg.len = len + 1;			/* Length of data to write + 1 */

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

//Read a register
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
	u8 data = 0;
	ap3216c_read_regs(dev, reg, &data, 1);
	return data;

 //Write a register
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);

 * @description	: Read the data of AP3216C and read the original data, including ALS,PS and IR. Attention!
 *				: If ALS and IR + PS are turned on at the same time, the time interval between two data reads should be greater than 112.5ms

void ap3216c_readdata(struct ap3216c_dev *dev)
	unsigned char i =0;
    unsigned char buf[6];
	/* Cycle through all sensor data */
    for(i = 0; i < 6; i++)	
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);	//Read the value 6 times, first the position and then the high position

    if(buf[0] & 0X80) 	/* IR_OF If the bit is 1, the data is invalid */
		dev->ir = 0;					
	else 				/* Read the data of IR sensor buf[1]: high 8 bits, buf[0]: take out the low 2 bits from the low 8 bits		*/
		dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); 			
	dev->als = ((unsigned short)buf[3] << 8) | buf[2];	/* Read data from ALS sensor 			 */  
    if(buf[4] & 0x40)	/* IR_OF If the bit is 1, the data is invalid 			*/
		dev->ps = 0;    													
	else 				/* Read the data of PS sensor    */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 

static int ap3216c_open(struct inode *inode, struct file *filp)
	filp->private_data = &ap3216cdev;

	/* Initialize AP3216C */
    //AP3216C_SYSTEMCONG= 0x00 refers to configuration function, 0x04 refers to soft reset and 0x03 refers to activation function
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* Soft reset AP3216C 			*/
	mdelay(50);														/* AP3216C Reset for at least 10ms 	*/
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* Activate ALS, PS+IR 		*/
	return 0;

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

	struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;

	data[0] = dev->ir;
	data[1] = dev->als;
	data[2] = dev->ps;
	err = copy_to_user(buf, data, sizeof(data));//The read data is returned to the user
	return 0;

static int ap3216c_release(struct inode *inode, struct file *filp)
	return 0;

/* AP3216C Application layer*/
static const struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open = ap3216c_open,
	.read = ap3216c_read,
	.release = ap3216c_release,

 //Execute probe after the driver matches the device
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
	/* 1,Build equipment number */
	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,Register device */
	cdev_init(&ap3216cdev.cdev, &ap3216c_ops);//ops operation function
	cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);

	/* 3,Create class */
	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.class)) {
		return PTR_ERR(ap3216cdev.class);

	/* 4,Create device */
	ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.device)) {
		return PTR_ERR(ap3216cdev.device);

	ap3216cdev.private_data = client;//i2c_client structure

	return 0;

static int ap3216c_remove(struct i2c_client *client)

	unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);

	device_destroy(ap3216cdev.class, ap3216cdev.devid);
	return 0;

/* Traditional matching method ID list */
static const struct i2c_device_id ap3216c_id[] = {
	{"alientek,ap3216c", 0},  

//The device tree matching list is compatible with that of the device tree
static const struct of_device_id ap3216c_of_match[] = {
	{ .compatible = "alientek,ap3216c" },
	{ /* Sentinel */ }

//i2c drive structure 
static struct i2c_driver ap3216c_driver = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "ap3216c",
		   	.of_match_table = ap3216c_of_match, //Device tree matching
	.id_table = ap3216c_id,
//Entry function
static int __init ap3216c_init(void)
	int ret = 0;

    //Add i2c driver
	ret = i2c_add_driver(&ap3216c_driver);
	return ret;

//Exit function
static void __exit ap3216c_exit(void)


Data processing understanding

When reading data, judge the bits according to the conditions and combine the data with the high and low eight bits.
For example, when reading ir data, the lowest two bits of data[0] are valid, and data[1] is the upper eight bits of data

So IR = data [1] < < 2 | data [0] & 0x03;
Similarly, ALS = data [3] < < 8 | data [2];
ps = data[5]<<4 | data[3]&0x0F;

3) Application Writing:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>

int main(int argc, char *argv[])
	int fd;
	char *filename;
	unsigned short databuf[3];
	unsigned short ir, als, ps;
	int ret = 0;

	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;

	while (1) {
		ret = read(fd, databuf, sizeof(databuf));
		if(ret == 0) { 			/* Data read successfully */
			ir =  databuf[0]; 	/* ir Sensor data */
			als = databuf[1]; 	/* als Sensor data */
			ps =  databuf[2]; 	/* ps Sensor data */
			printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
		usleep(200000); /*100ms */
	close(fd);	/* Close file */	
	return 0;