| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2016 Google, Inc |
| * Copyright 2020 NXP |
| * Written by Simon Glass <sjg@chromium.org> |
| */ |
| |
| #include <common.h> |
| #include <log.h> |
| #include <malloc.h> |
| #include <mmc.h> |
| #include "mmc_private.h" |
| |
| static struct list_head mmc_devices; |
| static int cur_dev_num = -1; |
| |
| #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; |
| if (m->preinit) |
| mmc_start_init(m); |
| } |
| |
| struct blk_desc *mmc_get_blk_desc(struct mmc *mmc) |
| { |
| return &mmc->block_dev; |
| } |
| #else |
| struct mmc *find_mmc_device(int dev_num) |
| { |
| struct mmc *m; |
| struct list_head *entry; |
| |
| list_for_each(entry, &mmc_devices) { |
| m = list_entry(entry, struct mmc, link); |
| |
| if (m->block_dev.devnum == dev_num) |
| return m; |
| } |
| |
| #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) |
| printf("MMC Device %d not found\n", dev_num); |
| #endif |
| |
| return NULL; |
| } |
| |
| int mmc_get_next_devnum(void) |
| { |
| return cur_dev_num++; |
| } |
| |
| struct blk_desc *mmc_get_blk_desc(struct mmc *mmc) |
| { |
| return &mmc->block_dev; |
| } |
| |
| int get_mmc_num(void) |
| { |
| return cur_dev_num; |
| } |
| |
| void mmc_do_preinit(void) |
| { |
| struct mmc *m; |
| struct list_head *entry; |
| |
| list_for_each(entry, &mmc_devices) { |
| m = list_entry(entry, struct mmc, link); |
| |
| if (m->preinit) |
| mmc_start_init(m); |
| } |
| } |
| #endif |
| |
| void mmc_list_init(void) |
| { |
| INIT_LIST_HEAD(&mmc_devices); |
| cur_dev_num = 0; |
| } |
| |
| void mmc_list_add(struct mmc *mmc) |
| { |
| INIT_LIST_HEAD(&mmc->link); |
| |
| list_add_tail(&mmc->link, &mmc_devices); |
| } |
| |
| #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) |
| void print_mmc_devices(char separator) |
| { |
| struct mmc *m; |
| struct list_head *entry; |
| char *mmc_type; |
| |
| list_for_each(entry, &mmc_devices) { |
| m = list_entry(entry, struct mmc, link); |
| |
| if (m->has_init) |
| mmc_type = IS_SD(m) ? "SD" : "eMMC"; |
| else |
| mmc_type = NULL; |
| |
| printf("%s: %d", m->cfg->name, m->block_dev.devnum); |
| if (mmc_type) |
| printf(" (%s)", mmc_type); |
| |
| if (entry->next != &mmc_devices) { |
| printf("%c", separator); |
| if (separator != '\n') |
| puts(" "); |
| } |
| } |
| |
| printf("\n"); |
| } |
| |
| #else |
| void print_mmc_devices(char separator) { } |
| #endif |
| |
| #if CONFIG_IS_ENABLED(MMC_TINY) |
| static struct mmc mmc_static = { |
| .dsr_imp = 0, |
| .dsr = 0xffffffff, |
| .block_dev = { |
| .if_type = UCLASS_MMC, |
| .removable = 1, |
| .devnum = 0, |
| .block_read = mmc_bread, |
| .block_write = mmc_bwrite, |
| .block_erase = mmc_berase, |
| .part_type = 0, |
| }, |
| }; |
| |
| struct mmc *mmc_create(const struct mmc_config *cfg, void *priv) |
| { |
| struct mmc *mmc = &mmc_static; |
| |
| /* First MMC device registered, fail to register a new one. |
| * Given users are not expecting this to fail, instead |
| * of failing let's just return the only MMC device |
| */ |
| if (mmc->cfg) { |
| debug("Warning: MMC_TINY doesn't support multiple MMC devices\n"); |
| return mmc; |
| } |
| |
| mmc->cfg = cfg; |
| mmc->priv = priv; |
| |
| return mmc; |
| } |
| |
| void mmc_destroy(struct mmc *mmc) |
| { |
| } |
| #else |
| struct mmc *mmc_create(const struct mmc_config *cfg, void *priv) |
| { |
| struct blk_desc *bdesc; |
| struct mmc *mmc; |
| |
| /* quick validation */ |
| if (cfg == NULL || cfg->f_min == 0 || |
| cfg->f_max == 0 || cfg->b_max == 0) |
| return NULL; |
| |
| #if !CONFIG_IS_ENABLED(DM_MMC) |
| if (cfg->ops == NULL || cfg->ops->send_cmd == NULL) |
| return NULL; |
| #endif |
| |
| mmc = calloc(1, sizeof(*mmc)); |
| if (mmc == NULL) |
| return NULL; |
| |
| mmc->cfg = cfg; |
| mmc->priv = priv; |
| |
| /* the following chunk was mmc_register() */ |
| |
| /* Setup dsr related values */ |
| mmc->dsr_imp = 0; |
| mmc->dsr = 0xffffffff; |
| /* Setup the universal parts of the block interface just once */ |
| bdesc = mmc_get_blk_desc(mmc); |
| bdesc->if_type = UCLASS_MMC; |
| bdesc->removable = 1; |
| bdesc->devnum = mmc_get_next_devnum(); |
| bdesc->block_read = mmc_bread; |
| bdesc->block_write = mmc_bwrite; |
| bdesc->block_erase = mmc_berase; |
| |
| /* setup initial part type */ |
| bdesc->part_type = mmc->cfg->part_type; |
| mmc_list_add(mmc); |
| |
| return mmc; |
| } |
| |
| void mmc_destroy(struct mmc *mmc) |
| { |
| /* only freeing memory for now */ |
| free(mmc); |
| } |
| #endif |
| |
| static int mmc_select_hwpartp(struct blk_desc *desc, int hwpart) |
| { |
| struct mmc *mmc = find_mmc_device(desc->devnum); |
| int ret; |
| |
| if (!mmc) |
| return -ENODEV; |
| |
| if (mmc->block_dev.hwpart == hwpart) |
| return 0; |
| |
| if (mmc->part_config == MMCPART_NOAVAILABLE) |
| return -EMEDIUMTYPE; |
| |
| ret = mmc_switch_part(mmc, hwpart); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int mmc_get_dev(int dev, struct blk_desc **descp) |
| { |
| struct mmc *mmc = find_mmc_device(dev); |
| int ret; |
| |
| if (!mmc) |
| return -ENODEV; |
| ret = mmc_init(mmc); |
| if (ret) |
| return ret; |
| |
| *descp = &mmc->block_dev; |
| |
| return 0; |
| } |
| |
| U_BOOT_LEGACY_BLK(mmc) = { |
| .if_typename = "mmc", |
| .if_type = UCLASS_MMC, |
| .max_devs = -1, |
| .get_dev = mmc_get_dev, |
| .select_hwpart = mmc_select_hwpartp, |
| }; |