mirror of
https://github.com/torvalds/linux.git
synced 2026-09-23 22:14:03 +02:00
dmaengine: dw-edma: Add virtual IRQ for interrupt-emulation doorbells
Interrupt emulation can assert the dw-edma IRQ line without updating the DONE/ABORT bits. With the shared read/write/common IRQ handlers, the driver cannot reliably distinguish such an emulated interrupt from a real one and leaving a level IRQ asserted may wedge the line. Allocate a dedicated, requestable Linux virtual IRQ (db_irq) for interrupt emulation and attach an irq_chip whose .irq_ack runs the core-specific deassert sequence (.ack_emulated_irq()). The physical dw-edma interrupt handlers raise this virtual IRQ via generic_handle_irq(), ensuring emulated IRQs are always deasserted. Export the virtual IRQ number (db_irq) and the doorbell register offset (db_offset) via struct dw_edma_chip so platform users can expose interrupt emulation as a doorbell. Without this, a single interrupt-emulation write can leave the level IRQ line asserted and cause the generic IRQ layer to disable it. Signed-off-by: Koichiro Den <den@valinux.co.jp> Reviewed-by: Frank Li <Frank.Li@nxp.com> Link: https://patch.msgid.link/20260215152216.3393561-3-den@valinux.co.jp Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
parent
2e87268795
commit
d9d5e1bdd1
|
|
@ -663,7 +663,96 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
|
||||||
chan->status = EDMA_ST_IDLE;
|
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;
|
struct dw_edma_irq *dw_irq = data;
|
||||||
|
|
||||||
|
|
@ -672,7 +761,7 @@ static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
|
||||||
dw_edma_abort_interrupt);
|
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;
|
struct dw_edma_irq *dw_irq = data;
|
||||||
|
|
||||||
|
|
@ -681,12 +770,33 @@ static inline irqreturn_t dw_edma_interrupt_read(int irq, void *data)
|
||||||
dw_edma_abort_interrupt);
|
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;
|
irqreturn_t ret = IRQ_NONE;
|
||||||
|
|
||||||
ret |= dw_edma_interrupt_write(irq, data);
|
ret |= dw_edma_interrupt_write_inner(irq, data);
|
||||||
ret |= dw_edma_interrupt_read(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_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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -973,6 +1083,11 @@ int dw_edma_probe(struct dw_edma_chip *chip)
|
||||||
if (err)
|
if (err)
|
||||||
return 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 */
|
/* Setup write/read channels */
|
||||||
err = dw_edma_channel_setup(dw, wr_alloc, rd_alloc);
|
err = dw_edma_channel_setup(dw, wr_alloc, rd_alloc);
|
||||||
if (err)
|
if (err)
|
||||||
|
|
@ -988,6 +1103,7 @@ int dw_edma_probe(struct dw_edma_chip *chip)
|
||||||
err_irq_free:
|
err_irq_free:
|
||||||
for (i = (dw->nr_irqs - 1); i >= 0; i--)
|
for (i = (dw->nr_irqs - 1); i >= 0; i--)
|
||||||
free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
|
free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
|
||||||
|
dw_edma_emul_irq_free(dw);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
@ -1010,6 +1126,7 @@ int dw_edma_remove(struct dw_edma_chip *chip)
|
||||||
/* Free irqs */
|
/* Free irqs */
|
||||||
for (i = (dw->nr_irqs - 1); i >= 0; i--)
|
for (i = (dw->nr_irqs - 1); i >= 0; i--)
|
||||||
free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
|
free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
|
||||||
|
dw_edma_emul_irq_free(dw);
|
||||||
|
|
||||||
/* Deregister eDMA device */
|
/* Deregister eDMA device */
|
||||||
dma_async_device_unregister(&dw->dma);
|
dma_async_device_unregister(&dw->dma);
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,8 @@ enum dw_edma_chip_flags {
|
||||||
* @ll_region_rd: DMA descriptor link list memory for read channel
|
* @ll_region_rd: DMA descriptor link list memory for read channel
|
||||||
* @dt_region_wr: DMA data memory for write channel
|
* @dt_region_wr: DMA data memory for write channel
|
||||||
* @dt_region_rd: DMA data memory for read channel
|
* @dt_region_rd: DMA data memory for read channel
|
||||||
|
* @db_irq: Virtual IRQ dedicated to interrupt emulation
|
||||||
|
* @db_offset: Offset from DMA register base
|
||||||
* @mf: DMA register map format
|
* @mf: DMA register map format
|
||||||
* @dw: struct dw_edma that is filled by dw_edma_probe()
|
* @dw: struct dw_edma that is filled by dw_edma_probe()
|
||||||
*/
|
*/
|
||||||
|
|
@ -94,6 +96,10 @@ struct dw_edma_chip {
|
||||||
struct dw_edma_region dt_region_wr[EDMA_MAX_WR_CH];
|
struct dw_edma_region dt_region_wr[EDMA_MAX_WR_CH];
|
||||||
struct dw_edma_region dt_region_rd[EDMA_MAX_RD_CH];
|
struct dw_edma_region dt_region_rd[EDMA_MAX_RD_CH];
|
||||||
|
|
||||||
|
/* interrupt emulation */
|
||||||
|
int db_irq;
|
||||||
|
resource_size_t db_offset;
|
||||||
|
|
||||||
enum dw_edma_map_format mf;
|
enum dw_edma_map_format mf;
|
||||||
|
|
||||||
struct dw_edma *dw;
|
struct dw_edma *dw;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user