Browse Source
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 <mr.nuke.me@gmail.com>pull/149/head
Alexandru Gagniuc
12 years ago
2 changed files with 482 additions and 2 deletions
@ -0,0 +1,479 @@ |
|||
/*
|
|||
* This file is part of the libopencm3 project. |
|||
* |
|||
* Copyright (C) 2013 Alexandru Gagniuc <mr.nuke.me@gmail.com> |
|||
* |
|||
* 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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
|
|||
/*
|
|||
* 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 <libopencm3/cm3/common.h> |
|||
#include <libopencm3/lm4f/usb.h> |
|||
#include <libopencm3/lm4f/rcc.h> |
|||
#include <libopencm3/usb/usbd.h> |
|||
#include "../../lib/usb/usb_private.h" |
|||
|
|||
#include <stdbool.h> |
|||
|
|||
|
|||
#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, |
|||
}; |
|||
|
Loading…
Reference in new issue