Commit f12e39db authored by Adrian Hunter's avatar Adrian Hunter Committed by Ulf Hansson

mmc: sdhci: Add CQE support

Add an interrupt hook and helper functions for enabling, disabling and
delivering interrupts to a CQE.
Signed-off-by: default avatarAdrian Hunter <adrian.hunter@intel.com>
Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
Tested-by: default avatarLudovic Desroches <ludovic.desroches@microchip.com>
parent f5c1ab82
...@@ -252,6 +252,8 @@ static void sdhci_init(struct sdhci_host *host, int soft) ...@@ -252,6 +252,8 @@ static void sdhci_init(struct sdhci_host *host, int soft)
sdhci_set_default_irqs(host); sdhci_set_default_irqs(host);
host->cqe_on = false;
if (soft) { if (soft) {
/* force clock reconfiguration */ /* force clock reconfiguration */
host->clock = 0; host->clock = 0;
...@@ -2672,13 +2674,19 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) ...@@ -2672,13 +2674,19 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
} }
do { do {
DBG("IRQ status 0x%08x\n", intmask);
if (host->ops->irq) {
intmask = host->ops->irq(host, intmask);
if (!intmask)
goto cont;
}
/* Clear selected interrupts. */ /* Clear selected interrupts. */
mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK | mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK |
SDHCI_INT_BUS_POWER); SDHCI_INT_BUS_POWER);
sdhci_writel(host, mask, SDHCI_INT_STATUS); sdhci_writel(host, mask, SDHCI_INT_STATUS);
DBG("IRQ status 0x%08x\n", intmask);
if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) & u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
SDHCI_CARD_PRESENT; SDHCI_CARD_PRESENT;
...@@ -2738,7 +2746,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) ...@@ -2738,7 +2746,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
unexpected |= intmask; unexpected |= intmask;
sdhci_writel(host, intmask, SDHCI_INT_STATUS); sdhci_writel(host, intmask, SDHCI_INT_STATUS);
} }
cont:
if (result == IRQ_NONE) if (result == IRQ_NONE)
result = IRQ_HANDLED; result = IRQ_HANDLED;
...@@ -2965,6 +2973,119 @@ EXPORT_SYMBOL_GPL(sdhci_runtime_resume_host); ...@@ -2965,6 +2973,119 @@ EXPORT_SYMBOL_GPL(sdhci_runtime_resume_host);
#endif /* CONFIG_PM */ #endif /* CONFIG_PM */
/*****************************************************************************\
* *
* Command Queue Engine (CQE) helpers *
* *
\*****************************************************************************/
void sdhci_cqe_enable(struct mmc_host *mmc)
{
struct sdhci_host *host = mmc_priv(mmc);
unsigned long flags;
u8 ctrl;
spin_lock_irqsave(&host->lock, flags);
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
ctrl &= ~SDHCI_CTRL_DMA_MASK;
if (host->flags & SDHCI_USE_64_BIT_DMA)
ctrl |= SDHCI_CTRL_ADMA64;
else
ctrl |= SDHCI_CTRL_ADMA32;
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 512),
SDHCI_BLOCK_SIZE);
/* Set maximum timeout */
sdhci_writeb(host, 0xE, SDHCI_TIMEOUT_CONTROL);
host->ier = host->cqe_ier;
sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
host->cqe_on = true;
pr_debug("%s: sdhci: CQE on, IRQ mask %#x, IRQ status %#x\n",
mmc_hostname(mmc), host->ier,
sdhci_readl(host, SDHCI_INT_STATUS));
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
}
EXPORT_SYMBOL_GPL(sdhci_cqe_enable);
void sdhci_cqe_disable(struct mmc_host *mmc, bool recovery)
{
struct sdhci_host *host = mmc_priv(mmc);
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
sdhci_set_default_irqs(host);
host->cqe_on = false;
if (recovery) {
sdhci_do_reset(host, SDHCI_RESET_CMD);
sdhci_do_reset(host, SDHCI_RESET_DATA);
}
pr_debug("%s: sdhci: CQE off, IRQ mask %#x, IRQ status %#x\n",
mmc_hostname(mmc), host->ier,
sdhci_readl(host, SDHCI_INT_STATUS));
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
}
EXPORT_SYMBOL_GPL(sdhci_cqe_disable);
bool sdhci_cqe_irq(struct sdhci_host *host, u32 intmask, int *cmd_error,
int *data_error)
{
u32 mask;
if (!host->cqe_on)
return false;
if (intmask & (SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC))
*cmd_error = -EILSEQ;
else if (intmask & SDHCI_INT_TIMEOUT)
*cmd_error = -ETIMEDOUT;
else
*cmd_error = 0;
if (intmask & (SDHCI_INT_DATA_END_BIT | SDHCI_INT_DATA_CRC))
*data_error = -EILSEQ;
else if (intmask & SDHCI_INT_DATA_TIMEOUT)
*data_error = -ETIMEDOUT;
else if (intmask & SDHCI_INT_ADMA_ERROR)
*data_error = -EIO;
else
*data_error = 0;
/* Clear selected interrupts. */
mask = intmask & host->cqe_ier;
sdhci_writel(host, mask, SDHCI_INT_STATUS);
if (intmask & SDHCI_INT_BUS_POWER)
pr_err("%s: Card is consuming too much power!\n",
mmc_hostname(host->mmc));
intmask &= ~(host->cqe_ier | SDHCI_INT_ERROR);
if (intmask) {
sdhci_writel(host, intmask, SDHCI_INT_STATUS);
pr_err("%s: CQE: Unexpected interrupt 0x%08x.\n",
mmc_hostname(host->mmc), intmask);
sdhci_dumpregs(host);
}
return true;
}
EXPORT_SYMBOL_GPL(sdhci_cqe_irq);
/*****************************************************************************\ /*****************************************************************************\
* * * *
* Device allocation/registration * * Device allocation/registration *
...@@ -2990,6 +3111,9 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev, ...@@ -2990,6 +3111,9 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev,
host->flags = SDHCI_SIGNALING_330; host->flags = SDHCI_SIGNALING_330;
host->cqe_ier = SDHCI_CQE_INT_MASK;
host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK;
return host; return host;
} }
......
...@@ -134,6 +134,7 @@ ...@@ -134,6 +134,7 @@
#define SDHCI_INT_CARD_REMOVE 0x00000080 #define SDHCI_INT_CARD_REMOVE 0x00000080
#define SDHCI_INT_CARD_INT 0x00000100 #define SDHCI_INT_CARD_INT 0x00000100
#define SDHCI_INT_RETUNE 0x00001000 #define SDHCI_INT_RETUNE 0x00001000
#define SDHCI_INT_CQE 0x00004000
#define SDHCI_INT_ERROR 0x00008000 #define SDHCI_INT_ERROR 0x00008000
#define SDHCI_INT_TIMEOUT 0x00010000 #define SDHCI_INT_TIMEOUT 0x00010000
#define SDHCI_INT_CRC 0x00020000 #define SDHCI_INT_CRC 0x00020000
...@@ -158,6 +159,13 @@ ...@@ -158,6 +159,13 @@
SDHCI_INT_BLK_GAP) SDHCI_INT_BLK_GAP)
#define SDHCI_INT_ALL_MASK ((unsigned int)-1) #define SDHCI_INT_ALL_MASK ((unsigned int)-1)
#define SDHCI_CQE_INT_ERR_MASK ( \
SDHCI_INT_ADMA_ERROR | SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | \
SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | \
SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT)
#define SDHCI_CQE_INT_MASK (SDHCI_CQE_INT_ERR_MASK | SDHCI_INT_CQE)
#define SDHCI_ACMD12_ERR 0x3C #define SDHCI_ACMD12_ERR 0x3C
#define SDHCI_HOST_CONTROL2 0x3E #define SDHCI_HOST_CONTROL2 0x3E
...@@ -518,6 +526,10 @@ struct sdhci_host { ...@@ -518,6 +526,10 @@ struct sdhci_host {
/* cached registers */ /* cached registers */
u32 ier; u32 ier;
bool cqe_on; /* CQE is operating */
u32 cqe_ier; /* CQE interrupt mask */
u32 cqe_err_ier; /* CQE error interrupt mask */
wait_queue_head_t buf_ready_int; /* Waitqueue for Buffer Read Ready interrupt */ wait_queue_head_t buf_ready_int; /* Waitqueue for Buffer Read Ready interrupt */
unsigned int tuning_done; /* Condition flag set when CMD19 succeeds */ unsigned int tuning_done; /* Condition flag set when CMD19 succeeds */
...@@ -544,6 +556,8 @@ struct sdhci_ops { ...@@ -544,6 +556,8 @@ struct sdhci_ops {
void (*set_power)(struct sdhci_host *host, unsigned char mode, void (*set_power)(struct sdhci_host *host, unsigned char mode,
unsigned short vdd); unsigned short vdd);
u32 (*irq)(struct sdhci_host *host, u32 intmask);
int (*enable_dma)(struct sdhci_host *host); int (*enable_dma)(struct sdhci_host *host);
unsigned int (*get_max_clock)(struct sdhci_host *host); unsigned int (*get_max_clock)(struct sdhci_host *host);
unsigned int (*get_min_clock)(struct sdhci_host *host); unsigned int (*get_min_clock)(struct sdhci_host *host);
...@@ -697,6 +711,11 @@ int sdhci_runtime_suspend_host(struct sdhci_host *host); ...@@ -697,6 +711,11 @@ int sdhci_runtime_suspend_host(struct sdhci_host *host);
int sdhci_runtime_resume_host(struct sdhci_host *host); int sdhci_runtime_resume_host(struct sdhci_host *host);
#endif #endif
void sdhci_cqe_enable(struct mmc_host *mmc);
void sdhci_cqe_disable(struct mmc_host *mmc, bool recovery);
bool sdhci_cqe_irq(struct sdhci_host *host, u32 intmask, int *cmd_error,
int *data_error);
void sdhci_dumpregs(struct sdhci_host *host); void sdhci_dumpregs(struct sdhci_host *host);
#endif /* __SDHCI_HW_H */ #endif /* __SDHCI_HW_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment