四、GPIO控制器驱动 —— pcf8575

前言

PCF8575 是一个16bit的GPIO扩展芯片,可以支持两组共16个GPIO的控制。但是由于较为简单,16个GPIO只能同时被配置为输入或者输出,不支持对单个GPIO的控制特殊控制。使用I2C通信,拥有一个中断引脚。电压支持2.5~5.5V,连接5V的输入,GPIO输出将支持到3.3V。

I2C控制

GPIO的输入输出配置,没有特殊的寄存器配置。I2C时序要求也比较简单,只能读取两个字节,或者写入两个字节。当读取两个字节时,16个GPIO会在读取时,配置为输入模式;当写入两个字节数据时,16个GPIO会在写入数据时,配置为输出模式,两个字节数据会被设置为16个GPIO的状态。这个操作就已经限制了,我们操作GPIO时,只能将两组设置为相同的功能,比如同时设置为输入,或者同时设置为输出。

GPIO控制器

控制器驱动流程比较简单,在我们驱动结构体中添加 struct gpio_chip 结构。然后再probe中对这个结构进行填充,主要涉及到以下字段的填充:

    pcf8575->chip.label = client->name;
    pcf8575->chip.base = -1;
    pcf8575->chip.parent = &client->dev;
    pcf8575->chip.owner = THIS_MODULE,
    pcf8575->chip.ngpio = GPIO_NUM;
    pcf8575->chip.can_sleep = 1;
    pcf8575->chip.get = pcf8575_get_value,
    pcf8575->chip.set = pcf8575_set_value,
    pcf8575->chip.direction_input = pcf8575_direction_input,
    pcf8575->chip.direction_output = pcf8575_direction_output,

base
用于控制GPIO控制器在系统中生成的GPIO起始序号。设置-1,将有系统自动分配。

[root@rk3399:/sys/class/gpio]# ls
export   gpiochip0    gpiochip32   gpiochip64  unexport
gpio504  gpiochip128  gpiochip496  gpiochip96

这里系统为我们分配到的是 496 。那么我们要导出某个GPIO时,需要再这个基础上偏移 0~15,范围为:496 ~ 511 。
ngpio
这个控制器支持的GPIO数量。
can_sleep
set/get 方法是否能够sleep。
set/get/direction_input/direction_output
这是我们主要使用的方法,必须要填充。

填充完 gpio_chip 的信息后,就能使用 gpiochip_add 进行注册了。推荐使用 devm_xxx 风格的API,在remove时,自动释放资源,不需要我们再添加代码进行管理。

ret = devm_gpiochip_add_data(&client->dev, &pcf8575->chip, NULL);

中断

pcf8575有INT引脚,支持中断输出。当GPIO被配置为输入时,能够使用中断功能。当有GPIO状态跳变时,INT引脚会产生脉冲。我们可以在SoC端连接到INT,使用一个GPIO中断来监测INT,当有中断发生时,我们通过I2C读取16个GPIO的状态,从而找到是哪一个引脚产生的中断信号。这可以使用内核的中断子系统框架来完成驱动代码,从而将控制器配置为内部控制器一样的功能。对于高速GPIO信号,GPIO扩展芯片应该是不适用的。pcf8575在I2C读取期间,如果GPIO发生变化,我们是无法捕获到跳变信息的。因此GPIO的控制速率,受限于I2C的访问速度。这里暂不完成irq_chip的代码,后续单独实现irq_chip的功能。

设备树配置

目前gpio控制器还没有添加中断控制器的功能,因此需要禁掉中断相关的内容。

&i2c4 {
    status = "okay";
    expander0: pcf8575@20 {
        status = "okay";
        compatible = "nxp,pcf8575";
        reg = <0x20>;
        // interrupt-controller;
        // #interrupt-cells = <2>;
        // gpio-controller;
        // #gpio-cells = <2>;
        // interrupt-parent = <&gpio4>;
        // interrupts = <29 IRQ_TYPE_EDGE_FAILLING>;
    };
};

驱动代码

#include "linux/device.h"
#include "linux/gpio/driver.h"
#include 
#include 
#include 

#define GPIO_NUM 16

struct pcf8575_dev {
    struct i2c_client *client;
    struct gpio_chip chip;

    u16 value;
};

static int pcf8575_i2c_read(struct i2c_client *client, u16 *value)
{
    u8 data[2] = {0};
    struct i2c_msg msg[] = {
        {
            .flags = I2C_M_RD | I2C_M_STOP,
            .addr = client->addr,
            .buf = data,
            .len = ARRAY_SIZE(data),
        },
    };

    if (i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)) != ARRAY_SIZE(msg)) {
        dev_err(&client->dev, "i2c read error, chip=0x%02x\n", client->addr);
        return -EREMOTEIO;
    }

    *value = data[0] | (data[1] << 8);
    return 0;
}

static int pcf8575_i2c_write(struct i2c_client *client, u16 value)
{
    u8 data[2] = {(value & 0xFF), ((value >> 8) & 0xFF)};
    struct i2c_msg msg[] = {
        {
            .flags = I2C_M_STOP,
            .addr = client->addr,
            .buf = data,
            .len = ARRAY_SIZE(data),
        },
    };

    if (i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)) != ARRAY_SIZE(msg)) {
        dev_err(&client->dev, "i2c write error, chip=0x%02x\n", client->addr);
        return -EREMOTEIO;
    }

    return 0;
}

static int pcf8575_get_value(struct gpio_chip *chip, unsigned offset)
{
    u16 value = 0;
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);

    if (offset >= GPIO_NUM) {
        dev_err(&pcf8575->client->dev, "offset over %d\n", GPIO_NUM);
        return 0;
    }
    
    pcf8575_i2c_read(pcf8575->client, &value);
    
    return (value & (1 << offset));
}

static void pcf8575_set_value(struct gpio_chip *chip, unsigned offset, int value)
{
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);

    if (offset >= GPIO_NUM) {
        dev_err(&pcf8575->client->dev, "offset over %d\n", GPIO_NUM);
        return ;
    }

    if (value) {
        pcf8575->value |= (1 << offset);
    } else {
        pcf8575->value &= (~(1 << offset));
    }

    pcf8575_i2c_write(pcf8575->client, pcf8575->value);
}

static int pcf8575_direction_input(struct gpio_chip *chip, unsigned offset)
{
    struct pcf8575_dev *pcf8575 = container_of(chip, struct pcf8575_dev, chip);
    pcf8575->value |= (1 << offset);

    pcf8575_i2c_write(pcf8575->client, pcf8575->value);

    return 0;
}

static int pcf8575_direction_output(struct gpio_chip *chip, unsigned offset,
                                    int value)
{
    pcf8575_set_value(chip, offset, value);

    return 0;
}

static int pcf8575_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    struct pcf8575_dev *pcf8575;
    int ret;

    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        dev_err(&client->dev, "i2c_check_functionality not support I2C_FUNC_I2C\n");
        return -EIO;
    }

    pcf8575 = devm_kzalloc(&client->dev, sizeof(struct pcf8575_dev), GFP_KERNEL);
    if (IS_ERR(pcf8575)) {
        dev_err(&client->dev, "kzalloc error: 0x%lx\n", PTR_ERR(pcf8575));
        return PTR_ERR(pcf8575);
    }

    pcf8575->chip.label = client->name;
    pcf8575->chip.base = -1;
    pcf8575->chip.parent = &client->dev;
    pcf8575->chip.owner = THIS_MODULE,
    pcf8575->chip.ngpio = GPIO_NUM;
    pcf8575->chip.can_sleep = 1;
    pcf8575->chip.get = pcf8575_get_value,
    pcf8575->chip.set = pcf8575_set_value,
    pcf8575->chip.direction_input = pcf8575_direction_input,
    pcf8575->chip.direction_output = pcf8575_direction_output,

    /* default value is 0xFFFF after power on */
    pcf8575->value = 0xFFFF;

    i2c_set_clientdata(client, pcf8575);
    pcf8575->client = client;

    ret = devm_gpiochip_add_data(&client->dev, &pcf8575->chip, NULL);
    if (ret) {
        return ret;
    }

    dev_info(&client->dev, "pcf8575 probe success\n");
    return 0;
}

static int pcf8575_remove(struct i2c_client *client)
{
    return 0;
}

static struct i2c_device_id pcf8575_id_table[] = {
    {"pcf8575", 0},
    {},
};
MODULE_DEVICE_TABLE(i2c, pcf8575_id_table);

static const struct of_device_id pcf8575_of_match[] = {
    {.compatible = "nxp,pcf8575"},
    { },
};
MODULE_DEVICE_TABLE(of, pcf8575_of_match);

static struct i2c_driver pcf8575_driver = {
    .probe = pcf8575_probe,
    .remove = pcf8575_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "pcf8575_drv",
        .of_match_table = of_match_ptr(pcf8575_of_match),
    },
    .id_table = pcf8575_id_table,
};

module_i2c_driver(pcf8575_driver);

MODULE_AUTHOR("duapple ");
MODULE_LICENSE("GPL");

验证

导出P10引脚:

cd /sys/class/gpio
echo 504 > export

配置输出:

cd gpio504
echo out > direction

配置输出值:

cd gpio504
echo 1 > value
echo 0 > value

使用万用表测量设置1值和0值的具体输出数值,为3.3V和0V。
配置输入:

echo in > direction

将P10分别接到3.3V,GND和悬空,读取 value 值:

cat value

3.3V:value = 1
GND: value = 0
悬空:value = 1 ,可能有内部上拉

你可能感兴趣的:(Linux设备驱动,Linux设备驱动)