Using I2C to Drive PCA9555 Expansion Chip

To further study Linux I2C, after the previous I2C driver for BL24C04, I have prepared the PCA9555/TCA9555 expansion GPIO chip. Its specific function is to extend 16 GPIOs via the I2C bus and it has input interrupt capabilities.
Using I2C to Drive PCA9555 Expansion Chip
1. Add PCA9555 Device Tree Node
According to the PCA9555 circuit diagram above, the 7-bit I2C address of the device is 0X20, so the device tree node content for PCA9555 is as follows:
tca9555@20{    compatible = "tca9555";    reg = <0x20>;};
Complete I2C1 Device Tree Node:
Using I2C to Drive PCA9555 Expansion Chip
2. Write PCA9555 Driver Code
This driver code at least simply implements the output state toggle of the GPIO ports; the input and interrupt functions have not yet been implemented, this part will be added in the next section.
#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/io.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/of.h>#include <linux/of_address.h>#include <linux/of_irq.h>#include <linux/gpio.h>#include <linux/of_gpio.h>#include <linux/string.h>#include <linux/irq.h>#include <linux/interrupt.h>#include <linux/input.h>#include <linux/i2c.h>#include <linux/delay.h>
#include "TCA9555.h"
static const struct of_device_id tca9555_of_match_table[] = {    {.compatible = "tca9555"},    {},};
/* Low-level read function */
int tca9555_read_reg(struct i2c_client *client, u8 reg_addr){    u8 data; // Value of the register, 8 bits
    /* Construct data */    struct i2c_msg msgs[] = {        /* The first segment of data is the device address (write address) */        {            .addr = client->addr,            .flags = 0,              // flags = 0 write = 1 read            .len = sizeof(reg_addr), // Length of data, here it is the length of the register address, 1 byte            .buf = &reg_addr,        // The data to be sent is the reg_addr passed in earlier        },        {            .addr = client->addr,            .flags = 1, // After sending the address, start reading the register value            .len = sizeof(data),            .buf = &data, // Length of data to read is 1 byte, storage location is data        }
    };
    /* Initiate I2C data transfer */    i2c_transfer(client->adapter, msgs, 2); // The number of msgs to send is 2
    return data;}
/* Low-level write function */
void tca9555_write_reg(struct i2c_client *client, u8 reg_addr, u8 data, u16 len){    u8 buffer[256]; // Value of the register, 8 bits
    /* Construct data */    struct i2c_msg msgs;
    buffer[0] = reg_addr;
    memcpy(&buffer[1], &data, len);
    msgs.addr = client->addr;    msgs.flags = 0; // flags = 0 write = 1 read    msgs.len = len + 1;    msgs.buf = buffer; // The data to be sent is the reg_addr passed in earlier
    /* Initiate I2C data transfer */    i2c_transfer(client->adapter, &msgs, 1); // The number of msgs to send is 1}
static int tca9555_probe(struct i2c_client *client, const struct i2c_device_id *id){    int i = 0;
    printk("tca9555_probe!\n");
    /* Configure registers: Configure pins as input or output, 0x06, 0x07 */    tca9555_write_reg(client, 0x06, 0x00, 1);    tca9555_write_reg(client, 0x07, 0x00, 1);
do    {
        /* Output registers: 0x02  0x03 */        tca9555_write_reg(client, 0x02, 0x00, 1);        tca9555_write_reg(client, 0x03, 0x00, 1);
        msleep(800);
        /* Output registers: 0x02  0x03 */        tca9555_write_reg(client, 0x02, 0xff, 1);        tca9555_write_reg(client, 0x03, 0xff, 1);
        msleep(200);
        i++;    } while (i < 65536);
    return 0;}
static int tca9555_remove(struct i2c_client *client){
    return 0;}
static struct i2c_driver tca9555_driver = {    .probe = tca9555_probe,    .remove = tca9555_remove,    .driver = {        .name = "tca9555",        .owner = THIS_MODULE,        .of_match_table = tca9555_of_match_table,    },};
static int __init tca9555_init(void){    int ret = 0;
    printk("tca9555_init\n");
    ret = i2c_add_driver(&tca9555_driver);
    if (ret < 0)        printk("i2c_add_driver is error\n");
    return 0;}
static void __exit tca9555_exit(void){    i2c_del_driver(&tca9555_driver);
    printk("tca9555_init\n");}
module_init(tca9555_init);module_exit(tca9555_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("xx");
3. Experimental Phenomenon
The above driver code only implements a simple toggle of the IO port to output state in the probe function, causing the IO port level to continuously flip, making the LED light blink.
Note: Before setting the GPIO to output, the LED is dimly lit due to the default state of GPIO being input, which has a pull-up resistor.
Using I2C to Drive PCA9555 Expansion Chip
LoadDriver:
Using I2C to Drive PCA9555 Expansion Chip
Blinking Phenomenon:

Leave a Comment