From bdfe058079b1aca94d6428bf3b704f9bcc55d4a9 Mon Sep 17 00:00:00 2001 From: Werner Almesberger Date: Fri, 13 Aug 2010 08:47:13 -0300 Subject: [PATCH] fw/common/ - copied from IDBG and generalized a little --- fw/common/Makefile.common | 100 ++++++++ fw/common/Makefile.system | 45 ++++ fw/common/regs.h | 77 ++++++ fw/common/uart.c | 125 +++++++++ fw/common/uart.h | 46 ++++ fw/common/usb-regs.h | 101 ++++++++ fw/common/usb.c | 519 ++++++++++++++++++++++++++++++++++++++ fw/common/usb.h | 150 +++++++++++ 8 files changed, 1163 insertions(+) create mode 100644 fw/common/Makefile.common create mode 100644 fw/common/Makefile.system create mode 100644 fw/common/regs.h create mode 100644 fw/common/uart.c create mode 100644 fw/common/uart.h create mode 100644 fw/common/usb-regs.h create mode 100644 fw/common/usb.c create mode 100644 fw/common/usb.h diff --git a/fw/common/Makefile.common b/fw/common/Makefile.common new file mode 100644 index 0000000..dbc38b0 --- /dev/null +++ b/fw/common/Makefile.common @@ -0,0 +1,100 @@ +# +# common/Makefile.common - Common rules and definitions +# +# 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. +# + + +CC=sdcc +CFLAGS=--std-c99 -I. -I../common \ + -DPAYLOAD_START=$(PAYLOAD_START) -DPAYLOAD_SIZE=$(PAYLOAD_SIZE) \ + -D`cat ../.target` +LDFLAGS=--xram-size 1024 + + +CPP := $(CPP) # make sure changing CC won't affect CPP + +CC_normal := $(CC) +CC_quiet = @echo " CC " $@ && $(CC_normal) +GEN_quiet = @echo " GENERATE " $@ && + +ifeq ($(V),1) + CC = $(CC_normal) + GEN = +else + CC = $(CC_quiet) + GEN = $(GEN_quiet) +endif + + +.SUFFIXES: .rel .ihx .bin + +.PHONY: clean spotless upload version + +all: $(MAIN).bin + +version version.h: + @if [ -f .version ]; then \ + v=`cat .version`; \ + expr $$v + 1 >.version; \ + else \ + echo 0 >.version; \ + fi + @[ -s .version ] || echo 0 >.version + @echo '/* MACHINE-GENERATED. DO NOT EDIT ! */' >version.c + @echo '#include "version.h"' >>version.c + @echo "const char *build_date = \"`date`\";" >>version.c + @echo "const uint16_t build_number = `cat .version`;" \ + >>version.c + +version.rel: version + +$(MAIN).ihx: $(OBJS:%=%.rel) + $(GEN) $(CC_normal) $(CFLAGS) $(LDFLAGS) $(OBJS:%=%.rel) + +.ihx.bin: + $(GEN) objcopy -I ihex $< -O binary $@ + @echo "build #`cat .version`, `ls -l $@`" + +.rel.ihx: + $(CC) $(CFLAGS) $< + +.c.rel: + $(CC) $(CFLAGS) -c $< + +$(OBJS:%=%.rel): ../.target + +# below, set dummy UART speed to make dependencies build without error + +depend .depend: + >.depend + touch version.h version.c + for n in $(OBJS:%=%.c); do \ + $(CPP) $(CFLAGS) -DUART_115200_BPS -MM -MG \ + `[ -f $$n ] || echo ../common/`$$n >>.depend || \ + { rm -f .depend; exit 1; }; \ + done + +.depend: ../.target + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif + +clean: + rm -f $(OBJS:%=%.rel) + rm -f $(OBJS:%=%.asm) $(OBJS:%=%.lst) + rm -f $(OBJS:%=%.rst) $(OBJS:%=%.sym) + rm -f $(MAIN).ihx $(MAIN).lnk $(MAIN).map $(MAIN).mem + +spotless: clean + rm -f $(MAIN).bin .depend + +upload: + ssh lab neo 'cat \>$(MAIN).bin' <$(MAIN).bin diff --git a/fw/common/Makefile.system b/fw/common/Makefile.system new file mode 100644 index 0000000..a7b7d6e --- /dev/null +++ b/fw/common/Makefile.system @@ -0,0 +1,45 @@ +# +# common/Makefile.system - System-specific definitions +# +# 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. +# + +# +# @@@ This is a template for now. Needs more work. +# + +# +# For now, we keep an 8k/7.5k division such that there's plenty of space for +# debugging the boot loader. Later, we should move to something like 4k/11.5k. +# +# The last 512 bytes are reserved and the 512 bytes before them cannot be +# erased, so they cannot be used for the payload either :-( +# + + +PAYLOAD_START=0x1000 +PAYLOAD_SIZE=0x2c00 + +# +# Set the serial speed, depending on target type +# + +ifneq (../.target,$(wildcard ../.target)) +$(error Please create ../.target first) +endif + +ifeq ($(shell cat ../.target),GTA) +DEFINE_UART_SPEED=-DUART_115200_BPS +else ifeq ($(shell cat ../.target),BEN_V1) +DEFINE_UART_SPEED=-DUART_57600_BPS +else ifeq ($(shell cat ../.target),BEN_V2) +DEFINE_UART_SPEED=-DUART_57600_BPS +else +DEFINE_UART_SPEED= +endif diff --git a/fw/common/regs.h b/fw/common/regs.h new file mode 100644 index 0000000..cf3fb39 --- /dev/null +++ b/fw/common/regs.h @@ -0,0 +1,77 @@ +/* + * common/regs.h - C8051F326 register definitions + * + * Written 2008 by Werner Almesberger + * Copyright 2008 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. + */ + + +#ifndef REGS_H +#define REGS_H + +#include +#include "usb-regs.h" + + +/* REG0CN */ +#define REGMOD 0x10 /* Voltage Regulator Mode Select */ +#define VBPOL 0x20 /* VBUS Interrupt Polarity Select */ +#define VBSTAT 0x40 /* VBUS Signal Status */ +#define REGDIS 0x80 /* Voltage Regulator Disable */ + +/* RSTSRC */ +#define PINRSF 0x01 /* HW Pin Reset Flag */ +/* PORSF 0x02 -- Power-On/VDD Monitor Reset Flag */ +#define MCFRSF 0x04 /* Missing Clock Detector Flag */ +/* SWRSF 0x10 -- Software Reset Force and Flag */ +#define FERROR 0x40 /* Flash Error Indicator */ +#define USBRSF 0x80 /* USB Reset Flag */ + +/* OSCICN */ +#define IFCN0 0x01 /* Internal Oscillator Frequency Control */ +#define IFCN1 0x02 /* 00: /8, 01: /4, 10: /2, 11: /1 */ + +/* CLKMUL */ +#define MULSEL 0x01 /* Clock Multiplier Input Select */ +#define MULRDY 0x20 /* Clock Multiplier Ready */ +#define MULINIT 0x40 /* Clock Multiplier Initialize */ +#define MULEN 0x80 /* Clock Multiplier Enable */ + +/* GPIOCN */ +#define SYSCLK 0x01 /* nSYSCLK Enable */ +#define INPUTEN 0x40 /* Global Digital Input Enable */ +#define WEAKPUD 0x80 /* Port I/O Weak Pullup Disable */ + +/* VDM0CN */ +#define VDMEN 0x80 /* VDD Monitor Enable */ +#define VDDSTAT 0x40 /* VDD Status */ + +/* USB0XCN */ +#define Dn 0x01 /* D- Signal Status */ +#define Dp 0x02 /* D+ Signal Status */ +#define DFREC 0x04 /* Differential Receiver */ +#define PHYTST0 0x08 /* Physical Layer Test */ +#define PHYTST1 0x10 /* 00: normal, 01: "1", 10: "0", 11: SE0 */ +#define SPEED 0x20 /* USB0 Speed Select */ +#define PHYEN 0x40 /* Physical Layer Enable */ +#define PREN 0x80 /* Internal Pullup Resistor Enable */ + +/* USB0ADR */ +#define BUSY 0x80 /* USB0 Register Read Busy Flag */ + +/* SMOD0 */ +#define S0DL0 0x04 /* Data Length */ +#define S0DL1 0x08 /* 00: 5-bit, 01: 6-bit, 10: 7-bit, 11: 8-bit */ + +/* SBCON0 */ +#define SB0PS0 0x01 /* Baud Rate Prescaler Select */ +#define SB0PS1 0x02 /* 00: /12, 01: /4, 10: /48, 11: /1 */ +#define SB0RUN 0x40 /* Baud Rate Generator Enable */ +#define SB0CLK 0x80 /* Baud Rate Clock Source */ + +#endif /* REGS_H */ diff --git a/fw/common/uart.c b/fw/common/uart.c new file mode 100644 index 0000000..325b915 --- /dev/null +++ b/fw/common/uart.c @@ -0,0 +1,125 @@ +/* + * common/uart.c - UART initialization and debug output + * + * Written 2008, 2009 by Werner Almesberger + * Copyright 2008, 2009 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. + */ + + +#include +#include + +#include "regs.h" +#include "uart.h" + + +/* gpio j4=0 */ + + +#ifndef CONFIG_USB_PUTCHAR + +void putchar(char c) +{ + if (c == '\n') + putchar('\r'); + SBUF0 = c; + while (!TI0); + TI0 = 0; +} + +#endif /* !CONFIG_USB_PUTCHAR */ + + +/* for now, we always use printf_tiny, which doesn't have a vprintf* version */ +#ifdef noCONFIG_PRINTK + +void printk(const char *fmt, ...) +{ + va_list ap; + __bit saved; + + va_start(ap, fmt); + saved = EA; + EA = 0; + vprintf(fmt, ap); + EA = saved; + va_end(ap); +} + +#endif /* CONFIG_PRINTK */ + + +void uart_init(uint8_t brg_mhz) +{ + /* + * UART: Enable only transmitter, no interrupts. + */ + SCON0 = 0; /* also clears TI0 */ + SMOD0 = S0DL1 | S0DL0; + + /* + * Configure the UART to 115200bps, see table 13.1 + * + * We can's support 1.5MHz (that is, without using the USB clock, but + * why would one want to run the core at 1.5MHz when USB is around ?) + * + * The closes settings would be: + * + * SBRL0 = 0xfff9; -- 107142.9 bps, error = -7% + * SBRL0 = 0xfffa; -- 125000 bps, error = +8.5% + * + * Depending on signal quality, we would need something like +/-5%. + */ + +#if defined(UART_115200_BPS) + switch (brg_mhz) { + case 3: + SBRL0 = 0xfff3; + break; + case 6: + SBRL0 = 0xffe6; + break; + case 12: + SBRL0 = 0xffcc; + break; + case 24: + SBRL0 = 0xff98; + break; + case 48: + SBRL0 = 0xff30; + break; + } +#elif defined(UART_57600_BPS) + switch (brg_mhz) { + case 3: + SBRL0 = 0xffe6; + break; + case 6: + SBRL0 = 0xffcc; + break; + case 12: + SBRL0 = 0xff98; + break; + case 24: + SBRL0 = 0xff30; + break; + case 48: + SBRL0 = 0xfe5f; + break; + } +#else +#error "must set either UART_115200_BPS or UART_57600_BPS" +#endif + + SBCON0 = SB0RUN | SB0PS0 | SB0PS1; + + /* + * Make TX a push-pull output + */ + P0MDOUT |= 1 << 4; +} diff --git a/fw/common/uart.h b/fw/common/uart.h new file mode 100644 index 0000000..24ba1c4 --- /dev/null +++ b/fw/common/uart.h @@ -0,0 +1,46 @@ +/* + * common/uart.h - UART initialization and debug output + * + * Written 2008 by Werner Almesberger + * Copyright 2008 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. + */ + + +#ifndef UART_H +#define UART_H + +#include +#include + +#include "config.h" + + +#ifdef CONFIG_DEBUG +#define debug printk +#define CONFIG_PRINTK +#else +#define debug(...) +#endif + +#ifdef CONFIG_ERROR +#define error printk +#define CONFIG_PRINTK +#else +#define error(...) +#endif + +#ifdef CONFIG_PRINTK +#define printk printf_fast +#else +#define printk(...) +#endif + +void putchar(char c); +void uart_init(uint8_t brg_mhz); + +#endif /* !UART_H */ diff --git a/fw/common/usb-regs.h b/fw/common/usb-regs.h new file mode 100644 index 0000000..d4358a2 --- /dev/null +++ b/fw/common/usb-regs.h @@ -0,0 +1,101 @@ +/* + * common/usb-regs.h - C8051F326 USB register definitions + * + * Written 2008 by Werner Almesberger + * Copyright 2008 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. + */ + + +#ifndef USB_REGS_H +#define USB_REGS_H + +/* Indirect USB registers */ + +#define FADDR 0x00 /* Function Address */ + +#define POWER 0x01 /* Power Management */ +#define SUSEN 0x01 /* Suspend Detection Enable */ +#define SUSMD 0x02 /* Suspend Mode */ +#define RESUME 0x04 /* Force Resume */ +#define USBRST 0x08 /* Reset Detect */ +#define USBINH 0x10 /* USB0 Inhibit */ +#define ISOUD 0x80 /* ISO Update */ + +#define IN1INT 0x02 /* EP0 and EP1 IN Interrupt Flags */ +#define EP0 0x01 /* EP0 Interrupt-pending Flag */ +#define IN1 0x02 /* IN EP1 Interrupt-pending Flag */ + +#define OUT1INT 0x04 /* EP1 OUT Interrupt Flag */ + +#define CMINT 0x06 /* Common USB Interrupt Flags */ +#define SUSINT 0x01 /* Suspend Interrupt-Pending Flag */ +#define RSUINT 0x02 /* Resume Interrupt-Pending Flag */ +#define RSTINT 0x04 /* Reset Interrupt-Pending Flag */ +#define SOF 0x08 /* Start of Frame Interrupt */ + +#define IN1IE 0x07 /* EP0 and EP1 IN Interrupt Enables */ + +#define OUT1IE 0x09 /* EP1 out Interrupt Enable */ + +#define CMIE 0x0b /* Common USB Interrupt Enable */ + +#define FRAMEL 0x0c /* Frame Number Low Byte */ + +#define FRAMEH 0x0d /* Frame Number Low Byte */ + +#define INDEX 0x0e /* USB0 EP Index */ + +#define CLKREC 0x0f /* Clock Recovery Control */ +#define CRLOW 0x20 /* Low Speed Clock Recovery Mode */ +#define CRSSEN 0x40 /* Clock Recovery Single Step */ +#define CRE 0x80 /* Clock Recovery Enable */ + +#define E0CSR 0x11 /* EP0 Control/Status */ +#define OPRDY_0 0x01 /* OUT Packet Ready */ +#define INPRDY_0 0x02 /* IN Packet Ready */ +#define STSTL_0 0x04 /* Sent Stall */ +#define DATAEND 0x08 /* Data End */ +#define SUEND 0x10 /* Setup End */ +#define SDSTL_0 0x20 /* Send Stall */ +#define SOPRDY 0x40 /* Serviced OPRDY */ +#define SSUEND 0x80 /* Serviced Setup End */ + +#define EINCSRL 0x11 /* EP IN Control/Status Low Byte */ +#define INPRDY_IN 0x01 /* IN Packet Ready */ +#define FIFONE 0x02 /* FIFO Not Empty */ +#define UNDRUN 0x04 /* Data Underrun */ +#define FLUSH_IN 0x08 /* FIFO Flush */ +#define SDSTL_IN 0x10 /* Send Stall */ +#define STSTL_IN 0x20 /* Sent Stall */ +#define CLRDT_IN 0x40 /* Clear Data Toggle */ + +#define EINCSRH 0x12 /* EP OUT Control/Status High Byte */ + +#define EOUTCSRL 0x14 /* EP OUT Control/Status Low Byte */ +#define OPRDY_OUT 0x01 /* OUT Packet Ready */ +#define FIFOFUL 0x02 /* OUT FIFO Full */ +#define OVRUN 0x04 /* Data Overrun */ +#define DATERR 0x08 /* Data Error */ +#define FLUSH_OUT 0x10 /* FIFO Flush */ +#define SDSTL_OUT 0x20 /* Send Stall */ +#define STSTL_OUT 0x40 /* Sent Stall */ +#define CLRDT_OUT 0x80 /* Clear Data Toggle */ + +#define EOUTCSRH 0x15 /* EP OUT Control/Status High Byte */ + +#define E0CNT 0x16 /* Number of Received Bytes in EP0 FIFO */ + +#define EOUTCNTL 0x16 /* EP OUT Packet Count Low Byte */ + +#define EOUTCNTH 0x17 /* EP OUT Packet Count High Byte */ + +#define FIFO0 0x20 /* EP0 FIFO */ + +#define FIFO1 0x21 /* EP1 FIFO */ + +#endif /* !USB_REGS_H */ diff --git a/fw/common/usb.c b/fw/common/usb.c new file mode 100644 index 0000000..3342126 --- /dev/null +++ b/fw/common/usb.c @@ -0,0 +1,519 @@ +/* + * common/usb.c - USB hardware setup and standard device requests + * + * Written 2008, 2009 by Werner Almesberger + * Copyright 2008m 2009 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 + +#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"); + usb_write(E0CSR, SDSTL_0); + ep0.state = EP_STALL; +} + + +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); +} diff --git a/fw/common/usb.h b/fw/common/usb.h new file mode 100644 index 0000000..b5c8a2b --- /dev/null +++ b/fw/common/usb.h @@ -0,0 +1,150 @@ +/* + * common/usb.h - USB hardware setup and standard device requests + * + * Written 2008, 2009 by Werner Almesberger + * Copyright 2008, 2009 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. + */ + + +#ifndef USB_H +#define USB_H + + +#include + +#include "config.h" + + +/* + * Descriptor types + * + * Reuse libusb naming scheme (/usr/include/usb.h) + */ + +#define USB_DT_DEVICE 1 +#define USB_DT_CONFIG 2 +#define USB_DT_STRING 3 +#define USB_DT_INTERFACE 4 +#define USB_DT_ENDPOINT 5 + +/* + * Device classes + * + * Reuse libusb naming scheme (/usr/include/usb.h) + */ + +#define USB_CLASS_PER_INTERFACE 0xfe +#define USB_CLASS_VENDOR_SPEC 0xff + +/* + * Configuration attributes + */ + +#define USB_ATTR_BUS_POWERED 0x80 +#define USB_ATTR_SELF_POWERED 0x40 +#define USB_ATTR_REMOTE_WAKEUP 0x20 + +/* + * Setup request types + */ + +#define TO_DEVICE(req) (0x00 | (req) << 8) +#define FROM_DEVICE(req) (0x80 | (req) << 8) +#define TO_INTERFACE(req) (0x01 | (req) << 8) +#define FROM_INTERFACE(req) (0x81 | (req) << 8) +#define TO_ENDPOINT(req) (0x02 | (req) << 8) +#define FROM_ENDPOINT(req) (0x82 | (req) << 8) + +/* + * Setup requests + */ + +#define GET_STATUS 0x00 +#define CLEAR_FEATURE 0x01 +#define SET_FEATURE 0x03 +#define SET_ADDRESS 0x05 +#define GET_DESCRIPTOR 0x06 +#define SET_DESCRIPTOR 0x07 +#define GET_CONFIGURATION 0x08 +#define SET_CONFIGURATION 0x09 +#define GET_INTERFACE 0x0a +#define SET_INTERFACE 0x0b +#define SYNCH_FRAME 0x0c + + +/* + * Odd. sdcc seems to think "x" assumes the size of the destination, i.e., + * uint8_t. Hence the cast. + */ + +#define LE(x) ((uint16_t) (x) & 0xff), ((uint16_t) (x) >> 8) + +#define LO(x) (((uint8_t *) &(x))[0]) +#define HI(x) (((uint8_t *) &(x))[1]) + + +#ifdef LOW_SPEED +#define EP0_SIZE 8 +#else +#define EP0_SIZE 64 +#endif + +#define EP1_SIZE 64 /* simplify */ + + +enum ep_state { + EP_IDLE, + EP_RX, + EP_TX, + EP_STALL, +}; + +struct ep_descr { + enum ep_state state; + uint8_t *buf; + uint8_t *end; + void (*callback)(void *user) __reentrant; + void *user; +}; + +struct setup_request { + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +}; + + +extern const uint8_t device_descriptor[]; +extern const uint8_t config_descriptor[]; +extern __xdata struct ep_descr ep0; +#ifdef CONFIG_EP1 +extern __xdata struct ep_descr ep1in, ep1out; +#endif + +extern __bit (*user_setup)(struct setup_request *setup) __reentrant; +extern __bit (*user_get_descriptor)(uint8_t type, uint8_t index, + const uint8_t * const *reply, uint8_t *size) __reentrant; +extern void (*user_reset)(void) __reentrant; + + +#define usb_left(ep) ((ep)->end-(ep)->buf) +#define usb_send(ep, buf, size, callback, user) \ + usb_io(ep, EP_TX, buf, size, callback, user) +#define usb_recv(ep, buf, size, callback, user) \ + usb_io(ep, EP_RX, buf, size, callback, user) + +void usb_io(struct ep_descr *ep, enum ep_state state, uint8_t *buf, + uint8_t size, void (*callback)(void *user), void *user); + + +void usb_init(void); +void usb_poll(void); + +#endif /* !USB_H */