mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-19 00:18:51 +09:00
![Tom Rini](/assets/img/avatar_default.png)
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
392 lines
10 KiB
C
392 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Davinci MMC Controller Driver
|
|
*
|
|
* Copyright (C) 2010 Texas Instruments Incorporated
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <common.h>
|
|
#include <command.h>
|
|
#include <errno.h>
|
|
#include <mmc.h>
|
|
#include <part.h>
|
|
#include <malloc.h>
|
|
#include <asm/io.h>
|
|
#include <asm/arch/sdmmc_defs.h>
|
|
|
|
#define DAVINCI_MAX_BLOCKS (32)
|
|
#define WATCHDOG_COUNT (100000)
|
|
|
|
#define get_val(addr) REG(addr)
|
|
#define set_val(addr, val) REG(addr) = (val)
|
|
#define set_bit(addr, val) set_val((addr), (get_val(addr) | (val)))
|
|
#define clear_bit(addr, val) set_val((addr), (get_val(addr) & ~(val)))
|
|
|
|
/* Set davinci clock prescalar value based on the required clock in HZ */
|
|
static void dmmc_set_clock(struct mmc *mmc, uint clock)
|
|
{
|
|
struct davinci_mmc *host = mmc->priv;
|
|
struct davinci_mmc_regs *regs = host->reg_base;
|
|
uint clkrt, sysclk2, act_clock;
|
|
|
|
if (clock < mmc->cfg->f_min)
|
|
clock = mmc->cfg->f_min;
|
|
if (clock > mmc->cfg->f_max)
|
|
clock = mmc->cfg->f_max;
|
|
|
|
set_val(®s->mmcclk, 0);
|
|
sysclk2 = host->input_clk;
|
|
clkrt = (sysclk2 / (2 * clock)) - 1;
|
|
|
|
/* Calculate the actual clock for the divider used */
|
|
act_clock = (sysclk2 / (2 * (clkrt + 1)));
|
|
|
|
/* Adjust divider if actual clock exceeds the required clock */
|
|
if (act_clock > clock)
|
|
clkrt++;
|
|
|
|
/* check clock divider boundary and correct it */
|
|
if (clkrt > 0xFF)
|
|
clkrt = 0xFF;
|
|
|
|
set_val(®s->mmcclk, (clkrt | MMCCLK_CLKEN));
|
|
}
|
|
|
|
/* Status bit wait loop for MMCST1 */
|
|
static int
|
|
dmmc_wait_fifo_status(volatile struct davinci_mmc_regs *regs, uint status)
|
|
{
|
|
uint wdog = WATCHDOG_COUNT;
|
|
|
|
while (--wdog && ((get_val(®s->mmcst1) & status) != status))
|
|
udelay(10);
|
|
|
|
if (!(get_val(®s->mmcctl) & MMCCTL_WIDTH_4_BIT))
|
|
udelay(100);
|
|
|
|
if (wdog == 0)
|
|
return -ECOMM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Busy bit wait loop for MMCST1 */
|
|
static int dmmc_busy_wait(volatile struct davinci_mmc_regs *regs)
|
|
{
|
|
uint wdog = WATCHDOG_COUNT;
|
|
|
|
while (--wdog && (get_val(®s->mmcst1) & MMCST1_BUSY))
|
|
udelay(10);
|
|
|
|
if (wdog == 0)
|
|
return -ECOMM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Status bit wait loop for MMCST0 - Checks for error bits as well */
|
|
static int dmmc_check_status(volatile struct davinci_mmc_regs *regs,
|
|
uint *cur_st, uint st_ready, uint st_error)
|
|
{
|
|
uint wdog = WATCHDOG_COUNT;
|
|
uint mmcstatus = *cur_st;
|
|
|
|
while (wdog--) {
|
|
if (mmcstatus & st_ready) {
|
|
*cur_st = mmcstatus;
|
|
mmcstatus = get_val(®s->mmcst1);
|
|
return 0;
|
|
} else if (mmcstatus & st_error) {
|
|
if (mmcstatus & MMCST0_TOUTRS)
|
|
return -ETIMEDOUT;
|
|
printf("[ ST0 ERROR %x]\n", mmcstatus);
|
|
/*
|
|
* Ignore CRC errors as some MMC cards fail to
|
|
* initialize on DM365-EVM on the SD1 slot
|
|
*/
|
|
if (mmcstatus & MMCST0_CRCRS)
|
|
return 0;
|
|
return -ECOMM;
|
|
}
|
|
udelay(10);
|
|
|
|
mmcstatus = get_val(®s->mmcst0);
|
|
}
|
|
|
|
printf("Status %x Timeout ST0:%x ST1:%x\n", st_ready, mmcstatus,
|
|
get_val(®s->mmcst1));
|
|
return -ECOMM;
|
|
}
|
|
|
|
/*
|
|
* Sends a command out on the bus. Takes the mmc pointer,
|
|
* a command pointer, and an optional data pointer.
|
|
*/
|
|
static int
|
|
dmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
|
|
{
|
|
struct davinci_mmc *host = mmc->priv;
|
|
volatile struct davinci_mmc_regs *regs = host->reg_base;
|
|
uint mmcstatus, status_rdy, status_err;
|
|
uint i, cmddata, bytes_left = 0;
|
|
int fifo_words, fifo_bytes, err;
|
|
char *data_buf = NULL;
|
|
|
|
/* Clear status registers */
|
|
mmcstatus = get_val(®s->mmcst0);
|
|
fifo_words = (host->version == MMC_CTLR_VERSION_2) ? 16 : 8;
|
|
fifo_bytes = fifo_words << 2;
|
|
|
|
/* Wait for any previous busy signal to be cleared */
|
|
dmmc_busy_wait(regs);
|
|
|
|
cmddata = cmd->cmdidx;
|
|
cmddata |= MMCCMD_PPLEN;
|
|
|
|
/* Send init clock for CMD0 */
|
|
if (cmd->cmdidx == MMC_CMD_GO_IDLE_STATE)
|
|
cmddata |= MMCCMD_INITCK;
|
|
|
|
switch (cmd->resp_type) {
|
|
case MMC_RSP_R1b:
|
|
cmddata |= MMCCMD_BSYEXP;
|
|
/* Fall-through */
|
|
case MMC_RSP_R1: /* R1, R1b, R5, R6, R7 */
|
|
cmddata |= MMCCMD_RSPFMT_R1567;
|
|
break;
|
|
case MMC_RSP_R2:
|
|
cmddata |= MMCCMD_RSPFMT_R2;
|
|
break;
|
|
case MMC_RSP_R3: /* R3, R4 */
|
|
cmddata |= MMCCMD_RSPFMT_R3;
|
|
break;
|
|
}
|
|
|
|
set_val(®s->mmcim, 0);
|
|
|
|
if (data) {
|
|
/* clear previous data transfer if any and set new one */
|
|
bytes_left = (data->blocksize * data->blocks);
|
|
|
|
/* Reset FIFO - Always use 32 byte fifo threshold */
|
|
set_val(®s->mmcfifoctl,
|
|
(MMCFIFOCTL_FIFOLEV | MMCFIFOCTL_FIFORST));
|
|
|
|
if (host->version == MMC_CTLR_VERSION_2)
|
|
cmddata |= MMCCMD_DMATRIG;
|
|
|
|
cmddata |= MMCCMD_WDATX;
|
|
if (data->flags == MMC_DATA_READ) {
|
|
set_val(®s->mmcfifoctl, MMCFIFOCTL_FIFOLEV);
|
|
} else if (data->flags == MMC_DATA_WRITE) {
|
|
set_val(®s->mmcfifoctl,
|
|
(MMCFIFOCTL_FIFOLEV |
|
|
MMCFIFOCTL_FIFODIR));
|
|
cmddata |= MMCCMD_DTRW;
|
|
}
|
|
|
|
set_val(®s->mmctod, 0xFFFF);
|
|
set_val(®s->mmcnblk, (data->blocks & MMCNBLK_NBLK_MASK));
|
|
set_val(®s->mmcblen, (data->blocksize & MMCBLEN_BLEN_MASK));
|
|
|
|
if (data->flags == MMC_DATA_WRITE) {
|
|
uint val;
|
|
data_buf = (char *)data->src;
|
|
/* For write, fill FIFO with data before issue of CMD */
|
|
for (i = 0; (i < fifo_words) && bytes_left; i++) {
|
|
memcpy((char *)&val, data_buf, 4);
|
|
set_val(®s->mmcdxr, val);
|
|
data_buf += 4;
|
|
bytes_left -= 4;
|
|
}
|
|
}
|
|
} else {
|
|
set_val(®s->mmcblen, 0);
|
|
set_val(®s->mmcnblk, 0);
|
|
}
|
|
|
|
set_val(®s->mmctor, 0x1FFF);
|
|
|
|
/* Send the command */
|
|
set_val(®s->mmcarghl, cmd->cmdarg);
|
|
set_val(®s->mmccmd, cmddata);
|
|
|
|
status_rdy = MMCST0_RSPDNE;
|
|
status_err = (MMCST0_TOUTRS | MMCST0_TOUTRD |
|
|
MMCST0_CRCWR | MMCST0_CRCRD);
|
|
if (cmd->resp_type & MMC_RSP_CRC)
|
|
status_err |= MMCST0_CRCRS;
|
|
|
|
mmcstatus = get_val(®s->mmcst0);
|
|
err = dmmc_check_status(regs, &mmcstatus, status_rdy, status_err);
|
|
if (err)
|
|
return err;
|
|
|
|
/* For R1b wait for busy done */
|
|
if (cmd->resp_type == MMC_RSP_R1b)
|
|
dmmc_busy_wait(regs);
|
|
|
|
/* Collect response from controller for specific commands */
|
|
if (mmcstatus & MMCST0_RSPDNE) {
|
|
/* Copy the response to the response buffer */
|
|
if (cmd->resp_type & MMC_RSP_136) {
|
|
cmd->response[0] = get_val(®s->mmcrsp67);
|
|
cmd->response[1] = get_val(®s->mmcrsp45);
|
|
cmd->response[2] = get_val(®s->mmcrsp23);
|
|
cmd->response[3] = get_val(®s->mmcrsp01);
|
|
} else if (cmd->resp_type & MMC_RSP_PRESENT) {
|
|
cmd->response[0] = get_val(®s->mmcrsp67);
|
|
}
|
|
}
|
|
|
|
if (data == NULL)
|
|
return 0;
|
|
|
|
if (data->flags == MMC_DATA_READ) {
|
|
/* check for DATDNE along with DRRDY as the controller might
|
|
* set the DATDNE without DRRDY for smaller transfers with
|
|
* less than FIFO threshold bytes
|
|
*/
|
|
status_rdy = MMCST0_DRRDY | MMCST0_DATDNE;
|
|
status_err = MMCST0_TOUTRD | MMCST0_CRCRD;
|
|
data_buf = data->dest;
|
|
} else {
|
|
status_rdy = MMCST0_DXRDY | MMCST0_DATDNE;
|
|
status_err = MMCST0_CRCWR;
|
|
}
|
|
|
|
/* Wait until all of the blocks are transferred */
|
|
while (bytes_left) {
|
|
err = dmmc_check_status(regs, &mmcstatus, status_rdy,
|
|
status_err);
|
|
if (err)
|
|
return err;
|
|
|
|
if (data->flags == MMC_DATA_READ) {
|
|
/*
|
|
* MMC controller sets the Data receive ready bit
|
|
* (DRRDY) in MMCST0 even before the entire FIFO is
|
|
* full. This results in erratic behavior if we start
|
|
* reading the FIFO soon after DRRDY. Wait for the
|
|
* FIFO full bit in MMCST1 for proper FIFO clearing.
|
|
*/
|
|
if (bytes_left > fifo_bytes)
|
|
dmmc_wait_fifo_status(regs, 0x4a);
|
|
else if (bytes_left == fifo_bytes) {
|
|
dmmc_wait_fifo_status(regs, 0x40);
|
|
if (cmd->cmdidx == MMC_CMD_SEND_EXT_CSD)
|
|
udelay(600);
|
|
}
|
|
|
|
for (i = 0; bytes_left && (i < fifo_words); i++) {
|
|
cmddata = get_val(®s->mmcdrr);
|
|
memcpy(data_buf, (char *)&cmddata, 4);
|
|
data_buf += 4;
|
|
bytes_left -= 4;
|
|
}
|
|
} else {
|
|
/*
|
|
* MMC controller sets the Data transmit ready bit
|
|
* (DXRDY) in MMCST0 even before the entire FIFO is
|
|
* empty. This results in erratic behavior if we start
|
|
* writing the FIFO soon after DXRDY. Wait for the
|
|
* FIFO empty bit in MMCST1 for proper FIFO clearing.
|
|
*/
|
|
dmmc_wait_fifo_status(regs, MMCST1_FIFOEMP);
|
|
for (i = 0; bytes_left && (i < fifo_words); i++) {
|
|
memcpy((char *)&cmddata, data_buf, 4);
|
|
set_val(®s->mmcdxr, cmddata);
|
|
data_buf += 4;
|
|
bytes_left -= 4;
|
|
}
|
|
dmmc_busy_wait(regs);
|
|
}
|
|
}
|
|
|
|
err = dmmc_check_status(regs, &mmcstatus, MMCST0_DATDNE, status_err);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Initialize Davinci MMC controller */
|
|
static int dmmc_init(struct mmc *mmc)
|
|
{
|
|
struct davinci_mmc *host = mmc->priv;
|
|
struct davinci_mmc_regs *regs = host->reg_base;
|
|
|
|
/* Clear status registers explicitly - soft reset doesn't clear it
|
|
* If Uboot is invoked from UBL with SDMMC Support, the status
|
|
* registers can have uncleared bits
|
|
*/
|
|
get_val(®s->mmcst0);
|
|
get_val(®s->mmcst1);
|
|
|
|
/* Hold software reset */
|
|
set_bit(®s->mmcctl, MMCCTL_DATRST);
|
|
set_bit(®s->mmcctl, MMCCTL_CMDRST);
|
|
udelay(10);
|
|
|
|
set_val(®s->mmcclk, 0x0);
|
|
set_val(®s->mmctor, 0x1FFF);
|
|
set_val(®s->mmctod, 0xFFFF);
|
|
|
|
/* Clear software reset */
|
|
clear_bit(®s->mmcctl, MMCCTL_DATRST);
|
|
clear_bit(®s->mmcctl, MMCCTL_CMDRST);
|
|
|
|
udelay(10);
|
|
|
|
/* Reset FIFO - Always use the maximum fifo threshold */
|
|
set_val(®s->mmcfifoctl, (MMCFIFOCTL_FIFOLEV | MMCFIFOCTL_FIFORST));
|
|
set_val(®s->mmcfifoctl, MMCFIFOCTL_FIFOLEV);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Set buswidth or clock as indicated by the MMC framework */
|
|
static int dmmc_set_ios(struct mmc *mmc)
|
|
{
|
|
struct davinci_mmc *host = mmc->priv;
|
|
struct davinci_mmc_regs *regs = host->reg_base;
|
|
|
|
/* Set the bus width */
|
|
if (mmc->bus_width == 4)
|
|
set_bit(®s->mmcctl, MMCCTL_WIDTH_4_BIT);
|
|
else
|
|
clear_bit(®s->mmcctl, MMCCTL_WIDTH_4_BIT);
|
|
|
|
/* Set clock speed */
|
|
if (mmc->clock)
|
|
dmmc_set_clock(mmc, mmc->clock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct mmc_ops dmmc_ops = {
|
|
.send_cmd = dmmc_send_cmd,
|
|
.set_ios = dmmc_set_ios,
|
|
.init = dmmc_init,
|
|
};
|
|
|
|
/* Called from board_mmc_init during startup. Can be called multiple times
|
|
* depending on the number of slots available on board and controller
|
|
*/
|
|
int davinci_mmc_init(bd_t *bis, struct davinci_mmc *host)
|
|
{
|
|
host->cfg.name = "davinci";
|
|
host->cfg.ops = &dmmc_ops;
|
|
host->cfg.f_min = 200000;
|
|
host->cfg.f_max = 25000000;
|
|
host->cfg.voltages = host->voltages;
|
|
host->cfg.host_caps = host->host_caps;
|
|
|
|
host->cfg.b_max = DAVINCI_MAX_BLOCKS;
|
|
|
|
mmc_create(&host->cfg, host);
|
|
|
|
return 0;
|
|
}
|