Cover image for Linux I2C

Linux I2C


Timeline

Timeline

2025-12-28

init

This article introduces the basics of the Linux I2C bus, discussing in detail its master-slave architecture, communication protocol, addressing mechanism, and multi-master support. Combining with the RK3568 platform, it summarizes the implementation differences between hardware I2C and software I2C, and explains the key role of pull-up resistors in ensuring the bus idle level and implementing the wired-AND function.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Competition
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. gpio Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

Introduction to I2C

The famous Dutch electronics company Philips invented an integrated circuit interconnection communication protocol called I2C (Inter-Integrated Circuit).

I2C
I2C

Inthe idle stateSDA and SCL are generally pulled high by pull-up resistors, maintaining a high level state, when data transmission is needed, the required signals for the I2C bus are generated through the high and low levels of SCL and SDA to perform data transfer.

Features

  • Bus Topology

The I2C bus adopts a master-slave architecture, consisting of one master device and one or more slave devices. The master device is responsible for initiating data transmission, while the slave devices respond to the master’s requests.

  • Physical Layer Interface
    The I2C bus uses two lines for communication:
    • **SCL(Serial Clock Line)**Clock line (SCL), which provides the clock signal from the master device.

    • SDA(Serial Data LineData line (SDA), used for bidirectional data transmission.

These two lines typically require pull-up resistors to maintain the signal at a high level.

  • Communication Protocol

I2C usessynchronous serial communicationmethod, where the master initiates communication and provides the clock. The master first sends a “start” signal, then sends the slave device address and data transfer direction (read or write). After receiving its own address, the slave sends an acknowledgment signal, indicating it is ready to receive or send data. Then the master and slave can begin data transmission. At the end of communication, the master sends a “stop” signal.

  • Clock Frequency
    The I2C bus supports multiple communication rates, common ones include:

    • Standard mode: 100 kbps

    • Fast mode: 400 kbps

    • High-speed mode: 3.4 Mbps

  • Addressing Mechanism
    I2C uses a 7-bit address space, capable of addressing up to 127 slave devices. The first 7 bits of the address space are used to specify the slave device, and the last bit is used to indicate the read/write direction.
    Each I2C peripheral corresponds to a unique address (this address can be obtained from the I2C peripheral device’s datasheet). Communication between the master and slave is determined by this address to identify which slave the master intends to communicate with.

  • Multi-Master Support
    The I2C bus supports multiple master devices sharing the same bus,avoiding conflicts through an arbitration mechanism. When multiple master devices attempt to occupy the bus simultaneously,the master device with the highest priority will gain control of the bus

  • Other Features:

    • The maximum bus capacitance is limited to 400pF
    • Data is transmitted in units of bytes
    • There are two implementation methods: hardware I2C and software I2C.

I2C on RK3568

rk3568 I2C
rk3568 I2C

  • Supports 6 I2C interfaces, namely I2C0, I2C1, I2C2, I2C3, I2C4, I2C5
  • Supports 7-bit and 10-bit address modes
  • Software programmable clock frequency
  • Data transfer rate on the I2C bus can reach
    • Standard mode up to 100Kbit/s
    • Fast mode up to 400Kbit/s
    • Fast mode plus up to 1Mbit/s

Hardware I2C and software I2C

The 6 I2C interfaces here refer toHardware I2CThere is a dedicated hardware I2C circuit on the SOC, the introduction to hardware I2C is as follows

Hardware I2C

Hardware I2CThere is a dedicated hardware I2C circuit on the SOC

  • Implementation method: The I2C bus protocol is implemented through a dedicated hardware I2C interface circuit.
  • Advantages: Low CPU usage, the I2C bus is automatically completed by hardware circuits. High transmission rate, up to 400kbit/s or 3.4Mbit/s. More reliable and stable, less susceptible to external interference.
  • Disadvantages: Requires dedicated hardware I2C interface circuit support, relatively high cost. The interface is fixed, not as flexible as software I2C.
  • Scope of Application: Suitable for high-speed, large data transmission scenarios, such as connecting peripherals like LCD, EEPROM, etc.

Software I2C

refers tosimulating the SCL and SDA signal lines through GPIO pins, when hardware I2C is insufficient, software I2C can be simulated through GPIO. The introduction to software I2C is as follows:

  • Implementation Method: Simulates the I2C bus protocol through software, using general-purpose I/O pins to simulate the SCL and SDA signal lines.
  • Advantages: High flexibility, can implement I2C interface on any I/O pin. Low cost, no additional hardware support required.
  • Disadvantages: High CPU usage because I2C timing needs to be simulated in software. The transmission rate is low, limited by CPU performance, typically around 100kbit/s.
  • Scope of Application: Suitable for low-speed, small data transmission scenarios.

I2C Pull-up Resistors

Pull-up Resistors
Pull-up Resistors

In I2C, a pull-up resistor is required on both the SDA data line and the SCL clock line.

Function of Pull-up Resistors

  • Ensure the bus remains at a high level when idle.

The I2C bus uses open-drain/open-collector output. When no device is driving the bus, the bus is in a high-impedance state.. Without pull-up resistors, the bus level is undefined and susceptible to noise interference. Pull-up resistors canensure a stable high level when the bus is idle.

  • Implement the wired-AND function.

The I2C bus allows multiple devices to be connected to the same bus.When one device pulls the bus low, the outputs of other devices are also pulled low. This is the wired-AND function, enabling bus arbitration.. Without pull-up resistors, the level is undefined when the bus is idle, and when one device pulls the bus low, other devices cannot detect the level change, making the wired-AND function impossible.

The pull-up resistor here cannot be arbitrarily chosen; it must considerthe bus capacitance. Various parasitic capacitances exist on the I2C bus, which can be equivalent to an RC charging circuit, as shown in the figure below

Equivalent RC charging circuit
Equivalent RC charging circuit

When the bus transitions from low to high level, the pull-up resistor supplies power to the bus, charging the bus capacitance.

  • If the pull-up resistor value is too large, the charging time becomes too long, causing a slow rising edge that may affect communication;

  • If the pull-up resistor value is too small, a low level cannot be generated

Therefore, the I2C specification requires that the bus capacitance not exceed 400pF. It is generally recommended to choose between 1kΩ and 10kΩ to ensure both rising edge speed and reliable pulling down of the bus level

I2C pull-up resistor value calculation

Minimum value

Formula:

Rp(min)=VDDVOL(max)IOR_{\text{p(min)}} = \frac{V_{\text{DD}} - V_{\text{OL(max)}}}{I_{\text{O}}}

  • VDD V_{\text{DD} } is typically a common supply voltage such as 5V or 3.3V; on the iTOP-RK3568 development board, it is 3.3V
  • VOL(max)V_{\text{OL(max)}}represents the maximum output voltage of the device at low level. The specific values are shown in the table below. Since VDD is 3.3V, the maximum value of VOL is 0.4

Maximum output voltage of the device at low level
Maximum output voltage of the device at low level

  • IOI_{\text{O}}The maximum sink current of the device at low level, with specific values shown in the table below, throughVOLV_{\text{OL}}value of 0.4, we can obtainIOI_{\text{O}}The value in standard mode and fast mode is 3 mA.

Maximum sink current of the device at low level
Maximum sink current of the device at low level

Calculate

  1. DetermineVDDV_{\text{DD}}VOL(max)V_{\text{OL(max)}}andIOLI_{\text{OL}}values: In RK3568,VDD=3.3VV_{\text{DD}} = 3.3\,\text{V}, takeVOL(max)=0.4VV_{\text{OL(max)}} = 0.4\,\text{V}, the correspondingIOL=3mAI_{\text{OL}} = 3\,\text{mA}
  2. Substitute into the formula: Rp(min)=VDDVOL(max)IOL R_{\text{p(min)}} = \frac{V_{\text{DD}} - V_{\text{OL(max)}}}{I_{\text{OL}}} to calculate the minimum pull-up resistor value: Rp(min)=3.3V0.4V3mA=2.9V0.003A966.7Ω R_{\text{p(min)}} = \frac{3.3\,\text{V} - 0.4\,\text{V}}{3\,\text{mA}} = \frac{2.9\,\text{V}}{0.003\,\text{A}} \approx 966.7\,\Omega Usually, standard resistor values can be taken as910 Ωor1 kΩ(if slightly below the theoretical minimum is allowed, it needs to be combined with actual bus capacitance and speed requirements).

Maximum value

Formula:

Rp(max)=tr0.8473CbR_{\text{p(max)}} = \frac{t_{\text{r}}}{0.8473 \cdot C_{\text{b}}}

  • Rp(max)R_{\text{p(max)}}: Maximum value of pull-up resistor (unit: Ω)
  • CbC_{\text{b}}: Bus capacitance (unit: F), including the sum of PCB trace capacitance, pin capacitance, and device input capacitance
  • trt_{\text{r}}: High-level rise time (unit: s), typically measured from 0.3VDD to 0.7VDD, details can be obtained from the datasheet

High-level rise time
High-level rise time

From the above figure, it can be obtained

  • In standard modetrt_{\text{r}}the value is ≤ 1000ns.
  • In fast modetrt_{\text{r}}the value is ≤ 300ns.
  • In ultra-fast modetrt_{\text{r}}the value is ≤ 120ns

Calculation

I²C operates in standard mode, the pull-up voltage is3.3V3.3\,\text{V}, the pin capacitance is10pF10\,\text{pF}, the connection capacitance is30pF30\,\text{pF}, the high-level rise timetr=1000nst_{\text{r}} = 1000\,\text{ns}. Calculate the maximum value of the pull-up resistor.

  1. Calculate the bus capacitanceCb=10pF+30pF=40pF=40×1012F C_{\text{b}} = 10\,\text{pF} + 30\,\text{pF} = 40\,\text{pF} = 40 \times 10^{-12}\,\text{F}
  2. Substitute into the formulaRp(max)=tr0.8473Cb R_{\text{p(max)}} = \frac{t_{\text{r}}}{0.8473 \cdot C_{\text{b}}}
  3. Substitute values for calculationRp(max)=1000×109s0.8473×40×1012F=10633.892×1012Ω29.51kΩ R_{\text{p(max)}} = \frac{1000 \times 10^{-9}\,\text{s}}{0.8473 \times 40 \times 10^{-12}\,\text{F}} = \frac{10^{-6}}{33.892 \times 10^{-12}}\,\Omega \approx 29.51\,\text{k}\Omega

Therefore, the maximum value of the pull-up resistor is approximately29.51 kΩ

Specific selection

Generally, the faster the I2C bus speed, the smaller the required pull-up resistor value. The specific selection is as follows:

  • 100kbps: Typically choose a 10k pull-up resistor

  • 400kbps: Typically choose a 4.7k pull-up resistor

  • 1Mbps: Typically choose a 2.2k pull-up resistor

Of course, the above selection may not be correct and needs to be adjusted based on actual test results. In practice, you can first choose a pull-up resistor value based on experience for trial use, without being overly fixated on the calculation formula.

I2C communication timing

Start signal and stop signal

All interactions areinitiated by the START (S) signal, andterminated by the STOP (P) signal. The specific communication timing diagrams for the start and stop signals are as follows:

Start and Stop Timing
Start and Stop Timing

  • Start Signal (START): Generated by the bus controller (i.e., the master), it is defined asa transition of the SDA line from high to low while the SCL line remains high
  • Stop Signal (STOP): Generated by the bus controller, it is defined asa transition of the SDA line from low to high while the SCL line remains high

After the START signal, the bus is considered busy until the STOP signal appears, after which the bus is considered idle

Data Format

  1. Each byte transmitted on the SDA line must be 8 bits long. Each transmission can contain any number of bytes.
  2. Each byte must be followed by an acknowledge bit.
  3. Data is transmitted withthe most significant bit (MSB) first.
  4. If the target device cannot immediately receive or send another complete byte of data due to operations such as handling internal interrupts, it**can put the controller into a wait state by pulling the SCL line low.**When the target device is ready to receive the next byte of data,releasing the SCL line allows data transmission to continue.

I2C Data Format
I2C Data Format

Acknowledge and Not Acknowledge Signals

  • The acknowledge signal occurs after each byte transmission. The acknowledge bit allows the receiving device to indicate to the transmitting device that the byte has been successfully received and the next byte can be sent.
    • Acknowledge Signal: Whenthe transmitting device releases the SDA line during the 9th clock pulse,, The receiving device can pull the SDA line low and keep it stable low during this clock high level.
    • Non-acknowledge signalDuring the 9th clock pulse, the SDA line remains high., the controller can generate a stop signal to terminate the transmission, or a repeated start signal to begin a new transmission.

Acknowledge signal and non-acknowledge signal
Acknowledge signal and non-acknowledge signal

The five situations that cause a NACK signal include:

  • No receiving device on the bus responds to the transmitted address.
  • The receiving device is busy with other real-time functions and cannot start communication.
  • The receiving device receives data or commands it cannot understand during the transmission.
  • The receiving device cannot receive any more data bytes.
  • The controller-receiver must indicate the end of transmission to the target transmitter.

Read/write direction

  1. Data transfer format: first,send a 7-bit target address,, followed by a read/write direction bit (R/W bit).

  2. The read/write direction bit is bit 8,0 indicates a write operation (WRITE)1 indicates a read operation (READ)

Data transmission is alwaysterminated by a stop condition (P) generated by the controller. However,if the controller needs to continue communicating on the bus, it can generate a repeated start condition (Sr) to address other target devices without first generating a stop condition

This allows various combinations of read/write formats to be implemented within the same transmission session

Read/Write
Read/Write

I2C Waveform

Write Operation

Write Operation
Write Operation

Before data transmission, the master must firstsend a start condition, which is a transition of the SDA line from high to low while the SCL line remains high, corresponding to the part shown in the figure; the logic analyzer software also marks it with a green dot.

The write operation can be divided into the following steps:

  1. The master sends a start signal
  2. The master sends the I2C peripheral address and write operation, waiting for an acknowledgment signal
  3. The slave sends an acknowledgment signal
  4. The master sends the register address, waiting for an acknowledgment signal
  5. The slave sends an acknowledgment signal
  6. The master sends the data to be written to the register, waiting for an acknowledgment signal
  7. The slave sends an acknowledgment signal
  8. The master sends a stop signal; if writing multiple registers, repeat steps 6 and 7

Read operation

Whether it is a read or write operation, the I2C peripheral address must be written first, so the initial waveform is the same

Read operation
Read operation

The read operation can be divided into the following steps:

  1. The master sends a start signal
  2. The master sends the I2C peripheral address and write operation, waiting for an acknowledgment signal
  3. Slave sends an acknowledgment signal
  4. Master sends the address of the register to be read and waits for an acknowledgment signal
  5. Slave sends an acknowledgment signal
  6. Master sends a start signal
  7. Master sends the address of the I2C peripheral to be read and a read operation, waiting for an acknowledgment signal
  8. Slave sends an acknowledgment signal
  9. Slave sends data, i.e., the data of the register to be read, waiting for an acknowledgment or non-acknowledgment signal
  10. If the master is not reading data, it sends a non-acknowledgment signal; if it continues reading, it sends an acknowledgment signal

I2C subsystem framework

Hierarchical structure

I2C subsystem framework
I2C subsystem framework

I2C device driver layer

The main role of the I2C device driver layer is to write drivers,so that the I2C peripheral can work properly, and thenCreated the corresponding device node, providing a standardized interface that allows upper-layer applications to easily interact with I2C devices.

Specifically, the I2C device driver layer includes the following key parts:

  • i2c_client
    • Represents a slave device connected to the I2C bus
    • Contains information such as the slave device’s address and its associated I2C adapter
  • /dev/i2XDevice Node
    • Provides an interface for upper-layer applications to access the device
    • By opening, reading/writing, and controlling the device node, applications can interact with I2C devices
    • The kernel I2C subsystem is responsible for forwarding application operations to the corresponding i2c_driver
  • i2c_driver
    • Implements the driver for a specific I2C slave device
    • Responsible for device initialization, reading/writing, configuration, and other operations
    • Interacts with the device via i2c_client
    • Provides a standardized interface for device access to the upper layer
  • I2C Bus Subsystem
    • Manages the entire I2C bus, including registering/unregistering I2C adapters and slave devices
    • Coordinate i2c_client and i2c_interaction between drivers
    • Provide a unified I2C access interface for the upper layer

I2C core layer

The I2C core layer is located between the I2C device driver layer and the I2C adapter driver layer, serving as a bridge that connects the two, responsible for data transfer between the I2C device driver layer and the I2C adapter driver layer. The main functions of the I2C core layer are

  • i2c_master_send
  • i2c_master_recv
  • i2c_transfer

i2c_master_sendandi2c_master_recvThese two functions are responsible forgenerating timing and data frames that comply with the I2C protocol, and perform actual bus operations through the corresponding I2C adapter driver.

Among them,i2c_master_sendandi2c_master_recvfunctions are the basic read and write interfaces provided by the I2C core layer.

i2c_master_sendis used to send data to the I2C slave device,i2c_master_recvis used to receive data from the slave device.

They respectively accept the following parameters:

  • struct i2c_client *client: Pointer to the target I2C slave device
  • const char *buf/char *buf: Data buffer
  • int count: Number of bytes to send/receive

Andi2c_transferfunction is a more comprehensive I2C transfer function,i2c_master_sendandi2c_master_recvfunctions actually call thei2c_transfer

i2c_transfer function, which accepts the following parameters:

  • struct i2c_adapter *adap: Pointer to the target I2C adapter
  • struct i2c_msg *msgs: Pointer to an array of I2C messages
  • int num: Number of messages in the message array

Since the corresponding device node has been created in the I2C device driver layer, with the driver, one can directly operate the specific I2C hardware. However, the I2C subsystem is not implemented this way; instead, it adds the I2C core layer and the I2C adapter driver layer. So why is it designed this way?

The main reason is that through driver layering, it cansolve the problem of multiple applications simultaneously accessing the same I2C device causing conflicts, in addition, through thismodular design, the reusability and maintainability of the code can be improved, allowing the I2C core layer and device drivers to be developed and upgraded independently, and the I2C adapter driver can also be optimized for different hardware platforms.

I2C adapter driver layer

The I2C adapter driver layer is another important component of the I2C subsystem, responsible forimplementing the driver for specific I2C hardware controllers. The functions of the I2C adapter driver are as follows:

  • Provide a standardized I2C transmission interface for the I2C core layer to call
  • Implement timing control and data transmission/reception of the I2C bus protocol
  • Manage slave devices on the I2C bus
  • Handle I2C bus errors and abnormal situations

I2C client code writing

struct i2c_client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* struct i2c_client - represent an I2C slave device
* @flags: see I2C_CLIENT_* for possible flags
* @addr: Address used on the I2C bus connected to the parent adapter.
* @name: Indicates the type of the device, usually a chip name that's
* generic enough to hide second-sourcing and compatible revisions.
* @adapter: manages the bus segment hosting this I2C device
* @dev: Driver model device node for the slave.
* @init_irq: IRQ that was set at initialization
* @irq: indicates the IRQ generated by this device (if any)
* @detected: member of an i2c_driver.clients list or i2c-core's
* userspace_devices list
* @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter
* calls it to pass on slave events to the slave driver.
*
* An i2c_client identifies a single device (i.e. chip) connected to an
* i2c bus. The behaviour exposed to Linux is defined by the driver
* managing the device.
*/
struct i2c_client {
unsigned short flags; /* div., see below */
#define I2C_CLIENT_PEC 0x04 /* Use Packet Error Checking */
#define I2C_CLIENT_TEN 0x10 /* we have a ten bit chip address */
/* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE 0x20 /* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY 0x40 /* We want to use I2C host notify */
#define I2C_CLIENT_WAKE 0x80 /* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB 0x9000 /* Use Omnivision SCCB protocol */
/* Must match I2C_M_STOP|IGNORE_NAK */

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 init_irq; /* irq set at initialization */
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
};

Device tree representation of I2C Client

Inrk3568.dtsithe device tree, there are device tree nodes for I2C0, I2C1, I2C2, I2C3, I2C4, and I2C5. Here, only the device tree node for I2C1 is listed, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
i2c1: i2c@fe5a0000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xfe5a0000 0x0 0x1000>;
clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c1_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};

i2c1: i2c@fe5a0000The node represents the I2C1 controller. If an I2C peripheral is mounted on I2C1, you can directly add a child node for the I2C peripheral under the I2C1 controller node. The device tree node for FT5X06 is inkernel/arch/arm64/boot/dts/rockchip/topeet-screen-lcds.dtsas shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&i2c1 {
status = "okay";

ft5x061:ft5x06@38 {
status = "disabled";
compatible = "edt,edt-ft5306";
reg = <0x38>;
touch-gpio = <&gpio3 RK_PA5 IRQ_TYPE_EDGE_RISING>;
interrupt-parent = <&gpio3>;
interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;
reset-gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <800>;
touchscreen-size-y = <1280>;

};
};

This node appends the FT5X06 touch chip related node to the I2C1 controller node. That is,ft5x06 as a child node of i2c1

Cancel the original driver of rk3568

  1. Uncheck the FT5X06 driver in menuconfig
  2. Intopeet-screen-lcds.dtsselectLCD_TYPE_MIPI
1
2
3
4
5
6
7
#define LCD_TYPE_MIPI       //in vp 1
//#define LCD_TYPE_LVDS_10_1_1024X600 //in vp 2
//#define LCD_TYPE_LVDS_10_1_1280X800_gt9271 //in vp 2
//#define LCD_TYPE_LVDS_7_0 //in vp 2
//#define LCD_TYPE_EDP_VGA //in vp 0
//#define LCD_TYPE_HDMI_VP0 //hdmi in vp 0
//#define LCD_TYPE_HDMI_VP1 //hdmi in vp 1
  1. Set the status of the original ft5x06 device tree node from okay to disabled
1
2
3
4
5
6
7
8
#if defined(LCD_TYPE_MIPI)
...
&ft5x061
{
status = "okay";
};
...
#endif

FT5X06 Client Device Tree Writing

1
2
3
4
5
6
7
&i2c1 {
status = "okay";
myft5x06: my-ft5x06@38 {
compatible = "my-ft5x06";
reg = <0x38>;
};
};

The description of the appended device tree node is as follows:

  • &i2c1: Indicates a reference to I2C controller 1.
  • status = "okay";: Indicates enabling I2C controller 1.
  • myft5x06: my-ft5x06@38: Defines a device node named myft5x06.my-ft5x06@38Indicates that the I2C address of this device is 0x38.
  • compatible = "my-ft5x06";: This attribute is used to identify the device type, here indicating that it is a device namedmy-ft5x06.
  • reg = <0x38>;: This attribute defines the device’s address on the I2C bus, here it is 0x38

In addition to the I2C part, the FT5X06 touch chip also has two other GPIOs: an interrupt pin and a reset pin. The function matching table for each pin is shown below

The two GPIOs of the FT5X06 are the interrupt pin and the reset pin
The two GPIOs of the FT5X06 are the interrupt pin and the reset pin

Therefore, we also need to describe this in the device tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
&i2c1 {
status = "okay";
ft5x061:ft5x06@38 {
status = "okay";
compatible = "my-ft5x06";
reg = <0x38>;
touch-gpio = <&gpio3 RK_PA5 IRQ_TYPE_EDGE_RISING>;
interrupt-parent = <&gpio3>;
interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;
reset-gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&myft5x06_pins>;
touchscreen-size-x = <800>;
touchscreen-size-y = <1280>;

};
};
  • reset-gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;: Defines the reset pin of the device, connected toGPIO0the RK_PB6 pin, with active low level.
  • interrupt-parent = <&gpio3>;: Specifies the interrupt parent node as GPIO3.
  • touch-gpio = <&gpio3 RK_PA5 IRQ_TYPE_EDGE_RISING>;: Defines the touch pin of the device, connected to the RK_PA5 pin of GPIO3.
  • interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;: Further describes the interrupt trigger method, which is low-level trigger.
  • pinctrl-names = "default";andpinctrl-0 = <&myft5x06_pins>;: Specifies the default pin configuration used by the device.

The pinctrl node specified here is namedmyft5x06_pins, so the pinctrl node needs to be appended. The appended content is as follows:

1
2
3
4
5
6
7
8
9
&pinctrl {
myft5x06 {
myft5x06_pins: myft5x06-pins {
rockchip,pins =
<0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>,
<0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>;
}
};
}

Writing I2C Client in C

Generally, the device tree is used to write I2C Client, but before the introduction of the device tree, using C files is also acceptable

struct i2c_adaper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;

/* data fields that are valid for all devices */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;

int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
unsigned long locked_flags; /* owned by the I2C core */
#define I2C_ALF_IS_SUSPENDED 0
#define I2C_ALF_SUSPEND_REPORTED 1

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;

struct irq_domain *host_notify_domain;
};

i2c_get_adapter()

i2c_get_adapterThe main function of this function is to search for the correspondingi2c_adapter_idrstructure from the given I2C adapter number nr in thei2c_adapterstructure, which is defined in thedrivers/i2c/i2c-core-base.cfile, with the specific content as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct i2c_adapter *i2c_get_adapter(int nr)
{
struct i2c_adapter *adapter;
// Acquire the i2c_adapter_lock in the idr
mutex_lock(&core_lock);
// In the i2c_adapter_search for the adapter with the specified number in the idr
adapter = idr_find(&i2c_adapter_idr, nr);
if (!adapter)
goto exit;
// Attempt to acquire the reference count of the module to which the adapter belongs
if (try_module_get(adapter->owner))
// Increase the reference count of the adapter device
get_device(&adapter->dev);
else
adapter = NULL;

exit:
// Release the i2c_adapter_lock in the idr
mutex_unlock(&core_lock);
return adapter;
}
EXPORT_SYMBOL(i2c_get_adapter);

i2c_put_adapter()

1
2
3
4
5
6
7
8
9
10
void i2c_put_adapter(struct i2c_adapter *adap)
{
if (!adap)
return;

module_put(adap->owner);
/* Should be last, otherwise we risk use-after-free with 'adap' */
put_device(&adap->dev);
}
EXPORT_SYMBOL(i2c_put_adapter);

When the driver is unloadedi2c_adapterThe structure needs to be freed, and when the structurei2c_put_adapterfunction is used to freei2c_adapterstructure,i2c_put_adapterThe function is also defined indrivers/i2c/i2c-core-base.cthe file

i2c_new_client_device()

i2c_new_client_devicefunction is used to create and register a device corresponding to the I2C bus. After registration, the I2C subsystem automatically creates the corresponding device node for the device, allowing upper-layer applications to access and control it. This function is also defined indrivers/i2c/i2c-core-base.cthe file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
* i2c_new_client_device - instantiate an i2c device
* @adap: the adapter managing the device
* @info: describes one I2C device; bus_num is ignored
* Context: can sleep
*
* Create an i2c device. Binding is handled through driver model
* probe()/remove() methods. A driver may be bound to this device when we
* return from this function, or any later moment (e.g. maybe hotplugging will
* load the driver module). This call is not appropriate for use by mainboard
* initialization logic, which usually runs during an arch_initcall() long
* before any i2c_adapter could exist.
*
* This returns the new i2c client, which may be saved for later use with
* i2c_unregister_device(); or an ERR_PTR to describe the error.
*/
struct i2c_client *
i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
// Allocate space for the i2c_client structure
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return ERR_PTR(-ENOMEM);
// Set the adapter pointer of i2c_client
client->adapter = adap;
// From i2c_board_Copy relevant information from the info structure to i2c_client
client->dev.platform_data = info->platform_data;
client->flags = info->flags;
client->addr = info->addr;

client->init_irq = info->irq;
if (!client->init_irq)
client->init_irq = i2c_dev_irq_from_resources(info->resources,
info->num_resources);

strlcpy(client->name, info->type, sizeof(client->name));
// Check if the address is valid
status = i2c_check_addr_validity(client->addr, client->flags);
if (status) {
dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
goto out_err_silent;
}

/* Check for address business */
// Check if the address is already occupied by another device
status = i2c_check_addr_ex(adap, i2c_encode_flags_to_addr(client));
if (status)
dev_err(&adap->dev,
"%d i2c clients have been registered at 0x%02x",
status, client->addr);
// Set the device information of i2c_client
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = of_node_get(info->of_node);
client->dev.fwnode = info->fwnode;

i2c_dev_set_name(adap, client, info, status);
// If there are device attributes, add them to the device
if (info->properties) {
status = device_add_properties(&client->dev, info->properties);
if (status) {
dev_err(&adap->dev,
"Failed to add properties to client %s: %d\n",
client->name, status);
goto out_err_put_of_node;
}
}
// Register device
status = device_register(&client->dev);
if (status)
goto out_free_props;

dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));

return client;

out_free_props:
if (info->properties)
device_remove_properties(&client->dev);
out_err_put_of_node:
of_node_put(info->of_node);
out_err_silent:
kfree(client);
return ERR_PTR(status);
}
EXPORT_SYMBOL_GPL(i2c_new_client_device);
i2c_bus_probe()

i2c_new_client_device()inclient->dev->busis assignedi2c_bus_type, executei2c_driverwill be executed before the probe ofi2c_bus_probe(). Here, the irq of the client is assigned

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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);

struct device_type i2c_client_type = {
.groups = i2c_dev_groups,
.uevent = i2c_device_uevent,
.release = i2c_client_dev_release,
};
EXPORT_SYMBOL_GPL(i2c_client_type);


static int i2c_device_probe(struct device *dev)
{
// Get the i2c_client structure from the device structure
struct i2c_client *client = i2c_verify_client(dev);
// Get the i2c_driver structure from the device structure
struct i2c_driver *driver;
int status;

if (!client)// If the client does not exist, return 0
return 0;

client->irq = client->init_irq;
// If the client has no interrupt number, try to get the interrupt number
if (!client->irq) {
int irq = -ENOENT;
// If the client uses Host Notify interrupt, use i2c_smbus_host_notify_to_irq to get the interrupt number
if (client->flags & I2C_CLIENT_HOST_NOTIFY) {
dev_dbg(dev, "Using Host Notify IRQ\n");
/* Keep adapter active when Host Notify is required */
pm_runtime_get_sync(&client->adapter->dev);
irq = i2c_smbus_host_notify_to_irq(client);
// If the device has a DT node, try to get the interrupt number from the DT node
} else if (dev->of_node) {
irq = of_irq_get_byname(dev->of_node, "irq");
if (irq == -EINVAL || irq == -ENODATA)
irq = of_irq_get(dev->of_node, 0);
// If the device has an ACPI association, try to get the interrupt number from ACPI
} else if (ACPI_COMPANION(dev)) {
irq = i2c_acpi_get_irq(client);
}
// If getting the interrupt number fails, set it to 0
if (irq == -EPROBE_DEFER) {
status = irq;
goto put_sync_adapter;
}

if (irq < 0)
irq = 0;
// Set the obtained interrupt number into the client structure
client->irq = irq;
}
// Convert dev->driver to i2c_driver type
driver = to_i2c_driver(dev->driver);

/*
* An I2C ID table is not mandatory, if and only if, a suitable OF
* or ACPI ID table is supplied for the probing device.
*/
// If the driver has no ID table and the device has no matching OF or ACPI ID table, return -ENODEV
if (!driver->id_table &&
!acpi_driver_match_device(dev, dev->driver) &&
!i2c_of_match_device(dev->driver->of_match_table, client)) {
status = -ENODEV;
goto put_sync_adapter;
}

// If the client requires wake-up functionality, attempt to set the wake-up interrupt
if (client->flags & I2C_CLIENT_WAKE) {
int wakeirq;

wakeirq = of_irq_get_byname(dev->of_node, "wakeup");
if (wakeirq == -EPROBE_DEFER) {
status = wakeirq;
goto put_sync_adapter;
}
// Enable the device's wake-up functionality
device_init_wakeup(&client->dev, true);
// If a wake-up interrupt number is obtained and differs from the normal interrupt number, set a dedicated wake-up interrupt
if (wakeirq > 0 && wakeirq != client->irq)
status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);
else if (client->irq > 0)// Otherwise, use the normal interrupt as the wake-up interrupt
status = dev_pm_set_wake_irq(dev, client->irq);
else
status = 0;
// If setting the wake-up interrupt fails, output a warning
if (status)
dev_warn(&client->dev, "failed to set up wakeup irq\n");
}

dev_dbg(dev, "probe\n");
// Set the default clock value for the device
status = of_clk_set_defaults(dev->of_node, false);
if (status < 0)
goto err_clear_wakeup_irq;
// Attach the PM domain
status = dev_pm_domain_attach(&client->dev, true);
if (status)
goto err_clear_wakeup_irq;

/*
* When there are no more users of probe(),
* rename probe_new to probe.
*/
if (driver->probe_new)// Call the driver's probe_new or probe function
status = driver->probe_new(client);
else if (driver->probe)
status = driver->probe(client,
i2c_match_id(driver->id_table, client));
else
status = -EINVAL;

if (status)// If the probe function fails, clear the wake-up interrupt and detach the PM domain
goto err_detach_pm_domain;

return 0;

err_detach_pm_domain:
dev_pm_domain_detach(&client->dev, true);
err_clear_wakeup_irq:
dev_pm_clear_wake_irq(&client->dev);
device_init_wakeup(&client->dev, false);
put_sync_adapter:
if (client->flags & I2C_CLIENT_HOST_NOTIFY)
pm_runtime_put_sync(&client->adapter->dev);

return status;
}

struct i2c_board_info

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// include/linux/i2c.h
/**
* struct i2c_board_info - template for device creation
* @type: chip type, to initialize i2c_client.name
* @flags: to initialize i2c_client.flags
* @addr: stored in i2c_client.addr
* @dev_name: Overrides the default <busnr>-<addr> dev_name if set
* @platform_data: stored in i2c_client.dev.platform_data
* @of_node: pointer to OpenFirmware device node
* @fwnode: device node supplied by the platform firmware
* @properties: additional device properties for the device
* @resources: resources associated with the device
* @num_resources: number of resources in the @resources array
* @irq: stored in i2c_client.irq
*
* I2C doesn't actually support hardware probing, although controllers and
* devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's
* a device at a given address. Drivers commonly need more information than
* that, such as chip type, configuration, associated IRQ, and so on.
*
* i2c_board_info is used to build tables of information listing I2C devices
* that are present. This information is used to grow the driver model tree.
* For mainboards this is done statically using i2c_register_board_info();
* bus numbers identify adapters that aren't yet available. For add-on boards,
* i2c_new_client_device() does this dynamically with the adapter already known.
*/
struct i2c_board_info {
char type[I2C_NAME_SIZE];// Type name of the I2C device, maximum length is I2C_NAME_SIZE
unsigned short flags;// Flags of the I2C device, used to specify special attributes of the device
unsigned short addr;// Address of the I2C device
const char *dev_name;// Device name of the I2C device
void *platform_data;// Platform data of the I2C device, can be NULL
struct device_node *of_node;// Node pointer of the I2C device node in the device tree
struct fwnode_handle *fwnode;// fwnode handle of the I2C device node in ACPI
const struct property_entry *properties;// Property list of the I2C device
const struct resource *resources;// Resource list used by the I2C device
unsigned int num_resources;// Number of resources used by the I2C device
int irq;
};

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>

// Define an i2c_adapter structure pointer
struct i2c_adapter *i2c_ada;

// Define i2c_board_info structure array, used to describe the ft5x06 device

static struct i2c_board_info ft5x06[] = {
{
.type = "my-ft5x06",
.addr = 0x38,
}
};

static int __init i2c_client_test_init(void){
// Get the i2c adapter
i2c_ada = i2c_get_adapter(1);
if(!i2c_ada){
pr_err("Fail to get i2c_adapter1");
return -ENODEV;
}
// Register the ft5x06 device
i2c_new_client_device(i2c_ada, ft5x06);

return 0;
}


static void __exit i2c_client_test_exit(void){
// Release the i2c adapter
i2c_put_adapter(i2c_ada);
}

module_init(i2c_client_test_init);
module_exit(i2c_client_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629 <asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a test sample for i2c client");

Core layer I2C communication

The main functions of the I2C core layer arei2c_master_sendi2c_master_recvandi2c_transfer, wherei2c_master_sendandi2c_master_recvfunctions are the basic read/write interfaces provided by the I2C core layer. These two functions are responsible for generating timing and data frames that comply with the I2C protocol, and perform actual bus operations through the corresponding I2C adapter driver. The two functions are defined ininclude/linux/i2c.hthe file

i2c_master_recv()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// include/linux/i2c.h
/**
* i2c_master_recv - issue a single I2C message in master receive mode
* @client: Handle to slave device
* @buf: Where to store data read from slave
* @count: How many bytes to read, must be less than 64k since msg.len is u16
*
* Returns negative errno, or else the number of bytes read.
*/
static inline int i2c_master_recv(const struct i2c_client *client,
char *buf, int count)
{
return i2c_transfer_buffer_flags(client, buf, count, I2C_M_RD);
};

i2c_master_send()

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* i2c_master_send - issue a single I2C message in master transmit mode
* @client: Handle to slave device
* @buf: Data that will be written to the slave
* @count: How many bytes to write, must be less than 64k since msg.len is u16
*
* Returns negative errno, or else the number of bytes written.
*/
static inline int i2c_master_send(const struct i2c_client *client,
const char *buf, int count)
{
return i2c_transfer_buffer_flags(client, (char *)buf, count, 0);
};

i2c_transfer_buffer_flags()

i2c_master_recv()andi2c_master_send()actually callsi2c_transfer_buffer_flags()this function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// drivers/i2c/i2c-core-base.c
/**
* i2c_transfer_buffer_flags - issue a single I2C message transferring data
* to/from a buffer
* @client: Handle to slave device
* @buf: Where the data is stored
* @count: How many bytes to transfer, must be less than 64k since msg.len is u16
* @flags: The flags to be used for the message, e.g. I2C_M_RD for reads
*
* Returns negative errno, or else the number of bytes transferred.
*/
int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf,
int count, u16 flags)
{
int ret;
// to construct the i2c_msg structure, describing the current transfer operation
struct i2c_msg msg = {
.addr = client->addr, // set the slave device address
.flags = flags | (client->flags & I2C_M_TEN),// set the transfer flags, including flags passed by the user and flags of the client object itself
.len = count,// set the transfer data length
.buf = buf,// set the data buffer
};

// call the i2c_transfer function to perform data transfer
// This function returns the actual number of messages successfully transmitted based on the number of messages sent.
ret = i2c_transfer(client->adapter, &msg, 1);

/*
* If everything went ok (i.e. 1 msg transferred), return #bytes
* transferred, else error code.
*/
return (ret == 1) ? count : ret;
}
EXPORT_SYMBOL(i2c_transfer_buffer_flags);

This function is used to transmit data via the I2C bus. It first constructs ai2c_msgstructure, describing the current transmission operation, including the slave device address, transmission flags, data length, and data buffer. Then it callsi2c_transferfunction to perform the actual data transmission.

i2c_transfer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* i2c_transfer - execute a single or combined I2C message
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to
* terminate the operation; each message begins with a START.
* @num: Number of messages to be executed.
*
* Returns negative errno, else the number of messages executed.
*
* Note that there is no requirement that each message be sent to
* the same slave address, although that is the most common model.
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
int ret;

if (!adap->algo->master_xfer) {// If the adapter does not support the master_xfer operation, it directly returns an error.
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}

/* REVISIT the fault reporting model here is weak:
*
* - When we get an error after receiving N bytes from a slave,
* there is no way to report "N".
*
* - When we get a NAK after transmitting N bytes to a slave,
* there is no way to report "N" ... or to let the master
* continue executing the rest of this combined message, if
* that's the appropriate response.
*
* - When for example "num" is two and we successfully complete
* the first message but get an error part way through the
* second, it's unclear whether that should be reported as
* one (discarding status on the second message) or errno
* (discarding status on the first one).
*/
ret = __i2c_lock_bus_helper(adap);
if (ret)
return ret;
// Call__i2c_transfer to execute the actual message transmission
ret = __i2c_transfer(adap, msgs, num);
i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);// Unlock the I2C bus

return ret;
}
EXPORT_SYMBOL(i2c_transfer);
  • struct i2c_adapter *adap: Indicates the I2C adapter to be used. Each I2C controller corresponds to ai2c_adapterstructure, which contains various attributes and operation functions of this adapter.
  • struct i2c_msg *msgs: Points to an array of i2c_msg structures, used to describe one or more I2C messages to be transmitted.

struct i2c_msg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// include/uapi/linux/i2c.h
/**
* struct i2c_msg - an I2C transaction segment beginning with START
* @addr: Slave address, either seven or ten bits. When this is a ten
* bit address, I2C_M_TEN must be set in @flags and the adapter
* must support I2C_FUNC_10BIT_ADDR.
* @flags: I2C_M_RD is handled by all adapters. No other flags may be
* provided unless the adapter exported the relevant I2C_FUNC_*
* flags through i2c_check_functionality().
* @len: Number of data bytes in @buf being read from or written to the
* I2C slave address. For read transactions where I2C_M_RECV_LEN
* is set, the caller guarantees that this buffer can hold up to
* 32 bytes in addition to the initial length byte sent by the
* slave (plus, if used, the SMBus PEC); and this value will be
* incremented by the number of block data bytes received.
* @buf: The buffer into which data is read, or from which it's written.
*
* An i2c_msg is the low level representation of one segment of an I2C
* transaction. It is visible to drivers in the @i2c_transfer() procedure,
* to userspace from i2c-dev, and to I2C adapter drivers through the
* @i2c_adapter.@master_xfer() method.
*
* Except when I2C "protocol mangling" is used, all I2C adapters implement
* the standard rules for I2C transactions. Each transaction begins with a
* START. That is followed by the slave address, and a bit encoding read
* versus write. Then follow all the data bytes, possibly including a byte
* with SMBus PEC. The transfer terminates with a NAK, or when all those
* bytes have been transferred and ACKed. If this is the last message in a
* group, it is followed by a STOP. Otherwise it is followed by the next
* @i2c_msg transaction segment, beginning with a (repeated) START.
*
* Alternatively, when the adapter supports I2C_FUNC_PROTOCOL_MANGLING then
* passing certain @flags may have changed those standard protocol behaviors.
* Those flags are only for use with broken/nonconforming slaves, and with
* adapters which are known to support the specific mangling options they
* need (one or more of IGNORE_NAK, NO_RD_ACK, NOSTART, and REV_DIR_ADDR).
*/
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe */
/* makes only sense in kernelspace */
/* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};

According toi2c_master_sendthe function’s input parameters, it can be inferred that 0 indicates a write operation.

struct i2c_algorithm

i2c_transferThe function itself does not have the ability to control hardware; in fact,master_xferis the function that actually drives the hardware to work, thereby achieving I2C communication,master_xferdefined ini2c_adapterof the structi2c_algorithm In the struct, the specific content is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 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.
* @master_xfer_atomic: same as @master_xfer. Yet, only using atomic context
* so e.g. PMICs can be accessed very late before shutdown. Optional.
* @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this
* is not present, then the bus layer will try and convert the SMBus calls
* into I2C transfers instead.
* @smbus_xfer_atomic: same as @smbus_xfer. Yet, only using atomic context
* so e.g. PMICs can be accessed very late before shutdown. Optional.
* @functionality: Return the flags that this algorithm/adapter pair supports
* from the ``I2C_FUNC_*`` flags.
* @reg_slave: Register given client to I2C slave mode of this adapter
* @unreg_slave: Unregister given client from I2C slave mode of this adapter
*
* The following structs are for those who like to implement new bus drivers:
* i2c_algorithm is the interface to a class of hardware solutions which can
* be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
* to name two of the most common.
*
* The return codes from the ``master_xfer{_atomic}`` fields should indicate the
* type of error code that occurred during the transfer, as documented in the
* Kernel Documentation file Documentation/i2c/fault-codes.rst.
*/
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 (*master_xfer_atomic)(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);
int (*smbus_xfer_atomic)(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 *adap);

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

master_xferandsmbus_xferBoth functions are hardware control functions at the I2C device driver layer, written by the original manufacturer’s engineers. The implementation functions for rk3568 are defined indrivers/i2c/busses/i2c-rk3x.cfile. In general, you only need to usei2c_transferfunction indirectly.

__i2c_lock_bus_helper

i2c_transfercall the function that actually performs the transmission in__i2c_transferbefore calling__i2c_lock_bus_helper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* We only allow atomic transfers for very late communication, e.g. to access a
* PMIC when powering down. Atomic transfers are a corner case and not for
* generic use!
*/
static inline bool i2c_in_atomic_xfer_mode(void)
{
return system_state > SYSTEM_RUNNING && irqs_disabled();
}

static inline int __i2c_lock_bus_helper(struct i2c_adapter *adap)
{
int ret = 0;

if (i2c_in_atomic_xfer_mode()) {
WARN(!adap->algo->master_xfer_atomic && !adap->algo->smbus_xfer_atomic,
"No atomic I2C transfer handler for '%s'\n", dev_name(&adap->dev));
ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT) ? 0 : -EAGAIN;
} else {
i2c_lock_bus(adap, I2C_LOCK_SEGMENT);
}

return ret;
}

The purpose of this code is to manage access to the I2C bus through appropriate locking mechanisms, ensuring system stability and preventing race conditions between multiple operations.

  • Ifcurrently in an atomic context or interrupts are disabled, in both cases,the kernel usually does not allow operations that may cause context switches

the code usesi2c_trylock_bus()function to attempt to acquire the I2C bus lock.I2C_LOCK_SEGMENTis used to specify the lock flag. Ifi2c_trylock_bus()returns failure (return value is false), indicating activity on the I2C bus, the function returns error code-EAGAIN, indicating that the lock cannot be acquired temporarily.

  • If not in an atomic context or interrupts are disabled

the code directly callsi2c_lock_bus()function to acquire the I2C bus lock without conditional checks. This is because in this case, the system allows operations that may cause context switches.

__i2c_transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* __i2c_transfer - unlocked flavor of i2c_transfer
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to
* terminate the operation; each message begins with a START.
* @num: Number of messages to be executed.
*
* Returns negative errno, else the number of messages executed.
*
* Adapter lock must be held when calling this function. No debug logging
* takes place. adap->algo->master_xfer existence isn't checked.
*/
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
unsigned long orig_jiffies;// Record the initial jiffies value
int ret, try;// Return value and retry count
// If msgs is empty or num is less than 1, return an invalid parameter error
if (WARN_ON(!msgs || num < 1))
return -EINVAL;

ret = __i2c_check_suspended(adap);
if (ret)
return ret;
// If the adapter has special requirements, check whether the current I2C message is supported
if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
return -EOPNOTSUPP;

/*
* i2c_trace_msg_key gets enabled when tracepoint i2c_transfer gets
* enabled. This is an efficient way of keeping the for-loop from
* being executed when not needed.
*/
/*
* If enabled i2c_trace_msg_key this branch point(used for tracking I2C transmit message),
* then traverse all messages,record trace information for read and write operations separately
*/
if (static_branch_unlikely(&i2c_trace_msg_key)) {
int i;
for (i = 0; i < num; i++)
if (msgs[i].flags & I2C_M_RD)
trace_i2c_read(adap, &msgs[i], i);
else
trace_i2c_write(adap, &msgs[i], i);
}

/* Retry automatically on arbitration loss */ // automatically retry on arbitration loss error
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
// call the adapter's master_xfer function to complete the I2C transfer
if (i2c_in_atomic_xfer_mode() && adap->algo->master_xfer_atomic)
ret = adap->algo->master_xfer_atomic(adap, msgs, num);
else
ret = adap->algo->master_xfer(adap, msgs, num);

if (ret != -EAGAIN)// if it is not an arbitration loss error, exit the loop
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))// if timeout occurs, exit the loop
break;
}
// if i2c is enabled_trace_at the msg_key branch point, record the result of the I2C transfer
if (static_branch_unlikely(&i2c_trace_msg_key)) {
int i;
for (i = 0; i < ret; i++)
if (msgs[i].flags & I2C_M_RD)
trace_i2c_reply(adap, &msgs[i], i);
trace_i2c_result(adap, num, ret);
}

return ret;
}
EXPORT_SYMBOL(__i2c_transfer);

__i2c_transferthe current timestamp will be recordedorig_jiffies, and loop up toadap->retriesretries. During each retry, call the adapter’smaster_xferfunction to complete the I2C transfer. If the return value is not-EAGAIN(indicating an arbitration loss error), or if timeout has occurred, exit the loop.

I2C driver

i2c_add_driver()

1
2
3
4
// include/linux/i2c.h
/* use a define to avoid include chaining to get THIS_MODULE */
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)

Specific implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// drivers/i2c/i2c-core-base.c
/*
* An i2c_driver is used with one or more i2c_client (device) nodes to access
* i2c slave chips, on a bus instance associated with some i2c_adapter.
*/

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
int res;

/* Can't register until after driver model init */
if (WARN_ON(!is_registered))
return -EAGAIN;

/* add the driver to the list of i2c drivers in the driver core */
driver->driver.owner = owner;
driver->driver.bus = &i2c_bus_type;
INIT_LIST_HEAD(&driver->clients);

/* When registration returns, the driver core
* will have called probe() for all matching-but-unbound devices.
*/
res = driver_register(&driver->driver);
if (res)
return res;

pr_debug("driver [%s] registered\n", driver->driver.name);

/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);

return 0;
}
EXPORT_SYMBOL(i2c_register_driver);

The main function of this function is to register the I2C device driver into the driver core and initialize the related data structures. The data structure type passed here isi2c_driver, which needs to be filled in when writing the driver. This structure is defined in theinclude/linux/i2c.hheader file, and its specific content is as follows:

struct i2c_driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// include/linux/i2c.h
/**
* struct i2c_driver - represent an I2C device driver
* @class: What kind of i2c device we instantiate (for detect)
* @probe: Callback for device binding - soon to be deprecated
* @probe_new: New callback for device binding
* @remove: Callback for device unbinding
* @shutdown: Callback for device shutdown
* @alert: Alert callback, for example for the SMBus alert protocol
* @command: Callback for bus-wide signaling (optional)
* @driver: Device driver model driver
* @id_table: List of I2C devices supported by this driver
* @detect: Callback for device detection
* @address_list: The I2C addresses to probe (for detect)
* @clients: List of detected clients we created (for i2c-core use only)
*
* The driver.owner field should be set to the module owner of this driver.
* The driver.name field should be set to the name of this driver.
*
* For automatic device detection, both @detect and @address_list must
* be defined. @class should also be set, otherwise only devices forced
* with module parameters will be created. The detect function must
* fill at least the name field of the i2c_board_info structure it is
* handed upon successful detection, and possibly also the flags field.
*
* If @detect is missing, the driver will still work fine for enumerated
* devices. Detected devices simply won't be supported. This is expected
* for the many I2C/SMBus devices which can't be detected reliably, and
* the ones which can always be enumerated in practice.
*
* The i2c_client structure which is handed to the @detect callback is
* not a real i2c_client. It is initialized just enough so that you can
* call i2c_smbus_read_byte_data and friends on it. Don't do anything
* else with it. In particular, calling dev_dbg and friends on it is
* not allowed.
*/
struct i2c_driver {
unsigned int class; // Device type to which the driver belongs

/* Standard driver model interfaces */
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);// Callback function for probing and binding the device
int (*remove)(struct i2c_client *client);// Callback function for unbinding the driver from the device

/* New driver model interface to aid the seamless removal of the
* current probe()'s, more commonly unused than used second parameter.
*/
int (*probe_new)(struct i2c_client *client);// New callback function for probing and binding the device

/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *client); // Callback function called when the device is shut down

/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
* For the SMBus Host Notify protocol, the data corresponds to the
* 16-bit payload data reported by the slave device acting as master.
*/
void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol,
unsigned int data);// Callback function called when the device alarms, with format and meaning depending on the protocol used

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

struct device_driver driver;// Device driver infrastructure
const struct i2c_device_id *id_table;// Device ID table matching this driver

/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *client, struct i2c_board_info *info);// Probe callback function for automatically creating devices
const unsigned short *address_list;// List of device addresses matching this driver
struct list_head clients;// List of I2C devices bound to this driver
};

When callingi2c_add_driverBefore registering an I2C device with the function, you need to first fill in thei2c_driverstructure, and then implement various callback functions, which is the same as the platform bus content explained earlier.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio/consumer.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

struct ft5x06_drv_data {
struct gpio_desc *reset_gpio;
struct i2c_client *ft5x06_client;
struct input_dev *ft5x06_input_dev;
struct work_struct ft5x06_irq_work;

};

int ft5x06_read_reg(struct i2c_client *client, u8 reg_addr)
{
u8 data;
// Define two i2c_msg structures, representing write operation and read operation respectively.
struct i2c_msg msgs[2] = {
[0] = {
.addr = client->addr, // Device address to read from
.flags = 0, // Write operation
.len = sizeof(reg_addr),
.buf = &reg_addr, // Write the register address to be read
},
[1] = {
.addr = client->addr,
.flags = I2C_M_RD, // Read operation
.len = sizeof(data),
.buf = &data,
},
};

// Use the i2c_transfer function to perform I2C bus read operation
if (i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)) != ARRAY_SIZE(msgs))
return -EIO;

return data;
}

int ft5x06_write_reg(struct i2c_client *client, u8 reg_addr, u8 data)
{
u8 buf[2] = {
reg_addr,
data,
};

struct i2c_msg msgs[1] = {
[0] = {
.addr = client->addr, // Device address to write to
.flags = 0, // Write operation
.len = ARRAY_SIZE(buf),
.buf = buf, // Write the register address to be read
},
};

if (i2c_transfer(client->adapter, msgs, 1) != ARRAY_SIZE(msgs))
return -EIO;

return 0;
}

irqreturn_t ft5x06_threaded_fn(int irq, void *dev_id)
{
int TOUCH1_XH, TOUCH1_XL, x;
int TOUCH1_YH, TOUCH1_YL, y;
int TD_STATUS;

struct ft5x06_drv_data *drv_data = (struct ft5x06_drv_data *)dev_id;
struct i2c_client *client = drv_data->ft5x06_client;
struct input_dev *input_dev = drv_data->ft5x06_input_dev;

// Read touch coordinate data from the register
TOUCH1_XH = ft5x06_read_reg(client, 0x03);
TOUCH1_XL = ft5x06_read_reg(client, 0x04);

x = ((TOUCH1_XH << 8) | TOUCH1_XL) & 0xfff;

TOUCH1_YH = ft5x06_read_reg(client, 0x05);
TOUCH1_YL = ft5x06_read_reg(client, 0x06);
y = ((TOUCH1_YH << 8) | TOUCH1_YL) & 0xfff;

// Read touch status register
TD_STATUS = ft5x06_read_reg(client, 0x02);
TD_STATUS = TD_STATUS & 0xf;

if (TD_STATUS == 0) {
// Touch release
input_report_key(input_dev, BTN_TOUCH, 0);
input_sync(input_dev);
} else {
// Touch press
input_report_key(input_dev, BTN_TOUCH, 1);
input_report_abs(input_dev, ABS_X, x);
input_report_abs(input_dev, ABS_Y, y);
input_sync(input_dev);
}

return IRQ_HANDLED;
}

irqreturn_t ft5x06_irq_handler(int irq, void *dev_id)
{
struct ft5x06_drv_data *drv_data = (struct ft5x06_drv_data *)dev_id;
schedule_work(&drv_data->ft5x06_irq_work);
return IRQ_WAKE_THREAD;
}

int ft5x06_init(struct gpio_desc *reset_gpio)
{
int ret;
// Set reset GPIO as output, pull low for 5ms, then pull high
// This is a reset operation used to initialize the ft5x06 device
ret = gpiod_direction_output(reset_gpio, 0);
if (ret < 0)
return ret;
msleep(5);
ret = gpiod_direction_output(reset_gpio, 1);
if (ret < 0) {
return ret;
}
return 0;
}

int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;

struct ft5x06_drv_data *drv_data;
struct device *dev;
struct input_dev *input_dev;

dev = &client->dev;
drv_data = devm_kzalloc(dev, sizeof(struct ft5x06_drv_data), GFP_KERNEL);
if (!drv_data)
return -ENOMEM;

i2c_set_clientdata(client, drv_data);

// Save i2c_client
drv_data->ft5x06_client = client;

// Get reset GPIO descriptor
drv_data->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(drv_data->reset_gpio))
return PTR_ERR(drv_data->reset_gpio);
if (!drv_data->reset_gpio)
return -ENODEV;

ret = devm_request_threaded_irq(dev, client->irq, ft5x06_irq_handler, ft5x06_threaded_fn,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "ft5x06 irq",
drv_data);
if (ret < 0)
return -ENODEV;

// Allocate an input device
input_dev = devm_input_allocate_device(dev);
drv_data->ft5x06_input_dev = input_dev;

input_dev->name = "ft5x06_dev";
set_bit(EV_KEY, input_dev->evbit);
set_bit(BTN_TOUCH, input_dev->keybit);
set_bit(EV_ABS, input_dev->evbit);
set_bit(ABS_X, input_dev->absbit);
set_bit(ABS_Y, input_dev->absbit);

// Set the absolute coordinate range of the input device
input_set_abs_params(input_dev, ABS_X, 0, 800, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 1280, 0, 0);

ret = input_register_device(input_dev);
if (ret < 0) {
input_free_device(input_dev);
return ret;
}

// ft5x06 reset initialization
ret = ft5x06_init(drv_data->reset_gpio);
if (ret < 0)
return ret;

return 0;
}

int ft5x06_remove(struct i2c_client *client)
{
// struct ft5x06_drv_data *drv_data = i2c_get_clientdata(client);

return 0;
}

struct i2c_device_id ft5x06_match_table[] = { { .name = "ft5x06" }, {} };
MODULE_DEVICE_TABLE(i2c, ft5x06_match_table);

static const struct of_device_id ft5x06_of_match_table[] = { { .compatible = "even629,ft5x06" },
{} };
MODULE_DEVICE_TABLE(of, ft5x06_of_match_table);

struct i2c_driver ft5x06_drver = {
.driver = {
.name = "ft5x06",
.owner = THIS_MODULE,
.of_match_table = ft5x06_of_match_table,
},
.probe = ft5x06_probe,
.remove = ft5x06_remove,
.id_table = ft5x06_match_table,
};

module_i2c_driver(ft5x06_drver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629 <asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is test sample for ft5x06");

Using I2C in applications

ioctl controls I2C

ioctl is an interface function used in device drivers to control devices. In applications, ioctl can be used to control the I2C controller to read and write I2C devices. The I2C controller nodes of RK3568 are as follows:

I2C controller node
I2C controller node

The control command CMD for the I2C controller is defined ininclude/uapi/linux/i2c-dev.hthe file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* /dev/i2c-X ioctl commands.  The ioctl's parameter is always an
* unsigned long, except for:
* - I2C_FUNCS, takes pointer to an unsigned long
* - I2C_RDWR, takes pointer to struct i2c_rdwr_ioctl_data
* - I2C_SMBUS, takes pointer to struct i2c_smbus_ioctl_data
*/
//Set the number of retries, i.e., the number of times to re-poll when the slave device does not respond
#define I2C_RETRIES 0x0701 /* number of times a device address should
be polled when not acknowledging */
//Set timeout, unit is 10 milliseconds
#define I2C_TIMEOUT 0x0702 /* set timeout in units of 10 ms */

/* NOTE: Slave address is 7 or 10 bits, but 10-bit addresses
* are NOT supported! (due to code brokenness)
*/
//Use this slave address
#define I2C_SLAVE 0x0703 /* Use this slave address */
//Force use of this slave address
#define I2C_SLAVE_FORCE 0x0706 /* Use this slave address, even if it
is already in use by a driver! */
//0 indicates 7-bit address, non-zero indicates 10-bit address
#define I2C_TENBIT 0x0704 /* 0 for 7 bit addrs, != 0 for 10 bit */
//Get adapter function mask
#define I2C_FUNCS 0x0705 /* Get the adapter functionality mask */
//Perform combined read-write transfer (only one STOP signal)
#define I2C_RDWR 0x0707 /* Combined R/W transfer (one STOP only) */
//Use PEC (Packet Error Code) for SMBus transfer
#define I2C_PEC 0x0708 /* != 0 to use PEC with SMBus */
//Perform SMBus transfer
#define I2C_SMBUS 0x0720 /* SMBus transfer */


/* This is the structure as used in the I2C_SMBUS ioctl call */
struct i2c_smbus_ioctl_data {
__u8 read_write;
__u8 command;
__u32 size;
union i2c_smbus_data __user *data;
};

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
};

#define I2C_RDWR_IOCTL_MAX_MSGS 42
/* Originally defined with a typo, keep it for compatibility */
#define I2C_RDRW_IOCTL_MAX_MSGS I2C_RDWR_IOCTL_MAX_MSGS

i2c_rdwr_ioctl_dataThis structure is used inI2C_RDWRioctl call to pass I2C messages. Where

  • msgsis a pointer toi2c_msgan array of structures, used to store one or more I2C messages.
  • nmsgsisi2c_msgthe length of the structure array, i.e., the number of I2C messages.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

/**
* @brief from I2C read data from the device's register
* @param fd opened I2C device file descriptor
* @param slave_addr I2C slave address of the device
* @param reg_addr register address to read
* @return register value
*/
int ft5x06_read_reg(int fd, unsigned char slave_addr, unsigned char reg_addr)
{
unsigned char data;
int ret;
// define two i2c_msg structures, the first for writing the register address, the second for reading data
struct i2c_msg dev_msgs[] = {
[0] = {
.addr = slave_addr,
.flags = 0,
.len = sizeof(reg_addr),
.buf = &reg_addr,
},
[1] = {
.addr = slave_addr,
.flags = I2C_M_RD,
.len = sizeof(data),
.buf = &data,
}
};
struct i2c_rdwr_ioctl_data i2c_msgs = {
.msgs = dev_msgs,
.nmsgs = 2
};


ret = ioctl(fd, I2C_RDWR, &i2c_msgs);
if(ret < 0){
printf("read error\n");
return ret;
}

return data;
}

int ft5x06_write_reg(int fd, unsigned char slave_addr, unsigned char reg_addr, unsigned char data)
{
int ret = 0;
unsigned char buf[2] = {reg_addr, data};

struct i2c_msg dev_msgs[1] = {
[0] = {
.addr = slave_addr,
.flags = 0,
.len = 2,
.buf = buf,
},
};

struct i2c_rdwr_ioctl_data i2c_msgs = {
.msgs = dev_msgs,
.nmsgs = 1,
};

ret = ioctl(fd, I2C_RDWR, &i2c_msgs);
if(ret < 0)
printf("write error\n");

return ret;
}

int main(int argc, char **argv)
{
int fd;
int ID_G_THGROUP;

// open the I2C device file
fd = open("/dev/i2c-1", O_RDWR);
if (fd < 0) {
printf("open error\n");
return fd;
}

unsigned char data = 0x55;
// write 0x55 to register 0x80 at address 0x38
ft5x06_write_reg(fd, 0x38, 0x80, data);
// read data from register 0x80 at address 0x38
ID_G_THGROUP = ft5x06_read_reg(fd, 0x38, 0x80);

printf("ID_G_THGROUP is 0x%02X\n", ID_G_THGROUP);

close(fd);
return 0;
}

generic I2C driver

the generic I2C driver file isdrivers/i2c/i2c-dev.c, which provides a unified driver framework for I2C peripherals, divided into I2C client and I2C driver.

it provides a generic device node for upper-layer applications/dev/i2c-X(X represents the I2C bus number).

The application can directly open this device node/dev/i2c-X, and use standard I/O operations such as open(), ioctl(), read(), write(), etc., to communicate with the I2C slave device.

This driver is generally enabled by default, and the specific path is as follows:

1
2
3
Device Drivers
I2C Support
I2C Device interface

i2c_dev_init()

Driver initialization function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static int __init i2c_dev_init(void)
{
int res;

// Print kernel log, indicating that the i2c /dev entry driver has been initialized
printk(KERN_INFO "i2c /dev entries driver\n");

// Register character device driver, with major device number I2C_MAJOR, minor device number range from 0 to I2C_MINORS-1, device name is "i2c"
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
if (res)
goto out;

// Create a class object named "i2c-dev" to create device nodes in user space
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}

// Set the i2c_groups array as the dev of this class_groups attribute
i2c_dev_class->dev_groups = i2c_groups;

/* Keep track of adapters which will be added or removed later */
// Register a bus notification function i2cdev_notifier to track newly added or removed adapters on the i2c bus
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class;

/* Bind to already existing adapters right away */
// Immediately bind existing i2c adapters to i2c devices
i2c_for_each_dev(NULL, i2cdev_attach_adapter);

return 0;

out_unreg_class:
// Destroy the created class object
class_destroy(i2c_dev_class);
out_unreg_chrdev:
// Unregister the registered character device driver
unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:
// Print kernel log for initialization failure
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}

i2c_dev_adapter()

i2c_dev_init()Called last in the functioni2cdev_attach_adapter()Function to bind an existing i2c adapter to an i2c device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int res;

// Check if the device type is i2c_adapter_type, if not, return
if (dev->type != &i2c_adapter_type)
return 0;
adap = to_i2c_adapter(dev);

// From i2c_dev_Get a free i2c_dev structure from the list
i2c_dev = get_free_i2c_dev(adap);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);

// Initialize i2c_cdev field in the dev structure, set file operations to i2cdev_fops
cdev_init(&i2c_dev->cdev, &i2cdev_fops);
i2c_dev->cdev.owner = THIS_MODULE;

// Initialize the device object i2c_dev->dev
device_initialize(&i2c_dev->dev);

// Set device number to major number I2C_MAJOR and minor number adap->nr
i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr);
i2c_dev->dev.class = i2c_dev_class;
i2c_dev->dev.parent = &adap->dev;
i2c_dev->dev.release = i2cdev_dev_release;

// Set device name to "i2c-{adap->nr}"
dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr);
res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev);
if (res) {
put_i2c_dev(i2c_dev, false);
return res;
}
// Print debug info indicating adapter [adap->name] registered as minor number adap->nr
pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
adap->name, adap->nr);
return 0;
}

The function of this function is to create a corresponding character device node for a new i2c adapter discovered on the system bus.

struct file_operations i2cdev_fops

i2c_devThe file operations structure specified by the cdev field in the structure isi2cdev_fops, and its specific content is as follows:

1
2
3
4
5
6
7
8
9
10
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.compat_ioctl = compat_i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};

i2cdev_open()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static int i2cdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);// Get the minor device number
struct i2c_client *client;// Declare i2c_client and i2c_adapter structure pointer
struct i2c_adapter *adap;
// Get the corresponding i2c_adapter based on the minor device number
adap = i2c_get_adapter(minor);
// If the corresponding i2c_adapter is not found, return the -ENODEV error
if (!adap)
return -ENODEV;

/* This creates an anonymous i2c_client, which may later be
* pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
*
* This client is ** NEVER REGISTERED ** with the driver model
* or I2C core code!! It just holds private copies of addressing
* information and maybe a PEC flag.
*/
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client) {// If memory allocation fails, release the i2c_adapter and return the -ENOMEM error
i2c_put_adapter(adap);
return -ENOMEM;
}
// Set the name of the i2c_client
snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

client->adapter = adap;// Assign the i2c_adapter to the i2c_client's adapter field
file->private_data = client; // Assign the i2c_Save the client pointer to file's private_data field

return 0;
}

i2cdev_read()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
* After opening an instance of this character special file, a file
* descriptor starts out associated only with an i2c_adapter (and bus).
*
* Using the I2C_RDWR ioctl(), you can then *immediately* issue i2c_msg
* traffic to any devices on the bus used by that adapter. That's because
* the i2c_msg vectors embed all the addressing information they need, and
* are submitted directly to an i2c_adapter. However, SMBus-only adapters
* don't support that interface.
*
* To use read()/write() system calls on that file descriptor, or to use
* SMBus interfaces (and work with SMBus-only hosts!), you must first issue
* an I2C_SLAVE (or I2C_SLAVE_FORCE) ioctl. That configures an anonymous
* (never registered) i2c_client so it holds the addressing information
* needed by those system calls and by this SMBus interface.
*/

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
loff_t *offset)
{
char *tmp;// Declare a temporary buffer pointer
int ret;// Save i2c_master_Return value of recv

struct i2c_client *client = file->private_data;// From file's private_Get i2c from the data field_client pointer
// Limit the maximum read bytes to 8192
if (count > 8192)
count = 8192;
// Allocate temporary buffer
tmp = kzalloc(count, GFP_KERNEL);
// If memory allocation fails, return -ENOMEM error
if (tmp == NULL)
return -ENOMEM;
// Print debug information
pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
iminor(file_inode(file)), count);
// Use i2c_master_The recv function reads data from the i2c_client device
ret = i2c_master_recv(client, tmp, count);
// If the read is successful
if (ret >= 0)
if (copy_to_user(buf, tmp, ret))// Copy the read data to the user-space buffer
ret = -EFAULT;// If the copy fails, return the -EFAULT error
kfree(tmp);// Release the temporary buffer
return ret;// Return the actual number of bytes read, or an error code
}

i2cdev_write()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static ssize_t i2cdev_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
int ret;// Save i2c_master_Return value of send
char *tmp;// Declare a temporary buffer pointer
struct i2c_client *client = file->private_data;// From the file's private_data field, obtain i2c_client pointer

if (count > 8192)// Limit the maximum number of write bytes to 8192
count = 8192;
// Allocate a temporary buffer and copy data from user space to it
tmp = memdup_user(buf, count);
if (IS_ERR(tmp))// If the memory copy fails, return an error code
return PTR_ERR(tmp);
// Print debug information
pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
iminor(file_inode(file)), count);
// Use i2c_master_The send function writes data to the i2c_client device
ret = i2c_master_send(client, tmp, count);
// Release the temporary buffer
kfree(tmp);
// Return the number of bytes actually written, or an error code
return ret;
}

i2cdev_ioctl()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
// From the file's private_data field, obtain the i2c_client pointer
struct i2c_client *client = file->private_data;
// Declare an unsigned long variable to store device capabilities
unsigned long funcs;
// Print debug information
dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
cmd, arg);
// Process according to different ioctl commands
switch (cmd) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
// Check if the slave device address is valid
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;
// If it is the I2C_SLAVE command, check if the address is already in use
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
/* REVISIT: address could become busy later */
// Set the slave device address
client->addr = arg;
return 0;
case I2C_TENBIT:
if (arg)// Set the 10-bit address mode
client->flags |= I2C_M_TEN;
else
client->flags &= ~I2C_M_TEN;
return 0;
case I2C_PEC:// Set the PEC flag
/*
* Setting the PEC flag here won't affect kernel drivers,
* which will be using the i2c_client node registered with
* the driver model core. Likewise, when that client has
* the PEC flag already set, the i2c-dev driver won't see
* (or use) this setting.
*/
if (arg)
client->flags |= I2C_CLIENT_PEC;
else
client->flags &= ~I2C_CLIENT_PEC;
return 0;
case I2C_FUNCS:
funcs = i2c_get_functionality(client->adapter);// Get the capabilities of the i2c adapter
return put_user(funcs, (unsigned long __user *)arg);// Write the result to the user-space address

case I2C_RDWR: {// Handle the I2C_RDWR command
struct i2c_rdwr_ioctl_data rdwr_arg;
struct i2c_msg *rdwr_pa;
// Copy the parameter structure from user space
if (copy_from_user(&rdwr_arg,
(struct i2c_rdwr_ioctl_data __user *)arg,
sizeof(rdwr_arg)))
return -EFAULT;
// Check parameter validity
if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)// Limit the maximum number of messages to I2C_RDWR_IOCTL_MAX_MSGS
return -EINVAL;

/*
* Put an arbitrary limit on the number of messages that can
* be sent at once
*/
if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
return -EINVAL;

rdwr_pa = memdup_user(rdwr_arg.msgs,
rdwr_arg.nmsgs * sizeof(struct i2c_msg));// Copy the message array from user space to kernel space
if (IS_ERR(rdwr_pa))
return PTR_ERR(rdwr_pa);
// Call i2cdev_ioctl_rdwr function to perform I2C read/write operations
return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
}

case I2C_SMBUS: {// Handle the I2C_SMBUS command
struct i2c_smbus_ioctl_data data_arg;
if (copy_from_user(&data_arg,
(struct i2c_smbus_ioctl_data __user *) arg,
sizeof(struct i2c_smbus_ioctl_data)))
return -EFAULT;
return i2cdev_ioctl_smbus(client, data_arg.read_write,
data_arg.command,
data_arg.size,
data_arg.data);
}
case I2C_RETRIES:// Set the retry count of the I2C adapter
if (arg > INT_MAX)
return -EINVAL;

client->adapter->retries = arg;
break;
case I2C_TIMEOUT:// Set the timeout of the I2C adapter
if (arg > INT_MAX)
return -EINVAL;

/* For historical reasons, user-space sets the timeout
* value in units of 10 ms.
*/
// The unit set in user space is 10 ms
client->adapter->timeout = msecs_to_jiffies(arg * 10);
break;
default:// Unsupported ioctl command
/* NOTE: returning a fault code here could cause trouble
* in buggy userspace code. Some old kernel bugs returned
* zero in this case, and userspace code might accidentally
* have depended on that bug.
*/
return -ENOTTY;
}
return 0;
}

Test example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>

/*
* From I2C Device reads register value
* @param fd: I2C Device file handle
* @param reg_addr: Register address to read
*/
int ft5x06_read_reg(int fd, unsigned char reg_addr)
{
unsigned char data = 0;

write(fd, &reg_addr, 1);
read(fd, &data, 1);

printf("reg value is %x\n", data);
return data;
}

/*
* To I2C Device writes register value
* @param fd: I2C Device file handle
* @param reg_addr: Register address to write
* @param data: Data to write
*/
void ft5x06_write_reg(int fd, unsigned char reg_addr, unsigned char data)
{
unsigned char wr_data[2] = { reg_addr, data };
write(fd, wr_data, 2);
}

int main(int argc, char **argv)
{
int fd;

fd = open("/dev/i2c-1", O_RDWR);
if (fd < 0) {
printf("open error\n");
return fd;
}

// Set slave device address to 0x38
ioctl(fd, I2C_SLAVE_FORCE, 0x38);

// Write data 0x66 to register 0x80
ft5x06_write_reg(fd, 0x80, 0x66);

// Read the value of register 0x80
ft5x06_read_reg(fd, 0x80);

return 0;
}

I2C_tools

Compile:

1
2
3
4
5
make CC=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarc
h64-linux-gnu/bin/aarch64-linux-gnu-gcc \
AR=/home/topeet/Linux/linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-lin
ux-gnu/bin/aarch64-linux-gnu-ar \
USE_STATIC_LIB=1

If using Ubuntu or Debian system, simply use the commandsudo apt install i2c-toolsto install

i2cdetect

i2cdetect can be used to detect and probe devices connected to the I2C bus.

  • i2cdetect -V: Output version information

i2cdetect -V
i2cdetect -V

  • i2cdetect -l:List all I2C buses

i2cdetect -l
i2cdetect -l

  • i2cdetect -FQuery the set of functions supported by devices on the bus, for examplei2cdetect -F 1will list the functions supported by devices on bus 1

i2cdetect -F 1
i2cdetect -F 1

  • i2cdetect -aScan all I2C device addresses in the range 0x00 to 0xFF on the bus. For example:i2cdetect -a -y 1will scan all I2C device addresses on the I2C1 bus

image-20251228201405427
image-20251228201405427

Here, 0x38 is the I2C device address of the FT5X06 touch chip

i2cdump

i2cdump can read the values of all registers on the device. The specific usage is as follows:

  • i2cdump -VView version number

i2cdump -V
i2cdump -V

  • i2cdump -f -aRead device registers, usei2cdump -f -a 1 0x38command can read all register values (from 0x00 to 0xFF) of the I2C device with address 0x38.
    • -foption is used to force the use of the device address
    • -aoption is used to read the entire address range.

i2cdump -f -a 1 0x38
i2cdump -f -a 1 0x38

  • i2cdump -f -rSpecify a register range to read, usei2cdump -f -r 0x80-0xff 1 0x38The command can read only the register values in the range 0x80 to 0xff of the I2C device with address 0x38. The -r option is used to specify the register address range to read.

i2cdump -f -r
i2cdump -f -r

i2cset

The i2cset command is used to write data to a specific register of an I2C device. Its usage is as follows:

1
i2cset -f -r 1 0x38 0x80 0x11

This command means:

  • Forcefully use device address 0x38 on I2C bus 1
  • Write the value 0x11 to register address 0x80 of the device

After writing, the command returns a confirmation message indicating whether the write was successful. If the write fails, it returns an error message.

i2cget

The i2cget command is used to read data from a specified register of an I2C device. Its usage is as follows:

1
i2cget -f 1 0x38 0x80

This command means:

  • Forcefully use device address 0x38 on I2C bus 1
  • Read data from register address 0x80 of the device

This command returns the value of register 0x80. Upon successful reading, it displays a hexadecimal value like 0x11. If the read fails, it returns an error message.

i2ctransfer

i2ctransfer is a more powerful and flexible I2C operation tool. Compared to the previously introduced i2cset and i2cget commands, it can perform read and write operations in a single command. The specific usage is as follows:
Write operation

1
i2ctransfer 1 w2@0x38 0x80 0x22
  • 1Indicates that the I2C bus number for the operation is 1
  • w2Indicates writing 2 bytes of data
  • @0x38Indicates that the device address is 0x38
  • 0x80Indicates that the register address to be written is 0x80
  • 0x22Indicates that the value to be written to the register is 0x22

Read operation

1
i2ctransfer 1 w1@0x38 0x80 r1
  • 1Indicates that the I2C bus number for the operation is 1
  • w1Indicates writing 1 byte of data
  • @0x38Indicates that the device address is 0x38
  • 0x80Indicates that the register address to be written is 0x80
  • r1Indicates reading 1 byte of data

Software I2C

Using GPIO to simulate I2C driver

Since software I2C is to be used, the hardware I2C1 must be disabled in the device tree. In the device tree, set theft5x06andi2c1’sstatusboth todisabled

At this point, the two multiplexed pins GPIO0 B3 and GPIO0 B4 of I2C1 will be set to the default GPIO function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/jiffies.h>

// Define the GPIO pin numbers corresponding to the clock line and data line of the I2C bus.
#define I2C_SCL 11
#define I2C_SDA 12

// Declare two GPIO descriptor variables to store the descriptors for the SCL and SDA pins.
struct gpio_desc *i2c_scl_desc;
struct gpio_desc *i2c_sda_desc;

// I2C start condition function
void i2c_start(void)
{
// Set the SCL and SDA pins to output mode and initialize them to high level.
// This is the idle state of the I2C bus.
gpiod_direction_output(i2c_scl_desc, 1);
gpiod_direction_output(i2c_sda_desc, 1);
mdelay(1); // Delay for 1 millisecond.

// Set the SDA pin to low level while keeping SCL at high level.
// This will generate the start condition of the I2C bus.
gpiod_direction_output(i2c_sda_desc, 0);
mdelay(1); // Delay for 1 millisecond.

// Set the SCL pin to low level.
// The start condition is established.
gpiod_direction_output(i2c_scl_desc, 0);
mdelay(1); // Delay for 1 millisecond.
}

// I2C stop condition function
void i2c_stop(void)
{
// Set the SCL and SDA pins to low level.
gpiod_direction_output(i2c_scl_desc, 0);
gpiod_direction_output(i2c_sda_desc, 0);
mdelay(1); // Delay 1 millisecond

// Set the SCL pin to high level
gpiod_direction_output(i2c_scl_desc, 1);
mdelay(1); // Delay 1 millisecond

// Set the SDA pin to high level
// This will generate the I2C bus stop condition
gpiod_direction_output(i2c_sda_desc, 1);
mdelay(1); // Delay 1 millisecond
}

// Send ACK signal
void i2c_send_ack(int ack)
{
// Set SDA line to output mode
gpiod_direction_output(i2c_sda_desc, 0);

if (ack) {
// Send ACK signal, pull SDA line low
gpiod_direction_output(i2c_sda_desc, 0);
} else {
// Send NACK signal, pull SDA line high
gpiod_direction_output(i2c_sda_desc, 1);
}

// Pull SCL line high for 1ms, then pull low
gpiod_direction_output(i2c_scl_desc, 1);
mdelay(1);
gpiod_direction_output(i2c_scl_desc, 0);
}

// Receive ACK signal
int i2c_recv_ack(void)
{
int value = 0;

// Set SDA line to input mode
gpiod_direction_input(i2c_sda_desc);

// Pull SCL line high for 1ms
gpiod_direction_output(i2c_scl_desc, 1);
mdelay(1);

// Read the level state of the SDA line
if (gpiod_get_value(i2c_sda_desc)) {
value = 1; // Received NACK signal
} else {
value = 0; // Received ACK signal
}

// Pull SCL line low
gpiod_direction_output(i2c_scl_desc, 0);

// Set SDA line to output mode and pull high
gpiod_direction_output(i2c_sda_desc, 1);

return value;
}

void i2c_send_data(int data)
{
int i;
int value;

// Set SCL line to output mode and pull low
gpiod_direction_output(i2c_scl_desc, 0);

// Send 8-bit data
for (i = 0; i < 8; i++) {
// Get current bit value
value = (data << i) & 0x80;

// Set SDA line according to current bit value
if (value) {
gpiod_direction_output(i2c_sda_desc, 1);
} else {
gpiod_direction_output(i2c_sda_desc, 0);
}

// Pull SCL line high for 1ms, then pull low
gpiod_direction_output(i2c_scl_desc, 1);
mdelay(1);
gpiod_direction_output(i2c_scl_desc, 0);
mdelay(1);
}
}

int i2c_recv_data(void)
{
int i;
int temp = 0;
int data = 0;

// Set SDA line to input mode
gpiod_direction_input(i2c_sda_desc);
mdelay(1);

// Receive 8-bit data
for (i = 0; i < 8; i++) {
// Pull SCL line low for 1ms
gpiod_direction_output(i2c_scl_desc, 0);
mdelay(1);

// Pull SCL line high for 1ms
gpiod_direction_output(i2c_scl_desc, 1);
mdelay(1);

// Read level state of SDA line
data = gpiod_get_value(i2c_sda_desc);

// Update received data according to current bit value
if (data) {
temp = (temp << 1) | data;
} else {
temp = (temp << 1) & ~data;
}
}

// Pull SCL line low
gpiod_direction_output(i2c_scl_desc, 0);
mdelay(1);

// Set SDA line to output mode and pull high
gpiod_direction_output(i2c_sda_desc, 1);

return temp;
}

// ft5x06 touch screen write register function
void ft5x06_write_reg(int addr, int reg, int value)
{
int ack;

// Start I2C communication
i2c_start();

// Send touch screen device address (write operation)
i2c_send_data(addr << 1 | 0x00);
ack = i2c_recv_ack();
if (ack) {
printk("send write + addr error\n");
goto end;
}

// Send register address
i2c_send_data(reg);
ack = i2c_recv_ack();
if (ack) {
printk("send reg error\n");
goto end;
}

// Send value to be written
i2c_send_data(value);
ack = i2c_recv_ack();
if (ack) {
printk("send value error\n");
}

end:
// End I2C communication
i2c_stop();
}

// ft5x06 touch screen read register function
int ft5x06_read_reg(int addr, int reg)
{
int ack = 0;
int data = 0;

// Start I2C communication
i2c_start();

// Send touch screen device address (write operation)
i2c_send_data(addr << 1 | 0x00);
ack = i2c_recv_ack();
if (ack) {
printk("send write + addr error\n");
goto end;
}

// Send register address to be read
i2c_send_data(reg);
ack = i2c_recv_ack();
if (ack) {
printk("send reg error\n");
goto end;
}

// Restart I2C communication, send read operation address
i2c_start();
i2c_send_data(addr << 1 | 0x01);
ack = i2c_recv_ack();
if (ack) {
printk("send read + addr error\n");
goto end;
}

// Read register value
data = i2c_recv_data();
printk("data is %d\n", data);

// Send ACK to end read operation
i2c_send_ack(0);

end:
// End I2C communication
i2c_stop();

return data;
}

static int __init ft5x06_soft_i2c_init(void)
{
// Convert GPIO number to GPIO descriptor
i2c_scl_desc = gpio_to_desc(I2C_SCL);
if (i2c_scl_desc == NULL) {
printk("gpio_to_desc error for SCL pin\n");
return -1;
}

i2c_sda_desc = gpio_to_desc(I2C_SDA);
if (i2c_sda_desc == NULL) {
printk("gpio_to_desc error for SDA pin\n");
return -1;
}

// Set GPIO pin to output mode and initialize to high level
// This is the idle state of the I2C bus
gpiod_direction_output(i2c_scl_desc, 1);
gpiod_direction_output(i2c_sda_desc, 1);

ft5x06_write_reg(0x38, 0x80, 0x33);
ft5x06_read_reg(0x38, 0x80);

return 0;
}

static void __exit ft5x06_soft_i2c_exit(void)
{
// Release GPIO descriptor
gpiod_put(i2c_scl_desc);
gpiod_put(i2c_sda_desc);
}

module_init(ft5x06_soft_i2c_init);
module_exit(ft5x06_soft_i2c_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629 <asqwgo@outlook.com>");
MODULE_DESCRIPTION("ft5x06 software i2c emulator");

Use the Linux default simulated I2C program

1
2
3
4
5
6
7
8
9
10
11
export ARCH=arm64
make rockchip_linux_defconfig
make menuconfig

> Device Drivers
> I2C support
> I2C Hardware Bus support
<*> GPIO-based bitbanging I2C


cp .config arch/arm64/configs/rockchip_linux_defconfig

Device tree modification

1
2
3
4
5
6
7
8
9
i2c6:i2c6@gpio {
compatible = "i2c-gpio";
#address-cells = <1>;
#size-cells = <0>;
gpios = <&gpi00 RK PB4 GPIO ACTIVE_HIGH>;
<&gpi00 RK PB3 GPIO ACTIVE_HIGH>;
i2c-gpio,delay-us = <5>;
status = "disabled";
};

Append to the I2C6 node, adding content related to the FT5X06 touch chip as follows:

1
2
3
4
5
6
7
&i2c6 {
status = "okay";
myft5x06: my-ft5x06@38 {
compatible = "my-ft5x06";
reg = <0x38>;
};
};

Note that since the previously written device tree node is also named myft5x06, a naming conflict will occur, so the previously written myft5x06 device tree node needs to be commented out

Write the driver as follows:

Removed GPIO-related content from the original driver code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>

struct ft5x06_drv_data {
struct i2c_client *ft5x06_client;
};

int ft5x06_read_reg(struct i2c_client *client, u8 reg_addr)
{
u8 data;
// Define two i2c_msg structures, representing write and read operations respectively
struct i2c_msg msgs[2] = {
[0] = {
.addr = client->addr, // Device address to read from
.flags = 0, // Write operation
.len = sizeof(reg_addr),
.buf = &reg_addr, // Write the register address to be read
},
[1] = {
.addr = client->addr,
.flags = I2C_M_RD, // Read operation
.len = sizeof(data),
.buf = &data,
},
};

// Use the i2c_transfer function to perform an I2C bus read operation
if (i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)) != ARRAY_SIZE(msgs))
return -EIO;

return data;
}

int ft5x06_write_reg(struct i2c_client *client, u8 reg_addr, u8 data)
{
u8 buf[2] = {
reg_addr,
data,
};

struct i2c_msg msgs[1] = {
[0] = {
.addr = client->addr, // Device address to write to
.flags = 0, // Write operation
.len = ARRAY_SIZE(buf),
.buf = buf, // Write the register address to be read
},
};

if (i2c_transfer(client->adapter, msgs, 1) != ARRAY_SIZE(msgs))
return -EIO;

return 0;
}


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

struct ft5x06_drv_data *drv_data;
struct device *dev = &client->dev;


drv_data = devm_kzalloc(dev, sizeof(struct ft5x06_drv_data), GFP_KERNEL);
if (!drv_data)
return -ENOMEM;

i2c_set_clientdata(client, drv_data);

// Save i2c_client
drv_data->ft5x06_client = client;

return 0;
}

int ft5x06_remove(struct i2c_client *client)
{
// struct ft5x06_drv_data *drv_data = i2c_get_clientdata(client);

return 0;
}

struct i2c_device_id ft5x06_match_table[] = { { .name = "my-ft5x06" }, {} };
MODULE_DEVICE_TABLE(i2c, ft5x06_match_table);

static const struct of_device_id ft5x06_of_match_table[] = { { .compatible = "my-ft5x06" },
{} };
MODULE_DEVICE_TABLE(of, ft5x06_of_match_table);

struct i2c_driver ft5x06_drver = {
.driver = {
.name = "my-ft5x06",
.owner = THIS_MODULE,
.of_match_table = ft5x06_of_match_table,
},
.probe = ft5x06_probe,
.remove = ft5x06_remove,
.id_table = ft5x06_match_table,
};

module_i2c_driver(ft5x06_drver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629 <asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is test sample for ft5x06");

SMBus

SMBus (System Management Bus) is a serial bus protocol based on the I2C bus, released by Intel in 1995. It was originally designed to connect smart batteries and other system management devices within computer systems.

SMBus is very similar to the I2C bus; both use two-wire serial communication. SMBus uses SMBDAT and SMBCLK as the data and clock lines, which are very similar to I2C’s SDA and SCL, as shown in the following figure:

SMBus
SMBus

SMBus features

The main features of SMBus are as follows:

Electrical characteristics:

  • Uses open-drain output, requiring external pull-up resistors
  • Voltage range: 0V to 5.5V
  • Maximum clock frequency: 100kHz

Communication Protocol:

  • Master-slave communication, one master controls multiple slaves
  • Address space: 7-bit or 10-bit
  • Supports read/write operations
  • Supports block transfer and byte transfer
  • Supports multiple transaction types, such as quick command, write byte, read byte, etc.

Functional Features:

  • Simple, low-cost, low-power
  • Aimed at system management applications, such as power management, temperature monitoring, etc.
  • Highly compatible with I2C, can reuse I2C hardware

Timing Characteristics:

  • Start and Stop conditions are the same as I2C
  • Address and data transfer timing is also similar to I2C
  • But there are some special timings, such as quick command, block transfer, etc.

Differences between SMBus and I2C

  • Speed Range:
    I2C supports a speed range from 10kHz to 3.4MHz, covering a wider range of application scenarios. SMBus, on the other hand, only supports a speed range from 10kHz to 100kHz, primarily targeting low-speed system management applications.
  • ACK Response:
    I2C does not mandate that the slave send an ACK response, which improves flexibility. However, if the slave does not respond, the master may generate an error. SMBus requires the slave to send an ACK response, ensuring the master can detect the slave’s presence and avoid misoperations.
  • Time Limit:
    SMBus specifies that the slave cannot hold the SCL line low for more than 35ms, otherwise the ongoing communication will be reset. I2C has no such time limit, and the master and slave can control the SCL line state autonomously.
  • Other Differences:
    SMBus has some commands and transaction types specifically designed for system management, such as quick commands and block transfers. SMBus has a smaller address space compared to I2C, supporting only 7-bit or 10-bit addresses. SMBus also has some differences in electrical characteristics, such as voltage range.

SMBus Bus Software Implementation

In the Linux kernel,I2C and SMBus share a common bus architecture,managed and abstracted through the i2c-core subsystem, in thei2c.hheader file, it is defined in the i2c_algorithmstructure, with the specific content as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 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.
* @master_xfer_atomic: same as @master_xfer. Yet, only using atomic context
* so e.g. PMICs can be accessed very late before shutdown. Optional.
* @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this
* is not present, then the bus layer will try and convert the SMBus calls
* into I2C transfers instead.
* @smbus_xfer_atomic: same as @smbus_xfer. Yet, only using atomic context
* so e.g. PMICs can be accessed very late before shutdown. Optional.
* @functionality: Return the flags that this algorithm/adapter pair supports
* from the ``I2C_FUNC_*`` flags.
* @reg_slave: Register given client to I2C slave mode of this adapter
* @unreg_slave: Unregister given client from I2C slave mode of this adapter
*
* The following structs are for those who like to implement new bus drivers:
* i2c_algorithm is the interface to a class of hardware solutions which can
* be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
* to name two of the most common.
*
* The return codes from the ``master_xfer{_atomic}`` fields should indicate the
* type of error code that occurred during the transfer, as documented in the
* Kernel Documentation file Documentation/i2c/fault-codes.rst.
*/
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 (*master_xfer_atomic)(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);
int (*smbus_xfer_atomic)(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 *adap);

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

Among them, the smbus_xfer function is used to implement some SMBus-specific operations, such as quick commands, write byte, read byte, etc.

When the I2C controller works in SMBus mode, it executes SMBus special transactions through the smbus_xfer function.

SMBus Bus API Functions

  1. i2c_smbus_read_byte(const struct i2c_client *client)
    • SendSMBus Read Byteprotocol.
    • Do not send the register address; directly read the data byte currently pointed to by the device.
    • Applicable to: devices that support auto-incrementing addresses (such as continuous reading of certain EEPROMs), or the address pointer has been set previously by other means.
    • ⚠️ Most modern I2C devicesdo not supportthis addressless read, soit is rarely used
  2. i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
    • SendSMBus Send Byteprotocol.
    • Send only one byte (valueas a command),No data phase
    • Note: Thevalueis treated ascommand, not as the data to be written!
    • Commonly used to trigger device actions (such as reset, start conversion),not for writing register values
    • ❗Easily misunderstood! If you want to ‘write a value to a register’, use thei2c_smbus_write_byte_data
  3. i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
    • SendSMBus Read Byte Dataprotocol.
    • First sendcommand(register address), then read back 1 byte of data.
    • The most common way to read a register
  4. i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value)
    • SendSMBus Write Byte Dataprotocol.
    • Sendcommand(register address) +value(data to be written).
    • The most common way to write to a register