Read chip registers (including i2c-tools) through I2C on Android phones

Keywords: Linux Android Makefile Mobile

Read chip registers (including i2c-tools) through I2C on Android phones

tags: Android Linux i2c driver

Requirement: Colleagues dump a brand of mobile phone, Android system, need to read out the register configuration parameters of a chip inside.

Tossing for two days (pit daddy's network and... xxx wall), record it for future queries

Train of thought:

  • Running a C language program under Android
  • Access i2c bus with this c language program
  • Using linux i2c bus driver to access the chip directly and read out the content

1 Running C Language Program under Android

Running C language program under android is actually configuring a cross-compiler environment, compiling C language program with the correct compiler, and then running it in the mobile phone. If there is linux foundation, this should not be difficult, the key is to find a suitable compiler.
The compiler used here is arm-none-linux-gnueabi-gcc to compile. I don't know if android has a special library for linux kernel or why. Everyone searched on the Internet uses this compiler. Generally, arm-linux-gcc is used when cross-compiling is configured under linux. I think this compiler should be OK. I wanted to try it, but apt-get did not come down... (Ten thousand words are omitted here... In a few days, I'll update it again after I've matched the arm-linux-gcc environment to verify it.

One more thing I forgot to mention here is that first you have to remove the root of your mobile phone and then configure the adb shell correctly on your computer. If there are too many random searches on the internet, you can do that.

Configuration of cross-compiler environment and running programs on mobile phones have been written in this link in great detail, here is no repetition, give the link directly.
Compile Environment Configuration Reference Link

It's better to configure arm-none-linux-gnueabi-gcc under Linux system. It seems that there is also a windows version of arm-none-linux-gnueabi-gcc, but it seems not very useful... linux bash, which comes with win10, is strongly recommended here. It is much better than virtual machine in embedded development.

Here are two points to note: 1. Files can not be run on sdcard, they should be copied to the bottom of the system directory; 2. When compiling, add the - static compilation option.

OK, now there is a C program that can run under Android.

2 Access i2c dev

First of all, we need to find out where your target device is mounted. If you are familiar with linux fs, it should be easy to solve this problem. Here, you can borrow a description from others, which has been written in more detail. In fact, there are other ways to see it. Here is not a list.
Device Query Method Reference Link

After finding the target device, use the old method of Linux devices, open, close, ioctrl, read, write to go up to ~

Generally speaking, the first step is open dev.

#define CHIP "/dev/i2c-xxx" 

int fd = open(CHIP,O_RDWR);  
if(-1 == fd) {
    printf("open faild \r\n");
    return 0;
}

printf("open success fd = 0x%08x \r\n", fd);

Here my device is mounted on i2c-2.

If this step succeeds, there will be basically no big problem

Next is reading and writing:
i2c can be read and written in two ways, ioctrl and read and write, but these two ways finally call the same function in linux kernel:

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

Interested students can download a linux kernel to see, if not interested, you can directly ignore.
First, read and write.

    unsigned char sendbuf[1] = { REG_CHIP_ID };
    unsigned char readbuf[1] = { 0x00 };

    if (ioctl(fd, I2C_SLAVE_FORCE, CHIP_ADDR_0) < 0) {         /* Setting Chip Address */         
      printf("oictl:set slave address failed\n");     
      goto __end;   
    }
    printf("oictl:set slave address success \r\n");

    if(write(fd,sendbuf,1) < 0) {
        printf("write failed \r\n");
        goto __end;
    }
    printf("write success 1 \r\n");

    if((ret = read(fd, readbuf, 1)) < 0) {
        printf("read failed \r\n");
        goto __end;
    }
    printf("readbuf = 0x%02x , ret = %d \r\n", readbuf[0], ret);

Let's not get bogged down about what the code structure is... This is just to read the data, not the code used in the project, so... Just look at the meaning...
The code here is to read a register where CHIP_ADDR_0 represents the i2c address of the device and REG_CHIP_ID represents the address of the register.
The first step is to set the client's address and call the ioctl(fd, I2C_SLAVE_FORCE, CHIP_ADDR_0) function.
The meaning of I2C_SLAVE_FORCE is explained in linux kernel as follows:

#define I2C_SLAVE   0x0703  /* Use this slave address */
#define I2C_SLAVE_FORCE 0x0706  /* Use this slave address, even if it
                   is already in use by a driver! */

This means that if the device is already occupied (other processes are already using the device), you can't set the client with I2C_SLAVE. If you set the client with I2C_SLAVE_FORCE, you can set it successfully anyway (provided your configuration parameters are correct...).
What does client do here? Used in read and write:
General i2c reading sequence: sending device address, RW location 0 - > sending register address - > sending device address, RW location 1 - > receiving returned data.
But here you can see that the actual read code only writes a register address, and then reads. We do not display the sending device address and RW bit. In fact, these tasks are done by the write and read functions for us. In the write and read functions, we send the address value of the client plus RW bit directly, so we only use the data sheet based on the chip. Write and read require data to be sent and received.

/**
 * 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.
 */
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
    int ret;
    struct i2c_adapter *adap = client->adapter;
    struct i2c_msg msg;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.len = count;
    msg.buf = (char *)buf;

    ret = i2c_transfer(adap, &msg, 1);

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

This is the source code of the function called by write in linux kernel. Interested students will see how the i2c address and RW bit of the device are filled in. read is not posted...

OK, write and read are finished, and then I'm going to read and write i2c through ioctrl.

    unsigned char outbuf[1];  
    struct i2c_rdwr_ioctl_data packets;  
    struct i2c_msg messages[2]; 

    outbuf[0] = REG_CHIP_ID;  

    messages[0].addr  = CHIP_ADDR_0;  
    messages[0].flags = 0;  
    messages[0].len   = 1;  
    messages[0].buf   = outbuf;  

    /* The data will get returned in this structure */  
    messages[1].addr  = CHIP_ADDR_0;  
    messages[1].flags = 1/* | I2C_M_NOSTART*/;  
    messages[1].len   = 1;  
    messages[1].buf   = outbuf;  

    /* Send the request to the kernel and get the result back */  
    packets.msgs      = messages;  
    packets.nmsgs     = 2;

    if((ret = ioctl(fd, I2C_RDWR, &packets)) < 0) {  
        printf("Unable to send data , ret = %d \r\n", ret);  
        close(fd);
        return -1;  
    }

    printf("outbuf = 0x%02x \r\n", outbuf[0]);

What are the messages and packets in this article? I won't start here. In fact, we can compare them with those in the kernel s posted above.

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

Functions, in fact, are filled in first

    struct i2c_adapter *adap = client->adapter;
    struct i2c_msg msg;

So two structures, and then one call

ret = i2c_transfer(adap, &msg, 1);

Another call

ret = ioctl(fd, I2C_RDWR, &packets)

Complete the final data transmission.
In fact, i2c_transfer (adap, & msg, 1) is also called in IOCTL (fd, I2C_RDWR, & packets), so the function that really completes the final data transmission should be:

i2c_transfer(adap, &msg, 1)

As for the behavior of i2c_transfer, interested students continue to see linux kernel. He calls master_xfer function, which is to write specific driver methods for I2C controllers of different chips and assign master_xfer function by registration. More specifically, this is not going to happen anymore.

3 Read the required data

That's all. Encapsulate the previous read, write, or ioctrl, then find the register to read and write according to the data sheet, and do the operation.

Finally... Don't forget close ()...

Stick a complete code.

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h> 
#include <fcntl.h>
#include <unistd.h>

#include <linux/i2c-dev.h> 
#include <linux/i2c.h> 

#define CHIP "/dev/i2c-xx" 
#define CHIP_ADDR_0 (0xxx)
#define CHIP_ADDR_1 (0xxx)
#define CHIP_ADDR_2 (0xxx)
#define CHIP_ADDR_3 (0xxx)

#define REG_CHIP_ID 0x00

int main() {

    int fd = open(CHIP,O_RDWR);  
    if(-1 == fd) {
        printf("open faild \r\n");
        return -1;
    }

    printf("open success fd = 0x%08x \r\n", fd);

    if (ioctl(fd, I2C_SLAVE_FORCE, CHIP_ADDR_0) < 0) 
    {         /* Setting Chip Address */         
      printf("oictl:set slave address failed\n");     

      close(fd);
        return -1;     
    }

    printf("oictl:set slave address success \r\n");

    int ret = 0;

#if 0

    unsigned char outbuf[1];  
    struct i2c_rdwr_ioctl_data packets;  
    struct i2c_msg messages[2]; 

    outbuf[0] = REG_CHIP_ID;  

    messages[0].addr  = CHIP_ADDR_0;  
    messages[0].flags = 0;  
    messages[0].len   = 1;  
    messages[0].buf   = outbuf;  

    /* The data will get returned in this structure */  
    messages[1].addr  = CHIP_ADDR_0;  
    messages[1].flags = 1/* | I2C_M_NOSTART*/;  
    messages[1].len   = 1;  
    messages[1].buf   = outbuf;  

    /* Send the request to the kernel and get the result back */  
    packets.msgs      = messages;  
    packets.nmsgs     = 2;

    if((ret = ioctl(fd, I2C_RDWR, &packets)) < 0) {  
        printf("Unable to send data , ret = %d \r\n", ret);  
        close(fd);
        return -1;  
    }

    printf("outbuf = 0x%02x \r\n", outbuf[0]);

#else

    unsigned char sendbuf[1] = { REG_CHIP_ID };
    unsigned char readbuf[1] = { 0x00 };

    if(write(fd,sendbuf,1) < 0) {
        printf("write failed \r\n");
        goto __end;
    }
    printf("write success 1 \r\n");

    if((ret = read(fd, readbuf, 1)) < 0) {
        printf("read failed \r\n");
        goto __end;
    }

    printf("readbuf = 0x%02x , ret = %d \r\n", readbuf[0], ret);

#endif

__end :

    close(fd);

    return 0;
}

In order to avoid exposing what you are reading, all the addresses involved are replaced by xxx... Then you can write a makefile or compile it directly with commands. Makefile is not pasted. Makefile is ugly to write. Include this code and see what it means. It's not for engineering use. Don't tangle with coding patterns, coding specifications, coding ideas and code structure...

OK~So much, all of these are wild roads... Let's talk about an official practice. (I know that some of my classmates here want to kill...)

Website: i2c-tools
Go here and download an i2c-tools, and then... Use arm-none-linux-gnueabi-gcc compiler to compile the execution file, and then download it to the mobile phone in the same way, run...

How does this thing work? I see someone posted on the Internet about how to use i2c-tools under android. It's written very well, but I don't need it!!... Why? Because I have no Android source code, no Android development environment... What should I do? Life has to go on...

Well, here's a simple way to say it. People who understand understand understand understand naturally. People who don't understand try to understand it.
In fact, i2c-tools is a C-language file, adding a makefile can be compiled and run the same way as before. There's actually one in the makefile source code here, but to change it, the key is to set the compiler to arm-none-linux-gnueabi-gcc. But I didn't do that, because I only need one command in it, so... I wrote a makefile myself and put it in my cell phone to run. Makefile, I don't post it. It's really ugly. Open the source code to see how makefile should be written...

Okay, it's purple and purple...

Yes... Finally, there's one point ~very, very important ~!!!!! Having been troubled by this problem all morning... The device open, ioctrl is all right, but it can't read anything alive or dead, and it all goes back to - 1...
Reason: The chip is dormant... Because of the power management in the cell phone, when there is nothing wrong with a chip, the chip will be dormant, so if it is for the cell phone, we must pay attention to this problem, do not think that the cell phone turned on the film on the power...

Well, over...

Posted by christofurr on Thu, 20 Dec 2018 06:42:04 -0800