9
0
Fork 0
barebox/drivers/mci/pxamci.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);