avionic design with actual uboot and tooling
submodule of avionic design uboot bootloader and with included tools to get you started , read readme.md and readme-tk1-loader.md
This commit is contained in:
70
u-boot/drivers/mmc/Kconfig
Normal file
70
u-boot/drivers/mmc/Kconfig
Normal file
@@ -0,0 +1,70 @@
|
||||
menu "MMC Host controller Support"
|
||||
|
||||
config MMC
|
||||
bool "Enable MMC support"
|
||||
depends on ARCH_SUNXI || SANDBOX
|
||||
help
|
||||
TODO: Move all architectures to use this option
|
||||
|
||||
config DM_MMC
|
||||
bool "Enable MMC controllers using Driver Model"
|
||||
depends on DM
|
||||
help
|
||||
This enables the MultiMediaCard (MMC) uclass which suports MMC and
|
||||
Secure Digital I/O (SDIO) cards. Both removable (SD, micro-SD, etc.)
|
||||
and non-removable (e.g. eMMC chip) devices are supported. These
|
||||
appear as block devices in U-Boot and can support filesystems such
|
||||
as EXT4 and FAT.
|
||||
|
||||
config MSM_SDHCI
|
||||
bool "Qualcomm SDHCI controller"
|
||||
depends on DM_MMC
|
||||
help
|
||||
Enables support for SDHCI 2.0 controller present on some Qualcomm
|
||||
Snapdragon devices. This device is compatible with eMMC v4.5 and
|
||||
SD 3.0 specifications. Both SD and eMMC devices are supported.
|
||||
Card-detect gpios are not supported.
|
||||
|
||||
config ROCKCHIP_DWMMC
|
||||
bool "Rockchip SD/MMC controller support"
|
||||
depends on DM_MMC && OF_CONTROL
|
||||
help
|
||||
This enables support for the Rockchip SD/MMM controller, which is
|
||||
based on Designware IP. The device is compatible with at least
|
||||
SD 3.0, SDIO 3.0 and MMC 4.5 and supports common eMMC chips as well
|
||||
as removeable SD and micro-SD cards.
|
||||
|
||||
config SH_SDHI
|
||||
bool "SuperH/Renesas ARM SoCs on-chip SDHI host controller support"
|
||||
depends on RMOBILE
|
||||
help
|
||||
Support for the on-chip SDHI host controller on SuperH/Renesas ARM SoCs platform
|
||||
|
||||
config PIC32_SDHCI
|
||||
bool "Microchip PIC32 on-chip SDHCI support"
|
||||
depends on DM_MMC && MACH_PIC32
|
||||
help
|
||||
Support for Microchip PIC32 SDHCI controller.
|
||||
|
||||
config ZYNQ_SDHCI
|
||||
bool "Arasan SDHCI controller support"
|
||||
depends on DM_MMC && OF_CONTROL
|
||||
help
|
||||
Support for Arasan SDHCI host controller on Zynq/ZynqMP ARM SoCs platform
|
||||
|
||||
config MMC_UNIPHIER
|
||||
bool "UniPhier SD/MMC Host Controller support"
|
||||
depends on ARCH_UNIPHIER
|
||||
help
|
||||
This selects support for the SD/MMC Host Controller on UniPhier SoCs.
|
||||
|
||||
config SANDBOX_MMC
|
||||
bool "Sandbox MMC support"
|
||||
depends on MMC && SANDBOX
|
||||
help
|
||||
This select a dummy sandbox MMC driver. At present this does nothing
|
||||
other than allow sandbox to be build with MMC support. This
|
||||
improves build coverage for sandbox and makes it easier to detect
|
||||
MMC build errors with sandbox.
|
||||
|
||||
endmenu
|
||||
63
u-boot/drivers/mmc/Makefile
Normal file
63
u-boot/drivers/mmc/Makefile
Normal file
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# (C) Copyright 2006
|
||||
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
|
||||
ifdef CONFIG_DM_MMC
|
||||
obj-$(CONFIG_GENERIC_MMC) += mmc-uclass.o
|
||||
endif
|
||||
|
||||
ifndef CONFIG_BLK
|
||||
obj-$(CONFIG_GENERIC_MMC) += mmc_legacy.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_ARM_PL180_MMCI) += arm_pl180_mmci.o
|
||||
obj-$(CONFIG_ATMEL_SDHCI) += atmel_sdhci.o
|
||||
obj-$(CONFIG_BCM2835_SDHCI) += bcm2835_sdhci.o
|
||||
obj-$(CONFIG_BFIN_SDH) += bfin_sdh.o
|
||||
obj-$(CONFIG_DAVINCI_MMC) += davinci_mmc.o
|
||||
obj-$(CONFIG_DWMMC) += dw_mmc.o
|
||||
obj-$(CONFIG_EXYNOS_DWMMC) += exynos_dw_mmc.o
|
||||
obj-$(CONFIG_HIKEY_DWMMC) += hi6220_dw_mmc.o
|
||||
obj-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o
|
||||
obj-$(CONFIG_FTSDC010) += ftsdc010_mci.o
|
||||
obj-$(CONFIG_FTSDC021) += ftsdc021_sdhci.o
|
||||
obj-$(CONFIG_GENERIC_MMC) += mmc.o
|
||||
obj-$(CONFIG_GENERIC_ATMEL_MCI) += gen_atmel_mci.o
|
||||
obj-$(CONFIG_KONA_SDHCI) += kona_sdhci.o
|
||||
obj-$(CONFIG_MMC_SPI) += mmc_spi.o
|
||||
obj-$(CONFIG_MMC_SUNXI) += sunxi_mmc.o
|
||||
obj-$(CONFIG_MV_SDHCI) += mv_sdhci.o
|
||||
obj-$(CONFIG_MVEBU_MMC) += mvebu_mmc.o
|
||||
obj-$(CONFIG_MXC_MMC) += mxcmmc.o
|
||||
obj-$(CONFIG_MXS_MMC) += mxsmmc.o
|
||||
obj-$(CONFIG_OMAP_HSMMC) += omap_hsmmc.o
|
||||
obj-$(CONFIG_X86) += pci_mmc.o
|
||||
obj-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o
|
||||
obj-$(CONFIG_ROCKCHIP_DWMMC) += rockchip_dw_mmc.o
|
||||
obj-$(CONFIG_SUPPORT_EMMC_RPMB) += rpmb.o
|
||||
obj-$(CONFIG_S3C_SDI) += s3c_sdi.o
|
||||
obj-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o
|
||||
ifdef CONFIG_BLK
|
||||
ifdef CONFIG_GENERIC_MMC
|
||||
obj-$(CONFIG_SANDBOX) += sandbox_mmc.o
|
||||
endif
|
||||
endif
|
||||
obj-$(CONFIG_SDHCI) += sdhci.o
|
||||
obj-$(CONFIG_SH_MMCIF) += sh_mmcif.o
|
||||
obj-$(CONFIG_SH_SDHI) += sh_sdhi.o
|
||||
obj-$(CONFIG_SOCFPGA_DWMMC) += socfpga_dw_mmc.o
|
||||
obj-$(CONFIG_SPEAR_SDHCI) += spear_sdhci.o
|
||||
obj-$(CONFIG_TEGRA_MMC) += tegra_mmc.o
|
||||
obj-$(CONFIG_MMC_UNIPHIER) += uniphier-sd.o
|
||||
obj-$(CONFIG_ZYNQ_SDHCI) += zynq_sdhci.o
|
||||
|
||||
ifdef CONFIG_SPL_BUILD
|
||||
obj-$(CONFIG_SPL_MMC_BOOT) += fsl_esdhc_spl.o
|
||||
else
|
||||
obj-$(CONFIG_GENERIC_MMC) += mmc_write.o
|
||||
endif
|
||||
obj-$(CONFIG_PIC32_SDHCI) += pic32_sdhci.o
|
||||
obj-$(CONFIG_MSM_SDHCI) += msm_sdhci.o
|
||||
381
u-boot/drivers/mmc/arm_pl180_mmci.c
Normal file
381
u-boot/drivers/mmc/arm_pl180_mmci.c
Normal file
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* ARM PrimeCell MultiMedia Card Interface - PL180
|
||||
*
|
||||
* Copyright (C) ST-Ericsson SA 2010
|
||||
*
|
||||
* Author: Ulf Hansson <ulf.hansson@stericsson.com>
|
||||
* Author: Martin Lundholm <martin.xa.lundholm@stericsson.com>
|
||||
* Ported to drivers/mmc/ by: Matt Waddel <matt.waddel@linaro.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
/* #define DEBUG */
|
||||
|
||||
#include <asm/io.h>
|
||||
#include "common.h"
|
||||
#include <errno.h>
|
||||
#include <mmc.h>
|
||||
#include "arm_pl180_mmci.h"
|
||||
#include <malloc.h>
|
||||
|
||||
static int wait_for_command_end(struct mmc *dev, struct mmc_cmd *cmd)
|
||||
{
|
||||
u32 hoststatus, statusmask;
|
||||
struct pl180_mmc_host *host = dev->priv;
|
||||
|
||||
statusmask = SDI_STA_CTIMEOUT | SDI_STA_CCRCFAIL;
|
||||
if ((cmd->resp_type & MMC_RSP_PRESENT))
|
||||
statusmask |= SDI_STA_CMDREND;
|
||||
else
|
||||
statusmask |= SDI_STA_CMDSENT;
|
||||
|
||||
do
|
||||
hoststatus = readl(&host->base->status) & statusmask;
|
||||
while (!hoststatus);
|
||||
|
||||
writel(statusmask, &host->base->status_clear);
|
||||
if (hoststatus & SDI_STA_CTIMEOUT) {
|
||||
debug("CMD%d time out\n", cmd->cmdidx);
|
||||
return TIMEOUT;
|
||||
} else if ((hoststatus & SDI_STA_CCRCFAIL) &&
|
||||
(cmd->resp_type & MMC_RSP_CRC)) {
|
||||
printf("CMD%d CRC error\n", cmd->cmdidx);
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_PRESENT) {
|
||||
cmd->response[0] = readl(&host->base->response0);
|
||||
cmd->response[1] = readl(&host->base->response1);
|
||||
cmd->response[2] = readl(&host->base->response2);
|
||||
cmd->response[3] = readl(&host->base->response3);
|
||||
debug("CMD%d response[0]:0x%08X, response[1]:0x%08X, "
|
||||
"response[2]:0x%08X, response[3]:0x%08X\n",
|
||||
cmd->cmdidx, cmd->response[0], cmd->response[1],
|
||||
cmd->response[2], cmd->response[3]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* send command to the mmc card and wait for results */
|
||||
static int do_command(struct mmc *dev, struct mmc_cmd *cmd)
|
||||
{
|
||||
int result;
|
||||
u32 sdi_cmd = 0;
|
||||
struct pl180_mmc_host *host = dev->priv;
|
||||
|
||||
sdi_cmd = ((cmd->cmdidx & SDI_CMD_CMDINDEX_MASK) | SDI_CMD_CPSMEN);
|
||||
|
||||
if (cmd->resp_type) {
|
||||
sdi_cmd |= SDI_CMD_WAITRESP;
|
||||
if (cmd->resp_type & MMC_RSP_136)
|
||||
sdi_cmd |= SDI_CMD_LONGRESP;
|
||||
}
|
||||
|
||||
writel((u32)cmd->cmdarg, &host->base->argument);
|
||||
udelay(COMMAND_REG_DELAY);
|
||||
writel(sdi_cmd, &host->base->command);
|
||||
result = wait_for_command_end(dev, cmd);
|
||||
|
||||
/* After CMD2 set RCA to a none zero value. */
|
||||
if ((result == 0) && (cmd->cmdidx == MMC_CMD_ALL_SEND_CID))
|
||||
dev->rca = 10;
|
||||
|
||||
/* After CMD3 open drain is switched off and push pull is used. */
|
||||
if ((result == 0) && (cmd->cmdidx == MMC_CMD_SET_RELATIVE_ADDR)) {
|
||||
u32 sdi_pwr = readl(&host->base->power) & ~SDI_PWR_OPD;
|
||||
writel(sdi_pwr, &host->base->power);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int read_bytes(struct mmc *dev, u32 *dest, u32 blkcount, u32 blksize)
|
||||
{
|
||||
u32 *tempbuff = dest;
|
||||
u64 xfercount = blkcount * blksize;
|
||||
struct pl180_mmc_host *host = dev->priv;
|
||||
u32 status, status_err;
|
||||
|
||||
debug("read_bytes: blkcount=%u blksize=%u\n", blkcount, blksize);
|
||||
|
||||
status = readl(&host->base->status);
|
||||
status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT |
|
||||
SDI_STA_RXOVERR);
|
||||
while ((!status_err) && (xfercount >= sizeof(u32))) {
|
||||
if (status & SDI_STA_RXDAVL) {
|
||||
*(tempbuff) = readl(&host->base->fifo);
|
||||
tempbuff++;
|
||||
xfercount -= sizeof(u32);
|
||||
}
|
||||
status = readl(&host->base->status);
|
||||
status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT |
|
||||
SDI_STA_RXOVERR);
|
||||
}
|
||||
|
||||
status_err = status &
|
||||
(SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND |
|
||||
SDI_STA_RXOVERR);
|
||||
while (!status_err) {
|
||||
status = readl(&host->base->status);
|
||||
status_err = status &
|
||||
(SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND |
|
||||
SDI_STA_RXOVERR);
|
||||
}
|
||||
|
||||
if (status & SDI_STA_DTIMEOUT) {
|
||||
printf("Read data timed out, xfercount: %llu, status: 0x%08X\n",
|
||||
xfercount, status);
|
||||
return -ETIMEDOUT;
|
||||
} else if (status & SDI_STA_DCRCFAIL) {
|
||||
printf("Read data bytes CRC error: 0x%x\n", status);
|
||||
return -EILSEQ;
|
||||
} else if (status & SDI_STA_RXOVERR) {
|
||||
printf("Read data RX overflow error\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
writel(SDI_ICR_MASK, &host->base->status_clear);
|
||||
|
||||
if (xfercount) {
|
||||
printf("Read data error, xfercount: %llu\n", xfercount);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_bytes(struct mmc *dev, u32 *src, u32 blkcount, u32 blksize)
|
||||
{
|
||||
u32 *tempbuff = src;
|
||||
int i;
|
||||
u64 xfercount = blkcount * blksize;
|
||||
struct pl180_mmc_host *host = dev->priv;
|
||||
u32 status, status_err;
|
||||
|
||||
debug("write_bytes: blkcount=%u blksize=%u\n", blkcount, blksize);
|
||||
|
||||
status = readl(&host->base->status);
|
||||
status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT);
|
||||
while (!status_err && xfercount) {
|
||||
if (status & SDI_STA_TXFIFOBW) {
|
||||
if (xfercount >= SDI_FIFO_BURST_SIZE * sizeof(u32)) {
|
||||
for (i = 0; i < SDI_FIFO_BURST_SIZE; i++)
|
||||
writel(*(tempbuff + i),
|
||||
&host->base->fifo);
|
||||
tempbuff += SDI_FIFO_BURST_SIZE;
|
||||
xfercount -= SDI_FIFO_BURST_SIZE * sizeof(u32);
|
||||
} else {
|
||||
while (xfercount >= sizeof(u32)) {
|
||||
writel(*(tempbuff), &host->base->fifo);
|
||||
tempbuff++;
|
||||
xfercount -= sizeof(u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
status = readl(&host->base->status);
|
||||
status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT);
|
||||
}
|
||||
|
||||
status_err = status &
|
||||
(SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND);
|
||||
while (!status_err) {
|
||||
status = readl(&host->base->status);
|
||||
status_err = status &
|
||||
(SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND);
|
||||
}
|
||||
|
||||
if (status & SDI_STA_DTIMEOUT) {
|
||||
printf("Write data timed out, xfercount:%llu,status:0x%08X\n",
|
||||
xfercount, status);
|
||||
return -ETIMEDOUT;
|
||||
} else if (status & SDI_STA_DCRCFAIL) {
|
||||
printf("Write data CRC error\n");
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
writel(SDI_ICR_MASK, &host->base->status_clear);
|
||||
|
||||
if (xfercount) {
|
||||
printf("Write data error, xfercount:%llu", xfercount);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_data_transfer(struct mmc *dev,
|
||||
struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
int error = -ETIMEDOUT;
|
||||
struct pl180_mmc_host *host = dev->priv;
|
||||
u32 blksz = 0;
|
||||
u32 data_ctrl = 0;
|
||||
u32 data_len = (u32) (data->blocks * data->blocksize);
|
||||
|
||||
if (!host->version2) {
|
||||
blksz = (ffs(data->blocksize) - 1);
|
||||
data_ctrl |= ((blksz << 4) & SDI_DCTRL_DBLKSIZE_MASK);
|
||||
} else {
|
||||
blksz = data->blocksize;
|
||||
data_ctrl |= (blksz << SDI_DCTRL_DBLOCKSIZE_V2_SHIFT);
|
||||
}
|
||||
data_ctrl |= SDI_DCTRL_DTEN | SDI_DCTRL_BUSYMODE;
|
||||
|
||||
writel(SDI_DTIMER_DEFAULT, &host->base->datatimer);
|
||||
writel(data_len, &host->base->datalength);
|
||||
udelay(DATA_REG_DELAY);
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
data_ctrl |= SDI_DCTRL_DTDIR_IN;
|
||||
writel(data_ctrl, &host->base->datactrl);
|
||||
|
||||
error = do_command(dev, cmd);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = read_bytes(dev, (u32 *)data->dest, (u32)data->blocks,
|
||||
(u32)data->blocksize);
|
||||
} else if (data->flags & MMC_DATA_WRITE) {
|
||||
error = do_command(dev, cmd);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
writel(data_ctrl, &host->base->datactrl);
|
||||
error = write_bytes(dev, (u32 *)data->src, (u32)data->blocks,
|
||||
(u32)data->blocksize);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int host_request(struct mmc *dev,
|
||||
struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
int result;
|
||||
|
||||
if (data)
|
||||
result = do_data_transfer(dev, cmd, data);
|
||||
else
|
||||
result = do_command(dev, cmd);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* MMC uses open drain drivers in the enumeration phase */
|
||||
static int mmc_host_reset(struct mmc *dev)
|
||||
{
|
||||
struct pl180_mmc_host *host = dev->priv;
|
||||
|
||||
writel(host->pwr_init, &host->base->power);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void host_set_ios(struct mmc *dev)
|
||||
{
|
||||
struct pl180_mmc_host *host = dev->priv;
|
||||
u32 sdi_clkcr;
|
||||
|
||||
sdi_clkcr = readl(&host->base->clock);
|
||||
|
||||
/* Ramp up the clock rate */
|
||||
if (dev->clock) {
|
||||
u32 clkdiv = 0;
|
||||
u32 tmp_clock;
|
||||
|
||||
if (dev->clock >= dev->cfg->f_max) {
|
||||
clkdiv = 0;
|
||||
dev->clock = dev->cfg->f_max;
|
||||
} else {
|
||||
clkdiv = (host->clock_in / dev->clock) - 2;
|
||||
}
|
||||
|
||||
tmp_clock = host->clock_in / (clkdiv + 2);
|
||||
while (tmp_clock > dev->clock) {
|
||||
clkdiv++;
|
||||
tmp_clock = host->clock_in / (clkdiv + 2);
|
||||
}
|
||||
|
||||
if (clkdiv > SDI_CLKCR_CLKDIV_MASK)
|
||||
clkdiv = SDI_CLKCR_CLKDIV_MASK;
|
||||
|
||||
tmp_clock = host->clock_in / (clkdiv + 2);
|
||||
dev->clock = tmp_clock;
|
||||
sdi_clkcr &= ~(SDI_CLKCR_CLKDIV_MASK);
|
||||
sdi_clkcr |= clkdiv;
|
||||
}
|
||||
|
||||
/* Set the bus width */
|
||||
if (dev->bus_width) {
|
||||
u32 buswidth = 0;
|
||||
|
||||
switch (dev->bus_width) {
|
||||
case 1:
|
||||
buswidth |= SDI_CLKCR_WIDBUS_1;
|
||||
break;
|
||||
case 4:
|
||||
buswidth |= SDI_CLKCR_WIDBUS_4;
|
||||
break;
|
||||
case 8:
|
||||
buswidth |= SDI_CLKCR_WIDBUS_8;
|
||||
break;
|
||||
default:
|
||||
printf("Invalid bus width: %d\n", dev->bus_width);
|
||||
break;
|
||||
}
|
||||
sdi_clkcr &= ~(SDI_CLKCR_WIDBUS_MASK);
|
||||
sdi_clkcr |= buswidth;
|
||||
}
|
||||
|
||||
writel(sdi_clkcr, &host->base->clock);
|
||||
udelay(CLK_CHANGE_DELAY);
|
||||
}
|
||||
|
||||
static const struct mmc_ops arm_pl180_mmci_ops = {
|
||||
.send_cmd = host_request,
|
||||
.set_ios = host_set_ios,
|
||||
.init = mmc_host_reset,
|
||||
};
|
||||
|
||||
/*
|
||||
* mmc_host_init - initialize the mmc controller.
|
||||
* Set initial clock and power for mmc slot.
|
||||
* Initialize mmc struct and register with mmc framework.
|
||||
*/
|
||||
int arm_pl180_mmci_init(struct pl180_mmc_host *host)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
u32 sdi_u32;
|
||||
|
||||
writel(host->pwr_init, &host->base->power);
|
||||
writel(host->clkdiv_init, &host->base->clock);
|
||||
udelay(CLK_CHANGE_DELAY);
|
||||
|
||||
/* Disable mmc interrupts */
|
||||
sdi_u32 = readl(&host->base->mask0) & ~SDI_MASK0_MASK;
|
||||
writel(sdi_u32, &host->base->mask0);
|
||||
|
||||
host->cfg.name = host->name;
|
||||
host->cfg.ops = &arm_pl180_mmci_ops;
|
||||
/* TODO remove the duplicates */
|
||||
host->cfg.host_caps = host->caps;
|
||||
host->cfg.voltages = host->voltages;
|
||||
host->cfg.f_min = host->clock_min;
|
||||
host->cfg.f_max = host->clock_max;
|
||||
if (host->b_max != 0)
|
||||
host->cfg.b_max = host->b_max;
|
||||
else
|
||||
host->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
|
||||
mmc = mmc_create(&host->cfg, host);
|
||||
if (mmc == NULL)
|
||||
return -1;
|
||||
|
||||
debug("registered mmc interface number is:%d\n", mmc->block_dev.devnum);
|
||||
|
||||
return 0;
|
||||
}
|
||||
195
u-boot/drivers/mmc/arm_pl180_mmci.h
Normal file
195
u-boot/drivers/mmc/arm_pl180_mmci.h
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* ARM PrimeCell MultiMedia Card Interface - PL180
|
||||
*
|
||||
* Copyright (C) ST-Ericsson SA 2010
|
||||
*
|
||||
* Author: Ulf Hansson <ulf.hansson@stericsson.com>
|
||||
* Author: Martin Lundholm <martin.xa.lundholm@stericsson.com>
|
||||
* Ported to drivers/mmc/ by: Matt Waddel <matt.waddel@linaro.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#ifndef __ARM_PL180_MMCI_H__
|
||||
#define __ARM_PL180_MMCI_H__
|
||||
|
||||
/* need definition of struct mmc_config */
|
||||
#include <mmc.h>
|
||||
|
||||
#define COMMAND_REG_DELAY 300
|
||||
#define DATA_REG_DELAY 1000
|
||||
#define CLK_CHANGE_DELAY 2000
|
||||
|
||||
#define INIT_PWR 0xBF /* Power on, full power, not open drain */
|
||||
#define ARM_MCLK (100*1000*1000)
|
||||
|
||||
/* SDI Power Control register bits */
|
||||
#define SDI_PWR_PWRCTRL_MASK 0x00000003
|
||||
#define SDI_PWR_PWRCTRL_ON 0x00000003
|
||||
#define SDI_PWR_PWRCTRL_OFF 0x00000000
|
||||
#define SDI_PWR_DAT2DIREN 0x00000004
|
||||
#define SDI_PWR_CMDDIREN 0x00000008
|
||||
#define SDI_PWR_DAT0DIREN 0x00000010
|
||||
#define SDI_PWR_DAT31DIREN 0x00000020
|
||||
#define SDI_PWR_OPD 0x00000040
|
||||
#define SDI_PWR_FBCLKEN 0x00000080
|
||||
#define SDI_PWR_DAT74DIREN 0x00000100
|
||||
#define SDI_PWR_RSTEN 0x00000200
|
||||
|
||||
#define VOLTAGE_WINDOW_MMC 0x00FF8080
|
||||
#define VOLTAGE_WINDOW_SD 0x80010000
|
||||
|
||||
/* SDI clock control register bits */
|
||||
#define SDI_CLKCR_CLKDIV_MASK 0x000000FF
|
||||
#define SDI_CLKCR_CLKEN 0x00000100
|
||||
#define SDI_CLKCR_PWRSAV 0x00000200
|
||||
#define SDI_CLKCR_BYPASS 0x00000400
|
||||
#define SDI_CLKCR_WIDBUS_MASK 0x00001800
|
||||
#define SDI_CLKCR_WIDBUS_1 0x00000000
|
||||
#define SDI_CLKCR_WIDBUS_4 0x00000800
|
||||
/* V2 only */
|
||||
#define SDI_CLKCR_WIDBUS_8 0x00001000
|
||||
#define SDI_CLKCR_NEDGE 0x00002000
|
||||
#define SDI_CLKCR_HWFC_EN 0x00004000
|
||||
|
||||
#define SDI_CLKCR_CLKDIV_INIT_V1 0x000000C6 /* MCLK/(2*(0xC6+1)) => 505KHz */
|
||||
#define SDI_CLKCR_CLKDIV_INIT_V2 0x000000FD
|
||||
|
||||
/* SDI command register bits */
|
||||
#define SDI_CMD_CMDINDEX_MASK 0x000000FF
|
||||
#define SDI_CMD_WAITRESP 0x00000040
|
||||
#define SDI_CMD_LONGRESP 0x00000080
|
||||
#define SDI_CMD_WAITINT 0x00000100
|
||||
#define SDI_CMD_WAITPEND 0x00000200
|
||||
#define SDI_CMD_CPSMEN 0x00000400
|
||||
#define SDI_CMD_SDIOSUSPEND 0x00000800
|
||||
#define SDI_CMD_ENDCMDCOMPL 0x00001000
|
||||
#define SDI_CMD_NIEN 0x00002000
|
||||
#define SDI_CMD_CE_ATACMD 0x00004000
|
||||
#define SDI_CMD_CBOOTMODEEN 0x00008000
|
||||
|
||||
#define SDI_DTIMER_DEFAULT 0xFFFF0000
|
||||
|
||||
/* SDI Status register bits */
|
||||
#define SDI_STA_CCRCFAIL 0x00000001
|
||||
#define SDI_STA_DCRCFAIL 0x00000002
|
||||
#define SDI_STA_CTIMEOUT 0x00000004
|
||||
#define SDI_STA_DTIMEOUT 0x00000008
|
||||
#define SDI_STA_TXUNDERR 0x00000010
|
||||
#define SDI_STA_RXOVERR 0x00000020
|
||||
#define SDI_STA_CMDREND 0x00000040
|
||||
#define SDI_STA_CMDSENT 0x00000080
|
||||
#define SDI_STA_DATAEND 0x00000100
|
||||
#define SDI_STA_STBITERR 0x00000200
|
||||
#define SDI_STA_DBCKEND 0x00000400
|
||||
#define SDI_STA_CMDACT 0x00000800
|
||||
#define SDI_STA_TXACT 0x00001000
|
||||
#define SDI_STA_RXACT 0x00002000
|
||||
#define SDI_STA_TXFIFOBW 0x00004000
|
||||
#define SDI_STA_RXFIFOBR 0x00008000
|
||||
#define SDI_STA_TXFIFOF 0x00010000
|
||||
#define SDI_STA_RXFIFOF 0x00020000
|
||||
#define SDI_STA_TXFIFOE 0x00040000
|
||||
#define SDI_STA_RXFIFOE 0x00080000
|
||||
#define SDI_STA_TXDAVL 0x00100000
|
||||
#define SDI_STA_RXDAVL 0x00200000
|
||||
#define SDI_STA_SDIOIT 0x00400000
|
||||
#define SDI_STA_CEATAEND 0x00800000
|
||||
#define SDI_STA_CARDBUSY 0x01000000
|
||||
#define SDI_STA_BOOTMODE 0x02000000
|
||||
#define SDI_STA_BOOTACKERR 0x04000000
|
||||
#define SDI_STA_BOOTACKTIMEOUT 0x08000000
|
||||
#define SDI_STA_RSTNEND 0x10000000
|
||||
|
||||
/* SDI Interrupt Clear register bits */
|
||||
#define SDI_ICR_MASK 0x1DC007FF
|
||||
#define SDI_ICR_CCRCFAILC 0x00000001
|
||||
#define SDI_ICR_DCRCFAILC 0x00000002
|
||||
#define SDI_ICR_CTIMEOUTC 0x00000004
|
||||
#define SDI_ICR_DTIMEOUTC 0x00000008
|
||||
#define SDI_ICR_TXUNDERRC 0x00000010
|
||||
#define SDI_ICR_RXOVERRC 0x00000020
|
||||
#define SDI_ICR_CMDRENDC 0x00000040
|
||||
#define SDI_ICR_CMDSENTC 0x00000080
|
||||
#define SDI_ICR_DATAENDC 0x00000100
|
||||
#define SDI_ICR_STBITERRC 0x00000200
|
||||
#define SDI_ICR_DBCKENDC 0x00000400
|
||||
#define SDI_ICR_SDIOITC 0x00400000
|
||||
#define SDI_ICR_CEATAENDC 0x00800000
|
||||
#define SDI_ICR_BUSYENDC 0x01000000
|
||||
#define SDI_ICR_BOOTACKERRC 0x04000000
|
||||
#define SDI_ICR_BOOTACKTIMEOUTC 0x08000000
|
||||
#define SDI_ICR_RSTNENDC 0x10000000
|
||||
|
||||
#define SDI_MASK0_MASK 0x1FFFFFFF
|
||||
|
||||
/* SDI Data control register bits */
|
||||
#define SDI_DCTRL_DTEN 0x00000001
|
||||
#define SDI_DCTRL_DTDIR_IN 0x00000002
|
||||
#define SDI_DCTRL_DTMODE_STREAM 0x00000004
|
||||
#define SDI_DCTRL_DMAEN 0x00000008
|
||||
#define SDI_DCTRL_DBLKSIZE_MASK 0x000000F0
|
||||
#define SDI_DCTRL_RWSTART 0x00000100
|
||||
#define SDI_DCTRL_RWSTOP 0x00000200
|
||||
#define SDI_DCTRL_RWMOD 0x00000200
|
||||
#define SDI_DCTRL_SDIOEN 0x00000800
|
||||
#define SDI_DCTRL_DMAREQCTL 0x00001000
|
||||
#define SDI_DCTRL_DBOOTMODEEN 0x00002000
|
||||
#define SDI_DCTRL_BUSYMODE 0x00004000
|
||||
#define SDI_DCTRL_DDR_MODE 0x00008000
|
||||
#define SDI_DCTRL_DBLOCKSIZE_V2_MASK 0x7fff0000
|
||||
#define SDI_DCTRL_DBLOCKSIZE_V2_SHIFT 16
|
||||
|
||||
#define SDI_FIFO_BURST_SIZE 8
|
||||
|
||||
struct sdi_registers {
|
||||
u32 power; /* 0x00*/
|
||||
u32 clock; /* 0x04*/
|
||||
u32 argument; /* 0x08*/
|
||||
u32 command; /* 0x0c*/
|
||||
u32 respcommand; /* 0x10*/
|
||||
u32 response0; /* 0x14*/
|
||||
u32 response1; /* 0x18*/
|
||||
u32 response2; /* 0x1c*/
|
||||
u32 response3; /* 0x20*/
|
||||
u32 datatimer; /* 0x24*/
|
||||
u32 datalength; /* 0x28*/
|
||||
u32 datactrl; /* 0x2c*/
|
||||
u32 datacount; /* 0x30*/
|
||||
u32 status; /* 0x34*/
|
||||
u32 status_clear; /* 0x38*/
|
||||
u32 mask0; /* 0x3c*/
|
||||
u32 mask1; /* 0x40*/
|
||||
u32 card_select; /* 0x44*/
|
||||
u32 fifo_count; /* 0x48*/
|
||||
u32 padding1[(0x80-0x4C)>>2];
|
||||
u32 fifo; /* 0x80*/
|
||||
u32 padding2[(0xFE0-0x84)>>2];
|
||||
u32 periph_id0; /* 0xFE0 mmc Peripheral Identifcation Register*/
|
||||
u32 periph_id1; /* 0xFE4*/
|
||||
u32 periph_id2; /* 0xFE8*/
|
||||
u32 periph_id3; /* 0xFEC*/
|
||||
u32 pcell_id0; /* 0xFF0*/
|
||||
u32 pcell_id1; /* 0xFF4*/
|
||||
u32 pcell_id2; /* 0xFF8*/
|
||||
u32 pcell_id3; /* 0xFFC*/
|
||||
};
|
||||
|
||||
struct pl180_mmc_host {
|
||||
struct sdi_registers *base;
|
||||
char name[32];
|
||||
unsigned int b_max;
|
||||
unsigned int voltages;
|
||||
unsigned int caps;
|
||||
unsigned int clock_in;
|
||||
unsigned int clock_min;
|
||||
unsigned int clock_max;
|
||||
unsigned int clkdiv_init;
|
||||
unsigned int pwr_init;
|
||||
int version2;
|
||||
struct mmc_config cfg;
|
||||
};
|
||||
|
||||
int arm_pl180_mmci_init(struct pl180_mmc_host *);
|
||||
|
||||
#endif
|
||||
40
u-boot/drivers/mmc/atmel_sdhci.c
Normal file
40
u-boot/drivers/mmc/atmel_sdhci.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Atmel Corporation
|
||||
* Wenyou.Yang <wenyou.yang@atmel.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
#include <asm/arch/clk.h>
|
||||
|
||||
#define ATMEL_SDHC_MIN_FREQ 400000
|
||||
|
||||
int atmel_sdhci_init(void *regbase, u32 id)
|
||||
{
|
||||
struct sdhci_host *host;
|
||||
u32 max_clk, min_clk = ATMEL_SDHC_MIN_FREQ;
|
||||
|
||||
host = (struct sdhci_host *)calloc(1, sizeof(struct sdhci_host));
|
||||
if (!host) {
|
||||
printf("%s: sdhci_host calloc failed\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
host->name = "atmel_sdhci";
|
||||
host->ioaddr = regbase;
|
||||
host->quirks = 0;
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
max_clk = at91_get_periph_generated_clk(id);
|
||||
if (!max_clk) {
|
||||
printf("%s: Failed to get the proper clock\n", __func__);
|
||||
free(host);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
add_sdhci(host, max_clk, min_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
191
u-boot/drivers/mmc/bcm2835_sdhci.c
Normal file
191
u-boot/drivers/mmc/bcm2835_sdhci.c
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* This code was extracted from:
|
||||
* git://github.com/gonzoua/u-boot-pi.git master
|
||||
* and hence presumably (C) 2012 Oleksandr Tymoshenko
|
||||
*
|
||||
* Tweaks for U-Boot upstreaming
|
||||
* (C) 2012 Stephen Warren
|
||||
*
|
||||
* Portions (e.g. read/write macros, concepts for back-to-back register write
|
||||
* timing workarounds) obviously extracted from the Linux kernel at:
|
||||
* https://github.com/raspberrypi/linux.git rpi-3.6.y
|
||||
*
|
||||
* The Linux kernel code has the following (c) and license, which is hence
|
||||
* propagated to Oleksandr's tree and here:
|
||||
*
|
||||
* Support for SDHCI device on 2835
|
||||
* Based on sdhci-bcm2708.c (c) 2010 Broadcom
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
/* Supports:
|
||||
* SDHCI platform device - Arasan SD controller in BCM2708
|
||||
*
|
||||
* Inspired by sdhci-pci.c, by Pierre Ossman
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
#include <mach/timer.h>
|
||||
#include <mach/sdhci.h>
|
||||
|
||||
/* 400KHz is max freq for card ID etc. Use that as min */
|
||||
#define MIN_FREQ 400000
|
||||
|
||||
struct bcm2835_sdhci_host {
|
||||
struct sdhci_host host;
|
||||
uint twoticks_delay;
|
||||
ulong last_write;
|
||||
};
|
||||
|
||||
static inline struct bcm2835_sdhci_host *to_bcm(struct sdhci_host *host)
|
||||
{
|
||||
return (struct bcm2835_sdhci_host *)host;
|
||||
}
|
||||
|
||||
static inline void bcm2835_sdhci_raw_writel(struct sdhci_host *host, u32 val,
|
||||
int reg)
|
||||
{
|
||||
struct bcm2835_sdhci_host *bcm_host = to_bcm(host);
|
||||
|
||||
/*
|
||||
* The Arasan has a bugette whereby it may lose the content of
|
||||
* successive writes to registers that are within two SD-card clock
|
||||
* cycles of each other (a clock domain crossing problem).
|
||||
* It seems, however, that the data register does not have this problem.
|
||||
* (Which is just as well - otherwise we'd have to nobble the DMA engine
|
||||
* too)
|
||||
*/
|
||||
while (timer_get_us() - bcm_host->last_write < bcm_host->twoticks_delay)
|
||||
;
|
||||
|
||||
writel(val, host->ioaddr + reg);
|
||||
bcm_host->last_write = timer_get_us();
|
||||
}
|
||||
|
||||
static inline u32 bcm2835_sdhci_raw_readl(struct sdhci_host *host, int reg)
|
||||
{
|
||||
return readl(host->ioaddr + reg);
|
||||
}
|
||||
|
||||
static void bcm2835_sdhci_writel(struct sdhci_host *host, u32 val, int reg)
|
||||
{
|
||||
bcm2835_sdhci_raw_writel(host, val, reg);
|
||||
}
|
||||
|
||||
static void bcm2835_sdhci_writew(struct sdhci_host *host, u16 val, int reg)
|
||||
{
|
||||
static u32 shadow;
|
||||
u32 oldval = (reg == SDHCI_COMMAND) ? shadow :
|
||||
bcm2835_sdhci_raw_readl(host, reg & ~3);
|
||||
u32 word_num = (reg >> 1) & 1;
|
||||
u32 word_shift = word_num * 16;
|
||||
u32 mask = 0xffff << word_shift;
|
||||
u32 newval = (oldval & ~mask) | (val << word_shift);
|
||||
|
||||
if (reg == SDHCI_TRANSFER_MODE)
|
||||
shadow = newval;
|
||||
else
|
||||
bcm2835_sdhci_raw_writel(host, newval, reg & ~3);
|
||||
}
|
||||
|
||||
static void bcm2835_sdhci_writeb(struct sdhci_host *host, u8 val, int reg)
|
||||
{
|
||||
u32 oldval = bcm2835_sdhci_raw_readl(host, reg & ~3);
|
||||
u32 byte_num = reg & 3;
|
||||
u32 byte_shift = byte_num * 8;
|
||||
u32 mask = 0xff << byte_shift;
|
||||
u32 newval = (oldval & ~mask) | (val << byte_shift);
|
||||
|
||||
bcm2835_sdhci_raw_writel(host, newval, reg & ~3);
|
||||
}
|
||||
|
||||
static u32 bcm2835_sdhci_readl(struct sdhci_host *host, int reg)
|
||||
{
|
||||
u32 val = bcm2835_sdhci_raw_readl(host, reg);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static u16 bcm2835_sdhci_readw(struct sdhci_host *host, int reg)
|
||||
{
|
||||
u32 val = bcm2835_sdhci_raw_readl(host, (reg & ~3));
|
||||
u32 word_num = (reg >> 1) & 1;
|
||||
u32 word_shift = word_num * 16;
|
||||
u32 word = (val >> word_shift) & 0xffff;
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
static u8 bcm2835_sdhci_readb(struct sdhci_host *host, int reg)
|
||||
{
|
||||
u32 val = bcm2835_sdhci_raw_readl(host, (reg & ~3));
|
||||
u32 byte_num = reg & 3;
|
||||
u32 byte_shift = byte_num * 8;
|
||||
u32 byte = (val >> byte_shift) & 0xff;
|
||||
|
||||
return byte;
|
||||
}
|
||||
|
||||
static const struct sdhci_ops bcm2835_ops = {
|
||||
.write_l = bcm2835_sdhci_writel,
|
||||
.write_w = bcm2835_sdhci_writew,
|
||||
.write_b = bcm2835_sdhci_writeb,
|
||||
.read_l = bcm2835_sdhci_readl,
|
||||
.read_w = bcm2835_sdhci_readw,
|
||||
.read_b = bcm2835_sdhci_readb,
|
||||
};
|
||||
|
||||
int bcm2835_sdhci_init(u32 regbase, u32 emmc_freq)
|
||||
{
|
||||
struct bcm2835_sdhci_host *bcm_host;
|
||||
struct sdhci_host *host;
|
||||
|
||||
bcm_host = calloc(1, sizeof(*bcm_host));
|
||||
if (!bcm_host) {
|
||||
printf("sdhci_host calloc fail!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* See the comments in bcm2835_sdhci_raw_writel().
|
||||
*
|
||||
* This should probably be dynamically calculated based on the actual
|
||||
* frequency. However, this is the longest we'll have to wait, and
|
||||
* doesn't seem to slow access down too much, so the added complexity
|
||||
* doesn't seem worth it for now.
|
||||
*
|
||||
* 1/MIN_FREQ is (max) time per tick of eMMC clock.
|
||||
* 2/MIN_FREQ is time for two ticks.
|
||||
* Multiply by 1000000 to get uS per two ticks.
|
||||
* +1 for hack rounding.
|
||||
*/
|
||||
bcm_host->twoticks_delay = ((2 * 1000000) / MIN_FREQ) + 1;
|
||||
bcm_host->last_write = 0;
|
||||
|
||||
host = &bcm_host->host;
|
||||
host->name = "bcm2835_sdhci";
|
||||
host->ioaddr = (void *)(unsigned long)regbase;
|
||||
host->quirks = SDHCI_QUIRK_BROKEN_VOLTAGE | SDHCI_QUIRK_BROKEN_R1B |
|
||||
SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_NO_HISPD_BIT;
|
||||
host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
|
||||
host->ops = &bcm2835_ops;
|
||||
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
add_sdhci(host, emmc_freq, MIN_FREQ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
304
u-boot/drivers/mmc/bfin_sdh.c
Normal file
304
u-boot/drivers/mmc/bfin_sdh.c
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Driver for Blackfin on-chip SDH controller
|
||||
*
|
||||
* Copyright (c) 2008-2009 Analog Devices Inc.
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <part.h>
|
||||
#include <mmc.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <asm/blackfin.h>
|
||||
#include <asm/clock.h>
|
||||
#include <asm/portmux.h>
|
||||
#include <asm/mach-common/bits/sdh.h>
|
||||
#include <asm/mach-common/bits/dma.h>
|
||||
|
||||
#if defined(__ADSPBF50x__) || defined(__ADSPBF51x__) || defined(__ADSPBF60x__)
|
||||
# define bfin_read_SDH_CLK_CTL bfin_read_RSI_CLK_CONTROL
|
||||
# define bfin_write_SDH_CLK_CTL bfin_write_RSI_CLK_CONTROL
|
||||
# define bfin_write_SDH_ARGUMENT bfin_write_RSI_ARGUMENT
|
||||
# define bfin_write_SDH_COMMAND bfin_write_RSI_COMMAND
|
||||
# define bfin_read_SDH_RESPONSE0 bfin_read_RSI_RESPONSE0
|
||||
# define bfin_read_SDH_RESPONSE1 bfin_read_RSI_RESPONSE1
|
||||
# define bfin_read_SDH_RESPONSE2 bfin_read_RSI_RESPONSE2
|
||||
# define bfin_read_SDH_RESPONSE3 bfin_read_RSI_RESPONSE3
|
||||
# define bfin_write_SDH_DATA_TIMER bfin_write_RSI_DATA_TIMER
|
||||
# define bfin_write_SDH_DATA_LGTH bfin_write_RSI_DATA_LGTH
|
||||
# define bfin_read_SDH_DATA_CTL bfin_read_RSI_DATA_CONTROL
|
||||
# define bfin_write_SDH_DATA_CTL bfin_write_RSI_DATA_CONTROL
|
||||
# define bfin_read_SDH_STATUS bfin_read_RSI_STATUS
|
||||
# define bfin_write_SDH_STATUS_CLR bfin_write_RSI_STATUSCL
|
||||
# define bfin_read_SDH_CFG bfin_read_RSI_CONFIG
|
||||
# define bfin_write_SDH_CFG bfin_write_RSI_CONFIG
|
||||
# if defined(__ADSPBF60x__)
|
||||
# define bfin_read_SDH_BLK_SIZE bfin_read_RSI_BLKSZ
|
||||
# define bfin_write_SDH_BLK_SIZE bfin_write_RSI_BLKSZ
|
||||
# define bfin_write_DMA_START_ADDR bfin_write_DMA10_START_ADDR
|
||||
# define bfin_write_DMA_X_COUNT bfin_write_DMA10_X_COUNT
|
||||
# define bfin_write_DMA_X_MODIFY bfin_write_DMA10_X_MODIFY
|
||||
# define bfin_write_DMA_CONFIG bfin_write_DMA10_CONFIG
|
||||
# else
|
||||
# define bfin_read_SDH_PWR_CTL bfin_read_RSI_PWR_CONTROL
|
||||
# define bfin_write_SDH_PWR_CTL bfin_write_RSI_PWR_CONTROL
|
||||
# define bfin_write_DMA_START_ADDR bfin_write_DMA4_START_ADDR
|
||||
# define bfin_write_DMA_X_COUNT bfin_write_DMA4_X_COUNT
|
||||
# define bfin_write_DMA_X_MODIFY bfin_write_DMA4_X_MODIFY
|
||||
# define bfin_write_DMA_CONFIG bfin_write_DMA4_CONFIG
|
||||
# endif
|
||||
# define PORTMUX_PINS \
|
||||
{ P_RSI_DATA0, P_RSI_DATA1, P_RSI_DATA2, P_RSI_DATA3, P_RSI_CMD, P_RSI_CLK, 0 }
|
||||
#elif defined(__ADSPBF54x__)
|
||||
# define bfin_write_DMA_START_ADDR bfin_write_DMA22_START_ADDR
|
||||
# define bfin_write_DMA_X_COUNT bfin_write_DMA22_X_COUNT
|
||||
# define bfin_write_DMA_X_MODIFY bfin_write_DMA22_X_MODIFY
|
||||
# define bfin_write_DMA_CONFIG bfin_write_DMA22_CONFIG
|
||||
# define PORTMUX_PINS \
|
||||
{ P_SD_D0, P_SD_D1, P_SD_D2, P_SD_D3, P_SD_CLK, P_SD_CMD, 0 }
|
||||
#else
|
||||
# error no support for this proc yet
|
||||
#endif
|
||||
|
||||
static int
|
||||
sdh_send_cmd(struct mmc *mmc, struct mmc_cmd *mmc_cmd)
|
||||
{
|
||||
unsigned int status, timeout;
|
||||
int cmd = mmc_cmd->cmdidx;
|
||||
int flags = mmc_cmd->resp_type;
|
||||
int arg = mmc_cmd->cmdarg;
|
||||
int ret;
|
||||
u16 sdh_cmd;
|
||||
|
||||
sdh_cmd = cmd | CMD_E;
|
||||
if (flags & MMC_RSP_PRESENT)
|
||||
sdh_cmd |= CMD_RSP;
|
||||
if (flags & MMC_RSP_136)
|
||||
sdh_cmd |= CMD_L_RSP;
|
||||
#ifdef RSI_BLKSZ
|
||||
sdh_cmd |= CMD_DATA0_BUSY;
|
||||
#endif
|
||||
|
||||
bfin_write_SDH_ARGUMENT(arg);
|
||||
bfin_write_SDH_COMMAND(sdh_cmd);
|
||||
|
||||
/* wait for a while */
|
||||
timeout = 0;
|
||||
do {
|
||||
if (++timeout > 1000000) {
|
||||
status = CMD_TIME_OUT;
|
||||
break;
|
||||
}
|
||||
udelay(1);
|
||||
status = bfin_read_SDH_STATUS();
|
||||
} while (!(status & (CMD_SENT | CMD_RESP_END | CMD_TIME_OUT |
|
||||
CMD_CRC_FAIL)));
|
||||
|
||||
if (flags & MMC_RSP_PRESENT) {
|
||||
mmc_cmd->response[0] = bfin_read_SDH_RESPONSE0();
|
||||
if (flags & MMC_RSP_136) {
|
||||
mmc_cmd->response[1] = bfin_read_SDH_RESPONSE1();
|
||||
mmc_cmd->response[2] = bfin_read_SDH_RESPONSE2();
|
||||
mmc_cmd->response[3] = bfin_read_SDH_RESPONSE3();
|
||||
}
|
||||
}
|
||||
|
||||
if (status & CMD_TIME_OUT)
|
||||
ret = TIMEOUT;
|
||||
else if (status & CMD_CRC_FAIL && flags & MMC_RSP_CRC)
|
||||
ret = COMM_ERR;
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
bfin_write_SDH_STATUS_CLR(CMD_SENT_STAT | CMD_RESP_END_STAT |
|
||||
CMD_TIMEOUT_STAT | CMD_CRC_FAIL_STAT);
|
||||
#ifdef RSI_BLKSZ
|
||||
/* wait till card ready */
|
||||
while (!(bfin_read_RSI_ESTAT() & SD_CARD_READY))
|
||||
continue;
|
||||
bfin_write_RSI_ESTAT(SD_CARD_READY);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set data for single block transfer */
|
||||
static int sdh_setup_data(struct mmc *mmc, struct mmc_data *data)
|
||||
{
|
||||
u16 data_ctl = 0;
|
||||
u16 dma_cfg = 0;
|
||||
unsigned long data_size = data->blocksize * data->blocks;
|
||||
|
||||
/* Don't support write yet. */
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
return UNUSABLE_ERR;
|
||||
#ifndef RSI_BLKSZ
|
||||
data_ctl |= ((ffs(data->blocksize) - 1) << 4);
|
||||
#else
|
||||
bfin_write_SDH_BLK_SIZE(data->blocksize);
|
||||
#endif
|
||||
data_ctl |= DTX_DIR;
|
||||
bfin_write_SDH_DATA_CTL(data_ctl);
|
||||
dma_cfg = WDSIZE_32 | PSIZE_32 | RESTART | WNR | DMAEN;
|
||||
|
||||
bfin_write_SDH_DATA_TIMER(-1);
|
||||
|
||||
blackfin_dcache_flush_invalidate_range(data->dest,
|
||||
data->dest + data_size);
|
||||
/* configure DMA */
|
||||
bfin_write_DMA_START_ADDR(data->dest);
|
||||
bfin_write_DMA_X_COUNT(data_size / 4);
|
||||
bfin_write_DMA_X_MODIFY(4);
|
||||
bfin_write_DMA_CONFIG(dma_cfg);
|
||||
bfin_write_SDH_DATA_LGTH(data_size);
|
||||
/* kick off transfer */
|
||||
bfin_write_SDH_DATA_CTL(bfin_read_SDH_DATA_CTL() | DTX_DMA_E | DTX_E);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int bfin_sdh_request(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
u32 status;
|
||||
int ret = 0;
|
||||
|
||||
if (data) {
|
||||
ret = sdh_setup_data(mmc, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sdh_send_cmd(mmc, cmd);
|
||||
if (ret) {
|
||||
bfin_write_SDH_COMMAND(0);
|
||||
bfin_write_DMA_CONFIG(0);
|
||||
bfin_write_SDH_DATA_CTL(0);
|
||||
SSYNC();
|
||||
printf("sending CMD%d failed\n", cmd->cmdidx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
do {
|
||||
udelay(1);
|
||||
status = bfin_read_SDH_STATUS();
|
||||
} while (!(status & (DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL |
|
||||
RX_OVERRUN)));
|
||||
|
||||
if (status & DAT_TIME_OUT) {
|
||||
bfin_write_SDH_STATUS_CLR(DAT_TIMEOUT_STAT);
|
||||
ret |= TIMEOUT;
|
||||
} else if (status & (DAT_CRC_FAIL | RX_OVERRUN)) {
|
||||
bfin_write_SDH_STATUS_CLR(DAT_CRC_FAIL_STAT | RX_OVERRUN_STAT);
|
||||
ret |= COMM_ERR;
|
||||
} else
|
||||
bfin_write_SDH_STATUS_CLR(DAT_BLK_END_STAT | DAT_END_STAT);
|
||||
|
||||
if (ret) {
|
||||
printf("tranfering data failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sdh_set_clk(unsigned long clk)
|
||||
{
|
||||
unsigned long sys_clk;
|
||||
unsigned long clk_div;
|
||||
u16 clk_ctl = 0;
|
||||
|
||||
clk_ctl = bfin_read_SDH_CLK_CTL();
|
||||
if (clk) {
|
||||
/* setting SD_CLK */
|
||||
sys_clk = get_sclk();
|
||||
bfin_write_SDH_CLK_CTL(clk_ctl & ~CLK_E);
|
||||
if (sys_clk % (2 * clk) == 0)
|
||||
clk_div = sys_clk / (2 * clk) - 1;
|
||||
else
|
||||
clk_div = sys_clk / (2 * clk);
|
||||
|
||||
if (clk_div > 0xff)
|
||||
clk_div = 0xff;
|
||||
clk_ctl |= (clk_div & 0xff);
|
||||
clk_ctl |= CLK_E;
|
||||
bfin_write_SDH_CLK_CTL(clk_ctl);
|
||||
} else
|
||||
bfin_write_SDH_CLK_CTL(clk_ctl & ~CLK_E);
|
||||
}
|
||||
|
||||
static void bfin_sdh_set_ios(struct mmc *mmc)
|
||||
{
|
||||
u16 cfg = 0;
|
||||
u16 clk_ctl = 0;
|
||||
|
||||
if (mmc->bus_width == 4) {
|
||||
cfg = bfin_read_SDH_CFG();
|
||||
#ifndef RSI_BLKSZ
|
||||
cfg &= ~PD_SDDAT3;
|
||||
#endif
|
||||
cfg |= PUP_SDDAT3;
|
||||
bfin_write_SDH_CFG(cfg);
|
||||
clk_ctl |= WIDE_BUS_4;
|
||||
}
|
||||
bfin_write_SDH_CLK_CTL(clk_ctl);
|
||||
sdh_set_clk(mmc->clock);
|
||||
}
|
||||
|
||||
static int bfin_sdh_init(struct mmc *mmc)
|
||||
{
|
||||
const unsigned short pins[] = PORTMUX_PINS;
|
||||
int ret;
|
||||
|
||||
/* Initialize sdh controller */
|
||||
ret = peripheral_request_list(pins, "bfin_sdh");
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
#if defined(__ADSPBF54x__)
|
||||
bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1);
|
||||
#endif
|
||||
bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN);
|
||||
/* Disable card detect pin */
|
||||
bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | 0x60);
|
||||
#ifndef RSI_BLKSZ
|
||||
bfin_write_SDH_PWR_CTL(PWR_ON | ROD_CTL);
|
||||
#else
|
||||
bfin_write_SDH_CFG(bfin_read_SDH_CFG() | PWR_ON);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops bfin_mmc_ops = {
|
||||
.send_cmd = bfin_sdh_request,
|
||||
.set_ios = bfin_sdh_set_ios,
|
||||
.init = bfin_sdh_init,
|
||||
};
|
||||
|
||||
static struct mmc_config bfin_mmc_cfg = {
|
||||
.name = "Blackfin SDH",
|
||||
.ops = &bfin_mmc_ops,
|
||||
.host_caps = MMC_MODE_4BIT,
|
||||
.voltages = MMC_VDD_32_33 | MMC_VDD_33_34,
|
||||
.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT,
|
||||
};
|
||||
|
||||
int bfin_mmc_init(bd_t *bis)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
|
||||
bfin_mmc_cfg.f_max = get_sclk();
|
||||
bfin_mmc_cfg.f_min = bfin_mmc_cfg.f_max >> 9;
|
||||
|
||||
mmc = mmc_create(&bfin_mmc_cfg, NULL);
|
||||
if (mmc == NULL)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
389
u-boot/drivers/mmc/davinci_mmc.c
Normal file
389
u-boot/drivers/mmc/davinci_mmc.c
Normal file
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* Davinci MMC Controller Driver
|
||||
*
|
||||
* Copyright (C) 2010 Texas Instruments Incorporated
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <common.h>
|
||||
#include <command.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 COMM_ERR;
|
||||
|
||||
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 COMM_ERR;
|
||||
|
||||
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 TIMEOUT;
|
||||
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 COMM_ERR;
|
||||
}
|
||||
udelay(10);
|
||||
|
||||
mmcstatus = get_val(®s->mmcst0);
|
||||
}
|
||||
|
||||
printf("Status %x Timeout ST0:%x ST1:%x\n", st_ready, mmcstatus,
|
||||
get_val(®s->mmcst1));
|
||||
return COMM_ERR;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 GENERIC_MMC framework */
|
||||
static void 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
498
u-boot/drivers/mmc/dw_mmc.c
Normal file
498
u-boot/drivers/mmc/dw_mmc.c
Normal file
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* (C) Copyright 2012 SAMSUNG Electronics
|
||||
* Jaehoon Chung <jh80.chung@samsung.com>
|
||||
* Rajeshawari Shinde <rajeshwari.s@samsung.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <bouncebuf.h>
|
||||
#include <common.h>
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include <memalign.h>
|
||||
#include <mmc.h>
|
||||
#include <dwmmc.h>
|
||||
#include <asm-generic/errno.h>
|
||||
|
||||
#define PAGE_SIZE 4096
|
||||
|
||||
static int dwmci_wait_reset(struct dwmci_host *host, u32 value)
|
||||
{
|
||||
unsigned long timeout = 1000;
|
||||
u32 ctrl;
|
||||
|
||||
dwmci_writel(host, DWMCI_CTRL, value);
|
||||
|
||||
while (timeout--) {
|
||||
ctrl = dwmci_readl(host, DWMCI_CTRL);
|
||||
if (!(ctrl & DWMCI_RESET_ALL))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dwmci_set_idma_desc(struct dwmci_idmac *idmac,
|
||||
u32 desc0, u32 desc1, u32 desc2)
|
||||
{
|
||||
struct dwmci_idmac *desc = idmac;
|
||||
|
||||
desc->flags = desc0;
|
||||
desc->cnt = desc1;
|
||||
desc->addr = desc2;
|
||||
desc->next_addr = (ulong)desc + sizeof(struct dwmci_idmac);
|
||||
}
|
||||
|
||||
static void dwmci_prepare_data(struct dwmci_host *host,
|
||||
struct mmc_data *data,
|
||||
struct dwmci_idmac *cur_idmac,
|
||||
void *bounce_buffer)
|
||||
{
|
||||
unsigned long ctrl;
|
||||
unsigned int i = 0, flags, cnt, blk_cnt;
|
||||
ulong data_start, data_end;
|
||||
|
||||
|
||||
blk_cnt = data->blocks;
|
||||
|
||||
dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET);
|
||||
|
||||
data_start = (ulong)cur_idmac;
|
||||
dwmci_writel(host, DWMCI_DBADDR, (ulong)cur_idmac);
|
||||
|
||||
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,
|
||||
(ulong)bounce_buffer + (i * PAGE_SIZE));
|
||||
|
||||
if (blk_cnt <= 8)
|
||||
break;
|
||||
blk_cnt -= 8;
|
||||
cur_idmac++;
|
||||
i++;
|
||||
} while(1);
|
||||
|
||||
data_end = (ulong)cur_idmac;
|
||||
flush_dcache_range(data_start, data_end + ARCH_DMA_MINALIGN);
|
||||
|
||||
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_data_transfer(struct dwmci_host *host, struct mmc_data *data)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 timeout = 240000;
|
||||
u32 mask, size, i, len = 0;
|
||||
u32 *buf = NULL;
|
||||
ulong start = get_timer(0);
|
||||
u32 fifo_depth = (((host->fifoth_val & RX_WMARK_MASK) >>
|
||||
RX_WMARK_SHIFT) + 1) * 2;
|
||||
|
||||
size = data->blocksize * data->blocks / 4;
|
||||
if (data->flags == MMC_DATA_READ)
|
||||
buf = (unsigned int *)data->dest;
|
||||
else
|
||||
buf = (unsigned int *)data->src;
|
||||
|
||||
for (;;) {
|
||||
mask = dwmci_readl(host, DWMCI_RINTSTS);
|
||||
/* Error during data transfer. */
|
||||
if (mask & (DWMCI_DATA_ERR | DWMCI_DATA_TOUT)) {
|
||||
debug("%s: DATA ERROR!\n", __func__);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (host->fifo_mode && size) {
|
||||
if (data->flags == MMC_DATA_READ) {
|
||||
if ((dwmci_readl(host, DWMCI_RINTSTS) &
|
||||
DWMCI_INTMSK_RXDR)) {
|
||||
len = dwmci_readl(host, DWMCI_STATUS);
|
||||
len = (len >> DWMCI_FIFO_SHIFT) &
|
||||
DWMCI_FIFO_MASK;
|
||||
for (i = 0; i < len; i++)
|
||||
*buf++ =
|
||||
dwmci_readl(host, DWMCI_DATA);
|
||||
dwmci_writel(host, DWMCI_RINTSTS,
|
||||
DWMCI_INTMSK_RXDR);
|
||||
}
|
||||
} else {
|
||||
if ((dwmci_readl(host, DWMCI_RINTSTS) &
|
||||
DWMCI_INTMSK_TXDR)) {
|
||||
len = dwmci_readl(host, DWMCI_STATUS);
|
||||
len = fifo_depth - ((len >>
|
||||
DWMCI_FIFO_SHIFT) &
|
||||
DWMCI_FIFO_MASK);
|
||||
for (i = 0; i < len; i++)
|
||||
dwmci_writel(host, DWMCI_DATA,
|
||||
*buf++);
|
||||
dwmci_writel(host, DWMCI_RINTSTS,
|
||||
DWMCI_INTMSK_TXDR);
|
||||
}
|
||||
}
|
||||
size = size > len ? (size - len) : 0;
|
||||
}
|
||||
|
||||
/* Data arrived correctly. */
|
||||
if (mask & DWMCI_INTMSK_DTO) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check for timeout. */
|
||||
if (get_timer(start) > timeout) {
|
||||
debug("%s: Timeout waiting for data!\n",
|
||||
__func__);
|
||||
ret = TIMEOUT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dwmci_writel(host, DWMCI_RINTSTS, mask);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dwmci_set_transfer_mode(struct dwmci_host *host,
|
||||
struct mmc_data *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(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct dwmci_host *host = mmc->priv;
|
||||
ALLOC_CACHE_ALIGN_BUFFER(struct dwmci_idmac, cur_idmac,
|
||||
data ? DIV_ROUND_UP(data->blocks, 8) : 0);
|
||||
int ret = 0, flags = 0, i;
|
||||
unsigned int timeout = 100000;
|
||||
u32 retry = 100000;
|
||||
u32 mask, ctrl;
|
||||
ulong start = get_timer(0);
|
||||
struct bounce_buffer bbstate;
|
||||
|
||||
while (dwmci_readl(host, DWMCI_STATUS) & DWMCI_BUSY) {
|
||||
if (get_timer(start) > timeout) {
|
||||
debug("%s: Timeout on data busy\n", __func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_ALL);
|
||||
|
||||
if (data) {
|
||||
if (host->fifo_mode) {
|
||||
dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize);
|
||||
dwmci_writel(host, DWMCI_BYTCNT,
|
||||
data->blocksize * data->blocks);
|
||||
dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET);
|
||||
} else {
|
||||
if (data->flags == MMC_DATA_READ) {
|
||||
bounce_buffer_start(&bbstate, (void*)data->dest,
|
||||
data->blocksize *
|
||||
data->blocks, GEN_BB_WRITE);
|
||||
} else {
|
||||
bounce_buffer_start(&bbstate, (void*)data->src,
|
||||
data->blocksize *
|
||||
data->blocks, GEN_BB_READ);
|
||||
}
|
||||
dwmci_prepare_data(host, data, cur_idmac,
|
||||
bbstate.bounce_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
debug("Sending CMD%d\n",cmd->cmdidx);
|
||||
|
||||
dwmci_writel(host, DWMCI_CMD, flags);
|
||||
|
||||
for (i = 0; i < retry; i++) {
|
||||
mask = dwmci_readl(host, DWMCI_RINTSTS);
|
||||
if (mask & DWMCI_INTMSK_CDONE) {
|
||||
if (!data)
|
||||
dwmci_writel(host, DWMCI_RINTSTS, mask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == retry) {
|
||||
debug("%s: Timeout.\n", __func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
if (mask & DWMCI_INTMSK_RTO) {
|
||||
/*
|
||||
* Timeout here is not necessarily fatal. (e)MMC cards
|
||||
* will splat here when they receive CMD55 as they do
|
||||
* not support this command and that is exactly the way
|
||||
* to tell them apart from SD cards. Thus, this output
|
||||
* below shall be debug(). eMMC cards also do not favor
|
||||
* CMD8, please keep that in mind.
|
||||
*/
|
||||
debug("%s: Response Timeout.\n", __func__);
|
||||
return TIMEOUT;
|
||||
} else if (mask & DWMCI_INTMSK_RE) {
|
||||
debug("%s: Response Error.\n", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
ret = dwmci_data_transfer(host, data);
|
||||
|
||||
/* only dma mode need it */
|
||||
if (!host->fifo_mode) {
|
||||
ctrl = dwmci_readl(host, DWMCI_CTRL);
|
||||
ctrl &= ~(DWMCI_DMA_EN);
|
||||
dwmci_writel(host, DWMCI_CTRL, ctrl);
|
||||
bounce_buffer_stop(&bbstate);
|
||||
}
|
||||
}
|
||||
|
||||
udelay(100);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dwmci_setup_bus(struct dwmci_host *host, u32 freq)
|
||||
{
|
||||
u32 div, status;
|
||||
int timeout = 10000;
|
||||
unsigned long sclk;
|
||||
|
||||
if ((freq == host->clock) || (freq == 0))
|
||||
return 0;
|
||||
/*
|
||||
* If host->get_mmc_clk isn't defined,
|
||||
* then assume that host->bus_hz is source clock value.
|
||||
* host->bus_hz should be set by user.
|
||||
*/
|
||||
if (host->get_mmc_clk)
|
||||
sclk = host->get_mmc_clk(host, freq);
|
||||
else if (host->bus_hz)
|
||||
sclk = host->bus_hz;
|
||||
else {
|
||||
debug("%s: Didn't get source clock value.\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (sclk == freq)
|
||||
div = 0; /* bypass mode */
|
||||
else
|
||||
div = DIV_ROUND_UP(sclk, 2 * freq);
|
||||
|
||||
dwmci_writel(host, DWMCI_CLKENA, 0);
|
||||
dwmci_writel(host, DWMCI_CLKSRC, 0);
|
||||
|
||||
dwmci_writel(host, DWMCI_CLKDIV, div);
|
||||
dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_PRV_DAT_WAIT |
|
||||
DWMCI_CMD_UPD_CLK | DWMCI_CMD_START);
|
||||
|
||||
do {
|
||||
status = dwmci_readl(host, DWMCI_CMD);
|
||||
if (timeout-- < 0) {
|
||||
debug("%s: Timeout!\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
} while (status & DWMCI_CMD_START);
|
||||
|
||||
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);
|
||||
|
||||
timeout = 10000;
|
||||
do {
|
||||
status = dwmci_readl(host, DWMCI_CMD);
|
||||
if (timeout-- < 0) {
|
||||
debug("%s: Timeout!\n", __func__);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
} while (status & DWMCI_CMD_START);
|
||||
|
||||
host->clock = freq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dwmci_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct dwmci_host *host = (struct dwmci_host *)mmc->priv;
|
||||
u32 ctype, regs;
|
||||
|
||||
debug("Buswidth = %d, clock: %d\n", mmc->bus_width, mmc->clock);
|
||||
|
||||
dwmci_setup_bus(host, mmc->clock);
|
||||
switch (mmc->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);
|
||||
|
||||
regs = dwmci_readl(host, DWMCI_UHS_REG);
|
||||
if (mmc->ddr_mode)
|
||||
regs |= DWMCI_DDR_MODE;
|
||||
else
|
||||
regs &= ~DWMCI_DDR_MODE;
|
||||
|
||||
dwmci_writel(host, DWMCI_UHS_REG, regs);
|
||||
|
||||
if (host->clksel)
|
||||
host->clksel(host);
|
||||
}
|
||||
|
||||
static int dwmci_init(struct mmc *mmc)
|
||||
{
|
||||
struct dwmci_host *host = mmc->priv;
|
||||
|
||||
if (host->board_init)
|
||||
host->board_init(host);
|
||||
|
||||
dwmci_writel(host, DWMCI_PWREN, 1);
|
||||
|
||||
if (!dwmci_wait_reset(host, DWMCI_RESET_ALL)) {
|
||||
debug("%s[%d] Fail-reset!!\n", __func__, __LINE__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Enumerate at 400KHz */
|
||||
dwmci_setup_bus(host, mmc->cfg->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);
|
||||
|
||||
if (!host->fifoth_val) {
|
||||
uint32_t fifo_size;
|
||||
|
||||
fifo_size = dwmci_readl(host, DWMCI_FIFOTH);
|
||||
fifo_size = ((fifo_size & RX_WMARK_MASK) >> RX_WMARK_SHIFT) + 1;
|
||||
host->fifoth_val = MSIZE(0x2) | RX_WMARK(fifo_size / 2 - 1) |
|
||||
TX_WMARK(fifo_size / 2);
|
||||
}
|
||||
dwmci_writel(host, DWMCI_FIFOTH, host->fifoth_val);
|
||||
|
||||
dwmci_writel(host, DWMCI_CLKENA, 0);
|
||||
dwmci_writel(host, DWMCI_CLKSRC, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops dwmci_ops = {
|
||||
.send_cmd = dwmci_send_cmd,
|
||||
.set_ios = dwmci_set_ios,
|
||||
.init = dwmci_init,
|
||||
};
|
||||
|
||||
void dwmci_setup_cfg(struct mmc_config *cfg, const char *name, int buswidth,
|
||||
uint caps, u32 max_clk, u32 min_clk)
|
||||
{
|
||||
cfg->name = name;
|
||||
cfg->ops = &dwmci_ops;
|
||||
cfg->f_min = min_clk;
|
||||
cfg->f_max = max_clk;
|
||||
|
||||
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
|
||||
|
||||
cfg->host_caps = caps;
|
||||
|
||||
if (buswidth == 8) {
|
||||
cfg->host_caps |= MMC_MODE_8BIT;
|
||||
cfg->host_caps &= ~MMC_MODE_4BIT;
|
||||
} else {
|
||||
cfg->host_caps |= MMC_MODE_4BIT;
|
||||
cfg->host_caps &= ~MMC_MODE_8BIT;
|
||||
}
|
||||
cfg->host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz;
|
||||
|
||||
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BLK
|
||||
int dwmci_bind(struct udevice *dev, struct mmc *mmc, struct mmc_config *cfg)
|
||||
{
|
||||
return mmc_bind(dev, mmc, cfg);
|
||||
}
|
||||
#else
|
||||
int add_dwmci(struct dwmci_host *host, u32 max_clk, u32 min_clk)
|
||||
{
|
||||
dwmci_setup_cfg(&host->cfg, host->name, host->buswidth, host->caps,
|
||||
max_clk, min_clk);
|
||||
|
||||
host->mmc = mmc_create(&host->cfg, host);
|
||||
if (host->mmc == NULL)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
289
u-boot/drivers/mmc/exynos_dw_mmc.c
Normal file
289
u-boot/drivers/mmc/exynos_dw_mmc.c
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* (C) Copyright 2012 SAMSUNG Electronics
|
||||
* Jaehoon Chung <jh80.chung@samsung.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dwmmc.h>
|
||||
#include <fdtdec.h>
|
||||
#include <libfdt.h>
|
||||
#include <malloc.h>
|
||||
#include <asm/arch/dwmmc.h>
|
||||
#include <asm/arch/clk.h>
|
||||
#include <asm/arch/pinmux.h>
|
||||
#include <asm/arch/power.h>
|
||||
#include <asm/gpio.h>
|
||||
#include <asm-generic/errno.h>
|
||||
|
||||
#define DWMMC_MAX_CH_NUM 4
|
||||
#define DWMMC_MAX_FREQ 52000000
|
||||
#define DWMMC_MIN_FREQ 400000
|
||||
#define DWMMC_MMC0_SDR_TIMING_VAL 0x03030001
|
||||
#define DWMMC_MMC2_SDR_TIMING_VAL 0x03020001
|
||||
|
||||
/* Exynos implmentation specific drver private data */
|
||||
struct dwmci_exynos_priv_data {
|
||||
u32 sdr_timing;
|
||||
};
|
||||
|
||||
/*
|
||||
* Function used as callback function to initialise the
|
||||
* CLKSEL register for every mmc channel.
|
||||
*/
|
||||
static void exynos_dwmci_clksel(struct dwmci_host *host)
|
||||
{
|
||||
struct dwmci_exynos_priv_data *priv = host->priv;
|
||||
|
||||
dwmci_writel(host, DWMCI_CLKSEL, priv->sdr_timing);
|
||||
}
|
||||
|
||||
unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq)
|
||||
{
|
||||
unsigned long sclk;
|
||||
int8_t clk_div;
|
||||
|
||||
/*
|
||||
* Since SDCLKIN is divided inside controller by the DIVRATIO
|
||||
* value set in the CLKSEL register, we need to use the same output
|
||||
* clock value to calculate the CLKDIV value.
|
||||
* as per user manual:cclk_in = SDCLKIN / (DIVRATIO + 1)
|
||||
*/
|
||||
clk_div = ((dwmci_readl(host, DWMCI_CLKSEL) >> DWMCI_DIVRATIO_BIT)
|
||||
& DWMCI_DIVRATIO_MASK) + 1;
|
||||
sclk = get_mmc_clk(host->dev_index);
|
||||
|
||||
/*
|
||||
* Assume to know divider value.
|
||||
* When clock unit is broken, need to set "host->div"
|
||||
*/
|
||||
return sclk / clk_div / (host->div + 1);
|
||||
}
|
||||
|
||||
static void exynos_dwmci_board_init(struct dwmci_host *host)
|
||||
{
|
||||
struct dwmci_exynos_priv_data *priv = host->priv;
|
||||
|
||||
if (host->quirks & DWMCI_QUIRK_DISABLE_SMU) {
|
||||
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);
|
||||
}
|
||||
|
||||
/* Set to timing value at initial time */
|
||||
if (priv->sdr_timing)
|
||||
exynos_dwmci_clksel(host);
|
||||
}
|
||||
|
||||
static int exynos_dwmci_core_init(struct dwmci_host *host, int index)
|
||||
{
|
||||
unsigned int div;
|
||||
unsigned long freq, sclk;
|
||||
struct dwmci_exynos_priv_data *priv = host->priv;
|
||||
|
||||
if (host->bus_hz)
|
||||
freq = host->bus_hz;
|
||||
else
|
||||
freq = DWMMC_MAX_FREQ;
|
||||
|
||||
/* request mmc clock vlaue of 52MHz. */
|
||||
sclk = get_mmc_clk(index);
|
||||
div = DIV_ROUND_UP(sclk, freq);
|
||||
/* set the clock divisor for mmc */
|
||||
set_mmc_clk(index, div);
|
||||
|
||||
host->name = "EXYNOS DWMMC";
|
||||
#ifdef CONFIG_EXYNOS5420
|
||||
host->quirks = DWMCI_QUIRK_DISABLE_SMU;
|
||||
#endif
|
||||
host->board_init = exynos_dwmci_board_init;
|
||||
|
||||
if (!priv->sdr_timing) {
|
||||
if (index == 0)
|
||||
priv->sdr_timing = DWMMC_MMC0_SDR_TIMING_VAL;
|
||||
else if (index == 2)
|
||||
priv->sdr_timing = DWMMC_MMC2_SDR_TIMING_VAL;
|
||||
}
|
||||
|
||||
host->caps = MMC_MODE_DDR_52MHz;
|
||||
host->clksel = exynos_dwmci_clksel;
|
||||
host->dev_index = index;
|
||||
host->get_mmc_clk = exynos_dwmci_get_clk;
|
||||
/* Add the mmc channel to be registered with mmc core */
|
||||
if (add_dwmci(host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ)) {
|
||||
printf("DWMMC%d registration failed\n", index);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function adds the mmc channel to be registered with mmc core.
|
||||
* index - mmc channel number.
|
||||
* regbase - register base address of mmc channel specified in 'index'.
|
||||
* bus_width - operating bus width of mmc channel specified in 'index'.
|
||||
* clksel - value to be written into CLKSEL register in case of FDT.
|
||||
* NULL in case od non-FDT.
|
||||
*/
|
||||
int exynos_dwmci_add_port(int index, u32 regbase, int bus_width, u32 clksel)
|
||||
{
|
||||
struct dwmci_host *host = NULL;
|
||||
struct dwmci_exynos_priv_data *priv;
|
||||
|
||||
host = malloc(sizeof(struct dwmci_host));
|
||||
if (!host) {
|
||||
error("dwmci_host malloc fail!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
priv = malloc(sizeof(struct dwmci_exynos_priv_data));
|
||||
if (!priv) {
|
||||
error("dwmci_exynos_priv_data malloc fail!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
host->ioaddr = (void *)regbase;
|
||||
host->buswidth = bus_width;
|
||||
|
||||
if (clksel)
|
||||
priv->sdr_timing = clksel;
|
||||
|
||||
host->priv = priv;
|
||||
|
||||
return exynos_dwmci_core_init(host, index);
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
static struct dwmci_host dwmci_host[DWMMC_MAX_CH_NUM];
|
||||
|
||||
static int do_dwmci_init(struct dwmci_host *host)
|
||||
{
|
||||
int index, flag, err;
|
||||
|
||||
index = host->dev_index;
|
||||
|
||||
flag = host->buswidth == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE;
|
||||
err = exynos_pinmux_config(host->dev_id, flag);
|
||||
if (err) {
|
||||
printf("DWMMC%d not configure\n", index);
|
||||
return err;
|
||||
}
|
||||
|
||||
return exynos_dwmci_core_init(host, index);
|
||||
}
|
||||
|
||||
static int exynos_dwmci_get_config(const void *blob, int node,
|
||||
struct dwmci_host *host)
|
||||
{
|
||||
int err = 0;
|
||||
u32 base, timing[3];
|
||||
struct dwmci_exynos_priv_data *priv;
|
||||
|
||||
priv = malloc(sizeof(struct dwmci_exynos_priv_data));
|
||||
if (!priv) {
|
||||
error("dwmci_exynos_priv_data malloc fail!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Extract device id for each mmc channel */
|
||||
host->dev_id = pinmux_decode_periph_id(blob, node);
|
||||
|
||||
host->dev_index = fdtdec_get_int(blob, node, "index", host->dev_id);
|
||||
if (host->dev_index == host->dev_id)
|
||||
host->dev_index = host->dev_id - PERIPH_ID_SDMMC0;
|
||||
|
||||
/* Get the bus width from the device node */
|
||||
host->buswidth = fdtdec_get_int(blob, node, "samsung,bus-width", 0);
|
||||
if (host->buswidth <= 0) {
|
||||
printf("DWMMC%d: Can't get bus-width\n", host->dev_index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Set the base address from the device node */
|
||||
base = fdtdec_get_addr(blob, node, "reg");
|
||||
if (!base) {
|
||||
printf("DWMMC%d: Can't get base address\n", host->dev_index);
|
||||
return -EINVAL;
|
||||
}
|
||||
host->ioaddr = (void *)base;
|
||||
|
||||
/* Extract the timing info from the node */
|
||||
err = fdtdec_get_int_array(blob, node, "samsung,timing", timing, 3);
|
||||
if (err) {
|
||||
printf("DWMMC%d: Can't get sdr-timings for devider\n",
|
||||
host->dev_index);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv->sdr_timing = (DWMCI_SET_SAMPLE_CLK(timing[0]) |
|
||||
DWMCI_SET_DRV_CLK(timing[1]) |
|
||||
DWMCI_SET_DIV_RATIO(timing[2]));
|
||||
|
||||
/* sdr_timing didn't assigned anything, use the default value */
|
||||
if (!priv->sdr_timing) {
|
||||
if (host->dev_index == 0)
|
||||
priv->sdr_timing = DWMMC_MMC0_SDR_TIMING_VAL;
|
||||
else if (host->dev_index == 2)
|
||||
priv->sdr_timing = DWMMC_MMC2_SDR_TIMING_VAL;
|
||||
}
|
||||
|
||||
host->fifoth_val = fdtdec_get_int(blob, node, "fifoth_val", 0);
|
||||
host->bus_hz = fdtdec_get_int(blob, node, "bus_hz", 0);
|
||||
host->div = fdtdec_get_int(blob, node, "div", 0);
|
||||
|
||||
host->priv = priv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos_dwmci_process_node(const void *blob,
|
||||
int node_list[], int count)
|
||||
{
|
||||
struct dwmci_host *host;
|
||||
int i, node, err;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
node = node_list[i];
|
||||
if (node <= 0)
|
||||
continue;
|
||||
host = &dwmci_host[i];
|
||||
err = exynos_dwmci_get_config(blob, node, host);
|
||||
if (err) {
|
||||
printf("%s: failed to decode dev %d\n", __func__, i);
|
||||
return err;
|
||||
}
|
||||
|
||||
do_dwmci_init(host);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_dwmmc_init(const void *blob)
|
||||
{
|
||||
int compat_id;
|
||||
int node_list[DWMMC_MAX_CH_NUM];
|
||||
int boot_dev_node;
|
||||
int err = 0, count;
|
||||
|
||||
compat_id = COMPAT_SAMSUNG_EXYNOS_DWMMC;
|
||||
|
||||
count = fdtdec_find_aliases_for_id(blob, "mmc",
|
||||
compat_id, node_list, DWMMC_MAX_CH_NUM);
|
||||
|
||||
/* For DWMMC always set boot device as mmc 0 */
|
||||
if (count >= 3 && get_boot_mode() == BOOT_MODE_SD) {
|
||||
boot_dev_node = node_list[2];
|
||||
node_list[2] = node_list[0];
|
||||
node_list[0] = boot_dev_node;
|
||||
}
|
||||
|
||||
err = exynos_dwmci_process_node(blob, node_list, count);
|
||||
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
1033
u-boot/drivers/mmc/fsl_esdhc.c
Normal file
1033
u-boot/drivers/mmc/fsl_esdhc.c
Normal file
File diff suppressed because it is too large
Load Diff
152
u-boot/drivers/mmc/fsl_esdhc_spl.c
Normal file
152
u-boot/drivers/mmc/fsl_esdhc_spl.c
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2013 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <mmc.h>
|
||||
#include <malloc.h>
|
||||
|
||||
/*
|
||||
* The environment variables are written to just after the u-boot image
|
||||
* on SDCard, so we must read the MBR to get the start address and code
|
||||
* length of the u-boot image, then calculate the address of the env.
|
||||
*/
|
||||
#define ESDHC_BOOT_IMAGE_SIZE 0x48
|
||||
#define ESDHC_BOOT_IMAGE_ADDR 0x50
|
||||
#define MBRDBR_BOOT_SIG_55 0x1fe
|
||||
#define MBRDBR_BOOT_SIG_AA 0x1ff
|
||||
#define CONFIG_CFG_DATA_SECTOR 0
|
||||
|
||||
|
||||
void mmc_spl_load_image(uint32_t offs, unsigned int size, void *vdst)
|
||||
{
|
||||
uint blk_start, blk_cnt, err;
|
||||
|
||||
struct mmc *mmc = find_mmc_device(0);
|
||||
if (!mmc) {
|
||||
puts("spl: mmc device not found!!\n");
|
||||
hang();
|
||||
}
|
||||
|
||||
if (mmc_init(mmc)) {
|
||||
puts("MMC init failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
blk_start = ALIGN(offs, mmc->read_bl_len) / mmc->read_bl_len;
|
||||
blk_cnt = ALIGN(size, mmc->read_bl_len) / mmc->read_bl_len;
|
||||
|
||||
err = mmc->block_dev.block_read(&mmc->block_dev, blk_start, blk_cnt,
|
||||
vdst);
|
||||
if (err != blk_cnt) {
|
||||
puts("spl: mmc read failed!!\n");
|
||||
hang();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The main entry for mmc booting. It's necessary that SDRAM is already
|
||||
* configured and available since this code loads the main U-Boot image
|
||||
* from mmc into SDRAM and starts it from there.
|
||||
*/
|
||||
|
||||
void __noreturn mmc_boot(void)
|
||||
{
|
||||
__attribute__((noreturn)) void (*uboot)(void);
|
||||
uint blk_start, blk_cnt, err;
|
||||
#ifndef CONFIG_FSL_CORENET
|
||||
uchar *tmp_buf;
|
||||
u32 blklen;
|
||||
uchar val;
|
||||
uint i, byte_num;
|
||||
#endif
|
||||
u32 offset, code_len;
|
||||
struct mmc *mmc;
|
||||
|
||||
mmc = find_mmc_device(0);
|
||||
if (!mmc) {
|
||||
puts("spl: mmc device not found!!\n");
|
||||
hang();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FSL_CORENET
|
||||
offset = CONFIG_SYS_MMC_U_BOOT_OFFS;
|
||||
code_len = CONFIG_SYS_MMC_U_BOOT_SIZE;
|
||||
#else
|
||||
blklen = mmc->read_bl_len;
|
||||
tmp_buf = malloc(blklen);
|
||||
if (!tmp_buf) {
|
||||
puts("spl: malloc memory failed!!\n");
|
||||
hang();
|
||||
}
|
||||
memset(tmp_buf, 0, blklen);
|
||||
|
||||
/*
|
||||
* Read source addr from sd card
|
||||
*/
|
||||
err = mmc->block_dev.block_read(&mmc->block_dev,
|
||||
CONFIG_CFG_DATA_SECTOR, 1, tmp_buf);
|
||||
if (err != 1) {
|
||||
puts("spl: mmc read failed!!\n");
|
||||
free(tmp_buf);
|
||||
hang();
|
||||
}
|
||||
|
||||
val = *(tmp_buf + MBRDBR_BOOT_SIG_55);
|
||||
if (0x55 != val) {
|
||||
puts("spl: mmc signature is not valid!!\n");
|
||||
free(tmp_buf);
|
||||
hang();
|
||||
}
|
||||
val = *(tmp_buf + MBRDBR_BOOT_SIG_AA);
|
||||
if (0xAA != val) {
|
||||
puts("spl: mmc signature is not valid!!\n");
|
||||
free(tmp_buf);
|
||||
hang();
|
||||
}
|
||||
|
||||
byte_num = 4;
|
||||
offset = 0;
|
||||
for (i = 0; i < byte_num; i++) {
|
||||
val = *(tmp_buf + ESDHC_BOOT_IMAGE_ADDR + i);
|
||||
offset = (offset << 8) + val;
|
||||
}
|
||||
offset += CONFIG_SYS_MMC_U_BOOT_OFFS;
|
||||
/* Get the code size from offset 0x48 */
|
||||
byte_num = 4;
|
||||
code_len = 0;
|
||||
for (i = 0; i < byte_num; i++) {
|
||||
val = *(tmp_buf + ESDHC_BOOT_IMAGE_SIZE + i);
|
||||
code_len = (code_len << 8) + val;
|
||||
}
|
||||
code_len -= CONFIG_SYS_MMC_U_BOOT_OFFS;
|
||||
/*
|
||||
* Load U-Boot image from mmc into RAM
|
||||
*/
|
||||
#endif
|
||||
blk_start = ALIGN(offset, mmc->read_bl_len) / mmc->read_bl_len;
|
||||
blk_cnt = ALIGN(code_len, mmc->read_bl_len) / mmc->read_bl_len;
|
||||
err = mmc->block_dev.block_read(&mmc->block_dev, blk_start, blk_cnt,
|
||||
(uchar *)CONFIG_SYS_MMC_U_BOOT_DST);
|
||||
if (err != blk_cnt) {
|
||||
puts("spl: mmc read failed!!\n");
|
||||
#ifndef CONFIG_FSL_CORENET
|
||||
free(tmp_buf);
|
||||
#endif
|
||||
hang();
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean d-cache and invalidate i-cache, to
|
||||
* make sure that no stale data is executed.
|
||||
*/
|
||||
flush_cache(CONFIG_SYS_MMC_U_BOOT_DST, CONFIG_SYS_MMC_U_BOOT_SIZE);
|
||||
|
||||
/*
|
||||
* Jump to U-Boot image
|
||||
*/
|
||||
uboot = (void *)CONFIG_SYS_MMC_U_BOOT_START;
|
||||
(*uboot)();
|
||||
}
|
||||
373
u-boot/drivers/mmc/ftsdc010_mci.c
Normal file
373
u-boot/drivers/mmc/ftsdc010_mci.c
Normal file
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* Faraday MMC/SD Host Controller
|
||||
*
|
||||
* (C) Copyright 2010 Faraday Technology
|
||||
* Dante Su <dantesu@faraday-tech.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <part.h>
|
||||
#include <mmc.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <faraday/ftsdc010.h>
|
||||
|
||||
#define CFG_CMD_TIMEOUT (CONFIG_SYS_HZ >> 4) /* 250 ms */
|
||||
#define CFG_RST_TIMEOUT CONFIG_SYS_HZ /* 1 sec reset timeout */
|
||||
|
||||
struct ftsdc010_chip {
|
||||
void __iomem *regs;
|
||||
uint32_t wprot; /* write protected (locked) */
|
||||
uint32_t rate; /* actual SD clock in Hz */
|
||||
uint32_t sclk; /* FTSDC010 source clock in Hz */
|
||||
uint32_t fifo; /* fifo depth in bytes */
|
||||
uint32_t acmd;
|
||||
struct mmc_config cfg; /* mmc configuration */
|
||||
};
|
||||
|
||||
static inline int ftsdc010_send_cmd(struct mmc *mmc, struct mmc_cmd *mmc_cmd)
|
||||
{
|
||||
struct ftsdc010_chip *chip = mmc->priv;
|
||||
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
||||
int ret = TIMEOUT;
|
||||
uint32_t ts, st;
|
||||
uint32_t cmd = FTSDC010_CMD_IDX(mmc_cmd->cmdidx);
|
||||
uint32_t arg = mmc_cmd->cmdarg;
|
||||
uint32_t flags = mmc_cmd->resp_type;
|
||||
|
||||
cmd |= FTSDC010_CMD_CMD_EN;
|
||||
|
||||
if (chip->acmd) {
|
||||
cmd |= FTSDC010_CMD_APP_CMD;
|
||||
chip->acmd = 0;
|
||||
}
|
||||
|
||||
if (flags & MMC_RSP_PRESENT)
|
||||
cmd |= FTSDC010_CMD_NEED_RSP;
|
||||
|
||||
if (flags & MMC_RSP_136)
|
||||
cmd |= FTSDC010_CMD_LONG_RSP;
|
||||
|
||||
writel(FTSDC010_STATUS_RSP_MASK | FTSDC010_STATUS_CMD_SEND,
|
||||
®s->clr);
|
||||
writel(arg, ®s->argu);
|
||||
writel(cmd, ®s->cmd);
|
||||
|
||||
if (!(flags & (MMC_RSP_PRESENT | MMC_RSP_136))) {
|
||||
for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) {
|
||||
if (readl(®s->status) & FTSDC010_STATUS_CMD_SEND) {
|
||||
writel(FTSDC010_STATUS_CMD_SEND, ®s->clr);
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
st = 0;
|
||||
for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) {
|
||||
st = readl(®s->status);
|
||||
writel(st & FTSDC010_STATUS_RSP_MASK, ®s->clr);
|
||||
if (st & FTSDC010_STATUS_RSP_MASK)
|
||||
break;
|
||||
}
|
||||
if (st & FTSDC010_STATUS_RSP_CRC_OK) {
|
||||
if (flags & MMC_RSP_136) {
|
||||
mmc_cmd->response[0] = readl(®s->rsp3);
|
||||
mmc_cmd->response[1] = readl(®s->rsp2);
|
||||
mmc_cmd->response[2] = readl(®s->rsp1);
|
||||
mmc_cmd->response[3] = readl(®s->rsp0);
|
||||
} else {
|
||||
mmc_cmd->response[0] = readl(®s->rsp0);
|
||||
}
|
||||
ret = 0;
|
||||
} else {
|
||||
debug("ftsdc010: rsp err (cmd=%d, st=0x%x)\n",
|
||||
mmc_cmd->cmdidx, st);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
debug("ftsdc010: cmd timeout (op code=%d)\n",
|
||||
mmc_cmd->cmdidx);
|
||||
} else if (mmc_cmd->cmdidx == MMC_CMD_APP_CMD) {
|
||||
chip->acmd = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ftsdc010_clkset(struct mmc *mmc, uint32_t rate)
|
||||
{
|
||||
struct ftsdc010_chip *chip = mmc->priv;
|
||||
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
||||
uint32_t div;
|
||||
|
||||
for (div = 0; div < 0x7f; ++div) {
|
||||
if (rate >= chip->sclk / (2 * (div + 1)))
|
||||
break;
|
||||
}
|
||||
chip->rate = chip->sclk / (2 * (div + 1));
|
||||
|
||||
writel(FTSDC010_CCR_CLK_DIV(div), ®s->ccr);
|
||||
|
||||
if (IS_SD(mmc)) {
|
||||
setbits_le32(®s->ccr, FTSDC010_CCR_CLK_SD);
|
||||
|
||||
if (chip->rate > 25000000)
|
||||
setbits_le32(®s->ccr, FTSDC010_CCR_CLK_HISPD);
|
||||
else
|
||||
clrbits_le32(®s->ccr, FTSDC010_CCR_CLK_HISPD);
|
||||
}
|
||||
}
|
||||
|
||||
static int ftsdc010_wait(struct ftsdc010_mmc __iomem *regs, uint32_t mask)
|
||||
{
|
||||
int ret = TIMEOUT;
|
||||
uint32_t st, ts;
|
||||
|
||||
for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) {
|
||||
st = readl(®s->status);
|
||||
if (!(st & mask))
|
||||
continue;
|
||||
writel(st & mask, ®s->clr);
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
debug("ftsdc010: wait st(0x%x) timeout\n", mask);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* u-boot mmc api
|
||||
*/
|
||||
|
||||
static int ftsdc010_request(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
int ret = UNUSABLE_ERR;
|
||||
uint32_t len = 0;
|
||||
struct ftsdc010_chip *chip = mmc->priv;
|
||||
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
||||
|
||||
if (data && (data->flags & MMC_DATA_WRITE) && chip->wprot) {
|
||||
printf("ftsdc010: the card is write protected!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
uint32_t dcr;
|
||||
|
||||
len = data->blocksize * data->blocks;
|
||||
|
||||
/* 1. data disable + fifo reset */
|
||||
dcr = 0;
|
||||
#ifdef CONFIG_FTSDC010_SDIO
|
||||
dcr |= FTSDC010_DCR_FIFO_RST;
|
||||
#endif
|
||||
writel(dcr, ®s->dcr);
|
||||
|
||||
/* 2. clear status register */
|
||||
writel(FTSDC010_STATUS_DATA_MASK | FTSDC010_STATUS_FIFO_URUN
|
||||
| FTSDC010_STATUS_FIFO_ORUN, ®s->clr);
|
||||
|
||||
/* 3. data timeout (1 sec) */
|
||||
writel(chip->rate, ®s->dtr);
|
||||
|
||||
/* 4. data length (bytes) */
|
||||
writel(len, ®s->dlr);
|
||||
|
||||
/* 5. data enable */
|
||||
dcr = (ffs(data->blocksize) - 1) | FTSDC010_DCR_DATA_EN;
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
dcr |= FTSDC010_DCR_DATA_WRITE;
|
||||
writel(dcr, ®s->dcr);
|
||||
}
|
||||
|
||||
ret = ftsdc010_send_cmd(mmc, cmd);
|
||||
if (ret) {
|
||||
printf("ftsdc010: CMD%d failed\n", cmd->cmdidx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!data)
|
||||
return ret;
|
||||
|
||||
if (data->flags & MMC_DATA_WRITE) {
|
||||
const uint8_t *buf = (const uint8_t *)data->src;
|
||||
|
||||
while (len > 0) {
|
||||
int wlen;
|
||||
|
||||
/* wait for tx ready */
|
||||
ret = ftsdc010_wait(regs, FTSDC010_STATUS_FIFO_URUN);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
/* write bytes to ftsdc010 */
|
||||
for (wlen = 0; wlen < len && wlen < chip->fifo; ) {
|
||||
writel(*(uint32_t *)buf, ®s->dwr);
|
||||
buf += 4;
|
||||
wlen += 4;
|
||||
}
|
||||
|
||||
len -= wlen;
|
||||
}
|
||||
|
||||
} else {
|
||||
uint8_t *buf = (uint8_t *)data->dest;
|
||||
|
||||
while (len > 0) {
|
||||
int rlen;
|
||||
|
||||
/* wait for rx ready */
|
||||
ret = ftsdc010_wait(regs, FTSDC010_STATUS_FIFO_ORUN);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
/* fetch bytes from ftsdc010 */
|
||||
for (rlen = 0; rlen < len && rlen < chip->fifo; ) {
|
||||
*(uint32_t *)buf = readl(®s->dwr);
|
||||
buf += 4;
|
||||
rlen += 4;
|
||||
}
|
||||
|
||||
len -= rlen;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
ret = ftsdc010_wait(regs,
|
||||
FTSDC010_STATUS_DATA_END | FTSDC010_STATUS_DATA_ERROR);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ftsdc010_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct ftsdc010_chip *chip = mmc->priv;
|
||||
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
||||
|
||||
ftsdc010_clkset(mmc, mmc->clock);
|
||||
|
||||
clrbits_le32(®s->bwr, FTSDC010_BWR_MODE_MASK);
|
||||
switch (mmc->bus_width) {
|
||||
case 4:
|
||||
setbits_le32(®s->bwr, FTSDC010_BWR_MODE_4BIT);
|
||||
break;
|
||||
case 8:
|
||||
setbits_le32(®s->bwr, FTSDC010_BWR_MODE_8BIT);
|
||||
break;
|
||||
default:
|
||||
setbits_le32(®s->bwr, FTSDC010_BWR_MODE_1BIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int ftsdc010_init(struct mmc *mmc)
|
||||
{
|
||||
struct ftsdc010_chip *chip = mmc->priv;
|
||||
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
||||
uint32_t ts;
|
||||
|
||||
if (readl(®s->status) & FTSDC010_STATUS_CARD_DETECT)
|
||||
return NO_CARD_ERR;
|
||||
|
||||
if (readl(®s->status) & FTSDC010_STATUS_WRITE_PROT) {
|
||||
printf("ftsdc010: write protected\n");
|
||||
chip->wprot = 1;
|
||||
}
|
||||
|
||||
chip->fifo = (readl(®s->feature) & 0xff) << 2;
|
||||
|
||||
/* 1. chip reset */
|
||||
writel(FTSDC010_CMD_SDC_RST, ®s->cmd);
|
||||
for (ts = get_timer(0); get_timer(ts) < CFG_RST_TIMEOUT; ) {
|
||||
if (readl(®s->cmd) & FTSDC010_CMD_SDC_RST)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (readl(®s->cmd) & FTSDC010_CMD_SDC_RST) {
|
||||
printf("ftsdc010: reset failed\n");
|
||||
return UNUSABLE_ERR;
|
||||
}
|
||||
|
||||
/* 2. enter low speed mode (400k card detection) */
|
||||
ftsdc010_clkset(mmc, 400000);
|
||||
|
||||
/* 3. interrupt disabled */
|
||||
writel(0, ®s->int_mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops ftsdc010_ops = {
|
||||
.send_cmd = ftsdc010_request,
|
||||
.set_ios = ftsdc010_set_ios,
|
||||
.init = ftsdc010_init,
|
||||
};
|
||||
|
||||
int ftsdc010_mmc_init(int devid)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
struct ftsdc010_chip *chip;
|
||||
struct ftsdc010_mmc __iomem *regs;
|
||||
#ifdef CONFIG_FTSDC010_BASE_LIST
|
||||
uint32_t base_list[] = CONFIG_FTSDC010_BASE_LIST;
|
||||
|
||||
if (devid < 0 || devid >= ARRAY_SIZE(base_list))
|
||||
return -1;
|
||||
regs = (void __iomem *)base_list[devid];
|
||||
#else
|
||||
regs = (void __iomem *)(CONFIG_FTSDC010_BASE + (devid << 20));
|
||||
#endif
|
||||
|
||||
chip = malloc(sizeof(struct ftsdc010_chip));
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
memset(chip, 0, sizeof(struct ftsdc010_chip));
|
||||
|
||||
chip->regs = regs;
|
||||
#ifdef CONFIG_SYS_CLK_FREQ
|
||||
chip->sclk = CONFIG_SYS_CLK_FREQ;
|
||||
#else
|
||||
chip->sclk = clk_get_rate("SDC");
|
||||
#endif
|
||||
|
||||
chip->cfg.name = "ftsdc010";
|
||||
chip->cfg.ops = &ftsdc010_ops;
|
||||
chip->cfg.host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz;
|
||||
switch (readl(®s->bwr) & FTSDC010_BWR_CAPS_MASK) {
|
||||
case FTSDC010_BWR_CAPS_4BIT:
|
||||
chip->cfg.host_caps |= MMC_MODE_4BIT;
|
||||
break;
|
||||
case FTSDC010_BWR_CAPS_8BIT:
|
||||
chip->cfg.host_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
chip->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
chip->cfg.f_max = chip->sclk / 2;
|
||||
chip->cfg.f_min = chip->sclk / 0x100;
|
||||
|
||||
chip->cfg.part_type = PART_TYPE_DOS;
|
||||
chip->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
|
||||
mmc = mmc_create(&chip->cfg, chip);
|
||||
if (mmc == NULL) {
|
||||
free(chip);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
33
u-boot/drivers/mmc/ftsdc021_sdhci.c
Normal file
33
u-boot/drivers/mmc/ftsdc021_sdhci.c
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* (C) Copyright 2013 Faraday Technology
|
||||
* Kuo-Jung Su <dantesu@faraday-tech.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
|
||||
#ifndef CONFIG_FTSDC021_CLOCK
|
||||
#define CONFIG_FTSDC021_CLOCK clk_get_rate("MMC")
|
||||
#endif
|
||||
|
||||
int ftsdc021_sdhci_init(u32 regbase)
|
||||
{
|
||||
struct sdhci_host *host = NULL;
|
||||
uint32_t freq = CONFIG_FTSDC021_CLOCK;
|
||||
|
||||
host = calloc(1, sizeof(struct sdhci_host));
|
||||
if (!host) {
|
||||
puts("sdh_host malloc fail!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
host->name = "FTSDC021";
|
||||
host->ioaddr = (void __iomem *)regbase;
|
||||
host->quirks = 0;
|
||||
add_sdhci(host, freq, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
456
u-boot/drivers/mmc/gen_atmel_mci.c
Normal file
456
u-boot/drivers/mmc/gen_atmel_mci.c
Normal file
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* Copyright (C) 2010
|
||||
* Rob Emanuele <rob@emanuele.us>
|
||||
* Reinhard Meyer, EMK Elektronik <reinhard.meyer@emk-elektronik.de>
|
||||
*
|
||||
* Original Driver:
|
||||
* Copyright (C) 2004-2006 Atmel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <mmc.h>
|
||||
#include <part.h>
|
||||
#include <malloc.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <asm/arch/clk.h>
|
||||
#include <asm/arch/hardware.h>
|
||||
#include "atmel_mci.h"
|
||||
|
||||
#ifndef CONFIG_SYS_MMC_CLK_OD
|
||||
# define CONFIG_SYS_MMC_CLK_OD 150000
|
||||
#endif
|
||||
|
||||
#define MMC_DEFAULT_BLKLEN 512
|
||||
|
||||
#if defined(CONFIG_ATMEL_MCI_PORTB)
|
||||
# define MCI_BUS 1
|
||||
#else
|
||||
# define MCI_BUS 0
|
||||
#endif
|
||||
|
||||
struct atmel_mci_priv {
|
||||
struct mmc_config cfg;
|
||||
struct atmel_mci *mci;
|
||||
unsigned int initialized:1;
|
||||
unsigned int curr_clk;
|
||||
};
|
||||
|
||||
/* Read Atmel MCI IP version */
|
||||
static unsigned int atmel_mci_get_version(struct atmel_mci *mci)
|
||||
{
|
||||
return readl(&mci->version) & 0x00000fff;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print command and status:
|
||||
*
|
||||
* - always when DEBUG is defined
|
||||
* - on command errors
|
||||
*/
|
||||
static void dump_cmd(u32 cmdr, u32 arg, u32 status, const char* msg)
|
||||
{
|
||||
debug("gen_atmel_mci: CMDR %08x (%2u) ARGR %08x (SR: %08x) %s\n",
|
||||
cmdr, cmdr & 0x3F, arg, status, msg);
|
||||
}
|
||||
|
||||
/* Setup for MCI Clock and Block Size */
|
||||
static void mci_set_mode(struct mmc *mmc, u32 hz, u32 blklen)
|
||||
{
|
||||
struct atmel_mci_priv *priv = mmc->priv;
|
||||
atmel_mci_t *mci = priv->mci;
|
||||
u32 bus_hz = get_mci_clk_rate();
|
||||
u32 clkdiv = 255;
|
||||
unsigned int version = atmel_mci_get_version(mci);
|
||||
u32 clkodd = 0;
|
||||
u32 mr;
|
||||
|
||||
debug("mci: bus_hz is %u, setting clock %u Hz, block size %u\n",
|
||||
bus_hz, hz, blklen);
|
||||
if (hz > 0) {
|
||||
if (version >= 0x500) {
|
||||
clkdiv = DIV_ROUND_UP(bus_hz, hz) - 2;
|
||||
if (clkdiv > 511)
|
||||
clkdiv = 511;
|
||||
|
||||
clkodd = clkdiv & 1;
|
||||
clkdiv >>= 1;
|
||||
|
||||
debug("mci: setting clock %u Hz, block size %u\n",
|
||||
bus_hz / (clkdiv * 2 + clkodd + 2), blklen);
|
||||
} else {
|
||||
/* find clkdiv yielding a rate <= than requested */
|
||||
for (clkdiv = 0; clkdiv < 255; clkdiv++) {
|
||||
if ((bus_hz / (clkdiv + 1) / 2) <= hz)
|
||||
break;
|
||||
}
|
||||
debug("mci: setting clock %u Hz, block size %u\n",
|
||||
(bus_hz / (clkdiv + 1)) / 2, blklen);
|
||||
|
||||
}
|
||||
}
|
||||
if (version >= 0x500)
|
||||
priv->curr_clk = bus_hz / (clkdiv * 2 + clkodd + 2);
|
||||
else
|
||||
priv->curr_clk = (bus_hz / (clkdiv + 1)) / 2;
|
||||
blklen &= 0xfffc;
|
||||
|
||||
mr = MMCI_BF(CLKDIV, clkdiv);
|
||||
|
||||
/* MCI IP version >= 0x200 has R/WPROOF */
|
||||
if (version >= 0x200)
|
||||
mr |= MMCI_BIT(RDPROOF) | MMCI_BIT(WRPROOF);
|
||||
|
||||
/*
|
||||
* MCI IP version >= 0x500 use bit 16 as clkodd.
|
||||
* MCI IP version < 0x500 use upper 16 bits for blklen.
|
||||
*/
|
||||
if (version >= 0x500)
|
||||
mr |= MMCI_BF(CLKODD, clkodd);
|
||||
else
|
||||
mr |= MMCI_BF(BLKLEN, blklen);
|
||||
|
||||
writel(mr, &mci->mr);
|
||||
|
||||
/* MCI IP version >= 0x200 has blkr */
|
||||
if (version >= 0x200)
|
||||
writel(MMCI_BF(BLKLEN, blklen), &mci->blkr);
|
||||
|
||||
if (mmc->card_caps & mmc->cfg->host_caps & MMC_MODE_HS)
|
||||
writel(MMCI_BIT(HSMODE), &mci->cfg);
|
||||
|
||||
priv->initialized = 1;
|
||||
}
|
||||
|
||||
/* Return the CMDR with flags for a given command and data packet */
|
||||
static u32 mci_encode_cmd(
|
||||
struct mmc_cmd *cmd, struct mmc_data *data, u32* error_flags)
|
||||
{
|
||||
u32 cmdr = 0;
|
||||
|
||||
/* Default Flags for Errors */
|
||||
*error_flags |= (MMCI_BIT(DTOE) | MMCI_BIT(RDIRE) | MMCI_BIT(RENDE) |
|
||||
MMCI_BIT(RINDE) | MMCI_BIT(RTOE));
|
||||
|
||||
/* Default Flags for the Command */
|
||||
cmdr |= MMCI_BIT(MAXLAT);
|
||||
|
||||
if (data) {
|
||||
cmdr |= MMCI_BF(TRCMD, 1);
|
||||
if (data->blocks > 1)
|
||||
cmdr |= MMCI_BF(TRTYP, 1);
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
cmdr |= MMCI_BIT(TRDIR);
|
||||
}
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_CRC)
|
||||
*error_flags |= MMCI_BIT(RCRCE);
|
||||
if (cmd->resp_type & MMC_RSP_136)
|
||||
cmdr |= MMCI_BF(RSPTYP, 2);
|
||||
else if (cmd->resp_type & MMC_RSP_BUSY)
|
||||
cmdr |= MMCI_BF(RSPTYP, 3);
|
||||
else if (cmd->resp_type & MMC_RSP_PRESENT)
|
||||
cmdr |= MMCI_BF(RSPTYP, 1);
|
||||
|
||||
return cmdr | MMCI_BF(CMDNB, cmd->cmdidx);
|
||||
}
|
||||
|
||||
/* Entered into function pointer in mci_send_cmd */
|
||||
static u32 mci_data_read(atmel_mci_t *mci, u32* data, u32 error_flags)
|
||||
{
|
||||
u32 status;
|
||||
|
||||
do {
|
||||
status = readl(&mci->sr);
|
||||
if (status & (error_flags | MMCI_BIT(OVRE)))
|
||||
goto io_fail;
|
||||
} while (!(status & MMCI_BIT(RXRDY)));
|
||||
|
||||
if (status & MMCI_BIT(RXRDY)) {
|
||||
*data = readl(&mci->rdr);
|
||||
status = 0;
|
||||
}
|
||||
io_fail:
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Entered into function pointer in mci_send_cmd */
|
||||
static u32 mci_data_write(atmel_mci_t *mci, u32* data, u32 error_flags)
|
||||
{
|
||||
u32 status;
|
||||
|
||||
do {
|
||||
status = readl(&mci->sr);
|
||||
if (status & (error_flags | MMCI_BIT(UNRE)))
|
||||
goto io_fail;
|
||||
} while (!(status & MMCI_BIT(TXRDY)));
|
||||
|
||||
if (status & MMCI_BIT(TXRDY)) {
|
||||
writel(*data, &mci->tdr);
|
||||
status = 0;
|
||||
}
|
||||
io_fail:
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Entered into mmc structure during driver init
|
||||
*
|
||||
* Sends a command out on the bus and deals with the block data.
|
||||
* Takes the mmc pointer, a command pointer, and an optional data pointer.
|
||||
*/
|
||||
static int
|
||||
mci_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
|
||||
{
|
||||
struct atmel_mci_priv *priv = mmc->priv;
|
||||
atmel_mci_t *mci = priv->mci;
|
||||
u32 cmdr;
|
||||
u32 error_flags = 0;
|
||||
u32 status;
|
||||
|
||||
if (!priv->initialized) {
|
||||
puts ("MCI not initialized!\n");
|
||||
return COMM_ERR;
|
||||
}
|
||||
|
||||
/* Figure out the transfer arguments */
|
||||
cmdr = mci_encode_cmd(cmd, data, &error_flags);
|
||||
|
||||
/* For multi blocks read/write, set the block register */
|
||||
if ((cmd->cmdidx == MMC_CMD_READ_MULTIPLE_BLOCK)
|
||||
|| (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK))
|
||||
writel(data->blocks | MMCI_BF(BLKLEN, mmc->read_bl_len),
|
||||
&mci->blkr);
|
||||
|
||||
/* Send the command */
|
||||
writel(cmd->cmdarg, &mci->argr);
|
||||
writel(cmdr, &mci->cmdr);
|
||||
|
||||
#ifdef DEBUG
|
||||
dump_cmd(cmdr, cmd->cmdarg, 0, "DEBUG");
|
||||
#endif
|
||||
|
||||
/* Wait for the command to complete */
|
||||
while (!((status = readl(&mci->sr)) & MMCI_BIT(CMDRDY)));
|
||||
|
||||
if ((status & error_flags) & MMCI_BIT(RTOE)) {
|
||||
dump_cmd(cmdr, cmd->cmdarg, status, "Command Time Out");
|
||||
return TIMEOUT;
|
||||
} else if (status & error_flags) {
|
||||
dump_cmd(cmdr, cmd->cmdarg, status, "Command Failed");
|
||||
return COMM_ERR;
|
||||
}
|
||||
|
||||
/* Copy the response to the response buffer */
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
cmd->response[0] = readl(&mci->rspr);
|
||||
cmd->response[1] = readl(&mci->rspr1);
|
||||
cmd->response[2] = readl(&mci->rspr2);
|
||||
cmd->response[3] = readl(&mci->rspr3);
|
||||
} else
|
||||
cmd->response[0] = readl(&mci->rspr);
|
||||
|
||||
/* transfer all of the blocks */
|
||||
if (data) {
|
||||
u32 word_count, block_count;
|
||||
u32* ioptr;
|
||||
u32 sys_blocksize, dummy, i;
|
||||
u32 (*mci_data_op)
|
||||
(atmel_mci_t *mci, u32* data, u32 error_flags);
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
mci_data_op = mci_data_read;
|
||||
sys_blocksize = mmc->read_bl_len;
|
||||
ioptr = (u32*)data->dest;
|
||||
} else {
|
||||
mci_data_op = mci_data_write;
|
||||
sys_blocksize = mmc->write_bl_len;
|
||||
ioptr = (u32*)data->src;
|
||||
}
|
||||
|
||||
status = 0;
|
||||
for (block_count = 0;
|
||||
block_count < data->blocks && !status;
|
||||
block_count++) {
|
||||
word_count = 0;
|
||||
do {
|
||||
status = mci_data_op(mci, ioptr, error_flags);
|
||||
word_count++;
|
||||
ioptr++;
|
||||
} while (!status && word_count < (data->blocksize/4));
|
||||
#ifdef DEBUG
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
{
|
||||
u32 cnt = word_count * 4;
|
||||
printf("Read Data:\n");
|
||||
print_buffer(0, data->dest + cnt * block_count,
|
||||
1, cnt, 0);
|
||||
}
|
||||
#endif
|
||||
#ifdef DEBUG
|
||||
if (!status && word_count < (sys_blocksize / 4))
|
||||
printf("filling rest of block...\n");
|
||||
#endif
|
||||
/* fill the rest of a full block */
|
||||
while (!status && word_count < (sys_blocksize / 4)) {
|
||||
status = mci_data_op(mci, &dummy,
|
||||
error_flags);
|
||||
word_count++;
|
||||
}
|
||||
if (status) {
|
||||
dump_cmd(cmdr, cmd->cmdarg, status,
|
||||
"Data Transfer Failed");
|
||||
return COMM_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for Transfer End */
|
||||
i = 0;
|
||||
do {
|
||||
status = readl(&mci->sr);
|
||||
|
||||
if (status & error_flags) {
|
||||
dump_cmd(cmdr, cmd->cmdarg, status,
|
||||
"DTIP Wait Failed");
|
||||
return COMM_ERR;
|
||||
}
|
||||
i++;
|
||||
} while ((status & MMCI_BIT(DTIP)) && i < 10000);
|
||||
if (status & MMCI_BIT(DTIP)) {
|
||||
dump_cmd(cmdr, cmd->cmdarg, status,
|
||||
"XFER DTIP never unset, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* After the switch command, wait for 8 clocks before the next
|
||||
* command
|
||||
*/
|
||||
if (cmd->cmdidx == MMC_CMD_SWITCH)
|
||||
udelay(8*1000000 / priv->curr_clk); /* 8 clk in us */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Entered into mmc structure during driver init */
|
||||
static void mci_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct atmel_mci_priv *priv = mmc->priv;
|
||||
atmel_mci_t *mci = priv->mci;
|
||||
int bus_width = mmc->bus_width;
|
||||
unsigned int version = atmel_mci_get_version(mci);
|
||||
int busw;
|
||||
|
||||
/* Set the clock speed */
|
||||
mci_set_mode(mmc, mmc->clock, MMC_DEFAULT_BLKLEN);
|
||||
|
||||
/*
|
||||
* set the bus width and select slot for this interface
|
||||
* there is no capability for multiple slots on the same interface yet
|
||||
*/
|
||||
if ((version & 0xf00) >= 0x300) {
|
||||
switch (bus_width) {
|
||||
case 8:
|
||||
busw = 3;
|
||||
break;
|
||||
case 4:
|
||||
busw = 2;
|
||||
break;
|
||||
default:
|
||||
busw = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
writel(busw << 6 | MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr);
|
||||
} else {
|
||||
busw = (bus_width == 4) ? 1 : 0;
|
||||
|
||||
writel(busw << 7 | MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Entered into mmc structure during driver init */
|
||||
static int mci_init(struct mmc *mmc)
|
||||
{
|
||||
struct atmel_mci_priv *priv = mmc->priv;
|
||||
atmel_mci_t *mci = priv->mci;
|
||||
|
||||
/* Initialize controller */
|
||||
writel(MMCI_BIT(SWRST), &mci->cr); /* soft reset */
|
||||
writel(MMCI_BIT(PWSDIS), &mci->cr); /* disable power save */
|
||||
writel(MMCI_BIT(MCIEN), &mci->cr); /* enable mci */
|
||||
writel(MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr); /* select port */
|
||||
|
||||
/* This delay can be optimized, but stick with max value */
|
||||
writel(0x7f, &mci->dtor);
|
||||
/* Disable Interrupts */
|
||||
writel(~0UL, &mci->idr);
|
||||
|
||||
/* Set default clocks and blocklen */
|
||||
mci_set_mode(mmc, CONFIG_SYS_MMC_CLK_OD, MMC_DEFAULT_BLKLEN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops atmel_mci_ops = {
|
||||
.send_cmd = mci_send_cmd,
|
||||
.set_ios = mci_set_ios,
|
||||
.init = mci_init,
|
||||
};
|
||||
|
||||
/*
|
||||
* This is the only exported function
|
||||
*
|
||||
* Call it with the MCI register base address
|
||||
*/
|
||||
int atmel_mci_init(void *regs)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
struct mmc_config *cfg;
|
||||
struct atmel_mci_priv *priv;
|
||||
unsigned int version;
|
||||
|
||||
priv = calloc(1, sizeof(*priv));
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
cfg = &priv->cfg;
|
||||
|
||||
cfg->name = "mci";
|
||||
cfg->ops = &atmel_mci_ops;
|
||||
|
||||
priv->mci = (struct atmel_mci *)regs;
|
||||
priv->initialized = 0;
|
||||
|
||||
/* need to be able to pass these in on a board by board basis */
|
||||
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
version = atmel_mci_get_version(priv->mci);
|
||||
if ((version & 0xf00) >= 0x300) {
|
||||
cfg->host_caps = MMC_MODE_8BIT;
|
||||
cfg->host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz;
|
||||
}
|
||||
|
||||
cfg->host_caps |= MMC_MODE_4BIT;
|
||||
|
||||
/*
|
||||
* min and max frequencies determined by
|
||||
* max and min of clock divider
|
||||
*/
|
||||
cfg->f_min = get_mci_clk_rate() / (2*256);
|
||||
cfg->f_max = get_mci_clk_rate() / (2*1);
|
||||
|
||||
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
|
||||
mmc = mmc_create(cfg, priv);
|
||||
|
||||
if (mmc == NULL) {
|
||||
free(priv);
|
||||
return -ENODEV;
|
||||
}
|
||||
/* NOTE: possibly leaking the priv structure */
|
||||
|
||||
return 0;
|
||||
}
|
||||
56
u-boot/drivers/mmc/hi6220_dw_mmc.c
Normal file
56
u-boot/drivers/mmc/hi6220_dw_mmc.c
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* (C) Copyright 2015 Linaro
|
||||
* peter.griffin <peter.griffin@linaro.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dwmmc.h>
|
||||
#include <malloc.h>
|
||||
#include <asm-generic/errno.h>
|
||||
|
||||
#define DWMMC_MAX_CH_NUM 4
|
||||
|
||||
#define DWMMC_MAX_FREQ 50000000
|
||||
#define DWMMC_MIN_FREQ 400000
|
||||
|
||||
/* Source clock is configured to 100MHz by ATF bl1*/
|
||||
#define MMC0_DEFAULT_FREQ 100000000
|
||||
|
||||
static int hi6220_dwmci_core_init(struct dwmci_host *host, int index)
|
||||
{
|
||||
host->name = "HiKey DWMMC";
|
||||
|
||||
host->dev_index = index;
|
||||
|
||||
/* Add the mmc channel to be registered with mmc core */
|
||||
if (add_dwmci(host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ)) {
|
||||
printf("DWMMC%d registration failed\n", index);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function adds the mmc channel to be registered with mmc core.
|
||||
* index - mmc channel number.
|
||||
* regbase - register base address of mmc channel specified in 'index'.
|
||||
* bus_width - operating bus width of mmc channel specified in 'index'.
|
||||
*/
|
||||
int hi6220_dwmci_add_port(int index, u32 regbase, int bus_width)
|
||||
{
|
||||
struct dwmci_host *host = NULL;
|
||||
|
||||
host = calloc(1, sizeof(struct dwmci_host));
|
||||
if (!host) {
|
||||
error("dwmci_host calloc failed!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
host->ioaddr = (void *)(ulong)regbase;
|
||||
host->buswidth = bus_width;
|
||||
host->bus_hz = MMC0_DEFAULT_FREQ;
|
||||
|
||||
return hi6220_dwmci_core_init(host, index);
|
||||
}
|
||||
137
u-boot/drivers/mmc/kona_sdhci.c
Normal file
137
u-boot/drivers/mmc/kona_sdhci.c
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright 2013 Broadcom Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/kona-common/clk.h>
|
||||
|
||||
#define SDHCI_CORECTRL_OFFSET 0x00008000
|
||||
#define SDHCI_CORECTRL_EN 0x01
|
||||
#define SDHCI_CORECTRL_RESET 0x02
|
||||
|
||||
#define SDHCI_CORESTAT_OFFSET 0x00008004
|
||||
#define SDHCI_CORESTAT_CD_SW 0x01
|
||||
|
||||
#define SDHCI_COREIMR_OFFSET 0x00008008
|
||||
#define SDHCI_COREIMR_IP 0x01
|
||||
|
||||
static int init_kona_mmc_core(struct sdhci_host *host)
|
||||
{
|
||||
unsigned int mask;
|
||||
unsigned int timeout;
|
||||
|
||||
if (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & SDHCI_RESET_ALL) {
|
||||
printf("%s: sd host controller reset error\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* For kona a hardware reset before anything else. */
|
||||
mask = sdhci_readl(host, SDHCI_CORECTRL_OFFSET) | SDHCI_CORECTRL_RESET;
|
||||
sdhci_writel(host, mask, SDHCI_CORECTRL_OFFSET);
|
||||
|
||||
/* Wait max 100 ms */
|
||||
timeout = 1000;
|
||||
do {
|
||||
if (timeout == 0) {
|
||||
printf("%s: reset timeout error\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
timeout--;
|
||||
udelay(100);
|
||||
} while (0 ==
|
||||
(sdhci_readl(host, SDHCI_CORECTRL_OFFSET) &
|
||||
SDHCI_CORECTRL_RESET));
|
||||
|
||||
/* Clear the reset bit. */
|
||||
mask = mask & ~SDHCI_CORECTRL_RESET;
|
||||
sdhci_writel(host, mask, SDHCI_CORECTRL_OFFSET);
|
||||
|
||||
/* Enable AHB clock */
|
||||
mask = sdhci_readl(host, SDHCI_CORECTRL_OFFSET);
|
||||
sdhci_writel(host, mask | SDHCI_CORECTRL_EN, SDHCI_CORECTRL_OFFSET);
|
||||
|
||||
/* Enable interrupts */
|
||||
sdhci_writel(host, SDHCI_COREIMR_IP, SDHCI_COREIMR_OFFSET);
|
||||
|
||||
/* Make sure Card is detected in controller */
|
||||
mask = sdhci_readl(host, SDHCI_CORESTAT_OFFSET);
|
||||
sdhci_writel(host, mask | SDHCI_CORESTAT_CD_SW, SDHCI_CORESTAT_OFFSET);
|
||||
|
||||
/* Wait max 100 ms */
|
||||
timeout = 1000;
|
||||
while (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
|
||||
if (timeout == 0) {
|
||||
printf("%s: CARD DETECT timeout error\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
timeout--;
|
||||
udelay(100);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int kona_sdhci_init(int dev_index, u32 min_clk, u32 quirks)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 max_clk;
|
||||
void *reg_base;
|
||||
struct sdhci_host *host = NULL;
|
||||
|
||||
host = (struct sdhci_host *)malloc(sizeof(struct sdhci_host));
|
||||
if (!host) {
|
||||
printf("%s: sdhci host malloc fail!\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
switch (dev_index) {
|
||||
case 0:
|
||||
reg_base = (void *)CONFIG_SYS_SDIO_BASE0;
|
||||
ret = clk_sdio_enable(reg_base, CONFIG_SYS_SDIO0_MAX_CLK,
|
||||
&max_clk);
|
||||
break;
|
||||
case 1:
|
||||
reg_base = (void *)CONFIG_SYS_SDIO_BASE1;
|
||||
ret = clk_sdio_enable(reg_base, CONFIG_SYS_SDIO1_MAX_CLK,
|
||||
&max_clk);
|
||||
break;
|
||||
case 2:
|
||||
reg_base = (void *)CONFIG_SYS_SDIO_BASE2;
|
||||
ret = clk_sdio_enable(reg_base, CONFIG_SYS_SDIO2_MAX_CLK,
|
||||
&max_clk);
|
||||
break;
|
||||
case 3:
|
||||
reg_base = (void *)CONFIG_SYS_SDIO_BASE3;
|
||||
ret = clk_sdio_enable(reg_base, CONFIG_SYS_SDIO3_MAX_CLK,
|
||||
&max_clk);
|
||||
break;
|
||||
default:
|
||||
printf("%s: sdio dev index %d not supported\n",
|
||||
__func__, dev_index);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (ret) {
|
||||
free(host);
|
||||
return ret;
|
||||
}
|
||||
|
||||
host->name = "kona-sdhci";
|
||||
host->ioaddr = reg_base;
|
||||
host->quirks = quirks;
|
||||
|
||||
if (init_kona_mmc_core(host)) {
|
||||
free(host);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (quirks & SDHCI_QUIRK_REG32_RW)
|
||||
host->version = sdhci_readl(host, SDHCI_HOST_VERSION - 2) >> 16;
|
||||
else
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
|
||||
add_sdhci(host, max_clk, min_clk);
|
||||
return ret;
|
||||
}
|
||||
140
u-boot/drivers/mmc/mmc-uclass.c
Normal file
140
u-boot/drivers/mmc/mmc-uclass.c
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <mmc.h>
|
||||
#include <dm.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dm/root.h>
|
||||
|
||||
struct mmc *mmc_get_mmc_dev(struct udevice *dev)
|
||||
{
|
||||
struct mmc_uclass_priv *upriv;
|
||||
|
||||
if (!device_active(dev))
|
||||
return NULL;
|
||||
upriv = dev_get_uclass_priv(dev);
|
||||
return upriv->mmc;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BLK
|
||||
struct mmc *find_mmc_device(int dev_num)
|
||||
{
|
||||
struct udevice *dev, *mmc_dev;
|
||||
int ret;
|
||||
|
||||
ret = blk_get_device(IF_TYPE_MMC, dev_num, &dev);
|
||||
|
||||
if (ret) {
|
||||
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
|
||||
printf("MMC Device %d not found\n", dev_num);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mmc_dev = dev_get_parent(dev);
|
||||
|
||||
return mmc_get_mmc_dev(mmc_dev);
|
||||
}
|
||||
|
||||
int get_mmc_num(void)
|
||||
{
|
||||
return max(blk_find_max_devnum(IF_TYPE_MMC), 0);
|
||||
}
|
||||
|
||||
int mmc_get_next_devnum(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = get_mmc_num();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return ret + 1;
|
||||
}
|
||||
|
||||
struct blk_desc *mmc_get_blk_desc(struct mmc *mmc)
|
||||
{
|
||||
struct blk_desc *desc;
|
||||
struct udevice *dev;
|
||||
|
||||
device_find_first_child(mmc->dev, &dev);
|
||||
if (!dev)
|
||||
return NULL;
|
||||
desc = dev_get_uclass_platdata(dev);
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
void mmc_do_preinit(void)
|
||||
{
|
||||
struct udevice *dev;
|
||||
struct uclass *uc;
|
||||
int ret;
|
||||
|
||||
ret = uclass_get(UCLASS_MMC, &uc);
|
||||
if (ret)
|
||||
return;
|
||||
uclass_foreach_dev(dev, uc) {
|
||||
struct mmc *m = mmc_get_mmc_dev(dev);
|
||||
|
||||
if (!m)
|
||||
continue;
|
||||
#ifdef CONFIG_FSL_ESDHC_ADAPTER_IDENT
|
||||
mmc_set_preinit(m, 1);
|
||||
#endif
|
||||
if (m->preinit)
|
||||
mmc_start_init(m);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
|
||||
void print_mmc_devices(char separator)
|
||||
{
|
||||
struct udevice *dev;
|
||||
char *mmc_type;
|
||||
bool first = true;
|
||||
|
||||
for (uclass_first_device(UCLASS_MMC, &dev);
|
||||
dev;
|
||||
uclass_next_device(&dev)) {
|
||||
struct mmc *m = mmc_get_mmc_dev(dev);
|
||||
|
||||
if (!first) {
|
||||
printf("%c", separator);
|
||||
if (separator != '\n')
|
||||
puts(" ");
|
||||
}
|
||||
if (m->has_init)
|
||||
mmc_type = IS_SD(m) ? "SD" : "eMMC";
|
||||
else
|
||||
mmc_type = NULL;
|
||||
|
||||
printf("%s: %d", m->cfg->name, mmc_get_blk_desc(m)->devnum);
|
||||
if (mmc_type)
|
||||
printf(" (%s)", mmc_type);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#else
|
||||
void print_mmc_devices(char separator) { }
|
||||
#endif
|
||||
#endif /* CONFIG_BLK */
|
||||
|
||||
U_BOOT_DRIVER(mmc) = {
|
||||
.name = "mmc",
|
||||
.id = UCLASS_MMC,
|
||||
};
|
||||
|
||||
UCLASS_DRIVER(mmc) = {
|
||||
.id = UCLASS_MMC,
|
||||
.name = "mmc",
|
||||
.flags = DM_UC_FLAG_SEQ_ALIAS,
|
||||
.per_device_auto_alloc_size = sizeof(struct mmc_uclass_priv),
|
||||
};
|
||||
1986
u-boot/drivers/mmc/mmc.c
Normal file
1986
u-boot/drivers/mmc/mmc.c
Normal file
File diff suppressed because it is too large
Load Diff
108
u-boot/drivers/mmc/mmc_legacy.c
Normal file
108
u-boot/drivers/mmc/mmc_legacy.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <mmc.h>
|
||||
|
||||
static struct list_head mmc_devices;
|
||||
static int cur_dev_num = -1;
|
||||
|
||||
struct mmc *find_mmc_device(int dev_num)
|
||||
{
|
||||
struct mmc *m;
|
||||
struct list_head *entry;
|
||||
|
||||
list_for_each(entry, &mmc_devices) {
|
||||
m = list_entry(entry, struct mmc, link);
|
||||
|
||||
if (m->block_dev.devnum == dev_num)
|
||||
return m;
|
||||
}
|
||||
|
||||
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
|
||||
printf("MMC Device %d not found\n", dev_num);
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int mmc_get_next_devnum(void)
|
||||
{
|
||||
return cur_dev_num++;
|
||||
}
|
||||
|
||||
struct blk_desc *mmc_get_blk_desc(struct mmc *mmc)
|
||||
{
|
||||
return &mmc->block_dev;
|
||||
}
|
||||
|
||||
int get_mmc_num(void)
|
||||
{
|
||||
return cur_dev_num;
|
||||
}
|
||||
|
||||
void mmc_do_preinit(void)
|
||||
{
|
||||
struct mmc *m;
|
||||
struct list_head *entry;
|
||||
|
||||
list_for_each(entry, &mmc_devices) {
|
||||
m = list_entry(entry, struct mmc, link);
|
||||
|
||||
#ifdef CONFIG_FSL_ESDHC_ADAPTER_IDENT
|
||||
mmc_set_preinit(m, 1);
|
||||
#endif
|
||||
if (m->preinit)
|
||||
mmc_start_init(m);
|
||||
}
|
||||
}
|
||||
|
||||
void mmc_list_init(void)
|
||||
{
|
||||
INIT_LIST_HEAD(&mmc_devices);
|
||||
cur_dev_num = 0;
|
||||
}
|
||||
|
||||
void mmc_list_add(struct mmc *mmc)
|
||||
{
|
||||
INIT_LIST_HEAD(&mmc->link);
|
||||
|
||||
list_add_tail(&mmc->link, &mmc_devices);
|
||||
}
|
||||
|
||||
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
|
||||
void print_mmc_devices(char separator)
|
||||
{
|
||||
struct mmc *m;
|
||||
struct list_head *entry;
|
||||
char *mmc_type;
|
||||
|
||||
list_for_each(entry, &mmc_devices) {
|
||||
m = list_entry(entry, struct mmc, link);
|
||||
|
||||
if (m->has_init)
|
||||
mmc_type = IS_SD(m) ? "SD" : "eMMC";
|
||||
else
|
||||
mmc_type = NULL;
|
||||
|
||||
printf("%s: %d", m->cfg->name, m->block_dev.devnum);
|
||||
if (mmc_type)
|
||||
printf(" (%s)", mmc_type);
|
||||
|
||||
if (entry->next != &mmc_devices) {
|
||||
printf("%c", separator);
|
||||
if (separator != '\n')
|
||||
puts(" ");
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#else
|
||||
void print_mmc_devices(char separator) { }
|
||||
#endif
|
||||
92
u-boot/drivers/mmc/mmc_private.h
Normal file
92
u-boot/drivers/mmc/mmc_private.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2008,2010 Freescale Semiconductor, Inc
|
||||
* Andy Fleming
|
||||
*
|
||||
* Based (loosely) on the Linux code
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#ifndef _MMC_PRIVATE_H_
|
||||
#define _MMC_PRIVATE_H_
|
||||
|
||||
#include <mmc.h>
|
||||
|
||||
extern int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data);
|
||||
extern int mmc_send_status(struct mmc *mmc, int timeout);
|
||||
extern int mmc_set_blocklen(struct mmc *mmc, int len);
|
||||
#ifdef CONFIG_FSL_ESDHC_ADAPTER_IDENT
|
||||
void mmc_adapter_card_type_ident(void);
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_SPL_BUILD
|
||||
|
||||
unsigned long mmc_berase(struct blk_desc *block_dev, lbaint_t start,
|
||||
lbaint_t blkcnt);
|
||||
|
||||
#ifdef CONFIG_BLK
|
||||
ulong mmc_bwrite(struct udevice *dev, lbaint_t start, lbaint_t blkcnt,
|
||||
const void *src);
|
||||
#else
|
||||
ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,
|
||||
const void *src);
|
||||
#endif
|
||||
|
||||
#else /* CONFIG_SPL_BUILD */
|
||||
|
||||
/* SPL will never write or erase, declare dummies to reduce code size. */
|
||||
|
||||
#ifdef CONFIG_BLK
|
||||
static inline unsigned long mmc_berase(struct udevice *dev,
|
||||
lbaint_t start, lbaint_t blkcnt)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline ulong mmc_bwrite(struct udevice *dev, lbaint_t start,
|
||||
lbaint_t blkcnt, const void *src)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline unsigned long mmc_berase(struct blk_desc *block_dev,
|
||||
lbaint_t start, lbaint_t blkcnt)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start,
|
||||
lbaint_t blkcnt, const void *src)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_SPL_BUILD */
|
||||
|
||||
/**
|
||||
* mmc_get_next_devnum() - Get the next available MMC device number
|
||||
*
|
||||
* @return next available device number (0 = first), or -ve on error
|
||||
*/
|
||||
int mmc_get_next_devnum(void);
|
||||
|
||||
/**
|
||||
* mmc_do_preinit() - Get an MMC device ready for use
|
||||
*/
|
||||
void mmc_do_preinit(void);
|
||||
|
||||
/**
|
||||
* mmc_list_init() - Set up the list of MMC devices
|
||||
*/
|
||||
void mmc_list_init(void);
|
||||
|
||||
/**
|
||||
* mmc_list_add() - Add a new MMC device to the list of devices
|
||||
*
|
||||
* @mmc: Device to add
|
||||
*/
|
||||
void mmc_list_add(struct mmc *mmc);
|
||||
|
||||
#endif /* _MMC_PRIVATE_H_ */
|
||||
291
u-boot/drivers/mmc/mmc_spi.c
Normal file
291
u-boot/drivers/mmc/mmc_spi.c
Normal file
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* generic mmc spi driver
|
||||
*
|
||||
* Copyright (C) 2010 Thomas Chou <thomas@wytron.com.tw>
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <part.h>
|
||||
#include <mmc.h>
|
||||
#include <spi.h>
|
||||
#include <crc.h>
|
||||
#include <linux/crc7.h>
|
||||
#include <asm/byteorder.h>
|
||||
|
||||
/* MMC/SD in SPI mode reports R1 status always */
|
||||
#define R1_SPI_IDLE (1 << 0)
|
||||
#define R1_SPI_ERASE_RESET (1 << 1)
|
||||
#define R1_SPI_ILLEGAL_COMMAND (1 << 2)
|
||||
#define R1_SPI_COM_CRC (1 << 3)
|
||||
#define R1_SPI_ERASE_SEQ (1 << 4)
|
||||
#define R1_SPI_ADDRESS (1 << 5)
|
||||
#define R1_SPI_PARAMETER (1 << 6)
|
||||
/* R1 bit 7 is always zero, reuse this bit for error */
|
||||
#define R1_SPI_ERROR (1 << 7)
|
||||
|
||||
/* Response tokens used to ack each block written: */
|
||||
#define SPI_MMC_RESPONSE_CODE(x) ((x) & 0x1f)
|
||||
#define SPI_RESPONSE_ACCEPTED ((2 << 1)|1)
|
||||
#define SPI_RESPONSE_CRC_ERR ((5 << 1)|1)
|
||||
#define SPI_RESPONSE_WRITE_ERR ((6 << 1)|1)
|
||||
|
||||
/* Read and write blocks start with these tokens and end with crc;
|
||||
* on error, read tokens act like a subset of R2_SPI_* values.
|
||||
*/
|
||||
#define SPI_TOKEN_SINGLE 0xfe /* single block r/w, multiblock read */
|
||||
#define SPI_TOKEN_MULTI_WRITE 0xfc /* multiblock write */
|
||||
#define SPI_TOKEN_STOP_TRAN 0xfd /* terminate multiblock write */
|
||||
|
||||
/* MMC SPI commands start with a start bit "0" and a transmit bit "1" */
|
||||
#define MMC_SPI_CMD(x) (0x40 | (x & 0x3f))
|
||||
|
||||
/* bus capability */
|
||||
#define MMC_SPI_VOLTAGE (MMC_VDD_32_33 | MMC_VDD_33_34)
|
||||
#define MMC_SPI_MIN_CLOCK 400000 /* 400KHz to meet MMC spec */
|
||||
|
||||
/* timeout value */
|
||||
#define CTOUT 8
|
||||
#define RTOUT 3000000 /* 1 sec */
|
||||
#define WTOUT 3000000 /* 1 sec */
|
||||
|
||||
static uint mmc_spi_sendcmd(struct mmc *mmc, ushort cmdidx, u32 cmdarg)
|
||||
{
|
||||
struct spi_slave *spi = mmc->priv;
|
||||
u8 cmdo[7];
|
||||
u8 r1;
|
||||
int i;
|
||||
cmdo[0] = 0xff;
|
||||
cmdo[1] = MMC_SPI_CMD(cmdidx);
|
||||
cmdo[2] = cmdarg >> 24;
|
||||
cmdo[3] = cmdarg >> 16;
|
||||
cmdo[4] = cmdarg >> 8;
|
||||
cmdo[5] = cmdarg;
|
||||
cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01;
|
||||
spi_xfer(spi, sizeof(cmdo) * 8, cmdo, NULL, 0);
|
||||
for (i = 0; i < CTOUT; i++) {
|
||||
spi_xfer(spi, 1 * 8, NULL, &r1, 0);
|
||||
if (i && (r1 & 0x80) == 0) /* r1 response */
|
||||
break;
|
||||
}
|
||||
debug("%s:cmd%d resp%d %x\n", __func__, cmdidx, i, r1);
|
||||
return r1;
|
||||
}
|
||||
|
||||
static uint mmc_spi_readdata(struct mmc *mmc, void *xbuf,
|
||||
u32 bcnt, u32 bsize)
|
||||
{
|
||||
struct spi_slave *spi = mmc->priv;
|
||||
u8 *buf = xbuf;
|
||||
u8 r1;
|
||||
u16 crc;
|
||||
int i;
|
||||
while (bcnt--) {
|
||||
for (i = 0; i < RTOUT; i++) {
|
||||
spi_xfer(spi, 1 * 8, NULL, &r1, 0);
|
||||
if (r1 != 0xff) /* data token */
|
||||
break;
|
||||
}
|
||||
debug("%s:tok%d %x\n", __func__, i, r1);
|
||||
if (r1 == SPI_TOKEN_SINGLE) {
|
||||
spi_xfer(spi, bsize * 8, NULL, buf, 0);
|
||||
spi_xfer(spi, 2 * 8, NULL, &crc, 0);
|
||||
#ifdef CONFIG_MMC_SPI_CRC_ON
|
||||
if (be_to_cpu16(crc16_ccitt(0, buf, bsize)) != crc) {
|
||||
debug("%s: CRC error\n", mmc->cfg->name);
|
||||
r1 = R1_SPI_COM_CRC;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
r1 = 0;
|
||||
} else {
|
||||
r1 = R1_SPI_ERROR;
|
||||
break;
|
||||
}
|
||||
buf += bsize;
|
||||
}
|
||||
return r1;
|
||||
}
|
||||
|
||||
static uint mmc_spi_writedata(struct mmc *mmc, const void *xbuf,
|
||||
u32 bcnt, u32 bsize, int multi)
|
||||
{
|
||||
struct spi_slave *spi = mmc->priv;
|
||||
const u8 *buf = xbuf;
|
||||
u8 r1;
|
||||
u16 crc;
|
||||
u8 tok[2];
|
||||
int i;
|
||||
tok[0] = 0xff;
|
||||
tok[1] = multi ? SPI_TOKEN_MULTI_WRITE : SPI_TOKEN_SINGLE;
|
||||
while (bcnt--) {
|
||||
#ifdef CONFIG_MMC_SPI_CRC_ON
|
||||
crc = cpu_to_be16(crc16_ccitt(0, (u8 *)buf, bsize));
|
||||
#endif
|
||||
spi_xfer(spi, 2 * 8, tok, NULL, 0);
|
||||
spi_xfer(spi, bsize * 8, buf, NULL, 0);
|
||||
spi_xfer(spi, 2 * 8, &crc, NULL, 0);
|
||||
for (i = 0; i < CTOUT; i++) {
|
||||
spi_xfer(spi, 1 * 8, NULL, &r1, 0);
|
||||
if ((r1 & 0x10) == 0) /* response token */
|
||||
break;
|
||||
}
|
||||
debug("%s:tok%d %x\n", __func__, i, r1);
|
||||
if (SPI_MMC_RESPONSE_CODE(r1) == SPI_RESPONSE_ACCEPTED) {
|
||||
for (i = 0; i < WTOUT; i++) { /* wait busy */
|
||||
spi_xfer(spi, 1 * 8, NULL, &r1, 0);
|
||||
if (i && r1 == 0xff) {
|
||||
r1 = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == WTOUT) {
|
||||
debug("%s:wtout %x\n", __func__, r1);
|
||||
r1 = R1_SPI_ERROR;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
debug("%s: err %x\n", __func__, r1);
|
||||
r1 = R1_SPI_COM_CRC;
|
||||
break;
|
||||
}
|
||||
buf += bsize;
|
||||
}
|
||||
if (multi && bcnt == -1) { /* stop multi write */
|
||||
tok[1] = SPI_TOKEN_STOP_TRAN;
|
||||
spi_xfer(spi, 2 * 8, tok, NULL, 0);
|
||||
for (i = 0; i < WTOUT; i++) { /* wait busy */
|
||||
spi_xfer(spi, 1 * 8, NULL, &r1, 0);
|
||||
if (i && r1 == 0xff) {
|
||||
r1 = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == WTOUT) {
|
||||
debug("%s:wstop %x\n", __func__, r1);
|
||||
r1 = R1_SPI_ERROR;
|
||||
}
|
||||
}
|
||||
return r1;
|
||||
}
|
||||
|
||||
static int mmc_spi_request(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct spi_slave *spi = mmc->priv;
|
||||
u8 r1;
|
||||
int i;
|
||||
int ret = 0;
|
||||
debug("%s:cmd%d %x %x\n", __func__,
|
||||
cmd->cmdidx, cmd->resp_type, cmd->cmdarg);
|
||||
spi_claim_bus(spi);
|
||||
spi_cs_activate(spi);
|
||||
r1 = mmc_spi_sendcmd(mmc, cmd->cmdidx, cmd->cmdarg);
|
||||
if (r1 == 0xff) { /* no response */
|
||||
ret = NO_CARD_ERR;
|
||||
goto done;
|
||||
} else if (r1 & R1_SPI_COM_CRC) {
|
||||
ret = COMM_ERR;
|
||||
goto done;
|
||||
} else if (r1 & ~R1_SPI_IDLE) { /* other errors */
|
||||
ret = TIMEOUT;
|
||||
goto done;
|
||||
} else if (cmd->resp_type == MMC_RSP_R2) {
|
||||
r1 = mmc_spi_readdata(mmc, cmd->response, 1, 16);
|
||||
for (i = 0; i < 4; i++)
|
||||
cmd->response[i] = be32_to_cpu(cmd->response[i]);
|
||||
debug("r128 %x %x %x %x\n", cmd->response[0], cmd->response[1],
|
||||
cmd->response[2], cmd->response[3]);
|
||||
} else if (!data) {
|
||||
switch (cmd->cmdidx) {
|
||||
case SD_CMD_APP_SEND_OP_COND:
|
||||
case MMC_CMD_SEND_OP_COND:
|
||||
cmd->response[0] = (r1 & R1_SPI_IDLE) ? 0 : OCR_BUSY;
|
||||
break;
|
||||
case SD_CMD_SEND_IF_COND:
|
||||
case MMC_CMD_SPI_READ_OCR:
|
||||
spi_xfer(spi, 4 * 8, NULL, cmd->response, 0);
|
||||
cmd->response[0] = be32_to_cpu(cmd->response[0]);
|
||||
debug("r32 %x\n", cmd->response[0]);
|
||||
break;
|
||||
case MMC_CMD_SEND_STATUS:
|
||||
spi_xfer(spi, 1 * 8, NULL, cmd->response, 0);
|
||||
cmd->response[0] = (cmd->response[0] & 0xff) ?
|
||||
MMC_STATUS_ERROR : MMC_STATUS_RDY_FOR_DATA;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
debug("%s:data %x %x %x\n", __func__,
|
||||
data->flags, data->blocks, data->blocksize);
|
||||
if (data->flags == MMC_DATA_READ)
|
||||
r1 = mmc_spi_readdata(mmc, data->dest,
|
||||
data->blocks, data->blocksize);
|
||||
else if (data->flags == MMC_DATA_WRITE)
|
||||
r1 = mmc_spi_writedata(mmc, data->src,
|
||||
data->blocks, data->blocksize,
|
||||
(cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK));
|
||||
if (r1 & R1_SPI_COM_CRC)
|
||||
ret = COMM_ERR;
|
||||
else if (r1) /* other errors */
|
||||
ret = TIMEOUT;
|
||||
}
|
||||
done:
|
||||
spi_cs_deactivate(spi);
|
||||
spi_release_bus(spi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mmc_spi_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct spi_slave *spi = mmc->priv;
|
||||
|
||||
debug("%s: clock %u\n", __func__, mmc->clock);
|
||||
if (mmc->clock)
|
||||
spi_set_speed(spi, mmc->clock);
|
||||
}
|
||||
|
||||
static int mmc_spi_init_p(struct mmc *mmc)
|
||||
{
|
||||
struct spi_slave *spi = mmc->priv;
|
||||
spi_set_speed(spi, MMC_SPI_MIN_CLOCK);
|
||||
spi_claim_bus(spi);
|
||||
/* cs deactivated for 100+ clock */
|
||||
spi_xfer(spi, 18 * 8, NULL, NULL, 0);
|
||||
spi_release_bus(spi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops mmc_spi_ops = {
|
||||
.send_cmd = mmc_spi_request,
|
||||
.set_ios = mmc_spi_set_ios,
|
||||
.init = mmc_spi_init_p,
|
||||
};
|
||||
|
||||
static struct mmc_config mmc_spi_cfg = {
|
||||
.name = "MMC_SPI",
|
||||
.ops = &mmc_spi_ops,
|
||||
.host_caps = MMC_MODE_SPI,
|
||||
.voltages = MMC_SPI_VOLTAGE,
|
||||
.f_min = MMC_SPI_MIN_CLOCK,
|
||||
.part_type = PART_TYPE_DOS,
|
||||
.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT,
|
||||
};
|
||||
|
||||
struct mmc *mmc_spi_init(uint bus, uint cs, uint speed, uint mode)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
struct spi_slave *spi;
|
||||
|
||||
spi = spi_setup_slave(bus, cs, speed, mode);
|
||||
if (spi == NULL)
|
||||
return NULL;
|
||||
|
||||
mmc_spi_cfg.f_max = speed;
|
||||
|
||||
mmc = mmc_create(&mmc_spi_cfg, spi);
|
||||
if (mmc == NULL) {
|
||||
spi_free_slave(spi);
|
||||
return NULL;
|
||||
}
|
||||
return mmc;
|
||||
}
|
||||
213
u-boot/drivers/mmc/mmc_write.c
Normal file
213
u-boot/drivers/mmc/mmc_write.c
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2008, Freescale Semiconductor, Inc
|
||||
* Andy Fleming
|
||||
*
|
||||
* Based vaguely on the Linux code
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <part.h>
|
||||
#include <div64.h>
|
||||
#include <linux/math64.h>
|
||||
#include "mmc_private.h"
|
||||
|
||||
static ulong mmc_erase_t(struct mmc *mmc, ulong start, lbaint_t blkcnt)
|
||||
{
|
||||
struct mmc_cmd cmd;
|
||||
ulong end;
|
||||
int err, start_cmd, end_cmd;
|
||||
|
||||
if (mmc->high_capacity) {
|
||||
end = start + blkcnt - 1;
|
||||
} else {
|
||||
end = (start + blkcnt - 1) * mmc->write_bl_len;
|
||||
start *= mmc->write_bl_len;
|
||||
}
|
||||
|
||||
if (IS_SD(mmc)) {
|
||||
start_cmd = SD_CMD_ERASE_WR_BLK_START;
|
||||
end_cmd = SD_CMD_ERASE_WR_BLK_END;
|
||||
} else {
|
||||
start_cmd = MMC_CMD_ERASE_GROUP_START;
|
||||
end_cmd = MMC_CMD_ERASE_GROUP_END;
|
||||
}
|
||||
|
||||
cmd.cmdidx = start_cmd;
|
||||
cmd.cmdarg = start;
|
||||
cmd.resp_type = MMC_RSP_R1;
|
||||
|
||||
err = mmc_send_cmd(mmc, &cmd, NULL);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
cmd.cmdidx = end_cmd;
|
||||
cmd.cmdarg = end;
|
||||
|
||||
err = mmc_send_cmd(mmc, &cmd, NULL);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
cmd.cmdidx = MMC_CMD_ERASE;
|
||||
cmd.cmdarg = MMC_ERASE_ARG;
|
||||
cmd.resp_type = MMC_RSP_R1b;
|
||||
|
||||
err = mmc_send_cmd(mmc, &cmd, NULL);
|
||||
if (err)
|
||||
goto err_out;
|
||||
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
puts("mmc erase failed\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
unsigned long mmc_berase(struct blk_desc *block_dev, lbaint_t start,
|
||||
lbaint_t blkcnt)
|
||||
{
|
||||
int dev_num = block_dev->devnum;
|
||||
int err = 0;
|
||||
u32 start_rem, blkcnt_rem;
|
||||
struct mmc *mmc = find_mmc_device(dev_num);
|
||||
lbaint_t blk = 0, blk_r = 0;
|
||||
int timeout = 1000;
|
||||
|
||||
if (!mmc)
|
||||
return -1;
|
||||
|
||||
err = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_num,
|
||||
block_dev->hwpart);
|
||||
if (err < 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* We want to see if the requested start or total block count are
|
||||
* unaligned. We discard the whole numbers and only care about the
|
||||
* remainder.
|
||||
*/
|
||||
err = div_u64_rem(start, mmc->erase_grp_size, &start_rem);
|
||||
err = div_u64_rem(blkcnt, mmc->erase_grp_size, &blkcnt_rem);
|
||||
if (start_rem || blkcnt_rem)
|
||||
printf("\n\nCaution! Your devices Erase group is 0x%x\n"
|
||||
"The erase range would be change to "
|
||||
"0x" LBAF "~0x" LBAF "\n\n",
|
||||
mmc->erase_grp_size, start & ~(mmc->erase_grp_size - 1),
|
||||
((start + blkcnt + mmc->erase_grp_size)
|
||||
& ~(mmc->erase_grp_size - 1)) - 1);
|
||||
|
||||
while (blk < blkcnt) {
|
||||
blk_r = ((blkcnt - blk) > mmc->erase_grp_size) ?
|
||||
mmc->erase_grp_size : (blkcnt - blk);
|
||||
err = mmc_erase_t(mmc, start + blk, blk_r);
|
||||
if (err)
|
||||
break;
|
||||
|
||||
blk += blk_r;
|
||||
|
||||
/* Waiting for the ready status */
|
||||
if (mmc_send_status(mmc, timeout))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return blk;
|
||||
}
|
||||
|
||||
static ulong mmc_write_blocks(struct mmc *mmc, lbaint_t start,
|
||||
lbaint_t blkcnt, const void *src)
|
||||
{
|
||||
struct mmc_cmd cmd;
|
||||
struct mmc_data data;
|
||||
int timeout = 1000;
|
||||
|
||||
if ((start + blkcnt) > mmc_get_blk_desc(mmc)->lba) {
|
||||
printf("MMC: block number 0x" LBAF " exceeds max(0x" LBAF ")\n",
|
||||
start + blkcnt, mmc_get_blk_desc(mmc)->lba);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (blkcnt == 0)
|
||||
return 0;
|
||||
else if (blkcnt == 1)
|
||||
cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK;
|
||||
else
|
||||
cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK;
|
||||
|
||||
if (mmc->high_capacity)
|
||||
cmd.cmdarg = start;
|
||||
else
|
||||
cmd.cmdarg = start * mmc->write_bl_len;
|
||||
|
||||
cmd.resp_type = MMC_RSP_R1;
|
||||
|
||||
data.src = src;
|
||||
data.blocks = blkcnt;
|
||||
data.blocksize = mmc->write_bl_len;
|
||||
data.flags = MMC_DATA_WRITE;
|
||||
|
||||
if (mmc_send_cmd(mmc, &cmd, &data)) {
|
||||
printf("mmc write failed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SPI multiblock writes terminate using a special
|
||||
* token, not a STOP_TRANSMISSION request.
|
||||
*/
|
||||
if (!mmc_host_is_spi(mmc) && blkcnt > 1) {
|
||||
cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
|
||||
cmd.cmdarg = 0;
|
||||
cmd.resp_type = MMC_RSP_R1b;
|
||||
if (mmc_send_cmd(mmc, &cmd, NULL)) {
|
||||
printf("mmc fail to send stop cmd\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Waiting for the ready status */
|
||||
if (mmc_send_status(mmc, timeout))
|
||||
return 0;
|
||||
|
||||
return blkcnt;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BLK
|
||||
ulong mmc_bwrite(struct udevice *dev, lbaint_t start, lbaint_t blkcnt,
|
||||
const void *src)
|
||||
#else
|
||||
ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt,
|
||||
const void *src)
|
||||
#endif
|
||||
{
|
||||
#ifdef CONFIG_BLK
|
||||
struct blk_desc *block_dev = dev_get_uclass_platdata(dev);
|
||||
#endif
|
||||
int dev_num = block_dev->devnum;
|
||||
lbaint_t cur, blocks_todo = blkcnt;
|
||||
int err;
|
||||
|
||||
struct mmc *mmc = find_mmc_device(dev_num);
|
||||
if (!mmc)
|
||||
return 0;
|
||||
|
||||
err = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_num, block_dev->hwpart);
|
||||
if (err < 0)
|
||||
return 0;
|
||||
|
||||
if (mmc_set_blocklen(mmc, mmc->write_bl_len))
|
||||
return 0;
|
||||
|
||||
do {
|
||||
cur = (blocks_todo > mmc->cfg->b_max) ?
|
||||
mmc->cfg->b_max : blocks_todo;
|
||||
if (mmc_write_blocks(mmc, start, cur, src) != cur)
|
||||
return 0;
|
||||
blocks_todo -= cur;
|
||||
start += cur;
|
||||
src += cur * mmc->write_bl_len;
|
||||
} while (blocks_todo > 0);
|
||||
|
||||
return blkcnt;
|
||||
}
|
||||
192
u-boot/drivers/mmc/msm_sdhci.c
Normal file
192
u-boot/drivers/mmc/msm_sdhci.c
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Qualcomm SDHCI driver - SD/eMMC controller
|
||||
*
|
||||
* (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com>
|
||||
*
|
||||
* Based on Linux driver
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <sdhci.h>
|
||||
#include <wait_bit.h>
|
||||
#include <asm/io.h>
|
||||
#include <linux/bitops.h>
|
||||
|
||||
/* Non-standard registers needed for SDHCI startup */
|
||||
#define SDCC_MCI_POWER 0x0
|
||||
#define SDCC_MCI_POWER_SW_RST BIT(7)
|
||||
|
||||
/* This is undocumented register */
|
||||
#define SDCC_MCI_VERSION 0x50
|
||||
#define SDCC_MCI_VERSION_MAJOR_SHIFT 28
|
||||
#define SDCC_MCI_VERSION_MAJOR_MASK (0xf << SDCC_MCI_VERSION_MAJOR_SHIFT)
|
||||
#define SDCC_MCI_VERSION_MINOR_MASK 0xff
|
||||
|
||||
#define SDCC_MCI_STATUS2 0x6C
|
||||
#define SDCC_MCI_STATUS2_MCI_ACT 0x1
|
||||
#define SDCC_MCI_HC_MODE 0x78
|
||||
|
||||
/* Offset to SDHCI registers */
|
||||
#define SDCC_SDHCI_OFFSET 0x900
|
||||
|
||||
/* Non standard (?) SDHCI register */
|
||||
#define SDHCI_VENDOR_SPEC_CAPABILITIES0 0x11c
|
||||
|
||||
struct msm_sdhc {
|
||||
struct sdhci_host host;
|
||||
void *base;
|
||||
};
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
static int msm_sdc_clk_init(struct udevice *dev)
|
||||
{
|
||||
uint clk_rate = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
|
||||
"clock-frequency", 400000);
|
||||
uint clkd[2]; /* clk_id and clk_no */
|
||||
int clk_offset;
|
||||
struct udevice *clk_dev;
|
||||
struct clk clk;
|
||||
int ret;
|
||||
|
||||
ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset, "clock", clkd,
|
||||
2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_offset = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]);
|
||||
if (clk_offset < 0)
|
||||
return clk_offset;
|
||||
|
||||
ret = uclass_get_device_by_of_offset(UCLASS_CLK, clk_offset, &clk_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk.id = clkd[1];
|
||||
ret = clk_request(clk_dev, &clk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = clk_set_rate(&clk, clk_rate);
|
||||
clk_free(&clk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int msm_sdc_probe(struct udevice *dev)
|
||||
{
|
||||
struct msm_sdhc *prv = dev_get_priv(dev);
|
||||
struct sdhci_host *host = &prv->host;
|
||||
u32 core_version, core_minor, core_major;
|
||||
int ret;
|
||||
|
||||
host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B;
|
||||
|
||||
/* Init clocks */
|
||||
ret = msm_sdc_clk_init(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Reset the core and Enable SDHC mode */
|
||||
writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST,
|
||||
prv->base + SDCC_MCI_POWER);
|
||||
|
||||
|
||||
/* Wait for reset to be written to register */
|
||||
if (wait_for_bit(__func__, prv->base + SDCC_MCI_STATUS2,
|
||||
SDCC_MCI_STATUS2_MCI_ACT, false, 10, false)) {
|
||||
printf("msm_sdhci: reset request failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */
|
||||
if (wait_for_bit(__func__, prv->base + SDCC_MCI_POWER,
|
||||
SDCC_MCI_POWER_SW_RST, false, 2, false)) {
|
||||
printf("msm_sdhci: stuck in reset\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* Enable host-controller mode */
|
||||
writel(1, prv->base + SDCC_MCI_HC_MODE);
|
||||
|
||||
core_version = readl(prv->base + SDCC_MCI_VERSION);
|
||||
|
||||
core_major = (core_version & SDCC_MCI_VERSION_MAJOR_MASK);
|
||||
core_major >>= SDCC_MCI_VERSION_MAJOR_SHIFT;
|
||||
|
||||
core_minor = core_version & SDCC_MCI_VERSION_MINOR_MASK;
|
||||
|
||||
/*
|
||||
* Support for some capabilities is not advertised by newer
|
||||
* controller versions and must be explicitly enabled.
|
||||
*/
|
||||
if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) {
|
||||
u32 caps = readl(host->ioaddr + SDHCI_CAPABILITIES);
|
||||
caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT;
|
||||
writel(caps, host->ioaddr + SDHCI_VENDOR_SPEC_CAPABILITIES0);
|
||||
}
|
||||
|
||||
/* Set host controller version */
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
|
||||
/* automatically detect max and min speed */
|
||||
ret = add_sdhci(host, 0, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
host->mmc->dev = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int msm_sdc_remove(struct udevice *dev)
|
||||
{
|
||||
struct msm_sdhc *priv = dev_get_priv(dev);
|
||||
|
||||
/* Disable host-controller mode */
|
||||
writel(0, priv->base + SDCC_MCI_HC_MODE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int msm_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct udevice *parent = dev->parent;
|
||||
struct msm_sdhc *priv = dev_get_priv(dev);
|
||||
struct sdhci_host *host = &priv->host;
|
||||
|
||||
host->name = strdup(dev->name);
|
||||
host->ioaddr = (void *)dev_get_addr(dev);
|
||||
host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
|
||||
"bus-width", 4);
|
||||
host->index = fdtdec_get_uint(gd->fdt_blob, dev->of_offset, "index", 0);
|
||||
priv->base = (void *)fdtdec_get_addr_size_auto_parent(gd->fdt_blob,
|
||||
parent->of_offset,
|
||||
dev->of_offset,
|
||||
"reg", 1, NULL);
|
||||
if (priv->base == (void *)FDT_ADDR_T_NONE ||
|
||||
host->ioaddr == (void *)FDT_ADDR_T_NONE)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id msm_mmc_ids[] = {
|
||||
{ .compatible = "qcom,sdhci-msm-v4" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(msm_sdc_drv) = {
|
||||
.name = "msm_sdc",
|
||||
.id = UCLASS_MMC,
|
||||
.of_match = msm_mmc_ids,
|
||||
.ofdata_to_platdata = msm_ofdata_to_platdata,
|
||||
.probe = msm_sdc_probe,
|
||||
.remove = msm_sdc_remove,
|
||||
.priv_auto_alloc_size = sizeof(struct msm_sdhc),
|
||||
};
|
||||
96
u-boot/drivers/mmc/mv_sdhci.c
Normal file
96
u-boot/drivers/mmc/mv_sdhci.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Marvell SD Host Controller Interface
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
#include <linux/mbus.h>
|
||||
|
||||
#define SDHCI_WINDOW_CTRL(win) (0x4080 + ((win) << 4))
|
||||
#define SDHCI_WINDOW_BASE(win) (0x4084 + ((win) << 4))
|
||||
|
||||
static void sdhci_mvebu_mbus_config(void __iomem *base)
|
||||
{
|
||||
const struct mbus_dram_target_info *dram;
|
||||
int i;
|
||||
|
||||
dram = mvebu_mbus_dram_info();
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
writel(0, base + SDHCI_WINDOW_CTRL(i));
|
||||
writel(0, base + SDHCI_WINDOW_BASE(i));
|
||||
}
|
||||
|
||||
for (i = 0; i < dram->num_cs; i++) {
|
||||
const struct mbus_dram_window *cs = dram->cs + i;
|
||||
|
||||
/* Write size, attributes and target id to control register */
|
||||
writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) |
|
||||
(dram->mbus_dram_target_id << 4) | 1,
|
||||
base + SDHCI_WINDOW_CTRL(i));
|
||||
|
||||
/* Write base address to base register */
|
||||
writel(cs->base, base + SDHCI_WINDOW_BASE(i));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
|
||||
static struct sdhci_ops mv_ops;
|
||||
|
||||
#if defined(CONFIG_SHEEVA_88SV331xV5)
|
||||
#define SD_CE_ATA_2 0xEA
|
||||
#define MMC_CARD 0x1000
|
||||
#define MMC_WIDTH 0x0100
|
||||
static inline void mv_sdhci_writeb(struct sdhci_host *host, u8 val, int reg)
|
||||
{
|
||||
struct mmc *mmc = host->mmc;
|
||||
u32 ata = (unsigned long)host->ioaddr + SD_CE_ATA_2;
|
||||
|
||||
if (!IS_SD(mmc) && reg == SDHCI_HOST_CONTROL) {
|
||||
if (mmc->bus_width == 8)
|
||||
writew(readw(ata) | (MMC_CARD | MMC_WIDTH), ata);
|
||||
else
|
||||
writew(readw(ata) & ~(MMC_CARD | MMC_WIDTH), ata);
|
||||
}
|
||||
|
||||
writeb(val, host->ioaddr + reg);
|
||||
}
|
||||
|
||||
#else
|
||||
#define mv_sdhci_writeb NULL
|
||||
#endif /* CONFIG_SHEEVA_88SV331xV5 */
|
||||
#endif /* CONFIG_MMC_SDHCI_IO_ACCESSORS */
|
||||
|
||||
static char *MVSDH_NAME = "mv_sdh";
|
||||
int mv_sdh_init(unsigned long regbase, u32 max_clk, u32 min_clk, u32 quirks)
|
||||
{
|
||||
struct sdhci_host *host = NULL;
|
||||
host = (struct sdhci_host *)malloc(sizeof(struct sdhci_host));
|
||||
if (!host) {
|
||||
printf("sdh_host malloc fail!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
host->name = MVSDH_NAME;
|
||||
host->ioaddr = (void *)regbase;
|
||||
host->quirks = quirks;
|
||||
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
|
||||
memset(&mv_ops, 0, sizeof(struct sdhci_ops));
|
||||
mv_ops.write_b = mv_sdhci_writeb;
|
||||
host->ops = &mv_ops;
|
||||
#endif
|
||||
|
||||
if (CONFIG_IS_ENABLED(ARCH_MVEBU)) {
|
||||
/* Configure SDHCI MBUS mbus bridge windows */
|
||||
sdhci_mvebu_mbus_config((void __iomem *)regbase);
|
||||
}
|
||||
|
||||
if (quirks & SDHCI_QUIRK_REG32_RW)
|
||||
host->version = sdhci_readl(host, SDHCI_HOST_VERSION - 2) >> 16;
|
||||
else
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
return add_sdhci(host, max_clk, min_clk);
|
||||
}
|
||||
438
u-boot/drivers/mmc/mvebu_mmc.c
Normal file
438
u-boot/drivers/mmc/mvebu_mmc.c
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Marvell MMC/SD/SDIO driver
|
||||
*
|
||||
* (C) Copyright 2012-2014
|
||||
* Marvell Semiconductor <www.marvell.com>
|
||||
* Written-by: Maen Suleiman, Gerald Kerma
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <part.h>
|
||||
#include <mmc.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/cpu.h>
|
||||
#include <asm/arch/soc.h>
|
||||
#include <mvebu_mmc.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
#define DRIVER_NAME "MVEBU_MMC"
|
||||
|
||||
#define MVEBU_TARGET_DRAM 0
|
||||
|
||||
#define TIMEOUT_DELAY 5*CONFIG_SYS_HZ /* wait 5 seconds */
|
||||
|
||||
static void mvebu_mmc_write(u32 offs, u32 val)
|
||||
{
|
||||
writel(val, CONFIG_SYS_MMC_BASE + (offs));
|
||||
}
|
||||
|
||||
static u32 mvebu_mmc_read(u32 offs)
|
||||
{
|
||||
return readl(CONFIG_SYS_MMC_BASE + (offs));
|
||||
}
|
||||
|
||||
static int mvebu_mmc_setup_data(struct mmc_data *data)
|
||||
{
|
||||
u32 ctrl_reg;
|
||||
|
||||
debug("%s, data %s : blocks=%d blksz=%d\n", DRIVER_NAME,
|
||||
(data->flags & MMC_DATA_READ) ? "read" : "write",
|
||||
data->blocks, data->blocksize);
|
||||
|
||||
/* default to maximum timeout */
|
||||
ctrl_reg = mvebu_mmc_read(SDIO_HOST_CTRL);
|
||||
ctrl_reg |= SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX);
|
||||
mvebu_mmc_write(SDIO_HOST_CTRL, ctrl_reg);
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
mvebu_mmc_write(SDIO_SYS_ADDR_LOW, (u32)data->dest & 0xffff);
|
||||
mvebu_mmc_write(SDIO_SYS_ADDR_HI, (u32)data->dest >> 16);
|
||||
} else {
|
||||
mvebu_mmc_write(SDIO_SYS_ADDR_LOW, (u32)data->src & 0xffff);
|
||||
mvebu_mmc_write(SDIO_SYS_ADDR_HI, (u32)data->src >> 16);
|
||||
}
|
||||
|
||||
mvebu_mmc_write(SDIO_BLK_COUNT, data->blocks);
|
||||
mvebu_mmc_write(SDIO_BLK_SIZE, data->blocksize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mvebu_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
ulong start;
|
||||
ushort waittype = 0;
|
||||
ushort resptype = 0;
|
||||
ushort xfertype = 0;
|
||||
ushort resp_indx = 0;
|
||||
|
||||
debug("%s: cmdidx [0x%x] resp_type[0x%x] cmdarg[0x%x]\n",
|
||||
DRIVER_NAME, cmd->cmdidx, cmd->resp_type, cmd->cmdarg);
|
||||
|
||||
debug("%s: cmd %d (hw state 0x%04x)\n", DRIVER_NAME,
|
||||
cmd->cmdidx, mvebu_mmc_read(SDIO_HW_STATE));
|
||||
|
||||
/*
|
||||
* Hardware weirdness. The FIFO_EMPTY bit of the HW_STATE
|
||||
* register is sometimes not set before a while when some
|
||||
* "unusual" data block sizes are used (such as with the SWITCH
|
||||
* command), even despite the fact that the XFER_DONE interrupt
|
||||
* was raised. And if another data transfer starts before
|
||||
* this bit comes to good sense (which eventually happens by
|
||||
* itself) then the new transfer simply fails with a timeout.
|
||||
*/
|
||||
if (!(mvebu_mmc_read(SDIO_HW_STATE) & CMD_FIFO_EMPTY)) {
|
||||
ushort hw_state, count = 0;
|
||||
|
||||
start = get_timer(0);
|
||||
do {
|
||||
hw_state = mvebu_mmc_read(SDIO_HW_STATE);
|
||||
if ((get_timer(0) - start) > TIMEOUT_DELAY) {
|
||||
printf("%s : FIFO_EMPTY bit missing\n",
|
||||
DRIVER_NAME);
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
} while (!(hw_state & CMD_FIFO_EMPTY));
|
||||
debug("%s *** wait for FIFO_EMPTY bit (hw=0x%04x, count=%d, jiffies=%ld)\n",
|
||||
DRIVER_NAME, hw_state, count, (get_timer(0) - (start)));
|
||||
}
|
||||
|
||||
/* Clear status */
|
||||
mvebu_mmc_write(SDIO_NOR_INTR_STATUS, SDIO_POLL_MASK);
|
||||
mvebu_mmc_write(SDIO_ERR_INTR_STATUS, SDIO_POLL_MASK);
|
||||
|
||||
resptype = SDIO_CMD_INDEX(cmd->cmdidx);
|
||||
|
||||
/* Analyzing resptype/xfertype/waittype for the command */
|
||||
if (cmd->resp_type & MMC_RSP_BUSY)
|
||||
resptype |= SDIO_CMD_RSP_48BUSY;
|
||||
else if (cmd->resp_type & MMC_RSP_136)
|
||||
resptype |= SDIO_CMD_RSP_136;
|
||||
else if (cmd->resp_type & MMC_RSP_PRESENT)
|
||||
resptype |= SDIO_CMD_RSP_48;
|
||||
else
|
||||
resptype |= SDIO_CMD_RSP_NONE;
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_CRC)
|
||||
resptype |= SDIO_CMD_CHECK_CMDCRC;
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_OPCODE)
|
||||
resptype |= SDIO_CMD_INDX_CHECK;
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_PRESENT) {
|
||||
resptype |= SDIO_UNEXPECTED_RESP;
|
||||
waittype |= SDIO_NOR_UNEXP_RSP;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
int err = mvebu_mmc_setup_data(data);
|
||||
|
||||
if (err) {
|
||||
debug("%s: command DATA error :%x\n",
|
||||
DRIVER_NAME, err);
|
||||
return err;
|
||||
}
|
||||
|
||||
resptype |= SDIO_CMD_DATA_PRESENT | SDIO_CMD_CHECK_DATACRC16;
|
||||
xfertype |= SDIO_XFER_MODE_HW_WR_DATA_EN;
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
xfertype |= SDIO_XFER_MODE_TO_HOST;
|
||||
waittype = SDIO_NOR_DMA_INI;
|
||||
} else {
|
||||
waittype |= SDIO_NOR_XFER_DONE;
|
||||
}
|
||||
} else {
|
||||
waittype |= SDIO_NOR_CMD_DONE;
|
||||
}
|
||||
|
||||
/* Setting cmd arguments */
|
||||
mvebu_mmc_write(SDIO_ARG_LOW, cmd->cmdarg & 0xffff);
|
||||
mvebu_mmc_write(SDIO_ARG_HI, cmd->cmdarg >> 16);
|
||||
|
||||
/* Setting Xfer mode */
|
||||
mvebu_mmc_write(SDIO_XFER_MODE, xfertype);
|
||||
|
||||
/* Sending command */
|
||||
mvebu_mmc_write(SDIO_CMD, resptype);
|
||||
|
||||
start = get_timer(0);
|
||||
|
||||
while (!((mvebu_mmc_read(SDIO_NOR_INTR_STATUS)) & waittype)) {
|
||||
if (mvebu_mmc_read(SDIO_NOR_INTR_STATUS) & SDIO_NOR_ERROR) {
|
||||
debug("%s: error! cmdidx : %d, err reg: %04x\n",
|
||||
DRIVER_NAME, cmd->cmdidx,
|
||||
mvebu_mmc_read(SDIO_ERR_INTR_STATUS));
|
||||
if (mvebu_mmc_read(SDIO_ERR_INTR_STATUS) &
|
||||
(SDIO_ERR_CMD_TIMEOUT | SDIO_ERR_DATA_TIMEOUT)) {
|
||||
debug("%s: command READ timed out\n",
|
||||
DRIVER_NAME);
|
||||
return TIMEOUT;
|
||||
}
|
||||
debug("%s: command READ error\n", DRIVER_NAME);
|
||||
return COMM_ERR;
|
||||
}
|
||||
|
||||
if ((get_timer(0) - start) > TIMEOUT_DELAY) {
|
||||
debug("%s: command timed out\n", DRIVER_NAME);
|
||||
return TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
/* Handling response */
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
uint response[8];
|
||||
|
||||
for (resp_indx = 0; resp_indx < 8; resp_indx++)
|
||||
response[resp_indx]
|
||||
= mvebu_mmc_read(SDIO_RSP(resp_indx));
|
||||
|
||||
cmd->response[0] = ((response[0] & 0x03ff) << 22) |
|
||||
((response[1] & 0xffff) << 6) |
|
||||
((response[2] & 0xfc00) >> 10);
|
||||
cmd->response[1] = ((response[2] & 0x03ff) << 22) |
|
||||
((response[3] & 0xffff) << 6) |
|
||||
((response[4] & 0xfc00) >> 10);
|
||||
cmd->response[2] = ((response[4] & 0x03ff) << 22) |
|
||||
((response[5] & 0xffff) << 6) |
|
||||
((response[6] & 0xfc00) >> 10);
|
||||
cmd->response[3] = ((response[6] & 0x03ff) << 22) |
|
||||
((response[7] & 0x3fff) << 8);
|
||||
} else if (cmd->resp_type & MMC_RSP_PRESENT) {
|
||||
uint response[3];
|
||||
|
||||
for (resp_indx = 0; resp_indx < 3; resp_indx++)
|
||||
response[resp_indx]
|
||||
= mvebu_mmc_read(SDIO_RSP(resp_indx));
|
||||
|
||||
cmd->response[0] = ((response[2] & 0x003f) << (8 - 8)) |
|
||||
((response[1] & 0xffff) << (14 - 8)) |
|
||||
((response[0] & 0x03ff) << (30 - 8));
|
||||
cmd->response[1] = ((response[0] & 0xfc00) >> 10);
|
||||
cmd->response[2] = 0;
|
||||
cmd->response[3] = 0;
|
||||
} else {
|
||||
cmd->response[0] = 0;
|
||||
cmd->response[1] = 0;
|
||||
cmd->response[2] = 0;
|
||||
cmd->response[3] = 0;
|
||||
}
|
||||
|
||||
debug("%s: resp[0x%x] ", DRIVER_NAME, cmd->resp_type);
|
||||
debug("[0x%x] ", cmd->response[0]);
|
||||
debug("[0x%x] ", cmd->response[1]);
|
||||
debug("[0x%x] ", cmd->response[2]);
|
||||
debug("[0x%x] ", cmd->response[3]);
|
||||
debug("\n");
|
||||
|
||||
if (mvebu_mmc_read(SDIO_ERR_INTR_STATUS) &
|
||||
(SDIO_ERR_CMD_TIMEOUT | SDIO_ERR_DATA_TIMEOUT))
|
||||
return TIMEOUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mvebu_mmc_power_up(void)
|
||||
{
|
||||
debug("%s: power up\n", DRIVER_NAME);
|
||||
|
||||
/* disable interrupts */
|
||||
mvebu_mmc_write(SDIO_NOR_INTR_EN, 0);
|
||||
mvebu_mmc_write(SDIO_ERR_INTR_EN, 0);
|
||||
|
||||
/* SW reset */
|
||||
mvebu_mmc_write(SDIO_SW_RESET, SDIO_SW_RESET_NOW);
|
||||
|
||||
mvebu_mmc_write(SDIO_XFER_MODE, 0);
|
||||
|
||||
/* enable status */
|
||||
mvebu_mmc_write(SDIO_NOR_STATUS_EN, SDIO_POLL_MASK);
|
||||
mvebu_mmc_write(SDIO_ERR_STATUS_EN, SDIO_POLL_MASK);
|
||||
|
||||
/* enable interrupts status */
|
||||
mvebu_mmc_write(SDIO_NOR_INTR_STATUS, SDIO_POLL_MASK);
|
||||
mvebu_mmc_write(SDIO_ERR_INTR_STATUS, SDIO_POLL_MASK);
|
||||
}
|
||||
|
||||
static void mvebu_mmc_set_clk(unsigned int clock)
|
||||
{
|
||||
unsigned int m;
|
||||
|
||||
if (clock == 0) {
|
||||
debug("%s: clock off\n", DRIVER_NAME);
|
||||
mvebu_mmc_write(SDIO_XFER_MODE, SDIO_XFER_MODE_STOP_CLK);
|
||||
mvebu_mmc_write(SDIO_CLK_DIV, MVEBU_MMC_BASE_DIV_MAX);
|
||||
} else {
|
||||
m = MVEBU_MMC_BASE_FAST_CLOCK/(2*clock) - 1;
|
||||
if (m > MVEBU_MMC_BASE_DIV_MAX)
|
||||
m = MVEBU_MMC_BASE_DIV_MAX;
|
||||
mvebu_mmc_write(SDIO_CLK_DIV, m & MVEBU_MMC_BASE_DIV_MAX);
|
||||
debug("%s: clock (%d) div : %d\n", DRIVER_NAME, clock, m);
|
||||
}
|
||||
}
|
||||
|
||||
static void mvebu_mmc_set_bus(unsigned int bus)
|
||||
{
|
||||
u32 ctrl_reg = 0;
|
||||
|
||||
ctrl_reg = mvebu_mmc_read(SDIO_HOST_CTRL);
|
||||
ctrl_reg &= ~SDIO_HOST_CTRL_DATA_WIDTH_4_BITS;
|
||||
|
||||
switch (bus) {
|
||||
case 4:
|
||||
ctrl_reg |= SDIO_HOST_CTRL_DATA_WIDTH_4_BITS;
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
ctrl_reg |= SDIO_HOST_CTRL_DATA_WIDTH_1_BIT;
|
||||
}
|
||||
|
||||
/* default transfer mode */
|
||||
ctrl_reg |= SDIO_HOST_CTRL_BIG_ENDIAN;
|
||||
ctrl_reg &= ~SDIO_HOST_CTRL_LSB_FIRST;
|
||||
|
||||
/* default to maximum timeout */
|
||||
ctrl_reg |= SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX);
|
||||
ctrl_reg |= SDIO_HOST_CTRL_TMOUT_EN;
|
||||
|
||||
ctrl_reg |= SDIO_HOST_CTRL_PUSH_PULL_EN;
|
||||
|
||||
ctrl_reg |= SDIO_HOST_CTRL_CARD_TYPE_MEM_ONLY;
|
||||
|
||||
debug("%s: ctrl 0x%04x: %s %s %s\n", DRIVER_NAME, ctrl_reg,
|
||||
(ctrl_reg & SDIO_HOST_CTRL_PUSH_PULL_EN) ?
|
||||
"push-pull" : "open-drain",
|
||||
(ctrl_reg & SDIO_HOST_CTRL_DATA_WIDTH_4_BITS) ?
|
||||
"4bit-width" : "1bit-width",
|
||||
(ctrl_reg & SDIO_HOST_CTRL_HI_SPEED_EN) ?
|
||||
"high-speed" : "");
|
||||
|
||||
mvebu_mmc_write(SDIO_HOST_CTRL, ctrl_reg);
|
||||
}
|
||||
|
||||
static void mvebu_mmc_set_ios(struct mmc *mmc)
|
||||
{
|
||||
debug("%s: bus[%d] clock[%d]\n", DRIVER_NAME,
|
||||
mmc->bus_width, mmc->clock);
|
||||
mvebu_mmc_set_bus(mmc->bus_width);
|
||||
mvebu_mmc_set_clk(mmc->clock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set window register.
|
||||
*/
|
||||
static void mvebu_window_setup(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
mvebu_mmc_write(WINDOW_CTRL(i), 0);
|
||||
mvebu_mmc_write(WINDOW_BASE(i), 0);
|
||||
}
|
||||
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
|
||||
u32 size, base, attrib;
|
||||
|
||||
/* Enable DRAM bank */
|
||||
switch (i) {
|
||||
case 0:
|
||||
attrib = KWCPU_ATTR_DRAM_CS0;
|
||||
break;
|
||||
case 1:
|
||||
attrib = KWCPU_ATTR_DRAM_CS1;
|
||||
break;
|
||||
case 2:
|
||||
attrib = KWCPU_ATTR_DRAM_CS2;
|
||||
break;
|
||||
case 3:
|
||||
attrib = KWCPU_ATTR_DRAM_CS3;
|
||||
break;
|
||||
default:
|
||||
/* invalide bank, disable access */
|
||||
attrib = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
size = gd->bd->bi_dram[i].size;
|
||||
base = gd->bd->bi_dram[i].start;
|
||||
if (size && attrib) {
|
||||
mvebu_mmc_write(WINDOW_CTRL(i),
|
||||
MVCPU_WIN_CTRL_DATA(size,
|
||||
MVEBU_TARGET_DRAM,
|
||||
attrib,
|
||||
MVCPU_WIN_ENABLE));
|
||||
} else {
|
||||
mvebu_mmc_write(WINDOW_CTRL(i), MVCPU_WIN_DISABLE);
|
||||
}
|
||||
mvebu_mmc_write(WINDOW_BASE(i), base);
|
||||
}
|
||||
}
|
||||
|
||||
static int mvebu_mmc_initialize(struct mmc *mmc)
|
||||
{
|
||||
debug("%s: mvebu_mmc_initialize\n", DRIVER_NAME);
|
||||
|
||||
/*
|
||||
* Setting host parameters
|
||||
* Initial Host Ctrl : Timeout : max , Normal Speed mode,
|
||||
* 4-bit data mode, Big Endian, SD memory Card, Push_pull CMD Line
|
||||
*/
|
||||
mvebu_mmc_write(SDIO_HOST_CTRL,
|
||||
SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX) |
|
||||
SDIO_HOST_CTRL_DATA_WIDTH_4_BITS |
|
||||
SDIO_HOST_CTRL_BIG_ENDIAN |
|
||||
SDIO_HOST_CTRL_PUSH_PULL_EN |
|
||||
SDIO_HOST_CTRL_CARD_TYPE_MEM_ONLY);
|
||||
|
||||
mvebu_mmc_write(SDIO_CLK_CTRL, 0);
|
||||
|
||||
/* enable status */
|
||||
mvebu_mmc_write(SDIO_NOR_STATUS_EN, SDIO_POLL_MASK);
|
||||
mvebu_mmc_write(SDIO_ERR_STATUS_EN, SDIO_POLL_MASK);
|
||||
|
||||
/* disable interrupts */
|
||||
mvebu_mmc_write(SDIO_NOR_INTR_EN, 0);
|
||||
mvebu_mmc_write(SDIO_ERR_INTR_EN, 0);
|
||||
|
||||
mvebu_window_setup();
|
||||
|
||||
/* SW reset */
|
||||
mvebu_mmc_write(SDIO_SW_RESET, SDIO_SW_RESET_NOW);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops mvebu_mmc_ops = {
|
||||
.send_cmd = mvebu_mmc_send_cmd,
|
||||
.set_ios = mvebu_mmc_set_ios,
|
||||
.init = mvebu_mmc_initialize,
|
||||
};
|
||||
|
||||
static struct mmc_config mvebu_mmc_cfg = {
|
||||
.name = DRIVER_NAME,
|
||||
.ops = &mvebu_mmc_ops,
|
||||
.f_min = MVEBU_MMC_BASE_FAST_CLOCK / MVEBU_MMC_BASE_DIV_MAX,
|
||||
.f_max = MVEBU_MMC_CLOCKRATE_MAX,
|
||||
.voltages = MMC_VDD_32_33 | MMC_VDD_33_34,
|
||||
.host_caps = MMC_MODE_4BIT | MMC_MODE_HS |
|
||||
MMC_MODE_HS_52MHz,
|
||||
.part_type = PART_TYPE_DOS,
|
||||
.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT,
|
||||
};
|
||||
|
||||
int mvebu_mmc_init(bd_t *bis)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
|
||||
mvebu_mmc_power_up();
|
||||
|
||||
mmc = mmc_create(&mvebu_mmc_cfg, bis);
|
||||
if (mmc == NULL)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
521
u-boot/drivers/mmc/mxcmmc.c
Normal file
521
u-boot/drivers/mmc/mxcmmc.c
Normal file
@@ -0,0 +1,521 @@
|
||||
/*
|
||||
* This is a driver for the SDHC controller found in Freescale MX2/MX3
|
||||
* SoCs. It is basically the same hardware as found on MX1 (imxmmc.c).
|
||||
* Unlike the hardware found on MX1, this hardware just works and does
|
||||
* not need all the quirks found in imxmmc.c, hence the seperate driver.
|
||||
*
|
||||
* Copyright (C) 2009 Ilya Yanok, <yanok@emcraft.com>
|
||||
* Copyright (C) 2008 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
|
||||
* Copyright (C) 2006 Pavel Pisa, PiKRON <ppisa@pikron.com>
|
||||
*
|
||||
* derived from pxamci.c by Russell King
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <common.h>
|
||||
#include <command.h>
|
||||
#include <mmc.h>
|
||||
#include <part.h>
|
||||
#include <malloc.h>
|
||||
#include <mmc.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/clock.h>
|
||||
|
||||
#define DRIVER_NAME "mxc-mmc"
|
||||
|
||||
struct mxcmci_regs {
|
||||
u32 str_stp_clk;
|
||||
u32 status;
|
||||
u32 clk_rate;
|
||||
u32 cmd_dat_cont;
|
||||
u32 res_to;
|
||||
u32 read_to;
|
||||
u32 blk_len;
|
||||
u32 nob;
|
||||
u32 rev_no;
|
||||
u32 int_cntr;
|
||||
u32 cmd;
|
||||
u32 arg;
|
||||
u32 pad;
|
||||
u32 res_fifo;
|
||||
u32 buffer_access;
|
||||
};
|
||||
|
||||
#define STR_STP_CLK_RESET (1 << 3)
|
||||
#define STR_STP_CLK_START_CLK (1 << 1)
|
||||
#define STR_STP_CLK_STOP_CLK (1 << 0)
|
||||
|
||||
#define STATUS_CARD_INSERTION (1 << 31)
|
||||
#define STATUS_CARD_REMOVAL (1 << 30)
|
||||
#define STATUS_YBUF_EMPTY (1 << 29)
|
||||
#define STATUS_XBUF_EMPTY (1 << 28)
|
||||
#define STATUS_YBUF_FULL (1 << 27)
|
||||
#define STATUS_XBUF_FULL (1 << 26)
|
||||
#define STATUS_BUF_UND_RUN (1 << 25)
|
||||
#define STATUS_BUF_OVFL (1 << 24)
|
||||
#define STATUS_SDIO_INT_ACTIVE (1 << 14)
|
||||
#define STATUS_END_CMD_RESP (1 << 13)
|
||||
#define STATUS_WRITE_OP_DONE (1 << 12)
|
||||
#define STATUS_DATA_TRANS_DONE (1 << 11)
|
||||
#define STATUS_READ_OP_DONE (1 << 11)
|
||||
#define STATUS_WR_CRC_ERROR_CODE_MASK (3 << 10)
|
||||
#define STATUS_CARD_BUS_CLK_RUN (1 << 8)
|
||||
#define STATUS_BUF_READ_RDY (1 << 7)
|
||||
#define STATUS_BUF_WRITE_RDY (1 << 6)
|
||||
#define STATUS_RESP_CRC_ERR (1 << 5)
|
||||
#define STATUS_CRC_READ_ERR (1 << 3)
|
||||
#define STATUS_CRC_WRITE_ERR (1 << 2)
|
||||
#define STATUS_TIME_OUT_RESP (1 << 1)
|
||||
#define STATUS_TIME_OUT_READ (1 << 0)
|
||||
#define STATUS_ERR_MASK 0x2f
|
||||
|
||||
#define CMD_DAT_CONT_CMD_RESP_LONG_OFF (1 << 12)
|
||||
#define CMD_DAT_CONT_STOP_READWAIT (1 << 11)
|
||||
#define CMD_DAT_CONT_START_READWAIT (1 << 10)
|
||||
#define CMD_DAT_CONT_BUS_WIDTH_4 (2 << 8)
|
||||
#define CMD_DAT_CONT_INIT (1 << 7)
|
||||
#define CMD_DAT_CONT_WRITE (1 << 4)
|
||||
#define CMD_DAT_CONT_DATA_ENABLE (1 << 3)
|
||||
#define CMD_DAT_CONT_RESPONSE_48BIT_CRC (1 << 0)
|
||||
#define CMD_DAT_CONT_RESPONSE_136BIT (2 << 0)
|
||||
#define CMD_DAT_CONT_RESPONSE_48BIT (3 << 0)
|
||||
|
||||
#define INT_SDIO_INT_WKP_EN (1 << 18)
|
||||
#define INT_CARD_INSERTION_WKP_EN (1 << 17)
|
||||
#define INT_CARD_REMOVAL_WKP_EN (1 << 16)
|
||||
#define INT_CARD_INSERTION_EN (1 << 15)
|
||||
#define INT_CARD_REMOVAL_EN (1 << 14)
|
||||
#define INT_SDIO_IRQ_EN (1 << 13)
|
||||
#define INT_DAT0_EN (1 << 12)
|
||||
#define INT_BUF_READ_EN (1 << 4)
|
||||
#define INT_BUF_WRITE_EN (1 << 3)
|
||||
#define INT_END_CMD_RES_EN (1 << 2)
|
||||
#define INT_WRITE_OP_DONE_EN (1 << 1)
|
||||
#define INT_READ_OP_EN (1 << 0)
|
||||
|
||||
struct mxcmci_host {
|
||||
struct mmc *mmc;
|
||||
struct mxcmci_regs *base;
|
||||
int irq;
|
||||
int detect_irq;
|
||||
int dma;
|
||||
int do_dma;
|
||||
unsigned int power_mode;
|
||||
|
||||
struct mmc_cmd *cmd;
|
||||
struct mmc_data *data;
|
||||
|
||||
unsigned int dma_nents;
|
||||
unsigned int datasize;
|
||||
unsigned int dma_dir;
|
||||
|
||||
u16 rev_no;
|
||||
unsigned int cmdat;
|
||||
|
||||
int clock;
|
||||
};
|
||||
|
||||
static struct mxcmci_host mxcmci_host;
|
||||
|
||||
/* maintainer note: do we really want to have a global host pointer? */
|
||||
static struct mxcmci_host *host = &mxcmci_host;
|
||||
|
||||
static inline int mxcmci_use_dma(struct mxcmci_host *host)
|
||||
{
|
||||
return host->do_dma;
|
||||
}
|
||||
|
||||
static void mxcmci_softreset(struct mxcmci_host *host)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* reset sequence */
|
||||
writel(STR_STP_CLK_RESET, &host->base->str_stp_clk);
|
||||
writel(STR_STP_CLK_RESET | STR_STP_CLK_START_CLK,
|
||||
&host->base->str_stp_clk);
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
writel(STR_STP_CLK_START_CLK, &host->base->str_stp_clk);
|
||||
|
||||
writel(0xff, &host->base->res_to);
|
||||
}
|
||||
|
||||
static void mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data)
|
||||
{
|
||||
unsigned int nob = data->blocks;
|
||||
unsigned int blksz = data->blocksize;
|
||||
unsigned int datasize = nob * blksz;
|
||||
|
||||
host->data = data;
|
||||
|
||||
writel(nob, &host->base->nob);
|
||||
writel(blksz, &host->base->blk_len);
|
||||
host->datasize = datasize;
|
||||
}
|
||||
|
||||
static int mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_cmd *cmd,
|
||||
unsigned int cmdat)
|
||||
{
|
||||
if (host->cmd != NULL)
|
||||
printf("mxcmci: error!\n");
|
||||
host->cmd = cmd;
|
||||
|
||||
switch (cmd->resp_type) {
|
||||
case MMC_RSP_R1: /* short CRC, OPCODE */
|
||||
case MMC_RSP_R1b:/* short CRC, OPCODE, BUSY */
|
||||
cmdat |= CMD_DAT_CONT_RESPONSE_48BIT_CRC;
|
||||
break;
|
||||
case MMC_RSP_R2: /* long 136 bit + CRC */
|
||||
cmdat |= CMD_DAT_CONT_RESPONSE_136BIT;
|
||||
break;
|
||||
case MMC_RSP_R3: /* short */
|
||||
cmdat |= CMD_DAT_CONT_RESPONSE_48BIT;
|
||||
break;
|
||||
case MMC_RSP_NONE:
|
||||
break;
|
||||
default:
|
||||
printf("mxcmci: unhandled response type 0x%x\n",
|
||||
cmd->resp_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(cmd->cmdidx, &host->base->cmd);
|
||||
writel(cmd->cmdarg, &host->base->arg);
|
||||
writel(cmdat, &host->base->cmd_dat_cont);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mxcmci_finish_request(struct mxcmci_host *host,
|
||||
struct mmc_cmd *cmd, struct mmc_data *data)
|
||||
{
|
||||
host->cmd = NULL;
|
||||
host->data = NULL;
|
||||
}
|
||||
|
||||
static int mxcmci_finish_data(struct mxcmci_host *host, unsigned int stat)
|
||||
{
|
||||
int data_error = 0;
|
||||
|
||||
if (stat & STATUS_ERR_MASK) {
|
||||
printf("request failed. status: 0x%08x\n",
|
||||
stat);
|
||||
if (stat & STATUS_CRC_READ_ERR) {
|
||||
data_error = -EILSEQ;
|
||||
} else if (stat & STATUS_CRC_WRITE_ERR) {
|
||||
u32 err_code = (stat >> 9) & 0x3;
|
||||
if (err_code == 2) /* No CRC response */
|
||||
data_error = TIMEOUT;
|
||||
else
|
||||
data_error = -EILSEQ;
|
||||
} else if (stat & STATUS_TIME_OUT_READ) {
|
||||
data_error = TIMEOUT;
|
||||
} else {
|
||||
data_error = -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
host->data = NULL;
|
||||
|
||||
return data_error;
|
||||
}
|
||||
|
||||
static int mxcmci_read_response(struct mxcmci_host *host, unsigned int stat)
|
||||
{
|
||||
struct mmc_cmd *cmd = host->cmd;
|
||||
int i;
|
||||
u32 a, b, c;
|
||||
u32 *resp = (u32 *)cmd->response;
|
||||
|
||||
if (!cmd)
|
||||
return 0;
|
||||
|
||||
if (stat & STATUS_TIME_OUT_RESP) {
|
||||
printf("CMD TIMEOUT\n");
|
||||
return TIMEOUT;
|
||||
} else if (stat & STATUS_RESP_CRC_ERR && cmd->resp_type & MMC_RSP_CRC) {
|
||||
printf("cmd crc error\n");
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_PRESENT) {
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
for (i = 0; i < 4; i++) {
|
||||
a = readl(&host->base->res_fifo) & 0xFFFF;
|
||||
b = readl(&host->base->res_fifo) & 0xFFFF;
|
||||
resp[i] = a << 16 | b;
|
||||
}
|
||||
} else {
|
||||
a = readl(&host->base->res_fifo) & 0xFFFF;
|
||||
b = readl(&host->base->res_fifo) & 0xFFFF;
|
||||
c = readl(&host->base->res_fifo) & 0xFFFF;
|
||||
resp[0] = a << 24 | b << 8 | c >> 8;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxcmci_poll_status(struct mxcmci_host *host, u32 mask)
|
||||
{
|
||||
u32 stat;
|
||||
unsigned long timeout = get_ticks() + CONFIG_SYS_HZ;
|
||||
|
||||
do {
|
||||
stat = readl(&host->base->status);
|
||||
if (stat & STATUS_ERR_MASK)
|
||||
return stat;
|
||||
if (timeout < get_ticks())
|
||||
return STATUS_TIME_OUT_READ;
|
||||
if (stat & mask)
|
||||
return 0;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int mxcmci_pull(struct mxcmci_host *host, void *_buf, int bytes)
|
||||
{
|
||||
unsigned int stat;
|
||||
u32 *buf = _buf;
|
||||
|
||||
while (bytes > 3) {
|
||||
stat = mxcmci_poll_status(host,
|
||||
STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE);
|
||||
if (stat)
|
||||
return stat;
|
||||
*buf++ = readl(&host->base->buffer_access);
|
||||
bytes -= 4;
|
||||
}
|
||||
|
||||
if (bytes) {
|
||||
u8 *b = (u8 *)buf;
|
||||
u32 tmp;
|
||||
|
||||
stat = mxcmci_poll_status(host,
|
||||
STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE);
|
||||
if (stat)
|
||||
return stat;
|
||||
tmp = readl(&host->base->buffer_access);
|
||||
memcpy(b, &tmp, bytes);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxcmci_push(struct mxcmci_host *host, const void *_buf, int bytes)
|
||||
{
|
||||
unsigned int stat;
|
||||
const u32 *buf = _buf;
|
||||
|
||||
while (bytes > 3) {
|
||||
stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY);
|
||||
if (stat)
|
||||
return stat;
|
||||
writel(*buf++, &host->base->buffer_access);
|
||||
bytes -= 4;
|
||||
}
|
||||
|
||||
if (bytes) {
|
||||
const u8 *b = (u8 *)buf;
|
||||
u32 tmp;
|
||||
|
||||
stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY);
|
||||
if (stat)
|
||||
return stat;
|
||||
|
||||
memcpy(&tmp, b, bytes);
|
||||
writel(tmp, &host->base->buffer_access);
|
||||
}
|
||||
|
||||
stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY);
|
||||
if (stat)
|
||||
return stat;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxcmci_transfer_data(struct mxcmci_host *host)
|
||||
{
|
||||
struct mmc_data *data = host->data;
|
||||
int stat;
|
||||
unsigned long length;
|
||||
|
||||
length = data->blocks * data->blocksize;
|
||||
host->datasize = 0;
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
stat = mxcmci_pull(host, data->dest, length);
|
||||
if (stat)
|
||||
return stat;
|
||||
host->datasize += length;
|
||||
} else {
|
||||
stat = mxcmci_push(host, (const void *)(data->src), length);
|
||||
if (stat)
|
||||
return stat;
|
||||
host->datasize += length;
|
||||
stat = mxcmci_poll_status(host, STATUS_WRITE_OP_DONE);
|
||||
if (stat)
|
||||
return stat;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat)
|
||||
{
|
||||
int datastat;
|
||||
int ret;
|
||||
|
||||
ret = mxcmci_read_response(host, stat);
|
||||
|
||||
if (ret) {
|
||||
mxcmci_finish_request(host, host->cmd, host->data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!host->data) {
|
||||
mxcmci_finish_request(host, host->cmd, host->data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
datastat = mxcmci_transfer_data(host);
|
||||
ret = mxcmci_finish_data(host, datastat);
|
||||
mxcmci_finish_request(host, host->cmd, host->data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxcmci_request(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct mxcmci_host *host = mmc->priv;
|
||||
unsigned int cmdat = host->cmdat;
|
||||
u32 stat;
|
||||
int ret;
|
||||
|
||||
host->cmdat &= ~CMD_DAT_CONT_INIT;
|
||||
if (data) {
|
||||
mxcmci_setup_data(host, data);
|
||||
|
||||
cmdat |= CMD_DAT_CONT_DATA_ENABLE;
|
||||
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
cmdat |= CMD_DAT_CONT_WRITE;
|
||||
}
|
||||
|
||||
if ((ret = mxcmci_start_cmd(host, cmd, cmdat))) {
|
||||
mxcmci_finish_request(host, cmd, data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
do {
|
||||
stat = readl(&host->base->status);
|
||||
writel(stat, &host->base->status);
|
||||
} while (!(stat & STATUS_END_CMD_RESP));
|
||||
|
||||
return mxcmci_cmd_done(host, stat);
|
||||
}
|
||||
|
||||
static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios)
|
||||
{
|
||||
unsigned int divider;
|
||||
int prescaler = 0;
|
||||
unsigned long clk_in = mxc_get_clock(MXC_ESDHC_CLK);
|
||||
|
||||
while (prescaler <= 0x800) {
|
||||
for (divider = 1; divider <= 0xF; divider++) {
|
||||
int x;
|
||||
|
||||
x = (clk_in / (divider + 1));
|
||||
|
||||
if (prescaler)
|
||||
x /= (prescaler * 2);
|
||||
|
||||
if (x <= clk_ios)
|
||||
break;
|
||||
}
|
||||
if (divider < 0x10)
|
||||
break;
|
||||
|
||||
if (prescaler == 0)
|
||||
prescaler = 1;
|
||||
else
|
||||
prescaler <<= 1;
|
||||
}
|
||||
|
||||
writel((prescaler << 4) | divider, &host->base->clk_rate);
|
||||
}
|
||||
|
||||
static void mxcmci_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct mxcmci_host *host = mmc->priv;
|
||||
if (mmc->bus_width == 4)
|
||||
host->cmdat |= CMD_DAT_CONT_BUS_WIDTH_4;
|
||||
else
|
||||
host->cmdat &= ~CMD_DAT_CONT_BUS_WIDTH_4;
|
||||
|
||||
if (mmc->clock) {
|
||||
mxcmci_set_clk_rate(host, mmc->clock);
|
||||
writel(STR_STP_CLK_START_CLK, &host->base->str_stp_clk);
|
||||
} else {
|
||||
writel(STR_STP_CLK_STOP_CLK, &host->base->str_stp_clk);
|
||||
}
|
||||
|
||||
host->clock = mmc->clock;
|
||||
}
|
||||
|
||||
static int mxcmci_init(struct mmc *mmc)
|
||||
{
|
||||
struct mxcmci_host *host = mmc->priv;
|
||||
|
||||
mxcmci_softreset(host);
|
||||
|
||||
host->rev_no = readl(&host->base->rev_no);
|
||||
if (host->rev_no != 0x400) {
|
||||
printf("wrong rev.no. 0x%08x. aborting.\n",
|
||||
host->rev_no);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* recommended in data sheet */
|
||||
writel(0x2db4, &host->base->read_to);
|
||||
|
||||
writel(0, &host->base->int_cntr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops mxcmci_ops = {
|
||||
.send_cmd = mxcmci_request,
|
||||
.set_ios = mxcmci_set_ios,
|
||||
.init = mxcmci_init,
|
||||
};
|
||||
|
||||
static struct mmc_config mxcmci_cfg = {
|
||||
.name = "MXC MCI",
|
||||
.ops = &mxcmci_ops,
|
||||
.host_caps = MMC_MODE_4BIT,
|
||||
.voltages = MMC_VDD_32_33 | MMC_VDD_33_34,
|
||||
.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT,
|
||||
};
|
||||
|
||||
static int mxcmci_initialize(bd_t *bis)
|
||||
{
|
||||
host->base = (struct mxcmci_regs *)CONFIG_MXC_MCI_REGS_BASE;
|
||||
|
||||
mxcmci_cfg.f_min = mxc_get_clock(MXC_ESDHC_CLK) >> 7;
|
||||
mxcmci_cfg.f_max = mxc_get_clock(MXC_ESDHC_CLK) >> 1;
|
||||
|
||||
host->mmc = mmc_create(&mxcmci_cfg, host);
|
||||
if (host->mmc == NULL)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mxc_mmc_init(bd_t *bis)
|
||||
{
|
||||
return mxcmci_initialize(bis);
|
||||
}
|
||||
427
u-boot/drivers/mmc/mxsmmc.c
Normal file
427
u-boot/drivers/mmc/mxsmmc.c
Normal file
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Freescale i.MX28 SSP MMC driver
|
||||
*
|
||||
* Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com>
|
||||
* on behalf of DENX Software Engineering GmbH
|
||||
*
|
||||
* Based on code from LTIB:
|
||||
* (C) Copyright 2008-2010 Freescale Semiconductor, Inc.
|
||||
* Terry Lv
|
||||
*
|
||||
* Copyright 2007, Freescale Semiconductor, Inc
|
||||
* Andy Fleming
|
||||
*
|
||||
* Based vaguely on the pxa mmc code:
|
||||
* (C) Copyright 2003
|
||||
* Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <mmc.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/clock.h>
|
||||
#include <asm/arch/imx-regs.h>
|
||||
#include <asm/arch/sys_proto.h>
|
||||
#include <asm/imx-common/dma.h>
|
||||
#include <bouncebuf.h>
|
||||
|
||||
struct mxsmmc_priv {
|
||||
int id;
|
||||
struct mxs_ssp_regs *regs;
|
||||
uint32_t buswidth;
|
||||
int (*mmc_is_wp)(int);
|
||||
int (*mmc_cd)(int);
|
||||
struct mxs_dma_desc *desc;
|
||||
struct mmc_config cfg; /* mmc configuration */
|
||||
};
|
||||
|
||||
#define MXSMMC_MAX_TIMEOUT 10000
|
||||
#define MXSMMC_SMALL_TRANSFER 512
|
||||
|
||||
static int mxsmmc_cd(struct mxsmmc_priv *priv)
|
||||
{
|
||||
struct mxs_ssp_regs *ssp_regs = priv->regs;
|
||||
|
||||
if (priv->mmc_cd)
|
||||
return priv->mmc_cd(priv->id);
|
||||
|
||||
return !(readl(&ssp_regs->hw_ssp_status) & SSP_STATUS_CARD_DETECT);
|
||||
}
|
||||
|
||||
static int mxsmmc_send_cmd_pio(struct mxsmmc_priv *priv, struct mmc_data *data)
|
||||
{
|
||||
struct mxs_ssp_regs *ssp_regs = priv->regs;
|
||||
uint32_t *data_ptr;
|
||||
int timeout = MXSMMC_MAX_TIMEOUT;
|
||||
uint32_t reg;
|
||||
uint32_t data_count = data->blocksize * data->blocks;
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
data_ptr = (uint32_t *)data->dest;
|
||||
while (data_count && --timeout) {
|
||||
reg = readl(&ssp_regs->hw_ssp_status);
|
||||
if (!(reg & SSP_STATUS_FIFO_EMPTY)) {
|
||||
*data_ptr++ = readl(&ssp_regs->hw_ssp_data);
|
||||
data_count -= 4;
|
||||
timeout = MXSMMC_MAX_TIMEOUT;
|
||||
} else
|
||||
udelay(1000);
|
||||
}
|
||||
} else {
|
||||
data_ptr = (uint32_t *)data->src;
|
||||
timeout *= 100;
|
||||
while (data_count && --timeout) {
|
||||
reg = readl(&ssp_regs->hw_ssp_status);
|
||||
if (!(reg & SSP_STATUS_FIFO_FULL)) {
|
||||
writel(*data_ptr++, &ssp_regs->hw_ssp_data);
|
||||
data_count -= 4;
|
||||
timeout = MXSMMC_MAX_TIMEOUT;
|
||||
} else
|
||||
udelay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
return timeout ? 0 : COMM_ERR;
|
||||
}
|
||||
|
||||
static int mxsmmc_send_cmd_dma(struct mxsmmc_priv *priv, struct mmc_data *data)
|
||||
{
|
||||
uint32_t data_count = data->blocksize * data->blocks;
|
||||
int dmach;
|
||||
struct mxs_dma_desc *desc = priv->desc;
|
||||
void *addr;
|
||||
unsigned int flags;
|
||||
struct bounce_buffer bbstate;
|
||||
|
||||
memset(desc, 0, sizeof(struct mxs_dma_desc));
|
||||
desc->address = (dma_addr_t)desc;
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
priv->desc->cmd.data = MXS_DMA_DESC_COMMAND_DMA_WRITE;
|
||||
addr = data->dest;
|
||||
flags = GEN_BB_WRITE;
|
||||
} else {
|
||||
priv->desc->cmd.data = MXS_DMA_DESC_COMMAND_DMA_READ;
|
||||
addr = (void *)data->src;
|
||||
flags = GEN_BB_READ;
|
||||
}
|
||||
|
||||
bounce_buffer_start(&bbstate, addr, data_count, flags);
|
||||
|
||||
priv->desc->cmd.address = (dma_addr_t)bbstate.bounce_buffer;
|
||||
|
||||
priv->desc->cmd.data |= MXS_DMA_DESC_IRQ | MXS_DMA_DESC_DEC_SEM |
|
||||
(data_count << MXS_DMA_DESC_BYTES_OFFSET);
|
||||
|
||||
dmach = MXS_DMA_CHANNEL_AHB_APBH_SSP0 + priv->id;
|
||||
mxs_dma_desc_append(dmach, priv->desc);
|
||||
if (mxs_dma_go(dmach)) {
|
||||
bounce_buffer_stop(&bbstate);
|
||||
return COMM_ERR;
|
||||
}
|
||||
|
||||
bounce_buffer_stop(&bbstate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a command out on the bus. Takes the mmc pointer,
|
||||
* a command pointer, and an optional data pointer.
|
||||
*/
|
||||
static int
|
||||
mxsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
|
||||
{
|
||||
struct mxsmmc_priv *priv = mmc->priv;
|
||||
struct mxs_ssp_regs *ssp_regs = priv->regs;
|
||||
uint32_t reg;
|
||||
int timeout;
|
||||
uint32_t ctrl0;
|
||||
int ret;
|
||||
|
||||
debug("MMC%d: CMD%d\n", mmc->block_dev.devnum, cmd->cmdidx);
|
||||
|
||||
/* Check bus busy */
|
||||
timeout = MXSMMC_MAX_TIMEOUT;
|
||||
while (--timeout) {
|
||||
udelay(1000);
|
||||
reg = readl(&ssp_regs->hw_ssp_status);
|
||||
if (!(reg &
|
||||
(SSP_STATUS_BUSY | SSP_STATUS_DATA_BUSY |
|
||||
SSP_STATUS_CMD_BUSY))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!timeout) {
|
||||
printf("MMC%d: Bus busy timeout!\n", mmc->block_dev.devnum);
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
/* See if card is present */
|
||||
if (!mxsmmc_cd(priv)) {
|
||||
printf("MMC%d: No card detected!\n", mmc->block_dev.devnum);
|
||||
return NO_CARD_ERR;
|
||||
}
|
||||
|
||||
/* Start building CTRL0 contents */
|
||||
ctrl0 = priv->buswidth;
|
||||
|
||||
/* Set up command */
|
||||
if (!(cmd->resp_type & MMC_RSP_CRC))
|
||||
ctrl0 |= SSP_CTRL0_IGNORE_CRC;
|
||||
if (cmd->resp_type & MMC_RSP_PRESENT) /* Need to get response */
|
||||
ctrl0 |= SSP_CTRL0_GET_RESP;
|
||||
if (cmd->resp_type & MMC_RSP_136) /* It's a 136 bits response */
|
||||
ctrl0 |= SSP_CTRL0_LONG_RESP;
|
||||
|
||||
if (data && (data->blocksize * data->blocks < MXSMMC_SMALL_TRANSFER))
|
||||
writel(SSP_CTRL1_DMA_ENABLE, &ssp_regs->hw_ssp_ctrl1_clr);
|
||||
else
|
||||
writel(SSP_CTRL1_DMA_ENABLE, &ssp_regs->hw_ssp_ctrl1_set);
|
||||
|
||||
/* Command index */
|
||||
reg = readl(&ssp_regs->hw_ssp_cmd0);
|
||||
reg &= ~(SSP_CMD0_CMD_MASK | SSP_CMD0_APPEND_8CYC);
|
||||
reg |= cmd->cmdidx << SSP_CMD0_CMD_OFFSET;
|
||||
if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION)
|
||||
reg |= SSP_CMD0_APPEND_8CYC;
|
||||
writel(reg, &ssp_regs->hw_ssp_cmd0);
|
||||
|
||||
/* Command argument */
|
||||
writel(cmd->cmdarg, &ssp_regs->hw_ssp_cmd1);
|
||||
|
||||
/* Set up data */
|
||||
if (data) {
|
||||
/* READ or WRITE */
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
ctrl0 |= SSP_CTRL0_READ;
|
||||
} else if (priv->mmc_is_wp &&
|
||||
priv->mmc_is_wp(mmc->block_dev.devnum)) {
|
||||
printf("MMC%d: Can not write a locked card!\n",
|
||||
mmc->block_dev.devnum);
|
||||
return UNUSABLE_ERR;
|
||||
}
|
||||
|
||||
ctrl0 |= SSP_CTRL0_DATA_XFER;
|
||||
|
||||
reg = data->blocksize * data->blocks;
|
||||
#if defined(CONFIG_MX23)
|
||||
ctrl0 |= reg & SSP_CTRL0_XFER_COUNT_MASK;
|
||||
|
||||
clrsetbits_le32(&ssp_regs->hw_ssp_cmd0,
|
||||
SSP_CMD0_BLOCK_SIZE_MASK | SSP_CMD0_BLOCK_COUNT_MASK,
|
||||
((data->blocks - 1) << SSP_CMD0_BLOCK_COUNT_OFFSET) |
|
||||
((ffs(data->blocksize) - 1) <<
|
||||
SSP_CMD0_BLOCK_SIZE_OFFSET));
|
||||
#elif defined(CONFIG_MX28)
|
||||
writel(reg, &ssp_regs->hw_ssp_xfer_size);
|
||||
|
||||
reg = ((data->blocks - 1) <<
|
||||
SSP_BLOCK_SIZE_BLOCK_COUNT_OFFSET) |
|
||||
((ffs(data->blocksize) - 1) <<
|
||||
SSP_BLOCK_SIZE_BLOCK_SIZE_OFFSET);
|
||||
writel(reg, &ssp_regs->hw_ssp_block_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Kick off the command */
|
||||
ctrl0 |= SSP_CTRL0_WAIT_FOR_IRQ | SSP_CTRL0_ENABLE | SSP_CTRL0_RUN;
|
||||
writel(ctrl0, &ssp_regs->hw_ssp_ctrl0);
|
||||
|
||||
/* Wait for the command to complete */
|
||||
timeout = MXSMMC_MAX_TIMEOUT;
|
||||
while (--timeout) {
|
||||
udelay(1000);
|
||||
reg = readl(&ssp_regs->hw_ssp_status);
|
||||
if (!(reg & SSP_STATUS_CMD_BUSY))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!timeout) {
|
||||
printf("MMC%d: Command %d busy\n",
|
||||
mmc->block_dev.devnum, cmd->cmdidx);
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
/* Check command timeout */
|
||||
if (reg & SSP_STATUS_RESP_TIMEOUT) {
|
||||
printf("MMC%d: Command %d timeout (status 0x%08x)\n",
|
||||
mmc->block_dev.devnum, cmd->cmdidx, reg);
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
/* Check command errors */
|
||||
if (reg & (SSP_STATUS_RESP_CRC_ERR | SSP_STATUS_RESP_ERR)) {
|
||||
printf("MMC%d: Command %d error (status 0x%08x)!\n",
|
||||
mmc->block_dev.devnum, cmd->cmdidx, reg);
|
||||
return COMM_ERR;
|
||||
}
|
||||
|
||||
/* Copy response to response buffer */
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
cmd->response[3] = readl(&ssp_regs->hw_ssp_sdresp0);
|
||||
cmd->response[2] = readl(&ssp_regs->hw_ssp_sdresp1);
|
||||
cmd->response[1] = readl(&ssp_regs->hw_ssp_sdresp2);
|
||||
cmd->response[0] = readl(&ssp_regs->hw_ssp_sdresp3);
|
||||
} else
|
||||
cmd->response[0] = readl(&ssp_regs->hw_ssp_sdresp0);
|
||||
|
||||
/* Return if no data to process */
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
if (data->blocksize * data->blocks < MXSMMC_SMALL_TRANSFER) {
|
||||
ret = mxsmmc_send_cmd_pio(priv, data);
|
||||
if (ret) {
|
||||
printf("MMC%d: Data timeout with command %d "
|
||||
"(status 0x%08x)!\n",
|
||||
mmc->block_dev.devnum, cmd->cmdidx, reg);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = mxsmmc_send_cmd_dma(priv, data);
|
||||
if (ret) {
|
||||
printf("MMC%d: DMA transfer failed\n",
|
||||
mmc->block_dev.devnum);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check data errors */
|
||||
reg = readl(&ssp_regs->hw_ssp_status);
|
||||
if (reg &
|
||||
(SSP_STATUS_TIMEOUT | SSP_STATUS_DATA_CRC_ERR |
|
||||
SSP_STATUS_FIFO_OVRFLW | SSP_STATUS_FIFO_UNDRFLW)) {
|
||||
printf("MMC%d: Data error with command %d (status 0x%08x)!\n",
|
||||
mmc->block_dev.devnum, cmd->cmdidx, reg);
|
||||
return COMM_ERR;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mxsmmc_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct mxsmmc_priv *priv = mmc->priv;
|
||||
struct mxs_ssp_regs *ssp_regs = priv->regs;
|
||||
|
||||
/* Set the clock speed */
|
||||
if (mmc->clock)
|
||||
mxs_set_ssp_busclock(priv->id, mmc->clock / 1000);
|
||||
|
||||
switch (mmc->bus_width) {
|
||||
case 1:
|
||||
priv->buswidth = SSP_CTRL0_BUS_WIDTH_ONE_BIT;
|
||||
break;
|
||||
case 4:
|
||||
priv->buswidth = SSP_CTRL0_BUS_WIDTH_FOUR_BIT;
|
||||
break;
|
||||
case 8:
|
||||
priv->buswidth = SSP_CTRL0_BUS_WIDTH_EIGHT_BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set the bus width */
|
||||
clrsetbits_le32(&ssp_regs->hw_ssp_ctrl0,
|
||||
SSP_CTRL0_BUS_WIDTH_MASK, priv->buswidth);
|
||||
|
||||
debug("MMC%d: Set %d bits bus width\n",
|
||||
mmc->block_dev.devnum, mmc->bus_width);
|
||||
}
|
||||
|
||||
static int mxsmmc_init(struct mmc *mmc)
|
||||
{
|
||||
struct mxsmmc_priv *priv = mmc->priv;
|
||||
struct mxs_ssp_regs *ssp_regs = priv->regs;
|
||||
|
||||
/* Reset SSP */
|
||||
mxs_reset_block(&ssp_regs->hw_ssp_ctrl0_reg);
|
||||
|
||||
/* Reconfigure the SSP block for MMC operation */
|
||||
writel(SSP_CTRL1_SSP_MODE_SD_MMC |
|
||||
SSP_CTRL1_WORD_LENGTH_EIGHT_BITS |
|
||||
SSP_CTRL1_DMA_ENABLE |
|
||||
SSP_CTRL1_POLARITY |
|
||||
SSP_CTRL1_RECV_TIMEOUT_IRQ_EN |
|
||||
SSP_CTRL1_DATA_CRC_IRQ_EN |
|
||||
SSP_CTRL1_DATA_TIMEOUT_IRQ_EN |
|
||||
SSP_CTRL1_RESP_TIMEOUT_IRQ_EN |
|
||||
SSP_CTRL1_RESP_ERR_IRQ_EN,
|
||||
&ssp_regs->hw_ssp_ctrl1_set);
|
||||
|
||||
/* Set initial bit clock 400 KHz */
|
||||
mxs_set_ssp_busclock(priv->id, 400);
|
||||
|
||||
/* Send initial 74 clock cycles (185 us @ 400 KHz)*/
|
||||
writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_set);
|
||||
udelay(200);
|
||||
writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_clr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops mxsmmc_ops = {
|
||||
.send_cmd = mxsmmc_send_cmd,
|
||||
.set_ios = mxsmmc_set_ios,
|
||||
.init = mxsmmc_init,
|
||||
};
|
||||
|
||||
int mxsmmc_initialize(bd_t *bis, int id, int (*wp)(int), int (*cd)(int))
|
||||
{
|
||||
struct mmc *mmc = NULL;
|
||||
struct mxsmmc_priv *priv = NULL;
|
||||
int ret;
|
||||
const unsigned int mxsmmc_clk_id = mxs_ssp_clock_by_bus(id);
|
||||
|
||||
if (!mxs_ssp_bus_id_valid(id))
|
||||
return -ENODEV;
|
||||
|
||||
priv = malloc(sizeof(struct mxsmmc_priv));
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->desc = mxs_dma_desc_alloc();
|
||||
if (!priv->desc) {
|
||||
free(priv);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = mxs_dma_init_channel(MXS_DMA_CHANNEL_AHB_APBH_SSP0 + id);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->mmc_is_wp = wp;
|
||||
priv->mmc_cd = cd;
|
||||
priv->id = id;
|
||||
priv->regs = mxs_ssp_regs_by_bus(id);
|
||||
|
||||
priv->cfg.name = "MXS MMC";
|
||||
priv->cfg.ops = &mxsmmc_ops;
|
||||
|
||||
priv->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
|
||||
priv->cfg.host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT |
|
||||
MMC_MODE_HS_52MHz | MMC_MODE_HS;
|
||||
|
||||
/*
|
||||
* SSPCLK = 480 * 18 / 29 / 1 = 297.731 MHz
|
||||
* SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)),
|
||||
* CLOCK_DIVIDE has to be an even value from 2 to 254, and
|
||||
* CLOCK_RATE could be any integer from 0 to 255.
|
||||
*/
|
||||
priv->cfg.f_min = 400000;
|
||||
priv->cfg.f_max = mxc_get_clock(MXC_SSP0_CLK + mxsmmc_clk_id) * 1000 / 2;
|
||||
priv->cfg.b_max = 0x20;
|
||||
|
||||
mmc = mmc_create(&priv->cfg, priv);
|
||||
if (mmc == NULL) {
|
||||
mxs_dma_desc_free(priv->desc);
|
||||
free(priv);
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
850
u-boot/drivers/mmc/omap_hsmmc.c
Normal file
850
u-boot/drivers/mmc/omap_hsmmc.c
Normal file
@@ -0,0 +1,850 @@
|
||||
/*
|
||||
* (C) Copyright 2008
|
||||
* Texas Instruments, <www.ti.com>
|
||||
* Sukumar Ghorai <s-ghorai@ti.com>
|
||||
*
|
||||
* 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's version 2 of
|
||||
* the License.
|
||||
*
|
||||
* 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 <config.h>
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <mmc.h>
|
||||
#include <part.h>
|
||||
#include <i2c.h>
|
||||
#include <twl4030.h>
|
||||
#include <twl6030.h>
|
||||
#include <palmas.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/mmc_host_def.h>
|
||||
#if !defined(CONFIG_SOC_KEYSTONE)
|
||||
#include <asm/gpio.h>
|
||||
#include <asm/arch/sys_proto.h>
|
||||
#endif
|
||||
#include <dm.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
/* simplify defines to OMAP_HSMMC_USE_GPIO */
|
||||
#if (defined(CONFIG_OMAP_GPIO) && !defined(CONFIG_SPL_BUILD)) || \
|
||||
(defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_GPIO_SUPPORT))
|
||||
#define OMAP_HSMMC_USE_GPIO
|
||||
#else
|
||||
#undef OMAP_HSMMC_USE_GPIO
|
||||
#endif
|
||||
|
||||
/* common definitions for all OMAPs */
|
||||
#define SYSCTL_SRC (1 << 25)
|
||||
#define SYSCTL_SRD (1 << 26)
|
||||
|
||||
struct omap_hsmmc_data {
|
||||
struct hsmmc *base_addr;
|
||||
struct mmc_config cfg;
|
||||
#ifdef OMAP_HSMMC_USE_GPIO
|
||||
#ifdef CONFIG_DM_MMC
|
||||
struct gpio_desc cd_gpio; /* Change Detect GPIO */
|
||||
struct gpio_desc wp_gpio; /* Write Protect GPIO */
|
||||
bool cd_inverted;
|
||||
#else
|
||||
int cd_gpio;
|
||||
int wp_gpio;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
/* If we fail after 1 second wait, something is really bad */
|
||||
#define MAX_RETRY_MS 1000
|
||||
|
||||
static int mmc_read_data(struct hsmmc *mmc_base, char *buf, unsigned int size);
|
||||
static int mmc_write_data(struct hsmmc *mmc_base, const char *buf,
|
||||
unsigned int siz);
|
||||
|
||||
#if defined(OMAP_HSMMC_USE_GPIO) && !defined(CONFIG_DM_MMC)
|
||||
static int omap_mmc_setup_gpio_in(int gpio, const char *label)
|
||||
{
|
||||
int ret;
|
||||
|
||||
#ifndef CONFIG_DM_GPIO
|
||||
if (!gpio_is_valid(gpio))
|
||||
return -1;
|
||||
#endif
|
||||
ret = gpio_request(gpio, label);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = gpio_direction_input(gpio);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return gpio;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_OMAP44XX)
|
||||
static void omap4_vmmc_pbias_config(struct mmc *mmc)
|
||||
{
|
||||
u32 value = 0;
|
||||
|
||||
value = readl((*ctrl)->control_pbiaslite);
|
||||
value &= ~(MMC1_PBIASLITE_PWRDNZ | MMC1_PWRDNZ);
|
||||
writel(value, (*ctrl)->control_pbiaslite);
|
||||
value = readl((*ctrl)->control_pbiaslite);
|
||||
value |= MMC1_PBIASLITE_VMODE | MMC1_PBIASLITE_PWRDNZ | MMC1_PWRDNZ;
|
||||
writel(value, (*ctrl)->control_pbiaslite);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_OMAP54XX) && defined(CONFIG_PALMAS_POWER)
|
||||
static void omap5_pbias_config(struct mmc *mmc)
|
||||
{
|
||||
u32 value = 0;
|
||||
|
||||
value = readl((*ctrl)->control_pbias);
|
||||
value &= ~SDCARD_PWRDNZ;
|
||||
writel(value, (*ctrl)->control_pbias);
|
||||
udelay(10); /* wait 10 us */
|
||||
value &= ~SDCARD_BIAS_PWRDNZ;
|
||||
writel(value, (*ctrl)->control_pbias);
|
||||
|
||||
palmas_mmc1_poweron_ldo();
|
||||
|
||||
value = readl((*ctrl)->control_pbias);
|
||||
value |= SDCARD_BIAS_PWRDNZ;
|
||||
writel(value, (*ctrl)->control_pbias);
|
||||
udelay(150); /* wait 150 us */
|
||||
value |= SDCARD_PWRDNZ;
|
||||
writel(value, (*ctrl)->control_pbias);
|
||||
udelay(150); /* wait 150 us */
|
||||
}
|
||||
#endif
|
||||
|
||||
static unsigned char mmc_board_init(struct mmc *mmc)
|
||||
{
|
||||
#if defined(CONFIG_OMAP34XX)
|
||||
t2_t *t2_base = (t2_t *)T2_BASE;
|
||||
struct prcm *prcm_base = (struct prcm *)PRCM_BASE;
|
||||
u32 pbias_lite;
|
||||
|
||||
pbias_lite = readl(&t2_base->pbias_lite);
|
||||
pbias_lite &= ~(PBIASLITEPWRDNZ1 | PBIASLITEPWRDNZ0);
|
||||
#ifdef CONFIG_TARGET_OMAP3_CAIRO
|
||||
/* for cairo board, we need to set up 1.8 Volt bias level on MMC1 */
|
||||
pbias_lite &= ~PBIASLITEVMODE0;
|
||||
#endif
|
||||
writel(pbias_lite, &t2_base->pbias_lite);
|
||||
|
||||
writel(pbias_lite | PBIASLITEPWRDNZ1 |
|
||||
PBIASSPEEDCTRL0 | PBIASLITEPWRDNZ0,
|
||||
&t2_base->pbias_lite);
|
||||
|
||||
writel(readl(&t2_base->devconf0) | MMCSDIO1ADPCLKISEL,
|
||||
&t2_base->devconf0);
|
||||
|
||||
writel(readl(&t2_base->devconf1) | MMCSDIO2ADPCLKISEL,
|
||||
&t2_base->devconf1);
|
||||
|
||||
/* Change from default of 52MHz to 26MHz if necessary */
|
||||
if (!(mmc->cfg->host_caps & MMC_MODE_HS_52MHz))
|
||||
writel(readl(&t2_base->ctl_prog_io1) & ~CTLPROGIO1SPEEDCTRL,
|
||||
&t2_base->ctl_prog_io1);
|
||||
|
||||
writel(readl(&prcm_base->fclken1_core) |
|
||||
EN_MMC1 | EN_MMC2 | EN_MMC3,
|
||||
&prcm_base->fclken1_core);
|
||||
|
||||
writel(readl(&prcm_base->iclken1_core) |
|
||||
EN_MMC1 | EN_MMC2 | EN_MMC3,
|
||||
&prcm_base->iclken1_core);
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_OMAP44XX)
|
||||
/* PBIAS config needed for MMC1 only */
|
||||
if (mmc->block_dev.devnum == 0)
|
||||
omap4_vmmc_pbias_config(mmc);
|
||||
#endif
|
||||
#if defined(CONFIG_OMAP54XX) && defined(CONFIG_PALMAS_POWER)
|
||||
if (mmc->block_dev.devnum == 0)
|
||||
omap5_pbias_config(mmc);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mmc_init_stream(struct hsmmc *mmc_base)
|
||||
{
|
||||
ulong start;
|
||||
|
||||
writel(readl(&mmc_base->con) | INIT_INITSTREAM, &mmc_base->con);
|
||||
|
||||
writel(MMC_CMD0, &mmc_base->cmd);
|
||||
start = get_timer(0);
|
||||
while (!(readl(&mmc_base->stat) & CC_MASK)) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for cc!\n", __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
writel(CC_MASK, &mmc_base->stat)
|
||||
;
|
||||
writel(MMC_CMD0, &mmc_base->cmd)
|
||||
;
|
||||
start = get_timer(0);
|
||||
while (!(readl(&mmc_base->stat) & CC_MASK)) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for cc2!\n", __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
writel(readl(&mmc_base->con) & ~INIT_INITSTREAM, &mmc_base->con);
|
||||
}
|
||||
|
||||
|
||||
static int omap_hsmmc_init_setup(struct mmc *mmc)
|
||||
{
|
||||
struct hsmmc *mmc_base;
|
||||
unsigned int reg_val;
|
||||
unsigned int dsor;
|
||||
ulong start;
|
||||
|
||||
mmc_base = ((struct omap_hsmmc_data *)mmc->priv)->base_addr;
|
||||
mmc_board_init(mmc);
|
||||
|
||||
writel(readl(&mmc_base->sysconfig) | MMC_SOFTRESET,
|
||||
&mmc_base->sysconfig);
|
||||
start = get_timer(0);
|
||||
while ((readl(&mmc_base->sysstatus) & RESETDONE) == 0) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for cc2!\n", __func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
}
|
||||
writel(readl(&mmc_base->sysctl) | SOFTRESETALL, &mmc_base->sysctl);
|
||||
start = get_timer(0);
|
||||
while ((readl(&mmc_base->sysctl) & SOFTRESETALL) != 0x0) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for softresetall!\n",
|
||||
__func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
}
|
||||
writel(DTW_1_BITMODE | SDBP_PWROFF | SDVS_3V0, &mmc_base->hctl);
|
||||
writel(readl(&mmc_base->capa) | VS30_3V0SUP | VS18_1V8SUP,
|
||||
&mmc_base->capa);
|
||||
|
||||
reg_val = readl(&mmc_base->con) & RESERVED_MASK;
|
||||
|
||||
writel(CTPL_MMC_SD | reg_val | WPP_ACTIVEHIGH | CDP_ACTIVEHIGH |
|
||||
MIT_CTO | DW8_1_4BITMODE | MODE_FUNC | STR_BLOCK |
|
||||
HR_NOHOSTRESP | INIT_NOINIT | NOOPENDRAIN, &mmc_base->con);
|
||||
|
||||
dsor = 240;
|
||||
mmc_reg_out(&mmc_base->sysctl, (ICE_MASK | DTO_MASK | CEN_MASK),
|
||||
(ICE_STOP | DTO_15THDTO | CEN_DISABLE));
|
||||
mmc_reg_out(&mmc_base->sysctl, ICE_MASK | CLKD_MASK,
|
||||
(dsor << CLKD_OFFSET) | ICE_OSCILLATE);
|
||||
start = get_timer(0);
|
||||
while ((readl(&mmc_base->sysctl) & ICS_MASK) == ICS_NOTREADY) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for ics!\n", __func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
}
|
||||
writel(readl(&mmc_base->sysctl) | CEN_ENABLE, &mmc_base->sysctl);
|
||||
|
||||
writel(readl(&mmc_base->hctl) | SDBP_PWRON, &mmc_base->hctl);
|
||||
|
||||
writel(IE_BADA | IE_CERR | IE_DEB | IE_DCRC | IE_DTO | IE_CIE |
|
||||
IE_CEB | IE_CCRC | IE_CTO | IE_BRR | IE_BWR | IE_TC | IE_CC,
|
||||
&mmc_base->ie);
|
||||
|
||||
mmc_init_stream(mmc_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* MMC controller internal finite state machine reset
|
||||
*
|
||||
* Used to reset command or data internal state machines, using respectively
|
||||
* SRC or SRD bit of SYSCTL register
|
||||
*/
|
||||
static void mmc_reset_controller_fsm(struct hsmmc *mmc_base, u32 bit)
|
||||
{
|
||||
ulong start;
|
||||
|
||||
mmc_reg_out(&mmc_base->sysctl, bit, bit);
|
||||
|
||||
/*
|
||||
* CMD(DAT) lines reset procedures are slightly different
|
||||
* for OMAP3 and OMAP4(AM335x,OMAP5,DRA7xx).
|
||||
* According to OMAP3 TRM:
|
||||
* Set SRC(SRD) bit in MMCHS_SYSCTL register to 0x1 and wait until it
|
||||
* returns to 0x0.
|
||||
* According to OMAP4(AM335x,OMAP5,DRA7xx) TRMs, CMD(DATA) lines reset
|
||||
* procedure steps must be as follows:
|
||||
* 1. Initiate CMD(DAT) line reset by writing 0x1 to SRC(SRD) bit in
|
||||
* MMCHS_SYSCTL register (SD_SYSCTL for AM335x).
|
||||
* 2. Poll the SRC(SRD) bit until it is set to 0x1.
|
||||
* 3. Wait until the SRC (SRD) bit returns to 0x0
|
||||
* (reset procedure is completed).
|
||||
*/
|
||||
#if defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) || \
|
||||
defined(CONFIG_AM33XX) || defined(CONFIG_AM43XX)
|
||||
if (!(readl(&mmc_base->sysctl) & bit)) {
|
||||
start = get_timer(0);
|
||||
while (!(readl(&mmc_base->sysctl) & bit)) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS)
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
start = get_timer(0);
|
||||
while ((readl(&mmc_base->sysctl) & bit) != 0) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for sysctl %x to clear\n",
|
||||
__func__, bit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int omap_hsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct hsmmc *mmc_base;
|
||||
unsigned int flags, mmc_stat;
|
||||
ulong start;
|
||||
|
||||
mmc_base = ((struct omap_hsmmc_data *)mmc->priv)->base_addr;
|
||||
start = get_timer(0);
|
||||
while ((readl(&mmc_base->pstate) & (DATI_MASK | CMDI_MASK)) != 0) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting on cmd inhibit to clear\n",
|
||||
__func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
}
|
||||
writel(0xFFFFFFFF, &mmc_base->stat);
|
||||
start = get_timer(0);
|
||||
while (readl(&mmc_base->stat)) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for STAT (%x) to clear\n",
|
||||
__func__, readl(&mmc_base->stat));
|
||||
return TIMEOUT;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* CMDREG
|
||||
* CMDIDX[13:8] : Command index
|
||||
* DATAPRNT[5] : Data Present Select
|
||||
* ENCMDIDX[4] : Command Index Check Enable
|
||||
* ENCMDCRC[3] : Command CRC Check Enable
|
||||
* RSPTYP[1:0]
|
||||
* 00 = No Response
|
||||
* 01 = Length 136
|
||||
* 10 = Length 48
|
||||
* 11 = Length 48 Check busy after response
|
||||
*/
|
||||
/* Delay added before checking the status of frq change
|
||||
* retry not supported by mmc.c(core file)
|
||||
*/
|
||||
if (cmd->cmdidx == SD_CMD_APP_SEND_SCR)
|
||||
udelay(50000); /* wait 50 ms */
|
||||
|
||||
if (!(cmd->resp_type & MMC_RSP_PRESENT))
|
||||
flags = 0;
|
||||
else if (cmd->resp_type & MMC_RSP_136)
|
||||
flags = RSP_TYPE_LGHT136 | CICE_NOCHECK;
|
||||
else if (cmd->resp_type & MMC_RSP_BUSY)
|
||||
flags = RSP_TYPE_LGHT48B;
|
||||
else
|
||||
flags = RSP_TYPE_LGHT48;
|
||||
|
||||
/* enable default flags */
|
||||
flags = flags | (CMD_TYPE_NORMAL | CICE_NOCHECK | CCCE_NOCHECK |
|
||||
MSBS_SGLEBLK | ACEN_DISABLE | BCE_DISABLE | DE_DISABLE);
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_CRC)
|
||||
flags |= CCCE_CHECK;
|
||||
if (cmd->resp_type & MMC_RSP_OPCODE)
|
||||
flags |= CICE_CHECK;
|
||||
|
||||
if (data) {
|
||||
if ((cmd->cmdidx == MMC_CMD_READ_MULTIPLE_BLOCK) ||
|
||||
(cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)) {
|
||||
flags |= (MSBS_MULTIBLK | BCE_ENABLE);
|
||||
data->blocksize = 512;
|
||||
writel(data->blocksize | (data->blocks << 16),
|
||||
&mmc_base->blk);
|
||||
} else
|
||||
writel(data->blocksize | NBLK_STPCNT, &mmc_base->blk);
|
||||
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
flags |= (DP_DATA | DDIR_READ);
|
||||
else
|
||||
flags |= (DP_DATA | DDIR_WRITE);
|
||||
}
|
||||
|
||||
writel(cmd->cmdarg, &mmc_base->arg);
|
||||
udelay(20); /* To fix "No status update" error on eMMC */
|
||||
writel((cmd->cmdidx << 24) | flags, &mmc_base->cmd);
|
||||
|
||||
start = get_timer(0);
|
||||
do {
|
||||
mmc_stat = readl(&mmc_base->stat);
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s : timeout: No status update\n", __func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
} while (!mmc_stat);
|
||||
|
||||
if ((mmc_stat & IE_CTO) != 0) {
|
||||
mmc_reset_controller_fsm(mmc_base, SYSCTL_SRC);
|
||||
return TIMEOUT;
|
||||
} else if ((mmc_stat & ERRI_MASK) != 0)
|
||||
return -1;
|
||||
|
||||
if (mmc_stat & CC_MASK) {
|
||||
writel(CC_MASK, &mmc_base->stat);
|
||||
if (cmd->resp_type & MMC_RSP_PRESENT) {
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
/* response type 2 */
|
||||
cmd->response[3] = readl(&mmc_base->rsp10);
|
||||
cmd->response[2] = readl(&mmc_base->rsp32);
|
||||
cmd->response[1] = readl(&mmc_base->rsp54);
|
||||
cmd->response[0] = readl(&mmc_base->rsp76);
|
||||
} else
|
||||
/* response types 1, 1b, 3, 4, 5, 6 */
|
||||
cmd->response[0] = readl(&mmc_base->rsp10);
|
||||
}
|
||||
}
|
||||
|
||||
if (data && (data->flags & MMC_DATA_READ)) {
|
||||
mmc_read_data(mmc_base, data->dest,
|
||||
data->blocksize * data->blocks);
|
||||
} else if (data && (data->flags & MMC_DATA_WRITE)) {
|
||||
mmc_write_data(mmc_base, data->src,
|
||||
data->blocksize * data->blocks);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_read_data(struct hsmmc *mmc_base, char *buf, unsigned int size)
|
||||
{
|
||||
unsigned int *output_buf = (unsigned int *)buf;
|
||||
unsigned int mmc_stat;
|
||||
unsigned int count;
|
||||
|
||||
/*
|
||||
* Start Polled Read
|
||||
*/
|
||||
count = (size > MMCSD_SECTOR_SIZE) ? MMCSD_SECTOR_SIZE : size;
|
||||
count /= 4;
|
||||
|
||||
while (size) {
|
||||
ulong start = get_timer(0);
|
||||
do {
|
||||
mmc_stat = readl(&mmc_base->stat);
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for status!\n",
|
||||
__func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
} while (mmc_stat == 0);
|
||||
|
||||
if ((mmc_stat & (IE_DTO | IE_DCRC | IE_DEB)) != 0)
|
||||
mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD);
|
||||
|
||||
if ((mmc_stat & ERRI_MASK) != 0)
|
||||
return 1;
|
||||
|
||||
if (mmc_stat & BRR_MASK) {
|
||||
unsigned int k;
|
||||
|
||||
writel(readl(&mmc_base->stat) | BRR_MASK,
|
||||
&mmc_base->stat);
|
||||
for (k = 0; k < count; k++) {
|
||||
*output_buf = readl(&mmc_base->data);
|
||||
output_buf++;
|
||||
}
|
||||
size -= (count*4);
|
||||
}
|
||||
|
||||
if (mmc_stat & BWR_MASK)
|
||||
writel(readl(&mmc_base->stat) | BWR_MASK,
|
||||
&mmc_base->stat);
|
||||
|
||||
if (mmc_stat & TC_MASK) {
|
||||
writel(readl(&mmc_base->stat) | TC_MASK,
|
||||
&mmc_base->stat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_write_data(struct hsmmc *mmc_base, const char *buf,
|
||||
unsigned int size)
|
||||
{
|
||||
unsigned int *input_buf = (unsigned int *)buf;
|
||||
unsigned int mmc_stat;
|
||||
unsigned int count;
|
||||
|
||||
/*
|
||||
* Start Polled Write
|
||||
*/
|
||||
count = (size > MMCSD_SECTOR_SIZE) ? MMCSD_SECTOR_SIZE : size;
|
||||
count /= 4;
|
||||
|
||||
while (size) {
|
||||
ulong start = get_timer(0);
|
||||
do {
|
||||
mmc_stat = readl(&mmc_base->stat);
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for status!\n",
|
||||
__func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
} while (mmc_stat == 0);
|
||||
|
||||
if ((mmc_stat & (IE_DTO | IE_DCRC | IE_DEB)) != 0)
|
||||
mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD);
|
||||
|
||||
if ((mmc_stat & ERRI_MASK) != 0)
|
||||
return 1;
|
||||
|
||||
if (mmc_stat & BWR_MASK) {
|
||||
unsigned int k;
|
||||
|
||||
writel(readl(&mmc_base->stat) | BWR_MASK,
|
||||
&mmc_base->stat);
|
||||
for (k = 0; k < count; k++) {
|
||||
writel(*input_buf, &mmc_base->data);
|
||||
input_buf++;
|
||||
}
|
||||
size -= (count*4);
|
||||
}
|
||||
|
||||
if (mmc_stat & BRR_MASK)
|
||||
writel(readl(&mmc_base->stat) | BRR_MASK,
|
||||
&mmc_base->stat);
|
||||
|
||||
if (mmc_stat & TC_MASK) {
|
||||
writel(readl(&mmc_base->stat) | TC_MASK,
|
||||
&mmc_base->stat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void omap_hsmmc_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct hsmmc *mmc_base;
|
||||
unsigned int dsor = 0;
|
||||
ulong start;
|
||||
|
||||
mmc_base = ((struct omap_hsmmc_data *)mmc->priv)->base_addr;
|
||||
/* configue bus width */
|
||||
switch (mmc->bus_width) {
|
||||
case 8:
|
||||
writel(readl(&mmc_base->con) | DTW_8_BITMODE,
|
||||
&mmc_base->con);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
writel(readl(&mmc_base->con) & ~DTW_8_BITMODE,
|
||||
&mmc_base->con);
|
||||
writel(readl(&mmc_base->hctl) | DTW_4_BITMODE,
|
||||
&mmc_base->hctl);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
default:
|
||||
writel(readl(&mmc_base->con) & ~DTW_8_BITMODE,
|
||||
&mmc_base->con);
|
||||
writel(readl(&mmc_base->hctl) & ~DTW_4_BITMODE,
|
||||
&mmc_base->hctl);
|
||||
break;
|
||||
}
|
||||
|
||||
/* configure clock with 96Mhz system clock.
|
||||
*/
|
||||
if (mmc->clock != 0) {
|
||||
dsor = (MMC_CLOCK_REFERENCE * 1000000 / mmc->clock);
|
||||
if ((MMC_CLOCK_REFERENCE * 1000000) / dsor > mmc->clock)
|
||||
dsor++;
|
||||
}
|
||||
|
||||
mmc_reg_out(&mmc_base->sysctl, (ICE_MASK | DTO_MASK | CEN_MASK),
|
||||
(ICE_STOP | DTO_15THDTO | CEN_DISABLE));
|
||||
|
||||
mmc_reg_out(&mmc_base->sysctl, ICE_MASK | CLKD_MASK,
|
||||
(dsor << CLKD_OFFSET) | ICE_OSCILLATE);
|
||||
|
||||
start = get_timer(0);
|
||||
while ((readl(&mmc_base->sysctl) & ICS_MASK) == ICS_NOTREADY) {
|
||||
if (get_timer(0) - start > MAX_RETRY_MS) {
|
||||
printf("%s: timedout waiting for ics!\n", __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
writel(readl(&mmc_base->sysctl) | CEN_ENABLE, &mmc_base->sysctl);
|
||||
}
|
||||
|
||||
#ifdef OMAP_HSMMC_USE_GPIO
|
||||
#ifdef CONFIG_DM_MMC
|
||||
static int omap_hsmmc_getcd(struct mmc *mmc)
|
||||
{
|
||||
struct omap_hsmmc_data *priv = mmc->priv;
|
||||
int value;
|
||||
|
||||
value = dm_gpio_get_value(&priv->cd_gpio);
|
||||
/* if no CD return as 1 */
|
||||
if (value < 0)
|
||||
return 1;
|
||||
|
||||
if (priv->cd_inverted)
|
||||
return !value;
|
||||
return value;
|
||||
}
|
||||
|
||||
static int omap_hsmmc_getwp(struct mmc *mmc)
|
||||
{
|
||||
struct omap_hsmmc_data *priv = mmc->priv;
|
||||
int value;
|
||||
|
||||
value = dm_gpio_get_value(&priv->wp_gpio);
|
||||
/* if no WP return as 0 */
|
||||
if (value < 0)
|
||||
return 0;
|
||||
return value;
|
||||
}
|
||||
#else
|
||||
static int omap_hsmmc_getcd(struct mmc *mmc)
|
||||
{
|
||||
struct omap_hsmmc_data *priv_data = mmc->priv;
|
||||
int cd_gpio;
|
||||
|
||||
/* if no CD return as 1 */
|
||||
cd_gpio = priv_data->cd_gpio;
|
||||
if (cd_gpio < 0)
|
||||
return 1;
|
||||
|
||||
/* NOTE: assumes card detect signal is active-low */
|
||||
return !gpio_get_value(cd_gpio);
|
||||
}
|
||||
|
||||
static int omap_hsmmc_getwp(struct mmc *mmc)
|
||||
{
|
||||
struct omap_hsmmc_data *priv_data = mmc->priv;
|
||||
int wp_gpio;
|
||||
|
||||
/* if no WP return as 0 */
|
||||
wp_gpio = priv_data->wp_gpio;
|
||||
if (wp_gpio < 0)
|
||||
return 0;
|
||||
|
||||
/* NOTE: assumes write protect signal is active-high */
|
||||
return gpio_get_value(wp_gpio);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static const struct mmc_ops omap_hsmmc_ops = {
|
||||
.send_cmd = omap_hsmmc_send_cmd,
|
||||
.set_ios = omap_hsmmc_set_ios,
|
||||
.init = omap_hsmmc_init_setup,
|
||||
#ifdef OMAP_HSMMC_USE_GPIO
|
||||
.getcd = omap_hsmmc_getcd,
|
||||
.getwp = omap_hsmmc_getwp,
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef CONFIG_DM_MMC
|
||||
int omap_mmc_init(int dev_index, uint host_caps_mask, uint f_max, int cd_gpio,
|
||||
int wp_gpio)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
struct omap_hsmmc_data *priv_data;
|
||||
struct mmc_config *cfg;
|
||||
uint host_caps_val;
|
||||
|
||||
priv_data = malloc(sizeof(*priv_data));
|
||||
if (priv_data == NULL)
|
||||
return -1;
|
||||
|
||||
host_caps_val = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS;
|
||||
|
||||
switch (dev_index) {
|
||||
case 0:
|
||||
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC1_BASE;
|
||||
break;
|
||||
#ifdef OMAP_HSMMC2_BASE
|
||||
case 1:
|
||||
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC2_BASE;
|
||||
#if (defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) || \
|
||||
defined(CONFIG_DRA7XX) || defined(CONFIG_AM57XX) || \
|
||||
defined(CONFIG_AM33XX) || \
|
||||
defined(CONFIG_AM43XX) || defined(CONFIG_SOC_KEYSTONE)) && \
|
||||
defined(CONFIG_HSMMC2_8BIT)
|
||||
/* Enable 8-bit interface for eMMC on OMAP4/5 or DRA7XX */
|
||||
host_caps_val |= MMC_MODE_8BIT;
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
#ifdef OMAP_HSMMC3_BASE
|
||||
case 2:
|
||||
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC3_BASE;
|
||||
#if (defined(CONFIG_DRA7XX) || defined(CONFIG_AM57XX)) && defined(CONFIG_HSMMC3_8BIT)
|
||||
/* Enable 8-bit interface for eMMC on DRA7XX */
|
||||
host_caps_val |= MMC_MODE_8BIT;
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC1_BASE;
|
||||
return 1;
|
||||
}
|
||||
#ifdef OMAP_HSMMC_USE_GPIO
|
||||
/* on error gpio values are set to -1, which is what we want */
|
||||
priv_data->cd_gpio = omap_mmc_setup_gpio_in(cd_gpio, "mmc_cd");
|
||||
priv_data->wp_gpio = omap_mmc_setup_gpio_in(wp_gpio, "mmc_wp");
|
||||
#endif
|
||||
|
||||
cfg = &priv_data->cfg;
|
||||
|
||||
cfg->name = "OMAP SD/MMC";
|
||||
cfg->ops = &omap_hsmmc_ops;
|
||||
|
||||
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
|
||||
cfg->host_caps = host_caps_val & ~host_caps_mask;
|
||||
|
||||
cfg->f_min = 400000;
|
||||
|
||||
if (f_max != 0)
|
||||
cfg->f_max = f_max;
|
||||
else {
|
||||
if (cfg->host_caps & MMC_MODE_HS) {
|
||||
if (cfg->host_caps & MMC_MODE_HS_52MHz)
|
||||
cfg->f_max = 52000000;
|
||||
else
|
||||
cfg->f_max = 26000000;
|
||||
} else
|
||||
cfg->f_max = 20000000;
|
||||
}
|
||||
|
||||
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
|
||||
#if defined(CONFIG_OMAP34XX)
|
||||
/*
|
||||
* Silicon revs 2.1 and older do not support multiblock transfers.
|
||||
*/
|
||||
if ((get_cpu_family() == CPU_OMAP34XX) && (get_cpu_rev() <= CPU_3XX_ES21))
|
||||
cfg->b_max = 1;
|
||||
#endif
|
||||
mmc = mmc_create(cfg, priv_data);
|
||||
if (mmc == NULL)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int omap_hsmmc_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct omap_hsmmc_data *priv = dev_get_priv(dev);
|
||||
const void *fdt = gd->fdt_blob;
|
||||
int node = dev->of_offset;
|
||||
struct mmc_config *cfg;
|
||||
int val;
|
||||
|
||||
priv->base_addr = map_physmem(dev_get_addr(dev), sizeof(struct hsmmc *),
|
||||
MAP_NOCACHE);
|
||||
cfg = &priv->cfg;
|
||||
|
||||
cfg->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS;
|
||||
val = fdtdec_get_int(fdt, node, "bus-width", -1);
|
||||
if (val < 0) {
|
||||
printf("error: bus-width property missing\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
switch (val) {
|
||||
case 0x8:
|
||||
cfg->host_caps |= MMC_MODE_8BIT;
|
||||
case 0x4:
|
||||
cfg->host_caps |= MMC_MODE_4BIT;
|
||||
break;
|
||||
default:
|
||||
printf("error: invalid bus-width property\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
cfg->f_min = 400000;
|
||||
cfg->f_max = fdtdec_get_int(fdt, node, "max-frequency", 52000000);
|
||||
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
|
||||
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
|
||||
priv->cd_inverted = fdtdec_get_bool(fdt, node, "cd-inverted");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_hsmmc_probe(struct udevice *dev)
|
||||
{
|
||||
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
||||
struct omap_hsmmc_data *priv = dev_get_priv(dev);
|
||||
struct mmc_config *cfg;
|
||||
struct mmc *mmc;
|
||||
|
||||
cfg = &priv->cfg;
|
||||
cfg->name = "OMAP SD/MMC";
|
||||
cfg->ops = &omap_hsmmc_ops;
|
||||
|
||||
mmc = mmc_create(cfg, priv);
|
||||
if (mmc == NULL)
|
||||
return -1;
|
||||
|
||||
#ifdef OMAP_HSMMC_USE_GPIO
|
||||
gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, GPIOD_IS_IN);
|
||||
gpio_request_by_name(dev, "wp-gpios", 0, &priv->wp_gpio, GPIOD_IS_IN);
|
||||
#endif
|
||||
|
||||
mmc->dev = dev;
|
||||
upriv->mmc = mmc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id omap_hsmmc_ids[] = {
|
||||
{ .compatible = "ti,omap3-hsmmc" },
|
||||
{ .compatible = "ti,omap4-hsmmc" },
|
||||
{ .compatible = "ti,am33xx-hsmmc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(omap_hsmmc) = {
|
||||
.name = "omap_hsmmc",
|
||||
.id = UCLASS_MMC,
|
||||
.of_match = omap_hsmmc_ids,
|
||||
.ofdata_to_platdata = omap_hsmmc_ofdata_to_platdata,
|
||||
.probe = omap_hsmmc_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct omap_hsmmc_data),
|
||||
};
|
||||
#endif
|
||||
41
u-boot/drivers/mmc/pci_mmc.c
Normal file
41
u-boot/drivers/mmc/pci_mmc.c
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2015, Google, Inc
|
||||
* Copyright (C) 2014, Bin Meng <bmeng.cn@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
#include <asm/pci.h>
|
||||
|
||||
int pci_mmc_init(const char *name, struct pci_device_id *mmc_supported)
|
||||
{
|
||||
struct sdhci_host *mmc_host;
|
||||
u32 iobase;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
for (i = 0; ; i++) {
|
||||
struct udevice *dev;
|
||||
|
||||
ret = pci_find_device_id(mmc_supported, i, &dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
mmc_host = malloc(sizeof(struct sdhci_host));
|
||||
if (!mmc_host)
|
||||
return -ENOMEM;
|
||||
|
||||
mmc_host->name = name;
|
||||
dm_pci_read_config32(dev, PCI_BASE_ADDRESS_0, &iobase);
|
||||
mmc_host->ioaddr = (void *)iobase;
|
||||
mmc_host->quirks = 0;
|
||||
ret = add_sdhci(mmc_host, 0, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
63
u-boot/drivers/mmc/pic32_sdhci.c
Normal file
63
u-boot/drivers/mmc/pic32_sdhci.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Support of SDHCI for Microchip PIC32 SoC.
|
||||
*
|
||||
* Copyright (C) 2015 Microchip Technology Inc.
|
||||
* Andrei Pistirica <andrei.pistirica@microchip.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <dm.h>
|
||||
#include <common.h>
|
||||
#include <sdhci.h>
|
||||
#include <asm/errno.h>
|
||||
#include <mach/pic32.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
static int pic32_sdhci_probe(struct udevice *dev)
|
||||
{
|
||||
struct sdhci_host *host = dev_get_priv(dev);
|
||||
const void *fdt = gd->fdt_blob;
|
||||
u32 f_min_max[2];
|
||||
fdt_addr_t addr;
|
||||
fdt_size_t size;
|
||||
int ret;
|
||||
|
||||
addr = fdtdec_get_addr_size(fdt, dev->of_offset, "reg", &size);
|
||||
if (addr == FDT_ADDR_T_NONE)
|
||||
return -EINVAL;
|
||||
|
||||
host->ioaddr = ioremap(addr, size);
|
||||
host->name = dev->name;
|
||||
host->quirks = SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_NO_CD;
|
||||
host->bus_width = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
|
||||
"bus-width", 4);
|
||||
|
||||
ret = fdtdec_get_int_array(gd->fdt_blob, dev->of_offset,
|
||||
"clock-freq-min-max", f_min_max, 2);
|
||||
if (ret) {
|
||||
printf("sdhci: clock-freq-min-max not found\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = add_sdhci(host, f_min_max[1], f_min_max[0]);
|
||||
if (ret)
|
||||
return ret;
|
||||
host->mmc->dev = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id pic32_sdhci_ids[] = {
|
||||
{ .compatible = "microchip,pic32mzda-sdhci" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(pic32_sdhci_drv) = {
|
||||
.name = "pic32_sdhci",
|
||||
.id = UCLASS_MMC,
|
||||
.of_match = pic32_sdhci_ids,
|
||||
.probe = pic32_sdhci_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct sdhci_host),
|
||||
};
|
||||
430
u-boot/drivers/mmc/pxa_mmc_gen.c
Normal file
430
u-boot/drivers/mmc/pxa_mmc_gen.c
Normal file
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com>
|
||||
*
|
||||
* Loosely based on the old code and Linux's PXA MMC driver
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <asm/arch/hardware.h>
|
||||
#include <asm/arch/regs-mmc.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/io.h>
|
||||
#include <malloc.h>
|
||||
#include <mmc.h>
|
||||
|
||||
/* PXAMMC Generic default config for various CPUs */
|
||||
#if defined(CONFIG_CPU_PXA25X)
|
||||
#define PXAMMC_FIFO_SIZE 1
|
||||
#define PXAMMC_MIN_SPEED 312500
|
||||
#define PXAMMC_MAX_SPEED 20000000
|
||||
#define PXAMMC_HOST_CAPS (0)
|
||||
#elif defined(CONFIG_CPU_PXA27X)
|
||||
#define PXAMMC_CRC_SKIP
|
||||
#define PXAMMC_FIFO_SIZE 32
|
||||
#define PXAMMC_MIN_SPEED 304000
|
||||
#define PXAMMC_MAX_SPEED 19500000
|
||||
#define PXAMMC_HOST_CAPS (MMC_MODE_4BIT)
|
||||
#elif defined(CONFIG_CPU_MONAHANS)
|
||||
#define PXAMMC_FIFO_SIZE 32
|
||||
#define PXAMMC_MIN_SPEED 304000
|
||||
#define PXAMMC_MAX_SPEED 26000000
|
||||
#define PXAMMC_HOST_CAPS (MMC_MODE_4BIT | MMC_MODE_HS)
|
||||
#else
|
||||
#error "This CPU isn't supported by PXA MMC!"
|
||||
#endif
|
||||
|
||||
#define MMC_STAT_ERRORS \
|
||||
(MMC_STAT_RES_CRC_ERROR | MMC_STAT_SPI_READ_ERROR_TOKEN | \
|
||||
MMC_STAT_CRC_READ_ERROR | MMC_STAT_TIME_OUT_RESPONSE | \
|
||||
MMC_STAT_READ_TIME_OUT | MMC_STAT_CRC_WRITE_ERROR)
|
||||
|
||||
/* 1 millisecond (in wait cycles below it's 100 x 10uS waits) */
|
||||
#define PXA_MMC_TIMEOUT 100
|
||||
|
||||
struct pxa_mmc_priv {
|
||||
struct pxa_mmc_regs *regs;
|
||||
};
|
||||
|
||||
/* Wait for bit to be set */
|
||||
static int pxa_mmc_wait(struct mmc *mmc, uint32_t mask)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
unsigned int timeout = PXA_MMC_TIMEOUT;
|
||||
|
||||
/* Wait for bit to be set */
|
||||
while (--timeout) {
|
||||
if (readl(®s->stat) & mask)
|
||||
break;
|
||||
udelay(10);
|
||||
}
|
||||
|
||||
if (!timeout)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pxa_mmc_stop_clock(struct mmc *mmc)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
unsigned int timeout = PXA_MMC_TIMEOUT;
|
||||
|
||||
/* If the clock aren't running, exit */
|
||||
if (!(readl(®s->stat) & MMC_STAT_CLK_EN))
|
||||
return 0;
|
||||
|
||||
/* Tell the controller to turn off the clock */
|
||||
writel(MMC_STRPCL_STOP_CLK, ®s->strpcl);
|
||||
|
||||
/* Wait until the clock are off */
|
||||
while (--timeout) {
|
||||
if (!(readl(®s->stat) & MMC_STAT_CLK_EN))
|
||||
break;
|
||||
udelay(10);
|
||||
}
|
||||
|
||||
/* The clock refused to stop, scream and die a painful death */
|
||||
if (!timeout)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
/* The clock stopped correctly */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pxa_mmc_start_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
uint32_t cmdat)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
int ret;
|
||||
|
||||
/* The card can send a "busy" response */
|
||||
if (cmd->resp_type & MMC_RSP_BUSY)
|
||||
cmdat |= MMC_CMDAT_BUSY;
|
||||
|
||||
/* Inform the controller about response type */
|
||||
switch (cmd->resp_type) {
|
||||
case MMC_RSP_R1:
|
||||
case MMC_RSP_R1b:
|
||||
cmdat |= MMC_CMDAT_R1;
|
||||
break;
|
||||
case MMC_RSP_R2:
|
||||
cmdat |= MMC_CMDAT_R2;
|
||||
break;
|
||||
case MMC_RSP_R3:
|
||||
cmdat |= MMC_CMDAT_R3;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Load command and it's arguments into the controller */
|
||||
writel(cmd->cmdidx, ®s->cmd);
|
||||
writel(cmd->cmdarg >> 16, ®s->argh);
|
||||
writel(cmd->cmdarg & 0xffff, ®s->argl);
|
||||
writel(cmdat, ®s->cmdat);
|
||||
|
||||
/* Start the controller clock and wait until they are started */
|
||||
writel(MMC_STRPCL_START_CLK, ®s->strpcl);
|
||||
|
||||
ret = pxa_mmc_wait(mmc, MMC_STAT_CLK_EN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Correct and happy end */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pxa_mmc_cmd_done(struct mmc *mmc, struct mmc_cmd *cmd)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
uint32_t a, b, c;
|
||||
int i;
|
||||
int stat;
|
||||
|
||||
/* Read the controller status */
|
||||
stat = readl(®s->stat);
|
||||
|
||||
/*
|
||||
* Linux says:
|
||||
* Did I mention this is Sick. We always need to
|
||||
* discard the upper 8 bits of the first 16-bit word.
|
||||
*/
|
||||
a = readl(®s->res) & 0xffff;
|
||||
for (i = 0; i < 4; i++) {
|
||||
b = readl(®s->res) & 0xffff;
|
||||
c = readl(®s->res) & 0xffff;
|
||||
cmd->response[i] = (a << 24) | (b << 8) | (c >> 8);
|
||||
a = c;
|
||||
}
|
||||
|
||||
/* The command response didn't arrive */
|
||||
if (stat & MMC_STAT_TIME_OUT_RESPONSE)
|
||||
return -ETIMEDOUT;
|
||||
else if (stat & MMC_STAT_RES_CRC_ERROR
|
||||
&& cmd->resp_type & MMC_RSP_CRC) {
|
||||
#ifdef PXAMMC_CRC_SKIP
|
||||
if (cmd->resp_type & MMC_RSP_136
|
||||
&& cmd->response[0] & (1 << 31))
|
||||
printf("Ignoring CRC, this may be dangerous!\n");
|
||||
else
|
||||
#endif
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
/* The command response was successfully read */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pxa_mmc_do_read_xfer(struct mmc *mmc, struct mmc_data *data)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
uint32_t len;
|
||||
uint32_t *buf = (uint32_t *)data->dest;
|
||||
int size;
|
||||
int ret;
|
||||
|
||||
len = data->blocks * data->blocksize;
|
||||
|
||||
while (len) {
|
||||
/* The controller has data ready */
|
||||
if (readl(®s->i_reg) & MMC_I_REG_RXFIFO_RD_REQ) {
|
||||
size = min(len, (uint32_t)PXAMMC_FIFO_SIZE);
|
||||
len -= size;
|
||||
size /= 4;
|
||||
|
||||
/* Read data into the buffer */
|
||||
while (size--)
|
||||
*buf++ = readl(®s->rxfifo);
|
||||
|
||||
}
|
||||
|
||||
if (readl(®s->stat) & MMC_STAT_ERRORS)
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Wait for the transmission-done interrupt */
|
||||
ret = pxa_mmc_wait(mmc, MMC_STAT_DATA_TRAN_DONE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pxa_mmc_do_write_xfer(struct mmc *mmc, struct mmc_data *data)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
uint32_t len;
|
||||
uint32_t *buf = (uint32_t *)data->src;
|
||||
int size;
|
||||
int ret;
|
||||
|
||||
len = data->blocks * data->blocksize;
|
||||
|
||||
while (len) {
|
||||
/* The controller is ready to receive data */
|
||||
if (readl(®s->i_reg) & MMC_I_REG_TXFIFO_WR_REQ) {
|
||||
size = min(len, (uint32_t)PXAMMC_FIFO_SIZE);
|
||||
len -= size;
|
||||
size /= 4;
|
||||
|
||||
while (size--)
|
||||
writel(*buf++, ®s->txfifo);
|
||||
|
||||
if (min(len, (uint32_t)PXAMMC_FIFO_SIZE) < 32)
|
||||
writel(MMC_PRTBUF_BUF_PART_FULL, ®s->prtbuf);
|
||||
}
|
||||
|
||||
if (readl(®s->stat) & MMC_STAT_ERRORS)
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Wait for the transmission-done interrupt */
|
||||
ret = pxa_mmc_wait(mmc, MMC_STAT_DATA_TRAN_DONE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Wait until the data are really written to the card */
|
||||
ret = pxa_mmc_wait(mmc, MMC_STAT_PRG_DONE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pxa_mmc_request(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
uint32_t cmdat = 0;
|
||||
int ret;
|
||||
|
||||
/* Stop the controller */
|
||||
ret = pxa_mmc_stop_clock(mmc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* If we're doing data transfer, configure the controller accordingly */
|
||||
if (data) {
|
||||
writel(data->blocks, ®s->nob);
|
||||
writel(data->blocksize, ®s->blklen);
|
||||
/* This delay can be optimized, but stick with max value */
|
||||
writel(0xffff, ®s->rdto);
|
||||
cmdat |= MMC_CMDAT_DATA_EN;
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
cmdat |= MMC_CMDAT_WRITE;
|
||||
}
|
||||
|
||||
/* Run in 4bit mode if the card can do it */
|
||||
if (mmc->bus_width == 4)
|
||||
cmdat |= MMC_CMDAT_SD_4DAT;
|
||||
|
||||
/* Execute the command */
|
||||
ret = pxa_mmc_start_cmd(mmc, cmd, cmdat);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Wait until the command completes */
|
||||
ret = pxa_mmc_wait(mmc, MMC_STAT_END_CMD_RES);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read back the result */
|
||||
ret = pxa_mmc_cmd_done(mmc, cmd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* In case there was a data transfer scheduled, do it */
|
||||
if (data) {
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
pxa_mmc_do_write_xfer(mmc, data);
|
||||
else
|
||||
pxa_mmc_do_read_xfer(mmc, data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pxa_mmc_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
uint32_t tmp;
|
||||
uint32_t pxa_mmc_clock;
|
||||
|
||||
if (!mmc->clock) {
|
||||
pxa_mmc_stop_clock(mmc);
|
||||
return;
|
||||
}
|
||||
|
||||
/* PXA3xx can do 26MHz with special settings. */
|
||||
if (mmc->clock == 26000000) {
|
||||
writel(0x7, ®s->clkrt);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set clock to the card the usual way. */
|
||||
pxa_mmc_clock = 0;
|
||||
tmp = mmc->cfg->f_max / mmc->clock;
|
||||
tmp += tmp % 2;
|
||||
|
||||
while (tmp > 1) {
|
||||
pxa_mmc_clock++;
|
||||
tmp >>= 1;
|
||||
}
|
||||
|
||||
writel(pxa_mmc_clock, ®s->clkrt);
|
||||
}
|
||||
|
||||
static int pxa_mmc_init(struct mmc *mmc)
|
||||
{
|
||||
struct pxa_mmc_priv *priv = mmc->priv;
|
||||
struct pxa_mmc_regs *regs = priv->regs;
|
||||
|
||||
/* Make sure the clock are stopped */
|
||||
pxa_mmc_stop_clock(mmc);
|
||||
|
||||
/* Turn off SPI mode */
|
||||
writel(0, ®s->spi);
|
||||
|
||||
/* Set up maximum timeout to wait for command response */
|
||||
writel(MMC_RES_TO_MAX_MASK, ®s->resto);
|
||||
|
||||
/* Mask all interrupts */
|
||||
writel(~(MMC_I_MASK_TXFIFO_WR_REQ | MMC_I_MASK_RXFIFO_RD_REQ),
|
||||
®s->i_mask);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops pxa_mmc_ops = {
|
||||
.send_cmd = pxa_mmc_request,
|
||||
.set_ios = pxa_mmc_set_ios,
|
||||
.init = pxa_mmc_init,
|
||||
};
|
||||
|
||||
static struct mmc_config pxa_mmc_cfg = {
|
||||
.name = "PXA MMC",
|
||||
.ops = &pxa_mmc_ops,
|
||||
.voltages = MMC_VDD_32_33 | MMC_VDD_33_34,
|
||||
.f_max = PXAMMC_MAX_SPEED,
|
||||
.f_min = PXAMMC_MIN_SPEED,
|
||||
.host_caps = PXAMMC_HOST_CAPS,
|
||||
.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT,
|
||||
};
|
||||
|
||||
int pxa_mmc_register(int card_index)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
struct pxa_mmc_priv *priv;
|
||||
uint32_t reg;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
priv = malloc(sizeof(struct pxa_mmc_priv));
|
||||
if (!priv)
|
||||
goto err0;
|
||||
|
||||
memset(priv, 0, sizeof(*priv));
|
||||
|
||||
switch (card_index) {
|
||||
case 0:
|
||||
priv->regs = (struct pxa_mmc_regs *)MMC0_BASE;
|
||||
break;
|
||||
case 1:
|
||||
priv->regs = (struct pxa_mmc_regs *)MMC1_BASE;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
printf("PXA MMC: Invalid MMC controller ID (card_index = %d)\n",
|
||||
card_index);
|
||||
goto err1;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_CPU_MONAHANS /* PXA2xx */
|
||||
reg = readl(CKEN);
|
||||
reg |= CKEN12_MMC;
|
||||
writel(reg, CKEN);
|
||||
#else /* PXA3xx */
|
||||
reg = readl(CKENA);
|
||||
reg |= CKENA_12_MMC0 | CKENA_13_MMC1;
|
||||
writel(reg, CKENA);
|
||||
#endif
|
||||
|
||||
mmc = mmc_create(&pxa_mmc_cfg, priv);
|
||||
if (mmc == NULL)
|
||||
goto err1;
|
||||
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
free(priv);
|
||||
err0:
|
||||
return ret;
|
||||
}
|
||||
188
u-boot/drivers/mmc/rockchip_dw_mmc.c
Normal file
188
u-boot/drivers/mmc/rockchip_dw_mmc.c
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Google, Inc
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <dwmmc.h>
|
||||
#include <errno.h>
|
||||
#include <pwrseq.h>
|
||||
#include <syscon.h>
|
||||
#include <asm/gpio.h>
|
||||
#include <asm/arch/clock.h>
|
||||
#include <asm/arch/periph.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
struct rockchip_mmc_plat {
|
||||
struct mmc_config cfg;
|
||||
struct mmc mmc;
|
||||
};
|
||||
|
||||
struct rockchip_dwmmc_priv {
|
||||
struct clk clk;
|
||||
struct dwmci_host host;
|
||||
};
|
||||
|
||||
static uint rockchip_dwmmc_get_mmc_clk(struct dwmci_host *host, uint freq)
|
||||
{
|
||||
struct udevice *dev = host->priv;
|
||||
struct rockchip_dwmmc_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
ret = clk_set_rate(&priv->clk, freq);
|
||||
if (ret < 0) {
|
||||
debug("%s: err=%d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
static int rockchip_dwmmc_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct rockchip_dwmmc_priv *priv = dev_get_priv(dev);
|
||||
struct dwmci_host *host = &priv->host;
|
||||
|
||||
host->name = dev->name;
|
||||
host->ioaddr = (void *)dev_get_addr(dev);
|
||||
host->buswidth = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
|
||||
"bus-width", 4);
|
||||
host->get_mmc_clk = rockchip_dwmmc_get_mmc_clk;
|
||||
host->priv = dev;
|
||||
|
||||
/* use non-removeable as sdcard and emmc as judgement */
|
||||
if (fdtdec_get_bool(gd->fdt_blob, dev->of_offset, "non-removable"))
|
||||
host->dev_index = 0;
|
||||
else
|
||||
host->dev_index = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rockchip_dwmmc_probe(struct udevice *dev)
|
||||
{
|
||||
#ifdef CONFIG_BLK
|
||||
struct rockchip_mmc_plat *plat = dev_get_platdata(dev);
|
||||
#endif
|
||||
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
||||
struct rockchip_dwmmc_priv *priv = dev_get_priv(dev);
|
||||
struct dwmci_host *host = &priv->host;
|
||||
struct udevice *pwr_dev __maybe_unused;
|
||||
u32 minmax[2];
|
||||
int ret;
|
||||
int fifo_depth;
|
||||
|
||||
ret = clk_get_by_index(dev, 0, &priv->clk);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (fdtdec_get_int_array(gd->fdt_blob, dev->of_offset,
|
||||
"clock-freq-min-max", minmax, 2))
|
||||
return -EINVAL;
|
||||
|
||||
fifo_depth = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
|
||||
"fifo-depth", 0);
|
||||
if (fifo_depth < 0)
|
||||
return -EINVAL;
|
||||
|
||||
host->fifoth_val = MSIZE(0x2) |
|
||||
RX_WMARK(fifo_depth / 2 - 1) | TX_WMARK(fifo_depth / 2);
|
||||
|
||||
if (fdtdec_get_bool(gd->fdt_blob, dev->of_offset, "fifo-mode"))
|
||||
host->fifo_mode = true;
|
||||
|
||||
#ifdef CONFIG_PWRSEQ
|
||||
/* Enable power if needed */
|
||||
ret = uclass_get_device_by_phandle(UCLASS_PWRSEQ, dev, "mmc-pwrseq",
|
||||
&pwr_dev);
|
||||
if (!ret) {
|
||||
ret = pwrseq_set_power(pwr_dev, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_BLK
|
||||
dwmci_setup_cfg(&plat->cfg, dev->name, host->buswidth, host->caps,
|
||||
minmax[1], minmax[0]);
|
||||
host->mmc = &plat->mmc;
|
||||
#else
|
||||
ret = add_dwmci(host, minmax[1], minmax[0]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
#endif
|
||||
host->mmc->priv = &priv->host;
|
||||
host->mmc->dev = dev;
|
||||
upriv->mmc = host->mmc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rockchip_dwmmc_bind(struct udevice *dev)
|
||||
{
|
||||
#ifdef CONFIG_BLK
|
||||
struct rockchip_mmc_plat *plat = dev_get_platdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = dwmci_bind(dev, &plat->mmc, &plat->cfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id rockchip_dwmmc_ids[] = {
|
||||
{ .compatible = "rockchip,rk3288-dw-mshc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_dwmmc_drv) = {
|
||||
.name = "rockchip_dwmmc",
|
||||
.id = UCLASS_MMC,
|
||||
.of_match = rockchip_dwmmc_ids,
|
||||
.ofdata_to_platdata = rockchip_dwmmc_ofdata_to_platdata,
|
||||
.bind = rockchip_dwmmc_bind,
|
||||
.probe = rockchip_dwmmc_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct rockchip_dwmmc_priv),
|
||||
.platdata_auto_alloc_size = sizeof(struct rockchip_mmc_plat),
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PWRSEQ
|
||||
static int rockchip_dwmmc_pwrseq_set_power(struct udevice *dev, bool enable)
|
||||
{
|
||||
struct gpio_desc reset;
|
||||
int ret;
|
||||
|
||||
ret = gpio_request_by_name(dev, "reset-gpios", 0, &reset, GPIOD_IS_OUT);
|
||||
if (ret)
|
||||
return ret;
|
||||
dm_gpio_set_value(&reset, 1);
|
||||
udelay(1);
|
||||
dm_gpio_set_value(&reset, 0);
|
||||
udelay(200);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwrseq_ops rockchip_dwmmc_pwrseq_ops = {
|
||||
.set_power = rockchip_dwmmc_pwrseq_set_power,
|
||||
};
|
||||
|
||||
static const struct udevice_id rockchip_dwmmc_pwrseq_ids[] = {
|
||||
{ .compatible = "mmc-pwrseq-emmc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_dwmmc_pwrseq_drv) = {
|
||||
.name = "mmc_pwrseq_emmc",
|
||||
.id = UCLASS_PWRSEQ,
|
||||
.of_match = rockchip_dwmmc_pwrseq_ids,
|
||||
.ops = &rockchip_dwmmc_pwrseq_ops,
|
||||
};
|
||||
#endif
|
||||
324
u-boot/drivers/mmc/rpmb.c
Normal file
324
u-boot/drivers/mmc/rpmb.c
Normal file
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright 2014, Staubli Faverges
|
||||
* Pierre Aubert
|
||||
*
|
||||
* eMMC- Replay Protected Memory Block
|
||||
* According to JEDEC Standard No. 84-A441
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <common.h>
|
||||
#include <memalign.h>
|
||||
#include <mmc.h>
|
||||
#include <u-boot/sha256.h>
|
||||
#include "mmc_private.h"
|
||||
|
||||
/* Request codes */
|
||||
#define RPMB_REQ_KEY 1
|
||||
#define RPMB_REQ_WCOUNTER 2
|
||||
#define RPMB_REQ_WRITE_DATA 3
|
||||
#define RPMB_REQ_READ_DATA 4
|
||||
#define RPMB_REQ_STATUS 5
|
||||
|
||||
/* Response code */
|
||||
#define RPMB_RESP_KEY 0x0100
|
||||
#define RPMB_RESP_WCOUNTER 0x0200
|
||||
#define RPMB_RESP_WRITE_DATA 0x0300
|
||||
#define RPMB_RESP_READ_DATA 0x0400
|
||||
|
||||
/* Error codes */
|
||||
#define RPMB_OK 0
|
||||
#define RPMB_ERR_GENERAL 1
|
||||
#define RPMB_ERR_AUTH 2
|
||||
#define RPMB_ERR_COUNTER 3
|
||||
#define RPMB_ERR_ADDRESS 4
|
||||
#define RPMB_ERR_WRITE 5
|
||||
#define RPMB_ERR_READ 6
|
||||
#define RPMB_ERR_KEY 7
|
||||
#define RPMB_ERR_CNT_EXPIRED 0x80
|
||||
#define RPMB_ERR_MSK 0x7
|
||||
|
||||
/* Sizes of RPMB data frame */
|
||||
#define RPMB_SZ_STUFF 196
|
||||
#define RPMB_SZ_MAC 32
|
||||
#define RPMB_SZ_DATA 256
|
||||
#define RPMB_SZ_NONCE 16
|
||||
|
||||
#define SHA256_BLOCK_SIZE 64
|
||||
|
||||
/* Error messages */
|
||||
static const char * const rpmb_err_msg[] = {
|
||||
"",
|
||||
"General failure",
|
||||
"Authentication failure",
|
||||
"Counter failure",
|
||||
"Address failure",
|
||||
"Write failure",
|
||||
"Read failure",
|
||||
"Authentication key not yet programmed",
|
||||
};
|
||||
|
||||
|
||||
/* Structure of RPMB data frame. */
|
||||
struct s_rpmb {
|
||||
unsigned char stuff[RPMB_SZ_STUFF];
|
||||
unsigned char mac[RPMB_SZ_MAC];
|
||||
unsigned char data[RPMB_SZ_DATA];
|
||||
unsigned char nonce[RPMB_SZ_NONCE];
|
||||
unsigned long write_counter;
|
||||
unsigned short address;
|
||||
unsigned short block_count;
|
||||
unsigned short result;
|
||||
unsigned short request;
|
||||
};
|
||||
|
||||
static int mmc_set_blockcount(struct mmc *mmc, unsigned int blockcount,
|
||||
bool is_rel_write)
|
||||
{
|
||||
struct mmc_cmd cmd = {0};
|
||||
|
||||
cmd.cmdidx = MMC_CMD_SET_BLOCK_COUNT;
|
||||
cmd.cmdarg = blockcount & 0x0000FFFF;
|
||||
if (is_rel_write)
|
||||
cmd.cmdarg |= 1 << 31;
|
||||
cmd.resp_type = MMC_RSP_R1;
|
||||
|
||||
return mmc_send_cmd(mmc, &cmd, NULL);
|
||||
}
|
||||
static int mmc_rpmb_request(struct mmc *mmc, const struct s_rpmb *s,
|
||||
unsigned int count, bool is_rel_write)
|
||||
{
|
||||
struct mmc_cmd cmd = {0};
|
||||
struct mmc_data data;
|
||||
int ret;
|
||||
|
||||
ret = mmc_set_blockcount(mmc, count, is_rel_write);
|
||||
if (ret) {
|
||||
#ifdef CONFIG_MMC_RPMB_TRACE
|
||||
printf("%s:mmc_set_blockcount-> %d\n", __func__, ret);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK;
|
||||
cmd.cmdarg = 0;
|
||||
cmd.resp_type = MMC_RSP_R1b;
|
||||
|
||||
data.src = (const char *)s;
|
||||
data.blocks = 1;
|
||||
data.blocksize = MMC_MAX_BLOCK_LEN;
|
||||
data.flags = MMC_DATA_WRITE;
|
||||
|
||||
ret = mmc_send_cmd(mmc, &cmd, &data);
|
||||
if (ret) {
|
||||
#ifdef CONFIG_MMC_RPMB_TRACE
|
||||
printf("%s:mmc_send_cmd-> %d\n", __func__, ret);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int mmc_rpmb_response(struct mmc *mmc, struct s_rpmb *s,
|
||||
unsigned short expected)
|
||||
{
|
||||
struct mmc_cmd cmd = {0};
|
||||
struct mmc_data data;
|
||||
int ret;
|
||||
|
||||
ret = mmc_set_blockcount(mmc, 1, false);
|
||||
if (ret) {
|
||||
#ifdef CONFIG_MMC_RPMB_TRACE
|
||||
printf("%s:mmc_set_blockcount-> %d\n", __func__, ret);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK;
|
||||
cmd.cmdarg = 0;
|
||||
cmd.resp_type = MMC_RSP_R1;
|
||||
|
||||
data.dest = (char *)s;
|
||||
data.blocks = 1;
|
||||
data.blocksize = MMC_MAX_BLOCK_LEN;
|
||||
data.flags = MMC_DATA_READ;
|
||||
|
||||
ret = mmc_send_cmd(mmc, &cmd, &data);
|
||||
if (ret) {
|
||||
#ifdef CONFIG_MMC_RPMB_TRACE
|
||||
printf("%s:mmc_send_cmd-> %d\n", __func__, ret);
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
/* Check the response and the status */
|
||||
if (be16_to_cpu(s->request) != expected) {
|
||||
#ifdef CONFIG_MMC_RPMB_TRACE
|
||||
printf("%s:response= %x\n", __func__,
|
||||
be16_to_cpu(s->request));
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
ret = be16_to_cpu(s->result);
|
||||
if (ret) {
|
||||
printf("%s %s\n", rpmb_err_msg[ret & RPMB_ERR_MSK],
|
||||
(ret & RPMB_ERR_CNT_EXPIRED) ?
|
||||
"Write counter has expired" : "");
|
||||
}
|
||||
|
||||
/* Return the status of the command */
|
||||
return ret;
|
||||
}
|
||||
static int mmc_rpmb_status(struct mmc *mmc, unsigned short expected)
|
||||
{
|
||||
ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1);
|
||||
|
||||
memset(rpmb_frame, 0, sizeof(struct s_rpmb));
|
||||
rpmb_frame->request = cpu_to_be16(RPMB_REQ_STATUS);
|
||||
if (mmc_rpmb_request(mmc, rpmb_frame, 1, false))
|
||||
return -1;
|
||||
|
||||
/* Read the result */
|
||||
return mmc_rpmb_response(mmc, rpmb_frame, expected);
|
||||
}
|
||||
static void rpmb_hmac(unsigned char *key, unsigned char *buff, int len,
|
||||
unsigned char *output)
|
||||
{
|
||||
sha256_context ctx;
|
||||
int i;
|
||||
unsigned char k_ipad[SHA256_BLOCK_SIZE];
|
||||
unsigned char k_opad[SHA256_BLOCK_SIZE];
|
||||
|
||||
sha256_starts(&ctx);
|
||||
|
||||
/* According to RFC 4634, the HMAC transform looks like:
|
||||
SHA(K XOR opad, SHA(K XOR ipad, text))
|
||||
|
||||
where K is an n byte key.
|
||||
ipad is the byte 0x36 repeated blocksize times
|
||||
opad is the byte 0x5c repeated blocksize times
|
||||
and text is the data being protected.
|
||||
*/
|
||||
|
||||
for (i = 0; i < RPMB_SZ_MAC; i++) {
|
||||
k_ipad[i] = key[i] ^ 0x36;
|
||||
k_opad[i] = key[i] ^ 0x5c;
|
||||
}
|
||||
/* remaining pad bytes are '\0' XOR'd with ipad and opad values */
|
||||
for ( ; i < SHA256_BLOCK_SIZE; i++) {
|
||||
k_ipad[i] = 0x36;
|
||||
k_opad[i] = 0x5c;
|
||||
}
|
||||
sha256_update(&ctx, k_ipad, SHA256_BLOCK_SIZE);
|
||||
sha256_update(&ctx, buff, len);
|
||||
sha256_finish(&ctx, output);
|
||||
|
||||
/* Init context for second pass */
|
||||
sha256_starts(&ctx);
|
||||
|
||||
/* start with outer pad */
|
||||
sha256_update(&ctx, k_opad, SHA256_BLOCK_SIZE);
|
||||
|
||||
/* then results of 1st hash */
|
||||
sha256_update(&ctx, output, RPMB_SZ_MAC);
|
||||
|
||||
/* finish up 2nd pass */
|
||||
sha256_finish(&ctx, output);
|
||||
}
|
||||
int mmc_rpmb_get_counter(struct mmc *mmc, unsigned long *pcounter)
|
||||
{
|
||||
int ret;
|
||||
ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1);
|
||||
|
||||
/* Fill the request */
|
||||
memset(rpmb_frame, 0, sizeof(struct s_rpmb));
|
||||
rpmb_frame->request = cpu_to_be16(RPMB_REQ_WCOUNTER);
|
||||
if (mmc_rpmb_request(mmc, rpmb_frame, 1, false))
|
||||
return -1;
|
||||
|
||||
/* Read the result */
|
||||
ret = mmc_rpmb_response(mmc, rpmb_frame, RPMB_RESP_WCOUNTER);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*pcounter = be32_to_cpu(rpmb_frame->write_counter);
|
||||
return 0;
|
||||
}
|
||||
int mmc_rpmb_set_key(struct mmc *mmc, void *key)
|
||||
{
|
||||
ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1);
|
||||
/* Fill the request */
|
||||
memset(rpmb_frame, 0, sizeof(struct s_rpmb));
|
||||
rpmb_frame->request = cpu_to_be16(RPMB_REQ_KEY);
|
||||
memcpy(rpmb_frame->mac, key, RPMB_SZ_MAC);
|
||||
|
||||
if (mmc_rpmb_request(mmc, rpmb_frame, 1, true))
|
||||
return -1;
|
||||
|
||||
/* read the operation status */
|
||||
return mmc_rpmb_status(mmc, RPMB_RESP_KEY);
|
||||
}
|
||||
int mmc_rpmb_read(struct mmc *mmc, void *addr, unsigned short blk,
|
||||
unsigned short cnt, unsigned char *key)
|
||||
{
|
||||
ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
/* Fill the request */
|
||||
memset(rpmb_frame, 0, sizeof(struct s_rpmb));
|
||||
rpmb_frame->address = cpu_to_be16(blk + i);
|
||||
rpmb_frame->request = cpu_to_be16(RPMB_REQ_READ_DATA);
|
||||
if (mmc_rpmb_request(mmc, rpmb_frame, 1, false))
|
||||
break;
|
||||
|
||||
/* Read the result */
|
||||
if (mmc_rpmb_response(mmc, rpmb_frame, RPMB_RESP_READ_DATA))
|
||||
break;
|
||||
|
||||
/* Check the HMAC if key is provided */
|
||||
if (key) {
|
||||
unsigned char ret_hmac[RPMB_SZ_MAC];
|
||||
|
||||
rpmb_hmac(key, rpmb_frame->data, 284, ret_hmac);
|
||||
if (memcmp(ret_hmac, rpmb_frame->mac, RPMB_SZ_MAC)) {
|
||||
printf("MAC error on block #%d\n", i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Copy data */
|
||||
memcpy(addr + i * RPMB_SZ_DATA, rpmb_frame->data, RPMB_SZ_DATA);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
int mmc_rpmb_write(struct mmc *mmc, void *addr, unsigned short blk,
|
||||
unsigned short cnt, unsigned char *key)
|
||||
{
|
||||
ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1);
|
||||
unsigned long wcount;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
if (mmc_rpmb_get_counter(mmc, &wcount)) {
|
||||
printf("Cannot read RPMB write counter\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Fill the request */
|
||||
memset(rpmb_frame, 0, sizeof(struct s_rpmb));
|
||||
memcpy(rpmb_frame->data, addr + i * RPMB_SZ_DATA, RPMB_SZ_DATA);
|
||||
rpmb_frame->address = cpu_to_be16(blk + i);
|
||||
rpmb_frame->block_count = cpu_to_be16(1);
|
||||
rpmb_frame->write_counter = cpu_to_be32(wcount);
|
||||
rpmb_frame->request = cpu_to_be16(RPMB_REQ_WRITE_DATA);
|
||||
/* Computes HMAC */
|
||||
rpmb_hmac(key, rpmb_frame->data, 284, rpmb_frame->mac);
|
||||
|
||||
if (mmc_rpmb_request(mmc, rpmb_frame, 1, true))
|
||||
break;
|
||||
|
||||
/* Get status */
|
||||
if (mmc_rpmb_status(mmc, RPMB_RESP_WRITE_DATA))
|
||||
break;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
321
u-boot/drivers/mmc/s3c_sdi.c
Normal file
321
u-boot/drivers/mmc/s3c_sdi.c
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* S3C24xx SD/MMC driver
|
||||
*
|
||||
* Based on OpenMoko S3C24xx driver by Harald Welte <laforge@openmoko.org>
|
||||
*
|
||||
* Copyright (C) 2014 Marek Vasut <marex@denx.de>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <mmc.h>
|
||||
#include <errno.h>
|
||||
#include <asm/arch/s3c24x0_cpu.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
#define S3C2440_SDICON_SDRESET (1 << 8)
|
||||
#define S3C2410_SDICON_FIFORESET (1 << 1)
|
||||
#define S3C2410_SDICON_CLOCKTYPE (1 << 0)
|
||||
|
||||
#define S3C2410_SDICMDCON_LONGRSP (1 << 10)
|
||||
#define S3C2410_SDICMDCON_WAITRSP (1 << 9)
|
||||
#define S3C2410_SDICMDCON_CMDSTART (1 << 8)
|
||||
#define S3C2410_SDICMDCON_SENDERHOST (1 << 6)
|
||||
#define S3C2410_SDICMDCON_INDEX 0x3f
|
||||
|
||||
#define S3C2410_SDICMDSTAT_CRCFAIL (1 << 12)
|
||||
#define S3C2410_SDICMDSTAT_CMDSENT (1 << 11)
|
||||
#define S3C2410_SDICMDSTAT_CMDTIMEOUT (1 << 10)
|
||||
#define S3C2410_SDICMDSTAT_RSPFIN (1 << 9)
|
||||
|
||||
#define S3C2440_SDIDCON_DS_WORD (2 << 22)
|
||||
#define S3C2410_SDIDCON_TXAFTERRESP (1 << 20)
|
||||
#define S3C2410_SDIDCON_RXAFTERCMD (1 << 19)
|
||||
#define S3C2410_SDIDCON_BLOCKMODE (1 << 17)
|
||||
#define S3C2410_SDIDCON_WIDEBUS (1 << 16)
|
||||
#define S3C2440_SDIDCON_DATSTART (1 << 14)
|
||||
#define S3C2410_SDIDCON_XFER_RXSTART (2 << 12)
|
||||
#define S3C2410_SDIDCON_XFER_TXSTART (3 << 12)
|
||||
#define S3C2410_SDIDCON_BLKNUM 0x7ff
|
||||
|
||||
#define S3C2410_SDIDSTA_FIFOFAIL (1 << 8)
|
||||
#define S3C2410_SDIDSTA_CRCFAIL (1 << 7)
|
||||
#define S3C2410_SDIDSTA_RXCRCFAIL (1 << 6)
|
||||
#define S3C2410_SDIDSTA_DATATIMEOUT (1 << 5)
|
||||
#define S3C2410_SDIDSTA_XFERFINISH (1 << 4)
|
||||
|
||||
#define S3C2410_SDIFSTA_TFHALF (1 << 11)
|
||||
#define S3C2410_SDIFSTA_COUNTMASK 0x7f
|
||||
|
||||
/*
|
||||
* WARNING: We only support one SD IP block.
|
||||
* NOTE: It's not likely there will ever exist an S3C24xx with two,
|
||||
* at least not in this universe all right.
|
||||
*/
|
||||
static int wide_bus;
|
||||
|
||||
static int
|
||||
s3cmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
|
||||
{
|
||||
struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi();
|
||||
uint32_t sdiccon, sdicsta, sdidcon, sdidsta, sdidat, sdifsta;
|
||||
uint32_t sdicsta_wait_bit = S3C2410_SDICMDSTAT_CMDSENT;
|
||||
unsigned int timeout = 100000;
|
||||
int ret = 0, xfer_len, data_offset = 0;
|
||||
const uint32_t sdidsta_err_mask = S3C2410_SDIDSTA_FIFOFAIL |
|
||||
S3C2410_SDIDSTA_CRCFAIL | S3C2410_SDIDSTA_RXCRCFAIL |
|
||||
S3C2410_SDIDSTA_DATATIMEOUT;
|
||||
|
||||
|
||||
writel(0xffffffff, &sdi_regs->sdicsta);
|
||||
writel(0xffffffff, &sdi_regs->sdidsta);
|
||||
writel(0xffffffff, &sdi_regs->sdifsta);
|
||||
|
||||
/* Set up data transfer (if applicable). */
|
||||
if (data) {
|
||||
writel(data->blocksize, &sdi_regs->sdibsize);
|
||||
|
||||
sdidcon = data->blocks & S3C2410_SDIDCON_BLKNUM;
|
||||
sdidcon |= S3C2410_SDIDCON_BLOCKMODE;
|
||||
#if defined(CONFIG_S3C2440)
|
||||
sdidcon |= S3C2440_SDIDCON_DS_WORD | S3C2440_SDIDCON_DATSTART;
|
||||
#endif
|
||||
if (wide_bus)
|
||||
sdidcon |= S3C2410_SDIDCON_WIDEBUS;
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
sdidcon |= S3C2410_SDIDCON_RXAFTERCMD;
|
||||
sdidcon |= S3C2410_SDIDCON_XFER_RXSTART;
|
||||
} else {
|
||||
sdidcon |= S3C2410_SDIDCON_TXAFTERRESP;
|
||||
sdidcon |= S3C2410_SDIDCON_XFER_TXSTART;
|
||||
}
|
||||
|
||||
writel(sdidcon, &sdi_regs->sdidcon);
|
||||
}
|
||||
|
||||
/* Write CMD arg. */
|
||||
writel(cmd->cmdarg, &sdi_regs->sdicarg);
|
||||
|
||||
/* Write CMD index. */
|
||||
sdiccon = cmd->cmdidx & S3C2410_SDICMDCON_INDEX;
|
||||
sdiccon |= S3C2410_SDICMDCON_SENDERHOST;
|
||||
sdiccon |= S3C2410_SDICMDCON_CMDSTART;
|
||||
|
||||
/* Command with short response. */
|
||||
if (cmd->resp_type & MMC_RSP_PRESENT) {
|
||||
sdiccon |= S3C2410_SDICMDCON_WAITRSP;
|
||||
sdicsta_wait_bit = S3C2410_SDICMDSTAT_RSPFIN;
|
||||
}
|
||||
|
||||
/* Command with long response. */
|
||||
if (cmd->resp_type & MMC_RSP_136)
|
||||
sdiccon |= S3C2410_SDICMDCON_LONGRSP;
|
||||
|
||||
/* Start the command. */
|
||||
writel(sdiccon, &sdi_regs->sdiccon);
|
||||
|
||||
/* Wait for the command to complete or for response. */
|
||||
for (timeout = 100000; timeout; timeout--) {
|
||||
sdicsta = readl(&sdi_regs->sdicsta);
|
||||
if (sdicsta & sdicsta_wait_bit)
|
||||
break;
|
||||
|
||||
if (sdicsta & S3C2410_SDICMDSTAT_CMDTIMEOUT)
|
||||
timeout = 1;
|
||||
}
|
||||
|
||||
/* Clean the status bits. */
|
||||
setbits_le32(&sdi_regs->sdicsta, 0xf << 9);
|
||||
|
||||
if (!timeout) {
|
||||
puts("S3C SDI: Command timed out!\n");
|
||||
ret = TIMEOUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Read out the response. */
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
cmd->response[0] = readl(&sdi_regs->sdirsp0);
|
||||
cmd->response[1] = readl(&sdi_regs->sdirsp1);
|
||||
cmd->response[2] = readl(&sdi_regs->sdirsp2);
|
||||
cmd->response[3] = readl(&sdi_regs->sdirsp3);
|
||||
} else {
|
||||
cmd->response[0] = readl(&sdi_regs->sdirsp0);
|
||||
}
|
||||
|
||||
/* If there are no data, we're done. */
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
xfer_len = data->blocksize * data->blocks;
|
||||
|
||||
while (xfer_len > 0) {
|
||||
sdidsta = readl(&sdi_regs->sdidsta);
|
||||
sdifsta = readl(&sdi_regs->sdifsta);
|
||||
|
||||
if (sdidsta & sdidsta_err_mask) {
|
||||
printf("S3C SDI: Data error (sdta=0x%08x)\n", sdidsta);
|
||||
ret = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
if ((sdifsta & S3C2410_SDIFSTA_COUNTMASK) < 4)
|
||||
continue;
|
||||
sdidat = readl(&sdi_regs->sdidat);
|
||||
put_unaligned_le32(sdidat, data->dest + data_offset);
|
||||
} else { /* Write */
|
||||
/* TX FIFO half full. */
|
||||
if (!(sdifsta & S3C2410_SDIFSTA_TFHALF))
|
||||
continue;
|
||||
|
||||
/* TX FIFO is below 32b full, write. */
|
||||
sdidat = get_unaligned_le32(data->src + data_offset);
|
||||
writel(sdidat, &sdi_regs->sdidat);
|
||||
}
|
||||
data_offset += 4;
|
||||
xfer_len -= 4;
|
||||
}
|
||||
|
||||
/* Wait for the command to complete or for response. */
|
||||
for (timeout = 100000; timeout; timeout--) {
|
||||
sdidsta = readl(&sdi_regs->sdidsta);
|
||||
if (sdidsta & S3C2410_SDIDSTA_XFERFINISH)
|
||||
break;
|
||||
|
||||
if (sdidsta & S3C2410_SDIDSTA_DATATIMEOUT)
|
||||
timeout = 1;
|
||||
}
|
||||
|
||||
/* Clear status bits. */
|
||||
writel(0x6f8, &sdi_regs->sdidsta);
|
||||
|
||||
if (!timeout) {
|
||||
puts("S3C SDI: Command timed out!\n");
|
||||
ret = TIMEOUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
writel(0, &sdi_regs->sdidcon);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void s3cmmc_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi();
|
||||
uint32_t divider = 0;
|
||||
|
||||
wide_bus = (mmc->bus_width == 4);
|
||||
|
||||
if (!mmc->clock)
|
||||
return;
|
||||
|
||||
divider = DIV_ROUND_UP(get_PCLK(), mmc->clock);
|
||||
if (divider)
|
||||
divider--;
|
||||
|
||||
writel(divider, &sdi_regs->sdipre);
|
||||
mdelay(125);
|
||||
}
|
||||
|
||||
static int s3cmmc_init(struct mmc *mmc)
|
||||
{
|
||||
struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power();
|
||||
struct s3c24x0_sdi *sdi_regs = s3c24x0_get_base_sdi();
|
||||
|
||||
/* Start the clock. */
|
||||
setbits_le32(&clk_power->clkcon, 1 << 9);
|
||||
|
||||
#if defined(CONFIG_S3C2440)
|
||||
writel(S3C2440_SDICON_SDRESET, &sdi_regs->sdicon);
|
||||
mdelay(10);
|
||||
writel(0x7fffff, &sdi_regs->sdidtimer);
|
||||
#else
|
||||
writel(0xffff, &sdi_regs->sdidtimer);
|
||||
#endif
|
||||
writel(MMC_MAX_BLOCK_LEN, &sdi_regs->sdibsize);
|
||||
writel(0x0, &sdi_regs->sdiimsk);
|
||||
|
||||
writel(S3C2410_SDICON_FIFORESET | S3C2410_SDICON_CLOCKTYPE,
|
||||
&sdi_regs->sdicon);
|
||||
|
||||
mdelay(125);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct s3cmmc_priv {
|
||||
struct mmc_config cfg;
|
||||
int (*getcd)(struct mmc *);
|
||||
int (*getwp)(struct mmc *);
|
||||
};
|
||||
|
||||
static int s3cmmc_getcd(struct mmc *mmc)
|
||||
{
|
||||
struct s3cmmc_priv *priv = mmc->priv;
|
||||
if (priv->getcd)
|
||||
return priv->getcd(mmc);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3cmmc_getwp(struct mmc *mmc)
|
||||
{
|
||||
struct s3cmmc_priv *priv = mmc->priv;
|
||||
if (priv->getwp)
|
||||
return priv->getwp(mmc);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops s3cmmc_ops = {
|
||||
.send_cmd = s3cmmc_send_cmd,
|
||||
.set_ios = s3cmmc_set_ios,
|
||||
.init = s3cmmc_init,
|
||||
.getcd = s3cmmc_getcd,
|
||||
.getwp = s3cmmc_getwp,
|
||||
};
|
||||
|
||||
int s3cmmc_initialize(bd_t *bis, int (*getcd)(struct mmc *),
|
||||
int (*getwp)(struct mmc *))
|
||||
{
|
||||
struct s3cmmc_priv *priv;
|
||||
struct mmc *mmc;
|
||||
struct mmc_config *cfg;
|
||||
|
||||
priv = calloc(1, sizeof(*priv));
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
cfg = &priv->cfg;
|
||||
|
||||
cfg->name = "S3C MMC";
|
||||
cfg->ops = &s3cmmc_ops;
|
||||
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS;
|
||||
cfg->f_min = 400000;
|
||||
cfg->f_max = get_PCLK() / 2;
|
||||
cfg->b_max = 0x80;
|
||||
|
||||
#if defined(CONFIG_S3C2410)
|
||||
/*
|
||||
* S3C2410 has some bug that prevents reliable
|
||||
* operation at higher speed
|
||||
*/
|
||||
cfg->f_max /= 2;
|
||||
#endif
|
||||
|
||||
mmc = mmc_create(cfg, priv);
|
||||
if (!mmc) {
|
||||
free(priv);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
217
u-boot/drivers/mmc/s5p_sdhci.c
Normal file
217
u-boot/drivers/mmc/s5p_sdhci.c
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* (C) Copyright 2012 SAMSUNG Electronics
|
||||
* Jaehoon Chung <jh80.chung@samsung.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
#include <fdtdec.h>
|
||||
#include <libfdt.h>
|
||||
#include <asm/gpio.h>
|
||||
#include <asm/arch/mmc.h>
|
||||
#include <asm/arch/clk.h>
|
||||
#include <errno.h>
|
||||
#include <asm/arch/pinmux.h>
|
||||
|
||||
static char *S5P_NAME = "SAMSUNG SDHCI";
|
||||
static void s5p_sdhci_set_control_reg(struct sdhci_host *host)
|
||||
{
|
||||
unsigned long val, ctrl;
|
||||
/*
|
||||
* SELCLKPADDS[17:16]
|
||||
* 00 = 2mA
|
||||
* 01 = 4mA
|
||||
* 10 = 7mA
|
||||
* 11 = 9mA
|
||||
*/
|
||||
sdhci_writel(host, SDHCI_CTRL4_DRIVE_MASK(0x3), SDHCI_CONTROL4);
|
||||
|
||||
val = sdhci_readl(host, SDHCI_CONTROL2);
|
||||
val &= SDHCI_CTRL2_SELBASECLK_MASK(3);
|
||||
|
||||
val |= SDHCI_CTRL2_ENSTAASYNCCLR |
|
||||
SDHCI_CTRL2_ENCMDCNFMSK |
|
||||
SDHCI_CTRL2_ENFBCLKRX |
|
||||
SDHCI_CTRL2_ENCLKOUTHOLD;
|
||||
|
||||
sdhci_writel(host, val, SDHCI_CONTROL2);
|
||||
|
||||
/*
|
||||
* FCSEL3[31] FCSEL2[23] FCSEL1[15] FCSEL0[7]
|
||||
* FCSel[1:0] : Rx Feedback Clock Delay Control
|
||||
* Inverter delay means10ns delay if SDCLK 50MHz setting
|
||||
* 01 = Delay1 (basic delay)
|
||||
* 11 = Delay2 (basic delay + 2ns)
|
||||
* 00 = Delay3 (inverter delay)
|
||||
* 10 = Delay4 (inverter delay + 2ns)
|
||||
*/
|
||||
val = SDHCI_CTRL3_FCSEL0 | SDHCI_CTRL3_FCSEL1;
|
||||
sdhci_writel(host, val, SDHCI_CONTROL3);
|
||||
|
||||
/*
|
||||
* SELBASECLK[5:4]
|
||||
* 00/01 = HCLK
|
||||
* 10 = EPLL
|
||||
* 11 = XTI or XEXTCLK
|
||||
*/
|
||||
ctrl = sdhci_readl(host, SDHCI_CONTROL2);
|
||||
ctrl &= ~SDHCI_CTRL2_SELBASECLK_MASK(0x3);
|
||||
ctrl |= SDHCI_CTRL2_SELBASECLK_MASK(0x2);
|
||||
sdhci_writel(host, ctrl, SDHCI_CONTROL2);
|
||||
}
|
||||
|
||||
static int s5p_sdhci_core_init(struct sdhci_host *host)
|
||||
{
|
||||
host->name = S5P_NAME;
|
||||
|
||||
host->quirks = SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_VOLTAGE |
|
||||
SDHCI_QUIRK_BROKEN_R1B | SDHCI_QUIRK_32BIT_DMA_ADDR |
|
||||
SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_USE_WIDE8;
|
||||
host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
|
||||
host->set_control_reg = &s5p_sdhci_set_control_reg;
|
||||
host->set_clock = set_mmc_clk;
|
||||
|
||||
if (host->bus_width == 8)
|
||||
host->host_caps |= MMC_MODE_8BIT;
|
||||
|
||||
return add_sdhci(host, 52000000, 400000);
|
||||
}
|
||||
|
||||
int s5p_sdhci_init(u32 regbase, int index, int bus_width)
|
||||
{
|
||||
struct sdhci_host *host = calloc(1, sizeof(struct sdhci_host));
|
||||
if (!host) {
|
||||
printf("sdhci__host allocation fail!\n");
|
||||
return 1;
|
||||
}
|
||||
host->ioaddr = (void *)regbase;
|
||||
host->index = index;
|
||||
host->bus_width = bus_width;
|
||||
|
||||
return s5p_sdhci_core_init(host);
|
||||
}
|
||||
|
||||
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
struct sdhci_host sdhci_host[SDHCI_MAX_HOSTS];
|
||||
|
||||
static int do_sdhci_init(struct sdhci_host *host)
|
||||
{
|
||||
int dev_id, flag, ret;
|
||||
|
||||
flag = host->bus_width == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE;
|
||||
dev_id = host->index + PERIPH_ID_SDMMC0;
|
||||
|
||||
ret = exynos_pinmux_config(dev_id, flag);
|
||||
if (ret) {
|
||||
printf("external SD not configured\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (dm_gpio_is_valid(&host->pwr_gpio)) {
|
||||
dm_gpio_set_value(&host->pwr_gpio, 1);
|
||||
ret = exynos_pinmux_config(dev_id, flag);
|
||||
if (ret) {
|
||||
debug("MMC not configured\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (dm_gpio_is_valid(&host->cd_gpio)) {
|
||||
ret = dm_gpio_get_value(&host->cd_gpio);
|
||||
if (ret) {
|
||||
debug("no SD card detected (%d)\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
return s5p_sdhci_core_init(host);
|
||||
}
|
||||
|
||||
static int sdhci_get_config(const void *blob, int node, struct sdhci_host *host)
|
||||
{
|
||||
int bus_width, dev_id;
|
||||
unsigned int base;
|
||||
|
||||
/* Get device id */
|
||||
dev_id = pinmux_decode_periph_id(blob, node);
|
||||
if (dev_id < PERIPH_ID_SDMMC0 && dev_id > PERIPH_ID_SDMMC3) {
|
||||
debug("MMC: Can't get device id\n");
|
||||
return -1;
|
||||
}
|
||||
host->index = dev_id - PERIPH_ID_SDMMC0;
|
||||
|
||||
/* Get bus width */
|
||||
bus_width = fdtdec_get_int(blob, node, "samsung,bus-width", 0);
|
||||
if (bus_width <= 0) {
|
||||
debug("MMC: Can't get bus-width\n");
|
||||
return -1;
|
||||
}
|
||||
host->bus_width = bus_width;
|
||||
|
||||
/* Get the base address from the device node */
|
||||
base = fdtdec_get_addr(blob, node, "reg");
|
||||
if (!base) {
|
||||
debug("MMC: Can't get base address\n");
|
||||
return -1;
|
||||
}
|
||||
host->ioaddr = (void *)base;
|
||||
|
||||
gpio_request_by_name_nodev(blob, node, "pwr-gpios", 0, &host->pwr_gpio,
|
||||
GPIOD_IS_OUT);
|
||||
gpio_request_by_name_nodev(blob, node, "cd-gpios", 0, &host->cd_gpio,
|
||||
GPIOD_IS_IN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int process_nodes(const void *blob, int node_list[], int count)
|
||||
{
|
||||
struct sdhci_host *host;
|
||||
int i, node, ret;
|
||||
int failed = 0;
|
||||
|
||||
debug("%s: count = %d\n", __func__, count);
|
||||
|
||||
/* build sdhci_host[] for each controller */
|
||||
for (i = 0; i < count; i++) {
|
||||
node = node_list[i];
|
||||
if (node <= 0)
|
||||
continue;
|
||||
|
||||
host = &sdhci_host[i];
|
||||
|
||||
ret = sdhci_get_config(blob, node, host);
|
||||
if (ret) {
|
||||
printf("%s: failed to decode dev %d (%d)\n", __func__, i, ret);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = do_sdhci_init(host);
|
||||
if (ret && ret != -ENODEV) {
|
||||
printf("%s: failed to initialize dev %d (%d)\n", __func__, i, ret);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
/* we only consider it an error when all nodes fail */
|
||||
return (failed == count ? -1 : 0);
|
||||
}
|
||||
|
||||
int exynos_mmc_init(const void *blob)
|
||||
{
|
||||
int count;
|
||||
int node_list[SDHCI_MAX_HOSTS];
|
||||
|
||||
count = fdtdec_find_aliases_for_id(blob, "mmc",
|
||||
COMPAT_SAMSUNG_EXYNOS_MMC, node_list,
|
||||
SDHCI_MAX_HOSTS);
|
||||
|
||||
return process_nodes(blob, node_list, count);
|
||||
}
|
||||
#endif
|
||||
157
u-boot/drivers/mmc/sandbox_mmc.c
Normal file
157
u-boot/drivers/mmc/sandbox_mmc.c
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Google, Inc
|
||||
* Written by Simon Glass <sjg@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <fdtdec.h>
|
||||
#include <mmc.h>
|
||||
#include <asm/test.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
struct sandbox_mmc_plat {
|
||||
struct mmc_config cfg;
|
||||
struct mmc mmc;
|
||||
};
|
||||
|
||||
/**
|
||||
* sandbox_mmc_send_cmd() - Emulate SD commands
|
||||
*
|
||||
* This emulate an SD card version 2. Single-block reads result in zero data.
|
||||
* Multiple-block reads return a test string.
|
||||
*/
|
||||
static int sandbox_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
switch (cmd->cmdidx) {
|
||||
case MMC_CMD_ALL_SEND_CID:
|
||||
break;
|
||||
case SD_CMD_SEND_RELATIVE_ADDR:
|
||||
cmd->response[0] = 0 << 16; /* mmc->rca */
|
||||
case MMC_CMD_GO_IDLE_STATE:
|
||||
break;
|
||||
case SD_CMD_SEND_IF_COND:
|
||||
cmd->response[0] = 0xaa;
|
||||
break;
|
||||
case MMC_CMD_SEND_STATUS:
|
||||
cmd->response[0] = MMC_STATUS_RDY_FOR_DATA;
|
||||
break;
|
||||
case MMC_CMD_SELECT_CARD:
|
||||
break;
|
||||
case MMC_CMD_SEND_CSD:
|
||||
cmd->response[0] = 0;
|
||||
cmd->response[1] = 10 << 16; /* 1 << block_len */
|
||||
break;
|
||||
case SD_CMD_SWITCH_FUNC: {
|
||||
u32 *resp = (u32 *)data->dest;
|
||||
|
||||
resp[7] = cpu_to_be32(SD_HIGHSPEED_BUSY);
|
||||
break;
|
||||
}
|
||||
case MMC_CMD_READ_SINGLE_BLOCK:
|
||||
memset(data->dest, '\0', data->blocksize);
|
||||
break;
|
||||
case MMC_CMD_READ_MULTIPLE_BLOCK:
|
||||
strcpy(data->dest, "this is a test");
|
||||
break;
|
||||
case MMC_CMD_STOP_TRANSMISSION:
|
||||
break;
|
||||
case SD_CMD_APP_SEND_OP_COND:
|
||||
cmd->response[0] = OCR_BUSY | OCR_HCS;
|
||||
cmd->response[1] = 0;
|
||||
cmd->response[2] = 0;
|
||||
break;
|
||||
case MMC_CMD_APP_CMD:
|
||||
break;
|
||||
case MMC_CMD_SET_BLOCKLEN:
|
||||
debug("block len %d\n", cmd->cmdarg);
|
||||
break;
|
||||
case SD_CMD_APP_SEND_SCR: {
|
||||
u32 *scr = (u32 *)data->dest;
|
||||
|
||||
scr[0] = cpu_to_be32(2 << 24 | 1 << 15); /* SD version 3 */
|
||||
break;
|
||||
}
|
||||
default:
|
||||
debug("%s: Unknown command %d\n", __func__, cmd->cmdidx);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sandbox_mmc_set_ios(struct mmc *mmc)
|
||||
{
|
||||
}
|
||||
|
||||
static int sandbox_mmc_init(struct mmc *mmc)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sandbox_mmc_getcd(struct mmc *mmc)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct mmc_ops sandbox_mmc_ops = {
|
||||
.send_cmd = sandbox_mmc_send_cmd,
|
||||
.set_ios = sandbox_mmc_set_ios,
|
||||
.init = sandbox_mmc_init,
|
||||
.getcd = sandbox_mmc_getcd,
|
||||
};
|
||||
|
||||
int sandbox_mmc_probe(struct udevice *dev)
|
||||
{
|
||||
struct sandbox_mmc_plat *plat = dev_get_platdata(dev);
|
||||
|
||||
return mmc_init(&plat->mmc);
|
||||
}
|
||||
|
||||
int sandbox_mmc_bind(struct udevice *dev)
|
||||
{
|
||||
struct sandbox_mmc_plat *plat = dev_get_platdata(dev);
|
||||
struct mmc_config *cfg = &plat->cfg;
|
||||
int ret;
|
||||
|
||||
cfg->name = dev->name;
|
||||
cfg->ops = &sandbox_mmc_ops;
|
||||
cfg->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS | MMC_MODE_8BIT;
|
||||
cfg->voltages = MMC_VDD_165_195 | MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
cfg->f_min = 1000000;
|
||||
cfg->f_max = 52000000;
|
||||
cfg->b_max = U32_MAX;
|
||||
|
||||
ret = mmc_bind(dev, &plat->mmc, cfg);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sandbox_mmc_unbind(struct udevice *dev)
|
||||
{
|
||||
mmc_unbind(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id sandbox_mmc_ids[] = {
|
||||
{ .compatible = "sandbox,mmc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(mmc_sandbox) = {
|
||||
.name = "mmc_sandbox",
|
||||
.id = UCLASS_MMC,
|
||||
.of_match = sandbox_mmc_ids,
|
||||
.bind = sandbox_mmc_bind,
|
||||
.unbind = sandbox_mmc_unbind,
|
||||
.probe = sandbox_mmc_probe,
|
||||
.platdata_auto_alloc_size = sizeof(struct sandbox_mmc_plat),
|
||||
};
|
||||
559
u-boot/drivers/mmc/sdhci.c
Normal file
559
u-boot/drivers/mmc/sdhci.c
Normal file
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
* Copyright 2011, Marvell Semiconductor Inc.
|
||||
* Lei Wen <leiwen@marvell.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*
|
||||
* Back ported to the 8xx platform (from the 8260 platform) by
|
||||
* Murray.Jensen@cmst.csiro.au, 27-Jan-01.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <mmc.h>
|
||||
#include <sdhci.h>
|
||||
|
||||
#if defined(CONFIG_FIXED_SDHCI_ALIGNED_BUFFER)
|
||||
void *aligned_buffer = (void *)CONFIG_FIXED_SDHCI_ALIGNED_BUFFER;
|
||||
#else
|
||||
void *aligned_buffer;
|
||||
#endif
|
||||
|
||||
static void sdhci_reset(struct sdhci_host *host, u8 mask)
|
||||
{
|
||||
unsigned long timeout;
|
||||
|
||||
/* Wait max 100 ms */
|
||||
timeout = 100;
|
||||
sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET);
|
||||
while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) {
|
||||
if (timeout == 0) {
|
||||
printf("%s: Reset 0x%x never completed.\n",
|
||||
__func__, (int)mask);
|
||||
return;
|
||||
}
|
||||
timeout--;
|
||||
udelay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdhci_cmd_done(struct sdhci_host *host, struct mmc_cmd *cmd)
|
||||
{
|
||||
int i;
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
/* CRC is stripped so we need to do some shifting. */
|
||||
for (i = 0; i < 4; i++) {
|
||||
cmd->response[i] = sdhci_readl(host,
|
||||
SDHCI_RESPONSE + (3-i)*4) << 8;
|
||||
if (i != 3)
|
||||
cmd->response[i] |= sdhci_readb(host,
|
||||
SDHCI_RESPONSE + (3-i)*4-1);
|
||||
}
|
||||
} else {
|
||||
cmd->response[0] = sdhci_readl(host, SDHCI_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdhci_transfer_pio(struct sdhci_host *host, struct mmc_data *data)
|
||||
{
|
||||
int i;
|
||||
char *offs;
|
||||
for (i = 0; i < data->blocksize; i += 4) {
|
||||
offs = data->dest + i;
|
||||
if (data->flags == MMC_DATA_READ)
|
||||
*(u32 *)offs = sdhci_readl(host, SDHCI_BUFFER);
|
||||
else
|
||||
sdhci_writel(host, *(u32 *)offs, SDHCI_BUFFER);
|
||||
}
|
||||
}
|
||||
|
||||
static int sdhci_transfer_data(struct sdhci_host *host, struct mmc_data *data,
|
||||
unsigned int start_addr)
|
||||
{
|
||||
unsigned int stat, rdy, mask, timeout, block = 0;
|
||||
#ifdef CONFIG_MMC_SDMA
|
||||
unsigned char ctrl;
|
||||
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
|
||||
ctrl &= ~SDHCI_CTRL_DMA_MASK;
|
||||
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
|
||||
#endif
|
||||
|
||||
timeout = 1000000;
|
||||
rdy = SDHCI_INT_SPACE_AVAIL | SDHCI_INT_DATA_AVAIL;
|
||||
mask = SDHCI_DATA_AVAILABLE | SDHCI_SPACE_AVAILABLE;
|
||||
do {
|
||||
stat = sdhci_readl(host, SDHCI_INT_STATUS);
|
||||
if (stat & SDHCI_INT_ERROR) {
|
||||
printf("%s: Error detected in status(0x%X)!\n",
|
||||
__func__, stat);
|
||||
return -1;
|
||||
}
|
||||
if (stat & rdy) {
|
||||
if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & mask))
|
||||
continue;
|
||||
sdhci_writel(host, rdy, SDHCI_INT_STATUS);
|
||||
sdhci_transfer_pio(host, data);
|
||||
data->dest += data->blocksize;
|
||||
if (++block >= data->blocks)
|
||||
break;
|
||||
}
|
||||
#ifdef CONFIG_MMC_SDMA
|
||||
if (stat & SDHCI_INT_DMA_END) {
|
||||
sdhci_writel(host, SDHCI_INT_DMA_END, SDHCI_INT_STATUS);
|
||||
start_addr &= ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1);
|
||||
start_addr += SDHCI_DEFAULT_BOUNDARY_SIZE;
|
||||
sdhci_writel(host, start_addr, SDHCI_DMA_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
if (timeout-- > 0)
|
||||
udelay(10);
|
||||
else {
|
||||
printf("%s: Transfer data timeout\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
} while (!(stat & SDHCI_INT_DATA_END));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* No command will be sent by driver if card is busy, so driver must wait
|
||||
* for card ready state.
|
||||
* Every time when card is busy after timeout then (last) timeout value will be
|
||||
* increased twice but only if it doesn't exceed global defined maximum.
|
||||
* Each function call will use last timeout value. Max timeout can be redefined
|
||||
* in board config file.
|
||||
*/
|
||||
#ifndef CONFIG_SDHCI_CMD_MAX_TIMEOUT
|
||||
#define CONFIG_SDHCI_CMD_MAX_TIMEOUT 3200
|
||||
#endif
|
||||
#define CONFIG_SDHCI_CMD_DEFAULT_TIMEOUT 100
|
||||
#define SDHCI_READ_STATUS_TIMEOUT 1000
|
||||
|
||||
static int sdhci_send_command(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct sdhci_host *host = mmc->priv;
|
||||
unsigned int stat = 0;
|
||||
int ret = 0;
|
||||
int trans_bytes = 0, is_aligned = 1;
|
||||
u32 mask, flags, mode;
|
||||
unsigned int time = 0, start_addr = 0;
|
||||
int mmc_dev = mmc_get_blk_desc(mmc)->devnum;
|
||||
unsigned start = get_timer(0);
|
||||
|
||||
/* Timeout unit - ms */
|
||||
static unsigned int cmd_timeout = CONFIG_SDHCI_CMD_DEFAULT_TIMEOUT;
|
||||
|
||||
sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS);
|
||||
mask = SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT;
|
||||
|
||||
/* We shouldn't wait for data inihibit for stop commands, even
|
||||
though they might use busy signaling */
|
||||
if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION)
|
||||
mask &= ~SDHCI_DATA_INHIBIT;
|
||||
|
||||
while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) {
|
||||
if (time >= cmd_timeout) {
|
||||
printf("%s: MMC: %d busy ", __func__, mmc_dev);
|
||||
if (2 * cmd_timeout <= CONFIG_SDHCI_CMD_MAX_TIMEOUT) {
|
||||
cmd_timeout += cmd_timeout;
|
||||
printf("timeout increasing to: %u ms.\n",
|
||||
cmd_timeout);
|
||||
} else {
|
||||
puts("timeout.\n");
|
||||
return COMM_ERR;
|
||||
}
|
||||
}
|
||||
time++;
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
mask = SDHCI_INT_RESPONSE;
|
||||
if (!(cmd->resp_type & MMC_RSP_PRESENT))
|
||||
flags = SDHCI_CMD_RESP_NONE;
|
||||
else if (cmd->resp_type & MMC_RSP_136)
|
||||
flags = SDHCI_CMD_RESP_LONG;
|
||||
else if (cmd->resp_type & MMC_RSP_BUSY) {
|
||||
flags = SDHCI_CMD_RESP_SHORT_BUSY;
|
||||
mask |= SDHCI_INT_DATA_END;
|
||||
} else
|
||||
flags = SDHCI_CMD_RESP_SHORT;
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_CRC)
|
||||
flags |= SDHCI_CMD_CRC;
|
||||
if (cmd->resp_type & MMC_RSP_OPCODE)
|
||||
flags |= SDHCI_CMD_INDEX;
|
||||
if (data)
|
||||
flags |= SDHCI_CMD_DATA;
|
||||
|
||||
/* Set Transfer mode regarding to data flag */
|
||||
if (data != 0) {
|
||||
sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL);
|
||||
mode = SDHCI_TRNS_BLK_CNT_EN;
|
||||
trans_bytes = data->blocks * data->blocksize;
|
||||
if (data->blocks > 1)
|
||||
mode |= SDHCI_TRNS_MULTI;
|
||||
|
||||
if (data->flags == MMC_DATA_READ)
|
||||
mode |= SDHCI_TRNS_READ;
|
||||
|
||||
#ifdef CONFIG_MMC_SDMA
|
||||
if (data->flags == MMC_DATA_READ)
|
||||
start_addr = (unsigned long)data->dest;
|
||||
else
|
||||
start_addr = (unsigned long)data->src;
|
||||
if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) &&
|
||||
(start_addr & 0x7) != 0x0) {
|
||||
is_aligned = 0;
|
||||
start_addr = (unsigned long)aligned_buffer;
|
||||
if (data->flags != MMC_DATA_READ)
|
||||
memcpy(aligned_buffer, data->src, trans_bytes);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_FIXED_SDHCI_ALIGNED_BUFFER)
|
||||
/*
|
||||
* Always use this bounce-buffer when
|
||||
* CONFIG_FIXED_SDHCI_ALIGNED_BUFFER is defined
|
||||
*/
|
||||
is_aligned = 0;
|
||||
start_addr = (unsigned long)aligned_buffer;
|
||||
if (data->flags != MMC_DATA_READ)
|
||||
memcpy(aligned_buffer, data->src, trans_bytes);
|
||||
#endif
|
||||
|
||||
sdhci_writel(host, start_addr, SDHCI_DMA_ADDRESS);
|
||||
mode |= SDHCI_TRNS_DMA;
|
||||
#endif
|
||||
sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG,
|
||||
data->blocksize),
|
||||
SDHCI_BLOCK_SIZE);
|
||||
sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
|
||||
sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
|
||||
} else if (cmd->resp_type & MMC_RSP_BUSY) {
|
||||
sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL);
|
||||
}
|
||||
|
||||
sdhci_writel(host, cmd->cmdarg, SDHCI_ARGUMENT);
|
||||
#ifdef CONFIG_MMC_SDMA
|
||||
flush_cache(start_addr, trans_bytes);
|
||||
#endif
|
||||
sdhci_writew(host, SDHCI_MAKE_CMD(cmd->cmdidx, flags), SDHCI_COMMAND);
|
||||
start = get_timer(0);
|
||||
do {
|
||||
stat = sdhci_readl(host, SDHCI_INT_STATUS);
|
||||
if (stat & SDHCI_INT_ERROR)
|
||||
break;
|
||||
} while (((stat & mask) != mask) &&
|
||||
(get_timer(start) < SDHCI_READ_STATUS_TIMEOUT));
|
||||
|
||||
if (get_timer(start) >= SDHCI_READ_STATUS_TIMEOUT) {
|
||||
if (host->quirks & SDHCI_QUIRK_BROKEN_R1B)
|
||||
return 0;
|
||||
else {
|
||||
printf("%s: Timeout for status update!\n", __func__);
|
||||
return TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
if ((stat & (SDHCI_INT_ERROR | mask)) == mask) {
|
||||
sdhci_cmd_done(host, cmd);
|
||||
sdhci_writel(host, mask, SDHCI_INT_STATUS);
|
||||
} else
|
||||
ret = -1;
|
||||
|
||||
if (!ret && data)
|
||||
ret = sdhci_transfer_data(host, data, start_addr);
|
||||
|
||||
if (host->quirks & SDHCI_QUIRK_WAIT_SEND_CMD)
|
||||
udelay(1000);
|
||||
|
||||
stat = sdhci_readl(host, SDHCI_INT_STATUS);
|
||||
sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS);
|
||||
if (!ret) {
|
||||
if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) &&
|
||||
!is_aligned && (data->flags == MMC_DATA_READ))
|
||||
memcpy(data->dest, aligned_buffer, trans_bytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sdhci_reset(host, SDHCI_RESET_CMD);
|
||||
sdhci_reset(host, SDHCI_RESET_DATA);
|
||||
if (stat & SDHCI_INT_TIMEOUT)
|
||||
return TIMEOUT;
|
||||
else
|
||||
return COMM_ERR;
|
||||
}
|
||||
|
||||
static int sdhci_set_clock(struct mmc *mmc, unsigned int clock)
|
||||
{
|
||||
struct sdhci_host *host = mmc->priv;
|
||||
unsigned int div, clk, timeout, reg;
|
||||
|
||||
/* Wait max 20 ms */
|
||||
timeout = 200;
|
||||
while (sdhci_readl(host, SDHCI_PRESENT_STATE) &
|
||||
(SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT)) {
|
||||
if (timeout == 0) {
|
||||
printf("%s: Timeout to wait cmd & data inhibit\n",
|
||||
__func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
timeout--;
|
||||
udelay(100);
|
||||
}
|
||||
|
||||
reg = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
|
||||
reg &= ~SDHCI_CLOCK_CARD_EN;
|
||||
sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
|
||||
|
||||
if (clock == 0)
|
||||
return 0;
|
||||
|
||||
if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) {
|
||||
/* Version 3.00 divisors must be a multiple of 2. */
|
||||
if (mmc->cfg->f_max <= clock)
|
||||
div = 1;
|
||||
else {
|
||||
for (div = 2; div < SDHCI_MAX_DIV_SPEC_300; div += 2) {
|
||||
if ((mmc->cfg->f_max / div) <= clock)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Version 2.00 divisors must be a power of 2. */
|
||||
for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *= 2) {
|
||||
if ((mmc->cfg->f_max / div) <= clock)
|
||||
break;
|
||||
}
|
||||
}
|
||||
div >>= 1;
|
||||
|
||||
if (host->set_clock)
|
||||
host->set_clock(host->index, div);
|
||||
|
||||
clk = (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT;
|
||||
clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN)
|
||||
<< SDHCI_DIVIDER_HI_SHIFT;
|
||||
clk |= SDHCI_CLOCK_INT_EN;
|
||||
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
||||
|
||||
/* Wait max 20 ms */
|
||||
timeout = 20;
|
||||
while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
|
||||
& SDHCI_CLOCK_INT_STABLE)) {
|
||||
if (timeout == 0) {
|
||||
printf("%s: Internal clock never stabilised.\n",
|
||||
__func__);
|
||||
return -1;
|
||||
}
|
||||
timeout--;
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
clk |= SDHCI_CLOCK_CARD_EN;
|
||||
sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sdhci_set_power(struct sdhci_host *host, unsigned short power)
|
||||
{
|
||||
u8 pwr = 0;
|
||||
|
||||
if (power != (unsigned short)-1) {
|
||||
switch (1 << power) {
|
||||
case MMC_VDD_165_195:
|
||||
pwr = SDHCI_POWER_180;
|
||||
break;
|
||||
case MMC_VDD_29_30:
|
||||
case MMC_VDD_30_31:
|
||||
pwr = SDHCI_POWER_300;
|
||||
break;
|
||||
case MMC_VDD_32_33:
|
||||
case MMC_VDD_33_34:
|
||||
pwr = SDHCI_POWER_330;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pwr == 0) {
|
||||
sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (host->quirks & SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER)
|
||||
sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
|
||||
|
||||
pwr |= SDHCI_POWER_ON;
|
||||
|
||||
sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
|
||||
}
|
||||
|
||||
static void sdhci_set_ios(struct mmc *mmc)
|
||||
{
|
||||
u32 ctrl;
|
||||
struct sdhci_host *host = mmc->priv;
|
||||
|
||||
if (host->set_control_reg)
|
||||
host->set_control_reg(host);
|
||||
|
||||
if (mmc->clock != host->clock)
|
||||
sdhci_set_clock(mmc, mmc->clock);
|
||||
|
||||
/* Set bus width */
|
||||
ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
|
||||
if (mmc->bus_width == 8) {
|
||||
ctrl &= ~SDHCI_CTRL_4BITBUS;
|
||||
if ((SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) ||
|
||||
(host->quirks & SDHCI_QUIRK_USE_WIDE8))
|
||||
ctrl |= SDHCI_CTRL_8BITBUS;
|
||||
} else {
|
||||
if ((SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) ||
|
||||
(host->quirks & SDHCI_QUIRK_USE_WIDE8))
|
||||
ctrl &= ~SDHCI_CTRL_8BITBUS;
|
||||
if (mmc->bus_width == 4)
|
||||
ctrl |= SDHCI_CTRL_4BITBUS;
|
||||
else
|
||||
ctrl &= ~SDHCI_CTRL_4BITBUS;
|
||||
}
|
||||
|
||||
if (mmc->clock > 26000000)
|
||||
ctrl |= SDHCI_CTRL_HISPD;
|
||||
else
|
||||
ctrl &= ~SDHCI_CTRL_HISPD;
|
||||
|
||||
if (host->quirks & SDHCI_QUIRK_NO_HISPD_BIT)
|
||||
ctrl &= ~SDHCI_CTRL_HISPD;
|
||||
|
||||
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
|
||||
}
|
||||
|
||||
static int sdhci_init(struct mmc *mmc)
|
||||
{
|
||||
struct sdhci_host *host = mmc->priv;
|
||||
|
||||
if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && !aligned_buffer) {
|
||||
aligned_buffer = memalign(8, 512*1024);
|
||||
if (!aligned_buffer) {
|
||||
printf("%s: Aligned buffer alloc failed!!!\n",
|
||||
__func__);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
sdhci_set_power(host, fls(mmc->cfg->voltages) - 1);
|
||||
|
||||
if (host->quirks & SDHCI_QUIRK_NO_CD) {
|
||||
#if defined(CONFIG_PIC32_SDHCI)
|
||||
/* PIC32 SDHCI CD errata:
|
||||
* - set CD_TEST and clear CD_TEST_INS bit
|
||||
*/
|
||||
sdhci_writeb(host, SDHCI_CTRL_CD_TEST, SDHCI_HOST_CONTROL);
|
||||
#else
|
||||
unsigned int status;
|
||||
|
||||
sdhci_writeb(host, SDHCI_CTRL_CD_TEST_INS | SDHCI_CTRL_CD_TEST,
|
||||
SDHCI_HOST_CONTROL);
|
||||
|
||||
status = sdhci_readl(host, SDHCI_PRESENT_STATE);
|
||||
while ((!(status & SDHCI_CARD_PRESENT)) ||
|
||||
(!(status & SDHCI_CARD_STATE_STABLE)) ||
|
||||
(!(status & SDHCI_CARD_DETECT_PIN_LEVEL)))
|
||||
status = sdhci_readl(host, SDHCI_PRESENT_STATE);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Enable only interrupts served by the SD controller */
|
||||
sdhci_writel(host, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK,
|
||||
SDHCI_INT_ENABLE);
|
||||
/* Mask all sdhci interrupt sources */
|
||||
sdhci_writel(host, 0x0, SDHCI_SIGNAL_ENABLE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct mmc_ops sdhci_ops = {
|
||||
.send_cmd = sdhci_send_command,
|
||||
.set_ios = sdhci_set_ios,
|
||||
.init = sdhci_init,
|
||||
};
|
||||
|
||||
int add_sdhci(struct sdhci_host *host, u32 max_clk, u32 min_clk)
|
||||
{
|
||||
unsigned int caps;
|
||||
|
||||
host->cfg.name = host->name;
|
||||
host->cfg.ops = &sdhci_ops;
|
||||
|
||||
caps = sdhci_readl(host, SDHCI_CAPABILITIES);
|
||||
#ifdef CONFIG_MMC_SDMA
|
||||
if (!(caps & SDHCI_CAN_DO_SDMA)) {
|
||||
printf("%s: Your controller doesn't support SDMA!!\n",
|
||||
__func__);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (max_clk)
|
||||
host->cfg.f_max = max_clk;
|
||||
else {
|
||||
if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300)
|
||||
host->cfg.f_max = (caps & SDHCI_CLOCK_V3_BASE_MASK)
|
||||
>> SDHCI_CLOCK_BASE_SHIFT;
|
||||
else
|
||||
host->cfg.f_max = (caps & SDHCI_CLOCK_BASE_MASK)
|
||||
>> SDHCI_CLOCK_BASE_SHIFT;
|
||||
host->cfg.f_max *= 1000000;
|
||||
}
|
||||
if (host->cfg.f_max == 0) {
|
||||
printf("%s: Hardware doesn't specify base clock frequency\n",
|
||||
__func__);
|
||||
return -1;
|
||||
}
|
||||
if (min_clk)
|
||||
host->cfg.f_min = min_clk;
|
||||
else {
|
||||
if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300)
|
||||
host->cfg.f_min = host->cfg.f_max /
|
||||
SDHCI_MAX_DIV_SPEC_300;
|
||||
else
|
||||
host->cfg.f_min = host->cfg.f_max /
|
||||
SDHCI_MAX_DIV_SPEC_200;
|
||||
}
|
||||
|
||||
host->cfg.voltages = 0;
|
||||
if (caps & SDHCI_CAN_VDD_330)
|
||||
host->cfg.voltages |= MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
if (caps & SDHCI_CAN_VDD_300)
|
||||
host->cfg.voltages |= MMC_VDD_29_30 | MMC_VDD_30_31;
|
||||
if (caps & SDHCI_CAN_VDD_180)
|
||||
host->cfg.voltages |= MMC_VDD_165_195;
|
||||
|
||||
if (host->quirks & SDHCI_QUIRK_BROKEN_VOLTAGE)
|
||||
host->cfg.voltages |= host->voltages;
|
||||
|
||||
host->cfg.host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT;
|
||||
if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) {
|
||||
if (caps & SDHCI_CAN_DO_8BIT)
|
||||
host->cfg.host_caps |= MMC_MODE_8BIT;
|
||||
}
|
||||
|
||||
if (host->quirks & SDHCI_QUIRK_NO_HISPD_BIT)
|
||||
host->cfg.host_caps &= ~(MMC_MODE_HS | MMC_MODE_HS_52MHz);
|
||||
|
||||
if (host->host_caps)
|
||||
host->cfg.host_caps |= host->host_caps;
|
||||
|
||||
host->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
|
||||
sdhci_reset(host, SDHCI_RESET_ALL);
|
||||
|
||||
host->mmc = mmc_create(&host->cfg, host);
|
||||
if (host->mmc == NULL) {
|
||||
printf("%s: mmc create fail!\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
606
u-boot/drivers/mmc/sh_mmcif.c
Normal file
606
u-boot/drivers/mmc/sh_mmcif.c
Normal file
@@ -0,0 +1,606 @@
|
||||
/*
|
||||
* MMCIF driver.
|
||||
*
|
||||
* Copyright (C) 2011 Renesas Solutions Corp.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include <common.h>
|
||||
#include <watchdog.h>
|
||||
#include <command.h>
|
||||
#include <mmc.h>
|
||||
#include <malloc.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/io.h>
|
||||
#include "sh_mmcif.h"
|
||||
|
||||
#define DRIVER_NAME "sh_mmcif"
|
||||
|
||||
static int sh_mmcif_intr(void *dev_id)
|
||||
{
|
||||
struct sh_mmcif_host *host = dev_id;
|
||||
u32 state = 0;
|
||||
|
||||
state = sh_mmcif_read(&host->regs->ce_int);
|
||||
state &= sh_mmcif_read(&host->regs->ce_int_mask);
|
||||
|
||||
if (state & INT_RBSYE) {
|
||||
sh_mmcif_write(~(INT_RBSYE | INT_CRSPE), &host->regs->ce_int);
|
||||
sh_mmcif_bitclr(MASK_MRBSYE, &host->regs->ce_int_mask);
|
||||
goto end;
|
||||
} else if (state & INT_CRSPE) {
|
||||
sh_mmcif_write(~INT_CRSPE, &host->regs->ce_int);
|
||||
sh_mmcif_bitclr(MASK_MCRSPE, &host->regs->ce_int_mask);
|
||||
/* one more interrupt (INT_RBSYE) */
|
||||
if (sh_mmcif_read(&host->regs->ce_cmd_set) & CMD_SET_RBSY)
|
||||
return -EAGAIN;
|
||||
goto end;
|
||||
} else if (state & INT_BUFREN) {
|
||||
sh_mmcif_write(~INT_BUFREN, &host->regs->ce_int);
|
||||
sh_mmcif_bitclr(MASK_MBUFREN, &host->regs->ce_int_mask);
|
||||
goto end;
|
||||
} else if (state & INT_BUFWEN) {
|
||||
sh_mmcif_write(~INT_BUFWEN, &host->regs->ce_int);
|
||||
sh_mmcif_bitclr(MASK_MBUFWEN, &host->regs->ce_int_mask);
|
||||
goto end;
|
||||
} else if (state & INT_CMD12DRE) {
|
||||
sh_mmcif_write(~(INT_CMD12DRE | INT_CMD12RBE | INT_CMD12CRE |
|
||||
INT_BUFRE), &host->regs->ce_int);
|
||||
sh_mmcif_bitclr(MASK_MCMD12DRE, &host->regs->ce_int_mask);
|
||||
goto end;
|
||||
} else if (state & INT_BUFRE) {
|
||||
sh_mmcif_write(~INT_BUFRE, &host->regs->ce_int);
|
||||
sh_mmcif_bitclr(MASK_MBUFRE, &host->regs->ce_int_mask);
|
||||
goto end;
|
||||
} else if (state & INT_DTRANE) {
|
||||
sh_mmcif_write(~INT_DTRANE, &host->regs->ce_int);
|
||||
sh_mmcif_bitclr(MASK_MDTRANE, &host->regs->ce_int_mask);
|
||||
goto end;
|
||||
} else if (state & INT_CMD12RBE) {
|
||||
sh_mmcif_write(~(INT_CMD12RBE | INT_CMD12CRE),
|
||||
&host->regs->ce_int);
|
||||
sh_mmcif_bitclr(MASK_MCMD12RBE, &host->regs->ce_int_mask);
|
||||
goto end;
|
||||
} else if (state & INT_ERR_STS) {
|
||||
/* err interrupts */
|
||||
sh_mmcif_write(~state, &host->regs->ce_int);
|
||||
sh_mmcif_bitclr(state, &host->regs->ce_int_mask);
|
||||
goto err;
|
||||
} else
|
||||
return -EAGAIN;
|
||||
|
||||
err:
|
||||
host->sd_error = 1;
|
||||
debug("%s: int err state = %08x\n", DRIVER_NAME, state);
|
||||
end:
|
||||
host->wait_int = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmcif_wait_interrupt_flag(struct sh_mmcif_host *host)
|
||||
{
|
||||
int timeout = 10000000;
|
||||
|
||||
while (1) {
|
||||
timeout--;
|
||||
if (timeout < 0) {
|
||||
printf("timeout\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!sh_mmcif_intr(host))
|
||||
break;
|
||||
|
||||
udelay(1); /* 1 usec */
|
||||
}
|
||||
|
||||
return 1; /* Return value: NOT 0 = complete waiting */
|
||||
}
|
||||
|
||||
static void sh_mmcif_clock_control(struct sh_mmcif_host *host, unsigned int clk)
|
||||
{
|
||||
sh_mmcif_bitclr(CLK_ENABLE, &host->regs->ce_clk_ctrl);
|
||||
sh_mmcif_bitclr(CLK_CLEAR, &host->regs->ce_clk_ctrl);
|
||||
|
||||
if (!clk)
|
||||
return;
|
||||
|
||||
if (clk == CLKDEV_EMMC_DATA)
|
||||
sh_mmcif_bitset(CLK_PCLK, &host->regs->ce_clk_ctrl);
|
||||
else
|
||||
sh_mmcif_bitset((fls(DIV_ROUND_UP(host->clk,
|
||||
clk) - 1) - 1) << 16,
|
||||
&host->regs->ce_clk_ctrl);
|
||||
sh_mmcif_bitset(CLK_ENABLE, &host->regs->ce_clk_ctrl);
|
||||
}
|
||||
|
||||
static void sh_mmcif_sync_reset(struct sh_mmcif_host *host)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
tmp = sh_mmcif_read(&host->regs->ce_clk_ctrl) & (CLK_ENABLE |
|
||||
CLK_CLEAR);
|
||||
|
||||
sh_mmcif_write(SOFT_RST_ON, &host->regs->ce_version);
|
||||
sh_mmcif_write(SOFT_RST_OFF, &host->regs->ce_version);
|
||||
sh_mmcif_bitset(tmp | SRSPTO_256 | SRBSYTO_29 | SRWDTO_29 | SCCSTO_29,
|
||||
&host->regs->ce_clk_ctrl);
|
||||
/* byte swap on */
|
||||
sh_mmcif_bitset(BUF_ACC_ATYP, &host->regs->ce_buf_acc);
|
||||
}
|
||||
|
||||
static int sh_mmcif_error_manage(struct sh_mmcif_host *host)
|
||||
{
|
||||
u32 state1, state2;
|
||||
int ret, timeout = 10000000;
|
||||
|
||||
host->sd_error = 0;
|
||||
host->wait_int = 0;
|
||||
|
||||
state1 = sh_mmcif_read(&host->regs->ce_host_sts1);
|
||||
state2 = sh_mmcif_read(&host->regs->ce_host_sts2);
|
||||
debug("%s: ERR HOST_STS1 = %08x\n", \
|
||||
DRIVER_NAME, sh_mmcif_read(&host->regs->ce_host_sts1));
|
||||
debug("%s: ERR HOST_STS2 = %08x\n", \
|
||||
DRIVER_NAME, sh_mmcif_read(&host->regs->ce_host_sts2));
|
||||
|
||||
if (state1 & STS1_CMDSEQ) {
|
||||
debug("%s: Forced end of command sequence\n", DRIVER_NAME);
|
||||
sh_mmcif_bitset(CMD_CTRL_BREAK, &host->regs->ce_cmd_ctrl);
|
||||
sh_mmcif_bitset(~CMD_CTRL_BREAK, &host->regs->ce_cmd_ctrl);
|
||||
while (1) {
|
||||
timeout--;
|
||||
if (timeout < 0) {
|
||||
printf(DRIVER_NAME": Forceed end of " \
|
||||
"command sequence timeout err\n");
|
||||
return -EILSEQ;
|
||||
}
|
||||
if (!(sh_mmcif_read(&host->regs->ce_host_sts1)
|
||||
& STS1_CMDSEQ))
|
||||
break;
|
||||
}
|
||||
sh_mmcif_sync_reset(host);
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
if (state2 & STS2_CRC_ERR)
|
||||
ret = -EILSEQ;
|
||||
else if (state2 & STS2_TIMEOUT_ERR)
|
||||
ret = TIMEOUT;
|
||||
else
|
||||
ret = -EILSEQ;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_mmcif_single_read(struct sh_mmcif_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
long time;
|
||||
u32 blocksize, i;
|
||||
unsigned long *p = (unsigned long *)data->dest;
|
||||
|
||||
if ((unsigned long)p & 0x00000001) {
|
||||
printf("%s: The data pointer is unaligned.", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
host->wait_int = 0;
|
||||
|
||||
/* buf read enable */
|
||||
sh_mmcif_bitset(MASK_MBUFREN, &host->regs->ce_int_mask);
|
||||
time = mmcif_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_mmcif_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
blocksize = (BLOCK_SIZE_MASK &
|
||||
sh_mmcif_read(&host->regs->ce_block_set)) + 3;
|
||||
for (i = 0; i < blocksize / 4; i++)
|
||||
*p++ = sh_mmcif_read(&host->regs->ce_data);
|
||||
|
||||
/* buffer read end */
|
||||
sh_mmcif_bitset(MASK_MBUFRE, &host->regs->ce_int_mask);
|
||||
time = mmcif_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_mmcif_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_mmcif_multi_read(struct sh_mmcif_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
long time;
|
||||
u32 blocksize, i, j;
|
||||
unsigned long *p = (unsigned long *)data->dest;
|
||||
|
||||
if ((unsigned long)p & 0x00000001) {
|
||||
printf("%s: The data pointer is unaligned.", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
host->wait_int = 0;
|
||||
blocksize = BLOCK_SIZE_MASK & sh_mmcif_read(&host->regs->ce_block_set);
|
||||
for (j = 0; j < data->blocks; j++) {
|
||||
sh_mmcif_bitset(MASK_MBUFREN, &host->regs->ce_int_mask);
|
||||
time = mmcif_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_mmcif_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
for (i = 0; i < blocksize / 4; i++)
|
||||
*p++ = sh_mmcif_read(&host->regs->ce_data);
|
||||
|
||||
WATCHDOG_RESET();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_mmcif_single_write(struct sh_mmcif_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
long time;
|
||||
u32 blocksize, i;
|
||||
const unsigned long *p = (unsigned long *)data->dest;
|
||||
|
||||
if ((unsigned long)p & 0x00000001) {
|
||||
printf("%s: The data pointer is unaligned.", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
host->wait_int = 0;
|
||||
sh_mmcif_bitset(MASK_MBUFWEN, &host->regs->ce_int_mask);
|
||||
|
||||
time = mmcif_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_mmcif_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
blocksize = (BLOCK_SIZE_MASK &
|
||||
sh_mmcif_read(&host->regs->ce_block_set)) + 3;
|
||||
for (i = 0; i < blocksize / 4; i++)
|
||||
sh_mmcif_write(*p++, &host->regs->ce_data);
|
||||
|
||||
/* buffer write end */
|
||||
sh_mmcif_bitset(MASK_MDTRANE, &host->regs->ce_int_mask);
|
||||
|
||||
time = mmcif_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_mmcif_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_mmcif_multi_write(struct sh_mmcif_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
long time;
|
||||
u32 i, j, blocksize;
|
||||
const unsigned long *p = (unsigned long *)data->dest;
|
||||
|
||||
if ((unsigned long)p & 0x00000001) {
|
||||
printf("%s: The data pointer is unaligned.", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
host->wait_int = 0;
|
||||
blocksize = BLOCK_SIZE_MASK & sh_mmcif_read(&host->regs->ce_block_set);
|
||||
for (j = 0; j < data->blocks; j++) {
|
||||
sh_mmcif_bitset(MASK_MBUFWEN, &host->regs->ce_int_mask);
|
||||
|
||||
time = mmcif_wait_interrupt_flag(host);
|
||||
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_mmcif_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
for (i = 0; i < blocksize / 4; i++)
|
||||
sh_mmcif_write(*p++, &host->regs->ce_data);
|
||||
|
||||
WATCHDOG_RESET();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sh_mmcif_get_response(struct sh_mmcif_host *host,
|
||||
struct mmc_cmd *cmd)
|
||||
{
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp3);
|
||||
cmd->response[1] = sh_mmcif_read(&host->regs->ce_resp2);
|
||||
cmd->response[2] = sh_mmcif_read(&host->regs->ce_resp1);
|
||||
cmd->response[3] = sh_mmcif_read(&host->regs->ce_resp0);
|
||||
debug(" RESP %08x, %08x, %08x, %08x\n", cmd->response[0],
|
||||
cmd->response[1], cmd->response[2], cmd->response[3]);
|
||||
} else {
|
||||
cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp0);
|
||||
}
|
||||
}
|
||||
|
||||
static void sh_mmcif_get_cmd12response(struct sh_mmcif_host *host,
|
||||
struct mmc_cmd *cmd)
|
||||
{
|
||||
cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp_cmd12);
|
||||
}
|
||||
|
||||
static u32 sh_mmcif_set_cmd(struct sh_mmcif_host *host,
|
||||
struct mmc_data *data, struct mmc_cmd *cmd)
|
||||
{
|
||||
u32 tmp = 0;
|
||||
u32 opc = cmd->cmdidx;
|
||||
|
||||
/* Response Type check */
|
||||
switch (cmd->resp_type) {
|
||||
case MMC_RSP_NONE:
|
||||
tmp |= CMD_SET_RTYP_NO;
|
||||
break;
|
||||
case MMC_RSP_R1:
|
||||
case MMC_RSP_R1b:
|
||||
case MMC_RSP_R3:
|
||||
tmp |= CMD_SET_RTYP_6B;
|
||||
break;
|
||||
case MMC_RSP_R2:
|
||||
tmp |= CMD_SET_RTYP_17B;
|
||||
break;
|
||||
default:
|
||||
printf(DRIVER_NAME": Not support type response.\n");
|
||||
break;
|
||||
}
|
||||
|
||||
/* RBSY */
|
||||
if (opc == MMC_CMD_SWITCH)
|
||||
tmp |= CMD_SET_RBSY;
|
||||
|
||||
/* WDAT / DATW */
|
||||
if (host->data) {
|
||||
tmp |= CMD_SET_WDAT;
|
||||
switch (host->bus_width) {
|
||||
case MMC_BUS_WIDTH_1:
|
||||
tmp |= CMD_SET_DATW_1;
|
||||
break;
|
||||
case MMC_BUS_WIDTH_4:
|
||||
tmp |= CMD_SET_DATW_4;
|
||||
break;
|
||||
case MMC_BUS_WIDTH_8:
|
||||
tmp |= CMD_SET_DATW_8;
|
||||
break;
|
||||
default:
|
||||
printf(DRIVER_NAME": Not support bus width.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* DWEN */
|
||||
if (opc == MMC_CMD_WRITE_SINGLE_BLOCK ||
|
||||
opc == MMC_CMD_WRITE_MULTIPLE_BLOCK)
|
||||
tmp |= CMD_SET_DWEN;
|
||||
/* CMLTE/CMD12EN */
|
||||
if (opc == MMC_CMD_READ_MULTIPLE_BLOCK ||
|
||||
opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) {
|
||||
tmp |= CMD_SET_CMLTE | CMD_SET_CMD12EN;
|
||||
sh_mmcif_bitset(data->blocks << 16, &host->regs->ce_block_set);
|
||||
}
|
||||
/* RIDXC[1:0] check bits */
|
||||
if (opc == MMC_CMD_SEND_OP_COND || opc == MMC_CMD_ALL_SEND_CID ||
|
||||
opc == MMC_CMD_SEND_CSD || opc == MMC_CMD_SEND_CID)
|
||||
tmp |= CMD_SET_RIDXC_BITS;
|
||||
/* RCRC7C[1:0] check bits */
|
||||
if (opc == MMC_CMD_SEND_OP_COND)
|
||||
tmp |= CMD_SET_CRC7C_BITS;
|
||||
/* RCRC7C[1:0] internal CRC7 */
|
||||
if (opc == MMC_CMD_ALL_SEND_CID ||
|
||||
opc == MMC_CMD_SEND_CSD || opc == MMC_CMD_SEND_CID)
|
||||
tmp |= CMD_SET_CRC7C_INTERNAL;
|
||||
|
||||
return opc = ((opc << 24) | tmp);
|
||||
}
|
||||
|
||||
static u32 sh_mmcif_data_trans(struct sh_mmcif_host *host,
|
||||
struct mmc_data *data, u16 opc)
|
||||
{
|
||||
u32 ret;
|
||||
|
||||
switch (opc) {
|
||||
case MMC_CMD_READ_MULTIPLE_BLOCK:
|
||||
ret = sh_mmcif_multi_read(host, data);
|
||||
break;
|
||||
case MMC_CMD_WRITE_MULTIPLE_BLOCK:
|
||||
ret = sh_mmcif_multi_write(host, data);
|
||||
break;
|
||||
case MMC_CMD_WRITE_SINGLE_BLOCK:
|
||||
ret = sh_mmcif_single_write(host, data);
|
||||
break;
|
||||
case MMC_CMD_READ_SINGLE_BLOCK:
|
||||
case MMC_CMD_SEND_EXT_CSD:
|
||||
ret = sh_mmcif_single_read(host, data);
|
||||
break;
|
||||
default:
|
||||
printf(DRIVER_NAME": NOT SUPPORT CMD = d'%08d\n", opc);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_mmcif_start_cmd(struct sh_mmcif_host *host,
|
||||
struct mmc_data *data, struct mmc_cmd *cmd)
|
||||
{
|
||||
long time;
|
||||
int ret = 0, mask = 0;
|
||||
u32 opc = cmd->cmdidx;
|
||||
|
||||
if (opc == MMC_CMD_STOP_TRANSMISSION) {
|
||||
/* MMCIF sends the STOP command automatically */
|
||||
if (host->last_cmd == MMC_CMD_READ_MULTIPLE_BLOCK)
|
||||
sh_mmcif_bitset(MASK_MCMD12DRE,
|
||||
&host->regs->ce_int_mask);
|
||||
else
|
||||
sh_mmcif_bitset(MASK_MCMD12RBE,
|
||||
&host->regs->ce_int_mask);
|
||||
|
||||
time = mmcif_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_mmcif_error_manage(host);
|
||||
|
||||
sh_mmcif_get_cmd12response(host, cmd);
|
||||
return 0;
|
||||
}
|
||||
if (opc == MMC_CMD_SWITCH)
|
||||
mask = MASK_MRBSYE;
|
||||
else
|
||||
mask = MASK_MCRSPE;
|
||||
|
||||
mask |= MASK_MCMDVIO | MASK_MBUFVIO | MASK_MWDATERR |
|
||||
MASK_MRDATERR | MASK_MRIDXERR | MASK_MRSPERR |
|
||||
MASK_MCCSTO | MASK_MCRCSTO | MASK_MWDATTO |
|
||||
MASK_MRDATTO | MASK_MRBSYTO | MASK_MRSPTO;
|
||||
|
||||
if (host->data) {
|
||||
sh_mmcif_write(0, &host->regs->ce_block_set);
|
||||
sh_mmcif_write(data->blocksize, &host->regs->ce_block_set);
|
||||
}
|
||||
opc = sh_mmcif_set_cmd(host, data, cmd);
|
||||
|
||||
sh_mmcif_write(INT_START_MAGIC, &host->regs->ce_int);
|
||||
sh_mmcif_write(mask, &host->regs->ce_int_mask);
|
||||
|
||||
debug("CMD%d ARG:%08x\n", cmd->cmdidx, cmd->cmdarg);
|
||||
/* set arg */
|
||||
sh_mmcif_write(cmd->cmdarg, &host->regs->ce_arg);
|
||||
host->wait_int = 0;
|
||||
/* set cmd */
|
||||
sh_mmcif_write(opc, &host->regs->ce_cmd_set);
|
||||
|
||||
time = mmcif_wait_interrupt_flag(host);
|
||||
if (time == 0)
|
||||
return sh_mmcif_error_manage(host);
|
||||
|
||||
if (host->sd_error) {
|
||||
switch (cmd->cmdidx) {
|
||||
case MMC_CMD_ALL_SEND_CID:
|
||||
case MMC_CMD_SELECT_CARD:
|
||||
case MMC_CMD_APP_CMD:
|
||||
ret = TIMEOUT;
|
||||
break;
|
||||
default:
|
||||
printf(DRIVER_NAME": Cmd(d'%d) err\n", cmd->cmdidx);
|
||||
ret = sh_mmcif_error_manage(host);
|
||||
break;
|
||||
}
|
||||
host->sd_error = 0;
|
||||
host->wait_int = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* if no response */
|
||||
if (!(opc & 0x00C00000))
|
||||
return 0;
|
||||
|
||||
if (host->wait_int == 1) {
|
||||
sh_mmcif_get_response(host, cmd);
|
||||
host->wait_int = 0;
|
||||
}
|
||||
if (host->data)
|
||||
ret = sh_mmcif_data_trans(host, data, cmd->cmdidx);
|
||||
host->last_cmd = cmd->cmdidx;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_mmcif_request(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct sh_mmcif_host *host = mmc->priv;
|
||||
int ret;
|
||||
|
||||
WATCHDOG_RESET();
|
||||
|
||||
switch (cmd->cmdidx) {
|
||||
case MMC_CMD_APP_CMD:
|
||||
return TIMEOUT;
|
||||
case MMC_CMD_SEND_EXT_CSD: /* = SD_SEND_IF_COND (8) */
|
||||
if (data)
|
||||
/* ext_csd */
|
||||
break;
|
||||
else
|
||||
/* send_if_cond cmd (not support) */
|
||||
return TIMEOUT;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
host->sd_error = 0;
|
||||
host->data = data;
|
||||
ret = sh_mmcif_start_cmd(host, data, cmd);
|
||||
host->data = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sh_mmcif_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct sh_mmcif_host *host = mmc->priv;
|
||||
|
||||
if (mmc->clock)
|
||||
sh_mmcif_clock_control(host, mmc->clock);
|
||||
|
||||
if (mmc->bus_width == 8)
|
||||
host->bus_width = MMC_BUS_WIDTH_8;
|
||||
else if (mmc->bus_width == 4)
|
||||
host->bus_width = MMC_BUS_WIDTH_4;
|
||||
else
|
||||
host->bus_width = MMC_BUS_WIDTH_1;
|
||||
|
||||
debug("clock = %d, buswidth = %d\n", mmc->clock, mmc->bus_width);
|
||||
}
|
||||
|
||||
static int sh_mmcif_init(struct mmc *mmc)
|
||||
{
|
||||
struct sh_mmcif_host *host = mmc->priv;
|
||||
|
||||
sh_mmcif_sync_reset(host);
|
||||
sh_mmcif_write(MASK_ALL, &host->regs->ce_int_mask);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mmc_ops sh_mmcif_ops = {
|
||||
.send_cmd = sh_mmcif_request,
|
||||
.set_ios = sh_mmcif_set_ios,
|
||||
.init = sh_mmcif_init,
|
||||
};
|
||||
|
||||
static struct mmc_config sh_mmcif_cfg = {
|
||||
.name = DRIVER_NAME,
|
||||
.ops = &sh_mmcif_ops,
|
||||
.host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT |
|
||||
MMC_MODE_8BIT,
|
||||
.voltages = MMC_VDD_32_33 | MMC_VDD_33_34,
|
||||
.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT,
|
||||
};
|
||||
|
||||
int mmcif_mmc_init(void)
|
||||
{
|
||||
struct mmc *mmc;
|
||||
struct sh_mmcif_host *host = NULL;
|
||||
|
||||
host = malloc(sizeof(struct sh_mmcif_host));
|
||||
if (!host)
|
||||
return -ENOMEM;
|
||||
memset(host, 0, sizeof(*host));
|
||||
|
||||
host->regs = (struct sh_mmcif_regs *)CONFIG_SH_MMCIF_ADDR;
|
||||
host->clk = CONFIG_SH_MMCIF_CLK;
|
||||
|
||||
sh_mmcif_cfg.f_min = MMC_CLK_DIV_MIN(host->clk);
|
||||
sh_mmcif_cfg.f_max = MMC_CLK_DIV_MAX(host->clk);
|
||||
|
||||
mmc = mmc_create(&sh_mmcif_cfg, host);
|
||||
if (mmc == NULL) {
|
||||
free(host);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
241
u-boot/drivers/mmc/sh_mmcif.h
Normal file
241
u-boot/drivers/mmc/sh_mmcif.h
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* MMCIF driver.
|
||||
*
|
||||
* Copyright (C) 2011 Renesas Solutions Corp.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*/
|
||||
|
||||
#ifndef _SH_MMCIF_H_
|
||||
#define _SH_MMCIF_H_
|
||||
|
||||
struct sh_mmcif_regs {
|
||||
unsigned long ce_cmd_set;
|
||||
unsigned long reserved;
|
||||
unsigned long ce_arg;
|
||||
unsigned long ce_arg_cmd12;
|
||||
unsigned long ce_cmd_ctrl;
|
||||
unsigned long ce_block_set;
|
||||
unsigned long ce_clk_ctrl;
|
||||
unsigned long ce_buf_acc;
|
||||
unsigned long ce_resp3;
|
||||
unsigned long ce_resp2;
|
||||
unsigned long ce_resp1;
|
||||
unsigned long ce_resp0;
|
||||
unsigned long ce_resp_cmd12;
|
||||
unsigned long ce_data;
|
||||
unsigned long reserved2[2];
|
||||
unsigned long ce_int;
|
||||
unsigned long ce_int_mask;
|
||||
unsigned long ce_host_sts1;
|
||||
unsigned long ce_host_sts2;
|
||||
unsigned long reserved3[11];
|
||||
unsigned long ce_version;
|
||||
};
|
||||
|
||||
/* CE_CMD_SET */
|
||||
#define CMD_MASK 0x3f000000
|
||||
#define CMD_SET_RTYP_NO ((0 << 23) | (0 << 22))
|
||||
/* R1/R1b/R3/R4/R5 */
|
||||
#define CMD_SET_RTYP_6B ((0 << 23) | (1 << 22))
|
||||
/* R2 */
|
||||
#define CMD_SET_RTYP_17B ((1 << 23) | (0 << 22))
|
||||
/* R1b */
|
||||
#define CMD_SET_RBSY (1 << 21)
|
||||
#define CMD_SET_CCSEN (1 << 20)
|
||||
/* 1: on data, 0: no data */
|
||||
#define CMD_SET_WDAT (1 << 19)
|
||||
/* 1: write to card, 0: read from card */
|
||||
#define CMD_SET_DWEN (1 << 18)
|
||||
/* 1: multi block trans, 0: single */
|
||||
#define CMD_SET_CMLTE (1 << 17)
|
||||
/* 1: CMD12 auto issue */
|
||||
#define CMD_SET_CMD12EN (1 << 16)
|
||||
/* index check */
|
||||
#define CMD_SET_RIDXC_INDEX ((0 << 15) | (0 << 14))
|
||||
/* check bits check */
|
||||
#define CMD_SET_RIDXC_BITS ((0 << 15) | (1 << 14))
|
||||
/* no check */
|
||||
#define CMD_SET_RIDXC_NO ((1 << 15) | (0 << 14))
|
||||
/* 1: CRC7 check*/
|
||||
#define CMD_SET_CRC7C ((0 << 13) | (0 << 12))
|
||||
/* 1: check bits check*/
|
||||
#define CMD_SET_CRC7C_BITS ((0 << 13) | (1 << 12))
|
||||
/* 1: internal CRC7 check*/
|
||||
#define CMD_SET_CRC7C_INTERNAL ((1 << 13) | (0 << 12))
|
||||
/* 1: CRC16 check*/
|
||||
#define CMD_SET_CRC16C (1 << 10)
|
||||
/* 1: not receive CRC status */
|
||||
#define CMD_SET_CRCSTE (1 << 8)
|
||||
/* 1: tran mission bit "Low" */
|
||||
#define CMD_SET_TBIT (1 << 7)
|
||||
/* 1: open/drain */
|
||||
#define CMD_SET_OPDM (1 << 6)
|
||||
#define CMD_SET_CCSH (1 << 5)
|
||||
/* 1bit */
|
||||
#define CMD_SET_DATW_1 ((0 << 1) | (0 << 0))
|
||||
/* 4bit */
|
||||
#define CMD_SET_DATW_4 ((0 << 1) | (1 << 0))
|
||||
/* 8bit */
|
||||
#define CMD_SET_DATW_8 ((1 << 1) | (0 << 0))
|
||||
|
||||
/* CE_CMD_CTRL */
|
||||
#define CMD_CTRL_BREAK (1 << 0)
|
||||
|
||||
/* CE_BLOCK_SET */
|
||||
#define BLOCK_SIZE_MASK 0x0000ffff
|
||||
|
||||
/* CE_CLK_CTRL */
|
||||
#define CLK_ENABLE (1 << 24)
|
||||
#define CLK_CLEAR ((1 << 19) | (1 << 18) | (1 << 17) | (1 << 16))
|
||||
#define CLK_PCLK ((1 << 19) | (1 << 18) | (1 << 17) | (1 << 16))
|
||||
/* respons timeout */
|
||||
#define SRSPTO_256 ((1 << 13) | (0 << 12))
|
||||
/* respons busy timeout */
|
||||
#define SRBSYTO_29 ((1 << 11) | (1 << 10) | (1 << 9) | (1 << 8))
|
||||
/* read/write timeout */
|
||||
#define SRWDTO_29 ((1 << 7) | (1 << 6) | (1 << 5) | (1 << 4))
|
||||
/* ccs timeout */
|
||||
#define SCCSTO_29 ((1 << 3) | (1 << 2) | (1 << 1) | (1 << 0))
|
||||
|
||||
/* CE_BUF_ACC */
|
||||
#define BUF_ACC_DMAWEN (1 << 25)
|
||||
#define BUF_ACC_DMAREN (1 << 24)
|
||||
#define BUF_ACC_BUSW_32 (0 << 17)
|
||||
#define BUF_ACC_BUSW_16 (1 << 17)
|
||||
#define BUF_ACC_ATYP (1 << 16)
|
||||
|
||||
/* CE_INT */
|
||||
#define INT_CCSDE (1 << 29)
|
||||
#define INT_CMD12DRE (1 << 26)
|
||||
#define INT_CMD12RBE (1 << 25)
|
||||
#define INT_CMD12CRE (1 << 24)
|
||||
#define INT_DTRANE (1 << 23)
|
||||
#define INT_BUFRE (1 << 22)
|
||||
#define INT_BUFWEN (1 << 21)
|
||||
#define INT_BUFREN (1 << 20)
|
||||
#define INT_CCSRCV (1 << 19)
|
||||
#define INT_RBSYE (1 << 17)
|
||||
#define INT_CRSPE (1 << 16)
|
||||
#define INT_CMDVIO (1 << 15)
|
||||
#define INT_BUFVIO (1 << 14)
|
||||
#define INT_WDATERR (1 << 11)
|
||||
#define INT_RDATERR (1 << 10)
|
||||
#define INT_RIDXERR (1 << 9)
|
||||
#define INT_RSPERR (1 << 8)
|
||||
#define INT_CCSTO (1 << 5)
|
||||
#define INT_CRCSTO (1 << 4)
|
||||
#define INT_WDATTO (1 << 3)
|
||||
#define INT_RDATTO (1 << 2)
|
||||
#define INT_RBSYTO (1 << 1)
|
||||
#define INT_RSPTO (1 << 0)
|
||||
#define INT_ERR_STS (INT_CMDVIO | INT_BUFVIO | INT_WDATERR | \
|
||||
INT_RDATERR | INT_RIDXERR | INT_RSPERR | \
|
||||
INT_CCSTO | INT_CRCSTO | INT_WDATTO | \
|
||||
INT_RDATTO | INT_RBSYTO | INT_RSPTO)
|
||||
#define INT_START_MAGIC 0xD80430C0
|
||||
|
||||
/* CE_INT_MASK */
|
||||
#define MASK_ALL 0x00000000
|
||||
#define MASK_MCCSDE (1 << 29)
|
||||
#define MASK_MCMD12DRE (1 << 26)
|
||||
#define MASK_MCMD12RBE (1 << 25)
|
||||
#define MASK_MCMD12CRE (1 << 24)
|
||||
#define MASK_MDTRANE (1 << 23)
|
||||
#define MASK_MBUFRE (1 << 22)
|
||||
#define MASK_MBUFWEN (1 << 21)
|
||||
#define MASK_MBUFREN (1 << 20)
|
||||
#define MASK_MCCSRCV (1 << 19)
|
||||
#define MASK_MRBSYE (1 << 17)
|
||||
#define MASK_MCRSPE (1 << 16)
|
||||
#define MASK_MCMDVIO (1 << 15)
|
||||
#define MASK_MBUFVIO (1 << 14)
|
||||
#define MASK_MWDATERR (1 << 11)
|
||||
#define MASK_MRDATERR (1 << 10)
|
||||
#define MASK_MRIDXERR (1 << 9)
|
||||
#define MASK_MRSPERR (1 << 8)
|
||||
#define MASK_MCCSTO (1 << 5)
|
||||
#define MASK_MCRCSTO (1 << 4)
|
||||
#define MASK_MWDATTO (1 << 3)
|
||||
#define MASK_MRDATTO (1 << 2)
|
||||
#define MASK_MRBSYTO (1 << 1)
|
||||
#define MASK_MRSPTO (1 << 0)
|
||||
|
||||
/* CE_HOST_STS1 */
|
||||
#define STS1_CMDSEQ (1 << 31)
|
||||
|
||||
/* CE_HOST_STS2 */
|
||||
#define STS2_CRCSTE (1 << 31)
|
||||
#define STS2_CRC16E (1 << 30)
|
||||
#define STS2_AC12CRCE (1 << 29)
|
||||
#define STS2_RSPCRC7E (1 << 28)
|
||||
#define STS2_CRCSTEBE (1 << 27)
|
||||
#define STS2_RDATEBE (1 << 26)
|
||||
#define STS2_AC12REBE (1 << 25)
|
||||
#define STS2_RSPEBE (1 << 24)
|
||||
#define STS2_AC12IDXE (1 << 23)
|
||||
#define STS2_RSPIDXE (1 << 22)
|
||||
#define STS2_CCSTO (1 << 15)
|
||||
#define STS2_RDATTO (1 << 14)
|
||||
#define STS2_DATBSYTO (1 << 13)
|
||||
#define STS2_CRCSTTO (1 << 12)
|
||||
#define STS2_AC12BSYTO (1 << 11)
|
||||
#define STS2_RSPBSYTO (1 << 10)
|
||||
#define STS2_AC12RSPTO (1 << 9)
|
||||
#define STS2_RSPTO (1 << 8)
|
||||
|
||||
#define STS2_CRC_ERR (STS2_CRCSTE | STS2_CRC16E | \
|
||||
STS2_AC12CRCE | STS2_RSPCRC7E | STS2_CRCSTEBE)
|
||||
#define STS2_TIMEOUT_ERR (STS2_CCSTO | STS2_RDATTO | \
|
||||
STS2_DATBSYTO | STS2_CRCSTTO | \
|
||||
STS2_AC12BSYTO | STS2_RSPBSYTO | \
|
||||
STS2_AC12RSPTO | STS2_RSPTO)
|
||||
|
||||
/* CE_VERSION */
|
||||
#define SOFT_RST_ON (1 << 31)
|
||||
#define SOFT_RST_OFF (0 << 31)
|
||||
|
||||
#define CLKDEV_EMMC_DATA 52000000 /* 52MHz */
|
||||
#ifdef CONFIG_RMOBILE
|
||||
#define MMC_CLK_DIV_MIN(clk) (clk / (1 << 9))
|
||||
#define MMC_CLK_DIV_MAX(clk) (clk / (1 << 1))
|
||||
#else
|
||||
#define MMC_CLK_DIV_MIN(clk) (clk / (1 << 8))
|
||||
#define MMC_CLK_DIV_MAX(clk) CLKDEV_EMMC_DATA
|
||||
#endif
|
||||
|
||||
#define MMC_BUS_WIDTH_1 0
|
||||
#define MMC_BUS_WIDTH_4 2
|
||||
#define MMC_BUS_WIDTH_8 3
|
||||
|
||||
struct sh_mmcif_host {
|
||||
struct mmc_data *data;
|
||||
struct sh_mmcif_regs *regs;
|
||||
unsigned int clk;
|
||||
int bus_width;
|
||||
u16 wait_int;
|
||||
u16 sd_error;
|
||||
u8 last_cmd;
|
||||
};
|
||||
|
||||
static inline u32 sh_mmcif_read(unsigned long *reg)
|
||||
{
|
||||
return readl(reg);
|
||||
}
|
||||
|
||||
static inline void sh_mmcif_write(u32 val, unsigned long *reg)
|
||||
{
|
||||
writel(val, reg);
|
||||
}
|
||||
|
||||
static inline void sh_mmcif_bitset(u32 val, unsigned long *reg)
|
||||
{
|
||||
sh_mmcif_write(val | sh_mmcif_read(reg), reg);
|
||||
}
|
||||
|
||||
static inline void sh_mmcif_bitclr(u32 val, unsigned long *reg)
|
||||
{
|
||||
sh_mmcif_write(~val & sh_mmcif_read(reg), reg);
|
||||
}
|
||||
|
||||
#endif /* _SH_MMCIF_H_ */
|
||||
695
u-boot/drivers/mmc/sh_sdhi.c
Normal file
695
u-boot/drivers/mmc/sh_sdhi.c
Normal file
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
* drivers/mmc/sh_sdhi.c
|
||||
*
|
||||
* SD/MMC driver for Renesas rmobile ARM SoCs.
|
||||
*
|
||||
* Copyright (C) 2011,2013-2014 Renesas Electronics Corporation
|
||||
* Copyright (C) 2014 Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com>
|
||||
* Copyright (C) 2008-2009 Renesas Solutions Corp.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <mmc.h>
|
||||
#include <asm/errno.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/rmobile.h>
|
||||
#include <asm/arch/sh_sdhi.h>
|
||||
|
||||
#define DRIVER_NAME "sh-sdhi"
|
||||
|
||||
struct sh_sdhi_host {
|
||||
unsigned long addr;
|
||||
int ch;
|
||||
int bus_shift;
|
||||
unsigned long quirks;
|
||||
unsigned char wait_int;
|
||||
unsigned char sd_error;
|
||||
unsigned char detect_waiting;
|
||||
};
|
||||
static inline void sh_sdhi_writew(struct sh_sdhi_host *host, int reg, u16 val)
|
||||
{
|
||||
writew(val, host->addr + (reg << host->bus_shift));
|
||||
}
|
||||
|
||||
static inline u16 sh_sdhi_readw(struct sh_sdhi_host *host, int reg)
|
||||
{
|
||||
return readw(host->addr + (reg << host->bus_shift));
|
||||
}
|
||||
|
||||
static void *mmc_priv(struct mmc *mmc)
|
||||
{
|
||||
return (void *)mmc->priv;
|
||||
}
|
||||
|
||||
static void sh_sdhi_detect(struct sh_sdhi_host *host)
|
||||
{
|
||||
sh_sdhi_writew(host, SDHI_OPTION,
|
||||
OPT_BUS_WIDTH_1 | sh_sdhi_readw(host, SDHI_OPTION));
|
||||
|
||||
host->detect_waiting = 0;
|
||||
}
|
||||
|
||||
static int sh_sdhi_intr(void *dev_id)
|
||||
{
|
||||
struct sh_sdhi_host *host = dev_id;
|
||||
int state1 = 0, state2 = 0;
|
||||
|
||||
state1 = sh_sdhi_readw(host, SDHI_INFO1);
|
||||
state2 = sh_sdhi_readw(host, SDHI_INFO2);
|
||||
|
||||
debug("%s: state1 = %x, state2 = %x\n", __func__, state1, state2);
|
||||
|
||||
/* CARD Insert */
|
||||
if (state1 & INFO1_CARD_IN) {
|
||||
sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_CARD_IN);
|
||||
if (!host->detect_waiting) {
|
||||
host->detect_waiting = 1;
|
||||
sh_sdhi_detect(host);
|
||||
}
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END |
|
||||
INFO1M_ACCESS_END | INFO1M_CARD_IN |
|
||||
INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
|
||||
return -EAGAIN;
|
||||
}
|
||||
/* CARD Removal */
|
||||
if (state1 & INFO1_CARD_RE) {
|
||||
sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_CARD_RE);
|
||||
if (!host->detect_waiting) {
|
||||
host->detect_waiting = 1;
|
||||
sh_sdhi_detect(host);
|
||||
}
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END |
|
||||
INFO1M_ACCESS_END | INFO1M_CARD_RE |
|
||||
INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
|
||||
sh_sdhi_writew(host, SDHI_SDIO_INFO1_MASK, SDIO_INFO1M_ON);
|
||||
sh_sdhi_writew(host, SDHI_SDIO_MODE, SDIO_MODE_OFF);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
if (state2 & INFO2_ALL_ERR) {
|
||||
sh_sdhi_writew(host, SDHI_INFO2,
|
||||
(unsigned short)~(INFO2_ALL_ERR));
|
||||
sh_sdhi_writew(host, SDHI_INFO2_MASK,
|
||||
INFO2M_ALL_ERR |
|
||||
sh_sdhi_readw(host, SDHI_INFO2_MASK));
|
||||
host->sd_error = 1;
|
||||
host->wait_int = 1;
|
||||
return 0;
|
||||
}
|
||||
/* Respons End */
|
||||
if (state1 & INFO1_RESP_END) {
|
||||
sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_RESP_END);
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK,
|
||||
INFO1M_RESP_END |
|
||||
sh_sdhi_readw(host, SDHI_INFO1_MASK));
|
||||
host->wait_int = 1;
|
||||
return 0;
|
||||
}
|
||||
/* SD_BUF Read Enable */
|
||||
if (state2 & INFO2_BRE_ENABLE) {
|
||||
sh_sdhi_writew(host, SDHI_INFO2, ~INFO2_BRE_ENABLE);
|
||||
sh_sdhi_writew(host, SDHI_INFO2_MASK,
|
||||
INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ |
|
||||
sh_sdhi_readw(host, SDHI_INFO2_MASK));
|
||||
host->wait_int = 1;
|
||||
return 0;
|
||||
}
|
||||
/* SD_BUF Write Enable */
|
||||
if (state2 & INFO2_BWE_ENABLE) {
|
||||
sh_sdhi_writew(host, SDHI_INFO2, ~INFO2_BWE_ENABLE);
|
||||
sh_sdhi_writew(host, SDHI_INFO2_MASK,
|
||||
INFO2_BWE_ENABLE | INFO2M_BUF_ILL_WRITE |
|
||||
sh_sdhi_readw(host, SDHI_INFO2_MASK));
|
||||
host->wait_int = 1;
|
||||
return 0;
|
||||
}
|
||||
/* Access End */
|
||||
if (state1 & INFO1_ACCESS_END) {
|
||||
sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_ACCESS_END);
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK,
|
||||
INFO1_ACCESS_END |
|
||||
sh_sdhi_readw(host, SDHI_INFO1_MASK));
|
||||
host->wait_int = 1;
|
||||
return 0;
|
||||
}
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
static int sh_sdhi_wait_interrupt_flag(struct sh_sdhi_host *host)
|
||||
{
|
||||
int timeout = 10000000;
|
||||
|
||||
while (1) {
|
||||
timeout--;
|
||||
if (timeout < 0) {
|
||||
debug(DRIVER_NAME": %s timeout\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!sh_sdhi_intr(host))
|
||||
break;
|
||||
|
||||
udelay(1); /* 1 usec */
|
||||
}
|
||||
|
||||
return 1; /* Return value: NOT 0 = complete waiting */
|
||||
}
|
||||
|
||||
static int sh_sdhi_clock_control(struct sh_sdhi_host *host, unsigned long clk)
|
||||
{
|
||||
u32 clkdiv, i, timeout;
|
||||
|
||||
if (sh_sdhi_readw(host, SDHI_INFO2) & (1 << 14)) {
|
||||
printf(DRIVER_NAME": Busy state ! Cannot change the clock\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
sh_sdhi_writew(host, SDHI_CLK_CTRL,
|
||||
~CLK_ENABLE & sh_sdhi_readw(host, SDHI_CLK_CTRL));
|
||||
|
||||
if (clk == 0)
|
||||
return -EIO;
|
||||
|
||||
clkdiv = 0x80;
|
||||
i = CONFIG_SH_SDHI_FREQ >> (0x8 + 1);
|
||||
for (; clkdiv && clk >= (i << 1); (clkdiv >>= 1))
|
||||
i <<= 1;
|
||||
|
||||
sh_sdhi_writew(host, SDHI_CLK_CTRL, clkdiv);
|
||||
|
||||
timeout = 100000;
|
||||
/* Waiting for SD Bus busy to be cleared */
|
||||
while (timeout--) {
|
||||
if ((sh_sdhi_readw(host, SDHI_INFO2) & 0x2000))
|
||||
break;
|
||||
}
|
||||
|
||||
if (timeout)
|
||||
sh_sdhi_writew(host, SDHI_CLK_CTRL,
|
||||
CLK_ENABLE | sh_sdhi_readw(host, SDHI_CLK_CTRL));
|
||||
else
|
||||
return -EBUSY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_sdhi_sync_reset(struct sh_sdhi_host *host)
|
||||
{
|
||||
u32 timeout;
|
||||
sh_sdhi_writew(host, SDHI_SOFT_RST, SOFT_RST_ON);
|
||||
sh_sdhi_writew(host, SDHI_SOFT_RST, SOFT_RST_OFF);
|
||||
sh_sdhi_writew(host, SDHI_CLK_CTRL,
|
||||
CLK_ENABLE | sh_sdhi_readw(host, SDHI_CLK_CTRL));
|
||||
|
||||
timeout = 100000;
|
||||
while (timeout--) {
|
||||
if (!(sh_sdhi_readw(host, SDHI_INFO2) & INFO2_CBUSY))
|
||||
break;
|
||||
udelay(100);
|
||||
}
|
||||
|
||||
if (!timeout)
|
||||
return -EBUSY;
|
||||
|
||||
if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF)
|
||||
sh_sdhi_writew(host, SDHI_HOST_MODE, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_sdhi_error_manage(struct sh_sdhi_host *host)
|
||||
{
|
||||
unsigned short e_state1, e_state2;
|
||||
int ret;
|
||||
|
||||
host->sd_error = 0;
|
||||
host->wait_int = 0;
|
||||
|
||||
e_state1 = sh_sdhi_readw(host, SDHI_ERR_STS1);
|
||||
e_state2 = sh_sdhi_readw(host, SDHI_ERR_STS2);
|
||||
if (e_state2 & ERR_STS2_SYS_ERROR) {
|
||||
if (e_state2 & ERR_STS2_RES_STOP_TIMEOUT)
|
||||
ret = TIMEOUT;
|
||||
else
|
||||
ret = -EILSEQ;
|
||||
debug("%s: ERR_STS2 = %04x\n",
|
||||
DRIVER_NAME, sh_sdhi_readw(host, SDHI_ERR_STS2));
|
||||
sh_sdhi_sync_reset(host);
|
||||
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK,
|
||||
INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
|
||||
return ret;
|
||||
}
|
||||
if (e_state1 & ERR_STS1_CRC_ERROR || e_state1 & ERR_STS1_CMD_ERROR)
|
||||
ret = -EILSEQ;
|
||||
else
|
||||
ret = TIMEOUT;
|
||||
|
||||
debug("%s: ERR_STS1 = %04x\n",
|
||||
DRIVER_NAME, sh_sdhi_readw(host, SDHI_ERR_STS1));
|
||||
sh_sdhi_sync_reset(host);
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK,
|
||||
INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_sdhi_single_read(struct sh_sdhi_host *host, struct mmc_data *data)
|
||||
{
|
||||
long time;
|
||||
unsigned short blocksize, i;
|
||||
unsigned short *p = (unsigned short *)data->dest;
|
||||
|
||||
if ((unsigned long)p & 0x00000001) {
|
||||
debug(DRIVER_NAME": %s: The data pointer is unaligned.",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
host->wait_int = 0;
|
||||
sh_sdhi_writew(host, SDHI_INFO2_MASK,
|
||||
~(INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ) &
|
||||
sh_sdhi_readw(host, SDHI_INFO2_MASK));
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK,
|
||||
~INFO1M_ACCESS_END &
|
||||
sh_sdhi_readw(host, SDHI_INFO1_MASK));
|
||||
time = sh_sdhi_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_sdhi_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
blocksize = sh_sdhi_readw(host, SDHI_SIZE);
|
||||
for (i = 0; i < blocksize / 2; i++)
|
||||
*p++ = sh_sdhi_readw(host, SDHI_BUF0);
|
||||
|
||||
time = sh_sdhi_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_sdhi_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_sdhi_multi_read(struct sh_sdhi_host *host, struct mmc_data *data)
|
||||
{
|
||||
long time;
|
||||
unsigned short blocksize, i, sec;
|
||||
unsigned short *p = (unsigned short *)data->dest;
|
||||
|
||||
if ((unsigned long)p & 0x00000001) {
|
||||
debug(DRIVER_NAME": %s: The data pointer is unaligned.",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
debug("%s: blocks = %d, blocksize = %d\n",
|
||||
__func__, data->blocks, data->blocksize);
|
||||
|
||||
host->wait_int = 0;
|
||||
for (sec = 0; sec < data->blocks; sec++) {
|
||||
sh_sdhi_writew(host, SDHI_INFO2_MASK,
|
||||
~(INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ) &
|
||||
sh_sdhi_readw(host, SDHI_INFO2_MASK));
|
||||
|
||||
time = sh_sdhi_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_sdhi_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
blocksize = sh_sdhi_readw(host, SDHI_SIZE);
|
||||
for (i = 0; i < blocksize / 2; i++)
|
||||
*p++ = sh_sdhi_readw(host, SDHI_BUF0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_sdhi_single_write(struct sh_sdhi_host *host,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
long time;
|
||||
unsigned short blocksize, i;
|
||||
const unsigned short *p = (const unsigned short *)data->src;
|
||||
|
||||
if ((unsigned long)p & 0x00000001) {
|
||||
debug(DRIVER_NAME": %s: The data pointer is unaligned.",
|
||||
__func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
debug("%s: blocks = %d, blocksize = %d\n",
|
||||
__func__, data->blocks, data->blocksize);
|
||||
|
||||
host->wait_int = 0;
|
||||
sh_sdhi_writew(host, SDHI_INFO2_MASK,
|
||||
~(INFO2M_BWE_ENABLE | INFO2M_BUF_ILL_WRITE) &
|
||||
sh_sdhi_readw(host, SDHI_INFO2_MASK));
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK,
|
||||
~INFO1M_ACCESS_END &
|
||||
sh_sdhi_readw(host, SDHI_INFO1_MASK));
|
||||
|
||||
time = sh_sdhi_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_sdhi_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
blocksize = sh_sdhi_readw(host, SDHI_SIZE);
|
||||
for (i = 0; i < blocksize / 2; i++)
|
||||
sh_sdhi_writew(host, SDHI_BUF0, *p++);
|
||||
|
||||
time = sh_sdhi_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_sdhi_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_sdhi_multi_write(struct sh_sdhi_host *host, struct mmc_data *data)
|
||||
{
|
||||
long time;
|
||||
unsigned short i, sec, blocksize;
|
||||
const unsigned short *p = (const unsigned short *)data->src;
|
||||
|
||||
debug("%s: blocks = %d, blocksize = %d\n",
|
||||
__func__, data->blocks, data->blocksize);
|
||||
|
||||
host->wait_int = 0;
|
||||
for (sec = 0; sec < data->blocks; sec++) {
|
||||
sh_sdhi_writew(host, SDHI_INFO2_MASK,
|
||||
~(INFO2M_BWE_ENABLE | INFO2M_BUF_ILL_WRITE) &
|
||||
sh_sdhi_readw(host, SDHI_INFO2_MASK));
|
||||
|
||||
time = sh_sdhi_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_sdhi_error_manage(host);
|
||||
|
||||
host->wait_int = 0;
|
||||
blocksize = sh_sdhi_readw(host, SDHI_SIZE);
|
||||
for (i = 0; i < blocksize / 2; i++)
|
||||
sh_sdhi_writew(host, SDHI_BUF0, *p++);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sh_sdhi_get_response(struct sh_sdhi_host *host, struct mmc_cmd *cmd)
|
||||
{
|
||||
unsigned short i, j, cnt = 1;
|
||||
unsigned short resp[8];
|
||||
unsigned long *p1, *p2;
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
cnt = 4;
|
||||
resp[0] = sh_sdhi_readw(host, SDHI_RSP00);
|
||||
resp[1] = sh_sdhi_readw(host, SDHI_RSP01);
|
||||
resp[2] = sh_sdhi_readw(host, SDHI_RSP02);
|
||||
resp[3] = sh_sdhi_readw(host, SDHI_RSP03);
|
||||
resp[4] = sh_sdhi_readw(host, SDHI_RSP04);
|
||||
resp[5] = sh_sdhi_readw(host, SDHI_RSP05);
|
||||
resp[6] = sh_sdhi_readw(host, SDHI_RSP06);
|
||||
resp[7] = sh_sdhi_readw(host, SDHI_RSP07);
|
||||
|
||||
/* SDHI REGISTER SPECIFICATION */
|
||||
for (i = 7, j = 6; i > 0; i--) {
|
||||
resp[i] = (resp[i] << 8) & 0xff00;
|
||||
resp[i] |= (resp[j--] >> 8) & 0x00ff;
|
||||
}
|
||||
resp[0] = (resp[0] << 8) & 0xff00;
|
||||
|
||||
/* SDHI REGISTER SPECIFICATION */
|
||||
p1 = ((unsigned long *)resp) + 3;
|
||||
|
||||
} else {
|
||||
resp[0] = sh_sdhi_readw(host, SDHI_RSP00);
|
||||
resp[1] = sh_sdhi_readw(host, SDHI_RSP01);
|
||||
|
||||
p1 = ((unsigned long *)resp);
|
||||
}
|
||||
|
||||
p2 = (unsigned long *)cmd->response;
|
||||
#if defined(__BIG_ENDIAN_BITFIELD)
|
||||
for (i = 0; i < cnt; i++) {
|
||||
*p2++ = ((*p1 >> 16) & 0x0000ffff) |
|
||||
((*p1 << 16) & 0xffff0000);
|
||||
p1--;
|
||||
}
|
||||
#else
|
||||
for (i = 0; i < cnt; i++)
|
||||
*p2++ = *p1--;
|
||||
#endif /* __BIG_ENDIAN_BITFIELD */
|
||||
}
|
||||
|
||||
static unsigned short sh_sdhi_set_cmd(struct sh_sdhi_host *host,
|
||||
struct mmc_data *data, unsigned short opc)
|
||||
{
|
||||
switch (opc) {
|
||||
case SD_CMD_APP_SEND_OP_COND:
|
||||
case SD_CMD_APP_SEND_SCR:
|
||||
opc |= SDHI_APP;
|
||||
break;
|
||||
case SD_CMD_APP_SET_BUS_WIDTH:
|
||||
/* SD_APP_SET_BUS_WIDTH*/
|
||||
if (!data)
|
||||
opc |= SDHI_APP;
|
||||
else /* SD_SWITCH */
|
||||
opc = SDHI_SD_SWITCH;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return opc;
|
||||
}
|
||||
|
||||
static unsigned short sh_sdhi_data_trans(struct sh_sdhi_host *host,
|
||||
struct mmc_data *data, unsigned short opc)
|
||||
{
|
||||
unsigned short ret;
|
||||
|
||||
switch (opc) {
|
||||
case MMC_CMD_READ_MULTIPLE_BLOCK:
|
||||
ret = sh_sdhi_multi_read(host, data);
|
||||
break;
|
||||
case MMC_CMD_WRITE_MULTIPLE_BLOCK:
|
||||
ret = sh_sdhi_multi_write(host, data);
|
||||
break;
|
||||
case MMC_CMD_WRITE_SINGLE_BLOCK:
|
||||
ret = sh_sdhi_single_write(host, data);
|
||||
break;
|
||||
case MMC_CMD_READ_SINGLE_BLOCK:
|
||||
case SDHI_SD_APP_SEND_SCR:
|
||||
case SDHI_SD_SWITCH: /* SD_SWITCH */
|
||||
ret = sh_sdhi_single_read(host, data);
|
||||
break;
|
||||
default:
|
||||
printf(DRIVER_NAME": SD: NOT SUPPORT CMD = d'%04d\n", opc);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_sdhi_start_cmd(struct sh_sdhi_host *host,
|
||||
struct mmc_data *data, struct mmc_cmd *cmd)
|
||||
{
|
||||
long time;
|
||||
unsigned short opc = cmd->cmdidx;
|
||||
int ret = 0;
|
||||
unsigned long timeout;
|
||||
|
||||
debug("opc = %d, arg = %x, resp_type = %x\n",
|
||||
opc, cmd->cmdarg, cmd->resp_type);
|
||||
|
||||
if (opc == MMC_CMD_STOP_TRANSMISSION) {
|
||||
/* SDHI sends the STOP command automatically by STOP reg */
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK, ~INFO1M_ACCESS_END &
|
||||
sh_sdhi_readw(host, SDHI_INFO1_MASK));
|
||||
|
||||
time = sh_sdhi_wait_interrupt_flag(host);
|
||||
if (time == 0 || host->sd_error != 0)
|
||||
return sh_sdhi_error_manage(host);
|
||||
|
||||
sh_sdhi_get_response(host, cmd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
if ((opc == MMC_CMD_READ_MULTIPLE_BLOCK) ||
|
||||
opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) {
|
||||
sh_sdhi_writew(host, SDHI_STOP, STOP_SEC_ENABLE);
|
||||
sh_sdhi_writew(host, SDHI_SECCNT, data->blocks);
|
||||
}
|
||||
sh_sdhi_writew(host, SDHI_SIZE, data->blocksize);
|
||||
}
|
||||
opc = sh_sdhi_set_cmd(host, data, opc);
|
||||
|
||||
/*
|
||||
* U-Boot cannot use interrupt.
|
||||
* So this flag may not be clear by timing
|
||||
*/
|
||||
sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_RESP_END);
|
||||
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK,
|
||||
INFO1M_RESP_END | sh_sdhi_readw(host, SDHI_INFO1_MASK));
|
||||
sh_sdhi_writew(host, SDHI_ARG0,
|
||||
(unsigned short)(cmd->cmdarg & ARG0_MASK));
|
||||
sh_sdhi_writew(host, SDHI_ARG1,
|
||||
(unsigned short)((cmd->cmdarg >> 16) & ARG1_MASK));
|
||||
|
||||
timeout = 100000;
|
||||
/* Waiting for SD Bus busy to be cleared */
|
||||
while (timeout--) {
|
||||
if ((sh_sdhi_readw(host, SDHI_INFO2) & 0x2000))
|
||||
break;
|
||||
}
|
||||
|
||||
sh_sdhi_writew(host, SDHI_CMD, (unsigned short)(opc & CMD_MASK));
|
||||
|
||||
host->wait_int = 0;
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK,
|
||||
~INFO1M_RESP_END & sh_sdhi_readw(host, SDHI_INFO1_MASK));
|
||||
sh_sdhi_writew(host, SDHI_INFO2_MASK,
|
||||
~(INFO2M_CMD_ERROR | INFO2M_CRC_ERROR |
|
||||
INFO2M_END_ERROR | INFO2M_TIMEOUT |
|
||||
INFO2M_RESP_TIMEOUT | INFO2M_ILA) &
|
||||
sh_sdhi_readw(host, SDHI_INFO2_MASK));
|
||||
|
||||
time = sh_sdhi_wait_interrupt_flag(host);
|
||||
if (!time)
|
||||
return sh_sdhi_error_manage(host);
|
||||
|
||||
if (host->sd_error) {
|
||||
switch (cmd->cmdidx) {
|
||||
case MMC_CMD_ALL_SEND_CID:
|
||||
case MMC_CMD_SELECT_CARD:
|
||||
case SD_CMD_SEND_IF_COND:
|
||||
case MMC_CMD_APP_CMD:
|
||||
ret = TIMEOUT;
|
||||
break;
|
||||
default:
|
||||
debug(DRIVER_NAME": Cmd(d'%d) err\n", opc);
|
||||
debug(DRIVER_NAME": cmdidx = %d\n", cmd->cmdidx);
|
||||
ret = sh_sdhi_error_manage(host);
|
||||
break;
|
||||
}
|
||||
host->sd_error = 0;
|
||||
host->wait_int = 0;
|
||||
return ret;
|
||||
}
|
||||
if (sh_sdhi_readw(host, SDHI_INFO1) & INFO1_RESP_END)
|
||||
return -EINVAL;
|
||||
|
||||
if (host->wait_int) {
|
||||
sh_sdhi_get_response(host, cmd);
|
||||
host->wait_int = 0;
|
||||
}
|
||||
if (data)
|
||||
ret = sh_sdhi_data_trans(host, data, opc);
|
||||
|
||||
debug("ret = %d, resp = %08x, %08x, %08x, %08x\n",
|
||||
ret, cmd->response[0], cmd->response[1],
|
||||
cmd->response[2], cmd->response[3]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_sdhi_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct sh_sdhi_host *host = mmc_priv(mmc);
|
||||
int ret;
|
||||
|
||||
host->sd_error = 0;
|
||||
|
||||
ret = sh_sdhi_start_cmd(host, data, cmd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sh_sdhi_set_ios(struct mmc *mmc)
|
||||
{
|
||||
int ret;
|
||||
struct sh_sdhi_host *host = mmc_priv(mmc);
|
||||
|
||||
ret = sh_sdhi_clock_control(host, mmc->clock);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
if (mmc->bus_width == 4)
|
||||
sh_sdhi_writew(host, SDHI_OPTION, ~OPT_BUS_WIDTH_1 &
|
||||
sh_sdhi_readw(host, SDHI_OPTION));
|
||||
else
|
||||
sh_sdhi_writew(host, SDHI_OPTION, OPT_BUS_WIDTH_1 |
|
||||
sh_sdhi_readw(host, SDHI_OPTION));
|
||||
|
||||
debug("clock = %d, buswidth = %d\n", mmc->clock, mmc->bus_width);
|
||||
}
|
||||
|
||||
static int sh_sdhi_initialize(struct mmc *mmc)
|
||||
{
|
||||
struct sh_sdhi_host *host = mmc_priv(mmc);
|
||||
int ret = sh_sdhi_sync_reset(host);
|
||||
|
||||
sh_sdhi_writew(host, SDHI_PORTSEL, USE_1PORT);
|
||||
|
||||
#if defined(__BIG_ENDIAN_BITFIELD)
|
||||
sh_sdhi_writew(host, SDHI_EXT_SWAP, SET_SWAP);
|
||||
#endif
|
||||
|
||||
sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END |
|
||||
INFO1M_ACCESS_END | INFO1M_CARD_RE |
|
||||
INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct mmc_ops sh_sdhi_ops = {
|
||||
.send_cmd = sh_sdhi_send_cmd,
|
||||
.set_ios = sh_sdhi_set_ios,
|
||||
.init = sh_sdhi_initialize,
|
||||
};
|
||||
|
||||
static struct mmc_config sh_sdhi_cfg = {
|
||||
.name = DRIVER_NAME,
|
||||
.ops = &sh_sdhi_ops,
|
||||
.f_min = CLKDEV_INIT,
|
||||
.f_max = CLKDEV_HS_DATA,
|
||||
.voltages = MMC_VDD_32_33 | MMC_VDD_33_34,
|
||||
.host_caps = MMC_MODE_4BIT | MMC_MODE_HS,
|
||||
.part_type = PART_TYPE_DOS,
|
||||
.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT,
|
||||
};
|
||||
|
||||
int sh_sdhi_init(unsigned long addr, int ch, unsigned long quirks)
|
||||
{
|
||||
int ret = 0;
|
||||
struct mmc *mmc;
|
||||
struct sh_sdhi_host *host = NULL;
|
||||
|
||||
if (ch >= CONFIG_SYS_SH_SDHI_NR_CHANNEL)
|
||||
return -ENODEV;
|
||||
|
||||
host = malloc(sizeof(struct sh_sdhi_host));
|
||||
if (!host)
|
||||
return -ENOMEM;
|
||||
|
||||
mmc = mmc_create(&sh_sdhi_cfg, host);
|
||||
if (!mmc) {
|
||||
ret = -1;
|
||||
goto error;
|
||||
}
|
||||
|
||||
host->ch = ch;
|
||||
host->addr = addr;
|
||||
host->quirks = quirks;
|
||||
|
||||
if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF)
|
||||
host->bus_shift = 1;
|
||||
|
||||
return ret;
|
||||
error:
|
||||
if (host)
|
||||
free(host);
|
||||
return ret;
|
||||
}
|
||||
128
u-boot/drivers/mmc/socfpga_dw_mmc.c
Normal file
128
u-boot/drivers/mmc/socfpga_dw_mmc.c
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* (C) Copyright 2013 Altera Corporation <www.altera.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <asm/arch/clock_manager.h>
|
||||
#include <asm/arch/system_manager.h>
|
||||
#include <dm.h>
|
||||
#include <dwmmc.h>
|
||||
#include <errno.h>
|
||||
#include <fdtdec.h>
|
||||
#include <libfdt.h>
|
||||
#include <linux/err.h>
|
||||
#include <malloc.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
static const struct socfpga_clock_manager *clock_manager_base =
|
||||
(void *)SOCFPGA_CLKMGR_ADDRESS;
|
||||
static const struct socfpga_system_manager *system_manager_base =
|
||||
(void *)SOCFPGA_SYSMGR_ADDRESS;
|
||||
|
||||
/* socfpga implmentation specific driver private data */
|
||||
struct dwmci_socfpga_priv_data {
|
||||
struct dwmci_host host;
|
||||
unsigned int drvsel;
|
||||
unsigned int smplsel;
|
||||
};
|
||||
|
||||
static void socfpga_dwmci_clksel(struct dwmci_host *host)
|
||||
{
|
||||
struct dwmci_socfpga_priv_data *priv = host->priv;
|
||||
u32 sdmmc_mask = ((priv->smplsel & 0x7) << SYSMGR_SDMMC_SMPLSEL_SHIFT) |
|
||||
((priv->drvsel & 0x7) << SYSMGR_SDMMC_DRVSEL_SHIFT);
|
||||
|
||||
/* Disable SDMMC clock. */
|
||||
clrbits_le32(&clock_manager_base->per_pll.en,
|
||||
CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK);
|
||||
|
||||
debug("%s: drvsel %d smplsel %d\n", __func__,
|
||||
priv->drvsel, priv->smplsel);
|
||||
writel(sdmmc_mask, &system_manager_base->sdmmcgrp_ctrl);
|
||||
|
||||
debug("%s: SYSMGR_SDMMCGRP_CTRL_REG = 0x%x\n", __func__,
|
||||
readl(&system_manager_base->sdmmcgrp_ctrl));
|
||||
|
||||
/* Enable SDMMC clock */
|
||||
setbits_le32(&clock_manager_base->per_pll.en,
|
||||
CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK);
|
||||
}
|
||||
|
||||
static int socfpga_dwmmc_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
/* FIXME: probe from DT eventually too/ */
|
||||
const unsigned long clk = cm_get_mmc_controller_clk_hz();
|
||||
|
||||
struct dwmci_socfpga_priv_data *priv = dev_get_priv(dev);
|
||||
struct dwmci_host *host = &priv->host;
|
||||
int fifo_depth;
|
||||
|
||||
if (clk == 0) {
|
||||
printf("DWMMC: MMC clock is zero!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fifo_depth = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
|
||||
"fifo-depth", 0);
|
||||
if (fifo_depth < 0) {
|
||||
printf("DWMMC: Can't get FIFO depth\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
host->name = dev->name;
|
||||
host->ioaddr = (void *)dev_get_addr(dev);
|
||||
host->buswidth = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
|
||||
"bus-width", 4);
|
||||
host->clksel = socfpga_dwmci_clksel;
|
||||
|
||||
/*
|
||||
* TODO(sjg@chromium.org): Remove the need for this hack.
|
||||
* We only have one dwmmc block on gen5 SoCFPGA.
|
||||
*/
|
||||
host->dev_index = 0;
|
||||
/* Fixed clock divide by 4 which due to the SDMMC wrapper */
|
||||
host->bus_hz = clk;
|
||||
host->fifoth_val = MSIZE(0x2) |
|
||||
RX_WMARK(fifo_depth / 2 - 1) | TX_WMARK(fifo_depth / 2);
|
||||
priv->drvsel = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
|
||||
"drvsel", 3);
|
||||
priv->smplsel = fdtdec_get_uint(gd->fdt_blob, dev->of_offset,
|
||||
"smplsel", 0);
|
||||
host->priv = priv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int socfpga_dwmmc_probe(struct udevice *dev)
|
||||
{
|
||||
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
||||
struct dwmci_socfpga_priv_data *priv = dev_get_priv(dev);
|
||||
struct dwmci_host *host = &priv->host;
|
||||
int ret;
|
||||
|
||||
ret = add_dwmci(host, host->bus_hz, 400000);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
upriv->mmc = host->mmc;
|
||||
host->mmc->dev = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id socfpga_dwmmc_ids[] = {
|
||||
{ .compatible = "altr,socfpga-dw-mshc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(socfpga_dwmmc_drv) = {
|
||||
.name = "socfpga_dwmmc",
|
||||
.id = UCLASS_MMC,
|
||||
.of_match = socfpga_dwmmc_ids,
|
||||
.ofdata_to_platdata = socfpga_dwmmc_ofdata_to_platdata,
|
||||
.probe = socfpga_dwmmc_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct dwmci_socfpga_priv_data),
|
||||
};
|
||||
32
u-boot/drivers/mmc/spear_sdhci.c
Normal file
32
u-boot/drivers/mmc/spear_sdhci.c
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* (C) Copyright 2012
|
||||
* Vipin Kumar, ST Micoelectronics, vipin.kumar@st.com.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
|
||||
int spear_sdhci_init(u32 regbase, u32 max_clk, u32 min_clk, u32 quirks)
|
||||
{
|
||||
struct sdhci_host *host = NULL;
|
||||
host = (struct sdhci_host *)malloc(sizeof(struct sdhci_host));
|
||||
if (!host) {
|
||||
printf("sdhci host malloc fail!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
host->name = "sdhci";
|
||||
host->ioaddr = (void *)regbase;
|
||||
host->quirks = quirks;
|
||||
|
||||
if (quirks & SDHCI_QUIRK_REG32_RW)
|
||||
host->version = sdhci_readl(host, SDHCI_HOST_VERSION - 2) >> 16;
|
||||
else
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
|
||||
add_sdhci(host, max_clk, min_clk);
|
||||
return 0;
|
||||
}
|
||||
499
u-boot/drivers/mmc/sunxi_mmc.c
Normal file
499
u-boot/drivers/mmc/sunxi_mmc.c
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* (C) Copyright 2007-2011
|
||||
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
||||
* Aaron <leafy.myeh@allwinnertech.com>
|
||||
*
|
||||
* MMC driver for allwinner sunxi platform.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include <mmc.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/clock.h>
|
||||
#include <asm/arch/cpu.h>
|
||||
#include <asm/arch/gpio.h>
|
||||
#include <asm/arch/mmc.h>
|
||||
#include <asm-generic/gpio.h>
|
||||
|
||||
struct sunxi_mmc_host {
|
||||
unsigned mmc_no;
|
||||
uint32_t *mclkreg;
|
||||
unsigned fatal_err;
|
||||
struct sunxi_mmc *reg;
|
||||
struct mmc_config cfg;
|
||||
};
|
||||
|
||||
/* support 4 mmc hosts */
|
||||
struct sunxi_mmc_host mmc_host[4];
|
||||
|
||||
static int sunxi_mmc_getcd_gpio(int sdc_no)
|
||||
{
|
||||
switch (sdc_no) {
|
||||
case 0: return sunxi_name_to_gpio(CONFIG_MMC0_CD_PIN);
|
||||
case 1: return sunxi_name_to_gpio(CONFIG_MMC1_CD_PIN);
|
||||
case 2: return sunxi_name_to_gpio(CONFIG_MMC2_CD_PIN);
|
||||
case 3: return sunxi_name_to_gpio(CONFIG_MMC3_CD_PIN);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mmc_resource_init(int sdc_no)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
|
||||
struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
|
||||
int cd_pin, ret = 0;
|
||||
|
||||
debug("init mmc %d resource\n", sdc_no);
|
||||
|
||||
switch (sdc_no) {
|
||||
case 0:
|
||||
mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC0_BASE;
|
||||
mmchost->mclkreg = &ccm->sd0_clk_cfg;
|
||||
break;
|
||||
case 1:
|
||||
mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC1_BASE;
|
||||
mmchost->mclkreg = &ccm->sd1_clk_cfg;
|
||||
break;
|
||||
case 2:
|
||||
mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC2_BASE;
|
||||
mmchost->mclkreg = &ccm->sd2_clk_cfg;
|
||||
break;
|
||||
case 3:
|
||||
mmchost->reg = (struct sunxi_mmc *)SUNXI_MMC3_BASE;
|
||||
mmchost->mclkreg = &ccm->sd3_clk_cfg;
|
||||
break;
|
||||
default:
|
||||
printf("Wrong mmc number %d\n", sdc_no);
|
||||
return -1;
|
||||
}
|
||||
mmchost->mmc_no = sdc_no;
|
||||
|
||||
cd_pin = sunxi_mmc_getcd_gpio(sdc_no);
|
||||
if (cd_pin >= 0) {
|
||||
ret = gpio_request(cd_pin, "mmc_cd");
|
||||
if (!ret) {
|
||||
sunxi_gpio_set_pull(cd_pin, SUNXI_GPIO_PULL_UP);
|
||||
ret = gpio_direction_input(cd_pin);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mmc_set_mod_clk(struct sunxi_mmc_host *mmchost, unsigned int hz)
|
||||
{
|
||||
unsigned int pll, pll_hz, div, n, oclk_dly, sclk_dly;
|
||||
|
||||
if (hz <= 24000000) {
|
||||
pll = CCM_MMC_CTRL_OSCM24;
|
||||
pll_hz = 24000000;
|
||||
} else {
|
||||
#ifdef CONFIG_MACH_SUN9I
|
||||
pll = CCM_MMC_CTRL_PLL_PERIPH0;
|
||||
pll_hz = clock_get_pll4_periph0();
|
||||
#else
|
||||
pll = CCM_MMC_CTRL_PLL6;
|
||||
pll_hz = clock_get_pll6();
|
||||
#endif
|
||||
}
|
||||
|
||||
div = pll_hz / hz;
|
||||
if (pll_hz % hz)
|
||||
div++;
|
||||
|
||||
n = 0;
|
||||
while (div > 16) {
|
||||
n++;
|
||||
div = (div + 1) / 2;
|
||||
}
|
||||
|
||||
if (n > 3) {
|
||||
printf("mmc %u error cannot set clock to %u\n",
|
||||
mmchost->mmc_no, hz);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* determine delays */
|
||||
if (hz <= 400000) {
|
||||
oclk_dly = 0;
|
||||
sclk_dly = 0;
|
||||
} else if (hz <= 25000000) {
|
||||
oclk_dly = 0;
|
||||
sclk_dly = 5;
|
||||
#ifdef CONFIG_MACH_SUN9I
|
||||
} else if (hz <= 50000000) {
|
||||
oclk_dly = 5;
|
||||
sclk_dly = 4;
|
||||
} else {
|
||||
/* hz > 50000000 */
|
||||
oclk_dly = 2;
|
||||
sclk_dly = 4;
|
||||
#else
|
||||
} else if (hz <= 50000000) {
|
||||
oclk_dly = 3;
|
||||
sclk_dly = 4;
|
||||
} else {
|
||||
/* hz > 50000000 */
|
||||
oclk_dly = 1;
|
||||
sclk_dly = 4;
|
||||
#endif
|
||||
}
|
||||
|
||||
writel(CCM_MMC_CTRL_ENABLE | pll | CCM_MMC_CTRL_SCLK_DLY(sclk_dly) |
|
||||
CCM_MMC_CTRL_N(n) | CCM_MMC_CTRL_OCLK_DLY(oclk_dly) |
|
||||
CCM_MMC_CTRL_M(div), mmchost->mclkreg);
|
||||
|
||||
debug("mmc %u set mod-clk req %u parent %u n %u m %u rate %u\n",
|
||||
mmchost->mmc_no, hz, pll_hz, 1u << n, div,
|
||||
pll_hz / (1u << n) / div);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_clk_io_on(int sdc_no)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
|
||||
struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
|
||||
|
||||
debug("init mmc %d clock and io\n", sdc_no);
|
||||
|
||||
/* config ahb clock */
|
||||
setbits_le32(&ccm->ahb_gate0, 1 << AHB_GATE_OFFSET_MMC(sdc_no));
|
||||
|
||||
#ifdef CONFIG_SUNXI_GEN_SUN6I
|
||||
/* unassert reset */
|
||||
setbits_le32(&ccm->ahb_reset0_cfg, 1 << AHB_RESET_OFFSET_MMC(sdc_no));
|
||||
#endif
|
||||
#if defined(CONFIG_MACH_SUN9I)
|
||||
/* sun9i has a mmc-common module, also set the gate and reset there */
|
||||
writel(SUNXI_MMC_COMMON_CLK_GATE | SUNXI_MMC_COMMON_RESET,
|
||||
SUNXI_MMC_COMMON_BASE + 4 * sdc_no);
|
||||
#endif
|
||||
|
||||
return mmc_set_mod_clk(mmchost, 24000000);
|
||||
}
|
||||
|
||||
static int mmc_update_clk(struct mmc *mmc)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = mmc->priv;
|
||||
unsigned int cmd;
|
||||
unsigned timeout_msecs = 2000;
|
||||
|
||||
cmd = SUNXI_MMC_CMD_START |
|
||||
SUNXI_MMC_CMD_UPCLK_ONLY |
|
||||
SUNXI_MMC_CMD_WAIT_PRE_OVER;
|
||||
writel(cmd, &mmchost->reg->cmd);
|
||||
while (readl(&mmchost->reg->cmd) & SUNXI_MMC_CMD_START) {
|
||||
if (!timeout_msecs--)
|
||||
return -1;
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
/* clock update sets various irq status bits, clear these */
|
||||
writel(readl(&mmchost->reg->rint), &mmchost->reg->rint);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_config_clock(struct mmc *mmc)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = mmc->priv;
|
||||
unsigned rval = readl(&mmchost->reg->clkcr);
|
||||
|
||||
/* Disable Clock */
|
||||
rval &= ~SUNXI_MMC_CLK_ENABLE;
|
||||
writel(rval, &mmchost->reg->clkcr);
|
||||
if (mmc_update_clk(mmc))
|
||||
return -1;
|
||||
|
||||
/* Set mod_clk to new rate */
|
||||
if (mmc_set_mod_clk(mmchost, mmc->clock))
|
||||
return -1;
|
||||
|
||||
/* Clear internal divider */
|
||||
rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK;
|
||||
writel(rval, &mmchost->reg->clkcr);
|
||||
|
||||
/* Re-enable Clock */
|
||||
rval |= SUNXI_MMC_CLK_ENABLE;
|
||||
writel(rval, &mmchost->reg->clkcr);
|
||||
if (mmc_update_clk(mmc))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sunxi_mmc_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = mmc->priv;
|
||||
|
||||
debug("set ios: bus_width: %x, clock: %d\n",
|
||||
mmc->bus_width, mmc->clock);
|
||||
|
||||
/* Change clock first */
|
||||
if (mmc->clock && mmc_config_clock(mmc) != 0) {
|
||||
mmchost->fatal_err = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Change bus width */
|
||||
if (mmc->bus_width == 8)
|
||||
writel(0x2, &mmchost->reg->width);
|
||||
else if (mmc->bus_width == 4)
|
||||
writel(0x1, &mmchost->reg->width);
|
||||
else
|
||||
writel(0x0, &mmchost->reg->width);
|
||||
}
|
||||
|
||||
static int sunxi_mmc_core_init(struct mmc *mmc)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = mmc->priv;
|
||||
|
||||
/* Reset controller */
|
||||
writel(SUNXI_MMC_GCTRL_RESET, &mmchost->reg->gctrl);
|
||||
udelay(1000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_trans_data_by_cpu(struct mmc *mmc, struct mmc_data *data)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = mmc->priv;
|
||||
const int reading = !!(data->flags & MMC_DATA_READ);
|
||||
const uint32_t status_bit = reading ? SUNXI_MMC_STATUS_FIFO_EMPTY :
|
||||
SUNXI_MMC_STATUS_FIFO_FULL;
|
||||
unsigned i;
|
||||
unsigned *buff = (unsigned int *)(reading ? data->dest : data->src);
|
||||
unsigned byte_cnt = data->blocksize * data->blocks;
|
||||
unsigned timeout_msecs = byte_cnt >> 8;
|
||||
if (timeout_msecs < 2000)
|
||||
timeout_msecs = 2000;
|
||||
|
||||
/* Always read / write data through the CPU */
|
||||
setbits_le32(&mmchost->reg->gctrl, SUNXI_MMC_GCTRL_ACCESS_BY_AHB);
|
||||
|
||||
for (i = 0; i < (byte_cnt >> 2); i++) {
|
||||
while (readl(&mmchost->reg->status) & status_bit) {
|
||||
if (!timeout_msecs--)
|
||||
return -1;
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
if (reading)
|
||||
buff[i] = readl(&mmchost->reg->fifo);
|
||||
else
|
||||
writel(buff[i], &mmchost->reg->fifo);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_rint_wait(struct mmc *mmc, unsigned int timeout_msecs,
|
||||
unsigned int done_bit, const char *what)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = mmc->priv;
|
||||
unsigned int status;
|
||||
|
||||
do {
|
||||
status = readl(&mmchost->reg->rint);
|
||||
if (!timeout_msecs-- ||
|
||||
(status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT)) {
|
||||
debug("%s timeout %x\n", what,
|
||||
status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT);
|
||||
return TIMEOUT;
|
||||
}
|
||||
udelay(1000);
|
||||
} while (!(status & done_bit));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sunxi_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = mmc->priv;
|
||||
unsigned int cmdval = SUNXI_MMC_CMD_START;
|
||||
unsigned int timeout_msecs;
|
||||
int error = 0;
|
||||
unsigned int status = 0;
|
||||
unsigned int bytecnt = 0;
|
||||
|
||||
if (mmchost->fatal_err)
|
||||
return -1;
|
||||
if (cmd->resp_type & MMC_RSP_BUSY)
|
||||
debug("mmc cmd %d check rsp busy\n", cmd->cmdidx);
|
||||
if (cmd->cmdidx == 12)
|
||||
return 0;
|
||||
|
||||
if (!cmd->cmdidx)
|
||||
cmdval |= SUNXI_MMC_CMD_SEND_INIT_SEQ;
|
||||
if (cmd->resp_type & MMC_RSP_PRESENT)
|
||||
cmdval |= SUNXI_MMC_CMD_RESP_EXPIRE;
|
||||
if (cmd->resp_type & MMC_RSP_136)
|
||||
cmdval |= SUNXI_MMC_CMD_LONG_RESPONSE;
|
||||
if (cmd->resp_type & MMC_RSP_CRC)
|
||||
cmdval |= SUNXI_MMC_CMD_CHK_RESPONSE_CRC;
|
||||
|
||||
if (data) {
|
||||
if ((u32)(long)data->dest & 0x3) {
|
||||
error = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
cmdval |= SUNXI_MMC_CMD_DATA_EXPIRE|SUNXI_MMC_CMD_WAIT_PRE_OVER;
|
||||
if (data->flags & MMC_DATA_WRITE)
|
||||
cmdval |= SUNXI_MMC_CMD_WRITE;
|
||||
if (data->blocks > 1)
|
||||
cmdval |= SUNXI_MMC_CMD_AUTO_STOP;
|
||||
writel(data->blocksize, &mmchost->reg->blksz);
|
||||
writel(data->blocks * data->blocksize, &mmchost->reg->bytecnt);
|
||||
}
|
||||
|
||||
debug("mmc %d, cmd %d(0x%08x), arg 0x%08x\n", mmchost->mmc_no,
|
||||
cmd->cmdidx, cmdval | cmd->cmdidx, cmd->cmdarg);
|
||||
writel(cmd->cmdarg, &mmchost->reg->arg);
|
||||
|
||||
if (!data)
|
||||
writel(cmdval | cmd->cmdidx, &mmchost->reg->cmd);
|
||||
|
||||
/*
|
||||
* transfer data and check status
|
||||
* STATREG[2] : FIFO empty
|
||||
* STATREG[3] : FIFO full
|
||||
*/
|
||||
if (data) {
|
||||
int ret = 0;
|
||||
|
||||
bytecnt = data->blocksize * data->blocks;
|
||||
debug("trans data %d bytes\n", bytecnt);
|
||||
writel(cmdval | cmd->cmdidx, &mmchost->reg->cmd);
|
||||
ret = mmc_trans_data_by_cpu(mmc, data);
|
||||
if (ret) {
|
||||
error = readl(&mmchost->reg->rint) & \
|
||||
SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT;
|
||||
error = TIMEOUT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
error = mmc_rint_wait(mmc, 1000, SUNXI_MMC_RINT_COMMAND_DONE, "cmd");
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
if (data) {
|
||||
timeout_msecs = 120;
|
||||
debug("cacl timeout %x msec\n", timeout_msecs);
|
||||
error = mmc_rint_wait(mmc, timeout_msecs,
|
||||
data->blocks > 1 ?
|
||||
SUNXI_MMC_RINT_AUTO_COMMAND_DONE :
|
||||
SUNXI_MMC_RINT_DATA_OVER,
|
||||
"data");
|
||||
if (error)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_BUSY) {
|
||||
timeout_msecs = 2000;
|
||||
do {
|
||||
status = readl(&mmchost->reg->status);
|
||||
if (!timeout_msecs--) {
|
||||
debug("busy timeout\n");
|
||||
error = TIMEOUT;
|
||||
goto out;
|
||||
}
|
||||
udelay(1000);
|
||||
} while (status & SUNXI_MMC_STATUS_CARD_DATA_BUSY);
|
||||
}
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
cmd->response[0] = readl(&mmchost->reg->resp3);
|
||||
cmd->response[1] = readl(&mmchost->reg->resp2);
|
||||
cmd->response[2] = readl(&mmchost->reg->resp1);
|
||||
cmd->response[3] = readl(&mmchost->reg->resp0);
|
||||
debug("mmc resp 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||||
cmd->response[3], cmd->response[2],
|
||||
cmd->response[1], cmd->response[0]);
|
||||
} else {
|
||||
cmd->response[0] = readl(&mmchost->reg->resp0);
|
||||
debug("mmc resp 0x%08x\n", cmd->response[0]);
|
||||
}
|
||||
out:
|
||||
if (error < 0) {
|
||||
writel(SUNXI_MMC_GCTRL_RESET, &mmchost->reg->gctrl);
|
||||
mmc_update_clk(mmc);
|
||||
}
|
||||
writel(0xffffffff, &mmchost->reg->rint);
|
||||
writel(readl(&mmchost->reg->gctrl) | SUNXI_MMC_GCTRL_FIFO_RESET,
|
||||
&mmchost->reg->gctrl);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int sunxi_mmc_getcd(struct mmc *mmc)
|
||||
{
|
||||
struct sunxi_mmc_host *mmchost = mmc->priv;
|
||||
int cd_pin;
|
||||
|
||||
cd_pin = sunxi_mmc_getcd_gpio(mmchost->mmc_no);
|
||||
if (cd_pin < 0)
|
||||
return 1;
|
||||
|
||||
return !gpio_get_value(cd_pin);
|
||||
}
|
||||
|
||||
int sunxi_mmc_has_egon_boot_signature(struct mmc *mmc)
|
||||
{
|
||||
char *buf = malloc(512);
|
||||
int valid_signature = 0;
|
||||
|
||||
if (buf == NULL)
|
||||
panic("Failed to allocate memory\n");
|
||||
|
||||
if (mmc_getcd(mmc) && mmc_init(mmc) == 0 &&
|
||||
mmc->block_dev.block_read(&mmc->block_dev, 16, 1, buf) == 1 &&
|
||||
strncmp(&buf[4], "eGON.BT0", 8) == 0)
|
||||
valid_signature = 1;
|
||||
|
||||
free(buf);
|
||||
return valid_signature;
|
||||
}
|
||||
|
||||
static const struct mmc_ops sunxi_mmc_ops = {
|
||||
.send_cmd = sunxi_mmc_send_cmd,
|
||||
.set_ios = sunxi_mmc_set_ios,
|
||||
.init = sunxi_mmc_core_init,
|
||||
.getcd = sunxi_mmc_getcd,
|
||||
};
|
||||
|
||||
struct mmc *sunxi_mmc_init(int sdc_no)
|
||||
{
|
||||
struct mmc_config *cfg = &mmc_host[sdc_no].cfg;
|
||||
|
||||
memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));
|
||||
|
||||
cfg->name = "SUNXI SD/MMC";
|
||||
cfg->ops = &sunxi_mmc_ops;
|
||||
|
||||
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
cfg->host_caps = MMC_MODE_4BIT;
|
||||
#ifdef CONFIG_MACH_SUN50I
|
||||
if (sdc_no == 2)
|
||||
cfg->host_caps = MMC_MODE_8BIT;
|
||||
#endif
|
||||
cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
|
||||
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
|
||||
cfg->f_min = 400000;
|
||||
cfg->f_max = 52000000;
|
||||
|
||||
if (mmc_resource_init(sdc_no) != 0)
|
||||
return NULL;
|
||||
|
||||
mmc_clk_io_on(sdc_no);
|
||||
|
||||
return mmc_create(cfg, &mmc_host[sdc_no]);
|
||||
}
|
||||
740
u-boot/drivers/mmc/tegra_mmc.c
Normal file
740
u-boot/drivers/mmc/tegra_mmc.c
Normal file
@@ -0,0 +1,740 @@
|
||||
/*
|
||||
* (C) Copyright 2009 SAMSUNG Electronics
|
||||
* Minkyu Kang <mk7.kang@samsung.com>
|
||||
* Jaehoon Chung <jh80.chung@samsung.com>
|
||||
* Portions Copyright 2011-2015 NVIDIA Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <bouncebuf.h>
|
||||
#include <common.h>
|
||||
#include <asm/gpio.h>
|
||||
#include <asm/io.h>
|
||||
#ifndef CONFIG_TEGRA186
|
||||
#include <asm/arch/clock.h>
|
||||
#include <asm/arch-tegra/clk_rst.h>
|
||||
#endif
|
||||
#include <asm/arch-tegra/mmc.h>
|
||||
#include <asm/arch-tegra/tegra_mmc.h>
|
||||
#include <mmc.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
struct mmc_host mmc_host[CONFIG_SYS_MMC_MAX_DEVICE];
|
||||
|
||||
#if !CONFIG_IS_ENABLED(OF_CONTROL)
|
||||
#error "Please enable device tree support to use this driver"
|
||||
#endif
|
||||
|
||||
static void mmc_set_power(struct mmc_host *host, unsigned short power)
|
||||
{
|
||||
u8 pwr = 0;
|
||||
debug("%s: power = %x\n", __func__, power);
|
||||
|
||||
if (power != (unsigned short)-1) {
|
||||
switch (1 << power) {
|
||||
case MMC_VDD_165_195:
|
||||
pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V1_8;
|
||||
break;
|
||||
case MMC_VDD_29_30:
|
||||
case MMC_VDD_30_31:
|
||||
pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V3_0;
|
||||
break;
|
||||
case MMC_VDD_32_33:
|
||||
case MMC_VDD_33_34:
|
||||
pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V3_3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug("%s: pwr = %X\n", __func__, pwr);
|
||||
|
||||
/* Set the bus voltage first (if any) */
|
||||
writeb(pwr, &host->reg->pwrcon);
|
||||
if (pwr == 0)
|
||||
return;
|
||||
|
||||
/* Now enable bus power */
|
||||
pwr |= TEGRA_MMC_PWRCTL_SD_BUS_POWER;
|
||||
writeb(pwr, &host->reg->pwrcon);
|
||||
}
|
||||
|
||||
static void mmc_prepare_data(struct mmc_host *host, struct mmc_data *data,
|
||||
struct bounce_buffer *bbstate)
|
||||
{
|
||||
unsigned char ctrl;
|
||||
|
||||
|
||||
debug("buf: %p (%p), data->blocks: %u, data->blocksize: %u\n",
|
||||
bbstate->bounce_buffer, bbstate->user_buffer, data->blocks,
|
||||
data->blocksize);
|
||||
|
||||
writel((u32)(unsigned long)bbstate->bounce_buffer, &host->reg->sysad);
|
||||
/*
|
||||
* DMASEL[4:3]
|
||||
* 00 = Selects SDMA
|
||||
* 01 = Reserved
|
||||
* 10 = Selects 32-bit Address ADMA2
|
||||
* 11 = Selects 64-bit Address ADMA2
|
||||
*/
|
||||
ctrl = readb(&host->reg->hostctl);
|
||||
ctrl &= ~TEGRA_MMC_HOSTCTL_DMASEL_MASK;
|
||||
ctrl |= TEGRA_MMC_HOSTCTL_DMASEL_SDMA;
|
||||
writeb(ctrl, &host->reg->hostctl);
|
||||
|
||||
/* We do not handle DMA boundaries, so set it to max (512 KiB) */
|
||||
writew((7 << 12) | (data->blocksize & 0xFFF), &host->reg->blksize);
|
||||
writew(data->blocks, &host->reg->blkcnt);
|
||||
}
|
||||
|
||||
static void mmc_set_transfer_mode(struct mmc_host *host, struct mmc_data *data)
|
||||
{
|
||||
unsigned short mode;
|
||||
debug(" mmc_set_transfer_mode called\n");
|
||||
/*
|
||||
* TRNMOD
|
||||
* MUL1SIN0[5] : Multi/Single Block Select
|
||||
* RD1WT0[4] : Data Transfer Direction Select
|
||||
* 1 = read
|
||||
* 0 = write
|
||||
* ENACMD12[2] : Auto CMD12 Enable
|
||||
* ENBLKCNT[1] : Block Count Enable
|
||||
* ENDMA[0] : DMA Enable
|
||||
*/
|
||||
mode = (TEGRA_MMC_TRNMOD_DMA_ENABLE |
|
||||
TEGRA_MMC_TRNMOD_BLOCK_COUNT_ENABLE);
|
||||
|
||||
if (data->blocks > 1)
|
||||
mode |= TEGRA_MMC_TRNMOD_MULTI_BLOCK_SELECT;
|
||||
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
mode |= TEGRA_MMC_TRNMOD_DATA_XFER_DIR_SEL_READ;
|
||||
|
||||
writew(mode, &host->reg->trnmod);
|
||||
}
|
||||
|
||||
static int mmc_wait_inhibit(struct mmc_host *host,
|
||||
struct mmc_cmd *cmd,
|
||||
struct mmc_data *data,
|
||||
unsigned int timeout)
|
||||
{
|
||||
/*
|
||||
* PRNSTS
|
||||
* CMDINHDAT[1] : Command Inhibit (DAT)
|
||||
* CMDINHCMD[0] : Command Inhibit (CMD)
|
||||
*/
|
||||
unsigned int mask = TEGRA_MMC_PRNSTS_CMD_INHIBIT_CMD;
|
||||
|
||||
/*
|
||||
* We shouldn't wait for data inhibit for stop commands, even
|
||||
* though they might use busy signaling
|
||||
*/
|
||||
if ((data == NULL) && (cmd->resp_type & MMC_RSP_BUSY))
|
||||
mask |= TEGRA_MMC_PRNSTS_CMD_INHIBIT_DAT;
|
||||
|
||||
while (readl(&host->reg->prnsts) & mask) {
|
||||
if (timeout == 0) {
|
||||
printf("%s: timeout error\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
timeout--;
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mmc_send_cmd_bounced(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data, struct bounce_buffer *bbstate)
|
||||
{
|
||||
struct mmc_host *host = mmc->priv;
|
||||
int flags, i;
|
||||
int result;
|
||||
unsigned int mask = 0;
|
||||
unsigned int retry = 0x100000;
|
||||
debug(" mmc_send_cmd called\n");
|
||||
|
||||
result = mmc_wait_inhibit(host, cmd, data, 10 /* ms */);
|
||||
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
if (data)
|
||||
mmc_prepare_data(host, data, bbstate);
|
||||
|
||||
debug("cmd->arg: %08x\n", cmd->cmdarg);
|
||||
writel(cmd->cmdarg, &host->reg->argument);
|
||||
|
||||
if (data)
|
||||
mmc_set_transfer_mode(host, data);
|
||||
|
||||
if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY))
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* CMDREG
|
||||
* CMDIDX[13:8] : Command index
|
||||
* DATAPRNT[5] : Data Present Select
|
||||
* ENCMDIDX[4] : Command Index Check Enable
|
||||
* ENCMDCRC[3] : Command CRC Check Enable
|
||||
* RSPTYP[1:0]
|
||||
* 00 = No Response
|
||||
* 01 = Length 136
|
||||
* 10 = Length 48
|
||||
* 11 = Length 48 Check busy after response
|
||||
*/
|
||||
if (!(cmd->resp_type & MMC_RSP_PRESENT))
|
||||
flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_NO_RESPONSE;
|
||||
else if (cmd->resp_type & MMC_RSP_136)
|
||||
flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_136;
|
||||
else if (cmd->resp_type & MMC_RSP_BUSY)
|
||||
flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_48_BUSY;
|
||||
else
|
||||
flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_48;
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_CRC)
|
||||
flags |= TEGRA_MMC_TRNMOD_CMD_CRC_CHECK;
|
||||
if (cmd->resp_type & MMC_RSP_OPCODE)
|
||||
flags |= TEGRA_MMC_TRNMOD_CMD_INDEX_CHECK;
|
||||
if (data)
|
||||
flags |= TEGRA_MMC_TRNMOD_DATA_PRESENT_SELECT_DATA_TRANSFER;
|
||||
|
||||
debug("cmd: %d\n", cmd->cmdidx);
|
||||
|
||||
writew((cmd->cmdidx << 8) | flags, &host->reg->cmdreg);
|
||||
|
||||
for (i = 0; i < retry; i++) {
|
||||
mask = readl(&host->reg->norintsts);
|
||||
/* Command Complete */
|
||||
if (mask & TEGRA_MMC_NORINTSTS_CMD_COMPLETE) {
|
||||
if (!data)
|
||||
writel(mask, &host->reg->norintsts);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == retry) {
|
||||
printf("%s: waiting for status update\n", __func__);
|
||||
writel(mask, &host->reg->norintsts);
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
if (mask & TEGRA_MMC_NORINTSTS_CMD_TIMEOUT) {
|
||||
/* Timeout Error */
|
||||
debug("timeout: %08x cmd %d\n", mask, cmd->cmdidx);
|
||||
writel(mask, &host->reg->norintsts);
|
||||
return TIMEOUT;
|
||||
} else if (mask & TEGRA_MMC_NORINTSTS_ERR_INTERRUPT) {
|
||||
/* Error Interrupt */
|
||||
debug("error: %08x cmd %d\n", mask, cmd->cmdidx);
|
||||
writel(mask, &host->reg->norintsts);
|
||||
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. */
|
||||
for (i = 0; i < 4; i++) {
|
||||
unsigned long offset =
|
||||
(unsigned long)(&host->reg->rspreg3 - i);
|
||||
cmd->response[i] = readl(offset) << 8;
|
||||
|
||||
if (i != 3) {
|
||||
cmd->response[i] |=
|
||||
readb(offset - 1);
|
||||
}
|
||||
debug("cmd->resp[%d]: %08x\n",
|
||||
i, cmd->response[i]);
|
||||
}
|
||||
} else if (cmd->resp_type & MMC_RSP_BUSY) {
|
||||
for (i = 0; i < retry; i++) {
|
||||
/* PRNTDATA[23:20] : DAT[3:0] Line Signal */
|
||||
if (readl(&host->reg->prnsts)
|
||||
& (1 << 20)) /* DAT[0] */
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == retry) {
|
||||
printf("%s: card is still busy\n", __func__);
|
||||
writel(mask, &host->reg->norintsts);
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
cmd->response[0] = readl(&host->reg->rspreg0);
|
||||
debug("cmd->resp[0]: %08x\n", cmd->response[0]);
|
||||
} else {
|
||||
cmd->response[0] = readl(&host->reg->rspreg0);
|
||||
debug("cmd->resp[0]: %08x\n", cmd->response[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
unsigned long start = get_timer(0);
|
||||
|
||||
while (1) {
|
||||
mask = readl(&host->reg->norintsts);
|
||||
|
||||
if (mask & TEGRA_MMC_NORINTSTS_ERR_INTERRUPT) {
|
||||
/* Error Interrupt */
|
||||
writel(mask, &host->reg->norintsts);
|
||||
printf("%s: error during transfer: 0x%08x\n",
|
||||
__func__, mask);
|
||||
return -1;
|
||||
} else if (mask & TEGRA_MMC_NORINTSTS_DMA_INTERRUPT) {
|
||||
/*
|
||||
* DMA Interrupt, restart the transfer where
|
||||
* it was interrupted.
|
||||
*/
|
||||
unsigned int address = readl(&host->reg->sysad);
|
||||
|
||||
debug("DMA end\n");
|
||||
writel(TEGRA_MMC_NORINTSTS_DMA_INTERRUPT,
|
||||
&host->reg->norintsts);
|
||||
writel(address, &host->reg->sysad);
|
||||
} else if (mask & TEGRA_MMC_NORINTSTS_XFER_COMPLETE) {
|
||||
/* Transfer Complete */
|
||||
debug("r/w is done\n");
|
||||
break;
|
||||
} else if (get_timer(start) > 8000UL) {
|
||||
writel(mask, &host->reg->norintsts);
|
||||
printf("%s: MMC Timeout\n"
|
||||
" Interrupt status 0x%08x\n"
|
||||
" Interrupt status enable 0x%08x\n"
|
||||
" Interrupt signal enable 0x%08x\n"
|
||||
" Present status 0x%08x\n",
|
||||
__func__, mask,
|
||||
readl(&host->reg->norintstsen),
|
||||
readl(&host->reg->norintsigen),
|
||||
readl(&host->reg->prnsts));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
writel(mask, &host->reg->norintsts);
|
||||
}
|
||||
|
||||
udelay(1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
void *buf;
|
||||
unsigned int bbflags;
|
||||
size_t len;
|
||||
struct bounce_buffer bbstate;
|
||||
int ret;
|
||||
|
||||
if (data) {
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
buf = data->dest;
|
||||
bbflags = GEN_BB_WRITE;
|
||||
} else {
|
||||
buf = (void *)data->src;
|
||||
bbflags = GEN_BB_READ;
|
||||
}
|
||||
len = data->blocks * data->blocksize;
|
||||
|
||||
bounce_buffer_start(&bbstate, buf, len, bbflags);
|
||||
}
|
||||
|
||||
ret = mmc_send_cmd_bounced(mmc, cmd, data, &bbstate);
|
||||
|
||||
if (data)
|
||||
bounce_buffer_stop(&bbstate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mmc_change_clock(struct mmc_host *host, uint clock)
|
||||
{
|
||||
int div;
|
||||
unsigned short clk;
|
||||
unsigned long timeout;
|
||||
|
||||
debug(" mmc_change_clock called\n");
|
||||
|
||||
/*
|
||||
* Change Tegra SDMMCx clock divisor here. Source is PLLP_OUT0
|
||||
*/
|
||||
if (clock == 0)
|
||||
goto out;
|
||||
#ifndef CONFIG_TEGRA186
|
||||
clock_adjust_periph_pll_div(host->mmc_id, CLOCK_ID_PERIPH, clock,
|
||||
&div);
|
||||
#else
|
||||
div = (20000000 + clock - 1) / clock;
|
||||
#endif
|
||||
debug("div = %d\n", div);
|
||||
|
||||
writew(0, &host->reg->clkcon);
|
||||
|
||||
/*
|
||||
* CLKCON
|
||||
* SELFREQ[15:8] : base clock divided by value
|
||||
* ENSDCLK[2] : SD Clock Enable
|
||||
* STBLINTCLK[1] : Internal Clock Stable
|
||||
* ENINTCLK[0] : Internal Clock Enable
|
||||
*/
|
||||
div >>= 1;
|
||||
clk = ((div << TEGRA_MMC_CLKCON_SDCLK_FREQ_SEL_SHIFT) |
|
||||
TEGRA_MMC_CLKCON_INTERNAL_CLOCK_ENABLE);
|
||||
writew(clk, &host->reg->clkcon);
|
||||
|
||||
/* Wait max 10 ms */
|
||||
timeout = 10;
|
||||
while (!(readw(&host->reg->clkcon) &
|
||||
TEGRA_MMC_CLKCON_INTERNAL_CLOCK_STABLE)) {
|
||||
if (timeout == 0) {
|
||||
printf("%s: timeout error\n", __func__);
|
||||
return;
|
||||
}
|
||||
timeout--;
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
clk |= TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE;
|
||||
writew(clk, &host->reg->clkcon);
|
||||
|
||||
debug("mmc_change_clock: clkcon = %08X\n", clk);
|
||||
|
||||
out:
|
||||
host->clock = clock;
|
||||
}
|
||||
|
||||
static void tegra_mmc_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct mmc_host *host = mmc->priv;
|
||||
unsigned char ctrl;
|
||||
debug(" mmc_set_ios called\n");
|
||||
|
||||
debug("bus_width: %x, clock: %d\n", mmc->bus_width, mmc->clock);
|
||||
|
||||
/* Change clock first */
|
||||
mmc_change_clock(host, mmc->clock);
|
||||
|
||||
ctrl = readb(&host->reg->hostctl);
|
||||
|
||||
/*
|
||||
* WIDE8[5]
|
||||
* 0 = Depend on WIDE4
|
||||
* 1 = 8-bit mode
|
||||
* WIDE4[1]
|
||||
* 1 = 4-bit mode
|
||||
* 0 = 1-bit mode
|
||||
*/
|
||||
if (mmc->bus_width == 8)
|
||||
ctrl |= (1 << 5);
|
||||
else if (mmc->bus_width == 4)
|
||||
ctrl |= (1 << 1);
|
||||
else
|
||||
ctrl &= ~(1 << 1);
|
||||
|
||||
writeb(ctrl, &host->reg->hostctl);
|
||||
debug("mmc_set_ios: hostctl = %08X\n", ctrl);
|
||||
}
|
||||
|
||||
static void mmc_reset(struct mmc_host *host, struct mmc *mmc)
|
||||
{
|
||||
unsigned int timeout;
|
||||
debug(" mmc_reset called\n");
|
||||
|
||||
/*
|
||||
* RSTALL[0] : Software reset for all
|
||||
* 1 = reset
|
||||
* 0 = work
|
||||
*/
|
||||
writeb(TEGRA_MMC_SWRST_SW_RESET_FOR_ALL, &host->reg->swrst);
|
||||
|
||||
host->clock = 0;
|
||||
|
||||
/* Wait max 100 ms */
|
||||
timeout = 100;
|
||||
|
||||
/* hw clears the bit when it's done */
|
||||
while (readb(&host->reg->swrst) & TEGRA_MMC_SWRST_SW_RESET_FOR_ALL) {
|
||||
if (timeout == 0) {
|
||||
printf("%s: timeout error\n", __func__);
|
||||
return;
|
||||
}
|
||||
timeout--;
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
/* Set SD bus voltage & enable bus power */
|
||||
mmc_set_power(host, fls(mmc->cfg->voltages) - 1);
|
||||
debug("%s: power control = %02X, host control = %02X\n", __func__,
|
||||
readb(&host->reg->pwrcon), readb(&host->reg->hostctl));
|
||||
|
||||
/* Make sure SDIO pads are set up */
|
||||
pad_init_mmc(host);
|
||||
}
|
||||
|
||||
static int tegra_mmc_core_init(struct mmc *mmc)
|
||||
{
|
||||
struct mmc_host *host = mmc->priv;
|
||||
unsigned int mask;
|
||||
debug(" mmc_core_init called\n");
|
||||
|
||||
mmc_reset(host, mmc);
|
||||
|
||||
host->version = readw(&host->reg->hcver);
|
||||
debug("host version = %x\n", host->version);
|
||||
|
||||
/* mask all */
|
||||
writel(0xffffffff, &host->reg->norintstsen);
|
||||
writel(0xffffffff, &host->reg->norintsigen);
|
||||
|
||||
writeb(0xe, &host->reg->timeoutcon); /* TMCLK * 2^27 */
|
||||
/*
|
||||
* NORMAL Interrupt Status Enable Register init
|
||||
* [5] ENSTABUFRDRDY : Buffer Read Ready Status Enable
|
||||
* [4] ENSTABUFWTRDY : Buffer write Ready Status Enable
|
||||
* [3] ENSTADMAINT : DMA boundary interrupt
|
||||
* [1] ENSTASTANSCMPLT : Transfre Complete Status Enable
|
||||
* [0] ENSTACMDCMPLT : Command Complete Status Enable
|
||||
*/
|
||||
mask = readl(&host->reg->norintstsen);
|
||||
mask &= ~(0xffff);
|
||||
mask |= (TEGRA_MMC_NORINTSTSEN_CMD_COMPLETE |
|
||||
TEGRA_MMC_NORINTSTSEN_XFER_COMPLETE |
|
||||
TEGRA_MMC_NORINTSTSEN_DMA_INTERRUPT |
|
||||
TEGRA_MMC_NORINTSTSEN_BUFFER_WRITE_READY |
|
||||
TEGRA_MMC_NORINTSTSEN_BUFFER_READ_READY);
|
||||
writel(mask, &host->reg->norintstsen);
|
||||
|
||||
/*
|
||||
* NORMAL Interrupt Signal Enable Register init
|
||||
* [1] ENSTACMDCMPLT : Transfer Complete Signal Enable
|
||||
*/
|
||||
mask = readl(&host->reg->norintsigen);
|
||||
mask &= ~(0xffff);
|
||||
mask |= TEGRA_MMC_NORINTSIGEN_XFER_COMPLETE;
|
||||
writel(mask, &host->reg->norintsigen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_mmc_getcd(struct mmc *mmc)
|
||||
{
|
||||
struct mmc_host *host = mmc->priv;
|
||||
|
||||
debug("tegra_mmc_getcd called\n");
|
||||
|
||||
if (dm_gpio_is_valid(&host->cd_gpio))
|
||||
return dm_gpio_get_value(&host->cd_gpio);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct mmc_ops tegra_mmc_ops = {
|
||||
.send_cmd = tegra_mmc_send_cmd,
|
||||
.set_ios = tegra_mmc_set_ios,
|
||||
.init = tegra_mmc_core_init,
|
||||
.getcd = tegra_mmc_getcd,
|
||||
};
|
||||
|
||||
static int do_mmc_init(int dev_index, bool removable)
|
||||
{
|
||||
struct mmc_host *host;
|
||||
struct mmc *mmc;
|
||||
|
||||
/* DT should have been read & host config filled in */
|
||||
host = &mmc_host[dev_index];
|
||||
if (!host->enabled)
|
||||
return -1;
|
||||
|
||||
debug(" do_mmc_init: index %d, bus width %d pwr_gpio %d cd_gpio %d\n",
|
||||
dev_index, host->width, gpio_get_number(&host->pwr_gpio),
|
||||
gpio_get_number(&host->cd_gpio));
|
||||
|
||||
host->clock = 0;
|
||||
#ifndef CONFIG_TEGRA186
|
||||
clock_start_periph_pll(host->mmc_id, CLOCK_ID_PERIPH, 20000000);
|
||||
#endif
|
||||
|
||||
if (dm_gpio_is_valid(&host->pwr_gpio))
|
||||
dm_gpio_set_value(&host->pwr_gpio, 1);
|
||||
|
||||
memset(&host->cfg, 0, sizeof(host->cfg));
|
||||
|
||||
host->cfg.name = "Tegra SD/MMC";
|
||||
host->cfg.ops = &tegra_mmc_ops;
|
||||
|
||||
host->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
|
||||
host->cfg.host_caps = 0;
|
||||
if (host->width == 8)
|
||||
host->cfg.host_caps |= MMC_MODE_8BIT;
|
||||
if (host->width >= 4)
|
||||
host->cfg.host_caps |= MMC_MODE_4BIT;
|
||||
host->cfg.host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
|
||||
|
||||
/*
|
||||
* min freq is for card identification, and is the highest
|
||||
* low-speed SDIO card frequency (actually 400KHz)
|
||||
* max freq is highest HS eMMC clock as per the SD/MMC spec
|
||||
* (actually 52MHz)
|
||||
*/
|
||||
host->cfg.f_min = 375000;
|
||||
#ifndef CONFIG_TEGRA186
|
||||
host->cfg.f_max = 48000000;
|
||||
#else
|
||||
host->cfg.f_max = 375000;
|
||||
#endif
|
||||
|
||||
host->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
||||
|
||||
mmc = mmc_create(&host->cfg, host);
|
||||
mmc->block_dev.removable = removable;
|
||||
if (mmc == NULL)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the host address and peripheral ID for a node.
|
||||
*
|
||||
* @param blob fdt blob
|
||||
* @param node Device index (0-3)
|
||||
* @param host Structure to fill in (reg, width, mmc_id)
|
||||
*/
|
||||
static int mmc_get_config(const void *blob, int node, struct mmc_host *host,
|
||||
bool *removablep)
|
||||
{
|
||||
debug("%s: node = %d\n", __func__, node);
|
||||
|
||||
host->enabled = fdtdec_get_is_enabled(blob, node);
|
||||
|
||||
host->reg = (struct tegra_mmc *)fdtdec_get_addr(blob, node, "reg");
|
||||
if ((fdt_addr_t)host->reg == FDT_ADDR_T_NONE) {
|
||||
debug("%s: no sdmmc base reg info found\n", __func__);
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_TEGRA186
|
||||
host->mmc_id = clock_decode_periph_id(blob, node);
|
||||
if (host->mmc_id == PERIPH_ID_NONE) {
|
||||
debug("%s: could not decode periph id\n", __func__);
|
||||
return -FDT_ERR_NOTFOUND;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* NOTE: mmc->bus_width is determined by mmc.c dynamically.
|
||||
* TBD: Override it with this value?
|
||||
*/
|
||||
host->width = fdtdec_get_int(blob, node, "bus-width", 0);
|
||||
if (!host->width)
|
||||
debug("%s: no sdmmc width found\n", __func__);
|
||||
|
||||
/* These GPIOs are optional */
|
||||
gpio_request_by_name_nodev(blob, node, "cd-gpios", 0, &host->cd_gpio,
|
||||
GPIOD_IS_IN);
|
||||
gpio_request_by_name_nodev(blob, node, "wp-gpios", 0, &host->wp_gpio,
|
||||
GPIOD_IS_IN);
|
||||
gpio_request_by_name_nodev(blob, node, "power-gpios", 0,
|
||||
&host->pwr_gpio, GPIOD_IS_OUT);
|
||||
*removablep = !fdtdec_get_bool(blob, node, "non-removable");
|
||||
|
||||
debug("%s: found controller at %p, width = %d, periph_id = %d\n",
|
||||
__func__, host->reg, host->width,
|
||||
#ifndef CONFIG_TEGRA186
|
||||
host->mmc_id
|
||||
#else
|
||||
-1
|
||||
#endif
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process a list of nodes, adding them to our list of SDMMC ports.
|
||||
*
|
||||
* @param blob fdt blob
|
||||
* @param node_list list of nodes to process (any <=0 are ignored)
|
||||
* @param count number of nodes to process
|
||||
* @return 0 if ok, -1 on error
|
||||
*/
|
||||
static int process_nodes(const void *blob, int node_list[], int count)
|
||||
{
|
||||
struct mmc_host *host;
|
||||
bool removable;
|
||||
int i, node;
|
||||
|
||||
debug("%s: count = %d\n", __func__, count);
|
||||
|
||||
/* build mmc_host[] for each controller */
|
||||
for (i = 0; i < count; i++) {
|
||||
node = node_list[i];
|
||||
if (node <= 0)
|
||||
continue;
|
||||
|
||||
host = &mmc_host[i];
|
||||
host->id = i;
|
||||
|
||||
if (mmc_get_config(blob, node, host, &removable)) {
|
||||
printf("%s: failed to decode dev %d\n", __func__, i);
|
||||
return -1;
|
||||
}
|
||||
do_mmc_init(i, removable);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tegra_mmc_init(void)
|
||||
{
|
||||
int node_list[CONFIG_SYS_MMC_MAX_DEVICE], count;
|
||||
const void *blob = gd->fdt_blob;
|
||||
debug("%s entry\n", __func__);
|
||||
|
||||
/* See if any Tegra186 MMC controllers are present */
|
||||
count = fdtdec_find_aliases_for_id(blob, "sdhci",
|
||||
COMPAT_NVIDIA_TEGRA186_SDMMC, node_list,
|
||||
CONFIG_SYS_MMC_MAX_DEVICE);
|
||||
debug("%s: count of Tegra186 sdhci nodes is %d\n", __func__, count);
|
||||
if (process_nodes(blob, node_list, count)) {
|
||||
printf("%s: Error processing T186 mmc node(s)!\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* See if any Tegra210 MMC controllers are present */
|
||||
count = fdtdec_find_aliases_for_id(blob, "sdhci",
|
||||
COMPAT_NVIDIA_TEGRA210_SDMMC, node_list,
|
||||
CONFIG_SYS_MMC_MAX_DEVICE);
|
||||
debug("%s: count of Tegra210 sdhci nodes is %d\n", __func__, count);
|
||||
if (process_nodes(blob, node_list, count)) {
|
||||
printf("%s: Error processing T210 mmc node(s)!\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* See if any Tegra124 MMC controllers are present */
|
||||
count = fdtdec_find_aliases_for_id(blob, "sdhci",
|
||||
COMPAT_NVIDIA_TEGRA124_SDMMC, node_list,
|
||||
CONFIG_SYS_MMC_MAX_DEVICE);
|
||||
debug("%s: count of Tegra124 sdhci nodes is %d\n", __func__, count);
|
||||
if (process_nodes(blob, node_list, count)) {
|
||||
printf("%s: Error processing T124 mmc node(s)!\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* See if any Tegra30 MMC controllers are present */
|
||||
count = fdtdec_find_aliases_for_id(blob, "sdhci",
|
||||
COMPAT_NVIDIA_TEGRA30_SDMMC, node_list,
|
||||
CONFIG_SYS_MMC_MAX_DEVICE);
|
||||
debug("%s: count of T30 sdhci nodes is %d\n", __func__, count);
|
||||
if (process_nodes(blob, node_list, count)) {
|
||||
printf("%s: Error processing T30 mmc node(s)!\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Now look for any Tegra20 MMC controllers */
|
||||
count = fdtdec_find_aliases_for_id(blob, "sdhci",
|
||||
COMPAT_NVIDIA_TEGRA20_SDMMC, node_list,
|
||||
CONFIG_SYS_MMC_MAX_DEVICE);
|
||||
debug("%s: count of T20 sdhci nodes is %d\n", __func__, count);
|
||||
if (process_nodes(blob, node_list, count)) {
|
||||
printf("%s: Error processing T20 mmc node(s)!\n", __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
756
u-boot/drivers/mmc/uniphier-sd.c
Normal file
756
u-boot/drivers/mmc/uniphier-sd.c
Normal file
@@ -0,0 +1,756 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Masahiro Yamada <yamada.masahiro@socionext.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <fdtdec.h>
|
||||
#include <mapmem.h>
|
||||
#include <mmc.h>
|
||||
#include <dm/device.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <asm/dma-mapping.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
#define UNIPHIER_SD_CMD 0x000 /* command */
|
||||
#define UNIPHIER_SD_CMD_NOSTOP BIT(14) /* No automatic CMD12 issue */
|
||||
#define UNIPHIER_SD_CMD_MULTI BIT(13) /* multiple block transfer */
|
||||
#define UNIPHIER_SD_CMD_RD BIT(12) /* 1: read, 0: write */
|
||||
#define UNIPHIER_SD_CMD_DATA BIT(11) /* data transfer */
|
||||
#define UNIPHIER_SD_CMD_APP BIT(6) /* ACMD preceded by CMD55 */
|
||||
#define UNIPHIER_SD_CMD_NORMAL (0 << 8)/* auto-detect of resp-type */
|
||||
#define UNIPHIER_SD_CMD_RSP_NONE (3 << 8)/* response: none */
|
||||
#define UNIPHIER_SD_CMD_RSP_R1 (4 << 8)/* response: R1, R5, R6, R7 */
|
||||
#define UNIPHIER_SD_CMD_RSP_R1B (5 << 8)/* response: R1b, R5b */
|
||||
#define UNIPHIER_SD_CMD_RSP_R2 (6 << 8)/* response: R2 */
|
||||
#define UNIPHIER_SD_CMD_RSP_R3 (7 << 8)/* response: R3, R4 */
|
||||
#define UNIPHIER_SD_ARG 0x008 /* command argument */
|
||||
#define UNIPHIER_SD_STOP 0x010 /* stop action control */
|
||||
#define UNIPHIER_SD_STOP_SEC BIT(8) /* use sector count */
|
||||
#define UNIPHIER_SD_STOP_STP BIT(0) /* issue CMD12 */
|
||||
#define UNIPHIER_SD_SECCNT 0x014 /* sector counter */
|
||||
#define UNIPHIER_SD_RSP10 0x018 /* response[39:8] */
|
||||
#define UNIPHIER_SD_RSP32 0x020 /* response[71:40] */
|
||||
#define UNIPHIER_SD_RSP54 0x028 /* response[103:72] */
|
||||
#define UNIPHIER_SD_RSP76 0x030 /* response[127:104] */
|
||||
#define UNIPHIER_SD_INFO1 0x038 /* IRQ status 1 */
|
||||
#define UNIPHIER_SD_INFO1_CD BIT(5) /* state of card detect */
|
||||
#define UNIPHIER_SD_INFO1_INSERT BIT(4) /* card inserted */
|
||||
#define UNIPHIER_SD_INFO1_REMOVE BIT(3) /* card removed */
|
||||
#define UNIPHIER_SD_INFO1_CMP BIT(2) /* data complete */
|
||||
#define UNIPHIER_SD_INFO1_RSP BIT(0) /* response complete */
|
||||
#define UNIPHIER_SD_INFO2 0x03c /* IRQ status 2 */
|
||||
#define UNIPHIER_SD_INFO2_ERR_ILA BIT(15) /* illegal access err */
|
||||
#define UNIPHIER_SD_INFO2_CBSY BIT(14) /* command busy */
|
||||
#define UNIPHIER_SD_INFO2_BWE BIT(9) /* write buffer ready */
|
||||
#define UNIPHIER_SD_INFO2_BRE BIT(8) /* read buffer ready */
|
||||
#define UNIPHIER_SD_INFO2_DAT0 BIT(7) /* SDDAT0 */
|
||||
#define UNIPHIER_SD_INFO2_ERR_RTO BIT(6) /* response time out */
|
||||
#define UNIPHIER_SD_INFO2_ERR_ILR BIT(5) /* illegal read err */
|
||||
#define UNIPHIER_SD_INFO2_ERR_ILW BIT(4) /* illegal write err */
|
||||
#define UNIPHIER_SD_INFO2_ERR_TO BIT(3) /* time out error */
|
||||
#define UNIPHIER_SD_INFO2_ERR_END BIT(2) /* END bit error */
|
||||
#define UNIPHIER_SD_INFO2_ERR_CRC BIT(1) /* CRC error */
|
||||
#define UNIPHIER_SD_INFO2_ERR_IDX BIT(0) /* cmd index error */
|
||||
#define UNIPHIER_SD_INFO1_MASK 0x040
|
||||
#define UNIPHIER_SD_INFO2_MASK 0x044
|
||||
#define UNIPHIER_SD_CLKCTL 0x048 /* clock divisor */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV_MASK 0x104ff
|
||||
#define UNIPHIER_SD_CLKCTL_DIV1024 BIT(16) /* SDCLK = CLK / 1024 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV512 BIT(7) /* SDCLK = CLK / 512 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV256 BIT(6) /* SDCLK = CLK / 256 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV128 BIT(5) /* SDCLK = CLK / 128 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV64 BIT(4) /* SDCLK = CLK / 64 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV32 BIT(3) /* SDCLK = CLK / 32 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV16 BIT(2) /* SDCLK = CLK / 16 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV8 BIT(1) /* SDCLK = CLK / 8 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV4 BIT(0) /* SDCLK = CLK / 4 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV2 0 /* SDCLK = CLK / 2 */
|
||||
#define UNIPHIER_SD_CLKCTL_DIV1 BIT(10) /* SDCLK = CLK */
|
||||
#define UNIPHIER_SD_CLKCTL_OFFEN BIT(9) /* stop SDCLK when unused */
|
||||
#define UNIPHIER_SD_CLKCTL_SCLKEN BIT(8) /* SDCLK output enable */
|
||||
#define UNIPHIER_SD_SIZE 0x04c /* block size */
|
||||
#define UNIPHIER_SD_OPTION 0x050
|
||||
#define UNIPHIER_SD_OPTION_WIDTH_MASK (5 << 13)
|
||||
#define UNIPHIER_SD_OPTION_WIDTH_1 (4 << 13)
|
||||
#define UNIPHIER_SD_OPTION_WIDTH_4 (0 << 13)
|
||||
#define UNIPHIER_SD_OPTION_WIDTH_8 (1 << 13)
|
||||
#define UNIPHIER_SD_BUF 0x060 /* read/write buffer */
|
||||
#define UNIPHIER_SD_EXTMODE 0x1b0
|
||||
#define UNIPHIER_SD_EXTMODE_DMA_EN BIT(1) /* transfer 1: DMA, 0: pio */
|
||||
#define UNIPHIER_SD_SOFT_RST 0x1c0
|
||||
#define UNIPHIER_SD_SOFT_RST_RSTX BIT(0) /* reset deassert */
|
||||
#define UNIPHIER_SD_VERSION 0x1c4 /* version register */
|
||||
#define UNIPHIER_SD_VERSION_IP 0xff /* IP version */
|
||||
#define UNIPHIER_SD_HOST_MODE 0x1c8
|
||||
#define UNIPHIER_SD_IF_MODE 0x1cc
|
||||
#define UNIPHIER_SD_IF_MODE_DDR BIT(0) /* DDR mode */
|
||||
#define UNIPHIER_SD_VOLT 0x1e4 /* voltage switch */
|
||||
#define UNIPHIER_SD_VOLT_MASK (3 << 0)
|
||||
#define UNIPHIER_SD_VOLT_OFF (0 << 0)
|
||||
#define UNIPHIER_SD_VOLT_330 (1 << 0)/* 3.3V signal */
|
||||
#define UNIPHIER_SD_VOLT_180 (2 << 0)/* 1.8V signal */
|
||||
#define UNIPHIER_SD_DMA_MODE 0x410
|
||||
#define UNIPHIER_SD_DMA_MODE_DIR_RD BIT(16) /* 1: from device, 0: to dev */
|
||||
#define UNIPHIER_SD_DMA_MODE_ADDR_INC BIT(0) /* 1: address inc, 0: fixed */
|
||||
#define UNIPHIER_SD_DMA_CTL 0x414
|
||||
#define UNIPHIER_SD_DMA_CTL_START BIT(0) /* start DMA (auto cleared) */
|
||||
#define UNIPHIER_SD_DMA_RST 0x418
|
||||
#define UNIPHIER_SD_DMA_RST_RD BIT(9)
|
||||
#define UNIPHIER_SD_DMA_RST_WR BIT(8)
|
||||
#define UNIPHIER_SD_DMA_INFO1 0x420
|
||||
#define UNIPHIER_SD_DMA_INFO1_END_RD2 BIT(20) /* DMA from device is complete*/
|
||||
#define UNIPHIER_SD_DMA_INFO1_END_RD BIT(17) /* Don't use! Hardware bug */
|
||||
#define UNIPHIER_SD_DMA_INFO1_END_WR BIT(16) /* DMA to device is complete */
|
||||
#define UNIPHIER_SD_DMA_INFO1_MASK 0x424
|
||||
#define UNIPHIER_SD_DMA_INFO2 0x428
|
||||
#define UNIPHIER_SD_DMA_INFO2_ERR_RD BIT(17)
|
||||
#define UNIPHIER_SD_DMA_INFO2_ERR_WR BIT(16)
|
||||
#define UNIPHIER_SD_DMA_INFO2_MASK 0x42c
|
||||
#define UNIPHIER_SD_DMA_ADDR_L 0x440
|
||||
#define UNIPHIER_SD_DMA_ADDR_H 0x444
|
||||
|
||||
/* alignment required by the DMA engine of this controller */
|
||||
#define UNIPHIER_SD_DMA_MINALIGN 0x10
|
||||
|
||||
struct uniphier_sd_priv {
|
||||
struct mmc_config cfg;
|
||||
struct mmc *mmc;
|
||||
struct udevice *dev;
|
||||
void __iomem *regbase;
|
||||
unsigned long mclk;
|
||||
unsigned int version;
|
||||
u32 caps;
|
||||
#define UNIPHIER_SD_CAP_NONREMOVABLE BIT(0) /* Nonremovable e.g. eMMC */
|
||||
#define UNIPHIER_SD_CAP_DMA_INTERNAL BIT(1) /* have internal DMA engine */
|
||||
#define UNIPHIER_SD_CAP_DIV1024 BIT(2) /* divisor 1024 is available */
|
||||
};
|
||||
|
||||
static dma_addr_t __dma_map_single(void *ptr, size_t size,
|
||||
enum dma_data_direction dir)
|
||||
{
|
||||
unsigned long addr = (unsigned long)ptr;
|
||||
|
||||
if (dir == DMA_FROM_DEVICE)
|
||||
invalidate_dcache_range(addr, addr + size);
|
||||
else
|
||||
flush_dcache_range(addr, addr + size);
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static void __dma_unmap_single(dma_addr_t addr, size_t size,
|
||||
enum dma_data_direction dir)
|
||||
{
|
||||
if (dir != DMA_TO_DEVICE)
|
||||
invalidate_dcache_range(addr, addr + size);
|
||||
}
|
||||
|
||||
static int uniphier_sd_check_error(struct uniphier_sd_priv *priv)
|
||||
{
|
||||
u32 info2 = readl(priv->regbase + UNIPHIER_SD_INFO2);
|
||||
|
||||
if (info2 & UNIPHIER_SD_INFO2_ERR_RTO) {
|
||||
/*
|
||||
* TIMEOUT must be returned for unsupported command. Do not
|
||||
* display error log since this might be a part of sequence to
|
||||
* distinguish between SD and MMC.
|
||||
*/
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
if (info2 & UNIPHIER_SD_INFO2_ERR_TO) {
|
||||
dev_err(priv->dev, "timeout error\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (info2 & (UNIPHIER_SD_INFO2_ERR_END | UNIPHIER_SD_INFO2_ERR_CRC |
|
||||
UNIPHIER_SD_INFO2_ERR_IDX)) {
|
||||
dev_err(priv->dev, "communication out of sync\n");
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
if (info2 & (UNIPHIER_SD_INFO2_ERR_ILA | UNIPHIER_SD_INFO2_ERR_ILR |
|
||||
UNIPHIER_SD_INFO2_ERR_ILW)) {
|
||||
dev_err(priv->dev, "illegal access\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_sd_wait_for_irq(struct uniphier_sd_priv *priv,
|
||||
unsigned int reg, u32 flag)
|
||||
{
|
||||
long wait = 1000000;
|
||||
int ret;
|
||||
|
||||
while (!(readl(priv->regbase + reg) & flag)) {
|
||||
if (wait-- < 0) {
|
||||
dev_err(priv->dev, "timeout\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
ret = uniphier_sd_check_error(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_sd_pio_read_one_block(struct mmc *mmc, u32 **pbuf,
|
||||
uint blocksize)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = mmc->priv;
|
||||
int i, ret;
|
||||
|
||||
/* wait until the buffer is filled with data */
|
||||
ret = uniphier_sd_wait_for_irq(priv, UNIPHIER_SD_INFO2,
|
||||
UNIPHIER_SD_INFO2_BRE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Clear the status flag _before_ read the buffer out because
|
||||
* UNIPHIER_SD_INFO2_BRE is edge-triggered, not level-triggered.
|
||||
*/
|
||||
writel(0, priv->regbase + UNIPHIER_SD_INFO2);
|
||||
|
||||
if (likely(IS_ALIGNED((unsigned long)*pbuf, 4))) {
|
||||
for (i = 0; i < blocksize / 4; i++)
|
||||
*(*pbuf)++ = readl(priv->regbase + UNIPHIER_SD_BUF);
|
||||
} else {
|
||||
for (i = 0; i < blocksize / 4; i++)
|
||||
put_unaligned(readl(priv->regbase + UNIPHIER_SD_BUF),
|
||||
(*pbuf)++);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_sd_pio_write_one_block(struct mmc *mmc, const u32 **pbuf,
|
||||
uint blocksize)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = mmc->priv;
|
||||
int i, ret;
|
||||
|
||||
/* wait until the buffer becomes empty */
|
||||
ret = uniphier_sd_wait_for_irq(priv, UNIPHIER_SD_INFO2,
|
||||
UNIPHIER_SD_INFO2_BWE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
writel(0, priv->regbase + UNIPHIER_SD_INFO2);
|
||||
|
||||
if (likely(IS_ALIGNED((unsigned long)*pbuf, 4))) {
|
||||
for (i = 0; i < blocksize / 4; i++)
|
||||
writel(*(*pbuf)++, priv->regbase + UNIPHIER_SD_BUF);
|
||||
} else {
|
||||
for (i = 0; i < blocksize / 4; i++)
|
||||
writel(get_unaligned((*pbuf)++),
|
||||
priv->regbase + UNIPHIER_SD_BUF);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_sd_pio_xfer(struct mmc *mmc, struct mmc_data *data)
|
||||
{
|
||||
u32 *dest = (u32 *)data->dest;
|
||||
const u32 *src = (const u32 *)data->src;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < data->blocks; i++) {
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
ret = uniphier_sd_pio_read_one_block(mmc, &dest,
|
||||
data->blocksize);
|
||||
else
|
||||
ret = uniphier_sd_pio_write_one_block(mmc, &src,
|
||||
data->blocksize);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uniphier_sd_dma_start(struct uniphier_sd_priv *priv,
|
||||
dma_addr_t dma_addr)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
writel(0, priv->regbase + UNIPHIER_SD_DMA_INFO1);
|
||||
writel(0, priv->regbase + UNIPHIER_SD_DMA_INFO2);
|
||||
|
||||
/* enable DMA */
|
||||
tmp = readl(priv->regbase + UNIPHIER_SD_EXTMODE);
|
||||
tmp |= UNIPHIER_SD_EXTMODE_DMA_EN;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_EXTMODE);
|
||||
|
||||
writel(dma_addr & U32_MAX, priv->regbase + UNIPHIER_SD_DMA_ADDR_L);
|
||||
|
||||
/* suppress the warning "right shift count >= width of type" */
|
||||
dma_addr >>= min_t(int, 32, 8 * sizeof(dma_addr));
|
||||
|
||||
writel(dma_addr & U32_MAX, priv->regbase + UNIPHIER_SD_DMA_ADDR_H);
|
||||
|
||||
writel(UNIPHIER_SD_DMA_CTL_START, priv->regbase + UNIPHIER_SD_DMA_CTL);
|
||||
}
|
||||
|
||||
static int uniphier_sd_dma_wait_for_irq(struct uniphier_sd_priv *priv, u32 flag,
|
||||
unsigned int blocks)
|
||||
{
|
||||
long wait = 1000000 + 10 * blocks;
|
||||
|
||||
while (!(readl(priv->regbase + UNIPHIER_SD_DMA_INFO1) & flag)) {
|
||||
if (wait-- < 0) {
|
||||
dev_err(priv->dev, "timeout during DMA\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
udelay(10);
|
||||
}
|
||||
|
||||
if (readl(priv->regbase + UNIPHIER_SD_DMA_INFO2)) {
|
||||
dev_err(priv->dev, "error during DMA\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_sd_dma_xfer(struct mmc *mmc, struct mmc_data *data)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = mmc->priv;
|
||||
size_t len = data->blocks * data->blocksize;
|
||||
void *buf;
|
||||
enum dma_data_direction dir;
|
||||
dma_addr_t dma_addr;
|
||||
u32 poll_flag, tmp;
|
||||
int ret;
|
||||
|
||||
tmp = readl(priv->regbase + UNIPHIER_SD_DMA_MODE);
|
||||
|
||||
if (data->flags & MMC_DATA_READ) {
|
||||
buf = data->dest;
|
||||
dir = DMA_FROM_DEVICE;
|
||||
poll_flag = UNIPHIER_SD_DMA_INFO1_END_RD2;
|
||||
tmp |= UNIPHIER_SD_DMA_MODE_DIR_RD;
|
||||
} else {
|
||||
buf = (void *)data->src;
|
||||
dir = DMA_TO_DEVICE;
|
||||
poll_flag = UNIPHIER_SD_DMA_INFO1_END_WR;
|
||||
tmp &= ~UNIPHIER_SD_DMA_MODE_DIR_RD;
|
||||
}
|
||||
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_DMA_MODE);
|
||||
|
||||
dma_addr = __dma_map_single(buf, len, dir);
|
||||
|
||||
uniphier_sd_dma_start(priv, dma_addr);
|
||||
|
||||
ret = uniphier_sd_dma_wait_for_irq(priv, poll_flag, data->blocks);
|
||||
|
||||
__dma_unmap_single(dma_addr, len, dir);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check if the address is DMA'able */
|
||||
static bool uniphier_sd_addr_is_dmaable(unsigned long addr)
|
||||
{
|
||||
if (!IS_ALIGNED(addr, UNIPHIER_SD_DMA_MINALIGN))
|
||||
return false;
|
||||
|
||||
#if defined(CONFIG_ARCH_UNIPHIER) && !defined(CONFIG_ARM64) && \
|
||||
defined(CONFIG_SPL_BUILD)
|
||||
/*
|
||||
* For UniPhier ARMv7 SoCs, the stack is allocated in the locked ways
|
||||
* of L2, which is unreachable from the DMA engine.
|
||||
*/
|
||||
if (addr < CONFIG_SPL_STACK)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int uniphier_sd_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
||||
struct mmc_data *data)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = mmc->priv;
|
||||
int ret;
|
||||
u32 tmp;
|
||||
|
||||
if (readl(priv->regbase + UNIPHIER_SD_INFO2) & UNIPHIER_SD_INFO2_CBSY) {
|
||||
dev_err(priv->dev, "command busy\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* clear all status flags */
|
||||
writel(0, priv->regbase + UNIPHIER_SD_INFO1);
|
||||
writel(0, priv->regbase + UNIPHIER_SD_INFO2);
|
||||
|
||||
/* disable DMA once */
|
||||
tmp = readl(priv->regbase + UNIPHIER_SD_EXTMODE);
|
||||
tmp &= ~UNIPHIER_SD_EXTMODE_DMA_EN;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_EXTMODE);
|
||||
|
||||
writel(cmd->cmdarg, priv->regbase + UNIPHIER_SD_ARG);
|
||||
|
||||
tmp = cmd->cmdidx;
|
||||
|
||||
if (data) {
|
||||
writel(data->blocksize, priv->regbase + UNIPHIER_SD_SIZE);
|
||||
writel(data->blocks, priv->regbase + UNIPHIER_SD_SECCNT);
|
||||
|
||||
/* Do not send CMD12 automatically */
|
||||
tmp |= UNIPHIER_SD_CMD_NOSTOP | UNIPHIER_SD_CMD_DATA;
|
||||
|
||||
if (data->blocks > 1)
|
||||
tmp |= UNIPHIER_SD_CMD_MULTI;
|
||||
|
||||
if (data->flags & MMC_DATA_READ)
|
||||
tmp |= UNIPHIER_SD_CMD_RD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do not use the response type auto-detection on this hardware.
|
||||
* CMD8, for example, has different response types on SD and eMMC,
|
||||
* while this controller always assumes the response type for SD.
|
||||
* Set the response type manually.
|
||||
*/
|
||||
switch (cmd->resp_type) {
|
||||
case MMC_RSP_NONE:
|
||||
tmp |= UNIPHIER_SD_CMD_RSP_NONE;
|
||||
break;
|
||||
case MMC_RSP_R1:
|
||||
tmp |= UNIPHIER_SD_CMD_RSP_R1;
|
||||
break;
|
||||
case MMC_RSP_R1b:
|
||||
tmp |= UNIPHIER_SD_CMD_RSP_R1B;
|
||||
break;
|
||||
case MMC_RSP_R2:
|
||||
tmp |= UNIPHIER_SD_CMD_RSP_R2;
|
||||
break;
|
||||
case MMC_RSP_R3:
|
||||
tmp |= UNIPHIER_SD_CMD_RSP_R3;
|
||||
break;
|
||||
default:
|
||||
dev_err(priv->dev, "unknown response type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(priv->dev, "sending CMD%d (SD_CMD=%08x, SD_ARG=%08x)\n",
|
||||
cmd->cmdidx, tmp, cmd->cmdarg);
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_CMD);
|
||||
|
||||
ret = uniphier_sd_wait_for_irq(priv, UNIPHIER_SD_INFO1,
|
||||
UNIPHIER_SD_INFO1_RSP);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (cmd->resp_type & MMC_RSP_136) {
|
||||
u32 rsp_127_104 = readl(priv->regbase + UNIPHIER_SD_RSP76);
|
||||
u32 rsp_103_72 = readl(priv->regbase + UNIPHIER_SD_RSP54);
|
||||
u32 rsp_71_40 = readl(priv->regbase + UNIPHIER_SD_RSP32);
|
||||
u32 rsp_39_8 = readl(priv->regbase + UNIPHIER_SD_RSP10);
|
||||
|
||||
cmd->response[0] = (rsp_127_104 & 0xffffff) << 8 |
|
||||
(rsp_103_72 & 0xff);
|
||||
cmd->response[1] = (rsp_103_72 & 0xffffff) << 8 |
|
||||
(rsp_71_40 & 0xff);
|
||||
cmd->response[2] = (rsp_71_40 & 0xffffff) << 8 |
|
||||
(rsp_39_8 & 0xff);
|
||||
cmd->response[3] = (rsp_39_8 & 0xffffff) << 8;
|
||||
} else {
|
||||
/* bit 39-8 */
|
||||
cmd->response[0] = readl(priv->regbase + UNIPHIER_SD_RSP10);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
/* use DMA if the HW supports it and the buffer is aligned */
|
||||
if (priv->caps & UNIPHIER_SD_CAP_DMA_INTERNAL &&
|
||||
uniphier_sd_addr_is_dmaable((long)data->src))
|
||||
ret = uniphier_sd_dma_xfer(mmc, data);
|
||||
else
|
||||
ret = uniphier_sd_pio_xfer(mmc, data);
|
||||
|
||||
ret = uniphier_sd_wait_for_irq(priv, UNIPHIER_SD_INFO1,
|
||||
UNIPHIER_SD_INFO1_CMP);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void uniphier_sd_set_bus_width(struct uniphier_sd_priv *priv,
|
||||
struct mmc *mmc)
|
||||
{
|
||||
u32 val, tmp;
|
||||
|
||||
switch (mmc->bus_width) {
|
||||
case 1:
|
||||
val = UNIPHIER_SD_OPTION_WIDTH_1;
|
||||
break;
|
||||
case 4:
|
||||
val = UNIPHIER_SD_OPTION_WIDTH_4;
|
||||
break;
|
||||
case 8:
|
||||
val = UNIPHIER_SD_OPTION_WIDTH_8;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
|
||||
tmp = readl(priv->regbase + UNIPHIER_SD_OPTION);
|
||||
tmp &= ~UNIPHIER_SD_OPTION_WIDTH_MASK;
|
||||
tmp |= val;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_OPTION);
|
||||
}
|
||||
|
||||
static void uniphier_sd_set_ddr_mode(struct uniphier_sd_priv *priv,
|
||||
struct mmc *mmc)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
tmp = readl(priv->regbase + UNIPHIER_SD_IF_MODE);
|
||||
if (mmc->ddr_mode)
|
||||
tmp |= UNIPHIER_SD_IF_MODE_DDR;
|
||||
else
|
||||
tmp &= ~UNIPHIER_SD_IF_MODE_DDR;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_IF_MODE);
|
||||
}
|
||||
|
||||
static void uniphier_sd_set_clk_rate(struct uniphier_sd_priv *priv,
|
||||
struct mmc *mmc)
|
||||
{
|
||||
unsigned int divisor;
|
||||
u32 val, tmp;
|
||||
|
||||
if (!mmc->clock)
|
||||
return;
|
||||
|
||||
divisor = DIV_ROUND_UP(priv->mclk, mmc->clock);
|
||||
|
||||
if (divisor <= 1)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV1;
|
||||
else if (divisor <= 2)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV2;
|
||||
else if (divisor <= 4)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV4;
|
||||
else if (divisor <= 8)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV8;
|
||||
else if (divisor <= 16)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV16;
|
||||
else if (divisor <= 32)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV32;
|
||||
else if (divisor <= 64)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV64;
|
||||
else if (divisor <= 128)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV128;
|
||||
else if (divisor <= 256)
|
||||
val = UNIPHIER_SD_CLKCTL_DIV256;
|
||||
else if (divisor <= 512 || !(priv->caps & UNIPHIER_SD_CAP_DIV1024))
|
||||
val = UNIPHIER_SD_CLKCTL_DIV512;
|
||||
else
|
||||
val = UNIPHIER_SD_CLKCTL_DIV1024;
|
||||
|
||||
tmp = readl(priv->regbase + UNIPHIER_SD_CLKCTL);
|
||||
|
||||
/* stop the clock before changing its rate to avoid a glitch signal */
|
||||
tmp &= ~UNIPHIER_SD_CLKCTL_SCLKEN;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_CLKCTL);
|
||||
|
||||
tmp &= ~UNIPHIER_SD_CLKCTL_DIV_MASK;
|
||||
tmp |= val | UNIPHIER_SD_CLKCTL_OFFEN;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_CLKCTL);
|
||||
|
||||
tmp |= UNIPHIER_SD_CLKCTL_SCLKEN;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_CLKCTL);
|
||||
}
|
||||
|
||||
static void uniphier_sd_set_ios(struct mmc *mmc)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = mmc->priv;
|
||||
|
||||
dev_dbg(priv->dev, "clock %uHz, DDRmode %d, width %u\n",
|
||||
mmc->clock, mmc->ddr_mode, mmc->bus_width);
|
||||
|
||||
uniphier_sd_set_bus_width(priv, mmc);
|
||||
uniphier_sd_set_ddr_mode(priv, mmc);
|
||||
uniphier_sd_set_clk_rate(priv, mmc);
|
||||
|
||||
udelay(1000);
|
||||
}
|
||||
|
||||
static int uniphier_sd_init(struct mmc *mmc)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = mmc->priv;
|
||||
u32 tmp;
|
||||
|
||||
/* soft reset of the host */
|
||||
tmp = readl(priv->regbase + UNIPHIER_SD_SOFT_RST);
|
||||
tmp &= ~UNIPHIER_SD_SOFT_RST_RSTX;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_SOFT_RST);
|
||||
tmp |= UNIPHIER_SD_SOFT_RST_RSTX;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_SOFT_RST);
|
||||
|
||||
/* FIXME: implement eMMC hw_reset */
|
||||
|
||||
writel(UNIPHIER_SD_STOP_SEC, priv->regbase + UNIPHIER_SD_STOP);
|
||||
|
||||
/*
|
||||
* Connected to 32bit AXI.
|
||||
* This register dropped backward compatibility at version 0x10.
|
||||
* Write an appropriate value depending on the IP version.
|
||||
*/
|
||||
writel(priv->version >= 0x10 ? 0x00000101 : 0x00000000,
|
||||
priv->regbase + UNIPHIER_SD_HOST_MODE);
|
||||
|
||||
if (priv->caps & UNIPHIER_SD_CAP_DMA_INTERNAL) {
|
||||
tmp = readl(priv->regbase + UNIPHIER_SD_DMA_MODE);
|
||||
tmp |= UNIPHIER_SD_DMA_MODE_ADDR_INC;
|
||||
writel(tmp, priv->regbase + UNIPHIER_SD_DMA_MODE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphier_sd_getcd(struct mmc *mmc)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = mmc->priv;
|
||||
|
||||
if (priv->caps & UNIPHIER_SD_CAP_NONREMOVABLE)
|
||||
return 1;
|
||||
|
||||
return !!(readl(priv->regbase + UNIPHIER_SD_INFO1) &
|
||||
UNIPHIER_SD_INFO1_CD);
|
||||
}
|
||||
|
||||
static const struct mmc_ops uniphier_sd_ops = {
|
||||
.send_cmd = uniphier_sd_send_cmd,
|
||||
.set_ios = uniphier_sd_set_ios,
|
||||
.init = uniphier_sd_init,
|
||||
.getcd = uniphier_sd_getcd,
|
||||
};
|
||||
|
||||
int uniphier_sd_probe(struct udevice *dev)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = dev_get_priv(dev);
|
||||
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
||||
fdt_addr_t base;
|
||||
struct clk clk;
|
||||
int ret;
|
||||
|
||||
priv->dev = dev;
|
||||
|
||||
base = dev_get_addr(dev);
|
||||
if (base == FDT_ADDR_T_NONE)
|
||||
return -EINVAL;
|
||||
|
||||
priv->regbase = map_sysmem(base, SZ_2K);
|
||||
if (!priv->regbase)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = clk_get_by_index(dev, 0, &clk);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to get host clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set to max rate */
|
||||
priv->mclk = clk_set_rate(&clk, ULONG_MAX);
|
||||
if (IS_ERR_VALUE(priv->mclk)) {
|
||||
dev_err(dev, "failed to set rate for host clock\n");
|
||||
clk_free(&clk);
|
||||
return priv->mclk;
|
||||
}
|
||||
|
||||
ret = clk_enable(&clk);
|
||||
clk_free(&clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable host clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
priv->cfg.name = dev->name;
|
||||
priv->cfg.ops = &uniphier_sd_ops;
|
||||
priv->cfg.host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS;
|
||||
|
||||
switch (fdtdec_get_int(gd->fdt_blob, dev->of_offset, "bus-width", 1)) {
|
||||
case 8:
|
||||
priv->cfg.host_caps |= MMC_MODE_8BIT;
|
||||
break;
|
||||
case 4:
|
||||
priv->cfg.host_caps |= MMC_MODE_4BIT;
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "Invalid \"bus-width\" value\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (fdt_get_property(gd->fdt_blob, dev->of_offset, "non-removable",
|
||||
NULL))
|
||||
priv->caps |= UNIPHIER_SD_CAP_NONREMOVABLE;
|
||||
|
||||
priv->version = readl(priv->regbase + UNIPHIER_SD_VERSION) &
|
||||
UNIPHIER_SD_VERSION_IP;
|
||||
dev_dbg(dev, "version %x\n", priv->version);
|
||||
if (priv->version >= 0x10) {
|
||||
priv->caps |= UNIPHIER_SD_CAP_DMA_INTERNAL;
|
||||
priv->caps |= UNIPHIER_SD_CAP_DIV1024;
|
||||
}
|
||||
|
||||
priv->cfg.voltages = MMC_VDD_165_195 | MMC_VDD_32_33 | MMC_VDD_33_34;
|
||||
priv->cfg.f_min = priv->mclk /
|
||||
(priv->caps & UNIPHIER_SD_CAP_DIV1024 ? 1024 : 512);
|
||||
priv->cfg.f_max = priv->mclk;
|
||||
priv->cfg.b_max = U32_MAX; /* max value of UNIPHIER_SD_SECCNT */
|
||||
|
||||
priv->mmc = mmc_create(&priv->cfg, priv);
|
||||
if (!priv->mmc)
|
||||
return -EIO;
|
||||
|
||||
upriv->mmc = priv->mmc;
|
||||
priv->mmc->dev = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uniphier_sd_remove(struct udevice *dev)
|
||||
{
|
||||
struct uniphier_sd_priv *priv = dev_get_priv(dev);
|
||||
|
||||
unmap_sysmem(priv->regbase);
|
||||
mmc_destroy(priv->mmc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id uniphier_sd_match[] = {
|
||||
{ .compatible = "socionext,uniphier-sdhc" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(uniphier_mmc) = {
|
||||
.name = "uniphier-mmc",
|
||||
.id = UCLASS_MMC,
|
||||
.of_match = uniphier_sd_match,
|
||||
.probe = uniphier_sd_probe,
|
||||
.remove = uniphier_sd_remove,
|
||||
.priv_auto_alloc_size = sizeof(struct uniphier_sd_priv),
|
||||
};
|
||||
65
u-boot/drivers/mmc/zynq_sdhci.c
Normal file
65
u-boot/drivers/mmc/zynq_sdhci.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* (C) Copyright 2013 - 2015 Xilinx, Inc.
|
||||
*
|
||||
* Xilinx Zynq SD Host Controller Interface
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <fdtdec.h>
|
||||
#include <libfdt.h>
|
||||
#include <malloc.h>
|
||||
#include <sdhci.h>
|
||||
|
||||
#ifndef CONFIG_ZYNQ_SDHCI_MIN_FREQ
|
||||
# define CONFIG_ZYNQ_SDHCI_MIN_FREQ 0
|
||||
#endif
|
||||
|
||||
static int arasan_sdhci_probe(struct udevice *dev)
|
||||
{
|
||||
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
||||
struct sdhci_host *host = dev_get_priv(dev);
|
||||
|
||||
host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD |
|
||||
SDHCI_QUIRK_BROKEN_R1B;
|
||||
|
||||
#ifdef CONFIG_ZYNQ_HISPD_BROKEN
|
||||
host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
|
||||
#endif
|
||||
|
||||
host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
|
||||
|
||||
add_sdhci(host, CONFIG_ZYNQ_SDHCI_MAX_FREQ,
|
||||
CONFIG_ZYNQ_SDHCI_MIN_FREQ);
|
||||
|
||||
upriv->mmc = host->mmc;
|
||||
host->mmc->dev = dev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int arasan_sdhci_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct sdhci_host *host = dev_get_priv(dev);
|
||||
|
||||
host->name = dev->name;
|
||||
host->ioaddr = (void *)dev_get_addr(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id arasan_sdhci_ids[] = {
|
||||
{ .compatible = "arasan,sdhci-8.9a" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(arasan_sdhci_drv) = {
|
||||
.name = "arasan_sdhci",
|
||||
.id = UCLASS_MMC,
|
||||
.of_match = arasan_sdhci_ids,
|
||||
.ofdata_to_platdata = arasan_sdhci_ofdata_to_platdata,
|
||||
.probe = arasan_sdhci_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct sdhci_host),
|
||||
};
|
||||
Reference in New Issue
Block a user