blob: 4e83712286b0ad87aaa8e112adf3aa8b918f75f1 [file] [log] [blame]
/*
* (C) Copyright 2012 SAMSUNG Electronics
* Jaehoon Chung <jh80.chung@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.
*
* 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 <libpayload.h>
#include <stdint.h>
#include "base/init_funcs.h"
#include "config.h"
#include "drivers/storage/dw_mmc.h"
#define DWMCI_CLKSEL 0x09C
#define DWMCI_SHIFT_0 0x0
#define DWMCI_SHIFT_1 0x1
#define DWMCI_SHIFT_2 0x2
#define DWMCI_SHIFT_3 0x3
#define DWMCI_SET_SAMPLE_CLK(x) (x)
#define DWMCI_SET_DRV_CLK(x) ((x) << 16)
#define DWMCI_SET_DIV_RATIO(x) ((x) << 24)
#define DWMMC_MAX_FREQ 52000000
#define DWMMC_MIN_FREQ 400000
#define DWMMC_MAX_DEVICES 2
typedef struct {
void *addr;
uint32_t sclk; /* sclk is setup by Coreboot. */
int bus_width;
int removable;
uint32_t timing[3];
} ExynosDwMmcConfig;
// TODO(hungte) Move these configuration to board-specific source file.
static const ExynosDwMmcConfig exynos_dwmmc_configs[DWMMC_MAX_DEVICES] = {
{(void *)0x12200000, 400000000, 8, 0, {1, 3, 3}, },
{(void *)0x12220000, 400000000, 4, 1, {1, 2, 3}, },
};
/*
* Function used as callback function to initialise the
* CLKSEL register for every mmc channel.
*/
static void exynos_dwmci_clksel(DwmciHost *host)
{
dwmci_writel(host, DWMCI_CLKSEL, host->clksel_val);
}
static int exynos_dwmci_is_card_present(MmcDevice *mmc)
{
DwmciHost *host = (DwmciHost *)mmc->host;
if (!mmc->block_dev->removable)
return 1;
/* CDETECT is Low-active. */
return !dwmci_readl(host, DWMCI_CDETECT);
}
int exynos_dwmmc_initialize(int index, const ExynosDwMmcConfig *config,
MmcDevice **refresh_list)
{
DwmciHost *host = NULL;
uint32_t clksel_val;
host = malloc(sizeof(*host));
if (!host) {
printf("dwmci_host malloc fail!\n");
return 1;
}
memset(host, 0, sizeof(*host));
host->name = "EXYNOS DWMMC";
host->dev_index = index;
host->ioaddr = config->addr;
host->buswidth = config->bus_width;
clksel_val = (DWMCI_SET_SAMPLE_CLK(config->timing[0]) |
DWMCI_SET_DRV_CLK(config->timing[1]) |
DWMCI_SET_DIV_RATIO(config->timing[2]));
host->clksel_val = clksel_val;
host->bus_hz = config->sclk;
host->clksel = exynos_dwmci_clksel;
/* Add the mmc channel to be registered with mmc core */
if (dw_mmc_register(host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ,
config->removable, refresh_list,
exynos_dwmci_is_card_present) != 0) {
mmc_debug("dwmmc%d registration failed\n", index);
return -1;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////
// Depthcharge block device I/O
static void exynos_dwmmc_ctrlr_init(BlockDevCtrlr *ctrlr)
{
int i;
MmcDevice **refresh_list = (MmcDevice **)(&ctrlr->ctrlr_data);
mmc_debug("%s started.\n", __func__);
assert(CONFIG_DRIVER_STORAGE_MMC_DW_EXYNOS_DEVICES <= DWMMC_MAX_DEVICES);
for (i = 0; i < CONFIG_DRIVER_STORAGE_MMC_DW_EXYNOS_DEVICES; i++) {
const ExynosDwMmcConfig *config = &exynos_dwmmc_configs[i];
exynos_dwmmc_initialize(i, config, refresh_list);
}
}
int exynos_dwmmc_ctrlr_register(void)
{
static BlockDevCtrlr exynos_dwmmc =
{
&exynos_dwmmc_ctrlr_init,
&block_mmc_ctrlr_refresh,
NULL,
};
list_insert_after(&exynos_dwmmc.list_node, &block_dev_controllers);
mmc_debug("%s: done.\n", __func__);
return 0;
}
INIT_FUNC(exynos_dwmmc_ctrlr_register);