/**************************************************************************** * arch/arm/src/samv7/sam_usbdevhs.c * * SPDX-License-Identifier: BSD-3-Clause * SPDX-FileCopyrightText: 2019 Gregory Nutt. All rights reserved. * SPDX-FileCopyrightText: 2015-2016 Gregory Nutt. All rights reserved. * SPDX-FileCopyrightText: 2009,2014 Atmel Corporation * * 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, Atmel, 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 #include #include #include #include #include "arm_internal.h" #include "chip.h" #include "sam_periphclks.h" #include "hardware/sam_usbhs.h" #include "sam_clockconfig.h" #include "sam_usbdev.h" #if defined(CONFIG_USBDEV) && defined(CONFIG_SAMV7_USBDEVHS) /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Configuration ************************************************************/ #ifndef CONFIG_USBDEV_EP0_MAXSIZE # define CONFIG_USBDEV_EP0_MAXSIZE 64 #endif #ifndef CONFIG_DEBUG_USB_INFO # undef CONFIG_SAMV7_USBHS_REGDEBUG #endif /* Number of DMA transfer descriptors. Default: 8 */ #ifndef CONFIG_SAMV7_USBDEVHS_NDTDS # define CONFIG_SAMV7_USBDEVHS_NDTDS 8 #endif #ifndef CONFIG_USBDEV_DMA # warning Currently CONFIG_USBDEV_DMA must be set to make all endpoints working #endif #if defined(CONFIG_USBDEV_DUALSPEED) && defined(CONFIG_SAMV7_USBDEVHS_LOWPOWER) # error CONFIG_USBDEV_DUALSPEED must not be defined with full-speed only support #endif #if !defined(CONFIG_USBDEV_DUALSPEED) && !defined(CONFIG_SAMV7_USBDEVHS_LOWPOWER) # warning CONFIG_USBDEV_DUALSPEED should be defined for high speed support #endif /* Not yet supported */ #undef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER /* Driver Definitions *******************************************************/ /* Initial interrupt mask: Reset + Suspend + Correct Transfer */ #define SAM_CNTR_SETUP (USB_CNTR_RESETM|USB_CNTR_SUSPM|USB_CNTR_CTRM) /* Endpoint definitions */ #define EP0 (0) #define SAM_EPSET_ALL (0xffff) /* All endpoints */ #define SAM_EPSET_NOTEP0 (0xfffe) /* All endpoints except EP0 */ #define SAM_EP_BIT(ep) (1 << (ep)) #define SAM_EP0_MAXPACKET (64) /* EP0 Max. packet size */ #ifdef CONFIG_SAMV7_USBHS_EP7DMA_WAR /* Normally EP1..7 should support DMA (0x1fe), but according an ERRATA in * "Atmel-11296D-ATARM-SAM E70-Datasheet_19-Jan-16" only the EP1..6 support * the DMA transfer (0x7e) */ # define SAM_EPSET_DMA (0x007e) /* All endpoints that support DMA transfers */ #else # define SAM_EPSET_DMA (0x01fe) /* All endpoints that support DMA transfers */ #endif /* DMA FIFO */ #define DMA_MAX_FIFO_SIZE (65536/1) /* Max size of the FMA FIFO */ #define EPT_FIFO_SIZE 16384 /* FIFO space size in units of 32-bit words */ /* USB-related masks */ #define REQRECIPIENT_MASK (USB_REQ_TYPE_MASK | USB_REQ_RECIPIENT_MASK) /* Endpoint register masks (handling toggle fields) */ #define EPR_NOTOG_MASK (USB_EPR_CTR_RX | USB_EPR_SETUP | USB_EPR_EPTYPE_MASK |\ USB_EPR_EP_KIND | USB_EPR_CTR_TX | USB_EPR_EA_MASK) #define EPR_TXDTOG_MASK (USB_EPR_STATTX_MASK | EPR_NOTOG_MASK) #define EPR_RXDTOG_MASK (USB_EPR_STATRX_MASK | EPR_NOTOG_MASK) /* Request queue operations *************************************************/ #define sam_rqempty(q) ((q)->head == NULL) #define sam_rqpeek(q) ((q)->head) /* Buffer Alignment ********************************************************* * * DMA buffers be aligned the 8-byte (2 word boundaries). However, if the * data cache is enabled the a higher level of alignment is required. That * is because the data will need to be invalidated and that cache * invalidation will occur in multiples of full cache lines. */ #ifdef CONFIG_ARMV7M_DCACHE /* Align to the cache line size which we assume is >= 8 */ # define USBHS_ALIGN ARMV7M_DCACHE_LINESIZE # define USBHS_ALIGN_MASK (USBHS_ALIGN-1) # define USBHS_ALIGN_DOWN(n) ((n) & ~USBHS_ALIGN_MASK) # define USBHS_ALIGN_UP(n) (((n) + USBHS_ALIGN_MASK) & ~USBHS_ALIGN_MASK) # ifndef CONFIG_ARMV7M_DCACHE_WRITETHROUGH # warning !!! This driver will not work without CONFIG_ARMV7M_DCACHE_WRITETHROUGH=y!!! # endif #else /* Use the minimum alignment requirement */ # define USBHS_ALIGN 8 # define USBHS_ALIGN_MASK 7 # define USBHS_ALIGN_DOWN(n) ((n) & ~7) # define USBHS_ALIGN_UP(n) (((n) + 7) & ~7) #endif /* USB trace ****************************************************************/ /* Trace error codes */ #define SAM_TRACEERR_ALLOCFAIL 0x0001 #define SAM_TRACEERR_BADCLEARFEATURE 0x0002 #define SAM_TRACEERR_BADDEVGETSTATUS 0x0003 #define SAM_TRACEERR_BADEPGETSTATUS 0x0004 #define SAM_TRACEERR_BADEOBSTATE 0x0005 #define SAM_TRACEERR_BADEPNO 0x0006 #define SAM_TRACEERR_BADEPTYPE 0x0007 #define SAM_TRACEERR_BADGETCONFIG 0x0008 #define SAM_TRACEERR_BADGETSETDESC 0x0009 #define SAM_TRACEERR_BADGETSTATUS 0x000a #define SAM_TRACEERR_BADSETADDRESS 0x000b #define SAM_TRACEERR_BADSETCONFIG 0x000c #define SAM_TRACEERR_BADSETFEATURE 0x000d #define SAM_TRACEERR_BINDFAILED 0x000e #define SAM_TRACEERR_DISPATCHSTALL 0x000f #define SAM_TRACEERR_DMAERR 0x0010 #define SAM_TRACEERR_DRIVER 0x0011 #define SAM_TRACEERR_DRIVERREGISTERED 0x0012 #define SAM_TRACERR_REMAINING 0x0013 #define SAM_TRACEERR_EP0SETUPOUTSIZE 0x0014 #define SAM_TRACEERR_EP0SETUPSTALLED 0x0015 #define SAM_TRACEERR_EPOUTNULLPACKET 0x0016 #define SAM_TRACEERR_EPRESERVE 0x0017 #define SAM_TRACEERR_NCFGOK 0x0018 #define SAM_TRACEERR_INVALIDCTRLREQ 0x0019 #define SAM_TRACEERR_IRQREGISTRATION 0x001a #define SAM_TRACEERR_NOTCONFIGURED 0x001b #define SAM_TRACEERR_REQABORTED 0x001c #define SAM_TRACEERR_TXINERR 0x001d /* Trace interrupt codes */ #define SAM_TRACEINTID_ADDRESSED 0x0001 #define SAM_TRACEINTID_CLEARFEATURE 0x0002 #define SAM_TRACEINTID_INTSUSPD 0x0003 #define SAM_TRACEINTID_DEVGETSTATUS 0x0004 #define SAM_TRACEINTID_DISPATCH 0x0005 #define SAM_TRACEINTID_DMA 0x0006 #define SAM_TRACEINTID_DMAEOB 0x0007 #define SAM_TRACEINTID_DMAEOC 0x0008 #define SAM_TRACEINTID_ENDRESET 0x0009 #define SAM_TRACEINTID_EP 0x000a #define SAM_TRACEINTID_EP0SETUPIN 0x000b #define SAM_TRACEINTID_EP0SETUPOUT 0x000c #define SAM_TRACEINTID_EP0SETUPSETADDRESS 0x000d #define SAM_TRACEINTID_EPDMAINT 0x000e #define SAM_TRACEINTID_EPGETSTATUS 0x000f #define SAM_TRACEINTID_EPINQEMPTY 0x0010 #define SAM_TRACEINTID_EPINT 0x0011 #define SAM_TRACEINTID_EPOUTQEMPTY 0x0012 #define SAM_TRACEINTID_GETCONFIG 0x0013 #define SAM_TRACEINTID_GETSETDESC 0x0014 #define SAM_TRACEINTID_GETSETIF 0x0015 #define SAM_TRACEINTID_GETSTATUS 0x0016 #define SAM_TRACEINTID_IFGETSTATUS 0x0017 #define SAM_TRACEINTID_INTERRUPT 0x0018 #define SAM_TRACEINTID_INTSOF 0x0019 #define SAM_TRACEINTID_INTMSOF 0x001a #define SAM_TRACEINTID_NOSTDREQ 0x001b #define SAM_TRACEINTID_PENDING 0x001c #define SAM_TRACEINTID_RXRDY 0x001d #define SAM_TRACEINTID_RXSETUP 0x001e #define SAM_TRACEINTID_SETCONFIG 0x001f #define SAM_TRACEINTID_SETFEATURE 0x0020 #define SAM_TRACEINTID_SPEED 0x0021 #define SAM_TRACEINTID_STALLSNT 0x0022 #define SAM_TRACEINTID_SYNCHFRAME 0x0023 #define SAM_TRACEINTID_TXINI 0x0024 #define SAM_TRACEINTID_UPSTRRES 0x0025 #define SAM_TRACEINTID_WAKEUP 0x0026 /* Byte ordering in host-based values */ #ifdef CONFIG_ENDIAN_BIG # define LSB 1 # define MSB 0 #else # define LSB 0 # define MSB 1 #endif /**************************************************************************** * * Private Types ****************************************************************************/ /* State of an endpoint */ enum sam_epstate_e { /* --- All Endpoints --- */ USBHS_EPSTATE_DISABLED = 0, /* Endpoint is disabled */ USBHS_EPSTATE_STALLED, /* Endpoint is stalled */ USBHS_EPSTATE_IDLE, /* Endpoint is idle (i.e. ready for transmission) */ USBHS_EPSTATE_SENDING, /* Endpoint is sending data */ USBHS_EPSTATE_SENDING_DMA, /* Endpoint is sending data via DMA */ USBHS_EPSTATE_NBUSYBK, /* Endpoint DMA complete, waiting for NBUSYBK==0 */ USBHS_EPSTATE_RECEIVING, /* Endpoint is receiving data */ /* --- Endpoint 0 Only --- */ USBHS_EPSTATE_EP0DATAOUT, /* Endpoint 0 is receiving SETUP OUT data */ USBHS_EPSTATE_EP0STATUSIN, /* Endpoint 0 is sending SETUP status */ USBHS_EPSTATE_EP0ADDRESS /* Address change is pending completion of status */ }; /* The overall state of the device */ enum sam_devstate_e { USBHS_DEVSTATE_SUSPENDED = 0, /* The device is currently suspended */ USBHS_DEVSTATE_POWERED, /* Host is providing +5V through the USB cable */ USBHS_DEVSTATE_DEFAULT, /* Device has been reset */ USBHS_DEVSTATE_ADDRESSED, /* The device has been given an address on the bus */ USBHS_DEVSTATE_CONFIGURED /* A valid configuration has been selected. */ }; /* The result of EP0 SETUP processing */ enum sam_ep0setup_e { USBHS_EP0SETUP_SUCCESS = 0, /* The SETUP was handle without incident */ USBHS_EP0SETUP_DISPATCHED, /* The SETUP was forwarded to the class driver */ USBHS_EP0SETUP_ADDRESS, /* A new device address is pending */ USBHS_EP0SETUP_STALL /* An error occurred */ }; /* DMA transfer descriptor */ #ifdef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER struct sam_dtd_s { struct usbhs_dtd_s hw; /* These are the fields as seen by the hardware */ uint32_t pad; /* Pad to 16 bytes to support arrays of descriptors */ }; #define SIZEOF_SAM_DTD_S 16 #endif /* The following is used to manage lists of free DMA transfer descriptors */ struct sam_list_s { struct sam_list_s *flink; /* Link to next entry in the list */ /* Variable length entry data follows */ }; 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 sam_req_s { struct usbdev_req_s req; /* Standard USB request */ struct sam_req_s *flink; /* Supports a singly linked list */ uint16_t inflight; /* Number of TX bytes written to FIFO */ }; /* The head of a queue of requests */ struct sam_rqhead_s { struct sam_req_s *head; /* Requests are added to the head of the list */ struct sam_req_s *tail; /* Requests are removed from the tail of the list */ }; /* This is the internal representation of an endpoint */ struct sam_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 sam_ep_s. */ struct usbdev_ep_s ep; /* Standard endpoint structure */ /* SAMV7-specific fields */ struct sam_usbdev_s *dev; /* Reference to private driver data */ struct sam_rqhead_s reqq; /* Read/write request queue */ #ifdef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER struct sam_dtd_s *dtdll; /* Head of the DMA transfer descriptor list */ #endif volatile uint8_t epstate; /* State of the endpoint (see enum sam_epstate_e) */ volatile uint8_t bank; /* Current reception bank (0 or 1) */ uint8_t stalled:1; /* true: Endpoint is stalled */ uint8_t halted:1; /* true: Endpoint feature halted */ uint8_t zlpneeded:1; /* Zero length packet needed at end of transfer */ uint8_t zlpsent:1; /* Zero length packet has been sent */ }; struct sam_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 structsam_usbdev_s. */ struct usbdev_s usbdev; /* The bound device class driver */ struct usbdevclass_driver_s *driver; /* USBHS-specific fields */ struct usb_ctrlreq_s ctrl; /* Last EP0 request */ uint8_t devstate; /* State of the device (see enum sam_devstate_e) */ uint8_t prevstate; /* Previous state of the device before SUSPEND */ uint8_t selfpowered:1; /* 1: Device is self powered */ uint16_t epavail; /* Bitset of available endpoints */ /* DMA Transfer descriptors */ #ifdef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER struct sam_dtd_s *tdfree; /* A list of free transfer descriptors */ #ifndef CONFIG_SAMV7_USBDEVHS_PREALLOCATE struct sam_dtd_s *tdpool; /* Pool of allocated DMA transfer descriptors */ #endif #endif /* The endpoint list */ struct sam_ep_s eplist[SAM_USBHS_NENDPOINTS]; /* EP0 data buffer. For data that is included in an EP0 SETUP OUT * transaction. In this case, no request is in place from the class * driver and the incoming data is caught in this buffer. The size * of valid dat in the buffer is given by ctrlreg.len[]. For the * case of EP0 SETUP IN transaction, the normal request mechanism is * used and the class driver provides the buffering. */ uint8_t ep0out[SAM_EP0_MAXPACKET]; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Register operations ******************************************************/ #ifdef CONFIG_SAMV7_USBHS_REGDEBUG static void sam_printreg(uintptr_t regaddr, uint32_t regval, bool iswrite); static void sam_checkreg(uintptr_t regaddr, uint32_t regval, bool iswrite); static uint32_t sam_getreg(uintptr_t regaddr); static void sam_putreg(uint32_t regval, uintptr_t regaddr); static void sam_dumpep(struct sam_usbdev_s *priv, int epno); #else static inline uint32_t sam_getreg(uintptr_t regaddr); static inline void sam_putreg(uint32_t regval, uintptr_t regaddr); # define sam_dumpep(priv,epno) #endif /* Suspend/Resume Helpers ***************************************************/ static void sam_suspend(struct sam_usbdev_s *priv); static void sam_resume(struct sam_usbdev_s *priv); /* DMA Transfer Helpers *****************************************************/ #ifdef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER static struct sam_dtd_s *sam_dtd_alloc(struct sam_usbdev_s *priv); static void sam_dtd_free(struct sam_usbdev_s *priv, struct sam_dtd_s *dtd); #endif static void sam_dma_single(uint8_t epno, struct sam_req_s *privreq, uint32_t dmacontrol); static void sam_dma_wrsetup(struct sam_usbdev_s *priv, struct sam_ep_s *privep, struct sam_req_s *privreq); static void sam_dma_rdsetup(struct sam_usbdev_s *priv, struct sam_ep_s *privep, struct sam_req_s *privreq); /* Request Helpers **********************************************************/ static struct sam_req_s * sam_req_dequeue(struct sam_rqhead_s *queue); static void sam_req_enqueue(struct sam_rqhead_s *queue, struct sam_req_s *req); static inline void sam_req_abort(struct sam_ep_s *privep, struct sam_req_s *privreq, int16_t result); static void sam_req_complete(struct sam_ep_s *privep, int16_t result); static void sam_ep_fifocon(unsigned int epno); static void sam_req_wrsetup(struct sam_usbdev_s *priv, struct sam_ep_s *privep, struct sam_req_s *privreq); static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep); static void sam_req_rddone(struct sam_usbdev_s *priv, struct sam_ep_s *privep, struct sam_req_s *privreq, uint16_t recvsize); static void sam_req_rdenable(uint8_t epno); static void sam_req_rddisable(uint8_t epno); static int sam_req_read(struct sam_usbdev_s *priv, struct sam_ep_s *privep, uint16_t recvsize); static void sam_req_cancel(struct sam_ep_s *privep, int16_t status); /* Interrupt level processing ***********************************************/ static void sam_ep0_read(uint8_t *buffer, size_t buflen); static void sam_ctrlep_write(struct sam_ep_s *privep, const uint8_t *buffer, size_t buflen); static void sam_ep_write(struct sam_ep_s *privep, const uint8_t *buffer, size_t buflen); static void sam_ep0_dispatch(struct sam_usbdev_s *priv); static void sam_ep0_setup(struct sam_usbdev_s *priv); #ifdef CONFIG_USBDEV_DMA static void sam_dma_interrupt(struct sam_usbdev_s *priv, int epno); #endif static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno); static int sam_usbhs_interrupt(int irq, void *context, void *arg); /* Endpoint helpers *********************************************************/ static void sam_ep_reset(struct sam_usbdev_s *priv, uint8_t epno); static void sam_epset_reset(struct sam_usbdev_s *priv, uint16_t epset); static inline struct sam_ep_s * sam_ep_reserve(struct sam_usbdev_s *priv, uint16_t epset); static inline void sam_ep_unreserve(struct sam_usbdev_s *priv, struct sam_ep_s *privep); static int sam_ep_configure_internal(struct sam_ep_s *privep, const struct usb_epdesc_s *desc); static inline int sam_ep0_configure(struct sam_usbdev_s *priv); /* Endpoint operations ******************************************************/ static int sam_ep_configure(struct usbdev_ep_s *ep, const struct usb_epdesc_s *desc, bool last); static int sam_ep_disable(struct usbdev_ep_s *ep); static struct usbdev_req_s * sam_ep_allocreq(struct usbdev_ep_s *ep); static void sam_ep_freereq(struct usbdev_ep_s *ep, struct usbdev_req_s *); #ifdef CONFIG_USBDEV_DMA static void *sam_ep_allocbuffer(struct usbdev_ep_s *ep, uint16_t nbytes); static void sam_ep_freebuffer(struct usbdev_ep_s *ep, void *buf); #endif static int sam_ep_submit(struct usbdev_ep_s *ep, struct usbdev_req_s *req); static int sam_ep_cancel(struct usbdev_ep_s *ep, struct usbdev_req_s *req); static int sam_ep_stall(struct usbdev_ep_s *ep, bool resume); /* USB device controller operations *****************************************/ static struct usbdev_ep_s * sam_allocep(struct usbdev_s *dev, uint8_t epno, bool in, uint8_t eptype); static void sam_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep); static int sam_getframe(struct usbdev_s *dev); static int sam_wakeup(struct usbdev_s *dev); static int sam_selfpowered(struct usbdev_s *dev, bool selfpowered); static int sam_pullup(struct usbdev_s *dev, bool enable); /* Initialization/Reset *****************************************************/ static void sam_reset(struct sam_usbdev_s *priv); static void sam_hw_setup(struct sam_usbdev_s *priv); static void sam_sw_setup(struct sam_usbdev_s *priv); static void sam_hw_shutdown(struct sam_usbdev_s *priv); static void sam_sw_shutdown(struct sam_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 sam_usbdev_s g_usbhs; static const struct usbdev_epops_s g_epops = { .configure = sam_ep_configure, .disable = sam_ep_disable, .allocreq = sam_ep_allocreq, .freereq = sam_ep_freereq, #ifdef CONFIG_USBDEV_DMA .allocbuffer = sam_ep_allocbuffer, .freebuffer = sam_ep_freebuffer, #endif .submit = sam_ep_submit, .cancel = sam_ep_cancel, .stall = sam_ep_stall, }; static const struct usbdev_ops_s g_devops = { .allocep = sam_allocep, .freeep = sam_freeep, .getframe = sam_getframe, .wakeup = sam_wakeup, .selfpowered = sam_selfpowered, .pullup = sam_pullup, }; /* This describes endpoint 0 */ static const struct usb_epdesc_s g_ep0desc = { .len = USB_SIZEOF_EPDESC, .type = USB_DESC_TYPE_ENDPOINT, .addr = EP0, .attr = USB_EP_ATTR_XFER_CONTROL, .mxpacketsize = {64, 0}, .interval = 0 }; #ifdef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER #ifdef CONFIG_SAMV7_USBDEVHS_PREALLOCATE /* This is a properly aligned pool of preallocated DMA transfer descriptors */ static struct sam_dtd_s g_dtdpool[CONFIG_SAMV7_USBDEVHS_NDTDS] aligned_data(16); #endif #endif /* Device error strings that may be enabled for more descriptive USB trace * output. */ #ifdef CONFIG_USBDEV_TRACE_STRINGS const struct trace_msg_t g_usb_trace_strings_deverror[] = { TRACE_STR(SAM_TRACEERR_ALLOCFAIL), TRACE_STR(SAM_TRACEERR_BADCLEARFEATURE), TRACE_STR(SAM_TRACEERR_BADDEVGETSTATUS), TRACE_STR(SAM_TRACEERR_BADEPGETSTATUS), TRACE_STR(SAM_TRACEERR_BADEOBSTATE), TRACE_STR(SAM_TRACEERR_BADEPNO), TRACE_STR(SAM_TRACEERR_BADEPTYPE), TRACE_STR(SAM_TRACEERR_BADGETCONFIG), TRACE_STR(SAM_TRACEERR_BADGETSETDESC), TRACE_STR(SAM_TRACEERR_BADGETSTATUS), TRACE_STR(SAM_TRACEERR_BADSETADDRESS), TRACE_STR(SAM_TRACEERR_BADSETCONFIG), TRACE_STR(SAM_TRACEERR_BADSETFEATURE), TRACE_STR(SAM_TRACEERR_BINDFAILED), TRACE_STR(SAM_TRACEERR_DISPATCHSTALL), TRACE_STR(SAM_TRACEERR_DMAERR), TRACE_STR(SAM_TRACEERR_DRIVER), TRACE_STR(SAM_TRACEERR_DRIVERREGISTERED), TRACE_STR(SAM_TRACERR_REMAINING), TRACE_STR(SAM_TRACEERR_EP0SETUPOUTSIZE), TRACE_STR(SAM_TRACEERR_EP0SETUPSTALLED), TRACE_STR(SAM_TRACEERR_EPOUTNULLPACKET), TRACE_STR(SAM_TRACEERR_EPRESERVE), TRACE_STR(SAM_TRACEERR_NCFGOK), TRACE_STR(SAM_TRACEERR_INVALIDCTRLREQ), TRACE_STR(SAM_TRACEERR_IRQREGISTRATION), TRACE_STR(SAM_TRACEERR_NOTCONFIGURED), TRACE_STR(SAM_TRACEERR_REQABORTED), TRACE_STR(SAM_TRACEERR_TXINERR), TRACE_STR_END }; #endif /* Interrupt event strings that may be enabled for more descriptive USB trace * output. */ #ifdef CONFIG_USBDEV_TRACE_STRINGS const struct trace_msg_t g_usb_trace_strings_intdecode[] = { TRACE_STR(SAM_TRACEINTID_ADDRESSED), TRACE_STR(SAM_TRACEINTID_CLEARFEATURE), TRACE_STR(SAM_TRACEINTID_INTSUSPD), TRACE_STR(SAM_TRACEINTID_DEVGETSTATUS), TRACE_STR(SAM_TRACEINTID_DISPATCH), TRACE_STR(SAM_TRACEINTID_DMA), TRACE_STR(SAM_TRACEINTID_DMAEOB), TRACE_STR(SAM_TRACEINTID_DMAEOC), TRACE_STR(SAM_TRACEINTID_ENDRESET), TRACE_STR(SAM_TRACEINTID_EP), TRACE_STR(SAM_TRACEINTID_EP0SETUPIN), TRACE_STR(SAM_TRACEINTID_EP0SETUPOUT), TRACE_STR(SAM_TRACEINTID_EP0SETUPSETADDRESS), TRACE_STR(SAM_TRACEINTID_EPDMAINT), TRACE_STR(SAM_TRACEINTID_EPGETSTATUS), TRACE_STR(SAM_TRACEINTID_EPINQEMPTY), TRACE_STR(SAM_TRACEINTID_EPINT), TRACE_STR(SAM_TRACEINTID_EPOUTQEMPTY), TRACE_STR(SAM_TRACEINTID_GETCONFIG), TRACE_STR(SAM_TRACEINTID_GETSETDESC), TRACE_STR(SAM_TRACEINTID_GETSETIF), TRACE_STR(SAM_TRACEINTID_GETSTATUS), TRACE_STR(SAM_TRACEINTID_IFGETSTATUS), TRACE_STR(SAM_TRACEINTID_INTERRUPT), TRACE_STR(SAM_TRACEINTID_INTSOF), TRACE_STR(SAM_TRACEINTID_INTMSOF), TRACE_STR(SAM_TRACEINTID_NOSTDREQ), TRACE_STR(SAM_TRACEINTID_PENDING), TRACE_STR(SAM_TRACEINTID_RXRDY), TRACE_STR(SAM_TRACEINTID_RXSETUP), TRACE_STR(SAM_TRACEINTID_SETCONFIG), TRACE_STR(SAM_TRACEINTID_SETFEATURE), TRACE_STR(SAM_TRACEINTID_SPEED), TRACE_STR(SAM_TRACEINTID_STALLSNT), TRACE_STR(SAM_TRACEINTID_SYNCHFRAME), TRACE_STR(SAM_TRACEINTID_TXINI), TRACE_STR(SAM_TRACEINTID_UPSTRRES), TRACE_STR(SAM_TRACEINTID_WAKEUP), TRACE_STR_END }; #endif /**************************************************************************** * Private Private Functions ****************************************************************************/ /**************************************************************************** * Register Operations ****************************************************************************/ /**************************************************************************** * Name: sam_printreg * * Description: * Print the contents of a SAMV7 USBHS register * ****************************************************************************/ #ifdef CONFIG_SAMV7_USBHS_REGDEBUG static void sam_printreg(uintptr_t regaddr, uint32_t regval, bool iswrite) { uinfo("%p%s%08x\n", regaddr, iswrite ? "<-" : "->", regval); } #endif /**************************************************************************** * Name: sam_checkreg * * Description: * Check if it is time to output debug information for accesses to a SAMV7 * USBHS register * ****************************************************************************/ #ifdef CONFIG_SAMV7_USBHS_REGDEBUG static void sam_checkreg(uintptr_t regaddr, uint32_t regval, bool iswrite) { static uintptr_t prevaddr = 0; static uint32_t preval = 0; static uint32_t count = 0; static bool prevwrite = false; /* Is this the same value that we read from/wrote * to the same register last time? * Are we polling the register? If so, suppress the output. */ if (regaddr == prevaddr && regval == preval && prevwrite == iswrite) { /* Yes.. Just increment the count */ count++; } else { /* No this is a new address or value or operation. Were there any * duplicate accesses before this one? */ if (count > 0) { /* Yes.. Just one? */ if (count == 1) { /* Yes.. Just one */ sam_printreg(prevaddr, preval, prevwrite); } else { /* No.. More than one. */ uinfo("[repeats %d more times]\n", count); } } /* Save the new address, value, count, and operation for next time */ prevaddr = regaddr; preval = regval; count = 0; prevwrite = iswrite; /* Show the new register access */ sam_printreg(regaddr, regval, iswrite); } } #endif /**************************************************************************** * Name: sam_getreg * * Description: * Get the contents of an SAMV7 register * ****************************************************************************/ #ifdef CONFIG_SAMV7_USBHS_REGDEBUG static uint32_t sam_getreg(uintptr_t regaddr) { /* Read the value from the register */ uint32_t regval = getreg32(regaddr); /* Check if we need to print this value */ sam_checkreg(regaddr, regval, false); return regval; } #else static inline uint32_t sam_getreg(uintptr_t regaddr) { return getreg32(regaddr); } #endif /**************************************************************************** * Name: sam_putreg * * Description: * Set the contents of an SAMV7 register to a value * ****************************************************************************/ #ifdef CONFIG_SAMV7_USBHS_REGDEBUG static void sam_putreg(uint32_t regval, uintptr_t regaddr) { /* Check if we need to print this value */ sam_checkreg(regaddr, regval, true); /* Write the value */ putreg32(regval, regaddr); } #else static inline void sam_putreg(uint32_t regval, uintptr_t regaddr) { putreg32(regval, regaddr); } #endif /**************************************************************************** * Name: sam_dumpep ****************************************************************************/ #ifdef CONFIG_SAMV7_USBHS_REGDEBUG static void sam_dumpep(struct sam_usbdev_s *priv, int epno) { /* Global Registers */ uinfo("Global Register:\n"); uinfo(" CTRL: %08x\n", sam_getreg(SAM_USBHS_DEVCTRL)); uinfo(" ISR: %08x\n", sam_getreg(SAM_USBHS_DEVISR)); uinfo(" IMR: %08x\n", sam_getreg(SAM_USBHS_DEVIMR)); uinfo(" EPT: %08x\n", sam_getreg(SAM_USBHS_DEVEPT)); uinfo(" FNUM: %08x\n", sam_getreg(SAM_USBHS_DEVFNUM)); /* Endpoint registers */ uinfo("Endpoint %d Register:\n", epno); uinfo(" CFG: %08x\n", sam_getreg(SAM_USBHS_DEVEPTCFG(epno))); uinfo(" ISR: %08x\n", sam_getreg(SAM_USBHS_DEVEPTISR(epno))); uinfo(" IMR: %08x\n", sam_getreg(SAM_USBHS_DEVEPTIMR(epno))); uinfo("DMA %d Register:\n", epno); if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0) { uinfo(" NXTDSC: %08x\n", sam_getreg(SAM_USBHS_DEVDMANXTDSC(epno))); uinfo(" ADDRESS: %08x\n", sam_getreg(SAM_USBHS_DEVDMAADDR(epno))); uinfo(" CONTROL: %08x\n", sam_getreg(SAM_USBHS_DEVDMACTRL(epno))); uinfo(" STATUS: %08x\n", sam_getreg(SAM_USBHS_DEVDMASTA(epno))); } else { uinfo(" None\n"); } } #endif /**************************************************************************** * DMA ****************************************************************************/ /**************************************************************************** * Name: sam_dtd_alloc * * Description: * Allocate a DMA transfer descriptor by removing it from the free list * * Assumption: Caller holds the lock * ****************************************************************************/ #ifdef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER static struct sam_dtd_s *sam_dtd_alloc(struct sam_usbdev_s *priv) { struct sam_dtd_s *dtd; /* Remove the DMA transfer descriptor from the freelist */ dtd = (struct sam_dtd_s *)g_usbhs.dtdfree; if (dtd) { g_usbhs.dtdfree = ((struct sam_list_s *)dtd)->flink; memset(dtd, 0, sizeof(struct sam_dtd_s)); } return dtd; } #endif /**************************************************************************** * Name: sam_dtd_free * * Description: * Free a DMA transfer descriptor by returning it to the free list * * Assumption: Caller holds the lock * ****************************************************************************/ #ifdef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER static void sam_dtd_free(struct sam_usbdev_s *priv, struct sam_dtd_s *dtd) { struct sam_list_s *entry = (struct sam_list_s *)dtd; /* Put the dtd structure back into the free list */ entry->flink = g_usbhs.dtdfree; g_usbhs.dtdfree = entry; } #endif /**************************************************************************** * Name: sam_dma_single * * Description: * Setup a start a single buffer DMA. * * Assumption: Called as part of USBHS interrupt handling * ****************************************************************************/ static void sam_dma_single(uint8_t epno, struct sam_req_s *privreq, uint32_t dmacontrol) { uintptr_t buffer; /* Not all endpoints support DMA */ DEBUGASSERT((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0); /* Flush the contents of the DMA buffer to RAM */ buffer = (uintptr_t)&privreq->req.buf[privreq->req.xfrd]; up_clean_dcache(buffer, buffer + privreq->inflight); /* Set up the DMA */ sam_putreg((uint32_t)buffer, SAM_USBHS_DEVDMAADDR(epno)); /* Clear any pending interrupts then enable the DMA interrupt */ sam_getreg(SAM_USBHS_DEVDMASTA(epno)); sam_putreg(USBHS_DEVINT_DMA(epno), SAM_USBHS_DEVIER); /* Setup and enable the DMA */ sam_putreg(0, SAM_USBHS_DEVDMACTRL(epno)); dmacontrol |= USBHS_DEVDMACTRL_BUFLEN(privreq->inflight); sam_putreg(dmacontrol, SAM_USBHS_DEVDMACTRL(epno)); } /**************************************************************************** * Name: sam_dma_wrsetup * * Description: * Process the next queued write request for an endpoint that supports DMA. * ****************************************************************************/ static void sam_dma_wrsetup(struct sam_usbdev_s *priv, struct sam_ep_s *privep, struct sam_req_s *privreq) { int remaining; int epno; /* Switch to the sending state */ privep->epstate = USBHS_EPSTATE_SENDING_DMA; privreq->inflight = 0; /* Get the endpoint number */ epno = USB_EPNO(privep->ep.eplog); /* How many bytes remain to be transferred in the request? */ remaining = (int)privreq->req.len - (int)privreq->req.xfrd; DEBUGASSERT(remaining >= 0 && remaining <= (int)privreq->req.len); /* If there are no bytes to send, then send a zero length packet */ if (remaining > 0) { /* Clip the transfer to the size of the DMA FIFO */ #if USBDEV_MAXREQUEUST > DMA_MAX_FIFO_SIZE if (remaining > DMA_MAX_FIFO_SIZE) { privreq->inflight = DMA_MAX_FIFO_SIZE; privep->zlpneeded = false; } else #endif { privreq->inflight = remaining; /* If the size is an exact multiple of full packets, then note if * we need to send a zero length packet next. */ privep->zlpneeded = ((privreq->req.flags & USBDEV_REQFLAGS_NULLPKT) != 0 && (remaining % privep->ep.maxpacket) == 0); } /* And perform the single DMA transfer. * * 32.6.10.6 Bulk IN or Interrupt IN: Sending a Buffer Using DMA * - END_B_EN: The endpoint can validate the packet (according to the * values programmed in the AUTO_VALID and SHRT_PCKT fields of * USBHS_EPTCTLx.) ... * - END_BUFFIT: generate an interrupt when the BUFF_COUNT in * USBHS_DMASTATUSx reaches 0. * - CHANN_ENB: Run and stop at end of buffer */ sam_dma_single(epno, privreq, USBHS_DEVDMACTRL_ENDBEN | USBHS_DEVDMACTRL_ENDBUFFIT | USBHS_DEVDMACTRL_CHANNENB); } /* Enable the endpoint interrupt */ sam_putreg(USBHS_DEVINT_PEP(epno), SAM_USBHS_DEVIER); } /**************************************************************************** * Name: sam_dma_rdsetup * * Description: * Process the next queued read request for an endpoint that supports DMA. * ****************************************************************************/ static void sam_dma_rdsetup(struct sam_usbdev_s *priv, struct sam_ep_s *privep, struct sam_req_s *privreq) { uint32_t regval; int remaining; int epno; /* Get the endpoint number */ epno = USB_EPNO(privep->ep.eplog); /* How many more bytes can we append to the request buffer? */ remaining = (int)privreq->req.len - (int)privreq->req.xfrd; DEBUGASSERT(remaining > 0 && remaining <= (int)privreq->req.len && privep->epstate == USBHS_EPSTATE_RECEIVING); /* Clip the DMA transfer size to the size available in the user buffer */ #if USBDEV_MAXREQUEUST > DMA_MAX_FIFO_SIZE if (remaining > DMA_MAX_FIFO_SIZE) { privreq->inflight = DMA_MAX_FIFO_SIZE; } else #endif { privreq->inflight = remaining; } /* And perform the single DMA transfer. * * 32.6.10.12 Bulk OUT or Interrupt OUT: Sending a Buffer Using DMA * - END_B_EN: Can be used for OUT packet truncation (discarding of * unbuffered packet data) at the end of DMA buffer. * - END_BUFFIT: Generate an interrupt when BUFF_COUNT in the * USBHS_DMASTATUSx register reaches 0. * - END_TR_EN: End of transfer enable, the USBHS device can put an * end to the current DMA transfer, in case of a short packet. * - END_TR_IT: End of transfer interrupt enable, an interrupt is sent * after the last USB packet has been transferred by the DMA, if the * USB transfer ended with a short packet. (Beneficial when the * receive size is unknown.) * - CHANN_ENB: Run and stop at end of buffer. */ regval = USBHS_DEVDMACTRL_ENDBEN | USBHS_DEVDMACTRL_ENDBUFFIT | USBHS_DEVDMACTRL_ENDTREN | USBHS_DEVDMACTRL_ENDTRIT | USBHS_DEVDMACTRL_CHANNENB; sam_dma_single(epno, privreq, regval); } /**************************************************************************** * * Request Helpers * ****************************************************************************/ /**************************************************************************** * * Name: sam_req_dequeue * ****************************************************************************/ static struct sam_req_s *sam_req_dequeue(struct sam_rqhead_s *queue) { struct sam_req_s *ret = queue->head; if (ret) { queue->head = ret->flink; if (!queue->head) { queue->tail = NULL; } ret->flink = NULL; } return ret; } /**************************************************************************** * * Name: sam_req_enqueue * ****************************************************************************/ static void sam_req_enqueue(struct sam_rqhead_s *queue, struct sam_req_s *req) { req->flink = NULL; if (!queue->head) { queue->head = req; queue->tail = req; } else { queue->tail->flink = req; queue->tail = req; } } /**************************************************************************** * * Name: sam_req_abort * ****************************************************************************/ static inline void sam_req_abort(struct sam_ep_s *privep, struct sam_req_s *privreq, int16_t result) { usbtrace(TRACE_DEVERROR(SAM_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: sam_req_complete * ****************************************************************************/ static void sam_req_complete(struct sam_ep_s *privep, int16_t result) { struct sam_req_s *privreq; irqstate_t flags; /* Remove the completed request at the head of the endpoint request list */ flags = enter_critical_section(); privreq = sam_req_dequeue(&privep->reqq); leave_critical_section(flags); if (privreq) { /* Save the result in the request structure */ privreq->req.result = result; /* Reset the endpoint state and restore the stalled indication. * * At least the USB class CDC/ACM calls the function * sam_ep_submit within the callback. * This function uses sam_req_write or * sam_req_read to process the request, * both functions can change the state. * Therefore it is very important to set * the state to USBHS_EPSTATE_IDLE * before the callback is called. */ privep->epstate = USBHS_EPSTATE_IDLE; privep->zlpneeded = false; privep->zlpsent = false; /* Callback to the request completion handler */ privreq->flink = NULL; privreq->req.callback(&privep->ep, &privreq->req); } } /**************************************************************************** * * Name: sam_ep_fifocon * * Description: * IN data has been loaded in the endpoint FIFO. Manage the endpoint to * (1) initiate sending of the data and (2) receive the TXIN interrupt * when the transfer completes. * ****************************************************************************/ static void sam_ep_fifocon(unsigned int epno) { /* Clear FIFOCON to indicate that the packet is ready to send (this works * even for zero length packets). We will get an TXIN interrupt with * FIFCON=1 when the transfer completes. Then we are able to send the * next packet. */ sam_putreg(USBHS_DEVEPTINT_FIFOCONI, SAM_USBHS_DEVEPTIDR(epno)); /* Clear the NAK IN bit to stop NAKing IN tokens from the host. We now * have data ready to go. * * REVISIT: I don't think the USBHS_DEVEPTINT_NAKINI is necessary. */ sam_putreg(USBHS_DEVEPTINT_NAKINI, SAM_USBHS_DEVEPTICR(epno)); /* Enable the TXIN interrupt on the endpoint */ sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTIER(epno)); } /**************************************************************************** * * Name: sam_req_wrsetup * * Description: * Process the next queued write request for an endpoint that does not * support DMA. * ****************************************************************************/ static void sam_req_wrsetup(struct sam_usbdev_s *priv, struct sam_ep_s *privep, struct sam_req_s *privreq) { uint32_t regval; const uint8_t *buf; uint8_t epno; unsigned int eptype; int nbytes; /* Get the unadorned endpoint number */ epno = USB_EPNO(privep->ep.eplog); /* Get the number of bytes remaining to be sent. */ DEBUGASSERT(privreq->req.xfrd < privreq->req.len); nbytes = privreq->req.len - privreq->req.xfrd; /* Either send the maxpacketsize or all of the remaining data in * the request. */ privep->zlpneeded = false; if (nbytes > privep->ep.maxpacket) { nbytes = privep->ep.maxpacket; } else if (nbytes == privep->ep.maxpacket) { /* If the size is exactly a full packet, then note if we need to * send a zero length packet next. */ privep->zlpneeded = ((privreq->req.flags & USBDEV_REQFLAGS_NULLPKT) != 0); } /* This is the new number of bytes "in-flight" */ privreq->inflight = nbytes; usbtrace(TRACE_WRITE(USB_EPNO(privep->ep.eplog)), nbytes); /* The new buffer pointer is the started of the buffer plus the number * of bytes successfully transferred plus the number of bytes previously * "in-flight". */ buf = privreq->req.buf + privreq->req.xfrd; /* How we send packets differs for control endpoints */ regval = sam_getreg(SAM_USBHS_DEVEPTCFG(epno)); eptype = regval & USBHS_DEVEPTCFG_EPTYPE_MASK; if (eptype == USBHS_DEVEPTCFG_EPTYPE_CTRL) { sam_ctrlep_write(privep, buf, nbytes); } else { sam_ep_write(privep, buf, nbytes); } } /**************************************************************************** * * Name: sam_req_write * * Description: * Process the next queued write request. This function is called in one * of three contexts: * (1) When the endpoint is IDLE and a new write request * is submitted (with interrupts disabled), * (2) from interrupt handling when the current * transfer completes (either DMA or FIFO), or * (3) when resuming a stalled IN or control endpoint. * * Calling rules: * * The transfer state must IDLE * * When a request is queued, the request 'len' is the number of bytes * to transfer and 'xfrd' and 'inflight' must be zero. * * When this function starts a transfer it will update the request * 'inflight' field to indicate the size of the transfer. * * When the transfer completes, the 'inflight' field must hold the * number of bytes that have completed the transfer. This function will * update 'xfrd' with the new size of the transfer. * ****************************************************************************/ static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep) { struct sam_req_s *privreq; uint32_t regval; uint32_t eptype; uint8_t epno; int bytesleft; /* Get the unadorned endpoint number */ epno = USB_EPNO(privep->ep.eplog); /* We get here when an IN endpoint interrupt occurs. So now we know that * there is no TX transfer in progress. */ while (privep->epstate == USBHS_EPSTATE_IDLE) { /* Check the request from the head of the endpoint request queue */ privreq = sam_rqpeek(&privep->reqq); if (!privreq) { /* There is no TX transfer in progress and no new pending TX * requests to send. */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPINQEMPTY), 0); /* Get the endpoint type */ regval = sam_getreg(SAM_USBHS_DEVEPTCFG(epno)); eptype = regval & USBHS_DEVEPTCFG_EPTYPE_MASK; /* Disable interrupts on non-control endpoints */ if (eptype != USBHS_DEVEPTCFG_EPTYPE_CTRL) { sam_putreg(USBHS_DEVINT_PEP(epno), SAM_USBHS_DEVIDR); } /* Clear and Disable the TXIN interrupt */ sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTICR(epno)); sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTIDR(epno)); return -ENOENT; } uinfo("epno=%d req=%p: len=%zu xfrd=%zu inflight=%d zlpneeded=%d\n", epno, privreq, privreq->req.len, privreq->req.xfrd, privreq->inflight, privep->zlpneeded); /* Handle any bytes in flight. */ privreq->req.xfrd += privreq->inflight; privreq->inflight = 0; /* Get the number of bytes left to be sent in the packet */ bytesleft = privreq->req.len - privreq->req.xfrd; if (bytesleft > 0) { /* The way that we handle the transfer is going to depend on * whether or not this endpoint supports DMA. In either case * the endpoint state will transition to SENDING. */ if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0) { sam_dma_wrsetup(priv, privep, privreq); } else { sam_req_wrsetup(priv, privep, privreq); } } /* No data to send... This can happen on one of two ways: * (1) The last packet sent was the final packet of a transfer. * If it was also exactly maxpacketsize and the protocol expects * a zero length packet to follow then privep->zlpneeded will be * set. Or (2) we called with a request packet that has * len == 0 (privep->zlpneeded will not be set). Either case * means that it is time to send a zero length packet and complete * this transfer. */ else if ((privreq->req.len == 0 || privep->zlpneeded) & !privep->zlpsent) { /* If we get here, then we sent the last of the data on the * previous pass and we need to send the zero length packet now. * * A Zero Length Packet can be sent by clearing just the FIFOCON * flag in the USBHS_DEVTEPTIDRx register */ privep->epstate = USBHS_EPSTATE_SENDING; privep->zlpneeded = false; privep->zlpsent = true; privreq->inflight = 0; /* Initiate the zero length transfer and configure to receive the * transfer complete interrupt. */ sam_ep_fifocon(epno); } /* If all of the bytes were sent (including any final zero length * packet) then we are finished with the request buffer and we can * return the request buffer to the class driver. The state will * remain IDLE only if nothing else was put in flight. * * Note that we will then loop to check to check the next queued * write request. */ if (privep->epstate == USBHS_EPSTATE_IDLE) { /* Return the write request to the class driver */ usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)), privreq->req.xfrd); DEBUGASSERT(privreq->req.len == privreq->req.xfrd); sam_req_complete(privep, OK); } } return OK; } /**************************************************************************** * * Name: sam_req_rddone * * Description: * The last non-DMA OUT transfer has completed. Read 'recvsize' byts from * the FIFO into the read request buffer. * ****************************************************************************/ static void sam_req_rddone(struct sam_usbdev_s *priv, struct sam_ep_s *privep, struct sam_req_s *privreq, uint16_t recvsize) { const uint8_t *fifo; uint8_t *dest; int remaining; int readlen; int epno; /* Get the number of bytes that can be received. This is the size of the * user-provided request buffer, minus the number of bytes already * transferred to the user-buffer. */ remaining = privreq->req.len - privreq->req.xfrd; /* Read the smaller of the number of bytes available in FIFO and the * size remaining in the request buffer provided by the caller. */ readlen = MIN(remaining, recvsize); privreq->req.xfrd += readlen; /* Get the source and destination transfer addresses */ epno = USB_EPNO(privep->ep.eplog); fifo = (const uint8_t *) ((uint32_t *)SAM_USBHSRAM_BASE + (EPT_FIFO_SIZE * epno)); dest = privreq->req.buf + privreq->req.xfrd; /* Retrieve packet from the FIFO */ for (; readlen > 0; readlen--) { *dest++ = *fifo++; } UP_MB(); } /**************************************************************************** * * Name: sam_req_rdenable * * Description: * Make sure that the endpoint RXRDY_TXTK interrupt is enabled in order * to receive the next incoming packet * ****************************************************************************/ static void sam_req_rdenable(uint8_t epno) { sam_putreg(USBHS_DEVINT_PEP(epno), SAM_USBHS_DEVIER); sam_putreg(USBHS_DEVEPTINT_RXOUTI, SAM_USBHS_DEVEPTIER(epno)); } /**************************************************************************** * * Name: sam_req_rddisable * * Description: * Disable endpoint interrupts * ****************************************************************************/ static void sam_req_rddisable(uint8_t epno) { sam_putreg(USBHS_DEVINT_PEP(epno), SAM_USBHS_DEVIDR); sam_putreg(USBHS_DEVEPTINT_RXOUTI, SAM_USBHS_DEVEPTIDR(epno)); } /**************************************************************************** * * Name: sam_req_read * * Description: * Complete the last read request, return the read request to the class * implementation, and try to started the next queued read request. * * This function is called in one of three contexts: (1) When the endpoint * is IDLE and a new read request is submitted (with interrupts disabled), * (2) from interrupt handling when the current transfer completes (either * DMA or FIFO), or (3) when resuming a stalled OUT or control endpoint. * * There is a fundamental difference between receiving packets via DMA and * via the FIFO: * * - When receiving data via DMA, then data has already been transferred * and this function is called on the terminating event. The transfer * is complete and we just need to check for end of request events and * if we need to setup the transfer for the next request. * - When receiving via the FIFO, the transfer is not complete. The * data is in the FIFO and must be transferred from the FIFO to the * request buffer. No setup is needed for the next transfer other than * assuring that the endpoint RXRDY_TXTK interrupt is enabled. * * Calling rules: * * The transfer state must IDLE * * When a request is queued, the request 'len' is size of the request * buffer. Any OUT request can be received that will fit in this * buffer. 'xfrd' and 'inflight' in the request must be zero * If sam_req_read() is called to start a new transfer, the recvsize * parameter must be zero. * * When this function starts a DMA transfer it will update the request * 'inflight' field to hold the maximum size of the transfer; but * 'inflight' is not used with FIFO transfers. * * When the transfer completes, the 'recvsize' parameter must be the * size of the transfer that just completed. For the case of DMA, * that is the size of the DMA transfer that has just been written to * memory; for the FIFO transfer, recvsize is the number of bytes * waiting in the FIFO to be read. * ****************************************************************************/ static int sam_req_read(struct sam_usbdev_s *priv, struct sam_ep_s *privep, uint16_t recvsize) { struct sam_req_s *privreq; uint32_t regval; uint32_t eptype; uint8_t epno; DEBUGASSERT(priv && privep && privep->epstate == USBHS_EPSTATE_IDLE); /* Loop in case we need to handle multiple read requests */ while (privep->epstate == USBHS_EPSTATE_IDLE) { /* Check the request from the head of the endpoint request queue */ epno = USB_EPNO(privep->ep.eplog); privreq = sam_rqpeek(&privep->reqq); if (!privreq) { /* No packet to receive data */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPOUTQEMPTY), epno); return -ENOENT; } uinfo("EP%d: len=%zu xfrd=%zu\n", epno, privreq->req.len, privreq->req.xfrd); /* Ignore any attempt to receive a zero length packet */ if (privreq->req.len == 0) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPOUTNULLPACKET), 0); sam_req_complete(privep, OK); recvsize = 0; continue; } usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), recvsize); /* Update the number of bytes transferred with the received size */ privreq->req.xfrd += recvsize; privreq->inflight = 0; /* If this was not a DMA transfer, * read the incoming data from the FIFO */ if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) == 0) { sam_req_rddone(priv, privep, privreq, recvsize); } /* In case we go through the loop again */ recvsize = 0; /* If nothing has yet be transferred into the read request, then * indicate that we are in the RECEIVING state and, if the endpoint * supports DMA, setup the receive DMA. */ if (privreq->req.xfrd == 0) { /* Set the RECEIVING state */ privep->epstate = USBHS_EPSTATE_RECEIVING; /* If the endpoint supports DMA, set up the DMA now */ if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0) { /* Set up the next DMA. We will come through this logic path * again with xrfd != 0 when the DMA completes. */ sam_dma_rdsetup(priv, privep, privreq); } /* No DMA for this endpoint and we have an available, empty read * request. We need to wait for data to become available. */ else { /* Enable endpoint RXRDY_TXTK interrupts */ sam_req_rdenable(epno); } } /* We will not try to accumulate packet data here. If anything * has been received, we will complete the transfer immediately and * give the data to the class driver. The idea is that we will let the * receiving be in-charge if incoming buffer. */ else { /* Return the read request to the class driver. */ usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd); sam_putreg(USBHS_DEVEPTINT_RXOUTI, SAM_USBHS_DEVEPTIDR(epno)); /* Get the endpoint type */ regval = sam_getreg(SAM_USBHS_DEVEPTCFG(epno)); eptype = regval & USBHS_DEVEPTCFG_EPTYPE_MASK; /* Disable endpoint interrupts if not the control endpoint */ if (eptype != USBHS_DEVEPTCFG_EPTYPE_CTRL) { sam_req_rddisable(epno); } /* And complete the request */ privep->epstate = USBHS_EPSTATE_IDLE; sam_req_complete(privep, OK); } } return OK; } /**************************************************************************** * * Name: sam_req_cancel * ****************************************************************************/ static void sam_req_cancel(struct sam_ep_s *privep, int16_t result) { uint32_t regval; uint8_t epno; /* Disable endpoint interrupts if not endpoint 0 */ epno = USB_EPNO(privep->ep.eplog); if (epno != 0) { regval = USBHS_DEVINT_DMA(epno) | USBHS_DEVINT_PEP(epno); sam_putreg(regval, SAM_USBHS_DEVIDR); } /* Then complete every queued request with the specified status */ while (!sam_rqempty(&privep->reqq)) { usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)), (sam_rqpeek(&privep->reqq))->req.xfrd); sam_req_complete(privep, result); } } /**************************************************************************** * * Interrupt Level Processing * ****************************************************************************/ /**************************************************************************** * * Name: sam_ep0_read * * Description: * Read a general USB request from the USBHS FIFO * ****************************************************************************/ static void sam_ep0_read(uint8_t *buffer, size_t buflen) { volatile const uint8_t *fifo; /* Retrieve packet from the FIFO */ fifo = (volatile const uint8_t *)SAM_USBHSRAM_BASE; for (; buflen > 0; buflen--) { *buffer++ = *fifo++; } UP_MB(); } /**************************************************************************** * * Name: sam_ctrlep_write * * Description: * Process the next queued write request for a control endpoint. * ****************************************************************************/ static void sam_ctrlep_write(struct sam_ep_s *privep, const uint8_t *buffer, size_t buflen) { volatile uint8_t *fifo; unsigned int epno; /* Get the endpoint number */ epno = USB_EPNO(privep->ep.eplog); /* Write packet in the FIFO buffer */ fifo = (uint8_t *) ((uint32_t *)SAM_USBHSRAM_BASE + (EPT_FIFO_SIZE * epno)); for (; buflen > 0; buflen--) { *fifo++ = *buffer++; } UP_MB(); /* Indicate that there is data in the TX packet memory. This will * be cleared when the next NAKIN interrupt is received. */ privep->epstate = USBHS_EPSTATE_SENDING; /* The FIFO Control (USBHS_DEVEPTIMRx.FIFOCON) bit and the Read/Write * Allowed (USBHS_DEVEPTISRx.RWALL) bit are irrelevant for control * endpoints. The user never uses them on these endpoints. */ /* USBHS_DEVEPTISRx.TXINI is cleared by software (by writing a one to * the Transmitted IN Data Interrupt Clear bit (USBHS_DEVEPTIDRx.TXINIC) * to acknowledge the interrupt, which has no effect on the endpoint * FIFO. This acknowledges the interrupt and sends the packet. */ sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTIDR(epno)); /* Clear the NAKIN bit to stop NAKing IN tokens from the host. We now * have data ready to go. */ sam_putreg((USBHS_DEVEPTINT_NAKINI | USBHS_DEVEPTINT_TXINI), SAM_USBHS_DEVEPTICR(epno)); /* Enable the TXIN interrupt on the endpoint */ sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTIER(epno)); } /**************************************************************************** * * Name: sam_ep_write * * Description: * Process the next queued write request for a control endpoint. * ****************************************************************************/ static void sam_ep_write(struct sam_ep_s *privep, const uint8_t *buffer, size_t buflen) { volatile uint8_t *fifo; unsigned int epno; /* Get the endpoint number */ epno = USB_EPNO(privep->ep.eplog); /* Write packet in the FIFO buffer */ fifo = (uint8_t *) ((uint32_t *)SAM_USBHSRAM_BASE + (EPT_FIFO_SIZE * epno)); for (; buflen; buflen--) { *fifo++ = *buffer++; } UP_MB(); /* Indicate that there is data in the TX packet memory. This will * be cleared when the next data out interrupt is received. */ privep->epstate = USBHS_EPSTATE_SENDING; /* Initiate the transfer and configure to receive the transfer complete * interrupt. */ sam_ep_fifocon(epno); } /**************************************************************************** * * Name: sam_ep0_dispatch * ****************************************************************************/ static void sam_ep0_dispatch(struct sam_usbdev_s *priv) { uint8_t *dataout; size_t outlen; int ret; usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DISPATCH), 0); if (priv && priv->driver) { /* Assume IN SETUP (or OUT SETUP with no data) */ dataout = NULL; outlen = 0; /* Was this an OUT SETUP command? */ if (USB_REQ_ISOUT(priv->ctrl.type)) { uint16_t tmplen = GETUINT16(priv->ctrl.len); if (tmplen > 0) { dataout = priv->ep0out; outlen = tmplen; } } /* Forward to the control request to the class driver implementation */ ret = CLASS_SETUP(priv->driver, &priv->usbdev, &priv->ctrl, dataout, outlen); if (ret < 0) { /* Stall on failure */ usbtrace(TRACE_DEVERROR(SAM_TRACEERR_DISPATCHSTALL), 0); sam_ep_stall(&priv->eplist[EP0].ep, false); } } } /**************************************************************************** * * Name: sam_ep0_setup * ****************************************************************************/ static void sam_ep0_setup(struct sam_usbdev_s *priv) { struct sam_ep_s *ep0 = &priv->eplist[EP0]; struct sam_ep_s *privep; union wb_u value; union wb_u index; union wb_u len; union wb_u response; enum sam_ep0setup_e ep0result; uint32_t regval; uint8_t epno; int nbytes = 0; /* Assume zero-length packet */ int ret; /* We want to pass pointer to response.b to sam_ctrlep_write function. * Memset the union to avoid compilation warnings of uninitialized * variable. */ memset(&response, 0, sizeof(union wb_u)); /* Terminate any pending requests */ sam_req_cancel(ep0, -EPROTO); /* Assume NOT stalled; no TX in progress */ ep0->stalled = 0; ep0->epstate = USBHS_EPSTATE_IDLE; /* 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); uinfo("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(SAM_TRACEINTID_NOSTDREQ), priv->ctrl.type); /* Let the class implementation handle all non-standar requests */ sam_ep0_dispatch(priv); return; } /* Handle standard request. Pick off the things of interest to the * USB device controller driver; pass what is left to the class driver */ ep0result = USBHS_EP0SETUP_SUCCESS; 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(SAM_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(SAM_TRACEERR_BADEPGETSTATUS), 0); ep0result = USBHS_EP0SETUP_STALL; } else { switch (priv->ctrl.type & USB_REQ_RECIPIENT_MASK) { case USB_REQ_RECIPIENT_ENDPOINT: { epno = USB_EPNO(index.b[LSB]); usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPGETSTATUS), epno); if (epno >= SAM_USBHS_NENDPOINTS) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEPGETSTATUS), epno); ep0result = USBHS_EP0SETUP_STALL; } else { privep = &priv->eplist[epno]; response.w = 0; /* Not stalled */ nbytes = 2; /* Response size: 2 bytes */ if (privep->stalled) { /* Endpoint stalled */ response.b[LSB] = 1; /* Stalled */ } } } break; case USB_REQ_RECIPIENT_DEVICE: { if (index.w == 0) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DEVGETSTATUS), 0); /* Features: Remote Wakeup=YES; selfpowered=? */ response.w = 0; response.b[LSB] = (priv->selfpowered << USB_FEATURE_SELFPOWERED) | (1 << USB_FEATURE_REMOTEWAKEUP); nbytes = 2; /* Response size: 2 bytes */ } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADDEVGETSTATUS), 0); ep0result = USBHS_EP0SETUP_STALL; } } break; case USB_REQ_RECIPIENT_INTERFACE: { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_IFGETSTATUS), 0); response.w = 0; nbytes = 2; /* Response size: 2 bytes */ } break; default: { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADGETSTATUS), 0); ep0result = USBHS_EP0SETUP_STALL; } break; } } } break; case USB_REQ_CLEARFEATURE: { /* type: host-to-device; recipient = device, interface or endpoint * value: feature selector * index: zero interface endpoint; * len: zero, data = none */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_CLEARFEATURE), priv->ctrl.type); if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) != USB_REQ_RECIPIENT_ENDPOINT) { /* Let the class implementation handle all * recipients (except for the endpoint recipient) */ sam_ep0_dispatch(priv); ep0result = USBHS_EP0SETUP_DISPATCHED; } else { /* Endpoint recipient */ epno = USB_EPNO(index.b[LSB]); if (epno < SAM_USBHS_NENDPOINTS && index.b[MSB] == 0 && value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0) { privep = &priv->eplist[epno]; privep->halted = 0; ret = sam_ep_stall(&privep->ep, true); if (ret < 0) { ep0result = USBHS_EP0SETUP_STALL; } } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADCLEARFEATURE), 0); ep0result = USBHS_EP0SETUP_STALL; } } } 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(SAM_TRACEINTID_SETFEATURE), priv->ctrl.type); if (((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_DEVICE) && value.w == USB_FEATURE_TESTMODE) { /* Special case recipient=device test mode */ uinfo("test mode: %d\n", index.w); } else if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) != USB_REQ_RECIPIENT_ENDPOINT) { /* The class driver handles all * recipients except recipient=endpoint */ sam_ep0_dispatch(priv); ep0result = USBHS_EP0SETUP_DISPATCHED; } else { /* Handler recipient=endpoint */ epno = USB_EPNO(index.b[LSB]); if (epno < SAM_USBHS_NENDPOINTS && index.b[MSB] == 0 && value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0) { privep = &priv->eplist[epno]; privep->halted = 1; ret = sam_ep_stall(&privep->ep, false); if (ret < 0) { ep0result = USBHS_EP0SETUP_STALL; } } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADSETFEATURE), 0); ep0result = USBHS_EP0SETUP_STALL; } } } break; case USB_REQ_SETADDRESS: { /* type: host-to-device; recipient = device * value: device address * index: 0 * len: 0; data = none */ if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) != USB_REQ_RECIPIENT_DEVICE || index.w != 0 || len.w != 0 || value.w > 127) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADSETADDRESS), 0); ep0result = USBHS_EP0SETUP_STALL; } else { /* Note that setting of the device address will be deferred. A * zero-length packet will be sent and the device address will * be set when the zero-length packet transfer completes. */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0SETUPSETADDRESS), value.w); /* Write this address to the USB Address * (USBHS_DEVCTRL.UADD) field * but do not yet enable (USBHS_DEVCTRL.ADDEN) * so the actual address is still 0. * * USBHS_DEVCTRL.UADD and USBHS_DEVCTRL.ADDEN * must not be written all at once. */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval &= ~(USBHS_DEVCTRL_UADD_MASK | USBHS_DEVCTRL_ADDEN); regval |= USBHS_DEVCTRL_UADD(value.w); sam_putreg(regval, SAM_USBHS_DEVCTRL); ep0result = USBHS_EP0SETUP_ADDRESS; } } 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(SAM_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 */ sam_ep0_dispatch(priv); ep0result = USBHS_EP0SETUP_DISPATCHED; } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADGETSETDESC), 0); ep0result = USBHS_EP0SETUP_STALL; } } break; case USB_REQ_GETCONFIGURATION: /* type: device-to-host; recipient = device * value: 0; * index: 0; * len: 1; data = configuration value */ { usbtrace(TRACE_INTDECODE(SAM_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 */ sam_ep0_dispatch(priv); ep0result = USBHS_EP0SETUP_DISPATCHED; } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADGETCONFIG), 0); ep0result = USBHS_EP0SETUP_STALL; } } break; case USB_REQ_SETCONFIGURATION: /* type: host-to-device; recipient = device * value: configuration value * index: 0; * len: 0; data = none */ { usbtrace(TRACE_INTDECODE(SAM_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. * If the class implementation accespts * it new configuration, it will * call sam_ep_configure() to configure the endpoints. */ sam_ep0_dispatch(priv); ep0result = USBHS_EP0SETUP_DISPATCHED; } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADSETCONFIG), 0); ep0result = USBHS_EP0SETUP_STALL; } } break; case USB_REQ_GETINTERFACE: /* type: device-to-host; recipient = interface * value: 0 * index: interface; * len: 1; data = alt interface */ case USB_REQ_SETINTERFACE: /* type: host-to-device; recipient = interface * value: alternate setting * index: interface; * len: 0; data = none */ { /* Let the class implementation handle the request */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_GETSETIF), priv->ctrl.type); sam_ep0_dispatch(priv); ep0result = USBHS_EP0SETUP_DISPATCHED; } break; case USB_REQ_SYNCHFRAME: /* type: device-to-host; recipient = endpoint * value: 0 * index: endpoint; * len: 2; data = frame number */ { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_SYNCHFRAME), 0); } break; default: { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDCTRLREQ), priv->ctrl.req); ep0result = USBHS_EP0SETUP_STALL; } break; } /* Restrict the data length to the length requested in the setup packet */ if (nbytes > len.w) { nbytes = len.w; } /* At this point, the request has been handled and there are three * (or four) possible outcomes: * * 1a. ep0result == USBHS_EP0SETUP_SUCCESS * * The setup request was successfully handled above and a response * packet must be sent (may be a zero length packet). * * 1b. ep0result == USBHS_EP0SETUP_ADDRESS * * A special case is the case where epstate=USBHS_EPSTATE_EP0ADDRESS. * This means that the above processing generated an additional state * where we need to wait until we complete the status phase before * applying the new device address. * * 2. ep0result == USBHS_EP0SETUP_DISPATCHED; * * The request was forwarded to the class implementation. In case, * EP0 IN data may have already been sent and the EP0 IN response * has already been queued? Or perhaps the endpoint has already * been stalled? This is all under the control of the class driver. * * NOTE that for the case of non-standard SETUP requested, those * requests were forwarded to the class driver and we don't even get * to this logic. * * 3. ep0result == USBHS_EP0SETUP_STALL; * * An error was detected in either the above logic or by the class * implementation logic. */ switch (ep0result) { case USBHS_EP0SETUP_SUCCESS: { /* Send the response (might be a zero-length packet) */ sam_ctrlep_write(ep0, response.b, nbytes); ep0->epstate = USBHS_EPSTATE_EP0STATUSIN; } break; case USBHS_EP0SETUP_ADDRESS: { /* Send the response (might be a zero-length packet) */ sam_ctrlep_write(ep0, response.b, nbytes); ep0->epstate = USBHS_EPSTATE_EP0ADDRESS; } break; case USBHS_EP0SETUP_STALL: { /* Stall EP0 */ usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EP0SETUPSTALLED), priv->ctrl.req); sam_ep_stall(&priv->eplist[EP0].ep, false); } break; case USBHS_EP0SETUP_DISPATCHED: default: break; } } /**************************************************************************** * * Name: sam_dma_interrupt * * Description: * Handle the USBHS DMA interrupt * ****************************************************************************/ #ifdef CONFIG_USBDEV_DMA static void sam_dma_interrupt(struct sam_usbdev_s *priv, int epno) { struct sam_ep_s *privep; struct sam_req_s *privreq; uintptr_t regaddr; uint32_t regval; uint32_t dmastatus; uint8_t *buf; int bufcnt; int xfrsize; /* Not all endpoints support DMA */ DEBUGASSERT((unsigned)epno < SAM_USBHS_NENDPOINTS && (SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0); /* Get the endpoint structure */ privep = &priv->eplist[epno]; /* Get the request from the head of the endpoint request queue */ privreq = sam_rqpeek(&privep->reqq); DEBUGASSERT(privreq); /* Disable DMA interrupt to avoid receiving 2 (B_EN and TR_EN) */ regaddr = SAM_USBHS_DEVDMACTRL(epno); regval = sam_getreg(regaddr); regval &= ~(USBHS_DEVDMACTRL_ENDTREN | USBHS_DEVDMACTRL_ENDBEN); sam_putreg(regval, regaddr); /* Get the result of the DMA operation */ dmastatus = sam_getreg(SAM_USBHS_DEVDMASTA(epno)); usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPDMAINT), dmastatus); /* Disable DMA interrupt to avoid receiving 2 (B_EN and TR_EN) */ regaddr = SAM_USBHS_DEVDMACTRL(epno); regval = sam_getreg(regaddr); regval &= ~(USBHS_DEVDMACTRL_ENDTREN | USBHS_DEVDMACTRL_ENDBEN); sam_putreg(regval, regaddr); /* Check for end of the buffer. Set by hardware when the BUFF_COUNT * downcount reaches zero. This could be either an IN or OUT transfer. */ if ((dmastatus & USBHS_DEVDMASTA_ENDBUFFST) != 0) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DMAEOB), (uint16_t)dmastatus); /* BUFF_COUNT holds the number of untransmitted bytes. BUFF_COUNT is * equal to zero in case of good transfer. BUFF_COUNT was set to * the 'inflight' count when the DMA started and the BUFF_COUNT has * now decremented to zero */ /* This is just debug logic that only does any if USB debug or tracing * are enabled. This just verifies that BUFF_COUNT is zero. */ bufcnt = (dmastatus & USBHS_DEVDMASTA_BUFCNT_MASK) >> USBHS_DEVDMASTA_BUFCNT_SHIFT; if (bufcnt != 0) { usbtrace(TRACE_DEVERROR(SAM_TRACERR_REMAINING), bufcnt); } /* Were we sending? Or receiving? */ if (privep->epstate == USBHS_EPSTATE_SENDING || privep->epstate == USBHS_EPSTATE_SENDING_DMA) { uint32_t nbusybk; uint32_t byct; /* This is an IN endpoint. Continuing processing the write * request. */ DEBUGASSERT(USB_ISEPIN(privep->ep.eplog)); /* Assume that will wait for the NBUSYBKS interrupt. Enable it * now. We do this PRIOR to sampling the BYCT and BUSBK * fields to avoid the race condition that would occur if * the interrupt were enabled afterward. */ sam_putreg(USBHS_DEVEPTINT_NBUSYBKI, SAM_USBHS_DEVEPTIER(epno)); /* Have all of the bytes in the FIFO been transmitted to the * host? * * BYCT == 0 Means that all of the data has been transferred * out of the FIFO. * Warning: This field may be updated one clock cycle * after the RWALL bit changes, so the user should not * poll this field as an interrupt bit. * NBUSYBK == 0 Indicates that all banks that have been sent to * the host. */ regval = sam_getreg(SAM_USBHS_DEVEPTISR(epno)); byct = (regval & USBHS_DEVEPTISR_BYCT_MASK) >> USBHS_DEVEPTISR_BYCT_SHIFT; nbusybk = (regval & USBHS_DEVEPTISR_NBUSYBK_MASK) >> USBHS_DEVEPTISR_NBUSYBK_SHIFT; if (byct > 0 || nbusybk > 0) { /* Not all of the data has been sent to the host. * A NBUSYBKE interrupt will be generated later. * It has already been enabled. * Now wait for the transfer to complete. */ privep->epstate = USBHS_EPSTATE_NBUSYBK; } else { /* All bytes have been sent to the host. We must call * sam_req_write() now in the IDLE state with the number of * bytes transferred in 'inflight'. There must not be a * pending TXIN interrupt when sam_req_write() is called. */ sam_putreg(USBHS_DEVEPTINT_NBUSYBKI, SAM_USBHS_DEVEPTIDR(epno)); sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTICR(epno)); privep->epstate = USBHS_EPSTATE_IDLE; sam_req_write(priv, privep); } } else if (privep->epstate == USBHS_EPSTATE_RECEIVING) { /* privreg->inflight holds the total transfer size */ xfrsize = privreq->inflight; privreq->inflight = 0; /* This is an OUT endpoint. Invalidate the data cache for * region that just completed DMA. This will force the * buffer data to be reloaded from RAM when it is accessed. * * REVISIT: If the buffer is not aligned to the cacheline * size the cached data might be lost at the boundaries. */ DEBUGASSERT(USB_ISEPOUT(privep->ep.eplog)); buf = &privreq->req.buf[privreq->req.xfrd]; up_invalidate_dcache((uintptr_t)buf, (uintptr_t)buf + xfrsize); /* Complete this transfer, return the request to the class * implementation, and try to start the next, queue read request. * We must call sam_req_read in the IDLE state, 'inflight' is * ignored (should be zero) and the transfer size is passed as * an argument to sam_req_read(). */ privep->epstate = USBHS_EPSTATE_IDLE; sam_req_read(priv, privep, xfrsize); } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEOBSTATE), bufcnt); } } /* Check for end of channel transfer. END_TR_ST is set by hardware when * the last packet transfer is complete if END_TR_EN is set in the * DMACONTROL register. The request is complete. * * "Used for OUT transfers only. * * "0 = USB end of transfer is ignored. * "1 = USBHS device can put an end to the current buffer transfer. * * "When set, a BULK or INTERRUPT short packet or the last packet of * an ISOCHRONOUS (micro) frame (DATAX) will close the current buffer * and the USBHS_DMASTATUSx register END_TR_ST flag will be raised." */ else if ((dmastatus & USBHS_DEVDMASTA_ENDTRST) != 0) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DMAEOC), (uint16_t)dmastatus); DEBUGASSERT(privep->epstate == USBHS_EPSTATE_RECEIVING && USB_ISEPOUT(privep->ep.eplog)); /* Get the number of bytes transferred from the DMA status. * * BUFF_COUNT holds the number of untransmitted bytes. In this case, * BUFF_COUNT should not be zero. BUFF_COUNT was set to the * 'inflight' count when the DMA started so the difference will * give us the actual size of the transfer. */ bufcnt = ((dmastatus & USBHS_DEVDMASTA_BUFCNT_MASK) >> USBHS_DEVDMASTA_BUFCNT_SHIFT); xfrsize = privreq->inflight - bufcnt; privreq->inflight = 0; /* Invalidate the data cache for region that just completed DMA. * This will force the buffer data to be reloaded from RAM. * * REVISIT: If the buffer is not aligned to the cacheline size the * cached data might be lost at the boundaries. */ buf = &privreq->req.buf[privreq->req.xfrd]; up_invalidate_dcache((uintptr_t)buf, (uintptr_t)buf + xfrsize); /* Complete this transfer, return the request to the class * implementation, and try to start the next, queue read request. */ privep->epstate = USBHS_EPSTATE_IDLE; sam_req_read(priv, privep, xfrsize); } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_DMAERR), (uint16_t)dmastatus); /* Return the request buffer to the class implementation with the I/O * error indication. */ sam_req_complete(privep, -EIO); } } #endif /**************************************************************************** * * Name: sam_ep_interrupt * * Description: * Handle the USBHS endpoint interrupt * ****************************************************************************/ static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno) { struct sam_ep_s *privep; uint32_t eptisr; uint32_t eptype; uint32_t regval; uint16_t pktsize; DEBUGASSERT((unsigned)epno < SAM_USBHS_NENDPOINTS); /* Get the endpoint structure */ privep = &priv->eplist[epno]; /* Get the endpoint status */ eptisr = sam_getreg(SAM_USBHS_DEVEPTISR(epno)); usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPINT), eptisr); /* Get the endpoint type */ regval = sam_getreg(SAM_USBHS_DEVEPTCFG(epno)); eptype = regval & USBHS_DEVEPTCFG_EPTYPE_MASK; #ifdef CONFIG_USBDEV_DMA /* Check for IN DMA packet sent and NBUSYBKS transitioning to zero. This * is the USBHS_DEVEPTINT_NBUSYBKI interrupt. There is no indication of * the pending USBHS_DEVEPTINT_NBUSYBKI in the status register. We depend * on (a) NBUSYBKS == 0, and (c) state == USBHS_EPSTATE_NBUSYBK. */ if ((eptisr & USBHS_DEVEPTISR_NBUSYBK_MASK) == 0 && privep->epstate == USBHS_EPSTATE_NBUSYBK) { /* Clear the pending NBUSYBKS (and TXIN) interrupts */ sam_putreg(USBHS_DEVEPTINT_TXINI | USBHS_DEVEPTINT_NBUSYBKI, SAM_USBHS_DEVEPTICR(epno)); /* Disable further NBUSYBKS interrupts */ sam_putreg(USBHS_DEVEPTINT_NBUSYBKI, SAM_USBHS_DEVEPTIDR(epno)); /* Continue/resume processing the write requests. */ privep->epstate = USBHS_EPSTATE_IDLE; sam_req_write(priv, privep); } else #endif /* IN packet sent */ if ((eptisr & USBHS_DEVEPTINT_TXINI) != 0 && (sam_getreg(SAM_USBHS_DEVEPTIMR(epno)) & USBHS_DEVEPTINT_TXINI) != 0) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_TXINI), (uint16_t)eptisr); /* Sending state. This is the completion of a "normal" write request * transfer. In this case, we need to resume request processing in * order to send the next outgoing packet. */ if (privep->epstate == USBHS_EPSTATE_SENDING || privep->epstate == USBHS_EPSTATE_EP0STATUSIN) { /* A control endpoint clears the TXIN bit after new data is written * to the fifo inside the function "sam_ctrlep_write" * (sam_req_write -> sam_req_wrsetup -> sam_ctrlep_write). * All other Endpoints needs to clear the bit here. * * REVISIT: normally all other endpoints also have to reset the bit * at a later point. * * Continue/resume processing the write requests. */ if (eptype != USBHS_DEVEPTCFG_EPTYPE_CTRL) { sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTICR(epno)); } privep->epstate = USBHS_EPSTATE_IDLE; sam_req_write(priv, privep); } /* Setting of the device address is a special case. The address was * obtained when a preceding SETADDRESS SETUP command was processed. * But the address is not set until the final SETUP status phase * completes. This interrupt indicates the completion of that status * phase and now we set the address. */ else if (privep->epstate == USBHS_EPSTATE_EP0ADDRESS) { DEBUGASSERT(epno == EP0); /* Enable the address previously set in the SETUP processing. * USBHS_DEVCTRL.UADD and USBHS_DEVCTRL.ADDEN must not be written * all at once. */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval |= USBHS_DEVCTRL_ADDEN; sam_putreg(regval, SAM_USBHS_DEVCTRL); usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_ADDRESSED), (regval & USBHS_DEVCTRL_UADD_MASK) << USBHS_DEVCTRL_UADD_SHIFT); /* Go to the addressed state. EP0 is now IDLE. */ priv->devstate = USBHS_DEVSTATE_ADDRESSED; privep->epstate = USBHS_EPSTATE_IDLE; /* Acknowledge then disable the further TXIN interrupts on EP0. */ sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTICR(epno)); sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTIDR(epno)); } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_TXINERR), privep->epstate); sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTICR(epno)); sam_putreg(USBHS_DEVEPTINT_TXINI, SAM_USBHS_DEVEPTIDR(epno)); } } /* OUT packet received */ if ((eptisr & USBHS_DEVEPTINT_RXOUTI) != 0) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_RXRDY), (uint16_t)eptisr); /* Are we receiving data for a read request? */ if (privep->epstate == USBHS_EPSTATE_RECEIVING) { /* Yes, get the size of the packet that we just received */ pktsize = (uint16_t) ((eptisr & USBHS_DEVEPTISR_BYCT_MASK) >> USBHS_DEVEPTISR_BYCT_SHIFT); /* And continue processing the read request, clearing RXOUT in * order to receive more data. */ privep->epstate = USBHS_EPSTATE_IDLE; sam_req_read(priv, privep, pktsize); sam_putreg(USBHS_DEVEPTINT_RXOUTI, SAM_USBHS_DEVEPTICR(epno)); } /* Did we just receive the data associated with an OUT SETUP command? */ else if (privep->epstate == USBHS_EPSTATE_EP0DATAOUT) { uint16_t len; /* Yes.. back to the IDLE state */ privep->epstate = USBHS_EPSTATE_IDLE; /* Get the size of the packet that we just received */ pktsize = (uint16_t) ((eptisr & USBHS_DEVEPTISR_BYCT_MASK) >> USBHS_DEVEPTISR_BYCT_SHIFT); /* Get the size that we expected to receive */ len = GETUINT16(priv->ctrl.len); if (len == pktsize) { /* Copy the OUT data from the EP0 FIFO * into a special EP0 buffer * and clear RXOUT in order to receive more data. */ sam_ep0_read(priv->ep0out, len); sam_putreg(USBHS_DEVEPTINT_RXOUTI, SAM_USBHS_DEVEPTICR(epno)); /* And handle the EP0 SETUP now. */ sam_ep0_setup(priv); } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EP0SETUPOUTSIZE), pktsize); /* STALL and discard received data. */ sam_ep_stall(&privep->ep, false); sam_putreg(USBHS_DEVEPTINT_STALLRQI, SAM_USBHS_DEVEPTICR(epno)); } } else { /* Check if ACK received on a Control EP */ if (eptype == USBHS_DEVEPTCFG_EPTYPE_CTRL && (eptisr & USBHS_DEVEPTISR_BYCT_MASK) == 0) { } /* Data has been STALLed */ else if ((eptisr & USBHS_DEVEPTINT_STALLEDI) != 0) { } /* NAK the data */ else { sam_putreg(USBHS_DEVINT_PEP(epno), SAM_USBHS_DEVIDR); } /* Acknowledge the interrupt. */ sam_putreg(USBHS_DEVEPTINT_RXOUTI, SAM_USBHS_DEVEPTICR(epno)); } } /* STALL sent */ if ((eptisr & USBHS_DEVEPTINT_STALLEDI) != 0) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_STALLSNT), (uint16_t)eptisr); /* Acknowledge */ sam_putreg(USBHS_DEVEPTINT_STALLEDI, SAM_USBHS_DEVEPTICR(epno)); /* ISO error */ if (eptype == USBHS_DEVEPTCFG_EPTYPE_ISO) { privep->epstate = USBHS_EPSTATE_IDLE; sam_req_complete(privep, -EIO); } /* If EP is not halted, clear STALL */ else if (privep->epstate != USBHS_EPSTATE_STALLED) { sam_putreg(USBHS_DEVEPTINT_STALLEDI, SAM_USBHS_DEVEPTIDR(epno)); } } /* SETUP packet received */ if ((eptisr & USBHS_DEVEPTINT_RXSTPI) != 0) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_RXSETUP), (uint16_t)eptisr); /* If a request transfer was pending, complete it. * Handle the case where during the status phase * of a control write transfer, the host receives * the device ZLP and ack it, but the ack is * not received by the device */ if (privep->epstate == USBHS_EPSTATE_RECEIVING || privep->epstate == USBHS_EPSTATE_SENDING || privep->epstate == USBHS_EPSTATE_SENDING_DMA) { sam_req_complete(privep, -EPROTO); } /* ISO Err Flow */ if (eptype == USBHS_DEVEPTCFG_EPTYPE_ISO) { /* Acknowledge setup packet */ sam_putreg(USBHS_DEVEPTINT_RXSTPI, SAM_USBHS_DEVEPTICR(epno)); } else { uint16_t len; /* Copy setup data from the EP0 FIFO into the driver structure. */ sam_ep0_read((uint8_t *)&priv->ctrl, USB_SIZEOF_CTRLREQ); /* Acknowledge setup packet */ sam_putreg(USBHS_DEVEPTINT_RXSTPI, SAM_USBHS_DEVEPTICR(epno)); /* Check for a SETUP IN transaction */ len = GETUINT16(priv->ctrl.len); if (USB_REQ_ISOUT(priv->ctrl.type) && len > 0) { /* Yes.. then we have to wait for the OUT data phase to * complete before processing the SETUP command. */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0SETUPOUT), priv->ctrl.req); privep->epstate = USBHS_EPSTATE_EP0DATAOUT; } else { /* This is an SETUP IN command (or a SETUP IN with no data). * Handle the EP0 SETUP now. */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0SETUPIN), len); privep->epstate = USBHS_EPSTATE_IDLE; sam_ep0_setup(priv); } } } } /**************************************************************************** * * Name: sam_usbhs_interrupt * * Description: * Handle the USBHS interrupt * ****************************************************************************/ static int sam_usbhs_interrupt(int irq, void *context, void *arg) { /* 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 * USBHS controllers easier. */ struct sam_usbdev_s *priv = &g_usbhs; uint32_t devisr; uint32_t pending; uint32_t regval; int i; /* Get the set of pending interrupts */ devisr = sam_getreg(SAM_USBHS_DEVISR); usbtrace(TRACE_INTENTRY(SAM_TRACEINTID_INTERRUPT), devisr); regval = sam_getreg(SAM_USBHS_DEVIMR); pending = devisr & regval; /* Handle all pending USBHS interrupts (and new interrupts that become * pending) */ while (pending) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_PENDING), (uint16_t)pending); /* Suspend, treated last */ if (pending == USBHS_DEVINT_SUSPD) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_INTSUSPD), (uint16_t)pending); /* Un-freeze the clock */ regval = sam_getreg(SAM_USBHS_CTRL); regval &= ~USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); /* Disable suspend interrupts */ sam_putreg(USBHS_DEVINT_SUSPD, SAM_USBHS_DEVIDR); /* Enable wakeup interrupts */ sam_putreg(USBHS_DEVINT_WAKEUP | USBHS_DEVINT_EORSM, SAM_USBHS_DEVIER); /* Acknowledge the suspend interrupt */ sam_putreg(USBHS_DEVINT_SUSPD | USBHS_DEVINT_WAKEUP, SAM_USBHS_DEVICR); /* Inform board logic that USB is suspended */ sam_suspend(priv); /* Re-freeze the clock */ regval |= USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); } /* SOF interrupt */ else if ((pending & USBHS_DEVINT_SOF) != 0) { /* Acknowledge interrupt */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_INTSOF), (uint16_t)pending); sam_putreg(USBHS_DEVINT_SOF, SAM_USBHS_DEVICR); } /* MSOF interrupt */ else if ((pending & USBHS_DEVINT_MSOF) != 0) { /* Acknowledge interrupt */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_INTMSOF), (uint16_t)pending); sam_putreg(USBHS_DEVINT_MSOF, SAM_USBHS_DEVICR); } /* Resume */ else if ((pending & USBHS_DEVINT_WAKEUP) != 0 || (pending & USBHS_DEVINT_EORSM) != 0) { usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_WAKEUP), (uint16_t)pending); sam_resume(priv); /* Un-freeze the clock */ regval = sam_getreg(SAM_USBHS_CTRL); regval &= ~USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); /* Acknowledge interrupt */ sam_putreg(USBHS_DEVINT_WAKEUP | USBHS_DEVINT_EORSM | USBHS_DEVINT_SUSPD, SAM_USBHS_DEVICR); /* Disable wakeup interrupts */ sam_putreg(USBHS_DEVINT_WAKEUP, SAM_USBHS_DEVIDR); /* Enable suspend interrupts and clear */ sam_putreg(USBHS_DEVINT_EORSM | USBHS_DEVINT_SUSPD, SAM_USBHS_DEVIER); } /* End of Reset. * * The USB bus reset is managed by hardware. * It is initiated by a connected host. * When a USB reset is detected on the USB line, the following * operations are performed by the controller: * * - All endpoints are disabled, except the default control endpoint. * - The default control endpoint is reset * - The data toggle sequence of the default control * endpoint is cleared. * - At the end of the reset process, * the End of Reset (USBHS_DEVISR.EORST) bit is set. * - During a reset, the USBHS automatically switches * to High-speed modeif the host is High-speed-capable * (the reset is called High-speed reset). * The user should observe the USBHS_SR.SPEED field to know * the speed running at the end of the reset * (USBHS_DEVISR.EORST = 1). */ else if ((pending & USBHS_DEVINT_EORST) != 0) { /* Sample the USBHS SR register at the time of the EORST event. */ regval = sam_getreg(SAM_USBHS_SR); usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_ENDRESET), regval); /* Acknowledge the interrupt, clear pending wakeup and suspend * status as we.. */ sam_putreg(USBHS_DEVINT_WAKEUP | USBHS_DEVINT_SUSPD | USBHS_DEVINT_EORST, SAM_USBHS_DEVICR); /* Enable suspend interrupts */ sam_putreg(USBHS_DEVINT_SUSPD, SAM_USBHS_DEVIER); /* Handle the reset (will select full speed mode) */ sam_reset(priv); /* Get the correct speed mode reported by the hardware */ switch (regval & USBHS_SR_SPEED_MASK) { default: case USBHS_SR_SPEED_FULL: priv->usbdev.speed = USB_SPEED_FULL; break; case USBHS_SR_SPEED_HIGH: priv->usbdev.speed = USB_SPEED_HIGH; break; case USBHS_SR_SPEED_LOW: priv->usbdev.speed = USB_SPEED_LOW; break; } usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_SPEED), priv->usbdev.speed); } /* Upstream resume */ else if ((pending & USBHS_DEVINT_UPRSM) != 0) { /* Acknowledge interrupt */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_UPSTRRES), (uint16_t)pending); sam_putreg(USBHS_DEVINT_UPRSM, SAM_USBHS_DEVICR); } #ifdef CONFIG_USBDEV_DMA /* Endpoint DMA interrupts */ else if ((pending & USBHS_DEVINT_DMA_MASK) != 0) { /* Each endpoint DMA ineterrupt is cleared when the * USBHS_DEVDMASTATUSx interrupt source is cleared. */ /* Process each pending endpoint DMA interrupt */ for (i = 1; i <= SAM_USBHS_NDMACHANNELS; i++) { /* Is there a DMA interrupt pending for endpoint i? */ if ((pending & USBHS_DEVINT_DMA(i)) != 0) { /* Yes.. process the endpoint i DMA interrupt */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_DMA), (uint16_t)i); sam_dma_interrupt(priv, i); } } } #endif /* Endpoint Interrupts */ else if ((pending & USBHS_DEVINT_PEP_MASK) != 0) { /* Each endpoint interrupt is cleared when the interrupt source * is serviced. */ /* Process each pending endpoint interrupt */ for (i = 0; i < SAM_USBHS_NENDPOINTS; i++) { /* Is there an interrupt pending for endpoint i? */ if ((pending & USBHS_DEVINT_PEP(i)) != 0) { /* Yes.. process the endpoint i interrupt */ usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP), (uint16_t)i); sam_ep_interrupt(priv, i); } } } /* Re-sample the set of pending interrupts */ devisr = sam_getreg(SAM_USBHS_DEVISR); regval = sam_getreg(SAM_USBHS_DEVIMR); pending = devisr & regval; } usbtrace(TRACE_INTEXIT(SAM_TRACEINTID_INTERRUPT), devisr); UP_MB(); return OK; } /**************************************************************************** * * Suspend/Resume Helpers * ****************************************************************************/ /**************************************************************************** * * Name: sam_suspend * ****************************************************************************/ static void sam_suspend(struct sam_usbdev_s *priv) { /* Don't do anything if the device is already suspended */ if (priv->devstate != USBHS_DEVSTATE_SUSPENDED) { /* Notify the class driver of the suspend event */ if (priv->driver) { CLASS_SUSPEND(priv->driver, &priv->usbdev); } /* Switch to the Suspended state */ priv->prevstate = priv->devstate; priv->devstate = USBHS_DEVSTATE_SUSPENDED; /* Let the board-specific logic know that we have entered the * suspend state. This may trigger additional reduced power * consumption measures. */ sam_usbsuspend((struct usbdev_s *)priv, false); } } /**************************************************************************** * * Name: sam_resume * ****************************************************************************/ static void sam_resume(struct sam_usbdev_s *priv) { uint32_t regval; /* This function is called when either * (1) a WKUP interrupt is received from * the host PC, or * (2) the class device implementation calls the wakeup() * method. */ /* Don't do anything if the device was not suspended */ if (priv->devstate == USBHS_DEVSTATE_SUSPENDED) { /* Un-freeze clocking */ regval = sam_getreg(SAM_USBHS_CTRL); regval &= ~USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); /* Revert to the previous state */ priv->devstate = priv->prevstate; /* Restore full power -- * whatever that means forSC this particular board */ /* Restore full power -- * whatever that means for this particular board */ sam_usbsuspend((struct usbdev_s *)priv, true); /* Notify the class driver of the resume event */ if (priv->driver) { CLASS_RESUME(priv->driver, &priv->usbdev); } } } /**************************************************************************** * * Endpoint Helpers * ****************************************************************************/ /**************************************************************************** * * Name: sam_ep_reset * * Description: * Reset and disable a set of endpoints. * ****************************************************************************/ static void sam_ep_reset(struct sam_usbdev_s *priv, uint8_t epno) { struct sam_ep_s *privep = &priv->eplist[epno]; uint32_t regval; /* Disable endpoint interrupt */ sam_putreg(USBHS_DEVINT_PEP(epno), SAM_USBHS_DEVIDR); /* Clear all pending interrupts */ sam_putreg(USBHS_DEVEPTICR_ALLINTS, SAM_USBHS_DEVEPTICR(epno)); /* Set DMA control register to a defined state */ if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0) { sam_putreg(0, SAM_USBHS_DEVDMACTRL(epno)); } /* Cancel any queued requests. Since they are cancelled 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 sam_ep_disable * for each of its configured endpoints. */ sam_req_cancel(privep, -ESHUTDOWN); /* Put the endpoint in reset */ regval = sam_getreg(SAM_USBHS_DEVEPT); regval |= USBHS_DEVEPT_EPRST(epno); sam_putreg(regval, SAM_USBHS_DEVEPT); /* Reset endpoint status */ privep->epstate = USBHS_EPSTATE_DISABLED; privep->stalled = false; privep->halted = false; privep->zlpneeded = false; privep->zlpsent = false; privep->bank = 0; /* Take the endpoint out of reset */ regval &= ~USBHS_DEVEPT_EPRST(epno); sam_putreg(regval, SAM_USBHS_DEVEPT); } /**************************************************************************** * * Name: sam_epset_reset * * Description: * Reset and disable a set of endpoints. * ****************************************************************************/ static void sam_epset_reset(struct sam_usbdev_s *priv, uint16_t epset) { uint32_t bit; int epno; /* Reset each endpoint in the set */ for (epno = 0, bit = 1, epset &= SAM_EPSET_ALL; epno < SAM_USBHS_NENDPOINTS && epset != 0; epno++, bit <<= 1) { /* Is this endpoint in the set? */ if ((epset & bit) != 0) { /* Yes.. reset it */ sam_ep_reset(priv, epno); epset &= ~bit; } } } /**************************************************************************** * * Name: sam_ep_reserve * * Description: * Find an un-reserved endpoint number and reserve it for the caller. * ****************************************************************************/ static inline struct sam_ep_s * sam_ep_reserve(struct sam_usbdev_s *priv, uint16_t epset) { struct sam_ep_s *privep = NULL; irqstate_t flags; int epndx = 0; flags = enter_critical_section(); epset &= priv->epavail; if (epset) { /* Select the lowest bit in the set of matching, available endpoints * (skipping EP0) */ for (epndx = 1; epndx < SAM_USBHS_NENDPOINTS; epndx++) { uint16_t bit = SAM_EP_BIT(epndx); if ((epset & bit) != 0) { /* Mark the endpoint no longer available */ priv->epavail &= ~bit; /* And return the pointer to the standard endpoint structure */ privep = &priv->eplist[epndx]; break; } } } leave_critical_section(flags); return privep; } /**************************************************************************** * * Name: sam_ep_unreserve * * Description: * The endpoint is no long in-used. It will be un-reserved and can be * reused if needed. * ****************************************************************************/ static inline void sam_ep_unreserve(struct sam_usbdev_s *priv, struct sam_ep_s *privep) { irqstate_t flags = enter_critical_section(); priv->epavail |= SAM_EP_BIT(USB_EPNO(privep->ep.eplog)); leave_critical_section(flags); } /**************************************************************************** * * Name: sam_ep_configure_internal * * Description: * This is the internal implementation of the endpoint configuration logic * and implements the endpoint configuration method of the usbdev_ep_s * interface. As an internal interface, it will be used to configure * endpoint 0 which is not available to the class implementation. * ****************************************************************************/ static int sam_ep_configure_internal(struct sam_ep_s *privep, const struct usb_epdesc_s *desc) { struct sam_usbdev_s *priv; uintptr_t regaddr; uint32_t regval; uint16_t maxpacket; uint8_t epno; uint8_t eptype; uint8_t nbtrans; bool dirin; uinfo("len: %02x type: %02x addr: %02x attr: %02x " "maxpacketsize: %02x %02x interval: %02x\n", desc->len, desc->type, desc->addr, desc->attr, desc->mxpacketsize[0], desc->mxpacketsize[1], desc->interval); /* Decode the endpoint descriptor */ epno = USB_EPNO(desc->addr); dirin = (desc->addr & USB_DIR_MASK) == USB_REQ_DIR_IN; eptype = (desc->attr & USB_EP_ATTR_XFERTYPE_MASK) >> USB_EP_ATTR_XFERTYPE_SHIFT; maxpacket = GETUINT16(desc->mxpacketsize); nbtrans = 1; /* Initialize the endpoint structure */ priv = privep->dev; privep->ep.eplog = desc->addr; /* Includes direction */ privep->epstate = USBHS_EPSTATE_IDLE; privep->bank = SAM_USBHS_NBANKS(epno); /* Special case maxpacket handling for high-speed endpoints */ if (priv->usbdev.speed == USB_SPEED_HIGH) { /* HS Interval, 125us */ /* MPS: Bits 12:11 specify NB_TRANS, as USB 2.0 Spec. */ nbtrans = ((maxpacket >> 11) & 3); if (nbtrans == 3) { nbtrans = 1; } else { nbtrans++; } /* nbtrans = 0: Reserved to endpoint that does not have the high- * bandwidth isochronous capability. * nbtrans = 1: Default value: one transaction per microframe. * nbtrans = 2: Two transactions per microframe. This endpoint * should be configured as double-bank. * nbtrans = 3 Three transactions per microframe. This endpoint * should be configured as triple-bank */ if (privep->bank < nbtrans) { nbtrans = privep->bank; } /* Mask, bit 10..0 is the max packet size */ maxpacket &= 0x7ff; } privep->ep.maxpacket = maxpacket; /* Initialize the endpoint hardware */ /* Disable the endpoint */ regval = sam_getreg(SAM_USBHS_DEVEPT); regval &= ~USBHS_DEVEPT_EPEN(epno); sam_putreg(regval, SAM_USBHS_DEVEPT); sam_putreg(USBHS_DEVEPTIDR_ALLINTS, SAM_USBHS_DEVEPTIDR(epno)); /* Clear toggle and stall indications */ sam_putreg(USBHS_DEVEPTISR_DTSEQ_MASK | USBHS_DEVEPTINT_STALLEDI, SAM_USBHS_DEVEPTICR(epno)); /* Reset the endpoint */ regval = sam_getreg(SAM_USBHS_DEVEPT); regval |= USBHS_DEVEPT_EPRST(epno); sam_putreg(regval, SAM_USBHS_DEVEPT); regval &= ~USBHS_DEVEPT_EPRST(epno); sam_putreg(regval, SAM_USBHS_DEVEPT); /* If this is EP0, disable interrupts now */ if (eptype == USB_EP_ATTR_XFER_CONTROL) { sam_putreg(USBHS_DEVINT_PEP(epno), SAM_USBHS_DEVIDR); } /* Enable the endpoint */ regval = sam_getreg(SAM_USBHS_DEVEPT); regval |= USBHS_DEVEPT_EPEN(epno); sam_putreg(regval, SAM_USBHS_DEVEPT); /* Configure the endpoint */ regval = USBHS_DEVEPTCFG_ALLOC | USBHS_DEVEPTCFG_EPDIR(dirin) | USBHS_DEVEPTCFG_EPTYPE(eptype) | USBHS_DEVEPTCFG_EPBK(privep->bank) | USBHS_DEVEPTCFG_NBTRANS(nbtrans); if (maxpacket <= 8) { regval |= USBHS_DEVEPTCFG_EPSIZE_8; } else if (maxpacket <= 16) { regval |= USBHS_DEVEPTCFG_EPSIZE_16; } else if (maxpacket <= 32) { regval |= USBHS_DEVEPTCFG_EPSIZE_32; } else if (maxpacket <= 64) { regval |= USBHS_DEVEPTCFG_EPSIZE_64; } else if (maxpacket <= 128) { regval |= USBHS_DEVEPTCFG_EPSIZE_128; } else if (maxpacket <= 256) { regval |= USBHS_DEVEPTCFG_EPSIZE_256; } else if (maxpacket <= 512) { regval |= USBHS_DEVEPTCFG_EPSIZE_512; } else if (maxpacket <= 1024) { regval |= USBHS_DEVEPTCFG_EPSIZE_1024; } else { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEPTYPE), eptype); DEBUGPANIC(); regval |= USBHS_DEVEPTCFG_EPSIZE_8; } regaddr = SAM_USBHS_DEVEPTCFG(epno); sam_putreg(regval, regaddr); /* Verify that the CFGOK flag is set. This flag is set if the * endpoint size and the number of banks are correct compared to * the FIFO maximum capacity and the maximum number of allowed banks. */ regaddr = SAM_USBHS_DEVEPTISR(epno); if ((sam_getreg(regaddr) & USBHS_DEVEPTISR_CFGOK) == 0) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_NCFGOK), epno); return -EINVAL; } /* Enable the endpoint. The way that the endpoint is enabled depends of * if the endpoint supports DMA transfers or not. */ if ((SAM_EPSET_DMA & SAM_EP_BIT(epno)) != 0) { /* Select AUTO_VALID so that the hardware will manage FIFCON. */ regaddr = SAM_USBHS_DEVEPTCFG(epno); regval |= USBHS_DEVEPTCFG_AUTOSW; sam_putreg(regval, SAM_USBHS_DEVEPTCFG(epno)); } else { /* No DMA... Software will manage FIFOCON. */ regval = USBHS_DEVEPTINT_KILLBKI | USBHS_DEVEPTINT_RXSTPI; sam_putreg(regval, SAM_USBHS_DEVEPTIER(epno)); } /* If this is EP0, enable interrupts now */ if (eptype == USB_EP_ATTR_XFER_CONTROL) { /* Enable the endpoint 0 RX interrupts */ regaddr = SAM_USBHS_DEVEPTIER(epno); regval = USBHS_DEVEPTINT_RXOUTI | USBHS_DEVEPTINT_RXSTPI; sam_putreg(regval, regaddr); /* Enable endpoint 0 general interrupts */ sam_putreg(USBHS_DEVINT_PEP(epno), SAM_USBHS_DEVIER); } #ifdef CONFIG_USBDEV_DMA else { /* Enable automatic bank switching */ regaddr = SAM_USBHS_DEVEPTIER(epno); regval = sam_getreg(regaddr); regval |= USBHS_DEVEPTCFG_AUTOSW; sam_putreg(regval, regaddr); } #endif sam_dumpep(priv, epno); return OK; } /**************************************************************************** * * Name: sam_ep0_configure * * Description: * Configure EP0 for normal operation. * ****************************************************************************/ static inline int sam_ep0_configure(struct sam_usbdev_s *priv) { return sam_ep_configure_internal(&priv->eplist[EP0], &g_ep0desc); } /**************************************************************************** * * Endpoint operations * ****************************************************************************/ /**************************************************************************** * * Name: sam_ep_configure * * Description: * This is the endpoint configuration method of the usbdev_ep_s interface. * ****************************************************************************/ static int sam_ep_configure(struct usbdev_ep_s *ep, const struct usb_epdesc_s *desc, bool last) { struct sam_ep_s *privep = (struct sam_ep_s *)ep; struct sam_usbdev_s *priv; int ret; /* Verify parameters. Endpoint 0 is not available at this interface */ #if defined(CONFIG_DEBUG_FEATURES) || defined(CONFIG_USBDEV_TRACE) uint8_t epno = USB_EPNO(desc->addr); usbtrace(TRACE_EPCONFIGURE, (uint16_t)epno); DEBUGASSERT(ep && desc && epno > 0 && epno < SAM_USBHS_NENDPOINTS); DEBUGASSERT(epno == USB_EPNO(ep->eplog)); #endif /* This logic is implemented in sam_ep_configure_internal */ ret = sam_ep_configure_internal(privep, desc); if (ret == OK && last) { /* If this was the last endpoint, then the class driver is fully * configured. */ priv = privep->dev; priv->devstate = USBHS_DEVSTATE_CONFIGURED; } return ret; } /**************************************************************************** * * Name: sam_ep_disable * * Description: * This is the disable() method of the USB device endpoint structure. * ****************************************************************************/ static int sam_ep_disable(struct usbdev_ep_s *ep) { struct sam_ep_s *privep = (struct sam_ep_s *)ep; struct sam_usbdev_s *priv; irqstate_t flags; uint8_t epno; DEBUGASSERT(ep != NULL); epno = USB_EPNO(ep->eplog); usbtrace(TRACE_EPDISABLE, epno); /* Reset the endpoint and cancel any ongoing activity */ flags = enter_critical_section(); priv = privep->dev; sam_ep_reset(priv, epno); /* Revert to the addressed-but-not-configured state */ priv->devstate = USBHS_DEVSTATE_ADDRESSED; leave_critical_section(flags); return OK; } /**************************************************************************** * * Name: sam_ep_allocreq * * Description: * This is the allocreq() method of the USB device endpoint structure. * ****************************************************************************/ static struct usbdev_req_s *sam_ep_allocreq(struct usbdev_ep_s *ep) { struct sam_req_s *privreq; DEBUGASSERT(ep != NULL); usbtrace(TRACE_EPALLOCREQ, USB_EPNO(ep->eplog)); privreq = kmm_malloc(sizeof(struct sam_req_s)); if (!privreq) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_ALLOCFAIL), 0); return NULL; } memset(privreq, 0, sizeof(struct sam_req_s)); return &privreq->req; } /**************************************************************************** * * Name: sam_ep_freereq * * Description: * This is the freereq() method of the USB device endpoint structure. * ****************************************************************************/ static void sam_ep_freereq(struct usbdev_ep_s *ep, struct usbdev_req_s *req) { struct sam_req_s *privreq = (struct sam_req_s *)req; DEBUGASSERT(ep != NULL && req != NULL); usbtrace(TRACE_EPFREEREQ, USB_EPNO(ep->eplog)); kmm_free(privreq); } /**************************************************************************** * * Name: sam_ep_allocbuffer * * Description: * This is the allocbuffer() method of the USB device endpoint structure. * ****************************************************************************/ #ifdef CONFIG_USBDEV_DMA static void *sam_ep_allocbuffer(struct usbdev_ep_s *ep, uint16_t nbytes) { /* Allocate properly aligned memory */ return kmm_memalign(USBHS_ALIGN, USBHS_ALIGN_UP(nbytes)); } #endif /**************************************************************************** * * Name: sam_ep_freebuffer * * Description: * This is the freebuffer() method of the USB device endpoint structure. * ****************************************************************************/ #ifdef CONFIG_USBDEV_DMA static void sam_ep_freebuffer(struct usbdev_ep_s *ep, void *buf) { /* There is no special buffer requirement to free aligned DMA buffers */ kumm_free(buf); } #endif /**************************************************************************** * * Name: sam_ep_submit * * Description: * This is the submit() method of the USB device endpoint structure. * ****************************************************************************/ static int sam_ep_submit(struct usbdev_ep_s *ep, struct usbdev_req_s *req) { struct sam_req_s *privreq = (struct sam_req_s *)req; struct sam_ep_s *privep = (struct sam_ep_s *)ep; struct sam_usbdev_s *priv; irqstate_t flags; uint8_t epno; int ret = OK; DEBUGASSERT(ep != NULL && req != NULL && req->callback != NULL && req->buf != NULL); usbtrace(TRACE_EPSUBMIT, USB_EPNO(ep->eplog)); priv = privep->dev; DEBUGASSERT(priv->driver != NULL); /* Handle the request from the class driver */ epno = USB_EPNO(ep->eplog); req->result = -EINPROGRESS; req->xfrd = 0; privreq->inflight = 0; flags = enter_critical_section(); /* 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 (EP0 SETUP OUT data receipt does not use requests). */ if (USB_ISEPIN(ep->eplog) || epno == EP0) { /* If the endpoint is stalled, then fail any attempts to write * through the endpoint. */ if (privep->stalled) { sam_req_abort(privep, privreq, -EBUSY); uerr("ERROR: stalled\n"); ret = -EPERM; } else { /* Add the new request to the request queue for the IN endpoint */ sam_req_enqueue(&privep->reqq, privreq); usbtrace(TRACE_INREQQUEUED(epno), req->len); /* If the IN endpoint is IDLE, then transfer the data now */ if (privep->epstate == USBHS_EPSTATE_IDLE) { ret = sam_req_write(priv, privep); } } } /* Handle OUT (host-to-device) requests */ else { /* Add the new request to the request queue for the OUT endpoint */ sam_req_enqueue(&privep->reqq, privreq); usbtrace(TRACE_OUTREQQUEUED(epno), req->len); /* If the OUT endpoint IDLE, then setup the read */ if (privep->epstate == USBHS_EPSTATE_IDLE) { ret = sam_req_read(priv, privep, 0); } } leave_critical_section(flags); return ret; } /**************************************************************************** * * Name: sam_ep_cancel * ****************************************************************************/ static int sam_ep_cancel(struct usbdev_ep_s *ep, struct usbdev_req_s *req) { struct sam_ep_s *privep = (struct sam_ep_s *)ep; irqstate_t flags; DEBUGASSERT(ep != NULL && req != NULL); usbtrace(TRACE_EPCANCEL, USB_EPNO(ep->eplog)); flags = enter_critical_section(); sam_req_cancel(privep, -EAGAIN); leave_critical_section(flags); return OK; } /**************************************************************************** * * Name: sam_ep_stall * ****************************************************************************/ static int sam_ep_stall(struct usbdev_ep_s *ep, bool resume) { struct sam_ep_s *privep; struct sam_usbdev_s *priv; uint8_t epno = USB_EPNO(ep->eplog); uintptr_t regaddr; uint32_t regval; irqstate_t flags; DEBUGASSERT(ep != NULL); /* Check that endpoint is in Idle state */ privep = (struct sam_ep_s *)ep; DEBUGASSERT(/* privep->epstate == USBHS_EPSTATE_IDLE && */ privep->dev); priv = (struct sam_usbdev_s *)privep->dev; epno = USB_EPNO(ep->eplog); /* STALL or RESUME the endpoint */ flags = enter_critical_section(); usbtrace(resume ? TRACE_EPRESUME : TRACE_EPSTALL, USB_EPNO(ep->eplog)); /* Handle the resume condition */ if (resume) { /* Check if the endpoint is halted */ if (privep->epstate == USBHS_EPSTATE_STALLED) { usbtrace(TRACE_EPRESUME, epno); privep->stalled = false; /* Return endpoint to Idle state */ privep->epstate = USBHS_EPSTATE_IDLE; /* Clear FORCESTALL request * REVISIT: Data sheet says to reset toggle to DATA0 only on OUT * endpoints. */ /* Clear FORCESTALL flag */ sam_putreg(USBHS_DEVEPTINT_STALLEDI, SAM_USBHS_DEVEPTIDR(epno)); regaddr = SAM_USBHS_DEVEPTCFG(epno); regval = sam_getreg(regaddr); regval |= USBHS_DEVEPTCFG_AUTOSW; sam_putreg(regval, regaddr); /* Reset the endpoint */ regval = sam_getreg(SAM_USBHS_DEVEPT); regval |= USBHS_DEVEPT_EPRST(epno); sam_putreg(regval, SAM_USBHS_DEVEPT); regval &= ~USBHS_DEVEPT_EPRST(epno); sam_putreg(regval, SAM_USBHS_DEVEPT); /* Resuming any blocked data transfers on the endpoint */ if (epno == 0 || USB_ISEPIN(ep->eplog)) { /* IN endpoint (or EP0). Restart any queued write requests */ sam_req_write(priv, privep); } else { /* OUT endpoint. Restart any queued read requests. */ sam_req_read(priv, privep, 0); } } } /* Handle the stall condition */ else { /* Check that endpoint is enabled and not already in Halt state */ if ((privep->epstate != USBHS_EPSTATE_DISABLED) && (privep->epstate != USBHS_EPSTATE_STALLED)) { usbtrace(TRACE_EPSTALL, epno); /* If this is an IN endpoint (or endpoint 0), then cancel all * of the pending write requests. */ if (epno == 0 || USB_ISEPIN(ep->eplog)) { sam_req_cancel(privep, -EPERM); } /* Otherwise, it is an OUT endpoint. Complete any read request * currently in progress (it will get requeued immediately). */ else if (privep->epstate == USBHS_EPSTATE_RECEIVING) { sam_req_complete(privep, -EPERM); } /* Put endpoint into stalled state */ privep->epstate = USBHS_EPSTATE_STALLED; privep->stalled = true; regaddr = SAM_USBHS_DEVEPTCFG(epno); regval = sam_getreg(regaddr); regval &= ~USBHS_DEVEPTCFG_AUTOSW; sam_putreg(regval, regaddr); sam_putreg(USBHS_DEVEPTINT_STALLRQI, SAM_USBHS_DEVEPTIER(epno)); /* Disable endpoint/DMA interrupts. They not be re-enabled until * the stall is cleared and the next transfer is started. It * would, of course, be a bad idea to do this on EP0 since it is * a SETUP request that is going to clear the STALL. */ if (epno != 0) { regval = sam_getreg(SAM_USBHS_DEVIDR); regval |= USBHS_DEVINT_PEP(epno); #ifdef CONFIG_USBDEV_DMA if (SAM_USBHS_DMA(epno)) { /* Disable the endpoint DMA interrupt */ regval |= USBHS_DEVINT_DMA(epno); } #endif sam_putreg(regval, SAM_USBHS_DEVIDR); } } } leave_critical_section(flags); return OK; } /**************************************************************************** * * Device Controller Operations * ****************************************************************************/ /**************************************************************************** * * Name: sam_allocep * * Description: * This is the allocep() method of the USB device driver interface * ****************************************************************************/ static struct usbdev_ep_s *sam_allocep(struct usbdev_s *dev, uint8_t epno, bool in, uint8_t eptype) { struct sam_usbdev_s *priv = (struct sam_usbdev_s *)dev; struct sam_ep_s *privep = NULL; uint16_t epset = SAM_EPSET_NOTEP0; usbtrace(TRACE_DEVALLOCEP, (uint16_t)epno); DEBUGASSERT(dev != NULL); /* 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 the hardware. */ if (epno >= SAM_USBHS_NENDPOINTS) { usbtrace(TRACE_DEVERROR(SAM_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 = SAM_EP_BIT(epno); } /* Check if the selected endpoint number is available */ privep = sam_ep_reserve(priv, epset); if (!privep) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPRESERVE), (uint16_t)epset); return NULL; } return &privep->ep; } /**************************************************************************** * * Name: sam_freeep * * Description: * This is the freeep() method of the USB device driver interface * ****************************************************************************/ static void sam_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep) { struct sam_usbdev_s *priv; struct sam_ep_s *privep; DEBUGASSERT(dev != NULL && ep != NULL); priv = (struct sam_usbdev_s *)dev; privep = (struct sam_ep_s *)ep; usbtrace(TRACE_DEVFREEEP, (uint16_t)USB_EPNO(ep->eplog)); if (priv && privep) { /* Mark the endpoint as available */ sam_ep_unreserve(priv, privep); } } /**************************************************************************** * * Name: sam_getframe * * Description: * This is the getframe() method of the USB device driver interface * ****************************************************************************/ static int sam_getframe(struct usbdev_s *dev) { uint32_t regval; uint16_t frameno; DEBUGASSERT(dev != NULL); /* Return the last frame number detected by the hardware */ regval = sam_getreg(SAM_USBHS_DEVFNUM); frameno = (regval & USBHS_DEVFNUM_FNUM_MASK) >> USBHS_DEVFNUM_FNUM_SHIFT; usbtrace(TRACE_DEVGETFRAME, frameno); return frameno; } /**************************************************************************** * * Name: sam_wakeup * * Description: * This is the wakeup() method of the USB device driver interface * ****************************************************************************/ static int sam_wakeup(struct usbdev_s *dev) { struct sam_usbdev_s *priv = (struct sam_usbdev_s *)dev; irqstate_t flags; uint32_t regval; usbtrace(TRACE_DEVWAKEUP, 0); DEBUGASSERT(dev != NULL); /* Resume normal operation */ flags = enter_critical_section(); sam_resume(priv); /* Activate a remote wakeup. * Setting this bit forces an external interrupt * on the USBHS controller for Remote Wake UP purposes. * An Upstream Resume is sent only after the * USBHS bus has been in SUSPEND state for at least 5 ms. */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval |= USBHS_DEVCTRL_RMWKUP; sam_putreg(regval, SAM_USBHS_DEVCTRL); leave_critical_section(flags); /* This bit is automatically cleared by hardware at the end of the Upstream * Resume */ while ((sam_getreg(SAM_USBHS_DEVCTRL) & USBHS_DEVCTRL_RMWKUP) != 0); return OK; } /**************************************************************************** * * Name: sam_selfpowered * * Description: * This is the selfpowered() method of the USB device driver interface * ****************************************************************************/ static int sam_selfpowered(struct usbdev_s *dev, bool selfpowered) { struct sam_usbdev_s *priv = (struct sam_usbdev_s *)dev; usbtrace(TRACE_DEVSELFPOWERED, (uint16_t)selfpowered); DEBUGASSERT(dev != NULL); priv->selfpowered = selfpowered; return OK; } /**************************************************************************** * * Name: sam_pullup * * Description: * This is the pullup() method of the USB device driver interface * ****************************************************************************/ static int sam_pullup(struct usbdev_s *dev, bool enable) { struct sam_usbdev_s *priv = (struct sam_usbdev_s *)dev; irqstate_t flags; uint32_t regval; usbtrace(TRACE_DEVPULLUP, (uint16_t)enable); flags = enter_critical_section(); if (enable) { /* Un-freeze clocking. * * When the clock is frozen, on certain bits in the USBCH_CTRL * register can be modified (FRZCLK, USBE, and LS). In addition, * only the asynchronous interrupt sources can trigger the USB * interrupt: * * - Wake-up Interrupt (USBHS_DEVISR.WAKEUP) * - Host Wake-up Interrupt (USBHS_HSTISR.HWUPI) */ regval = sam_getreg(SAM_USBHS_CTRL); regval &= ~USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); /* DETACH=0: USBHS is attached. ARMs the USBHS to pull up the DP line * when the USBHS is no longer suspended. */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval &= ~USBHS_DEVCTRL_DETACH; sam_putreg(regval, SAM_USBHS_DEVCTRL); priv->usbdev.speed = USB_SPEED_FULL; /* There are several possibilities: * * 1. The device may not be plugged into a host. * In that case, the hardware will be in a suspended state. * When an idle USB bus state has been detected for 3 ms, * the controller sets the Suspend * (USBHS_DEVISR.SUSP) interrupt bit. * 2. We may have been suspended but a WAKEUP * event has already occurred. * The USBHS_DEVISR.WAKEUP interrupt bit is * set when a non-idle event is detected, * it can occur whether the controller is in the Suspend * mode or not. * The USBHS_DEVISR.SUSP and USBHS_DEVISR.WAKEUP interrupts * are thus independent, except that one bit is cleared * when the other is set. * 3. Because we have already enabled the pull-up, * that event may have already have been reset by the host. */ regval = sam_getreg(SAM_USBHS_DEVISR); if ((regval & USBHS_DEVINT_SUSPD) == 0) { /* If the USBHS detects activity on the BUS, then it will not be * suspended. In that case, next event that we expect to see is a * reset from the connected host. When a USB reset is detected on * the USB line, the following operations are performed by the * controller: * * - All endpoints are disabled, except the default control * endpoint. * - The default control endpoint is reset * - The data toggle sequence of the default control endpoint is * cleared. * - At the end of the reset process, the End of Reset * (USBHS_DEVISR.EORST) bit is set. * - During a reset, the USBHS automatically switches to High- * speed mode if the host is High-speed-capable (the reset is * called High-speed reset). The user should observe the * USBHS_SR.SPEED field to know the speed running at the end * of the reset (USBHS_DEVISR.EORST = 1). * * The class implementation should not call this method with * enable == true until is is fully initialized and ready to * accept connections. */ /* Enable expected interrupts */ sam_putreg(USBHS_DEVINT_EORST | USBHS_DEVINT_EORSM | USBHS_DEVINT_SUSPD, SAM_USBHS_DEVIER); /* Leave the clock unfrozen */ } else { /* If there is no host connected (no bus activity), then we * might get a SUSPend interrupt instead of a End of Reset. In * the case, we would like to keep the clock frozen until the * host is connected. * * The strategy here was taken from the SAMV7 sample code: It * will force a SUSPend event. Then disable clocking. We will * take the SUSPend interrupt (because it is already pending), * but after the clock is frozen, only a WAKEUP interrupt can be * received. */ /* Enable wakeup interrupts */ sam_putreg(USBHS_DEVINT_WAKEUP | USBHS_DEVINT_EORSM, SAM_USBHS_DEVIER); /* Enable expected interrupts */ sam_putreg(USBHS_DEVINT_EORST | USBHS_DEVINT_WAKEUP | USBHS_DEVINT_SUSPD, SAM_USBHS_DEVIER); /* Clear pending interrupts */ sam_putreg(USBHS_DEVINT_EORST | USBHS_DEVINT_SUSPD, SAM_USBHS_DEVICR); /* Force the first suspend event */ sam_putreg(USBHS_DEVINT_SUSPD, SAM_USBHS_DEVIFR); sam_putreg(USBHS_DEVINT_WAKEUP, SAM_USBHS_DEVICR); /* Refreeze the clock and wait for the wakeup event */ regval = sam_getreg(SAM_USBHS_CTRL); regval |= USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); } } else { /* DETACH=1: USBHS is detached, UTMI transceiver is suspended. */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval |= USBHS_DEVCTRL_DETACH; sam_putreg(regval, SAM_USBHS_DEVCTRL); /* Freeze clocking */ regval = sam_getreg(SAM_USBHS_CTRL); regval |= USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); /* Device returns to the Powered state */ if (priv->devstate > USBHS_DEVSTATE_POWERED) { priv->devstate = USBHS_DEVSTATE_POWERED; } if (priv->prevstate > USBHS_DEVSTATE_POWERED) { priv->prevstate = USBHS_DEVSTATE_POWERED; } } leave_critical_section(flags); return OK; } /**************************************************************************** * * Initialization/Reset * ****************************************************************************/ /**************************************************************************** * * Name: sam_reset * ****************************************************************************/ static void sam_reset(struct sam_usbdev_s *priv) { uint32_t regval; uint8_t epno; /* Unfreeze clocking to the USBHS peripheral. */ regval = sam_getreg(SAM_USBHS_CTRL); regval &= ~USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); /* Tell the class driver that we are disconnected. The class driver * should then accept any new configurations. */ CLASS_DISCONNECT(priv->driver, &priv->usbdev); /* The device enters the Default state */ /* Disable the device address */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval &= ~(USBHS_DEVCTRL_UADD_MASK | USBHS_DEVCTRL_ADDEN); sam_putreg(regval, SAM_USBHS_DEVCTRL); /* Revert to the un-addressed, default state */ priv->devstate = USBHS_DEVSTATE_DEFAULT; /* Reset and disable all endpoints. Then re-configure EP0 */ sam_epset_reset(priv, SAM_EPSET_ALL); sam_ep0_configure(priv); /* Reset endpoint data structures */ for (epno = 0; epno < SAM_USBHS_NENDPOINTS; epno++) { struct sam_ep_s *privep = &priv->eplist[epno]; /* Cancel any queued requests. Since they are cancelled * with status -ESHUTDOWN, then will not be re-queued * until the configuration is reset. NOTE: This should * not be necessary... the CLASS_DISCONNECT above should * result in the class implementation calling sam_ep_disable * for each of its configured endpoints. */ sam_req_cancel(privep, -ESHUTDOWN); /* Reset endpoint status */ privep->stalled = false; privep->halted = false; privep->zlpneeded = false; privep->zlpsent = false; } /* Re-configure the USB controller in its initial, unconnected state */ priv->usbdev.speed = USB_SPEED_FULL; /* Enable normal operational interrupts (including endpoint 0) */ regval = USBHS_DEVINT_EORST | USBHS_DEVINT_WAKEUP | USBHS_DEVINT_SUSPD | USBHS_DEVINT_PEP0; sam_putreg(regval, SAM_USBHS_DEVIER); sam_dumpep(priv, EP0); } /**************************************************************************** * * Name: sam_hw_setup * ****************************************************************************/ static void sam_hw_setup(struct sam_usbdev_s *priv) { uint32_t regval; int i; /* Disable USB hardware. Will perform a reset of most resisters. */ regval = sam_getreg(SAM_USBHS_CTRL); regval &= ~USBHS_CTRL_USBE; sam_putreg(regval, SAM_USBHS_CTRL); /* Configure USBHS pins. Nothing needs to be done: HDM and HDP are the * primary pin functions and there are no alternatives. */ /* Enable clocking to the USBHS peripheral. * * The clock for the USBHS bus interface is generated by the Power * Management Controller (PMC). Before enabling the USB clock in the * PMC, the USBHS must be enabled (by writing a one to the * USBHS_CTRL.USBE bit and a zero to the USBHS_CTRL.FRZCLK bit). * * The USBHS can work in two modes: * * - Normal mode (SPDCONF = 0) where High speed, Full speed and Low * speed are available. * - Low-power mode (SPDCONF = 1) where Full speed and Low speed are * available. * * Only the normal mode is supported by this driver. For Normal mode: * * 1. Enable the USBHS peripheral clock (PMC_PCER). * 2. Enable the USBHS (UIMOD = 1, USBE = 1, FRZCLK = 0). * 3. Enable the UPLL 480 MHz. * 4. Wait for the UPLL 480 MHz to be considered as locked by the PMC. * * Steps 3 and 4 are performed in sam_usbclock.c. */ /* Enable the USBHS peripheral clock (PMC_PCER) */ sam_usbhs_enableclk(); /* Enable USBHS peripheral (USBE = 1) in device mode (UIMOD = 1) and * unfreeze clocking (FRZCLK = 0) */ regval |= USBHS_CTRL_UIMOD_DEVICE; sam_putreg(regval, SAM_USBHS_CTRL); regval |= USBHS_CTRL_USBE; sam_putreg(regval, SAM_USBHS_CTRL); regval &= ~USBHS_CTRL_UIDE; sam_putreg(regval, SAM_USBHS_CTRL); regval &= ~USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); /* Enable the UPLL */ sam_usbclock(); /* Select High Speed or force Full Speed */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval &= ~USBHS_DEVCTRL_SPDCONF_MASK; #ifdef CONFIG_SAMV7_USBDEVHS_LOWPOWER regval |= USBHS_DEVCTRL_SPDCONF_LOWPOWER; #else regval |= USBHS_DEVCTRL_SPDCONF_NORMAL; #endif sam_putreg(regval, SAM_USBHS_DEVCTRL); /* Wait for UTMI clocking to be usable */ while ((sam_getreg(SAM_USBHS_SR) & USBHS_SR_CLKUSABLE) == 0); /* Make sure that we are not in Forced Low-Speed mode */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval &= ~USBHS_DEVCTRL_LS; sam_putreg(regval, SAM_USBHS_DEVCTRL); /* Reset and disable all endpoints, re-initializing endpoint 0. */ sam_epset_reset(priv, SAM_EPSET_ALL); sam_ep0_configure(priv); /* Disconnect the device */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval |= USBHS_DEVCTRL_DETACH; sam_putreg(regval, SAM_USBHS_DEVCTRL); /* Initialize DMA channels */ for (i = 1; i <= SAM_USBHS_NDMACHANNELS; i++) { /* Stop any DMA transfer */ sam_putreg(0, SAM_USBHS_DEVDMACTRL(i)); } /* Disable all endpoint interrupts. Disable all endpoints */ sam_putreg(USBHS_DEVEPT_ALLEPEN, SAM_USBHS_DEVIDR); sam_putreg(0, SAM_USBHS_DEVEPT); /* Disable each endpoint interrupt */ for (i = 0; i < SAM_USBHS_NENDPOINTS; i++) { /* Disable endpoint interrupts */ sam_putreg(USBHS_DEVEPTIDR_ALLINTS, SAM_USBHS_DEVEPTIDR(i)); /* Clear endpoint status */ sam_putreg(USBHS_DEVEPTICR_ALLINTS, SAM_USBHS_DEVEPTICR(i)); } /* Disable all interrupts */ sam_putreg(USBHS_DEVINT_ALL, SAM_USBHS_DEVIDR); /* Initialization complete... Freeze the clock */ regval = sam_getreg(SAM_USBHS_CTRL); regval |= USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); } /**************************************************************************** * * Name: sam_sw_setup * ****************************************************************************/ static void sam_sw_setup(struct sam_usbdev_s *priv) { int epno; #ifdef CONFIG_SAMV7_USBDEVHS_SCATTERGATHER #ifndef CONFIG_SAMV7_USBDEVHS_PREALLOCATE int i; /* Allocate a pool of free DMA transfer descriptors */ priv->dtdpool = (struct sam_dtd_s *) kmm_memalign(16, CONFIG_SAMV7_USBDEVHS_NDTDS * sizeof(struct sam_dtd_s)); if (!priv->dtdpool) { uerr("ERROR: Failed to allocate the DMA transfer descriptor pool\n"); return NULL; } /* Initialize the list of free DMA transfer descriptors */ for (i = 0; i < CONFIG_SAMV7_USBDEVHS_NDTDS; i++) { /* Put the transfer descriptor in a free list */ sam_td_free(&priv->dtdpool[i]); } #else /* Initialize the list of free DMA transfer descriptors */ DEBUGASSERT(((uintptr_t)&g_dtdpool & 15) == 0); for (i = 0; i < CONFIG_SAMV7_USBDEVHS_NDTDS; i++) { /* Put the transfer descriptor in a free list */ sam_td_free(&g_dtdpool[i]); } #endif /* CONFIG_SAMV7_USBDEVHS_PREALLOCATE */ #endif /* CONFIG_SAMV7_USBDEVHS_SCATTERGATHER */ /* 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 sam_usbdev_s)); priv->usbdev.ops = &g_devops; priv->usbdev.ep0 = &priv->eplist[EP0].ep; priv->epavail = SAM_EPSET_ALL & ~SAM_EP_BIT(EP0); priv->devstate = USBHS_DEVSTATE_SUSPENDED; priv->prevstate = USBHS_DEVSTATE_POWERED; /* Initialize the endpoint list */ for (epno = 0; epno < SAM_USBHS_NENDPOINTS; 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. */ priv->eplist[epno].ep.ops = &g_epops; priv->eplist[epno].dev = priv; priv->eplist[epno].ep.eplog = epno; /* We will use a maxpacket size for supported for each endpoint */ priv->eplist[epno].ep.maxpacket = SAM_USBHS_MAXPACKETSIZE(epno); } #if CONFIG_USBDEV_EP0_MAXSIZE < SAM_EP0_MAXPACKET /* Select a smaller endpoint size for EP0 if so configured */ priv->eplist[EP0].ep.maxpacket = CONFIG_USBDEV_EP0_MAXSIZE; #endif } /**************************************************************************** * * Name: sam_hw_shutdown * ****************************************************************************/ static void sam_hw_shutdown(struct sam_usbdev_s *priv) { uint32_t regval; priv->usbdev.speed = USB_SPEED_UNKNOWN; /* Disable all interrupts */ sam_putreg(USBHS_DEVINT_ALL, SAM_USBHS_DEVIDR); /* Clear all pending interrupt status */ sam_putreg(USBHS_DEVINT_ALL, SAM_USBHS_DEVICR); /* DETACH=1: USBHS is detached, UTMI transceiver is suspended. */ regval = sam_getreg(SAM_USBHS_DEVCTRL); regval |= USBHS_DEVCTRL_DETACH; sam_putreg(regval, SAM_USBHS_DEVCTRL); /* Freeze clocking */ regval = sam_getreg(SAM_USBHS_CTRL); regval |= USBHS_CTRL_FRZCLK; sam_putreg(regval, SAM_USBHS_CTRL); /* Disable USB hardware */ regval &= ~USBHS_CTRL_USBE; sam_putreg(regval, SAM_USBHS_CTRL); /* Disable clocking to the USBHS peripheral */ sam_usbhs_disableclk(); } /**************************************************************************** * * Name: sam_sw_shutdown * ****************************************************************************/ static void sam_sw_shutdown(struct sam_usbdev_s *priv) { } /**************************************************************************** * * Public Functions * ****************************************************************************/ /**************************************************************************** * * Name: arm_usbinitialize * Description: * Initialize the USB driver * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ void arm_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 sam_usbdev_s *priv = &g_usbhs; usbtrace(TRACE_DEVINIT, 0); /* Software initialization */ sam_sw_setup(priv); /* Power up and initialize USB controller. Interrupts from the USBHS * controller are initialized here, but will not be enabled at the NVIC * until the class driver is installed. */ sam_hw_setup(priv); /* Attach USB controller interrupt handlers. 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(SAM_IRQ_USBHS, sam_usbhs_interrupt, NULL) != 0) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION), (uint16_t)SAM_IRQ_USBHS); goto errout; } /* Enable USB controller interrupts at the NVIC. Interrupts are still * disabled at the USBHS. */ up_enable_irq(SAM_IRQ_USBHS); return; errout: arm_usbuninitialize(); } /**************************************************************************** * * Name: arm_usbuninitialize * Description: * Initialize the USB driver * Input Parameters: * None * * Returned Value: * None * ****************************************************************************/ void arm_usbuninitialize(void) { struct sam_usbdev_s *priv = &g_usbhs; irqstate_t flags; /* 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. */ flags = enter_critical_section(); usbtrace(TRACE_DEVUNINIT, 0); /* Disable and detach the USBHS IRQ */ up_disable_irq(SAM_IRQ_USBHS); irq_detach(SAM_IRQ_USBHS); if (priv->driver) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_DRIVERREGISTERED), 0); usbdev_unregister(priv->driver); } /* Put the hardware in an inactive state */ sam_hw_shutdown(priv); sam_sw_shutdown(priv); leave_critical_section(flags); } /**************************************************************************** * * Name: usbdev_register * * Description: * Register a USB device class driver. * The class driver's bind() method will be * called to bind it to a USB device driver. * ****************************************************************************/ int usbdev_register(struct usbdevclass_driver_s *driver) { /* For now there is only one USB controller, but we will always refer to * it using a pointer to make any future ports to multiple USB controllers * easier. */ struct sam_usbdev_s *priv = &g_usbhs; int ret; usbtrace(TRACE_DEVREGISTER, 0); DEBUGASSERT(driver != NULL && driver->ops->bind != NULL && driver->ops->unbind != NULL && driver->ops->disconnect != NULL && driver->ops->setup != NULL); DEBUGASSERT(priv->driver == NULL); /* First hook up the driver */ priv->driver = driver; /* Then bind the class driver */ ret = CLASS_BIND(driver, &priv->usbdev); if (ret != OK) { usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BINDFAILED), (uint16_t)-ret); priv->driver = NULL; } 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 sam_usbdev_s *priv = &g_usbhs; irqstate_t flags; usbtrace(TRACE_DEVUNREGISTER, 0); DEBUGASSERT(driver == priv->driver); /* Reset the hardware and cancel all requests. All requests must be * canceled while the class driver is still bound. */ flags = enter_critical_section(); /* Unbind the class driver */ CLASS_UNBIND(driver, &priv->usbdev); /* Disable USB controller interrupts (but keep them attached) */ up_disable_irq(SAM_IRQ_USBHS); /* Put the hardware in an inactive state. Then bring the hardware back up * in the initial state. This is essentially the same state as we were * in when arm_usbinitialize() was first called. */ sam_hw_shutdown(priv); sam_sw_shutdown(priv); sam_sw_setup(priv); sam_hw_setup(priv); /* Unhook the driver */ priv->driver = NULL; leave_critical_section(flags); return OK; } #endif /* CONFIG_USBDEV && CONFIG_SAMV7_USBDEVHS */