/*
* linux/drivers/mmc/s3c2410mci.h - Samsung S3C2410 SDI Interface driver
*
* Copyright (C) 2004 Thomas Kleffel, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/dma-mapping.h>
#include <linux/mmc/host.h>
#include <linux/mmc/protocol.h>
//#include <linux/clk.h>
#include <asm/dma.h>
#include <asm/dma-mapping.h>
#include <asm/arch/dma.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/hardware/clock.h>
#include <asm/mach/mmc.h>
#include <asm/arch/regs-sdi.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/mmc.h>
#include <asm/arch/regs-clock.h>
//#define S3C2410SDI_DMA_BACKBUF
#ifdef CONFIG_MMC_DEBUG
#define DBG(x...) printk(KERN_INFO x)
#else
#define DBG(x...) do { } while (0)
#endif
#include "s3c2410mci.h"
#define DRIVER_NAME "mmci-s3c2410"
#define PFX DRIVER_NAME ": "
#define RESSIZE(ressource) (((ressource)->end - (ressource)->start)+1)
static struct s3c2410_dma_client s3c2410sdi_dma_client = {
.name = "s3c2410-sdi",
};
/*
* ISR for SDI Interface IRQ
* Communication between driver and ISR works as follows:
* host->mrq points to current request
* host->complete_what tells the ISR when the request is considered done
* COMPLETION_CMDSENT when the command was sent
* COMPLETION_RSPFIN when a response was received
* COMPLETION_XFERFINISH when the data transfer is finished
* COMPLETION_XFERFINISH_RSPFIN both of the above.
* host->complete_request is the completion-object the driver waits for
*
* 1) Driver sets up host->mrq and host->complete_what
* 2) Driver prepares the transfer
* 3) Driver enables interrupts
* 4) Driver starts transfer
* 5) Driver waits for host->complete_rquest
* 6) ISR checks for request status (errors and success)
* 6) ISR sets host->mrq->cmd->error and host->mrq->data->error
* 7) ISR completes host->complete_request
* 8) ISR disables interrupts
* 9) Driver wakes up and takes care of the request
*/
static irqreturn_t s3c2410sdi_irq(int irq, void *dev_id, struct pt_regs *regs)
{
struct s3c2410sdi_host *host;
u32 sdi_csta, sdi_dsta, sdi_dcnt;
u32 sdi_cclear, sdi_dclear;
unsigned long iflags;
host = (struct s3c2410sdi_host *)dev_id;
//Check for things not supposed to happen
if(!host) return IRQ_HANDLED;
sdi_csta = readl(host->base + S3C2410_SDICMDSTAT);
sdi_dsta = readl(host->base + S3C2410_SDIDSTA);
sdi_dcnt = readl(host->base + S3C2410_SDIDCNT);
DBG(PFX "IRQ csta=0x%08x dsta=0x%08x dcnt:0x%08x\n", sdi_csta, sdi_dsta, sdi_dcnt);
spin_lock_irqsave( &host->complete_lock, iflags);
if( host->complete_what==COMPLETION_NONE ) {
goto clear_imask;
}
if(!host->mrq) {
goto clear_imask;
}
sdi_csta = readl(host->base + S3C2410_SDICMDSTAT);
sdi_dsta = readl(host->base + S3C2410_SDIDSTA);
sdi_dcnt = readl(host->base + S3C2410_SDIDCNT);
sdi_cclear = 0;
sdi_dclear = 0;
if(sdi_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) {
host->mrq->cmd->error = MMC_ERR_TIMEOUT;
goto transfer_closed;
}
if(sdi_csta & S3C2410_SDICMDSTAT_CMDSENT) {
if(host->complete_what == COMPLETION_CMDSENT) {
host->mrq->cmd->error = MMC_ERR_NONE;
goto transfer_closed;
}
sdi_cclear |= S3C2410_SDICMDSTAT_CMDSENT;
}
if(sdi_csta & S3C2410_SDICMDSTAT_CRCFAIL) {
if (host->mrq->cmd->flags & MMC_RSP_LONG) {
DBG(PFX "s3c2410 fixup : ignore CRC fail with long rsp\n");
}
else {
DBG(PFX "COMMAND CRC FAILED %x\n", sdi_csta);
if(host->mrq->cmd->flags & MMC_RSP_CRC) {
host->mrq->cmd->error = MMC_ERR_BADCRC;
goto transfer_closed;
}
}
sdi_cclear |= S3C2410_SDICMDSTAT_CRCFAIL;
}
if(sdi_csta & S3C2410_SDICMDSTAT_RSPFIN) {
if(host->complete_what == COMPLETION_RSPFIN) {
host->mrq->cmd->error = MMC_ERR_NONE;
goto transfer_closed;
}
if(host->complete_what == COMPLETION_XFERFINISH_RSPFIN) {
host->mrq->cmd->error = MMC_ERR_NONE;
host->complete_what = COMPLETION_XFERFINISH;
}
sdi_cclear |= S3C2410_SDICMDSTAT_RSPFIN;
}
if(sdi_dsta & S3C2410_SDIDSTA_FIFOFAIL) {
host->mrq->cmd->error = MMC_ERR_NONE;
host->mrq->data->error = MMC_ERR_FIFO;
goto transfer_closed;
}
if(sdi_dsta & S3C2410_SDIDSTA_RXCRCFAIL) {
host->mrq->cmd->error = MMC_ERR_NONE;
host->mrq->data->error = MMC_ERR_BADCRC;
goto transfer_closed;
}
if(sdi_dsta & S3C2410_SDIDSTA_CRCFAIL) {
host->mrq->cmd->error = MMC_ERR_NONE;
host->mrq->data->error = MMC_ERR_BADCRC;
goto transfer_closed;
}
if(sdi_dsta & S3C2410_SDIDSTA_DATATIMEOUT) {
host->mrq->cmd->error = MMC_ERR_NONE;
host->mrq->data->error = MMC_ERR_TIMEOUT;
goto transfer_closed;
}
if(sdi_dsta & S3C2410_SDIDSTA_XFERFINISH) {
if(host->complete_what == COMPLETION_XFERFINISH) {
host->mrq->cmd->error = MMC_ERR_NONE;
host->mrq->data->error = MMC_ERR_NONE;
goto transfer_closed;
}
if(host->complete_what == COMPLETION_XFERFINISH_RSPFIN) {
host->mrq->data->error = MMC_ERR_NONE;
host->complete_what = COMPLETION_RSPFIN;
}
sdi_dclear |= S3C2410_SDIDSTA_XFERFINISH;
}
writel(sdi_cclear, host->base + S3C2410_SDICMDSTAT);
writel(sdi_dclear, host->base + S3C2410_SDIDSTA);
spin_unlock_irqrestore( &host->complete_lock, iflags);
DBG(PFX "IRQ still waiting.\n");
return IRQ_HANDLED;
transfer_closed:
writel(sdi_cclear, host->base + S3C2410_SDICMDSTAT);
writel(sdi_dclear, host->base + S3C2410_SDIDSTA);
host->complete_what = COMPLETION_NONE;
complete(&host->complete_request);
writel(0, host->base + S3C2410_SDIIMSK);
spin_unlock_irqrestore( &host->complete_lock, iflags);
DBG(PFX "IRQ transfer closed.\n");
return IRQ_HANDLED;
clear_imask:
writel(0, host->base + S3C2410_SDIIMSK);
spin_unlock_irqrestore( &host->complete_lock, iflags);
DBG(PFX "IRQ clear imask.\n");
return IRQ_HANDLED;
}
/*
* ISR for the CardDetect Pin
*/
static irqreturn_t s3c2410sdi_irq_cd(int irq, void *dev_id, struct pt_regs *regs)
{
struct s3c2410sdi_host *host = (struct s3c2410sdi_host *)dev_id;
//printk("s3c2410sdi_irq_cd\n");
mmc_detect_change(host->mmc, S3C2410SDI_CDLATENCY);
return IRQ_HANDLED;
}
void s3c2410sdi_dma_done_callback(s3c2410_dma_chan_t *dma_ch, void *buf_id,
int size, s3c2410_dma_buffresult_t result)
{ unsigned long iflags;
u32 sdi_csta, sdi_dsta,sdi_dcnt;
struct s3c2410sdi_host *host = (struct s3c2410sdi_host *)buf_id;
sdi_csta = readl(host->base + S3C2410_SDICMDSTAT);
sdi_dsta = readl(host->base + S3C2410_SDIDSTA);
sdi_dcnt = readl(host->base + S3C2410_SDIDCNT);
DBG(PFX "DMAD csta=0x%08x dsta=0x%08x dcnt:0x%08x result:0x%08x\n", sdi_csta, sdi_dsta, sdi_dcnt, result);
spin_lock_irqsave( &host->complete_lock, iflags);
if(!host->mrq) goto out;
if(!host->mrq->data) goto out;
sdi_csta = readl(host->base + S3C2410_SDICMDSTAT);
sdi_dsta = readl(host->base + S3C2410_SDIDSTA);
sdi_dcnt = readl(host->base + S3C2410_SDIDCNT);
if( result!=S3C2410_RES_OK ) {
printk("result !=S3C2410_RES_OK\n");
goto fail_request;
}
if(host->mrq->data->flags & MMC_DATA_READ) {
if( sdi_dcnt>0 ) {
printk("MMC_DATA_READ:sdi_dcnt>0\n");
goto fail_request;
}
}
out:
complete(&host->complete_dma);
spin_unlock_irqrestore( &host->complete_lock, iflags);
return;
fail_request:
host->mrq->data->error = MMC_ERR_FAILED;
host->complete_what = COMPLETION_NONE;
complete(&host->complete_request);
writel(0, host->base + S3C2410_SDIIMSK);
goto out;
}
void s3c2410sdi_dma_setup(struct s3c2410sdi_host *host, s3c2410_dmasrc_t source) {
s3c2410_dma_devconfig(host->dma, source, 3, host->mem->start + S3C2410_SDIDATA);
s3c2410_dma_config(host->dma, 4, (1<<23)