chan->status = EDMA_ST_IDLE;
}
-static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
+static void dw_edma_emul_irq_ack(struct irq_data *d)
+{
+ struct dw_edma *dw = irq_data_get_irq_chip_data(d);
+
+ dw_edma_core_ack_emulated_irq(dw);
+}
+
+/*
+ * irq_chip implementation for interrupt-emulation doorbells.
+ *
+ * The emulated source has no mask/unmask mechanism. With handle_level_irq(),
+ * the flow is therefore:
+ * 1) .irq_ack() deasserts the source
+ * 2) registered handlers (if any) are dispatched
+ * Since deassertion is already done in .irq_ack(), handlers do not need to take
+ * care of it, hence IRQCHIP_ONESHOT_SAFE.
+ */
+static struct irq_chip dw_edma_emul_irqchip = {
+ .name = "dw-edma-emul",
+ .irq_ack = dw_edma_emul_irq_ack,
+ .flags = IRQCHIP_ONESHOT_SAFE | IRQCHIP_SKIP_SET_WAKE,
+};
+
+static int dw_edma_emul_irq_alloc(struct dw_edma *dw)
+{
+ struct dw_edma_chip *chip = dw->chip;
+ int virq;
+
+ chip->db_irq = 0;
+ chip->db_offset = ~0;
+
+ /*
+ * Only meaningful when the core provides the deassert sequence
+ * for interrupt emulation.
+ */
+ if (!dw->core->ack_emulated_irq)
+ return 0;
+
+ /*
+ * Allocate a single, requestable Linux virtual IRQ number.
+ * Use >= 1 so that 0 can remain a "not available" sentinel.
+ */
+ virq = irq_alloc_desc(NUMA_NO_NODE);
+ if (virq < 0)
+ return virq;
+
+ irq_set_chip_and_handler(virq, &dw_edma_emul_irqchip, handle_level_irq);
+ irq_set_chip_data(virq, dw);
+ irq_set_noprobe(virq);
+
+ chip->db_irq = virq;
+ chip->db_offset = dw_edma_core_db_offset(dw);
+
+ return 0;
+}
+
+static void dw_edma_emul_irq_free(struct dw_edma *dw)
+{
+ struct dw_edma_chip *chip = dw->chip;
+
+ if (!chip)
+ return;
+ if (chip->db_irq <= 0)
+ return;
+
+ irq_free_descs(chip->db_irq, 1);
+ chip->db_irq = 0;
+ chip->db_offset = ~0;
+}
+
+static inline irqreturn_t dw_edma_interrupt_emulated(void *data)
+{
+ struct dw_edma_irq *dw_irq = data;
+ struct dw_edma *dw = dw_irq->dw;
+ int db_irq = dw->chip->db_irq;
+
+ if (db_irq > 0) {
+ /*
+ * Interrupt emulation may assert the IRQ line without updating the
+ * normal DONE/ABORT status bits. With a shared IRQ handler we
+ * cannot reliably detect such events by status registers alone, so
+ * always perform the core-specific deassert sequence.
+ */
+ generic_handle_irq(db_irq);
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data)
{
struct dw_edma_irq *dw_irq = data;
dw_edma_abort_interrupt);
}
-static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data)
+static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data)
{
struct dw_edma_irq *dw_irq = data;
dw_edma_abort_interrupt);
}
-static irqreturn_t dw_edma_interrupt_common(int irq, void *data)
+static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
+{
+ irqreturn_t ret = IRQ_NONE;
+
+ ret |= dw_edma_interrupt_write_inner(irq, data);
+ ret |= dw_edma_interrupt_emulated(data);
+
+ return ret;
+}
+
+static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data)
{
irqreturn_t ret = IRQ_NONE;
- ret |= dw_edma_interrupt_write(irq, data);
- ret |= dw_edma_interrupt_read(irq, data);
+ ret |= dw_edma_interrupt_read_inner(irq, data);
+ ret |= dw_edma_interrupt_emulated(data);
+
+ return ret;
+}
+
+static inline irqreturn_t dw_edma_interrupt_common(int irq, void *data)
+{
+ irqreturn_t ret = IRQ_NONE;
+
+ ret |= dw_edma_interrupt_write_inner(irq, data);
+ ret |= dw_edma_interrupt_read_inner(irq, data);
+ ret |= dw_edma_interrupt_emulated(data);
return ret;
}
if (err)
return err;
+ /* Allocate a dedicated virtual IRQ for interrupt-emulation doorbells */
+ err = dw_edma_emul_irq_alloc(dw);
+ if (err)
+ dev_warn(dev, "Failed to allocate emulation IRQ: %d\n", err);
+
/* Setup write/read channels */
err = dw_edma_channel_setup(dw, wr_alloc, rd_alloc);
if (err)
err_irq_free:
for (i = (dw->nr_irqs - 1); i >= 0; i--)
free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
+ dw_edma_emul_irq_free(dw);
return err;
}
/* Free irqs */
for (i = (dw->nr_irqs - 1); i >= 0; i--)
free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
+ dw_edma_emul_irq_free(dw);
/* Deregister eDMA device */
dma_async_device_unregister(&dw->dma);