This adds Renesas rmobile ARM SoC's SD/MMC host support. This drivers tested with Gose board and Koelsch board. Signed-off-by: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> Acked-by: Pantelis Antoniou <panto@antoniou-consulting.com>master
parent
ab77f24119
commit
72d42bad58
@ -0,0 +1,168 @@ |
||||
/*
|
||||
* drivers/mmc/sh-sdhi.h |
||||
* |
||||
* SD/MMC driver for Reneas rmobile ARM SoCs |
||||
* |
||||
* Copyright (C) 2013-2014 Renesas Electronics Corporation |
||||
* Copyright (C) 2008-2009 Renesas Solutions Corp. |
||||
* |
||||
* SPDX-License-Identifier: GPL-2.0 |
||||
*/ |
||||
|
||||
#ifndef _SH_SDHI_H |
||||
#define _SH_SDHI_H |
||||
|
||||
#define SDHI_CMD (0x0000 >> 1) |
||||
#define SDHI_PORTSEL (0x0004 >> 1) |
||||
#define SDHI_ARG0 (0x0008 >> 1) |
||||
#define SDHI_ARG1 (0x000C >> 1) |
||||
#define SDHI_STOP (0x0010 >> 1) |
||||
#define SDHI_SECCNT (0x0014 >> 1) |
||||
#define SDHI_RSP00 (0x0018 >> 1) |
||||
#define SDHI_RSP01 (0x001C >> 1) |
||||
#define SDHI_RSP02 (0x0020 >> 1) |
||||
#define SDHI_RSP03 (0x0024 >> 1) |
||||
#define SDHI_RSP04 (0x0028 >> 1) |
||||
#define SDHI_RSP05 (0x002C >> 1) |
||||
#define SDHI_RSP06 (0x0030 >> 1) |
||||
#define SDHI_RSP07 (0x0034 >> 1) |
||||
#define SDHI_INFO1 (0x0038 >> 1) |
||||
#define SDHI_INFO2 (0x003C >> 1) |
||||
#define SDHI_INFO1_MASK (0x0040 >> 1) |
||||
#define SDHI_INFO2_MASK (0x0044 >> 1) |
||||
#define SDHI_CLK_CTRL (0x0048 >> 1) |
||||
#define SDHI_SIZE (0x004C >> 1) |
||||
#define SDHI_OPTION (0x0050 >> 1) |
||||
#define SDHI_ERR_STS1 (0x0058 >> 1) |
||||
#define SDHI_ERR_STS2 (0x005C >> 1) |
||||
#define SDHI_BUF0 (0x0060 >> 1) |
||||
#define SDHI_SDIO_MODE (0x0068 >> 1) |
||||
#define SDHI_SDIO_INFO1 (0x006C >> 1) |
||||
#define SDHI_SDIO_INFO1_MASK (0x0070 >> 1) |
||||
#define SDHI_CC_EXT_MODE (0x01B0 >> 1) |
||||
#define SDHI_SOFT_RST (0x01C0 >> 1) |
||||
#define SDHI_VERSION (0x01C4 >> 1) |
||||
#define SDHI_HOST_MODE (0x01C8 >> 1) |
||||
#define SDHI_SDIF_MODE (0x01CC >> 1) |
||||
#define SDHI_EXT_SWAP (0x01E0 >> 1) |
||||
#define SDHI_SD_DMACR (0x0324 >> 1) |
||||
|
||||
/* SDHI CMD VALUE */ |
||||
#define CMD_MASK 0x0000ffff |
||||
#define SDHI_APP 0x0040 |
||||
#define SDHI_SD_APP_SEND_SCR 0x0073 |
||||
#define SDHI_SD_SWITCH 0x1C06 |
||||
|
||||
/* SDHI_PORTSEL */ |
||||
#define USE_1PORT (1 << 8) /* 1 port */ |
||||
|
||||
/* SDHI_ARG */ |
||||
#define ARG0_MASK 0x0000ffff |
||||
#define ARG1_MASK 0x0000ffff |
||||
|
||||
/* SDHI_STOP */ |
||||
#define STOP_SEC_ENABLE (1 << 8) |
||||
|
||||
/* SDHI_INFO1 */ |
||||
#define INFO1_RESP_END (1 << 0) |
||||
#define INFO1_ACCESS_END (1 << 2) |
||||
#define INFO1_CARD_RE (1 << 3) |
||||
#define INFO1_CARD_IN (1 << 4) |
||||
#define INFO1_ISD0CD (1 << 5) |
||||
#define INFO1_WRITE_PRO (1 << 7) |
||||
#define INFO1_DATA3_CARD_RE (1 << 8) |
||||
#define INFO1_DATA3_CARD_IN (1 << 9) |
||||
#define INFO1_DATA3 (1 << 10) |
||||
|
||||
/* SDHI_INFO2 */ |
||||
#define INFO2_CMD_ERROR (1 << 0) |
||||
#define INFO2_CRC_ERROR (1 << 1) |
||||
#define INFO2_END_ERROR (1 << 2) |
||||
#define INFO2_TIMEOUT (1 << 3) |
||||
#define INFO2_BUF_ILL_WRITE (1 << 4) |
||||
#define INFO2_BUF_ILL_READ (1 << 5) |
||||
#define INFO2_RESP_TIMEOUT (1 << 6) |
||||
#define INFO2_SDDAT0 (1 << 7) |
||||
#define INFO2_BRE_ENABLE (1 << 8) |
||||
#define INFO2_BWE_ENABLE (1 << 9) |
||||
#define INFO2_CBUSY (1 << 14) |
||||
#define INFO2_ILA (1 << 15) |
||||
#define INFO2_ALL_ERR (0x807f) |
||||
|
||||
/* SDHI_INFO1_MASK */ |
||||
#define INFO1M_RESP_END (1 << 0) |
||||
#define INFO1M_ACCESS_END (1 << 2) |
||||
#define INFO1M_CARD_RE (1 << 3) |
||||
#define INFO1M_CARD_IN (1 << 4) |
||||
#define INFO1M_DATA3_CARD_RE (1 << 8) |
||||
#define INFO1M_DATA3_CARD_IN (1 << 9) |
||||
#define INFO1M_ALL (0xffff) |
||||
#define INFO1M_SET (INFO1M_RESP_END | \ |
||||
INFO1M_ACCESS_END | \
|
||||
INFO1M_DATA3_CARD_RE | \
|
||||
INFO1M_DATA3_CARD_IN) |
||||
|
||||
/* SDHI_INFO2_MASK */ |
||||
#define INFO2M_CMD_ERROR (1 << 0) |
||||
#define INFO2M_CRC_ERROR (1 << 1) |
||||
#define INFO2M_END_ERROR (1 << 2) |
||||
#define INFO2M_TIMEOUT (1 << 3) |
||||
#define INFO2M_BUF_ILL_WRITE (1 << 4) |
||||
#define INFO2M_BUF_ILL_READ (1 << 5) |
||||
#define INFO2M_RESP_TIMEOUT (1 << 6) |
||||
#define INFO2M_BRE_ENABLE (1 << 8) |
||||
#define INFO2M_BWE_ENABLE (1 << 9) |
||||
#define INFO2M_ILA (1 << 15) |
||||
#define INFO2M_ALL (0xffff) |
||||
#define INFO2M_ALL_ERR (0x807f) |
||||
|
||||
/* SDHI_CLK_CTRL */ |
||||
#define CLK_ENABLE (1 << 8) |
||||
|
||||
/* SDHI_OPTION */ |
||||
#define OPT_BUS_WIDTH_1 (1 << 15) /* bus width = 1 bit */ |
||||
|
||||
/* SDHI_ERR_STS1 */ |
||||
#define ERR_STS1_CRC_ERROR ((1 << 11) | (1 << 10) | (1 << 9) | \ |
||||
(1 << 8) | (1 << 5)) |
||||
#define ERR_STS1_CMD_ERROR ((1 << 4) | (1 << 3) | (1 << 2) | \ |
||||
(1 << 1) | (1 << 0)) |
||||
|
||||
/* SDHI_ERR_STS2 */ |
||||
#define ERR_STS2_RES_TIMEOUT (1 << 0) |
||||
#define ERR_STS2_RES_STOP_TIMEOUT ((1 << 0) | (1 << 1)) |
||||
#define ERR_STS2_SYS_ERROR ((1 << 6) | (1 << 5) | (1 << 4) | \ |
||||
(1 << 3) | (1 << 2) | (1 << 1) | \
|
||||
(1 << 0)) |
||||
|
||||
/* SDHI_SDIO_MODE */ |
||||
#define SDIO_MODE_ON (1 << 0) |
||||
#define SDIO_MODE_OFF (0 << 0) |
||||
|
||||
/* SDHI_SDIO_INFO1 */ |
||||
#define SDIO_INFO1_IOIRQ (1 << 0) |
||||
#define SDIO_INFO1_EXPUB52 (1 << 14) |
||||
#define SDIO_INFO1_EXWT (1 << 15) |
||||
|
||||
/* SDHI_SDIO_INFO1_MASK */ |
||||
#define SDIO_INFO1M_CLEAR ((1 << 1) | (1 << 2)) |
||||
#define SDIO_INFO1M_ON ((1 << 15) | (1 << 14) | (1 << 2) | \ |
||||
(1 << 1) | (1 << 0)) |
||||
|
||||
/* SDHI_EXT_SWAP */ |
||||
#define SET_SWAP ((1 << 6) | (1 << 7)) /* SWAP */ |
||||
|
||||
/* SDHI_SOFT_RST */ |
||||
#define SOFT_RST_ON (0 << 0) |
||||
#define SOFT_RST_OFF (1 << 0) |
||||
|
||||
#define CLKDEV_SD_DATA 25000000 /* 25 MHz */ |
||||
#define CLKDEV_HS_DATA 50000000 /* 50 MHz */ |
||||
#define CLKDEV_MMC_DATA 20000000 /* 20MHz */ |
||||
#define CLKDEV_INIT 400000 /* 100 - 400 KHz */ |
||||
|
||||
/* For quirk */ |
||||
#define SH_SDHI_QUIRK_16BIT_BUF (1) |
||||
int sh_sdhi_init(unsigned long addr, int ch, unsigned long quirks); |
||||
|
||||
#endif /* _SH_SDHI_H */ |
@ -0,0 +1,9 @@ |
||||
menu "MMC Host controller Support" |
||||
|
||||
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 |
||||
|
||||
endmenu |
@ -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; |
||||
} |
Loading…
Reference in new issue