I. Overview
Speaking of writing I2C drivers in Linux, there are currently two main methods: one is to treat I2C devices as a common character device, the other is to use the Linux I2C driver architecture. The two drivers are compared below.
The advantages of the first method (corresponding to the disadvantages of the second method) include:
● The idea is straightforward and you don't need to take the time to understand the complex I2C subsystem operation methods in the Linux kernel.
The first method problems (corresponding to the advantages of the second method) include:
● Require engineers not only familiar with I2C device operations, but also with I2C adapter operations;
● Engineers are required to be familiar with I2C devices and I2C device operation methods. The most important thing is the poor portability of written programs;
● Kernel resources cannot be directly used. Because all I2C devices and device drivers provided by the kernel are based on the I2C subsystem format. The I2C adapter is easy to operate. If you encounter complicated I2C adapters (such as PCI-based I2C adapters), the workload will be much larger.
This article targets developers who are familiar with I2C protocol and want to use Linux kernel subsystems.
The source code structure of the I2C subsystem is introduced on the Internet and some books. However, after reading these articles, many developers still do not know what to do. The reason is that we still haven't figured out what the I2C subsystem has done for us and how we use the I2C subsystem. This article first solves how to use the I2C adapter supported by the existing kernel to complete the operations on the I2C device, and then overwrite the adapter code. This article focuses on solving the problem and does not involve detailed code tracking.
Ii. Compilation of I2C Device Drivers
First, we need to clarify that the role of the Adapter driver is to allow us to send out time sequences that comply with the I2C standard protocol through it.
The drivers/I2C/busses directory in the Linux kernel source code contains some adapter drivers. Such as S3C2410 driver i2c-s3c2410.c. After the adapter is loaded to the kernel, the next step is to write a device driver for a specific device.
There are also two methods to compile the I2C device driver.One typeIs a device file that uses the i2c-dev.c provided by the system to implement an I2C adapter. Then, the I2C device is controlled by operating the I2C adapter at the application layer.AnotherIs a device driver for I2C devices. Note: In the latter case, i2c-dev.c is not required.
1. Use i2c-dev.c operation adapter to control I2C Device
I2c-dev.c is not designed for specific devices, but provides general interfaces such as read (), write () and IOCTL, the application layer can use these interfaces to access the storage space or registers of I2C devices attached to the adapter and control how the I2C devices work.
Note that the read () and write () Methods of the i2c-dev.c are only applicable to the following data formats (you can view the kernel-related source code)
Figure 1 sequence of single start signals
Therefore, it is not too general. For example, the following situations do not apply (usually when reading a target ).
Figure 2 Multi-start signal timing
The read () and write () methods are only applicable when the adapter supports I2C algorithms, for example:
Static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
. Master_xfer = s3c24xx_i2c_xfer,
. Functionality = s3c24xx_i2c_func,
};
This method is not suitable when the adapter only supports the SMBus algorithm, for example:
Static const struct i2c_algorithm smbus_algorithm = {
. Smbus_xfer = i801_access,
. Functionality = i801_func,
};
Based on the above reasons, so generally do not use the i2c-dev.c of the read (), write () method. The most common method is IOCTL. The IOCTL () method can implement all the above situations (two data formats, I2C algorithm and SMBus algorithm ).
For I2C algorithms, you need to be familiar with struct i2c_rdwr_ioctl_data and struct i2c_msg. The command used is i2c_rdwr.
Struct i2c_rdwr_ioctl_data {
Struct i2c_msg _ User * msgs;/* pointers to i2c_msgs */
_ U32 nmsgs;/* Number of i2c_msgs */
};
Struct i2c_msg {
_ 2010addr;/* slave address */
_ Flags;/* flag (read and write )*/
_ 2010len;/* Msg Length */
_ U8 * Buf;/* pointer to MSG data */
};
For SMBus algorithms, you need to be familiar with struct i2c_smbus_ioctl_data. The command used is i2c_smbus. The "Multi-start signal timing" issue does not need to be considered for SMBus algorithms.
Struct i2c_smbus_ioctl_data {
_ U8 read_write; // read and write
_ U8 command; // command
_ U32 size; // Data Length identifier
Union i2c_smbus_data _ User * data; // data
};
The following example describes the specific operation process. Operate at24c02 E2PROM through S3C2410. Read and Write Data anywhere in at24c02.
First, the kernel contains the support for the I2C controller driver in S3C2410. Provides the I2C algorithm (non-SMBus type, so the subsequent IOCTL command is i2c_rdwr)
Static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
. Master_xfer = s3c24xx_i2c_xfer,
. Functionality = s3c24xx_i2c_func,
};
On the other hand, it is necessary to determine the address and read/write access time sequence of at24c02 in order to perform operations on at24c02 E2PROM.
● At24c02 address Determination
The schematics ground A2, A1, and A0, so the address is 0x50.
● At24c02 time series written by any address byte
It can be seen that the order is consistent with the "single start signal timing" mentioned above"
● At24c02 any address byte read Time Sequence
It can be seen that the order is consistent with the "Multi-start signal timing" mentioned above"
The following code is analyzed (the Code passes the test on the 2.6.22 kernel ):
/* I2c_test.c
* Hongtao_liu <lht@farsight.com.cn>
*/
# Include <stdio. h>
# Include <Linux/types. h>
# Include <stdlib. h>
# Include <fcntl. h>
# Include <unistd. h>
# Include <sys/types. h>
# Include <sys/IOCTL. h>
# Include <errno. h>
# Define i2c_retries 0x0701
# Define i2c_timeout 0x0702
# Define i2c_rdwr 0x0707
/******** Define struct i2c_rdwr_ioctl_data and struct i2c_msg, which must be consistent with the kernel *******/
Struct i2c_msg
{
Unsigned short ADDR;
Unsigned short flags;
# Define i2c_m_ten 0x0010
# Define i2c_m_rd 0x0001
Unsigned short Len;
Unsigned char * Buf;
};
Struct i2c_rdwr_ioctl_data
{
Struct i2c_msg * msgs;
Int nmsgs;
/* The number of nmsgs determines the number of start signals. For "Single Start sequence", 1 */
};
***********/
Int main ()
{
Int FD, RET;
Struct i2c_rdwr_ioctl_data e2prom_data;
FD = open ("/dev/i2c-0", o_rdwr );
/*
*/Dev/i2c-0 is generated after the i2c-dev.c is registered, representing an operable adapter. If you do not use a i2c-dev.c
.
*/
If (FD <0)
{
Perror ("Open error ");
}
E2prom_data.nmsgs = 2;
/*
* Because at most two start signals (in the byte read operation) are used in the operation sequence
* E2prom_data.nmsgs is configured as 2
*/
E2prom_data.msgs = (struct i2c_msg *) malloc (e2prom_data.nmsgs * sizeof (struct i2c_msg ));
If (! E2prom_data.msgs)
{
Perror ("malloc error ");
Exit (1 );
}
IOCTL (FD, i2c_timeout, 1);/* timeout */
IOCTL (FD, i2c_retries, 2);/* repeat times */
/*** Write data to E2PROM **/
E2prom_data.nmsgs = 1;
(E2prom_data.msgs [0]). Len = 2; // One E2PROM writes the destination address and one data
(E2prom_data.msgs [0]). ADDR = 0x50; // E2PROM device address
(E2prom_data.msgs [0]). Flags = 0; // write
(E2prom_data.msgs [0]). Buf = (unsigned char *) malloc (2 );
(E2prom_data.msgs [0]). Buf [0] = 0x10; // E2PROM write Destination Address
(E2prom_data.msgs [0]). Buf [1] = 0x58; // the data to write
Ret = IOCTL (FD, i2c_rdwr, (unsigned long) & e2prom_data );
If (Ret <0)
{
Perror ("IOCTL error1 ");
}
Sleep (1 );
/****** Read data from E2PROM *******/
E2prom_data.nmsgs = 2;
(E2prom_data.msgs [0]). Len = 1; // E2PROM target data address
(E2prom_data.msgs [0]). ADDR = 0x50; // E2PROM device address
(E2prom_data.msgs [0]). Flags = 0; // write
(E2prom_data.msgs [0]). Buf [0] = 0x10; // E2PROM data address
(E2prom_data.msgs [1]). Len = 1; // read data
(E2prom_data.msgs [1]). ADDR = 0x50; // E2PROM device address
(E2prom_data.msgs [1]). Flags = i2c_m_rd; // read
(E2prom_data.msgs [1]). Buf = (unsigned char *) malloc (1); // address for storing the returned value.
(E2prom_data.msgs [1]). Buf [0] = 0; // initialize the read buffer
Ret = IOCTL (FD, i2c_rdwr, (unsigned long) & e2prom_data );
If (Ret <0)
{
Perror ("IOCTL error2 ");
}
Printf ("Buff [0] = % x/N", (e2prom_data.msgs [1]). Buf [0]);
/*** Print the read value. If yes, it should be 0x58 previously written ***/
Close (FD );
Return 0;
}
The above describes a more common method of using i2c-dev.c to operate I2C equipment, this method can be said to be completed at the application layer of the specific I2C device driver work.