Add a simple PIO based host controller for MMC and SD cards on PXA SoCs. Reads and writes are available, and no usage is made of DMA or IRQs. SPI mode is not supported yet. Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
360 lines
8.6 KiB
C
360 lines
8.6 KiB
C
/*
|
|
* PXA MCI driver
|
|
*
|
|
* Copyright (C) 2011 Robert Jarzmik
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Insprired by linux kernel driver
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <errno.h>
|
|
#include <io.h>
|
|
#include <gpio.h>
|
|
#include <clock.h>
|
|
#include <init.h>
|
|
#include <mci.h>
|
|
|
|
#include <mach/clock.h>
|
|
#include <mach/mci_pxa2xx.h>
|
|
#include <mach/pxa-regs.h>
|
|
#include "pxamci.h"
|
|
|
|
#define DRIVER_NAME "pxa-mmc"
|
|
|
|
static void clk_enable(void)
|
|
{
|
|
CKEN |= CKEN_MMC;
|
|
}
|
|
|
|
static int pxamci_set_power(struct pxamci_host *host, int on)
|
|
{
|
|
mci_dbg("on=%d\n", on);
|
|
if (host->pdata && host->pdata->gpio_power > 0)
|
|
gpio_set_value(host->pdata->gpio_power,
|
|
!!on ^ host->pdata->gpio_power_invert);
|
|
else if (host->pdata && host->pdata->setpower)
|
|
host->pdata->setpower(&host->mci, on);
|
|
return 0;
|
|
}
|
|
|
|
static void pxamci_start_clock(struct pxamci_host *host)
|
|
{
|
|
mmc_writel(START_CLOCK, MMC_STRPCL);
|
|
}
|
|
|
|
static void pxamci_stop_clock(struct pxamci_host *host)
|
|
{
|
|
uint64_t start = get_time_ns();
|
|
unsigned stat;
|
|
|
|
stat = mmc_readl(MMC_STAT);
|
|
if (stat & STAT_CLK_EN)
|
|
writel(STOP_CLOCK, host->base + MMC_STRPCL);
|
|
while (!is_timeout(start, 10 * MSECOND) && stat & STAT_CLK_EN)
|
|
stat = mmc_readl(MMC_STAT);
|
|
|
|
if (stat & STAT_CLK_EN)
|
|
mci_err("unable to stop clock\n");
|
|
}
|
|
|
|
static void pxamci_setup_data(struct pxamci_host *host, struct mci_data *data)
|
|
{
|
|
static const unsigned int timeout = 100000000; /* 10ms */
|
|
|
|
mci_dbg("nbblocks=%d, blocksize=%d\n", data->blocks, data->blocksize);
|
|
mmc_writel(data->blocks, MMC_NOB);
|
|
mmc_writel(data->blocksize, MMC_BLKLEN);
|
|
mmc_writel((timeout + 255) / 256, MMC_RDTO);
|
|
}
|
|
|
|
static int pxamci_read_data(struct pxamci_host *host, unsigned char *dst,
|
|
unsigned len)
|
|
{
|
|
int trf_len, ret = 0;
|
|
uint64_t start;
|
|
|
|
mci_dbg("dst=%p, len=%u\n", dst, len);
|
|
while (!ret && len > 0) {
|
|
trf_len = min_t(int, len, MMC_FIFO_LENGTH);
|
|
|
|
for (start = get_time_ns(), ret = -ETIMEDOUT;
|
|
ret && !is_timeout(start, 10 * MSECOND);)
|
|
if (mmc_readl(MMC_I_REG) & RXFIFO_RD_REQ)
|
|
ret = 0;
|
|
for (; !ret && trf_len > 0; trf_len--, len--)
|
|
*dst++ = mmc_readb(MMC_RXFIFO);
|
|
}
|
|
|
|
if (!ret)
|
|
for (start = get_time_ns(), ret = -ETIMEDOUT;
|
|
ret && !is_timeout(start, 10 * MSECOND);)
|
|
if (mmc_readl(MMC_STAT) & STAT_DATA_TRAN_DONE)
|
|
ret = 0;
|
|
mci_dbg("ret=%d, remain=%d, stat=%x, mmc_i_reg=%x\n",
|
|
ret, len, mmc_readl(MMC_STAT), mmc_readl(MMC_I_REG));
|
|
return ret;
|
|
}
|
|
|
|
static int pxamci_write_data(struct pxamci_host *host, const unsigned char *src,
|
|
unsigned len)
|
|
{
|
|
uint64_t start;
|
|
int trf_len, partial = 0, ret = 0;
|
|
unsigned stat;
|
|
|
|
mci_dbg("src=%p, len=%u\n", src, len);
|
|
while (!ret && len > 0) {
|
|
trf_len = min_t(int, len, MMC_FIFO_LENGTH);
|
|
partial = trf_len < MMC_FIFO_LENGTH;
|
|
|
|
for (start = get_time_ns(), ret = -ETIMEDOUT;
|
|
ret && !is_timeout(start, 10 * MSECOND);)
|
|
if (mmc_readl(MMC_I_REG) & TXFIFO_WR_REQ)
|
|
ret = 0;
|
|
for (; !ret && trf_len > 0; trf_len--, len--)
|
|
mmc_writeb(*src++, MMC_TXFIFO);
|
|
if (partial)
|
|
mmc_writeb(BUF_PART_FULL, MMC_PRTBUF);
|
|
}
|
|
|
|
if (!ret)
|
|
for (start = get_time_ns(), ret = -ETIMEDOUT;
|
|
ret && !is_timeout(start, 10 * MSECOND);) {
|
|
stat = mmc_readl(MMC_STAT);
|
|
stat &= STAT_DATA_TRAN_DONE | STAT_PRG_DONE;
|
|
if (stat == (STAT_DATA_TRAN_DONE | STAT_PRG_DONE))
|
|
ret = 0;
|
|
}
|
|
mci_dbg("ret=%d, remain=%d, stat=%x, mmc_i_reg=%x\n",
|
|
ret, len, mmc_readl(MMC_STAT), mmc_readl(MMC_I_REG));
|
|
return ret;
|
|
}
|
|
|
|
static int pxamci_transfer_data(struct pxamci_host *host,
|
|
struct mci_data *data)
|
|
{
|
|
int nbbytes = data->blocks * data->blocksize;
|
|
int ret;
|
|
unsigned err_mask = STAT_CRC_READ_ERROR | STAT_CRC_WRITE_ERROR |
|
|
STAT_READ_TIME_OUT;
|
|
|
|
if (data->flags & MMC_DATA_WRITE)
|
|
ret = pxamci_write_data(host, data->src, nbbytes);
|
|
else
|
|
ret = pxamci_read_data(host, data->dest, nbbytes);
|
|
|
|
if (!ret && (mmc_readl(MMC_STAT) & err_mask))
|
|
ret = -EILSEQ;
|
|
return ret;
|
|
}
|
|
|
|
static void pxamci_start_cmd(struct pxamci_host *host, struct mci_cmd *cmd,
|
|
unsigned int cmdat)
|
|
{
|
|
mci_dbg("cmd=(idx=%d,type=%d)\n", cmd->cmdidx, cmd->resp_type);
|
|
if (cmd->resp_type & MMC_RSP_BUSY)
|
|
cmdat |= CMDAT_BUSY;
|
|
|
|
switch (cmd->resp_type) {
|
|
/* r1, r1b, r6, r7 */
|
|
case MMC_RSP_R1:
|
|
case MMC_RSP_R1b:
|
|
cmdat |= CMDAT_RESP_SHORT;
|
|
break;
|
|
case MMC_RSP_R2:
|
|
cmdat |= CMDAT_RESP_R2;
|
|
break;
|
|
case MMC_RSP_R3:
|
|
cmdat |= CMDAT_RESP_R3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mmc_writel(cmd->cmdidx, MMC_CMD);
|
|
mmc_writel(cmd->cmdarg >> 16, MMC_ARGH);
|
|
mmc_writel(cmd->cmdarg & 0xffff, MMC_ARGL);
|
|
mmc_writel(host->clkrt, MMC_CLKRT);
|
|
pxamci_start_clock(host);
|
|
mmc_writel(cmdat, MMC_CMDAT);
|
|
}
|
|
|
|
static int pxamci_cmd_response(struct pxamci_host *host, struct mci_cmd *cmd)
|
|
{
|
|
unsigned v, stat;
|
|
int i;
|
|
|
|
/*
|
|
* Did I mention this is Sick. We always need to
|
|
* discard the upper 8 bits of the first 16-bit word.
|
|
*/
|
|
v = mmc_readl(MMC_RES) & 0xffff;
|
|
for (i = 0; i < 4; i++) {
|
|
u32 w1 = mmc_readl(MMC_RES) & 0xffff;
|
|
u32 w2 = mmc_readl(MMC_RES) & 0xffff;
|
|
cmd->response[i] = v << 24 | w1 << 8 | w2 >> 8;
|
|
v = w2;
|
|
}
|
|
|
|
stat = mmc_readl(MMC_STAT);
|
|
if (stat & STAT_TIME_OUT_RESPONSE)
|
|
return -ETIMEDOUT;
|
|
if (stat & STAT_RES_CRC_ERR && cmd->resp_type & MMC_RSP_CRC) {
|
|
/*
|
|
* workaround for erratum #42:
|
|
* Intel PXA27x Family Processor Specification Update Rev 001
|
|
* A bogus CRC error can appear if the msb of a 136 bit
|
|
* response is a one.
|
|
*/
|
|
if (cpu_is_pxa27x() && cmd->resp_type & MMC_RSP_136 &&
|
|
cmd->response[0] & 0x80000000)
|
|
pr_debug("ignoring CRC from command %d - *risky*\n",
|
|
cmd->cmdidx);
|
|
else
|
|
return -EILSEQ;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pxamci_mmccmd(struct pxamci_host *host, struct mci_cmd *cmd,
|
|
struct mci_data *data, unsigned int cmddat)
|
|
{
|
|
int ret = 0;
|
|
uint64_t start;
|
|
|
|
pxamci_start_cmd(host, cmd, cmddat);
|
|
for (start = get_time_ns(), ret = -ETIMEDOUT;
|
|
ret && !is_timeout(start, 10 * MSECOND);)
|
|
if (mmc_readl(MMC_STAT) & STAT_END_CMD_RES)
|
|
ret = 0;
|
|
|
|
if (!ret && data)
|
|
ret = pxamci_transfer_data(host, data);
|
|
|
|
if (!ret)
|
|
ret = pxamci_cmd_response(host, cmd);
|
|
return ret;
|
|
}
|
|
|
|
static int pxamci_request(struct mci_host *mci, struct mci_cmd *cmd,
|
|
struct mci_data *data)
|
|
{
|
|
struct pxamci_host *host = to_pxamci(mci);
|
|
unsigned int cmdat;
|
|
int ret;
|
|
|
|
pxamci_stop_clock(host);
|
|
|
|
cmdat = host->cmdat;
|
|
host->cmdat &= ~CMDAT_INIT;
|
|
|
|
if (data) {
|
|
pxamci_setup_data(host, data);
|
|
|
|
cmdat &= ~CMDAT_BUSY;
|
|
cmdat |= CMDAT_DATAEN;
|
|
if (data->flags & MMC_DATA_WRITE)
|
|
cmdat |= CMDAT_WRITE;
|
|
}
|
|
|
|
ret = pxamci_mmccmd(host, cmd, data, cmdat);
|
|
return ret;
|
|
}
|
|
|
|
static void pxamci_set_ios(struct mci_host *mci, struct device_d *dev,
|
|
unsigned bus_width, unsigned clock)
|
|
{
|
|
struct pxamci_host *host = to_pxamci(mci);
|
|
unsigned int clk_in = pxa_get_mmcclk();
|
|
unsigned long fact;
|
|
|
|
mci_dbg("bus_width=%d, clock=%ud\n", bus_width, clock);
|
|
fact = min_t(int, clock / clk_in, 1);
|
|
fact = max_t(int, fact, 1 << 6);
|
|
|
|
/*
|
|
* We calculate clkrt here, and will write it on the next command
|
|
* MMC card clock = mmcclk / (2 ^ clkrt)
|
|
*/
|
|
/* to handle (19.5MHz, 26MHz) */
|
|
host->clkrt = fls(fact) - 1;
|
|
|
|
if (bus_width == 4)
|
|
host->cmdat |= CMDAT_SD_4DAT;
|
|
else
|
|
host->cmdat &= ~CMDAT_SD_4DAT;
|
|
host->cmdat |= CMDAT_INIT;
|
|
|
|
clk_enable();
|
|
pxamci_set_power(host, 1);
|
|
}
|
|
|
|
static int pxamci_init(struct mci_host *mci, struct device_d *dev)
|
|
{
|
|
struct pxamci_host *host = to_pxamci(mci);
|
|
|
|
if (host->pdata && host->pdata->init)
|
|
return host->pdata->init(mci, dev);
|
|
return 0;
|
|
}
|
|
|
|
static int pxamci_probe(struct device_d *dev)
|
|
{
|
|
struct pxamci_host *host;
|
|
int gpio_power = -1;
|
|
|
|
host = xzalloc(sizeof(*host));
|
|
host->base = dev_request_mem_region(dev, 0);
|
|
|
|
host->mci.init = pxamci_init;
|
|
host->mci.send_cmd = pxamci_request;
|
|
host->mci.set_ios = pxamci_set_ios;
|
|
host->mci.host_caps = MMC_MODE_4BIT;
|
|
host->mci.hw_dev = dev;
|
|
host->mci.voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
|
|
|
/*
|
|
* Calculate minimum clock rate, rounding up.
|
|
*/
|
|
host->mci.f_min = pxa_get_mmcclk() >> 6;
|
|
host->mci.f_max = pxa_get_mmcclk();
|
|
|
|
/*
|
|
* Ensure that the host controller is shut down, and setup
|
|
* with our defaults.
|
|
*/
|
|
pxamci_stop_clock(host);
|
|
mmc_writel(0, MMC_SPI);
|
|
mmc_writel(64, MMC_RESTO);
|
|
mmc_writel(0, MMC_I_MASK);
|
|
|
|
host->pdata = dev->platform_data;
|
|
if (host->pdata)
|
|
gpio_power = host->pdata->gpio_power;
|
|
|
|
if (gpio_power > 0)
|
|
gpio_direction_output(gpio_power,
|
|
host->pdata->gpio_power_invert);
|
|
|
|
mci_register(&host->mci);
|
|
return 0;
|
|
}
|
|
|
|
static struct driver_d pxamci_driver = {
|
|
.name = DRIVER_NAME,
|
|
.probe = pxamci_probe,
|
|
};
|
|
|
|
static int __init pxamci_init_driver(void)
|
|
{
|
|
register_driver(&pxamci_driver);
|
|
return 0;
|
|
}
|
|
|
|
device_initcall(pxamci_init_driver);
|