/* 
 * iPAQ H5400 MMC ASIC lowlevel
 *
 * Copyright 2002 Hewlett-Packard Company
 *
 * Use consistent with the GNU GPL is permitted,
 * provided that this copyright notice is
 * preserved in its entirety in all copies and derived works.
 *
 * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
 * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
 * FITNESS FOR ANY PARTICULAR PURPOSE.
 *
 * Author:  Jamey Hicks
 *          7 July 2003
 */


#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>

#include <asm/irq.h>       
#include <asm/io.h>        /* ioremap() */
#include <asm/unaligned.h>
#include <asm/mach-types.h>

#include <asm/arch/hardware.h>
#include <asm/arch/h5400-gpio.h>
#include <asm/arch/h5400-asic.h>
#include <asm/arch/h5400-irqs.h>
#include <linux/mmc/mmc_ll.h>
#include "mmc_samsung.h"

static int h5400_mmc_slot_init(struct s3c_mmc_lowlevel *lowlevel)
{
	printk("%s", __FUNCTION__);
	/* enable power to slot */
	SET_H5400_GPIO(POWER_SD_N, 1);

	/* sd_detect (wakeup) interrupt on both edges */
	H5400_ASIC_SET_BIT(H5400_ASIC_GPIO_INT3, 0xf);
	/* set filtering width to 0xF + 1 */
	H5400_ASIC_CLEAR_BIT(H5400_ASIC_GPIO_INT3, (0xFFFF << 16));
	H5400_ASIC_SET_BIT(H5400_ASIC_GPIO_FLTCONFIG7, (0xf << 16));

	H5400_ASIC_SET_BIT(H5400_ASIC_GPIO_ENINT1, H5400_ASIC_GPIO_ENINT1_SDWAKE);

	/* turn on data pullups */
	H5400_ASIC_CLEAR_BIT(H5400_ASIC_GPIO_SPCR, H5400_ASIC_GPIO_SPCR_SDPUCR);

	/* enable pull up */
	H5400_ASIC_CLEAR_BIT(H5400_ASIC_GPIO_GPA_PUP, H5400_ASIC_GPIO_GPA_SD_DETECT_N);
	/* enable clock to SD */
	H5400_ASIC_SET_BIT(H5400_ASIC_CPM_ClockControl, H5400_ASIC_CPM_CLKCON_SD_CLKEN);

	return 0;
}

static int h5400_mmc_slot_cleanup(struct s3c_mmc_lowlevel *lowlevel)
{
	printk("%s", __FUNCTION__);
	/* disable power to slot */
	SET_H5400_GPIO(POWER_SD_N, 0);

	H5400_ASIC_CLEAR_BIT(H5400_ASIC_GPIO_ENINT1, H5400_ASIC_GPIO_ENINT1_SDWAKE);

	/* disable pull up */
	H5400_ASIC_SET_BIT(H5400_ASIC_GPIO_GPA_PUP,  H5400_ASIC_GPIO_GPA_SD_DETECT_N);
	/* disable clock to SD */
	H5400_ASIC_CLEAR_BIT(H5400_ASIC_CPM_ClockControl, H5400_ASIC_CPM_CLKCON_SD_CLKEN);
	
	return 0;
}

static int h5400_mmc_slot_is_empty(struct s3c_mmc_lowlevel *lowlevel)
{
	int retval = (h5400_asic_read_register(H5400_ASIC_GPIO_GPA_DAT) & H5400_ASIC_GPIO_GPA_SD_DETECT_N) ? 1 : 0;
	MMC_DEBUG(2, " is_empty=%d", retval);
	return retval;
}

struct s3c_mmc_lowlevel h5400_mmc_lowlevel = {
	.name          = "h5400_mmc",
	.sdi_irq       = IRQ_H5400_SD,
	.detect_irq    = IRQ_H5400_SD_WAKEUP,
	.slot_init     = h5400_mmc_slot_init,
	.slot_cleanup  = h5400_mmc_slot_cleanup,
	.slot_is_empty = h5400_mmc_slot_is_empty
};

static int __init h5400_mmc_init(void)
{
        int retval;

	if (!machine_is_h5400())
		return -ENODEV;

	h5400_mmc_lowlevel.sd_base = ioremap(H5400_ASIC_PHYS + _H5400_ASIC_SDI_Base, 0x10000) + 0x30;
	retval = s3c_mmc_register_slot(&h5400_mmc_lowlevel);
	if ( retval < 0 )
		printk(KERN_INFO "%s: unable to register slot\n", __FUNCTION__);

	return retval;
}

void __exit h5400_mmc_cleanup(void)
{
	MMC_DEBUG(0,"");
	s3c_mmc_unregister_slot();
}


module_init(h5400_mmc_init);
module_exit(h5400_mmc_cleanup);

MODULE_AUTHOR("Jamey Hicks <jamey.hicks@hp.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("iPAQ H5400 SD/MMC driver ");
