[U-Boot] [PATCH 06/10] Add MMC Framework
Haavard Skinnemoen
haavard.skinnemoen at atmel.com
Tue Nov 4 10:37:56 CET 2008
Andy Fleming <afleming at freescale.com> wrote:
> Here's a new framework (based roughly off the linux one) for managing
> MMC controllers. It handles all of the standard SD/MMC transactions,
> leaving the host drivers to implement only what is necessary to
> deal with their specific hardware.
>
> This also hooks the infrastructure into the PowerPC board code
> (similar to how the ethernet infrastructure now hooks in)
>
> Some of this code was contributed by Dave Liu <daveliu at freescale.com>
>
> Signed-off-by: Andy Fleming <afleming at freescale.com>
Nice. I have a few comments, please see below.
> ---
> common/cmd_mmc.c | 122 +++++++
> drivers/mmc/Makefile | 1 +
> drivers/mmc/mmc.c | 926 ++++++++++++++++++++++++++++++++++++++++++++++++++
> include/mmc.h | 174 +++++++++-
> lib_ppc/board.c | 9 +
> 5 files changed, 1223 insertions(+), 9 deletions(-)
> create mode 100644 drivers/mmc/mmc.c
>
> diff --git a/common/cmd_mmc.c b/common/cmd_mmc.c
> index 21873d6..f27d7ef 100644
> --- a/common/cmd_mmc.c
> +++ b/common/cmd_mmc.c
> @@ -25,6 +25,7 @@
> #include <command.h>
> #include <mmc.h>
>
> +#ifndef CONFIG_GENERIC_MMC
> int do_mmc (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
> {
> if (mmc_legacy_init (1) != 0) {
> @@ -39,3 +40,124 @@ U_BOOT_CMD(
> "mmcinit - init mmc card\n",
> NULL
> );
> +#endif /* !CONFIG_GENERIC_MMC */
> +
> +static void print_mmcinfo(struct mmc *mmc)
> +{
> + printf("Device: %s\n", mmc->name);
> + printf("Manufacturer ID: %x\n", mmc->cid[0] >> 24);
> + printf("OEM: %x\n", (mmc->cid[0] >> 8) & 0xffff);
> + printf("Name: %c%c%c%c%c \n", mmc->cid[0] & 0xff,
> + (mmc->cid[1] >> 24), (mmc->cid[1] >> 16) & 0xff,
> + (mmc->cid[1] >> 8) & 0xff, mmc->cid[1] & 0xff);
> +
> + printf("Tran Speed: %d\n", mmc->tran_speed);
> + printf("Rd Block Len: %d\n", mmc->read_bl_len);
> +
> + printf("%s version %d.%d\n", IS_SD(mmc) ? "SD" : "MMC",
> + (mmc->version >> 4) & 0xf, mmc->version & 0xf);
> +
> + printf("High Capacity: %s\n", mmc->high_capacity ? "Yes" : "No");
> + printf("Capacity: %lld\n", mmc->capacity);
> +
> + printf("Bus Width: %d-bit\n", mmc->bus_width);
> +}
> +
> +int do_mmcinfo (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
> +{
> + struct mmc *mmc;
> + int dev_num;
> +
> + if (argc < 2)
> + dev_num = 0;
> + else
> + dev_num = simple_strtoul(argv[1], NULL, 0);
> +
> + mmc = find_mmc_device(dev_num);
> +
> + if (mmc) {
> + mmc_init(mmc);
> +
> + print_mmcinfo(mmc);
> + }
> +
> + return 0;
> +}
> +
> +U_BOOT_CMD(mmcinfo, 2, 0, do_mmcinfo, "mmcinfo <dev num>-- display MMC info\n",
> + NULL);
> +
> +int do_mmcops(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
> +{
> + int rc = 0;
> +
> + switch (argc) {
> + case 0:
> + case 1:
> + case 3:
> + case 4:
> + printf("Usage:\n%s\n", cmdtp->usage);
> + return 1;
> +
> + case 2:
> + if (!strcmp(argv[1], "list")) {
> + print_mmc_devices('\n');
> + return 0;
> + }
> + return 1;
> + default: /* at least 5 args */
> + if (strcmp(argv[1], "read") == 0) {
> + int dev = simple_strtoul(argv[2], NULL, 10);
> + void *addr = (void *)simple_strtoul(argv[3], NULL, 16);
> + u32 cnt = simple_strtoul(argv[5], NULL, 16);
> + u32 n;
> + u32 blk = simple_strtoul(argv[4], NULL, 16);
> + struct mmc *mmc = find_mmc_device(dev);
> +
> + printf("\nMMC read: dev # %d, block # %d, count %d ... ",
> + dev, blk, cnt);
> +
> + mmc_init(mmc);
> +
> + n = mmc->block_dev.block_read(dev, blk, cnt, addr);
> +
> + /* flush cache after read */
> + flush_cache((ulong)addr, cnt * 512); //FIXME
> +
> + printf("%d blocks read: %s\n",
> + n, (n==cnt) ? "OK" : "ERROR");
> + return (n == cnt) ? 0 : 1;
> + } else if (strcmp(argv[1], "write") == 0) {
> + int dev = simple_strtoul(argv[2], NULL, 10);
> + void *addr = (void *)simple_strtoul(argv[3], NULL, 16);
> + u32 cnt = simple_strtoul(argv[5], NULL, 16);
> + u32 n;
> + struct mmc *mmc = find_mmc_device(dev);
> +
> + int blk = simple_strtoul(argv[4], NULL, 16);
> +
> + printf("\nMMC write: dev # %d, block # %d, count %d ... ",
> + dev, blk, cnt);
> +
> + mmc_init(mmc);
> +
> + n = mmc->block_dev.block_write(dev, blk, cnt, addr);
> +
> + printf("%d blocks written: %s\n",
> + n, (n == cnt) ? "OK" : "ERROR");
> + return (n == cnt) ? 0 : 1;
> + } else {
> + printf("Usage:\n%s\n", cmdtp->usage);
> + rc = 1;
> + }
Is there any way to rescan the bus like the old "mmcinit" command did?
This can be useful if you need to change the card.
> + return rc;
> + }
> +}
> +
> +U_BOOT_CMD(
> + mmc, 6, 1, do_mmcops,
> + "mmc - MMC sub system\n",
> + "mmc read <device num> addr blk# cnt\n"
> + "mmc write <device num> addr blk# cnt\n"
> + "mmc list - lists available devices\n");
> diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
> index 3dc031b..d5a969c 100644
> --- a/drivers/mmc/Makefile
> +++ b/drivers/mmc/Makefile
> @@ -25,6 +25,7 @@ include $(TOPDIR)/config.mk
>
> LIB := $(obj)libmmc.a
>
> +COBJS-$(CONFIG_GENERIC_MMC) += mmc.o
> COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o
>
> COBJS := $(COBJS-y)
> diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
> new file mode 100644
> index 0000000..cd4eb77
> --- /dev/null
> +++ b/drivers/mmc/mmc.c
> @@ -0,0 +1,926 @@
> +/*
> + * Copyright 2008, Freescale Semiconductor, Inc
> + * Andy Fleming
> + *
> + * Based vaguely on the Linux code
> + *
> + * See file CREDITS for list of people who contributed to this
> + * project.
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
> + * MA 02111-1307 USA
> + */
> +
> +#include <config.h>
> +#include <common.h>
> +#include <command.h>
> +#include <mmc.h>
> +#include <part.h>
> +#include <malloc.h>
> +#include <mmc.h>
> +
> +static struct mmc *mmc_devices;
Maybe a struct list_head would be more appropriate here?
> +static int cur_dev_num = -1;
> +
> +int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
> +{
> + return mmc->send_cmd(mmc, cmd, data);
> +}
> +
> +int mmc_set_blocklen(struct mmc *mmc, int len)
> +{
> + struct mmc_cmd cmd;
> +
> + cmd.cmdidx = MMC_CMD_SET_BLOCKLEN;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = len;
> + cmd.flags = 0;
> +
> + return mmc_send_cmd(mmc, &cmd, NULL);
> +}
> +
> +struct mmc *find_mmc_device(int dev_num)
> +{
> + struct mmc *m = mmc_devices;
> +
> + if (!m) {
> + printf("No MMC devices registered!\n");
> + return NULL;
> + }
> +
> + do {
> + if (m->block_dev.dev == dev_num)
> + return m;
> +
> + m = m->next;
> + } while (m->next != mmc_devices);
> +
> + printf("MMC Device %d not found\n", dev_num);
> +
> + return NULL;
> +}
> +
> +static ulong
> +mmc_bwrite(int dev_num, ulong start, lbaint_t blkcnt, const void*src)
> +{
> + struct mmc_cmd cmd;
> + struct mmc_data data;
> + int err;
> + int stoperr = 0;
> + struct mmc *mmc = find_mmc_device(dev_num);
> + int blklen;
> +
> + if (!mmc)
> + return -1;
> +
> + blklen = mmc->write_bl_len;
> +
> + err = mmc_set_blocklen(mmc, mmc->write_bl_len);
> +
> + if (err) {
> + printf("set write bl len failed\n\r");
> + return err;
> + }
> +
> + if (blkcnt > 1)
> + cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK;
> + else
> + cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK;
> +
> + if (mmc->high_capacity)
> + cmd.cmdarg = start;
> + else
> + cmd.cmdarg = start * blklen;
> +
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.flags = 0;
> +
> + data.src = src;
> + data.blocks = blkcnt;
> + data.blocksize = blklen;
> + data.flags = MMC_DATA_WRITE;
> +
> + err = mmc_send_cmd(mmc, &cmd, &data);
> +
> + if (err) {
> + printf("mmc write failed\n\r");
> + return err;
> + }
> +
> + if (blkcnt > 1) {
> + cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
> + cmd.cmdarg = 0;
> + cmd.resp_type = MMC_CMD_R1b;
> + cmd.flags = 0;
> + stoperr = mmc_send_cmd(mmc, &cmd, NULL);
> + }
> +
> + return blkcnt;
> +}
> +
> +int mmc_read_block(struct mmc *mmc, void *dst, uint blocknum)
> +{
> + struct mmc_cmd cmd;
> + struct mmc_data data;
> +
> + cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK;
> +
> + if (mmc->high_capacity)
> + cmd.cmdarg = blocknum;
> + else
> + cmd.cmdarg = blocknum * mmc->read_bl_len;
> +
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.flags = 0;
> +
> + data.dest = dst;
> + data.blocks = 1;
> + data.blocksize = mmc->read_bl_len;
> + data.flags = MMC_DATA_READ;
> +
> + return mmc_send_cmd(mmc, &cmd, &data);
> +}
> +
> +int mmc_read(struct mmc *mmc, ulong src, uchar *dst, int size)
> +{
> + char *buffer;
> + int i;
> + int blklen = mmc->read_bl_len;
> + int startblock = src / blklen;
> + int endblock = (src + size - 1) / blklen;
> + int err = 0;
> +
> + /* Make a buffer big enough to hold all the blocks we might read */
> + buffer = malloc(blklen);
> +
> + if (!buffer) {
> + printf("Could not allocate buffer for MMC read!\n");
> + return -1;
> + }
> +
> + /* We always do full block reads from the card */
> + err = mmc_set_blocklen(mmc, mmc->read_bl_len);
> +
> + if (err)
> + return err;
> +
> + for (i = startblock; i <= endblock; i++) {
> + int segment_size;
> + int offset;
> +
> + err = mmc_read_block(mmc, buffer, i);
> +
> + if (err)
> + goto free_buffer;
> +
> + /*
> + * The first block may not be aligned, so we
> + * copy from the desired point in the block
> + */
> + offset = (src & (blklen - 1));
> + segment_size = MIN(blklen - offset, size);
> +
> + memcpy(dst, buffer + offset, segment_size);
> +
> + dst += segment_size;
> + src += segment_size;
> + size -= segment_size;
> + }
> +
> +free_buffer:
> + free(buffer);
> +
> + return err;
> +}
> +
> +static ulong mmc_bread(int dev_num, ulong start, lbaint_t blkcnt, void *dst)
> +{
> + int err;
> + ulong src;
> + struct mmc *mmc = find_mmc_device(dev_num);
> +
> + if (!mmc)
> + return 0;
> +
> + src = start * mmc->read_bl_len;
> +
> + err = mmc_read(mmc, src, dst, blkcnt * mmc->read_bl_len);
Hmm...here you multiply to get a byte offset, then mmc_read does a
division to get back to the block numbers. Wouldn't it be better to do
it the other way around and let mmc_read() call mmc_bread()? It would
also avoid the bounce buffer allocation and unaligned block handling.
I also think this will overflow on SDHC since src is a 32-bit quantity
on most platforms, and SDHC cards can be larger than 4 GiB.
> + if (err) {
> + printf("block read failed: %d\n", err);
> + return 0;
> + }
> +
> + return blkcnt;
> +}
> +
> +int mmc_go_idle(struct mmc* mmc)
> +{
> + struct mmc_cmd cmd;
> + int err;
> +
> + udelay(1000);
> +
> + cmd.cmdidx = MMC_CMD_GO_IDLE_STATE;
> + cmd.cmdarg = 0;
> + cmd.resp_type = MMC_CMD_RSP_NONE;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + udelay(2000);
> +
> + return 0;
> +}
> +
> +int
> +sd_send_op_cond(struct mmc *mmc)
> +{
> + int timeout = 1000;
> + int err;
> + struct mmc_cmd cmd;
> +
> + do {
> + cmd.cmdidx = MMC_CMD_APP_CMD;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = 0;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + cmd.cmdidx = SD_CMD_APP_SEND_OP_COND;
> + cmd.resp_type = MMC_CMD_R3;
> + cmd.cmdarg = 0xff8000;
Hmm...hardcoded voltages? Better let the board define that...
> + if (mmc->version == SD_VERSION_2)
> + cmd.cmdarg |= 0x40000000;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + udelay(1000);
> + } while ((!(cmd.response[0] & OCR_BUSY)) && timeout--);
> +
> + if (timeout <= 0)
> + return UNUSABLE_ERR;
> +
> + if (mmc->version != SD_VERSION_2)
> + mmc->version = SD_VERSION_1_0;
> +
> + mmc->ocr = ((uint *)(cmd.response))[0];
> +
> + mmc->high_capacity = ((mmc->ocr & OCR_HCS) == OCR_HCS);
> + mmc->rca = 0;
> +
> + return 0;
> +}
> +
> +int mmc_send_op_cond(struct mmc *mmc)
> +{
> + int timeout = 1000;
> + struct mmc_cmd cmd;
> + int err;
> +
> + /* Some cards seem to need this */
> + mmc_go_idle(mmc);
> +
> + do {
> + cmd.cmdidx = MMC_CMD_SEND_OP_COND;
> + cmd.resp_type = MMC_CMD_R3;
> + cmd.cmdarg = 0x40ff8000;
Same here. Would be nice if this could be defined in terms of a few
constants too...it's hard to tell exactly what voltages you're
announcing here...
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + udelay(1000);
> + } while (!(cmd.response[0] & OCR_BUSY) && timeout--);
> +
> + if (timeout <= 0)
> + return UNUSABLE_ERR;
> +
> + mmc->version = MMC_VERSION_UNKNOWN;
> + mmc->ocr = ((uint *)(cmd.response))[0];
> +
> + mmc->high_capacity = ((mmc->ocr & OCR_HCS) == OCR_HCS);
> + mmc->rca = 0;
> +
> + return 0;
> +}
> +
> +
> +int mmc_send_ext_csd(struct mmc *mmc, char *ext_csd)
> +{
> + struct mmc_cmd cmd;
> + struct mmc_data data;
> + int err;
> +
> + /* Get the Card Status Register */
> + cmd.cmdidx = MMC_CMD_SEND_EXT_CSD;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = 0;
> + cmd.flags = 0;
> +
> + data.dest = ext_csd;
> + data.blocks = 1;
> + data.blocksize = 512;
> + data.flags = MMC_DATA_READ;
> +
> + err = mmc_send_cmd(mmc, &cmd, &data);
> +
> + return err;
> +}
> +
> +
> +int mmc_change_freq(struct mmc *mmc)
> +{
> + struct mmc_cmd cmd;
> + char ext_csd[512];
> + char cardtype;
> + int err;
> +
> + mmc->caps = 0;
> +
> + /* Only version 4 supports high-speed */
> + if (mmc->version < MMC_VERSION_4)
> + return 0;
Hmm...but mmc_change_freq() doesn't sound like it has anything to do
with high-speed...?
> + mmc->caps |= MMC_MODE_4BIT;
I though the host was supposed to set that. It may even depend on how
the board is wired.
> + err = mmc_send_ext_csd(mmc, ext_csd);
> +
> + if (err)
> + return err;
> +
> + if (ext_csd[212] || ext_csd[213] || ext_csd[214] || ext_csd[215])
> + mmc->high_capacity = 1;
> +
> + cardtype = ext_csd[196] & 0xf;
> +
> + /* Set the card to use High Speed */
> + cmd.cmdidx = MMC_CMD_SWITCH;
> + cmd.resp_type = MMC_CMD_R1b;
> + cmd.cmdarg = 0x1b90100;
Some constant(s) would be nice...
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + /* Now check to see that it worked */
> + err = mmc_send_ext_csd(mmc, ext_csd);
> +
> + if (err)
> + return err;
> +
> + /* No high-speed support */
> + if (!ext_csd[185])
> + return 0;
> +
> + /* High Speed is set, there are two types: 52MHz and 26MHz */
> + if (cardtype & MMC_HS_52MHZ)
> + mmc->caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
> + else
> + mmc->caps |= MMC_MODE_HS;
How do you know the host supports high-speed? How does the host driver
know when to switch into high-speed mode?
> + return 0;
> +}
> +
> +int sd_change_freq(struct mmc *mmc)
> +{
> + int err;
> + struct mmc_cmd cmd;
> + uint scr[2];
> + uint switch_status[16];
> + struct mmc_data data;
> + int timeout;
> +
> + mmc->caps = 0;
> +
> + /* Read the SCR to find out if this card supports higher speeds */
> + cmd.cmdidx = MMC_CMD_APP_CMD;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = mmc->rca << 16;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + cmd.cmdidx = SD_CMD_APP_SEND_SCR;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = 0;
> + cmd.flags = 0;
> +
> + timeout = 3;
> +
> +retry_scr:
> + data.dest = (char *)&scr;
> + data.blocksize = 8;
> + data.blocks = 1;
> + data.flags = MMC_DATA_READ;
> +
> + err = mmc_send_cmd(mmc, &cmd, &data);
> +
> + if (err) {
> + if (timeout--)
> + goto retry_scr;
> +
> + return err;
> + }
> +
> + mmc->scr[0] = scr[0];
> + mmc->scr[1] = scr[1];
> +
> + switch ((mmc->scr[0] >> 24) & 0xf) {
> + case 0:
> + mmc->version = SD_VERSION_1_0;
> + break;
> + case 1:
> + mmc->version = SD_VERSION_1_10;
> + break;
> + case 2:
> + mmc->version = SD_VERSION_2;
> + break;
> + default:
> + mmc->version = SD_VERSION_1_0;
> + break;
> + }
> +
> + /* Version 1.0 doesn't support switching */
> + if (mmc->version == SD_VERSION_1_0)
> + return 0;
> +
> + timeout = 4;
> + while (timeout--) {
> + /* Switch the frequency */
> + cmd.cmdidx = SD_CMD_SWITCH_FUNC;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = 0xfffff1;
> + cmd.flags = 0;
> +
> + data.dest = (char *)&switch_status;
> + data.blocksize = 64;
> + data.blocks = 1;
> + data.flags = MMC_DATA_READ;
> +
> + err = mmc_send_cmd(mmc, &cmd, &data);
> +
> + if (err)
> + return err;
> +
> + /* The high-speed function is busy. Try again */
> + if (!switch_status[7] & SD_HIGHSPEED_BUSY)
> + break;
> + }
> +
> + if (mmc->scr[0] & SD_DATA_4BIT)
> + mmc->caps |= MMC_MODE_4BIT;
Again, we don't know if the host supports this.
> + /* If high-speed isn't supported, we return */
> + if (!(switch_status[3] & SD_HIGHSPEED_SUPPORTED))
> + return 0;
> +
> + cmd.cmdidx = SD_CMD_SWITCH_FUNC;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = 0x80fffff1;
Constants please.
> + cmd.flags = 0;
> +
> + data.dest = (char *)&switch_status;
> + data.blocksize = 64;
> + data.blocks = 1;
> + data.flags = MMC_DATA_READ;
> +
> + err = mmc_send_cmd(mmc, &cmd, &data);
> +
> + if (err)
> + return err;
> +
> + if ((switch_status[4] & 0x0f000000) == 0x01000000)
> + mmc->caps |= MMC_MODE_HS;
And we overrule the host again. Note that the host may support any
combination of 1-bit, 4-bit, 8-bit busses and normal/high-speed
signaling, and it needs to be told when to switch between them.
> + return 0;
> +}
> +
> +/* frequency bases */
> +/* divided by 10 to be nice to platforms without floating point */
> +int fbase[] = {
> + 10000,
> + 100000,
> + 1000000,
> + 10000000,
> +};
> +
> +/* Multiplier values for TRAN_SPEED. Multiplied by 10 to be nice
> + * to platforms without floating point.
> + */
> +int multipliers[] = {
> + 0, /* reserved */
> + 10,
> + 12,
> + 13,
> + 15,
> + 20,
> + 25,
> + 30,
> + 35,
> + 40,
> + 45,
> + 50,
> + 55,
> + 60,
> + 70,
> + 80,
> +};
> +
> +void mmc_set_ios(struct mmc *mmc)
> +{
> + mmc->set_ios(mmc);
> +}
> +
> +void mmc_set_clock(struct mmc *mmc, uint clock)
> +{
> + mmc->clock = clock;
> +
> + mmc_set_ios(mmc);
> +}
> +
> +void mmc_set_bus_width(struct mmc *mmc, uint width)
> +{
> + mmc->bus_width = width;
> +
> + mmc_set_ios(mmc);
> +}
> +
> +int mmc_startup(struct mmc *mmc)
> +{
> + int err;
> + uint mult, freq;
> + u64 cmult, csize;
> + struct mmc_cmd cmd;
> +
> + /* Put the Card in Identify Mode */
> + cmd.cmdidx = MMC_CMD_ALL_SEND_CID;
> + cmd.resp_type = MMC_CMD_R2;
> + cmd.cmdarg = 0;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + memcpy(mmc->cid, cmd.response, 16);
> +
> + /*
> + * For MMC cards, set the Relative Address.
> + * For SD cards, get the Relatvie Address.
> + * This also puts the cards into Standby State
> + */
> + cmd.cmdidx = SD_CMD_SEND_RELATIVE_ADDR;
> + cmd.cmdarg = mmc->rca << 16;
> + cmd.resp_type = MMC_CMD_R6;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + if (IS_SD(mmc))
> + mmc->rca = (((uint *)(cmd.response))[0] >> 16) & 0xffff;
> +
> + /* Get the Card-Specific Data */
> + cmd.cmdidx = MMC_CMD_SEND_CSD;
> + cmd.resp_type = MMC_CMD_R2;
> + cmd.cmdarg = mmc->rca << 16;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + mmc->csd[0] = ((uint *)(cmd.response))[0];
> + mmc->csd[1] = ((uint *)(cmd.response))[1];
> + mmc->csd[2] = ((uint *)(cmd.response))[2];
> + mmc->csd[3] = ((uint *)(cmd.response))[3];
> +
> + if (mmc->version == MMC_VERSION_UNKNOWN) {
> + int version = (cmd.response[0] >> 2) & 0xf;
> +
> + switch (version) {
> + case 0:
> + mmc->version = MMC_VERSION_1_2;
> + break;
> + case 1:
> + mmc->version = MMC_VERSION_1_4;
> + break;
> + case 2:
> + mmc->version = MMC_VERSION_2_2;
> + break;
> + case 3:
> + mmc->version = MMC_VERSION_3;
> + break;
> + case 4:
> + mmc->version = MMC_VERSION_4;
> + break;
> + default:
> + mmc->version = MMC_VERSION_1_2;
> + break;
> + }
> + }
> +
> + /* divide frequency by 10, since the mults are 10x bigger */
> + freq = fbase[(cmd.response[3] & 0x7)];
> + mult = multipliers[((cmd.response[3] >> 3) & 0xf)];
> +
> + mmc->tran_speed = freq * mult;
> +
> + mmc->read_bl_len = 1 << ((((uint *)(cmd.response))[1] >> 16) & 0xf);
> +
> + if (IS_SD(mmc))
> + mmc->write_bl_len = mmc->read_bl_len;
> + else
> + mmc->write_bl_len = 1 << ((((uint *)(cmd.response))[3] >> 22) & 0xf);
> +
> + if (mmc->high_capacity) {
> + csize = (mmc->csd[1] & 0x3f) << 16
> + | (mmc->csd[2] & 0xffff0000) >> 16;
> + cmult = 8;
> + } else {
> + csize = (mmc->csd[1] & 0x3ff) << 2
> + | (mmc->csd[2] & 0xc0000000) >> 30;
> + cmult = (mmc->csd[2] & 0x00038000) >> 15;
> + }
> +
> + mmc->capacity = (csize + 1) << (cmult + 2);
> + mmc->capacity *= mmc->read_bl_len;
64-bit multiply...hopefully all platforms are fine with that.
> + if (mmc->read_bl_len > 512)
> + mmc->read_bl_len = 512;
> +
> + if (mmc->write_bl_len > 512)
> + mmc->write_bl_len = 512;
> +
> + /* Select the card, and put it into Transfer Mode */
> + cmd.cmdidx = MMC_CMD_SELECT_CARD;
> + cmd.resp_type = MMC_CMD_R1b;
> + cmd.cmdarg = mmc->rca << 16;
> + cmd.flags = 0;
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + if (IS_SD(mmc))
> + err = sd_change_freq(mmc);
> + else
> + err = mmc_change_freq(mmc);
> +
> + if (err)
> + return err;
> +
> + if (IS_SD(mmc)) {
> + if (mmc->caps & MMC_MODE_4BIT) {
> + cmd.cmdidx = MMC_CMD_APP_CMD;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = mmc->rca << 16;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> + if (err)
> + return err;
> +
> + cmd.cmdidx = SD_CMD_APP_SET_BUS_WIDTH;
> + cmd.resp_type = MMC_CMD_R1;
> + cmd.cmdarg = 2;
> + cmd.flags = 0;
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> + if (err)
> + return err;
> +
> + mmc_set_bus_width(mmc, 4);
> + }
> +
> + if (mmc->caps & MMC_MODE_HS)
> + mmc_set_clock(mmc, 12000000);
> + else
> + mmc_set_clock(mmc, 12000000);
Surely high-speed capable cards can do more than 12 MHz? And where are
the host/board/card-specific frequency limits taken into account?
> + } else {
> + if (mmc->caps & MMC_MODE_4BIT) {
> + /* Set the card to use 4 bit*/
> + cmd.cmdidx = MMC_CMD_SWITCH;
> + cmd.resp_type = MMC_CMD_R1b;
> + cmd.cmdarg = 0x1b70100;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + mmc_set_bus_width(mmc, 4);
> + } else if (mmc->caps & MMC_MODE_8BIT) {
> + /* Set the card to use 8 bit*/
> + cmd.cmdidx = MMC_CMD_SWITCH;
> + cmd.resp_type = MMC_CMD_R1b;
> + cmd.cmdarg = 0x1b70200;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + mmc_set_bus_width(mmc, 8);
> + }
> +
> + if (mmc->caps & MMC_MODE_HS) {
> + if (mmc->caps & MMC_MODE_HS_52MHz)
> + mmc_set_clock(mmc, 52000000);
> + else
> + mmc_set_clock(mmc, 26000000);
> + } else
> + mmc_set_clock(mmc, 20000000);
Same here. You calculated tran_speed above, why don't you use it?
> + }
> +
> + /* fill in device description */
> + mmc->block_dev.if_type = IF_TYPE_MMC;
> + mmc->block_dev.part_type = PART_TYPE_DOS;
> + mmc->block_dev.dev = cur_dev_num++;
> + mmc->block_dev.lun = 0;
> + mmc->block_dev.type = 0;
> + mmc->block_dev.blksz = mmc->read_bl_len;
> + mmc->block_dev.lba = mmc->capacity/mmc->read_bl_len;
> + sprintf(mmc->block_dev.vendor,"Man %02x%02x%02x Snr %02x%02x%02x%02x",
> + mmc->cid[0], mmc->cid[1], mmc->cid[2],
> + mmc->cid[9], mmc->cid[10], mmc->cid[11], mmc->cid[12]);
> + sprintf(mmc->block_dev.product,"%c%c%c%c%c", mmc->cid[3],
> + mmc->cid[4], mmc->cid[5], mmc->cid[6], mmc->cid[7]);
> + sprintf(mmc->block_dev.revision,"%d.%d", mmc->cid[8] >> 4,
> + mmc->cid[8] & 0xf);
> + mmc->block_dev.removable = 1;
> + mmc->block_dev.block_read = mmc_bread;
> + mmc->block_dev.block_write = mmc_bwrite;
> +
> + init_part(&mmc->block_dev);
> +
> + return 0;
> +}
> +
> +int mmc_send_if_cond(struct mmc *mmc)
> +{
> + struct mmc_cmd cmd;
> + int err;
> +
> + cmd.cmdidx = SD_CMD_SEND_IF_COND;
> + cmd.cmdarg = 0x1aa;
Constant(s) please. Should this be board-dependent?
> + cmd.resp_type = MMC_CMD_R7;
> + cmd.flags = 0;
> +
> + err = mmc_send_cmd(mmc, &cmd, NULL);
> +
> + if (err)
> + return err;
> +
> + if (((uint *)(cmd.response))[0] != 0x1aa)
Ditto.
> + return UNUSABLE_ERR;
> + else
> + mmc->version = SD_VERSION_2;
> +
> + return 0;
> +}
> +
> +int mmc_register(struct mmc *mmc)
> +{
> + struct mmc *m;
> +
> + if (!mmc_devices)
> + mmc_devices = mmc;
> + else {
> + for (m = mmc_devices; m->next != mmc_devices; m=m->next);
> + m->next = mmc;
> + }
> +
> + mmc->next = mmc_devices;
A list_head would make this so much simpler:
list_add_tail(&mmc->node, &mmc_devices);
> + return 0;
> +}
> +
> +block_dev_desc_t *mmc_get_dev(int dev)
> +{
> + struct mmc *mmc = find_mmc_device(dev);
> +
> + return &mmc->block_dev;
> +}
> +
> +int mmc_init(struct mmc *mmc)
> +{
> + int err;
> +
> + err = mmc->init(mmc);
> +
> + if (err)
> + return err;
> +
> + /* Reset the Card */
> + err = mmc_go_idle(mmc);
> +
> + if (err)
> + return err;
> +
> + /* Test for SD version 2 */
> + err = mmc_send_if_cond(mmc);
> +
> + /* If we got an error other than timeout, we bail */
> + if (err && err != TIMEOUT)
> + return err;
> +
> + /* Now try to get the SD card's operating condition */
> + err = sd_send_op_cond(mmc);
> +
> + /* If the command timed out, we check for an MMC card */
> + if (err == TIMEOUT) {
> + err = mmc_send_op_cond(mmc);
> +
> + if (err) {
> + printf("Card did not respond to voltage select!\n");
> + return UNUSABLE_ERR;
> + }
> + }
> +
> + err = mmc_startup(mmc);
> +
> + if (!err)
> + mmc->initialized = 1;
> +
> + return err;
> +}
> +
> +/*
> + * CPU and board-specific MMC initializations. Aliased function
> + * signals caller to move on
> + */
> +static int __def_mmc_init(bd_t *bis)
> +{
> + return -1;
> +}
> +
> +int cpu_mmc_init(bd_t *bis) __attribute((weak, alias("__def_mmc_init")));
> +int board_mmc_init(bd_t *bis) __attribute((weak, alias("__def_mmc_init")));
> +
> +void print_mmc_devices(char separator)
> +{
> + struct mmc *m = mmc_devices;
> +
> + do {
> + if (m != mmc_devices)
> + printf("%c ", separator);
> +
> + printf("%s: %d", m->name, m->block_dev.dev);
> + m = m->next;
> + } while (m->next != mmc_devices);
> +
> + printf("\n");
> +}
> +
> +int mmc_initialize(bd_t *bis)
> +{
> + mmc_devices = NULL;
> + cur_dev_num = 0;
> +
> + if (board_mmc_init(bis) < 0)
> + cpu_mmc_init(bis);
> +
> + if (!mmc_devices)
> + puts ("No MMC devices found.\n");
> + else
> + print_mmc_devices(',');
> +
> + return 0;
> +}
> diff --git a/include/mmc.h b/include/mmc.h
> index 7a03ed2..aed449c 100644
> --- a/include/mmc.h
> +++ b/include/mmc.h
> @@ -1,6 +1,8 @@
> /*
> - * (C) Copyright 2000-2003
> - * Wolfgang Denk, DENX Software Engineering, wd at denx.de.
> + * Copyright 2008, Freescale Semiconductor, Inc
> + * Andy Fleming
> + *
> + * Based (loosely) on the Linux code
> *
> * See file CREDITS for list of people who contributed to this
> * project.
> @@ -24,32 +26,186 @@
> #ifndef _MMC_H_
> #define _MMC_H_
>
> -/* MMC command numbers */
> +#define SD_VERSION_SD 0x20000
> +#define SD_VERSION_2 (SD_VERSION_SD | 0x20)
> +#define SD_VERSION_1_0 (SD_VERSION_SD | 0x10)
> +#define SD_VERSION_1_10 (SD_VERSION_SD | 0x1a)
> +#define MMC_VERSION_MMC 0x10000
> +#define MMC_VERSION_UNKNOWN (MMC_VERSION_MMC)
> +#define MMC_VERSION_1_2 (MMC_VERSION_MMC | 0x12)
> +#define MMC_VERSION_1_4 (MMC_VERSION_MMC | 0x14)
> +#define MMC_VERSION_2_2 (MMC_VERSION_MMC | 0x22)
> +#define MMC_VERSION_3 (MMC_VERSION_MMC | 0x30)
> +#define MMC_VERSION_4 (MMC_VERSION_MMC | 0x40)
> +
> +#define MMC_MODE_HS 0x001
> +#define MMC_MODE_HS_52MHz 0x010
> +#define MMC_MODE_4BIT 0x100
> +#define MMC_MODE_8BIT 0x200
> +
> +#define SD_DATA_4BIT 0x00040000
> +
> +#define IS_SD(x) (mmc->version & SD_VERSION_SD)
> +
> +#define MMC_DATA_READ 1
> +#define MMC_DATA_WRITE 2
> +
> +#define NO_CARD_ERR -16 /* No SD/MMC card inserted */
> +#define UNUSABLE_ERR -17 /* Unusable Card */
> +#define COMM_ERR -18 /* Communications Error */
> +#define TIMEOUT -19
> +
> #define MMC_CMD_GO_IDLE_STATE 0
> #define MMC_CMD_SEND_OP_COND 1
> #define MMC_CMD_ALL_SEND_CID 2
> #define MMC_CMD_SET_RELATIVE_ADDR 3
> #define MMC_CMD_SET_DSR 4
> +#define MMC_CMD_SWITCH 6
> #define MMC_CMD_SELECT_CARD 7
> +#define MMC_CMD_SEND_EXT_CSD 8
> #define MMC_CMD_SEND_CSD 9
> #define MMC_CMD_SEND_CID 10
> +#define MMC_CMD_STOP_TRANSMISSION 12
> #define MMC_CMD_SEND_STATUS 13
> #define MMC_CMD_SET_BLOCKLEN 16
> #define MMC_CMD_READ_SINGLE_BLOCK 17
> #define MMC_CMD_READ_MULTIPLE_BLOCK 18
> -#define MMC_CMD_WRITE_BLOCK 24
> +#define MMC_CMD_WRITE_SINGLE_BLOCK 24
> +#define MMC_CMD_WRITE_MULTIPLE_BLOCK 25
> #define MMC_CMD_APP_CMD 55
>
> -/* SD Card command numbers */
> #define SD_CMD_SEND_RELATIVE_ADDR 3
> -#define SD_CMD_SWITCH 6
> +#define SD_CMD_SWITCH_FUNC 6
> #define SD_CMD_SEND_IF_COND 8
>
> #define SD_CMD_APP_SET_BUS_WIDTH 6
> #define SD_CMD_APP_SEND_OP_COND 41
> +#define SD_CMD_APP_SEND_SCR 51
>
> -int mmc_legacy_init(int verbose);
> -int mmc_read(ulong src, uchar *dst, int size);
> -int mmc_write(uchar *src, ulong dst, int size);
> +#define SD_HIGHSPEED_BUSY 0x00020000
> +#define SD_HIGHSPEED_SUPPORTED 0x00020000
Aren't those identical?
> +#define MMC_HS_TIMING 0x00000100
> +#define MMC_HS_52MHZ 0x2
> +
> +#define OCR_BUSY 0x80
> +#define OCR_HCS 0x40000000
>
> +#define R1_ILLEGAL_COMMAND (1 << 22)
> +#define R1_APP_CMD (1 << 5)
> +
> +enum {
> + MMC_CMD_RSP_NONE,
> + MMC_CMD_R1 = 1,
> + MMC_CMD_R1b,
> + MMC_CMD_R2,
> + MMC_CMD_R3,
> + MMC_CMD_R4,
> + MMC_CMD_R5,
> + MMC_CMD_R5b,
> + MMC_CMD_R6,
> + MMC_CMD_R7
Please define these in terms of sensible constants which make it easy to
deduce the length of the reply, whether the CRC is expected to be
valid, whether the card may signal busy, etc. The following flags seem
to do the trick on Linux:
#define MMC_RSP_PRESENT (1 << 0)
#define MMC_RSP_136 (1 << 1) /* 136 bit response */
#define MMC_RSP_CRC (1 << 2) /* expect valid crc */
#define MMC_RSP_BUSY (1 << 3) /* card may send busy */
#define MMC_RSP_OPCODE (1 << 4) /* response contains opcode */
which becomes
#define MMC_RSP_NONE (0)
#define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY)
#define MMC_RSP_R2 (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC)
#define MMC_RSP_R3 (MMC_RSP_PRESENT)
#define MMC_RSP_R4 (MMC_RSP_PRESENT)
#define MMC_RSP_R5 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R6 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R7 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
> +};
> +
> +struct mmc_cid {
> + unsigned long psn;
> + unsigned short oid;
> + unsigned char mid;
> + unsigned char prv;
> + unsigned char mdt;
> + char pnm[7];
> +};
> +
> +struct mmc_csd
> +{
> + u8 csd_structure:2,
> + spec_vers:4,
> + rsvd1:2;
> + u8 taac;
> + u8 nsac;
> + u8 tran_speed;
> + u16 ccc:12,
> + read_bl_len:4;
> + u64 read_bl_partial:1,
> + write_blk_misalign:1,
> + read_blk_misalign:1,
> + dsr_imp:1,
> + rsvd2:2,
> + c_size:12,
> + vdd_r_curr_min:3,
> + vdd_r_curr_max:3,
> + vdd_w_curr_min:3,
> + vdd_w_curr_max:3,
> + c_size_mult:3,
> + sector_size:5,
> + erase_grp_size:5,
> + wp_grp_size:5,
> + wp_grp_enable:1,
> + default_ecc:2,
> + r2w_factor:3,
> + write_bl_len:4,
> + write_bl_partial:1,
> + rsvd3:5;
> + u8 file_format_grp:1,
> + copy:1,
> + perm_write_protect:1,
> + tmp_write_protect:1,
> + file_format:2,
> + ecc:2;
> + u8 crc:7;
> + u8 one:1;
> +};
Are these the same in all versions of the SD and MMC specs?
> +struct mmc_cmd {
> + ushort cmdidx;
> + int resp_type;
Should be unsigned.
> + uint cmdarg;
> + char response[18];
Hmm...why 18 bytes? Linux only needs 4 32-bit words.
> + uint flags;
> +};
> +
> +struct mmc_data {
> + char *dest;
> + const char *src; /* src buffers don't get written to */
But you never use both at the same time. Why two pointers?
> + uint flags;
> + int blocks;
> + int blocksize;
Do these have to be signed?
> +};
> +
> +struct mmc {
> + char name[32];
> + void *priv;
> + uint initialized;
> + uint version;
> + int high_capacity;
There are a few 32-bit fields which contain 1 bit of information. Maybe
they can be combined into some kind of "flags" field?
> + uint bus_width;
> + uint clock;
> + uint caps;
Caps for whom? The host or the card?
> + uint ocr;
> + uint scr[2];
> + uint csd[4];
> + char cid[16];
> + ushort rca;
> + uint tran_speed;
It's not entirely clear how this is different from "clock". Ok, since I
just went through the code, I happen to know the difference, but I'm
sure others will be confused.
> + uint read_bl_len;
> + uint write_bl_len;
> + u64 capacity;
> + block_dev_desc_t block_dev;
> + int (*send_cmd)(struct mmc *mmc,
> + struct mmc_cmd *cmd, struct mmc_data *data);
> + void (*set_ios)(struct mmc *mmc);
> + int (*init)(struct mmc *mmc);
> + struct mmc *next;
Some documentation would be nice. Maybe it would be a good idea to
separate the host-specific bits from the card-specific bits too?
> +};
> +
> +int mmc_register(struct mmc *mmc);
> +int mmc_initialize(bd_t *bis);
> +int mmc_init(struct mmc *mmc);
> +int mmc_read(struct mmc *mmc, ulong src, uchar *dst, int size);
> +struct mmc *find_mmc_device(int dev_num);
> +void print_mmc_devices(char separator);
These are not the only non-static functions you define:
+int do_mmcinfo (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
+int do_mmcops(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
+int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
+int mmc_set_blocklen(struct mmc *mmc, int len)
+int mmc_read_block(struct mmc *mmc, void *dst, uint blocknum)
+int mmc_go_idle(struct mmc* mmc)
+int
+sd_send_op_cond(struct mmc *mmc)
+int mmc_send_op_cond(struct mmc *mmc)
+int mmc_send_ext_csd(struct mmc *mmc, char *ext_csd)
+int mmc_change_freq(struct mmc *mmc)
+int sd_change_freq(struct mmc *mmc)
+int fbase[] = {
+int multipliers[] = {
+void mmc_set_ios(struct mmc *mmc)
+void mmc_set_clock(struct mmc *mmc, uint clock)
+void mmc_set_bus_width(struct mmc *mmc, uint width)
+int mmc_startup(struct mmc *mmc)
and more. All of them are undocumented, so it's not clear how they're
supposed to be used.
> +#ifndef CONFIG_GENERIC_MMC
> +int mmc_legacy_init(int verbose);
> +#endif
> #endif /* _MMC_H_ */
> diff --git a/lib_ppc/board.c b/lib_ppc/board.c
> index ce07c4e..e304d8c 100644
> --- a/lib_ppc/board.c
> +++ b/lib_ppc/board.c
> @@ -51,6 +51,9 @@
> #include <status_led.h>
> #endif
> #include <net.h>
> +#ifdef CONFIG_GENERIC_MMC
> +#include <mmc.h>
> +#endif
> #include <serial.h>
> #ifdef CONFIG_SYS_ALLOC_DPRAM
> #if !defined(CONFIG_CPM2)
> @@ -1083,6 +1086,12 @@ void board_init_r (gd_t *id, ulong dest_addr)
> scsi_init ();
> #endif
>
> +#ifdef CONFIG_GENERIC_MMC
> + WATCHDOG_RESET ();
> + puts ("MMC: ");
> + mmc_initialize (bd);
> +#endif
Why can't mmc_initialize() print the "MMC: " part too?
Better yet, why not define mmc_initialize() as an empty function if
CONFIG_GENERIC_MMC is not set?
Ok, that's all for now. Thanks a lot for taking the effort to do this.
A common mmc layer is much needed.
Haavard
More information about the U-Boot
mailing list