mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 08:31:55 +00:00
mmc: mxs-mmc: add mmc host driver for i.MX23/28
This adds the mmc host driver for Freescale MXS-based SoC i.MX23/28. The driver calls into mxs-dma via generic dmaengine api for both pio and data transfer. Thanks Chris Ball for the indentation patch. Signed-off-by: Shawn Guo <shawn.guo@freescale.com> Reviewed-by: Arnd Bergmann <arnd@arndb.de> Tested-by: Wolfram Sang <w.sang@pengutronix.de> Signed-off-by: Chris Ball <cjb@laptop.org>
This commit is contained in:
parent
8154b5756d
commit
e4243f13d1
18
arch/arm/mach-mxs/include/mach/mmc.h
Normal file
18
arch/arm/mach-mxs/include/mach/mmc.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __MACH_MXS_MMC_H__
|
||||
#define __MACH_MXS_MMC_H__
|
||||
|
||||
struct mxs_mmc_platform_data {
|
||||
int wp_gpio; /* write protect pin */
|
||||
unsigned int flags;
|
||||
#define SLOTF_4_BIT_CAPABLE (1 << 0)
|
||||
#define SLOTF_8_BIT_CAPABLE (1 << 1)
|
||||
};
|
||||
#endif /* __MACH_MXS_MMC_H__ */
|
@ -319,6 +319,15 @@ config MMC_MXC
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_MXS
|
||||
tristate "Freescale MXS Multimedia Card Interface support"
|
||||
depends on ARCH_MXS && MXS_DMA
|
||||
help
|
||||
This selects the Freescale SSP MMC controller found on MXS based
|
||||
platforms like mx23/28.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config MMC_TIFM_SD
|
||||
tristate "TI Flash Media MMC/SD Interface support (EXPERIMENTAL)"
|
||||
depends on EXPERIMENTAL && PCI
|
||||
|
@ -6,6 +6,7 @@ obj-$(CONFIG_MMC_ARMMMCI) += mmci.o
|
||||
obj-$(CONFIG_MMC_PXA) += pxamci.o
|
||||
obj-$(CONFIG_MMC_IMX) += imxmmc.o
|
||||
obj-$(CONFIG_MMC_MXC) += mxcmmc.o
|
||||
obj-$(CONFIG_MMC_MXS) += mxs-mmc.o
|
||||
obj-$(CONFIG_MMC_SDHCI) += sdhci.o
|
||||
obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o
|
||||
obj-$(CONFIG_MMC_SDHCI_PXA) += sdhci-pxa.o
|
||||
|
874
drivers/mmc/host/mxs-mmc.c
Normal file
874
drivers/mmc/host/mxs-mmc.c
Normal file
@ -0,0 +1,874 @@
|
||||
/*
|
||||
* Portions copyright (C) 2003 Russell King, PXA MMCI Driver
|
||||
* Portions copyright (C) 2004-2005 Pierre Ossman, W83L51xD SD/MMC driver
|
||||
*
|
||||
* Copyright 2008 Embedded Alley Solutions, Inc.
|
||||
* Copyright 2009-2011 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
#include <linux/mmc/sdio.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <mach/mxs.h>
|
||||
#include <mach/common.h>
|
||||
#include <mach/dma.h>
|
||||
#include <mach/mmc.h>
|
||||
|
||||
#define DRIVER_NAME "mxs-mmc"
|
||||
|
||||
/* card detect polling timeout */
|
||||
#define MXS_MMC_DETECT_TIMEOUT (HZ/2)
|
||||
|
||||
#define SSP_VERSION_LATEST 4
|
||||
#define ssp_is_old() (host->version < SSP_VERSION_LATEST)
|
||||
|
||||
/* SSP registers */
|
||||
#define HW_SSP_CTRL0 0x000
|
||||
#define BM_SSP_CTRL0_RUN (1 << 29)
|
||||
#define BM_SSP_CTRL0_SDIO_IRQ_CHECK (1 << 28)
|
||||
#define BM_SSP_CTRL0_IGNORE_CRC (1 << 26)
|
||||
#define BM_SSP_CTRL0_READ (1 << 25)
|
||||
#define BM_SSP_CTRL0_DATA_XFER (1 << 24)
|
||||
#define BP_SSP_CTRL0_BUS_WIDTH (22)
|
||||
#define BM_SSP_CTRL0_BUS_WIDTH (0x3 << 22)
|
||||
#define BM_SSP_CTRL0_WAIT_FOR_IRQ (1 << 21)
|
||||
#define BM_SSP_CTRL0_LONG_RESP (1 << 19)
|
||||
#define BM_SSP_CTRL0_GET_RESP (1 << 17)
|
||||
#define BM_SSP_CTRL0_ENABLE (1 << 16)
|
||||
#define BP_SSP_CTRL0_XFER_COUNT (0)
|
||||
#define BM_SSP_CTRL0_XFER_COUNT (0xffff)
|
||||
#define HW_SSP_CMD0 0x010
|
||||
#define BM_SSP_CMD0_DBL_DATA_RATE_EN (1 << 25)
|
||||
#define BM_SSP_CMD0_SLOW_CLKING_EN (1 << 22)
|
||||
#define BM_SSP_CMD0_CONT_CLKING_EN (1 << 21)
|
||||
#define BM_SSP_CMD0_APPEND_8CYC (1 << 20)
|
||||
#define BP_SSP_CMD0_BLOCK_SIZE (16)
|
||||
#define BM_SSP_CMD0_BLOCK_SIZE (0xf << 16)
|
||||
#define BP_SSP_CMD0_BLOCK_COUNT (8)
|
||||
#define BM_SSP_CMD0_BLOCK_COUNT (0xff << 8)
|
||||
#define BP_SSP_CMD0_CMD (0)
|
||||
#define BM_SSP_CMD0_CMD (0xff)
|
||||
#define HW_SSP_CMD1 0x020
|
||||
#define HW_SSP_XFER_SIZE 0x030
|
||||
#define HW_SSP_BLOCK_SIZE 0x040
|
||||
#define BP_SSP_BLOCK_SIZE_BLOCK_COUNT (4)
|
||||
#define BM_SSP_BLOCK_SIZE_BLOCK_COUNT (0xffffff << 4)
|
||||
#define BP_SSP_BLOCK_SIZE_BLOCK_SIZE (0)
|
||||
#define BM_SSP_BLOCK_SIZE_BLOCK_SIZE (0xf)
|
||||
#define HW_SSP_TIMING (ssp_is_old() ? 0x050 : 0x070)
|
||||
#define BP_SSP_TIMING_TIMEOUT (16)
|
||||
#define BM_SSP_TIMING_TIMEOUT (0xffff << 16)
|
||||
#define BP_SSP_TIMING_CLOCK_DIVIDE (8)
|
||||
#define BM_SSP_TIMING_CLOCK_DIVIDE (0xff << 8)
|
||||
#define BP_SSP_TIMING_CLOCK_RATE (0)
|
||||
#define BM_SSP_TIMING_CLOCK_RATE (0xff)
|
||||
#define HW_SSP_CTRL1 (ssp_is_old() ? 0x060 : 0x080)
|
||||
#define BM_SSP_CTRL1_SDIO_IRQ (1 << 31)
|
||||
#define BM_SSP_CTRL1_SDIO_IRQ_EN (1 << 30)
|
||||
#define BM_SSP_CTRL1_RESP_ERR_IRQ (1 << 29)
|
||||
#define BM_SSP_CTRL1_RESP_ERR_IRQ_EN (1 << 28)
|
||||
#define BM_SSP_CTRL1_RESP_TIMEOUT_IRQ (1 << 27)
|
||||
#define BM_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN (1 << 26)
|
||||
#define BM_SSP_CTRL1_DATA_TIMEOUT_IRQ (1 << 25)
|
||||
#define BM_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN (1 << 24)
|
||||
#define BM_SSP_CTRL1_DATA_CRC_IRQ (1 << 23)
|
||||
#define BM_SSP_CTRL1_DATA_CRC_IRQ_EN (1 << 22)
|
||||
#define BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ (1 << 21)
|
||||
#define BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ_EN (1 << 20)
|
||||
#define BM_SSP_CTRL1_RECV_TIMEOUT_IRQ (1 << 17)
|
||||
#define BM_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN (1 << 16)
|
||||
#define BM_SSP_CTRL1_FIFO_OVERRUN_IRQ (1 << 15)
|
||||
#define BM_SSP_CTRL1_FIFO_OVERRUN_IRQ_EN (1 << 14)
|
||||
#define BM_SSP_CTRL1_DMA_ENABLE (1 << 13)
|
||||
#define BM_SSP_CTRL1_POLARITY (1 << 9)
|
||||
#define BP_SSP_CTRL1_WORD_LENGTH (4)
|
||||
#define BM_SSP_CTRL1_WORD_LENGTH (0xf << 4)
|
||||
#define BP_SSP_CTRL1_SSP_MODE (0)
|
||||
#define BM_SSP_CTRL1_SSP_MODE (0xf)
|
||||
#define HW_SSP_SDRESP0 (ssp_is_old() ? 0x080 : 0x0a0)
|
||||
#define HW_SSP_SDRESP1 (ssp_is_old() ? 0x090 : 0x0b0)
|
||||
#define HW_SSP_SDRESP2 (ssp_is_old() ? 0x0a0 : 0x0c0)
|
||||
#define HW_SSP_SDRESP3 (ssp_is_old() ? 0x0b0 : 0x0d0)
|
||||
#define HW_SSP_STATUS (ssp_is_old() ? 0x0c0 : 0x100)
|
||||
#define BM_SSP_STATUS_CARD_DETECT (1 << 28)
|
||||
#define BM_SSP_STATUS_SDIO_IRQ (1 << 17)
|
||||
#define HW_SSP_VERSION (cpu_is_mx23() ? 0x110 : 0x130)
|
||||
#define BP_SSP_VERSION_MAJOR (24)
|
||||
|
||||
#define BF_SSP(value, field) (((value) << BP_SSP_##field) & BM_SSP_##field)
|
||||
|
||||
#define MXS_MMC_IRQ_BITS (BM_SSP_CTRL1_SDIO_IRQ | \
|
||||
BM_SSP_CTRL1_RESP_ERR_IRQ | \
|
||||
BM_SSP_CTRL1_RESP_TIMEOUT_IRQ | \
|
||||
BM_SSP_CTRL1_DATA_TIMEOUT_IRQ | \
|
||||
BM_SSP_CTRL1_DATA_CRC_IRQ | \
|
||||
BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ | \
|
||||
BM_SSP_CTRL1_RECV_TIMEOUT_IRQ | \
|
||||
BM_SSP_CTRL1_FIFO_OVERRUN_IRQ)
|
||||
|
||||
#define SSP_PIO_NUM 3
|
||||
|
||||
struct mxs_mmc_host {
|
||||
struct mmc_host *mmc;
|
||||
struct mmc_request *mrq;
|
||||
struct mmc_command *cmd;
|
||||
struct mmc_data *data;
|
||||
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
struct resource *res;
|
||||
struct resource *dma_res;
|
||||
struct clk *clk;
|
||||
unsigned int clk_rate;
|
||||
|
||||
struct dma_chan *dmach;
|
||||
struct mxs_dma_data dma_data;
|
||||
unsigned int dma_dir;
|
||||
u32 ssp_pio_words[SSP_PIO_NUM];
|
||||
|
||||
unsigned int version;
|
||||
unsigned char bus_width;
|
||||
spinlock_t lock;
|
||||
int sdio_irq_en;
|
||||
};
|
||||
|
||||
static int mxs_mmc_get_ro(struct mmc_host *mmc)
|
||||
{
|
||||
struct mxs_mmc_host *host = mmc_priv(mmc);
|
||||
struct mxs_mmc_platform_data *pdata =
|
||||
mmc_dev(host->mmc)->platform_data;
|
||||
|
||||
if (!pdata)
|
||||
return -EFAULT;
|
||||
|
||||
if (!gpio_is_valid(pdata->wp_gpio))
|
||||
return -EINVAL;
|
||||
|
||||
return gpio_get_value(pdata->wp_gpio);
|
||||
}
|
||||
|
||||
static int mxs_mmc_get_cd(struct mmc_host *mmc)
|
||||
{
|
||||
struct mxs_mmc_host *host = mmc_priv(mmc);
|
||||
|
||||
return !(readl(host->base + HW_SSP_STATUS) &
|
||||
BM_SSP_STATUS_CARD_DETECT);
|
||||
}
|
||||
|
||||
static void mxs_mmc_reset(struct mxs_mmc_host *host)
|
||||
{
|
||||
u32 ctrl0, ctrl1;
|
||||
|
||||
mxs_reset_block(host->base);
|
||||
|
||||
ctrl0 = BM_SSP_CTRL0_IGNORE_CRC;
|
||||
ctrl1 = BF_SSP(0x3, CTRL1_SSP_MODE) |
|
||||
BF_SSP(0x7, CTRL1_WORD_LENGTH) |
|
||||
BM_SSP_CTRL1_DMA_ENABLE |
|
||||
BM_SSP_CTRL1_POLARITY |
|
||||
BM_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN |
|
||||
BM_SSP_CTRL1_DATA_CRC_IRQ_EN |
|
||||
BM_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN |
|
||||
BM_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN |
|
||||
BM_SSP_CTRL1_RESP_ERR_IRQ_EN;
|
||||
|
||||
writel(BF_SSP(0xffff, TIMING_TIMEOUT) |
|
||||
BF_SSP(2, TIMING_CLOCK_DIVIDE) |
|
||||
BF_SSP(0, TIMING_CLOCK_RATE),
|
||||
host->base + HW_SSP_TIMING);
|
||||
|
||||
if (host->sdio_irq_en) {
|
||||
ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
|
||||
ctrl1 |= BM_SSP_CTRL1_SDIO_IRQ_EN;
|
||||
}
|
||||
|
||||
writel(ctrl0, host->base + HW_SSP_CTRL0);
|
||||
writel(ctrl1, host->base + HW_SSP_CTRL1);
|
||||
}
|
||||
|
||||
static void mxs_mmc_start_cmd(struct mxs_mmc_host *host,
|
||||
struct mmc_command *cmd);
|
||||
|
||||
static void mxs_mmc_request_done(struct mxs_mmc_host *host)
|
||||
{
|
||||
struct mmc_command *cmd = host->cmd;
|
||||
struct mmc_data *data = host->data;
|
||||
struct mmc_request *mrq = host->mrq;
|
||||
|
||||
if (mmc_resp_type(cmd) & MMC_RSP_PRESENT) {
|
||||
if (mmc_resp_type(cmd) & MMC_RSP_136) {
|
||||
cmd->resp[3] = readl(host->base + HW_SSP_SDRESP0);
|
||||
cmd->resp[2] = readl(host->base + HW_SSP_SDRESP1);
|
||||
cmd->resp[1] = readl(host->base + HW_SSP_SDRESP2);
|
||||
cmd->resp[0] = readl(host->base + HW_SSP_SDRESP3);
|
||||
} else {
|
||||
cmd->resp[0] = readl(host->base + HW_SSP_SDRESP0);
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
|
||||
data->sg_len, host->dma_dir);
|
||||
/*
|
||||
* If there was an error on any block, we mark all
|
||||
* data blocks as being in error.
|
||||
*/
|
||||
if (!data->error)
|
||||
data->bytes_xfered = data->blocks * data->blksz;
|
||||
else
|
||||
data->bytes_xfered = 0;
|
||||
|
||||
host->data = NULL;
|
||||
if (mrq->stop) {
|
||||
mxs_mmc_start_cmd(host, mrq->stop);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
host->mrq = NULL;
|
||||
mmc_request_done(host->mmc, mrq);
|
||||
}
|
||||
|
||||
static void mxs_mmc_dma_irq_callback(void *param)
|
||||
{
|
||||
struct mxs_mmc_host *host = param;
|
||||
|
||||
mxs_mmc_request_done(host);
|
||||
}
|
||||
|
||||
static irqreturn_t mxs_mmc_irq_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct mxs_mmc_host *host = dev_id;
|
||||
struct mmc_command *cmd = host->cmd;
|
||||
struct mmc_data *data = host->data;
|
||||
u32 stat;
|
||||
|
||||
spin_lock(&host->lock);
|
||||
|
||||
stat = readl(host->base + HW_SSP_CTRL1);
|
||||
writel(stat & MXS_MMC_IRQ_BITS,
|
||||
host->base + HW_SSP_CTRL1 + MXS_CLR_ADDR);
|
||||
|
||||
if ((stat & BM_SSP_CTRL1_SDIO_IRQ) && (stat & BM_SSP_CTRL1_SDIO_IRQ_EN))
|
||||
mmc_signal_sdio_irq(host->mmc);
|
||||
|
||||
spin_unlock(&host->lock);
|
||||
|
||||
if (stat & BM_SSP_CTRL1_RESP_TIMEOUT_IRQ)
|
||||
cmd->error = -ETIMEDOUT;
|
||||
else if (stat & BM_SSP_CTRL1_RESP_ERR_IRQ)
|
||||
cmd->error = -EIO;
|
||||
|
||||
if (data) {
|
||||
if (stat & (BM_SSP_CTRL1_DATA_TIMEOUT_IRQ |
|
||||
BM_SSP_CTRL1_RECV_TIMEOUT_IRQ))
|
||||
data->error = -ETIMEDOUT;
|
||||
else if (stat & BM_SSP_CTRL1_DATA_CRC_IRQ)
|
||||
data->error = -EILSEQ;
|
||||
else if (stat & (BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ |
|
||||
BM_SSP_CTRL1_FIFO_OVERRUN_IRQ))
|
||||
data->error = -EIO;
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct dma_async_tx_descriptor *mxs_mmc_prep_dma(
|
||||
struct mxs_mmc_host *host, unsigned int append)
|
||||
{
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct mmc_data *data = host->data;
|
||||
struct scatterlist * sgl;
|
||||
unsigned int sg_len;
|
||||
|
||||
if (data) {
|
||||
/* data */
|
||||
dma_map_sg(mmc_dev(host->mmc), data->sg,
|
||||
data->sg_len, host->dma_dir);
|
||||
sgl = data->sg;
|
||||
sg_len = data->sg_len;
|
||||
} else {
|
||||
/* pio */
|
||||
sgl = (struct scatterlist *) host->ssp_pio_words;
|
||||
sg_len = SSP_PIO_NUM;
|
||||
}
|
||||
|
||||
desc = host->dmach->device->device_prep_slave_sg(host->dmach,
|
||||
sgl, sg_len, host->dma_dir, append);
|
||||
if (desc) {
|
||||
desc->callback = mxs_mmc_dma_irq_callback;
|
||||
desc->callback_param = host;
|
||||
} else {
|
||||
if (data)
|
||||
dma_unmap_sg(mmc_dev(host->mmc), data->sg,
|
||||
data->sg_len, host->dma_dir);
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
static void mxs_mmc_bc(struct mxs_mmc_host *host)
|
||||
{
|
||||
struct mmc_command *cmd = host->cmd;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
u32 ctrl0, cmd0, cmd1;
|
||||
|
||||
ctrl0 = BM_SSP_CTRL0_ENABLE | BM_SSP_CTRL0_IGNORE_CRC;
|
||||
cmd0 = BF_SSP(cmd->opcode, CMD0_CMD) | BM_SSP_CMD0_APPEND_8CYC;
|
||||
cmd1 = cmd->arg;
|
||||
|
||||
if (host->sdio_irq_en) {
|
||||
ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
|
||||
cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
|
||||
}
|
||||
|
||||
host->ssp_pio_words[0] = ctrl0;
|
||||
host->ssp_pio_words[1] = cmd0;
|
||||
host->ssp_pio_words[2] = cmd1;
|
||||
host->dma_dir = DMA_NONE;
|
||||
desc = mxs_mmc_prep_dma(host, 0);
|
||||
if (!desc)
|
||||
goto out;
|
||||
|
||||
dmaengine_submit(desc);
|
||||
return;
|
||||
|
||||
out:
|
||||
dev_warn(mmc_dev(host->mmc),
|
||||
"%s: failed to prep dma\n", __func__);
|
||||
}
|
||||
|
||||
static void mxs_mmc_ac(struct mxs_mmc_host *host)
|
||||
{
|
||||
struct mmc_command *cmd = host->cmd;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
u32 ignore_crc, get_resp, long_resp;
|
||||
u32 ctrl0, cmd0, cmd1;
|
||||
|
||||
ignore_crc = (mmc_resp_type(cmd) & MMC_RSP_CRC) ?
|
||||
0 : BM_SSP_CTRL0_IGNORE_CRC;
|
||||
get_resp = (mmc_resp_type(cmd) & MMC_RSP_PRESENT) ?
|
||||
BM_SSP_CTRL0_GET_RESP : 0;
|
||||
long_resp = (mmc_resp_type(cmd) & MMC_RSP_136) ?
|
||||
BM_SSP_CTRL0_LONG_RESP : 0;
|
||||
|
||||
ctrl0 = BM_SSP_CTRL0_ENABLE | ignore_crc | get_resp | long_resp;
|
||||
cmd0 = BF_SSP(cmd->opcode, CMD0_CMD);
|
||||
cmd1 = cmd->arg;
|
||||
|
||||
if (host->sdio_irq_en) {
|
||||
ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
|
||||
cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
|
||||
}
|
||||
|
||||
host->ssp_pio_words[0] = ctrl0;
|
||||
host->ssp_pio_words[1] = cmd0;
|
||||
host->ssp_pio_words[2] = cmd1;
|
||||
host->dma_dir = DMA_NONE;
|
||||
desc = mxs_mmc_prep_dma(host, 0);
|
||||
if (!desc)
|
||||
goto out;
|
||||
|
||||
dmaengine_submit(desc);
|
||||
return;
|
||||
|
||||
out:
|
||||
dev_warn(mmc_dev(host->mmc),
|
||||
"%s: failed to prep dma\n", __func__);
|
||||
}
|
||||
|
||||
static unsigned short mxs_ns_to_ssp_ticks(unsigned clock_rate, unsigned ns)
|
||||
{
|
||||
const unsigned int ssp_timeout_mul = 4096;
|
||||
/*
|
||||
* Calculate ticks in ms since ns are large numbers
|
||||
* and might overflow
|
||||
*/
|
||||
const unsigned int clock_per_ms = clock_rate / 1000;
|
||||
const unsigned int ms = ns / 1000;
|
||||
const unsigned int ticks = ms * clock_per_ms;
|
||||
const unsigned int ssp_ticks = ticks / ssp_timeout_mul;
|
||||
|
||||
WARN_ON(ssp_ticks == 0);
|
||||
return ssp_ticks;
|
||||
}
|
||||
|
||||
static void mxs_mmc_adtc(struct mxs_mmc_host *host)
|
||||
{
|
||||
struct mmc_command *cmd = host->cmd;
|
||||
struct mmc_data *data = cmd->data;
|
||||
struct dma_async_tx_descriptor *desc;
|
||||
struct scatterlist *sgl = data->sg, *sg;
|
||||
unsigned int sg_len = data->sg_len;
|
||||
int i;
|
||||
|
||||
unsigned short dma_data_dir, timeout;
|
||||
unsigned int data_size = 0, log2_blksz;
|
||||
unsigned int blocks = data->blocks;
|
||||
|
||||
u32 ignore_crc, get_resp, long_resp, read;
|
||||
u32 ctrl0, cmd0, cmd1, val;
|
||||
|
||||
ignore_crc = (mmc_resp_type(cmd) & MMC_RSP_CRC) ?
|
||||
0 : BM_SSP_CTRL0_IGNORE_CRC;
|
||||
get_resp = (mmc_resp_type(cmd) & MMC_RSP_PRESENT) ?
|
||||
BM_SSP_CTRL0_GET_RESP : 0;
|
||||
long_resp = (mmc_resp_type(cmd) & MMC_RSP_136) ?
|
||||
BM_SSP_CTRL0_LONG_RESP : 0;
|
||||
|
||||
if (data->flags & MMC_DATA_WRITE) {
|
||||
dma_data_dir = DMA_TO_DEVICE;
|
||||
read = 0;
|
||||
} else {
|
||||
dma_data_dir = DMA_FROM_DEVICE;
|
||||
read = BM_SSP_CTRL0_READ;
|
||||
}
|
||||
|
||||
ctrl0 = BF_SSP(host->bus_width, CTRL0_BUS_WIDTH) |
|
||||
ignore_crc | get_resp | long_resp |
|
||||
BM_SSP_CTRL0_DATA_XFER | read |
|
||||
BM_SSP_CTRL0_WAIT_FOR_IRQ |
|
||||
BM_SSP_CTRL0_ENABLE;
|
||||
|
||||
cmd0 = BF_SSP(cmd->opcode, CMD0_CMD);
|
||||
|
||||
/* get logarithm to base 2 of block size for setting register */
|
||||
log2_blksz = ilog2(data->blksz);
|
||||
|
||||
/*
|
||||
* take special care of the case that data size from data->sg
|
||||
* is not equal to blocks x blksz
|
||||
*/
|
||||
for_each_sg(sgl, sg, sg_len, i)
|
||||
data_size += sg->length;
|
||||
|
||||
if (data_size != data->blocks * data->blksz)
|
||||
blocks = 1;
|
||||
|
||||
/* xfer count, block size and count need to be set differently */
|
||||
if (ssp_is_old()) {
|
||||
ctrl0 |= BF_SSP(data_size, CTRL0_XFER_COUNT);
|
||||
cmd0 |= BF_SSP(log2_blksz, CMD0_BLOCK_SIZE) |
|
||||
BF_SSP(blocks - 1, CMD0_BLOCK_COUNT);
|
||||
} else {
|
||||
writel(data_size, host->base + HW_SSP_XFER_SIZE);
|
||||
writel(BF_SSP(log2_blksz, BLOCK_SIZE_BLOCK_SIZE) |
|
||||
BF_SSP(blocks - 1, BLOCK_SIZE_BLOCK_COUNT),
|
||||
host->base + HW_SSP_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
if ((cmd->opcode == MMC_STOP_TRANSMISSION) ||
|
||||
(cmd->opcode == SD_IO_RW_EXTENDED))
|
||||
cmd0 |= BM_SSP_CMD0_APPEND_8CYC;
|
||||
|
||||
cmd1 = cmd->arg;
|
||||
|
||||
if (host->sdio_irq_en) {
|
||||
ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
|
||||
cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
|
||||
}
|
||||
|
||||
/* set the timeout count */
|
||||
timeout = mxs_ns_to_ssp_ticks(host->clk_rate, data->timeout_ns);
|
||||
val = readl(host->base + HW_SSP_TIMING);
|
||||
val &= ~(BM_SSP_TIMING_TIMEOUT);
|
||||
val |= BF_SSP(timeout, TIMING_TIMEOUT);
|
||||
writel(val, host->base + HW_SSP_TIMING);
|
||||
|
||||
/* pio */
|
||||
host->ssp_pio_words[0] = ctrl0;
|
||||
host->ssp_pio_words[1] = cmd0;
|
||||
host->ssp_pio_words[2] = cmd1;
|
||||
host->dma_dir = DMA_NONE;
|
||||
desc = mxs_mmc_prep_dma(host, 0);
|
||||
if (!desc)
|
||||
goto out;
|
||||
|
||||
/* append data sg */
|
||||
WARN_ON(host->data != NULL);
|
||||
host->data = data;
|
||||
host->dma_dir = dma_data_dir;
|
||||
desc = mxs_mmc_prep_dma(host, 1);
|
||||
if (!desc)
|
||||
goto out;
|
||||
|
||||
dmaengine_submit(desc);
|
||||
return;
|
||||
out:
|
||||
dev_warn(mmc_dev(host->mmc),
|
||||
"%s: failed to prep dma\n", __func__);
|
||||
}
|
||||
|
||||
static void mxs_mmc_start_cmd(struct mxs_mmc_host *host,
|
||||
struct mmc_command *cmd)
|
||||
{
|
||||
host->cmd = cmd;
|
||||
|
||||
switch (mmc_cmd_type(cmd)) {
|
||||
case MMC_CMD_BC:
|
||||
mxs_mmc_bc(host);
|
||||
break;
|
||||
case MMC_CMD_BCR:
|
||||
mxs_mmc_ac(host);
|
||||
break;
|
||||
case MMC_CMD_AC:
|
||||
mxs_mmc_ac(host);
|
||||
break;
|
||||
case MMC_CMD_ADTC:
|
||||
mxs_mmc_adtc(host);
|
||||
break;
|
||||
default:
|
||||
dev_warn(mmc_dev(host->mmc),
|
||||
"%s: unknown MMC command\n", __func__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void mxs_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
||||
{
|
||||
struct mxs_mmc_host *host = mmc_priv(mmc);
|
||||
|
||||
WARN_ON(host->mrq != NULL);
|
||||
host->mrq = mrq;
|
||||
mxs_mmc_start_cmd(host, mrq->cmd);
|
||||
}
|
||||
|
||||
static void mxs_mmc_set_clk_rate(struct mxs_mmc_host *host, unsigned int rate)
|
||||
{
|
||||
unsigned int ssp_rate, bit_rate;
|
||||
u32 div1, div2;
|
||||
u32 val;
|
||||
|
||||
ssp_rate = clk_get_rate(host->clk);
|
||||
|
||||
for (div1 = 2; div1 < 254; div1 += 2) {
|
||||
div2 = ssp_rate / rate / div1;
|
||||
if (div2 < 0x100)
|
||||
break;
|
||||
}
|
||||
|
||||
if (div1 >= 254) {
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"%s: cannot set clock to %d\n", __func__, rate);
|
||||
return;
|
||||
}
|
||||
|
||||
if (div2 == 0)
|
||||
bit_rate = ssp_rate / div1;
|
||||
else
|
||||
bit_rate = ssp_rate / div1 / div2;
|
||||
|
||||
val = readl(host->base + HW_SSP_TIMING);
|
||||
val &= ~(BM_SSP_TIMING_CLOCK_DIVIDE | BM_SSP_TIMING_CLOCK_RATE);
|
||||
val |= BF_SSP(div1, TIMING_CLOCK_DIVIDE);
|
||||
val |= BF_SSP(div2 - 1, TIMING_CLOCK_RATE);
|
||||
writel(val, host->base + HW_SSP_TIMING);
|
||||
|
||||
host->clk_rate = bit_rate;
|
||||
|
||||
dev_dbg(mmc_dev(host->mmc),
|
||||
"%s: div1 %d, div2 %d, ssp %d, bit %d, rate %d\n",
|
||||
__func__, div1, div2, ssp_rate, bit_rate, rate);
|
||||
}
|
||||
|
||||
static void mxs_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
||||
{
|
||||
struct mxs_mmc_host *host = mmc_priv(mmc);
|
||||
|
||||
if (ios->bus_width == MMC_BUS_WIDTH_8)
|
||||
host->bus_width = 2;
|
||||
else if (ios->bus_width == MMC_BUS_WIDTH_4)
|
||||
host->bus_width = 1;
|
||||
else
|
||||
host->bus_width = 0;
|
||||
|
||||
if (ios->clock)
|
||||
mxs_mmc_set_clk_rate(host, ios->clock);
|
||||
}
|
||||
|
||||
static void mxs_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
|
||||
{
|
||||
struct mxs_mmc_host *host = mmc_priv(mmc);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
|
||||
host->sdio_irq_en = enable;
|
||||
|
||||
if (enable) {
|
||||
writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK,
|
||||
host->base + HW_SSP_CTRL0 + MXS_SET_ADDR);
|
||||
writel(BM_SSP_CTRL1_SDIO_IRQ_EN,
|
||||
host->base + HW_SSP_CTRL1 + MXS_SET_ADDR);
|
||||
|
||||
if (readl(host->base + HW_SSP_STATUS) & BM_SSP_STATUS_SDIO_IRQ)
|
||||
mmc_signal_sdio_irq(host->mmc);
|
||||
|
||||
} else {
|
||||
writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK,
|
||||
host->base + HW_SSP_CTRL0 + MXS_CLR_ADDR);
|
||||
writel(BM_SSP_CTRL1_SDIO_IRQ_EN,
|
||||
host->base + HW_SSP_CTRL1 + MXS_CLR_ADDR);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
}
|
||||
|
||||
static const struct mmc_host_ops mxs_mmc_ops = {
|
||||
.request = mxs_mmc_request,
|
||||
.get_ro = mxs_mmc_get_ro,
|
||||
.get_cd = mxs_mmc_get_cd,
|
||||
.set_ios = mxs_mmc_set_ios,
|
||||
.enable_sdio_irq = mxs_mmc_enable_sdio_irq,
|
||||
};
|
||||
|
||||
static bool mxs_mmc_dma_filter(struct dma_chan *chan, void *param)
|
||||
{
|
||||
struct mxs_mmc_host *host = param;
|
||||
|
||||
if (!mxs_dma_is_apbh(chan))
|
||||
return false;
|
||||
|
||||
if (chan->chan_id != host->dma_res->start)
|
||||
return false;
|
||||
|
||||
chan->private = &host->dma_data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int mxs_mmc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mxs_mmc_host *host;
|
||||
struct mmc_host *mmc;
|
||||
struct resource *iores, *dmares, *r;
|
||||
struct mxs_mmc_platform_data *pdata;
|
||||
int ret = 0, irq_err, irq_dma;
|
||||
dma_cap_mask_t mask;
|
||||
|
||||
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
|
||||
irq_err = platform_get_irq(pdev, 0);
|
||||
irq_dma = platform_get_irq(pdev, 1);
|
||||
if (!iores || !dmares || irq_err < 0 || irq_dma < 0)
|
||||
return -EINVAL;
|
||||
|
||||
r = request_mem_region(iores->start, resource_size(iores), pdev->name);
|
||||
if (!r)
|
||||
return -EBUSY;
|
||||
|
||||
mmc = mmc_alloc_host(sizeof(struct mxs_mmc_host), &pdev->dev);
|
||||
if (!mmc) {
|
||||
ret = -ENOMEM;
|
||||
goto out_release_mem;
|
||||
}
|
||||
|
||||
host = mmc_priv(mmc);
|
||||
host->base = ioremap(r->start, resource_size(r));
|
||||
if (!host->base) {
|
||||
ret = -ENOMEM;
|
||||
goto out_mmc_free;
|
||||
}
|
||||
|
||||
/* only major verion does matter */
|
||||
host->version = readl(host->base + HW_SSP_VERSION) >>
|
||||
BP_SSP_VERSION_MAJOR;
|
||||
|
||||
host->mmc = mmc;
|
||||
host->res = r;
|
||||
host->dma_res = dmares;
|
||||
host->irq = irq_err;
|
||||
host->sdio_irq_en = 0;
|
||||
|
||||
host->clk = clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(host->clk)) {
|
||||
ret = PTR_ERR(host->clk);
|
||||
goto out_iounmap;
|
||||
}
|
||||
clk_enable(host->clk);
|
||||
|
||||
mxs_mmc_reset(host);
|
||||
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
host->dma_data.chan_irq = irq_dma;
|
||||
host->dmach = dma_request_channel(mask, mxs_mmc_dma_filter, host);
|
||||
if (!host->dmach) {
|
||||
dev_err(mmc_dev(host->mmc),
|
||||
"%s: failed to request dma\n", __func__);
|
||||
goto out_clk_put;
|
||||
}
|
||||
|
||||
/* set mmc core parameters */
|
||||
mmc->ops = &mxs_mmc_ops;
|
||||
mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
|
||||
MMC_CAP_SDIO_IRQ | MMC_CAP_NEEDS_POLL;
|
||||
|
||||
pdata = mmc_dev(host->mmc)->platform_data;
|
||||
if (pdata) {
|
||||
if (pdata->flags & SLOTF_8_BIT_CAPABLE)
|
||||
mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
|
||||
if (pdata->flags & SLOTF_4_BIT_CAPABLE)
|
||||
mmc->caps |= MMC_CAP_4_BIT_DATA;
|
||||
}
|
||||
|
||||
mmc->f_min = 400000;
|
||||
mmc->f_max = 288000000;
|
||||
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
|
||||
mmc->max_segs = 52;
|
||||
mmc->max_blk_size = 1 << 0xf;
|
||||
mmc->max_blk_count = (ssp_is_old()) ? 0xff : 0xffffff;
|
||||
mmc->max_req_size = (ssp_is_old()) ? 0xffff : 0xffffffff;
|
||||
mmc->max_seg_size = dma_get_max_seg_size(host->dmach->device->dev);
|
||||
|
||||
platform_set_drvdata(pdev, mmc);
|
||||
|
||||
ret = request_irq(host->irq, mxs_mmc_irq_handler, 0, DRIVER_NAME, host);
|
||||
if (ret)
|
||||
goto out_free_dma;
|
||||
|
||||
spin_lock_init(&host->lock);
|
||||
|
||||
ret = mmc_add_host(mmc);
|
||||
if (ret)
|
||||
goto out_free_irq;
|
||||
|
||||
dev_info(mmc_dev(host->mmc), "initialized\n");
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_irq:
|
||||
free_irq(host->irq, host);
|
||||
out_free_dma:
|
||||
if (host->dmach)
|
||||
dma_release_channel(host->dmach);
|
||||
out_clk_put:
|
||||
clk_disable(host->clk);
|
||||
clk_put(host->clk);
|
||||
out_iounmap:
|
||||
iounmap(host->base);
|
||||
out_mmc_free:
|
||||
mmc_free_host(mmc);
|
||||
out_release_mem:
|
||||
release_mem_region(iores->start, resource_size(iores));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxs_mmc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mmc_host *mmc = platform_get_drvdata(pdev);
|
||||
struct mxs_mmc_host *host = mmc_priv(mmc);
|
||||
struct resource *res = host->res;
|
||||
|
||||
mmc_remove_host(mmc);
|
||||
|
||||
free_irq(host->irq, host);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
if (host->dmach)
|
||||
dma_release_channel(host->dmach);
|
||||
|
||||
clk_disable(host->clk);
|
||||
clk_put(host->clk);
|
||||
|
||||
iounmap(host->base);
|
||||
|
||||
mmc_free_host(mmc);
|
||||
|
||||
release_mem_region(res->start, resource_size(res));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int mxs_mmc_suspend(struct device *dev)
|
||||
{
|
||||
struct mmc_host *mmc = dev_get_drvdata(dev);
|
||||
struct mxs_mmc_host *host = mmc_priv(mmc);
|
||||
int ret = 0;
|
||||
|
||||
ret = mmc_suspend_host(mmc);
|
||||
|
||||
clk_disable(host->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxs_mmc_resume(struct device *dev)
|
||||
{
|
||||
struct mmc_host *mmc = dev_get_drvdata(dev);
|
||||
struct mxs_mmc_host *host = mmc_priv(mmc);
|
||||
int ret = 0;
|
||||
|
||||
clk_enable(host->clk);
|
||||
|
||||
ret = mmc_resume_host(mmc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops mxs_mmc_pm_ops = {
|
||||
.suspend = mxs_mmc_suspend,
|
||||
.resume = mxs_mmc_resume,
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct platform_driver mxs_mmc_driver = {
|
||||
.probe = mxs_mmc_probe,
|
||||
.remove = mxs_mmc_remove,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_PM
|
||||
.pm = &mxs_mmc_pm_ops,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
static int __init mxs_mmc_init(void)
|
||||
{
|
||||
return platform_driver_register(&mxs_mmc_driver);
|
||||
}
|
||||
|
||||
static void __exit mxs_mmc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&mxs_mmc_driver);
|
||||
}
|
||||
|
||||
module_init(mxs_mmc_init);
|
||||
module_exit(mxs_mmc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("FREESCALE MXS MMC peripheral");
|
||||
MODULE_AUTHOR("Freescale Semiconductor");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user