241 lines
7.6 KiB
C
241 lines
7.6 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 "jz4760b.h"
|
||
|
|
||
|
static void udc_reset(void)
|
||
|
{
|
||
|
REG_USB_FADDR = 0;
|
||
|
/* Reset EP0 */
|
||
|
REG_USB_INDEX = 0;
|
||
|
REG_USB_CSR0 = USB_CSR0_FLUSHFIFO | USB_CSR0_SVDOUTPKTRDY | USB_CSR0_SVDSETUPEND; /* clear setupend and rxpktrdy */
|
||
|
REG_USB_POWER = USB_POWER_SOFTCONN | USB_POWER_HSENAB | USB_POWER_SUSPENDM;
|
||
|
}
|
||
|
|
||
|
void usb_drv_init(void)
|
||
|
{
|
||
|
/* in case usb is running, soft disconnect */
|
||
|
REG_USB_POWER &= ~USB_POWER_SOFTCONN;
|
||
|
/* A delay seems necessary to avoid causing havoc. The USB spec says disconnect
|
||
|
* detection time (T_DDIS) is around 2us but in practice many hubs might
|
||
|
* require more. */
|
||
|
target_mdelay(1);
|
||
|
/* disable usb */
|
||
|
REG_CPM_CLKGR0 |= CLKGR0_OTG;
|
||
|
/* power up usb: assume EXCLK=12Mhz */
|
||
|
REG_CPM_CPCCR &= ~CPCCR_ECS; /* use EXCLK as source (and not EXCLK/2) */
|
||
|
REG_CPM_USBCDR = 0; /* use EXCLK as source, no divisor */
|
||
|
REG_CPM_CPCCR |= CPCCR_CE; /* change source now */
|
||
|
/* wait for stable clock */
|
||
|
target_udelay(3);
|
||
|
/* enable usb */
|
||
|
REG_CPM_CLKGR0 &= ~CLKGR0_OTG;
|
||
|
/* tweak various parameters */
|
||
|
REG_CPM_USBVBFIL = 0x80;
|
||
|
REG_CPM_USBRDT = 0x96;
|
||
|
REG_CPM_USBRDT |= (1 << 25);
|
||
|
REG_CPM_USBPCR &= ~0x3f;
|
||
|
REG_CPM_USBPCR |= 0x35;
|
||
|
REG_CPM_USBPCR &= ~USBPCR_USB_MODE;
|
||
|
REG_CPM_USBPCR |= USBPCR_VBUSVLDEXT;
|
||
|
/* reset otg phy */
|
||
|
REG_CPM_USBPCR |= USBPCR_POR;
|
||
|
target_udelay(30);
|
||
|
REG_CPM_USBPCR &= ~USBPCR_POR;
|
||
|
target_udelay(300);
|
||
|
/* enable otg phy */
|
||
|
REG_CPM_OPCR |= OPCR_OTGPHY_ENABLE;
|
||
|
/* wait for stable phy */
|
||
|
target_udelay(300);
|
||
|
/* reset */
|
||
|
udc_reset();
|
||
|
}
|
||
|
|
||
|
static void *read_fifo0(void *dst, unsigned size)
|
||
|
{
|
||
|
unsigned char *p = dst;
|
||
|
while(size-- > 0)
|
||
|
*p++ = *(volatile uint8_t *)USB_FIFO_EP(0);
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
static void *write_fifo0(void *src, unsigned size)
|
||
|
{
|
||
|
unsigned char *p = src;
|
||
|
while(size-- > 0)
|
||
|
*(volatile uint8_t *)USB_FIFO_EP(0) = *p++;
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
/* NOTE: this core is a bit weird, it handles the status stage automatically
|
||
|
* as soon as DataEnd is written to CSR. The problem is that DataEnd needs
|
||
|
* to be written as part of a read (INPKTRDY) or write (SVDOUTPKTRDY) request
|
||
|
* but not on its own.
|
||
|
* Thus the design is follows: after receiving the setup packet, we DO NOT
|
||
|
* acknowledge it with SVDOUTPKTRDY. Instead it will be acknowledged
|
||
|
* either as part of STALL or recv/send. If there is an OUT data stage, we use
|
||
|
* a similar trick: we do not acknowledge the last packet and leave a pending
|
||
|
* SVDOUTPKTRDY to be done as part of a final STALL or ZLP. */
|
||
|
|
||
|
int usb_drv_recv_setup(struct usb_ctrlrequest *req)
|
||
|
{
|
||
|
while(1)
|
||
|
{
|
||
|
unsigned intr = REG_USB_INTRUSB;
|
||
|
unsigned intrin = REG_USB_INTRIN;
|
||
|
/* handle reset */
|
||
|
if(intr & USB_INTR_RESET)
|
||
|
{
|
||
|
udc_reset();
|
||
|
continue;
|
||
|
}
|
||
|
/* ignore anything but EP0 irq */
|
||
|
if(!(intrin & 1))
|
||
|
continue;
|
||
|
/* select EP0 */
|
||
|
REG_USB_INDEX = 0;
|
||
|
/* load csr to examine the cause of the interrupt */
|
||
|
unsigned csr0 = REG_USB_CSR0;
|
||
|
/* wait setup: we expect to receive a packet */
|
||
|
if(csr0 & USB_CSR0_OUTPKTRDY)
|
||
|
{
|
||
|
unsigned cnt = REG_USB_COUNT0;
|
||
|
/* anything other than 8-byte is wrong */
|
||
|
if(cnt == 8)
|
||
|
{
|
||
|
read_fifo0(req, 8);
|
||
|
/* DO NOT acknowledge the packet, leave this to recv/send/stall */
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int usb_drv_port_speed(void)
|
||
|
{
|
||
|
return (REG_USB_POWER & USB_POWER_HSMODE) ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
void usb_drv_set_address(int address)
|
||
|
{
|
||
|
REG_USB_FADDR = address;
|
||
|
}
|
||
|
|
||
|
int usb_drv_send(int endpoint, void *ptr, int length)
|
||
|
{
|
||
|
(void) endpoint;
|
||
|
/* select EP0 */
|
||
|
REG_USB_INDEX = 0;
|
||
|
/* clear packet ready for the PREVIOUS packet: warning, there is a trap here!
|
||
|
* if we are clearing without sending anything (length=0) then we must
|
||
|
* set DataEnd at the same time. Otherwise, we must set it by itself and then
|
||
|
* send data */
|
||
|
if(length > 0)
|
||
|
{
|
||
|
/* clear packet ready for the PREVIOUS packet (SETUP) */
|
||
|
REG_USB_CSR0 |= USB_CSR0_SVDOUTPKTRDY;
|
||
|
/* send data */
|
||
|
do
|
||
|
{
|
||
|
unsigned csr = REG_USB_CSR0;
|
||
|
/* write data */
|
||
|
int cnt = MIN(length, 64);
|
||
|
ptr = write_fifo0(ptr, cnt);
|
||
|
length -= cnt;
|
||
|
csr |= USB_CSR0_INPKTRDY;
|
||
|
/* last packet ? */
|
||
|
if(length == 0)
|
||
|
csr |= USB_CSR0_DATAEND;
|
||
|
/* write csr */
|
||
|
REG_USB_CSR0 = csr;
|
||
|
/* wait for packet to be transmitted */
|
||
|
while(REG_USB_CSR0 & USB_CSR0_INPKTRDY) {}
|
||
|
}while(length > 0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* clear packet ready for the PREVIOUS packet (SETUP or DATA) and finish */
|
||
|
REG_USB_CSR0 |= USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND;
|
||
|
}
|
||
|
/* wait until acknowledgement */
|
||
|
while(REG_USB_CSR0 & USB_CSR0_DATAEND) {}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int usb_drv_recv(int endpoint, void* ptr, int length)
|
||
|
{
|
||
|
(void) endpoint;
|
||
|
int old_len = length;
|
||
|
/* select EP0 */
|
||
|
REG_USB_INDEX = 0;
|
||
|
/* ZLP: ignore receive, the core does it automatically on DataEnd */
|
||
|
if(length == 0)
|
||
|
return 0;
|
||
|
/* receive data
|
||
|
* NOTE when we are called here, there is a pending SVDOUTPKTRDY to
|
||
|
* be done (see note above usb_drv_recv_setup), and when we will finish,
|
||
|
* we will also leave a pending SVDOUTPKTRDY to be done in stall or send */
|
||
|
while(length > 0)
|
||
|
{
|
||
|
/* clear packet ready for the PREVIOUS packet */
|
||
|
REG_USB_CSR0 |= USB_CSR0_SVDOUTPKTRDY;
|
||
|
/* wait for data */
|
||
|
while(!(REG_USB_CSR0 & USB_CSR0_OUTPKTRDY)) {}
|
||
|
int cnt = REG_USB_COUNT0;
|
||
|
/* clamp just in case */
|
||
|
cnt = MIN(cnt, length);
|
||
|
/* read fifo */
|
||
|
ptr = read_fifo0(ptr, cnt);
|
||
|
length -= cnt;
|
||
|
}
|
||
|
/* there is still a pending SVDOUTPKTRDY here */
|
||
|
return old_len;
|
||
|
}
|
||
|
|
||
|
void usb_drv_stall(int endpoint, bool stall, bool in)
|
||
|
{
|
||
|
(void) endpoint;
|
||
|
(void) in;
|
||
|
if(!stall)
|
||
|
return; /* EP0 unstall automatically */
|
||
|
/* select EP0 */
|
||
|
REG_USB_INDEX = 0;
|
||
|
/* set stall */
|
||
|
REG_USB_CSR0 |= USB_CSR0_SVDOUTPKTRDY | USB_CSR0_SENDSTALL;
|
||
|
}
|
||
|
|
||
|
void usb_drv_exit(void)
|
||
|
{
|
||
|
/* in case usb is running, soft disconnect */
|
||
|
REG_USB_POWER &= ~USB_POWER_SOFTCONN;
|
||
|
/* A delay seems necessary to avoid causing havoc. The USB spec says disconnect
|
||
|
* detection time (T_DDIS) is around 2us but in practice many hubs might
|
||
|
* require more. */
|
||
|
target_mdelay(1);
|
||
|
/* disable usb */
|
||
|
REG_CPM_CLKGR0 |= CLKGR0_OTG;
|
||
|
}
|