4528 lines
132 KiB
C
4528 lines
132 KiB
C
/****************************************************************************
|
|
* arch/mips/src/pic32mx/pic32mx_usbdev.c
|
|
*
|
|
* Copyright (C) 2011-2014 Gregory Nutt. All rights reserved.
|
|
* Author: Gregory Nutt <gnutt@nuttx.org>
|
|
*
|
|
* References:
|
|
* This file derives from the STM32 USB device driver with modifications
|
|
* based on additional information from:
|
|
*
|
|
* - "USB On-The-Go (OTG)", DS61126E, Microchip Technology Inc., 2009
|
|
* - Sample code provided with the Sure Electronics PIC32 board
|
|
* (which seems to have derived from Microchip PICDEM PIC18 code).
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/wdog.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/usb/usb.h>
|
|
#include <nuttx/usb/usbdev.h>
|
|
#include <nuttx/usb/usbdev_trace.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
|
|
#include "up_arch.h"
|
|
#include "pic32mx.h"
|
|
#include "pic32mx-usbotg.h"
|
|
|
|
#if defined(CONFIG_USBDEV) && defined(CONFIG_PIC32MX_USBDEV)
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
/* Configuration ************************************************************/
|
|
|
|
#ifndef CONFIG_USBDEV_EP0_MAXSIZE
|
|
# define CONFIG_USBDEV_EP0_MAXSIZE 64
|
|
#endif
|
|
|
|
/* Disable this logic because it is buggy. It works most of the time but
|
|
* has some lurking issues that keep this higher performance solution from
|
|
* being usable.
|
|
*/
|
|
|
|
#undef CONFIG_USBDEV_NOREADAHEAD /* Makes no difference */
|
|
|
|
#undef CONFIG_USBDEV_NOWRITEAHEAD
|
|
#define CONFIG_USBDEV_NOWRITEAHEAD 1 /* Fixes some problems with IN transfers */
|
|
|
|
/* Interrupts ***************************************************************/
|
|
/* Initial interrupt sets */
|
|
|
|
#ifdef CONFIG_USB_SOFINTS
|
|
# define USB_SOF_INTERRUPT USB_INT_SOF
|
|
#else
|
|
# define USB_SOF_INTERRUPT 0
|
|
#endif
|
|
|
|
#define ERROR_INTERRUPTS (USB_EINT_PID | USB_EINT_CRC5 | USB_EINT_EOF | \
|
|
USB_EINT_CRC16 | USB_EINT_DFN8 | USB_EINT_BTO | \
|
|
USB_EINT_BTS)
|
|
#define NORMAL_INTERRUPTS (USB_INT_URST | USB_INT_UERR | USB_SOF_INTERRUPT | \
|
|
USB_INT_TRN | USB_INT_IDLE | USB_INT_STALL)
|
|
|
|
/* Endpoints ****************************************************************/
|
|
/* Endpoint identifiers. The PIC32MX supports up to 16 mono-directional or 8
|
|
* bidirectional endpoints. However, when you take into account PMA buffer
|
|
* usage (see below) and the fact that EP0 is bidirectional, then there is
|
|
* a functional limitation of EP0 + 5 mono-directional endpoints = 6. We'll
|
|
* define PIC32MX_NENDPOINTS to be 8, however, because that is how many
|
|
* endpoint register sets there are.
|
|
*/
|
|
|
|
#define PIC32MX_NENDPOINTS (16)
|
|
#define EP0 (0)
|
|
|
|
#define PIC32MX_ENDP_BIT(ep) (1 << (ep))
|
|
#define PIC32MX_ENDP_ALLSET 0xffff
|
|
|
|
/* BDT Table Indexing. The BDT is addressed in the hardware as follows:
|
|
*
|
|
* Bits 9-31: These come the BDT address bits written into the BDTP3,
|
|
* BDTP2, and BDTP1 registers
|
|
* Bits 5-8: The endpoint number
|
|
* Bit 4: Direction:
|
|
* 1 = Transmit: SETUP/OUT for host, IN for function
|
|
* 0 = Receive: IN for host, SETUP/OUT for function
|
|
* Bit 3: PPBI, the ping point buffer index bit (0=EVEN, 1=ODD)
|
|
* Bits 0-2: Supports 8-byte BDT entries
|
|
*/
|
|
|
|
#define EP0_OUT_EVEN (0)
|
|
#define EP0_OUT_ODD (1)
|
|
#define EP0_IN_EVEN (2)
|
|
#define EP0_IN_ODD (3)
|
|
#define EP_OUT_EVEN(ep) ((int)(ep) << 2)
|
|
#define EP_OUT_ODD(ep) (((int)(ep) << 2) + 1)
|
|
#define EP_IN_EVEN(ep) (((int)(ep) << 2) + 2)
|
|
#define EP_IN_ODD(ep) (((int)(ep) << 2) + 3)
|
|
|
|
#define EP(ep,dir,pp) (((int)(ep) << 2) + ((int)(dir) << 1) + (int)(pp))
|
|
#define EP_DIR_OUT 0
|
|
#define EP_DIR_IN 1
|
|
#define EP_PP_EVEN 0
|
|
#define EP_PP_ODD 1
|
|
|
|
/* Packet sizes. We use a fixed 64 max packet size for all endpoint types */
|
|
|
|
#define PIC32MX_MAXPACKET_SHIFT (6)
|
|
#define PIC32MX_MAXPACKET_SIZE (1 << (PIC32MX_MAXPACKET_SHIFT))
|
|
|
|
#define PIC32MX_EP0MAXPACKET PIC32MX_MAXPACKET_SIZE
|
|
|
|
/* Endpoint register initialization parameters */
|
|
|
|
#define PIC32MX_EP_CONTROL (USB_EP_EPHSHK|USB_EP_EPTXEN|USB_EP_EPRXEN)
|
|
#define PIC32MX_EP_BULKIN (USB_EP_EPTXEN|USB_EP_EPCONDIS|USB_EP_EPHSHK)
|
|
#define PIC32MX_EP_BULKOUT (USB_EP_EPRXEN|USB_EP_EPCONDIS|USB_EP_EPHSHK)
|
|
#define PIC32MX_EP_INTIN (USB_EP_EPTXEN|USB_EP_EPCONDIS|USB_EP_EPHSHK)
|
|
#define PIC32MX_EP_INTOUT (USB_EP_EPRXEN|USB_EP_EPCONDIS|USB_EP_EPHSHK)
|
|
#define PIC32MX_EP_ISOCIN (USB_EP_EPTXEN|USB_EP_EPCONDIS)
|
|
#define PIC32MX_EP_ISOCOUT (USB_EP_EPRXEN|USB_EP_EPCONDIS)
|
|
|
|
/* USB-related masks */
|
|
|
|
#define REQRECIPIENT_MASK (USB_REQ_TYPE_MASK | USB_REQ_RECIPIENT_MASK)
|
|
|
|
/* Request queue operations *************************************************/
|
|
|
|
#define pic32mx_rqempty(q) ((q)->head == NULL)
|
|
#define pic32mx_rqhead(q) ((q)->head)
|
|
#define pic32mx_rqtail(q) ((q)->tail)
|
|
|
|
#define RESTART_DELAY (150 * CLOCKS_PER_SEC / 1000)
|
|
|
|
/* USB trace ****************************************************************/
|
|
/* Trace error codes */
|
|
|
|
#define PIC32MX_TRACEERR_ALLOCFAIL 0x0001
|
|
#define PIC32MX_TRACEERR_BADCLEARFEATURE 0x0002
|
|
#define PIC32MX_TRACEERR_BADDEVGETSTATUS 0x0003
|
|
#define PIC32MX_TRACEERR_BADEPGETSTATUS 0x0004
|
|
#define PIC32MX_TRACEERR_BADEPNO 0x0005
|
|
#define PIC32MX_TRACEERR_BADEPTYPE 0x0006
|
|
#define PIC32MX_TRACEERR_BADGETCONFIG 0x0007
|
|
#define PIC32MX_TRACEERR_BADGETSETDESC 0x0008
|
|
#define PIC32MX_TRACEERR_BADGETSTATUS 0x0009
|
|
#define PIC32MX_TRACEERR_BADSETADDRESS 0x000a
|
|
#define PIC32MX_TRACEERR_BADSETCONFIG 0x000b
|
|
#define PIC32MX_TRACEERR_BADSETFEATURE 0x000c
|
|
#define PIC32MX_TRACEERR_BINDFAILED 0x000d
|
|
#define PIC32MX_TRACEERR_DISPATCHSTALL 0x000e
|
|
#define PIC32MX_TRACEERR_DRIVER 0x000f
|
|
#define PIC32MX_TRACEERR_DRIVERREGISTERED 0x0010
|
|
#define PIC32MX_TRACEERR_EP0SETUPSTALLED 0x0011
|
|
#define PIC32MX_TRACEERR_EPDISABLED 0x0012
|
|
#define PIC32MX_TRACEERR_EPOUTNULLPACKET 0x0013
|
|
#define PIC32MX_TRACEERR_EPRESERVE 0x0014
|
|
#define PIC32MX_TRACEERR_INVALIDCTRLREQ 0x0015
|
|
#define PIC32MX_TRACEERR_INVALIDPARMS 0x0016
|
|
#define PIC32MX_TRACEERR_IRQREGISTRATION 0x0017
|
|
#define PIC32MX_TRACEERR_NOTCONFIGURED 0x0018
|
|
#define PIC32MX_TRACEERR_REQABORTED 0x0019
|
|
#define PIC32MX_TRACEERR_INVALIDSTATE 0x001a
|
|
|
|
/* Trace interrupt codes */
|
|
|
|
#define PIC32MX_TRACEINTID_CLEARFEATURE 0x0001
|
|
#define PIC32MX_TRACEINTID_DEVGETSTATUS 0x0002
|
|
#define PIC32MX_TRACEINTID_DISPATCH 0x0003
|
|
#define PIC32MX_TRACEINTID_EP0IN 0x0004
|
|
#define PIC32MX_TRACEINTID_EP0INDONE 0x0005
|
|
#define PIC32MX_TRACEINTID_EP0OUTDONE 0x0006
|
|
#define PIC32MX_TRACEINTID_EP0SETUPDONE 0x0007
|
|
#define PIC32MX_TRACEINTID_EP0SETUPSETADDRESS 0x0008
|
|
#define PIC32MX_TRACEINTID_EP0ADDRESSSET 0x0009
|
|
#define PIC32MX_TRACEINTID_EPGETSTATUS 0x000a
|
|
#define PIC32MX_TRACEINTID_EPINDONE 0x000b
|
|
#define PIC32MX_TRACEINTID_EPINQEMPTY 0x000c
|
|
#define PIC32MX_TRACEINTID_EPOUTDONE 0x000d
|
|
#define PIC32MX_TRACEINTID_EPOUTQEMPTY 0x000e
|
|
#define PIC32MX_TRACEINTID_SOF 0x000f
|
|
#define PIC32MX_TRACEINTID_GETCONFIG 0x0010
|
|
#define PIC32MX_TRACEINTID_GETSETDESC 0x0011
|
|
#define PIC32MX_TRACEINTID_GETSETIF 0x0012
|
|
#define PIC32MX_TRACEINTID_GETSTATUS 0x0013
|
|
#define PIC32MX_TRACEINTID_IFGETSTATUS 0x0014
|
|
#define PIC32MX_TRACEINTID_TRNC 0x0015
|
|
#define PIC32MX_TRACEINTID_TRNCS 0x0016
|
|
#define PIC32MX_TRACEINTID_INTERRUPT 0x0017
|
|
#define PIC32MX_TRACEINTID_NOSTDREQ 0x0018
|
|
#define PIC32MX_TRACEINTID_RESET 0x0019
|
|
#define PIC32MX_TRACEINTID_SETCONFIG 0x001a
|
|
#define PIC32MX_TRACEINTID_SETFEATURE 0x001b
|
|
#define PIC32MX_TRACEINTID_IDLE 0x001c
|
|
#define PIC32MX_TRACEINTID_SYNCHFRAME 0x001d
|
|
#define PIC32MX_TRACEINTID_WKUP 0x001e
|
|
#define PIC32MX_TRACEINTID_T1MSEC 0x001f
|
|
#define PIC32MX_TRACEINTID_OTGID 0x0020
|
|
#define PIC32MX_TRACEINTID_STALL 0x0021
|
|
#define PIC32MX_TRACEINTID_UERR 0x0022
|
|
#define PIC32MX_TRACEINTID_SUSPENDED 0x0023
|
|
#define PIC32MX_TRACEINTID_WAITRESET 0x0024
|
|
|
|
/* Misc Helper Macros *******************************************************/
|
|
|
|
#define PHYS_ADDR(va) ((uint32_t)(va) & 0x1fffffff)
|
|
#define VIRT_ADDR(pa) (KSEG1_BASE | (uint32_t)(pa))
|
|
|
|
/* Ever-present MIN and MAX macros */
|
|
|
|
#ifndef MIN
|
|
# define MIN(a,b) (a < b ? a : b)
|
|
#endif
|
|
|
|
#ifndef MAX
|
|
# define MAX(a,b) (a > b ? a : b)
|
|
#endif
|
|
|
|
/* Byte ordering in host-based values */
|
|
|
|
#ifdef CONFIG_ENDIAN_BIG
|
|
# define LSB 1
|
|
# define MSB 0
|
|
#else
|
|
# define LSB 0
|
|
# define MSB 1
|
|
#endif
|
|
|
|
/* Debug ********************************************************************/
|
|
/* CONFIG_PIC32MX_USBDEV_REGDEBUG enables dumping of all low-level register
|
|
* access and BDT accesses. Normally, this generates so much debug output
|
|
* that USB may not even be functional.
|
|
*/
|
|
|
|
#ifdef CONFIG_PIC32MX_USBDEV_REGDEBUG
|
|
|
|
# undef CONFIG_PIC32MX_USBDEV_BDTDEBUG
|
|
# define CONFIG_PIC32MX_USBDEV_BDTDEBUG 1
|
|
|
|
# define regerr llerr
|
|
# define regwarn llwarn
|
|
# define reginfo llinfo
|
|
|
|
#else
|
|
|
|
# define pic32mx_getreg(addr) getreg16(addr)
|
|
# define pic32mx_putreg(val,addr) putreg16(val,addr)
|
|
# define regerr(x...)
|
|
# define regwarn(x...)
|
|
# define reginfo(x...)
|
|
|
|
#endif
|
|
|
|
/* CONFIG_PIC32MX_USBDEV_BDTDEBUG dumps most BDT settings */
|
|
|
|
#ifdef CONFIG_PIC32MX_USBDEV_BDTDEBUG
|
|
|
|
# define bdterr llerr
|
|
# define bdtwarn llwarn
|
|
# define bdtinfo llinfo
|
|
|
|
#else
|
|
|
|
# define bdterr(x...)
|
|
# define bdtwarn(x...)
|
|
# define bdtinfo(x...)
|
|
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Type Definitions
|
|
****************************************************************************/
|
|
|
|
/* Overvall device state */
|
|
|
|
enum pic32mx_devstate_e
|
|
{
|
|
DEVSTATE_DETACHED = 0, /* Not connected to a host */
|
|
DEVSTATE_ATTACHED, /* Connected to a host */
|
|
DEVSTATE_POWERED, /* Powered */
|
|
DEVSTATE_DEFAULT, /* Default state */
|
|
DEVSTATE_ADDRPENDING, /* Waiting for an address */
|
|
DEVSTATE_ADDRESS, /* Address received */
|
|
DEVSTATE_CONFIGURED, /* Configuration received */
|
|
};
|
|
|
|
/* The various states of the control pipe */
|
|
|
|
enum pic32mx_ctrlstate_e
|
|
{
|
|
CTRLSTATE_WAITSETUP = 0, /* No request in progress, waiting for setup */
|
|
CTRLSTATE_RDREQUEST, /* Read request (OUT) in progress */
|
|
CTRLSTATE_WRREQUEST, /* Write request (IN) in progress */
|
|
CTRLSTATE_STALL, /* EP0 stall requested */
|
|
CTRLSTATE_STALLED /* EP0 is stalled */
|
|
};
|
|
|
|
union wb_u
|
|
{
|
|
uint16_t w;
|
|
uint8_t b[2];
|
|
};
|
|
|
|
/* A container for a request so that the request make be retained in a
|
|
* singly-linked list.
|
|
*/
|
|
|
|
struct pic32mx_req_s
|
|
{
|
|
struct usbdev_req_s req; /* Standard USB request */
|
|
#ifdef CONFIG_USBDEV_NOWRITEAHEAD
|
|
uint16_t inflight[1]; /* The number of bytes "in-flight" */
|
|
#else
|
|
uint16_t inflight[2]; /* The number of bytes "in-flight" */
|
|
#endif
|
|
struct pic32mx_req_s *flink; /* Supports a singly linked list */
|
|
};
|
|
|
|
/* This structure represents the 'head' of a singly linked list of requests */
|
|
|
|
struct pic32mx_queue_s
|
|
{
|
|
struct pic32mx_req_s *head; /* Head of the request queue */
|
|
struct pic32mx_req_s *tail; /* Tail of the request queue */
|
|
};
|
|
|
|
/* This is the internal representation of an endpoint */
|
|
|
|
struct pic32mx_ep_s
|
|
{
|
|
/* Common endpoint fields. This must be the first thing defined in the
|
|
* structure so that it is possible to simply cast from struct usbdev_ep_s
|
|
* to struct pic32mx_ep_s.
|
|
*/
|
|
|
|
struct usbdev_ep_s ep; /* Standard endpoint structure */
|
|
|
|
/* PIC32MX-specific fields */
|
|
|
|
struct pic32mx_usbdev_s *dev; /* Reference to private driver data */
|
|
struct pic32mx_queue_s pend; /* List of pending (inactive) requests for this endpoint */
|
|
struct pic32mx_queue_s active; /* List of active requests for this endpoint */
|
|
uint8_t stalled:1; /* true: Endpoint is stalled */
|
|
uint8_t halted:1; /* true: Endpoint feature halted */
|
|
uint8_t txnullpkt:1; /* Null packet needed at end of TX transfer */
|
|
uint8_t txdata1:1; /* Data0/1 of next TX transfer */
|
|
uint8_t rxdata1:1; /* Data0/1 of next RX transfer */
|
|
volatile struct usbotg_bdtentry_s *bdtin; /* BDT entry for the IN transaction */
|
|
volatile struct usbotg_bdtentry_s *bdtout; /* BDT entry for the OUT transaction */
|
|
};
|
|
|
|
struct pic32mx_usbdev_s
|
|
{
|
|
/* Common device fields. This must be the first thing defined in the
|
|
* structure so that it is possible to simply cast from struct usbdev_s
|
|
* to structpic32mx_usbdev_s.
|
|
*/
|
|
|
|
struct usbdev_s usbdev;
|
|
|
|
/* The bound device class driver */
|
|
|
|
struct usbdevclass_driver_s *driver;
|
|
|
|
/* PIC32MX-specific fields */
|
|
|
|
struct usb_ctrlreq_s ctrl; /* Last EP0 request */
|
|
uint8_t devstate; /* Driver state (see enum pic32mx_devstate_e) */
|
|
uint8_t ctrlstate; /* Control EP state (see enum pic32mx_ctrlstate_e) */
|
|
uint8_t selfpowered:1; /* 1: Device is self powered */
|
|
uint8_t rwakeup:1; /* 1: Device supports remote wakeup */
|
|
uint8_t attached:1; /* Device is attached to the host */
|
|
uint8_t ep0done:1; /* EP0 OUT already prepared */
|
|
uint8_t rxbusy:1; /* EP0 OUT data transfer in progress */
|
|
uint16_t epavail; /* Bitset of available endpoints */
|
|
uint16_t epstalled; /* Bitset of stalled endpoints */
|
|
WDOG_ID wdog; /* Supports the restart delay */
|
|
|
|
/* The endpoint list */
|
|
|
|
struct pic32mx_ep_s eplist[PIC32MX_NENDPOINTS];
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Register operations ******************************************************/
|
|
|
|
#ifdef CONFIG_PIC32MX_USBDEV_REGDEBUG
|
|
static uint16_t pic32mx_getreg(uint32_t addr);
|
|
static void pic32mx_putreg(uint16_t val, uint32_t addr);
|
|
#endif
|
|
|
|
/* Suspend/Resume Helpers ***************************************************/
|
|
|
|
static void pic32mx_suspend(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_resume(struct pic32mx_usbdev_s *priv);
|
|
|
|
/* Request Queue Management *************************************************/
|
|
|
|
static struct pic32mx_req_s *pic32mx_remfirst(struct pic32mx_queue_s *queue);
|
|
static struct pic32mx_req_s *pic32mx_remlast(struct pic32mx_queue_s *queue);
|
|
static void pic32mx_addlast(struct pic32mx_queue_s *queue,
|
|
struct pic32mx_req_s *req);
|
|
static void pic32mx_addfirst(struct pic32mx_queue_s *queue,
|
|
struct pic32mx_req_s *req);
|
|
|
|
/* Request Helpers **********************************************************/
|
|
|
|
static void pic32mx_reqreturn(struct pic32mx_ep_s *privep,
|
|
struct pic32mx_req_s *privreq, int16_t result);
|
|
static void pic32mx_reqcomplete(struct pic32mx_ep_s *privep,
|
|
int16_t result);
|
|
static void pic32mx_epwrite(struct pic32mx_ep_s *privep,
|
|
volatile struct usbotg_bdtentry_s *bdt,
|
|
const uint8_t *src, uint32_t nbytes);
|
|
static void pic32mx_wrcomplete(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep);
|
|
static void pic32mx_rqrestart(int argc, uint32_t arg1, ...);
|
|
static void pic32mx_delayedrestart(struct pic32mx_usbdev_s *priv,
|
|
uint8_t epno);
|
|
static void pic32mx_rqstop(struct pic32mx_ep_s *privep);
|
|
static int pic32mx_wrstart(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep);
|
|
static int pic32mx_wrrequest(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep);
|
|
static int pic32mx_rdcomplete(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep);
|
|
static int pic32mx_ep0rdsetup(struct pic32mx_usbdev_s *priv,
|
|
uint8_t *dest, int readlen);
|
|
static int pic32mx_rdsetup(struct pic32mx_ep_s *privep, uint8_t *dest,
|
|
int readlen);
|
|
static int pic32mx_rdrequest(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep);
|
|
static void pic32mx_cancelrequests(struct pic32mx_ep_s *privep,
|
|
int16_t result);
|
|
|
|
/* Interrupt level processing ***********************************************/
|
|
|
|
static void pic32mx_dispatchrequest(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_ep0stall(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_eptransfer(struct pic32mx_usbdev_s *priv, uint8_t epno,
|
|
uint16_t ustat);
|
|
static void pic32mx_ep0nextsetup(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_ep0rdcomplete(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_ep0setup(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_ep0outcomplete(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_ep0incomplete(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_ep0transfer(struct pic32mx_usbdev_s *priv,
|
|
uint16_t ustat);
|
|
static int pic32mx_interrupt(int irq, void *context);
|
|
|
|
/* Endpoint helpers *********************************************************/
|
|
|
|
static inline struct pic32mx_ep_s *
|
|
pic32mx_epreserve(struct pic32mx_usbdev_s *priv, uint8_t epset);
|
|
static inline void
|
|
pic32mx_epunreserve(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep);
|
|
static inline bool
|
|
pic32mx_epreserved(struct pic32mx_usbdev_s *priv, int epno);
|
|
static void pic32mx_ep0configure(struct pic32mx_usbdev_s *priv);
|
|
|
|
/* Endpoint operations ******************************************************/
|
|
|
|
static int pic32mx_epconfigure(struct usbdev_ep_s *ep,
|
|
const struct usb_epdesc_s *desc, bool last);
|
|
static int pic32mx_epdisable(struct usbdev_ep_s *ep);
|
|
static struct usbdev_req_s *
|
|
pic32mx_epallocreq(struct usbdev_ep_s *ep);
|
|
static void pic32mx_epfreereq(struct usbdev_ep_s *ep,
|
|
struct usbdev_req_s *);
|
|
static int pic32mx_epsubmit(struct usbdev_ep_s *ep,
|
|
struct usbdev_req_s *req);
|
|
static int pic32mx_epcancel(struct usbdev_ep_s *ep,
|
|
struct usbdev_req_s *req);
|
|
static int pic32mx_epbdtstall(struct usbdev_ep_s *ep, bool resume,
|
|
bool epin);
|
|
static int pic32mx_epstall(struct usbdev_ep_s *ep, bool resume);
|
|
|
|
/* USB device controller operations *****************************************/
|
|
|
|
static struct usbdev_ep_s *
|
|
pic32mx_allocep(struct usbdev_s *dev, uint8_t epno, bool in,
|
|
uint8_t eptype);
|
|
static void pic32mx_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep);
|
|
static int pic32mx_getframe(struct usbdev_s *dev);
|
|
static int pic32mx_wakeup(struct usbdev_s *dev);
|
|
static int pic32mx_selfpowered(struct usbdev_s *dev, bool selfpowered);
|
|
|
|
/* Initialization/Reset *****************************************************/
|
|
|
|
static void pic32mx_reset(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_attach(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_detach(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_swreset(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_hwreset(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_stateinit(struct pic32mx_usbdev_s *priv);
|
|
static void pic32mx_hwshutdown(struct pic32mx_usbdev_s *priv);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
/* Since there is only a single USB interface, all status information can be
|
|
* be simply retained in a single global instance.
|
|
*/
|
|
|
|
static struct pic32mx_usbdev_s g_usbdev;
|
|
|
|
static const struct usbdev_epops_s g_epops =
|
|
{
|
|
.configure = pic32mx_epconfigure,
|
|
.disable = pic32mx_epdisable,
|
|
.allocreq = pic32mx_epallocreq,
|
|
.freereq = pic32mx_epfreereq,
|
|
.submit = pic32mx_epsubmit,
|
|
.cancel = pic32mx_epcancel,
|
|
.stall = pic32mx_epstall,
|
|
};
|
|
|
|
static const struct usbdev_ops_s g_devops =
|
|
{
|
|
.allocep = pic32mx_allocep,
|
|
.freeep = pic32mx_freeep,
|
|
.getframe = pic32mx_getframe,
|
|
.wakeup = pic32mx_wakeup,
|
|
.selfpowered = pic32mx_selfpowered,
|
|
.pullup = pic32mx_usbpullup,
|
|
};
|
|
|
|
/* Buffer Descriptor Table. Four BDT entries per
|
|
*
|
|
* The BDT is addressed in the hardware as follows:
|
|
*
|
|
* Bits 9-31: These come the BDT address bits written into the BDTP3, BDTP2
|
|
* and BDTP1 registers
|
|
* Bits 5-8: The endpoint number
|
|
* Bit 4: Direction (0=IN/Tx, 1 = OUT/Rx)
|
|
* Bit 3: PPBI, the ping point buffer index bit.
|
|
* Bits 0-2: Supports 8-byte BDT entries
|
|
*/
|
|
|
|
static volatile struct usbotg_bdtentry_s g_bdt[4*PIC32MX_NENDPOINTS]
|
|
__attribute__ ((aligned(512)));
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Register Operations
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_getreg
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_PIC32MX_USBDEV_REGDEBUG
|
|
static uint16_t pic32mx_getreg(uint32_t addr)
|
|
{
|
|
static uint32_t prevaddr = 0;
|
|
static uint16_t preval = 0;
|
|
static uint32_t count = 0;
|
|
|
|
/* Read the value from the register */
|
|
|
|
uint16_t val = getreg16(addr);
|
|
|
|
/* Is this the same value that we read from the same register last time?
|
|
* Are we polling the register? If so, suppress some of the output.
|
|
*/
|
|
|
|
if (addr == prevaddr && val == preval)
|
|
{
|
|
if (count == 0xffffffff || ++count > 3)
|
|
{
|
|
if (count == 4)
|
|
{
|
|
reginfo("...\n");
|
|
}
|
|
|
|
return val;
|
|
}
|
|
}
|
|
|
|
/* No this is a new address or value */
|
|
|
|
else
|
|
{
|
|
/* Did we print "..." for the previous value? */
|
|
|
|
if (count > 3)
|
|
{
|
|
/* Yes.. then show how many times the value repeated */
|
|
|
|
reginfo("[repeats %d more times]\n", count-3);
|
|
}
|
|
|
|
/* Save the new address, value, and count */
|
|
|
|
prevaddr = addr;
|
|
preval = val;
|
|
count = 1;
|
|
}
|
|
|
|
/* Show the register value read */
|
|
|
|
reginfo("%08x->%04x\n", addr, val);
|
|
return val;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_putreg
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_PIC32MX_USBDEV_REGDEBUG
|
|
static void pic32mx_putreg(uint16_t val, uint32_t addr)
|
|
{
|
|
/* Show the register value being written */
|
|
|
|
reginfo("%08x<-%04x\n", addr, val);
|
|
|
|
/* Write the value */
|
|
|
|
putreg16(val, addr);
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Request Helpers
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: pic32mx_remfirst
|
|
****************************************************************************/
|
|
|
|
static struct pic32mx_req_s *pic32mx_remfirst(struct pic32mx_queue_s *queue)
|
|
{
|
|
struct pic32mx_req_s *ret = queue->head;
|
|
|
|
if (ret)
|
|
{
|
|
queue->head = ret->flink;
|
|
if (!queue->head)
|
|
{
|
|
queue->tail = NULL;
|
|
}
|
|
|
|
ret->flink = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_remlast
|
|
****************************************************************************/
|
|
|
|
static struct pic32mx_req_s *pic32mx_remlast(struct pic32mx_queue_s *queue)
|
|
{
|
|
struct pic32mx_req_s *prev;
|
|
struct pic32mx_req_s *ret = queue->tail;
|
|
|
|
ret = queue->tail;
|
|
if (ret)
|
|
{
|
|
if (queue->head == queue->tail)
|
|
{
|
|
queue->head = NULL;
|
|
queue->tail = NULL;
|
|
}
|
|
else
|
|
{
|
|
for (prev = queue->head;
|
|
prev && prev->flink != ret;
|
|
prev = prev->flink);
|
|
|
|
if (prev)
|
|
{
|
|
prev->flink = NULL;
|
|
queue->tail = prev;
|
|
}
|
|
}
|
|
|
|
ret->flink = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_addlast
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_addlast(struct pic32mx_queue_s *queue, struct pic32mx_req_s *req)
|
|
{
|
|
req->flink = NULL;
|
|
if (!queue->head)
|
|
{
|
|
queue->head = req;
|
|
queue->tail = req;
|
|
}
|
|
else
|
|
{
|
|
queue->tail->flink = req;
|
|
queue->tail = req;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_addfirst
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_addfirst(struct pic32mx_queue_s *queue, struct pic32mx_req_s *req)
|
|
{
|
|
req->flink = queue->head;
|
|
if (!queue->head)
|
|
{
|
|
queue->tail = req;
|
|
}
|
|
queue->head = req;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_reqreturn
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_reqreturn(struct pic32mx_ep_s *privep,
|
|
struct pic32mx_req_s *privreq, int16_t result)
|
|
{
|
|
/* If endpoint 0, temporarily reflect the state of protocol stalled
|
|
* in the callback.
|
|
*/
|
|
|
|
bool stalled = privep->stalled;
|
|
if (USB_EPNO(privep->ep.eplog) == EP0)
|
|
{
|
|
privep->stalled = (privep->dev->ctrlstate == CTRLSTATE_STALLED);
|
|
}
|
|
|
|
/* Save the result in the request structure */
|
|
|
|
privreq->req.result = result;
|
|
|
|
/* Callback to the request completion handler */
|
|
|
|
privreq->flink = NULL;
|
|
privreq->req.callback(&privep->ep, &privreq->req);
|
|
|
|
/* Restore the stalled indication */
|
|
|
|
privep->stalled = stalled;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_reqcomplete
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_reqcomplete(struct pic32mx_ep_s *privep, int16_t result)
|
|
{
|
|
struct pic32mx_req_s *privreq;
|
|
irqstate_t flags;
|
|
|
|
/* Remove the completed request at the head of the endpoint's active
|
|
* request list.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
privreq = pic32mx_remfirst(&privep->active);
|
|
leave_critical_section(flags);
|
|
|
|
if (privreq)
|
|
{
|
|
/* Return the request to the class driver */
|
|
|
|
pic32mx_reqreturn(privep, privreq, result);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epwrite
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_epwrite(struct pic32mx_ep_s *privep,
|
|
volatile struct usbotg_bdtentry_s *bdt,
|
|
const uint8_t *src, uint32_t nbytes)
|
|
{
|
|
uint32_t status;
|
|
|
|
usbtrace(TRACE_WRITE(USB_EPNO(privep->ep.eplog)), nbytes);
|
|
|
|
/* Clear all bits in the status (assuring that we own the BDT) */
|
|
|
|
bdt->status = 0;
|
|
|
|
/* Get the correct data toggle (as well as other BDT bits) */
|
|
|
|
if (privep->txdata1)
|
|
{
|
|
status = (USB_BDT_UOWN | USB_BDT_DATA1 | USB_BDT_DTS);
|
|
privep->txdata1 = 0;
|
|
}
|
|
else
|
|
{
|
|
status = (USB_BDT_UOWN | USB_BDT_DATA0 | USB_BDT_DTS);
|
|
privep->txdata1 = 1;
|
|
}
|
|
|
|
/* Set the data pointer and data length */
|
|
|
|
bdt->addr = (uint8_t *)PHYS_ADDR(src);
|
|
status |= (nbytes << USB_BDT_BYTECOUNT_SHIFT) | USB_BDT_DTS;
|
|
|
|
/* And, finally, give the BDT to the USB */
|
|
|
|
bdtinfo("EP%d BDT IN [%p] {%08x, %08x}\n",
|
|
USB_EPNO(privep->ep.eplog), bdt, status, bdt->addr);
|
|
|
|
bdt->status = status;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_wrcomplete
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_wrcomplete(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdtin;
|
|
struct pic32mx_req_s *privreq;
|
|
int bytesleft;
|
|
int epno;
|
|
|
|
/* Check the request at the head of the endpoint's active request queue.
|
|
* Since we got here from a write completion event, the active request queue
|
|
* should not be empty.
|
|
*/
|
|
|
|
privreq = pic32mx_rqhead(&privep->active);
|
|
DEBUGASSERT(privreq != NULL);
|
|
|
|
/* An outgoing IN packet has completed. bdtin should point to the BDT
|
|
* that just completed.
|
|
*/
|
|
|
|
bdtin = privep->bdtin;
|
|
epno = USB_EPNO(privep->ep.eplog);
|
|
|
|
#ifdef CONFIG_USBDEV_NOWRITEAHEAD
|
|
ullinfo("EP%d: len=%d xfrd=%d inflight=%d\n",
|
|
epno, privreq->req.len, privreq->req.xfrd, privreq->inflight[0]);
|
|
#else
|
|
ullinfo("EP%d: len=%d xfrd=%d inflight={%d, %d}\n",
|
|
epno, privreq->req.len, privreq->req.xfrd,
|
|
privreq->inflight[0], privreq->inflight[1]);
|
|
#endif
|
|
bdtinfo("EP%d BDT IN [%p] {%08x, %08x}\n",
|
|
epno, bdtin, bdtin->status, bdtin->addr);
|
|
|
|
/* We should own the BDT that just completed. But NULLify the entire BDT IN.
|
|
* Why? So that we can tell later that the BDT available. No, it is not
|
|
* sufficient to look at the UOWN bit. If UOWN==0, then the transfer has
|
|
* been completed BUT it may not yet have been processed. But a completely
|
|
* NULLified BDT is a sure indication
|
|
*/
|
|
|
|
DEBUGASSERT((bdtin->status & USB_BDT_UOWN) == USB_BDT_COWN);
|
|
bdtin->status = 0;
|
|
bdtin->addr = 0;
|
|
|
|
/* Toggle bdtin to the other BDT. Is the current bdtin the EVEN bdt? */
|
|
|
|
privep->bdtin = &g_bdt[EP_IN_EVEN(epno)];
|
|
if (bdtin == privep->bdtin)
|
|
{
|
|
/* Yes.. Then the other BDT is the ODD BDT */
|
|
|
|
privep->bdtin++;
|
|
}
|
|
|
|
/* Update the number of bytes transferred. */
|
|
|
|
privreq->req.xfrd += privreq->inflight[0];
|
|
#ifdef CONFIG_USBDEV_NOWRITEAHEAD
|
|
privreq->inflight[0] = 0;
|
|
#else
|
|
privreq->inflight[0] = privreq->inflight[1];
|
|
privreq->inflight[1] = 0;
|
|
#endif
|
|
bytesleft = privreq->req.len - privreq->req.xfrd;
|
|
|
|
/* If all of the bytes were sent (bytesleft == 0) and no NULL packet is
|
|
* needed (!txnullpkt), then we are finished with the transfer
|
|
*/
|
|
|
|
if (bytesleft == 0 && !privep->txnullpkt)
|
|
{
|
|
/* The transfer is complete. Give the completed request back to
|
|
* the class driver.
|
|
*/
|
|
|
|
usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)), privreq->req.xfrd);
|
|
pic32mx_reqcomplete(privep, OK);
|
|
|
|
/* Special case writes to endpoint zero. If there is no transfer in
|
|
* progress, then we need to configure to received the next SETUP packet.
|
|
*/
|
|
|
|
if (USB_EPNO(privep->ep.eplog) == 0)
|
|
{
|
|
priv->ctrlstate = CTRLSTATE_WAITSETUP;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_rqrestart
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_rqrestart(int argc, uint32_t arg1, ...)
|
|
{
|
|
struct pic32mx_usbdev_s *priv;
|
|
struct pic32mx_ep_s *privep;
|
|
struct pic32mx_req_s *privreq;
|
|
uint16_t epstalled;
|
|
uint16_t mask;
|
|
int epno;
|
|
|
|
/* Recover the pointer to the driver structure */
|
|
|
|
priv = (struct pic32mx_usbdev_s *)((uintptr_t)arg1);
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
/* Sample and clear the set of endpoints that have recovered from a stall */
|
|
|
|
epstalled = priv->epstalled;
|
|
priv->epstalled = 0;
|
|
|
|
/* Loop, checking each bit in the epstalled bit set */
|
|
|
|
for (epno = 0; epstalled && epno < PIC32MX_NENDPOINTS; epno++)
|
|
{
|
|
/* Has this encpoint recovered from a stall? */
|
|
|
|
mask = (1 << epno);
|
|
if ((epstalled & mask) != 0)
|
|
{
|
|
/* Yes, this endpoint needs to be restarteed */
|
|
|
|
epstalled &= ~mask;
|
|
privep = &priv->eplist[epno];
|
|
|
|
/* Reset some endpoint state variables */
|
|
|
|
privep->stalled = false;
|
|
privep->txnullpkt = false;
|
|
|
|
/* Check the request at the head of the endpoint's pending request queue */
|
|
|
|
privreq = pic32mx_rqhead(&privep->pend);
|
|
if (privreq)
|
|
{
|
|
/* Restart transmission after we have recovered from a stall */
|
|
|
|
privreq->req.xfrd = 0;
|
|
privreq->inflight[0] = 0;
|
|
#ifndef CONFIG_USBDEV_NOWRITEAHEAD
|
|
privreq->inflight[1] = 0;
|
|
#endif
|
|
(void)pic32mx_wrrequest(priv, privep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_delayedrestart
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_delayedrestart(struct pic32mx_usbdev_s *priv, uint8_t epno)
|
|
{
|
|
/* Add endpoint to the set of endpoints that need to be restarted */
|
|
|
|
priv->epstalled |= (1 << epno);
|
|
|
|
/* And start (or re-start) the watchdog timer */
|
|
|
|
wd_start(priv->wdog, RESTART_DELAY, pic32mx_rqrestart, 1, (uint32_t)priv);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_rqstop
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_rqstop(struct pic32mx_ep_s *privep)
|
|
{
|
|
struct pic32mx_req_s *privreq;
|
|
|
|
/* Move all of the active requests back to the pending request queue */
|
|
|
|
while ((privreq = pic32mx_remlast(&privep->active)))
|
|
{
|
|
/* Move the request back to the head of the pending list */
|
|
|
|
pic32mx_addfirst(&privep->pend, privreq);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_wrstart
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_wrstart(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdt;
|
|
struct pic32mx_req_s *privreq;
|
|
uint8_t *buf;
|
|
uint8_t epno;
|
|
int nbytes;
|
|
int bytesleft;
|
|
int xfrd;
|
|
int index;
|
|
|
|
/* We get here when either (1) an IN endpoint completion interrupt occurs,
|
|
* or (2) a new write request is reqeived from the class.
|
|
*/
|
|
|
|
/* Get the endpoint number that we are servicing */
|
|
|
|
epno = USB_EPNO(privep->ep.eplog);
|
|
|
|
/* Decide which BDT to use. bdtin points to the "current" BDT. That is,
|
|
* the one that either (1) available for next transfer, or (2) the one
|
|
* that is currently busy with the current transfer. If the current
|
|
* BDT is busy, we have the option of setting up the other BDT in advance
|
|
* in order to improve data transfer performance.
|
|
*/
|
|
|
|
bdt = privep->bdtin;
|
|
index = 0;
|
|
|
|
if (bdt->status || bdt->addr)
|
|
{
|
|
#ifdef CONFIG_USBDEV_NOWRITEAHEAD
|
|
/* The current BDT is not available and write ahead is disabled. There
|
|
* is nothing we can do now. Return -EBUSY to indicate this condition.
|
|
*/
|
|
|
|
return -EBUSY;
|
|
#else
|
|
/* The current BDT is not available, check the other BDT */
|
|
|
|
volatile struct usbotg_bdtentry_s *otherbdt;
|
|
otherbdt = &g_bdt[EP(epno, EP_DIR_IN, EP_PP_EVEN)];
|
|
if (otherbdt == bdt)
|
|
{
|
|
otherbdt++;
|
|
}
|
|
|
|
/* Is it available? */
|
|
|
|
if (otherbdt->status || otherbdt->addr)
|
|
{
|
|
/* No, neither are available. We cannot perform the transfer now.
|
|
* Return -EBUSY to indicate this condition.
|
|
*/
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Yes... use the other BDT */
|
|
|
|
bdt = otherbdt;
|
|
index = 1;
|
|
#endif
|
|
}
|
|
|
|
/* A BDT is available. Which request should we be operating on? The last
|
|
* incomplete, active request would be at the tail of the active list.
|
|
*/
|
|
|
|
privreq = pic32mx_rqtail(&privep->active);
|
|
|
|
/* This request would be NULL if there is no incomplete, active request. */
|
|
|
|
if (privreq)
|
|
{
|
|
/* Get the number of bytes left to be transferred in the request */
|
|
|
|
xfrd = privreq->req.xfrd;
|
|
bytesleft = privreq->req.len - xfrd;
|
|
|
|
/* Even if the request is incomplete, transfer of all the requested
|
|
* bytes may already been started. NOTE: inflight[1] should be zero
|
|
* because we know that there is a BDT available.
|
|
*/
|
|
|
|
#ifndef CONFIG_USBDEV_NOWRITEAHEAD
|
|
DEBUGASSERT(privreq->inflight[1] == 0);
|
|
#endif
|
|
/* Has the transfer been initiated for all of the bytes? */
|
|
|
|
if (bytesleft > privreq->inflight[0])
|
|
{
|
|
/* No.. we have more work to do with this request */
|
|
|
|
xfrd += privreq->inflight[0];
|
|
bytesleft -= privreq->inflight[0];
|
|
}
|
|
|
|
/* Do we need to send a null packet after this packet? */
|
|
|
|
else if (privep->txnullpkt)
|
|
{
|
|
/* Yes... set up for the NULL packet transfer */
|
|
|
|
xfrd = privreq->req.len;
|
|
bytesleft = 0;
|
|
}
|
|
else
|
|
{
|
|
/* No.. We are finished with this request. We need to get the
|
|
* next request from the head of the pending request list.
|
|
*/
|
|
|
|
privreq = NULL;
|
|
}
|
|
}
|
|
|
|
/* If privreq is NULL here then either (1) there is no active request, or
|
|
* (2) the (only) active request is fully queued. In either case, we need
|
|
* to get the next request from the head of the pending request list.
|
|
*/
|
|
|
|
if (!privreq)
|
|
{
|
|
/* Remove the next request from the head of the pending request list */
|
|
|
|
privreq = pic32mx_remfirst(&privep->pend);
|
|
if (!privreq)
|
|
{
|
|
/* The pending request list is empty. There are no queued TX
|
|
* requests to be sent.
|
|
*/
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPINQEMPTY), epno);
|
|
|
|
/* Return -ENODATA to indicate that there are no further requests
|
|
* to be processed.
|
|
*/
|
|
|
|
return -ENODATA;
|
|
}
|
|
|
|
/* Add this request to the tail of the active request list */
|
|
|
|
pic32mx_addlast(&privep->active, privreq);
|
|
|
|
/* Set up the first transfer for this request */
|
|
|
|
xfrd = 0;
|
|
bytesleft = privreq->req.len;
|
|
}
|
|
|
|
ullinfo("epno=%d req=%p: len=%d xfrd=%d index=%d nullpkt=%d\n",
|
|
epno, privreq, privreq->req.len, xfrd, index, privep->txnullpkt);
|
|
|
|
/* Get the number of bytes left to be sent in the packet */
|
|
|
|
nbytes = bytesleft;
|
|
if (nbytes > 0 || privep->txnullpkt)
|
|
{
|
|
/* Either send the maxpacketsize or all of the remaining data in
|
|
* the request.
|
|
*/
|
|
|
|
privep->txnullpkt = 0;
|
|
if (nbytes >= privep->ep.maxpacket)
|
|
{
|
|
nbytes = privep->ep.maxpacket;
|
|
|
|
/* Handle the case where this packet is exactly the
|
|
* maxpacketsize. Do we need to send a zero-length packet
|
|
* in this case?
|
|
*/
|
|
|
|
if (bytesleft == privep->ep.maxpacket &&
|
|
(privreq->req.flags & USBDEV_REQFLAGS_NULLPKT) != 0)
|
|
{
|
|
privep->txnullpkt = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Send the packet (might be a null packet with nbytes == 0) */
|
|
|
|
buf = privreq->req.buf + xfrd;
|
|
|
|
/* Setup the writes to the endpoints */
|
|
|
|
pic32mx_epwrite(privep, bdt, buf, nbytes);
|
|
|
|
/* Special case endpoint 0 state information. The write request is in
|
|
* progress.
|
|
*/
|
|
|
|
if (epno == 0)
|
|
{
|
|
priv->ctrlstate = CTRLSTATE_WRREQUEST;
|
|
}
|
|
|
|
/* Update for the next data IN interrupt */
|
|
|
|
privreq->inflight[index] = nbytes;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_wrrequest
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_wrrequest(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep)
|
|
{
|
|
int ret;
|
|
|
|
/* Always try to start two transfers in order to take advantage of the
|
|
* PIC32MX's ping pong buffering.
|
|
*/
|
|
|
|
ret = pic32mx_wrstart(priv, privep);
|
|
#ifndef CONFIG_USBDEV_NOWRITEAHEAD
|
|
if (ret == OK)
|
|
{
|
|
/* Note: We need to return the error condition only if nothing was
|
|
* queued
|
|
*/
|
|
|
|
(void)pic32mx_wrstart(priv, privep);
|
|
}
|
|
#else
|
|
UNUSED(ret);
|
|
#endif
|
|
|
|
/* We return OK to indicate that a write request is still in progress */
|
|
|
|
return pic32mx_rqhead(&privep->active) == NULL ? -ENODATA : OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_rdcomplete
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_rdcomplete(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdtout;
|
|
struct pic32mx_req_s *privreq;
|
|
int epno;
|
|
|
|
/* Check the request at the head of the endpoint's active request queue */
|
|
|
|
privreq = pic32mx_rqhead(&privep->active);
|
|
if (!privreq)
|
|
{
|
|
/* There is no active packet waiting to receive any data. Then why are
|
|
* we here?
|
|
*/
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPOUTQEMPTY),
|
|
USB_EPNO(privep->ep.eplog));
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* bdtout should point to the BDT that just completed */
|
|
|
|
bdtout = privep->bdtout;
|
|
epno = USB_EPNO(privep->ep.eplog);
|
|
|
|
ullinfo("EP%d: len=%d xfrd=%d\n",
|
|
epno, privreq->req.len, privreq->req.xfrd);
|
|
bdtinfo("EP%d BDT OUT [%p] {%08x, %08x}\n",
|
|
epno, bdtout, bdtout->status, bdtout->addr);
|
|
|
|
/* We should own the BDT that just completed */
|
|
|
|
DEBUGASSERT((bdtout->status & USB_BDT_UOWN) == USB_BDT_COWN);
|
|
|
|
/* Get the length of the data received from the BDT. */
|
|
|
|
privreq->req.xfrd = (bdtout->status & USB_BDT_BYTECOUNT_MASK) >> USB_BDT_BYTECOUNT_SHIFT;
|
|
|
|
/* Complete the transfer and return the request to the class driver. */
|
|
|
|
usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)), privreq->req.xfrd);
|
|
pic32mx_reqcomplete(privep, OK);
|
|
|
|
/* Nullify the BDT entry that just completed. Why? So that we can tell later
|
|
* that the BDT has been processed. No, it is not sufficient to look at the
|
|
* UOWN bit. If UOWN==0, then the transfer has been completed BUT it may not
|
|
* yet have been processed.
|
|
*/
|
|
|
|
bdtout->status = 0;
|
|
bdtout->addr = 0;
|
|
|
|
/* Toggle bdtout to the other BDT. Is the current bdtout the EVEN bdt? */
|
|
|
|
privep->bdtout = &g_bdt[EP_OUT_EVEN(epno)];
|
|
if (bdtout == privep->bdtout)
|
|
{
|
|
/* Yes.. Then the other BDT is the ODD BDT */
|
|
|
|
privep->bdtout++;
|
|
}
|
|
|
|
/* Set up the next read operation */
|
|
|
|
return pic32mx_rdrequest(priv, privep);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0rdsetup
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_ep0rdsetup(struct pic32mx_usbdev_s *priv, uint8_t *dest,
|
|
int readlen)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdtout;
|
|
volatile struct usbotg_bdtentry_s *otherbdt;
|
|
struct pic32mx_ep_s *privep;
|
|
uint32_t status;
|
|
|
|
/* bdtout refers to the next ping-pong BDT to use. */
|
|
|
|
privep = &priv->eplist[EP0];
|
|
bdtout = privep->bdtout;
|
|
|
|
/* Get the other BDT. Check if the current BDT the EVEN BDT? */
|
|
|
|
otherbdt = &g_bdt[EP_OUT_EVEN(EP0)];
|
|
if (bdtout == otherbdt)
|
|
{
|
|
/* Yes.. then the other BDT is the ODD BDT. */
|
|
|
|
otherbdt++;
|
|
}
|
|
|
|
/* If there is no RX transfer in progress, then the other BDT is setup
|
|
* to receive the next setup packet. There is a race condition here!
|
|
* Stop any setup packet.
|
|
*/
|
|
|
|
if (!priv->rxbusy)
|
|
{
|
|
/* Nullify all BDT OUT entries. Why? So that we can tell later
|
|
* that the BDT available. No, it is not sufficient to look at the
|
|
* UOWN bit. If UOWN==0, then the transfer has been completed BUT
|
|
* it may not yet have been processed. But a completely NULLified
|
|
* BDT is a sure indication
|
|
*/
|
|
|
|
bdtout->status = 0;
|
|
bdtout->addr = 0;
|
|
otherbdt->status = 0;
|
|
otherbdt->addr = 0;
|
|
|
|
/* Reset the other BDT to zero... this will cause any attempted use
|
|
* of the other BDT to be NAKed. Set the first DATA0/1 value to 1.
|
|
*/
|
|
|
|
privep->rxdata1 = 1;
|
|
}
|
|
|
|
/* Otherwise, there are RX transfers in progress. bdtout may be
|
|
* unavailable now. In that case, we are free to setup the other BDT
|
|
* in order to improve performance. NOTE: That we check if the
|
|
* entire BDT has been NULLified. That is the only sure indication
|
|
* that the BDT is available (see above).
|
|
*/
|
|
|
|
if (bdtout->status || bdtout->addr)
|
|
{
|
|
#ifdef CONFIG_USBDEV_NOREADAHEAD
|
|
/* We will not try to read ahead */
|
|
|
|
return -EBUSY;
|
|
#else
|
|
/* bdtout is not available. Is the other BDT available? */
|
|
|
|
if (otherbdt->status || otherbdt->addr)
|
|
{
|
|
/* Neither are available... we cannot accept the request now */
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Use the other BDT */
|
|
|
|
bdtout = otherbdt;
|
|
#endif
|
|
}
|
|
|
|
usbtrace(TRACE_READ(EP0), readlen);
|
|
|
|
/* Get the correct data toggle (as well as other BDT bits) */
|
|
|
|
if (privep->rxdata1)
|
|
{
|
|
status = (USB_BDT_UOWN | USB_BDT_DATA1 | USB_BDT_DTS);
|
|
privep->rxdata1 = 0;
|
|
}
|
|
else
|
|
{
|
|
status = (USB_BDT_UOWN | USB_BDT_DATA0 | USB_BDT_DTS);
|
|
privep->rxdata1 = 1;
|
|
}
|
|
|
|
/* Set the data pointer, data length, and enable the endpoint */
|
|
|
|
bdtout->addr = (uint8_t *)PHYS_ADDR(dest);
|
|
status |= ((uint32_t)readlen << USB_BDT_BYTECOUNT_SHIFT);
|
|
|
|
/* Then give the BDT to the USB */
|
|
|
|
bdtinfo("EP0 BDT OUT [%p] {%08x, %08x}\n",
|
|
bdtout, status, bdtout->addr);
|
|
bdtout->status = status;
|
|
|
|
priv->ctrlstate = CTRLSTATE_RDREQUEST;
|
|
priv->rxbusy = 1;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_rdsetup
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_rdsetup(struct pic32mx_ep_s *privep, uint8_t *dest, int readlen)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdtout;
|
|
uint32_t status;
|
|
int epno;
|
|
|
|
/* Select a BDT. Check both the even and the ODD BDT and use the first one
|
|
* that we own.
|
|
*/
|
|
|
|
epno = USB_EPNO(privep->ep.eplog);
|
|
|
|
/* bdtout refers to the next ping-pong BDT to use. However, bdtout may be
|
|
* unavailable now. But, in that case, we are free to setup the other BDT
|
|
* in order to improve performance.
|
|
*
|
|
* Note that we NULLify the BDT OUT entries. This is so that we can tell
|
|
* that the BDT readlly available. No, it is not sufficient to look at the
|
|
* UOWN bit. If UOWN==0, then the transfer has been completed BUT it may
|
|
* not yet have been processed. But a completely NULLified BDT is a sure
|
|
* indication
|
|
*/
|
|
|
|
bdtout = privep->bdtout;
|
|
if (bdtout->status || bdtout->addr)
|
|
{
|
|
#ifdef CONFIG_USBDEV_NOREADAHEAD
|
|
/* We will not try to read-ahead */
|
|
|
|
return -EBUSY;
|
|
#else
|
|
volatile struct usbotg_bdtentry_s *otherbdt;
|
|
|
|
/* Is the current BDT the EVEN BDT? */
|
|
|
|
otherbdt = &g_bdt[EP_OUT_EVEN(epno)];
|
|
if (bdtout == otherbdt)
|
|
{
|
|
/* Yes.. select the ODD BDT */
|
|
|
|
otherbdt++;
|
|
}
|
|
|
|
/* Is the other BDT available? */
|
|
|
|
if (otherbdt->status || otherbdt->addr)
|
|
{
|
|
/* Neither are available... we cannot accept the request now */
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Use the other BDT */
|
|
|
|
bdtout = otherbdt;
|
|
#endif
|
|
}
|
|
|
|
usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), readlen);
|
|
|
|
/* Clear status bits (making sure that UOWN is cleared before doing anything
|
|
* else).
|
|
*/
|
|
|
|
bdtout->status = 0;
|
|
|
|
/* Set the data pointer, data length, and enable the endpoint */
|
|
|
|
bdtout->addr = (uint8_t *)PHYS_ADDR(dest);
|
|
|
|
/* Get the correct data toggle. */
|
|
|
|
if (privep->rxdata1)
|
|
{
|
|
status = (USB_BDT_UOWN | USB_BDT_DATA1 | USB_BDT_DTS);
|
|
privep->rxdata1 = 0;
|
|
}
|
|
else
|
|
{
|
|
status = (USB_BDT_UOWN | USB_BDT_DATA0 | USB_BDT_DTS);
|
|
privep->rxdata1 = 1;
|
|
}
|
|
|
|
/* Set the data length (preserving the data toggle). */
|
|
|
|
status |= ((uint32_t)readlen << USB_BDT_BYTECOUNT_SHIFT);
|
|
|
|
/* Then give the BDT to the USB */
|
|
|
|
bdtinfo("EP%d BDT OUT [%p] {%08x, %08x}\n",
|
|
epno, bdtout, status, bdtout->addr);
|
|
|
|
bdtout->status = status;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_rdrequest
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_rdrequest(struct pic32mx_usbdev_s *priv,
|
|
struct pic32mx_ep_s *privep)
|
|
{
|
|
struct pic32mx_req_s *privreq;
|
|
int readlen;
|
|
int ret;
|
|
|
|
/* Check the request at the head of the endpoint request queue */
|
|
|
|
privreq = pic32mx_rqhead(&privep->pend);
|
|
if (!privreq)
|
|
{
|
|
/* There is no packet to receive any data. */
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPOUTQEMPTY),
|
|
USB_EPNO(privep->ep.eplog));
|
|
|
|
/* Special case reads from to endpoint zero. If there is no transfer in
|
|
* progress, then we need to configure to received the next SETUP packet.
|
|
*/
|
|
|
|
if (USB_EPNO(privep->ep.eplog) == 0 &&
|
|
priv->ctrlstate == CTRLSTATE_RDREQUEST)
|
|
{
|
|
priv->ctrlstate = CTRLSTATE_WAITSETUP;
|
|
priv->rxbusy = 0;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
ullinfo("EP%d: len=%d\n", USB_EPNO(privep->ep.eplog), privreq->req.len);
|
|
|
|
/* Ignore any attempt to receive a zero length packet */
|
|
|
|
if (privreq->req.len == 0)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_EPOUTNULLPACKET), 0);
|
|
pic32mx_reqcomplete(privep, OK);
|
|
return OK;
|
|
}
|
|
|
|
/* Limit the size of the transfer to either the buffer size or the max
|
|
* packet size of the endpoint.
|
|
*/
|
|
|
|
readlen = MIN(privreq->req.len, privep->ep.maxpacket);
|
|
|
|
/* Handle EP0 in a few special ways */
|
|
|
|
if (USB_EPNO(privep->ep.eplog) == EP0)
|
|
{
|
|
ret = pic32mx_ep0rdsetup(priv, privreq->req.buf, readlen);
|
|
}
|
|
else
|
|
{
|
|
ret = pic32mx_rdsetup(privep, privreq->req.buf, readlen);
|
|
}
|
|
|
|
/* If the read request was successfully setup, then move the request from
|
|
* the head of the pending request queue to the tail of the active request
|
|
* queue.
|
|
*/
|
|
|
|
if (ret == OK)
|
|
{
|
|
privreq = pic32mx_remfirst(&privep->pend);
|
|
DEBUGASSERT(privreq != NULL);
|
|
pic32mx_addlast(&privep->active, privreq);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_cancelrequests
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_cancelrequests(struct pic32mx_ep_s *privep, int16_t result)
|
|
{
|
|
struct pic32mx_req_s *privreq;
|
|
|
|
while ((privreq = pic32mx_remfirst(&privep->active)))
|
|
{
|
|
usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)), privreq->req.xfrd);
|
|
pic32mx_reqreturn(privep, privreq, result);
|
|
}
|
|
|
|
while ((privreq = pic32mx_remfirst(&privep->pend)))
|
|
{
|
|
usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)), privreq->req.xfrd);
|
|
pic32mx_reqreturn(privep, privreq, result);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Interrupt Level Processing
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: pic32mx_dispatchrequest
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_dispatchrequest(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_DISPATCH), 0);
|
|
if (priv && priv->driver)
|
|
{
|
|
/* Forward to the control request to the class driver implementation */
|
|
|
|
ret = CLASS_SETUP(priv->driver, &priv->usbdev, &priv->ctrl, NULL, 0);
|
|
if (ret < 0)
|
|
{
|
|
/* Stall on failure */
|
|
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_DISPATCHSTALL), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0stall
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_ep0stall(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
uint16_t regval;
|
|
|
|
/* Check if EP0 is stalled */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USB_EP0);
|
|
if ((regval & USB_EP_EPSTALL) != 0)
|
|
{
|
|
/* If so, clear the EP0 stall status */
|
|
|
|
regval &= ~USB_EP_EPSTALL;
|
|
pic32mx_putreg(regval, PIC32MX_USB_EP0);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_eptransfer
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_eptransfer(struct pic32mx_usbdev_s *priv, uint8_t epno,
|
|
uint16_t ustat)
|
|
{
|
|
struct pic32mx_ep_s *privep;
|
|
int ret;
|
|
|
|
/* Decode and service non control endpoints interrupt */
|
|
|
|
privep = &priv->eplist[epno];
|
|
|
|
/* Check if the last transaction was an EP0 OUT transaction */
|
|
|
|
if ((ustat & USB_STAT_DIR) == USB_STAT_DIR_OUT)
|
|
{
|
|
/* OUT: host-to-device */
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPOUTDONE), ustat);
|
|
|
|
/* Handle read requests. Call pic32mx_rdcomplete() to complete the OUT
|
|
* transfer and setup the next out transfer.
|
|
*/
|
|
|
|
ret = pic32mx_rdcomplete(priv, privep);
|
|
#ifdef CONFIG_USBDEV_NOREADAHEAD
|
|
if (ret == OK)
|
|
{
|
|
/* If that succeeds, then try to set up another OUT transfer. */
|
|
|
|
(void)pic32mx_rdrequest(priv, privep);
|
|
}
|
|
#else
|
|
UNUSED(ret);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/* IN: device-to-host */
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPINDONE), ustat);
|
|
|
|
/* An outgoing IN packet has completed. Update the number of bytes transferred
|
|
* and check for completion of the transfer.
|
|
*/
|
|
|
|
pic32mx_wrcomplete(priv, privep);
|
|
|
|
/* Handle additional queued write requests */
|
|
|
|
(void)pic32mx_wrrequest(priv, privep);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0nextsetup
|
|
*
|
|
* Description:
|
|
* This function is called (1) after sucessful completion of an EP0 Setup
|
|
* command, or (2) after receipt of the OUT complete event (for simple
|
|
* transfers). It simply sets up the single BDT to accept the next
|
|
* SETUP commend.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_ep0nextsetup(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdt = priv->eplist[EP0].bdtout;
|
|
uint32_t bytecount;
|
|
|
|
/* This operation should be performed no more than once per OUT transaction.
|
|
* priv->ep0done is set to zero at the beginning of processing of each EP0
|
|
* transfer. It is set the first time that this function runs after the EP0
|
|
* transfer.
|
|
*/
|
|
|
|
if (!priv->ep0done)
|
|
{
|
|
bytecount = (USB_SIZEOF_CTRLREQ << USB_BDT_BYTECOUNT_SHIFT);
|
|
bdt->addr = (uint8_t *)PHYS_ADDR(&priv->ctrl);
|
|
bdt->status = (USB_BDT_UOWN | bytecount);
|
|
priv->ep0done = 1;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0rdcomplete
|
|
*
|
|
* Description:
|
|
* This function is called after a sequence of read sequence. In this
|
|
* context, only one BDT is used. Both BDTs must be prepared to receive
|
|
* SETUP packets.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_ep0rdcomplete(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdt;
|
|
struct pic32mx_ep_s *ep0;
|
|
uint32_t physaddr;
|
|
uint32_t bytecount;
|
|
|
|
/* This operation should be performed no more than once per OUT transaction.
|
|
* priv->ep0done is set to zero at the beginning of processing of each EP0
|
|
* transfer. It is set the first time that this function runs after the EP0
|
|
* transfer.
|
|
*/
|
|
|
|
if (!priv->ep0done)
|
|
{
|
|
bytecount = (USB_SIZEOF_CTRLREQ << USB_BDT_BYTECOUNT_SHIFT);
|
|
physaddr = PHYS_ADDR(&priv->ctrl);
|
|
|
|
bdt = &g_bdt[EP0_OUT_EVEN];
|
|
bdt->addr = (uint8_t *)physaddr;
|
|
bdt->status = (USB_BDT_UOWN | bytecount);
|
|
|
|
bdt = &g_bdt[EP0_OUT_ODD];
|
|
bdt->addr = (uint8_t *)physaddr;
|
|
bdt->status = (USB_BDT_UOWN | bytecount);
|
|
|
|
priv->ep0done = 1;
|
|
|
|
/* Data toggling is not used on SETUP transfers. And IN transfer
|
|
* resulting from a SETUP command should begin with DATA1.
|
|
*/
|
|
|
|
ep0 = &priv->eplist[EP0];
|
|
ep0->rxdata1 = 0;
|
|
ep0->txdata1 = 1;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0setup
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_ep0setup(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdt;
|
|
struct pic32mx_ep_s *ep0;
|
|
struct pic32mx_ep_s *privep;
|
|
union wb_u value;
|
|
union wb_u index;
|
|
union wb_u len;
|
|
union wb_u response;
|
|
uint16_t regval;
|
|
bool dispatched = false;
|
|
uint8_t epno;
|
|
int nbytes = 0; /* Assume zero-length packet */
|
|
int ret;
|
|
|
|
/* Cancel any pending requests. */
|
|
|
|
ep0 = &priv->eplist[EP0];
|
|
pic32mx_cancelrequests(ep0, -EPROTO);
|
|
|
|
/* Assume NOT stalled; no TX in progress; no RX overrun. Data 0/1 toggling
|
|
* is not used on SETUP packets, but any following EP0 IN transfer should
|
|
* beginning with DATA1.
|
|
*/
|
|
|
|
ep0->stalled = false;
|
|
ep0->rxdata1 = 0;
|
|
ep0->txdata1 = 1;
|
|
|
|
/* Initialize for the SETUP */
|
|
|
|
priv->ctrlstate = CTRLSTATE_WAITSETUP;
|
|
|
|
/* And extract the little-endian 16-bit values to host order */
|
|
|
|
value.w = GETUINT16(priv->ctrl.value);
|
|
index.w = GETUINT16(priv->ctrl.index);
|
|
len.w = GETUINT16(priv->ctrl.len);
|
|
|
|
ullinfo("SETUP: type=%02x req=%02x value=%04x index=%04x len=%04x\n",
|
|
priv->ctrl.type, priv->ctrl.req, value.w, index.w, len.w);
|
|
|
|
/* Dispatch any non-standard requests */
|
|
|
|
if ((priv->ctrl.type & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_NOSTDREQ), priv->ctrl.type);
|
|
|
|
/* Let the class implementation handle all non-standar requests */
|
|
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
goto resume_packet_processing; /* Sorry about the goto */
|
|
}
|
|
|
|
/* Handle standard request. Pick off the things of interest to the
|
|
* USB device controller driver; pass what is left to the class driver
|
|
*/
|
|
|
|
switch (priv->ctrl.req)
|
|
{
|
|
case USB_REQ_GETSTATUS:
|
|
{
|
|
/* type: device-to-host; recipient = device, interface, endpoint
|
|
* value: 0
|
|
* index: zero interface endpoint
|
|
* len: 2; data = status
|
|
*/
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_GETSTATUS), priv->ctrl.type);
|
|
if (len.w != 2 || (priv->ctrl.type & USB_REQ_DIR_IN) == 0 ||
|
|
index.b[MSB] != 0 || value.w != 0)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADEPGETSTATUS), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
else
|
|
{
|
|
switch (priv->ctrl.type & USB_REQ_RECIPIENT_MASK)
|
|
{
|
|
case USB_REQ_RECIPIENT_ENDPOINT:
|
|
{
|
|
epno = USB_EPNO(index.b[LSB]);
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPGETSTATUS), epno);
|
|
if (epno >= PIC32MX_NENDPOINTS)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADEPGETSTATUS), epno);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
else
|
|
{
|
|
privep = &priv->eplist[epno];
|
|
response.w = 0; /* Not stalled */
|
|
nbytes = 2; /* Response size: 2 bytes */
|
|
|
|
if (USB_ISEPIN(index.b[LSB]))
|
|
{
|
|
/* IN endpoint */
|
|
|
|
bdt = privep->bdtin;
|
|
}
|
|
else
|
|
{
|
|
/* OUT endpoint */
|
|
|
|
bdt = privep->bdtout;
|
|
}
|
|
|
|
/* BSTALL set if stalled */
|
|
|
|
if ((bdt->status & USB_BDT_BSTALL) != 0)
|
|
{
|
|
response.b[LSB] = 1; /* Stalled, set bit 0 */
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_RECIPIENT_DEVICE:
|
|
{
|
|
if (index.w == 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_DEVGETSTATUS), 0);
|
|
|
|
/* Features: Remote Wakeup=YES; selfpowered=? */
|
|
|
|
response.w = 0;
|
|
response.b[LSB] = (priv->selfpowered << USB_FEATURE_SELFPOWERED) |
|
|
(priv->rwakeup << USB_FEATURE_REMOTEWAKEUP);
|
|
nbytes = 2; /* Response size: 2 bytes */
|
|
}
|
|
else
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADDEVGETSTATUS), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_RECIPIENT_INTERFACE:
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_IFGETSTATUS), 0);
|
|
response.w = 0;
|
|
nbytes = 2; /* Response size: 2 bytes */
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADGETSTATUS), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_CLEARFEATURE:
|
|
{
|
|
/* type: host-to-device; recipient = device, interface or endpoint
|
|
* value: feature selector
|
|
* index: zero interface endpoint;
|
|
* len: zero, data = none
|
|
*/
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_CLEARFEATURE), priv->ctrl.type);
|
|
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE)
|
|
{
|
|
/* Disable B device from performing HNP */
|
|
|
|
#ifdef CONFIG_USBOTG
|
|
if (value.w == USBOTG_FEATURE_B_HNP_ENABLE)
|
|
{
|
|
/* Disable HNP */
|
|
#warning Missing Logic
|
|
}
|
|
|
|
/* Disable A device HNP support */
|
|
|
|
else if (value.w == USBOTG_FEATURE_A_HNP_SUPPORT)
|
|
{
|
|
/* Disable HNP support */
|
|
#warning Missing Logic
|
|
}
|
|
|
|
/* Disable alternate HNP support */
|
|
|
|
else if (value.w == USBOTG_FEATURE_A_ALT_HNP_SUPPORT)
|
|
{
|
|
/* Disable alternate HNP */
|
|
#warning Missing Logic
|
|
}
|
|
else
|
|
#endif
|
|
/* Disable remote wakeup */
|
|
|
|
if (value.w == USB_FEATURE_REMOTEWAKEUP)
|
|
{
|
|
priv->rwakeup = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Let the class implementation handle all other device features */
|
|
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
}
|
|
}
|
|
else if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_ENDPOINT)
|
|
{
|
|
epno = USB_EPNO(index.b[LSB]);
|
|
if (epno > 0 && epno < PIC32MX_NENDPOINTS && index.b[MSB] == 0 &&
|
|
value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0)
|
|
{
|
|
privep = &priv->eplist[epno];
|
|
privep->halted = false;
|
|
ret = pic32mx_epstall(&privep->ep, true);
|
|
UNUSED(ret);
|
|
}
|
|
else
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADCLEARFEATURE), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Let the class implementation handle all other recipients. */
|
|
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_SETFEATURE:
|
|
{
|
|
/* type: host-to-device; recipient = device, interface, endpoint
|
|
* value: feature selector
|
|
* index: zero interface endpoint;
|
|
* len: 0; data = none
|
|
*/
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_SETFEATURE), priv->ctrl.type);
|
|
|
|
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE)
|
|
{
|
|
/* Enable B device to perform HNP */
|
|
|
|
#ifdef CONFIG_USBOTG
|
|
if (value.w == USBOTG_FEATURE_B_HNP_ENABLE)
|
|
{
|
|
/* Enable HNP */
|
|
#warning "Missing logic"
|
|
}
|
|
|
|
/* Enable A device HNP supports */
|
|
|
|
else if (value.w == USBOTG_FEATURE_A_HNP_SUPPORT)
|
|
{
|
|
/* Enable HNP support */
|
|
#warning "Missing logic"
|
|
}
|
|
|
|
/* Another port on the A device supports HNP */
|
|
|
|
else if (value.w == USBOTG_FEATURE_A_ALT_HNP_SUPPORT)
|
|
{
|
|
/* Enable alternate HNP */
|
|
#warning "Missing logic"
|
|
}
|
|
else
|
|
#endif
|
|
|
|
if (value.w == USB_FEATURE_REMOTEWAKEUP)
|
|
{
|
|
priv->rwakeup = 0;
|
|
}
|
|
else if (value.w == USB_FEATURE_TESTMODE)
|
|
{
|
|
/* Special case recipient=device test mode */
|
|
|
|
ullinfo("test mode: %d\n", index.w);
|
|
}
|
|
else
|
|
{
|
|
/* Let the class implementation handle all other device features */
|
|
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
}
|
|
}
|
|
else if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_ENDPOINT)
|
|
{
|
|
/* Handler recipient=endpoint */
|
|
|
|
epno = USB_EPNO(index.b[LSB]);
|
|
if (epno < PIC32MX_NENDPOINTS && index.b[MSB] == 0 &&
|
|
value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0)
|
|
{
|
|
privep = &priv->eplist[epno];
|
|
privep->halted = true;
|
|
ret = pic32mx_epstall(&privep->ep, false);
|
|
UNUSED(ret);
|
|
}
|
|
else
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADSETFEATURE), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The class driver handles all recipients except recipient=endpoint */
|
|
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_SETADDRESS:
|
|
{
|
|
/* type: host-to-device; recipient = device
|
|
* value: device address
|
|
* index: 0
|
|
* len: 0; data = none
|
|
*/
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EP0SETUPSETADDRESS), value.w);
|
|
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) != USB_REQ_RECIPIENT_DEVICE ||
|
|
index.w != 0 || len.w != 0 || value.w > 127)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADSETADDRESS), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
else
|
|
{
|
|
/* Note that setting of the device address will be deferred. A zero-length
|
|
* packet will be sent and the device address will be set when the zero-
|
|
* length packet transfer completes.
|
|
*/
|
|
|
|
priv->devstate = DEVSTATE_ADDRPENDING;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_GETDESCRIPTOR:
|
|
/* type: device-to-host; recipient = device
|
|
* value: descriptor type and index
|
|
* index: 0 or language ID;
|
|
* len: descriptor len; data = descriptor
|
|
*/
|
|
case USB_REQ_SETDESCRIPTOR:
|
|
/* type: host-to-device; recipient = device
|
|
* value: descriptor type and index
|
|
* index: 0 or language ID;
|
|
* len: descriptor len; data = descriptor
|
|
*/
|
|
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_GETSETDESC), priv->ctrl.type);
|
|
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE)
|
|
{
|
|
/* The request seems valid... let the class implementation handle it */
|
|
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
}
|
|
else
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADGETSETDESC), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_GETCONFIGURATION:
|
|
/* type: device-to-host; recipient = device
|
|
* value: 0;
|
|
* index: 0;
|
|
* len: 1; data = configuration value
|
|
*/
|
|
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_GETCONFIG), priv->ctrl.type);
|
|
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE &&
|
|
value.w == 0 && index.w == 0 && len.w == 1)
|
|
{
|
|
/* The request seems valid... let the class implementation handle it */
|
|
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
}
|
|
else
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADGETCONFIG), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_SETCONFIGURATION:
|
|
/* type: host-to-device; recipient = device
|
|
* value: configuration value
|
|
* index: 0;
|
|
* len: 0; data = none
|
|
*/
|
|
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_SETCONFIG), priv->ctrl.type);
|
|
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE &&
|
|
index.w == 0 && len.w == 0)
|
|
{
|
|
/* The request seems valid... let the class implementation handle it */
|
|
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
}
|
|
else
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADSETCONFIG), 0);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_GETINTERFACE:
|
|
/* type: device-to-host; recipient = interface
|
|
* value: 0
|
|
* index: interface;
|
|
* len: 1; data = alt interface
|
|
*/
|
|
case USB_REQ_SETINTERFACE:
|
|
/* type: host-to-device; recipient = interface
|
|
* value: alternate setting
|
|
* index: interface;
|
|
* len: 0; data = none
|
|
*/
|
|
|
|
{
|
|
/* Let the class implementation handle the request */
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_GETSETIF), priv->ctrl.type);
|
|
pic32mx_dispatchrequest(priv);
|
|
dispatched = true;
|
|
}
|
|
break;
|
|
|
|
case USB_REQ_SYNCHFRAME:
|
|
/* type: device-to-host; recipient = endpoint
|
|
* value: 0
|
|
* index: endpoint;
|
|
* len: 2; data = frame number
|
|
*/
|
|
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_SYNCHFRAME), 0);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDCTRLREQ), priv->ctrl.req);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* PKTDIS bit is set when a Setup Transaction is received. Clear to resume
|
|
* packet processing.
|
|
*/
|
|
|
|
resume_packet_processing:
|
|
regval = pic32mx_getreg(PIC32MX_USB_CON);
|
|
regval &= ~USB_CON_PKTDIS;
|
|
pic32mx_putreg(regval, PIC32MX_USB_CON);
|
|
|
|
/* At this point, the request has been handled and there are three possible
|
|
* outcomes:
|
|
*
|
|
* 1. The setup request was successfully handled above and a response packet
|
|
* must be sent (may be a zero length packet).
|
|
* 2. The request was successfully handled by the class implementation. In
|
|
* case, the EP0 IN response has already been queued and the local variable
|
|
* 'dispatched' will be set to true and ctrlstate != CTRLSTATE_STALL;
|
|
* 3. An error was detected in either the above logic or by the class implementation
|
|
* logic. In either case, priv->state will be set CTRLSTATE_STALL
|
|
* to indicate this case.
|
|
*
|
|
* NOTE: Non-standard requests are a special case. They are handled by the
|
|
* class implementation and this function returned early above, skipping this
|
|
* logic altogether.
|
|
*/
|
|
|
|
if (!dispatched && (priv->ctrlstate != CTRLSTATE_STALL))
|
|
{
|
|
/* The SETUP command was not dispatched to the class driver and the SETUP
|
|
* command did not cause a stall. We will respond. First, restrict the
|
|
* data length to the length requested in the setup packet
|
|
*/
|
|
|
|
if (nbytes > len.w)
|
|
{
|
|
nbytes = len.w;
|
|
}
|
|
|
|
/* Send the EP0 SETUP response (might be a zero-length packet) */
|
|
|
|
pic32mx_epwrite(ep0, ep0->bdtin, response.b, nbytes);
|
|
priv->ctrlstate = CTRLSTATE_WAITSETUP;
|
|
}
|
|
|
|
/* Did we stall? This might have occurred from the above logic OR the stall
|
|
* condition may have been set less obviously in pic32mx_dispatchrequest().
|
|
* In either case, we handle the stall condition the same.
|
|
*
|
|
* However, bad things happen if we try to stall a SETUP packet. So lets
|
|
* not. If we wait a bit, things will recover. Hmmm.. If we completed
|
|
* the data phase (perhaps by sending a NULL packet), then I think we
|
|
* could stall the endpoint and perhaps speed things up a bit???.
|
|
*/
|
|
|
|
/* Set up the BDT to accept the next setup commend. */
|
|
|
|
pic32mx_ep0nextsetup(priv);
|
|
priv->ctrlstate = CTRLSTATE_WAITSETUP;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0incomplete
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_ep0incomplete(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
struct pic32mx_ep_s *ep0 = &priv->eplist[EP0];
|
|
volatile struct usbotg_bdtentry_s *bdtlast;
|
|
int ret;
|
|
|
|
/* Get the last BDT and make sure that we own it. */
|
|
|
|
bdtlast = ep0->bdtin;
|
|
|
|
/* Make sure that we own the last BDT. */
|
|
|
|
bdtlast->status = 0;
|
|
bdtlast->addr = 0;
|
|
|
|
/* Are we processing the completion of one packet of an outgoing request
|
|
* from the class driver?
|
|
*/
|
|
|
|
if (priv->ctrlstate == CTRLSTATE_WRREQUEST)
|
|
{
|
|
/* An outgoing EP0 transfer has completed. Update the byte count and
|
|
* check for the completion of the transfer.
|
|
*
|
|
* NOTE: pic32mx_wrcomplete() will toggle bdtin to the other buffer so
|
|
* we do not need to that for this case.
|
|
*/
|
|
|
|
pic32mx_wrcomplete(priv, &priv->eplist[EP0]);
|
|
|
|
/* Handle the next queue IN transfer. If there are no further queued
|
|
* IN transfers, pic32mx_wrrequest will return -ENODATA and that is the
|
|
* only expected error return value in this context.
|
|
*/
|
|
|
|
ret = pic32mx_wrrequest(priv, &priv->eplist[EP0]);
|
|
if (ret < 0)
|
|
{
|
|
DEBUGASSERT(ret == -ENODATA);
|
|
|
|
/* If there is nothing to be sent, then we need to configure to
|
|
* receive the next SETUP packet.
|
|
*/
|
|
|
|
priv->ctrlstate = CTRLSTATE_WAITSETUP;
|
|
}
|
|
}
|
|
|
|
/* No.. Are we processing the completion of a status response? */
|
|
|
|
else if (priv->ctrlstate == CTRLSTATE_WAITSETUP)
|
|
{
|
|
/* Get the next IN BDT */
|
|
|
|
if (bdtlast == &g_bdt[EP0_IN_EVEN])
|
|
{
|
|
ep0->bdtin = &g_bdt[EP0_IN_ODD];
|
|
}
|
|
else
|
|
{
|
|
DEBUGASSERT(bdtlast == &g_bdt[EP0_IN_ODD]);
|
|
ep0->bdtin = &g_bdt[EP0_IN_EVEN];
|
|
}
|
|
|
|
/* Look at the saved SETUP command. Was it a SET ADDRESS request?
|
|
* If so, then now is the time to set the address.
|
|
*/
|
|
|
|
if (priv->devstate == DEVSTATE_ADDRPENDING)
|
|
{
|
|
uint16_t addr = GETUINT16(priv->ctrl.value);
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EP0ADDRESSSET), addr);
|
|
|
|
/* This should be the equivalent state */
|
|
|
|
DEBUGASSERT(priv->ctrl.req == USB_REQ_SETADDRESS &&
|
|
(priv->ctrl.type & REQRECIPIENT_MASK) ==
|
|
(USB_REQ_TYPE_STANDARD | USB_REQ_RECIPIENT_DEVICE));
|
|
|
|
/* Set (or clear) the address */
|
|
|
|
pic32mx_putreg(addr, PIC32MX_USB_ADDR);
|
|
if (addr > 0)
|
|
{
|
|
priv->devstate = DEVSTATE_ADDRESS;
|
|
}
|
|
else
|
|
{
|
|
priv->devstate = DEVSTATE_DEFAULT;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* No other state is expected in this context */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDSTATE), priv->ctrlstate);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0outcomplete
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_ep0outcomplete(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
struct pic32mx_ep_s *ep0 = &priv->eplist[EP0];
|
|
|
|
switch (priv->ctrlstate)
|
|
{
|
|
/* Read request in progress */
|
|
|
|
case CTRLSTATE_RDREQUEST:
|
|
|
|
/* Process the next read request for EP0 */
|
|
|
|
pic32mx_rdcomplete(priv, ep0);
|
|
|
|
/* Was this the end of the OUT transfer? */
|
|
|
|
if (priv->ctrlstate == CTRLSTATE_WAITSETUP)
|
|
{
|
|
/* Prepare EP0 OUT for the next SETUP transaction. */
|
|
|
|
pic32mx_ep0rdcomplete(priv);
|
|
}
|
|
break;
|
|
|
|
/* No transfer in progress, waiting for SETUP */
|
|
|
|
case CTRLSTATE_WAITSETUP:
|
|
{
|
|
/* In this case the last OUT transaction must have been a status
|
|
* stage of a CTRLSTATE_WRREQUEST: Prepare EP0 OUT for the next SETUP
|
|
* transaction.
|
|
*/
|
|
|
|
pic32mx_ep0nextsetup(priv);
|
|
}
|
|
break;
|
|
|
|
/* Unexpected state OR host aborted the OUT transfer before it completed,
|
|
* STALL the endpoint in either case
|
|
*/
|
|
|
|
default:
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDSTATE), priv->ctrlstate);
|
|
priv->ctrlstate = CTRLSTATE_STALL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0transfer
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_ep0transfer(struct pic32mx_usbdev_s *priv, uint16_t ustat)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdt;
|
|
|
|
/* The following information is available in the status register :
|
|
*
|
|
* ENDPT - The 4 bit endpoint number that cause the interrupt.
|
|
* DIR - The direction of the endpoint.
|
|
* PPBI - The ping-pong buffer used in the transaction.
|
|
*/
|
|
|
|
priv->ep0done = 0;
|
|
|
|
/* Check if the last transaction was an EP0 OUT transaction */
|
|
|
|
if ((ustat & USB_STAT_DIR) == USB_STAT_DIR_OUT)
|
|
{
|
|
int index;
|
|
|
|
/* It was an EP0 OUT transaction. Get the index to the BDT. */
|
|
|
|
index = ((ustat & USB_STAT_PPBI) == 0 ? EP0_OUT_EVEN : EP0_OUT_ODD);
|
|
bdt = &g_bdt[index];
|
|
priv->eplist[0].bdtout = bdt;
|
|
|
|
bdtinfo("EP0 BDT OUT [%p] {%08x, %08x}\n",
|
|
bdt, bdt->status, bdt->addr);
|
|
|
|
/* Check the current EP0 OUT buffer contains a SETUP packet */
|
|
|
|
if (((bdt->status & USB_BDT_PID_MASK) >> USB_BDT_PID_SHIFT) == USB_PID_SETUP_TOKEN)
|
|
{
|
|
/* Check if the SETUP transaction data went into the priv->ctrl
|
|
* buffer. If not, then we will need to copy it.
|
|
*/
|
|
|
|
if (bdt->addr != (uint8_t *)PHYS_ADDR(&priv->ctrl))
|
|
{
|
|
void *src = (void *)VIRT_ADDR(bdt->addr);
|
|
void *dest = &priv->ctrl;
|
|
|
|
memcpy(dest, src, USB_SIZEOF_CTRLREQ);
|
|
bdt->addr = (uint8_t *)PHYS_ADDR(&priv->ctrl);
|
|
}
|
|
|
|
/* Handle the control OUT transfer */
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EP0SETUPDONE), bdt->status);
|
|
pic32mx_ep0setup(priv);
|
|
}
|
|
else
|
|
{
|
|
/* Handle the data OUT transfer */
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EP0OUTDONE), ustat);
|
|
pic32mx_ep0outcomplete(priv);
|
|
}
|
|
}
|
|
|
|
/* No.. it was an EP0 IN transfer */
|
|
|
|
else /* if ((status & USB_STAT_DIR) == USB_STAT_DIR_IN) */
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EP0INDONE), ustat);
|
|
|
|
/* Handle the IN transfer complete */
|
|
|
|
pic32mx_ep0incomplete(priv);
|
|
}
|
|
|
|
/* Check for a request to stall EP0 */
|
|
|
|
if (priv->ctrlstate == CTRLSTATE_STALL)
|
|
{
|
|
/* Stall EP0 */
|
|
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_EP0SETUPSTALLED), priv->ctrlstate);
|
|
(void)pic32mx_epstall(&priv->eplist[EP0].ep, false);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_interrupt
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_interrupt(int irq, void *context)
|
|
{
|
|
/* For now there is only one USB controller, but we will always refer to
|
|
* it using a pointer to make any future ports to multiple USB controllers
|
|
* easier.
|
|
*/
|
|
|
|
struct pic32mx_usbdev_s *priv = &g_usbdev;
|
|
uint16_t usbir;
|
|
uint16_t otgir;
|
|
uint16_t regval;
|
|
int i;
|
|
|
|
/* Get the set of pending USB and OTG interrupts interrupts */
|
|
|
|
usbir = pic32mx_getreg(PIC32MX_USB_IR) & pic32mx_getreg(PIC32MX_USB_IE);
|
|
otgir = pic32mx_getreg(PIC32MX_USBOTG_IR) & pic32mx_getreg(PIC32MX_USBOTG_IE);
|
|
usbtrace(TRACE_INTENTRY(PIC32MX_TRACEINTID_INTERRUPT), usbir | otgir);
|
|
|
|
#ifdef CONFIG_USBOTG
|
|
/* Session Request Protocol (SRP) Time Out Check */
|
|
|
|
/* if USB OTG SRP is ready */
|
|
# warning "Missing logic"
|
|
{
|
|
/* Check if the 1 millisecond timer has expired */
|
|
|
|
if (otgir & OTG_INT_T1MSEC) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_T1MSEC), otgir);
|
|
|
|
/* Check for the USB OTG SRP timeout */
|
|
# warning "Missing logic"
|
|
{
|
|
/* Handle OTG events of the SRP timeout has expired */
|
|
# warning "Missing logic"
|
|
}
|
|
|
|
/* Clear Interrupt 1 msec timer Flag */
|
|
|
|
pic32mx_putreg(USBOTG_INT_T1MSEC, PIC32MX_USBOTG_IR);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Handle events while we are in the attached state */
|
|
|
|
if (priv->devstate == DEVSTATE_ATTACHED)
|
|
{
|
|
/* Clear all USB interrupts */
|
|
|
|
pic32mx_putreg(USB_INT_ALL, PIC32MX_USB_IR);
|
|
|
|
/* Make sure that the USE reset and IDLE detect interrupts are enabled */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USB_IE);
|
|
regval |= (USB_INT_URST | USB_INT_IDLE);
|
|
pic32mx_putreg(regval, PIC32MX_USB_IE);
|
|
|
|
/* Now were are in the powered state */
|
|
|
|
priv->devstate = DEVSTATE_POWERED;
|
|
}
|
|
|
|
#ifdef CONFIG_USBOTG
|
|
/* Check if the ID Pin Changed State */
|
|
|
|
if (otgir & USBOTG_INT_ID) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_OTGID), otgir);
|
|
|
|
/* Re-detect and re-initialize */
|
|
#warning "Missing logic"
|
|
|
|
pic32mx_putreg(USBOTG_INT_ID, PIC32MX_USBOTG_IR);
|
|
}
|
|
#endif
|
|
|
|
/* Service the USB Activity Interrupt */
|
|
|
|
if ((otgir & USBOTG_INT_ACTV) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_WKUP), otgir);
|
|
|
|
/* Wake-up from susepnd mode */
|
|
|
|
pic32mx_putreg(USBOTG_INT_ACTV, PIC32MX_USBOTG_IR);
|
|
pic32mx_resume(priv);
|
|
}
|
|
|
|
/* It is pointless to continue servicing if the device is in suspend mode. */
|
|
|
|
if ((pic32mx_getreg(PIC32MX_USB_PWRC) & USB_PWRC_USUSPEND) != 0)
|
|
{
|
|
/* Just clear the interrupt and return */
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_SUSPENDED), pic32mx_getreg(PIC32MX_USB_PWRC));
|
|
goto interrupt_exit;
|
|
}
|
|
|
|
/* Service USB Bus Reset Interrupt. When bus reset is received during
|
|
* suspend, ACTVIF will be set first, once the UCONbits.SUSPND is clear,
|
|
* then the URSTIF bit will be asserted. This is why URSTIF is checked
|
|
* after ACTVIF. The USB reset flag is masked when the USB state is in
|
|
* DEVSTATE_DETACHED or DEVSTATE_ATTACHED, and therefore cannot cause a
|
|
* USB reset event during these two states.
|
|
*/
|
|
|
|
if ((usbir & USB_INT_URST) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_RESET), usbir);
|
|
|
|
/* Reset interrupt received. Restore our initial state. NOTE: the
|
|
* hardware automatically resets the USB address, so we really just
|
|
* need reset any existing configuration/transfer states.
|
|
*/
|
|
|
|
pic32mx_reset(priv);
|
|
priv->devstate = DEVSTATE_DEFAULT;
|
|
|
|
#ifdef CONFIG_USBOTG
|
|
/* Disable and deactivate HNP */
|
|
#warning Missing Logic
|
|
#endif
|
|
/* Acknowlege the reset interrupt */
|
|
|
|
pic32mx_putreg(USB_INT_URST, PIC32MX_USB_IR);
|
|
goto interrupt_exit;
|
|
}
|
|
|
|
/* Service IDLE interrupts */
|
|
|
|
if ((usbir & USB_INT_IDLE) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_IDLE), usbir);
|
|
|
|
#ifdef CONFIG_USBOTG
|
|
/* If Suspended, Try to switch to Host */
|
|
#warning "Missing logic"
|
|
#else
|
|
pic32mx_suspend(priv);
|
|
|
|
#endif
|
|
pic32mx_putreg(USB_INT_IDLE, PIC32MX_USB_IR);
|
|
}
|
|
|
|
/* Service SOF interrupts */
|
|
|
|
#ifdef CONFIG_USB_SOFINTS
|
|
if ((usbir & USB_INT_SOF) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_SOF), 0);
|
|
|
|
/* I am not sure why you would ever enable SOF interrupts */
|
|
|
|
pic32mx_putreg(USB_INT_SOF, PIC32MX_USB_IR);
|
|
}
|
|
#endif
|
|
|
|
/* Service stall interrupts */
|
|
|
|
if ((usbir & USB_INT_STALL) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_STALL), usbir);
|
|
|
|
pic32mx_ep0stall(priv);
|
|
|
|
/* Clear the pending STALL interrupt */
|
|
|
|
pic32mx_putreg(USB_INT_STALL, PIC32MX_USB_IR);
|
|
}
|
|
|
|
/* Service error interrupts */
|
|
|
|
if ((usbir & USB_INT_UERR) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_UERR), usbir);
|
|
ullerr("ERROR: EIR=%04x\n", pic32mx_getreg(PIC32MX_USB_EIR));
|
|
|
|
/* Clear all pending USB error interrupts */
|
|
|
|
pic32mx_putreg(USB_EINT_ALL, PIC32MX_USB_EIR);
|
|
}
|
|
|
|
/* There is no point in continuing if the host has not sent a bus reset.
|
|
* Once bus reset is received, the device transitions into the DEFAULT
|
|
* state and is ready for communication.
|
|
*/
|
|
|
|
if (priv->devstate < DEVSTATE_DEFAULT)
|
|
{
|
|
/* Just clear the interrupt and return */
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_WAITRESET), priv->devstate);
|
|
goto interrupt_exit;
|
|
}
|
|
|
|
/* Service USB Transaction Complete Interrupt */
|
|
|
|
if ((usbir & USB_INT_TRN) != 0)
|
|
{
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_TRNC), usbir);
|
|
|
|
/* Drain the USAT FIFO entries. If the USB FIFO ever gets full, USB
|
|
* bandwidth utilization can be compromised, and the device won't be
|
|
* able to receive SETUP packets.
|
|
*/
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
uint8_t epno;
|
|
|
|
/* Check the pending interrupt register. Is token processing complete. */
|
|
|
|
if ((pic32mx_getreg(PIC32MX_USB_IR) & USB_INT_TRN) != 0)
|
|
{
|
|
regval = pic32mx_getreg(PIC32MX_USB_STAT);
|
|
pic32mx_putreg(USB_INT_TRN, PIC32MX_USB_IR);
|
|
|
|
usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_TRNCS), regval);
|
|
|
|
/* Handle the endpoint transfer complete event. */
|
|
|
|
epno = (regval & USB_STAT_ENDPT_MASK) >> USB_STAT_ENDPT_SHIFT;
|
|
if (epno == 0)
|
|
{
|
|
pic32mx_ep0transfer(priv, regval);
|
|
}
|
|
else
|
|
{
|
|
pic32mx_eptransfer(priv, epno, regval);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* USTAT FIFO must be empty. */
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clear the pending USB interrupt. Goto is used in the above to assure
|
|
* that all interrupt exists pass through this logic.
|
|
*/
|
|
|
|
interrupt_exit:
|
|
up_clrpend_irq(PIC32MX_IRQSRC_USB);
|
|
usbtrace(TRACE_INTEXIT(PIC32MX_TRACEINTID_INTERRUPT), usbir | otgir);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Suspend/Resume Helpers
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: pic32mx_suspend
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_suspend(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
uint16_t regval;
|
|
|
|
/* Notify the class driver of the suspend event */
|
|
|
|
if (priv->driver)
|
|
{
|
|
CLASS_SUSPEND(priv->driver, &priv->usbdev);
|
|
}
|
|
|
|
/* Enable the ACTV interrupt.
|
|
*
|
|
* NOTE: Do not clear UIRbits.ACTVIF here! Reason: ACTVIF is only
|
|
* generated once an IDLEIF has been generated. This is a 1:1 ratio
|
|
* interrupt generation. For every IDLEIF, there will be only one ACTVIF
|
|
* regardless of the number of subsequent bus transitions. If the ACTIF
|
|
* is cleared here, a problem could occur. The driver services IDLEIF
|
|
* first because ACTIVIE=0. If this routine clears the only ACTIVIF,
|
|
* then it can never get out of the suspend mode.
|
|
*/
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USBOTG_IE);
|
|
regval |= USBOTG_INT_ACTV;
|
|
pic32mx_putreg(regval, PIC32MX_USBOTG_IE);
|
|
|
|
/* Disable further IDLE interrupts. Once is enough. */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USB_IE);
|
|
regval &= ~USB_INT_IDLE;
|
|
pic32mx_putreg(regval, PIC32MX_USB_IE);
|
|
|
|
/* Invoke a callback into board-specific logic. The board-specific logic
|
|
* may enter into sleep or idle modes or switch to a slower clock, etc.
|
|
*/
|
|
|
|
pic32mx_usbsuspend((struct usbdev_s *)priv, false);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_resume
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_resume(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
irqstate_t flags;
|
|
uint16_t regval;
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Start RESUME signaling */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USB_CON);
|
|
regval |= USB_CON_RESUME;
|
|
pic32mx_putreg(regval, PIC32MX_USB_CON);
|
|
|
|
/* Keep the RESUME line set for 1-13 ms */
|
|
|
|
up_mdelay(10);
|
|
|
|
regval &= ~USB_CON_RESUME;
|
|
pic32mx_putreg(regval, PIC32MX_USB_CON);
|
|
|
|
/* This function is called when the USB activity interrupt occurs.
|
|
* If using clock switching, this is the place to call out to
|
|
* logic to restore the original MCU core clock frequency.
|
|
*/
|
|
|
|
pic32mx_usbsuspend((struct usbdev_s *)priv, true);
|
|
|
|
/* Disable further activity interrupts */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USBOTG_IE);
|
|
regval &= ~USBOTG_INT_ACTV;
|
|
pic32mx_putreg(regval, PIC32MX_USBOTG_IE);
|
|
|
|
/* The ACTVIF bit cannot be cleared immediately after the USB module wakes
|
|
* up from Suspend or while the USB module is suspended. A few clock cycles
|
|
* are required to synchronize the internal hardware state machine before
|
|
* the ACTIVIF bit can be cleared by firmware. Clearing the ACTVIF bit
|
|
* before the internal hardware is synchronized may not have an effect on
|
|
* the value of ACTVIF. Additionally, if the USB module uses the clock from
|
|
* the 96 MHz PLL source, then after clearing the SUSPND bit, the USB
|
|
* module may not be immediately operational while waiting for the 96 MHz
|
|
* PLL to lock.
|
|
*/
|
|
|
|
pic32mx_putreg(USB_INT_IDLE, PIC32MX_USBOTG_IR);
|
|
|
|
/* Notify the class driver of the resume event */
|
|
|
|
if (priv->driver)
|
|
{
|
|
CLASS_RESUME(priv->driver, &priv->usbdev);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Endpoint Helpers
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: pic32mx_epreserve
|
|
****************************************************************************/
|
|
|
|
static inline struct pic32mx_ep_s *
|
|
pic32mx_epreserve(struct pic32mx_usbdev_s *priv, uint8_t epset)
|
|
{
|
|
struct pic32mx_ep_s *privep = NULL;
|
|
irqstate_t flags;
|
|
int epndx = 0;
|
|
|
|
flags = enter_critical_section();
|
|
epset &= priv->epavail;
|
|
if (epset)
|
|
{
|
|
/* Select the lowest bit in the set of matching, available endpoints
|
|
* (skipping EP0)
|
|
*/
|
|
|
|
for (epndx = 1; epndx < PIC32MX_NENDPOINTS; epndx++)
|
|
{
|
|
uint8_t bit = PIC32MX_ENDP_BIT(epndx);
|
|
if ((epset & bit) != 0)
|
|
{
|
|
/* Mark the endpoint no longer available */
|
|
|
|
priv->epavail &= ~bit;
|
|
|
|
/* And return the pointer to the standard endpoint structure */
|
|
|
|
privep = &priv->eplist[epndx];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return privep;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epunreserve
|
|
****************************************************************************/
|
|
|
|
static inline void
|
|
pic32mx_epunreserve(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep)
|
|
{
|
|
irqstate_t flags = enter_critical_section();
|
|
priv->epavail |= PIC32MX_ENDP_BIT(USB_EPNO(privep->ep.eplog));
|
|
leave_critical_section(flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epreserved
|
|
****************************************************************************/
|
|
|
|
static inline bool
|
|
pic32mx_epreserved(struct pic32mx_usbdev_s *priv, int epno)
|
|
{
|
|
return ((priv->epavail & PIC32MX_ENDP_BIT(epno)) == 0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_ep0configure
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_ep0configure(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
volatile struct usbotg_bdtentry_s *bdt;
|
|
struct pic32mx_ep_s *ep0;
|
|
uint32_t bytecount;
|
|
|
|
/* Enable the EP0 endpoint */
|
|
|
|
pic32mx_putreg(PIC32MX_EP_CONTROL, PIC32MX_USB_EP0);
|
|
|
|
/* Configure the OUT BDTs. We assume that the ping-poing buffer index has
|
|
* just been reset and we expect to receive on the EVEN BDT first. Data
|
|
* toggle synchronization is not needed for SETUP packets.
|
|
*/
|
|
|
|
ep0 = &priv->eplist[EP0];
|
|
bytecount = (USB_SIZEOF_CTRLREQ << USB_BDT_BYTECOUNT_SHIFT);
|
|
|
|
bdt = &g_bdt[EP0_OUT_EVEN];
|
|
bdt->addr = (uint8_t *)PHYS_ADDR(&priv->ctrl);
|
|
bdt->status = (USB_BDT_UOWN | bytecount);
|
|
ep0->bdtout = bdt;
|
|
|
|
bdt++;
|
|
bdt->status = (USB_BDT_UOWN | bytecount);
|
|
bdt->addr = (uint8_t *)PHYS_ADDR(&priv->ctrl);
|
|
|
|
/* Configure the IN BDTs. */
|
|
|
|
bdt = &g_bdt[EP0_IN_EVEN];
|
|
bdt->status = 0;
|
|
bdt->addr = 0;
|
|
ep0->bdtin = bdt;
|
|
|
|
bdt++;
|
|
bdt->status = 0;
|
|
bdt->addr = 0;
|
|
|
|
/* Data toggling is not used on SETUP transfers. And IN transfer resulting
|
|
* from a SETUP command should begin with DATA1.
|
|
*/
|
|
|
|
ep0->rxdata1 = 0;
|
|
ep0->txdata1 = 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Endpoint operations
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: pic32mx_epconfigure
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_epconfigure(struct usbdev_ep_s *ep,
|
|
const struct usb_epdesc_s *desc,
|
|
bool last)
|
|
{
|
|
struct pic32mx_ep_s *privep = (struct pic32mx_ep_s *)ep;
|
|
volatile struct usbotg_bdtentry_s *bdt;
|
|
uint16_t maxpacket;
|
|
uint16_t regval;
|
|
uint8_t epno;
|
|
bool epin;
|
|
bool bidi;
|
|
int index;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!ep || !desc)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
ullerr("ERROR: ep=%p desc=%p\n");
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
/* Get the unadorned endpoint address */
|
|
|
|
epno = USB_EPNO(desc->addr);
|
|
epin = USB_ISEPIN(desc->addr);
|
|
|
|
usbtrace(TRACE_EPCONFIGURE, (uint16_t)epno);
|
|
DEBUGASSERT(epno == USB_EPNO(ep->eplog));
|
|
|
|
/* Set the requested type */
|
|
|
|
switch (desc->attr & USB_EP_ATTR_XFERTYPE_MASK)
|
|
{
|
|
case USB_EP_ATTR_XFER_INT: /* Interrupt endpoint */
|
|
regval = epin ? PIC32MX_EP_INTIN : PIC32MX_EP_INTOUT;
|
|
break;
|
|
|
|
case USB_EP_ATTR_XFER_BULK: /* Bulk endpoint */
|
|
regval = epin ? PIC32MX_EP_BULKIN : PIC32MX_EP_BULKOUT;
|
|
break;
|
|
|
|
case USB_EP_ATTR_XFER_ISOC: /* Isochronous endpoint */
|
|
regval = epin ? PIC32MX_EP_ISOCIN : PIC32MX_EP_ISOCOUT;
|
|
break;
|
|
|
|
case USB_EP_ATTR_XFER_CONTROL: /* Control endpoint */
|
|
regval = PIC32MX_EP_CONTROL;
|
|
bidi = true;
|
|
break;
|
|
|
|
default:
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADEPTYPE), (uint16_t)desc->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Enable the endpoint */
|
|
|
|
pic32mx_putreg(regval, PIC32MX_USB_EP(epno));
|
|
|
|
/* Setup up buffer descriptor table (BDT) entry/ies for this endpoint */
|
|
|
|
if (epin || bidi)
|
|
{
|
|
/* Get the pointer to BDT entry */
|
|
|
|
index = EP(epno, EP_DIR_IN, EP_PP_EVEN);
|
|
bdt = &g_bdt[index];
|
|
privep->bdtin = bdt;
|
|
|
|
/* Mark that we own the entry */
|
|
|
|
bdt->status = 0;
|
|
bdt->addr = 0;
|
|
|
|
bdtinfo("EP%d BDT IN [%p] {%08x, %08x}\n",
|
|
epno, bdt, bdt->status, bdt->addr);
|
|
|
|
/* Now do the same for the other buffer. */
|
|
|
|
bdt++;
|
|
bdt->status = 0;
|
|
bdt->addr = 0;
|
|
|
|
bdtinfo("EP%d BDT IN [%p] {%08x, %08x}\n",
|
|
epno, bdt, bdt->status, bdt->addr);
|
|
}
|
|
|
|
if (!epin || bidi)
|
|
{
|
|
index = EP(epno, EP_DIR_OUT, EP_PP_EVEN);
|
|
bdt = &g_bdt[index];
|
|
privep->bdtout = bdt;
|
|
|
|
/* Mark that we own the entry */
|
|
|
|
bdt->status = 0;
|
|
bdt->addr = 0;
|
|
|
|
bdtinfo("EP%d BDT OUT [%p] {%08x, %08x}\n",
|
|
epno, bdt, bdt->status, bdt->addr);
|
|
|
|
/* Now do the same for the other buffer. */
|
|
|
|
bdt++;
|
|
bdt->status = 0;
|
|
bdt->addr = 0;
|
|
|
|
bdtinfo("EP%d BDT OUT [%p] {%08x, %08x}\n",
|
|
epno, bdt, bdt->status, bdt->addr);
|
|
}
|
|
|
|
/* Get the maxpacket size of the endpoint. */
|
|
|
|
maxpacket = GETUINT16(desc->mxpacketsize);
|
|
DEBUGASSERT(maxpacket <= PIC32MX_MAXPACKET_SIZE);
|
|
ep->maxpacket = maxpacket;
|
|
|
|
/* Set the full, logic EP number (that includes direction encoded in bit 7) */
|
|
|
|
if (epin)
|
|
{
|
|
ep->eplog = USB_EPIN(epno);
|
|
}
|
|
else
|
|
{
|
|
ep->eplog = USB_EPOUT(epno);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epdisable
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_epdisable(struct usbdev_ep_s *ep)
|
|
{
|
|
struct pic32mx_ep_s *privep;
|
|
volatile uint32_t *ptr;
|
|
int epno;
|
|
int i;
|
|
irqstate_t flags;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!ep)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
ullerr("ERROR: ep=%p\n", ep);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
privep = (struct pic32mx_ep_s *)ep;
|
|
epno = USB_EPNO(ep->eplog);
|
|
usbtrace(TRACE_EPDISABLE, epno);
|
|
|
|
/* Cancel any ongoing activity */
|
|
|
|
flags = enter_critical_section();
|
|
pic32mx_cancelrequests(privep, -ESHUTDOWN);
|
|
|
|
/* Disable the endpoint */
|
|
|
|
pic32mx_putreg(0, PIC32MX_USB_EP(epno));
|
|
|
|
/* Reset the BDTs for the endpoint. Four BDT entries per endpoint; Two
|
|
* 32-bit words per BDT.
|
|
*/
|
|
|
|
ptr = (uint32_t *)&g_bdt[EP(epno, EP_DIR_OUT, EP_PP_EVEN)];
|
|
for (i = 0; i < USB_BDT_WORD_SIZE * USB_NBDTS_PER_EP; i++)
|
|
{
|
|
*ptr++ = 0;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epallocreq
|
|
****************************************************************************/
|
|
|
|
static struct usbdev_req_s *pic32mx_epallocreq(struct usbdev_ep_s *ep)
|
|
{
|
|
struct pic32mx_req_s *privreq;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!ep)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
usbtrace(TRACE_EPALLOCREQ, USB_EPNO(ep->eplog));
|
|
|
|
privreq = (struct pic32mx_req_s *)kmm_malloc(sizeof(struct pic32mx_req_s));
|
|
if (!privreq)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_ALLOCFAIL), 0);
|
|
return NULL;
|
|
}
|
|
|
|
memset(privreq, 0, sizeof(struct pic32mx_req_s));
|
|
return &privreq->req;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epfreereq
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_epfreereq(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
|
|
{
|
|
struct pic32mx_req_s *privreq = (struct pic32mx_req_s *)req;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!ep || !req)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return;
|
|
}
|
|
#endif
|
|
usbtrace(TRACE_EPFREEREQ, USB_EPNO(ep->eplog));
|
|
|
|
kmm_free(privreq);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epsubmit
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_epsubmit(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
|
|
{
|
|
struct pic32mx_req_s *privreq = (struct pic32mx_req_s *)req;
|
|
struct pic32mx_ep_s *privep = (struct pic32mx_ep_s *)ep;
|
|
struct pic32mx_usbdev_s *priv;
|
|
irqstate_t flags;
|
|
uint8_t epno;
|
|
int ret = OK;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!req || !req->callback || !req->buf || !ep)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
ullerr("ERROR: req=%p callback=%p buf=%p ep=%p\n",
|
|
req, req->callback, req->buf, ep);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
usbtrace(TRACE_EPSUBMIT, USB_EPNO(ep->eplog));
|
|
priv = privep->dev;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!priv->driver)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_NOTCONFIGURED), priv->usbdev.speed);
|
|
ullerr("ERROR: driver=%p\n", priv->driver);
|
|
return -ESHUTDOWN;
|
|
}
|
|
#endif
|
|
|
|
/* Handle the request from the class driver */
|
|
|
|
epno = USB_EPNO(ep->eplog);
|
|
req->result = -EINPROGRESS;
|
|
req->xfrd = 0;
|
|
privreq->inflight[0] = 0;
|
|
#ifndef CONFIG_USBDEV_NOWRITEAHEAD
|
|
privreq->inflight[1] = 0;
|
|
#endif
|
|
flags = enter_critical_section();
|
|
|
|
/* Add the new request to the request queue for the OUT endpoint */
|
|
|
|
pic32mx_addlast(&privep->pend, privreq);
|
|
|
|
/* Handle IN (device-to-host) requests. NOTE: If the class device is
|
|
* using the bi-directional EP0, then we assume that they intend the EP0
|
|
* IN functionality.
|
|
*/
|
|
|
|
if (USB_ISEPIN(ep->eplog) || epno == EP0)
|
|
{
|
|
usbtrace(TRACE_INREQQUEUED(epno), req->len);
|
|
|
|
/* If the endpoint is not stalled and an IN endpoint BDT is available,
|
|
* then transfer the data now.
|
|
*/
|
|
|
|
if (!privep->stalled)
|
|
{
|
|
(void)pic32mx_wrrequest(priv, privep);
|
|
}
|
|
}
|
|
|
|
/* Handle OUT (host-to-device) requests */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_OUTREQQUEUED(epno), req->len);
|
|
|
|
/* Set up the read operation (unless the endpoint is stalled). Because
|
|
* the PIC32MX supports ping-pong* buffering. There may be two pending
|
|
* read requests. The following call will attempt to setup a read
|
|
* using this request for this endpoint. It is not harmful if this
|
|
* fails.
|
|
*/
|
|
|
|
if (!privep->stalled)
|
|
{
|
|
(void)pic32mx_rdrequest(priv, privep);
|
|
}
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epcancel
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_epcancel(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
|
|
{
|
|
struct pic32mx_ep_s *privep = (struct pic32mx_ep_s *)ep;
|
|
irqstate_t flags;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!ep || !req)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
usbtrace(TRACE_EPCANCEL, USB_EPNO(ep->eplog));
|
|
|
|
flags = enter_critical_section();
|
|
pic32mx_cancelrequests(privep, -EAGAIN);
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epbdtstall
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_epbdtstall(struct usbdev_ep_s *ep, bool resume, bool epin)
|
|
{
|
|
struct pic32mx_ep_s *privep;
|
|
struct pic32mx_usbdev_s *priv;
|
|
volatile struct usbotg_bdtentry_s *bdt;
|
|
volatile struct usbotg_bdtentry_s *otherbdt;
|
|
uint32_t regaddr;
|
|
uint16_t regval;
|
|
uint8_t epno;
|
|
|
|
/* Recover pointers */
|
|
|
|
privep = (struct pic32mx_ep_s *)ep;
|
|
priv = (struct pic32mx_usbdev_s *)privep->dev;
|
|
epno = USB_EPNO(ep->eplog);
|
|
|
|
/* Check for an IN endpoint */
|
|
|
|
if (epin)
|
|
{
|
|
/* Get a pointer to the current IN BDT */
|
|
|
|
bdt = privep->bdtin;
|
|
|
|
/* Get the other BDT */
|
|
|
|
otherbdt = &g_bdt[EP(epno, EP_DIR_IN, EP_PP_EVEN)];
|
|
if (otherbdt == bdt)
|
|
{
|
|
otherbdt++;
|
|
}
|
|
|
|
/* Reset the data toggle */
|
|
|
|
privep->txdata1 = false;
|
|
}
|
|
|
|
/* Otherwise it is an an OUT endpoint. */
|
|
|
|
else
|
|
{
|
|
/* Get a pointer to the current OUT BDT */
|
|
|
|
bdt = privep->bdtout;
|
|
|
|
/* Get a pointer to the other BDT */
|
|
|
|
otherbdt = &g_bdt[EP(epno, EP_DIR_OUT, EP_PP_EVEN)];
|
|
if (otherbdt == bdt)
|
|
{
|
|
otherbdt++;
|
|
}
|
|
|
|
/* Reset the data toggle */
|
|
|
|
privep->rxdata1 = false;
|
|
}
|
|
|
|
/* Handle the resume condition */
|
|
|
|
if (resume)
|
|
{
|
|
/* Resuming a stalled endpoint */
|
|
|
|
usbtrace(TRACE_EPRESUME, epno);
|
|
|
|
/* Point to the appropriate EP register */
|
|
|
|
regaddr = PIC32MX_USB_EP(epno);
|
|
|
|
/* Clear the STALL bit in the UEP register */
|
|
|
|
regval = pic32mx_getreg(regaddr);
|
|
regval &= ~USB_EP_EPSTALL;
|
|
pic32mx_putreg(regval, regaddr);
|
|
|
|
/* Check for the EP0 OUT endpoint. This is a special case because we
|
|
* need to set it up to receive the next setup packet (Hmmm... what
|
|
* if there are queued outgoing reponses. We need to revisit this.)
|
|
*/
|
|
|
|
if (epno == 0 && !epin)
|
|
{
|
|
uint32_t bytecount = (USB_SIZEOF_CTRLREQ << USB_BDT_BYTECOUNT_SHIFT);
|
|
uint32_t physaddr = PHYS_ADDR(&priv->ctrl);
|
|
|
|
/* Configure the other BDT to receive a SETUP command. */
|
|
|
|
otherbdt->addr = (uint8_t *)physaddr;
|
|
otherbdt->status = (USB_BDT_UOWN | bytecount);
|
|
|
|
/* Configure the current BDT to receive a SETUP command. */
|
|
|
|
bdt->addr = (uint8_t *)physaddr;
|
|
bdt->status = (USB_BDT_UOWN | bytecount);
|
|
|
|
bdtinfo("EP0 BDT IN [%p] {%08x, %08x}\n",
|
|
bdt, bdt->status, bdt->addr);
|
|
bdtinfo("EP0 BDT IN [%p] {%08x, %08x}\n",
|
|
otherbdt, otherbdt->status, otherbdt->addr);
|
|
}
|
|
else
|
|
{
|
|
/* Return the other BDT to the CPU. */
|
|
|
|
otherbdt->addr = 0;
|
|
otherbdt->status = 0;
|
|
|
|
/* Return the current BDT to the CPU. */
|
|
|
|
bdt->addr = 0;
|
|
bdt->status = 0;
|
|
|
|
bdtinfo("EP%d BDT %s [%p] {%08x, %08x}\n",
|
|
epno, epin ? "IN" : "OUT", bdt, bdt->status, bdt->addr);
|
|
bdtinfo("EP%d BDT %s [%p] {%08x, %08x}\n",
|
|
epno, epin ? "IN" : "OUT", otherbdt, otherbdt->status,
|
|
otherbdt->addr);
|
|
|
|
/* Restart any queued requests (after a delay so that we can be assured
|
|
* that the hardware has recovered from the stall -- I don't know of any
|
|
* other way to assure this.).
|
|
*/
|
|
|
|
pic32mx_delayedrestart(priv, epno);
|
|
}
|
|
}
|
|
|
|
/* Handle the stall condition */
|
|
|
|
else
|
|
{
|
|
usbtrace(TRACE_EPSTALL, epno);
|
|
privep->stalled = true;
|
|
|
|
/* Stall the other BDT. */
|
|
|
|
otherbdt->status = (USB_BDT_UOWN | USB_BDT_BSTALL);
|
|
otherbdt->addr = 0;
|
|
|
|
/* Stall the current BDT. */
|
|
|
|
bdt->status = (USB_BDT_UOWN | USB_BDT_BSTALL);
|
|
bdt->addr = 0;
|
|
|
|
/* Stop any queued requests. Hmmm.. is there a race condition here? */
|
|
|
|
pic32mx_rqstop(privep);
|
|
|
|
bdtinfo("EP%d BDT %s [%p] {%08x, %08x}\n",
|
|
epno, epin ? "IN" : "OUT", bdt, bdt->status, bdt->addr);
|
|
bdtinfo("EP%d BDT %s [%p] {%08x, %08x}\n",
|
|
epno, epin ? "IN" : "OUT", otherbdt, otherbdt->status,
|
|
otherbdt->addr);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_epstall
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_epstall(struct usbdev_ep_s *ep, bool resume)
|
|
{
|
|
struct pic32mx_ep_s *privep;
|
|
irqstate_t flags;
|
|
int ret;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!ep)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
/* Recover pointers */
|
|
|
|
privep = (struct pic32mx_ep_s *)ep;
|
|
|
|
/* STALL or RESUME the endpoint */
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Special case EP0. When we stall EP0 we have to stall both the IN and
|
|
* OUT BDTs.
|
|
*/
|
|
|
|
if (USB_EPNO(ep->eplog) == 0)
|
|
{
|
|
ret = pic32mx_epbdtstall(ep, resume, true);
|
|
if (ret == OK)
|
|
{
|
|
ret = pic32mx_epbdtstall(ep, resume, false);
|
|
}
|
|
|
|
/* Set the EP0 control state appropriately */
|
|
|
|
privep->dev->ctrlstate = resume ? CTRLSTATE_WAITSETUP : CTRLSTATE_STALLED;
|
|
}
|
|
|
|
/* Otherwise, select the BDT for the endpoint direction */
|
|
|
|
else
|
|
{
|
|
/* It is a unidirectional endpoint */
|
|
|
|
ret = pic32mx_epbdtstall(ep, resume, USB_ISEPIN(ep->eplog));
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Device Controller Operations
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: pic32mx_allocep
|
|
****************************************************************************/
|
|
|
|
static struct usbdev_ep_s *pic32mx_allocep(struct usbdev_s *dev, uint8_t epno,
|
|
bool epin, uint8_t eptype)
|
|
{
|
|
struct pic32mx_usbdev_s *priv = (struct pic32mx_usbdev_s *)dev;
|
|
struct pic32mx_ep_s *privep = NULL;
|
|
uint16_t epset = PIC32MX_ENDP_ALLSET;
|
|
|
|
usbtrace(TRACE_DEVALLOCEP, (uint16_t)epno);
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!dev)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/* Ignore any direction bits in the logical address */
|
|
|
|
epno = USB_EPNO(epno);
|
|
|
|
/* A logical address of 0 means that any endpoint will do */
|
|
|
|
if (epno > 0)
|
|
{
|
|
/* Otherwise, we will return the endpoint structure only for the requested
|
|
* 'logical' endpoint. All of the other checks will still be performed.
|
|
*
|
|
* First, verify that the logical endpoint is in the range supported by
|
|
* by the hardware.
|
|
*/
|
|
|
|
if (epno >= PIC32MX_NENDPOINTS)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADEPNO), (uint16_t)epno);
|
|
return NULL;
|
|
}
|
|
|
|
/* Convert the logical address to a physical OUT endpoint address and
|
|
* remove all of the candidate endpoints from the bitset except for the
|
|
* the IN/OUT pair for this logical address.
|
|
*/
|
|
|
|
epset = PIC32MX_ENDP_BIT(epno);
|
|
}
|
|
|
|
/* Check if the selected endpoint number is available */
|
|
|
|
privep = pic32mx_epreserve(priv, epset);
|
|
if (!privep)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_EPRESERVE), (uint16_t)epset);
|
|
return NULL;
|
|
}
|
|
|
|
return &privep->ep;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_freeep
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep)
|
|
{
|
|
struct pic32mx_usbdev_s *priv;
|
|
struct pic32mx_ep_s *privep;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!dev || !ep)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
priv = (struct pic32mx_usbdev_s *)dev;
|
|
privep = (struct pic32mx_ep_s *)ep;
|
|
usbtrace(TRACE_DEVFREEEP, (uint16_t)USB_EPNO(ep->eplog));
|
|
DEBUGASSERT(priv && privep);
|
|
|
|
/* Disable the endpoint */
|
|
|
|
(void)pic32mx_epdisable(ep);
|
|
|
|
/* Mark the endpoint as available */
|
|
|
|
pic32mx_epunreserve(priv, privep);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_getframe
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_getframe(struct usbdev_s *dev)
|
|
{
|
|
uint16_t frml;
|
|
uint16_t frmh;
|
|
uint16_t tmp;
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!dev)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
/* Return the last frame number detected by the hardware. Thr FRMH/L
|
|
* registers are updated with the current frame number whenever a SOF
|
|
* TOKEN is received.
|
|
*/
|
|
|
|
do
|
|
{
|
|
/* Loop until we can be sure that there was no wrap from the FRML
|
|
* to the FRMH register.
|
|
*/
|
|
|
|
frmh = pic32mx_getreg(PIC32MX_USB_FRMH) & USB_FRMH_MASK;
|
|
frml = pic32mx_getreg(PIC32MX_USB_FRML) & USB_FRML_MASK;
|
|
tmp = pic32mx_getreg(PIC32MX_USB_FRMH) & USB_FRMH_MASK;
|
|
}
|
|
while (frmh != tmp);
|
|
|
|
/* Combine to for the full 11-bit value */
|
|
|
|
tmp = (frmh) << 8 | frml;
|
|
usbtrace(TRACE_DEVGETFRAME, tmp);
|
|
return tmp;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_wakeup
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_wakeup(struct usbdev_s *dev)
|
|
{
|
|
struct pic32mx_usbdev_s *priv = (struct pic32mx_usbdev_s *)dev;
|
|
|
|
usbtrace(TRACE_DEVWAKEUP, 0);
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!dev)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
/* Resume normal operation. */
|
|
|
|
pic32mx_resume(priv);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_selfpowered
|
|
****************************************************************************/
|
|
|
|
static int pic32mx_selfpowered(struct usbdev_s *dev, bool selfpowered)
|
|
{
|
|
struct pic32mx_usbdev_s *priv = (struct pic32mx_usbdev_s *)dev;
|
|
|
|
usbtrace(TRACE_DEVSELFPOWERED, (uint16_t)selfpowered);
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!dev)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return -ENODEV;
|
|
}
|
|
#endif
|
|
|
|
priv->selfpowered = selfpowered;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Initialization/Reset
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: pic32mx_reset
|
|
*
|
|
* Description:
|
|
* Reset the software and hardware states. If the USB controller has been
|
|
* attached to a host, then connect to the bus as well. At the end of
|
|
* this reset, the hardware should be in the full up, ready-to-run state.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_reset(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
/* Reset the software configuration */
|
|
|
|
pic32mx_swreset(priv);
|
|
|
|
/* Re-configure the USB controller in its initial, unconnected state */
|
|
|
|
pic32mx_hwreset(priv);
|
|
|
|
/* pic32mx_attach() was called, then the attach flag will be set and we
|
|
* should also attach to the USB bus.
|
|
*/
|
|
|
|
if (priv->attached)
|
|
{
|
|
/* usbdev_attach() has already been called.. attach to the bus
|
|
* now
|
|
*/
|
|
|
|
pic32mx_attach(priv);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_attach
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_attach(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
uint16_t regval;
|
|
|
|
/* Check if we are in the detached state */
|
|
|
|
if (priv->devstate == DEVSTATE_DETACHED)
|
|
{
|
|
/* Disable USB interrupts at the interrupt controller */
|
|
|
|
up_disable_irq(PIC32MX_IRQSRC_USB);
|
|
|
|
/* Initialize registers to known states. */
|
|
|
|
pic32mx_putreg(0, PIC32MX_USB_CON);
|
|
|
|
/* Configure things like: pull ups, full/low-speed mode,
|
|
* set the ping pong mode, and set internal transceiver
|
|
*/
|
|
|
|
pic32mx_putreg(0, PIC32MX_USB_CNFG1);
|
|
|
|
/* Enable interrupts at the USB controller */
|
|
|
|
pic32mx_putreg(ERROR_INTERRUPTS, PIC32MX_USB_EIE);
|
|
pic32mx_putreg(NORMAL_INTERRUPTS, PIC32MX_USB_IE);
|
|
|
|
/* Configure EP0 */
|
|
|
|
pic32mx_ep0configure(priv);
|
|
|
|
/* Flush any pending transactions */
|
|
|
|
while ((pic32mx_getreg(PIC32MX_USB_IR) & USB_INT_TRN) != 0)
|
|
{
|
|
pic32mx_putreg(USB_INT_TRN, PIC32MX_USB_IR);
|
|
}
|
|
|
|
/* Make sure packet processing is enabled */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USB_CON);
|
|
regval &= ~USB_CON_PKTDIS;
|
|
pic32mx_putreg(regval, PIC32MX_USB_CON);
|
|
|
|
/* Enable the USB module and attach to bus */
|
|
|
|
do
|
|
{
|
|
regval = pic32mx_getreg(PIC32MX_USB_CON);
|
|
if ((regval & USB_CON_USBEN) == 0)
|
|
{
|
|
pic32mx_putreg(regval | USB_CON_USBEN, PIC32MX_USB_CON);
|
|
}
|
|
}
|
|
while ((regval & USB_CON_USBEN) == 0);
|
|
|
|
/* Enable OTG */
|
|
|
|
#ifdef CONFIG_USBOTG
|
|
regval = pic32mx_getreg(PIC32MX_USBOTG_CON);
|
|
regval |= (USBOTG_CON_DPPULUP | USBOTG_CON_OTGEN);
|
|
pic32mx_putreg(regval, PIC32MX_USBOTG_CON);
|
|
#endif
|
|
|
|
/* Transition to the attached state */
|
|
|
|
priv->devstate = DEVSTATE_ATTACHED;
|
|
priv->usbdev.speed = USB_SPEED_FULL;
|
|
|
|
/* Clear all pending USB interrupts */
|
|
|
|
pic32mx_putreg(USB_EINT_ALL, PIC32MX_USB_EIR);
|
|
pic32mx_putreg(USB_INT_ALL, PIC32MX_USB_IR);
|
|
|
|
/* Enable USB interrupts at the interrupt controller */
|
|
|
|
up_enable_irq(PIC32MX_IRQSRC_USB);
|
|
|
|
/* Enable pull-up to connect the device. The host should enumerate us
|
|
* some time after this
|
|
*/
|
|
|
|
pic32mx_usbpullup(&priv->usbdev, true);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_detach
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_detach(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
/* Disable USB interrupts at the interrupt controller */
|
|
|
|
up_disable_irq(PIC32MX_IRQSRC_USB);
|
|
|
|
/* Disable the USB controller and detach from the bus. */
|
|
|
|
pic32mx_putreg(0, PIC32MX_USB_CON);
|
|
|
|
/* Mask all USB interrupts */
|
|
|
|
pic32mx_putreg(0, PIC32MX_USB_IE);
|
|
|
|
/* We are now in the detached state */
|
|
|
|
priv->attached = 0;
|
|
priv->devstate = DEVSTATE_DETACHED;
|
|
|
|
#ifdef CONFIG_USBOTG
|
|
/* Disable the D+ Pullup */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USBOTG_CON);
|
|
regval &= ~USBOTG_CON_DPPULUP;
|
|
pic32mx_putreg(regval, PIC32MX_USBOTG_CON);
|
|
|
|
/* Disable and deactivate HNP */
|
|
#warning Missing Logic
|
|
|
|
/* Check if the ID Pin Changed State */
|
|
|
|
if ((pic32mx_getreg(PIC32MX_USBOTG_IR) & pic32mx_getreg(PIC32MX_USBOTG_IE) & USBOTG_INT_ID) != 0)
|
|
{
|
|
/* Re-detect & Initialize */
|
|
#warning "Missing logic"
|
|
|
|
/* Clear ID Interrupt Flag */
|
|
|
|
pic32mx_putreg(USBOTG_INT_ID, PIC32MX_USBOTG_IR);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_swreset
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_swreset(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
int epno;
|
|
|
|
/* Tell the class driver that we are disconnected. The class driver
|
|
* should then accept any new configurations.
|
|
*/
|
|
|
|
if (priv->driver)
|
|
{
|
|
CLASS_DISCONNECT(priv->driver, &priv->usbdev);
|
|
}
|
|
|
|
/* Flush and reset endpoint states (except EP0) */
|
|
|
|
for (epno = 1; epno < PIC32MX_NENDPOINTS; epno++)
|
|
{
|
|
struct pic32mx_ep_s *privep = &priv->eplist[epno];
|
|
|
|
/* Cancel any queued requests. Since they are canceled
|
|
* with status -ESHUTDOWN, then will not be requeued
|
|
* until the configuration is reset. NOTE: This should
|
|
* not be necessary... the CLASS_DISCONNECT above should
|
|
* result in the class implementation calling pic32mx_epdisable
|
|
* for each of its configured endpoints.
|
|
*/
|
|
|
|
pic32mx_cancelrequests(privep, -EAGAIN);
|
|
|
|
/* Reset endpoint status */
|
|
|
|
privep->stalled = false;
|
|
privep->halted = false;
|
|
privep->txnullpkt = false;
|
|
}
|
|
|
|
/* Reset to the default address */
|
|
|
|
pic32mx_putreg(0, PIC32MX_USB_ADDR);
|
|
|
|
/* Unconfigure each endpoint by clearing the endpoint control registers
|
|
* (except EP0)
|
|
*/
|
|
|
|
for (epno = 1; epno < PIC32MX_NENDPOINTS; epno++)
|
|
{
|
|
pic32mx_putreg(0, PIC32MX_USB_EP(epno));
|
|
}
|
|
|
|
/* Reset the control state */
|
|
|
|
priv->ctrlstate = CTRLSTATE_WAITSETUP;
|
|
priv->rxbusy = 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_hwreset
|
|
*
|
|
* Description:
|
|
* Reset the hardware and leave it in a known, unready state.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_hwreset(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
uint32_t physaddr;
|
|
uint16_t regval;
|
|
|
|
/* Power down the USB module. This will reset all USB registers. */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USB_PWRC);
|
|
regval &= ~USB_PWRC_USBPWR;
|
|
pic32mx_putreg(regval, PIC32MX_USB_PWRC);
|
|
|
|
/* Clear all of the buffer descriptor table (BDT) entries */
|
|
|
|
memset((void *)g_bdt, 0, sizeof(g_bdt));
|
|
|
|
/* Power up the USB module */
|
|
|
|
regval |= USB_PWRC_USBPWR;
|
|
pic32mx_putreg(regval, PIC32MX_USB_PWRC);
|
|
|
|
/* Set the address of the buffer descriptor table (BDT)
|
|
*
|
|
* BDTP1: Bit 1-7: Bits 9-15 of the BDT base address
|
|
* BDTP2: Bit 0-7: Bits 16-23 of the BDT base address
|
|
* BDTP3: Bit 0-7: Bits 24-31 of the BDT base address
|
|
*/
|
|
|
|
physaddr = PHYS_ADDR(g_bdt);
|
|
pic32mx_putreg((uint16_t)((physaddr >> 24) & USB_BDTP3_MASK), PIC32MX_USB_BDTP3);
|
|
pic32mx_putreg((uint16_t)((physaddr >> 16) & USB_BDTP2_MASK), PIC32MX_USB_BDTP2);
|
|
pic32mx_putreg((uint16_t)((physaddr >> 8) & USB_BDTP1_MASK), PIC32MX_USB_BDTP1);
|
|
|
|
/* Assert reset request to all of the Ping Pong buffer pointers. This
|
|
* will reset all Even/Odd buffer pointers to the EVEN BD banks.
|
|
*/
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USB_CON);
|
|
regval |= USB_CON_PPBRST;
|
|
pic32mx_putreg(regval, PIC32MX_USB_CON);
|
|
|
|
/* Bring the ping pong buffer pointers out of reset */
|
|
|
|
regval &= ~USB_CON_PPBRST;
|
|
pic32mx_putreg(regval, PIC32MX_USB_CON);
|
|
|
|
/* Indicate that we are now in the detached state */
|
|
|
|
priv->devstate = DEVSTATE_DETACHED;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_stateinit
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_stateinit(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
int epno;
|
|
|
|
/* Disconnect the device / disable the pull-up. We don't want the
|
|
* host to enumerate us until the class driver is registered.
|
|
*/
|
|
|
|
pic32mx_usbpullup(&priv->usbdev, false);
|
|
|
|
/* Initialize the device state structure. NOTE: many fields
|
|
* have the initial value of zero and, hence, are not explicitly
|
|
* initialized here.
|
|
*/
|
|
|
|
memset(priv, 0, sizeof(struct pic32mx_usbdev_s));
|
|
priv->usbdev.ops = &g_devops;
|
|
priv->usbdev.ep0 = &priv->eplist[EP0].ep;
|
|
priv->epavail = PIC32MX_ENDP_ALLSET & ~PIC32MX_ENDP_BIT(EP0);
|
|
priv->rwakeup = 1;
|
|
|
|
/* Initialize the endpoint list */
|
|
|
|
for (epno = 0; epno < PIC32MX_NENDPOINTS; epno++)
|
|
{
|
|
struct pic32mx_ep_s *privep = &priv->eplist[epno];
|
|
|
|
/* Set endpoint operations, reference to driver structure (not
|
|
* really necessary because there is only one controller), and
|
|
* the (physical) endpoint number which is just the index to the
|
|
* endpoint.
|
|
*/
|
|
|
|
privep->ep.ops = &g_epops;
|
|
privep->dev = priv;
|
|
privep->ep.eplog = epno;
|
|
|
|
/* We will use a fixed maxpacket size for all endpoints (perhaps
|
|
* ISOC endpoints could have larger maxpacket???). A smaller
|
|
* packet size can be selected when the endpoint is configured.
|
|
*/
|
|
|
|
privep->ep.maxpacket = PIC32MX_MAXPACKET_SIZE;
|
|
}
|
|
|
|
/* Select a smaller endpoint size for EP0 */
|
|
|
|
#if PIC32MX_EP0MAXPACKET < PIC32MX_MAXPACKET_SIZE
|
|
priv->eplist[EP0].ep.maxpacket = PIC32MX_EP0MAXPACKET;
|
|
#endif
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_hwshutdown
|
|
****************************************************************************/
|
|
|
|
static void pic32mx_hwshutdown(struct pic32mx_usbdev_s *priv)
|
|
{
|
|
uint16_t regval;
|
|
|
|
/* Put the hardware and driver in its initial, unconnected state */
|
|
|
|
pic32mx_swreset(priv);
|
|
pic32mx_hwreset(priv);
|
|
priv->usbdev.speed = USB_SPEED_UNKNOWN;
|
|
|
|
/* Disable all interrupts and force the USB controller into reset */
|
|
|
|
pic32mx_putreg(0, PIC32MX_USB_EIE);
|
|
pic32mx_putreg(0, PIC32MX_USB_IE);
|
|
|
|
/* Clear any pending interrupts */
|
|
|
|
pic32mx_putreg(USB_EINT_ALL, PIC32MX_USB_EIR);
|
|
pic32mx_putreg(USB_INT_ALL, PIC32MX_USB_IR);
|
|
|
|
/* Disconnect the device / disable the pull-up */
|
|
|
|
pic32mx_usbpullup(&priv->usbdev, false);
|
|
|
|
/* Power down the USB controller */
|
|
|
|
regval = pic32mx_getreg(PIC32MX_USB_PWRC);
|
|
regval &= ~USB_PWRC_USBPWR;
|
|
pic32mx_putreg(regval, PIC32MX_USB_PWRC);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
/****************************************************************************
|
|
* Name: up_usbinitialize
|
|
*
|
|
* Description:
|
|
* Initialize the USB driver
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void up_usbinitialize(void)
|
|
{
|
|
/* For now there is only one USB controller, but we will always refer to
|
|
* it using a pointer to make any future ports to multiple USB controllers
|
|
* easier.
|
|
*/
|
|
|
|
struct pic32mx_usbdev_s *priv = &g_usbdev;
|
|
|
|
usbtrace(TRACE_DEVINIT, 0);
|
|
|
|
/* Initialize the driver state structure */
|
|
|
|
pic32mx_stateinit(priv);
|
|
|
|
/* Then perform a few one-time initialization operstions. First, initialize
|
|
* the watchdog timer that is used to perform a delayed queue restart
|
|
* after recovering from a stall.
|
|
*/
|
|
|
|
priv->epstalled = 0;
|
|
priv->wdog = wd_create();
|
|
|
|
/* Attach USB controller interrupt handler. The hardware will not be
|
|
* initialized and interrupts will not be enabled until the class device
|
|
* driver is bound. Getting the IRQs here only makes sure that we have
|
|
* them when we need them later.
|
|
*/
|
|
|
|
if (irq_attach(PIC32MX_IRQ_USB, pic32mx_interrupt) != 0)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_IRQREGISTRATION),
|
|
(uint16_t)PIC32MX_IRQ_USB);
|
|
up_usbuninitialize();
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: up_usbuninitialize
|
|
* Description:
|
|
* Initialize the USB driver
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
****************************************************************************/
|
|
|
|
void up_usbuninitialize(void)
|
|
{
|
|
/* For now there is only one USB controller, but we will always refer to
|
|
* it using a pointer to make any future ports to multiple USB controllers
|
|
* easier.
|
|
*/
|
|
|
|
struct pic32mx_usbdev_s *priv = &g_usbdev;
|
|
irqstate_t flags;
|
|
|
|
flags = enter_critical_section();
|
|
usbtrace(TRACE_DEVUNINIT, 0);
|
|
|
|
/* Disable and detach the USB IRQs */
|
|
|
|
up_disable_irq(PIC32MX_IRQSRC_USB);
|
|
irq_detach(PIC32MX_IRQ_USB);
|
|
|
|
if (priv->driver)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_DRIVERREGISTERED), 0);
|
|
usbdev_unregister(priv->driver);
|
|
}
|
|
|
|
/* Put the hardware in an inactive state */
|
|
|
|
pic32mx_hwshutdown(priv);
|
|
leave_critical_section(flags);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbdev_register
|
|
*
|
|
* Description:
|
|
* Register a USB device class driver. The class driver's bind() method
|
|
* will be called to bind it to a USB device driver.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int usbdev_register(struct usbdevclass_driver_s *driver)
|
|
{
|
|
/* For now there is only one USB controller, but we will always refer to
|
|
* it using a pointer to make any future ports to multiple USB controllers
|
|
* easier.
|
|
*/
|
|
|
|
struct pic32mx_usbdev_s *priv = &g_usbdev;
|
|
int ret;
|
|
|
|
usbtrace(TRACE_DEVREGISTER, 0);
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (!driver || !driver->ops->bind || !driver->ops->unbind ||
|
|
!driver->ops->disconnect || !driver->ops->setup)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (priv->driver)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_DRIVER), 0);
|
|
return -EBUSY;
|
|
}
|
|
#endif
|
|
|
|
/* First hook up the driver */
|
|
|
|
priv->driver = driver;
|
|
|
|
/* Then bind the class driver */
|
|
|
|
ret = CLASS_BIND(driver, &priv->usbdev);
|
|
if (ret)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BINDFAILED), (uint16_t)-ret);
|
|
priv->driver = NULL;
|
|
}
|
|
|
|
/* The class driver has been successfully bound. */
|
|
|
|
else
|
|
{
|
|
/* Setup the USB controller in it initial ready-to-run state (might
|
|
* be connected or unconnected, depending on usbdev_attach() has
|
|
* been called).
|
|
*/
|
|
|
|
DEBUGASSERT(priv->devstate == DEVSTATE_DETACHED);
|
|
pic32mx_reset(priv);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbdev_unregister
|
|
*
|
|
* Description:
|
|
* Un-register usbdev class driver. If the USB device is connected to a
|
|
* USB host, it will first disconnect(). The driver is also requested to
|
|
* unbind() and clean up any device state, before this procedure finally
|
|
* returns.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int usbdev_unregister(struct usbdevclass_driver_s *driver)
|
|
{
|
|
/* For now there is only one USB controller, but we will always refer to
|
|
* it using a pointer to make any future ports to multiple USB controllers
|
|
* easier.
|
|
*/
|
|
|
|
struct pic32mx_usbdev_s *priv = &g_usbdev;
|
|
irqstate_t flags;
|
|
|
|
usbtrace(TRACE_DEVUNREGISTER, 0);
|
|
|
|
#ifdef CONFIG_DEBUG_FEATURES
|
|
if (driver != priv->driver)
|
|
{
|
|
usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
|
|
/* Reset the hardware and cancel all requests. All requests must be
|
|
* canceled while the class driver is still bound. This will put
|
|
* the hardware back into its initial, unconnected state.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
pic32mx_swreset(priv);
|
|
pic32mx_hwreset(priv);
|
|
|
|
/* Unbind the class driver */
|
|
|
|
CLASS_UNBIND(driver, &priv->usbdev);
|
|
|
|
/* Disable USB controller interrupts (but keep them attached) */
|
|
|
|
up_disable_irq(PIC32MX_IRQSRC_USB);
|
|
|
|
/* Put the hardware in an inactive state. Then bring the hardware back up
|
|
* in the reset state (this is probably not necessary, the pic32mx_hwreset()
|
|
* call above was probably sufficient).
|
|
*/
|
|
|
|
pic32mx_hwshutdown(priv);
|
|
pic32mx_stateinit(priv);
|
|
|
|
/* Unhook the driver */
|
|
|
|
priv->driver = NULL;
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: pic32mx_usbattach and pic32mx_usbdetach
|
|
*
|
|
* Description:
|
|
* The USB stack must be notified when the device is attached or detached
|
|
* by calling one of these functions.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void pic32mx_usbattach(void)
|
|
{
|
|
/* For now there is only one USB controller, but we will always refer to
|
|
* it using a pointer to make any future ports to multiple USB controllers
|
|
* easier.
|
|
*/
|
|
|
|
struct pic32mx_usbdev_s *priv = &g_usbdev;
|
|
|
|
/* Mark that we are attached */
|
|
|
|
priv->attached = 1;
|
|
|
|
/* This API may be called asynchronously from other initialization
|
|
* interfaces. In particular, we may not want to attach the bus yet...
|
|
* that should only be done when the class driver is attached. Has
|
|
* the class driver been attached?
|
|
*/
|
|
|
|
if (priv->driver)
|
|
{
|
|
/* Yes.. then attach to the bus */
|
|
|
|
pic32mx_attach(priv);
|
|
}
|
|
}
|
|
|
|
void pic32mx_usbdetach(void)
|
|
{
|
|
/* For now there is only one USB controller, but we will always refer to
|
|
* it using a pointer to make any future ports to multiple USB controllers
|
|
* easier.
|
|
*/
|
|
|
|
struct pic32mx_usbdev_s *priv = &g_usbdev;
|
|
|
|
/* Detach from the bus */
|
|
|
|
pic32mx_detach(priv);
|
|
}
|
|
|
|
#endif /* CONFIG_USBDEV && CONFIG_PIC32MX_USB */
|