blob: f30d5fc457753b548616fb6bcc791f5e48560d11 [file] [log] [blame]
/****************************************************************************
* arch/risc-v/src/mpfs/mpfs_usb.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 <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/irq.h>
#include <nuttx/signal.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#include <nuttx/usb/usbhost.h>
#include <nuttx/usb/usbhost_devaddr.h>
#include <nuttx/usb/usbhost_trace.h>
#include <nuttx/spinlock.h>
#include <arch/board/board.h>
#include "hardware/mpfs_usb.h"
#include "hardware/mpfs_mpucfg.h"
#include "mpfs_gpio.h"
#include "riscv_internal.h"
#include "chip.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* USB trace error codes */
#define MPFS_TRACEERR_ALLOCFAIL 0x0001
#define MPFS_TRACEERR_BADCLEARFEATURE 0x0002
#define MPFS_TRACEERR_BADDEVGETSTATUS 0x0003
#define MPFS_TRACEERR_BADEPGETSTATUS 0x0004
#define MPFS_TRACEERR_BADEPNO 0x0006
#define MPFS_TRACEERR_BADEPTYPE 0x0007
#define MPFS_TRACEERR_BADGETCONFIG 0x0008
#define MPFS_TRACEERR_BADGETSTATUS 0x0009
#define MPFS_TRACEERR_BADSETADDRESS 0x000a
#define MPFS_TRACEERR_BADSETCONFIG 0x000b
#define MPFS_TRACEERR_BADSETFEATURE 0x000c
#define MPFS_TRACEERR_BINDFAILED 0x000d
#define MPFS_TRACEERR_DISPATCHSTALL 0x000e
#define MPFS_TRACEERR_EP0SETUPSTALLED 0x000f
#define MPFS_TRACEERR_EPOUTNULLPACKET 0x0010
#define MPFS_TRACEERR_INVALIDCTRLREQ 0x0011
#define MPFS_TRACEERR_IRQREGISTRATION 0x0012
#define MPFS_TRACEERR_TXCOMPERR 0x0013
#define MPFS_TRACEERR_INVALID_EP0_STATE 0x0014
#define MPFS_TRACEERR_EP0SETUPOUTSIZE 0x0015
#define MPFS_TRACEERR_EPOUTQEMPTY 0x0016
#define MPFS_TRACEERR_EP0PREMATURETERM 0x0017
#define MPFS_TRACEERR_TXHALT 0x0018
/* USB trace interrupt codes */
#define MPFS_TRACEINTID_INTERRUPT 0x0001
#define MPFS_TRACEINTID_EP_TX_IRQ 0x0002
#define MPFS_TRACEINTID_EP_RX_IRQ 0x0003
#define MPFS_TRACEINTID_EP_RX_CSR 0x0004
#define MPFS_TRACEINTID_EP_RX_COUNT 0x0005
#define MPFS_TRACEINTID_EP_TX_CSR 0x0006
#define MPFS_TRACEINTID_EP0_CSR0 0x0007
#define MPFS_TRACEINTID_EP0_COUNT0 0x0008
#define MPFS_TRACEINTID_EP0SETUPSETADDRESS 0x0009
#define MPFS_TRACEINTID_GETSTATUS 0x000a
#define MPFS_TRACEINTID_DEVGETSTATUS 0x000b
#define MPFS_TRACEINTID_IFGETSTATUS 0x000c
#define MPFS_TRACEINTID_CLEARFEATURE 0x000d
#define MPFS_TRACEINTID_SETFEATURE 0x000e
#define MPFS_TRACEINTID_GETCONFIG 0x000f
#define MPFS_TRACEINTID_SETCONFIG 0x0010
#define MPFS_TRACEINTID_GETSETIF 0x0011
#define MPFS_TRACEINTID_SYNCHFRAME 0x0012
#define MPFS_TRACEINTID_DISPATCH 0x0013
#define MPFS_TRACEINTID_EP0_STALLSENT 0x0014
#define MPFS_TRACEINTID_DATA_END 0x0015
/* Reset and clock control registers */
#define MPFS_SYSREG_SOFT_RESET_CR (MPFS_SYSREG_BASE + \
MPFS_SYSREG_SOFT_RESET_CR_OFFSET)
#define MPFS_SYSREG_SUBBLK_CLOCK_CR (MPFS_SYSREG_BASE + \
MPFS_SYSREG_SUBBLK_CLOCK_CR_OFFSET)
#ifdef CONFIG_ENDIAN_BIG
# define LSB 1
# define MSB 0
#else
# define LSB 0
# define MSB 1
#endif
#define MPFS_NUM_USB_PKT 1
#define MPFS_MIN_EP_FIFO_SIZE 8
#define MPFS_USB_REG_MAX 0x2000
/* Request queue operations *************************************************/
#define mpfs_rqempty(q) ((q)->head == NULL)
#define mpfs_rqpeek(q) ((q)->head)
#define MPFS_EPSET_ALL (0x1ff) /* All endpoints */
#define MPFS_EPSET_NOTEP0 (0x1fe) /* All endpoints except EP0 */
#define MPFS_EP_BIT(ep) (1 << (ep))
#define MPFS_MAX_MULTIPACKET_SIZE (0x3fff)
#define MPFS_EPIN_START (MPFS_USB_NENDPOINTS / 2)
/****************************************************************************
* Private Types
****************************************************************************/
enum mpfs_epstate_e
{
USB_EPSTATE_DISABLED = 0, /* Endpoint is disabled */
USB_EPSTATE_STALLED, /* Endpoint is stalled */
USB_EPSTATE_IDLE, /* Endpoint is idle */
USB_EPSTATE_SENDING, /* Endpoint is sending data */
USB_EPSTATE_RXSTOPPED, /* EP is stopped waiting for a read request */
USB_EPSTATE_EP0DATAOUT, /* Endpoint 0 is receiving SETUP OUT data */
USB_EPSTATE_EP0STATUSIN, /* Endpoint 0 is sending SETUP status */
USB_EPSTATE_EP0ADDRESS /* Address change is pending completion */
};
/* Device states */
enum mpfs_devstate_e
{
USB_DEVSTATE_SUSPENDED = 0, /* The device is currently suspended */
USB_DEVSTATE_POWERED, /* Host is powered through the USB cable */
USB_DEVSTATE_DEFAULT, /* Device has been reset */
USB_DEVSTATE_ADDRESSED, /* The device has an address on the bus */
USB_DEVSTATE_CONFIGURED /* A valid configuration has been selected. */
};
/* The result of EP0 SETUP */
enum mpfs_ep0setup_e
{
USB_EP0SETUP_SUCCESS = 0, /* The SETUP was handled without incident */
USB_EP0SETUP_DISPATCHED, /* The SETUP was forwarded */
USB_EP0SETUP_ADDRESS, /* A new device address is pending */
USB_EP0SETUP_STALL /* An error occurred */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int mpfs_ep_configure(struct usbdev_ep_s *ep,
const struct usb_epdesc_s *desc, bool last);
static int mpfs_ep_disable(struct usbdev_ep_s *ep);
static struct usbdev_req_s *mpfs_ep_allocreq(struct usbdev_ep_s *ep);
#ifdef CONFIG_USBDEV_DMA
static void *mpfs_ep_allocbuffer(struct usbdev_ep_s *ep, uint16_t nbytes);
static void mpfs_ep_freebuffer(struct usbdev_ep_s *ep, void *buf);
#endif
static void mpfs_ep_freereq(struct usbdev_ep_s *ep,
struct usbdev_req_s *);
static int mpfs_ep_submit(struct usbdev_ep_s *ep,
struct usbdev_req_s *req);
static int mpfs_ep_cancel(struct usbdev_ep_s *ep,
struct usbdev_req_s *req);
static int mpfs_ep_stallresume(struct usbdev_ep_s *ep, bool resume);
/* USB device controller operations */
static struct usbdev_ep_s *mpfs_allocep(struct usbdev_s *dev, uint8_t epno,
bool in, uint8_t eptype);
static void mpfs_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep);
static int mpfs_getframe(struct usbdev_s *dev);
static int mpfs_wakeup(struct usbdev_s *dev);
static int mpfs_selfpowered(struct usbdev_s *dev, bool selfpowered);
static int mpfs_pullup(struct usbdev_s *dev, bool enable);
/* Interrupt level processing */
static void mpfs_ep0_ctrlread(struct mpfs_usbdev_s *priv);
static void mpfs_ep0_wrstatus(struct mpfs_usbdev_s *priv,
const uint8_t *buffer, size_t buflen);
static void mpfs_ep0_dispatch(struct mpfs_usbdev_s *priv);
static void mpfs_setdevaddr(struct mpfs_usbdev_s *priv, uint8_t value);
static void mpfs_ep0_setup(struct mpfs_usbdev_s *priv);
static int mpfs_usb_interrupt(int irq, void *context, void *arg);
static void mpfs_epset_reset(struct mpfs_usbdev_s *priv, uint16_t epset);
/****************************************************************************
* Private Data
****************************************************************************/
static struct mpfs_usbdev_s g_usbd;
static uint8_t g_clkrefs;
static const struct usbdev_epops_s g_epops =
{
.configure = mpfs_ep_configure,
.disable = mpfs_ep_disable,
.allocreq = mpfs_ep_allocreq,
.freereq = mpfs_ep_freereq,
#ifdef CONFIG_USBDEV_DMA
.allocbuffer = mpfs_ep_allocbuffer,
.freebuffer = mpfs_ep_freebuffer,
#endif
.submit = mpfs_ep_submit,
.cancel = mpfs_ep_cancel,
.stall = mpfs_ep_stallresume,
};
static const struct usbdev_ops_s g_devops =
{
.allocep = mpfs_allocep,
.freeep = mpfs_freeep,
.getframe = mpfs_getframe,
.wakeup = mpfs_wakeup,
.selfpowered = mpfs_selfpowered,
.pullup = mpfs_pullup,
};
/* This describes the endpoint 0 */
static const struct usb_epdesc_s g_ep0desc =
{
.len = USB_SIZEOF_EPDESC,
.type = USB_DESC_TYPE_ENDPOINT,
.addr = EP0,
.attr = USB_EP_ATTR_XFER_CONTROL,
.mxpacketsize =
{
64, 0
},
.interval = 0
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mpfs_modifyreg16
*
* Description:
* Atomically modify the specified bits in the memory mapped register.
* This also checks the addr range is valid.
*
* Input Parameters:
* addr - Address to access
* clearbits - Bits to clear
* setbits - Bits to set
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_modifyreg16(uintptr_t addr, uint16_t clearbits,
uint16_t setbits)
{
irqstate_t flags;
uint16_t regval;
DEBUGASSERT((addr >= MPFS_USB_BASE) && addr < (MPFS_USB_BASE +
MPFS_USB_REG_MAX));
flags = spin_lock_irqsave(NULL);
regval = getreg16(addr);
regval &= ~clearbits;
regval |= setbits;
putreg16(regval, addr);
spin_unlock_irqrestore(NULL, flags);
}
/****************************************************************************
* Name: mpfs_modifyreg8
*
* Description:
* Atomically modify the specified bits in the memory mapped register.
* This also checks the addr range is valid.
*
* Input Parameters:
* addr - Address to access
* clearbits - Bits to clear
* setbits - Bits to set
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_modifyreg8(uintptr_t addr, uint8_t clearbits,
uint8_t setbits)
{
irqstate_t flags;
uint8_t regval;
DEBUGASSERT((addr >= MPFS_USB_BASE) && addr < (MPFS_USB_BASE +
MPFS_USB_REG_MAX));
flags = spin_lock_irqsave(NULL);
regval = getreg8(addr);
regval &= ~clearbits;
regval |= setbits;
putreg8(regval, addr);
spin_unlock_irqrestore(NULL, flags);
}
/****************************************************************************
* Register Operations
****************************************************************************/
/****************************************************************************
* Name: mpfs_putreg32
*
* Description:
* Set the contents of a 32-bit register. This helper wrapper checks
* the range before accessing the address.
*
* Input Parameters:
* regval - Value to store
* regaddr - Address where the regval is stored
*
* Returned Value:
* None
*
****************************************************************************/
static inline void mpfs_putreg32(uint32_t regval, uintptr_t regaddr)
{
DEBUGASSERT((regaddr >= MPFS_USB_BASE) && regaddr < (MPFS_USB_BASE +
MPFS_USB_REG_MAX));
putreg32(regval, regaddr);
}
/****************************************************************************
* Name: mpfs_putreg16
*
* Description:
* Set the contents of a 16-bit register. This helper wrapper checks
* the range before accessing the address.
*
* Input Parameters:
* regval - Value to store
* regaddr - Address where the regval is stored
*
* Returned Value:
* None
*
****************************************************************************/
static inline void mpfs_putreg16(uint16_t regval, uintptr_t regaddr)
{
DEBUGASSERT((regaddr >= MPFS_USB_BASE) && regaddr < (MPFS_USB_BASE +
MPFS_USB_REG_MAX));
putreg16(regval, regaddr);
}
/****************************************************************************
* Name: mpfs_putreg8
*
* Description:
* Set the contents of an 8-bit register. This helper wrapper checks
* the range before accessing the address.
*
* Input Parameters:
* regval - Value to store
* regaddr - Address where the regval is stored
*
* Returned Value:
* None
*
****************************************************************************/
static inline void mpfs_putreg8(uint8_t regval, uintptr_t regaddr)
{
DEBUGASSERT((regaddr >= MPFS_USB_BASE) && regaddr < (MPFS_USB_BASE +
MPFS_USB_REG_MAX));
putreg8(regval, regaddr);
}
/****************************************************************************
* Name: mpfs_enableclk
*
* Description:
* Enables the USB clock.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_enableclk(void)
{
/* Handle the counter atomically */
irqstate_t flags = enter_critical_section();
if (g_clkrefs == 0)
{
modifyreg32(MPFS_SYSREG_SUBBLK_CLOCK_CR, 0,
SYSREG_SUBBLK_CLOCK_CR_USB);
}
g_clkrefs++;
leave_critical_section(flags);
}
/****************************************************************************
* Name: mpfs_disableclk
*
* Description:
* Disables the USB clock.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_disableclk(void)
{
/* Handle the counter atomically */
irqstate_t flags = enter_critical_section();
g_clkrefs--;
if (g_clkrefs == 0)
{
modifyreg32(MPFS_SYSREG_SUBBLK_CLOCK_CR, SYSREG_SUBBLK_CLOCK_CR_USB,
0);
}
leave_critical_section(flags);
}
/****************************************************************************
* Request Helpers
****************************************************************************/
/****************************************************************************
* Name: mpfs_req_dequeue
*
* Description:
* Removes an entry from the linked list.
*
* Input Parameters:
* queue - Head of the linked list
*
* Returned Value:
* The entry or NULL is none in the list
*
****************************************************************************/
static struct mpfs_req_s *mpfs_req_dequeue(struct mpfs_rqhead_s *queue)
{
struct mpfs_req_s *ret = queue->head;
if (ret != NULL)
{
queue->head = ret->flink;
if (queue->head == NULL)
{
queue->tail = NULL;
}
ret->flink = NULL;
}
return ret;
}
/****************************************************************************
* Name: mpfs_req_enqueue
*
* Description:
* Add an entry to the linked list.
*
* Input Parameters:
* queue - Head of the linked list
* req - The entry to be added
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_req_enqueue(struct mpfs_rqhead_s *queue,
struct mpfs_req_s *req)
{
req->flink = NULL;
if (queue->head == NULL)
{
queue->head = req;
queue->tail = req;
}
else
{
queue->tail->flink = req;
queue->tail = req;
}
}
/****************************************************************************
* Name: mpfs_req_complete
*
* Description:
* Add an entry to the linked list.
*
* Input Parameters:
* privep - Endpoint private data
* result - Status of the completion process
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_req_complete(struct mpfs_ep_s *privep, int16_t result)
{
struct mpfs_req_s *privreq;
irqstate_t flags;
/* Remove the completed request at the head of the endpoint request list */
flags = enter_critical_section();
privreq = mpfs_req_dequeue(&privep->reqq);
leave_critical_section(flags);
if (privreq)
{
/* Save the result in the request structure */
privreq->req.result = result;
/* Callback to the request completion handler */
privreq->flink = NULL;
privreq->req.callback(&privep->ep, &privreq->req);
/* Mark the endpoint ready for the next transmission */
privep->epstate = USB_EPSTATE_IDLE;
privep->zlpsent = false;
}
}
/****************************************************************************
* Name: mpfs_req_cancel
*
* Description:
* Cancel all entries of an endpoint queue
*
* Input Parameters:
* privep - Endpoint private data
* result - Status of the completion process
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_req_cancel(struct mpfs_ep_s *privep, int16_t result)
{
/* Complete every queued request with the specified status */
while (!mpfs_rqempty(&privep->reqq))
{
usbtrace(TRACE_COMPLETE(USB_EPNO(privep->ep.eplog)),
(mpfs_rqpeek(&privep->reqq))->req.xfrd);
mpfs_req_complete(privep, result);
}
}
/****************************************************************************
* Name: mpfs_write_tx_fifo
*
* Description:
* Write data into the TX fifo buffer.
*
* Input Parameters:
* in_data - Pointer to the data to be sent
* length - Amount of bytes to write
* epno - Endpoint number
*
* Returned Value:
* OK, or error
*
****************************************************************************/
static int mpfs_write_tx_fifo(const void *in_data, uint32_t length,
uint8_t epno)
{
uint32_t i;
uint32_t *temp;
uint8_t *temp_8bit;
uint16_t tx_csr;
uint16_t words = length / 4;
uint16_t bytes = length - words * 4;
uint16_t offset;
uint16_t retries = 10000;
temp = (uint32_t *)in_data;
temp_8bit = (uint8_t *)in_data;
/* Poll mode: wait for fifo empty first */
if (epno > EP0)
{
do
{
tx_csr = getreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET);
}
while ((tx_csr & TXCSRL_REG_EPN_TX_FIFO_NE_MASK) && --retries);
}
if (retries == 0)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_TXHALT), epno);
return -EIO;
}
/* Send 32-bit words first */
for (i = 0; i < words; i++)
{
mpfs_putreg32((uint32_t)temp[i], MPFS_USB_FIFO(epno));
}
offset = words << 2;
/* Send the remaining bytes */
for (i = offset; i < (offset + bytes); i++)
{
mpfs_putreg8((uint8_t)temp_8bit[i], MPFS_USB_FIFO(epno));
}
return OK;
}
/****************************************************************************
* Name: mpfs_req_wrsetup
*
* Description:
* Setup the next queued write request.
*
* Input Parameters:
* priv - Private USB device abstraction
* privep - Private endpoint abstraction
* privreq - The actual write request
*
* Returned Value:
* OK if success, error otherwise
*
****************************************************************************/
static int mpfs_req_wrsetup(struct mpfs_usbdev_s *priv,
struct mpfs_ep_s *privep,
struct mpfs_req_s *privreq)
{
const uint8_t *buf;
uint32_t packetsize;
uint8_t epno;
int nbytes;
int ret;
int idx;
epno = USB_EPNO(privep->ep.eplog);
if (USB_ISEPIN(privep->ep.eplog))
{
idx = epno + MPFS_EPIN_START;
}
else
{
idx = epno;
}
mpfs_putreg8(epno, MPFS_USB_INDEX);
/* Get the number of bytes remaining to be sent. */
DEBUGASSERT(privreq->req.xfrd < privreq->req.len);
nbytes = privreq->req.len - privreq->req.xfrd;
usbtrace(TRACE_WRITE(USB_EPNO(privep->ep.eplog)), nbytes);
/* Either send the maxpacketsize(multi) or all of the remaining data in
* the request.
*/
if (nbytes >= MPFS_MAX_MULTIPACKET_SIZE)
{
nbytes = MPFS_MAX_MULTIPACKET_SIZE;
}
/* This is the new number of bytes "in-flight" */
privreq->inflight = nbytes;
/* The new buffer pointer is the start of the buffer plus the number of
* bytes successfully transferred plus the number of bytes previously
* "in-flight".
*/
buf = privreq->req.buf + privreq->req.xfrd;
/* Setup TX transfer using ep configured maxpacket size */
priv->eplist[idx].descb[1]->addr = (uintptr_t)buf;
packetsize = priv->eplist[idx].descb[1]->pktsize;
/* Set automatic ZLP sending if requested on req */
if (privreq->req.flags & USBDEV_REQFLAGS_NULLPKT)
{
/* Handle this properly when DMA supported */
}
priv->eplist[idx].descb[1]->pktsize = packetsize;
/* Indicate that we are in the sending state */
privep->epstate = USB_EPSTATE_SENDING;
if (nbytes > packetsize)
{
ret = mpfs_write_tx_fifo(buf, packetsize, epno);
if (ret != OK)
{
privep->epstate = USB_EPSTATE_IDLE;
return ret;
}
if (epno == EP0)
{
mpfs_modifyreg16(MPFS_USB_INDEXED_CSR_EP0_CSR0, 0,
CSR0L_DEV_TX_PKT_RDY_MASK);
}
else
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
TXCSRL_REG_EPN_UNDERRUN_MASK,
TXCSRL_REG_EPN_TX_PKT_RDY_MASK);
}
privreq->inflight = packetsize;
return OK;
}
else
{
ret = mpfs_write_tx_fifo(buf, nbytes, epno);
if (ret != OK)
{
privep->epstate = USB_EPSTATE_IDLE;
return ret;
}
}
privreq->req.xfrd += nbytes;
/* With poll mode (no DMA), we're done sending */
privep->epstate = USB_EPSTATE_IDLE;
if (epno == EP0)
{
mpfs_putreg16(CSR0L_DEV_TX_PKT_RDY_MASK | CSR0L_DEV_DATA_END_MASK,
MPFS_USB_INDEXED_CSR_EP0_CSR0);
}
else
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
TXCSRL_REG_EPN_UNDERRUN_MASK,
TXCSRL_REG_EPN_TX_PKT_RDY_MASK);
}
return OK;
}
/****************************************************************************
* Name: mpfs_ep_stall
*
* Description:
* Stall the endpoint.
*
* Input Parameters:
* privep - Private endpoint abstraction
*
* Returned Value:
* OK always
*
****************************************************************************/
static int mpfs_ep_stall(struct mpfs_ep_s *privep)
{
irqstate_t flags;
uint8_t epno;
DEBUGASSERT(privep->dev);
epno = USB_EPNO(privep->ep.eplog);
mpfs_putreg8(epno, MPFS_USB_INDEX);
/* Check that endpoint is enabled and not already in Halt state */
flags = enter_critical_section();
if ((privep->epstate != USB_EPSTATE_DISABLED) &&
(privep->epstate != USB_EPSTATE_STALLED))
{
epno = USB_EPNO(privep->ep.eplog);
usbtrace(TRACE_EPSTALL, epno);
/* If this is an IN endpoint (or endpoint 0), then cancel any
* write requests in progress.
*/
if (epno == EP0 || USB_ISEPIN(privep->ep.eplog))
{
mpfs_req_cancel(privep, -EPERM);
}
/* Put endpoint into stalled state */
privep->epstate = USB_EPSTATE_STALLED;
privep->stalled = true;
privep->pending = false;
if (epno == EP0)
{
mpfs_putreg16(CSR0L_DEV_SEND_STALL_MASK |
CSR0L_DEV_SERVICED_RX_PKT_RDY_MASK,
MPFS_USB_INDEXED_CSR_EP0_CSR0);
}
else if (USB_ISEPIN(privep->ep.eplog))
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
0,
TXCSRL_REG_EPN_SEND_STALL_MASK);
}
else
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
0,
RXCSRL_REG_EPN_SEND_STALL_MASK |
RXCSRL_REG_EPN_RX_PKT_RDY_MASK);
}
}
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: mpfs_req_write
*
* Description:
* Write all available entries on the queue.
*
* Input Parameters:
* priv - Private USB device abstraction
* privep - Private endpoint abstraction
*
* Returned Value:
* OK on success, a negated error code in case of failure
*
****************************************************************************/
static int mpfs_req_write(struct mpfs_usbdev_s *priv,
struct mpfs_ep_s *privep)
{
struct mpfs_req_s *privreq;
uint8_t epno;
int bytesleft;
int ret;
epno = USB_EPNO(privep->ep.eplog);
mpfs_putreg8(epno, MPFS_USB_INDEX);
/* We get here when an IN endpoint interrupt occurs. So now we know that
* there is no TX transfer in progress (epstate should be IDLE).
*/
DEBUGASSERT(privep->epstate == USB_EPSTATE_IDLE);
while (privep->epstate == USB_EPSTATE_IDLE)
{
/* Check the request from the head of the endpoint request queue */
privreq = mpfs_rqpeek(&privep->reqq);
if (privreq == NULL)
{
/* Was there a pending endpoint stall? */
if (privep->pending)
{
/* Yes... stall the endpoint now */
mpfs_ep_stall(privep);
}
return -ENOENT;
}
uinfo("epno=%d req=%p: len=%d xfrd=%d inflight=%d\n",
epno, privreq, privreq->req.len, privreq->req.xfrd,
privreq->inflight);
/* Handle any bytes in flight. */
privreq->req.xfrd += privreq->inflight;
privreq->inflight = 0;
/* Get the number of bytes left to be sent in the packet */
bytesleft = privreq->req.len - privreq->req.xfrd;
if (bytesleft > 0)
{
/* Perform the write operation. */
ret = mpfs_req_wrsetup(priv, privep, privreq);
if (ret != OK)
{
mpfs_req_complete(privep, ret);
return ret;
}
}
else if ((privreq->req.len == 0) && !privep->zlpsent)
{
/* If we get here, we requested to send the zero length packet now.
*/
DEBUGASSERT(epno == EP0);
privep->zlpsent = true;
privreq->inflight = 0;
/* Setup 0 length TX transfer */
priv->eplist[EP0].descb[1]->addr = (uintptr_t)&priv->ep0out[0];
}
if (privep->epstate == USB_EPSTATE_IDLE)
{
/* Return the write request to the class driver. Set the txbusy
* bit to prevent being called recursively from any new submission
* generated by returning the write request.
*/
DEBUGASSERT(privreq->req.len == privreq->req.xfrd);
privep->txbusy = true;
usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd);
mpfs_req_complete(privep, OK);
privep->txbusy = false;
return OK;
}
}
return OK;
}
/****************************************************************************
* Name: mpfs_read_rx_fifo
*
* Description:
* Read data from the RX fifo.
*
* Input Parameters:
* out_data - Address to read the data
* length - Amount of data to read
* epno - The endpoint number
*
* Returned Value:
* Amount of data read
*
****************************************************************************/
uint16_t mpfs_read_rx_fifo(uint8_t *out_data, uint32_t length, uint8_t epno)
{
uint16_t count;
uint32_t i;
uint32_t *temp;
uint8_t *temp_8bit;
uint16_t words;
uint16_t bytes;
uint16_t offset;
if (epno == EP0)
{
/* Already read the count0 register, this is correct */
count = length;
}
else
{
count = getreg16(MPFS_USB_INDEXED_CSR_RX_COUNT);
}
words = count / 4;
bytes = count - words * 4;
temp = (uint32_t *)out_data;
temp_8bit = out_data;
if (count == 0)
{
/* Nothing to do */
return 0;
}
/* 32-bit writes first */
for (i = 0; i < words; i++)
{
temp[i] = getreg32(MPFS_USB_FIFO(epno));
}
offset = words << 2;
/* ...and the remaining bytes last */
for (i = offset; i < (offset + bytes); i++)
{
temp_8bit[i] = getreg8(MPFS_USB_FIFO(epno));
}
return count;
}
/****************************************************************************
* Name: mpfs_req_read
*
* Description:
* Issue a read according to the request.
*
* Input Parameters:
* priv - USB device abstraction
* privep - Endpoint abstraction
* recvsize - Size of the read
*
* Returned Value:
* OK on success, an error code in case of trouble
*
****************************************************************************/
static int mpfs_req_read(struct mpfs_usbdev_s *priv,
struct mpfs_ep_s *privep, uint16_t recvsize)
{
struct mpfs_req_s *privreq;
uint16_t reg;
int epno;
DEBUGASSERT(priv && privep);
/* Check the request from the head of the endpoint request queue */
epno = USB_EPNO(privep->ep.eplog);
reg = getreg16(MPFS_USB_ENDPOINT(epno) + MPFS_USB_ENDPOINT_RX_CSR_OFFSET);
uint16_t count = getreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_COUNT_OFFSET);
usbtrace(TRACE_READ(USB_EPNO(epno)), count);
do
{
/* Peek at the next read request in the request queue */
privreq = mpfs_rqpeek(&privep->reqq);
if (privreq == NULL)
{
/* When no read requests are pending no EP descriptors are set to
* ready.
*/
privep->epstate = USB_EPSTATE_RXSTOPPED;
return OK;
}
uinfo("EP%d: req.len=%d xfrd=%d recvsize=%d\n",
epno, privreq->req.len, privreq->req.xfrd, recvsize);
/* Ignore any attempt to receive a zero length packet */
if (privreq->req.len == 0)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_EPOUTNULLPACKET), 0);
if (epno == EP0)
{
mpfs_putreg16(CSR0L_DEV_SERVICED_RX_PKT_RDY_MASK |
CSR0L_DEV_DATA_END_MASK,
MPFS_USB_INDEXED_CSR_EP0_CSR0);
}
else
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
RXCSRL_REG_EPN_RX_PKT_RDY_MASK, 0);
}
mpfs_req_complete(privep, OK);
privreq = NULL;
}
if ((privreq != NULL) && (privreq->inflight > 0) && (count != 0) &&
(reg & RXCSRL_REG_EPN_RX_PKT_RDY_MASK) != 0)
{
/* Update the total number of bytes transferred */
mpfs_putreg8(epno, MPFS_USB_INDEX);
privep->rxactive = true;
privreq->req.xfrd += mpfs_read_rx_fifo(privreq->req.buf,
recvsize,
epno);
privreq->inflight = 0;
usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd);
mpfs_req_complete(privep, OK);
if (epno == EP0)
{
mpfs_putreg16(CSR0L_DEV_SERVICED_RX_PKT_RDY_MASK |
CSR0L_DEV_DATA_END_MASK,
MPFS_USB_INDEXED_CSR_EP0_CSR0);
}
else
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
RXCSRL_REG_EPN_RX_PKT_RDY_MASK, 0);
}
/* Need to set recvsize to zero. When calling mpfs_req_complete()
* class driver could call submit() again and we have new request
* ready on next while() loop.
*/
privep->rxactive = false;
recvsize = 0;
privreq = NULL;
}
}
while (privreq == NULL);
/* Activate new read request from queue */
privep->rxactive = true;
if (privreq != NULL)
{
privreq->req.xfrd = 0;
privreq->inflight = privreq->req.len;
priv->eplist[epno].descb[0]->addr = (uintptr_t)privreq->req.buf;
}
return OK;
}
/****************************************************************************
* Name: mpfs_ep_set_fifo_size
*
* Description:
* Sets the fifo size for the endpoint.
*
* Input Parameters:
* epno - Endpoint number
* in - Device to host (TX) fifo if set, RX fifo if unset
* fifo_size - Desired fifo size
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ep_set_fifo_size(uint8_t epno, uint8_t in,
uint16_t fifo_size)
{
uint16_t temp;
uint8_t i = 0;
DEBUGASSERT(epno > EP0);
DEBUGASSERT(fifo_size > 8);
DEBUGASSERT(fifo_size == 8 || fifo_size == 16 || fifo_size == 32 ||
fifo_size == 64 || fifo_size == 128 || fifo_size == 512 ||
fifo_size == 1024 || fifo_size == 2048 || fifo_size == 4096);
mpfs_putreg8(epno, MPFS_USB_INDEX);
temp = fifo_size / MPFS_MIN_EP_FIFO_SIZE;
while ((temp & 0x01) == 0)
{
temp >>= 1;
i++;
}
/* in: Device to host */
if (in)
{
mpfs_putreg8(i, MPFS_USB_TX_FIFO_SIZE);
}
else
{
mpfs_putreg8(i, MPFS_USB_RX_FIFO_SIZE);
}
}
/****************************************************************************
* Name: mpfs_ep_configure_internal
*
* Description:
* This is the internal implementation of the endpoint configuration logic
* and implements the endpoint configuration method of the usbdev_ep_s
* interface. As an internal interface, it will be used to configure
* endpoint 0 which is not available to the class implementation.
*
* Input Parameters:
* privep - Endpoint abstraction
* desc - USB descriptor
*
* Returned Value:
* OK always
*
****************************************************************************/
static int mpfs_ep_configure_internal(struct mpfs_ep_s *privep,
const struct usb_epdesc_s *desc)
{
uint16_t maxpacket;
uint8_t epno;
uint8_t eptype;
bool dirin;
DEBUGASSERT(privep && privep->dev && desc);
uinfo("len: 0x%02x type: 0x%02x addr: 0x%02x attr: 0x%02x "
"maxpacketsize: 0x%02x%02x interval: 0x%02x\n",
desc->len, desc->type, desc->addr, desc->attr,
desc->mxpacketsize[1], desc->mxpacketsize[0],
desc->interval);
/* Decode the endpoint descriptor */
epno = USB_EPNO(desc->addr);
dirin = (desc->addr & USB_DIR_MASK) == USB_REQ_DIR_IN;
eptype = (desc->attr & USB_EP_ATTR_XFERTYPE_MASK);
maxpacket = GETUINT16(desc->mxpacketsize);
/* update endpoint descriptors to correct size */
privep->descb[0]->pktsize = maxpacket;
privep->descb[1]->pktsize = maxpacket;
/* Initialize the endpoint structure */
privep->ep.eplog = desc->addr; /* Includes direction */
privep->ep.maxpacket = maxpacket;
privep->epstate = USB_EPSTATE_IDLE;
/* get current config IN and OUT */
mpfs_putreg8(epno, MPFS_USB_INDEX);
/* Re-configure and enable the endpoint */
if (dirin && epno)
{
/* Clear previous config first */
mpfs_putreg16(0, MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET);
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
TXCSRL_REG_EPN_UNDERRUN_MASK |
TXCSRL_REG_EPN_SEND_STALL_MASK |
TXCSRL_REG_EPN_STALL_SENT_MASK,
0);
mpfs_ep_set_fifo_size(epno, dirin, maxpacket);
/* Give EP0 64 bytes (8*8) and configure 512 bytes for TX fifo.
* This is a pointer to internal RAM where the data should be
* stored. It must not overlap with other EPs or it will cause
* corruption. One unit is 8 bytes, so 8 is 8*8 = 64 bytes.
*/
mpfs_putreg16(8 + (64 * epno * 2), MPFS_USB_TX_FIFO_ADDR);
/* Disable double buffering */
mpfs_modifyreg16(MPFS_USB_TX_DPBUF_DIS, 0, (1 << epno));
/* Sequence for setting the max packet size */
mpfs_putreg16(0, MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_MAX_P_OFFSET);
mpfs_putreg16(MPFS_NUM_USB_PKT - 1, MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_MAX_P_OFFSET);
mpfs_putreg16((MPFS_NUM_USB_PKT - 1) << TX_MAX_P_REG_NUM_USB_PKT_SHIFT,
MPFS_USB_ENDPOINT(epno) + MPFS_USB_ENDPOINT_TX_MAX_P_OFFSET);
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_MAX_P_OFFSET, 0, maxpacket);
/* Clear data toggle (by setting the bit, not clearing */
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
0,
TXCSRL_REG_EPN_CLR_DATA_TOG_MASK);
}
else if (epno)
{
/* Clear previous config */
mpfs_putreg16(0, MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET);
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
RXCSRL_REG_EPN_OVERRUN_MASK |
RXCSRL_REG_EPN_STALL_SENT_MASK |
RXCSRL_REG_EPN_SEND_STALL_MASK,
RXCSRL_REG_EPN_RX_PKT_RDY_MASK);
mpfs_ep_set_fifo_size(epno, dirin, maxpacket);
/* Give EP0 64 bytes (8*8) and configure 512 bytes for RX fifo */
mpfs_putreg16(8 + 64 + (64 * epno * 2), MPFS_USB_RX_FIFO_ADDR);
/* Disable double buffering for RX, will run into trouble with it.
* The host will send faster than we can handle and all packets
* are ACK:ed now, although we should NAK in such situations. We
* cannot NACK in bulk mode now.
*/
mpfs_modifyreg16(MPFS_USB_RX_DPBUF_DIS, 0, (1 << epno));
mpfs_putreg16(0, MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_MAX_P_OFFSET);
mpfs_putreg16(MPFS_NUM_USB_PKT - 1, MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_MAX_P_OFFSET);
mpfs_putreg16((MPFS_NUM_USB_PKT - 1) << RX_MAX_P_REG_NUM_USB_PKT_SHIFT,
MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_MAX_P_OFFSET);
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_MAX_P_OFFSET, 0, maxpacket);
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
0,
RXCSRL_REG_EPN_CLR_DAT_TOG_MASK |
RXCSRL_REG_EPN_RX_PKT_RDY_MASK);
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
RXCSRL_REG_EPN_RX_PKT_RDY_MASK,
0);
}
switch (eptype)
{
case USB_EP_ATTR_XFER_CONTROL:
mpfs_putreg16(0, MPFS_USB_INDEXED_CSR_EP0_CSR0);
break;
case USB_EP_ATTR_XFER_BULK:
if (dirin)
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
TXCSRH_REG_EPN_ENABLE_ISO_MASK,
0);
}
else
{
/* RXCSRL_REG_EPN_BI_DIS_NYET_MASK is enabled when cleared */
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
RXCSRL_REG_EPN_ENABLE_ISO_MASK,
RXCSRL_REG_EPN_ENABLE_AUTOCLR_MASK |
RXCSRL_REG_EPN_BI_DIS_NYET_MASK |
RXCSRL_REG_EPN_RX_PKT_RDY_MASK);
}
break;
case USB_EP_ATTR_XFER_INT:
if (dirin)
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
TXCSRH_REG_EPN_ENABLE_ISO_MASK |
TXCSRH_REG_EPN_ENABLE_AUTOSET_MASK,
0);
}
else
{
/* RXCSRL_REG_EPN_BI_DIS_NYET_MASK disabled when set */
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
RXCSRL_REG_EPN_ENABLE_ISO_MASK |
RXCSRL_REG_EPN_BI_DIS_NYET_MASK,
RXCSRL_REG_EPN_ENABLE_AUTOCLR_MASK |
RXCSRL_REG_EPN_RX_PKT_RDY_MASK);
}
break;
default:
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADEPTYPE), eptype);
return -EINVAL;
}
/* Enable endpoint interrupts */
if (dirin)
{
mpfs_modifyreg16(MPFS_USB_TX_IRQ_ENABLE, 0, (1 << epno));
}
else
{
mpfs_modifyreg16(MPFS_USB_RX_IRQ_ENABLE, 0, (1 << epno));
}
return OK;
}
/****************************************************************************
* Name: mpfs_ep_configure
*
* Description:
* This is the endpoint configuration method of the usbdev_ep_s interface.
*
* Input Parameters:
* ep - USB device abstraction
* desc - USB descriptor
* last - Set if the endpoint is the last one
*
* Returned Value:
* OK on success, a negated error code otherwise
*
****************************************************************************/
static int mpfs_ep_configure(struct usbdev_ep_s *ep,
const struct usb_epdesc_s *desc,
bool last)
{
struct mpfs_ep_s *privep = (struct mpfs_ep_s *)ep;
int ret;
usbtrace(TRACE_EPCONFIGURE, USB_EPNO(desc->addr));
ret = mpfs_ep_configure_internal(privep, desc);
/* If this was the last endpoint, then the class driver is fully
* configured.
*/
if (ret == OK && last)
{
struct mpfs_usbdev_s *priv = privep->dev;
/* Go to the configured state (we should have been in the addressed
* state)
*/
priv->devstate = USB_DEVSTATE_CONFIGURED;
}
return ret;
}
static void mpfs_ep_reset(struct mpfs_usbdev_s *priv, uint8_t epno)
{
struct mpfs_ep_s *privep = &priv->eplist[epno];
/* Disable endpoint interrupts */
mpfs_modifyreg16(MPFS_USB_RX_IRQ_ENABLE, (1 << epno), 0);
mpfs_modifyreg16(MPFS_USB_TX_IRQ_ENABLE, (1 << epno), 0);
/* Cancel any queued requests. Since they are cancelled with status
* -ESHUTDOWN, then will not be requeued until the configuration is reset.
* NOTE: This should not be necessary... the CLASS_DISCONNECT above
* should result in the class implementation calling mpfs_ep_disable
* for each of its configured endpoints.
*/
mpfs_req_cancel(privep, -ESHUTDOWN);
/* Reset endpoint status */
privep->epstate = USB_EPSTATE_DISABLED;
privep->stalled = false;
privep->pending = false;
privep->halted = false;
privep->zlpsent = false;
privep->txbusy = false;
privep->rxactive = false;
}
/****************************************************************************
* Name: mpfs_setdevaddr
*
* Description:
* This function is called after the completion of the STATUS phase to
* instantiate the device address that was received during the SETUP
* phase. This enters the ADDRESSED state from either the DEFAULT or the
* CONFIGURED states.
*
* If called with address == 0, then function will revert to the DEFAULT,
* un-configured and un-addressed state.
*
* Input Parameters:
* priv - USB device abstraction
* address - Address to be set (or cleared)
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_setdevaddr(struct mpfs_usbdev_s *priv, uint8_t address)
{
uinfo("ENTRY address=0x%x\n", address);
DEBUGASSERT(address <= 0x7f);
mpfs_putreg8(address, MPFS_USB_FADDR);
if (address != 0)
{
priv->devstate = USB_DEVSTATE_ADDRESSED;
}
else
{
/* Revert to the un-addressed, default state */
priv->devstate = USB_DEVSTATE_DEFAULT;
}
}
/****************************************************************************
* Name: mpfs_ep_disable
*
* Description:
* This is the disable() method of the USB device endpoint structure.
*
* Input Parameters:
* ep - USB device endpoint
*
* Returned Value:
* OK always
*
****************************************************************************/
static int mpfs_ep_disable(struct usbdev_ep_s *ep)
{
struct mpfs_ep_s *privep = (struct mpfs_ep_s *)ep;
struct mpfs_usbdev_s *priv;
irqstate_t flags;
uint8_t epno;
epno = USB_EPNO(ep->eplog);
/* Reset the endpoint and cancel any ongoing activity */
flags = enter_critical_section();
priv = privep->dev;
mpfs_ep_reset(priv, epno);
/* Revert to the addressed-but-not-configured state */
mpfs_setdevaddr(priv, priv->devaddr);
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: mpfs_ep_allocreq
*
* Description:
* This is the allocreq() method of the USB device endpoint structure.
*
* Input Parameters:
* ep - USB device endpoint (unused)
*
* Returned Value:
* New allocated request struct or NULL if no mempry
*
****************************************************************************/
static struct usbdev_req_s *mpfs_ep_allocreq(struct usbdev_ep_s *ep)
{
struct mpfs_req_s *privreq;
privreq = kmm_malloc(sizeof(struct mpfs_req_s));
if (privreq == NULL)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_ALLOCFAIL), 0);
return NULL;
}
memset(privreq, 0, sizeof(struct mpfs_req_s));
return &privreq->req;
}
/****************************************************************************
* Name: mpfs_ep_freereq
*
* Description:
* This is the freereq() method of the USB device endpoint structure. This
* frees the addressed request.
*
* Input Parameters:
* ep - USB device endpoint
* req - Request to free
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ep_freereq(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
{
struct mpfs_req_s *privreq = (struct mpfs_req_s *)req;
kmm_free(privreq);
}
/****************************************************************************
* Name: mpfs_ep_allocbuffer
*
* Description:
* This is the allocbuffer() method of the USB device endpoint structure.
*
* Input Parameters:
* ep - USB device endpoint
* nbytes - Number of bytes to allocate
*
* Returned Value:
* Pointer to the data or NULL if no memory
*
* Assumptions/Limitations:
* DMA is not supported yet
*
****************************************************************************/
#ifdef CONFIG_USBDEV_DMA
static void *mpfs_ep_allocbuffer(struct usbdev_ep_s *ep, uint16_t nbytes)
{
/* There is not special buffer allocation requirement */
return kmm_malloc(nbytes);
}
#endif
/****************************************************************************
* Name: mpfs_ep_freebuffer
*
* Description:
* This is the freebuffer() method of the USB device endpoint structure.
*
* Input Parameters:
* ep - USB device endpoint
* buf - Pointer to the data
*
* Returned Value:
* None
*
* Assumptions/Limitations:
* DMA is not supported yet
*
****************************************************************************/
#ifdef CONFIG_USBDEV_DMA
static void mpfs_ep_freebuffer(struct usbdev_ep_s *ep, void *buf)
{
/* There is not special buffer allocation requirement */
kmm_free(buf);
}
#endif
/****************************************************************************
* Name: mpfs_ep_submit
*
* Description:
* This is the submit() method of the USB device endpoint structure.
*
* Input Parameters:
* ep - USB device endpoint
* req - The request to be submitted
*
* Returned Value:
* OK on success
*
****************************************************************************/
static int mpfs_ep_submit(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
{
struct mpfs_req_s *privreq = (struct mpfs_req_s *)req;
struct mpfs_ep_s *privep = (struct mpfs_ep_s *)ep;
struct mpfs_usbdev_s *priv;
irqstate_t flags;
uint8_t epno;
int ret = OK;
usbtrace(TRACE_EPSUBMIT, USB_EPNO(ep->eplog));
priv = privep->dev;
/* Handle the request from the class driver */
epno = USB_EPNO(ep->eplog);
req->result = -EINPROGRESS;
req->xfrd = 0;
privreq->inflight = 0;
flags = enter_critical_section();
/* Handle IN (device-to-host) requests. NOTE: If the class device is
* using the bi-directional EP0, then we assume that they intend the EP0
* IN functionality (EP0 SETUP OUT data receipt does not use requests).
*/
if (USB_ISEPIN(ep->eplog) || epno == EP0)
{
/* Check if the endpoint is stalled (or there is a stall pending) */
if (privep->stalled || privep->pending)
{
mpfs_req_enqueue(&privep->pendq, privreq);
usbtrace(TRACE_INREQQUEUED(epno), req->len);
ret = OK;
}
else
{
/* Add the new request to the request queue for the IN endpoint */
mpfs_req_enqueue(&privep->reqq, privreq);
usbtrace(TRACE_INREQQUEUED(epno), req->len);
/* If the IN endpoint is IDLE and there is not write queue
* processing in progress, then transfer the data now.
*/
if (privep->epstate == USB_EPSTATE_IDLE && !privep->txbusy)
{
ret = mpfs_req_write(priv, privep);
}
}
}
/* Handle OUT (host-to-device) requests */
else
{
/* Add the new request to the request queue for the OUT endpoint */
mpfs_req_enqueue(&privep->reqq, privreq);
usbtrace(TRACE_OUTREQQUEUED(epno), req->len);
/* Check if we have stopped RX receipt due to lack of read
* requests. In that case we are not receiving anything from host.
* and HW sends NAK to host. see mpfs_req_read()
* so this "state" is actually not required (at least yet)
*/
if (privep->epstate == USB_EPSTATE_RXSTOPPED)
{
privep->epstate = USB_EPSTATE_IDLE;
privreq->inflight = privreq->req.len;
privep->rxactive = true;
privreq->req.xfrd = 0;
ret = mpfs_req_read(priv, privep, req->len);
}
/* start new read if no active yet */
else if (!privep->rxactive)
{
ret = mpfs_req_read(priv, privep, req->len);
}
}
leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Name: mpfs_ep_cancel
*
* Description:
* This is the cancel() method of the USB device endpoint structure.
*
* Input Parameters:
* ep - USB device endpoint
* req - The request to be canceled
*
* Returned Value:
* OK unconditionally
*
****************************************************************************/
static int mpfs_ep_cancel(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
{
struct mpfs_ep_s *privep = (struct mpfs_ep_s *)ep;
irqstate_t flags;
usbtrace(TRACE_EPCANCEL, USB_EPNO(ep->eplog));
flags = enter_critical_section();
mpfs_req_cancel(privep, -EAGAIN);
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: mpfs_ep_resume
*
* Description:
* This is the resume() method.
*
* Input Parameters:
* privep - Endpoint abstraction
*
* Returned Value:
* OK unconditionally
*
****************************************************************************/
static int mpfs_ep_resume(struct mpfs_ep_s *privep)
{
struct mpfs_usbdev_s *priv;
struct mpfs_req_s *req;
irqstate_t flags;
uint8_t epno;
/* Check that endpoint is in Idle state */
DEBUGASSERT(privep->dev);
usbtrace(TRACE_EPRESUME, USB_EPNO(privep->ep.eplog));
flags = enter_critical_section();
/* Check if the endpoint is stalled */
if (privep->epstate == USB_EPSTATE_STALLED)
{
epno = USB_EPNO(privep->ep.eplog);
priv = (struct mpfs_usbdev_s *)privep->dev;
/* Return endpoint to Idle state */
privep->stalled = false;
privep->pending = false;
privep->epstate = USB_EPSTATE_IDLE;
/* Clear STALLRQx request and reset data toggle if needed */
if (epno == EP0)
{
mpfs_modifyreg16(MPFS_USB_INDEXED_CSR_EP0_CSR0,
CSR0L_DEV_STALL_SENT_MASK,
0);
}
else if (USB_ISEPIN(privep->ep.eplog))
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
TXCSRL_REG_EPN_SEND_STALL_MASK,
0);
}
else
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
RXCSRL_REG_EPN_SEND_STALL_MASK,
RXCSRL_REG_EPN_RX_PKT_RDY_MASK);
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_CSR_OFFSET,
0,
RXCSRL_REG_EPN_CLR_DAT_TOG_MASK |
RXCSRL_REG_EPN_RX_PKT_RDY_MASK);
}
/* Copy any requests in the pending request queue to the working
* request queue.
*/
while ((req = mpfs_req_dequeue(&privep->pendq)) != NULL)
{
mpfs_req_enqueue(&privep->reqq, req);
}
/* Resuming any blocked data transfers on the endpoint */
if (epno == 0 || USB_ISEPIN(privep->ep.eplog))
{
/* IN endpoint (or EP0). Restart any queued write requests */
mpfs_req_write(priv, privep);
}
}
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: mpfs_ep_stallresume
*
* Description:
* This is the stallresume() method.
*
* Input Parameters:
* ep - Endpoint abstraction
* resume - Resume (or stall)
*
* Returned Value:
* OK on success
*
****************************************************************************/
static int mpfs_ep_stallresume(struct usbdev_ep_s *ep, bool resume)
{
struct mpfs_ep_s *privep;
uint8_t epno;
irqstate_t flags;
int ret;
/* Handle the resume condition */
privep = (struct mpfs_ep_s *)ep;
if (resume)
{
ret = mpfs_ep_resume(privep);
}
/* Handle the stall condition */
else
{
/* If this is an IN endpoint (and not EP0) and if there are queued
* write requests, then we cannot stall now. Perhaps this is a
* protocol stall. In that case, we will need to drain the write
* requests before sending the stall.
*/
flags = enter_critical_section();
epno = USB_EPNO(ep->eplog);
if (epno != 0 && USB_ISEPIN(ep->eplog))
{
/* Are there any unfinished write requests in the request
* queue?
*/
if (!mpfs_rqempty(&privep->reqq))
{
/* Just set a flag to indicate that the endpoint must be
* stalled on the next TRCPTX interrupt when the request
* queue becomes empty.
*/
privep->pending = true;
leave_critical_section(flags);
return OK;
}
}
/* Not an IN endpoint, endpoint 0, or no pending write requests.
* Stall the endpoint now.
*/
ret = mpfs_ep_stall(privep);
leave_critical_section(flags);
}
return ret;
}
/****************************************************************************
* Device Controller Operations
****************************************************************************/
/****************************************************************************
* Name: mpfs_ep_reserve
*
* Description:
* Find and un-reserved endpoint number and reserve it for the caller.
*
* Input Parameters:
* priv - USB device abstraction
* epset - Set of bits, one bit reflects one endpoint
* in - If the EP is direction IN
*
* Returned Value:
* Endpoint structure or NULL in case of error
*
****************************************************************************/
static inline struct mpfs_ep_s *
mpfs_ep_reserve(struct mpfs_usbdev_s *priv, uint16_t epset, bool in)
{
struct mpfs_ep_s *privep = NULL;
irqstate_t flags;
int epndx = 0;
flags = enter_critical_section();
epset &= priv->epavail;
if (epset != 0)
{
/* Select the lowest bit in the set of matching, available endpoints
* (skipping EP0)
*/
int max;
if (in)
{
/* 5, 6, 7 and 8 */
max = MPFS_USB_NENDPOINTS;
}
else
{
/* 1, 2, 3 and 4 */
max = MPFS_EPIN_START + 1;
}
for (epndx = 1 + (MPFS_USB_NENDPOINTS / 2) * in;
epndx < max;
epndx++)
{
uint16_t bit = MPFS_EP_BIT(epndx);
if ((epset & bit) != 0)
{
/* Mark the endpoint no longer available */
priv->epavail &= ~bit;
/* And return the pointer to the standard endpoint
* structure.
*/
privep = &priv->eplist[epndx];
break;
}
}
}
DEBUGASSERT(privep != NULL);
leave_critical_section(flags);
return privep;
}
/****************************************************************************
* Name: mpfs_allocep
*
* Description:
* This is the allocep() method of the USB device driver interface
*
* Input Parameters:
* dev - USB device abstraction
* epno - Endpoint number
* in - Direction
* eptype - EP type, unused
*
* Returned Value:
* USB device endpoint structure or NULL in case of error
*
****************************************************************************/
static struct usbdev_ep_s *mpfs_allocep(struct usbdev_s *dev, uint8_t epno,
bool in, uint8_t eptype)
{
struct mpfs_usbdev_s *priv = (struct mpfs_usbdev_s *)dev;
struct mpfs_ep_s *privep = NULL;
uint16_t epset = MPFS_EPSET_NOTEP0;
/* Ignore any direction bits in the logical address */
epno = USB_EPNO(epno);
/* A logical address of 0 means that any endpoint will do */
if (epno > 0)
{
/* Otherwise, we will return the endpoint structure only for the
* requested 'logical' endpoint. All of the other checks will still
* be performed.
*/
if (epno >= MPFS_USB_NENDPOINTS)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADEPNO), (uint16_t)epno);
return NULL;
}
/* Convert the logical address to a physical OUT endpoint address and
* remove all of the candidate endpoints from the bitset except for the
* the IN/OUT pair for this logical address.
*/
epset = MPFS_EP_BIT(epno);
}
if (in)
{
epset <<= MPFS_EPIN_START;
}
/* Check if the selected endpoint number is available */
privep = mpfs_ep_reserve(priv, epset, in);
if (privep == NULL)
{
return NULL;
}
return &privep->ep;
}
/****************************************************************************
* Name: mpfs_ep_unreserve
*
* Description:
* The endpoint is no longer in use. It will be unreserved and can be
* re-used if needed.
*
* Input Parameters:
* priv - USB device abstraction
* privep - Endpoint private struct
*
* Returned Value:
* None
*
****************************************************************************/
static inline void
mpfs_ep_unreserve(struct mpfs_usbdev_s *priv, struct mpfs_ep_s *privep)
{
irqstate_t flags = enter_critical_section();
priv->epavail |= MPFS_EP_BIT(USB_EPNO(privep->ep.eplog));
leave_critical_section(flags);
}
/****************************************************************************
* Name: mpfs_freeep
*
* Description:
* This is the freeep() method of the USB device driver interface
*
* Input Parameters:
* dev - USB device abstraction
* ep - Endpoint private struct
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_freeep(struct usbdev_s *dev, struct usbdev_ep_s *ep)
{
struct mpfs_usbdev_s *priv;
struct mpfs_ep_s *privep;
priv = (struct mpfs_usbdev_s *)dev;
privep = (struct mpfs_ep_s *)ep;
if (priv != NULL && privep != NULL)
{
/* Mark the endpoint as available */
mpfs_ep_unreserve(priv, privep);
}
}
/****************************************************************************
* Name: mpfs_getframe
*
* Description:
* This is the getframe() method of the USB device driver interface
*
* Input Parameters:
* dev - USB device abstraction
*
* Returned Value:
* Last known frame number
*
****************************************************************************/
static int mpfs_getframe(struct usbdev_s *dev)
{
uint16_t frameno;
/* Return the last frame number detected by the hardware */
frameno = getreg16(MPFS_USB_FRAME);
return frameno;
}
void mpfs_usb_suspend(struct usbdev_s *dev, bool resume)
{
}
/****************************************************************************
* Name: mpfs_resume
*
* Description:
* Resumes action in contrast to suspend.
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_resume(struct mpfs_usbdev_s *priv)
{
/* This function is called when either (1) a WAKEUP interrupt is received
* from the host PC, or (2) the class device implementation calls the
* wakeup() method.
*/
/* Don't do anything if the device was not suspended */
if (priv->devstate == USB_DEVSTATE_SUSPENDED)
{
/* Revert to the previous state */
priv->devstate = priv->prevstate;
/* Restore clocking to the USB peripheral */
mpfs_enableclk();
/* Restore full power -- whatever that means for this
* particular board
*/
mpfs_usb_suspend((struct usbdev_s *)priv, true);
/* Notify the class driver of the resume event */
if (priv->driver)
{
CLASS_RESUME(priv->driver, &priv->usbdev);
}
}
}
/****************************************************************************
* Name: mpfs_wakeup
*
* Description:
* This is the wakeup() method of the USB device driver interface
*
* Input Parameters:
* dev - USB device abstraction
*
* Returned Value:
* OK unconditionally
*
****************************************************************************/
static int mpfs_wakeup(struct usbdev_s *dev)
{
struct mpfs_usbdev_s *priv = (struct mpfs_usbdev_s *)dev;
irqstate_t flags;
/* Resume normal operation */
flags = enter_critical_section();
mpfs_resume(priv);
/* Device is always self-powered. Remote wakeup is not supported */
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: mpfs_selfpowered
*
* Description:
* This is the selfpowered() method of the USB device driver interface
*
* Input Parameters:
* dev - USB device abstraction
* selfpowered - Selfpowered if set true
*
* Returned Value:
* OK unconditionally
*
****************************************************************************/
static int mpfs_selfpowered(struct usbdev_s *dev, bool selfpowered)
{
struct mpfs_usbdev_s *priv = (struct mpfs_usbdev_s *)dev;
priv->selfpowered = selfpowered;
return OK;
}
/****************************************************************************
* Name: mpfs_reset
*
* Description:
* This resets the USB state but doesn't reset the hardware itself
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_reset(struct mpfs_usbdev_s *priv)
{
uint8_t epno;
/* Tell the class driver that we are disconnected. The class driver
* should then accept any new configurations.
*/
CLASS_DISCONNECT(priv->driver, &priv->usbdev);
/* The device enters the default state (un-addressed and un-configured) */
priv->devaddr = 0;
mpfs_setdevaddr(priv, 0);
priv->devstate = USB_DEVSTATE_DEFAULT;
/* Reset and disable all endpoints. Then re-configure EP0 */
mpfs_putreg16(0, MPFS_USB_RX_IRQ_ENABLE);
mpfs_putreg16(0, MPFS_USB_TX_IRQ_ENABLE);
mpfs_epset_reset(priv, MPFS_EPSET_ALL);
mpfs_ep_configure_internal(&priv->eplist[EP0], &g_ep0desc);
/* Set EP0 waiting for SETUP */
mpfs_ep0_ctrlread(priv);
/* Reset endpoint data structures */
for (epno = 0; epno < MPFS_USB_NENDPOINTS; epno++)
{
struct mpfs_ep_s *privep = &priv->eplist[epno];
/* Cancel any queued requests. Since they are cancelled
* with status -ESHUTDOWN, then will not be requeued
* until the configuration is reset. NOTE: This should
* not be necessary... the CLASS_DISCONNECT above should
* result in the class implementation calling mpfs_ep_disable
* for each of its configured endpoints.
*/
mpfs_req_cancel(privep, -ESHUTDOWN);
/* Reset endpoint status */
privep->stalled = false;
privep->pending = false;
privep->halted = false;
privep->zlpsent = false;
privep->txbusy = false;
privep->rxactive = false;
}
/* Re-configure the USB controller in its initial, unconnected state */
#ifdef CONFIG_USBDEV_DUALSPEED
priv->usbdev.speed = USB_SPEED_HIGH;
priv->usbdev.dualspeed = 0;
#else
priv->usbdev.speed = USB_SPEED_FULL;
priv->usbdev.dualspeed = 1;
#endif
/* Clear all pending interrupt statuses */
mpfs_putreg8(0, MPFS_USB_INDEX);
mpfs_putreg8(0, MPFS_USB_IRQ);
mpfs_putreg16(0, MPFS_USB_INDEXED_CSR_EP0_CSR0);
mpfs_putreg16(1, MPFS_USB_TX_IRQ_ENABLE);
}
/****************************************************************************
* Name: mpfs_ep0_wrstatus
*
* Description:
* Writes the ep0 status reply back to the host.
*
* Input Parameters:
* priv - USB device abstraction
* buffer - Data buffer
* buflen - Size of the data buffer
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ep0_wrstatus(struct mpfs_usbdev_s *priv,
const uint8_t *buffer, size_t buflen)
{
struct mpfs_ep_s *privep;
privep = &priv->eplist[EP0];
/* We need to make copy of data as source is in stack
* reusing the static ep0 setup buffer
*/
DEBUGASSERT(buflen <= MPFS_EP0_MAXPACKET);
memcpy(&priv->ep0out[0], buffer, buflen);
/* Setup TX transfer */
priv->eplist[EP0].descb[1]->addr = (uintptr_t) &priv->ep0out[0];
if (buflen > 0)
{
DEBUGASSERT(privep->epstate != USB_EPSTATE_SENDING);
mpfs_write_tx_fifo(buffer, buflen, 0);
mpfs_putreg16(CSR0L_DEV_TX_PKT_RDY_MASK | CSR0L_DEV_DATA_END_MASK,
MPFS_USB_INDEXED_CSR_EP0_CSR0);
}
else
{
mpfs_putreg16(CSR0L_DEV_SERVICED_RX_PKT_RDY_MASK |
CSR0L_DEV_DATA_END_MASK,
MPFS_USB_INDEXED_CSR_EP0_CSR0);
}
/* set read for next setup OUT */
mpfs_ep0_ctrlread(priv);
}
/****************************************************************************
* Name: mpfs_ep0_dispatch
*
* Description:
* Writes the ep0 status reply back to the host.
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ep0_dispatch(struct mpfs_usbdev_s *priv)
{
uint8_t *dataout;
size_t outlen;
int ret;
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_DISPATCH), 0);
if (priv && priv->driver)
{
/* Assume IN SETUP (or OUT SETUP with no data) */
dataout = NULL;
outlen = 0;
/* Was this an OUT SETUP command? */
if (USB_REQ_ISOUT(priv->ctrl.type))
{
uint16_t tmplen = GETUINT16(priv->ctrl.len);
if (tmplen > 0)
{
dataout = priv->ep0out;
outlen = tmplen;
}
}
/* Forward to the control request to the class driver implementation */
ret = CLASS_SETUP(priv->driver, &priv->usbdev, &priv->ctrl,
dataout, outlen);
if (ret < 0)
{
/* Stall on failure */
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_DISPATCHSTALL), 0);
mpfs_ep_stall(&priv->eplist[EP0]);
}
}
}
/****************************************************************************
* Name: mpfs_ep0_setup
*
* Description:
* This function is called after the receiving of the SETUP packet
* data is ready on usb_ctrlreq_s struct.
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ep0_setup(struct mpfs_usbdev_s *priv)
{
struct mpfs_ep_s *ep0 = &priv->eplist[EP0];
struct mpfs_ep_s *privep;
union wb_u value;
union wb_u index;
union wb_u len;
union wb_u response;
enum mpfs_ep0setup_e ep0result;
uint8_t epno;
int nbytes = 0; /* Assume zero-length packet */
int ret;
int idx;
/* Terminate any pending requests */
mpfs_req_cancel(ep0, -EPROTO);
/* Assume NOT stalled; no TX in progress */
ep0->stalled = false;
ep0->pending = false;
ep0->epstate = USB_EPSTATE_IDLE;
/* And extract the little-endian 16-bit values to host order */
value.w = GETUINT16(priv->ctrl.value);
index.w = GETUINT16(priv->ctrl.index);
len.w = GETUINT16(priv->ctrl.len);
uinfo("SETUP: type=%02x req=%02x value=%04x index=%04x len=%04x\n",
priv->ctrl.type, priv->ctrl.req, value.w, index.w, len.w);
/* Dispatch any non-standard requests */
if ((priv->ctrl.type & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_STANDARD)
{
/* Let the class implementation handle all non-standard requests */
mpfs_ep0_dispatch(priv);
return;
}
/* Handle standard request. Pick off the things of interest to the
* USB device controller driver; pass what is left to the class driver
*/
ep0result = USB_EP0SETUP_SUCCESS;
switch (priv->ctrl.req)
{
case USB_REQ_GETSTATUS:
{
/* type: device-to-host; recipient = device, interface, endpoint
* value: 0
* index: zero interface endpoint
* len: 2; data = status
*/
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_GETSTATUS),
priv->ctrl.type);
if (len.w != 2 || (priv->ctrl.type & USB_REQ_DIR_IN) == 0 ||
index.b[MSB] != 0 || value.w != 0)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADEPGETSTATUS), 0);
ep0result = USB_EP0SETUP_STALL;
}
else
{
switch (priv->ctrl.type & USB_REQ_RECIPIENT_MASK)
{
case USB_REQ_RECIPIENT_ENDPOINT:
{
epno = USB_EPNO(index.b[LSB]);
if (USB_ISEPIN(index.b[LSB]))
{
idx = epno + MPFS_EPIN_START;
}
else
{
idx = epno;
}
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_GETSTATUS), epno);
if (epno >= MPFS_USB_NENDPOINTS)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADEPGETSTATUS),
epno);
ep0result = USB_EP0SETUP_STALL;
}
else
{
privep = &priv->eplist[idx];
response.w = 0; /* Not stalled */
nbytes = 2; /* Response size: 2 bytes */
if (privep->stalled)
{
/* Endpoint stalled */
response.b[LSB] = 1; /* Stalled */
}
}
}
break;
case USB_REQ_RECIPIENT_DEVICE:
{
if (index.w == 0)
{
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_DEVGETSTATUS),
0);
/* Features: Remote Wakeup=YES; selfpowered=? */
response.w = 0;
response.b[LSB] = (priv->selfpowered <<
USB_FEATURE_SELFPOWERED) |
(1 << USB_FEATURE_REMOTEWAKEUP);
nbytes = 2; /* Response size: 2 bytes */
}
else
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADDEVGETSTATUS),
0);
ep0result = USB_EP0SETUP_STALL;
}
}
break;
case USB_REQ_RECIPIENT_INTERFACE:
{
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_IFGETSTATUS), 0);
response.w = 0;
nbytes = 2; /* Response size: 2 bytes */
}
break;
default:
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADGETSTATUS), 0);
ep0result = USB_EP0SETUP_STALL;
}
break;
}
}
}
break;
case USB_REQ_CLEARFEATURE:
{
/* type: host-to-device; recipient = device, interface or endpoint
* value: feature selector
* index: zero interface endpoint;
* len: zero, data = none
*/
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_CLEARFEATURE),
priv->ctrl.type);
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) !=
USB_REQ_RECIPIENT_ENDPOINT)
{
/* Let the class implementation handle all
* recipients (except for the
* endpoint recipient)
*/
mpfs_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
/* Endpoint recipient */
epno = USB_EPNO(index.b[LSB]);
if (epno < MPFS_USB_NENDPOINTS && index.b[MSB] == 0 &&
value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0)
{
if (USB_ISEPIN(index.b[LSB]))
{
idx = epno + MPFS_EPIN_START;
}
else
{
idx = epno;
}
privep = &priv->eplist[idx];
privep->halted = false;
ret = mpfs_ep_resume(privep);
if (ret < 0)
{
ep0result = USB_EP0SETUP_STALL;
}
}
else
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADCLEARFEATURE), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
}
break;
case USB_REQ_SETFEATURE:
{
/* type: host-to-device; recipient = device, interface, endpoint
* value: feature selector
* index: zero interface endpoint;
* len: 0; data = none
*/
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_SETFEATURE),
priv->ctrl.type);
if (((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_DEVICE) &&
value.w == USB_FEATURE_TESTMODE)
{
/* Special case recipient=device test mode */
uinfo("test mode: %d\n", index.w);
}
else if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) !=
USB_REQ_RECIPIENT_ENDPOINT)
{
/* The class driver handles all recipients
* except recipient=endpoint
*/
mpfs_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
/* Handler recipient=endpoint */
epno = USB_EPNO(index.b[LSB]);
if (epno < MPFS_USB_NENDPOINTS && index.b[MSB] == 0 &&
value.w == USB_FEATURE_ENDPOINTHALT && len.w == 0)
{
if (USB_ISEPIN(index.b[LSB]))
{
idx = epno + MPFS_EPIN_START;
}
else
{
idx = epno;
}
privep = &priv->eplist[idx];
privep->halted = true;
ret = mpfs_ep_stall(privep);
if (ret < 0)
{
ep0result = USB_EP0SETUP_STALL;
}
}
else
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADSETFEATURE), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
}
break;
case USB_REQ_SETADDRESS:
{
/* type: host-to-device; recipient = device
* value: device address
* index: 0
* len: 0; data = none
*/
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) !=
USB_REQ_RECIPIENT_DEVICE ||
index.w != 0 || len.w != 0 || value.w > 127)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADSETADDRESS), 0);
ep0result = USB_EP0SETUP_STALL;
}
else
{
/* Note that setting of the device address will be deferred.
* A zero-length packet will be sent and the device address will
* be set when the zero-length packet transfer completes.
*/
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_EP0SETUPSETADDRESS),
value.w);
priv->devaddr = value.w;
ep0result = USB_EP0SETUP_ADDRESS;
}
}
break;
case USB_REQ_GETDESCRIPTOR:
/* type: device-to-host; recipient = device
* value: descriptor type and index
* index: 0 or language ID;
* len: descriptor len; data = descriptor
*/
case USB_REQ_SETDESCRIPTOR:
/* type: host-to-device; recipient = device
* value: descriptor type and index
* index: 0 or language ID;
* len: descriptor len; data = descriptor
*/
{
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_DEVICE)
{
/* The request seems valid... let the class implementation
* handle it.
*/
mpfs_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
ep0result = USB_EP0SETUP_STALL;
}
}
break;
case USB_REQ_GETCONFIGURATION:
/* type: device-to-host; recipient = device
* value: 0;
* index: 0;
* len: 1; data = configuration value
*/
{
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_GETCONFIG),
priv->ctrl.type);
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_DEVICE &&
value.w == 0 && index.w == 0 && len.w == 1)
{
/* The request seems valid... let the class implementation
* handle it.
*/
mpfs_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADGETCONFIG), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
break;
case USB_REQ_SETCONFIGURATION:
/* type: host-to-device; recipient = device
* value: configuration value
* index: 0;
* len: 0; data = none
*/
{
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_SETCONFIG),
priv->ctrl.type);
if ((priv->ctrl.type & USB_REQ_RECIPIENT_MASK) ==
USB_REQ_RECIPIENT_DEVICE &&
index.w == 0 && len.w == 0)
{
/* The request seems valid... let the class implementation
* handle it. If the class implementation accepts it new
* configuration, it will call mpfs_ep_configure() to configure
* the endpoints.
*/
mpfs_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
else
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BADSETCONFIG), 0);
ep0result = USB_EP0SETUP_STALL;
}
}
break;
case USB_REQ_GETINTERFACE:
/* type: device-to-host; recipient = interface
* value: 0
* index: interface;
* len: 1; data = alt interface
*/
case USB_REQ_SETINTERFACE:
/* type: host-to-device; recipient = interface
* value: alternate setting
* index: interface;
* len: 0; data = none
*/
{
/* Let the class implementation handle the request */
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_GETSETIF), priv->ctrl.type);
mpfs_ep0_dispatch(priv);
ep0result = USB_EP0SETUP_DISPATCHED;
}
break;
case USB_REQ_SYNCHFRAME:
/* type: device-to-host; recipient = endpoint
* value: 0
* index: endpoint;
* len: 2; data = frame number
*/
{
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_SYNCHFRAME), 0);
}
break;
default:
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_INVALIDCTRLREQ),
priv->ctrl.req);
ep0result = USB_EP0SETUP_STALL;
}
break;
}
/* Restrict the data length to the length requested in the setup packet */
if (nbytes > len.w)
{
nbytes = len.w;
}
switch (ep0result)
{
case USB_EP0SETUP_SUCCESS:
{
/* Send the response (might be a zero-length packet) */
ep0->epstate = USB_EPSTATE_EP0STATUSIN;
mpfs_ep0_wrstatus(priv, response.b, nbytes);
}
break;
case USB_EP0SETUP_ADDRESS:
{
/* Send the response (might be a zero-length packet) */
ep0->epstate = USB_EPSTATE_EP0ADDRESS;
mpfs_ep0_wrstatus(priv, response.b, nbytes);
}
break;
case USB_EP0SETUP_STALL:
{
/* Stall EP0 */
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_EP0SETUPSTALLED),
priv->ctrl.req);
mpfs_ep_stall(&priv->eplist[EP0]);
}
break;
case USB_EP0SETUP_DISPATCHED:
default:
break;
}
}
/****************************************************************************
* Name: mpfs_ep0_ctrlread
*
* Description:
* Prepare 8-byte read req for ep0-ctrl setup phase.
* data is received on priv->ep0out buffer
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ep0_ctrlread(struct mpfs_usbdev_s *priv)
{
priv->eplist[EP0].descb[0]->addr = (uintptr_t)&priv->ep0out[0];
priv->eplist[EP0].descb[0]->pktsize = 8;
}
/****************************************************************************
* Name: mpfs_ep_rx_interrupt
*
* Description:
* Handle the USB endpoint interrupt from the host (OUT).
*
* Input Parameters:
* priv - USB device abstraction
* epno - The endpoint number
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ep_rx_interrupt(struct mpfs_usbdev_s *priv, int epno)
{
struct mpfs_ep_s *privep;
#ifdef CONFIG_HAVE_USBTRACE
uint16_t reg;
#endif
uint16_t count;
privep = &priv->eplist[epno];
mpfs_putreg8(epno, MPFS_USB_INDEX);
count = getreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_RX_COUNT_OFFSET);
#ifdef CONFIG_HAVE_USBTRACE
reg = getreg16(MPFS_USB_ENDPOINT(epno) + MPFS_USB_ENDPOINT_RX_CSR_OFFSET);
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_EP_RX_CSR), reg);
#endif
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_EP_RX_COUNT), count);
if (privep->epstate == USB_EPSTATE_IDLE)
{
/* Continue processing the read request. */
usbtrace(TRACE_READ(USB_EPNO(epno)), count);
mpfs_req_read(priv, privep, count);
}
}
/****************************************************************************
* Name: mpfs_ep_tx_interrupt
*
* Description:
* Handle the TX endpoint interrupt (IN, to the host)
*
* Input Parameters:
* priv - USB device abstraction
* epno - The endpoint number
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ep_tx_interrupt(struct mpfs_usbdev_s *priv, int epno)
{
struct mpfs_ep_s *privep;
privep = &priv->eplist[epno + MPFS_EPIN_START];
DEBUGASSERT((epno + MPFS_EPIN_START) < MPFS_USB_NENDPOINTS);
mpfs_putreg8(epno, MPFS_USB_INDEX);
uint16_t tx_csr = getreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET);
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_EP_TX_CSR), tx_csr);
if ((tx_csr & TXCSRL_REG_EPN_UNDERRUN_MASK) != 0)
{
/* Under-run errors should happen only for ISO endpoints. */
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
TXCSRL_REG_EPN_UNDERRUN_MASK,
0);
}
if ((tx_csr & TXCSRL_REG_EPN_STALL_SENT_MASK) != 0)
{
mpfs_modifyreg16(MPFS_USB_ENDPOINT(epno) +
MPFS_USB_ENDPOINT_TX_CSR_OFFSET,
TXCSRL_REG_EPN_STALL_SENT_MASK,
0);
}
if (privep->epstate == USB_EPSTATE_SENDING ||
privep->epstate == USB_EPSTATE_EP0STATUSIN)
{
/* Continue / resume processing the write requests */
privep->epstate = USB_EPSTATE_IDLE;
mpfs_req_write(priv, privep);
}
}
/****************************************************************************
* Name: mpfs_ctrl_ep_interrupt
*
* Description:
* Handle the EP0 USB endpoint interrupt
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_ctrl_ep_interrupt(struct mpfs_usbdev_s *priv)
{
struct mpfs_ep_s *privep;
uint16_t count0;
uint16_t csr0;
uint8_t mode;
/* Get the endpoint structure */
privep = &priv->eplist[EP0];
mpfs_putreg8(EP0, MPFS_USB_INDEX);
/* Make sure we're in device mode */
mode = getreg8(MPFS_USB_DEV_CTRL) & DEV_CTRL_HOST_MODE_MASK;
DEBUGASSERT(!mode);
count0 = getreg16(MPFS_USB_INDEXED_CSR_EP0_COUNT0);
csr0 = getreg16(MPFS_USB_INDEXED_CSR_EP0_CSR0);
if (privep->epstate == USB_EPSTATE_EP0ADDRESS)
{
mpfs_setdevaddr(priv, priv->devaddr);
privep->epstate = USB_EPSTATE_IDLE;
}
/* Endpoint stall */
if ((csr0 & CSR0L_DEV_STALL_SENT_MASK) != 0)
{
if (privep->epstate == USB_EPSTATE_STALLED)
{
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_EP0_STALLSENT), csr0);
mpfs_modifyreg16(MPFS_USB_INDEXED_CSR_EP0_CSR0,
CSR0L_DEV_STALL_SENT_MASK, 0);
}
}
/* Clear setup end if set */
if ((csr0 & CSR0L_DEV_SETUP_END_MASK) != 0)
{
/* Setting SERVICED_SETUP_END bit clears Setup End bit */
mpfs_modifyreg16(MPFS_USB_INDEXED_CSR_EP0_CSR0, 0,
CSR0L_DEV_SERVICED_SETUP_END_MASK);
}
if (privep->epstate == USB_EPSTATE_SENDING ||
privep->epstate == USB_EPSTATE_EP0STATUSIN)
{
/* Continue/resume processing the write requests */
privep->epstate = USB_EPSTATE_IDLE;
mpfs_req_write(priv, privep);
}
/* RX packet received. Should not get them here. */
if ((csr0 & CSR0L_DEV_DATA_END_MASK) != 0)
{
/* Premature termination. Control transfer has ended before DATAEND,
* this indicates some trouble!
*/
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_EP0PREMATURETERM), count0);
}
/* SETUP packet received */
if ((csr0 & CSR0L_DEV_RX_PKT_RDY_MASK) != 0)
{
uint16_t len;
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_EP0_CSR0), csr0);
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_EP0_COUNT0), count0);
/* If a write request transfer was pending, complete it. */
if (privep->epstate == USB_EPSTATE_SENDING)
{
mpfs_req_complete(privep, -EPROTO);
}
usbtrace(TRACE_READ(USB_EPNO(EP0)), count0);
if (count0 > 0)
{
if (privep->epstate == USB_EPSTATE_EP0DATAOUT)
{
mpfs_read_rx_fifo((uint8_t *)&priv->ep0out, count0, EP0);
mpfs_putreg16(CSR0L_DEV_DATA_END_MASK |
CSR0L_DEV_SERVICED_RX_PKT_RDY_MASK,
MPFS_USB_INDEXED_CSR_EP0_CSR0);
}
else
{
DEBUGASSERT(count0 == sizeof(struct usb_ctrlreq_s));
mpfs_read_rx_fifo((uint8_t *)&priv->ctrl, count0, EP0);
mpfs_putreg16(CSR0L_DEV_SERVICED_RX_PKT_RDY_MASK,
MPFS_USB_INDEXED_CSR_EP0_CSR0);
}
}
if (privep->epstate == USB_EPSTATE_EP0DATAOUT)
{
uint16_t rlen;
/* Yes.. back to the IDLE state */
privep->epstate = USB_EPSTATE_IDLE;
/* Get the size that we expected to receive */
rlen = GETUINT16(priv->ctrl.len);
if (rlen == count0)
{
/* And handle the EP0 SETUP now. */
mpfs_ep0_setup(priv);
}
else
{
/* Then stall. */
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_EP0SETUPOUTSIZE),
count0);
mpfs_ep_stall(privep);
}
return;
}
/* SETUP data is ready */
len = GETUINT16(priv->ctrl.len);
if (USB_REQ_ISOUT(priv->ctrl.type) && len > 0)
{
/* Yes.. then we have to wait for the OUT data phase to complete
* before processing the SETUP command.
*/
privep->epstate = USB_EPSTATE_EP0DATAOUT;
}
else
{
/* This is an SETUP IN command (or a SETUP IN with no data). */
privep->epstate = USB_EPSTATE_IDLE;
/* Handle the SETUP OUT command now */
mpfs_ep0_setup(priv);
}
/* Ready for next setup data */
mpfs_ep0_ctrlread(priv);
}
}
/****************************************************************************
* Name: mpfs_usb_interrupt
*
* Description:
* Handle the USB interrupt, for the device mode only.
*
* Input Parameters:
* irq - IRQ number (unused)
* context - Context (unused)
* arg - Pointer to the private data
*
* Returned Value:
* OK unconditionally
*
****************************************************************************/
static int mpfs_usb_interrupt(int irq, void *context, void *arg)
{
struct mpfs_usbdev_s *priv = (struct mpfs_usbdev_s *)arg;
uint16_t isr;
uint16_t pending_rx_ep;
uint16_t pending_tx_ep;
int i;
/* Get the device interrupts */
isr = getreg8(MPFS_USB_IRQ);
usbtrace(TRACE_INTDECODE(MPFS_TRACEINTID_INTERRUPT), isr);
/* Get the endpoint interrupts */
pending_tx_ep = getreg16(MPFS_USB_TX_IRQ);
pending_rx_ep = getreg16(MPFS_USB_RX_IRQ);
if (isr & RESET_IRQ_MASK)
{
/* Handle the reset */
mpfs_reset(priv);
#ifdef CONFIG_USBDEV_DUALSPEED
priv->usbdev.speed = USB_SPEED_HIGH;
priv->usbdev.dualspeed = 0;
#else
priv->usbdev.speed = USB_SPEED_FULL;
priv->usbdev.dualspeed = 1;
#endif
return OK;
}
/* Serve Endpoint Interrupts first */
if ((pending_tx_ep & 0x01) != 0)
{
mpfs_ctrl_ep_interrupt(priv);
}
if (pending_tx_ep != 0)
{
for (i = 1; i < MPFS_USB_NENDPOINTS; i++)
{
if ((pending_tx_ep & (1 << i)) != 0)
{
mpfs_ep_tx_interrupt(priv, i);
}
}
}
if (pending_rx_ep != 0)
{
for (i = 0; i < MPFS_USB_NENDPOINTS; i++)
{
if ((pending_rx_ep & (1 << i)) != 0)
{
mpfs_ep_rx_interrupt(priv, i);
}
}
}
if ((isr & SUSPEND_IRQ_MASK) != 0)
{
/* Unhandled */
uinfo("SUSPEND IRQ received\n");
}
/* SOF interrupt */
if ((isr & SOF_IRQ_MASK) != 0)
{
/* Unhandled */
uinfo("SOF IRQ received\n");
}
if ((isr & DISCONNECT_IRQ_MASK) != 0)
{
/* Unhandled */
uinfo("Disconnect IRQ\n");
}
if ((isr & RESUME_IRQ_MASK) != 0)
{
mpfs_resume(priv);
}
return OK;
}
/****************************************************************************
* Name: mpfs_usb_dma_interrupt
*
* Description:
* Handle the USB DMA interrupts.
*
* Input Parameters:
* irq - IRQ number (unused)
* context - Context (unused)
* arg - Pointer to the private data
*
* Returned Value:
* OK unconditionally
*
* Assumptions/Limitations:
* DMA is not supported yet
*
****************************************************************************/
#ifdef CONFIG_USBDEV_DMA
static int mpfs_usb_dma_interrupt(int irq, void *context, void *arg)
{
return OK;
}
#endif
/****************************************************************************
* Endpoint Helpers
****************************************************************************/
/****************************************************************************
* Name: mpfs_epset_reset
*
* Description:
* Reset and disable a set of endpoints.
*
* Input Parameters:
* priv - USB device private data
* epset - Set of EPs to reset
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_epset_reset(struct mpfs_usbdev_s *priv, uint16_t epset)
{
uint16_t bit;
int epno;
/* Reset each endpoint in the set */
for (epno = 0, bit = 1, epset &= MPFS_EPSET_ALL;
epno < MPFS_USB_NENDPOINTS && epset != 0;
epno++, bit <<= 1)
{
/* Is this endpoint in the set? */
if ((epset & bit) != 0)
{
/* Yes, reset and disable it */
mpfs_ep_reset(priv, epno);
epset &= ~bit;
}
}
}
/****************************************************************************
* Name: mpfs_pullup
*
* Description:
* This is the pullup() method of the USB device driver interface. This is
* a no-op.
*
* Input Parameters:
* dev - USB device
* enable - Enable or disable
*
* Returned Value:
* OK unconditionally
*
****************************************************************************/
static int mpfs_pullup(struct usbdev_s *dev, bool enable)
{
return OK;
}
/****************************************************************************
* Initialization / Reset
****************************************************************************/
/****************************************************************************
* Name: mpfs_usb_iomux
*
* Description:
* This initializes the necessary pin-muxes for the USB controller.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_usb_iomux(void)
{
mpfs_configgpio(MSSIO_USB_CLK);
mpfs_configgpio(MSSIO_USB_DIR);
mpfs_configgpio(MSSIO_USB_NXT);
mpfs_configgpio(MSSIO_USB_STP);
mpfs_configgpio(MSSIO_USB_DATA0);
mpfs_configgpio(MSSIO_USB_DATA1);
mpfs_configgpio(MSSIO_USB_DATA2);
mpfs_configgpio(MSSIO_USB_DATA3);
mpfs_configgpio(MSSIO_USB_DATA4);
mpfs_configgpio(MSSIO_USB_DATA5);
mpfs_configgpio(MSSIO_USB_DATA6);
mpfs_configgpio(MSSIO_USB_DATA7);
#ifdef CONFIG_USBDEV_DMA
/* DMA operations need to open the USB PMP registers for proper
* operation.
*/
uint64_t pmpcfg_usb_x;
pmpcfg_usb_x = getreg64(MPFS_PMPCFG_USB_0);
if ((pmpcfg_usb_x & 0x1ffffff000000000llu) != 0x1f00000000000000llu)
{
uerr("Please check the MPFS_PMPCFG_USB_0 register.\n");
}
pmpcfg_usb_x = getreg64(MPFS_PMPCFG_USB_1);
if ((pmpcfg_usb_x & 0x1ffffff000000000llu) != 0x1f00000000000000llu)
{
uerr("Please check the MPFS_PMPCFG_USB_1 register.\n");
}
pmpcfg_usb_x = getreg64(MPFS_PMPCFG_USB_2);
if ((pmpcfg_usb_x & 0x1ffffff000000000llu) != 0x1f00000000000000llu)
{
uerr("Please check the MPFS_PMPCFG_USB_2 register.\n");
}
pmpcfg_usb_x = getreg64(MPFS_PMPCFG_USB_3);
if ((pmpcfg_usb_x & 0x1ffffff000000000llu) != 0x1f00000000000000llu)
{
uerr("Please check the MPFS_PMPCFG_USB_3 register.\n");
}
#endif
}
/****************************************************************************
* Name: mpfs_hw_setup
*
* Description:
* This sets up the hardware by setting the clocks and resetting the proper
* modules. When ready, it issues the soft-connect procedure.
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_hw_setup(struct mpfs_usbdev_s *priv)
{
volatile uint8_t soft_reset;
/* Prepare the pads properly first */
mpfs_usb_iomux();
/* Enable the clock */
mpfs_enableclk();
/* Disable USB block reset, also from FPGA */
modifyreg32(MPFS_SYSREG_SOFT_RESET_CR, SYSREG_SOFT_RESET_CR_USB |
SYSREG_SOFT_RESET_CR_FPGA, 0);
/* Reset the controller */
mpfs_putreg8(SOFT_RESET_REG_MASK, MPFS_USB_SOFT_RST);
do
{
soft_reset = getreg8(MPFS_USB_SOFT_RST);
}
while (soft_reset);
/* Enable main PLIC IRQ */
up_enable_irq(MPFS_IRQ_USB_MC);
/* Clear IRQ status */
mpfs_putreg8(0x0, MPFS_USB_IRQ);
/* Init descriptor base address */
memset((uint8_t *)(&priv->ep_descriptors[0]), 0,
sizeof(priv->ep_descriptors));
/* Enable interrupts */
mpfs_putreg8(SUSPEND_IRQ_MASK | RESUME_IRQ_MASK | RESET_IRQ_MASK |
CONNECT_IRQ_MASK | DISCONNECT_IRQ_MASK,
MPFS_USB_ENABLE);
mpfs_putreg16(0x01, MPFS_USB_C_T_HSBT);
/* Disable EP interrupts */
mpfs_putreg16(0x0, MPFS_USB_RX_IRQ_ENABLE);
mpfs_putreg16(0x0, MPFS_USB_TX_IRQ_ENABLE);
/* Perform soft-connect */
#ifdef CONFIG_USBDEV_DUALSPEED
mpfs_putreg8(POWER_REG_SOFT_CONN_MASK |
POWER_REG_ENABLE_HS_MASK, MPFS_USB_POWER);
#else
mpfs_putreg8(POWER_REG_SOFT_CONN_MASK, MPFS_USB_POWER);
#endif
}
/****************************************************************************
* Name: mpfs_hw_shutdown
*
* Description:
* This shuts down the USB device.
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_hw_shutdown(struct mpfs_usbdev_s *priv)
{
priv->usbdev.speed = USB_SPEED_UNKNOWN;
/* Force disconnect and give some time to finish it up */
mpfs_modifyreg8(MPFS_USB_POWER, POWER_REG_SOFT_CONN_MASK, 0);
nxsig_usleep(1000);
/* Disable all interrupts */
mpfs_putreg8(0, MPFS_USB_ENABLE);
up_disable_irq(MPFS_IRQ_USB_MC);
/* Disable clocking to the peripheral */
mpfs_disableclk();
}
/****************************************************************************
* Name: mpfs_sw_setup
*
* Description:
* This sets up and initializes the software.
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_sw_setup(struct mpfs_usbdev_s *priv)
{
int epno;
memset(priv, 0, sizeof(struct mpfs_usbdev_s));
priv->usbdev.ops = &g_devops;
priv->usbdev.ep0 = &priv->eplist[EP0].ep;
priv->epavail = MPFS_EPSET_ALL & ~MPFS_EP_BIT(EP0);
priv->devstate = USB_DEVSTATE_SUSPENDED;
priv->prevstate = USB_DEVSTATE_POWERED;
/* Initialize the endpoint list */
for (epno = 0; epno < MPFS_USB_NENDPOINTS; epno++)
{
/* Set endpoint operations, reference to driver structure (not
* really necessary because there is only one controller), and
* the (physical) endpoint number which is just the index to the
* endpoint.
*/
priv->eplist[epno].ep.ops = &g_epops;
priv->eplist[epno].dev = priv;
if (epno < (MPFS_EPIN_START + 1))
{
priv->eplist[epno].ep.eplog = epno;
}
else
{
priv->eplist[epno].ep.eplog = epno - MPFS_EPIN_START;
}
/* We will use a maxpacket size for supported for each endpoint */
#ifdef CONFIG_USBDEV_DUALSPEED
if (epno == EP0)
{
priv->eplist[epno].ep.maxpacket = MPFS_USB_MAXPACKETSIZE(epno);
}
else
{
priv->eplist[epno].ep.maxpacket = MPFS_USB_MAXPACKETSIZE_HS(epno);
}
#else
priv->eplist[epno].ep.maxpacket = MPFS_USB_MAXPACKETSIZE(epno);
#endif
/* set descriptor addresses */
priv->eplist[epno].descb[0] = &priv->ep_descriptors[(epno << 1)];
priv->eplist[epno].descb[1] = &priv->ep_descriptors[(epno << 1) + 1];
}
/* Select a smaller endpoint size for EP0 */
#if MPFS_EP0_MAXPACKET < 64
priv->eplist[EP0].ep.maxpacket = MPFS_EP0_MAXPACKET;
#endif
}
/****************************************************************************
* Name: mpfs_sw_shutdown
*
* Description:
* This shuts down the sw. Currently this is a no-operation.
*
* Input Parameters:
* priv - USB device abstraction
*
* Returned Value:
* None
*
****************************************************************************/
static void mpfs_sw_shutdown(struct mpfs_usbdev_s *priv)
{
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: usbdev_register
*
* Description:
* Register a USB device class driver. The class driver's bind() method
* will be called to bind it to a USB device driver.
*
* Input Parameters:
* driver - USB device driver abstraction
*
* Returned Value:
* OK on success, a negated error otherwise
*
****************************************************************************/
int usbdev_register(struct usbdevclass_driver_s *driver)
{
struct mpfs_usbdev_s *priv = &g_usbd;
int ret;
DEBUGASSERT(driver != NULL);
mpfs_sw_setup(priv);
/* First hook up the driver */
priv->driver = driver;
/* Then bind the class driver */
ret = CLASS_BIND(driver, &priv->usbdev);
if (ret != OK)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_BINDFAILED), (uint16_t)-ret);
priv->driver = NULL;
}
else
{
mpfs_hw_setup(priv);
}
return ret;
}
/****************************************************************************
* Name: usbdev_unregister
*
* Description:
* Unregister 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.
*
* Input Parameters:
* driver - USB device driver abstraction
*
* Returned Value:
* OK on success, a negated error otherwise
*
****************************************************************************/
int usbdev_unregister(struct usbdevclass_driver_s *driver)
{
/* For now there is only one USB controller, but we will always refer to
* it using a pointer to make any future ports to multiple USB controllers
* easier.
*/
struct mpfs_usbdev_s *priv = &g_usbd;
irqstate_t flags;
/* Reset the hardware and cancel all requests. All requests must be
* canceled while the class driver is still bound.
*/
flags = enter_critical_section();
/* Unbind the class driver */
CLASS_UNBIND(driver, &priv->usbdev);
mpfs_hw_shutdown(priv);
mpfs_sw_shutdown(priv);
/* Unhook the driver */
priv->driver = NULL;
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: mpfs_usbinitialize
*
* Description:
* This is called from mpfs_usbinitialize() if it fails.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
void mpfs_usbuninitialize(void)
{
/* Nothing to do */
}
/****************************************************************************
* Name: mpfs_usbinitialize
*
* Description:
* This is called in the board startup phase. This prepares the software
* ready for the later phase.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
void mpfs_usbinitialize(void)
{
/* For now there is only one USB controller, but we will always refer to
* it using a pointer to make any future ports to multiple USB controllers
* easier.
*/
struct mpfs_usbdev_s *priv = &g_usbd;
usbtrace(TRACE_DEVINIT, 0);
/* Software initialization */
mpfs_sw_setup(priv);
/* Attach interrupts */
if (irq_attach(MPFS_IRQ_USB_MC, mpfs_usb_interrupt, priv) != 0)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_IRQREGISTRATION),
MPFS_IRQ_USB_MC);
goto errout;
}
#ifdef CONFIG_USBDEV_DMA
if (irq_attach(MPFS_IRQ_USB_DMA, mpfs_usb_dma_interrupt, priv) != 0)
{
usbtrace(TRACE_DEVERROR(MPFS_TRACEERR_IRQREGISTRATION),
MPFS_IRQ_USB_DMA);
goto errout;
}
#endif
return;
errout:
mpfs_usbuninitialize();
}
/****************************************************************************
* Name: mpfs_vbus_detect
*
* Description:
* Read the VBUS state from the USB OTG controller.
*
* Input Parameters:
* None
*
* Returned Value:
* true if VBUS is valid; false otherwise
*
****************************************************************************/
bool mpfs_vbus_detect(void)
{
uint8_t vbus;
/* Accessing the peripheral needs the clock */
mpfs_enableclk();
vbus = getreg8(MPFS_USB_DEV_CTRL) & DEV_CTRL_VBUS_MASK;
mpfs_disableclk();
return vbus == VBUS_ABOVE_VBUS_VALID;
}