diff --git a/arch/arm/src/samdl/Kconfig b/arch/arm/src/samdl/Kconfig index 59e444d1c64..ec8507a048b 100644 --- a/arch/arm/src/samdl/Kconfig +++ b/arch/arm/src/samdl/Kconfig @@ -466,6 +466,10 @@ config SAMDL_HAVE_TC7 bool default n +config SAMDL_HAVE_USB + bool + default n + config SAMDL_AC bool "Analog Comparator" default n @@ -737,3 +741,25 @@ config SAMDL_I2C_REGDEBUG Enable very low-level register access debug. Depends on DEBUG_I2C. endif # SAMDL_HAVE_I2C + +if SAMDL_HAVE_USB + +config SAMDL_USB_ENABLE_PPEP + bool "Enable Ping-Pong Endpoints" + default n + ---help--- + To maximize throughput, an endpoint can be configured for ping-pong + operation. When this is done the input and output endpoint with the same + address are used in the same direction. The CPU or DMA Controller can + then read/write one data buffer while the USB module writes/reads from + the other buffer. This gives double buffered communication. + +config SAMDL_USB_REGDEBUG + bool "USB register-Level Debug" + default n + depends on DEBUG_USB_INFO + ---help--- + Enable very low-level register access debug. Depends on + CONFIG_DEBUG_USB_INFO. + +endif # SAMDL_HAVE_USB diff --git a/arch/arm/src/samdl/Make.defs b/arch/arm/src/samdl/Make.defs index 188ee7d40dd..71b156c6c77 100644 --- a/arch/arm/src/samdl/Make.defs +++ b/arch/arm/src/samdl/Make.defs @@ -98,3 +98,7 @@ endif ifeq ($(CONFIG_SAMDL_HAVE_I2C),y) CHIP_CSRCS += sam_i2c_master.c endif + +ifeq ($(CONFIG_SAMDL_USB),y) +CHIP_CSRCS += sam_usb.c +endif diff --git a/arch/arm/src/samdl/chip/saml_usb.h b/arch/arm/src/samdl/chip/saml_usb.h index 190df37d9f8..29ffadde9ae 100644 --- a/arch/arm/src/samdl/chip/saml_usb.h +++ b/arch/arm/src/samdl/chip/saml_usb.h @@ -37,6 +37,7 @@ * ********************************************************************************************/ + #ifndef __ARCH_ARM_SRC_SAMDL_CHIP_SAML_USB_H #define __ARCH_ARM_SRC_SAMDL_CHIP_SAML_USB_H @@ -53,6 +54,29 @@ /******************************************************************************************** * Pre-processor Definitions ********************************************************************************************/ + +/* Capabilities and characteristics of endpoints ********************************************/ + +/* EP EP BANKS EP SIZE EP TYPE + * --- --------- --------- --------- + * 0 2 64/1023 Control/Bulk/Iso/Interrupt + * 1 2 64/1023 Control/Bulk/Iso/Interrupt + * 2 2 64/1023 Control/Bulk/Iso/Interrupt + * 3 2 64/1023 Control/Bulk/Iso/Interrupt + * 4 2 64/1023 Control/Bulk/Iso/Interrupt + * 5 2 64/1023 Control/Bulk/Iso/Interrupt + * 6 2 64/1023 Control/Bulk/Iso/Interrupt + * 7 2 64/1023 Control/Bulk/Iso/Interrupt + */ + +#define SAM_USB_NENDPOINTS (8) +#define SAM_USB_MAXPACKETSIZE(ep) (64) +#define SAM_USB_NBANKS(ep) (2) +#define SAM_USB_CONTROL(ep) (true) +#define SAM_USB_BULK(ep) (true) +#define SAM_USB_ISOCHRONOUS(ep) (true) +#define SAM_USB_INTERRUPT(ep) (true) + /* USB register offsets ********************************************************************/ /* Common USB Device/Host Register Offsets */ diff --git a/arch/arm/src/samdl/sam_usb.c b/arch/arm/src/samdl/sam_usb.c new file mode 100644 index 00000000000..f878b322d06 --- /dev/null +++ b/arch/arm/src/samdl/sam_usb.c @@ -0,0 +1,4134 @@ +/***************************************************************************** + * arch/arm/src/samdl/sam_usb.h + * + * Copyright (C) 2015 Filament - www.filament.com + * Copyright (C) 2015 Offcode Ltd. All rights reserved. + * Author: Janne Rosberg + * + * This driver is derived from the SAM34 UDP driver: + * Copyright (C) 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * USB core features: + * + * - Compatible with the USB 2.1 specification + * - USB Embedded Host and Device mode + * - Supports full (12Mbit/s) and low (1.5Mbit/s) speed communication + * - Supports Link Power Management (LPM-L1) protocol + * - On-chip transceivers with built-in pull-ups and pull-downs + * - On-Chip USB serial resistors + * - 1kHz SOF clock available on external pin + * + * Device mode + * - Supports 8 IN endpoints and 8 OUT endpoints + * – No endpoint size limitations + * – Built-in DMA with multi-packet and dual bank for all endpoints + * – Supports feedback endpoint + * – Supports crystal less clock + * + * Host mode + * - Supports 8 physical pipes + * – No pipe size limitations + * – Supports multiplexed virtual pipe on one physical pipe to allow an + * unlimited USB tree + * – Built-in DMA with multi-packet support and dual bank for all pipes + * – Supports feedback endpoint + * – Supports the USB 2.0 Phase-locked SOFs feature + * + ****************************************************************************/ + + +/**************************************************************************** + * WIP NOTES: + * + * DS 38.1 (859) + * To maximize throughput, an endpoint can be configured for ping-pong + * operation. + * When this is done the input and output endpoint with the same address are + * used in the same direction. The CPU or DMA Controller can then read/write + * one data buffer while the USB module writes/reads from the other buffer. + * This gives double buffered communication. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "up_arch.h" +#include "up_internal.h" +#include "saml_periphclks.h" +#include "sam_gclk.h" +#include "chip.h" +#include "sam_port.h" +#include "sam_pinmap.h" +#include "sam_usb.h" + +#if defined(CONFIG_USBHOST) && defined(CONFIG_SAMDL_USB) +# error USBHOST mode not yet implemented! +#endif + +#if defined(CONFIG_USBDEV) && defined(CONFIG_SAMDL_USB) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifndef CONFIG_USBDEV_EP0_MAXSIZE +# define CONFIG_USBDEV_EP0_MAXSIZE 64 +#endif + +/* Extremely detailed register debug that you would normally never want + * enabled. + */ + +#ifndef CONFIG_DEBUG +# undef CONFIG_SAMDL_USB_REGDEBUG +#endif + +/* Driver Definitions *******************************************************/ + +#define EP0 (0) +#define SAM_EPSET_ALL (0xff) /* All endpoints */ +#define SAM_EPSET_NOTEP0 (0xfe) /* All endpoints except EP0 */ +#define SAM_EP_BIT(ep) (1 << (ep)) +#define SAM_EP0_MAXPACKET (CONFIG_USBDEV_EP0_MAXSIZE) /* EP0 Max. packet size */ +#define SAM_MAX_MULTIPACKET_SIZE (0x3fff) + +/* Request queue operations *************************************************/ + +#define sam_rqempty(q) ((q)->head == NULL) +#define sam_rqpeek(q) ((q)->head) + +/* 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_DRIVER 0x0010 +#define SAM_TRACEERR_DRIVERREGISTERED 0x0011 +#define SAM_TRACEERR_EP0SETUPOUTSIZE 0x0012 +#define SAM_TRACEERR_EP0SETUPSTALLED 0x0013 +#define SAM_TRACEERR_EPOUTNULLPACKET 0x0014 +#define SAM_TRACEERR_EPRESERVE 0x0015 +#define SAM_TRACEERR_INVALIDCTRLREQ 0x0016 +#define SAM_TRACEERR_INVALIDPARMS 0x0017 +#define SAM_TRACEERR_IRQREGISTRATION 0x0018 +#define SAM_TRACEERR_NOTCONFIGURED 0x0019 +#define SAM_TRACEERR_REQABORTED 0x001a +#define SAM_TRACEERR_RXDATABKERR 0x001b +#define SAM_TRACEERR_TXCOMPERR 0x001c +#define SAM_TRACEERR_UNSUPPEPTYPE 0x001d + +/* Trace interrupt codes */ + +#define SAM_TRACEINTID_INTERRUPT 0x0001 +#define SAM_TRACEINTID_PENDING 0x0002 +#define SAM_TRACEINTID_PENDING_EP 0x0003 +#define SAM_TRACEINTID_SUSPEND 0x0004 +#define SAM_TRACEINTID_SOF 0x0005 +#define SAM_TRACEINTID_EORST 0x0006 +#define SAM_TRACEINTID_WAKEUP 0x0007 +#define SAM_TRACEINTID_EORSM 0x0008 +#define SAM_TRACEINTID_UPRSM 0x0009 +#define SAM_TRACEINTID_RAMACER 0x000a +#define SAM_TRACEINTID_LPMNYET 0x000b +#define SAM_TRACEINTID_LPMSUSP 0x000c +#define SAM_TRACEINTID_EPNO 0x000d +#define SAM_TRACEINTID_EPINTFLAGS 0x000e +#define SAM_TRACEINTID_EPTRCPT0 0x000f +#define SAM_TRACEINTID_EPTRCPT1 0x0010 +#define SAM_TRACEINTID_EPTRFAIL0 0x0011 +#define SAM_TRACEINTID_EPTRFAIL1 0x0012 +#define SAM_TRACEINTID_EPRXSTP 0x0013 +#define SAM_TRACEINTID_EPSTALL0 0x0014 +#define SAM_TRACEINTID_EPSTALL1 0x0015 +#define SAM_TRACEINTID_EPINQEMPTY 0x0016 +#define SAM_TRACEINTID_EPOUTQEMPTY 0x0017 +#define SAM_TRACEINTID_EP0SETUPOUT 0x0018 +#define SAM_TRACEINTID_EP0SETUPIN 0x0019 +#define SAM_TRACEINTID_EP0SETUPSETADDRESS 0x001a +#define SAM_TRACEINTID_NOSTDREQ 0x001b +#define SAM_TRACEINTID_GETSTATUS 0x001c +#define SAM_TRACEINTID_DEVGETSTATUS 0x001d +#define SAM_TRACEINTID_IFGETSTATUS 0x001e +#define SAM_TRACEINTID_CLEARFEATURE 0x001f +#define SAM_TRACEINTID_SETFEATURE 0x0020 +#define SAM_TRACEINTID_GETSETDESC 0x0021 +#define SAM_TRACEINTID_GETCONFIG 0x0022 +#define SAM_TRACEINTID_SETCONFIG 0x0023 +#define SAM_TRACEINTID_GETSETIF 0x0024 +#define SAM_TRACEINTID_SYNCHFRAME 0x0025 +#define SAM_TRACEINTID_DISPATCH 0x0026 +#define SAM_TRACEINTID_ADDRESSED 0x0027 +#define SAM_TRACEINTID_EPCONF 0x0028 +#define SAM_TRACEINTID_EPINTEN 0x0029 +#define SAM_TRACEINTID_EP0WRSTATUS 0x002a +#define SAM_TRACEINTID_EPTRCPT0_LEN 0x002b + +/* Ever-present MIN and MAX macros */ + +#ifndef MIN +# define MIN(a,b) (a < b ? a : b) +#endif + +#ifndef MAX +# define MAX(a,b) (a > b ? a : b) +#endif + +/* Byte ordering in host-based values */ + +#ifdef CONFIG_ENDIAN_BIG +# define LSB 1 +# define MSB 0 +#else +# define LSB 0 +# define MSB 1 +#endif + +/**************************************************************************** + * Private Type Definitions + ****************************************************************************/ + +/* State of an endpoint */ + +enum sam_epstate_e +{ + /* --- All Endpoints --- */ + USB_EPSTATE_DISABLED = 0, /* Endpoint is disabled */ + USB_EPSTATE_STALLED, /* Endpoint is stalled */ + USB_EPSTATE_IDLE, /* Endpoint is idle (i.e. ready for transmission) */ + USB_EPSTATE_SENDING, /* Endpoint is sending data */ + USB_EPSTATE_RXSTOPPED, /* OUT endpoint is stopped waiting for a read request */ + /* --- Endpoint 0 Only --- */ + USB_EPSTATE_EP0DATAOUT, /* Endpoint 0 is receiving SETUP OUT data */ + USB_EPSTATE_EP0STATUSIN, /* Endpoint 0 is sending SETUP status */ + USB_EPSTATE_EP0ADDRESS /* Address change is pending completion of status */ +}; + +/* The overall state of the device */ + +enum sam_devstate_e +{ + USB_DEVSTATE_SUSPENDED = 0, /* The device is currently suspended */ + USB_DEVSTATE_POWERED, /* Host is providing +5V through the USB cable */ + USB_DEVSTATE_DEFAULT, /* Device has been reset */ + USB_DEVSTATE_ADDRESSED, /* The device has been given an address on the bus */ + USB_DEVSTATE_CONFIGURED /* A valid configuration has been selected. */ +}; + +/* The result of EP0 SETUP processing */ + +enum sam_ep0setup_e +{ + USB_EP0SETUP_SUCCESS = 0, /* The SETUP was handle without incident */ + USB_EP0SETUP_DISPATCHED, /* The SETUP was forwarded to the class driver */ + USB_EP0SETUP_ADDRESS, /* A new device address is pending */ + USB_EP0SETUP_STALL /* An error occurred */ +}; + +union wb_u +{ + uint16_t w; + uint8_t b[2]; +}; + +/* A container for a request so that the request may 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 tansmitting or + * number of RX bytes we are waiting */ +}; + +/* 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 */ + + /* SAMDL-specific fields */ + + struct sam_usbdev_s *dev; /* Reference to private driver data */ + struct sam_rqhead_s reqq; /* Read/write request queue */ + struct sam_rqhead_s pendq; /* Write requests pending stall sent */ + struct usbdev_epdesc_s *descb[2]; /* Pointers to this endpoints descriptors */ + volatile uint8_t epstate; /* State of the endpoint (see enum sam_epstate_e) */ + uint8_t stalled:1; /* true: Endpoint is stalled */ + uint8_t pending:1; /* true: IN Endpoint stall is pending */ + uint8_t halted:1; /* true: Endpoint feature halted */ + uint8_t zlpsent:1; /* Zero length packet has been sent */ + uint8_t txbusy:1; /* Write request queue is busy (recursion avoidance kludge) */ + uint8_t rxactive:1; /* read request is active (for top of queue) */ +}; + +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 struct sam_usbdev_s. + */ + + struct usbdev_s usbdev; + + /* The bound device class driver */ + + struct usbdevclass_driver_s *driver; + + /* USB-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 devaddr; /* Assigned device address */ + uint8_t selfpowered:1; /* 1: Device is self powered */ + uint16_t epavail; /* Bitset of available endpoints */ + + /* The endpoint list */ + + struct sam_ep_s eplist[SAM_USB_NENDPOINTS]; + + /* Endpoint descriptors 2 banks for each endpoint */ + + struct usbdev_epdesc_s ep_descriptors[SAM_USB_NENDPOINTS * 2]; + + /* 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 data 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_SAMDL_USB_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_getreg32(uintptr_t regaddr); +static void sam_putreg32(uint32_t regval, uintptr_t regaddr); +static uint32_t sam_getreg16(uintptr_t regaddr); +static void sam_putreg16(uint16_t regval, uintptr_t regaddr); +static uint32_t sam_getreg8(uintptr_t regaddr); +static void sam_putreg8(uint8_t regval, uintptr_t regaddr); +static void sam_dumpep(struct sam_usbdev_s *priv, uint8_t epno); +#else +static inline uint32_t sam_getreg32(uintptr_t regaddr); +static inline void sam_putreg32(uint32_t regval, uintptr_t regaddr); +static inline uint32_t sam_getreg16(uintptr_t regaddr); +static inline void sam_putreg16(uint16_t regval, uintptr_t regaddr); +static inline uint32_t sam_getreg8(uintptr_t regaddr); +static inline void sam_putreg8(uint8_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); + +/* 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 void sam_req_complete(struct sam_ep_s *privep, int16_t result); +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 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_ctrlread(struct sam_usbdev_s *priv); +static void sam_ep0_wrstatus(struct sam_usbdev_s *priv, + const uint8_t *buffer, size_t buflen); +static void sam_ep0_dispatch(struct sam_usbdev_s *priv); +static void sam_setdevaddr(struct sam_usbdev_s *priv, uint8_t value); +static void sam_ep0_setup(struct sam_usbdev_s *priv); +static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno); +static int sam_usb_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 int sam_ep_stall(struct sam_ep_s *privep); +static int sam_ep_resume(struct sam_ep_s *privep); +static inline struct sam_ep_s * + sam_ep_reserve(struct sam_usbdev_s *priv, uint8_t epset); +static inline void + sam_ep_unreserve(struct sam_usbdev_s *priv, + struct sam_ep_s *privep); +static inline bool + sam_ep_reserved(struct sam_usbdev_s *priv, int epno); +static int sam_ep_configure_internal(struct sam_ep_s *privep, + const struct usb_epdesc_s *desc); + +/* 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 *); +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_stallresume(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_enableclks(void); +static void sam_disableclks(void); +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_usbd; + +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_stallresume, +}; + +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, + // REVISIT: + .mxpacketsize = {64, 0}, + .interval = 0 +}; + +/* Device error strings that may be enabled for more desciptive 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_DRIVER), + TRACE_STR(SAM_TRACEERR_DRIVERREGISTERED), + TRACE_STR(SAM_TRACEERR_EP0SETUPOUTSIZE), + TRACE_STR(SAM_TRACEERR_EP0SETUPSTALLED), + TRACE_STR(SAM_TRACEERR_EPOUTNULLPACKET), + TRACE_STR(SAM_TRACEERR_EPRESERVE), + TRACE_STR(SAM_TRACEERR_INVALIDCTRLREQ), + TRACE_STR(SAM_TRACEERR_INVALIDPARMS), + TRACE_STR(SAM_TRACEERR_IRQREGISTRATION), + TRACE_STR(SAM_TRACEERR_NOTCONFIGURED), + TRACE_STR(SAM_TRACEERR_REQABORTED), + TRACE_STR(SAM_TRACEERR_RXDATABKERR), + TRACE_STR(SAM_TRACEERR_TXCOMPERR), + TRACE_STR(SAM_TRACEERR_UNSUPPEPTYPE), + TRACE_STR_END +}; +#endif + + +/* Interrupt event strings that may be enabled for more desciptive USB trace + * output. + */ + +#ifdef CONFIG_USBDEV_TRACE_STRINGS +const struct trace_msg_t g_usb_trace_strings_intdecode[] = +{ + TRACE_STR(SAM_TRACEINTID_INTERRUPT), + TRACE_STR(SAM_TRACEINTID_PENDING), + TRACE_STR(SAM_TRACEINTID_PENDING_EP), + TRACE_STR(SAM_TRACEINTID_SUSPEND), + TRACE_STR(SAM_TRACEINTID_SOF), + TRACE_STR(SAM_TRACEINTID_EORST), + TRACE_STR(SAM_TRACEINTID_WAKEUP), + TRACE_STR(SAM_TRACEINTID_EORSM), + TRACE_STR(SAM_TRACEINTID_UPRSM), + TRACE_STR(SAM_TRACEINTID_RAMACER), + TRACE_STR(SAM_TRACEINTID_LPMNYET), + TRACE_STR(SAM_TRACEINTID_LPMSUSP), + + TRACE_STR(SAM_TRACEINTID_EPNO), + TRACE_STR(SAM_TRACEINTID_EPINTFLAGS), + TRACE_STR(SAM_TRACEINTID_EPTRCPT0), + TRACE_STR(SAM_TRACEINTID_EPTRCPT1), + TRACE_STR(SAM_TRACEINTID_EPTRFAIL0), + TRACE_STR(SAM_TRACEINTID_EPTRFAIL1), + TRACE_STR(SAM_TRACEINTID_EPRXSTP), + TRACE_STR(SAM_TRACEINTID_EPSTALL0), + TRACE_STR(SAM_TRACEINTID_EPSTALL1), + + TRACE_STR(SAM_TRACEINTID_EPINQEMPTY), + TRACE_STR(SAM_TRACEINTID_EPOUTQEMPTY), + TRACE_STR(SAM_TRACEINTID_EP0SETUPOUT), + TRACE_STR(SAM_TRACEINTID_EP0SETUPIN), + TRACE_STR(SAM_TRACEINTID_EP0SETUPSETADDRESS), + TRACE_STR(SAM_TRACEINTID_NOSTDREQ), + TRACE_STR(SAM_TRACEINTID_GETSTATUS), + TRACE_STR(SAM_TRACEINTID_DEVGETSTATUS), + TRACE_STR(SAM_TRACEINTID_IFGETSTATUS), + TRACE_STR(SAM_TRACEINTID_CLEARFEATURE), + TRACE_STR(SAM_TRACEINTID_SETFEATURE), + TRACE_STR(SAM_TRACEINTID_GETSETDESC), + TRACE_STR(SAM_TRACEINTID_GETCONFIG), + TRACE_STR(SAM_TRACEINTID_SETCONFIG), + TRACE_STR(SAM_TRACEINTID_GETSETIF), + TRACE_STR(SAM_TRACEINTID_SYNCHFRAME), + + TRACE_STR(SAM_TRACEINTID_DISPATCH), + TRACE_STR(SAM_TRACEINTID_ADDRESSED), + TRACE_STR(SAM_TRACEINTID_EPCONF), + TRACE_STR(SAM_TRACEINTID_EPINTEN), + TRACE_STR(SAM_TRACEINTID_EP0WRSTATUS), + TRACE_STR(SAM_TRACEINTID_EPTRCPT0_LEN), + + /* + TRACE_STR(SAM_TRACEINTID_ADDRESSED), + TRACE_STR(SAM_TRACEINTID_CLEARFEATURE), + TRACE_STR(SAM_TRACEINTID_RXSUSP), + TRACE_STR(SAM_TRACEINTID_DEVGETSTATUS), + TRACE_STR(SAM_TRACEINTID_DISPATCH), + TRACE_STR(SAM_TRACEINTID_ENDBUSRES), + TRACE_STR(SAM_TRACEINTID_EP), + TRACE_STR(SAM_TRACEINTID_EP0SETUPIN), + TRACE_STR(SAM_TRACEINTID_EP0SETUPOUT), + TRACE_STR(SAM_TRACEINTID_EP0SETUPSETADDRESS), + TRACE_STR(SAM_TRACEINTID_EPGETSTATUS), + TRACE_STR(SAM_TRACEINTID_EPINQEMPTY), + 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_SOF), + TRACE_STR(SAM_TRACEINTID_NOSTDREQ), + TRACE_STR(SAM_TRACEINTID_RXDATABK0), + TRACE_STR(SAM_TRACEINTID_RXDATABK1), + TRACE_STR(SAM_TRACEINTID_RXSETUP), + TRACE_STR(SAM_TRACEINTID_SETCONFIG), + TRACE_STR(SAM_TRACEINTID_SETFEATURE), + TRACE_STR(SAM_TRACEINTID_STALLSNT), + TRACE_STR(SAM_TRACEINTID_SYNCHFRAME), + TRACE_STR(SAM_TRACEINTID_TXCOMP), + TRACE_STR(SAM_TRACEINTID_UPSTRRES), + TRACE_STR(SAM_TRACEINTID_WAKEUP), + */ + TRACE_STR_END +}; +#endif + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Register Operations + ****************************************************************************/ + +/******************************************************************************* + * Name: sam_printreg + * + * Description: + * Print the SAMDL USB register access + * + *******************************************************************************/ + +#ifdef CONFIG_SAMDL_USB_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 SAMDL + * USB registers + * + *******************************************************************************/ + +#ifdef CONFIG_SAMDL_USB_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_getreg32 + * + * Description: + * Get the contents of an 32-bit SAMDL USB register + * + ****************************************************************************/ + +#ifdef CONFIG_SAMDL_USB_REGDEBUG +static uint32_t sam_getreg32(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_getreg32(uintptr_t regaddr) +{ + return getreg32(regaddr); +} +#endif + +/**************************************************************************** + * Name: sam_putreg32 + * + * Description: + * Set the contents of an 32-bit SAMDL USB register to a value + * + ****************************************************************************/ + +#ifdef CONFIG_SAMDL_USB_REGDEBUG +static void sam_putreg32(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_putreg32(uint32_t regval, uint32_t regaddr) +{ + putreg32(regval, regaddr); +} +#endif + +/**************************************************************************** + * Name: sam_getreg16 + * + * Description: + * Get the contents of an 16-bit SAMDL USB register + * + ****************************************************************************/ + +#ifdef CONFIG_SAMDL_USB_REGDEBUG +static uint32_t sam_getreg16(uintptr_t regaddr) +{ + /* Read the value from the register */ + + uint32_t regval = getreg16(regaddr); + + /* Check if we need to print this value */ + + sam_checkreg(regaddr, regval, false); + return regval; +} +#else +static inline uint32_t sam_getreg16(uintptr_t regaddr) +{ + return getreg16(regaddr); +} +#endif + +/**************************************************************************** + * Name: sam_putreg16 + * + * Description: + * Set the contents of an 16-bit SAMDL USB register to a value + * + ****************************************************************************/ + +#ifdef CONFIG_SAMDL_USB_REGDEBUG +static void sam_putreg16(uint16_t regval, uintptr_t regaddr) +{ + /* Check if we need to print this value */ + + sam_checkreg(regaddr, regval, true); + + /* Write the value */ + + putreg16(regval, regaddr); +} +#else +static inline void sam_putreg16(uint16_t regval, uint32_t regaddr) +{ + putreg16(regval, regaddr); +} +#endif + +/**************************************************************************** + * Name: sam_getreg8 + * + * Description: + * Get the contents of an 8-bit SAMDL USB register + * + ****************************************************************************/ + +#ifdef CONFIG_SAMDL_USB_REGDEBUG +static uint32_t sam_getreg8(uintptr_t regaddr) +{ + /* Read the value from the register */ + + uint32_t regval = getreg8(regaddr); + + /* Check if we need to print this value */ + + sam_checkreg(regaddr, regval, false); + return regval; +} +#else +static inline uint32_t sam_getreg8(uintptr_t regaddr) +{ + return getreg8(regaddr); +} +#endif + +/**************************************************************************** + * Name: sam_putreg8 + * + * Description: + * Set the contents of an 8-bit SAMDL USB register to a value + * + ****************************************************************************/ + +#ifdef CONFIG_SAMDL_USB_REGDEBUG +static void sam_putreg8(uint8_t regval, uintptr_t regaddr) +{ + /* Check if we need to print this value */ + + sam_checkreg(regaddr, regval, true); + + /* Write the value */ + + putreg8(regval, regaddr); +} +#else +static inline void sam_putreg8(uint8_t regval, uint32_t regaddr) +{ + putreg8(regval, regaddr); +} +#endif + + +/**************************************************************************** + * Name: sam_dumpep + ****************************************************************************/ + +#if defined(CONFIG_SAMDL_USB_REGDEBUG) && defined(CONFIG_DEBUG) +static void sam_dumpep(struct sam_usbdev_s *priv, uint8_t epno) +{ + /* Global Registers */ + + uinfo("Global Registers:\n"); + uinfo(" CTRLB: %04x\n", sam_getreg16(SAM_USBDEV_CTRLB)); + uinfo(" FNUM: %04x\n", sam_getreg16(SAM_USBDEV_FNUM)); + uinfo(" DADD: %02x\n", sam_getreg8(SAM_USBDEV_DADD)); + uinfo(" INTEN: %04x\n", sam_getreg16(SAM_USBDEV_INTENSET)); + uinfo(" STATUS: %02x\n", sam_getreg8(SAM_USBDEV_STATUS)); + uinfo(" INTFLAG: %04x\n", sam_getreg16(SAM_USBDEV_INTFLAG)); + uinfo(" EPCFG[%d]: %02x\n", epno, sam_getreg8(SAM_USBDEV_EPCFG(epno))); + uinfo("EPSTATUS[%d]: %02x\n", epno, sam_getreg8(SAM_USBDEV_EPSTATUS(epno))); +} +#endif + +/**************************************************************************** + * 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_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; + + /* Callback to the request completion handler */ + + privreq->flink = NULL; + privreq->req.callback(&privep->ep, &privreq->req); + + /* mark endpoint ready to next transmission */ + + privep->epstate = USB_EPSTATE_IDLE; + privep->zlpsent = false; + } +} + +/**************************************************************************** + * Name: sam_req_wrsetup + * + * Description: + * Process the next queued write request. + * + ****************************************************************************/ + +static void sam_req_wrsetup(struct sam_usbdev_s *priv, + struct sam_ep_s *privep, + struct sam_req_s *privreq) +{ + const uint8_t *buf; + uint8_t epno; + int nbytes; + uint32_t packetsize; + + /* 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(multi) or all of the remaining data in + * the request. + */ + + if (nbytes >= SAM_MAX_MULTIPACKET_SIZE) + { + nbytes = SAM_MAX_MULTIPACKET_SIZE; + } + + /* 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 start 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; + + /* setup TX transfer using ep configured maxpacket size */ + + priv->eplist[epno].descb[1]->addr = (uint32_t) buf; + packetsize = priv->eplist[epno].descb[1]->pktsize; + packetsize &= ~USBDEV_PKTSIZE_BCNT_MASK; + packetsize &= ~USBDEV_PKTSIZE_MPKTSIZE_MASK; + packetsize |= USBDEV_PKTSIZE_BCNT(nbytes); + + /* also set automatic ZLP sending if requested on req */ + + if (privreq->req.flags & USBDEV_REQFLAGS_NULLPKT) + { + packetsize |= USBDEV_PKTSIZE_AUTOZLP; + } + + priv->eplist[epno].descb[1]->pktsize = packetsize; + + /* Indicate that we are in the sending state + * This indication will be need in interrupt processing (TRCPT1) + * in order to properly terminate the request. + */ + + privep->epstate = USB_EPSTATE_SENDING; + + /* Set BK1RDY to notify the USB hardware that TX data is ready on + * descriptor bank1. We will be notified that the descriptor has been + * transmitted by the USB device when TRCPT1 in the endpoint's EPINTFLAG + * register has been set. + */ + + sam_putreg8(USBDEV_EPSTATUS_BK1RDY, SAM_USBDEV_EPSTATUSSET(epno)); +} + +/**************************************************************************** + * 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 TRCPT1 interrupt handling when the current Tx transfer + * completes + * (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; + 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 (epstate should be IDLE). + */ + + DEBUGASSERT(privep->epstate == USB_EPSTATE_IDLE); + while (privep->epstate == USB_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), epno); + + /* Was there a pending endpoint stall? */ + + if (privep->pending) + { + /* Yes... stall the endpoint now */ + + (void)sam_ep_stall(privep); + } + + return -ENOENT; + } + + uinfo("epno=%d req=%p: len=%d xfrd=%d inflight=%d\n", + epno, privreq, privreq->req.len, privreq->req.xfrd, + privreq->inflight); + + /* 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) + { + /* Perform the write operation. epstate will become SENDING. */ + + 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. + * Or + * (2) called with a request packet that has len == 0 + * + * len == 0 means that it is requested to send a zero length packet + * required by protocoll + */ + + else if ((privreq->req.len == 0) && !privep->zlpsent) + { + /* If we get here, we requested to send the zero length packet now. + */ + + privep->epstate = USB_EPSTATE_SENDING; + privep->zlpsent = true; + privreq->inflight = 0; + + usbtrace(TRACE_WRITE(epno), 0); + + /* setup 0 length TX transfer */ + + priv->eplist[0].descb[1]->addr = (uint32_t) &priv->ep0out[0]; + priv->eplist[0].descb[1]->pktsize &= ~USBDEV_PKTSIZE_BCNT_MASK; + priv->eplist[0].descb[1]->pktsize &= ~USBDEV_PKTSIZE_MPKTSIZE_MASK; + priv->eplist[0].descb[1]->pktsize |= USBDEV_PKTSIZE_BCNT(0); + sam_putreg8(USBDEV_EPSTATUS_BK1RDY, SAM_USBDEV_EPSTATUSSET(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 == USB_EPSTATE_IDLE) + { + /* Return the write request to the class driver. Set the txbusy + * bit to prevent being called recursively from any new submission + * generated by returning the write request. + */ + + usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd); + DEBUGASSERT(privreq->req.len == privreq->req.xfrd); + + privep->txbusy = true; + sam_req_complete(privep, OK); + privep->txbusy = false; + } + } + + return OK; +} + +/**************************************************************************** + * Name: sam_req_read + * + * Description: + * Complete the last read request. full or partial. + * The USB core has transferred the data to user request buffer. + * return the completed read request to the class + * implementation, and try to start the next queued read request. + * + * REVISIT: + * This function is called in one of two contexts: + * The normal case is + * (1) When the endpoint is IDLE and a new read request is submitted + * (with interrupts disabled), + * (2) from interrupt handling when the current RX transfer completes. + * But there is also a special case + * (3) when the OUT endpoint is stopped because there are no + * available read requests. + * + * 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 the transfer completes, the 'recvsize' is the number of bytes + * ready on req->buffer. + * + ****************************************************************************/ + +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 packetsize; + int epno; + + DEBUGASSERT(priv && privep && privep->epstate == USB_EPSTATE_IDLE); + + /* Check the request from the head of the endpoint request queue */ + + epno = USB_EPNO(privep->ep.eplog); + do + { + /* Peek at the next read request in the request queue */ + + privreq = sam_rqpeek(&privep->reqq); + if (!privreq) + { + /* No read request to receive data */ + + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPOUTQEMPTY), epno); + + /* When no read requests are pending no EP descriptors are set to + * ready. HW sends NAK to host if it tries to send something. + */ + + privep->epstate = USB_EPSTATE_RXSTOPPED; + return -ENOENT; + } + + uinfo("EP%d: req.len=%d xfrd=%d recvsize=%d\n", + epno, privreq->req.len, privreq->req.xfrd, recvsize); + + /* 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); + privreq = NULL; + } + + /* complete read request with available data */ + + if ((privreq->inflight) && (recvsize != 0)) + { + usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), recvsize); + + /* Update the total number of bytes transferred */ + + privreq->req.xfrd += recvsize; + privreq->inflight = 0; + usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd); + sam_req_complete(privep, OK); + + /* need to set recvsize to zero. When calling sam_req_complete() + * class driver could call submit() again and we have new request + * ready on next while() loop. + */ + + privep->rxactive = false; + recvsize = 0; + privreq = NULL; + } + } + while (privreq == NULL); + + DEBUGASSERT(recvsize == 0); + + /* activate new read request from queue */ + + privep->rxactive = true; + privreq->req.xfrd = 0; + privreq->inflight = privreq->req.len; + priv->eplist[epno].descb[0]->addr = (uint32_t) privreq->req.buf; + packetsize = priv->eplist[epno].descb[0]->pktsize; + packetsize &= ~USBDEV_PKTSIZE_BCNT_MASK; + packetsize &= ~USBDEV_PKTSIZE_MPKTSIZE_MASK; + packetsize |= USBDEV_PKTSIZE_MPKTSIZE(privreq->inflight); + sam_putreg8(USBDEV_EPSTATUS_BK0RDY, SAM_USBDEV_EPSTATUSCLR(epno)); + + return OK; +} + +/**************************************************************************** + * Name: sam_req_cancel + ****************************************************************************/ + +static void sam_req_cancel(struct sam_ep_s *privep, int16_t result) +{ + /* 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); + } +} + +/**************************************************************************** + * 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) +{ + uint8_t epconf; + uint16_t maxpacket; + uint8_t epno; + uint8_t eptype; + uint8_t intflags; + uint32_t ephwsize; + bool dirin; + + DEBUGASSERT(privep && privep->dev && desc); + + 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); + maxpacket = GETUINT16(desc->mxpacketsize); + + if (maxpacket <= 8) + { + ephwsize = USBDEV_PKTSIZE_SIZE_8B; + } + else if (maxpacket <= 16) + { + ephwsize = USBDEV_PKTSIZE_SIZE_16B; + } + else if (maxpacket <= 32) + { + ephwsize = USBDEV_PKTSIZE_SIZE_32B; + } + else if (maxpacket <= 64) + { + ephwsize = USBDEV_PKTSIZE_SIZE_64B; + } + else if ((maxpacket <= 128) && (eptype == USB_EP_ATTR_XFER_ISOC)) + { + ephwsize = USBDEV_PKTSIZE_SIZE_128B; + } + else if ((maxpacket <= 256) && (eptype == USB_EP_ATTR_XFER_ISOC)) + { + ephwsize = USBDEV_PKTSIZE_SIZE_256B; + } + else if ((maxpacket <= 512) && (eptype == USB_EP_ATTR_XFER_ISOC)) + { + ephwsize = USBDEV_PKTSIZE_SIZE_512B; + } + else if ((maxpacket <= 1023) && (eptype == USB_EP_ATTR_XFER_ISOC)) + { + ephwsize = USBDEV_PKTSIZE_SIZE_1023B; + } + else + { + return -EINVAL; + } + + /* update endpoint descriptors to correct size */ + + privep->descb[0]->pktsize = ephwsize; + privep->descb[1]->pktsize = ephwsize; + + /* Initialize the endpoint structure */ + + privep->ep.eplog = desc->addr; /* Includes direction */ + privep->ep.maxpacket = maxpacket; + privep->epstate = USB_EPSTATE_IDLE; + + /* get current config IN and OUT */ + + epconf = 0x00; + sam_putreg8(0x00, SAM_USBDEV_EPCFG(epno)); + //epconf = sam_getreg8(SAM_USBDEV_EPCFG(epno)); + //uinfo("BEFORE: epconf=0x%X\n", epconf); + + if (dirin) + { + /* disable bank1 (IN) */ + + //epconf &= ~USBDEV_EPCFG_EPTYPE1_MASK; + intflags = USBDEV_EPINT_TRCPT1 | /*USBDEV_EPINT_TRFAIL1 |*/ USBDEV_EPINT_STALL1; + } + else + { + /* disable bank0 (OUT) */ + //epconf &= ~USBDEV_EPCFG_EPTYPE0_MASK; + intflags = USBDEV_EPINT_TRCPT0 | /*USBDEV_EPINT_TRFAIL0 |*/ USBDEV_EPINT_STALL0; + } + + /* write back disabled config */ + +//sam_putreg8(epconf, SAM_USBDEV_EPCFG(epno)); + sam_putreg8(0x7e, SAM_USBDEV_EPINTENCLR(epno)); + sam_putreg8(0x7e, SAM_USBDEV_EPINTFLAG(epno)); +//sam_putreg8(intflags, SAM_USBDEV_EPINTENCLR(epno)); +//sam_putreg8(intflags, SAM_USBDEV_EPINTFLAG(epno)); + + /* re-configure and enable the endpoint */ + + //epconf = SAM_USBDEV_EPCFG(epno); + switch (eptype) + { + case USB_EP_ATTR_XFER_CONTROL: + { + epconf = USBDEV_EPCCFG_EPTYPE0_CTRLOUT | USBDEV_EPCCFG_EPTYPE1_CTRLIN; + + /* Also enable IN interrupts */ + + intflags = USBDEV_EPINT_TRCPT0 | /* USBDEV_EPINT_TRFAIL1 |*/ + USBDEV_EPINT_STALL0; + intflags |= USBDEV_EPINT_TRCPT1 | /* USBDEV_EPINT_TRFAIL0 |*/ + USBDEV_EPINT_STALL1; + intflags |= USBDEV_EPINT_RXSTP; + sam_putreg8(USBDEV_EPSTATUS_BK0RDY, SAM_USBDEV_EPSTATUSSET(0)); + sam_putreg8(USBDEV_EPSTATUS_BK1RDY, SAM_USBDEV_EPSTATUSCLR(0)); + } + break; + +#ifdef CONFIG_USBDEV_ISOCHRONOUS + case USB_EP_ATTR_XFER_ISOC: + if (dirin) + { + epconf |= USBDEV_EPCCFG_EPTYPE1_ISOCIN; + } + else + { + epconf |= USBDEV_EPCCFG_EPTYPE0_ISOCOUT; + } + break; +#endif + + case USB_EP_ATTR_XFER_BULK: + if (dirin) + { + epconf |= USBDEV_EPCCFG_EPTYPE1_BULKIN; + } + else + { + epconf |= USBDEV_EPCCFG_EPTYPE0_BULKOUT; + } + break; + + case USB_EP_ATTR_XFER_INT: + if (dirin) + { + epconf |= USBDEV_EPCCFG_EPTYPE1_INTIN; + } + else + { + epconf |= USBDEV_EPCCFG_EPTYPE0_INTOUT; + } + break; + + default: + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEPTYPE), + eptype >> USB_EP_ATTR_XFERTYPE_SHIFT); + return -EINVAL; + } + + + sam_putreg8(epconf, SAM_USBDEV_EPCFG(epno)); + + /* Enable endpoint interrupts */ + + sam_putreg8(intflags, SAM_USBDEV_EPINTENSET(epno)); + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPCONF), epno << 8 | epconf); + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPINTEN), epno << 8 | intflags); + + sam_dumpep(privep->dev, epno); + return OK; +} + +/**************************************************************************** + * Name: sam_ep_reserve + * + * Description: + * Find and un-reserved endpoint number and reserve it for the caller. + * + ****************************************************************************/ + +static inline struct sam_ep_s * +sam_ep_reserve(struct sam_usbdev_s *priv, uint8_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_USB_NENDPOINTS; epndx++) + { + uint8_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 unreserved and can be + * re-used 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_reserved + * + * Description: + * Check if the endpoint has already been allocated. + * + ****************************************************************************/ + +static inline bool +sam_ep_reserved(struct sam_usbdev_s *priv, int epno) +{ + return ((priv->epavail & SAM_EP_BIT(epno)) == 0); +} + +/**************************************************************************** + * 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; + int ret; + + /* Verify parameters. Endpoint 0 is not available at this interface */ + +#if defined(CONFIG_DEBUG) || defined(CONFIG_USBDEV_TRACE) + uint8_t epno = USB_EPNO(desc->addr); + usbtrace(TRACE_EPCONFIGURE, (uint16_t)epno); + + DEBUGASSERT(ep && desc && epno > 0 && epno < SAM_USB_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 this was the last endpoint, then the class driver is fully + * configured. + */ + + if (ret == OK && last) + { + struct sam_usbdev_s *priv = privep->dev; + + /* Go to the configured state (we should have been in the addressed + * state) + */ + + DEBUGASSERT(priv && priv->devstate == USB_DEVSTATE_ADDRESSED); + priv->devstate = USB_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; + +#ifdef CONFIG_DEBUG + if (!ep) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + uerr("ERROR: ep=%p\n", ep); + return -EINVAL; + } +#endif + + 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 */ + + sam_setdevaddr(priv, priv->devaddr); + 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; + +#ifdef CONFIG_DEBUG + if (!ep) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return NULL; + } +#endif + + usbtrace(TRACE_EPALLOCREQ, USB_EPNO(ep->eplog)); + + privreq = (struct sam_req_s *)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; + +#ifdef CONFIG_DEBUG + if (!ep || !req) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return; + } +#endif + usbtrace(TRACE_EPFREEREQ, USB_EPNO(ep->eplog)); + + kmm_free(privreq); +} + +#if 0 // FIXME, not required, check... + +/**************************************************************************** + * 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) +{ + /* There is not special buffer allocation requirement */ + + return kumm_malloc(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 not special buffer allocation requirement */ + + kumm_free(buf); +} +#endif + +#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; + +#ifdef CONFIG_DEBUG + if (!req || !req->callback || !req->buf || !ep) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + uerr("ERROR: req=%p callback=%p buf=%p ep=%p\n", req, req->callback, + req->buf, ep); + return -EINVAL; + } +#endif + + usbtrace(TRACE_EPSUBMIT, USB_EPNO(ep->eplog)); + priv = privep->dev; + +#ifdef CONFIG_DEBUG + if (!priv->driver) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_NOTCONFIGURED), priv->usbdev.speed); + uerr("ERROR: driver=%p\n", priv->driver); + return -ESHUTDOWN; + } +#endif + + /* Handle the request from the class driver */ + + epno = USB_EPNO(ep->eplog); + req->result = -EINPROGRESS; + req->xfrd = 0; + privreq->inflight = 0; + flags = 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) + { + /* Check if the endpoint is stalled (or there is a stall pending) */ + + if (privep->stalled || privep->pending) + { + /* Yes.. in this case, save the new they will get in a special + * "pending" they will get queue until the stall is cleared. + */ + + uinfo("Pending stall clear\n"); + sam_req_enqueue(&privep->pendq, privreq); + usbtrace(TRACE_INREQQUEUED(epno), req->len); + ret = OK; + } + + 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 and there is not write queue + * processing in progress, then transfer the data now. + */ + + if (privep->epstate == USB_EPSTATE_IDLE && !privep->txbusy) + { + 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); + + /* Check if we have stopped RX receipt due to lack of read + * requests. In that case we are not receiving anything from host. + * and HW sends NAK to host. see sam_req_read() + * so this "state" is actually not required (at least yet) + */ + + if (privep->epstate == USB_EPSTATE_RXSTOPPED) + { + privep->epstate = USB_EPSTATE_IDLE; + } + + /* start new read if no active yet */ + + if (!privep->rxactive) + { + 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; + +#ifdef CONFIG_DEBUG + if (!ep || !req) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return -EINVAL; + } +#endif + 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_stallresume + ****************************************************************************/ + +static int sam_ep_stallresume(struct usbdev_ep_s *ep, bool resume) +{ + struct sam_ep_s *privep; + uint8_t epno; + irqstate_t flags; + int ret; + +#ifdef CONFIG_DEBUG + if (!ep) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return -EINVAL; + } +#endif + + /* Handle the resume condition */ + + privep = (struct sam_ep_s *)ep; + if (resume) + { + ret = sam_ep_resume(privep); + } + + /* Handle the stall condition */ + + else + { + /* If this is an IN endpoint (and not EP0) and if there are queued + * write requests, then we cannot stall now. Perhaps this is a + * protocol stall. In that case, we will need to drain the write + * requests before sending the stall. + */ + + flags = enter_critical_section(); + epno = USB_EPNO(ep->eplog); + if (epno != 0 && USB_ISEPIN(ep->eplog)) + { + /* Are there any unfinished write requests in the request queue? */ + + if (!sam_rqempty(&privep->reqq)) + { + /* Just set a flag to indicate that the endpoint must be + * stalled on the next TRCPTx interrupt when the request + * queue becomes empty. + */ + + privep->pending = true; + leave_critical_section(flags); + return OK; + } + } + + /* Not an IN endpoint, endpoint 0, or no pending write requests. + * Stall the endpoint now. + */ + + ret = sam_ep_stall(privep); + leave_critical_section(flags); + } + + return ret; +} + +/**************************************************************************** + * 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); +#ifdef CONFIG_DEBUG + if (!dev) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return NULL; + } +#endif + + /* Ignore any direction bits in the logical address */ + + epno = USB_EPNO(epno); + + /* A logical address of 0 means that any endpoint will do */ + + if (epno > 0) + { + /* Otherwise, we will return the endpoint structure only for the requested + * 'logical' endpoint. All of the other checks will still be performed. + * + * First, verify that the logical endpoint is in the range supported by + * by the hardware. + */ + + if (epno >= SAM_USB_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; + +#ifdef CONFIG_DEBUG + if (!dev || !ep) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return; + } +#endif + 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; + +#ifdef CONFIG_DEBUG + if (!dev) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return -EINVAL; + } +#endif + + /* Return the last frame number detected by the hardware */ + + regval = sam_getreg16(SAM_USBDEV_FNUM); + frameno = (regval & USBDEV_FNUM_MASK) >> USBDEV_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; + uint16_t regval; + + usbtrace(TRACE_DEVWAKEUP, 0); +#ifdef CONFIG_DEBUG + if (!dev) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return -EINVAL; + } +#endif + + /* Resume normal operation */ + + flags = enter_critical_section(); + sam_resume(priv); + + /* Activate a remote wakeup. (aka upstream resume) + * Setting the Remote Wakeup bit in CTRLB.UPRSM starts the + * Remote Wake Up procedure. + * + * This will automatically be done by the controller after 5 ms of + * inactivity on the USB bus. + * + * When the controller sends the Upstream Resume INTFLAG.WAKEUP is set + * and INTFLAG.SUSPEND is cleared. + * The CTRLB.UPRSM is cleared at the end of the transmitting Upstream Resume. + */ + + regval = sam_getreg16(SAM_USBDEV_CTRLB); + regval |= USBDEV_CTRLB_UPRSM; + sam_putreg16(regval, SAM_USBDEV_CTRLB); + + leave_critical_section(flags); + 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); + +#ifdef CONFIG_DEBUG + if (!dev) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return -ENODEV; + } +#endif + + priv->selfpowered = selfpowered; + 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 != USB_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 = USB_DEVSTATE_SUSPENDED; + + /* Disable clocking to the USB peripheral */ + + sam_disableclks(); + + /* Let the board-specific logic know that we have entered the + * suspend state. This may trigger additional reduced power + * consumption measures. + */ + + sam_usb_suspend((struct usbdev_s *)priv, false); + } +} + +/**************************************************************************** + * Name: sam_resume + ****************************************************************************/ + +static void sam_resume(struct sam_usbdev_s *priv) +{ + /* This function is called when either (1) a WAKEUP 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 == USB_DEVSTATE_SUSPENDED) + { + /* Revert to the previous state */ + + priv->devstate = priv->prevstate; + + /* Restore clocking to the USB peripheral */ + + sam_enableclks(); + + /* Restore full power -- whatever that means for this particular board */ + + sam_usb_suspend((struct usbdev_s *)priv, true); + + /* Notify the class driver of the resume event */ + + if (priv->driver) + { + CLASS_RESUME(priv->driver, &priv->usbdev); + } + } +} + +/**************************************************************************** + * Initialization/Reset + ****************************************************************************/ + +/**************************************************************************** + * Name: sam_reset + ****************************************************************************/ + +static void sam_reset(struct sam_usbdev_s *priv) +{ + uint16_t regval; + uint8_t epno; + + /* Make sure that clocking is enabled to the USB peripheral. */ + + sam_enableclks(); + + /* 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 (un-addressed and un-configured) */ + + priv->devaddr = 0; + sam_setdevaddr(priv, 0); + + priv->devstate = USB_DEVSTATE_DEFAULT; + + /* Reset and disable all endpoints. Then re-configure EP0 */ + + sam_epset_reset(priv, SAM_EPSET_ALL); + sam_ep_configure_internal(&priv->eplist[EP0], &g_ep0desc); + + /* set EP0 waiting for SETUP */ + + sam_ep0_ctrlread(priv); + + /* Reset endpoint data structures */ + + for (epno = 0; epno < SAM_USB_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 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); + + /* Reset endpoint status */ + + privep->stalled = false; + privep->pending = false; + privep->halted = false; + privep->zlpsent = false; + privep->txbusy = false; + privep->rxactive = false; + } + + /* Re-configure the USB controller in its initial, unconnected state */ + + priv->usbdev.speed = USB_SPEED_FULL; + + /* Clear all pending interrupt status */ + + regval = USBDEV_INT_SUSPEND | USBDEV_INT_SOF | USBDEV_INT_EORST | + USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | USBDEV_INT_UPRSM | + USBDEV_INT_RAMACER | USBDEV_INT_LPMNYET | USBDEV_INT_LPMSUSP; + + sam_putreg16(regval, SAM_USBDEV_INTFLAG); + + /* Enable normal operational interrupts + * endpoint 0 is enabled on sam_ep_configure_internal() + */ + + regval = USBDEV_INT_SOF | USBDEV_INT_WAKEUP | USBDEV_INT_SUSPEND; + sam_putreg16(regval, SAM_USBDEV_INTENSET); + + sam_dumpep(priv, EP0); +} + +/**************************************************************************** + * Interrupt Level Processing + ****************************************************************************/ + +/**************************************************************************** + * Name: sam_ep0_wrstatus + * + * Description: + * write ep0 status reply back to host + * + ****************************************************************************/ + +static void sam_ep0_wrstatus(struct sam_usbdev_s *priv, + const uint8_t *buffer, size_t buflen) +{ + uint32_t packetsize; + + /* we need to make copy of data as source is in stack + * reusing the static ep0 setup buffer + */ + + DEBUGASSERT(buflen < SAM_EP0_MAXPACKET); + memcpy(&priv->ep0out[0], buffer, buflen); + + /* set read for next setup OUT */ + + sam_ep0_ctrlread(priv); + + /* setup TX transfer */ + + priv->eplist[0].descb[1]->addr = (uint32_t) &priv->ep0out[0]; + packetsize = priv->eplist[0].descb[1]->pktsize; + packetsize &= ~USBDEV_PKTSIZE_BCNT_MASK; + packetsize &= ~USBDEV_PKTSIZE_MPKTSIZE_MASK; + packetsize |= USBDEV_PKTSIZE_BCNT(buflen); + priv->eplist[0].descb[1]->pktsize = packetsize; + + /* Set BK1RDY to notify the USB hardware that TX data is ready on + * descriptor bank1. We will be notified that the descriptor has been + * transmitted by the USB device when TRCPT1 in the endpoint's EPINTFLAG + * register has been set. + */ + + sam_putreg8(USBDEV_EPSTATUS_BK1RDY, SAM_USBDEV_EPSTATUSSET(0)); + + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0WRSTATUS), buflen); +} + +/**************************************************************************** + * 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); + (void)sam_ep_stall(&priv->eplist[EP0]); + } + } +} + +/**************************************************************************** + * Name: sam_setdevaddr + * + * Description: + * This function is called after the completion of the STATUS phase to + * instantiate the device address that was received during the SETUP + * phase. This enters the ADDRESSED state from either the DEFAULT or the + * CONFIGURED states. + * + * If called with address == 0, then function will revert to the DEFAULT, + * un-configured and un-addressed state. + * + ****************************************************************************/ + +static void sam_setdevaddr(struct sam_usbdev_s *priv, uint8_t address) +{ + uint32_t regval; + + DEBUGASSERT(address <= 0x7f); + if (address) + { + /* Enable the address */ + + address |= USBDEV_DADD_ADDEN; + sam_putreg8(address, SAM_USBDEV_DADD); + + /* Go to the addressed but not configured state */ + + priv->devstate = USB_DEVSTATE_ADDRESSED; + } + else + { + /* Set address to zero. clear ADDEN bit */ + + sam_putreg8(0x00, SAM_USBDEV_DADD); + + /* Revert to the un-addressed, default state */ + + priv->devstate = USB_DEVSTATE_DEFAULT; + } +} + +/**************************************************************************** + * Name: sam_ep0_setup + * + * Description: + * This function is called after the receiving of the SETUP packet + * data is ready on usb_ctrlreq_s struct + * + ****************************************************************************/ + +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; + uint8_t epno; + int nbytes = 0; /* Assume zero-length packet */ + int ret; + + /* Terminate any pending requests */ + + sam_req_cancel(ep0, -EPROTO); + + /* Assume NOT stalled; no TX in progress */ + + ep0->stalled = false; + ep0->pending = false; + ep0->epstate = USB_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-standard 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 = USB_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 = USB_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_GETSTATUS), epno); + if (epno >= SAM_USB_NENDPOINTS) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADEPGETSTATUS), epno); + ep0result = USB_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 = USB_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 = USB_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 = USB_EP0SETUP_DISPATCHED; + } + else + { + /* Endpoint recipient */ + + epno = USB_EPNO(index.b[LSB]); + if (epno < SAM_USB_NENDPOINTS && index.b[MSB] == 0 && + value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0) + { + privep = &priv->eplist[epno]; + privep->halted = false; + + ret = sam_ep_resume(privep); + if (ret < 0) + { + ep0result = USB_EP0SETUP_STALL; + } + } + else + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADCLEARFEATURE), 0); + ep0result = USB_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 = USB_EP0SETUP_DISPATCHED; + } + else + { + /* Handler recipient=endpoint */ + + epno = USB_EPNO(index.b[LSB]); + if (epno < SAM_USB_NENDPOINTS && index.b[MSB] == 0 && + value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0) + { + privep = &priv->eplist[epno]; + privep->halted = true; + + ret = sam_ep_stall(privep); + if (ret < 0) + { + ep0result = USB_EP0SETUP_STALL; + } + } + else + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADSETFEATURE), 0); + ep0result = USB_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 = USB_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); + priv->devaddr = value.w; + ep0result = USB_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 = USB_EP0SETUP_DISPATCHED; + } + else + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADGETSETDESC), 0); + ep0result = USB_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 = USB_EP0SETUP_DISPATCHED; + } + else + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADGETCONFIG), 0); + ep0result = USB_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 accepts it new configuration, it will + * call sam_ep_configure() to configure the endpoints. + */ + + sam_ep0_dispatch(priv); + ep0result = USB_EP0SETUP_DISPATCHED; + } + else + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BADSETCONFIG), 0); + ep0result = USB_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 = USB_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 = USB_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 == USB_EP0SETUP_SUCCESS + * + * The setup request was successfully handled above and a response + * packet must be sent (may be a zero length packet). + * + * 1b. ep0result == USB_EP0SETUP_ADDRESS + * + * A special case is the case where epstate=USB_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 == USB_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 == USB_EP0SETUP_STALL; + * + * An error was detected in either the above logic or by the class + * implementation logic. + */ + + switch (ep0result) + { + case USB_EP0SETUP_SUCCESS: + { + /* Send the response (might be a zero-length packet) */ + + ep0->epstate = USB_EPSTATE_EP0STATUSIN; + sam_ep0_wrstatus(priv, response.b, nbytes); + } + break; + + case USB_EP0SETUP_ADDRESS: + { + /* Send the response (might be a zero-length packet) */ + + ep0->epstate = USB_EPSTATE_EP0ADDRESS; + sam_ep0_wrstatus(priv, response.b, nbytes); + } + break; + + case USB_EP0SETUP_STALL: + { + /* Stall EP0 */ + + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EP0SETUPSTALLED), + priv->ctrl.req); + + (void)sam_ep_stall(&priv->eplist[EP0]); + } + break; + + case USB_EP0SETUP_DISPATCHED: + default: + break; + } +} + + + + +/**************************************************************************** + * Name: sam_ctrla_write + * + * Description: + * writes value to CTRLA register some bits needs write-synchronisation + * + ****************************************************************************/ + +static void sam_ctrla_write(uint8_t value) +{ + sam_putreg8(value, SAM_USB_CTRLA); + + if (value & USB_CTRLA_SWRST) + { + + /* Due to synchronization there is a delay from writing CTRLA.SWRST + * until the reset is complete. CTRLA.SWRST and SYNCBUSY.SWRST will + * both be cleared when the reset is complete. + */ + + while ((sam_getreg8(SAM_USB_CTRLA) & USB_CTRLA_SWRST) && + (sam_getreg8(SAM_USB_SYNCBUSY) & USB_SYNCBUSY_SWRST)) + ; + + return; + } + + if (value & USB_CTRLA_ENABLE) + { + + /* Due to synchronization there is delay from writing CTRLA.ENABLE + * until the peripheral is enabled/disabled. + * SYNCBUSY.ENABLE will be cleared when the operation is complete. + */ + + while ((sam_getreg8(SAM_USB_SYNCBUSY) & USB_SYNCBUSY_ENABLE)) + ; + } +} + +/**************************************************************************** + * Name: sam_ep_trcpt_interrupt + * + * Description: + * Transmit completed on Bank 0/1 + * Normal: + * OUT data transmit has been completed bank=0 + * Ping-Pong: + * TODO: + * + ****************************************************************************/ +static void sam_ep_trcpt_interrupt(struct sam_usbdev_s *priv, + struct sam_ep_s *privep, + uint32_t flags, int bank) +{ + uint32_t eptype; + uint16_t pktsize; + uint8_t epno; + + /* Get the endpoint type */ + + epno = USB_EPNO(privep->ep.eplog); + eptype = sam_getreg8(SAM_USBDEV_EPCFG(epno)) & USBDEV_EPCFG_EPTYPE0_MASK; + + /* Are we receiving data for a read request? EP0 does not receive data + * using read requests. + */ + + /* Get the size of the packet that we just received */ + + pktsize = privep->descb[bank]->pktsize & USBDEV_PKTSIZE_BCNT_MASK; + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRCPT0_LEN), (uint16_t)pktsize); + + if (privep->epstate == USB_EPSTATE_IDLE && epno != 0) + { + /* continue processing the read request. */ + + (void)sam_req_read(priv, privep, pktsize); + } + + /* Did we just receive the data associated with an OUT SETUP command? */ + + else if (privep->epstate == USB_EPSTATE_EP0DATAOUT) + { + uint16_t len; + + DEBUGASSERT(epno == EP0 && bank == 0); + + /* Yes.. back to the IDLE state */ + + privep->epstate = USB_EPSTATE_IDLE; + + /* Get the size that we expected to receive */ + + len = GETUINT16(priv->ctrl.len); + if (len == pktsize) + { + /* And handle the EP0 SETUP now. */ + + sam_ep0_setup(priv); + } + else + { + /* Then stall. */ + + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EP0SETUPOUTSIZE), pktsize); + + (void)sam_ep_stall(privep); + } + } + + /* Check for a EP0 STATUS packet returned by the host at the end of a + * SETUP status phase + */ + + else if ((eptype == USBDEV_EPCCFG_EPTYPE0_CTRLOUT) && pktsize == 0) + { + DEBUGASSERT(epno == EP0 && bank == 0); + } + + /* Otherwise there is a problem. Complain an clear the interrupt */ + + else + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_RXDATABKERR), privep->epstate); + } +} + +/**************************************************************************** + * Name: sam_ep0_ctrlread + * + * Description: + * setup 8-byte read req for ep0-ctrl setup phase. + * data is received on priv->ep0out buffer. This is notified by + * endpoint TRCPT0 interrupt + * + ****************************************************************************/ + +static void sam_ep0_ctrlread(struct sam_usbdev_s *priv) +{ + priv->eplist[0].descb[0]->addr = (uint32_t) &priv->ep0out[0]; + priv->eplist[0].descb[0]->pktsize = USBDEV_PKTSIZE_MPKTSIZE(8) | USBDEV_PKTSIZE_SIZE_64B; + sam_putreg8(USBDEV_EPSTATUS_BK0RDY, SAM_USBDEV_EPSTATUSCLR(0)); +} + +/**************************************************************************** + * Name: sam_ep_interrupt + * + * Description: + * Handle the USB endpoint interrupt + * + ****************************************************************************/ + +static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno) +{ + struct sam_ep_s *privep; + uint16_t flags; + + DEBUGASSERT((unsigned)epno < SAM_USB_NENDPOINTS); + + /* Get the endpoint structure */ + + privep = &priv->eplist[epno]; + + /* Get the endpoint irq */ + + flags = sam_getreg8(SAM_USBDEV_EPINTFLAG(epno)); + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPINTFLAGS), flags); + + /* TRCPT1: IN packet sent and acknowledged by the host */ + + if ((flags & USBDEV_EPINT_TRCPT1) != 0) + { + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRCPT1), flags); + + /* Clear the TRCPT1 interrupt */ + + sam_putreg8(USBDEV_EPINT_TRCPT1, SAM_USBDEV_EPINTFLAG(epno)); + + /* 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 == USB_EPSTATE_SENDING || + privep->epstate == USB_EPSTATE_EP0STATUSIN) + { + /* Continue/resume processing the write requests */ + + privep->epstate = USB_EPSTATE_IDLE; + (void)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 == USB_EPSTATE_EP0ADDRESS) + { + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_ADDRESSED), priv->devaddr); + DEBUGASSERT(epno == EP0); + + /* Set the device address */ + + privep->epstate = USB_EPSTATE_IDLE; + sam_setdevaddr(priv, priv->devaddr); + } + else + { + /* Unexpected TRCPT1 interrupt */ + + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_TXCOMPERR), privep->epstate); + } + } + + /* OUT packet received */ + + if ((flags & USBDEV_EPINT_TRCPT0) != 0) + { + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRCPT0), flags); + + /* Clear the TRCPT0 interrupt. */ + + sam_putreg8(USBDEV_EPINT_TRCPT0, SAM_USBDEV_EPINTFLAG(epno)); + + sam_ep_trcpt_interrupt(priv, privep, flags, 0); + } + + /* Endpoint stall */ + + if ((flags & USBDEV_EPINT_STALL0) != 0) + { + sam_putreg8(USBDEV_EPINT_STALL0, SAM_USBDEV_EPINTFLAG(epno)); + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPSTALL0), flags); + + /* If EP is not halted, clear STALL */ + + if (privep->epstate != USB_EPSTATE_STALLED) + { + sam_putreg8(USBDEV_EPSTATUS_STALLRQ0, SAM_USBDEV_EPSTATUSCLR(epno)); + } + } + + if ((flags & USBDEV_EPINT_STALL1) != 0) + { + sam_putreg8(USBDEV_EPINT_STALL1, SAM_USBDEV_EPINTFLAG(epno)); + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPSTALL1), flags); + + /* If EP is not halted, clear STALL */ + + if (privep->epstate != USB_EPSTATE_STALLED) + { + sam_putreg8(USBDEV_EPSTATUS_STALLRQ1, SAM_USBDEV_EPSTATUSCLR(epno)); + } + } + + // FIXME: Transmit FAIL! + if ((flags & USBDEV_EPINT_TRFAIL0) != 0) + { + //uerr("ERROR: OUT TRFAIL0!\n"); + sam_putreg8(USBDEV_EPINT_TRFAIL0, SAM_USBDEV_EPINTFLAG(epno)); + privep->descb[0]->stausbk &= ~USBDEV_STATUSBK_ERRORFLOW; + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRFAIL0), flags); + } + if ((flags & USBDEV_EPINT_TRFAIL1) != 0) + { + //uerr("ERROR: IN TRFAIL1!\n"); + sam_putreg8(USBDEV_EPINT_TRFAIL1, SAM_USBDEV_EPINTFLAG(epno)); + privep->descb[1]->stausbk &= ~USBDEV_STATUSBK_ERRORFLOW; + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPTRFAIL1), flags); + } + + /* SETUP packet received */ + + if ((flags & USBDEV_EPINT_RXSTP) != 0) + { + uint16_t len; + + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPRXSTP), flags); + + /* If a write request transfer was pending, complete it. */ + + if (privep->epstate == USB_EPSTATE_SENDING) + { + sam_req_complete(privep, -EPROTO); + } + + /* SETUP data is ready in the ep0out buffer. */ + + memcpy((uint8_t *)&priv->ctrl, (uint8_t *)&priv->ep0out[0], + USB_SIZEOF_CTRLREQ); + + /* Check for a SETUP IN transaction with data. */ + + 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 = USB_EPSTATE_EP0DATAOUT; + + //sam_ep0_ctrlread(priv); + + /* Clear the RXSTP indication. */ + + sam_putreg8(USBDEV_EPINT_RXSTP, SAM_USBDEV_EPINTFLAG(epno)); + } + else + { + /* This is an SETUP IN command (or a SETUP IN with no data). */ + + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EP0SETUPIN), len); + privep->epstate = USB_EPSTATE_IDLE; + + /* Clear the RXSTP indication. */ + + sam_putreg8(USBDEV_EPINT_RXSTP, SAM_USBDEV_EPINTFLAG(epno)); + + /* Handle the SETUP OUT command now */ + + sam_ep0_setup(priv); + } + + /* ready for next setup data */ + + sam_ep0_ctrlread(priv); + + } +} + +/**************************************************************************** + * Name: sam_usb_interrupt + * + * Description: + * Handle the USB interrupt. + * Device Mode only TODO: Host + * + ****************************************************************************/ + +static int sam_usb_interrupt(int irq, void *context, void *arg) +{ + struct sam_usbdev_s *priv = (struct sam_usbdev_s *)arg; + uint16_t isr; + uint16_t pending; + uint16_t regval; + uint16_t pendingep; + int i; + + /* Get the set of pending device interrupts */ + + isr = sam_getreg16(SAM_USBDEV_INTFLAG); + + //usbtrace(TRACE_INTENTRY(SAM_TRACEINTID_INTERRUPT), irq); + + regval = sam_getreg16(SAM_USBDEV_INTENSET); + pending = isr & regval; + + /* Get the set of pending enpoint interrupts */ + + pendingep = sam_getreg16(SAM_USBDEV_EPINTSMRY); + + /* Handle all pending USB interrupts (and new interrupts that become + * pending) + */ + + //while (pending || pendingep) + { + //usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_PENDING), pending); + + /* serve Endpoint Interrupts first */ + + if (pendingep) + { + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_PENDING_EP), pendingep); + + for (i = 0; i < SAM_USB_NENDPOINTS; i++) + { + if ((pendingep & USBDEV_EPINTSMRY_EPINT(i))) + { + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPNO), (uint16_t)i); + sam_ep_interrupt(priv, i); + } + } + } + + /* Suspend, treated last */ + + if (pending == USBDEV_INT_SUSPEND) + { + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_SUSPEND), pending); + + /* Enable wakeup interrupts */ + + sam_putreg16(USBDEV_INT_SUSPEND, SAM_USBDEV_INTENCLR); + // REVISIT: Check USBDEV_INT_UPRSM + sam_putreg16(USBDEV_INT_UPRSM | USBDEV_INT_WAKEUP | USBDEV_INT_EORSM, + SAM_USBDEV_INTENSET); + + /* Clear the pending suspend (and any wakeup) interrupts */ + + sam_putreg16(USBDEV_INT_SUSPEND | USBDEV_INT_WAKEUP, + SAM_USBDEV_INTFLAG); + + /* Perform board-specific suspend operations. + * REVISIT: is the following true? + * The USB device peripheral clocks can be switched off. + * Resume event is asynchronously detected. MCK and USBCK can be + * switched off in the Power Management controller and + * Other board-specific operations could also be performed. + */ + + // FIXME: fix suspend... + //sam_suspend(priv); + } + + /* SOF interrupt*/ + + else if ((pending & USBDEV_INT_SOF) != 0) + { + /* Clear the pending SOF interrupt */ + + //usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_SOF), pending); + sam_putreg16(SAM_TRACEINTID_SOF, SAM_USBDEV_INTFLAG); + + /* TODO: do we need check frame errors FNUM.FNCERR */ + } + + /* Resume or wakeup. REVISIT: Treat the same? */ + + else if ((pending & (USBDEV_INT_WAKEUP | USBDEV_INT_EORSM)) != 0) + { + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_WAKEUP), (uint16_t)pending); + sam_resume(priv); + + /* Clear the pending wakeup, resume, (and any suspend) interrupts */ + + sam_putreg16(USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | + USBDEV_INT_SUSPEND, SAM_USBDEV_INTFLAG); + + /* Disable wakup and endofresume Enable suspend interrupt */ + + sam_putreg16(USBDEV_INT_WAKEUP | USBDEV_INT_EORSM, SAM_USBDEV_INTENCLR); + sam_putreg16(USBDEV_INT_SUSPEND, SAM_USBDEV_INTENSET); + } + + /* End of Reset. Set by hardware when an End Of Reset has been + * detected by the USB controller. + */ + + if ((pending & USBDEV_INT_EORST) != 0) + { + usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EORST), pending); + + /* Clear the end-of-reset interrupt */ + + sam_putreg16(USBDEV_INT_EORST, SAM_USBDEV_INTFLAG); + + /* Handle the reset */ + + sam_reset(priv); + + /* REVISIT: Set the device speed Why here ?? */ + + priv->usbdev.speed = USB_SPEED_FULL; + } + + /* Re-sample the set of pending interrupts */ + + isr = sam_getreg16(SAM_USBDEV_INTFLAG); + regval = sam_getreg16(SAM_USBDEV_INTENSET); + pending = isr & regval; + pendingep = sam_getreg16(SAM_USBDEV_EPINTSMRY); + + } /* while (pending) */ + +#if 0 + /* for DEBUG help: check for pending unhandled irq's */ + + isr = sam_getreg16(SAM_USBDEV_INTFLAG); + if (isr) + { + uwarn("WARNING: Unhandled:0x%X\n", isr); + } + + pendingep = sam_getreg16(SAM_USBDEV_EPINTSMRY); + if (pendingep) + { + uwarn("WARNING: Unhandled_EP:0x%X\n", pendingep); + } +#endif + + //usbtrace(TRACE_INTEXIT(SAM_TRACEINTID_INTERRUPT), irq); +} + +void up_usbuninitialize(void) +{ + uinfo("up_usbuninitialize()\n"); + // FIXME: +} + +void up_usbinitialize(void) +{ + /* For now there is only one USB controller, but we will always refer to + * it using a pointer to make any future ports to multiple USB controllers + * easier. + */ + + struct sam_usbdev_s *priv = &g_usbd; + + usbtrace(TRACE_DEVINIT, 0); + + /* Software initialization */ + + sam_sw_setup(priv); + + /* Power up and initialize USB controller. Interrupt from the USB + * controller is initialized here, but will not be enabled at the AIC + * 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_USB, sam_usb_interrupt, priv) != 0) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_IRQREGISTRATION), + (uint16_t)SAM_IRQ_USB); + goto errout; + } + + uinfo("OK\n"); + return; + +errout: + up_usbuninitialize(); +} + +/**************************************************************************** + * Endpoint Helpers + ****************************************************************************/ + +/**************************************************************************** + * Name: sam_ep_reset + * + * Description + * Reset and disable one endpoints. + * + ****************************************************************************/ + +static void sam_ep_reset(struct sam_usbdev_s *priv, uint8_t epno) +{ + struct sam_ep_s *privep = &priv->eplist[epno]; + + /* Disable endpoint interrupts */ + + sam_putreg8(0x7e, SAM_USBDEV_EPINTENCLR(epno)); + sam_putreg8(0x7e, SAM_USBDEV_EPINTFLAG(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); + + /* Reset endpoint status */ + + privep->epstate = USB_EPSTATE_DISABLED; + privep->stalled = false; + privep->pending = false; + privep->halted = false; + privep->zlpsent = false; + privep->txbusy = false; + privep->rxactive = false; +} + +/**************************************************************************** + * 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_USB_NENDPOINTS && epset != 0; + epno++, bit <<= 1) + { + /* Is this endpoint in the set? */ + + if ((epset & bit) != 0) + { + /* Yes.. reset and disable it */ + + sam_ep_reset(priv, epno); + epset &= ~bit; + } + } +} + +/**************************************************************************** + * Name: sam_ep_stall + ****************************************************************************/ + +static int sam_ep_stall(struct sam_ep_s *privep) +{ + irqstate_t flags; + uint8_t epno; + + /* Check that endpoint is in Idle state */ + + DEBUGASSERT(/* privep->epstate == UDP_EPSTATE_IDLE && */ privep->dev); + + /* Check that endpoint is enabled and not already in Halt state */ + + flags = enter_critical_section(); + if ((privep->epstate != USB_EPSTATE_DISABLED) && + (privep->epstate != USB_EPSTATE_STALLED)) + { + epno = USB_EPNO(privep->ep.eplog); + usbtrace(TRACE_EPSTALL, epno); + + /* If this is an IN endpoint (or endpoint 0), then cancel any + * write requests in progress. + */ + + if (epno == 0 || USB_ISEPIN(privep->ep.eplog)) + { + sam_req_cancel(privep, -EPERM); + } + + /* Put endpoint into stalled state */ + + privep->epstate = USB_EPSTATE_STALLED; + privep->stalled = true; + privep->pending = false; + + sam_putreg8(USBDEV_EPSTATUS_STALLRQ0 | USBDEV_EPSTATUS_STALLRQ1, + SAM_USBDEV_EPSTATUSSET(epno)); + } + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: sam_ep_resume + ****************************************************************************/ + +static int sam_ep_resume(struct sam_ep_s *privep) +{ + struct sam_usbdev_s *priv; + struct sam_req_s *req; + irqstate_t flags; + uint8_t epno; + + /* Check that endpoint is in Idle state */ + + DEBUGASSERT(/* privep->epstate == UDP_EPSTATE_IDLE && */ privep->dev); + + flags = enter_critical_section(); + + /* Check if the endpoint is stalled */ + + if (privep->epstate == USB_EPSTATE_STALLED) + { + epno = USB_EPNO(privep->ep.eplog); + usbtrace(TRACE_EPRESUME, epno); + + priv = (struct sam_usbdev_s *)privep->dev; + + /* Return endpoint to Idle state */ + + privep->stalled = false; + privep->pending = false; + privep->epstate = USB_EPSTATE_IDLE; + + /* Clear STALLRQx request and reset data toggle */ + + if (USB_ISEPIN(privep->ep.eplog)) + { + sam_putreg8(USBDEV_EPSTATUS_STALLRQ1, SAM_USBDEV_EPSTATUSCLR(epno)); + sam_putreg8(USBDEV_EPSTATUS_DTGLIN, SAM_USBDEV_EPSTATUSCLR(epno)); + sam_putreg8(USBDEV_EPINT_STALL1, SAM_USBDEV_EPINTFLAG(epno)); + } + else + { + sam_putreg8(USBDEV_EPSTATUS_STALLRQ0, SAM_USBDEV_EPSTATUSCLR(epno)); + sam_putreg8(USBDEV_EPSTATUS_DTGLOUT, SAM_USBDEV_EPSTATUSCLR(epno)); + sam_putreg8(USBDEV_EPINT_STALL0, SAM_USBDEV_EPINTFLAG(epno)); + } + + /* Copy any requests in the pending request queue to the working + * request queue. + */ + + while ((req = sam_req_dequeue(&privep->pendq)) != NULL) + { + sam_req_enqueue(&privep->reqq, req); + } + + /* Resuming any blocked data transfers on the endpoint */ + + if (epno == 0 || USB_ISEPIN(privep->ep.eplog)) + { + /* IN endpoint (or EP0). Restart any queued write requests */ + + (void)sam_req_write(priv, privep); + } + } + + leave_critical_section(flags); + 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; + uint32_t regval; + + usbtrace(TRACE_DEVPULLUP, (uint16_t)enable); + + /* Enable/disable the USB pull-up resistor */ + + regval = sam_getreg8(SAM_USBDEV_CTRLB); + + if (enable) + { + /* Connect the 1.5 KOhm integrated pull-up on USB */ + + regval &= ~USBDEV_CTRLB_DETACH; + } + else + { + /* Disconnect the 1.5 KOhm integrated pull-up on USB */ + + regval |= USBDEV_CTRLB_DETACH; + + /* Device returns to the Powered state */ + + if (priv->devstate > USB_DEVSTATE_POWERED) + { + priv->devstate = USB_DEVSTATE_POWERED; + } + } + + sam_putreg8((uint8_t)regval, SAM_USBDEV_CTRLB); + + return OK; +} + +/**************************************************************************** + * Initialization/Reset + ****************************************************************************/ + +/**************************************************************************** + * Name: sam_enableclks + ****************************************************************************/ + +static void sam_enableclks(void) +{ + sam_usb_enableperiph(); + sam_gclk_chan_enable(GCLK_CHAN_USB, BOARD_USB_GCLKGEN); +} + +/**************************************************************************** + * Name: sam_disableclks + ****************************************************************************/ + +static void sam_disableclks(void) +{ + sam_gclk_chan_disable(GCLK_CHAN_USB); + sam_usb_disableperiph(); + +} + +/**************************************************************************** + * Name: sam_hw_setup + ****************************************************************************/ + +static void sam_hw_setup(struct sam_usbdev_s *priv) +{ + int i; + uint16_t regval; + uint32_t padcalib; + + /* To use the USB, the programmer must first configure the USB clock + * input, + */ + + sam_enableclks(); + + /* full reset USB */ + + sam_ctrla_write(USB_CTRLA_SWRST); + + /* TODO: load PAD calibration from NVM or ... + * now using default values + */ + + padcalib = USB_PADCAL_TRANSP(29) | USB_PADCAL_TRANSN(5) | USB_PADCAL_TRIM(3); + sam_putreg32(padcalib, SAM_USB_PADCAL); + + /* set config + * NREPLY = Any transaction to endpoint 0 will be ignored except SETUP + * cleared by hardware when receiving a SETUP packet. + * DETACH = The internal device pull-ups are disabled + */ + + regval = USBDEV_CTRLB_NREPLY | USBDEV_CTRLB_DETACH; + + /* do we need config to set LOW_SPEED mode? */ + + regval |= USBDEV_CTRLB_SPDCONF_FULL; + + sam_putreg16(regval, SAM_USBDEV_CTRLB); + + /* Enable USB core */ + +#ifdef CONFIG_USBDEV + sam_ctrla_write(USB_CTRLA_ENABLE | USB_CTRLA_MODE_DEVICE); +#endif +#ifdef CONFIG_USBHOST + sam_ctrla_write(USB_CTRLA_ENABLE | USB_CTRLA_MODE_HOST); +#endif + + /* Set up the USB DP/DM pins */ + + sam_configport(PORT_USB_DP); + sam_configport(PORT_USB_DM); + + /* Reset and disable endpoints */ + + sam_epset_reset(priv, SAM_EPSET_ALL); + + /* Initialize Endpoints */ + + for (i = 0; i < SAM_USB_NENDPOINTS; i++) + { + /* Reset endpoint configuration */ + + sam_putreg8(0, SAM_USBDEV_EPCFG(i)); + } + + /* Init descriptor base address */ + + sam_putreg32((uint32_t)&priv->ep_descriptors, SAM_USB_DESCADD); + + /* clear all previous descriptor data so no accidental + * DMA transfers could happen */ + + memset((uint8_t *)(&priv->ep_descriptors[0]), 0, + sizeof(priv->ep_descriptors)); + + /* Disable all interrupts */ + + sam_putreg16(USBDEV_INT_SUSPEND | USBDEV_INT_SOF | USBDEV_INT_EORST | + USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | USBDEV_INT_UPRSM | + USBDEV_INT_RAMACER | USBDEV_INT_LPMNYET | USBDEV_INT_LPMSUSP, + SAM_USBDEV_INTENCLR); + + sam_pullup(&priv->usbdev, false); +} + +/**************************************************************************** + * Name: sam_hw_shutdown + ****************************************************************************/ + +static void sam_hw_shutdown(struct sam_usbdev_s *priv) +{ + uint16_t regval; + + priv->usbdev.speed = USB_SPEED_UNKNOWN; + + /* Disable all interrupts */ + + sam_putreg16(USBDEV_INT_SUSPEND | USBDEV_INT_SOF | USBDEV_INT_EORST | + USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | USBDEV_INT_UPRSM | + USBDEV_INT_RAMACER | USBDEV_INT_LPMNYET | USBDEV_INT_LPMSUSP, + SAM_USBDEV_INTENCLR); + + /* Clear all pending interrupt status */ + + sam_putreg16(USBDEV_INT_SUSPEND | USBDEV_INT_SOF | USBDEV_INT_EORST | + USBDEV_INT_WAKEUP | USBDEV_INT_EORSM | USBDEV_INT_UPRSM | + USBDEV_INT_RAMACER | USBDEV_INT_LPMNYET | USBDEV_INT_LPMSUSP, + SAM_USBDEV_INTFLAG); + + /* Disconnect the device / disable the pull-up */ + + sam_pullup(&priv->usbdev, false); + + /* Disable clocking to the UDP peripheral */ + + sam_disableclks(); +} + +/**************************************************************************** + * Name: sam_sw_setup + ****************************************************************************/ + +static void sam_sw_setup(struct sam_usbdev_s *priv) +{ + int epno; + + /* 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 = USB_DEVSTATE_SUSPENDED; + priv->prevstate = USB_DEVSTATE_POWERED; + + /* Initialize the endpoint list */ + + for (epno = 0; epno < SAM_USB_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_USB_MAXPACKETSIZE(epno); + + /* set descriptor addresses */ + + priv->eplist[epno].descb[0] = &priv->ep_descriptors[(epno << 1)]; + priv->eplist[epno].descb[1] = &priv->ep_descriptors[(epno << 1) + 1]; + } + + /* Select a smaller endpoint size for EP0 */ + +#if SAM_EP0_MAXPACKET < SAM_USB_MAXPACKETSIZE(0) + priv->eplist[EP0].ep.maxpacket = SAM_EP0_MAXPACKET; +#endif +} + +/**************************************************************************** + * Name: sam_sw_shutdown + ****************************************************************************/ + +static void sam_sw_shutdown(struct sam_usbdev_s *priv) +{ + (void)priv; +} + +/**************************************************************************** + * 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_usbd; + int ret; + + usbtrace(TRACE_DEVREGISTER, 0); + +#ifdef CONFIG_DEBUG + if (!driver || !driver->ops->bind || !driver->ops->unbind || + !driver->ops->disconnect || !driver->ops->setup) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return -EINVAL; + } + + if (priv->driver) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_DRIVER), 0); + return -EBUSY; + } +#endif + + /* First hook up the driver */ + + priv->driver = driver; + + /* Then bind the class driver */ + + ret = CLASS_BIND(driver, &priv->usbdev); + if (ret) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_BINDFAILED), (uint16_t)-ret); + priv->driver = NULL; + } + else + { + /* Enable USB controller interrupts at the NVIC. */ + + sam_hw_setup(priv); + up_enable_irq(SAM_IRQ_USB); + + /* Enable EORST irq */ + + sam_putreg16(USBDEV_INT_EORST, SAM_USBDEV_INTENSET); + + /* Enable pull-up to connect the device. The host should enumerate us + * some time after this. The next thing we expect is the EORST + * interrupt. + */ + + sam_pullup(&priv->usbdev, true); + priv->usbdev.speed = USB_SPEED_FULL; + } + + 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_usbd; + irqstate_t flags; + + usbtrace(TRACE_DEVUNREGISTER, 0); + +#ifdef CONFIG_DEBUG + if (driver != priv->driver) + { + usbtrace(TRACE_DEVERROR(SAM_TRACEERR_INVALIDPARMS), 0); + return -EINVAL; + } +#endif + + /* Reset the hardware and cancel all requests. All requests must be + * canceled while the class driver is still bound. + */ + + flags = 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_USB); + + /* 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 up_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_SAMDL_USB */ diff --git a/arch/arm/src/samdl/sam_usb.h b/arch/arm/src/samdl/sam_usb.h new file mode 100644 index 00000000000..278f8e88ebf --- /dev/null +++ b/arch/arm/src/samdl/sam_usb.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * arch/arm/src/samdl/sam_usb.h + * + * Copyright (C) 2015 Filament - www.filament.com + * Copyright (C) 2015 Offcode Ltd. All rights reserved. + * Author: Janne Rosberg + * + * This driver is derived from the SAM34 UDP driver: + * Copyright (C) 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_SAMDL_SAM_USB_H +#define __ARCH_ARM_SRC_SAMDL_SAM_USB_H + +/***************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include "chip.h" +#include "chip/saml_usb.h" + +/***************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/***************************************************************************** + * Name: sam_usb_suspend + * + * Description: + * Board logic must provide the sam_usb_suspend logic if the USB driver is + * used. This function is called whenever the USB enters or leaves + * suspend mode. + * + * When 'resume' is false, this function call provides an opportunity to + * perform board-specific power-saving actions so that less power is consumed + * while the USB is suspended. + * + * XXX: + * Certain power-saving operations are performed by the UDP driver when it + * enters suspend mode: The USB device peripheral clocks are be switched off. + * MCK and UDPCK are switched off and the USB transceiver is disabled. + * + * When 'resume' is true, normal clocking and operations must all be restored. + * + *****************************************************************************/ + +void sam_udp_suspend(FAR struct usbdev_s *dev, bool resume); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __ARCH_ARM_SRC_SAMDL_SAM_USB_H */ diff --git a/configs/samd21-xplained/include/board.h b/configs/samd21-xplained/include/board.h index 40b19e775fc..e51baf7a90f 100644 --- a/configs/samd21-xplained/include/board.h +++ b/configs/samd21-xplained/include/board.h @@ -480,6 +480,19 @@ #define BOARD_SERCOM5_FREQUENCY BOARD_GCLK0_FREQUENCY +/* USB definitions ******************************************************************/ +/* This is the source clock generator for the GCLK_USB clock + */ + +#define BOARD_USB_GCLKGEN 0 +#define BOARD_USB_FREQUENCY BOARD_GCLK0_FREQUENCY + +/* default USB Pad calibration (not used yet by USB driver) */ + +#define BOARD_USB_PADCAL_P 29 +#define BOARD_USB_PADCAL_N 5 +#define BOARD_USB_PADCAL_TRIM 3 + /* LED definitions ******************************************************************/ /* There are three LEDs on board the SAMD21 Xplained Pro board: The EDBG * controls two of the LEDs, a power LED and a status LED. There is only diff --git a/configs/saml21-xplained/include/board.h b/configs/saml21-xplained/include/board.h index ec93296ec80..5cc260d2741 100644 --- a/configs/saml21-xplained/include/board.h +++ b/configs/saml21-xplained/include/board.h @@ -604,6 +604,19 @@ #define BOARD_SERCOM5_FREQUENCY BOARD_GCLK0_FREQUENCY +/* USB definitions ******************************************************************/ +/* This is the source clock generator for the GCLK_USB clock + */ + +#define BOARD_USB_GCLKGEN 0 +#define BOARD_USB_FREQUENCY BOARD_GCLK0_FREQUENCY + +/* default USB Pad calibration (not used yet by USB driver) */ + +#define BOARD_USB_PADCAL_P 29 +#define BOARD_USB_PADCAL_N 5 +#define BOARD_USB_PADCAL_TRIM 3 + /* LED definitions ******************************************************************/ /* There are three LEDs on board the SAML21 Xplained Pro board: The EDBG * controls two of the LEDs, a power LED and a status LED. There is only