mmc: meson-gx: make sure the descriptor is stopped on errors
On errors, if we don't stop the descriptor chain, it may continue to
run and raise IRQ after we have called mmc_request_done(). This is bad
because we won't be able to get cmd anymore and properly deal with the
IRQ.
This patch makes sure the descriptor chain is stopped before
calling mmc_request_done()
Fixes: 79ed05e329
("mmc: meson-gx: add support for descriptor chain mode")
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
parent
41fd4caeb0
commit
18f92bc02f
@ -21,6 +21,7 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
@ -90,9 +91,11 @@
|
||||
#define CFG_CLK_ALWAYS_ON BIT(18)
|
||||
#define CFG_CHK_DS BIT(20)
|
||||
#define CFG_AUTO_CLK BIT(23)
|
||||
#define CFG_ERR_ABORT BIT(27)
|
||||
|
||||
#define SD_EMMC_STATUS 0x48
|
||||
#define STATUS_BUSY BIT(31)
|
||||
#define STATUS_DESC_BUSY BIT(30)
|
||||
#define STATUS_DATI GENMASK(23, 16)
|
||||
|
||||
#define SD_EMMC_IRQ_EN 0x4c
|
||||
@ -928,6 +931,7 @@ static void meson_mmc_start_cmd(struct mmc_host *mmc, struct mmc_command *cmd)
|
||||
|
||||
cmd_cfg |= FIELD_PREP(CMD_CFG_CMD_INDEX_MASK, cmd->opcode);
|
||||
cmd_cfg |= CMD_CFG_OWNER; /* owned by CPU */
|
||||
cmd_cfg |= CMD_CFG_ERROR; /* stop in case of error */
|
||||
|
||||
meson_mmc_set_response_bits(cmd, &cmd_cfg);
|
||||
|
||||
@ -1022,6 +1026,17 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
|
||||
u32 irq_en, status, raw_status;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
|
||||
irq_en = readl(host->regs + SD_EMMC_IRQ_EN);
|
||||
raw_status = readl(host->regs + SD_EMMC_STATUS);
|
||||
status = raw_status & irq_en;
|
||||
|
||||
if (!status) {
|
||||
dev_dbg(host->dev,
|
||||
"Unexpected IRQ! irq_en 0x%08x - status 0x%08x\n",
|
||||
irq_en, raw_status);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
if (WARN_ON(!host) || WARN_ON(!host->cmd))
|
||||
return IRQ_NONE;
|
||||
|
||||
@ -1029,22 +1044,18 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
|
||||
|
||||
cmd = host->cmd;
|
||||
data = cmd->data;
|
||||
irq_en = readl(host->regs + SD_EMMC_IRQ_EN);
|
||||
raw_status = readl(host->regs + SD_EMMC_STATUS);
|
||||
status = raw_status & irq_en;
|
||||
|
||||
cmd->error = 0;
|
||||
if (status & IRQ_CRC_ERR) {
|
||||
dev_dbg(host->dev, "CRC Error - status 0x%08x\n", status);
|
||||
cmd->error = -EILSEQ;
|
||||
ret = IRQ_HANDLED;
|
||||
ret = IRQ_WAKE_THREAD;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (status & IRQ_TIMEOUTS) {
|
||||
dev_dbg(host->dev, "Timeout - status 0x%08x\n", status);
|
||||
cmd->error = -ETIMEDOUT;
|
||||
ret = IRQ_HANDLED;
|
||||
ret = IRQ_WAKE_THREAD;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -1069,17 +1080,49 @@ out:
|
||||
/* ack all enabled interrupts */
|
||||
writel(irq_en, host->regs + SD_EMMC_STATUS);
|
||||
|
||||
if (cmd->error) {
|
||||
/* Stop desc in case of errors */
|
||||
u32 start = readl(host->regs + SD_EMMC_START);
|
||||
|
||||
start &= ~START_DESC_BUSY;
|
||||
writel(start, host->regs + SD_EMMC_START);
|
||||
}
|
||||
|
||||
if (ret == IRQ_HANDLED)
|
||||
meson_mmc_request_done(host->mmc, cmd->mrq);
|
||||
else if (ret == IRQ_NONE)
|
||||
dev_warn(host->dev,
|
||||
"Unexpected IRQ! status=0x%08x, irq_en=0x%08x\n",
|
||||
raw_status, irq_en);
|
||||
|
||||
spin_unlock(&host->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int meson_mmc_wait_desc_stop(struct meson_host *host)
|
||||
{
|
||||
int loop;
|
||||
u32 status;
|
||||
|
||||
/*
|
||||
* It may sometimes take a while for it to actually halt. Here, we
|
||||
* are giving it 5ms to comply
|
||||
*
|
||||
* If we don't confirm the descriptor is stopped, it might raise new
|
||||
* IRQs after we have called mmc_request_done() which is bad.
|
||||
*/
|
||||
for (loop = 50; loop; loop--) {
|
||||
status = readl(host->regs + SD_EMMC_STATUS);
|
||||
if (status & (STATUS_BUSY | STATUS_DESC_BUSY))
|
||||
udelay(100);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (status & (STATUS_BUSY | STATUS_DESC_BUSY)) {
|
||||
dev_err(host->dev, "Timed out waiting for host to stop\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id)
|
||||
{
|
||||
struct meson_host *host = dev_id;
|
||||
@ -1090,6 +1133,13 @@ static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id)
|
||||
if (WARN_ON(!cmd))
|
||||
return IRQ_NONE;
|
||||
|
||||
if (cmd->error) {
|
||||
meson_mmc_wait_desc_stop(host);
|
||||
meson_mmc_request_done(host->mmc, cmd->mrq);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
data = cmd->data;
|
||||
if (meson_mmc_bounce_buf_read(data)) {
|
||||
xfer_bytes = data->blksz * data->blocks;
|
||||
@ -1130,6 +1180,9 @@ static void meson_mmc_cfg_init(struct meson_host *host)
|
||||
cfg |= FIELD_PREP(CFG_RC_CC_MASK, ilog2(SD_EMMC_CFG_CMD_GAP));
|
||||
cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, ilog2(SD_EMMC_CFG_BLK_SIZE));
|
||||
|
||||
/* abort chain on R/W errors */
|
||||
cfg |= CFG_ERR_ABORT;
|
||||
|
||||
writel(cfg, host->regs + SD_EMMC_CFG);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user