DeviceDriver (12): I2C driver

One: Linux I2C driver framework

The Linux kernel divides the I2C driver into two parts:

(1) I2C bus driver, I2C bus driver is the SOC I2C controller driver, also called I2C adapter driver

(2) I2C device driver, I2C device driver is a driver written for specific I2C devices.

As a SOC developer, there is no need to care about the i2c bus driver, just the i2c device driver.

1. I2C bus driver

       The difference between I2C devices and platform devices is that I2C has its own bus, the I2C bus, and does not need to create one. The I2C bus driver focuses on the I2C adapter (SOC's I2C interface controller) driver. There are two important data structures: i2c_adapter and i2c_algorithm. The Linux kernel abstracts the I2C adapter into the i2c_adapter structure:

include/linux/i2c.h

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

For an i2c adapter, it is necessary to provide external read and write API functions, and the device driver can use these API functions to complete read and write operations. The pointer variable algo of type i2c_algorithm is the method for the i2c adapter to communicate with the i2c device.

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

master_xfer: The transfer function of the i2c adapter, which can be used to complete the communication with the i2c device.

smbus_xfer: The transfer function of SMBUS bus.

The main task of the i2c bus driver is to initialize the i2c_adapter structure variable, and then set the master_xfer function in i2c_algorithm. After completion, register the set i2c_adapter with the system through the two functions i2c_add_numberd_adapter and i2c_add_adapter. The prototype is as follows:

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

Among them, adapter and adap: i2c_adapter to be added to the Linux kernel, that is, the I2C adapter.

2. I2C device driver

        The i2c device driver focuses on two data structures: i2c_client and i2c_driver. According to bus, device, and driver model, i2c_client describes device information, and i2c_driver describes drive content, similar to platform_driver.

(1) i2c_client: one device corresponds to one i2c_client, and this data is allocated every time an i2c device is detected

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

(2) i2c_driver: similar to platform_driver, the probe function will be executed when the i2c device is successfully matched with the 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 *);

	void (*alert)(struct i2c_client *, unsigned int data);

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

If you use the device tree, you need to set the of_match_table member variable of device_driver, which is the compatible attribute of the driver.

For the i2c device driver, the key task is to build the i2c_driver. After the build is completed, the i2c_driver needs to be registered with the Linux kernel. The registration function is i2c_register_driver:

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

Or use the registered macro definition: i2c_add_driver

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

Logout function: i2c_del_driver

void i2c_del_driver(struct i2c_driver *driver)

i2c_driver registration example:

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

/* i2c 驱动的 remove 函数 */
static int ap3216c_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 device and driver matching

The matching process of the device and the driver is completed by the I2C bus. The data structure of the I2C bus is 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,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);

The member function match is the matching function of the device and driver of the i2c bus: 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;
}

Two: I2C adapter driver analysis

In general, the i2c adapter driver does not require us to write, so we will make a simple analysis.

Find the i2c1 controller node in the corresponding SOC device tree shared file (imx6ull.dtsi): i2c1

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

Search the compatible property to find the i2c adapter driver file:

static struct platform_device_id imx_i2c_devtype[] = {
	{
		.name = "imx1-i2c",
		.driver_data = (kernel_ulong_t)&imx1_i2c_hwdata,
	}, {
		.name = "imx21-i2c",
		.driver_data = (kernel_ulong_t)&imx21_i2c_hwdata,
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(platform, imx_i2c_devtype);

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 */ }
};
MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);


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

Analysis of the adapter driver shows that the i2c adapter driver is a standard platform driver framework, while the i2c device driver uses the i2c bus driver framework.

When the device and the driver are successfully matched, the i2c_imx_probe function will execute:

static int i2c_imx_probe(struct platform_device *pdev)

The main work of the i2c_imx_probe function has two points:

(1) Initialize i2c_adapter, set i2c_algorithm to i2c_imx_algo, and finally register i2c_adapter with the Linux kernel

(2) Initialize the relevant registers of the i2c1 controller

i2c_imx->adapter.algo		= &i2c_imx_algo;

static struct i2c_algorithm i2c_imx_algo = {
	.master_xfer	= i2c_imx_xfer,
	.functionality	= i2c_imx_func,
};

.functionality is used to return what kind of communication protocol the i2c adapter supports:

static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL
		| I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}

.master_xfer is used to complete communication with i2c devices

static int i2c_imx_xfer(struct i2c_adapter *adapter,
						struct i2c_msg *msgs, int num)
{
	unsigned int i, temp;
	int result;
	bool is_lastmsg = false;
	struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);

	dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);

	/* Start I2C transfer */
	result = i2c_imx_start(i2c_imx);
	if (result)
		goto fail0;

	/* read/write data */
	for (i = 0; i < num; i++) {
		if (i == num - 1)
			is_lastmsg = true;

		if (i) {
			dev_dbg(&i2c_imx->adapter.dev,
				"<%s> repeated start\n", __func__);
			temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
			temp |= I2CR_RSTA;
			imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
			result =  i2c_imx_bus_busy(i2c_imx, 1);
			if (result)
				goto fail0;
		}
		dev_dbg(&i2c_imx->adapter.dev,
			"<%s> transfer message: %d\n", __func__, i);
		/* write/read data */
... ...
		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]);
			else
				result = i2c_imx_write(i2c_imx, &msgs[i]);
		}
		if (result)
			goto fail0;
	}

fail0:
	/* Stop I2C transfer */
	i2c_imx_stop(i2c_imx);

	dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,
		(result < 0) ? "error" : "success msg",
			(result < 0) ? result : num);
	return (result < 0) ? result : num;
}

Among them, i2c_imx_start, i2c_imx_read, i2c_imx_write, i2c_imx_stop these functions are the specific operation functions of the i2c register.

Three: I2C device driver process

1. Unused device tree

When the device tree is not used, the i2c_board_info structure needs to be used in the BSP to describe a specific I2C device.

struct i2c_board_info {
	char		type[I2C_NAME_SIZE];
	unsigned short	flags;
	unsigned short	addr;
	void		*platform_data;
	struct dev_archdata	*archdata;
	struct device_node *of_node;
	struct fwnode_handle *fwnode;
	int		irq;
};

The two member variables type and addr must be set, one is the name of the I2C device, and the other is the device address of the I2C device. Example OV2640 device

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

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

2. Use the device tree

When using the device tree, the i2c device information can be created by creating the corresponding node, such as imx6ull-14x14-evk.dts

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

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

"mag3110@0e" is the name of the child node, and the "0e" after "@" is the i2c device address of mag3110, which is the same as "reg = <0x0e>".

compatible = "fsl,mag3110"; used to set i2c device attributes for matching drivers.

3. Device data receiving and processing

       As mentioned in the first chapter, the first thing an i2c device driver needs to do is to initialize i2c_driver and register it with the Linux kernel. When the device and the driver match, the probe function in i2c_driver will be executed. What the probe function does is the set of character device drivers. Generally, the i2c device needs to be initialized. To initialize the i2c device, you need to read and write the i2c device register. You need to use the i2c_transfer function, which will eventually call the master_xfer function in the i2c_algorithm of the i2c adapter, which is the i2c_imx_xfer function for imx6u.

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

The msgs parameter is a pointer parameter of the i2c_msg type. The data receiving and sending messages of the i2c device in the Linux kernel are described by the i2c_msg structure:

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

Before using the i2c_transfer function to send data, build i2c_msg first.

In addition to the i2c_transfer function, there are two APIs that can be used: i2c_master_send and i2c_master_recv, which will eventually call i2c_transfer

int i2c_master_send(const struct i2c_client *client,
    const char *buf,
    int count)

int i2c_master_recv(const struct i2c_client *client,
    char *buf,
    int count)

4. Example: mag3110 equipment

Search according to the compatible attribute of the device node:

#define MAG3110_DRV_NAME       "mag3110"


static const struct i2c_device_id mag3110_id[] = {
	{MAG3110_DRV_NAME, 0},
	{}
};

MODULE_DEVICE_TABLE(i2c, mag3110_id);
static struct i2c_driver mag3110_driver = {
	.driver = {
		.name	= MAG3110_DRV_NAME,
		.owner	= THIS_MODULE,
		.pm	= MAG3110_DEV_PM_OPS,
	},
	.probe = mag3110_probe,
	.remove = mag3110_remove,
	.id_table = mag3110_id,
};

static int __init mag3110_init(void)
{
	return i2c_add_driver(&mag3110_driver);
}

static void __exit mag3110_exit(void)
{
	i2c_del_driver(&mag3110_driver);
}

Analysis of the probe function structure: mag3110_probe

static int mag3110_probe(struct i2c_client *client,
				   const struct i2c_device_id *id)
{
    ... ...
	/* Initialize mag3110 chip */
	mag3110_init_client(client);
}

-->>mag3110_init_client(client);
    -->> mag3110_write_reg(client, MAG3110_CTRL_REG2, val);
        -->> i2c_smbus_write_byte_data(client, reg, value);
            -->> i2c_smbus_xfer(client->adapter, client->addr, client->flags,
			          I2C_SMBUS_WRITE, command,
			          I2C_SMBUS_BYTE_DATA, &data);
                -->> i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
				          command, protocol, data);
                    -->> i2c_transfer(adapter, msg, num);

Then the third section gives an example of the construction process of the msg parameter in the i2c_transfer function: refer to the third section i2c_msg structure for the specific meaning

static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
				   unsigned short flags,
				   char read_write, u8 command, int size,
				   union i2c_smbus_data *data)
{
	unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
	unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];

	struct i2c_msg msg[2] = {
		{
			.addr = addr,
			.flags = flags,
			.len = 1,
			.buf = msgbuf0,
		}, {
			.addr = addr,
			.flags = flags | I2C_M_RD,
			.len = 0,
			.buf = msgbuf1,
		},
	};

    ... ...
	case I2C_SMBUS_PROC_CALL:
		num = 2; /* Special case */
		read_write = I2C_SMBUS_READ;
		msg[0].len = 3;
		msg[1].len = 2;
		msgbuf0[1] = data->word & 0xff;
		msgbuf0[2] = data->word >> 8;
		break;
    ... ...

	status = i2c_transfer(adapter, msg, num);
}

Four: sample code

1. Modify the device tree

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

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

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

2. Drive

#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;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int major;
    void *private_data;
    unsigned short ir, als, ps;
};

struct ap3216c_dev ap3216cdev;

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]为发送要读取的首地址 */
    msg[0].addr = client->addr; /* ap3216c 地址 */
    msg[0].flags = 0; /* 标记为发送数据 */
    msg[0].buf = &reg; /* 读取的首地址 */
    msg[0].len = 1; /* reg 长度 */

    /* msg[1]读取数据 */
    msg[1].addr = client->addr; /* ap3216c 地址 */
    msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
    msg[1].buf = val; /* 读取数据缓冲区 */
    msg[1].len = len; /* 要读取的数据长度 */

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


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;                         /* 寄存器首地址 */
    memcpy(&b[1], buf, len);            /* 将要写入的数据拷贝到数组 b 里面 */
    
    msg.addr = client->addr;            /* ap3216c 地址 */
    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 i = 0;
    unsigned char buf[6];

    for(i = 0; i < 6; i++)
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
    }

    if(buf[0] & 0x80)
        dev->ir = 0;
    else
        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
        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;
    ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
    mdelay(50);
    ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x03);
    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;

    ap3216c_readdata(dev);

    data[0] = dev->ir;
    data[1] = dev->als;
    data[2] = dev->ps;
    err = copy_to_user(buf, data, sizeof(data));
    return 0;
}

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

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

static int ap3216c_probe(struct i2c_client *client,
    const struct i2c_device_id *id)
{
    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);
    }

    cdev_init(&ap3216cdev.cdev, &ap3216c_fops);
    cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);

    ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
    ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);

    ap3216cdev.private_data = client;

    return 0;
}

static int ap3216c_remove(struct i2c_client *client)
{
    device_destroy(ap3216cdev.class, ap3216cdev.devid);
    class_destroy(ap3216cdev.class);

    cdev_del(&ap3216cdev.cdev);
    unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);

    return 0;
}

static const struct i2c_device_id ap3216c_id[] = {
    {"ap3216c", 0},
    {}
};

static const struct of_device_id ap3216c_of_match[] = {
    {.compatible = "alientek, ap3216c"},
    {}
};


static 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 ap3216c_init(void)
{
    int ret = 0;
    
    ret = i2c_add_driver(&ap3216c_driver);
    return ret;
}

static void ap3216c_exit(void)
{
    i2c_del_driver(&ap3216c_driver);
}

// module_i2c_driver(ap3216c_driver);
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");

 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/104819330