/**
  **************************************************************************
  * @file     usbh_msc_class.c
  * @version  v2.0.0
  * @date     2021-11-26
  * @brief    usb host msc class type
  **************************************************************************
  *                       Copyright notice & Disclaimer
  *
  * The software Board Support Package (BSP) that is made available to 
  * download from Artery official website is the copyrighted work of Artery. 
  * Artery authorizes customers to use, copy, and distribute the BSP 
  * software and its related documentation for the purpose of design and 
  * development in conjunction with Artery microcontrollers. Use of the 
  * software is governed by this copyright notice and the following disclaimer.
  *
  * THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
  * GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
  * TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
  * STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
  * INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
  *
  **************************************************************************
  */
#include "usbh_msc_class.h"
#include "usb_conf.h"
#include "usbh_core.h"
#include "usbh_ctrl.h"

/** @addtogroup AT32F415_middlewares_usbh_class
  * @{
  */
  
/** @defgroup USBH_msc_class
  * @brief usb host class msc demo
  * @{
  */  

/** @defgroup USBH_msc_class_private_functions
  * @{
  */

usb_sts_type uhost_init_handler(void *uhost);
usb_sts_type uhost_reset_handler(void *uhost);
usb_sts_type uhost_request_handler(void *uhost);
usb_sts_type uhost_process_handler(void *uhost);

usb_sts_type usbh_msc_get_max_lun(void *uhost, uint8_t *lun);
usb_sts_type usbh_msc_clear_feature(void *uhost, uint8_t ept_num);


usbh_msc_type usbh_msc;

usbh_class_handler_type uhost_class_handler = 
{
 uhost_init_handler,
 uhost_reset_handler,
 uhost_request_handler,
 uhost_process_handler,
 &usbh_msc
};



/**
  * @brief  usb host class init handler
  * @param  uhost: to the structure of usbh_core_type
  * @retval status: usb_sts_type status
  */
usb_sts_type uhost_init_handler(void *uhost)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usb_sts_type status = USB_OK;
  uint8_t if_x, eptidx = 0;
  usbh_msc_type *pmsc = &usbh_msc;
  puhost->class_handler->pdata = &usbh_msc;
  
  if_x = usbh_find_interface(puhost, USB_CLASS_CODE_MSC, MSC_SUBCLASS_SCSI_TRANS, MSC_PROTOCOL_BBB);
  if(if_x == 0xFF)
  {
    USBH_DEBUG("Unsupport Device!");
    return USB_NOT_SUPPORT;
  }
  pmsc->protocol = puhost->dev.cfg_desc.interface[if_x].interface.bInterfaceProtocol;
  
  for(eptidx = 0; eptidx < puhost->dev.cfg_desc.interface[if_x].interface.bNumEndpoints; eptidx ++)
  {
    if(puhost->dev.cfg_desc.interface[if_x].endpoint[eptidx].bEndpointAddress & 0x80)
    {
      pmsc->eptin = puhost->dev.cfg_desc.interface[if_x].endpoint[eptidx].bEndpointAddress;
      pmsc->in_maxpacket = puhost->dev.cfg_desc.interface[if_x].endpoint[eptidx].wMaxPacketSize;
      pmsc->in_poll = puhost->dev.cfg_desc.interface[if_x].endpoint[eptidx].bInterval;
      
      pmsc->chin = usbh_alloc_channel(puhost, pmsc->eptin);
      /* enable channel */
      usbh_hc_open(puhost, pmsc->chin, pmsc->eptin,
                    puhost->dev.address, EPT_BULK_TYPE,
                    pmsc->in_maxpacket, 
                    puhost->dev.speed);
    }
    else
    {
      pmsc->eptout = puhost->dev.cfg_desc.interface[if_x].endpoint[eptidx].bEndpointAddress;
      pmsc->out_maxpacket = puhost->dev.cfg_desc.interface[if_x].endpoint[eptidx].wMaxPacketSize;
      pmsc->out_poll = puhost->dev.cfg_desc.interface[if_x].endpoint[eptidx].bInterval;
      
      pmsc->chout = usbh_alloc_channel(puhost, pmsc->eptout);
      /* enable channel */
      usbh_hc_open(puhost, pmsc->chout,pmsc->eptout,
                    puhost->dev.address, EPT_BULK_TYPE,
                    pmsc->out_maxpacket, 
                    puhost->dev.speed);
    }
  }
  
  msc_bot_scsi_init(pmsc);
  usbh_set_toggle(puhost, pmsc->chout, 0);
  usbh_set_toggle(puhost, pmsc->chin, 0);
  return status;
}

/**
  * @brief  usb host class reset handler
  * @param  uhost: to the structure of usbh_core_type
  * @retval status: usb_sts_type status
  */
usb_sts_type uhost_reset_handler(void *uhost)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usbh_msc_type *pmsc = (usbh_msc_type *)puhost->class_handler->pdata;
  usb_sts_type status = USB_OK;
  uint8_t i_index = 0;

  if(puhost->class_handler->pdata)
  {
    return status;
  }
  
  for(i_index = 0; i_index < pmsc->max_lun ; i_index ++)
  {
    pmsc->l_unit_n[i_index].pre_state = USB_FAIL;
    pmsc->l_unit_n[i_index].change = 0;
    pmsc->l_unit_n[i_index].state = USBH_MSC_INIT;
    pmsc->l_unit_n[i_index].ready = MSC_NOT_READY;
  }
  
  if(pmsc->chin != 0 )
  {
    usbh_free_channel(puhost, pmsc->chin);
    usbh_ch_disable(puhost, pmsc->chin);
    pmsc->chin = 0;
  }
  
  if(pmsc->chout != 0 )
  {
    usbh_free_channel(puhost, pmsc->chout);
    usbh_ch_disable(puhost, pmsc->chout);
    pmsc->chout = 0;
  }

  return status;
}

/**
  * @brief  usb host hid class request handler
  * @param  uhost: to the structure of usbh_core_type
  * @retval status: usb_sts_type status
  */
usb_sts_type uhost_request_handler(void *uhost)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usbh_msc_type *pmsc = (usbh_msc_type *)puhost->class_handler->pdata;
  usb_sts_type status = USB_WAIT;
  uint8_t i_index = 0;
  
  switch(pmsc->ctrl_state)
  {
    case USBH_MSC_STATE_IDLE:
      pmsc->ctrl_state = USBH_MSC_STATE_GET_LUN;
      break;
    case USBH_MSC_STATE_GET_LUN:
      if((status = usbh_msc_get_max_lun(uhost, (uint8_t *)&pmsc->max_lun)) == USB_OK)
      {
        pmsc->max_lun = (pmsc->max_lun & 0xFF) > USBH_SUPPORT_MAX_LUN ? USBH_SUPPORT_MAX_LUN:((pmsc->max_lun & 0xFF) + 1);
        USBH_DEBUG("Support max lun %d", pmsc->max_lun);
        for(i_index = 0; i_index < pmsc->max_lun ; i_index ++)
        {
          pmsc->l_unit_n[i_index].pre_state = USB_FAIL;
          pmsc->l_unit_n[i_index].change = 0;
          pmsc->l_unit_n[i_index].state = USBH_MSC_INIT;
        }
      }
      break;
    case USBH_MSC_STATE_ERROR:
      if((usbh_msc_clear_feature(uhost, 0)) == USB_OK)
      {
        pmsc->ctrl_state = USBH_MSC_STATE_GET_LUN;
      }
      break;
    default:
      break;
  }
  
  return status;
}

/**
  * @brief  usb host class process handler
  * @param  uhost: to the structure of usbh_core_type
  * @retval status: usb_sts_type status
  */
usb_sts_type uhost_process_handler(void *uhost)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usbh_msc_type *pmsc = (usbh_msc_type *)puhost->class_handler->pdata;
  uint64_t msize = 0;
  usb_sts_type status;
  switch(pmsc->state)
  {
    case USBH_MSC_INIT:
      if(pmsc->cur_lun < pmsc->max_lun)
      {
        pmsc->l_unit_n[pmsc->cur_lun].ready = MSC_NOT_READY;
        
        switch(pmsc->l_unit_n[pmsc->cur_lun].state)
        {
          case USBH_MSC_INIT:
            pmsc->l_unit_n[pmsc->cur_lun].state = USBH_MSC_INQUIRY;
            break;
          case USBH_MSC_INQUIRY:
            status = usbh_msc_bot_scsi_get_inquiry(uhost, &pmsc->bot_trans, pmsc->cur_lun, &pmsc->l_unit_n[pmsc->cur_lun].inquiry);
            if(status == USB_OK)
            {
              pmsc->l_unit_n[pmsc->cur_lun].state = USBH_MSC_TEST_UNIT_READY;
            }
            else if(status == USB_FAIL)
            {
              pmsc->l_unit_n[pmsc->cur_lun].state = USBH_MSC_REQUEST_SENSE;
            }
            break;
          case USBH_MSC_TEST_UNIT_READY:
            status = usbh_msc_bot_scsi_test_unit_ready(uhost, &pmsc->bot_trans, pmsc->cur_lun);
            if(status == USB_OK)
            {
              pmsc->l_unit_n[pmsc->cur_lun].state = USBH_MSC_READ_CAPACITY10;
              pmsc->l_unit_n[pmsc->cur_lun].ready = MSC_OK;
            }
            else if(status == USB_FAIL)
            {
              pmsc->l_unit_n[pmsc->cur_lun].state = USBH_MSC_REQUEST_SENSE;
              pmsc->l_unit_n[pmsc->cur_lun].ready = MSC_NOT_READY;
            }
            break;
          
          case USBH_MSC_READ_CAPACITY10:
            status = usbh_msc_bot_scsi_capacity(uhost, &pmsc->bot_trans, pmsc->cur_lun, &pmsc->l_unit_n[pmsc->cur_lun].capacity);
            if(status == USB_OK)
            {
              msize = (uint64_t)pmsc->l_unit_n[pmsc->cur_lun].capacity.blk_nbr * (uint64_t)pmsc->l_unit_n[pmsc->cur_lun].capacity.blk_size;
              USBH_DEBUG("Device capacity: %llu Byte", msize);
              USBH_DEBUG("Block num: %d ", pmsc->l_unit_n[pmsc->cur_lun].capacity.blk_nbr);
              USBH_DEBUG("Block size: %d Byte", pmsc->l_unit_n[pmsc->cur_lun].capacity.blk_size);
              pmsc->l_unit_n[pmsc->cur_lun].state = USBH_MSC_IDLE;
              pmsc->state = USBH_MSC_IDLE;
              pmsc->cur_lun ++;
            }
            else if(status == USB_FAIL)
            {
              pmsc->l_unit_n[pmsc->cur_lun].state = USBH_MSC_REQUEST_SENSE;
            }
            break;
          case USBH_MSC_REQUEST_SENSE:
            status = usbh_msc_bot_scsi_request_sense(uhost, &pmsc->bot_trans, pmsc->cur_lun);
            if(status == USB_OK)
            {
              pmsc->l_unit_n[pmsc->cur_lun].state = USBH_MSC_IDLE;
              pmsc->cur_lun ++;
            }
            else if(status == USB_FAIL)
            {
              pmsc->cur_lun ++;
            }
            break;
 
          case USBH_MSC_BUSY:
            break;
          case USBH_MSC_ERROR:
            break;
          default:
            break;
        }
      }
      break;
    case USBH_MSC_IDLE:
    if(puhost->user_handler->user_application != NULL)
    {
      puhost->user_handler->user_application();
    }
    default:
      break;
  }
  return USB_OK;
}


/**
  * @brief  usb host msc get max lun
  * @param  uhost: to the structure of usbh_core_type
  * @param  lun: max lun buffer
  * @retval status: usb_sts_type status
  */
usb_sts_type usbh_msc_get_max_lun(void *uhost, uint8_t *lun)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usb_sts_type status = USB_WAIT;
  if(puhost->ctrl.state == CONTROL_IDLE )
  {
    puhost->ctrl.setup.bmRequestType = USB_DIR_D2H | USB_REQ_RECIPIENT_INTERFACE | USB_REQ_TYPE_CLASS;
    puhost->ctrl.setup.bRequest = MSC_REQ_GET_MAX_LUN;
    puhost->ctrl.setup.wValue = 0;
    puhost->ctrl.setup.wIndex = 0;
    puhost->ctrl.setup.wLength = 1;
    usbh_ctrl_request(puhost, lun, 1);
  }
  else
  {
    status = usbh_ctrl_result_check(puhost, CONTROL_IDLE, ENUM_IDLE);
    if(status == USB_OK || status == USB_NOT_SUPPORT)
    {
      status = USB_OK;
    }
  }
  return status;
}

/**
  * @brief  usb host msc clear feature
  * @param  uhost: to the structure of usbh_core_type
  * @param  ept_num: endpoint number
  * @retval status: usb_sts_type status
  */
usb_sts_type usbh_msc_clear_feature(void *uhost, uint8_t ept_num)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usb_sts_type status = USB_WAIT;
  if(puhost->ctrl.state == CONTROL_IDLE )
  {
    puhost->ctrl.setup.bmRequestType = USB_DIR_H2D | USB_REQ_RECIPIENT_ENDPOINT | USB_REQ_TYPE_STANDARD;
    puhost->ctrl.setup.bRequest = USB_STD_REQ_CLEAR_FEATURE;
    puhost->ctrl.setup.wValue = 0;
    puhost->ctrl.setup.wIndex = ept_num;
    puhost->ctrl.setup.wLength = 0;
    usbh_ctrl_request(puhost, 0, 0);
  }
  else
  {
    status = usbh_ctrl_result_check(puhost, CONTROL_IDLE, ENUM_IDLE);
    if(status == USB_OK || status == USB_NOT_SUPPORT)
    {
      status = USB_OK;
    }
  }
  return status;
}

/**
  * @brief  usb host msc clear feature
  * @param  lun: logical unit number
  * @retval msc_error_type status
  */
msc_error_type usbh_msc_is_ready(void *uhost, uint8_t lun)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usbh_msc_type *pmsc = (usbh_msc_type *)puhost->class_handler->pdata;
  return pmsc->l_unit_n[pmsc->cur_lun].ready;
}

/**
  * @brief  usb host msc read
  * @param  uhost: to the structure of usbh_core_type
  * @param  address: logical block address
  * @param  data_len: transfer data length
  * @param  buffer: transfer data buffer
  * @param  lun: logical unit number
  * @retval status: usb_sts_type status
  */
usb_sts_type usbh_msc_read(void *uhost, uint32_t address, uint32_t len, uint8_t *buffer, uint8_t lun)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usbh_msc_type *pmsc = (usbh_msc_type *)puhost->class_handler->pdata;
  usb_sts_type status;
  if(puhost->conn_sts == 0 || puhost->global_state != USBH_CLASS
    || pmsc->l_unit_n[lun].state != USBH_MSC_IDLE)
  {
    return USB_FAIL;
  }
  pmsc->bot_trans.msc_struct = &usbh_msc;
  pmsc->state = USBH_MSC_READ10;
  pmsc->l_unit_n[lun].state = USBH_MSC_READ10;
  pmsc->use_lun = lun;
  while(1)
  {
    status = usbh_msc_bot_scsi_read(uhost, &pmsc->bot_trans, address, buffer, len, lun);
    if(status == USB_OK)
    {
      pmsc->l_unit_n[lun].state = USBH_MSC_IDLE;
      break;
    }
    else if(status == USB_FAIL)
    {
      pmsc->l_unit_n[lun].state = USBH_MSC_IDLE;
      break;
    }
  }
  return status;
}

/**
  * @brief  usb host msc write
  * @param  uhost: to the structure of usbh_core_type
  * @param  address: logical block address
  * @param  data_len: transfer data length
  * @param  buffer: transfer data buffer
  * @param  lun: logical unit number
  * @retval status: usb_sts_type status
  */
usb_sts_type usbh_msc_write(void *uhost, uint32_t address, uint32_t len, uint8_t *buffer, uint8_t lun)
{
  usbh_core_type *puhost = (usbh_core_type *)uhost;
  usbh_msc_type *pmsc = (usbh_msc_type *)puhost->class_handler->pdata;
  usb_sts_type status;
  if(puhost->conn_sts == 0 || puhost->global_state != USBH_CLASS
    || pmsc->l_unit_n[lun].state != USBH_MSC_IDLE)
  {
    return USB_FAIL;
  }
  
  pmsc->bot_trans.msc_struct = &usbh_msc;
  pmsc->state = USBH_MSC_WRITE;
  pmsc->l_unit_n[lun].state = USBH_MSC_WRITE;
  pmsc->use_lun = lun;
  
  while(1)
  {
    status = usbh_msc_bot_scsi_write(uhost, &pmsc->bot_trans, address, buffer, len, lun);
    if(status == USB_OK)
    {
      pmsc->l_unit_n[lun].state = USBH_MSC_IDLE;
      break;
    }
    else if(status == USB_FAIL)
    {
      pmsc->l_unit_n[lun].state = USBH_MSC_IDLE;
      break;
    }
  }
  return status;
}

/**
  * @}
  */ 

/**
  * @}
  */

/**
  * @}
  */
