/*
 *  linux/drivers/usb/usb-ohci-omap1510.c
 *
 *  The outline of this code was taken from Brad Parkers <brad@heeltoe.com>
 *  original OHCI driver modifications, and reworked into a cleaner form
 *  by Russell King <rmk@arm.linux.org.uk>.
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/pci.h>		/* for pci_pool_* prototypes */
#include <linux/usb.h>
#ifdef CONFIG_PM
#include <linux/pm.h>
#endif

#include <asm/hardware.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/gpio.h>
#include <asm/arch/pm.h>
#include <asm/arch/ck.h>

#include "usb-ohci.h"

extern int __devinit hc_add_ohci(struct pci_dev *dev, int irq, void *membase,
				 unsigned long flags, ohci_t ** ohci,
				 const char *name, const char *slot_name);
extern void __devexit hc_remove_ohci(ohci_t * ohci);

static ohci_t *omap1510_ohci;

/* bogus pci_dev structure */
static struct pci_dev bogus_pcidev;

static int __devinit omap1510_ohci_configure(void);
static void __devexit omap1510_ohci_release(void);

#define APLL_CTRL_REG_APLL_NDPLL_SWITCH		0x0001
#define DPLL_CTRL_REG_PLL_ENABLE		0x0010
#define DPLL_CTRL_REG_LOCK			0x0001
#define SOFT_REQ_REG_DPLL_REQ			0x0001
#define CLOCK_CTRL_REG_USB_MCLK_EN		0x0010
#define MOD_CONF_CTRL_0_USB_HOST_HHC_UHOST_EN	0x00000200

#if defined (CONFIG_ARCHOS_AV500)
static int av500_ohci_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data)
{
        unsigned long val32;
        ohci_t * ohci = omap1510_ohci;

        if (rqst == PM_SUSPEND) {
                /* switch off VBUS power */
                omap_gpio_set(7);
                /* wait 1 second, should result in a disconnect event on the bus */
                set_current_state(TASK_UNINTERRUPTIBLE);
                schedule_timeout(HZ);
                /* switch off the USB Host */
                val32 = inl(MOD_CONF_CTRL_0) & ~MOD_CONF_CTRL_0_USB_HOST_HHC_UHOST_EN;
                outl(val32, MOD_CONF_CTRL_0);
                /* release the MCLK request */
                *ULPD_CLOCK_CTRL_REG &= ~CLOCK_CTRL_REG_USB_MCLK_EN;
                /* release the software request for the DPLL */
                *ULPD_SOFT_REQ_REG &= ~SOFT_REQ_REG_DPLL_REQ;
        } else
        if (rqst == PM_RESUME) {
                int timeout = 30;
                int smm_timeout = 50; /* 0,5 sec */
                unsigned int fminterval;

                /* request the 48MHz DPLL */
                *ULPD_SOFT_REQ_REG |= SOFT_REQ_REG_DPLL_REQ;
                /* MCLK request */
                *ULPD_CLOCK_CTRL_REG |= CLOCK_CTRL_REG_USB_MCLK_EN;
                /* switch on USB Host */
                val32 = inl(MOD_CONF_CTRL_0) | MOD_CONF_CTRL_0_USB_HOST_HHC_UHOST_EN;
                outl(val32, MOD_CONF_CTRL_0);

                /*
                 * HOST CONTROLLER RESET
                 */

                /* copied from hc_reset(ohci) */
                if (readl (&ohci->regs->control) & OHCI_CTRL_IR) { /* SMM owns the HC */
                        writel (OHCI_OCR, &ohci->regs->cmdstatus); /* request ownership */
                        printk(KERN_DEBUG "USB HC TakeOver from SMM\n");
                        while (readl (&ohci->regs->control) & OHCI_CTRL_IR) {
                                wait_ms (10);
                                if (--smm_timeout == 0) {
                                        printk(KERN_ERR "USB HC TakeOver failed!\n");
                                        return 1;
                                }
                        }
                }

                /* Disable HC interrupts */
                writel (OHCI_INTR_MIE, &ohci->regs->intrdisable);

                printk(KERN_DEBUG "USB HC reset_hc usb-%s: ctrl = 0x%x\n",
                        ohci->slot_name,
                        readl (&ohci->regs->control));

                /* Reset USB (needed by some controllers) */
                writel (0, &ohci->regs->control);

                /* HC Reset requires max 10 ms delay */
                writel (OHCI_HCR,  &ohci->regs->cmdstatus);
                while ((readl (&ohci->regs->cmdstatus) & OHCI_HCR) != 0) {
                        if (--timeout == 0) {
                                printk(KERN_ERR "USB HC reset timed out!\n");
                                return 1;
                        }
                        udelay (1);
                }

                /*
                 * HOST CONTROLLER START
                 */

                /* code copied from hc_start(ohci) */
                ohci->disabled = 1;

                /* Tell the controller where the control and bulk lists are
                * The lists are empty now. */

                writel (0, &ohci->regs->ed_controlhead);
                writel (0, &ohci->regs->ed_bulkhead);

                writel (ohci->hcca_dma, &ohci->regs->hcca); /* a reset clears this */

                fminterval = 0x2edf;
                writel ((fminterval * 9) / 10, &ohci->regs->periodicstart);
                fminterval |= ((((fminterval - 210) * 6) / 7) << 16);
                writel (fminterval, &ohci->regs->fminterval);
                writel (0x628, &ohci->regs->lsthresh);

                /* start controller operations */
                ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER;
                ohci->disabled = 0;
                writel (ohci->hc_control, &ohci->regs->control);

                /* Choose the interrupts we care about now, others later on demand */
                val32 = OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_WDH | OHCI_INTR_SO;
                writel (val32, &ohci->regs->intrenable);
                writel (val32, &ohci->regs->intrstatus);

                /* switch on VBUS power */
                omap_gpio_clr(7);
        }

        return 0;
}
#endif

static int __devinit
omap1510_ohci_configure(void)
{
	/* TO DO:  make a proper header file for all of these registers. */
#ifdef CONFIG_OMAP_INNOVATOR
	volatile unsigned char *fpga_usb_host_ctrl =
	    (unsigned char *) 0xe800020c;
#endif
	volatile unsigned short *apll_ctrl_reg = (unsigned short *) 0xfffe084c;

	volatile unsigned long *lb_clock_div = (unsigned long *) 0xfffec10c;
	volatile unsigned long *ohci_host_tmout_ctrl = (unsigned long*) 0xfffba0e8;
	unsigned long val32;
	int ret;

	/*
	 * Request memory resources.
	 */
//	if (!request_mem_region(_USB_OHCI_OP_BASE, _USB_EXTENT, "usb-ohci"))
//		return -EBUSY;

        /* switch ULPD PLL from APLL to DPLL */
	*apll_ctrl_reg &= ~APLL_CTRL_REG_APLL_NDPLL_SWITCH;
        /* enable the DPLL and generate a software request for the clock */
	*ULPD_DPLL_CTRL_REG  |= DPLL_CTRL_REG_PLL_ENABLE;
	*ULPD_SOFT_REQ_REG   |= SOFT_REQ_REG_DPLL_REQ;
        /* wait for the DPLL to lock */
	while (!(*ULPD_DPLL_CTRL_REG & DPLL_CTRL_REG_LOCK))
                ;
        /* request 48MHz MCLOCK for USB */
	*ULPD_CLOCK_CTRL_REG |= CLOCK_CTRL_REG_USB_MCLK_EN;
        /* enable the local bus and lbfree??? clock */
        ck_enable(lb_ck);
        ck_enable(lbfree_ck);

        /* switch on USB host controller with HMC=18 */
        val32 = inl(MOD_CONF_CTRL_0);
        val32 &= ~(0x3f<<1);
        val32 |= MOD_CONF_CTRL_0_USB_HOST_HHC_UHOST_EN | (18<<1);
        outl(val32, MOD_CONF_CTRL_0);

#if defined(CONFIG_OMAP_INNOVATOR) && !defined(CONFIG_ARCHOS_AV500)
	*fpga_usb_host_ctrl |= 0x20;
#endif

	*lb_clock_div = (*lb_clock_div & 0xfffffff8) | 0x4;

	/*
	 * Fill in some fields of the bogus pci_dev.
	 */
	memset(&bogus_pcidev, 0, sizeof(struct pci_dev));
	strcpy(bogus_pcidev.name, "OMAP1510 OHCI");
	strcpy(bogus_pcidev.slot_name, "builtin");
	bogus_pcidev.resource[0].name = "OHCI Operational Registers";
	bogus_pcidev.resource[0].start = 0xfffba000;
	bogus_pcidev.resource[0].end = 0xfffba000 + 4096; /* REVISIT */
	bogus_pcidev.resource[0].flags = 0;
	bogus_pcidev.irq = IH2_BASE + 6;

	/*
	 * Initialise the generic OHCI driver.
	 */
	ret = hc_add_ohci(&bogus_pcidev, bogus_pcidev.irq,
			  (void *) bogus_pcidev.resource[0].start, 0,
			  &omap1510_ohci, "usb-ohci", "omap1510");

//	if (ret)
//		release_mem_region(_USB_OHCI_OP_BASE, _USB_EXTENT);

#if defined(CONFIG_ARCHOS_AV500) && !defined(CONFIG_OMAP_INNOVATOR)
        omap_gpio_dir(7, GPIO_DIR_OUT);
        omap_gpio_clr(7);
	
	/* disable HostTimeoutCtrl (OMAP5910-specific) */
	*ohci_host_tmout_ctrl = 1;
#ifdef CONFIG_PM
	pm_register(PM_SYS_DEV, PM_SYS_ARCH_SPECIFIC, av500_ohci_pm_callback);
#endif

#endif

	return ret;
}

static void __devexit
omap1510_ohci_release(void)
{
	/* REVISIT: Need to properly shut off clocks here. */
	volatile unsigned long *mod_conf_ctrl_0 = (unsigned long *) 0xfffe1080;

	hc_remove_ohci(omap1510_ohci);

	*mod_conf_ctrl_0 &= ~MOD_CONF_CTRL_0_USB_HOST_HHC_UHOST_EN;

	/*
	 * Release memory resources.
	 */
//	release_mem_region(_USB_OHCI_OP_BASE, _USB_EXTENT);
}

static int __init
omap1510_ohci_init(void)
{
	int ret = 0;

	ret = omap1510_ohci_configure();

	return ret;
}

static void __exit
omap1510_ohci_exit(void)
{
	omap1510_ohci_release();
}

module_init(omap1510_ohci_init);
module_exit(omap1510_ohci_exit);

MODULE_LICENSE("GPL");
