/**HEADER********************************************************************
* 
* Copyright (c) 2008 Freescale Semiconductor;
* All Rights Reserved
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
* IN NO EVENT SHALL FREESCALE OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
* THE POSSIBILITY OF SUCH DAMAGE.
*
**************************************************************************
*
* $FileName: usb_host_hub_sm.c$
* $Version : 3.5.16.0$
* $Date    : Dec-9-2009$
*
* Comments:
*
*   This file contains the implementation of hub state machine on host.
*
*END************************************************************************/
#include "usb_host_hub.h"
#include "usb_host_hub_prv.h"
#include "usb_host_hub_sm.h"

static HUB_DEVICE_STRUCT_PTR  hubs = NULL;

static void usb_host_hub_device_sm(_usb_pipe_handle, pointer, pointer, uint_32, USB_STATUS);
static void usb_host_hub_int_callback(_usb_pipe_handle, pointer, pointer, uint_32, USB_STATUS);
static USB_STATUS usb_hub_wait_for_interrupt(HUB_DEVICE_STRUCT_PTR);


/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_host_hub_device_event
* Returned Value : None
* Comments       :
*     called when a hub has been attached, detached, etc.
*END*--------------------------------------------------------------------*/
void usb_host_hub_device_event
   (
      /* [IN] pointer to device instance */
      _usb_device_instance_handle      dev_handle,

      /* [IN] pointer to interface descriptor */
      _usb_interface_descriptor_handle intf_handle,

      /* [IN] code number for event causing callback */
      uint_32           event_code
   )
{
    HUB_COMMAND hub_com;

    switch (event_code) {
        case USB_CONFIG_EVENT:
            /* Drop through into attach, same processing */
        case USB_ATTACH_EVENT: {
            HUB_DEVICE_STRUCT_PTR new_hub, hub_parser;

            if (NULL == (new_hub = USB_mem_alloc_zero(sizeof(HUB_DEVICE_STRUCT))))
                break;
            if (hubs == NULL)
                hubs = new_hub;
            else {
                for (hub_parser = hubs; hub_parser->CLASS_INTF.next != NULL; hub_parser = hub_parser->CLASS_INTF.next)
                    /* void */;
                hub_parser->CLASS_INTF.next = new_hub;
            }

            new_hub->DEV_HANDLE = dev_handle;
            new_hub->INTF_HANDLE = intf_handle;
            _usb_hostdev_select_interface(dev_handle,
                intf_handle,
                (pointer)&new_hub->CLASS_INTF
            );
        }
        break;
        case USB_INTF_EVENT: {
            HUB_DEVICE_STRUCT_PTR hub_parser;

            for (hub_parser = hubs; hub_parser->INTF_HANDLE != NULL ; hub_parser = hub_parser->CLASS_INTF.next)
                if (hub_parser->INTF_HANDLE == intf_handle) {
                    /* prepare buffer where to store data */
                    /* allocate 7 bytes of descriptor to get its full size and number of ports */
                    if (NULL == (hub_parser->control_buffer = USB_mem_alloc_uncached_zero(7)))
                        break; 
                    /* set we are in proceMss of getting hub descriptor */
                    hub_parser->STATE = HUB_GET_DESCRIPTOR_TINY_PROCESS;
                    hub_com.CLASS_PTR = (CLASS_CALL_STRUCT_PTR) &hub_parser->CLASS_INTF;
                    hub_com.CALLBACK_FN = (tr_callback) usb_host_hub_device_sm;
                    hub_com.CALLBACK_PARAM = (pointer) hub_parser;
                    
                    /* here, we should retrieve information from the hub */
                    usb_class_hub_get_descriptor(&hub_com, hub_parser->control_buffer, 7);
                    break;
                }
            }
            break;
        case USB_DETACH_EVENT: {
            HUB_DEVICE_STRUCT_PTR hub_previous, hub_parser;
            _mqx_int              i;

            /* find hub in the list */
            for (hub_previous = NULL, hub_parser = hubs; hub_parser->INTF_HANDLE != NULL ; hub_previous = hub_parser, hub_parser = hub_parser->CLASS_INTF.next)
                if (hub_parser->INTF_HANDLE == intf_handle) {
                    /* found hub in the list, remove it from the list */
                    if (hub_previous == NULL)
                        hubs = hub_parser->CLASS_INTF.next;
                    else
                        hub_previous->CLASS_INTF.next = hub_parser->CLASS_INTF.next;
                    /* detach all the devices connected to all the ports of found hub */
                    for (i = 0; i < hub_parser->HUB_PORT_NR; i++) {
                        if ((hub_parser->HUB_PORTS + i)->APP_STATUS & APP_STATUS_ATTACHED)
                            usb_dev_list_detach_device(
                                ((USB_HUB_CLASS_INTF_STRUCT_PTR) hub_parser->CLASS_INTF.class_intf_handle)->G.host_handle,
                                ((DEV_INSTANCE_PTR) ((USB_HUB_CLASS_INTF_STRUCT_PTR) hub_parser->CLASS_INTF.class_intf_handle)->G.dev_handle)->address, /* hub address */
                                i + 1 /* hub port */
                            );
                    }
                    if (hub_parser->control_buffer != NULL)
                        USB_mem_free(hub_parser->control_buffer);
                    if (hub_parser->interrupt_buffer != NULL)
                        USB_mem_free(hub_parser->interrupt_buffer);
                    if (hub_parser->HUB_PORTS != NULL)
                        USB_mem_free(hub_parser->HUB_PORTS);
                    USB_mem_free(hub_parser);
                    break;
                }
            }
            break;
        default:
            break;
   } /* EndSwitch */

} /* Enbbody */

/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_host_hub_device_sm
* Returned Value : None
* Comments       :
*     called when a hub changes state; sm = state machine
*END*--------------------------------------------------------------------*/
static void usb_host_hub_device_sm
   (
         /* [IN] structure with USB pipe information on the interface */
         _usb_pipe_handle  pipe,

         /* [IN] parameters */
         pointer           param,

         /* [IN] buffer of data from IN stage */
         pointer           buffer,

         /* [IN] length of data from IN stage */
         uint_32           len,

         /* [IN] status of transaction */
         USB_STATUS        status
   )
{ /* Body */
    HUB_COMMAND                    hub_com;
    register HUB_DEVICE_STRUCT_PTR hub_instance = (HUB_DEVICE_STRUCT_PTR) param;
    _mqx_int                       i;

    hub_com.CLASS_PTR = (CLASS_CALL_STRUCT_PTR) &hub_instance->CLASS_INTF;
    hub_com.CALLBACK_FN = (tr_callback) usb_host_hub_device_sm;
    hub_com.CALLBACK_PARAM = (pointer) hub_instance;

    switch (hub_instance->STATE) {
        case HUB_GET_DESCRIPTOR_TINY_PROCESS: 
        {
            /* here we get number of ports from USB data */
            hub_instance->HUB_PORT_NR = ((HUB_DESCRIPTOR_STRUCT_PTR) buffer)->BNRPORTS;
            USB_mem_free(buffer); /* USB_memfree(hub_instance->control_buffer) in fact */

            if (NULL == (hub_instance->control_buffer = USB_mem_alloc_uncached_zero(7 + hub_instance->HUB_PORT_NR / 8 + 1)))
                break;
            
            hub_instance->STATE = HUB_GET_DESCRIPTOR_PROCESS;
            usb_class_hub_get_descriptor(&hub_com, hub_instance->control_buffer, 7 + hub_instance->HUB_PORT_NR / 8 + 1);
            break;
        }

        case HUB_GET_DESCRIPTOR_PROCESS: 
            {
                /* here, we get information from the hub and fill info in hub_instance */
                HUB_DESCRIPTOR_STRUCT_PTR hub_desc = (HUB_DESCRIPTOR_STRUCT_PTR) buffer;

                hub_instance->HUB_PORTS = USB_mem_alloc_zero(hub_instance->HUB_PORT_NR * sizeof(HUB_PORT_STRUCT));
            
                for (i = 0; i < hub_instance->HUB_PORT_NR; i++) 
                {
                    /* get REMOVABLE bit from the desriptor for appropriate installed port */
                    (hub_instance->HUB_PORTS + i)->REMOVABLE = hub_desc->DEVICEREMOVABLE[(i + 1) / 8];
                    (hub_instance->HUB_PORTS + i)->REMOVABLE &= (0x01 << ((i + 1) % 8));
                    (hub_instance->HUB_PORTS + i)->APP_STATUS = 0x00;
                }

                USB_mem_free(buffer);  /* USB_mem_free(hub_instance->control_buffer) in fact */
            }

            /* pass fluently to HUB_SET_PORT_FEATURE_PROCESS */
            hub_instance->STATE = HUB_SET_PORT_FEATURE_PROCESS;
            hub_instance->port_iterator = 0;
        case HUB_SET_PORT_FEATURE_PROCESS: 
//              if (hub_instance->port_iterator)
                /* register, that the port is now powered */
//                hub_instance->HUB_PORTS[hub_instance->port_iterator - 1].POWERED = 1;
            if (hub_instance->port_iterator < hub_instance->HUB_PORT_NR) 
            {
                usb_class_hub_set_port_feature(&hub_com, ++hub_instance->port_iterator, PORT_POWER);
                break;
            } /* EndIf */
            /* else */

            /* pass fluently to HUB_CLEAR_PORT_FEATURE_PROCESS */
            hub_instance->STATE = HUB_CLEAR_PORT_FEATURE_PROCESS;
            hub_instance->port_iterator = 0;
        case HUB_CLEAR_PORT_FEATURE_PROCESS: 
            if (hub_instance->port_iterator < hub_instance->HUB_PORT_NR) 
            {
                usb_class_hub_clear_port_feature(&hub_com, ++hub_instance->port_iterator, C_PORT_CONNECTION);
                break;
            } /* EndIf */
            /* else */

            /* pass fluently to HUB_GET_PORT_STATUS_PROCESS */
            if (NULL == (hub_instance->control_buffer = USB_mem_alloc_uncached_zero(4)))
                break;
            hub_instance->STATE = HUB_GET_PORT_STATUS_PROCESS;
            hub_instance->port_iterator = 0;
        case HUB_GET_PORT_STATUS_PROCESS: 
            if (hub_instance->port_iterator)
                /* register the current status of the port, do the conversion LSB -> MSB */
                (hub_instance->HUB_PORTS + hub_instance->port_iterator - 1)->STATUS = SHORT_LE_TO_HOST(*(uint_16*)buffer);
            if (hub_instance->port_iterator < hub_instance->HUB_PORT_NR)
            {
                usb_class_hub_get_port_status(&hub_com, ++hub_instance->port_iterator, hub_instance->control_buffer, 4);
                break;
            } /* EndIf */
            /* else */
            
            if (NULL == (hub_instance->interrupt_buffer = USB_mem_alloc_uncached_zero(hub_instance->HUB_PORT_NR / 8 + 1)))
                break;

            
            /* test if device is attached since startup */
            for (i = 0; i < hub_instance->HUB_PORT_NR; i++)
                if ((hub_instance->HUB_PORTS + i)->STATUS & (1 << PORT_CONNECTION))
                {
                    USB_STATUS s;

                    /* if some device is attached since startup, then start session */
                    hub_instance->port_iterator = i;
                    hub_instance->STATE = HUB_NONE;
                    (hub_instance->HUB_PORTS + i)->APP_STATUS |= APP_STATUS_ATTACHED;
                    s = usb_class_hub_set_port_feature(&hub_com, hub_instance->port_iterator + 1, PORT_RESET);
                    break;
                }

            usb_hub_wait_for_interrupt(hub_instance);
            break;
            
        case HUB_RESET_DEVICE_PORT_PROCESS:
            hub_instance->STATE = HUB_NONE;
            
            usb_class_hub_set_port_feature(&hub_com, hub_instance->port_iterator + 1, PORT_RESET);

            usb_hub_wait_for_interrupt(hub_instance);
            break;

        case HUB_ADDRESS_DEVICE_PORT_PROCESS:
            /* compute speed */
            if ((hub_instance->HUB_PORTS + hub_instance->port_iterator)->STATUS & (1 << PORT_HIGH_SPEED))
                i = USB_SPEED_HIGH;
            else if ((hub_instance->HUB_PORTS + hub_instance->port_iterator)->STATUS & (1 << PORT_LOW_SPEED))
                i = USB_SPEED_LOW;
            else
                i = USB_SPEED_FULL;
            
            usb_dev_list_attach_device(
                ((USB_HUB_CLASS_INTF_STRUCT_PTR) hub_instance->CLASS_INTF.class_intf_handle)->G.host_handle,
                i, /* port speed */
                ((PIPE_INIT_PARAM_STRUCT_PTR) pipe)->DEVICE_ADDRESS, /* hub address */
                hub_instance->port_iterator + 1 /* hub port */
                );

            /* test if there is still device which was not reset */
            for (i = 0; i < hub_instance->HUB_PORT_NR; i++)
                if (((hub_instance->HUB_PORTS + i)->STATUS & (1 << PORT_CONNECTION)) && !((hub_instance->HUB_PORTS + i)->APP_STATUS & APP_STATUS_ATTACHED))
                {
                    USB_STATUS s;
                    /* if some device is attached since startup, then start session */
                    hub_instance->port_iterator = i;
                    hub_instance->STATE = HUB_NONE;
                    (hub_instance->HUB_PORTS + i)->APP_STATUS |= APP_STATUS_ATTACHED;
                    s = usb_class_hub_set_port_feature(&hub_com, hub_instance->port_iterator + 1, PORT_RESET);
                    break;
                }

            usb_hub_wait_for_interrupt(hub_instance);
            break;
        
        case HUB_DETACH_DEVICE_PORT_PROCESS:
            usb_dev_list_detach_device(
                ((USB_HUB_CLASS_INTF_STRUCT_PTR) hub_instance->CLASS_INTF.class_intf_handle)->G.host_handle,
                ((PIPE_INIT_PARAM_STRUCT_PTR) pipe)->DEVICE_ADDRESS, /* hub address */
                hub_instance->port_iterator + 1 /* hub port */
                );
            
            /* reset the app status */
            (hub_instance->HUB_PORTS + hub_instance->port_iterator)->APP_STATUS = 0x00;

            /* test if there is still device which was not reset */
            for (i = 0; i < hub_instance->HUB_PORT_NR; i++)
                if (((hub_instance->HUB_PORTS + i)->STATUS & (1 << PORT_CONNECTION)) && !((hub_instance->HUB_PORTS + i)->APP_STATUS & APP_STATUS_ATTACHED))
                {
                    USB_STATUS s;

                    /* if some device is attached since startup, then start session */
                    hub_instance->port_iterator = i;
                    hub_instance->STATE = HUB_NONE;
                    (hub_instance->HUB_PORTS + i)->APP_STATUS |= APP_STATUS_ATTACHED;
                    s = usb_class_hub_set_port_feature(&hub_com, hub_instance->port_iterator + 1, PORT_RESET);
                    break;
                }

            usb_hub_wait_for_interrupt(hub_instance);
            break;
            
        case HUB_GET_PORT_STATUS_ASYNC:
        {
            uint_32 stat = LONG_LE_TO_HOST(*(uint_32*)buffer);
            
            /* register the current status of the port */
            (hub_instance->HUB_PORTS + hub_instance->port_iterator)->STATUS = stat;
    
            /* let's see what happened */
            if ((1 << C_PORT_CONNECTION) & stat)
            {
                /* get if a device on port was attached or detached */
                if ((hub_instance->HUB_PORTS + hub_instance->port_iterator)->APP_STATUS & APP_STATUS_ATTACHED)
                {
                    hub_instance->STATE = HUB_DETACH_DEVICE_PORT_PROCESS;
                    usb_class_hub_clear_port_feature(&hub_com, hub_instance->port_iterator + 1, C_PORT_CONNECTION);
                }
                else 
                {
                    hub_instance->STATE = HUB_RESET_DEVICE_PORT_PROCESS;
                    usb_class_hub_clear_port_feature(&hub_com, hub_instance->port_iterator + 1, C_PORT_CONNECTION);
                }
                break;
            }
            else if ((1 << C_PORT_RESET) & stat)
            {
                hub_instance->STATE = HUB_ADDRESS_DEVICE_PORT_PROCESS;
                (hub_instance->HUB_PORTS + hub_instance->port_iterator)->APP_STATUS |= APP_STATUS_ATTACHED;
                usb_class_hub_clear_port_feature(&hub_com, hub_instance->port_iterator + 1, C_PORT_RESET);
                break;
            }
            else if ((1 << C_PORT_ENABLE) & stat)
            {
                /* unexpected event (error), just ignore the port */
                hub_instance->STATE = HUB_DETACH_DEVICE_PORT_PROCESS;
                usb_class_hub_clear_port_feature(&hub_com, hub_instance->port_iterator + 1, C_PORT_ENABLE);
                break;
            }
            else if ((1 << C_PORT_OVER_CURRENT) & stat) 
            {
                /* unexpected event (error), just ignore the port */
                hub_instance->STATE = HUB_NONE;
                usb_class_hub_clear_port_feature(&hub_com, hub_instance->port_iterator + 1, C_PORT_OVER_CURRENT);
                usb_hub_wait_for_interrupt(hub_instance);
                break;
            }
            else if ((1 << C_PORT_POWER) & stat) 
            {
                /* unexpected event (error), just ignore the port */
                hub_instance->STATE = HUB_NONE;
                usb_class_hub_clear_port_feature(&hub_com, hub_instance->port_iterator + 1, C_PORT_POWER);
                usb_hub_wait_for_interrupt(hub_instance);
                break;
            }
            /* FIXME: handle more events */

            break;
            
        }
        
    } /* EndSwitch */
    return;
} /* EndBody */

/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_host_hub_int_callback
* Returned Value : None
* Comments       :
*     Called on interrupt endpoint data reception
*END*--------------------------------------------------------------------*/

static void usb_host_hub_int_callback
   (
      /* [IN] pointer to pipe */
      _usb_pipe_handle  pipe,

      /* [IN] user-defined parameter */
      pointer           param,

      /* [IN] buffer address */
      pointer           buffer,

      /* [IN] length of data transferred */
      uint_32           len,

      /* [IN] status, hopefully USB_OK or USB_DONE */
      uint_32           status
   )
{ /* Body */
    HUB_DEVICE_STRUCT_PTR          hub_instance = (HUB_DEVICE_STRUCT_PTR) param;
    USB_HUB_CLASS_INTF_STRUCT_PTR  if_ptr;
    _mqx_int                       i, j, port;
    uint_8 _PTR_                   port_pattern= (uint_8 _PTR_) buffer;
    HUB_COMMAND                    hub_com;
   
    USB_lock();

    /* Validity checking, always needed when passing data to lower API */
    if (usb_host_class_intf_validate(&hub_instance->CLASS_INTF)) {
        if_ptr = (USB_HUB_CLASS_INTF_STRUCT_PTR) hub_instance->CLASS_INTF.class_intf_handle;
        if(USB_OK != usb_hostdev_validate(if_ptr->G.dev_handle)) {
            USB_unlock();
            return;
        }
       
        if_ptr->INT_PIPE = 0; /* mark we are not using interrupt pipe */

        USB_unlock();
        /* find which port changed its state */
        for (i = 0; i < len; i++) 
        {
            if (!port_pattern[i])
                continue;
            port = i * 8;
            for (j = 0; j < 8; j++, port++) 
            {
                if (port_pattern[i] & 0x01)
                    break;
                port_pattern[i] >>= 1;
            }
            break;
        }

        /* The port number, which changed status, is written in variable "port"
        ** Note, that if there are more ports, which changed its status, these will
        ** be invoked later (in next interrupt)
        */
 
        hub_com.CLASS_PTR = (CLASS_CALL_STRUCT_PTR) &hub_instance->CLASS_INTF;
        hub_com.CALLBACK_FN = (tr_callback) usb_host_hub_device_sm;
        hub_com.CALLBACK_PARAM = (pointer) hub_instance;
   
        hub_instance->STATE = HUB_GET_PORT_STATUS_ASYNC;
        hub_instance->port_iterator = port - 1;
        usb_class_hub_get_port_status(&hub_com, port, hub_instance->control_buffer, 4);
   
    }
    else
        USB_unlock();
} /*EndBody */


/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_hub_wait_for_interrupt
* Returned Value : None
* Comments       :
*     Starts interrupt endpoint to poll for interrupt on specified hub
*END*--------------------------------------------------------------------*/

static USB_STATUS usb_hub_wait_for_interrupt
    (
        /* hub device instance */
        HUB_DEVICE_STRUCT_PTR      hub_instance
    )
{ /* Body */
    USB_HUB_CLASS_INTF_STRUCT_PTR  if_ptr;
    TR_INIT_PARAM_STRUCT           tr;
    USB_STATUS                     status = USBERR_NO_INTERFACE;

    USB_lock();
    /* Validity checking, always needed when passing data to lower API */
    if (usb_host_class_intf_validate(&hub_instance->CLASS_INTF)) {
        if_ptr = (USB_HUB_CLASS_INTF_STRUCT_PTR) hub_instance->CLASS_INTF.class_intf_handle;
        status = usb_hostdev_validate(if_ptr->G.dev_handle);
    }

    if (!status && !if_ptr->INT_PIPE) {
        if_ptr->INT_PIPE = 1; /* mark we are using interrupt pipe */
        usb_hostdev_tr_init(&tr, (tr_callback) usb_host_hub_int_callback, (pointer) hub_instance);
        tr.RX_BUFFER = hub_instance->interrupt_buffer;
        tr.RX_LENGTH = hub_instance->HUB_PORT_NR / 8 + 1;
        status = _usb_host_recv_data(
            if_ptr->G.host_handle,
            if_ptr->P_INT_IN,
            &tr
            );
   }

   USB_unlock();

   return status;
}
