diff options
Diffstat (limited to 'drivers/mmc/host/mxcmmc.c')
| -rw-r--r-- | drivers/mmc/host/mxcmmc.c | 702 |
1 files changed, 478 insertions, 224 deletions
diff --git a/drivers/mmc/host/mxcmmc.c b/drivers/mmc/host/mxcmmc.c index bdd2cbb87cb..ed1cb93c378 100644 --- a/drivers/mmc/host/mxcmmc.c +++ b/drivers/mmc/host/mxcmmc.c @@ -31,18 +31,23 @@ #include <linux/clk.h> #include <linux/io.h> #include <linux/gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/dmaengine.h> +#include <linux/types.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_dma.h> +#include <linux/of_gpio.h> +#include <linux/mmc/slot-gpio.h> #include <asm/dma.h> #include <asm/irq.h> -#include <asm/sizes.h> -#include <mach/mmc.h> +#include <linux/platform_data/mmc-mxcmmc.h> -#ifdef CONFIG_ARCH_MX2 -#include <mach/dma-mx1-mx2.h> -#define HAS_DMA -#endif +#include <linux/platform_data/dma-imx.h> #define DRIVER_NAME "mxc-mmc" +#define MXCMCI_TIMEOUT_MS 10000 #define MMC_REG_STR_STP_CLK 0x00 #define MMC_REG_STATUS 0x04 @@ -111,13 +116,19 @@ #define INT_WRITE_OP_DONE_EN (1 << 1) #define INT_READ_OP_EN (1 << 0) +enum mxcmci_type { + IMX21_MMC, + IMX31_MMC, + MPC512X_MMC, +}; + struct mxcmci_host { struct mmc_host *mmc; - struct resource *res; void __iomem *base; - int irq; + dma_addr_t phys_base; int detect_irq; - int dma; + struct dma_chan *dma; + struct dma_async_tx_descriptor *desc; int do_dma; int default_irq_mask; int use_sdio; @@ -128,23 +139,120 @@ struct mxcmci_host { struct mmc_command *cmd; struct mmc_data *data; - unsigned int dma_nents; unsigned int datasize; unsigned int dma_dir; u16 rev_no; unsigned int cmdat; - struct clk *clk; + struct clk *clk_ipg; + struct clk *clk_per; int clock; struct work_struct datawork; spinlock_t lock; + + int burstlen; + int dmareq; + struct dma_slave_config dma_slave_config; + struct imx_dma_data dma_data; + + struct timer_list watchdog; + enum mxcmci_type devtype; +}; + +static const struct platform_device_id mxcmci_devtype[] = { + { + .name = "imx21-mmc", + .driver_data = IMX21_MMC, + }, { + .name = "imx31-mmc", + .driver_data = IMX31_MMC, + }, { + .name = "mpc512x-sdhc", + .driver_data = MPC512X_MMC, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, mxcmci_devtype); + +static const struct of_device_id mxcmci_of_match[] = { + { + .compatible = "fsl,imx21-mmc", + .data = &mxcmci_devtype[IMX21_MMC], + }, { + .compatible = "fsl,imx31-mmc", + .data = &mxcmci_devtype[IMX31_MMC], + }, { + .compatible = "fsl,mpc5121-sdhc", + .data = &mxcmci_devtype[MPC512X_MMC], + }, { + /* sentinel */ + } }; +MODULE_DEVICE_TABLE(of, mxcmci_of_match); + +static inline int is_imx31_mmc(struct mxcmci_host *host) +{ + return host->devtype == IMX31_MMC; +} + +static inline int is_mpc512x_mmc(struct mxcmci_host *host) +{ + return host->devtype == MPC512X_MMC; +} + +static inline u32 mxcmci_readl(struct mxcmci_host *host, int reg) +{ + if (IS_ENABLED(CONFIG_PPC_MPC512x)) + return ioread32be(host->base + reg); + else + return readl(host->base + reg); +} + +static inline void mxcmci_writel(struct mxcmci_host *host, u32 val, int reg) +{ + if (IS_ENABLED(CONFIG_PPC_MPC512x)) + iowrite32be(val, host->base + reg); + else + writel(val, host->base + reg); +} + +static inline u16 mxcmci_readw(struct mxcmci_host *host, int reg) +{ + if (IS_ENABLED(CONFIG_PPC_MPC512x)) + return ioread32be(host->base + reg); + else + return readw(host->base + reg); +} + +static inline void mxcmci_writew(struct mxcmci_host *host, u16 val, int reg) +{ + if (IS_ENABLED(CONFIG_PPC_MPC512x)) + iowrite32be(val, host->base + reg); + else + writew(val, host->base + reg); +} static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios); +static void mxcmci_set_power(struct mxcmci_host *host, unsigned int vdd) +{ + if (!IS_ERR(host->mmc->supply.vmmc)) { + if (host->power_mode == MMC_POWER_UP) + mmc_regulator_set_ocr(host->mmc, + host->mmc->supply.vmmc, vdd); + else if (host->power_mode == MMC_POWER_OFF) + mmc_regulator_set_ocr(host->mmc, + host->mmc->supply.vmmc, 0); + } + + if (host->pdata && host->pdata->setpower) + host->pdata->setpower(mmc_dev(host->mmc), vdd); +} + static inline int mxcmci_use_dma(struct mxcmci_host *host) { return host->do_dma; @@ -157,39 +265,63 @@ static void mxcmci_softreset(struct mxcmci_host *host) dev_dbg(mmc_dev(host->mmc), "mxcmci_softreset\n"); /* reset sequence */ - writew(STR_STP_CLK_RESET, host->base + MMC_REG_STR_STP_CLK); - writew(STR_STP_CLK_RESET | STR_STP_CLK_START_CLK, - host->base + MMC_REG_STR_STP_CLK); + mxcmci_writew(host, STR_STP_CLK_RESET, MMC_REG_STR_STP_CLK); + mxcmci_writew(host, STR_STP_CLK_RESET | STR_STP_CLK_START_CLK, + MMC_REG_STR_STP_CLK); for (i = 0; i < 8; i++) - writew(STR_STP_CLK_START_CLK, host->base + MMC_REG_STR_STP_CLK); + mxcmci_writew(host, STR_STP_CLK_START_CLK, MMC_REG_STR_STP_CLK); + + mxcmci_writew(host, 0xff, MMC_REG_RES_TO); +} + +#if IS_ENABLED(CONFIG_PPC_MPC512x) +static inline void buffer_swap32(u32 *buf, int len) +{ + int i; + + for (i = 0; i < ((len + 3) / 4); i++) { + st_le32(buf, *buf); + buf++; + } +} + +static void mxcmci_swap_buffers(struct mmc_data *data) +{ + struct scatterlist *sg; + int i; - writew(0xff, host->base + MMC_REG_RES_TO); + for_each_sg(data->sg, sg, data->sg_len, i) + buffer_swap32(sg_virt(sg), sg->length); } +#else +static inline void mxcmci_swap_buffers(struct mmc_data *data) {} +#endif static int mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data) { unsigned int nob = data->blocks; unsigned int blksz = data->blksz; unsigned int datasize = nob * blksz; -#ifdef HAS_DMA struct scatterlist *sg; - int i; - int ret; -#endif + enum dma_transfer_direction slave_dirn; + int i, nents; + if (data->flags & MMC_DATA_STREAM) nob = 0xffff; host->data = data; data->bytes_xfered = 0; - writew(nob, host->base + MMC_REG_NOB); - writew(blksz, host->base + MMC_REG_BLK_LEN); + mxcmci_writew(host, nob, MMC_REG_NOB); + mxcmci_writew(host, blksz, MMC_REG_BLK_LEN); host->datasize = datasize; -#ifdef HAS_DMA + if (!mxcmci_use_dma(host)) + return 0; + for_each_sg(data->sg, sg, data->sg_len, i) { - if (sg->offset & 3 || sg->length & 3) { + if (sg->offset & 3 || sg->length & 3 || sg->length < 512) { host->do_dma = 0; return 0; } @@ -197,35 +329,60 @@ static int mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data) if (data->flags & MMC_DATA_READ) { host->dma_dir = DMA_FROM_DEVICE; - host->dma_nents = dma_map_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, host->dma_dir); - - ret = imx_dma_setup_sg(host->dma, data->sg, host->dma_nents, - datasize, - host->res->start + MMC_REG_BUFFER_ACCESS, - DMA_MODE_READ); + slave_dirn = DMA_DEV_TO_MEM; } else { host->dma_dir = DMA_TO_DEVICE; - host->dma_nents = dma_map_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, host->dma_dir); + slave_dirn = DMA_MEM_TO_DEV; - ret = imx_dma_setup_sg(host->dma, data->sg, host->dma_nents, - datasize, - host->res->start + MMC_REG_BUFFER_ACCESS, - DMA_MODE_WRITE); + mxcmci_swap_buffers(data); } - if (ret) { - dev_err(mmc_dev(host->mmc), "failed to setup DMA : %d\n", ret); - return ret; + nents = dma_map_sg(host->dma->device->dev, data->sg, + data->sg_len, host->dma_dir); + if (nents != data->sg_len) + return -EINVAL; + + host->desc = dmaengine_prep_slave_sg(host->dma, + data->sg, data->sg_len, slave_dirn, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (!host->desc) { + dma_unmap_sg(host->dma->device->dev, data->sg, data->sg_len, + host->dma_dir); + host->do_dma = 0; + return 0; /* Fall back to PIO */ } wmb(); - imx_dma_enable(host->dma); -#endif /* HAS_DMA */ + 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 = mxcmci_readl(host, MMC_REG_STATUS); + mxcmci_writel(host, stat & ~STATUS_DATA_TRANS_DONE, MMC_REG_STATUS); + + dev_dbg(mmc_dev(host->mmc), "%s: 0x%08x\n", __func__, stat); + + if (stat & STATUS_READ_OP_DONE) + mxcmci_writel(host, STATUS_READ_OP_DONE, MMC_REG_STATUS); + + mxcmci_data_done(host, stat); +} + static int mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd, unsigned int cmdat) { @@ -257,18 +414,24 @@ 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) int_cntr |= INT_SDIO_IRQ_EN; - writel(int_cntr, host->base + MMC_REG_INT_CNTR); + mxcmci_writel(host, int_cntr, MMC_REG_INT_CNTR); spin_unlock_irqrestore(&host->lock, flags); - writew(cmd->opcode, host->base + MMC_REG_CMD); - writel(cmd->arg, host->base + MMC_REG_ARG); - writew(cmdat, host->base + MMC_REG_CMD_DAT_CONT); + mxcmci_writew(host, cmd->opcode, MMC_REG_CMD); + mxcmci_writel(host, cmd->arg, MMC_REG_ARG); + mxcmci_writew(host, cmdat, MMC_REG_CMD_DAT_CONT); return 0; } @@ -282,7 +445,7 @@ static void mxcmci_finish_request(struct mxcmci_host *host, spin_lock_irqsave(&host->lock, flags); if (host->use_sdio) int_cntr |= INT_SDIO_IRQ_EN; - writel(int_cntr, host->base + MMC_REG_INT_CNTR); + mxcmci_writel(host, int_cntr, MMC_REG_INT_CNTR); spin_unlock_irqrestore(&host->lock, flags); host->req = NULL; @@ -297,13 +460,11 @@ static int mxcmci_finish_data(struct mxcmci_host *host, unsigned int stat) struct mmc_data *data = host->data; int data_error; -#ifdef HAS_DMA if (mxcmci_use_dma(host)) { - imx_dma_disable(host->dma); - dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_nents, + dma_unmap_sg(host->dma->device->dev, data->sg, data->sg_len, host->dma_dir); + mxcmci_swap_buffers(data); } -#endif if (stat & STATUS_ERR_MASK) { dev_dbg(mmc_dev(host->mmc), "request failed. status: 0x%08x\n", @@ -361,14 +522,14 @@ static void mxcmci_read_response(struct mxcmci_host *host, unsigned int stat) if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { for (i = 0; i < 4; i++) { - a = readw(host->base + MMC_REG_RES_FIFO); - b = readw(host->base + MMC_REG_RES_FIFO); + a = mxcmci_readw(host, MMC_REG_RES_FIFO); + b = mxcmci_readw(host, MMC_REG_RES_FIFO); cmd->resp[i] = a << 16 | b; } } else { - a = readw(host->base + MMC_REG_RES_FIFO); - b = readw(host->base + MMC_REG_RES_FIFO); - c = readw(host->base + MMC_REG_RES_FIFO); + a = mxcmci_readw(host, MMC_REG_RES_FIFO); + b = mxcmci_readw(host, MMC_REG_RES_FIFO); + c = mxcmci_readw(host, MMC_REG_RES_FIFO); cmd->resp[0] = a << 24 | b << 8 | c >> 8; } } @@ -380,7 +541,7 @@ static int mxcmci_poll_status(struct mxcmci_host *host, u32 mask) unsigned long timeout = jiffies + HZ; do { - stat = readl(host->base + MMC_REG_STATUS); + stat = mxcmci_readl(host, MMC_REG_STATUS); if (stat & STATUS_ERR_MASK) return stat; if (time_after(jiffies, timeout)) { @@ -404,7 +565,7 @@ static int mxcmci_pull(struct mxcmci_host *host, void *_buf, int bytes) STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE); if (stat) return stat; - *buf++ = readl(host->base + MMC_REG_BUFFER_ACCESS); + *buf++ = cpu_to_le32(mxcmci_readl(host, MMC_REG_BUFFER_ACCESS)); bytes -= 4; } @@ -416,7 +577,7 @@ static int mxcmci_pull(struct mxcmci_host *host, void *_buf, int bytes) STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE); if (stat) return stat; - tmp = readl(host->base + MMC_REG_BUFFER_ACCESS); + tmp = cpu_to_le32(mxcmci_readl(host, MMC_REG_BUFFER_ACCESS)); memcpy(b, &tmp, bytes); } @@ -432,7 +593,7 @@ static int mxcmci_push(struct mxcmci_host *host, void *_buf, int bytes) stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY); if (stat) return stat; - writel(*buf++, host->base + MMC_REG_BUFFER_ACCESS); + mxcmci_writel(host, cpu_to_le32(*buf++), MMC_REG_BUFFER_ACCESS); bytes -= 4; } @@ -445,7 +606,7 @@ static int mxcmci_push(struct mxcmci_host *host, void *_buf, int bytes) return stat; memcpy(&tmp, b, bytes); - writel(tmp, host->base + MMC_REG_BUFFER_ACCESS); + mxcmci_writel(host, cpu_to_le32(tmp), MMC_REG_BUFFER_ACCESS); } stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY); @@ -491,8 +652,8 @@ static void mxcmci_datawork(struct work_struct *work) datawork); int datastat = mxcmci_transfer_data(host); - writel(STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE, - host->base + MMC_REG_STATUS); + mxcmci_writel(host, STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE, + MMC_REG_STATUS); mxcmci_finish_data(host, datastat); if (host->req->stop) { @@ -505,30 +666,44 @@ static void mxcmci_datawork(struct work_struct *work) } } -#ifdef HAS_DMA static void mxcmci_data_done(struct mxcmci_host *host, unsigned int stat) { - struct mmc_data *data = host->data; + struct mmc_request *req; int data_error; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (!host->data) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } - if (!data) + if (!host->req) { + spin_unlock_irqrestore(&host->lock, flags); return; + } + + req = host->req; + if (!req->stop) + host->req = NULL; /* we will handle finish req below */ data_error = mxcmci_finish_data(host, stat); + spin_unlock_irqrestore(&host->lock, flags); + mxcmci_read_response(host, stat); host->cmd = NULL; - if (host->req->stop) { - if (mxcmci_start_cmd(host, host->req->stop, 0)) { - mxcmci_finish_request(host, host->req); + if (req->stop) { + if (mxcmci_start_cmd(host, req->stop, 0)) { + mxcmci_finish_request(host, req); return; } } else { - mxcmci_finish_request(host, host->req); + mxcmci_finish_request(host, req); } } -#endif /* HAS_DMA */ static void mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat) { @@ -556,9 +731,11 @@ static irqreturn_t mxcmci_irq(int irq, void *devid) bool sdio_irq; u32 stat; - stat = readl(host->base + MMC_REG_STATUS); - writel(stat & ~(STATUS_SDIO_INT_ACTIVE | STATUS_DATA_TRANS_DONE | - STATUS_WRITE_OP_DONE), host->base + MMC_REG_STATUS); + stat = mxcmci_readl(host, MMC_REG_STATUS); + mxcmci_writel(host, + stat & ~(STATUS_SDIO_INT_ACTIVE | STATUS_DATA_TRANS_DONE | + STATUS_WRITE_OP_DONE), + MMC_REG_STATUS); dev_dbg(mmc_dev(host->mmc), "%s: 0x%08x\n", __func__, stat); @@ -566,29 +743,29 @@ static irqreturn_t mxcmci_irq(int irq, void *devid) sdio_irq = (stat & STATUS_SDIO_INT_ACTIVE) && host->use_sdio; spin_unlock_irqrestore(&host->lock, flags); -#ifdef HAS_DMA if (mxcmci_use_dma(host) && (stat & (STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE))) - writel(STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE, - host->base + MMC_REG_STATUS); -#endif + mxcmci_writel(host, STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE, + MMC_REG_STATUS); if (sdio_irq) { - writel(STATUS_SDIO_INT_ACTIVE, host->base + MMC_REG_STATUS); + mxcmci_writel(host, STATUS_SDIO_INT_ACTIVE, MMC_REG_STATUS); mmc_signal_sdio_irq(host->mmc); } if (stat & STATUS_END_CMD_RESP) mxcmci_cmd_done(host, stat); -#ifdef HAS_DMA 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); -#endif + } + if (host->default_irq_mask && (stat & (STATUS_CARD_INSERTION | STATUS_CARD_REMOVAL))) mmc_detect_change(host->mmc, msecs_to_jiffies(200)); + return IRQ_HANDLED; } @@ -602,9 +779,10 @@ static void mxcmci_request(struct mmc_host *mmc, struct mmc_request *req) host->req = req; host->cmdat &= ~CMD_DAT_CONT_INIT; -#ifdef HAS_DMA - host->do_dma = 1; -#endif + + if (host->dma) + host->do_dma = 1; + if (req->data) { error = mxcmci_setup_data(host, req->data); if (error) { @@ -620,6 +798,7 @@ static void mxcmci_request(struct mmc_host *mmc, struct mmc_request *req) } error = mxcmci_start_cmd(host, req->cmd, cmdat); + out: if (error) mxcmci_finish_request(host, req); @@ -629,7 +808,7 @@ static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios) { unsigned int divider; int prescaler = 0; - unsigned int clk_in = clk_get_rate(host->clk); + unsigned int clk_in = clk_get_rate(host->clk_per); while (prescaler <= 0x800) { for (divider = 1; divider <= 0xF; divider++) { @@ -652,46 +831,72 @@ static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios) prescaler <<= 1; } - writew((prescaler << 4) | divider, host->base + MMC_REG_CLK_RATE); + mxcmci_writew(host, (prescaler << 4) | divider, MMC_REG_CLK_RATE); dev_dbg(mmc_dev(host->mmc), "scaler: %d divider: %d in: %d out: %d\n", prescaler, divider, clk_in, clk_ios); } +static int mxcmci_setup_dma(struct mmc_host *mmc) +{ + struct mxcmci_host *host = mmc_priv(mmc); + struct dma_slave_config *config = &host->dma_slave_config; + + config->dst_addr = host->phys_base + MMC_REG_BUFFER_ACCESS; + config->src_addr = host->phys_base + MMC_REG_BUFFER_ACCESS; + config->dst_addr_width = 4; + config->src_addr_width = 4; + config->dst_maxburst = host->burstlen; + config->src_maxburst = host->burstlen; + config->device_fc = false; + + return dmaengine_slave_config(host->dma, config); +} + static void mxcmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct mxcmci_host *host = mmc_priv(mmc); -#ifdef HAS_DMA - unsigned int blen; + int burstlen, ret; + /* - * use burstlen of 64 in 4 bit mode (--> reg value 0) - * use burstlen of 16 in 1 bit mode (--> reg value 16) + * use burstlen of 64 (16 words) in 4 bit mode (--> reg value 0) + * use burstlen of 16 (4 words) in 1 bit mode (--> reg value 16) */ if (ios->bus_width == MMC_BUS_WIDTH_4) - blen = 0; + burstlen = 16; else - blen = 16; + burstlen = 4; + + if (mxcmci_use_dma(host) && burstlen != host->burstlen) { + host->burstlen = burstlen; + ret = mxcmci_setup_dma(mmc); + if (ret) { + dev_err(mmc_dev(host->mmc), + "failed to config DMA channel. Falling back to PIO\n"); + dma_release_channel(host->dma); + host->do_dma = 0; + host->dma = NULL; + } + } - imx_dma_config_burstlen(host->dma, blen); -#endif if (ios->bus_width == MMC_BUS_WIDTH_4) host->cmdat |= CMD_DAT_CONT_BUS_WIDTH_4; else host->cmdat &= ~CMD_DAT_CONT_BUS_WIDTH_4; if (host->power_mode != ios->power_mode) { - if (host->pdata && host->pdata->setpower) - host->pdata->setpower(mmc_dev(mmc), ios->vdd); host->power_mode = ios->power_mode; + mxcmci_set_power(host, ios->vdd); + if (ios->power_mode == MMC_POWER_ON) host->cmdat |= CMD_DAT_CONT_INIT; } if (ios->clock) { mxcmci_set_clk_rate(host, ios->clock); - writew(STR_STP_CLK_START_CLK, host->base + MMC_REG_STR_STP_CLK); + mxcmci_writew(host, STR_STP_CLK_START_CLK, MMC_REG_STR_STP_CLK); } else { - writew(STR_STP_CLK_STOP_CLK, host->base + MMC_REG_STR_STP_CLK); + mxcmci_writew(host, STR_STP_CLK_STOP_CLK, MMC_REG_STR_STP_CLK); } host->clock = ios->clock; @@ -714,10 +919,11 @@ static int mxcmci_get_ro(struct mmc_host *mmc) if (host->pdata && host->pdata->get_ro) return !!host->pdata->get_ro(mmc_dev(mmc)); /* - * Board doesn't support read only detection; let the mmc core - * decide what to do. + * If board doesn't support read only detection (no mmc_gpio + * context or gpio is invalid), then let the mmc core decide + * what to do. */ - return -ENOSYS; + return mmc_gpio_get_ro(mmc); } static void mxcmci_enable_sdio_irq(struct mmc_host *mmc, int enable) @@ -728,19 +934,21 @@ static void mxcmci_enable_sdio_irq(struct mmc_host *mmc, int enable) spin_lock_irqsave(&host->lock, flags); host->use_sdio = enable; - int_cntr = readl(host->base + MMC_REG_INT_CNTR); + int_cntr = mxcmci_readl(host, MMC_REG_INT_CNTR); if (enable) int_cntr |= INT_SDIO_IRQ_EN; else int_cntr &= ~INT_SDIO_IRQ_EN; - writel(int_cntr, host->base + MMC_REG_INT_CNTR); + mxcmci_writel(host, int_cntr, MMC_REG_INT_CNTR); spin_unlock_irqrestore(&host->lock, flags); } static void mxcmci_init_card(struct mmc_host *host, struct mmc_card *card) { + struct mxcmci_host *mxcmci = mmc_priv(host); + /* * MX3 SoCs have a silicon bug which corrupts CRC calculation of * multi-block transfers when connected SDIO peripheral doesn't @@ -748,12 +956,53 @@ static void mxcmci_init_card(struct mmc_host *host, struct mmc_card *card) * One way to prevent this is to only allow 1-bit transfers. */ - if (cpu_is_mx3() && card->type == MMC_TYPE_SDIO) + if (is_imx31_mmc(mxcmci) && card->type == MMC_TYPE_SDIO) host->caps &= ~MMC_CAP_4_BIT_DATA; else host->caps |= MMC_CAP_4_BIT_DATA; } +static bool filter(struct dma_chan *chan, void *param) +{ + struct mxcmci_host *host = param; + + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = &host->dma_data; + + 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 = mxcmci_readl(host, 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 */ + + if (host->data) + 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, @@ -765,72 +1014,108 @@ static const struct mmc_host_ops mxcmci_ops = { static int mxcmci_probe(struct platform_device *pdev) { struct mmc_host *mmc; - struct mxcmci_host *host = NULL; - struct resource *iores, *r; + struct mxcmci_host *host; + struct resource *res; int ret = 0, irq; + bool dat3_card_detect = false; + dma_cap_mask_t mask; + const struct of_device_id *of_id; + struct imxmmc_platform_data *pdata = pdev->dev.platform_data; + + pr_info("i.MX/MPC512x SDHC driver\n"); - printk(KERN_INFO "i.MX SDHC driver\n"); + of_id = of_match_device(mxcmci_of_match, &pdev->dev); - iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); - if (!iores || irq < 0) + if (irq < 0) return -EINVAL; - r = request_mem_region(iores->start, resource_size(iores), pdev->name); - if (!r) - return -EBUSY; + mmc = mmc_alloc_host(sizeof(*host), &pdev->dev); + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); - mmc = mmc_alloc_host(sizeof(struct mxcmci_host), &pdev->dev); - if (!mmc) { - ret = -ENOMEM; - goto out_release_mem; + host->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->base)) { + ret = PTR_ERR(host->base); + goto out_free; } + host->phys_base = res->start; + + ret = mmc_of_parse(mmc); + if (ret) + goto out_free; mmc->ops = &mxcmci_ops; - mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ; + + /* For devicetree parsing, the bus width is read from devicetree */ + if (pdata) + mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ; + else + mmc->caps |= MMC_CAP_SDIO_IRQ; /* MMC core transfer sizes tunable parameters */ - mmc->max_segs = 64; mmc->max_blk_size = 2048; mmc->max_blk_count = 65535; mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; mmc->max_seg_size = mmc->max_req_size; - host = mmc_priv(mmc); - host->base = ioremap(r->start, resource_size(r)); - if (!host->base) { - ret = -ENOMEM; - goto out_free; + if (of_id) { + const struct platform_device_id *id_entry = of_id->data; + host->devtype = id_entry->driver_data; + } else { + host->devtype = pdev->id_entry->driver_data; } + /* adjust max_segs after devtype detection */ + if (!is_mpc512x_mmc(host)) + mmc->max_segs = 64; + host->mmc = mmc; - host->pdata = pdev->dev.platform_data; + host->pdata = pdata; spin_lock_init(&host->lock); - if (host->pdata && host->pdata->ocr_avail) - mmc->ocr_avail = host->pdata->ocr_avail; - else - mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + if (pdata) + dat3_card_detect = pdata->dat3_card_detect; + else if (!(mmc->caps & MMC_CAP_NONREMOVABLE) + && !of_property_read_bool(pdev->dev.of_node, "cd-gpios")) + dat3_card_detect = true; - if (host->pdata && host->pdata->dat3_card_detect) + ret = mmc_regulator_get_supply(mmc); + if (ret) { + if (pdata && ret != -EPROBE_DEFER) + mmc->ocr_avail = pdata->ocr_avail ? : + MMC_VDD_32_33 | MMC_VDD_33_34; + else + goto out_free; + } + + if (dat3_card_detect) host->default_irq_mask = INT_CARD_INSERTION_EN | INT_CARD_REMOVAL_EN; else host->default_irq_mask = 0; - host->res = r; - host->irq = irq; + host->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(host->clk_ipg)) { + ret = PTR_ERR(host->clk_ipg); + goto out_free; + } - host->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(host->clk)) { - ret = PTR_ERR(host->clk); - goto out_iounmap; + host->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(host->clk_per)) { + ret = PTR_ERR(host->clk_per); + goto out_free; } - clk_enable(host->clk); + + clk_prepare_enable(host->clk_per); + clk_prepare_enable(host->clk_ipg); mxcmci_softreset(host); - host->rev_no = readw(host->base + MMC_REG_REV_NO); + host->rev_no = mxcmci_readw(host, MMC_REG_REV_NO); if (host->rev_no != 0x400) { ret = -ENODEV; dev_err(mmc_dev(host->mmc), "wrong rev.no. 0x%08x. aborting.\n", @@ -838,40 +1123,38 @@ static int mxcmci_probe(struct platform_device *pdev) goto out_clk_put; } - mmc->f_min = clk_get_rate(host->clk) >> 16; - mmc->f_max = clk_get_rate(host->clk) >> 1; + mmc->f_min = clk_get_rate(host->clk_per) >> 16; + mmc->f_max = clk_get_rate(host->clk_per) >> 1; /* recommended in data sheet */ - writew(0x2db4, host->base + MMC_REG_READ_TO); - - writel(host->default_irq_mask, host->base + MMC_REG_INT_CNTR); + mxcmci_writew(host, 0x2db4, MMC_REG_READ_TO); -#ifdef HAS_DMA - host->dma = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_LOW); - if (host->dma < 0) { - dev_err(mmc_dev(host->mmc), "imx_dma_request_by_prio failed\n"); - ret = -EBUSY; - goto out_clk_put; - } + mxcmci_writel(host, host->default_irq_mask, MMC_REG_INT_CNTR); - r = platform_get_resource(pdev, IORESOURCE_DMA, 0); - if (!r) { - ret = -EINVAL; - goto out_free_dma; + if (!host->pdata) { + host->dma = dma_request_slave_channel(&pdev->dev, "rx-tx"); + } else { + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (res) { + host->dmareq = res->start; + host->dma_data.peripheral_type = IMX_DMATYPE_SDHC; + host->dma_data.priority = DMA_PRIO_LOW; + host->dma_data.dma_request = host->dmareq; + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + host->dma = dma_request_channel(mask, filter, host); + } } + if (host->dma) + mmc->max_seg_size = dma_get_max_seg_size( + host->dma->device->dev); + else + dev_info(mmc_dev(host->mmc), "dma not available. Using PIO\n"); - ret = imx_dma_config_channel(host->dma, - IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_FIFO, - IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, - r->start, 0); - if (ret) { - dev_err(mmc_dev(host->mmc), "failed to config DMA channel\n"); - goto out_free_dma; - } -#endif INIT_WORK(&host->datawork, mxcmci_datawork); - ret = request_irq(host->irq, mxcmci_irq, 0, DRIVER_NAME, host); + ret = devm_request_irq(&pdev->dev, irq, mxcmci_irq, 0, + dev_name(&pdev->dev), host); if (ret) goto out_free_dma; @@ -881,28 +1164,28 @@ static int mxcmci_probe(struct platform_device *pdev) ret = host->pdata->init(&pdev->dev, mxcmci_detect_irq, host->mmc); if (ret) - goto out_free_irq; + goto out_free_dma; } + init_timer(&host->watchdog); + host->watchdog.function = &mxcmci_watchdog; + host->watchdog.data = (unsigned long)mmc; + mmc_add_host(mmc); return 0; -out_free_irq: - free_irq(host->irq, host); out_free_dma: -#ifdef HAS_DMA - imx_dma_free(host->dma); -#endif + if (host->dma) + dma_release_channel(host->dma); + out_clk_put: - clk_disable(host->clk); - clk_put(host->clk); -out_iounmap: - iounmap(host->base); + clk_disable_unprepare(host->clk_per); + clk_disable_unprepare(host->clk_ipg); + out_free: mmc_free_host(mmc); -out_release_mem: - release_mem_region(iores->start, resource_size(iores)); + return ret; } @@ -911,88 +1194,59 @@ static int mxcmci_remove(struct platform_device *pdev) struct mmc_host *mmc = platform_get_drvdata(pdev); struct mxcmci_host *host = mmc_priv(mmc); - platform_set_drvdata(pdev, NULL); - mmc_remove_host(mmc); if (host->pdata && host->pdata->exit) host->pdata->exit(&pdev->dev, mmc); - free_irq(host->irq, host); - iounmap(host->base); -#ifdef HAS_DMA - imx_dma_free(host->dma); -#endif - clk_disable(host->clk); - clk_put(host->clk); + if (host->dma) + dma_release_channel(host->dma); - release_mem_region(host->res->start, resource_size(host->res)); - release_resource(host->res); + clk_disable_unprepare(host->clk_per); + clk_disable_unprepare(host->clk_ipg); mmc_free_host(mmc); return 0; } -#ifdef CONFIG_PM -static int mxcmci_suspend(struct device *dev) +static int __maybe_unused mxcmci_suspend(struct device *dev) { struct mmc_host *mmc = dev_get_drvdata(dev); struct mxcmci_host *host = mmc_priv(mmc); - int ret = 0; - - if (mmc) - ret = mmc_suspend_host(mmc); - clk_disable(host->clk); - return ret; + clk_disable_unprepare(host->clk_per); + clk_disable_unprepare(host->clk_ipg); + return 0; } -static int mxcmci_resume(struct device *dev) +static int __maybe_unused mxcmci_resume(struct device *dev) { struct mmc_host *mmc = dev_get_drvdata(dev); struct mxcmci_host *host = mmc_priv(mmc); - int ret = 0; - - clk_enable(host->clk); - if (mmc) - ret = mmc_resume_host(mmc); - return ret; + clk_prepare_enable(host->clk_per); + clk_prepare_enable(host->clk_ipg); + return 0; } -static const struct dev_pm_ops mxcmci_pm_ops = { - .suspend = mxcmci_suspend, - .resume = mxcmci_resume, -}; -#endif +static SIMPLE_DEV_PM_OPS(mxcmci_pm_ops, mxcmci_suspend, mxcmci_resume); static struct platform_driver mxcmci_driver = { .probe = mxcmci_probe, .remove = mxcmci_remove, + .id_table = mxcmci_devtype, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, -#ifdef CONFIG_PM .pm = &mxcmci_pm_ops, -#endif + .of_match_table = mxcmci_of_match, } }; -static int __init mxcmci_init(void) -{ - return platform_driver_register(&mxcmci_driver); -} - -static void __exit mxcmci_exit(void) -{ - platform_driver_unregister(&mxcmci_driver); -} - -module_init(mxcmci_init); -module_exit(mxcmci_exit); +module_platform_driver(mxcmci_driver); MODULE_DESCRIPTION("i.MX Multimedia Card Interface Driver"); MODULE_AUTHOR("Sascha Hauer, Pengutronix"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:imx-mmc"); +MODULE_ALIAS("platform:mxc-mmc"); |
