Linux I2C driver experiment

I2C is a very common serial communication interface, which is used to connect various peripherals, sensors and other devices. The I2C interface of I.MX6U has been explained in detail in the bare metal part. In this chapter, we will learn how to develop I2C interface device driver under Linux. The focus is to learn the I2C driver framework under Linux and write I2C device driver according to the specified framework. This chapter also takes the three in one ambient light sensor AP3216C on the I.MX6U-ALPHA development board as an example to explain how to write I2C device driver under Linux through AP3216C.

Introduction to Linux I2C driver framework

Recall how we wrote the AP3216C driver in the bare metal article. We wrote four files: bsp_i2c.c,bsp_i2c.h,bsp_ap3216c.c and bsp_ap3216c.h. The first two files are the IIC interface driver of I.MX6U, and the last two files are the I2C device driver file AP3216C. Equivalent to two-part drive:
① . I2C host driver.
② I2C device driver.
Once the I2C host driver is written, it does not need to be modified. Other I2C devices can directly call the API functions provided by the host driver to complete the read-write operation. This is just in line with the idea of driver separation and layering in Linux. Therefore, the Linux kernel also divides the I2C driver into two parts:
① 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.

I2C bus driver

First, let's take a look at the I2C bus. When talking about platform, we said that platform is a virtual bus to realize the bus, device and driver framework. For I2C, there is no need to virtualize a bus, and I2C bus can be used directly. I2C bus driver focuses on I2C adapter (i.e. I2C interface controller of SOC). Here, two important data structures are used: i2c_adapter and i2c_algorithm, the Linux kernel uses the I2C adapter (controller) of SOC
Abstract into i2c_adapter,i2c_ The adapter structure is defined in the include/linux/i2c.h file. The structure is as follows:

498 struct i2c_adapter {
499 struct module *owner;
500 unsigned int class; /* classes to allow probing for */
501 const struct i2c_algorithm *algo; /* Bus access algorithm*/
502 void *algo_data;
504 /* data fields that are valid for all devices */
505 struct rt_mutex bus_lock;
507 int timeout; /* in jiffies */
508 int retries;
509 struct device dev; /* the adapter device */
511 int nr;
512 char name[48];
513 struct completion dev_released;
515 struct mutex userspace_clients_lock;
516 struct list_head userspace_clients;
518 struct i2c_bus_recovery_info *bus_recovery_info;
519 const struct i2c_adapter_quirks *quirks;
520 };

Line 501, I2C_ The pointer variable algo of algorithm type must provide external read-write API functions for an I2C adapter. The device driver can use these API functions to complete read-write operations. i2c_algorithm is the method by which the I2C adapter communicates with the IIC device.

i2c_ The algorithm structure is defined in the include/linux/i2c.h file. The contents are as follows (delete conditional compilation):

391 struct i2c_algorithm {
398 int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
399 int num);
400 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
401 unsigned short flags, char read_write,
402 u8 command, int size, union i2c_smbus_data *data);
404 /* To determine what the adapter supports */
405 u32 (*functionality) (struct i2c_adapter *);
411 };

Line 398, master_xfer is the transfer function of I2C adapter, which can be used to complete the communication with IIC devices.
Line 400, smbus_xfer is the transfer function of SMBUS bus.

To sum up, the main work of I2C bus driver or I2C adapter driver is to initialize I2C_ The adapter structure variable, and then set I2C_ Master in algorithm_ Xfer function. Pass I2C after completion_ 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)

The difference between the two functions is i2c_add_adapter uses a dynamic bus number instead of i2c_add_numbered_adapter uses a static bus number. Function parameters and return values have the following meanings:
Adapter or adap: I2C to add to the Linux kernel_ Adapter, that is, I2C adapter.
Return value: 0, successful; Negative value, failed.
Use I2C if you want to remove the I2C adapter_ del_ Just use the adapter function. The function prototype is as follows:

void i2c_del_adapter(struct i2c_adapter * adap)

Function parameters and return values have the following meanings:
adap: I2C adapter to remove.
Return value: none.
That's all for the I2C bus (controller or adapter) driver. Generally, the I2C bus driver of SOC is written by semiconductor manufacturers. For example, the I2C adapter driver NXP of I.MX6U has been written, which does not need to be written by users. Therefore, I2C bus driver is shielded for SoC users. We just need to focus on I2C device driver. Unless you work in a semiconductor company, your job is to write I2C adapter driver.

I2C device driver

I2C device driver focuses on two data structures: i2c_client and i2c_driver, according to the bus, device and driver model, I2C bus has been described in the previous section. There are devices and drivers left, i2c_client describes the device information, i2c_driver describes the driving content, which is similar to platform_driver.
1,i2c_client structure
i2c_ The client structure is defined in the include/linux/i2c.h file, as follows:

217 struct i2c_client {
218 unsigned short flags; /* sign*/
219 unsigned short addr; /* Chip address, 7 bits, low 7 bits*/
222 char name[I2C_NAME_SIZE]; /* name*/
223 struct i2c_adapter *adapter; /* Corresponding I2C adapter*/
224 struct device dev; /* Equipment structure*/
225 int irq; /* interrupt*/
226 struct list_head detected;
230 };

One device corresponds to one i2c_client, every time an I2C device is detected, an I2C device will be assigned to this I2C device_ client.
2,i2c_driver structure
i2c_driver is similar to platform_driver is the key content we need to deal with when writing I2C device driver_ The driver structure is defined in the include/linux/i2c.h file, as follows:

161 struct i2c_driver {
162 unsigned int class;
164 /* Notifies the driver that a new bus has appeared. You should
165 * avoid using this, it will be removed in a near future.
166 */
167 int (*attach_adapter)(struct i2c_adapter *) __deprecated;
169 /* Standard driver model interfaces */
170 int (*probe)(struct i2c_client *, const struct i2c_device_id *);
171 int (*remove)(struct i2c_client *);
173 /* driver model interfaces that don't relate to enumeration */
174 void (*shutdown)(struct i2c_client *);
176 /* Alert callback, for example for the SMBus alert protocol.
177 * The format and meaning of the data value depends on the
178 * protocol.For the SMBus alert protocol, there is a single bit
179 * of data passed as the alert response's low bit ("event
180 flag"). */
181 void (*alert)(struct i2c_client *, unsigned int data);
183 /* a ioctl like command that can be used to perform specific
184 * functions with the device.
185 */
186 int (*command)(struct i2c_client *client, unsigned int cmd,
void *arg);
188 struct device_driver driver;
189 const struct i2c_device_id *id_table;
191 /* Device detection callback for automatic device creation */
192 int (*detect)(struct i2c_client *, struct i2c_board_info *);
193 const unsigned short *address_list;
194 struct list_head clients;
195 };

In line 170, after the I2C device and driver are successfully matched, the probe function will execute, just like the platform driver.

Line 188, device_driver drives the structure. If you use the device tree, you need to set device_ Of driver_ match_ Table member variable, that is, the compatible attribute of the driver.

Line 189, id_table is a traditional device matching ID table that does not use the device tree.

For our I2C device driver writers, the key work is to build i2c_driver, after the build is completed, you need to register this I2C with the Linux kernel_ driver. i2c_ The driver registration function is int i2c_register_driver, the prototype of this function is as follows:

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

Function parameters and return values have the following meanings:
owner: This is generally used_ MODULE.
Driver: I2C to register_ driver.
Return value: 0, successful; Negative value, failed.

In addition, i2c_add_driver is also often used to register i2c_driver,i2c_add_driver is a macro defined as follows:

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

i2c_add_driver is right i2c_register_driver makes a simple package with only one parameter, i.e. I2C to be registered_ driver.
When you log off the I2C device driver, you need to register the I2C previously_ Driver logs out of the Linux kernel and needs i2c_del_driver function. The prototype of this function is as follows:

void i2c_del_driver(struct i2c_driver *driver)

Function parameters and return values have the following meanings:
Driver: I2C to log off_ driver.
Return value: none.
i2c_ The registration example code of driver is as follows:

1 /* i2c Driven probe function*/
2 static int xxx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
3 {
4 /* Function specific program*/
5 return 0;
6 }
8 /* i2c Driven remove function*/
9 static int xxx_remove(struct i2c_client *client)
10 {
11 /* Function specific program*/
12 return 0;
13 }
15 /* Traditional matching method ID list*/
16 static const struct i2c_device_id xxx_id[] = {
17 {"xxx", 0},
18 {}
19 };
21 /* Device tree matching list*/
22 static const struct of_device_id xxx_of_match[] = {
23 { .compatible = "xxx" },
24 { /* Sentinel */ }
25 };
27 /* i2c Drive structure*/
28 static struct i2c_driver xxx_driver = {
29 .probe = xxx_probe,
30 .remove = xxx_remove,
31 .driver = {
32 .owner = THIS_MODULE,
33 .name = "xxx",
34 .of_match_table = xxx_of_match,
35 },
36 .id_table = xxx_id,
37 };
39 /* Drive entry function*/
40 static int __init xxx_init(void)
41 {
42 int ret = 0;
44 ret = i2c_add_driver(&xxx_driver);
45 return ret;
46 }
48 /* Drive exit function*/
49 static void __exit xxx_exit(void)
50 {
51 i2c_del_driver(&xxx_driver);
52 }
54 module_init(xxx_init);
55 module_exit(xxx_exit);

Lines 16-19, i2c_device_id, which matches the ID table when there is no device tree.
Lines 22-25, of_device_id, the matching table used by the device tree.
Lines 28-37, i2c_driver, after the I2C device and I2C driver are successfully matched, the probe function will be executed. Like the platform driver, the probe function is basically the standard character device driver.

I2C device and drive matching process

The matching process between I2C device and driver is completed by I2C core. Drivers / I2C / I2C core. C is the core of I2C. I2C core provides some API functions independent of specific hardware, such as the above:

1,i2c_adapter register / unregister function
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)

2,i2c_driver registration / logoff function
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)
The matching process between device and driver is also completed by I2C bus. The data structure of I2C bus is i2c_bus_type, defined in the drivers/i2c/i2c-core.c file, I2C_ bus_ The content of type is as follows:

736 struct bus_type i2c_bus_type = {
737 .name = "i2c",
738 .match = i2c_device_match,
739 .probe = i2c_device_probe,
740 .remove = i2c_device_remove,
741 .shutdown = i2c_device_shutdown,
742 };

. match is the device and driver matching function of I2C bus, here is i2c_device_match is a function. The contents of this function are as follows:

457 static int i2c_device_match(struct device *dev, struct device_driver *drv)
458 {
459 struct i2c_client *client = i2c_verify_client(dev);
460 struct i2c_driver *driver;
462 if (!client)
463 return 0;
465 /* Attempt an OF style match */
466 if (of_driver_match_device(dev, drv))
467 return 1;
469 /* Then ACPI style match */
470 if (acpi_driver_match_device(dev, drv))
471 return 1;
473 driver = to_i2c_driver(drv);
474 /* match on an id table if there is one */
475 if (driver->id_table)
476 return i2c_match_id(driver->id_table, client) != NULL;
478 return 0;
479 }

Line 466, 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 the I2C device and driver match.
Line 470, ACPI_ driver_ match_ The device function is used for matching in ACPI form.
Line 476, I2C_ match_ The ID function is used for the traditional I2C device and driver matching process without device tree. Compare I2C device name with I2C_ device_ Whether the name field of ID is equal. If it is equal, it indicates that the I2C device and driver match.

1. Analysis of I2C adapter driver for mx6u

In the previous section, we explained the I2C driver framework under Linux, which is mainly divided into I2C adapter driver and I2C device driver. I2C adapter driver is the I2C controller driver of SOC. I2C device drivers need to be written by users according to different I2C devices, and I2C adapter drivers are generally written by SOC manufacturers. For example, NXP has written the I2C adapter driver of I.MX6U. Find the I2C1 controller node of I.MX6U in the imx6ull.dtsi file. The node contents are as follows:

1 i2c1: i2c@021a0000 {
2 #address-cells = <1>;
3 #size-cells = <0>;
4 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
5 reg = <0x021a0000 0x4000>;
6 interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
7 clocks = <&clks IMX6UL_CLK_I2C1>;
8 status = "disabled";
9 };

Focus on the compatible attribute value of i2c1 node, because the corresponding driver file can be found in the Linux source code through the compatible attribute value. Here, the i2c1 node has two compatible attribute values: "fsl,imx6ul-i2c" and "fsl,imx21"-
I2C ", search these two strings in the Linux source code to find the corresponding driver file. 1. The I2C adapter driver file of mx6u is drivers / I2C / busses / I2C IMX. C, which contains the following contents:

244 static struct platform_device_id imx_i2c_devtype[] = {
245 {
246 .name = "imx1-i2c",
247 .driver_data = (kernel_ulong_t)&imx1_i2c_hwdata,
248 }, {
249 .name = "imx21-i2c",
250 .driver_data = (kernel_ulong_t)&imx21_i2c_hwdata,
251 }, {
252 /* sentinel */
253 }
254 };
255 MODULE_DEVICE_TABLE(platform, imx_i2c_devtype);
257 static const struct of_device_id i2c_imx_dt_ids[] = {
258 { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
259 { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
260 { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
261 { /* sentinel */ }
262 };
263 MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);
1119 static struct platform_driver i2c_imx_driver = {
1120 .probe = i2c_imx_probe,
1121 .remove = i2c_imx_remove,
1122 .driver = {
1123 .name = DRIVER_NAME,
1124 .owner = THIS_MODULE,
1125 .of_match_table = i2c_imx_dt_ids,
1126 .pm = IMX_I2C_PM,
1127 },
1128 .id_table = imx_i2c_devtype,
1129 };
1131 static int __init i2c_adap_imx_init(void)
1132 {
1133 return platform_driver_register(&i2c_imx_driver);
1134 }
1135 subsys_initcall(i2c_adap_imx_init);
1137 static void __exit i2c_adap_imx_exit(void)
1138 {
1139 platform_driver_unregister(&i2c_imx_driver);
1140 }
1141 module_exit(i2c_adap_imx_exit);

As can be seen from the example code 61.2.2, the I2C adapter driver of I.MX6U is a standard platform driver. It can be seen that although the I2C bus provides a bus driver framework for other devices, the I2C adapter is a platform driver. Just like your department boss is your leader and you are his subordinate, but in the whole company, your department boss is also his subordinate.

Line 259, "fsl,imx21-i2c" attribute value. The compatible attribute value of i2c1 node in the device tree matches this. Therefore, the i2c-imx.c file is the I2C adapter driver file of I.MX6U.

Line 1120, after the device and driver are successfully matched I2C_ imx_ The probe function will execute, I2C_ imx_ The probe function will complete the initialization of the I2C adapter.
i2c_ imx_ The contents of probe function are as follows (omitted):

971 static int i2c_imx_probe(struct platform_device *pdev)
972 {
973 const struct of_device_id *of_id =
974 of_match_device(i2c_imx_dt_ids, &pdev->dev);
975 struct imx_i2c_struct *i2c_imx;
976 struct resource *res;
977 struct imxi2c_platform_data *pdata =
978 void __iomem *base;
979 int irq, ret;
980 dma_addr_t phy_addr;
982 dev_dbg(&pdev->dev, "<%s>\n", __func__);
984 irq = platform_get_irq(pdev, 0);
990 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
991 base = devm_ioremap_resource(&pdev->dev, res);
992 if (IS_ERR(base))
993 return PTR_ERR(base);
995 phy_addr = (dma_addr_t)res->start;
996 i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx),
997 if (!i2c_imx)
998 return -ENOMEM;
1000 if (of_id)
1001 i2c_imx->hwdata = of_id->data;
1002 else
1003 i2c_imx->hwdata = (struct imx_i2c_hwdata *)
1004 platform_get_device_id(pdev)->driver_data;
1006 /* Setup i2c_imx driver structure */
1007 strlcpy(i2c_imx->, pdev->name,
1008 i2c_imx->adapter.owner = THIS_MODULE;
1009 i2c_imx->adapter.algo = &i2c_imx_algo;
1010 i2c_imx-> = &pdev->dev;
1011 i2c_imx-> = pdev->id;
1012 i2c_imx-> = pdev->dev.of_node;
1013 i2c_imx->base = base;
1015 /* Get I2C clock */
1016 i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
1022 ret = clk_prepare_enable(i2c_imx->clk);
1027 /* Request IRQ */
1028 ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
1029 IRQF_NO_SUSPEND, pdev->name, i2c_imx);
1035 /* Init queue */
1036 init_waitqueue_head(&i2c_imx->queue);
1038 /* Set up adapter data */
1039 i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);
1041 /* Set up clock divider */
1042 i2c_imx->bitrate = IMX_I2C_BIT_RATE;
1043 ret = of_property_read_u32(pdev->dev.of_node,
1044 "clock-frequency", &i2c_imx->bitrate);
1045 if (ret < 0 && pdata && pdata->bitrate)
1046 i2c_imx->bitrate = pdata->bitrate;
1048 /* Set up chip registers to defaults */
1049 imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
1050 i2c_imx, IMX_I2C_I2CR);
1051 imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx,

Line 984, call platform_ get_ The IRQ function gets the interrupt number.
Lines 990 to 991, call platform_ get_ The resource function obtains the physical base address of the I2C1 controller register from the device tree, that is, 0X021A0000. After obtaining the register base address, use devm_ ioremap_ The resource function maps the memory to get the virtual address that can be used in the Linux kernel.
Line 996, NXP uses imx_i2c_struct structure to represent I2C controller of I.MX series SOC. Devm is used here_ Kzalloc function to request memory.
Lines 1008-1013, IMX_ i2c_ The struct structure should have a member variable called adapter, which is i2c_adapter, where I2C is initialized_ adapter. Line 1009 set I2C_ The algo member variable of adapter is i2c_imx_algo, that is, set i2c_algorithm.
Lines 1028 ~ 1029, register the I2C controller interrupt, and the interrupt service function is i2c_imx_isr.
Lines 1042 ~ 1044, set the I2C frequency to IMX by default_ I2C_ BIT_ Rate = 100kHz. If the "clock frequency" attribute is set in the device tree node, the I2C frequency will use the clock frequency attribute value.
Lines 1049 ~ 1051 set I2CR and I2SR registers controlled by I2C1.
Line 1054, call I2C_ add_ numbered_ The adapter function registers I2C with the Linux kernel_ adapter.
Line 1071, apply for DMA. It seems that the I2C adapter driver of I.MX adopts DMA mode.
i2c_ imx_ The probe function mainly works on the following two points:
① . initialize i2c_adapter, setting I2C_ The algorithm is i2c_imx_algo, and finally register I2C with the Linux kernel_ adapter.
② Initialize the relevant registers of I2C1 controller.
i2c_imx_algo contains the communication function master between I2C1 adapter and I2C device_ xfer,i2c_ imx_ The algo structure is defined as follows:

966 static struct i2c_algorithm i2c_imx_algo = {
967 .master_xfer = i2c_imx_xfer,
968 .functionality = i2c_imx_func,
969 };

Let's take a look at. Functionality. Functionality is used to return what communication protocol this I2C adapter supports. Here, functionality is i2c_imx_func function, i2c_imx_func function is as follows:

static u32 i2c_imx_func(struct i2c_adapter *adapter)

Let's focus on i2c_imx_xfer function, because this function is used to complete the communication with I2C device. The contents of this function are as follows (omitted):

888 static int i2c_imx_xfer(struct i2c_adapter *adapter,
889 struct i2c_msg *msgs, int num)
890 {
891 unsigned int i, temp;
892 int result;
893 bool is_lastmsg = false;
894 struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
896 dev_dbg(&i2c_imx->, "<%s>\n", __func__);
898 /* Start I2C transfer */
899 result = i2c_imx_start(i2c_imx);
900 if (result)
901 goto fail0;
903 /* read/write data */
904 for (i = 0; i < num; i++) {
905 if (i == num - 1)
906 is_lastmsg = true;
908 if (i) {
909 dev_dbg(&i2c_imx->,
910 "<%s> repeated start\n", __func__);
911 temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
912 temp |= I2CR_RSTA;
913 imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
914 result = i2c_imx_bus_busy(i2c_imx, 1);

Line 899, call I2C_ imx_ The start function turns on I2C communication.
In line 939, if the data is read from the I2C device, call i2c_imx_read function.
Lines 941 ~ 945, write data to I2C device. If DMA is to be used, use i2c_imx_dma_write function to write data. If you don't use DMA, use I2C_ imx_ The write function completes writing data.
The 952nd row, after I2C communication completes, calls i2c_. imx_ The stop function stops I2C communication.
i2c_imx_start,i2c_imx_read,i2c_imx_write and i2c_imx_stop these functions are the specific operation functions of the I2C register. The content of the functions is basically the same as that of the I2C driver in our bare metal article. We won't analyze them in detail here. You can analyze them by yourself according to the experiment in Chapter 26.

I2C device driver writing process

The SOC manufacturer of I2C adapter driver has written it for us. What we need to do is to write specific device drivers. In this section, we will learn the detailed writing process of I2C device drivers.

I2C device information description

1. When the device tree is not used
First, you must describe the I2C device node information. First, let's see how to describe the I2C device information in the BSP when the device tree is not used. When the device tree is not used, you need to use I2C in the BSP_ board_ Info structure to describe a specific I2C device. i2c_ board_ The info structure is as follows:

295 struct i2c_board_info {
296 char type[I2C_NAME_SIZE]; /* I2C Device name*/
297 unsigned short flags; /* sign*/
298 unsigned short addr; /* I2C Device address*/
299 void *platform_data;
300 struct dev_archdata *archdata;
301 struct device_node *of_node;
302 struct fwnode_handle *fwnode;
303 int irq;
304 };

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. Open arch / arm / Mach IMX / Mach mx27_ 3DS. C file, in which the I2C device information about OV2640 is described as follows:

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

I2C is used in example code BOARD_ Info to complete mx27_ 3ds_ i2c_ Initialization of camera, I2C_BOARD_INFO is a macro, which is defined as follows:
Example code I2C_BOARD_INFO macro

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

It can be seen that I2C_ BOARD_ Info macro is actually setting I2C_ board_ Info type and addr, so the main work of example code is to set the I2C device name to ov2640 and the device address of ov2640 to 0X30.
You can search I2C globally in the Linux source code_ board_ Info, you will find a lot to I2C_ board_ The I2C device information defined by info is the description method of I2C devices when the device tree is not used. When the device tree is used, I2C will not be used again_ board_ Info to describe the I2C device.

2. When using the device tree
When using the device tree, the I2C device information can be obtained by creating a corresponding node. For example, the official EVK development board of NXP connects the magnetometer chip mag3110 on i2c1. Therefore, a child node of mag3110 must be created under the i2c1 node, and then the relevant information of the chip mag3110 must be described in this child node. Open imx6ull-14x14-evk.dts, the device tree file, and find the following contents:

1 &i2c1 {
2 clock-frequency = <100000>;
3 pinctrl-names = "default";
4 pinctrl-0 = <&pinctrl_i2c1>;
5 status = "okay";
7 mag3110@0e {
8 compatible = "fsl,mag3110";
9 reg = <0x0e>;
10 position = <2>;
11 };
20 };

Lines 7-11, add mag3110 child node to i2c1, line 7“ mag3110@0e ”Is the child node name, and "0e" after "@" is the I2C device address of mag3110. In line 8, set the compatible attribute value to "fsl,mag3110".
The reg attribute in line 9 also sets the device address of mag3110, so the value is 0x0e. The creation of I2C device node focuses on the setting of compatible attribute and reg attribute, one for matching driver and one for setting device address.

I2C equipment data transceiver processing flow

As mentioned in section 61.1.2, the first thing for I2C device driver is to initialize i2c_driver and register with the Linux kernel. When the device and driver match I2C_ The probe function in the driver will execute. What the probe function does is the character device driver. Generally, the I2C device needs to be initialized in the probe function. To initialize the I2C device, you must be able to read and write the I2C device register. I2C is used here_ The transfer function. i2c_transfer function
Eventually, I2C in the I2C adapter will be called_ Master in algorithm_ The xfer function is I2C for I.MX6U_ imx_ Xfer this function. i2c_ The prototype of transfer function is as follows:

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

Function parameters and return values have the following meanings:
adap: I2C adapter used, I2C_ The client saves 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.
Return value: negative value, failure, other non negative values, number of msgs sent.

Let's focus on the parameter msgs, which is an i2c_msg type pointer parameter. I2C sends and receives data. In short, it is message transmission. The Linux kernel uses i2c_msg structure to describe a message. i2c_ The MSG structure is defined in the include/uapi/linux/i2c.h file. The structure is as follows:

68 struct i2c_msg {
69 __u16 addr; /* Slave address*/
70 __u16 flags; /* sign*/
71 #define I2C_M_TEN 0x0010
72 #define I2C_M_RD 0x0001
73 #define I2C_M_STOP 0x8000
74 #define I2C_M_NOSTART 0x4000
75 #define I2C_M_REV_DIR_ADDR 0x2000
76 #define I2C_M_IGNORE_NAK 0x1000
77 #define I2C_M_NO_RD_ACK 0x0800
78 #define I2C_M_RECV_LEN 0x0400
79 __u16 len; /* Message (msg) length*/
80 __u8 *buf; /* Message data*/
81 };

Using I2C_ The transfer function should construct I2C before sending data_ MSG, using I2C_ The example code of I2C data transmission and reception by transfer is as follows:

1 /* Equipment structure*/
2 struct xxx_dev {
3 ......
4 void *private_data; /* Private data, usually set to i2c_client */
5 };
7 /*
8 * @description : Read multiple register data of I2C device
9 * @param – dev : I2C equipment
10 * @param – reg : First register address to read
11 * @param – val : Read data
12 * @param – len : Length of data to read
13 * @return : Operation results
14 */
15 static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,
int len)
16 {
17 int ret;
18 struct i2c_msg msg[2];
19 struct i2c_client *client = (struct i2c_client *)
21 /* msg[0],The first write message sends the first address of the register to be read*/
22 msg[0].addr = client->addr; /* I2C Device address*/
23 msg[0].flags = 0; /* Mark as send data*/
24 msg[0].buf = &reg; /* First address read*/
25 msg[0].len = 1; /* reg length*/
27 /* msg[1],The second read message reads the register data*/
28 msg[1].addr = client->addr; /* I2C Device address*/
29 msg[1].flags = I2C_M_RD; /* Mark as read data*/
30 msg[1].buf = val; /* Read data buffer*/
31 msg[1].len = len; /* Length of data to read*/
33 ret = i2c_transfer(client->adapter, msg, 2);
34 if(ret == 2) {
35 ret = 0;
36 } else {
37 ret = -EREMOTEIO;
38 }
39 return ret;
40 }
42 /*
43 * @description : Write data to multiple registers of I2C device
44 * @param – dev : Device structure to write
45 * @param – reg : First address of register to be written
46 * @param – buf : Data buffer to write to
47 * @param – len : Length of data to write
48 * @return : Operation results
49 */
50 static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,
u8 len)
51 {
52 u8 b[256];
53 struct i2c_msg msg;
54 struct i2c_client *client = (struct i2c_client *)
56 b[0] = reg; /* Register header address*/
57 memcpy(&b[1],buf,len); /* Copy the data to be sent to array b*/
59 msg.addr = client->addr; /* I2C Device address*/
60 msg.flags = 0; /* Mark as write data*/
62 msg.buf = b; /* Data buffer to send*/
63 msg.len = len + 1; /* Length of data to send*/
65 return i2c_transfer(client->adapter, &msg, 1);
66 }

Lines 2 to 5, the device structure, add a pointer member variable private that executes void to the device structure_ Data, this member variable is used to save the private data of the device. In the I2C device driver, we generally point it to the I2C corresponding to the I2C device_ client.

Lines 15-40, XXX_ read_ The regs function is used to read multiple register data of I2C device. Line 18 defines an i2c_msg array, two array elements. When I2C reads data, it must first send the register address to be read, and then read the data. Therefore, it is necessary to prepare two i2c_msg. One is used to send the register address and the other is used to read the register value. For msg[0], set flags to 0 to write data. addr of msg[0] is the device address of I2C device and buf of msg[0]
The member variable is the register address to be read. For msg[1], set flags to I2C_M_RD, read data.
The buf member variable of msg[1] is used to save the read data, and the len member variable is the length of the data to be read. call
i2c_ The transfer function completes the I2C data reading operation.

Lines 50-66, XXX_ write_ The regs function is used to write data to multiple registers of the I2C device. The I2C write operation is a little simpler than the read operation, so an i2c_msg is enough. Array b is used to store the first address of the register and the data to be sent. In line 59, set the addr of msg as the I2C device address. In line 60, set the flags of msg to 0, that is, write data. Line 62 sets the data to be sent, that is, array b. In line 63, set len of msg to len+1, because a register address of one byte is added. Finally, through I2C_ The transfer function completes the write operation to the I2C device.

In addition, there are two API functions respectively used for I2C data sending and receiving operations. These two functions will eventually call i2c_transfer. Let's first look at the I2C data sending function I2C_ master_ The prototype of send function is as follows:

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

Function parameters and return values have the following meanings:
Client: I2C corresponding to I2C device_ client.
buf: data to be sent.
count: the number of data bytes to be sent. It should be less than 64KB, which is I2C_ The len member variable of MSG is a U16 (unsigned 16 bit) type of data.
Return value: negative value, failure, other non negative values, number of bytes sent.
The I2C data receiving function is i2c_master_recv, the function prototype is as follows:

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

Function parameters and return values have the following meanings:
Client: I2C corresponding to I2C device_ client.
buf: data to be received.
count: the number of data bytes to be received. It should be less than 64KB, which is I2C_ The len member variable of MSG is a U16 (unsigned 16 bit) type of data.
Return value: negative value, failure, other non negative values, number of bytes sent.
That's all for the writing process of I2C device driver under Linux, with the focus on I2C_ Construction and I2C implementation of MSG_ Call the transfer function. Next, we will write the linux driver of AP3216C, an I2C device.

Hardware schematic analysis

Refer to section 26.2 for the schematic diagram of experimental hardware in this chapter.

Experimental programming

The routine path corresponding to this experiment is: development board CD - > 2, Linux driver routine - > 21_ iic.

Modify device tree

1. IO modify or add

First of all, the IO must be modified. AP3216C uses the I2C1 interface, and the I2C1 interface on the I.MX6U-ALPHA development board uses UART4_TXD and UART4_RXD, so these two IOS must be set in the device tree. If you want to use the interrupt function of AP3216C, you also need to initialize the AP_ Gio1 corresponding to int_ Io01 for this IO, we do not use the interrupt function in this chapter. Therefore, only uart4 needs to be set_ TXD and uart4_ NXP has actually set the two Io's of RXD. Open imx6ull-alientek-emmc.dts and find the following:

1 pinctrl_i2c1: i2c1grp {
2 fsl,pins = <
3 MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
4 MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
5 >;
6 };

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

2. Add ap3216c child node to i2c1 node
Ap3216c is connected to i2c1, so you need to add the device child node of ap3216c under i2c1 node. Find i2c1 node in imx6ull-alientek-emmc.dts file. The default content of this node is as follows:

1 &i2c1 {
2 clock-frequency = <100000>;
3 pinctrl-names = "default";
4 pinctrl-0 = <&pinctrl_i2c1>;
5 status = "okay";
7 mag3110@0e {
8 compatible = "fsl,mag3110";
9 reg = <0x0e>;
10 position = <2>;
11 };
13 fxls8471@1e {
14 compatible = "fsl,fxls8471";
15 reg = <0x1e>;
16 position = <0>;
17 interrupt-parent = <&gpio5>;
18 interrupts = <0 8>;
19 };
20 };

In line 2, the clock frequency attribute is I2C frequency, which is set to 100KHz here.
In line 4, the pinctrl-0 attribute specifies that the IO used by I2C is pinctrl in example code I2c1 child node.

In lines 7 to 11, mag3110 is a magnetometer. The official EVK development board of NXP is connected with mag3110, so NXP adds the child node of mag3110 under the i2c1 node. Mag3110 is not used on the I.MX6U-ALPHA development board of punctual atom, so this node needs to be deleted.

In lines 13-19, the NXP official EVK development board is also connected with a fxls8471. The I.MX6U-ALPHA development board of punctual atom also does not have this device, so it should also be deleted.

Delete the original two I2C child nodes mag3110 and fxls8471 in the i2c1 node, and then add the ap3216c child node information. The content of the i2c1 node after completion is as follows:

1 &i2c1 {
2 clock-frequency = <100000>;
3 pinctrl-names = "default";
4 pinctrl-0 = <&pinctrl_i2c1>;
5 status = "okay";
7 ap3216c@1e {
8 compatible = "alientek,ap3216c";
9 reg = <0x1e>;
10 };
11 };

Line 7, ap3216c child node, "1e" after @ is the device address of ap3216c.
In line 8, set the compatible value to "alientek,ap3216c".
In line 9, the reg attribute also sets the ap3216c device address, so reg is set to 0x1e.

After modifying the device tree, recompile it with "make dtbs", and then start the Linux kernel with the new device tree/ All I2C devices are stored in the sys/bus/i2c/devices directory. If the device tree is modified correctly, a subdirectory named "0-001e" will be seen in the / sys/bus/i2c/devices directory, as shown in figure

"0-001e" in figure is the device directory of ap3216c, and "1E" is the device address of ap3216c. Enter the 0-001e directory and you can see the "name" file. The name of the device is saved when asking for price, which is "ap3216c", as shown in figure

AP3216C driver writing

Create a new folder named "21_iic", and then_ Create a vscode project in the IIC folder, and the workspace is named "IIC". After the project is created, create two files ap3216c.c and ap3216creg.h. ap3216c.c is the driver code of AP3216C and ap3216creg.h is the register header file of AP3216C. First define the register of AP3216C in ap3216creg.h, and enter the following contents,

1 #ifndef AP3216C_H
2 #define AP3216C_H
3 /***************************************************************
4 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
5 File name: ap3216creg.h
6 Author: Zuo Zhongkai
7 Version: V1.0
8 Description: AP3216C register address description header file
9 Others: None
10 Forum:
11 Log: first version v1.0 created by Zuo Zhongkai on September 2, 2019
12 ***************************************************************/
13 /* AP3316C register*/
14 #define AP3216C_SYSTEMCONG 0x00 / * configuration register*/
15 #define AP3216C_INTSTATUS 0X01 / * interrupt status register*/
16 #define AP3216C_INTCLEAR 0X02 / * interrupt clear register*/
17 #define AP3216C_IRDATALOW 0x0A /* IR data low byte*/
18 #define AP3216C_ Irdataweight 0x0b / * IR data high byte*/
19 #define AP3216C_ALSDATALOW 0x0C /* ALS data low byte*/
20 #define AP3216C_ Alsdataheight 0x0D / * ALS data high byte*/
21 #define AP3216C_PSDATALOW 0X0E /* PS data low byte*/
22 #define AP3216C_ Psdataweight 0x0f / * PS data high byte*/
24 #endif

ap3216creg.h has nothing to say, just some register macro definitions. Then enter the following in ap3216c.c:

1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of_gpio.h>
12 #include <linux/semaphore.h>
13 #include <linux/timer.h>
14 #include <linux/i2c.h>
15 #include <asm/mach/map.h>
16 #include <asm/uaccess.h>
17 #include <asm/io.h>

Lines 32-41, ap3216c equipment structure, line 39, private_ The data member variable is used to store the I2C corresponding to ap3216c_ client. IR, ALS and PS in line 40 store IR, ALS and PS data of ap3216c respectively.
Line 43, define an ap3216c_ Device structure variable ap3216cdev of type dev.
Lines 53-79, ap3216c_read_regs function realizes multi byte reading, but AP3216C does not seem to support continuous multi byte reading. This function can realize continuous reading of multiple bytes when testing other I2C devices, but multiple bytes cannot be read continuously on AP3216C. But there is no problem reading a byte.
Lines 89-105, ap3216c_ write_ The regs function implements continuous multi byte write operations.

Lines 113-124, AP3216C_ read_ The reg function is used to read the data of the specified register of AP3216C and to read the data of a register.
Lines 133-138, AP3216C_ write_ The reg function is used to write data to the specified register of AP3216C and to write data to a register.
Lines 148 ~ 170, read the original data values of PS, ALS, IR and other sensors of AP3216C.
Lines 179-230, standard character device driver frame.
Lines 239-269, ap3216c_probe function. This function will be executed after the I2C device and driver are successfully matched, just like the platform driver framework. This function is preceded by the standard character device registration code, and the first parameter client of this function will be passed to ap3216cdev's private_data member variable.
Lines 289-292, ap3216c_id matching table, i2c_device_id type. It is used for traditional device and driver matching, that is, when the device tree is not used.
Lines 295-298, ap3216c_of_match table, of_device_id type, used for device tree device and driver matching. Only one compatible attribute is written here, and the value is "alientek,ap3216c".
Lines 301-310, ap3216c_driver struct variable, i2c_driver type.
Lines 317-323, drive the entry function ap3216c_init, this function calls i2c_add_driver to register I2C with the Linux kernel_ Driver, i.e. ap3216c_driver.
Lines 330 to 333, drive the exit function ap3216c_exit, this function calls i2c_del_driver to unregister the previously registered ap3216c_driver.

Write test APP

Create a new ap3216cApp.c file, and then enter the following contents in it:

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "sys/ioctl.h"
6 #include "fcntl.h"
7 #include "stdlib.h"
8 #include "string.h"
9 #include <poll.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 /***************************************************************
15 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
16 file name: ap3216cApp.c
17 author: Zuo Zhongkai
18 edition: V1.0
19 describe: ap3216c Equipment testing APP. 
20 other: nothing
21 usage method:./ap3216cApp /dev/ap3216c

The content of ap3216cApp.c file is very simple. It is to continuously read the AP3216C device file in the while loop to obtain the three data values of ir, als and ps, and then output them to the terminal.

Run test

Compile driver and test APP

1. Compile driver
Write the Makefile file. The Makefile file of the experiment in this chapter is basically the same as the experiment in Chapter 40, except that the value of obj-m variable is changed to "ap3216c.o". The contents of the Makefile are as follows:

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
4 obj-m := ap3216c.o
11 clean:

In line 4, set the value of obj-m variable to "ap3216c.o".
Input the following command to compile the driver module file:

make -j32

After successful compilation, a driver module file named "ap3216c.ko" will be generated.

2. Compile test APP
Enter the following command to compile ap3216cApp.c this test program:

arm-linux-gnueabihf-gcc ap3216cApp.c -o ap3216cApp

After successful compilation, ap3216cApp will be generated.

Run test

Copy the ap3216c.ko and ap3216cApp files compiled from the previous section to the rootfs/lib/modules/4.1.15 directory, restart the development board and enter the directory lib/modules/4.1.15. Enter the following command to load ap3216c.ko this driver module.

depmod //This command needs to be run when the driver is loaded for the first time
modprobe ap3216c.ko //Load driver module

When the driver module is loaded successfully, use ap3216cApp to test, and enter the following command:

./ap3216cApp /dev/ap3216c

The test APP will continuously read data from AP3216C and then output it to the terminal, as shown in figure

You can shine a flashlight on the AP3216C, or your fingers close to the AP3216C to observe whether the sensor data has changed.

Tags: Linux Operation & Maintenance

Posted on Thu, 11 Nov 2021 16:52:03 -0500 by beesgirl713