diff options
Diffstat (limited to 'src/commonlib/storage/sdhci.c')
-rw-r--r-- | src/commonlib/storage/sdhci.c | 813 |
1 files changed, 813 insertions, 0 deletions
diff --git a/src/commonlib/storage/sdhci.c b/src/commonlib/storage/sdhci.c new file mode 100644 index 0000000000..bac510e12a --- /dev/null +++ b/src/commonlib/storage/sdhci.c @@ -0,0 +1,813 @@ +/* + * Copyright 2011, Marvell Semiconductor Inc. + * Lei Wen <leiwen@marvell.com> + * + * Copyright 2017 Intel Corporation + * + * Secure Digital (SD) Host Controller interface specific code + * + * 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. + */ + +#include <assert.h> +#include "bouncebuf.h" +#include <console/console.h> +#include <commonlib/sd_mmc_ctrlr.h> +#include <commonlib/sdhci.h> +#include <commonlib/storage.h> +#include <delay.h> +#include <endian.h> +#include <halt.h> +#include "sdhci.h" +#include "sd_mmc.h" +#include "storage.h" +#include <string.h> +#include <timer.h> +#include <commonlib/stdlib.h> + +#define DMA_AVAILABLE ((CONFIG_SDHCI_ADMA_IN_BOOTBLOCK && ENV_BOOTBLOCK) \ + || (CONFIG_SDHCI_ADMA_IN_VERSTAGE && ENV_VERSTAGE) \ + || (CONFIG_SDHCI_ADMA_IN_ROMSTAGE && ENV_ROMSTAGE) \ + || ENV_POSTCAR || ENV_RAMSTAGE) + +__attribute__((weak)) void *dma_malloc(size_t length_in_bytes) +{ + return malloc(length_in_bytes); +} + +void sdhci_reset(struct sdhci_ctrlr *sdhci_ctrlr, u8 mask) +{ + struct stopwatch sw; + + /* Wait max 100 ms */ + stopwatch_init_msecs_expire(&sw, 100); + + sdhci_writeb(sdhci_ctrlr, mask, SDHCI_SOFTWARE_RESET); + while (sdhci_readb(sdhci_ctrlr, SDHCI_SOFTWARE_RESET) & mask) { + if (stopwatch_expired(&sw)) { + sdhc_error("Reset 0x%x never completed.\n", (int)mask); + return; + } + udelay(1000); + } +} + +void sdhci_cmd_done(struct sdhci_ctrlr *sdhci_ctrlr, struct mmc_command *cmd) +{ + int i; + if (cmd->resp_type & CARD_RSP_136) { + /* CRC is stripped so we need to do some shifting. */ + for (i = 0; i < 4; i++) { + cmd->response[i] = sdhci_readl(sdhci_ctrlr, + SDHCI_RESPONSE + (3-i)*4) << 8; + if (i != 3) + cmd->response[i] |= sdhci_readb(sdhci_ctrlr, + SDHCI_RESPONSE + (3-i)*4-1); + } + sdhc_log_response(4, &cmd->response[0]); + sdhc_trace("Response: 0x%08x.%08x.%08x.%08x\n", + cmd->response[3], cmd->response[2], cmd->response[1], + cmd->response[0]); + } else { + cmd->response[0] = sdhci_readl(sdhci_ctrlr, SDHCI_RESPONSE); + sdhc_log_response(1, &cmd->response[0]); + sdhc_trace("Response: 0x%08x\n", cmd->response[0]); + } +} + +static int sdhci_transfer_data(struct sdhci_ctrlr *sdhci_ctrlr, + struct mmc_data *data, unsigned int start_addr) +{ + uint32_t block_count; + uint32_t *buffer; + uint32_t *buffer_end; + uint32_t ps; + uint32_t ps_mask; + uint32_t stat; + struct stopwatch sw; + + block_count = 0; + buffer = (uint32_t *)data->dest; + ps_mask = (data->flags & DATA_FLAG_READ) + ? SDHCI_DATA_AVAILABLE : SDHCI_SPACE_AVAILABLE; + stopwatch_init_msecs_expire(&sw, 100); + do { + /* Stop transfers if there is an error */ + stat = sdhci_readl(sdhci_ctrlr, SDHCI_INT_STATUS); + sdhci_writel(sdhci_ctrlr, stat, SDHCI_INT_STATUS); + if (stat & SDHCI_INT_ERROR) { + sdhc_error("Error detected in status(0x%X)!\n", stat); + return -1; + } + + /* Determine if the buffer is ready to move data */ + ps = sdhci_readl(sdhci_ctrlr, SDHCI_PRESENT_STATE); + if (!(ps & ps_mask)) { + if (stopwatch_expired(&sw)) { + sdhc_error("Transfer data timeout\n"); + return -1; + } + udelay(1); + continue; + } + + /* Transfer a block of data */ + buffer_end = &buffer[data->blocksize >> 2]; + if (data->flags == DATA_FLAG_READ) + while (buffer_end > buffer) + *buffer++ = sdhci_readl(sdhci_ctrlr, + SDHCI_BUFFER); + else + while (buffer_end > buffer) + sdhci_writel(sdhci_ctrlr, *buffer++, + SDHCI_BUFFER); + if (++block_count >= data->blocks) + break; + } while (!(stat & SDHCI_INT_DATA_END)); + return 0; +} + +static int sdhci_send_command_bounced(struct sd_mmc_ctrlr *ctrlr, + struct mmc_command *cmd, struct mmc_data *data, + struct bounce_buffer *bbstate) +{ + struct sdhci_ctrlr *sdhci_ctrlr = (struct sdhci_ctrlr *)ctrlr; + u16 mode = 0; + unsigned int stat = 0; + int ret = 0; + u32 mask, flags; + unsigned int timeout, start_addr = 0; + struct stopwatch sw; + + /* Wait max 1 s */ + timeout = 1000; + + sdhci_writel(sdhci_ctrlr, 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->flags & CMD_FLAG_IGNORE_INHIBIT) + mask &= ~SDHCI_DATA_INHIBIT; + + while (sdhci_readl(sdhci_ctrlr, SDHCI_PRESENT_STATE) & mask) { + if (timeout == 0) { + sdhc_trace("Cmd: %2d, Arg: 0x%08x, not sent\n", + cmd->cmdidx, cmd->cmdarg); + sdhc_error("Controller never released inhibit bit(s), " + "present state %#8.8x.\n", + sdhci_readl(sdhci_ctrlr, SDHCI_PRESENT_STATE)); + return CARD_COMM_ERR; + } + timeout--; + udelay(1000); + } + + mask = SDHCI_INT_RESPONSE; + if (!(cmd->resp_type & CARD_RSP_PRESENT)) + flags = SDHCI_CMD_RESP_NONE; + else if (cmd->resp_type & CARD_RSP_136) + flags = SDHCI_CMD_RESP_LONG; + else if (cmd->resp_type & CARD_RSP_BUSY) { + flags = SDHCI_CMD_RESP_SHORT_BUSY; + mask |= SDHCI_INT_DATA_END; + } else + flags = SDHCI_CMD_RESP_SHORT; + + if (cmd->resp_type & CARD_RSP_CRC) + flags |= SDHCI_CMD_CRC; + if (cmd->resp_type & CARD_RSP_OPCODE) + flags |= SDHCI_CMD_INDEX; + if (data) + flags |= SDHCI_CMD_DATA; + + /* Set Transfer mode regarding to data flag */ + if (data) { + sdhci_writew(sdhci_ctrlr, + SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, + data->blocksize), SDHCI_BLOCK_SIZE); + + if (data->flags == DATA_FLAG_READ) + mode |= SDHCI_TRNS_READ; + + if (data->blocks > 1) + mode |= SDHCI_TRNS_BLK_CNT_EN | + SDHCI_TRNS_MULTI | SDHCI_TRNS_ACMD12; + + sdhci_writew(sdhci_ctrlr, data->blocks, SDHCI_BLOCK_COUNT); + + if (DMA_AVAILABLE && (ctrlr->caps & DRVR_CAP_AUTO_CMD12) + && (cmd->cmdidx != MMC_CMD_AUTO_TUNING_SEQUENCE)) { + if (sdhci_setup_adma(sdhci_ctrlr, data)) + return -1; + mode |= SDHCI_TRNS_DMA; + } + sdhci_writew(sdhci_ctrlr, mode, SDHCI_TRANSFER_MODE); + } + + sdhc_trace("Cmd: %2d, Arg: 0x%08x\n", cmd->cmdidx, cmd->cmdarg); + sdhci_writel(sdhci_ctrlr, cmd->cmdarg, SDHCI_ARGUMENT); + sdhci_writew(sdhci_ctrlr, SDHCI_MAKE_CMD(cmd->cmdidx, flags), + SDHCI_COMMAND); + sdhc_log_command_issued(); + + if (DMA_AVAILABLE && (mode & SDHCI_TRNS_DMA)) + return sdhci_complete_adma(sdhci_ctrlr, cmd); + + stopwatch_init_msecs_expire(&sw, 2550); + do { + stat = sdhci_readl(sdhci_ctrlr, SDHCI_INT_STATUS); + if (stat & SDHCI_INT_ERROR) { + sdhc_trace("Error - IntStatus: 0x%08x\n", stat); + break; + } + + if (stat & SDHCI_INT_DATA_AVAIL) { + sdhci_writel(sdhci_ctrlr, stat, SDHCI_INT_STATUS); + return 0; + } + + /* Apply max timeout for R1b-type CMD defined in eMMC ext_csd + except for erase ones */ + if (stopwatch_expired(&sw)) { + if (ctrlr->caps & DRVR_CAP_BROKEN_R1B) + return 0; + sdhc_error( + "Timeout for status update! IntStatus: 0x%08x\n", + stat); + return CARD_TIMEOUT; + } + } while ((stat & mask) != mask); + + if ((stat & (SDHCI_INT_ERROR | mask)) == mask) { + if (cmd->cmdidx) + sdhci_cmd_done(sdhci_ctrlr, cmd); + sdhci_writel(sdhci_ctrlr, mask, SDHCI_INT_STATUS); + } else + ret = -1; + + if (!ret && data) + ret = sdhci_transfer_data(sdhci_ctrlr, data, start_addr); + + if (ctrlr->udelay_wait_after_cmd) + udelay(ctrlr->udelay_wait_after_cmd); + + stat = sdhci_readl(sdhci_ctrlr, SDHCI_INT_STATUS); + sdhci_writel(sdhci_ctrlr, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); + + if (!ret) + return 0; + + sdhci_reset(sdhci_ctrlr, SDHCI_RESET_CMD); + sdhci_reset(sdhci_ctrlr, SDHCI_RESET_DATA); + if (stat & SDHCI_INT_TIMEOUT) { + sdhc_error("CMD%d timeout, IntStatus: 0x%08x\n", cmd->cmdidx, + stat); + return CARD_TIMEOUT; + } + + sdhc_error("CMD%d failed, IntStatus: 0x%08x\n", cmd->cmdidx, stat); + return CARD_COMM_ERR; +} + +__attribute__((weak)) void sdhc_log_command(struct mmc_command *cmd) +{ +} + +__attribute__((weak)) void sdhc_log_command_issued(void) +{ +} + +__attribute__((weak)) void sdhc_log_response(uint32_t entries, + uint32_t *response) +{ +} + +__attribute__((weak)) void sdhc_log_ret(int ret) +{ +} + +static void sdhci_led_control(struct sd_mmc_ctrlr *ctrlr, int on) +{ + uint8_t host_ctrl; + struct sdhci_ctrlr *sdhci_ctrlr = (struct sdhci_ctrlr *)ctrlr; + + host_ctrl = sdhci_readb(sdhci_ctrlr, SDHCI_HOST_CONTROL); + host_ctrl &= ~SDHCI_CTRL_LED; + if (on) + host_ctrl |= SDHCI_CTRL_LED; + sdhci_writeb(sdhci_ctrlr, host_ctrl, SDHCI_HOST_CONTROL); +} + +static int sdhci_send_command(struct sd_mmc_ctrlr *ctrlr, + struct mmc_command *cmd, struct mmc_data *data) +{ + void *buf; + unsigned int bbflags; + size_t len; + struct bounce_buffer *bbstate = NULL; + struct bounce_buffer bbstate_val; + int ret; + + sdhc_log_command(cmd); + + if (IS_ENABLED(CONFIG_SDHCI_BOUNCE_BUFFER) && data) { + if (data->flags & DATA_FLAG_READ) { + buf = data->dest; + bbflags = GEN_BB_WRITE; + } else { + buf = (void *)data->src; + bbflags = GEN_BB_READ; + } + len = data->blocks * data->blocksize; + + /* + * on some platform(like rk3399 etc) need to worry about + * cache coherency, so check the buffer, if not dma + * coherent, use bounce_buffer to do DMA management. + */ + if (!dma_coherent(buf)) { + bbstate = &bbstate_val; + if (bounce_buffer_start(bbstate, buf, len, bbflags)) { + sdhc_error( + "ERROR: Failed to get bounce buffer.\n"); + return -1; + } + } + } + + sdhci_led_control(ctrlr, 1); + ret = sdhci_send_command_bounced(ctrlr, cmd, data, bbstate); + sdhci_led_control(ctrlr, 0); + sdhc_log_ret(ret); + + if (IS_ENABLED(CONFIG_SDHCI_BOUNCE_BUFFER) && bbstate) + bounce_buffer_stop(bbstate); + + return ret; +} + +static int sdhci_set_clock(struct sdhci_ctrlr *sdhci_ctrlr, unsigned int clock) +{ + struct sd_mmc_ctrlr *ctrlr = &sdhci_ctrlr->sd_mmc_ctrlr; + unsigned int actual, div, clk, timeout; + + /* Turn off the clock if requested */ + actual = clock; + if (actual == 0) { + sdhci_writew(sdhci_ctrlr, 0, SDHCI_CLOCK_CONTROL); + sdhc_debug("SDHCI bus clock: Off\n"); + return 0; + } + + /* Compute the divisor for the new clock frequency */ + actual = MIN(actual, ctrlr->f_max); + actual = MAX(actual, ctrlr->f_min); + if (ctrlr->clock_base <= actual) + div = 0; + else { + /* Version 3.00 divisors must be a multiple of 2. */ + if ((ctrlr->version & SDHCI_SPEC_VER_MASK) + >= SDHCI_SPEC_300) { + div = MIN(((ctrlr->clock_base + actual - 1) + / actual), SDHCI_MAX_DIV_SPEC_300); + actual = ctrlr->clock_base / div; + div += 1; + } else { + /* Version 2.00 divisors must be a power of 2. */ + for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *= 2) { + if ((ctrlr->clock_base / div) <= actual) + break; + } + actual = ctrlr->clock_base / div; + } + div >>= 1; + } + + /* Set the new clock frequency */ + if (actual != ctrlr->bus_hz) { + /* Turn off the clock */ + sdhci_writew(sdhci_ctrlr, 0, SDHCI_CLOCK_CONTROL); + + /* Set the new clock frequency */ + 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(sdhci_ctrlr, clk, SDHCI_CLOCK_CONTROL); + + /* Display the requested clock frequency */ + sdhc_debug("SDHCI bus clock: %d.%03d MHz\n", + actual / 1000000, + (actual / 1000) % 1000); + + /* Wait max 20 ms */ + timeout = 20; + while (!((clk = sdhci_readw(sdhci_ctrlr, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + sdhc_error( + "Internal clock never stabilised.\n"); + return -1; + } + timeout--; + udelay(1000); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(sdhci_ctrlr, clk, SDHCI_CLOCK_CONTROL); + ctrlr->bus_hz = actual; + } + return 0; +} + +/* Find leftmost set bit in a 32 bit integer */ +static int fls(u32 x) +{ + int r = 32; + + if (!x) + return 0; + if (!(x & 0xffff0000u)) { + x <<= 16; + r -= 16; + } + if (!(x & 0xff000000u)) { + x <<= 8; + r -= 8; + } + if (!(x & 0xf0000000u)) { + x <<= 4; + r -= 4; + } + if (!(x & 0xc0000000u)) { + x <<= 2; + r -= 2; + } + if (!(x & 0x80000000u)) { + x <<= 1; + r -= 1; + } + return r; +} + +static void sdhci_set_power(struct sdhci_ctrlr *sdhci_ctrlr, + unsigned short power) +{ + struct sd_mmc_ctrlr *ctrlr = &sdhci_ctrlr->sd_mmc_ctrlr; + u8 pwr = 0; + u8 pwr_ctrl; + const char *voltage; + + if (power != (unsigned short)-1) { + switch (1 << power) { + case MMC_VDD_165_195: + voltage = "1.8"; + pwr = SDHCI_POWER_180; + break; + case MMC_VDD_29_30: + case MMC_VDD_30_31: + voltage = "3.0"; + pwr = SDHCI_POWER_300; + break; + case MMC_VDD_32_33: + case MMC_VDD_33_34: + voltage = "3.3"; + pwr = SDHCI_POWER_330; + break; + } + } + + /* Determine the power state */ + pwr_ctrl = sdhci_readb(sdhci_ctrlr, SDHCI_POWER_CONTROL); + if (pwr == 0) { + if (pwr_ctrl & SDHCI_POWER_ON) + sdhc_debug("SDHCI voltage: Off\n"); + sdhci_writeb(sdhci_ctrlr, 0, SDHCI_POWER_CONTROL); + return; + } + + /* Determine if the power has changed */ + if (pwr_ctrl != (pwr | SDHCI_POWER_ON)) { + sdhc_debug("SDHCI voltage: %s Volts\n", voltage); + + /* Select the voltage */ + if (ctrlr->caps & DRVR_CAP_NO_SIMULT_VDD_AND_POWER) + sdhci_writeb(sdhci_ctrlr, pwr, SDHCI_POWER_CONTROL); + + /* Apply power to the SD/MMC device */ + pwr |= SDHCI_POWER_ON; + sdhci_writeb(sdhci_ctrlr, pwr, SDHCI_POWER_CONTROL); + } +} + +const u16 speed_driver_voltage[] = { + 0, /* 0: BUS_TIMING_LEGACY */ + 0, /* 1: BUS_TIMING_MMC_HS */ + 0, /* 2: BUS_TIMING_SD_HS */ + SDHCI_CTRL_UHS_SDR12 | SDHCI_CTRL_VDD_180, /* 3: BUS_TIMING_UHS_SDR12 */ + SDHCI_CTRL_UHS_SDR25 | SDHCI_CTRL_VDD_180, /* 4: BUS_TIMING_UHS_SDR25 */ + SDHCI_CTRL_UHS_SDR50 | SDHCI_CTRL_VDD_180, /* 5: BUS_TIMING_UHS_SDR50 */ + /* 6: BUS_TIMING_UHS_SDR104 */ + SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_DRV_TYPE_A | SDHCI_CTRL_VDD_180, + SDHCI_CTRL_UHS_DDR50 | SDHCI_CTRL_VDD_180, /* 7: BUS_TIMING_UHS_DDR50 */ + SDHCI_CTRL_UHS_DDR50 | SDHCI_CTRL_VDD_180, /* 8: BUS_TIMING_MMC_DDR52 */ + /* 9: BUS_TIMING_MMC_HS200 */ + SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_DRV_TYPE_A | SDHCI_CTRL_VDD_180, + /* 10: BUS_TIMING_MMC_HS400 */ + SDHCI_CTRL_HS400 | SDHCI_CTRL_DRV_TYPE_A | SDHCI_CTRL_VDD_180, + /* 11: BUS_TIMING_MMC_HS400ES */ + SDHCI_CTRL_HS400 | SDHCI_CTRL_DRV_TYPE_A | SDHCI_CTRL_VDD_180 +}; + +static void sdhci_set_uhs_signaling(struct sdhci_ctrlr *sdhci_ctrlr, + uint32_t timing) +{ + u16 ctrl_2; + + /* Select bus speed mode, driver and VDD 1.8 volt support */ + ctrl_2 = sdhci_readw(sdhci_ctrlr, SDHCI_HOST_CONTROL2); + ctrl_2 &= ~(SDHCI_CTRL_UHS_MASK | SDHCI_CTRL_DRV_TYPE_MASK + | SDHCI_CTRL_VDD_180); + if (timing < ARRAY_SIZE(speed_driver_voltage)) + ctrl_2 |= speed_driver_voltage[timing]; + sdhci_writew(sdhci_ctrlr, ctrl_2, SDHCI_HOST_CONTROL2); +} + +static void sdhci_set_ios(struct sd_mmc_ctrlr *ctrlr) +{ + struct sdhci_ctrlr *sdhci_ctrlr = (struct sdhci_ctrlr *)ctrlr; + u32 ctrl; + u32 previous_ctrl; + u32 bus_width; + int version; + + /* Set the clock frequency */ + if (ctrlr->bus_hz != ctrlr->request_hz) + sdhci_set_clock(sdhci_ctrlr, ctrlr->request_hz); + + /* Switch to 1.8 volt for HS200 */ + if (ctrlr->caps & DRVR_CAP_1V8_VDD) + if (ctrlr->bus_hz == CLOCK_200MHZ) + sdhci_set_power(sdhci_ctrlr, MMC_VDD_165_195_SHIFT); + + /* Determine the new bus width */ + bus_width = 1; + ctrl = sdhci_readb(sdhci_ctrlr, SDHCI_HOST_CONTROL); + previous_ctrl = ctrl; + ctrl &= ~SDHCI_CTRL_4BITBUS; + version = ctrlr->version & SDHCI_SPEC_VER_MASK; + if (version >= SDHCI_SPEC_300) + ctrl &= ~SDHCI_CTRL_8BITBUS; + + if ((ctrlr->bus_width == 8) && (version >= SDHCI_SPEC_300)) { + ctrl |= SDHCI_CTRL_8BITBUS; + bus_width = 8; + } else if (ctrlr->bus_width == 4) { + ctrl |= SDHCI_CTRL_4BITBUS; + bus_width = 4; + } + + if (!(ctrlr->timing == BUS_TIMING_LEGACY) && + !(ctrlr->caps & DRVR_CAP_NO_HISPD_BIT)) + ctrl |= SDHCI_CTRL_HISPD; + else + ctrl &= ~SDHCI_CTRL_HISPD; + + sdhci_set_uhs_signaling(sdhci_ctrlr, ctrlr->timing); + + if (DMA_AVAILABLE) { + if (ctrlr->caps & DRVR_CAP_AUTO_CMD12) { + ctrl &= ~SDHCI_CTRL_DMA_MASK; + if (ctrlr->caps & DRVR_CAP_DMA_64BIT) + ctrl |= SDHCI_CTRL_ADMA64; + else + ctrl |= SDHCI_CTRL_ADMA32; + } + } + + /* Set the new bus width */ + if (IS_ENABLED(CONFIG_SDHC_DEBUG) + && ((ctrl ^ previous_ctrl) & (SDHCI_CTRL_4BITBUS + | ((version >= SDHCI_SPEC_300) ? SDHCI_CTRL_8BITBUS : 0)))) + sdhc_debug("SDHCI bus width: %d bit%s\n", bus_width, + (bus_width != 1) ? "s" : ""); + sdhci_writeb(sdhci_ctrlr, ctrl, SDHCI_HOST_CONTROL); +} + +static void sdhci_tuning_start(struct sd_mmc_ctrlr *ctrlr, int retune) +{ + uint16_t host_ctrl2; + struct sdhci_ctrlr *sdhci_ctrlr = (struct sdhci_ctrlr *)ctrlr; + + /* Start the bus tuning */ + host_ctrl2 = sdhci_readw(sdhci_ctrlr, SDHCI_HOST_CONTROL2); + host_ctrl2 &= ~SDHCI_CTRL_TUNED_CLK; + host_ctrl2 |= (retune ? SDHCI_CTRL_TUNED_CLK : 0) + | SDHCI_CTRL_EXEC_TUNING; + sdhci_writew(sdhci_ctrlr, host_ctrl2, SDHCI_HOST_CONTROL2); +} + +static int sdhci_is_tuning_complete(struct sd_mmc_ctrlr *ctrlr, int *successful) +{ + uint16_t host_ctrl2; + struct sdhci_ctrlr *sdhci_ctrlr = (struct sdhci_ctrlr *)ctrlr; + + /* Determine if the bus tuning has completed */ + host_ctrl2 = sdhci_readw(sdhci_ctrlr, SDHCI_HOST_CONTROL2); + *successful = ((host_ctrl2 & SDHCI_CTRL_TUNED_CLK) != 0); + return ((host_ctrl2 & SDHCI_CTRL_EXEC_TUNING) == 0); +} + +/* Prepare SDHCI controller to be initialized */ +static int sdhci_pre_init(struct sdhci_ctrlr *sdhci_ctrlr) +{ + struct sd_mmc_ctrlr *ctrlr = &sdhci_ctrlr->sd_mmc_ctrlr; + unsigned int caps, caps_1; + + /* Get controller version and capabilities */ + ctrlr->version = sdhci_readw(sdhci_ctrlr, SDHCI_HOST_VERSION) & 0xff; + caps = sdhci_readl(sdhci_ctrlr, SDHCI_CAPABILITIES); + caps_1 = sdhci_readl(sdhci_ctrlr, SDHCI_CAPABILITIES_1); + + /* Determine the supported voltages */ + if (caps & SDHCI_CAN_VDD_330) + ctrlr->voltages |= MMC_VDD_32_33 | MMC_VDD_33_34; + if (caps & SDHCI_CAN_VDD_300) + ctrlr->voltages |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (caps & SDHCI_CAN_VDD_180) + ctrlr->voltages |= MMC_VDD_165_195; + + /* Get the controller's base clock frequency */ + if ((ctrlr->version & SDHCI_SPEC_VER_MASK) >= SDHCI_SPEC_300) + ctrlr->clock_base = (caps & SDHCI_CLOCK_V3_BASE_MASK) + >> SDHCI_CLOCK_BASE_SHIFT; + else + ctrlr->clock_base = (caps & SDHCI_CLOCK_BASE_MASK) + >> SDHCI_CLOCK_BASE_SHIFT; + ctrlr->clock_base *= 1000000; + ctrlr->f_max = ctrlr->clock_base; + + /* Determine the controller's clock frequency range */ + ctrlr->f_min = 0; + if ((ctrlr->version & SDHCI_SPEC_VER_MASK) >= SDHCI_SPEC_300) + ctrlr->f_min = + ctrlr->clock_base / SDHCI_MAX_DIV_SPEC_300; + else + ctrlr->f_min = + ctrlr->clock_base / SDHCI_MAX_DIV_SPEC_200; + + /* Determine the controller's modes of operation */ + ctrlr->caps |= DRVR_CAP_HS52 | DRVR_CAP_HS; + if (ctrlr->clock_base >= CLOCK_200MHZ) { + ctrlr->caps |= DRVR_CAP_HS200 | DRVR_CAP_HS200_TUNING; + if (caps_1 & SDHCI_SUPPORT_HS400) + ctrlr->caps |= DRVR_CAP_HS400 + | DRVR_CAP_ENHANCED_STROBE; + } + + /* Determine the bus widths the controller supports */ + ctrlr->caps |= DRVR_CAP_4BIT; + if (caps & SDHCI_CAN_DO_8BIT) + ctrlr->caps |= DRVR_CAP_8BIT; + + /* Determine the controller's DMA support */ + if (caps & SDHCI_CAN_DO_ADMA2) + ctrlr->caps |= DRVR_CAP_AUTO_CMD12; + if (DMA_AVAILABLE && (caps & SDHCI_CAN_64BIT)) + ctrlr->caps |= DRVR_CAP_DMA_64BIT; + + /* Specify the modes that the driver stack supports */ + ctrlr->caps |= DRVR_CAP_HC; + + /* Let the SOC adjust the configuration to handle controller quirks */ + soc_sd_mmc_controller_quirks(&sdhci_ctrlr->sd_mmc_ctrlr); + if (ctrlr->clock_base == 0) { + sdhc_error("Hardware doesn't specify base clock frequency\n"); + return -1; + } + if (!ctrlr->f_max) + ctrlr->f_max = ctrlr->clock_base; + + /* Display the results */ + sdhc_trace("0x%08x: ctrlr->caps\n", ctrlr->caps); + sdhc_trace("%d.%03d MHz: ctrlr->clock_base\n", + ctrlr->clock_base / 1000000, + (ctrlr->clock_base / 1000) % 1000); + sdhc_trace("%d.%03d MHz: ctrlr->f_max\n", + ctrlr->f_max / 1000000, + (ctrlr->f_max / 1000) % 1000); + sdhc_trace("%d.%03d MHz: ctrlr->f_min\n", + ctrlr->f_min / 1000000, + (ctrlr->f_min / 1000) % 1000); + sdhc_trace("0x%08x: ctrlr->voltages\n", ctrlr->voltages); + + sdhci_reset(sdhci_ctrlr, SDHCI_RESET_ALL); + + return 0; +} + +__attribute__((weak)) void soc_sd_mmc_controller_quirks(struct sd_mmc_ctrlr + *ctrlr) +{ +} + +static int sdhci_init(struct sdhci_ctrlr *sdhci_ctrlr) +{ + struct sd_mmc_ctrlr *ctrlr = &sdhci_ctrlr->sd_mmc_ctrlr; + int rv; + + /* Only initialize the controller upon reset or card insertion */ + if (ctrlr->initialized) + return 0; + + sdhc_debug("SDHCI Controller Base Address: 0x%p\n", + sdhci_ctrlr->ioaddr); + + rv = sdhci_pre_init(sdhci_ctrlr); + if (rv) + return rv; /* The error has been already reported */ + + sdhci_set_power(sdhci_ctrlr, fls(ctrlr->voltages) - 1); + + if (ctrlr->caps & DRVR_CAP_NO_CD) { + unsigned int status; + + sdhci_writel(sdhci_ctrlr, SDHCI_CTRL_CD_TEST_INS + | SDHCI_CTRL_CD_TEST, SDHCI_HOST_CONTROL); + + status = sdhci_readl(sdhci_ctrlr, SDHCI_PRESENT_STATE); + while ((!(status & SDHCI_CARD_PRESENT)) || + (!(status & SDHCI_CARD_STATE_STABLE)) || + (!(status & SDHCI_CARD_DETECT_PIN_LEVEL))) + status = sdhci_readl(sdhci_ctrlr, SDHCI_PRESENT_STATE); + } + + /* Enable only interrupts served by the SD controller */ + sdhci_writel(sdhci_ctrlr, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK, + SDHCI_INT_ENABLE); + /* Mask all sdhci interrupt sources */ + sdhci_writel(sdhci_ctrlr, 0x0, SDHCI_SIGNAL_ENABLE); + + /* Set timeout to maximum, shouldn't happen if everything's right. */ + sdhci_writeb(sdhci_ctrlr, 0xe, SDHCI_TIMEOUT_CONTROL); + + mdelay(10); + ctrlr->initialized = 1; + return 0; +} + +static int sdhci_update(struct sdhci_ctrlr *sdhci_ctrlr) +{ + struct sd_mmc_ctrlr *ctrlr = &sdhci_ctrlr->sd_mmc_ctrlr; + + if (ctrlr->caps & DRVR_CAP_REMOVABLE) { + int present = (sdhci_readl(sdhci_ctrlr, SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT) != 0; + + if (!present) { + /* A card was present indicate the controller needs + * initialization on the next call. + */ + ctrlr->initialized = 0; + return 0; + } + } + + /* A card is present, get it ready. */ + if (sdhci_init(sdhci_ctrlr)) + return -1; + return 0; +} + +void sdhci_update_pointers(struct sdhci_ctrlr *sdhci_ctrlr) +{ + struct sd_mmc_ctrlr *ctrlr = &sdhci_ctrlr->sd_mmc_ctrlr; + + /* Update the routine pointers */ + ctrlr->send_cmd = &sdhci_send_command; + ctrlr->set_ios = &sdhci_set_ios; + ctrlr->tuning_start = &sdhci_tuning_start; + ctrlr->is_tuning_complete = &sdhci_is_tuning_complete; +} + +int add_sdhci(struct sdhci_ctrlr *sdhci_ctrlr) +{ + struct sd_mmc_ctrlr *ctrlr = &sdhci_ctrlr->sd_mmc_ctrlr; + + sdhci_update_pointers(sdhci_ctrlr); + + /* TODO(vbendeb): check if SDHCI spec allows to retrieve this value. */ + ctrlr->b_max = 65535; + + /* Initialize the SDHC controller */ + return sdhci_update(sdhci_ctrlr); +} |