Lee Leahy has uploaded a new change for review. ( https://review.coreboot.org/19004 )
Change subject: drivers/storage: Add depthcharge SD/EMMC driver ......................................................................
drivers/storage: Add depthcharge SD/EMMC driver
Copy the SD/MMC driver from depthcharge revision 59c7166a85d2f17085914fc9444c9444160c77f8 into coreboot.
TEST=Does not build
Change-Id: Ib2b3c2b7eabbb64d3760c0da154a5c434d48ee53 Signed-off-by: Lee Leahy Leroy.P.Leahy@intel.com --- A src/drivers/storage/mmc.c A src/drivers/storage/mmc.h A src/drivers/storage/sdhci.c A src/drivers/storage/sdhci.h 4 files changed, 2,864 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/04/19004/1
diff --git a/src/drivers/storage/mmc.c b/src/drivers/storage/mmc.c new file mode 100644 index 0000000..b877548 --- /dev/null +++ b/src/drivers/storage/mmc.c @@ -0,0 +1,1332 @@ +/* + * Copyright 2008, Freescale Semiconductor, Inc + * Andy Fleming + * + * Copyright 2013 Google Inc. All rights reserved. + * + * Based vaguely on the Linux code + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 <endian.h> +#include <libpayload.h> +#include <stdint.h> + +#include "config.h" +#include "drivers/storage/mmc.h" + +/* Set block count limit because of 16 bit register limit on some hardware*/ +#ifndef CONFIG_SYS_MMC_MAX_BLK_COUNT +#define CONFIG_SYS_MMC_MAX_BLK_COUNT 65535 +#endif + +/* Set to 1 to turn on debug messages. */ +int __mmc_debug = 0; +int __mmc_trace = 0; + +int mmc_busy_wait_io(volatile uint32_t *address, uint32_t *output, + uint32_t io_mask, uint32_t timeout_ms) +{ + uint32_t value = (uint32_t)-1; + uint64_t start = timer_us(0); + + if (!output) + output = &value; + for (; *output & io_mask; *output = readl(address)) { + if (timer_us(start) > timeout_ms * 1000) + return -1; + } + return 0; +} + +int mmc_busy_wait_io_until(volatile uint32_t *address, uint32_t *output, + uint32_t io_mask, uint32_t timeout_ms) +{ + uint32_t value = 0; + uint64_t start = timer_us(0); + + if (!output) + output = &value; + for (; !(*output & io_mask); *output = readl(address)) { + if (timer_us(start) > timeout_ms * 1000) + return -1; + } + return 0; +} + +static uint64_t extract_uint32_bits(const uint32_t *array, int start, int count) +{ + int i; + uint64_t value = 0; + + for (i = 0; i < count; i++, start++) { + value <<= 1; + value |= (array[start / 32] >> (31 - (start % 32))) & 0x1; + } + return value; +} + +static int mmc_send_cmd(MmcCtrlr *ctrlr, MmcCommand *cmd, MmcData *data) +{ + int ret = -1, retries = 2; + + mmc_trace("CMD_SEND:%d %p\n", cmd->cmdidx, ctrlr); + mmc_trace("\tARG\t\t\t %#8.8x\n", cmd->cmdarg); + mmc_trace("\tFLAG\t\t\t %d\n", cmd->flags); + if (data) { + mmc_trace("\t%s %d block(s) of %d bytes (%p)\n", + data->flags == MMC_DATA_READ ? "READ" : "WRITE", + data->blocks, + data->blocksize, + data->dest); + } + + while (retries--) { + ret = ctrlr->send_cmd(ctrlr, cmd, data); + + switch (cmd->resp_type) { + case MMC_RSP_NONE: + mmc_trace("\tMMC_RSP_NONE\n"); + break; + + case MMC_RSP_R1: + mmc_trace("\tMMC_RSP_R1,5,6,7 \t %#8.8x\n", + cmd->response[0]); + break; + + case MMC_RSP_R1b: + mmc_trace("\tMMC_RSP_R1b\t\t %#8.8x\n", + cmd->response[0]); + break; + + case MMC_RSP_R2: + mmc_trace("\tMMC_RSP_R2\t\t %#8.8x\n", + cmd->response[0]); + mmc_trace("\t \t\t %#8.8x\n", + cmd->response[1]); + mmc_trace("\t \t\t %#8.8x\n", + cmd->response[2]); + mmc_trace("\t \t\t %#8.8x\n", + cmd->response[3]); + break; + + case MMC_RSP_R3: + mmc_trace("\tMMC_RSP_R3,4\t\t %#8.8x\n", + cmd->response[0]); + break; + + default: + mmc_trace("\tERROR MMC rsp not supported\n"); + break; + } + mmc_trace("\trv:\t\t\t %d\n", ret); + + /* Retry failed commands, bail out otherwise. */ + if (!ret) + break; + } + return ret; +} + +static int mmc_send_status(MmcMedia *media, ssize_t tries) +{ + MmcCommand cmd; + cmd.cmdidx = MMC_CMD_SEND_STATUS; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = media->rca << 16; + cmd.flags = 0; + + while (tries--) { + int err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + else if (cmd.response[0] & MMC_STATUS_RDY_FOR_DATA) + break; + else if (cmd.response[0] & MMC_STATUS_MASK) { + mmc_error("Status Error: %#8.8x\n", cmd.response[0]); + return MMC_COMM_ERR; + } + + udelay(100); + } + + mmc_trace("CURR STATE:%d\n", + (cmd.response[0] & MMC_STATUS_CURR_STATE) >> 9); + + if (tries < 0) { + mmc_error("Timeout waiting card ready\n"); + return MMC_TIMEOUT; + } + return 0; +} + +static int mmc_set_blocklen(MmcCtrlr *ctrlr, int len) +{ + MmcCommand cmd; + cmd.cmdidx = MMC_CMD_SET_BLOCKLEN; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = len; + cmd.flags = 0; + + return mmc_send_cmd(ctrlr, &cmd, NULL); +} + +static uint32_t mmc_write(MmcMedia *media, uint32_t start, lba_t block_count, + const void *src) +{ + MmcCommand cmd; + cmd.resp_type = MMC_RSP_R1; + cmd.flags = 0; + + if (block_count > 1) + cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK; + else + cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK; + + if (media->high_capacity) + cmd.cmdarg = start; + else + cmd.cmdarg = start * media->write_bl_len; + + MmcData data; + data.src = src; + data.blocks = block_count; + data.blocksize = media->write_bl_len; + data.flags = MMC_DATA_WRITE; + + if (mmc_send_cmd(media->ctrlr, &cmd, &data)) { + mmc_error("mmc write failed\n"); + return 0; + } + + /* SPI multiblock writes terminate using a special + * token, not a STOP_TRANSMISSION request. + */ + if ((block_count > 1) && !(media->ctrlr->caps & MMC_AUTO_CMD12)) { + cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1b; + cmd.flags = 0; + if (mmc_send_cmd(media->ctrlr, &cmd, NULL)) { + mmc_error("mmc fail to send stop cmd\n"); + return 0; + } + + /* Waiting for the ready status */ + mmc_send_status(media, MMC_IO_RETRIES); + } + + return block_count; +} + +static int mmc_read(MmcMedia *media, void *dest, uint32_t start, + lba_t block_count) +{ + + MmcCommand cmd; + cmd.resp_type = MMC_RSP_R1; + cmd.flags = 0; + + if (block_count > 1) + cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK; + else + cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK; + + if (media->high_capacity) + cmd.cmdarg = start; + else + cmd.cmdarg = start * media->read_bl_len; + + MmcData data; + data.dest = dest; + data.blocks = block_count; + data.blocksize = media->read_bl_len; + data.flags = MMC_DATA_READ; + + if (mmc_send_cmd(media->ctrlr, &cmd, &data)) + return 0; + + if ((block_count > 1) && !(media->ctrlr->caps & MMC_AUTO_CMD12)) { + cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1b; + cmd.flags = 0; + if (mmc_send_cmd(media->ctrlr, &cmd, NULL)) { + mmc_error("mmc fail to send stop cmd\n"); + return 0; + } + + /* Waiting for the ready status */ + mmc_send_status(media, MMC_IO_RETRIES); + } + + return block_count; +} + +static int mmc_go_idle(MmcMedia *media) +{ + // Some cards can't accept idle commands without delay. + if (media->dev.removable) + mdelay(1); + + MmcCommand cmd; + cmd.cmdidx = MMC_CMD_GO_IDLE_STATE; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_NONE; + cmd.flags = 0; + + int err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + + // Some cards need more than half second to respond to next command (ex, + // SEND_OP_COND). + if (media->dev.removable) + mdelay(2); + + return 0; +} + +static int sd_send_op_cond(MmcMedia *media) +{ + int err; + MmcCommand cmd; + + int tries = MMC_IO_RETRIES; + while (tries--) { + cmd.cmdidx = MMC_CMD_APP_CMD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + cmd.flags = 0; + + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + + cmd.cmdidx = SD_CMD_APP_SEND_OP_COND; + cmd.resp_type = MMC_RSP_R3; + + /* + * Most cards do not answer if some reserved bits + * in the ocr are set. However, Some controller + * can set bit 7 (reserved for low voltages), but + * how to manage low voltages SD card is not yet + * specified. + */ + cmd.cmdarg = (media->ctrlr->voltages & 0xff8000); + + if (media->version == SD_VERSION_2) + cmd.cmdarg |= OCR_HCS; + + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + + // OCR_BUSY means "initialization complete". + if (cmd.response[0] & OCR_BUSY) + break; + + udelay(100); + } + if (tries < 0) + return MMC_UNUSABLE_ERR; + + if (media->version != SD_VERSION_2) + media->version = SD_VERSION_1_0; + + media->ocr = cmd.response[0]; + media->high_capacity = ((media->ocr & OCR_HCS) == OCR_HCS); + media->rca = 0; + return 0; +} + +/* We pass in the cmd since otherwise the init seems to fail */ +static int mmc_send_op_cond_iter(MmcMedia *media, MmcCommand *cmd, int use_arg) +{ + cmd->cmdidx = MMC_CMD_SEND_OP_COND; + cmd->resp_type = MMC_RSP_R3; + + if (use_arg) { + uint32_t mask = media->op_cond_response & + (OCR_VOLTAGE_MASK | OCR_ACCESS_MODE); + cmd->cmdarg = media->ctrlr->voltages & mask; + + if (media->ctrlr->caps & MMC_MODE_HC) + cmd->cmdarg |= OCR_HCS; + } + cmd->flags = 0; + int err = mmc_send_cmd(media->ctrlr, cmd, NULL); + if (err) + return err; + + media->op_cond_response = cmd->response[0]; + return 0; +} + +static int mmc_send_op_cond(MmcMedia *media) +{ + MmcCommand cmd; + int max_iters; + + /* Some cards seem to need this */ + mmc_go_idle(media); + + /* Devices with hardcoded voltage do not need second iteration. */ + cmd.cmdarg = media->ctrlr->hardcoded_voltage; + max_iters = cmd.cmdarg ? 1 : 2; + + /* Ask the card for its capabilities unless required to be hardcoded. */ + for (int i = 0; i < max_iters; i++) { + int err = mmc_send_op_cond_iter(media, &cmd, i != 0); + if (err) + return err; + + // OCR_BUSY is active low, this bit set means + // "initialization complete". + if (media->op_cond_response & OCR_BUSY) + return 0; + } + return MMC_IN_PROGRESS; +} + +static int mmc_complete_op_cond(MmcMedia *media) +{ + MmcCommand cmd; + + int timeout = MMC_INIT_TIMEOUT_US; + uint64_t start; + + start = timer_us(0); + while (1) { + // CMD1 queries whether initialization is done. + int err = mmc_send_op_cond_iter(media, &cmd, 1); + if (err) + return err; + + // OCR_BUSY means "initialization complete". + if (media->op_cond_response & OCR_BUSY) + break; + + // Check if init timeout has expired. + if (timer_us(start) > timeout) + return MMC_UNUSABLE_ERR; + + udelay(100); + } + + media->version = MMC_VERSION_UNKNOWN; + media->ocr = cmd.response[0]; + + media->high_capacity = ((media->ocr & OCR_HCS) == OCR_HCS); + media->rca = 0; + return 0; +} + +static int mmc_send_ext_csd(MmcCtrlr *ctrlr, unsigned char *ext_csd) +{ + int rv; + /* Get the Card Status Register */ + MmcCommand cmd; + cmd.cmdidx = MMC_CMD_SEND_EXT_CSD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + cmd.flags = 0; + + MmcData data; + data.dest = (char *)ext_csd; + data.blocks = 1; + data.blocksize = 512; + data.flags = MMC_DATA_READ; + + rv = mmc_send_cmd(ctrlr, &cmd, &data); + + if (!rv && __mmc_trace) { + int i, size; + + size = data.blocks * data.blocksize; + mmc_trace("\t%p ext_csd:", ctrlr); + for (i = 0; i < size; i++) { + if (!(i % 32)) + printf("\n"); + printf(" %2.2x", ext_csd[i]); + } + printf("\n"); + } + return rv; +} + +static int mmc_switch(MmcMedia *media, uint8_t set, uint8_t index, + uint8_t value) +{ + MmcCommand cmd; + cmd.cmdidx = MMC_CMD_SWITCH; + cmd.resp_type = MMC_RSP_R1b; + cmd.cmdarg = ((MMC_SWITCH_MODE_WRITE_BYTE << 24) | + (index << 16) | (value << 8)); + cmd.flags = 0; + + int ret = mmc_send_cmd(media->ctrlr, &cmd, NULL); + + /* Waiting for the ready status */ + mmc_send_status(media, MMC_IO_RETRIES); + return ret; + +} + +static void mmc_set_bus_width(MmcCtrlr *ctrlr, uint32_t width) +{ + ctrlr->bus_width = width; + ctrlr->set_ios(ctrlr); +} + +static void mmc_set_timing(MmcCtrlr *ctrlr, uint32_t timing) +{ + ctrlr->timing = timing; + ctrlr->set_ios(ctrlr); +} + +static void mmc_set_clock(MmcCtrlr *ctrlr, uint32_t clock) +{ + clock = MIN(clock, ctrlr->f_max); + clock = MAX(clock, ctrlr->f_min); + + ctrlr->bus_hz = clock; + ctrlr->set_ios(ctrlr); +} + +static void mmc_recalculate_clock(MmcMedia *media) +{ + uint32_t clock; + + if (IS_SD(media)) { + if (media->caps & MMC_MODE_HS) + clock = MMC_CLOCK_50MHZ; + else + clock = MMC_CLOCK_25MHZ; + } else { + if (media->caps & MMC_MODE_HS) { + if ((media->caps & MMC_MODE_HS_200MHz) || + (media->caps & MMC_MODE_HS400ES)) + clock = MMC_CLOCK_200MHZ; + else if (media->caps & MMC_MODE_HS_52MHz) + clock = MMC_CLOCK_52MHZ; + else + clock = MMC_CLOCK_26MHZ; + } + } + + mmc_set_clock(media->ctrlr, clock); +} + +static int mmc_select_hs(MmcMedia *media) +{ + int ret; + ret = mmc_switch(media, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS); + + if (ret) + return ret; + + mmc_set_timing(media->ctrlr, MMC_TIMING_MMC_HS); + media->caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + mmc_recalculate_clock(media); + + ret = mmc_send_status(media, MMC_IO_RETRIES); + + return ret; +} + +static int mmc_select_hs400es(MmcMedia *media) +{ + int ret; + + /* Switch card to HS mode */ + ret = mmc_select_hs(media); + if (ret) { + mmc_error("switch to high-speed failed\n"); + return ret; + } + + /* Switch card to DDR with strobe bit */ + ret = mmc_switch(media, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, + EXT_CSD_DDR_BUS_WIDTH_8 | EXT_CSD_BUS_WIDTH_STROBE); + if (ret) { + mmc_error("switch to bus width for hs400es failed\n"); + return ret; + } + + /* Adjust Host Bus With to 8-bit */ + mmc_set_bus_width(media->ctrlr, 8); + media->caps |= EXT_CSD_BUS_WIDTH_8; + + /* Switch card to HS400 */ + ret = mmc_switch(media, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS400); + if (ret) { + mmc_error("switch to hs400es failed\n"); + return ret; + } + /* Set host controller to HS400 timing and frequency */ + mmc_set_timing(media->ctrlr, MMC_TIMING_MMC_HS400ES); + media->caps |= MMC_MODE_HS400ES | MMC_MODE_HS_52MHz | MMC_MODE_HS; + + mmc_recalculate_clock(media); + + ret = mmc_send_status(media, MMC_IO_RETRIES); + + return ret; +} + +static int mmc_select_hs200(MmcMedia *media) +{ + int ret; + ret = mmc_switch(media, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_8); + if (ret) + return ret; + + /* Adjust host bus width to 8-bit */ + mmc_set_bus_width(media->ctrlr, 8); + media->caps |= EXT_CSD_BUS_WIDTH_8; + + /* Switch to HS200 */ + ret = mmc_switch(media, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, EXT_CSD_TIMING_HS200); + + if (ret) + return ret; + + mmc_set_timing(media->ctrlr, MMC_TIMING_MMC_HS200); + media->caps |= MMC_MODE_HS_200MHz | MMC_MODE_HS_52MHz | MMC_MODE_HS; + + mmc_recalculate_clock(media); + + return ret; +} + +static int mmc_change_freq(MmcMedia *media) +{ + int err; + ALLOC_CACHE_ALIGN_BUFFER(unsigned char, ext_csd, 512); + + media->caps = 0; + + /* Only version 4 supports high-speed */ + if (media->version < MMC_VERSION_4) + return 0; + + err = mmc_send_ext_csd(media->ctrlr, ext_csd); + if (err) + return err; + + if ((media->ctrlr->caps & MMC_MODE_HS400ES) && + (ext_csd[EXT_CSD_CARD_TYPE] & MMC_HS400) && + ext_csd[EXT_CSD_STROBE_SUPPORT]) + err = mmc_select_hs400es(media); + else if ((media->ctrlr->caps & MMC_MODE_HS_200MHz) && + (ext_csd[EXT_CSD_CARD_TYPE] & MMC_HS_200MHZ)) + err = mmc_select_hs200(media); + else + err = mmc_select_hs(media); + + return err; +} + +static int sd_switch(MmcCtrlr *ctrlr, int mode, int group, uint8_t value, + uint8_t *resp) +{ + /* Switch the frequency */ + MmcCommand cmd; + cmd.cmdidx = SD_CMD_SWITCH_FUNC; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = (mode << 31) | (0xffffff & ~(0xf << (group * 4))) | + (value << (group * 4)); + cmd.flags = 0; + + MmcData data; + data.dest = (char *)resp; + data.blocksize = 64; + data.blocks = 1; + data.flags = MMC_DATA_READ; + + return mmc_send_cmd(ctrlr, &cmd, &data); +} + +static int sd_change_freq(MmcMedia *media) +{ + int err, timeout; + MmcCommand cmd; + MmcData data; + ALLOC_CACHE_ALIGN_BUFFER(uint32_t, scr, 2); + ALLOC_CACHE_ALIGN_BUFFER(uint32_t, switch_status, 16); + + media->caps = 0; + + /* Read the SCR to find out if this card supports higher speeds */ + cmd.cmdidx = MMC_CMD_APP_CMD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = media->rca << 16; + cmd.flags = 0; + + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + + mmc_debug("%s: before SD_CMD_APP_SEND_SCR\n", __func__); + cmd.cmdidx = SD_CMD_APP_SEND_SCR; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + cmd.flags = 0; + + timeout = 3; + while (timeout--) { + data.dest = (char *)scr; + data.blocksize = 8; + data.blocks = 1; + data.flags = MMC_DATA_READ; + err = mmc_send_cmd(media->ctrlr, &cmd, &data); + if (!err) + break; + } + if (err) { + mmc_error("%s: return err (%d).\n", __func__, err); + return err; + } + mmc_debug("%s: end SD_CMD_APP_SEND_SCR\n", __func__); + + media->scr[0] = betohl(scr[0]); + media->scr[1] = betohl(scr[1]); + + switch ((media->scr[0] >> 24) & 0xf) { + case 0: + media->version = SD_VERSION_1_0; + break; + case 1: + media->version = SD_VERSION_1_10; + break; + case 2: + media->version = SD_VERSION_2; + break; + default: + media->version = SD_VERSION_1_0; + break; + } + + if (media->scr[0] & SD_DATA_4BIT) + media->caps |= MMC_MODE_4BIT; + + /* Version 1.0 doesn't support switching */ + if (media->version == SD_VERSION_1_0) + goto out; + + timeout = 4; + while (timeout--) { + err = sd_switch(media->ctrlr, SD_SWITCH_CHECK, 0, 1, + (uint8_t *)switch_status); + if (err) + return err; + + /* The high-speed function is busy. Try again */ + if (!(ntohl(switch_status[7]) & SD_HIGHSPEED_BUSY)) + break; + } + + /* If high-speed isn't supported, we return */ + if (!(ntohl(switch_status[3]) & SD_HIGHSPEED_SUPPORTED)) + goto out; + + /* + * If the host doesn't support SD_HIGHSPEED, do not switch card to + * HIGHSPEED mode even if the card support SD_HIGHSPPED. + * This can avoid furthur problem when the card runs in different + * mode between the host. + */ + if (!((media->ctrlr->caps & MMC_MODE_HS_52MHz) && + (media->ctrlr->caps & MMC_MODE_HS))) + goto out; + + err = sd_switch(media->ctrlr, SD_SWITCH_SWITCH, 0, 1, + (uint8_t *)switch_status); + if (err) + return err; + + if ((ntohl(switch_status[4]) & 0x0f000000) == 0x01000000) { + media->caps |= MMC_MODE_HS; + mmc_set_timing(media->ctrlr, MMC_TIMING_SD_HS); + } + +out: + mmc_recalculate_clock(media); + return 0; +} + +static uint32_t mmc_calculate_transfer_speed(uint32_t csd0) +{ + uint32_t mult, freq; + + /* frequency bases, divided by 10 to be nice to platforms without + * floating point */ + static const int fbase[] = { + 10000, + 100000, + 1000000, + 10000000, + }; + /* Multiplier values for TRAN_SPEED. Multiplied by 10 to be nice + * to platforms without floating point. */ + static const int multipliers[] = { + 0, // reserved + 10, + 12, + 13, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50, + 55, + 60, + 70, + 80, + }; + + /* divide frequency by 10, since the mults are 10x bigger */ + freq = fbase[csd0 & 0x7]; + mult = multipliers[(csd0 >> 3) & 0xf]; + return freq * mult; +} + +static int mmc_startup(MmcMedia *media) +{ + int err, width; + uint64_t cmult, csize, capacity; + + MmcCommand cmd; + ALLOC_CACHE_ALIGN_BUFFER(unsigned char, ext_csd, EXT_CSD_SIZE); + ALLOC_CACHE_ALIGN_BUFFER(unsigned char, test_csd, EXT_CSD_SIZE); + + /* Put the Card in Identify Mode */ + cmd.cmdidx = MMC_CMD_ALL_SEND_CID; + cmd.resp_type = MMC_RSP_R2; + cmd.cmdarg = 0; + cmd.flags = 0; + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + memcpy(media->cid, cmd.response, sizeof(media->cid)); + + /* + * For MMC cards, set the Relative Address. + * For SD cards, get the Relatvie Address. + * This also puts the cards into Standby State + */ + cmd.cmdidx = SD_CMD_SEND_RELATIVE_ADDR; + cmd.cmdarg = media->rca << 16; + cmd.resp_type = MMC_RSP_R6; + cmd.flags = 0; + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + if (IS_SD(media)) + media->rca = (cmd.response[0] >> 16) & 0xffff; + + /* Get the Card-Specific Data */ + cmd.cmdidx = MMC_CMD_SEND_CSD; + cmd.resp_type = MMC_RSP_R2; + cmd.cmdarg = media->rca << 16; + cmd.flags = 0; + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + + /* Waiting for the ready status */ + mmc_send_status(media, MMC_IO_RETRIES); + if (err) + return err; + + memcpy(media->csd, cmd.response, sizeof(media->csd)); + if (media->version == MMC_VERSION_UNKNOWN) { + int version = extract_uint32_bits(media->csd, 2, 4); + switch (version) { + case 0: + media->version = MMC_VERSION_1_2; + break; + case 1: + media->version = MMC_VERSION_1_4; + break; + case 2: + media->version = MMC_VERSION_2_2; + break; + case 3: + media->version = MMC_VERSION_3; + break; + case 4: + media->version = MMC_VERSION_4; + break; + default: + media->version = MMC_VERSION_1_2; + break; + } + } + media->tran_speed = mmc_calculate_transfer_speed(media->csd[0]); + media->read_bl_len = 1 << extract_uint32_bits(media->csd, 44, 4); + + if (IS_SD(media)) + media->write_bl_len = media->read_bl_len; + else + media->write_bl_len = + 1 << extract_uint32_bits(media->csd, 102, 4); + + if (media->high_capacity) { + cmult = 8; + csize = extract_uint32_bits(media->csd, 58, 22); + + } else { + csize = extract_uint32_bits(media->csd, 54, 12); + cmult = extract_uint32_bits(media->csd, 78, 3); + } + + media->capacity = (csize + 1) << (cmult + 2); + media->capacity *= media->read_bl_len; + + if (media->read_bl_len > 512) + media->read_bl_len = 512; + + if (media->write_bl_len > 512) + media->write_bl_len = 512; + + mmc_debug("mmc media info: version=%#x, tran_speed=%d\n", + media->version, (int)media->tran_speed); + + /* Select the card, and put it into Transfer Mode */ + cmd.cmdidx = MMC_CMD_SELECT_CARD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = media->rca << 16; + cmd.flags = 0; + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + + if (err) + return err; + + if (!IS_SD(media) && (media->version >= MMC_VERSION_4)) { + /* check ext_csd version and capacity */ + err = mmc_send_ext_csd(media->ctrlr, ext_csd); + if (!err & (ext_csd[EXT_CSD_REV] >= 2)) { + /* According to the JEDEC Standard, the value of + * ext_csd's capacity is valid if the value is more + * than 2GB */ + // TODO(hungte) Replace by letohl(). + capacity = (ext_csd[EXT_CSD_SEC_CNT + 0] << 0 | + ext_csd[EXT_CSD_SEC_CNT + 1] << 8 | + ext_csd[EXT_CSD_SEC_CNT + 2] << 16 | + ext_csd[EXT_CSD_SEC_CNT + 3] << 24); + capacity *= 512; + + if ((capacity >> 20) > 2 * 1024) + media->capacity = capacity; + } + } + + if (IS_SD(media)) + err = sd_change_freq(media); + else + err = mmc_change_freq(media); + if (err) + return err; + + /* Restrict card's capabilities by what the host can do */ + media->caps &= media->ctrlr->caps; + + if (IS_SD(media)) { + if (media->caps & MMC_MODE_4BIT) { + cmd.cmdidx = MMC_CMD_APP_CMD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = media->rca << 16; + cmd.flags = 0; + + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + + cmd.cmdidx = SD_CMD_APP_SET_BUS_WIDTH; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 2; + cmd.flags = 0; + err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + + mmc_set_bus_width(media->ctrlr, 4); + } + } else { + for (width = EXT_CSD_BUS_WIDTH_8; width >= 0; width--) { + /* If HS200 is switched, Bus Width has been 8-bit */ + if ((media->caps & MMC_MODE_HS_200MHz) || + (media->caps & MMC_MODE_HS400ES)) + break; + + /* Set the card to use 4 bit*/ + err = mmc_switch(media, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, width); + if (err) + continue; + + if (!width) { + mmc_set_bus_width(media->ctrlr, 1); + break; + } else + mmc_set_bus_width(media->ctrlr, 4 * width); + + err = mmc_send_ext_csd(media->ctrlr, test_csd); + if (!err && + (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] == + test_csd[EXT_CSD_PARTITIONING_SUPPORT]) && + (ext_csd[EXT_CSD_ERASE_GROUP_DEF] == + test_csd[EXT_CSD_ERASE_GROUP_DEF]) && + (ext_csd[EXT_CSD_REV] == + test_csd[EXT_CSD_REV]) && + (ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] == + test_csd[EXT_CSD_HC_ERASE_GRP_SIZE]) && + memcmp(&ext_csd[EXT_CSD_SEC_CNT], + &test_csd[EXT_CSD_SEC_CNT], 4) == 0) { + media->caps |= width; + break; + } + } + } + + media->dev.block_count = media->capacity / media->read_bl_len; + media->dev.block_size = media->read_bl_len; + + printf("Man %06x Snr %u ", + media->cid[0] >> 24, + (((media->cid[2] & 0xffff) << 16) | + ((media->cid[3] >> 16) & 0xffff))); + printf("Product %c%c%c%c", media->cid[0] & 0xff, + (media->cid[1] >> 24), (media->cid[1] >> 16) & 0xff, + (media->cid[1] >> 8) & 0xff); + if (!IS_SD(media)) /* eMMC product string is longer */ + printf("%c%c", media->cid[1] & 0xff, + (media->cid[2] >> 24) & 0xff); + printf(" Revision %d.%d\n", (media->cid[2] >> 20) & 0xf, + (media->cid[2] >> 16) & 0xf); + + /* Check whether to use HC erase group size or not. */ + if (ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x1) + media->erase_size = ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * + 512 * KiB; + else + media->erase_size = (extract_uint32_bits(media->csd, 81, 5) + + 1) * + (extract_uint32_bits(media->csd, 86, 5) + 1); + + media->trim_mult = ext_csd[EXT_CSD_TRIM_MULT]; + + return 0; +} + +static int mmc_send_if_cond(MmcMedia *media) +{ + MmcCommand cmd; + cmd.cmdidx = SD_CMD_SEND_IF_COND; + // Set if host supports voltages between 2.7 and 3.6 V. + cmd.cmdarg = ((media->ctrlr->voltages & 0xff8000) != 0) << 8 | 0xaa; + cmd.resp_type = MMC_RSP_R7; + cmd.flags = 0; + int err = mmc_send_cmd(media->ctrlr, &cmd, NULL); + if (err) + return err; + + if ((cmd.response[0] & 0xff) != 0xaa) + return MMC_UNUSABLE_ERR; + else + media->version = SD_VERSION_2; + return 0; +} + +int mmc_setup_media(MmcCtrlr *ctrlr) +{ + int err; + + MmcMedia *media = xzalloc(sizeof(*media)); + media->ctrlr = ctrlr; + + mmc_set_bus_width(ctrlr, 1); + mmc_set_clock(ctrlr, 1); + + /* Reset the Card */ + err = mmc_go_idle(media); + if (err) { + free(media); + return err; + } + + /* Test for SD version 2 */ + err = mmc_send_if_cond(media); + + /* Get SD card operating condition */ + err = sd_send_op_cond(media); + + /* If the command timed out, we check for an MMC card */ + if (err == MMC_TIMEOUT) { + err = mmc_send_op_cond(media); + + if (err && err != MMC_IN_PROGRESS) { + mmc_error("Card did not respond to voltage select!\n"); + free(media); + return MMC_UNUSABLE_ERR; + } + } + + if (err && err != MMC_IN_PROGRESS) { + free(media); + return err; + } + + if (err == MMC_IN_PROGRESS) + err = mmc_complete_op_cond(media); + + if (!err) { + err = mmc_startup(media); + if (!err) { + ctrlr->media = media; + return 0; + } + } + + free(media); + return err; +} + +///////////////////////////////////////////////////////////////////////////// +// BlockDevice utilities and callbacks + +static inline MmcMedia *mmc_media(BlockDevOps *me) +{ + return container_of(me, MmcMedia, dev.ops); +} + +static inline MmcCtrlr *mmc_ctrlr(MmcMedia *media) +{ + return media->ctrlr; +} + +static int block_mmc_setup(BlockDevOps *me, lba_t start, lba_t count, + int is_read) +{ + MmcMedia *media = mmc_media(me); + MmcCtrlr *ctrlr = mmc_ctrlr(media); + + if (count == 0) + return 0; + + if (start > media->dev.block_count || + start + count > media->dev.block_count) + return 0; + + uint32_t bl_len = is_read ? media->read_bl_len : + media->write_bl_len; + + /* + * CMD16 only applies to single data rate mode, and block + * length for double data rate is always 512 bytes. + */ + if ((ctrlr->timing == MMC_TIMING_UHS_DDR50) || + (ctrlr->timing == MMC_TIMING_MMC_DDR52) || + (ctrlr->timing == MMC_TIMING_MMC_HS400) || + (ctrlr->timing == MMC_TIMING_MMC_HS400ES)) + return 1; + if (mmc_set_blocklen(ctrlr, bl_len)) + return 0; + + return 1; +} + +lba_t block_mmc_read(BlockDevOps *me, lba_t start, lba_t count, void *buffer) +{ + uint8_t *dest = (uint8_t *)buffer; + + if (block_mmc_setup(me, start, count, 1) == 0) + return 0; + + lba_t todo = count; + MmcMedia *media = mmc_media(me); + MmcCtrlr *ctrlr = mmc_ctrlr(media); + do { + lba_t cur = MIN(todo, ctrlr->b_max); + if (mmc_read(media, dest, start, cur) != cur) + return 0; + todo -= cur; + mmc_debug("%s: Got %d blocks, more %d (total %d) to go.\n", + __func__, (int)cur, (int)todo, (int)count); + start += cur; + dest += cur * media->read_bl_len; + } while (todo > 0); + return count; +} + +lba_t block_mmc_write(BlockDevOps *me, lba_t start, lba_t count, + const void *buffer) +{ + const uint8_t *src = (const uint8_t *)buffer; + + if (block_mmc_setup(me, start, count, 0) == 0) + return 0; + + lba_t todo = count; + MmcMedia *media = mmc_media(me); + MmcCtrlr *ctrlr = mmc_ctrlr(media); + do { + lba_t cur = MIN(todo, ctrlr->b_max); + if (mmc_write(media, start, cur, src) != cur) + return 0; + todo -= cur; + start += cur; + src += cur * media->write_bl_len; + } while (todo > 0); + return count; +} + +lba_t block_mmc_erase(BlockDevOps *me, lba_t start, lba_t count) +{ + MmcCommand cmd; + + if (block_mmc_setup(me, start, count, 0) == 0) + return 0; + + MmcMedia *media = mmc_media(me); + MmcCtrlr *ctrlr = mmc_ctrlr(media); + + cmd.cmdidx = MMC_CMD_ERASE_GROUP_START; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = start; + cmd.flags = 0; + + if (mmc_send_cmd(ctrlr, &cmd, NULL)) + return 0; + + cmd.cmdidx = MMC_CMD_ERASE_GROUP_END; + cmd.cmdarg = start + count - 1; + cmd.resp_type = MMC_RSP_R1; + cmd.flags = 0; + + if (mmc_send_cmd(ctrlr, &cmd, NULL)) + return 0; + + cmd.cmdidx = MMC_CMD_ERASE; + cmd.cmdarg = MMC_TRIM_ARG; /* just unmap blocks */ + cmd.resp_type = MMC_RSP_R1; + cmd.flags = 0; + + if (mmc_send_cmd(ctrlr, &cmd, NULL)) + return 0; + + size_t erase_blocks; + /* + * Timeout for TRIM operation on one erase group is defined as: + * TRIM timeout = 300ms x TRIM_MULT + * + * This timeout is expressed in units of 100us to mmc_send_status. + * + * Hence, timeout_per_erase_block = TRIM timeout * 1000us/100us; + */ + size_t timeout_per_erase_block = (media->trim_mult * 300) * 10; + int err = 0; + + erase_blocks = ALIGN_UP(count, media->erase_size) / media->erase_size; + + while (erase_blocks) { + /* + * To avoid overflow of timeout value, loop in calls to + * mmc_send_status for erase_blocks number of times. + */ + err = mmc_send_status(media, timeout_per_erase_block); + + /* Send status successful, erase action complete. */ + if (err == 0) + break; + + erase_blocks--; + } + + /* Total timeout done. Still status not successful. */ + if (err) { + mmc_error("TRIM operation not successful within timeout.\n"); + return 0; + } + + return count; +} + +lba_t block_mmc_fill_write(BlockDevOps *me, lba_t start, lba_t count, + uint32_t fill_pattern) +{ + if (block_mmc_setup(me, start, count, 0) == 0) + return 0; + + MmcMedia *media = mmc_media(me); + MmcCtrlr *ctrlr = mmc_ctrlr(media); + uint64_t block_size = media->dev.block_size; + /* + * We allocate max 4 MiB buffer on heap and set it to fill_pattern and + * perform mmc_write operation using this 4MiB buffer until requested + * size on disk is written by the fill byte. + * + * 4MiB was chosen after repeating several experiments with the max + * buffer size to be used. Using 1 lba i.e. block_size buffer results in + * very large fill_write time. On the other hand, choosing 4MiB, 8MiB or + * even 128 Mib resulted in similar write times. With 2MiB, the + * fill_write time increased by several seconds. So, 4MiB was chosen as + * the default max buffer size. + */ + lba_t heap_lba = (4 * MiB) / block_size; + /* + * Actual allocated buffer size is minimum of three entities: + * 1) 4MiB equivalent in lba + * 2) count: Number of lbas to overwrite + * 3) ctrlr->b_max: Max lbas that the block device allows write + * operation on at a time. + */ + lba_t buffer_lba = MIN(MIN(heap_lba, count), ctrlr->b_max); + + uint64_t buffer_bytes = buffer_lba * block_size; + uint64_t buffer_words = buffer_bytes / sizeof(uint32_t); + uint32_t *buffer = xmalloc(buffer_bytes); + uint32_t *ptr = buffer; + + for ( ; buffer_words ; buffer_words--) + *ptr++ = fill_pattern; + + lba_t todo = count; + int ret = 0; + + do { + lba_t curr_lba = MIN(buffer_lba, todo); + + if (mmc_write(media, start, curr_lba, buffer) != curr_lba) + goto cleanup; + todo -= curr_lba; + start += curr_lba; + } while (todo > 0); + + ret = count; + +cleanup: + free(buffer); + return ret; +} diff --git a/src/drivers/storage/mmc.h b/src/drivers/storage/mmc.h new file mode 100644 index 0000000..a7448e3 --- /dev/null +++ b/src/drivers/storage/mmc.h @@ -0,0 +1,335 @@ +/* + * Copyright 2008,2010 Freescale Semiconductor, Inc + * Andy Fleming + * + * Copyright 2013 Google Inc. All rights reserved. + * + * Based (loosely) on the Linux code + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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. + */ + +#ifndef __DRIVERS_STORAGE_MMC_H__ +#define __DRIVERS_STORAGE_MMC_H__ + +#include "drivers/storage/blockdev.h" +#include "drivers/storage/bouncebuf.h" + +#define SD_VERSION_SD 0x20000 +#define SD_VERSION_2 (SD_VERSION_SD | 0x20) +#define SD_VERSION_1_0 (SD_VERSION_SD | 0x10) +#define SD_VERSION_1_10 (SD_VERSION_SD | 0x1a) +#define MMC_VERSION_MMC 0x10000 +#define MMC_VERSION_UNKNOWN (MMC_VERSION_MMC) +#define MMC_VERSION_1_2 (MMC_VERSION_MMC | 0x12) +#define MMC_VERSION_1_4 (MMC_VERSION_MMC | 0x14) +#define MMC_VERSION_2_2 (MMC_VERSION_MMC | 0x22) +#define MMC_VERSION_3 (MMC_VERSION_MMC | 0x30) +#define MMC_VERSION_4 (MMC_VERSION_MMC | 0x40) + +#define MMC_MODE_HS 0x001 +#define MMC_MODE_HS_52MHz 0x010 +#define MMC_MODE_HS_200MHz 0x020 +#define MMC_MODE_HS400 0x040 +#define MMC_MODE_HS400ES 0x080 +#define MMC_MODE_1V8_VDD 0x100 +#define MMC_MODE_4BIT 0x200 +#define MMC_MODE_8BIT 0x400 +#define MMC_MODE_SPI 0x800 +#define MMC_MODE_HC 0x1000 +#define MMC_AUTO_CMD12 0x2000 + +#define SD_DATA_4BIT 0x00040000 + +#define IS_SD(x) (x->version & SD_VERSION_SD) + +#define MMC_DATA_READ 1 +#define MMC_DATA_WRITE 2 + +#define MMC_SUPPORT_ERR -15 /* No support feature */ +#define MMC_NO_CARD_ERR -16 /* No SD/MMC card inserted */ +#define MMC_UNUSABLE_ERR -17 /* Unusable Card */ +#define MMC_COMM_ERR -18 /* Communications Error */ +#define MMC_TIMEOUT -19 +#define MMC_IN_PROGRESS -20 /* operation is in progress */ +#define MMC_INVALID_ERR -21 /* A catch all case. */ + +#define MMC_CMD_GO_IDLE_STATE 0 +#define MMC_CMD_SEND_OP_COND 1 +#define MMC_CMD_ALL_SEND_CID 2 +#define MMC_CMD_SET_RELATIVE_ADDR 3 +#define MMC_CMD_SET_DSR 4 +#define MMC_CMD_SWITCH 6 +#define MMC_CMD_SELECT_CARD 7 +#define MMC_CMD_SEND_EXT_CSD 8 +#define MMC_CMD_SEND_CSD 9 +#define MMC_CMD_SEND_CID 10 +#define MMC_CMD_STOP_TRANSMISSION 12 +#define MMC_CMD_SEND_STATUS 13 +#define MMC_CMD_SET_BLOCKLEN 16 +#define MMC_CMD_READ_SINGLE_BLOCK 17 +#define MMC_CMD_READ_MULTIPLE_BLOCK 18 +#define MMC_CMD_WRITE_SINGLE_BLOCK 24 +#define MMC_CMD_WRITE_MULTIPLE_BLOCK 25 +#define MMC_CMD_ERASE_GROUP_START 35 +#define MMC_CMD_ERASE_GROUP_END 36 +#define MMC_CMD_ERASE 38 +#define MMC_CMD_APP_CMD 55 +#define MMC_CMD_SPI_READ_OCR 58 +#define MMC_CMD_SPI_CRC_ON_OFF 59 + +#define MMC_TRIM_ARG 0x1 +#define MMC_SECURE_ERASE_ARG 0x80000000 + +#define SD_CMD_SEND_RELATIVE_ADDR 3 +#define SD_CMD_SWITCH_FUNC 6 +#define SD_CMD_SEND_IF_COND 8 + +#define SD_CMD_APP_SET_BUS_WIDTH 6 +#define SD_CMD_ERASE_WR_BLK_START 32 +#define SD_CMD_ERASE_WR_BLK_END 33 +#define SD_CMD_APP_SEND_OP_COND 41 +#define SD_CMD_APP_SEND_SCR 51 + +/* SCR definitions in different words */ +#define SD_HIGHSPEED_BUSY 0x00020000 +#define SD_HIGHSPEED_SUPPORTED 0x00020000 + +#define MMC_HS_TIMING 0x00000100 +#define MMC_HS_52MHZ 0x2 +#define MMC_HS_200MHZ 0x10 +#define MMC_HS400 0x40 + +#define OCR_BUSY 0x80000000 +#define OCR_HCS 0x40000000 +#define OCR_VOLTAGE_MASK 0x007FFF80 +#define OCR_ACCESS_MODE 0x60000000 + +#define SECURE_ERASE 0x80000000 + +#define MMC_STATUS_MASK (~0x0206BF7F) +#define MMC_STATUS_RDY_FOR_DATA (1 << 8) +#define MMC_STATUS_CURR_STATE (0xf << 9) +#define MMC_STATUS_ERROR (1 << 19) + +#define MMC_VDD_165_195 0x00000080 /* VDD voltage 1.65 - 1.95 */ +#define MMC_VDD_20_21 0x00000100 /* VDD voltage 2.0 ~ 2.1 */ +#define MMC_VDD_21_22 0x00000200 /* VDD voltage 2.1 ~ 2.2 */ +#define MMC_VDD_22_23 0x00000400 /* VDD voltage 2.2 ~ 2.3 */ +#define MMC_VDD_23_24 0x00000800 /* VDD voltage 2.3 ~ 2.4 */ +#define MMC_VDD_24_25 0x00001000 /* VDD voltage 2.4 ~ 2.5 */ +#define MMC_VDD_25_26 0x00002000 /* VDD voltage 2.5 ~ 2.6 */ +#define MMC_VDD_26_27 0x00004000 /* VDD voltage 2.6 ~ 2.7 */ +#define MMC_VDD_27_28 0x00008000 /* VDD voltage 2.7 ~ 2.8 */ +#define MMC_VDD_28_29 0x00010000 /* VDD voltage 2.8 ~ 2.9 */ +#define MMC_VDD_29_30 0x00020000 /* VDD voltage 2.9 ~ 3.0 */ +#define MMC_VDD_30_31 0x00040000 /* VDD voltage 3.0 ~ 3.1 */ +#define MMC_VDD_31_32 0x00080000 /* VDD voltage 3.1 ~ 3.2 */ +#define MMC_VDD_32_33 0x00100000 /* VDD voltage 3.2 ~ 3.3 */ +#define MMC_VDD_33_34 0x00200000 /* VDD voltage 3.3 ~ 3.4 */ +#define MMC_VDD_34_35 0x00400000 /* VDD voltage 3.4 ~ 3.5 */ +#define MMC_VDD_35_36 0x00800000 /* VDD voltage 3.5 ~ 3.6 */ + +#define MMC_VDD_165_195_SHIFT 7 + +#define MMC_SWITCH_MODE_CMD_SET 0x00 /* Change the command set */ +#define MMC_SWITCH_MODE_SET_BITS 0x01 /* Set bits in EXT_CSD byte + addressed by index which are + 1 in value field */ +#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits in EXT_CSD byte + addressed by index, which are + 1 in value field */ +#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target byte to value */ + +#define SD_SWITCH_CHECK 0 +#define SD_SWITCH_SWITCH 1 + +/* + * EXT_CSD fields + */ +#define EXT_CSD_PARTITIONING_SUPPORT 160 /* RO */ +#define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ +#define EXT_CSD_PART_CONF 179 /* R/W */ +#define EXT_CSD_BUS_WIDTH 183 /* R/W */ +#define EXT_CSD_STROBE_SUPPORT 184 /* RO */ +#define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_REV 192 /* RO */ +#define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ +#define EXT_CSD_HC_ERASE_GRP_SIZE 224 /* RO */ +#define EXT_CSD_TRIM_MULT 232 /* RO */ + +/* + * EXT_CSD field definitions + */ + +#define EXT_CSD_CMD_SET_NORMAL (1 << 0) +#define EXT_CSD_CMD_SET_SECURE (1 << 1) +#define EXT_CSD_CMD_SET_CPSECURE (1 << 2) + +#define EXT_CSD_CARD_TYPE_26 (1 << 0) /* Card can run at 26MHz */ +#define EXT_CSD_CARD_TYPE_52 (1 << 1) /* Card can run at 52MHz */ + +#define EXT_CSD_BUS_WIDTH_1 0 /* Card is in 1 bit mode */ +#define EXT_CSD_BUS_WIDTH_4 1 /* Card is in 4 bit mode */ +#define EXT_CSD_BUS_WIDTH_8 2 /* Card is in 8 bit mode */ +#define EXT_CSD_DDR_BUS_WIDTH_4 5 /* Card is in 4 bit DDR mode */ +#define EXT_CSD_DDR_BUS_WIDTH_8 6 /* Card is in 8 bit DDR mode */ +#define EXT_CSD_BUS_WIDTH_STROBE (1<<7) /* Enhanced strobe mode */ + +#define EXT_CSD_TIMING_BC 0 /* Backwards compatility */ +#define EXT_CSD_TIMING_HS 1 /* High speed */ +#define EXT_CSD_TIMING_HS200 2 /* HS200 */ +#define EXT_CSD_TIMING_HS400 3 /* HS400 */ + +#define R1_ILLEGAL_COMMAND (1 << 22) +#define R1_APP_CMD (1 << 5) + +#define MMC_RSP_PRESENT (1 << 0) +#define MMC_RSP_136 (1 << 1) /* 136 bit response */ +#define MMC_RSP_CRC (1 << 2) /* expect valid crc */ +#define MMC_RSP_BUSY (1 << 3) /* card may send busy */ +#define MMC_RSP_OPCODE (1 << 4) /* response contains opcode */ + +#define MMC_RSP_NONE (0) +#define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) +#define MMC_RSP_R1b (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE| \ + MMC_RSP_BUSY) +#define MMC_RSP_R2 (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC) +#define MMC_RSP_R3 (MMC_RSP_PRESENT) +#define MMC_RSP_R4 (MMC_RSP_PRESENT) +#define MMC_RSP_R5 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) +#define MMC_RSP_R6 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) +#define MMC_RSP_R7 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) + +#define MMC_INIT_TIMEOUT_US (1000 * 1000) +#define MMC_IO_RETRIES (1000) +#define MMC_CLOCK_20MHZ (20000000) +#define MMC_CLOCK_25MHZ (25000000) +#define MMC_CLOCK_26MHZ (26000000) +#define MMC_CLOCK_50MHZ (50000000) +#define MMC_CLOCK_52MHZ (52000000) +#define MMC_CLOCK_200MHZ (200000000) +#define MMC_CLOCK_DEFAULT_MHZ (MMC_CLOCK_20MHZ) + +#define EXT_CSD_SIZE (512) + +typedef struct MmcCommand { + uint16_t cmdidx; + uint32_t resp_type; + uint32_t cmdarg; + uint32_t response[4]; + uint32_t flags; +} MmcCommand; + +typedef struct MmcData { + union { + char *dest; + const char *src; + }; + uint32_t flags; + uint32_t blocks; + uint32_t blocksize; +} MmcData; + +struct MmcMedia; +typedef struct MmcMedia MmcMedia; + +typedef struct MmcCtrlr { + BlockDevCtrlr ctrlr; + + MmcMedia *media; + + uint32_t voltages; + uint32_t f_min; + uint32_t f_max; + uint32_t bus_width; + uint32_t bus_hz; + uint32_t caps; + uint32_t b_max; + uint32_t timing; + +#define MMC_TIMING_LEGACY 0 +#define MMC_TIMING_MMC_HS 1 +#define MMC_TIMING_SD_HS 2 +#define MMC_TIMING_UHS_SDR12 3 +#define MMC_TIMING_UHS_SDR25 4 +#define MMC_TIMING_UHS_SDR50 5 +#define MMC_TIMING_UHS_SDR104 6 +#define MMC_TIMING_UHS_DDR50 7 +#define MMC_TIMING_MMC_DDR52 8 +#define MMC_TIMING_MMC_HS200 9 +#define MMC_TIMING_MMC_HS400 10 +#define MMC_TIMING_MMC_HS400ES 11 + + /* + * Some eMMC devices do not support iterative OCR setting, they need + * to be programmed with the required/expected value. This field is + * used to set the value for those devices. + */ + uint32_t hardcoded_voltage; + + int (*send_cmd)(struct MmcCtrlr *me, MmcCommand *cmd, MmcData *data); + void (*set_ios)(struct MmcCtrlr *me); +} MmcCtrlr; + +typedef struct MmcMedia { + BlockDev dev; + + MmcCtrlr *ctrlr; + + uint32_t caps; + uint32_t version; + uint32_t read_bl_len; + uint32_t write_bl_len; + uint64_t capacity; + int high_capacity; + uint32_t tran_speed; + /* Erase size in terms of block length. */ + uint32_t erase_size; + /* Trim operation multiplier for determining timeout. */ + uint32_t trim_mult; + + uint32_t ocr; + uint16_t rca; + uint32_t scr[2]; + uint32_t csd[4]; + uint32_t cid[4]; + + uint32_t op_cond_response; // The response byte from the last op_cond +} MmcMedia; + +int mmc_busy_wait_io(volatile uint32_t *address, uint32_t *output, + uint32_t io_mask, uint32_t timeout_ms); +int mmc_busy_wait_io_until(volatile uint32_t *address, uint32_t *output, + uint32_t io_mask, uint32_t timeout_ms); + +int mmc_setup_media(MmcCtrlr *ctrlr); + +lba_t block_mmc_read(BlockDevOps *me, lba_t start, lba_t count, void *buffer); +lba_t block_mmc_write(BlockDevOps *me, lba_t start, lba_t count, + const void *buffer); +lba_t block_mmc_erase(BlockDevOps *me, lba_t start, lba_t count); +lba_t block_mmc_fill_write(BlockDevOps *me, lba_t start, lba_t count, + uint32_t fill_pattern); + +// Debug functions. +extern int __mmc_debug, __mmc_trace; +#define mmc_debug(format...) \ + while (__mmc_debug) { printf("mmc: " format); break; } +#define mmc_trace(format...) \ + while (__mmc_trace) { printf(format); break; } +#define mmc_error(format...) printf("mmc: ERROR: " format) + +#endif /* __DRIVERS_STORAGE_MMC_H__ */ diff --git a/src/drivers/storage/sdhci.c b/src/drivers/storage/sdhci.c new file mode 100644 index 0000000..d2a0991 --- /dev/null +++ b/src/drivers/storage/sdhci.c @@ -0,0 +1,833 @@ +/* + * Copyright 2011, Marvell Semiconductor Inc. + * Lei Wen leiwen@marvell.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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. + * + * Back ported to the 8xx platform (from the 8260 platform) by + * Murray.Jensen@cmst.csiro.au, 27-Jan-01. + */ + +#include <libpayload.h> + +#include "drivers/storage/blockdev.h" +#include "drivers/storage/mmc.h" +#include "drivers/storage/sdhci.h" + +static void sdhci_reset(SdhciHost *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("Reset 0x%x never completed.\n", (int)mask); + return; + } + timeout--; + udelay(1000); + } +} + +static void sdhci_cmd_done(SdhciHost *host, MmcCommand *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(SdhciHost *host, MmcData *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(SdhciHost *host, MmcData *data, + unsigned int start_addr) +{ + unsigned int stat, rdy, mask, timeout, block = 0; + + 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("Error detected in status(0x%X)!\n", 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; + } + if (timeout-- > 0) + udelay(10); + else { + printf("Transfer data timeout\n"); + return -1; + } + } while (!(stat & SDHCI_INT_DATA_END)); + return 0; +} + +static void sdhci_alloc_adma_descs(SdhciHost *host, u32 need_descriptors) +{ + if (host->adma_descs) { + if (host->adma_desc_count < need_descriptors) { + /* Previously allocated array is too small */ + free(host->adma_descs); + host->adma_desc_count = 0; + host->adma_descs = NULL; + } + } + + /* use dma_malloc() to make sure we get the coherent/uncached memory */ + if (!host->adma_descs) { + host->adma_descs = dma_malloc(need_descriptors * + sizeof(*host->adma_descs)); + if (host->adma_descs == NULL) + die("fail to malloc adma_descs\n"); + host->adma_desc_count = need_descriptors; + } + + memset(host->adma_descs, 0, sizeof(*host->adma_descs) * + need_descriptors); +} + +static void sdhci_alloc_adma64_descs(SdhciHost *host, u32 need_descriptors) +{ + if (host->adma64_descs) { + if (host->adma_desc_count < need_descriptors) { + /* Previously allocated array is too small */ + free(host->adma64_descs); + host->adma_desc_count = 0; + host->adma64_descs = NULL; + } + } + + /* use dma_malloc() to make sure we get the coherent/uncached memory */ + if (!host->adma64_descs) { + host->adma64_descs = dma_malloc(need_descriptors * + sizeof(*host->adma64_descs)); + if (host->adma64_descs == NULL) + die("fail to malloc adma64_descs\n"); + + host->adma_desc_count = need_descriptors; + } + + memset(host->adma64_descs, 0, sizeof(*host->adma64_descs) * + need_descriptors); +} + +static int sdhci_setup_adma(SdhciHost *host, MmcData *data, + struct bounce_buffer *bbstate) +{ + int i, togo, need_descriptors; + char *buffer_data; + u16 attributes; + + togo = data->blocks * data->blocksize; + if (!togo) { + printf("%s: MmcData corrupted: %d blocks of %d bytes\n", + __func__, data->blocks, data->blocksize); + return -1; + } + + need_descriptors = 1 + togo / SDHCI_MAX_PER_DESCRIPTOR; + + if (host->dma64) + sdhci_alloc_adma64_descs(host, need_descriptors); + else + sdhci_alloc_adma_descs(host, need_descriptors); + + if (bbstate) + buffer_data = (char *)bbstate->bounce_buffer; + else + buffer_data = data->dest; + + /* Now set up the descriptor chain. */ + for (i = 0; togo; i++) { + unsigned desc_length; + + if (togo < SDHCI_MAX_PER_DESCRIPTOR) + desc_length = togo; + else + desc_length = SDHCI_MAX_PER_DESCRIPTOR; + togo -= desc_length; + + attributes = SDHCI_ADMA_VALID | SDHCI_ACT_TRAN; + if (togo == 0) + attributes |= SDHCI_ADMA_END; + + if (host->dma64) { + host->adma64_descs[i].addr = (uintptr_t) buffer_data; + host->adma64_descs[i].addr_hi = 0; + host->adma64_descs[i].length = desc_length; + host->adma64_descs[i].attributes = attributes; + + } else { + host->adma_descs[i].addr = (uintptr_t) buffer_data; + host->adma_descs[i].length = desc_length; + host->adma_descs[i].attributes = attributes; + } + + buffer_data += desc_length; + } + + if (host->dma64) + sdhci_writel(host, (uintptr_t) host->adma64_descs, + SDHCI_ADMA_ADDRESS); + else + sdhci_writel(host, (uintptr_t) host->adma_descs, + SDHCI_ADMA_ADDRESS); + + return 0; +} + +static int sdhci_complete_adma(SdhciHost *host, MmcCommand *cmd) +{ + int retry; + u32 stat = 0, mask; + + mask = SDHCI_INT_RESPONSE | SDHCI_INT_ERROR; + + retry = 10000; /* Command should be done in way less than 10 ms. */ + while (--retry) { + stat = sdhci_readl(host, SDHCI_INT_STATUS); + if (stat & mask) + break; + udelay(1); + } + + sdhci_writel(host, SDHCI_INT_RESPONSE, SDHCI_INT_STATUS); + + if (retry && !(stat & SDHCI_INT_ERROR)) { + /* Command OK, let's wait for data transfer completion. */ + mask = SDHCI_INT_DATA_END | + SDHCI_INT_ERROR | SDHCI_INT_ADMA_ERROR; + + /* Transfer should take 10 seconds tops. */ + retry = 10 * 1000 * 1000; + while (--retry) { + stat = sdhci_readl(host, SDHCI_INT_STATUS); + if (stat & mask) + break; + udelay(1); + } + + sdhci_writel(host, stat, SDHCI_INT_STATUS); + if (retry && !(stat & SDHCI_INT_ERROR)) { + sdhci_cmd_done(host, cmd); + return 0; + } + } + + printf("%s: transfer error, stat %#x, adma error %#x, retry %d\n", + __func__, stat, sdhci_readl(host, SDHCI_ADMA_ERROR), retry); + + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + + if (stat & SDHCI_INT_TIMEOUT) + return MMC_TIMEOUT; + else + return MMC_COMM_ERR; +} + +static int sdhci_send_command_bounced(MmcCtrlr *mmc_ctrl, MmcCommand *cmd, + MmcData *data, + struct bounce_buffer *bbstate) +{ + unsigned int stat = 0; + int ret = 0; + u32 mask, flags; + unsigned int timeout, start_addr = 0; + uint64_t start; + SdhciHost *host = container_of(mmc_ctrl, SdhciHost, mmc_ctrlr); + + /* Wait max 1 s */ + timeout = 1000; + + 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 (timeout == 0) { + printf("Controller never released inhibit bit(s), " + "present state %#8.8x.\n", + sdhci_readl(host, SDHCI_PRESENT_STATE)); + return MMC_COMM_ERR; + } + timeout--; + 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) { + u16 mode = 0; + + sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, + data->blocksize), + SDHCI_BLOCK_SIZE); + + if (data->flags == MMC_DATA_READ) + mode |= SDHCI_TRNS_READ; + + if (data->blocks > 1) + mode |= SDHCI_TRNS_BLK_CNT_EN | + SDHCI_TRNS_MULTI | SDHCI_TRNS_ACMD12; + + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); + + if (host->host_caps & MMC_AUTO_CMD12) { + if (sdhci_setup_adma(host, data, bbstate)) + return -1; + + mode |= SDHCI_TRNS_DMA; + } + sdhci_writew(host, mode, SDHCI_TRANSFER_MODE); + } + + sdhci_writel(host, cmd->cmdarg, SDHCI_ARGUMENT); + sdhci_writew(host, SDHCI_MAKE_CMD(cmd->cmdidx, flags), SDHCI_COMMAND); + + if (data && (host->host_caps & MMC_AUTO_CMD12)) + return sdhci_complete_adma(host, cmd); + + start = timer_us(0); + do { + stat = sdhci_readl(host, SDHCI_INT_STATUS); + if (stat & SDHCI_INT_ERROR) + break; + + /* Apply max timeout for R1b-type CMD defined in eMMC ext_csd + except for erase ones */ + if (timer_us(start) > 2550000) { + if (host->quirks & SDHCI_QUIRK_BROKEN_R1B) + return 0; + else { + printf("Timeout for status update!\n"); + return MMC_TIMEOUT; + } + } + } while ((stat & mask) != mask); + + 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) + return 0; + + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + if (stat & SDHCI_INT_TIMEOUT) + return MMC_TIMEOUT; + else + return MMC_COMM_ERR; +} + +static int sdhci_send_command(MmcCtrlr *mmc_ctrl, MmcCommand *cmd, + MmcData *data) +{ + void *buf; + unsigned int bbflags; + size_t len; + struct bounce_buffer *bbstate = NULL; + struct bounce_buffer bbstate_val; + int ret; + + if (data) { + if (data->flags & MMC_DATA_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)) { + printf("ERROR: Failed to get bounce buffer.\n"); + return -1; + } + } + } + + ret = sdhci_send_command_bounced(mmc_ctrl, cmd, data, bbstate); + + if (bbstate) + bounce_buffer_stop(bbstate); + + return ret; +} + +static int sdhci_set_clock(SdhciHost *host, unsigned int clock) +{ + unsigned int div, clk, timeout; + + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + + if (clock == 0) + return 0; + + if ((host->version & SDHCI_SPEC_VER_MASK) >= SDHCI_SPEC_300) { + /* Version 3.00 divisors must be a multiple of 2. */ + if (host->clock_base <= clock) + div = 1; + else { + for (div = 2; div < SDHCI_MAX_DIV_SPEC_300; div += 2) { + if ((host->clock_base / 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 ((host->clock_base / div) <= clock) + break; + } + } + div >>= 1; + + 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("Internal clock never stabilised.\n"); + return -1; + } + timeout--; + udelay(1000); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + host->clock = host->mmc_ctrlr.bus_hz; + + 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(SdhciHost *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_uhs_signaling(SdhciHost *host, uint32_t timing) +{ + u16 ctrl_2; + + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); + /* Select Bus Speed Mode for host */ + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + + if ((timing != MMC_TIMING_LEGACY) && + (timing != MMC_TIMING_MMC_HS) && + (timing != MMC_TIMING_SD_HS)) + ctrl_2 |= SDHCI_CTRL_VDD_180; + + if ((timing == MMC_TIMING_MMC_HS200) || + (timing == MMC_TIMING_UHS_SDR104)) + ctrl_2 |= SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_DRV_TYPE_A; + else if (timing == MMC_TIMING_UHS_SDR12) + ctrl_2 |= SDHCI_CTRL_UHS_SDR12; + else if (timing == MMC_TIMING_UHS_SDR25) + ctrl_2 |= SDHCI_CTRL_UHS_SDR25; + else if (timing == MMC_TIMING_UHS_SDR50) + ctrl_2 |= SDHCI_CTRL_UHS_SDR50; + else if ((timing == MMC_TIMING_UHS_DDR50) || + (timing == MMC_TIMING_MMC_DDR52)) + ctrl_2 |= SDHCI_CTRL_UHS_DDR50; + else if (timing == MMC_TIMING_MMC_HS400 || + timing == MMC_TIMING_MMC_HS400ES) + ctrl_2 |= SDHCI_CTRL_HS400 | SDHCI_CTRL_DRV_TYPE_A; + + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); +} + +void sdhci_set_ios(MmcCtrlr *mmc_ctrlr) +{ + u32 ctrl; + SdhciHost *host = container_of(mmc_ctrlr, + SdhciHost, mmc_ctrlr); + + if (host->set_control_reg) + host->set_control_reg(host); + + if (mmc_ctrlr->bus_hz != host->clock) + sdhci_set_clock(host, mmc_ctrlr->bus_hz); + + /* Switch to 1.8 volt for HS200 */ + if (mmc_ctrlr->caps & MMC_MODE_1V8_VDD) + if (mmc_ctrlr->bus_hz == MMC_CLOCK_200MHZ) + sdhci_set_power(host, MMC_VDD_165_195_SHIFT); + + /* Set bus width */ + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + if (mmc_ctrlr->bus_width == 8) { + ctrl &= ~SDHCI_CTRL_4BITBUS; + if ((host->version & SDHCI_SPEC_VER_MASK) >= SDHCI_SPEC_300) + ctrl |= SDHCI_CTRL_8BITBUS; + } else { + if ((host->version & SDHCI_SPEC_VER_MASK) >= SDHCI_SPEC_300) + ctrl &= ~SDHCI_CTRL_8BITBUS; + if (mmc_ctrlr->bus_width == 4) + ctrl |= SDHCI_CTRL_4BITBUS; + else + ctrl &= ~SDHCI_CTRL_4BITBUS; + } + + if (!(mmc_ctrlr->timing == MMC_TIMING_LEGACY) && + !(host->quirks & SDHCI_QUIRK_NO_HISPD_BIT)) + ctrl |= SDHCI_CTRL_HISPD; + else + ctrl &= ~SDHCI_CTRL_HISPD; + + sdhci_set_uhs_signaling(host, mmc_ctrlr->timing); + + if (host->host_caps & MMC_AUTO_CMD12) { + ctrl &= ~SDHCI_CTRL_DMA_MASK; + if (host->dma64) + ctrl |= SDHCI_CTRL_ADMA64; + else + ctrl |= SDHCI_CTRL_ADMA32; + } + + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); +} + +/* Prepare SDHCI controller to be initialized */ +static int sdhci_pre_init(SdhciHost *host) +{ + unsigned int caps, caps_1; + + if (host->attach) { + int rv = host->attach(host); + if (rv) + return rv; + } + + host->version = sdhci_readw(host, SDHCI_HOST_VERSION) & 0xff; + + caps = sdhci_readl(host, SDHCI_CAPABILITIES); + caps_1 = sdhci_readl(host, SDHCI_CAPABILITIES_1); + + if ((caps_1 & SDHCI_SUPPORT_HS400) && + (host->quirks & SDHCI_QUIRK_SUPPORTS_HS400ES)) + host->host_caps |= MMC_MODE_HS400ES; + + if (caps & SDHCI_CAN_DO_ADMA2) + host->host_caps |= MMC_AUTO_CMD12; + + /* get base clock frequency from CAP register */ + if (!(host->quirks & SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN)) { + if ((host->version & SDHCI_SPEC_VER_MASK) >= SDHCI_SPEC_300) + host->clock_base = (caps & SDHCI_CLOCK_V3_BASE_MASK) + >> SDHCI_CLOCK_BASE_SHIFT; + else + host->clock_base = (caps & SDHCI_CLOCK_BASE_MASK) + >> SDHCI_CLOCK_BASE_SHIFT; + } + + if (host->clock_base == 0) { + printf("Hardware doesn't specify base clock frequency\n"); + return -1; + } + + host->clock_base *= 1000000; + + if (host->clock_f_max) + host->mmc_ctrlr.f_max = host->clock_f_max; + else + host->mmc_ctrlr.f_max = host->clock_base; + + if (host->clock_f_min) { + host->mmc_ctrlr.f_min = host->clock_f_min; + } else { + if ((host->version & SDHCI_SPEC_VER_MASK) >= SDHCI_SPEC_300) + host->mmc_ctrlr.f_min = + host->clock_base / SDHCI_MAX_DIV_SPEC_300; + else + host->mmc_ctrlr.f_min = + host->clock_base / SDHCI_MAX_DIV_SPEC_200; + } + + if (caps & SDHCI_CAN_VDD_330) + host->mmc_ctrlr.voltages |= MMC_VDD_32_33 | MMC_VDD_33_34; + if (caps & SDHCI_CAN_VDD_300) + host->mmc_ctrlr.voltages |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (caps & SDHCI_CAN_VDD_180) + host->mmc_ctrlr.voltages |= MMC_VDD_165_195; + + if (host->quirks & SDHCI_QUIRK_BROKEN_VOLTAGE) + host->mmc_ctrlr.voltages |= host->voltages; + + if (host->quirks & SDHCI_QUIRK_NO_EMMC_HS200) + host->mmc_ctrlr.caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | + MMC_MODE_4BIT | MMC_MODE_HC; + else + host->mmc_ctrlr.caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | + MMC_MODE_4BIT | MMC_MODE_HC | MMC_MODE_HS_200MHz; + + if (host->quirks & SDHCI_QUIRK_EMMC_1V8_POWER) + host->mmc_ctrlr.caps |= MMC_MODE_1V8_VDD; + + if (caps & SDHCI_CAN_DO_8BIT) + host->mmc_ctrlr.caps |= MMC_MODE_8BIT; + if (host->host_caps) + host->mmc_ctrlr.caps |= host->host_caps; + if (caps & SDHCI_CAN_64BIT) + host->dma64 = 1; + + sdhci_reset(host, SDHCI_RESET_ALL); + + return 0; +} + +static int sdhci_init(SdhciHost *host) +{ + int rv = sdhci_pre_init(host); + + if (rv) + return rv; /* The error has been already reported */ + + sdhci_set_power(host, fls(host->mmc_ctrlr.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); + + /* Set timeout to maximum, shouldn't happen if everything's right. */ + sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL); + + udelay(10000); + return 0; +} + +static int sdhci_update(BlockDevCtrlrOps *me) +{ + SdhciHost *host = container_of + (me, SdhciHost, mmc_ctrlr.ctrlr.ops); + + if (host->removable) { + int present = (sdhci_readl(host, SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT) != 0; + + if (!present) { + if (host->mmc_ctrlr.media) { + /* + * A card was present but isn't any more. Get + * rid of it. + */ + list_remove + (&host->mmc_ctrlr.media->dev.list_node); + free(host->mmc_ctrlr.media); + host->mmc_ctrlr.media = NULL; + } + return 0; + } + + if (!host->mmc_ctrlr.media) { + /* + * A card is present and not set up yet. Get it ready. + */ + if (sdhci_init(host)) + return -1; + + if (mmc_setup_media(&host->mmc_ctrlr)) + return -1; + host->mmc_ctrlr.media->dev.name = "SDHCI card"; + list_insert_after + (&host-> mmc_ctrlr.media->dev.list_node, + &removable_block_devices); + } + } else { + if (!host->initialized && sdhci_init(host)) + return -1; + + host->initialized = 1; + + if (mmc_setup_media(&host->mmc_ctrlr)) + return -1; + host->mmc_ctrlr.media->dev.name = "SDHCI fixed"; + list_insert_after(&host->mmc_ctrlr.media->dev.list_node, + &fixed_block_devices); + host->mmc_ctrlr.ctrlr.need_update = 0; + } + + host->mmc_ctrlr.media->dev.removable = host->removable; + host->mmc_ctrlr.media->dev.ops.read = block_mmc_read; + host->mmc_ctrlr.media->dev.ops.write = block_mmc_write; + host->mmc_ctrlr.media->dev.ops.new_stream = new_simple_stream; + + return 0; +} + +void add_sdhci(SdhciHost *host) +{ + host->mmc_ctrlr.send_cmd = &sdhci_send_command; + host->mmc_ctrlr.set_ios = &sdhci_set_ios; + + host->mmc_ctrlr.ctrlr.ops.update = &sdhci_update; + host->mmc_ctrlr.ctrlr.need_update = 1; + + /* TODO(vbendeb): check if SDHCI spec allows to retrieve this value. */ + host->mmc_ctrlr.b_max = 65535; +} diff --git a/src/drivers/storage/sdhci.h b/src/drivers/storage/sdhci.h new file mode 100644 index 0000000..3ba74a0 --- /dev/null +++ b/src/drivers/storage/sdhci.h @@ -0,0 +1,364 @@ +/* + * Copyright 2011, Marvell Semiconductor Inc. + * Lei Wen leiwen@marvell.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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. + * + * Back ported to the 8xx platform (from the 8260 platform) by + * Murray.Jensen@cmst.csiro.au, 27-Jan-01. + */ +#ifndef __DRIVER_STORAGE_SDHCI_H__ +#define __DRIVER_STORAGE_SDHCI_H__ + +#include <libpayload.h> + +#include "mmc.h" + +/* + * Controller registers + */ + +#define SDHCI_DMA_ADDRESS 0x00 + +#define SDHCI_BLOCK_SIZE 0x04 +#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF)) + +#define SDHCI_BLOCK_COUNT 0x06 + +#define SDHCI_ARGUMENT 0x08 + +#define SDHCI_TRANSFER_MODE 0x0C +#define SDHCI_TRNS_DMA 0x01 +#define SDHCI_TRNS_BLK_CNT_EN 0x02 +#define SDHCI_TRNS_ACMD12 0x04 +#define SDHCI_TRNS_READ 0x10 +#define SDHCI_TRNS_MULTI 0x20 + +#define SDHCI_COMMAND 0x0E +#define SDHCI_CMD_RESP_MASK 0x03 +#define SDHCI_CMD_CRC 0x08 +#define SDHCI_CMD_INDEX 0x10 +#define SDHCI_CMD_DATA 0x20 +#define SDHCI_CMD_ABORTCMD 0xC0 + +#define SDHCI_CMD_RESP_NONE 0x00 +#define SDHCI_CMD_RESP_LONG 0x01 +#define SDHCI_CMD_RESP_SHORT 0x02 +#define SDHCI_CMD_RESP_SHORT_BUSY 0x03 + +#define SDHCI_MAKE_CMD(c, f) (((c & 0xff) << 8) | (f & 0xff)) +#define SDHCI_GET_CMD(c) ((c>>8) & 0x3f) + +#define SDHCI_RESPONSE 0x10 + +#define SDHCI_BUFFER 0x20 + +#define SDHCI_PRESENT_STATE 0x24 +#define SDHCI_CMD_INHIBIT 0x00000001 +#define SDHCI_DATA_INHIBIT 0x00000002 +#define SDHCI_DOING_WRITE 0x00000100 +#define SDHCI_DOING_READ 0x00000200 +#define SDHCI_SPACE_AVAILABLE 0x00000400 +#define SDHCI_DATA_AVAILABLE 0x00000800 +#define SDHCI_CARD_PRESENT 0x00010000 +#define SDHCI_CARD_STATE_STABLE 0x00020000 +#define SDHCI_CARD_DETECT_PIN_LEVEL 0x00040000 +#define SDHCI_WRITE_PROTECT 0x00080000 + +#define SDHCI_HOST_CONTROL 0x28 +#define SDHCI_CTRL_LED 0x01 +#define SDHCI_CTRL_4BITBUS 0x02 +#define SDHCI_CTRL_HISPD 0x04 +#define SDHCI_CTRL_DMA_MASK 0x18 +#define SDHCI_CTRL_SDMA 0x00 +#define SDHCI_CTRL_ADMA1 0x08 +#define SDHCI_CTRL_ADMA32 0x10 +#define SDHCI_CTRL_ADMA64 0x18 +#define SDHCI_CTRL_8BITBUS 0x20 +#define SDHCI_CTRL_CD_TEST_INS 0x40 +#define SDHCI_CTRL_CD_TEST 0x80 + +#define SDHCI_POWER_CONTROL 0x29 +#define SDHCI_POWER_ON 0x01 +#define SDHCI_POWER_180 0x0A +#define SDHCI_POWER_300 0x0C +#define SDHCI_POWER_330 0x0E + +#define SDHCI_BLOCK_GAP_CONTROL 0x2A + +#define SDHCI_WAKE_UP_CONTROL 0x2B +#define SDHCI_WAKE_ON_INT 0x01 +#define SDHCI_WAKE_ON_INSERT 0x02 +#define SDHCI_WAKE_ON_REMOVE 0x04 + +#define SDHCI_CLOCK_CONTROL 0x2C +#define SDHCI_DIVIDER_SHIFT 8 +#define SDHCI_DIVIDER_HI_SHIFT 6 +#define SDHCI_DIV_MASK 0xFF +#define SDHCI_DIV_MASK_LEN 8 +#define SDHCI_DIV_HI_MASK 0x300 +#define SDHCI_CLOCK_CARD_EN 0x0004 +#define SDHCI_CLOCK_INT_STABLE 0x0002 +#define SDHCI_CLOCK_INT_EN 0x0001 + +#define SDHCI_TIMEOUT_CONTROL 0x2E + +#define SDHCI_SOFTWARE_RESET 0x2F +#define SDHCI_RESET_ALL 0x01 +#define SDHCI_RESET_CMD 0x02 +#define SDHCI_RESET_DATA 0x04 + +#define SDHCI_INT_STATUS 0x30 +#define SDHCI_INT_ENABLE 0x34 +#define SDHCI_SIGNAL_ENABLE 0x38 +#define SDHCI_INT_RESPONSE 0x00000001 +#define SDHCI_INT_DATA_END 0x00000002 +#define SDHCI_INT_DMA_END 0x00000008 +#define SDHCI_INT_SPACE_AVAIL 0x00000010 +#define SDHCI_INT_DATA_AVAIL 0x00000020 +#define SDHCI_INT_CARD_INSERT 0x00000040 +#define SDHCI_INT_CARD_REMOVE 0x00000080 +#define SDHCI_INT_CARD_INT 0x00000100 +#define SDHCI_INT_ERROR 0x00008000 +#define SDHCI_INT_TIMEOUT 0x00010000 +#define SDHCI_INT_CRC 0x00020000 +#define SDHCI_INT_END_BIT 0x00040000 +#define SDHCI_INT_INDEX 0x00080000 +#define SDHCI_INT_DATA_TIMEOUT 0x00100000 +#define SDHCI_INT_DATA_CRC 0x00200000 +#define SDHCI_INT_DATA_END_BIT 0x00400000 +#define SDHCI_INT_BUS_POWER 0x00800000 +#define SDHCI_INT_ACMD12ERR 0x01000000 +#define SDHCI_INT_ADMA_ERROR 0x02000000 + +#define SDHCI_INT_NORMAL_MASK 0x00007FFF +#define SDHCI_INT_ERROR_MASK 0xFFFF8000 + +#define SDHCI_INT_CMD_MASK (SDHCI_INT_RESPONSE | SDHCI_INT_TIMEOUT | \ + SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX) +#define SDHCI_INT_DATA_MASK (SDHCI_INT_DATA_END | SDHCI_INT_DMA_END | \ + SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | \ + SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \ + SDHCI_INT_DATA_END_BIT | SDHCI_INT_ADMA_ERROR) +#define SDHCI_INT_ALL_MASK ((unsigned int)-1) + +#define SDHCI_ACMD12_ERR 0x3C + +#define SDHCI_HOST_CONTROL2 0x3E +#define SDHCI_CTRL_UHS_MASK 0x0007 +#define SDHCI_CTRL_UHS_SDR12 0x0000 +#define SDHCI_CTRL_UHS_SDR25 0x0001 +#define SDHCI_CTRL_UHS_SDR50 0x0002 +#define SDHCI_CTRL_UHS_SDR104 0x0003 +#define SDHCI_CTRL_UHS_DDR50 0x0004 +#define SDHCI_CTRL_HS400 0x0005 /* reserved value in SDIO spec */ +#define SDHCI_CTRL_VDD_180 0x0008 +#define SDHCI_CTRL_DRV_TYPE_MASK 0x0030 +#define SDHCI_CTRL_DRV_TYPE_B 0x0000 +#define SDHCI_CTRL_DRV_TYPE_A 0x0010 +#define SDHCI_CTRL_DRV_TYPE_C 0x0020 +#define SDHCI_CTRL_DRV_TYPE_D 0x0030 +#define SDHCI_CTRL_EXEC_TUNING 0x0040 +#define SDHCI_CTRL_TUNED_CLK 0x0080 +#define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000 + +#define SDHCI_CAPABILITIES 0x40 +#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F +#define SDHCI_TIMEOUT_CLK_SHIFT 0 +#define SDHCI_TIMEOUT_CLK_UNIT 0x00000080 +#define SDHCI_CLOCK_BASE_MASK 0x00003F00 +#define SDHCI_CLOCK_V3_BASE_MASK 0x0000FF00 +#define SDHCI_CLOCK_BASE_SHIFT 8 +#define SDHCI_MAX_BLOCK_MASK 0x00030000 +#define SDHCI_MAX_BLOCK_SHIFT 16 +#define SDHCI_CAN_DO_8BIT 0x00040000 +#define SDHCI_CAN_DO_ADMA2 0x00080000 +#define SDHCI_CAN_DO_ADMA1 0x00100000 +#define SDHCI_CAN_DO_HISPD 0x00200000 +#define SDHCI_CAN_DO_SDMA 0x00400000 +#define SDHCI_CAN_VDD_330 0x01000000 +#define SDHCI_CAN_VDD_300 0x02000000 +#define SDHCI_CAN_VDD_180 0x04000000 +#define SDHCI_CAN_64BIT 0x10000000 + +#define SDHCI_CAPABILITIES_1 0x44 +#define SDHCI_SUPPORT_HS400 0x80000000 + +#define SDHCI_MAX_CURRENT 0x48 + +/* 4C-4F reserved for more max current */ + +#define SDHCI_SET_ACMD12_ERROR 0x50 +#define SDHCI_SET_INT_ERROR 0x52 + +#define SDHCI_ADMA_ERROR 0x54 + +/* 55-57 reserved */ + +#define SDHCI_ADMA_ADDRESS 0x58 + +/* 60-FB reserved */ + +#define SDHCI_SLOT_INT_STATUS 0xFC + +#define SDHCI_HOST_VERSION 0xFE +#define SDHCI_VENDOR_VER_MASK 0xFF00 +#define SDHCI_VENDOR_VER_SHIFT 8 +#define SDHCI_SPEC_VER_MASK 0x00FF +#define SDHCI_SPEC_VER_SHIFT 0 +#define SDHCI_SPEC_100 0 +#define SDHCI_SPEC_200 1 +#define SDHCI_SPEC_300 2 + +/* + * End of controller registers. + */ + +#define SDHCI_MAX_DIV_SPEC_200 256 +#define SDHCI_MAX_DIV_SPEC_300 2046 + +/* +* platform_infos +*/ +#define SDHCI_PLATFORM_REMOVABLE (1 << 0) +#define SDHCI_PLATFORM_NO_EMMC_HS200 (1 << 1) +#define SDHCI_PLATFORM_EMMC_1V8_POWER (1 << 2) +#define SDHCI_PLATFORM_NO_CLK_BASE (1 << 3) +#define SDHCI_PLATFORM_SUPPORTS_HS400ES (1 << 4) +/* + * quirks + */ +#define SDHCI_QUIRK_32BIT_DMA_ADDR (1 << 0) +#define SDHCI_QUIRK_REG32_RW (1 << 1) +#define SDHCI_QUIRK_BROKEN_R1B (1 << 2) +#define SDHCI_QUIRK_NO_HISPD_BIT (1 << 3) +#define SDHCI_QUIRK_BROKEN_VOLTAGE (1 << 4) +#define SDHCI_QUIRK_NO_CD (1 << 5) +#define SDHCI_QUIRK_WAIT_SEND_CMD (1 << 6) +#define SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER (1 << 7) +#define SDHCI_QUIRK_NO_EMMC_HS200 (1 << 8) +#define SDHCI_QUIRK_EMMC_1V8_POWER (1 << 9) +#define SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN (1 << 10) +#define SDHCI_QUIRK_SUPPORTS_HS400ES (1 << 11) + +/* + * Host SDMA buffer boundary. Valid values from 4K to 512K in powers of 2. + */ +#define SDHCI_DEFAULT_BOUNDARY_SIZE (512 * 1024) +#define SDHCI_DEFAULT_BOUNDARY_ARG (7) + +/* ADMA packet descriptor */ +typedef struct { + u16 attributes; + u16 length; + u32 addr; +} SdhciAdma; + +typedef struct { + u16 attributes; + u16 length; + u32 addr; + u32 addr_hi; +} SdhciAdma64; + +#define SDHCI_MAX_PER_DESCRIPTOR 0x10000 + +/* ADMA descriptor attributes */ +#define SDHCI_ADMA_VALID (1 << 0) +#define SDHCI_ADMA_END (1 << 1) +#define SDHCI_ADMA_INT (1 << 2) +#define SDHCI_ACT_NOP (0 << 4) +#define SDHCI_ACT_TRAN (2 << 4) +#define SDHCI_ACT_LINK (3 << 4) + +typedef struct sdhci_host SdhciHost; + +struct sdhci_host { + MmcCtrlr mmc_ctrlr; + char *name; + void *ioaddr; + + int initialized; + unsigned quirks; + unsigned host_caps; + unsigned version; + unsigned clock; /* current, min and max interface clocks */ + unsigned clock_f_min; + unsigned clock_f_max; + unsigned clock_base; /* controller base clock */ + int removable; + unsigned voltages; + + /* + * Dynamically allocated array of ADMA descriptors to use for data + * transfers + */ + SdhciAdma *adma_descs; + SdhciAdma64 *adma64_descs; + /* select 32bit or 64bit ADMA operations */ + unsigned dma64; + + /* Number of ADMA descriptors currently in the array. */ + int adma_desc_count; + + int (*attach)(SdhciHost *host); + void (*set_control_reg)(SdhciHost *host); +}; + +static inline void sdhci_writel(SdhciHost *host, u32 val, int reg) +{ + writel(val, host->ioaddr + reg); +} + +static inline void sdhci_writew(SdhciHost *host, u16 val, int reg) +{ + writew(val, host->ioaddr + reg); +} + +static inline void sdhci_writeb(SdhciHost *host, u8 val, int reg) +{ + writeb(val, host->ioaddr + reg); +} +static inline u32 sdhci_readl(SdhciHost *host, int reg) +{ + return readl(host->ioaddr + reg); +} + +static inline u16 sdhci_readw(SdhciHost *host, int reg) +{ + return readw(host->ioaddr + reg); +} + +static inline u8 sdhci_readb(SdhciHost *host, int reg) +{ + return readb(host->ioaddr + reg); +} + +void add_sdhci(SdhciHost *host); +void sdhci_set_ios(MmcCtrlr *mmc_ctrlr); + +/* Add SDHCI controller from PCI */ +SdhciHost *new_pci_sdhci_host(pcidev_t dev, + int platform_info, + int clock_min, + int clock_max); + +/* Add SDHCI controller with memory address */ +SdhciHost *new_mem_sdhci_host(void *ioaddr, + int platform_info, + int clock_min, + int clock_max, + int clock_base); + +#endif