summaryrefslogtreecommitdiff
path: root/drivers/pinctrl/pinctrl-mcp23s08.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pinctrl/pinctrl-mcp23s08.c')
-rw-r--r--drivers/pinctrl/pinctrl-mcp23s08.c848
1 files changed, 158 insertions, 690 deletions
diff --git a/drivers/pinctrl/pinctrl-mcp23s08.c b/drivers/pinctrl/pinctrl-mcp23s08.c
index b03481ef99a1..586f2f67c617 100644
--- a/drivers/pinctrl/pinctrl-mcp23s08.c
+++ b/drivers/pinctrl/pinctrl-mcp23s08.c
@@ -1,33 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-only
/* MCP23S08 SPI/I2C GPIO driver */
+#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/mutex.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/export.h>
#include <linux/gpio/driver.h>
-#include <linux/i2c.h>
-#include <linux/spi/spi.h>
-#include <linux/spi/mcp23s08.h>
+#include <linux/gpio/consumer.h>
+#include <linux/seq_file.h>
#include <linux/slab.h>
#include <asm/byteorder.h>
#include <linux/interrupt.h>
-#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/pinctrl/pinconf.h>
#include <linux/pinctrl/pinconf-generic.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
-#define MCP_TYPE_018 5
-
-#define MCP_MAX_DEV_PER_CS 8
+#include "pinctrl-mcp23s08.h"
/* Registers are all 8 bits wide.
*
@@ -52,41 +44,6 @@
#define MCP_GPIO 0x09
#define MCP_OLAT 0x0a
-struct mcp23s08;
-
-struct mcp23s08 {
- u8 addr;
- bool irq_active_high;
- bool reg_shift;
-
- u16 irq_rise;
- u16 irq_fall;
- int irq;
- bool irq_controller;
- int cached_gpio;
- /* lock protects regmap access with bypass/cache flags */
- struct mutex lock;
-
- struct gpio_chip chip;
-
- struct regmap *regmap;
- struct device *dev;
-
- struct pinctrl_dev *pctldev;
- struct pinctrl_desc pinctrl_desc;
-};
-
-static const struct reg_default mcp23x08_defaults[] = {
- {.reg = MCP_IODIR, .def = 0xff},
- {.reg = MCP_IPOL, .def = 0x00},
- {.reg = MCP_GPINTEN, .def = 0x00},
- {.reg = MCP_DEFVAL, .def = 0x00},
- {.reg = MCP_INTCON, .def = 0x00},
- {.reg = MCP_IOCON, .def = 0x00},
- {.reg = MCP_GPPU, .def = 0x00},
- {.reg = MCP_OLAT, .def = 0x00},
-};
-
static const struct regmap_range mcp23x08_volatile_range = {
.range_min = MCP_INTF,
.range_max = MCP_GPIO,
@@ -107,63 +64,54 @@ static const struct regmap_access_table mcp23x08_precious_table = {
.n_yes_ranges = 1,
};
-static const struct regmap_config mcp23x08_regmap = {
+const struct regmap_config mcp23x08_regmap = {
.reg_bits = 8,
.val_bits = 8,
.reg_stride = 1,
.volatile_table = &mcp23x08_volatile_table,
.precious_table = &mcp23x08_precious_table,
- .reg_defaults = mcp23x08_defaults,
- .num_reg_defaults = ARRAY_SIZE(mcp23x08_defaults),
- .cache_type = REGCACHE_FLAT,
+ .num_reg_defaults_raw = MCP_OLAT + 1,
+ .cache_type = REGCACHE_MAPLE,
.max_register = MCP_OLAT,
+ .disable_locking = true, /* mcp->lock protects the regmap */
};
+EXPORT_SYMBOL_GPL(mcp23x08_regmap);
-static const struct reg_default mcp23x16_defaults[] = {
- {.reg = MCP_IODIR << 1, .def = 0xffff},
- {.reg = MCP_IPOL << 1, .def = 0x0000},
- {.reg = MCP_GPINTEN << 1, .def = 0x0000},
- {.reg = MCP_DEFVAL << 1, .def = 0x0000},
- {.reg = MCP_INTCON << 1, .def = 0x0000},
- {.reg = MCP_IOCON << 1, .def = 0x0000},
- {.reg = MCP_GPPU << 1, .def = 0x0000},
- {.reg = MCP_OLAT << 1, .def = 0x0000},
-};
-
-static const struct regmap_range mcp23x16_volatile_range = {
+static const struct regmap_range mcp23x17_volatile_range = {
.range_min = MCP_INTF << 1,
.range_max = MCP_GPIO << 1,
};
-static const struct regmap_access_table mcp23x16_volatile_table = {
- .yes_ranges = &mcp23x16_volatile_range,
+static const struct regmap_access_table mcp23x17_volatile_table = {
+ .yes_ranges = &mcp23x17_volatile_range,
.n_yes_ranges = 1,
};
-static const struct regmap_range mcp23x16_precious_range = {
- .range_min = MCP_GPIO << 1,
+static const struct regmap_range mcp23x17_precious_range = {
+ .range_min = MCP_INTCAP << 1,
.range_max = MCP_GPIO << 1,
};
-static const struct regmap_access_table mcp23x16_precious_table = {
- .yes_ranges = &mcp23x16_precious_range,
+static const struct regmap_access_table mcp23x17_precious_table = {
+ .yes_ranges = &mcp23x17_precious_range,
.n_yes_ranges = 1,
};
-static const struct regmap_config mcp23x17_regmap = {
+const struct regmap_config mcp23x17_regmap = {
.reg_bits = 8,
.val_bits = 16,
.reg_stride = 2,
.max_register = MCP_OLAT << 1,
- .volatile_table = &mcp23x16_volatile_table,
- .precious_table = &mcp23x16_precious_table,
- .reg_defaults = mcp23x16_defaults,
- .num_reg_defaults = ARRAY_SIZE(mcp23x16_defaults),
- .cache_type = REGCACHE_FLAT,
+ .volatile_table = &mcp23x17_volatile_table,
+ .precious_table = &mcp23x17_precious_table,
+ .num_reg_defaults_raw = MCP_OLAT + 1,
+ .cache_type = REGCACHE_MAPLE,
.val_format_endian = REGMAP_ENDIAN_LITTLE,
+ .disable_locking = true, /* mcp->lock protects the regmap */
};
+EXPORT_SYMBOL_GPL(mcp23x17_regmap);
static int mcp_read(struct mcp23s08 *mcp, unsigned int reg, unsigned int *val)
{
@@ -175,10 +123,9 @@ static int mcp_write(struct mcp23s08 *mcp, unsigned int reg, unsigned int val)
return regmap_write(mcp->regmap, reg << mcp->reg_shift, val);
}
-static int mcp_set_mask(struct mcp23s08 *mcp, unsigned int reg,
- unsigned int mask, bool enabled)
+static int mcp_update_bits(struct mcp23s08 *mcp, unsigned int reg,
+ unsigned int mask, unsigned int val)
{
- u16 val = enabled ? 0xffff : 0x0000;
return regmap_update_bits(mcp->regmap, reg << mcp->reg_shift,
mask, val);
}
@@ -187,7 +134,7 @@ static int mcp_set_bit(struct mcp23s08 *mcp, unsigned int reg,
unsigned int pin, bool enabled)
{
u16 mask = BIT(pin);
- return mcp_set_mask(mcp, reg, mask, enabled);
+ return mcp_update_bits(mcp, reg, mask, enabled ? mask : 0);
}
static const struct pinctrl_pin_desc mcp23x08_pins[] = {
@@ -259,13 +206,14 @@ static int mcp_pinconf_get(struct pinctrl_dev *pctldev, unsigned int pin,
switch (param) {
case PIN_CONFIG_BIAS_PULL_UP:
+ mutex_lock(&mcp->lock);
ret = mcp_read(mcp, MCP_GPPU, &data);
+ mutex_unlock(&mcp->lock);
if (ret < 0)
return ret;
status = (data & BIT(pin)) ? 1 : 0;
break;
default:
- dev_err(mcp->dev, "Invalid config param %04x\n", param);
return -ENOTSUPP;
}
@@ -289,10 +237,12 @@ static int mcp_pinconf_set(struct pinctrl_dev *pctldev, unsigned int pin,
switch (param) {
case PIN_CONFIG_BIAS_PULL_UP:
+ mutex_lock(&mcp->lock);
ret = mcp_set_bit(mcp, MCP_GPPU, pin, arg);
+ mutex_unlock(&mcp->lock);
break;
default:
- dev_err(mcp->dev, "Invalid config param %04x\n", param);
+ dev_dbg(mcp->dev, "Invalid config param %04x\n", param);
return -ENOTSUPP;
}
}
@@ -308,80 +258,6 @@ static const struct pinconf_ops mcp_pinconf_ops = {
/*----------------------------------------------------------------------*/
-#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[1] = *((u8 *) reg);
-
- return spi_write_then_read(spi, tx, sizeof(tx), val, val_size);
-}
-
-static const struct regmap_bus mcp23sxx_spi_regmap = {
- .write = mcp23sxx_spi_write,
- .gather_write = mcp23sxx_spi_gather_write,
- .read = mcp23sxx_spi_read,
-};
-
-#endif /* CONFIG_SPI_MASTER */
-
-/*----------------------------------------------------------------------*/
-
-/* A given spi_device can represent up to eight mcp23sxx chips
- * sharing the same chipselect but using different addresses
- * (e.g. chips #0 and #3 might be populated, but not #1 or $2).
- * Driver data holds all the per-chip data.
- */
-struct mcp23s08_driver_data {
- unsigned ngpio;
- struct mcp23s08 *mcp[8];
- struct mcp23s08 chip[];
-};
-
-
static int mcp23s08_direction_input(struct gpio_chip *chip, unsigned offset)
{
struct mcp23s08 *mcp = gpiochip_get_data(chip);
@@ -414,19 +290,57 @@ static int mcp23s08_get(struct gpio_chip *chip, unsigned offset)
return status;
}
+static int mcp23s08_get_multiple(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct mcp23s08 *mcp = gpiochip_get_data(chip);
+ unsigned int status;
+ int ret;
+
+ mutex_lock(&mcp->lock);
+
+ /* REVISIT reading this clears any IRQ ... */
+ ret = mcp_read(mcp, MCP_GPIO, &status);
+ if (ret < 0)
+ status = 0;
+ else {
+ mcp->cached_gpio = status;
+ *bits = status;
+ }
+
+ mutex_unlock(&mcp->lock);
+ return ret;
+}
+
static int __mcp23s08_set(struct mcp23s08 *mcp, unsigned mask, bool value)
{
- return mcp_set_mask(mcp, MCP_OLAT, mask, value);
+ return mcp_update_bits(mcp, MCP_OLAT, mask, value ? mask : 0);
}
-static void mcp23s08_set(struct gpio_chip *chip, unsigned offset, int value)
+static int mcp23s08_set(struct gpio_chip *chip, unsigned int offset, int value)
{
struct mcp23s08 *mcp = gpiochip_get_data(chip);
unsigned mask = BIT(offset);
+ int ret;
+
+ mutex_lock(&mcp->lock);
+ ret = __mcp23s08_set(mcp, mask, !!value);
+ mutex_unlock(&mcp->lock);
+
+ return ret;
+}
+
+static int mcp23s08_set_multiple(struct gpio_chip *chip,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct mcp23s08 *mcp = gpiochip_get_data(chip);
+ int ret;
mutex_lock(&mcp->lock);
- __mcp23s08_set(mcp, mask, !!value);
+ ret = mcp_update_bits(mcp, MCP_OLAT, *mask, *bits);
mutex_unlock(&mcp->lock);
+
+ return ret;
}
static int
@@ -439,7 +353,7 @@ mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value)
mutex_lock(&mcp->lock);
status = __mcp23s08_set(mcp, mask, value);
if (status == 0) {
- status = mcp_set_mask(mcp, MCP_IODIR, mask, false);
+ status = mcp_update_bits(mcp, MCP_IODIR, mask, 0);
}
mutex_unlock(&mcp->lock);
return status;
@@ -449,7 +363,9 @@ mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value)
static irqreturn_t mcp23s08_irq(int irq, void *data)
{
struct mcp23s08 *mcp = data;
- int intcap, intcon, intf, i, gpio, gpio_orig, intcap_mask, defval;
+ int intcap, intcon, intf, i, gpio, gpio_orig, intcap_mask, defval, gpinten;
+ bool need_unmask = false;
+ unsigned long int enabled_interrupts;
unsigned int child_irq;
bool intf_set, intcap_changed, gpio_bit_changed,
defval_changed, gpio_set;
@@ -458,15 +374,30 @@ static irqreturn_t mcp23s08_irq(int irq, void *data)
if (mcp_read(mcp, MCP_INTF, &intf))
goto unlock;
- if (mcp_read(mcp, MCP_INTCAP, &intcap))
+ if (intf == 0) {
+ /* There is no interrupt pending */
goto unlock;
+ }
if (mcp_read(mcp, MCP_INTCON, &intcon))
goto unlock;
+ if (mcp_read(mcp, MCP_GPINTEN, &gpinten))
+ goto unlock;
+
if (mcp_read(mcp, MCP_DEFVAL, &defval))
goto unlock;
+ /* Mask level interrupts to avoid their immediate reactivation after clearing */
+ if (intcon) {
+ need_unmask = true;
+ if (mcp_write(mcp, MCP_GPINTEN, gpinten & ~intcon))
+ goto unlock;
+ }
+
+ if (mcp_read(mcp, MCP_INTCAP, &intcap))
+ goto unlock;
+
/* This clears the interrupt(configurable on S18) */
if (mcp_read(mcp, MCP_GPIO, &gpio))
goto unlock;
@@ -475,18 +406,16 @@ static irqreturn_t mcp23s08_irq(int irq, void *data)
mcp->cached_gpio = gpio;
mutex_unlock(&mcp->lock);
- if (intf == 0) {
- /* There is no interrupt pending */
- return IRQ_HANDLED;
- }
-
dev_dbg(mcp->chip.parent,
"intcap 0x%04X intf 0x%04X gpio_orig 0x%04X gpio 0x%04X\n",
intcap, intf, gpio_orig, gpio);
- for (i = 0; i < mcp->chip.ngpio; i++) {
- /* We must check all of the inputs on the chip,
- * otherwise we may not notice a change on >=2 pins.
+ enabled_interrupts = gpinten;
+ for_each_set_bit(i, &enabled_interrupts, mcp->chip.ngpio) {
+ /*
+ * We must check all of the inputs with enabled interrupts
+ * on the chip, otherwise we may not notice a change
+ * on more than one pin.
*
* On at least the mcp23s17, INTCAP is only updated
* one byte at a time(INTCAPA and INTCAPB are
@@ -531,9 +460,18 @@ static irqreturn_t mcp23s08_irq(int irq, void *data)
}
}
+ if (need_unmask) {
+ mutex_lock(&mcp->lock);
+ goto unlock;
+ }
+
return IRQ_HANDLED;
unlock:
+ if (need_unmask)
+ if (mcp_write(mcp, MCP_GPINTEN, gpinten))
+ dev_err(mcp->chip.parent, "can't unmask GPINTEN\n");
+
mutex_unlock(&mcp->lock);
return IRQ_HANDLED;
}
@@ -542,17 +480,19 @@ static void mcp23s08_irq_mask(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct mcp23s08 *mcp = gpiochip_get_data(gc);
- unsigned int pos = data->hwirq;
+ unsigned int pos = irqd_to_hwirq(data);
mcp_set_bit(mcp, MCP_GPINTEN, pos, false);
+ gpiochip_disable_irq(gc, pos);
}
static void mcp23s08_irq_unmask(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct mcp23s08 *mcp = gpiochip_get_data(gc);
- unsigned int pos = data->hwirq;
+ unsigned int pos = irqd_to_hwirq(data);
+ gpiochip_enable_irq(gc, pos);
mcp_set_bit(mcp, MCP_GPINTEN, pos, true);
}
@@ -560,8 +500,7 @@ static int mcp23s08_irq_set_type(struct irq_data *data, unsigned int type)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct mcp23s08 *mcp = gpiochip_get_data(gc);
- unsigned int pos = data->hwirq;
- int status = 0;
+ unsigned int pos = irqd_to_hwirq(data);
if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) {
mcp_set_bit(mcp, MCP_INTCON, pos, false);
@@ -584,7 +523,7 @@ static int mcp23s08_irq_set_type(struct irq_data *data, unsigned int type)
} else
return -EINVAL;
- return status;
+ return 0;
}
static void mcp23s08_irq_bus_lock(struct irq_data *data)
@@ -607,15 +546,6 @@ static void mcp23s08_irq_bus_unlock(struct irq_data *data)
mutex_unlock(&mcp->lock);
}
-static struct irq_chip mcp23s08_irq_chip = {
- .name = "gpio-mcp23xxx",
- .irq_mask = mcp23s08_irq_mask,
- .irq_unmask = mcp23s08_irq_unmask,
- .irq_set_type = mcp23s08_irq_set_type,
- .irq_bus_lock = mcp23s08_irq_bus_lock,
- .irq_bus_sync_unlock = mcp23s08_irq_bus_unlock,
-};
-
static int mcp23s08_irq_setup(struct mcp23s08 *mcp)
{
struct gpio_chip *chip = &mcp->chip;
@@ -639,250 +569,62 @@ static int mcp23s08_irq_setup(struct mcp23s08 *mcp)
return 0;
}
-static int mcp23s08_irqchip_setup(struct mcp23s08 *mcp)
+static void mcp23s08_irq_print_chip(struct irq_data *d, struct seq_file *p)
{
- struct gpio_chip *chip = &mcp->chip;
- int err;
-
- err = gpiochip_irqchip_add_nested(chip,
- &mcp23s08_irq_chip,
- 0,
- handle_simple_irq,
- IRQ_TYPE_NONE);
- if (err) {
- dev_err(chip->parent,
- "could not connect irqchip to gpiochip: %d\n", err);
- return err;
- }
-
- gpiochip_set_nested_irqchip(chip,
- &mcp23s08_irq_chip,
- mcp->irq);
-
- return 0;
-}
-
-/*----------------------------------------------------------------------*/
-
-#ifdef CONFIG_DEBUG_FS
-
-#include <linux/seq_file.h>
-
-/*
- * This compares the chip's registers with the register
- * cache and corrects any incorrectly set register. This
- * can be used to fix state for MCP23xxx, that temporary
- * lost its power supply.
- */
-#define MCP23S08_CONFIG_REGS 7
-static int __check_mcp23s08_reg_cache(struct mcp23s08 *mcp)
-{
- int cached[MCP23S08_CONFIG_REGS];
- int err = 0, i;
-
- /* read cached config registers */
- for (i = 0; i < MCP23S08_CONFIG_REGS; i++) {
- err = mcp_read(mcp, i, &cached[i]);
- if (err)
- goto out;
- }
-
- regcache_cache_bypass(mcp->regmap, true);
-
- for (i = 0; i < MCP23S08_CONFIG_REGS; i++) {
- int uncached;
- err = mcp_read(mcp, i, &uncached);
- if (err)
- goto out;
-
- if (uncached != cached[i]) {
- dev_err(mcp->dev, "restoring reg 0x%02x from 0x%04x to 0x%04x (power-loss?)\n",
- i, uncached, cached[i]);
- mcp_write(mcp, i, cached[i]);
- }
- }
-
-out:
- if (err)
- dev_err(mcp->dev, "read error: reg=%02x, err=%d", i, err);
- regcache_cache_bypass(mcp->regmap, false);
- return err;
-}
-
-/*
- * This shows more info than the generic gpio dump code:
- * pullups, deglitching, open drain drive.
- */
-static void mcp23s08_dbg_show(struct seq_file *s, struct gpio_chip *chip)
-{
- struct mcp23s08 *mcp;
- char bank;
- int t;
- unsigned mask;
- int iodir, gpio, gppu;
-
- mcp = gpiochip_get_data(chip);
-
- /* NOTE: we only handle one bank for now ... */
- bank = '0' + ((mcp->addr >> 1) & 0x7);
-
- mutex_lock(&mcp->lock);
-
- t = __check_mcp23s08_reg_cache(mcp);
- if (t) {
- seq_printf(s, " I/O Error\n");
- goto done;
- }
- t = mcp_read(mcp, MCP_IODIR, &iodir);
- if (t) {
- seq_printf(s, " I/O Error\n");
- goto done;
- }
- t = mcp_read(mcp, MCP_GPIO, &gpio);
- if (t) {
- seq_printf(s, " I/O Error\n");
- goto done;
- }
- t = mcp_read(mcp, MCP_GPPU, &gppu);
- if (t) {
- seq_printf(s, " I/O Error\n");
- goto done;
- }
-
- for (t = 0, mask = BIT(0); t < chip->ngpio; t++, mask <<= 1) {
- const char *label;
-
- label = gpiochip_is_requested(chip, t);
- if (!label)
- continue;
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mcp23s08 *mcp = gpiochip_get_data(gc);
- seq_printf(s, " gpio-%-3d P%c.%d (%-12s) %s %s %s\n",
- chip->base + t, bank, t, label,
- (iodir & mask) ? "in " : "out",
- (gpio & mask) ? "hi" : "lo",
- (gppu & mask) ? "up" : " ");
- /* NOTE: ignoring the irq-related registers */
- }
-done:
- mutex_unlock(&mcp->lock);
+ seq_puts(p, dev_name(mcp->dev));
}
-#else
-#define mcp23s08_dbg_show NULL
-#endif
+static const struct irq_chip mcp23s08_irq_chip = {
+ .irq_mask = mcp23s08_irq_mask,
+ .irq_unmask = mcp23s08_irq_unmask,
+ .irq_set_type = mcp23s08_irq_set_type,
+ .irq_bus_lock = mcp23s08_irq_bus_lock,
+ .irq_bus_sync_unlock = mcp23s08_irq_bus_unlock,
+ .irq_print_chip = mcp23s08_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
/*----------------------------------------------------------------------*/
-static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev,
- void *data, unsigned addr, unsigned type,
- unsigned int base, int cs)
+int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev,
+ unsigned int addr, unsigned int type, unsigned int base)
{
int status, ret;
bool mirror = false;
bool open_drain = false;
- struct regmap_config *one_regmap_config = NULL;
- int raw_chip_address = (addr & ~0x40) >> 1;
mutex_init(&mcp->lock);
mcp->dev = dev;
mcp->addr = addr;
+
mcp->irq_active_high = false;
mcp->chip.direction_input = mcp23s08_direction_input;
mcp->chip.get = mcp23s08_get;
+ mcp->chip.get_multiple = mcp23s08_get_multiple;
mcp->chip.direction_output = mcp23s08_direction_output;
mcp->chip.set = mcp23s08_set;
- mcp->chip.dbg_show = mcp23s08_dbg_show;
-#ifdef CONFIG_OF_GPIO
- mcp->chip.of_gpio_n_cells = 2;
- mcp->chip.of_node = dev->of_node;
-#endif
-
- switch (type) {
-#ifdef CONFIG_SPI_MASTER
- case MCP_TYPE_S08:
- case MCP_TYPE_S17:
- switch (type) {
- case MCP_TYPE_S08:
- one_regmap_config =
- devm_kmemdup(dev, &mcp23x08_regmap,
- sizeof(struct regmap_config), GFP_KERNEL);
- mcp->reg_shift = 0;
- mcp->chip.ngpio = 8;
- mcp->chip.label = devm_kasprintf(dev, GFP_KERNEL,
- "mcp23s08.%d", raw_chip_address);
- break;
- case MCP_TYPE_S17:
- one_regmap_config =
- devm_kmemdup(dev, &mcp23x17_regmap,
- sizeof(struct regmap_config), GFP_KERNEL);
- mcp->reg_shift = 1;
- mcp->chip.ngpio = 16;
- mcp->chip.label = devm_kasprintf(dev, GFP_KERNEL,
- "mcp23s17.%d", raw_chip_address);
- break;
- }
- if (!one_regmap_config)
- return -ENOMEM;
-
- one_regmap_config->name = devm_kasprintf(dev, GFP_KERNEL, "%d", raw_chip_address);
- mcp->regmap = devm_regmap_init(dev, &mcp23sxx_spi_regmap, mcp,
- one_regmap_config);
- break;
-
- case MCP_TYPE_S18:
- mcp->regmap = devm_regmap_init(dev, &mcp23sxx_spi_regmap, mcp,
- &mcp23x17_regmap);
- mcp->reg_shift = 1;
- mcp->chip.ngpio = 16;
- mcp->chip.label = "mcp23s18";
- break;
-#endif /* CONFIG_SPI_MASTER */
-
-#if IS_ENABLED(CONFIG_I2C)
- case MCP_TYPE_008:
- mcp->regmap = devm_regmap_init_i2c(data, &mcp23x08_regmap);
- mcp->reg_shift = 0;
- mcp->chip.ngpio = 8;
- mcp->chip.label = "mcp23008";
- break;
-
- case MCP_TYPE_017:
- mcp->regmap = devm_regmap_init_i2c(data, &mcp23x17_regmap);
- mcp->reg_shift = 1;
- mcp->chip.ngpio = 16;
- mcp->chip.label = "mcp23017";
- break;
-
- case MCP_TYPE_018:
- mcp->regmap = devm_regmap_init_i2c(data, &mcp23x17_regmap);
- mcp->reg_shift = 1;
- mcp->chip.ngpio = 16;
- mcp->chip.label = "mcp23018";
- break;
-#endif /* CONFIG_I2C */
-
- default:
- dev_err(dev, "invalid device type (%d)\n", type);
- return -EINVAL;
- }
-
- if (IS_ERR(mcp->regmap))
- return PTR_ERR(mcp->regmap);
+ mcp->chip.set_multiple = mcp23s08_set_multiple;
mcp->chip.base = base;
mcp->chip.can_sleep = true;
mcp->chip.parent = dev;
mcp->chip.owner = THIS_MODULE;
+ mcp->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+
/* verify MCP_IOCON.SEQOP = 0, so sequential reads work,
* and MCP_IOCON.HAEN = 1, so we work with all chips.
*/
ret = mcp_read(mcp, MCP_IOCON, &status);
if (ret < 0)
- goto fail;
+ return dev_err_probe(dev, ret, "can't identify chip %d\n", addr);
mcp->irq_controller =
device_property_read_bool(dev, "interrupt-controller");
@@ -916,27 +658,26 @@ static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev,
ret = mcp_write(mcp, MCP_IOCON, status);
if (ret < 0)
- goto fail;
+ return dev_err_probe(dev, ret, "can't write IOCON %d\n", addr);
}
if (mcp->irq && mcp->irq_controller) {
- ret = mcp23s08_irqchip_setup(mcp);
- if (ret)
- goto fail;
+ struct gpio_irq_chip *girq = &mcp->chip.irq;
+
+ gpio_irq_chip_set_chip(girq, &mcp23s08_irq_chip);
+ /* This will let us handle the parent IRQ in the driver */
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->handler = handle_simple_irq;
+ girq->threaded = true;
}
ret = devm_gpiochip_add_data(dev, &mcp->chip, mcp);
if (ret < 0)
- goto fail;
-
- if (one_regmap_config) {
- mcp->pinctrl_desc.name = devm_kasprintf(dev, GFP_KERNEL,
- "mcp23xxx-pinctrl.%d", raw_chip_address);
- if (!mcp->pinctrl_desc.name)
- return -ENOMEM;
- } else {
- mcp->pinctrl_desc.name = "mcp23xxx-pinctrl";
- }
+ return dev_err_probe(dev, ret, "can't add GPIO chip\n");
+
mcp->pinctrl_desc.pctlops = &mcp_pinctrl_ops;
mcp->pinctrl_desc.confops = &mcp_pinconf_ops;
mcp->pinctrl_desc.npins = mcp->chip.ngpio;
@@ -947,291 +688,18 @@ static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev,
mcp->pinctrl_desc.owner = THIS_MODULE;
mcp->pctldev = devm_pinctrl_register(dev, &mcp->pinctrl_desc, mcp);
- if (IS_ERR(mcp->pctldev)) {
- ret = PTR_ERR(mcp->pctldev);
- goto fail;
- }
+ if (IS_ERR(mcp->pctldev))
+ return dev_err_probe(dev, PTR_ERR(mcp->pctldev), "can't register controller\n");
- if (mcp->irq)
+ if (mcp->irq) {
ret = mcp23s08_irq_setup(mcp);
-
-fail:
- if (ret < 0)
- dev_dbg(dev, "can't setup chip %d, --> %d\n", addr, ret);
- return ret;
-}
-
-/*----------------------------------------------------------------------*/
-
-#ifdef CONFIG_OF
-#ifdef CONFIG_SPI_MASTER
-static const struct of_device_id mcp23s08_spi_of_match[] = {
- {
- .compatible = "microchip,mcp23s08",
- .data = (void *) MCP_TYPE_S08,
- },
- {
- .compatible = "microchip,mcp23s17",
- .data = (void *) MCP_TYPE_S17,
- },
- {
- .compatible = "microchip,mcp23s18",
- .data = (void *) MCP_TYPE_S18,
- },
-/* NOTE: The use of the mcp prefix is deprecated and will be removed. */
- {
- .compatible = "mcp,mcp23s08",
- .data = (void *) MCP_TYPE_S08,
- },
- {
- .compatible = "mcp,mcp23s17",
- .data = (void *) MCP_TYPE_S17,
- },
- { },
-};
-MODULE_DEVICE_TABLE(of, mcp23s08_spi_of_match);
-#endif
-
-#if IS_ENABLED(CONFIG_I2C)
-static const struct of_device_id mcp23s08_i2c_of_match[] = {
- {
- .compatible = "microchip,mcp23008",
- .data = (void *) MCP_TYPE_008,
- },
- {
- .compatible = "microchip,mcp23017",
- .data = (void *) MCP_TYPE_017,
- },
- {
- .compatible = "microchip,mcp23018",
- .data = (void *) MCP_TYPE_018,
- },
-/* NOTE: The use of the mcp prefix is deprecated and will be removed. */
- {
- .compatible = "mcp,mcp23008",
- .data = (void *) MCP_TYPE_008,
- },
- {
- .compatible = "mcp,mcp23017",
- .data = (void *) MCP_TYPE_017,
- },
- { },
-};
-MODULE_DEVICE_TABLE(of, mcp23s08_i2c_of_match);
-#endif
-#endif /* CONFIG_OF */
-
-
-#if IS_ENABLED(CONFIG_I2C)
-
-static int mcp230xx_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
-{
- struct mcp23s08_platform_data *pdata, local_pdata;
- struct mcp23s08 *mcp;
- int status;
-
- pdata = dev_get_platdata(&client->dev);
- if (!pdata) {
- pdata = &local_pdata;
- pdata->base = -1;
- }
-
- mcp = devm_kzalloc(&client->dev, sizeof(*mcp), GFP_KERNEL);
- if (!mcp)
- return -ENOMEM;
-
- mcp->irq = client->irq;
- status = mcp23s08_probe_one(mcp, &client->dev, client, client->addr,
- id->driver_data, pdata->base, 0);
- if (status)
- return status;
-
- i2c_set_clientdata(client, mcp);
-
- return 0;
-}
-
-static const struct i2c_device_id mcp230xx_id[] = {
- { "mcp23008", MCP_TYPE_008 },
- { "mcp23017", MCP_TYPE_017 },
- { "mcp23018", MCP_TYPE_018 },
- { },
-};
-MODULE_DEVICE_TABLE(i2c, mcp230xx_id);
-
-static struct i2c_driver mcp230xx_driver = {
- .driver = {
- .name = "mcp230xx",
- .of_match_table = of_match_ptr(mcp23s08_i2c_of_match),
- },
- .probe = mcp230xx_probe,
- .id_table = mcp230xx_id,
-};
-
-static int __init mcp23s08_i2c_init(void)
-{
- return i2c_add_driver(&mcp230xx_driver);
-}
-
-static void mcp23s08_i2c_exit(void)
-{
- i2c_del_driver(&mcp230xx_driver);
-}
-
-#else
-
-static int __init mcp23s08_i2c_init(void) { return 0; }
-static void mcp23s08_i2c_exit(void) { }
-
-#endif /* CONFIG_I2C */
-
-/*----------------------------------------------------------------------*/
-
-#ifdef CONFIG_SPI_MASTER
-
-static int mcp23s08_probe(struct spi_device *spi)
-{
- struct mcp23s08_platform_data *pdata, local_pdata;
- unsigned addr;
- int chips = 0;
- struct mcp23s08_driver_data *data;
- int status, type;
- unsigned ngpio = 0;
- const struct of_device_id *match;
-
- match = of_match_device(of_match_ptr(mcp23s08_spi_of_match), &spi->dev);
- if (match)
- type = (int)(uintptr_t)match->data;
- else
- type = spi_get_device_id(spi)->driver_data;
-
- pdata = dev_get_platdata(&spi->dev);
- if (!pdata) {
- pdata = &local_pdata;
- pdata->base = -1;
-
- status = device_property_read_u32(&spi->dev,
- "microchip,spi-present-mask", &pdata->spi_present_mask);
- if (status) {
- status = device_property_read_u32(&spi->dev,
- "mcp,spi-present-mask",
- &pdata->spi_present_mask);
-
- if (status) {
- dev_err(&spi->dev, "missing spi-present-mask");
- return -ENODEV;
- }
- }
- }
-
- if (!pdata->spi_present_mask || pdata->spi_present_mask > 0xff) {
- dev_err(&spi->dev, "invalid spi-present-mask");
- return -ENODEV;
- }
-
- for (addr = 0; addr < MCP_MAX_DEV_PER_CS; addr++) {
- if (pdata->spi_present_mask & BIT(addr))
- chips++;
- }
-
- if (!chips)
- return -ENODEV;
-
- data = devm_kzalloc(&spi->dev,
- sizeof(*data) + chips * sizeof(struct mcp23s08),
- GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- spi_set_drvdata(spi, data);
-
- for (addr = 0; addr < MCP_MAX_DEV_PER_CS; addr++) {
- if (!(pdata->spi_present_mask & BIT(addr)))
- continue;
- chips--;
- data->mcp[addr] = &data->chip[chips];
- data->mcp[addr]->irq = spi->irq;
- status = mcp23s08_probe_one(data->mcp[addr], &spi->dev, spi,
- 0x40 | (addr << 1), type,
- pdata->base, addr);
- if (status < 0)
- return status;
-
- if (pdata->base != -1)
- pdata->base += data->mcp[addr]->chip.ngpio;
- ngpio += data->mcp[addr]->chip.ngpio;
+ if (ret)
+ return dev_err_probe(dev, ret, "can't setup IRQ\n");
}
- data->ngpio = ngpio;
return 0;
}
+EXPORT_SYMBOL_GPL(mcp23s08_probe_one);
-static const struct spi_device_id mcp23s08_ids[] = {
- { "mcp23s08", MCP_TYPE_S08 },
- { "mcp23s17", MCP_TYPE_S17 },
- { "mcp23s18", MCP_TYPE_S18 },
- { },
-};
-MODULE_DEVICE_TABLE(spi, mcp23s08_ids);
-
-static struct spi_driver mcp23s08_driver = {
- .probe = mcp23s08_probe,
- .id_table = mcp23s08_ids,
- .driver = {
- .name = "mcp23s08",
- .of_match_table = of_match_ptr(mcp23s08_spi_of_match),
- },
-};
-
-static int __init mcp23s08_spi_init(void)
-{
- return spi_register_driver(&mcp23s08_driver);
-}
-
-static void mcp23s08_spi_exit(void)
-{
- spi_unregister_driver(&mcp23s08_driver);
-}
-
-#else
-
-static int __init mcp23s08_spi_init(void) { return 0; }
-static void mcp23s08_spi_exit(void) { }
-
-#endif /* CONFIG_SPI_MASTER */
-
-/*----------------------------------------------------------------------*/
-
-static int __init mcp23s08_init(void)
-{
- int ret;
-
- ret = mcp23s08_spi_init();
- if (ret)
- goto spi_fail;
-
- ret = mcp23s08_i2c_init();
- if (ret)
- goto i2c_fail;
-
- return 0;
-
- i2c_fail:
- mcp23s08_spi_exit();
- spi_fail:
- return ret;
-}
-/* register after spi/i2c postcore initcall and before
- * subsys initcalls that may rely on these GPIOs
- */
-subsys_initcall(mcp23s08_init);
-
-static void __exit mcp23s08_exit(void)
-{
- mcp23s08_spi_exit();
- mcp23s08_i2c_exit();
-}
-module_exit(mcp23s08_exit);
-
+MODULE_DESCRIPTION("MCP23S08 SPI/I2C GPIO driver");
MODULE_LICENSE("GPL");