/*
* MCP23S08 SPI/I2C GPIO gpio expander driver
*
* The inputs and outputs of the mcp23s08, mcp23s17, mcp23008 and mcp23017 are
* supported.
* For the I2C versions of the chips (mcp23008 and mcp23017) generation of
* interrupts is also supported.
* The hardware of the SPI versions of the chips (mcp23s08 and mcp23s17) is
* also capable of generating interrupts, but the linux driver does not
* support that yet.
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/spi/mcp23s08.h>
#include <linux/slab.h>
#include <asm/byteorder.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
/**
* MCP types supported by driver
*/
#define MCP_TYPE_S08 0
#define MCP_TYPE_S17 1
#define MCP_TYPE_008 2
#define MCP_TYPE_017 3
#define MCP_TYPE_S18 4
/* Registers are all 8 bits wide.
*
* The mcp23s17 has twice as many bits, and can be configured to work
* with either 16 bit registers or with two adjacent 8 bit banks.
*/
#define MCP_IODIR 0x00 /* init/reset: all ones */
#define MCP_IPOL 0x01
#define MCP_GPINTEN 0x02
#define MCP_DEFVAL 0x03
#define MCP_INTCON 0x04
#define MCP_IOCON 0x05
# define IOCON_MIRROR (1 << 6)
# define IOCON_SEQOP (1 << 5)
# define IOCON_HAEN (1 << 3)
# define IOCON_ODR (1 << 2)
# define IOCON_INTPOL (1 << 1)
# define IOCON_INTCC (1)
#define MCP_GPPU 0x06
#define MCP_INTF 0x07
#define MCP_INTCAP 0x08
#define MCP_GPIO 0x09
#define MCP_OLAT 0x0a
struct mcp23s08;
struct mcp23s08 {
u8 addr;
bool irq_active_high;
bool reg_shift;
u16 cache[11];
u16 irq_rise;
u16 irq_fall;
int irq;
bool irq_controller;
/* lock protects the cached values */
struct mutex lock;
struct mutex irq_lock;
struct gpio_chip chip;
struct regmap *regmap;
struct device *dev;
};
static const struct regmap_config mcp23x08_regmap = {
.reg_bits = 8,
.val_bits = 8,
.reg_stride = 1,
.max_register = MCP_OLAT,
};
static const struct regmap_config mcp23x17_regmap = {
.reg_bits = 8,
.val_bits = 16,
.reg_stride = 2,
.max_register = MCP_OLAT << 1,
.val_format_endian = REGMAP_ENDIAN_LITTLE,
};
/*----------------------------------------------------------------------*/
#ifdef CONFIG_SPI_MASTER
static int mcp23sxx_spi_write(void *context, const void *data, size_t count)
{
struct mcp23s08 *mcp = context;
struct spi_device *spi = to_spi_device(mcp->dev);
struct spi_message m;
struct spi_transfer t[2] = { { .tx_buf = &mcp->addr, .len = 1, },
{ .tx_buf = data, .len = count, }, };
spi_message_init(&m);
spi_message_add_tail(&t[0], &m);
spi_message_add_tail(&t[1], &m);
return spi_sync(spi, &m);
}
static int mcp23sxx_spi_gather_write(void *context,
const void *reg, size_t reg_size,
const void *val, size_t val_size)
{
struct mcp23s08 *mcp = context;
struct spi_device *spi = to_spi_device(mcp->dev);
struct spi_message m;
struct spi_transfer t[3] = { { .tx_buf = &mcp->addr, .len = 1, },
{ .tx_buf = reg, .len = reg_size, },
{ .tx_buf = val, .len = val_size, }, };
spi_message_init(&m);
spi_message_add_tail(&t[0], &m);
spi_message_add_tail(&t[1], &m);
spi_message_add_tail(&t[2], &m);
return spi_sync(spi, &m);
}
static int mcp23sxx_spi_read(void *context, const void *reg, size_t reg_size,
void *val, size_t val_size)
{
struct mcp23s08 *mcp = context;
struct spi_device *spi = to_spi_device(mcp->dev);
u8 tx[2];
if (reg_size != 1)
return -EINVAL;
tx[0] = mcp->addr | 0x01;
tx[