/****************************************************************************
 * drivers/usbhost/usbhost_ft232r.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/arch.h>
#include <nuttx/wqueue.h>
#include <nuttx/clock.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/semaphore.h>
#include <nuttx/serial/serial.h>

#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbhost.h>
#include <nuttx/usb/usbhost_devaddr.h>

#ifdef CONFIG_USBHOST_FT232R

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Configuration ************************************************************/

#ifndef CONFIG_USBHOST
#  warning USB host support not enabled (CONFIG_USBHOST)
#endif

#ifdef CONFIG_USBHOST_BULK_DISABLE
#  warning USB bulk endpoint support is disabled (CONFIG_USBHOST_BULK_DISABLE)
#endif

#ifdef CONFIG_USBHOST_INT_DISABLE
#  warning USB interrupt endpoint support is disabled (CONFIG_USBHOST_INT_DISABLE)
#endif

#if !defined(CONFIG_SCHED_WORKQUEUE)
#  warning Worker thread support is required (CONFIG_SCHED_WORKQUEUE)
#else
#  ifndef CONFIG_SCHED_HPWORK
#    warning High priority work thread support is required (CONFIG_SCHED_HPWORK)
#  endif
#  ifndef CONFIG_SCHED_LPWORK
#    warning Low priority work thread support is required (CONFIG_SCHED_LPWORK)
#  endif
#  if CONFIG_SCHED_LPNTHREADS < 2
#    warning Multiple low priority work threads recommended for performance (CONFIG_SCHED_LPNTHREADS > 1)
#  endif
#endif

#ifndef CONFIG_SERIAL_REMOVABLE
#  warning Removable serial device support is required (CONFIG_SERIAL_REMOVABLE)
#endif

#ifdef CONFIG_USBHOST_FT232R_RXDELAY
#  define USBHOST_FT232R_RXDELAY MSEC2TICK(CONFIG_USBHOST_FT232R_RXDELAY)
#else
#  define USBHOST_FT232R_RXDELAY MSEC2TICK(200)
#endif

#ifdef CONFIG_USBHOST_FT232R_TXDELAY
#  define USBHOST_FT232R_TXDELAY MSEC2TICK(CONFIG_USBHOST_FT232R_TXDELAY)
#else
#  define USBHOST_FT232R_TXDELAY MSEC2TICK(200)
#endif

/* If the create() method is called by the USB host device driver from an
 * interrupt handler, then it will be unable to call kmm_malloc() in order to
 * allocate a new class instance.  If the create() method is called from the
 * interrupt level, then class instances must be pre-allocated.
 */

#ifndef CONFIG_USBHOST_FT232R_NPREALLOC
#  define CONFIG_USBHOST_FT232R_NPREALLOC 0
#endif

#if CONFIG_USBHOST_FT232R_NPREALLOC > 32
#  error Currently limited to 32 devices /dev/ttyUSB[n]
#endif

#ifndef CONFIG_USBHOST_FT232R_RXBUFSIZE
#  define CONFIG_USBHOST_FT232R_RXBUFSIZE 128
#endif

#ifndef CONFIG_USBHOST_FT232R_TXBUFSIZE
#  define CONFIG_USBHOST_FT232R_TXBUFSIZE 128
#endif

/* Initial line coding */

#ifndef CONFIG_USBHOST_FT232R_BAUD
#  define CONFIG_USBHOST_FT232R_BAUD 115200
#endif

#ifndef CONFIG_USBHOST_FT232R_PARITY
#  define CONFIG_USBHOST_FT232R_PARITY 0
#endif

#ifndef CONFIG_USBHOST_FT232R_BITS
#  define CONFIG_USBHOST_FT232R_BITS 8
#endif

#ifndef CONFIG_USBHOST_FT232R_2STOP
#  define CONFIG_USBHOST_FT232R_2STOP 0
#endif

#ifndef CONFIG_USBHOST_FT232R_LATENCY
#  define CONFIG_USBHOST_FT232R_LATENCY 16
#endif

/* Driver support ***********************************************************/

/* This format is used to construct the /dev/sd[n] device driver path.  It
 * defined here so that it will be used consistently in all places.
 */

#define DEV_FORMAT             "/dev/ttyUSB%d"
#define DEV_NAMELEN            16

#define MAX_NOTIFICATION       32

/* Used in usbhost_connect() */

#define USBHOST_DATAIF_FOUND   0x01      /* Data interface found */
#define USBHOST_BULKIN_FOUND   0x02      /* Bulk IN interface found */
#define USBHOST_BULKOUT_FOUND  0x04      /* Bulk OUT interface found */

#define USBHOST_ALLFOUND       0x07      /* All configuration things */

#define USBHOST_MAX_CREFS      INT16_MAX /* Max cref count before signed overflow */

/* Configuration options */

/* Special case baud rates */

#define USBHOST_FT232R_BAUD_2MHZ 2000000
#define USBHOST_FT232R_BAUD_3MHZ 3000000
#define USBHOST_FT232R_MAX_BAUD USBHOST_FT232R_BAUD_3MHZ

/* FT232R Control Transfer Request Types */

#define USBHOST_FT232R_CTRLREQ_RESET        0x0
#define USBHOST_FT232R_CTRLREQ_MODEMCTRL    0x1
#define USBHOST_FT232R_CTRLREQ_SETFLOWCTRL  0x2
#define USBHOST_FT232R_CTRLREQ_SETBAUD      0x3
#define USBHOST_FT232R_CTRLREQ_SETDATA      0x4
#define USBHOST_FT232R_CTRLREQ_GETMODEMSTAT 0x5
#define USBHOST_FT232R_CTRLREQ_SETLATTIMER  0x9
#define USBHOST_FT232R_CTRLREQ_GETLATTIMER  0xa

#define USBHOST_FT232R_MODEMCTRL_VAL_DTR 0x1
#define USBHOST_FT232R_MODEMCTRL_VAL_RTS 0x2
#define USBHOST_FT232R_MODEMCTRL_VAL_DTR_EN 0x100
#define USBHOST_FT232R_MODEMCTRL_VAL_RTS_EN 0x200

#define USBHOST_FT232R_SETDATA_NBIT_MASK    0xff
#define USBHOST_FT232R_SETDATA_PARITY_MASK  0x7
#define USBHOST_FT232R_SETDATA_PARITY_SHIFT 8
#define USBHOST_FT232R_SETDATA_2STOP        0x1000
#define USBHOST_FT232R_SETDATA_BREAK        0x4000

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This structure contains the internal, private state of the USB host ftdi.
 */

struct usbhost_ft232r_s
{
  /* This is the externally visible portion of the state.  The usbclass must
   * the first element of the structure.  It is then cast compatible with
   * struct usbhost_ft232r_s.
   */

  struct usbhost_class_s usbclass;

  /* This is the standard of the lower-half serial interface.  It is not
   * the first element of the structure, but includes a pointer back to the
   * the beginning of this structure.
   */

  struct uart_dev_s uartdev;

  /* The remainder of the fields are provide to the FTDI class */

  volatile bool  disconnected;   /* TRUE: Device has been disconnected */
  bool           stop2;          /* True: 2 stop bits (for line coding) */
  bool           txena;          /* True: TX "interrupts" enabled */
  bool           rxena;          /* True: RX "interrupts" enabled */
#ifdef CONFIG_SERIAL_IFLOWCONTROL
  bool           iflow;          /* True: Input flow control (RTS) enabled */
  bool           rts;            /* True: Input flow control is in effect */
#endif
#ifdef CONFIG_SERIAL_OFLOWCONTROL
  bool           oflow;          /* True: Output flow control (CTS) enabled */
#endif
#ifdef CONFIG_USBHOST_FT232R_HWFLOWCTRL
  bool           cts;            /* True: Clear to send to FTDI chip */
#endif
  uint8_t        minor;          /* Minor number identifying the /dev/ttyUSB[n] device */
  uint8_t        dataif;         /* Data interface number */
  uint8_t        nbits;          /* Number of bits (for line encoding) */
  uint8_t        parity;         /* Parity (for line encoding) */
  uint16_t       pktsize;        /* Allocated size of transfer buffers */
  uint16_t       nrxbytes;       /* Number of bytes in the RX packet buffer */
  uint16_t       rxndx;          /* Index to the next byte in the RX packet buffer */
  int16_t        crefs;          /* Reference count on the driver instance */
  int16_t        nbytes;         /* The number of bytes actually transferred */
  sem_t          exclsem;        /* Used to maintain mutual exclusive access */
  struct work_s  ntwork;         /* For asynchronous notification work */
  struct work_s  rxwork;         /* For RX packet work */
  struct work_s  txwork;         /* For TX packet work */
  FAR uint8_t   *ctrlreq;        /* Allocated ctrl request structure */
  FAR uint8_t   *inbuf;          /* Allocated RX buffer for the Bulk IN endpoint */
  FAR uint8_t   *outbuf;         /* Allocated TX buffer for the Bulk OUT endpoint */
  uint32_t       baud;           /* Current baud for line coding */
  usbhost_ep_t   bulkin;         /* Bulk IN endpoint */
  usbhost_ep_t   bulkout;        /* Bulk OUT endpoint */

  /* This is the serial data buffer */

  char           rxbuffer[CONFIG_USBHOST_FT232R_RXBUFSIZE];
  char           txbuffer[CONFIG_USBHOST_FT232R_TXBUFSIZE];
};

/* This is how struct usbhost_ft232r_s looks to the free list logic */

struct usbhost_freestate_s
{
  FAR struct usbhost_freestate_s *flink;
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Semaphores */

static int usbhost_takesem(FAR sem_t *sem);
static void usbhost_forcetake(FAR sem_t *sem);
#define usbhost_givesem(s) nxsem_post(s);

/* Memory allocation services */

static FAR struct usbhost_ft232r_s *usbhost_allocclass(void);
static void usbhost_freeclass(FAR struct usbhost_ft232r_s *usbclass);

/* Device name management */

static int  usbhost_devno_alloc(FAR struct usbhost_ft232r_s *priv);
static void usbhost_devno_free(FAR struct usbhost_ft232r_s *priv);
static inline void usbhost_mkdevname(FAR struct usbhost_ft232r_s *priv,
              FAR char *devname);

/* UART buffer data transfer */

static void usbhost_txdata_work(FAR void *arg);
static void usbhost_rxdata_work(FAR void *arg);

/* Worker thread actions */

static void usbhost_destroy(FAR void *arg);

/* Helpers for usbhost_connect() */

static int  usbhost_cfgdesc(FAR struct usbhost_ft232r_s *priv,
              FAR const uint8_t *configdesc, int desclen);

/* (Little Endian) Data helpers */

static inline uint16_t usbhost_getle16(FAR const uint8_t *val);
static inline uint16_t usbhost_getbe16(FAR const uint8_t *val);
static inline void usbhost_putle16(FAR uint8_t *dest, uint16_t val);

/* Transfer descriptor memory management */

static int  usbhost_alloc_buffers(FAR struct usbhost_ft232r_s *priv);
static void usbhost_free_buffers(FAR struct usbhost_ft232r_s *priv);

/* struct usbhost_registry_s methods */

static struct usbhost_class_s *usbhost_create(
              FAR struct usbhost_hubport_s *hport,
              FAR const struct usbhost_id_s *id);

/* struct usbhost_class_s methods */

static int  usbhost_connect(FAR struct usbhost_class_s *usbclass,
              FAR const uint8_t *configdesc, int desclen);
static int  usbhost_disconnected(FAR struct usbhost_class_s *usbclass);

/* Serial driver lower-half interfaces */

static int  usbhost_setup(FAR struct uart_dev_s *uartdev);
static void usbhost_shutdown(FAR struct uart_dev_s *uartdev);
static int  usbhost_attach(FAR struct uart_dev_s *uartdev);
static void usbhost_detach(FAR struct uart_dev_s *uartdev);
static int  usbhost_ioctl(FAR struct file *filep, int cmd,
              unsigned long arg);
static void usbhost_rxint(FAR struct uart_dev_s *uartdev, bool enable);
static bool usbhost_rxavailable(FAR struct uart_dev_s *uartdev);
#ifdef CONFIG_SERIAL_IFLOWCONTROL
static bool usbhost_rxflowcontrol(FAR struct uart_dev_s *uartdev,
              unsigned int nbuffered, bool upper);
#endif
static void usbhost_txint(FAR struct uart_dev_s *uartdev, bool enable);
static bool usbhost_txready(FAR struct uart_dev_s *uartdev);
static bool usbhost_txempty(FAR struct uart_dev_s *uartdev);

/* FTDI control transfer helpers */

static int ft232r_ctrlxfer(FAR struct usbhost_ft232r_s *priv, uint8_t req,
                          uint16_t value, uint16_t index);
static int ft232r_reset(FAR struct usbhost_ft232r_s *priv, bool purgerxtx);
static int ft232r_modemctrl(FAR struct usbhost_ft232r_s *priv);
static int ft232r_setflowctrl(FAR struct usbhost_ft232r_s *priv);
static int ft232r_setdivisor(uint32_t *divisor, uint32_t baud);
static int ft232r_setbaud(FAR struct usbhost_ft232r_s *priv);
static int ft232r_setdata(FAR struct usbhost_ft232r_s *priv);
static int ft232r_setlat(FAR struct usbhost_ft232r_s *priv);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* This structure provides the registry entry ID information that will  be
 * used to associate the USB host FTDI class to a connected USB
 * device.
 */

static const struct usbhost_id_s g_id[4] =
{
  {
    USB_CLASS_VENDOR_SPEC,  /* base     */
    0xff,                   /* subclass */
    0xff,                   /* proto    */
    0x0403,                 /* vid      */
    0x6001                  /* pid      */
  },
  {
    USB_CLASS_VENDOR_SPEC,  /* base     */
    0xff,                   /* subclass */
    0xff,                   /* proto    */
    0x0403,                 /* vid      */
    0x6015                  /* pid      */
  }
};

/* This is the USB host FTDI class's registry entry */

static struct usbhost_registry_s g_ft232r =
{
  NULL,                   /* flink    */
  usbhost_create,         /* create   */
  2,                      /* nids     */
  &g_id[0]                /* id[]     */
};

/* Serial driver lower half interface */

static const struct uart_ops_s g_uart_ops =
{
  usbhost_setup,         /* setup */
  usbhost_shutdown,      /* shutdown */
  usbhost_attach,        /* attach */
  usbhost_detach,        /* detach */
  usbhost_ioctl,         /* ioctl */
  NULL           ,       /* receive */
  usbhost_rxint,         /* rxinit */
  usbhost_rxavailable,   /* rxavailable */
#ifdef CONFIG_SERIAL_IFLOWCONTROL
  usbhost_rxflowcontrol, /* rxflowcontrol */
#endif
  NULL,                  /* send */
  usbhost_txint,         /* txinit */
  usbhost_txready,       /* txready */
  usbhost_txempty        /* txempty */
};

/* This is an array of pre-allocated USB host FTDI class instances */

#if CONFIG_USBHOST_FT232R_NPREALLOC > 0
static struct usbhost_ft232r_s g_prealloc[CONFIG_USBHOST_FT232R_NPREALLOC];
#endif

/* This is a list of free, pre-allocated USB host FTDI class instances */

#if CONFIG_USBHOST_FT232R_NPREALLOC > 0
static FAR struct usbhost_freestate_s *g_freelist;
#endif

/* This is a bitmap that is used to allocate device
 * minor numbers /dev/ttyUSB[n].
 */

static uint32_t g_devinuse;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: usbhost_takesem
 *
 * Description:
 *   This is just a wrapper to handle the annoying behavior of semaphore
 *   waits that return due to the receipt of a signal.
 *
 ****************************************************************************/

static int usbhost_takesem(FAR sem_t *sem)
{
  return nxsem_wait_uninterruptible(sem);
}

/****************************************************************************
 * Name: usbhost_forcetake
 *
 * Description:
 *   This is just another wrapper but this one continues even if the thread
 *   is canceled.  This must be done in certain conditions where were must
 *   continue in order to clean-up resources.
 *
 ****************************************************************************/

static void usbhost_forcetake(FAR sem_t *sem)
{
  int ret;

  do
    {
      ret = nxsem_wait_uninterruptible(sem);

      /* The only expected error would -ECANCELED meaning that the
       * parent thread has been canceled.  We have to continue and
       * terminate the poll in this case.
       */

      DEBUGASSERT(ret == OK || ret == -ECANCELED);
    }
  while (ret < 0);
}

/****************************************************************************
 * Name: usbhost_allocclass
 *
 * Description:
 *   This is really part of the logic that implements the create() method
 *   of struct usbhost_registry_s.  This function allocates memory for one
 *   new class instance.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   On success, this function will return a non-NULL instance of struct
 *   usbhost_class_s.  NULL is returned on failure; this function will
 *   will fail only if there are insufficient resources to create another
 *   USB host class instance.
 *
 ****************************************************************************/

#if CONFIG_USBHOST_FT232R_NPREALLOC > 0
static FAR struct usbhost_ft232r_s *usbhost_allocclass(void)
{
  FAR struct usbhost_freestate_s *entry;
  irqstate_t flags;

  /* We may be executing from an interrupt handler so we need to take one of
   * our pre-allocated class instances from the free list.
   */

  flags = enter_critical_section();
  entry = g_freelist;
  if (entry)
    {
      g_freelist = entry->flink;
    }

  leave_critical_section(flags);
  uinfo("Allocated: %p\n", entry);
  return (FAR struct usbhost_ft232r_s *)entry;
}
#else
static FAR struct usbhost_ft232r_s *usbhost_allocclass(void)
{
  FAR struct usbhost_ft232r_s *priv;

  /* We are not executing from an interrupt handler so we can just call
   * kmm_malloc() to get memory for the class instance.
   */

  DEBUGASSERT(!up_interrupt_context());
  priv = (FAR struct usbhost_ft232r_s *)
    kmm_malloc(sizeof(struct usbhost_ft232r_s));

  uinfo("Allocated: %p\n", priv);
  return priv;
}
#endif

/****************************************************************************
 * Name: usbhost_freeclass
 *
 * Description:
 *   Free a class instance previously allocated by usbhost_allocclass().
 *
 * Input Parameters:
 *   usbclass - A reference to the class instance to be freed.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#if CONFIG_USBHOST_FT232R_NPREALLOC > 0
static void usbhost_freeclass(FAR struct usbhost_ft232r_s *usbclass)
{
  FAR struct usbhost_freestate_s *entry =
    (FAR struct usbhost_freestate_s *)usbclass;
  irqstate_t flags;
  DEBUGASSERT(entry != NULL);

  uinfo("Freeing: %p\n", entry);

  /* Just put the pre-allocated class structure back on the freelist */

  flags = enter_critical_section();
  entry->flink = g_freelist;
  g_freelist = entry;
  leave_critical_section(flags);
}
#else
static void usbhost_freeclass(FAR struct usbhost_ft232r_s *usbclass)
{
  DEBUGASSERT(usbclass != NULL);

  /* Free the class instance (calling kmm_free() in case we are executing
   * from an interrupt handler.
   */

  uinfo("Freeing: %p\n", usbclass);
  kmm_free(usbclass);
}
#endif

/****************************************************************************
 * Name: usbhost_devno_alloc
 *
 * Description:
 *   Allocate a unique /dev/ttyACM[n] minor number in the range 0-31.
 *
 ****************************************************************************/

static int usbhost_devno_alloc(FAR struct usbhost_ft232r_s *priv)
{
  irqstate_t flags;
  int devno;

  flags = enter_critical_section();
  for (devno = 0; devno < 32; devno++)
    {
      uint32_t bitno = 1 << devno;
      if ((g_devinuse & bitno) == 0)
        {
          g_devinuse |= bitno;
          priv->minor = devno;
          leave_critical_section(flags);
          return OK;
        }
    }

  leave_critical_section(flags);
  return -EMFILE;
}

/****************************************************************************
 * Name: usbhost_devno_free
 *
 * Description:
 *   Free a /dev/ttyACM[n] minor number so that it can be used.
 *
 ****************************************************************************/

static void usbhost_devno_free(FAR struct usbhost_ft232r_s *priv)
{
  int devno = priv->minor;

  if (devno >= 0 && devno < 32)
    {
      irqstate_t flags = enter_critical_section();
      g_devinuse &= ~(1 << devno);
      leave_critical_section(flags);
    }
}

/****************************************************************************
 * Name: usbhost_mkdevname
 *
 * Description:
 *   Format a /dev/ttyACM[n] device name given a minor number.
 *
 ****************************************************************************/

static inline void usbhost_mkdevname(FAR struct usbhost_ft232r_s *priv,
                                     FAR char *devname)
{
  snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->minor);
}

/****************************************************************************
 * Name: ft232r_ctrlxfer
 *
 * Description:
 *   Free a class instance previously allocated by usbhost_allocclass().
 *
 * Input Parameters:
 *   priv  - A reference to the USB host class instance.
 *   req   - FTDI control transfer type.
 *   value - Value for control transfer.
 *   index - Index for control transfer.
 *
 * Returned Value:
 *   0 on success. Negated errno on failure.
 *
 ****************************************************************************/

static int ft232r_ctrlxfer(FAR struct usbhost_ft232r_s *priv, uint8_t req,
                           uint16_t value, uint16_t index)
{
  FAR struct usbhost_hubport_s *hport;
  FAR struct usb_ctrlreq_s *ctrlreq;
  int ret;

  hport = priv->usbclass.hport;
  DEBUGASSERT(hport);

  /* Initialize the control request */

  ctrlreq          = (FAR struct usb_ctrlreq_s *)priv->ctrlreq;
  ctrlreq->type    = USB_DIR_OUT | USB_REQ_TYPE_VENDOR |
                     USB_REQ_RECIPIENT_DEVICE;
  ctrlreq->req     = req;

  usbhost_putle16(ctrlreq->value, value);
  usbhost_putle16(ctrlreq->index, index);
  usbhost_putle16(ctrlreq->len,   0);

  /* And send the request */

  ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, NULL);
  if (ret < 0)
    {
      uerr("ERROR: DRVR_CTRLOUT failed: %d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: ft232r_reset
 *
 * Description:
 *   Resets the FT232.
 *
 * Input Parameters:
 *   priv  - A reference to the USB host class instance.
 *   purgerxtx - True if the RX and TX buffers of the FTDI chip
 *               should be flushed.
 *
 * Returned Value:
 *   0 on success. Negated errno on failure.
 *
 ****************************************************************************/

static int ft232r_reset(FAR struct usbhost_ft232r_s *priv, bool purgerxtx)
{
  int ret = ft232r_ctrlxfer(priv, USBHOST_FT232R_CTRLREQ_RESET,
                            purgerxtx, 0);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_ctrlxfer failed: %d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: ft232r_modemctrl
 *
 * Description:
 *   Sets RTS and DTR.
 *
 * Input Parameters:
 *   priv  - A reference to the USB host class instance.
 *
 * Returned Value:
 *   0 on success. Negated errno on failure.
 *
 ****************************************************************************/

static int ft232r_modemctrl(FAR struct usbhost_ft232r_s *priv)
{
  uint16_t value =  USBHOST_FT232R_MODEMCTRL_VAL_RTS |
                    USBHOST_FT232R_MODEMCTRL_VAL_DTR |
                    USBHOST_FT232R_MODEMCTRL_VAL_RTS_EN |
                    USBHOST_FT232R_MODEMCTRL_VAL_DTR_EN;

  int ret = ft232r_ctrlxfer(priv, USBHOST_FT232R_CTRLREQ_MODEMCTRL,
                            value, 0);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_ctrlxfer failed: %d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: ft232r_setflowctrl
 *
 * Description:
 *   Enables/ disables hardware flow control.
 *
 * Input Parameters:
 *   priv  - A reference to the USB host class instance.
 *
 * Returned Value:
 *   0 on success. Negated errno on failure.
 *
 ****************************************************************************/

static int ft232r_setflowctrl(FAR struct usbhost_ft232r_s *priv)
{
  /* upper byte XOFF char, lower byte XON char */

  uint16_t value = 0;

  /* Upper byte: flow control settings.
   *    0th bit: RTS/CTS flow control.
   *    1st bit: DTR/DSR flow control.
   *    2nd bit: XON/XOFF flow control.
   * Lower byte: 0
   */

  uint16_t index = 0;

#ifdef CONFIG_USBHOST_FT232R_HWFLOWCTRL
  index = 0x100;
#endif

  int ret = ft232r_ctrlxfer(priv, USBHOST_FT232R_CTRLREQ_SETFLOWCTRL,
                            value, index);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_ctrlxfer failed: %d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: ft232r_setdivisor
 *
 * Description:
 *   Converts a baud rate to the corresponding FT232R divisor. Returns an
 *   error if the requested baud rate is not possible.
 *
 * Input Parameters:
 *   divisor - Where the calculated divisor will be written to.
 *   baud    - The requested baud rate.
 * Returned Value:
 *   0 on success. Negated errno on failure.
 *
 ****************************************************************************/

static int ft232r_setdivisor(uint32_t *divisor, uint32_t baud)
{
  int ret = 0;

  /* Deal with special case baud rates: 3MHz and 2MHz */

  if (baud == USBHOST_FT232R_BAUD_3MHZ)
    {
      *divisor = 0;
    }
  else if (baud == USBHOST_FT232R_BAUD_2MHZ)
    {
      *divisor = 1;
    }
  else if (baud > USBHOST_FT232R_BAUD_3MHZ / 2)
    {
      /* FT232 doesn't support fractional divisors between 0 and 2. */

      ret = -EINVAL;
    }
  else
    {
      int divfrac[9] =
        {
          0, 500, 250, 125, 375, 625, 750, 875, 1000
        };

      double frac = (double)USBHOST_FT232R_MAX_BAUD / (double)baud;
      int rem;

      *divisor = (uint32_t)frac;
      rem      = (frac - *divisor) * 1000.0;

      if (rem > 0)
        {
          int i;
          int j = 0;
          int closest = rem;
          double err;

          for (i = 1; i < 9; i++)
            {
              if (rem == divfrac[i])
                {
                  j = i;
                  break;
                }

              if ((rem > divfrac[i] && (rem - divfrac[i]) < closest))
                {
                  j = i;
                  closest = rem - divfrac[i];
                }
              else if (rem < divfrac[i] && (divfrac[i] - rem) < closest)
                {
                  j = i;
                  closest = divfrac[i] - rem;
                }
            }

          /* Make sure the calculated baud is within 3% of the
           * requested baud rate.
           */

          err = (frac -
                (double)(*divisor + (double)divfrac[j] / 1000.0)) /
                frac;

          if (err < -0.03 || err > 0.03)
            {
              ret = -ENOTSUP;
            }

          if (j == 9)
            {
              *divisor += 1;
            }
          else
            {
              *divisor |= (j & 0x7) << 14;
            }
        }
    }

  return ret;
}

/****************************************************************************
 * Name: ft232r_setbaud
 *
 * Description:
 *   Sets the FT232 baud rate.
 *
 * Input Parameters:
 *   priv  - A reference to the USB host class instance.
 *
 * Returned Value:
 *   0 on success. Negated errno on failure.
 *
 ****************************************************************************/

static int ft232r_setbaud(FAR struct usbhost_ft232r_s *priv)
{
  /* Convert baud to FT232 divisor */

  uint32_t divisor;
  int ret = ft232r_setdivisor(&divisor, priv->baud);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_setdivisor failed: %d\n", ret);
    }
  else
    {
      ret = ft232r_ctrlxfer(priv, USBHOST_FT232R_CTRLREQ_SETBAUD,
              divisor & 0xffff, divisor && 0x10000);
      if (ret < 0)
        {
          uerr("ERROR: ft232r_ctrlxfer failed: %d\n", ret);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: ft232r_setdata
 *
 * Description:
 *   Sets the packet characteristics for the UART side of the FT232.
 *
 * Input Parameters:
 *   priv  - A reference to the USB host class instance.
 *
 * Returned Value:
 *   0 on success. Negated errno on failure.
 *
 ****************************************************************************/

static int ft232r_setdata(FAR struct usbhost_ft232r_s *priv)
{
  uint16_t linecoding;
  int ret;

  /* Build up line coding */

  linecoding = (priv->nbits & USBHOST_FT232R_SETDATA_NBIT_MASK) |
      ((priv->parity & USBHOST_FT232R_SETDATA_PARITY_MASK)
      << USBHOST_FT232R_SETDATA_PARITY_SHIFT);
  if (priv->stop2)
    {
      linecoding |= CONFIG_USBHOST_FT232R_2STOP;
    }

  ret = ft232r_ctrlxfer(priv, USBHOST_FT232R_CTRLREQ_SETDATA,
                        linecoding, 0);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_ctrlxfer failed: %d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * Name: ft232r_setlat
 *
 * Description:
 *   Sets the UART latency of the FT232.
 *
 * Input Parameters:
 *   priv  - A reference to the USB host class instance.
 *
 * Returned Value:
 *   0 on success. Negated errno on failure.
 *
 ****************************************************************************/

static int ft232r_setlat(FAR struct usbhost_ft232r_s *priv)
{
  int ret = ft232r_ctrlxfer(priv, USBHOST_FT232R_CTRLREQ_SETLATTIMER,
                            CONFIG_USBHOST_FT232R_LATENCY, 0);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_ctrlxfer failed: %d\n", ret);
    }

  return ret;
}

/****************************************************************************
 * UART buffer data transfer
 ****************************************************************************/

/****************************************************************************
 * Name: usbhost_txdata_work
 *
 * Description:
 *   Send more OUT data to the attached FTDI device.
 *
 * Input Parameters:
 *   arg - A reference to the FTDI class private data
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void usbhost_txdata_work(FAR void *arg)
{
  FAR struct usbhost_ft232r_s *priv;
  FAR struct usbhost_hubport_s *hport;
  FAR struct uart_dev_s *uartdev;
  FAR struct uart_buffer_s *txbuf;
  ssize_t nwritten;
  int txndx;
  int txtail;
  int ret;

  priv = (FAR struct usbhost_ft232r_s *)arg;
  DEBUGASSERT(priv);

  hport = priv->usbclass.hport;
  DEBUGASSERT(hport);

  uartdev = &priv->uartdev;
  txbuf   = &uartdev->xmit;

  /* Do nothing if TX transmission is disabled */

  if (!priv->txena)
    {
      /* Terminate the work now *without* rescheduling */

      return;
    }

  /* Loop until The UART TX buffer is empty (or we become disconnected) */

  txtail = txbuf->tail;
  txndx  = 0;

#ifdef CONFIG_USBHOST_FT232R_HWFLOWCTRL
  while (txtail != txbuf->head && priv->txena &&
          !priv->disconnected && priv->cts)
#else
  while (txtail != txbuf->head && priv->txena && !priv->disconnected)
#endif
    {
      /* Copy data from the UART TX buffer until either 1) the UART TX
       * buffer has been emptied, or 2) the Bulk OUT buffer is full.
       */

      txndx = 0;
      while (txtail != txbuf->head && txndx < priv->pktsize)
        {
          /* Copy the next byte */

          priv->outbuf[txndx] = txbuf->buffer[txtail];

          /* Increment counters and indices */

          txndx++;
          if (++txtail >= txbuf->size)
            {
             txtail = 0;
            }
        }

      /* Save the updated tail pointer so that it cannot be sent again */

      txbuf->tail = txtail;

      /* Bytes were removed from the TX buffer.  Inform any waiters that
       * there is space available in the TX buffer.
       */

      uart_datasent(uartdev);

      /* Send the filled TX buffer to the FTDI device */

      nwritten = DRVR_TRANSFER(hport->drvr, priv->bulkout,
                               priv->outbuf, txndx);
      if (nwritten < 0)
        {
          /* The most likely reason for a failure is that FTDI device
           * NAK'ed our packet OR that the device has been disconnected.
           *
           * Just break out of the loop, rescheduling the work (unless
           * the device is disconnected).
           */

          uerr("ERROR: DRVR_TRANSFER for packet failed: %d\n",
               (int)nwritten);
          break;
        }
    }

  /* We get here because: 1) the UART TX buffer is empty and there is
   * nothing more to send, 2) the FTDI device was not ready to accept our
   * data, or the device is no longer available.
   *
   * If the last packet sent was and even multiple of the packet size, then
   * we need to send a zero length packet (ZLP).
   */

  if (txndx == priv->pktsize && !priv->disconnected)
    {
      /* Send the ZLP to the FTDI device */

      nwritten = DRVR_TRANSFER(hport->drvr, priv->bulkout,
                               priv->outbuf, 0);
      if (nwritten < 0)
        {
          /* The most likely reason for a failure is that FTDI device
           * NAK'ed our packet.
           */

          uerr("ERROR: DRVR_TRANSFER for ZLP failed: %d\n", (int)nwritten);
        }
    }

  /* Check again if TX reception is enabled and that the device is still
   * connected.  These states could have changed since we started the
   * transfer.
   */

  if (priv->txena && !priv->disconnected)
    {
      /* Schedule TX data work to occur after a delay. */

      ret = work_queue(LPWORK, &priv->txwork, usbhost_txdata_work, priv,
                       USBHOST_FT232R_TXDELAY);
      DEBUGASSERT(ret >= 0);
      UNUSED(ret);
    }
}

/****************************************************************************
 * Name: usbhost_rxdata_work
 *
 * Description:
 *   Get more IN data from the attached FTDI device.
 *
 * Input Parameters:
 *   arg - A reference to the FTDI class private data
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void usbhost_rxdata_work(FAR void *arg)
{
  FAR struct usbhost_ft232r_s *priv;
  FAR struct usbhost_hubport_s *hport;
  FAR struct uart_dev_s *uartdev;
  FAR struct uart_buffer_s *rxbuf;
  ssize_t nread;
  int nxfrd;
  int nexthead;
  int rxndx;
  int ret;

  priv    = (FAR struct usbhost_ft232r_s *)arg;
  DEBUGASSERT(priv);

  hport   = priv->usbclass.hport;
  DEBUGASSERT(hport);

  uartdev = &priv->uartdev;
  rxbuf   = &uartdev->recv;

  /* Get the index in the RX packet buffer where we will take the first
   * byte of data.
   */

  rxndx    = priv->rxndx;
  nxfrd    = 0;

  /* Get the index to the value of the UART RX buffer head AFTER the
   * first character has been stored.  We need to know this in order
   * to test if the UART RX buffer is full.
   */

  nexthead = rxbuf->head + 1;
  if (nexthead >= rxbuf->size)
    {
      nexthead = 0;
    }

  /* Loop until either:
   *
   * 1. The UART RX buffer is full
   * 2. There is no more data available from the FTDI device
   * 3. RX rec
   */

#ifdef CONFIG_SERIAL_IFLOWCONTROL
  while (priv->rxena && priv->rts && !priv->disconnected)
#else
  while (priv->rxena && !priv->disconnected)
#endif
    {
      /* Stop now if there is no room for another
       * character in the RX buffer.
       */

      if (nexthead == rxbuf->tail)
        {
          /* Break out of the loop, rescheduling the work */

          break;
        }

      /* Do we have any buffer RX data to transfer? */

      if (priv->nrxbytes < 1)
        {
          /* No.. Read more data from the FTDI device */

          nread = DRVR_TRANSFER(hport->drvr, priv->bulkin,
                                priv->inbuf, priv->pktsize);
          if (nread < 0)
            {
              /* The most likely reason for a failure is that the has no
               * data available now and NAK'ed the IN token OR that the
               * transfer was cancelled because the device was disconnected.
               *
               * Just break out of the loop, rescheduling the work (if the
               * device was not disconnected.
               */

              uerr("ERROR: DRVR_TRANSFER for packet failed: %d\n",
                   (int)nread);
              break;
            }

          /* Save the number of bytes read.  This might be zero if
           * a Zero Length Packet (ZLP) is received.  The ZLP is
           * part of the bulk transfer protocol, but otherwise of
           * no interest to us. Alternatively it can be 2 bytes of
           * only FTDI status information.
           */

          priv->nrxbytes = (uint16_t)nread - 2;
          rxndx          = 0;

          /* When Hardware flow control is used, CTS is reported in the
           * first byte of RX payload.
           */

#ifdef CONFIG_USBHOST_FT232R_HWFLOWCTRL
          if (nread >= 2)
            {
              priv->cts = priv->inbuf[0] & 0x10;
            }
#endif

          /* Ignore ZLPs and RX of only FTDI status */

          if (nread < 3)
            {
              continue;
            }
        }

      /* Transfer one byte from the RX packet buffer into UART RX buffer.
       * +2 to account for the two status byes
       */

      rxbuf->buffer[rxbuf->head] = priv->inbuf[rxndx + 2];
      nxfrd++;

      /* Save the updated indices */

      rxbuf->head = nexthead;
      priv->rxndx = rxndx;

      /* Update the head point for for the next pass through the loop
       * handling. If nexthead incremented to rxbuf->tail, then the
       * RX buffer will and we will exit the loop at the top.
       */

      if (++nexthead >= rxbuf->size)
        {
           nexthead = 0;
        }

      /* Increment the index in the USB IN packet buffer.  If the
       * index becomes equal to the number of bytes in the buffer, then
       * we have consumed all of the RX data.
       */

      if (++rxndx >= priv->nrxbytes)
        {
          /* In that case set the number of bytes in the buffer to zero.
           * This will force re-reading on the next time through the loop.
           */

          priv->nrxbytes = 0;
          priv->rxndx    = 0;

          /* Inform any waiters there there is new incoming data available. */

          uart_datareceived(uartdev);
          nxfrd = 0;
        }
    }

  /* We break out to here:  1) the UART RX buffer is full, 2) the FTDI
   * device is not ready to provide us with more serial data, or 3) the
   * device has been disconnected.
   *
   * Check if the device is still available:  RX enabled, no RX flow
   * control in effect, and that the device is not disconnected. These
   * states could have changed since we started the transfer.
   */

#ifdef CONFIG_SERIAL_IFLOWCONTROL
  if (priv->rxena && priv->rts && work_available(&priv->rxwork) &&
      !priv->disconnected)
#else
  if (priv->rxena && work_available(&priv->rxwork) && !priv->disconnected)
#endif
    {
      /* Schedule RX data reception work to occur after a delay.  This will
       * affect our responsive in certain cases.  The delayed work, however,
       * will be cancelled and replaced with immediate work when the upper
       * layer demands more data.
       */

      ret = work_queue(LPWORK, &priv->rxwork, usbhost_rxdata_work, priv,
                       USBHOST_FT232R_RXDELAY);
      DEBUGASSERT(ret >= 0);
      UNUSED(ret);
    }

  /* If any bytes were added to the buffer, inform any waiters there there
   * is new incoming data available.
   */

  if (nxfrd)
    {
      uart_datareceived(uartdev);
    }
}

/****************************************************************************
 * Name: usbhost_destroy
 *
 * Description:
 *   The USB FTDI device has been disconnected and the reference count
 *   on the USB host class instance has gone to 1.. Time to destroy the USB
 *   host class instance.
 *
 * Input Parameters:
 *   arg - A reference to the class instance to be destroyed.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void usbhost_destroy(FAR void *arg)
{
  FAR struct usbhost_ft232r_s *priv = (FAR struct usbhost_ft232r_s *)arg;
  FAR struct usbhost_hubport_s *hport;
  char devname[DEV_NAMELEN];

  DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL);
  hport = priv->usbclass.hport;

  uinfo("crefs: %d\n", priv->crefs);

  /* Unregister the serial lower half driver */

  usbhost_mkdevname(priv, devname);
  unregister_driver(devname);

  /* Release the device name used by this connection */

  usbhost_devno_free(priv);

  /* Free the allocated endpoints */

  if (priv->bulkout)
    {
      DRVR_EPFREE(hport->drvr, priv->bulkout);
    }

  if (priv->bulkin)
    {
      DRVR_EPFREE(hport->drvr, priv->bulkin);
    }

  /* Free any transfer buffers */

  usbhost_free_buffers(priv);

  /* Destroy the semaphores */

  nxsem_destroy(&priv->exclsem);

  /* Disconnect the USB host device */

  DRVR_DISCONNECT(hport->drvr, hport);

  /* Free the function address assigned to this device */

  usbhost_devaddr_destroy(hport, hport->funcaddr);
  hport->funcaddr = 0;

  /* And free the class instance.  */

  usbhost_freeclass(priv);
}

/****************************************************************************
 * Name: usbhost_cfgdesc
 *
 * Description:
 *   This function implements the connect() method of struct
 *   usbhost_class_s.  This method is a callback into the class
 *   implementation.  It is used to provide the device's configuration
 *   descriptor to the class so that the class may initialize properly
 *
 * Input Parameters:
 *   priv - The USB host class instance.
 *   configdesc - A pointer to a uint8_t buffer container the configuration
 *     descriptor.
 *   desclen - The length in bytes of the configuration descriptor.
 *
 * Returned Value:
 *   On success, zero (OK) is returned. On a failure, a negated errno value
 *   is returned indicating the nature of the failure
 *
 * Assumptions:
 *   This function will *not* be called from an interrupt handler.
 *
 ****************************************************************************/

static int usbhost_cfgdesc(FAR struct usbhost_ft232r_s *priv,
                           FAR const uint8_t *configdesc, int desclen)
{
  FAR struct usbhost_hubport_s *hport;
  FAR struct usb_cfgdesc_s *cfgdesc;
  FAR struct usb_desc_s *desc;
  struct usbhost_epdesc_s bindesc;
  struct usbhost_epdesc_s boutdesc;
  struct usbhost_epdesc_s iindesc;
  int remaining;
  uint8_t found  = 0;
  uint8_t currif = 0;
  int ret;

  DEBUGASSERT(priv != NULL && priv->usbclass.hport &&
              configdesc != NULL &&
              desclen >= sizeof(struct usb_cfgdesc_s));

  hport = priv->usbclass.hport;

  /* Keep the compiler from complaining about uninitialized variables */

  memset(&bindesc, 0, sizeof(struct usbhost_epdesc_s));
  memset(&boutdesc, 0, sizeof(struct usbhost_epdesc_s));
  memset(&iindesc, 0, sizeof(struct usbhost_epdesc_s));

  /* Verify that we were passed a configuration descriptor */

  cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc;
  if (cfgdesc->type != USB_DESC_TYPE_CONFIG)
    {
      return -EINVAL;
    }

  /* Get the total length of the configuration descriptor (little endian).
   * It might be a good check to get the number of interfaces here too.
   */

  remaining = (int)usbhost_getle16(cfgdesc->totallen);

  /* Skip to the next entry descriptor */

  configdesc += cfgdesc->len;
  remaining  -= cfgdesc->len;

  /* Loop where there are more descriptors to examine */

  while (remaining >= sizeof(struct usb_desc_s))
    {
      /* What is the next descriptor? */

      desc = (FAR struct usb_desc_s *)configdesc;
      switch (desc->type)
        {
        /* Interface descriptor. The FTDI device may include two
         * interfaces:
         *
         * 1) A data interface which consists of two endpoints (bulk in +
         *    bulk out) and
         * 2) A control interface which consists of one interrupt in
         *    endpoint.
         */

        case USB_DESC_TYPE_INTERFACE:
          {
            FAR struct usb_ifdesc_s *ifdesc =
              (FAR struct usb_ifdesc_s *)configdesc;

            uinfo("Interface descriptor: class: %d subclass: %d proto: %d\n",
                  ifdesc->classid, ifdesc->subclass, ifdesc->protocol);
            DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC);

            /* Check for the FTDI data interface */

            if (ifdesc->classid == USB_CLASS_VENDOR_SPEC &&
                (found & USBHOST_DATAIF_FOUND) == 0)
              {
                /* Save the data interface number and mark that the data
                 * interface found has been found.
                 */

                priv->dataif  = ifdesc->ifno;
                found        |= USBHOST_DATAIF_FOUND;
                currif        = USBHOST_DATAIF_FOUND;
              }
            else
              {
                /* Its something else */

                currif        = 0;
              }
          }
          break;

        /* Endpoint descriptor.  We expect two bulk endpoints, an IN and an
         * OUT.
         */

        case USB_DESC_TYPE_ENDPOINT:
          {
            FAR struct usb_epdesc_s *epdesc =
              (FAR struct usb_epdesc_s *)configdesc;

            uinfo("Endpoint descriptor: currif: %02x attr: %02x\n",
                  currif, epdesc->attr);
            DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC);

            /* Check for a bulk endpoint. */

            if (currif == USBHOST_DATAIF_FOUND &&
                (epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) ==
                USB_EP_ATTR_XFER_BULK)
              {
                /* Yes.. it is a bulk endpoint.  IN or OUT? */

                if (USB_ISEPOUT(epdesc->addr))
                  {
                    /* It is an OUT bulk endpoint.  There should be only one
                     * bulk OUT endpoint.
                     */

                    if ((found & USBHOST_BULKOUT_FOUND) != 0)
                      {
                        /* Oops.. more than one endpoint.  We don't know
                         * what to do with this.
                         */

                        return -EINVAL;
                      }

                    found |= USBHOST_BULKOUT_FOUND;

                    /* Save the bulk OUT endpoint information */

                    boutdesc.hport        = hport;
                    boutdesc.addr         = epdesc->addr &
                                            USB_EP_ADDR_NUMBER_MASK;
                    boutdesc.in           = false;
                    boutdesc.xfrtype      = USB_EP_ATTR_XFER_BULK;
                    boutdesc.interval     = epdesc->interval;
                    boutdesc.mxpacketsize =
                      usbhost_getle16(epdesc->mxpacketsize);

                    uinfo("Bulk OUT EP addr:%d mxpacketsize:%d\n",
                          boutdesc.addr, boutdesc.mxpacketsize);
                  }
                else
                  {
                    /* It is an IN bulk endpoint.  There should be only one
                     * bulk IN endpoint.
                     */

                    if ((found & USBHOST_BULKIN_FOUND) != 0)
                      {
                        /* Oops.. more than one endpoint.  We don't know
                         * what to do with this.
                         */

                        return -EINVAL;
                      }

                    found |= USBHOST_BULKIN_FOUND;

                    /* Save the bulk IN endpoint information */

                    bindesc.hport        = hport;
                    bindesc.addr         = epdesc->addr &
                                           USB_EP_ADDR_NUMBER_MASK;
                    bindesc.in           = 1;
                    bindesc.xfrtype      = USB_EP_ATTR_XFER_BULK;
                    bindesc.interval     = epdesc->interval;
                    bindesc.mxpacketsize =
                      usbhost_getle16(epdesc->mxpacketsize);

                    uinfo("Bulk IN EP addr:%d mxpacketsize:%d\n",
                          bindesc.addr, bindesc.mxpacketsize);
                  }
              }
          }
          break;

        /* Other descriptors are just ignored for now */

        default:
          break;
        }

      /* If we found everything we need with this interface, then break out
       * of the loop early.
       */

      if (found == USBHOST_ALLFOUND)
        {
          break;
        }

      /* Increment the address of the next descriptor */

      configdesc += desc->len;
      remaining  -= desc->len;
    }

  /* Sanity checking... did we find all of things that we needed for the
   * FT232R interface?
   */

  if (found != USBHOST_ALLFOUND)
    {
      uerr("ERROR: Found DATA IF:%s BULK IN:%s BULK OUT:%s\n",
           (found & USBHOST_DATAIF_FOUND)  != 0  ? "YES" : "NO",
           (found & USBHOST_BULKIN_FOUND)  != 0 ? "YES" : "NO",
           (found & USBHOST_BULKOUT_FOUND) != 0 ? "YES" : "NO");
      return -EINVAL;
    }

  /* We are good... Allocate the endpoints */

  ret = DRVR_EPALLOC(hport->drvr, &boutdesc, &priv->bulkout);
  if (ret < 0)
    {
      uerr("ERROR: Failed to allocate Bulk OUT endpoint\n");
      return ret;
    }

  ret = DRVR_EPALLOC(hport->drvr, &bindesc, &priv->bulkin);
  if (ret < 0)
    {
      uerr("ERROR: Failed to allocate Bulk IN endpoint\n");
      DRVR_EPFREE(hport->drvr, priv->bulkout);
      return ret;
    }

  uinfo("Endpoints allocated\n");
  return OK;
}

/****************************************************************************
 * Name: usbhost_getle16
 *
 * Description:
 *   Get a (possibly unaligned) 16-bit little endian value.
 *
 * Input Parameters:
 *   val - A pointer to the first byte of the little endian value.
 *
 * Returned Value:
 *   A uint16_t representing the whole 16-bit integer value
 *
 ****************************************************************************/

static inline uint16_t usbhost_getle16(FAR const uint8_t *val)
{
  return (uint16_t)val[1] << 8 | (uint16_t)val[0];
}

/****************************************************************************
 * Name: usbhost_getbe16
 *
 * Description:
 *   Get a (possibly unaligned) 16-bit big endian value.
 *
 * Input Parameters:
 *   val - A pointer to the first byte of the big endian value.
 *
 * Returned Value:
 *   A uint16_t representing the whole 16-bit integer value
 *
 ****************************************************************************/

static inline uint16_t usbhost_getbe16(FAR const uint8_t *val)
{
  return (uint16_t)val[0] << 8 | (uint16_t)val[1];
}

/****************************************************************************
 * Name: usbhost_putle16
 *
 * Description:
 *   Put a (possibly unaligned) 16-bit little endian value.
 *
 * Input Parameters:
 *   dest - A pointer to the first byte to save the little endian value.
 *   val - The 16-bit value to be saved.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void usbhost_putle16(FAR uint8_t *dest, uint16_t val)
{
  /* Little endian means LS byte first in byte stream */

  dest[0] = val & 0xff;
  dest[1] = val >> 8;
}

/****************************************************************************
 * Name: usbhost_alloc_buffers
 *
 * Description:
 *   Allocate transfer buffer memory.
 *
 * Input Parameters:
 *   priv - A reference to the class instance.
 *
 * Returned Value:
 *   On success, zero (OK) is returned.  On failure, an negated errno value
 *   is returned to indicate the nature of the failure.
 *
 ****************************************************************************/

static int usbhost_alloc_buffers(FAR struct usbhost_ft232r_s *priv)
{
  FAR struct usbhost_hubport_s *hport;
  size_t maxlen;
  int ret;

  DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL);
  hport = priv->usbclass.hport;

  /* Allocate memory for control requests */

  ret = DRVR_ALLOC(hport->drvr, (FAR uint8_t **)&priv->ctrlreq, &maxlen);
  if (ret < 0)
    {
      uerr("ERROR: DRVR_ALLOC of ctrlreq failed: %d\n", ret);
      goto errout;
    }

  DEBUGASSERT(maxlen >= sizeof(struct usb_ctrlreq_s));

  /* Set the size of Bulk IN and OUT buffers to the max packet size */

  priv->pktsize = (hport->speed == USB_SPEED_HIGH) ? 512 : 64;

  /* Allocate a RX buffer for Bulk IN transfers */

  ret = DRVR_IOALLOC(hport->drvr, &priv->inbuf, priv->pktsize);
  if (ret < 0)
    {
      uerr("ERROR: DRVR_IOALLOC of Bulk IN buffer failed: %d (%d bytes)\n",
           ret, priv->pktsize);
      goto errout;
    }

  /* Allocate a TX buffer for Bulk IN transfers */

  ret = DRVR_IOALLOC(hport->drvr, &priv->outbuf, priv->pktsize);
  if (ret < 0)
    {
      uerr("ERROR: DRVR_IOALLOC of Bulk OUT buffer failed: %d (%d bytes)\n",
           ret, priv->pktsize);
      goto errout;
    }

  return OK;

errout:
  usbhost_free_buffers(priv);
  return ret;
}

/****************************************************************************
 * Name: usbhost_free_buffers
 *
 * Description:
 *   Free transfer buffer memory.
 *
 * Input Parameters:
 *   priv - A reference to the class instance.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void usbhost_free_buffers(FAR struct usbhost_ft232r_s *priv)
{
  FAR struct usbhost_hubport_s *hport;

  DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL);
  hport = priv->usbclass.hport;

  if (priv->ctrlreq)
    {
      DRVR_FREE(hport->drvr, priv->ctrlreq);
    }

  if (priv->inbuf)
    {
      DRVR_IOFREE(hport->drvr, priv->inbuf);
    }

  if (priv->outbuf)
    {
      DRVR_IOFREE(hport->drvr, priv->outbuf);
    }

  priv->pktsize      = 0;
  priv->ctrlreq      = NULL;
  priv->inbuf        = NULL;
  priv->outbuf       = NULL;
}

/****************************************************************************
 * struct usbhost_registry_s methods
 ****************************************************************************/

/****************************************************************************
 * Name: usbhost_create
 *
 * Description:
 *   This function implements the create() method of struct
 *   usbhost_registry_s.  The create() method is a callback into the class
 *   implementation.  It is used to (1) create a new instance of the USB
 *   host class state and to (2) bind a USB host driver "session" to the
 *   class instance.  Use of this create() method will support environments
 *   where there may be multiple USB ports and multiple USB devices
 *   simultaneously connected.
 *
 * Input Parameters:
 *   hport - The hub port that manages the new class instance.
 *   id - In the case where the device supports multiple base classes,
 *     subclasses, or protocols, this specifies which to configure for.
 *
 * Returned Value:
 *   On success, this function will return a non-NULL instance of struct
 *   usbhost_class_s that can be used by the USB host driver to communicate
 *   with the USB host class.  NULL is returned on failure; this function
 *   will fail only if the hport input parameter is NULL or if there are
 *   insufficient resources to create another USB host class instance.
 *
 ****************************************************************************/

static FAR struct usbhost_class_s *
usbhost_create(FAR struct usbhost_hubport_s *hport,
               FAR const struct usbhost_id_s *id)
{
  FAR struct usbhost_ft232r_s *priv;
  FAR struct uart_dev_s *uartdev;

  /* Allocate a USB host FTDI class instance */

  priv = usbhost_allocclass();
  if (priv)
    {
      /* Initialize the allocated FTDI class instance */

      memset(priv, 0, sizeof(struct usbhost_ft232r_s));

      /* Assign a device number to this class instance */

      if (usbhost_devno_alloc(priv) == OK)
        {
          /* Initialize class method function pointers */

          priv->usbclass.hport        = hport;
          priv->usbclass.connect      = usbhost_connect;
          priv->usbclass.disconnected = usbhost_disconnected;

          /* The initial reference count is 1...
           * One reference is held by the driver
           */

          priv->crefs = 1;

          /* Initialize semaphores
           * (this works okay in the interrupt context)
           */

          nxsem_init(&priv->exclsem, 0, 1);

          /* Set up the serial lower-half interface */

          uartdev              = &priv->uartdev;
          uartdev->recv.size   = CONFIG_USBHOST_FT232R_RXBUFSIZE;
          uartdev->recv.buffer = priv->rxbuffer;
          uartdev->xmit.size   = CONFIG_USBHOST_FT232R_TXBUFSIZE;
          uartdev->xmit.buffer = priv->txbuffer;
          uartdev->ops         = &g_uart_ops;
          uartdev->priv        = priv;

          /* Set up the initial line status */

          priv->baud           = CONFIG_USBHOST_FT232R_BAUD;
          priv->nbits          = CONFIG_USBHOST_FT232R_BITS;
          priv->parity         = CONFIG_USBHOST_FT232R_PARITY;
          priv->stop2          = CONFIG_USBHOST_FT232R_2STOP;

#ifdef CONFIG_SERIAL_IFLOWCONTROL
          priv->rts            = true;
#endif

          /* Return the instance of the USB FTDI class */

          return &priv->usbclass;
        }
    }

  /* An error occurred. Free the allocation and return NULL on all failures */

  if (priv)
    {
      usbhost_freeclass(priv);
    }

  return NULL;
}

/****************************************************************************
 * struct usbhost_class_s methods
 ****************************************************************************/

/****************************************************************************
 * Name: usbhost_connect
 *
 * Description:
 *   This function implements the connect() method of struct
 *   usbhost_class_s.  This method is a callback into the class
 *   implementation.  It is used to provide the device's configuration
 *   descriptor to the class so that the class may initialize properly
 *
 * Input Parameters:
 *   usbclass - The USB host class entry previously obtained from a call to
 *     create().
 *   configdesc - A pointer to a uint8_t buffer container the configuration
 *     descriptor.
 *   desclen - The length in bytes of the configuration descriptor.
 *
 * Returned Value:
 *   On success, zero (OK) is returned. On a failure, a negated errno value
 *   is returned indicating the nature of the failure
 *
 *   NOTE that the class instance remains valid upon return with a failure.
 *   It is the responsibility of the higher level enumeration logic to call
 *   CLASS_DISCONNECTED to free up the class driver resources.
 *
 * Assumptions:
 *   - This function will *not* be called from an interrupt handler.
 *   - If this function returns an error, the USB host controller driver
 *     must call to DISCONNECTED method to recover from the error
 *
 ****************************************************************************/

static int usbhost_connect(FAR struct usbhost_class_s *usbclass,
                           FAR const uint8_t *configdesc, int desclen)
{
  FAR struct usbhost_ft232r_s *priv =
                  (FAR struct usbhost_ft232r_s *)usbclass;
  char devname[DEV_NAMELEN];
  int ret;

  DEBUGASSERT(priv != NULL &&
              configdesc != NULL &&
              desclen >= sizeof(struct usb_cfgdesc_s));

  /* Get exclusive access to the device structure */

  ret = usbhost_takesem(&priv->exclsem);
  if (ret < 0)
    {
      return ret;
    }

  /* Increment the reference count.  This will prevent usbhost_destroy() from
   * being called asynchronously if the device is removed.
   */

  priv->crefs++;
  DEBUGASSERT(priv->crefs == 2);

  /* Parse the configuration descriptor to get the bulk I/O endpoints */

  ret = usbhost_cfgdesc(priv, configdesc, desclen);
  if (ret < 0)
    {
      uerr("ERROR: usbhost_cfgdesc() failed: %d\n", ret);
      goto errout;
    }

  /* Set aside a transfer buffer for exclusive use by the FTDI driver */

  ret = usbhost_alloc_buffers(priv);
  if (ret < 0)
    {
      uerr("ERROR: Failed to allocate transfer buffer\n");
      goto errout;
    }

  /* Send the initial line encoding */

  ret = ft232r_reset(priv, true);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_reset() failed: %d\n", ret);
    }

  ret = ft232r_setflowctrl(priv);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_setflowctrl() failed: %d\n", ret);
    }

  ret = ft232r_modemctrl(priv);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_modemctrl() failed: %d\n", ret);
    }

  ret = ft232r_setbaud(priv);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_setbaud() failed: %d\n", ret);
    }

  ret = ft232r_setdata(priv);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_setdata() failed: %d\n", ret);
    }

  ret = ft232r_setlat(priv);
  if (ret < 0)
    {
      uerr("ERROR: ft232r_setlat() failed: %d\n", ret);
    }

  /* Register the lower half serial instance with the upper half serial
   * driver.
   */

  usbhost_mkdevname(priv, devname);
  uinfo("Register device: %s\n", devname);

  ret = uart_register(devname, &priv->uartdev);
  if (ret < 0)
    {
      uerr("ERROR: uart_register() failed: %d\n", ret);
      goto errout;
    }

errout:
  /* Decrement the reference count.  We incremented the reference count
   * above so that usbhost_destroy() could not be called.  We now have to
   * be concerned about asynchronous modification of crefs because the
   * serial driver has been registered.
   */

  DEBUGASSERT(priv->crefs >= 2);
  priv->crefs--;

  /* Release the semaphore... there is a race condition here.
   * Decrementing the reference count and releasing the semaphore
   * allows usbhost_destroy() to execute (on the worker thread);
   * the class driver instance could get destroyed before we are
   * ready to handle it!
   */

  usbhost_givesem(&priv->exclsem);
  return ret;
}

/****************************************************************************
 * Name: usbhost_disconnected
 *
 * Description:
 *   This function implements the disconnected() method of struct
 *   usbhost_class_s.  This method is a callback into the class
 *   implementation.  It is used to inform the class that the USB device has
 *   been disconnected.
 *
 * Input Parameters:
 *   usbclass - The USB host class entry previously obtained from a call to
 *     create().
 *
 * Returned Value:
 *   On success, zero (OK) is returned. On a failure, a negated errno value
 *   is returned indicating the nature of the failure
 *
 * Assumptions:
 *   This function may be called from an interrupt handler.
 *
 ****************************************************************************/

static int usbhost_disconnected(FAR struct usbhost_class_s *usbclass)
{
  FAR struct usbhost_ft232r_s *priv =
    (FAR struct usbhost_ft232r_s *)usbclass;
  irqstate_t flags;

  DEBUGASSERT(priv != NULL);

  /* Set an indication to any users of the FTDI device that the device
   * is no longer available.
   */

  flags              = enter_critical_section();
  priv->disconnected = true;

  /* Let the upper half driver know that serial device is no longer
   * connected.
   */

  uart_connected(&priv->uartdev, false);

  /* Now check the number of references on the class instance.  If it is one,
   * then we can free the class instance now.  Otherwise, we will have to
   * wait until the holders of the references free them by closing the
   * serial driver.
   */

  uinfo("crefs: %d\n", priv->crefs);
  if (priv->crefs == 1)
    {
      /* Destroy the class instance.  If we are executing from an interrupt
       * handler, then defer the destruction to the worker thread.
       * Otherwise, destroy the instance now.
       */

      if (up_interrupt_context())
        {
          /* Destroy the instance on the worker thread. */

          uinfo("Queuing destruction: worker %p->%p\n",
                priv->ntwork.worker, usbhost_destroy);

          DEBUGASSERT(work_available(&priv->ntwork));
          work_queue(HPWORK, &priv->ntwork, usbhost_destroy, priv, 0);
        }
      else
        {
          /* Do the work now */

          usbhost_destroy(priv);
        }
    }

  leave_critical_section(flags);
  return OK;
}

/****************************************************************************
 * Serial Lower-Half Interfaces
 ****************************************************************************/

/****************************************************************************
 * Name: usbhost_setup
 *
 * Description:
 *   Configure the USART baud, bits, parity, etc. This method is called the
 *   first time that the serial port is opened.
 *
 ****************************************************************************/

static int usbhost_setup(FAR struct uart_dev_s *uartdev)
{
  FAR struct usbhost_ft232r_s *priv;
  irqstate_t flags;
  int ret;

  uinfo("Entry\n");
  DEBUGASSERT(uartdev && uartdev->priv);
  priv = (FAR struct usbhost_ft232r_s *)uartdev->priv;

  /* Make sure that we have exclusive access to the private data structure */

  DEBUGASSERT(priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS);
  ret = usbhost_takesem(&priv->exclsem);
  if (ret < 0)
    {
      return ret;
    }

  /* Check if the FTDI device is still connected.  We need to disable
   * interrupts momentarily to assure that there are no asynchronous
   * isconnect events.
   */

  flags = enter_critical_section();
  if (priv->disconnected)
    {
      /* No... the block driver is no longer bound to the class.  That means
       * that the USB FTDI device is no longer connected.  Refuse any
       * further attempts to open the driver.
       */

      ret = -ENODEV;
    }
  else
    {
      /* Otherwise, just increment the reference count on the driver */

      priv->crefs++;
      ret = OK;
    }

  leave_critical_section(flags);
  usbhost_givesem(&priv->exclsem);
  return ret;
}

/****************************************************************************
 * Name: usbhost_shutdown
 *
 * Description:
 *   Disable the USART.  This method is called when the serial
 *   port is closed
 *
 ****************************************************************************/

static void usbhost_shutdown(FAR struct uart_dev_s *uartdev)
{
  FAR struct usbhost_ft232r_s *priv;
  irqstate_t flags;

  uinfo("Entry\n");
  DEBUGASSERT(uartdev && uartdev->priv);
  priv = (FAR struct usbhost_ft232r_s *)uartdev->priv;

  /* Decrement the reference count on the block driver */

  DEBUGASSERT(priv->crefs > 1);
  usbhost_forcetake(&priv->exclsem);
  priv->crefs--;

  /* Release the semaphore.  The following operations when crefs == 1 are
   * safe because we know that there is no outstanding open references to
   * the block driver.
   */

  usbhost_givesem(&priv->exclsem);

  /* We need to disable interrupts momentarily to assure that there are
   * no asynchronous disconnect events.
   */

  flags = enter_critical_section();

  /* Check if the USB FTDI device is still connected.  If the
   * FTDI device is not connected and the reference count just
   * decremented to one, then unregister the block driver and free
   * the class instance.
   */

  if (priv->crefs <= 1 && priv->disconnected)
    {
      /* Destroy the class instance */

      DEBUGASSERT(priv->crefs == 1);
      usbhost_destroy(priv);
    }

  leave_critical_section(flags);
}

/****************************************************************************
 * Name: usbhost_attach
 *
 * Description:
 *   Configure the USART to operation in interrupt driven mode.  This method
 *   is called when the serial port is opened.  Normally, this is just after
 *   the setup() method is called, however, the serial console may operate in
 *   a non-interrupt driven mode during the boot phase.
 *
 *   RX and TX interrupts are not enabled when by the attach method (unless
 *   the hardware supports multiple levels of interrupt enabling).  The RX
 *   and TX interrupts are not enabled until the txint() and rxint() methods
 *   are called.
 *
 ****************************************************************************/

static int usbhost_attach(FAR struct uart_dev_s *uartdev)
{
  return OK;
}

/****************************************************************************
 * Name: usbhost_detach
 *
 * Description:
 *   Detach USART interrupts.  This method is called when the serial port is
 *   closed normally just before the shutdown method is called.  The
 *   exception is the serial console which is never shutdown.
 *
 ****************************************************************************/

static void usbhost_detach(FAR struct uart_dev_s *uartdev)
{
}

/****************************************************************************
 * Name: usbhost_ioctl
 *
 * Description:
 *   All ioctl calls will be routed through this method
 *
 ****************************************************************************/

static int usbhost_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode *inode;
  FAR struct usbhost_ft232r_s *priv;
  FAR struct uart_dev_s *uartdev;
  int ret = 0;

  uinfo("Entry\n");
  DEBUGASSERT(filep && filep->f_inode);
  inode = filep->f_inode;

  DEBUGASSERT(inode && inode->i_private);
  uartdev = (FAR struct uart_dev_s *)inode->i_private;

  DEBUGASSERT(uartdev && uartdev->priv);
  priv  = (FAR struct usbhost_ft232r_s *)uartdev->priv;

  /* Check if the FTDI device is still connected */

  if (priv->disconnected)
    {
      /* No... the serial device has been disconnecgted.  Refuse to process
       * any ioctl commands.
       */

      ret = -ENODEV;
    }
  else
    {
      /* Process the IOCTL by command */

      ret = usbhost_takesem(&priv->exclsem);
      if (ret < 0)
        {
          return ret;
        }

      switch (cmd)
        {
#ifdef CONFIG_SERIAL_TIOCSERGSTRUCT
        case TIOCSERGSTRUCT:
          {
            FAR struct usbhost_ft232r_s *user =
              (FAR struct usbhost_ft232r_s *)arg;
            if (!user)
              {
                ret = -EINVAL;
              }
            else
              {
                memcpy(user, uartdev, sizeof(struct usbhost_ft232r_s));
              }
          }
          break;
#endif

#ifdef CONFIG_SERIAL_TERMIOS
        case TCGETS:
          {
            FAR struct termios *termiosp = (FAR struct termios *)arg;

            if (!termiosp)
              {
                ret = -EINVAL;
                break;
              }

            cfsetispeed(termiosp, priv->baud);

            termiosp->c_cflag =
              ((priv->parity != 0) ? PARENB : 0) |
              ((priv->parity == 1) ? PARODD : 0) |
              ((priv->stop2) ? CSTOPB : 0)
#ifdef CONFIG_SERIAL_OFLOWCONTROL
              | ((priv->oflow) ? CCTS_OFLOW : 0)
#endif
#ifdef CONFIG_SERIAL_IFLOWCONTROL
              | ((priv->iflow) ? CRTS_IFLOW : 0)
#endif
              ;

            switch (priv->nbits)
              {
              case 7:
                termiosp->c_cflag |= CS7;
                break;

              default:
              case 8:
                termiosp->c_cflag |= CS8;
                break;
              }
          }
          break;

        case TCSETS:
          {
            FAR struct termios *termiosp = (FAR struct termios *)arg;
#ifdef CONFIG_SERIAL_IFLOWCONTROL
            bool iflow;
#endif

            if (!termiosp)
              {
                ret = -EINVAL;
                break;
              }

            if (termiosp->c_cflag & PARENB)
              {
                priv->parity = (termiosp->c_cflag & PARODD) ? 1 : 2;
              }
            else
               {
                priv->parity = 0;
              }

            priv->stop2 = (termiosp->c_cflag & CSTOPB) != 0;
#ifdef CONFIG_SERIAL_OFLOWCONTROL
            priv->oflow = (termiosp->c_cflag & CCTS_OFLOW) != 0;
#endif
#ifdef CONFIG_SERIAL_IFLOWCONTROL
            iflow       = priv->iflow;
            priv->iflow = (termiosp->c_cflag & CRTS_IFLOW) != 0;
#endif

            switch (termiosp->c_cflag & CSIZE)
              {
              case CS7:
                priv->nbits = 7;
                break;

              default:
              case CS8:
                priv->nbits = 8;
                break;
              }

            /* Note that only cfgetispeed is used because we have knowledge
             * that only one speed is supported.
             */

            priv->baud = cfgetispeed(termiosp);

#ifdef CONFIG_SERIAL_IFLOWCONTROL
            /* Set RTS if input flow control changed */

            if (iflow != !priv->iflow)
              {
                 priv->rts = true;
              }
#endif

            ret = ft232r_setbaud(priv);
            if (ret < 0)
              {
                uerr("ERROR: ft232r_setbaud failed: %d\n", ret);
              }

            ret = ft232r_setdata(priv);
            if (ret < 0)
              {
                uerr("ERROR: ft232r_setdata failed: %d\n", ret);
              }

            ret = ft232r_setflowctrl(priv);
            if (ret < 0)
              {
                uerr("ERROR: ft232r_setflowctrl failed: %d\n", ret);
              }
          }

          break;

#endif /* CONFIG_SERIAL_TERMIOS */

        default:
          ret = -ENOTTY;
          break;
        }

      usbhost_givesem(&priv->exclsem);
    }

  return ret;
}

/****************************************************************************
 * Name: usbhost_rxint
 *
 * Description:
 *   Call to enable or disable RX interrupts
 *
 ****************************************************************************/

static void usbhost_rxint(FAR struct uart_dev_s *uartdev, bool enable)
{
  FAR struct usbhost_ft232r_s *priv;
  int ret;

  DEBUGASSERT(uartdev && uartdev->priv);
  priv = (FAR struct usbhost_ft232r_s *)uartdev->priv;

  /* Are we enabling or disabling RX reception? */

  if (enable && !priv->rxena)
    {
      /* Cancel any pending, delayed RX data reception work */

      work_cancel(LPWORK, &priv->rxwork);

      /* Restart immediate RX data reception work (unless RX flow control
       * is in effect.
       */

#ifdef CONFIG_SERIAL_IFLOWCONTROL
      if (priv->rts)
#endif
        {
          ret = work_queue(LPWORK, &priv->rxwork,
                           usbhost_rxdata_work, priv, 0);
          DEBUGASSERT(ret >= 0);
          UNUSED(ret);
        }
    }
  else if (!enable && priv->rxena)
    {
      /* Cancel any pending RX data reception work */

      work_cancel(LPWORK, &priv->rxwork);
    }

  /* Save the new RX enable state */

  priv->rxena = enable;
}

/****************************************************************************
 * Name: usbhost_rxavailable
 *
 * Description:
 *   Return true if the receive buffer is not empty
 *
 ****************************************************************************/

static bool usbhost_rxavailable(FAR struct uart_dev_s *uartdev)
{
  FAR struct usbhost_ft232r_s *priv;

  DEBUGASSERT(uartdev && uartdev->priv);
  priv = (FAR struct usbhost_ft232r_s *)uartdev->priv;
  return (priv->nrxbytes > 0);
}

/****************************************************************************
 * Name: usbhost_rxflowcontrol
 *
 * Description:
 *   Called when Rx buffer is full (or exceeds configured watermark levels
 *   if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is defined).
 *   Return true if UART activated RX flow control to block more incoming
 *   data
 *
 * Input Parameters:
 *   uartdev   - UART device instance
 *   nbuffered - the number of characters currently buffered
 *               (if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is
 *               not defined the value will be 0 for an empty buffer or the
 *               defined buffer size for a full buffer)
 *   upper     - true indicates the upper watermark was crossed where
 *               false indicates the lower watermark has been crossed
 *
 * Returned Value:
 *   true if RX flow control activated.
 *
 ****************************************************************************/

#ifdef CONFIG_SERIAL_IFLOWCONTROL
static bool usbhost_rxflowcontrol(FAR struct uart_dev_s *uartdev,
                                  unsigned int nbuffered, bool upper)
{
  FAR struct usbhost_ft232r_s *priv;
  bool newrts;
  int ret;

  DEBUGASSERT(uartdev && uartdev->priv);
  priv = (FAR struct usbhost_ft232r_s *)uartdev->priv

  /* Is RX flow control enabled? */

  if (!priv->iflow)
    {
      /* Now.. make sure that RTS is set */

      priv->rts = true;
      return false;
    }

  /* Are we enabling or disabling RX flow control? */

  if (priv->rts && upper)
    {
      /* RX flow control is not in effect (RTS is true) but we have just
       * crossed the upper threshold, meaning that we should now clear
       * RTS.
       */

      priv->rts = false;

      /* Cancel any pending RX data reception work */

      work_cancel(LPWORK, &priv->rxwork);
      return true;
    }
  else if (!priv->rts && !upper)
    {
      /* RX flow control is in effect (RTS is false) and we have just
       * crossed the lower threshold, meaning that we should now set
       * RTS.
       */

       priv->rts = true;

      /* Restart RX data reception work flow unless RX reception is
       * disabled.
       */

      if (priv->rxena && work_available(&priv->rxwork))
        {
          ret = work_queue(LPWORK, &priv->rxwork,
                           usbhost_rxdata_work, priv, 0);
          DEBUGASSERT(ret >= 0);
          UNUSED(ret);
        }

      return false;
    }
}
#endif

/****************************************************************************
 * Name: usbhost_txint
 *
 * Description:
 *   Call to enable or disable TX interrupts
 *
 ****************************************************************************/

static void usbhost_txint(FAR struct uart_dev_s *uartdev, bool enable)
{
  FAR struct usbhost_ft232r_s *priv;
  int ret;

  DEBUGASSERT(uartdev && uartdev->priv);
  priv = (FAR struct usbhost_ft232r_s *)uartdev->priv;

  /* Are we enabling or disabling TX transmission? */

  if (enable && !priv->txena)
    {
      /* Cancel any pending, delayed TX data transmission work */

      work_cancel(LPWORK, &priv->txwork);

      /* Restart immediate TX data transmission work */

      ret = work_queue(LPWORK, &priv->txwork,
                       usbhost_txdata_work, priv, 0);
      DEBUGASSERT(ret >= 0);
      UNUSED(ret);
    }
  else if (!enable && priv->txena)
    {
      /* Cancel any pending TX data transmission work */

      work_cancel(LPWORK, &priv->txwork);
    }

  /* Save the new TX enable state */

  priv->txena = enable;
}

/****************************************************************************
 * Name: usbhost_txready
 *
 * Description:
 *   Return true if the transmit data register is not full.
 *
 ****************************************************************************/

static bool usbhost_txready(FAR struct uart_dev_s *uartdev)
{
  return true;
}

/****************************************************************************
 * Name: usbhost_txempty
 *
 * Description:
 *   Return true if the transmit data buffer is empty
 *
 ****************************************************************************/

static bool usbhost_txempty(FAR struct uart_dev_s *uartdev)
{
  return true;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: usbhost_ft232r_initialize
 *
 * Description:
 *   Initialize the USB host FTDI. This function should be called
 *   by platform-specific code in order to initialize and register support
 *   for the FT232.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   On success this function will return zero (OK);  A negated errno value
 *   will be returned on failure.
 *
 ****************************************************************************/

int usbhost_ft232r_initialize(void)
{
  /* If we have been configured to use pre-allocated FTDI class instances,
   * then place all of the pre-allocated USB host FTDI class instances
   * into a free list.
   */

#if CONFIG_USBHOST_FT232R_NPREALLOC > 0
  FAR struct usbhost_freestate_s *entry;
  int i;

  g_freelist = NULL;
  for (i = 0; i < CONFIG_USBHOST_FT232R_NPREALLOC; i++)
    {
      entry        = (FAR struct usbhost_freestate_s *)&g_prealloc[i];
      entry->flink = g_freelist;
      g_freelist   = entry;
    }
#endif

  /* Advertise our availability to support (certain) FTDI devices */

  return usbhost_registerclass(&g_ft232r);
}

#endif /* CONFIG_USBHOST_FT232R */