1
0
mirror of git://projects.qi-hardware.com/f32xbase.git synced 2024-12-02 23:35:19 +02:00
f32xbase/fw/common/usb.c

521 lines
9.7 KiB
C
Raw Normal View History

/*
* common/usb.c - USB hardware setup and standard device requests
*
* Written 2008-2010 by Werner Almesberger
* Copyright 2008-2010 Werner Almesberger
*
* 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.
*/
/*
* Known issues:
* - no suspend/resume
* - EP0-sized packets cause an (otherwise harmless) SUEND at the end of the
* packet
* - #ifdef hell
*/
/*
* This code follows the register read/write sequences from the examples in
* SiLabs/MCU/Examples/C8051F326_7/USB_Interrupt/Firmware/F326_USB_Main.c and
* SiLabs/MCU/Examples/C8051F326_7/USB_Interrupt/Firmware/F326_USB_ISR.c
*
* More resources:
* http://www.beyondlogic.org/usbnutshell/usb1.htm
*/
#include <stdint.h>
#include "regs.h"
#include "uart.h"
#include "usb.h"
#ifndef NULL
#define NULL 0
#endif
#define BUG_ON(x)
#define NO_ADDRESS 0xff /* null value for function address */
__xdata struct ep_descr ep0;
#ifdef CONFIG_EP1
__xdata struct ep_descr ep1in, ep1out;
#endif
__bit (*user_setup)(struct setup_request *setup) __reentrant;
__bit (*user_get_descriptor)(uint8_t type, uint8_t index,
const uint8_t * const *reply, uint8_t *size) __reentrant;
void (*user_reset)(void) __reentrant;
static uint8_t addr = NO_ADDRESS;
void usb_io(struct ep_descr *ep, enum ep_state state, uint8_t *buf,
uint8_t size, void (*callback)(void *user), void *user)
{
BUG_ON(ep->state);
ep->state = state;
ep->buf = buf;
ep->end = buf+size;
ep->callback = callback;
ep->user = user;
}
static void usb_write(uint8_t reg, uint8_t value)
{
while (USB0ADR & BUSY);
USB0ADR = reg;
USB0DAT = value;
}
static uint8_t usb_read(uint8_t reg)
{
while (USB0ADR & BUSY);
USB0ADR = reg | BUSY;
while (USB0ADR & BUSY);
return USB0DAT;
}
static uint16_t usb_read_word(uint8_t reg)
{
uint8_t low;
low = usb_read(reg);
return low | usb_read(reg) << 8;
}
static __bit get_descriptor(uint8_t type, uint8_t index, uint16_t length)
{
const uint8_t *reply;
uint8_t size;
debug("get_descriptor(0x%02x, 0x%02x, 0x%04x)\n", type, index, length);
switch (type) {
case USB_DT_DEVICE:
reply = device_descriptor;
size = reply[0];
break;
case USB_DT_CONFIG:
if (index)
return 0;
reply = config_descriptor;
size = reply[2];
break;
default:
if (!user_get_descriptor)
return 0;
if (!user_get_descriptor(type, index, &reply, &size))
return 0;
}
if (length < size)
size = length;
usb_send(&ep0, reply, size, NULL, NULL);
return 1;
}
/*
* Process a SETUP packet. Hardware ensures that length is 8 bytes.
*/
static void handle_setup(void)
{
struct setup_request setup;
__bit ok = 0;
BUG_ON(usb_read(E0CNT) < 8);
setup.bmRequestType = usb_read(FIFO0);
setup.bRequest = usb_read(FIFO0);
setup.wValue = usb_read_word(FIFO0);
setup.wIndex = usb_read_word(FIFO0);
setup.wLength = usb_read_word(FIFO0);
switch (setup.bmRequestType | setup.bRequest << 8) {
/*
* Device request
*
* See http://www.beyondlogic.org/usbnutshell/usb6.htm
*/
case FROM_DEVICE(GET_STATUS):
debug("GET_STATUS\n");
if (setup.wLength != 2)
goto stall;
usb_send(&ep0, "\000", 2, NULL, NULL);
ok = 1;
break;
case TO_DEVICE(CLEAR_FEATURE):
debug("CLEAR_FEATURE\n");
ok = 1;
break;
case TO_DEVICE(SET_FEATURE):
debug("SET_FEATURE\n");
break;
case TO_DEVICE(SET_ADDRESS):
debug("SET_ADDRESS (0x%x)\n", setup.wValue);
addr = setup.wValue;
ok = 1;
break;
case FROM_DEVICE(GET_DESCRIPTOR):
ok = get_descriptor(setup.wValue >> 8, setup.wValue,
setup.wLength);
break;
case TO_DEVICE(SET_DESCRIPTOR):
error("SET_DESCRIPTOR\n");
break;
case FROM_DEVICE(GET_CONFIGURATION):
debug("GET_CONFIGURATION\n");
usb_send(&ep0, "", 1, NULL, NULL);
ok = 1;
break;
case TO_DEVICE(SET_CONFIGURATION):
debug("SET_CONFIGURATION\n");
ok = setup.wValue == config_descriptor[5];
break;
/*
* Interface request
*/
case FROM_INTERFACE(GET_STATUS):
printk("GET_STATUS\n");
break;
case TO_INTERFACE(CLEAR_FEATURE):
printk("CLEAR_FEATURE\n");
break;
case TO_INTERFACE(SET_FEATURE):
printk("SET_FEATURE\n");
break;
case FROM_INTERFACE(GET_INTERFACE):
printk("GET_INTERFACE\n");
break;
case TO_INTERFACE(SET_INTERFACE):
debug("SET_INTERFACE\n");
{
uint8_t *interface_descriptor = config_descriptor+9;
ok = setup.wIndex == interface_descriptor[2] &&
setup.wValue == interface_descriptor[3];
}
break;
/*
* Endpoint request
*/
case FROM_ENDPOINT(GET_STATUS):
printk("GET_STATUS\n");
break;
case TO_ENDPOINT(CLEAR_FEATURE):
printk("CLEAR_FEATURE(EP)\n");
break;
case TO_ENDPOINT(SET_FEATURE):
printk("SET_FEATURE(EP)\n");
break;
case FROM_ENDPOINT(SYNCH_FRAME):
printk("SYNCH_FRAME\n");
break;
default:
if (user_setup) {
ok = user_setup(&setup);
/*
* If we "break" here instead of "goto", "EVELYN the
* modified DOG" gets furious, says SDCC.
*/
if (ok)
goto okay;
}
printk("Unrecognized SETUP(%02x %02x ...\n",
setup.bmRequestType, setup.bRequest);
}
if (ok) {
okay:
if ((setup.bmRequestType & 0x80) || ep0.state == EP_RX)
usb_write(E0CSR, SOPRDY);
else
usb_write(E0CSR, SOPRDY | DATAEND);
return;
}
stall:
printk("STALL\n");
/* AN139 recommends this sequence */
usb_write(E0CSR, SOPRDY | SDSTL_0);
ep0.state = EP_IDLE;
}
static void ep0_data(void)
{
uint8_t fifo;
fifo = usb_read(E0CNT);
if (fifo > ep0.end-ep0.buf) {
usb_write(E0CSR, SDSTL_0);
return;
}
while (fifo--)
*ep0.buf++ = usb_read(FIFO0);
if (ep0.buf == ep0.end) {
ep0.state = EP_IDLE;
if (ep0.callback)
ep0.callback(ep0.user);
}
if (ep0.state == EP_IDLE)
usb_write(E0CSR, SOPRDY | DATAEND);
else
usb_write(E0CSR, SOPRDY);
}
static void handle_ep0(void)
{
uint8_t csr, size, left;
if (addr != NO_ADDRESS) {
usb_write(FADDR, addr);
debug("A");
addr = NO_ADDRESS;
}
csr = usb_read(E0CSR);
/* clear sent stall indication */
if (csr & STSTL_0) {
usb_write(E0CSR, 0);
/*
* @@@ Should return to IDLE, but this causes confusion with
* OPRDY_0. Need to investigate.
* ep0.state = EP_IDLE;
*/
}
/* if transaction was interrupted, clean up */
if (csr & SUEND) {
debug("S");
usb_write(E0CSR, DATAEND | SSUEND);
ep0.state = EP_IDLE;
}
if (csr & OPRDY_0) {
switch (ep0.state) {
case EP_IDLE:
handle_setup();
break;
case EP_RX:
ep0_data();
break;
default:
printk("??? %d\n", ep0.state);
break;
}
}
if (ep0.state != EP_TX)
return;
csr = usb_read(E0CSR);
if (csr & INPRDY_0)
return;
if (csr & (SUEND | OPRDY_0))
return;
size = ep0.end-ep0.buf;
if (size > EP0_SIZE)
size = EP0_SIZE;
for (left = size; left; left--)
usb_write(FIFO0, *ep0.buf++);
csr |= INPRDY_0;
if (size != EP0_SIZE) {
ep0.state = EP_IDLE;
csr |= DATAEND;
}
usb_write(E0CSR, csr);
if (ep0.state == EP_IDLE && ep0.callback)
ep0.callback(ep0.user);
}
#ifdef CONFIG_EP1
static void handle_ep1in(void)
{
uint8_t csrl;
csrl = usb_read(EINCSRL);
debug("handle_ep1in: EINCSRL 0x%02x\n", csrl);
if (csrl & UNDRUN)
csrl &= ~UNDRUN;
if (csrl & STSTL_IN)
csrl &= ~STSTL_IN;
usb_write(EINCSRL, csrl);
}
static void fill_ep1in(void)
{
uint8_t csrl, left;
uint16_t size;
if (ep1in.state != EP_TX)
return;
csrl = usb_read(EINCSRL);
debug("fill_ep1in: EINCSRL 0x%02x\n", csrl);
if (csrl & FIFONE)
return;
size = ep1in.end-ep1in.buf;
if (size > EP1_SIZE)
size = EP1_SIZE;
for (left = size; left; left--)
usb_write(FIFO1, *ep1in.buf++);
if (size != EP1_SIZE)
ep1in.state = EP_IDLE;
csrl |= INPRDY_IN;
usb_write(EINCSRL, csrl);
if (ep1in.state == EP_IDLE && ep1in.callback)
ep1in.callback(ep1in.user);
}
static void handle_ep1out(void)
{
uint8_t csrl, fifo;
csrl = usb_read(EOUTCSRL);
debug("EOUTCSRL 0x%02x\n", csrl);
if (csrl & OVRUN)
csrl &= ~OVRUN;
if (csrl & STSTL_OUT) {
csrl &= ~STSTL_OUT;
csrl |= CLRDT_OUT;
}
usb_write(EINCSRL, csrl);
if (!(csrl & OPRDY_OUT))
return;
if (ep1out.state != EP_RX) {
usb_write(EOUTCSRL, FLUSH_OUT);
return;
}
fifo = usb_read(EOUTCNTL);
if (fifo > ep1out.end-ep1out.buf) {
usb_write(EOUTCSRL, SDSTL_OUT | FLUSH_OUT);
return;
}
while (fifo--)
*ep1out.buf++ = usb_read(FIFO1);
ep1out.state = EP_IDLE;
if (ep1out.callback)
ep1out.callback(ep1out.user);
csrl &= ~OPRDY_OUT;
usb_write(EOUTCSRL, csrl);
}
#endif /* CONFIG_EP1 */
void usb_poll(void)
{
uint8_t flags;
flags = usb_read(CMINT);
if (flags) {
debug("CMINT 0x%02x\n", flags);
if (flags & RSTINT) {
ep0.state = EP_IDLE;
/*
* EP state serves as "buffer is valid" indicator for
* EP1OUT, so don't reset it. We need to call back
* EP1IN to tell it that the URB can be reused.
*
* (@@@ does this make sense ?)
*/
#ifdef CONFIG_EP1
if (ep1in.state == EP_TX && ep1in.callback)
ep1in.callback(ep1in.user);
ep1in.state = EP_IDLE;
#endif
usb_write(POWER, 0);
if (user_reset)
user_reset();
/* @@@ 1 for suspend signaling */
}
}
flags = usb_read(IN1INT);
if (flags) {
debug("IN1INT 0x%02x\n", flags);
if (flags & EP0) {
usb_write(INDEX, 0);
handle_ep0();
}
#ifdef CONFIG_EP1
if (flags & IN1) {
usb_write(INDEX, 1);
handle_ep1in();
}
#endif
}
#ifdef CONFIG_EP1
usb_write(INDEX, 1);
fill_ep1in();
#endif
#ifdef CONFIG_EP1
flags = usb_read(OUT1INT);
if (flags) {
debug("OUT1INT 0x%02x\n", flags);
usb_write(INDEX, 1);
handle_ep1out();
}
#endif
}
void usb_init(void)
{
usb_write(POWER, USBRST);
#ifdef LOW_SPEED
USB0XCN = PHYEN | PREN;
usb_write(CLKREC, CRE | CRLOW);
#else
USB0XCN = PHYEN | PREN | SPEED;
usb_write(CLKREC, CRE);
#endif
//usb_write(POWER, 0x01); /* we don't implement suspend yet */
usb_write(POWER, 0x00);
}