[U-Boot] [PATCH] mmc: add generic mmc spi driver

This patch supports mmc/sd card with spi interface. I have tested with sd and mmc cards. But there is still ocr issue with SDHC.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- drivers/mmc/Makefile | 1 + drivers/mmc/mmc_spi.c | 252 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+), 0 deletions(-) create mode 100644 drivers/mmc/mmc_spi.c
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 1b8f5bd..d03eb47 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -31,6 +31,7 @@ LIB := $(obj)libmmc.a COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..76c5977 --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,252 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <spi.h> +#include <asm/errno.h> + +#define CTOUT 0x10 +#define RTOUT 0x10000 +#define WTOUT 0x10000 + +static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{ + u8 cmdo[6]; + u8 r1; + int i; + cmdo[0] = 0x40 + cmdidx; + cmdo[1] = cmdarg >> 24; + cmdo[2] = cmdarg >> 16; + cmdo[3] = cmdarg >> 8; + cmdo[4] = cmdarg; + cmdo[5] = 0x95; /* crc valid only for cmd00 */ + spi_xfer(mmc->priv, 6 * 8, cmdo, NULL, SPI_XFER_BEGIN); + for (i = 0; i < CTOUT; i++) { + spi_xfer(mmc->priv, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x80) == 0) + break; + } + debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1); + return r1; +} + +static uint mmc_spi_readdata(struct mmc *mmc, char *buf, + u32 bcnt, u32 bsize) +{ + u8 r1; + u8 crc[2]; + int i; + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + spi_xfer(mmc->priv, 1 * 8, NULL, &r1, 0); + if (r1 != 0xff) + break; + } + debug("%s:tok%d %x\n", __func__, i, r1); + if (r1 == 0xfe) { + spi_xfer(mmc->priv, bsize * 8, NULL, buf, 0); + buf += bsize; + spi_xfer(mmc->priv, 2 * 8, NULL, crc, 0); + r1 = 0; + } else + break; + } + return r1; +} + +static uint mmc_spi_writedata(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + u8 r1; + u8 tok[2] = { 0xff, 0xfe }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(mmc->priv, 2 * 8, tok, NULL, 0); + spi_xfer(mmc->priv, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(mmc->priv, 2 * 8, crc, NULL, 0); + spi_xfer(mmc->priv, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(mmc->priv, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } else + break; + } + return r1; +} + +static uint mmc_spi_writeblock(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + u8 r1; + u8 tok[2] = { 0xff, 0xfc }; + u8 stop[2] = { 0xff, 0xfd }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(mmc->priv, 2 * 8, tok, NULL, 0); + spi_xfer(mmc->priv, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(mmc->priv, 2 * 8, crc, NULL, 0); + spi_xfer(mmc->priv, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(mmc->priv, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } + } + if (bcnt == 0 && r1 == 0) { + spi_xfer(mmc->priv, 2 * 8, stop, NULL, 0); + for (i = 0; i < WTOUT; i++) { + spi_xfer(mmc->priv, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + } + } + return r1; +} + +static inline uint rswab(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + u8 r1; + u8 resp[16]; + int i; + debug("%s:cmd%d %x %x %x\n", __func__, + cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags); + if (cmd->cmdidx == MMC_CMD_ALL_SEND_CID) + cmd->cmdidx = MMC_CMD_SEND_CID; + r1 = mmc_spi_sendcmd(mmc, cmd->cmdidx, cmd->cmdarg); + if (cmd->cmdidx == 55 && (r1 & 0x04)) + return TIMEOUT; + if ((cmd->cmdidx == MMC_CMD_SEND_OP_COND || + cmd->cmdidx == SD_CMD_APP_SEND_OP_COND) && + cmd->resp_type == MMC_RSP_R3) { + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(mmc->priv, 4 * 8, NULL, resp, 0); + cmd->response[0] = rswab(resp); + debug("ocr %x\n", cmd->response[0]); + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(mmc, resp, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = rswab(resp + i * 4); + debug("r136 %x %x %x %x\n", cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } else if (cmd->resp_type == MMC_RSP_R1) { + cmd->response[0] = 0; + if (r1 & 0x7e) + cmd->response[0] |= (1 << 19); + if (r1 & 0x40) + cmd->response[0] |= (1 << 31); + if (r1 & 0x20) + cmd->response[0] |= (1 << 30); + if (r1 & 0x10) + cmd->response[0] |= (1 << 28); + if (r1 & 0x08) + cmd->response[0] |= (1 << 23); + if (r1 & 0x04) + cmd->response[0] |= (1 << 22); + if (r1 & 0x02) + cmd->response[0] |= (1 << 13); + } + if (data) { + debug("%s:data %x %x %x\n", __func__, + data->flags, data->blocks, data->blocksize); + if (data->flags == MMC_DATA_READ) { + r1 = mmc_spi_readdata(mmc, data->dest, + data->blocks, data->blocksize); + } else if (data->flags == MMC_DATA_WRITE) { + if (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK) + r1 = mmc_spi_writeblock(mmc, data->src, + data->blocks, data->blocksize); + else + r1 = mmc_spi_writedata(mmc, data->src, + data->blocks, data->blocksize); + } + } + return 0; +} + +static void mmc_spi_set_ios(struct mmc *mmc) +{ + debug("%s:\n", __func__); +} + +static int mmc_spi_init_p(struct mmc *mmc) +{ + u8 d = 0xff; + int i; + debug("%s:\n", __func__); + spi_release_bus(mmc->priv); + for (i = 0; i < 16; i++) /* power on initialization */ + spi_xfer(mmc->priv, 1 * 8, &d, NULL, 0); + spi_claim_bus(mmc->priv); + return 0; +} + +int mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{ + struct mmc *mmc = NULL; + + mmc = malloc(sizeof(struct mmc)); + if (!mmc) + return -ENOMEM; + sprintf(mmc->name, "mmc_spi"); + mmc->priv = spi_setup_slave(bus, cs, speed, mode); + if (!mmc->priv) { + free(mmc); + return -ENOMEM; + } + mmc->send_cmd = mmc_spi_request; + mmc->set_ios = mmc_spi_set_ios; + mmc->init = mmc_spi_init_p; + mmc->host_caps = 0; + + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->f_max = speed; + mmc->f_min = mmc->f_max >> 9; + mmc->block_dev.part_type = PART_TYPE_DOS; + + mmc_register(mmc); + + return 0; +}

On Thursday 22 April 2010 22:53:34 Thomas Chou wrote:
This patch supports mmc/sd card with spi interface. I have tested with sd and mmc cards. But there is still ocr issue with SDHC.
now the semi-obvious question ... how does this differ from the old spi_mmc.c driver ? i see this doesnt rely on the legacy mmc framework, but what about device/commandset support parity ?
it's too bad the new framework doesnt allow for dynamic probing like the spi layer. makes it a pain to work with a SPI/MMC card that can have the CS changed on the fly. -mike

On 04/23/2010 11:35 AM, Mike Frysinger wrote:
On Thursday 22 April 2010 22:53:34 Thomas Chou wrote:
This patch supports mmc/sd card with spi interface. I have tested with sd and mmc cards. But there is still ocr issue with SDHC.
now the semi-obvious question ... how does this differ from the old spi_mmc.c driver ? i see this doesnt rely on the legacy mmc framework, but what about device/commandset support parity ?
Hi Mike,
Right after playing your spi_mmc.c, I started this one based on generic mmc framework last night. I tried hard to recall my memory about mmc/sd, and hoped to catch up the merge window.
Do you mean "parity" as crc? Crc is not supported, but can be added. I make some command set translation when spi doesn't support.
it's too bad the new framework doesnt allow for dynamic probing like the spi layer. makes it a pain to work with a SPI/MMC card that can have the CS changed on the fly. -mike
Maybe we can move spi_setup_slave to the mmc->init() and do probing. Or shall we add a subcommand?
Cheers, Thomas

On 04/23/2010 12:04 PM, Thomas Chou wrote:
it's too bad the new framework doesnt allow for dynamic probing like the spi
layer. makes it a pain to work with a SPI/MMC card that can have the CS changed on the fly. -mike
Second thought. With generic mmc framework, we can instance multiple mmc devices. Then we can probe one of them when we want.
#ifdef CONFIG_GENERIC_MMC int board_mmc_init(bd_t *bis) { int rc = 0; #ifdef CONFIG_MMC_SPI extern int mmc_spi_init(uint bus, uint cs, uint speed, uint mode); mmc_spi_init(CONFIG_MMC_SPI_BUS, CONFIG_MMC_SPI_CS_0, CONFIG_MMC_SPI_SPEED, CONFIG_MMC_SPI_MODE); mmc_spi_init(CONFIG_MMC_SPI_BUS, CONFIG_MMC_SPI_CS_1, CONFIG_MMC_SPI_SPEED, CONFIG_MMC_SPI_MODE); #endif return rc; } #endif
Cheers, Thomas

On Friday 23 April 2010 01:55:11 Thomas Chou wrote:
On 04/23/2010 12:04 PM, Thomas Chou wrote:
it's too bad the new framework doesnt allow for dynamic probing like the spi layer. makes it a pain to work with a SPI/MMC card that can have the CS changed on the fly.
Second thought. With generic mmc framework, we can instance multiple mmc devices. Then we can probe one of them when we want.
i dont think that's a scalable solution. what if you have multiple spi busses or 10's of GPIO CS's ? only way to scale is to have a new subcommand so people can do it themselves. a simple CONFIG_CMD_MMC_SPI command which forwards along calls to mmc_spi_init() on the fly would work. -mike

On 04/25/2010 02:56 PM, Mike Frysinger wrote:
On Friday 23 April 2010 01:55:11 Thomas Chou wrote:
On 04/23/2010 12:04 PM, Thomas Chou wrote:
it's too bad the new framework doesnt allow for dynamic probing like the spi layer. makes it a pain to work with a SPI/MMC card that can have the CS changed on the fly.
Second thought. With generic mmc framework, we can instance multiple mmc devices. Then we can probe one of them when we want.
i dont think that's a scalable solution. what if you have multiple spi busses or 10's of GPIO CS's ? only way to scale is to have a new subcommand so people can do it themselves. a simple CONFIG_CMD_MMC_SPI command which forwards along calls to mmc_spi_init() on the fly would work. -mike
Hi Mike,
I have resolved the SDHC issue and have tested various MMC/SD cards, including writing. I will submit the patch tomorrow.
I have moved spi slave setup so that we can change the cs on the fly. We could add a subcommand, like,
mmc_spi [bus:]cs
Where should I place this command? in a new file in common dir or inside the mmc_spi driver?
Cheers, Thomas

On Monday 26 April 2010 10:37:07 Thomas Chou wrote:
I have moved spi slave setup so that we can change the cs on the fly. We could add a subcommand, like,
mmc_spi [bus:]cs
Where should I place this command? in a new file in common dir or inside the mmc_spi driver?
i would keep the current board init hook you have currently so that the boards which do have static settings can leverage that, and have the mmc_spi command simply call that. i'd have the syntax be: mmc_spi [bus:]cs [hz [mode]]
i'd have it be a dedicated command in common/ so that people can disable it if they dont want it. -mike

This patch supports mmc/sd card with spi interface. It is based on the generic mmc framework. It works with SDHC and supports write.
The crc7 lib func is merged from linux and used to compute mmc command checksum.
There is a subcomamnd "mmc_spi" to setup spi bus and cs at run time.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- v2 add crc7, use cmd58 to read ocr, add subcommand mmc_spi.
common/Makefile | 1 + common/cmd_mmc_spi.c | 92 ++++++++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/mmc_spi.c | 316 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/crc7.h | 14 ++ include/mmc_spi.h | 19 +++ lib/Makefile | 1 + lib/crc7.c | 62 ++++++++++ 8 files changed, 506 insertions(+), 0 deletions(-) create mode 100644 common/cmd_mmc_spi.c create mode 100644 drivers/mmc/mmc_spi.c create mode 100644 include/linux/crc7.h create mode 100644 include/mmc_spi.h create mode 100644 lib/crc7.c
diff --git a/common/Makefile b/common/Makefile index dbf7a05..ee23e2f 100644 --- a/common/Makefile +++ b/common/Makefile @@ -118,6 +118,7 @@ COBJS-$(CONFIG_CMD_MII) += miiphyutil.o COBJS-$(CONFIG_CMD_MII) += cmd_mii.o COBJS-$(CONFIG_CMD_MISC) += cmd_misc.o COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o +COBJS-$(CONFIG_CMD_MMC_SPI) += cmd_mmc_spi.o COBJS-$(CONFIG_MP) += cmd_mp.o COBJS-$(CONFIG_CMD_MTDPARTS) += cmd_mtdparts.o COBJS-$(CONFIG_CMD_NAND) += cmd_nand.o diff --git a/common/cmd_mmc_spi.c b/common/cmd_mmc_spi.c new file mode 100644 index 0000000..578d7a7 --- /dev/null +++ b/common/cmd_mmc_spi.c @@ -0,0 +1,92 @@ +/* + * Command for mmc_spi setup. + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> + +static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{ + int dev_num; + uint bus; + uint cs; + uint speed; + uint mode; + char *endp; + struct mmc *mmc = NULL; + struct mmc_spi_priv *priv; + + if (argc < 2) + goto usage; + + dev_num = simple_strtoul(argv[1], &endp, 0); + if (*endp != 0) + goto usage; + mmc = find_mmc_device(dev_num); + if (!mmc || strcmp(mmc->name, "MMC_SPI")) + goto usage; + priv = mmc->priv; + bus = priv->bus; + cs = priv->cs; + speed = priv->speed; + mode = priv->mode; + + if (argc < 3) + goto info; + + cs = simple_strtoul(argv[2], &endp, 0); + if (*argv[2] == 0 || (*endp != 0 && *endp != ':')) + goto usage; + if (*endp == ':') { + if (endp[1] == 0) + goto usage; + bus = cs; + cs = simple_strtoul(endp + 1, &endp, 0); + if (*endp != 0) + goto usage; + } + + if (argc >= 4) { + speed = simple_strtoul(argv[3], &endp, 0); + if (*argv[3] == 0 || *endp != 0) + goto usage; + } + if (argc >= 5) { + mode = simple_strtoul(argv[4], &endp, 16); + if (*argv[4] == 0 || *endp != 0) + goto usage; + } + + if (bus != priv->bus || cs != priv->cs || + speed != priv->speed || mode != priv->speed) { + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + if (priv->slave) { + free(priv->slave); + priv->slave = NULL; + } + } +info: + printf("%s:%d at %u:%u %u %u\n", mmc->name, dev_num, + bus, cs, speed, mode); + return 0; + +usage: + cmd_usage(cmdtp); + return 1; +} + +U_BOOT_CMD( + mmc_spi, 5, 0, do_mmc_spi, + "mmc_spi setup", + "dev_num [bus:][cs] [hz] [mode] - setup mmc_spi device on given\n" + " SPI bus and chip select\n" +); diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 1b8f5bd..d03eb47 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -31,6 +31,7 @@ LIB := $(obj)libmmc.a COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..3ff171f --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,316 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> +#include <linux/crc7.h> +#include <asm/errno.h> + +#define CTOUT 0x10 +#define RTOUT 0x10000 +#define WTOUT 0x10000 + +static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 cmdo[7]; + u8 r1; + int i; + cmdo[0] = 0xff; + cmdo[1] = 0x40 + cmdidx; + cmdo[2] = cmdarg >> 24; + cmdo[3] = cmdarg >> 16; + cmdo[4] = cmdarg >> 8; + cmdo[5] = cmdarg; + cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01; + spi_xfer(priv->slave, 7 * 8, cmdo, NULL, SPI_XFER_BEGIN); + for (i = 0; i < CTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x80) == 0) + break; + } + debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1); + return r1; +} + +static uint mmc_spi_readdata(struct mmc *mmc, char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 crc[2]; + int i; + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 != 0xff) + break; + } + debug("%s:tok%d %x\n", __func__, i, r1); + if (r1 == 0xfe) { + spi_xfer(priv->slave, bsize * 8, NULL, buf, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, NULL, crc, 0); + r1 = 0; + } else + break; + } + return r1; +} + +static uint mmc_spi_writedata(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = { 0xff, 0xfe }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } else + break; + } + return r1; +} + +static uint mmc_spi_writeblock(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = { 0xff, 0xfc }; + u8 stop[2] = { 0xff, 0xfd }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } + } + if (bcnt == 0 && r1 == 0) { + spi_xfer(priv->slave, 2 * 8, stop, NULL, 0); + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + } + } + return r1; +} + +static inline uint rswab(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static inline void mmc_spi_deactivate(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + spi_xfer(priv->slave, 1 * 8, NULL, NULL, SPI_XFER_END); + spi_xfer(priv->slave, 1 * 8, NULL, NULL, 0); +} + +static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + ushort cmdidx = cmd->cmdidx; + uint cmdarg = cmd->cmdarg; + u8 resp[16]; + int i; + int ret = 0; + debug("%s:cmd%d %x %x %x\n", __func__, + cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags); + if (cmdidx == MMC_CMD_ALL_SEND_CID) + cmdidx = MMC_CMD_SEND_CID; + r1 = mmc_spi_sendcmd(mmc, cmdidx, cmdarg); + if (r1 == 0xff) { + ret = NO_CARD_ERR; + goto done; + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(mmc, resp, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = rswab(resp + i * 4); + debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } else { + if (!data) { + spi_xfer(priv->slave, 4 * 8, NULL, resp, 0); + cmd->response[0] = rswab(resp); + debug("r32 %x\n", cmd->response[0]); + } + if (cmdidx == MMC_CMD_GO_IDLE_STATE) { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswab(resp) & ~OCR_BUSY; + debug("ocr58 %x\n", priv->ocr58); + } else if (cmdidx == SD_CMD_SEND_IF_COND) { + if ((r1 & 0x7e) == 0) + priv->sd_if_cond = 1; + } else if (cmdidx == SD_CMD_APP_SEND_OP_COND || + cmdidx == MMC_CMD_SEND_OP_COND) { + if (r1 & 0x04) + ret = TIMEOUT; + else { + if (r1 & 0x01) + cmd->response[0] = priv->ocr58; + else { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswab(resp); + debug("ocr58 %x\n", priv->ocr58); + cmd->response[0] = priv->ocr58; + } + } + } else { + cmd->response[0] = 0; + if (r1 & 0x7e) + cmd->response[0] |= (1 << 19); + if (r1 & 0x40) + cmd->response[0] |= (1 << 31); + if (r1 & 0x20) + cmd->response[0] |= (1 << 30); + if (r1 & 0x10) + cmd->response[0] |= (1 << 28); + if (r1 & 0x08) + cmd->response[0] |= (1 << 23); + if (r1 & 0x04) + cmd->response[0] |= (1 << 22); + if (r1 & 0x02) + cmd->response[0] |= (1 << 13); + } + } + if (data) { + debug("%s:data %x %x %x\n", __func__, + data->flags, data->blocks, data->blocksize); + if (data->flags == MMC_DATA_READ) { + r1 = mmc_spi_readdata(mmc, data->dest, + data->blocks, data->blocksize); + } else if (data->flags == MMC_DATA_WRITE) { + if (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK) + r1 = mmc_spi_writeblock(mmc, data->src, + data->blocks, data->blocksize); + else + r1 = mmc_spi_writedata(mmc, data->src, + data->blocks, data->blocksize); + } + } +done: + mmc_spi_deactivate(mmc); + return ret; +} + +static void mmc_spi_set_ios(struct mmc *mmc) +{ + debug("%s:\n", __func__); +} + +static int mmc_spi_init_p(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + debug("%s:\n", __func__); + if (!priv->slave) { + debug("%s: setup %u:%u %u %u\n", __func__, + priv->bus, priv->cs, + priv->speed, priv->mode); + priv->slave = spi_setup_slave(priv->bus, priv->cs, + priv->speed, priv->mode); + if (!priv->slave) + return -ENOMEM; + } + priv->sd_if_cond = 0; + priv->ocr58 = 0; + spi_claim_bus(priv->slave); + /* power on initialization */ + spi_xfer(priv->slave, 39 * 8, NULL, NULL, + SPI_XFER_BEGIN | SPI_XFER_END); + spi_xfer(priv->slave, 18 * 8, NULL, NULL, 0); + return 0; +} + +int mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{ + struct mmc *mmc; + struct mmc_spi_priv *priv; + + mmc = malloc(sizeof(*mmc)); + if (!mmc) + return -ENOMEM; + priv = malloc(sizeof(*priv)); + if (!priv) { + free(mmc); + return -ENOMEM; + } + mmc->priv = priv; + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + sprintf(mmc->name, "MMC_SPI"); + mmc->send_cmd = mmc_spi_request; + mmc->set_ios = mmc_spi_set_ios; + mmc->init = mmc_spi_init_p; + mmc->host_caps = 0; + + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->f_max = speed; + mmc->f_min = mmc->f_max >> 9; + mmc->block_dev.part_type = PART_TYPE_DOS; + + mmc_register(mmc); + + return 0; +} diff --git a/include/linux/crc7.h b/include/linux/crc7.h new file mode 100644 index 0000000..1786e77 --- /dev/null +++ b/include/linux/crc7.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_CRC7_H +#define _LINUX_CRC7_H +#include <linux/types.h> + +extern const u8 crc7_syndrome_table[256]; + +static inline u8 crc7_byte(u8 crc, u8 data) +{ + return crc7_syndrome_table[(crc << 1) ^ data]; +} + +extern u8 crc7(u8 crc, const u8 *buffer, size_t len); + +#endif diff --git a/include/mmc_spi.h b/include/mmc_spi.h new file mode 100644 index 0000000..66aa6aa --- /dev/null +++ b/include/mmc_spi.h @@ -0,0 +1,19 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#ifndef _MMC_SPI_H_ +#define _MMC_SPI_H_ +struct mmc_spi_priv { + struct spi_slave *slave; + uint bus; + uint cs; + uint speed; + uint mode; + uint ocr58; + uint sd_if_cond:1; +}; +#endif diff --git a/lib/Makefile b/lib/Makefile index c45f07c..aef2893 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -32,6 +32,7 @@ COBJS-$(CONFIG_BZIP2) += bzlib_decompress.o COBJS-$(CONFIG_BZIP2) += bzlib_randtable.o COBJS-$(CONFIG_BZIP2) += bzlib_huffman.o COBJS-$(CONFIG_USB_TTY) += circbuf.o +COBJS-y += crc7.o COBJS-y += crc16.o COBJS-y += crc32.o COBJS-y += ctype.o diff --git a/lib/crc7.c b/lib/crc7.c new file mode 100644 index 0000000..e635c9c --- /dev/null +++ b/lib/crc7.c @@ -0,0 +1,62 @@ +/* + * crc7.c + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/types.h> +#include <linux/crc7.h> + + +/* Table for CRC-7 (polynomial x^7 + x^3 + 1) */ +const u8 crc7_syndrome_table[256] = { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, + 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, + 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, + 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, + 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, + 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, + 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c, + 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, + 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, + 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, + 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a, + 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, + 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21, + 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, + 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, + 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, + 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36, + 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, + 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f, + 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, + 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, + 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d, + 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, + 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52, + 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, + 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, + 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, + 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60, + 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, + 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79 +}; + +/** + * crc7 - update the CRC7 for the data buffer + * @crc: previous CRC7 value + * @buffer: data pointer + * @len: number of bytes in the buffer + * Context: any + * + * Returns the updated CRC7 value. + */ +u8 crc7(u8 crc, const u8 *buffer, size_t len) +{ + while (len--) + crc = crc7_byte(crc, *buffer++); + return crc; +}

This patch supports mmc/sd card with spi interface. It is based on the generic mmc framework. It works with SDHC and supports write.
The crc7 lib func is merged from linux and used to compute mmc command checksum.
There is a subcomamnd "mmc_spi" to setup spi bus and cs at run time.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- v3: add mmc_spi_init() proto to mmc_spi.h. v2: add crc7, use cmd58 to read ocr, add subcommand mmc_spi.
common/Makefile | 1 + common/cmd_mmc_spi.c | 92 ++++++++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/mmc_spi.c | 316 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/crc7.h | 14 ++ include/mmc_spi.h | 26 ++++ lib/Makefile | 1 + lib/crc7.c | 62 ++++++++++ 8 files changed, 513 insertions(+), 0 deletions(-) create mode 100644 common/cmd_mmc_spi.c create mode 100644 drivers/mmc/mmc_spi.c create mode 100644 include/linux/crc7.h create mode 100644 include/mmc_spi.h create mode 100644 lib/crc7.c
diff --git a/common/Makefile b/common/Makefile index dbf7a05..ee23e2f 100644 --- a/common/Makefile +++ b/common/Makefile @@ -118,6 +118,7 @@ COBJS-$(CONFIG_CMD_MII) += miiphyutil.o COBJS-$(CONFIG_CMD_MII) += cmd_mii.o COBJS-$(CONFIG_CMD_MISC) += cmd_misc.o COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o +COBJS-$(CONFIG_CMD_MMC_SPI) += cmd_mmc_spi.o COBJS-$(CONFIG_MP) += cmd_mp.o COBJS-$(CONFIG_CMD_MTDPARTS) += cmd_mtdparts.o COBJS-$(CONFIG_CMD_NAND) += cmd_nand.o diff --git a/common/cmd_mmc_spi.c b/common/cmd_mmc_spi.c new file mode 100644 index 0000000..578d7a7 --- /dev/null +++ b/common/cmd_mmc_spi.c @@ -0,0 +1,92 @@ +/* + * Command for mmc_spi setup. + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> + +static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{ + int dev_num; + uint bus; + uint cs; + uint speed; + uint mode; + char *endp; + struct mmc *mmc = NULL; + struct mmc_spi_priv *priv; + + if (argc < 2) + goto usage; + + dev_num = simple_strtoul(argv[1], &endp, 0); + if (*endp != 0) + goto usage; + mmc = find_mmc_device(dev_num); + if (!mmc || strcmp(mmc->name, "MMC_SPI")) + goto usage; + priv = mmc->priv; + bus = priv->bus; + cs = priv->cs; + speed = priv->speed; + mode = priv->mode; + + if (argc < 3) + goto info; + + cs = simple_strtoul(argv[2], &endp, 0); + if (*argv[2] == 0 || (*endp != 0 && *endp != ':')) + goto usage; + if (*endp == ':') { + if (endp[1] == 0) + goto usage; + bus = cs; + cs = simple_strtoul(endp + 1, &endp, 0); + if (*endp != 0) + goto usage; + } + + if (argc >= 4) { + speed = simple_strtoul(argv[3], &endp, 0); + if (*argv[3] == 0 || *endp != 0) + goto usage; + } + if (argc >= 5) { + mode = simple_strtoul(argv[4], &endp, 16); + if (*argv[4] == 0 || *endp != 0) + goto usage; + } + + if (bus != priv->bus || cs != priv->cs || + speed != priv->speed || mode != priv->speed) { + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + if (priv->slave) { + free(priv->slave); + priv->slave = NULL; + } + } +info: + printf("%s:%d at %u:%u %u %u\n", mmc->name, dev_num, + bus, cs, speed, mode); + return 0; + +usage: + cmd_usage(cmdtp); + return 1; +} + +U_BOOT_CMD( + mmc_spi, 5, 0, do_mmc_spi, + "mmc_spi setup", + "dev_num [bus:][cs] [hz] [mode] - setup mmc_spi device on given\n" + " SPI bus and chip select\n" +); diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 6fa04b8..02ed329 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libmmc.a COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..3ff171f --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,316 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> +#include <linux/crc7.h> +#include <asm/errno.h> + +#define CTOUT 0x10 +#define RTOUT 0x10000 +#define WTOUT 0x10000 + +static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 cmdo[7]; + u8 r1; + int i; + cmdo[0] = 0xff; + cmdo[1] = 0x40 + cmdidx; + cmdo[2] = cmdarg >> 24; + cmdo[3] = cmdarg >> 16; + cmdo[4] = cmdarg >> 8; + cmdo[5] = cmdarg; + cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01; + spi_xfer(priv->slave, 7 * 8, cmdo, NULL, SPI_XFER_BEGIN); + for (i = 0; i < CTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x80) == 0) + break; + } + debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1); + return r1; +} + +static uint mmc_spi_readdata(struct mmc *mmc, char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 crc[2]; + int i; + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 != 0xff) + break; + } + debug("%s:tok%d %x\n", __func__, i, r1); + if (r1 == 0xfe) { + spi_xfer(priv->slave, bsize * 8, NULL, buf, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, NULL, crc, 0); + r1 = 0; + } else + break; + } + return r1; +} + +static uint mmc_spi_writedata(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = { 0xff, 0xfe }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } else + break; + } + return r1; +} + +static uint mmc_spi_writeblock(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = { 0xff, 0xfc }; + u8 stop[2] = { 0xff, 0xfd }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } + } + if (bcnt == 0 && r1 == 0) { + spi_xfer(priv->slave, 2 * 8, stop, NULL, 0); + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + } + } + return r1; +} + +static inline uint rswab(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static inline void mmc_spi_deactivate(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + spi_xfer(priv->slave, 1 * 8, NULL, NULL, SPI_XFER_END); + spi_xfer(priv->slave, 1 * 8, NULL, NULL, 0); +} + +static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + ushort cmdidx = cmd->cmdidx; + uint cmdarg = cmd->cmdarg; + u8 resp[16]; + int i; + int ret = 0; + debug("%s:cmd%d %x %x %x\n", __func__, + cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags); + if (cmdidx == MMC_CMD_ALL_SEND_CID) + cmdidx = MMC_CMD_SEND_CID; + r1 = mmc_spi_sendcmd(mmc, cmdidx, cmdarg); + if (r1 == 0xff) { + ret = NO_CARD_ERR; + goto done; + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(mmc, resp, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = rswab(resp + i * 4); + debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } else { + if (!data) { + spi_xfer(priv->slave, 4 * 8, NULL, resp, 0); + cmd->response[0] = rswab(resp); + debug("r32 %x\n", cmd->response[0]); + } + if (cmdidx == MMC_CMD_GO_IDLE_STATE) { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswab(resp) & ~OCR_BUSY; + debug("ocr58 %x\n", priv->ocr58); + } else if (cmdidx == SD_CMD_SEND_IF_COND) { + if ((r1 & 0x7e) == 0) + priv->sd_if_cond = 1; + } else if (cmdidx == SD_CMD_APP_SEND_OP_COND || + cmdidx == MMC_CMD_SEND_OP_COND) { + if (r1 & 0x04) + ret = TIMEOUT; + else { + if (r1 & 0x01) + cmd->response[0] = priv->ocr58; + else { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswab(resp); + debug("ocr58 %x\n", priv->ocr58); + cmd->response[0] = priv->ocr58; + } + } + } else { + cmd->response[0] = 0; + if (r1 & 0x7e) + cmd->response[0] |= (1 << 19); + if (r1 & 0x40) + cmd->response[0] |= (1 << 31); + if (r1 & 0x20) + cmd->response[0] |= (1 << 30); + if (r1 & 0x10) + cmd->response[0] |= (1 << 28); + if (r1 & 0x08) + cmd->response[0] |= (1 << 23); + if (r1 & 0x04) + cmd->response[0] |= (1 << 22); + if (r1 & 0x02) + cmd->response[0] |= (1 << 13); + } + } + if (data) { + debug("%s:data %x %x %x\n", __func__, + data->flags, data->blocks, data->blocksize); + if (data->flags == MMC_DATA_READ) { + r1 = mmc_spi_readdata(mmc, data->dest, + data->blocks, data->blocksize); + } else if (data->flags == MMC_DATA_WRITE) { + if (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK) + r1 = mmc_spi_writeblock(mmc, data->src, + data->blocks, data->blocksize); + else + r1 = mmc_spi_writedata(mmc, data->src, + data->blocks, data->blocksize); + } + } +done: + mmc_spi_deactivate(mmc); + return ret; +} + +static void mmc_spi_set_ios(struct mmc *mmc) +{ + debug("%s:\n", __func__); +} + +static int mmc_spi_init_p(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + debug("%s:\n", __func__); + if (!priv->slave) { + debug("%s: setup %u:%u %u %u\n", __func__, + priv->bus, priv->cs, + priv->speed, priv->mode); + priv->slave = spi_setup_slave(priv->bus, priv->cs, + priv->speed, priv->mode); + if (!priv->slave) + return -ENOMEM; + } + priv->sd_if_cond = 0; + priv->ocr58 = 0; + spi_claim_bus(priv->slave); + /* power on initialization */ + spi_xfer(priv->slave, 39 * 8, NULL, NULL, + SPI_XFER_BEGIN | SPI_XFER_END); + spi_xfer(priv->slave, 18 * 8, NULL, NULL, 0); + return 0; +} + +int mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{ + struct mmc *mmc; + struct mmc_spi_priv *priv; + + mmc = malloc(sizeof(*mmc)); + if (!mmc) + return -ENOMEM; + priv = malloc(sizeof(*priv)); + if (!priv) { + free(mmc); + return -ENOMEM; + } + mmc->priv = priv; + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + sprintf(mmc->name, "MMC_SPI"); + mmc->send_cmd = mmc_spi_request; + mmc->set_ios = mmc_spi_set_ios; + mmc->init = mmc_spi_init_p; + mmc->host_caps = 0; + + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->f_max = speed; + mmc->f_min = mmc->f_max >> 9; + mmc->block_dev.part_type = PART_TYPE_DOS; + + mmc_register(mmc); + + return 0; +} diff --git a/include/linux/crc7.h b/include/linux/crc7.h new file mode 100644 index 0000000..1786e77 --- /dev/null +++ b/include/linux/crc7.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_CRC7_H +#define _LINUX_CRC7_H +#include <linux/types.h> + +extern const u8 crc7_syndrome_table[256]; + +static inline u8 crc7_byte(u8 crc, u8 data) +{ + return crc7_syndrome_table[(crc << 1) ^ data]; +} + +extern u8 crc7(u8 crc, const u8 *buffer, size_t len); + +#endif diff --git a/include/mmc_spi.h b/include/mmc_spi.h new file mode 100644 index 0000000..6c72f65 --- /dev/null +++ b/include/mmc_spi.h @@ -0,0 +1,26 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#ifndef _MMC_SPI_H_ +#define _MMC_SPI_H_ + +#include <spi.h> +#include <linux/types.h> + +struct mmc_spi_priv { + struct spi_slave *slave; + uint bus; + uint cs; + uint speed; + uint mode; + uint ocr58; + uint sd_if_cond:1; +}; + +int mmc_spi_init(uint bus, uint cs, uint speed, uint mode); + +#endif diff --git a/lib/Makefile b/lib/Makefile index c45f07c..aef2893 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -32,6 +32,7 @@ COBJS-$(CONFIG_BZIP2) += bzlib_decompress.o COBJS-$(CONFIG_BZIP2) += bzlib_randtable.o COBJS-$(CONFIG_BZIP2) += bzlib_huffman.o COBJS-$(CONFIG_USB_TTY) += circbuf.o +COBJS-y += crc7.o COBJS-y += crc16.o COBJS-y += crc32.o COBJS-y += ctype.o diff --git a/lib/crc7.c b/lib/crc7.c new file mode 100644 index 0000000..e635c9c --- /dev/null +++ b/lib/crc7.c @@ -0,0 +1,62 @@ +/* + * crc7.c + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/types.h> +#include <linux/crc7.h> + + +/* Table for CRC-7 (polynomial x^7 + x^3 + 1) */ +const u8 crc7_syndrome_table[256] = { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, + 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, + 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, + 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, + 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, + 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, + 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c, + 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, + 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, + 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, + 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a, + 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, + 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21, + 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, + 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, + 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, + 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36, + 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, + 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f, + 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, + 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, + 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d, + 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, + 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52, + 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, + 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, + 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, + 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60, + 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, + 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79 +}; + +/** + * crc7 - update the CRC7 for the data buffer + * @crc: previous CRC7 value + * @buffer: data pointer + * @len: number of bytes in the buffer + * Context: any + * + * Returns the updated CRC7 value. + */ +u8 crc7(u8 crc, const u8 *buffer, size_t len) +{ + while (len--) + crc = crc7_byte(crc, *buffer++); + return crc; +}

On Monday 26 April 2010 23:27:16 Thomas Chou wrote:
--- /dev/null +++ b/common/cmd_mmc_spi.c
- struct mmc *mmc = NULL;
useless assignment since mmc gets set shortly after
- struct mmc_spi_priv *priv;
- if (argc < 2)
goto usage;
- dev_num = simple_strtoul(argv[1], &endp, 0);
- if (*endp != 0)
goto usage;
- mmc = find_mmc_device(dev_num);
- if (!mmc || strcmp(mmc->name, "MMC_SPI"))
goto usage;
too many indents on the goto
also, this looks like it bails if no existing mmc spi device is found ? i thought the point of this command was to create new instances on the fly. this looks like it just reprograms the settings for an existing device.
- if (bus != priv->bus || cs != priv->cs ||
speed != priv->speed || mode != priv->speed) {
priv->bus = bus;
priv->cs = cs;
priv->speed = speed;
priv->mode = mode;
if (priv->slave) {
free(priv->slave);
priv->slave = NULL;
}
you shouldnt be poking the spi internals like this. use the common spi funcs to free/alloc the slave device. -mike

Hi Mike,
On 04/28/2010 12:49 AM, Mike Frysinger wrote:
On Monday 26 April 2010 23:27:16 Thomas Chou wrote:
--- /dev/null +++ b/common/cmd_mmc_spi.c
- struct mmc *mmc = NULL;
useless assignment since mmc gets set shortly after
OK.
- struct mmc_spi_priv *priv;
- if (argc< 2)
goto usage;
- dev_num = simple_strtoul(argv[1],&endp, 0);
- if (*endp != 0)
goto usage;
- mmc = find_mmc_device(dev_num);
- if (!mmc || strcmp(mmc->name, "MMC_SPI"))
goto usage;
too many indents on the goto
The tabs were interfered by the '+' . It is correct when applied.
also, this looks like it bails if no existing mmc spi device is found ? i thought the point of this command was to create new instances on the fly. this looks like it just reprograms the settings for an existing device.
OK. Now v4 patch creates a MMC dev on the fly.
- if (bus != priv->bus || cs != priv->cs ||
speed != priv->speed || mode != priv->speed) {
priv->bus = bus;
priv->cs = cs;
priv->speed = speed;
priv->mode = mode;
if (priv->slave) {
free(priv->slave);
priv->slave = NULL;
}
you shouldnt be poking the spi internals like this. use the common spi funcs to free/alloc the slave device.
OK.
Best regards, Thomas

This patch supports mmc/sd card with spi interface. It is based on the generic mmc framework. It works with SDHC and supports write.
The crc7 lib func is merged from linux and used to compute mmc command checksum.
There is a subcomamnd "mmc_spi" to setup spi bus and cs at run time.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- v4: change mmc_spi subcommand to search and create new mmc dev. v3: add mmc_spi_init() proto to mmc_spi.h. v2: add crc7, use cmd58 to read ocr, add subcommand mmc_spi.
common/Makefile | 1 + common/cmd_mmc_spi.c | 117 ++++++++++++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/mmc_spi.c | 318 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/crc7.h | 14 ++ include/mmc_spi.h | 27 ++++ lib/Makefile | 1 + lib/crc7.c | 62 ++++++++++ 8 files changed, 541 insertions(+), 0 deletions(-) create mode 100644 common/cmd_mmc_spi.c create mode 100644 drivers/mmc/mmc_spi.c create mode 100644 include/linux/crc7.h create mode 100644 include/mmc_spi.h create mode 100644 lib/crc7.c
diff --git a/common/Makefile b/common/Makefile index dbf7a05..ee23e2f 100644 --- a/common/Makefile +++ b/common/Makefile @@ -118,6 +118,7 @@ COBJS-$(CONFIG_CMD_MII) += miiphyutil.o COBJS-$(CONFIG_CMD_MII) += cmd_mii.o COBJS-$(CONFIG_CMD_MISC) += cmd_misc.o COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o +COBJS-$(CONFIG_CMD_MMC_SPI) += cmd_mmc_spi.o COBJS-$(CONFIG_MP) += cmd_mp.o COBJS-$(CONFIG_CMD_MTDPARTS) += cmd_mtdparts.o COBJS-$(CONFIG_CMD_NAND) += cmd_nand.o diff --git a/common/cmd_mmc_spi.c b/common/cmd_mmc_spi.c new file mode 100644 index 0000000..d883837 --- /dev/null +++ b/common/cmd_mmc_spi.c @@ -0,0 +1,117 @@ +/* + * Command for mmc_spi setup. + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> + +#define MMC_SPI_DEV_NUM_MAX 16 /* number of MMC devs to query */ +#ifndef CONFIG_MMC_SPI_BUS +# define CONFIG_MMC_SPI_BUS 0 +#endif +#ifndef CONFIG_MMC_SPI_CS +# define CONFIG_MMC_SPI_CS 1 +#endif +#ifndef CONFIG_MMC_SPI_SPEED +# define CONFIG_MMC_SPI_SPEED 30000000 +#endif +#ifndef CONFIG_MMC_SPI_MODE +# define CONFIG_MMC_SPI_MODE SPI_MODE_3 +#endif + +static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{ + int dev_num; + uint bus; + uint cs; + uint speed; + uint mode; + char *endp; + struct mmc *mmc; + struct mmc_spi_priv *priv; + + for (dev_num = 0; dev_num < MMC_SPI_DEV_NUM_MAX; dev_num++) { + mmc = find_mmc_device(dev_num); + if (!mmc || strcmp(mmc->name, "MMC_SPI") == 0) + break; + } + if (!mmc || dev_num == MMC_SPI_DEV_NUM_MAX) { + printf("Create MMC Device\n"); + mmc = mmc_spi_init(CONFIG_MMC_SPI_BUS, + CONFIG_MMC_SPI_CS, + CONFIG_MMC_SPI_SPEED, + CONFIG_MMC_SPI_MODE); + if (!mmc) { + printf("Failed to create MMC Device\n"); + return 1; + } + dev_num = mmc->block_dev.dev; + } + + priv = mmc->priv; + bus = priv->bus; + cs = priv->cs; + speed = priv->speed; + mode = priv->mode; + + if (argc < 2) + goto info; + cs = simple_strtoul(argv[1], &endp, 0); + if (*argv[1] == 0 || (*endp != 0 && *endp != ':')) + goto usage; + if (*endp == ':') { + if (endp[1] == 0) + goto usage; + bus = cs; + cs = simple_strtoul(endp + 1, &endp, 0); + if (*endp != 0) + goto usage; + } + if (argc >= 3) { + speed = simple_strtoul(argv[2], &endp, 0); + if (*argv[2] == 0 || *endp != 0) + goto usage; + } + if (argc >= 4) { + mode = simple_strtoul(argv[3], &endp, 16); + if (*argv[3] == 0 || *endp != 0) + goto usage; + } + if (!spi_cs_is_valid(bus, cs)) { + printf("Invalid SPI bus %u cs %u\n", bus, cs); + return 1; + } + + if (bus != priv->bus || cs != priv->cs || + speed != priv->speed || mode != priv->mode) { + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + if (priv->slave) { + spi_free_slave(priv->slave); + priv->slave = NULL; + } + } +info: + printf("%s: %d at %u:%u %u %u\n", mmc->name, dev_num, + bus, cs, speed, mode); + return 0; + +usage: + cmd_usage(cmdtp); + return 1; +} + +U_BOOT_CMD( + mmc_spi, 4, 0, do_mmc_spi, + "mmc_spi setup", + "[bus:][cs] [hz] [mode] - setup mmc_spi device on given\n" + " SPI bus and chip select\n" +); diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 6fa04b8..02ed329 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libmmc.a COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..dedcef7 --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,318 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> +#include <linux/crc7.h> +#include <asm/errno.h> + +#define CTOUT 0x10 +#define RTOUT 0x10000 +#define WTOUT 0x10000 + +static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 cmdo[7]; + u8 r1; + int i; + cmdo[0] = 0xff; + cmdo[1] = 0x40 + cmdidx; + cmdo[2] = cmdarg >> 24; + cmdo[3] = cmdarg >> 16; + cmdo[4] = cmdarg >> 8; + cmdo[5] = cmdarg; + cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01; + spi_xfer(priv->slave, 7 * 8, cmdo, NULL, SPI_XFER_BEGIN); + for (i = 0; i < CTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x80) == 0) + break; + } + debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1); + return r1; +} + +static uint mmc_spi_readdata(struct mmc *mmc, char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 crc[2]; + int i; + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 != 0xff) + break; + } + debug("%s:tok%d %x\n", __func__, i, r1); + if (r1 == 0xfe) { + spi_xfer(priv->slave, bsize * 8, NULL, buf, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, NULL, crc, 0); + r1 = 0; + } else + break; + } + return r1; +} + +static uint mmc_spi_writedata(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = { 0xff, 0xfe }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } else + break; + } + return r1; +} + +static uint mmc_spi_writeblock(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = { 0xff, 0xfc }; + u8 stop[2] = { 0xff, 0xfd }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } + } + if (bcnt == 0 && r1 == 0) { + spi_xfer(priv->slave, 2 * 8, stop, NULL, 0); + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + } + } + return r1; +} + +static inline uint rswab(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static inline void mmc_spi_deactivate(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + spi_xfer(priv->slave, 1 * 8, NULL, NULL, SPI_XFER_END); + spi_xfer(priv->slave, 1 * 8, NULL, NULL, 0); +} + +static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + ushort cmdidx = cmd->cmdidx; + uint cmdarg = cmd->cmdarg; + u8 resp[16]; + int i; + int ret = 0; + debug("%s:cmd%d %x %x %x\n", __func__, + cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags); + if (cmdidx == MMC_CMD_ALL_SEND_CID) + cmdidx = MMC_CMD_SEND_CID; + r1 = mmc_spi_sendcmd(mmc, cmdidx, cmdarg); + if (r1 == 0xff) { + ret = NO_CARD_ERR; + goto done; + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(mmc, resp, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = rswab(resp + i * 4); + debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } else { + if (!data) { + spi_xfer(priv->slave, 4 * 8, NULL, resp, 0); + cmd->response[0] = rswab(resp); + debug("r32 %x\n", cmd->response[0]); + } + if (cmdidx == MMC_CMD_GO_IDLE_STATE) { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswab(resp) & ~OCR_BUSY; + debug("ocr58 %x\n", priv->ocr58); + } else if (cmdidx == SD_CMD_SEND_IF_COND) { + if ((r1 & 0x7e) == 0) + priv->sd_if_cond = 1; + } else if (cmdidx == SD_CMD_APP_SEND_OP_COND || + cmdidx == MMC_CMD_SEND_OP_COND) { + if (r1 & 0x04) + ret = TIMEOUT; + else { + if (r1 & 0x01) + cmd->response[0] = priv->ocr58; + else { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswab(resp); + debug("ocr58 %x\n", priv->ocr58); + cmd->response[0] = priv->ocr58; + } + } + } else { + cmd->response[0] = 0; + if (r1 & 0x7e) + cmd->response[0] |= (1 << 19); + if (r1 & 0x40) + cmd->response[0] |= (1 << 31); + if (r1 & 0x20) + cmd->response[0] |= (1 << 30); + if (r1 & 0x10) + cmd->response[0] |= (1 << 28); + if (r1 & 0x08) + cmd->response[0] |= (1 << 23); + if (r1 & 0x04) + cmd->response[0] |= (1 << 22); + if (r1 & 0x02) + cmd->response[0] |= (1 << 13); + } + } + if (data) { + debug("%s:data %x %x %x\n", __func__, + data->flags, data->blocks, data->blocksize); + if (data->flags == MMC_DATA_READ) { + r1 = mmc_spi_readdata(mmc, data->dest, + data->blocks, data->blocksize); + } else if (data->flags == MMC_DATA_WRITE) { + if (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK) + r1 = mmc_spi_writeblock(mmc, data->src, + data->blocks, data->blocksize); + else + r1 = mmc_spi_writedata(mmc, data->src, + data->blocks, data->blocksize); + } + } +done: + mmc_spi_deactivate(mmc); + return ret; +} + +static void mmc_spi_set_ios(struct mmc *mmc) +{ + debug("%s:\n", __func__); +} + +static int mmc_spi_init_p(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + debug("%s:\n", __func__); + if (!priv->slave) { + debug("%s: setup %u:%u %u %u\n", __func__, + priv->bus, priv->cs, + priv->speed, priv->mode); + priv->slave = spi_setup_slave(priv->bus, priv->cs, + priv->speed, priv->mode); + if (!priv->slave) { + debug("%s: failed to setup spi slave\n", __func__); + return -ENOMEM; + } + } + priv->sd_if_cond = 0; + priv->ocr58 = 0; + spi_claim_bus(priv->slave); + /* power on initialization */ + spi_xfer(priv->slave, 39 * 8, NULL, NULL, + SPI_XFER_BEGIN | SPI_XFER_END); + spi_xfer(priv->slave, 18 * 8, NULL, NULL, 0); + return 0; +} + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{ + struct mmc *mmc; + struct mmc_spi_priv *priv; + + mmc = malloc(sizeof(*mmc)); + if (!mmc) + return NULL; + priv = malloc(sizeof(*priv)); + if (!priv) { + free(mmc); + return NULL; + } + mmc->priv = priv; + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + sprintf(mmc->name, "MMC_SPI"); + mmc->send_cmd = mmc_spi_request; + mmc->set_ios = mmc_spi_set_ios; + mmc->init = mmc_spi_init_p; + mmc->host_caps = 0; + + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->f_max = speed; + mmc->f_min = mmc->f_max >> 9; + mmc->block_dev.part_type = PART_TYPE_DOS; + + mmc_register(mmc); + + return mmc; +} diff --git a/include/linux/crc7.h b/include/linux/crc7.h new file mode 100644 index 0000000..1786e77 --- /dev/null +++ b/include/linux/crc7.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_CRC7_H +#define _LINUX_CRC7_H +#include <linux/types.h> + +extern const u8 crc7_syndrome_table[256]; + +static inline u8 crc7_byte(u8 crc, u8 data) +{ + return crc7_syndrome_table[(crc << 1) ^ data]; +} + +extern u8 crc7(u8 crc, const u8 *buffer, size_t len); + +#endif diff --git a/include/mmc_spi.h b/include/mmc_spi.h new file mode 100644 index 0000000..fbfa1ce --- /dev/null +++ b/include/mmc_spi.h @@ -0,0 +1,27 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#ifndef _MMC_SPI_H_ +#define _MMC_SPI_H_ + +#include <spi.h> +#include <mmc.h> +#include <linux/types.h> + +struct mmc_spi_priv { + struct spi_slave *slave; + uint bus; + uint cs; + uint speed; + uint mode; + uint ocr58; + uint sd_if_cond:1; +}; + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode); + +#endif diff --git a/lib/Makefile b/lib/Makefile index c45f07c..aef2893 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -32,6 +32,7 @@ COBJS-$(CONFIG_BZIP2) += bzlib_decompress.o COBJS-$(CONFIG_BZIP2) += bzlib_randtable.o COBJS-$(CONFIG_BZIP2) += bzlib_huffman.o COBJS-$(CONFIG_USB_TTY) += circbuf.o +COBJS-y += crc7.o COBJS-y += crc16.o COBJS-y += crc32.o COBJS-y += ctype.o diff --git a/lib/crc7.c b/lib/crc7.c new file mode 100644 index 0000000..e635c9c --- /dev/null +++ b/lib/crc7.c @@ -0,0 +1,62 @@ +/* + * crc7.c + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/types.h> +#include <linux/crc7.h> + + +/* Table for CRC-7 (polynomial x^7 + x^3 + 1) */ +const u8 crc7_syndrome_table[256] = { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, + 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, + 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, + 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, + 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, + 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, + 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c, + 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, + 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, + 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, + 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a, + 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, + 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21, + 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, + 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, + 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, + 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36, + 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, + 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f, + 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, + 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, + 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d, + 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, + 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52, + 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, + 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, + 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, + 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60, + 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, + 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79 +}; + +/** + * crc7 - update the CRC7 for the data buffer + * @crc: previous CRC7 value + * @buffer: data pointer + * @len: number of bytes in the buffer + * Context: any + * + * Returns the updated CRC7 value. + */ +u8 crc7(u8 crc, const u8 *buffer, size_t len) +{ + while (len--) + crc = crc7_byte(crc, *buffer++); + return crc; +}

This patch supports mmc/sd card with spi interface. It is based on the generic mmc framework. It works with SDHC and supports write.
The crc7 lib func is merged from linux and used to compute mmc command checksum.
There is a subcomamnd "mmc_spi" to setup spi bus and cs at run time.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- v5: remove dev_num limit to search. v4: change mmc_spi subcommand to search and create new mmc dev. v3: add mmc_spi_init() proto to mmc_spi.h. v2: add crc7, use cmd58 to read ocr, add subcommand mmc_spi.
common/Makefile | 1 + common/cmd_mmc_spi.c | 114 ++++++++++++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/mmc_spi.c | 318 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/crc7.h | 14 ++ include/mmc_spi.h | 27 ++++ lib/Makefile | 1 + lib/crc7.c | 62 ++++++++++ 8 files changed, 538 insertions(+), 0 deletions(-) create mode 100644 common/cmd_mmc_spi.c create mode 100644 drivers/mmc/mmc_spi.c create mode 100644 include/linux/crc7.h create mode 100644 include/mmc_spi.h create mode 100644 lib/crc7.c
diff --git a/common/Makefile b/common/Makefile index dbf7a05..ee23e2f 100644 --- a/common/Makefile +++ b/common/Makefile @@ -118,6 +118,7 @@ COBJS-$(CONFIG_CMD_MII) += miiphyutil.o COBJS-$(CONFIG_CMD_MII) += cmd_mii.o COBJS-$(CONFIG_CMD_MISC) += cmd_misc.o COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o +COBJS-$(CONFIG_CMD_MMC_SPI) += cmd_mmc_spi.o COBJS-$(CONFIG_MP) += cmd_mp.o COBJS-$(CONFIG_CMD_MTDPARTS) += cmd_mtdparts.o COBJS-$(CONFIG_CMD_NAND) += cmd_nand.o diff --git a/common/cmd_mmc_spi.c b/common/cmd_mmc_spi.c new file mode 100644 index 0000000..0cbb449 --- /dev/null +++ b/common/cmd_mmc_spi.c @@ -0,0 +1,114 @@ +/* + * Command for mmc_spi setup. + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> + +#ifndef CONFIG_MMC_SPI_BUS +# define CONFIG_MMC_SPI_BUS 0 +#endif +#ifndef CONFIG_MMC_SPI_CS +# define CONFIG_MMC_SPI_CS 1 +#endif +#ifndef CONFIG_MMC_SPI_SPEED +# define CONFIG_MMC_SPI_SPEED 30000000 +#endif +#ifndef CONFIG_MMC_SPI_MODE +# define CONFIG_MMC_SPI_MODE SPI_MODE_3 +#endif + +static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{ + int dev_num = -1; + uint bus; + uint cs; + uint speed; + uint mode; + char *endp; + struct mmc *mmc; + struct mmc_spi_priv *priv; + + do { + mmc = find_mmc_device(++dev_num); + } while (mmc && strcmp(mmc->name, "MMC_SPI")); + if (!mmc) { + printf("Create MMC Device\n"); + mmc = mmc_spi_init(CONFIG_MMC_SPI_BUS, + CONFIG_MMC_SPI_CS, + CONFIG_MMC_SPI_SPEED, + CONFIG_MMC_SPI_MODE); + if (!mmc) { + printf("Failed to create MMC Device\n"); + return 1; + } + dev_num = mmc->block_dev.dev; + } + + priv = mmc->priv; + bus = priv->bus; + cs = priv->cs; + speed = priv->speed; + mode = priv->mode; + + if (argc < 2) + goto info; + cs = simple_strtoul(argv[1], &endp, 0); + if (*argv[1] == 0 || (*endp != 0 && *endp != ':')) + goto usage; + if (*endp == ':') { + if (endp[1] == 0) + goto usage; + bus = cs; + cs = simple_strtoul(endp + 1, &endp, 0); + if (*endp != 0) + goto usage; + } + if (argc >= 3) { + speed = simple_strtoul(argv[2], &endp, 0); + if (*argv[2] == 0 || *endp != 0) + goto usage; + } + if (argc >= 4) { + mode = simple_strtoul(argv[3], &endp, 16); + if (*argv[3] == 0 || *endp != 0) + goto usage; + } + if (!spi_cs_is_valid(bus, cs)) { + printf("Invalid SPI bus %u cs %u\n", bus, cs); + return 1; + } + + if (bus != priv->bus || cs != priv->cs || + speed != priv->speed || mode != priv->mode) { + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + if (priv->slave) { + spi_free_slave(priv->slave); + priv->slave = NULL; + } + } +info: + printf("%s: %d at %u:%u %u %u\n", mmc->name, dev_num, + bus, cs, speed, mode); + return 0; + +usage: + cmd_usage(cmdtp); + return 1; +} + +U_BOOT_CMD( + mmc_spi, 4, 0, do_mmc_spi, + "mmc_spi setup", + "[bus:][cs] [hz] [mode] - setup mmc_spi device on given\n" + " SPI bus and chip select\n" +); diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 6fa04b8..02ed329 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libmmc.a COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..dedcef7 --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,318 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> +#include <linux/crc7.h> +#include <asm/errno.h> + +#define CTOUT 0x10 +#define RTOUT 0x10000 +#define WTOUT 0x10000 + +static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 cmdo[7]; + u8 r1; + int i; + cmdo[0] = 0xff; + cmdo[1] = 0x40 + cmdidx; + cmdo[2] = cmdarg >> 24; + cmdo[3] = cmdarg >> 16; + cmdo[4] = cmdarg >> 8; + cmdo[5] = cmdarg; + cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01; + spi_xfer(priv->slave, 7 * 8, cmdo, NULL, SPI_XFER_BEGIN); + for (i = 0; i < CTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x80) == 0) + break; + } + debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1); + return r1; +} + +static uint mmc_spi_readdata(struct mmc *mmc, char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 crc[2]; + int i; + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 != 0xff) + break; + } + debug("%s:tok%d %x\n", __func__, i, r1); + if (r1 == 0xfe) { + spi_xfer(priv->slave, bsize * 8, NULL, buf, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, NULL, crc, 0); + r1 = 0; + } else + break; + } + return r1; +} + +static uint mmc_spi_writedata(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = { 0xff, 0xfe }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } else + break; + } + return r1; +} + +static uint mmc_spi_writeblock(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = { 0xff, 0xfc }; + u8 stop[2] = { 0xff, 0xfd }; + u8 crc[2]; + int i; + while (bcnt--) { + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + buf += bsize; + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0x05) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + break; + } + } + } + if (bcnt == 0 && r1 == 0) { + spi_xfer(priv->slave, 2 * 8, stop, NULL, 0); + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = 0x04; + } + } + return r1; +} + +static inline uint rswab(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static inline void mmc_spi_deactivate(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + spi_xfer(priv->slave, 1 * 8, NULL, NULL, SPI_XFER_END); + spi_xfer(priv->slave, 1 * 8, NULL, NULL, 0); +} + +static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + ushort cmdidx = cmd->cmdidx; + uint cmdarg = cmd->cmdarg; + u8 resp[16]; + int i; + int ret = 0; + debug("%s:cmd%d %x %x %x\n", __func__, + cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags); + if (cmdidx == MMC_CMD_ALL_SEND_CID) + cmdidx = MMC_CMD_SEND_CID; + r1 = mmc_spi_sendcmd(mmc, cmdidx, cmdarg); + if (r1 == 0xff) { + ret = NO_CARD_ERR; + goto done; + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(mmc, resp, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = rswab(resp + i * 4); + debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } else { + if (!data) { + spi_xfer(priv->slave, 4 * 8, NULL, resp, 0); + cmd->response[0] = rswab(resp); + debug("r32 %x\n", cmd->response[0]); + } + if (cmdidx == MMC_CMD_GO_IDLE_STATE) { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswab(resp) & ~OCR_BUSY; + debug("ocr58 %x\n", priv->ocr58); + } else if (cmdidx == SD_CMD_SEND_IF_COND) { + if ((r1 & 0x7e) == 0) + priv->sd_if_cond = 1; + } else if (cmdidx == SD_CMD_APP_SEND_OP_COND || + cmdidx == MMC_CMD_SEND_OP_COND) { + if (r1 & 0x04) + ret = TIMEOUT; + else { + if (r1 & 0x01) + cmd->response[0] = priv->ocr58; + else { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, 58, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswab(resp); + debug("ocr58 %x\n", priv->ocr58); + cmd->response[0] = priv->ocr58; + } + } + } else { + cmd->response[0] = 0; + if (r1 & 0x7e) + cmd->response[0] |= (1 << 19); + if (r1 & 0x40) + cmd->response[0] |= (1 << 31); + if (r1 & 0x20) + cmd->response[0] |= (1 << 30); + if (r1 & 0x10) + cmd->response[0] |= (1 << 28); + if (r1 & 0x08) + cmd->response[0] |= (1 << 23); + if (r1 & 0x04) + cmd->response[0] |= (1 << 22); + if (r1 & 0x02) + cmd->response[0] |= (1 << 13); + } + } + if (data) { + debug("%s:data %x %x %x\n", __func__, + data->flags, data->blocks, data->blocksize); + if (data->flags == MMC_DATA_READ) { + r1 = mmc_spi_readdata(mmc, data->dest, + data->blocks, data->blocksize); + } else if (data->flags == MMC_DATA_WRITE) { + if (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK) + r1 = mmc_spi_writeblock(mmc, data->src, + data->blocks, data->blocksize); + else + r1 = mmc_spi_writedata(mmc, data->src, + data->blocks, data->blocksize); + } + } +done: + mmc_spi_deactivate(mmc); + return ret; +} + +static void mmc_spi_set_ios(struct mmc *mmc) +{ + debug("%s:\n", __func__); +} + +static int mmc_spi_init_p(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + debug("%s:\n", __func__); + if (!priv->slave) { + debug("%s: setup %u:%u %u %u\n", __func__, + priv->bus, priv->cs, + priv->speed, priv->mode); + priv->slave = spi_setup_slave(priv->bus, priv->cs, + priv->speed, priv->mode); + if (!priv->slave) { + debug("%s: failed to setup spi slave\n", __func__); + return -ENOMEM; + } + } + priv->sd_if_cond = 0; + priv->ocr58 = 0; + spi_claim_bus(priv->slave); + /* power on initialization */ + spi_xfer(priv->slave, 39 * 8, NULL, NULL, + SPI_XFER_BEGIN | SPI_XFER_END); + spi_xfer(priv->slave, 18 * 8, NULL, NULL, 0); + return 0; +} + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{ + struct mmc *mmc; + struct mmc_spi_priv *priv; + + mmc = malloc(sizeof(*mmc)); + if (!mmc) + return NULL; + priv = malloc(sizeof(*priv)); + if (!priv) { + free(mmc); + return NULL; + } + mmc->priv = priv; + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + sprintf(mmc->name, "MMC_SPI"); + mmc->send_cmd = mmc_spi_request; + mmc->set_ios = mmc_spi_set_ios; + mmc->init = mmc_spi_init_p; + mmc->host_caps = 0; + + mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->f_max = speed; + mmc->f_min = mmc->f_max >> 9; + mmc->block_dev.part_type = PART_TYPE_DOS; + + mmc_register(mmc); + + return mmc; +} diff --git a/include/linux/crc7.h b/include/linux/crc7.h new file mode 100644 index 0000000..1786e77 --- /dev/null +++ b/include/linux/crc7.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_CRC7_H +#define _LINUX_CRC7_H +#include <linux/types.h> + +extern const u8 crc7_syndrome_table[256]; + +static inline u8 crc7_byte(u8 crc, u8 data) +{ + return crc7_syndrome_table[(crc << 1) ^ data]; +} + +extern u8 crc7(u8 crc, const u8 *buffer, size_t len); + +#endif diff --git a/include/mmc_spi.h b/include/mmc_spi.h new file mode 100644 index 0000000..fbfa1ce --- /dev/null +++ b/include/mmc_spi.h @@ -0,0 +1,27 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#ifndef _MMC_SPI_H_ +#define _MMC_SPI_H_ + +#include <spi.h> +#include <mmc.h> +#include <linux/types.h> + +struct mmc_spi_priv { + struct spi_slave *slave; + uint bus; + uint cs; + uint speed; + uint mode; + uint ocr58; + uint sd_if_cond:1; +}; + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode); + +#endif diff --git a/lib/Makefile b/lib/Makefile index c45f07c..aef2893 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -32,6 +32,7 @@ COBJS-$(CONFIG_BZIP2) += bzlib_decompress.o COBJS-$(CONFIG_BZIP2) += bzlib_randtable.o COBJS-$(CONFIG_BZIP2) += bzlib_huffman.o COBJS-$(CONFIG_USB_TTY) += circbuf.o +COBJS-y += crc7.o COBJS-y += crc16.o COBJS-y += crc32.o COBJS-y += ctype.o diff --git a/lib/crc7.c b/lib/crc7.c new file mode 100644 index 0000000..e635c9c --- /dev/null +++ b/lib/crc7.c @@ -0,0 +1,62 @@ +/* + * crc7.c + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/types.h> +#include <linux/crc7.h> + + +/* Table for CRC-7 (polynomial x^7 + x^3 + 1) */ +const u8 crc7_syndrome_table[256] = { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, + 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, + 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, + 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, + 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, + 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, + 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c, + 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, + 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, + 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, + 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a, + 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, + 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21, + 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, + 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, + 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, + 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36, + 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, + 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f, + 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, + 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, + 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d, + 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, + 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52, + 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, + 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, + 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, + 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60, + 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, + 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79 +}; + +/** + * crc7 - update the CRC7 for the data buffer + * @crc: previous CRC7 value + * @buffer: data pointer + * @len: number of bytes in the buffer + * Context: any + * + * Returns the updated CRC7 value. + */ +u8 crc7(u8 crc, const u8 *buffer, size_t len) +{ + while (len--) + crc = crc7_byte(crc, *buffer++); + return crc; +}

On Wed, Apr 28, 2010 at 1:00 AM, Thomas Chou thomas@wytron.com.tw wrote:
This patch supports mmc/sd card with spi interface. It is based on the generic mmc framework. It works with SDHC and supports write.
The crc7 lib func is merged from linux and used to compute mmc command checksum.
This should probably be a separate patch.
There is a subcomamnd "mmc_spi" to setup spi bus and cs at run time.
Signed-off-by: Thomas Chou thomas@wytron.com.tw
+static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{
- int dev_num = -1;
- uint bus;
- uint cs;
- uint speed;
- uint mode;
- char *endp;
- struct mmc *mmc;
- struct mmc_spi_priv *priv;
- do {
- mmc = find_mmc_device(++dev_num);
- } while (mmc && strcmp(mmc->name, "MMC_SPI"));
- if (!mmc) {
- printf("Create MMC Device\n");
- mmc = mmc_spi_init(CONFIG_MMC_SPI_BUS,
- CONFIG_MMC_SPI_CS,
- CONFIG_MMC_SPI_SPEED,
- CONFIG_MMC_SPI_MODE);
- if (!mmc) {
- printf("Failed to create MMC Device\n");
- return 1;
- }
- dev_num = mmc->block_dev.dev;
- }
I'm not sure I understand the logic behind this code. The arguments to the command should be used to either find the already-existing bus, or to create a new one. Unless I'm misunderstanding, this searches for the first MMC_SPI bus, and if it finds it, uses that, otherwise it creates a new one with the values specified in the config file. Then it parses the command and overwrites the old parameters for the bus with new ones? Why? My instinct would be to create a separate instance of an MMC_SPI bus for each bus and chip select. My SPI is rusty, so maybe chip-select should be configurable on a use-by-use basis.
Certainly the current code will only use at most one MMC_SPI bus even if more are created, which seems wrong.
- priv = mmc->priv;
- bus = priv->bus;
- cs = priv->cs;
- speed = priv->speed;
- mode = priv->mode;
- if (argc < 2)
- goto info;
- cs = simple_strtoul(argv[1], &endp, 0);
- if (*argv[1] == 0 || (*endp != 0 && *endp != ':'))
- goto usage;
- if (*endp == ':') {
- if (endp[1] == 0)
- goto usage;
- bus = cs;
- cs = simple_strtoul(endp + 1, &endp, 0);
- if (*endp != 0)
- goto usage;
- }
- if (argc >= 3) {
- speed = simple_strtoul(argv[2], &endp, 0);
- if (*argv[2] == 0 || *endp != 0)
- goto usage;
- }
- if (argc >= 4) {
- mode = simple_strtoul(argv[3], &endp, 16);
- if (*argv[3] == 0 || *endp != 0)
- goto usage;
- }
- if (!spi_cs_is_valid(bus, cs)) {
- printf("Invalid SPI bus %u cs %u\n", bus, cs);
- return 1;
- }
- if (bus != priv->bus || cs != priv->cs ||
- speed != priv->speed || mode != priv->mode) {
- priv->bus = bus;
- priv->cs = cs;
- priv->speed = speed;
- priv->mode = mode;
- if (priv->slave) {
- spi_free_slave(priv->slave);
- priv->slave = NULL;
- }
- }
diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..dedcef7 --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,318 @@ +/*
- generic mmc spi driver
+static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{
- struct mmc_spi_priv *priv = mmc->priv;
- u8 cmdo[7];
- u8 r1;
- int i;
- cmdo[0] = 0xff;
- cmdo[1] = 0x40 + cmdidx;
Use a #define'd constant for 0x40. Maybe do this:
/* MMC SPI commands start with a start bit "0" and a transmit bit "1" */ #define MMC_SPI_CMD(x) (0x40 | (x & 0x3f))
cmdo[1] = MMC_SPI_CMD(cmdidx);
- cmdo[2] = cmdarg >> 24;
- cmdo[3] = cmdarg >> 16;
- cmdo[4] = cmdarg >> 8;
- cmdo[5] = cmdarg;
- cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01;
- spi_xfer(priv->slave, 7 * 8, cmdo, NULL, SPI_XFER_BEGIN);
- for (i = 0; i < CTOUT; i++) {
- spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0);
- if ((r1 & 0x80) == 0)
define a constant
- break;
- }
- debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1);
- return r1;
+}
+static uint mmc_spi_readdata(struct mmc *mmc, char *buf,
- u32 bcnt, u32 bsize)
+{
- struct mmc_spi_priv *priv = mmc->priv;
- u8 r1;
- u8 crc[2];
- int i;
- while (bcnt--) {
- for (i = 0; i < RTOUT; i++) {
- spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0);
- if (r1 != 0xff)
- break;
- }
- debug("%s:tok%d %x\n", __func__, i, r1);
- if (r1 == 0xfe) {
- spi_xfer(priv->slave, bsize * 8, NULL, buf, 0);
- buf += bsize;
- spi_xfer(priv->slave, 2 * 8, NULL, crc, 0);
- r1 = 0;
- } else
- break;
- }
- return r1;
Why not check the CRC to make sure your data is correct?
+}
+static uint mmc_spi_writedata(struct mmc *mmc, const char *buf,
- u32 bcnt, u32 bsize)
+{
- struct mmc_spi_priv *priv = mmc->priv;
- u8 r1;
- u8 tok[2] = { 0xff, 0xfe };
- u8 crc[2];
- int i;
- while (bcnt--) {
- spi_xfer(priv->slave, 2 * 8, tok, NULL, 0);
- spi_xfer(priv->slave, bsize * 8, buf, NULL, 0);
- buf += bsize;
- spi_xfer(priv->slave, 2 * 8, crc, NULL, 0);
- spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0);
- if (r1 == 0x05) {
Either define a named constant for 0x05, or put a comment describing what's happening. Isn't this technically a data response, rather than r1? Also,
- for (i = 0; i < WTOUT; i++) {
- spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0);
- if (r1 == 0xff) {
- r1 = 0;
- break;
- }
- }
- if (i == WTOUT) {
- debug("%s:wtout %x\n", __func__, r1);
- r1 = 0x04;
A constant for this would be good, too.
- break;
- }
- } else
- break;
- }
- return r1;
+}
+static uint mmc_spi_writeblock(struct mmc *mmc, const char *buf,
- u32 bcnt, u32 bsize)
+{
- struct mmc_spi_priv *priv = mmc->priv;
- u8 r1;
- u8 tok[2] = { 0xff, 0xfc };
- u8 stop[2] = { 0xff, 0xfd };
Constants for 0xfc and 0xfd, please.
- u8 crc[2];
- int i;
- while (bcnt--) {
- spi_xfer(priv->slave, 2 * 8, tok, NULL, 0);
- spi_xfer(priv->slave, bsize * 8, buf, NULL, 0);
- buf += bsize;
- spi_xfer(priv->slave, 2 * 8, crc, NULL, 0);
- spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0);
- if (r1 == 0x05) {
- for (i = 0; i < WTOUT; i++) {
- spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0);
- if (r1 == 0xff) {
- r1 = 0;
- break;
- }
- }
- if (i == WTOUT) {
- debug("%s:wtout %x\n", __func__, r1);
- r1 = 0x04;
- break;
- }
- }
- }
- if (bcnt == 0 && r1 == 0) {
- spi_xfer(priv->slave, 2 * 8, stop, NULL, 0);
- for (i = 0; i < WTOUT; i++) {
- spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0);
- if (r1 == 0xff) {
- r1 = 0;
- break;
- }
- }
- if (i == WTOUT) {
- debug("%s:wtout %x\n", __func__, r1);
- r1 = 0x04;
- }
- }
- return r1;
+}
+static inline uint rswab(u8 *d)
Did you mean "swap", here?
+{
- return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3];
+}
+static inline void mmc_spi_deactivate(struct mmc *mmc) +{
- struct mmc_spi_priv *priv = mmc->priv;
- spi_xfer(priv->slave, 1 * 8, NULL, NULL, SPI_XFER_END);
- spi_xfer(priv->slave, 1 * 8, NULL, NULL, 0);
+}
+static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd,
- struct mmc_data *data)
+{
- struct mmc_spi_priv *priv = mmc->priv;
- u8 r1;
- ushort cmdidx = cmd->cmdidx;
- uint cmdarg = cmd->cmdarg;
- u8 resp[16];
- int i;
- int ret = 0;
- debug("%s:cmd%d %x %x %x\n", __func__,
- cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags);
- if (cmdidx == MMC_CMD_ALL_SEND_CID)
- cmdidx = MMC_CMD_SEND_CID;
- r1 = mmc_spi_sendcmd(mmc, cmdidx, cmdarg);
- if (r1 == 0xff) {
- ret = NO_CARD_ERR;
- goto done;
- } else if (cmd->resp_type == MMC_RSP_R2) {
- r1 = mmc_spi_readdata(mmc, resp, 1, 16);
- for (i = 0; i < 4; i++)
- cmd->response[i] = rswab(resp + i * 4);
- debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1],
- cmd->response[2], cmd->response[3]);
- } else {
- if (!data) {
- spi_xfer(priv->slave, 4 * 8, NULL, resp, 0);
- cmd->response[0] = rswab(resp);
- debug("r32 %x\n", cmd->response[0]);
- }
- if (cmdidx == MMC_CMD_GO_IDLE_STATE) {
- mmc_spi_deactivate(mmc);
- r1 = mmc_spi_sendcmd(mmc, 58, 0);
Name "58", please.
- spi_xfer(priv->slave, 4 * 8,
- NULL, resp, 0);
- priv->ocr58 = rswab(resp) & ~OCR_BUSY;
- debug("ocr58 %x\n", priv->ocr58);
- } else if (cmdidx == SD_CMD_SEND_IF_COND) {
- if ((r1 & 0x7e) == 0)
- priv->sd_if_cond = 1;
- } else if (cmdidx == SD_CMD_APP_SEND_OP_COND ||
- cmdidx == MMC_CMD_SEND_OP_COND) {
- if (r1 & 0x04)
- ret = TIMEOUT;
- else {
- if (r1 & 0x01)
- cmd->response[0] = priv->ocr58;
- else {
- mmc_spi_deactivate(mmc);
- r1 = mmc_spi_sendcmd(mmc, 58, 0);
- spi_xfer(priv->slave, 4 * 8,
- NULL, resp, 0);
- priv->ocr58 = rswab(resp);
- debug("ocr58 %x\n", priv->ocr58);
- cmd->response[0] = priv->ocr58;
- }
- }
- } else {
- cmd->response[0] = 0;
- if (r1 & 0x7e)
- cmd->response[0] |= (1 << 19);
- if (r1 & 0x40)
- cmd->response[0] |= (1 << 31);
- if (r1 & 0x20)
- cmd->response[0] |= (1 << 30);
- if (r1 & 0x10)
- cmd->response[0] |= (1 << 28);
- if (r1 & 0x08)
- cmd->response[0] |= (1 << 23);
- if (r1 & 0x04)
- cmd->response[0] |= (1 << 22);
- if (r1 & 0x02)
- cmd->response[0] |= (1 << 13);
- }
Hmmm.... I'm not entirely sure this is the way to go. If I'm reading this correctly, this function is converting between the standard SD protocol, and the SPI version of the protocol. It might be better to make mmc.c aware of which protocol it is using, so it a) doesn't issue commands that the SPI card doesn't understand, and b) can properly interpret responses.
- }
- if (data) {
- debug("%s:data %x %x %x\n", __func__,
- data->flags, data->blocks, data->blocksize);
- if (data->flags == MMC_DATA_READ) {
- r1 = mmc_spi_readdata(mmc, data->dest,
- data->blocks, data->blocksize);
- } else if (data->flags == MMC_DATA_WRITE) {
- if (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)
- r1 = mmc_spi_writeblock(mmc, data->src,
- data->blocks, data->blocksize);
- else
- r1 = mmc_spi_writedata(mmc, data->src,
- data->blocks, data->blocksize);
- }
Why not check r1 to make sure your writes actually succeeded?
+struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{
- struct mmc *mmc;
- struct mmc_spi_priv *priv;
- mmc = malloc(sizeof(*mmc));
- if (!mmc)
- return NULL;
- priv = malloc(sizeof(*priv));
- if (!priv) {
- free(mmc);
- return NULL;
- }
- mmc->priv = priv;
- priv->bus = bus;
- priv->cs = cs;
- priv->speed = speed;
- priv->mode = mode;
- sprintf(mmc->name, "MMC_SPI");
- mmc->send_cmd = mmc_spi_request;
- mmc->set_ios = mmc_spi_set_ios;
- mmc->init = mmc_spi_init_p;
- mmc->host_caps = 0;
- mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
Is this part of the SPI spec? If so, this is fine, otherwise, we need to get the voltage information from the SPI bus, somehow.
- mmc->f_max = speed;
- mmc->f_min = mmc->f_max >> 9;
What's the logic behind f_min being f_max/512?
- mmc->block_dev.part_type = PART_TYPE_DOS;
- mmc_register(mmc);
- return mmc;
+}
Andy

Hi Andy,
Thanks for you review.
On 04/28/2010 11:21 PM, Andy Fleming wrote:
The crc7 lib func is merged from linux and used to compute mmc command checksum.
This should probably be a separate patch.
OK.
do {
mmc = find_mmc_device(++dev_num);
} while (mmc&& strcmp(mmc->name, "MMC_SPI"));
if (!mmc) {
printf("Create MMC Device\n");
mmc = mmc_spi_init(CONFIG_MMC_SPI_BUS,
CONFIG_MMC_SPI_CS,
CONFIG_MMC_SPI_SPEED,
CONFIG_MMC_SPI_MODE);
if (!mmc) {
printf("Failed to create MMC Device\n");
return 1;
}
dev_num = mmc->block_dev.dev;
}
I'm not sure I understand the logic behind this code. The arguments to the command should be used to either find the already-existing bus, or to create a new one. Unless I'm misunderstanding, this searches for the first MMC_SPI bus, and if it finds it, uses that, otherwise it creates a new one with the values specified in the config file. Then it parses the command and overwrites the old parameters for the bus with new ones? Why? My instinct would be to create a separate instance of an MMC_SPI bus for each bus and chip select. My SPI is rusty, so maybe chip-select should be configurable on a use-by-use basis.
Certainly the current code will only use at most one MMC_SPI bus even if more are created, which seems wrong.
Though more than one MMC_SPI device can be created for specific bus and cs by calling mmc_spi_init() within board_mmc_init() or cpu_mmc_init(). This command is used to change the spi bus and chip select on a use-by-use basis at run time, so one changeable mmc_spi device (the first one if we found) should be enough.
cmdo[1] = 0x40 + cmdidx;
Use a #define'd constant for 0x40. Maybe do this:
/* MMC SPI commands start with a start bit "0" and a transmit bit "1" */ #define MMC_SPI_CMD(x) (0x40 | (x& 0x3f))
cmdo[1] = MMC_SPI_CMD(cmdidx);
OK. Will define macros for all the cmd, token and response.
Why not check the CRC to make sure your data is correct?
OK. Will check CRC on data packet.
+static inline uint rswab(u8 *d)
Did you mean "swap", here?
It should be swap.
Hmmm.... I'm not entirely sure this is the way to go. If I'm reading this correctly, this function is converting between the standard SD protocol, and the SPI version of the protocol. It might be better to make mmc.c aware of which protocol it is using, so it a) doesn't issue commands that the SPI card doesn't understand, and b) can properly interpret responses.
I have avoided to touch the core mmc.c, so that I won't break other SD hosts by accident. Only minor translation of initialization commands and status mapping is enough to support SPI protocol.
}
if (data) {
debug("%s:data %x %x %x\n", __func__,
data->flags, data->blocks, data->blocksize);
if (data->flags == MMC_DATA_READ) {
r1 = mmc_spi_readdata(mmc, data->dest,
data->blocks, data->blocksize);
} else if (data->flags == MMC_DATA_WRITE) {
if (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)
r1 = mmc_spi_writeblock(mmc, data->src,
data->blocks, data->blocksize);
else
r1 = mmc_spi_writedata(mmc, data->src,
data->blocks, data->blocksize);
}
Why not check r1 to make sure your writes actually succeeded?
OK. Will check data response.
mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
Is this part of the SPI spec? If so, this is fine, otherwise, we need to get the voltage information from the SPI bus, somehow.
There is no voltage capability from SPI bus. This assumes 3.3V interface. Should I include other voltage?
mmc->f_max = speed;
mmc->f_min = mmc->f_max>> 9;
What's the logic behind f_min being f_max/512?
It yields f_min lower than 400KHz to meet the requirement for MMC.
Best regards, Thomas

On 04/28/2010 11:21 PM, Andy Fleming wrote:
+static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{
int dev_num = -1;
uint bus;
uint cs;
uint speed;
uint mode;
char *endp;
struct mmc *mmc;
struct mmc_spi_priv *priv;
do {
mmc = find_mmc_device(++dev_num);
} while (mmc&& strcmp(mmc->name, "MMC_SPI"));
if (!mmc) {
printf("Create MMC Device\n");
mmc = mmc_spi_init(CONFIG_MMC_SPI_BUS,
CONFIG_MMC_SPI_CS,
CONFIG_MMC_SPI_SPEED,
CONFIG_MMC_SPI_MODE);
if (!mmc) {
printf("Failed to create MMC Device\n");
return 1;
}
dev_num = mmc->block_dev.dev;
}
I'm not sure I understand the logic behind this code. The arguments to the command should be used to either find the already-existing bus, or to create a new one. Unless I'm misunderstanding, this searches for the first MMC_SPI bus, and if it finds it, uses that, otherwise it creates a new one with the values specified in the config file. Then it parses the command and overwrites the old parameters for the bus with new ones? Why? My instinct would be to create a separate instance of an MMC_SPI bus for each bus and chip select. My SPI is rusty, so maybe chip-select should be configurable on a use-by-use basis.
Certainly the current code will only use at most one MMC_SPI bus even if more are created, which seems wrong.
Hi Mike,
Could you please give me some suggestion on the mmc_spi subcommand?
1. In v5 patch, I assumed a single changeable mmc_spi device is enough.
2. Andy suggested to create a new mmc_spi device for each bus and cs.
Either way is fine to me. Which one do you prefer?
Best regards, Thomas

On 04/30/2010 03:07 AM, Mike Frysinger wrote:
On Thursday 29 April 2010 10:51:03 Thomas Chou wrote:
- Andy suggested to create a new mmc_spi device for each bus and cs.
this is what i was suggesting from the start ... sorry if i wasnt clear -mike
Hi Mike,
Thank you anyway.
Hi Andy,
I will change the command like this,
1. if no args given, show the bus and cs of existing mmc_spi devices. 2. with args given, check for duplicated bus and cs from existing devices, then create a new device.
I will need to move the spurious printing of "MMC Device %d not found\n" out from find_mmc_device() of mmc.c .
Best regards, Thomas

This patch series adds a mmc_spi driver.
Thomas Chou (3): lib: add crc7 from Linux mmc: add find_mmc_device_quiet that doesnt print not found message mmc: add generic mmc spi driver
common/Makefile | 1 + common/cmd_mmc_spi.c | 115 ++++++++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/mmc.c | 14 ++- drivers/mmc/mmc_spi.c | 393 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/crc7.h | 14 ++ include/mmc.h | 1 + include/mmc_spi.h | 27 ++++ lib/Makefile | 1 + lib/crc7.c | 62 ++++++++ 10 files changed, 626 insertions(+), 3 deletions(-) create mode 100644 common/cmd_mmc_spi.c create mode 100644 drivers/mmc/mmc_spi.c create mode 100644 include/linux/crc7.h create mode 100644 include/mmc_spi.h create mode 100644 lib/crc7.c

Crc7 is used to compute mmc spi comamnd packet checksum.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- include/linux/crc7.h | 14 +++++++++++ lib/Makefile | 1 + lib/crc7.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 0 deletions(-) create mode 100644 include/linux/crc7.h create mode 100644 lib/crc7.c
diff --git a/include/linux/crc7.h b/include/linux/crc7.h new file mode 100644 index 0000000..1786e77 --- /dev/null +++ b/include/linux/crc7.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_CRC7_H +#define _LINUX_CRC7_H +#include <linux/types.h> + +extern const u8 crc7_syndrome_table[256]; + +static inline u8 crc7_byte(u8 crc, u8 data) +{ + return crc7_syndrome_table[(crc << 1) ^ data]; +} + +extern u8 crc7(u8 crc, const u8 *buffer, size_t len); + +#endif diff --git a/lib/Makefile b/lib/Makefile index c45f07c..aef2893 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -32,6 +32,7 @@ COBJS-$(CONFIG_BZIP2) += bzlib_decompress.o COBJS-$(CONFIG_BZIP2) += bzlib_randtable.o COBJS-$(CONFIG_BZIP2) += bzlib_huffman.o COBJS-$(CONFIG_USB_TTY) += circbuf.o +COBJS-y += crc7.o COBJS-y += crc16.o COBJS-y += crc32.o COBJS-y += ctype.o diff --git a/lib/crc7.c b/lib/crc7.c new file mode 100644 index 0000000..e635c9c --- /dev/null +++ b/lib/crc7.c @@ -0,0 +1,62 @@ +/* + * crc7.c + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include <linux/types.h> +#include <linux/crc7.h> + + +/* Table for CRC-7 (polynomial x^7 + x^3 + 1) */ +const u8 crc7_syndrome_table[256] = { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, + 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26, + 0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, + 0x32, 0x3b, 0x20, 0x29, 0x16, 0x1f, 0x04, 0x0d, + 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45, + 0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, + 0x63, 0x6a, 0x71, 0x78, 0x47, 0x4e, 0x55, 0x5c, + 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b, + 0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, + 0x7d, 0x74, 0x6f, 0x66, 0x59, 0x50, 0x4b, 0x42, + 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a, + 0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, + 0x1e, 0x17, 0x0c, 0x05, 0x3a, 0x33, 0x28, 0x21, + 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70, + 0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, + 0x41, 0x48, 0x53, 0x5a, 0x65, 0x6c, 0x77, 0x7e, + 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36, + 0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, + 0x10, 0x19, 0x02, 0x0b, 0x34, 0x3d, 0x26, 0x2f, + 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, + 0x6a, 0x63, 0x78, 0x71, 0x4e, 0x47, 0x5c, 0x55, + 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d, + 0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, + 0x6d, 0x64, 0x7f, 0x76, 0x49, 0x40, 0x5b, 0x52, + 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03, + 0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, + 0x17, 0x1e, 0x05, 0x0c, 0x33, 0x3a, 0x21, 0x28, + 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60, + 0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, + 0x46, 0x4f, 0x54, 0x5d, 0x62, 0x6b, 0x70, 0x79 +}; + +/** + * crc7 - update the CRC7 for the data buffer + * @crc: previous CRC7 value + * @buffer: data pointer + * @len: number of bytes in the buffer + * Context: any + * + * Returns the updated CRC7 value. + */ +u8 crc7(u8 crc, const u8 *buffer, size_t len) +{ + while (len--) + crc = crc7_byte(crc, *buffer++); + return crc; +}

We need to query mmc devices in mmc_spi subcommand and don't want the "Device not found" message.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- drivers/mmc/mmc.c | 14 +++++++++++--- include/mmc.h | 1 + 2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index cf4ea16..55ab13e 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -60,7 +60,7 @@ int mmc_set_blocklen(struct mmc *mmc, int len) return mmc_send_cmd(mmc, &cmd, NULL); }
-struct mmc *find_mmc_device(int dev_num) +struct mmc *find_mmc_device_quiet(int dev_num) { struct mmc *m; struct list_head *entry; @@ -72,11 +72,19 @@ struct mmc *find_mmc_device(int dev_num) return m; }
- printf("MMC Device %d not found\n", dev_num); - return NULL; }
+struct mmc *find_mmc_device(int dev_num) +{ + struct mmc *m = find_mmc_device_quiet(dev_num); + + if (!m) + printf("MMC Device %d not found\n", dev_num); + + return m; +} + static ulong mmc_bwrite(int dev_num, ulong start, lbaint_t blkcnt, const void*src) { diff --git a/include/mmc.h b/include/mmc.h index 8973bc7..b24b596 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -270,6 +270,7 @@ int mmc_register(struct mmc *mmc); int mmc_initialize(bd_t *bis); int mmc_init(struct mmc *mmc); int mmc_read(struct mmc *mmc, u64 src, uchar *dst, int size); +struct mmc *find_mmc_device_quiet(int dev_num); struct mmc *find_mmc_device(int dev_num); void print_mmc_devices(char separator); int board_mmc_getcd(u8 *cd, struct mmc *mmc);

Dear Thomas Chou,
In message 1272848085-10698-3-git-send-email-thomas@wytron.com.tw you wrote:
We need to query mmc devices in mmc_spi subcommand and don't want the "Device not found" message.
Your subject line is way too long.
Signed-off-by: Thomas Chou thomas@wytron.com.tw
drivers/mmc/mmc.c | 14 +++++++++++--- include/mmc.h | 1 + 2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index cf4ea16..55ab13e 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -60,7 +60,7 @@ int mmc_set_blocklen(struct mmc *mmc, int len) return mmc_send_cmd(mmc, &cmd, NULL); }
-struct mmc *find_mmc_device(int dev_num) +struct mmc *find_mmc_device_quiet(int dev_num) { struct mmc *m; struct list_head *entry; @@ -72,11 +72,19 @@ struct mmc *find_mmc_device(int dev_num) return m; }
- printf("MMC Device %d not found\n", dev_num);
- return NULL;
}
+struct mmc *find_mmc_device(int dev_num) +{
- struct mmc *m = find_mmc_device_quiet(dev_num);
- if (!m)
printf("MMC Device %d not found\n", dev_num);
- return m;
+}
Instead of creating a new function please consider using an argument instead?
Best regards,
Wolfgang Denk

Dear Wolfgang,
On 05/07/2010 06:14 AM, Wolfgang Denk wrote:
Dear Thomas Chou,
In message1272848085-10698-3-git-send-email-thomas@wytron.com.tw you wrote:
We need to query mmc devices in mmc_spi subcommand and don't want the "Device not found" message.
Your subject line is way too long.
I will resubmit with a shorter subject.
Instead of creating a new function please consider using an argument instead?
I will add an argument, int verbose.
Thanks.
Best regards, Thomas

An argument, verbose, is added to enable/disable the "Device not found" message. Because we need to query mmc devices in mmc_spi subcommand and don't want the message.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- v2: add an argument instead of add a new func, per Wolfgang.
common/cmd_mmc.c | 8 ++++---- drivers/mmc/mmc.c | 11 ++++++----- include/mmc.h | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/common/cmd_mmc.c b/common/cmd_mmc.c index c67c9cf..9736e5a 100644 --- a/common/cmd_mmc.c +++ b/common/cmd_mmc.c @@ -124,7 +124,7 @@ int do_mmcinfo (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) else dev_num = simple_strtoul(argv[1], NULL, 0);
- mmc = find_mmc_device(dev_num); + mmc = find_mmc_device(dev_num, 1);
if (mmc) { mmc_init(mmc); @@ -148,7 +148,7 @@ int do_mmcops(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) case 3: if (strcmp(argv[1], "rescan") == 0) { int dev = simple_strtoul(argv[2], NULL, 10); - struct mmc *mmc = find_mmc_device(dev); + struct mmc *mmc = find_mmc_device(dev, 1);
if (!mmc) return 1; @@ -177,7 +177,7 @@ int do_mmcops(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) 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); + struct mmc *mmc = find_mmc_device(dev, 1);
if (!mmc) return 1; @@ -200,7 +200,7 @@ int do_mmcops(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) 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); + struct mmc *mmc = find_mmc_device(dev, 1);
int blk = simple_strtoul(argv[4], NULL, 16);
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index cf4ea16..42e7937 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -60,7 +60,7 @@ int mmc_set_blocklen(struct mmc *mmc, int len) return mmc_send_cmd(mmc, &cmd, NULL); }
-struct mmc *find_mmc_device(int dev_num) +struct mmc *find_mmc_device(int dev_num, int verbose) { struct mmc *m; struct list_head *entry; @@ -72,7 +72,8 @@ struct mmc *find_mmc_device(int dev_num) return m; }
- printf("MMC Device %d not found\n", dev_num); + if (verbose) + printf("MMC Device %d not found\n", dev_num);
return NULL; } @@ -84,7 +85,7 @@ mmc_bwrite(int dev_num, ulong start, lbaint_t blkcnt, const void*src) struct mmc_data data; int err; int stoperr = 0; - struct mmc *mmc = find_mmc_device(dev_num); + struct mmc *mmc = find_mmc_device(dev_num, 1); int blklen;
if (!mmc) @@ -214,7 +215,7 @@ static ulong mmc_bread(int dev_num, ulong start, lbaint_t blkcnt, void *dst) { int err; int i; - struct mmc *mmc = find_mmc_device(dev_num); + struct mmc *mmc = find_mmc_device(dev_num, 1);
if (!mmc) return 0; @@ -860,7 +861,7 @@ int mmc_register(struct mmc *mmc)
block_dev_desc_t *mmc_get_dev(int dev) { - struct mmc *mmc = find_mmc_device(dev); + struct mmc *mmc = find_mmc_device(dev, 1);
return mmc ? &mmc->block_dev : NULL; } diff --git a/include/mmc.h b/include/mmc.h index 8973bc7..0903491 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -270,7 +270,7 @@ int mmc_register(struct mmc *mmc); int mmc_initialize(bd_t *bis); int mmc_init(struct mmc *mmc); int mmc_read(struct mmc *mmc, u64 src, uchar *dst, int size); -struct mmc *find_mmc_device(int dev_num); +struct mmc *find_mmc_device(int dev_num, int verbose); void print_mmc_devices(char separator); int board_mmc_getcd(u8 *cd, struct mmc *mmc);

Thomas Chou wrote:
An argument, verbose, is added to enable/disable the "Device not found" message. Because we need to query mmc devices in mmc_spi subcommand and don't want the message.
Please drop this one. Because I reduced the cmd_mmc_spi.c, so that the query is not needed.
- Thomas

This patch supports mmc/sd card with spi interface. It is based on the generic mmc framework. It works with SDHC and supports write.
The crc checksum on data packet is enabled with the def, #define CONFIG_MMC_SPI_CRC_ON
There is a subcomamnd "mmc_spi" to setup spi bus and cs at run time.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- v7: use find_mmc_device(dev, verbose), per Wolfgang. v6: add constant macros, crc check on data, per Andy. v5: remove dev_num limit to search. v4: change mmc_spi subcommand to search and create new mmc dev. v3: add mmc_spi_init() proto to mmc_spi.h. v2: add crc7, use cmd58 to read ocr, add subcommand mmc_spi.
common/Makefile | 1 + common/cmd_mmc_spi.c | 115 ++++++++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/mmc_spi.c | 393 +++++++++++++++++++++++++++++++++++++++++++++++++ include/mmc_spi.h | 27 ++++ 5 files changed, 537 insertions(+), 0 deletions(-) create mode 100644 common/cmd_mmc_spi.c create mode 100644 drivers/mmc/mmc_spi.c create mode 100644 include/mmc_spi.h
diff --git a/common/Makefile b/common/Makefile index dbf7a05..ee23e2f 100644 --- a/common/Makefile +++ b/common/Makefile @@ -118,6 +118,7 @@ COBJS-$(CONFIG_CMD_MII) += miiphyutil.o COBJS-$(CONFIG_CMD_MII) += cmd_mii.o COBJS-$(CONFIG_CMD_MISC) += cmd_misc.o COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o +COBJS-$(CONFIG_CMD_MMC_SPI) += cmd_mmc_spi.o COBJS-$(CONFIG_MP) += cmd_mp.o COBJS-$(CONFIG_CMD_MTDPARTS) += cmd_mtdparts.o COBJS-$(CONFIG_CMD_NAND) += cmd_nand.o diff --git a/common/cmd_mmc_spi.c b/common/cmd_mmc_spi.c new file mode 100644 index 0000000..cb33457 --- /dev/null +++ b/common/cmd_mmc_spi.c @@ -0,0 +1,115 @@ +/* + * Command for mmc_spi setup. + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> + +#ifndef CONFIG_MMC_SPI_BUS +# define CONFIG_MMC_SPI_BUS 0 +#endif +#ifndef CONFIG_MMC_SPI_CS +# define CONFIG_MMC_SPI_CS 1 +#endif +#ifndef CONFIG_MMC_SPI_SPEED +# define CONFIG_MMC_SPI_SPEED 30000000 +#endif +#ifndef CONFIG_MMC_SPI_MODE +# define CONFIG_MMC_SPI_MODE SPI_MODE_3 +#endif + +static void print_mmc_spi(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + printf("%s: %d at %u:%u %u %u\n", mmc->name, mmc->block_dev.dev, + priv->bus, priv->cs, priv->speed, priv->mode); +} + +static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{ + int dev_num = -1; + uint bus = CONFIG_MMC_SPI_BUS; + uint cs = CONFIG_MMC_SPI_CS; + uint speed = CONFIG_MMC_SPI_SPEED; + uint mode = CONFIG_MMC_SPI_MODE; + char *endp; + struct mmc *mmc; + struct mmc_spi_priv *priv; + + if (argc < 2) { + while (1) { + mmc = find_mmc_device(++dev_num, 0); + if (!mmc) + break; + if (strcmp(mmc->name, "MMC_SPI") == 0) + print_mmc_spi(mmc); + } + return 0; + } + + cs = simple_strtoul(argv[1], &endp, 0); + if (*argv[1] == 0 || (*endp != 0 && *endp != ':')) + goto usage; + if (*endp == ':') { + if (endp[1] == 0) + goto usage; + bus = cs; + cs = simple_strtoul(endp + 1, &endp, 0); + if (*endp != 0) + goto usage; + } + if (argc >= 3) { + speed = simple_strtoul(argv[2], &endp, 0); + if (*argv[2] == 0 || *endp != 0) + goto usage; + } + if (argc >= 4) { + mode = simple_strtoul(argv[3], &endp, 16); + if (*argv[3] == 0 || *endp != 0) + goto usage; + } + if (!spi_cs_is_valid(bus, cs)) { + printf("Invalid SPI bus %u cs %u\n", bus, cs); + return 1; + } + + while (1) { + mmc = find_mmc_device(++dev_num, 0); + if (!mmc) + break; + if (strcmp(mmc->name, "MMC_SPI") == 0) { + priv = mmc->priv; + if (priv->bus == bus && priv->cs == cs) { + printf("SPI bus and cs in use\n"); + print_mmc_spi(mmc); + return 1; + } + } + } + + printf("Create MMC Device\n"); + mmc = mmc_spi_init(bus, cs, speed, mode); + if (!mmc) { + printf("Failed to create MMC Device\n"); + return 1; + } + print_mmc_spi(mmc); + return 0; + +usage: + cmd_usage(cmdtp); + return 1; +} + +U_BOOT_CMD( + mmc_spi, 4, 0, do_mmc_spi, + "mmc_spi setup", + "[bus:][cs] [hz] [mode] - setup mmc_spi device on given\n" + " SPI bus and chip select\n" +); diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 6fa04b8..02ed329 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libmmc.a COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..27d9269 --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,393 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> +#include <crc.h> +#include <linux/crc7.h> +#include <asm/errno.h> + +/* Standard MMC commands (4.1) type argument response */ +#define MMC_SPI_READ_OCR 58 /* spi spi_R3 */ +#define MMC_SPI_CRC_ON_OFF 59 /* spi [0:0] flag spi_R1 */ + +/* + * MMC/SD in SPI mode reports R1 status always + */ +#define R1_SPI_IDLE (1 << 0) +#define R1_SPI_ERASE_RESET (1 << 1) +#define R1_SPI_ILLEGAL_COMMAND (1 << 2) +#define R1_SPI_COM_CRC (1 << 3) +#define R1_SPI_ERASE_SEQ (1 << 4) +#define R1_SPI_ADDRESS (1 << 5) +#define R1_SPI_PARAMETER (1 << 6) +/* R1 bit 7 is always zero */ +#define R1_SPI_ERROR (1 << 7) + +/* Response tokens used to ack each block written: */ +#define SPI_MMC_RESPONSE_CODE(x) ((x) & 0x1f) +#define SPI_RESPONSE_ACCEPTED ((2 << 1)|1) +#define SPI_RESPONSE_CRC_ERR ((5 << 1)|1) +#define SPI_RESPONSE_WRITE_ERR ((6 << 1)|1) + +/* Read and write blocks start with these tokens and end with crc; + * on error, read tokens act like a subset of R2_SPI_* values. + */ +#define SPI_TOKEN_SINGLE 0xfe /* single block r/w, multiblock read */ +#define SPI_TOKEN_MULTI_WRITE 0xfc /* multiblock write */ +#define SPI_TOKEN_STOP_TRAN 0xfd /* terminate multiblock write */ + +/* MMC SPI commands start with a start bit "0" and a transmit bit "1" */ +#define MMC_SPI_CMD(x) (0x40 | (x & 0x3f)) + +/* bus capability */ +#define MMC_SPI_VOLTAGE (MMC_VDD_32_33 | MMC_VDD_33_34) +#define MMC_SPI_MIN_CLOCK 400000 /* 400KHz to meet MMC spec */ + +/* timeout value */ +#define CTOUT 0x10 +#define RTOUT 0x10000 +#define WTOUT 0x10000 + +static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 cmdo[7]; + u8 r1; + int i; + cmdo[0] = 0xff; + cmdo[1] = MMC_SPI_CMD(cmdidx); + cmdo[2] = cmdarg >> 24; + cmdo[3] = cmdarg >> 16; + cmdo[4] = cmdarg >> 8; + cmdo[5] = cmdarg; + cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01; + spi_xfer(priv->slave, 7 * 8, cmdo, NULL, SPI_XFER_BEGIN); + for (i = 0; i < CTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x80) == 0) /* r1 response */ + break; + } + debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1); + return r1; +} + +static uint mmc_spi_readdata(struct mmc *mmc, char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 tok; + u8 crc[2]; + int i; + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &tok, 0); + if (tok != 0xff) + break; + } + debug("%s:tok%d %x\n", __func__, i, tok); + if (tok == SPI_TOKEN_SINGLE) { + spi_xfer(priv->slave, bsize * 8, NULL, buf, 0); + spi_xfer(priv->slave, 2 * 8, NULL, crc, 0); +#ifdef CONFIG_MMC_SPI_CRC_ON + if (cyg_crc16((unsigned char *)buf, bsize) != + ((crc[0] << 8) | crc[1])) { + printf("%s: Readdata CRC error\n", mmc->name); + tok = R1_SPI_COM_CRC; + break; + } +#endif + tok = 0; + } else { + tok = R1_SPI_ERROR; + break; + } + buf += bsize; + } + return tok; +} + +static uint mmc_spi_writedata(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = {0xff, SPI_TOKEN_SINGLE}; + u8 crc[2]; + int i; + while (bcnt--) { +#ifdef CONFIG_MMC_SPI_CRC_ON + u16 cks; + cks = cyg_crc16((unsigned char *)buf, bsize); + crc[0] = cks >> 8; + crc[1] = cks; +#endif + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (SPI_MMC_RESPONSE_CODE(r1) == SPI_RESPONSE_ACCEPTED) { + for (i = 0; i < WTOUT; i++) { /* wait busy */ + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + break; + } + } else { + debug("%s: err %x\n", __func__, r1); + r1 = R1_SPI_COM_CRC; + break; + } + buf += bsize; + } + return r1; +} + +static uint mmc_spi_writeblock(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = {0xff, SPI_TOKEN_MULTI_WRITE}; + u8 stop[2] = {0xff, SPI_TOKEN_STOP_TRAN}; + u8 crc[2]; + int i; + while (bcnt--) { +#ifdef CONFIG_MMC_SPI_CRC_ON + u16 cks; + cks = cyg_crc16((unsigned char *)buf, bsize); + crc[0] = cks >> 8; + crc[1] = cks; +#endif + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (SPI_MMC_RESPONSE_CODE(r1) == SPI_RESPONSE_ACCEPTED) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + break; + } + } else { + debug("%s: err %x\n", __func__, r1); + r1 = R1_SPI_COM_CRC; + break; + } + buf += bsize; + } + if (bcnt == 0 && r1 == 0) { + spi_xfer(priv->slave, 2 * 8, stop, NULL, 0); + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + } + } + return r1; +} + +static inline uint rswap(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static inline void mmc_spi_deactivate(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + spi_xfer(priv->slave, 1 * 8, NULL, NULL, SPI_XFER_END); + spi_xfer(priv->slave, 1 * 8, NULL, NULL, 0); +} + +static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + ushort cmdidx = cmd->cmdidx; + uint cmdarg = cmd->cmdarg; + u8 resp[16]; + int i; + int ret = 0; + debug("%s:cmd%d %x %x %x\n", __func__, + cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags); + if (cmdidx == MMC_CMD_ALL_SEND_CID) + cmdidx = MMC_CMD_SEND_CID; + r1 = mmc_spi_sendcmd(mmc, cmdidx, cmdarg); + if (r1 == 0xff) { + ret = NO_CARD_ERR; + goto done; + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(mmc, resp, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = rswap(resp + i * 4); + debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } else { + if (!data) { + spi_xfer(priv->slave, 4 * 8, NULL, resp, 0); + cmd->response[0] = rswap(resp); + debug("r32 %x\n", cmd->response[0]); + } + if (cmdidx == MMC_CMD_GO_IDLE_STATE) { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, MMC_SPI_READ_OCR, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswap(resp) & ~OCR_BUSY; + debug("ocr58 %x\n", priv->ocr58); + } else if (cmdidx == SD_CMD_SEND_IF_COND) { + if ((r1 & R1_SPI_ERROR) == 0) + priv->sd_if_cond = 1; + } else if (cmdidx == SD_CMD_APP_SEND_OP_COND || + cmdidx == MMC_CMD_SEND_OP_COND) { + if (r1 & R1_SPI_ILLEGAL_COMMAND) + ret = TIMEOUT; + else { + if (r1 & R1_SPI_IDLE) + cmd->response[0] = priv->ocr58; + else { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, + MMC_SPI_READ_OCR, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswap(resp); + debug("ocr58 %x\n", priv->ocr58); + cmd->response[0] = priv->ocr58; +#ifdef CONFIG_MMC_SPI_CRC_ON + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, + MMC_SPI_CRC_ON_OFF, 1); +#endif + } + } + } else { + cmd->response[0] = 0; + if (r1 & R1_SPI_COM_CRC) + ret = COMM_ERR; + } + } + if (data) { + debug("%s:data %x %x %x\n", __func__, + data->flags, data->blocks, data->blocksize); + if (data->flags == MMC_DATA_READ) { + r1 = mmc_spi_readdata(mmc, data->dest, + data->blocks, data->blocksize); + } else if (data->flags == MMC_DATA_WRITE) { + if (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK) + r1 = mmc_spi_writeblock(mmc, data->src, + data->blocks, data->blocksize); + else + r1 = mmc_spi_writedata(mmc, data->src, + data->blocks, data->blocksize); + } + if (r1 & R1_SPI_COM_CRC) + ret = COMM_ERR; + else if (r1) + ret = TIMEOUT; + } +done: + mmc_spi_deactivate(mmc); + return ret; +} + +static void mmc_spi_set_ios(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + debug("%s: clock %u\n", __func__, mmc->clock); + if (mmc->clock && mmc->clock != priv->speed) { + /* change clock rate */ + priv->speed = mmc->clock; + spi_claim_bus(priv->slave); + } +} + +static int mmc_spi_init_p(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + debug("%s: clock %u\n", __func__, mmc->clock); + if (!priv->slave) { + debug("%s: setup %u:%u %u %u\n", __func__, + priv->bus, priv->cs, + priv->speed, priv->mode); + priv->slave = spi_setup_slave(priv->bus, priv->cs, + priv->speed, priv->mode); + if (!priv->slave) { + debug("%s: failed to setup spi slave\n", __func__); + return -ENOMEM; + } + } + priv->sd_if_cond = 0; + priv->ocr58 = 0; + /* power on initialization with low speed clock */ + priv->speed = mmc->f_min; + spi_claim_bus(priv->slave); + /* finish any pending transfer */ + spi_xfer(priv->slave, 39 * 8, NULL, NULL, + SPI_XFER_BEGIN | SPI_XFER_END); + /* cs deactivated for 100+ clock */ + spi_xfer(priv->slave, 18 * 8, NULL, NULL, 0); + return 0; +} + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{ + struct mmc *mmc; + struct mmc_spi_priv *priv; + + mmc = malloc(sizeof(*mmc)); + if (!mmc) + return NULL; + priv = malloc(sizeof(*priv)); + if (!priv) { + free(mmc); + return NULL; + } + mmc->priv = priv; + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + sprintf(mmc->name, "MMC_SPI"); + mmc->send_cmd = mmc_spi_request; + mmc->set_ios = mmc_spi_set_ios; + mmc->init = mmc_spi_init_p; + mmc->host_caps = 0; + + mmc->voltages = MMC_SPI_VOLTAGE; + mmc->f_max = speed; + mmc->f_min = MMC_SPI_MIN_CLOCK; + mmc->block_dev.part_type = PART_TYPE_DOS; + + mmc_register(mmc); + + return mmc; +} diff --git a/include/mmc_spi.h b/include/mmc_spi.h new file mode 100644 index 0000000..fbfa1ce --- /dev/null +++ b/include/mmc_spi.h @@ -0,0 +1,27 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#ifndef _MMC_SPI_H_ +#define _MMC_SPI_H_ + +#include <spi.h> +#include <mmc.h> +#include <linux/types.h> + +struct mmc_spi_priv { + struct spi_slave *slave; + uint bus; + uint cs; + uint speed; + uint mode; + uint ocr58; + uint sd_if_cond:1; +}; + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode); + +#endif

This patch supports mmc/sd card with spi interface. It is based on the generic mmc framework. It works with SDHC and supports write.
The crc checksum on data packet is enabled with the def, #define CONFIG_MMC_SPI_CRC_ON
There is a subcomamnd "mmc_spi" to setup spi bus and cs at run time.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- v6: add constant macros, crc check on data, per Andy. v5: remove dev_num limit to search. v4: change mmc_spi subcommand to search and create new mmc dev. v3: add mmc_spi_init() proto to mmc_spi.h. v2: add crc7, use cmd58 to read ocr, add subcommand mmc_spi.
common/Makefile | 1 + common/cmd_mmc_spi.c | 115 ++++++++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/mmc_spi.c | 393 +++++++++++++++++++++++++++++++++++++++++++++++++ include/mmc_spi.h | 27 ++++ 5 files changed, 537 insertions(+), 0 deletions(-) create mode 100644 common/cmd_mmc_spi.c create mode 100644 drivers/mmc/mmc_spi.c create mode 100644 include/mmc_spi.h
diff --git a/common/Makefile b/common/Makefile index dbf7a05..ee23e2f 100644 --- a/common/Makefile +++ b/common/Makefile @@ -118,6 +118,7 @@ COBJS-$(CONFIG_CMD_MII) += miiphyutil.o COBJS-$(CONFIG_CMD_MII) += cmd_mii.o COBJS-$(CONFIG_CMD_MISC) += cmd_misc.o COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o +COBJS-$(CONFIG_CMD_MMC_SPI) += cmd_mmc_spi.o COBJS-$(CONFIG_MP) += cmd_mp.o COBJS-$(CONFIG_CMD_MTDPARTS) += cmd_mtdparts.o COBJS-$(CONFIG_CMD_NAND) += cmd_nand.o diff --git a/common/cmd_mmc_spi.c b/common/cmd_mmc_spi.c new file mode 100644 index 0000000..8affa02 --- /dev/null +++ b/common/cmd_mmc_spi.c @@ -0,0 +1,115 @@ +/* + * Command for mmc_spi setup. + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> + +#ifndef CONFIG_MMC_SPI_BUS +# define CONFIG_MMC_SPI_BUS 0 +#endif +#ifndef CONFIG_MMC_SPI_CS +# define CONFIG_MMC_SPI_CS 1 +#endif +#ifndef CONFIG_MMC_SPI_SPEED +# define CONFIG_MMC_SPI_SPEED 30000000 +#endif +#ifndef CONFIG_MMC_SPI_MODE +# define CONFIG_MMC_SPI_MODE SPI_MODE_3 +#endif + +static void print_mmc_spi(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + printf("%s: %d at %u:%u %u %u\n", mmc->name, mmc->block_dev.dev, + priv->bus, priv->cs, priv->speed, priv->mode); +} + +static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{ + int dev_num = -1; + uint bus = CONFIG_MMC_SPI_BUS; + uint cs = CONFIG_MMC_SPI_CS; + uint speed = CONFIG_MMC_SPI_SPEED; + uint mode = CONFIG_MMC_SPI_MODE; + char *endp; + struct mmc *mmc; + struct mmc_spi_priv *priv; + + if (argc < 2) { + while (1) { + mmc = find_mmc_device_quiet(++dev_num); + if (!mmc) + break; + if (strcmp(mmc->name, "MMC_SPI") == 0) + print_mmc_spi(mmc); + } + return 0; + } + + cs = simple_strtoul(argv[1], &endp, 0); + if (*argv[1] == 0 || (*endp != 0 && *endp != ':')) + goto usage; + if (*endp == ':') { + if (endp[1] == 0) + goto usage; + bus = cs; + cs = simple_strtoul(endp + 1, &endp, 0); + if (*endp != 0) + goto usage; + } + if (argc >= 3) { + speed = simple_strtoul(argv[2], &endp, 0); + if (*argv[2] == 0 || *endp != 0) + goto usage; + } + if (argc >= 4) { + mode = simple_strtoul(argv[3], &endp, 16); + if (*argv[3] == 0 || *endp != 0) + goto usage; + } + if (!spi_cs_is_valid(bus, cs)) { + printf("Invalid SPI bus %u cs %u\n", bus, cs); + return 1; + } + + while (1) { + mmc = find_mmc_device_quiet(++dev_num); + if (!mmc) + break; + if (strcmp(mmc->name, "MMC_SPI") == 0) { + priv = mmc->priv; + if (priv->bus == bus && priv->cs == cs) { + printf("SPI bus and cs in use\n"); + print_mmc_spi(mmc); + return 1; + } + } + } + + printf("Create MMC Device\n"); + mmc = mmc_spi_init(bus, cs, speed, mode); + if (!mmc) { + printf("Failed to create MMC Device\n"); + return 1; + } + print_mmc_spi(mmc); + return 0; + +usage: + cmd_usage(cmdtp); + return 1; +} + +U_BOOT_CMD( + mmc_spi, 4, 0, do_mmc_spi, + "mmc_spi setup", + "[bus:][cs] [hz] [mode] - setup mmc_spi device on given\n" + " SPI bus and chip select\n" +); diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 6fa04b8..02ed329 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libmmc.a COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..b601941 --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,393 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <spi.h> +#include <mmc_spi.h> +#include <crc.h> +#include <linux/crc7.h> +#include <asm/errno.h> + +/* Standard MMC commands (4.1) type argument response */ +#define MMC_SPI_READ_OCR 58 /* spi spi_R3 */ +#define MMC_SPI_CRC_ON_OFF 59 /* spi [0:0] flag spi_R1 */ + +/* + * MMC/SD in SPI mode reports R1 status always + */ +#define R1_SPI_IDLE (1 << 0) +#define R1_SPI_ERASE_RESET (1 << 1) +#define R1_SPI_ILLEGAL_COMMAND (1 << 2) +#define R1_SPI_COM_CRC (1 << 3) +#define R1_SPI_ERASE_SEQ (1 << 4) +#define R1_SPI_ADDRESS (1 << 5) +#define R1_SPI_PARAMETER (1 << 6) +/* R1 bit 7 is always zero */ +#define R1_SPI_ERROR (1 << 7) + +/* Response tokens used to ack each block written: */ +#define SPI_MMC_RESPONSE_CODE(x) ((x) & 0x1f) +#define SPI_RESPONSE_ACCEPTED ((2 << 1)|1) +#define SPI_RESPONSE_CRC_ERR ((5 << 1)|1) +#define SPI_RESPONSE_WRITE_ERR ((6 << 1)|1) + +/* Read and write blocks start with these tokens and end with crc; + * on error, read tokens act like a subset of R2_SPI_* values. + */ +#define SPI_TOKEN_SINGLE 0xfe /* single block r/w, multiblock read */ +#define SPI_TOKEN_MULTI_WRITE 0xfc /* multiblock write */ +#define SPI_TOKEN_STOP_TRAN 0xfd /* terminate multiblock write */ + +/* MMC SPI commands start with a start bit "0" and a transmit bit "1" */ +#define MMC_SPI_CMD(x) (0x40 | (x & 0x3f)) + +/* bus capability */ +#define MMC_SPI_VOLTAGE (MMC_VDD_32_33 | MMC_VDD_33_34) +#define MMC_SPI_MIN_CLOCK 400000 /* 400KHz to meet MMC spec */ + +/* timeout value */ +#define CTOUT 0x10 +#define RTOUT 0x10000 +#define WTOUT 0x10000 + +static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 cmdo[7]; + u8 r1; + int i; + cmdo[0] = 0xff; + cmdo[1] = MMC_SPI_CMD(cmdidx); + cmdo[2] = cmdarg >> 24; + cmdo[3] = cmdarg >> 16; + cmdo[4] = cmdarg >> 8; + cmdo[5] = cmdarg; + cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01; + spi_xfer(priv->slave, 7 * 8, cmdo, NULL, SPI_XFER_BEGIN); + for (i = 0; i < CTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x80) == 0) /* r1 response */ + break; + } + debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1); + return r1; +} + +static uint mmc_spi_readdata(struct mmc *mmc, char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 tok; + u8 crc[2]; + int i; + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &tok, 0); + if (tok != 0xff) + break; + } + debug("%s:tok%d %x\n", __func__, i, tok); + if (tok == SPI_TOKEN_SINGLE) { + spi_xfer(priv->slave, bsize * 8, NULL, buf, 0); + spi_xfer(priv->slave, 2 * 8, NULL, crc, 0); +#ifdef CONFIG_MMC_SPI_CRC_ON + if (cyg_crc16((unsigned char *)buf, bsize) != + ((crc[0] << 8) | crc[1])) { + printf("%s: Readdata CRC error\n", mmc->name); + tok = R1_SPI_COM_CRC; + break; + } +#endif + tok = 0; + } else { + tok = R1_SPI_ERROR; + break; + } + buf += bsize; + } + return tok; +} + +static uint mmc_spi_writedata(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = {0xff, SPI_TOKEN_SINGLE}; + u8 crc[2]; + int i; + while (bcnt--) { +#ifdef CONFIG_MMC_SPI_CRC_ON + u16 cks; + cks = cyg_crc16((unsigned char *)buf, bsize); + crc[0] = cks >> 8; + crc[1] = cks; +#endif + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (SPI_MMC_RESPONSE_CODE(r1) == SPI_RESPONSE_ACCEPTED) { + for (i = 0; i < WTOUT; i++) { /* wait busy */ + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + break; + } + } else { + debug("%s: err %x\n", __func__, r1); + r1 = R1_SPI_COM_CRC; + break; + } + buf += bsize; + } + return r1; +} + +static uint mmc_spi_writeblock(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + u8 tok[2] = {0xff, SPI_TOKEN_MULTI_WRITE}; + u8 stop[2] = {0xff, SPI_TOKEN_STOP_TRAN}; + u8 crc[2]; + int i; + while (bcnt--) { +#ifdef CONFIG_MMC_SPI_CRC_ON + u16 cks; + cks = cyg_crc16((unsigned char *)buf, bsize); + crc[0] = cks >> 8; + crc[1] = cks; +#endif + spi_xfer(priv->slave, 2 * 8, tok, NULL, 0); + spi_xfer(priv->slave, bsize * 8, buf, NULL, 0); + spi_xfer(priv->slave, 2 * 8, crc, NULL, 0); + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (SPI_MMC_RESPONSE_CODE(r1) == SPI_RESPONSE_ACCEPTED) { + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + break; + } + } else { + debug("%s: err %x\n", __func__, r1); + r1 = R1_SPI_COM_CRC; + break; + } + buf += bsize; + } + if (bcnt == 0 && r1 == 0) { + spi_xfer(priv->slave, 2 * 8, stop, NULL, 0); + for (i = 0; i < WTOUT; i++) { + spi_xfer(priv->slave, 1 * 8, NULL, &r1, 0); + if (r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + } + } + return r1; +} + +static inline uint rswap(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static inline void mmc_spi_deactivate(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + spi_xfer(priv->slave, 1 * 8, NULL, NULL, SPI_XFER_END); + spi_xfer(priv->slave, 1 * 8, NULL, NULL, 0); +} + +static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc_spi_priv *priv = mmc->priv; + u8 r1; + ushort cmdidx = cmd->cmdidx; + uint cmdarg = cmd->cmdarg; + u8 resp[16]; + int i; + int ret = 0; + debug("%s:cmd%d %x %x %x\n", __func__, + cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags); + if (cmdidx == MMC_CMD_ALL_SEND_CID) + cmdidx = MMC_CMD_SEND_CID; + r1 = mmc_spi_sendcmd(mmc, cmdidx, cmdarg); + if (r1 == 0xff) { + ret = NO_CARD_ERR; + goto done; + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(mmc, resp, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = rswap(resp + i * 4); + debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } else { + if (!data) { + spi_xfer(priv->slave, 4 * 8, NULL, resp, 0); + cmd->response[0] = rswap(resp); + debug("r32 %x\n", cmd->response[0]); + } + if (cmdidx == MMC_CMD_GO_IDLE_STATE) { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, MMC_SPI_READ_OCR, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswap(resp) & ~OCR_BUSY; + debug("ocr58 %x\n", priv->ocr58); + } else if (cmdidx == SD_CMD_SEND_IF_COND) { + if ((r1 & R1_SPI_ERROR) == 0) + priv->sd_if_cond = 1; + } else if (cmdidx == SD_CMD_APP_SEND_OP_COND || + cmdidx == MMC_CMD_SEND_OP_COND) { + if (r1 & R1_SPI_ILLEGAL_COMMAND) + ret = TIMEOUT; + else { + if (r1 & R1_SPI_IDLE) + cmd->response[0] = priv->ocr58; + else { + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, + MMC_SPI_READ_OCR, 0); + spi_xfer(priv->slave, 4 * 8, + NULL, resp, 0); + priv->ocr58 = rswap(resp); + debug("ocr58 %x\n", priv->ocr58); + cmd->response[0] = priv->ocr58; +#ifdef CONFIG_MMC_SPI_CRC_ON + mmc_spi_deactivate(mmc); + r1 = mmc_spi_sendcmd(mmc, + MMC_SPI_CRC_ON_OFF, 1); +#endif + } + } + } else { + cmd->response[0] = 0; + if (r1 & R1_SPI_COM_CRC) + ret = COMM_ERR; + } + } + if (data) { + debug("%s:data %x %x %x\n", __func__, + data->flags, data->blocks, data->blocksize); + if (data->flags == MMC_DATA_READ) { + r1 = mmc_spi_readdata(mmc, data->dest, + data->blocks, data->blocksize); + } else if (data->flags == MMC_DATA_WRITE) { + if (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK) + r1 = mmc_spi_writeblock(mmc, data->src, + data->blocks, data->blocksize); + else + r1 = mmc_spi_writedata(mmc, data->src, + data->blocks, data->blocksize); + } + if (r1 & R1_SPI_COM_CRC) + ret = COMM_ERR; + else if (r1) + ret = TIMEOUT; + } +done: + mmc_spi_deactivate(mmc); + return ret; +} + +static void mmc_spi_set_ios(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + debug("%s: clock %u\n", __func__, mmc->clock); + if (mmc->clock && mmc->clock != priv->speed) { + /* change clock rate */ + priv->speed = mmc->clock; + spi_claim_bus(priv->slave); + } +} + +static int mmc_spi_init_p(struct mmc *mmc) +{ + struct mmc_spi_priv *priv = mmc->priv; + debug("%s: clock %u\n", __func__, mmc->clock); + if (!priv->slave) { + debug("%s: setup %u:%u %u %u\n", __func__, + priv->bus, priv->cs, + priv->speed, priv->mode); + priv->slave = spi_setup_slave(priv->bus, priv->cs, + priv->speed, priv->mode); + if (!priv->slave) { + debug("%s: failed to setup spi slave\n", __func__); + return -ENOMEM; + } + } + priv->sd_if_cond = 0; + priv->ocr58 = 0; + /* power on initialization with low speed clock */ + priv->speed = mmc->f_min; + spi_claim_bus(priv->slave); + /* finish any pending transfer */ + spi_xfer(priv->slave, 39 * 8, NULL, NULL, + SPI_XFER_BEGIN | SPI_XFER_END); + /* cs deactivated for 100+ clock */ + spi_xfer(priv->slave, 18 * 8, NULL, NULL, 0); + return 0; +} + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{ + struct mmc *mmc; + struct mmc_spi_priv *priv; + + mmc = malloc(sizeof(*mmc)); + if (!mmc) + return NULL; + priv = malloc(sizeof(*priv)); + if (!priv) { + free(mmc); + return NULL; + } + mmc->priv = priv; + priv->bus = bus; + priv->cs = cs; + priv->speed = speed; + priv->mode = mode; + sprintf(mmc->name, "MMC_SPI"); + mmc->send_cmd = mmc_spi_request; + mmc->set_ios = mmc_spi_set_ios; + mmc->init = mmc_spi_init_p; + mmc->host_caps = 0; + + mmc->voltages = MMC_SPI_VOLTAGE; + mmc->f_max = speed; + mmc->f_min = MMC_SPI_MIN_CLOCK; + mmc->block_dev.part_type = PART_TYPE_DOS; + + mmc_register(mmc); + + return mmc; +} diff --git a/include/mmc_spi.h b/include/mmc_spi.h new file mode 100644 index 0000000..fbfa1ce --- /dev/null +++ b/include/mmc_spi.h @@ -0,0 +1,27 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#ifndef _MMC_SPI_H_ +#define _MMC_SPI_H_ + +#include <spi.h> +#include <mmc.h> +#include <linux/types.h> + +struct mmc_spi_priv { + struct spi_slave *slave; + uint bus; + uint cs; + uint speed; + uint mode; + uint ocr58; + uint sd_if_cond:1; +}; + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode); + +#endif

This patch supports mmc/sd card with spi interface. It is based on the generic mmc framework. It works with SDHC and supports multi blocks read/write.
The crc checksum on data packet is enabled with the def, #define CONFIG_MMC_SPI_CRC_ON
There is a subcomamnd "mmc_spi" to setup spi bus and cs at run time.
Signed-off-by: Thomas Chou thomas@wytron.com.tw --- v8: make mmc.c core aware of spi protocol, per Andy. work with multi blocks read/write. reduce cmd_mmc_spi.c, doesnt query to mmc dev list. v7: use find_mmc_device(dev, verbose), per Wolfgang. v6: add constant macros, crc check on data, per Andy. v5: remove dev_num limit to search. v4: change mmc_spi subcommand to search and create new mmc dev. v3: add mmc_spi_init() proto to mmc_spi.h. v2: add crc7, use cmd58 to read ocr, add subcommand mmc_spi.
common/Makefile | 1 + common/cmd_mmc_spi.c | 82 ++++++++++++++ drivers/mmc/Makefile | 1 + drivers/mmc/mmc.c | 81 +++++++++++--- drivers/mmc/mmc_spi.c | 296 +++++++++++++++++++++++++++++++++++++++++++++++++ include/mmc.h | 8 ++ 6 files changed, 451 insertions(+), 18 deletions(-) create mode 100644 common/cmd_mmc_spi.c create mode 100644 drivers/mmc/mmc_spi.c
diff --git a/common/Makefile b/common/Makefile index 2c37073..55beac5 100644 --- a/common/Makefile +++ b/common/Makefile @@ -119,6 +119,7 @@ COBJS-$(CONFIG_CMD_MII) += miiphyutil.o COBJS-$(CONFIG_CMD_MII) += cmd_mii.o COBJS-$(CONFIG_CMD_MISC) += cmd_misc.o COBJS-$(CONFIG_CMD_MMC) += cmd_mmc.o +COBJS-$(CONFIG_CMD_MMC_SPI) += cmd_mmc_spi.o COBJS-$(CONFIG_MP) += cmd_mp.o COBJS-$(CONFIG_CMD_MTDPARTS) += cmd_mtdparts.o COBJS-$(CONFIG_CMD_NAND) += cmd_nand.o diff --git a/common/cmd_mmc_spi.c b/common/cmd_mmc_spi.c new file mode 100644 index 0000000..13933b0 --- /dev/null +++ b/common/cmd_mmc_spi.c @@ -0,0 +1,82 @@ +/* + * Command for mmc_spi setup. + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <mmc.h> +#include <spi.h> + +#ifndef CONFIG_MMC_SPI_BUS +# define CONFIG_MMC_SPI_BUS 0 +#endif +#ifndef CONFIG_MMC_SPI_CS +# define CONFIG_MMC_SPI_CS 1 +#endif +#ifndef CONFIG_MMC_SPI_SPEED +# define CONFIG_MMC_SPI_SPEED 25000000 +#endif +#ifndef CONFIG_MMC_SPI_MODE +# define CONFIG_MMC_SPI_MODE SPI_MODE_3 +#endif + +static int do_mmc_spi(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) +{ + uint bus = CONFIG_MMC_SPI_BUS; + uint cs = CONFIG_MMC_SPI_CS; + uint speed = CONFIG_MMC_SPI_SPEED; + uint mode = CONFIG_MMC_SPI_MODE; + char *endp; + struct mmc *mmc; + + if (argc < 2) + goto usage; + + cs = simple_strtoul(argv[1], &endp, 0); + if (*argv[1] == 0 || (*endp != 0 && *endp != ':')) + goto usage; + if (*endp == ':') { + if (endp[1] == 0) + goto usage; + bus = cs; + cs = simple_strtoul(endp + 1, &endp, 0); + if (*endp != 0) + goto usage; + } + if (argc >= 3) { + speed = simple_strtoul(argv[2], &endp, 0); + if (*argv[2] == 0 || *endp != 0) + goto usage; + } + if (argc >= 4) { + mode = simple_strtoul(argv[3], &endp, 16); + if (*argv[3] == 0 || *endp != 0) + goto usage; + } + if (!spi_cs_is_valid(bus, cs)) { + printf("Invalid SPI bus %u cs %u\n", bus, cs); + return 1; + } + + mmc = mmc_spi_init(bus, cs, speed, mode); + if (!mmc) { + printf("Failed to create MMC Device\n"); + return 1; + } + printf("%s: %d at %u:%u %u %u\n", mmc->name, mmc->block_dev.dev, + bus, cs, speed, mode); + return 0; + +usage: + cmd_usage(cmdtp); + return 1; +} + +U_BOOT_CMD( + mmc_spi, 4, 0, do_mmc_spi, + "mmc_spi setup", + "[bus:]cs [hz] [mode] - setup mmc_spi device on given\n" + " SPI bus and chip select\n" +); diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 6fa04b8..02ed329 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libmmc.a COBJS-$(CONFIG_GENERIC_MMC) += mmc.o COBJS-$(CONFIG_ATMEL_MCI) += atmel_mci.o COBJS-$(CONFIG_BFIN_SDH) += bfin_sdh.o +COBJS-$(CONFIG_MMC_SPI) += mmc_spi.o COBJS-$(CONFIG_OMAP3_MMC) += omap3_mmc.o COBJS-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o COBJS-$(CONFIG_MXC_MMC) += mxcmmc.o diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index aefd721..0e819f8 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -283,7 +283,10 @@ static int mmc_write_blocks(struct mmc *mmc, const char *src, uint start, return err; }
- if (blkcnt > 1) { + /* SPI multiblock writes terminate using a special + * token, not a STOP_TRANSMISSION request. + */ + if (!mmc_host_is_spi(mmc) && blkcnt > 1) { cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; cmd.cmdarg = 0; cmd.resp_type = MMC_RSP_R1b; @@ -458,6 +461,18 @@ sd_send_op_cond(struct mmc *mmc) if (mmc->version != SD_VERSION_2) mmc->version = SD_VERSION_1_0;
+ if (mmc_host_is_spi(mmc)) { /* read OCR for spi */ + cmd.cmdidx = MMC_CMD_SPI_READ_OCR; + cmd.resp_type = MMC_RSP_R3; + cmd.cmdarg = 0; + cmd.flags = 0; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + } + mmc->ocr = cmd.response[0];
mmc->high_capacity = ((mmc->ocr & OCR_HCS) == OCR_HCS); @@ -492,6 +507,18 @@ int mmc_send_op_cond(struct mmc *mmc) if (timeout <= 0) return UNUSABLE_ERR;
+ if (mmc_host_is_spi(mmc)) { /* read OCR for spi */ + cmd.cmdidx = MMC_CMD_SPI_READ_OCR; + cmd.resp_type = MMC_RSP_R3; + cmd.cmdarg = 0; + cmd.flags = 0; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + } + mmc->version = MMC_VERSION_UNKNOWN; mmc->ocr = cmd.response[0];
@@ -776,8 +803,22 @@ int mmc_startup(struct mmc *mmc) u64 cmult, csize; struct mmc_cmd cmd;
+#ifdef CONFIG_MMC_SPI_CRC_ON + if (mmc_host_is_spi(mmc)) { /* enable CRC check for spi */ + cmd.cmdidx = MMC_CMD_SPI_CRC_ON_OFF; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 1; + cmd.flags = 0; + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + } +#endif + /* Put the Card in Identify Mode */ - cmd.cmdidx = MMC_CMD_ALL_SEND_CID; + cmd.cmdidx = mmc_host_is_spi(mmc) ? MMC_CMD_SEND_CID : + MMC_CMD_ALL_SEND_CID; /* cmd not supported in spi */ cmd.resp_type = MMC_RSP_R2; cmd.cmdarg = 0; cmd.flags = 0; @@ -794,18 +835,20 @@ int mmc_startup(struct mmc *mmc) * 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_RSP_R6; - cmd.flags = 0; + if (!mmc_host_is_spi(mmc)) { /* cmd not supported in spi */ + cmd.cmdidx = SD_CMD_SEND_RELATIVE_ADDR; + cmd.cmdarg = mmc->rca << 16; + cmd.resp_type = MMC_RSP_R6; + cmd.flags = 0;
- err = mmc_send_cmd(mmc, &cmd, NULL); + err = mmc_send_cmd(mmc, &cmd, NULL);
- if (err) - return err; + if (err) + return err;
- if (IS_SD(mmc)) - mmc->rca = (cmd.response[0] >> 16) & 0xffff; + if (IS_SD(mmc)) + mmc->rca = (cmd.response[0] >> 16) & 0xffff; + }
/* Get the Card-Specific Data */ cmd.cmdidx = MMC_CMD_SEND_CSD; @@ -881,14 +924,16 @@ int mmc_startup(struct mmc *mmc) mmc->write_bl_len = 512;
/* Select the card, and put it into Transfer Mode */ - cmd.cmdidx = MMC_CMD_SELECT_CARD; - cmd.resp_type = MMC_RSP_R1b; - cmd.cmdarg = mmc->rca << 16; - cmd.flags = 0; - err = mmc_send_cmd(mmc, &cmd, NULL); + if (!mmc_host_is_spi(mmc)) { /* cmd not supported in spi */ + cmd.cmdidx = MMC_CMD_SELECT_CARD; + cmd.resp_type = MMC_RSP_R1b; + cmd.cmdarg = mmc->rca << 16; + cmd.flags = 0; + err = mmc_send_cmd(mmc, &cmd, NULL);
- if (err) - return err; + if (err) + return err; + }
if (IS_SD(mmc)) err = sd_change_freq(mmc); diff --git a/drivers/mmc/mmc_spi.c b/drivers/mmc/mmc_spi.c new file mode 100644 index 0000000..d675c7f --- /dev/null +++ b/drivers/mmc/mmc_spi.c @@ -0,0 +1,296 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou thomas@wytron.com.tw + * Licensed under the GPL-2 or later. + */ + +#include <common.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <spi.h> +#include <crc.h> +#include <linux/crc7.h> + +/* + * MMC/SD in SPI mode reports R1 status always + */ +#define R1_SPI_IDLE (1 << 0) +#define R1_SPI_ERASE_RESET (1 << 1) +#define R1_SPI_ILLEGAL_COMMAND (1 << 2) +#define R1_SPI_COM_CRC (1 << 3) +#define R1_SPI_ERASE_SEQ (1 << 4) +#define R1_SPI_ADDRESS (1 << 5) +#define R1_SPI_PARAMETER (1 << 6) +/* R1 bit 7 is always zero */ +#define R1_SPI_ERROR (1 << 7) + +/* Response tokens used to ack each block written: */ +#define SPI_MMC_RESPONSE_CODE(x) ((x) & 0x1f) +#define SPI_RESPONSE_ACCEPTED ((2 << 1)|1) +#define SPI_RESPONSE_CRC_ERR ((5 << 1)|1) +#define SPI_RESPONSE_WRITE_ERR ((6 << 1)|1) + +/* Read and write blocks start with these tokens and end with crc; + * on error, read tokens act like a subset of R2_SPI_* values. + */ +#define SPI_TOKEN_SINGLE 0xfe /* single block r/w, multiblock read */ +#define SPI_TOKEN_MULTI_WRITE 0xfc /* multiblock write */ +#define SPI_TOKEN_STOP_TRAN 0xfd /* terminate multiblock write */ + +/* MMC SPI commands start with a start bit "0" and a transmit bit "1" */ +#define MMC_SPI_CMD(x) (0x40 | (x & 0x3f)) + +/* bus capability */ +#define MMC_SPI_VOLTAGE (MMC_VDD_32_33 | MMC_VDD_33_34) +#define MMC_SPI_MIN_CLOCK 400000 /* 400KHz to meet MMC spec */ +#define MMC_SPI_MAX_BLOCKS 65536 + +/* timeout value */ +#define CTOUT 8 +#define RTOUT 3000000 /* 1 sec */ +#define WTOUT 3000000 /* 1 sec */ + +static uint mmc_spi_sendcmd(struct mmc *mmc, u8 cmdidx, u32 cmdarg) +{ + struct spi_slave *spi = mmc->priv; + u8 cmdo[7]; + u8 r1; + int i; + cmdo[0] = 0xff; + cmdo[1] = MMC_SPI_CMD(cmdidx); + cmdo[2] = cmdarg >> 24; + cmdo[3] = cmdarg >> 16; + cmdo[4] = cmdarg >> 8; + cmdo[5] = cmdarg; + cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01; + spi_xfer(spi, 7 * 8, cmdo, NULL, SPI_XFER_BEGIN); + for (i = 0; i < CTOUT; i++) { + spi_xfer(spi, 1 * 8, NULL, &r1, 0); + if (i && (r1 & 0x80) == 0) /* r1 response */ + break; + } + debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1); + return r1; +} + +static uint mmc_spi_readdata(struct mmc *mmc, char *buf, + u32 bcnt, u32 bsize) +{ + struct spi_slave *spi = mmc->priv; + u8 tok; + u8 crc[2]; + int i; + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + spi_xfer(spi, 1 * 8, NULL, &tok, 0); + if (tok != 0xff) /* data token */ + break; + } + debug("%s:tok%d %x\n", __func__, i, tok); + if (tok == SPI_TOKEN_SINGLE) { + spi_xfer(spi, bsize * 8, NULL, buf, 0); + spi_xfer(spi, 2 * 8, NULL, crc, 0); +#ifdef CONFIG_MMC_SPI_CRC_ON + if (cyg_crc16((unsigned char *)buf, bsize) != + ((crc[0] << 8) | crc[1])) { + debug("%s: CRC error\n", mmc->name); + tok = R1_SPI_COM_CRC; + break; + } +#endif + tok = 0; + } else { + tok = R1_SPI_ERROR; + break; + } + buf += bsize; + } + return tok; +} + +static uint mmc_spi_writedata(struct mmc *mmc, const char *buf, + u32 bcnt, u32 bsize, int multi) +{ + struct spi_slave *spi = mmc->priv; + u8 r1; + const u8 toks[2] = {0xff, SPI_TOKEN_SINGLE}; + const u8 tokm[2] = {0xff, SPI_TOKEN_MULTI_WRITE}; + const u8 stop[2] = {0xff, SPI_TOKEN_STOP_TRAN}; + u8 crc[2]; + int i; + while (bcnt--) { +#ifdef CONFIG_MMC_SPI_CRC_ON + u16 cks; + cks = cyg_crc16((unsigned char *)buf, bsize); + crc[0] = cks >> 8; + crc[1] = cks; +#endif + spi_xfer(spi, 2 * 8, multi ? tokm : toks, NULL, 0); + spi_xfer(spi, bsize * 8, buf, NULL, 0); + spi_xfer(spi, 2 * 8, crc, NULL, 0); + for (i = 0; i < CTOUT; i++) { + spi_xfer(spi, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x10) == 0) /* response token */ + break; + } + debug("%s:tok%d %x\n", __func__, i, r1); + if (SPI_MMC_RESPONSE_CODE(r1) == SPI_RESPONSE_ACCEPTED) { + for (i = 0; i < WTOUT; i++) { /* wait busy */ + spi_xfer(spi, 1 * 8, NULL, &r1, 0); + if (i && r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wtout %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + break; + } + } else { + debug("%s: err %x\n", __func__, r1); + r1 = R1_SPI_COM_CRC; + break; + } + buf += bsize; + } + if (multi && bcnt == -1) { /* stop multi write */ + spi_xfer(spi, 2 * 8, stop, NULL, 0); + for (i = 0; i < WTOUT; i++) { /* wait busy */ + spi_xfer(spi, 1 * 8, NULL, &r1, 0); + if (i && r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + debug("%s:wstop %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + } + } + return r1; +} + +/* rswap: swap spi byte stream to mmc response */ +static inline uint rswap(u8 *d) +{ + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; +} + +static inline void mmc_spi_deactivate(struct mmc *mmc) +{ + struct spi_slave *spi = mmc->priv; + spi_xfer(spi, 1 * 8, NULL, NULL, SPI_XFER_END); + spi_xfer(spi, 1 * 8, NULL, NULL, 0); +} + +static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct spi_slave *spi = mmc->priv; + u8 r1; + ushort cmdidx = cmd->cmdidx; + uint cmdarg = cmd->cmdarg; + u8 resp[16]; + int i; + int ret = 0; + debug("%s:cmd%d %x %x %x\n", __func__, + cmd->cmdidx, cmd->resp_type, cmd->cmdarg, cmd->flags); + r1 = mmc_spi_sendcmd(mmc, cmdidx, cmdarg); + if (r1 == 0xff) { /* no response */ + ret = NO_CARD_ERR; + goto done; + } else if (r1 & R1_SPI_COM_CRC) { + ret = COMM_ERR; + goto done; + } else if (r1 & ~R1_SPI_IDLE) { /* other errors */ + ret = TIMEOUT; + goto done; + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(mmc, (char *)resp, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = rswap(resp + i * 4); + debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } else if (!data) { + switch (cmdidx) { + case SD_CMD_APP_SEND_OP_COND: + case MMC_CMD_SEND_OP_COND: + cmd->response[0] = (r1 & R1_SPI_IDLE) ? 0 : OCR_BUSY; + break; + case SD_CMD_SEND_IF_COND: + case MMC_CMD_SPI_READ_OCR: + spi_xfer(spi, 4 * 8, NULL, resp, 0); + cmd->response[0] = rswap(resp); + debug("r32 %x\n", cmd->response[0]); + break; + } + } else { + debug("%s:data %x %x %x\n", __func__, + data->flags, data->blocks, data->blocksize); + if (data->flags == MMC_DATA_READ) + r1 = mmc_spi_readdata(mmc, data->dest, + data->blocks, data->blocksize); + else if (data->flags == MMC_DATA_WRITE) + r1 = mmc_spi_writedata(mmc, data->src, + data->blocks, data->blocksize, + (cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)); + if (r1 & R1_SPI_COM_CRC) + ret = COMM_ERR; + else if (r1) /* other errors */ + ret = TIMEOUT; + } +done: + mmc_spi_deactivate(mmc); + return ret; +} + +static void mmc_spi_set_ios(struct mmc *mmc) +{ + debug("%s: clock %u\n", __func__, mmc->clock); + /* the current spi framework does not support change clock freq */ +} + +static int mmc_spi_init_p(struct mmc *mmc) +{ + struct spi_slave *spi = mmc->priv; + debug("%s: clock %u\n", __func__, mmc->clock); + spi_claim_bus(spi); + /* finish any pending transfer */ + spi_xfer(spi, 39 * 8, NULL, NULL, + SPI_XFER_BEGIN | SPI_XFER_END); + /* cs deactivated for 100+ clock */ + spi_xfer(spi, 18 * 8, NULL, NULL, 0); + return 0; +} + +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{ + struct mmc *mmc; + + mmc = malloc(sizeof(*mmc)); + if (!mmc) + return NULL; + mmc->priv = spi_setup_slave(bus, cs, speed, mode); + if (!mmc->priv) { + free(mmc); + return NULL; + } + sprintf(mmc->name, "MMC_SPI"); + mmc->send_cmd = mmc_spi_request; + mmc->set_ios = mmc_spi_set_ios; + mmc->init = mmc_spi_init_p; + mmc->host_caps = MMC_MODE_SPI; + + mmc->voltages = MMC_SPI_VOLTAGE; + mmc->f_max = speed; + mmc->f_min = MMC_SPI_MIN_CLOCK; + mmc->block_dev.part_type = PART_TYPE_DOS; + mmc->b_max = MMC_SPI_MAX_BLOCKS; + + mmc_register(mmc); + + return mmc; +} diff --git a/include/mmc.h b/include/mmc.h index 04c7eaf..0422e22 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -44,6 +44,9 @@ #define MMC_MODE_HS_52MHz 0x010 #define MMC_MODE_4BIT 0x100 #define MMC_MODE_8BIT 0x200 +#define MMC_MODE_SPI 0x400 + +#define mmc_host_is_spi(mmc) ((mmc)->host_caps & MMC_MODE_SPI)
#define SD_DATA_4BIT 0x00040000
@@ -75,6 +78,8 @@ #define MMC_CMD_WRITE_SINGLE_BLOCK 24 #define MMC_CMD_WRITE_MULTIPLE_BLOCK 25 #define MMC_CMD_APP_CMD 55 +#define MMC_CMD_SPI_READ_OCR 58 +#define MMC_CMD_SPI_CRC_ON_OFF 59
#define SD_CMD_SEND_RELATIVE_ADDR 3 #define SD_CMD_SWITCH_FUNC 6 @@ -277,6 +282,9 @@ struct mmc *find_mmc_device(int dev_num); void print_mmc_devices(char separator); int board_mmc_getcd(u8 *cd, struct mmc *mmc);
+/* Driver initialization prototypes */ +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode); + #ifndef CONFIG_GENERIC_MMC int mmc_legacy_init(int verbose); #endif

Hi Andy,
Would you please review these patches?
05/03 [PATCH 1/3] lib: add crc7 from Linux 05/19 [PATCH 3/3 v8] mmc: add generic mmc spi driver
Best regards, Thomas

On Wednesday, May 19, 2010 00:37:47 Thomas Chou wrote:
--- /dev/null +++ b/common/cmd_mmc_spi.c
- printf("%s: %d at %u:%u %u %u\n", mmc->name, mmc->block_dev.dev,
bus, cs, speed, mode);
this is a bit terse. how about prefixing the hz output with like "hz:" and the mode with like "mode:" ?
+U_BOOT_CMD(
- mmc_spi, 4, 0, do_mmc_spi,
- "mmc_spi setup",
- "[bus:]cs [hz] [mode] - setup mmc_spi device on given\n"
- " SPI bus and chip select\n"
+);
there should be no newline at the end of the help string
--- /dev/null +++ b/drivers/mmc/mmc_spi.c +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{
- struct mmc *mmc;
- mmc->b_max = MMC_SPI_MAX_BLOCKS;
do you have some local modification ? i dont see b_max anywhere in include/mmc.h ...
unfortunately though, i tried this on my system and it doesnt seem to work. using a simple SPI<->MMC card, the old mmc_spi driver works on my board, but booting the new u-boot and running the same things shows:
bfin> mmc_spi 4 30000000 3 MMC_SPI: 0 at 0:4 30000000 3
bfin> mmc list MMC_SPI: 0
bfin> mmcinfo Card did not respond to voltage select! Device: MMC_SPI Manufacturer ID: 0 OEM: 0 Name: Tran Speed: 0 Rd Block Len: 0 MMC version 0.0 High Capacity: No Capacity: 0 Bus Width: 1-bit
enabling debugging in the driver shows this: bfin> mmcinfo mmc_spi_init_p: clock 0 mmc_spi_set_ios: clock 0 mmc_spi_set_ios: clock 400000 mmc_spi_request:cmd0 0 0 0 mmc_spi_sendcmd:cmd0 resp6 1 mmc_spi_request:cmd8 15 1aa 0 mmc_spi_sendcmd:cmd8 resp8 ff mmc_spi_request:cmd55 15 0 0 mmc_spi_sendcmd:cmd55 resp6 5 mmc_spi_request:cmd0 0 0 0 mmc_spi_sendcmd:cmd0 resp6 1 mmc_spi_request:cmd1 1 40300000 0 mmc_spi_sendcmd:cmd1 resp6 1 mmc_spi_request:cmd1 1 40300000 0 mmc_spi_sendcmd:cmd1 resp6 1 mmc_spi_request:cmd1 1 40300000 0 mmc_spi_sendcmd:cmd1 resp4 1 mmc_spi_request:cmd1 1 40300000 0 mmc_spi_sendcmd:cmd1 resp4 1 <these last 2 lines repeat for a while> Card did not respond to voltage select! ... -mike

Hi Mike,
Thank you very much for the review and testing.
On 07/05/2010 03:40 PM, Mike Frysinger wrote:
On Wednesday, May 19, 2010 00:37:47 Thomas Chou wrote:
--- /dev/null +++ b/common/cmd_mmc_spi.c
- printf("%s: %d at %u:%u %u %u\n", mmc->name, mmc->block_dev.dev,
bus, cs, speed, mode);
this is a bit terse. how about prefixing the hz output with like "hz:" and the mode with like "mode:" ?
Yes, I will add them.
+U_BOOT_CMD(
- mmc_spi, 4, 0, do_mmc_spi,
- "mmc_spi setup",
- "[bus:]cs [hz] [mode] - setup mmc_spi device on given\n"
- " SPI bus and chip select\n"
+);
there should be no newline at the end of the help string
OK. I will remove the last newline.
--- /dev/null +++ b/drivers/mmc/mmc_spi.c +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{
- struct mmc *mmc;
- mmc->b_max = MMC_SPI_MAX_BLOCKS;
do you have some local modification ? i dont see b_max anywhere in include/mmc.h ...
Please apply the multi-blocks patches (1,2,3/4) from Alagu on 05/12, as Andy said he had applied them.
unfortunately though, i tried this on my system and it doesnt seem to work. using a simple SPI<->MMC card, the old mmc_spi driver works on my board, but booting the new u-boot and running the same things shows:
mmc_spi_request:cmd1 1 40300000 0 mmc_spi_sendcmd:cmd1 resp4 1 <these last 2 lines repeat for a while> Card did not respond to voltage select!
It seems the mmc card was not initialized and timed out. Please try remove the OCR_HCS in mmc_send_op_cond() of mmc.c temporarily.
cmd.cmdarg = OCR_HCS | mmc->voltages; ---------------------^^^^^^^^^
Best regards, Thomas

On Monday, July 05, 2010 10:22:45 Thomas Chou wrote:
On 07/05/2010 03:40 PM, Mike Frysinger wrote:
On Wednesday, May 19, 2010 00:37:47 Thomas Chou wrote:
--- /dev/null +++ b/drivers/mmc/mmc_spi.c +struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) +{
- struct mmc *mmc;
- mmc->b_max = MMC_SPI_MAX_BLOCKS;
do you have some local modification ? i dont see b_max anywhere in include/mmc.h ...
Please apply the multi-blocks patches (1,2,3/4) from Alagu on 05/12, as Andy said he had applied them.
those arent required for basic probing functionality, right ?
unfortunately though, i tried this on my system and it doesnt seem to work. using a simple SPI<->MMC card, the old mmc_spi driver works on my board, but booting the new u-boot and running the same things shows:
mmc_spi_request:cmd1 1 40300000 0 mmc_spi_sendcmd:cmd1 resp4 1 <these last 2 lines repeat for a while> Card did not respond to voltage select!
It seems the mmc card was not initialized and timed out. Please try remove the OCR_HCS in mmc_send_op_cond() of mmc.c temporarily.
cmd.cmdarg = OCR_HCS | mmc->voltages; ---------------------^^^^^^^^^
that does fix the timeout/warning, but the card doesnt probe yet: bfin> mmcinfo mmc_spi_init_p: clock 0 mmc_spi_set_ios: clock 0 mmc_spi_set_ios: clock 400000 mmc_spi_request:cmd0 0 0 0 mmc_spi_sendcmd:cmd0 resp8 ff Device: MMC_SPI Manufacturer ID: 0 OEM: 0 Name: Tran Speed: 0 Rd Block Len: 0 MMC version 0.0 High Capacity: No Capacity: 0 Bus Width: 1-bit
if i boot up the old u-boot and probe the card there, then load up the new u- boot and try again, things get further: bfin> mmcinfo mmc_spi_init_p: clock 0 mmc_spi_set_ios: clock 0 mmc_spi_set_ios: clock 400000 mmc_spi_request:cmd0 0 0 0 mmc_spi_sendcmd:cmd0 resp6 1 mmc_spi_request:cmd8 15 1aa 0 mmc_spi_sendcmd:cmd8 resp8 ff mmc_spi_request:cmd55 15 0 0 mmc_spi_sendcmd:cmd55 resp6 5 mmc_spi_request:cmd0 0 0 0 mmc_spi_sendcmd:cmd0 resp6 1 mmc_spi_request:cmd1 1 300000 0 mmc_spi_sendcmd:cmd1 resp6 1 mmc_spi_request:cmd1 1 300000 0 mmc_spi_sendcmd:cmd1 resp6 1 mmc_spi_request:cmd1 1 300000 0 mmc_spi_sendcmd:cmd1 resp6 1 mmc_spi_request:cmd1 1 300000 0 mmc_spi_sendcmd:cmd1 resp6 1 mmc_spi_request:cmd1 1 300000 0 mmc_spi_sendcmd:cmd1 resp6 1 mmc_spi_request:cmd1 1 300000 0 mmc_spi_sendcmd:cmd1 resp6 1 mmc_spi_request:cmd1 1 300000 0 mmc_spi_sendcmd:cmd1 resp6 0 mmc_spi_request:cmd58 1 0 0 mmc_spi_sendcmd:cmd58 resp6 0 r32 ffffffff mmc_spi_request:cmd10 7 0 0 mmc_spi_sendcmd:cmd10 resp6 0 mmc_spi_readdata:tok0 80 r128 0 ff7a0000 fdff 3831f903 mmc_spi_request:cmd9 7 0 0 mmc_spi_sendcmd:cmd9 resp6 0 mmc_spi_readdata:tok1 fe r128 ff4900 263 61726420 1659810 mmc_spi_set_ios: clock 20000000 mmc_spi_request:cmd16 15 1 0 mmc_spi_sendcmd:cmd16 resp6 0 mmc_spi_request:cmd17 15 0 0 mmc_spi_sendcmd:cmd17 resp8 ff block read failed: -16 Device: MMC_SPI Manufacturer ID: 0 OEM: 0 Name: Tran Speed: 0 Rd Block Len: 1 MMC version 1.2 High Capacity: Yes Capacity: 2374355968 Bus Width: 1-bit
however, in poking the code, i see your mmc_spi_init_p() function calls spi_claim_bus(), but nowhere do i see spi_release_bus(). -mike
participants (4)
-
Andy Fleming
-
Mike Frysinger
-
Thomas Chou
-
Wolfgang Denk