blob: d74148a0cfb45f77e75004556ae0912b5c94a9c8 [file] [log] [blame]
/*
* (C) Copyright 2012 Samsung Electronics Co. Ltd
* Copyright 2013 Google Inc. All rights reserved.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <assert.h>
#include <endian.h>
#include <libpayload.h>
#include <stdint.h>
#include "base/init_funcs.h"
#include "config.h"
#include "arch/cache.h"
#include "drivers/storage/exynos_mshc.h"
#include "drivers/storage/mmc.h"
static void setbits32(uint32_t *data, uint32_t bits)
{
writel(readl(data) | bits, data);
}
static void clrbits32(uint32_t *data, uint32_t bits)
{
writel(readl(data) & ~bits, data);
}
static inline S5pMshci *s5p_get_base_mshci(int dev_index)
{
switch(dev_index) {
case 0:
return ((S5pMshci *)
CONFIG_DRIVER_STORAGE_MSHC_S5P_MMC0_ADDRESS);
case 1:
return ((S5pMshci *)
CONFIG_DRIVER_STORAGE_MSHC_S5P_MMC1_ADDRESS);
}
mmc_error("Unknown device index (%d): Check your Kconfig settings for "
"DRIVER_MSHC_S5P_DEVICES.\n", dev_index);
assert(dev_index < CONFIG_DRIVER_STORAGE_MSHC_S5P_DEVICES);
return NULL;
}
static int s5p_mshci_is_card_present(S5pMshci *reg)
{
return !readl(&reg->cdetect);
}
static int mshci_setbits(MshciHost *host, unsigned int bits)
{
setbits32(&host->reg->ctrl, bits);
if (mmc_busy_wait_io(&host->reg->ctrl, NULL, bits,
S5P_MSHC_TIMEOUT_MS)) {
mmc_error("Set bits failed\n");
return -1;
}
return 0;
}
static int mshci_reset_all(MshciHost *host)
{
/*
* Before we reset ciu check the DATA0 line. If it is low and
* we resets the ciu then we might see some errors.
*/
if (mmc_busy_wait_io(&host->reg->status, NULL, DATA_BUSY,
S5P_MSHC_TIMEOUT_MS)) {
mmc_error("Controller did not release data0 before ciu reset\n");
return -1;
}
if (mshci_setbits(host, CTRL_RESET)) {
mmc_error("Fail to reset card.\n");
return -1;
}
if (mshci_setbits(host, FIFO_RESET)) {
mmc_error("Fail to reset fifo.\n");
return -1;
}
if (mshci_setbits(host, DMA_RESET)) {
mmc_error("Fail to reset dma.\n");
return -1;
}
return 0;
}
static void mshci_set_mdma_desc(uint8_t *desc_vir, uint8_t *desc_phy,
unsigned int des0, unsigned int des1,
unsigned int des2)
{
MshciIdmac *desc = (MshciIdmac *)desc_vir;
desc->des0 = des0;
desc->des1 = des1;
desc->des2 = des2;
desc->des3 = (unsigned int)desc_phy + sizeof(MshciIdmac);
}
static int mshci_prepare_data(MshciHost *host, MmcData *data)
{
unsigned int i;
unsigned int data_cnt;
unsigned int des_flag;
unsigned int blksz;
static MshciIdmac idmac_desc[0x10000];
/* TODO(alim.akhtar@samsung.com): do we really need this big array? */
MshciIdmac *pdesc_dmac;
if (mshci_setbits(host, FIFO_RESET)) {
mmc_error("Fail to reset FIFO\n");
return -1;
}
pdesc_dmac = idmac_desc;
blksz = data->blocksize;
data_cnt = data->blocks;
for (i = 0;; i++) {
des_flag = (MSHCI_IDMAC_OWN | MSHCI_IDMAC_CH);
des_flag |= (i == 0) ? MSHCI_IDMAC_FS : 0;
if (data_cnt <= 8) {
des_flag |= MSHCI_IDMAC_LD;
mshci_set_mdma_desc((uint8_t *)pdesc_dmac,
(uint8_t *)virt_to_phys(pdesc_dmac),
des_flag, blksz * data_cnt,
(unsigned int)(virt_to_phys(data->dest)) +
(unsigned int)(i * 0x1000));
break;
}
/* max transfer size is 4KB per descriptor */
mshci_set_mdma_desc((uint8_t *)pdesc_dmac,
(uint8_t *)virt_to_phys(pdesc_dmac),
des_flag, blksz * 8,
virt_to_phys(data->dest) +
(unsigned int)(i * 0x1000));
data_cnt -= 8;
pdesc_dmac++;
}
uint32_t data_start, data_len;
data_start = (uintptr_t)idmac_desc;
data_len = (uintptr_t)pdesc_dmac - (uintptr_t)idmac_desc + DMA_MINALIGN;
dcache_clean_invalidate_by_mva(data_start, data_len);
data_start = (uint32_t)data->dest;
data_len = (uint32_t)(data->blocks * data->blocksize);
dcache_clean_invalidate_by_mva(data_start, data_len);
writel((unsigned int)virt_to_phys(idmac_desc), &host->reg->dbaddr);
/* enable the Internal DMA Controller */
setbits32(&host->reg->ctrl, ENABLE_IDMAC | DMA_ENABLE);
setbits32(&host->reg->bmod, BMOD_IDMAC_ENABLE | BMOD_IDMAC_FB);
writel(data->blocksize, &host->reg->blksiz);
writel(data->blocksize * data->blocks, &host->reg->bytcnt);
return 0;
}
static int mshci_set_transfer_mode(MshciHost *host, MmcData *data)
{
int mode = CMD_DATA_EXP_BIT;
if (data->blocks > 1)
mode |= CMD_SENT_AUTO_STOP_BIT;
if (data->flags & MMC_DATA_WRITE)
mode |= CMD_RW_BIT;
return mode;
}
static int s5p_mshci_send_command(MmcDevice *mmc, MmcCommand *cmd,
MmcData *data)
{
MshciHost *host = (MshciHost *)mmc->host;
int flags = 0, i;
unsigned int mask;
/*
* If auto stop is enabled in the control register, ignore STOP
* command, as controller will internally send a STOP command after
* every block read/write
*/
if ((readl(&host->reg->ctrl) & SEND_AS_CCSD) &&
(cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION))
return 0;
/*
* We shouldn't wait for data inihibit for stop commands, even
* though they might use busy signaling
*/
if (mmc_busy_wait_io(&host->reg->status, NULL, DATA_BUSY,
S5P_MSHC_COMMAND_TIMEOUT)) {
mmc_error("timeout on data busy\n");
return -1;
}
if ((readl(&host->reg->rintsts) & (INTMSK_CDONE | INTMSK_ACD)) == 0) {
uint32_t rintsts = readl(&host->reg->rintsts);
if (rintsts) {
mmc_debug("there're pending interrupts %#x\n", rintsts);
}
}
/* It clears all pending interrupts before sending a command*/
writel(INTMSK_ALL, &host->reg->rintsts);
if (data) {
if (mshci_prepare_data(host, data)) {
mmc_error("fail to prepare data\n");
return -1;
}
}
writel(cmd->cmdarg, &host->reg->cmdarg);
if (data)
flags = mshci_set_transfer_mode(host, data);
if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) {
/* this is out of SD spec */
mmc_error("wrong response type or response busy for cmd %d\n",
cmd->cmdidx);
return -1;
}
if (cmd->resp_type & MMC_RSP_PRESENT) {
flags |= CMD_RESP_EXP_BIT;
if (cmd->resp_type & MMC_RSP_136)
flags |= CMD_RESP_LENGTH_BIT;
}
if (cmd->resp_type & MMC_RSP_CRC)
flags |= CMD_CHECK_CRC_BIT;
flags |= (cmd->cmdidx | CMD_STRT_BIT | CMD_USE_HOLD_REG |
CMD_WAIT_PRV_DAT_BIT);
mask = readl(&host->reg->cmd);
if (mask & CMD_STRT_BIT) {
mmc_error("cmd busy, current cmd: %d", cmd->cmdidx);
return -1;
}
writel(flags, &host->reg->cmd);
/* wait for command complete by busy waiting. */
for (i = 0; i < S5P_MSHC_COMMAND_TIMEOUT; i++) {
mask = readl(&host->reg->rintsts);
if (mask & INTMSK_CDONE) {
if (!data)
writel(mask, &host->reg->rintsts);
break;
}
}
/* timeout for command complete. */
if (S5P_MSHC_COMMAND_TIMEOUT == i) {
mmc_error("timeout waiting for status update\n");
return MMC_TIMEOUT;
}
if (mask & INTMSK_RTO)
return MMC_TIMEOUT;
else if (mask & INTMSK_RE) {
mmc_error("response mmc_error: %#x cmd: %d\n", mask, cmd->cmdidx);
return -1;
}
if (cmd->resp_type & MMC_RSP_PRESENT) {
if (cmd->resp_type & MMC_RSP_136) {
/* CRC is stripped so we need to do some shifting. */
cmd->response[0] = readl(&host->reg->resp3);
cmd->response[1] = readl(&host->reg->resp2);
cmd->response[2] = readl(&host->reg->resp1);
cmd->response[3] = readl(&host->reg->resp0);
} else {
cmd->response[0] = readl(&host->reg->resp0);
mmc_debug("\tcmd->response[0]: %#08x\n",
cmd->response[0]);
}
}
if (data) {
if (mmc_busy_wait_io_until(&host->reg->rintsts,
&mask,
DATA_ERR | DATA_TOUT | INTMSK_DTO,
S5P_MSHC_COMMAND_TIMEOUT)) {
mmc_debug("timeout on data error\n");
return -1;
}
mmc_debug("%s: writel(mask, rintsts)\n", __func__);
writel(mask, &host->reg->rintsts);
if (data->flags & MMC_DATA_READ) {
uint32_t data_base, data_len;
data_base = (uint32_t)data->dest;
data_len = data->blocks * data->blocksize;
dcache_clean_invalidate_by_mva(data_base, data_len);
}
mmc_debug("%s: clrbits32(ctrl, +DMA, +IDMAC)\n", __func__);
/* make sure disable IDMAC and IDMAC_Interrupts */
clrbits32(&host->reg->ctrl, DMA_ENABLE | ENABLE_IDMAC);
if (mask & (DATA_ERR | DATA_TOUT)) {
mmc_error("error during transfer: %#x\n", mask);
/* mask all interrupt source of IDMAC */
writel(0, &host->reg->idinten);
return -1;
} else if (mask & INTMSK_DTO) {
mmc_debug("mshci dma interrupt end\n");
} else {
mmc_error("unexpected condition %#x\n", mask);
return -1;
}
clrbits32(&host->reg->ctrl, DMA_ENABLE | ENABLE_IDMAC);
/* mask all interrupt source of IDMAC */
writel(0, &host->reg->idinten);
}
/* TODO(alim.akhtar@samsung.com): check why we need this delay */
udelay(100);
return 0;
}
static int mshci_clock_onoff(MshciHost *host, int val)
{
if (val)
writel(CLK_ENABLE, &host->reg->clkena);
else
writel(CLK_DISABLE, &host->reg->clkena);
writel(0, &host->reg->cmd);
writel(CMD_ONLY_CLK, &host->reg->cmd);
/* wait till command is taken by CIU, when this bit is set
* host should not attempted to write to any command registers.
*/
if (mmc_busy_wait_io(&host->reg->cmd, NULL, CMD_STRT_BIT,
S5P_MSHC_COMMAND_TIMEOUT)) {
mmc_error("Clock %s has failed.\n ", val ? "ON" : "OFF");
return -1;
}
return 0;
}
static int mshci_change_clock(MshciHost *host, uint32_t clock)
{
int div;
uint32_t sclk_mshc;
if (clock == host->clock)
return 0;
/* If Input clock is higher than maximum mshc clock */
if (clock > MAX_MSHCI_CLOCK) {
mmc_debug("Input clock is too high\n");
clock = MAX_MSHCI_CLOCK;
}
/* disable the clock before changing it */
if (mshci_clock_onoff(host, CLK_DISABLE)) {
mmc_error("failed to DISABLE clock\n");
return -1;
}
/* Calculate clock division */
sclk_mshc = CONFIG_DRIVER_STORAGE_MSHC_S5P_CLOCK_RATE;
for (div = 1 ; div <= 0xff; div++) {
if (((sclk_mshc / 4) / (2 * div)) <= clock) {
writel(div, &host->reg->clkdiv);
break;
}
}
writel(div, &host->reg->clkdiv);
writel(0, &host->reg->cmd);
writel(CMD_ONLY_CLK, &host->reg->cmd);
/*
* wait till command is taken by CIU, when this bit is set
* host should not attempted to write to any command registers.
*/
if (mmc_busy_wait_io(&host->reg->cmd, NULL, CMD_STRT_BIT,
S5P_MSHC_COMMAND_TIMEOUT)) {
mmc_error("Changing clock has timed out.\n");
return -1;
}
clrbits32(&host->reg->cmd, CMD_SEND_CLK_ONLY);
if (mshci_clock_onoff(host, CLK_ENABLE)) {
mmc_error("failed to ENABLE clock\n");
return -1;
}
host->clock = clock;
return 0;
}
static void s5p_mshci_set_ios(MmcDevice *mmc)
{
MshciHost *host = (MshciHost *)mmc->host;
mmc_debug("bus_width: %x, clock: %d\n", mmc->bus_width, mmc->clock);
if (mmc->clock > 0 && mshci_change_clock(host, mmc->clock))
mmc_debug("mshci_change_clock failed\n");
if (mmc->bus_width == 8)
writel(PORT0_CARD_WIDTH8, &host->reg->ctype);
else if (mmc->bus_width == 4)
writel(PORT0_CARD_WIDTH4, &host->reg->ctype);
else
writel(PORT0_CARD_WIDTH1, &host->reg->ctype);
/* TODO(hungte) Put these into macro or config variables. */
if (host->dev_index == 0)
writel(0x03030001, &host->reg->clksel);
else if (host->dev_index == 1)
writel(0x03020001, &host->reg->clksel);
}
static void mshci_fifo_init(MshciHost *host)
{
int fifo_val, fifo_depth, fifo_threshold;
fifo_val = readl(&host->reg->fifoth);
fifo_depth = 0x80;
fifo_threshold = fifo_depth / 2;
fifo_val &= ~(RX_WMARK | TX_WMARK | MSIZE_MASK);
fifo_val |= (fifo_threshold | (fifo_threshold << 16) | MSIZE_8);
writel(fifo_val, &host->reg->fifoth);
}
static int mshci_is_card_present(MmcDevice *mmc)
{
BlockDev *block_dev = (BlockDev *)mmc->block_dev;
MshciHost *host = (MshciHost *)mmc->host;
assert(block_dev);
mmc_debug("%s\n", __func__);
if (!block_dev->removable)
return 1;
return s5p_mshci_is_card_present(host->reg);
}
static int mshci_init(MshciHost *host)
{
writel(POWER_ENABLE, &host->reg->pwren);
if (mshci_reset_all(host)) {
mmc_error("mshci_reset_all() failed\n");
return -1;
}
mshci_fifo_init(host);
/* clear all pending interrupts */
writel(INTMSK_ALL, &host->reg->rintsts);
/* interrupts are not used, disable all */
writel(0, &host->reg->intmask);
return 0;
}
static int s5p_mshci_init(MmcDevice *mmc)
{
MshciHost *host = (MshciHost *)mmc->host;
unsigned int ier;
if (mshci_init(host)) {
mmc_error("mshci_init() failed\n");
return -1;
}
/* enumerate at 400KHz */
if (mshci_change_clock(host, 400000)) {
mmc_error("mshci_change_clock failed\n");
return -1;
}
/* set auto stop command */
ier = readl(&host->reg->ctrl);
ier |= SEND_AS_CCSD;
writel(ier, &host->reg->ctrl);
/* set 1bit card mode */
writel(PORT0_CARD_WIDTH1, &host->reg->ctype);
writel(0xfffff, &host->reg->debnce);
/* set bus mode register for IDMAC */
writel(BMOD_IDMAC_RESET, &host->reg->bmod);
writel(0x0, &host->reg->idinten);
/* set the max timeout for data and response */
writel(TMOUT_MAX, &host->reg->tmout);
return 0;
}
static int s5p_mshc_initialize(int dev_index, S5pMshci *reg,
MmcDevice **refresh_list)
{
int bus_width = 1;
const int name_size = 16;
char *name;
MshciHost *host = (MshciHost *)malloc(sizeof(MshciHost));
MmcDevice *mmc_dev = (MmcDevice *)malloc(sizeof(MmcDevice));
BlockDev *mshc_dev = (BlockDev *)malloc(sizeof(BlockDev));
mmc_debug("%s: init device #%d using reg %p\n", __func__, dev_index, reg);
memset(host, 0, sizeof(*host));
memset(mmc_dev, 0, sizeof(*mmc_dev));
memset(mshc_dev, 0, sizeof(*mshc_dev));
host->clock = 0;
host->reg = reg;
host->dev_index = dev_index;
mmc_dev->host = host;
mmc_dev->send_cmd = s5p_mshci_send_command;
mmc_dev->set_ios = s5p_mshci_set_ios;
mmc_dev->init = s5p_mshci_init;
mmc_dev->is_card_present = mshci_is_card_present;
mmc_dev->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc_dev->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS | MMC_MODE_HC;
mmc_dev->f_min = MIN_MSHCI_CLOCK;
mmc_dev->f_max = MAX_MSHCI_CLOCK;
name = malloc(name_size);
snprintf(name, name_size, "s5p_mshc%d", dev_index);
mshc_dev->name = name;
mshc_dev->block_size = reg->blksiz;
/* TODO(hungte) Get configuration data instead of hard-coded. */
if (dev_index == 0) {
bus_width = 8;
mshc_dev->removable = 0;
mmc_dev->host_caps |= MMC_MODE_8BIT;
} else {
bus_width = 4;
mshc_dev->removable = 1;
mmc_dev->host_caps |= MMC_MODE_4BIT;
}
mmc_dev->bus_width = bus_width;
if (mshc_dev->removable) {
block_mmc_register(mshc_dev, mmc_dev, refresh_list);
mmc_debug("%s: removable registered (init on refresh).\n", name);
} else {
block_mmc_register(mshc_dev, mmc_dev, NULL);
if (mmc_init(mmc_dev)) {
mmc_error("%s: init failed: %s.\n", __func__, name);
free(name);
free(mmc_dev);
free(mshc_dev);
free(host);
return -1;
}
list_insert_after(&mshc_dev->list_node, &fixed_block_devices);
mmc_debug("%s: init success (reset OK): %s\n", __func__, name);
}
return 0;
}
////////////////////////////////////////////////////////////////////////////
// Depthcharge block device I/O
static void s5p_mshc_ctrlr_init(BlockDevCtrlr *ctrlr)
{
int i;
MmcDevice **refresh_list = (MmcDevice **)(&ctrlr->ctrlr_data);
mmc_debug("%s started.\n", __func__);
for (i = 0; i < CONFIG_DRIVER_STORAGE_MSHC_S5P_DEVICES; i++) {
s5p_mshc_initialize(i, s5p_get_base_mshci(i), refresh_list);
}
}
int s5p_mshc_ctrlr_register(void)
{
static BlockDevCtrlr s5p_mshc =
{
&s5p_mshc_ctrlr_init,
&block_mmc_ctrlr_refresh,
NULL,
};
list_insert_after(&s5p_mshc.list_node, &block_dev_controllers);
mmc_debug("%s: done.\n", __func__);
return 0;
}
INIT_FUNC(s5p_mshc_ctrlr_register);