diff options
Diffstat (limited to 'board/st/u8500/mmc_host.c')
-rw-r--r-- | board/st/u8500/mmc_host.c | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/board/st/u8500/mmc_host.c b/board/st/u8500/mmc_host.c new file mode 100644 index 000000000..479e76391 --- /dev/null +++ b/board/st/u8500/mmc_host.c @@ -0,0 +1,636 @@ +/* + * 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. + */ + +/*#define DEBUG*/ + +#include "common.h" +#include <mmc.h> +#include "gpio.h" /* two copies of gpio.h exists - why? */ +#include "mmc_host.h" +#include <malloc.h> + +struct mmc_host { + struct sdi_registers *base; +}; + +/* + * wait_for_command_end() - waiting for the command completion + * this function will wait till 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); + + debug("SDI_ICR <= 0x%08X\n", statusmask); + writel(statusmask, &host->base->status_clear); + + if (hoststatus & SDI_STA_CTIMEOUT) { + debug("CMD%d time out\n", cmd->cmdidx); + return TIMEOUT; + } else if ((hoststatus & SDI_STA_CCRCFAIL) && + (cmd->flags & MMC_RSP_CRC)) { + debug("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); + debug("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; + + debug("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; + } + + debug("SDI_ARG <= 0x%08X\n", cmd->cmdarg); + writel((u32)cmd->cmdarg, &host->base->argument); + udelay(COMMAND_REG_DELAY); /* DONT REMOVE */ + debug("SDI_CMD <= 0x%08X\n", sdi_cmd); + writel(sdi_cmd, &host->base->command); + + result = wait_for_command_end(dev, cmd); + + /* 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; + debug("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) +{ + u32 *tempbuff = dest; + int i; + u64 xfercount = blkcount * blksize; + struct mmc_host *host = dev->priv; + u32 status; + u32 status_err; + + debug("read_bytes: blkcount=%u blksize=%u\n", blkcount, blksize); + + status = readl(&host->base->status); + status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT); + while (!status_err && + (xfercount >= SDI_FIFO_BURST_SIZE * sizeof(u32))) { + if (status & SDI_STA_RXFIFOBR) { + for (i = 0; i < SDI_FIFO_BURST_SIZE; i++) + *(tempbuff + i) = readl(&host->base->fifo); + tempbuff += SDI_FIFO_BURST_SIZE; + xfercount -= SDI_FIFO_BURST_SIZE * sizeof(u32); + } + status = readl(&host->base->status); + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT); + } + + if (status & SDI_STA_DTIMEOUT) { + printf("Reading data timedout, xfercount:%llu," + "status:0x%08X\n", xfercount, status); + return MMC_DATA_TIMEOUT; + } else if (status & SDI_STA_DCRCFAIL) { + printf("Reading data CRC error\n"); + return MMC_DATA_CRC_FAIL; + } + + while ((!status_err) && (xfercount >= sizeof(u32))) { + if (status & SDI_STA_RXDAVL) { + *(tempbuff) = readl(&host->base->fifo); + tempbuff++; + xfercount -= sizeof(u32); + } + status = readl(&host->base->status); + status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT); + } + + /* Wait for DBCKEND */ + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND); + while (!status_err) { + status = readl(&host->base->status); + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND); + } + + if (status & SDI_STA_DTIMEOUT) { + printf("Reading data timedout, xfercount:%llu," + "status:0x%08X\n", xfercount, status); + return MMC_DATA_TIMEOUT; + } else if (status & SDI_STA_DCRCFAIL) { + printf("Reading data CRC error\n"); + return MMC_DATA_CRC_FAIL; + } + + debug("SDI_ICR <= 0x%08X\n", SDI_ICR_MASK); + writel(SDI_ICR_MASK, &host->base->status_clear); + debug("Reading loop ended\n"); + + if (xfercount) { + printf("Reading data error, xfercount:%llu", + (unsigned long long) xfercount); + return MMC_GENERAL_UNKNOWN_ERROR; + } + + 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) +{ + u32 *tempbuff = src; + int i; + u64 xfercount = blkcount * blksize; + struct mmc_host *host = dev->priv; + u32 status; + u32 status_err; + + debug("write_bytes: blkcount=%u blksize=%u\n", blkcount, blksize); + + status = readl(&host->base->status); + status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT); + while (!status_err && xfercount) { + if (status & SDI_STA_TXFIFOBW) { + if (xfercount >= SDI_FIFO_BURST_SIZE * sizeof(u32)) { + for (i = 0; i < SDI_FIFO_BURST_SIZE; i++) + writel(*(tempbuff + i), + &host->base->fifo); + tempbuff += SDI_FIFO_BURST_SIZE; + xfercount -= SDI_FIFO_BURST_SIZE * sizeof(u32); + } else { + while (xfercount >= sizeof(u32)) { + writel(*(tempbuff), &host->base->fifo); + tempbuff++; + xfercount -= sizeof(u32); + } + } + } + status = readl(&host->base->status); + status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT); + } + + /* Wait for DBCKEND */ + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND); + while (!status_err) { + status = readl(&host->base->status); + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND); + } + + if (status & SDI_STA_DTIMEOUT) { + printf( + "Writing data timedout, xfercount:%llu,status:0x%08X\n", + xfercount, status); + return MMC_DATA_TIMEOUT; + } else if (status & SDI_STA_DCRCFAIL) { + printf("Writing data CRC error\n"); + return MMC_DATA_CRC_FAIL; + } + + + writel(SDI_ICR_MASK, &host->base->status_clear); + debug("Writing data loop completed status:0x%08X\n", status); + + if (xfercount) { + printf("Writing data error, xfercount:%llu", + (unsigned long long) xfercount); + return MMC_GENERAL_UNKNOWN_ERROR; + } + + 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) +{ + 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); + + debug("Request to do data xfer on %s\n", dev->name); + debug("do_data_transfer(%u) start\n", data->blocks); + + blksz = convert_from_bytes_to_power_of_two(data->blocksize); + data_ctrl |= (blksz << INDEX(SDI_DCTRL_DBLOCKSIZE_MASK)); + data_ctrl |= SDI_DCTRL_DTEN; + + debug("SDI_DTIMER <= 0x%08X\n", SDI_DTIMER_DEFAULT); + writel(SDI_DTIMER_DEFAULT, &host->base->datatimer); + debug("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)) { + debug("It is a read operation\n"); + + data_ctrl |= SDI_DCTRL_DTDIR_IN; + debug("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)) { + debug("It is a write operation\n"); + + error = do_command(dev, cmd); + if (error) + return error; + + debug("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); + } + + debug("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; + + debug("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) +{ + 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; + + debug("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; + } + + debug("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; + + mmc_device->priv = host; + return mmc_device; + err: + free(host); + return NULL; +} + +static int host_poweroff(struct mmc *dev) +{ + struct mmc_host *host = dev->priv; + u32 sdi_pwr = ~SDI_PWR_PWRCTRL_ON; + debug("SDI_PWR <= 0x%08X\n", sdi_pwr); + writel(sdi_pwr, &host->base->power); + return 0; +} + +/* + * 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; + gpio_error gpioerror; + u32 sdi_u32; + + /* TODO: Investigate what is actually needed of the below. */ + + if (u8500_is_earlydrop()) { + debug("configuring EMMC for ED\n"); + /* Initialize the gpio alternate function for eMMC */ + struct gpio_register *p_gpio_register = + (void *)IO_ADDRESS(CFG_GPIO_6_BASE); + p_gpio_register->gpio_dats |= 0x0000FFE0; + p_gpio_register->gpio_pdis &= ~0x0000FFE0; + + gpioerror = gpio_altfuncenable(GPIO_ALT_EMMC, "EMMC"); + if (GPIO_OK != gpioerror) { + printf( + "emmc_host_init() gpio_altfuncenable %d failed\n", + gpioerror); + goto end; + } + + host->base = (struct sdi_registers *)CFG_EMMC_BASE_ED; + } else { + debug("configuring EMMC for V1\n"); + /* enable the alternate function of PoP EMMC */ + gpioerror = gpio_altfuncenable(GPIO_ALT_POP_EMMC, "EMMC"); + if (gpioerror != GPIO_OK) { + printf( + "emmc_host_init() gpio_altfuncenable %d failed \n", + gpioerror); + goto end; + } + host->base = (struct sdi_registers *)CFG_EMMC_BASE_V1; + } + + sdi_u32 = SDI_PWR_OPD | SDI_PWR_PWRCTRL_ON; + debug("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; + debug("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; + debug("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; + dev->voltages = VOLTAGE_WINDOW_MMC; + dev->f_min = dev->clock; + dev->f_max = MCLK / 2; + return 0; + + end: + if (u8500_is_earlydrop()) + gpio_altfuncdisable(GPIO_ALT_EMMC, "EMMC"); + else + gpio_altfuncdisable(GPIO_ALT_POP_EMMC, "EMMC"); + + host_poweroff(dev); + return MMC_UNSUPPORTED_HW; +} + +/* + * 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; + gpio_error gpioerror; + struct gpio_register *gpio_base_address; + u32 sdi_u32; + + gpio_base_address = (void *) IO_ADDRESS(CFG_GPIO_0_BASE); + gpio_base_address->gpio_dats |= 0xFFC0000; + gpio_base_address->gpio_pdis &= ~0xFFC0000; + + /* save the GPIO0 AFSELA register */ + gpioerror = gpio_altfuncenable(GPIO_ALT_SD_CARD0, "MMC"); + if (gpioerror != GPIO_OK) { + printf("mmc_host_init() gpio_altfuncenable %d failed \n", + gpioerror); + goto end; + } + + host->base = (struct sdi_registers *)CFG_MMC_BASE; + sdi_u32 = 0xBF; + debug("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; + debug("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; + debug("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; + return 0; + + end: + gpio_altfuncdisable(GPIO_ALT_SD_CARD0, "MMC"); + host_poweroff(dev); + return MMC_UNSUPPORTED_HW; +} + +/* + * 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; + + debug("mmc_host - board_mmc_init\n"); + + 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); + debug("registered emmc interface number is:%d\n", + dev->block_dev.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); + debug("registered mmc/sd interface number is:%d\n", + dev->block_dev.dev); + + return 0; +} |