Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 105832
b: refs/heads/master
c: 8f1cc3b
h: refs/heads/master
v: v3
  • Loading branch information
David Brownell authored and Linus Torvalds committed Jul 25, 2008
1 parent 6909ae4 commit a92fd78
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 41 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: d8f388d8dc8d4f36539dd37c1fff62cc404ea0fc
refs/heads/master: 8f1cc3b10e6ee0c5c7c8ed27f8771c4f252b4862
133 changes: 102 additions & 31 deletions trunk/drivers/gpio/mcp23s08.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,26 @@ struct mcp23s08 {
struct spi_device *spi;
u8 addr;

u8 cache[11];
/* lock protects the cached values */
struct mutex lock;
u8 cache[11];

struct gpio_chip chip;

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)
{
u8 tx[2], rx[1];
Expand Down Expand Up @@ -208,25 +219,18 @@ static void mcp23s08_dbg_show(struct seq_file *s, struct gpio_chip *chip)

/*----------------------------------------------------------------------*/

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_platform_data *pdata;
struct mcp23s08_driver_data *data = spi_get_drvdata(spi);
struct mcp23s08 *mcp = data->mcp[addr];
int status;
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);

mcp->spi = spi;
mcp->addr = 0x40 | (pdata->slave << 1);
mcp->addr = 0x40 | (addr << 1);

mcp->chip.label = "mcp23s08",

Expand All @@ -236,27 +240,28 @@ static int mcp23s08_probe(struct spi_device *spi)
mcp->chip.set = mcp23s08_set;
mcp->chip.dbg_show = mcp23s08_dbg_show;

mcp->chip.base = pdata->base;
mcp->chip.base = base;
mcp->chip.ngpio = 8;
mcp->chip.can_sleep = 1;
mcp->chip.dev = &spi->dev;
mcp->chip.owner = THIS_MODULE;

spi_set_drvdata(spi, mcp);

/* verify MCP_IOCON.SEQOP = 0, so sequential reads work */
/* verify MCP_IOCON.SEQOP = 0, so sequential reads work,
* and MCP_IOCON.HAEN = 1, so we work with all chips.
*/
status = mcp23s08_read(mcp, MCP_IOCON);
if (status < 0)
goto fail;
if (status & IOCON_SEQOP) {
if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) {
status &= ~IOCON_SEQOP;
status |= IOCON_HAEN;
status = mcp23s08_write(mcp, MCP_IOCON, (u8) status);
if (status < 0)
goto fail;
}

/* configure ~100K pullups */
status = mcp23s08_write(mcp, MCP_GPPU, pdata->pullups);
status = mcp23s08_write(mcp, MCP_GPPU, pullups);
if (status < 0)
goto fail;

Expand All @@ -283,52 +288,119 @@ static int mcp23s08_probe(struct spi_device *spi)
tx[1] = MCP_IPOL;
memcpy(&tx[2], &mcp->cache[MCP_IPOL], sizeof(tx) - 2);
status = spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0);

/* FIXME check status... */
if (status < 0)
goto fail;
}

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
* per-signal masking and level/edge triggering. It's not yet
* handled here...
*/

if (pdata->setup) {
status = pdata->setup(spi, mcp->chip.base,
mcp->chip.ngpio, pdata->context);
status = pdata->setup(spi,
pdata->base, data->ngpio,
pdata->context);
if (status < 0)
dev_dbg(&spi->dev, "setup --> %d\n", status);
}

return 0;

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;
}

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;
unsigned addr;
int status = 0;

if (pdata->teardown) {
status = pdata->teardown(spi,
mcp->chip.base, mcp->chip.ngpio,
pdata->base, data->ngpio,
pdata->context);
if (status < 0) {
dev_err(&spi->dev, "%s --> %d\n", "teardown", status);
return status;
}
}

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)
kfree(mcp);
else
dev_err(&spi->dev, "%s --> %d\n", "remove", status);
kfree(data);
return status;
}

Expand Down Expand Up @@ -356,4 +428,3 @@ static void __exit mcp23s08_exit(void)
module_exit(mcp23s08_exit);

MODULE_LICENSE("GPL");

25 changes: 16 additions & 9 deletions trunk/include/linux/spi/mcp23s08.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@

/* FIXME driver should be able to handle all four slaves that
* can be hooked up to each chipselect, as well as IRQs...
*/
/* FIXME driver should be able to handle IRQs... */

struct mcp23s08_chip_info {
bool is_present; /* true iff populated */
u8 pullups; /* BIT(x) means enable pullup x */
};

struct mcp23s08_platform_data {
/* four slaves can share one SPI chipselect */
u8 slave;
/* Four slaves (numbered 0..3) can share one SPI chipselect, and
* 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;

/* pins with pullups */
u8 pullups;

void *context; /* param to setup/teardown */

int (*setup)(struct spi_device *spi,
Expand Down

0 comments on commit a92fd78

Please sign in to comment.