diff options
Diffstat (limited to 'src/libs/usb/cusb.c')
| -rw-r--r-- | src/libs/usb/cusb.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/src/libs/usb/cusb.c b/src/libs/usb/cusb.c new file mode 100644 index 0000000..249945a --- /dev/null +++ b/src/libs/usb/cusb.c @@ -0,0 +1,309 @@ +#include "cusb.h" +#include "../addrmap.h" +#include "../bitmap.h" +#include "../interrupts.h" +#include "../gpio.h" +#include "proto.h" + +u8 xferbuff[64]; // Free to use for transfer (IN) +u8 *xferptr; +u8 xferremain=0; +DeviceConfiguration *cusb_dc; + +void cusb_init(DeviceConfiguration *dc){ + + // Reset USB controller + REG_WRITE_BITMAP_CLEAR(RESETS_RESET, 1<<24); // Reset USBCTRL + while((REG_READ(RESETS_DONE) & 1<<24) == 0){} // Wait controller reset + + // Flush USB Controller DPSRAM + memset((void*)USBCTRL_DPSRAM_BASE, 0, USBCTRL_DPSRAM_SIZE); + + // Enable usb controller interrupt + interrupts_enable(5); + + // Setup controller + REG_WRITE(USBCTRL_MUXING, BIT_USBCTRL_MUXING_TO_PHY|BIT_USBCTRL_MUXING_SOFTCON); + REG_WRITE(USBCTRL_PWR, BIT_USBCTRL_PWR_VBUS_DETECT|BIT_USBCTRL_PWR_VBUS_DETECT_OVERRIDE_EN); + REG_WRITE(USBCTRL_MAINCTRL, BIT_USBCTRL_MAIN_CTRL_CONTROLLER_EN); // Set device mode + enable controller + REG_WRITE(USBCTRL_SIE_CTRL, BIT_USBCTRL_SIE_CTRL_EP0_INT_1BUF); // Enable usb interrupts on EP0 + REG_WRITE(USBCTRL_INTE, BIT_USBCTRL_INTE_SETUP_REQ|BIT_USBCTRL_INTE_BUS_RESET|BIT_USBCTRL_INTE_BUFF_STATUS); // Setup interrupt granularity + + // Initialize device configuration + cusb_dc=dc; // Store controller device config + cusb_init_device_configuration(); + + // Initialize endpoints + for(int i=0;i<USB_ENDPOINT_COUNT;i++){ + EndPointConfiguration *ep=&(cusb_dc->endpoints[i]); + u32 dir=ep->endpoint_descriptor.bEndpointAddress & 0x80; + u32 id=ep->endpoint_descriptor.bEndpointAddress & 0xF; + // Endpoint and buffer control + u32 endpoint_control=USBCTRL_EP1_ENDP_CTRL_IN+0x8*(id-1); // -1 since start at 1 (ep0 already covered in cusb_init_device_configuration()) + u32 buffer_control=USBCTRL_EP1_BUFFER_CTRL_IN+0x8*(id-1); + if(dir == USB_DIRECTION_OUT){ + endpoint_control+=4; + buffer_control+=4; + } + ep->endpoint_control=(void*)(endpoint_control); + ep->buffer_control=(void*)(buffer_control); + // Init buffers + u32 buffer0=USBCTRL_DATA_BUFFER_START+(64*2)*(id-1); // 64 bits aligned warning see p388 + u32 buffer1=buffer0+64; + ep->buffer0=(void*)buffer0; + ep->buffer1=(void*)buffer1; + // Init ep control + u32 epctrl=BIT_USBCTRL_ENDP_CTRL_ENABLE | + BIT_USBCTRL_ENDP_CTRL_INT_PER_BUFF1 | + ep->endpoint_descriptor.bmAttributes << BIT_USBCTRL_ENDP_CTRL_TYPE_LSB| + USB_BUFFER_OFFSET(buffer0); + *ep->endpoint_control=epctrl; // Apply buffer control setting + ep->next_pid=0; // Maybe that is good? + } + + // Pull-up usb line! (for the host :) + REG_WRITE_BITMAP_SET(USBCTRL_SIE_CTRL, BIT_USBCTRL_SIE_CTRL_PULLUP_EN); +} + +void cusb_init_device_configuration(){ + + // Init device descriptor + cusb_dc->device_descriptor.bLength=sizeof(USB_DEVICE_DESCRIPTOR); + cusb_dc->device_descriptor.bDescriptorType=USB_DESCRIPTOR_TYPE_DEVICE; + cusb_dc->device_descriptor.bNumConfigurations = 1; // We support only 1 + cusb_dc->device_descriptor.bcdUSB=BCD_USB; + cusb_dc->device_descriptor.bMaxPacketSize0=64; + + // Init configuration descriptor + cusb_dc->configuration_descriptor.bLength=sizeof(USB_CONFIGURATION_DESCRIPTOR); + cusb_dc->configuration_descriptor.bDescriptorType=USB_DESCRIPTOR_TYPE_CONFIGURATION; + cusb_dc->configuration_descriptor.bConfigurationValue = 1; // Configuration id + cusb_dc->configuration_descriptor.iConfiguration = 0; // No string + cusb_dc->configuration_descriptor.bmAttributes = 0xC0; // attributes: self powered, no remote wakeup + cusb_dc->configuration_descriptor.bMaxPower = 0x32; // 100ma + + // Init device state + cusb_dc->devaddr=0; // Just in case + cusb_dc->setdevaddr=0; // Just in case + cusb_dc->setdevaddr=0; // Just in case + + // Init string zero descriptor + cusb_dc->supported_languages.bLength = sizeof(USB_STRING_DESCRIPTOR_ZERO); + cusb_dc->supported_languages.bDescriptorType = USB_DESCRIPTOR_TYPE_STRING; + + // Init ep0 in + EndPointConfiguration *ep0_in= &(cusb_dc->endpoints[USB_ENDPOINT_COUNT]); + ep0_in->buffer0=(void*)USBCTRL_EP0_BUFFER0; + ep0_in->buffer1=(void*)USBCTRL_EP0_BUFFER0+0x40; + ep0_in->buffer_control=(void*)USBCTRL_EP0_BUFFER_CTRL_IN; + ep0_in->handler=cusb_ep0_in_handler; + ep0_in->endpoint_descriptor.bEndpointAddress = B_ENDPOINT_ADDRESS(0, USB_DIRECTION_IN); + ep0_in->endpoint_descriptor.bmAttributes = USB_TRANSFERT_TYPE_CONTROL; + ep0_in->endpoint_descriptor.wMaxPacketSize=64; + + // Init ep0 out + EndPointConfiguration *ep0_out= &(cusb_dc->endpoints[USB_ENDPOINT_COUNT+1]); + ep0_out->buffer0=(void*)USBCTRL_EP0_BUFFER0; + ep0_out->buffer1=(void*)USBCTRL_EP0_BUFFER0+0x40; + ep0_out->buffer_control=(void*)USBCTRL_EP0_BUFFER_CTRL_OUT; + ep0_out->handler=cusb_ep0_out_handler; + ep0_out->endpoint_descriptor.bEndpointAddress = B_ENDPOINT_ADDRESS(0, USB_DIRECTION_OUT); + ep0_out->endpoint_descriptor.bmAttributes = USB_TRANSFERT_TYPE_CONTROL; + ep0_out->endpoint_descriptor.wMaxPacketSize=64; + + // Init bLength + for(char i=0;i<USB_ENDPOINT_COUNT+2;i++){ + cusb_dc->endpoints[i].endpoint_descriptor.bLength=sizeof(USB_ENDPOINT_DESCRIPTOR); + cusb_dc->endpoints[i].endpoint_descriptor.bDescriptorType=USB_DESCRIPTOR_TYPE_ENDPOINT; + } + + // Init interfaces + for(char i=0;i<USB_INTERFACE_COUNT;i++){ + cusb_dc->interface_descriptors[i].bLength=sizeof(USB_INTERFACE_DESCRIPTOR); + cusb_dc->interface_descriptors[i].bDescriptorType=USB_DESCRIPTOR_TYPE_INTERFACE; + } +} + +int cusb_check_interrupt(int int_bit){ + if(REG_READ(USBCTRL_INTS) & int_bit) + return 1; + return 0; +} + +void cusb_handle_bus_reset(void){ + // https://github.com/raspberrypi/pico-examples/blob/master/usb/device/dev_lowlevel/dev_lowlevel.c#L469 + REG_WRITE(USBCTRL_ADDR_ENDP, 0); // Remove device address from controller + cusb_dc->devaddr=0; // No more device address + cusb_dc->setdevaddr=0; // Ensure no device address will be set + cusb_dc->configured=0; +} + +EndPointConfiguration* cusb_get_endpoint(char num, u32 direction){ + EndPointConfiguration *ep; + for(char i=0;i<(USB_ENDPOINT_COUNT+2);i++){ + ep=&(cusb_dc->endpoints[i]); + u32 bEndpointAddress=ep->endpoint_descriptor.bEndpointAddress; + // Bit 7 (mask 0x80) is IN or OUT and first 4 bits is addr (see p269) + if((bEndpointAddress & 0x80) == direction && (bEndpointAddress & 0xF) == num) + return ep; + } + + return 0; +} + +void cusb_handle_setup(void){ + UD_SETUP *pkt=(UD_SETUP*)USBCTRL_DPSRAM_SETUP_PACKET; + + // Always responds with DATA1 PID + EndPointConfiguration *ep=cusb_get_endpoint(0, USB_DIRECTION_IN); + ep->next_pid=1; + + u8 direction=pkt->bmRequestType & BM_REQUEST_TYPE_DIR_BIT; + if(direction == USB_DIRECTION_OUT){ + if (pkt->bRequest == USB_REQUEST_CODE_SET_ADDRESS){ + cusb_dc->devaddr= pkt->wValue & 0xff; + cusb_dc->setdevaddr=1; + // Since we should acknowledge (status phase) before setting the address + // we use setdevaddr boolean. When status done, buffer_status interrupt will be triggered + // and ep0_in_handler will set the address + cusb_status_xfer(USB_DIRECTION_IN); + } else if (pkt->bRequest == USB_REQUEST_CODE_SET_CONFIGURATION) { + cusb_status_xfer(USB_DIRECTION_IN); + cusb_dc->configured=1; + } else { + if(cusb_dc->setup_handler){ + cusb_dc->setup_handler(pkt); + } + // Acknowledge whatever other requests + cusb_status_xfer(USB_DIRECTION_IN); + } + } else if(direction == USB_DIRECTION_IN){ + if(pkt->bRequest == USB_REQUEST_CODE_GET_DESCRIPTOR){ + int desc_type=pkt->wValue>>8; // See USB SPecification (wValue contains descriptor type+index) + if(desc_type == USB_DESCRIPTOR_TYPE_DEVICE ){ + // Send descriptor + cusb_start_xfer(&cusb_dc->device_descriptor, sizeof(USB_DEVICE_DESCRIPTOR), ep); + } else if(desc_type == USB_DESCRIPTOR_TYPE_CONFIGURATION ){ + + USB_CONFIGURATION_DESCRIPTOR *conf=cusb_dc->full_configuration_descriptor; + int size=conf->bLength; + + if(pkt->wLength > size) + size=conf->wTotalLength; + + // Send descriptors!! + cusb_start_xfer(conf, size, ep); + } else if(desc_type == USB_DESCRIPTOR_TYPE_STRING ){ + u8 id = pkt->wValue & 0xff; // Get string id + if(id==0){ // This is the special string descriptor for supported language + cusb_start_xfer(&(cusb_dc->supported_languages), sizeof(USB_STRING_DESCRIPTOR_ZERO), ep); + } + else { + char *str=cusb_dc->descriptor_strings[id-1]; // Remember id 0 taken by ZERO DESCriptor + u8 *ptr=xferbuff; + USB_UNICODE_STRING_DESCRIPTOR *u=(USB_UNICODE_STRING_DESCRIPTOR*)ptr; + u->bLength = sizeof(USB_UNICODE_STRING_DESCRIPTOR); + u->bDescriptorType=USB_DESCRIPTOR_TYPE_STRING; + char c; + ptr+=sizeof(USB_UNICODE_STRING_DESCRIPTOR); // String first 2 descriptor entries + do { + c = *str++; + *ptr++ = c; + *ptr++ = 0; // Unicode + u->bLength+=2; // Unicode + } while(c != '\0'); + cusb_start_xfer(xferbuff, u->bLength, ep); + } + } + else { + cusb_dc->setup_handler(pkt); + } + } + } +} + +void cusb_handle_buffer_status(void){ + u32 status=REG_READ(USBCTRL_BUFF_STATUS); + for(u8 num=0;num<16;num++){ + for(u8 i=0;i<2;i++){ + u32 bit=1u << (num*2+i); // Shift register for IN and OUT for endpoint num + if(status & bit){ + u32 dir=i ? USB_DIRECTION_OUT : USB_DIRECTION_IN; + EndPointConfiguration *ep=cusb_get_endpoint(num,dir); + REG_WRITE_BITMAP_CLEAR(USBCTRL_BUFF_STATUS, bit); // Clear buffer status + ep->handler((u8*)ep->buffer0,*(ep->buffer_control)&MASK_USBCTRL_BUFFER0_LENGTH); + } + } + } +} + +void cusb_status_xfer(u32 dir){ + // If dir == USB_DIRECTION_OUT + // controller will receive the out token from host then send acknowledgement + // otherwise if we do not perform xfer when receiving out token, then controller do not send acknowledgement which do not complete the control transaction + // If dir == USB_DIRECTION_IN + // controller will receive the in token from host then send zlp and receive acknowledgement from host + EndPointConfiguration *ep=cusb_get_endpoint(0, dir); + cusb_start_xfer(0,0,ep); +} + +void cusb_ep0_in_handler(u8 *buffer, int len) { + // This function is trigger when buff_status interrupt is handled + // it is called inside cusb_handle_buffer_status + if(cusb_dc->setdevaddr){ + REG_WRITE(USBCTRL_ADDR_ENDP, cusb_dc->devaddr); + cusb_dc->setdevaddr=0; + } + else if(xferremain>0){ + EndPointConfiguration *ep=cusb_get_endpoint(0, USB_DIRECTION_IN); + cusb_start_xfer(xferptr,xferremain,ep); + } + else { + cusb_status_xfer(USB_DIRECTION_OUT); // Acknowledge with zlp when setup transaction ends + } +} + +void cusb_ep0_out_handler(u8 *buffer, int len) { + // This function is trigger when buff_status interrupt is handled + // it is called inside cusb_handle_buffer_status +} + + + +void cusb_start_xfer(void* data, u32 size, EndPointConfiguration *ep){ + u32 buffer_ctrl = size; // Set data size + buffer_ctrl|=ep->next_pid ? BIT_USBCTRL_BUFFER0_PID : 0; // Set DATA0 or DATA1 + ep->next_pid ^= 1u; // For next transfert + + // Move user data to usb buffer if needed + u32 direction=ep->endpoint_descriptor.bEndpointAddress & 0x80; + if(direction == USB_DIRECTION_IN){ + memcpy((void *)ep->buffer0, data, size); + buffer_ctrl |= BIT_USBCTRL_BUFFER0_FULL; // Set buffer full for controller + // Support for data > 64 bytes (see also cusb_ep0_in_handler()) + if(size>64){ + xferremain=size-64; + xferptr=((u8*) data)+64; + } + else { + xferremain=0; + } + } + + // Setup buffer (not available bit yet) + *(ep->buffer_control)=buffer_ctrl; + + // Now do available bit (because USB and cores have different clock speed (race condition see p389) + asm volatile("nop;nop;nop;"); // ceil(125MHz/48MHz) Should wait 3 cycles see warning p392 + *(ep->buffer_control)=buffer_ctrl| BIT_USBCTRL_BUFFER0_AVAILABLE; // Apply available bit + +} + +void cusb_eoi(){ + if(cusb_dc->eoi) + cusb_dc->eoi(); +} + +void cusb_isr_trigger(){ + REG_WRITE(PPB_NVIC_ISPR, 1u<<5); // Trigger interrupt 5 (USB Controller) +} |
