forked from len0rd/rockbox
		
	
		
			
				
	
	
		
			335 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
|     }
 | |
| }
 |