nes-proj/drivers/cpu/cc2538/usb/usb-arch.c

1296 lines
32 KiB
C

/*
* Copyright (c) 2012, Philippe Retornaz
* Copyright (c) 2012, EPFL STI IMT LSRO1 -- Mobots group
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \addtogroup cc2538-usb
* @{
*
* \file
* Arch-specific routines for the cc2538 USB controller. Heavily based on
* the cc2530 driver written by Philippe Retornaz
*/
#include "contiki.h"
#include "energest.h"
#include "usb-arch.h"
#include "usb-api.h"
#include "dev/usb-regs.h"
#include "dev/nvic.h"
#include "dev/gpio.h"
#include "dev/ioc.h"
#include "dev/udma.h"
#include "sys/clock.h"
#include "lpm.h"
#include "reg.h"
#include "dev/watchdog.h"
#include <stdbool.h>
#include <stdint.h>
/*---------------------------------------------------------------------------*/
#ifdef USB_PULLUP_PORT
#define USB_PULLUP_PORT_BASE GPIO_PORT_TO_BASE(USB_PULLUP_PORT)
#endif
#ifdef USB_PULLUP_PIN
#define USB_PULLUP_PIN_MASK GPIO_PIN_MASK(USB_PULLUP_PIN)
#endif
/*---------------------------------------------------------------------------*/
/* EP max FIFO sizes without double buffering */
#if CTRL_EP_SIZE > 32
#error Control endpoint size too big
#endif
#if USB_EP1_SIZE > 32
#error Endpoint 1 size too big
#endif
#if USB_EP2_SIZE > 64
#error Endpoint 2 size too big
#endif
#if USB_EP3_SIZE > 128
#error Endpoint 3 size too big
#endif
#if USB_EP4_SIZE > 256
#error Endpoint 4 size too big
#endif
#if USB_EP5_SIZE > 512
#error Endpoint 5 size too big
#endif
/*---------------------------------------------------------------------------*/
/* uDMA transfer threshold. Use DMA only for data size higher than this */
#define UDMA_SIZE_THRESHOLD 8
/* uDMA channel control persistent flags */
#define UDMA_TX_FLAGS (UDMA_CHCTL_ARBSIZE_128 | UDMA_CHCTL_XFERMODE_AUTO \
| UDMA_CHCTL_SRCSIZE_8 | UDMA_CHCTL_DSTSIZE_8 \
| UDMA_CHCTL_SRCINC_8 | UDMA_CHCTL_DSTINC_NONE)
#define UDMA_RX_FLAGS (UDMA_CHCTL_ARBSIZE_128 | UDMA_CHCTL_XFERMODE_AUTO \
| UDMA_CHCTL_SRCSIZE_8 | UDMA_CHCTL_DSTSIZE_8 \
| UDMA_CHCTL_SRCINC_NONE | UDMA_CHCTL_DSTINC_8)
/*---------------------------------------------------------------------------*/
static const uint16_t ep_xfer_size[] = {
CTRL_EP_SIZE,
USB_EP1_SIZE,
USB_EP2_SIZE,
USB_EP3_SIZE,
USB_EP4_SIZE,
USB_EP5_SIZE,
};
/*---------------------------------------------------------------------------*/
typedef struct _USBBuffer usb_buffer;
/*---------------------------------------------------------------------------*/
struct usb_endpoint {
uint8_t halted;
uint8_t addr;
uint8_t flags;
usb_buffer *buffer;
struct process *event_process;
unsigned int events;
uint16_t xfer_size;
};
typedef struct usb_endpoint usb_endpoint_t;
/*---------------------------------------------------------------------------*/
#define EP_STATUS_IDLE 0
#define EP_STATUS_RX 1
#define EP_STATUS_TX 2
#define USB_EP_FLAGS_TYPE_MASK 0x03
#define USB_EP_FLAGS_TYPE_BULK 0x00
#define USB_EP_FLAGS_TYPE_CONTROL 0x01
#define USB_EP_FLAGS_TYPE_ISO 0x02
#define USB_EP_FLAGS_TYPE_INTERRUPT 0x03
#define USB_EP_FLAGS_ENABLED 0x04
#define EP_TYPE(ep) ((ep)->flags & USB_EP_FLAGS_TYPE_MASK)
#define IS_EP_TYPE(ep, type) (EP_TYPE(ep) == (type))
#define IS_CONTROL_EP(ep) IS_EP_TYPE(ep, USB_EP_FLAGS_TYPE_CONTROL)
#define IS_BULK_EP(ep) IS_EP_TYPE(ep, USB_EP_FLAGS_TYPE_BULK)
#define IS_INTERRUPT_EP(ep) IS_EP_TYPE(ep, USB_EP_FLAGS_TYPE_INTERRUPT)
#define IS_ISO_EP(ep) IS_EP_TYPE(ep, USB_EP_FLAGS_TYPE_ISO)
#define USBIIE_INEPxIE(x) (1 << x)
#define USBOIE_OUEPxIE(x) (1 << x)
#define EPxIF(x) (1 << x)
#define USB_READ_BLOCK 0x01
#define USB_WRITE_NOTIFY 0x02
#define USB_READ_NOTIFY 0x02
#define USB_READ_FAIL 0x04
/* Index in endpoint array */
#define EP_INDEX(addr) ((addr) & 0x7f)
/* Get address of endpoint struct */
#define EP_STRUCT(addr) &usb_endpoints[EP_INDEX(addr)];
/* Number of hardware endpoint */
#define EP_HW_NUM(addr) ((addr) & 0x7f)
/*---------------------------------------------------------------------------*/
static usb_endpoint_t usb_endpoints[USB_MAX_ENDPOINTS];
struct process *event_process = 0;
volatile static unsigned int events = 0;
static uint8_t ep0status;
/*---------------------------------------------------------------------------*/
static uint8_t ep0_tx(void);
static uint8_t ep_tx(uint8_t ep_hw);
static void in_ep_interrupt_handler(uint8_t ep_hw);
static void out_ep_interrupt_handler(uint8_t ep_hw);
static void ep0_interrupt_handler(void);
/*---------------------------------------------------------------------------*/
static uint8_t
disable_irq(void)
{
uint8_t enabled = NVIC_IsIRQEnabled(USB_IRQn);
if(enabled) {
NVIC_DisableIRQ(USB_IRQn);
}
return enabled;
}
/*---------------------------------------------------------------------------*/
static void
restore_irq(uint8_t enabled)
{
if(enabled) {
NVIC_EnableIRQ(USB_IRQn);
}
}
/*---------------------------------------------------------------------------*/
static void
notify_process(unsigned int e)
{
events |= e;
if(event_process) {
process_poll(event_process);
}
}
/*---------------------------------------------------------------------------*/
static void
notify_ep_process(usb_endpoint_t *ep, unsigned int e)
{
ep->events |= e;
if(ep->event_process) {
process_poll(ep->event_process);
}
}
/*---------------------------------------------------------------------------*/
void
usb_set_ep_event_process(unsigned char addr, struct process *p)
{
usb_endpoint_t *ep = EP_STRUCT(addr);
ep->event_process = p;
}
/*---------------------------------------------------------------------------*/
void
usb_arch_set_global_event_process(struct process *p)
{
event_process = p;
}
/*---------------------------------------------------------------------------*/
unsigned int
usb_arch_get_global_events(void)
{
uint8_t flag;
volatile unsigned int e;
flag = disable_irq();
e = events;
events = 0;
restore_irq(flag);
return e;
}
/*---------------------------------------------------------------------------*/
unsigned int
usb_get_ep_events(uint8_t addr)
{
volatile unsigned int e;
uint8_t flag;
usb_endpoint_t *ep = EP_STRUCT(addr);
flag = disable_irq();
e = ep->events;
ep->events = 0;
restore_irq(flag);
return e;
}
/*---------------------------------------------------------------------------*/
static void
read_hw_buffer(uint8_t *to, uint8_t hw_ep, unsigned int len)
{
uint32_t fifo_addr = USB_F0 + (hw_ep << 3);
if(USB_ARCH_CONF_DMA && len > UDMA_SIZE_THRESHOLD) {
/* Set the transfer source and destination addresses */
udma_set_channel_src(USB_ARCH_CONF_RX_DMA_CHAN, fifo_addr);
udma_set_channel_dst(USB_ARCH_CONF_RX_DMA_CHAN,
(uint32_t)(to) + len - 1);
/* Configure the control word */
udma_set_channel_control_word(USB_ARCH_CONF_RX_DMA_CHAN,
UDMA_RX_FLAGS | udma_xfer_size(len));
/* Enabled the RF RX uDMA channel */
udma_channel_enable(USB_ARCH_CONF_RX_DMA_CHAN);
/* Trigger the uDMA transfer */
udma_channel_sw_request(USB_ARCH_CONF_RX_DMA_CHAN);
/* Wait for the transfer to complete. */
while(udma_channel_get_mode(USB_ARCH_CONF_RX_DMA_CHAN));
} else {
while(len--) {
*to++ = REG(fifo_addr);
}
}
}
/*---------------------------------------------------------------------------*/
static void
write_hw_buffer(uint8_t hw_ep, uint8_t *from, unsigned int len)
{
uint32_t fifo_addr = USB_F0 + (hw_ep << 3);
if(USB_ARCH_CONF_DMA && len > UDMA_SIZE_THRESHOLD) {
/* Set the transfer source and destination addresses */
udma_set_channel_src(USB_ARCH_CONF_TX_DMA_CHAN,
(uint32_t)(from) + len - 1);
udma_set_channel_dst(USB_ARCH_CONF_TX_DMA_CHAN, fifo_addr);
/* Configure the control word */
udma_set_channel_control_word(USB_ARCH_CONF_TX_DMA_CHAN,
UDMA_TX_FLAGS | udma_xfer_size(len));
/* Enabled the RF RX uDMA channel */
udma_channel_enable(USB_ARCH_CONF_TX_DMA_CHAN);
/* Trigger the uDMA transfer */
udma_channel_sw_request(USB_ARCH_CONF_TX_DMA_CHAN);
/* Wait for the transfer to complete. */
while(udma_channel_get_mode(USB_ARCH_CONF_TX_DMA_CHAN));
} else {
while(len--) {
REG(fifo_addr) = *from++;
}
}
}
/*---------------------------------------------------------------------------*/
static void
reset(void)
{
uint8_t e;
for(e = 0; e < USB_MAX_ENDPOINTS; e++) {
if(usb_endpoints[e].flags & USB_EP_FLAGS_ENABLED) {
usb_buffer *buffer = usb_endpoints[e].buffer;
usb_endpoints[e].flags = 0;
usb_disable_endpoint(e);
while(buffer) {
buffer->flags &= ~USB_BUFFER_SUBMITTED;
buffer = buffer->next;
}
}
}
usb_arch_setup_control_endpoint(0);
}
/*---------------------------------------------------------------------------*/
static bool
permit_pm1(void)
{
/*
* Note: USB Suspend/Resume/Remote Wake-Up are not supported. Once the PLL is
* on, it stays on.
*/
return REG(USB_CTRL) == 0;
}
/*---------------------------------------------------------------------------*/
/* Init USB */
void
usb_arch_setup(void)
{
uint8_t i;
lpm_register_peripheral(permit_pm1);
/* Switch on USB PLL & USB module */
REG(USB_CTRL) = USB_CTRL_USB_EN | USB_CTRL_PLL_EN;
/* Wait until USB PLL is stable */
while(!(REG(USB_CTRL) & USB_CTRL_PLL_LOCKED));
/* Enable pull-up on usb port if driven by GPIO */
#if defined(USB_PULLUP_PORT_BASE) && defined(USB_PULLUP_PIN_MASK)
GPIO_SET_OUTPUT(USB_PULLUP_PORT_BASE, USB_PULLUP_PIN_MASK);
GPIO_SET_PIN(USB_PULLUP_PORT_BASE, USB_PULLUP_PIN_MASK);
#endif
for(i = 0; i < USB_MAX_ENDPOINTS; i++) {
usb_endpoints[i].flags = 0;
usb_endpoints[i].event_process = 0;
}
reset();
/* Disable all EP interrupts, EP0 interrupt will be enabled later */
REG(USB_IIE) = 0;
REG(USB_OIE) = 0;
/* Initialise the USB control structures */
if(USB_ARCH_CONF_DMA) {
/* Disable peripheral triggers for our channels */
udma_channel_mask_set(USB_ARCH_CONF_RX_DMA_CHAN);
udma_channel_mask_set(USB_ARCH_CONF_TX_DMA_CHAN);
}
NVIC_EnableIRQ(USB_IRQn);
}
/*---------------------------------------------------------------------------*/
void
usb_submit_recv_buffer(uint8_t addr, usb_buffer *buffer)
{
usb_buffer **tailp;
uint8_t flag;
usb_endpoint_t *ep = EP_STRUCT(addr);
if(!(ep->flags & USB_EP_FLAGS_ENABLED)) {
return;
}
if(buffer->data == NULL && EP_HW_NUM(addr) == 0) {
if(buffer->flags & USB_BUFFER_NOTIFY) {
notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION);
}
return;
}
flag = disable_irq();
tailp = &ep->buffer;
while(*tailp) {
tailp = &(*tailp)->next;
}
*tailp = buffer;
while(buffer) {
buffer->flags |= USB_BUFFER_SUBMITTED;
buffer = buffer->next;
}
REG(USB_INDEX) = EP_HW_NUM(addr);
if(!EP_HW_NUM(ep->addr)) {
if(REG(USB_CS0) & USB_CS0_OUTPKT_RDY) {
ep0_interrupt_handler();
}
} else {
if(REG(USB_CSOL) & USB_CSOL_OUTPKT_RDY) {
out_ep_interrupt_handler(EP_HW_NUM(ep->addr));
}
}
restore_irq(flag);
}
/*---------------------------------------------------------------------------*/
void
usb_submit_xmit_buffer(uint8_t addr, usb_buffer *buffer)
{
usb_buffer **tailp;
uint8_t flag;
uint8_t res;
usb_endpoint_t *ep = EP_STRUCT(addr);
if(!(ep->flags & USB_EP_FLAGS_ENABLED)) {
return;
}
flag = disable_irq();
if(EP_HW_NUM(addr) == 0) {
if(buffer->data == NULL) {
/* We are asked to send a STATUS packet.
* But the USB hardware will do this automatically
* as soon as we release the HW FIFO. */
REG(USB_INDEX) = 0;
REG(USB_CS0) = USB_CS0_CLR_OUTPKT_RDY | USB_CS0_DATA_END;
notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION);
restore_irq(flag);
return;
} else {
/* Release the HW FIFO */
REG(USB_INDEX) = 0;
REG(USB_CS0) = USB_CS0_CLR_OUTPKT_RDY;
}
}
tailp = &ep->buffer;
while(*tailp) {
tailp = &(*tailp)->next;
}
*tailp = buffer;
while(buffer) {
buffer->flags |= USB_BUFFER_SUBMITTED | USB_BUFFER_IN;
buffer = buffer->next;
}
REG(USB_INDEX) = EP_HW_NUM(ep->addr);
if(EP_HW_NUM(ep->addr)) {
res = ep_tx(EP_HW_NUM(ep->addr));
} else {
res = ep0_tx();
}
restore_irq(flag);
if(res & USB_WRITE_NOTIFY) {
notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION);
}
}
/*---------------------------------------------------------------------------*/
static void
ep0_setup(void)
{
REG(USB_IIE) |= USB_IIE_EP0IE;
}
/*---------------------------------------------------------------------------*/
static void
in_ep_setup(uint8_t addr)
{
uint8_t ei = EP_HW_NUM(addr);
usb_endpoint_t *ep = EP_STRUCT(addr);
/* Enable IN EP interrupt */
REG(USB_IIE) |= USBIIE_INEPxIE(ei);
/* Set internal FIFO size */
REG(USB_MAXI) = ep->xfer_size / 8;
if(IS_ISO_EP(ep)) {
REG(USB_CSIH) |= USB_CSOH_ISO;
} else {
REG(USB_CSIH) &= ~USB_CSOH_ISO;
}
}
/*---------------------------------------------------------------------------*/
static void
out_ep_setup(uint8_t addr)
{
uint8_t ei = EP_HW_NUM(addr);
usb_endpoint_t *ep = EP_STRUCT(addr);
/* Enable OUT EP interrupt */
REG(USB_OIE) |= USBOIE_OUEPxIE(ei);
/* Set internal FIFO size */
REG(USB_MAXO) = ep->xfer_size / 8;
if(IS_ISO_EP(ep)) {
REG(USB_CSOH) |= USB_CSOH_ISO;
} else {
REG(USB_CSOH) &= ~USB_CSOH_ISO;
}
}
/*---------------------------------------------------------------------------*/
static void
ep_setup(uint8_t addr)
{
uint8_t ei = EP_HW_NUM(addr);
uint8_t flag;
usb_endpoint_t *ep = EP_STRUCT(addr);
ep->halted = 0;
ep->flags |= USB_EP_FLAGS_ENABLED;
ep->buffer = 0;
ep->addr = addr;
ep->events = 0;
ep->xfer_size = ep_xfer_size[ei];
flag = disable_irq();
/* Select endpoint register */
REG(USB_INDEX) = ei;
/* EP0 requires special handing */
if(ei == 0) {
ep0_setup();
} else {
if(addr & 0x80) {
in_ep_setup(addr);
} else {
out_ep_setup(addr);
}
}
restore_irq(flag);
}
/*---------------------------------------------------------------------------*/
void
usb_arch_setup_iso_endpoint(uint8_t addr)
{
usb_endpoint_t *ep = EP_STRUCT(addr);
ep->flags = USB_EP_FLAGS_TYPE_ISO;
ep_setup(addr);
}
/*---------------------------------------------------------------------------*/
void
usb_arch_setup_control_endpoint(uint8_t addr)
{
usb_endpoint_t *ep = EP_STRUCT(addr);
ep->flags = USB_EP_FLAGS_TYPE_CONTROL;
ep_setup(addr);
}
/*---------------------------------------------------------------------------*/
void
usb_arch_setup_bulk_endpoint(uint8_t addr)
{
usb_endpoint_t *ep = EP_STRUCT(addr);
ep->flags = USB_EP_FLAGS_TYPE_BULK;
ep_setup(addr);
}
/*---------------------------------------------------------------------------*/
void
usb_arch_setup_interrupt_endpoint(uint8_t addr)
{
usb_endpoint_t *ep = EP_STRUCT(addr);
ep->flags = USB_EP_FLAGS_TYPE_INTERRUPT;
ep_setup(addr);
}
/*---------------------------------------------------------------------------*/
static void
ep0_dis(void)
{
REG(USB_IIE) &= ~USB_IIE_EP0IE;
/* Clear any pending status flags */
REG(USB_CS0) = 0xC0;
}
/*---------------------------------------------------------------------------*/
static void
in_ep_dis(uint8_t addr)
{
REG(USB_MAXI) = 0;
REG(USB_IIE) &= ~USBIIE_INEPxIE(EP_HW_NUM(addr));
/* Flush pending */
REG(USB_CSIL) = USB_CSIL_FLUSH_PACKET;
}
/*---------------------------------------------------------------------------*/
static void
out_ep_dis(uint8_t addr)
{
REG(USB_MAXO) = 0;
REG(USB_OIE) &= ~USBOIE_OUEPxIE(EP_HW_NUM(addr));
/* Flush pending */
REG(USB_CSOL) = USB_CSIL_FLUSH_PACKET;
}
/*---------------------------------------------------------------------------*/
void
usb_arch_disable_endpoint(uint8_t addr)
{
uint8_t ei = EP_HW_NUM(addr);
uint8_t flag;
usb_endpoint_t *ep = EP_STRUCT(addr);
ep->flags &= ~USB_EP_FLAGS_ENABLED;
flag = disable_irq();
REG(USB_INDEX) = ei;
if(ei == 0) {
ep0_dis();
} else {
if(addr & 0x80) {
in_ep_dis(addr);
} else {
out_ep_dis(addr);
}
}
restore_irq(flag);
}
/*---------------------------------------------------------------------------*/
void
usb_arch_discard_all_buffers(uint8_t addr)
{
usb_buffer *buffer;
uint8_t flag;
volatile usb_endpoint_t *ep = EP_STRUCT(addr);
flag = disable_irq();
buffer = ep->buffer;
ep->buffer = NULL;
restore_irq(flag);
while(buffer) {
buffer->flags &= ~USB_BUFFER_SUBMITTED;
buffer = buffer->next;
}
}
/*---------------------------------------------------------------------------*/
static void
set_stall(uint8_t addr, uint8_t stall)
{
uint8_t ei = EP_HW_NUM(addr);
REG(USB_INDEX) = ei;
if(ei == 0) {
/* Stall is automatically deasserted on EP0 */
if(stall) {
ep0status = EP_STATUS_IDLE;
REG(USB_CS0) |= USB_CS0_SEND_STALL | USB_CS0_OUTPKT_RDY;
}
} else {
if(addr & 0x80) {
if(stall) {
REG(USB_CSIL) |= USB_CSIL_SEND_STALL;
} else {
REG(USB_CSIL) &= ~USB_CSIL_SEND_STALL;
}
} else {
if(stall) {
REG(USB_CSOL) |= USB_CSOL_SEND_STALL;
} else {
REG(USB_CSOL) &= ~USB_CSOL_SEND_STALL;
}
}
}
}
/*---------------------------------------------------------------------------*/
void
usb_arch_control_stall(uint8_t addr)
{
uint8_t ei = EP_HW_NUM(addr);
uint8_t flag;
if(ei > USB_MAX_ENDPOINTS) {
return;
}
flag = disable_irq();
set_stall(addr, 1);
restore_irq(flag);
}
/*---------------------------------------------------------------------------*/
void
usb_arch_halt_endpoint(uint8_t addr, int halt)
{
uint8_t ei = EP_HW_NUM(addr);
uint8_t flag;
usb_endpoint_t *ep = EP_STRUCT(addr);
if(ei > USB_MAX_ENDPOINTS) {
return;
}
if(!(ep->flags & USB_EP_FLAGS_ENABLED)) {
return;
}
flag = disable_irq();
if(halt) {
ep->halted = 0x1;
set_stall(addr, 1);
} else {
ep->halted = 0;
set_stall(addr, 0);
if(ep->buffer && (ep->buffer->flags & USB_BUFFER_HALT)) {
ep->buffer->flags &= ~USB_BUFFER_SUBMITTED;
if(ep->buffer->flags & USB_BUFFER_NOTIFY) {
notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION);
}
ep->buffer = ep->buffer->next;
}
if(ei) {
out_ep_interrupt_handler(EP_HW_NUM(addr));
}
}
restore_irq(flag);
}
/*---------------------------------------------------------------------------*/
void
usb_arch_set_configuration(uint8_t usb_configuration_value)
{
return;
}
/*---------------------------------------------------------------------------*/
uint16_t
usb_arch_get_ep_status(uint8_t addr)
{
uint8_t ei = EP_INDEX(addr);
usb_endpoint_t *ep = EP_STRUCT(addr);
if(ei > USB_MAX_ENDPOINTS) {
return 0;
}
return ep->halted;
}
/*---------------------------------------------------------------------------*/
void
usb_arch_set_address(uint8_t addr)
{
REG(USB_ADDR) = addr;
}
/*---------------------------------------------------------------------------*/
int
usb_arch_send_pending(uint8_t addr)
{
uint8_t flag;
uint8_t ret;
uint8_t ei = EP_INDEX(addr);
flag = disable_irq();
REG(USB_INDEX) = ei;
if(ei == 0) {
ret = REG(USB_CS0) & USB_CS0_INPKT_RDY;
} else {
ret = REG(USB_CSIL) & USB_CSIL_INPKT_RDY;
}
restore_irq(flag);
return ret;
}
/*---------------------------------------------------------------------------*/
static unsigned int
get_receive_capacity(usb_buffer *buffer)
{
unsigned int capacity = 0;
while(buffer &&
!(buffer->flags & (USB_BUFFER_IN | USB_BUFFER_SETUP | USB_BUFFER_HALT))) {
capacity += buffer->left;
buffer = buffer->next;
}
return capacity;
}
/*---------------------------------------------------------------------------*/
static usb_buffer *
skip_buffers_until(usb_buffer *buffer, unsigned int mask, unsigned int flags,
uint8_t *resp)
{
while(buffer && !((buffer->flags & mask) == flags)) {
buffer->flags &= ~USB_BUFFER_SUBMITTED;
buffer->flags |= USB_BUFFER_FAILED;
if(buffer->flags & USB_BUFFER_NOTIFY) {
*resp |= USB_READ_NOTIFY;
}
buffer = buffer->next;
}
return buffer;
}
/*---------------------------------------------------------------------------*/
static uint8_t
fill_buffers(usb_buffer *buffer, uint8_t hw_ep, unsigned int len,
uint8_t short_packet)
{
unsigned int t;
uint8_t res = 0;
do {
if(buffer->left < len) {
t = buffer->left;
} else {
t = len;
}
len -= t;
buffer->left -= t;
read_hw_buffer(buffer->data, hw_ep, t);
buffer->data += t;
if(len == 0) {
break;
}
buffer->flags &= ~(USB_BUFFER_SUBMITTED | USB_BUFFER_SHORT_PACKET);
if(buffer->flags & USB_BUFFER_NOTIFY) {
res |= USB_READ_NOTIFY;
}
buffer = buffer->next;
} while(1);
if(short_packet) {
buffer->flags |= USB_BUFFER_SHORT_PACKET;
}
if((buffer->left == 0) || (buffer->flags & USB_BUFFER_PACKET_END)) {
buffer->flags &= ~USB_BUFFER_SUBMITTED;
if(buffer->flags & USB_BUFFER_NOTIFY) {
res |= USB_READ_NOTIFY;
}
buffer = buffer->next;
} else {
if(short_packet) {
if(buffer->left && !(buffer->flags & USB_BUFFER_SHORT_END)) {
buffer->flags |= USB_BUFFER_FAILED;
res |= USB_READ_FAIL;
}
buffer->flags &= ~USB_BUFFER_SUBMITTED;
if(buffer->flags & USB_BUFFER_NOTIFY) {
res |= USB_READ_NOTIFY;
}
buffer = buffer->next;
}
}
usb_endpoints[hw_ep].buffer = buffer;
return res;
}
/*---------------------------------------------------------------------------*/
static uint8_t
ep0_get_setup_pkt(void)
{
uint8_t res = 0;
usb_buffer *buffer =
skip_buffers_until(usb_endpoints[0].buffer, USB_BUFFER_SETUP,
USB_BUFFER_SETUP, &res);
usb_endpoints[0].buffer = buffer;
if(!buffer || buffer->left < 8) {
return USB_READ_BLOCK;
}
read_hw_buffer(buffer->data, 0, 8);
buffer->left -= 8;
buffer->flags &= ~USB_BUFFER_SUBMITTED;
if(buffer->flags & USB_BUFFER_NOTIFY) {
res |= USB_READ_NOTIFY;
}
if(buffer->data[6] || buffer->data[7]) {
REG(USB_CS0) |= USB_CS0_CLR_OUTPKT_RDY;
ep0status = buffer->data[0] & 0x80 ? EP_STATUS_TX : EP_STATUS_RX;
}
buffer->data += 8;
usb_endpoints[0].buffer = buffer->next;
return res;
}
/*---------------------------------------------------------------------------*/
static uint8_t
ep0_get_data_pkt(void)
{
uint8_t res = 0;
uint8_t short_packet = 0;
usb_buffer *buffer = usb_endpoints[0].buffer;
uint8_t len = REG(USB_CNT0);
if(!buffer) {
return USB_READ_BLOCK;
}
if(buffer->flags & (USB_BUFFER_SETUP | USB_BUFFER_IN)) {
buffer->flags |= USB_BUFFER_FAILED;
buffer->flags &= ~USB_BUFFER_SUBMITTED;
if(buffer->flags & USB_BUFFER_NOTIFY) {
res |= USB_READ_NOTIFY;
}
/* Flush the fifo */
while(len--) {
REG(USB_F0);
}
usb_endpoints[0].buffer = buffer->next;
/* Force data stage end */
REG(USB_CS0) |= USB_CS0_CLR_OUTPKT_RDY | USB_CS0_DATA_END;
ep0status = EP_STATUS_IDLE;
return res;
}
if(get_receive_capacity(buffer) < len) {
/* Wait until we queue more buffers */
return USB_READ_BLOCK;
}
if(len < usb_endpoints[0].xfer_size) {
short_packet = 1;
}
res = fill_buffers(buffer, 0, len, short_packet);
if(short_packet) {
/* The usb-core will send a status packet, we will release the fifo at this stage */
ep0status = EP_STATUS_IDLE;
} else {
REG(USB_CS0) |= USB_CS0_CLR_OUTPKT_RDY;
}
return res;
}
/*---------------------------------------------------------------------------*/
static uint8_t
ep0_tx(void)
{
usb_buffer *buffer = usb_endpoints[0].buffer;
unsigned int len = usb_endpoints[0].xfer_size;
uint8_t data_end = 0;
uint8_t res = 0;
/* If TX Fifo still busy or ep0 not in TX data stage don't do anything */
if((REG(USB_CS0) & USB_CS0_INPKT_RDY) || (ep0status != EP_STATUS_TX)) {
return 0;
}
if(!buffer) {
return 0;
}
if(!(buffer->flags & USB_BUFFER_IN)) {
/* We should TX but queued buffer is in RX */
return 0;
}
while(buffer) {
unsigned int copy;
if(buffer->left < len) {
copy = buffer->left;
} else {
copy = len;
}
len -= copy;
buffer->left -= copy;
write_hw_buffer(0, buffer->data, copy);
buffer->data += copy;
if(buffer->left == 0) {
if(buffer->flags & USB_BUFFER_SHORT_END) {
if(len == 0) {
break; // We keep the buffer in queue so we will send a ZLP next time.
} else {
data_end = 1;
len = 0; // Stop looking for more data to send
}
}
buffer->flags &= ~USB_BUFFER_SUBMITTED;
if(buffer->flags & USB_BUFFER_NOTIFY) {
res |= USB_WRITE_NOTIFY;
}
buffer = buffer->next;
}
if(len == 0) {
break; // FIFO is full, send packet.
}
}
if(len) {
data_end = 1;
}
usb_endpoints[0].buffer = buffer;
/*
* Workaround the fact that the usb controller do not like to have DATA_END
* set after INPKT_RDY for the last packet. Thus if no more is in the queue
* set DATA_END
*/
if(data_end || !buffer) {
ep0status = EP_STATUS_IDLE;
REG(USB_CS0) |= USB_CS0_INPKT_RDY | USB_CS0_DATA_END;
} else {
REG(USB_CS0) |= USB_CS0_INPKT_RDY;
}
return res;
}
/*---------------------------------------------------------------------------*/
static void
ep0_interrupt_handler(void)
{
uint8_t cs0;
uint8_t res;
REG(USB_INDEX) = 0;
cs0 = REG(USB_CS0);
if(cs0 & USB_CS0_SENT_STALL) {
/* Ack the stall */
REG(USB_CS0) = 0;
ep0status = EP_STATUS_IDLE;
}
if(cs0 & USB_CS0_SETUP_END) {
/* Clear it */
REG(USB_CS0) = USB_CS0_CLR_SETUP_END;
ep0status = EP_STATUS_IDLE;
}
if(cs0 & USB_CS0_OUTPKT_RDY) {
if(ep0status == EP_STATUS_IDLE) {
res = ep0_get_setup_pkt();
} else {
res = ep0_get_data_pkt();
}
if(res & USB_READ_NOTIFY) {
notify_ep_process(&usb_endpoints[0], USB_EP_EVENT_NOTIFICATION);
}
if(res & USB_READ_BLOCK) {
return;
}
}
res = ep0_tx();
if(res & USB_WRITE_NOTIFY) {
notify_ep_process(&usb_endpoints[0], USB_EP_EVENT_NOTIFICATION);
}
}
/*---------------------------------------------------------------------------*/
static uint8_t
ep_tx(uint8_t ep_hw)
{
unsigned int len;
uint8_t res = 0;
usb_endpoint_t *ep = EP_STRUCT(ep_hw);
len = ep->xfer_size;
if(ep->halted) {
return 0;
}
if(!ep->buffer || !(ep->buffer->flags & USB_BUFFER_IN)) {
return 0;
}
while(ep->buffer) {
unsigned int copy;
if(ep->buffer->left < len) {
copy = ep->buffer->left;
} else {
copy = len;
}
len -= copy;
ep->buffer->left -= copy;
/*
* Delay somewhat if the previous packet has not yet left the IN FIFO,
* making sure the dog doesn't bark while we're waiting
*/
while(REG(USB_CSIL) & USB_CSIL_INPKT_RDY) {
watchdog_periodic();
}
write_hw_buffer(EP_INDEX(ep_hw), ep->buffer->data, copy);
ep->buffer->data += copy;
if(ep->buffer->left == 0) {
if(ep->buffer->flags & USB_BUFFER_SHORT_END) {
if(len == 0) {
/* We keep the buffer in queue so we will send a ZLP next */
break;
} else {
/* Stop looking for more data to send */
len = 0;
}
}
ep->buffer->flags &= ~USB_BUFFER_SUBMITTED;
if(ep->buffer->flags & USB_BUFFER_NOTIFY) {
res |= USB_WRITE_NOTIFY;
}
ep->buffer = ep->buffer->next;
}
if(len == 0) {
/* FIFO full, send */
break;
}
}
REG(USB_CSIL) |= USB_CSIL_INPKT_RDY;
return res;
}
/*---------------------------------------------------------------------------*/
static uint8_t
ep_get_data_pkt(uint8_t ep_hw)
{
uint16_t pkt_len;
uint8_t res;
uint8_t short_packet = 0;
usb_endpoint_t *ep = EP_STRUCT(ep_hw);
if(!ep->buffer) {
return USB_READ_BLOCK;
}
if(ep->buffer->flags & USB_BUFFER_HALT) {
ep->halted = 1;
if(!(REG(USB_CSOL) & USB_CSOL_SEND_STALL)) {
REG(USB_CSOL) |= USB_CSOL_SEND_STALL;
}
return 0;
}
/* Disambiguate UG CNTL bits */
pkt_len = REG(USB_CNTL) | (REG(USB_CNTH) << 8);
if(get_receive_capacity(ep->buffer) < pkt_len) {
return USB_READ_BLOCK;
}
if(pkt_len < ep->xfer_size) {
short_packet = 1;
}
res = fill_buffers(ep->buffer, ep_hw, pkt_len, short_packet);
REG(USB_CSOL) &= ~USB_CSOL_OUTPKT_RDY;
return res;
}
/*---------------------------------------------------------------------------*/
static void
out_ep_interrupt_handler(uint8_t ep_hw)
{
uint8_t csl;
uint8_t res;
usb_endpoint_t *ep = EP_STRUCT(ep_hw);
REG(USB_INDEX) = ep_hw;
csl = REG(USB_CSOL);
if(csl & USB_CSOL_SENT_STALL) {
REG(USB_CSOL) &= ~USB_CSOL_SENT_STALL;
}
if(csl & USB_CSOL_OVERRUN) {
/* We lost one isochronous packet */
REG(USB_CSOL) &= ~USB_CSOL_OVERRUN;
}
if(csl & USB_CSOL_OUTPKT_RDY) {
res = ep_get_data_pkt(ep_hw);
if(res & USB_READ_NOTIFY) {
notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION);
}
}
}
/*---------------------------------------------------------------------------*/
static void
in_ep_interrupt_handler(uint8_t ep_hw)
{
uint8_t csl;
#if USB_ARCH_WRITE_NOTIFY
uint8_t res;
usb_endpoint_t *ep = EP_STRUCT(ep_hw);
#endif
REG(USB_INDEX) = ep_hw;
csl = REG(USB_CSIL);
if(csl & USB_CSIL_SENT_STALL) {
REG(USB_CSIL) &= ~USB_CSIL_SENT_STALL;
}
if(csl & USB_CSIL_UNDERRUN) {
REG(USB_CSIL) &= ~USB_CSIL_UNDERRUN;
}
#if USB_ARCH_WRITE_NOTIFY
if(!(csl & USB_CSIL_INPKT_RDY)) {
res = ep_tx(ep_hw);
if(res & USB_WRITE_NOTIFY) {
notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION);
}
}
#endif
}
/*---------------------------------------------------------------------------*/
void
usb_isr(void)
{
uint8_t ep_in_if = REG(USB_IIF) & REG(USB_IIE);
uint8_t ep_out_if = REG(USB_OIF) & REG(USB_OIE);
uint8_t common_if = REG(USB_CIF) & REG(USB_CIE);
uint8_t i;
ENERGEST_ON(ENERGEST_TYPE_IRQ);
if(ep_in_if) {
/* EP0 flag is in the IN Interrupt Flags register */
if(ep_in_if & USB_IIF_EP0IF) {
ep0_interrupt_handler();
}
for(i = 1; i < 6; i++) {
if(ep_in_if & EPxIF(i)) {
in_ep_interrupt_handler(i);
}
}
}
if(ep_out_if) {
for(i = 1; i < 6; i++) {
if(ep_out_if & EPxIF(i)) {
out_ep_interrupt_handler(i);
}
}
}
if(common_if & USB_CIF_RSTIF) {
reset();
notify_process(USB_EVENT_RESET);
}
if(common_if & USB_CIF_RESUMEIF) {
notify_process(USB_EVENT_RESUME);
}
if(common_if & USB_CIF_SUSPENDIF) {
notify_process(USB_EVENT_SUSPEND);
}
ENERGEST_OFF(ENERGEST_TYPE_IRQ);
}
/*---------------------------------------------------------------------------*/
/** @} */