From patchwork Mon Jun 11 12:48:26 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rajeshwari Shinde X-Patchwork-Id: 9190 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 7D49F23E56 for ; Mon, 11 Jun 2012 12:47:00 +0000 (UTC) Received: from mail-gh0-f180.google.com (mail-gh0-f180.google.com [209.85.160.180]) by fiordland.canonical.com (Postfix) with ESMTP id 111B1A182C1 for ; Mon, 11 Jun 2012 12:46:59 +0000 (UTC) Received: by ghbz12 with SMTP id z12so2633866ghb.11 for ; Mon, 11 Jun 2012 05:46:59 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf:x-auditid :from:to:cc:subject:date:message-id:x-mailer:x-brightmail-tracker :x-tm-as-mml:x-gm-message-state; bh=Wc0G32GlrX+538vPIPnmo01vhxrwe3XvnNrAw66iCsc=; b=g+fZfA6MkHMwqS31N6UzDZuwvXCIdNDyLhwbrmqH9bif7gVJNHRBW9REYMugz0q3j1 nujHXz2CKwYMNwykbFGkJ0agdlv9Q0nqxetFvbvpIVV5iVLXQ+RrENkqrjTpZBiFuDLB GbR5F8VIBwoCHHpKTRlLaTduxLtTyoNhi1JX0cuN6gU4AmX7jQq4h7bFH5R2fzzXgE0A DRvdZxGtWCX0KOONyLI62upfYc0DSKkOmgiWPjyx6oy9RjjhGLOmlgbLm9ZZKbXO7Umm EWZI9V+XOazEAcygvdgzEgXtOZwvqyvizshJR3GcY8NlMYiShDG3AzfxglAa10Mlt3Mk wQXQ== Received: by 10.50.46.232 with SMTP id y8mr6042322igm.57.1339418819307; Mon, 11 Jun 2012 05:46:59 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.231.24.148 with SMTP id v20csp107342ibb; Mon, 11 Jun 2012 05:46:57 -0700 (PDT) Received: by 10.68.227.197 with SMTP id sc5mr26777598pbc.58.1339418817054; Mon, 11 Jun 2012 05:46:57 -0700 (PDT) Received: from mailout3.samsung.com (mailout3.samsung.com. [203.254.224.33]) by mx.google.com with ESMTP id ps9si11168962pbc.296.2012.06.11.05.46.56; Mon, 11 Jun 2012 05:46:57 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of rajeshwari.s@samsung.com designates 203.254.224.33 as permitted sender) client-ip=203.254.224.33; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of rajeshwari.s@samsung.com designates 203.254.224.33 as permitted sender) smtp.mail=rajeshwari.s@samsung.com Received: from epcpsbgm1.samsung.com (mailout3.samsung.com [203.254.224.33]) by mailout3.samsung.com (Oracle Communications Messaging Server 7u4-24.01(7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTP id <0M5G00JJQE67PTY0@mailout3.samsung.com>; Mon, 11 Jun 2012 21:46:55 +0900 (KST) X-AuditID: cbfee61a-b7f9f6d0000016a8-98-4fd5e8bf31ca Received: from epmmp1.local.host ( [203.254.227.16]) by epcpsbgm1.samsung.com (EPCPMTA) with SMTP id 3A.6C.05800.FB8E5DF4; Mon, 11 Jun 2012 21:46:55 +0900 (KST) Received: from rajeshwari-linux.sisodomain.com ([107.108.215.115]) by mmp1.samsung.com (Oracle Communications Messaging Server 7u4-24.01(7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTPA id <0M5G005XNE165U60@mmp1.samsung.com>; Mon, 11 Jun 2012 21:46:55 +0900 (KST) From: Rajeshwari Shinde To: u-boot@lists.denx.de Cc: patches@linaro.org, sjg@chromium.org, mk7.kang@samsung.com, chander.kashyap@linaro.org, afleming@gmail.com, tlambert@chromium.org, alim.akhtar@samsung.com Subject: [PATCH V2] MMC: DWMMC: Add DWMMC driver Date: Mon, 11 Jun 2012 18:18:26 +0530 Message-id: <1339418906-20590-1-git-send-email-rajeshwari.s@samsung.com> X-Mailer: git-send-email 1.7.4.4 X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFrrMJMWRmVeSWpSXmKPExsVy+t9jAd39L676G7z/zmnxcP1NFosph7+w ODB53Lm2hy2AMYrLJiU1J7MstUjfLoErY9r+RtaCjbsZK77d3crSwDhhOmMXIyeHhICJxKRj L9kgbDGJC/fWA9lcHEICixglzr16BOVMZJL4uWMJWBWbgJHE1pPTwLpFBCQkfvVfZQQpYgbp uPJvAzNIQljAQOLK26tgNouAqsTEqSvBbF4BD4lfcxZDrVOQODb1K+sERu4FjAyrGEVTC5IL ipPScw31ihNzi0vz0vWS83M3MYK9+kxqB+PKBotDjAIcjEo8vAWPrvoLsSaWFVfmHmKU4GBW EuGNvAgU4k1JrKxKLcqPLyrNSS0+xCjNwaIkzttkfcFfSCA9sSQ1OzW1ILUIJsvEwSkFDAlt hodOR1g908X452m/nTB5Z93l6KNvFCO8Mr88qG9e3B0UvVppR65zbtnhc4tipRvvcPiyMwUJ sya67f3bs+fYsQllgovOzszhtsiJVOtZn3jGXGF2wNzPuktnP1g1LdffhOn8oRJzzYJbPPHT ZLsiv1zd7n31NtO1NfWN/av17/zo13E0VGIpzkg01GIuKk4EAFCw/3vmAQAA X-TM-AS-MML: No X-Gm-Message-State: ALoCoQkoA/YB/BPwfwB3iO6FnBH/tV5ClpSBQYhY/ImtX74Rc3YhXzqvIgyRw7x5A6BQ2hfwinBv Add DWMMC driver support and resgister description for same. Signed-off-by: Alim Akhtar Signed-off-by: Terry Lambert Signed-off-by: Rajeshwari Shinde --- Changes in V2: - Incorporated comments from Jaehung Chung. - Renamed MSHCI to DWMMC through out the driver. - Renamed files to exynos_dwmmc from exynos_mshc. - Removed major hard codings of values. - Wrote dw_mci_writel and dw_mci_readl functions for writel and readl. - Removed structure of registers and defined each one separately. orch/arm/include/asm/arch-exynos/exynos_dwmmc.h | 229 +++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/exynos_dwmmc.c | 566 +++++++++++++++++++++++ 3 files changed, 796 insertions(+), 0 deletions(-) create mode 100644 arch/arm/include/asm/arch-exynos/exynos_dwmmc.h create mode 100644 drivers/mmc/exynos_dwmmc.c diff --git a/arch/arm/include/asm/arch-exynos/exynos_dwmmc.h b/arch/arm/include/asm/arch-exynos/exynos_dwmmc.h new file mode 100644 index 0000000..349bd75 --- /dev/null +++ b/arch/arm/include/asm/arch-exynos/exynos_dwmmc.h @@ -0,0 +1,229 @@ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Abhilash Kesavan + * + * 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 __ASM_ARCH_COMMON_DWMMC_H +#define __ASM_ARCH_COMMON_DWMMC_H + +#include + +#ifndef __ASSEMBLY__ +struct dw_mci_host { + void *ioaddr; + unsigned int clock; /* Current clock in MHz */ + enum periph_id peripheral; + unsigned int verid; /* SDHCI spec. version */ + unsigned int data_offset; /* DATA offset */ +}; + +/* + * Struct idma + * Holds the descriptor list + */ +struct dw_mci_idmac { + u32 des0; + u32 des1; + u32 des2; + u32 des3; +}; + +/* Control Register Register */ +#define DWMCI_CONTROL 0x00 +#define CTRL_RESET (0x1 << 0) +#define FIFO_RESET (0x1 << 1) +#define DMA_RESET (0x1 << 2) +#define DMA_ENABLE (0x1 << 5) +#define SEND_AS_CCSD (0x1 << 10) +#define ENABLE_IDMAC (0x1 << 25) + +/* Power Enable Register */ +#define DWMCI_PWREN 0x04 +#define POWER_ENABLE (0x1 << 0) + +#define DWMCI_CLKDIV 0x08 +#define DWMCI_CLKSRC 0x0c + +/* Clock Enable Register */ +#define DWMCI_CLKENA 0x10 +#define CLK_ENABLE (0x1 << 0) +#define CLK_DISABLE (0x0 << 0) + +/* Timeout Register */ +#define DWMCI_TMOUT 0x14 +#define TMOUT_MAX 0xffffffff + +/* Card Type Register */ +#define DWMCI_CTYPE 0x18 +#define PORT0_CARD_WIDTH1 0 +#define PORT0_CARD_WIDTH4 (0x1 << 0) +#define PORT0_CARD_WIDTH8 (0x1 << 16) + +#define DWMCI_BLKSIZE 0x1c +#define DWMCI_BYTCNT 0x20 + +/* Interrupt Mask Register */ +#define DWMCI_INTMASK 0x24 +#define INTMSK_ALL 0xffffffff +#define INTMSK_RE (0x1 << 1) +#define INTMSK_CDONE (0x1 << 2) +#define INTMSK_DTO (0x1 << 3) +#define INTMSK_DCRC (0x1 << 7) +#define INTMSK_RTO (0x1 << 8) +#define INTMSK_DRTO (0x1 << 9) +#define INTMSK_HTO (0x1 << 10) +#define INTMSK_FRUN (0x1 << 11) +#define INTMSK_HLE (0x1 << 12) +#define INTMSK_SBE (0x1 << 13) +#define INTMSK_ACD (0x1 << 14) +#define INTMSK_EBE (0x1 << 15) + +#define DWMCI_CMDARG 0x28 + +/* Command Register */ +#define DWMCI_CMD 0x2c +#define CMD_RESP_EXP_BIT (0x1 << 6) +#define CMD_RESP_LENGTH_BIT (0x1 << 7) +#define CMD_CHECK_CRC_BIT (0x1 << 8) +#define CMD_DATA_EXP_BIT (0x1 << 9) +#define CMD_RW_BIT (0x1 << 10) +#define CMD_SENT_AUTO_STOP_BIT (0x1 << 12) +#define CMD_WAIT_PRV_DAT_BIT (0x1 << 13) +#define CMD_SEND_CLK_ONLY (0x1 << 21) +#define CMD_USE_HOLD_REG (0x1 << 29) +#define CMD_STRT_BIT (0x1 << 31) +#define CMD_ONLY_CLK (CMD_STRT_BIT | CMD_SEND_CLK_ONLY | \ + CMD_WAIT_PRV_DAT_BIT) + +#define DWMCI_RESP0 0x30 +#define DWMCI_RESP1 0x34 +#define DWMCI_RESP2 0x38 +#define DWMCI_RESP3 0x3c + +#define DWMCI_MINTSTS 0x40 + +/* Raw Interrupt Register */ +#define DWMCI_RINTSTS 0x44 +#define DATA_ERR (INTMSK_EBE | INTMSK_SBE | INTMSK_HLE | \ + INTMSK_FRUN | INTMSK_EBE | INTMSK_DCRC) +#define DATA_TOUT (INTMSK_HTO | INTMSK_DRTO) + +/* Status Register */ +#define DWMCI_STATUS 0x48 +#define DATA_BUSY (0x1 << 9) + +/* FIFO Threshold Watermark Register */ +#define DWMCI_FIFOTH 0x4c +#define TX_WMARK (0xFFF << 0) +#define RX_WMARK (0xFFF << 16) +#define MSIZE_MASK (0x7 << 28) + +#define DWMCI_CDETECT 0x50 +#define DWMCI_WRTORT 0x54 +#define DWMCI_GPIO 0x58 +#define DWMCI_TCBCNT 0x5c +#define DWMCI_TBBCNT 0x60 +#define DWMCI_DEBENCE 0x64 +#define DWMCI_USRID 0x68 +#define DWMCI_VERID 0x6c +#define DWMCI_HCON 0x70 +#define DWMCI_UHS_REG 0x74 +#define DWMCI_RST_n 0x78 + +/* DW DMA Mutiple Transaction Size */ +#define MSIZE_8 (2 << 28) + +/* Bus Mode Register */ +#define DWMCI_BMOD 0x80 +#define BMOD_IDMAC_RESET (0x1 << 0) +#define BMOD_IDMAC_FB (0x1 << 1) +#define BMOD_IDMAC_ENABLE (0x1 << 7) + +#define DWMCI_PLDMND 0x84 +#define DWMCI_DBADDR 0x88 + +/* IDMAC bits */ +#define DWMCI_IDSTS 0x8c +#define DWMCI_IDMAC_OWN (0x1 << 31) +#define DWMCI_IDMAC_CH (0x1 << 4) +#define DWMCI_IDMAC_FS (0x1 << 3) +#define DWMCI_IDMAC_LD (0x1 << 2) + +#define DWMCI_IDINTEN 0x90 +#define DWMCI_DSCADDR 0x94 +#define DWMCI_BUFADDR 0x98 + +/* CLKSEL bits*/ +#define DWMCI_CLKSEL 0x9c +#define SELCLK_SAMPLE_1PHASE_Shift (0x1 << 0) +#define SELCLK_DRV_3PHASE_SHIFT (0x3 << 16) +#define SELCLK_DRV_2PHASE_SHIFT (0x2 << 16) +#define SELCLK_DIV_RATIO (0x3 << 24) + +#define DWMCI_CARDTHRCTL 0x100 + +/* + * Data offset is difference according to Version + * Lower than 2.40a : data register offest is 0x100 + */ +#define DW_MMC_240A 0x240a +#define DATA_OFFSET 0x100 +#define DATA_240A_OFFSET 0x200 + +#define MAX_DWMMC_CLOCK 52000000 /* Max limit is 52MHz */ +#define MIN_DWMMC_CLOCK 400000 /* Lower limit is 400KHz */ +#define COMMAND_TIMEOUT 10000 +#define TIMEOUT_MS 100 +#define MAXCLKDIV 0xff + +/* Version ID register define */ +#define GET_VERID(x) ((x) & 0xFFFF) + +static inline void dw_mci_writel(struct dw_mci_host *host, u32 val, int reg) +{ + writel(val, host->ioaddr + reg); +} + +static inline void dw_mci_writew(struct dw_mci_host *host, u16 val, int reg) +{ + writew(val, host->ioaddr + reg); +} + +static inline void dw_mci_writeb(struct dw_mci_host *host, u8 val, int reg) +{ + writeb(val, host->ioaddr + reg); +} + +static inline u32 dw_mci_readl(struct dw_mci_host *host, int reg) +{ + return readl(host->ioaddr + reg); +} + +static inline u16 dw_mci_readw(struct dw_mci_host *host, int reg) +{ + return readw(host->ioaddr + reg); +} + +static inline u8 dw_mci_readb(struct dw_mci_host *host, int reg) +{ + return readb(host->ioaddr + reg); +} + +int dw_mci_init(enum periph_id periph_id, int bus_width); + +#endif /* __ASSEMBLY__ */ +#endif /* __ASM_ARCH_COMMON_DWMMC_H */ diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index c245352..cf0be05 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -27,6 +27,7 @@ LIB := $(obj)libmmc.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o COBJS-$(CONFIG_DAVINCI_MMC) += davinci_mmc.o +COBJS-$(CONFIG_EXYNOS_DWMMC) += exynos_dwmmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_FTSDC010) += ftsdc010_esdhc.o COBJS-$(CONFIG_GENERIC_MMC) += mmc.o diff --git a/drivers/mmc/exynos_dwmmc.c b/drivers/mmc/exynos_dwmmc.c new file mode 100644 index 0000000..96f6ceb --- /dev/null +++ b/drivers/mmc/exynos_dwmmc.c @@ -0,0 +1,566 @@ +/* + * (C) Copyright 2012 Samsung Electronics Co. Ltd + * + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +/* support 4 mmc hosts */ +enum { + MAX_MMC_HOSTS = 4 +}; + +static struct mmc dw_mci_dev[MAX_MMC_HOSTS]; +static struct dw_mci_host dw_mci_host[MAX_MMC_HOSTS]; +static int num_devs; + +/** + * Set bits of DWMMC host control register. + * + * @param host DWMMC host + * @param bits bits to be set + * @return 0 on success, TIMEOUT on failure + */ +static int dw_mci_setbits(struct dw_mci_host *host, unsigned int bits) +{ + ulong start; + + setbits_le32(host->ioaddr + DWMCI_CONTROL, bits); + + start = get_timer(0); + while (dw_mci_readl(host, DWMCI_CONTROL) & bits) { + if (get_timer(start) > TIMEOUT_MS) { + debug("Set bits failed\n"); + return TIMEOUT; + } + } + return 0; +} + +/** + * Reset DWMMC host control register. + * + * @param host DWMMC host + * @return 0 on success, TIMEOUT on failure + */ +static int dw_mci_reset_all(struct dw_mci_host *host) +{ + ulong start; + + /* + * Before we reset ciu check the DATA0 line. If it is low and + * we resets the ciu then we might see some errors. + */ + start = get_timer(0); + while (dw_mci_readl(host, DWMCI_STATUS) & DATA_BUSY) { + if (get_timer(start) > TIMEOUT_MS) { + debug("Controller did not release" + "data0 before ciu reset\n"); + return TIMEOUT; + } + } + return dw_mci_setbits(host, CTRL_RESET | FIFO_RESET | DMA_RESET); +} + +static void dw_mci_set_mdma_desc(u8 *desc_vir, u8 *desc_phy, + unsigned int des0, unsigned int des1, unsigned int des2) +{ + struct dw_mci_idmac *desc = (struct dw_mci_idmac *)desc_vir; + + desc->des0 = des0; + desc->des1 = des1; + desc->des2 = des2; + desc->des3 = (unsigned int)desc_phy + sizeof(struct dw_mci_idmac); +} + +static int dw_mci_prepare_data(struct dw_mci_host *host, struct mmc_data *data) +{ + unsigned int i, data_cnt, des_flag, blksz; + int err; + ulong data_start, data_end; + static struct dw_mci_idmac idmac_desc[0x10000]; + struct dw_mci_idmac *pdesc_dmac; + + err = dw_mci_setbits(host, FIFO_RESET); + if (err) { + debug("Fail to reset FIFO\n"); + return err; + } + + pdesc_dmac = idmac_desc; + blksz = data->blocksize; + data_cnt = data->blocks; + + for (i = 0;; i++) { + des_flag = (DWMCI_IDMAC_OWN | DWMCI_IDMAC_CH); + des_flag |= (i == 0) ? DWMCI_IDMAC_FS : 0; + if (data_cnt <= 8) { + des_flag |= DWMCI_IDMAC_LD; + dw_mci_set_mdma_desc((u8 *)pdesc_dmac, + (u8 *)virt_to_phys(pdesc_dmac), + des_flag, blksz * data_cnt, + (unsigned int)(virt_to_phys(data->dest)) + + (unsigned int)(i * 0x1000)); + break; + } + /* max transfer size is 4KB per descriptor */ + dw_mci_set_mdma_desc((u8 *)pdesc_dmac, + (u8 *)virt_to_phys(pdesc_dmac), + des_flag, blksz * 8, + virt_to_phys(data->dest) + + (unsigned int)(i * 0x1000)); + + data_cnt -= 8; + pdesc_dmac++; + } + + data_start = (ulong)idmac_desc; + data_end = (ulong)pdesc_dmac; + flush_dcache_range(data_start, data_end + ARCH_DMA_MINALIGN); + + data_start = (ulong)data->dest; + data_end = (ulong)(data->dest + data->blocks * data->blocksize); + flush_dcache_range(data_start, data_end); + + dw_mci_writel(host, (unsigned int)virt_to_phys(idmac_desc), + DWMCI_DBADDR); + + /* enable the Internal DMA Controller */ + setbits_le32(host->ioaddr + DWMCI_CONTROL, ENABLE_IDMAC | + DMA_ENABLE); + setbits_le32(host->ioaddr + DWMCI_BMOD, BMOD_IDMAC_ENABLE | + BMOD_IDMAC_FB); + + dw_mci_writel(host, data->blocksize, DWMCI_BLKSIZE); + dw_mci_writel(host, data->blocksize * data->blocks, DWMCI_BYTCNT); + + return 0; +} + +static int dw_mci_set_transfer_mode(struct dw_mci_host *host, + struct mmc_data *data) +{ + int mode = CMD_DATA_EXP_BIT; + + if (data->blocks > 1) + mode |= CMD_SENT_AUTO_STOP_BIT; + if (data->flags & MMC_DATA_WRITE) + mode |= CMD_RW_BIT; + + return mode; +} + +/* + * Sends a command out on the bus. + * + * @param mmc mmc device + * @param cmd mmc_cmd to be sent on bus + * @param data mmc data to be sent (optional) + * + * @return return 0 if ok, else error number + */ +static int dw_mci_send_command(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct dw_mci_host *host = mmc->priv; + + int flags = 0, i, err; + unsigned int mask; + ulong start, data_start, data_end; + + /* + * We shouldn't wait for data inihibit for stop commands, even + * though they might use busy signaling + */ + start = get_timer(0); + while (dw_mci_readl(host, DWMCI_STATUS) & DATA_BUSY) { + if (get_timer(start) > COMMAND_TIMEOUT) { + debug("timeout on data busy\n"); + return TIMEOUT; + } + } + + if (dw_mci_readl(host, DWMCI_RINTSTS)) { + if ((dw_mci_readl(host, DWMCI_RINTSTS) & + (INTMSK_CDONE | INTMSK_ACD)) == 0) + debug("there are pending interrupts 0x%x\n", + dw_mci_readl(host, DWMCI_RINTSTS)); + } + /* It clears all pending interrupts before sending a command*/ + dw_mci_writel(host, INTMSK_ALL, DWMCI_RINTSTS); + + if (data) { + err = dw_mci_prepare_data(host, data); + if (err) { + debug("fail to prepare data\n"); + return err; + } + } + + dw_mci_writel(host, cmd->cmdarg, DWMCI_CMDARG); + + if (data) + flags = dw_mci_set_transfer_mode(host, data); + + if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) + /* this is out of SD spec */ + return -1; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + flags |= CMD_RESP_EXP_BIT; + if (cmd->resp_type & MMC_RSP_136) + flags |= CMD_RESP_LENGTH_BIT; + } + + if (cmd->resp_type & MMC_RSP_CRC) + flags |= CMD_CHECK_CRC_BIT; + flags |= (cmd->cmdidx | CMD_STRT_BIT | CMD_USE_HOLD_REG | + CMD_WAIT_PRV_DAT_BIT); + + mask = dw_mci_readl(host, DWMCI_CMD); + if (mask & CMD_STRT_BIT) + debug("cmd busy, current cmd: %d", cmd->cmdidx); + + dw_mci_writel(host, flags, DWMCI_CMD); + /* wait for command complete by busy waiting. */ + for (i = 0; i < COMMAND_TIMEOUT; i++) { + mask = dw_mci_readl(host, DWMCI_RINTSTS); + if (mask & INTMSK_CDONE) { + if (!data) + dw_mci_writel(host, mask, DWMCI_RINTSTS); + break; + } + } + /* timeout for command complete. */ + if (COMMAND_TIMEOUT == i) { + debug("timeout waiting for status update\n"); + return TIMEOUT; + } + + if (mask & INTMSK_RTO) { + if (((cmd->cmdidx == MMC_CMD_SEND_EXT_CSD || + cmd->cmdidx == MMC_CMD_APP_CMD)) == 0) { + debug("response timeout error: 0x%x cmd: %d\n", + mask, cmd->cmdidx); + } + return TIMEOUT; + } else if (mask & INTMSK_RE) { + debug("response error: 0x%x cmd: %d\n", mask, cmd->cmdidx); + return -1; + } + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + /* CRC is stripped so we need to do some shifting. */ + cmd->response[0] = dw_mci_readl(host, + DWMCI_RESP3); + cmd->response[1] = dw_mci_readl(host, + DWMCI_RESP2); + cmd->response[2] = dw_mci_readl(host, + DWMCI_RESP1); + cmd->response[3] = dw_mci_readl(host, + DWMCI_RESP0); + } else { + cmd->response[0] = dw_mci_readl(host, DWMCI_RESP0); + debug("\tcmd->response[0]: 0x%08x\n", cmd->response[0]); + } + } + + if (data) { + while (!(mask & (DATA_ERR | DATA_TOUT | INTMSK_DTO))) + mask = dw_mci_readl(host, DWMCI_RINTSTS); + dw_mci_writel(host, mask, DWMCI_RINTSTS); + if (data->flags & MMC_DATA_READ) { + data_start = (ulong)data->dest; + data_end = (ulong)data->dest + + data->blocks * data->blocksize; + invalidate_dcache_range(data_start, data_end); + } + if (mask & (DATA_ERR | DATA_TOUT)) { + debug("error during transfer: 0x%x\n", mask); + /* make sure disable IDMAC and IDMAC_Interrupts */ + dw_mci_writel(host, (dw_mci_readl(host, DWMCI_CONTROL) & + ~(DMA_ENABLE | ENABLE_IDMAC)), DWMCI_CONTROL); + /* mask all interrupt source of IDMAC */ + dw_mci_writel(host, 0, DWMCI_IDINTEN); + return -1; + } else if (mask & INTMSK_DTO) { + debug("dwmmc dma interrupt end\n"); + } else { + debug("unexpected condition 0x%x\n", mask); + } + /* make sure disable IDMAC and IDMAC_Interrupts */ + dw_mci_writel(host, (dw_mci_readl(host, DWMCI_CONTROL) & + ~(DMA_ENABLE | ENABLE_IDMAC)), + DWMCI_CONTROL); + /* mask all interrupt source of IDMAC */ + dw_mci_writel(host, 0, DWMCI_IDINTEN); + } + + udelay(100); + + return 0; +} + +/* + * ON/OFF host controller clock + * + * @param host pointer to dw_mci_host + * @param val to enable/disable clock + */ +static void dw_mci_clock_onoff(struct dw_mci_host *host, int val) +{ + + if (val) + dw_mci_writel(host, CLK_ENABLE, DWMCI_CLKENA); + else + dw_mci_writel(host, CLK_DISABLE, DWMCI_CLKENA); + + dw_mci_writel(host, 0, DWMCI_CMD); + dw_mci_writel(host, CMD_ONLY_CLK, DWMCI_CMD); +} + +/* + * change host controller clock + * + * @param host pointer to dw_mci_host + * @param clock request clock + */ +static void dw_mci_change_clock(struct dw_mci_host *host, uint clock) +{ + int div; + u32 sclk_mshc; + + if (clock == host->clock) + return; + + /* If Input clock is higher than maximum mshc clock */ + if (clock > MAX_DWMMC_CLOCK) { + debug("Input clock is too high\n"); + clock = MAX_DWMMC_CLOCK; + } + + /* disable the clock before changing it */ + dw_mci_clock_onoff(host, CLK_DISABLE); + + /* get the clock division */ + if (host->peripheral == PERIPH_ID_SDMMC4) + sclk_mshc = get_dw_mci_clk_div(host->peripheral) / 2; + else + sclk_mshc = get_dw_mci_clk_div(host->peripheral) / 4; + + /* CLKDIV */ + for (div = 1 ; div <= MAXCLKDIV; div++) { + if ((sclk_mshc / (2 * div)) <= clock) { + dw_mci_writel(host, div, DWMCI_CLKDIV); + break; + } + } + + dw_mci_writel(host, 0, DWMCI_CMD); + dw_mci_writel(host, CMD_ONLY_CLK, DWMCI_CMD); + + dw_mci_writel(host, dw_mci_readl(host, DWMCI_CMD) & + (~CMD_SEND_CLK_ONLY), + DWMCI_CMD); + + dw_mci_clock_onoff(host, CLK_ENABLE); + host->clock = clock; +} + +/* + * Set ios for host controller clock + * + * This sets the card bus width and clksel + */ +static void dw_mci_set_ios(struct mmc *mmc) +{ + struct dw_mci_host *host = mmc->priv; + int val; + + debug("bus_width: %x, clock: %d\n", mmc->bus_width, mmc->clock); + + if (mmc->clock > 0) + dw_mci_change_clock(host, mmc->clock); + + if (mmc->bus_width == 8) + dw_mci_writel(host, PORT0_CARD_WIDTH8, DWMCI_CTYPE); + else if (mmc->bus_width == 4) + dw_mci_writel(host, PORT0_CARD_WIDTH4, DWMCI_CTYPE); + else + dw_mci_writel(host, PORT0_CARD_WIDTH1, DWMCI_CTYPE); + + val = dw_mci_readl(host, DWMCI_CLKSEL); + if (host->peripheral == PERIPH_ID_SDMMC0) + val |= (SELCLK_SAMPLE_1PHASE_Shift | SELCLK_DRV_3PHASE_SHIFT | + SELCLK_DIV_RATIO); + if (host->peripheral == PERIPH_ID_SDMMC2) + val |= (SELCLK_SAMPLE_1PHASE_Shift | SELCLK_DRV_2PHASE_SHIFT | + SELCLK_DIV_RATIO); + if (host->peripheral == PERIPH_ID_SDMMC4) + val |= (SELCLK_SAMPLE_1PHASE_Shift | SELCLK_DRV_2PHASE_SHIFT); + + dw_mci_writel(host, val, DWMCI_CLKSEL); +} + +/* + * Fifo init for host controller + */ +static void dw_mci_fifo_init(struct dw_mci_host *host) +{ + int fifo_val, fifo_depth, fifo_threshold; + + fifo_val = dw_mci_readl(host, DWMCI_FIFOTH); + + /* Power-on value of RX_WMark is FIFO_DEPTH-1 */ + fifo_depth = 1 + ((fifo_val >> 16) & 0x7ff); + fifo_threshold = fifo_depth / 2; + + fifo_val &= ~(RX_WMARK | TX_WMARK | MSIZE_MASK); + fifo_val |= (fifo_threshold | (fifo_threshold << 16) | MSIZE_8); + dw_mci_writel(host, fifo_val, DWMCI_FIFOTH); +} + + +static int dw_mci_reset(struct dw_mci_host *host) +{ + int err; + + /* power on the card */ + dw_mci_writel(host, POWER_ENABLE, DWMCI_PWREN); + + err = dw_mci_reset_all(host); + if (err) + return err; + + dw_mci_fifo_init(host); + + /* clear all pending interrupts */ + dw_mci_writel(host, INTMSK_ALL, DWMCI_RINTSTS); + + /* interrupts are not used, disable all */ + dw_mci_writel(host, 0, DWMCI_INTMASK); + + return 0; +} + +static int dw_mci_initialize(struct mmc *mmc) +{ + struct dw_mci_host *host = (struct dw_mci_host *)mmc->priv; + unsigned int ier; + int err; + + err = dw_mci_reset(host); + if (err) + return err; + + /* enumerate at 400KHz */ + dw_mci_change_clock(host, MIN_DWMMC_CLOCK); + + /* set auto stop command */ + ier = dw_mci_readl(host, DWMCI_CONTROL); + ier |= SEND_AS_CCSD; + dw_mci_writel(host, ier, DWMCI_CONTROL); + + /* set 1bit card mode */ + dw_mci_writel(host, PORT0_CARD_WIDTH1, DWMCI_CTYPE); + + dw_mci_writel(host, 0xfffff, DWMCI_DEBENCE); + + /* set bus mode register for IDMAC */ + dw_mci_writel(host, BMOD_IDMAC_RESET, DWMCI_BMOD); + + dw_mci_writel(host, 0x0, DWMCI_IDINTEN); + + /* set the max timeout for data and response */ + dw_mci_writel(host, TMOUT_MAX, DWMCI_TMOUT); + + return 0; +} + +int dw_mci_init(enum periph_id periph_id, int bus_width) +{ + struct dw_mci_host *mmc_host; + struct mmc *mmc; + + if (num_devs == MAX_MMC_HOSTS) { + debug("%s: Too many hosts\n", __func__); + return -1; + } + + /* set the clock for dwmmc controller */ + if (set_dw_mci_clk_div(periph_id)) { + debug("clock_set_dw_mci failed\n"); + return -EINVAL; + } + + mmc = &dw_mci_dev[num_devs]; + mmc_host = &dw_mci_host[num_devs]; + + sprintf(mmc->name, "DWMMC%d", num_devs); + num_devs++; + + mmc->priv = mmc_host; + mmc->send_cmd = dw_mci_send_command; + mmc->set_ios = dw_mci_set_ios; + mmc->init = dw_mci_initialize; + + /* + * In 2.40a spec, Data offset is changed. + * Need to check the version-id and set data-offset for DATA register. + */ + mmc_host->verid = GET_VERID(dw_mci_readl(mmc_host, DWMCI_VERID)); + debug("Version ID is %04x\n", mmc_host->verid); + + if (mmc_host->verid < DW_MMC_240A) + mmc_host->data_offset = DATA_OFFSET; + else + mmc_host->data_offset = DATA_240A_OFFSET; + + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS | MMC_MODE_HC; + + if (bus_width == 8) + mmc->host_caps |= MMC_MODE_8BIT; + else + mmc->host_caps |= MMC_MODE_4BIT; + + mmc->f_min = MIN_DWMMC_CLOCK; + mmc->f_max = MAX_DWMMC_CLOCK; + + exynos_pinmux_config(periph_id, + bus_width == 8 ? PINMUX_FLAG_8BIT_MODE : 0); + + mmc_host->clock = 0; + mmc_host->peripheral = periph_id; + mmc_host->ioaddr = (void *)samsung_get_base_dwmmc(); + mmc->b_max = 1; + mmc_register(mmc); + mmc->block_dev.removable = 1; + debug("dwmmc: periph_id=%d, width=%d, ioaddr=%p\n", + periph_id, bus_width, mmc_host->ioaddr); + + return 0; +}