This patch adds the MMC/SD/SDIO Arasan host controller. Advanced DMA, Single DMA and PIO modes are supported. The former is the default. This has been tested on the 7108/06 STM platforms. Signed-off-by: Giuseppe Cavallaro <peppe.cavallaro@xxxxxx> --- drivers/mmc/host/Kconfig | 7 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/arasan.c | 1354 +++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/arasan.h | 237 +++++++ include/linux/mmc/arasan_plat.h | 49 ++ 5 files changed, 1648 insertions(+), 0 deletions(-) create mode 100644 drivers/mmc/host/arasan.c create mode 100644 drivers/mmc/host/arasan.h create mode 100644 include/linux/mmc/arasan_plat.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index f06d06e..f403f1d 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -432,3 +432,10 @@ config MMC_SH_MMCIF This selects the MMC Host Interface controler (MMCIF). This driver supports MMCIF in sh7724/sh7757/sh7372. + +config MMC_ARASAN + tristate "Arasan MMC/SD/SDIO host driver" + depends on CPU_SUBTYPE_ST40 + help + This selects the Arasan MMC/SD/SDIO host controller integrated + in the STMicroelectronics platforms (stx7108 and stx7106). diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index e30c2ee..8337f15 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o +obj-$(CONFIG_MMC_ARASAN) += arasan.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o sdhci-of-y := sdhci-of-core.o diff --git a/drivers/mmc/host/arasan.c b/drivers/mmc/host/arasan.c new file mode 100644 index 0000000..7dc49ae --- /dev/null +++ b/drivers/mmc/host/arasan.c @@ -0,0 +1,1354 @@ +/* + * Arasan MMC/SD/SDIO driver + * + * This is the driver for the Arasan MMC/SD/SDIO host controller + * integrated in the STMicroelectronics platforms + * + * Author: Giuseppe Cavallaro <peppe.cavallaro@xxxxxx> + * Copyright (C) 2010 STMicroelectronics Ltd + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/mbus.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/scatterlist.h> +#include <linux/irq.h> +#include <linux/highmem.h> +#include <linux/mmc/host.h> +#include <linux/mmc/arasan_plat.h> + +#include <asm/sizes.h> +#include <asm/unaligned.h> + +#include "arasan.h" + +/* To enable more debug information. */ +#undef ARASAN_DEBUG +/*#define ARASAN_DEBUG*/ +#ifdef ARASAN_DEBUG +#define DBG(fmt, args...) pr_info(fmt, ## args) +#else +#define DBG(fmt, args...) do { } while (0) +#endif + +static int maxfreq = ARASAN_CLOCKRATE_MAX; +module_param(maxfreq, int, S_IRUGO); +MODULE_PARM_DESC(maxfreq, "Maximum card clock frequency (default 50MHz)"); + +static unsigned int adma = 1; +module_param(adma, int, S_IRUGO); +MODULE_PARM_DESC(adma, "Disable/Enable the Advanced DMA mode"); + +static unsigned int led; +module_param(led, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(led, "Enable|Disable LED"); + +static unsigned int pio; +module_param(pio, int, S_IRUGO); +MODULE_PARM_DESC(pio, "PIO mode (no DMA)"); + +struct arasan_cap { + unsigned int timer_freq; + unsigned int timer_unit; + unsigned int base_clk_sd; + unsigned int max_blk_len; + unsigned int adma2; + unsigned int high_speed; + unsigned int sdma; + unsigned int suspend; + unsigned int voltage33; + unsigned int voltage30; + unsigned int voltage18; + unsigned int int_mode; + unsigned int spi; + unsigned int spi_block; +}; + +struct arasan_host { + void __iomem *base; + struct mmc_request *mrq; + unsigned int intr_en; + u8 ctrl; + unsigned int sg_frags; + struct timer_list timer; + struct mmc_host *mmc; + struct device *dev; + struct resource *res; + int irq; + struct arasan_cap cap; + u8 vdd; + unsigned int freq; + unsigned int status; + unsigned int adma; + unsigned int use_pio; + u16 pio_blksz; + u32 pio_blocks; + u32 *pio_blkbuf; + spinlock_t lock; + struct tasklet_struct card_tasklet; + u8 *adma_desc; + dma_addr_t adma_addr; + int need_poll; +}; + +static inline void arsan_sw_reset(struct arasan_host *host, unsigned int flag) +{ + /* After completing the reset, wait the HC clears these bits */ + if (likely(flag == reset_all)) { + writeb(ARSAN_RESET_ALL, host->base + ARASAN_SW_RESET); + do { } while ((readb(host->base + ARASAN_SW_RESET)) & + ARSAN_RESET_ALL); + } else if (flag == reset_cmd_line) { + writeb(ARSAN_RESET_CMD_LINE, host->base + ARASAN_SW_RESET); + do { } while ((readb(host->base + ARASAN_SW_RESET)) & + ARSAN_RESET_CMD_LINE); + + } else if (flag == reset_dat_line) { + writeb(ARSAN_RESET_DAT_LINE, host->base + ARASAN_SW_RESET); + do { } while ((readb(host->base + ARASAN_SW_RESET)) & + ARSAN_RESET_DAT_LINE); + } +} + +static inline void arsan_hc_version(struct arasan_host *host) +{ + u16 version; + + version = readw(host->base + ARASAN_HOST_VERSION); + pr_debug("Arasan MMC/SDIO:\n\tHC Vendor Version Number: %d\n", + (version >> 8)); + pr_debug("\tHC SPEC Version Number: %d\n", (version & 0x00ff)); +} + +static void arasan_capabilities(struct arasan_host *host) +{ + unsigned int cap; + unsigned int max_blk_len; + + cap = readl(host->base + ARASAN_CAPABILITIES); + + pr_debug("\tArasan capabilities: 0x%x\n", cap); + + host->cap.timer_freq = cap & 0x3f; + host->cap.timer_unit = (cap >> 7) & 0x1; + + pr_debug("\tTimeout Clock Freq: %d %s\n", host->cap.timer_freq, + host->cap.timer_unit ? "MHz" : "KHz"); + + host->cap.base_clk_sd = (cap >> 8) & 0x3f; + pr_debug("\tBase Clock Freq for SD: %d MHz\n", host->cap.base_clk_sd); + + max_blk_len = (cap >> 16) & 0x3; + switch (max_blk_len) { + case 0: + host->cap.max_blk_len = 512; + break; + case 1: + host->cap.max_blk_len = 1024; + break; + case 2: + host->cap.max_blk_len = 2048; + break; + case 3: + host->cap.max_blk_len = 4096; + break; + default: + break; + } + pr_debug("\tMax Block size: %d bytes\n", host->cap.max_blk_len); + + host->cap.adma2 = (cap >> 19) & 0x1; + host->cap.high_speed = (cap >> 21) & 0x1; + host->cap.sdma = (cap >> 22) & 0x1; + + pr_debug("\tadma2 %s, high speed %s, sdma %s\n", + host->cap.adma2 ? "Yes" : "Not", + host->cap.high_speed ? "Yes" : "Not", + host->cap.sdma ? "Yes" : "Not"); + + host->cap.suspend = (cap >> 23) & 0x1; + pr_debug("\tsuspend/resume %s suported\n", + host->cap.adma2 ? "is" : "Not"); + + /* Disable adma user option if cap not supported. */ + if (!host->cap.adma2) + adma = 0; + + host->cap.voltage33 = (cap >> 24) & 0x1; + host->cap.voltage30 = (cap >> 25) & 0x1; + host->cap.voltage18 = (cap >> 26) & 0x1; + host->cap.int_mode = (cap >> 27) & 0x1; + host->cap.spi = (cap >> 29) & 0x1; + host->cap.spi_block = (cap >> 30) & 0x1; + + if (host->cap.voltage33) + pr_debug("\t3.3V voltage suported\n"); + if (host->cap.voltage30) + pr_debug("\t3.0V voltage suported\n"); + if (host->cap.voltage18) + pr_debug("\t1.8V voltage suported\n"); + + if (host->cap.int_mode) + pr_debug("\tInterrupt Mode supported\n"); + if (host->cap.spi) + pr_debug("\tSPI Mode supported\n"); + if (host->cap.spi_block) + pr_debug("\tSPI Block Mode supported\n"); +} + +static void arasan_ctrl_led(struct arasan_host *host, unsigned int flag) +{ + if (led) { + u8 ctrl_reg = readb(host->base + ARASAN_HOST_CTRL); + + if (flag) + ctrl_reg |= ARASAN_HOST_CTRL_LED; + else + ctrl_reg &= ~ARASAN_HOST_CTRL_LED; + + host->ctrl = ctrl_reg; + writeb(host->ctrl, host->base + ARASAN_HOST_CTRL); + } +} + +static inline void arasan_set_interrupts(struct arasan_host *host) +{ + host->intr_en = ARASAN_IRQ_DEFAULT_MASK; + writel(host->intr_en, host->base + ARASAN_NORMAL_INT_STATUS_EN); + writel(host->intr_en, host->base + ARASAN_NORMAL_INT_SIGN_EN); +} + +static inline void arasan_clear_interrupts(struct arasan_host *host) +{ + writel(0, host->base + ARASAN_NORMAL_INT_STATUS_EN); + writel(0, host->base + ARASAN_ERR_INT_STATUS_EN); + writel(0, host->base + ARASAN_NORMAL_INT_SIGN_EN); +} + +static void arasan_power_set(struct arasan_host *host, unsigned int pwr, u8 vdd) +{ + u8 pwr_reg; + + pwr_reg = readb(host->base + ARASAN_PWR_CTRL); + + host->vdd = (1 << vdd); + + if (pwr) { + pwr_reg &= 0xf1; + + if ((host->vdd & MMC_VDD_165_195) && host->cap.voltage18) + pwr_reg |= ARASAN_PWR_BUS_VOLTAGE_18; + else if ((host->vdd & MMC_VDD_29_30) && host->cap.voltage30) + pwr_reg |= ARASAN_PWR_BUS_VOLTAGE_30; + else if ((host->vdd & MMC_VDD_32_33) && host->cap.voltage33) + pwr_reg |= ARASAN_PWR_BUS_VOLTAGE_33; + + pwr_reg |= ARASAN_PWR_CTRL_UP; + } else + pwr_reg &= ~ARASAN_PWR_CTRL_UP; + + DBG("%s: pwr_reg 0x%x, host->vdd = 0x%x\n", __func__, pwr_reg, + host->vdd); + writeb(pwr_reg, host->base + ARASAN_PWR_CTRL); +} + +static int arasan_test_card(struct arasan_host *host) +{ + unsigned int ret = 0; + u32 present = readl(host->base + ARASAN_PRESENT_STATE); + if (likely(!(present & ARASAN_PRESENT_STATE_CARD_PRESENT))) + ret = -1; + +#ifdef ARASAN_DEBUG + if (present & ARASAN_PRESENT_STATE_CARD_STABLE) + pr_info("\tcard stable..."); + if (!(present & ARASAN_PRESENT_STATE_WR_EN)) + pr_info("\tcard Write protected..."); + if (present & ARASAN_PRESENT_STATE_BUFFER_RD_EN) + pr_info("\tPIO Read Enable..."); + if (present & ARASAN_PRESENT_STATE_BUFFER_WR_EN) + pr_info("\tPIO Write Enable..."); + if (present & ARASAN_PRESENT_STATE_RD_ACTIVE) + pr_info("\tRead Xfer data..."); + if (present & ARASAN_PRESENT_STATE_WR_ACTIVE) + pr_info("\tWrite Xfer data..."); + if (present & ARASAN_PRESENT_STATE_DAT_ACTIVE) + pr_info("\tDAT line active..."); +#endif + return ret; +} +static void arasan_set_clock(struct arasan_host *host, unsigned int freq) +{ + u16 clock = 0; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if ((host->freq != freq) && (freq)) { + u16 divisor; + + /* Ensure clock is off before making any changes */ + writew(clock, host->base + ARASAN_CLOCK_CTRL); + + /* core checks if this is a good freq < max_freq */ + host->freq = freq; + + DBG("%s:\n\tnew freq %d", __func__, host->freq); + + /* Work out divisor for specified clock frequency */ + for (divisor = 1; divisor <= 256; divisor *= 2) + /* Find first divisor producing a frequency less + * than or equal to MHz */ + if ((maxfreq / divisor) <= freq) + break; + + DBG("\tdivisor %d", divisor); + /* Set the clock divisor and enable the internal clock */ + clock = divisor << (ARASAN_CLOCK_CTRL_SDCLK_SHIFT); + clock &= ARASAN_CLOCK_CTRL_SDCLK_MASK; + clock |= ARASAN_CLOCK_CTRL_ICLK_ENABLE; + writew(clock, host->base + ARASAN_CLOCK_CTRL); + + /* Busy wait for the clock to become stable */ + do { } while (((readw(host->base + ARASAN_CLOCK_CTRL)) & + ARASAN_CLOCK_CTRL_ICLK_STABLE) == 0); + + /* Enable the SD clock */ + clock |= ARASAN_CLOCK_CTRL_SDCLK_ENABLE; + writew(clock, host->base + ARASAN_CLOCK_CTRL); + + DBG("\tclk ctrl reg. [0x%x]\n", + (unsigned int)readw(host->base + ARASAN_CLOCK_CTRL)); + } + + spin_unlock_irqrestore(&host->lock, flags); +} + +/* Read the response from the card */ +static void arasan_get_resp(struct mmc_command *cmd, struct arasan_host *host) +{ + unsigned int i; + unsigned int resp[4]; + + for (i = 0; i < 4; i++) + resp[i] = readl(host->base + ARASAN_RSP(i)); + + if (cmd->flags & MMC_RSP_136) { + cmd->resp[3] = (resp[0] << 8); + cmd->resp[2] = (resp[0] >> 24) | (resp[1] << 8); + cmd->resp[1] = (resp[1] >> 24) | (resp[2] << 8); + cmd->resp[0] = (resp[2] >> 24) | (resp[3] << 8); + } else { + cmd->resp[0] = resp[0]; + cmd->resp[1] = resp[1]; + } + + DBG("%s: resp length %s\n-(CMD%u):\n %08x %08x %08x %08x\n" + "-RAW reg:\n %08x %08x %08x %08x\n", + __func__, (cmd->flags & MMC_RSP_136) ? "136" : "48", cmd->opcode, + cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], + resp[0], resp[1], resp[2], resp[3]); +} + +static void arasan_read_block_pio(struct arasan_host *host) +{ + unsigned long flags; + u16 blksz; + + DBG("\tPIO reading\n"); + + local_irq_save(flags); + + for (blksz = host->pio_blksz; blksz > 0; blksz -= 4) { + *host->pio_blkbuf = + readl(host->base + ARASAN_BUFF); + host->pio_blkbuf++; + } + + local_irq_restore(flags); +} + +static void arasan_write_block_pio(struct arasan_host *host) +{ + unsigned long flags; + u16 blksz; + + DBG("\tPIO writing\n"); + local_irq_save(flags); + + for (blksz = host->pio_blksz; blksz > 0; blksz -= 4) { + writel(*host->pio_blkbuf, + host->base + ARASAN_BUFF); + host->pio_blkbuf++; + } + + local_irq_restore(flags); +} + +static void arasan_data_pio(struct arasan_host *host) +{ + if (host->pio_blocks == 0) + return; + + if (host->status == STATE_DATA_READ) { + while (readl(host->base + ARASAN_PRESENT_STATE) & + ARASAN_PRESENT_STATE_BUFFER_RD_EN) { + + arasan_read_block_pio(host); + + host->pio_blocks--; + if (host->pio_blocks == 0) + break; + } + + } else { + while (readl(host->base + ARASAN_PRESENT_STATE) & + ARASAN_PRESENT_STATE_BUFFER_WR_EN) { + + arasan_write_block_pio(host); + + host->pio_blocks--; + if (host->pio_blocks == 0) + break; + } + } + DBG("\tPIO transfer complete.\n"); +} + +static void arasan_start_cmd(struct arasan_host *host, struct mmc_command *cmd) +{ + u16 cmdreg = 0; + + /* Command Request */ + cmdreg = ARASAN_CMD_INDEX(cmd->opcode); + DBG("%s: cmd type %04x, CMD%d\n", __func__, + mmc_resp_type(cmd), cmd->opcode); + + if (cmd->flags & MMC_RSP_BUSY) { + cmdreg |= ARASAN_CMD_RSP_48BUSY; + DBG("\tResponse length 48 check Busy.\n"); + } else if (cmd->flags & MMC_RSP_136) { + cmdreg |= ARASAN_CMD_RSP_136; + DBG("\tResponse length 136\n"); + } else if (cmd->flags & MMC_RSP_PRESENT) { + cmdreg |= ARASAN_CMD_RSP_48; + DBG("\tResponse length 48\n"); + } else { + cmdreg |= ARASAN_CMD_RSP_NONE; + DBG("\tNo Response\n"); + } + + if (cmd->flags & MMC_RSP_CRC) { + cmdreg |= ARASAN_CMD_CHECK_CMDCRC; + DBG("\tCheck the CRC field in the response\n"); + } + if (cmd->flags & MMC_RSP_OPCODE) { + cmdreg |= ARASAN_CMD_INDX_CHECK; + DBG("\tCheck the Index field in the response\n"); + } + + /* Wait until the CMD line is not in use */ + do { } while ((readl(host->base + ARASAN_PRESENT_STATE)) & + ARASAN_PRESENT_STATE_CMD_INHIBIT); + + /* Set the argument register */ + writel(cmd->arg, host->base + ARASAN_ARG); + + /* Data present and must be transferred */ + if (likely(host->mrq->data)) { + cmdreg |= ARASAN_CMD_DATA_PRESENT; + if (cmd->flags & MMC_RSP_BUSY) + /* Wait for data inhibit */ + do { } while ((readl(host->base + + ARASAN_PRESENT_STATE)) & + ARASAN_PRESENT_STATE_DAT_INHIBIT); + } + + /* Write the Command */ + writew(cmdreg, host->base + ARASAN_CMD); + + DBG("\tcmd: 0x%x cmd reg: 0x%x - cmd->arg 0x%x, reg 0x%x\n", + cmdreg, readw(host->base + ARASAN_CMD), cmd->arg, + readl(host->base + ARASAN_ARG)); +} + +#ifdef ARASAN_DEBUG +static void arasan_adma_error(struct arasan_host *host) +{ + u8 status = readb(host->base + ARASAN_ADMA_ERR_STATUS); + + if (status & ARASAN_ADMA_ERROR_LENGTH) + pr_err("-ADMA Length Mismatch Error..."); + + if (status & ARASAN_ADMA_ERROR_ST_TFR) + pr_err("-Transfer Data Error desc: "); + else if (status & ARASAN_ADMA_ERROR_ST_FDS) + pr_err("-Fetch Data Error desc: "); + else if (status & ARASAN_ADMA_ERROR_ST_STOP) + pr_err("-Stop DMA Data Error desc: "); + + pr_err("0x%x", readl(host->base + ARASAN_ADMA_ADDRESS)); +} + +static void arasan_adma_dump_desc(u8 *desc) +{ + __le32 *dma; + __le16 *len; + u8 attr; + + pr_info("\tDescriptors:"); + + while (1) { + dma = (__le32 *) (desc + 4); + len = (__le16 *) (desc + 2); + attr = *desc; + + pr_info("\t\t%p: Buff 0x%08x, len %d, Attr 0x%02x\n", + desc, le32_to_cpu(*dma), le16_to_cpu(*len), attr); + + desc += 8; + + if (attr & 2) /* END of descriptor */ + break; + } +} +#else +static void arasan_adma_error(struct arasan_host *host) +{ +} + +static void arasan_adma_dump_desc(u8 *desc) +{ +} +#endif + +static int arasan_init_sg(struct arasan_host *host) +{ + + host->adma_desc = kmalloc((ARASAN_DMA_DESC_NUM * 2 + 1) * 4, + GFP_KERNEL); + + if (unlikely(host->adma_desc == NULL)) + return -ENOMEM; + + return 0; +} + +static void arasan_adma_table_pre(struct arasan_host *host, + struct mmc_data *data) +{ + int direction, i; + u8 *desc; + struct scatterlist *sg; + int len; + dma_addr_t addr; + + if (host->status == STATE_DATA_READ) + direction = DMA_FROM_DEVICE; + else + direction = DMA_TO_DEVICE; + + DBG("\t%s: sg entries %d\n", __func__, data->sg_len); + + host->sg_frags = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, direction); + desc = host->adma_desc; + + for_each_sg(data->sg, sg, host->sg_frags, i) { + addr = sg_dma_address(sg); + len = sg_dma_len(sg); + + DBG("\t\tFrag %d: addr 0x%x, len %d\n", i, addr, len); + + /* Preparing the descriptor */ + desc[7] = (addr >> 24) & 0xff; + desc[6] = (addr >> 16) & 0xff; + desc[5] = (addr >> 8) & 0xff; + desc[4] = (addr >> 0) & 0xff; + + desc[3] = (len >> 8) & 0xff; + desc[2] = (len >> 0) & 0xff; + + desc[1] = 0x00; + desc[0] = 0x21; + + desc += 8; + } + desc -= 8; + desc[0] = 0x23; + + arasan_adma_dump_desc(host->adma_desc); + + host->adma_addr = dma_map_single(mmc_dev(host->mmc), + host->adma_desc, + (ARASAN_DMA_DESC_NUM * 2 + 1) * 4, + DMA_TO_DEVICE); + + writel(host->adma_addr, host->base + ARASAN_ADMA_ADDRESS); +} + +static void arasan_adma_table_post(struct arasan_host *host, + struct mmc_data *data) +{ + int direction; + + if (host->status == STATE_DATA_READ) + direction = DMA_FROM_DEVICE; + else + direction = DMA_TO_DEVICE; + + DBG("\t%s\n", __func__); + + dma_unmap_single(mmc_dev(host->mmc), host->adma_addr, + (ARASAN_DMA_DESC_NUM * 2 + 1) * 4, DMA_TO_DEVICE); + + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction); +} + +static int arasan_setup_data(struct arasan_host *host) +{ + u16 blksz; + u16 xfer = 0; + struct mmc_data *data = host->mrq->data; + + DBG("%s:\n\t%s mode, data dir: %s; Buff=0x%08x," + "blocks=%d, blksz=%d\n", __func__, host->use_pio ? "PIO" : "DMA", + (data->flags & MMC_DATA_READ) ? "read" : "write", + (unsigned int)sg_virt(data->sg), data->blocks, data->blksz); + + /* Transfer Direction */ + if (data->flags & MMC_DATA_READ) { + xfer |= ARASAN_XFER_DATA_DIR; + host->status = STATE_DATA_READ; + } else { + xfer &= ~ARASAN_XFER_DATA_DIR; + host->status = STATE_DATA_WRITE; + } + + xfer |= ARASAN_XFER_BLK_COUNT_EN; + + if (data->blocks > 1) + xfer |= ARASAN_XFER_MULTI_BLK | ARASAN_XFER_AUTOCMD12; + + /* Set the block size register */ + blksz = ARASAN_BLOCK_SIZE_SDMA_512KB; + blksz |= (data->blksz & ARASAN_BLOCK_SIZE_TRANSFER); + blksz |= (data->blksz & 0x1000) ? ARASAN_BLOCK_SIZE_SDMA_8KB : 0; + + writew(blksz, host->base + ARASAN_BLK_SIZE); + + /* Set the block count register */ + writew(data->blocks, host->base + ARASAN_BLK_COUNT); + + /* PIO mode is used when 'pio' var is set by the user or no + * sdma is available from HC caps. */ + if (unlikely(host->use_pio || (host->cap.sdma == 0))) { + host->pio_blksz = data->blksz; + host->pio_blocks = data->blocks; + host->pio_blkbuf = sg_virt(data->sg); + } else { + dma_addr_t phys_addr; + + /* Enable DMA */ + xfer |= ARASAN_XFER_DMA_EN; + + /* Scatter list init */ + host->sg_frags = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, + (host->status & STATE_DATA_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + + phys_addr = sg_dma_address(data->sg); + + if (likely(host->adma)) { + /* Set the host control register dma bits for adma + * if supported and enabled by user. */ + host->ctrl |= ARASAN_HOST_CTRL_ADMA2_32; + + /* Prepare ADMA table */ + arasan_adma_table_pre(host, data); + } else { + /* SDMA Mode selected (default mode) */ + host->ctrl &= ~ARASAN_HOST_CTRL_ADMA2_64; + + writel((unsigned int)phys_addr, + host->base + ARASAN_SDMA_SYS_ADDR); + } + writeb(host->ctrl, host->base + ARASAN_HOST_CTRL); + + } + /* Set the data transfer mode register */ + writew(xfer, host->base + ARASAN_XFER_MODE); + + DBG("\tHC Reg [xfer 0x%x] [blksz 0x%x] [blkcount 0x%x] [CRTL 0x%x]\n", + readw(host->base + ARASAN_XFER_MODE), + readw(host->base + ARASAN_BLK_SIZE), + readw(host->base + ARASAN_BLK_COUNT), + readb(host->base + ARASAN_HOST_CTRL)); + + return 0; +} + +static void arasan_finish_data(struct arasan_host *host) +{ + struct mmc_data *data = host->mrq->data; + + DBG("\t%s\n", __func__); + + if (unlikely(host->pio_blkbuf)) { + host->pio_blksz = 0; + host->pio_blocks = 0; + host->pio_blkbuf = NULL; + } else { + if (likely(host->adma)) { + arasan_adma_table_post(host, data); + } else { + dma_unmap_sg(mmc_dev(host->mmc), data->sg, + host->sg_frags, + (host->status & STATE_DATA_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + } + } + + data->bytes_xfered = data->blocks * data->blksz; + host->status = STATE_CMD; +} + +static int arasan_finish_cmd(unsigned int err_status, unsigned int status, + unsigned int opcode) +{ + int ret = 0; + + if (unlikely(err_status)) { + if (err_status & ARASAN_CMD_TIMEOUT) { + DBG("\tcmd_timeout...\n"); + ret = -ETIMEDOUT; + } + if (err_status & ARASAN_CMD_CRC_ERROR) { + DBG("\tcmd_crc_error...\n"); + ret = -EILSEQ; + } + if (err_status & ARASAN_CMD_END_BIT_ERROR) { + DBG("\tcmd_end_bit_error...\n"); + ret = -EILSEQ; + } + if (err_status & ARASAN_CMD_INDEX_ERROR) { + DBG("\tcmd_index_error...\n"); + ret = -EILSEQ; + } + } + if (likely(status & ARASAN_N_CMD_COMPLETE)) + DBG("\tCommand (CMD%u) Completed irq...\n", opcode); + + return ret; +} + +/* Enable/Disable Normal and Error interrupts */ +static void aranan_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + unsigned long flags; + struct arasan_host *host = mmc_priv(mmc); + + DBG("%s: %s CARD_IRQ\n", __func__, enable ? "enable" : "disable"); + + spin_lock_irqsave(&host->lock, flags); + if (enable) + host->intr_en = ARASAN_IRQ_DEFAULT_MASK; + else + host->intr_en = 0; + + writel(host->intr_en, host->base + ARASAN_NORMAL_INT_STATUS_EN); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void arasan_timeout_timer(unsigned long data) +{ + struct arasan_host *host = (struct arasan_host *)data; + struct mmc_request *mrq; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if ((host->mrq) && (host->status == STATE_CMD)) { + mrq = host->mrq; + + pr_debug("%s: Timeout waiting for hardware interrupt.\n", + mmc_hostname(host->mmc)); + + writel(0xffffffff, host->base + ARASAN_NORMAL_INT_STATUS); + aranan_enable_sdio_irq(host->mmc, 1); + + if (mrq->data) { + arasan_finish_data(host); + arsan_sw_reset(host, reset_dat_line); + mrq->data->error = -ETIMEDOUT; + } + if (likely(mrq->cmd)) { + mrq->cmd->error = -ETIMEDOUT; + arsan_sw_reset(host, reset_cmd_line); + arasan_get_resp(mrq->cmd, host); + } + arasan_ctrl_led(host, 0); + host->mrq = NULL; + mmc_request_done(host->mmc, mrq); + } + spin_unlock_irqrestore(&host->lock, flags); +} + +/* Process requests from the MMC layer */ +static void arasan_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct arasan_host *host = mmc_priv(mmc); + struct mmc_command *cmd = mrq->cmd; + unsigned long flags; + + BUG_ON(host->mrq != NULL); + + spin_lock_irqsave(&host->lock, flags); + + DBG(">>> araran_request:\n"); + /* Check that there is a card in the slot */ + if (unlikely(arasan_test_card(host) < 0)) { + DBG("%s: Error: No card present...\n", mmc_hostname(host->mmc)); + + mrq->cmd->error = -ENOMEDIUM; + mmc_request_done(mmc, mrq); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + host->mrq = mrq; + + host->status = STATE_CMD; + if (likely(mrq->data)) + arasan_setup_data(host); + + /* Turn-on/off the LED when send/complete a cmd */ + arasan_ctrl_led(host, 1); + + arasan_start_cmd(host, cmd); + + mod_timer(&host->timer, jiffies + 5 * HZ); + + DBG("<<< araran_request done!\n"); + spin_unlock_irqrestore(&host->lock, flags); +} + +static int arasan_get_ro(struct mmc_host *mmc) +{ + struct arasan_host *host = mmc_priv(mmc); + + u32 ro = readl(host->base + ARASAN_PRESENT_STATE); + if (!(ro & ARASAN_PRESENT_STATE_WR_EN)) + return 1; + + return 0; +} + +/* I/O bus settings (MMC clock/power ...) */ +static void arasan_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct arasan_host *host = mmc_priv(mmc); + u8 ctrl_reg = readb(host->base + ARASAN_HOST_CTRL); + + DBG("%s: pwr %d, clk %d, vdd %d, bus_width %d, timing %d\n", + __func__, ios->power_mode, ios->clock, ios->vdd, ios->bus_width, + ios->timing); + + /* Set the power supply mode */ + if (ios->power_mode == MMC_POWER_OFF) + arasan_power_set(host, 0, ios->vdd); + else + arasan_power_set(host, 1, ios->vdd); + + /* Timing (high speed supported?) */ + if ((ios->timing == MMC_TIMING_MMC_HS || + ios->timing == MMC_TIMING_SD_HS) && host->cap.high_speed) + ctrl_reg |= ARASAN_HOST_CTRL_HIGH_SPEED; + + /* Clear the current bus width configuration */ + ctrl_reg &= ~ARASAN_HOST_CTRL_SD_MASK; + + /* Set SD bus bit mode */ + switch (ios->bus_width) { + case MMC_BUS_WIDTH_8: + ctrl_reg |= ARASAN_HOST_CTRL_SD8; + break; + case MMC_BUS_WIDTH_4: + ctrl_reg |= ARASAN_HOST_CTRL_SD; + break; + } + + /* Default to maximum timeout */ + writeb(0x0e, host->base + ARASAN_TIMEOUT_CTRL); + + /* Disable Card Interrupt in Host in case we change + * the Bus Width. */ + aranan_enable_sdio_irq(host->mmc, 0); + + host->ctrl = ctrl_reg; + writeb(host->ctrl, host->base + ARASAN_HOST_CTRL); + + aranan_enable_sdio_irq(host->mmc, 1); + + /* Set clock */ + arasan_set_clock(host, ios->clock); +} + +/* Tasklet for Card-detection */ +static void arasan_tasklet_card(unsigned long data) +{ + unsigned long flags; + struct arasan_host *host = (struct arasan_host *)data; + + spin_lock_irqsave(&host->lock, flags); + + if (likely((readl(host->base + ARASAN_PRESENT_STATE) & + ARASAN_PRESENT_STATE_CARD_PRESENT))) { + if (host->mrq) { + pr_err("%s: Card removed during transfer!\n", + mmc_hostname(host->mmc)); + /* Reset cmd and dat lines */ + arsan_sw_reset(host, reset_cmd_line); + arsan_sw_reset(host, reset_dat_line); + + if (likely(host->mrq->cmd)) { + host->mrq->cmd->error = -ENOMEDIUM; + mmc_request_done(host->mmc, host->mrq); + } + } + } + + spin_unlock_irqrestore(&host->lock, flags); + + if (likely(host->mmc)) + mmc_detect_change(host->mmc, msecs_to_jiffies(200)); +} + +static irqreturn_t arasan_irq(int irq, void *dev) +{ + struct arasan_host *host = dev; + unsigned int status, err_status, handled = 0; + struct mmc_command *cmd = NULL; + struct mmc_data *data = NULL; + + spin_lock(&host->lock); + + /* Interrupt Status */ + status = readl(host->base + ARASAN_NORMAL_INT_STATUS); + err_status = (status >> 16) & 0xffff; + + DBG("%s: Normal IRQ status 0x%x, Error status 0x%x\n", + __func__, status & 0xffff, err_status); + + if ((!host->need_poll) && + ((status & ARASAN_N_CARD_REMOVAL) || + (status & ARASAN_N_CARD_INS))) + tasklet_schedule(&host->card_tasklet); + + if (unlikely(!host->mrq)) + goto out; + + cmd = host->mrq->cmd; + data = host->mrq->data; + + cmd->error = 0; + /* Check for any CMD interrupts */ + if (likely(status & ARASAN_INT_CMD_MASK)) { + + cmd->error = arasan_finish_cmd(err_status, status, cmd->opcode); + if (cmd->error) + arsan_sw_reset(host, reset_cmd_line); + + if ((host->status == STATE_CMD) || cmd->error) { + arasan_get_resp(cmd, host); + + handled = 1; + } + } + + /* Check for any data interrupts */ + if (likely((status & ARASAN_INT_DATA_MASK)) && data) { + data->error = 0; + if (unlikely(err_status)) { + if (err_status & ARASAN_DATA_TIMEOUT_ERROR) { + DBG("\tdata_timeout_error...\n"); + data->error = -ETIMEDOUT; + } + if (err_status & ARASAN_DATA_CRC_ERROR) { + DBG("\tdata_crc_error...\n"); + data->error = -EILSEQ; + } + if (err_status & ARASAN_DATA_END_ERROR) { + DBG("\tdata_end_error...\n"); + data->error = -EILSEQ; + } + if (err_status & ARASAN_AUTO_CMD12_ERROR) { + unsigned int err_cmd12 = + readw(host->base + ARASAN_CMD12_ERR_STATUS); + + DBG("\tc12err 0x%04x\n", err_cmd12); + + if (err_cmd12 & ARASAN_AUTOCMD12_ERR_NOTEXE) + data->stop->error = -ENOEXEC; + + if ((err_cmd12 & ARASAN_AUTOCMD12_ERR_TIMEOUT) + && !(err_cmd12 & ARASAN_AUTOCMD12_ERR_CRC)) + /* Timeout Error */ + data->stop->error = -ETIMEDOUT; + else if (!(err_cmd12 & + ARASAN_AUTOCMD12_ERR_TIMEOUT) + && (err_cmd12 & + ARASAN_AUTOCMD12_ERR_CRC)) + /* CRC Error */ + data->stop->error = -EILSEQ; + else if ((err_cmd12 & + ARASAN_AUTOCMD12_ERR_TIMEOUT) + && (err_cmd12 & + ARASAN_AUTOCMD12_ERR_CRC)) + DBG("\tCMD line Conflict\n"); + } + arsan_sw_reset(host, reset_dat_line); + handled = 1; + } else { + if (likely(((status & ARASAN_N_BUFF_READ) || + status & ARASAN_N_BUFF_WRITE))) { + DBG("\tData R/W interrupts...\n"); + arasan_data_pio(host); + } + + if (likely(status & ARASAN_N_DMA_IRQ)) + DBG("\tDMA interrupts...\n"); + + if (likely(status & ARASAN_N_TRANS_COMPLETE)) { + DBG("\tData XFER completed interrupts...\n"); + arasan_finish_data(host); + if (data->stop) { + u32 opcode = data->stop->opcode; + data->stop->error = + arasan_finish_cmd(err_status, + status, opcode); + arasan_get_resp(data->stop, host); + } + handled = 1; + } + } + } + if (err_status & ARASAN_ADMA_ERROR) { + DBG("\tADMA Error...\n"); + arasan_adma_error(host); + cmd->error = -EIO; + } + if (err_status & ARASAN_CURRENT_LIMIT_ERROR) { + DBG("\tPower Fail...\n"); + cmd->error = -EIO; + } + + if (likely(host->mrq && handled)) { + struct mmc_request *mrq = host->mrq; + + arasan_ctrl_led(host, 0); + + del_timer(&host->timer); + + host->mrq = NULL; + DBG("\tcalling mmc_request_done...\n"); + mmc_request_done(host->mmc, mrq); + } +out: + DBG("\tclear status and exit...\n"); + writel(status, host->base + ARASAN_NORMAL_INT_STATUS); + + spin_unlock(&host->lock); + + return IRQ_HANDLED; +} + +static void arasan_setup_hc(struct arasan_host *host) +{ + /* Clear all the interrupts before resetting */ + arasan_clear_interrupts(host); + + /* Reset All and get the HC version */ + arsan_sw_reset(host, reset_all); + + /* Print HC version and SPEC */ + arsan_hc_version(host); + + /* Set capabilities and print theri info */ + arasan_capabilities(host); + + /* Enable interrupts */ + arasan_set_interrupts(host); +} + +static const struct mmc_host_ops arasan_ops = { + .request = arasan_request, + .get_ro = arasan_get_ro, + .set_ios = arasan_set_ios, + .enable_sdio_irq = aranan_enable_sdio_irq, +}; + +static int __init arasan_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc = NULL; + struct arasan_host *host = NULL; + const struct arasan_platform_data *arasan_data; + struct resource *r; + int ret, irq; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + irq = platform_get_irq_byname(pdev, "mmcirq"); + + arasan_data = pdev->dev.platform_data; + + if (!r || irq < 0 || !arasan_data) + return -ENXIO; + + r = request_mem_region(r->start, resource_size(r), pdev->name); + if (!r) { + pr_err("%s: ERROR: memory allocation failed\n", __func__); + return -EBUSY; + goto out; + } + /* Allocate the mmc_host with private data size */ + mmc = mmc_alloc_host(sizeof(struct arasan_host), &pdev->dev); + if (!mmc) { + pr_err("%s: ERROR: mmc_alloc_host failed\n", __func__); + ret = -ENOMEM; + goto out; + } + + /* Verify resource from the platform */ + ret = arasan_claim_resource(pdev); + if (ret < 0) + goto out; + + host = mmc_priv(mmc); + host->mmc = mmc; + host->dev = &pdev->dev; + host->res = r; + + host->need_poll = arasan_data->need_poll; + if (host->need_poll) { + mmc->caps |= MMC_CAP_NEEDS_POLL; + DBG("\tHC needs polling to detect the card..."); + } else + /* no set the MMC_CAP_NEEDS_POLL in cap */ + tasklet_init(&host->card_tasklet, arasan_tasklet_card, + (unsigned long)host); + + host->base = ioremap(r->start, resource_size(r)); + if (!host->base) { + pr_err("%s: ERROR: memory mapping failed\n", __func__); + ret = -ENOMEM; + goto out; + } + + ret = + request_irq(irq, arasan_irq, IRQF_SHARED, ARASAN_DRIVER_NAME, host); + if (ret) { + pr_err("%s: cannot assign irq %d\n", __func__, irq); + goto out; + } else + host->irq = irq; + + spin_lock_init(&host->lock); + + /* Setup the Host Controller according to its capabilities */ + arasan_setup_hc(host); + + mmc->ops = &arasan_ops; + + if (host->cap.voltage33) + mmc->ocr_avail |= MMC_VDD_32_33 | MMC_VDD_33_34; + if (host->cap.voltage30) + mmc->ocr_avail |= MMC_VDD_29_30; + if (host->cap.voltage18) + mmc->ocr_avail |= MMC_VDD_165_195; + + mmc->caps = MMC_CAP_SDIO_IRQ; + mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA; + + if (host->cap.high_speed) + mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; + + host->freq = host->cap.timer_freq * 1000000; + host->use_pio = pio; + mmc->f_max = maxfreq; + mmc->f_min = mmc->f_max / 256; + + /* + * Maximum block size. This is specified in the capabilities register. + */ + mmc->max_blk_size = host->cap.max_blk_len; + mmc->max_blk_count = 65535; + + mmc->max_hw_segs = 1; + mmc->max_phys_segs = 128; + mmc->max_seg_size = 65535; + mmc->max_req_size = 524288; + + /* Passing the "pio" option, we force the driver to not + * use any DMA engines. */ + if (unlikely(host->use_pio)) { + adma = 0; + pr_debug("\tPIO mode\n"); + } else { + if (likely(adma)) { + /* Turn-on the ADMA if supported by the HW + * or Fall back to SDMA in case of failures */ + pr_debug("\tADMA mode\n"); + ret = arasan_init_sg(host); + if (unlikely(ret)) { + pr_warning("\tSG init failed (disable ADMA)\n"); + adma = 0; + } else + /* Set the Maximum number of segments + * becasue we can do scatter/gathering in ADMA + * mode. */ + mmc->max_hw_segs = 128; + } else + pr_debug("\tSDMA mode\n"); + } + host->adma = adma; + + platform_set_drvdata(pdev, mmc); + ret = mmc_add_host(mmc); + if (ret) + goto out; + + setup_timer(&host->timer, arasan_timeout_timer, (unsigned long)host); + + pr_info("%s: driver initialized... IRQ: %d, Base addr 0x%x\n", + mmc_hostname(mmc), irq, (unsigned int)host->base); + +#ifdef ARASAN_DEBUG + led = 1; +#endif + return 0; +out: + if (host) { + if (host->irq) + free_irq(host->irq, host); + if (host->base) + iounmap(host->base); + } + if (r) + release_resource(r); + if (mmc) + mmc_free_host(mmc); + + return ret; +} + +static int __exit arasan_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + + if (mmc) { + struct arasan_host *host = mmc_priv(mmc); + + arasan_clear_interrupts(host); + if (!host->need_poll) + tasklet_kill(&host->card_tasklet); + mmc_remove_host(mmc); + free_irq(host->irq, host); + arasan_power_set(host, 0, -1); + iounmap(host->base); + if (likely(host->adma)) + kfree(host->adma_desc); + release_resource(host->res); + mmc_free_host(mmc); + } + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +static int arasan_suspend(struct platform_device *dev, pm_message_t state) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + struct arasan_host *host = mmc_priv(mmc); + int ret = 0; + + if (mmc && host->cap.suspend) + ret = mmc_suspend_host(mmc); + + return ret; +} + +static int arasan_resume(struct platform_device *dev) +{ + struct mmc_host *mmc = platform_get_drvdata(dev); + struct arasan_host *host = mmc_priv(mmc); + int ret = 0; + + if (mmc && host->cap.suspend) + ret = mmc_resume_host(mmc); + + return ret; +} +#endif + +static struct platform_driver arasan_driver = { + .remove = __exit_p(arasan_remove), +#ifdef CONFIG_PM + .suspend = arasan_suspend, + .resume = arasan_resume, +#endif + .driver = { + .name = ARASAN_DRIVER_NAME, + }, +}; + +static int __init arasan_init(void) +{ + return platform_driver_probe(&arasan_driver, arasan_probe); +} + +static void __exit arasan_exit(void) +{ + platform_driver_unregister(&arasan_driver); +} + +#ifndef MODULE +static int __init arasan_cmdline_opt(char *str) +{ + char *opt; + + if (!str || !*str) + return -EINVAL; + + while ((opt = strsep(&str, ",")) != NULL) { + if (!strncmp(opt, "maxfreq:", 8)) + strict_strtoul(opt + 8, 0, (unsigned long *)&maxfreq); + else if (!strncmp(opt, "adma:", 5)) + strict_strtoul(opt + 5, 0, (unsigned long *)&adma); + else if (!strncmp(opt, "led:", 4)) + strict_strtoul(opt + 4, 0, (unsigned long *)&led); + else if (!strncmp(opt, "pio:", 4)) + strict_strtoul(opt + 4, 0, (unsigned long *)&pio); + } + return 0; +} + +__setup("arasanmmc=", arasan_cmdline_opt); +#endif + +module_init(arasan_init); +module_exit(arasan_exit); + +MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@xxxxxx>"); +MODULE_DESCRIPTION("Arasan MMC/SD/SDIO Host Controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/arasan.h b/drivers/mmc/host/arasan.h new file mode 100644 index 0000000..56a4f8b --- /dev/null +++ b/drivers/mmc/host/arasan.h @@ -0,0 +1,237 @@ +/* + * Author: Giuseppe Cavallaro <peppe.cavallaro@xxxxxx> + * + * Copyright (C) 2010 STMicroelectronics Ltd + * + * 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 __ARASAN_H +#define __ARASAN_H + +#define ARASAN_CLOCKRATE_MAX 50000000 +#define ARASAN_DRIVER_NAME "arasan" +#define ARASAN_DMA_DESC_NUM 128 + +/* + * Register offsets + */ +#define ARASAN_SDMA_SYS_ADDR 0x000 +#define ARASAN_BLK_SIZE 0x004 +#define ARASAN_BLK_COUNT 0x006 +#define ARASAN_ARG 0x008 +#define ARASAN_XFER_MODE 0x00c +#define ARASAN_CMD 0x00e +#define ARASAN_RSP(i) (0x010 + ((i)<<2)) +#define ARASAN_RSP0 0x010 +#define ARASAN_RSP1 0x012 +#define ARASAN_RSP2 0x014 +#define ARASAN_RSP3 0x016 +#define ARASAN_RSP4 0x018 +#define ARASAN_RSP5 0x01a +#define ARASAN_RSP6 0x01c +#define ARASAN_RSP7 0x01e +#define ARASAN_BUFF 0x020 +#define ARASAN_PRESENT_STATE 0x024 +#define ARASAN_HOST_CTRL 0x028 +#define ARASAN_PWR_CTRL 0x029 +#define ARASAN_GAP_CTRL 0x02a +#define ARASAN_GAP_WAKEUP 0x02b +#define ARASAN_CLOCK_CTRL 0x02c +#define ARASAN_TIMEOUT_CTRL 0x02e +#define ARASAN_SW_RESET 0x02f + +#define ARASAN_NORMAL_INT_STATUS 0x030 +#define ARASAN_ERR_INT_STATUS 0x032 +#define ARASAN_NORMAL_INT_STATUS_EN 0x034 +#define ARASAN_ERR_INT_STATUS_EN 0x036 +#define ARASAN_NORMAL_INT_SIGN_EN 0x038 +#define ARASAN_ERR_INT_SIGN_EN 0x03a + +#define ARASAN_CMD12_ERR_STATUS 0x03c + +#define ARASAN_CAPABILITIES 0x040 + +#define ARASAN_ADMA_ERR_STATUS 0x054 +#define ARASAN_ADMA_ADDRESS 0x058 + +#define ARASAN_SPI_INT_SUPPORT 0x0f0 +#define ARASAN_HOST_VERSION 0x0fe + +/* Error Interrupt Status Register */ +#define ARASAN_CMD_TIMEOUT (1 << 0) +#define ARASAN_CMD_CRC_ERROR (1 << 1) +#define ARASAN_CMD_END_BIT_ERROR (1 << 2) +#define ARASAN_CMD_INDEX_ERROR (1 << 3) +#define ARASAN_DATA_TIMEOUT_ERROR (1 << 4) +#define ARASAN_DATA_CRC_ERROR (1 << 5) +#define ARASAN_DATA_END_ERROR (1 << 6) +#define ARASAN_CURRENT_LIMIT_ERROR (1 << 7) +#define ARASAN_AUTO_CMD12_ERROR (1 << 8) +#define ARASAN_ADMA_ERROR (1 << 9) +#define ARASAN_TARGET_RESP_ERROR (1 << 12) +#define ARASAN_CEATA_ERROR (1 << 13) + +/* Error Interrupt Status ENABLE reg. (0- Masked, 1: Enabled) */ +#define ARASAN_E_EN_CMD_TIMEOUT (1 << 0) +#define ARASAN_E_EN_CMD_CRC_ERROR (1 << 1) +#define ARASAN_E_EN_CMD_END_BIT_ERROR (1 << 2) +#define ARASAN_E_EN_CMD_INDEX_ERROR (1 << 3) +#define ARASAN_E_EN_DATA_TIMEOUT_ERROR (1 << 4) +#define ARASAN_E_EN_DATA_CRC_ERROR (1 << 5) +#define ARASAN_E_EN_DATA_END_ERROR (1 << 6) +#define ARASAN_E_EN_CURRENT_LIMIT_ERROR (1 << 7) +#define ARASAN_E_EN_AUTO_CMD12_ERROR (1 << 8) +#define ARASAN_E_EN_ADMA_ERROR (1 << 9) +#define ARASAN_E_EN_TARGET_RESP_ERROR (1 << 12) +#define ARASAN_E_EN_CEATA_ERROR (1 << 13) + +/* Normal Interrupt Status Register */ +#define ARASAN_N_CMD_COMPLETE (1 << 0) +#define ARASAN_N_TRANS_COMPLETE (1 << 1) +#define ARASAN_N_BLK_GAP_EVENT (1 << 2) +#define ARASAN_N_DMA_IRQ (1 << 3) +#define ARASAN_N_BUFF_WRITE (1 << 4) +#define ARASAN_N_BUFF_READ (1 << 5) +#define ARASAN_N_CARD_INS (1 << 6) +#define ARASAN_N_CARD_REMOVAL (1 << 7) +#define ARASAN_N_CARD_IRQ (1 << 8) +#define ARASAN_N_ERROR_IRQ (1 << 15) + +/* Normal Interrupt Status ENABLE reg. (0- Masked, 1: Enabled) */ +#define ARASAN_N_EN_CMD_COMPLETE (1 << 0) +#define ARASAN_N_EN_TRANS_COMPL (1 << 1) +#define ARASAN_N_EN_BLOCK_GAP (1 << 2) +#define ARASAN_N_EN_DMA_IRQ (1 << 3) +#define ARASAN_N_EN_BUFF_WRITE (1 << 4) +#define ARASAN_N_EN_BUFF_READ (1 << 5) +#define ARASAN_N_EN_CARD_INS (1 << 6) +#define ARASAN_N_EN_CARD_REM (1 << 7) +#define ARASAN_N_EN_CARD_IRQ (1 << 8) + +/* Default Enable Normal/Error interrupt mask */ +#define ARASAN_IRQ_DEFAULT_MASK 0x02ff00fb + +/* Mask normal and error fields */ +#define ARASAN_INT_DATA_MASK 0x0070003a +#define ARASAN_INT_CMD_MASK 0x000f0001 + +/* Command Register */ +#define ARASAN_CMD_RSP_NONE (0 << 0) +#define ARASAN_CMD_RSP_136 (1 << 0) +#define ARASAN_CMD_RSP_48 (2 << 0) +#define ARASAN_CMD_RSP_48BUSY (3 << 0) +#define ARASAN_CMD_CHECK_CMDCRC (1 << 3) +#define ARASAN_CMD_INDX_CHECK (1 << 4) +#define ARASAN_CMD_DATA_PRESENT (1 << 5) +#define ARASAN_COMMAD_TYPE_NORM (0 << 6) +#define ARASAN_COMMAD_TYPE_SUSP (1 << 6) +#define ARASAN_COMMAD_TYPE_RESU (2 << 6) +#define ARASAN_COMMAD_TYPE_ABOR (3 << 6) +#define ARASAN_CMD_INDEX(x) ((x) << 8) + +/* Transfer Mode Register */ +#define ARASAN_XFER_DMA_EN (1 << 0) +#define ARASAN_XFER_BLK_COUNT_EN (1 << 1) +#define ARASAN_XFER_AUTOCMD12 (1 << 2) /* 1: Enable */ +#define ARASAN_XFER_DATA_DIR (1 << 4) /* 0: Write, 1: Read */ +#define ARASAN_XFER_MULTI_BLK (1 << 5) /* 0: Single 1: Multi */ +#define ARASAN_XFER_SPI_MODE (1 << 7) /* 1: SPI 0: SD Mode */ + +enum xfer_dat_cmd_status { + STATE_CMD = 0, + STATE_DATA_WRITE = 1, + STATE_DATA_READ = 2, + STATE_DATA_STOP = 2, +}; + +/* Software Reset */ +#define ARSAN_RESET_ALL 0x1 +#define ARSAN_RESET_CMD_LINE 0x2 +#define ARSAN_RESET_DAT_LINE 0x4 + +enum sw_reset_cmd { + reset_all = 0, + reset_cmd_line = 1, + reset_dat_line = 2, +}; + +/* Host Control Register */ +#define ARASAN_HOST_CTRL_LED (1 << 0) +#define ARASAN_HOST_CTRL_SD (1 << 1) /* 1: 4 bit mode */ +#define ARASAN_HOST_CTRL_HIGH_SPEED (1 << 2) +#define ARASAN_HOST_CTRL_SDMA_SEL (0 << 3) +#define ARASAN_HOST_CTRL_ADMA1 (1 << 3) +#define ARASAN_HOST_CTRL_ADMA2_32 (2 << 3) +#define ARASAN_HOST_CTRL_ADMA2_64 (3 << 3) +#define ARASAN_HOST_CTRL_SD8 (1 << 5) +#define ARASAN_HOST_CTRL_CARD_LEV_TEST (1 << 6) +#define ARASAN_HOST_CTRL_CARD_SIG_TEST (1 << 7) + +#define ARASAN_HOST_CTRL_SD_MASK 0x22 + +/* Clock Control Register */ +#define ARASAN_CLOCK_CTRL_SDCLK_MASK 0xff00 +#define ARASAN_CLOCK_CTRL_SDCLK_SHIFT 7 +#define ARASAN_CLOCK_CTRL_SDCLK_256 0x8000 +#define ARASAN_CLOCK_CTRL_SDCLK_128 0x4000 +#define ARASAN_CLOCK_CTRL_SDCLK_64 0x2000 +#define ARASAN_CLOCK_CTRL_SDCLK_32 0x1000 +#define ARASAN_CLOCK_CTRL_SDCLK_16 0x0800 +#define ARASAN_CLOCK_CTRL_SDCLK_8 0x0400 +#define ARASAN_CLOCK_CTRL_SDCLK_4 0x0200 +#define ARASAN_CLOCK_CTRL_SDCLK_2 0x0100 +#define ARASAN_CLOCK_CTRL_SDCLK_1 0x0000 +#define ARASAN_CLOCK_CTRL_SDCLK_ENABLE (1 << 2) +#define ARASAN_CLOCK_CTRL_ICLK_STABLE (1 << 1) +#define ARASAN_CLOCK_CTRL_ICLK_ENABLE (1 << 0) + +/* Power Control Register */ +#define ARASAN_PWR_CTRL_UP (1 << 0) /* 1: Power-On */ +#define ARASAN_PWR_BUS_VOLTAGE_33 (7 << 1) +#define ARASAN_PWR_BUS_VOLTAGE_30 (6 << 1) +#define ARASAN_PWR_BUS_VOLTAGE_18 (5 << 1) + +/* CMD12 error status bits */ +#define ARASAN_AUTOCMD12_ERR_NOTEXE (1 << 0) +#define ARASAN_AUTOCMD12_ERR_TIMEOUT (1 << 1) +#define ARASAN_AUTOCMD12_ERR_CRC (1 << 2) +#define ARASAN_AUTOCMD12_ERR_ENDBIT (1 << 3) +#define ARASAN_AUTOCMD12_ERR_INDEX (1 << 4) +#define ARASAN_AUTOCMD12_ERR_NOT_ISSUED (1 << 7) + +/* Present State Register */ +#define ARASAN_PRESENT_STATE_DAT7_4 0x1e000000 +#define ARASAN_PRESENT_STATE_CMD_LINE 0x01000000 +#define ARASAN_PRESENT_STATE_DAT3_0 0x00f00000 +#define ARASAN_PRESENT_STATE_WR_EN 0x00080000 +#define ARASAN_PRESENT_STATE_CARD_DETECT 0x00040000 +#define ARASAN_PRESENT_STATE_CARD_STABLE 0x00020000 +#define ARASAN_PRESENT_STATE_CARD_PRESENT 0x00010000 +#define ARASAN_PRESENT_STATE_BUFFER_RD_EN 0x00000800 +#define ARASAN_PRESENT_STATE_BUFFER_WR_EN 0x00000400 +#define ARASAN_PRESENT_STATE_RD_ACTIVE 0x00000200 +#define ARASAN_PRESENT_STATE_WR_ACTIVE 0x00000100 +#define ARASAN_PRESENT_STATE_DAT_ACTIVE 0x00000004 +#define ARASAN_PRESENT_STATE_DAT_INHIBIT 0x00000002 +#define ARASAN_PRESENT_STATE_CMD_INHIBIT 0x00000001 + +/* Block size register defines */ +#define ARASAN_BLOCK_SIZE_SDMA_512KB 0x7000 +#define ARASAN_BLOCK_SIZE_SDMA_256KB 0x6000 +#define ARASAN_BLOCK_SIZE_SDMA_128KB 0x5000 +#define ARASAN_BLOCK_SIZE_SDMA_64KB 0x4000 +#define ARASAN_BLOCK_SIZE_SDMA_32KB 0x3000 +#define ARASAN_BLOCK_SIZE_SDMA_16KB 0x2000 +#define ARASAN_BLOCK_SIZE_SDMA_8KB 0x1000 +#define ARASAN_BLOCK_SIZE_SDMA_4KB 0x0000 +#define ARASAN_BLOCK_SIZE_TRANSFER 0x0fff + +/* ADMA Error Status Register */ +#define ARASAN_ADMA_ERROR_LENGTH 0x04 +#define ARASAN_ADMA_ERROR_ST_TFR 0x03 +#define ARASAN_ADMA_ERROR_ST_FDS 0x01 +#define ARASAN_ADMA_ERROR_ST_STOP 0x00 +#endif diff --git a/include/linux/mmc/arasan_plat.h b/include/linux/mmc/arasan_plat.h new file mode 100644 index 0000000..9e16287 --- /dev/null +++ b/include/linux/mmc/arasan_plat.h @@ -0,0 +1,49 @@ +/* + * Author: Giuseppe Cavallaro <peppe.cavallaro@xxxxxx> + * + * include/linux/mmc/arsan_plat.h + * + * platform data for the Arasan MMC/SD/SDI HC driver + * + * Copyright (C) 2010 STMicroelectronics Ltd + * + * 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. + * + */ + +#ifndef __ARASAN_PLAT_H__ +#define __ARASAN_PLAT_H__ + +struct arasan_platform_data { + unsigned int need_poll; +#ifdef CONFIG_STM_DRIVERS + struct stm_pad_config *pad_config; +#endif +}; + +/* ARASAN Resource configuration */ +#ifdef CONFIG_STM_DRIVERS +#include <linux/stm/platform.h> +#include <linux/stm/pad.h> +static inline int arasan_claim_resource(struct platform_device *pdev) +{ + int ret = 0; + struct arasan_platform_data *plat_dat = pdev->dev.platform_data; + + /* Pad routing setup required on STM platforms */ + if (!devm_stm_pad_claim(&pdev->dev, plat_dat->pad_config, + dev_name(&pdev->dev))) { + pr_err("%s: Failed to request pads!\n", __func__); + ret = -ENODEV; + } + return ret; +} +#else +static inline int arasan_claim_resource(struct platform_device *pdev) +{ + return 0; +} +#endif +#endif -- 1.5.5.6 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html