| /**************************************************************************** |
| * arch/arm/src/cxd56xx/cxd56_usbdev.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| |
| #include <sys/types.h> |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| |
| #include <nuttx/arch.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/usb/usb.h> |
| #include <nuttx/usb/usbdev.h> |
| #include <nuttx/usb/usbdev_trace.h> |
| #include <nuttx/fs/procfs.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/signal.h> |
| #include <arch/chip/usbdev.h> |
| #include <arch/chip/pm.h> |
| |
| #include "chip.h" |
| #include "arm_internal.h" |
| #include "cxd56_clock.h" |
| #include "cxd56_usbdev.h" |
| #include "hardware/cxd5602_topreg.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* TOPREG VBUS register */ |
| |
| #define CLR_EDGE (1 << 9) |
| #define CLR_EN (1 << 8) |
| #define VBUS_DET (1 << 0) |
| |
| /* Configuration ************************************************************/ |
| |
| #ifndef CONFIG_USBDEV_MAXPOWER |
| # define CONFIG_USBDEV_MAXPOWER 100 /* mA */ |
| #endif |
| |
| /* Vendor ID & Product ID of the USB device */ |
| |
| #ifndef CONFIG_CXD56_VENDORID |
| # define CONFIG_CXD56_VENDORID 0x054c |
| #endif |
| |
| #ifndef CONFIG_CXD56_PRODUCTID |
| # define CONFIG_CXD56_PRODUCTID 0x0bc2 |
| #endif |
| |
| #ifndef CONFIG_USBDEV_EP0_MAXSIZE |
| # define CONFIG_USBDEV_EP0_MAXSIZE 64 |
| #endif |
| |
| #ifndef CONFIG_USBDEV_SETUP_MAXDATASIZE |
| # define CONFIG_USBDEV_SETUP_MAXDATASIZE (CONFIG_USBDEV_EP0_MAXSIZE * 4) |
| #endif |
| |
| #define CONFIG_DEFAULT_PHY_CFG0 \ |
| (PHY_STAGSELECT | PHY_HSFALLCNTRL | PHY_IHSTX(0xc) | PHY_INHSRFRED | \ |
| PHY_INHSIPLUS | PHY_INHSDRVSLEW| PHY_INLFSFBCAP) |
| |
| /* Debug ********************************************************************/ |
| |
| /* Trace error codes */ |
| |
| #define CXD56_TRACEERR_ALLOCFAIL 0x0001 |
| #define CXD56_TRACEERR_ATTACHIRQREG 0x0002 |
| #define CXD56_TRACEERR_BINDFAILED 0x0003 |
| #define CXD56_TRACEERR_COREIRQREG 0x0004 |
| #define CXD56_TRACEERR_DRIVER 0x0005 |
| #define CXD56_TRACEERR_DRIVERREGISTERED 0x0006 |
| #define CXD56_TRACEERR_EPREAD 0x0007 |
| #define CXD56_TRACEERR_EWRITE 0x0008 |
| #define CXD56_TRACEERR_INVALIDPARMS 0x0009 |
| #define CXD56_TRACEERR_NOEP 0x000a |
| #define CXD56_TRACEERR_NOTCONFIGURED 0x000b |
| #define CXD56_TRACEERR_NULLPACKET 0x000c |
| #define CXD56_TRACEERR_NULLREQUEST 0x000d |
| #define CXD56_TRACEERR_REQABORTED 0x000e |
| #define CXD56_TRACEERR_STALLEDCLRFEATURE 0x000f |
| #define CXD56_TRACEERR_STALLEDISPATCH 0x0010 |
| #define CXD56_TRACEERR_STALLEDGETST 0x0011 |
| #define CXD56_TRACEERR_STALLEDGETSTEP 0x0012 |
| #define CXD56_TRACEERR_STALLEDGETSTRECIP 0x0013 |
| #define CXD56_TRACEERR_STALLEDREQUEST 0x0014 |
| #define CXD56_TRACEERR_STALLEDSETFEATURE 0x0015 |
| #define CXD56_TRACEERR_TXREQLOST 0x0016 |
| #define CXD56_TRACEERR_RXREQLOST 0x0017 |
| #define CXD56_TRACEERR_VBUSIRQREG 0x0018 |
| #define CXD56_TRACEERR_VBUSNIRQREG 0x0019 |
| |
| /* Trace interrupt codes */ |
| |
| #define CXD56_TRACEINTID_USB 0x0001 |
| #define CXD56_TRACEINTID_SYS 0x0002 |
| #define CXD56_TRACEINTID_VBUS 0x0004 |
| #define CXD56_TRACEINTID_VBUSN 0x0008 |
| |
| #define CXD56_TRACEINTID_RMTWKP 1 |
| #define CXD56_TRACEINTID_ENUM 2 |
| #define CXD56_TRACEINTID_SOF 3 |
| #define CXD56_TRACEINTID_US 4 |
| #define CXD56_TRACEINTID_UR 5 |
| #define CXD56_TRACEINTID_ES 6 |
| #define CXD56_TRACEINTID_SI 7 |
| #define CXD56_TRACEINTID_SC 8 |
| #define CXD56_TRACEINTID_GETSTATUS 9 |
| #define CXD56_TRACEINTID_GETIFDEV 10 |
| #define CXD56_TRACEINTID_CLEARFEATURE 11 |
| #define CXD56_TRACEINTID_SETFEATURE 12 |
| #define CXD56_TRACEINTID_TESTMODE 13 |
| #define CXD56_TRACEINTID_SETADDRESS 14 |
| #define CXD56_TRACEINTID_GETSETDESC 15 |
| #define CXD56_TRACEINTID_GETSETIFCONFIG 16 |
| #define CXD56_TRACEINTID_SYNCHFRAME 17 |
| #define CXD56_TRACEINTID_DISPATCH 18 |
| #define CXD56_TRACEINTID_GETENDPOINT 19 |
| #define CXD56_TRACEINTID_RESUME 20 |
| #define CXD56_TRACEINTID_CDCCLEAR 21 |
| #define CXD56_TRACEINTID_TXDMAERROR 22 |
| #define CXD56_TRACEINTID_RXDMAERROR 23 |
| #define CXD56_TRACEINTID_TXBNA 24 |
| #define CXD56_TRACEINTID_RXBNA 25 |
| #define CXD56_TRACEINTID_XFERDONE 26 |
| #define CXD56_TRACEINTID_TXEMPTY 27 |
| #define CXD56_TRACEINTID_TDC 28 |
| #define CXD56_TRACEINTID_IN 29 |
| #define CXD56_TRACEINTID_EPOUTQEMPTY 31 |
| #define CXD56_TRACEINTID_OUTSETUP 32 |
| #define CXD56_TRACEINTID_OUTDATA 33 |
| |
| #ifdef CONFIG_USBDEV_TRACE_STRINGS |
| const struct trace_msg_t g_usb_trace_strings_intdecode[] = |
| { |
| TRACE_STR(CXD56_TRACEINTID_RMTWKP), |
| TRACE_STR(CXD56_TRACEINTID_ENUM), |
| TRACE_STR(CXD56_TRACEINTID_SOF), |
| TRACE_STR(CXD56_TRACEINTID_US), |
| TRACE_STR(CXD56_TRACEINTID_UR), |
| TRACE_STR(CXD56_TRACEINTID_ES), |
| TRACE_STR(CXD56_TRACEINTID_SI), |
| TRACE_STR(CXD56_TRACEINTID_SC), |
| TRACE_STR(CXD56_TRACEINTID_GETSTATUS), |
| TRACE_STR(CXD56_TRACEINTID_GETIFDEV), |
| TRACE_STR(CXD56_TRACEINTID_CLEARFEATURE), |
| TRACE_STR(CXD56_TRACEINTID_SETFEATURE), |
| TRACE_STR(CXD56_TRACEINTID_TESTMODE), |
| TRACE_STR(CXD56_TRACEINTID_SETADDRESS), |
| TRACE_STR(CXD56_TRACEINTID_GETSETDESC), |
| TRACE_STR(CXD56_TRACEINTID_GETSETIFCONFIG), |
| TRACE_STR(CXD56_TRACEINTID_SYNCHFRAME), |
| TRACE_STR(CXD56_TRACEINTID_DISPATCH), |
| TRACE_STR(CXD56_TRACEINTID_GETENDPOINT), |
| TRACE_STR(CXD56_TRACEINTID_RESUME), |
| TRACE_STR(CXD56_TRACEINTID_CDCCLEAR), |
| TRACE_STR(CXD56_TRACEINTID_TXDMAERROR), |
| TRACE_STR(CXD56_TRACEINTID_RXDMAERROR), |
| TRACE_STR(CXD56_TRACEINTID_TXBNA), |
| TRACE_STR(CXD56_TRACEINTID_RXBNA), |
| TRACE_STR(CXD56_TRACEINTID_XFERDONE), |
| TRACE_STR(CXD56_TRACEINTID_TXEMPTY), |
| TRACE_STR(CXD56_TRACEINTID_TDC), |
| TRACE_STR(CXD56_TRACEINTID_IN), |
| TRACE_STR(CXD56_TRACEINTID_EPOUTQEMPTY), |
| TRACE_STR(CXD56_TRACEINTID_OUTSETUP), |
| TRACE_STR(CXD56_TRACEINTID_OUTDATA), |
| TRACE_STR_END |
| }; |
| |
| const struct trace_msg_t g_usb_trace_strings_deverror[] = |
| { |
| TRACE_STR(CXD56_TRACEERR_ALLOCFAIL), |
| TRACE_STR(CXD56_TRACEERR_ATTACHIRQREG), |
| TRACE_STR(CXD56_TRACEERR_BINDFAILED), |
| TRACE_STR(CXD56_TRACEERR_COREIRQREG), |
| TRACE_STR(CXD56_TRACEERR_DRIVER), |
| TRACE_STR(CXD56_TRACEERR_DRIVERREGISTERED), |
| TRACE_STR(CXD56_TRACEERR_EPREAD), |
| TRACE_STR(CXD56_TRACEERR_EWRITE), |
| TRACE_STR(CXD56_TRACEERR_INVALIDPARMS), |
| TRACE_STR(CXD56_TRACEERR_NOEP), |
| TRACE_STR(CXD56_TRACEERR_NOTCONFIGURED), |
| TRACE_STR(CXD56_TRACEERR_NULLPACKET), |
| TRACE_STR(CXD56_TRACEERR_NULLREQUEST), |
| TRACE_STR(CXD56_TRACEERR_REQABORTED), |
| TRACE_STR(CXD56_TRACEERR_STALLEDCLRFEATURE), |
| TRACE_STR(CXD56_TRACEERR_STALLEDISPATCH), |
| TRACE_STR(CXD56_TRACEERR_STALLEDGETST), |
| TRACE_STR(CXD56_TRACEERR_STALLEDGETSTEP), |
| TRACE_STR(CXD56_TRACEERR_STALLEDGETSTRECIP), |
| TRACE_STR(CXD56_TRACEERR_STALLEDREQUEST), |
| TRACE_STR(CXD56_TRACEERR_STALLEDSETFEATURE), |
| TRACE_STR(CXD56_TRACEERR_TXREQLOST), |
| TRACE_STR(CXD56_TRACEERR_RXREQLOST), |
| TRACE_STR(CXD56_TRACEERR_VBUSIRQREG), |
| TRACE_STR(CXD56_TRACEERR_VBUSNIRQREG), |
| TRACE_STR_END |
| }; |
| #endif |
| |
| /* Hardware interface *******************************************************/ |
| |
| /* The CXD56 hardware supports 8 configurable endpoints EP1-4, IN and OUT |
| * (in addition to EP0 IN and OUT). This driver, however, does not exploit |
| * the full configuratability of the hardware at this time but, instead, |
| * supports the one interrupt IN, one bulk IN and one bulk OUT endpoint. |
| */ |
| |
| /* Hardware dependent sizes and numbers */ |
| |
| #define CXD56_EP0MAXPACKET 64 /* EP0 max packet size */ |
| #define CXD56_BULKMAXPACKET 512 /* Bulk endpoint max packet */ |
| #define CXD56_INTRMAXPACKET 64 /* Interrupt endpoint max packet */ |
| #define CXD56_EP0BUFSIZE 64 /* EP0 max packet size */ |
| #define CXD56_BULKBUFSIZE 1024 /* Bulk endpoint max packet */ |
| #define CXD56_INTRBUFSIZE 64 /* Interrupt endpoint max packet */ |
| #define CXD56_NENDPOINTS 7 /* Includes EP0 */ |
| |
| /* Endpoint numbers */ |
| |
| #define CXD56_EP0 0 /* Control endpoint */ |
| #define CXD56_EPBULKIN0 1 /* Bulk EP for send to host */ |
| #define CXD56_EPBULKOUT0 2 /* Bulk EP for recv to host */ |
| #define CXD56_EPINTRIN0 3 /* Intr EP for host poll */ |
| #define CXD56_EPBULKIN1 4 /* Bulk EP for send to host */ |
| #define CXD56_EPBULKOUT1 5 /* Bulk EP for recv to host */ |
| #define CXD56_EPINTRIN1 6 /* Intr EP for host poll */ |
| |
| /* Request queue operations *************************************************/ |
| |
| #define cxd56_rqempty(ep) ((ep)->head == NULL) |
| #define cxd56_rqpeek(ep) ((ep)->head) |
| |
| #define CXD56_USBDEV_LINELEN 32 |
| #define CXD56_USBDEV_TIMEOUT 1000 |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct cxd56_setup_desc_s |
| { |
| volatile uint32_t status; |
| volatile uint32_t reserved; |
| volatile uint32_t setup_1; |
| volatile uint32_t setup_2; |
| }; |
| |
| struct cxd56_data_desc_s |
| { |
| volatile uint32_t status; |
| volatile uint32_t reserved; |
| volatile uint32_t buf; |
| volatile uint32_t next; |
| }; |
| |
| /* A container for a request so that the request make be retained in a list */ |
| |
| struct cxd56_req_s |
| { |
| struct usbdev_req_s req; /* Standard USB request */ |
| struct cxd56_req_s *flink; /* Supports a singly linked list */ |
| }; |
| |
| /* This is the internal representation of an endpoint */ |
| |
| struct cxd56_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 cxd56_ep_s. |
| */ |
| |
| struct usbdev_ep_s ep; /* Standard endpoint structure */ |
| |
| /* CXD56-specific fields */ |
| |
| struct cxd56_usbdev_s *dev; /* Reference to private driver data */ |
| struct cxd56_req_s *head; /* Request list for this endpoint */ |
| struct cxd56_req_s *tail; |
| struct cxd56_data_desc_s *desc; /* DMA descriptor */ |
| void *buffer; /* OUT only, receiving data buffer */ |
| uint8_t epphy; /* Physical EP address/index */ |
| uint8_t stalled : 1; /* Endpoint is halted */ |
| uint8_t in : 1; /* Endpoint is IN only */ |
| uint8_t halted : 1; /* Endpoint feature halted */ |
| uint8_t txnullpkt : 1; /* Null packet needed at end of transfer */ |
| uint8_t txwait : 1; /* IN transaction already requested from host */ |
| }; |
| |
| /* This structure encapsulates the overall driver state */ |
| |
| struct cxd56_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 cxd56_usbdev_s. |
| */ |
| |
| struct usbdev_s usbdev; |
| |
| /* The bound device class driver */ |
| |
| struct usbdevclass_driver_s *driver; |
| |
| /* CXD56-specific fields */ |
| |
| uint8_t stalled : 1; /* 1: Protocol stalled */ |
| uint8_t selfpowered : 1; /* 1: Device is self powered */ |
| uint8_t paddrset : 1; /* 1: Peripheral addr has been set */ |
| uint8_t attached : 1; /* 1: Host attached */ |
| uint8_t paddr; /* Peripheral address */ |
| uint8_t avail; |
| |
| /* E0 SETUP data buffering. |
| * |
| * ctrl |
| * The 8-byte SETUP request is received on the EP0 OUT endpoint and is |
| * saved. |
| * |
| * ep0data |
| * For OUT SETUP requests, the SETUP data phase must also complete before |
| * the SETUP command can be processed. |
| * |
| * ep0datlen |
| * Length of OUT DATA received in ep0data[] |
| */ |
| |
| struct usb_ctrlreq_s ctrl; /* Last EP0 request */ |
| |
| uint8_t ep0data[CONFIG_USBDEV_SETUP_MAXDATASIZE]; |
| uint16_t ep0datlen; |
| uint16_t ep0reqlen; |
| |
| /* The endpoint list */ |
| |
| struct cxd56_ep_s eplist[CXD56_NENDPOINTS]; |
| |
| /* attach status */ |
| |
| int state; |
| int power; |
| |
| /* signal */ |
| |
| int signo; |
| pid_t pid; |
| }; |
| |
| /* For maintaining tables of endpoint info */ |
| |
| struct cxd56_epinfo_s |
| { |
| uint8_t addr; /* Logical endpoint address */ |
| uint8_t attr; /* Endpoint attributes */ |
| uint16_t maxpacket; /* Max packet size */ |
| uint16_t bufsize; /* Buffer size */ |
| }; |
| |
| /* This structure describes one open "file" */ |
| |
| struct cxd56_usbdev_file_s |
| { |
| struct procfs_file_s base; /* Base open file structure */ |
| unsigned int linesize; /* Number of valid characters in line[] */ |
| |
| /* Pre-allocated buffer for formatted lines */ |
| |
| char line[CXD56_USBDEV_LINELEN]; |
| }; |
| |
| static struct pm_cpu_freqlock_s g_hv_lock = |
| PM_CPUFREQLOCK_INIT(PM_CPUFREQLOCK_TAG('U', 'S', 0), |
| PM_CPUFREQLOCK_FLAG_HV); |
| static struct pm_cpu_wakelock_s g_wake_lock = |
| { |
| .count = 0, |
| .info = PM_CPUWAKELOCK_TAG('U', 'S', 0), |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Request queue operations *************************************************/ |
| |
| static struct cxd56_req_s *cxd56_rqdequeue( |
| struct cxd56_ep_s *privep); |
| static void cxd56_rqenqueue(struct cxd56_ep_s *privep, |
| struct cxd56_req_s *req); |
| |
| /* Low level data transfers and request operations */ |
| |
| static int cxd56_epwrite(struct cxd56_ep_s *privep, uint8_t *buf, |
| uint16_t nbytes); |
| static inline void cxd56_abortrequest(struct cxd56_ep_s *privep, |
| struct cxd56_req_s *privreq, |
| int16_t result); |
| static void cxd56_reqcomplete(struct cxd56_ep_s *privep, int16_t result); |
| static int cxd56_wrrequest(struct cxd56_ep_s *privep); |
| static int cxd56_rdrequest(struct cxd56_ep_s *privep); |
| static void cxd56_cancelrequests(struct cxd56_ep_s *privep); |
| static void cxd56_usbdevreset(struct cxd56_usbdev_s *priv); |
| static void cxd56_usbreset(struct cxd56_usbdev_s *priv); |
| |
| /* Interrupt handling */ |
| |
| static struct cxd56_ep_s * |
| cxd56_epfindbyaddr(struct cxd56_usbdev_s *priv, uint16_t eplog); |
| static void cxd56_dispatchrequest(struct cxd56_usbdev_s *priv); |
| static inline void cxd56_ep0setup(struct cxd56_usbdev_s *priv); |
| static int cxd56_usbinterrupt(int irq, void *context, void *arg); |
| static int cxd56_sysinterrupt(int irq, void *context, void *arg); |
| static int cxd56_vbusinterrupt(int irq, void *context, void *arg); |
| static int cxd56_vbusninterrupt(int irq, void *context, void *arg); |
| |
| /* Initialization operations */ |
| |
| static inline void cxd56_ep0hwinitialize(struct cxd56_usbdev_s *priv); |
| static void cxd56_ctrlinitialize(struct cxd56_usbdev_s *priv); |
| static void cxd56_epinitialize(struct cxd56_usbdev_s *priv); |
| |
| /* Un-initialization operations */ |
| |
| static void cxd56_usbhwuninit(void); |
| |
| /* Endpoint methods */ |
| |
| static int cxd56_epconfigure(struct usbdev_ep_s *ep, |
| const struct usb_epdesc_s *desc, bool last); |
| static int cxd56_epdisable(struct usbdev_ep_s *ep); |
| static struct usbdev_req_s *cxd56_epallocreq(struct usbdev_ep_s *ep); |
| static void cxd56_epfreereq(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req); |
| #ifdef CONFIG_USBDEV_DMA |
| static void *cxd56_epallocbuffer(struct usbdev_ep_s *ep, |
| uint16_t nbytes); |
| static void cxd56_epfreebuffer(struct usbdev_ep_s *ep, void *buf); |
| #endif |
| static int cxd56_epsubmit(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *privreq); |
| static int cxd56_epcancel(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *privreq); |
| static int cxd56_epstall(struct usbdev_ep_s *ep, bool resume); |
| |
| /* USB device controller methods */ |
| |
| static struct usbdev_ep_s *cxd56_allocep(struct usbdev_s *dev, |
| uint8_t epno, bool in, |
| uint8_t eptype); |
| static void cxd56_freeep(struct usbdev_s *dev, |
| struct usbdev_ep_s *ep); |
| static int cxd56_getframe(struct usbdev_s *dev); |
| static int cxd56_wakeup(struct usbdev_s *dev); |
| static int cxd56_selfpowered(struct usbdev_s *dev, bool selfpowered); |
| static int cxd56_pullup(struct usbdev_s *dev, bool enable); |
| |
| /* Notify USB device attach/detach signal */ |
| |
| static void cxd56_notify_signal(uint16_t state, uint16_t power); |
| |
| #ifdef CONFIG_FS_PROCFS |
| |
| /* procfs methods */ |
| |
| static int cxd56_usbdev_open(struct file *filep, const char *relpath, |
| int oflags, mode_t mode); |
| static int cxd56_usbdev_close(struct file *filep); |
| static ssize_t cxd56_usbdev_read(struct file *filep, char *buffer, |
| size_t buflen); |
| static int cxd56_usbdev_dup(const struct file *oldp, |
| struct file *newp); |
| static int cxd56_usbdev_stat(const char *relpath, struct stat *buf); |
| |
| #endif |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* Endpoint methods */ |
| |
| static const struct usbdev_epops_s g_epops = |
| { |
| .configure = cxd56_epconfigure, |
| .disable = cxd56_epdisable, |
| .allocreq = cxd56_epallocreq, |
| .freereq = cxd56_epfreereq, |
| #ifdef CONFIG_USBDEV_DMA |
| .allocbuffer = cxd56_epallocbuffer, |
| .freebuffer = cxd56_epfreebuffer, |
| #endif |
| .submit = cxd56_epsubmit, |
| .cancel = cxd56_epcancel, |
| .stall = cxd56_epstall, |
| }; |
| |
| /* USB controller device methods */ |
| |
| static const struct usbdev_ops_s g_devops = |
| { |
| .allocep = cxd56_allocep, |
| .freeep = cxd56_freeep, |
| .getframe = cxd56_getframe, |
| .wakeup = cxd56_wakeup, |
| .selfpowered = cxd56_selfpowered, |
| .pullup = cxd56_pullup, |
| }; |
| |
| /* There is only one, single, pre-allocated instance of the driver |
| * structure. |
| */ |
| |
| static struct cxd56_usbdev_s g_usbdev; |
| |
| /* DMA Descriptors for each endpoints */ |
| |
| static struct cxd56_setup_desc_s aligned_data(4) g_ep0setup; |
| static struct cxd56_data_desc_s aligned_data(4) g_ep0in; |
| static struct cxd56_data_desc_s aligned_data(4) g_ep0out; |
| |
| /* Summarizes information about all CXD56 endpoints */ |
| |
| static const struct cxd56_epinfo_s g_epinfo[CXD56_NENDPOINTS] = |
| { |
| { |
| CXD56_EP0, /* EP0 */ |
| USB_EP_ATTR_XFER_CONTROL, /* Type: Control IN/OUT */ |
| CXD56_EP0MAXPACKET, /* Max packet size */ |
| CXD56_EP0BUFSIZE, /* Buffer size */ |
| }, |
| { |
| CXD56_EPBULKIN0 | USB_DIR_IN, /* Logical endpoint number: 1 IN */ |
| USB_EP_ATTR_XFER_BULK, /* Type: Bulk */ |
| CXD56_BULKMAXPACKET, /* Max packet size */ |
| CXD56_BULKBUFSIZE, /* Buffer size */ |
| }, |
| { |
| CXD56_EPBULKOUT0 | USB_DIR_OUT, /* Logical endpoint number: 2 OUT */ |
| USB_EP_ATTR_XFER_BULK, /* Type: Bulk */ |
| CXD56_BULKMAXPACKET, /* Max packet size */ |
| CXD56_BULKBUFSIZE, /* Buffer size */ |
| }, |
| { |
| CXD56_EPINTRIN0 | USB_DIR_IN, /* Logical endpoint number: 3 IN */ |
| USB_EP_ATTR_XFER_INT, /* Type: Interrupt */ |
| CXD56_INTRMAXPACKET, /* Max packet size */ |
| CXD56_INTRBUFSIZE, /* Buffer size */ |
| }, |
| { |
| CXD56_EPBULKIN1 | USB_DIR_IN, /* Logical endpoint number: 4 IN */ |
| USB_EP_ATTR_XFER_BULK, /* Type: Bulk */ |
| CXD56_BULKMAXPACKET, /* Max packet size */ |
| CXD56_BULKBUFSIZE, /* Buffer size */ |
| }, |
| { |
| CXD56_EPBULKOUT1 | USB_DIR_OUT, /* Logical endpoint number: 5 OUT */ |
| USB_EP_ATTR_XFER_BULK, /* Type: Bulk */ |
| CXD56_BULKMAXPACKET, /* Max packet size */ |
| CXD56_BULKBUFSIZE, /* Buffer size */ |
| }, |
| { |
| CXD56_EPINTRIN1 | USB_DIR_IN, /* Logical endpoint number: 6 IN */ |
| USB_EP_ATTR_XFER_INT, /* Type: Interrupt */ |
| CXD56_INTRMAXPACKET, /* Max packet size */ |
| CXD56_INTRBUFSIZE, /* Buffer size */ |
| } |
| }; |
| |
| static uint8_t g_ep0outbuffer[CXD56_EP0MAXPACKET]; |
| |
| #ifdef CONFIG_FS_PROCFS |
| |
| /* See include/nutts/fs/procfs.h |
| * We use the old-fashioned kind of initializers so that this will compile |
| * with any compiler. |
| */ |
| |
| const struct procfs_operations cxd56_usbdev_operations = |
| { |
| cxd56_usbdev_open, /* open */ |
| cxd56_usbdev_close, /* close */ |
| cxd56_usbdev_read, /* read */ |
| NULL, /* write */ |
| NULL, /* poll */ |
| cxd56_usbdev_dup, /* dup */ |
| |
| NULL, /* opendir */ |
| NULL, /* closedir */ |
| NULL, /* readdir */ |
| NULL, /* rewinddir */ |
| |
| cxd56_usbdev_stat /* stat */ |
| }; |
| |
| # ifdef CONFIG_FS_PROCFS_REGISTER |
| static const struct procfs_entry_s g_procfs_usbdev = |
| { |
| "usbdev", |
| &cxd56_usbdev_operations |
| }; |
| # endif |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: cxd56_cablestatus |
| * |
| * Description: |
| * Set VBUS connected status to system register |
| * |
| ****************************************************************************/ |
| |
| static inline void cxd56_cableconnected(bool connected) |
| { |
| uint32_t val; |
| |
| val = getreg32(CXD56_TOPREG_USB_VBUS); |
| if (connected) |
| { |
| putreg32(val | VBUS_DET, CXD56_TOPREG_USB_VBUS); |
| } |
| else |
| { |
| putreg32(val & ~VBUS_DET, CXD56_TOPREG_USB_VBUS); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_rqdequeue |
| * |
| * Description: |
| * Remove a request from an endpoint request queue |
| * |
| ****************************************************************************/ |
| |
| static struct cxd56_req_s *cxd56_rqdequeue(struct cxd56_ep_s *privep) |
| { |
| struct cxd56_req_s *ret = privep->head; |
| |
| if (ret) |
| { |
| privep->head = ret->flink; |
| if (!privep->head) |
| { |
| privep->tail = NULL; |
| } |
| |
| ret->flink = NULL; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_rqenqueue |
| * |
| * Description: |
| * Add a request from an endpoint request queue |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_rqenqueue(struct cxd56_ep_s *privep, |
| struct cxd56_req_s *req) |
| { |
| req->flink = NULL; |
| if (!privep->head) |
| { |
| privep->head = req; |
| privep->tail = req; |
| } |
| else |
| { |
| privep->tail->flink = req; |
| privep->tail = req; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epwrite |
| * |
| * Description: |
| * Endpoint write (IN) |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_epwrite(struct cxd56_ep_s *privep, uint8_t *buf, |
| uint16_t nbytes) |
| { |
| struct cxd56_data_desc_s *desc; |
| uint32_t ctrl; |
| uint8_t epphy = privep->epphy; |
| |
| /* Setup IN descriptor */ |
| |
| desc = epphy == 0 ? &g_ep0in : privep->desc; |
| |
| if (IS_BS_DMA_BUSY(desc)) |
| { |
| return 0; |
| } |
| |
| desc->buf = CXD56_PHYSADDR(buf); |
| desc->status = nbytes | DESC_LAST; /* always last descriptor */ |
| |
| /* Set Poll bit to ready to send */ |
| |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(epphy)); |
| putreg32(ctrl | USB_P | USB_CNAK, CXD56_USB_IN_EP_CONTROL(epphy)); |
| |
| return nbytes; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_abortrequest |
| * |
| * Description: |
| * Discard a request |
| * |
| ****************************************************************************/ |
| |
| static inline void cxd56_abortrequest(struct cxd56_ep_s *privep, |
| struct cxd56_req_s *privreq, |
| int16_t result) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_REQABORTED), |
| (uint16_t)privep->epphy); |
| |
| /* Save the result in the request structure */ |
| |
| privreq->req.result = result; |
| |
| /* Callback to the request completion handler */ |
| |
| privreq->req.callback(&privep->ep, &privreq->req); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_reqcomplete |
| * |
| * Description: |
| * Handle termination of a request. |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_reqcomplete(struct cxd56_ep_s *privep, int16_t result) |
| { |
| struct cxd56_req_s *privreq; |
| int stalled = privep->stalled; |
| irqstate_t flags; |
| |
| /* Remove the completed request at the head of the endpoint request list */ |
| |
| flags = enter_critical_section(); |
| privreq = cxd56_rqdequeue(privep); |
| leave_critical_section(flags); |
| |
| if (privreq) |
| { |
| /* If endpoint 0, temporarily reflect the state of protocol stalled |
| * in the callback. |
| */ |
| |
| if (privep->epphy == CXD56_EP0) |
| { |
| privep->stalled = privep->dev->stalled; |
| } |
| |
| /* Save the result in the request structure */ |
| |
| privreq->req.result = result; |
| |
| /* Callback to the request completion handler */ |
| |
| privreq->flink = NULL; |
| privreq->req.callback(&privep->ep, &privreq->req); |
| |
| /* Restore the stalled indication */ |
| |
| privep->stalled = stalled; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_txdmacomplete |
| * |
| * Description: |
| * DMA transfer to TxFIFO is completed. |
| * if exist queued request, do the next transfer request. |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_txdmacomplete(struct cxd56_ep_s *privep) |
| { |
| struct cxd56_data_desc_s *desc; |
| struct cxd56_req_s *privreq; |
| |
| desc = privep->epphy == CXD56_EP0 ? &g_ep0in : privep->desc; |
| |
| /* Avoid invalid transfer by USB Core */ |
| |
| DEBUGASSERT(IS_BS_DMA_DONE(desc)); |
| |
| desc->status |= DESC_BS_HOST_BUSY; |
| |
| privreq = cxd56_rqpeek(privep); |
| if (!privreq) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_TXREQLOST), privep->epphy); |
| } |
| else |
| { |
| privreq->req.xfrd += desc->status & DESC_SIZE_MASK; |
| |
| if (privreq->req.xfrd >= privreq->req.len && !privep->txnullpkt) |
| { |
| usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd); |
| privep->txnullpkt = 0; |
| cxd56_reqcomplete(privep, OK); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_wrrequest |
| * |
| * Description: |
| * Send from the next queued write request |
| * |
| * Returned Value: |
| * 0:not finished; 1:completed; <0:error |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_wrrequest(struct cxd56_ep_s *privep) |
| { |
| struct cxd56_req_s *privreq; |
| uint8_t *buf; |
| int nbytes; |
| int bytesleft; |
| |
| /* Check the request from the head of the endpoint request queue */ |
| |
| privreq = cxd56_rqpeek(privep); |
| if (!privreq) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_NULLREQUEST), 0); |
| return OK; |
| } |
| |
| /* Ignore any attempt to send a zero length packet on anything but EP0IN */ |
| |
| if (privreq->req.len == 0) |
| { |
| if (privep->epphy == CXD56_EP0) |
| { |
| cxd56_epwrite(privep, NULL, 0); |
| } |
| else |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_NULLPACKET), 0); |
| } |
| |
| return OK; |
| } |
| |
| /* Get the number of bytes left to be sent in the packet */ |
| |
| bytesleft = privreq->req.len - privreq->req.xfrd; |
| |
| /* Send the next packet if (1) there are more bytes to be sent, or |
| * (2) the last packet sent was exactly maxpacketsize (bytesleft == 0) |
| */ |
| |
| usbtrace(TRACE_WRITE(privep->epphy), (uint16_t)bytesleft); |
| if (bytesleft > 0 || privep->txnullpkt) |
| { |
| /* Try to send maxpacketsize -- unless we don't have that many |
| * bytes to send. |
| */ |
| |
| privep->txnullpkt = 0; |
| if (bytesleft > privep->ep.maxpacket) |
| { |
| nbytes = privep->ep.maxpacket; |
| } |
| else |
| { |
| nbytes = bytesleft; |
| if ((privreq->req.flags & USBDEV_REQFLAGS_NULLPKT) != 0) |
| { |
| privep->txnullpkt = (bytesleft == privep->ep.maxpacket); |
| } |
| } |
| |
| /* Send the largest number of bytes that we can in this packet */ |
| |
| buf = privreq->req.buf + privreq->req.xfrd; |
| cxd56_epwrite(privep, buf, nbytes); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_rxdmacomplete |
| * |
| * Description: |
| * Notify the upper layer and continue to next receive request. |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_rxdmacomplete(struct cxd56_ep_s *privep) |
| { |
| struct cxd56_data_desc_s *desc = privep->desc; |
| struct cxd56_req_s *privreq; |
| uint32_t status = desc->status; |
| uint16_t nrxbytes; |
| |
| nrxbytes = status & DESC_SIZE_MASK; |
| |
| privreq = cxd56_rqpeek(privep); |
| if (!privreq) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_RXREQLOST), privep->epphy); |
| return; |
| } |
| |
| desc->status = DESC_BS_HOST_BUSY; |
| |
| if ((status & DESC_BS_MASK) == DESC_BS_DMA_DONE) |
| { |
| privreq->req.xfrd += nrxbytes; |
| |
| if (privreq->req.xfrd >= privreq->req.len || |
| nrxbytes < privep->ep.maxpacket) |
| { |
| usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd); |
| cxd56_reqcomplete(privep, OK); |
| } |
| } |
| else |
| { |
| uerr("Descriptor status error %08" PRIx32 "\n", status); |
| } |
| |
| cxd56_rdrequest(privep); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_rdrequest |
| * |
| * Description: |
| * Receive to the next queued read request |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_rdrequest(struct cxd56_ep_s *privep) |
| { |
| struct cxd56_data_desc_s *desc = privep->desc; |
| struct cxd56_req_s *privreq; |
| uint32_t ctrl; |
| |
| /* Check the request from the head of the endpoint request queue */ |
| |
| privreq = cxd56_rqpeek(privep); |
| if (!privreq) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_EPOUTQEMPTY), 0); |
| return OK; |
| } |
| |
| /* Receive the next packet */ |
| |
| if (!IS_BS_HOST_BUSY(desc)) |
| { |
| return OK; |
| } |
| |
| usbtrace(TRACE_READ(privep->epphy), privep->ep.maxpacket); |
| |
| desc->buf = CXD56_PHYSADDR(privreq->req.buf); |
| desc->status = privep->ep.maxpacket | DESC_LAST; |
| |
| /* Ready to receive next packet */ |
| |
| ctrl = getreg32(CXD56_USB_OUT_EP_CONTROL(privep->epphy)); |
| putreg32(ctrl | USB_RRDY | USB_CNAK, |
| CXD56_USB_OUT_EP_CONTROL(privep->epphy)); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_stopinep |
| * |
| * Description: |
| * Stop IN endpoint forcibly |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_stopinep(struct cxd56_ep_s *privep) |
| { |
| uint32_t ctrl; |
| |
| /* Stop TX */ |
| |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(privep->epphy)); |
| ctrl |= USB_F; |
| putreg32(ctrl, CXD56_USB_IN_EP_CONTROL(privep->epphy)); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_stopoutep |
| * |
| * Description: |
| * Stop OUT endpoint forcibly |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_stopoutep(struct cxd56_ep_s *privep) |
| { |
| uint32_t ctrl; |
| uint32_t stat; |
| |
| /* Stop RX if FIFO not empty */ |
| |
| stat = getreg32(CXD56_USB_OUT_EP_STATUS(privep->epphy)); |
| if (stat & USB_INT_MRXFIFOEMPTY) |
| { |
| return; |
| } |
| |
| ctrl = getreg32(CXD56_USB_OUT_EP_CONTROL(privep->epphy)); |
| ctrl |= USB_CLOSEDESC | USB_MRXFLUSH; |
| putreg32(ctrl, CXD56_USB_OUT_EP_CONTROL(privep->epphy)); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_cancelrequests |
| * |
| * Description: |
| * Cancel all pending requests for an endpoint |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_cancelrequests(struct cxd56_ep_s *privep) |
| { |
| if (privep->epphy > 0) |
| { |
| if (privep->in) |
| { |
| cxd56_stopinep(privep); |
| } |
| else |
| { |
| cxd56_stopoutep(privep); |
| } |
| } |
| |
| while (!cxd56_rqempty(privep)) |
| { |
| usbtrace(TRACE_COMPLETE(privep->epphy), |
| (cxd56_rqpeek(privep))->req.xfrd); |
| cxd56_reqcomplete(privep, -ESHUTDOWN); |
| } |
| |
| if (privep->epphy > 0) |
| { |
| if (privep->in) |
| { |
| putreg32(0, CXD56_USB_IN_EP_DATADESC(privep->epphy)); |
| } |
| else |
| { |
| putreg32(0, CXD56_USB_OUT_EP_DATADESC(privep->epphy)); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epfindbyaddr |
| * |
| * Description: |
| * Find the physical endpoint structure corresponding to a logic endpoint |
| * address |
| * |
| ****************************************************************************/ |
| |
| static struct cxd56_ep_s * |
| cxd56_epfindbyaddr(struct cxd56_usbdev_s *priv, uint16_t eplog) |
| { |
| struct cxd56_ep_s *privep; |
| int i; |
| |
| /* Endpoint zero is a special case */ |
| |
| if (USB_EPNO(eplog) == 0) |
| { |
| return &priv->eplist[0]; |
| } |
| |
| /* Handle the remaining */ |
| |
| for (i = 1; i < CXD56_NENDPOINTS; i++) |
| { |
| privep = &priv->eplist[i]; |
| |
| /* Same logical endpoint number? (includes direction bit) */ |
| |
| if (eplog == privep->ep.eplog) |
| { |
| /* Return endpoint found */ |
| |
| return privep; |
| } |
| } |
| |
| /* Return endpoint not found */ |
| |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_dispatchrequest |
| * |
| * Description: |
| * Provide unhandled setup actions to the class driver |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_dispatchrequest(struct cxd56_usbdev_s *priv) |
| { |
| int ret; |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_DISPATCH), 0); |
| if (priv && priv->driver) |
| { |
| ret = CLASS_SETUP(priv->driver, &priv->usbdev, &priv->ctrl, |
| priv->ep0data, priv->ep0datlen); |
| if (ret < 0) |
| { |
| /* Stall on failure */ |
| |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_STALLEDISPATCH), |
| priv->ctrl.req); |
| priv->stalled = 1; |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_ep0setup |
| * |
| * Description: |
| * USB Ctrl EP Setup Event |
| * |
| ****************************************************************************/ |
| |
| static inline void cxd56_ep0setup(struct cxd56_usbdev_s *priv) |
| { |
| struct cxd56_ep_s *ep0 = &priv->eplist[0]; |
| struct cxd56_req_s *privreq = cxd56_rqpeek(ep0); |
| struct cxd56_ep_s *privep; |
| uint16_t index; |
| uint16_t value; |
| uint16_t len; |
| uint32_t reg; |
| |
| /* Terminate any pending requests */ |
| |
| while (!cxd56_rqempty(ep0)) |
| { |
| int16_t result = OK; |
| if (privreq->req.xfrd != privreq->req.len) |
| { |
| result = -EPROTO; |
| } |
| |
| usbtrace(TRACE_COMPLETE(ep0->epphy), privreq->req.xfrd); |
| cxd56_reqcomplete(ep0, result); |
| } |
| |
| /* Assume NOT stalled */ |
| |
| ep0->stalled = 0; |
| priv->stalled = 0; |
| |
| /* Read EP0 SETUP data */ |
| |
| memcpy(&priv->ctrl, (void *)&g_ep0setup.setup_1, USB_SIZEOF_CTRLREQ); |
| memset(&g_ep0setup, 0, USB_SIZEOF_CTRLREQ); |
| |
| index = GETUINT16(priv->ctrl.index); |
| value = GETUINT16(priv->ctrl.value); |
| len = GETUINT16(priv->ctrl.len); |
| |
| priv->ep0reqlen = len; |
| |
| uinfo("type=%02x req=%02x value=%04x index=%04x len=%04x\n", |
| priv->ctrl.type, priv->ctrl.req, value, index, len); |
| |
| ep0->in = (priv->ctrl.type & USB_DIR_IN) != 0; |
| |
| /* Is this an setup with OUT and data of length > 0 */ |
| |
| if (USB_REQ_ISOUT(priv->ctrl.type) && len != priv->ep0datlen) |
| { |
| /* At this point priv->ctrl is the setup packet. */ |
| |
| return; |
| } |
| |
| /* Dispatch any non-standard requests */ |
| |
| if ((priv->ctrl.type & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD) |
| { |
| cxd56_dispatchrequest(priv); |
| } |
| else |
| { |
| /* Handle standard request. Pick off the things of interest to the |
| * USB device controller driver; pass what is left to the class driver |
| */ |
| |
| switch (priv->ctrl.req) |
| { |
| case USB_REQ_GETSTATUS: |
| { |
| /* type: device-to-host; |
| * recipient = device, |
| * interface, |
| * endpoint |
| * value: 0 |
| * index: zero interface endpoint |
| * len: 2; data = status |
| */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_GETSTATUS), 0); |
| |
| if (len != 2 || (priv->ctrl.type & USB_REQ_DIR_IN) == 0 || |
| value != 0) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_STALLEDGETST), |
| priv->ctrl.req); |
| priv->stalled = 1; |
| } |
| else |
| { |
| switch (priv->ctrl.type & USB_REQ_RECIPIENT_MASK) |
| { |
| case USB_REQ_RECIPIENT_ENDPOINT: |
| { |
| usbtrace(TRACE_INTDECODE( |
| CXD56_TRACEINTID_GETENDPOINT), |
| 0); |
| privep = cxd56_epfindbyaddr(priv, index); |
| if (!privep) |
| { |
| usbtrace( |
| TRACE_DEVERROR( |
| CXD56_TRACEERR_STALLEDGETSTEP), |
| priv->ctrl.type); |
| priv->stalled = 1; |
| } |
| } |
| break; |
| |
| case USB_REQ_RECIPIENT_DEVICE: |
| case USB_REQ_RECIPIENT_INTERFACE: |
| usbtrace(TRACE_INTDECODE( |
| CXD56_TRACEINTID_GETIFDEV), |
| 0); |
| break; |
| |
| default: |
| { |
| usbtrace(TRACE_DEVERROR( |
| CXD56_TRACEERR_STALLEDGETSTRECIP), |
| priv->ctrl.type); |
| priv->stalled = 1; |
| } |
| 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(CXD56_TRACEINTID_CLEARFEATURE), |
| (uint16_t)priv->ctrl.req); |
| if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) != |
| USB_REQ_RECIPIENT_ENDPOINT) |
| { |
| cxd56_dispatchrequest(priv); |
| } |
| else if (priv->paddrset != 0 && |
| value == USB_FEATURE_ENDPOINTHALT && |
| len == 0 && |
| (privep = cxd56_epfindbyaddr(priv, index)) != NULL) |
| { |
| privep->halted = 0; |
| cxd56_epstall(&privep->ep, true); |
| cxd56_epwrite(ep0, NULL, 0); |
| } |
| else |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_STALLEDCLRFEATURE), |
| priv->ctrl.type); |
| priv->stalled = 1; |
| } |
| } |
| 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(CXD56_TRACEINTID_SETFEATURE), 0); |
| if (priv->ctrl.type == USB_REQ_RECIPIENT_DEVICE && |
| value == USB_FEATURE_TESTMODE) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_TESTMODE), |
| index); |
| } |
| else if (priv->ctrl.type != USB_REQ_RECIPIENT_ENDPOINT) |
| { |
| cxd56_dispatchrequest(priv); |
| } |
| else if (value == USB_FEATURE_ENDPOINTHALT && len == 0 && |
| (privep = cxd56_epfindbyaddr(priv, index)) != NULL) |
| { |
| privep->halted = 1; |
| } |
| else |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_STALLEDSETFEATURE), |
| priv->ctrl.type); |
| priv->stalled = 1; |
| } |
| } |
| break; |
| |
| case USB_REQ_SETADDRESS: |
| { |
| /* type: host-to-device; recipient = device |
| * value: device address |
| * index: 0 |
| * len: 0; data = none |
| */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_SETADDRESS), 0); |
| priv->paddr = value & 0xff; |
| } |
| 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(CXD56_TRACEINTID_GETSETDESC), 0); |
| cxd56_dispatchrequest(priv); |
| } |
| |
| break; |
| |
| case USB_REQ_GETCONFIGURATION: |
| /* type: device-to-host; recipient = device |
| * value: 0; |
| * index: 0; |
| * len: 1; data = configuration value |
| */ |
| |
| case USB_REQ_SETCONFIGURATION: |
| /* type: host-to-device; recipient = device |
| * value: configuration value |
| * index: 0; |
| * len: 0; data = none |
| */ |
| |
| 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 |
| */ |
| |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_GETSETIFCONFIG), 0); |
| cxd56_dispatchrequest(priv); |
| } |
| |
| break; |
| |
| case USB_REQ_SYNCHFRAME: |
| { |
| /* type: device-to-host; recipient = endpoint |
| * value: 0 |
| * index: endpoint; |
| * len: 2; data = frame number |
| */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_SYNCHFRAME), 0); |
| } |
| break; |
| |
| default: |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_STALLEDREQUEST), |
| priv->ctrl.req); |
| priv->stalled = 1; |
| } |
| break; |
| } |
| } |
| |
| /* Check if the setup processing resulted in a STALL */ |
| |
| if (priv->stalled) |
| { |
| reg = getreg32(CXD56_USB_IN_EP_CONTROL(0)); |
| putreg32(reg | USB_STALL, CXD56_USB_IN_EP_CONTROL(0)); |
| reg = getreg32(CXD56_USB_OUT_EP_CONTROL(0)); |
| putreg32(reg | USB_STALL, CXD56_USB_OUT_EP_CONTROL(0)); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epinterrupt |
| * |
| * Description: |
| * Handle USB endpoint interrupts |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_epinterrupt(int irq, void *context) |
| { |
| struct cxd56_usbdev_s *priv = &g_usbdev; |
| struct cxd56_ep_s *privep; |
| uint32_t eps; |
| uint32_t stat; |
| uint32_t ctrl; |
| uint16_t len; |
| int n; |
| |
| eps = getreg32(CXD56_USB_DEV_EP_INTR); |
| { |
| for (n = 0; n < CXD56_NENDPOINTS; n++) |
| { |
| /* Determine IN endpoint interrupts */ |
| |
| privep = &priv->eplist[n]; |
| |
| if (eps & (1 << n)) |
| { |
| stat = getreg32(CXD56_USB_IN_EP_STATUS(n)); |
| |
| if (stat & USB_INT_RCS) |
| { |
| /* Handle Clear_Feature */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_CLEARFEATURE), |
| n); |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(n)); |
| putreg32(ctrl | USB_F, CXD56_USB_IN_EP_CONTROL(n)); |
| putreg32(ctrl | USB_CNAK, CXD56_USB_IN_EP_CONTROL(n)); |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(n)); |
| putreg32(USB_INT_RCS, CXD56_USB_IN_EP_STATUS(n)); |
| |
| privep->stalled = 0; |
| privep->halted = 0; |
| } |
| |
| if (stat & USB_INT_RSS) |
| { |
| /* Handle Set_Feature */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_SETFEATURE), n); |
| putreg32(USB_INT_RSS, CXD56_USB_IN_EP_STATUS(n)); |
| privep->halted = 1; |
| } |
| |
| if (stat & USB_INT_TXEMPTY) |
| { |
| /* Transmit FIFO Empty detected */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_TXEMPTY), n); |
| putreg32(USB_INT_TXEMPTY, CXD56_USB_IN_EP_STATUS(n)); |
| } |
| |
| if (stat & USB_INT_TDC) |
| { |
| /* DMA Transmit complete for TxFIFO */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_TDC), n); |
| putreg32(USB_INT_TDC, CXD56_USB_IN_EP_STATUS(n)); |
| } |
| |
| if (stat & USB_INT_XFERDONE) |
| { |
| /* Transfer Done/Transmit FIFO Empty */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_XFERDONE), n); |
| |
| /* Set NAK during processing IN request completion */ |
| |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(n)); |
| putreg32(ctrl | USB_SNAK, CXD56_USB_IN_EP_CONTROL(n)); |
| |
| putreg32(USB_INT_XFERDONE, CXD56_USB_IN_EP_STATUS(n)); |
| |
| cxd56_txdmacomplete(privep); |
| |
| /* Clear NAK to raise IN interrupt for send next IN |
| * packets. |
| */ |
| |
| putreg32(ctrl | USB_CNAK, CXD56_USB_IN_EP_CONTROL(n)); |
| } |
| |
| if (stat & USB_INT_IN) |
| { |
| /* Reply NAK for IN token when TxFIFO empty */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_IN), n); |
| |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(n)); |
| putreg32(ctrl | USB_SNAK, CXD56_USB_IN_EP_CONTROL(n)); |
| |
| /* If IN request is ready, then send it. */ |
| |
| if (!cxd56_rqempty(privep)) |
| { |
| cxd56_wrrequest(privep); |
| } |
| else |
| { |
| privep->txwait = 1; |
| } |
| |
| putreg32(USB_INT_IN, CXD56_USB_IN_EP_STATUS(n)); |
| } |
| |
| if (stat & USB_INT_HE) |
| { |
| /* Detect AHB Bus error */ |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_TXDMAERROR), n); |
| putreg32(USB_INT_HE, CXD56_USB_IN_EP_STATUS(n)); |
| } |
| |
| if (stat & USB_INT_BNA) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_TXBNA), n); |
| putreg32(USB_INT_BNA, CXD56_USB_IN_EP_STATUS(n)); |
| } |
| |
| putreg32(1 << n, CXD56_USB_DEV_EP_INTR); |
| } |
| |
| /* Determine OUT endpoint interrupts */ |
| |
| if (eps & (1 << (n + 16))) |
| { |
| stat = getreg32(CXD56_USB_OUT_EP_STATUS(n)); |
| |
| if (USB_INT_OUT(stat) == USB_INT_OUT_SETUP) |
| { |
| putreg32(USB_INT_OUT_SETUP, CXD56_USB_OUT_EP_STATUS(n)); |
| if (n == 0) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_OUTSETUP), |
| 0); |
| |
| ctrl = getreg32(CXD56_USB_OUT_EP_CONTROL(0)); |
| putreg32(ctrl | USB_SNAK, CXD56_USB_OUT_EP_CONTROL(0)); |
| |
| cxd56_ep0setup(priv); |
| |
| putreg32(ctrl | USB_CNAK | USB_RRDY, |
| CXD56_USB_OUT_EP_CONTROL(0)); |
| |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(0)); |
| putreg32(ctrl | USB_CNAK, CXD56_USB_IN_EP_CONTROL(0)); |
| } |
| } |
| |
| if (USB_INT_OUT(stat) == USB_INT_OUT_DATA) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_OUTDATA), n); |
| putreg32(USB_INT_OUT_DATA, CXD56_USB_OUT_EP_STATUS(n)); |
| if (n == 0) |
| { |
| len = g_ep0out.status & DESC_SIZE_MASK; |
| |
| /* Reset DMA descriptor for next packet */ |
| |
| g_ep0out.status = privep->ep.maxpacket | DESC_LAST; |
| |
| if (0 < len) |
| { |
| ASSERT(priv->ep0datlen + len <= |
| sizeof(priv->ep0data)); |
| |
| memcpy(priv->ep0data + priv->ep0datlen, |
| (const void *)g_ep0out.buf, |
| len); |
| |
| priv->ep0datlen += len; |
| } |
| |
| /* Dispatch to cxd56_ep0setup if received all OUT |
| * data. |
| */ |
| |
| if (priv->ep0datlen == priv->ep0reqlen) |
| { |
| if (((priv->ctrl.type & USB_REQ_TYPE_MASK) != |
| USB_REQ_TYPE_STANDARD) && |
| USB_REQ_ISOUT(priv->ctrl.type)) |
| { |
| /* Ready to receive the next setup packet */ |
| |
| ctrl = getreg32(CXD56_USB_OUT_EP_CONTROL(0)); |
| putreg32(ctrl | USB_SNAK | USB_RRDY, |
| CXD56_USB_OUT_EP_CONTROL(0)); |
| |
| cxd56_ep0setup(priv); |
| priv->ep0datlen = 0; |
| } |
| } |
| else |
| { |
| /* Ready to receive the next OUT packet */ |
| |
| ctrl = getreg32(CXD56_USB_OUT_EP_CONTROL(0)); |
| putreg32(ctrl | USB_CNAK | USB_RRDY, |
| CXD56_USB_OUT_EP_CONTROL(0)); |
| } |
| } |
| else |
| { |
| cxd56_rxdmacomplete(privep); |
| } |
| } |
| |
| if (stat & USB_INT_CDC_CLEAR) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_CDCCLEAR), n); |
| putreg32(USB_INT_CDC_CLEAR, CXD56_USB_OUT_EP_STATUS(n)); |
| } |
| |
| if (stat & USB_INT_RSS) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_SETFEATURE), n); |
| ctrl = getreg32(CXD56_USB_OUT_EP_CONTROL(0)); |
| putreg32(USB_INT_RSS, CXD56_USB_OUT_EP_STATUS(n)); |
| privep->halted = 1; |
| } |
| |
| if (stat & USB_INT_RCS) |
| { |
| uint32_t status; |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_CLEARFEATURE), |
| n); |
| |
| ctrl = getreg32(CXD56_USB_OUT_EP_CONTROL(n)); |
| |
| /* Make sure that want the DMA transfer stopped. */ |
| |
| /* The S bit needs to be clear by hand */ |
| |
| ctrl &= ~USB_STALL; |
| |
| putreg32(ctrl | USB_CLOSEDESC, |
| CXD56_USB_OUT_EP_CONTROL(n)); |
| do |
| { |
| status = getreg32(CXD56_USB_OUT_EP_STATUS(n)); |
| } |
| while (!(status & USB_INT_CDC_CLEAR)); |
| putreg32(USB_INT_CDC_CLEAR, CXD56_USB_OUT_EP_STATUS(n)); |
| |
| if (!(stat & USB_INT_MRXFIFOEMPTY)) |
| { |
| /* Flush Receive FIFO and clear NAK to finish status |
| * stage. |
| */ |
| |
| putreg32(ctrl | USB_MRXFLUSH, |
| CXD56_USB_OUT_EP_CONTROL(n)); |
| } |
| |
| putreg32(ctrl | USB_CNAK, CXD56_USB_OUT_EP_CONTROL(n)); |
| putreg32(USB_INT_RCS, CXD56_USB_OUT_EP_STATUS(n)); |
| privep->stalled = 0; |
| privep->halted = 0; |
| } |
| |
| if (stat & USB_INT_HE) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_RXDMAERROR), n); |
| putreg32(USB_INT_HE, CXD56_USB_OUT_EP_STATUS(n)); |
| } |
| |
| if (stat & USB_INT_BNA) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_RXBNA), n); |
| cxd56_rdrequest(privep); |
| putreg32(USB_INT_BNA, CXD56_USB_OUT_EP_STATUS(n)); |
| } |
| |
| putreg32(1 << (n + 16), CXD56_USB_DEV_EP_INTR); |
| } |
| } |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbinterrupt |
| * |
| * Description: |
| * Handle USB controller core interrupts |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_usbinterrupt(int irq, void *context, void *arg) |
| { |
| struct usb_ctrlreq_s ctrl; |
| uint32_t intr; |
| uint32_t status; |
| int ret; |
| |
| intr = getreg32(CXD56_USB_DEV_INTR); |
| putreg32(intr, CXD56_USB_DEV_INTR); |
| |
| usbtrace(TRACE_INTENTRY(CXD56_TRACEINTID_USB), intr & 0xff); |
| |
| /* Set/Clear Remove Wakeup is received by the core */ |
| |
| if (intr & USB_INT_RMTWKP_STATE) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_RMTWKP), 0); |
| } |
| |
| /* Speed enumeration is complete */ |
| |
| if (intr & USB_INT_ENUM) |
| { |
| struct cxd56_usbdev_s *priv = &g_usbdev; |
| uint32_t speed; |
| uint32_t config; |
| |
| /* Read established speed type (high or full) */ |
| |
| speed = USB_STATUS_SPD(getreg32(CXD56_USB_DEV_STATUS)); |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_ENUM), speed); |
| |
| /* Set established speed type to device configuration and device |
| * instance. |
| */ |
| |
| config = getreg32(CXD56_USB_DEV_CONFIG) & ~USB_CONFIG_SPD_MASK; |
| if (speed == USB_CONFIG_HS) |
| { |
| priv->usbdev.speed = USB_SPEED_HIGH; |
| config |= USB_CONFIG_HS; |
| } |
| else if (speed == USB_CONFIG_FS) |
| { |
| priv->usbdev.speed = USB_SPEED_FULL; |
| config |= USB_CONFIG_FS; |
| } |
| |
| putreg32(config, CXD56_USB_DEV_CONFIG); |
| } |
| |
| /* An SOF token is detected */ |
| |
| if (intr & USB_INT_SOF) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_SOF), 0); |
| } |
| |
| /* A suspend state is detected */ |
| |
| if (intr & USB_INT_US) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_US), 0); |
| } |
| |
| /* A USB Reset is detected */ |
| |
| if (intr & USB_INT_UR) |
| { |
| int i; |
| |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_UR), 0); |
| |
| for (i = 1; i < CXD56_NENDPOINTS; i++) |
| { |
| struct cxd56_ep_s *privep = |
| (struct cxd56_ep_s *)&g_usbdev.eplist[i]; |
| |
| cxd56_cancelrequests(privep); |
| } |
| |
| cxd56_pullup(&g_usbdev.usbdev, false); |
| if (g_usbdev.driver) |
| { |
| CLASS_DISCONNECT(g_usbdev.driver, &g_usbdev.usbdev); |
| } |
| } |
| |
| /* An idle state is detected */ |
| |
| if (intr & USB_INT_ES) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_ES), 0); |
| } |
| |
| /* The device has received a Set_Interface command */ |
| |
| if (intr & USB_INT_SI) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_SI), 0); |
| status = getreg32(CXD56_USB_DEV_STATUS); |
| memset(&ctrl, 0, USB_SIZEOF_CTRLREQ); |
| ctrl.type = USB_REQ_RECIPIENT_INTERFACE; |
| ctrl.req = USB_REQ_SETINTERFACE; |
| ctrl.value[0] = USB_STATUS_ALT(status); |
| ctrl.index[0] = USB_STATUS_INTF(status); |
| g_usbdev.ctrl = ctrl; |
| ret = CLASS_SETUP(g_usbdev.driver, &g_usbdev.usbdev, &ctrl, NULL, 0); |
| if (ret < 0) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_STALLEDISPATCH), |
| USB_REQ_SETINTERFACE); |
| g_usbdev.stalled = 1; |
| } |
| |
| putreg32(getreg32(CXD56_USB_DEV_CONTROL) | USB_CTRL_CSR_DONE, |
| CXD56_USB_DEV_CONTROL); |
| } |
| |
| /* The device has received a Set_Configuration command */ |
| |
| if (intr & USB_INT_SC) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_SC), 0); |
| status = getreg32(CXD56_USB_DEV_STATUS); |
| memset(&ctrl, 0, USB_SIZEOF_CTRLREQ); |
| ctrl.req = USB_REQ_SETCONFIGURATION; |
| ctrl.value[0] = USB_STATUS_CFG(status); |
| g_usbdev.ctrl = ctrl; |
| ret = CLASS_SETUP(g_usbdev.driver, &g_usbdev.usbdev, &ctrl, NULL, 0); |
| if (ret < 0) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_STALLEDISPATCH), |
| USB_REQ_SETCONFIGURATION); |
| g_usbdev.stalled = 1; |
| } |
| |
| putreg32(getreg32(CXD56_USB_DEV_CONTROL) | USB_CTRL_CSR_DONE, |
| CXD56_USB_DEV_CONTROL); |
| } |
| |
| /* Handle each EP interrupts */ |
| |
| cxd56_epinterrupt(irq, context); |
| |
| usbtrace(TRACE_INTEXIT(CXD56_TRACEINTID_USB), 0); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_sysinterrupt |
| * |
| * Description: |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_sysinterrupt(int irq, void *context, void *arg) |
| { |
| struct cxd56_usbdev_s *priv = (struct cxd56_usbdev_s *)arg; |
| uint32_t status; |
| |
| UNUSED(priv); |
| |
| usbtrace(TRACE_INTENTRY(CXD56_TRACEINTID_SYS), 0); |
| |
| status = getreg32(CXD56_USB_SYS_INTR); |
| putreg32(status, CXD56_USB_SYS_INTR); |
| |
| if (status & USB_INT_RESUME) |
| { |
| usbtrace(TRACE_INTDECODE(CXD56_TRACEINTID_RESUME), 0); |
| } |
| |
| usbtrace(TRACE_INTEXIT(CXD56_TRACEINTID_SYS), 0); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_ep0hwinitialize |
| * |
| * Description: |
| * Initialize endpoints. This is logically a part of cxd56_ctrlinitialize |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_ep0hwinitialize(struct cxd56_usbdev_s *priv) |
| { |
| uint32_t maxp = g_epinfo[0].maxpacket; |
| uint32_t bufsz = g_epinfo[0].bufsize / 4; |
| uint32_t status; |
| |
| /* Initialize DMA descriptors */ |
| |
| memset(&g_ep0setup, 0, sizeof(g_ep0setup)); |
| memset(&g_ep0in, 0, sizeof(g_ep0in)); |
| memset(&g_ep0out, 0, sizeof(g_ep0out)); |
| |
| g_ep0out.buf = CXD56_PHYSADDR(g_ep0outbuffer); |
| g_ep0out.status = CXD56_EP0MAXPACKET | DESC_LAST; |
| |
| putreg32(CXD56_PHYSADDR(&g_ep0setup), CXD56_USB_OUT_EP_SETUP(0)); |
| putreg32(CXD56_PHYSADDR(&g_ep0in), CXD56_USB_IN_EP_DATADESC(0)); |
| putreg32(CXD56_PHYSADDR(&g_ep0out), CXD56_USB_OUT_EP_DATADESC(0)); |
| |
| /* Clear all interrupts */ |
| |
| status = getreg32(CXD56_USB_IN_EP_STATUS(0)); |
| putreg32(status, CXD56_USB_IN_EP_STATUS(0)); |
| status = getreg32(CXD56_USB_OUT_EP_STATUS(0)); |
| putreg32(status, CXD56_USB_OUT_EP_STATUS(0)); |
| |
| /* EP0 setup for control */ |
| |
| putreg32(maxp, CXD56_USB_IN_EP_MAXPKTSIZE(0)); |
| putreg32(bufsz, CXD56_USB_IN_EP_BUFSIZE(0)); |
| putreg32(maxp | (bufsz << 16), CXD56_USB_OUT_EP_BUFSIZE(0)); |
| putreg32(USB_ET(USB_EP_CONTROL) | USB_F, CXD56_USB_IN_EP_CONTROL(0)); |
| putreg32(USB_ET(USB_EP_CONTROL) | USB_SNAK, CXD56_USB_OUT_EP_CONTROL(0)); |
| putreg32(maxp << 19, CXD56_USB_DEV_UDC_EP(0)); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_ctrlinitialize |
| * |
| * Description: |
| * Initialize the CXD56 USB controller for peripheral mode operation . |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_ctrlinitialize(struct cxd56_usbdev_s *priv) |
| { |
| uint32_t ctrl; |
| uint32_t config; |
| |
| config = USB_CONFIG_CSR_PRG | USB_CONFIG_PI | USB_CONFIG_RWKP; |
| #ifdef CONFIG_USBDEV_SELFPOWERED |
| config |= USB_CONFIG_SP; |
| #endif |
| putreg32(config, CXD56_USB_DEV_CONFIG); |
| ctrl = USB_CTRL_SD | USB_CTRL_RES; |
| putreg32(ctrl, CXD56_USB_DEV_CONTROL); |
| |
| /* Polling wait for resumed */ |
| |
| while (getreg32(CXD56_USB_DEV_STATUS) & USB_STATUS_SUSP); |
| |
| ctrl |= USB_CTRL_MODE | USB_CTRL_RDE | USB_CTRL_TDE; |
| putreg32(ctrl, CXD56_USB_DEV_CONTROL); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbdevreset |
| * |
| * Description: |
| * Reset USB engine |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_usbdevreset(struct cxd56_usbdev_s *priv) |
| { |
| uint32_t mask; |
| int i; |
| int timeout = 0; |
| |
| /* Initialize USB Device controller */ |
| |
| putreg32(0, CXD56_USB_RESET); |
| putreg32(1, CXD56_USB_RESET); |
| putreg32(getreg32(CXD56_USB_PHY_CONFIG1) | PHY_PLLENABLE, |
| CXD56_USB_PHY_CONFIG1); |
| |
| while (!(getreg32(CXD56_USB_SYS_INTR) & USB_INT_READY)) |
| { |
| timeout++; |
| if (timeout > CXD56_USBDEV_TIMEOUT) |
| { |
| uinfo("usb reset timeout.\n"); |
| break; |
| } |
| |
| up_mdelay(1); |
| } |
| |
| /* Workaround for recovery from reset to slow issue. |
| * Wait to recover from usb reset condition, |
| * until any register value can read correctly. |
| */ |
| |
| while (getreg32(CXD56_USB_DEV_INTR_MASK) == 0) |
| { |
| timeout++; |
| if (timeout > CXD56_USBDEV_TIMEOUT) |
| { |
| uinfo("intr mask register timeout.\n"); |
| break; |
| } |
| |
| up_mdelay(1); |
| } |
| |
| putreg32(USB_INT_READY, CXD56_USB_SYS_INTR); |
| putreg32(1 << 24, CXD56_USB_PJ_DEMAND); /* XXX */ |
| |
| cxd56_pullup(&priv->usbdev, false); |
| |
| /* Initialize the CXD56 USB controller for DMA mode operation. */ |
| |
| cxd56_ctrlinitialize(priv); |
| |
| cxd56_ep0hwinitialize(priv); |
| |
| for (i = 1; i < CXD56_NENDPOINTS; i++) |
| { |
| const struct cxd56_epinfo_s *info = &g_epinfo[i]; |
| uint32_t stat; |
| |
| if (USB_ISEPIN(info->addr)) |
| { |
| stat = getreg32(CXD56_USB_IN_EP_STATUS(i)); |
| putreg32(stat, CXD56_USB_IN_EP_STATUS(i)); |
| putreg32(info->maxpacket, CXD56_USB_IN_EP_MAXPKTSIZE(i)); |
| putreg32(info->bufsize / 4, CXD56_USB_IN_EP_BUFSIZE(i)); |
| putreg32(USB_ET(info->attr) | USB_F, CXD56_USB_IN_EP_CONTROL(i)); |
| } |
| else |
| { |
| stat = getreg32(CXD56_USB_OUT_EP_STATUS(i)); |
| putreg32(stat, CXD56_USB_OUT_EP_STATUS(i)); |
| putreg32(info->maxpacket | ((info->bufsize / 4) << 16), |
| CXD56_USB_OUT_EP_BUFSIZE(i)); |
| putreg32(USB_ET(info->attr) | USB_SNAK, |
| CXD56_USB_OUT_EP_CONTROL(i)); |
| } |
| } |
| |
| /* Enable device interrupts */ |
| |
| mask = getreg32(CXD56_USB_DEV_INTR_MASK); |
| mask &= ~(USB_INT_RMTWKP_STATE | USB_INT_ENUM | USB_INT_UR | USB_INT_SI | |
| USB_INT_SC); |
| putreg32(mask, CXD56_USB_DEV_INTR_MASK); |
| |
| /* Enable EP0 IN/OUT */ |
| |
| mask = getreg32(CXD56_USB_DEV_EP_INTR_MASK); |
| mask &= ~(1 << 16 | 1); |
| putreg32(mask, CXD56_USB_DEV_EP_INTR_MASK); |
| } |
| |
| /**************************************************************************** |
| * Endpoint Methods |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: cxd56_epconfigure |
| * |
| * Description: |
| * Configure endpoint, making it usable |
| * |
| * Input Parameters: |
| * ep - the struct usbdev_ep_s instance obtained from allocep() |
| * desc - A struct usb_epdesc_s instance describing the endpoint |
| * last - true if this is the last endpoint to be configured. Some |
| * hardware needs to take special action when all of the endpoints |
| * have been configured. |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_epconfigure(struct usbdev_ep_s *ep, |
| const struct usb_epdesc_s *desc, bool last) |
| { |
| struct cxd56_ep_s *privep = (struct cxd56_ep_s *)ep; |
| int n; |
| int eptype; |
| uint16_t maxpacket; |
| uint32_t status; |
| uint32_t udc; |
| uint32_t addr; |
| uint32_t ctrl; |
| |
| usbtrace(TRACE_EPCONFIGURE, privep->epphy); |
| DEBUGASSERT(desc->addr == ep->eplog); |
| |
| n = privep->epphy; |
| eptype = desc->attr & USB_EP_ATTR_XFERTYPE_MASK; |
| maxpacket = GETUINT16(desc->mxpacketsize); |
| ep->maxpacket = maxpacket; |
| |
| status = getreg32(CXD56_USB_DEV_STATUS); |
| |
| uinfo("config: EP%d %s %d maxpacket=%d (status: %08" PRIx32 ")\n", n, |
| privep->in ? "IN" : "OUT", eptype, maxpacket, status); |
| |
| udc = n; |
| udc |= privep->in ? (1 << 4) : 0; |
| udc |= eptype << 5; |
| udc |= USB_STATUS_CFG(status) << 7; |
| udc |= USB_STATUS_INTF(status) << 11; |
| udc |= USB_STATUS_ALT(status) << 15; |
| udc |= maxpacket << 19; |
| uinfo("UDC: %08" PRIx32 "\n", udc); |
| |
| /* This register is write-only (why?) */ |
| |
| putreg32(udc, CXD56_USB_DEV_UDC_EP(n)); |
| |
| /* Write to UDC EP register takes time, so wait for the USBBusy bit */ |
| |
| while (getreg32(CXD56_USB_BUSY)); |
| |
| /* Off STALL bit and enable receive */ |
| |
| addr = USB_ISEPIN(ep->eplog) ? CXD56_USB_IN_EP_CONTROL(privep->epphy) |
| : CXD56_USB_OUT_EP_CONTROL(privep->epphy); |
| ctrl = getreg32(addr); |
| |
| putreg32((ctrl & ~USB_STALL), addr); |
| |
| privep->stalled = 0; |
| |
| /* Clear and setup DMA descriptor */ |
| |
| privep->desc->status = DESC_BS_HOST_BUSY; |
| privep->desc->buf = 0; |
| |
| if (privep->in) |
| { |
| putreg32(CXD56_PHYSADDR(privep->desc), |
| CXD56_USB_IN_EP_DATADESC(privep->epphy)); |
| } |
| else |
| { |
| putreg32(CXD56_PHYSADDR(privep->desc), |
| CXD56_USB_OUT_EP_DATADESC(privep->epphy)); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epdisable |
| * |
| * Description: |
| * The endpoint will no longer be used |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_epdisable(struct usbdev_ep_s *ep) |
| { |
| struct cxd56_ep_s *privep = (struct cxd56_ep_s *)ep; |
| irqstate_t flags; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!ep) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_INVALIDPARMS), 0); |
| return -EINVAL; |
| } |
| |
| #endif |
| usbtrace(TRACE_EPDISABLE, privep->epphy); |
| uinfo("EP%d\n", ((struct cxd56_ep_s *)ep)->epphy); |
| |
| /* Cancel any ongoing activity and reset the endpoint */ |
| |
| flags = enter_critical_section(); |
| cxd56_epstall(&privep->ep, false); |
| cxd56_cancelrequests(privep); |
| leave_critical_section(flags); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epallocreq |
| * |
| * Description: |
| * Allocate an I/O request |
| * |
| ****************************************************************************/ |
| |
| static struct usbdev_req_s *cxd56_epallocreq(struct usbdev_ep_s *ep) |
| { |
| struct cxd56_req_s *privreq; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!ep) |
| { |
| return NULL; |
| } |
| |
| #endif |
| usbtrace(TRACE_EPALLOCREQ, ((struct cxd56_ep_s *)ep)->epphy); |
| |
| privreq = kmm_malloc(sizeof(struct cxd56_req_s)); |
| if (!privreq) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_ALLOCFAIL), 0); |
| return NULL; |
| } |
| |
| memset(privreq, 0, sizeof(struct cxd56_req_s)); |
| return &privreq->req; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epfreereq |
| * |
| * Description: |
| * Free an I/O request |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_epfreereq(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req) |
| { |
| struct cxd56_req_s *privreq = (struct cxd56_req_s *)req; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!ep || !req) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_INVALIDPARMS), 0); |
| return; |
| } |
| #endif |
| |
| usbtrace(TRACE_EPFREEREQ, ((struct cxd56_ep_s *)ep)->epphy); |
| kmm_free(privreq); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epallocbuffer |
| * |
| * Description: |
| * Allocate an I/O buffer |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_USBDEV_DMA |
| static void *cxd56_epallocbuffer(struct usbdev_ep_s *ep, |
| uint16_t bytes) |
| { |
| struct cxd56_ep_s *privep = (struct cxd56_ep_s *)ep; |
| |
| UNUSED(privep); |
| usbtrace(TRACE_EPALLOCBUFFER, privep->epphy); |
| |
| return kmm_malloc(bytes); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: cxd56_epfreebuffer |
| * |
| * Description: |
| * Free an I/O buffer |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_USBDEV_DMA |
| static void cxd56_epfreebuffer(struct usbdev_ep_s *ep, void *buf) |
| { |
| struct cxd56_ep_s *privep = (struct cxd56_ep_s *)ep; |
| |
| UNUSED(privep); |
| usbtrace(TRACE_EPFREEBUFFER, privep->epphy); |
| |
| kmm_free(buf); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: cxd56_epsubmit |
| * |
| * Description: |
| * Submit an I/O request to the endpoint |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_epsubmit(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req) |
| { |
| struct cxd56_req_s *privreq = (struct cxd56_req_s *)req; |
| struct cxd56_ep_s *privep = (struct cxd56_ep_s *)ep; |
| struct cxd56_usbdev_s *priv; |
| uint32_t ctrl; |
| irqstate_t flags; |
| int ret = OK; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!req || !req->callback || !req->buf || !ep) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_INVALIDPARMS), 0); |
| return -EINVAL; |
| } |
| |
| #endif |
| usbtrace(TRACE_EPSUBMIT, privep->epphy); |
| priv = privep->dev; |
| |
| if (!priv->driver || priv->usbdev.speed == USB_SPEED_UNKNOWN) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_NOTCONFIGURED), 0); |
| return -ESHUTDOWN; |
| } |
| |
| req->result = -EINPROGRESS; |
| req->xfrd = 0; |
| flags = enter_critical_section(); |
| |
| /* If we are stalled, then drop all requests on the floor, except OUT */ |
| |
| if (privep->stalled && privep->in) |
| { |
| cxd56_abortrequest(privep, privreq, -EBUSY); |
| ret = -EBUSY; |
| } |
| |
| /* Handle control requests. |
| * Submit on endpoint 0 is always IN request |
| */ |
| |
| else if (privep->epphy == 0) |
| { |
| cxd56_rqenqueue(privep, privreq); |
| |
| /* SetConfiguration and SetInterface are handled by hardware, |
| * USB device IP automatically returns NULL packet to host, so I drop |
| * this request and indicate complete to upper driver. |
| */ |
| |
| if (priv->ctrl.req == USB_REQ_SETCONFIGURATION || |
| priv->ctrl.req == USB_REQ_SETINTERFACE) |
| { |
| /* Nothing to transfer -- exit success, with zero bytes |
| * transferred |
| */ |
| |
| usbtrace(TRACE_COMPLETE(privep->epphy), privreq->req.xfrd); |
| cxd56_reqcomplete(privep, OK); |
| } |
| |
| if (priv->ctrl.req == USB_REQ_SETCONFIGURATION) |
| { |
| /* Notify attach signal. |
| * max supply current is returned in response to GET_CONFIGURATION |
| * from the device. host receives the response and determines the |
| * supply current value. |
| */ |
| |
| cxd56_notify_signal(USBDEV_STATE_ATTACH, priv->power); |
| } |
| |
| /* Get max supply current value from GET_CONFIGURATION response. |
| * max supply current value is stored in units of 2 mA. |
| */ |
| |
| if (priv->ctrl.req == USB_REQ_GETDESCRIPTOR && |
| priv->ctrl.value[1] == USB_DESC_TYPE_CONFIG) |
| { |
| struct usb_cfgdesc_s *cfgdesc; |
| |
| cfgdesc = (struct usb_cfgdesc_s *)req->buf; |
| priv->power = cfgdesc->mxpower * 2; |
| } |
| |
| /* If IN transaction has been requested, clear NAK bit to be able |
| * to raise IN interrupt to start IN packets. |
| */ |
| |
| if (privep->txwait) |
| { |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(privep->epphy)); |
| putreg32(ctrl | USB_CNAK, CXD56_USB_IN_EP_CONTROL(privep->epphy)); |
| } |
| } |
| |
| /* Handle IN (device-to-host) requests */ |
| |
| else if (privep->in) |
| { |
| /* Add the new request to the request queue for the IN endpoint */ |
| |
| cxd56_rqenqueue(privep, privreq); |
| usbtrace(TRACE_INREQQUEUED(privep->epphy), privreq->req.len); |
| |
| /* If IN transaction has been requested, clear NAK bit to be able |
| * to raise IN interrupt to start IN packets. |
| */ |
| |
| if (privep->txwait) |
| { |
| ctrl = getreg32(CXD56_USB_IN_EP_CONTROL(privep->epphy)); |
| putreg32(ctrl | USB_CNAK, CXD56_USB_IN_EP_CONTROL(privep->epphy)); |
| } |
| } |
| |
| /* Handle OUT (host-to-device) requests */ |
| |
| else |
| { |
| /* Add the new request to the request queue for the OUT endpoint */ |
| |
| privep->txnullpkt = 0; |
| cxd56_rqenqueue(privep, privreq); |
| usbtrace(TRACE_OUTREQQUEUED(privep->epphy), privreq->req.len); |
| |
| /* This there a incoming data pending the availability of a request? */ |
| |
| ret = cxd56_rdrequest(privep); |
| } |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epcancel |
| * |
| * Description: |
| * Cancel an I/O request previously sent to an endpoint |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_epcancel(struct usbdev_ep_s *ep, |
| struct usbdev_req_s *req) |
| { |
| struct cxd56_ep_s *privep = (struct cxd56_ep_s *)ep; |
| irqstate_t flags; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!ep || !req) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_INVALIDPARMS), 0); |
| return -EINVAL; |
| } |
| #endif |
| |
| usbtrace(TRACE_EPCANCEL, privep->epphy); |
| |
| flags = enter_critical_section(); |
| cxd56_cancelrequests(privep); |
| leave_critical_section(flags); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epstall |
| * |
| * Description: |
| * Stall or resume and endpoint |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_epstall(struct usbdev_ep_s *ep, bool resume) |
| { |
| struct cxd56_ep_s *privep = (struct cxd56_ep_s *)ep; |
| uint32_t ctrl; |
| uint32_t addr; |
| |
| addr = USB_ISEPIN(ep->eplog) ? CXD56_USB_IN_EP_CONTROL(privep->epphy) |
| : CXD56_USB_OUT_EP_CONTROL(privep->epphy); |
| |
| if (resume) |
| { |
| usbtrace(TRACE_EPRESUME, privep->epphy); |
| ctrl = getreg32(addr); |
| putreg32(ctrl & ~USB_STALL, addr); |
| privep->stalled = 0; |
| } |
| else |
| { |
| usbtrace(TRACE_EPSTALL, privep->epphy); |
| ctrl = getreg32(addr); |
| putreg32(ctrl | USB_STALL, addr); |
| privep->stalled = 1; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Device Methods |
| ****************************************************************************/ |
| |
| static int cxd56_allocepbuffer(struct cxd56_ep_s *privep) |
| { |
| DEBUGASSERT(!privep->desc && !privep->buffer); |
| DEBUGASSERT(privep->epphy); /* Do not use for EP0 */ |
| |
| privep->desc = kmm_malloc(sizeof(struct cxd56_data_desc_s)); |
| if (!privep->desc) |
| { |
| return -1; |
| } |
| |
| privep->buffer = NULL; |
| privep->desc->status = DESC_BS_HOST_BUSY; |
| privep->desc->buf = 0; |
| privep->desc->next = 0; |
| |
| if (privep->in) |
| { |
| putreg32(CXD56_PHYSADDR(privep->desc), |
| CXD56_USB_IN_EP_DATADESC(privep->epphy)); |
| } |
| else |
| { |
| putreg32(CXD56_PHYSADDR(privep->desc), |
| CXD56_USB_OUT_EP_DATADESC(privep->epphy)); |
| } |
| |
| return 0; |
| } |
| |
| static void cxd56_freeepbuffer(struct cxd56_ep_s *privep) |
| { |
| DEBUGASSERT(privep->epphy); /* Do not use for EP0 */ |
| |
| if (privep->in) |
| { |
| putreg32(0, CXD56_USB_IN_EP_DATADESC(privep->epphy)); |
| } |
| else |
| { |
| putreg32(0, CXD56_USB_OUT_EP_DATADESC(privep->epphy)); |
| } |
| |
| if (privep->desc) |
| { |
| kmm_free(privep->desc); |
| privep->desc = NULL; |
| } |
| |
| if (privep->buffer) |
| { |
| kmm_free(privep->buffer); |
| privep->buffer = NULL; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_allocep |
| * |
| * Description: |
| * Allocate an endpoint matching the parameters |
| * |
| * Input Parameters: |
| * eplog - 7-bit logical endpoint number (direction bit ignored). |
| * Zero means that any endpoint matching the other requirements |
| * will suffice. The assigned endpoint can be found in the eplog |
| * field. |
| * in - true: IN (device-to-host) endpoint requested |
| * eptype - Endpoint type. |
| * One of {USB_EP_ATTR_XFER_ISOC, |
| * USB_EP_ATTR_XFER_BULK, |
| * USB_EP_ATTR_XFER_INT} |
| * |
| ****************************************************************************/ |
| |
| static struct usbdev_ep_s *cxd56_allocep(struct usbdev_s *dev, |
| uint8_t eplog, bool in, |
| uint8_t eptype) |
| { |
| struct cxd56_usbdev_s *priv = (struct cxd56_usbdev_s *)dev; |
| int ndx; |
| |
| usbtrace(TRACE_DEVALLOCEP, eplog); |
| |
| /* Ignore any direction bits in the logical address */ |
| |
| eplog = USB_EPNO(eplog); |
| |
| /* Check all endpoints (except EP0) */ |
| |
| for (ndx = 1; ndx < CXD56_NENDPOINTS; ndx++) |
| { |
| /* if not used? */ |
| |
| if (!(priv->avail & (1 << ndx))) |
| { |
| continue; |
| } |
| |
| /* Does this match the endpoint number (if one was provided?) */ |
| |
| if (eplog != 0 && eplog != USB_EPNO(priv->eplist[ndx].ep.eplog)) |
| { |
| continue; |
| } |
| |
| /* Does the direction match */ |
| |
| if (in) |
| { |
| if (!USB_ISEPIN(g_epinfo[ndx].addr)) |
| { |
| continue; |
| } |
| } |
| else |
| { |
| if (!USB_ISEPOUT(g_epinfo[ndx].addr)) |
| { |
| continue; |
| } |
| } |
| |
| /* Does the type match? */ |
| |
| if (g_epinfo[ndx].attr == eptype) |
| { |
| /* Success! */ |
| |
| irqstate_t flags; |
| uint32_t mask; |
| |
| if (cxd56_allocepbuffer(&priv->eplist[ndx]) < 0) |
| { |
| continue; |
| } |
| |
| flags = enter_critical_section(); |
| priv->avail &= ~(1 << ndx); |
| mask = getreg32(CXD56_USB_DEV_EP_INTR_MASK); |
| mask &= ~(1 << ndx << (in ? 0 : 16)); |
| putreg32(mask, CXD56_USB_DEV_EP_INTR_MASK); |
| leave_critical_section(flags); |
| return &priv->eplist[ndx].ep; |
| } |
| } |
| |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_NOEP), 0); |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_freeep |
| * |
| * Description: |
| * Free the previously allocated endpoint |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_freeep(struct usbdev_s *dev, |
| struct usbdev_ep_s *ep) |
| { |
| struct cxd56_ep_s *privep = (struct cxd56_ep_s *)ep; |
| struct cxd56_usbdev_s *pdev = privep->dev; |
| irqstate_t flags; |
| |
| usbtrace(TRACE_DEVFREEEP, (uint16_t)privep->epphy); |
| |
| cxd56_freeepbuffer(privep); |
| |
| flags = enter_critical_section(); |
| pdev->avail |= 1 << privep->epphy; |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_getframe |
| * |
| * Description: |
| * Returns the current frame number |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_getframe(struct usbdev_s *dev) |
| { |
| irqstate_t flags; |
| int ret = 0; |
| |
| usbtrace(TRACE_DEVGETFRAME, 0); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!dev) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_INVALIDPARMS), 0); |
| return -ENODEV; |
| } |
| #endif |
| |
| /* Return the contents of the frame register. Interrupts must be disabled |
| * because the operation is not atomic. |
| */ |
| |
| flags = enter_critical_section(); |
| ret = getreg32(CXD56_USB_DEV_STATUS) >> 18; |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_wakeup |
| * |
| * Description: |
| * Tries to wake up the host connected to this device |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_wakeup(struct usbdev_s *dev) |
| { |
| irqstate_t flags; |
| |
| usbtrace(TRACE_DEVWAKEUP, 0); |
| |
| flags = enter_critical_section(); |
| putreg32(getreg32(CXD56_USB_DEV_CONTROL) | 1, CXD56_USB_DEV_CONTROL); |
| leave_critical_section(flags); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_selfpowered |
| * |
| * Description: |
| * Sets/clears the device selfpowered feature |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_selfpowered(struct usbdev_s *dev, bool selfpowered) |
| { |
| struct cxd56_usbdev_s *priv = &g_usbdev; |
| |
| usbtrace(TRACE_DEVSELFPOWERED, (uint16_t)selfpowered); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!dev) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_INVALIDPARMS), 0); |
| return -ENODEV; |
| } |
| #endif |
| |
| priv->selfpowered = selfpowered; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_pullup |
| * |
| * Description: |
| * Software-controlled connect to/disconnect from USB host |
| * |
| ****************************************************************************/ |
| |
| static int cxd56_pullup(struct usbdev_s *dev, bool enable) |
| { |
| uint32_t ctrl; |
| uint32_t ep; |
| |
| usbtrace(TRACE_DEVPULLUP, (uint16_t)enable); |
| |
| ctrl = getreg32(CXD56_USB_DEV_CONTROL); |
| ep = getreg32(CXD56_USB_OUT_EP_CONTROL(0)); |
| |
| if (enable) |
| { |
| ctrl &= ~(USB_CTRL_SD | USB_CTRL_RES); |
| ep |= USB_RRDY; |
| } |
| else |
| { |
| ctrl |= USB_CTRL_SD | USB_CTRL_RES; |
| ep &= USB_RRDY; |
| } |
| |
| putreg32(ctrl, CXD56_USB_DEV_CONTROL); |
| putreg32(ep, CXD56_USB_OUT_EP_CONTROL(0)); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_epinitialize |
| * |
| * Description: |
| * Initialize all of the endpoint data |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_epinitialize(struct cxd56_usbdev_s *priv) |
| { |
| int i; |
| |
| /* Initialize the device state structure */ |
| |
| priv->usbdev.ops = &g_devops; |
| |
| /* Set up the standard stuff */ |
| |
| memset(&priv->eplist[0], 0, sizeof(struct cxd56_ep_s)); |
| priv->eplist[0].ep.ops = &g_epops; |
| priv->eplist[0].dev = priv; |
| |
| /* The index, i, is the physical endpoint address; Map this |
| * to a logical endpoint address usable by the class driver. |
| */ |
| |
| priv->eplist[0].epphy = 0; |
| priv->eplist[0].ep.eplog = g_epinfo[0].addr; |
| |
| /* Setup the endpoint-specific stuff */ |
| |
| priv->eplist[0].ep.maxpacket = g_epinfo[0].maxpacket; |
| |
| /* Expose only the standard EP0 */ |
| |
| priv->usbdev.ep0 = &priv->eplist[0].ep; |
| |
| /* Initilialize USB hardware */ |
| |
| for (i = 1; i < CXD56_NENDPOINTS; i++) |
| { |
| const struct cxd56_epinfo_s *info = &g_epinfo[i]; |
| struct cxd56_ep_s *privep; |
| |
| /* Set up the standard stuff */ |
| |
| privep = &priv->eplist[i]; |
| memset(privep, 0, sizeof(struct cxd56_ep_s)); |
| privep->ep.ops = &g_epops; |
| privep->dev = priv; |
| privep->desc = NULL; |
| privep->buffer = NULL; |
| |
| /* The index, i, is the physical endpoint address; Map this |
| * to a logical endpoint address usable by the class driver. |
| */ |
| |
| privep->epphy = i; |
| privep->ep.eplog = info->addr; |
| |
| /* Setup the endpoint-specific stuff */ |
| |
| privep->ep.maxpacket = g_epinfo[i].maxpacket; |
| |
| if (USB_ISEPIN(info->addr)) |
| { |
| privep->in = 1; |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbhwuninit |
| ****************************************************************************/ |
| |
| static void cxd56_usbhwuninit(void) |
| { |
| /* Un-initilialize USB hardware */ |
| |
| putreg32(getreg32(CXD56_USB_PHY_CONFIG1) & ~PHY_PLLENABLE, |
| CXD56_USB_PHY_CONFIG1); |
| putreg32(getreg32(CXD56_USB_PJ_DEMAND) & ~(1 << 24), CXD56_USB_PJ_DEMAND); |
| |
| /* USB Device Reset */ |
| |
| putreg32(0, CXD56_USB_RESET); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_vbusinterrupt |
| ****************************************************************************/ |
| |
| static int cxd56_vbusinterrupt(int irq, void *context, void *arg) |
| { |
| struct cxd56_usbdev_s *priv = (struct cxd56_usbdev_s *)arg; |
| |
| cxd56_cableconnected(true); |
| |
| usbtrace(TRACE_INTENTRY(CXD56_TRACEINTID_VBUS), 0); |
| uinfo("irq=%d context=%p\n", irq, context); |
| |
| /* Toggle vbus interrupts */ |
| |
| up_disable_irq(CXD56_IRQ_USB_VBUS); |
| up_enable_irq(CXD56_IRQ_USB_VBUSN); |
| |
| /* Enable interrupts */ |
| |
| up_enable_irq(CXD56_IRQ_USB_SYS); |
| up_enable_irq(CXD56_IRQ_USB_INT); |
| |
| /* reconstruct Endpoints and restart Configuration */ |
| |
| if (priv->driver) |
| { |
| cxd56_usbreset(priv); |
| } |
| |
| /* Notify attach signal. |
| * if class driver not binded, can't get supply curret value. |
| */ |
| |
| if (!priv->driver) |
| { |
| cxd56_notify_signal(USBDEV_STATE_ATTACH, 0); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_vbusninterrupt |
| ****************************************************************************/ |
| |
| static int cxd56_vbusninterrupt(int irq, void *context, void *arg) |
| { |
| struct cxd56_usbdev_s *priv = (struct cxd56_usbdev_s *)arg; |
| struct cxd56_ep_s *privep; |
| int i; |
| |
| cxd56_cableconnected(false); |
| |
| usbtrace(TRACE_INTENTRY(CXD56_TRACEINTID_VBUSN), 0); |
| |
| uinfo("irq=%d context=%p\n", irq, context); |
| |
| /* Toggle vbus interrupts */ |
| |
| up_disable_irq(CXD56_IRQ_USB_VBUSN); |
| up_enable_irq(CXD56_IRQ_USB_VBUS); |
| |
| /* Disconnect device */ |
| |
| for (i = 1; i < CXD56_NENDPOINTS; i++) |
| { |
| privep = (struct cxd56_ep_s *)&priv->eplist[i]; |
| |
| cxd56_epstall(&privep->ep, false); |
| cxd56_cancelrequests(privep); |
| } |
| |
| if (g_usbdev.driver) |
| { |
| CLASS_DISCONNECT(priv->driver, &priv->usbdev); |
| } |
| |
| /* Disable USB_INT interrupt */ |
| |
| up_disable_irq(CXD56_IRQ_USB_INT); |
| up_disable_irq(CXD56_IRQ_USB_SYS); |
| |
| /* Notify detach signal */ |
| |
| priv->power = 0; |
| cxd56_notify_signal(USBDEV_STATE_DETACH, priv->power); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: arm_usbinitialize |
| * |
| * Description: |
| * Initialize USB hardware |
| * |
| ****************************************************************************/ |
| |
| void arm_usbinitialize(void) |
| { |
| usbtrace(TRACE_DEVINIT, 0); |
| |
| /* Enable USB clock */ |
| |
| cxd56_usb_clock_enable(); |
| |
| if (irq_attach(CXD56_IRQ_USB_SYS, cxd56_sysinterrupt, &g_usbdev) != 0) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_ATTACHIRQREG), 0); |
| goto errout; |
| } |
| |
| if (irq_attach(CXD56_IRQ_USB_INT, cxd56_usbinterrupt, &g_usbdev) != 0) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_COREIRQREG), 0); |
| goto errout; |
| } |
| |
| if (irq_attach(CXD56_IRQ_USB_VBUS, cxd56_vbusinterrupt, &g_usbdev) != 0) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_VBUSIRQREG), 0); |
| goto errout; |
| } |
| |
| if (irq_attach(CXD56_IRQ_USB_VBUSN, cxd56_vbusninterrupt, &g_usbdev) != 0) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_VBUSNIRQREG), 0); |
| goto errout; |
| } |
| |
| /* Initialize driver instance */ |
| |
| memset(&g_usbdev, 0, sizeof(struct cxd56_usbdev_s)); |
| g_usbdev.avail = 0xff; |
| |
| /* Initialize endpoint data */ |
| |
| cxd56_epinitialize(&g_usbdev); |
| |
| /* Enable interrupts */ |
| |
| up_enable_irq(CXD56_IRQ_USB_VBUS); |
| |
| return; |
| |
| errout: |
| arm_usbuninitialize(); |
| } |
| |
| /**************************************************************************** |
| * Name: arm_usbuninitialize |
| ****************************************************************************/ |
| |
| void arm_usbuninitialize(void) |
| { |
| struct cxd56_usbdev_s *priv = &g_usbdev; |
| irqstate_t flags; |
| |
| usbtrace(TRACE_DEVUNINIT, 0); |
| if (priv->driver) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_DRIVERREGISTERED), 0); |
| usbdev_unregister(priv->driver); |
| } |
| |
| flags = enter_critical_section(); |
| cxd56_pullup(&priv->usbdev, false); |
| priv->usbdev.speed = USB_SPEED_UNKNOWN; |
| |
| /* Disable and detach IRQs */ |
| |
| up_disable_irq(CXD56_IRQ_USB_INT); |
| up_disable_irq(CXD56_IRQ_USB_SYS); |
| up_disable_irq(CXD56_IRQ_USB_VBUS); |
| up_disable_irq(CXD56_IRQ_USB_VBUSN); |
| |
| irq_detach(CXD56_IRQ_USB_INT); |
| irq_detach(CXD56_IRQ_USB_SYS); |
| irq_detach(CXD56_IRQ_USB_VBUS); |
| irq_detach(CXD56_IRQ_USB_VBUSN); |
| |
| cxd56_usb_clock_disable(); |
| leave_critical_section(flags); |
| |
| /* Clear signal */ |
| |
| priv->signo = 0; |
| priv->pid = 0; |
| } |
| |
| /**************************************************************************** |
| * Name: usbdevclass_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) |
| { |
| int ret; |
| |
| usbtrace(TRACE_DEVREGISTER, 0); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!driver || !driver->ops->bind || !driver->ops->unbind || |
| !driver->ops->setup) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_INVALIDPARMS), 0); |
| return -EINVAL; |
| } |
| |
| if (g_usbdev.driver) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_DRIVER), 0); |
| return -EBUSY; |
| } |
| #endif |
| |
| /* Take freqlock to keep clock faster */ |
| |
| up_pm_acquire_freqlock(&g_hv_lock); |
| up_pm_acquire_wakelock(&g_wake_lock); |
| |
| /* Hook up the driver */ |
| |
| g_usbdev.driver = driver; |
| |
| /* Then bind the class driver */ |
| |
| ret = CLASS_BIND(driver, &g_usbdev.usbdev); |
| if (ret) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_BINDFAILED), (uint16_t)-ret); |
| g_usbdev.driver = NULL; |
| return ret; |
| } |
| |
| /* Enable interrupts */ |
| |
| up_enable_irq(CXD56_IRQ_USB_VBUS); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * 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) |
| { |
| struct cxd56_usbdev_s *priv = &g_usbdev; |
| irqstate_t flags; |
| |
| usbtrace(TRACE_DEVUNREGISTER, 0); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (driver != g_usbdev.driver) |
| { |
| usbtrace(TRACE_DEVERROR(CXD56_TRACEERR_INVALIDPARMS), 0); |
| return -EINVAL; |
| } |
| #endif |
| |
| /* Unbind the class driver */ |
| |
| CLASS_UNBIND(driver, &g_usbdev.usbdev); |
| |
| flags = enter_critical_section(); |
| |
| /* Disable IRQs */ |
| |
| up_disable_irq(CXD56_IRQ_USB_INT); |
| up_disable_irq(CXD56_IRQ_USB_SYS); |
| |
| /* Disconnect device */ |
| |
| cxd56_pullup(&priv->usbdev, false); |
| priv->usbdev.speed = USB_SPEED_UNKNOWN; |
| |
| /* Unhook the driver */ |
| |
| g_usbdev.driver = NULL; |
| |
| /* Un-initialize USB hardware */ |
| |
| cxd56_usbhwuninit(); |
| |
| leave_critical_section(flags); |
| |
| up_pm_release_freqlock(&g_hv_lock); |
| up_pm_release_wakelock(&g_wake_lock); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbreset |
| * |
| * Description: |
| * Reinitialize the endpoint and restore the EP configuration |
| * before disconnecting the host. Then start the Configuration again. |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_usbreset(struct cxd56_usbdev_s *priv) |
| { |
| uint32_t mask; |
| int i; |
| |
| /* USB reset assert */ |
| |
| cxd56_usbdevreset(priv); |
| |
| /* Check all endpoints (except EP0) */ |
| |
| for (i = 1; i < CXD56_NENDPOINTS; i++) |
| { |
| /* skip unused EP */ |
| |
| if (priv->avail & (1 << i)) |
| { |
| continue; |
| } |
| |
| mask = getreg32(CXD56_USB_DEV_EP_INTR_MASK); |
| mask &= ~(1 << i << (priv->eplist[i].in ? 0 : 16)); |
| putreg32(mask, CXD56_USB_DEV_EP_INTR_MASK); |
| |
| /* DMA descripter setting */ |
| |
| priv->eplist[i].buffer = NULL; |
| priv->eplist[i].desc->status = DESC_BS_HOST_BUSY; |
| priv->eplist[i].desc->buf = 0; |
| priv->eplist[i].desc->next = 0; |
| |
| if (priv->eplist[i].in) |
| { |
| putreg32(CXD56_PHYSADDR(priv->eplist[i].desc), |
| CXD56_USB_IN_EP_DATADESC(priv->eplist[i].epphy)); |
| } |
| else |
| { |
| putreg32(CXD56_PHYSADDR(priv->eplist[i].desc), |
| CXD56_USB_OUT_EP_DATADESC(priv->eplist[i].epphy)); |
| } |
| |
| /* resume EP stall */ |
| |
| cxd56_epstall(&priv->eplist[i].ep, true); |
| } |
| |
| cxd56_pullup(&priv->usbdev, true); |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbdev_setsigno |
| ****************************************************************************/ |
| |
| int cxd56_usbdev_setsigno(int signo) |
| { |
| struct cxd56_usbdev_s *priv = &g_usbdev; |
| |
| uinfo("signo = %d\n", signo); |
| |
| priv->signo = signo; |
| priv->pid = nxsched_getpid(); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_notify_signal |
| * |
| * Description: |
| * Notify the application of USB attach/detach event |
| * |
| ****************************************************************************/ |
| |
| static void cxd56_notify_signal(uint16_t state, uint16_t power) |
| { |
| struct cxd56_usbdev_s *priv = &g_usbdev; |
| |
| if (priv->signo > 0) |
| { |
| union sigval value; |
| value.sival_int = state << 16 | power; |
| nxsig_queue(priv->pid, priv->signo, value); |
| } |
| } |
| |
| #ifdef CONFIG_FS_PROCFS |
| |
| /**************************************************************************** |
| * Name: cxd56_usbdev_open |
| ****************************************************************************/ |
| |
| static int cxd56_usbdev_open(struct file *filep, const char *relpath, |
| int oflags, mode_t mode) |
| { |
| struct cxd56_usbdev_file_s *priv; |
| |
| uinfo("Open '%s'\n", relpath); |
| |
| /* PROCFS is read-only. Any attempt to open with any kind of write |
| * access is not permitted. |
| * |
| * REVISIT: Write-able proc files could be quite useful. |
| */ |
| |
| if (((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0)) |
| { |
| uerr("ERROR: Only O_RDONLY supported\n"); |
| return -EACCES; |
| } |
| |
| /* Allocate the open file structure */ |
| |
| priv = kmm_zalloc(sizeof(struct cxd56_usbdev_file_s)); |
| if (!priv) |
| { |
| uerr("ERROR: Failed to allocate file attributes\n"); |
| return -ENOMEM; |
| } |
| |
| /* Save the open file structure as the open-specific state in |
| * filep->f_priv. |
| */ |
| |
| filep->f_priv = (void *)priv; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: modprocfs_close |
| ****************************************************************************/ |
| |
| static int cxd56_usbdev_close(struct file *filep) |
| { |
| struct cxd56_usbdev_file_s *priv; |
| |
| /* Recover our private data from the struct file instance */ |
| |
| priv = (struct cxd56_usbdev_file_s *)filep->f_priv; |
| DEBUGASSERT(priv); |
| |
| /* Release the file attributes structure */ |
| |
| kmm_free(priv); |
| filep->f_priv = NULL; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbdev_read |
| ****************************************************************************/ |
| |
| static ssize_t cxd56_usbdev_read(struct file *filep, char *buffer, |
| size_t buflen) |
| { |
| struct cxd56_usbdev_file_s *attr; |
| struct cxd56_usbdev_s *priv = &g_usbdev; |
| off_t offset; |
| int ret; |
| |
| uinfo("buffer=%p buflen=%lu\n", buffer, (unsigned long)buflen); |
| |
| /* Recover our private data from the struct file instance */ |
| |
| attr = (struct cxd56_usbdev_file_s *)filep->f_priv; |
| DEBUGASSERT(attr); |
| |
| /* Traverse all installed modules */ |
| |
| attr->linesize = snprintf(attr->line, CXD56_USBDEV_LINELEN, "%-12s%d mA\n", |
| "Power:", priv->power); |
| |
| /* Transfer the system up time to user receive buffer */ |
| |
| offset = filep->f_pos; |
| ret = procfs_memcpy(attr->line, attr->linesize, |
| buffer, buflen, &offset); |
| |
| /* Update the file offset */ |
| |
| if (ret > 0) |
| { |
| filep->f_pos += ret; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbdev_dup |
| ****************************************************************************/ |
| |
| static int cxd56_usbdev_dup(const struct file *oldp, |
| struct file *newp) |
| { |
| struct cxd56_usbdev_file_s *oldattr; |
| struct cxd56_usbdev_file_s *newattr; |
| |
| uinfo("Dup %p->%p\n", oldp, newp); |
| |
| /* Recover our private data from the old struct file instance */ |
| |
| oldattr = (struct cxd56_usbdev_file_s *)oldp->f_priv; |
| DEBUGASSERT(oldattr); |
| |
| /* Allocate a new container to hold the task and attribute selection */ |
| |
| newattr = kmm_malloc(sizeof(struct cxd56_usbdev_file_s)); |
| if (!newattr) |
| { |
| uerr("ERROR: Failed to allocate file attributes\n"); |
| return -ENOMEM; |
| } |
| |
| /* The copy the file attributes from the old attributes to the new */ |
| |
| memcpy(newattr, oldattr, sizeof(struct cxd56_usbdev_file_s)); |
| |
| /* Save the new attributes in the new file structure */ |
| |
| newp->f_priv = (void *)newattr; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbdev_stat |
| ****************************************************************************/ |
| |
| static int cxd56_usbdev_stat(const char *relpath, struct stat *buf) |
| { |
| buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; |
| buf->st_size = 0; |
| buf->st_blksize = 0; |
| buf->st_blocks = 0; |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cxd56_usbdev_procfs_register |
| * |
| * Description: |
| * Register the usbdev procfs file system entry |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_FS_PROCFS_REGISTER |
| int cxd56_usbdev_procfs_register(void) |
| { |
| return procfs_register(&g_procfs_usbdev); |
| } |
| #endif |
| |
| #endif |