mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
293 lines
7.4 KiB
293 lines
7.4 KiB
/* |
|
* generic mmc spi driver |
|
* |
|
* Copyright (C) 2010 Thomas Chou <[email protected]> |
|
* Licensed under the GPL-2 or later. |
|
*/ |
|
#include <common.h> |
|
#include <errno.h> |
|
#include <malloc.h> |
|
#include <part.h> |
|
#include <mmc.h> |
|
#include <spi.h> |
|
#include <crc.h> |
|
#include <linux/crc7.h> |
|
#include <asm/byteorder.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, reuse this bit for error */ |
|
#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 8 |
|
#define RTOUT 3000000 /* 1 sec */ |
|
#define WTOUT 3000000 /* 1 sec */ |
|
|
|
static uint mmc_spi_sendcmd(struct mmc *mmc, ushort 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, sizeof(cmdo) * 8, cmdo, NULL, 0); |
|
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, void *xbuf, |
|
u32 bcnt, u32 bsize) |
|
{ |
|
struct spi_slave *spi = mmc->priv; |
|
u8 *buf = xbuf; |
|
u8 r1; |
|
u16 crc; |
|
int i; |
|
while (bcnt--) { |
|
for (i = 0; i < RTOUT; i++) { |
|
spi_xfer(spi, 1 * 8, NULL, &r1, 0); |
|
if (r1 != 0xff) /* data token */ |
|
break; |
|
} |
|
debug("%s:tok%d %x\n", __func__, i, r1); |
|
if (r1 == 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 (be_to_cpu16(crc16_ccitt(0, buf, bsize)) != crc) { |
|
debug("%s: CRC error\n", mmc->cfg->name); |
|
r1 = R1_SPI_COM_CRC; |
|
break; |
|
} |
|
#endif |
|
r1 = 0; |
|
} else { |
|
r1 = R1_SPI_ERROR; |
|
break; |
|
} |
|
buf += bsize; |
|
} |
|
return r1; |
|
} |
|
|
|
static uint mmc_spi_writedata(struct mmc *mmc, const void *xbuf, |
|
u32 bcnt, u32 bsize, int multi) |
|
{ |
|
struct spi_slave *spi = mmc->priv; |
|
const u8 *buf = xbuf; |
|
u8 r1; |
|
u16 crc; |
|
u8 tok[2]; |
|
int i; |
|
tok[0] = 0xff; |
|
tok[1] = multi ? SPI_TOKEN_MULTI_WRITE : SPI_TOKEN_SINGLE; |
|
while (bcnt--) { |
|
#ifdef CONFIG_MMC_SPI_CRC_ON |
|
crc = cpu_to_be16(crc16_ccitt(0, (u8 *)buf, bsize)); |
|
#endif |
|
spi_xfer(spi, 2 * 8, tok, 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 */ |
|
tok[1] = SPI_TOKEN_STOP_TRAN; |
|
spi_xfer(spi, 2 * 8, tok, 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; |
|
} |
|
|
|
static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd, |
|
struct mmc_data *data) |
|
{ |
|
struct spi_slave *spi = mmc->priv; |
|
u8 r1; |
|
int i; |
|
int ret = 0; |
|
debug("%s:cmd%d %x %x\n", __func__, |
|
cmd->cmdidx, cmd->resp_type, cmd->cmdarg); |
|
spi_claim_bus(spi); |
|
spi_cs_activate(spi); |
|
r1 = mmc_spi_sendcmd(mmc, cmd->cmdidx, cmd->cmdarg); |
|
if (r1 == 0xff) { /* no response */ |
|
ret = -ENOMEDIUM; |
|
goto done; |
|
} else if (r1 & R1_SPI_COM_CRC) { |
|
ret = -ECOMM; |
|
goto done; |
|
} else if (r1 & ~R1_SPI_IDLE) { /* other errors */ |
|
ret = -ETIMEDOUT; |
|
goto done; |
|
} else if (cmd->resp_type == MMC_RSP_R2) { |
|
r1 = mmc_spi_readdata(mmc, cmd->response, 1, 16); |
|
for (i = 0; i < 4; i++) |
|
cmd->response[i] = be32_to_cpu(cmd->response[i]); |
|
debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1], |
|
cmd->response[2], cmd->response[3]); |
|
} else if (!data) { |
|
switch (cmd->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, cmd->response, 0); |
|
cmd->response[0] = be32_to_cpu(cmd->response[0]); |
|
debug("r32 %x\n", cmd->response[0]); |
|
break; |
|
case MMC_CMD_SEND_STATUS: |
|
spi_xfer(spi, 1 * 8, NULL, cmd->response, 0); |
|
cmd->response[0] = (cmd->response[0] & 0xff) ? |
|
MMC_STATUS_ERROR : MMC_STATUS_RDY_FOR_DATA; |
|
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, |
|
(cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)); |
|
if (r1 & R1_SPI_COM_CRC) |
|
ret = -ECOMM; |
|
else if (r1) /* other errors */ |
|
ret = -ETIMEDOUT; |
|
} |
|
done: |
|
spi_cs_deactivate(spi); |
|
spi_release_bus(spi); |
|
return ret; |
|
} |
|
|
|
static int mmc_spi_set_ios(struct mmc *mmc) |
|
{ |
|
struct spi_slave *spi = mmc->priv; |
|
|
|
debug("%s: clock %u\n", __func__, mmc->clock); |
|
if (mmc->clock) |
|
spi_set_speed(spi, mmc->clock); |
|
return 0; |
|
} |
|
|
|
static int mmc_spi_init_p(struct mmc *mmc) |
|
{ |
|
struct spi_slave *spi = mmc->priv; |
|
spi_set_speed(spi, MMC_SPI_MIN_CLOCK); |
|
spi_claim_bus(spi); |
|
/* cs deactivated for 100+ clock */ |
|
spi_xfer(spi, 18 * 8, NULL, NULL, 0); |
|
spi_release_bus(spi); |
|
return 0; |
|
} |
|
|
|
static const struct mmc_ops mmc_spi_ops = { |
|
.send_cmd = mmc_spi_request, |
|
.set_ios = mmc_spi_set_ios, |
|
.init = mmc_spi_init_p, |
|
}; |
|
|
|
static struct mmc_config mmc_spi_cfg = { |
|
.name = "MMC_SPI", |
|
.ops = &mmc_spi_ops, |
|
.host_caps = MMC_MODE_SPI, |
|
.voltages = MMC_SPI_VOLTAGE, |
|
.f_min = MMC_SPI_MIN_CLOCK, |
|
.part_type = PART_TYPE_DOS, |
|
.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, |
|
}; |
|
|
|
struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode) |
|
{ |
|
struct mmc *mmc; |
|
struct spi_slave *spi; |
|
|
|
spi = spi_setup_slave(bus, cs, speed, mode); |
|
if (spi == NULL) |
|
return NULL; |
|
|
|
mmc_spi_cfg.f_max = speed; |
|
|
|
mmc = mmc_create(&mmc_spi_cfg, spi); |
|
if (mmc == NULL) { |
|
spi_free_slave(spi); |
|
return NULL; |
|
} |
|
return mmc; |
|
}
|
|
|