#include "s3csdi.h"
#include <arm/s3c2xx0/s3c2440reg.h>
#include <lib/libsa/stand.h>
#include <machine/int_mwgwtypes.h>
#include <machine/limits.h>
#include <dev/sdmmc/sdmmcreg.h>
#define SDI_REG(reg) (*(volatile uint32_t*)(S3C2440_SDI_BASE+reg))
#ifdef SSSDI_DEBUG
#define DPRINTF(s) do {printf s; } while (0)
#else
#define DPRINTF(s) do {} while (0)
#endif
struct s3csdi_softc {
int width;
};
extern int pclk;
static void sssdi_perform_pio_read(struct sdmmc_command *cmd);
static struct s3csdi_softc s3csdi_softc;
int
s3csd_match(unsigned int tag)
{
printf("Found S3C2440 SD/MMC\n");
return 1;
}
void*
s3csd_init(unsigned int tag, uint32_t *caps)
{
uint32_t data;
*caps = SMC_CAPS_4BIT_MODE;
DPRINTF(("CLKCON: 0x%X\n", *(volatile uint32_t*)(S3C2440_CLKMAN_BASE + CLKMAN_CLKCON)));
DPRINTF(("SDI_INT_MASK: 0x%X\n", SDI_REG(SDI_INT_MASK)));
SDI_REG(SDI_INT_MASK) = 0x0;
SDI_REG(SDI_DTIMER) = 0x007FFFFF;
SDI_REG(SDI_CON) &= ~SDICON_ENCLK;
SDI_REG(SDI_CON) = SDICON_SD_RESET | SDICON_CTYP_SD;
data = *(volatile uint32_t*)(S3C2440_GPIO_BASE+GPIO_PGCON);
data = GPIO_SET_FUNC(data, 8, 0x00);
*(volatile uint32_t*)(S3C2440_GPIO_BASE+GPIO_PGCON) = data;
data = *(volatile uint32_t*)(S3C2440_GPIO_BASE+GPIO_PGDAT);
if ( (data & (1<<8)) == (1<<8)) {
printf("No card detected\n");
return 0;
}
printf("Card detected\n");
s3csdi_softc.width = 1;
return (void*)0x01;
}
int
s3csd_bus_clock(void *priv, int freq)
{
int div;
int clock_set = 0;
int control;
int clk = pclk/1000;
clk = (clk / 1000) * 1000;
control = SDI_REG(SDI_CON);
SDI_REG(SDI_CON) = control & ~SDICON_ENCLK;
if (freq == 0)
return 0;
for (div = 1; div <= 256; div++) {
if ( clk / div <= freq) {
DPRINTF(("Using divisor %d: %d/%d = %d\n", div, clk,
div, clk/div));
clock_set = 1;
SDI_REG(SDI_PRE) = div-1;
break;
}
}
if (clock_set) {
SDI_REG(SDI_CON) = control | SDICON_ENCLK;
if (div-1 != SDI_REG(SDI_PRE)) {
return 1;
}
sdmmc_delay(74000/freq);
return 0;
} else {
return 1;
}
}
#define SSSDI_TRANSFER_NONE 0
#define SSSDI_TRANSFER_READ 1
#define SSSDI_TRANSFER_WRITE 2
void
s3csd_exec_cmd(void *priv, struct sdmmc_command *cmd)
{
uint32_t cmd_control;
int status = 0;
uint32_t data_status;
int transfer = SSSDI_TRANSFER_NONE;
DPRINTF(("s3csd_exec_cmd\n"));
SDI_REG(SDI_DAT_FSTA) = 0xFFFFFFFF;
SDI_REG(SDI_DAT_STA) = 0xFFFFFFFF;
SDI_REG(SDI_CMD_STA) = 0xFFFFFFFF;
SDI_REG(SDI_CMD_ARG) = cmd->c_arg;
cmd_control = (cmd->c_opcode & SDICMDCON_CMD_MASK) |
SDICMDCON_HOST_CMD | SDICMDCON_CMST;
if (cmd->c_flags & SCF_RSP_PRESENT)
cmd_control |= SDICMDCON_WAIT_RSP;
if (cmd->c_flags & SCF_RSP_136)
cmd_control |= SDICMDCON_LONG_RSP;
if (cmd->c_datalen > 0 && cmd->c_data != NULL) {
DPRINTF(("DATA, datalen: %d, blk_size: %d, offset: %d\n", cmd->c_datalen,
cmd->c_blklen, cmd->c_arg));
cmd_control |= SDICMDCON_WITH_DATA;
}
if (cmd->c_opcode == MMC_STOP_TRANSMISSION) {
cmd_control |= SDICMDCON_ABORT_CMD;
}
SDI_REG(SDI_DTIMER) = 0x007FFFFF;
SDI_REG(SDI_BSIZE) = cmd->c_blklen;
if ( (cmd->c_flags & SCF_CMD_READ) &&
(cmd_control & SDICMDCON_WITH_DATA)) {
uint32_t data_control;
DPRINTF(("Reading %d bytes\n", cmd->c_datalen));
transfer = SSSDI_TRANSFER_READ;
data_control = SDIDATCON_DATMODE_RECEIVE | SDIDATCON_RACMD |
SDIDATCON_DTST | SDIDATCON_BLKMODE |
((cmd->c_datalen / cmd->c_blklen) & SDIDATCON_BLKNUM_MASK) |
SDIDATCON_DATA_WORD;
if (s3csdi_softc.width == 4) {
data_control |= SDIDATCON_WIDEBUS;
}
SDI_REG(SDI_DAT_CON) = data_control;
} else if (cmd_control & SDICMDCON_WITH_DATA) {
uint32_t data_control;
DPRINTF(("Writing %d bytes\n", cmd->c_datalen));
DPRINTF(("Requesting %d blocks\n",
cmd->c_datalen / cmd->c_blklen));
transfer = SSSDI_TRANSFER_WRITE;
data_control = SDIDATCON_DATMODE_TRANSMIT | SDIDATCON_BLKMODE |
SDIDATCON_TARSP | SDIDATCON_DTST |
((cmd->c_datalen / cmd->c_blklen) & SDIDATCON_BLKNUM_MASK) |
SDIDATCON_DATA_WORD;
SDI_REG(SDI_DAT_CON) = data_control;
}
DPRINTF(("SID_CMD_CON: 0x%X\n", cmd_control));
SDI_REG(SDI_CMD_CON) = cmd_control;
DPRINTF(("Status before cmd sent: 0x%X\n", SDI_REG(SDI_CMD_STA)));
DPRINTF(("Waiting for command being sent\n"));
while( !(SDI_REG(SDI_CMD_STA) & SDICMDSTA_CMD_SENT));
DPRINTF(("Command has been sent\n"));
if (!(cmd_control & SDICMDCON_WAIT_RSP)) {
SDI_REG(SDI_CMD_STA) |= SDICMDSTA_CMD_SENT;
cmd->c_flags |= SCF_ITSDONE;
goto out;
}
DPRINTF(("waiting for response\n"));
while(1) {
status = SDI_REG(SDI_CMD_STA);
if (status & SDICMDSTA_RSP_FIN) {
break;
}
if (status & SDICMDSTA_CMD_TIMEOUT) {
break;
}
}
DPRINTF(("Status: 0x%X\n", status));
if (status & SDICMDSTA_CMD_TIMEOUT) {
cmd->c_error = ETIMEDOUT;
DPRINTF(("Timeout waiting for response\n"));
goto out;
}
DPRINTF(("Got Response\n"));
if (cmd->c_flags & SCF_RSP_136 ) {
uint32_t w[4];
w[0] = SDI_REG(SDI_RSP3);
w[1] = SDI_REG(SDI_RSP2);
w[2] = SDI_REG(SDI_RSP1);
w[3] = SDI_REG(SDI_RSP0);
cmd->c_resp[0] = (w[0] >> 8) | ((w[1] & 0xFF) << 24);
cmd->c_resp[1] = (w[1] >> 8) | ((w[2] & 0XFF) << 24);
cmd->c_resp[2] = (w[2] >> 8) | ((w[3] & 0XFF) << 24);
cmd->c_resp[3] = (w[3] >> 8);
} else {
cmd->c_resp[0] = SDI_REG(SDI_RSP0);
cmd->c_resp[1] = SDI_REG(SDI_RSP1);
}
DPRINTF(("Response: %X %X %X %X\n",
cmd->c_resp[0],
cmd->c_resp[1],
cmd->c_resp[2],
cmd->c_resp[3]));
status = SDI_REG(SDI_DAT_CNT);
DPRINTF(("Remaining bytes of current block: %d\n",
SDIDATCNT_BLK_CNT(status)));
DPRINTF(("Remaining Block Number : %d\n",
SDIDATCNT_BLK_NUM_CNT(status)));
data_status = SDI_REG(SDI_DAT_STA);
DPRINTF(("SDI Data Status Register Before xfer: 0x%X\n", data_status));
if (data_status & SDIDATSTA_DATA_TIMEOUT) {
cmd->c_error = ETIMEDOUT;
DPRINTF(("Timeout waiting for data\n"));
goto out;
}
if (transfer == SSSDI_TRANSFER_READ) {
DPRINTF(("Waiting for transfer to complete\n"));
sssdi_perform_pio_read(cmd);
} else if (transfer == SSSDI_TRANSFER_WRITE) {
}
cmd->c_flags |= SCF_ITSDONE;
out:
data_status = SDI_REG(SDI_DAT_STA);
DPRINTF(("SDI Data Status Register after execute: 0x%X\n", data_status));
SDI_REG(SDI_CMD_STA) = 0xFFFFFFFF;
SDI_REG(SDI_DAT_CON) = 0x0;
}
void
sssdi_perform_pio_read(struct sdmmc_command *cmd)
{
uint32_t status;
uint32_t fifo_status;
int count;
uint32_t written;
uint8_t *dest = (uint8_t*)cmd->c_data;
int i;
written = 0;
while (written < cmd->c_datalen ) {
do {
status = SDI_REG(SDI_DAT_FSTA);
} while( !(status & SDIDATFSTA_RF_FULL) && !(status & SDIDATFSTA_RF_LAST));
fifo_status = SDI_REG(SDI_DAT_FSTA);
count = SDIDATFSTA_FFCNT(fifo_status);
for(i=0; i<count; i+=4) {
uint32_t buf;
buf = SDI_REG(SDI_DAT_LI_W);
*dest = (buf & 0xFF); dest++;
*dest = (buf >> 8) & 0xFF; dest++;
*dest = (buf >> 16) & 0xFF; dest++;
*dest = (buf >> 24) & 0xFF; dest++;
written += 4;
}
}
}
#if 0
void
sssdi_perform_pio_write(struct sdmmc_command *cmd)
{
uint32_t status;
uint32_t fifo_status;
int count;
uint32_t written;
uint32_t *dest = (uint32_t*)cmd->c_data;
written = 0;
while (written < cmd->c_datalen ) {
DPRINTF(("Waiting for FIFO to become empty\n"));
status = sssdi_wait_intr(sc, SDI_FIFO_TX_EMPTY, 1000);
fifo_status = bus_space_read_4(sc->iot, sc->ioh, SDI_DAT_FSTA);
DPRINTF(("PIO Write FIFO Status: 0x%X\n", fifo_status));
count = 64-SDIDATFSTA_FFCNT(fifo_status);
status = bus_space_read_4(sc->iot, sc->ioh, SDI_DAT_CNT);
DPRINTF(("Remaining bytes of current block: %d\n",
SDIDATCNT_BLK_CNT(status)));
DPRINTF(("Remaining Block Number : %d\n",
SDIDATCNT_BLK_NUM_CNT(status)));
status = bus_space_read_4(sc->iot,sc->ioh, SDI_DAT_STA);
DPRINTF(("PIO Write Data Status: 0x%X\n", status));
if (status & SDIDATSTA_DATA_TIMEOUT) {
cmd->c_error = ETIMEDOUT;
bus_space_write_4(sc->iot, sc->ioh, SDI_DAT_STA,
SDIDATSTA_DATA_TIMEOUT);
printf("%s: Data timeout\n", device_xname(sc->dev));
break;
}
DPRINTF(("Filling FIFO with %d bytes\n", count));
for(int i=0; i<count; i+=4) {
bus_space_write_4(sc->iot, sc->ioh, SDI_DAT_LI_W, *dest);
written += 4;
dest++;
}
}
}
#endif
int
s3csd_host_ocr(void *priv)
{
return MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V;
}
int
s3csd_bus_power(void *priv, int ocr)
{
return 0;
}
int
s3csd_bus_width(void *priv, int width)
{
s3csdi_softc.width = width;
return 0;
}
int
s3csd_get_max_bus_clock(void *priv)
{
return pclk / 1;
}