382 lines
9.2 KiB
C
382 lines
9.2 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"
|
|
|
|
#define RX_TIMEOUT (100 * MSECOND)
|
|
#define TX_TIMEOUT (250 * MSECOND)
|
|
#define CMD_TIMEOUT (100 * MSECOND)
|
|
|
|
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);
|
|
mdelay(250);
|
|
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, CMD_TIMEOUT) && 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_ns = 1000 * MSECOND; /* 1000 ms */
|
|
|
|
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(DIV_ROUND_UP(timeout_ns, 13128), MMC_RDTO);
|
|
}
|
|
|
|
static int pxamci_read_data(struct pxamci_host *host, unsigned char *dst,
|
|
unsigned len)
|
|
{
|
|
int trf_len, trf_len1, trf_len4, ret = 0;
|
|
uint64_t start;
|
|
u32 *dst4;
|
|
|
|
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, RX_TIMEOUT);)
|
|
if (mmc_readl(MMC_I_REG) & RXFIFO_RD_REQ)
|
|
ret = 0;
|
|
|
|
trf_len1 = trf_len % 4;
|
|
trf_len4 = trf_len / 4;
|
|
for (dst4 = (u32 *)dst; !ret && trf_len4 > 0; trf_len4--)
|
|
*dst4++ = mmc_readl(MMC_RXFIFO);
|
|
for (dst = (u8 *)dst4; !ret && trf_len1 > 0; trf_len1--)
|
|
*dst++ = mmc_readb(MMC_RXFIFO);
|
|
len -= trf_len;
|
|
}
|
|
|
|
if (!ret)
|
|
for (start = get_time_ns(), ret = -ETIMEDOUT;
|
|
ret && !is_timeout(start, RX_TIMEOUT);)
|
|
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, TX_TIMEOUT);)
|
|
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, TX_TIMEOUT);) {
|
|
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;
|
|
}
|
|
|
|
#define MMC_RSP_MASK (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC | \
|
|
MMC_RSP_BUSY | MMC_RSP_OPCODE)
|
|
static void pxamci_start_cmd(struct pxamci_host *host, struct mci_cmd *cmd,
|
|
unsigned int cmdat)
|
|
{
|
|
mci_dbg("cmd=(idx=%d,type=%d,clkrt=%d)\n", cmd->cmdidx, cmd->resp_type,
|
|
host->clkrt);
|
|
|
|
switch (cmd->resp_type & MMC_RSP_MASK) {
|
|
/* r1, r1b, r6, r7 */
|
|
case MMC_RSP_R1b:
|
|
cmdat |= CMDAT_BUSY;
|
|
case MMC_RSP_R1:
|
|
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;
|
|
}
|
|
|
|
if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION)
|
|
cmdat |= CMDAT_STOP_TRAN;
|
|
|
|
mmc_writel(cmd->cmdidx, MMC_CMD);
|
|
mmc_writel(cmd->cmdarg >> 16, MMC_ARGH);
|
|
mmc_writel(cmd->cmdarg & 0xffff, MMC_ARGL);
|
|
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, stat_mask;
|
|
uint64_t start;
|
|
|
|
pxamci_start_cmd(host, cmd, cmddat);
|
|
|
|
stat_mask = STAT_END_CMD_RES;
|
|
if (cmd->resp_type & MMC_RSP_BUSY)
|
|
stat_mask |= STAT_PRG_DONE;
|
|
for (start = get_time_ns(), ret = -ETIMEDOUT;
|
|
ret && !is_timeout(start, CMD_TIMEOUT);)
|
|
if ((mmc_readl(MMC_STAT) & stat_mask) == stat_mask)
|
|
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;
|
|
|
|
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 mci_ios *ios)
|
|
{
|
|
struct pxamci_host *host = to_pxamci(mci);
|
|
unsigned int clk_in = pxa_get_mmcclk();
|
|
int fact;
|
|
|
|
mci_dbg("bus_width=%d, clock=%u\n", ios->bus_width, ios->clock);
|
|
if (ios->clock)
|
|
fact = min_t(int, clk_in / ios->clock, 1 << 6);
|
|
else
|
|
fact = 1 << 6;
|
|
fact = max_t(int, fact, 1);
|
|
|
|
/*
|
|
* 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;
|
|
|
|
switch (ios->bus_width) {
|
|
case MMC_BUS_WIDTH_4:
|
|
host->cmdat |= CMDAT_SD_4DAT;
|
|
break;
|
|
case MMC_BUS_WIDTH_1:
|
|
host->cmdat &= ~CMDAT_SD_4DAT;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
host->cmdat |= CMDAT_INIT;
|
|
|
|
pxamci_set_power(host, 1);
|
|
pxamci_stop_clock(host);
|
|
mmc_writel(host->clkrt, MMC_CLKRT);
|
|
}
|
|
|
|
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;
|
|
|
|
clk_enable();
|
|
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_CAP_4_BIT_DATA;
|
|
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,
|
|
};
|
|
device_platform_driver(pxamci_driver);
|