/*
 * HCD (OHCI) Virtual Root Hub for USB.
 *
 * (C) Copyright 1999 Roman Weissgaerber (weissg@vienna.at)
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * 
 * The Root Hub is build into the HC (UHCI or OHCI) hardware. 
 * This piece of code lets it look like it resides on the usb
 * like the other hubs.
 * (for anyone who wants to do a control operation on the root hub)
 * 
 * v4.0 1999/08/18 
 * v2.1 1999/05/09 
 * v2.0 1999/05/04
 * v1.0 1999/04/27
 * ohci-root-hub.c
 *  
 */
 

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/malloc.h>
#include <linux/string.h>

#include "usb.h"
#include "ohci-hcd.h" 

#ifdef VROOTHUB 

#include "ohci-root-hub.h"

static __u8 root_hub_dev_des[] =
{
        0x12,       /*  __u8  bLength; */
		0x01,       /*  __u8  bDescriptorType; Device */
		0x00,	    /*  __u16 bcdUSB; v1.0 */
        0x01,
		0x09,	    /*  __u8  bDeviceClass; HUB_CLASSCODE */
		0x00,	    /*  __u8  bDeviceSubClass; */
		0x00,       /*  __u8  bDeviceProtocol; */
		0x08,       /*  __u8  bMaxPacketSize0; 8 Bytes */
		0x00,       /*  __u16 idVendor; */
        0x00,
		0x00,       /*  __u16 idProduct; */
        0x00,
		0x00,       /*  __u16 bcdDevice; */
        0x00,
		0x00,       /*  __u8  iManufacturer; */
		0x00,       /*  __u8  iProduct; */
		0x00,       /*  __u8  iSerialNumber; */
        0x01        /*  __u8  bNumConfigurations; */
};


/* Configuration descriptor */
static __u8 root_hub_config_des[] =
{
		0x09,       /*  __u8  bLength; */
		0x02,       /*  __u8  bDescriptorType; Configuration */
		0x19,       /*  __u16 wTotalLength; */
        0x00,
		0x01,       /*  __u8  bNumInterfaces; */
		0x01,       /*  __u8  bConfigurationValue; */
		0x00,       /*  __u8  iConfiguration; */
		0x40,       /*  __u8  bmAttributes; 
                 Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, 4..0: resvd */
		0x00,       /*  __u8  MaxPower; */
      
     /* interface */	  
        0x09,       /*  __u8  if_bLength; */
        0x04,       /*  __u8  if_bDescriptorType; Interface */
        0x00,       /*  __u8  if_bInterfaceNumber; */
        0x00,       /*  __u8  if_bAlternateSetting; */
        0x01,       /*  __u8  if_bNumEndpoints; */
        0x09,       /*  __u8  if_bInterfaceClass; HUB_CLASSCODE */
        0x00,       /*  __u8  if_bInterfaceSubClass; */
        0x00,       /*  __u8  if_bInterfaceProtocol; */
        0x00,       /*  __u8  if_iInterface; */
     
     /* endpoint */
        0x07,       /*  __u8  ep_bLength; */
        0x05,       /*  __u8  ep_bDescriptorType; Endpoint */
        0x81,       /*  __u8  ep_bEndpointAddress; IN Endpoint 1 */
        0x03,       /*  __u8  ep_bmAttributes; Interrupt */
        0x08,       /*  __u16 ep_wMaxPacketSize; 8 Bytes */
        0x00,
        0xff        /*  __u8  ep_bInterval; 255 ms */
};

/* 
For OHCI we need just the  2nd Byte, so we 
don't need this constant byte-array 

static __u8 root_hub_hub_des[] =
{ 
        0x00,       *  __u8  bLength; *
		0x29,       *  __u8  bDescriptorType; Hub-descriptor *
        0x02,       *  __u8  bNbrPorts; *
        0x00,       * __u16  wHubCharacteristics; *
        0x00,
        0x01,       *  __u8  bPwrOn2pwrGood; 2ms * 
        0x00,       *  __u8  bHubContrCurrent; 0 mA *
        0x00,       *  __u8  DeviceRemovable; *** 8 Ports max *** *
        0xff        *  __u8  PortPwrCtrlMask; *** 8 ports max *** *
};
*/

#define OK(x) 			len = (x); req_reply = 0; break
#define WR_RH_STAT(x) 		writel((x), &ohci->regs->roothub.status)
#define WR_RH_PORTSTAT(x) 	writel((x), &ohci->regs->roothub.portstatus[wIndex-1])
#define RD_RH_STAT			readl(&ohci->regs->roothub.status)
#define RD_RH_PORTSTAT		readl(&ohci->regs->roothub.portstatus[wIndex-1])

int root_hub_control_msg(struct usb_device *usb_dev, unsigned int pipe, devrequest * cmd, void *data, int leni)
{ 
	struct ohci * ohci = usb_dev->bus->hcpriv;
	__u8 data_buf[16];
	int req_reply=4;
 
	int len =leni;

	__u16 bmRType_bReq  = cmd->requesttype | (cmd->request << 8);
	__u16 wValue        = cpu_to_le16(cmd->value);
	__u16 wIndex        = cpu_to_le16(cmd->index);
	__u16 wLength       = cpu_to_le16(cmd->length);

	OHCI_DEBUG(printk("USB root hub: adr: %2x cmd(%1x): ", ohci->rh.devnum, 8);)
	OHCI_DEBUG({ int i; for(i=0;i<8;i++) printk(" %02x", ((unsigned char *)cmd)[i]);})
	OHCI_DEBUG(printk(" ; \n");)
 
	switch (bmRType_bReq) {
	/* Request Destination:
	   without flags: Device, 
	   RH_INTERFACE: interface, 
	   RH_ENDPOINT: endpoint,
	   RH_CLASS means HUB here, 
	   RH_OTHER | RH_CLASS  almost ever means HUB_PORT here 
	*/
  
		case RH_GET_STATUS: 				 		*(__u16 *)data = cpu_to_le16(1); OK(2);
		case RH_GET_STATUS | RH_INTERFACE: 	 		*(__u16 *)data = cpu_to_le16(0); OK(2);
		case RH_GET_STATUS | RH_ENDPOINT:	 		*(__u16 *)data = cpu_to_le16(0); OK(2);   
		case RH_GET_STATUS | RH_CLASS: 				*(__u32 *)data = cpu_to_le32(RD_RH_STAT & 0x7fff7fff); OK(4);
		case RH_GET_STATUS | RH_OTHER | RH_CLASS: 	*(__u32 *)data = cpu_to_le32(RD_RH_PORTSTAT); OK(4);

		case RH_CLEAR_FEATURE | RH_ENDPOINT:  
			switch (wValue) {
				case (RH_ENDPOINT_STALL): OK(0);
			}
			break;

		case RH_CLEAR_FEATURE | RH_CLASS:
			switch (wValue) {
				case (RH_C_HUB_OVER_CURRENT): WR_RH_STAT(RH_PS_OCIC); OK(0);
			}
			break;
		
		case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS:
			switch (wValue) {
				case (RH_PORT_ENABLE): 			WR_RH_PORTSTAT(RH_PS_CCS ); OK(0);
				case (RH_PORT_SUSPEND):			WR_RH_PORTSTAT(RH_PS_POCI); OK(0);
				case (RH_PORT_POWER):			WR_RH_PORTSTAT(RH_PS_LSDA); OK(0);
				case (RH_C_PORT_CONNECTION):	WR_RH_PORTSTAT(RH_PS_CSC ); OK(0);
				case (RH_C_PORT_ENABLE):		WR_RH_PORTSTAT(RH_PS_PESC); OK(0);
				case (RH_C_PORT_SUSPEND):		WR_RH_PORTSTAT(RH_PS_PSSC); OK(0);
				case (RH_C_PORT_OVER_CURRENT):	WR_RH_PORTSTAT(RH_PS_OCIC); OK(0);
				case (RH_C_PORT_RESET):			WR_RH_PORTSTAT(RH_PS_PRSC); OK(0); 
			}
			break;
 
		case RH_SET_FEATURE | RH_OTHER | RH_CLASS:
			switch (wValue) {
				case (RH_PORT_SUSPEND):			WR_RH_PORTSTAT(RH_PS_PSS ); OK(0); 
				case (RH_PORT_RESET): if((RD_RH_PORTSTAT &1) != 0)  WR_RH_PORTSTAT(RH_PS_PRS ); /* BUG IN HUP CODE *********/ OK(0);
				case (RH_PORT_POWER):			WR_RH_PORTSTAT(RH_PS_PPS ); OK(0); 
				case (RH_PORT_ENABLE):			WR_RH_PORTSTAT(RH_PS_PES ); OK(0);
			}
			break;

		case RH_SET_ADDRESS: ohci->rh.devnum = wValue; OK(0);

		case RH_GET_DESCRIPTOR:
			switch ((wValue & 0xff00) >> 8) {
				case (0x01): /* device descriptor */
					len = min(leni, min(sizeof(root_hub_dev_des), wLength));
					memcpy(data, root_hub_dev_des, len); OK(len);
				case (0x02): /* configuration descriptor */
					len = min(leni, min(sizeof(root_hub_config_des), wLength));
					memcpy(data, root_hub_config_des, len); OK(len);
				case (0x03): /* string descriptors */
				default:
			}
			break;
		
		case RH_GET_DESCRIPTOR | RH_CLASS:
			*(__u8 *)(data_buf+1) = 0x29;
			*(__u32 *)(data_buf+2) = cpu_to_le32(readl(&ohci->regs->roothub.a));  
	 		*(__u8 *)data_buf = (*(__u8 *)(data_buf+2) / 8) * 2 + 9; /* length of descriptor */
				 
			len = min(leni, min(*(__u8 *)data_buf, wLength));
			*(__u8 *)(data_buf+6) = 0; /* Root Hub needs no current from bus */
			if(*(__u8 *)(data_buf+2) < 8) { /* less than 8 Ports */
				*(__u8 *) (data_buf+7) = readl(&ohci->regs->roothub.b) & 0xff; 
				*(__u8 *) (data_buf+8) = (readl(&ohci->regs->roothub.b) & 0xff0000) >> 16; 
			}
			else {
				*(__u32 *) (data_buf+7) = cpu_to_le32(readl(&ohci->regs->roothub.b)); 
			}
			memcpy(data, data_buf, len);
			OK(len); 
 
		case RH_GET_CONFIGURATION: 	*(__u8 *)data = 0x01; OK(1);

		case RH_SET_CONFIGURATION: 	WR_RH_STAT( 0x10000); OK(0);
	}
	
	OHCI_DEBUG(printk("USB HC roothubstat1: %x \n", readl( &(ohci->regs->roothub.portstatus[0]) ));)
	OHCI_DEBUG(printk("USB HC roothubstat2: %x \n", readl( &(ohci->regs->roothub.portstatus[1]) ));)
  		
	
	return req_reply;
}

/* prepare Interrupt pipe transaction data; HUB INTERRUPT ENDPOINT */ 
static int root_hub_send_irq(struct ohci * ohci, void * rh_data, int rh_len ) {

  int num_ports;
  int i;
  int ret;
  int len;

  __u8 * data = rh_data;

  num_ports = readl(&ohci->regs->roothub.a) & 0xff; 
  *(__u8 *)data = (readl(&ohci->regs->roothub.status) & 0x00030000)>0?1:0;
  ret = *(__u8 *)data;

  for(i=0; i < num_ports; i++) {
    *(__u8 *)(data+i/8) |= ((readl(&ohci->regs->roothub.portstatus[i]) & 0x001f0000)>0?1:0) << ((i+1) % 8);
    ret += *(__u8 *)(data+i/8);
  }
  len = i/8 + 1;
  
  if (ret > 0) return len;

  return  RH_NACK;
}

 
static int ohci_init_rh_int_timer(struct usb_device * usb_dev, int interval);

/* Virtual Root Hub INTs are polled by this timer every "intervall" ms */
static void rh_int_timer_do(unsigned long ptr) {
	int len; 
	int interval;
	int ret;
	
	struct usb_device * usb_dev = (struct usb_device *) ptr;
    struct ohci * ohci = usb_dev->bus->hcpriv;
	struct ohci_device * dev = usb_to_ohci(usb_dev);


	if(ohci->rh.send) { 
		len = root_hub_send_irq(ohci, dev->data, 1 );

		if(len > 0) { 
			
			ret = ohci->rh.handler(0, dev->data, len, ohci->rh.dev_id);
			if(ret <= 0)ohci->rh.send = 0; /* 0 .. do not requeue  */
		}
	}	
	interval = ohci->rh.interval;
	ohci_init_rh_int_timer(usb_dev, interval);
}

/* Root Hub INTs are polled by this timer */
static int ohci_init_rh_int_timer(struct usb_device * usb_dev, int interval) {
	struct ohci * ohci = usb_dev->bus->hcpriv;

	ohci->rh.interval = interval;
	init_timer(& ohci->rh.rh_int_timer);
	ohci->rh.rh_int_timer.function = rh_int_timer_do;
	ohci->rh.rh_int_timer.data = (unsigned long) usb_dev;
	ohci->rh.rh_int_timer.expires = jiffies + (HZ * (interval<30?30: interval)) /1000;
	add_timer(&ohci->rh.rh_int_timer);
	
	return 0;
}

static int ohci_del_rh_int_timer(struct ohci * ohci) {
	del_timer(&ohci->rh.rh_int_timer);
	return 0;
}
 
int root_hub_request_irq(struct usb_device *usb_dev, unsigned int pipe, usb_device_irq handler, int period, void *dev_id, void **handle)
{
	struct ohci * ohci = usb_dev->bus->hcpriv;
	
	OHCI_DEBUG( printk("USB HC-RH IRQ>>>: RH every %d ms\n", period);) 
	ohci->rh.handler = handler;
	ohci->rh.dev_id =  dev_id;
	ohci->rh.send = 1;
	ohci->rh.interval = period;
	ohci_init_rh_int_timer(usb_dev, period);
	*handle = ohci->rh.int_addr = usb_to_ohci(usb_dev);
	return 0;
}

int root_hub_release_irq(struct usb_device *usb_dev, void * ed) 
{
	// struct usb_device *usb_dev = ((struct ohci_device *) ((unsigned int)ed & 0xfffff000))->usb;
	struct ohci * ohci = usb_dev->bus->hcpriv;

	OHCI_DEBUG( printk("USB HC-RH RM_IRQ>>>:\n");) 
	ohci->rh.send = 0;
	ohci_del_rh_int_timer(ohci);
	return 0;
}


 
#endif
