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 <tho...@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 <tho...@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 <tho...@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 -- 1.7.1.86.g0e460 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot