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