diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0b4c2ed22bce..da6bd95fa4bb 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1776,6 +1776,94 @@ int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen) } EXPORT_SYMBOL(mmc_set_blocklen); +static void mmc_hw_reset_for_init(struct mmc_host *host) +{ + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) + return; + mmc_host_clk_hold(host); + host->ops->hw_reset(host); + mmc_host_clk_release(host); +} + +int mmc_can_reset(struct mmc_card *card) +{ + u8 rst_n_function; + + if (!mmc_card_mmc(card)) + return 0; + rst_n_function = card->ext_csd.rst_n_function; + if ((rst_n_function & EXT_CSD_RST_N_EN_MASK) != EXT_CSD_RST_N_ENABLED) + return 0; + return 1; +} +EXPORT_SYMBOL(mmc_can_reset); + +static int mmc_do_hw_reset(struct mmc_host *host, int check) +{ + struct mmc_card *card = host->card; + + if (!host->bus_ops->power_restore) + return -EOPNOTSUPP; + + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) + return -EOPNOTSUPP; + + if (!card) + return -EINVAL; + + if (!mmc_can_reset(card)) + return -EOPNOTSUPP; + + mmc_host_clk_hold(host); + mmc_set_clock(host, host->f_init); + + host->ops->hw_reset(host); + + /* If the reset has happened, then a status command will fail */ + if (check) { + struct mmc_command cmd = {0}; + int err; + + cmd.opcode = MMC_SEND_STATUS; + if (!mmc_host_is_spi(card->host)) + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (!err) { + mmc_host_clk_release(host); + return -ENOSYS; + } + } + + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_DDR); + if (mmc_host_is_spi(host)) { + host->ios.chip_select = MMC_CS_HIGH; + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + } else { + host->ios.chip_select = MMC_CS_DONTCARE; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + } + host->ios.bus_width = MMC_BUS_WIDTH_1; + host->ios.timing = MMC_TIMING_LEGACY; + mmc_set_ios(host); + + mmc_host_clk_release(host); + + return host->bus_ops->power_restore(host); +} + +int mmc_hw_reset(struct mmc_host *host) +{ + return mmc_do_hw_reset(host, 0); +} +EXPORT_SYMBOL(mmc_hw_reset); + +int mmc_hw_reset_check(struct mmc_host *host) +{ + return mmc_do_hw_reset(host, 1); +} +EXPORT_SYMBOL(mmc_hw_reset_check); + static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq; @@ -1786,6 +1874,12 @@ static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) #endif mmc_power_up(host); + /* + * Some eMMCs (with VCCQ always on) may not be reset after power up, so + * do a hardware reset if possible. + */ + mmc_hw_reset_for_init(host); + /* * sdio_reset sends CMD52 to reset card. Since we do not know * if the card is being re-initialized, just send it. CMD52 diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index b74e6f14b3ac..7adc30da8366 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -402,8 +402,10 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) ext_csd[EXT_CSD_TRIM_MULT]; } - if (card->ext_csd.rev >= 5) + if (card->ext_csd.rev >= 5) { card->ext_csd.rel_param = ext_csd[EXT_CSD_WR_REL_PARAM]; + card->ext_csd.rst_n_function = ext_csd[EXT_CSD_RST_N_FUNCTION]; + } if (ext_csd[EXT_CSD_ERASED_MEM_CONT]) card->erased_byte = 0xFF; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 6dfb293326e2..5294ddf382ae 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -50,6 +50,7 @@ struct mmc_ext_csd { u8 rel_sectors; u8 rel_param; u8 part_config; + u8 rst_n_function; unsigned int part_time; /* Units: ms */ unsigned int sa_timeout; /* Units: 100ns */ unsigned int hs_max_dtr; diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index b8b1b7a311f1..56e5625b6f41 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -162,6 +162,9 @@ extern int mmc_erase_group_aligned(struct mmc_card *card, unsigned int from, extern unsigned int mmc_calc_max_discard(struct mmc_card *card); extern int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen); +extern int mmc_hw_reset(struct mmc_host *host); +extern int mmc_hw_reset_check(struct mmc_host *host); +extern int mmc_can_reset(struct mmc_card *card); extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); extern unsigned int mmc_align_data_size(struct mmc_card *, unsigned int); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 340cc0c9409f..b2aefea97048 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -151,6 +151,7 @@ struct mmc_host_ops { int (*execute_tuning)(struct mmc_host *host); void (*enable_preset_value)(struct mmc_host *host, bool enable); int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv); + void (*hw_reset)(struct mmc_host *host); }; struct mmc_card; @@ -233,6 +234,7 @@ struct mmc_host { #define MMC_CAP_MAX_CURRENT_600 (1 << 28) /* Host max current limit is 600mA */ #define MMC_CAP_MAX_CURRENT_800 (1 << 29) /* Host max current limit is 800mA */ #define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */ +#define MMC_CAP_HW_RESET (1 << 31) /* Hardware reset */ mmc_pm_flag_t pm_caps; /* supported pm features */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 5a794cb503ea..ed8fca890ee2 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -272,6 +272,7 @@ struct _mmc_csd { #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ +#define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ #define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ #define EXT_CSD_PART_CONFIG 179 /* R/W */ @@ -328,6 +329,9 @@ struct _mmc_csd { #define EXT_CSD_SEC_BD_BLK_EN BIT(2) #define EXT_CSD_SEC_GB_CL_EN BIT(4) +#define EXT_CSD_RST_N_EN_MASK 0x3 +#define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ + /* * MMC_SWITCH access modes */