diff options
Diffstat (limited to 'drivers/mci/mci_spi.c')
-rw-r--r-- | drivers/mci/mci_spi.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/drivers/mci/mci_spi.c b/drivers/mci/mci_spi.c new file mode 100644 index 0000000000..653910bf7f --- /dev/null +++ b/drivers/mci/mci_spi.c @@ -0,0 +1,430 @@ +/* + * (C) Copyright 2011 - Franck JULLIEN <elec4fun@gmail.com> + * + * This code was inspired from u-boot mmc_spi.c: + * Copyright (C) 2010 Thomas Chou <thomas@wytron.com.tw> + * + * and linux mmc_spi.c: + * (C) Copyright 2005, Intec Automation, + * Mike Lavender (mike@steroidmicros) + * (C) Copyright 2006-2007, David Brownell + * (C) Copyright 2007, Axis Communications, + * Hans-Peter Nilsson (hp@axis.com) + * (C) Copyright 2007, ATRON electronic GmbH, + * Jan Nikitenko <jan.nikitenko@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <common.h> +#include <init.h> +#include <errno.h> +#include <clock.h> +#include <asm/io.h> +#include <driver.h> +#include <spi/spi.h> +#include <mci.h> +#include <crc.h> +#include <crc7.h> + +#define to_spi_host(mci) container_of(mci, struct mmc_spi_host, mci) +#define spi_setup(spi) spi->master->setup(spi) + +/* Response tokens used to ack each block written: */ +#define SPI_MMC_RESPONSE_CODE(x) ((x) & 0x1f) +#define SPI_RESPONSE_ACCEPTED ((2 << 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)) + +#define MMC_SPI_BLOCKSIZE 512 + +/* timeout value */ +#define CTOUT 8 +#define RTOUT 3000000 /* 1 sec */ +#define WTOUT 3000000 /* 1 sec */ + +struct mmc_spi_host { + struct mci_host mci; + struct spi_device *spi; + struct device_d *dev; + + /* for bulk data transfers */ + struct spi_transfer t_tx; + struct spi_message m_tx; + + /* for status readback */ + struct spi_transfer t_rx; + struct spi_message m_rx; + + void *ones; +}; + +static char *maptype(struct mci_cmd *cmd) +{ + switch (cmd->resp_type) { + case MMC_RSP_NONE: return "NONE"; + case MMC_RSP_R1: return "R1"; + case MMC_RSP_R1b: return "R1B"; + case MMC_RSP_R2: return "R2/R5"; + case MMC_RSP_R3: return "R3/R4/R7"; + default: return "?"; + } +} + +static inline int mmc_cs_off(struct mmc_spi_host *host) +{ + /* chipselect will always be inactive after setup() */ + return spi_setup(host->spi); +} + +static int +mmc_spi_readbytes(struct mmc_spi_host *host, unsigned len, void *data) +{ + int status; + + host->t_rx.len = len; + host->t_rx.rx_buf = data; + + status = spi_sync(host->spi, &host->m_rx); + + return status; +} + +static int +mmc_spi_writebytes(struct mmc_spi_host *host, unsigned len, void *data) +{ + int status; + + host->t_tx.len = len; + host->t_tx.tx_buf = data; + + status = spi_sync(host->spi, &host->m_tx); + + return status; +} + +/* + * Note that while the CRC, in general, is ignored in SPI mode, the very first + * command must be followed by a valid CRC, since the card is not yet in SPI mode. + * The CRC byte for a CMD0 command with a zero argument is a constant 0x4A. For + * simplicity, this CRC byte is always sent with every command. + */ +#define MMC_SPI_CMD0_CRC ((0x4a << 1) | 0x1) + +static int mmc_spi_command_send(struct mmc_spi_host *host, struct mci_cmd *cmd) +{ + uint8_t r1; + uint8_t command[7]; + int i; + + command[0] = 0xff; + command[1] = MMC_SPI_CMD(cmd->cmdidx); + command[2] = cmd->cmdarg >> 24; + command[3] = cmd->cmdarg >> 16; + command[4] = cmd->cmdarg >> 8; + command[5] = cmd->cmdarg; +#ifdef CONFIG_MMC_SPI_CRC_ON + command[6] = (crc7(0, &command[1], 5) << 1) | 0x01; +#else + command[6] = MMC_SPI_CMD0_CRC; +#endif + + mmc_spi_writebytes(host, 7, command); + + for (i = 0; i < CTOUT; i++) { + mmc_spi_readbytes(host, 1, &r1); + if (i && ((r1 & 0x80) == 0)) { /* r1 response */ + dev_dbg(host->dev, "%s: CMD%d, TRY %d, RESP %x\n", __func__, cmd->cmdidx, i, r1); + break; + } + } + + return r1; +} + +static uint mmc_spi_readdata(struct mmc_spi_host *host, void *xbuf, + uint32_t bcnt, uint32_t bsize) +{ + uint8_t *buf = xbuf; + uint8_t r1; + uint16_t crc; + int i; + + while (bcnt--) { + for (i = 0; i < RTOUT; i++) { + mmc_spi_readbytes(host, 1, &r1); + if (r1 != 0xff) /* data token */ + break; + } + if (r1 == SPI_TOKEN_SINGLE) { + mmc_spi_readbytes(host, bsize, buf); + mmc_spi_readbytes(host, 2, &crc); +#ifdef CONFIG_MMC_SPI_CRC_ON + if (swab16(cyg_crc16(buf, bsize)) != crc) { + dev_dbg(host->dev, "%s: CRC error\n", __func__); + 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_spi_host *host, const void *xbuf, + uint32_t bcnt, uint32_t bsize, int multi) +{ + const uint8_t *buf = xbuf; + uint8_t r1; + uint16_t crc = 0; + uint8_t 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 = swab16(cyg_crc16((u8 *)buf, bsize)); +#endif + mmc_spi_writebytes(host, 2, tok); + mmc_spi_writebytes(host, bsize, (void *)buf); + mmc_spi_writebytes(host, 2, &crc); + + for (i = 0; i < CTOUT; i++) { + mmc_spi_readbytes(host, 1, &r1); + if ((r1 & 0x11) == 0x01) /* response token */ + break; + } + + dev_dbg(host->dev,"%s : TOKEN%d RESP 0x%X\n", __func__, i, r1); + if (SPI_MMC_RESPONSE_CODE(r1) == SPI_RESPONSE_ACCEPTED) { + for (i = 0; i < WTOUT; i++) { /* wait busy */ + mmc_spi_readbytes(host, 1, &r1); + if (i && r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + dev_dbg(host->dev, "%s: wtout %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + break; + } + } else { + dev_dbg(host->dev, "%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; + mmc_spi_writebytes(host, 2, tok); + for (i = 0; i < WTOUT; i++) { /* wait busy */ + mmc_spi_readbytes(host, 1, &r1); + if (i && r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WTOUT) { + dev_dbg(host->dev, "%s: wstop %x\n", __func__, r1); + r1 = R1_SPI_ERROR; + } + } + + return r1; +} + +static int mmc_spi_request(struct mci_host *mci, struct mci_cmd *cmd, struct mci_data *data) +{ + struct mmc_spi_host *host = to_spi_host(mci); + uint8_t r1; + int i; + int ret = 0; + + dev_dbg(host->dev, "%s : CMD%02d, RESP %s, ARG 0x%X\n", __func__, + cmd->cmdidx, maptype(cmd), cmd->cmdarg); + + r1 = mmc_spi_command_send(host, cmd); + + cmd->response[0] = r1; + + if (r1 == 0xff) { /* no response */ + ret = -ETIME; + goto done; + } else if (r1 & R1_SPI_COM_CRC) { + ret = -ECOMM; + goto done; + } else if (r1 & ~R1_SPI_IDLE) { /* other errors */ + ret = -ETIME; + goto done; + } else if (cmd->resp_type == MMC_RSP_R2) { + r1 = mmc_spi_readdata(host, cmd->response, 1, 16); + for (i = 0; i < 4; i++) + cmd->response[i] = swab32(cmd->response[i]); + dev_dbg(host->dev, "MMC_RSP_R2 -> %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_SEND_IF_COND: + case MMC_CMD_SPI_READ_OCR: + mmc_spi_readbytes(host, 4, cmd->response); + cmd->response[0] = swab32(cmd->response[0]); + break; + } + } else { + if (data->flags == MMC_DATA_READ) { + dev_dbg(host->dev, "%s : DATA READ, %x blocks, bsize = 0x%X\n", __func__, + data->blocks, data->blocksize); + r1 = mmc_spi_readdata(host, data->dest, + data->blocks, data->blocksize); + } else if (data->flags == MMC_DATA_WRITE) { + dev_dbg(host->dev, "%s : DATA WRITE, %x blocks, bsize = 0x%X\n", __func__, + data->blocks, data->blocksize); + r1 = mmc_spi_writedata(host, data->src, + data->blocks, data->blocksize, + (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)); + } + if (r1 & R1_SPI_COM_CRC) + ret = -ECOMM; + else if (r1) + ret = -ETIME; + } + +done: + mmc_cs_off(host); + return ret; + +return 0; + +} + +static void mmc_spi_set_ios(struct mci_host *mci, struct device_d *mci_dev, + unsigned bus_width, unsigned clock) +{ + struct mmc_spi_host *host = to_spi_host(mci); + + spi_setup(host->spi); +} + +static int mmc_spi_init(struct mci_host *mci, struct device_d *mci_dev) +{ + struct mmc_spi_host *host = to_spi_host(mci); + mmc_spi_readbytes(host, 10, NULL); + + /* + * Do a burst with chipselect active-high. We need to do this to + * meet the requirement of 74 clock cycles with both chipselect + * and CMD (MOSI) high before CMD0 ... after the card has been + * powered up to Vdd(min), and so is ready to take commands. + * + * Some cards are particularly needy of this (e.g. Viking "SD256") + * while most others don't seem to care. + * + * Note that this is one of the places MMC/SD plays games with the + * SPI protocol. Another is that when chipselect is released while + * the card returns BUSY status, the clock must issue several cycles + * with chipselect high before the card will stop driving its output. + */ + + host->spi->mode |= SPI_CS_HIGH; + if (spi_setup(host->spi) != 0) { + /* Just warn; most cards work without it. */ + dev_warn(&host->spi->dev, + "can't change chip-select polarity\n"); + host->spi->mode &= ~SPI_CS_HIGH; + } else { + mmc_spi_readbytes(host, 18, NULL); + + host->spi->mode &= ~SPI_CS_HIGH; + if (spi_setup(host->spi) != 0) { + /* Wot, we can't get the same setup we had before? */ + dev_err(&host->spi->dev, + "can't restore chip-select polarity\n"); + } + } + + return 0; +} + +static int spi_mci_probe(struct device_d *dev) +{ + struct spi_device *spi = (struct spi_device *)dev->type_data; + struct mmc_spi_host *host; + void *ones; + + host = xzalloc(sizeof(*host)); + host->mci.send_cmd = mmc_spi_request; + host->mci.set_ios = mmc_spi_set_ios; + host->mci.init = mmc_spi_init; + + host->dev = dev; + host->spi = spi; + dev->priv = host; + + ones = xmalloc(MMC_SPI_BLOCKSIZE); + memset(ones, 0xff, MMC_SPI_BLOCKSIZE); + + host->ones = ones; + + spi_message_init(&host->m_tx); + spi_message_init(&host->m_rx); + + spi_message_add_tail(&host->t_tx, &host->m_tx); + spi_message_add_tail(&host->t_rx, &host->m_rx); + + host->t_rx.tx_buf = host->ones; + host->t_rx.cs_change = 1; + + host->t_tx.cs_change = 1; + + host->mci.voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + host->mci.host_caps = MMC_CAP_SPI; + + mci_register(&host->mci); + + return 0; +} + +static struct driver_d spi_mci_driver = { + .name = "spi_mci", + .probe = spi_mci_probe, +}; + +static int spi_mci_init_driver(void) +{ + register_driver(&spi_mci_driver); + return 0; +} + +device_initcall(spi_mci_init_driver); |