subrepo:
  subdir:   "libopencm3"
  merged:   "88e91c9a7cce"
upstream:
  origin:   "https://github.com/libopencm3/libopencm3.git"
  branch:   "master"
  commit:   "88e91c9a7cce"
git-subrepo:
  version:  "0.4.3"
  origin:   "???"
  commit:   "???"
This commit is contained in:
2023-01-21 21:54:42 +02:00
parent f01f2a30fa
commit 054740c5de
1205 changed files with 191912 additions and 0 deletions

174
libopencm3/lib/usb/usb.c Normal file
View File

@@ -0,0 +1,174 @@
/** @defgroup usb_drivers_file Generic USB Drivers
@ingroup USB
@brief <b>Generic USB Drivers</b>
@version 1.0.0
@author @htmlonly &copy; @endhtmlonly 2010
Gareth McMullin <gareth@blacksphere.co.nz>
@date 10 March 2013
LGPL License Terms @ref lgpl_license
*/
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz>
*
* 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/>.
*/
/**@{*/
#include <string.h>
#include <libopencm3/usb/usbd.h>
#include "usb_private.h"
usbd_device *usbd_init(const usbd_driver *driver,
const struct usb_device_descriptor *dev,
const struct usb_config_descriptor *conf,
const char * const *strings, int num_strings,
uint8_t *control_buffer, uint16_t control_buffer_size)
{
usbd_device *usbd_dev;
usbd_dev = driver->init();
usbd_dev->driver = driver;
usbd_dev->desc = dev;
usbd_dev->config = conf;
usbd_dev->strings = strings;
usbd_dev->num_strings = num_strings;
usbd_dev->extra_string_idx = 0;
usbd_dev->extra_string = NULL;
usbd_dev->ctrl_buf = control_buffer;
usbd_dev->ctrl_buf_len = control_buffer_size;
usbd_dev->user_callback_ctr[0][USB_TRANSACTION_SETUP] =
_usbd_control_setup;
usbd_dev->user_callback_ctr[0][USB_TRANSACTION_OUT] =
_usbd_control_out;
usbd_dev->user_callback_ctr[0][USB_TRANSACTION_IN] =
_usbd_control_in;
int i;
for (i = 0; i < MAX_USER_SET_CONFIG_CALLBACK; i++) {
usbd_dev->user_callback_set_config[i] = NULL;
}
return usbd_dev;
}
void usbd_register_reset_callback(usbd_device *usbd_dev, void (*callback)(void))
{
usbd_dev->user_callback_reset = callback;
}
void usbd_register_suspend_callback(usbd_device *usbd_dev,
void (*callback)(void))
{
usbd_dev->user_callback_suspend = callback;
}
void usbd_register_resume_callback(usbd_device *usbd_dev,
void (*callback)(void))
{
usbd_dev->user_callback_resume = callback;
}
void usbd_register_sof_callback(usbd_device *usbd_dev, void (*callback)(void))
{
usbd_dev->user_callback_sof = callback;
}
void usbd_register_extra_string(usbd_device *usbd_dev, int index, const char* string)
{
/*
* Note: string index 0 is reserved for LANGID requests and cannot
* be overwritten using this functionality.
*/
if (string != NULL && index > 0) {
usbd_dev->extra_string_idx = index;
usbd_dev->extra_string = string;
} else {
usbd_dev->extra_string_idx = 0;
}
}
void _usbd_reset(usbd_device *usbd_dev)
{
usbd_dev->current_address = 0;
usbd_dev->current_config = 0;
usbd_ep_setup(usbd_dev, 0, USB_ENDPOINT_ATTR_CONTROL, usbd_dev->desc->bMaxPacketSize0, NULL);
usbd_dev->driver->set_address(usbd_dev, 0);
if (usbd_dev->user_callback_reset) {
usbd_dev->user_callback_reset();
}
}
/* Functions to wrap the low-level driver */
void usbd_poll(usbd_device *usbd_dev)
{
usbd_dev->driver->poll(usbd_dev);
}
__attribute__((weak)) void usbd_disconnect(usbd_device *usbd_dev,
bool disconnected)
{
/* not all drivers support disconnection */
if (usbd_dev->driver->disconnect) {
usbd_dev->driver->disconnect(usbd_dev, disconnected);
}
}
void usbd_ep_setup(usbd_device *usbd_dev, uint8_t addr, uint8_t type,
uint16_t max_size, usbd_endpoint_callback callback)
{
usbd_dev->driver->ep_setup(usbd_dev, addr, type, max_size, callback);
}
uint16_t usbd_ep_write_packet(usbd_device *usbd_dev, uint8_t addr,
const void *buf, uint16_t len)
{
return usbd_dev->driver->ep_write_packet(usbd_dev, addr, buf, len);
}
uint16_t usbd_ep_read_packet(usbd_device *usbd_dev, uint8_t addr, void *buf,
uint16_t len)
{
return usbd_dev->driver->ep_read_packet(usbd_dev, addr, buf, len);
}
void usbd_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, uint8_t stall)
{
usbd_dev->driver->ep_stall_set(usbd_dev, addr, stall);
}
uint8_t usbd_ep_stall_get(usbd_device *usbd_dev, uint8_t addr)
{
return usbd_dev->driver->ep_stall_get(usbd_dev, addr);
}
void usbd_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, uint8_t nak)
{
usbd_dev->driver->ep_nak_set(usbd_dev, addr, nak);
}
/**@}*/

View File

@@ -0,0 +1,20 @@
/*
* This file is part of the libopencm3 project.
*
* 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/>.
*/
#include <libopencm3/usb/audio.h>
/* This stub exists just to trick doxygen. */

View File

@@ -0,0 +1,20 @@
/*
* This file is part of the libopencm3 project.
*
* 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/>.
*/
#include <libopencm3/usb/cdc.h>
/* This stub exists just to trick doxygen. */

View File

@@ -0,0 +1,316 @@
/** @defgroup usb_control_file Generic USB Control Requests
@ingroup USB
@brief <b>Generic USB Control Requests</b>
@version 1.0.0
@author @htmlonly &copy; @endhtmlonly 2010
Gareth McMullin <gareth@blacksphere.co.nz>
@date 10 March 2013
LGPL License Terms @ref lgpl_license
*/
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz>
*
* 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/>.
*/
/**@{*/
#include <stdlib.h>
#include <libopencm3/usb/usbd.h>
#include "usb_private.h"
/*
* According to the USB 2.0 specification, section 8.5.3, when a control
* transfer is stalled, the pipe becomes idle. We provide one utility to stall
* a transaction to reduce boilerplate code.
*/
static void stall_transaction(usbd_device *usbd_dev)
{
usbd_ep_stall_set(usbd_dev, 0, 1);
usbd_dev->control_state.state = IDLE;
}
/**
* If we're replying with _some_ data, but less than the host is expecting,
* then we normally just do a short transfer. But if it's short, but a
* multiple of the endpoint max packet size, we need an explicit ZLP.
* @param len how much data we want to transfer
* @param wLength how much the host asked for
* @param ep_size
* @return
*/
static bool needs_zlp(uint16_t len, uint16_t wLength, uint8_t ep_size)
{
if (len < wLength) {
if (len && (len % ep_size == 0)) {
return true;
}
}
return false;
}
/* Register application callback function for handling USB control requests. */
int usbd_register_control_callback(usbd_device *usbd_dev, uint8_t type,
uint8_t type_mask,
usbd_control_callback callback)
{
int i;
for (i = 0; i < MAX_USER_CONTROL_CALLBACK; i++) {
if (usbd_dev->user_control_callback[i].cb) {
continue;
}
usbd_dev->user_control_callback[i].type = type;
usbd_dev->user_control_callback[i].type_mask = type_mask;
usbd_dev->user_control_callback[i].cb = callback;
return 0;
}
return -1;
}
static void usb_control_send_chunk(usbd_device *usbd_dev)
{
if (usbd_dev->desc->bMaxPacketSize0 <
usbd_dev->control_state.ctrl_len) {
/* Data stage, normal transmission */
usbd_ep_write_packet(usbd_dev, 0,
usbd_dev->control_state.ctrl_buf,
usbd_dev->desc->bMaxPacketSize0);
usbd_dev->control_state.state = DATA_IN;
usbd_dev->control_state.ctrl_buf +=
usbd_dev->desc->bMaxPacketSize0;
usbd_dev->control_state.ctrl_len -=
usbd_dev->desc->bMaxPacketSize0;
} else {
/* Data stage, end of transmission */
usbd_ep_write_packet(usbd_dev, 0,
usbd_dev->control_state.ctrl_buf,
usbd_dev->control_state.ctrl_len);
usbd_dev->control_state.state =
usbd_dev->control_state.needs_zlp ?
DATA_IN : LAST_DATA_IN;
usbd_dev->control_state.needs_zlp = false;
usbd_dev->control_state.ctrl_len = 0;
usbd_dev->control_state.ctrl_buf = NULL;
}
}
static int usb_control_recv_chunk(usbd_device *usbd_dev)
{
uint16_t packetsize = MIN(usbd_dev->desc->bMaxPacketSize0,
usbd_dev->control_state.req.wLength -
usbd_dev->control_state.ctrl_len);
uint16_t size = usbd_ep_read_packet(usbd_dev, 0,
usbd_dev->control_state.ctrl_buf +
usbd_dev->control_state.ctrl_len,
packetsize);
if (size != packetsize) {
stall_transaction(usbd_dev);
return -1;
}
usbd_dev->control_state.ctrl_len += size;
return packetsize;
}
static enum usbd_request_return_codes
usb_control_request_dispatch(usbd_device *usbd_dev,
struct usb_setup_data *req)
{
int i, result = 0;
struct user_control_callback *cb = usbd_dev->user_control_callback;
/* Call user command hook function. */
for (i = 0; i < MAX_USER_CONTROL_CALLBACK; i++) {
if (cb[i].cb == NULL) {
break;
}
if ((req->bmRequestType & cb[i].type_mask) == cb[i].type) {
result = cb[i].cb(usbd_dev, req,
&(usbd_dev->control_state.ctrl_buf),
&(usbd_dev->control_state.ctrl_len),
&(usbd_dev->control_state.complete));
if (result == USBD_REQ_HANDLED ||
result == USBD_REQ_NOTSUPP) {
return result;
}
}
}
/* Try standard request if not already handled. */
return _usbd_standard_request(usbd_dev, req,
&(usbd_dev->control_state.ctrl_buf),
&(usbd_dev->control_state.ctrl_len));
}
/* Handle commands and read requests. */
static void usb_control_setup_read(usbd_device *usbd_dev,
struct usb_setup_data *req)
{
usbd_dev->control_state.ctrl_buf = usbd_dev->ctrl_buf;
usbd_dev->control_state.ctrl_len = req->wLength;
if (usb_control_request_dispatch(usbd_dev, req)) {
if (req->wLength) {
usbd_dev->control_state.needs_zlp =
needs_zlp(usbd_dev->control_state.ctrl_len,
req->wLength,
usbd_dev->desc->bMaxPacketSize0);
/* Go to data out stage if handled. */
usb_control_send_chunk(usbd_dev);
} else {
/* Go to status stage if handled. */
usbd_ep_write_packet(usbd_dev, 0, NULL, 0);
usbd_dev->control_state.state = STATUS_IN;
}
} else {
/* Stall endpoint on failure. */
stall_transaction(usbd_dev);
}
}
static void usb_control_setup_write(usbd_device *usbd_dev,
struct usb_setup_data *req)
{
if (req->wLength > usbd_dev->ctrl_buf_len) {
stall_transaction(usbd_dev);
return;
}
/* Buffer into which to write received data. */
usbd_dev->control_state.ctrl_buf = usbd_dev->ctrl_buf;
usbd_dev->control_state.ctrl_len = 0;
/* Wait for DATA OUT stage. */
if (req->wLength > usbd_dev->desc->bMaxPacketSize0) {
usbd_dev->control_state.state = DATA_OUT;
} else {
usbd_dev->control_state.state = LAST_DATA_OUT;
}
usbd_ep_nak_set(usbd_dev, 0, 0);
}
/* Do not appear to belong to the API, so are omitted from docs */
/**@}*/
void _usbd_control_setup(usbd_device *usbd_dev, uint8_t ea)
{
struct usb_setup_data *req = &usbd_dev->control_state.req;
(void)ea;
usbd_dev->control_state.complete = NULL;
usbd_ep_nak_set(usbd_dev, 0, 1);
if (req->wLength == 0) {
usb_control_setup_read(usbd_dev, req);
} else if (req->bmRequestType & 0x80) {
usb_control_setup_read(usbd_dev, req);
} else {
usb_control_setup_write(usbd_dev, req);
}
}
void _usbd_control_out(usbd_device *usbd_dev, uint8_t ea)
{
(void)ea;
switch (usbd_dev->control_state.state) {
case DATA_OUT:
if (usb_control_recv_chunk(usbd_dev) < 0) {
break;
}
if ((usbd_dev->control_state.req.wLength -
usbd_dev->control_state.ctrl_len) <=
usbd_dev->desc->bMaxPacketSize0) {
usbd_dev->control_state.state = LAST_DATA_OUT;
}
break;
case LAST_DATA_OUT:
if (usb_control_recv_chunk(usbd_dev) < 0) {
break;
}
/*
* We have now received the full data payload.
* Invoke callback to process.
*/
if (usb_control_request_dispatch(usbd_dev,
&(usbd_dev->control_state.req))) {
/* Go to status stage on success. */
usbd_ep_write_packet(usbd_dev, 0, NULL, 0);
usbd_dev->control_state.state = STATUS_IN;
} else {
stall_transaction(usbd_dev);
}
break;
case STATUS_OUT:
usbd_ep_read_packet(usbd_dev, 0, NULL, 0);
usbd_dev->control_state.state = IDLE;
if (usbd_dev->control_state.complete) {
usbd_dev->control_state.complete(usbd_dev,
&(usbd_dev->control_state.req));
}
usbd_dev->control_state.complete = NULL;
break;
default:
stall_transaction(usbd_dev);
}
}
void _usbd_control_in(usbd_device *usbd_dev, uint8_t ea)
{
(void)ea;
struct usb_setup_data *req = &(usbd_dev->control_state.req);
switch (usbd_dev->control_state.state) {
case DATA_IN:
usb_control_send_chunk(usbd_dev);
break;
case LAST_DATA_IN:
usbd_dev->control_state.state = STATUS_OUT;
usbd_ep_nak_set(usbd_dev, 0, 0);
break;
case STATUS_IN:
if (usbd_dev->control_state.complete) {
usbd_dev->control_state.complete(usbd_dev,
&(usbd_dev->control_state.req));
}
/* Exception: Handle SET ADDRESS function here... */
if ((req->bmRequestType == 0) &&
(req->bRequest == USB_REQ_SET_ADDRESS)) {
usbd_dev->driver->set_address(usbd_dev, req->wValue);
}
usbd_dev->control_state.state = IDLE;
break;
default:
stall_transaction(usbd_dev);
}
}

View File

@@ -0,0 +1,448 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
*
* 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/>.
*/
#include <string.h>
#include <libopencm3/cm3/common.h>
#include <libopencm3/usb/usbd.h>
#include <libopencm3/usb/dwc/otg_common.h>
#include "usb_private.h"
#include "usb_dwc_common.h"
/* The FS core and the HS core have the same register layout.
* As the code can be used on both cores, the registers offset is modified
* according to the selected cores base address. */
#define dev_base_address (usbd_dev->driver->base_address)
#define REBASE(x) MMIO32((x) + (dev_base_address))
void dwc_set_address(usbd_device *usbd_dev, uint8_t addr)
{
REBASE(OTG_DCFG) = (REBASE(OTG_DCFG) & ~OTG_DCFG_DAD) | (addr << 4);
}
void dwc_ep_setup(usbd_device *usbd_dev, uint8_t addr, uint8_t type,
uint16_t max_size,
void (*callback) (usbd_device *usbd_dev, uint8_t ep))
{
/*
* Configure endpoint address and type. Allocate FIFO memory for
* endpoint. Install callback function.
*/
uint8_t dir = addr & 0x80;
addr &= 0x7f;
if (addr == 0) { /* For the default control endpoint */
/* Configure IN part. */
if (max_size >= 64) {
REBASE(OTG_DIEPCTL0) = OTG_DIEPCTL0_MPSIZ_64;
} else if (max_size >= 32) {
REBASE(OTG_DIEPCTL0) = OTG_DIEPCTL0_MPSIZ_32;
} else if (max_size >= 16) {
REBASE(OTG_DIEPCTL0) = OTG_DIEPCTL0_MPSIZ_16;
} else {
REBASE(OTG_DIEPCTL0) = OTG_DIEPCTL0_MPSIZ_8;
}
REBASE(OTG_DIEPTSIZ0) =
(max_size & OTG_DIEPSIZ0_XFRSIZ_MASK);
REBASE(OTG_DIEPCTL0) |=
OTG_DIEPCTL0_EPENA | OTG_DIEPCTL0_SNAK;
/* Configure OUT part. */
usbd_dev->doeptsiz[0] = OTG_DIEPSIZ0_STUPCNT_1 |
OTG_DIEPSIZ0_PKTCNT |
(max_size & OTG_DIEPSIZ0_XFRSIZ_MASK);
REBASE(OTG_DOEPTSIZ(0)) = usbd_dev->doeptsiz[0];
REBASE(OTG_DOEPCTL(0)) |=
OTG_DOEPCTL0_EPENA | OTG_DIEPCTL0_SNAK;
REBASE(OTG_GNPTXFSIZ) = ((max_size / 4) << 16) |
usbd_dev->driver->rx_fifo_size;
usbd_dev->fifo_mem_top += max_size / 4;
usbd_dev->fifo_mem_top_ep0 = usbd_dev->fifo_mem_top;
return;
}
if (dir) {
REBASE(OTG_DIEPTXF(addr)) = ((max_size / 4) << 16) |
usbd_dev->fifo_mem_top;
usbd_dev->fifo_mem_top += max_size / 4;
REBASE(OTG_DIEPTSIZ(addr)) =
(max_size & OTG_DIEPSIZ0_XFRSIZ_MASK);
REBASE(OTG_DIEPCTL(addr)) |=
OTG_DIEPCTL0_EPENA | OTG_DIEPCTL0_SNAK | (type << 18)
| OTG_DIEPCTL0_USBAEP | OTG_DIEPCTLX_SD0PID
| (addr << 22) | max_size;
if (callback) {
usbd_dev->user_callback_ctr[addr][USB_TRANSACTION_IN] =
(void *)callback;
}
}
if (!dir) {
usbd_dev->doeptsiz[addr] = OTG_DIEPSIZ0_PKTCNT |
(max_size & OTG_DIEPSIZ0_XFRSIZ_MASK);
REBASE(OTG_DOEPTSIZ(addr)) = usbd_dev->doeptsiz[addr];
REBASE(OTG_DOEPCTL(addr)) |= OTG_DOEPCTL0_EPENA |
OTG_DOEPCTL0_USBAEP | OTG_DIEPCTL0_CNAK |
OTG_DOEPCTLX_SD0PID | (type << 18) | max_size;
if (callback) {
usbd_dev->user_callback_ctr[addr][USB_TRANSACTION_OUT] =
(void *)callback;
}
}
}
void dwc_endpoints_reset(usbd_device *usbd_dev)
{
int i;
/* The core resets the endpoints automatically on reset. */
usbd_dev->fifo_mem_top = usbd_dev->fifo_mem_top_ep0;
/* Disable any currently active endpoints */
for (i = 1; i < 4; i++) {
if (REBASE(OTG_DOEPCTL(i)) & OTG_DOEPCTL0_EPENA) {
REBASE(OTG_DOEPCTL(i)) |= OTG_DOEPCTL0_EPDIS;
}
if (REBASE(OTG_DIEPCTL(i)) & OTG_DIEPCTL0_EPENA) {
REBASE(OTG_DIEPCTL(i)) |= OTG_DIEPCTL0_EPDIS;
}
}
/* Flush all tx/rx fifos */
REBASE(OTG_GRSTCTL) = OTG_GRSTCTL_TXFFLSH | OTG_GRSTCTL_TXFNUM_ALL
| OTG_GRSTCTL_RXFFLSH;
}
void dwc_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, uint8_t stall)
{
if (addr == 0) {
if (stall) {
REBASE(OTG_DIEPCTL(addr)) |= OTG_DIEPCTL0_STALL;
} else {
REBASE(OTG_DIEPCTL(addr)) &= ~OTG_DIEPCTL0_STALL;
}
}
if (addr & 0x80) {
addr &= 0x7F;
if (stall) {
REBASE(OTG_DIEPCTL(addr)) |= OTG_DIEPCTL0_STALL;
} else {
REBASE(OTG_DIEPCTL(addr)) &= ~OTG_DIEPCTL0_STALL;
REBASE(OTG_DIEPCTL(addr)) |= OTG_DIEPCTLX_SD0PID;
}
} else {
if (stall) {
REBASE(OTG_DOEPCTL(addr)) |= OTG_DOEPCTL0_STALL;
} else {
REBASE(OTG_DOEPCTL(addr)) &= ~OTG_DOEPCTL0_STALL;
REBASE(OTG_DOEPCTL(addr)) |= OTG_DOEPCTLX_SD0PID;
}
}
}
uint8_t dwc_ep_stall_get(usbd_device *usbd_dev, uint8_t addr)
{
/* Return non-zero if STALL set. */
if (addr & 0x80) {
return (REBASE(OTG_DIEPCTL(addr & 0x7f)) &
OTG_DIEPCTL0_STALL) ? 1 : 0;
} else {
return (REBASE(OTG_DOEPCTL(addr)) &
OTG_DOEPCTL0_STALL) ? 1 : 0;
}
}
void dwc_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, uint8_t nak)
{
/* It does not make sense to force NAK on IN endpoints. */
if (addr & 0x80) {
return;
}
usbd_dev->force_nak[addr] = nak;
if (nak) {
REBASE(OTG_DOEPCTL(addr)) |= OTG_DOEPCTL0_SNAK;
} else {
REBASE(OTG_DOEPCTL(addr)) |= OTG_DOEPCTL0_CNAK;
}
}
uint16_t dwc_ep_write_packet(usbd_device *usbd_dev, uint8_t addr,
const void *buf, uint16_t len)
{
const uint32_t *buf32 = buf;
#if defined(__ARM_ARCH_6M__)
const uint8_t *buf8 = buf;
uint32_t word32;
#endif /* defined(__ARM_ARCH_6M__) */
int i;
addr &= 0x7F;
/* Return if endpoint is already enabled. */
if (REBASE(OTG_DIEPTSIZ(addr)) & OTG_DIEPSIZ0_PKTCNT) {
return 0;
}
/* Enable endpoint for transmission. */
REBASE(OTG_DIEPTSIZ(addr)) = OTG_DIEPSIZ0_PKTCNT | len;
REBASE(OTG_DIEPCTL(addr)) |= OTG_DIEPCTL0_EPENA |
OTG_DIEPCTL0_CNAK;
/* Copy buffer to endpoint FIFO, note - memcpy does not work.
* ARMv7M supports non-word-aligned accesses, ARMv6M does not. */
#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
for (i = len; i > 0; i -= 4) {
REBASE(OTG_FIFO(addr)) = *buf32++;
}
#endif /* defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) */
#if defined(__ARM_ARCH_6M__)
/* Take care of word-aligned and non-word-aligned buffers */
if (((uint32_t)buf8 & 0x3) == 0) {
for (i = len; i > 0; i -= 4) {
REBASE(OTG_FIFO(addr)) = *buf32++;
}
} else {
for (i = len; i > 0; i -= 4) {
memcpy(&word32, buf8, 4);
REBASE(OTG_FIFO(addr)) = word32;
buf8 += 4;
}
}
#endif /* defined(__ARM_ARCH_6M__) */
return len;
}
uint16_t dwc_ep_read_packet(usbd_device *usbd_dev, uint8_t addr,
void *buf, uint16_t len)
{
int i;
uint32_t *buf32 = buf;
#if defined(__ARM_ARCH_6M__)
uint8_t *buf8 = buf;
uint32_t word32;
#endif /* defined(__ARM_ARCH_6M__) */
uint32_t extra;
/* We do not need to know the endpoint address since there is only one
* receive FIFO for all endpoints.
*/
(void) addr;
len = MIN(len, usbd_dev->rxbcnt);
/* ARMv7M supports non-word-aligned accesses, ARMv6M does not. */
#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
for (i = len; i >= 4; i -= 4) {
*buf32++ = REBASE(OTG_FIFO(0));
usbd_dev->rxbcnt -= 4;
}
#endif /* defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) */
#if defined(__ARM_ARCH_6M__)
/* Take care of word-aligned and non-word-aligned buffers */
if (((uint32_t)buf8 & 0x3) == 0) {
for (i = len; i >= 4; i -= 4) {
*buf32++ = REBASE(OTG_FIFO(0));
usbd_dev->rxbcnt -= 4;
}
} else {
for (i = len; i >= 4; i -= 4) {
word32 = REBASE(OTG_FIFO(0));
memcpy(buf8, &word32, 4);
usbd_dev->rxbcnt -= 4;
buf8 += 4;
}
/* buf32 needs to be updated as it is used for extra */
buf32 = (uint32_t *)buf8;
}
#endif /* defined(__ARM_ARCH_6M__) */
if (i) {
extra = REBASE(OTG_FIFO(0));
/* we read 4 bytes from the fifo, so update rxbcnt */
if (usbd_dev->rxbcnt < 4) {
/* Be careful not to underflow (rxbcnt is unsigned) */
usbd_dev->rxbcnt = 0;
} else {
usbd_dev->rxbcnt -= 4;
}
memcpy(buf32, &extra, i);
}
return len;
}
static void dwc_flush_txfifo(usbd_device *usbd_dev, int ep)
{
uint32_t fifo;
/* set IN endpoint NAK */
REBASE(OTG_DIEPCTL(ep)) |= OTG_DIEPCTL0_SNAK;
/* wait for core to respond */
while (!(REBASE(OTG_DIEPINT(ep)) & OTG_DIEPINTX_INEPNE)) {
/* idle */
}
/* get fifo for this endpoint */
fifo = (REBASE(OTG_DIEPCTL(ep)) & OTG_DIEPCTL0_TXFNUM_MASK) >> 22;
/* wait for core to idle */
while (!(REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_AHBIDL)) {
/* idle */
}
/* flush tx fifo */
REBASE(OTG_GRSTCTL) = (fifo << 6) | OTG_GRSTCTL_TXFFLSH;
/* reset packet counter */
REBASE(OTG_DIEPTSIZ(ep)) = 0;
while ((REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_TXFFLSH)) {
/* idle */
}
}
void dwc_poll(usbd_device *usbd_dev)
{
/* Read interrupt status register. */
uint32_t intsts = REBASE(OTG_GINTSTS);
int i;
if (intsts & OTG_GINTSTS_ENUMDNE) {
/* Handle USB RESET condition. */
REBASE(OTG_GINTSTS) = OTG_GINTSTS_ENUMDNE;
usbd_dev->fifo_mem_top = usbd_dev->driver->rx_fifo_size;
_usbd_reset(usbd_dev);
return;
}
/*
* There is no global interrupt flag for transmit complete.
* The XFRC bit must be checked in each OTG_DIEPINT(x).
*/
for (i = 0; i < 4; i++) { /* Iterate over endpoints. */
if (REBASE(OTG_DIEPINT(i)) & OTG_DIEPINTX_XFRC) {
/* Transfer complete. */
if (usbd_dev->user_callback_ctr[i]
[USB_TRANSACTION_IN]) {
usbd_dev->user_callback_ctr[i]
[USB_TRANSACTION_IN](usbd_dev, i);
}
REBASE(OTG_DIEPINT(i)) = OTG_DIEPINTX_XFRC;
}
}
/* Note: RX and TX handled differently in this device. */
if (intsts & OTG_GINTSTS_RXFLVL) {
/* Receive FIFO non-empty. */
uint32_t rxstsp = REBASE(OTG_GRXSTSP);
uint32_t pktsts = rxstsp & OTG_GRXSTSP_PKTSTS_MASK;
uint8_t ep = rxstsp & OTG_GRXSTSP_EPNUM_MASK;
if (pktsts == OTG_GRXSTSP_PKTSTS_SETUP_COMP) {
usbd_dev->user_callback_ctr[ep][USB_TRANSACTION_SETUP] (usbd_dev, ep);
}
if (pktsts == OTG_GRXSTSP_PKTSTS_OUT_COMP
|| pktsts == OTG_GRXSTSP_PKTSTS_SETUP_COMP) {
REBASE(OTG_DOEPTSIZ(ep)) = usbd_dev->doeptsiz[ep];
REBASE(OTG_DOEPCTL(ep)) |= OTG_DOEPCTL0_EPENA |
(usbd_dev->force_nak[ep] ?
OTG_DOEPCTL0_SNAK : OTG_DOEPCTL0_CNAK);
return;
}
if ((pktsts != OTG_GRXSTSP_PKTSTS_OUT) &&
(pktsts != OTG_GRXSTSP_PKTSTS_SETUP)) {
return;
}
uint8_t type;
if (pktsts == OTG_GRXSTSP_PKTSTS_SETUP) {
type = USB_TRANSACTION_SETUP;
} else {
type = USB_TRANSACTION_OUT;
}
if (type == USB_TRANSACTION_SETUP
&& (REBASE(OTG_DIEPTSIZ(ep)) & OTG_DIEPSIZ0_PKTCNT)) {
/* SETUP received but there is still something stuck
* in the transmit fifo. Flush it.
*/
dwc_flush_txfifo(usbd_dev, ep);
}
/* Save packet size for dwc_ep_read_packet(). */
usbd_dev->rxbcnt = (rxstsp & OTG_GRXSTSP_BCNT_MASK) >> 4;
if (type == USB_TRANSACTION_SETUP) {
dwc_ep_read_packet(usbd_dev, ep, &usbd_dev->control_state.req, 8);
} else if (usbd_dev->user_callback_ctr[ep][type]) {
usbd_dev->user_callback_ctr[ep][type] (usbd_dev, ep);
}
/* Discard unread packet data. */
for (i = 0; i < usbd_dev->rxbcnt; i += 4) {
/* There is only one receive FIFO, so use OTG_FIFO(0) */
(void)REBASE(OTG_FIFO(0));
}
usbd_dev->rxbcnt = 0;
}
if (intsts & OTG_GINTSTS_USBSUSP) {
if (usbd_dev->user_callback_suspend) {
usbd_dev->user_callback_suspend();
}
REBASE(OTG_GINTSTS) = OTG_GINTSTS_USBSUSP;
}
if (intsts & OTG_GINTSTS_WKUPINT) {
if (usbd_dev->user_callback_resume) {
usbd_dev->user_callback_resume();
}
REBASE(OTG_GINTSTS) = OTG_GINTSTS_WKUPINT;
}
if (intsts & OTG_GINTSTS_SOF) {
if (usbd_dev->user_callback_sof) {
usbd_dev->user_callback_sof();
}
REBASE(OTG_GINTSTS) = OTG_GINTSTS_SOF;
}
if (usbd_dev->user_callback_sof) {
REBASE(OTG_GINTMSK) |= OTG_GINTMSK_SOFM;
} else {
REBASE(OTG_GINTMSK) &= ~OTG_GINTMSK_SOFM;
}
}
void dwc_disconnect(usbd_device *usbd_dev, bool disconnected)
{
if (disconnected) {
REBASE(OTG_DCTL) |= OTG_DCTL_SDIS;
} else {
REBASE(OTG_DCTL) &= ~OTG_DCTL_SDIS;
}
}

View File

@@ -0,0 +1,39 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
*
* 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/>.
*/
#ifndef __USB_DWC_COMMON_H_
#define __USB_DWC_COMMON_H_
void dwc_set_address(usbd_device *usbd_dev, uint8_t addr);
void dwc_ep_setup(usbd_device *usbd_dev, uint8_t addr, uint8_t type,
uint16_t max_size,
void (*callback)(usbd_device *usbd_dev, uint8_t ep));
void dwc_endpoints_reset(usbd_device *usbd_dev);
void dwc_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, uint8_t stall);
uint8_t dwc_ep_stall_get(usbd_device *usbd_dev, uint8_t addr);
void dwc_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, uint8_t nak);
uint16_t dwc_ep_write_packet(usbd_device *usbd_dev, uint8_t addr,
const void *buf, uint16_t len);
uint16_t dwc_ep_read_packet(usbd_device *usbd_dev, uint8_t addr,
void *buf, uint16_t len);
void dwc_poll(usbd_device *usbd_dev);
void dwc_disconnect(usbd_device *usbd_dev, bool disconnected);
#endif /* __USB_DWC_COMMON_H_ */

View File

@@ -0,0 +1,436 @@
/** @addtogroup usb_file USB peripheral API
* @ingroup peripheral_apis
*
* @sa usb_defines
* @copyright See @ref lgpl_license
*/
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
* Copyright (C) 2015 Kuldeep Singh Dhaka <kuldeepdhaka9@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/>.
*/
#include <string.h>
#include <libopencm3/cm3/common.h>
#include <libopencm3/efm32/memorymap.h>
#include <libopencm3/efm32/cmu.h>
#include <libopencm3/efm32/usb.h>
#include <libopencm3/usb/usbd.h>
#include "usb_private.h"
/**@{*/
/* Receive FIFO size in 32-bit words. */
#define RX_FIFO_SIZE 256
/* FIME: EFM32LG have 6 bidirectonal-endpoint
* problem is "uint32_t doeptsiz[4];" in usb_private.h
* doeptsiz is fixed size of length 4,
* if we it to be of length 6
* possibly, same with "uint8_t force_nak[4];"
*
* solution: remove everything driver specific from usb_private.h
* and move that to there specific driver files.
* maybe a pointer to driver specific data will do the task. */
#define ENDPOINT_COUNT 4
static struct _usbd_device _usbd_dev;
/** Initialize the USB_FS device controller hardware of the STM32. */
static usbd_device *efm32lg_usbd_init(void)
{
/* Enable clock */
CMU_HFCORECLKEN0 |= CMU_HFCORECLKEN0_USB | CMU_HFCORECLKEN0_USBC;
CMU_CMD = CMU_CMD_USBCCLKSEL_HFCLKNODIV;
/* wait till clock not selected */
while (!(CMU_STATUS & CMU_STATUS_USBCHFCLKSEL));
USB_GINTSTS = USB_GINTSTS_MMIS;
USB_CTRL &= ~USB_CTRL_DMPUAP;
USB_ROUTE = USB_ROUTE_DMPUPEN | USB_ROUTE_PHYPEN;
/* Wait for AHB idle. */
while (!(USB_GRSTCTL & USB_GRSTCTL_AHBIDL));
/* Do core soft reset. */
USB_GRSTCTL |= USB_GRSTCTL_CSRST;
while (USB_GRSTCTL & USB_GRSTCTL_CSRST);
/* Force peripheral only mode. */
USB_GUSBCFG |= USB_GUSBCFG_FDMOD | USB_GUSBCFG_TRDT_16BIT;
/* Full speed device. */
USB_DCFG |= USB_DCFG_DSPD;
/* Restart the PHY clock. */
USB_PCGCCTL = 0;
USB_GRXFSIZ = efm32lg_usb_driver.rx_fifo_size;
_usbd_dev.fifo_mem_top = efm32lg_usb_driver.rx_fifo_size;
/* Unmask interrupts for TX and RX. */
USB_GAHBCFG |= USB_GAHBCFG_GLBLINTRMSK;
USB_GINTMSK = USB_GINTMSK_ENUMDNEM |
USB_GINTMSK_RXFLVLM |
USB_GINTMSK_IEPINT |
USB_GINTMSK_USBSUSPM |
USB_GINTMSK_WUIM;
USB_DAINTMSK = 0xF;
USB_DIEPMSK = USB_DIEPMSK_XFRCM;
return &_usbd_dev;
}
static void efm32lg_set_address(usbd_device *usbd_dev, uint8_t addr)
{
(void)usbd_dev;
USB_DCFG = (USB_DCFG & ~USB_DCFG_DAD) | (addr << 4);
}
static void efm32lg_ep_setup(usbd_device *usbd_dev, uint8_t addr, uint8_t type,
uint16_t max_size,
void (*callback) (usbd_device *usbd_dev, uint8_t ep))
{
/*
* Configure endpoint address and type. Allocate FIFO memory for
* endpoint. Install callback function.
*/
uint8_t dir = addr & 0x80;
addr &= 0x7f;
if (addr == 0) { /* For the default control endpoint */
/* Configure IN part. */
if (max_size >= 64) {
USB_DIEP0CTL = USB_DIEP0CTL_MPSIZ_64;
} else if (max_size >= 32) {
USB_DIEP0CTL = USB_DIEP0CTL_MPSIZ_32;
} else if (max_size >= 16) {
USB_DIEP0CTL = USB_DIEP0CTL_MPSIZ_16;
} else {
USB_DIEP0CTL = USB_DIEP0CTL_MPSIZ_8;
}
USB_DIEP0TSIZ =
(max_size & USB_DIEP0TSIZ_XFRSIZ_MASK);
USB_DIEP0CTL |=
USB_DIEP0CTL_EPENA | USB_DIEP0CTL_SNAK;
/* Configure OUT part. */
usbd_dev->doeptsiz[0] = USB_DIEP0TSIZ_STUPCNT_1 |
USB_DIEP0TSIZ_PKTCNT |
(max_size & USB_DIEP0TSIZ_XFRSIZ_MASK);
USB_DOEPx_TSIZ(0) = usbd_dev->doeptsiz[0];
USB_DOEPx_CTL(0) |=
USB_DOEP0CTL_EPENA | USB_DIEP0CTL_SNAK;
USB_GNPTXFSIZ = ((max_size / 4) << 16) |
usbd_dev->driver->rx_fifo_size;
usbd_dev->fifo_mem_top += max_size / 4;
usbd_dev->fifo_mem_top_ep0 = usbd_dev->fifo_mem_top;
return;
}
if (dir) {
USB_DIEPTXF(addr) = ((max_size / 4) << 16) |
usbd_dev->fifo_mem_top;
usbd_dev->fifo_mem_top += max_size / 4;
USB_DIEPx_TSIZ(addr) =
(max_size & USB_DIEP0TSIZ_XFRSIZ_MASK);
USB_DIEPx_CTL(addr) |=
USB_DIEP0CTL_EPENA | USB_DIEP0CTL_SNAK | (type << 18)
| USB_DIEP0CTL_USBAEP | USB_DIEP0CTL_SD0PID
| (addr << 22) | max_size;
if (callback) {
usbd_dev->user_callback_ctr[addr][USB_TRANSACTION_IN] =
(void *)callback;
}
}
if (!dir) {
usbd_dev->doeptsiz[addr] = USB_DIEP0TSIZ_PKTCNT |
(max_size & USB_DIEP0TSIZ_XFRSIZ_MASK);
USB_DOEPx_TSIZ(addr) = usbd_dev->doeptsiz[addr];
USB_DOEPx_CTL(addr) |= USB_DOEP0CTL_EPENA |
USB_DOEP0CTL_USBAEP | USB_DIEP0CTL_CNAK |
USB_DOEP0CTL_SD0PID | (type << 18) | max_size;
if (callback) {
usbd_dev->user_callback_ctr[addr][USB_TRANSACTION_OUT] =
(void *)callback;
}
}
}
static void efm32lg_endpoints_reset(usbd_device *usbd_dev)
{
/* The core resets the endpoints automatically on reset. */
usbd_dev->fifo_mem_top = usbd_dev->fifo_mem_top_ep0;
}
static void efm32lg_ep_stall_set(usbd_device *usbd_dev, uint8_t addr,
uint8_t stall)
{
(void)usbd_dev;
if (addr == 0) {
if (stall) {
USB_DIEPx_CTL(addr) |= USB_DIEP0CTL_STALL;
} else {
USB_DIEPx_CTL(addr) &= ~USB_DIEP0CTL_STALL;
}
}
if (addr & 0x80) {
addr &= 0x7F;
if (stall) {
USB_DIEPx_CTL(addr) |= USB_DIEP0CTL_STALL;
} else {
USB_DIEPx_CTL(addr) &= ~USB_DIEP0CTL_STALL;
USB_DIEPx_CTL(addr) |= USB_DIEP0CTL_SD0PID;
}
} else {
if (stall) {
USB_DOEPx_CTL(addr) |= USB_DOEP0CTL_STALL;
} else {
USB_DOEPx_CTL(addr) &= ~USB_DOEP0CTL_STALL;
USB_DOEPx_CTL(addr) |= USB_DOEP0CTL_SD0PID;
}
}
}
static uint8_t efm32lg_ep_stall_get(usbd_device *usbd_dev, uint8_t addr)
{
(void)usbd_dev;
/* Return non-zero if STALL set. */
if (addr & 0x80) {
return (USB_DIEPx_CTL(addr & 0x7f) &
USB_DIEP0CTL_STALL) ? 1 : 0;
} else {
return (USB_DOEPx_CTL(addr) &
USB_DOEP0CTL_STALL) ? 1 : 0;
}
}
static void efm32lg_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, uint8_t nak)
{
/* It does not make sence to force NAK on IN endpoints. */
if (addr & 0x80) {
return;
}
usbd_dev->force_nak[addr] = nak;
if (nak) {
USB_DOEPx_CTL(addr) |= USB_DOEP0CTL_SNAK;
} else {
USB_DOEPx_CTL(addr) |= USB_DOEP0CTL_CNAK;
}
}
static uint16_t efm32lg_ep_write_packet(usbd_device *usbd_dev, uint8_t addr,
const void *buf, uint16_t len)
{
(void)usbd_dev;
const uint32_t *buf32 = buf;
int i;
addr &= 0x7F;
/* Return if endpoint is already enabled. */
if (USB_DIEPx_TSIZ(addr) & USB_DIEP0TSIZ_PKTCNT) {
return 0;
}
/* Enable endpoint for transmission. */
USB_DIEPx_TSIZ(addr) = USB_DIEP0TSIZ_PKTCNT | len;
USB_DIEPx_CTL(addr) |= USB_DIEP0CTL_EPENA |
USB_DIEP0CTL_CNAK;
volatile uint32_t *fifo = USB_FIFOxD(addr);
/* Copy buffer to endpoint FIFO, note - memcpy does not work */
for (i = len; i > 0; i -= 4) {
*fifo++ = *buf32++;
}
return len;
}
static uint16_t efm32lg_ep_read_packet(usbd_device *usbd_dev, uint8_t addr,
void *buf, uint16_t len)
{
int i;
uint32_t *buf32 = buf;
uint32_t extra;
len = MIN(len, usbd_dev->rxbcnt);
usbd_dev->rxbcnt -= len;
volatile uint32_t *fifo = USB_FIFOxD(addr);
for (i = len; i >= 4; i -= 4) {
*buf32++ = *fifo++;
}
if (i) {
extra = *fifo++;
memcpy(buf32, &extra, i);
}
USB_DOEPx_TSIZ(addr) = usbd_dev->doeptsiz[addr];
USB_DOEPx_CTL(addr) |= USB_DOEP0CTL_EPENA |
(usbd_dev->force_nak[addr] ?
USB_DOEP0CTL_SNAK : USB_DOEP0CTL_CNAK);
return len;
}
static void efm32lg_poll(usbd_device *usbd_dev)
{
/* Read interrupt status register. */
uint32_t intsts = USB_GINTSTS;
int i;
if (intsts & USB_GINTSTS_ENUMDNE) {
/* Handle USB RESET condition. */
USB_GINTSTS = USB_GINTSTS_ENUMDNE;
usbd_dev->fifo_mem_top = usbd_dev->driver->rx_fifo_size;
_usbd_reset(usbd_dev);
return;
}
/* Note: RX and TX handled differently in this device. */
if (intsts & USB_GINTSTS_RXFLVL) {
/* Receive FIFO non-empty. */
uint32_t rxstsp = USB_GRXSTSP;
uint32_t pktsts = rxstsp & USB_GRXSTSP_PKTSTS_MASK;
if ((pktsts != USB_GRXSTSP_PKTSTS_OUT) &&
(pktsts != USB_GRXSTSP_PKTSTS_SETUP)) {
return;
}
uint8_t ep = rxstsp & USB_GRXSTSP_EPNUM_MASK;
uint8_t type;
if (pktsts == USB_GRXSTSP_PKTSTS_SETUP) {
type = USB_TRANSACTION_SETUP;
} else {
type = USB_TRANSACTION_OUT;
}
/* Save packet size for stm32f107_ep_read_packet(). */
usbd_dev->rxbcnt = (rxstsp & USB_GRXSTSP_BCNT_MASK) >> 4;
/*
* FIXME: Why is a delay needed here?
* This appears to fix a problem where the first 4 bytes
* of the DATA OUT stage of a control transaction are lost.
*/
for (i = 0; i < 1000; i++) {
__asm__("nop");
}
if (usbd_dev->user_callback_ctr[ep][type]) {
usbd_dev->user_callback_ctr[ep][type] (usbd_dev, ep);
}
/* Discard unread packet data. */
for (i = 0; i < usbd_dev->rxbcnt; i += 4) {
(void)*USB_FIFOxD(ep);
}
usbd_dev->rxbcnt = 0;
}
/*
* There is no global interrupt flag for transmit complete.
* The XFRC bit must be checked in each USB_DIEPx_INT(x).
*/
for (i = 0; i < ENDPOINT_COUNT; i++) { /* Iterate over endpoints. */
if (USB_DIEPx_INT(i) & USB_DIEP_INT_XFRC) {
/* Transfer complete. */
if (usbd_dev->user_callback_ctr[i]
[USB_TRANSACTION_IN]) {
usbd_dev->user_callback_ctr[i]
[USB_TRANSACTION_IN](usbd_dev, i);
}
USB_DIEPx_INT(i) = USB_DIEP_INT_XFRC;
}
}
if (intsts & USB_GINTSTS_USBSUSP) {
if (usbd_dev->user_callback_suspend) {
usbd_dev->user_callback_suspend();
}
USB_GINTSTS = USB_GINTSTS_USBSUSP;
}
if (intsts & USB_GINTSTS_WKUPINT) {
if (usbd_dev->user_callback_resume) {
usbd_dev->user_callback_resume();
}
USB_GINTSTS = USB_GINTSTS_WKUPINT;
}
if (intsts & USB_GINTSTS_SOF) {
if (usbd_dev->user_callback_sof) {
usbd_dev->user_callback_sof();
}
USB_GINTSTS = USB_GINTSTS_SOF;
}
if (usbd_dev->user_callback_sof) {
USB_GINTMSK |= USB_GINTMSK_SOFM;
} else {
USB_GINTMSK &= ~USB_GINTMSK_SOFM;
}
}
static void efm32lg_disconnect(usbd_device *usbd_dev, bool disconnected)
{
(void)usbd_dev;
if (disconnected) {
USB_DCTL |= USB_DCTL_SDIS;
} else {
USB_DCTL &= ~USB_DCTL_SDIS;
}
}
const struct _usbd_driver efm32lg_usb_driver = {
.init = efm32lg_usbd_init,
.set_address = efm32lg_set_address,
.ep_setup = efm32lg_ep_setup,
.ep_reset = efm32lg_endpoints_reset,
.ep_stall_set = efm32lg_ep_stall_set,
.ep_stall_get = efm32lg_ep_stall_get,
.ep_nak_set = efm32lg_ep_nak_set,
.ep_write_packet = efm32lg_ep_write_packet,
.ep_read_packet = efm32lg_ep_read_packet,
.poll = efm32lg_poll,
.disconnect = efm32lg_disconnect,
.base_address = USB_BASE,
.set_address_before_status = 1,
.rx_fifo_size = RX_FIFO_SIZE,
};
/**@}*/

View File

@@ -0,0 +1,140 @@
/** @addtogroup usb_file USB peripheral API
* @ingroup peripheral_apis
*
* @brief USB Peripheral for Happy Gecko
*
* The Happy Gecko uses the "standard" usb_dwc_otg core.
*
* @sa usb_defines
* @copyright See @ref lgpl_license
*/
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
* Copyright (C) 2018 Seb Holzapfel <schnommus@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/>.
*/
#include <string.h>
#include <libopencm3/cm3/common.h>
#include <libopencm3/efm32/memorymap.h>
#include <libopencm3/efm32/cmu.h>
#include <libopencm3/efm32/usb.h>
#include <libopencm3/usb/usbd.h>
#include <libopencm3/usb/dwc/otg_fs.h>
#include "usb_private.h"
#include "usb_dwc_common.h"
/**@{*/
/* Receive FIFO size in 32-bit words. */
#define RX_FIFO_SIZE 256
/* FIXME: EFM32HG has 6 bidirectional endpoints.
* problem is "uint32_t doeptsiz[4];" in usb_private.h */
#define ENDPOINT_COUNT 4
static struct _usbd_device _usbd_dev;
/** Initialize the USB device controller hardware of the EFM32HG. */
static usbd_device *efm32hg_usbd_init(void)
{
/* Enable peripheral clocks required for USB */
cmu_periph_clock_enable(CMU_USB);
cmu_periph_clock_enable(CMU_USBC);
cmu_periph_clock_enable(CMU_LE);
/* Select LFRCO as LFCCLK clock */
CMU_LFCLKSEL = CMU_LFCLKSEL_LFC_LFRCO;
/* Enable the USBLE peripheral clock (sits on LFCCLK) */
cmu_periph_clock_enable(CMU_USBLE);
/* Calibrate USB based on communications */
CMU_USHFRCOCONF = CMU_USHFRCOCONF_BAND_48MHZ;
/* Enable USHFRCO Clock Recovery mode. */
CMU_USBCRCTRL |= CMU_USBCRCTRL_EN;
/* Select USHFRCO as clock source for USB */
cmu_osc_on(USHFRCO);
cmu_wait_for_osc_ready(USHFRCO);
/* Set up the USB clock source */
cmu_set_usbclk_source(USHFRCO);
cmu_wait_for_usbclk_selected(USHFRCO);
/* Turn off all Low Energy Mode (LEM) features. */
USB_CTRL = 0;
/* Initialize USB core */
USB_ROUTE = USB_ROUTE_PHYPEN; /* Enable PHY pins. */
/* Wait for AHB idle. */
while (!(OTG_FS_GRSTCTL & OTG_GRSTCTL_AHBIDL));
/* Do core soft reset. */
OTG_FS_GRSTCTL |= OTG_GRSTCTL_CSRST;
while (OTG_FS_GRSTCTL & OTG_GRSTCTL_CSRST);
/* Explicitly enable DP pullup (not all cores do this by default) */
OTG_FS_DCTL &= ~OTG_DCTL_SDIS;
/* Force peripheral only mode. */
OTG_FS_GUSBCFG |= OTG_GUSBCFG_FDMOD | OTG_GUSBCFG_TRDT_MASK;
OTG_FS_GINTSTS = OTG_GINTSTS_MMIS;
/* Full speed device. */
OTG_FS_DCFG |= OTG_DCFG_DSPD;
/* Restart the PHY clock. */
OTG_FS_PCGCCTL = 0;
OTG_FS_GRXFSIZ = efm32hg_usb_driver.rx_fifo_size;
_usbd_dev.fifo_mem_top = efm32hg_usb_driver.rx_fifo_size;
/* Unmask interrupts for TX and RX. */
OTG_FS_GAHBCFG |= OTG_GAHBCFG_GINT;
OTG_FS_GINTMSK = OTG_GINTMSK_ENUMDNEM |
OTG_GINTMSK_RXFLVLM |
OTG_GINTMSK_IEPINT |
OTG_GINTMSK_USBSUSPM |
OTG_GINTMSK_WUIM;
OTG_FS_DAINTMSK = 0xF;
OTG_FS_DIEPMSK = OTG_DIEPMSK_XFRCM;
return &_usbd_dev;
}
const struct _usbd_driver efm32hg_usb_driver = {
.init = efm32hg_usbd_init,
.set_address = dwc_set_address,
.ep_setup = dwc_ep_setup,
.ep_reset = dwc_endpoints_reset,
.ep_stall_set = dwc_ep_stall_set,
.ep_stall_get = dwc_ep_stall_get,
.ep_nak_set = dwc_ep_nak_set,
.ep_write_packet = dwc_ep_write_packet,
.ep_read_packet = dwc_ep_read_packet,
.poll = dwc_poll,
.disconnect = dwc_disconnect,
.base_address = USB_OTG_FS_BASE,
.set_address_before_status = 1,
.rx_fifo_size = RX_FIFO_SIZE,
};
/**@}*/

View File

@@ -0,0 +1,100 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
*
* 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/>.
*/
#include <string.h>
#include <libopencm3/cm3/common.h>
#include <libopencm3/stm32/tools.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/usb/usbd.h>
#include <libopencm3/usb/dwc/otg_fs.h>
#include "usb_private.h"
#include "usb_dwc_common.h"
/* Receive FIFO size in 32-bit words. */
#define RX_FIFO_SIZE 128
static usbd_device *stm32f107_usbd_init(void);
static struct _usbd_device usbd_dev;
const struct _usbd_driver stm32f107_usb_driver = {
.init = stm32f107_usbd_init,
.set_address = dwc_set_address,
.ep_setup = dwc_ep_setup,
.ep_reset = dwc_endpoints_reset,
.ep_stall_set = dwc_ep_stall_set,
.ep_stall_get = dwc_ep_stall_get,
.ep_nak_set = dwc_ep_nak_set,
.ep_write_packet = dwc_ep_write_packet,
.ep_read_packet = dwc_ep_read_packet,
.poll = dwc_poll,
.disconnect = dwc_disconnect,
.base_address = USB_OTG_FS_BASE,
.set_address_before_status = 1,
.rx_fifo_size = RX_FIFO_SIZE,
};
/** Initialize the USB device controller hardware of the STM32. */
static usbd_device *stm32f107_usbd_init(void)
{
rcc_periph_clock_enable(RCC_OTGFS);
OTG_FS_GUSBCFG |= OTG_GUSBCFG_PHYSEL;
/* Wait for AHB idle. */
while (!(OTG_FS_GRSTCTL & OTG_GRSTCTL_AHBIDL));
/* Do core soft reset. */
OTG_FS_GRSTCTL |= OTG_GRSTCTL_CSRST;
while (OTG_FS_GRSTCTL & OTG_GRSTCTL_CSRST);
if (OTG_FS_CID >= OTG_CID_HAS_VBDEN) {
/* Enable VBUS detection in device mode and power up the PHY. */
OTG_FS_GCCFG |= OTG_GCCFG_VBDEN | OTG_GCCFG_PWRDWN;
} else {
/* Enable VBUS sensing in device mode and power up the PHY. */
OTG_FS_GCCFG |= OTG_GCCFG_VBUSBSEN | OTG_GCCFG_PWRDWN;
}
/* Explicitly enable DP pullup (not all cores do this by default) */
OTG_FS_DCTL &= ~OTG_DCTL_SDIS;
/* Force peripheral only mode. */
OTG_FS_GUSBCFG |= OTG_GUSBCFG_FDMOD | OTG_GUSBCFG_TRDT_MASK;
OTG_FS_GINTSTS = OTG_GINTSTS_MMIS;
/* Full speed device. */
OTG_FS_DCFG |= OTG_DCFG_DSPD;
/* Restart the PHY clock. */
OTG_FS_PCGCCTL = 0;
OTG_FS_GRXFSIZ = stm32f107_usb_driver.rx_fifo_size;
usbd_dev.fifo_mem_top = stm32f107_usb_driver.rx_fifo_size;
/* Unmask interrupts for TX and RX. */
OTG_FS_GAHBCFG |= OTG_GAHBCFG_GINT;
OTG_FS_GINTMSK = OTG_GINTMSK_ENUMDNEM |
OTG_GINTMSK_RXFLVLM |
OTG_GINTMSK_IEPINT |
OTG_GINTMSK_USBSUSPM |
OTG_GINTMSK_WUIM;
OTG_FS_DAINTMSK = 0xF;
OTG_FS_DIEPMSK = OTG_DIEPMSK_XFRCM;
return &usbd_dev;
}

View File

@@ -0,0 +1,92 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
*
* 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/>.
*/
#include <string.h>
#include <libopencm3/cm3/common.h>
#include <libopencm3/stm32/tools.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/usb/usbd.h>
#include <libopencm3/usb/dwc/otg_hs.h>
#include "usb_private.h"
#include "usb_dwc_common.h"
/* Receive FIFO size in 32-bit words. */
#define RX_FIFO_SIZE 512
static usbd_device *stm32f207_usbd_init(void);
static struct _usbd_device usbd_dev;
const struct _usbd_driver stm32f207_usb_driver = {
.init = stm32f207_usbd_init,
.set_address = dwc_set_address,
.ep_setup = dwc_ep_setup,
.ep_reset = dwc_endpoints_reset,
.ep_stall_set = dwc_ep_stall_set,
.ep_stall_get = dwc_ep_stall_get,
.ep_nak_set = dwc_ep_nak_set,
.ep_write_packet = dwc_ep_write_packet,
.ep_read_packet = dwc_ep_read_packet,
.poll = dwc_poll,
.disconnect = dwc_disconnect,
.base_address = USB_OTG_HS_BASE,
.set_address_before_status = 1,
.rx_fifo_size = RX_FIFO_SIZE,
};
/** Initialize the USB device controller hardware of the STM32. */
static usbd_device *stm32f207_usbd_init(void)
{
rcc_periph_clock_enable(RCC_OTGHS);
OTG_HS_GINTSTS = OTG_GINTSTS_MMIS;
OTG_HS_GUSBCFG |= OTG_GUSBCFG_PHYSEL;
/* Enable VBUS sensing in device mode and power down the PHY. */
OTG_HS_GCCFG |= OTG_GCCFG_VBUSBSEN | OTG_GCCFG_PWRDWN;
/* Wait for AHB idle. */
while (!(OTG_HS_GRSTCTL & OTG_GRSTCTL_AHBIDL));
/* Do core soft reset. */
OTG_HS_GRSTCTL |= OTG_GRSTCTL_CSRST;
while (OTG_HS_GRSTCTL & OTG_GRSTCTL_CSRST);
/* Force peripheral only mode. */
OTG_HS_GUSBCFG |= OTG_GUSBCFG_FDMOD | OTG_GUSBCFG_TRDT_MASK;
/* Full speed device. */
OTG_HS_DCFG |= OTG_DCFG_DSPD;
/* Restart the PHY clock. */
OTG_HS_PCGCCTL = 0;
OTG_HS_GRXFSIZ = stm32f207_usb_driver.rx_fifo_size;
usbd_dev.fifo_mem_top = stm32f207_usb_driver.rx_fifo_size;
/* Unmask interrupts for TX and RX. */
OTG_HS_GAHBCFG |= OTG_GAHBCFG_GINT;
OTG_HS_GINTMSK = OTG_GINTMSK_ENUMDNEM |
OTG_GINTMSK_RXFLVLM |
OTG_GINTMSK_IEPINT |
OTG_GINTMSK_USBSUSPM |
OTG_GINTMSK_WUIM;
OTG_HS_DAINTMSK = 0xF;
OTG_HS_DIEPMSK = OTG_DIEPMSK_XFRCM;
return &usbd_dev;
}

View File

@@ -0,0 +1,20 @@
/*
* This file is part of the libopencm3 project.
*
* 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/>.
*/
#include <libopencm3/usb/hid.h>
/* This stub exists just to trick doxygen. */

View File

@@ -0,0 +1,653 @@
/*
* 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/>.
*/
/**
* @defgroup usb_file USB
*
* @ingroup LM4Fxx
*
* @author @htmlonly &copy; @endhtmlonly 2013
* Alexandru Gagniuc <mr.nuke.me@gmail.com>
*
* \brief <b>libopencm3 LM4F Universal Serial Bus controller </b>
*
* The LM4F USB driver is integrated with the libopencm3 USB stack. You should
* use the generic stack.
*
* To use this driver, tell the linker to look for it:
* @code{.c}
* extern usbd_driver lm4f_usb_driver;
* @endcode
*
* And pass this driver as an argument when initializing the USB stack:
* @code{.c}
* usbd_device *usbd_dev;
* usbd_dev = usbd_init(&lm4f_usb_driver, ...);
* @endcode
*
* <b>Polling or interrupt-driven? </b>
*
* The LM4F USB driver will work fine regardless of whether it is called from an
* interrupt service routine, or from the main program loop.
*
* Polling USB from the main loop requires calling @ref usbd_poll() from the
* main program loop.
* For example:
* @code{.c}
* // Main program loop
* while(1) {
* usbd_poll(usb_dev);
* do_other_stuff();
* ...
* @endcode
*
* Running @ref usbd_poll() from an interrupt has the advantage that it is only
* called when needed, saving CPU cycles for the main program.
*
* RESET, DISCON, RESUME, and SUSPEND interrupts must be enabled, along with the
* interrupts for any endpoint that is used. The EP0_TX interrupt must be
* enabled for the control endpoint to function correctly.
* For example, if EP1IN and EP2OUT are used, then the EP0_TX, EP1_TX, and
* EP2_RX interrupts should be enabled:
* @code{.c}
* // Enable USB interrupts for EP0, EP1IN, and EP2OUT
* ints = USB_INT_RESET | USB_INT_DISCON | USB_INT_RESUME |
* USB_INT_SUSPEND;
* usb_enable_interrupts(ints, USB_EP2_INT, USB_EP0_INT | USB_EP1_INT);
* // Route the interrupts through the NVIC
* nvic_enable_irq(NVIC_USB0_IRQ);
* @endcode
*
* The USB ISR only has to call @ref usbd_poll().
*
* @code{.c}
* void usb0_isr(void)
* {
* usbd_poll(usb_dev);
* }
* @endcode
* @{
*/
/*
* 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;
/**
* \brief Enable Specific USB Interrupts
*
* Enable any combination of interrupts. Interrupts may be OR'ed together to
* enable them with one call. For example, to enable both the RESUME and RESET
* interrupts, pass (USB_INT_RESUME | USB_INT_RESET)
*
* Note that the NVIC must be enabled and properly configured for the interrupt
* to be routed to the CPU.
*
* @param[in] ints Interrupts which to enable. Any combination of interrupts may
* be specified by OR'ing then together
* @param[in] rx_ints Endpoints for which to generate an interrupt when a packet
* packet is received.
* @param[in] tx_ints Endpoints for which to generate an interrupt when a packet
* packet is finished transmitting.
*/
void usb_enable_interrupts(enum usb_interrupt ints,
enum usb_ep_interrupt rx_ints,
enum usb_ep_interrupt tx_ints)
{
USB_IE |= ints;
USB_RXIE |= rx_ints;
USB_TXIE |= tx_ints;
}
/**
* \brief Disable Specific USB Interrupts
*
* Disable any combination of interrupts. Interrupts may be OR'ed together to
* enable them with one call. For example, to disable both the RESUME and RESET
* interrupts, pass (USB_INT_RESUME | USB_INT_RESET)
*
* Note that the NVIC must be enabled and properly configured for the interrupt
* to be routed to the CPU.
*
* @param[in] ints Interrupts which to disable. Any combination of interrupts
* may be specified by OR'ing then together
* @param[in] rx_ints Endpoints for which to stop generating an interrupt when a
* packet packet is received.
* @param[in] tx_ints Endpoints for which to stop generating an interrupt when a
* packet packet is finished transmitting.
*/
void usb_disable_interrupts(enum usb_interrupt ints,
enum usb_ep_interrupt rx_ints,
enum usb_ep_interrupt tx_ints)
{
USB_IE &= ~ints;
USB_RXIE &= ~rx_ints;
USB_TXIE &= ~tx_ints;
}
/**
* @cond private
*/
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, uint8_t addr)
{
(void)usbd_dev;
USB_FADDR = addr & USB_FADDR_FUNCADDR_MASK;
}
static void lm4f_ep_setup(usbd_device *usbd_dev, uint8_t addr, uint8_t type,
uint16_t max_size,
void (*callback) (usbd_device *usbd_dev, uint8_t ep))
{
(void)usbd_dev;
(void)type;
uint8_t reg8;
uint16_t fifo_size;
const bool dir_tx = addr & 0x80;
const uint8_t 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, uint8_t addr,
uint8_t stall)
{
(void)usbd_dev;
const uint8_t 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 uint8_t lm4f_ep_stall_get(usbd_device *usbd_dev, uint8_t addr)
{
(void)usbd_dev;
const uint8_t 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, uint8_t addr, uint8_t nak)
{
(void)usbd_dev;
(void)addr;
(void)nak;
/* NAK's are handled automatically by hardware. Move along. */
}
static uint16_t lm4f_ep_write_packet(usbd_device *usbd_dev, uint8_t addr,
const void *buf, uint16_t len)
{
const uint8_t ep = addr & 0xf;
uint16_t 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;
}
/*
* We don't need to worry about buf not being aligned. If it's not,
* the reads are downgraded to 8-bit in hardware. We lose a bit of
* performance, but we don't crash.
*/
for (i = 0; i < (len & ~0x3); i += 4) {
USB_FIFO32(ep) = *((uint32_t *)(buf + i));
}
if (len & 0x2) {
USB_FIFO16(ep) = *((uint16_t *)(buf + i));
i += 2;
}
if (len & 0x1) {
USB_FIFO8(ep) = *((uint8_t *)(buf + i));
i += 1;
}
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 uint16_t lm4f_ep_read_packet(usbd_device *usbd_dev, uint8_t addr,
void *buf, uint16_t len)
{
(void)usbd_dev;
uint16_t rlen;
uint8_t ep = addr & 0xf;
uint16_t fifoin = USB_RXCOUNT(ep);
rlen = (fifoin > len) ? len : fifoin;
/*
* We don't need to worry about buf not being aligned. If it's not,
* the writes are downgraded to 8-bit in hardware. We lose a bit of
* performance, but we don't crash.
*/
for (len = 0; len < (rlen & ~0x3); len += 4) {
*((uint32_t *)(buf + len)) = USB_FIFO32(ep);
}
if (rlen & 0x2) {
*((uint16_t *)(buf + len)) = USB_FIFO16(ep);
len += 2;
}
if (rlen & 0x1) {
*((uint8_t *)(buf + 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, uint8_t ea);
void (*rx_cb)(usbd_device *usbd_dev, uint8_t 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 uint8_t usb_is = USB_IS;
const uint8_t usb_rxis = USB_RXIS;
const uint8_t usb_txis = USB_TXIS;
const uint8_t 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 (type == USB_TRANSACTION_SETUP) {
lm4f_ep_read_packet(usbd_dev, 0, &usbd_dev->control_state.req, 8);
}
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,
};
/**
* @endcond
*/
/**
* @}
*/

View File

@@ -0,0 +1,20 @@
/*
* This file is part of the libopencm3 project.
*
* 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/>.
*/
#include <libopencm3/usb/midi.h>
/* This stub exists just to trick doxygen. */

View File

@@ -0,0 +1,844 @@
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2013 Weston Schmidt <weston_schmidt@alumni.purdue.edu>
* Copyright (C) 2013 Pavol Rusnak <stick@gk2.sk>
*
* 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/>.
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <libopencm3/cm3/common.h>
#include <libopencm3/usb/usbd.h>
#include <libopencm3/usb/msc.h>
#include "usb_private.h"
/* Definitions of Mass Storage Class from:
*
* (A) "Universal Serial Bus Mass Storage Class Bulk-Only Transport
* Revision 1.0"
*
* (B) "Universal Serial Bus Mass Storage Class Specification Overview
* Revision 1.0"
*/
/* Command Block Wrapper */
#define CBW_SIGNATURE 0x43425355
#define CBW_STATUS_SUCCESS 0
#define CBW_STATUS_FAILED 1
#define CBW_STATUS_PHASE_ERROR 2
/* Command Status Wrapper */
#define CSW_SIGNATURE 0x53425355
#define CSW_STATUS_SUCCESS 0
#define CSW_STATUS_FAILED 1
#define CSW_STATUS_PHASE_ERROR 2
/* Implemented SCSI Commands */
#define SCSI_TEST_UNIT_READY 0x00
#define SCSI_REQUEST_SENSE 0x03
#define SCSI_FORMAT_UNIT 0x04
#define SCSI_READ_6 0x08
#define SCSI_WRITE_6 0x0A
#define SCSI_INQUIRY 0x12
#define SCSI_MODE_SENSE_6 0x1A
#define SCSI_SEND_DIAGNOSTIC 0x1D
#define SCSI_READ_CAPACITY 0x25
#define SCSI_READ_10 0x28
/* Required SCSI Commands */
/* Optional SCSI Commands */
#define SCSI_REPORT_LUNS 0xA0
#define SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E
#define SCSI_MODE_SELECT_6 0x15
#define SCSI_MODE_SELECT_10 0x55
#define SCSI_MODE_SENSE_10 0x5A
#define SCSI_READ_12 0xA8
#define SCSI_READ_FORMAT_CAPACITIES 0x23
#define SCSI_READ_TOC_PMA_ATIP 0x43
#define SCSI_START_STOP_UNIT 0x1B
#define SCSI_SYNCHRONIZE_CACHE 0x35
#define SCSI_VERIFY 0x2F
#define SCSI_WRITE_10 0x2A
#define SCSI_WRITE_12 0xAA
/* The sense codes */
enum sbc_sense_key {
SBC_SENSE_KEY_NO_SENSE = 0x00,
SBC_SENSE_KEY_RECOVERED_ERROR = 0x01,
SBC_SENSE_KEY_NOT_READY = 0x02,
SBC_SENSE_KEY_MEDIUM_ERROR = 0x03,
SBC_SENSE_KEY_HARDWARE_ERROR = 0x04,
SBC_SENSE_KEY_ILLEGAL_REQUEST = 0x05,
SBC_SENSE_KEY_UNIT_ATTENTION = 0x06,
SBC_SENSE_KEY_DATA_PROTECT = 0x07,
SBC_SENSE_KEY_BLANK_CHECK = 0x08,
SBC_SENSE_KEY_VENDOR_SPECIFIC = 0x09,
SBC_SENSE_KEY_COPY_ABORTED = 0x0A,
SBC_SENSE_KEY_ABORTED_COMMAND = 0x0B,
SBC_SENSE_KEY_VOLUME_OVERFLOW = 0x0D,
SBC_SENSE_KEY_MISCOMPARE = 0x0E
};
enum sbc_asc {
SBC_ASC_NO_ADDITIONAL_SENSE_INFORMATION = 0x00,
SBC_ASC_PERIPHERAL_DEVICE_WRITE_FAULT = 0x03,
SBC_ASC_LOGICAL_UNIT_NOT_READY = 0x04,
SBC_ASC_UNRECOVERED_READ_ERROR = 0x11,
SBC_ASC_INVALID_COMMAND_OPERATION_CODE = 0x20,
SBC_ASC_LBA_OUT_OF_RANGE = 0x21,
SBC_ASC_INVALID_FIELD_IN_CDB = 0x24,
SBC_ASC_WRITE_PROTECTED = 0x27,
SBC_ASC_NOT_READY_TO_READY_CHANGE = 0x28,
SBC_ASC_FORMAT_ERROR = 0x31,
SBC_ASC_MEDIUM_NOT_PRESENT = 0x3A
};
enum sbc_ascq {
SBC_ASCQ_NA = 0x00,
SBC_ASCQ_FORMAT_COMMAND_FAILED = 0x01,
SBC_ASCQ_INITIALIZING_COMMAND_REQUIRED = 0x02,
SBC_ASCQ_OPERATION_IN_PROGRESS = 0x07
};
enum trans_event {
EVENT_CBW_VALID,
EVENT_NEED_STATUS
};
struct usb_msc_cbw {
uint32_t dCBWSignature;
uint32_t dCBWTag;
uint32_t dCBWDataTransferLength;
uint8_t bmCBWFlags;
uint8_t bCBWLUN;
uint8_t bCBWCBLength;
uint8_t CBWCB[16];
} __attribute__((packed));
struct usb_msc_csw {
uint32_t dCSWSignature;
uint32_t dCSWTag;
uint32_t dCSWDataResidue;
uint8_t bCSWStatus;
} __attribute__((packed));
struct sbc_sense_info {
uint8_t key;
uint8_t asc;
uint8_t ascq;
};
struct usb_msc_trans {
uint8_t cbw_cnt; /* Read until 31 bytes */
union {
struct usb_msc_cbw cbw;
uint8_t buf[1];
} cbw;
uint32_t bytes_to_read;
uint32_t bytes_to_write;
uint32_t byte_count; /* Either read until equal to
bytes_to_read or write until equal
to bytes_to_write. */
uint32_t lba_start;
uint32_t block_count;
uint32_t current_block;
uint8_t msd_buf[512];
bool csw_valid;
uint8_t csw_sent; /* Write until 13 bytes */
union {
struct usb_msc_csw csw;
uint8_t buf[1];
} csw;
};
struct _usbd_mass_storage {
usbd_device *usbd_dev;
uint8_t ep_in;
uint8_t ep_in_size;
uint8_t ep_out;
uint8_t ep_out_size;
const char *vendor_id;
const char *product_id;
const char *product_revision_level;
uint32_t block_count;
int (*read_block)(uint32_t lba, uint8_t *copy_to);
int (*write_block)(uint32_t lba, const uint8_t *copy_from);
void (*lock)(void);
void (*unlock)(void);
struct usb_msc_trans trans;
struct sbc_sense_info sense;
};
static usbd_mass_storage _mass_storage;
/*-- SCSI Base Responses -----------------------------------------------------*/
static const uint8_t _spc3_inquiry_response[36] = {
0x00, /* Byte 0: Peripheral Qualifier = 0, Peripheral Device Type = 0 */
0x80, /* Byte 1: RMB = 1, Reserved = 0 */
0x04, /* Byte 2: Version = 0 */
0x02, /* Byte 3: Obsolete = 0, NormACA = 0, HiSup = 0, Response Data Format = 2 */
0x20, /* Byte 4: Additional Length (n-4) = 31 + 4 */
0x00, /* Byte 5: SCCS = 0, ACC = 0, TPGS = 0, 3PC = 0, Reserved = 0, Protect = 0 */
0x00, /* Byte 6: BQue = 0, EncServ = 0, VS = 0, MultiP = 0, MChngr = 0, Obsolete = 0, Addr16 = 0 */
0x00, /* Byte 7: Obsolete = 0, Wbus16 = 0, Sync = 0, Linked = 0, CmdQue = 0, VS = 0 */
/* Byte 8 - Byte 15: Vendor Identification */
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
/* Byte 16 - Byte 31: Product Identification */
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
/* Byte 32 - Byte 35: Product Revision Level */
0x20, 0x20, 0x20, 0x20
};
static const uint8_t _spc3_request_sense[18] = {
0x70, /* Byte 0: VALID = 0, Response Code = 112 */
0x00, /* Byte 1: Obsolete = 0 */
0x00, /* Byte 2: Filemark = 0, EOM = 0, ILI = 0, Reserved = 0, Sense Key = 0 */
/* Byte 3 - Byte 6: Information = 0 */
0, 0, 0, 0,
0x0a, /* Byte 7: Additional Sense Length = 10 */
/* Byte 8 - Byte 11: Command Specific Info = 0 */
0, 0, 0, 0,
0x00, /* Byte 12: Additional Sense Code (ASC) = 0 */
0x00, /* Byte 13: Additional Sense Code Qualifier (ASCQ) = 0 */
0x00, /* Byte 14: Field Replaceable Unit Code (FRUC) = 0 */
0x00, /* Byte 15: SKSV = 0, SenseKeySpecific[0] = 0 */
0x00, /* Byte 16: SenseKeySpecific[0] = 0 */
0x00 /* Byte 17: SenseKeySpecific[0] = 0 */
};
/*-- SCSI Layer --------------------------------------------------------------*/
static void set_sbc_status(usbd_mass_storage *ms,
enum sbc_sense_key key,
enum sbc_asc asc,
enum sbc_ascq ascq)
{
ms->sense.key = (uint8_t) key;
ms->sense.asc = (uint8_t) asc;
ms->sense.ascq = (uint8_t) ascq;
}
static void set_sbc_status_good(usbd_mass_storage *ms)
{
set_sbc_status(ms,
SBC_SENSE_KEY_NO_SENSE,
SBC_ASC_NO_ADDITIONAL_SENSE_INFORMATION,
SBC_ASCQ_NA);
}
static uint8_t *get_cbw_buf(struct usb_msc_trans *trans)
{
return &trans->cbw.cbw.CBWCB[0];
}
static void scsi_read_6(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
if (EVENT_CBW_VALID == event) {
uint8_t *buf;
buf = get_cbw_buf(trans);
trans->lba_start = (buf[2] << 8) | buf[3];
trans->block_count = buf[4];
trans->current_block = 0;
/* TODO: Check the lba & block_count for range. */
/* both are in terms of 512 byte blocks, so shift by 9 */
trans->bytes_to_write = trans->block_count << 9;
set_sbc_status_good(ms);
}
}
static void scsi_write_6(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
(void) ms;
if (EVENT_CBW_VALID == event) {
uint8_t *buf;
buf = get_cbw_buf(trans);
trans->lba_start = ((0x1f & buf[1]) << 16)
| (buf[2] << 8) | buf[3];
trans->block_count = buf[4];
trans->current_block = 0;
trans->bytes_to_read = trans->block_count << 9;
}
}
static void scsi_write_10(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
(void) ms;
if (EVENT_CBW_VALID == event) {
uint8_t *buf;
buf = get_cbw_buf(trans);
trans->lba_start = (buf[2] << 24) | (buf[3] << 16) |
(buf[4] << 8) | buf[5];
trans->block_count = (buf[7] << 8) | buf[8];
trans->current_block = 0;
trans->bytes_to_read = trans->block_count << 9;
}
}
static void scsi_read_10(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
if (EVENT_CBW_VALID == event) {
uint8_t *buf;
buf = get_cbw_buf(trans);
trans->lba_start = (buf[2] << 24) | (buf[3] << 16)
| (buf[4] << 8) | buf[5];
trans->block_count = (buf[7] << 8) | buf[8];
/* TODO: Check the lba & block_count for range. */
/* both are in terms of 512 byte blocks, so shift by 9 */
trans->bytes_to_write = trans->block_count << 9;
set_sbc_status_good(ms);
}
}
static void scsi_read_capacity(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
if (EVENT_CBW_VALID == event) {
trans->msd_buf[0] = ms->block_count >> 24;
trans->msd_buf[1] = 0xff & (ms->block_count >> 16);
trans->msd_buf[2] = 0xff & (ms->block_count >> 8);
trans->msd_buf[3] = 0xff & ms->block_count;
/* Block size: 512 */
trans->msd_buf[4] = 0;
trans->msd_buf[5] = 0;
trans->msd_buf[6] = 2;
trans->msd_buf[7] = 0;
trans->bytes_to_write = 8;
set_sbc_status_good(ms);
}
}
static void scsi_format_unit(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
if (EVENT_CBW_VALID == event) {
uint32_t i;
memset(trans->msd_buf, 0, 512);
for (i = 0; i < ms->block_count; i++) {
(*ms->write_block)(i, trans->msd_buf);
}
set_sbc_status_good(ms);
}
}
static void scsi_request_sense(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
if (EVENT_CBW_VALID == event) {
uint8_t *buf;
buf = &trans->cbw.cbw.CBWCB[0];
trans->bytes_to_write = buf[4]; /* allocation length */
memcpy(trans->msd_buf, _spc3_request_sense,
sizeof(_spc3_request_sense));
trans->msd_buf[2] = ms->sense.key;
trans->msd_buf[12] = ms->sense.asc;
trans->msd_buf[13] = ms->sense.ascq;
}
}
static void scsi_mode_sense_6(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
(void) ms;
if (EVENT_CBW_VALID == event) {
#if 0
uint8_t *buf;
uint8_t page_code;
uint8_t allocation_length;
buf = &trans->cbw.cbw.CBWCB[0];
page_code = buf[2];
allocation_length = buf[4];
if (0x1C == page_code) { /* Informational Exceptions */
#endif
trans->bytes_to_write = 4;
trans->msd_buf[0] = 3; /* Num bytes that follow */
trans->msd_buf[1] = 0; /* Medium Type */
trans->msd_buf[2] = 0; /* Device specific param */
trans->csw.csw.dCSWDataResidue = 4;
#if 0
} else if (0x01 == page_code) { /* Error recovery */
} else if (0x3F == page_code) { /* All */
} else {
/* Error */
trans->csw.csw.bCSWStatus = CSW_STATUS_FAILED;
set_sbc_status(ms,
SBC_SENSE_KEY_ILLEGAL_REQUEST,
SBC_ASC_INVALID_FIELD_IN_CDB,
SBC_ASCQ_NA);
}
#endif
}
}
static void scsi_inquiry(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
if (EVENT_CBW_VALID == event) {
uint8_t evpd;
uint8_t *buf;
buf = get_cbw_buf(trans);
evpd = 1 & buf[1];
if (0 == evpd) {
size_t len;
trans->bytes_to_write = sizeof(_spc3_inquiry_response);
memcpy(trans->msd_buf, _spc3_inquiry_response,
sizeof(_spc3_inquiry_response));
len = strlen(ms->vendor_id);
len = MIN(len, 8);
memcpy(&trans->msd_buf[8], ms->vendor_id, len);
len = strlen(ms->product_id);
len = MIN(len, 16);
memcpy(&trans->msd_buf[16], ms->product_id, len);
len = strlen(ms->product_revision_level);
len = MIN(len, 4);
memcpy(&trans->msd_buf[32], ms->product_revision_level,
len);
trans->csw.csw.dCSWDataResidue =
sizeof(_spc3_inquiry_response);
set_sbc_status_good(ms);
} else {
/* TODO: Add VPD 0x83 support */
/* TODO: Add VPD 0x00 support */
}
}
}
static void scsi_command(usbd_mass_storage *ms,
struct usb_msc_trans *trans,
enum trans_event event)
{
if (EVENT_CBW_VALID == event) {
/* Setup the default success */
trans->csw_sent = 0;
trans->csw.csw.dCSWSignature = CSW_SIGNATURE;
trans->csw.csw.dCSWTag = trans->cbw.cbw.dCBWTag;
trans->csw.csw.dCSWDataResidue = 0;
trans->csw.csw.bCSWStatus = CSW_STATUS_SUCCESS;
trans->bytes_to_write = 0;
trans->bytes_to_read = 0;
trans->byte_count = 0;
}
switch (trans->cbw.cbw.CBWCB[0]) {
case SCSI_TEST_UNIT_READY:
case SCSI_SEND_DIAGNOSTIC:
/* Do nothing, just send the success. */
set_sbc_status_good(ms);
break;
case SCSI_FORMAT_UNIT:
scsi_format_unit(ms, trans, event);
break;
case SCSI_REQUEST_SENSE:
scsi_request_sense(ms, trans, event);
break;
case SCSI_MODE_SENSE_6:
scsi_mode_sense_6(ms, trans, event);
break;
case SCSI_READ_6:
scsi_read_6(ms, trans, event);
break;
case SCSI_INQUIRY:
scsi_inquiry(ms, trans, event);
break;
case SCSI_READ_CAPACITY:
scsi_read_capacity(ms, trans, event);
break;
case SCSI_READ_10:
scsi_read_10(ms, trans, event);
break;
case SCSI_WRITE_6:
scsi_write_6(ms, trans, event);
break;
case SCSI_WRITE_10:
scsi_write_10(ms, trans, event);
break;
default:
set_sbc_status(ms, SBC_SENSE_KEY_ILLEGAL_REQUEST,
SBC_ASC_INVALID_COMMAND_OPERATION_CODE,
SBC_ASCQ_NA);
trans->bytes_to_write = 0;
trans->bytes_to_read = 0;
trans->csw.csw.bCSWStatus = CSW_STATUS_FAILED;
break;
}
}
/*-- USB Mass Storage Layer --------------------------------------------------*/
/** @brief Handle the USB 'OUT' requests. */
static void msc_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
{
usbd_mass_storage *ms;
struct usb_msc_trans *trans;
int len, max_len, left;
void *p;
ms = &_mass_storage;
trans = &ms->trans;
/* RX only */
left = sizeof(struct usb_msc_cbw) - trans->cbw_cnt;
if (0 < left) {
max_len = MIN(ms->ep_out_size, left);
p = &trans->cbw.buf[0x1ff & trans->cbw_cnt];
len = usbd_ep_read_packet(usbd_dev, ep, p, max_len);
trans->cbw_cnt += len;
if (sizeof(struct usb_msc_cbw) == trans->cbw_cnt) {
scsi_command(ms, trans, EVENT_CBW_VALID);
if (trans->byte_count < trans->bytes_to_read) {
/* We must wait until there is something to
* read again. */
return;
}
}
}
if (trans->byte_count < trans->bytes_to_read) {
if (0 < trans->block_count) {
if ((0 == trans->byte_count) && (NULL != ms->lock)) {
(*ms->lock)();
}
}
left = trans->bytes_to_read - trans->byte_count;
max_len = MIN(ms->ep_out_size, left);
p = &trans->msd_buf[0x1ff & trans->byte_count];
len = usbd_ep_read_packet(usbd_dev, ep, p, max_len);
trans->byte_count += len;
if (0 < trans->block_count) {
if (0 == (0x1ff & trans->byte_count)) {
uint32_t lba;
lba = trans->lba_start + trans->current_block;
if (0 != (*ms->write_block)(lba,
trans->msd_buf)) {
/* Error */
}
trans->current_block++;
}
}
/* Fix "writes aren't acknowledged" bug on Linux (PR #409) */
if (false == trans->csw_valid) {
scsi_command(ms, trans, EVENT_NEED_STATUS);
trans->csw_valid = true;
}
left = sizeof(struct usb_msc_csw) - trans->csw_sent;
if (0 < left) {
max_len = MIN(ms->ep_out_size, left);
p = &trans->csw.buf[trans->csw_sent];
len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p,
max_len);
trans->csw_sent += len;
}
} else if (trans->byte_count < trans->bytes_to_write) {
if (0 < trans->block_count) {
if ((0 == trans->byte_count) && (NULL != ms->lock)) {
(*ms->lock)();
}
if (0 == (0x1ff & trans->byte_count)) {
uint32_t lba;
lba = trans->lba_start + trans->current_block;
if (0 != (*ms->read_block)(lba,
trans->msd_buf)) {
/* Error */
}
trans->current_block++;
}
}
left = trans->bytes_to_write - trans->byte_count;
max_len = MIN(ms->ep_out_size, left);
p = &trans->msd_buf[0x1ff & trans->byte_count];
len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p, max_len);
trans->byte_count += len;
} else {
if (0 < trans->block_count) {
if (trans->current_block == trans->block_count) {
uint32_t lba;
lba = trans->lba_start + trans->current_block;
if (0 != (*ms->write_block)(lba,
trans->msd_buf)) {
/* Error */
}
trans->current_block = 0;
if (NULL != ms->unlock) {
(*ms->unlock)();
}
}
}
if (false == trans->csw_valid) {
scsi_command(ms, trans, EVENT_NEED_STATUS);
trans->csw_valid = true;
}
left = sizeof(struct usb_msc_csw) - trans->csw_sent;
if (0 < left) {
max_len = MIN(ms->ep_out_size, left);
p = &trans->csw.buf[trans->csw_sent];
len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p,
max_len);
trans->csw_sent += len;
}
}
}
/** @brief Handle the USB 'IN' requests. */
static void msc_data_tx_cb(usbd_device *usbd_dev, uint8_t ep)
{
usbd_mass_storage *ms;
struct usb_msc_trans *trans;
int len, max_len, left;
void *p;
ms = &_mass_storage;
trans = &ms->trans;
if (trans->byte_count < trans->bytes_to_write) {
if (0 < trans->block_count) {
if (0 == (0x1ff & trans->byte_count)) {
uint32_t lba;
lba = trans->lba_start + trans->current_block;
if (0 != (*ms->read_block)(lba,
trans->msd_buf)) {
/* Error */
}
trans->current_block++;
}
}
left = trans->bytes_to_write - trans->byte_count;
max_len = MIN(ms->ep_out_size, left);
p = &trans->msd_buf[0x1ff & trans->byte_count];
len = usbd_ep_write_packet(usbd_dev, ep, p, max_len);
trans->byte_count += len;
} else {
if (0 < trans->block_count) {
if (trans->current_block == trans->block_count) {
trans->current_block = 0;
if (NULL != ms->unlock) {
(*ms->unlock)();
}
}
}
if (false == trans->csw_valid) {
scsi_command(ms, trans, EVENT_NEED_STATUS);
trans->csw_valid = true;
}
left = sizeof(struct usb_msc_csw) - trans->csw_sent;
if (0 < left) {
max_len = MIN(ms->ep_out_size, left);
p = &trans->csw.buf[trans->csw_sent];
len = usbd_ep_write_packet(usbd_dev, ep, p, max_len);
trans->csw_sent += len;
} else if (sizeof(struct usb_msc_csw) == trans->csw_sent) {
/* End of transaction */
trans->lba_start = 0xffffffff;
trans->block_count = 0;
trans->current_block = 0;
trans->cbw_cnt = 0;
trans->bytes_to_read = 0;
trans->bytes_to_write = 0;
trans->byte_count = 0;
trans->csw_sent = 0;
trans->csw_valid = false;
}
}
}
/** @brief Handle various control requests related to the msc storage
* interface.
*/
static enum usbd_request_return_codes
msc_control_request(usbd_device *usbd_dev,
struct usb_setup_data *req, uint8_t **buf, uint16_t *len,
usbd_control_complete_callback *complete)
{
(void)complete;
(void)usbd_dev;
switch (req->bRequest) {
case USB_MSC_REQ_BULK_ONLY_RESET:
/* Do any special reset code here. */
return USBD_REQ_HANDLED;
case USB_MSC_REQ_GET_MAX_LUN:
/* Return the number of LUNs. We use 0. */
*buf[0] = 0;
*len = 1;
return USBD_REQ_HANDLED;
}
return USBD_REQ_NOTSUPP;
}
/** @brief Setup the endpoints to be bulk & register the callbacks. */
static void msc_set_config(usbd_device *usbd_dev, uint16_t wValue)
{
usbd_mass_storage *ms = &_mass_storage;
(void)wValue;
usbd_ep_setup(usbd_dev, ms->ep_in, USB_ENDPOINT_ATTR_BULK,
ms->ep_in_size, msc_data_tx_cb);
usbd_ep_setup(usbd_dev, ms->ep_out, USB_ENDPOINT_ATTR_BULK,
ms->ep_out_size, msc_data_rx_cb);
usbd_register_control_callback(
usbd_dev,
USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE,
USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
msc_control_request);
}
/** @addtogroup usb_msc */
/** @{ */
/** @brief Initializes the USB Mass Storage subsystem.
@note Currently you can only have this profile active.
@param[in] usbd_dev The USB device to associate the Mass Storage with.
@param[in] ep_in The USB 'IN' endpoint.
@param[in] ep_in_size The maximum endpoint size. Valid values: 8, 16, 32 or 64
@param[in] ep_out The USB 'OUT' endpoint.
@param[in] ep_out_size The maximum endpoint size. Valid values: 8, 16, 32 or 64
@param[in] vendor_id The SCSI vendor ID to return. Maximum used length is 8.
@param[in] product_id The SCSI product ID to return. Maximum used length is 16.
@param[in] product_revision_level The SCSI product revision level to return.
Maximum used length is 4.
@param[in] block_count The number of 512-byte blocks available.
@param[in] read_block The function called when the host requests to read a LBA
block. Must _NOT_ be NULL.
@param[in] write_block The function called when the host requests to write a
LBA block. Must _NOT_ be NULL.
@return Pointer to the usbd_mass_storage struct.
*/
usbd_mass_storage *usb_msc_init(usbd_device *usbd_dev,
uint8_t ep_in, uint8_t ep_in_size,
uint8_t ep_out, uint8_t ep_out_size,
const char *vendor_id,
const char *product_id,
const char *product_revision_level,
const uint32_t block_count,
int (*read_block)(uint32_t lba,
uint8_t *copy_to),
int (*write_block)(uint32_t lba,
const uint8_t *copy_from))
{
_mass_storage.usbd_dev = usbd_dev;
_mass_storage.ep_in = ep_in;
_mass_storage.ep_in_size = ep_in_size;
_mass_storage.ep_out = ep_out;
_mass_storage.ep_out_size = ep_out_size;
_mass_storage.vendor_id = vendor_id;
_mass_storage.product_id = product_id;
_mass_storage.product_revision_level = product_revision_level;
_mass_storage.block_count = block_count - 1;
_mass_storage.read_block = read_block;
_mass_storage.write_block = write_block;
_mass_storage.lock = NULL;
_mass_storage.unlock = NULL;
_mass_storage.trans.lba_start = 0xffffffff;
_mass_storage.trans.block_count = 0;
_mass_storage.trans.current_block = 0;
_mass_storage.trans.cbw_cnt = 0;
_mass_storage.trans.bytes_to_read = 0;
_mass_storage.trans.bytes_to_write = 0;
_mass_storage.trans.byte_count = 0;
_mass_storage.trans.csw_valid = false;
_mass_storage.trans.csw_sent = 0;
set_sbc_status_good(&_mass_storage);
usbd_register_set_config_callback(usbd_dev, msc_set_config);
return &_mass_storage;
}
/** @} */

View File

@@ -0,0 +1,167 @@
/** @defgroup usb_private_defines USB Private Structures
@brief <b>Defined Constants and Types for the USB Private Structures</b>
@ingroup USB_defines
@version 1.0.0
@author @htmlonly &copy; @endhtmlonly 2010
Gareth McMullin <gareth@blacksphere.co.nz>
@date 10 March 2013
LGPL License Terms @ref lgpl_license
*/
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz>
*
* 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/>.
*/
/**@{*/
#ifndef __USB_PRIVATE_H
#define __USB_PRIVATE_H
#define MAX_USER_CONTROL_CALLBACK 4
#define MAX_USER_SET_CONFIG_CALLBACK 4
#define MIN(a, b) ((a) < (b) ? (a) : (b))
/** Internal collection of device information. */
struct _usbd_device {
const struct usb_device_descriptor *desc;
const struct usb_config_descriptor *config;
const char * const *strings;
int num_strings;
uint8_t *ctrl_buf; /**< Internal buffer used for control transfers */
uint16_t ctrl_buf_len;
uint8_t current_address;
uint8_t current_config;
uint16_t pm_top; /**< Top of allocated endpoint buffer memory */
/* User callback functions for various USB events */
void (*user_callback_reset)(void);
void (*user_callback_suspend)(void);
void (*user_callback_resume)(void);
void (*user_callback_sof)(void);
struct usb_control_state {
enum {
IDLE, STALLED,
DATA_IN, LAST_DATA_IN, STATUS_IN,
DATA_OUT, LAST_DATA_OUT, STATUS_OUT,
} state;
struct usb_setup_data req __attribute__((aligned(4)));
uint8_t *ctrl_buf;
uint16_t ctrl_len;
usbd_control_complete_callback complete;
bool needs_zlp;
} control_state;
struct user_control_callback {
usbd_control_callback cb;
uint8_t type;
uint8_t type_mask;
} user_control_callback[MAX_USER_CONTROL_CALLBACK];
usbd_endpoint_callback user_callback_ctr[8][3];
/* User callback function for some standard USB function hooks */
usbd_set_config_callback user_callback_set_config[MAX_USER_SET_CONFIG_CALLBACK];
usbd_set_altsetting_callback user_callback_set_altsetting;
const struct _usbd_driver *driver;
/* Extra, non-contiguous user string descriptor index and value */
int extra_string_idx;
const char* extra_string;
/* private driver data */
uint16_t fifo_mem_top;
uint16_t fifo_mem_top_ep0;
uint8_t force_nak[4];
/*
* We keep a backup copy of the out endpoint size registers to restore
* them after a transaction.
*/
uint32_t doeptsiz[4];
/*
* Received packet size for each endpoint. This is assigned in
* stm32f107_poll() which reads the packet status push register GRXSTSP
* for use in stm32f107_ep_read_packet().
*/
uint16_t rxbcnt;
};
enum _usbd_transaction {
USB_TRANSACTION_IN,
USB_TRANSACTION_OUT,
USB_TRANSACTION_SETUP,
};
/* Do not appear to belong to the API, so are omitted from docs */
/**@}*/
void _usbd_control_in(usbd_device *usbd_dev, uint8_t ea);
void _usbd_control_out(usbd_device *usbd_dev, uint8_t ea);
void _usbd_control_setup(usbd_device *usbd_dev, uint8_t ea);
enum usbd_request_return_codes _usbd_standard_request_device(usbd_device *usbd_dev,
struct usb_setup_data *req, uint8_t **buf,
uint16_t *len);
enum usbd_request_return_codes _usbd_standard_request_interface(usbd_device *usbd_dev,
struct usb_setup_data *req, uint8_t **buf,
uint16_t *len);
enum usbd_request_return_codes _usbd_standard_request_endpoint(usbd_device *usbd_dev,
struct usb_setup_data *req, uint8_t **buf,
uint16_t *len);
enum usbd_request_return_codes _usbd_standard_request(usbd_device *usbd_dev, struct usb_setup_data *req,
uint8_t **buf, uint16_t *len);
void _usbd_reset(usbd_device *usbd_dev);
/* Functions provided by the hardware abstraction. */
struct _usbd_driver {
usbd_device *(*init)(void);
void (*set_address)(usbd_device *usbd_dev, uint8_t addr);
void (*ep_setup)(usbd_device *usbd_dev, uint8_t addr, uint8_t type,
uint16_t max_size, usbd_endpoint_callback cb);
void (*ep_reset)(usbd_device *usbd_dev);
void (*ep_stall_set)(usbd_device *usbd_dev, uint8_t addr,
uint8_t stall);
void (*ep_nak_set)(usbd_device *usbd_dev, uint8_t addr, uint8_t nak);
uint8_t (*ep_stall_get)(usbd_device *usbd_dev, uint8_t addr);
uint16_t (*ep_write_packet)(usbd_device *usbd_dev, uint8_t addr,
const void *buf, uint16_t len);
uint16_t (*ep_read_packet)(usbd_device *usbd_dev, uint8_t addr,
void *buf, uint16_t len);
void (*poll)(usbd_device *usbd_dev);
void (*disconnect)(usbd_device *usbd_dev, bool disconnected);
uint32_t base_address;
bool set_address_before_status;
uint16_t rx_fifo_size;
};
#endif

View File

@@ -0,0 +1,636 @@
/** @defgroup usb_standard_file Generic USB Standard Request Interface
@ingroup USB
@brief <b>Generic USB Standard Request Interface</b>
@version 1.0.0
@author @htmlonly &copy; @endhtmlonly 2010
Gareth McMullin <gareth@blacksphere.co.nz>
@date 10 March 2013
LGPL License Terms @ref lgpl_license
*/
/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2010 Gareth McMullin <gareth@blacksphere.co.nz>
*
* 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/>.
*/
/**@{*/
#include <string.h>
#include <libopencm3/usb/usbd.h>
#include "usb_private.h"
int usbd_register_set_config_callback(usbd_device *usbd_dev,
usbd_set_config_callback callback)
{
int i;
for (i = 0; i < MAX_USER_SET_CONFIG_CALLBACK; i++) {
if (usbd_dev->user_callback_set_config[i]) {
if (usbd_dev->user_callback_set_config[i] == callback) {
return 0;
}
continue;
}
usbd_dev->user_callback_set_config[i] = callback;
return 0;
}
return -1;
}
void usbd_register_set_altsetting_callback(usbd_device *usbd_dev,
usbd_set_altsetting_callback callback)
{
usbd_dev->user_callback_set_altsetting = callback;
}
static uint16_t build_config_descriptor(usbd_device *usbd_dev,
uint8_t index, uint8_t *buf, uint16_t len)
{
uint8_t *tmpbuf = buf;
const struct usb_config_descriptor *cfg = &usbd_dev->config[index];
uint16_t count, total = 0, totallen = 0;
uint16_t i, j, k;
memcpy(buf, cfg, count = MIN(len, cfg->bLength));
buf += count;
len -= count;
total += count;
totallen += cfg->bLength;
/* For each interface... */
for (i = 0; i < cfg->bNumInterfaces; i++) {
/* Interface Association Descriptor, if any */
if (cfg->interface[i].iface_assoc) {
const struct usb_iface_assoc_descriptor *assoc =
cfg->interface[i].iface_assoc;
memcpy(buf, assoc, count = MIN(len, assoc->bLength));
buf += count;
len -= count;
total += count;
totallen += assoc->bLength;
}
/* For each alternate setting... */
for (j = 0; j < cfg->interface[i].num_altsetting; j++) {
const struct usb_interface_descriptor *iface =
&cfg->interface[i].altsetting[j];
/* Copy interface descriptor. */
memcpy(buf, iface, count = MIN(len, iface->bLength));
buf += count;
len -= count;
total += count;
totallen += iface->bLength;
/* Copy extra bytes (function descriptors). */
if (iface->extra) {
memcpy(buf, iface->extra,
count = MIN(len, iface->extralen));
buf += count;
len -= count;
total += count;
totallen += iface->extralen;
}
/* For each endpoint... */
for (k = 0; k < iface->bNumEndpoints; k++) {
const struct usb_endpoint_descriptor *ep =
&iface->endpoint[k];
memcpy(buf, ep, count = MIN(len, ep->bLength));
buf += count;
len -= count;
total += count;
totallen += ep->bLength;
/* Copy extra bytes (class specific). */
if (ep->extra) {
memcpy(buf, ep->extra,
count = MIN(len, ep->extralen));
buf += count;
len -= count;
total += count;
totallen += ep->extralen;
}
}
}
}
/* Fill in wTotalLength.
* Note that tmpbuf is sometimes not halfword-aligned */
memcpy((tmpbuf + 2), &totallen, sizeof(uint16_t));
return total;
}
static int usb_descriptor_type(uint16_t wValue)
{
return wValue >> 8;
}
static int usb_descriptor_index(uint16_t wValue)
{
return wValue & 0xFF;
}
static enum usbd_request_return_codes
usb_standard_get_descriptor(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
int i, array_idx, descr_idx;
struct usb_string_descriptor *sd;
descr_idx = usb_descriptor_index(req->wValue);
switch (usb_descriptor_type(req->wValue)) {
case USB_DT_DEVICE:
*buf = (uint8_t *) usbd_dev->desc;
*len = MIN(*len, usbd_dev->desc->bLength);
return USBD_REQ_HANDLED;
case USB_DT_CONFIGURATION:
*buf = usbd_dev->ctrl_buf;
*len = build_config_descriptor(usbd_dev, descr_idx, *buf, *len);
return USBD_REQ_HANDLED;
case USB_DT_STRING:
sd = (struct usb_string_descriptor *)usbd_dev->ctrl_buf;
if (descr_idx == 0) {
/* Send sane Language ID descriptor... */
sd->wData[0] = USB_LANGID_ENGLISH_US;
sd->bLength = sizeof(sd->bLength) +
sizeof(sd->bDescriptorType) +
sizeof(sd->wData[0]);
*len = MIN(*len, sd->bLength);
} else if (descr_idx == usbd_dev->extra_string_idx) {
/* This string is returned as UTF16, hence the
* multiplication
*/
sd->bLength = strlen(usbd_dev->extra_string) * 2 +
sizeof(sd->bLength) +
sizeof(sd->bDescriptorType);
*len = MIN(*len, sd->bLength);
for (i = 0; i < (*len / 2) - 1; i++) {
sd->wData[i] =
usbd_dev->extra_string[i];
}
} else {
array_idx = descr_idx - 1;
if (!usbd_dev->strings) {
/* Device doesn't support strings. */
return USBD_REQ_NOTSUPP;
}
/* Check that string index is in range. */
if (array_idx >= usbd_dev->num_strings) {
return USBD_REQ_NOTSUPP;
}
/* Strings with Language ID differnet from
* USB_LANGID_ENGLISH_US are not supported */
if (req->wIndex != USB_LANGID_ENGLISH_US) {
return USBD_REQ_NOTSUPP;
}
/* This string is returned as UTF16, hence the
* multiplication
*/
sd->bLength = strlen(usbd_dev->strings[array_idx]) * 2 +
sizeof(sd->bLength) +
sizeof(sd->bDescriptorType);
*len = MIN(*len, sd->bLength);
for (i = 0; i < (*len / 2) - 1; i++) {
sd->wData[i] =
usbd_dev->strings[array_idx][i];
}
}
sd->bDescriptorType = USB_DT_STRING;
*buf = (uint8_t *)sd;
return USBD_REQ_HANDLED;
}
return USBD_REQ_NOTSUPP;
}
static enum usbd_request_return_codes
usb_standard_set_address(usbd_device *usbd_dev,
struct usb_setup_data *req, uint8_t **buf,
uint16_t *len)
{
(void)req;
(void)buf;
(void)len;
/* The actual address is only latched at the STATUS IN stage. */
if ((req->bmRequestType != 0) || (req->wValue >= 128)) {
return USBD_REQ_NOTSUPP;
}
usbd_dev->current_address = req->wValue;
/*
* Special workaround for STM32F10[57] that require the address
* to be set here. This is undocumented!
*/
if (usbd_dev->driver->set_address_before_status) {
usbd_dev->driver->set_address(usbd_dev, req->wValue);
}
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_set_configuration(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
unsigned i;
int found_index = -1;
const struct usb_config_descriptor *cfg;
(void)req;
(void)buf;
(void)len;
if (req->wValue > 0) {
for (i = 0; i < usbd_dev->desc->bNumConfigurations; i++) {
if (req->wValue
== usbd_dev->config[i].bConfigurationValue) {
found_index = i;
break;
}
}
if (found_index < 0) {
return USBD_REQ_NOTSUPP;
}
}
usbd_dev->current_config = found_index + 1;
if (usbd_dev->current_config > 0) {
cfg = &usbd_dev->config[usbd_dev->current_config - 1];
/* reset all alternate settings configuration */
for (i = 0; i < cfg->bNumInterfaces; i++) {
if (cfg->interface[i].cur_altsetting) {
*cfg->interface[i].cur_altsetting = 0;
}
}
}
/* Reset all endpoints. */
usbd_dev->driver->ep_reset(usbd_dev);
if (usbd_dev->user_callback_set_config[0]) {
/*
* Flush control callbacks. These will be reregistered
* by the user handler.
*/
for (i = 0; i < MAX_USER_CONTROL_CALLBACK; i++) {
usbd_dev->user_control_callback[i].cb = NULL;
}
for (i = 0; i < MAX_USER_SET_CONFIG_CALLBACK; i++) {
if (usbd_dev->user_callback_set_config[i]) {
usbd_dev->user_callback_set_config[i](usbd_dev,
req->wValue);
}
}
}
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_get_configuration(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
(void)req;
if (*len > 1) {
*len = 1;
}
if (usbd_dev->current_config > 0) {
const struct usb_config_descriptor *cfg =
&usbd_dev->config[usbd_dev->current_config - 1];
(*buf)[0] = cfg->bConfigurationValue;
} else {
(*buf)[0] = 0;
}
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_set_interface(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
const struct usb_config_descriptor *cfx =
&usbd_dev->config[usbd_dev->current_config - 1];
const struct usb_interface *iface;
(void)buf;
if (req->wIndex >= cfx->bNumInterfaces) {
return USBD_REQ_NOTSUPP;
}
iface = &cfx->interface[req->wIndex];
if (req->wValue >= iface->num_altsetting) {
return USBD_REQ_NOTSUPP;
}
if (iface->cur_altsetting) {
*iface->cur_altsetting = req->wValue;
} else if (req->wValue > 0) {
return USBD_REQ_NOTSUPP;
}
if (usbd_dev->user_callback_set_altsetting) {
usbd_dev->user_callback_set_altsetting(usbd_dev,
req->wIndex,
req->wValue);
}
*len = 0;
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_get_interface(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
uint8_t *cur_altsetting;
const struct usb_config_descriptor *cfx =
&usbd_dev->config[usbd_dev->current_config - 1];
if (req->wIndex >= cfx->bNumInterfaces) {
return USBD_REQ_NOTSUPP;
}
*len = 1;
cur_altsetting = cfx->interface[req->wIndex].cur_altsetting;
(*buf)[0] = (cur_altsetting) ? *cur_altsetting : 0;
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_device_get_status(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
(void)usbd_dev;
(void)req;
/* bit 0: self powered */
/* bit 1: remote wakeup */
if (*len > 2) {
*len = 2;
}
(*buf)[0] = 0;
(*buf)[1] = 0;
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_interface_get_status(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
(void)usbd_dev;
(void)req;
/* not defined */
if (*len > 2) {
*len = 2;
}
(*buf)[0] = 0;
(*buf)[1] = 0;
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_endpoint_get_status(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
(void)req;
if (*len > 2) {
*len = 2;
}
(*buf)[0] = usbd_ep_stall_get(usbd_dev, req->wIndex) ? 1 : 0;
(*buf)[1] = 0;
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_endpoint_stall(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
(void)buf;
(void)len;
usbd_ep_stall_set(usbd_dev, req->wIndex, 1);
return USBD_REQ_HANDLED;
}
static enum usbd_request_return_codes
usb_standard_endpoint_unstall(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
(void)buf;
(void)len;
usbd_ep_stall_set(usbd_dev, req->wIndex, 0);
return USBD_REQ_HANDLED;
}
/* Do not appear to belong to the API, so are omitted from docs */
/**@}*/
enum usbd_request_return_codes
_usbd_standard_request_device(usbd_device *usbd_dev,
struct usb_setup_data *req, uint8_t **buf,
uint16_t *len)
{
enum usbd_request_return_codes (*command)(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len) = NULL;
switch (req->bRequest) {
case USB_REQ_CLEAR_FEATURE:
case USB_REQ_SET_FEATURE:
if (req->wValue == USB_FEAT_DEVICE_REMOTE_WAKEUP) {
/* Device wakeup code goes here. */
}
if (req->wValue == USB_FEAT_TEST_MODE) {
/* Test mode code goes here. */
}
break;
case USB_REQ_SET_ADDRESS:
/*
* SET ADDRESS is an exception.
* It is only processed at STATUS stage.
*/
command = usb_standard_set_address;
break;
case USB_REQ_SET_CONFIGURATION:
command = usb_standard_set_configuration;
break;
case USB_REQ_GET_CONFIGURATION:
command = usb_standard_get_configuration;
break;
case USB_REQ_GET_DESCRIPTOR:
command = usb_standard_get_descriptor;
break;
case USB_REQ_GET_STATUS:
/*
* GET_STATUS always responds with zero reply.
* The application may override this behaviour.
*/
command = usb_standard_device_get_status;
break;
case USB_REQ_SET_DESCRIPTOR:
/* SET_DESCRIPTOR is optional and not implemented. */
break;
}
if (!command) {
return USBD_REQ_NOTSUPP;
}
return command(usbd_dev, req, buf, len);
}
enum usbd_request_return_codes
_usbd_standard_request_interface(usbd_device *usbd_dev,
struct usb_setup_data *req, uint8_t **buf,
uint16_t *len)
{
enum usbd_request_return_codes (*command)(usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len) = NULL;
switch (req->bRequest) {
case USB_REQ_CLEAR_FEATURE:
case USB_REQ_SET_FEATURE:
/* not defined */
break;
case USB_REQ_GET_INTERFACE:
command = usb_standard_get_interface;
break;
case USB_REQ_SET_INTERFACE:
command = usb_standard_set_interface;
break;
case USB_REQ_GET_STATUS:
command = usb_standard_interface_get_status;
break;
}
if (!command) {
return USBD_REQ_NOTSUPP;
}
return command(usbd_dev, req, buf, len);
}
enum usbd_request_return_codes
_usbd_standard_request_endpoint(usbd_device *usbd_dev,
struct usb_setup_data *req, uint8_t **buf,
uint16_t *len)
{
enum usbd_request_return_codes (*command) (usbd_device *usbd_dev,
struct usb_setup_data *req,
uint8_t **buf, uint16_t *len) = NULL;
switch (req->bRequest) {
case USB_REQ_CLEAR_FEATURE:
if (req->wValue == USB_FEAT_ENDPOINT_HALT) {
command = usb_standard_endpoint_unstall;
}
break;
case USB_REQ_SET_FEATURE:
if (req->wValue == USB_FEAT_ENDPOINT_HALT) {
command = usb_standard_endpoint_stall;
}
break;
case USB_REQ_GET_STATUS:
command = usb_standard_endpoint_get_status;
break;
case USB_REQ_SET_SYNCH_FRAME:
/* FIXME: SYNCH_FRAME is not implemented. */
/*
* SYNCH_FRAME is used for synchronization of isochronous
* endpoints which are not yet implemented.
*/
break;
}
if (!command) {
return USBD_REQ_NOTSUPP;
}
return command(usbd_dev, req, buf, len);
}
enum usbd_request_return_codes
_usbd_standard_request(usbd_device *usbd_dev, struct usb_setup_data *req,
uint8_t **buf, uint16_t *len)
{
/* FIXME: Have class/vendor requests as well. */
if ((req->bmRequestType & USB_REQ_TYPE_TYPE) != USB_REQ_TYPE_STANDARD) {
return USBD_REQ_NOTSUPP;
}
switch (req->bmRequestType & USB_REQ_TYPE_RECIPIENT) {
case USB_REQ_TYPE_DEVICE:
return _usbd_standard_request_device(usbd_dev, req, buf, len);
case USB_REQ_TYPE_INTERFACE:
return _usbd_standard_request_interface(usbd_dev, req,
buf, len);
case USB_REQ_TYPE_ENDPOINT:
return _usbd_standard_request_endpoint(usbd_dev, req, buf, len);
default:
return USBD_REQ_NOTSUPP;
}
}