mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-14 08:57:58 +00:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
746 lines
18 KiB
C
746 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* MMCIF driver.
|
|
*
|
|
* Copyright (C) 2011 Renesas Solutions Corp.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <common.h>
|
|
#include <watchdog.h>
|
|
#include <command.h>
|
|
#include <mmc.h>
|
|
#include <clk.h>
|
|
#include <dm.h>
|
|
#include <malloc.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/io.h>
|
|
#include <linux/sizes.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 = -ETIMEDOUT;
|
|
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 = -ETIMEDOUT;
|
|
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_send_cmd_common(struct sh_mmcif_host *host,
|
|
struct mmc_cmd *cmd, struct mmc_data *data)
|
|
{
|
|
int ret;
|
|
|
|
WATCHDOG_RESET();
|
|
|
|
switch (cmd->cmdidx) {
|
|
case MMC_CMD_APP_CMD:
|
|
return -ETIMEDOUT;
|
|
case MMC_CMD_SEND_EXT_CSD: /* = SD_SEND_IF_COND (8) */
|
|
if (data)
|
|
/* ext_csd */
|
|
break;
|
|
else
|
|
/* send_if_cond cmd (not support) */
|
|
return -ETIMEDOUT;
|
|
default:
|
|
break;
|
|
}
|
|
host->sd_error = 0;
|
|
host->data = data;
|
|
ret = sh_mmcif_start_cmd(host, data, cmd);
|
|
host->data = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sh_mmcif_set_ios_common(struct sh_mmcif_host *host, struct mmc *mmc)
|
|
{
|
|
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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sh_mmcif_initialize_common(struct sh_mmcif_host *host)
|
|
{
|
|
sh_mmcif_sync_reset(host);
|
|
sh_mmcif_write(MASK_ALL, &host->regs->ce_int_mask);
|
|
return 0;
|
|
}
|
|
|
|
#ifndef CONFIG_DM_MMC
|
|
static void *mmc_priv(struct mmc *mmc)
|
|
{
|
|
return (void *)mmc->priv;
|
|
}
|
|
|
|
static int sh_mmcif_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
|
|
struct mmc_data *data)
|
|
{
|
|
struct sh_mmcif_host *host = mmc_priv(mmc);
|
|
|
|
return sh_mmcif_send_cmd_common(host, cmd, data);
|
|
}
|
|
|
|
static int sh_mmcif_set_ios(struct mmc *mmc)
|
|
{
|
|
struct sh_mmcif_host *host = mmc_priv(mmc);
|
|
|
|
return sh_mmcif_set_ios_common(host, mmc);
|
|
}
|
|
|
|
static int sh_mmcif_initialize(struct mmc *mmc)
|
|
{
|
|
struct sh_mmcif_host *host = mmc_priv(mmc);
|
|
|
|
return sh_mmcif_initialize_common(host);
|
|
}
|
|
|
|
static const struct mmc_ops sh_mmcif_ops = {
|
|
.send_cmd = sh_mmcif_send_cmd,
|
|
.set_ios = sh_mmcif_set_ios,
|
|
.init = sh_mmcif_initialize,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
#else
|
|
struct sh_mmcif_plat {
|
|
struct mmc_config cfg;
|
|
struct mmc mmc;
|
|
};
|
|
|
|
int sh_mmcif_dm_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
|
|
struct mmc_data *data)
|
|
{
|
|
struct sh_mmcif_host *host = dev_get_priv(dev);
|
|
|
|
return sh_mmcif_send_cmd_common(host, cmd, data);
|
|
}
|
|
|
|
int sh_mmcif_dm_set_ios(struct udevice *dev)
|
|
{
|
|
struct sh_mmcif_host *host = dev_get_priv(dev);
|
|
struct mmc *mmc = mmc_get_mmc_dev(dev);
|
|
|
|
return sh_mmcif_set_ios_common(host, mmc);
|
|
}
|
|
|
|
static const struct dm_mmc_ops sh_mmcif_dm_ops = {
|
|
.send_cmd = sh_mmcif_dm_send_cmd,
|
|
.set_ios = sh_mmcif_dm_set_ios,
|
|
};
|
|
|
|
static int sh_mmcif_dm_bind(struct udevice *dev)
|
|
{
|
|
struct sh_mmcif_plat *plat = dev_get_platdata(dev);
|
|
|
|
return mmc_bind(dev, &plat->mmc, &plat->cfg);
|
|
}
|
|
|
|
static int sh_mmcif_dm_probe(struct udevice *dev)
|
|
{
|
|
struct sh_mmcif_plat *plat = dev_get_platdata(dev);
|
|
struct sh_mmcif_host *host = dev_get_priv(dev);
|
|
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
|
struct clk sh_mmcif_clk;
|
|
fdt_addr_t base;
|
|
int ret;
|
|
|
|
base = devfdt_get_addr(dev);
|
|
if (base == FDT_ADDR_T_NONE)
|
|
return -EINVAL;
|
|
|
|
host->regs = (struct sh_mmcif_regs *)devm_ioremap(dev, base, SZ_2K);
|
|
if (!host->regs)
|
|
return -ENOMEM;
|
|
|
|
ret = clk_get_by_index(dev, 0, &sh_mmcif_clk);
|
|
if (ret) {
|
|
debug("failed to get clock, ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_enable(&sh_mmcif_clk);
|
|
if (ret) {
|
|
debug("failed to enable clock, ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
host->clk = clk_get_rate(&sh_mmcif_clk);
|
|
|
|
plat->cfg.name = dev->name;
|
|
plat->cfg.host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS;
|
|
|
|
switch (fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), "bus-width",
|
|
1)) {
|
|
case 8:
|
|
plat->cfg.host_caps |= MMC_MODE_8BIT;
|
|
break;
|
|
case 4:
|
|
plat->cfg.host_caps |= MMC_MODE_4BIT;
|
|
break;
|
|
case 1:
|
|
break;
|
|
default:
|
|
dev_err(dev, "Invalid \"bus-width\" value\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sh_mmcif_initialize_common(host);
|
|
|
|
plat->cfg.voltages = MMC_VDD_165_195 | MMC_VDD_32_33 | MMC_VDD_33_34;
|
|
plat->cfg.f_min = MMC_CLK_DIV_MIN(host->clk);
|
|
plat->cfg.f_max = MMC_CLK_DIV_MAX(host->clk);
|
|
plat->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
|
|
|
upriv->mmc = &plat->mmc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct udevice_id sh_mmcif_sd_match[] = {
|
|
{ .compatible = "renesas,sh-mmcif" },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(sh_mmcif_mmc) = {
|
|
.name = "sh-mmcif",
|
|
.id = UCLASS_MMC,
|
|
.of_match = sh_mmcif_sd_match,
|
|
.bind = sh_mmcif_dm_bind,
|
|
.probe = sh_mmcif_dm_probe,
|
|
.priv_auto_alloc_size = sizeof(struct sh_mmcif_host),
|
|
.platdata_auto_alloc_size = sizeof(struct sh_mmcif_plat),
|
|
.ops = &sh_mmcif_dm_ops,
|
|
};
|
|
#endif
|