[PATCH] ep93xx: Implement double buffering for M2M DMA channels

H Hartley Sweeten hartleys at visionengravers.com
Tue Apr 17 16:51:47 EDT 2012


On Tuesday, April 17, 2012 8:46 AM, H Hartley Sweeten wrote:
> On Tuesday, April 17, 2012 12:16 AM, Rafal Prylowski wrote:
>> On 2012-04-16 20:59, H Hartley Sweeten wrote:
>>> It appears your patch is causing an interrupt storm on my system.
>>> 
>>
>> Could you please apply the following patch on top of double buffering
>> patch? I would like to know the state of dma channel when you get
>> that interrupt storm.
>
> Rafal,
>
> Here's the output:
>
> mmc_spi spi0.1: SD/MMC host mmc0, no poweroff
> M2M: 20c3
> M2M: 20c3
> M2M: 21c3
> M2M: 21c3
> M2M: 21c3
> M2M: 21c3
> M2M: 21c3
> M2M: 21c3
>
> The "M2M: 21c3" keeps getting output until the system is turned off.

Rafal,

It appears that the M2M_CONTROL_NFBINT bit is never getting set when
dma is used with the spi-ep93xx.c and mmc_spi.c drivers.

I added a prink in msm_hw_submit():

	if (ep93xx_dma_advance_active(edmac)) {
		m2m_fill_desc(edmac);
		control |= M2M_CONTROL_NFBINT;
		printk("%s: NFB enabled\n", __func__);
	}

This message is never displayed.

And for some reason the txd.cookie is not getting set correctly to detect
the last entry in m2m_hw_interrupt.

	/*
	 * Check whether we are done with descriptors or not. This, together
	 * with DMA channel state, determines action to take in interrupt.
	 */
	last = list_first_entry(edmac->active.next,
		struct ep93xx_dma_desc, node)->txd.cookie;

This is causing the code for the INTERRUPT_NEXT_BUFFER to be executed
even though a "next" buffer does not exist.

I think your handling logic in m2m_hw_interrupt needs a bit more work.

I hacked it to test and this appears to work but it's not optimal...

Here's the whole function:


static int m2m_hw_interrupt(struct ep93xx_dma_chan *edmac)
{
	u32 status = readl(edmac->regs + M2M_STATUS);
	u32 ctl_fsm = status & M2M_STATUS_CTL_MASK;
	u32 buf_fsm = status & M2M_STATUS_BUF_MASK;
	bool done = status & M2M_STATUS_DONE;
	bool last;
	u32 control;

	/* Accept only DONE and NFB interrupts */
	if (!(readl(edmac->regs + M2M_INTERRUPT) & M2M_INTERRUPT_MASK))
		return INTERRUPT_UNKNOWN;

	if (done)
		/* Clear the DONE bit */
		writel(0, edmac->regs + M2M_INTERRUPT);

	/*
	 * Check whether we are done with descriptors or not. This, together
	 * with DMA channel state, determines action to take in interrupt.
	 */
	last = list_first_entry(edmac->active.next,
		struct ep93xx_dma_desc, node)->txd.cookie;

	/*
	 * Use M2M DMA Buffer FSM and Control FSM to check current state of
	 * DMA channel. Using DONE and NFB bits from channel status register
	 * or bits from channel interrupt register was proven not to be
	 * reliable.
	 */
	if (!last &&
	    (buf_fsm == M2M_STATUS_BUF_NO ||
	     buf_fsm == M2M_STATUS_BUF_ON)) {
		/*
		 * Two buffers are ready for update when Buffer FSM is in
		 * DMA_NO_BUF state. Only one buffer can be prepared without
		 * disabling the channel, or polling the DONE bit.
		 * To simplify things, always prepare only one buffer.
		 */
		if (ep93xx_dma_advance_active(edmac)) {
			m2m_fill_desc(edmac);
			if (done && !edmac->chan.private) {
				/* Software trigger for memcpy channel */
				control = readl(edmac->regs + M2M_CONTROL);
				control |= M2M_CONTROL_START;
				writel(control, edmac->regs + M2M_CONTROL);
			}
			return INTERRUPT_NEXT_BUFFER;
		} else {
			/*
			 * HACK: We don't have another buffer to prepare.
			 * Just disable the channel.
			 */
			control = readl(edmac->regs + M2M_CONTROL);
			control &= ~(M2M_CONTROL_DONEINT | M2M_CONTROL_NFBINT
				| M2M_CONTROL_ENABLE);
			writel(control, edmac->regs + M2M_CONTROL);
			return INTERRUPT_DONE;
		}
	}

	/*
	 * Disable the channel only when Buffer FSM is in DMA_NO_BUF state
	 * and Control FSM is in DMA_STALL state.
	 */
	if (last &&
	    buf_fsm == M2M_STATUS_BUF_NO &&
	    ctl_fsm == M2M_STATUS_CTL_STALL) {
		ep93xx_dma_advance_active(edmac);
		/* Disable interrupts and the channel */
		control = readl(edmac->regs + M2M_CONTROL);
		control &= ~(M2M_CONTROL_DONEINT | M2M_CONTROL_NFBINT
			    | M2M_CONTROL_ENABLE);
		writel(control, edmac->regs + M2M_CONTROL);
		return INTERRUPT_DONE;
	}

	/*
	 * Nothing to do this time.
	 */
	return INTERRUPT_NEXT_BUFFER;
}


With this I am able to boot my system and use the mmc card.

Regards,
Hartley




More information about the linux-arm-kernel mailing list