/* * 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 #include /*---------------------------------------------------------------------------*/ #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 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 = nvic_interrupt_en_save(NVIC_INT_USB); e = events; events = 0; nvic_interrupt_en_restore(NVIC_INT_USB, 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 = nvic_interrupt_en_save(NVIC_INT_USB); e = ep->events; ep->events = 0; nvic_interrupt_en_restore(NVIC_INT_USB, 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_interrupt_enable(NVIC_INT_USB); } /*---------------------------------------------------------------------------*/ 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 = nvic_interrupt_en_save(NVIC_INT_USB); 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)); } } nvic_interrupt_en_restore(NVIC_INT_USB, 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 = nvic_interrupt_en_save(NVIC_INT_USB); 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); nvic_interrupt_en_restore(NVIC_INT_USB, 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(); } nvic_interrupt_en_restore(NVIC_INT_USB, 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 = nvic_interrupt_en_save(NVIC_INT_USB); /* 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); } } nvic_interrupt_en_restore(NVIC_INT_USB, 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 = nvic_interrupt_en_save(NVIC_INT_USB); REG(USB_INDEX) = ei; if(ei == 0) { ep0_dis(); } else { if(addr & 0x80) { in_ep_dis(addr); } else { out_ep_dis(addr); } } nvic_interrupt_en_restore(NVIC_INT_USB, 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 = nvic_interrupt_en_save(NVIC_INT_USB); buffer = ep->buffer; ep->buffer = NULL; nvic_interrupt_en_restore(NVIC_INT_USB, 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 = nvic_interrupt_en_save(NVIC_INT_USB); set_stall(addr, 1); nvic_interrupt_en_restore(NVIC_INT_USB, 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 = nvic_interrupt_en_save(NVIC_INT_USB); 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)); } } nvic_interrupt_en_restore(NVIC_INT_USB, 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 = nvic_interrupt_en_save(NVIC_INT_USB); REG(USB_INDEX) = ei; if(ei == 0) { ret = REG(USB_CS0) & USB_CS0_INPKT_RDY; } else { ret = REG(USB_CSIL) & USB_CSIL_INPKT_RDY; } nvic_interrupt_en_restore(NVIC_INT_USB, 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; 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)) { 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 = 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); } /*---------------------------------------------------------------------------*/ /** @} */