Skip to content
Snippets Groups Projects
Commit 07c6986a authored by Gianfranco Mariotti's avatar Gianfranco Mariotti
Browse files

[DRIVER] fxl6408: add I2C GPIO expander driver

Add minimal driver for Fairchild FXL6408 8-bit I2C-controlled GPIO expander.
Missing feature: IRQ handling.

Imported from: https://git.toradex.com/cgit/linux-toradex.git/?h=toradex_5.4-2.3.x-imx
parent 955d2dbb
No related branches found
No related tags found
No related merge requests found
......@@ -281,6 +281,13 @@ config GPIO_FTGPIO010
Support for common GPIOs from the Faraday FTGPIO010 IP core, found in
Cortina systems Gemini platforms, Moxa ART and others.
config GPIO_FXL6408
tristate "FXL6408 I2C GPIO expander"
help
GPIO driver for Fairchild Semiconductor FXL6408 GPIO expander.
To compile this driver as a module, choose M here: the module
will be called gpio-fxl6408.
config GPIO_GENERIC_PLATFORM
tristate "Generic memory-mapped GPIO controller support (MMIO platform device)"
select GPIO_GENERIC
......
......@@ -59,6 +59,7 @@ obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o
obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o
obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o
obj-$(CONFIG_GPIO_FTGPIO010) += gpio-ftgpio010.o
obj-$(CONFIG_GPIO_FXL6408) += gpio-fxl6408.o
obj-$(CONFIG_GPIO_GE_FPGA) += gpio-ge.o
obj-$(CONFIG_GPIO_GPIO_MM) += gpio-gpio-mm.o
obj-$(CONFIG_GPIO_GRGPIO) += gpio-grgpio.o
......
/*
* Copyright (C) 2016 Broadcom Limited.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*/
/**
* DOC: FXL6408 I2C to GPIO expander.
*
* This chip has has 8 GPIO lines out of it, and is controlled by an
* I2C bus (a pair of lines), providing 4x expansion of GPIO lines.
* It also provides an interrupt line out for notifying of
* statechanges.
*
* Any preconfigured state will be left in place until the GPIO lines
* get activated. At power on, everything is treated as an input.
*
* Documentation can be found at:
* https://www.fairchildsemi.com/datasheets/FX/FXL6408.pdf
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/of_platform.h>
#define FXL6408_DEVICE_ID 0x01
# define FXL6408_RST_INT BIT(1)
# define FXL6408_SW_RST BIT(0)
/* Bits set here indicate that the GPIO is an output. */
#define FXL6408_IO_DIR 0x03
/* Bits set here, when the corresponding bit of IO_DIR is set, drive
* the output high instead of low.
*/
#define FXL6408_OUTPUT 0x05
/* Bits here make the output High-Z, instead of the OUTPUT value. */
#define FXL6408_OUTPUT_HIGH_Z 0x07
/* Bits here define the expected input state of the GPIO.
* INTERRUPT_STAT bits will be set when the INPUT transitions away
* from this value.
*/
#define FXL6408_INPUT_DEFAULT_STATE 0x09
/* Bits here enable either pull up or pull down according to
* FXL6408_PULL_DOWN.
*/
#define FXL6408_PULL_ENABLE 0x0b
/* Bits set here (when the corresponding PULL_ENABLE is set) enable a
* pull-up instead of a pull-down.
*/
#define FXL6408_PULL_UP 0x0d
/* Returns the current status (1 = HIGH) of the input pins. */
#define FXL6408_INPUT_STATUS 0x0f
/* Mask of pins which can generate interrupts. */
#define FXL6408_INTERRUPT_MASK 0x11
/* Mask of pins which have generated an interrupt. Cleared on read. */
#define FXL6408_INTERRUPT_STAT 0x13
struct fxl6408_chip {
struct gpio_chip gpio_chip;
struct i2c_client *client;
struct mutex i2c_lock;
/* Caches of register values so we don't have to read-modify-write. */
u8 reg_io_dir;
u8 reg_output;
};
static int fxl6408_gpio_direction_input(struct gpio_chip *gc, unsigned off)
{
struct fxl6408_chip *chip = gpiochip_get_data(gc);
mutex_lock(&chip->i2c_lock);
chip->reg_io_dir &= ~BIT(off);
i2c_smbus_write_byte_data(chip->client, FXL6408_IO_DIR,
chip->reg_io_dir);
mutex_unlock(&chip->i2c_lock);
return 0;
}
static int fxl6408_gpio_direction_output(struct gpio_chip *gc,
unsigned off, int val)
{
struct fxl6408_chip *chip = gpiochip_get_data(gc);
mutex_lock(&chip->i2c_lock);
chip->reg_io_dir |= BIT(off);
i2c_smbus_write_byte_data(chip->client, FXL6408_IO_DIR,
chip->reg_io_dir);
mutex_unlock(&chip->i2c_lock);
return 0;
}
static int fxl6408_gpio_get_direction(struct gpio_chip *gc, unsigned off)
{
struct fxl6408_chip *chip = gpiochip_get_data(gc);
return (chip->reg_io_dir & BIT(off)) == 0;
}
static int fxl6408_gpio_get_value(struct gpio_chip *gc, unsigned off)
{
struct fxl6408_chip *chip = gpiochip_get_data(gc);
u8 reg;
if (fxl6408_gpio_get_direction(gc, off) == 0)
{
reg = chip->reg_output;
} else {
mutex_lock(&chip->i2c_lock);
reg = i2c_smbus_read_byte_data(chip->client, FXL6408_INPUT_STATUS);
mutex_unlock(&chip->i2c_lock);
}
return (reg & BIT(off)) != 0;
}
static void fxl6408_gpio_set_value(struct gpio_chip *gc, unsigned off, int val)
{
struct fxl6408_chip *chip = gpiochip_get_data(gc);
mutex_lock(&chip->i2c_lock);
if (val)
chip->reg_output |= BIT(off);
else
chip->reg_output &= ~BIT(off);
i2c_smbus_write_byte_data(chip->client, FXL6408_OUTPUT,
chip->reg_output);
mutex_unlock(&chip->i2c_lock);
}
static void fxl6408_gpio_set_multiple(struct gpio_chip *gc,
unsigned long *mask, unsigned long *bits)
{
struct fxl6408_chip *chip = gpiochip_get_data(gc);
mutex_lock(&chip->i2c_lock);
chip->reg_output = (chip->reg_output & ~mask[0]) | bits[0];
i2c_smbus_write_byte_data(chip->client, FXL6408_OUTPUT,
chip->reg_output);
mutex_unlock(&chip->i2c_lock);
}
static int fxl6408_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct fxl6408_chip *chip;
struct gpio_chip *gc;
unsigned int val;
int ret;
u8 device_id;
/* Check the device ID register to see if it's responding.
* This also clears RST_INT as a side effect, so we won't get
* the "we've been power cycled" interrupt once we enable
* interrupts.
*/
device_id = i2c_smbus_read_byte_data(client, FXL6408_DEVICE_ID);
if (device_id < 0) {
dev_err(dev, "FXL6408 probe returned %d\n", device_id);
return device_id;
} else if (device_id >> 5 != 5) {
dev_err(dev, "FXL6408 probe returned DID: 0x%02x\n", device_id);
return -ENODEV;
}
/* Disable High-Z of outputs, so that our OUTPUT updates
* actually take effect.
*/
i2c_smbus_write_byte_data(client, FXL6408_OUTPUT_HIGH_Z, 0);
chip = devm_kzalloc(dev, sizeof(struct fxl6408_chip), GFP_KERNEL);
if (chip == NULL)
return -ENOMEM;
chip->client = client;
mutex_init(&chip->i2c_lock);
/* if configured, set initial output state and direction,
* otherwise read them from the chip */
if (of_property_read_u32(dev->of_node, "inital_io_dir", &val)) {
chip->reg_io_dir = i2c_smbus_read_byte_data(client, FXL6408_IO_DIR);
} else {
chip->reg_io_dir = val & 0xff;
i2c_smbus_write_byte_data(client, FXL6408_IO_DIR, chip->reg_io_dir);
}
if (of_property_read_u32(dev->of_node, "inital_output", &val)) {
chip->reg_output = i2c_smbus_read_byte_data(client, FXL6408_OUTPUT);
} else {
chip->reg_output = val & 0xff;
i2c_smbus_write_byte_data(client, FXL6408_OUTPUT, chip->reg_output);
}
gc = &chip->gpio_chip;
gc->direction_input = fxl6408_gpio_direction_input;
gc->direction_output = fxl6408_gpio_direction_output;
gc->get_direction = fxl6408_gpio_get_direction;
gc->get = fxl6408_gpio_get_value;
gc->set = fxl6408_gpio_set_value;
gc->set_multiple = fxl6408_gpio_set_multiple;
gc->can_sleep = true;
gc->base =-1;
gc->ngpio = 8;
gc->label = chip->client->name;
gc->parent = dev;
gc->owner = THIS_MODULE;
ret = gpiochip_add_data(gc, chip);
if (ret)
return ret;
i2c_set_clientdata(client, chip);
return 0;
}
static int fxl6408_remove(struct i2c_client *client)
{
struct fxl6408_chip *chip = i2c_get_clientdata(client);
gpiochip_remove(&chip->gpio_chip);
return 0;
}
static const struct of_device_id fxl6408_dt_ids[] = {
{ .compatible = "fcs,fxl6408" },
{ }
};
MODULE_DEVICE_TABLE(of, fxl6408_dt_ids);
static const struct i2c_device_id fxl6408_id[] = {
{ "fxl6408", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, fxl6408_id);
static struct i2c_driver fxl6408_driver = {
.driver = {
.name = "fxl6408",
.of_match_table = fxl6408_dt_ids,
},
.probe = fxl6408_probe,
.remove = fxl6408_remove,
.id_table = fxl6408_id,
};
module_i2c_driver(fxl6408_driver);
MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
MODULE_DESCRIPTION("GPIO expander driver for FXL6408");
MODULE_LICENSE("GPL");
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment