From 72d42bad5849dd063fb2f2de84384f97dd990c30 Mon Sep 17 00:00:00 2001 From: Nobuhiro Iwamatsu Date: Wed, 17 Dec 2014 08:03:00 +0900 Subject: mmc: rmobile: Add SDHC support for Renesas rmobile ARM SoC This adds Renesas rmobile ARM SoC's SD/MMC host support. This drivers tested with Gose board and Koelsch board. Signed-off-by: Nobuhiro Iwamatsu Acked-by: Pantelis Antoniou --- drivers/mmc/Kconfig | 9 + drivers/mmc/Makefile | 1 + drivers/mmc/sh_sdhi.c | 695 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 705 insertions(+) create mode 100644 drivers/mmc/sh_sdhi.c (limited to 'drivers') diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index e69de29bb2d..7ba85a2b62c 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -0,0 +1,9 @@ +menu "MMC Host controller Support" + +config SH_SDHI + bool "SuperH/Renesas ARM SoCs on-chip SDHI host controller support" + depends on RMOBILE + help + Support for the on-chip SDHI host controller on SuperH/Renesas ARM SoCs platform + +endmenu diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 461d7d8ec1c..4ba58789361 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_S3C_SDI) += s3c_sdi.o obj-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o obj-$(CONFIG_SDHCI) += sdhci.o obj-$(CONFIG_SH_MMCIF) += sh_mmcif.o +obj-$(CONFIG_SH_SDHI) += sh_sdhi.o obj-$(CONFIG_SOCFPGA_DWMMC) += socfpga_dw_mmc.o obj-$(CONFIG_SPEAR_SDHCI) += spear_sdhci.o obj-$(CONFIG_TEGRA_MMC) += tegra_mmc.o diff --git a/drivers/mmc/sh_sdhi.c b/drivers/mmc/sh_sdhi.c new file mode 100644 index 00000000000..cc62c89a259 --- /dev/null +++ b/drivers/mmc/sh_sdhi.c @@ -0,0 +1,695 @@ +/* + * drivers/mmc/sh_sdhi.c + * + * SD/MMC driver for Renesas rmobile ARM SoCs. + * + * Copyright (C) 2011,2013-2014 Renesas Electronics Corporation + * Copyright (C) 2014 Nobuhiro Iwamatsu + * Copyright (C) 2008-2009 Renesas Solutions Corp. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "sh-sdhi" + +struct sh_sdhi_host { + unsigned long addr; + int ch; + int bus_shift; + unsigned long quirks; + unsigned char wait_int; + unsigned char sd_error; + unsigned char detect_waiting; +}; +static inline void sh_sdhi_writew(struct sh_sdhi_host *host, int reg, u16 val) +{ + writew(val, host->addr + (reg << host->bus_shift)); +} + +static inline u16 sh_sdhi_readw(struct sh_sdhi_host *host, int reg) +{ + return readw(host->addr + (reg << host->bus_shift)); +} + +static void *mmc_priv(struct mmc *mmc) +{ + return (void *)mmc->priv; +} + +static void sh_sdhi_detect(struct sh_sdhi_host *host) +{ + sh_sdhi_writew(host, SDHI_OPTION, + OPT_BUS_WIDTH_1 | sh_sdhi_readw(host, SDHI_OPTION)); + + host->detect_waiting = 0; +} + +static int sh_sdhi_intr(void *dev_id) +{ + struct sh_sdhi_host *host = dev_id; + int state1 = 0, state2 = 0; + + state1 = sh_sdhi_readw(host, SDHI_INFO1); + state2 = sh_sdhi_readw(host, SDHI_INFO2); + + debug("%s: state1 = %x, state2 = %x\n", __func__, state1, state2); + + /* CARD Insert */ + if (state1 & INFO1_CARD_IN) { + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_CARD_IN); + if (!host->detect_waiting) { + host->detect_waiting = 1; + sh_sdhi_detect(host); + } + sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END | + INFO1M_ACCESS_END | INFO1M_CARD_IN | + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + return -EAGAIN; + } + /* CARD Removal */ + if (state1 & INFO1_CARD_RE) { + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_CARD_RE); + if (!host->detect_waiting) { + host->detect_waiting = 1; + sh_sdhi_detect(host); + } + sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END | + INFO1M_ACCESS_END | INFO1M_CARD_RE | + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + sh_sdhi_writew(host, SDHI_SDIO_INFO1_MASK, SDIO_INFO1M_ON); + sh_sdhi_writew(host, SDHI_SDIO_MODE, SDIO_MODE_OFF); + return -EAGAIN; + } + + if (state2 & INFO2_ALL_ERR) { + sh_sdhi_writew(host, SDHI_INFO2, + (unsigned short)~(INFO2_ALL_ERR)); + sh_sdhi_writew(host, SDHI_INFO2_MASK, + INFO2M_ALL_ERR | + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + host->sd_error = 1; + host->wait_int = 1; + return 0; + } + /* Respons End */ + if (state1 & INFO1_RESP_END) { + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_RESP_END); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1M_RESP_END | + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + host->wait_int = 1; + return 0; + } + /* SD_BUF Read Enable */ + if (state2 & INFO2_BRE_ENABLE) { + sh_sdhi_writew(host, SDHI_INFO2, ~INFO2_BRE_ENABLE); + sh_sdhi_writew(host, SDHI_INFO2_MASK, + INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ | + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + host->wait_int = 1; + return 0; + } + /* SD_BUF Write Enable */ + if (state2 & INFO2_BWE_ENABLE) { + sh_sdhi_writew(host, SDHI_INFO2, ~INFO2_BWE_ENABLE); + sh_sdhi_writew(host, SDHI_INFO2_MASK, + INFO2_BWE_ENABLE | INFO2M_BUF_ILL_WRITE | + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + host->wait_int = 1; + return 0; + } + /* Access End */ + if (state1 & INFO1_ACCESS_END) { + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_ACCESS_END); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1_ACCESS_END | + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + host->wait_int = 1; + return 0; + } + return -EAGAIN; +} + +static int sh_sdhi_wait_interrupt_flag(struct sh_sdhi_host *host) +{ + int timeout = 10000000; + + while (1) { + timeout--; + if (timeout < 0) { + debug(DRIVER_NAME": %s timeout\n", __func__); + return 0; + } + + if (!sh_sdhi_intr(host)) + break; + + udelay(1); /* 1 usec */ + } + + return 1; /* Return value: NOT 0 = complete waiting */ +} + +static int sh_sdhi_clock_control(struct sh_sdhi_host *host, unsigned long clk) +{ + u32 clkdiv, i, timeout; + + if (sh_sdhi_readw(host, SDHI_INFO2) & (1 << 14)) { + printf(DRIVER_NAME": Busy state ! Cannot change the clock\n"); + return -EBUSY; + } + + sh_sdhi_writew(host, SDHI_CLK_CTRL, + ~CLK_ENABLE & sh_sdhi_readw(host, SDHI_CLK_CTRL)); + + if (clk == 0) + return -EIO; + + clkdiv = 0x80; + i = CONFIG_SH_SDHI_FREQ >> (0x8 + 1); + for (; clkdiv && clk >= (i << 1); (clkdiv >>= 1)) + i <<= 1; + + sh_sdhi_writew(host, SDHI_CLK_CTRL, clkdiv); + + timeout = 100000; + /* Waiting for SD Bus busy to be cleared */ + while (timeout--) { + if ((sh_sdhi_readw(host, SDHI_INFO2) & 0x2000)) + break; + } + + if (timeout) + sh_sdhi_writew(host, SDHI_CLK_CTRL, + CLK_ENABLE | sh_sdhi_readw(host, SDHI_CLK_CTRL)); + else + return -EBUSY; + + return 0; +} + +static int sh_sdhi_sync_reset(struct sh_sdhi_host *host) +{ + u32 timeout; + sh_sdhi_writew(host, SDHI_SOFT_RST, SOFT_RST_ON); + sh_sdhi_writew(host, SDHI_SOFT_RST, SOFT_RST_OFF); + sh_sdhi_writew(host, SDHI_CLK_CTRL, + CLK_ENABLE | sh_sdhi_readw(host, SDHI_CLK_CTRL)); + + timeout = 100000; + while (timeout--) { + if (!(sh_sdhi_readw(host, SDHI_INFO2) & INFO2_CBUSY)) + break; + udelay(100); + } + + if (!timeout) + return -EBUSY; + + if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF) + sh_sdhi_writew(host, SDHI_HOST_MODE, 1); + + return 0; +} + +static int sh_sdhi_error_manage(struct sh_sdhi_host *host) +{ + unsigned short e_state1, e_state2; + int ret; + + host->sd_error = 0; + host->wait_int = 0; + + e_state1 = sh_sdhi_readw(host, SDHI_ERR_STS1); + e_state2 = sh_sdhi_readw(host, SDHI_ERR_STS2); + if (e_state2 & ERR_STS2_SYS_ERROR) { + if (e_state2 & ERR_STS2_RES_STOP_TIMEOUT) + ret = TIMEOUT; + else + ret = -EILSEQ; + debug("%s: ERR_STS2 = %04x\n", + DRIVER_NAME, sh_sdhi_readw(host, SDHI_ERR_STS2)); + sh_sdhi_sync_reset(host); + + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + return ret; + } + if (e_state1 & ERR_STS1_CRC_ERROR || e_state1 & ERR_STS1_CMD_ERROR) + ret = -EILSEQ; + else + ret = TIMEOUT; + + debug("%s: ERR_STS1 = %04x\n", + DRIVER_NAME, sh_sdhi_readw(host, SDHI_ERR_STS1)); + sh_sdhi_sync_reset(host); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + return ret; +} + +static int sh_sdhi_single_read(struct sh_sdhi_host *host, struct mmc_data *data) +{ + long time; + unsigned short blocksize, i; + unsigned short *p = (unsigned short *)data->dest; + + if ((unsigned long)p & 0x00000001) { + debug(DRIVER_NAME": %s: The data pointer is unaligned.", + __func__); + return -EIO; + } + + host->wait_int = 0; + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + ~INFO1M_ACCESS_END & + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + blocksize = sh_sdhi_readw(host, SDHI_SIZE); + for (i = 0; i < blocksize / 2; i++) + *p++ = sh_sdhi_readw(host, SDHI_BUF0); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + return 0; +} + +static int sh_sdhi_multi_read(struct sh_sdhi_host *host, struct mmc_data *data) +{ + long time; + unsigned short blocksize, i, sec; + unsigned short *p = (unsigned short *)data->dest; + + if ((unsigned long)p & 0x00000001) { + debug(DRIVER_NAME": %s: The data pointer is unaligned.", + __func__); + return -EIO; + } + + debug("%s: blocks = %d, blocksize = %d\n", + __func__, data->blocks, data->blocksize); + + host->wait_int = 0; + for (sec = 0; sec < data->blocks; sec++) { + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + blocksize = sh_sdhi_readw(host, SDHI_SIZE); + for (i = 0; i < blocksize / 2; i++) + *p++ = sh_sdhi_readw(host, SDHI_BUF0); + } + + return 0; +} + +static int sh_sdhi_single_write(struct sh_sdhi_host *host, + struct mmc_data *data) +{ + long time; + unsigned short blocksize, i; + const unsigned short *p = (const unsigned short *)data->src; + + if ((unsigned long)p & 0x00000001) { + debug(DRIVER_NAME": %s: The data pointer is unaligned.", + __func__); + return -EIO; + } + + debug("%s: blocks = %d, blocksize = %d\n", + __func__, data->blocks, data->blocksize); + + host->wait_int = 0; + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_BWE_ENABLE | INFO2M_BUF_ILL_WRITE) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + ~INFO1M_ACCESS_END & + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + blocksize = sh_sdhi_readw(host, SDHI_SIZE); + for (i = 0; i < blocksize / 2; i++) + sh_sdhi_writew(host, SDHI_BUF0, *p++); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + return 0; +} + +static int sh_sdhi_multi_write(struct sh_sdhi_host *host, struct mmc_data *data) +{ + long time; + unsigned short i, sec, blocksize; + const unsigned short *p = (const unsigned short *)data->src; + + debug("%s: blocks = %d, blocksize = %d\n", + __func__, data->blocks, data->blocksize); + + host->wait_int = 0; + for (sec = 0; sec < data->blocks; sec++) { + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_BWE_ENABLE | INFO2M_BUF_ILL_WRITE) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + blocksize = sh_sdhi_readw(host, SDHI_SIZE); + for (i = 0; i < blocksize / 2; i++) + sh_sdhi_writew(host, SDHI_BUF0, *p++); + } + + return 0; +} + +static void sh_sdhi_get_response(struct sh_sdhi_host *host, struct mmc_cmd *cmd) +{ + unsigned short i, j, cnt = 1; + unsigned short resp[8]; + unsigned long *p1, *p2; + + if (cmd->resp_type & MMC_RSP_136) { + cnt = 4; + resp[0] = sh_sdhi_readw(host, SDHI_RSP00); + resp[1] = sh_sdhi_readw(host, SDHI_RSP01); + resp[2] = sh_sdhi_readw(host, SDHI_RSP02); + resp[3] = sh_sdhi_readw(host, SDHI_RSP03); + resp[4] = sh_sdhi_readw(host, SDHI_RSP04); + resp[5] = sh_sdhi_readw(host, SDHI_RSP05); + resp[6] = sh_sdhi_readw(host, SDHI_RSP06); + resp[7] = sh_sdhi_readw(host, SDHI_RSP07); + + /* SDHI REGISTER SPECIFICATION */ + for (i = 7, j = 6; i > 0; i--) { + resp[i] = (resp[i] << 8) & 0xff00; + resp[i] |= (resp[j--] >> 8) & 0x00ff; + } + resp[0] = (resp[0] << 8) & 0xff00; + + /* SDHI REGISTER SPECIFICATION */ + p1 = ((unsigned long *)resp) + 3; + + } else { + resp[0] = sh_sdhi_readw(host, SDHI_RSP00); + resp[1] = sh_sdhi_readw(host, SDHI_RSP01); + + p1 = ((unsigned long *)resp); + } + + p2 = (unsigned long *)cmd->response; +#if defined(__BIG_ENDIAN_BITFIELD) + for (i = 0; i < cnt; i++) { + *p2++ = ((*p1 >> 16) & 0x0000ffff) | + ((*p1 << 16) & 0xffff0000); + p1--; + } +#else + for (i = 0; i < cnt; i++) + *p2++ = *p1--; +#endif /* __BIG_ENDIAN_BITFIELD */ +} + +static unsigned short sh_sdhi_set_cmd(struct sh_sdhi_host *host, + struct mmc_data *data, unsigned short opc) +{ + switch (opc) { + case SD_CMD_APP_SEND_OP_COND: + case SD_CMD_APP_SEND_SCR: + opc |= SDHI_APP; + break; + case SD_CMD_APP_SET_BUS_WIDTH: + /* SD_APP_SET_BUS_WIDTH*/ + if (!data) + opc |= SDHI_APP; + else /* SD_SWITCH */ + opc = SDHI_SD_SWITCH; + break; + default: + break; + } + return opc; +} + +static unsigned short sh_sdhi_data_trans(struct sh_sdhi_host *host, + struct mmc_data *data, unsigned short opc) +{ + unsigned short ret; + + switch (opc) { + case MMC_CMD_READ_MULTIPLE_BLOCK: + ret = sh_sdhi_multi_read(host, data); + break; + case MMC_CMD_WRITE_MULTIPLE_BLOCK: + ret = sh_sdhi_multi_write(host, data); + break; + case MMC_CMD_WRITE_SINGLE_BLOCK: + ret = sh_sdhi_single_write(host, data); + break; + case MMC_CMD_READ_SINGLE_BLOCK: + case SDHI_SD_APP_SEND_SCR: + case SDHI_SD_SWITCH: /* SD_SWITCH */ + ret = sh_sdhi_single_read(host, data); + break; + default: + printf(DRIVER_NAME": SD: NOT SUPPORT CMD = d'%04d\n", opc); + ret = -EINVAL; + break; + } + return ret; +} + +static int sh_sdhi_start_cmd(struct sh_sdhi_host *host, + struct mmc_data *data, struct mmc_cmd *cmd) +{ + long time; + unsigned short opc = cmd->cmdidx; + int ret = 0; + unsigned long timeout; + + debug("opc = %d, arg = %x, resp_type = %x\n", + opc, cmd->cmdarg, cmd->resp_type); + + if (opc == MMC_CMD_STOP_TRANSMISSION) { + /* SDHI sends the STOP command automatically by STOP reg */ + sh_sdhi_writew(host, SDHI_INFO1_MASK, ~INFO1M_ACCESS_END & + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + sh_sdhi_get_response(host, cmd); + return 0; + } + + if (data) { + if ((opc == MMC_CMD_READ_MULTIPLE_BLOCK) || + opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) { + sh_sdhi_writew(host, SDHI_STOP, STOP_SEC_ENABLE); + sh_sdhi_writew(host, SDHI_SECCNT, data->blocks); + } + sh_sdhi_writew(host, SDHI_SIZE, data->blocksize); + } + opc = sh_sdhi_set_cmd(host, data, opc); + + /* + * U-boot cannot use interrupt. + * So this flag may not be clear by timing + */ + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_RESP_END); + + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1M_RESP_END | sh_sdhi_readw(host, SDHI_INFO1_MASK)); + sh_sdhi_writew(host, SDHI_ARG0, + (unsigned short)(cmd->cmdarg & ARG0_MASK)); + sh_sdhi_writew(host, SDHI_ARG1, + (unsigned short)((cmd->cmdarg >> 16) & ARG1_MASK)); + + timeout = 100000; + /* Waiting for SD Bus busy to be cleared */ + while (timeout--) { + if ((sh_sdhi_readw(host, SDHI_INFO2) & 0x2000)) + break; + } + + sh_sdhi_writew(host, SDHI_CMD, (unsigned short)(opc & CMD_MASK)); + + host->wait_int = 0; + sh_sdhi_writew(host, SDHI_INFO1_MASK, + ~INFO1M_RESP_END & sh_sdhi_readw(host, SDHI_INFO1_MASK)); + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_CMD_ERROR | INFO2M_CRC_ERROR | + INFO2M_END_ERROR | INFO2M_TIMEOUT | + INFO2M_RESP_TIMEOUT | INFO2M_ILA) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (!time) + return sh_sdhi_error_manage(host); + + if (host->sd_error) { + switch (cmd->cmdidx) { + case MMC_CMD_ALL_SEND_CID: + case MMC_CMD_SELECT_CARD: + case SD_CMD_SEND_IF_COND: + case MMC_CMD_APP_CMD: + ret = TIMEOUT; + break; + default: + debug(DRIVER_NAME": Cmd(d'%d) err\n", opc); + debug(DRIVER_NAME": cmdidx = %d\n", cmd->cmdidx); + ret = sh_sdhi_error_manage(host); + break; + } + host->sd_error = 0; + host->wait_int = 0; + return ret; + } + if (sh_sdhi_readw(host, SDHI_INFO1) & INFO1_RESP_END) + return -EINVAL; + + if (host->wait_int) { + sh_sdhi_get_response(host, cmd); + host->wait_int = 0; + } + if (data) + ret = sh_sdhi_data_trans(host, data, opc); + + debug("ret = %d, resp = %08x, %08x, %08x, %08x\n", + ret, cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + return ret; +} + +static int sh_sdhi_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sh_sdhi_host *host = mmc_priv(mmc); + int ret; + + host->sd_error = 0; + + ret = sh_sdhi_start_cmd(host, data, cmd); + + return ret; +} + +static void sh_sdhi_set_ios(struct mmc *mmc) +{ + int ret; + struct sh_sdhi_host *host = mmc_priv(mmc); + + ret = sh_sdhi_clock_control(host, mmc->clock); + if (ret) + return; + + if (mmc->bus_width == 4) + sh_sdhi_writew(host, SDHI_OPTION, ~OPT_BUS_WIDTH_1 & + sh_sdhi_readw(host, SDHI_OPTION)); + else + sh_sdhi_writew(host, SDHI_OPTION, OPT_BUS_WIDTH_1 | + sh_sdhi_readw(host, SDHI_OPTION)); + + debug("clock = %d, buswidth = %d\n", mmc->clock, mmc->bus_width); +} + +static int sh_sdhi_initialize(struct mmc *mmc) +{ + struct sh_sdhi_host *host = mmc_priv(mmc); + int ret = sh_sdhi_sync_reset(host); + + sh_sdhi_writew(host, SDHI_PORTSEL, USE_1PORT); + +#if defined(__BIG_ENDIAN_BITFIELD) + sh_sdhi_writew(host, SDHI_EXT_SWAP, SET_SWAP); +#endif + + sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END | + INFO1M_ACCESS_END | INFO1M_CARD_RE | + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + + return ret; +} + +static const struct mmc_ops sh_sdhi_ops = { + .send_cmd = sh_sdhi_send_cmd, + .set_ios = sh_sdhi_set_ios, + .init = sh_sdhi_initialize, +}; + +static struct mmc_config sh_sdhi_cfg = { + .name = DRIVER_NAME, + .ops = &sh_sdhi_ops, + .f_min = CLKDEV_INIT, + .f_max = CLKDEV_HS_DATA, + .voltages = MMC_VDD_32_33 | MMC_VDD_33_34, + .host_caps = MMC_MODE_4BIT | MMC_MODE_HS, + .part_type = PART_TYPE_DOS, + .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, +}; + +int sh_sdhi_init(unsigned long addr, int ch, unsigned long quirks) +{ + int ret = 0; + struct mmc *mmc; + struct sh_sdhi_host *host = NULL; + + if (ch >= CONFIG_SYS_SH_SDHI_NR_CHANNEL) + return -ENODEV; + + host = malloc(sizeof(struct sh_sdhi_host)); + if (!host) + return -ENOMEM; + + mmc = mmc_create(&sh_sdhi_cfg, host); + if (!mmc) { + ret = -1; + goto error; + } + + host->ch = ch; + host->addr = addr; + host->quirks = quirks; + + if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF) + host->bus_shift = 1; + + return ret; +error: + if (host) + free(host); + return ret; +} -- cgit v1.2.3 From c3dbb4f9b7539e39d418fd1f518129fd60c8eca9 Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:17 +0100 Subject: mmc: extend mmcinfo to show enhanced partition attribute This extends the mmcinfo command's output to show which eMMC partitions have the enhanced attribute set. Note that the eMMC spec says that if the enhanced attribute is supported then the boot and RPMB partitions are of the enhanced type. The output of mmcinfo becomes: Device: OMAP SD/MMC Manufacturer ID: fe OEM: 14e Name: MMC16 Tran Speed: 52000000 Rd Block Len: 512 MMC version 4.41 High Capacity: Yes Capacity: 13.8 GiB Bus Width: 4-bit User Capacity: 13.8 GiB ENH Boot Capacity: 16 MiB ENH RPMB Capacity: 128 KiB ENH GP1 Capacity: 64 MiB ENH GP2 Capacity: 64 MiB ENH Signed-off-by: Diego Santa Cruz --- common/cmd_mmc.c | 14 ++++++++++---- drivers/mmc/mmc.c | 3 +++ include/mmc.h | 6 ++++++ 3 files changed, 19 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/common/cmd_mmc.c b/common/cmd_mmc.c index cdcbf5f80f9..d95cfaa4f8e 100644 --- a/common/cmd_mmc.c +++ b/common/cmd_mmc.c @@ -96,16 +96,22 @@ static void print_mmcinfo(struct mmc *mmc) mmc->ddr_mode ? " DDR" : ""); if (!IS_SD(mmc) && mmc->version >= MMC_VERSION_4) { + bool has_enh = (mmc->part_support & ENHNCD_SUPPORT) != 0; puts("User Capacity: "); - print_size(mmc->capacity_user, "\n"); + print_size(mmc->capacity_user, + has_enh && (mmc->part_attr & EXT_CSD_ENH_USR) ? + " ENH\n" : "\n"); puts("Boot Capacity: "); - print_size(mmc->capacity_boot, "\n"); + print_size(mmc->capacity_boot, has_enh ? " ENH\n" : "\n"); puts("RPMB Capacity: "); - print_size(mmc->capacity_rpmb, "\n"); + print_size(mmc->capacity_rpmb, has_enh ? " ENH\n" : "\n"); for (i = 0; i < ARRAY_SIZE(mmc->capacity_gp); i++) { + bool is_enh = has_enh && + (mmc->part_attr & EXT_CSD_ENH_GP(i)); if (mmc->capacity_gp[i]) { printf("GP%i Capacity: ", i); - print_size(mmc->capacity_gp[i], "\n"); + print_size(mmc->capacity_gp[i], + is_enh ? " ENH\n" : "\n"); } } } diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 1eb9c273394..9ce15d03e2c 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -1050,9 +1050,12 @@ static int mmc_startup(struct mmc *mmc) } /* store the partition info of emmc */ + mmc->part_support = ext_csd[EXT_CSD_PARTITIONING_SUPPORT]; if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) || ext_csd[EXT_CSD_BOOT_MULT]) mmc->part_config = ext_csd[EXT_CSD_PART_CONF]; + if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & ENHNCD_SUPPORT) + mmc->part_attr = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE]; mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17; diff --git a/include/mmc.h b/include/mmc.h index 7ec255d8821..69c60706df9 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -201,6 +201,9 @@ #define EXT_CSD_PARTITION_SETTING_COMPLETED (1 << 0) +#define EXT_CSD_ENH_USR (1 << 0) /* user data area is enhanced */ +#define EXT_CSD_ENH_GP(x) (1 << ((x)+1)) /* GP part (x+1) is enhanced */ + #define R1_ILLEGAL_COMMAND (1 << 22) #define R1_APP_CMD (1 << 5) @@ -224,6 +227,7 @@ #define MMCPART_NOAVAILABLE (0xff) #define PART_ACCESS_MASK (0x7) #define PART_SUPPORT (0x1) +#define ENHNCD_SUPPORT (0x2) #define PART_ENH_ATTRIB (0x1f) /* Maximum block size for MMC */ @@ -302,6 +306,8 @@ struct mmc { uint csd[4]; uint cid[4]; ushort rca; + u8 part_support; + u8 part_attr; char part_config; char part_num; uint tran_speed; -- cgit v1.2.3 From 0c453bb76c9578139e1c43e4f0d7a5575fafce8e Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:20 +0100 Subject: mmc: incomplete test to switch to high-capacity group size definitions The eMMC spec mandates that the high-capacity group size definitions should be enabled when the device is partitioned (by setting ERASE_GROUP_DEF in EXT_CSD). The current test to determine when this is required misses a few cases. In particular a device may have been partitioned without setting the enhanced attribute on any partition or partitioning may be completed without creating any extra partitions. This change moves the code to set ERASE_GROUP_DEF to after reading all partition information. It is also enabled when PARTITIONING_SETTING_COMPLETED is set as it is necessary to enable ERASE_GROUP_DEF before setting that bit, so it means that the user previously switched to the high capacity definitions. Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 52 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 9ce15d03e2c..5e9926cddf6 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -818,6 +818,7 @@ static int mmc_startup(struct mmc *mmc) ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN); int timeout = 1000; + bool has_parts = false; #ifdef CONFIG_MMC_SPI_CRC_ON if (mmc_host_is_spi(mmc)) { /* enable CRC check for spi */ @@ -1006,13 +1007,41 @@ static int mmc_startup(struct mmc *mmc) break; } + /* store the partition info of emmc */ + mmc->part_support = ext_csd[EXT_CSD_PARTITIONING_SUPPORT]; + if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) || + ext_csd[EXT_CSD_BOOT_MULT]) + mmc->part_config = ext_csd[EXT_CSD_PART_CONF]; + if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & ENHNCD_SUPPORT) + mmc->part_attr = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE]; + + mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17; + + mmc->capacity_rpmb = ext_csd[EXT_CSD_RPMB_MULT] << 17; + + for (i = 0; i < 4; i++) { + int idx = EXT_CSD_GP_SIZE_MULT + i * 3; + mmc->capacity_gp[i] = (ext_csd[idx + 2] << 16) + + (ext_csd[idx + 1] << 8) + ext_csd[idx]; + mmc->capacity_gp[i] *= + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + if (mmc->capacity_gp[i]) + has_parts = true; + } + /* * Host needs to enable ERASE_GRP_DEF bit if device is * partitioned. This bit will be lost every time after a reset * or power off. This will affect erase size. */ + if (ext_csd[EXT_CSD_PARTITION_SETTING] & + EXT_CSD_PARTITION_SETTING_COMPLETED) + has_parts = true; if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) && - (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & PART_ENH_ATTRIB)) { + (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & PART_ENH_ATTRIB)) + has_parts = true; + if (has_parts) { err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_ERASE_GROUP_DEF, 1); @@ -1048,27 +1077,6 @@ static int mmc_startup(struct mmc *mmc) mmc->erase_grp_size = (erase_gsz + 1) * (erase_gmul + 1); } - - /* store the partition info of emmc */ - mmc->part_support = ext_csd[EXT_CSD_PARTITIONING_SUPPORT]; - if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) || - ext_csd[EXT_CSD_BOOT_MULT]) - mmc->part_config = ext_csd[EXT_CSD_PART_CONF]; - if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & ENHNCD_SUPPORT) - mmc->part_attr = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE]; - - mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17; - - mmc->capacity_rpmb = ext_csd[EXT_CSD_RPMB_MULT] << 17; - - for (i = 0; i < 4; i++) { - int idx = EXT_CSD_GP_SIZE_MULT + i * 3; - mmc->capacity_gp[i] = (ext_csd[idx + 2] << 16) + - (ext_csd[idx + 1] << 8) + ext_csd[idx]; - mmc->capacity_gp[i] *= - ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; - mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; - } } err = mmc_set_capacity(mmc, mmc->part_num); -- cgit v1.2.3 From f8e89d67166bbcd34a3144b8001b3819b58d02c9 Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:21 +0100 Subject: mmc: computation of eMMC GP partition size was missing 512 KiB factor Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 5e9926cddf6..86c4db9f6f7 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -1026,6 +1026,7 @@ static int mmc_startup(struct mmc *mmc) mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + mmc->capacity_gp[i] <<= 19; if (mmc->capacity_gp[i]) has_parts = true; } -- cgit v1.2.3 From a7f852b6885dc7b89741b1e76921e160b9c9877b Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:22 +0100 Subject: mmc: read the size of eMMC enhanced user data area This modification reads the size of the eMMC enhanced user data area upon initialization of an mmc device, it will be used later by mmcinfo. Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 15 +++++++++++++++ include/mmc.h | 4 ++++ 2 files changed, 19 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 86c4db9f6f7..f07505f5564 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -1031,6 +1031,21 @@ static int mmc_startup(struct mmc *mmc) has_parts = true; } + mmc->enh_user_size = + (ext_csd[EXT_CSD_ENH_SIZE_MULT+2] << 16) + + (ext_csd[EXT_CSD_ENH_SIZE_MULT+1] << 8) + + ext_csd[EXT_CSD_ENH_SIZE_MULT]; + mmc->enh_user_size *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + mmc->enh_user_size *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + mmc->enh_user_size <<= 19; + mmc->enh_user_start = + (ext_csd[EXT_CSD_ENH_START_ADDR+3] << 24) + + (ext_csd[EXT_CSD_ENH_START_ADDR+2] << 16) + + (ext_csd[EXT_CSD_ENH_START_ADDR+1] << 8) + + ext_csd[EXT_CSD_ENH_START_ADDR]; + if (mmc->high_capacity) + mmc->enh_user_start <<= 9; + /* * Host needs to enable ERASE_GRP_DEF bit if device is * partitioned. This bit will be lost every time after a reset diff --git a/include/mmc.h b/include/mmc.h index 69c60706df9..18155c9d6cd 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -147,6 +147,8 @@ /* * EXT_CSD fields */ +#define EXT_CSD_ENH_START_ADDR 136 /* R/W */ +#define EXT_CSD_ENH_SIZE_MULT 140 /* R/W */ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_SETTING 155 /* R/W */ #define EXT_CSD_PARTITIONS_ATTRIBUTE 156 /* R/W */ @@ -319,6 +321,8 @@ struct mmc { u64 capacity_boot; u64 capacity_rpmb; u64 capacity_gp[4]; + u64 enh_user_start; + u64 enh_user_size; block_dev_desc_t block_dev; char op_cond_pending; /* 1 if we are waiting on an op_cond command */ char init_in_progress; /* 1 if we have done mmc_start_init() */ -- cgit v1.2.3 From a4ff9f83f5f902717e87c05cf9d2d02b472d4257 Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:24 +0100 Subject: mmc: fix erase_grp_size computation with high-capacity size definition The erase_grp_size in struct mmc is to be a size in 512-byte sectors but the code used to compute it for eMMC when EXT_CSD_ERASE_GROUP_DEF is enabled computed it as bytes, leading to erase sizes and alignment much larger than what is actually required by the mmc device. Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 3 +-- include/mmc.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index f07505f5564..be211016cf5 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -1068,8 +1068,7 @@ static int mmc_startup(struct mmc *mmc) /* Read out group size from ext_csd */ mmc->erase_grp_size = - ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * - MMC_MAX_BLOCK_LEN * 1024; + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024; /* * if high capacity and partition setting completed * SEC_COUNT is valid even if it is smaller than 2 GiB diff --git a/include/mmc.h b/include/mmc.h index 18155c9d6cd..6c8bbfc952b 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -315,7 +315,7 @@ struct mmc { uint tran_speed; uint read_bl_len; uint write_bl_len; - uint erase_grp_size; + uint erase_grp_size; /* in 512-byte sectors */ u64 capacity; u64 capacity_user; u64 capacity_boot; -- cgit v1.2.3 From 037dc0ab5dff36984d97735987dff01a7ec73893 Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:25 +0100 Subject: mmc: read the high capacity WP group size for eMMC Read the eMMC high capacity write protect group size at mmc device initialization. This is useful to correctly partition an eMMC device, as partitions need to be aligned to this size. Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 6 ++++++ include/mmc.h | 1 + 2 files changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index be211016cf5..16a7a90398f 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -1065,7 +1065,9 @@ static int mmc_startup(struct mmc *mmc) return err; else ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1; + } + if (ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x01) { /* Read out group size from ext_csd */ mmc->erase_grp_size = ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024; @@ -1092,6 +1094,10 @@ static int mmc_startup(struct mmc *mmc) mmc->erase_grp_size = (erase_gsz + 1) * (erase_gmul + 1); } + + mmc->hc_wp_grp_size = 1024 + * ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] + * ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; } err = mmc_set_capacity(mmc, mmc->part_num); diff --git a/include/mmc.h b/include/mmc.h index 6c8bbfc952b..bcaf9f012f0 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -316,6 +316,7 @@ struct mmc { uint read_bl_len; uint write_bl_len; uint erase_grp_size; /* in 512-byte sectors */ + uint hc_wp_grp_size; /* in 512-byte sectors */ u64 capacity; u64 capacity_user; u64 capacity_boot; -- cgit v1.2.3 From 8a0cf4901021b09c283f172d4d29d0d05721b0ba Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:27 +0100 Subject: mmc: eMMC partitioning data is not effective till partitioning completed The eMMC spec says that partitioning is only effective after the PARTITION_SETTING_COMPLETED is set in EXT_CSD (and a power cycle was done, but that we cannot know). Thus the partition sizes and attributes should be ignored when that bit is not set, otherwise the various capacities are not coherent (e.g., the user data capacity will be that of the unpartitioned device while partition sizes would be non-zero). Prescence of non-zero partitioning data is nevertheless still used to activate the high-capacity size definitions (EXT_CSD_ERASE_GROUP_DEF) as it is necessary to set that to write any of the partitioning fields in EXT_CSD, so having partitioning data means someone previously activated that and we should keep it activated. Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 58 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 16a7a90398f..403843b104d 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -819,6 +819,7 @@ static int mmc_startup(struct mmc *mmc) ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN); int timeout = 1000; bool has_parts = false; + bool part_completed; #ifdef CONFIG_MMC_SPI_CRC_ON if (mmc_host_is_spi(mmc)) { /* enable CRC check for spi */ @@ -1007,12 +1008,21 @@ static int mmc_startup(struct mmc *mmc) break; } + /* The partition data may be non-zero but it is only + * effective if PARTITION_SETTING_COMPLETED is set in + * EXT_CSD, so ignore any data if this bit is not set, + * except for enabling the high-capacity group size + * definition (see below). */ + part_completed = !!(ext_csd[EXT_CSD_PARTITION_SETTING] & + EXT_CSD_PARTITION_SETTING_COMPLETED); + /* store the partition info of emmc */ mmc->part_support = ext_csd[EXT_CSD_PARTITIONING_SUPPORT]; if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) || ext_csd[EXT_CSD_BOOT_MULT]) mmc->part_config = ext_csd[EXT_CSD_PART_CONF]; - if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & ENHNCD_SUPPORT) + if (part_completed && + (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & ENHNCD_SUPPORT)) mmc->part_attr = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE]; mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17; @@ -1021,38 +1031,42 @@ static int mmc_startup(struct mmc *mmc) for (i = 0; i < 4; i++) { int idx = EXT_CSD_GP_SIZE_MULT + i * 3; - mmc->capacity_gp[i] = (ext_csd[idx + 2] << 16) + + uint mult = (ext_csd[idx + 2] << 16) + (ext_csd[idx + 1] << 8) + ext_csd[idx]; + if (mult) + has_parts = true; + if (!part_completed) + continue; + mmc->capacity_gp[i] = mult; mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; mmc->capacity_gp[i] <<= 19; - if (mmc->capacity_gp[i]) - has_parts = true; } - mmc->enh_user_size = - (ext_csd[EXT_CSD_ENH_SIZE_MULT+2] << 16) + - (ext_csd[EXT_CSD_ENH_SIZE_MULT+1] << 8) + - ext_csd[EXT_CSD_ENH_SIZE_MULT]; - mmc->enh_user_size *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; - mmc->enh_user_size *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; - mmc->enh_user_size <<= 19; - mmc->enh_user_start = - (ext_csd[EXT_CSD_ENH_START_ADDR+3] << 24) + - (ext_csd[EXT_CSD_ENH_START_ADDR+2] << 16) + - (ext_csd[EXT_CSD_ENH_START_ADDR+1] << 8) + - ext_csd[EXT_CSD_ENH_START_ADDR]; - if (mmc->high_capacity) - mmc->enh_user_start <<= 9; + if (part_completed) { + mmc->enh_user_size = + (ext_csd[EXT_CSD_ENH_SIZE_MULT+2] << 16) + + (ext_csd[EXT_CSD_ENH_SIZE_MULT+1] << 8) + + ext_csd[EXT_CSD_ENH_SIZE_MULT]; + mmc->enh_user_size *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + mmc->enh_user_size *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + mmc->enh_user_size <<= 19; + mmc->enh_user_start = + (ext_csd[EXT_CSD_ENH_START_ADDR+3] << 24) + + (ext_csd[EXT_CSD_ENH_START_ADDR+2] << 16) + + (ext_csd[EXT_CSD_ENH_START_ADDR+1] << 8) + + ext_csd[EXT_CSD_ENH_START_ADDR]; + if (mmc->high_capacity) + mmc->enh_user_start <<= 9; + } /* * Host needs to enable ERASE_GRP_DEF bit if device is * partitioned. This bit will be lost every time after a reset * or power off. This will affect erase size. */ - if (ext_csd[EXT_CSD_PARTITION_SETTING] & - EXT_CSD_PARTITION_SETTING_COMPLETED) + if (part_completed) has_parts = true; if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) && (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & PART_ENH_ATTRIB)) @@ -1076,9 +1090,7 @@ static int mmc_startup(struct mmc *mmc) * SEC_COUNT is valid even if it is smaller than 2 GiB * JEDEC Standard JESD84-B45, 6.2.4 */ - if (mmc->high_capacity && - (ext_csd[EXT_CSD_PARTITION_SETTING] & - EXT_CSD_PARTITION_SETTING_COMPLETED)) { + if (mmc->high_capacity && part_completed) { capacity = (ext_csd[EXT_CSD_SEC_CNT]) | (ext_csd[EXT_CSD_SEC_CNT + 1] << 8) | (ext_csd[EXT_CSD_SEC_CNT + 2] << 16) | -- cgit v1.2.3 From 9cf199ebcff13f8d98b31ab48e3bcce37ed8925d Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:28 +0100 Subject: mmc: the ext_csd data may be used during init even if reading failed The mmc_startup() function uses the ext_csd data even if reading it from the mmc device failed. This bug was introduced in commit bc897b1d4d86597311430dbe7b3e6c807c8c53e5. We now bail out if reading it fails, this should not be a problem as ext_csd was introduced in MMC 4.0 and this code is conditional on MMC >= 4.0. Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 403843b104d..63a1e0c6fa3 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -972,7 +972,9 @@ static int mmc_startup(struct mmc *mmc) if (!IS_SD(mmc) && (mmc->version >= MMC_VERSION_4)) { /* check ext_csd version and capacity */ err = mmc_send_ext_csd(mmc, ext_csd); - if (!err && (ext_csd[EXT_CSD_REV] >= 2)) { + if (err) + return err; + if (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 -- cgit v1.2.3 From ac9da0e08c01e3addc55241ffc09db29aa800473 Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:29 +0100 Subject: mmc: add API to do eMMC hardware partitioning This adds an API to do hardware partitioning on eMMC devices. The new mmc_hwpart_config() function does the partitioning in one go. As the different attributes and partitioning options on eMMC may be interdependent validation has to be done based on the complete partitioning configuration. The function accepts three modes: - MMC_HWPART_CONF_CHECK: just validates that the configuration is valid. - MMC_HWPART_CONF_SET: validates and sets all the fields in EXT_CSD but without setting the "partitioning completed" bit, and thus is reversible. - MMC_HWPART_CONF_COMPLETE: does everything and is thus not reversible. Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/mmc.h | 20 +++++++ 2 files changed, 175 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 63a1e0c6fa3..2a42e503d50 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -605,6 +605,161 @@ int mmc_switch_part(int dev_num, unsigned int part_num) return ret; } +int mmc_hwpart_config(struct mmc *mmc, + const struct mmc_hwpart_conf *conf, + enum mmc_hwpart_conf_mode mode) +{ + u8 part_attrs = 0; + u32 enh_size_mult; + u32 enh_start_addr; + u32 gp_size_mult[4]; + u32 max_enh_size_mult; + u32 tot_enh_size_mult = 0; + int i, pidx, err; + ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); + + if (mode < MMC_HWPART_CONF_CHECK || mode > MMC_HWPART_CONF_COMPLETE) + return -EINVAL; + + if (IS_SD(mmc) || (mmc->version < MMC_VERSION_4_41)) { + printf("eMMC >= 4.4 required for enhanced user data area\n"); + return -EMEDIUMTYPE; + } + + if (!(mmc->part_support & PART_SUPPORT)) { + printf("Card does not support partitioning\n"); + return -EMEDIUMTYPE; + } + + if (!mmc->hc_wp_grp_size) { + printf("Card does not define HC WP group size\n"); + return -EMEDIUMTYPE; + } + + /* check partition alignment and total enhanced size */ + if (conf->user.enh_size) { + if (conf->user.enh_size % mmc->hc_wp_grp_size || + conf->user.enh_start % mmc->hc_wp_grp_size) { + printf("User data enhanced area not HC WP group " + "size aligned\n"); + return -EINVAL; + } + part_attrs |= EXT_CSD_ENH_USR; + enh_size_mult = conf->user.enh_size / mmc->hc_wp_grp_size; + if (mmc->high_capacity) { + enh_start_addr = conf->user.enh_start; + } else { + enh_start_addr = (conf->user.enh_start << 9); + } + } else { + enh_size_mult = 0; + enh_start_addr = 0; + } + tot_enh_size_mult += enh_size_mult; + + for (pidx = 0; pidx < 4; pidx++) { + if (conf->gp_part[pidx].size % mmc->hc_wp_grp_size) { + printf("GP%i partition not HC WP group size " + "aligned\n", pidx+1); + return -EINVAL; + } + gp_size_mult[pidx] = conf->gp_part[pidx].size / mmc->hc_wp_grp_size; + if (conf->gp_part[pidx].size && conf->gp_part[pidx].enhanced) { + part_attrs |= EXT_CSD_ENH_GP(pidx); + tot_enh_size_mult += gp_size_mult[pidx]; + } + } + + if (part_attrs && ! (mmc->part_support & ENHNCD_SUPPORT)) { + printf("Card does not support enhanced attribute\n"); + return -EMEDIUMTYPE; + } + + err = mmc_send_ext_csd(mmc, ext_csd); + if (err) + return err; + + max_enh_size_mult = + (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT+2] << 16) + + (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT+1] << 8) + + ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT]; + if (tot_enh_size_mult > max_enh_size_mult) { + printf("Total enhanced size exceeds maximum (%u > %u)\n", + tot_enh_size_mult, max_enh_size_mult); + return -EMEDIUMTYPE; + } + + if (ext_csd[EXT_CSD_PARTITION_SETTING] & + EXT_CSD_PARTITION_SETTING_COMPLETED) { + printf("Card already partitioned\n"); + return -EPERM; + } + + if (mode == MMC_HWPART_CONF_CHECK) + return 0; + + /* Partitioning requires high-capacity size definitions */ + if (!(ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x01)) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ERASE_GROUP_DEF, 1); + + if (err) + return err; + + ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1; + + /* update erase group size to be high-capacity */ + mmc->erase_grp_size = + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024; + + } + + /* all OK, write the configuration */ + for (i = 0; i < 4; i++) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ENH_START_ADDR+i, + (enh_start_addr >> (i*8)) & 0xFF); + if (err) + return err; + } + for (i = 0; i < 3; i++) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ENH_SIZE_MULT+i, + (enh_size_mult >> (i*8)) & 0xFF); + if (err) + return err; + } + for (pidx = 0; pidx < 4; pidx++) { + for (i = 0; i < 3; i++) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_GP_SIZE_MULT+pidx*3+i, + (gp_size_mult[pidx] >> (i*8)) & 0xFF); + if (err) + return err; + } + } + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PARTITIONS_ATTRIBUTE, part_attrs); + if (err) + return err; + + if (mode == MMC_HWPART_CONF_SET) + return 0; + + /* Setting PART_SETTING_COMPLETED confirms the partition + * configuration but it only becomes effective after power + * cycle, so we do not adjust the partition related settings + * in the mmc struct. */ + + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PARTITION_SETTING, + EXT_CSD_PARTITION_SETTING_COMPLETED); + if (err) + return err; + + return 0; +} + int mmc_getcd(struct mmc *mmc) { int cd; diff --git a/include/mmc.h b/include/mmc.h index bcaf9f012f0..aacf8201910 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -152,6 +152,7 @@ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_SETTING 155 /* R/W */ #define EXT_CSD_PARTITIONS_ATTRIBUTE 156 /* R/W */ +#define EXT_CSD_MAX_ENH_SIZE_MULT 157 /* R */ #define EXT_CSD_PARTITIONING_SUPPORT 160 /* RO */ #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ #define EXT_CSD_RPMB_MULT 168 /* RO */ @@ -332,6 +333,23 @@ struct mmc { int ddr_mode; }; +struct mmc_hwpart_conf { + struct { + uint enh_start; /* in 512-byte sectors */ + uint enh_size; /* in 512-byte sectors, if 0 no enh area */ + } user; + struct { + uint size; /* in 512-byte sectors */ + int enhanced; + } gp_part[4]; +}; + +enum mmc_hwpart_conf_mode { + MMC_HWPART_CONF_CHECK, + MMC_HWPART_CONF_SET, + MMC_HWPART_CONF_COMPLETE, +}; + int mmc_register(struct mmc *mmc); struct mmc *mmc_create(const struct mmc_config *cfg, void *priv); void mmc_destroy(struct mmc *mmc); @@ -344,6 +362,8 @@ int mmc_set_dev(int dev_num); void print_mmc_devices(char separator); int get_mmc_num(void); int mmc_switch_part(int dev_num, unsigned int part_num); +int mmc_hwpart_config(struct mmc *mmc, const struct mmc_hwpart_conf *conf, + enum mmc_hwpart_conf_mode mode); int mmc_getcd(struct mmc *mmc); int board_mmc_getcd(struct mmc *mmc); int mmc_getwp(struct mmc *mmc); -- cgit v1.2.3 From 8dda5b0e60b84f2679bd51ae4bcdbe527fa438d4 Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:31 +0100 Subject: mmc: extend the mmc hardware partitioning API with write reliability The eMMC partition write reliability settings are to be set while partitioning a device, as per the eMMC spec, so changes to these attributes needs to be done in the hardware partitioning API. This commit adds such support. Signed-off-by: Diego Santa Cruz --- drivers/mmc/mmc.c | 39 +++++++++++++++++++++++++++++++++++++++ include/mmc.h | 13 ++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 2a42e503d50..6fa5435000e 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -615,6 +615,7 @@ int mmc_hwpart_config(struct mmc *mmc, u32 gp_size_mult[4]; u32 max_enh_size_mult; u32 tot_enh_size_mult = 0; + u8 wr_rel_set; int i, pidx, err; ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); @@ -689,6 +690,33 @@ int mmc_hwpart_config(struct mmc *mmc, return -EMEDIUMTYPE; } + /* The default value of EXT_CSD_WR_REL_SET is device + * dependent, the values can only be changed if the + * EXT_CSD_HS_CTRL_REL bit is set. The values can be + * changed only once and before partitioning is completed. */ + wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET]; + if (conf->user.wr_rel_change) { + if (conf->user.wr_rel_set) + wr_rel_set |= EXT_CSD_WR_DATA_REL_USR; + else + wr_rel_set &= ~EXT_CSD_WR_DATA_REL_USR; + } + for (pidx = 0; pidx < 4; pidx++) { + if (conf->gp_part[pidx].wr_rel_change) { + if (conf->gp_part[pidx].wr_rel_set) + wr_rel_set |= EXT_CSD_WR_DATA_REL_GP(pidx); + else + wr_rel_set &= ~EXT_CSD_WR_DATA_REL_GP(pidx); + } + } + + if (wr_rel_set != ext_csd[EXT_CSD_WR_REL_SET] && + !(ext_csd[EXT_CSD_WR_REL_PARAM] & EXT_CSD_HS_CTRL_REL)) { + puts("Card does not support host controlled partition write " + "reliability settings\n"); + return -EMEDIUMTYPE; + } + if (ext_csd[EXT_CSD_PARTITION_SETTING] & EXT_CSD_PARTITION_SETTING_COMPLETED) { printf("Card already partitioned\n"); @@ -746,6 +774,17 @@ int mmc_hwpart_config(struct mmc *mmc, if (mode == MMC_HWPART_CONF_SET) return 0; + /* The WR_REL_SET is a write-once register but shall be + * written before setting PART_SETTING_COMPLETED. As it is + * write-once we can only write it when completing the + * partitioning. */ + if (wr_rel_set != ext_csd[EXT_CSD_WR_REL_SET]) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_WR_REL_SET, wr_rel_set); + if (err) + return err; + } + /* Setting PART_SETTING_COMPLETED confirms the partition * configuration but it only becomes effective after power * cycle, so we do not adjust the partition related settings diff --git a/include/mmc.h b/include/mmc.h index aacf8201910..8d41234e207 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -155,6 +155,8 @@ #define EXT_CSD_MAX_ENH_SIZE_MULT 157 /* R */ #define EXT_CSD_PARTITIONING_SUPPORT 160 /* RO */ #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ +#define EXT_CSD_WR_REL_PARAM 166 /* R */ +#define EXT_CSD_WR_REL_SET 167 /* R/W */ #define EXT_CSD_RPMB_MULT 168 /* RO */ #define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ #define EXT_CSD_BOOT_BUS_WIDTH 177 @@ -207,6 +209,11 @@ #define EXT_CSD_ENH_USR (1 << 0) /* user data area is enhanced */ #define EXT_CSD_ENH_GP(x) (1 << ((x)+1)) /* GP part (x+1) is enhanced */ +#define EXT_CSD_HS_CTRL_REL (1 << 0) /* host controlled WR_REL_SET */ + +#define EXT_CSD_WR_DATA_REL_USR (1 << 0) /* user data area WR_REL */ +#define EXT_CSD_WR_DATA_REL_GP(x) (1 << ((x)+1)) /* GP part (x+1) WR_REL */ + #define R1_ILLEGAL_COMMAND (1 << 22) #define R1_APP_CMD (1 << 5) @@ -337,10 +344,14 @@ struct mmc_hwpart_conf { struct { uint enh_start; /* in 512-byte sectors */ uint enh_size; /* in 512-byte sectors, if 0 no enh area */ + unsigned wr_rel_change : 1; + unsigned wr_rel_set : 1; } user; struct { uint size; /* in 512-byte sectors */ - int enhanced; + unsigned enhanced : 1; + unsigned wr_rel_change : 1; + unsigned wr_rel_set : 1; } gp_part[4]; }; -- cgit v1.2.3 From 9e41a00b572f415bbcc3d8f6fb1c6c541293655b Mon Sep 17 00:00:00 2001 From: Diego Santa Cruz Date: Tue, 23 Dec 2014 10:50:33 +0100 Subject: mmc: extend mmcinfo output to show partition write reliability settings This extends the mmcinfo hardware partition info output to show partitions with write reliability enabled with the "WRREL" string. If the partition does not have write reliability enabled the "WRREL" string is omitted; this is analogous to the ehhanced attribute. Example output: Device: OMAP SD/MMC Manufacturer ID: fe OEM: 14e Name: MMC16 Tran Speed: 52000000 Rd Block Len: 512 MMC version 4.41 High Capacity: Yes Capacity: 13.8 GiB Bus Width: 4-bit Erase Group Size: 8 MiB HC WP Group Size: 16 MiB User Capacity: 13.8 GiB ENH WRREL User Enhanced Start: 0 Bytes User Enhanced Size: 512 MiB Boot Capacity: 16 MiB ENH RPMB Capacity: 128 KiB ENH GP1 Capacity: 64 MiB ENH WRREL GP2 Capacity: 64 MiB ENH WRREL Signed-off-by: Diego Santa Cruz --- common/cmd_mmc.c | 12 ++++++++++-- drivers/mmc/mmc.c | 2 ++ include/mmc.h | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/common/cmd_mmc.c b/common/cmd_mmc.c index 632ddae7a2f..4e28c9d7a4d 100644 --- a/common/cmd_mmc.c +++ b/common/cmd_mmc.c @@ -106,7 +106,11 @@ static void print_mmcinfo(struct mmc *mmc) print_size(((u64)mmc->hc_wp_grp_size) << 9, "\n"); puts("User Capacity: "); - print_size(mmc->capacity_user, usr_enh ? " ENH\n" : "\n"); + print_size(mmc->capacity_user, usr_enh ? " ENH" : ""); + if (mmc->wr_rel_set & EXT_CSD_WR_DATA_REL_USR) + puts(" WRREL\n"); + else + putc('\n'); if (usr_enh) { puts("User Enhanced Start: "); print_size(mmc->enh_user_start, "\n"); @@ -124,7 +128,11 @@ static void print_mmcinfo(struct mmc *mmc) if (mmc->capacity_gp[i]) { printf("GP%i Capacity: ", i+1); print_size(mmc->capacity_gp[i], - is_enh ? " ENH\n" : "\n"); + is_enh ? " ENH" : ""); + if (mmc->wr_rel_set & EXT_CSD_WR_DATA_REL_GP(i)) + puts(" WRREL\n"); + else + putc('\n'); } } } diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 6fa5435000e..19ac4c482fa 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -1306,6 +1306,8 @@ static int mmc_startup(struct mmc *mmc) mmc->hc_wp_grp_size = 1024 * ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + + mmc->wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET]; } err = mmc_set_capacity(mmc, mmc->part_num); diff --git a/include/mmc.h b/include/mmc.h index 8d41234e207..09101e2c87a 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -318,6 +318,7 @@ struct mmc { ushort rca; u8 part_support; u8 part_attr; + u8 wr_rel_set; char part_config; char part_num; uint tran_speed; -- cgit v1.2.3 From bf4770731c1fbd23befb1ffcd0436df2262a3199 Mon Sep 17 00:00:00 2001 From: Andrew Gabbasov Date: Thu, 25 Dec 2014 10:22:24 -0600 Subject: mmc: Avoid redundant switching to 1-bit bus width for MMC cards If all the commands switching an MMC card to 4- or 8-bit bus width fail, and the bus width for the controller and the driver is still set to default 1 bit, there is no need to send one more command to switch the card to 1-bit bus width. Also, if the card or host controller do not support wider bus widths, there is no need to send a switch command at all. However, if one of switch commands succeeds, but the subsequent ext_csd fields comparison fails, the card should be switched to some other bus width (next in the list for the loop), or to default 1-bit bus width as a last resort. That's why it would be incorrect to just remove the 1-bit bus width case from the list, it should still be processed in some cases. panto: Minor cosmetic edit removing superfluous parentheses. Signed-off-by: Andrew Gabbasov Tested-by: Alexey Brodkin Signed-off-by: Pantelis Antoniou --- drivers/mmc/mmc.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 19ac4c482fa..bdd7894231e 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -1380,6 +1380,18 @@ static int mmc_startup(struct mmc *mmc) unsigned int extw = ext_csd_bits[idx]; unsigned int caps = ext_to_hostcaps[extw]; + /* + * If the bus width is still not changed, + * don't try to set the default again. + * Otherwise, recover from switch attempts + * by switching to 1-bit bus width. + */ + if (extw == EXT_CSD_BUS_WIDTH_1 && + mmc->bus_width == 1) { + err = 0; + break; + } + /* * Check to make sure the card and controller support * these capabilities -- cgit v1.2.3 From fc5b32fbf3bd43dabaf409e424e0bfb9a1b4df26 Mon Sep 17 00:00:00 2001 From: Andrew Gabbasov Date: Thu, 25 Dec 2014 10:22:25 -0600 Subject: mmc: Skip changing bus width for MMC cards earlier than version 4.0 Wider bus widths (larger than default 1 bit) appeared in MMC standard version 4.0. So, for MMC cards of any earlier version trying to change the bus width (including ext_csd comparison) does not make any sense. It may work incorrectly and at least cause unnecessary timeouts. So, just skip the entire bus width related activity for earlier versions. Signed-off-by: Andrew Gabbasov Tested-by: Alexey Brodkin --- drivers/mmc/mmc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index bdd7894231e..b8039cd092a 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -486,7 +486,7 @@ static int mmc_change_freq(struct mmc *mmc) char cardtype; int err; - mmc->card_caps = MMC_MODE_4BIT | MMC_MODE_8BIT; + mmc->card_caps = 0; if (mmc_host_is_spi(mmc)) return 0; @@ -495,6 +495,8 @@ static int mmc_change_freq(struct mmc *mmc) if (mmc->version < MMC_VERSION_4) return 0; + mmc->card_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT; + err = mmc_send_ext_csd(mmc, ext_csd); if (err) @@ -1349,7 +1351,8 @@ static int mmc_startup(struct mmc *mmc) mmc->tran_speed = 50000000; else mmc->tran_speed = 25000000; - } else { + } else if (mmc->version >= MMC_VERSION_4) { + /* Only version 4 of MMC supports wider bus widths */ int idx; /* An array of possible bus widths in order of preference */ -- cgit v1.2.3