git subrepo clone https://github.com/libopencm3/libopencm3
subrepo: subdir: "libopencm3" merged: "f5813a54" upstream: origin: "https://github.com/libopencm3/libopencm3" branch: "master" commit: "f5813a54" git-subrepo: version: "0.4.3" origin: "???" commit: "???"
This commit is contained in:
174
libopencm3/lib/usb/usb.c
Normal file
174
libopencm3/lib/usb/usb.c
Normal 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 © @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);
|
||||
}
|
||||
|
||||
/**@}*/
|
||||
|
||||
20
libopencm3/lib/usb/usb_audio.c
Normal file
20
libopencm3/lib/usb/usb_audio.c
Normal 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. */
|
||||
20
libopencm3/lib/usb/usb_cdc.c
Normal file
20
libopencm3/lib/usb/usb_cdc.c
Normal 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. */
|
||||
316
libopencm3/lib/usb/usb_control.c
Normal file
316
libopencm3/lib/usb/usb_control.c
Normal 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 © @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);
|
||||
}
|
||||
}
|
||||
|
||||
448
libopencm3/lib/usb/usb_dwc_common.c
Normal file
448
libopencm3/lib/usb/usb_dwc_common.c
Normal 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;
|
||||
}
|
||||
}
|
||||
39
libopencm3/lib/usb/usb_dwc_common.h
Normal file
39
libopencm3/lib/usb/usb_dwc_common.h
Normal 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_ */
|
||||
436
libopencm3/lib/usb/usb_efm32.c
Normal file
436
libopencm3/lib/usb/usb_efm32.c
Normal 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,
|
||||
};
|
||||
|
||||
/**@}*/
|
||||
140
libopencm3/lib/usb/usb_efm32hg.c
Normal file
140
libopencm3/lib/usb/usb_efm32hg.c
Normal 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,
|
||||
};
|
||||
|
||||
/**@}*/
|
||||
100
libopencm3/lib/usb/usb_f107.c
Normal file
100
libopencm3/lib/usb/usb_f107.c
Normal 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;
|
||||
}
|
||||
92
libopencm3/lib/usb/usb_f207.c
Normal file
92
libopencm3/lib/usb/usb_f207.c
Normal 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;
|
||||
}
|
||||
20
libopencm3/lib/usb/usb_hid.c
Normal file
20
libopencm3/lib/usb/usb_hid.c
Normal 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. */
|
||||
653
libopencm3/lib/usb/usb_lm4f.c
Normal file
653
libopencm3/lib/usb/usb_lm4f.c
Normal 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 © @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
20
libopencm3/lib/usb/usb_midi.c
Normal file
20
libopencm3/lib/usb/usb_midi.c
Normal 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. */
|
||||
844
libopencm3/lib/usb/usb_msc.c
Normal file
844
libopencm3/lib/usb/usb_msc.c
Normal 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;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
167
libopencm3/lib/usb/usb_private.h
Normal file
167
libopencm3/lib/usb/usb_private.h
Normal 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 © @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
|
||||
|
||||
636
libopencm3/lib/usb/usb_standard.c
Normal file
636
libopencm3/lib/usb/usb_standard.c
Normal 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 © @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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user