summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/s3c-hsmmc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host/s3c-hsmmc.c')
-rw-r--r--drivers/mmc/host/s3c-hsmmc.c1457
1 files changed, 1457 insertions, 0 deletions
diff --git a/drivers/mmc/host/s3c-hsmmc.c b/drivers/mmc/host/s3c-hsmmc.c
new file mode 100644
index 000000000000..21986a2c72f7
--- /dev/null
+++ b/drivers/mmc/host/s3c-hsmmc.c
@@ -0,0 +1,1457 @@
+/* -*- linux-c -*-
+ *
+ * linux/drivers/mmc/s3c-hsmmc.c - Samsung S3C24XX HS-MMC driver
+ *
+ * $Id: s3c-hsmmc.c,v 1.18 2007/06/07 07:14:57 scsuh Exp $
+ *
+ * Copyright (C) 2006 Samsung Electronics, All Rights Reserved.
+ * by Seung-Chull, Suh <sc.suh@samsung.com>
+ *
+ * This driver is made for High Speed MMC interface. This interface
+ * is adopted and implemented since s3c2443 was made.
+ *
+ * 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.
+ *
+ * Modified by Ryu,Euiyoul <steven.ryu@samsung.com>
+ * Modified by Seung-chull, Suh to support s3c6400
+ * Modified by Luis Galdos, Added PM-support
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/highmem.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/err.h>
+#include <linux/proc_fs.h>
+#include <linux/clk.h>
+
+#include <asm/dma.h>
+#include <asm/dma-mapping.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/scatterlist.h>
+#include <asm/sizes.h>
+#include <asm/mach/mmc.h>
+
+#include <mach/dma.h>
+#include <plat/hsmmc.h>
+#include <mach/regs-hsmmc.h>
+#include <mach/regs-gpio.h>
+#include <mach/regs-gpioj.h>
+
+
+#define printk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] hsmmc: " fmt, ## args)
+#define printk_info(fmt, args...) printk(KERN_INFO "hsmmc: " fmt, ## args)
+
+#if 0
+#define CONFIG_S3CMCI_DEBUG
+#endif
+
+#ifdef CONFIG_S3CMCI_DEBUG
+# define printk_debug(fmt, args...) printk(KERN_DEBUG "hsmmc: %s() " fmt, __FUNCTION__ , ## args)
+#else
+# define printk_debug(fmt, args...)
+#endif
+
+/* Enables the support for the scatterlists */
+#define CONFIG_HSMMC_PSEUDO_SCATTERGATHER (1)
+
+#define DBG(x, ...)
+
+#include "s3c-hsmmc.h"
+
+#define DRIVER_NAME "s3c-hsmmc"
+#define PFX DRIVER_NAME ": "
+
+#define RESSIZE(ressource) (((ressource)->end - (ressource)->start)+1)
+
+#ifdef CONFIG_HSMMC_PROC_DATA
+struct s3c_hsmmc_host *global_host[3];
+#endif
+
+/*****************************************************************************\
+ * *
+ * Low level functions *
+ * *
+\*****************************************************************************/
+
+static struct s3c_hsmmc_cfg s3c_hsmmc_platform = {
+ .hwport = 0,
+ .host_caps = (MMC_CAP_4_BIT_DATA | MMC_CAP_MMC_HIGHSPEED),
+ .base = NULL,
+ .ctrl3[0] = 0x80800000,
+ .ctrl3[1] = 0x80800000,
+ .set_gpio = NULL,
+};
+
+/* s3c_hsmmc_get_platdata
+ *
+ * get the platform data associated with the given device, or return
+ * the default if there is none
+ */
+
+static struct s3c_hsmmc_cfg *s3c_hsmmc_get_platdata (struct device *dev)
+{
+ if (dev->platform_data != NULL)
+ return (struct s3c_hsmmc_cfg *)dev->platform_data;
+
+ return &s3c_hsmmc_platform;
+}
+
+static void s3c_hsmmc_reset (struct s3c_hsmmc_host *host, u8 mask)
+{
+ unsigned long timeout;
+
+ s3c_hsmmc_writeb(mask, S3C2410_HSMMC_SWRST);
+
+ if (mask & S3C_HSMMC_RESET_ALL)
+ host->clock = (uint)-1;
+
+ /* Wait max 100 ms */
+ timeout = 100;
+
+ /* hw clears the bit when it's done */
+ while (s3c_hsmmc_readb(S3C2410_HSMMC_SWRST) & mask) {
+ if (timeout == 0) {
+ printk("%s: Reset 0x%x never completed. \n",
+ mmc_hostname(host->mmc), (int)mask);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+}
+
+static void s3c_hsmmc_ios_init (struct s3c_hsmmc_host *host)
+{
+ u32 intmask;
+
+ s3c_hsmmc_reset(host, S3C_HSMMC_RESET_ALL);
+
+ intmask = S3C_HSMMC_INT_BUS_POWER | S3C_HSMMC_INT_DATA_END_BIT |
+ S3C_HSMMC_INT_DATA_CRC | S3C_HSMMC_INT_DATA_TIMEOUT | S3C_HSMMC_INT_INDEX |
+ S3C_HSMMC_INT_END_BIT | S3C_HSMMC_INT_CRC | S3C_HSMMC_INT_TIMEOUT |
+ S3C_HSMMC_INT_CARD_REMOVE | S3C_HSMMC_INT_CARD_INSERT |
+ S3C_HSMMC_INT_DATA_AVAIL | S3C_HSMMC_INT_SPACE_AVAIL |
+ S3C_HSMMC_INT_DATA_END | S3C_HSMMC_INT_RESPONSE;
+
+#ifdef CONFIG_HSMMC_PSEUDO_SCATTERGATHER
+ intmask |= S3C_HSMMC_INT_DMA_END;
+#endif
+ s3c_hsmmc_writel(intmask, S3C2410_HSMMC_NORINTSTSEN);
+ s3c_hsmmc_writel(intmask, S3C2410_HSMMC_NORINTSIGEN);
+}
+
+/*****************************************************************************\
+ * *
+ * Tasklets *
+ * *
+\*****************************************************************************/
+
+static void s3c_hsmmc_tasklet_card (ulong param)
+{
+ struct s3c_hsmmc_host *host;
+ unsigned long iflags;
+
+ host = (struct s3c_hsmmc_host*)param;
+ spin_lock_irqsave( &host->lock, iflags);
+
+ if (!(s3c_hsmmc_readl(S3C2410_HSMMC_PRNSTS) & S3C_HSMMC_CARD_PRESENT)) {
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Card removed during transfer!\n",
+ mmc_hostname(host->mmc));
+ printk(KERN_ERR "%s: Resetting controller.\n",
+ mmc_hostname(host->mmc));
+
+ s3c_hsmmc_reset(host, S3C_HSMMC_RESET_CMD);
+ s3c_hsmmc_reset(host, S3C_HSMMC_RESET_DATA);
+
+ host->mrq->cmd->error = -EILSEQ;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ }
+
+ spin_unlock_irqrestore( &host->lock, iflags);
+
+ mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+}
+
+static void s3c_hsmmc_activate_led(struct s3c_hsmmc_host *host)
+{
+ unsigned int ctrl;
+ struct s3c_hsmmc_cfg *cfg = host->plat_data;
+
+ if (cfg->gpio_led == S3C2443_GPJ13) {
+ ctrl = s3c_hsmmc_readl(S3C2410_HSMMC_HOSTCTL);
+ ctrl &= ~S3C_HSMMC_CTRL_LED;
+ s3c_hsmmc_writel(ctrl, S3C2410_HSMMC_HOSTCTL);
+ } else if (cfg->gpio_led)
+ s3c2410_gpio_setpin(cfg->gpio_led, 1);
+
+
+#if 0 // for 6400
+ s3c_gpio_cfgpin(S3C_GPJ13, S3C_GPJ13_SD0LED);
+#endif
+}
+
+static void s3c_hsmmc_deactivate_led(struct s3c_hsmmc_host *host)
+{
+ unsigned int ctrl;
+ struct s3c_hsmmc_cfg *cfg = host->plat_data;
+
+ if (cfg->gpio_led == S3C2443_GPJ13) {
+ ctrl = s3c_hsmmc_readl(S3C2410_HSMMC_HOSTCTL);
+ ctrl |= S3C_HSMMC_CTRL_LED;
+ s3c_hsmmc_writel(ctrl, S3C2410_HSMMC_HOSTCTL);
+ } else if (cfg->gpio_led)
+ s3c2410_gpio_setpin(cfg->gpio_led, 0);
+
+
+#if 0 // for 6400
+ s3c_gpio_cfgpin(S3C_GPJ13, S3C_GPJ13_INP);
+#endif
+}
+
+/*****************************************************************************\
+ * *
+ * Core functions *
+ * *
+\*****************************************************************************/
+
+#ifdef CONFIG_HSMMC_PSEUDO_SCATTERGATHER
+static inline uint s3c_hsmmc_build_dma_table (struct s3c_hsmmc_host *host,
+ struct mmc_data *data)
+{
+ uint i, j = 0, sub_num = 0;
+ dma_addr_t addr;
+ uint size, length, end;
+ int boundary, xor_bit;
+ struct scatterlist * sg = data->sg;
+
+ /* build dma table except last one */
+ for (i=0; i<(host->sg_len-1); i++) {
+ addr = sg[i].dma_address;
+ size = sg[i].length;
+ DBG("%d - addr: %08x, size: %08x\n", i, addr, size);
+
+ for (; (j<CONFIG_S3C_HSMMC_MAX_HW_SEGS*4) && size; j++) {
+ end = addr + size;
+ xor_bit = min(7+(2+8+2), fls(addr^end) -1);
+
+ DBG("%08x %08x %08x %d\n", addr, size, end, xor_bit);
+
+ host->dblk[j].dma_address = addr;
+
+ length = (end & ~((1<<xor_bit)-1)) - addr;
+ boundary = xor_bit - (2+8+2);
+ DBG("length: %x, boundary: %d\n", length, boundary);
+
+ if (length < S3C_HSMMC_MALLOC_SIZE) {
+ boundary = 0;
+
+ if ((addr+length) & (S3C_HSMMC_MALLOC_SIZE-1)) {
+ void *dest;
+
+ DBG("#########error fixing: %08x, %x\n", addr, length);
+ dest = host->sub_block[sub_num] + S3C_HSMMC_MALLOC_SIZE - length;
+ if (data->flags & MMC_DATA_WRITE) { /* write */
+ memcpy(dest, phys_to_virt(addr), length);
+ }
+
+ host->dblk[j].original = phys_to_virt(addr);
+ host->dblk[j].dma_address = dma_map_single(NULL, dest, length, host->dma_dir);
+ sub_num++;
+ }
+ }
+
+ host->dblk[j].length = length;
+ host->dblk[j].boundary = boundary;
+ DBG(" %d: %08x, %08x %x\n",
+ j, addr, length, boundary);
+ addr += length;
+ size -= length;
+ }
+ }
+
+ /* the last one */
+ host->dblk[j].dma_address = sg[i].dma_address;
+ host->dblk[j].length = sg[i].length;
+ host->dblk[j].boundary = 0x7;
+
+ return (j+1);
+}
+#endif
+
+static inline void s3c_hsmmc_prepare_data(struct s3c_hsmmc_host *host,
+ struct mmc_command *cmd)
+{
+ u32 reg;
+ struct mmc_data *data = cmd->data;
+
+ /* If no data to send, only enable the CMD complete interrupt */
+ if (data == NULL) {
+ reg = s3c_hsmmc_readl(S3C2410_HSMMC_NORINTSTSEN);
+ reg |= S3C_HSMMC_NIS_CMDCMP;
+ s3c_hsmmc_writel(reg, S3C2410_HSMMC_NORINTSTSEN);
+ return;
+ }
+
+#ifdef CONFIG_HSMMC_PROC_DATA
+ {
+ u32 total_size;
+ int bit;
+
+ total_size = data->blksz * data->blocks;
+ bit = fls(total_size) - 13;
+ if (bit < 0) bit = 0;
+ if (data->flags & MMC_DATA_WRITE) { /* write */
+ host->tx_pkt[bit]++;
+ } else { /* read */
+ host->rx_pkt[bit]++;
+ }
+ }
+#endif
+
+ reg = s3c_hsmmc_readl(S3C2410_HSMMC_NORINTSTSEN) & ~S3C_HSMMC_NIS_CMDCMP;
+ reg |= S3C_HSMMC_NIS_TRSCMP;
+ s3c_hsmmc_writel(reg, S3C2410_HSMMC_NORINTSTSEN);
+
+ host->dma_dir = (data->flags & MMC_DATA_READ)
+ ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+
+
+
+#ifdef CONFIG_HSMMC_PSEUDO_SCATTERGATHER
+ host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ host->dma_dir);
+
+ reg = s3c_hsmmc_readl(S3C2410_HSMMC_NORINTSTSEN);
+ if (host->sg_len == 1) {
+ reg &= ~S3C_HSMMC_NIS_DMA;
+ } else {
+ reg |= S3C_HSMMC_NIS_DMA;
+ }
+ s3c_hsmmc_writel(reg, S3C2410_HSMMC_NORINTSTSEN);
+
+ DBG("data->sg_len: %d\n", data->sg_len);
+ host->dma_blk = s3c_hsmmc_build_dma_table(host, data);
+ host->next_blk = 0;
+#else
+ dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ host->dma_dir);
+#endif
+}
+
+static inline void s3c_hsmmc_set_transfer_mode (struct s3c_hsmmc_host *host,
+ struct mmc_data *data)
+{
+ u16 mode;
+
+ mode = S3C_HSMMC_TRNS_DMA;
+
+ if (data->stop)
+ mode |= S3C_HSMMC_TRNS_ACMD12;
+
+ /* Set the configuration for the number of blocks to transfer */
+ if (data->blocks > 1)
+ mode |= (S3C_HSMMC_TRNS_MULTI | S3C_HSMMC_TRNS_BLK_CNT_EN);
+
+ /* Set the transfer direction */
+ if (data->flags & MMC_DATA_READ)
+ mode |= S3C_HSMMC_TRNS_READ;
+
+ printk_debug("Setting the transfer mode to 0x%08x\n", mode);
+ s3c_hsmmc_writew(mode, S3C2410_HSMMC_TRNMOD);
+}
+
+static inline void s3c_hsmmc_send_register (struct s3c_hsmmc_host *host)
+{
+ struct mmc_command *cmd = host->cmd;
+ struct mmc_data *data = cmd->data;
+
+ u32 cmd_val;
+
+ if (data) {
+#ifdef CONFIG_HSMMC_PSEUDO_SCATTERGATHER
+ struct s3c_hsmmc_dma_blk *dblk;
+
+ dblk = &host->dblk[0];
+
+ s3c_hsmmc_writew(S3C_HSMMC_MAKE_BLKSZ(dblk->boundary, data->blksz),
+ S3C2410_HSMMC_BLKSIZE);
+
+ s3c_hsmmc_writel(dblk->dma_address, S3C2410_HSMMC_SYSAD);
+#else
+ s3c_hsmmc_writew(S3C_HSMMC_MAKE_BLKSZ(0x7, data->blksz),
+ S3C2410_HSMMC_BLKSIZE);
+
+ s3c_hsmmc_writel(sg_dma_address(data->sg), S3C2410_HSMMC_SYSAD);
+#endif
+
+ s3c_hsmmc_writew(data->blocks, S3C2410_HSMMC_BLKCNT);
+ s3c_hsmmc_set_transfer_mode(host, data);
+ }
+
+ s3c_hsmmc_writel(cmd->arg, S3C2410_HSMMC_ARGUMENT);
+
+ cmd_val = (cmd->opcode << 8);
+ if (cmd_val == (12<<8))
+ cmd_val |= (3 << 6);
+
+ if (cmd->flags & MMC_RSP_136) /* Long RSP */
+ cmd_val |= S3C_HSMMC_CMD_RESP_LONG;
+ else if (cmd->flags & MMC_RSP_BUSY) /* R1B */
+ cmd_val |= S3C_HSMMC_CMD_RESP_SHORT_BUSY;
+ else if (cmd->flags & MMC_RSP_PRESENT) /* Normal RSP */
+ cmd_val |= S3C_HSMMC_CMD_RESP_SHORT;
+
+ if (cmd->flags & MMC_RSP_OPCODE)
+ cmd_val |= S3C_HSMMC_CMD_INDEX;
+
+ if (cmd->flags & MMC_RSP_CRC)
+ cmd_val |= S3C_HSMMC_CMD_CRC;
+
+ if (data)
+ cmd_val |= S3C_HSMMC_CMD_DATA;
+
+ s3c_hsmmc_writew(cmd_val, S3C2410_HSMMC_CMDREG);
+}
+
+
+/* Send a command to the HSMMC-controller */
+static inline void s3c_hsmmc_send_command(struct s3c_hsmmc_host *host,
+ struct mmc_command *cmd)
+{
+ u32 mask;
+ ulong timeout;
+
+ /* Wait max 10 ms */
+ timeout = 10;
+
+ mask = S3C_HSMMC_CMD_INHIBIT;
+ if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY))
+ mask |= S3C_HSMMC_DATA_INHIBIT;
+
+ while (s3c_hsmmc_readl(S3C2410_HSMMC_PRNSTS) & mask) {
+ if (timeout == 0) {
+ printk_err("%s: Controller never released "
+ "inhibit bit(s).\n", mmc_hostname(host->mmc));
+ cmd->error = -EILSEQ;
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ mod_timer(&host->timer, jiffies + 4 * HZ);
+
+ host->cmd = cmd;
+
+ s3c_hsmmc_prepare_data(host, cmd);
+ s3c_hsmmc_send_register(host);
+}
+
+
+static void s3c_hsmmc_finish_data(struct s3c_hsmmc_host *host)
+{
+ struct mmc_data *data;
+ u16 blocks;
+
+ BUG_ON(!host->data);
+
+ data = host->data;
+ host->data = NULL;
+
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ (data->flags & MMC_DATA_READ)
+ ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
+
+ /*
+ * Controller doesn't count down when in single block mode.
+ */
+ if ((data->blocks == 1) && (data->error == 0))
+ blocks = 0;
+ else {
+ blocks = s3c_hsmmc_readw(S3C2410_HSMMC_BLKCNT);
+ }
+ data->bytes_xfered = data->blksz * (data->blocks - blocks);
+
+ if ((data->error == 0) && blocks) {
+ printk_err("%s: Controller signalled completion even "
+ "though there were blocks left. : %d\n",
+ mmc_hostname(host->mmc), blocks);
+ data->error = -EILSEQ;
+ }
+
+ printk_debug("Ending data transfer (%d bytes)\n", data->bytes_xfered);
+
+ tasklet_schedule(&host->finish_tasklet);
+}
+
+static void s3c_hsmmc_finish_command (struct s3c_hsmmc_host *host)
+{
+ int i;
+ unsigned int resp;
+
+ BUG_ON(host->cmd == NULL);
+
+ if (host->cmd->flags & MMC_RSP_PRESENT) {
+ if (host->cmd->flags & MMC_RSP_136) {
+ /* CRC is stripped so we need to do some shifting. */
+ for (i = 0; i < 4; i++) {
+ resp = s3c_hsmmc_readl(S3C2410_HSMMC_RSPREG0 +
+ (3 - i) * 4);
+ host->cmd->resp[i] = resp << 8;
+ if (i != 3) {
+ resp = s3c_hsmmc_readb(S3C2410_HSMMC_RSPREG0 +
+ ((3 - i) * 4) - 1);
+ host->cmd->resp[i] |= resp;
+ }
+ }
+ } else {
+ resp = s3c_hsmmc_readl(S3C2410_HSMMC_RSPREG0);
+ printk_debug("Got a RSP: 0x%08x\n", resp);
+ host->cmd->resp[0] = resp;
+ }
+ }
+
+ host->cmd->error = 0;
+
+ printk_debug("Ending cmd (%u)\n", host->cmd->opcode);
+
+ if (host->cmd->data)
+ host->data = host->cmd->data;
+ else
+ tasklet_schedule(&host->finish_tasklet);
+
+ host->cmd = NULL;
+}
+
+static void s3c_hsmmc_tasklet_finish (unsigned long param)
+{
+ struct s3c_hsmmc_host *host;
+ unsigned long iflags;
+ struct mmc_request *mrq;
+
+ host = (struct s3c_hsmmc_host*)param;
+
+ spin_lock_irqsave(&host->lock, iflags);
+
+ del_timer(&host->timer);
+
+ mrq = host->mrq;
+
+ /*
+ * The controller needs a reset of internal state machines
+ * upon error conditions.
+ */
+ if ((mrq->cmd->error != 0) ||
+ (mrq->data && (mrq->data->error != 0)) ) {
+ s3c_hsmmc_reset(host, S3C_HSMMC_RESET_CMD);
+ s3c_hsmmc_reset(host, S3C_HSMMC_RESET_DATA);
+ }
+
+ host->mrq = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+
+ s3c_hsmmc_deactivate_led(host);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, iflags);
+
+ mmc_request_done(host->mmc, mrq);
+}
+
+/*****************************************************************************
+ * *
+ * Interrupt handling *
+ * *
+ *****************************************************************************/
+
+static void s3c_hsmmc_cmd_irq (struct s3c_hsmmc_host *host, u32 intmask)
+{
+ if (intmask & S3C_HSMMC_INT_TIMEOUT)
+ host->cmd->error = -ETIMEDOUT;
+ else if (intmask & S3C_HSMMC_INT_CRC)
+ host->cmd->error = -EILSEQ;
+ else if (intmask & (S3C_HSMMC_INT_END_BIT | S3C_HSMMC_INT_INDEX))
+ host->cmd->error = -EILSEQ;
+ else
+ host->cmd->error = -EILSEQ;
+
+ tasklet_schedule(&host->finish_tasklet);
+}
+
+static void s3c_hsmmc_data_irq (struct s3c_hsmmc_host *host, u32 intmask)
+{
+ if (!host->data) {
+ /*
+ * A data end interrupt is sent together with the response
+ * for the stop command.
+ */
+ if (intmask & S3C_HSMMC_INT_DATA_END)
+ return;
+
+ printk_err("%s: Got data interrupt even though no "
+ "data operation was in progress.\n",
+ mmc_hostname(host->mmc));
+ return;
+ }
+
+ if (intmask & S3C_HSMMC_INT_DATA_TIMEOUT)
+ host->data->error = -ETIMEDOUT;
+ else if (intmask & S3C_HSMMC_INT_DATA_CRC)
+ host->data->error = -EILSEQ;
+ else if (intmask & S3C_HSMMC_INT_DATA_END_BIT)
+ host->data->error = -EILSEQ;
+
+ if (host->data->error != 0)
+ s3c_hsmmc_finish_data(host);
+}
+
+
+/*****************************************************************************\
+ * *
+ * Interrupt handling *
+ * *
+\*****************************************************************************/
+
+/*
+ * ISR for SDI Interface IRQ
+ * Communication between driver and ISR works as follows:
+ * host->mrq points to current request
+ * host->complete_what tells the ISR when the request is considered done
+ * COMPLETION_CMDSENT when the command was sent
+ * COMPLETION_RSPFIN when a response was received
+ * COMPLETION_XFERFINISH when the data transfer is finished
+ * COMPLETION_XFERFINISH_RSPFIN both of the above.
+ * host->complete_request is the completion-object the driver waits for
+ *
+ * 1) Driver sets up host->mrq and host->complete_what
+ * 2) Driver prepares the transfer
+ * 3) Driver enables interrupts
+ * 4) Driver starts transfer
+ * 5) Driver waits for host->complete_rquest
+ * 6) ISR checks for request status (errors and success)
+ * 6) ISR sets host->mrq->cmd->error and host->mrq->data->error
+ * 7) ISR completes host->complete_request
+ * 8) ISR disables interrupts
+ * 9) Driver wakes up and takes care of the request
+ */
+
+static irqreturn_t s3c_hsmmc_irq (int irq, void *dev_id)
+{
+ irqreturn_t result = 0;
+ struct s3c_hsmmc_host *host = dev_id;
+ struct mmc_request *mrq;
+ u32 intsts;
+#ifdef CONFIG_HSMMC_S3C_IRQ_WORKAROUND
+ uint i, org_irq_sts;
+#endif
+ spin_lock(&host->lock);
+
+ mrq = host->mrq;
+
+ intsts = s3c_hsmmc_readw(S3C2410_HSMMC_NORINTSTS);
+
+ /* Sometimes, hsmmc does not update its status bit immediately
+ * when it generates irqs. by scsuh.
+ */
+#ifdef CONFIG_HSMMC_S3C_IRQ_WORKAROUND
+ for (i=0; i<0x1000; i++) {
+ if ((intsts = s3c_hsmmc_readw(S3C2410_HSMMC_NORINTSTS)))
+ break;
+ }
+#endif
+
+ if (unlikely(!intsts)) {
+ result = IRQ_NONE;
+ goto out;
+ }
+ intsts = s3c_hsmmc_readl(S3C2410_HSMMC_NORINTSTS);
+#ifdef CONFIG_HSMMC_S3C_IRQ_WORKAROUND
+ org_irq_sts = intsts;
+#endif
+
+ printk_debug("IRQ : Status 0x%08x\n", intsts);
+#if 0
+ printk(PFX "got interrupt = 0x%08x\n", intsts);
+ printk(PFX "got mask = 0x%08x\n", s3c_hsmmc_readl(S3C2410_HSMMC_NORINTSTSEN));
+ printk(PFX "got signal = 0x%08x\n", s3c_hsmmc_readl(S3C2410_HSMMC_NORINTSIGEN));
+#endif
+
+ if (unlikely(intsts & S3C_HSMMC_INT_CARD_CHANGE)) {
+ u32 reg16;
+
+ if (intsts & S3C_HSMMC_INT_CARD_INSERT)
+ printk_debug("card inserted.\n");
+ else if (intsts & S3C_HSMMC_INT_CARD_REMOVE)
+ printk_debug("card removed.\n");
+
+ reg16 = s3c_hsmmc_readw(S3C2410_HSMMC_NORINTSTSEN);
+ s3c_hsmmc_writew(reg16 & ~S3C_HSMMC_INT_CARD_CHANGE,
+ S3C2410_HSMMC_NORINTSTSEN);
+ s3c_hsmmc_writew(S3C_HSMMC_INT_CARD_CHANGE, S3C2410_HSMMC_NORINTSTS);
+ s3c_hsmmc_writew(reg16, S3C2410_HSMMC_NORINTSTSEN);
+
+ intsts &= ~S3C_HSMMC_INT_CARD_CHANGE;
+
+ tasklet_schedule(&host->card_tasklet);
+ goto insert;
+ }
+
+ if (likely(!(intsts & S3C_HSMMC_NIS_ERR))) {
+ s3c_hsmmc_writel(intsts, S3C2410_HSMMC_NORINTSTS);
+
+ if (intsts & S3C_HSMMC_NIS_CMDCMP) {
+ printk_debug("command done\n");
+ s3c_hsmmc_finish_command(host);
+ }
+
+ if (intsts & S3C_HSMMC_NIS_TRSCMP) {
+ printk_debug("transfer done\n\n");
+ s3c_hsmmc_finish_command(host);
+ s3c_hsmmc_finish_data(host);
+ intsts &= ~S3C_HSMMC_NIS_DMA;
+ }
+
+#ifdef CONFIG_HSMMC_PSEUDO_SCATTERGATHER
+ if (intsts & S3C_HSMMC_NIS_DMA) {
+ struct s3c_hsmmc_dma_blk *dblk;
+
+ dblk = &host->dblk[host->next_blk];
+ if (dblk->original) {
+ /* on read */
+ if (host->dma_dir == DMA_FROM_DEVICE) {
+ memcpy(dblk->original,
+ phys_to_virt(dblk->dma_address),
+ dblk->length);
+ }
+ dma_unmap_single(NULL, dblk->dma_address, dblk->length,
+ host->dma_dir);
+ dblk->original = 0;
+ }
+
+ host->next_blk++;
+ dblk = &host->dblk[host->next_blk];
+
+ if (host->next_blk == (host->dma_blk-1)) {
+ u32 reg;
+
+ reg = s3c_hsmmc_readl(S3C2410_HSMMC_NORINTSTSEN);
+ reg &= ~S3C_HSMMC_NIS_DMA;
+ s3c_hsmmc_writel(reg, S3C2410_HSMMC_NORINTSTSEN);
+ }
+
+ /* We do not handle DMA boundaries, so set it to max (512 KiB) */
+ s3c_hsmmc_writew((dblk->boundary<<12 | 0x200),
+ S3C2410_HSMMC_BLKSIZE);
+ s3c_hsmmc_writel(dblk->dma_address, S3C2410_HSMMC_SYSAD);
+ }
+#endif
+ } else {
+ unsigned int interr;
+ interr = s3c_hsmmc_readl(S3C2410_HSMMC_ERRINTSTS) & 0xffff;
+
+ /* Timeout errors are printed in the tasklet, or? (Luis Galdos) */
+ if (interr != S3C_HSMMC_EIS_CMDTIMEOUT)
+ printk_err("Error detected : 0x%04x\n", interr);
+
+ if (intsts & S3C_HSMMC_INT_CMD_MASK) {
+ s3c_hsmmc_writel(intsts & S3C_HSMMC_INT_CMD_MASK,
+ S3C2410_HSMMC_NORINTSTS);
+ s3c_hsmmc_cmd_irq(host, intsts & S3C_HSMMC_INT_CMD_MASK);
+ }
+
+ if (intsts & S3C_HSMMC_INT_DATA_MASK) {
+ s3c_hsmmc_writel(intsts & S3C_HSMMC_INT_DATA_MASK,
+ S3C2410_HSMMC_NORINTSTS);
+ s3c_hsmmc_finish_command(host);
+ s3c_hsmmc_data_irq(host, intsts & S3C_HSMMC_INT_DATA_MASK);
+ }
+
+ intsts &= ~(S3C_HSMMC_INT_CMD_MASK | S3C_HSMMC_INT_DATA_MASK);
+ }
+
+ /* XXX: fix later by scsuh */
+#if 0
+ if (intsts & S3C_HSMMC_INT_BUS_POWER) {
+ printk_err("%s: Card is consuming too much power!\n",
+ mmc_hostname(host->mmc));
+ s3c_hsmmc_writel(S3C_HSMMC_INT_BUS_POWER, S3C2410_HSMMC_NORINTSTS);
+ }
+
+ intsts &= S3C_HSMMC_INT_BUS_POWER;
+
+ if (intsts) {
+ printk_err("%s: Unexpected interrupt 0x%08x.\n",
+ mmc_hostname(host->mmc), intsts);
+
+ s3c_hsmmc_writel(intsts, S3C2410_HSMMC_NORINTSTS);
+ }
+#endif
+
+#ifdef CONFIG_HSMMC_S3C_IRQ_WORKAROUND
+ for (i=0; i<0x1000; i++) {
+ if (org_irq_sts != s3c_hsmmc_readl(S3C2410_HSMMC_NORINTSTS))
+ break;
+ }
+#endif
+
+insert:
+ result = IRQ_HANDLED;
+ mmiowb();
+
+out:
+ spin_unlock(&host->lock);
+
+ return result;
+}
+
+static void s3c_hsmmc_check_status (unsigned long data)
+{
+ struct s3c_hsmmc_host *host = (struct s3c_hsmmc_host *)data;
+
+ s3c_hsmmc_irq(0, host);
+}
+
+static void s3c_hsmmc_request (struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct s3c_hsmmc_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ printk_debug("[CMD%d] arg:0x%08x flags:0x%02x retries:%u\n",
+ mrq->cmd->opcode, mrq->cmd->arg,
+ mrq->cmd->flags, mrq->cmd->retries);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ WARN_ON(host->mrq != NULL);
+
+ s3c_hsmmc_activate_led(host);
+
+ host->mrq = mrq;
+
+ if (likely(s3c_hsmmc_readl(S3C2410_HSMMC_PRNSTS) & S3C_HSMMC_CARD_PRESENT)) {
+ s3c_hsmmc_send_command(host, mrq->cmd);
+ } else {
+ printk_debug("No card present? Aborting a command request.\n");
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/* return 0: OK
+ * return -1: error
+ */
+static int set_bus_width (struct s3c_hsmmc_host *host, uint width)
+{
+ u8 reg;
+
+ reg = s3c_hsmmc_readb(S3C2410_HSMMC_HOSTCTL);
+
+ switch (width) {
+ case MMC_BUS_WIDTH_1:
+ reg &= ~(S3C_HSMMC_CTRL_4BIT | S3C_HSMMC_CTRL_8BIT);
+ break;
+ case MMC_BUS_WIDTH_4:
+ reg |= S3C_HSMMC_CTRL_4BIT;
+ break;
+ case MMC_BUS_WIDTH_8:
+ reg |= S3C_HSMMC_CTRL_8BIT;
+ break;
+ default:
+ printk_err("Invalid bus width %u\n", width);
+ return -EINVAL;
+ }
+
+ s3c_hsmmc_writeb(reg, S3C2410_HSMMC_HOSTCTL);
+
+ printk_debug("HOSTCTL 0x%02x\n", s3c_hsmmc_readb(S3C2410_HSMMC_HOSTCTL));
+
+ return 0;
+}
+
+static void hsmmc_set_clock (struct s3c_hsmmc_host *host, ulong clock)
+{
+ uint i, j;
+ u32 val = 0, tmp_clk = 0, clk_src = 0;
+ ulong timeout;
+ u16 div = -1;
+ u8 ctrl;
+
+ /* if we already set, just out. */
+ if (clock == host->clock) {
+ printk("%p:host->clock0 : %d\n", host->base, host->clock);
+ return;
+ }
+
+ /* before setting clock, clkcon must be disabled. */
+ s3c_hsmmc_writew(0, S3C2410_HSMMC_CLKCON);
+
+ s3c_hsmmc_writeb(S3C_HSMMC_TIMEOUT_MAX, S3C2410_HSMMC_TIMEOUTCON);
+
+ /* change the edge type according to frequency */
+ ctrl = s3c_hsmmc_readb(S3C2410_HSMMC_HOSTCTL);
+ if (clock > 25000000)
+ ctrl |= S3C_HSMMC_CTRL_HIGHSPEED;
+ else
+ ctrl &= ~S3C_HSMMC_CTRL_HIGHSPEED;
+ s3c_hsmmc_writeb(ctrl, S3C2410_HSMMC_HOSTCTL);
+
+ if (clock == 0) {
+ return;
+ }
+
+ /* calculate optimal clock. by scsuh */
+ for (i=0; i < 3; i++) {
+ tmp_clk = clk_get_rate(host->clk[i]);
+ if (tmp_clk <= clock) {
+ if (tmp_clk >= val) {
+ val = tmp_clk;
+ div = 0;
+ clk_src = i+1;
+ }
+ }
+
+ for (j=0x1; j<=0x80; j <<= 1) {
+ tmp_clk = clk_get_rate(host->clk[i]) / (j<<1);
+ if ((val < tmp_clk) && (tmp_clk <= clock)) {
+ val = tmp_clk;
+ div = j;
+ clk_src = i+1;
+ break;
+ }
+ }
+ }
+
+ /* clk_src = 0x00; */
+ printk_debug("val: %d, div: %x, clk_src: %d\n", val, div, clk_src);
+
+ s3c_hsmmc_writel(0x0000c100 | (clk_src << 4), S3C2410_HSMMC_CONTROL2);
+ if (clock > 25000000)
+ /* 0x00008080 6400, 0x00800080 2443 */
+ s3c_hsmmc_writel(host->ctrl3[1], S3C2410_HSMMC_CONTROL3);
+ else
+ /* 0x80808080 6400, 0x00800080 2443 */
+ s3c_hsmmc_writel(host->ctrl3[0], S3C2410_HSMMC_CONTROL3);
+
+ s3c_hsmmc_writew(((div<<8) | S3C_HSMMC_CLOCK_INT_EN), S3C2410_HSMMC_CLKCON);
+
+ timeout = 10;
+ while (!((val = s3c_hsmmc_readw(S3C2410_HSMMC_CLKCON))
+ & S3C_HSMMC_CLOCK_INT_STABLE)) {
+ if (!timeout) {
+ printk_err("Clock stabilization: %08x | Div %u\n", val, div);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ s3c_hsmmc_writew(val | S3C_HSMMC_CLOCK_CARD_EN, S3C2410_HSMMC_CLKCON);
+ timeout = 10;
+ while (!((val = s3c_hsmmc_readw(S3C2410_HSMMC_CLKCON))
+ & S3C_HSMMC_CLOCK_EXT_STABLE)) {
+ if (!timeout) {
+ printk_err("Clock stabilization: %08x | Div %u\n", val, div);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+//out:
+// host->clock = clock;
+// printk("%p: host->clock1 : %d\n", host->base, host->clock);
+}
+
+
+static void s3c_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct s3c_hsmmc_host *host = mmc_priv(mmc);
+ struct s3c_hsmmc_cfg *cfg = host->plat_data;
+
+ unsigned long iflags;
+
+ spin_lock_irqsave(&host->lock, iflags);
+
+
+ switch (ios->power_mode) {
+ case MMC_POWER_ON:
+ case MMC_POWER_UP:
+ s3c2410_gpio_cfgpin(S3C2443_GPL0, S3C2443_GPL0_SD0DAT0);
+
+ if (cfg->host_caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA)) {
+ s3c2410_gpio_cfgpin(S3C2443_GPL1, S3C2443_GPL1_SD0DAT1);
+ s3c2410_gpio_cfgpin(S3C2443_GPL2, S3C2443_GPL2_SD0DAT2);
+ s3c2410_gpio_cfgpin(S3C2443_GPL3, S3C2443_GPL3_SD0DAT3);
+ }
+
+ if (cfg->host_caps & MMC_CAP_8_BIT_DATA) {
+ s3c2410_gpio_cfgpin(S3C2443_GPL4, S3C2443_GPL4_SD0DAT4);
+ s3c2410_gpio_cfgpin(S3C2443_GPL5, S3C2443_GPL5_SD0DAT5);
+ s3c2410_gpio_cfgpin(S3C2443_GPL6, S3C2443_GPL6_SD0DAT6);
+ s3c2410_gpio_cfgpin(S3C2443_GPL7, S3C2443_GPL7_SD0DAT7);
+ }
+
+ s3c2410_gpio_cfgpin(S3C2443_GPL8, S3C2443_GPL8_SD0CMD);
+ s3c2410_gpio_cfgpin(S3C2443_GPL9, S3C2443_GPL9_SD0CLK);
+
+ /* Check for the dedicated GPIOs of the HSMMC-controller */
+ if (cfg->gpio_led == S3C2443_GPJ13)
+ s3c2410_gpio_cfgpin(S3C2443_GPJ13, S3C2440_GPJ13_SD0LED);
+
+ if (cfg->gpio_detect == S3C2443_GPJ14)
+ s3c2410_gpio_cfgpin(S3C2443_GPJ14, S3C2440_GPJ14_SD0CD);
+
+ if (cfg->gpio_wprotect == S3C2443_GPJ15)
+ s3c2410_gpio_cfgpin(S3C2443_GPJ15, S3C2440_GPJ15_SD0WP);
+
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Reset the chip on each power off.
+ * Should clear out any weird states.
+ */
+ if (ios->power_mode == MMC_POWER_OFF) {
+ s3c_hsmmc_writew(0, S3C2410_HSMMC_NORINTSIGEN);
+ s3c_hsmmc_ios_init(host);
+ }
+
+ if (host->plat_data->set_gpio)
+ host->plat_data->set_gpio();
+
+ printk_debug("Clock: %d | Bus width %d\n", ios->clock, ios->bus_width);
+ hsmmc_set_clock(host, ios->clock);
+ set_bus_width(host, ios->bus_width);
+
+ printk_debug("CTRL2 0x%08x | CTRL3 0x%08x | CLKCON 0x%04x\n",
+ s3c_hsmmc_readl(S3C2410_HSMMC_CONTROL2),
+ s3c_hsmmc_readl(S3C2410_HSMMC_CONTROL3),
+ s3c_hsmmc_readw(S3C2410_HSMMC_CLKCON));
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ s3c_hsmmc_writeb(S3C_HSMMC_POWER_OFF, S3C2410_HSMMC_PWRCON);
+ else
+ s3c_hsmmc_writeb(S3C_HSMMC_POWER_ON_ALL, S3C2410_HSMMC_PWRCON);
+
+ udelay(1000);
+ spin_unlock_irqrestore(&host->lock, iflags);
+}
+
+
+
+/*
+ * If no GPIO was configured for the write protect, then assume that the card
+ * is NOT write protected
+ */
+static int s3c_hsmmc_get_ro(struct mmc_host *mmc)
+{
+ struct s3c_hsmmc_host *host = mmc_priv(mmc);
+ struct s3c_hsmmc_cfg *cfg = host->plat_data;
+ unsigned int retval;
+
+ /* Depending on the configured GPIO get the RO-value */
+ if (cfg->gpio_wprotect == S3C2443_GPJ15) {
+ retval = s3c_hsmmc_readl(S3C2410_HSMMC_PRNSTS);
+ retval &= S3C_HSMMC_WRITE_PROTECT;
+ } else if (cfg->gpio_wprotect) {
+ retval = s3c2410_gpio_getpin(cfg->gpio_wprotect);
+ retval = cfg->wprotect_invert ? !retval : retval;
+ } else
+ retval = 0;
+
+ return retval;
+}
+
+
+static struct mmc_host_ops s3c_hsmmc_ops = {
+ .request = s3c_hsmmc_request,
+ .set_ios = s3c_hsmmc_set_ios,
+ .get_ro = s3c_hsmmc_get_ro,
+};
+
+
+
+static int s3c_hsmmc_probe (struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct s3c_hsmmc_host *host;
+ struct s3c_hsmmc_cfg *plat_data;
+ uint i;
+ int ret;
+
+ mmc = mmc_alloc_host(sizeof(struct s3c_hsmmc_host), &pdev->dev);
+ if (!mmc)
+ ret = -ENOMEM;
+
+ plat_data = s3c_hsmmc_get_platdata(&pdev->dev);
+
+ host = mmc_priv(mmc);
+
+ host->mmc = mmc;
+ host->plat_data = plat_data;
+ platform_set_drvdata(pdev, mmc);
+
+ host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!host->mem) {
+ printk_err("Failed to get io memory region resouce.\n");
+ ret = -ENOENT;
+ goto probe_free_host;
+ }
+
+ host->mem = request_mem_region(host->mem->start,
+ RESSIZE(host->mem), pdev->name);
+ if (!host->mem) {
+ printk_err("Failed to request io memory region.\n");
+ ret = -ENOENT;
+ goto probe_free_host;
+ }
+
+ host->base = ioremap(host->mem->start, RESSIZE(host->mem));
+ if (!host->base) {
+ printk_err("Failed to ioremap() the region %p\n", host->mem);
+ ret = -EINVAL;
+ goto err_free_mem_region;
+ }
+
+ host->irq = platform_get_irq(pdev, 0);
+ if (host->irq == 0) {
+ printk("failed to get interrupt resouce.\n");
+ ret = -EINVAL;
+ goto untasklet;
+ }
+
+ host->flags |= S3C_HSMMC_USE_DMA;
+
+ /* every platform has different ctrl3 value. by scsuh */
+ host->ctrl3[0] = plat_data->ctrl3[0];
+ host->ctrl3[1] = plat_data->ctrl3[1];
+
+#ifdef CONFIG_HSMMC_PSEUDO_SCATTERGATHER
+ for (i=0; i<S3C_HSMMC_MAX_SUB_BUF; i++)
+ host->sub_block[i] = kmalloc(S3C_HSMMC_MALLOC_SIZE, GFP_KERNEL);
+#endif
+
+ s3c_hsmmc_reset(host, S3C_HSMMC_RESET_ALL);
+
+#ifdef CONFIG_ARCH_S3C6400
+ clk_enable(clk_get(&pdev->dev, "sclk_48m"));
+#endif
+
+ /* register 3 clock source if exist */
+ for (i=0; i<3; i++) {
+ host->clk[i] = clk_get(&pdev->dev, plat_data->clk_name[i]);
+ if (IS_ERR(host->clk[i])) {
+ ret = PTR_ERR(host->clk[i]);
+ host->clk[i] = ERR_PTR(-ENOENT);
+ }
+
+ if (clk_enable(host->clk[i])) {
+ host->clk[i] = ERR_PTR(-ENOENT);
+ }
+ }
+
+ mmc->ops = &s3c_hsmmc_ops;
+ mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
+ mmc->f_min = 400 * 1000; /* at least 400kHz */
+
+ /* you must make sure that our hsmmc block can support
+ * up to 52MHz. by scsuh
+ */
+ mmc->f_max = 100 * MHZ;
+ mmc->caps = plat_data->host_caps;
+ printk_debug("mmc->caps: %08x\n", (unsigned int)mmc->caps);
+
+ spin_lock_init(&host->lock);
+
+ /*
+ * Maximum number of segments. Hardware cannot do scatter lists.
+ * XXX: must modify later. by scsuh
+ */
+#ifdef CONFIG_HSMMC_PSEUDO_SCATTERGATHER
+ mmc->max_hw_segs = CONFIG_S3C_HSMMC_MAX_HW_SEGS;
+ mmc->max_phys_segs = CONFIG_S3C_HSMMC_MAX_HW_SEGS;
+#else
+ mmc->max_hw_segs = 1;
+#endif
+
+ /*
+ * Maximum number of sectors in one transfer. Limited by DMA boundary
+ * size (512KiB), which means (512 KiB/512=) 1024 entries.
+ */
+ mmc->max_blk_count = 1024;
+
+ /*
+ * Maximum segment size. Could be one segment with the maximum number
+ * of sectors.
+ */
+ mmc->max_seg_size = mmc->max_blk_count * 512;
+ mmc->max_req_size = mmc->max_blk_count * 512;
+
+ init_timer(&host->timer);
+ host->timer.data = (unsigned long)host;
+ host->timer.function = s3c_hsmmc_check_status;
+ host->timer.expires = jiffies + HZ;
+
+ /*
+ * Init tasklets.
+ */
+ tasklet_init(&host->card_tasklet,
+ s3c_hsmmc_tasklet_card, (unsigned long)host);
+ tasklet_init(&host->finish_tasklet,
+ s3c_hsmmc_tasklet_finish, (unsigned long)host);
+
+ ret = request_irq(host->irq, s3c_hsmmc_irq, 0, DRIVER_NAME, host);
+ if (ret)
+ goto untasklet;
+
+ s3c_hsmmc_ios_init(host);
+
+ mmc_add_host(mmc);
+
+#ifdef CONFIG_HSMMC_PROC_DATA
+ global_host[plat_data->hwport] = host;
+#endif
+
+ printk(KERN_INFO "%s.%d: at 0x%p with irq %d. clk src:",
+ pdev->name, pdev->id, host->base, host->irq);
+ for (i=0; i<3; i++) {
+ if (!IS_ERR(host->clk[i]))
+ printk(" %s", plat_data->clk_name[i]);
+ }
+ printk("\n");
+
+ return 0;
+
+ untasklet:
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+
+ for (i=0; i<3; i++) {
+ clk_disable(host->clk[i]);
+ clk_put(host->clk[i]);
+ }
+
+ err_free_mem_region:
+ release_mem_region(host->mem->start, RESSIZE(host->mem));
+
+ probe_free_host:
+ mmc_free_host(mmc);
+ platform_set_drvdata(pdev, NULL);
+
+ return ret;
+}
+
+static int s3c_hsmmc_remove(struct platform_device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct s3c_hsmmc_host *host = mmc_priv(mmc);
+ int i;
+
+ printk_debug("Removing the MMC %p | S3C host %p\n", mmc, host);
+
+ mmc_remove_host(mmc);
+
+ /* Reset the MMC controller */
+ s3c_hsmmc_reset(host, S3C_HSMMC_RESET_ALL);
+
+#ifdef CONFIG_ARCH_S3C6400
+ clk_disable(clk_get(&dev->dev, "sclk_48m"));
+#endif
+
+ /* Free only the requested clocks */
+ for (i =0; i< 3; i++) {
+ if (!PTR_ERR(host->clk[i])) {
+ clk_disable(host->clk[i]);
+ clk_put(host->clk[i]);
+ }
+ }
+
+ /* Free the IRQ and mapped memory */
+ free_irq(host->irq, host);
+ iounmap(host->base);
+ release_mem_region(host->mem->start, RESSIZE(host->mem));
+
+ del_timer_sync(&host->timer);
+
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+
+#ifdef CONFIG_HSMMC_PSEUDO_SCATTERGATHER
+ kfree(host->sub_block);
+#endif
+ mmc_free_host(mmc);
+
+ return 0;
+}
+
+/* This is for the power management support */
+#ifdef CONFIG_PM
+static int s3c_hsmmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct mmc_host *mmc;
+ struct s3c_hsmmc_host *host;
+ int cnt, retval;
+
+ mmc = platform_get_drvdata(pdev);
+ host = mmc_priv(mmc);
+
+ /* Check for all the three available clocks to disable */
+ for (cnt = 0; cnt < 3; cnt++) {
+ if (!PTR_ERR(host->clk[cnt]))
+ clk_disable(host->clk[cnt]);
+ }
+
+ retval = mmc_suspend_host(mmc, state);
+
+ return retval;
+}
+
+static int s3c_hsmmc_resume(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct s3c_hsmmc_host *host;
+ int cnt, retval;
+
+ mmc = platform_get_drvdata(pdev);
+ host = mmc_priv(mmc);
+
+ /* Reenable the clocks first */
+ for (cnt = 0; cnt < 3; cnt++) {
+ if (!PTR_ERR(host->clk[cnt]))
+ clk_enable(host->clk[cnt]);
+ }
+
+ s3c_hsmmc_ios_init(host);
+
+ /*
+ * By unsafe resumes we MUST check the card state at this point, then the
+ * higher MMC-layer is probably transferring some kind of data to the
+ * block device that doesn't exist any more.
+ */
+#if defined(CONFIG_MMC_UNSAFE_RESUME)
+ if (s3c_hsmmc_readl(S3C2410_HSMMC_PRNSTS) & S3C_HSMMC_CARD_PRESENT)
+ retval = mmc_resume_host(mmc);
+ else {
+ retval = 0;
+ mmc_detect_change(mmc, msecs_to_jiffies(500));
+ }
+#else
+ retval = mmc_resume_host(mmc);
+#endif /* CONFIG_MMC_UNSAFE_RESUME */
+
+ return retval;
+}
+#else
+#define s3c_hsmmc_suspend NULL
+#define s3c_hsmmc_resume NULL
+#endif
+
+static struct platform_driver s3c_hsmmc_driver = {
+ .probe = s3c_hsmmc_probe,
+ .remove = s3c_hsmmc_remove,
+ .suspend = s3c_hsmmc_suspend,
+ .resume = s3c_hsmmc_resume,
+ .driver = {
+ .name = "s3c-hsmmc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init s3c_hsmmc_drv_init(void)
+{
+ return platform_driver_register(&s3c_hsmmc_driver);
+}
+
+static void __exit s3c_hsmmc_drv_exit(void)
+{
+ platform_driver_unregister(&s3c_hsmmc_driver);
+}
+
+#ifdef CONFIG_HSMMC_PROC_DATA
+static struct proc_dir_entry *evb_resource_dump;
+
+
+static int mem_proc_read (char *buffer, char **buffer_location, off_t offset,
+ int buffer_length, int *zero, void *ptr)
+{
+ return 0;
+}
+
+
+static int mem_proc_write(struct file *file, const char *buffer,
+ unsigned long count, void *data)
+{
+ uint i, range = 0;
+ char flag = 'l';
+ uint port = 0;
+
+#if 0
+ printk("buffers: %s\n", buffer);
+#endif
+ sscanf(buffer, "%d %c", &port, &flag);
+
+ switch (flag) {
+ case 'c':
+ printk("clean %d port\n", port);
+ for (i=0; i<7; i++) {
+ global_host[port]->rx_pkt[i] = 0;
+ global_host[port]->tx_pkt[i] = 0;
+ }
+// break;
+
+ case 'l':
+ printk("\t\t\trx\ttx\n");
+ for (i=0; i<7; i++) {
+ printk("0x%05x ~ 0x%05x\t%-7d\t%-7d\n",
+ range, 0x1000 << i,
+ global_host[port]->rx_pkt[i],
+ global_host[port]->tx_pkt[i]);
+ range = 0x1000 << i;
+
+ }
+ break;
+ }
+
+ return count;
+}
+
+int __init mem_proc_scsuh (void)
+{
+ evb_resource_dump = create_proc_entry("mmc", 0666, &proc_root);
+ evb_resource_dump->read_proc = mem_proc_read;
+ evb_resource_dump->write_proc = mem_proc_write;
+ evb_resource_dump->nlink = 1;
+ return 0;
+}
+
+module_init(mem_proc_scsuh);
+#endif
+
+module_init(s3c_hsmmc_drv_init);
+module_exit(s3c_hsmmc_drv_exit);
+
+
+MODULE_DESCRIPTION("S3C SD HOST I/F 1.0 Driver");
+MODULE_LICENSE("GPL");