Commit 8f1cc3b1 authored by David Brownell's avatar David Brownell Committed by Linus Torvalds

gpio: mcp23s08 handles multiple chips per chipselect

Teach the mcp23s08 driver about a curious feature of these chips: up to
four of them can share the same chipselect, with the SPI signals wired in
parallel, by matching two bits in the first protocol byte against two
address lines on the chip.

This is handled by three software changes:

  * Platform data now holds an array of per-chip structs, not
    just one chip's address and pullup configuration.

  * Probe() and remove() now use another level of structure,
    wrapping an instance of the original structure for each
    mcp23s08 chip sharing that chipselect.

  * The HAEN bit is set, so that the hardware address bits can no
    longer be ignored (boot firmware may not have enabled them).

The "one struct per chip" preserves the guts of the current code,
but platform_data will need minor changes.

    OLD:
	/* incorrect "slave" ID may not have mattered */
	.slave = 3,
	.pullups = BIT(3) | BIT(1) | BIT(0),

    NEW:
	/* slave address _must_ match chip's wiring */
	.chip[3] = {
		.is_present = true,
		.pullups = BIT(3) | BIT(1) | BIT(0),
	},

There's no change in how things _behave_ for spi_device nodes with a
single mcp23s08 chip.  New multi-chip configurations assign GPIOs in
sequence, without holes.  The spi_device just resembles a bigger
controller, but internally it has multiple gpio_chip instances.
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent d8f388d8
...@@ -40,15 +40,26 @@ struct mcp23s08 { ...@@ -40,15 +40,26 @@ struct mcp23s08 {
struct spi_device *spi; struct spi_device *spi;
u8 addr; u8 addr;
u8 cache[11];
/* lock protects the cached values */ /* lock protects the cached values */
struct mutex lock; struct mutex lock;
u8 cache[11];
struct gpio_chip chip; struct gpio_chip chip;
struct work_struct work; struct work_struct work;
}; };
/* A given spi_device can represent up to four mcp23s08 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[4];
struct mcp23s08 chip[];
};
static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg)
{ {
u8 tx[2], rx[1]; u8 tx[2], rx[1];
...@@ -208,25 +219,18 @@ done: ...@@ -208,25 +219,18 @@ done:
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
static int mcp23s08_probe(struct spi_device *spi) static int mcp23s08_probe_one(struct spi_device *spi, unsigned addr,
unsigned base, unsigned pullups)
{ {
struct mcp23s08 *mcp; struct mcp23s08_driver_data *data = spi_get_drvdata(spi);
struct mcp23s08_platform_data *pdata; struct mcp23s08 *mcp = data->mcp[addr];
int status; int status;
int do_update = 0; int do_update = 0;
pdata = spi->dev.platform_data;
if (!pdata || pdata->slave > 3 || !pdata->base)
return -ENODEV;
mcp = kzalloc(sizeof *mcp, GFP_KERNEL);
if (!mcp)
return -ENOMEM;
mutex_init(&mcp->lock); mutex_init(&mcp->lock);
mcp->spi = spi; mcp->spi = spi;
mcp->addr = 0x40 | (pdata->slave << 1); mcp->addr = 0x40 | (addr << 1);
mcp->chip.label = "mcp23s08", mcp->chip.label = "mcp23s08",
...@@ -236,27 +240,28 @@ static int mcp23s08_probe(struct spi_device *spi) ...@@ -236,27 +240,28 @@ static int mcp23s08_probe(struct spi_device *spi)
mcp->chip.set = mcp23s08_set; mcp->chip.set = mcp23s08_set;
mcp->chip.dbg_show = mcp23s08_dbg_show; mcp->chip.dbg_show = mcp23s08_dbg_show;
mcp->chip.base = pdata->base; mcp->chip.base = base;
mcp->chip.ngpio = 8; mcp->chip.ngpio = 8;
mcp->chip.can_sleep = 1; mcp->chip.can_sleep = 1;
mcp->chip.dev = &spi->dev; mcp->chip.dev = &spi->dev;
mcp->chip.owner = THIS_MODULE; mcp->chip.owner = THIS_MODULE;
spi_set_drvdata(spi, mcp); /* verify MCP_IOCON.SEQOP = 0, so sequential reads work,
* and MCP_IOCON.HAEN = 1, so we work with all chips.
/* verify MCP_IOCON.SEQOP = 0, so sequential reads work */ */
status = mcp23s08_read(mcp, MCP_IOCON); status = mcp23s08_read(mcp, MCP_IOCON);
if (status < 0) if (status < 0)
goto fail; goto fail;
if (status & IOCON_SEQOP) { if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) {
status &= ~IOCON_SEQOP; status &= ~IOCON_SEQOP;
status |= IOCON_HAEN;
status = mcp23s08_write(mcp, MCP_IOCON, (u8) status); status = mcp23s08_write(mcp, MCP_IOCON, (u8) status);
if (status < 0) if (status < 0)
goto fail; goto fail;
} }
/* configure ~100K pullups */ /* configure ~100K pullups */
status = mcp23s08_write(mcp, MCP_GPPU, pdata->pullups); status = mcp23s08_write(mcp, MCP_GPPU, pullups);
if (status < 0) if (status < 0)
goto fail; goto fail;
...@@ -283,11 +288,58 @@ static int mcp23s08_probe(struct spi_device *spi) ...@@ -283,11 +288,58 @@ static int mcp23s08_probe(struct spi_device *spi)
tx[1] = MCP_IPOL; tx[1] = MCP_IPOL;
memcpy(&tx[2], &mcp->cache[MCP_IPOL], sizeof(tx) - 2); memcpy(&tx[2], &mcp->cache[MCP_IPOL], sizeof(tx) - 2);
status = spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0); status = spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0);
if (status < 0)
/* FIXME check status... */ goto fail;
} }
status = gpiochip_add(&mcp->chip); status = gpiochip_add(&mcp->chip);
fail:
if (status < 0)
dev_dbg(&spi->dev, "can't setup chip %d, --> %d\n",
addr, status);
return status;
}
static int mcp23s08_probe(struct spi_device *spi)
{
struct mcp23s08_platform_data *pdata;
unsigned addr;
unsigned chips = 0;
struct mcp23s08_driver_data *data;
int status;
unsigned base;
pdata = spi->dev.platform_data;
if (!pdata || !gpio_is_valid(pdata->base))
return -ENODEV;
for (addr = 0; addr < 4; addr++) {
if (!pdata->chip[addr].is_present)
continue;
chips++;
}
if (!chips)
return -ENODEV;
data = kzalloc(sizeof *data + chips * sizeof(struct mcp23s08),
GFP_KERNEL);
if (!data)
return -ENOMEM;
spi_set_drvdata(spi, data);
base = pdata->base;
for (addr = 0; addr < 4; addr++) {
if (!pdata->chip[addr].is_present)
continue;
chips--;
data->mcp[addr] = &data->chip[chips];
status = mcp23s08_probe_one(spi, addr, base,
pdata->chip[addr].pullups);
if (status < 0)
goto fail;
base += 8;
}
data->ngpio = base - pdata->base;
/* NOTE: these chips have a relatively sane IRQ framework, with /* NOTE: these chips have a relatively sane IRQ framework, with
* per-signal masking and level/edge triggering. It's not yet * per-signal masking and level/edge triggering. It's not yet
...@@ -295,8 +347,9 @@ static int mcp23s08_probe(struct spi_device *spi) ...@@ -295,8 +347,9 @@ static int mcp23s08_probe(struct spi_device *spi)
*/ */
if (pdata->setup) { if (pdata->setup) {
status = pdata->setup(spi, mcp->chip.base, status = pdata->setup(spi,
mcp->chip.ngpio, pdata->context); pdata->base, data->ngpio,
pdata->context);
if (status < 0) if (status < 0)
dev_dbg(&spi->dev, "setup --> %d\n", status); dev_dbg(&spi->dev, "setup --> %d\n", status);
} }
...@@ -304,19 +357,29 @@ static int mcp23s08_probe(struct spi_device *spi) ...@@ -304,19 +357,29 @@ static int mcp23s08_probe(struct spi_device *spi)
return 0; return 0;
fail: fail:
kfree(mcp); for (addr = 0; addr < 4; addr++) {
int tmp;
if (!data->mcp[addr])
continue;
tmp = gpiochip_remove(&data->mcp[addr]->chip);
if (tmp < 0)
dev_err(&spi->dev, "%s --> %d\n", "remove", tmp);
}
kfree(data);
return status; return status;
} }
static int mcp23s08_remove(struct spi_device *spi) static int mcp23s08_remove(struct spi_device *spi)
{ {
struct mcp23s08 *mcp = spi_get_drvdata(spi); struct mcp23s08_driver_data *data = spi_get_drvdata(spi);
struct mcp23s08_platform_data *pdata = spi->dev.platform_data; struct mcp23s08_platform_data *pdata = spi->dev.platform_data;
unsigned addr;
int status = 0; int status = 0;
if (pdata->teardown) { if (pdata->teardown) {
status = pdata->teardown(spi, status = pdata->teardown(spi,
mcp->chip.base, mcp->chip.ngpio, pdata->base, data->ngpio,
pdata->context); pdata->context);
if (status < 0) { if (status < 0) {
dev_err(&spi->dev, "%s --> %d\n", "teardown", status); dev_err(&spi->dev, "%s --> %d\n", "teardown", status);
...@@ -324,11 +387,20 @@ static int mcp23s08_remove(struct spi_device *spi) ...@@ -324,11 +387,20 @@ static int mcp23s08_remove(struct spi_device *spi)
} }
} }
status = gpiochip_remove(&mcp->chip); for (addr = 0; addr < 4; addr++) {
int tmp;
if (!data->mcp[addr])
continue;
tmp = gpiochip_remove(&data->mcp[addr]->chip);
if (tmp < 0) {
dev_err(&spi->dev, "%s --> %d\n", "remove", tmp);
status = tmp;
}
}
if (status == 0) if (status == 0)
kfree(mcp); kfree(data);
else
dev_err(&spi->dev, "%s --> %d\n", "remove", status);
return status; return status;
} }
...@@ -356,4 +428,3 @@ static void __exit mcp23s08_exit(void) ...@@ -356,4 +428,3 @@ static void __exit mcp23s08_exit(void)
module_exit(mcp23s08_exit); module_exit(mcp23s08_exit);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
/* FIXME driver should be able to handle all four slaves that /* FIXME driver should be able to handle IRQs... */
* can be hooked up to each chipselect, as well as IRQs...
*/ struct mcp23s08_chip_info {
bool is_present; /* true iff populated */
u8 pullups; /* BIT(x) means enable pullup x */
};
struct mcp23s08_platform_data { struct mcp23s08_platform_data {
/* four slaves can share one SPI chipselect */ /* Four slaves (numbered 0..3) can share one SPI chipselect, and
u8 slave; * will provide 8..32 GPIOs using 1..4 gpio_chip instances.
*/
struct mcp23s08_chip_info chip[4];
/* number assigned to the first GPIO */ /* "base" is the number of the first GPIO. Dynamic assignment is
* not currently supported, and even if there are gaps in chip
* addressing the GPIO numbers are sequential .. so for example
* if only slaves 0 and 3 are present, their GPIOs range from
* base to base+15.
*/
unsigned base; unsigned base;
/* pins with pullups */
u8 pullups;
void *context; /* param to setup/teardown */ void *context; /* param to setup/teardown */
int (*setup)(struct spi_device *spi, int (*setup)(struct spi_device *spi,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment