blob: c1c84511bae634932197137f8f95ac0f97b149c3 [file] [log] [blame]
/*
* (C) Copyright 2012 SAMSUNG Electronics
* Jaehoon Chung <jh80.chung@samsung.com>
* Rajeshawari Shinde <rajeshwari.s@samsung.com>
* Copyright 2013 Google Inc. All rights reserved.
*
* 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.
*
*/
#include <assert.h>
#include <libpayload.h>
#include <stddef.h>
#include <stdint.h>
#include "drivers/storage/dw_mmc.h"
enum {
DwmmcMaxFreq = 52000000,
DwmmcMinFreq = 400000
};
enum {
PageSize = 4096
};
static int dwmci_wait_reset(DwmciHost *host, uint32_t value)
{
unsigned long timeout_ms = 10;
dwmci_writel(host, DWMCI_CTRL, value);
return !mmc_busy_wait_io(dwmci_get_ioaddr(host, DWMCI_CTRL), NULL,
DWMCI_RESET_ALL, timeout_ms);
}
static void dwmci_set_idma_desc(DwmciIdmac *idmac, uint32_t desc0,
uint32_t desc1, uint32_t desc2)
{
DwmciIdmac *desc = idmac;
desc->flags = desc0;
desc->cnt = desc1;
desc->addr = desc2;
desc->next_addr = (uintptr_t)desc + sizeof(DwmciIdmac);
}
static void dwmci_prepare_data(DwmciHost *host, MmcData *data)
{
unsigned long ctrl;
unsigned int i = 0, flags, cnt, blk_cnt;
void *data_start;
void const *start_addr;
size_t data_len;
ALLOC_CACHE_ALIGN_BUFFER(DwmciIdmac, cur_idmac, data->blocks);
blk_cnt = data->blocks;
dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET);
data_start = cur_idmac;
dwmci_writel(host, DWMCI_DBADDR, (uintptr_t)cur_idmac);
if (data->flags == MMC_DATA_READ)
start_addr = data->dest;
else
start_addr = data->src;
do {
flags = DWMCI_IDMAC_OWN | DWMCI_IDMAC_CH ;
flags |= (i == 0) ? DWMCI_IDMAC_FS : 0;
if (blk_cnt <= 8) {
flags |= DWMCI_IDMAC_LD;
cnt = data->blocksize * blk_cnt;
} else
cnt = data->blocksize * 8;
dwmci_set_idma_desc(cur_idmac, flags, cnt,
(uintptr_t)start_addr + (i * PageSize));
if(blk_cnt < 8)
break;
blk_cnt -= 8;
cur_idmac++;
i++;
} while(1);
data_len = (void *)cur_idmac - data_start;
dcache_clean_invalidate_by_mva(data_start, data_len + DMA_MINALIGN);
dcache_clean_invalidate_by_mva(start_addr,
(data->blocks * data->blocksize));
ctrl = dwmci_readl(host, DWMCI_CTRL);
ctrl |= DWMCI_IDMAC_EN | DWMCI_DMA_EN;
dwmci_writel(host, DWMCI_CTRL, ctrl);
ctrl = dwmci_readl(host, DWMCI_BMOD);
ctrl |= DWMCI_BMOD_IDMAC_FB | DWMCI_BMOD_IDMAC_EN;
dwmci_writel(host, DWMCI_BMOD, ctrl);
dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize);
dwmci_writel(host, DWMCI_BYTCNT, data->blocksize * data->blocks);
}
static int dwmci_set_transfer_mode(DwmciHost *host, MmcData *data)
{
unsigned long mode;
mode = DWMCI_CMD_DATA_EXP;
if (data->flags & MMC_DATA_WRITE)
mode |= DWMCI_CMD_RW;
return mode;
}
static int dwmci_send_cmd(MmcCtrlr *ctrlr, MmcCommand *cmd, MmcData *data)
{
DwmciHost *host = container_of(ctrlr, DwmciHost, mmc);
int flags = 0;
unsigned int busy_timeout_ms = 100, send_timeout_ms = 10;
uint32_t mask, ctrl;
if (mmc_busy_wait_io(dwmci_get_ioaddr(host, DWMCI_STATUS), NULL,
DWMCI_BUSY, busy_timeout_ms) != 0) {
printf("Timeout on data busy\n");
return MMC_TIMEOUT;
}
dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_ALL);
if (data)
dwmci_prepare_data(host, data);
dwmci_writel(host, DWMCI_CMDARG, cmd->cmdarg);
if (data)
flags = dwmci_set_transfer_mode(host, data);
if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY))
return -1;
if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION)
flags |= DWMCI_CMD_ABORT_STOP;
else
flags |= DWMCI_CMD_PRV_DAT_WAIT;
if (cmd->resp_type & MMC_RSP_PRESENT) {
flags |= DWMCI_CMD_RESP_EXP;
if (cmd->resp_type & MMC_RSP_136)
flags |= DWMCI_CMD_RESP_LENGTH;
}
if (cmd->resp_type & MMC_RSP_CRC)
flags |= DWMCI_CMD_CHECK_CRC;
flags |= (cmd->cmdidx | DWMCI_CMD_START | DWMCI_CMD_USE_HOLD_REG);
mmc_debug("Sending CMD%d\n", cmd->cmdidx);
dwmci_writel(host, DWMCI_CMD, flags);
mask = 0;
if (mmc_busy_wait_io_until(
dwmci_get_ioaddr(host, DWMCI_RINTSTS), &mask,
DWMCI_INTMSK_CDONE, send_timeout_ms) != 0) {
mmc_error("CMD%d timeout.\n", cmd->cmdidx);
return MMC_TIMEOUT;
}
if (!data)
dwmci_writel(host, DWMCI_RINTSTS, mask);
if (mask & DWMCI_INTMSK_RTO) {
mmc_debug("Response Timeout..\n");
return MMC_TIMEOUT;
} else if (mask & DWMCI_INTMSK_RE) {
mmc_debug("Response Error..\n");
return -1;
}
if (cmd->resp_type & MMC_RSP_PRESENT) {
if (cmd->resp_type & MMC_RSP_136) {
cmd->response[0] = dwmci_readl(host, DWMCI_RESP3);
cmd->response[1] = dwmci_readl(host, DWMCI_RESP2);
cmd->response[2] = dwmci_readl(host, DWMCI_RESP1);
cmd->response[3] = dwmci_readl(host, DWMCI_RESP0);
} else {
cmd->response[0] = dwmci_readl(host, DWMCI_RESP0);
}
}
if (data) {
uint32_t error_mask = (DWMCI_DATA_ERR | DWMCI_DATA_TOUT),
timeout_ms = 240000;
mask = 0;
if (mmc_busy_wait_io_until(
dwmci_get_ioaddr(host, DWMCI_RINTSTS),
&mask, error_mask | DWMCI_INTMSK_DTO,
timeout_ms) != 0) {
mmc_error("DATA timeout!\n");
return MMC_TIMEOUT;
}
if (mask & error_mask) {
mmc_error("DATA ERROR!\n");
return -1;
}
dwmci_writel(host, DWMCI_RINTSTS, mask);
ctrl = dwmci_readl(host, DWMCI_CTRL);
ctrl &= ~(DWMCI_DMA_EN);
dwmci_writel(host, DWMCI_CTRL, ctrl);
if (data->flags & MMC_DATA_READ) {
unsigned long data_len = data->blocks * data->blocksize;
/*
* TODO(hungte) The buffer is supposed to have padding
* to the closest cache line boundary, ex:
* data_len = ALIGN(data_end, dcache_get_line_size());
*/
dcache_invalidate_by_mva(data->dest, data_len);
}
}
return 0;
}
static void dwmci_set_clock(DwmciHost *host, uint32_t freq)
{
uint32_t div;
unsigned long sclk;
sclk = host->src_hz / (DWMCI_GET_DIV_RATIO(host->clksel_val) + 1);
div = (sclk + (2 * freq) - 1) / (2 * freq);
dwmci_writel(host, DWMCI_CLKDIV, div);
}
static int dwmci_setup_bus(DwmciHost *host, uint32_t freq)
{
int timeout_ms = 10;
if ((freq == host->clock) || (freq == 0))
return 0;
dwmci_writel(host, DWMCI_CLKENA, 0);
dwmci_writel(host, DWMCI_CLKSRC, 0);
host->set_clk(host, freq);
dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_PRV_DAT_WAIT |
DWMCI_CMD_UPD_CLK | DWMCI_CMD_START);
if (mmc_busy_wait_io(dwmci_get_ioaddr(host, DWMCI_CMD),
NULL, DWMCI_CMD_START, timeout_ms) != 0) {
mmc_error("%s: TIMEOUT error!!\n", __func__);
return -1;
}
dwmci_writel(host, DWMCI_CLKENA, DWMCI_CLKEN_ENABLE |
DWMCI_CLKEN_LOW_PWR);
dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_PRV_DAT_WAIT |
DWMCI_CMD_UPD_CLK | DWMCI_CMD_START);
if (mmc_busy_wait_io(dwmci_get_ioaddr(host, DWMCI_CMD),
NULL, DWMCI_CMD_START, timeout_ms) != 0) {
mmc_error("%s: TIMEOUT error!!\n", __func__);
return -1;
}
host->clock = freq;
return 0;
}
static void dwmci_set_ios(MmcCtrlr *ctrlr)
{
DwmciHost *host = container_of(ctrlr, DwmciHost, mmc);
uint32_t ctype;
mmc_debug("Buswidth = %d, clock: %d\n",
ctrlr->bus_width, ctrlr->bus_hz);
dwmci_setup_bus(host, ctrlr->bus_hz);
switch (ctrlr->bus_width) {
case 8:
ctype = DWMCI_CTYPE_8BIT;
break;
case 4:
ctype = DWMCI_CTYPE_4BIT;
break;
default:
ctype = DWMCI_CTYPE_1BIT;
break;
}
dwmci_writel(host, DWMCI_CTYPE, ctype);
dwmci_writel(host, DWMCI_CLKSEL, host->clksel_val);
}
static int dwmci_init(BlockDevCtrlrOps *me)
{
DwmciHost *host = container_of(me, DwmciHost, mmc.ctrlr.ops);
uint32_t fifo_size, fifoth_val;
dwmci_writel(host, EMMCP_MPSBEGIN0, 0);
dwmci_writel(host, EMMCP_SEND0, 0);
dwmci_writel(host, EMMCP_CTRL0,
MPSCTRL_SECURE_READ_BIT | MPSCTRL_SECURE_WRITE_BIT |
MPSCTRL_NON_SECURE_READ_BIT | MPSCTRL_NON_SECURE_WRITE_BIT |
MPSCTRL_VALID);
dwmci_writel(host, DWMCI_PWREN, 1);
if (!dwmci_wait_reset(host, DWMCI_RESET_ALL)) {
printf("%s[%d] Reset failed!!\n", __func__, __LINE__);
return -1;
}
/* Enumerate at 400KHz */
dwmci_setup_bus(host, host->mmc.f_min);
dwmci_writel(host, DWMCI_RINTSTS, 0xFFFFFFFF);
dwmci_writel(host, DWMCI_INTMASK, 0);
dwmci_writel(host, DWMCI_TMOUT, 0xFFFFFFFF);
dwmci_writel(host, DWMCI_IDINTEN, 0);
dwmci_writel(host, DWMCI_BMOD, 1);
fifo_size = dwmci_readl(host, DWMCI_FIFOTH);
fifo_size = ((fifo_size & RX_WMARK_MASK) >> RX_WMARK_SHIFT) + 1;
if (host->fifoth_val) {
fifoth_val = host->fifoth_val;
} else {
fifoth_val = MSIZE(0x2) | RX_WMARK(fifo_size / 2 - 1) |
TX_WMARK(fifo_size / 2);
host->fifoth_val = fifoth_val;
}
dwmci_writel(host, DWMCI_FIFOTH, fifoth_val);
dwmci_writel(host, DWMCI_CLKENA, 0);
dwmci_writel(host, DWMCI_CLKSRC, 0);
return 0;
}
static int dwmci_update(BlockDevCtrlrOps *me)
{
DwmciHost *host = container_of(me, DwmciHost, mmc.ctrlr.ops);
if (!host->initialized && dwmci_init(me))
return -1;
host->initialized = 1;
if (host->removable) {
int present = 0;
if (host->cd_gpio) //use gpio detect
present = gpio_get(host->cd_gpio);
else
present = !dwmci_readl(host, DWMCI_CDETECT);
if (present && !host->mmc.media) {
// A card is present and not set up yet. Get it ready.
if (mmc_setup_media(&host->mmc))
return -1;
host->mmc.media->dev.name = "removable dwmmc";
host->mmc.media->dev.removable = 1;
host->mmc.media->dev.ops.read = &block_mmc_read;
host->mmc.media->dev.ops.write = &block_mmc_write;
host->mmc.media->dev.ops.erase = &block_mmc_erase;
host->mmc.media->dev.ops.fill_write =
&block_mmc_fill_write;
host->mmc.media->dev.ops.new_stream =
&new_simple_stream;
list_insert_after(&host->mmc.media->dev.list_node,
&removable_block_devices);
} else if (!present && host->mmc.media) {
// A card was present but isn't any more. Get rid of it.
list_remove(&host->mmc.media->dev.list_node);
free(host->mmc.media);
host->mmc.media = NULL;
}
} else {
if (mmc_setup_media(&host->mmc))
return -1;
host->mmc.media->dev.name = "dwmmc";
host->mmc.media->dev.removable = 0;
host->mmc.media->dev.ops.read = &block_mmc_read;
host->mmc.media->dev.ops.write = &block_mmc_write;
host->mmc.media->dev.ops.erase = &block_mmc_erase;
host->mmc.media->dev.ops.fill_write =
&block_mmc_fill_write;
host->mmc.media->dev.ops.new_stream = &new_simple_stream;
list_insert_after(&host->mmc.media->dev.list_node,
&fixed_block_devices);
host->mmc.ctrlr.need_update = 0;
}
return 0;
}
DwmciHost *new_dwmci_host(uintptr_t ioaddr, uint32_t src_hz,
int bus_width, int removable,
GpioOps *card_detect, uint32_t clksel_val)
{
DwmciHost *ctrlr = xzalloc(sizeof(*ctrlr));
ctrlr->mmc.ctrlr.ops.update = &dwmci_update;
ctrlr->mmc.ctrlr.ops.is_bdev_owned = block_mmc_is_bdev_owned;
ctrlr->mmc.ctrlr.need_update = 1;
ctrlr->mmc.voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
ctrlr->mmc.f_min = DwmmcMinFreq;
ctrlr->mmc.f_max = DwmmcMaxFreq;
ctrlr->mmc.bus_width = bus_width;
ctrlr->mmc.bus_hz = ctrlr->mmc.f_min;
ctrlr->mmc.b_max = 65535; // Some controllers use 16-bit regs.
if (bus_width == 8) {
ctrlr->mmc.caps |= MMC_CAPS_8BIT;
ctrlr->mmc.caps &= ~MMC_CAPS_4BIT;
} else {
ctrlr->mmc.caps |= MMC_CAPS_4BIT;
ctrlr->mmc.caps &= ~MMC_CAPS_8BIT;
}
ctrlr->mmc.caps |= MMC_CAPS_HS | MMC_CAPS_HS_52MHz | MMC_CAPS_HC;
ctrlr->mmc.send_cmd = &dwmci_send_cmd;
ctrlr->mmc.set_ios = &dwmci_set_ios;
ctrlr->ioaddr = (void *)ioaddr;
ctrlr->src_hz = src_hz;
ctrlr->clksel_val = clksel_val;
ctrlr->removable = removable;
ctrlr->cd_gpio = card_detect;
ctrlr->set_clk = dwmci_set_clock;
return ctrlr;
}