rockbox/utils/hwstub/stub/atj213x/usb_drv_atj213x.c
Marcin Bukat 0e2b4908d0 hwstub: rework usb driver for atj213x
Change-Id: I7b175103e567ae4375ff94e74ed1a06215f640c3
2016-03-14 12:21:42 +01:00

305 lines
6.7 KiB
C

/***************************************************************************
* __________ __ ___.
* 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;
}