|
@@ -44,6 +44,7 @@
|
|
|
#include <mach/hardware.h>
|
|
|
|
|
|
#define DRIVER_NAME "mxc-mmc"
|
|
|
+#define MXCMCI_TIMEOUT_MS 10000
|
|
|
|
|
|
#define MMC_REG_STR_STP_CLK 0x00
|
|
|
#define MMC_REG_STATUS 0x04
|
|
@@ -150,6 +151,8 @@ struct mxcmci_host {
|
|
|
int dmareq;
|
|
|
struct dma_slave_config dma_slave_config;
|
|
|
struct imx_dma_data dma_data;
|
|
|
+
|
|
|
+ struct timer_list watchdog;
|
|
|
};
|
|
|
|
|
|
static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios);
|
|
@@ -271,9 +274,32 @@ static int mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data)
|
|
|
dmaengine_submit(host->desc);
|
|
|
dma_async_issue_pending(host->dma);
|
|
|
|
|
|
+ mod_timer(&host->watchdog, jiffies + msecs_to_jiffies(MXCMCI_TIMEOUT_MS));
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static void mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat);
|
|
|
+static void mxcmci_data_done(struct mxcmci_host *host, unsigned int stat);
|
|
|
+
|
|
|
+static void mxcmci_dma_callback(void *data)
|
|
|
+{
|
|
|
+ struct mxcmci_host *host = data;
|
|
|
+ u32 stat;
|
|
|
+
|
|
|
+ del_timer(&host->watchdog);
|
|
|
+
|
|
|
+ stat = readl(host->base + MMC_REG_STATUS);
|
|
|
+ writel(stat & ~STATUS_DATA_TRANS_DONE, host->base + MMC_REG_STATUS);
|
|
|
+
|
|
|
+ dev_dbg(mmc_dev(host->mmc), "%s: 0x%08x\n", __func__, stat);
|
|
|
+
|
|
|
+ if (stat & STATUS_READ_OP_DONE)
|
|
|
+ writel(STATUS_READ_OP_DONE, host->base + MMC_REG_STATUS);
|
|
|
+
|
|
|
+ mxcmci_data_done(host, stat);
|
|
|
+}
|
|
|
+
|
|
|
static int mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd,
|
|
|
unsigned int cmdat)
|
|
|
{
|
|
@@ -305,8 +331,14 @@ static int mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd,
|
|
|
|
|
|
int_cntr = INT_END_CMD_RES_EN;
|
|
|
|
|
|
- if (mxcmci_use_dma(host))
|
|
|
- int_cntr |= INT_READ_OP_EN | INT_WRITE_OP_DONE_EN;
|
|
|
+ if (mxcmci_use_dma(host)) {
|
|
|
+ if (host->dma_dir == DMA_FROM_DEVICE) {
|
|
|
+ host->desc->callback = mxcmci_dma_callback;
|
|
|
+ host->desc->callback_param = host;
|
|
|
+ } else {
|
|
|
+ int_cntr |= INT_WRITE_OP_DONE_EN;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
|
if (host->use_sdio)
|
|
@@ -345,11 +377,9 @@ static int mxcmci_finish_data(struct mxcmci_host *host, unsigned int stat)
|
|
|
struct mmc_data *data = host->data;
|
|
|
int data_error;
|
|
|
|
|
|
- if (mxcmci_use_dma(host)) {
|
|
|
- dmaengine_terminate_all(host->dma);
|
|
|
+ if (mxcmci_use_dma(host))
|
|
|
dma_unmap_sg(host->dma->device->dev, data->sg, data->sg_len,
|
|
|
host->dma_dir);
|
|
|
- }
|
|
|
|
|
|
if (stat & STATUS_ERR_MASK) {
|
|
|
dev_dbg(mmc_dev(host->mmc), "request failed. status: 0x%08x\n",
|
|
@@ -624,8 +654,10 @@ static irqreturn_t mxcmci_irq(int irq, void *devid)
|
|
|
mxcmci_cmd_done(host, stat);
|
|
|
|
|
|
if (mxcmci_use_dma(host) &&
|
|
|
- (stat & (STATUS_DATA_TRANS_DONE | STATUS_WRITE_OP_DONE)))
|
|
|
+ (stat & (STATUS_DATA_TRANS_DONE | STATUS_WRITE_OP_DONE))) {
|
|
|
+ del_timer(&host->watchdog);
|
|
|
mxcmci_data_done(host, stat);
|
|
|
+ }
|
|
|
|
|
|
if (host->default_irq_mask &&
|
|
|
(stat & (STATUS_CARD_INSERTION | STATUS_CARD_REMOVAL)))
|
|
@@ -836,6 +868,34 @@ static bool filter(struct dma_chan *chan, void *param)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+static void mxcmci_watchdog(unsigned long data)
|
|
|
+{
|
|
|
+ struct mmc_host *mmc = (struct mmc_host *)data;
|
|
|
+ struct mxcmci_host *host = mmc_priv(mmc);
|
|
|
+ struct mmc_request *req = host->req;
|
|
|
+ unsigned int stat = readl(host->base + MMC_REG_STATUS);
|
|
|
+
|
|
|
+ if (host->dma_dir == DMA_FROM_DEVICE) {
|
|
|
+ dmaengine_terminate_all(host->dma);
|
|
|
+ dev_err(mmc_dev(host->mmc),
|
|
|
+ "%s: read time out (status = 0x%08x)\n",
|
|
|
+ __func__, stat);
|
|
|
+ } else {
|
|
|
+ dev_err(mmc_dev(host->mmc),
|
|
|
+ "%s: write time out (status = 0x%08x)\n",
|
|
|
+ __func__, stat);
|
|
|
+ mxcmci_softreset(host);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Mark transfer as erroneus and inform the upper layers */
|
|
|
+
|
|
|
+ host->data->error = -ETIMEDOUT;
|
|
|
+ host->req = NULL;
|
|
|
+ host->cmd = NULL;
|
|
|
+ host->data = NULL;
|
|
|
+ mmc_request_done(host->mmc, req);
|
|
|
+}
|
|
|
+
|
|
|
static const struct mmc_host_ops mxcmci_ops = {
|
|
|
.request = mxcmci_request,
|
|
|
.set_ios = mxcmci_set_ios,
|
|
@@ -968,6 +1028,10 @@ static int mxcmci_probe(struct platform_device *pdev)
|
|
|
|
|
|
mmc_add_host(mmc);
|
|
|
|
|
|
+ init_timer(&host->watchdog);
|
|
|
+ host->watchdog.function = &mxcmci_watchdog;
|
|
|
+ host->watchdog.data = (unsigned long)mmc;
|
|
|
+
|
|
|
return 0;
|
|
|
|
|
|
out_free_irq:
|