--- zzzz-none-000/linux-3.10.107/drivers/gpio/gpio-mcp23s08.c 2017-06-27 09:49:32.000000000 +0000 +++ scorpion-7490-727/linux-3.10.107/drivers/gpio/gpio-mcp23s08.c 2021-02-04 17:41:59.000000000 +0000 @@ -1,5 +1,13 @@ /* - * MCP23S08 SPI/GPIO gpio expander driver + * 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 @@ -12,7 +20,8 @@ #include #include #include -#include +#include +#include #include /** @@ -34,6 +43,7 @@ #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) @@ -55,10 +65,17 @@ struct mcp23s08 { u8 addr; + bool irq_active_high; 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 irq_domain *irq_domain; struct gpio_chip chip; @@ -77,6 +94,11 @@ struct mcp23s08 chip[]; }; +/* This lock class tells lockdep that GPIO irqs are in a different + * category than their parents, so it won't report false recursion. + */ +static struct lock_class_key gpio_lock_class; + /*----------------------------------------------------------------------*/ #if IS_ENABLED(CONFIG_I2C) @@ -152,7 +174,7 @@ tx[0] = mcp->addr | 0x01; tx[1] = reg; - status = spi_write_then_read(mcp->data, tx, sizeof tx, rx, sizeof rx); + status = spi_write_then_read(mcp->data, tx, sizeof(tx), rx, sizeof(rx)); return (status < 0) ? status : rx[0]; } @@ -163,7 +185,7 @@ tx[0] = mcp->addr; tx[1] = reg; tx[2] = val; - return spi_write_then_read(mcp->data, tx, sizeof tx, NULL, 0); + return spi_write_then_read(mcp->data, tx, sizeof(tx), NULL, 0); } static int @@ -172,13 +194,13 @@ u8 tx[2], *tmp; int status; - if ((n + reg) > sizeof mcp->cache) + if ((n + reg) > sizeof(mcp->cache)) return -EINVAL; tx[0] = mcp->addr | 0x01; tx[1] = reg; tmp = (u8 *)vals; - status = spi_write_then_read(mcp->data, tx, sizeof tx, tmp, n); + status = spi_write_then_read(mcp->data, tx, sizeof(tx), tmp, n); if (status >= 0) { while (n--) vals[n] = tmp[n]; /* expand to 16bit */ @@ -193,7 +215,7 @@ tx[0] = mcp->addr | 0x01; tx[1] = reg << 1; - status = spi_write_then_read(mcp->data, tx, sizeof tx, rx, sizeof rx); + status = spi_write_then_read(mcp->data, tx, sizeof(tx), rx, sizeof(rx)); return (status < 0) ? status : (rx[0] | (rx[1] << 8)); } @@ -205,7 +227,7 @@ tx[1] = reg << 1; tx[2] = val; tx[3] = val >> 8; - return spi_write_then_read(mcp->data, tx, sizeof tx, NULL, 0); + return spi_write_then_read(mcp->data, tx, sizeof(tx), NULL, 0); } static int @@ -214,12 +236,12 @@ u8 tx[2]; int status; - if ((n + reg) > sizeof mcp->cache) + if ((n + reg) > sizeof(mcp->cache)) return -EINVAL; tx[0] = mcp->addr | 0x01; tx[1] = reg << 1; - status = spi_write_then_read(mcp->data, tx, sizeof tx, + status = spi_write_then_read(mcp->data, tx, sizeof(tx), (u8 *)vals, n * 2); if (status >= 0) { while (n--) @@ -316,6 +338,194 @@ } /*----------------------------------------------------------------------*/ +static irqreturn_t mcp23s08_irq(int irq, void *data) +{ + struct mcp23s08 *mcp = data; + int intcap, intf, i; + unsigned int child_irq; + + mutex_lock(&mcp->lock); + intf = mcp->ops->read(mcp, MCP_INTF); + if (intf < 0) { + mutex_unlock(&mcp->lock); + return IRQ_HANDLED; + } + + mcp->cache[MCP_INTF] = intf; + + intcap = mcp->ops->read(mcp, MCP_INTCAP); + if (intcap < 0) { + mutex_unlock(&mcp->lock); + return IRQ_HANDLED; + } + + mcp->cache[MCP_INTCAP] = intcap; + mutex_unlock(&mcp->lock); + + + for (i = 0; i < mcp->chip.ngpio; i++) { + if ((BIT(i) & mcp->cache[MCP_INTF]) && + ((BIT(i) & intcap & mcp->irq_rise) || + (mcp->irq_fall & ~intcap & BIT(i)))) { + child_irq = irq_find_mapping(mcp->irq_domain, i); + handle_nested_irq(child_irq); + } + } + + return IRQ_HANDLED; +} + +static int mcp23s08_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); + + return irq_find_mapping(mcp->irq_domain, offset); +} + +static void mcp23s08_irq_mask(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + unsigned int pos = data->hwirq; + + mcp->cache[MCP_GPINTEN] &= ~BIT(pos); +} + +static void mcp23s08_irq_unmask(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + unsigned int pos = data->hwirq; + + mcp->cache[MCP_GPINTEN] |= BIT(pos); +} + +static int mcp23s08_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + unsigned int pos = data->hwirq; + int status = 0; + + if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) { + mcp->cache[MCP_INTCON] &= ~BIT(pos); + mcp->irq_rise |= BIT(pos); + mcp->irq_fall |= BIT(pos); + } else if (type & IRQ_TYPE_EDGE_RISING) { + mcp->cache[MCP_INTCON] &= ~BIT(pos); + mcp->irq_rise |= BIT(pos); + mcp->irq_fall &= ~BIT(pos); + } else if (type & IRQ_TYPE_EDGE_FALLING) { + mcp->cache[MCP_INTCON] &= ~BIT(pos); + mcp->irq_rise &= ~BIT(pos); + mcp->irq_fall |= BIT(pos); + } else + return -EINVAL; + + return status; +} + +static void mcp23s08_irq_bus_lock(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + + mutex_lock(&mcp->irq_lock); +} + +static void mcp23s08_irq_bus_unlock(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + + mutex_lock(&mcp->lock); + mcp->ops->write(mcp, MCP_GPINTEN, mcp->cache[MCP_GPINTEN]); + mcp->ops->write(mcp, MCP_DEFVAL, mcp->cache[MCP_DEFVAL]); + mcp->ops->write(mcp, MCP_INTCON, mcp->cache[MCP_INTCON]); + mutex_unlock(&mcp->lock); + mutex_unlock(&mcp->irq_lock); +} + +static int mcp23s08_irq_reqres(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + + if (gpiochip_lock_as_irq(&mcp->chip, data->hwirq)) { + dev_err(mcp->chip.dev, + "unable to lock HW IRQ %lu for IRQ usage\n", + data->hwirq); + return -EINVAL; + } + + return 0; +} + +static void mcp23s08_irq_relres(struct irq_data *data) +{ + struct mcp23s08 *mcp = irq_data_get_irq_chip_data(data); + + gpiochip_unlock_as_irq(&mcp->chip, data->hwirq); +} + +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, + .irq_request_resources = mcp23s08_irq_reqres, + .irq_release_resources = mcp23s08_irq_relres, +}; + +static int mcp23s08_irq_setup(struct mcp23s08 *mcp) +{ + struct gpio_chip *chip = &mcp->chip; + int err, irq, j; + unsigned long irqflags = IRQF_ONESHOT | IRQF_SHARED; + + mutex_init(&mcp->irq_lock); + + mcp->irq_domain = irq_domain_add_linear(chip->dev->of_node, chip->ngpio, + &irq_domain_simple_ops, mcp); + if (!mcp->irq_domain) + return -ENODEV; + + if (mcp->irq_active_high) + irqflags |= IRQF_TRIGGER_HIGH; + else + irqflags |= IRQF_TRIGGER_LOW; + + err = devm_request_threaded_irq(chip->dev, mcp->irq, NULL, mcp23s08_irq, + irqflags, dev_name(chip->dev), mcp); + if (err != 0) { + dev_err(chip->dev, "unable to request IRQ#%d: %d\n", + mcp->irq, err); + return err; + } + + chip->to_irq = mcp23s08_gpio_to_irq; + + for (j = 0; j < mcp->chip.ngpio; j++) { + irq = irq_create_mapping(mcp->irq_domain, j); + irq_set_lockdep_class(irq, &gpio_lock_class); + irq_set_chip_data(irq, mcp); + irq_set_chip(irq, &mcp23s08_irq_chip); + irq_set_nested_thread(irq, true); + irq_set_noprobe(irq); + } + return 0; +} + +static void mcp23s08_irq_teardown(struct mcp23s08 *mcp) +{ + unsigned int irq, i; + + for (i = 0; i < mcp->chip.ngpio; i++) { + irq = irq_find_mapping(mcp->irq_domain, i); + if (irq > 0) + irq_dispose_mapping(irq); + } + + irq_domain_remove(mcp->irq_domain); +} + +/*----------------------------------------------------------------------*/ #ifdef CONFIG_DEBUG_FS @@ -357,7 +567,7 @@ (mcp->cache[MCP_GPIO] & mask) ? "hi" : "lo", (mcp->cache[MCP_GPPU] & mask) ? "up" : " "); /* NOTE: ignoring the irq-related registers */ - seq_printf(s, "\n"); + seq_puts(s, "\n"); } done: mutex_unlock(&mcp->lock); @@ -370,15 +580,17 @@ /*----------------------------------------------------------------------*/ static int mcp23s08_probe_one(struct mcp23s08 *mcp, struct device *dev, - void *data, unsigned addr, - unsigned type, unsigned base, unsigned pullups) + void *data, unsigned addr, unsigned type, + struct mcp23s08_platform_data *pdata, int cs) { int status; + bool mirror = false; mutex_init(&mcp->lock); mcp->data = data; mcp->addr = addr; + mcp->irq_active_high = false; mcp->chip.direction_input = mcp23s08_direction_input; mcp->chip.get = mcp23s08_get; @@ -424,28 +636,49 @@ return -EINVAL; } - mcp->chip.base = base; - mcp->chip.can_sleep = 1; + mcp->chip.base = pdata->base; + mcp->chip.can_sleep = true; mcp->chip.dev = dev; mcp->chip.owner = THIS_MODULE; /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, * and MCP_IOCON.HAEN = 1, so we work with all chips. */ + status = mcp->ops->read(mcp, MCP_IOCON); if (status < 0) goto fail; - if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) { + + mcp->irq_controller = pdata->irq_controller; + if (mcp->irq && mcp->irq_controller) { + mcp->irq_active_high = + of_property_read_bool(mcp->chip.dev->of_node, + "microchip,irq-active-high"); + + if (type == MCP_TYPE_017) + mirror = pdata->mirror; + } + + if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN) || mirror || + mcp->irq_active_high) { /* mcp23s17 has IOCON twice, make sure they are in sync */ status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); status |= IOCON_HAEN | (IOCON_HAEN << 8); + if (mcp->irq_active_high) + status |= IOCON_INTPOL | (IOCON_INTPOL << 8); + else + status &= ~(IOCON_INTPOL | (IOCON_INTPOL << 8)); + + if (mirror) + status |= IOCON_MIRROR | (IOCON_MIRROR << 8); + status = mcp->ops->write(mcp, MCP_IOCON, status); if (status < 0) goto fail; } /* configure ~100K pullups */ - status = mcp->ops->write(mcp, MCP_GPPU, pullups); + status = mcp->ops->write(mcp, MCP_GPPU, pdata->chip[cs].pullups); if (status < 0) goto fail; @@ -470,6 +703,16 @@ } status = gpiochip_add(&mcp->chip); + if (status < 0) + goto fail; + + if (mcp->irq && mcp->irq_controller) { + status = mcp23s08_irq_setup(mcp); + if (status) { + mcp23s08_irq_teardown(mcp); + goto fail; + } + } fail: if (status < 0) dev_dbg(dev, "can't setup chip %d, --> %d\n", @@ -481,12 +724,23 @@ #ifdef CONFIG_OF #ifdef CONFIG_SPI_MASTER -static struct of_device_id mcp23s08_spi_of_match[] = { +static const struct of_device_id mcp23s08_spi_of_match[] = { + { + .compatible = "microchip,mcp23s08", + .data = (void *) MCP_TYPE_S08, + }, { - .compatible = "mcp,mcp23s08", .data = (void *) MCP_TYPE_S08, + .compatible = "microchip,mcp23s17", + .data = (void *) MCP_TYPE_S17, }, +/* NOTE: The use of the mcp prefix is deprecated and will be removed. */ { - .compatible = "mcp,mcp23s17", .data = (void *) MCP_TYPE_S17, + .compatible = "mcp,mcp23s08", + .data = (void *) MCP_TYPE_S08, + }, + { + .compatible = "mcp,mcp23s17", + .data = (void *) MCP_TYPE_S17, }, { }, }; @@ -494,12 +748,23 @@ #endif #if IS_ENABLED(CONFIG_I2C) -static struct of_device_id mcp23s08_i2c_of_match[] = { +static const struct of_device_id mcp23s08_i2c_of_match[] = { + { + .compatible = "microchip,mcp23008", + .data = (void *) MCP_TYPE_008, + }, { - .compatible = "mcp,mcp23008", .data = (void *) MCP_TYPE_008, + .compatible = "microchip,mcp23017", + .data = (void *) MCP_TYPE_017, }, +/* NOTE: The use of the mcp prefix is deprecated and will be removed. */ { - .compatible = "mcp,mcp23017", .data = (void *) MCP_TYPE_017, + .compatible = "mcp,mcp23008", + .data = (void *) MCP_TYPE_008, + }, + { + .compatible = "mcp,mcp23017", + .data = (void *) MCP_TYPE_017, }, { }, }; @@ -513,33 +778,40 @@ static int mcp230xx_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct mcp23s08_platform_data *pdata; + struct mcp23s08_platform_data *pdata, local_pdata; struct mcp23s08 *mcp; - int status, base, pullups; + int status; const struct of_device_id *match; match = of_match_device(of_match_ptr(mcp23s08_i2c_of_match), &client->dev); if (match) { - base = -1; - pullups = 0; + pdata = &local_pdata; + pdata->base = -1; + pdata->chip[0].pullups = 0; + pdata->irq_controller = of_property_read_bool( + client->dev.of_node, + "interrupt-controller"); + pdata->mirror = of_property_read_bool(client->dev.of_node, + "microchip,irq-mirror"); + client->irq = irq_of_parse_and_map(client->dev.of_node, 0); } else { - pdata = client->dev.platform_data; - if (!pdata || !gpio_is_valid(pdata->base)) { - dev_dbg(&client->dev, - "invalid or missing platform data\n"); - return -EINVAL; + pdata = dev_get_platdata(&client->dev); + if (!pdata) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct mcp23s08_platform_data), + GFP_KERNEL); + pdata->base = -1; } - base = pdata->base; - pullups = pdata->chip[0].pullups; } - mcp = kzalloc(sizeof *mcp, GFP_KERNEL); + mcp = kzalloc(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, base, pullups); + id->driver_data, pdata, 0); if (status) goto fail; @@ -556,13 +828,14 @@ static int mcp230xx_remove(struct i2c_client *client) { struct mcp23s08 *mcp = i2c_get_clientdata(client); - int status; - status = gpiochip_remove(&mcp->chip); - if (status == 0) - kfree(mcp); + if (client->irq && mcp->irq_controller) + mcp23s08_irq_teardown(mcp); - return status; + gpiochip_remove(&mcp->chip); + kfree(mcp); + + return 0; } static const struct i2c_device_id mcp230xx_id[] = { @@ -575,7 +848,6 @@ static struct i2c_driver mcp230xx_driver = { .driver = { .name = "mcp230xx", - .owner = THIS_MODULE, .of_match_table = of_match_ptr(mcp23s08_i2c_of_match), }, .probe = mcp230xx_probe, @@ -606,40 +878,54 @@ static int mcp23s08_probe(struct spi_device *spi) { - struct mcp23s08_platform_data *pdata; + struct mcp23s08_platform_data *pdata, local_pdata; unsigned addr; - unsigned chips = 0; + int chips = 0; struct mcp23s08_driver_data *data; int status, type; - unsigned base = -1, - ngpio = 0, - pullups[ARRAY_SIZE(pdata->chip)]; + unsigned ngpio = 0; const struct of_device_id *match; u32 spi_present_mask = 0; match = of_match_device(of_match_ptr(mcp23s08_spi_of_match), &spi->dev); if (match) { - type = (int)match->data; + type = (int)(uintptr_t)match->data; status = of_property_read_u32(spi->dev.of_node, - "mcp,spi-present-mask", &spi_present_mask); + "microchip,spi-present-mask", &spi_present_mask); if (status) { - dev_err(&spi->dev, "DT has no spi-present-mask\n"); - return -ENODEV; + status = of_property_read_u32(spi->dev.of_node, + "mcp,spi-present-mask", &spi_present_mask); + if (status) { + dev_err(&spi->dev, + "DT has no spi-present-mask\n"); + return -ENODEV; + } } if ((spi_present_mask <= 0) || (spi_present_mask >= 256)) { dev_err(&spi->dev, "invalid spi-present-mask\n"); return -ENODEV; } - for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) - pullups[addr] = 0; + pdata = &local_pdata; + pdata->base = -1; + for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { + pdata->chip[addr].pullups = 0; + if (spi_present_mask & (1 << addr)) + chips++; + } + pdata->irq_controller = of_property_read_bool( + spi->dev.of_node, + "interrupt-controller"); + pdata->mirror = of_property_read_bool(spi->dev.of_node, + "microchip,irq-mirror"); } else { type = spi_get_device_id(spi)->driver_data; - pdata = spi->dev.platform_data; - if (!pdata || !gpio_is_valid(pdata->base)) { - dev_dbg(&spi->dev, - "invalid or missing platform data\n"); - return -EINVAL; + pdata = dev_get_platdata(&spi->dev); + if (!pdata) { + pdata = devm_kzalloc(&spi->dev, + sizeof(struct mcp23s08_platform_data), + GFP_KERNEL); + pdata->base = -1; } for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { @@ -652,34 +938,36 @@ return -EINVAL; } spi_present_mask |= 1 << addr; - pullups[addr] = pdata->chip[addr].pullups; } - - if (!chips) - return -ENODEV; - - base = pdata->base; } - data = kzalloc(sizeof *data + chips * sizeof(struct mcp23s08), - GFP_KERNEL); + 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); + spi->irq = irq_of_parse_and_map(spi->dev.of_node, 0); + for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { if (!(spi_present_mask & (1 << 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, base, - pullups[addr]); + 0x40 | (addr << 1), type, pdata, + addr); if (status < 0) goto fail; - if (base != -1) - base += (type == MCP_TYPE_S17) ? 16 : 8; + if (pdata->base != -1) + pdata->base += (type == MCP_TYPE_S17) ? 16 : 8; ngpio += (type == MCP_TYPE_S17) ? 16 : 8; } data->ngpio = ngpio; @@ -693,15 +981,11 @@ fail: for (addr = 0; addr < ARRAY_SIZE(data->mcp); 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); + gpiochip_remove(&data->mcp[addr]->chip); } - kfree(data); return status; } @@ -709,23 +993,18 @@ { struct mcp23s08_driver_data *data = spi_get_drvdata(spi); unsigned addr; - int status = 0; for (addr = 0; addr < ARRAY_SIZE(data->mcp); 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 (spi->irq && data->mcp[addr]->irq_controller) + mcp23s08_irq_teardown(data->mcp[addr]); + gpiochip_remove(&data->mcp[addr]->chip); } - if (status == 0) - kfree(data); - return status; + + return 0; } static const struct spi_device_id mcp23s08_ids[] = { @@ -741,7 +1020,6 @@ .id_table = mcp23s08_ids, .driver = { .name = "mcp23s08", - .owner = THIS_MODULE, .of_match_table = of_match_ptr(mcp23s08_spi_of_match), }, };