/*
 * EHCI HCD (Host Controller Driver) for USB.
 *
 * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
 * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
 * (C) Copyright 2002 Hewlett-Packard Company
 *
 * Bus Glue for ADI VOX160
 *
 * Written by Christopher Hoover <ch@hpl.hp.com>
 * Based on fragments of previous driver by Rusell King et al.
 *
 * Modified for LH7A404 from ohci-sa1111.c
 *  by Durgesh Pattamatta <pattamattad@sharpsec.com>
 * Modified for AMD Alchemy Au1xxx
 *  by Matt Porter <mporter@kernel.crashing.org>
 * Modified for ADI VOX160
 *  by Vivek Dharmadhikari <mporter@kernel.crashing.org>
 *
 * This file is licenced under the GPL.
 */

#include <linux/platform_device.h>

#include <asm/mach-adi_fusiv/vox160usb.h>
#include <linux/sbi_defs.h>

#define USBH_ENABLE_BE (1<<0)
#define USBH_ENABLE_C  (1<<1)
#define USBH_ENABLE_E  (1<<2)
#define USBH_ENABLE_CE (1<<3)
#define USBH_ENABLE_RD (1<<4)

#ifdef __LITTLE_ENDIAN
#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C)
#elif __BIG_ENDIAN
#define USBH_ENABLE_INIT (USBH_ENABLE_CE | USBH_ENABLE_E | USBH_ENABLE_C | USBH_ENABLE_BE)
#else
#error not byte order defined
#endif

extern int usb_disabled(void);

/*-------------------------------------------------------------------------*/
static void vox160_start_ehci_hc(struct platform_device *dev)
{
        /* address of Clock Gate Control Register  */
        int *clock_gate_control_reg = (int *)0xb90000cc;

        /* address of Frame Length Register  */
        int *framelen_reg = (int *)0xb9230090;
        int temp;

	printk(KERN_DEBUG __FILE__
		": starting VOX160 EHCI USB Controller\n");

        /* program DDR register */
        *((int *)0xb9180020) = 0x00100060;
        udelay(1000);

        /* Enable Clock to USB host controller Phy */
        *((int *)0xb90000dc) = 0x00000001;
        udelay(1000);

        /* Reset USB host controller Phy */
        *((int *)0xb90000dc) = 0x00000005;
        udelay(1000);

        /* Release USB host controller Phy reset */
        *((int *)0xb90000dc) = 0x00000001;
        udelay(1000);

        /* Release USB host controller Phy suspend  */
        *((int *)0xb90000dc) = 0x00000003;
        udelay(1000);

        *clock_gate_control_reg |= (1 << 13);

	printk(KERN_DEBUG __FILE__
	": Clock to USB host has been enabled \n");

        /*=================================
         * 29th bit-->usb2_desc_endian_conv
         * 30th bit-->usb2_data_endian_conv
         * 31st bit--> usb2_csr_endian_conv
         *=================================
	 */
        temp = *((int *)0xb9000088);
        temp |= 0x60000000;
        *((int *)0xb9000088) = temp;
        udelay(1000);

        /* turn HC on (run/stop) bit
         * Refer 2.3.1 section of ehci spec
         * for each bit explanation
         * 11th bit = Async schedule enable
         */
        *((int *)0xb9230010) = 0x00080b01;
        udelay(1000);


        /* program frame length register with value 0x00001d4d */
        *framelen_reg = 0x00001d4d;
}
static void vox160_stop_ehci_hc(struct platform_device *dev)
{
        /* address of Clock Gate Control Register  */
        /* int *clock_gate_control_reg = (int *)0xb90000cc; */

	printk(KERN_DEBUG __FILE__ ": stopping VOX160 EHCI USB Controller\n");

	/* Disable clock */
	/* disable USBH_CLK_EN bit in clock gate control register */
        /* *clock_gate_control_reg &= ~(1 << 13); */
}


/*-------------------------------------------------------------------------*/

/* configure so an HC device and id are always provided */
/* always called with process context; sleeping is OK */


/**
 * usb_hcd_vox160_probe - initialize Au1xxx-based HCDs
 * Context: !in_interrupt()
 *
 * Allocates basic resources for this USB host controller, and
 * then invokes the start() method for the HCD associated with it
 * through the hotplug entry's driver_data.
 *
 */
int usb_hcd_vox160_ehci_probe (const struct hc_driver *driver,
			  struct platform_device *dev)
{
	int retval;
	struct usb_hcd *hcd;

	printk(KERN_DEBUG __FILE__ ": Probing USB EHCI  \n");

        /* offset 1 contains IRQ info */
	if(dev->resource[1].flags != IORESOURCE_IRQ) {
		pr_debug ("resource[2] is not IORESOURCE_IRQ");
		return -ENOMEM;
	}

	hcd = usb_create_hcd(driver, &dev->dev, "vox160 ehci");
	if (!hcd)
		return -ENOMEM;

        /* offset 0 contains EHCI info */
	hcd->rsrc_start = dev->resource[0].start;
	hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;

	if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
		pr_debug("request_mem_region failed");
		retval = -EBUSY;
		goto err1;
	}

	hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
	if (!hcd->regs) {
		pr_debug("ioremap failed");
		retval = -ENOMEM;
		goto err2;
	}

        vox160_start_ehci_hc(dev);

	retval = usb_add_hcd(hcd, dev->resource[1].start, SA_INTERRUPT|SA_SHIRQ);

	if (retval == 0)
		return retval;

        vox160_stop_ehci_hc(dev);

	iounmap(hcd->regs);

 err2:
	release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
 err1:
	usb_put_hcd(hcd);
	return retval;
}

/* Added for LBR */

static int __devinit
ehci_vox160_start (struct usb_hcd *hcd)
{
  int		ret;

  if ((ret = ehci_init (hcd)) < 0)
    return ret;

  if ((ret = ehci_run (hcd)) < 0)
    {
      err ("can't start %s", hcd->self.bus_name);
      ehci_stop (hcd);
      return ret;
    }

  return 0;
}

/* TESTTTTTT */
/* called by khubd or root hub init threads */

static int ehci_hc_reset (struct usb_hcd *hcd)
{
	struct ehci_hcd		*ehci = hcd_to_ehci (hcd);
	u32			temp;

	spin_lock_init (&ehci->lock);

	ehci->caps = hcd->regs;
	ehci->regs = hcd->regs + HC_LENGTH (readl (&ehci->caps->hc_capbase));
	dbg_hcs_params (ehci, "reset");
	dbg_hcc_params (ehci, "reset");

	/* cache this readonly data; minimize chip reads */
	ehci->hcs_params = readl (&ehci->caps->hcs_params);


	ehci_port_power (ehci, 0);

	/* at least the Genesys GL880S needs fixup here */
	temp = HCS_N_CC(ehci->hcs_params) * HCS_N_PCC(ehci->hcs_params);
	temp &= 0x0f;
	if (temp && HCS_N_PORTS(ehci->hcs_params) > temp) {
		ehci_dbg (ehci, "bogus port configuration: "
			"cc=%d x pcc=%d < ports=%d\n",
			HCS_N_CC(ehci->hcs_params),
			HCS_N_PCC(ehci->hcs_params),
			HCS_N_PORTS(ehci->hcs_params));

	}

	/* force HC to halt state */
	return ehci_halt (ehci);
}

/* END TESTTTTTTT */
static const struct hc_driver ehci_driver = {
	.description =		hcd_name,
	.product_desc =		"EHCI Host Controller",
	.hcd_priv_size =	sizeof(struct ehci_hcd),

	/*
	 * generic hardware linkage
	 */
	.irq =			ehci_irq,
	.flags =		HCD_MEMORY | HCD_USB2,

	/*
	 * basic lifecycle operations
	 */
	.reset =		ehci_hc_reset,
	.start =		ehci_vox160_start,
#ifdef	CONFIG_PM
/* 	.suspend =		ehci_suspend, */
/* 	.resume =		ehci_resume, */
#endif
	.stop =			ehci_stop,

	/*
	 * managing i/o requests and associated device resources
	 */
	.urb_enqueue =		ehci_urb_enqueue,
	.urb_dequeue =		ehci_urb_dequeue,
	.endpoint_disable =	ehci_endpoint_disable,

	/*
	 * scheduling support
	 */
	.get_frame_number =	ehci_get_frame,

	/*
	 * root hub support
	 */
	.hub_status_data =	ehci_hub_status_data,
	.hub_control =		ehci_hub_control,
/* 	.hub_suspend =		ehci_hub_suspend, */
/* 	.hub_resume =		ehci_hub_resume, */
};

/*End added for LBR*/


/* may be called without controller electrically present */
/* may be called with controller, bus, and devices active */

/**
 * usb_hcd_au1xxx_remove - shutdown processing for Au1xxx-based HCDs
 * @dev: USB Host Controller being removed
 * Context: !in_interrupt()
 *
 * Reverses the effect of usb_hcd_vox160_probe(), first invoking
 * the HCD's stop() method.  It is always called from a thread
 * context, normally "rmmod", "apmd", or something similar.
 *
 */
void usb_hcd_vox160_echi_remove (struct usb_hcd *hcd, struct platform_device *dev)
{
	usb_remove_hcd(hcd);
        pr_debug("stopping VOX160 EHCI USB Controller\n");
	vox160_stop_ehci_hc(dev);
	iounmap(hcd->regs);
	release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
	usb_put_hcd(hcd);
}

static int ehci_hcd_vox160_drv_probe(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	int ret;

	if (usb_disabled())
		return -ENODEV;

	ret = usb_hcd_vox160_ehci_probe(&ehci_driver, pdev);
	return ret;
}

static int ehci_hcd_vox160_drv_remove(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct usb_hcd *hcd = dev_get_drvdata(dev);

	pr_debug ("In ehci_hcd_vox160_drv_remove");
        usb_hcd_vox160_echi_remove(hcd, pdev);
	return 0;
}
	/*TBD*/
/*static int ehci_hcd_vox160_drv_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct usb_hcd *hcd = dev_get_drvdata(dev);

	return 0;
}
static int ehci_hcd_vox160_drv_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct usb_hcd *hcd = dev_get_drvdata(dev);

	return 0;
}
*/

static struct device_driver ehci_hcd_vox160_driver = {
	.name		= "vox160-ehci-hcd",
	.bus		= &platform_bus_type,
	.probe		= ehci_hcd_vox160_drv_probe,
	.remove		= ehci_hcd_vox160_drv_remove,
	/*.suspend	= ehci_hcd_vox160_drv_suspend, */
	/*.resume	= ehci_hcd_vox160_drv_resume, */
};
