PCF8575 是一个16bit的GPIO扩展芯片,可以支持两组共16个GPIO的控制。但是由于较为简单,16个GPIO只能同时被配置为输入或者输出,不支持对单个GPIO的控制特殊控制。使用I2C通信,拥有一个中断引脚。电压支持2.5~5.5V,连接5V的输入,GPIO输出将支持到3.3V。
GPIO的输入输出配置,没有特殊的寄存器配置。I2C时序要求也比较简单,只能读取两个字节,或者写入两个字节。当读取两个字节时,16个GPIO会在读取时,配置为输入模式;当写入两个字节数据时,16个GPIO会在写入数据时,配置为输出模式,两个字节数据会被设置为16个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 ,可能有内部上拉