diff options
Diffstat (limited to 'board/st-ericsson/snowball/mmc_host.c')
-rw-r--r-- | board/st-ericsson/snowball/mmc_host.c | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/board/st-ericsson/snowball/mmc_host.c b/board/st-ericsson/snowball/mmc_host.c new file mode 100644 index 000000000..4e5c0e39a --- /dev/null +++ b/board/st-ericsson/snowball/mmc_host.c @@ -0,0 +1,589 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ulf Hansson <ulf.hansson@stericsson.com> + * Author: Martin Lundholm <martin.xa.lundholm@stericsson.com> + * + * License terms: GNU General Public License (GPL), version 2. + */ + +/* + * There are two levels of debug printouts in this file. The macro DEBUG can be + * set to either DBG_LVL_INFO (1) or DBG_LVL_VERBOSE (2). + */ +#define DBG_LVL_INFO (1) +#define DBG_LVL_VERBOSE (2) + +#include <asm/io.h> +#include <asm/arch/common.h> +#include <asm/arch/cpu.h> +#include <mmc.h> +#include "mmc_host.h" +#include <malloc.h> +#include <div64.h> +#include "mmc_fifo.h" + + +struct mmc_host { + struct sdi_registers *base; +}; + +/* + * wait_for_command_end() - waiting for the command completion + * this function will wait until the command completion has happened or + * any error generated by reading the status register + */ +static int wait_for_command_end(struct mmc *dev, struct mmc_cmd *cmd) +{ + u32 hoststatus, statusmask; + struct mmc_host *host = dev->priv; + + statusmask = SDI_STA_CTIMEOUT | SDI_STA_CCRCFAIL; + if ((cmd->resp_type & MMC_RSP_PRESENT)) + statusmask |= SDI_STA_CMDREND; + else + statusmask |= SDI_STA_CMDSENT; + + do + hoststatus = readl(&host->base->status) & statusmask; + while (!hoststatus); + + debugX(DBG_LVL_VERBOSE, "SDI_ICR <= 0x%08X\n", statusmask); + writel(statusmask, &host->base->status_clear); + + if (hoststatus & SDI_STA_CTIMEOUT) { + debugX(DBG_LVL_VERBOSE, "CMD%d time out\n", cmd->cmdidx); + return TIMEOUT; + } else if ((hoststatus & SDI_STA_CCRCFAIL) && + (cmd->flags & MMC_RSP_CRC)) { + debugX(DBG_LVL_VERBOSE, "CMD%d CRC error\n", cmd->cmdidx); + return MMC_CMD_CRC_FAIL; + } + + if (cmd->resp_type & MMC_RSP_PRESENT) { + cmd->response[0] = readl(&host->base->response0); + cmd->response[1] = readl(&host->base->response1); + cmd->response[2] = readl(&host->base->response2); + cmd->response[3] = readl(&host->base->response3); + debugX(DBG_LVL_VERBOSE, + "CMD%d response[0]:0x%08X, response[1]:0x%08X, " + "response[2]:0x%08X, response[3]:0x%08X\n", + cmd->cmdidx, cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } + return MMC_OK; +} + +/* + * do_command - sends command to card, and waits for its result. + */ +static int do_command(struct mmc *dev, struct mmc_cmd *cmd) +{ + int result; + u32 sdi_cmd = 0; + struct mmc_host *host = dev->priv; + u32 lap = 0; + + debugX(DBG_LVL_VERBOSE, "Request to do CMD%d on %s\n", cmd->cmdidx, + dev->name); + + sdi_cmd = (cmd->cmdidx & SDI_CMD_CMDINDEX_MASK) | SDI_CMD_CPSMEN; + + if (cmd->resp_type) { + sdi_cmd |= SDI_CMD_WAITRESP; + if (cmd->resp_type & MMC_RSP_136) + sdi_cmd |= SDI_CMD_LONGRESP; + } + + debugX(DBG_LVL_VERBOSE, "SDI_ARG <= 0x%08X\n", cmd->cmdarg); + writel((u32)cmd->cmdarg, &host->base->argument); + udelay(COMMAND_REG_DELAY); /* DONT REMOVE */ + debugX(DBG_LVL_VERBOSE, "SDI_CMD <= 0x%08X\n", sdi_cmd); + + /* + * It has been noticed that after a write operation some cards does + * not respond to a new command for a few milliseconds. So here we + * retry the command a couple of times if we get a timeout. + */ + do { + writel(sdi_cmd, &host->base->command); + result = wait_for_command_end(dev, cmd); + if ((result != TIMEOUT) || (lap >= 10)) + break; + udelay(1000); + lap++; + } while (1); + + /* After CMD2 set RCA to a none zero value. */ + if ((result == MMC_OK) && (cmd->cmdidx == MMC_CMD_ALL_SEND_CID)) + dev->rca = 10; + + /* After CMD3 open drain is switched off and push pull is used. */ + if ((result == MMC_OK) && (cmd->cmdidx == MMC_CMD_SET_RELATIVE_ADDR)) { + u32 sdi_pwr = readl(&host->base->power) & ~SDI_PWR_OPD; + debugX(DBG_LVL_VERBOSE, "SDI_PWR <= 0x%08X\n", sdi_pwr); + writel(sdi_pwr, &host->base->power); + } + + return result; +} + +static int convert_from_bytes_to_power_of_two(unsigned int x) +{ + int y = 0; + y = (x & 0xAAAA) ? 1 : 0; + y |= ((x & 0xCCCC) ? 1 : 0)<<1; + y |= ((x & 0xF0F0) ? 1 : 0)<<2; + y |= ((x & 0xFF00) ? 1 : 0)<<3; + + return y; +} + +/* + * read_bytes - reads bytes from the card, part of data transfer. + */ +static int read_bytes(struct mmc *dev, u32 *dest, u32 blkcount, u32 blksize) +{ + u64 xfercount = blkcount * blksize; + struct mmc_host *host = dev->priv; + u32 status; + + debugX(DBG_LVL_VERBOSE, "read_bytes: blkcount=%u blksize=%u\n", + blkcount, blksize); + + status = mmc_fifo_read(&host->base->fifo, dest, xfercount, + &host->base->status); + + if (status & (SDI_STA_DTIMEOUT | SDI_STA_DCRCFAIL)) { + printf("Reading data failed: status:0x%08X\n", status); + if (status & SDI_STA_DTIMEOUT) + return MMC_DATA_TIMEOUT; + else if (status & SDI_STA_DCRCFAIL) + return MMC_DATA_CRC_FAIL; + } + + debugX(DBG_LVL_VERBOSE, "SDI_ICR <= 0x%08X\n", SDI_ICR_MASK); + writel(SDI_ICR_MASK, &host->base->status_clear); + debugX(DBG_LVL_VERBOSE, "Reading data completed status:0x%08X\n", + status); + + return MMC_OK; +} + +/* + * write_bytes - writes byte to the card, part of data transfer. + */ +static int write_bytes(struct mmc *dev, u32 *src, u32 blkcount, u32 blksize) +{ + u64 xfercount = blkcount * blksize; + struct mmc_host *host = dev->priv; + u32 status; + u32 status_busy; + + debugX(DBG_LVL_VERBOSE, "write_bytes: blkcount=%u blksize=%u\n", + blkcount, blksize); + + status = mmc_fifo_write(src, &host->base->fifo, xfercount, + &host->base->status); + + if (status & (SDI_STA_DTIMEOUT | SDI_STA_DCRCFAIL)) { + printf("Writing data failed: status=0x%08X\n", status); + if (status & SDI_STA_DTIMEOUT) + return MMC_DATA_TIMEOUT; + else if (status & SDI_STA_DCRCFAIL) + return MMC_DATA_CRC_FAIL; + } + + /* Wait if busy */ + status_busy = status & SDI_STA_CARDBUSY; + while (status_busy) + status_busy = readl(&host->base->status) & SDI_STA_CARDBUSY; + + writel(SDI_ICR_MASK, &host->base->status_clear); + debugX(DBG_LVL_VERBOSE, "Writing data completed status:0x%08X\n", + status); + + return MMC_OK; +} + +/* + * do_data_transfer - for doing any data transfer operation. + * + * dev: mmc device for doing the operation on. + * cmd: cmd to do. + * data: if cmd warrants any data transfer. + */ +static int do_data_transfer(struct mmc *dev, + struct mmc_cmd *cmd, + struct mmc_data *data) +{ +#if (DEBUG >= DBG_LVL_INFO) + u32 start_time = 0; +#endif + int error = MMC_DATA_TIMEOUT; + struct mmc_host *host = dev->priv; + u32 blksz = 0; + u32 data_ctrl = 0; + u32 data_len = (u32) (data->blocks * data->blocksize); + + debugX(DBG_LVL_VERBOSE, "Request to do data xfer on %s\n", dev->name); + debugX(DBG_LVL_VERBOSE, "do_data_transfer(%u) start\n", data->blocks); + +#if (DEBUG >= DBG_LVL_INFO) + // if (data->blocks > 1) + // start_time = (u32) get_timer_us(); +#endif + + if (cpu_is_u8500v1() || u8500_is_earlydrop()) { + blksz = convert_from_bytes_to_power_of_two(data->blocksize); + data_ctrl |= (blksz << INDEX(SDI_DCTRL_DBLOCKSIZE_MASK)); + } else { + blksz = data->blocksize; + data_ctrl |= (blksz << INDEX(SDI_DCTRL_DBLOCKSIZE_V2_MASK)); + } + data_ctrl |= SDI_DCTRL_DTEN | SDI_DCTRL_BUSYMODE; + if (dev->ddr_en && + ((cmd->cmdidx == MMC_CMD_READ_SINGLE_BLOCK) || + (cmd->cmdidx == MMC_CMD_READ_MULTIPLE_BLOCK) || + (cmd->cmdidx == MMC_CMD_WRITE_SINGLE_BLOCK) || + (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK) || + (cmd->cmdidx == MMC_CMD_SEND_EXT_CSD))) + data_ctrl |= SDI_DCTRL_DDR_MODE; + +#if (DEBUG >= DBG_LVL_VERBOSE) + if (data_ctrl & SDI_DCTRL_DDR_MODE) + printf("SDI_DCTRL_DDR_MODE\n"); +#endif + + debugX(DBG_LVL_VERBOSE, "SDI_DTIMER <= 0x%08X\n", dev->data_timeout); + writel(dev->data_timeout, &host->base->datatimer); + debugX(DBG_LVL_VERBOSE, "SDI_DLEN <= 0x%08X\n", data_len); + writel(data_len, &host->base->datalength); + udelay(DATA_REG_DELAY); /* DONT REMOVE */ + + if (data->flags & (MMC_DATA_READ)) { + debugX(DBG_LVL_VERBOSE, "It is a read operation\n"); + + data_ctrl |= SDI_DCTRL_DTDIR_IN; + debugX(DBG_LVL_VERBOSE, "SDI_DCTRL <= 0x%08X\n", data_ctrl); + writel(data_ctrl, &host->base->datactrl); + + error = do_command(dev, cmd); + if (error) + return error; + + error = read_bytes(dev, + (u32 *)data->dest, + (u32)data->blocks, + (u32)data->blocksize); + } else if (data->flags & (MMC_DATA_WRITE)) { + debugX(DBG_LVL_VERBOSE, "It is a write operation\n"); + + error = do_command(dev, cmd); + if (error) + return error; + + debugX(DBG_LVL_VERBOSE, "SDI_DCTRL <= 0x%08X\n", data_ctrl); + writel(data_ctrl, &host->base->datactrl); + + error = write_bytes(dev, + (u32 *)data->src, + (u32)data->blocks, + (u32)data->blocksize); + } + +#if (DEBUG >= DBG_LVL_INFO) +#if 0 + if (data->blocks > 1) { + u32 transfer_time = (u32) get_timer_us() - start_time; + u64 throughput = lldiv((u64) 1000 * 1000 * data->blocks * + data->blocksize, transfer_time); + printf("MMC %s: %u bytes in %u [us] => %llu [B/s] = " + "%llu [kB/s] = %llu.%02u [MB/s] = %llu [Mbits/s]\n", + (data->flags & (MMC_DATA_READ)) ? "read" : "write", + data->blocks * data->blocksize, + transfer_time, + throughput, throughput / 1024, + throughput / (1024 * 1024), + (u32)((100 * throughput) / (1024 * 1024) - + (throughput / (1024 * 1024)) * 100), + throughput * 8 / (1024 * 1024)); + } +#endif +#endif + debugX(DBG_LVL_VERBOSE, "do_data_transfer() end\n"); + + return error; +} + +/* + * host_request - For all operations on cards. + * + * dev: mmc device for doing the operation on. + * cmd: cmd to do. + * data: if cmd warrants any data transfer. + */ +static int host_request(struct mmc *dev, + struct mmc_cmd *cmd, + struct mmc_data *data) +{ + int result; + + if (data) + result = do_data_transfer(dev, cmd, data); + else + result = do_command(dev, cmd); + + return result; +} + +/* + * This is to initialize card specific things just before enumerating + * them. MMC cards uses open drain drivers in enumeration phase. + */ +static int mmc_host_reset(struct mmc *dev) +{ + struct mmc_host *host = dev->priv; + u32 sdi_u32 = SDI_PWR_OPD | SDI_PWR_PWRCTRL_ON; + + debugX(DBG_LVL_VERBOSE, "SDI_PWR <= 0x%08X\n", sdi_u32); + writel(sdi_u32, &host->base->power); + return MMC_OK; +} + +/* + * This is to initialize card specific things just before enumerating + * them. SD cards does not need to be initialized. + */ +static int sd_host_reset(struct mmc *dev) +{ + (void) dev; /* Parameter not used! */ + + return MMC_OK; +} +/* + * host_set_ios:to configure host parameters. + * + * dev: the pointer to the host structure for MMC. + */ +static void host_set_ios(struct mmc *dev) +{ + struct mmc_host *host = dev->priv; + u32 sdi_clkcr; + + /* First read out the contents of clock control register. */ + sdi_clkcr = readl(&host->base->clock); + + /* Set the clock rate and bus width */ + if (dev->clock) { + u32 clkdiv = 0; + u32 tmp_clock; + + debugX(DBG_LVL_VERBOSE, + "setting clock and bus width in the host:"); + if (dev->clock >= dev->f_max) { + clkdiv = 0; + dev->clock = dev->f_max; + } else { + clkdiv = (MCLK / dev->clock) - 2; + } + tmp_clock = MCLK / (clkdiv + 2); + while (tmp_clock > dev->clock) { + clkdiv++; + tmp_clock = MCLK / (clkdiv + 2); + } + if (clkdiv > SDI_CLKCR_CLKDIV_MASK) + clkdiv = SDI_CLKCR_CLKDIV_MASK; + tmp_clock = MCLK / (clkdiv + 2); + dev->clock = tmp_clock; + sdi_clkcr &= ~(SDI_CLKCR_CLKDIV_MASK); + sdi_clkcr |= clkdiv; + } + + if (dev->bus_width) { + u32 buswidth = 0; + + switch (dev->bus_width) { + case 1: + buswidth |= SDI_CLKCR_WIDBUS_1; + break; + case 4: + buswidth |= SDI_CLKCR_WIDBUS_4; + break; + case 8: + buswidth |= SDI_CLKCR_WIDBUS_8; + break; + default: + printf("wrong bus width, so ignoring"); + break; + } + sdi_clkcr &= ~(SDI_CLKCR_WIDBUS_MASK); + sdi_clkcr |= buswidth; + } + + + dev->data_timeout = MMC_DATA_TIMEOUT * dev->clock; + + debugX(DBG_LVL_VERBOSE, "SDI_CLKCR <= 0x%08X\n", sdi_clkcr); + writel(sdi_clkcr, &host->base->clock); + udelay(CLK_CHANGE_DELAY); +} + +struct mmc *alloc_mmc_struct(void) +{ + struct mmc_host *host = NULL; + struct mmc *mmc_device = NULL; + + host = malloc(sizeof(struct mmc_host)); + if (!host) + return NULL; + + mmc_device = malloc(sizeof(struct mmc)); + if (!mmc_device) + goto err; + + memset(mmc_device, 0x00, sizeof(struct mmc)); + + mmc_device->priv = host; + return mmc_device; + err: + free(host); + return NULL; +} + +/* + * emmc_host_init - initialize the emmc controller. + * Configure GPIO settings, set initial clock and power for emmc slot. + * Initialize mmc struct and register with mmc framework. + */ +static int emmc_host_init(struct mmc *dev) +{ + struct mmc_host *host = dev->priv; + u32 sdi_u32; + + /* TODO: Investigate what is actually needed of the below. */ + + if (u8500_is_earlydrop() || u8500_is_snowball()) { + debugX(DBG_LVL_VERBOSE, "configuring EMMC for ED\n"); + host->base = (struct sdi_registers *)CFG_EMMC_BASE_ED; + } else { + debugX(DBG_LVL_VERBOSE, "configuring EMMC for V1\n"); + host->base = (struct sdi_registers *)CFG_EMMC_BASE_V1; + } + + sdi_u32 = SDI_PWR_OPD | SDI_PWR_PWRCTRL_ON; + debugX(DBG_LVL_VERBOSE, "SDI_PWR <= 0x%08X\n", sdi_u32); + writel(sdi_u32, &host->base->power); + /* setting clk freq less than 400KHz */ + sdi_u32 = SDI_CLKCR_CLKDIV_INIT | SDI_CLKCR_CLKEN | SDI_CLKCR_HWFC_EN; + debugX(DBG_LVL_VERBOSE, "SDI_CLKCR <= 0x%08X\n", sdi_u32); + writel(sdi_u32, &host->base->clock); + udelay(CLK_CHANGE_DELAY); + sdi_u32 = readl(&host->base->mask0) & ~SDI_MASK0_MASK; + debugX(DBG_LVL_VERBOSE, "SDI_MASK0 <= 0x%08X\n", sdi_u32); + writel(sdi_u32, &host->base->mask0); + dev->clock = MCLK / (2 + SDI_CLKCR_CLKDIV_INIT); + sprintf(dev->name, "EMMC"); + dev->send_cmd = host_request; + dev->set_ios = host_set_ios; + dev->init = mmc_host_reset; + dev->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT | MMC_MODE_HS | + MMC_MODE_HS_52MHz /* | MMC_MODE_REL_WR | MMC_MODE_DDR */; + dev->voltages = VOLTAGE_WINDOW_MMC; + dev->f_min = dev->clock; + dev->f_max = MCLK / 2; + dev->ddr_en = 0; + return 0; +} + +/* + * mmc_host_init - initialize the external mmc controller. + * Configure GPIO settings, set initial clock and power for mmc slot. + * Initialize mmc struct and register with mmc framework. + */ +static int mmc_host_init(struct mmc *dev) +{ + struct mmc_host *host = dev->priv; + u32 sdi_u32; + + host->base = (struct sdi_registers *)CFG_MMC_BASE; + sdi_u32 = 0xBF; + debugX(DBG_LVL_VERBOSE, "SDI_PWR <= 0x%08X\n", sdi_u32); + writel(sdi_u32, &host->base->power); + /* setting clk freq just less than 400KHz */ + sdi_u32 = SDI_CLKCR_CLKDIV_INIT | SDI_CLKCR_CLKEN | SDI_CLKCR_HWFC_EN; + debugX(DBG_LVL_VERBOSE, "SDI_CLKCR <= 0x%08X\n", sdi_u32); + writel(sdi_u32, &host->base->clock); + udelay(CLK_CHANGE_DELAY); + sdi_u32 = readl(&host->base->mask0) & ~SDI_MASK0_MASK; + debugX(DBG_LVL_VERBOSE, "SDI_MASK0 <= 0x%08X\n", sdi_u32); + writel(sdi_u32, &host->base->mask0); + dev->clock = MCLK / (2 + SDI_CLKCR_CLKDIV_INIT); + sprintf(dev->name, "MMC"); + dev->send_cmd = host_request; + dev->set_ios = host_set_ios; + dev->init = sd_host_reset; + dev->host_caps = /* MMC_MODE_4BIT */ 0; /* Some SD cards do not work in + 4 bit mode! */ + dev->voltages = VOLTAGE_WINDOW_SD; + dev->f_min = dev->clock; + dev->f_max = MCLK / 2; + dev->ddr_en = 0; + return 0; +} + +/* + * board_mmc_init - initialize all the mmc/sd host controllers. + * Called by generic mmc framework. + */ +int board_mmc_init(bd_t *bis) +{ + int error; + struct mmc *dev; + + debugX(DBG_LVL_VERBOSE, "[%s] mmc_host - board_mmc_init board ed %d, snow %d\n", + __func__,u8500_is_earlydrop(), u8500_is_snowball()); + + (void) bis; /* Parameter not used! */ + + dev = alloc_mmc_struct(); + if (!dev) + return -1; + + error = emmc_host_init(dev); + if (error) { + printf("emmc_host_init() %d \n", error); + return -1; + } + mmc_register(dev); + debugX(DBG_LVL_VERBOSE, "registered emmc interface number is:%d\n", + dev->block_dev.dev); + + + mmc_init(dev); + + /* + * In a perfect world board_early_access shouldn't be here but we want + * some functionality to be loaded as quickly as possible and putting it + * here will get the shortest time to start that functionality. Time + * saved by putting it here compared to later is somewhere between + * 0.3-0.7s. That is enough to be able to justify putting it here. + */ + + board_early_access(&dev->block_dev); + + dev = alloc_mmc_struct(); + if (!dev) + return -1; + + error = mmc_host_init(dev); + if (error) { + printf("mmc_host_init() %d \n", error); + return -1; + } + mmc_register(dev); + debugX(DBG_LVL_VERBOSE, "registered mmc/sd interface number is:%d\n", + dev->block_dev.dev); + + return 0; +} |