diff options
Diffstat (limited to 'qemu/roms/u-boot/drivers/mmc/sdhci.c')
-rw-r--r-- | qemu/roms/u-boot/drivers/mmc/sdhci.c | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/qemu/roms/u-boot/drivers/mmc/sdhci.c b/qemu/roms/u-boot/drivers/mmc/sdhci.c new file mode 100644 index 000000000..3125d13ba --- /dev/null +++ b/qemu/roms/u-boot/drivers/mmc/sdhci.c @@ -0,0 +1,513 @@ +/* + * Copyright 2011, Marvell Semiconductor Inc. + * Lei Wen <leiwen@marvell.com> + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Back ported to the 8xx platform (from the 8260 platform) by + * Murray.Jensen@cmst.csiro.au, 27-Jan-01. + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <sdhci.h> + +void *aligned_buffer; + +static void sdhci_reset(struct sdhci_host *host, u8 mask) +{ + unsigned long timeout; + + /* Wait max 100 ms */ + timeout = 100; + sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET); + while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { + if (timeout == 0) { + printf("%s: Reset 0x%x never completed.\n", + __func__, (int)mask); + return; + } + timeout--; + udelay(1000); + } +} + +static void sdhci_cmd_done(struct sdhci_host *host, struct mmc_cmd *cmd) +{ + int i; + if (cmd->resp_type & MMC_RSP_136) { + /* CRC is stripped so we need to do some shifting. */ + for (i = 0; i < 4; i++) { + cmd->response[i] = sdhci_readl(host, + SDHCI_RESPONSE + (3-i)*4) << 8; + if (i != 3) + cmd->response[i] |= sdhci_readb(host, + SDHCI_RESPONSE + (3-i)*4-1); + } + } else { + cmd->response[0] = sdhci_readl(host, SDHCI_RESPONSE); + } +} + +static void sdhci_transfer_pio(struct sdhci_host *host, struct mmc_data *data) +{ + int i; + char *offs; + for (i = 0; i < data->blocksize; i += 4) { + offs = data->dest + i; + if (data->flags == MMC_DATA_READ) + *(u32 *)offs = sdhci_readl(host, SDHCI_BUFFER); + else + sdhci_writel(host, *(u32 *)offs, SDHCI_BUFFER); + } +} + +static int sdhci_transfer_data(struct sdhci_host *host, struct mmc_data *data, + unsigned int start_addr) +{ + unsigned int stat, rdy, mask, timeout, block = 0; +#ifdef CONFIG_MMC_SDMA + unsigned char ctrl; + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl &= ~SDHCI_CTRL_DMA_MASK; + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); +#endif + + timeout = 1000000; + rdy = SDHCI_INT_SPACE_AVAIL | SDHCI_INT_DATA_AVAIL; + mask = SDHCI_DATA_AVAILABLE | SDHCI_SPACE_AVAILABLE; + do { + stat = sdhci_readl(host, SDHCI_INT_STATUS); + if (stat & SDHCI_INT_ERROR) { + printf("%s: Error detected in status(0x%X)!\n", + __func__, stat); + return -1; + } + if (stat & rdy) { + if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & mask)) + continue; + sdhci_writel(host, rdy, SDHCI_INT_STATUS); + sdhci_transfer_pio(host, data); + data->dest += data->blocksize; + if (++block >= data->blocks) + break; + } +#ifdef CONFIG_MMC_SDMA + if (stat & SDHCI_INT_DMA_END) { + sdhci_writel(host, SDHCI_INT_DMA_END, SDHCI_INT_STATUS); + start_addr &= ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1); + start_addr += SDHCI_DEFAULT_BOUNDARY_SIZE; + sdhci_writel(host, start_addr, SDHCI_DMA_ADDRESS); + } +#endif + if (timeout-- > 0) + udelay(10); + else { + printf("%s: Transfer data timeout\n", __func__); + return -1; + } + } while (!(stat & SDHCI_INT_DATA_END)); + return 0; +} + +/* + * No command will be sent by driver if card is busy, so driver must wait + * for card ready state. + * Every time when card is busy after timeout then (last) timeout value will be + * increased twice but only if it doesn't exceed global defined maximum. + * Each function call will use last timeout value. Max timeout can be redefined + * in board config file. + */ +#ifndef CONFIG_SDHCI_CMD_MAX_TIMEOUT +#define CONFIG_SDHCI_CMD_MAX_TIMEOUT 3200 +#endif +#define CONFIG_SDHCI_CMD_DEFAULT_TIMEOUT 100 + +int sdhci_send_command(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sdhci_host *host = mmc->priv; + unsigned int stat = 0; + int ret = 0; + int trans_bytes = 0, is_aligned = 1; + u32 mask, flags, mode; + unsigned int time = 0, start_addr = 0; + unsigned int retry = 10000; + int mmc_dev = mmc->block_dev.dev; + + /* Timeout unit - ms */ + static unsigned int cmd_timeout = CONFIG_SDHCI_CMD_DEFAULT_TIMEOUT; + + sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); + mask = SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT; + + /* We shouldn't wait for data inihibit for stop commands, even + though they might use busy signaling */ + if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) + mask &= ~SDHCI_DATA_INHIBIT; + + while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) { + if (time >= cmd_timeout) { + printf("%s: MMC: %d busy ", __func__, mmc_dev); + if (2 * cmd_timeout <= CONFIG_SDHCI_CMD_MAX_TIMEOUT) { + cmd_timeout += cmd_timeout; + printf("timeout increasing to: %u ms.\n", + cmd_timeout); + } else { + puts("timeout.\n"); + return COMM_ERR; + } + } + time++; + udelay(1000); + } + + mask = SDHCI_INT_RESPONSE; + if (!(cmd->resp_type & MMC_RSP_PRESENT)) + flags = SDHCI_CMD_RESP_NONE; + else if (cmd->resp_type & MMC_RSP_136) + flags = SDHCI_CMD_RESP_LONG; + else if (cmd->resp_type & MMC_RSP_BUSY) { + flags = SDHCI_CMD_RESP_SHORT_BUSY; + mask |= SDHCI_INT_DATA_END; + } else + flags = SDHCI_CMD_RESP_SHORT; + + if (cmd->resp_type & MMC_RSP_CRC) + flags |= SDHCI_CMD_CRC; + if (cmd->resp_type & MMC_RSP_OPCODE) + flags |= SDHCI_CMD_INDEX; + if (data) + flags |= SDHCI_CMD_DATA; + + /* Set Transfer mode regarding to data flag */ + if (data != 0) { + sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL); + mode = SDHCI_TRNS_BLK_CNT_EN; + trans_bytes = data->blocks * data->blocksize; + if (data->blocks > 1) + mode |= SDHCI_TRNS_MULTI; + + if (data->flags == MMC_DATA_READ) + mode |= SDHCI_TRNS_READ; + +#ifdef CONFIG_MMC_SDMA + if (data->flags == MMC_DATA_READ) + start_addr = (unsigned int)data->dest; + else + start_addr = (unsigned int)data->src; + if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && + (start_addr & 0x7) != 0x0) { + is_aligned = 0; + start_addr = (unsigned int)aligned_buffer; + if (data->flags != MMC_DATA_READ) + memcpy(aligned_buffer, data->src, trans_bytes); + } + + sdhci_writel(host, start_addr, SDHCI_DMA_ADDRESS); + mode |= SDHCI_TRNS_DMA; +#endif + sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, + data->blocksize), + SDHCI_BLOCK_SIZE); + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); + sdhci_writew(host, mode, SDHCI_TRANSFER_MODE); + } + + sdhci_writel(host, cmd->cmdarg, SDHCI_ARGUMENT); +#ifdef CONFIG_MMC_SDMA + flush_cache(start_addr, trans_bytes); +#endif + sdhci_writew(host, SDHCI_MAKE_CMD(cmd->cmdidx, flags), SDHCI_COMMAND); + do { + stat = sdhci_readl(host, SDHCI_INT_STATUS); + if (stat & SDHCI_INT_ERROR) + break; + if (--retry == 0) + break; + } while ((stat & mask) != mask); + + if (retry == 0) { + if (host->quirks & SDHCI_QUIRK_BROKEN_R1B) + return 0; + else { + printf("%s: Timeout for status update!\n", __func__); + return TIMEOUT; + } + } + + if ((stat & (SDHCI_INT_ERROR | mask)) == mask) { + sdhci_cmd_done(host, cmd); + sdhci_writel(host, mask, SDHCI_INT_STATUS); + } else + ret = -1; + + if (!ret && data) + ret = sdhci_transfer_data(host, data, start_addr); + + if (host->quirks & SDHCI_QUIRK_WAIT_SEND_CMD) + udelay(1000); + + stat = sdhci_readl(host, SDHCI_INT_STATUS); + sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); + if (!ret) { + if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && + !is_aligned && (data->flags == MMC_DATA_READ)) + memcpy(data->dest, aligned_buffer, trans_bytes); + return 0; + } + + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + if (stat & SDHCI_INT_TIMEOUT) + return TIMEOUT; + else + return COMM_ERR; +} + +static int sdhci_set_clock(struct mmc *mmc, unsigned int clock) +{ + struct sdhci_host *host = mmc->priv; + unsigned int div, clk, timeout; + + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + + if (clock == 0) + return 0; + + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) { + /* Version 3.00 divisors must be a multiple of 2. */ + if (mmc->cfg->f_max <= clock) + div = 1; + else { + for (div = 2; div < SDHCI_MAX_DIV_SPEC_300; div += 2) { + if ((mmc->cfg->f_max / div) <= clock) + break; + } + } + } else { + /* Version 2.00 divisors must be a power of 2. */ + for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *= 2) { + if ((mmc->cfg->f_max / div) <= clock) + break; + } + } + div >>= 1; + + if (host->set_clock) + host->set_clock(host->index, div); + + clk = (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT; + clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN) + << SDHCI_DIVIDER_HI_SHIFT; + clk |= SDHCI_CLOCK_INT_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Wait max 20 ms */ + timeout = 20; + while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + printf("%s: Internal clock never stabilised.\n", + __func__); + return -1; + } + timeout--; + udelay(1000); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + return 0; +} + +static void sdhci_set_power(struct sdhci_host *host, unsigned short power) +{ + u8 pwr = 0; + + if (power != (unsigned short)-1) { + switch (1 << power) { + case MMC_VDD_165_195: + pwr = SDHCI_POWER_180; + break; + case MMC_VDD_29_30: + case MMC_VDD_30_31: + pwr = SDHCI_POWER_300; + break; + case MMC_VDD_32_33: + case MMC_VDD_33_34: + pwr = SDHCI_POWER_330; + break; + } + } + + if (pwr == 0) { + sdhci_writeb(host, 0, SDHCI_POWER_CONTROL); + return; + } + + if (host->quirks & SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER) + sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); + + pwr |= SDHCI_POWER_ON; + + sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); +} + +void sdhci_set_ios(struct mmc *mmc) +{ + u32 ctrl; + struct sdhci_host *host = mmc->priv; + + if (host->set_control_reg) + host->set_control_reg(host); + + if (mmc->clock != host->clock) + sdhci_set_clock(mmc, mmc->clock); + + /* Set bus width */ + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + if (mmc->bus_width == 8) { + ctrl &= ~SDHCI_CTRL_4BITBUS; + if ((SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) || + (host->quirks & SDHCI_QUIRK_USE_WIDE8)) + ctrl |= SDHCI_CTRL_8BITBUS; + } else { + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) + ctrl &= ~SDHCI_CTRL_8BITBUS; + if (mmc->bus_width == 4) + ctrl |= SDHCI_CTRL_4BITBUS; + else + ctrl &= ~SDHCI_CTRL_4BITBUS; + } + + if (mmc->clock > 26000000) + ctrl |= SDHCI_CTRL_HISPD; + else + ctrl &= ~SDHCI_CTRL_HISPD; + + if (host->quirks & SDHCI_QUIRK_NO_HISPD_BIT) + ctrl &= ~SDHCI_CTRL_HISPD; + + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); +} + +int sdhci_init(struct mmc *mmc) +{ + struct sdhci_host *host = mmc->priv; + + if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && !aligned_buffer) { + aligned_buffer = memalign(8, 512*1024); + if (!aligned_buffer) { + printf("%s: Aligned buffer alloc failed!!!\n", + __func__); + return -1; + } + } + + sdhci_set_power(host, fls(mmc->cfg->voltages) - 1); + + if (host->quirks & SDHCI_QUIRK_NO_CD) { + unsigned int status; + + sdhci_writel(host, SDHCI_CTRL_CD_TEST_INS | SDHCI_CTRL_CD_TEST, + SDHCI_HOST_CONTROL); + + status = sdhci_readl(host, SDHCI_PRESENT_STATE); + while ((!(status & SDHCI_CARD_PRESENT)) || + (!(status & SDHCI_CARD_STATE_STABLE)) || + (!(status & SDHCI_CARD_DETECT_PIN_LEVEL))) + status = sdhci_readl(host, SDHCI_PRESENT_STATE); + } + + /* Enable only interrupts served by the SD controller */ + sdhci_writel(host, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK, + SDHCI_INT_ENABLE); + /* Mask all sdhci interrupt sources */ + sdhci_writel(host, 0x0, SDHCI_SIGNAL_ENABLE); + + return 0; +} + + +static const struct mmc_ops sdhci_ops = { + .send_cmd = sdhci_send_command, + .set_ios = sdhci_set_ios, + .init = sdhci_init, +}; + +int add_sdhci(struct sdhci_host *host, u32 max_clk, u32 min_clk) +{ + unsigned int caps; + + host->cfg.name = host->name; + host->cfg.ops = &sdhci_ops; + + caps = sdhci_readl(host, SDHCI_CAPABILITIES); +#ifdef CONFIG_MMC_SDMA + if (!(caps & SDHCI_CAN_DO_SDMA)) { + printf("%s: Your controller doesn't support SDMA!!\n", + __func__); + return -1; + } +#endif + + if (max_clk) + host->cfg.f_max = max_clk; + else { + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) + host->cfg.f_max = (caps & SDHCI_CLOCK_V3_BASE_MASK) + >> SDHCI_CLOCK_BASE_SHIFT; + else + host->cfg.f_max = (caps & SDHCI_CLOCK_BASE_MASK) + >> SDHCI_CLOCK_BASE_SHIFT; + host->cfg.f_max *= 1000000; + } + if (host->cfg.f_max == 0) { + printf("%s: Hardware doesn't specify base clock frequency\n", + __func__); + return -1; + } + if (min_clk) + host->cfg.f_min = min_clk; + else { + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) + host->cfg.f_min = host->cfg.f_max / + SDHCI_MAX_DIV_SPEC_300; + else + host->cfg.f_min = host->cfg.f_max / + SDHCI_MAX_DIV_SPEC_200; + } + + host->cfg.voltages = 0; + if (caps & SDHCI_CAN_VDD_330) + host->cfg.voltages |= MMC_VDD_32_33 | MMC_VDD_33_34; + if (caps & SDHCI_CAN_VDD_300) + host->cfg.voltages |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (caps & SDHCI_CAN_VDD_180) + host->cfg.voltages |= MMC_VDD_165_195; + + if (host->quirks & SDHCI_QUIRK_BROKEN_VOLTAGE) + host->cfg.voltages |= host->voltages; + + host->cfg.host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT; + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) { + if (caps & SDHCI_CAN_DO_8BIT) + host->cfg.host_caps |= MMC_MODE_8BIT; + } + if (host->host_caps) + host->cfg.host_caps |= host->host_caps; + + host->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + sdhci_reset(host, SDHCI_RESET_ALL); + + host->mmc = mmc_create(&host->cfg, host); + if (host->mmc == NULL) { + printf("%s: mmc create fail!\n", __func__); + return -1; + } + + return 0; +} |