incubator-nuttx/drivers/usbhost/usbhost_ft232r.c

2756 lines
78 KiB
C

/****************************************************************************
* 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/mutex.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 */
mutex_t lock; /* 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
****************************************************************************/
/* 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_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 < 2)
{
/* 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 mutex */
nxmutex_destroy(&priv->lock);
/* 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 mutex
* (this works okay in the interrupt context)
*/
nxmutex_init(&priv->lock);
/* 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 = nxmutex_lock(&priv->lock);
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!
*/
nxmutex_unlock(&priv->lock);
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 = nxmutex_lock(&priv->lock);
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);
nxmutex_unlock(&priv->lock);
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);
nxmutex_lock(&priv->lock);
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.
*/
nxmutex_unlock(&priv->lock);
/* 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");
inode = filep->f_inode;
DEBUGASSERT(inode->i_private);
uartdev = 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 = nxmutex_lock(&priv->lock);
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;
}
nxmutex_unlock(&priv->lock);
}
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 */