Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / u-boot / drivers / mmc / gen_atmel_mci.c
diff --git a/qemu/roms/u-boot/drivers/mmc/gen_atmel_mci.c b/qemu/roms/u-boot/drivers/mmc/gen_atmel_mci.c
new file mode 100644 (file)
index 0000000..acca026
--- /dev/null
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2010
+ * Rob Emanuele <rob@emanuele.us>
+ * Reinhard Meyer, EMK Elektronik <reinhard.meyer@emk-elektronik.de>
+ *
+ * Original Driver:
+ * Copyright (C) 2004-2006 Atmel Corporation
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#include <common.h>
+#include <mmc.h>
+#include <part.h>
+#include <malloc.h>
+#include <asm/io.h>
+#include <asm/errno.h>
+#include <asm/byteorder.h>
+#include <asm/arch/clk.h>
+#include <asm/arch/hardware.h>
+#include "atmel_mci.h"
+
+#ifndef CONFIG_SYS_MMC_CLK_OD
+# define CONFIG_SYS_MMC_CLK_OD 150000
+#endif
+
+#define MMC_DEFAULT_BLKLEN     512
+
+#if defined(CONFIG_ATMEL_MCI_PORTB)
+# define MCI_BUS 1
+#else
+# define MCI_BUS 0
+#endif
+
+static int initialized = 0;
+
+/* Read Atmel MCI IP version */
+static unsigned int atmel_mci_get_version(struct atmel_mci *mci)
+{
+       return readl(&mci->version) & 0x00000fff;
+}
+
+/*
+ * Print command and status:
+ *
+ * - always when DEBUG is defined
+ * - on command errors
+ */
+static void dump_cmd(u32 cmdr, u32 arg, u32 status, const char* msg)
+{
+       printf("gen_atmel_mci: CMDR %08x (%2u) ARGR %08x (SR: %08x) %s\n",
+               cmdr, cmdr&0x3F, arg, status, msg);
+}
+
+/* Setup for MCI Clock and Block Size */
+static void mci_set_mode(struct mmc *mmc, u32 hz, u32 blklen)
+{
+       atmel_mci_t *mci = mmc->priv;
+       u32 bus_hz = get_mci_clk_rate();
+       u32 clkdiv = 255;
+
+       debug("mci: bus_hz is %u, setting clock %u Hz, block size %u\n",
+               bus_hz, hz, blklen);
+       if (hz > 0) {
+               /* find lowest clkdiv yielding a rate <= than requested */
+               for (clkdiv=0; clkdiv<255; clkdiv++) {
+                       if ((bus_hz / (clkdiv+1) / 2) <= hz)
+                               break;
+               }
+       }
+       printf("mci: setting clock %u Hz, block size %u\n",
+               (bus_hz / (clkdiv+1)) / 2, blklen);
+
+       blklen &= 0xfffc;
+       /* On some platforms RDPROOF and WRPROOF are ignored */
+       writel((MMCI_BF(CLKDIV, clkdiv)
+                | MMCI_BF(BLKLEN, blklen)
+                | MMCI_BIT(RDPROOF)
+                | MMCI_BIT(WRPROOF)), &mci->mr);
+       /*
+        * On some new platforms BLKLEN in mci->mr is ignored.
+        * Should use the BLKLEN in the block register.
+        */
+       writel(MMCI_BF(BLKLEN, blklen), &mci->blkr);
+       initialized = 1;
+}
+
+/* Return the CMDR with flags for a given command and data packet */
+static u32 mci_encode_cmd(
+       struct mmc_cmd *cmd, struct mmc_data *data, u32* error_flags)
+{
+       u32 cmdr = 0;
+
+       /* Default Flags for Errors */
+       *error_flags |= (MMCI_BIT(DTOE) | MMCI_BIT(RDIRE) | MMCI_BIT(RENDE) |
+               MMCI_BIT(RINDE) | MMCI_BIT(RTOE));
+
+       /* Default Flags for the Command */
+       cmdr |= MMCI_BIT(MAXLAT);
+
+       if (data) {
+               cmdr |= MMCI_BF(TRCMD, 1);
+               if (data->blocks > 1)
+                       cmdr |= MMCI_BF(TRTYP, 1);
+               if (data->flags & MMC_DATA_READ)
+                       cmdr |= MMCI_BIT(TRDIR);
+       }
+
+       if (cmd->resp_type & MMC_RSP_CRC)
+               *error_flags |= MMCI_BIT(RCRCE);
+       if (cmd->resp_type & MMC_RSP_136)
+               cmdr |= MMCI_BF(RSPTYP, 2);
+       else if (cmd->resp_type & MMC_RSP_BUSY)
+               cmdr |= MMCI_BF(RSPTYP, 3);
+       else if (cmd->resp_type & MMC_RSP_PRESENT)
+               cmdr |= MMCI_BF(RSPTYP, 1);
+
+       return cmdr | MMCI_BF(CMDNB, cmd->cmdidx);
+}
+
+/* Entered into function pointer in mci_send_cmd */
+static u32 mci_data_read(atmel_mci_t *mci, u32* data, u32 error_flags)
+{
+       u32 status;
+
+       do {
+               status = readl(&mci->sr);
+               if (status & (error_flags | MMCI_BIT(OVRE)))
+                       goto io_fail;
+       } while (!(status & MMCI_BIT(RXRDY)));
+
+       if (status & MMCI_BIT(RXRDY)) {
+               *data = readl(&mci->rdr);
+               status = 0;
+       }
+io_fail:
+       return status;
+}
+
+/* Entered into function pointer in mci_send_cmd */
+static u32 mci_data_write(atmel_mci_t *mci, u32* data, u32 error_flags)
+{
+       u32 status;
+
+       do {
+               status = readl(&mci->sr);
+               if (status & (error_flags | MMCI_BIT(UNRE)))
+                       goto io_fail;
+       } while (!(status & MMCI_BIT(TXRDY)));
+
+       if (status & MMCI_BIT(TXRDY)) {
+               writel(*data, &mci->tdr);
+               status = 0;
+       }
+io_fail:
+       return status;
+}
+
+/*
+ * Entered into mmc structure during driver init
+ *
+ * Sends a command out on the bus and deals with the block data.
+ * Takes the mmc pointer, a command pointer, and an optional data pointer.
+ */
+static int
+mci_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
+{
+       atmel_mci_t *mci = mmc->priv;
+       u32 cmdr;
+       u32 error_flags = 0;
+       u32 status;
+
+       if (!initialized) {
+               puts ("MCI not initialized!\n");
+               return COMM_ERR;
+       }
+
+       /* Figure out the transfer arguments */
+       cmdr = mci_encode_cmd(cmd, data, &error_flags);
+
+       /* For multi blocks read/write, set the block register */
+       if ((cmd->cmdidx == MMC_CMD_READ_MULTIPLE_BLOCK)
+                       || (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK))
+               writel(data->blocks | MMCI_BF(BLKLEN, mmc->read_bl_len),
+                       &mci->blkr);
+
+       /* Send the command */
+       writel(cmd->cmdarg, &mci->argr);
+       writel(cmdr, &mci->cmdr);
+
+#ifdef DEBUG
+       dump_cmd(cmdr, cmd->cmdarg, 0, "DEBUG");
+#endif
+
+       /* Wait for the command to complete */
+       while (!((status = readl(&mci->sr)) & MMCI_BIT(CMDRDY)));
+
+       if ((status & error_flags) & MMCI_BIT(RTOE)) {
+               dump_cmd(cmdr, cmd->cmdarg, status, "Command Time Out");
+               return TIMEOUT;
+       } else if (status & error_flags) {
+               dump_cmd(cmdr, cmd->cmdarg, status, "Command Failed");
+               return COMM_ERR;
+       }
+
+       /* Copy the response to the response buffer */
+       if (cmd->resp_type & MMC_RSP_136) {
+               cmd->response[0] = readl(&mci->rspr);
+               cmd->response[1] = readl(&mci->rspr1);
+               cmd->response[2] = readl(&mci->rspr2);
+               cmd->response[3] = readl(&mci->rspr3);
+       } else
+               cmd->response[0] = readl(&mci->rspr);
+
+       /* transfer all of the blocks */
+       if (data) {
+               u32 word_count, block_count;
+               u32* ioptr;
+               u32 sys_blocksize, dummy, i;
+               u32 (*mci_data_op)
+                       (atmel_mci_t *mci, u32* data, u32 error_flags);
+
+               if (data->flags & MMC_DATA_READ) {
+                       mci_data_op = mci_data_read;
+                       sys_blocksize = mmc->read_bl_len;
+                       ioptr = (u32*)data->dest;
+               } else {
+                       mci_data_op = mci_data_write;
+                       sys_blocksize = mmc->write_bl_len;
+                       ioptr = (u32*)data->src;
+               }
+
+               status = 0;
+               for (block_count = 0;
+                               block_count < data->blocks && !status;
+                               block_count++) {
+                       word_count = 0;
+                       do {
+                               status = mci_data_op(mci, ioptr, error_flags);
+                               word_count++;
+                               ioptr++;
+                       } while (!status && word_count < (data->blocksize/4));
+#ifdef DEBUG
+                       if (data->flags & MMC_DATA_READ)
+                       {
+                               printf("Read Data:\n");
+                               print_buffer(0, data->dest, 1,
+                                       word_count*4, 0);
+                       }
+#endif
+#ifdef DEBUG
+                       if (!status && word_count < (sys_blocksize / 4))
+                               printf("filling rest of block...\n");
+#endif
+                       /* fill the rest of a full block */
+                       while (!status && word_count < (sys_blocksize / 4)) {
+                               status = mci_data_op(mci, &dummy,
+                                       error_flags);
+                               word_count++;
+                       }
+                       if (status) {
+                               dump_cmd(cmdr, cmd->cmdarg, status,
+                                       "Data Transfer Failed");
+                               return COMM_ERR;
+                       }
+               }
+
+               /* Wait for Transfer End */
+               i = 0;
+               do {
+                       status = readl(&mci->sr);
+
+                       if (status & error_flags) {
+                               dump_cmd(cmdr, cmd->cmdarg, status,
+                                       "DTIP Wait Failed");
+                               return COMM_ERR;
+                       }
+                       i++;
+               } while ((status & MMCI_BIT(DTIP)) && i < 10000);
+               if (status & MMCI_BIT(DTIP)) {
+                       dump_cmd(cmdr, cmd->cmdarg, status,
+                               "XFER DTIP never unset, ignoring");
+               }
+       }
+
+       return 0;
+}
+
+/* Entered into mmc structure during driver init */
+static void mci_set_ios(struct mmc *mmc)
+{
+       atmel_mci_t *mci = mmc->priv;
+       int bus_width = mmc->bus_width;
+       unsigned int version = atmel_mci_get_version(mci);
+       int busw;
+
+       /* Set the clock speed */
+       mci_set_mode(mmc, mmc->clock, MMC_DEFAULT_BLKLEN);
+
+       /*
+        * set the bus width and select slot for this interface
+        * there is no capability for multiple slots on the same interface yet
+        */
+       if ((version & 0xf00) >= 0x300) {
+               switch (bus_width) {
+               case 8:
+                       busw = 3;
+                       break;
+               case 4:
+                       busw = 2;
+                       break;
+               default:
+                       busw = 0;
+                       break;
+               }
+
+               writel(busw << 6 | MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr);
+       } else {
+               busw = (bus_width == 4) ? 1 : 0;
+
+               writel(busw << 7 | MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr);
+       }
+}
+
+/* Entered into mmc structure during driver init */
+static int mci_init(struct mmc *mmc)
+{
+       atmel_mci_t *mci = mmc->priv;
+
+       /* Initialize controller */
+       writel(MMCI_BIT(SWRST), &mci->cr);      /* soft reset */
+       writel(MMCI_BIT(PWSDIS), &mci->cr);     /* disable power save */
+       writel(MMCI_BIT(MCIEN), &mci->cr);      /* enable mci */
+       writel(MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr);   /* select port */
+
+       /* This delay can be optimized, but stick with max value */
+       writel(0x7f, &mci->dtor);
+       /* Disable Interrupts */
+       writel(~0UL, &mci->idr);
+
+       /* Set default clocks and blocklen */
+       mci_set_mode(mmc, CONFIG_SYS_MMC_CLK_OD, MMC_DEFAULT_BLKLEN);
+
+       return 0;
+}
+
+static const struct mmc_ops atmel_mci_ops = {
+       .send_cmd       = mci_send_cmd,
+       .set_ios        = mci_set_ios,
+       .init           = mci_init,
+};
+
+/*
+ * This is the only exported function
+ *
+ * Call it with the MCI register base address
+ */
+int atmel_mci_init(void *regs)
+{
+       struct mmc *mmc;
+       struct mmc_config *cfg;
+       struct atmel_mci *mci;
+       unsigned int version;
+
+       cfg = malloc(sizeof(*cfg));
+       if (cfg == NULL)
+               return -1;
+       memset(cfg, 0, sizeof(*cfg));
+
+       mci = (struct atmel_mci *)regs;
+
+       cfg->name = "mci";
+       cfg->ops = &atmel_mci_ops;
+
+       /* need to be able to pass these in on a board by board basis */
+       cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
+       version = atmel_mci_get_version(mci);
+       if ((version & 0xf00) >= 0x300)
+               cfg->host_caps = MMC_MODE_8BIT;
+
+       cfg->host_caps |= MMC_MODE_4BIT;
+
+       /*
+        * min and max frequencies determined by
+        * max and min of clock divider
+        */
+       cfg->f_min = get_mci_clk_rate() / (2*256);
+       cfg->f_max = get_mci_clk_rate() / (2*1);
+
+       cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
+
+       mmc = mmc_create(cfg, regs);
+
+       if (mmc == NULL) {
+               free(cfg);
+               return -1;
+       }
+       /* NOTE: possibly leaking the cfg structure */
+
+       return 0;
+}