mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-19 11:18:28 +00:00
c67bee1460
The esdhc controller in the mx51 processor is quite the same as the one in some powerpc processors (MPC83xx, MPC85xx). This patches adapts the driver to support the arm mx51. Signed-off-by: Stefano Babic <sbabic@denx.de>
420 lines
9.9 KiB
C
420 lines
9.9 KiB
C
/*
|
|
* 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
|
|
*
|
|
* See file CREDITS for list of people who contributed to this
|
|
* project.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
|
* MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <common.h>
|
|
#include <command.h>
|
|
#include <hwconfig.h>
|
|
#include <mmc.h>
|
|
#include <part.h>
|
|
#include <malloc.h>
|
|
#include <mmc.h>
|
|
#include <fsl_esdhc.h>
|
|
#include <fdt_support.h>
|
|
#include <asm/io.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
struct fsl_esdhc {
|
|
uint dsaddr;
|
|
uint blkattr;
|
|
uint cmdarg;
|
|
uint xfertyp;
|
|
uint cmdrsp0;
|
|
uint cmdrsp1;
|
|
uint cmdrsp2;
|
|
uint cmdrsp3;
|
|
uint datport;
|
|
uint prsstat;
|
|
uint proctl;
|
|
uint sysctl;
|
|
uint irqstat;
|
|
uint irqstaten;
|
|
uint irqsigen;
|
|
uint autoc12err;
|
|
uint hostcapblt;
|
|
uint wml;
|
|
char reserved1[8];
|
|
uint fevt;
|
|
char reserved2[168];
|
|
uint hostver;
|
|
char reserved3[780];
|
|
uint scr;
|
|
};
|
|
|
|
/* Return the XFERTYP flags for a given command and data packet */
|
|
uint esdhc_xfertyp(struct mmc_cmd *cmd, struct mmc_data *data)
|
|
{
|
|
uint xfertyp = 0;
|
|
|
|
if (data) {
|
|
xfertyp |= XFERTYP_DPSEL | XFERTYP_DMAEN;
|
|
|
|
if (data->blocks > 1) {
|
|
xfertyp |= XFERTYP_MSBSEL;
|
|
xfertyp |= XFERTYP_BCEN;
|
|
}
|
|
|
|
if (data->flags & MMC_DATA_READ)
|
|
xfertyp |= XFERTYP_DTDSEL;
|
|
}
|
|
|
|
if (cmd->resp_type & MMC_RSP_CRC)
|
|
xfertyp |= XFERTYP_CCCEN;
|
|
if (cmd->resp_type & MMC_RSP_OPCODE)
|
|
xfertyp |= XFERTYP_CICEN;
|
|
if (cmd->resp_type & MMC_RSP_136)
|
|
xfertyp |= XFERTYP_RSPTYP_136;
|
|
else if (cmd->resp_type & MMC_RSP_BUSY)
|
|
xfertyp |= XFERTYP_RSPTYP_48_BUSY;
|
|
else if (cmd->resp_type & MMC_RSP_PRESENT)
|
|
xfertyp |= XFERTYP_RSPTYP_48;
|
|
|
|
return XFERTYP_CMD(cmd->cmdidx) | xfertyp;
|
|
}
|
|
|
|
static int esdhc_setup_data(struct mmc *mmc, struct mmc_data *data)
|
|
{
|
|
uint wml_value;
|
|
int timeout;
|
|
struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv;
|
|
struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base;
|
|
|
|
wml_value = data->blocksize/4;
|
|
|
|
if (data->flags & MMC_DATA_READ) {
|
|
if (wml_value > 0x10)
|
|
wml_value = 0x10;
|
|
|
|
wml_value = 0x100000 | wml_value;
|
|
|
|
esdhc_write32(®s->dsaddr, (u32)data->dest);
|
|
} else {
|
|
if (wml_value > 0x80)
|
|
wml_value = 0x80;
|
|
if ((esdhc_read32(®s->prsstat) & PRSSTAT_WPSPL) == 0) {
|
|
printf("\nThe SD card is locked. Can not write to a locked card.\n\n");
|
|
return TIMEOUT;
|
|
}
|
|
wml_value = wml_value << 16 | 0x10;
|
|
esdhc_write32(®s->dsaddr, (u32)data->src);
|
|
}
|
|
|
|
esdhc_write32(®s->wml, wml_value);
|
|
|
|
esdhc_write32(®s->blkattr, data->blocks << 16 | data->blocksize);
|
|
|
|
/* Calculate the timeout period for data transactions */
|
|
timeout = fls(mmc->tran_speed/10) - 1;
|
|
timeout -= 13;
|
|
|
|
if (timeout > 14)
|
|
timeout = 14;
|
|
|
|
if (timeout < 0)
|
|
timeout = 0;
|
|
|
|
esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, timeout << 16);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Sends a command out on the bus. Takes the mmc pointer,
|
|
* a command pointer, and an optional data pointer.
|
|
*/
|
|
static int
|
|
esdhc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data)
|
|
{
|
|
uint xfertyp;
|
|
uint irqstat;
|
|
struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv;
|
|
volatile struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base;
|
|
|
|
esdhc_write32(®s->irqstat, -1);
|
|
|
|
sync();
|
|
|
|
/* Wait for the bus to be idle */
|
|
while ((esdhc_read32(®s->prsstat) & PRSSTAT_CICHB) ||
|
|
(esdhc_read32(®s->prsstat) & PRSSTAT_CIDHB))
|
|
;
|
|
|
|
while (esdhc_read32(®s->prsstat) & PRSSTAT_DLA)
|
|
;
|
|
|
|
/* Wait at least 8 SD clock cycles before the next command */
|
|
/*
|
|
* Note: This is way more than 8 cycles, but 1ms seems to
|
|
* resolve timing issues with some cards
|
|
*/
|
|
udelay(1000);
|
|
|
|
/* Set up for a data transfer if we have one */
|
|
if (data) {
|
|
int err;
|
|
|
|
err = esdhc_setup_data(mmc, data);
|
|
if(err)
|
|
return err;
|
|
}
|
|
|
|
/* Figure out the transfer arguments */
|
|
xfertyp = esdhc_xfertyp(cmd, data);
|
|
|
|
/* Send the command */
|
|
esdhc_write32(®s->cmdarg, cmd->cmdarg);
|
|
esdhc_write32(®s->xfertyp, xfertyp);
|
|
|
|
/* Wait for the command to complete */
|
|
while (!(esdhc_read32(®s->irqstat) & IRQSTAT_CC))
|
|
;
|
|
|
|
irqstat = esdhc_read32(®s->irqstat);
|
|
esdhc_write32(®s->irqstat, irqstat);
|
|
|
|
if (irqstat & CMD_ERR)
|
|
return COMM_ERR;
|
|
|
|
if (irqstat & IRQSTAT_CTOE)
|
|
return TIMEOUT;
|
|
|
|
/* Copy the response to the response buffer */
|
|
if (cmd->resp_type & MMC_RSP_136) {
|
|
u32 cmdrsp3, cmdrsp2, cmdrsp1, cmdrsp0;
|
|
|
|
cmdrsp3 = esdhc_read32(®s->cmdrsp3);
|
|
cmdrsp2 = esdhc_read32(®s->cmdrsp2);
|
|
cmdrsp1 = esdhc_read32(®s->cmdrsp1);
|
|
cmdrsp0 = esdhc_read32(®s->cmdrsp0);
|
|
cmd->response[0] = (cmdrsp3 << 8) | (cmdrsp2 >> 24);
|
|
cmd->response[1] = (cmdrsp2 << 8) | (cmdrsp1 >> 24);
|
|
cmd->response[2] = (cmdrsp1 << 8) | (cmdrsp0 >> 24);
|
|
cmd->response[3] = (cmdrsp0 << 8);
|
|
} else
|
|
cmd->response[0] = esdhc_read32(®s->cmdrsp0);
|
|
|
|
/* Wait until all of the blocks are transferred */
|
|
if (data) {
|
|
do {
|
|
irqstat = esdhc_read32(®s->irqstat);
|
|
|
|
if (irqstat & DATA_ERR)
|
|
return COMM_ERR;
|
|
|
|
if (irqstat & IRQSTAT_DTOE)
|
|
return TIMEOUT;
|
|
} while (!(irqstat & IRQSTAT_TC) &&
|
|
(esdhc_read32(®s->prsstat) & PRSSTAT_DLA));
|
|
}
|
|
|
|
esdhc_write32(®s->irqstat, -1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void set_sysctl(struct mmc *mmc, uint clock)
|
|
{
|
|
int sdhc_clk = gd->sdhc_clk;
|
|
int div, pre_div;
|
|
struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv;
|
|
volatile struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base;
|
|
uint clk;
|
|
|
|
if (clock < mmc->f_min)
|
|
clock = mmc->f_min;
|
|
|
|
if (sdhc_clk / 16 > clock) {
|
|
for (pre_div = 2; pre_div < 256; pre_div *= 2)
|
|
if ((sdhc_clk / pre_div) <= (clock * 16))
|
|
break;
|
|
} else
|
|
pre_div = 2;
|
|
|
|
for (div = 1; div <= 16; div++)
|
|
if ((sdhc_clk / (div * pre_div)) <= clock)
|
|
break;
|
|
|
|
pre_div >>= 1;
|
|
div -= 1;
|
|
|
|
clk = (pre_div << 8) | (div << 4);
|
|
|
|
/* On imx the clock must be stopped before changing frequency */
|
|
if (cfg->clk_enable)
|
|
esdhc_clrbits32(®s->sysctl, SYSCTL_CKEN);
|
|
|
|
esdhc_clrsetbits32(®s->sysctl, SYSCTL_CLOCK_MASK, clk);
|
|
|
|
udelay(10000);
|
|
|
|
clk = SYSCTL_PEREN;
|
|
/* On imx systems the clock must be explicitely enabled */
|
|
if (cfg->clk_enable)
|
|
clk |= SYSCTL_CKEN;
|
|
|
|
esdhc_setbits32(®s->sysctl, clk);
|
|
}
|
|
|
|
static void esdhc_set_ios(struct mmc *mmc)
|
|
{
|
|
struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv;
|
|
struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base;
|
|
|
|
/* Set the clock speed */
|
|
set_sysctl(mmc, mmc->clock);
|
|
|
|
/* Set the bus width */
|
|
esdhc_clrbits32(®s->proctl, PROCTL_DTW_4 | PROCTL_DTW_8);
|
|
|
|
if (mmc->bus_width == 4)
|
|
esdhc_setbits32(®s->proctl, PROCTL_DTW_4);
|
|
else if (mmc->bus_width == 8)
|
|
esdhc_setbits32(®s->proctl, PROCTL_DTW_8);
|
|
|
|
}
|
|
|
|
static int esdhc_init(struct mmc *mmc)
|
|
{
|
|
struct fsl_esdhc_cfg *cfg = (struct fsl_esdhc_cfg *)mmc->priv;
|
|
struct fsl_esdhc *regs = (struct fsl_esdhc *)cfg->esdhc_base;
|
|
int timeout = 1000;
|
|
int ret = 0;
|
|
u8 card_absent;
|
|
|
|
/* Enable cache snooping */
|
|
if (cfg && !cfg->no_snoop)
|
|
esdhc_write32(®s->scr, 0x00000040);
|
|
|
|
/* Reset the entire host controller */
|
|
esdhc_write32(®s->sysctl, SYSCTL_RSTA);
|
|
|
|
/* Wait until the controller is available */
|
|
while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTA) && --timeout)
|
|
udelay(1000);
|
|
|
|
esdhc_write32(®s->sysctl, SYSCTL_HCKEN | SYSCTL_IPGEN);
|
|
|
|
/* Set the initial clock speed */
|
|
set_sysctl(mmc, 400000);
|
|
|
|
/* Disable the BRR and BWR bits in IRQSTAT */
|
|
esdhc_clrbits32(®s->irqstaten, IRQSTATEN_BRR | IRQSTATEN_BWR);
|
|
|
|
/* Put the PROCTL reg back to the default */
|
|
esdhc_write32(®s->proctl, PROCTL_INIT);
|
|
|
|
/* Set timout to the maximum value */
|
|
esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, 14 << 16);
|
|
|
|
/* Check if there is a callback for detecting the card */
|
|
if (board_mmc_getcd(&card_absent, mmc)) {
|
|
timeout = 1000;
|
|
while (!(esdhc_read32(®s->prsstat) & PRSSTAT_CINS) &&
|
|
--timeout)
|
|
udelay(1000);
|
|
|
|
if (timeout <= 0)
|
|
ret = NO_CARD_ERR;
|
|
} else {
|
|
if (card_absent)
|
|
ret = NO_CARD_ERR;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int fsl_esdhc_initialize(bd_t *bis, struct fsl_esdhc_cfg *cfg)
|
|
{
|
|
struct fsl_esdhc *regs;
|
|
struct mmc *mmc;
|
|
u32 caps;
|
|
|
|
if (!cfg)
|
|
return -1;
|
|
|
|
mmc = malloc(sizeof(struct mmc));
|
|
|
|
sprintf(mmc->name, "FSL_ESDHC");
|
|
regs = (struct fsl_esdhc *)cfg->esdhc_base;
|
|
|
|
mmc->priv = cfg;
|
|
mmc->send_cmd = esdhc_send_cmd;
|
|
mmc->set_ios = esdhc_set_ios;
|
|
mmc->init = esdhc_init;
|
|
|
|
caps = regs->hostcapblt;
|
|
|
|
if (caps & ESDHC_HOSTCAPBLT_VS18)
|
|
mmc->voltages |= MMC_VDD_165_195;
|
|
if (caps & ESDHC_HOSTCAPBLT_VS30)
|
|
mmc->voltages |= MMC_VDD_29_30 | MMC_VDD_30_31;
|
|
if (caps & ESDHC_HOSTCAPBLT_VS33)
|
|
mmc->voltages |= MMC_VDD_32_33 | MMC_VDD_33_34;
|
|
|
|
mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT;
|
|
|
|
if (caps & ESDHC_HOSTCAPBLT_HSS)
|
|
mmc->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
|
|
|
|
mmc->f_min = 400000;
|
|
mmc->f_max = MIN(gd->sdhc_clk, 50000000);
|
|
|
|
mmc_register(mmc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fsl_esdhc_mmc_init(bd_t *bis)
|
|
{
|
|
struct fsl_esdhc_cfg *cfg;
|
|
|
|
cfg = malloc(sizeof(struct fsl_esdhc_cfg));
|
|
memset(cfg, 0, sizeof(struct fsl_esdhc_cfg));
|
|
cfg->esdhc_base = CONFIG_SYS_FSL_ESDHC_ADDR;
|
|
return fsl_esdhc_initialize(bis, cfg);
|
|
}
|
|
|
|
#ifdef CONFIG_OF_LIBFDT
|
|
void fdt_fixup_esdhc(void *blob, bd_t *bd)
|
|
{
|
|
const char *compat = "fsl,esdhc";
|
|
const char *status = "okay";
|
|
|
|
if (!hwconfig("esdhc")) {
|
|
status = "disabled";
|
|
goto out;
|
|
}
|
|
|
|
do_fixup_by_compat_u32(blob, compat, "clock-frequency",
|
|
gd->sdhc_clk, 1);
|
|
out:
|
|
do_fixup_by_compat(blob, compat, "status", status,
|
|
strlen(status) + 1, 1);
|
|
}
|
|
#endif
|