From 2fda5282ab2e5fc1bb19b13514fadd88bdbb491f Mon Sep 17 00:00:00 2001 From: Alexandru Gagniuc Date: Thu, 10 Jan 2013 18:46:04 -0600 Subject: [PATCH] lm4f: Implement a basic USB driver Implement a basic driver for the LM4F USB controller. The driver is in a basic form. DMA is not yet implemented. Double-buffering is supported by the hardware, but is not yet implemented Signed-off-by: Alexandru Gagniuc --- lib/lm4f/Makefile | 5 +- lib/lm4f/usb_lm4f.c | 479 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 482 insertions(+), 2 deletions(-) create mode 100644 lib/lm4f/usb_lm4f.c diff --git a/lib/lm4f/Makefile b/lib/lm4f/Makefile index 0133133a..5e522bf7 100644 --- a/lib/lm4f/Makefile +++ b/lib/lm4f/Makefile @@ -32,8 +32,9 @@ CFLAGS = -Os -g \ -ffunction-sections -fdata-sections -MD -DLM4F # ARFLAGS = rcsv ARFLAGS = rcs -OBJS = gpio.o vector.o assert.o systemcontrol.o rcc.o uart.o +OBJS = gpio.o vector.o assert.o systemcontrol.o rcc.o uart.o \ + usb_lm4f.o usb.o usb_control.o usb_standard.o -VPATH += ../cm3 +VPATH += ../usb:../cm3 include ../Makefile.include diff --git a/lib/lm4f/usb_lm4f.c b/lib/lm4f/usb_lm4f.c new file mode 100644 index 00000000..1609bfc5 --- /dev/null +++ b/lib/lm4f/usb_lm4f.c @@ -0,0 +1,479 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2013 Alexandru Gagniuc + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +/* + * TODO list: + * + * 1) Driver works by reading and writing to the FIFOs one byte at a time. It + * has no knowledge of DMA. + * 2) Double-buffering is supported. How can we take advantage of it to speed + * up endpoint transfers. + * 3) No benchmarks as to the endpoint's performance has been done. + */ +/* + * The following are resources referenced in comments: + * [1] http://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/t/238784.aspx + */ + +#include +#include +#include +#include +#include "../../lib/usb/usb_private.h" + +#include + + +#define MAX_FIFO_RAM (4 * 1024) + +const struct _usbd_driver lm4f_usb_driver; + +static inline void lm4f_usb_soft_disconnect(void) +{ + USB_POWER &= ~USB_POWER_SOFTCONN; +} + +static inline void lm4f_usb_soft_connect(void) +{ + USB_POWER |= USB_POWER_SOFTCONN; +} + +static void lm4f_set_address(usbd_device *usbd_dev, u8 addr) +{ + (void)usbd_dev; + + USB_FADDR = addr & USB_FADDR_FUNCADDR_MASK; +} + +static void lm4f_ep_setup(usbd_device *usbd_dev, u8 addr, u8 type, u16 max_size, + void (*callback) (usbd_device *usbd_dev, u8 ep)) +{ + (void)usbd_dev; + (void)type; + + u8 reg8; + u16 fifo_size; + + const bool dir_tx = addr & 0x80; + const u8 ep = addr & 0x0f; + + /* + * We do not mess with the maximum packet size, but we can only allocate + * the FIFO in power-of-two increments. + */ + if (max_size > 1024) { + fifo_size = 2048; + reg8 = USB_FIFOSZ_SIZE_2048; + } else if (max_size > 512) { + fifo_size = 1024; + reg8 = USB_FIFOSZ_SIZE_1024; + } else if (max_size > 256) { + fifo_size = 512; + reg8 = USB_FIFOSZ_SIZE_512; + } else if (max_size > 128) { + fifo_size = 256; + reg8 = USB_FIFOSZ_SIZE_256; + } else if (max_size > 64) { + fifo_size = 128; + reg8 = USB_FIFOSZ_SIZE_128; + } else if (max_size > 32) { + fifo_size = 64; + reg8 = USB_FIFOSZ_SIZE_64; + } else if (max_size > 16) { + fifo_size = 32; + reg8 = USB_FIFOSZ_SIZE_32; + } else if (max_size > 8) { + fifo_size = 16; + reg8 = USB_FIFOSZ_SIZE_16; + } else { + fifo_size = 8; + reg8 = USB_FIFOSZ_SIZE_8; + } + + /* Endpoint 0 is more special */ + if (addr == 0) { + USB_EPIDX = 0; + + if (reg8 > USB_FIFOSZ_SIZE_64) + reg8 = USB_FIFOSZ_SIZE_64; + + /* The RX and TX FIFOs are shared for EP0 */ + USB_RXFIFOSZ = reg8; + USB_TXFIFOSZ = reg8; + + /* + * Regardless of how much we allocate, the first 64 bytes + * are always reserved for EP0. + */ + usbd_dev->fifo_mem_top_ep0 = 64; + return; + } + + /* Are we out of FIFO space? */ + if (usbd_dev->fifo_mem_top + fifo_size > MAX_FIFO_RAM) + return; + + USB_EPIDX = addr & USB_EPIDX_MASK; + + /* FIXME: What about double buffering? */ + if (dir_tx) { + USB_TXMAXP(ep) = max_size; + USB_TXFIFOSZ = reg8; + USB_TXFIFOADD = ((usbd_dev->fifo_mem_top) >> 3); + if (callback) { + usbd_dev->user_callback_ctr[ep][USB_TRANSACTION_IN] = + (void *)callback; + } + if (type == USB_ENDPOINT_ATTR_ISOCHRONOUS) + USB_TXCSRH(ep) |= USB_TXCSRH_ISO; + else + USB_TXCSRH(ep) &= ~USB_TXCSRH_ISO; + } + else { + USB_RXMAXP(ep) = max_size; + USB_RXFIFOSZ = reg8; + USB_RXFIFOADD = ((usbd_dev->fifo_mem_top) >> 3); + if (callback) { + usbd_dev->user_callback_ctr[ep][USB_TRANSACTION_OUT] = + (void *)callback; + } + if (type == USB_ENDPOINT_ATTR_ISOCHRONOUS) + USB_RXCSRH(ep) |= USB_RXCSRH_ISO; + else + USB_RXCSRH(ep) &= ~USB_RXCSRH_ISO; + } + + usbd_dev->fifo_mem_top += fifo_size; +} + +static void lm4f_endpoints_reset(usbd_device *usbd_dev) +{ + /* + * The core resets the endpoints automatically on reset. + * The first 64 bytes are always reserved for EP0 + */ + usbd_dev->fifo_mem_top = 64; +} + +static void lm4f_ep_stall_set(usbd_device *usbd_dev, u8 addr, u8 stall) +{ + (void)usbd_dev; + + const u8 ep = addr & 0x0f; + const bool dir_tx = addr & 0x80; + + if (ep == 0) { + if (stall) + USB_CSRL0 |= USB_CSRL0_STALL; + else + USB_CSRL0 &= ~USB_CSRL0_STALL; + return; + } + + if (dir_tx) { + if (stall) + (USB_TXCSRL(ep)) |= USB_TXCSRL_STALL; + else + (USB_TXCSRL(ep)) &= ~USB_TXCSRL_STALL; + } + else { + if (stall) + (USB_RXCSRL(ep)) |= USB_RXCSRL_STALL; + else + (USB_RXCSRL(ep)) &= ~USB_RXCSRL_STALL; + } +} + +static u8 lm4f_ep_stall_get(usbd_device *usbd_dev, u8 addr) +{ + (void)usbd_dev; + + const u8 ep = addr & 0x0f; + const bool dir_tx = addr & 0x80; + + if (ep == 0) { + return (USB_CSRL0 & USB_CSRL0_STALLED); + } + + if (dir_tx) + return (USB_TXCSRL(ep) & USB_TXCSRL_STALLED); + else + return (USB_RXCSRL(ep) & USB_RXCSRL_STALLED); +} + +static void lm4f_ep_nak_set(usbd_device *usbd_dev, u8 addr, u8 nak) +{ + (void)usbd_dev; + (void)addr; + (void)nak; + + /* NAK's are handled automatically by hardware. Move along. */ +} + +static u16 lm4f_ep_write_packet(usbd_device *usbd_dev, u8 addr, + const void *buf, u16 len) +{ + const u8 ep = addr & 0xf; + u16 i; + + (void)usbd_dev; + + /* Don't touch the FIFO if there is still a packet being transmitted */ + if (ep == 0 && (USB_CSRL0 & USB_CSRL0_TXRDY)) { + return 0; + } else if (USB_TXCSRL(ep) & USB_TXCSRL_TXRDY) { + return 0; + } + + /* + * For some reason, using 16 or 32-bit transfers to the FIFO does not + * work well. + */ + for (i = 0; i < len; i++) + USB_FIFO8(ep) = ((u8 *)buf)[i]; + + + if (ep == 0) { + /* + * EP0 is very special. We should only set DATAEND when we + * transmit the last packet in the transaction. A transaction + * that is a multiple of 64 bytes will end with a zero-length + * packet, so our check is sane. + */ + if (len != 64) + USB_CSRL0 |= USB_CSRL0_TXRDY | USB_CSRL0_DATAEND; + else + USB_CSRL0 |= USB_CSRL0_TXRDY; + + } else { + USB_TXCSRL(ep) |= USB_TXCSRL_TXRDY; + } + + return i; +} + +static u16 lm4f_ep_read_packet(usbd_device *usbd_dev, u8 addr, void *buf, u16 len) +{ + (void)usbd_dev; + + u8 * buffy = buf; + u16 rlen; + u8 ep = addr & 0xf; + + u16 fifoin = USB_RXCOUNT(ep); + + rlen = (fifoin > len) ? len : fifoin; + + for (len = 0; len < rlen; len++) + buffy[len] = USB_FIFO8(ep); + + if (ep == 0) { + /* + * Clear RXRDY + * Datasheet says that DATAEND must also be set when clearing + * RXRDY. We don't do that. If did this when transmitting a + * packet larger than 64 bytes, only the first 64 bytes would + * be transmitted, followed by a handshake. The host would only + * get 64 bytes, seeing it as a malformed packet. Usually, we + * would not get past enumeration. + */ + USB_CSRL0 |= USB_CSRL0_RXRDYC; + + } else { + USB_RXCSRL(ep) &= ~USB_RXCSRL_RXRDY; + } + + return rlen; +} + +static void lm4f_poll(usbd_device *usbd_dev) +{ + void (*tx_cb)(usbd_device *usbd_dev, u8 ea); + void (*rx_cb)(usbd_device *usbd_dev, u8 ea); + int i; + + /* + * The initial state of these registers might change, as we process the + * interrupt, but we need the initial state in order to decide how to + * handle events. + */ + const u8 usb_is = USB_IS; + const u8 usb_rxis = USB_RXIS; + const u8 usb_txis = USB_TXIS; + const u8 usb_csrl0 = USB_CSRL0; + + if ((usb_is & USB_IM_SUSPEND) && (usbd_dev->user_callback_suspend)) + usbd_dev->user_callback_suspend(); + + if ((usb_is & USB_IM_RESUME) && (usbd_dev->user_callback_resume)) + usbd_dev->user_callback_resume(); + + if (usb_is & USB_IM_RESET) + _usbd_reset(usbd_dev); + + if ((usb_is & USB_IM_SOF) && (usbd_dev->user_callback_sof)) + usbd_dev->user_callback_sof(); + + if (usb_txis & USB_EP0) { + /* + * The EP0 bit in USB_TXIS is special. It tells us that + * something happened on EP0, but does not tell us what. This + * bit does not necessarily tell us that a packet was + * transmitted, so we have to go through all the possibilities + * to figure out exactly what did. Only after we've exhausted + * all other possibilities, can we assume this is a EPO + * "transmit complete" interrupt. + */ + if (usb_csrl0 & USB_CSRL0_RXRDY) { + enum _usbd_transaction type; + type = (usbd_dev->control_state.state != DATA_OUT && + usbd_dev->control_state.state != LAST_DATA_OUT) + ? USB_TRANSACTION_SETUP : + USB_TRANSACTION_OUT ; + + if (usbd_dev->user_callback_ctr[0][type]) + usbd_dev->user_callback_ctr[0][type] (usbd_dev, 0); + + + } else { + tx_cb = usbd_dev->user_callback_ctr[0][USB_TRANSACTION_IN]; + + /* + * EP0 bit in TXIS is set not only when a packet is + * finished transmitting, but also when RXRDY is set, or + * when we set TXRDY to transmit a packet. If any of + * those are the case, then we do not want to call our + * IN callback, since the state machine will be in the + * wrong state, and we'll just stall our control + * endpoint. + * In fact, the only way to know if it's time to call + * our TX callback is to know what to expect. The + * hardware does not tell us what sort of transaction + * this is. We need to work with the state machine to + * figure it all out. See [1] for details. + */ + if ((usbd_dev->control_state.state != DATA_IN) && + (usbd_dev->control_state.state != LAST_DATA_IN) && + (usbd_dev->control_state.state != STATUS_IN)) + return; + + if (tx_cb) + tx_cb (usbd_dev, 0); + } + } + + /* See which interrupt occurred */ + for (i = 1; i < 8; i++) { + tx_cb = usbd_dev->user_callback_ctr[i][USB_TRANSACTION_IN]; + rx_cb = usbd_dev->user_callback_ctr[i][USB_TRANSACTION_OUT]; + + if ( (usb_txis & (1 << i)) && tx_cb) + tx_cb(usbd_dev, i); + + if ( (usb_rxis & (1 << i)) && rx_cb) + rx_cb(usbd_dev, i); + } + + +} + +static void lm4f_disconnect(usbd_device *usbd_dev, bool disconnected) +{ + (void)usbd_dev; + + /* + * This is all it takes: + * usbd_disconnect(dev, 1) followed by usbd_disconnect(dev, 0) + * causes the device to re-enumerate and re-configure properly. + */ + if (disconnected) + lm4f_usb_soft_disconnect(); + else + lm4f_usb_soft_connect(); +} + +/* + * A static struct works as long as we have only one USB peripheral. If we + * meet LM4Fs with more than one USB, then we need to rework this approach. + */ +static struct _usbd_device usbd_dev; + +/** Initialize the USB device controller hardware of the LM4F. */ +static usbd_device *lm4f_usbd_init(void) +{ + int i; + + /* Start the USB clock */ + periph_clock_enable(RCC_USB0); + /* Enable the USB PLL interrupts - used to assert PLL lock */ + SYSCTL_IMC |= (SYSCTL_IMC_USBPLLLIM | SYSCTL_IMC_PLLLIM); + rcc_usb_pll_on(); + + /* Make sure we're disconnected. We'll reconnect later */ + lm4f_usb_soft_disconnect(); + + /* Software reset USB */ + SYSCTL_SRUSB = 1; + for (i = 0; i < 1000; i++) + __asm__("nop"); + SYSCTL_SRUSB = 0; + + /* + * Wait for the PLL to lock before soft connecting + * This will result in a deadlock if the system clock is not setup + * correctly (clock from main oscillator). + */ + /* Wait for it */ + i = 0; + while ( (SYSCTL_RIS & SYSCTL_RIS_USBPLLLRIS) == 0) { + i ++; + if (i > 0xffff) { + return 0; + } + } + + /* Now connect to USB */ + lm4f_usb_soft_connect(); + + /* No FIFO allocated yet, but the first 64 bytes are still reserved */ + usbd_dev.fifo_mem_top = 64; + + return &usbd_dev; +} + +/* What is this thing even good for */ +#define RX_FIFO_SIZE 512 + +const struct _usbd_driver lm4f_usb_driver = { + .init = lm4f_usbd_init, + .set_address = lm4f_set_address, + .ep_setup = lm4f_ep_setup, + .ep_reset = lm4f_endpoints_reset, + .ep_stall_set = lm4f_ep_stall_set, + .ep_stall_get = lm4f_ep_stall_get, + .ep_nak_set = lm4f_ep_nak_set, + .ep_write_packet = lm4f_ep_write_packet, + .ep_read_packet = lm4f_ep_read_packet, + .poll = lm4f_poll, + .disconnect = lm4f_disconnect, + .base_address = USB_BASE, + .set_address_before_status = false, + .rx_fifo_size = RX_FIFO_SIZE, +}; +