2793 lines
79 KiB
C
2793 lines
79 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/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;
|
|
|
|
*divisor = (uint32_t)frac;
|
|
int rem = (frac - *divisor) * 1000.0;
|
|
|
|
if (rem > 0)
|
|
{
|
|
int i;
|
|
int j = 0;
|
|
int closest = rem;
|
|
|
|
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.
|
|
*/
|
|
|
|
double 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)
|
|
{
|
|
/* Build up line coding */
|
|
|
|
uint16_t 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;
|
|
}
|
|
|
|
int 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 */
|
|
|
|
rxbuf->buffer[rxbuf->head] = priv->inbuf[rxndx + 2]; /* +2 to account for the two status byes */
|
|
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)
|
|
{
|
|
dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */
|
|
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 */
|