> -----Original Message----- > From: Giuseppe CAVALLARO [mailto:peppe.cavallaro@xxxxxx] > Sent: Wednesday, July 28, 2010 8:39 AM > To: linux-mmc@xxxxxxxxxxxxxxx > Cc: Peppe CAVALLARO > Subject: [PATCH] mmc: add the MMC/SD/SDIO Arasan host controller > > 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> Hello! Just a few notes, I hope useful for reviewing the driver: - its configuration depends on ST40 CPU because I have only tested it on STM platforms (ST40 based). At any rate, the driver builds fine on x86 machine against the latest Kernel from git. - I've added the linux/mmc/arasan_plat.h for the driver's platform information and the drivers/mmc/host/arasan.h header file. I've voluntarily preferred to split the two headers avoiding to include the header comes from ../../../driver/mmc/host within our include/linux/stm/platform.h. I don't know if it's the best solution and if you like it. I did the same for the stmmac driver in net-2.6, in the past. - No problems while running the mmc_test.ko. Welcome advice and feedback. Best Regards, Giuseppe > --- > 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