diff --git a/cpu/cc253x/usb/usb-arch.c b/cpu/cc253x/usb/usb-arch.c new file mode 100644 index 000000000..ec0b96210 --- /dev/null +++ b/cpu/cc253x/usb/usb-arch.c @@ -0,0 +1,1307 @@ +/* +Copyright (c) 2012, Philippe Retornaz +Copyright (c) 2012, EPFL STI IMT LSRO1 -- Mobots group +All rights reserved. + +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. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. +*/ + +#include "contiki.h" +#include "usb-arch.h" +#include "p2-intr.h" + +#include "cc253x.h" +#include "sfr-bits.h" +#include "port.h" + +#include "dma.h" + +// P1_0 +#define USB_PULLUP_PORT 1 +#define USB_PULLUP_PIN 0 + +#define USBCTRL_USB_EN (1 << 0) +#define USBCTRL_PLL_EN (1 << 1) +#define USBCTRL_PLL_LOCKED (1 << 7) + +#define USBIIE_EP0IE (1 << 0) +#define USBIIE_INEPxIE(x) (1 << x) + +#define USBOIE_OUEPxIE(x) (1 << x) + +#define USBCSxH_ISO (1 << 6) + +#define USBCS0_CLR_SETUP_END (1 << 7) +#define USBCS0_CLR_OUTPKT_RDY (1 << 6) +#define USBCS0_SEND_STALL (1 << 5) +#define USBCS0_SETUP_END (1 << 4) +#define USBCS0_DATA_END (1 << 3) +#define USBCS0_SENT_STALL (1 << 2) +#define USBCS0_INPKT_RDY (1 << 1) +#define USBCS0_OUTPKT_RDY (1 << 0) + +#define USBCSIL_SENT_STALL (1 << 5) +#define USBCSIL_SEND_STALL (1 << 4) +#define USBCSIL_FLUSH_PACKET (1 << 3) +#define USBCSIL_UNDERRUN (1 << 2) +#define USBCSIL_PKT_PRESENT (1 << 1) +#define USBCSIL_INPKT_RDY (1 << 0) + +#define USBCSOL_SENT_STALL (1 << 6) +#define USBCSOL_SEND_STALL (1 << 5) +#define USBCSOL_OVERRUN (1 << 2) +#define USBCSOL_OUTPKT_RDY (1 << 0) + +// Double buffering not used +// We assume only the IN or the OUT of +// each endpoint is enabled and not both. +#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 + + +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, +}; + + +#define EP_IDLE 0 +#define EP_RX 1 +#define EP_TX 2 +static uint8_t ep0status; + +typedef struct _USBEndpoint USBEndpoint; +struct _USBEndpoint { + uint8_t halted; + uint8_t addr; + uint8_t flags; + USBBuffer *buffer; /* NULL if no current buffer */ + struct process *event_process; + unsigned int events; + uint16_t xfer_size; +}; + +#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 USB_WRITE_BLOCK 0x01 +#define USB_READ_BLOCK 0x01 /* The currently submitted buffers + can't hold the received data, wait + for more buffers. No data was read + from the hardware buffer */ +#define USB_WRITE_NOTIFY 0x02 +#define USB_READ_NOTIFY 0x02 /* Some buffers that had the + USB_BUFFER_NOTIFY flags set were + released */ +#define USB_READ_FAIL 0x4 + + +/* 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) + +#define usb_irq_disable(flag) cc253x_p2_irq_disable(flag) +#define usb_irq_enable(flag) cc253x_p2_irq_enable(flag) + +static uint8_t usb_irq(void); +static void usb_arch_epin_irq(uint8_t ep_hw); +static void usb_arch_epout_irq(uint8_t ep_hw); +static void usb_arch_ep0_irq(void); + +static struct cc253x_p2_handler usb_irq_handler = { NULL, usb_irq }; + +static USBEndpoint usb_endpoints[USB_MAX_ENDPOINTS]; +struct process *event_process = 0; +volatile static unsigned int events = 0; + +static uint8_t ep0_tx(void); +static uint8_t ep_tx(uint8_t ep_hw); + +static void +notify_process(unsigned int e) +{ + events |= e; + if(event_process) { + process_poll(event_process); + } +} + +static void +notify_ep_process(USBEndpoint * 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) +{ + USBEndpoint *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; + + usb_irq_disable(flag); + e = events; + events = 0; + usb_irq_enable(flag); + + return e; +} + +unsigned int +usb_get_ep_events(uint8_t addr) +{ + volatile unsigned int e; + uint8_t flag; + USBEndpoint *ep = EP_STRUCT(addr); + + usb_irq_disable(flag); + e = ep->events; + ep->events = 0; + usb_irq_enable(flag); + + return e; +} +#if DMA_ON +#ifndef DMA_USB_CHANNEL +#error You must set DMA_USB_CHANNEL to a valid dma channel. +#endif +static void +read_hw_buffer_dma(uint8_t tl, uint8_t th, uint8_t __xdata * xptr, + unsigned int len) +{ + dma_conf[DMA_USB_CHANNEL].src_h = ((uint16_t) xptr) >> 8; + dma_conf[DMA_USB_CHANNEL].src_l = ((uint16_t) xptr) & 0xFF; + dma_conf[DMA_USB_CHANNEL].dst_h = th; + dma_conf[DMA_USB_CHANNEL].dst_l = tl; + + // Maximum USB len transfert is 512bytes, maximum DMA len: 4096, we are safe. + dma_conf[DMA_USB_CHANNEL].len_h = len >> 8; + dma_conf[DMA_USB_CHANNEL].len_l = len & 0xFF; + dma_conf[DMA_USB_CHANNEL].wtt = DMA_T_NONE | DMA_BLOCK; + // Maximum prio, we will be polling until the transfert is done. + dma_conf[DMA_USB_CHANNEL].inc_prio = + DMA_SRC_INC_NO | DMA_DST_INC_1 | DMA_PRIO_HIGH; + + DMA_ARM(DMA_USB_CHANNEL); + // Wait until the channel is armed + while(!(DMAARM & (1 << DMA_USB_CHANNEL))); + + DMA_TRIGGER(DMA_USB_CHANNEL); + // Wait until the transfert is done. + // For some unknown reason, the DMA channel do not set the IRQ flag + // sometimes, so use the DMAARM bit to check if transfert is done + while(DMAARM & (1 << DMA_USB_CHANNEL)); + // Clear interrupt flag + DMAIRQ = ~(1 << DMA_USB_CHANNEL); +} +static void +write_hw_buffer_dma(uint8_t __xdata * xptr, uint8_t fl, uint8_t fh, + unsigned int len) +{ + dma_conf[DMA_USB_CHANNEL].src_h = fh; + dma_conf[DMA_USB_CHANNEL].src_l = fl; + dma_conf[DMA_USB_CHANNEL].dst_h = ((uint16_t) xptr) >> 8; + dma_conf[DMA_USB_CHANNEL].dst_l = ((uint16_t) xptr) & 0xFF; + + // Maximum USB len transfert is 512bytes, maximum DMA len: 4096, we are safe. + dma_conf[DMA_USB_CHANNEL].len_h = len >> 8; + dma_conf[DMA_USB_CHANNEL].len_l = len & 0xFF; + dma_conf[DMA_USB_CHANNEL].wtt = DMA_T_NONE | DMA_BLOCK; + // Maximum prio, we will be polling until the transfert is done. + dma_conf[DMA_USB_CHANNEL].inc_prio = + DMA_SRC_INC_1 | DMA_DST_INC_NO | DMA_PRIO_HIGH; + + DMA_ARM(DMA_USB_CHANNEL); + // Wait until the channel is armed + while(!(DMAARM & (1 << DMA_USB_CHANNEL))); + + DMA_TRIGGER(DMA_USB_CHANNEL); + // Wait until the transfert is done. + while(DMAARM & (1 << DMA_USB_CHANNEL)); + + // Clear interrupt flag + DMAIRQ = ~(1 << DMA_USB_CHANNEL); +} + +#endif +static void +read_hw_buffer(uint8_t * to, uint8_t hw_ep, unsigned int len) +{ + __xdata uint8_t *from = &USBF0 + (hw_ep << 1); + +#if DMA_ON + // For small transfers we use PIO + // This check is specific to SDCC and large/huge memory model + if(len > 8 && ((uint8_t *) & to)[2] == 0x0 /* x data pointer */ ) { + read_hw_buffer_dma(((uint8_t *) & to)[0], ((uint8_t *) & to)[1], from, + len); + return; + } +#endif + while(len--) { + *to++ = *from; + } +} + +static void +write_hw_buffer(uint8_t hw_ep, uint8_t * from, unsigned int len) +{ + __xdata uint8_t *to = &USBF0 + (hw_ep << 1); + +#if DMA_ON + // For small transfers we use PIO + if(len > 8 && ((uint8_t *) & from)[2] == 0x0 /* x data pointer */ ) { + write_hw_buffer_dma(to, ((uint8_t *) & from)[0], ((uint8_t *) & from)[1], + len); + return; + } +#endif + while(len--) { + *to = *from++; + } +} + +static void +usb_arch_reset(void) +{ + uint8_t e; + + for(e = 0; e < USB_MAX_ENDPOINTS; e++) { + if(usb_endpoints[e].flags & USB_EP_FLAGS_ENABLED) { + USBBuffer *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); +} + +/* Init USB */ +void +usb_arch_setup(void) +{ + uint8_t i; + + /* Switch on USB PLL & USB module */ + USBCTRL = USBCTRL_USB_EN | USBCTRL_PLL_EN; + + /* Wait until USB PLL is stable */ + while(!(USBCTRL & USBCTRL_PLL_LOCKED)); + + /* Enable pull-up on usb port */ + PORT_SET(USB_PULLUP_PORT, USB_PULLUP_PIN); + PORT_DIR_OUTPUT(USB_PULLUP_PORT, USB_PULLUP_PIN); + + for(i = 0; i < USB_MAX_ENDPOINTS; i++) { + usb_endpoints[i].flags = 0; + usb_endpoints[i].event_process = 0; + } + + usb_arch_reset(); + + // Disable all endpoints interrupts + // Enpoint 0 interrupt will be enabled later + USBIIE = 0; + USBOIE = 0; + + cc253x_p2_register_handler(&usb_irq_handler); + // We have to force IRQ enable as we might be the first user + cc253x_p2_irq_force_enable(); +} + +void +usb_submit_recv_buffer(uint8_t addr, USBBuffer * buffer) +{ + USBBuffer **tailp; + uint8_t flag; + USBEndpoint *ep = EP_STRUCT(addr); + + if(!(ep->flags & USB_EP_FLAGS_ENABLED)) { + return; + } + + if(buffer->data == NULL && EP_HW_NUM(addr) == 0) { + // the usb-core is trying to get the STATUS packet (ZLP in this case) + // but the USB hardware is catching them, thus ignore this. + // FIXME: Use the interrupt to release this buffer when the packet is sent + // at least the timing would be better ... + if(buffer->flags & USB_BUFFER_NOTIFY) { + notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION); + } + return; + } + + usb_irq_disable(flag); + + tailp = &ep->buffer; + while(*tailp) { + tailp = &(*tailp)->next; + } + *tailp = buffer; + while(buffer) { + buffer->flags |= USB_BUFFER_SUBMITTED; + buffer = buffer->next; + } + + USBINDEX = EP_HW_NUM(addr); + if(!EP_HW_NUM(ep->addr)) { + if(USBCS0 & USBCS0_OUTPKT_RDY) { + usb_arch_ep0_irq(); + } + } else { + if(USBCSOL & USBCSOL_OUTPKT_RDY) { + usb_arch_epout_irq(EP_HW_NUM(ep->addr)); + } + } + usb_irq_enable(flag); +} + +void +usb_submit_xmit_buffer(uint8_t addr, USBBuffer * buffer) +{ + USBBuffer **tailp; + uint8_t flag; + uint8_t res; + USBEndpoint *ep = EP_STRUCT(addr); + + if(!(ep->flags & USB_EP_FLAGS_ENABLED)) { + return; + } + + usb_irq_disable(flag); + + + if(EP_HW_NUM(addr) == 0) { + if(buffer->data == NULL) { + // We are asked to send a STATUS packet. + // But the USB hardware is doing this automatically + // as soon as we release hw FIFO. + USBINDEX = 0; + USBCS0 = USBCS0_CLR_OUTPKT_RDY | USBCS0_DATA_END; + notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION); + usb_irq_enable(flag); + return; + } else { + // Release the hw FIFO ... + USBINDEX = 0; + USBCS0 = USBCS0_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; + } + + USBINDEX = EP_HW_NUM(ep->addr); + if(EP_HW_NUM(ep->addr)) { + res = ep_tx(EP_HW_NUM(ep->addr)); + } else { + res = ep0_tx(); + } + + usb_irq_enable(flag); + + if(res & USB_WRITE_NOTIFY) { + notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION); + } + +} + + +static void +usb_arch_setup_endpoint0(void) +{ + USBIIE |= USBIIE_EP0IE; +} + +static void +usb_arch_setup_in_endpoint(uint8_t addr) +{ + uint8_t ei = EP_HW_NUM(addr); + USBEndpoint *ep = EP_STRUCT(addr); + + // Enable interrupt + USBIIE |= USBIIE_INEPxIE(ei); + + // Set internal FIFO size + USBMAXI = ep->xfer_size / 8; + + if(IS_ISO_EP(ep)) { + USBCSIH |= USBCSxH_ISO; + } else { + USBCSIH &= ~USBCSxH_ISO; + } +} + +static void +usb_arch_setup_out_endpoint(uint8_t addr) +{ + uint8_t ei = EP_HW_NUM(addr); + USBEndpoint *ep = EP_STRUCT(addr); + + // Enable interrupt + USBOIE |= USBOIE_OUEPxIE(ei); + + // Set internal FIFO size + USBMAXO = ep->xfer_size / 8; + + if(IS_ISO_EP(ep)) { + USBCSOH |= USBCSxH_ISO; + } else { + USBCSOH &= ~USBCSxH_ISO; + } +} + +static void +usb_arch_setup_endpoint(uint8_t addr) +{ + uint8_t ei = EP_HW_NUM(addr); + uint8_t flag; + USBEndpoint *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]; + + usb_irq_disable(flag); + // Select endpoint banked register + USBINDEX = ei; + + // special case for ep 0 + if(ei == 0) { + usb_arch_setup_endpoint0(); + } else { + if(addr & 0x80) { + usb_arch_setup_in_endpoint(addr); + } else { + usb_arch_setup_out_endpoint(addr); + } + } + usb_irq_enable(flag); +} + +void +usb_arch_setup_iso_endpoint(uint8_t addr) +{ + USBEndpoint *ep = EP_STRUCT(addr); + + ep->flags = USB_EP_FLAGS_TYPE_ISO; + + usb_arch_setup_endpoint(addr); +} + +void +usb_arch_setup_control_endpoint(uint8_t addr) +{ + USBEndpoint *ep = EP_STRUCT(addr); + + ep->flags = USB_EP_FLAGS_TYPE_CONTROL; + + usb_arch_setup_endpoint(addr); +} + +void +usb_arch_setup_bulk_endpoint(uint8_t addr) +{ + USBEndpoint *ep = EP_STRUCT(addr); + + ep->flags = USB_EP_FLAGS_TYPE_BULK; + + usb_arch_setup_endpoint(addr); +} + +void +usb_arch_setup_interrupt_endpoint(uint8_t addr) +{ + USBEndpoint *ep = EP_STRUCT(addr); + + ep->flags = USB_EP_FLAGS_TYPE_INTERRUPT; + + usb_arch_setup_endpoint(addr); +} + +static void +usb_arch_disable_ep0(void) +{ + USBIIE &= ~USBIIE_EP0IE; + USBCS0 = 0xC0; // Clear any pending status flags +} + +static void +usb_arch_disable_in_endpoint(uint8_t addr) +{ + USBMAXI = 0; + USBIIE &= ~USBIIE_INEPxIE(EP_HW_NUM(addr)); + + // Flush any pending packets + USBCSIL = 0x08; // Double-buffering not used. Flush only once +} + +static void +usb_arch_disable_out_endpoint(uint8_t addr) +{ + USBMAXO = 0; + USBOIE &= ~USBOIE_OUEPxIE(EP_HW_NUM(addr)); + + // Flush any pending packets + USBCSOL = 0x08; // Double buffering not used, flush only once +} + + +void +usb_arch_disable_endpoint(uint8_t addr) +{ + uint8_t ei = EP_HW_NUM(addr); + uint8_t flag; + USBEndpoint *ep = EP_STRUCT(addr); + + ep->flags &= ~USB_EP_FLAGS_ENABLED; + + usb_irq_disable(flag); + USBINDEX = ei; + if(ei == 0) { + usb_arch_disable_ep0(); + } else { + if(addr & 0x80) { + usb_arch_disable_in_endpoint(addr); + } else { + usb_arch_disable_out_endpoint(addr); + } + } + usb_irq_enable(flag); +} + +void +usb_arch_discard_all_buffers(uint8_t addr) +{ + USBBuffer *buffer; + uint8_t flag; + volatile USBEndpoint *ep = EP_STRUCT(addr); + + usb_irq_disable(flag); + buffer = ep->buffer; + ep->buffer = NULL; + usb_irq_enable(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); + + USBINDEX = ei; + if(ei == 0) { + // Stall is automatically deasserted on EP0 + if(stall) { + ep0status = EP_IDLE; + USBCS0 |= USBCS0_SEND_STALL | USBCS0_OUTPKT_RDY; + } + } else { + if(addr & 0x80) { + if(stall) { + USBCSIL |= USBCSIL_SEND_STALL; + } else { + USBCSIL &= ~USBCSIL_SEND_STALL; + } + } else { + if(stall) { + USBCSOL |= USBCSOL_SEND_STALL; + } else { + USBCSOL &= ~USBCSOL_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; + } + + usb_irq_disable(flag); + + set_stall(addr, 1); + + usb_irq_enable(flag); +} + +void +usb_arch_halt_endpoint(uint8_t addr, int halt) +{ + uint8_t ei = EP_HW_NUM(addr); + uint8_t flag; + USBEndpoint *ep = EP_STRUCT(addr); + + + if(ei > USB_MAX_ENDPOINTS) { + return; + } + + if(!(ep->flags & USB_EP_FLAGS_ENABLED)) { + return; + } + + usb_irq_disable(flag); + + 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) { + usb_arch_epout_irq(EP_HW_NUM(addr)); + } + } + + usb_irq_enable(flag); +} + +void +usb_arch_set_configuration(uint8_t usb_configuration_value) +{ + // Nothing to do ? +} + +uint16_t +usb_arch_get_ep_status(uint8_t addr) +{ + uint8_t ei = EP_INDEX(addr); + USBEndpoint *ep = EP_STRUCT(addr); + + if(ei > USB_MAX_ENDPOINTS) { + return 0; + } + + return ep->halted; +} + +void +usb_arch_set_address(uint8_t addr) +{ + USBADDR = addr; +} + +int +usb_arch_send_pending(uint8_t addr) +{ + uint8_t flag; + uint8_t ret; + uint8_t ei = EP_INDEX(addr); + + usb_irq_disable(flag); + USBINDEX = ei; + if(ei == 0) { + ret = USBCS0 & USBCS0_INPKT_RDY; + } else { + ret = USBCSIL & USBCSIL_INPKT_RDY; + } + usb_irq_enable(flag); + + return ret; +} + +static unsigned int +get_receive_capacity(USBBuffer * 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 USBBuffer * +skip_buffers_until(USBBuffer * 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(USBBuffer * 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) +{ + // The USB controller check that the packet size is == 8 + // First get a valid setup buffer + uint8_t res; + USBBuffer *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]) { + // DATA stage ... + USBCS0 |= USBCS0_CLR_OUTPKT_RDY; + ep0status = buffer->data[0] & 0x80 ? EP_TX : EP_RX; + } else { + // The usb-core will submit an empty data packet + // This will trigger the clr (see the buffer submission routine) +// USBCS0 |= USBCS0_CLR_OUTPKT_RDY | USBCS0_DATA_END; + } + + 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; + USBBuffer *buffer = usb_endpoints[0].buffer; + uint8_t len = USBCNT0; + + if(!buffer) { + return USB_READ_BLOCK; + } + + if(buffer->flags & (USB_BUFFER_SETUP | USB_BUFFER_IN)) { + uint8_t temp; + + 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--) { + temp = USBF0; + } + usb_endpoints[0].buffer = buffer->next; + // Force an end to the data stage + USBCS0 |= USBCS0_CLR_OUTPKT_RDY | USBCS0_DATA_END; + + ep0status = EP_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_IDLE; + } else { + // More data to come + USBCS0 |= USBCS0_CLR_OUTPKT_RDY; + } + return res; +} + +static uint8_t +ep0_tx(void) +{ + USBBuffer *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((USBCS0 & USBCS0_INPKT_RDY) || (ep0status != EP_TX)) { + return 0; + } + + if(!buffer) { + return 0; + } + + if(!(buffer->flags & USB_BUFFER_IN)) { + return 0; // Huh .. problem ... we should TX but queued buffer is in RX ... + } + + 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) { + // Short packet will be sent. + 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_IDLE; + USBCS0 |= USBCS0_INPKT_RDY | USBCS0_DATA_END; + } else { + USBCS0 |= USBCS0_INPKT_RDY; + } + + return res; +} + +static void +usb_arch_ep0_irq(void) +{ + uint8_t cs0; + uint8_t res; + + USBINDEX = 0; + cs0 = USBCS0; + if(cs0 & USBCS0_SENT_STALL) { + // Ack the stall + USBCS0 = 0; + ep0status = EP_IDLE; + } + if(cs0 & USBCS0_SETUP_END) { + // Clear it + USBCS0 = USBCS0_CLR_SETUP_END; + ep0status = EP_IDLE; + } + + if(cs0 & USBCS0_OUTPKT_RDY) { + if(ep0status == EP_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; + } + } + // Trigger the TX path + 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; + USBEndpoint *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; + 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) { + break; // We keep the buffer in queue so we will send a ZLP next time. + } else { + len = 0; // Stop looking for more data to send + } + } + 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) + break; // FIFO is full, send packet. + } + + USBCSIL |= USBCSIL_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; + USBEndpoint *ep = EP_STRUCT(ep_hw); + + if(!ep->buffer) { + return USB_READ_BLOCK; + } + + if(ep->buffer->flags & USB_BUFFER_HALT) { + ep->halted = 1; + if(!(USBCSOL & USBCSOL_SEND_STALL)) { + USBCSOL |= USBCSOL_SEND_STALL; + } + return 0; + } + + pkt_len = USBCNTL | (USBCNTH << 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); + + USBCSOL &= ~USBCSOL_OUTPKT_RDY; + + return res; +} + +static void +usb_arch_epout_irq(uint8_t ep_hw) +{ + // RX interrupt + uint8_t csl; + uint8_t res; + USBEndpoint *ep = EP_STRUCT(ep_hw); + + + USBINDEX = ep_hw; + csl = USBCSOL; + + if(csl & USBCSOL_SENT_STALL) { + USBCSOL &= ~USBCSOL_SENT_STALL; + } + + if(csl & USBCSOL_OVERRUN) { + // We lost one isochronous packet ... + USBCSOL &= ~USBCSOL_OVERRUN; + } + + if(csl & USBCSOL_OUTPKT_RDY) { + res = ep_get_data_pkt(ep_hw); + + if(res & USB_READ_NOTIFY) { + notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION); + } + } +} + +static void +usb_arch_epin_irq(uint8_t ep_hw) +{ + uint8_t csl; + uint8_t res; + USBEndpoint *ep = EP_STRUCT(ep_hw); + + // TX interrupt + + USBINDEX = ep_hw; + csl = USBCSIL; + + if(csl & USBCSIL_SENT_STALL) { + USBCSIL &= ~USBCSIL_SENT_STALL; + } + + if(csl & USBCSIL_UNDERRUN) { + USBCSIL &= ~USBCSIL_UNDERRUN; + } + + if(!(csl & USBCSIL_INPKT_RDY)) { + res = ep_tx(ep_hw); + if(res & USB_WRITE_NOTIFY) { + notify_ep_process(ep, USB_EP_EVENT_NOTIFICATION); + } + } +} + +#define EPxIF(x) (1 << x) +#define RSTIF (1 << 2) +#define RESUMEIF (1 << 1) +#define SUSPENDIF (1 << 0) + +uint8_t +usb_irq(void) +{ + uint8_t ep_in_if = USBIIF & USBIIE; + uint8_t ep_out_if = USBOIF & USBOIE; + uint8_t common_if = USBCIF & USBCIE; + uint8_t i; + + if(ep_in_if) { + /* EP0 is routed on IN irq flags */ + if(ep_in_if & 0x1) { + usb_arch_ep0_irq(); + } + for(i = 1; i < 6; i++) { + if(ep_in_if & EPxIF(i)) { + usb_arch_epin_irq(i); + } + } + } + if(ep_out_if) { + for(i = 1; i < 6; i++) { + if(ep_out_if & EPxIF(i)) { + usb_arch_epout_irq(i); + } + } + } + if(common_if & RSTIF) { + usb_arch_reset(); + notify_process(USB_EVENT_RESET); + } + if(common_if & RESUMEIF) { + notify_process(USB_EVENT_RESUME); + } + if(common_if & SUSPENDIF) { + notify_process(USB_EVENT_SUSPEND); + } + + return ep_in_if || ep_out_if || common_if ? CC253x_P2_ACK : CC253x_P2_NACK; +}