blob: 4bad52afe3033b379abf1a3f5087746bba44756b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the Pericom PI4IOE5V6408 GPIO Expander.
*
* Copyright (C) 2018 Google, LLC.
*/
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>
#define PI4IO_CHIP_ID 0x1
#define PI4IO_IO_DIRECTION 0x3
#define PI4IO_OUTPUT 0x5
#define PI4IO_OUTPUT_HI_IMPEDANCE 0x7
#define PI4IO_INPUT_STATUS 0xF
#define PI4IO_INTERRUPT_STATUS 0x13
#define PI4IO_CHIP_ID_VAL 0xA0
#define PI4IO_CHIP_ID_MASK 0xFC
#define PI4IO_DIRECTION_TO_GPIOD(x) ((x) ? GPIOF_DIR_OUT : GPIOF_DIR_IN)
#define GPIOD_DIRECTION_TO_PI4IO(x) ((x) == GPIOF_DIR_OUT ? 1 : 0)
#define GPIO_OUT_LOW 0
#define GPIO_OUT_HIGH 1
static bool pi4io_readable_reg(struct device *dev, unsigned int reg)
{
// All readable registers are odd-numbered.
return (reg % 2) == 1;
}
static bool pi4io_writeable_reg(struct device *dev, unsigned int reg)
{
// All odd-numbered registers are writable except for 0xF.
if ((reg % 2) == 1) {
if (reg != PI4IO_INPUT_STATUS) {
return true;
}
}
return false;
}
static bool pi4io_volatile_reg(struct device *dev, unsigned int reg)
{
if (reg == PI4IO_INPUT_STATUS || reg == PI4IO_INTERRUPT_STATUS) {
return true;
}
return false;
}
static const struct regmap_config pi4io_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0x13,
.writeable_reg = pi4io_writeable_reg,
.readable_reg = pi4io_readable_reg,
.volatile_reg = pi4io_volatile_reg,
};
struct pi4io_priv {
struct i2c_client *i2c;
struct regmap *regmap;
struct gpio_chip gpio;
};
static int pi4io_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
{
int ret, io_dir;
struct pi4io_priv *pi4io = gpiochip_get_data(chip);
struct device *dev = &pi4io->i2c->dev;
ret = regmap_read(pi4io->regmap, PI4IO_IO_DIRECTION, &io_dir);
if (ret) {
dev_err(dev, "Failed to read I/O direction: %d", ret);
return ret;
}
return PI4IO_DIRECTION_TO_GPIOD((io_dir >> offset) & 1);
}
static int pi4io_gpio_set_direction(
struct gpio_chip *chip, unsigned offset, int direction)
{
int ret;
struct pi4io_priv *pi4io = gpiochip_get_data(chip);
struct device *dev = &pi4io->i2c->dev;
ret = regmap_update_bits(pi4io->regmap, PI4IO_IO_DIRECTION, 1 << offset,
GPIOD_DIRECTION_TO_PI4IO(direction) << offset);
if (ret) {
dev_err(dev, "Failed to set direction: %d", ret);
return ret;
}
// We desire the hi-impedance state to track the output state.
ret = regmap_update_bits(pi4io->regmap, PI4IO_OUTPUT_HI_IMPEDANCE,
1 << offset, direction << offset);
return ret;
}
static int pi4io_gpio_get(struct gpio_chip *chip, unsigned offset)
{
int ret, out;
struct pi4io_priv *pi4io = gpiochip_get_data(chip);
struct device *dev = &pi4io->i2c->dev;
ret = regmap_read(pi4io->regmap, PI4IO_OUTPUT, &out);
if (ret) {
dev_err(dev, "Failed to read output: %d", ret);
return ret;
}
if (out & (1 << offset)) {
return 1;
}
return 0;
}
static void pi4io_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
{
int ret;
struct pi4io_priv *pi4io = gpiochip_get_data(chip);
struct device *dev = &pi4io->i2c->dev;
ret = regmap_update_bits(
pi4io->regmap, PI4IO_OUTPUT, 1 << offset, value << offset);
if (ret) {
dev_err(dev, "Failed to write output: %d", ret);
}
}
static int pi4io_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
{
return pi4io_gpio_set_direction(chip, offset, GPIOF_DIR_IN);
}
static int pi4io_gpio_direction_output(
struct gpio_chip *chip, unsigned offset, int value)
{
int ret;
struct pi4io_priv *pi4io = gpiochip_get_data(chip);
struct device *dev = &pi4io->i2c->dev;
ret = pi4io_gpio_set_direction(chip, offset, GPIOF_DIR_OUT);
if (ret) {
dev_err(dev, "Failed to set direction: %d", ret);
return ret;
}
pi4io_gpio_set(chip, offset, value);
return 0;
}
static int pi4io_gpio_setup(struct pi4io_priv *pi4io)
{
int ret;
struct device *dev = &pi4io->i2c->dev;
struct gpio_chip *gc = &pi4io->gpio;
gc->ngpio = 8;
gc->label = pi4io->i2c->name;
gc->parent = &pi4io->i2c->dev;
gc->owner = THIS_MODULE;
gc->base = -1;
gc->can_sleep = true;
gc->get_direction = pi4io_gpio_get_direction;
gc->direction_input = pi4io_gpio_direction_input;
gc->direction_output = pi4io_gpio_direction_output;
gc->get = pi4io_gpio_get;
gc->set = pi4io_gpio_set;
ret = devm_gpiochip_add_data(dev, gc, pi4io);
if (ret) {
dev_err(dev, "devm_gpiochip_add_data failed: %d", ret);
return ret;
}
return 0;
}
static int pi4io_probe(
struct i2c_client *client, const struct i2c_device_id *id)
{
int ret, chip_id;
struct device *dev = &client->dev;
struct pi4io_priv *pi4io;
pi4io = devm_kzalloc(dev, sizeof(struct pi4io_priv), GFP_KERNEL);
if (!pi4io) {
return -ENOMEM;
}
i2c_set_clientdata(client, pi4io);
pi4io->i2c = client;
pi4io->regmap = devm_regmap_init_i2c(client, &pi4io_regmap);
ret = regmap_read(pi4io->regmap, PI4IO_CHIP_ID, &chip_id);
if (ret < 0) {
dev_err(dev, "Failed to read Chip ID: %d", ret);
return ret;
}
if ((chip_id & PI4IO_CHIP_ID_MASK) != PI4IO_CHIP_ID_VAL) {
dev_err(dev, "Invalid Chip ID!");
return -EINVAL;
}
ret = pi4io_gpio_setup(pi4io);
if (ret < 0) {
dev_err(dev, "Failed to setup GPIOs: %d", ret);
return ret;
}
dev_dbg(dev, "PI4IO probe finished");
return 0;
}
static const struct i2c_device_id pi4io_id_table[] = { { "pi4io", 0 }, {} };
MODULE_DEVICE_TABLE(i2c, pi4io_id_table);
static struct i2c_driver pi4io_driver = {
.driver = {
.name = "pi4io-gpio",
},
.probe = pi4io_probe,
.id_table = pi4io_id_table,
};
module_i2c_driver(pi4io_driver);
MODULE_AUTHOR("Alex Van Damme <atv@google.com>");
MODULE_DESCRIPTION("PI4IOE5V6408");
MODULE_LICENSE("GPL v2");