/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * * Copyright (C) 2014 by Marcin Bukat Amaury Pouly * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "usb_drv.h" #include "config.h" #include "memory.h" #include "target.h" #include "atj213x.h" #define USB_FULL_SPEED 0 #define USB_HIGH_SPEED 1 volatile bool setup_data_valid = false; volatile int udc_speed = USB_FULL_SPEED; struct endpoint_t { void *buf; int length; bool zlp; bool finished; }; static volatile struct endpoint_t ep0in = {NULL,0,false,false}; static volatile struct endpoint_t ep0out = {NULL,0,false,false}; static void usb_copy_from(void *ptr, volatile void *reg, size_t sz) { uint32_t *p = ptr; volatile uint32_t *rp = reg; /* do not overflow the destination buffer ! */ while(sz >= 4) { *p++ = *rp++; sz -= 4; } if(sz == 0) return; /* reminder */ uint32_t cache = *rp; uint8_t *p8 = (void *)p; while(sz-- > 0) { *p8++ = cache; cache >>= 8; } } static void usb_copy_to(volatile void *reg, void *ptr, size_t sz) { uint32_t *p = ptr; volatile uint32_t *rp = reg; sz = (sz + 3) / 4; /* read may overflow the source buffer but * it will not overwrite anything */ while(sz-- > 0) *rp++ = *p++; } void usb_drv_init(void) { OTG_USBCS |= 0x40; /* soft disconnect */ OTG_ENDPRST = 0x10; /* reset all ep fifos */ OTG_ENDPRST = 0x70; OTG_ENDPRST = 0x00; OTG_ENDPRST = 0x60; OTG_USBIRQ = 0xff; /* clear all pending interrupts */ OTG_OTGIRQ = 0xff; OTG_IN04IRQ = 0xff; OTG_OUT04IRQ = 0xff; OTG_USBEIRQ = 0x50; /* UDC ? with 0x40 there is irq storm */ OTG_USBIEN = (1<<5) | (1<<4) | (1<<0); /* HS, Reset, Setup_data */ OTG_OTGIEN = 0; /* enable interrupts from ep0 */ OTG_IN04IEN = 1; OTG_OUT04IEN = 1; /* unmask UDC interrupt in interrupt controller */ INTC_MSK = (1<<4); target_mdelay(100); OTG_USBCS &= ~0x40; /* soft connect */ } int usb_drv_recv_setup(struct usb_ctrlrequest *req) { while (!setup_data_valid) ; usb_copy_from(req, &OTG_SETUPDAT, sizeof(struct usb_ctrlrequest)); setup_data_valid = false; return 0; } int usb_drv_port_speed(void) { return (int)udc_speed; } /* Set the address (usually it's in a register). * There is a problem here: some controller want the address to be set between * control out and ack and some want to wait for the end of the transaction. * In the first case, you need to write some code special code when getting * setup packets and ignore this function (have a look at other drives) */ void usb_drv_set_address(int address) { (void)address; /* UDC sets this automaticaly */ } static void ep0_write(void) { int xfer_size = MIN(ep0in.length, 64); /* copy data to UDC buffer */ usb_copy_to(&OTG_EP0INDAT, ep0in.buf, xfer_size); ep0in.buf += xfer_size; ep0in.length -= xfer_size; /* this marks data as ready to send */ OTG_IN0BC = xfer_size; } /* TODO: Maybe adapt to irq scheme */ int usb_drv_send(int endpoint, void *ptr, int length) { (void)endpoint; if (length) { ep0in.length = length; ep0in.buf = ptr; ep0in.zlp = (length % 64 == 0) ? true : false; ep0in.finished = false; ep0_write(); while(!ep0in.finished) ; } else { OTG_EP0CS = 2; } return 0; } static int ep0_read(void) { int xfer_size = OTG_OUT0BC; usb_copy_from(ep0out.buf, &OTG_EP0OUTDAT, xfer_size); ep0out.buf += xfer_size; ep0out.length -= xfer_size; return xfer_size; } /* TODO: Maybe adapt to irq scheme */ int usb_drv_recv(int endpoint, void* ptr, int length) { (void)endpoint; ep0out.length = length; ep0out.buf = ptr; ep0out.zlp = (length == 0) ? true : false; ep0out.finished = false; /* Arm receiving buffer by writing * any value to OUT0BC. This sets * OUT_BUSY bit in EP0CS until the data * are correctly received and ACK'd */ OTG_OUT0BC = 0; while (!ep0out.finished) ; return (length - ep0out.length); } void usb_drv_stall(int endpoint, bool stall, bool in) { (void)endpoint; (void)in; /* only EP0 in hwstub */ if (stall) OTG_EP0CS |= 1; else OTG_EP0CS &= ~1; } void usb_drv_exit(void) { } void INT_UDC(void) { /* get possible sources */ unsigned int usbirq = OTG_USBIRQ; unsigned int otgirq = OTG_OTGIRQ; unsigned int epinirq = OTG_IN04IRQ; unsigned int epoutirq = OTG_OUT04IRQ; /* HS, Reset, Setup */ if (usbirq) { if (usbirq & (1<<5)) { /* HS irq */ udc_speed = USB_HIGH_SPEED; } else if (usbirq & (1<<4)) { /* Reset */ udc_speed = USB_FULL_SPEED; /* clear all pending irqs */ OTG_OUT04IRQ = 0xff; OTG_IN04IRQ = 0xff; } else if (usbirq & (1<<0)) { /* Setup data valid */ setup_data_valid = true; } /* clear irq flags */ OTG_USBIRQ = usbirq; } if (epoutirq) { if (ep0_read() == 64) { /* rearm receive buffer */ OTG_OUT0BC = 0; } else { if (ep0out.length == 0 && ep0out.zlp) { OTG_EP0CS = 2; } ep0out.finished = true; } OTG_OUT04IRQ = epoutirq; } if (epinirq) { if (ep0in.length) { ep0_write(); } else { if (ep0in.zlp) { OTG_EP0CS = 2; } ep0in.finished = true; } /* ack interrupt */ OTG_IN04IRQ = epinirq; } if (otgirq) { OTG_OTGIRQ = otgirq; } OTG_USBEIRQ = 0x50; }