diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2017-09-05 11:49:48 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-09-05 11:49:48 -0700 |
commit | 70b8e9eb3b50d8bded63f808b09c4844ef63c3b8 (patch) | |
tree | 42b5b22becc9e9542923be3bc78525e0d9f5c67c /drivers/gpio/gpio-thunderx.c | |
parent | d16605c9128a498f9b8575b5c43be95c45dbcfab (diff) | |
parent | 02b6bddb0b0a4e27fad6623d82579f2a1e35d3d3 (diff) |
Merge tag 'gpio-v4.14-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio
Pull GPIO updates from Linus Walleij:
"This is the bulk of the GPIO changes for the v4.14 cycle.
Not so much changes this time, phew. David Daney and Bartosz
Golaszewski did all the really interesting work in infrastructure
improvement across GPIO and IRQ core, hats off for them and to tglx
and Marc Z for general help with these patch sets.
Core changes:
- Allow the GPIO irqchip to allocate IRQs dynamically. This is an
important change on systems where only a restricted number of IRQs,
lesser than the number of GPIO lines, can be utilized. Now we can
allocate these on a first-come-first-served basis instead of
hogging up valuable IRQ lines.
- Serious fix-up of the kerneldoc documentation and inclusion into
the kerneldoc builds.
- Pulled in the IRQ simulator from the IRQ core tree and use this in
the GPIO mockup driver for exhaustive testing of interrupt
abilities.
New drivers:
- New driver for ThunderX and OCTEON-TX. This is especially
interesting as it picks up improvements from the IRQ core that
allow us to handle fasteoi ACKs upwards in a hierarchy when there
are IRQ flag latches on several levels in a hierarchy. Very
interesting work here.
- New subdriver for Renesas R-Car r8a7745 (RZ/G1E).
Misc:
- Several fixes and improvements for Xilinx Zynq GPIO.
- Support an enablement GPIO for the 74x164 GPIO.
- Switch a bunch of chips to use devres to allocate irq descriptors.
- A bunch of constification fixes"
* tag 'gpio-v4.14-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio: (63 commits)
gpio: mockup: remove unused variable gc
gpio: pl061: constify amba_id
Revert "gpiolib: request the gpio before querying its direction"
gpio: twl6040: remove unneeded forward declaration
gpio: zevio: make gpio_chip const
gpio: add gpio_add_lookup_tables() to add several tables at once
gpio: rcar: Add r8a7745 (RZ/G1E) support
gpio: brcmstb: check return value of gpiochip_irqchip_add()
MAINTAINERS: Add entry for THUNDERX GPIO Driver.
gpio: Add gpio driver support for ThunderX and OCTEON-TX
gpio: mockup: use irq_sim
gpio: mxs: use devres for irq generic chip
gpio: mxc: use devres for irq generic chip
gpio: pch: use devres for irq generic chip
gpio: ml-ioh: use devres for irq generic chip
gpio: sta2x11: use devres for irq generic chip
gpio: sta2x11: disallow unbinding the driver
gpio: mxs: disallow unbinding the driver
gpio: mxc: disallow unbinding the driver
gpio: aspeed: Remove reference to clock name in debounce warning message
...
Diffstat (limited to 'drivers/gpio/gpio-thunderx.c')
-rw-r--r-- | drivers/gpio/gpio-thunderx.c | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-thunderx.c b/drivers/gpio/gpio-thunderx.c new file mode 100644 index 000000000000..57efb251f9c4 --- /dev/null +++ b/drivers/gpio/gpio-thunderx.c @@ -0,0 +1,639 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2016, 2017 Cavium Inc. + */ + +#include <linux/bitops.h> +#include <linux/gpio/driver.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/spinlock.h> + + +#define GPIO_RX_DAT 0x0 +#define GPIO_TX_SET 0x8 +#define GPIO_TX_CLR 0x10 +#define GPIO_CONST 0x90 +#define GPIO_CONST_GPIOS_MASK 0xff +#define GPIO_BIT_CFG 0x400 +#define GPIO_BIT_CFG_TX_OE BIT(0) +#define GPIO_BIT_CFG_PIN_XOR BIT(1) +#define GPIO_BIT_CFG_INT_EN BIT(2) +#define GPIO_BIT_CFG_INT_TYPE BIT(3) +#define GPIO_BIT_CFG_FIL_MASK GENMASK(11, 4) +#define GPIO_BIT_CFG_FIL_CNT_SHIFT 4 +#define GPIO_BIT_CFG_FIL_SEL_SHIFT 8 +#define GPIO_BIT_CFG_TX_OD BIT(12) +#define GPIO_BIT_CFG_PIN_SEL_MASK GENMASK(25, 16) +#define GPIO_INTR 0x800 +#define GPIO_INTR_INTR BIT(0) +#define GPIO_INTR_INTR_W1S BIT(1) +#define GPIO_INTR_ENA_W1C BIT(2) +#define GPIO_INTR_ENA_W1S BIT(3) +#define GPIO_2ND_BANK 0x1400 + +#define GLITCH_FILTER_400NS ((4u << GPIO_BIT_CFG_FIL_SEL_SHIFT) | \ + (9u << GPIO_BIT_CFG_FIL_CNT_SHIFT)) + +struct thunderx_gpio; + +struct thunderx_line { + struct thunderx_gpio *txgpio; + unsigned int line; + unsigned int fil_bits; +}; + +struct thunderx_gpio { + struct gpio_chip chip; + u8 __iomem *register_base; + struct irq_domain *irqd; + struct msix_entry *msix_entries; /* per line MSI-X */ + struct thunderx_line *line_entries; /* per line irq info */ + raw_spinlock_t lock; + unsigned long invert_mask[2]; + unsigned long od_mask[2]; + int base_msi; +}; + +static unsigned int bit_cfg_reg(unsigned int line) +{ + return 8 * line + GPIO_BIT_CFG; +} + +static unsigned int intr_reg(unsigned int line) +{ + return 8 * line + GPIO_INTR; +} + +static bool thunderx_gpio_is_gpio_nowarn(struct thunderx_gpio *txgpio, + unsigned int line) +{ + u64 bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line)); + + return (bit_cfg & GPIO_BIT_CFG_PIN_SEL_MASK) == 0; +} + +/* + * Check (and WARN) that the pin is available for GPIO. We will not + * allow modification of the state of non-GPIO pins from this driver. + */ +static bool thunderx_gpio_is_gpio(struct thunderx_gpio *txgpio, + unsigned int line) +{ + bool rv = thunderx_gpio_is_gpio_nowarn(txgpio, line); + + WARN_RATELIMIT(!rv, "Pin %d not available for GPIO\n", line); + + return rv; +} + +static int thunderx_gpio_request(struct gpio_chip *chip, unsigned int line) +{ + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + + return thunderx_gpio_is_gpio(txgpio, line) ? 0 : -EIO; +} + +static int thunderx_gpio_dir_in(struct gpio_chip *chip, unsigned int line) +{ + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + + if (!thunderx_gpio_is_gpio(txgpio, line)) + return -EIO; + + raw_spin_lock(&txgpio->lock); + clear_bit(line, txgpio->invert_mask); + clear_bit(line, txgpio->od_mask); + writeq(txgpio->line_entries[line].fil_bits, + txgpio->register_base + bit_cfg_reg(line)); + raw_spin_unlock(&txgpio->lock); + return 0; +} + +static void thunderx_gpio_set(struct gpio_chip *chip, unsigned int line, + int value) +{ + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + int bank = line / 64; + int bank_bit = line % 64; + + void __iomem *reg = txgpio->register_base + + (bank * GPIO_2ND_BANK) + (value ? GPIO_TX_SET : GPIO_TX_CLR); + + writeq(BIT_ULL(bank_bit), reg); +} + +static int thunderx_gpio_dir_out(struct gpio_chip *chip, unsigned int line, + int value) +{ + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + u64 bit_cfg = txgpio->line_entries[line].fil_bits | GPIO_BIT_CFG_TX_OE; + + if (!thunderx_gpio_is_gpio(txgpio, line)) + return -EIO; + + raw_spin_lock(&txgpio->lock); + + thunderx_gpio_set(chip, line, value); + + if (test_bit(line, txgpio->invert_mask)) + bit_cfg |= GPIO_BIT_CFG_PIN_XOR; + + if (test_bit(line, txgpio->od_mask)) + bit_cfg |= GPIO_BIT_CFG_TX_OD; + + writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(line)); + + raw_spin_unlock(&txgpio->lock); + return 0; +} + +static int thunderx_gpio_get_direction(struct gpio_chip *chip, unsigned int line) +{ + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + u64 bit_cfg; + + if (!thunderx_gpio_is_gpio_nowarn(txgpio, line)) + /* + * Say it is input for now to avoid WARNing on + * gpiochip_add_data(). We will WARN if someone + * requests it or tries to use it. + */ + return 1; + + bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line)); + + return !(bit_cfg & GPIO_BIT_CFG_TX_OE); +} + +static int thunderx_gpio_set_config(struct gpio_chip *chip, + unsigned int line, + unsigned long cfg) +{ + bool orig_invert, orig_od, orig_dat, new_invert, new_od; + u32 arg, sel; + u64 bit_cfg; + int bank = line / 64; + int bank_bit = line % 64; + int ret = -ENOTSUPP; + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + void __iomem *reg = txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_SET; + + if (!thunderx_gpio_is_gpio(txgpio, line)) + return -EIO; + + raw_spin_lock(&txgpio->lock); + orig_invert = test_bit(line, txgpio->invert_mask); + new_invert = orig_invert; + orig_od = test_bit(line, txgpio->od_mask); + new_od = orig_od; + orig_dat = ((readq(reg) >> bank_bit) & 1) ^ orig_invert; + bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line)); + switch (pinconf_to_config_param(cfg)) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + /* + * Weird, setting open-drain mode causes signal + * inversion. Note this so we can compensate in the + * dir_out function. + */ + set_bit(line, txgpio->invert_mask); + new_invert = true; + set_bit(line, txgpio->od_mask); + new_od = true; + ret = 0; + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + clear_bit(line, txgpio->invert_mask); + new_invert = false; + clear_bit(line, txgpio->od_mask); + new_od = false; + ret = 0; + break; + case PIN_CONFIG_INPUT_DEBOUNCE: + arg = pinconf_to_config_argument(cfg); + if (arg > 1228) { /* 15 * 2^15 * 2.5nS maximum */ + ret = -EINVAL; + break; + } + arg *= 400; /* scale to 2.5nS clocks. */ + sel = 0; + while (arg > 15) { + sel++; + arg++; /* always round up */ + arg >>= 1; + } + txgpio->line_entries[line].fil_bits = + (sel << GPIO_BIT_CFG_FIL_SEL_SHIFT) | + (arg << GPIO_BIT_CFG_FIL_CNT_SHIFT); + bit_cfg &= ~GPIO_BIT_CFG_FIL_MASK; + bit_cfg |= txgpio->line_entries[line].fil_bits; + writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(line)); + ret = 0; + break; + default: + break; + } + raw_spin_unlock(&txgpio->lock); + + /* + * If currently output and OPEN_DRAIN changed, install the new + * settings + */ + if ((new_invert != orig_invert || new_od != orig_od) && + (bit_cfg & GPIO_BIT_CFG_TX_OE)) + ret = thunderx_gpio_dir_out(chip, line, orig_dat ^ new_invert); + + return ret; +} + +static int thunderx_gpio_get(struct gpio_chip *chip, unsigned int line) +{ + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + int bank = line / 64; + int bank_bit = line % 64; + u64 read_bits = readq(txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_RX_DAT); + u64 masked_bits = read_bits & BIT_ULL(bank_bit); + + if (test_bit(line, txgpio->invert_mask)) + return masked_bits == 0; + else + return masked_bits != 0; +} + +static void thunderx_gpio_set_multiple(struct gpio_chip *chip, + unsigned long *mask, + unsigned long *bits) +{ + int bank; + u64 set_bits, clear_bits; + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + + for (bank = 0; bank <= chip->ngpio / 64; bank++) { + set_bits = bits[bank] & mask[bank]; + clear_bits = ~bits[bank] & mask[bank]; + writeq(set_bits, txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_SET); + writeq(clear_bits, txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_CLR); + } +} + +static void thunderx_gpio_irq_ack(struct irq_data *data) +{ + struct thunderx_line *txline = irq_data_get_irq_chip_data(data); + + writeq(GPIO_INTR_INTR, + txline->txgpio->register_base + intr_reg(txline->line)); +} + +static void thunderx_gpio_irq_mask(struct irq_data *data) +{ + struct thunderx_line *txline = irq_data_get_irq_chip_data(data); + + writeq(GPIO_INTR_ENA_W1C, + txline->txgpio->register_base + intr_reg(txline->line)); +} + +static void thunderx_gpio_irq_mask_ack(struct irq_data *data) +{ + struct thunderx_line *txline = irq_data_get_irq_chip_data(data); + + writeq(GPIO_INTR_ENA_W1C | GPIO_INTR_INTR, + txline->txgpio->register_base + intr_reg(txline->line)); +} + +static void thunderx_gpio_irq_unmask(struct irq_data *data) +{ + struct thunderx_line *txline = irq_data_get_irq_chip_data(data); + + writeq(GPIO_INTR_ENA_W1S, + txline->txgpio->register_base + intr_reg(txline->line)); +} + +static int thunderx_gpio_irq_set_type(struct irq_data *data, + unsigned int flow_type) +{ + struct thunderx_line *txline = irq_data_get_irq_chip_data(data); + struct thunderx_gpio *txgpio = txline->txgpio; + u64 bit_cfg; + + irqd_set_trigger_type(data, flow_type); + + bit_cfg = txline->fil_bits | GPIO_BIT_CFG_INT_EN; + + if (flow_type & IRQ_TYPE_EDGE_BOTH) { + irq_set_handler_locked(data, handle_fasteoi_ack_irq); + bit_cfg |= GPIO_BIT_CFG_INT_TYPE; + } else { + irq_set_handler_locked(data, handle_fasteoi_mask_irq); + } + + raw_spin_lock(&txgpio->lock); + if (flow_type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW)) { + bit_cfg |= GPIO_BIT_CFG_PIN_XOR; + set_bit(txline->line, txgpio->invert_mask); + } else { + clear_bit(txline->line, txgpio->invert_mask); + } + clear_bit(txline->line, txgpio->od_mask); + writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(txline->line)); + raw_spin_unlock(&txgpio->lock); + + return IRQ_SET_MASK_OK; +} + +static void thunderx_gpio_irq_enable(struct irq_data *data) +{ + irq_chip_enable_parent(data); + thunderx_gpio_irq_unmask(data); +} + +static void thunderx_gpio_irq_disable(struct irq_data *data) +{ + thunderx_gpio_irq_mask(data); + irq_chip_disable_parent(data); +} + +static int thunderx_gpio_irq_request_resources(struct irq_data *data) +{ + struct thunderx_line *txline = irq_data_get_irq_chip_data(data); + struct thunderx_gpio *txgpio = txline->txgpio; + struct irq_data *parent_data = data->parent_data; + int r; + + r = gpiochip_lock_as_irq(&txgpio->chip, txline->line); + if (r) + return r; + + if (parent_data && parent_data->chip->irq_request_resources) { + r = parent_data->chip->irq_request_resources(parent_data); + if (r) + goto error; + } + + return 0; +error: + gpiochip_unlock_as_irq(&txgpio->chip, txline->line); + return r; +} + +static void thunderx_gpio_irq_release_resources(struct irq_data *data) +{ + struct thunderx_line *txline = irq_data_get_irq_chip_data(data); + struct thunderx_gpio *txgpio = txline->txgpio; + struct irq_data *parent_data = data->parent_data; + + if (parent_data && parent_data->chip->irq_release_resources) + parent_data->chip->irq_release_resources(parent_data); + + gpiochip_unlock_as_irq(&txgpio->chip, txline->line); +} + +/* + * Interrupts are chained from underlying MSI-X vectors. We have + * these irq_chip functions to be able to handle level triggering + * semantics and other acknowledgment tasks associated with the GPIO + * mechanism. + */ +static struct irq_chip thunderx_gpio_irq_chip = { + .name = "GPIO", + .irq_enable = thunderx_gpio_irq_enable, + .irq_disable = thunderx_gpio_irq_disable, + .irq_ack = thunderx_gpio_irq_ack, + .irq_mask = thunderx_gpio_irq_mask, + .irq_mask_ack = thunderx_gpio_irq_mask_ack, + .irq_unmask = thunderx_gpio_irq_unmask, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, + .irq_request_resources = thunderx_gpio_irq_request_resources, + .irq_release_resources = thunderx_gpio_irq_release_resources, + .irq_set_type = thunderx_gpio_irq_set_type, + + .flags = IRQCHIP_SET_TYPE_MASKED +}; + +static int thunderx_gpio_irq_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct thunderx_gpio *txgpio = d->host_data; + + if (hwirq >= txgpio->chip.ngpio) + return -EINVAL; + if (!thunderx_gpio_is_gpio_nowarn(txgpio, hwirq)) + return -EPERM; + return 0; +} + +static int thunderx_gpio_irq_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + irq_hw_number_t *hwirq, + unsigned int *type) +{ + struct thunderx_gpio *txgpio = d->host_data; + + if (WARN_ON(fwspec->param_count < 2)) + return -EINVAL; + if (fwspec->param[0] >= txgpio->chip.ngpio) + return -EINVAL; + *hwirq = fwspec->param[0]; + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; + return 0; +} + +static int thunderx_gpio_irq_alloc(struct irq_domain *d, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + struct thunderx_line *txline = arg; + + return irq_domain_set_hwirq_and_chip(d, virq, txline->line, + &thunderx_gpio_irq_chip, txline); +} + +static const struct irq_domain_ops thunderx_gpio_irqd_ops = { + .map = thunderx_gpio_irq_map, + .alloc = thunderx_gpio_irq_alloc, + .translate = thunderx_gpio_irq_translate +}; + +static int thunderx_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) +{ + struct thunderx_gpio *txgpio = gpiochip_get_data(chip); + + return irq_find_mapping(txgpio->irqd, offset); +} + +static int thunderx_gpio_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + void __iomem * const *tbl; + struct device *dev = &pdev->dev; + struct thunderx_gpio *txgpio; + struct gpio_chip *chip; + int ngpio, i; + int err = 0; + + txgpio = devm_kzalloc(dev, sizeof(*txgpio), GFP_KERNEL); + if (!txgpio) + return -ENOMEM; + + raw_spin_lock_init(&txgpio->lock); + chip = &txgpio->chip; + + pci_set_drvdata(pdev, txgpio); + + err = pcim_enable_device(pdev); + if (err) { + dev_err(dev, "Failed to enable PCI device: err %d\n", err); + goto out; + } + + err = pcim_iomap_regions(pdev, 1 << 0, KBUILD_MODNAME); + if (err) { + dev_err(dev, "Failed to iomap PCI device: err %d\n", err); + goto out; + } + + tbl = pcim_iomap_table(pdev); + txgpio->register_base = tbl[0]; + if (!txgpio->register_base) { + dev_err(dev, "Cannot map PCI resource\n"); + err = -ENOMEM; + goto out; + } + + if (pdev->subsystem_device == 0xa10a) { + /* CN88XX has no GPIO_CONST register*/ + ngpio = 50; + txgpio->base_msi = 48; + } else { + u64 c = readq(txgpio->register_base + GPIO_CONST); + + ngpio = c & GPIO_CONST_GPIOS_MASK; + txgpio->base_msi = (c >> 8) & 0xff; + } + + txgpio->msix_entries = devm_kzalloc(dev, + sizeof(struct msix_entry) * ngpio, + GFP_KERNEL); + if (!txgpio->msix_entries) { + err = -ENOMEM; + goto out; + } + + txgpio->line_entries = devm_kzalloc(dev, + sizeof(struct thunderx_line) * ngpio, + GFP_KERNEL); + if (!txgpio->line_entries) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < ngpio; i++) { + u64 bit_cfg = readq(txgpio->register_base + bit_cfg_reg(i)); + + txgpio->msix_entries[i].entry = txgpio->base_msi + (2 * i); + txgpio->line_entries[i].line = i; + txgpio->line_entries[i].txgpio = txgpio; + /* + * If something has already programmed the pin, use + * the existing glitch filter settings, otherwise go + * to 400nS. + */ + txgpio->line_entries[i].fil_bits = bit_cfg ? + (bit_cfg & GPIO_BIT_CFG_FIL_MASK) : GLITCH_FILTER_400NS; + + if ((bit_cfg & GPIO_BIT_CFG_TX_OE) && (bit_cfg & GPIO_BIT_CFG_TX_OD)) + set_bit(i, txgpio->od_mask); + if (bit_cfg & GPIO_BIT_CFG_PIN_XOR) + set_bit(i, txgpio->invert_mask); + } + + + /* Enable all MSI-X for interrupts on all possible lines. */ + err = pci_enable_msix_range(pdev, txgpio->msix_entries, ngpio, ngpio); + if (err < 0) + goto out; + + /* + * Push GPIO specific irqdomain on hierarchy created as a side + * effect of the pci_enable_msix() + */ + txgpio->irqd = irq_domain_create_hierarchy(irq_get_irq_data(txgpio->msix_entries[0].vector)->domain, + 0, 0, of_node_to_fwnode(dev->of_node), + &thunderx_gpio_irqd_ops, txgpio); + if (!txgpio->irqd) + goto out; + + /* Push on irq_data and the domain for each line. */ + for (i = 0; i < ngpio; i++) { + err = irq_domain_push_irq(txgpio->irqd, + txgpio->msix_entries[i].vector, + &txgpio->line_entries[i]); + if (err < 0) + dev_err(dev, "irq_domain_push_irq: %d\n", err); + } + + chip->label = KBUILD_MODNAME; + chip->parent = dev; + chip->owner = THIS_MODULE; + chip->request = thunderx_gpio_request; + chip->base = -1; /* System allocated */ + chip->can_sleep = false; + chip->ngpio = ngpio; + chip->get_direction = thunderx_gpio_get_direction; + chip->direction_input = thunderx_gpio_dir_in; + chip->get = thunderx_gpio_get; + chip->direction_output = thunderx_gpio_dir_out; + chip->set = thunderx_gpio_set; + chip->set_multiple = thunderx_gpio_set_multiple; + chip->set_config = thunderx_gpio_set_config; + chip->to_irq = thunderx_gpio_to_irq; + err = devm_gpiochip_add_data(dev, chip, txgpio); + if (err) + goto out; + + dev_info(dev, "ThunderX GPIO: %d lines with base %d.\n", + ngpio, chip->base); + return 0; +out: + pci_set_drvdata(pdev, NULL); + return err; +} + +static void thunderx_gpio_remove(struct pci_dev *pdev) +{ + int i; + struct thunderx_gpio *txgpio = pci_get_drvdata(pdev); + + for (i = 0; i < txgpio->chip.ngpio; i++) + irq_domain_pop_irq(txgpio->irqd, + txgpio->msix_entries[i].vector); + + irq_domain_remove(txgpio->irqd); + + pci_set_drvdata(pdev, NULL); +} + +static const struct pci_device_id thunderx_gpio_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA00A) }, + { 0, } /* end of table */ +}; + +MODULE_DEVICE_TABLE(pci, thunderx_gpio_id_table); + +static struct pci_driver thunderx_gpio_driver = { + .name = KBUILD_MODNAME, + .id_table = thunderx_gpio_id_table, + .probe = thunderx_gpio_probe, + .remove = thunderx_gpio_remove, +}; + +module_pci_driver(thunderx_gpio_driver); + +MODULE_DESCRIPTION("Cavium Inc. ThunderX/OCTEON-TX GPIO Driver"); +MODULE_LICENSE("GPL"); |