1577 lines
46 KiB
C
1577 lines
46 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2007-2011
|
|
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
* Aaron <leafy.myeh@allwinnertech.com>
|
|
*
|
|
* MMC driver for allwinner sunxi platform.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <errno.h>
|
|
#include <malloc.h>
|
|
#include <mmc.h>
|
|
#include <asm/io.h>
|
|
#include <asm/arch/clock.h>
|
|
#include <asm/arch/timer.h>
|
|
#include <asm/arch/cpu.h>
|
|
#include <asm/arch/gpio.h>
|
|
#include <private_uboot.h>
|
|
#include "sunxi_mmc.h"
|
|
#include "host/sunxi_mmc_host_common.h"
|
|
#include "mmc_def.h"
|
|
|
|
#ifndef readl
|
|
#define readl(a) *(volatile uint *)(ulong)(a)
|
|
#endif
|
|
#ifndef writel
|
|
#define writel(v, c) *(volatile uint *)(ulong)(c) = (v)
|
|
#endif
|
|
|
|
#define DTO_MAX 200
|
|
|
|
#if !CONFIG_IS_ENABLED(DM_MMC)
|
|
/* support 4 mmc hosts */
|
|
struct sunxi_mmc_priv mmc_host[4];
|
|
struct mmc_reg_v4p1 mmc_host_reg_bak[3];
|
|
|
|
//extern u8 ext_odly_spd_freq[];
|
|
//extern u8 ext_sdly_spd_freq[];
|
|
//extern u8 ext_odly_spd_freq_sdc0[];
|
|
//extern u8 ext_sdly_spd_freq_sdc0[];
|
|
static u8 ext_odly_spd_freq[MAX_SPD_MD_NUM*MAX_CLK_FREQ_NUM];
|
|
static u8 ext_sdly_spd_freq[MAX_SPD_MD_NUM*MAX_CLK_FREQ_NUM];
|
|
static u8 ext_odly_spd_freq_sdc0[MAX_SPD_MD_NUM*MAX_CLK_FREQ_NUM];
|
|
static u8 ext_sdly_spd_freq_sdc0[MAX_SPD_MD_NUM*MAX_CLK_FREQ_NUM];
|
|
|
|
void dumphex32(char *name, char *base, int len)
|
|
{
|
|
__u32 i;
|
|
|
|
printf("dump %s registers:", name);
|
|
for (i = 0; i < len; i += 4) {
|
|
if (!(i & 0xf))
|
|
printf("\n0x%p : ", base + i);
|
|
printf("0x%08x ", readl((ulong)base + i));
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void mmc_dump_errinfo(struct sunxi_mmc_priv *smc_priv, struct mmc_cmd *cmd)
|
|
{
|
|
MMCMSG(smc_priv->mmc, "smc %d err, cmd %d, %s%s%s%s%s%s%s%s%s%s%s\n",
|
|
smc_priv->mmc_no, cmd ? cmd->cmdidx : -1,
|
|
smc_priv->raw_int_bak & SDXC_RespErr ? " RE" : "",
|
|
smc_priv->raw_int_bak & SDXC_RespCRCErr ? " RCE" : "",
|
|
smc_priv->raw_int_bak & SDXC_DataCRCErr ? " DCE" : "",
|
|
smc_priv->raw_int_bak & SDXC_RespTimeout ? " RTO" : "",
|
|
smc_priv->raw_int_bak & SDXC_DataTimeout ? " DTO" : "",
|
|
smc_priv->raw_int_bak & SDXC_DataStarve ? " DS" : "",
|
|
smc_priv->raw_int_bak & SDXC_FIFORunErr ? " FE" : "",
|
|
smc_priv->raw_int_bak & SDXC_HardWLocked ? " HL" : "",
|
|
smc_priv->raw_int_bak & SDXC_StartBitErr ? " SBE" : "",
|
|
smc_priv->raw_int_bak & SDXC_EndBitErr ? " EBE" : "",
|
|
smc_priv->raw_int_bak == 0 ? " STO" : ""
|
|
);
|
|
}
|
|
|
|
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_priv *priv = &mmc_host[sdc_no];
|
|
struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
|
|
int cd_pin, ret = 0;
|
|
|
|
MMCDBG("init mmc %d resource\n", sdc_no);
|
|
|
|
switch (sdc_no) {
|
|
case 0:
|
|
priv->reg = (struct mmc_reg_v4p1 *)SUNXI_MMC0_BASE;
|
|
priv->mclkreg = &ccm->sd0_clk_cfg;
|
|
break;
|
|
case 1:
|
|
priv->reg = (struct mmc_reg_v4p1 *)SUNXI_MMC1_BASE;
|
|
priv->mclkreg = &ccm->sd1_clk_cfg;
|
|
break;
|
|
case 2:
|
|
priv->reg = (struct mmc_reg_v4p1 *)SUNXI_MMC2_BASE;
|
|
priv->mclkreg = &ccm->sd2_clk_cfg;
|
|
break;
|
|
#ifdef SUNXI_MMC3_BASE
|
|
case 3:
|
|
priv->reg = (struct mmc_reg_v4p1 *)SUNXI_MMC3_BASE;
|
|
priv->mclkreg = &ccm->sd3_clk_cfg;
|
|
break;
|
|
#endif
|
|
default:
|
|
MMCINFO("Wrong mmc number %d\n", sdc_no);
|
|
return -1;
|
|
}
|
|
|
|
#if !defined(CONFIG_MACH_SUN8IW5) && !defined(CONFIG_MACH_SUN8IW6) && !defined(CONFIG_MACH_SUN8IW7) && !defined(CONFIG_MACH_SUN8IW8)\
|
|
&& !defined(CONFIG_MACH_SUN8IW10) && !defined(CONFIG_MACH_SUN8IW11)
|
|
priv->hclkbase = IOMEM_ADDR(&ccm->sd_gate_reset);
|
|
priv->hclkrst = IOMEM_ADDR(&ccm->sd_gate_reset);
|
|
#else
|
|
priv->hclkbase = IOMEM_ADDR(&ccm->ahb_gate0);
|
|
priv->hclkrst = IOMEM_ADDR(&ccm->ahb_reset0_cfg);
|
|
#endif
|
|
priv->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);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_MACH_SUN55IW3
|
|
#ifdef CONFIG_CLK_SUNXI
|
|
priv->cfg.clk_mmc = clk_get(NULL, priv->cfg.clk_mmc_name);
|
|
if (IS_ERR_OR_NULL(priv->cfg.clk_mmc)) {
|
|
MMCINFO("Error to get clk_mmc\n");
|
|
return -1;
|
|
}
|
|
#else
|
|
MMCINFO("%s: need ccu config open\n", __func__);
|
|
return -1;
|
|
#endif
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int sunxi_mmc_pin_set(int sdc_no)
|
|
{
|
|
struct sunxi_mmc_priv *priv = &mmc_host[sdc_no];
|
|
struct sunxi_mmc_pininfo *pin_default = &priv->pin_default;
|
|
struct sunxi_mmc_pininfo *pin_disable = &priv->pin_disable;
|
|
int ret = -1;
|
|
|
|
if (priv->pwr_handler != 0 && pin_disable->pin_count > 0) {
|
|
ret = gpio_request_early(pin_disable->pin_set, pin_disable->pin_count, 1);
|
|
gpio_write_one_pin_value(priv->pwr_handler, 1, "card-pwr-gpios");
|
|
mdelay(priv->time_pwroff);
|
|
gpio_write_one_pin_value(priv->pwr_handler, 0, "card-pwr-gpios");
|
|
/*delay to ensure voltage stability*/
|
|
mdelay(1);
|
|
}
|
|
|
|
if (pin_default->pin_count > 0) {
|
|
ret = gpio_request_early(pin_default->pin_set, pin_default->pin_count, 1);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void sunxi_mmc_pin_release(int sdc_no)
|
|
{
|
|
struct sunxi_mmc_priv *priv = &mmc_host[sdc_no];
|
|
|
|
if (priv->pwr_handler != 0) {
|
|
gpio_release(priv->pwr_handler, 0);
|
|
}
|
|
}
|
|
|
|
int mmc_clk_io_onoff(int sdc_no, int onoff, int reset_clk)
|
|
{
|
|
struct sunxi_mmc_priv *priv = &mmc_host[sdc_no];
|
|
|
|
priv->sunxi_mmc_clk_io_onoff(sdc_no, onoff, reset_clk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_update_clk(struct sunxi_mmc_priv *priv)
|
|
{
|
|
unsigned int cmd;
|
|
unsigned timeout_msecs = 10000;
|
|
unsigned long start = get_timer(0);
|
|
|
|
writel(readl(&priv->reg->clkcr)|(0x1 << 31), &priv->reg->clkcr);
|
|
cmd = SUNXI_MMC_CMD_START |
|
|
SUNXI_MMC_CMD_UPCLK_ONLY |
|
|
SUNXI_MMC_CMD_WAIT_PRE_OVER;
|
|
|
|
writel(cmd, &priv->reg->cmd);
|
|
while (readl(&priv->reg->cmd) & SUNXI_MMC_CMD_START) {
|
|
if (get_timer(start) > timeout_msecs) {
|
|
dumphex32("mmc", (char *)priv->reg, 0x200);
|
|
return -1;
|
|
}
|
|
}
|
|
writel(readl(&priv->reg->clkcr) & (~(0x1 << 31)), &priv->reg->clkcr);
|
|
|
|
/* clock update sets various irq status bits, clear these */
|
|
writel(readl(&priv->reg->rint), &priv->reg->rint);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz)
|
|
{
|
|
int rval;
|
|
|
|
rval = priv->mmc_set_mod_clk(priv, hz);
|
|
return rval;
|
|
}
|
|
|
|
static int mmc_config_clock(struct sunxi_mmc_priv *priv, struct mmc *mmc)
|
|
{
|
|
unsigned rval = readl(&priv->reg->clkcr);
|
|
|
|
/* Disable Clock */
|
|
rval &= ~SUNXI_MMC_CLK_ENABLE;
|
|
writel(rval, &priv->reg->clkcr);
|
|
if (mmc_update_clk(priv)) {
|
|
MMCINFO("Disable clock: mmc update clk failed\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Set mod_clk to new rate */
|
|
if (mmc_set_mod_clk(priv, mmc->clock))
|
|
return -1;
|
|
#if 0
|
|
/* Clear internal divider */
|
|
rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK;
|
|
writel(rval, &priv->reg->clkcr);
|
|
#endif
|
|
/* Re-enable Clock */
|
|
rval = readl(&priv->reg->clkcr);
|
|
rval |= SUNXI_MMC_CLK_ENABLE;
|
|
writel(rval, &priv->reg->clkcr);
|
|
if (mmc_update_clk(priv)) {
|
|
MMCINFO("Re-enable clock: mmc update clk failed\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_mmc_set_ios_common(struct sunxi_mmc_priv *priv,
|
|
struct mmc *mmc)
|
|
{
|
|
MMCDBG("set ios: bus_width: %x, clock: %d\n",
|
|
mmc->bus_width, mmc->clock);
|
|
|
|
/* Change clock first */
|
|
if (mmc->clock && mmc_config_clock(priv, mmc) != 0) {
|
|
priv->fatal_err = 1;
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Change bus width */
|
|
if (mmc->bus_width == 8)
|
|
writel(0x2, &priv->reg->width);
|
|
else if (mmc->bus_width == 4)
|
|
writel(0x1, &priv->reg->width);
|
|
else
|
|
writel(0x0, &priv->reg->width);
|
|
|
|
/* set speed mode */
|
|
priv->sunxi_mmc_set_speed_mode(priv, mmc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if !CONFIG_IS_ENABLED(DM_MMC)
|
|
static int sunxi_mmc_core_init(struct mmc *mmc)
|
|
{
|
|
struct sunxi_mmc_priv *priv = mmc->priv;
|
|
|
|
priv->sunxi_mmc_core_init(mmc);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int mmc_save_regs(struct sunxi_mmc_priv *mmchost)
|
|
{
|
|
struct mmc_reg_v4p1 *reg = (struct mmc_reg_v4p1 *)mmchost->reg;
|
|
struct mmc_reg_v4p1 *reg_bak = (struct mmc_reg_v4p1 *)mmchost->reg_bak;
|
|
|
|
reg_bak->gctrl = readl(®->gctrl);
|
|
reg_bak->clkcr = readl(®->clkcr);
|
|
reg_bak->timeout = readl(®->timeout);
|
|
reg_bak->width = readl(®->width);
|
|
reg_bak->imask = readl(®->imask);
|
|
reg_bak->ftrglevel = readl(®->ftrglevel);
|
|
reg_bak->dbgc = readl(®->dbgc);
|
|
reg_bak->ntsr = readl(®->ntsr);
|
|
reg_bak->hwrst = readl(®->hwrst);
|
|
reg_bak->dmac = readl(®->dmac);
|
|
reg_bak->idie = readl(®->idie);
|
|
reg_bak->thldc = readl(®->thldc);
|
|
reg_bak->dsbd = readl(®->dsbd);
|
|
#if (!defined(CONFIG_MACH_SUN8IW7))
|
|
reg_bak->csdc = readl(®->csdc);
|
|
reg_bak->drv_dl = readl(®->drv_dl);
|
|
reg_bak->samp_dl = readl(®->samp_dl);
|
|
reg_bak->ds_dl = readl(®->ds_dl);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_restore_regs(struct sunxi_mmc_priv *mmchost)
|
|
{ struct mmc_reg_v4p1 *reg = (struct mmc_reg_v4p1 *)mmchost->reg;
|
|
struct mmc_reg_v4p1 *reg_bak = (struct mmc_reg_v4p1 *)mmchost->reg_bak;
|
|
|
|
writel(reg_bak->gctrl, ®->gctrl);
|
|
writel(reg_bak->clkcr, ®->clkcr);
|
|
writel(reg_bak->timeout, ®->timeout);
|
|
writel(reg_bak->width, ®->width);
|
|
writel(reg_bak->imask, ®->imask);
|
|
writel(reg_bak->ftrglevel, ®->ftrglevel);
|
|
if (reg_bak->dbgc)
|
|
writel(0xdeb, ®->dbgc);
|
|
writel(reg_bak->ntsr, ®->ntsr);
|
|
writel(reg_bak->hwrst, ®->hwrst);
|
|
writel(reg_bak->dmac, ®->dmac);
|
|
writel(reg_bak->idie, ®->idie);
|
|
writel(reg_bak->thldc, ®->thldc);
|
|
writel(reg_bak->dsbd, ®->dsbd);
|
|
#if (!defined(CONFIG_MACH_SUN8IW7))
|
|
writel(reg_bak->csdc, ®->csdc);
|
|
sunxi_r_op(mmchost, writel(reg_bak->drv_dl, ®->drv_dl));
|
|
writel(reg_bak->samp_dl, ®->samp_dl);
|
|
writel(reg_bak->ds_dl, ®->ds_dl);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_trans_data_by_cpu(struct sunxi_mmc_priv *priv, struct mmc *mmc,
|
|
struct mmc_data *data)
|
|
{
|
|
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;
|
|
unsigned long start;
|
|
|
|
if (timeout_msecs < 2000)
|
|
timeout_msecs = 2000;
|
|
|
|
/* Always read / write data through the CPU */
|
|
setbits_le32(&priv->reg->gctrl, SUNXI_MMC_GCTRL_ACCESS_BY_AHB);
|
|
|
|
start = get_timer(0);
|
|
|
|
for (i = 0; i < (byte_cnt >> 2); i++) {
|
|
while (readl(&priv->reg->status) & status_bit) {
|
|
if (get_timer(start) > timeout_msecs)
|
|
return -1;
|
|
}
|
|
|
|
if (reading)
|
|
buff[i] = readl(&priv->reg->fifo);
|
|
else
|
|
writel(buff[i], &priv->reg->fifo);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_trans_data_by_dma(struct sunxi_mmc_priv *priv, struct mmc *mmc, struct mmc_data *data)
|
|
{
|
|
struct mmc_des_v4p1 *pdes = priv->pdes;
|
|
unsigned byte_cnt = data->blocksize * data->blocks;
|
|
unsigned char *buff;
|
|
unsigned des_idx = 0;
|
|
unsigned buff_frag_num = 0;
|
|
unsigned remain;
|
|
unsigned i, rval;
|
|
|
|
buff = data->flags & MMC_DATA_READ ?
|
|
(unsigned char *)data->dest : (unsigned char *)data->src;
|
|
buff_frag_num = byte_cnt >> SDXC_DES_NUM_SHIFT;
|
|
remain = byte_cnt & (SDXC_DES_BUFFER_MAX_LEN - 1);
|
|
if (remain)
|
|
buff_frag_num++;
|
|
else
|
|
remain = SDXC_DES_BUFFER_MAX_LEN;
|
|
flush_cache((unsigned long)buff, ALIGN((unsigned long)byte_cnt, CONFIG_SYS_CACHELINE_SIZE));
|
|
for (i = 0; i < buff_frag_num; i++, des_idx++) {
|
|
memset((void *)&pdes[des_idx], 0, sizeof(struct mmc_des_v4p1));
|
|
pdes[des_idx].des_chain = 1;
|
|
pdes[des_idx].own = 1;
|
|
pdes[des_idx].dic = 1;
|
|
if (buff_frag_num > 1 && i != buff_frag_num - 1)
|
|
pdes[des_idx].data_buf1_sz = SDXC_DES_BUFFER_MAX_LEN;
|
|
else
|
|
pdes[des_idx].data_buf1_sz = remain;
|
|
if (priv->version == 0x40200 || priv->version == 0x40502 || priv->version >= 0x50300 || priv->version == 0x40104)
|
|
pdes[des_idx].buf_addr_ptr1 = ((ulong)buff + i * SDXC_DES_BUFFER_MAX_LEN)
|
|
>> 2;
|
|
else
|
|
pdes[des_idx].buf_addr_ptr1 = ((ulong)buff + i * SDXC_DES_BUFFER_MAX_LEN);
|
|
if (i == 0)
|
|
pdes[des_idx].first_des = 1;
|
|
|
|
if (i == buff_frag_num - 1) {
|
|
pdes[des_idx].dic = 0;
|
|
pdes[des_idx].last_des = 1;
|
|
pdes[des_idx].end_of_ring = 1;
|
|
pdes[des_idx].buf_addr_ptr2 = 0;
|
|
} else {
|
|
if (priv->version == 0x40200 || priv->version == 0x40502 || priv->version >= 0x50300 || priv->version == 0x40104)
|
|
pdes[des_idx].buf_addr_ptr2 = ((ulong)&pdes[des_idx + 1]) >> 2;
|
|
else
|
|
pdes[des_idx].buf_addr_ptr2 = ((ulong)&pdes[des_idx + 1]);
|
|
}
|
|
MMCDBG("frag %d, remain %d, des[%d](%08x): "
|
|
"[0] = %08x, [1] = %08x, [2] = %08x, [3] = %08x\n",
|
|
i, remain, des_idx, PT_TO_PHU(&pdes[des_idx]),
|
|
(u32)((u32 *)&pdes[des_idx])[0], (u32)((u32 *)&pdes[des_idx])[1],
|
|
(u32)((u32 *)&pdes[des_idx])[2], (u32)((u32 *)&pdes[des_idx])[3]);
|
|
}
|
|
flush_cache((unsigned long)pdes, ALIGN(sizeof(struct mmc_des_v4p1) * (des_idx + 1), CONFIG_SYS_CACHELINE_SIZE));
|
|
|
|
WR_MB();
|
|
|
|
/*
|
|
* GCTRLREG
|
|
* GCTRL[2] : DMA reset
|
|
* GCTRL[5] : DMA enable
|
|
*
|
|
* IDMACREG
|
|
* IDMAC[0] : IDMA soft reset
|
|
* IDMAC[1] : IDMA fix burst flag
|
|
* IDMAC[7] : IDMA on
|
|
*
|
|
* IDIECREG
|
|
* IDIE[0] : IDMA transmit interrupt flag
|
|
* IDIE[1] : IDMA receive interrupt flag
|
|
*/
|
|
rval = readl(&priv->reg->gctrl);
|
|
writel(rval | (1 << 5) | (1 << 2), &priv->reg->gctrl); /* dma enable */
|
|
writel((1 << 0), &priv->reg->dmac); /* idma reset */
|
|
while (readl(&priv->reg->dmac) & 0x1) {
|
|
} /* wait idma reset done */
|
|
|
|
writel((1 << 1) | (1 << 7), &priv->reg->dmac); /* idma on */
|
|
rval = readl(&priv->reg->idie) & (~3);
|
|
if (data->flags & MMC_DATA_WRITE)
|
|
rval |= (1 << 0);
|
|
else
|
|
rval |= (1 << 1);
|
|
writel(rval, &priv->reg->idie);
|
|
|
|
if (priv->version == 0x40200 || priv->version == 0x40502 || priv->version >= 0x50300 || priv->version == 0x40104)
|
|
writel(((unsigned long)pdes) >> 2, &priv->reg->dlba);
|
|
else
|
|
writel(((unsigned long)pdes), &priv->reg->dlba);
|
|
writel(priv->dma_tl, &priv->reg->ftrglevel);
|
|
return 0;
|
|
}
|
|
|
|
static int mmc_rint_wait(struct sunxi_mmc_priv *priv, struct mmc *mmc,
|
|
uint timeout_msecs, uint done_bit, const char *what, uint usedma)
|
|
{
|
|
unsigned int status;
|
|
unsigned int done = 0;
|
|
unsigned long start = get_timer(0);
|
|
do {
|
|
status = readl(&priv->reg->rint);
|
|
if ((get_timer(start) > timeout_msecs) || (status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT)) {
|
|
MMCMSG(mmc, "mmc %d %s timeout %x status %x\n", priv->mmc_no, what,
|
|
status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT, status);
|
|
return -ETIMEDOUT;
|
|
}
|
|
if (usedma && !strncmp(what, "data", sizeof("data")))
|
|
done = ((status & done_bit) && (readl(&priv->reg->idst) & 0x3)) ? 1 : 0;
|
|
else
|
|
done = (status & done_bit);
|
|
} while (!done);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sunxi_mmc_set_rdtmout_reg(struct sunxi_mmc_priv *priv, struct mmc *mmc,
|
|
unsigned int rdtmout)
|
|
{
|
|
unsigned int rval = 0;
|
|
unsigned int rdto_clk = 0;
|
|
unsigned int mode_2x = 0;
|
|
unsigned int hs400_ntm_en = 0;
|
|
|
|
rdto_clk = mmc->clock / 1000 * rdtmout;
|
|
rval = readl(&priv->reg->ntsr);
|
|
mode_2x = rval & (0x1 << 31);
|
|
hs400_ntm_en = rval & 0x1;
|
|
|
|
if ((mmc->speed_mode == HS400 && hs400_ntm_en)
|
|
|| (mmc->speed_mode == HSDDR52_DDR50 && mmc->bus_width == 8)
|
|
|| (mmc->speed_mode == HSDDR52_DDR50 && mmc->bus_width == 4 && mode_2x)) {
|
|
rdto_clk = rdto_clk << 1;
|
|
}
|
|
|
|
rval = readl(&priv->reg->gctrl);
|
|
/*ddr50 mode don't use 256x timeout unit*/
|
|
if (rdto_clk > 0xffffff && mmc->speed_mode != HSDDR52_DDR50) {
|
|
rdto_clk = (rdto_clk + 255)/256;
|
|
rval |= (0x1 << 11);
|
|
} else {
|
|
rdto_clk = 0xffffff;
|
|
rval &= ~(0x1 << 11);
|
|
}
|
|
writel(rval, &priv->reg->gctrl);
|
|
|
|
rval = readl(&priv->reg->timeout);
|
|
rval &= ~(0xffffff << 8);
|
|
rval |= (rdto_clk << 8);
|
|
writel(rval, &priv->reg->timeout);
|
|
|
|
MMCDBG("rdtoclk:%d, reg-tmout:%d, gctl:%x, speed_mode:%d, clock:%d, nstr:%x\n",
|
|
rdto_clk, readl(&priv->reg->timeout), readl(&priv->reg->gctrl),
|
|
mmc->speed_mode, mmc->clock, readl(&priv->reg->ntsr));
|
|
}
|
|
|
|
static int sunxi_mmc_do_send_cmd_common(struct sunxi_mmc_priv *priv,
|
|
struct mmc *mmc, struct mmc_cmd *cmd,
|
|
struct mmc_data *data)
|
|
{
|
|
unsigned int cmdval = SUNXI_MMC_CMD_START;
|
|
unsigned int timeout_msecs;
|
|
int error = 0;
|
|
unsigned int status = 0;
|
|
unsigned int usedma = 0;
|
|
unsigned int bytecnt = 0;
|
|
|
|
if (priv->fatal_err) {
|
|
MMCINFO("mmc %d Found fatal err,so no send cmd\n", priv->mmc_no);
|
|
return -1;
|
|
}
|
|
if (cmd->resp_type & MMC_RSP_BUSY)
|
|
MMCDBG("mmc cmd %d check rsp busy\n", cmd->cmdidx);
|
|
if (cmd->cmdidx == 12 && mmc->manual_stop_flag == 0) {
|
|
MMCDBG("usually, cmd12 is sent after cmd18/cmd25 automantically.\n");
|
|
/* don't wait write busy here, because no cmd12 will be sent for cmd24.
|
|
* write busy status will be check after sent cmd25. */
|
|
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 0
|
|
/* write ds dly Traversal test*/
|
|
if ((uboot_spare_head.boot_data.work_mode == WORK_MODE_BOOT)
|
|
&& ((cmd->cmdidx == 24) || (cmd->cmdidx == 25))) {
|
|
unsigned tmode = priv->timing_mode;
|
|
printf("===========there is write opration!========\n");
|
|
if (mmc->speed_mode == HS200_SDR104 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((40 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hs200 samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
} else if (mmc->speed_mode == HSSDR52_SDR25 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((38 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hssdr samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
} else if (mmc->speed_mode == HS400 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->ds_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((31 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->ds_dl);
|
|
printf("hs400 ds 0x%x\n", readl(&priv->reg->ds_dl));
|
|
}
|
|
}
|
|
|
|
#endif
|
|
#if 0
|
|
/*read ds dly Traversal test*/
|
|
if ((uboot_spare_head.boot_data.work_mode == WORK_MODE_BOOT)
|
|
&& ((cmd->cmdidx == 17) || (cmd->cmdidx == 18))) {
|
|
unsigned tmode = priv->timing_mode;
|
|
int work_mode = uboot_spare_head.boot_data.work_mode;
|
|
if (work_mode != WORK_MODE_BOOT
|
|
|| (mmc->do_tuning == 0x1 && mmc->tuning_end == 0x0)
|
|
|| (mmc->cfg->sample_mode != AUTO_SAMPLE_MODE)) {
|
|
} else {
|
|
printf("===========There is read opration!========\n");
|
|
if (mmc->speed_mode == HS200_SDR104 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((40 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hs200 samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
} else if (mmc->speed_mode == HSSDR52_SDR25 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((38 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hssdr samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
} else if (mmc->speed_mode == HS400 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->ds_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((30 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->ds_dl);
|
|
printf("hs400 ds 0x%x\n", readl(&priv->reg->ds_dl));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
#if 0
|
|
/*read ds retry test*/
|
|
if ((uboot_spare_head.boot_data.work_mode == WORK_MODE_BOOT)
|
|
&& ((cmd->cmdidx == 18) || (cmd->cmdidx == 17))) {
|
|
unsigned tmode = priv->timing_mode;
|
|
int work_mode = uboot_spare_head.boot_data.work_mode;
|
|
static int i = 1;
|
|
if (work_mode != WORK_MODE_BOOT
|
|
|| (mmc->do_tuning == 0x1 && mmc->tuning_end == 0x0)
|
|
|| (mmc->cfg->sample_mode != AUTO_SAMPLE_MODE)) {
|
|
} else {
|
|
printf("===========There is read opration!========\n");
|
|
if ((i < 5) && (tmode == HS400)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->ds_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((40 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->ds_dl);
|
|
MMCINFO("ds %x\n", readl(&priv->reg->ds_dl));
|
|
i++;
|
|
} else
|
|
MMCINFO("end mauanl failed%d\n", i);
|
|
}
|
|
}
|
|
#endif
|
|
#if 0
|
|
/*write ds delay retry test*/
|
|
if ((uboot_spare_head.boot_data.work_mode == WORK_MODE_BOOT)
|
|
&& ((cmd->cmdidx == 24) || (cmd->cmdidx == 25))) {
|
|
unsigned tmode = priv->timing_mode;
|
|
int work_mode = uboot_spare_head.boot_data.work_mode;
|
|
static int i = 1;
|
|
if (work_mode != WORK_MODE_BOOT
|
|
|| (mmc->do_tuning == 0x1 && mmc->tuning_end == 0x0)
|
|
|| (mmc->cfg->sample_mode != AUTO_SAMPLE_MODE)) {
|
|
} else {
|
|
if ((i < 5) && (tmode == HS400)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->ds_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((58 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->ds_dl);
|
|
printf("ds %x\n", readl(&priv->reg->ds_dl));
|
|
i++;
|
|
} else
|
|
MMCINFO("end\n");
|
|
}
|
|
}
|
|
#endif
|
|
#if 0
|
|
/*sdlay retry test*/
|
|
if ((uboot_spare_head.boot_data.work_mode == WORK_MODE_BOOT)
|
|
&& ((cmd->cmdidx == 24) || (cmd->cmdidx == 25))) {
|
|
unsigned tmode = priv->timing_mode;
|
|
int work_mode = uboot_spare_head.boot_data.work_mode;
|
|
static int i = 1;
|
|
if (work_mode != WORK_MODE_BOOT
|
|
|| (mmc->do_tuning == 0x1 && mmc->tuning_end == 0x0)
|
|
|| (mmc->cfg->sample_mode != AUTO_SAMPLE_MODE)) {
|
|
} else {
|
|
if ((i < 3) && (tmode == SUNXI_MMC_TIMING_MODE_4) && (mmc->speed_mode == HS200_SDR104)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((54 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
MMCINFO("***********samp %x\n", readl(&priv->reg->samp_dl));
|
|
i++;
|
|
} else
|
|
MMCINFO("end %d tm %d\n", i, tmode);
|
|
}
|
|
}
|
|
#endif
|
|
#if 0
|
|
/*sdly Traversal write*/
|
|
if ((uboot_spare_head.boot_data.work_mode == WORK_MODE_BOOT)
|
|
&& ((cmd->cmdidx == 24) || (cmd->cmdidx == 25))) {
|
|
unsigned tmode = priv->timing_mode;
|
|
int work_mode = uboot_spare_head.boot_data.work_mode;
|
|
if (work_mode != WORK_MODE_BOOT
|
|
|| (mmc->do_tuning == 0x1 && mmc->tuning_end == 0x0)
|
|
|| (mmc->cfg->sample_mode != AUTO_SAMPLE_MODE)) {
|
|
} else {
|
|
printf("===========there is write opration!========\n");
|
|
if (mmc->speed_mode == HS200_SDR104 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((40 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hs200 samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
} else if (mmc->speed_mode == HSSDR52_SDR25 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((45 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hssdr samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#if 0
|
|
/*sdly Traversal read*/
|
|
if ((uboot_spare_head.boot_data.work_mode == WORK_MODE_BOOT)
|
|
&& ((cmd->cmdidx == 17) || (cmd->cmdidx == 18))) {
|
|
unsigned tmode = priv->timing_mode;
|
|
int work_mode = uboot_spare_head.boot_data.work_mode;
|
|
if (work_mode != WORK_MODE_BOOT
|
|
|| (mmc->do_tuning == 0x1 && mmc->tuning_end == 0x0)
|
|
|| (mmc->cfg->sample_mode != AUTO_SAMPLE_MODE)) {
|
|
} else {
|
|
printf("===========There is read opration!========\n");
|
|
if (mmc->speed_mode == HS200_SDR104 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((40 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hs200 samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
} else if (mmc->speed_mode == HSSDR52_SDR25 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((47 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hssdr samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
} else if (mmc->speed_mode == HS400 && (tmode == SUNXI_MMC_TIMING_MODE_4)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((53 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
writel(rval, &priv->reg->samp_dl);
|
|
printf("hs400 samp 0x%x\n", readl(&priv->reg->samp_dl));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#if 0
|
|
/*read samply delay retry test*/
|
|
if ((uboot_spare_head.boot_data.work_mode == WORK_MODE_BOOT)
|
|
&& ((cmd->cmdidx == 18) || (cmd->cmdidx == 17))) {
|
|
unsigned tmode = priv->timing_mode;
|
|
int work_mode = uboot_spare_head.boot_data.work_mode;
|
|
static int i = 1;
|
|
if (work_mode != WORK_MODE_BOOT
|
|
|| (mmc->do_tuning == 0x1 && mmc->tuning_end == 0x0)
|
|
|| (mmc->cfg->sample_mode != AUTO_SAMPLE_MODE)) {
|
|
} else {
|
|
printf("===========There is read opration!========\n");
|
|
if ((i < 4) && (tmode == HS400)) {
|
|
u32 rval = 0;
|
|
rval = readl(&priv->reg->samp_dl);
|
|
rval &= (~SDXC_CfgDly);
|
|
rval |= ((53 & SDXC_CfgDly) | SDXC_EnableDly);
|
|
#ifdef FPGA_PLATFORM
|
|
rval &= (~0x7);
|
|
#endif
|
|
writel(rval, &priv->reg->samp_dl);
|
|
MMCINFO("samp %x\n", readl(&priv->reg->samp_dl));
|
|
i++;
|
|
} else
|
|
MMCINFO("end mauanl failed%d\n", i);
|
|
}
|
|
}
|
|
#endif
|
|
if (data) {
|
|
if ((u32)(long)data->dest & 0x3) {
|
|
error = -1;
|
|
MMCINFO("%s,%d,dest is not 4 aligned\n", __FUNCTION__, __LINE__);
|
|
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, &priv->reg->blksz);
|
|
writel(data->blocks * data->blocksize, &priv->reg->bytecnt);
|
|
} else {
|
|
if (cmd->cmdidx == 12 && mmc->manual_stop_flag == 1) {
|
|
/*stop current data transferin progress.*/
|
|
cmdval |= SUNXI_MMC_CMD_STOP_ABORT;
|
|
/*Send command at once, even if previous data transfer has not completed*/
|
|
cmdval &= ~SUNXI_MMC_CMD_WAIT_PRE_OVER;
|
|
}
|
|
}
|
|
|
|
MMCDBG("mmc %d, cmd %d(0x%08x), arg 0x%08x\n", priv->mmc_no,
|
|
cmd->cmdidx, cmdval | cmd->cmdidx, cmd->cmdarg);
|
|
writel(cmd->cmdarg, &priv->reg->arg);
|
|
|
|
if (!data)
|
|
writel(cmdval | cmd->cmdidx, &priv->reg->cmd);
|
|
/*
|
|
* transfer data and check status
|
|
* STATREG[2] : FIFO empty
|
|
* STATREG[3] : FIFO full
|
|
*/
|
|
if (data) {
|
|
int ret = 0;
|
|
|
|
/*dto set to 200ms*/
|
|
sunxi_mmc_set_rdtmout_reg(priv, mmc, DTO_MAX);
|
|
|
|
bytecnt = data->blocksize * data->blocks;
|
|
MMCDBG("trans data %d bytes\n", bytecnt);
|
|
#ifdef CONFIG_MMC_SUNXI_USE_DMA
|
|
if (bytecnt > 64) {
|
|
#else
|
|
if (0) {
|
|
#endif
|
|
usedma = 1;
|
|
writel(readl(&priv->reg->gctrl) & (~SUNXI_MMC_GCTRL_ACCESS_BY_AHB), &priv->reg->gctrl);
|
|
ret = mmc_trans_data_by_dma(priv, mmc, data);
|
|
writel(cmdval | cmd->cmdidx, &priv->reg->cmd);
|
|
} else {
|
|
writel(readl(&priv->reg->gctrl) | SUNXI_MMC_GCTRL_ACCESS_BY_AHB, &priv->reg->gctrl);
|
|
writel(cmdval | cmd->cmdidx, &priv->reg->cmd);
|
|
ret = mmc_trans_data_by_cpu(priv, mmc, data);
|
|
}
|
|
if (ret) {
|
|
error = readl(&priv->reg->rint) &
|
|
SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT;
|
|
error = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
error = mmc_rint_wait(priv, mmc, 1000, SUNXI_MMC_RINT_COMMAND_DONE,
|
|
"cmd", usedma);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
|
|
if (data) {
|
|
timeout_msecs = 6000;
|
|
MMCDBG("cacl timeout %x msec\n", timeout_msecs);
|
|
error = mmc_rint_wait(priv, mmc, timeout_msecs,
|
|
data->blocks > 1 ?
|
|
SUNXI_MMC_RINT_AUTO_COMMAND_DONE :
|
|
SUNXI_MMC_RINT_DATA_OVER,
|
|
"data", usedma);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if ((cmd->resp_type & MMC_RSP_BUSY) ||
|
|
((data) && (data->flags & MMC_DATA_WRITE))) {
|
|
unsigned long start = get_timer(0);
|
|
if ((cmd->cmdidx == MMC_CMD_ERASE) ||
|
|
((cmd->cmdidx == MMC_CMD_SWITCH) &&
|
|
(((cmd->cmdarg >> 16) & 0xFF) == EXT_CSD_SANITIZE_START)))
|
|
timeout_msecs = 0x1fffffff;
|
|
else
|
|
timeout_msecs = 2000;
|
|
|
|
do {
|
|
status = readl(&priv->reg->status);
|
|
if (get_timer(start) > timeout_msecs) {
|
|
MMCDBG("busy timeout\n");
|
|
error = -ETIMEDOUT;
|
|
goto out;
|
|
}
|
|
} while (status & SUNXI_MMC_STATUS_CARD_DATA_BUSY);
|
|
if ((cmd->cmdidx == MMC_CMD_ERASE) ||
|
|
((cmd->cmdidx == MMC_CMD_SWITCH) &&
|
|
(((cmd->cmdarg >> 16) & 0xFF) == EXT_CSD_SANITIZE_START)))
|
|
MMCINFO("%s: cmd %d wait rsp busy 0x%x ms \n", __FUNCTION__,
|
|
cmd->cmdidx, get_timer(start));
|
|
}
|
|
|
|
if (cmd->resp_type & MMC_RSP_136) {
|
|
cmd->response[0] = readl(&priv->reg->resp3);
|
|
cmd->response[1] = readl(&priv->reg->resp2);
|
|
cmd->response[2] = readl(&priv->reg->resp1);
|
|
cmd->response[3] = readl(&priv->reg->resp0);
|
|
MMCDBG("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(&priv->reg->resp0);
|
|
MMCDBG("mmc resp 0x%08x\n", cmd->response[0]);
|
|
}
|
|
out:
|
|
if (error) {
|
|
priv->raw_int_bak = readl(&priv->reg->rint) & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT;
|
|
mmc_dump_errinfo(priv, cmd);
|
|
#if 0
|
|
if (cmd->cmdidx == 8) {
|
|
dumphex32("mmc", (char *)priv->reg, 0x200);
|
|
dumphex32("ccmu_mmc0", (char *)SUNXI_CCM_BASE + 0x830, 0x4);
|
|
dumphex32("ccmu_pll", (char *)SUNXI_PRCM_BASE + 0x1010, 0x10);
|
|
dumphex32("ccmu_bgr", (char *)SUNXI_CCM_BASE + 0x84C, 0x4);
|
|
dumphex32("ccmu_srr", (char *)SUNXI_CCM_BASE + 0x84C, 0x4);
|
|
dumphex32("gpio_config", (char *)SUNXI_PIO_BASE + 0x48, 0x10);
|
|
dumphex32("gpio_pull", (char *)SUNXI_PIO_BASE + 0x64, 0x8);
|
|
}
|
|
#endif
|
|
}
|
|
if (data && usedma) {
|
|
status = readl(&priv->reg->idst);
|
|
writel(status, &priv->reg->idst);
|
|
writel(0, &priv->reg->idie);
|
|
writel(0, &priv->reg->dmac);
|
|
writel(readl(&priv->reg->gctrl) & (~(1 << 5)), &priv->reg->gctrl);
|
|
}
|
|
if (error < 0) {
|
|
/* during tuning sample point, some sample point may cause timing problem.
|
|
for example, if a RTO error occurs, host may stop clock and device may still output data.
|
|
we need to read all data(512bytes) from device to avoid to update clock fail.
|
|
*/
|
|
signed int timeout = 0;
|
|
if (mmc->do_tuning && data && (data->flags&MMC_DATA_READ) && (bytecnt == 512)) {
|
|
writel(readl(&priv->reg->gctrl)|0x80000000, &priv->reg->gctrl);
|
|
writel(0xdeb, &priv->reg->dbgc);
|
|
timeout = 1000;
|
|
MMCMSG(mmc, "Read remain data\n");
|
|
while (readl(&priv->reg->bbcr) < 512) {
|
|
unsigned int tmp = readl(priv->reg->fifo);
|
|
tmp = tmp + 1;
|
|
MMCDBG("Read data 0x%x, bbcr 0x%x\n", tmp, readl(&priv->reg->bbcr));
|
|
__usdelay(1);
|
|
if (!(timeout--)) {
|
|
MMCMSG(mmc, "Read remain data timeout\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
writel(0x7, &priv->reg->gctrl);
|
|
while (readl(&priv->reg->gctrl)&0x7) {
|
|
MMCDBG("mmc reset dma fifo and fifo\n");
|
|
};
|
|
|
|
{
|
|
mmc_save_regs(priv);
|
|
mmc_clk_io_onoff(priv->mmc_no, 0, 0);
|
|
MMCMSG(mmc, "mmc %d close bus gating and reset\n", priv->mmc_no);
|
|
mmc_clk_io_onoff(priv->mmc_no, 1, 0);
|
|
mmc_restore_regs(priv);
|
|
|
|
writel(0x7, &priv->reg->gctrl);
|
|
while (readl(&priv->reg->gctrl)&0x7) {
|
|
MMCDBG("mmc reset dma fifo and fifo\n");
|
|
};
|
|
}
|
|
|
|
writel(SUNXI_MMC_GCTRL_RESET, &priv->reg->gctrl);
|
|
mmc_update_clk(priv);
|
|
}
|
|
writel(0xffffffff, &priv->reg->rint);
|
|
writel(readl(&priv->reg->gctrl) | SUNXI_MMC_GCTRL_FIFO_RESET,
|
|
&priv->reg->gctrl);
|
|
if (data && (data->flags&MMC_DATA_READ) && usedma) {
|
|
unsigned char *buff = (unsigned char *)data->dest;
|
|
unsigned byte_cnt = data->blocksize * data->blocks;
|
|
invalidate_dcache_range((unsigned long)buff,
|
|
((unsigned long)buff + ALIGN((unsigned long)byte_cnt,
|
|
CONFIG_SYS_CACHELINE_SIZE)));
|
|
|
|
MMCDBG("invald cache after read complete\n");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int mmc_raw_send_manual_stop(struct mmc *mmc)
|
|
{
|
|
int ret = 0;
|
|
struct mmc_cmd cmd;
|
|
struct sunxi_mmc_priv *priv = mmc->priv;
|
|
|
|
cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
|
|
cmd.cmdarg = 0;
|
|
cmd.resp_type = MMC_RSP_R1b;
|
|
mmc->manual_stop_flag = 1;
|
|
ret = sunxi_mmc_do_send_cmd_common(priv, mmc, &cmd, NULL);
|
|
if (ret)
|
|
MMCINFO("bsp send manual stop failed\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mmc_check_r1_ready(struct mmc *mmc, u32 timeout_us)
|
|
{
|
|
struct sunxi_mmc_priv *priv = mmc->priv;
|
|
struct mmc_reg_v4p1 *reg = priv->reg;
|
|
int error = 0;
|
|
unsigned int status = 0;
|
|
|
|
do {
|
|
status = readl(®->status);
|
|
if (!timeout_us--) {
|
|
error = -1;
|
|
MMCINFO("mmc %d check busy timeout %u\n", priv->mmc_no, timeout_us);
|
|
goto out;
|
|
}
|
|
__usdelay(1);
|
|
} while (status & (1 << 9));
|
|
out:
|
|
MMCDBG("host bsp ready\n");
|
|
return error;
|
|
}
|
|
|
|
static int sunxi_mmc_send_cmd_common(struct sunxi_mmc_priv *priv,
|
|
struct mmc *mmc, struct mmc_cmd *cmd,
|
|
struct mmc_data *data)
|
|
{
|
|
int work_mode = uboot_spare_head.boot_data.work_mode;
|
|
int err = 0;
|
|
int has_reinit = 0;
|
|
|
|
host_retry:
|
|
err = sunxi_mmc_do_send_cmd_common(priv, mmc, cmd, data);
|
|
if (work_mode != WORK_MODE_BOOT
|
|
|| (mmc->do_tuning == 0x1 && mmc->tuning_end == 0x0)
|
|
|| (mmc->cfg->sample_mode != AUTO_SAMPLE_MODE)) {
|
|
return err;
|
|
}
|
|
|
|
if (err) {
|
|
if (!has_reinit) {
|
|
if (sunxi_need_rty(mmc)) {
|
|
#if 0
|
|
MMCINFO("start reinit\n");
|
|
struct mmc_config *cfg = (struct mmc_config *) (mmc->cfg);
|
|
mmc->has_init = 0;
|
|
cfg->host_caps &= ~(MMC_MODE_HS400 | MMC_MODE_HS200 | MMC_MODE_DDR_52MHz);
|
|
mmc->speed_mode = 0;
|
|
mmc->cfg->ops->init(mmc);
|
|
mmc_set_bus_width(mmc, 1);
|
|
mmc_set_clock(mmc, 1, false);
|
|
mmc_init(mmc);
|
|
has_reinit = 1;
|
|
goto host_retry;
|
|
#else
|
|
MMCINFO("give up reinit\n");
|
|
mmc->cfg->ops->decide_retry(mmc, 0, 1);
|
|
return err;
|
|
#endif
|
|
} else {
|
|
MMCINFO("host retry\n");
|
|
mmc_raw_send_manual_stop(mmc);
|
|
mmc_check_r1_ready(mmc, 1000*1000);
|
|
mmc->cfg->ops->set_ios(mmc);
|
|
goto host_retry;
|
|
}
|
|
} else {
|
|
MMCINFO("retry giveup!\n");
|
|
mmc->cfg->ops->decide_retry(mmc, 0, 1);
|
|
}
|
|
} else {
|
|
MMCDBG("Reset retry cnt\n");
|
|
mmc->cfg->ops->decide_retry(mmc, 0, 1);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#if !CONFIG_IS_ENABLED(DM_MMC)
|
|
static int sunxi_mmc_set_ios_legacy(struct mmc *mmc)
|
|
{
|
|
struct sunxi_mmc_priv *priv = mmc->priv;
|
|
|
|
return sunxi_mmc_set_ios_common(priv, mmc);
|
|
}
|
|
|
|
static int sunxi_mmc_send_cmd_legacy(struct mmc *mmc, struct mmc_cmd *cmd,
|
|
struct mmc_data *data)
|
|
{
|
|
struct sunxi_mmc_priv *priv = mmc->priv;
|
|
|
|
return sunxi_mmc_send_cmd_common(priv, mmc, cmd, data);
|
|
}
|
|
|
|
static int sunxi_mmc_getcd_legacy(struct mmc *mmc)
|
|
{
|
|
struct sunxi_mmc_priv *priv = mmc->priv;
|
|
int cd_pin;
|
|
|
|
cd_pin = sunxi_mmc_getcd_gpio(priv->mmc_no);
|
|
if (cd_pin < 0)
|
|
return 1;
|
|
|
|
return !gpio_get_value(cd_pin);
|
|
}
|
|
|
|
static int sunxi_tm4_retry(struct mmc *mmc, u8 tm4_retry_gap, u8 type)
|
|
{
|
|
struct sunxi_mmc_priv *priv = (struct sunxi_mmc_priv *)mmc->priv;
|
|
u32 spd_md, freq;
|
|
u8 *sdly;
|
|
|
|
spd_md = priv->tm4.cur_spd_md;
|
|
freq = priv->tm4.cur_freq;
|
|
|
|
if (type == 1) {
|
|
sdly = &priv->tm4.sdly[spd_md*MAX_CLK_FREQ_NUM+freq];
|
|
MMCINFO("Current spd_md %d freq_id %d sdly %d\n", spd_md, freq, *sdly);
|
|
priv->sd_retry_cnt++;
|
|
if (priv->sd_retry_cnt * tm4_retry_gap < MMC_CLK_SAMPLE_POINIT_MODE_4) {
|
|
if ((*sdly + tm4_retry_gap) < MMC_CLK_SAMPLE_POINIT_MODE_4) {
|
|
*sdly = *sdly + tm4_retry_gap;
|
|
} else {
|
|
*sdly = *sdly + tm4_retry_gap - MMC_CLK_SAMPLE_POINIT_MODE_4;
|
|
}
|
|
MMCINFO("Get next samply point %d at spd_md %d freq_id %d\n", *sdly, spd_md, freq);
|
|
} else {
|
|
MMCINFO("Beyond the sdly retry times\n");
|
|
return -1;
|
|
}
|
|
} else {
|
|
sdly = &priv->tm4.dsdly[freq];
|
|
MMCINFO("Current spd_md %d freq_id %d dsdly %d\n", spd_md, freq, *sdly);
|
|
priv->dsd_retry_cnt++;
|
|
if (priv->dsd_retry_cnt * tm4_retry_gap < MMC_CLK_SAMPLE_POINIT_MODE_4) {
|
|
if ((*sdly + tm4_retry_gap) < MMC_CLK_SAMPLE_POINIT_MODE_4) {
|
|
*sdly = *sdly + tm4_retry_gap;
|
|
} else {
|
|
*sdly = *sdly + tm4_retry_gap - MMC_CLK_SAMPLE_POINIT_MODE_4;
|
|
}
|
|
MMCINFO("Get next ds point %d at spd_md %d freq_id %d\n", *sdly, spd_md, freq);
|
|
} else {
|
|
MMCINFO("Beyond the dsdly retry times\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_decide_rty(struct mmc *mmc, int err_no, uint rst_cnt)
|
|
{
|
|
struct sunxi_mmc_priv *priv = (struct sunxi_mmc_priv *)mmc->priv;
|
|
unsigned tmode = priv->timing_mode;
|
|
u32 spd_md, freq;
|
|
u8 *sdly;
|
|
u8 tm1_retry_gap = 1;
|
|
u8 tm3_retry_gap = 8;
|
|
#ifdef SUNXI_MMC_RETRY_TEST
|
|
u8 tm4_retry_gap = 32;
|
|
#else
|
|
u8 tm4_retry_gap = 2;
|
|
#endif
|
|
|
|
if (rst_cnt) {
|
|
priv->sd_retry_cnt = 0;
|
|
priv->dsd_retry_cnt = 0;
|
|
}
|
|
|
|
if (err_no && (!(err_no & SDXC_RespTimeout) || (err_no == 0xffffffff))) {
|
|
if (tmode == SUNXI_MMC_TIMING_MODE_1) {
|
|
spd_md = priv->tm1.cur_spd_md;
|
|
freq = priv->tm1.cur_freq;
|
|
sdly = &priv->tm1.sdly[spd_md*MAX_CLK_FREQ_NUM+freq];
|
|
|
|
priv->sd_retry_cnt++;
|
|
if (priv->sd_retry_cnt * tm1_retry_gap < MMC_CLK_SAMPLE_POINIT_MODE_1) {
|
|
if ((*sdly + tm1_retry_gap) < MMC_CLK_SAMPLE_POINIT_MODE_1) {
|
|
*sdly = *sdly + tm1_retry_gap;
|
|
} else {
|
|
*sdly = *sdly + tm1_retry_gap - MMC_CLK_SAMPLE_POINIT_MODE_1;
|
|
}
|
|
MMCINFO("Get next samply point %d at spd_md %d freq_id %d\n", *sdly, spd_md, freq);
|
|
} else {
|
|
MMCINFO("Beyond the retry times\n");
|
|
return -1;
|
|
}
|
|
} else if (tmode == SUNXI_MMC_TIMING_MODE_3) {
|
|
spd_md = priv->tm3.cur_spd_md;
|
|
freq = priv->tm3.cur_freq;
|
|
sdly = &priv->tm3.sdly[spd_md*MAX_CLK_FREQ_NUM+freq];
|
|
|
|
priv->sd_retry_cnt++;
|
|
if (priv->sd_retry_cnt * tm3_retry_gap < MMC_CLK_SAMPLE_POINIT_MODE_3) {
|
|
if ((*sdly + tm3_retry_gap) < MMC_CLK_SAMPLE_POINIT_MODE_3) {
|
|
*sdly = *sdly + tm3_retry_gap;
|
|
} else {
|
|
*sdly = *sdly + tm3_retry_gap - MMC_CLK_SAMPLE_POINIT_MODE_3;
|
|
}
|
|
MMCINFO("Get next samply point %d at spd_md %d freq_id %d\n", *sdly, spd_md, freq);
|
|
} else {
|
|
MMCINFO("Beyond the retry times\n");
|
|
return -1;
|
|
}
|
|
} else if (tmode == SUNXI_MMC_TIMING_MODE_4) {
|
|
spd_md = priv->tm4.cur_spd_md;
|
|
freq = priv->tm4.cur_freq;
|
|
if (spd_md == HS400) {
|
|
if ((err_no == 0xffffffff)) {
|
|
if (sunxi_tm4_retry(mmc, tm4_retry_gap, 1)) {
|
|
if (sunxi_tm4_retry(mmc, tm4_retry_gap, 0))
|
|
return -1;
|
|
}
|
|
} else if ((err_no & (SDXC_RespErr | SDXC_RespCRCErr))) {
|
|
if (sunxi_tm4_retry(mmc, tm4_retry_gap, 1))
|
|
return -1;
|
|
} else {
|
|
if (sunxi_tm4_retry(mmc, tm4_retry_gap, 0))
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (sunxi_tm4_retry(mmc, tm4_retry_gap, 1))
|
|
return -1;
|
|
}
|
|
}
|
|
priv->raw_int_bak = 0;
|
|
return 0;
|
|
}
|
|
MMCDBG("rto or no error or software timeout,no need retry\n");
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int sunxi_detail_errno(struct mmc *mmc)
|
|
{
|
|
struct sunxi_mmc_priv *priv = (struct sunxi_mmc_priv *)mmc->priv;
|
|
u32 err_no = priv->raw_int_bak;
|
|
|
|
priv->raw_int_bak = 0;
|
|
return err_no;
|
|
}
|
|
|
|
|
|
static const struct mmc_ops sunxi_mmc_ops = {
|
|
.send_cmd = sunxi_mmc_send_cmd_legacy,
|
|
.set_ios = sunxi_mmc_set_ios_legacy,
|
|
.init = sunxi_mmc_core_init,
|
|
.getcd = sunxi_mmc_getcd_legacy,
|
|
.decide_retry = sunxi_decide_rty,
|
|
.get_detail_errno = sunxi_detail_errno,
|
|
};
|
|
|
|
|
|
int sunxi_mmcno_to_devnum(int sdc_no)
|
|
{
|
|
struct mmc *m;
|
|
struct sunxi_mmc_priv *ppriv = &mmc_host[sdc_no];
|
|
if (ppriv->mmc_no != sdc_no) {
|
|
printk("error,card no error\n");
|
|
return -1;
|
|
}
|
|
// m = container_of((void *)ppriv, struct mmc, priv);
|
|
m = ppriv->mmc;
|
|
if (m == NULL) {
|
|
printk("error card no error\n");
|
|
return -1;
|
|
}
|
|
MMCDBG("%s ,devnum %d\n", __FUNCTION__, m->block_dev.devnum);
|
|
MMCDBG("devnum %d, pprv %x, bdesc %x\n", m->block_dev.devnum, PT_TO_PHU(ppriv), PT_TO_PHU(&m->block_dev));
|
|
MMCDBG("m %x, ppriv %x", PT_TO_PHU(m), PT_TO_PHU(ppriv));
|
|
return m->block_dev.devnum;
|
|
}
|
|
|
|
struct mmc *sunxi_mmc_init(int sdc_no)
|
|
{
|
|
__attribute__((unused)) struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
|
|
struct sunxi_mmc_priv *priv = &mmc_host[sdc_no];
|
|
struct mmc_config *cfg = &priv->cfg;
|
|
int ret;
|
|
int version;
|
|
struct mmc *mmc = NULL;
|
|
|
|
memset(priv, 0, sizeof(struct sunxi_mmc_priv));
|
|
MMCINFO("mmc driver ver %s\n", DRIVER_VER);
|
|
memset(&mmc_host_reg_bak[sdc_no], 0, sizeof(struct mmc_reg_v4p1));
|
|
priv->reg_bak = &mmc_host_reg_bak[sdc_no];
|
|
|
|
if ((sdc_no == 2)) {
|
|
cfg->odly_spd_freq = &ext_odly_spd_freq[0];
|
|
cfg->sdly_spd_freq = &ext_sdly_spd_freq[0];
|
|
} else if (sdc_no == 0) {
|
|
cfg->odly_spd_freq = &ext_odly_spd_freq_sdc0[0];
|
|
cfg->sdly_spd_freq = &ext_sdly_spd_freq_sdc0[0];
|
|
}
|
|
|
|
cfg->name = "SUNXI SD/MMC";
|
|
cfg->host_no = sdc_no;
|
|
cfg->ops = &sunxi_mmc_ops;
|
|
|
|
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
|
cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS | MMC_MODE_DDR_52MHz;
|
|
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
|
|
|
/* default f_min and f_max */
|
|
cfg->f_min = 400000;/* 400K */
|
|
cfg->f_max = 50000000;/* 50M */
|
|
|
|
/* default timing mode */
|
|
priv->timing_mode = SUNXI_MMC_TIMING_MODE_1;
|
|
|
|
priv->pdes = memalign(CONFIG_SYS_CACHELINE_SIZE, 256 * 1024);
|
|
|
|
if (priv->pdes == NULL) {
|
|
MMCINFO("get mem for descripter failed !\n");
|
|
return NULL;
|
|
} else {
|
|
MMCDBG("get mem for descripter OK !\n");
|
|
}
|
|
if (sunxi_host_mmc_config(sdc_no) != 0) {
|
|
MMCINFO("sunxi host mmc config failed!\n");
|
|
return NULL;
|
|
}
|
|
if (sunxi_mmc_pin_set(sdc_no) != 0) {
|
|
MMCINFO("sunxi mmc pin set failed!\n");
|
|
return NULL;
|
|
}
|
|
if (mmc_resource_init(sdc_no) != 0) {
|
|
MMCINFO("mmc resourse init failed!\n");
|
|
return NULL;
|
|
}
|
|
mmc_clk_io_onoff(sdc_no, 1, 1);
|
|
|
|
version = readl(&priv->reg->vers);
|
|
MMCINFO("SUNXI SDMMC Controller Version:0x%x\n", version);
|
|
priv->version = version;
|
|
|
|
if (cfg->io_is_1v8) {
|
|
MMCDBG("io is 1.8V\n");
|
|
cfg->host_caps |= MMC_MODE_HS200;
|
|
if (cfg->host_caps & MMC_MODE_8BIT)
|
|
cfg->host_caps |= MMC_MODE_HS400;
|
|
}
|
|
|
|
if (cfg->host_caps_mask) {
|
|
u32 mask = cfg->host_caps_mask;
|
|
if (mask & DRV_PARA_DISABLE_MMC_MODE_HS400)
|
|
cfg->host_caps &= (~MMC_MODE_HS400);
|
|
if (mask & DRV_PARA_DISABLE_MMC_MODE_HS200)
|
|
cfg->host_caps &= (~(MMC_MODE_HS200
|
|
| MMC_MODE_HS400));
|
|
if (mask & DRV_PARA_DISABLE_MMC_MODE_DDR_52MHz)
|
|
cfg->host_caps &= (~(MMC_MODE_DDR_52MHz
|
|
| MMC_MODE_HS400
|
|
| MMC_MODE_HS200));
|
|
if (mask & DRV_PARA_DISABLE_MMC_MODE_HS_52MHz)
|
|
cfg->host_caps &= (~(MMC_MODE_HS_52MHz
|
|
| MMC_MODE_DDR_52MHz
|
|
| MMC_MODE_HS400
|
|
| MMC_MODE_HS200));
|
|
if (mask & DRV_PARA_DISABLE_MMC_MODE_8BIT)
|
|
cfg->host_caps &= (~(MMC_MODE_8BIT
|
|
| MMC_MODE_HS400));
|
|
if (mask & DRV_PARA_ENABLE_EMMC_HW_RST)
|
|
cfg->host_caps |= DRV_PARA_ENABLE_EMMC_HW_RST;
|
|
}
|
|
MMCDBG("host_caps:0x%x\n", cfg->host_caps);
|
|
|
|
#ifdef FPGA_PLATFORM
|
|
int i = 0;
|
|
if (sdc_no == 0) {
|
|
for (i = 0; i < 6; i++) {
|
|
sunxi_gpio_set_cfgpin(SUNXI_GPF(i), SUNXI_GPF_SDC0);
|
|
sunxi_gpio_set_pull(SUNXI_GPF(i), 1);
|
|
sunxi_gpio_set_drv(SUNXI_GPF(i), 2);
|
|
}
|
|
} else {
|
|
unsigned int pin;
|
|
for (pin = SUNXI_GPC(0); pin <= SUNXI_GPF(25); pin++) {
|
|
sunxi_gpio_set_cfgpin(pin, 2);
|
|
sunxi_gpio_set_pull(pin, SUNXI_GPIO_PULL_UP);
|
|
sunxi_gpio_set_drv(pin, 2);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* config ahb clock */
|
|
MMCDBG("init mmc %d clock and io\n", sdc_no);
|
|
#if !defined(CONFIG_MACH_SUN50I_H6) && !defined(CONFIG_MACH_SUN8IW16)\
|
|
&& !defined(CONFIG_MACH_SUN50IW9) && !defined(CONFIG_MACH_SUN8IW19)\
|
|
&& !defined(CONFIG_MACH_SUN50IW10) && !defined(CONFIG_MACH_SUN8IW15)\
|
|
&& !defined(CONFIG_MACH_SUN50IW11) && !defined(CONFIG_MACH_SUN50IW12)\
|
|
&& !defined(CONFIG_MACH_SUN20IW1) && !defined(CONFIG_MACH_SUN8IW20)\
|
|
&& !defined(CONFIG_MACH_SUN8IW21) && !defined(CONFIG_MACH_SUN50IW5)\
|
|
&& !defined(CONFIG_MACH_SUN55IW3) && !defined(CONFIG_MACH_SUN60IW1)
|
|
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
|
|
#elif defined CONFIG_MACH_SUN50I_H6 /* CONFIG_MACH_SUN50I_H6 */
|
|
setbits_le32(&ccm->sd_gate_reset, 1 << sdc_no);
|
|
/* unassert reset */
|
|
setbits_le32(&ccm->sd_gate_reset, 1 << (RESET_SHIFT + sdc_no));
|
|
#endif
|
|
mmc = mmc_create(cfg, priv);
|
|
if (!mmc)
|
|
return NULL;
|
|
ret = mmc_set_mod_clk(priv, 24000000);
|
|
if (ret)
|
|
return NULL;
|
|
return mmc;
|
|
}
|
|
#else
|
|
|
|
static int sunxi_mmc_set_ios(struct udevice *dev)
|
|
{
|
|
struct sunxi_mmc_plat *plat = dev_get_platdata(dev);
|
|
struct sunxi_mmc_priv *priv = dev_get_priv(dev);
|
|
|
|
return sunxi_mmc_set_ios_common(priv, &plat->mmc);
|
|
}
|
|
|
|
static int sunxi_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
|
|
struct mmc_data *data)
|
|
{
|
|
struct sunxi_mmc_plat *plat = dev_get_platdata(dev);
|
|
struct sunxi_mmc_priv *priv = dev_get_priv(dev);
|
|
|
|
return sunxi_mmc_send_cmd_common(priv, &plat->mmc, cmd, data);
|
|
}
|
|
|
|
static int sunxi_mmc_getcd(struct udevice *dev)
|
|
{
|
|
struct sunxi_mmc_priv *priv = dev_get_priv(dev);
|
|
|
|
if (dm_gpio_is_valid(&priv->cd_gpio)) {
|
|
int cd_state = dm_gpio_get_value(&priv->cd_gpio);
|
|
|
|
return cd_state ^ priv->cd_inverted;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static const struct dm_mmc_ops sunxi_mmc_ops = {
|
|
.send_cmd = sunxi_mmc_send_cmd,
|
|
.set_ios = sunxi_mmc_set_ios,
|
|
.get_cd = sunxi_mmc_getcd,
|
|
};
|
|
|
|
static int sunxi_mmc_probe(struct udevice *dev)
|
|
{
|
|
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
|
struct sunxi_mmc_plat *plat = dev_get_platdata(dev);
|
|
struct sunxi_mmc_priv *priv = dev_get_priv(dev);
|
|
struct mmc_config *cfg = &plat->cfg;
|
|
struct ofnode_phandle_args args;
|
|
u32 *gate_reg;
|
|
int bus_width, ret;
|
|
|
|
cfg->name = dev->name;
|
|
bus_width = dev_read_u32_default(dev, "bus-width", 1);
|
|
|
|
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
|
|
cfg->host_caps = 0;
|
|
if (bus_width == 8)
|
|
cfg->host_caps |= MMC_MODE_8BIT;
|
|
if (bus_width >= 4)
|
|
cfg->host_caps |= MMC_MODE_4BIT;
|
|
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;
|
|
|
|
priv->reg = (void *)dev_read_addr(dev);
|
|
|
|
/* We don't have a sunxi clock driver so find the clock address here */
|
|
ret = dev_read_phandle_with_args(dev, "clocks", "#clock-cells", 0,
|
|
1, &args);
|
|
if (ret)
|
|
return ret;
|
|
priv->mclkreg = (u32 *)ofnode_get_addr(args.node);
|
|
|
|
ret = dev_read_phandle_with_args(dev, "clocks", "#clock-cells", 0,
|
|
0, &args);
|
|
if (ret)
|
|
return ret;
|
|
gate_reg = (u32 *)ofnode_get_addr(args.node);
|
|
setbits_le32(gate_reg, 1 << args.args[0]);
|
|
priv->mmc_no = args.args[0] - 8;
|
|
|
|
ret = mmc_set_mod_clk(priv, 24000000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* This GPIO is optional */
|
|
if (!gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio,
|
|
GPIOD_IS_IN)) {
|
|
int cd_pin = gpio_get_number(&priv->cd_gpio);
|
|
|
|
sunxi_gpio_set_pull(cd_pin, SUNXI_GPIO_PULL_UP);
|
|
}
|
|
|
|
/* Check if card detect is inverted */
|
|
priv->cd_inverted = dev_read_bool(dev, "cd-inverted");
|
|
|
|
upriv->mmc = &plat->mmc;
|
|
|
|
/* Reset controller */
|
|
writel(SUNXI_MMC_GCTRL_RESET, &priv->reg->gctrl);
|
|
udelay(1000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_mmc_bind(struct udevice *dev)
|
|
{
|
|
struct sunxi_mmc_plat *plat = dev_get_platdata(dev);
|
|
|
|
return mmc_bind(dev, &plat->mmc, &plat->cfg);
|
|
}
|
|
|
|
static const struct udevice_id sunxi_mmc_ids[] = {
|
|
{ .compatible = "allwinner,sun5i-a13-mmc" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(sunxi_mmc_drv) = {
|
|
.name = "sunxi_mmc",
|
|
.id = UCLASS_MMC,
|
|
.of_match = sunxi_mmc_ids,
|
|
.bind = sunxi_mmc_bind,
|
|
.probe = sunxi_mmc_probe,
|
|
.ops = &sunxi_mmc_ops,
|
|
.platdata_auto_alloc_size = sizeof(struct sunxi_mmc_plat),
|
|
.priv_auto_alloc_size = sizeof(struct sunxi_mmc_priv),
|
|
};
|
|
#endif
|