static irqreturn_t mvsd_irq()

in host/mvsdio.c [344:508]


static irqreturn_t mvsd_irq(int irq, void *dev)
{
	struct mvsd_host *host = dev;
	void __iomem *iobase = host->base;
	u32 intr_status, intr_done_mask;
	int irq_handled = 0;

	intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
	dev_dbg(host->dev, "intr 0x%04x intr_en 0x%04x hw_state 0x%04x\n",
		intr_status, mvsd_read(MVSD_NOR_INTR_EN),
		mvsd_read(MVSD_HW_STATE));

	/*
	 * It looks like, SDIO IP can issue one late, spurious irq
	 * although all irqs should be disabled. To work around this,
	 * bail out early, if we didn't expect any irqs to occur.
	 */
	if (!mvsd_read(MVSD_NOR_INTR_EN) && !mvsd_read(MVSD_ERR_INTR_EN)) {
		dev_dbg(host->dev, "spurious irq detected intr 0x%04x intr_en 0x%04x erri 0x%04x erri_en 0x%04x\n",
			mvsd_read(MVSD_NOR_INTR_STATUS),
			mvsd_read(MVSD_NOR_INTR_EN),
			mvsd_read(MVSD_ERR_INTR_STATUS),
			mvsd_read(MVSD_ERR_INTR_EN));
		return IRQ_HANDLED;
	}

	spin_lock(&host->lock);

	/* PIO handling, if needed. Messy business... */
	if (host->pio_size &&
	    (intr_status & host->intr_en &
	     (MVSD_NOR_RX_READY | MVSD_NOR_RX_FIFO_8W))) {
		u16 *p = host->pio_ptr;
		int s = host->pio_size;
		while (s >= 32 && (intr_status & MVSD_NOR_RX_FIFO_8W)) {
			readsw(iobase + MVSD_FIFO, p, 16);
			p += 16;
			s -= 32;
			intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
		}
		/*
		 * Normally we'd use < 32 here, but the RX_FIFO_8W bit
		 * doesn't appear to assert when there is exactly 32 bytes
		 * (8 words) left to fetch in a transfer.
		 */
		if (s <= 32) {
			while (s >= 4 && (intr_status & MVSD_NOR_RX_READY)) {
				put_unaligned(mvsd_read(MVSD_FIFO), p++);
				put_unaligned(mvsd_read(MVSD_FIFO), p++);
				s -= 4;
				intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
			}
			if (s && s < 4 && (intr_status & MVSD_NOR_RX_READY)) {
				u16 val[2] = {0, 0};
				val[0] = mvsd_read(MVSD_FIFO);
				val[1] = mvsd_read(MVSD_FIFO);
				memcpy(p, ((void *)&val) + 4 - s, s);
				s = 0;
				intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
			}
			if (s == 0) {
				host->intr_en &=
				     ~(MVSD_NOR_RX_READY | MVSD_NOR_RX_FIFO_8W);
				mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
			} else if (host->intr_en & MVSD_NOR_RX_FIFO_8W) {
				host->intr_en &= ~MVSD_NOR_RX_FIFO_8W;
				host->intr_en |= MVSD_NOR_RX_READY;
				mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
			}
		}
		dev_dbg(host->dev, "pio %d intr 0x%04x hw_state 0x%04x\n",
			s, intr_status, mvsd_read(MVSD_HW_STATE));
		host->pio_ptr = p;
		host->pio_size = s;
		irq_handled = 1;
	} else if (host->pio_size &&
		   (intr_status & host->intr_en &
		    (MVSD_NOR_TX_AVAIL | MVSD_NOR_TX_FIFO_8W))) {
		u16 *p = host->pio_ptr;
		int s = host->pio_size;
		/*
		 * The TX_FIFO_8W bit is unreliable. When set, bursting
		 * 16 halfwords all at once in the FIFO drops data. Actually
		 * TX_AVAIL does go off after only one word is pushed even if
		 * TX_FIFO_8W remains set.
		 */
		while (s >= 4 && (intr_status & MVSD_NOR_TX_AVAIL)) {
			mvsd_write(MVSD_FIFO, get_unaligned(p++));
			mvsd_write(MVSD_FIFO, get_unaligned(p++));
			s -= 4;
			intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
		}
		if (s < 4) {
			if (s && (intr_status & MVSD_NOR_TX_AVAIL)) {
				u16 val[2] = {0, 0};
				memcpy(((void *)&val) + 4 - s, p, s);
				mvsd_write(MVSD_FIFO, val[0]);
				mvsd_write(MVSD_FIFO, val[1]);
				s = 0;
				intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
			}
			if (s == 0) {
				host->intr_en &=
				     ~(MVSD_NOR_TX_AVAIL | MVSD_NOR_TX_FIFO_8W);
				mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
			}
		}
		dev_dbg(host->dev, "pio %d intr 0x%04x hw_state 0x%04x\n",
			s, intr_status, mvsd_read(MVSD_HW_STATE));
		host->pio_ptr = p;
		host->pio_size = s;
		irq_handled = 1;
	}

	mvsd_write(MVSD_NOR_INTR_STATUS, intr_status);

	intr_done_mask = MVSD_NOR_CARD_INT | MVSD_NOR_RX_READY |
			 MVSD_NOR_RX_FIFO_8W | MVSD_NOR_TX_FIFO_8W;
	if (intr_status & host->intr_en & ~intr_done_mask) {
		struct mmc_request *mrq = host->mrq;
		struct mmc_command *cmd = mrq->cmd;
		u32 err_status = 0;

		del_timer(&host->timer);
		host->mrq = NULL;

		host->intr_en &= MVSD_NOR_CARD_INT;
		mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
		mvsd_write(MVSD_ERR_INTR_EN, 0);

		spin_unlock(&host->lock);

		if (intr_status & MVSD_NOR_UNEXP_RSP) {
			cmd->error = -EPROTO;
		} else if (intr_status & MVSD_NOR_ERROR) {
			err_status = mvsd_read(MVSD_ERR_INTR_STATUS);
			dev_dbg(host->dev, "err 0x%04x\n", err_status);
		}

		err_status = mvsd_finish_cmd(host, cmd, err_status);
		if (mrq->data)
			err_status = mvsd_finish_data(host, mrq->data, err_status);
		if (err_status) {
			dev_err(host->dev, "unhandled error status %#04x\n",
				err_status);
			cmd->error = -ENOMSG;
		}

		mmc_request_done(host->mmc, mrq);
		irq_handled = 1;
	} else
		spin_unlock(&host->lock);

	if (intr_status & MVSD_NOR_CARD_INT) {
		mmc_signal_sdio_irq(host->mmc);
		irq_handled = 1;
	}

	if (irq_handled)
		return IRQ_HANDLED;

	dev_err(host->dev, "unhandled interrupt status=0x%04x en=0x%04x pio=%d\n",
		intr_status, host->intr_en, host->pio_size);
	return IRQ_NONE;
}