diff options
-rw-r--r-- | arch/arm/mach-s3c24xx/include/mach/mci.h | 46 | ||||
-rw-r--r-- | drivers/mci/Kconfig | 7 | ||||
-rw-r--r-- | drivers/mci/Makefile | 1 | ||||
-rw-r--r-- | drivers/mci/s3c.c | 817 |
4 files changed, 871 insertions, 0 deletions
diff --git a/arch/arm/mach-s3c24xx/include/mach/mci.h b/arch/arm/mach-s3c24xx/include/mach/mci.h new file mode 100644 index 0000000000..6ba8961693 --- /dev/null +++ b/arch/arm/mach-s3c24xx/include/mach/mci.h @@ -0,0 +1,46 @@ +/* + * (C) Copyright 2010 Juergen Beisert, Pengutronix + * + * This code is partially based on u-boot code: + * + * Copyright 2008, Freescale Semiconductor, Inc + * Andy Fleming + * + * Based (loosely) on the Linux code + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef __MACH_MMC_H_ +#define __MACH_MMC_H_ + +struct s3c_mci_platform_data { + unsigned caps; /**< supported operating modes (MMC_MODE_*) */ + unsigned voltages; /**< supported voltage range (MMC_VDD_*) */ + unsigned f_min; /**< min operating frequency in Hz (0 -> no limit) */ + unsigned f_max; /**< max operating frequency in Hz (0 -> no limit) */ + /* TODO */ + /* function to modify the voltage */ + /* function to switch the voltage */ + /* function to detect the presence of a SD card in the socket */ + unsigned gpio_detect; + unsigned detect_invert; +}; + +#endif /* __MACH_MMC_H_ */ diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig index a24ae6dd77..644c0a3ae5 100644 --- a/drivers/mci/Kconfig +++ b/drivers/mci/Kconfig @@ -34,4 +34,11 @@ config MCI_STM378X Enable this entry to add support to read and write SD cards on a i.MX23 based system. +config MCI_S3C + bool "S3C" + depends on ARCH_S3C24xx + help + Enable this entry to add support to read and write SD cards on a + Samsung S3C24xx based system. + endif diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile index e5e19b0e59..be184465a6 100644 --- a/drivers/mci/Makefile +++ b/drivers/mci/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_MCI) += mci-core.o obj-$(CONFIG_MCI_STM378X) += stm378x.o +obj-$(CONFIG_MCI_S3C) += s3c.o diff --git a/drivers/mci/s3c.c b/drivers/mci/s3c.c new file mode 100644 index 0000000000..9810683f4b --- /dev/null +++ b/drivers/mci/s3c.c @@ -0,0 +1,817 @@ +/* + * Copyright (C) 2010 Juergen Beisert <juergen@kreuzholzen.de> + * + * This code is partially based on u-boot code: + * + * This code is based on various Linux and u-boot sources: + * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de> + * Copyright (C) 2008 Simtec Electronics <ben-linux@fluff.org> + * (C) Copyright 2006 by OpenMoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * based on u-boot pxa MMC driver and linux/drivers/mmc/s3c2410mci.c + * (C) 2005-2005 Thomas Kleffel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/** + * @file + * @brief MCI card host interface for S3C2440 CPU + */ + +/* #define DEBUG */ + +#include <common.h> +#include <init.h> +#include <mci.h> +#include <errno.h> +#include <clock.h> +#include <asm/io.h> +#include <mach/mci.h> +#include <mach/s3c24xx-generic.h> +#include <mach/s3c24x0-iomap.h> + +#define SDICON 0x0 +# define SDICON_SDRESET (1 << 8) +# define SDICON_MMCCLOCK (1 << 5) /* this is a clock type SD or MMC style WTF? */ +# define SDICON_BYTEORDER (1 << 4) +# define SDICON_SDIOIRQ (1 << 3) +# define SDICON_RWAITEN (1 << 2) +# define SDICON_FIFORESET (1 << 1) /* reserved bit on 2440 ????? */ +# define SDICON_CLKEN (1 << 0) /* enable/disable external clock */ + +#define SDIPRE 0x4 + +#define SDICMDARG 0x8 + +#define SDICMDCON 0xc +# define SDICMDCON_ABORT (1 << 12) +# define SDICMDCON_WITHDATA (1 << 11) +# define SDICMDCON_LONGRSP (1 << 10) +# define SDICMDCON_WAITRSP (1 << 9) +# define SDICMDCON_CMDSTART (1 << 8) +# define SDICMDCON_SENDERHOST (1 << 6) +# define SDICMDCON_INDEX (0x3f) + +#define SDICMDSTAT 0x10 +# define SDICMDSTAT_CRCFAIL (1 << 12) +# define SDICMDSTAT_CMDSENT (1 << 11) +# define SDICMDSTAT_CMDTIMEOUT (1 << 10) +# define SDICMDSTAT_RSPFIN (1 << 9) +# define SDICMDSTAT_XFERING (1 << 8) +# define SDICMDSTAT_INDEX (0xff) + +#define SDIRSP0 0x14 +#define SDIRSP1 0x18 +#define SDIRSP2 0x1C +#define SDIRSP3 0x20 + +#define SDITIMER 0x24 +#define SDIBSIZE 0x28 + +#define SDIDCON 0x2c +# define SDIDCON_DS_BYTE (0 << 22) +# define SDIDCON_DS_HALFWORD (1 << 22) +# define SDIDCON_DS_WORD (2 << 22) +# define SDIDCON_IRQPERIOD (1 << 21) +# define SDIDCON_TXAFTERRESP (1 << 20) +# define SDIDCON_RXAFTERCMD (1 << 19) +# define SDIDCON_BUSYAFTERCMD (1 << 18) +# define SDIDCON_BLOCKMODE (1 << 17) +# define SDIDCON_WIDEBUS (1 << 16) +# define SDIDCON_DMAEN (1 << 15) +# define SDIDCON_STOP (0 << 14) +# define SDIDCON_DATSTART (1 << 14) +# define SDIDCON_DATMODE (3 << 12) +# define SDIDCON_BLKNUM (0xfff) +# define SDIDCON_XFER_READY (0 << 12) +# define SDIDCON_XFER_CHKSTART (1 << 12) +# define SDIDCON_XFER_RXSTART (2 << 12) +# define SDIDCON_XFER_TXSTART (3 << 12) + +#define SDIDCNT 0x30 +# define SDIDCNT_BLKNUM_SHIFT 12 + +#define SDIDSTA 0x34 +# define SDIDSTA_RDYWAITREQ (1 << 10) +# define SDIDSTA_SDIOIRQDETECT (1 << 9) +# define SDIDSTA_FIFOFAIL (1 << 8) /* reserved on 2440 */ +# define SDIDSTA_CRCFAIL (1 << 7) +# define SDIDSTA_RXCRCFAIL (1 << 6) +# define SDIDSTA_DATATIMEOUT (1 << 5) +# define SDIDSTA_XFERFINISH (1 << 4) +# define SDIDSTA_BUSYFINISH (1 << 3) +# define SDIDSTA_SBITERR (1 << 2) /* reserved on 2410a/2440 */ +# define SDIDSTA_TXDATAON (1 << 1) +# define SDIDSTA_RXDATAON (1 << 0) + +#define SDIFSTA 0x38 +# define SDIFSTA_FIFORESET (1<<16) +# define SDIFSTA_FIFOFAIL (3<<14) /* 3 is correct (2 bits) */ +# define SDIFSTA_TFDET (1<<13) +# define SDIFSTA_RFDET (1<<12) +# define SDIFSTA_TFHALF (1<<11) +# define SDIFSTA_TFEMPTY (1<<10) +# define SDIFSTA_RFLAST (1<<9) +# define SDIFSTA_RFFULL (1<<8) +# define SDIFSTA_RFHALF (1<<7) +# define SDIFSTA_COUNTMASK (0x7f) + +#define SDIIMSK 0x3C +# define SDIIMSK_RESPONSECRC (1<<17) +# define SDIIMSK_CMDSENT (1<<16) +# define SDIIMSK_CMDTIMEOUT (1<<15) +# define SDIIMSK_RESPONSEND (1<<14) +# define SDIIMSK_READWAIT (1<<13) +# define SDIIMSK_SDIOIRQ (1<<12) +# define SDIIMSK_FIFOFAIL (1<<11) +# define SDIIMSK_CRCSTATUS (1<<10) +# define SDIIMSK_DATACRC (1<<9) +# define SDIIMSK_DATATIMEOUT (1<<8) +# define SDIIMSK_DATAFINISH (1<<7) +# define SDIIMSK_BUSYFINISH (1<<6) +# define SDIIMSK_SBITERR (1<<5) /* reserved 2440/2410a */ +# define SDIIMSK_TXFIFOHALF (1<<4) +# define SDIIMSK_TXFIFOEMPTY (1<<3) +# define SDIIMSK_RXFIFOLAST (1<<2) +# define SDIIMSK_RXFIFOFULL (1<<1) +# define SDIIMSK_RXFIFOHALF (1<<0) + +#define SDIDATA 0x40 + +struct s3c_mci_host { + int bus_width:2; /* 0 = 1 bit, 1 = 4 bit, 2 = 8 bit */ + unsigned clock; /* current clock in Hz */ + unsigned data_size; /* data transfer in bytes */ +}; + +/* + * There is only one host MCI hardware instance available. + * It makes no sense to dynamically allocate this data + */ +static struct s3c_mci_host host_data; + +/** + * Finish a request + * @param hw_dev Host interface instance + * + * Just a little bit paranoia. + */ +static void s3c_finish_request(struct device_d *hw_dev) +{ + /* TODO ensure the engines are stopped */ +} + +/* TODO GPIO feature is required for this architecture */ +static unsigned gpio_get_value(unsigned val) +{ + return 0; +} + +/** + * Detect if a card is plugged in + * @param hw_dev Host interface instance + * @return 0 if a card is plugged in + * + * Note: If there is no GPIO registered to detect if a card is present, we + * assume a card _is_ present. + */ +static int s3c_mci_card_present(struct device_d *hw_dev) +{ + struct s3c_mci_platform_data *pd = GET_HOST_PDATA(hw_dev); + int ret; + + if (pd->gpio_detect == 0) + return 0; /* assume the card is present */ + + ret = gpio_get_value(pd->gpio_detect) ? 0 : 1; + return ret ^ pd->detect_invert; +} + +/** + * Setup a new clock frequency on this MCI bus + * @param hw_dev Host interface instance + * @param nc New clock value in Hz (can be 0) + * @return New clock value (may differ from 'nc') + */ +static unsigned s3c_setup_clock_speed(struct device_d *hw_dev, unsigned nc) +{ + unsigned clock; + uint32_t mci_psc; + + if (nc == 0) + return 0; + + clock = s3c24xx_get_pclk(); + /* Calculate the required prescaler value to get the requested frequency */ + mci_psc = (clock + (nc >> 2)) / nc; + + if (mci_psc > 256) { + mci_psc = 256; + pr_warning("SD/MMC clock might be too high!\n"); + } + + writel(mci_psc - 1, hw_dev->map_base + SDIPRE); + + return clock / mci_psc; +} + +/** + * Reset the MCI engine (the hard way) + * @param hw_dev Host interface instance + * + * This will reset everything in all registers of this unit! + */ +static void s3c_mci_reset(struct device_d *hw_dev) +{ + /* reset the hardware */ + writel(SDICON_SDRESET, hw_dev->map_base + SDICON); + /* wait until reset it finished */ + while (readl(hw_dev->map_base + SDICON) & SDICON_SDRESET) + ; +} + +/** + * Initialize hard and software + * @param hw_dev Host interface instance + * @param mci_dev MCI device instance (might be NULL) + */ +static int s3c_mci_initialize(struct device_d *hw_dev, struct device_d *mci_dev) +{ + struct s3c_mci_host *host_data = GET_HOST_DATA(hw_dev); + + s3c_mci_reset(hw_dev); + + /* restore last settings */ + host_data->clock = s3c_setup_clock_speed(hw_dev, host_data->clock); + writel(0x007FFFFF, hw_dev->map_base + SDITIMER); + writel(SDICON_MMCCLOCK, hw_dev->map_base + SDICON); + writel(512, hw_dev->map_base + SDIBSIZE); + + return 0; +} + +/** + * Prepare engine's bits for the next command transfer + * @param cmd_flags MCI's command flags + * @param data_flags MCI's data flags + * @return Register bits for this transfer + */ +static uint32_t s3c_prepare_command_setup(unsigned cmd_flags, unsigned data_flags) +{ + uint32_t reg; + + /* source (=host) */ + reg = SDICMDCON_SENDERHOST; + + if (cmd_flags & MMC_RSP_PRESENT) { + reg |= SDICMDCON_WAITRSP; + pr_debug("Command with response\n"); + } + if (cmd_flags & MMC_RSP_136) { + reg |= SDICMDCON_LONGRSP; + pr_debug("Command with long response\n"); + } + if (cmd_flags & MMC_RSP_CRC) + ; /* FIXME */ + if (cmd_flags & MMC_RSP_BUSY) + ; /* FIXME */ + if (cmd_flags & MMC_RSP_OPCODE) + ; /* FIXME */ + if (data_flags != 0) + reg |= SDICMDCON_WITHDATA; + + return reg; +} + +/** + * Prepare engine's bits for the next data transfer + * @param hw_dev Host interface device instance + * @param data_flags MCI's data flags + * @return Register bits for this transfer + */ +static uint32_t s3c_prepare_data_setup(struct device_d *hw_dev, unsigned data_flags) +{ + struct s3c_mci_host *host_data = (struct s3c_mci_host*)GET_HOST_DATA(hw_dev); + uint32_t reg = SDIDCON_BLOCKMODE; /* block mode only is supported */ + + if (host_data->bus_width == 1) + reg |= SDIDCON_WIDEBUS; + + /* enable any kind of data transfers on demand only */ + if (data_flags & MMC_DATA_WRITE) + reg |= SDIDCON_TXAFTERRESP | SDIDCON_XFER_TXSTART; + + if (data_flags & MMC_DATA_READ) + reg |= SDIDCON_RXAFTERCMD | SDIDCON_XFER_RXSTART; + + /* TODO: Support more than the 2440 CPU */ + reg |= SDIDCON_DS_WORD | SDIDCON_DATSTART; + + return reg; +} + +/** + * Terminate a current running transfer + * @param hw_dev Host interface device instance + * @return 0 on success + * + * Note: Try to stop a running transfer. This should not happen, as all + * transfers must complete in this driver. But who knows... ;-) + */ +static int s3c_terminate_transfer(struct device_d *hw_dev) +{ + unsigned stoptries = 3; + + while (readl(hw_dev->map_base + SDIDSTA) & (SDIDSTA_TXDATAON | SDIDSTA_RXDATAON)) { + pr_debug("Transfer still in progress.\n"); + + writel(SDIDCON_STOP, hw_dev->map_base + SDIDCON); + s3c_mci_initialize(hw_dev, NULL); + + if ((stoptries--) == 0) { + pr_warning("Cannot stop the engine!\n"); + return -EINVAL; + } + } + + return 0; +} + +/** + * Setup registers for data transfer + * @param hw_dev Host interface device instance + * @param data The data information (buffer, direction aso.) + * @return 0 on success + */ +static int s3c_prepare_data_transfer(struct device_d *hw_dev, struct mci_data *data) +{ + uint32_t reg; + + writel(data->blocksize, hw_dev->map_base + SDIBSIZE); + reg = s3c_prepare_data_setup(hw_dev, data->flags); + reg |= data->blocks & SDIDCON_BLKNUM; + writel(reg, hw_dev->map_base + SDIDCON); + writel(0x007FFFFF, hw_dev->map_base + SDITIMER); + + return 0; +} + +/** + * Send a command and receive the response + * @param hw_dev Host interface device instance + * @param cmd The command to handle + * @param data The data information (buffer, direction aso.) + * @return 0 on success + */ +static int s3c_send_command(struct device_d *hw_dev, struct mci_cmd *cmd, + struct mci_data *data) +{ + uint32_t reg, t1; + int rc; + + writel(0x007FFFFF, hw_dev->map_base + SDITIMER); + + /* setup argument */ + writel(cmd->cmdarg, hw_dev->map_base + SDICMDARG); + + /* setup command and transfer characteristic */ + reg = s3c_prepare_command_setup(cmd->resp_type, data != NULL ? data->flags : 0); + reg |= cmd->cmdidx & SDICMDCON_INDEX; + + /* run the command right now */ + writel(reg | SDICMDCON_CMDSTART, hw_dev->map_base + SDICMDCON); + t1 = readl(hw_dev->map_base + SDICMDSTAT); + /* wait until command is done */ + while (1) { + reg = readl(hw_dev->map_base + SDICMDSTAT); + /* done? */ + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (reg & SDICMDSTAT_RSPFIN) { + writel(SDICMDSTAT_RSPFIN, + hw_dev->map_base + SDICMDSTAT); + rc = 0; + break; + } + } else { + if (reg & SDICMDSTAT_CMDSENT) { + writel(SDICMDSTAT_CMDSENT, + hw_dev->map_base + SDICMDSTAT); + rc = 0; + break; + } + } + /* timeout? */ + if (reg & SDICMDSTAT_CMDTIMEOUT) { + writel(SDICMDSTAT_CMDTIMEOUT, + hw_dev->map_base + SDICMDSTAT); + rc = -ETIMEDOUT; + break; + } + } + + if ((rc == 0) && (cmd->resp_type & MMC_RSP_PRESENT)) { + cmd->response[0] = readl(hw_dev->map_base + SDIRSP0); + cmd->response[1] = readl(hw_dev->map_base + SDIRSP1); + cmd->response[2] = readl(hw_dev->map_base + SDIRSP2); + cmd->response[3] = readl(hw_dev->map_base + SDIRSP3); + } + /* do not disable the clock! */ + return rc; +} + +/** + * Clear major registers prior a new transaction + * @param hw_dev Host interface device instance + * @return 0 on success + * + * FIFO clear is only necessary on 2440, but doesn't hurt on 2410 + */ +static int s3c_prepare_engine(struct device_d *hw_dev) +{ + int rc; + + rc = s3c_terminate_transfer(hw_dev); + if (rc != 0) + return rc; + + writel(-1, hw_dev->map_base + SDICMDSTAT); + writel(-1, hw_dev->map_base + SDIDSTA); + writel(-1, hw_dev->map_base + SDIFSTA); + + return 0; +} + +/** + * Handle MCI commands without data + * @param hw_dev Host interface device instance + * @param cmd The command to handle + * @return 0 on success + * + * This functions handles the following MCI commands: + * - "broadcast command (BC)" without a response + * - "broadcast commands with response (BCR)" + * - "addressed command (AC)" with response, but without data + */ +static int s3c_mci_std_cmds(struct device_d *hw_dev, struct mci_cmd *cmd) +{ + int rc; + + rc = s3c_prepare_engine(hw_dev); + if (rc != 0) + return 0; + + return s3c_send_command(hw_dev, cmd, NULL); +} + +/** + * Read one block of data from the FIFO + * @param hw_dev Host interface device instance + * @param data The data information (buffer, direction aso.) + * @return 0 on success + */ +static int s3c_mci_read_block(struct device_d *hw_dev, struct mci_data *data) +{ + uint32_t *p; + unsigned cnt, data_size; + +#define READ_REASON_TO_FAIL (SDIDSTA_CRCFAIL | SDIDSTA_RXCRCFAIL | SDIDSTA_DATATIMEOUT) + + p = (uint32_t*)data->dest; + data_size = data->blocksize * data->blocks; + + while (data_size > 0) { + + /* serious error? */ + if (readl(hw_dev->map_base + SDIDSTA) & READ_REASON_TO_FAIL) { + pr_err("Failed while reading data\n"); + return -EIO; + } + + /* now check the FIFO status */ + if (readl(hw_dev->map_base + SDIFSTA) & SDIFSTA_FIFOFAIL) { + pr_err("Data loss due to FIFO overflow when reading\n"); + return -EIO; + } + + /* we only want to read full words */ + cnt = (readl(hw_dev->map_base + SDIFSTA) & SDIFSTA_COUNTMASK) >> 2; + + /* read one chunk of data from the FIFO */ + while (cnt--) { + *p = readl(hw_dev->map_base + SDIDATA); + p++; + if (data_size >= 4) + data_size -= 4; + else { + data_size = 0; + break; + } + } + } + + return 0; +} + +/** + * Write one block of data into the FIFO + * @param hw_dev Host interface device instance + * @param cmd The command to handle + * @param data The data information (buffer, direction aso.) + * @return 0 on success + * + * We must ensure data in the FIFO when the command phase changes into the + * data phase. To ensure this, the FIFO gets filled first, then the command. + */ +static int s3c_mci_write_block(struct device_d *hw_dev, struct mci_cmd *cmd, + struct mci_data *data) +{ + const uint32_t *p = (const uint32_t*)data->src; + unsigned cnt, data_size; + uint32_t reg; + +#define WRITE_REASON_TO_FAIL (SDIDSTA_CRCFAIL | SDIDSTA_DATATIMEOUT) + + data_size = data->blocksize * data->blocks; + /* + * With high clock rates we must fill the FIFO as early as possible + * Its size is 16 words. We assume its empty, when this function is + * entered. + */ + cnt = 16; + while (cnt--) { + writel(*p, hw_dev->map_base + SDIDATA); + p++; + if (data_size >= 4) + data_size -= 4; + else { + data_size = 0; + break; + } + } + + /* data is now in place and waits for transmitt. Start the command right now */ + s3c_send_command(hw_dev, cmd, data); + + if ((reg = readl(hw_dev->map_base + SDIFSTA)) & SDIFSTA_FIFOFAIL) { + pr_err("Command fails immediatly due to FIFO underrun when writing %08X\n", + reg); + return -EIO; + } + + while (data_size > 0) { + + if (readl(hw_dev->map_base + SDIDSTA) & WRITE_REASON_TO_FAIL) { + pr_err("Failed writing data\n"); + return -EIO; + } + + /* now check the FIFO status */ + if ((reg = readl(hw_dev->map_base + SDIFSTA)) & SDIFSTA_FIFOFAIL) { + pr_err("Data loss due to FIFO underrun when writing %08X\n", + reg); + return -EIO; + } + + /* we only want to write full words */ + cnt = 16 - (((readl(hw_dev->map_base + SDIFSTA) & SDIFSTA_COUNTMASK) + 3) >> 2); + + /* fill the FIFO if it has free entries */ + while (cnt--) { + writel(*p, hw_dev->map_base + SDIDATA); + p++; + if (data_size >= 4) + data_size -= 4; + else { + data_size = 0; + break; + } + } + } + + return 0; +} + +/** + * Handle MCI commands with or without data + * @param hw_dev Host interface device instance + * @param cmd The command to handle + * @param data The data information (buffer, direction aso.) + * @return 0 on success +*/ +static int s3c_mci_adtc(struct device_d *hw_dev, struct mci_cmd *cmd, + struct mci_data *data) +{ + int rc; + + rc = s3c_prepare_engine(hw_dev); + if (rc != 0) + return rc; + + rc = s3c_prepare_data_transfer(hw_dev, data); + if (rc != 0) + return rc; + + if (data->flags & MMC_DATA_READ) { + s3c_send_command(hw_dev, cmd, data); + rc = s3c_mci_read_block(hw_dev, data); + if (rc == 0) { + while (!(readl(hw_dev->map_base + SDIDSTA) & SDIDSTA_XFERFINISH)) + ; + } else + s3c_terminate_transfer(hw_dev); + } + + if (data->flags & MMC_DATA_WRITE) { + rc = s3c_mci_write_block(hw_dev, cmd, data); + if (rc == 0) { + while (!(readl(hw_dev->map_base + SDIDSTA) & SDIDSTA_XFERFINISH)) + ; + } else + s3c_terminate_transfer(hw_dev); + } + writel(0, hw_dev->map_base + SDIDCON); + + return rc; +} + +/* ------------------------- MCI API -------------------------------------- */ + +/** + * Keep the attached MMC/SD unit in a well know state + * @param mci_pdata MCI platform data + * @param mci_dev MCI device instance + * @return 0 on success, negative value else + */ +static int mci_reset(struct mci_host *mci_pdata, struct device_d *mci_dev) +{ + struct device_d *hw_dev = mci_pdata->hw_dev; + + return s3c_mci_initialize(hw_dev, mci_dev); +} + +/** + * Process one command to the MCI card + * @param mci_pdata MCI platform data + * @param cmd The command to process + * @param data The data to handle in the command (can be NULL) + * @return 0 on success, negative value else + */ +static int mci_request(struct mci_host *mci_pdata, struct mci_cmd *cmd, + struct mci_data *data) +{ + struct device_d *hw_dev = mci_pdata->hw_dev; + int rc; + + /* enable clock */ + writel(readl(hw_dev->map_base + SDICON) | SDICON_CLKEN, + hw_dev->map_base + SDICON); + + if ((cmd->resp_type == 0) || (data == NULL)) + rc = s3c_mci_std_cmds(hw_dev, cmd); + else + rc = s3c_mci_adtc(hw_dev, cmd, data); /* with response and data */ + + s3c_finish_request(hw_dev); + + /* disable clock */ + writel(readl(hw_dev->map_base + SDICON) & ~SDICON_CLKEN, + hw_dev->map_base + SDICON); + return rc; +} + +/** + * Setup the bus width and IO speed + * @param mci_pdata MCI platform data + * @param mci_dev MCI device instance + * @param bus_width New bus width value (1, 4 or 8) + * @param clock New clock in Hz (can be '0' to disable the clock) + */ +static void mci_set_ios(struct mci_host *mci_pdata, struct device_d *mci_dev, + unsigned bus_width, unsigned clock) +{ + struct device_d *hw_dev = mci_pdata->hw_dev; + struct s3c_mci_host *host_data = GET_HOST_DATA(hw_dev); + struct mci_host *host = GET_MCI_PDATA(mci_dev); + uint32_t reg; + + switch (bus_width) { + case 8: /* no 8 bit support, fall back to 4 bit */ + case 4: + host_data->bus_width = 1; + host->bus_width = 4; /* 4 bit is possible */ + break; + default: + host_data->bus_width = 0; + host->bus_width = 1; /* 1 bit is possible */ + break; + } + + reg = readl(hw_dev->map_base + SDICON); + if (clock) { + /* setup the IO clock frequency and enable it */ + host->clock = host_data->clock = s3c_setup_clock_speed(hw_dev, clock); + reg |= SDICON_CLKEN; /* enable the clock */ + } else { + reg &= ~SDICON_CLKEN; /* disable the clock */ + host->clock = host_data->clock = 0; + } + writel(reg, hw_dev->map_base + SDICON); + + pr_debug("IO settings: bus width=%d, frequency=%u Hz\n", + host->bus_width, host->clock); +} + +/* ----------------------------------------------------------------------- */ + +#ifdef CONFIG_MCI_INFO +static void s3c_info(struct device_d *hw_dev) +{ + struct s3c_mci_host *host = hw_dev->priv; + struct s3c_mci_platform_data *pd = hw_dev->platform_data; + + printf(" Bus data width: %d bit\n", host->bus_width == 1 ? 4 : 1); + printf(" Bus frequency: %u Hz\n", host->clock); + printf(" Frequency limits: "); + if (pd->f_min == 0) + printf("no lower limit "); + else + printf("%u Hz lower limit ", pd->f_min); + if (pd->f_max == 0) + printf("- no upper limit"); + else + printf("- %u Hz upper limit", pd->f_max); + printf("\n Card detection support: %s\n", + pd->gpio_detect != 0 ? "yes" : "no"); +} +#endif + +/* + * There is only one host MCI hardware instance available. + * It makes no sense to dynamically allocate this data + */ +static struct mci_host mci_pdata = { + .send_cmd = mci_request, + .set_ios = mci_set_ios, + .init = mci_reset, +}; + +static int s3c_mci_probe(struct device_d *hw_dev) +{ + struct s3c_mci_platform_data *pd = hw_dev->platform_data; + + /* TODO replace by the global func: enable the SDI unit clock */ + writel(readl(S3C24X0_CLOCK_POWER_BASE + 0x0c) | 0x200, + S3C24X0_CLOCK_POWER_BASE + 0x0c); + + if (pd == NULL) { + pr_err("Missing platform data\n"); + return -EINVAL; + } + + hw_dev->priv = &host_data; + mci_pdata.hw_dev = hw_dev; + + /* feed forward the platform specific values */ + mci_pdata.voltages = pd->voltages; + mci_pdata.host_caps = pd->caps; + mci_pdata.f_min = pd->f_min == 0 ? s3c24xx_get_pclk() / 256 : pd->f_min; + mci_pdata.f_max = pd->f_max == 0 ? s3c24xx_get_pclk() / 2 : pd->f_max; + + /* + * Start the clock to let the engine and the card finishes its startup + */ + host_data.clock = s3c_setup_clock_speed(hw_dev, mci_pdata.f_min); + writel(SDICON_FIFORESET | SDICON_MMCCLOCK, hw_dev->map_base + SDICON); + + return mci_register(&mci_pdata); +} + +static struct driver_d s3c_mci_driver = { + .name = "s3c_mci", + .probe = s3c_mci_probe, +#ifdef CONFIG_MCI_INFO + .info = s3c_info, +#endif +}; + +static int s3c_mci_init_driver(void) +{ + register_driver(&s3c_mci_driver); + return 0; +} + +device_initcall(s3c_mci_init_driver); |