rockbox/utils/hwstub/stub/rk27xx/usb_drv_rk27xx.c
Marcin Bukat b7e3515a62 hwstub: small fixup in rk27xx usb driver
Change-Id: Ibf3b91af11041834ce650f663b213bac0113f721
2014-09-11 12:31:52 +02:00

335 lines
9.3 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2011 by Marcin Bukat
* Copyright (C) 2012 by 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 "rk27xx.h"
typedef volatile uint32_t reg32;
#define USB_FULL_SPEED 0
#define USB_HIGH_SPEED 1
/* max allowed packet size definitions */
#define CTL_MAX_SIZE 64
struct endpoint_t {
const int type; /* EP type */
const int dir; /* DIR_IN/DIR_OUT */
const unsigned int intr_mask;
bool allocated; /* flag to mark EPs taken */
volatile void *buf; /* tx/rx buffer address */
volatile int len; /* size of the transfer (bytes) */
volatile int cnt; /* number of bytes transfered/received */
};
static struct endpoint_t ctrlep[2] = {
{USB_ENDPOINT_XFER_CONTROL, DIR_OUT, 0, true, NULL, 0, 0},
{USB_ENDPOINT_XFER_CONTROL, DIR_IN, 0, true, NULL, 0, 0}
};
volatile bool setup_data_valid = false;
static volatile uint32_t setup_data[2];
static volatile bool usb_drv_send_done = false;
static volatile bool usb_drv_rcv_done = false;
void usb_drv_configure_endpoint(int ep_num, int type)
{
/* not needed as we use EP0 only */
(void)ep_num;
(void)type;
}
int usb_drv_recv_setup(struct usb_ctrlrequest *req)
{
while (!setup_data_valid)
;
memcpy(req, (void *)setup_data, sizeof(struct usb_ctrlrequest));
setup_data_valid = false;
return 0;
}
static void setup_irq_handler(void)
{
/* copy setup data from packet */
setup_data[0] = SETUP1;
setup_data[1] = SETUP2;
/* ack upper layer we have setup data */
setup_data_valid = true;
}
/* service ep0 IN transaction */
static void ep0_in_dma_setup(void)
{
int xfer_size = MIN(ctrlep[DIR_IN].cnt, CTL_MAX_SIZE);
while (TX0BUF & TXFULL) /* TX0FULL flag */
;
TX0STAT = xfer_size; /* size of the transfer */
TX0DMALM_IADDR = (uint32_t)ctrlep[DIR_IN].buf; /* local buffer address */
TX0DMAINCTL = DMA_START; /* start DMA */
ctrlep[DIR_IN].cnt -= CTL_MAX_SIZE;
ctrlep[DIR_IN].buf += xfer_size;
TX0CON &= ~TXNAK; /* clear NAK */
}
static void ep0_out_dma_setup(void)
{
RX0DMAOUTLMADDR = (uint32_t)ctrlep[DIR_OUT].buf; /* buffer address */
RX0DMACTLO = DMA_START; /* start DMA */
/* clear NAK bit */
RX0CON &= ~RXNAK;
}
static void udc_phy_reset(void)
{
DEV_CTL |= SOFT_POR;
target_mdelay(10); /* min 10ms */
DEV_CTL &= ~SOFT_POR;
}
static void udc_soft_connect(void)
{
DEV_CTL |= CSR_DONE |
DEV_SOFT_CN |
DEV_SELF_PWR;
}
/* return port speed */
int usb_drv_port_speed(void)
{
return ((DEV_INFO & DEV_SPEED) ? USB_FULL_SPEED : USB_HIGH_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 */
}
int usb_drv_send(int endpoint, void *ptr, int length)
{
(void)endpoint;
struct endpoint_t *ep = &ctrlep[DIR_IN];
ep->buf = ptr;
ep->len = ep->cnt = length;
ep0_in_dma_setup();
/* wait for transfer to end */
while(!usb_drv_send_done)
;
usb_drv_send_done = false;
return 0;
}
/* Setup a receive transfer. (blocking) */
int usb_drv_recv(int endpoint, void* ptr, int length)
{
(void)endpoint;
struct endpoint_t *ep = &ctrlep[DIR_OUT];
ep->buf = ptr;
ep->len = ep->cnt = length;
if (length)
{
usb_drv_rcv_done = false;
ep0_out_dma_setup();
/* block here until the transfer is finished */
while (!usb_drv_rcv_done)
;
}
else
{
/* ZLP, clear NAK bit */
RX0CON &= ~RXNAK;
}
return (length - ep->cnt);
}
/* Stall the endpoint. Usually set a flag in the controller */
void usb_drv_stall(int endpoint, bool stall, bool in)
{
/* ctrl only anyway */
(void)endpoint;
if(in)
{
if(stall)
TX0CON |= TXSTALL;
else
TX0CON &= ~TXSTALL;
}
else
{
if (stall)
RX0CON |= RXSTALL;
else
RX0CON &= ~RXSTALL; /* doc says Auto clear by UDC 2.0 */
}
}
/* one time init (once per connection) - basicaly enable usb core */
void usb_drv_init(void)
{
udc_phy_reset();
target_mdelay(10); /* wait at least 10ms */
udc_soft_connect();
EN_INT = EN_SUSP_INTR | /* Enable Suspend Irq */
EN_RESUME_INTR | /* Enable Resume Irq */
EN_USBRST_INTR | /* Enable USB Reset Irq */
EN_OUT0_INTR | /* Enable OUT Token receive Irq EP0 */
EN_IN0_INTR | /* Enable IN Token transmit Irq EP0 */
EN_SETUP_INTR; /* Enable SETUP Packet Receive Irq */
INTCON = UDC_INTHIGH_ACT | /* interrupt high active */
UDC_INTEN; /* enable EP0 irqs */
}
/* turn off usb core */
void usb_drv_exit(void)
{
/* udc module reset */
SCU_RSTCFG |= (1<<1);
target_udelay(10);
SCU_RSTCFG &= ~(1<<1);
}
/* UDC ISR function */
void INT_UDC(void)
{
uint32_t txstat, rxstat;
/* read what caused UDC irq */
uint32_t intsrc = INT2FLAG & 0x7fffff;
if (intsrc & USBRST_INTR) /* usb reset */
{
EN_INT = EN_SUSP_INTR | /* Enable Suspend Irq */
EN_RESUME_INTR | /* Enable Resume Irq */
EN_USBRST_INTR | /* Enable USB Reset Irq */
EN_OUT0_INTR | /* Enable OUT Token receive Irq EP0 */
EN_IN0_INTR | /* Enable IN Token transmit Irq EP0 */
EN_SETUP_INTR; /* Enable SETUP Packet Receive Irq */
TX0CON = TXACKINTEN | /* Set as one to enable the EP0 tx irq */
TXNAK; /* Set as one to response NAK handshake */
RX0CON = RXACKINTEN |
RXEPEN | /* Endpoint 0 Enable. When cleared the
* endpoint does not respond to an SETUP
* or OUT token */
RXNAK; /* Set as one to response NAK handshake */
usb_drv_rcv_done = true;
}
if (intsrc & SETUP_INTR) /* setup interrupt */
{
setup_irq_handler();
}
if (intsrc & IN0_INTR) /* ep0 in interrupt */
{
txstat = TX0STAT; /* read clears flags */
/* TODO handle errors */
if (txstat & TXACK) /* check TxACK flag */
{
/* Decrement by max packet size is intentional.
* This way if we have final packet short one we will get negative len
* after transfer, which in turn indicates we *don't* need to send
* zero length packet. If the final packet is max sized packet we will
* get zero len after transfer which indicates we need to send
* zero length packet to signal host end of the transfer.
*/
if (ctrlep[DIR_IN].cnt > 0)
{
/* we still have data to send */
ep0_in_dma_setup();
}
else
{
if (ctrlep[DIR_IN].cnt == 0)
{
ep0_in_dma_setup();
}
/* final ack received */
usb_drv_send_done = true;
}
}
}
if (intsrc & OUT0_INTR) /* ep0 out interrupt */
{
rxstat = RX0STAT;
/* TODO handle errors */
if (rxstat & RXACK) /* RxACK */
{
int xfer_size = rxstat & 0x7ff;
ctrlep[DIR_OUT].cnt -= xfer_size;
if (ctrlep[DIR_OUT].cnt > 0 && xfer_size == 64)
{
/* advance the buffer */
ctrlep[DIR_OUT].buf += xfer_size;
ep0_out_dma_setup();
}
else
usb_drv_rcv_done = true;
}
}
if (intsrc & RESUME_INTR) /* usb resume */
{
TX0CON |= TXCLR; /* TxClr */
TX0CON &= ~TXCLR;
RX0CON |= RXCLR; /* RxClr */
RX0CON &= ~RXCLR;
}
}