/*
 *  linux/drivers/media/mmc/pxa.c - PXA MMCI driver
 *
 *  Copyright (C) 2003 Russell King, 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.
 *
 *  This hardware is really sick.  No way to clear interrupts.  Have
 *  to turn off the clock whenever we touch the device.  Yuck!
 *
 *	1 and 3 byte data transfers not supported
 *	max block length up to 1023
 *
 * 16-4-2004: Eddi De Pieri  This is still just a test. At the moment it only
 *			     compile successfully. I don't remember if it make
 *			     linux to crash or not
 */

// if/when driver will be finished
//# mknod /dev/mmcblk0 b 254 1
//# mkdir /mmc
//# mount t vfat /dev/mmcblk0 /mmc

#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/blkdev.h>
#include <linux/dma-mapping.h>
#include <linux/mmc/host.h>
#include <linux/mmc/protocol.h>

#include <asm/dma.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/sizes.h>

#include "asic3_mmc.h"

#include <asm/arch-pxa/h4000-gpio.h>

#ifdef CONFIG_MMC_DEBUG
#define DBG(x...)	printk(KERN_DEBUG x)
#else
#define DBG(x...)	do { } while (0)
#endif

struct asic3_mmc_host {
	struct mmc_host		mmc;
	spinlock_t		lock;
	struct resource		*res;
	void			*base;
	int			irq;
	int			dma;
	unsigned int		clkrt;
	unsigned int		cmdat;
	unsigned int		imask;
	unsigned int		power_mode;

	struct mmc_request	*req;
	struct mmc_command	*cmd;
	struct mmc_data		*data;

	dma_addr_t		sg_dma;
	struct pxa_dma_desc	*sg_cpu;

	dma_addr_t		dma_buf;
	unsigned int		dma_size;
	unsigned int		dma_dir;
};

#define to_asic3_mmc_host(x)	container_of(x, struct asic3_mmc_host, mmc)

/*
 * The base MMC clock rate
 */
#define CLOCKRATE	20000000

static inline unsigned int ns_to_clocks(unsigned int ns)
{
	return (ns * (CLOCKRATE / 1000000) + 999) / 1000;
}

static void asic3_mmc_stop_clock(struct asic3_mmc_host *host)
{
//	if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
//		unsigned long flags;
//		unsigned int v;
//
//		writel(STOP_CLOCK, host->base + MMC_STRPCL);
//
//		/*
//		 * Wait for the "clock has stopped" interrupt.
//		 * We need to unmask the interrupt to receive
//		 * the notification.  Sigh.
//		 */
//		spin_lock_irqsave(&host->lock, flags);
//		writel(host->imask & ~CLK_IS_OFF, host->base + MMC_I_MASK);
//		do {
//			v = readl(host->base + MMC_I_REG);
//		} while (!(v & CLK_IS_OFF));
//		writel(host->imask, host->base + MMC_I_MASK);
//		spin_unlock_irqrestore(&host->lock, flags);
//	}
}

static void asic3_mmc_enable_irq(struct asic3_mmc_host *host, unsigned int mask)
{
//	unsigned long flags;

//	spin_lock_irqsave(&host->lock, flags);
//	host->imask &= ~mask;
//	writel(host->imask, host->base + MMC_I_MASK);
//	spin_unlock_irqrestore(&host->lock, flags);
}

static void asic3_mmc_disable_irq(struct asic3_mmc_host *host, unsigned int mask)
{
//	unsigned long flags;
//
//	spin_lock_irqsave(&host->lock, flags);
//	host->imask |= mask;
//	writel(host->imask, host->base + MMC_I_MASK);
//	spin_unlock_irqrestore(&host->lock, flags);
}

static void asic3_mmc_setup_data(struct asic3_mmc_host *host, struct mmc_data *data)
{
//	unsigned int nob = data->blocks;
//	unsigned int timeout, size;
//	dma_addr_t dma;
//	u32 dcmd;
//	int i;
//
//	host->data = data;
//
//	if (data->flags & MMC_DATA_STREAM)
//		nob = 0xffff;
//
//	writel(nob, host->base + MMC_NOB);
//	writel(1 << data->blksz_bits, host->base + MMC_BLKLEN);
//
//	timeout = ns_to_clocks(data->timeout_ns) + data->timeout_clks;
//	writel((timeout + 255) / 256, host->base + MMC_RDTO);
//
//	if (data->flags & MMC_DATA_READ) {
//		host->dma_dir = DMA_FROM_DEVICE;
//		dcmd = DCMD_INCTRGADDR | DCMD_FLOWTRG;
//		DRCMRTXMMC = 0;
//		DRCMRRXMMC = host->dma | DRCMR_MAPVLD;
//	} else {
//		host->dma_dir = DMA_TO_DEVICE;
//		dcmd = DCMD_INCSRCADDR | DCMD_FLOWSRC;
//		DRCMRRXMMC = 0;
//		DRCMRTXMMC = host->dma | DRCMR_MAPVLD;
//	}
//
//	dcmd |= DCMD_BURST32 | DCMD_WIDTH1;
//
//	host->dma_size = data->blocks << data->blksz_bits;
//	host->dma_buf = dma_map_single(host->mmc.dev, data->rq->buffer,
//				       host->dma_size, host->dma_dir);
//
//	for (i = 0, size = host->dma_size, dma = host->dma_buf; size; i++) {
//		u32 len = size;
//
//		if (len > DCMD_LENGTH)
//			len = 0x1000;
//
//		if (data->flags & MMC_DATA_READ) {
//			host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO;
//			host->sg_cpu[i].dtadr = dma;
//		} else {
//			host->sg_cpu[i].dsadr = dma;
//			host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO;
//		}
//		host->sg_cpu[i].dcmd = dcmd | len;
//
//		dma += len;
//		size -= len;
//
//		if (size) {
//			host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) *
//						 sizeof(struct pxa_dma_desc);
//		} else {
//			host->sg_cpu[i].ddadr = DDADR_STOP;
//		}
//	}
//	wmb();
//
//	DDADR(host->dma) = host->sg_dma;
//	DCSR(host->dma) = DCSR_RUN;
}

static void asic3_mmc_start_cmd(struct asic3_mmc_host *host, struct mmc_command *cmd, unsigned int cmdat)
{
//	WARN_ON(host->cmd != NULL);
//	host->cmd = cmd;
//
//	if (cmd->flags & MMC_RSP_BUSY)
//		cmdat |= CMDAT_BUSY;
//
//	switch (cmd->flags & (MMC_RSP_MASK | MMC_RSP_CRC)) {
//	case MMC_RSP_SHORT | MMC_RSP_CRC:
//		cmdat |= CMDAT_RESP_SHORT;
//		break;
//	case MMC_RSP_SHORT:
//		cmdat |= CMDAT_RESP_R3;
//		break;
//	case MMC_RSP_LONG | MMC_RSP_CRC:
//		cmdat |= CMDAT_RESP_R2;
//		break;
//	default:
//		break;
//	}
//
//	writel(cmd->opcode, host->base + MMC_CMD);
//	writel(cmd->arg >> 16, host->base + MMC_ARGH);
//	writel(cmd->arg & 0xffff, host->base + MMC_ARGL);
//	writel(cmdat, host->base + MMC_CMDAT);
//	writel(host->clkrt, host->base + MMC_CLKRT);
//
//	writel(START_CLOCK, host->base + MMC_STRPCL);

//	asic3_mmc_enable_irq(host, END_CMD_RES);
}

static void asic3_mmc_finish_request(struct asic3_mmc_host *host, struct mmc_request *req)
{
//	DBG("asic3_mmc: request done\n");
//	host->req = NULL;
//	host->cmd = NULL;
//	host->data = NULL;
//	mmc_request_done(&host->mmc, req);
}

static int asic3_mmc_cmd_done(struct asic3_mmc_host *host, unsigned int stat)
{
//	struct mmc_command *cmd = host->cmd;
//	int i;
//	u32 v;
//
//	if (!cmd)
//		return 0;
//
//	host->cmd = NULL;
//
//	/*
//	 * Did I mention this is Sick.  We always need to
//	 * discard the upper 8 bits of the first 16-bit word.
//	 */
//	v = readl(host->base + MMC_RES) & 0xffff;
//	for (i = 0; i < 4; i++) {
//		u32 w1 = readl(host->base + MMC_RES) & 0xffff;
//		u32 w2 = readl(host->base + MMC_RES) & 0xffff;
//		cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8;
//		v = w2;
//	}

//	if (stat & STAT_TIME_OUT_RESPONSE) {
//		cmd->error = MMC_ERR_TIMEOUT;
//	} else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
//		cmd->error = MMC_ERR_BADCRC;
//	}
//
//	asic3_mmc_disable_irq(host, END_CMD_RES);
//	if (host->data && cmd->error == MMC_ERR_NONE) {
//		asic3_mmc_enable_irq(host, DATA_TRAN_DONE);
//	} else {
//		asic3_mmc_finish_request(host, host->req);
//	}
//
	return 1;
}

static int asic3_mmc_data_done(struct asic3_mmc_host *host, unsigned int stat)
{
	struct mmc_data *data = host->data;

	if (!data)
		return 0;

	DCSR(host->dma) = 0;
	dma_unmap_single(host->mmc.dev, host->dma_buf, host->dma_size,
			 host->dma_dir);

	if (stat & STAT_READ_TIME_OUT)
		data->error = MMC_ERR_TIMEOUT;
	else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR))
		data->error = MMC_ERR_BADCRC;

	data->bytes_xfered = (data->blocks - readl(host->base + MMC_NOB))
			       << data->blksz_bits;

	asic3_mmc_disable_irq(host, DATA_TRAN_DONE);

	host->data = NULL;
	if (host->req->stop && data->error == MMC_ERR_NONE) {
		asic3_mmc_stop_clock(host);
		asic3_mmc_start_cmd(host, host->req->stop, 0);
	} else {
		asic3_mmc_finish_request(host, host->req);
	}

	return 1;
}

static irqreturn_t asic3_mmc_irq(int irq, void *devid, struct pt_regs *regs)
{
	struct asic3_mmc_host *host = devid;
	unsigned int ireg;
	int handled = 0;

	ireg = readl(host->base + MMC_I_REG);

	DBG("asic3_mmc: irq %08x\n", ireg);

	if (ireg) {
		unsigned stat = readl(host->base + MMC_STAT);

		DBG("asic3_mmc: stat %08x\n", stat);

		if (ireg & END_CMD_RES)
			handled |= asic3_mmc_cmd_done(host, stat);
		if (ireg & DATA_TRAN_DONE)
			handled |= asic3_mmc_data_done(host, stat);
	}

	return IRQ_RETVAL(handled);
}

static void asic3_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
{
	struct asic3_mmc_host *host = to_asic3_mmc_host(mmc);
	unsigned int cmdat;

	WARN_ON(host->req != NULL);

	host->req = req;

	asic3_mmc_stop_clock(host);

	cmdat = host->cmdat;
	host->cmdat &= ~CMDAT_INIT;

	if (req->data) {
		asic3_mmc_setup_data(host, req->data);

		cmdat &= ~CMDAT_BUSY;
		cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;
		if (req->data->flags & MMC_DATA_WRITE)
			cmdat |= CMDAT_WRITE;

		if (req->data->flags & MMC_DATA_STREAM)
			cmdat |= CMDAT_STREAM;
	}

	asic3_mmc_start_cmd(host, req->cmd, cmdat);
}

static void asic3_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
	struct asic3_mmc_host *host = to_asic3_mmc_host(mmc);

	DBG("asic3_mmc_set_ios: clock %u power %u vdd %u.%02u\n",
	    ios->clock, ios->power_mode, ios->vdd / 100,
	    ios->vdd % 100);

	if (ios->clock) {
		unsigned int clk = CLOCKRATE / ios->clock;
		if (CLOCKRATE / clk > ios->clock)
			clk <<= 1;
		host->clkrt = fls(clk) - 1;

		/*
		 * we write clkrt on the next command
		 */
	} else if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
		/*
		 * Ensure that the clock is off.
		 */
		writel(STOP_CLOCK, host->base + MMC_STRPCL);
	}

	if (host->power_mode != ios->power_mode) {
		host->power_mode = ios->power_mode;

		/*
		 * power control?  none on the lubbock.
		 */

		if (ios->power_mode == MMC_POWER_ON)
			host->cmdat |= CMDAT_INIT;
	}

	DBG("asic3_mmc_set_ios: clkrt = %x cmdat = %x\n",
	    host->clkrt, host->cmdat);
}

static struct mmc_host_ops asic3_mmc_ops = {
	.request	= asic3_mmc_request,
	.set_ios	= asic3_mmc_set_ios,
};

static struct resource *platform_device_resource(struct platform_device *dev, unsigned int mask, int nr)
{
	int i;

	for (i = 0; i < dev->num_resources; i++)
		if (dev->resource[i].flags == mask && nr-- == 0)
			return &dev->resource[i];
	return NULL;
}

static int platform_device_irq(struct platform_device *dev, int nr)
{
	int i;

	for (i = 0; i < dev->num_resources; i++)
		if (dev->resource[i].flags == IORESOURCE_IRQ && nr-- == 0)
			return dev->resource[i].start;
	return NO_IRQ;
}

static void asic3_mmc_dma_irq(int dma, void *devid, struct pt_regs *regs)
{
	printk(KERN_ERR "DMA%d: IRQ???\n", dma);
	DCSR(dma) = DCSR_STARTINTR|DCSR_ENDINTR|DCSR_BUSERR;
}

static int asic3_mmc_probe(struct device *dev)
{
//	struct platform_device *pdev = to_platform_device(dev);
	struct asic3_mmc_host *host;
//	struct resource *r;
	int ret, irq;

	printk("%s: MMC probing...\n", __FUNCTION__);

//	r = platform_device_resource(pdev, IORESOURCE_MEM, 0);
//	irq = platform_device_irq(pdev, 0);
//	if (!r || irq == NO_IRQ)
	printk("%s: Calculating irq...",__FUNCTION__);
	irq = IRQ_GPIO(GPIO_NR_H4000_SD_DETECT_N);
	printk("%d\n",irq);
	if ( irq == NO_IRQ)
		return -ENXIO;
//
//	r = request_mem_region(r->start, SZ_4K, "asic3_mmc");
//	if (!r)
//		return -EBUSY;
//
	host = kmalloc(sizeof(struct asic3_mmc_host), GFP_KERNEL);
	if (!host) {
		ret = -ENOMEM;
		goto out;
	}

	memset(host, 0, sizeof(struct asic3_mmc_host));
	host->dma = -1;

//	host->sg_cpu = dma_alloc_coherent(dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
//	if (!host->sg_cpu) {
//		ret = -ENOMEM;
//		goto out;
//	}

	ret = mmc_init_host(&host->mmc);
	if (ret)
		goto out;

	spin_lock_init(&host->lock);
//	host->res = r;
	host->irq = irq;
//	host->imask = TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD|
//		      END_CMD_RES|PRG_DONE|DATA_TRAN_DONE;
	host->mmc.dev = dev;
	host->mmc.ops = &asic3_mmc_ops;
	host->mmc.f_min	= 312500;
	host->mmc.f_max	= 20000000;
	host->mmc.ocr_avail = MMC_VDD_32_33;

//	host->base = ioremap(r->start, SZ_4K);
//	if (!host->base) {
//		ret = -ENOMEM;
//		goto out;
//	}
//
//	/*
//	 * Ensure that the host controller is shut down, and setup
//	 * with our defaults.
//	 */
//	asic3_mmc_stop_clock(host);
//	writel(0, host->base + MMC_SPI);
//	writel(64, host->base + MMC_RESTO);
//
//#ifdef CONFIG_PREEMPT
//#error Not Preempt-safe
//#endif
//	pxa_gpio_mode(GPIO6_MMCCLK_MD);
//	pxa_gpio_mode(GPIO8_MMCCS0_MD);
//	CKEN |= CKEN12_MMC;
//
//	host->dma = pxa_request_dma("asic3_mmc", DMA_PRIO_LOW, asic3_mmc_dma_irq, host);
//	if (host->dma < 0)
//		goto out;
//
	ret = request_irq(host->irq, asic3_mmc_irq, 0, "asic3_mmc", host);
	if (ret)
		goto out;
//
//	dev_set_drvdata(dev, host);
//
//	mmc_add_host(&host->mmc);
//
//	return 0;
//
 out:
//	if (host) {
//		if (host->dma >= 0)
//			pxa_free_dma(host->dma);
//		if (host->base)
//			iounmap(host->base);
//		if (host->sg_cpu)
//			dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
//		kfree(host);
//	}
//	release_resource(r);
	return ret;
}

static int asic3_mmc_remove(struct device *dev)
{
	struct asic3_mmc_host *host = dev_get_drvdata(dev);

	printk("%s: MMC removing...\n", __FUNCTION__);

	dev_set_drvdata(dev, NULL);

	if (host) {
		mmc_remove_host(&host->mmc);

//		asic3_mmc_stop_clock(host);
//		writel(TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD|
//		       END_CMD_RES|PRG_DONE|DATA_TRAN_DONE,
//		       host->base + MMC_I_MASK);
//
		free_irq(host->irq, host);
//		pxa_free_dma(host->dma);
//		iounmap(host->base);
//		dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);

		release_resource(host->res);

		kfree(host);
	}
	return 0;
}

static int asic3_mmc_suspend(struct device *dev, u32 state, u32 level)
{
//	struct asic3_mmc_host *host = dev_get_drvdata(dev);
	int ret = 0;

//	if (host && level == SUSPEND_DISABLE)
//		ret = mmc_suspend_host(&host->mmc, state);
	return ret;
}

static int asic3_mmc_resume(struct device *dev, u32 level)
{
//	struct asic3_mmc_host *host = dev_get_drvdata(dev);
	int ret = 0;
//
//	if (host && level == RESUME_ENABLE)
//		ret = mmc_resume_host(&host->mmc);
	return ret;
}

static struct device_driver asic3_mmc_driver = {
	.name		= "asic3_mmc",
	.bus		= &platform_bus_type,
	.probe		= asic3_mmc_probe,
	.remove		= asic3_mmc_remove,
	.suspend	= asic3_mmc_suspend,
	.resume		= asic3_mmc_resume,
};

static int __init asic3_mmc_init(void)
{
	printk("%s: Driver register...\n", __FUNCTION__);
	return driver_register(&asic3_mmc_driver);
}

static void __exit asic3_mmc_exit(void)
{
	printk("%s: Driver unregister...\n", __FUNCTION__);
	driver_unregister(&asic3_mmc_driver);
}

module_init(asic3_mmc_init);
module_exit(asic3_mmc_exit);

MODULE_DESCRIPTION("ASIC3 Multimedia Card Interface Driver");
MODULE_LICENSE("GPL");
