blob: 08f8d878aae34cb6e65efda8d201112a3a9b2d02 [file] [log] [blame] [edit]
/*
* Copyright 2017, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
#include "sdhc.h"
#include <autoconf.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "services.h"
#include "mmc.h"
#define DBG_INFO "info:"
//#define DEBUG
#undef DEBUG
#ifdef DEBUG
#define D(x, ...) printf(__VA_ARGS__)
#else
#define D(...) do{}while(0)
#endif
#define DS_ADDR 0x00 //DMA System Address
#define BLK_ATT 0x04 //Block Attributes
#define CMD_ARG 0x08 //Command Argument
#define CMD_XFR_TYP 0x0C //Command Transfer Type
#define CMD_RSP0 0x10 //Command Response0
#define CMD_RSP1 0x14 //Command Response1
#define CMD_RSP2 0x18 //Command Response2
#define CMD_RSP3 0x1C //Command Response3
#define DATA_BUFF_ACC_PORT 0x20 //Data Buffer Access Port
#define PRES_STATE 0x24 //Present State
#define PROT_CTRL 0x28 //Protocol Control
#define SYS_CTRL 0x2C //System Control
#define INT_STATUS 0x30 //Interrupt Status
#define INT_STATUS_EN 0x34 //Interrupt Status Enable
#define INT_SIGNAL_EN 0x38 //Interrupt Signal Enable
#define AUTOCMD12_ERR_STATUS 0x3C //Auto CMD12 Error Status
#define HOST_CTRL_CAP 0x40 //Host Controller Capabilities
#define WTMK_LVL 0x44 //Watermark Level
#define MIX_CTRL 0x48 //Mixer Control
#define FORCE_EVENT 0x50 //Force Event
#define ADMA_ERR_STATUS 0x54 //ADMA Error Status Register
#define ADMA_SYS_ADDR 0x58 //ADMA System Address
#define DLL_CTRL 0x60 //DLL (Delay Line) Control
#define DLL_STATUS 0x64 //DLL Status
#define CLK_TUNE_CTRL_STATUS 0x68 //CLK Tuning Control and Status
#define VEND_SPEC 0xC0 //Vendor Specific Register
#define MMC_BOOT 0xC4 //MMC Boot Register
#define VEND_SPEC2 0xC8 //Vendor Specific 2 Register
#define HOST_VERSION 0xFC //Host Version (0xFE adjusted for alignment)
/* Block Attributes Register */
#define BLK_ATT_BLKCNT_SHF 16 //Blocks Count For Current Transfer
#define BLK_ATT_BLKCNT_MASK 0xFFFF //Blocks Count For Current Transfer
#define BLK_ATT_BLKSIZE_SHF 0 //Transfer Block Size
#define BLK_ATT_BLKSIZE_MASK 0xFFF //Transfer Block Size
/* Command Transfer Type Register */
#define CMD_XFR_TYP_CMDINX_SHF 24 //Command Index
#define CMD_XFR_TYP_CMDINX_MASK 0x3F //Command Index
#define CMD_XFR_TYP_CMDTYP_SHF 22 //Command Type
#define CMD_XFR_TYP_CMDTYP_MASK 0x3 //Command Type
#define CMD_XFR_TYP_DPSEL (1 << 21) //Data Present Select
#define CMD_XFR_TYP_CICEN (1 << 20) //Command Index Check Enable
#define CMD_XFR_TYP_CCCEN (1 << 19) //Command CRC Check Enable
#define CMD_XFR_TYP_RSPTYP_SHF 16 //Response Type Select
#define CMD_XFR_TYP_RSPTYP_MASK 0x3 //Response Type Select
/* System Control Register */
#define SYS_CTRL_INITA (1 << 27) //Initialization Active
#define SYS_CTRL_RSTD (1 << 26) //Software Reset for DAT Line
#define SYS_CTRL_RSTC (1 << 25) //Software Reset for CMD Line
#define SYS_CTRL_RSTA (1 << 24) //Software Reset for ALL
#define SYS_CTRL_DTOCV_SHF 16 //Data Timeout Counter Value
#define SYS_CTRL_DTOCV_MASK 0xF //Data Timeout Counter Value
#define SYS_CTRL_SDCLKS_SHF 8 //SDCLK Frequency Select
#define SYS_CTRL_SDCLKS_MASK 0xFF //SDCLK Frequency Select
#define SYS_CTRL_DVS_SHF 4 //Divisor
#define SYS_CTRL_DVS_MASK 0xF //Divisor
#define SYS_CTRL_CLK_INT_EN (1 << 0) //Internal clock enable (exl. IMX6)
#define SYS_CTRL_CLK_INT_STABLE (1 << 1) //Internal clock stable (exl. IMX6)
#define SYS_CTRL_CLK_CARD_EN (1 << 2) //SD clock enable (exl. IMX6)
/* Present State Register */
#define PRES_STATE_DAT3 (1 << 23)
#define PRES_STATE_DAT2 (1 << 22)
#define PRES_STATE_DAT1 (1 << 21)
#define PRES_STATE_DAT0 (1 << 20)
#define PRES_STATE_WPSPL (1 << 19) //Write Protect Switch Pin Level
#define PRES_STATE_CDPL (1 << 18) //Card Detect Pin Level
#define PRES_STATE_CINST (1 << 16) //Card Inserted
#define PRES_STATE_BWEN (1 << 10) //Buffer Write Enable
#define PRES_STATE_RTA (1 << 9) //Read Transfer Active
#define PRES_STATE_WTA (1 << 8) //Write Transfer Active
#define PRES_STATE_SDSTB (1 << 3) //SD Clock Stable
#define PRES_STATE_DLA (1 << 2) //Data Line Active
#define PRES_STATE_CDIHB (1 << 1) //Command Inhibit(DATA)
#define PRES_STATE_CIHB (1 << 0) //Command Inhibit(CMD)
/* Interrupt Status Register */
#define INT_STATUS_DMAE (1 << 28) //DMA Error (only IMX6)
#define INT_STATUS_TNE (1 << 26) //Tuning Error
#define INT_STATUS_ADMAE (1 << 25) //ADMA error (exl. IMX6)
#define INT_STATUS_AC12E (1 << 24) //Auto CMD12 Error
#define INT_STATUS_OVRCURE (1 << 23) //Bus over current (exl. IMX6)
#define INT_STATUS_DEBE (1 << 22) //Data End Bit Error
#define INT_STATUS_DCE (1 << 21) //Data CRC Error
#define INT_STATUS_DTOE (1 << 20) //Data Timeout Error
#define INT_STATUS_CIE (1 << 19) //Command Index Error
#define INT_STATUS_CEBE (1 << 18) //Command End Bit Error
#define INT_STATUS_CCE (1 << 17) //Command CRC Error
#define INT_STATUS_CTOE (1 << 16) //Command Timeout Error
#define INT_STATUS_ERR (1 << 15) //Error interrupt (exl. IMX6)
#define INT_STATUS_TP (1 << 14) //Tuning Pass
#define INT_STATUS_RTE (1 << 12) //Re-Tuning Event
#define INT_STATUS_CINT (1 << 8) //Card Interrupt
#define INT_STATUS_CRM (1 << 7) //Card Removal
#define INT_STATUS_CINS (1 << 6) //Card Insertion
#define INT_STATUS_BRR (1 << 5) //Buffer Read Ready
#define INT_STATUS_BWR (1 << 4) //Buffer Write Ready
#define INT_STATUS_DINT (1 << 3) //DMA Interrupt
#define INT_STATUS_BGE (1 << 2) //Block Gap Event
#define INT_STATUS_TC (1 << 1) //Transfer Complete
#define INT_STATUS_CC (1 << 0) //Command Complete
/* Host Controller Capabilities Register */
#define HOST_CTRL_CAP_VS18 (1 << 26) //Voltage Support 1.8V
#define HOST_CTRL_CAP_VS30 (1 << 25) //Voltage Support 3.0V
#define HOST_CTRL_CAP_VS33 (1 << 24) //Voltage Support 3.3V
#define HOST_CTRL_CAP_SRS (1 << 23) //Suspend/Resume Support
#define HOST_CTRL_CAP_DMAS (1 << 22) //DMA Support
#define HOST_CTRL_CAP_HSS (1 << 21) //High Speed Support
#define HOST_CTRL_CAP_ADMAS (1 << 20) //ADMA Support
#define HOST_CTRL_CAP_MBL_SHF 16 //Max Block Length
#define HOST_CTRL_CAP_MBL_MASK 0x3 //Max Block Length
/* Mixer Control Register */
#define MIX_CTRL_MSBSEL (1 << 5) //Multi/Single Block Select.
#define MIX_CTRL_DTDSEL (1 << 4) //Data Transfer Direction Select.
#define MIX_CTRL_DDR_EN (1 << 3) //Dual Data Rate mode selection
#define MIX_CTRL_AC12EN (1 << 2) //Auto CMD12 Enable
#define MIX_CTRL_BCEN (1 << 1) //Block Count Enable
#define MIX_CTRL_DMAEN (1 << 0) //DMA Enable
/* Watermark Level register */
#define WTMK_LVL_WR_WML_SHF 16 //Write Watermark Level
#define WTMK_LVL_RD_WML_SHF 0 //Write Watermark Level
#define writel(v, a) (*(volatile uint32_t*)(a) = (v))
#define readl(a) (*(volatile uint32_t*)(a))
enum dma_mode {
DMA_MODE_NONE,
DMA_MODE_SDMA,
DMA_MODE_ADMA
};
static inline sdhc_dev_t sdio_get_sdhc(sdio_host_dev_t *sdio)
{
return (sdhc_dev_t)sdio->priv;
}
/** Print uSDHC registers. */
UNUSED static void print_sdhc_regs(struct sdhc *host)
{
int i;
for (i = DS_ADDR; i <= HOST_VERSION; i += 0x4) {
printf("%x: %X\n", i, readl(host->base + i));
}
}
static inline enum dma_mode get_dma_mode(struct sdhc *host, struct mmc_cmd *cmd)
{
if (cmd->data == NULL) {
return DMA_MODE_NONE;
}
if (cmd->data->pbuf == 0) {
return DMA_MODE_NONE;
}
/* Currently only SDMA supported */
return DMA_MODE_SDMA;
}
static inline int cap_sdma_supported(struct sdhc *host)
{
uint32_t v;
v = readl(host->base + HOST_CTRL_CAP);
return !!(v & HOST_CTRL_CAP_DMAS);
}
static inline int cap_max_buffer_size(struct sdhc *host)
{
uint32_t v;
v = readl(host->base + HOST_CTRL_CAP);
v = ((v >> HOST_CTRL_CAP_MBL_SHF) & HOST_CTRL_CAP_MBL_MASK);
return 512 << v;
}
static int sdhc_next_cmd(sdhc_dev_t host)
{
struct mmc_cmd *cmd = host->cmd_list_head;
uint32_t val;
uint32_t mix_ctrl;
/* Enable IRQs */
val = (INT_STATUS_ADMAE | INT_STATUS_OVRCURE | INT_STATUS_DEBE
| INT_STATUS_DCE | INT_STATUS_DTOE | INT_STATUS_CRM
| INT_STATUS_CINS | INT_STATUS_CIE | INT_STATUS_CEBE
| INT_STATUS_CCE | INT_STATUS_CTOE | INT_STATUS_TC
| INT_STATUS_CC);
if (get_dma_mode(host, cmd) == DMA_MODE_NONE) {
val |= INT_STATUS_BRR | INT_STATUS_BWR;
}
writel(val, host->base + INT_STATUS_EN);
/* Check if the Host is ready for transit. */
while (readl(host->base + PRES_STATE) & (PRES_STATE_CIHB | PRES_STATE_CDIHB));
while (readl(host->base + PRES_STATE) & PRES_STATE_DLA);
/* Two commands need to have at least 8 clock cycles in between.
* Lets assume that the hcd will enforce this. */
//udelay(1000);
/* Write to the argument register. */
D(DBG_INFO, "CMD: %d with arg %x ", cmd->index, cmd->arg);
writel(cmd->arg, host->base + CMD_ARG);
if (cmd->data) {
/* Use the default timeout. */
val = readl(host->base + SYS_CTRL);
val &= ~(0xffUL << 16);
val |= 0xE << 16;
writel(val, host->base + SYS_CTRL);
/* Set the DMA boundary. */
val = (cmd->data->block_size & BLK_ATT_BLKSIZE_MASK);
val |= (cmd->data->blocks << BLK_ATT_BLKCNT_SHF);
writel(val, host->base + BLK_ATT);
/* Set watermark level */
val = cmd->data->block_size / 4;
if (val > 0x80) {
val = 0x80;
}
if (cmd->index == MMC_READ_SINGLE_BLOCK) {
val = (val << WTMK_LVL_RD_WML_SHF);
} else {
val = (val << WTMK_LVL_WR_WML_SHF);
}
writel(val, host->base + WTMK_LVL);
/* Set Mixer Control */
mix_ctrl = MIX_CTRL_BCEN;
if (cmd->data->blocks > 1) {
mix_ctrl |= MIX_CTRL_MSBSEL;
}
if (cmd->index == MMC_READ_SINGLE_BLOCK) {
mix_ctrl |= MIX_CTRL_DTDSEL;
}
/* Configure DMA */
if (get_dma_mode(host, cmd) != DMA_MODE_NONE) {
/* Enable DMA */
mix_ctrl |= MIX_CTRL_DMAEN;
/* Set DMA address */
writel(cmd->data->pbuf, host->base + DS_ADDR);
}
/* Record the number of blocks to be sent */
host->blocks_remaining = cmd->data->blocks;
}
/* The command should be MSB and the first two bits should be '00' */
val = (cmd->index & CMD_XFR_TYP_CMDINX_MASK) << CMD_XFR_TYP_CMDINX_SHF;
val &= ~(CMD_XFR_TYP_CMDTYP_MASK << CMD_XFR_TYP_CMDTYP_SHF);
if (cmd->data) {
if (host->version == 2) {
/* Some controllers implement MIX_CTRL as part of the XFR_TYP */
val |= mix_ctrl;
} else {
writel(mix_ctrl, host->base + MIX_CTRL);
}
}
/* Set response type */
val &= ~CMD_XFR_TYP_CICEN;
val &= ~CMD_XFR_TYP_CCCEN;
val &= ~(CMD_XFR_TYP_RSPTYP_MASK << CMD_XFR_TYP_RSPTYP_SHF);
switch (cmd->rsp_type) {
case MMC_RSP_TYPE_R2:
val |= (0x1 << CMD_XFR_TYP_RSPTYP_SHF);
val |= CMD_XFR_TYP_CCCEN;
break;
case MMC_RSP_TYPE_R3:
case MMC_RSP_TYPE_R4:
val |= (0x2 << CMD_XFR_TYP_RSPTYP_SHF);
break;
case MMC_RSP_TYPE_R1:
case MMC_RSP_TYPE_R5:
case MMC_RSP_TYPE_R6:
val |= (0x2 << CMD_XFR_TYP_RSPTYP_SHF);
val |= CMD_XFR_TYP_CICEN;
val |= CMD_XFR_TYP_CCCEN;
break;
case MMC_RSP_TYPE_R1b:
case MMC_RSP_TYPE_R5b:
val |= (0x3 << CMD_XFR_TYP_RSPTYP_SHF);
val |= CMD_XFR_TYP_CICEN;
val |= CMD_XFR_TYP_CCCEN;
break;
default:
break;
}
if (cmd->data) {
val |= CMD_XFR_TYP_DPSEL;
}
/* Issue the command. */
writel(val, host->base + CMD_XFR_TYP);
return 0;
}
/** Pass control to the devices IRQ handler
* @param[in] sd_dev The sdhc interface device that triggered
* the interrupt event.
*/
static int sdhc_handle_irq(sdio_host_dev_t *sdio, int irq UNUSED)
{
sdhc_dev_t host = sdio_get_sdhc(sdio);
struct mmc_cmd *cmd = host->cmd_list_head;
uint32_t int_status;
int_status = readl(host->base + INT_STATUS);
if (!cmd) {
/* Clear flags */
writel(int_status, host->base + INT_STATUS);
return 0;
}
/** Handle errors **/
if (int_status & INT_STATUS_TNE) {
LOG_ERROR("Tuning error");
}
if (int_status & INT_STATUS_OVRCURE) {
LOG_ERROR("Bus overcurrent"); /* (exl. IMX6) */
}
if (int_status & INT_STATUS_ERR) {
LOG_ERROR("CMD/DATA transfer error"); /* (exl. IMX6) */
cmd->complete = -1;
}
if (int_status & INT_STATUS_AC12E) {
LOG_ERROR("Auto CMD12 Error");
cmd->complete = -1;
}
/** DMA errors **/
if (int_status & INT_STATUS_DMAE) {
LOG_ERROR("DMA Error");
cmd->complete = -1;
}
if (int_status & INT_STATUS_ADMAE) {
LOG_ERROR("ADMA error"); /* (exl. IMX6) */
cmd->complete = -1;
}
/** DATA errors **/
if (int_status & INT_STATUS_DEBE) {
LOG_ERROR("Data end bit error");
cmd->complete = -1;
}
if (int_status & INT_STATUS_DCE) {
LOG_ERROR("Data CRC error");
cmd->complete = -1;
}
if (int_status & INT_STATUS_DTOE) {
LOG_ERROR("Data transfer error");
cmd->complete = -1;
}
/** CMD errors **/
if (int_status & INT_STATUS_CIE) {
LOG_ERROR("Command index error");
cmd->complete = -1;
}
if (int_status & INT_STATUS_CEBE) {
LOG_ERROR("Command end bit error");
cmd->complete = -1;
}
if (int_status & INT_STATUS_CCE) {
LOG_ERROR("Command CRC error");
cmd->complete = -1;
}
if (int_status & INT_STATUS_CTOE) {
LOG_ERROR("CMD Timeout...");
cmd->complete = -1;
}
if (int_status & INT_STATUS_TP) {
LOG_INFO("Tuning pass");
}
if (int_status & INT_STATUS_RTE) {
LOG_INFO("Retuning event");
}
if (int_status & INT_STATUS_CINT) {
LOG_INFO("Card interrupt");
}
if (int_status & INT_STATUS_CRM) {
LOG_INFO("Card removal");
cmd->complete = -1;
}
if (int_status & INT_STATUS_CINS) {
LOG_INFO("Card insertion");
}
if (int_status & INT_STATUS_DINT) {
LOG_INFO("DMA interrupt");
}
if (int_status & INT_STATUS_BGE) {
LOG_INFO("Block gap event");
}
/* Command complete */
if (int_status & INT_STATUS_CC) {
/* Command complete */
switch (cmd->rsp_type) {
case MMC_RSP_TYPE_R2:
cmd->response[0] = readl(host->base + CMD_RSP0);
cmd->response[1] = readl(host->base + CMD_RSP1);
cmd->response[2] = readl(host->base + CMD_RSP2);
cmd->response[3] = readl(host->base + CMD_RSP3);
break;
case MMC_RSP_TYPE_R1b:
if (cmd->index == MMC_STOP_TRANSMISSION) {
cmd->response[3] = readl(host->base + CMD_RSP3);
} else {
cmd->response[0] = readl(host->base + CMD_RSP0);
}
break;
case MMC_RSP_TYPE_NONE:
break;
default:
cmd->response[0] = readl(host->base + CMD_RSP0);
}
/* If there is no data segment, the transfer is complete */
if (cmd->data == NULL) {
assert(cmd->complete == 0);
cmd->complete = 1;
}
}
/* DATA: Programmed IO handling */
if (int_status & (INT_STATUS_BRR | INT_STATUS_BWR)) {
volatile uint32_t *io_buf;
uint32_t *usr_buf;
assert(cmd->data);
assert(cmd->data->vbuf);
assert(cmd->complete == 0);
if (host->blocks_remaining) {
io_buf = (volatile uint32_t *)((void *)host->base + DATA_BUFF_ACC_PORT);
usr_buf = (uint32_t *)cmd->data->vbuf;
if (int_status & INT_STATUS_BRR) {
/* Buffer Read Ready */
int i;
for (i = 0; i < cmd->data->block_size; i += sizeof(*usr_buf)) {
*usr_buf++ = *io_buf;
}
} else {
/* Buffer Write Ready */
int i;
for (i = 0; i < cmd->data->block_size; i += sizeof(*usr_buf)) {
*io_buf = *usr_buf++;
}
}
host->blocks_remaining--;
}
}
/* Data complete */
if (int_status & INT_STATUS_TC) {
assert(cmd->complete == 0);
cmd->complete = 1;
}
/* Clear flags */
writel(int_status, host->base + INT_STATUS);
/* If the transaction has finished */
if (cmd != NULL && cmd->complete != 0) {
if (cmd->next == NULL) {
/* Shutdown */
host->cmd_list_head = NULL;
host->cmd_list_tail = &host->cmd_list_head;
} else {
/* Next */
host->cmd_list_head = cmd->next;
sdhc_next_cmd(host);
}
cmd->next = NULL;
/* Send callback if required */
if (cmd->cb) {
cmd->cb(sdio, 0, cmd, cmd->token);
}
}
return 0;
}
static int sdhc_is_voltage_compatible(sdio_host_dev_t *sdio, int mv)
{
uint32_t val;
sdhc_dev_t host = sdio_get_sdhc(sdio);
val = readl(host->base + HOST_CTRL_CAP);
if (mv == 3300 && (val & HOST_CTRL_CAP_VS33)) {
return 1;
} else {
return 0;
}
}
static int sdhc_send_cmd(sdio_host_dev_t *sdio, struct mmc_cmd *cmd, sdio_cb cb, void *token)
{
sdhc_dev_t host = sdio_get_sdhc(sdio);
int ret;
/* Initialise callbacks */
cmd->complete = 0;
cmd->next = NULL;
cmd->cb = cb;
cmd->token = token;
/* Append to list */
*host->cmd_list_tail = cmd;
host->cmd_list_tail = &cmd->next;
/* If idle, bump */
if (host->cmd_list_head == cmd) {
ret = sdhc_next_cmd(host);
if (ret) {
return ret;
}
}
/* finalise the transacton */
if (cb == NULL) {
/* Wait for completion */
while (!cmd->complete) {
sdhc_handle_irq(sdio, 0);
}
/* Return result */
if (cmd->complete < 0) {
return cmd->complete;
} else {
return 0;
}
} else {
/* Defer to IRQ handler */
return 0;
}
}
/** Software Reset */
static int sdhc_reset(sdio_host_dev_t *sdio)
{
sdhc_dev_t host = sdio_get_sdhc(sdio);
uint32_t val;
/* Reset the host */
val = readl(host->base + SYS_CTRL);
val |= SYS_CTRL_RSTA;
writel(val, host->base + SYS_CTRL);
do {
val = readl(host->base + SYS_CTRL);
} while (val & SYS_CTRL_RSTA);
/* Enable IRQs */
val = (INT_STATUS_ADMAE | INT_STATUS_OVRCURE | INT_STATUS_DEBE
| INT_STATUS_DCE | INT_STATUS_DTOE | INT_STATUS_CRM
| INT_STATUS_CINS | INT_STATUS_BRR | INT_STATUS_BWR
| INT_STATUS_CIE | INT_STATUS_CEBE | INT_STATUS_CCE
| INT_STATUS_CTOE | INT_STATUS_TC | INT_STATUS_CC);
writel(val, host->base + INT_STATUS_EN);
writel(val, host->base + INT_SIGNAL_EN);
/* Set clock */
val = readl(host->base + SYS_CTRL);
val |= SYS_CTRL_CLK_INT_EN;
writel(val, host->base + SYS_CTRL);
do {
val = readl(host->base + SYS_CTRL);
} while (!(val & SYS_CTRL_CLK_INT_STABLE));
val |= SYS_CTRL_CLK_CARD_EN;
writel(val, host->base + SYS_CTRL);
/* Set Clock
* TODO: Hard-coded clock freq based on a *198MHz* default input.
*/
/* make sure the clock state is stable. */
if (readl(host->base + PRES_STATE) & PRES_STATE_SDSTB) {
val = readl(host->base + SYS_CTRL);
/* The SDCLK bit varies with Data Rate Mode. */
if (readl(host->base + MIX_CTRL) & MIX_CTRL_DDR_EN) {
val &= ~(SYS_CTRL_SDCLKS_MASK << SYS_CTRL_SDCLKS_SHF);
val |= (0x80 << SYS_CTRL_SDCLKS_SHF);
val &= ~(SYS_CTRL_DVS_MASK << SYS_CTRL_DVS_SHF);
val |= (0x0 << SYS_CTRL_DVS_SHF);
} else {
val &= ~(SYS_CTRL_SDCLKS_MASK << SYS_CTRL_SDCLKS_SHF);
val |= (0x80 << SYS_CTRL_SDCLKS_SHF);
val &= ~(SYS_CTRL_DVS_MASK << SYS_CTRL_DVS_SHF);
val |= (0x1 << SYS_CTRL_DVS_SHF);
}
/* Set data timeout value */
val |= (0xE << SYS_CTRL_DTOCV_SHF);
writel(val, host->base + SYS_CTRL);
} else {
D(DBG_ERR, "The clock is unstable, unable to change it!\n");
}
/* TODO: Select Voltage Level */
/* Set bus width */
val = readl(host->base + PROT_CTRL);
val |= MMC_MODE_4BIT;
writel(val, host->base + PROT_CTRL);
/* Wait until the Command and Data Lines are ready. */
while ((readl(host->base + PRES_STATE) & PRES_STATE_CDIHB) ||
(readl(host->base + PRES_STATE) & PRES_STATE_CIHB));
/* Send 80 clock ticks to card to power up. */
val = readl(host->base + SYS_CTRL);
val |= SYS_CTRL_INITA;
writel(val, host->base + SYS_CTRL);
while (readl(host->base + SYS_CTRL) & SYS_CTRL_INITA);
/* Check if a SD card is inserted. */
val = readl(host->base + PRES_STATE);
if (val & PRES_STATE_CINST) {
printf("Card Inserted");
if (!(val & PRES_STATE_WPSPL)) {
printf("(Read Only)");
}
printf("...\n");
} else {
printf("Card Not Present...\n");
}
return 0;
}
static int sdhc_get_nth_irq(sdio_host_dev_t *sdio, int n)
{
sdhc_dev_t host = sdio_get_sdhc(sdio);
if (n < 0 || n >= host->nirqs) {
return -1;
} else {
return host->irq_table[n];
}
}
static uint32_t sdhc_get_present_state_register(sdio_host_dev_t *sdio)
{
return readl(sdio_get_sdhc(sdio)->base + PRES_STATE);
}
int sdhc_init(void *iobase, const int *irq_table, int nirqs, ps_io_ops_t *io_ops,
sdio_host_dev_t *dev)
{
sdhc_dev_t sdhc;
/* Allocate memory for SDHC structure */
sdhc = (sdhc_dev_t)malloc(sizeof(*sdhc));
if (!sdhc) {
LOG_ERROR("Not enough memory!\n");
return -1;
}
/* Complete the initialisation of the SDHC structure */
sdhc->base = iobase;
sdhc->nirqs = nirqs;
sdhc->irq_table = irq_table;
sdhc->dalloc = &io_ops->dma_manager;
sdhc->cmd_list_head = NULL;
sdhc->cmd_list_tail = &sdhc->cmd_list_head;
sdhc->version = ((readl(sdhc->base + HOST_VERSION) >> 16) & 0xff) + 1;
printf("SDHC version %d.00\n", sdhc->version);
/* Initialise SDIO structure */
dev->handle_irq = &sdhc_handle_irq;
dev->nth_irq = &sdhc_get_nth_irq;
dev->send_command = &sdhc_send_cmd;
dev->is_voltage_compatible = &sdhc_is_voltage_compatible;
dev->reset = &sdhc_reset;
dev->get_present_state = &sdhc_get_present_state_register;
dev->priv = sdhc;
/* Clear IRQs */
writel(0, sdhc->base + INT_STATUS_EN);
writel(0, sdhc->base + INT_SIGNAL_EN);
writel(readl(sdhc->base + INT_STATUS), sdhc->base + INT_STATUS);
return 0;
}