gpio: Add driver for TI PCF8575 I2C GPIO expander

TI's PCF8575 is a 16-bit I2C GPIO expander.The device features a
16-bit quasi-bidirectional I/O ports. Each quasi-bidirectional I/O can
be used as an input or output without the use of a data-direction
control signal. The I/Os should be high before being used as inputs.
Read the device documentation for more details[1].

This driver is based on pcf857x driver available in Linux v4.7 kernel.
It supports basic reading and writing of gpio pins.

[1] http://www.ti.com/lit/ds/symlink/pcf8575.pdf

Signed-off-by: Vignesh R <vigneshr@ti.com>
Reviewed-by: Tom Rini <trini@konsulko.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Mugunthan V N <mugunthanvnm@ti.com>
master
Vignesh R 8 years ago committed by Tom Rini
parent 5aa79f2676
commit 5746b0df9c
  1. 71
      doc/device-tree-bindings/gpio/gpio-pcf857x.txt
  2. 7
      drivers/gpio/Kconfig
  3. 1
      drivers/gpio/Makefile
  4. 180
      drivers/gpio/pcf8575_gpio.c

@ -0,0 +1,71 @@
* PCF857x-compatible I/O expanders
The PCF857x-compatible chips have "quasi-bidirectional" I/O lines that can be
driven high by a pull-up current source or driven low to ground. This combines
the direction and output level into a single bit per line, which can't be read
back. We can't actually know at initialization time whether a line is configured
(a) as output and driving the signal low/high, or (b) as input and reporting a
low/high value, without knowing the last value written since the chip came out
of reset (if any). The only reliable solution for setting up line direction is
thus to do it explicitly.
Required Properties:
- compatible: should be one of the following.
- "maxim,max7328": For the Maxim MAX7378
- "maxim,max7329": For the Maxim MAX7329
- "nxp,pca8574": For the NXP PCA8574
- "nxp,pca8575": For the NXP PCA8575
- "nxp,pca9670": For the NXP PCA9670
- "nxp,pca9671": For the NXP PCA9671
- "nxp,pca9672": For the NXP PCA9672
- "nxp,pca9673": For the NXP PCA9673
- "nxp,pca9674": For the NXP PCA9674
- "nxp,pca9675": For the NXP PCA9675
- "nxp,pcf8574": For the NXP PCF8574
- "nxp,pcf8574a": For the NXP PCF8574A
- "nxp,pcf8575": For the NXP PCF8575
- "ti,tca9554": For the TI TCA9554
- reg: I2C slave address.
- gpio-controller: Marks the device node as a gpio controller.
- #gpio-cells: Should be 2. The first cell is the GPIO number and the second
cell specifies GPIO flags, as defined in <dt-bindings/gpio/gpio.h>. Only the
GPIO_ACTIVE_HIGH and GPIO_ACTIVE_LOW flags are supported.
Optional Properties:
- lines-initial-states: Bitmask that specifies the initial state of each
line. When a bit is set to zero, the corresponding line will be initialized to
the input (pulled-up) state. When the bit is set to one, the line will be
initialized the low-level output state. If the property is not specified
all lines will be initialized to the input state.
The I/O expander can detect input state changes, and thus optionally act as
an interrupt controller. When the expander interrupt line is connected all the
following properties must be set. For more information please see the
interrupt controller device tree bindings documentation available at
Documentation/devicetree/bindings/interrupt-controller/interrupts.txt.
- interrupt-controller: Identifies the node as an interrupt controller.
- #interrupt-cells: Number of cells to encode an interrupt source, shall be 2.
- interrupt-parent: phandle of the parent interrupt controller.
- interrupts: Interrupt specifier for the controllers interrupt.
Please refer to gpio.txt in this directory for details of the common GPIO
bindings used by client devices.
Example: PCF8575 I/O expander node
pcf8575: gpio@20 {
compatible = "nxp,pcf8575";
reg = <0x20>;
interrupt-parent = <&irqpin2>;
interrupts = <3 0>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};

@ -79,6 +79,13 @@ config PM8916_GPIO
Power and reset buttons are placed in "pm8916_key" bank and
have gpio numbers 0 and 1 respectively.
config PCF8575_GPIO
bool "PCF8575 I2C GPIO Expander driver"
depends on DM_GPIO && DM_I2C
help
Support for PCF8575 I2C 16-bit GPIO expander. Most of these
chips are from NXP and TI.
config ROCKCHIP_GPIO
bool "Rockchip GPIO driver"
depends on DM_GPIO

@ -56,4 +56,5 @@ obj-$(CONFIG_HIKEY_GPIO) += hi6220_gpio.o
obj-$(CONFIG_PIC32_GPIO) += pic32_gpio.o
obj-$(CONFIG_MVEBU_GPIO) += mvebu_gpio.o
obj-$(CONFIG_MSM_GPIO) += msm_gpio.o
obj-$(CONFIG_$(SPL_)PCF8575_GPIO) += pcf8575_gpio.o
obj-$(CONFIG_PM8916_GPIO) += pm8916_gpio.o

@ -0,0 +1,180 @@
/*
* PCF8575 I2C GPIO EXPANDER DRIVER
*
* Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/
*
* Vignesh R <vigneshr@ti.com>
*
* SPDX-License-Identifier: GPL-2.0
*
*
* Driver for TI PCF-8575 16-bit I2C gpio expander. Based on
* gpio-pcf857x Linux Kernel(v4.7) driver.
*
* Copyright (C) 2007 David Brownell
*
*/
/*
* NOTE: The driver and devicetree bindings are borrowed from Linux
* Kernel, but driver does not support all PCF857x devices. It currently
* supports PCF8575 16-bit expander by TI and NXP.
*
* TODO(vigneshr@ti.com):
* Support 8 bit PCF857x compatible expanders.
*/
#include <common.h>
#include <dm.h>
#include <i2c.h>
#include <asm-generic/gpio.h>
DECLARE_GLOBAL_DATA_PTR;
struct pcf8575_chip {
int gpio_count; /* No. GPIOs supported by the chip */
/* NOTE: these chips have strange "quasi-bidirectional" I/O pins.
* We can't actually know whether a pin is configured (a) as output
* and driving the signal low, or (b) as input and reporting a low
* value ... without knowing the last value written since the chip
* came out of reset (if any). We can't read the latched output.
* In short, the only reliable solution for setting up pin direction
* is to do it explicitly.
*
* Using "out" avoids that trouble. When left initialized to zero,
* our software copy of the "latch" then matches the chip's all-ones
* reset state. Otherwise it flags pins to be driven low.
*/
unsigned int out; /* software latch */
const char *bank_name; /* Name of the expander bank */
};
/* Read/Write to 16-bit I/O expander */
static int pcf8575_i2c_write_le16(struct udevice *dev, unsigned int word)
{
struct dm_i2c_chip *chip = dev_get_parent_platdata(dev);
u8 buf[2] = { word & 0xff, word >> 8, };
int ret;
ret = dm_i2c_write(dev, 0, buf, 2);
if (ret)
printf("%s i2c write failed to addr %x\n", __func__,
chip->chip_addr);
return ret;
}
static int pcf8575_i2c_read_le16(struct udevice *dev)
{
struct dm_i2c_chip *chip = dev_get_parent_platdata(dev);
u8 buf[2];
int ret;
ret = dm_i2c_read(dev, 0, buf, 2);
if (ret) {
printf("%s i2c read failed from addr %x\n", __func__,
chip->chip_addr);
return ret;
}
return (buf[1] << 8) | buf[0];
}
static int pcf8575_direction_input(struct udevice *dev, unsigned offset)
{
struct pcf8575_chip *plat = dev_get_platdata(dev);
int status;
plat->out |= BIT(offset);
status = pcf8575_i2c_write_le16(dev, plat->out);
return status;
}
static int pcf8575_direction_output(struct udevice *dev,
unsigned int offset, int value)
{
struct pcf8575_chip *plat = dev_get_platdata(dev);
int ret;
if (value)
plat->out |= BIT(offset);
else
plat->out &= ~BIT(offset);
ret = pcf8575_i2c_write_le16(dev, plat->out);
return ret;
}
static int pcf8575_get_value(struct udevice *dev, unsigned int offset)
{
int value;
value = pcf8575_i2c_read_le16(dev);
return (value < 0) ? value : ((value & BIT(offset)) >> offset);
}
static int pcf8575_set_value(struct udevice *dev, unsigned int offset,
int value)
{
return pcf8575_direction_output(dev, offset, value);
}
static int pcf8575_ofdata_platdata(struct udevice *dev)
{
struct pcf8575_chip *plat = dev_get_platdata(dev);
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
int n_latch;
uc_priv->gpio_count = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
"gpio-count", 16);
uc_priv->bank_name = fdt_getprop(gd->fdt_blob, dev->of_offset,
"gpio-bank-name", NULL);
if (!uc_priv->bank_name)
uc_priv->bank_name = fdt_get_name(gd->fdt_blob,
dev->of_offset, NULL);
n_latch = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
"lines-initial-states", 0);
plat->out = ~n_latch;
return 0;
}
static int pcf8575_gpio_probe(struct udevice *dev)
{
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
debug("%s GPIO controller with %d gpios probed\n",
uc_priv->bank_name, uc_priv->gpio_count);
return 0;
}
static const struct dm_gpio_ops pcf8575_gpio_ops = {
.direction_input = pcf8575_direction_input,
.direction_output = pcf8575_direction_output,
.get_value = pcf8575_get_value,
.set_value = pcf8575_set_value,
};
static const struct udevice_id pcf8575_gpio_ids[] = {
{ .compatible = "nxp,pcf8575" },
{ .compatible = "ti,pcf8575" },
{ }
};
U_BOOT_DRIVER(gpio_pcf8575) = {
.name = "gpio_pcf8575",
.id = UCLASS_GPIO,
.ops = &pcf8575_gpio_ops,
.of_match = pcf8575_gpio_ids,
.ofdata_to_platdata = pcf8575_ofdata_platdata,
.probe = pcf8575_gpio_probe,
.platdata_auto_alloc_size = sizeof(struct pcf8575_chip),
};
Loading…
Cancel
Save