@@ -7,6 +7,7 @@
#include <linux/kernel.h>
#include <linux/init.h>
+#include <linux/cleanup.h>
#include <linux/irq.h>
#include <linux/irqdesc.h>
#include <linux/module.h>
@@ -17,6 +18,7 @@
#include <linux/platform_device.h>
#include <linux/bitops.h>
#include <linux/reset.h>
+#include <linux/interrupt.h>
#include <asm/types.h>
enum loongson_gpio_mode {
@@ -24,6 +26,8 @@ enum loongson_gpio_mode {
BYTE_CTRL_MODE,
};
+struct loongson_gpio_chip;
+
struct loongson_gpio_chip_data {
const char *label;
enum loongson_gpio_mode mode;
@@ -39,14 +43,19 @@ struct loongson_gpio_chip_data {
unsigned int intr_num;
irq_flow_handler_t irq_handler;
const struct irq_chip *girqchip;
+ unsigned int (*irq_mapping)(struct loongson_gpio_chip *lgpio,
+ unsigned int pin);
int (*irq_init)(struct platform_device *pdev,
struct loongson_gpio_chip *lgpio);
};
+struct loongson_gpio_sirq_data;
+
struct loongson_gpio_chip {
struct gpio_generic_chip chip;
spinlock_t lock;
void __iomem *reg_base;
+ struct loongson_gpio_sirq_data *sirq_data;
const struct loongson_gpio_chip_data *chip_data;
};
@@ -291,6 +300,607 @@ static int loongson_gpio_init_irqchip(struct platform_device *pdev,
return 0;
}
+/*
+ * About shared IRQs for GPIO pins:
+ *
+ * This kind of GPIO controller lacks interrupt status register to identify
+ * edge-triggered interrupts, and the handling of such interrupts must be
+ * deligated to the parent IRQ controller.
+ * This kind of GPIO controller should typically have a inten register to enable
+ * or disable interrupts for individual GPIO pins, intpol register to configure
+ * the interrupt polarity. The signal sent to the parent interrupt controller
+ * is calculated by bit-wise comparing the GPIO line and the intpol register and
+ * then masking with inten.
+ * However, The interrput input lines of the parent IRQ controller is not enough
+ * to 1:1 map to the GPIO pins, necessitating the use of shared IRQs. A mapping
+ * is statically establised to map multiple GPIO pins to a single parent IRQ
+ * line. The level of the parent IRQ is determined by or-ing the levels of all
+ * GPIO pins mapped to it. Suppose gpio pin 0 and gpio pin 8 are mapped to the
+ * same parent IRQ, the level of the parent IRQ is determined by the following
+ * expression:
+ * (~(gpio[0] ^ intpol[0]) & inten[0]) |
+ * (~(gpio[8] ^ intpol[8]) & inten[8])
+ * When the intpol register is not available, its value is assumed to be 1, and
+ * the expression becomes:
+ * (gpio[0] & inten[0]) | (gpio[8] & inten[8])
+ *
+ * According to the hardware design, IRQ can be requested on only one of the
+ * GPIO pins sharing the same parent IRQ, unless the intpol register is
+ * available and all the IRQs are level-triggered.
+ *
+ * Convention on the usage of shared IRQs for GPIO pins:
+ * - When a parent IRQ is exclusively used, the parent IRQ should be masked or
+ * unmasked accordingly when the irq pin using it is masked or unmasked.
+ * - When a parent IRQ is shared among multiple GPIO pins, the parent IRQ
+ * should always be unmasked and masking or unmasking individual GPIO pins
+ * should be done through the GPIO controller's inten register.
+ */
+
+/*
+ * We assume a maximum of 128 GPIOs for the bitmap, which should be
+ * sufficient for all the actual hardware controllers defined in this
+ * driver.
+ */
+enum { MAX_GPIO_WITH_IRQ = 128 };
+
+struct loongson_gpio_sirq_parent_data {
+ unsigned int parent_index;
+ unsigned int parent_irq;
+ /*
+ * The lock of the irq_desc of the parent irq also protects
+ * exclusive/usedby below. It must be taken before
+ * lgpio->sirq_data->lock.
+ */
+ struct irq_data *parent_irq_data;
+ struct loongson_gpio_chip *lgpio;
+
+ /*
+ * exclusive: exclusive usage, only one gpio pin can use this parent irq.
+ * It will happen in two cases:
+ * - The parent irq is used by a gpio pin which is edge triggered, and
+ * thus cannot be used by other gpio pins which are mapped to the same
+ * parent irq.
+ * - The parent irq is used by a gpio pin which is level triggered, and
+ * the gpio chip lacks the polarity control register, and thus the
+ * interrupt polarity is controlled by the parent irq controller, and
+ * thus cannot be used by other gpio pins which are mapped to the
+ * same parent irq.
+ * When set, the request for other gpio pins which are mapped to this
+ * parent irq will be rejected.
+ */
+ bool exclusive : 1;
+ /*
+ * usedby: When exclusive is set, indicates the only gpio pin which
+ * is using this parent irq. When exclusive is cleared, indicates the
+ * number of gpio pins which are using this parent irq.
+ * When both exclusive and usedby are 0, it means that the parent irq is not
+ * used by any gpio pin.
+ */
+ unsigned int usedby : 31;
+};
+
+struct loongson_gpio_sirq_data {
+ /*
+ * Protects the read/write operations on the irq related registers
+ */
+ raw_spinlock_t lock;
+ int *irq_in_use;
+ struct loongson_gpio_sirq_parent_data parent_data[];
+};
+
+static void loongson_gpio_write_register(struct loongson_gpio_chip *lgpio,
+ unsigned int reg_offset,
+ unsigned int line, u8 value)
+{
+ void __iomem *reg = lgpio->reg_base + reg_offset;
+
+ if (lgpio->chip_data->mode == BIT_CTRL_MODE) {
+ u32 bval;
+
+ reg += (line / 32) * 4;
+ bval = readl(reg);
+
+ if (value)
+ bval |= BIT(line % 32);
+ else
+ bval &= ~BIT(line % 32);
+
+ writel(bval, reg);
+ } else {
+ writeb(value, reg + line);
+ }
+}
+
+static u8 loongson_gpio_read_register(struct loongson_gpio_chip *lgpio,
+ unsigned int reg_offset,
+ unsigned int line)
+{
+ void __iomem *reg = lgpio->reg_base + reg_offset;
+
+ if (lgpio->chip_data->mode == BIT_CTRL_MODE) {
+ u32 bval;
+
+ reg += (line / 32) * 4;
+ bval = readl(reg);
+
+ return (bval >> (line % 32)) & 1;
+ }
+
+ return readb(reg + line) & 1;
+}
+
+static void loongson_gpio_shared_mask_locked(struct loongson_gpio_chip *lgpio,
+ irq_hw_number_t hwirq,
+ struct loongson_gpio_sirq_parent_data *pirqd)
+{
+ struct irq_data *parent_irq_data = pirqd->parent_irq_data;
+
+ if (pirqd->exclusive)
+ parent_irq_data->chip->irq_mask(parent_irq_data);
+
+ scoped_guard(raw_spinlock_irqsave, &lgpio->sirq_data->lock)
+ loongson_gpio_write_register(lgpio, lgpio->chip_data->inten_offset,
+ hwirq, 0);
+}
+
+static void loongson_gpio_shared_irq_mask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+ unsigned int pirq_idx = lgpio->chip_data->irq_mapping(lgpio, hwirq);
+ struct loongson_gpio_sirq_parent_data *pirqd =
+ &lgpio->sirq_data->parent_data[pirq_idx];
+
+ /*
+ * If irq_in_use is not set, then set_type handler must has not been called
+ * and no trigger type has been set for this gpio pin, so we should not
+ * proceed and do nothing.
+ */
+ if (!lgpio->sirq_data->irq_in_use[hwirq])
+ return;
+
+ guard(raw_spinlock_irqsave)(&irq_data_to_desc(pirqd->parent_irq_data)->lock);
+
+ loongson_gpio_shared_mask_locked(lgpio, hwirq, pirqd);
+}
+
+static void loongson_gpio_shared_irq_unmask(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+ unsigned int pirq_idx = lgpio->chip_data->irq_mapping(lgpio, hwirq);
+ struct loongson_gpio_sirq_parent_data *pirqd =
+ &lgpio->sirq_data->parent_data[pirq_idx];
+ struct irq_data *parent_irq_data = pirqd->parent_irq_data;
+
+ if (!lgpio->sirq_data->irq_in_use[hwirq])
+ return;
+
+ guard(raw_spinlock_irqsave)(&irq_data_to_desc(pirqd->parent_irq_data)->lock);
+
+ if (pirqd->exclusive)
+ parent_irq_data->chip->irq_mask(parent_irq_data);
+
+ scoped_guard(raw_spinlock_irqsave, &lgpio->sirq_data->lock)
+ loongson_gpio_write_register(lgpio, lgpio->chip_data->inten_offset,
+ hwirq, 1);
+
+ if (pirqd->exclusive)
+ parent_irq_data->chip->irq_unmask(parent_irq_data);
+}
+
+static void loongson_gpio_shared_irq_handler(struct irq_desc *desc);
+
+static int loongson_gpio_shared_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+ unsigned int pirq_idx = lgpio->chip_data->irq_mapping(lgpio, hwirq);
+ struct loongson_gpio_sirq_parent_data *pirqd =
+ &lgpio->sirq_data->parent_data[pirq_idx];
+ unsigned int intpol_offset = lgpio->chip_data->intpol_offset;
+ struct irq_data *parent_irq_data = pirqd->parent_irq_data;
+ struct irq_chip *parent_irq_chip = parent_irq_data->chip;
+ int ret = 0;
+
+ type &= IRQ_TYPE_SENSE_MASK;
+
+ if (type != IRQ_TYPE_EDGE_FALLING && type != IRQ_TYPE_EDGE_RISING &&
+ type != IRQ_TYPE_LEVEL_LOW && type != IRQ_TYPE_LEVEL_HIGH)
+ return -EINVAL;
+
+ guard(raw_spinlock_irqsave)(&irq_data_to_desc(pirqd->parent_irq_data)->lock);
+
+ if (pirqd->exclusive && hwirq != pirqd->usedby)
+ return -EBUSY;
+
+ /*
+ * When the irq is not exclusively used and is currently shared by
+ * more than one gpio pin; or is currently used by single gpio pin and
+ * the gpio pin is not the one requesting to set the irq type, then the
+ * irq type can only be set to level triggered.
+ */
+ if (!pirqd->exclusive &&
+ (pirqd->usedby > 1 ||
+ (pirqd->usedby == 1 && !lgpio->sirq_data->irq_in_use[hwirq]))) {
+ if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_LEVEL_LOW)
+ return -EBUSY;
+
+ /*
+ * When exclusive is not set and usedby at least one, the gpio chip must
+ * have intpol register to control the interrupt polarity. We will never
+ * reach here if the gpio chip lacks intpol register, because in that
+ * case, exclusive will be set no matter what trigger type is requested.
+ */
+ BUG_ON(!intpol_offset);
+ /*
+ * Since only level triggered irqs are allowed to be shared, we can
+ * safely set the intpol register without first disabling the pin in
+ * inten register.
+ */
+ scoped_guard(raw_spinlock_irqsave, &lgpio->sirq_data->lock)
+ loongson_gpio_write_register(lgpio, intpol_offset, hwirq,
+ type == IRQ_TYPE_LEVEL_HIGH);
+
+ if (!lgpio->sirq_data->irq_in_use[hwirq])
+ pirqd->usedby++;
+
+ irq_set_handler_locked(data, handle_level_irq);
+ } else {
+ parent_irq_chip->irq_mask(parent_irq_data);
+
+ if (type == IRQ_TYPE_EDGE_FALLING || type == IRQ_TYPE_EDGE_RISING) {
+ if (intpol_offset) {
+ ret = parent_irq_chip->irq_set_type(parent_irq_data,
+ IRQ_TYPE_EDGE_RISING);
+ if (ret < 0)
+ goto out_restore_handler;
+
+ scoped_guard(raw_spinlock_irqsave, &lgpio->sirq_data->lock)
+ loongson_gpio_write_register(lgpio, intpol_offset,
+ hwirq,
+ type == IRQ_TYPE_EDGE_RISING);
+ } else {
+ ret = parent_irq_chip->irq_set_type(parent_irq_data, type);
+ if (ret < 0)
+ goto out_restore_handler;
+ }
+
+ pirqd->exclusive = true;
+ pirqd->usedby = hwirq;
+
+ /*
+ * We deliberately set the handler to level_irq even for
+ * edge-triggered interrupts because we cannot provide .irq_ack()
+ * handler.
+ */
+ irq_set_handler_locked(data, handle_level_irq);
+ } else {
+ if (intpol_offset) {
+ ret = parent_irq_chip->irq_set_type(parent_irq_data,
+ IRQ_TYPE_LEVEL_HIGH);
+ if (ret < 0)
+ goto out_restore_handler;
+
+ scoped_guard(raw_spinlock_irqsave, &lgpio->sirq_data->lock)
+ loongson_gpio_write_register(lgpio, intpol_offset,
+ hwirq,
+ type == IRQ_TYPE_LEVEL_HIGH);
+ pirqd->exclusive = false;
+ pirqd->usedby = 1;
+ } else {
+ ret = parent_irq_chip->irq_set_type(parent_irq_data, type);
+ if (ret < 0)
+ goto out_restore_handler;
+
+ pirqd->exclusive = true;
+ pirqd->usedby = hwirq;
+ }
+
+ irq_set_handler_locked(data, handle_level_irq);
+ }
+out_restore_handler:
+ /* ->irq_set_type() would override irq_handler, so restore here */
+ irq_set_handler_locked(parent_irq_data, loongson_gpio_shared_irq_handler);
+ if (ret < 0)
+ goto out;
+ }
+
+ lgpio->sirq_data->irq_in_use[hwirq] = 1;
+
+out:
+ if (pirqd->exclusive) {
+ u8 inten;
+
+ scoped_guard(raw_spinlock_irqsave, &lgpio->sirq_data->lock)
+ inten = loongson_gpio_read_register(lgpio, lgpio->chip_data->inten_offset,
+ hwirq);
+ if (inten)
+ parent_irq_chip->irq_unmask(parent_irq_data);
+ } else
+ parent_irq_chip->irq_unmask(parent_irq_data);
+
+ return ret;
+}
+
+static void loongson_gpio_shared_irq_shutdown(struct irq_data *data)
+{
+ struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(chip);
+ irq_hw_number_t hwirq = irqd_to_hwirq(data);
+ unsigned int parent_irq_index = lgpio->chip_data->irq_mapping(lgpio, hwirq);
+ struct loongson_gpio_sirq_parent_data *pirqd =
+ &lgpio->sirq_data->parent_data[parent_irq_index];
+
+ if (!lgpio->sirq_data->irq_in_use[hwirq])
+ return;
+
+ guard(raw_spinlock_irqsave)(&irq_data_to_desc(pirqd->parent_irq_data)->lock);
+
+ loongson_gpio_shared_mask_locked(lgpio, hwirq, pirqd);
+
+ if (!pirqd->exclusive) {
+ if (lgpio->sirq_data->irq_in_use[hwirq]) {
+ if (!WARN_ON(pirqd->usedby == 0))
+ pirqd->usedby--;
+ }
+ } else {
+ if (pirqd->usedby == hwirq) {
+ pirqd->usedby = 0;
+ pirqd->exclusive = false;
+ }
+ }
+
+ lgpio->sirq_data->irq_in_use[hwirq] = 0;
+}
+
+static void loongson_gpio_shared_irq_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(gc);
+ struct loongson_gpio_sirq_data *shared_irq_data = lgpio->sirq_data;
+ const struct loongson_gpio_chip_data *data = lgpio->chip_data;
+ unsigned int i;
+
+ for (i = 0; i < ngpios; i++) {
+ unsigned int parent_irq_index = data->irq_mapping(lgpio, i);
+
+ /*
+ * A parent_irq of 0 means the corresponding interrupt line is
+ * absent from the device tree or ACPI namespace.
+ */
+ if (i >= MAX_GPIO_WITH_IRQ ||
+ parent_irq_index >= data->intr_num ||
+ !shared_irq_data->parent_data[parent_irq_index].parent_irq)
+ clear_bit(i, valid_mask);
+ }
+}
+
+static int loongson_gpio_shared_irq_init_hw(struct gpio_chip *gc)
+{
+ struct loongson_gpio_chip *lgpio = to_loongson_gpio_chip(gc);
+ const struct loongson_gpio_chip_data *data = lgpio->chip_data;
+ struct loongson_gpio_sirq_data *sirq_data = lgpio->sirq_data;
+ int i;
+
+ sirq_data->irq_in_use = devm_kcalloc(gc->parent, gc->ngpio,
+ sizeof(*sirq_data->irq_in_use),
+ GFP_KERNEL);
+
+ if (!sirq_data->irq_in_use)
+ return -ENOMEM;
+
+ for (i = 0; i < gc->ngpio; i++)
+ loongson_gpio_write_register(lgpio, data->inten_offset, i, 0);
+
+ return 0;
+}
+
+static int loongson_gpio_init_shared_irqchip(struct platform_device *pdev,
+ struct loongson_gpio_chip *lgpio)
+{
+ const struct loongson_gpio_chip_data *data = lgpio->chip_data;
+ struct gpio_chip *chip = &lgpio->chip.gc;
+ struct loongson_gpio_sirq_data *sirq_data;
+ int i;
+ int available_parent_irqs;
+
+ chip->irq.default_type = IRQ_TYPE_NONE;
+ chip->irq.handler = handle_bad_irq;
+ chip->irq.parent_handler = data->irq_handler;
+ chip->irq.init_valid_mask = loongson_gpio_shared_irq_init_valid_mask;
+ chip->irq.init_hw = loongson_gpio_shared_irq_init_hw;
+
+ sirq_data = devm_kzalloc(&pdev->dev,
+ struct_size(sirq_data, parent_data, data->intr_num),
+ GFP_KERNEL);
+ if (!sirq_data)
+ return -ENOMEM;
+
+ lgpio->sirq_data = sirq_data;
+ raw_spin_lock_init(&sirq_data->lock);
+ for (i = 0; i < data->intr_num; i++) {
+ sirq_data->parent_data[i].parent_index = i;
+ sirq_data->parent_data[i].lgpio = lgpio;
+ }
+
+ gpio_irq_chip_set_chip(&chip->irq, data->girqchip);
+
+ chip->irq.parents = devm_kcalloc(&pdev->dev, data->intr_num,
+ sizeof(*chip->irq.parents), GFP_KERNEL);
+ if (!chip->irq.parents)
+ return -ENOMEM;
+
+ chip->irq.per_parent_data = true;
+ chip->irq.parent_handler_data_array =
+ devm_kcalloc(&pdev->dev, data->intr_num,
+ sizeof(*chip->irq.parent_handler_data_array),
+ GFP_KERNEL);
+ if (!chip->irq.parent_handler_data_array)
+ return -ENOMEM;
+
+ for (i = 0, available_parent_irqs = 0; i < data->intr_num; i++) {
+ int ret;
+ struct irq_data *parent_irq_data;
+ int dup = 0, j;
+
+ ret = platform_get_irq_optional(pdev, i);
+
+ if (ret == -ENXIO)
+ continue;
+
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to get IRQ %d\n", i);
+
+ for (j = 0; j < available_parent_irqs; j++)
+ if (chip->irq.parents[j] == ret) {
+ dup = 1;
+ break;
+ }
+ if (dup) {
+ dev_warn(&pdev->dev,
+ FW_BUG "IRQ for parent index %d is duplicated, skipping\n", i);
+ continue;
+ }
+
+ parent_irq_data = irq_get_irq_data(ret);
+ if (WARN_ON(!parent_irq_data))
+ continue;
+ /* Implementing ->irq_set_type() is required for a parent irqchip */
+ if (parent_irq_data->chip->irq_set_type == NULL)
+ continue;
+
+ chip->irq.parents[available_parent_irqs] = ret;
+ chip->irq.parent_handler_data_array[available_parent_irqs] =
+ &sirq_data->parent_data[i];
+
+ sirq_data->parent_data[i].parent_irq = ret;
+ sirq_data->parent_data[i].parent_irq_data = irq_get_irq_data(ret);
+
+ available_parent_irqs++;
+ }
+ chip->irq.num_parents = available_parent_irqs;
+
+ return 0;
+}
+
+/*
+ * Determine the GPIO pins which are triggering the parent irq given by
+ * pirqd, by comparing each mapped pin's line level against its polarity.
+ */
+static void loongson_gpio_shared_irq_poll(struct loongson_gpio_chip *lgpio,
+ unsigned int parent_index,
+ unsigned long *irq_pending_map,
+ unsigned int ngpio)
+{
+ const struct loongson_gpio_chip_data *data = lgpio->chip_data;
+ unsigned int i;
+
+ for (i = 0; i < ngpio; i++) {
+ u8 inten, intpol, level;
+
+ if (data->irq_mapping(lgpio, i) != parent_index)
+ continue;
+
+ scoped_guard(raw_spinlock_irqsave, &lgpio->sirq_data->lock) {
+ inten = !!loongson_gpio_read_register(lgpio, data->inten_offset, i);
+ intpol = !!loongson_gpio_read_register(lgpio, data->intpol_offset, i);
+ level = !!loongson_gpio_read_register(lgpio, data->in_offset, i);
+ }
+
+ if (inten && intpol == level)
+ bitmap_set(irq_pending_map, i, 1);
+ }
+}
+
+static void loongson_gpio_shared_irq_handler(struct irq_desc *desc)
+{
+ struct loongson_gpio_sirq_parent_data *pirqd = irq_desc_get_handler_data(desc);
+ struct loongson_gpio_chip *lgpio = pirqd->lgpio;
+ struct gpio_chip *chip = &lgpio->chip.gc;
+ struct irq_chip *girqchip = irq_desc_get_chip(desc);
+ unsigned int ngpio = chip->ngpio > MAX_GPIO_WITH_IRQ ? MAX_GPIO_WITH_IRQ : chip->ngpio;
+ unsigned int usedby, i;
+ bool exclusive;
+ DECLARE_BITMAP(irq_pending_map, MAX_GPIO_WITH_IRQ);
+
+ bitmap_clear(irq_pending_map, 0, ngpio);
+
+ chained_irq_enter(girqchip, desc);
+
+ /*
+ * Release the lock after copying the exclusive and usedby fields.
+ * This ensures lock ordering and prevents deadlocks.
+ */
+ scoped_guard(raw_spinlock_irqsave, &desc->lock) {
+ exclusive = pirqd->exclusive;
+ usedby = pirqd->usedby;
+ }
+ if (exclusive && usedby < ngpio)
+ bitmap_set(irq_pending_map, usedby, 1);
+ else if (usedby) {
+ /*
+ * When a parent irq is shared by more than 1 gpio pin,
+ * these IRQs must be level-triggered, and the polarity
+ * register must be available in the GPIO controller to
+ * determine the triggering GPIO line.
+ */
+ loongson_gpio_shared_irq_poll(lgpio, pirqd->parent_index, irq_pending_map, ngpio);
+ }
+
+ if (!bitmap_empty(irq_pending_map, ngpio)) {
+ for (i = 0; i < ngpio; i++) {
+ if (test_bit(i, irq_pending_map))
+ generic_handle_domain_irq(chip->irq.domain, i);
+ }
+ }
+
+ chained_irq_exit(girqchip, desc);
+}
+
+static const struct irq_chip loongson_gpio_shared_irqchip = {
+ .irq_mask = loongson_gpio_shared_irq_mask,
+ .irq_unmask = loongson_gpio_shared_irq_unmask,
+ .irq_set_type = loongson_gpio_shared_irq_set_type,
+ .irq_shutdown = loongson_gpio_shared_irq_shutdown,
+ .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static unsigned int loongson_gpio_irq_mapping_mod8(struct loongson_gpio_chip *lgpio,
+ unsigned int pin)
+{
+ return pin % 8;
+}
+
+static unsigned int loongson_gpio_irq_mapping_cap4(struct loongson_gpio_chip *lgpio,
+ unsigned int pin)
+{
+ return pin >= 4 ? 4 : pin;
+}
+
+static unsigned int loongson_gpio_irq_mapping_cap4_2(struct loongson_gpio_chip *lgpio,
+ unsigned int pin)
+{
+ if (pin < 4)
+ return pin;
+ else if (pin < 32)
+ return 4;
+ else
+ return 5;
+}
+
+static unsigned int loongson_gpio_irq_mapping_div32(struct loongson_gpio_chip *lgpio,
+ unsigned int pin)
+{
+ return pin / 32;
+}
+
static int loongson_gpio_init(struct platform_device *pdev, struct loongson_gpio_chip *lgpio,
void __iomem *reg_base)
{
@@ -371,6 +981,11 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls2k_data = {
.in_offset = 0x20,
.out_offset = 0x10,
.inten_offset = 0x30,
+ .intr_num = 6,
+ .irq_init = loongson_gpio_init_shared_irqchip,
+ .irq_handler = loongson_gpio_shared_irq_handler,
+ .girqchip = &loongson_gpio_shared_irqchip,
+ .irq_mapping = loongson_gpio_irq_mapping_cap4_2,
};
static const struct loongson_gpio_chip_data loongson_gpio_ls2k0300_data = {
@@ -398,6 +1013,11 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls2k0500_data0 = {
.in_offset = 0x8,
.out_offset = 0x10,
.inten_offset = 0xb0,
+ .intr_num = 2,
+ .irq_init = loongson_gpio_init_shared_irqchip,
+ .irq_handler = loongson_gpio_shared_irq_handler,
+ .girqchip = &loongson_gpio_shared_irqchip,
+ .irq_mapping = loongson_gpio_irq_mapping_div32,
};
static const struct loongson_gpio_chip_data loongson_gpio_ls2k0500_data1 = {
@@ -407,6 +1027,11 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls2k0500_data1 = {
.in_offset = 0x8,
.out_offset = 0x10,
.inten_offset = 0x98,
+ .intr_num = 2,
+ .irq_init = loongson_gpio_init_shared_irqchip,
+ .irq_handler = loongson_gpio_shared_irq_handler,
+ .girqchip = &loongson_gpio_shared_irqchip,
+ .irq_mapping = loongson_gpio_irq_mapping_div32,
};
static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data0 = {
@@ -416,6 +1041,12 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data0 = {
.in_offset = 0xc,
.out_offset = 0x8,
.inten_offset = 0x14,
+ .intpol_offset = 0x10,
+ .intr_num = 8,
+ .irq_init = loongson_gpio_init_shared_irqchip,
+ .irq_handler = loongson_gpio_shared_irq_handler,
+ .girqchip = &loongson_gpio_shared_irqchip,
+ .irq_mapping = loongson_gpio_irq_mapping_mod8,
};
static const struct loongson_gpio_chip_data loongson_gpio_ls2k2000_data1 = {
@@ -450,6 +1081,12 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls3a5000_data = {
.in_offset = 0xc,
.out_offset = 0x8,
.inten_offset = 0x14,
+ .intpol_offset = 0x10,
+ .intr_num = 8,
+ .irq_init = loongson_gpio_init_shared_irqchip,
+ .irq_handler = loongson_gpio_shared_irq_handler,
+ .girqchip = &loongson_gpio_shared_irqchip,
+ .irq_mapping = loongson_gpio_irq_mapping_mod8,
};
static const struct loongson_gpio_chip_data loongson_gpio_ls7a_data = {
@@ -459,6 +1096,11 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls7a_data = {
.in_offset = 0xa00,
.out_offset = 0x900,
.inten_offset = 0xb00,
+ .intr_num = 5,
+ .irq_init = loongson_gpio_init_shared_irqchip,
+ .irq_handler = loongson_gpio_shared_irq_handler,
+ .girqchip = &loongson_gpio_shared_irqchip,
+ .irq_mapping = loongson_gpio_irq_mapping_cap4,
};
/* LS7A2000 chipset GPIO */
@@ -496,6 +1138,12 @@ static const struct loongson_gpio_chip_data loongson_gpio_ls3a6000_data = {
.in_offset = 0xc,
.out_offset = 0x8,
.inten_offset = 0x14,
+ .intpol_offset = 0x10,
+ .intr_num = 8,
+ .irq_init = loongson_gpio_init_shared_irqchip,
+ .irq_handler = loongson_gpio_shared_irq_handler,
+ .girqchip = &loongson_gpio_shared_irqchip,
+ .irq_mapping = loongson_gpio_irq_mapping_mod8,
};
static const struct of_device_id loongson_gpio_of_match[] = {