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: 79ed05e329c3 ("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/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/of_device.h>
|
#include <linux/of_device.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
@ -90,9 +91,11 @@
|
|||||||
#define CFG_CLK_ALWAYS_ON BIT(18)
|
#define CFG_CLK_ALWAYS_ON BIT(18)
|
||||||
#define CFG_CHK_DS BIT(20)
|
#define CFG_CHK_DS BIT(20)
|
||||||
#define CFG_AUTO_CLK BIT(23)
|
#define CFG_AUTO_CLK BIT(23)
|
||||||
|
#define CFG_ERR_ABORT BIT(27)
|
||||||
|
|
||||||
#define SD_EMMC_STATUS 0x48
|
#define SD_EMMC_STATUS 0x48
|
||||||
#define STATUS_BUSY BIT(31)
|
#define STATUS_BUSY BIT(31)
|
||||||
|
#define STATUS_DESC_BUSY BIT(30)
|
||||||
#define STATUS_DATI GENMASK(23, 16)
|
#define STATUS_DATI GENMASK(23, 16)
|
||||||
|
|
||||||
#define SD_EMMC_IRQ_EN 0x4c
|
#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 |= FIELD_PREP(CMD_CFG_CMD_INDEX_MASK, cmd->opcode);
|
||||||
cmd_cfg |= CMD_CFG_OWNER; /* owned by CPU */
|
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);
|
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;
|
u32 irq_en, status, raw_status;
|
||||||
irqreturn_t ret = IRQ_NONE;
|
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))
|
if (WARN_ON(!host) || WARN_ON(!host->cmd))
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
|
|
||||||
@ -1029,22 +1044,18 @@ static irqreturn_t meson_mmc_irq(int irq, void *dev_id)
|
|||||||
|
|
||||||
cmd = host->cmd;
|
cmd = host->cmd;
|
||||||
data = cmd->data;
|
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;
|
cmd->error = 0;
|
||||||
if (status & IRQ_CRC_ERR) {
|
if (status & IRQ_CRC_ERR) {
|
||||||
dev_dbg(host->dev, "CRC Error - status 0x%08x\n", status);
|
dev_dbg(host->dev, "CRC Error - status 0x%08x\n", status);
|
||||||
cmd->error = -EILSEQ;
|
cmd->error = -EILSEQ;
|
||||||
ret = IRQ_HANDLED;
|
ret = IRQ_WAKE_THREAD;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status & IRQ_TIMEOUTS) {
|
if (status & IRQ_TIMEOUTS) {
|
||||||
dev_dbg(host->dev, "Timeout - status 0x%08x\n", status);
|
dev_dbg(host->dev, "Timeout - status 0x%08x\n", status);
|
||||||
cmd->error = -ETIMEDOUT;
|
cmd->error = -ETIMEDOUT;
|
||||||
ret = IRQ_HANDLED;
|
ret = IRQ_WAKE_THREAD;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1069,17 +1080,49 @@ out:
|
|||||||
/* ack all enabled interrupts */
|
/* ack all enabled interrupts */
|
||||||
writel(irq_en, host->regs + SD_EMMC_STATUS);
|
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)
|
if (ret == IRQ_HANDLED)
|
||||||
meson_mmc_request_done(host->mmc, cmd->mrq);
|
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);
|
spin_unlock(&host->lock);
|
||||||
return ret;
|
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)
|
static irqreturn_t meson_mmc_irq_thread(int irq, void *dev_id)
|
||||||
{
|
{
|
||||||
struct meson_host *host = 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))
|
if (WARN_ON(!cmd))
|
||||||
return IRQ_NONE;
|
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;
|
data = cmd->data;
|
||||||
if (meson_mmc_bounce_buf_read(data)) {
|
if (meson_mmc_bounce_buf_read(data)) {
|
||||||
xfer_bytes = data->blksz * data->blocks;
|
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_RC_CC_MASK, ilog2(SD_EMMC_CFG_CMD_GAP));
|
||||||
cfg |= FIELD_PREP(CFG_BLK_LEN_MASK, ilog2(SD_EMMC_CFG_BLK_SIZE));
|
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);
|
writel(cfg, host->regs + SD_EMMC_CFG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user