/**************************************************************************** * arch/mips/src/pic32mx/pic32mx_usbdev.c * * Copyright (C) 2011-2012 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "up_arch.h" #include "pic32mx-internal.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 /* Extremely detailed register/BDT debug that you would normally never want * enabled. */ #ifndef CONFIG_DEBUG # undef CONFIG_PIC32MX_USBDEV_REGDEBUG # undef CONFIG_PIC32MX_USBDEV_BDTDEBUG #endif /* 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) ((ep) << 2) #define EP_OUT_ODD(ep) (((ep) << 2) + 1) #define EP_IN_EVEN(ep) (((ep) << 2) + 2) #define EP_IN_ODD(ep) (((ep) << 2) + 3) #define EP(ep,dir,pp) (((ep) << 2) + ((dir) << 1) + (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(ep) ((ep)->head == NULL) #define pic32mx_rqpeek(ep) ((ep)->head) /* 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_EPOUTPENDING 0x000e #define PIC32MX_TRACEINTID_EPOUTQEMPTY 0x000f #define PIC32MX_TRACEINTID_SOF 0x0010 #define PIC32MX_TRACEINTID_GETCONFIG 0x0011 #define PIC32MX_TRACEINTID_GETSETDESC 0x0012 #define PIC32MX_TRACEINTID_GETSETIF 0x0013 #define PIC32MX_TRACEINTID_GETSTATUS 0x0014 #define PIC32MX_TRACEINTID_IFGETSTATUS 0x0015 #define PIC32MX_TRACEINTID_TRNC 0x0016 #define PIC32MX_TRACEINTID_TRNCS 0x0017 #define PIC32MX_TRACEINTID_INTERRUPT 0x0018 #define PIC32MX_TRACEINTID_NOSTDREQ 0x0019 #define PIC32MX_TRACEINTID_RESET 0x001a #define PIC32MX_TRACEINTID_SETCONFIG 0x001b #define PIC32MX_TRACEINTID_SETFEATURE 0x001c #define PIC32MX_TRACEINTID_IDLE 0x001d #define PIC32MX_TRACEINTID_SYNCHFRAME 0x001e #define PIC32MX_TRACEINTID_WKUP 0x001f #define PIC32MX_TRACEINTID_T1MSEC 0x0020 #define PIC32MX_TRACEINTID_OTGID 0x0021 #define PIC32MX_TRACEINTID_STALL 0x0022 #define PIC32MX_TRACEINTID_UERR 0x0023 #define PIC32MX_TRACEINTID_SUSPENDED 0x0024 #define PIC32MX_TRACEINTID_WAITRESET 0x0025 /* 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 ********************************************************************/ #ifdef CONFIG_PIC32MX_USBDEV_REGDEBUG # undef CONFIG_PIC32MX_USBDEV_BDTDEBUG # define CONFIG_PIC32MX_USBDEV_BDTDEBUG 1 # define regdbg lldbg # ifdef CONFIG_DEBUG_VERBOSE # define regvdbg lldbg # else # define regvdbg(x...) # endif #else # define pic32mx_getreg(addr) getreg16(addr) # define pic32mx_putreg(val,addr) putreg16(val,addr) # define regdbg(x...) # define regvdbg(x...) #endif #ifdef CONFIG_PIC32MX_USBDEV_BDTDEBUG # define bdtdbg lldbg # ifdef CONFIG_DEBUG_VERBOSE # define bdtvdbg lldbg # else # define bdtvdbg(x...) # endif #else # define bdtdbg(x...) # define bdtvdbg(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_STALLED /* We are 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 list */ struct pic32mx_req_s { struct usbdev_req_s req; /* Standard USB request */ uint16_t inflight; /* The number of bytes "in-flight" */ struct pic32mx_req_s *flink; /* Supports a singly linked list */ }; /* 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_req_s *head; /* Request list for this endpoint */ struct pic32mx_req_s *tail; uint8_t stalled:1; /* true: Endpoint is stalled */ uint8_t halted:1; /* true: Endpoint feature halted */ uint8_t txbusy:1; /* true: TX endpoint FIFO full */ uint8_t txnullpkt:1; /* Null packet needed at end of 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 */ uint16_t epavail; /* Bitset of available endpoints */ /* 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 Helpers **********************************************************/ static struct pic32mx_req_s * pic32mx_rqdequeue(struct pic32mx_ep_s *privep); static void pic32mx_rqenqueue(struct pic32mx_ep_s *privep, struct pic32mx_req_s *req); static inline void pic32mx_abortrequest(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, const uint8_t *src, uint32_t nbytes); static void pic32mx_wrcomplete(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep); static void pic32mx_wrrestart(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_usbdev_s *priv, 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); /* 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 status); static void pic32mx_ep0nextsetup(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 status); 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, volatile struct usbotg_bdtentry_s *bdt, 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))); /**************************************************************************** * Public Data ****************************************************************************/ /**************************************************************************** * Private 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) { lldbg("...\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 */ lldbg("[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 */ lldbg("%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 */ lldbg("%08x<-%04x\n", addr, val); /* Write the value */ putreg16(val, addr); } #endif /**************************************************************************** * Request Helpers ****************************************************************************/ /**************************************************************************** * Name: pic32mx_rqdequeue ****************************************************************************/ static struct pic32mx_req_s *pic32mx_rqdequeue(struct pic32mx_ep_s *privep) { struct pic32mx_req_s *ret = privep->head; if (ret) { privep->head = ret->flink; if (!privep->head) { privep->tail = NULL; } ret->flink = NULL; } return ret; } /**************************************************************************** * Name: pic32mx_rqenqueue ****************************************************************************/ static void pic32mx_rqenqueue(struct pic32mx_ep_s *privep, struct pic32mx_req_s *req) { req->flink = NULL; if (!privep->head) { privep->head = req; privep->tail = req; } else { privep->tail->flink = req; privep->tail = req; } } /**************************************************************************** * Name: pic32mx_abortrequest ****************************************************************************/ static inline void pic32mx_abortrequest(struct pic32mx_ep_s *privep, struct pic32mx_req_s *privreq, int16_t result) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_REQABORTED), (uint16_t)USB_EPNO(privep->ep.eplog)); /* Save the result in the request structure */ privreq->req.result = result; /* Callback to the request completion handler */ privreq->req.callback(&privep->ep, &privreq->req); } /**************************************************************************** * 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 request list */ flags = irqsave(); privreq = pic32mx_rqdequeue(privep); irqrestore(flags); if (privreq) { /* 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_epwrite ****************************************************************************/ static void pic32mx_epwrite(struct pic32mx_ep_s *privep, const uint8_t *src, uint32_t nbytes) { volatile struct usbotg_bdtentry_s *bdt = privep->bdtin; uint32_t status; usbtrace(TRACE_WRITE(USB_EPNO(privep->ep.eplog)), nbytes); /* Clear all bits in the status preserving only the data toggle bit */ status = bdt->status; status &= USB_BDT_DATA01; /* Toggle the data toggle */ status ^= USB_BDT_DATA01; bdt->status = status; /* Set the data pointer, data length, and enable the endpoint */ bdt->addr = (uint8_t *)PHYS_ADDR(src); /* Set the data length and give the BDT to USB. Preserving * only the data toggle. */ status |= (nbytes << USB_BDT_BYTECOUNT_SHIFT) | USB_BDT_DTS; /* And, finally, give the BDT to the USB */ status |= USB_BDT_UOWN; bdtdbg("EP%d BDT IN [%p] {%08x, %08x}\n", USB_EPNO(privep->ep.eplog), bdt, status, bdt->addr); bdt->status = status; /* Indicate that there is TX data inflight. This will be cleared * when the next data out interrupt is received. */ privep->txbusy = true; } /**************************************************************************** * Name: pic32mx_wrcomplete ****************************************************************************/ static void pic32mx_wrcomplete(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep) { struct pic32mx_req_s *privreq; int bytesleft; /* Check the request from the head of the endpoint request queue. Since * we got here from a write completion event, the request queue should * be non-NULL. */ privreq = pic32mx_rqpeek(privep); if (privreq != NULL) { /* An outgoing IN packet has completed. Update the number of bytes * transferred. */ privreq->req.xfrd += privreq->inflight; privreq->inflight = 0; 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_wrrestart ****************************************************************************/ static void pic32mx_wrrestart(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep) { struct pic32mx_req_s *privreq; /* Reset some endpoint state variables */ privep->txnullpkt = false; privep->txbusy = false; /* Check the request from the head of the endpoint request queue */ privreq = pic32mx_rqpeek(privep); if (!privreq) { /* Restart transmission after we have recovered from a stall */ privreq->req.xfrd = 0; privreq->inflight = 0; (void)pic32mx_wrrequest(priv, privep); } } /**************************************************************************** * Name: pic32mx_wrrequest ****************************************************************************/ static int pic32mx_wrrequest(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep) { struct pic32mx_req_s *privreq; uint8_t *buf; uint8_t epno; int nbytes; int bytesleft; /* We get here when an IN endpoint interrupt occurs. So now we know that * there is no TX transfer in progress. */ privep->txbusy = false; /* Get the endpoint number that we are servicing */ epno = USB_EPNO(privep->ep.eplog); /* Check the request from the head of the endpoint request queue */ privreq = pic32mx_rqpeek(privep); if (!privreq) { /* There is no TX transfer in progress and no new pending TX * requests to send. */ usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPINQEMPTY), epno); /* Special case endpoint zero. If there is nothing to be sent, then * we need to configure to received the next SETUP packet. */ if (epno == 0) { priv->ctrlstate = CTRLSTATE_WAITSETUP; } return OK; } ullvdbg("epno=%d req=%p: len=%d xfrd=%d nullpkt=%d\n", epno, privreq, privreq->req.len, privreq->req.xfrd, privep->txnullpkt); /* Get the number of bytes left to be sent in the packet */ bytesleft = privreq->req.len - privreq->req.xfrd; nbytes = bytesleft; /* Send the next packet */ if (nbytes > 0) { /* 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 nbytes == 0) */ buf = privreq->req.buf + privreq->req.xfrd; /* Setup the writes to the endpoints */ pic32mx_epwrite(privep, 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 = nbytes; return OK; } /**************************************************************************** * Name: pic32mx_rdcomplete ****************************************************************************/ static int pic32mx_rdcomplete(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep) { volatile struct usbotg_bdtentry_s *bdt = privep->bdtout; struct pic32mx_req_s *privreq; int readlen; /* Check the request from the head of the endpoint request queue */ privreq = pic32mx_rqpeek(privep); if (!privreq) { /* There is no packet to receive any data. Then why are we here? */ usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPOUTQEMPTY), USB_EPNO(privep->ep.eplog)); return -EINVAL; } ullvdbg("EP%d: len=%d xfrd=%d BDT={%08x, %08x}\n", USB_EPNO(privep->ep.eplog), privreq->req.len, privreq->req.xfrd, bdt->status, bdt->addr); /* Get the length of the data received from the BDT */ readlen = (bdt->status & USB_BDT_BYTECOUNT_MASK) >> USB_BDT_BYTECOUNT_SHIFT; /* If the receive buffer is full or this is a partial packet, * then we are finished with the transfer */ privreq->req.xfrd += readlen; if (readlen < privep->ep.maxpacket || privreq->req.xfrd >= privreq->req.len) { /* Complete the transfer and mark the state IDLE. The endpoint * RX will be marked valid when the data phase completes. */ usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)), privreq->req.xfrd); pic32mx_reqcomplete(privep, OK); } /* 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 *bdt = priv->eplist[EP0].bdtout; uint16_t status; /* Clear status bits (making sure that UOWN is cleared before doing anything * else. */ status = bdt->status; status &= ~(USB_BDT_BSTALL | USB_BDT_NINC | USB_BDT_KEEP| USB_BDT_UOWN | USB_BDT_BYTECOUNT_MASK | USB_BDT_DATA01); bdt->status = status; /* Set the data pointer, data length, and enable the endpoint */ bdt->addr = (uint8_t *)PHYS_ADDR(&priv->ctrl); status |= (USB_SIZEOF_CTRLREQ << USB_BDT_BYTECOUNT_SHIFT); /* Select data0 or data 1 when enabling */ if ((status & USB_BDT_DATA01) == USB_BDT_DATA0) { status |= (USB_BDT_UOWN | USB_BDT_DATA1 | USB_BDT_DTS); } else { status |= (USB_BDT_UOWN | USB_BDT_DATA0 | USB_BDT_DTS); } /* Then give the BDT to the USB */ bdtdbg("EP0 BDT OUT [%p] {%08x, %08x}\n", bdt, status, bdt->addr); bdt->status = status; priv->ctrlstate = CTRLSTATE_RDREQUEST; return OK; } /**************************************************************************** * Name: pic32mx_rdsetup ****************************************************************************/ static int pic32mx_rdsetup(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep, uint8_t *dest, int readlen) { volatile struct usbotg_bdtentry_s *bdt = privep->bdtout; uint16_t status; /* Clear status bits (making sure that UOWN is cleared before doing anything * else). The DATA01 is (only) is preserved. */ status = bdt->status & USB_BDT_DATA01; bdt->status = status; /* Set the data pointer, data length, and enable the endpoint */ bdt->addr = (uint8_t *)PHYS_ADDR(dest); /* Set the data length (preserving the data toggle). */ status |= (readlen << USB_BDT_BYTECOUNT_SHIFT) | USB_BDT_DTS; /* Then give the BDT to the USB */ status |= USB_BDT_UOWN; bdtdbg("EP%d BDT OUT [%p] {%08x, %08x}\n", USB_EPNO(privep->ep.eplog), bdt, status, bdt->addr); bdt->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; uint8_t *dest; int readlen; int ret; /* Check the request from the head of the endpoint request queue */ privreq = pic32mx_rqpeek(privep); if (!privreq) { /* There is no packet to receive any data. */ usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPOUTQEMPTY), USB_EPNO(privep->ep.eplog)); return OK; } ullvdbg("EP%d: len=%d xfrd=%d\n", USB_EPNO(privep->ep.eplog), privreq->req.len, privreq->req.xfrd); /* 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; } usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), privreq->req.xfrd); /* Get the destination transfer address and size */ dest = privreq->req.buf + privreq->req.xfrd; 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, dest, readlen); } else { ret = pic32mx_rdsetup(priv, privep, dest, readlen); } return ret; } /**************************************************************************** * Name: pic32mx_cancelrequests ****************************************************************************/ static void pic32mx_cancelrequests(struct pic32mx_ep_s *privep) { while (!pic32mx_rqempty(privep)) { usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)), (pic32mx_rqpeek(privep))->req.xfrd); pic32mx_reqcomplete(privep, -ESHUTDOWN); } } /**************************************************************************** * 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); if (ret < 0) { /* Stall on failure */ usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_DISPATCHSTALL), 0); priv->ctrlstate = CTRLSTATE_STALLED; } } } /**************************************************************************** * 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) { /* Check if we own the BDTs. All BDs of endpoint zero should be owned by * the USB. Check anyway. */ struct pic32mx_ep_s *ep0 = &priv->eplist[EP0]; if ((ep0->bdtout->status & USB_BDT_UOWN) != 0 && (ep0->bdtin->status & (USB_BDT_UOWN | USB_BDT_BSTALL)) == (USB_BDT_UOWN | USB_BDT_BSTALL)) { /* Set ep0 BDT status to stall also */ uint16_t status = (USB_BDT_UOWN| USB_BDT_DATA0 | USB_BDT_DTS | USB_BDT_BSTALL); bdtdbg("EP0 BDT OUT [%p] {%08x, %08x}\n", ep0->bdtout, status, ep0->bdtout->addr); ep0->bdtout->status = status; } /* Then 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 status) { struct pic32mx_ep_s *privep; /* Decode and service non control endpoints interrupt */ privep = &priv->eplist[epno]; /* Check if the last transaction was an EP0 OUT transaction */ if ((status & USB_STAT_DIR) == USB_STAT_DIR_OUT) { /* OUT: host-to-device */ usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPOUTDONE), status); /* Handle read requests. First check if a read request is available to * accept the host data. */ if (!pic32mx_rqempty(privep)) { /* Read host data into the current read request */ pic32mx_rdcomplete(priv, privep); } /* NAK further OUT packets if there there no more read requests */ if (pic32mx_rqempty(privep)) { usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPOUTPENDING), (uint16_t)epno); #warning "Missing logic" } } else { /* IN: device-to-host */ usbtrace(TRACE_INTDECODE(PIC32MX_TRACEINTID_EPINDONE), status); /* 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 */ pic32mx_wrrequest(priv, privep); } } /**************************************************************************** * Name: pic32mx_ep0nextsetup ****************************************************************************/ static void pic32mx_ep0nextsetup(struct pic32mx_usbdev_s *priv) { struct pic32mx_ep_s *ep0 = &priv->eplist[EP0]; volatile struct usbotg_bdtentry_s *bdtlast; volatile struct usbotg_bdtentry_s *bdtnext; uint32_t status; 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) { /* Which BDT did we just finish working with? Which one will be next */ bdtlast = ep0->bdtout; if (bdtlast == &g_bdt[EP0_OUT_EVEN]) { bdtnext = &g_bdt[EP0_OUT_ODD]; } else { DEBUGASSERT(bdtlast == &g_bdt[EP0_OUT_ODD]); bdtnext = &g_bdt[EP0_OUT_EVEN]; } /* Setup to receive the next SETUP packet. Data toggle synchronization * is not needed for SETUP packets. */ bytecount = (USB_SIZEOF_CTRLREQ << USB_BDT_BYTECOUNT_SHIFT); status = bdtnext->status; status = (USB_BDT_UOWN | bytecount); bdtnext->addr = (uint8_t *)PHYS_ADDR(&priv->ctrl); bdtnext->status = status; priv->eplist[EP0].bdtout = bdtnext; /* Force a STALL if there is any access to the other buffer. */ status = bdtlast->status; status = (USB_BDT_BSTALL | bytecount); bdtlast->status = status; bdtlast->addr = (uint8_t *)PHYS_ADDR(&priv->ctrl); priv->ep0done = 1; } } /**************************************************************************** * Name: pic32mx_ep0setup ****************************************************************************/ static void pic32mx_ep0setup(struct pic32mx_usbdev_s *priv) { volatile struct usbotg_bdtentry_s *bdt; struct pic32mx_ep_s *ep0 = &priv->eplist[EP0]; struct pic32mx_req_s *privreq = pic32mx_rqpeek(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; /* Terminate any pending requests (doesn't work if the pending request * was a zero-length transfer!) */ while (!pic32mx_rqempty(ep0)) { int16_t result = OK; if (privreq->req.xfrd != privreq->req.len) { result = -EPROTO; } usbtrace(TRACE_COMPLETE(ep0->ep.eplog), privreq->req.xfrd); pic32mx_reqcomplete(ep0, result); } /* Check if the USB currently owns the buffer */ if ((ep0->bdtin->status & USB_BDT_UOWN) != 0) { /* Yes.. give control back to the CPU. This handles for the * ownership after a stall. */ bdtdbg("EP0 BDT IN [%p] {%08x, %08x}\n", ep0->bdtin, ep0->bdtin->status, ep0->bdtin->addr); ep0->bdtin->status &= ~USB_BDT_UOWN; } /* Assume NOT stalled; no TX in progress */ ep0->stalled = false; ep0->txbusy = false; /* 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); ullvdbg("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_STALLED; } 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_STALLED; } 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_STALLED; } } 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_STALLED; } 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 = 0; ret = pic32mx_epstall(&privep->ep, true); } else { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADCLEARFEATURE), 0); priv->ctrlstate = CTRLSTATE_STALLED; } } else { /* Let the class implementation handle all other recipients (except for the * endpoint recipient) */ 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 */ ullvdbg("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 = 1; ret = pic32mx_epstall(&privep->ep, false); } else { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_BADSETFEATURE), 0); priv->ctrlstate = CTRLSTATE_STALLED; } } 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_STALLED; } 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_STALLED; } } 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_STALLED; } } 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_STALLED; } } 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_STALLED; } 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_STALLED; * 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_STALLED * 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_STALLED)) { /* 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(&priv->eplist[EP0], 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. */ if (priv->ctrlstate != CTRLSTATE_STALLED) { /* No.. Set up the BDTs to accept the next setup commend. */ pic32mx_ep0nextsetup(priv); } else { /* Stall EP0 */ (void)pic32mx_epstall(&ep0->ep, false); } } /**************************************************************************** * 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; volatile struct usbotg_bdtentry_s *bdtnext; uint32_t data01; int ret; /* An EP0 OUT transfer has just completed */ ep0->txbusy = false; /* Get the last IN BDT and its data toggle */ bdtlast = ep0->bdtin; data01 = (bdtlast->status & USB_BDT_DATA01); /* Get the next BDT */ if (bdtlast == &g_bdt[EP0_IN_EVEN]) { bdtnext = &g_bdt[EP0_IN_ODD]; } else { DEBUGASSERT(bdtlast == &g_bdt[EP0_IN_ODD]); bdtnext = &g_bdt[EP0_IN_EVEN]; } /* Make sure that we own the last BDT. */ bdtlast->status = (USB_BDT_DATA0 | USB_BDT_DTS | USB_BDT_BSTALL); bdtlast->addr = 0; /* Setup the next BDT with the same data toggle (We own this one too) */ bdtnext->status = (data01 | USB_BDT_DTS | USB_BDT_BSTALL); bdtnext->addr = 0; /* Save the next BDT */ ep0->bdtin = bdtnext; /* 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. */ pic32mx_wrcomplete(priv, &priv->eplist[EP0]); /* Handle the next queue IN transfer. If there are no further IN * transfers, pic32mx_wrrequest will set ctrlstate = CTRLSTATE_WAITSETUP */ ret = pic32mx_wrrequest(priv, &priv->eplist[EP0]); if (ret == OK) { /* Is there another IN transfer in-flight? */ if (priv->ctrlstate == CTRLSTATE_WAITSETUP) { /* Set DATA1 to one in the next BDT because the first thing * we will do when transmitting is toggle the bit */ bdtnext->status &= ~USB_BDT_DATA01; } } } /* No.. Are we processing the completion of a status response? */ else if (priv->ctrlstate == CTRLSTATE_WAITSETUP) { /* 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; } } /* Clear DATA1 to one in the next BDT because the first thing we will * do when transmitting is toggle the bit */ bdtnext->status &= ~USB_BDT_DATA01; } /* No other state is expected in this context */ else { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDSTATE), priv->ctrlstate); priv->ctrlstate = CTRLSTATE_STALLED; } } /**************************************************************************** * Name: pic32mx_ep0outcomplete ****************************************************************************/ static void pic32mx_ep0outcomplete(struct pic32mx_usbdev_s *priv) { struct pic32mx_ep_s *ep0 = &priv->eplist[EP0]; switch (priv->ctrlstate) { case CTRLSTATE_RDREQUEST: /* Read request in progress */ /* 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_ep0nextsetup(priv); } break; case CTRLSTATE_WAITSETUP: /* No transfer in progress, waiting for SETUP */ { /* 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; default: { /* Unexpected state OR host aborted the OUT transfer before it * completed, STALL the endpoint in either case */ usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDSTATE), priv->ctrlstate); priv->ctrlstate = CTRLSTATE_STALLED; } break; } } /**************************************************************************** * Name: pic32mx_ep0transfer ****************************************************************************/ static void pic32mx_ep0transfer(struct pic32mx_usbdev_s *priv, uint16_t status) { 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 ((status & USB_STAT_DIR) == USB_STAT_DIR_OUT) { int index; /* It was an EP0 OUT transaction. Get the index to the BDT. */ index = ((status & USB_STAT_PPBI) == 0 ? EP0_OUT_EVEN : EP0_OUT_ODD); bdt = &g_bdt[index]; priv->eplist[0].bdtout = bdt; bdtdbg("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), status); 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), status); /* Handle the IN transfer complete */ pic32mx_ep0incomplete(priv); } /* Check for a stall */ if (priv->ctrlstate == CTRLSTATE_STALLED) { /* 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 just need * reset any existing configuration/transfer states. */ pic32mx_swreset(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); ulldbg("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 tranfer 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; /* 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 = irqsave(); /* 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); irqrestore(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 = irqsave(); 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; } } } irqrestore(flags); return privep; } /**************************************************************************** * Name: pic32mx_epunreserve ****************************************************************************/ static inline void pic32mx_epunreserve(struct pic32mx_usbdev_s *priv, struct pic32mx_ep_s *privep) { irqstate_t flags = irqsave(); priv->epavail |= PIC32MX_ENDP_BIT(USB_EPNO(privep->ep.eplog)); irqrestore(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; 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. */ 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); priv->eplist[EP0].bdtout = bdt; bdt = &g_bdt[EP0_OUT_ODD]; bdt->status = (USB_BDT_BSTALL | bytecount); bdt->addr = (uint8_t *)PHYS_ADDR(&priv->ctrl); /* Configure the IN BDTs. Set DATA0 in the EVEN BDT because the first * thing we will do when transmitting is toggle the bit */ bdt = &g_bdt[EP0_IN_EVEN]; bdt->status = (USB_BDT_DATA0 | USB_BDT_DTS | USB_BDT_BSTALL); bdt->addr = 0; priv->eplist[EP0].bdtin = bdt; bdt = &g_bdt[EP0_IN_ODD]; bdt->status = (USB_BDT_DATA0 | USB_BDT_DTS | USB_BDT_BSTALL); bdt->addr = 0; } /**************************************************************************** * 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; uint16_t status; uint8_t epno; bool epin; bool bidi; int index; #ifdef CONFIG_DEBUG if (!ep || !desc) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0); ulldbg("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, 1, 0); bdt = &g_bdt[index]; privep->bdtin = bdt; /* Mark that we own the entry */ status = bdt->status; status &= ~USB_BDT_UOWN; /* Set DATA1 to one because the first thing we will do when transmitting is * toggle the bit */ status |= USB_BDT_DATA1; bdt->status = status; bdtdbg("EP%d BDT IN [%p] {%08x, %08x}\n", epno, bdt, status, bdt->addr); /* Now do the same for the other buffer. The only difference is the * we clear DATA1 (making it DATA0) */ bdt = &g_bdt[index+1]; status = bdt->status; status &= ~(USB_BDT_UOWN | USB_BDT_DATA01); bdt->status = status; bdtdbg("EP%d BDT IN [%p] {%08x, %08x}\n", epno, bdt, status, bdt->addr); } if (!epin || bidi) { index = EP(epno, 0, 0); bdt = &g_bdt[index]; privep->bdtout = bdt; /* Mark that we own the entry */ status = bdt->status; status &= ~USB_BDT_UOWN; /* Set DATA1 to one because the first thing we will do when transmitting is * toggle the bit */ status |= USB_BDT_DATA1; bdt->status = status; bdtdbg("EP%d BDT OUT [%p] {%08x, %08x}\n", epno, bdt, status, bdt->addr); /* Now do the same for the other buffer. The only difference is the * we clear DATA1 (making it DATA0) */ bdt = &g_bdt[index+1]; status = bdt->status & ~USB_BDT_UOWN; status &= ~USB_BDT_DATA01; bdt->status = status; bdtdbg("EP%d BDT OUT [%p] {%08x, %08x}\n", epno, 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_usbdev_s *priv; struct pic32mx_ep_s *privep; volatile uint32_t *ptr; int epno; int i; irqstate_t flags; #ifdef CONFIG_DEBUG if (!ep) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0); ulldbg("ERROR: ep=%p\n", ep); return -EINVAL; } #endif privep = (struct pic32mx_ep_s *)ep; priv = privep->dev; epno = USB_EPNO(ep->eplog); usbtrace(TRACE_EPDISABLE, epno); /* Cancel any ongoing activity */ flags = irqsave(); pic32mx_cancelrequests(privep); /* 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, 0, 0)]; for (i = 0; i < USB_BDT_WORD_SIZE * USB_NBDTS_PER_EP; i++) { *ptr++ = 0; } irqrestore(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 if (!ep) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0); return NULL; } #endif usbtrace(TRACE_EPALLOCREQ, USB_EPNO(ep->eplog)); privreq = (struct pic32mx_req_s *)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 if (!ep || !req) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0); return; } #endif usbtrace(TRACE_EPFREEREQ, USB_EPNO(ep->eplog)); 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 if (!req || !req->callback || !req->buf || !ep) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0); ulldbg("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 if (!priv->driver) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_NOTCONFIGURED), priv->usbdev.speed); ulldbg("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; flags = irqsave(); /* If we are stalled, then drop all requests on the floor */ if (privep->stalled) { pic32mx_abortrequest(privep, privreq, -EBUSY); ulldbg("ERROR: stalled\n"); ret = -EBUSY; } /* 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. */ else if (USB_ISEPIN(ep->eplog) || epno == EP0) { /* Add the new request to the request queue for the IN endpoint */ pic32mx_rqenqueue(privep, privreq); usbtrace(TRACE_INREQQUEUED(epno), req->len); /* If the IN endpoint FIFO is available, then transfer the data now */ if (!privep->txbusy) { ret = pic32mx_wrrequest(priv, privep); } } /* Handle OUT (host-to-device) requests */ else { /* Add the new request to the request queue for the OUT endpoint */ privep->txnullpkt = 0; pic32mx_rqenqueue(privep, privreq); usbtrace(TRACE_OUTREQQUEUED(epno), req->len); } irqrestore(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; struct pic32mx_usbdev_s *priv; irqstate_t flags; #ifdef CONFIG_DEBUG if (!ep || !req) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0); return -EINVAL; } #endif usbtrace(TRACE_EPCANCEL, USB_EPNO(ep->eplog)); priv = privep->dev; flags = irqsave(); pic32mx_cancelrequests(privep); irqrestore(flags); return OK; } /**************************************************************************** * Name: pic32mx_epbdtstall ****************************************************************************/ static int pic32mx_epbdtstall(struct usbdev_ep_s *ep, volatile struct usbotg_bdtentry_s *bdt, bool resume, bool epin) { struct pic32mx_ep_s *privep; struct pic32mx_usbdev_s *priv; uint32_t regaddr; uint16_t regval; uint16_t status; uint8_t epno; /* Recover pointers */ privep = (struct pic32mx_ep_s *)ep; priv = (struct pic32mx_usbdev_s *)privep->dev; epno = USB_EPNO(ep->eplog); /* Handle the resume condition */ if (resume) { /* Resuming a stalled endpoint */ usbtrace(TRACE_EPRESUME, epno); privep->stalled = false; /* 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 an IN endpoint */ if (USB_ISEPIN(ep->eplog)) { /* If the endpoint is an IN endpoint then we need to return it * to the CPU and reset the DTS bit so that the next transfer * is correct */ status = bdt->status; status |= (USB_BDT_UOWN | USB_BDT_DATA1); /* Then give the BDT to the USB */ bdtdbg("EP%d BDT OUT [%p] {%08x, %08x}\n", epno, bdt, status, bdt->addr); bdt->status = status; /* Restart any queued write requests */ pic32mx_wrrestart(priv, privep); } else { /* If the endpoint was an OUT endpoint then we need to give * control of the endpoint back to the USB so that the * function driver can receive the data as they expected. * Also need to set the DTS bit so the next packet will be * correct. */ status = bdt->status; status |= (USB_BDT_UOWN | USB_BDT_DATA1 | USB_BDT_DTS); /* Then give the BDT to the USB */ bdtdbg("EP%d BDT OUT [%p] {%08x, %08x}\n", epno, bdt, status, bdt->addr); bdt->status = status; } } /* Handle the stall condition */ else { usbtrace(TRACE_EPSTALL, epno); privep->stalled = true; /* Then STALL the endpoint */ status = bdt->status & ~(USB_BDT_BYTECOUNT_MASK | USB_BDT_DATA01); status |= (USB_BDT_UOWN | USB_BDT_DATA0 | USB_BDT_DTS | USB_BDT_BSTALL); /* And give the BDT to the USB */ bdtdbg("EP%d BDT OUT [%p] {%08x, %08x}\n", epno, bdt, status, bdt->addr); bdt->status = status; } 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 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 = irqsave(); usbtrace(resume ? TRACE_EPRESUME : TRACE_EPSTALL, USB_EPNO(ep->eplog)); /* 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, privep->bdtin, resume, true); if (ret == OK) { ret = pic32mx_epbdtstall(ep, privep->bdtout, resume, false); } } /* Otherwise, select the BDT for the endpoint direction */ else if (USB_ISEPIN(ep->eplog)) { /* It is an IN endpoint */ ret = pic32mx_epbdtstall(ep, privep->bdtin, resume, true); } else { /* It is an OUT endpoint */ ret = pic32mx_epbdtstall(ep, privep->bdtout, resume, false); } irqrestore(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 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 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 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 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 if (!dev) { usbtrace(TRACE_DEVERROR(PIC32MX_TRACEERR_INVALIDPARMS), 0); return -ENODEV; } #endif priv->selfpowered = selfpowered; return OK; } /**************************************************************************** * Initialization/Reset ****************************************************************************/ /**************************************************************************** * Name: pic32mx_reset ****************************************************************************/ 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); } /**************************************************************************** * 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); /* Reset endpoint status */ privep->stalled = false; privep->halted = false; privep->txbusy = 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; } /**************************************************************************** * Name: pic32mx_hwreset ****************************************************************************/ 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; /* 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 */ up_mdelay(5); 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 in its normal, unconnected state */ pic32mx_reset(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); /* 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 = irqsave(); 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); irqrestore(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 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 unconnected state */ DEBUGASSERT(priv->devstate == DEVSTATE_DETACHED); pic32mx_reset(priv); /* We do not know the order in which the user will call APIs. If * pic32mx_attach() were called before we got here, the 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); } } 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 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. */ flags = irqsave(); pic32mx_reset(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_reset() * call above was probably sufficient). */ pic32mx_hwshutdown(priv); pic32mx_stateinit(priv); /* Unhook the driver */ priv->driver = NULL; irqrestore(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 */