SZ_16M / 512, (SZ_16M + SZ_8M) / 512, SZ_32M / 512, SZ_64M / 512,
};
-#ifndef CONFIG_DM_MMC_OPS
+static int mmc_set_signal_voltage(struct mmc *mmc, uint signal_voltage);
+
+#if CONFIG_IS_ENABLED(MMC_TINY)
+static struct mmc mmc_static;
+struct mmc *find_mmc_device(int dev_num)
+{
+ return &mmc_static;
+}
+
+void mmc_do_preinit(void)
+{
+ struct mmc *m = &mmc_static;
+#ifdef CONFIG_FSL_ESDHC_ADAPTER_IDENT
+ mmc_set_preinit(m, 1);
+#endif
+ if (m->preinit)
+ mmc_start_init(m);
+}
+
+struct blk_desc *mmc_get_blk_desc(struct mmc *mmc)
+{
+ return &mmc->block_dev;
+}
+#endif
+
+#if !CONFIG_IS_ENABLED(DM_MMC)
__weak int board_mmc_getwp(struct mmc *mmc)
{
return -1;
}
#endif
-#ifndef CONFIG_DM_MMC_OPS
+#if CONFIG_IS_ENABLED(MMC_VERBOSE) || defined(DEBUG)
+const char *mmc_mode_name(enum bus_mode mode)
+{
+ static const char *const names[] = {
+ [MMC_LEGACY] = "MMC legacy",
+ [SD_LEGACY] = "SD Legacy",
+ [MMC_HS] = "MMC High Speed (26MHz)",
+ [SD_HS] = "SD High Speed (50MHz)",
+ [UHS_SDR12] = "UHS SDR12 (25MHz)",
+ [UHS_SDR25] = "UHS SDR25 (50MHz)",
+ [UHS_SDR50] = "UHS SDR50 (100MHz)",
+ [UHS_SDR104] = "UHS SDR104 (208MHz)",
+ [UHS_DDR50] = "UHS DDR50 (50MHz)",
+ [MMC_HS_52] = "MMC High Speed (52MHz)",
+ [MMC_DDR_52] = "MMC DDR52 (52MHz)",
+ [MMC_HS_200] = "HS200 (200MHz)",
+ };
+
+ if (mode >= MMC_MODES_END)
+ return "Unknown mode";
+ else
+ return names[mode];
+}
+#endif
+
+static uint mmc_mode2freq(struct mmc *mmc, enum bus_mode mode)
+{
+ static const int freqs[] = {
+ [SD_LEGACY] = 25000000,
+ [MMC_HS] = 26000000,
+ [SD_HS] = 50000000,
+ [UHS_SDR12] = 25000000,
+ [UHS_SDR25] = 50000000,
+ [UHS_SDR50] = 100000000,
+ [UHS_SDR104] = 208000000,
+ [UHS_DDR50] = 50000000,
+ [MMC_HS_52] = 52000000,
+ [MMC_DDR_52] = 52000000,
+ [MMC_HS_200] = 200000000,
+ };
+
+ if (mode == MMC_LEGACY)
+ return mmc->legacy_speed;
+ else if (mode >= MMC_MODES_END)
+ return 0;
+ else
+ return freqs[mode];
+}
+
+static int mmc_select_mode(struct mmc *mmc, enum bus_mode mode)
+{
+ mmc->selected_mode = mode;
+ mmc->tran_speed = mmc_mode2freq(mmc, mode);
+ mmc->ddr_mode = mmc_is_mode_ddr(mode);
+ debug("selecting mode %s (freq : %d MHz)\n", mmc_mode_name(mode),
+ mmc->tran_speed / 1000000);
+ return 0;
+}
+
+#if !CONFIG_IS_ENABLED(DM_MMC)
int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
{
int ret;
(cmd.response[0] & MMC_STATUS_CURR_STATE) !=
MMC_STATE_PRG)
break;
- else if (cmd.response[0] & MMC_STATUS_MASK) {
+
+ if (cmd.response[0] & MMC_STATUS_MASK) {
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
printf("Status Error: 0x%08X\n",
cmd.response[0]);
return blkcnt;
}
-#ifdef CONFIG_BLK
+#if CONFIG_IS_ENABLED(BLK)
ulong mmc_bread(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, void *dst)
#else
ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,
void *dst)
#endif
{
-#ifdef CONFIG_BLK
+#if CONFIG_IS_ENABLED(BLK)
struct blk_desc *block_dev = dev_get_uclass_platdata(dev);
#endif
int dev_num = block_dev->devnum;
if (!mmc)
return 0;
- err = blk_dselect_hwpart(block_dev, block_dev->hwpart);
+ if (CONFIG_IS_ENABLED(MMC_TINY))
+ err = mmc_switch_part(mmc, block_dev->hwpart);
+ else
+ err = blk_dselect_hwpart(block_dev, block_dev->hwpart);
+
if (err < 0)
return 0;
{
struct mmc_cmd cmd;
int timeout = 1000;
+ int retries = 3;
int ret;
cmd.cmdidx = MMC_CMD_SWITCH;
(index << 16) |
(value << 8);
- ret = mmc_send_cmd(mmc, &cmd, NULL);
+ while (retries > 0) {
+ ret = mmc_send_cmd(mmc, &cmd, NULL);
- /* Waiting for the ready status */
- if (!ret)
- ret = mmc_send_status(mmc, timeout);
+ /* Waiting for the ready status */
+ if (!ret) {
+ ret = mmc_send_status(mmc, timeout);
+ return ret;
+ }
+
+ retries--;
+ }
return ret;
}
-static int mmc_change_freq(struct mmc *mmc)
+static int mmc_set_card_speed(struct mmc *mmc, enum bus_mode mode)
{
- ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
- char cardtype;
int err;
+ int speed_bits;
- mmc->card_caps = 0;
+ ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN);
- if (mmc_host_is_spi(mmc))
- return 0;
+ switch (mode) {
+ case MMC_HS:
+ case MMC_HS_52:
+ case MMC_DDR_52:
+ speed_bits = EXT_CSD_TIMING_HS;
+ case MMC_LEGACY:
+ speed_bits = EXT_CSD_TIMING_LEGACY;
+ break;
+ default:
+ return -EINVAL;
+ }
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING,
+ speed_bits);
+ if (err)
+ return err;
- /* Only version 4 supports high-speed */
- if (mmc->version < MMC_VERSION_4)
- return 0;
+ if ((mode == MMC_HS) || (mode == MMC_HS_52)) {
+ /* Now check to see that it worked */
+ err = mmc_send_ext_csd(mmc, test_csd);
+ if (err)
+ return err;
- mmc->card_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT;
+ /* No high-speed support */
+ if (!test_csd[EXT_CSD_HS_TIMING])
+ return -ENOTSUPP;
+ }
- err = mmc_send_ext_csd(mmc, ext_csd);
+ return 0;
+}
- if (err)
- return err;
+static int mmc_get_capabilities(struct mmc *mmc)
+{
+ u8 *ext_csd = mmc->ext_csd;
+ char cardtype;
- cardtype = ext_csd[EXT_CSD_CARD_TYPE] & 0xf;
+ mmc->card_caps = MMC_MODE_1BIT;
- err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 1);
+ if (mmc_host_is_spi(mmc))
+ return 0;
- if (err)
- return err;
+ /* Only version 4 supports high-speed */
+ if (mmc->version < MMC_VERSION_4)
+ return 0;
- /* Now check to see that it worked */
- err = mmc_send_ext_csd(mmc, ext_csd);
+ if (!ext_csd) {
+ printf("No ext_csd found!\n"); /* this should enver happen */
+ return -ENOTSUPP;
+ }
- if (err)
- return err;
+ mmc->card_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT;
- /* No high-speed support */
- if (!ext_csd[EXT_CSD_HS_TIMING])
- return 0;
+ cardtype = ext_csd[EXT_CSD_CARD_TYPE] & 0xf;
/* High Speed is set, there are two types: 52MHz and 26MHz */
if (cardtype & EXT_CSD_CARD_TYPE_52) {
- if (cardtype & EXT_CSD_CARD_TYPE_DDR_1_8V)
+ if (cardtype & EXT_CSD_CARD_TYPE_DDR_52)
mmc->card_caps |= MMC_MODE_DDR_52MHz;
- mmc->card_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
- } else {
- mmc->card_caps |= MMC_MODE_HS;
+ mmc->card_caps |= MMC_MODE_HS_52MHz;
}
+ if (cardtype & EXT_CSD_CARD_TYPE_26)
+ mmc->card_caps |= MMC_MODE_HS;
return 0;
}
return 0;
}
-#ifndef CONFIG_DM_MMC_OPS
+#if !CONFIG_IS_ENABLED(DM_MMC)
int mmc_getcd(struct mmc *mmc)
{
int cd;
}
-static int sd_change_freq(struct mmc *mmc)
+static int sd_get_capabilities(struct mmc *mmc)
{
int err;
struct mmc_cmd cmd;
- ALLOC_CACHE_ALIGN_BUFFER(uint, scr, 2);
- ALLOC_CACHE_ALIGN_BUFFER(uint, switch_status, 16);
+ ALLOC_CACHE_ALIGN_BUFFER(__be32, scr, 2);
+ ALLOC_CACHE_ALIGN_BUFFER(__be32, switch_status, 16);
struct mmc_data data;
int timeout;
- mmc->card_caps = 0;
+ mmc->card_caps = MMC_MODE_1BIT;
if (mmc_host_is_spi(mmc))
return 0;
}
/* If high-speed isn't supported, we return */
- if (!(__be32_to_cpu(switch_status[3]) & SD_HIGHSPEED_SUPPORTED))
- return 0;
+ if (__be32_to_cpu(switch_status[3]) & SD_HIGHSPEED_SUPPORTED)
+ mmc->card_caps |= MMC_CAP(SD_HS);
- /*
- * 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 (!((mmc->cfg->host_caps & MMC_MODE_HS_52MHz) &&
- (mmc->cfg->host_caps & MMC_MODE_HS)))
- return 0;
+ return 0;
+}
+
+static int sd_set_card_speed(struct mmc *mmc, enum bus_mode mode)
+{
+ int err;
+
+ ALLOC_CACHE_ALIGN_BUFFER(uint, switch_status, 16);
err = sd_switch(mmc, SD_SWITCH_SWITCH, 0, 1, (u8 *)switch_status);
+ if (err)
+ return err;
+
+ if ((__be32_to_cpu(switch_status[4]) & 0x0f000000) != 0x01000000)
+ return -ENOTSUPP;
+
+ return 0;
+}
+
+int sd_select_bus_width(struct mmc *mmc, int w)
+{
+ int err;
+ struct mmc_cmd cmd;
+
+ if ((w != 4) && (w != 1))
+ return -EINVAL;
+ cmd.cmdidx = MMC_CMD_APP_CMD;
+ cmd.resp_type = MMC_RSP_R1;
+ cmd.cmdarg = mmc->rca << 16;
+
+ err = mmc_send_cmd(mmc, &cmd, NULL);
if (err)
return err;
- if ((__be32_to_cpu(switch_status[4]) & 0x0f000000) == 0x01000000)
- mmc->card_caps |= MMC_MODE_HS;
+ cmd.cmdidx = SD_CMD_APP_SET_BUS_WIDTH;
+ cmd.resp_type = MMC_RSP_R1;
+ if (w == 4)
+ cmd.cmdarg = 2;
+ else if (w == 1)
+ cmd.cmdarg = 0;
+ err = mmc_send_cmd(mmc, &cmd, NULL);
+ if (err)
+ return err;
return 0;
}
80,
};
-#ifndef CONFIG_DM_MMC_OPS
-static void mmc_set_ios(struct mmc *mmc)
+static inline int bus_width(uint cap)
{
+ if (cap == MMC_MODE_8BIT)
+ return 8;
+ if (cap == MMC_MODE_4BIT)
+ return 4;
+ if (cap == MMC_MODE_1BIT)
+ return 1;
+ printf("invalid bus witdh capability 0x%x\n", cap);
+ return 0;
+}
+
+#if !CONFIG_IS_ENABLED(DM_MMC)
+static int mmc_set_ios(struct mmc *mmc)
+{
+ int ret = 0;
+
if (mmc->cfg->ops->set_ios)
- mmc->cfg->ops->set_ios(mmc);
+ ret = mmc->cfg->ops->set_ios(mmc);
+
+ return ret;
}
#endif
-void mmc_set_clock(struct mmc *mmc, uint clock)
+int mmc_set_clock(struct mmc *mmc, uint clock)
{
if (clock > mmc->cfg->f_max)
clock = mmc->cfg->f_max;
mmc->clock = clock;
- mmc_set_ios(mmc);
+ return mmc_set_ios(mmc);
}
-static void mmc_set_bus_width(struct mmc *mmc, uint width)
+static int mmc_set_bus_width(struct mmc *mmc, uint width)
{
mmc->bus_width = width;
- mmc_set_ios(mmc);
+ return mmc_set_ios(mmc);
+}
+
+#if CONFIG_IS_ENABLED(MMC_VERBOSE) || defined(DEBUG)
+/*
+ * helper function to display the capabilities in a human
+ * friendly manner. The capabilities include bus width and
+ * supported modes.
+ */
+void mmc_dump_capabilities(const char *text, uint caps)
+{
+ enum bus_mode mode;
+
+ printf("%s: widths [", text);
+ if (caps & MMC_MODE_8BIT)
+ printf("8, ");
+ if (caps & MMC_MODE_4BIT)
+ printf("4, ");
+ if (caps & MMC_MODE_1BIT)
+ printf("1, ");
+ printf("\b\b] modes [");
+ for (mode = MMC_LEGACY; mode < MMC_MODES_END; mode++)
+ if (MMC_CAP(mode) & caps)
+ printf("%s, ", mmc_mode_name(mode));
+ printf("\b\b]\n");
+}
+#endif
+
+struct mode_width_tuning {
+ enum bus_mode mode;
+ uint widths;
+};
+
+static int mmc_set_signal_voltage(struct mmc *mmc, uint signal_voltage)
+{
+ mmc->signal_voltage = signal_voltage;
+ return mmc_set_ios(mmc);
+}
+
+static const struct mode_width_tuning sd_modes_by_pref[] = {
+ {
+ .mode = SD_HS,
+ .widths = MMC_MODE_4BIT | MMC_MODE_1BIT,
+ },
+ {
+ .mode = SD_LEGACY,
+ .widths = MMC_MODE_4BIT | MMC_MODE_1BIT,
+ }
+};
+
+#define for_each_sd_mode_by_pref(caps, mwt) \
+ for (mwt = sd_modes_by_pref;\
+ mwt < sd_modes_by_pref + ARRAY_SIZE(sd_modes_by_pref);\
+ mwt++) \
+ if (caps & MMC_CAP(mwt->mode))
+
+static int sd_select_mode_and_width(struct mmc *mmc)
+{
+ int err;
+ uint widths[] = {MMC_MODE_4BIT, MMC_MODE_1BIT};
+ const struct mode_width_tuning *mwt;
+
+ err = sd_get_capabilities(mmc);
+ if (err)
+ return err;
+ /* Restrict card's capabilities by what the host can do */
+ mmc->card_caps &= (mmc->cfg->host_caps | MMC_MODE_1BIT);
+
+ for_each_sd_mode_by_pref(mmc->card_caps, mwt) {
+ uint *w;
+
+ for (w = widths; w < widths + ARRAY_SIZE(widths); w++) {
+ if (*w & mmc->card_caps & mwt->widths) {
+ debug("trying mode %s width %d (at %d MHz)\n",
+ mmc_mode_name(mwt->mode),
+ bus_width(*w),
+ mmc_mode2freq(mmc, mwt->mode) / 1000000);
+
+ /* configure the bus width (card + host) */
+ err = sd_select_bus_width(mmc, bus_width(*w));
+ if (err)
+ goto error;
+ mmc_set_bus_width(mmc, bus_width(*w));
+
+ /* configure the bus mode (card) */
+ err = sd_set_card_speed(mmc, mwt->mode);
+ if (err)
+ goto error;
+
+ /* configure the bus mode (host) */
+ mmc_select_mode(mmc, mwt->mode);
+ mmc_set_clock(mmc, mmc->tran_speed);
+
+ err = sd_read_ssr(mmc);
+ if (!err)
+ return 0;
+
+ printf("bad ssr\n");
+
+error:
+ /* revert to a safer bus speed */
+ mmc_select_mode(mmc, SD_LEGACY);
+ mmc_set_clock(mmc, mmc->tran_speed);
+ }
+ }
+ }
+
+ printf("unable to select a mode\n");
+ return -ENOTSUPP;
+}
+
+/*
+ * read the compare the part of ext csd that is constant.
+ * This can be used to check that the transfer is working
+ * as expected.
+ */
+static int mmc_read_and_compare_ext_csd(struct mmc *mmc)
+{
+ int err;
+ const u8 *ext_csd = mmc->ext_csd;
+ ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN);
+
+ err = mmc_send_ext_csd(mmc, test_csd);
+ if (err)
+ return err;
+
+ /* Only compare read only fields */
+ if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT]
+ == test_csd[EXT_CSD_PARTITIONING_SUPPORT] &&
+ ext_csd[EXT_CSD_HC_WP_GRP_SIZE]
+ == test_csd[EXT_CSD_HC_WP_GRP_SIZE] &&
+ 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)
+ return 0;
+
+ return -EBADMSG;
+}
+
+static const struct mode_width_tuning mmc_modes_by_pref[] = {
+ {
+ .mode = MMC_HS_200,
+ .widths = MMC_MODE_8BIT | MMC_MODE_4BIT,
+ },
+ {
+ .mode = MMC_DDR_52,
+ .widths = MMC_MODE_8BIT | MMC_MODE_4BIT,
+ },
+ {
+ .mode = MMC_HS_52,
+ .widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT,
+ },
+ {
+ .mode = MMC_HS,
+ .widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT,
+ },
+ {
+ .mode = MMC_LEGACY,
+ .widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT,
+ }
+};
+
+#define for_each_mmc_mode_by_pref(caps, mwt) \
+ for (mwt = mmc_modes_by_pref;\
+ mwt < mmc_modes_by_pref + ARRAY_SIZE(mmc_modes_by_pref);\
+ mwt++) \
+ if (caps & MMC_CAP(mwt->mode))
+
+static const struct ext_csd_bus_width {
+ uint cap;
+ bool is_ddr;
+ uint ext_csd_bits;
+} ext_csd_bus_width[] = {
+ {MMC_MODE_8BIT, true, EXT_CSD_DDR_BUS_WIDTH_8},
+ {MMC_MODE_4BIT, true, EXT_CSD_DDR_BUS_WIDTH_4},
+ {MMC_MODE_8BIT, false, EXT_CSD_BUS_WIDTH_8},
+ {MMC_MODE_4BIT, false, EXT_CSD_BUS_WIDTH_4},
+ {MMC_MODE_1BIT, false, EXT_CSD_BUS_WIDTH_1},
+};
+
+#define for_each_supported_width(caps, ddr, ecbv) \
+ for (ecbv = ext_csd_bus_width;\
+ ecbv < ext_csd_bus_width + ARRAY_SIZE(ext_csd_bus_width);\
+ ecbv++) \
+ if ((ddr == ecbv->is_ddr) && (caps & ecbv->cap))
+
+static int mmc_select_mode_and_width(struct mmc *mmc)
+{
+ int err;
+ const struct mode_width_tuning *mwt;
+ const struct ext_csd_bus_width *ecbw;
+
+ err = mmc_get_capabilities(mmc);
+ if (err)
+ return err;
+
+ /* Restrict card's capabilities by what the host can do */
+ mmc->card_caps &= (mmc->cfg->host_caps | MMC_MODE_1BIT);
+
+ /* Only version 4 of MMC supports wider bus widths */
+ if (mmc->version < MMC_VERSION_4)
+ return 0;
+
+ if (!mmc->ext_csd) {
+ debug("No ext_csd found!\n"); /* this should enver happen */
+ return -ENOTSUPP;
+ }
+
+ for_each_mmc_mode_by_pref(mmc->card_caps, mwt) {
+ for_each_supported_width(mmc->card_caps & mwt->widths,
+ mmc_is_mode_ddr(mwt->mode), ecbw) {
+ debug("trying mode %s width %d (at %d MHz)\n",
+ mmc_mode_name(mwt->mode),
+ bus_width(ecbw->cap),
+ mmc_mode2freq(mmc, mwt->mode) / 1000000);
+ /* configure the bus width (card + host) */
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH,
+ ecbw->ext_csd_bits & ~EXT_CSD_DDR_FLAG);
+ if (err)
+ goto error;
+ mmc_set_bus_width(mmc, bus_width(ecbw->cap));
+
+ /* configure the bus speed (card) */
+ err = mmc_set_card_speed(mmc, mwt->mode);
+ if (err)
+ goto error;
+
+ /*
+ * configure the bus width AND the ddr mode (card)
+ * The host side will be taken care of in the next step
+ */
+ if (ecbw->ext_csd_bits & EXT_CSD_DDR_FLAG) {
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH,
+ ecbw->ext_csd_bits);
+ if (err)
+ goto error;
+ }
+
+ /* configure the bus mode (host) */
+ mmc_select_mode(mmc, mwt->mode);
+ mmc_set_clock(mmc, mmc->tran_speed);
+
+ /* do a transfer to check the configuration */
+ err = mmc_read_and_compare_ext_csd(mmc);
+ if (!err)
+ return 0;
+error:
+ /* if an error occured, revert to a safer bus mode */
+ mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_1);
+ mmc_select_mode(mmc, MMC_LEGACY);
+ mmc_set_bus_width(mmc, 1);
+ }
+ }
+
+ printf("unable to select a mode\n");
+
+ return -ENOTSUPP;
+}
+
+static int mmc_startup_v4(struct mmc *mmc)
+{
+ int err, i;
+ u64 capacity;
+ bool has_parts = false;
+ bool part_completed;
+ u8 *ext_csd;
+
+ if (IS_SD(mmc) || (mmc->version < MMC_VERSION_4))
+ return 0;
+
+ ext_csd = malloc_cache_aligned(MMC_MAX_BLOCK_LEN);
+ if (!ext_csd)
+ return -ENOMEM;
+
+ mmc->ext_csd = ext_csd;
+
+ /* check ext_csd version and capacity */
+ err = mmc_send_ext_csd(mmc, ext_csd);
+ 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
+ * than 2GB
+ */
+ capacity = ext_csd[EXT_CSD_SEC_CNT] << 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 *= MMC_MAX_BLOCK_LEN;
+ if ((capacity >> 20) > 2 * 1024)
+ mmc->capacity_user = capacity;
+ }
+
+ switch (ext_csd[EXT_CSD_REV]) {
+ case 1:
+ mmc->version = MMC_VERSION_4_1;
+ break;
+ case 2:
+ mmc->version = MMC_VERSION_4_2;
+ break;
+ case 3:
+ mmc->version = MMC_VERSION_4_3;
+ break;
+ case 5:
+ mmc->version = MMC_VERSION_4_41;
+ break;
+ case 6:
+ mmc->version = MMC_VERSION_4_5;
+ break;
+ case 7:
+ mmc->version = MMC_VERSION_5_0;
+ break;
+ case 8:
+ mmc->version = MMC_VERSION_5_1;
+ 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 (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;
+
+ 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;
+ 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 (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 (part_completed)
+ has_parts = true;
+ if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) &&
+ (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);
+
+ if (err)
+ return err;
+
+ 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;
+ /*
+ * if high capacity and partition setting completed
+ * SEC_COUNT is valid even if it is smaller than 2 GiB
+ * JEDEC Standard JESD84-B45, 6.2.4
+ */
+ 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) |
+ (ext_csd[EXT_CSD_SEC_CNT + 3] << 24);
+ capacity *= MMC_MAX_BLOCK_LEN;
+ mmc->capacity_user = capacity;
+ }
+ } else {
+ /* Calculate the group size from the csd value. */
+ int erase_gsz, erase_gmul;
+
+ erase_gsz = (mmc->csd[2] & 0x00007c00) >> 10;
+ erase_gmul = (mmc->csd[2] & 0x000003e0) >> 5;
+ 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];
+
+ mmc->wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET];
+
+ return 0;
}
static int mmc_startup(struct mmc *mmc)
{
int err, i;
uint mult, freq;
- u64 cmult, csize, capacity;
+ u64 cmult, csize;
struct mmc_cmd cmd;
- 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;
- bool part_completed;
struct blk_desc *bdesc;
#ifdef CONFIG_MMC_SPI_CRC_ON
err = mmc_send_cmd(mmc, &cmd, NULL);
- /* Waiting for the ready status */
- mmc_send_status(mmc, timeout);
-
if (err)
return err;
freq = fbase[(cmd.response[0] & 0x7)];
mult = multipliers[((cmd.response[0] >> 3) & 0xf)];
- mmc->tran_speed = freq * mult;
+ mmc->legacy_speed = freq * mult;
+ mmc_select_mode(mmc, MMC_LEGACY);
mmc->dsr_imp = ((cmd.response[1] >> 12) & 0x1);
mmc->read_bl_len = 1 << ((cmd.response[1] >> 16) & 0xf);
*/
mmc->erase_grp_size = 1;
mmc->part_config = MMCPART_NOAVAILABLE;
- 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)
- 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
- * than 2GB
- */
- capacity = ext_csd[EXT_CSD_SEC_CNT] << 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 *= MMC_MAX_BLOCK_LEN;
- if ((capacity >> 20) > 2 * 1024)
- mmc->capacity_user = capacity;
- }
-
- switch (ext_csd[EXT_CSD_REV]) {
- case 1:
- mmc->version = MMC_VERSION_4_1;
- break;
- case 2:
- mmc->version = MMC_VERSION_4_2;
- break;
- case 3:
- mmc->version = MMC_VERSION_4_3;
- break;
- case 5:
- mmc->version = MMC_VERSION_4_41;
- break;
- case 6:
- mmc->version = MMC_VERSION_4_5;
- break;
- case 7:
- mmc->version = MMC_VERSION_5_0;
- break;
- case 8:
- mmc->version = MMC_VERSION_5_1;
- 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 (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;
-
- 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;
- 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 (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 (part_completed)
- has_parts = true;
- if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) &&
- (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);
-
- if (err)
- 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;
- /*
- * if high capacity and partition setting completed
- * SEC_COUNT is valid even if it is smaller than 2 GiB
- * JEDEC Standard JESD84-B45, 6.2.4
- */
- 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) |
- (ext_csd[EXT_CSD_SEC_CNT + 3] << 24);
- capacity *= MMC_MAX_BLOCK_LEN;
- mmc->capacity_user = capacity;
- }
- } else {
- /* Calculate the group size from the csd value. */
- int erase_gsz, erase_gmul;
- erase_gsz = (mmc->csd[2] & 0x00007c00) >> 10;
- erase_gmul = (mmc->csd[2] & 0x000003e0) >> 5;
- 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];
-
- mmc->wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET];
- }
+ err = mmc_startup_v4(mmc);
+ if (err)
+ return err;
err = mmc_set_capacity(mmc, mmc_get_blk_desc(mmc)->hwpart);
if (err)
return err;
if (IS_SD(mmc))
- err = sd_change_freq(mmc);
+ err = sd_select_mode_and_width(mmc);
else
- err = mmc_change_freq(mmc);
+ err = mmc_select_mode_and_width(mmc);
if (err)
return err;
- /* Restrict card's capabilities by what the host can do */
- mmc->card_caps &= mmc->cfg->host_caps;
-
- if (IS_SD(mmc)) {
- if (mmc->card_caps & MMC_MODE_4BIT) {
- cmd.cmdidx = MMC_CMD_APP_CMD;
- cmd.resp_type = MMC_RSP_R1;
- cmd.cmdarg = mmc->rca << 16;
-
- err = mmc_send_cmd(mmc, &cmd, NULL);
- if (err)
- return err;
-
- cmd.cmdidx = SD_CMD_APP_SET_BUS_WIDTH;
- cmd.resp_type = MMC_RSP_R1;
- cmd.cmdarg = 2;
- err = mmc_send_cmd(mmc, &cmd, NULL);
- if (err)
- return err;
-
- mmc_set_bus_width(mmc, 4);
- }
-
- err = sd_read_ssr(mmc);
- if (err)
- return err;
-
- if (mmc->card_caps & MMC_MODE_HS)
- mmc->tran_speed = 50000000;
- else
- mmc->tran_speed = 25000000;
- } 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 */
- static unsigned ext_csd_bits[] = {
- EXT_CSD_DDR_BUS_WIDTH_8,
- EXT_CSD_DDR_BUS_WIDTH_4,
- EXT_CSD_BUS_WIDTH_8,
- EXT_CSD_BUS_WIDTH_4,
- EXT_CSD_BUS_WIDTH_1,
- };
-
- /* An array to map CSD bus widths to host cap bits */
- static unsigned ext_to_hostcaps[] = {
- [EXT_CSD_DDR_BUS_WIDTH_4] =
- MMC_MODE_DDR_52MHz | MMC_MODE_4BIT,
- [EXT_CSD_DDR_BUS_WIDTH_8] =
- MMC_MODE_DDR_52MHz | MMC_MODE_8BIT,
- [EXT_CSD_BUS_WIDTH_4] = MMC_MODE_4BIT,
- [EXT_CSD_BUS_WIDTH_8] = MMC_MODE_8BIT,
- };
-
- /* An array to map chosen bus width to an integer */
- static unsigned widths[] = {
- 8, 4, 8, 4, 1,
- };
-
- for (idx=0; idx < ARRAY_SIZE(ext_csd_bits); idx++) {
- 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
- */
- if ((mmc->card_caps & caps) != caps)
- continue;
-
- err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
- EXT_CSD_BUS_WIDTH, extw);
-
- if (err)
- continue;
-
- mmc->ddr_mode = (caps & MMC_MODE_DDR_52MHz) ? 1 : 0;
- mmc_set_bus_width(mmc, widths[idx]);
-
- err = mmc_send_ext_csd(mmc, test_csd);
-
- if (err)
- continue;
-
- /* Only compare read only fields */
- if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT]
- == test_csd[EXT_CSD_PARTITIONING_SUPPORT] &&
- ext_csd[EXT_CSD_HC_WP_GRP_SIZE]
- == test_csd[EXT_CSD_HC_WP_GRP_SIZE] &&
- 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)
- break;
- else
- err = -EBADMSG;
- }
-
- if (err)
- return err;
-
- if (mmc->card_caps & MMC_MODE_HS) {
- if (mmc->card_caps & MMC_MODE_HS_52MHz)
- mmc->tran_speed = 52000000;
- else
- mmc->tran_speed = 26000000;
- }
- }
-
- mmc_set_clock(mmc, mmc->tran_speed);
/* Fix the block length for DDR mode */
if (mmc->ddr_mode) {
return 0;
}
+#if !CONFIG_IS_ENABLED(DM_MMC)
/* board-specific MMC power initializations. */
__weak void board_mmc_power_init(void)
{
}
+#endif
static int mmc_power_init(struct mmc *mmc)
{
- board_mmc_power_init();
-
-#if defined(CONFIG_DM_MMC) && defined(CONFIG_DM_REGULATOR) && \
- !defined(CONFIG_SPL_BUILD)
- struct udevice *vmmc_supply;
+#if CONFIG_IS_ENABLED(DM_MMC)
+#if CONFIG_IS_ENABLED(DM_REGULATOR)
int ret;
ret = device_get_supply_regulator(mmc->dev, "vmmc-supply",
- &vmmc_supply);
- if (ret) {
+ &mmc->vmmc_supply);
+ if (ret)
debug("%s: No vmmc supply\n", mmc->dev->name);
- return 0;
- }
- ret = regulator_set_enable(vmmc_supply, true);
- if (ret) {
- puts("Error enabling VMMC supply\n");
- return ret;
+ ret = device_get_supply_regulator(mmc->dev, "vqmmc-supply",
+ &mmc->vqmmc_supply);
+ if (ret)
+ debug("%s: No vqmmc supply\n", mmc->dev->name);
+
+ if (mmc->vmmc_supply) {
+ ret = regulator_set_enable(mmc->vmmc_supply, true);
+ if (ret) {
+ puts("Error enabling VMMC supply\n");
+ return ret;
+ }
}
+#endif
+#else /* !CONFIG_DM_MMC */
+ /*
+ * Driver model should use a regulator, as above, rather than calling
+ * out to board code.
+ */
+ board_mmc_power_init();
#endif
return 0;
}
/* we pretend there's no card when init is NULL */
no_card = mmc_getcd(mmc) == 0;
-#ifndef CONFIG_DM_MMC_OPS
+#if !CONFIG_IS_ENABLED(DM_MMC)
no_card = no_card || (mmc->cfg->ops->init == NULL);
#endif
if (no_card) {
if (err)
return err;
-#ifdef CONFIG_DM_MMC_OPS
+#if CONFIG_IS_ENABLED(DM_MMC)
/* The device has already been probed ready for use */
#else
/* made sure it's not NULL earlier */
return err;
#endif
mmc->ddr_mode = 0;
+
+ /* First try to set 3.3V. If it fails set to 1.8V */
+ err = mmc_set_signal_voltage(mmc, MMC_SIGNAL_VOLTAGE_330);
+ if (err != 0)
+ err = mmc_set_signal_voltage(mmc, MMC_SIGNAL_VOLTAGE_180);
+ if (err != 0)
+ printf("failed to set signal voltage\n");
+
mmc_set_bus_width(mmc, 1);
mmc_set_clock(mmc, 1);
int mmc_init(struct mmc *mmc)
{
int err = 0;
- unsigned start;
-#ifdef CONFIG_DM_MMC
+ __maybe_unused unsigned start;
+#if CONFIG_IS_ENABLED(DM_MMC)
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(mmc->dev);
upriv->mmc = mmc;
if (!err)
err = mmc_complete_init(mmc);
- debug("%s: %d, time %lu\n", __func__, err, get_timer(start));
+ if (err)
+ printf("%s: %d, time %lu\n", __func__, err, get_timer(start));
+
return err;
}
mmc->preinit = preinit;
}
-#if defined(CONFIG_DM_MMC) && defined(CONFIG_SPL_BUILD)
+#if CONFIG_IS_ENABLED(DM_MMC) && defined(CONFIG_SPL_BUILD)
static int mmc_probe(bd_t *bis)
{
return 0;
}
-#elif defined(CONFIG_DM_MMC)
+#elif CONFIG_IS_ENABLED(DM_MMC)
static int mmc_probe(bd_t *bis)
{
int ret, i;
return 0;
initialized = 1;
-#ifndef CONFIG_BLK
+#if !CONFIG_IS_ENABLED(BLK)
+#if !CONFIG_IS_ENABLED(MMC_TINY)
mmc_list_init();
+#endif
#endif
ret = mmc_probe(bis);
if (ret)
mmc_do_preinit();
return 0;
}
+
+#ifdef CONFIG_CMD_BKOPS_ENABLE
+int mmc_set_bkops_enable(struct mmc *mmc)
+{
+ int err;
+ ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
+
+ err = mmc_send_ext_csd(mmc, ext_csd);
+ if (err) {
+ puts("Could not get ext_csd register values\n");
+ return err;
+ }
+
+ if (!(ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1)) {
+ puts("Background operations not supported on device\n");
+ return -EMEDIUMTYPE;
+ }
+
+ if (ext_csd[EXT_CSD_BKOPS_EN] & 0x1) {
+ puts("Background operations already enabled\n");
+ return 0;
+ }
+
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BKOPS_EN, 1);
+ if (err) {
+ puts("Failed to enable manual background operations\n");
+ return err;
+ }
+
+ puts("Enabled manual background operations\n");
+
+ return 0;
+}
+#endif