Add software
This commit is contained in:
@ -0,0 +1,175 @@
|
||||
/** @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"
|
||||
|
||||
/**
|
||||
* Main initialization entry point.
|
||||
*
|
||||
* Initialize the USB firmware library to implement the USB device described
|
||||
* by the descriptors provided.
|
||||
*
|
||||
* It is required that the 48MHz USB clock is already available.
|
||||
*
|
||||
* @param driver TODO
|
||||
* @param dev Pointer to USB device descriptor. This must not be changed while
|
||||
* the device is in use.
|
||||
* @param conf Pointer to array of USB configuration descriptors. These must
|
||||
* not be changed while the device is in use. The length of this
|
||||
* array is determined by the bNumConfigurations field in the
|
||||
* device descriptor.
|
||||
* @param strings TODO
|
||||
* @param control_buffer Pointer to array that would hold the data
|
||||
* received during control requests with DATA
|
||||
* stage
|
||||
* @param control_buffer_size Size of control_buffer
|
||||
* @return Zero on success (currently cannot fail).
|
||||
*/
|
||||
usbd_device *usbd_init(const usbd_driver *driver,
|
||||
const struct usb_device_descriptor *dev,
|
||||
const struct usb_config_descriptor *conf,
|
||||
const char **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->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;
|
||||
|
||||
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_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, 64, 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);
|
||||
}
|
||||
|
||||
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,
|
||||
void (*callback)(usbd_device *usbd_dev, uint8_t ep))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/**@}*/
|
||||
|
@ -0,0 +1,288 @@
|
||||
/** @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;
|
||||
}
|
||||
|
||||
/* 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 = LAST_DATA_IN;
|
||||
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 int 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 (usbd_dev->control_state.ctrl_len) {
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
if (usbd_ep_read_packet(usbd_dev, 0, req, 8) != 8) {
|
||||
stall_transaction(usbd_dev);
|
||||
return;
|
||||
}
|
||||
|
||||
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))) {
|
||||
/* Got 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 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 <libopencm3/stm32/f1/rcc.h>
|
||||
#include <libopencm3/cm3/common.h>
|
||||
#include <libopencm3/stm32/tools.h>
|
||||
#include <libopencm3/stm32/usb.h>
|
||||
#include <libopencm3/usb/usbd.h>
|
||||
#include "usb_private.h"
|
||||
|
||||
static usbd_device *stm32f103_usbd_init(void);
|
||||
static void stm32f103_set_address(usbd_device *usbd_dev, uint8_t addr);
|
||||
static void stm32f103_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));
|
||||
static void stm32f103_endpoints_reset(usbd_device *usbd_dev);
|
||||
static void stm32f103_ep_stall_set(usbd_device *usbd_dev, uint8_t addr,
|
||||
uint8_t stall);
|
||||
static uint8_t stm32f103_ep_stall_get(usbd_device *usbd_dev, uint8_t addr);
|
||||
static void stm32f103_ep_nak_set(usbd_device *usbd_dev, uint8_t addr,
|
||||
uint8_t nak);
|
||||
static uint16_t stm32f103_ep_write_packet(usbd_device *usbd_dev, uint8_t addr,
|
||||
const void *buf, uint16_t len);
|
||||
static uint16_t stm32f103_ep_read_packet(usbd_device *usbd_dev, uint8_t addr,
|
||||
void *buf, uint16_t len);
|
||||
static void stm32f103_poll(usbd_device *usbd_dev);
|
||||
|
||||
static uint8_t force_nak[8];
|
||||
static struct _usbd_device usbd_dev;
|
||||
|
||||
const struct _usbd_driver stm32f103_usb_driver = {
|
||||
.init = stm32f103_usbd_init,
|
||||
.set_address = stm32f103_set_address,
|
||||
.ep_setup = stm32f103_ep_setup,
|
||||
.ep_reset = stm32f103_endpoints_reset,
|
||||
.ep_stall_set = stm32f103_ep_stall_set,
|
||||
.ep_stall_get = stm32f103_ep_stall_get,
|
||||
.ep_nak_set = stm32f103_ep_nak_set,
|
||||
.ep_write_packet = stm32f103_ep_write_packet,
|
||||
.ep_read_packet = stm32f103_ep_read_packet,
|
||||
.poll = stm32f103_poll,
|
||||
};
|
||||
|
||||
/** Initialize the USB device controller hardware of the STM32. */
|
||||
static usbd_device *stm32f103_usbd_init(void)
|
||||
{
|
||||
rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_USBEN);
|
||||
SET_REG(USB_CNTR_REG, 0);
|
||||
SET_REG(USB_BTABLE_REG, 0);
|
||||
SET_REG(USB_ISTR_REG, 0);
|
||||
|
||||
/* Enable RESET, SUSPEND, RESUME and CTR interrupts. */
|
||||
SET_REG(USB_CNTR_REG, USB_CNTR_RESETM | USB_CNTR_CTRM |
|
||||
USB_CNTR_SUSPM | USB_CNTR_WKUPM);
|
||||
return &usbd_dev;
|
||||
}
|
||||
|
||||
static void stm32f103_set_address(usbd_device *dev, uint8_t addr)
|
||||
{
|
||||
(void)dev;
|
||||
/* Set device address and enable. */
|
||||
SET_REG(USB_DADDR_REG, (addr & USB_DADDR_ADDR) | USB_DADDR_ENABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the receive buffer size for a given USB endpoint.
|
||||
*
|
||||
* @param ep Index of endpoint to configure.
|
||||
* @param size Size in bytes of the RX buffer.
|
||||
*/
|
||||
static void usb_set_ep_rx_bufsize(usbd_device *dev, uint8_t ep, uint32_t size)
|
||||
{
|
||||
(void)dev;
|
||||
if (size > 62) {
|
||||
if (size & 0x1f) {
|
||||
size -= 32;
|
||||
}
|
||||
USB_SET_EP_RX_COUNT(ep, (size << 5) | 0x8000);
|
||||
} else {
|
||||
if (size & 1) {
|
||||
size++;
|
||||
}
|
||||
USB_SET_EP_RX_COUNT(ep, size << 10);
|
||||
}
|
||||
}
|
||||
|
||||
static void stm32f103_ep_setup(usbd_device *dev, uint8_t addr, uint8_t type,
|
||||
uint16_t max_size,
|
||||
void (*callback) (usbd_device *usbd_dev,
|
||||
uint8_t ep))
|
||||
{
|
||||
/* Translate USB standard type codes to STM32. */
|
||||
const uint16_t typelookup[] = {
|
||||
[USB_ENDPOINT_ATTR_CONTROL] = USB_EP_TYPE_CONTROL,
|
||||
[USB_ENDPOINT_ATTR_ISOCHRONOUS] = USB_EP_TYPE_ISO,
|
||||
[USB_ENDPOINT_ATTR_BULK] = USB_EP_TYPE_BULK,
|
||||
[USB_ENDPOINT_ATTR_INTERRUPT] = USB_EP_TYPE_INTERRUPT,
|
||||
};
|
||||
uint8_t dir = addr & 0x80;
|
||||
addr &= 0x7f;
|
||||
|
||||
/* Assign address. */
|
||||
USB_SET_EP_ADDR(addr, addr);
|
||||
USB_SET_EP_TYPE(addr, typelookup[type]);
|
||||
|
||||
if (dir || (addr == 0)) {
|
||||
USB_SET_EP_TX_ADDR(addr, dev->pm_top);
|
||||
if (callback) {
|
||||
dev->user_callback_ctr[addr][USB_TRANSACTION_IN] =
|
||||
(void *)callback;
|
||||
}
|
||||
USB_CLR_EP_TX_DTOG(addr);
|
||||
USB_SET_EP_TX_STAT(addr, USB_EP_TX_STAT_NAK);
|
||||
dev->pm_top += max_size;
|
||||
}
|
||||
|
||||
if (!dir) {
|
||||
USB_SET_EP_RX_ADDR(addr, dev->pm_top);
|
||||
usb_set_ep_rx_bufsize(dev, addr, max_size);
|
||||
if (callback) {
|
||||
dev->user_callback_ctr[addr][USB_TRANSACTION_OUT] =
|
||||
(void *)callback;
|
||||
}
|
||||
USB_CLR_EP_RX_DTOG(addr);
|
||||
USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID);
|
||||
dev->pm_top += max_size;
|
||||
}
|
||||
}
|
||||
|
||||
static void stm32f103_endpoints_reset(usbd_device *dev)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Reset all endpoints. */
|
||||
for (i = 1; i < 8; i++) {
|
||||
USB_SET_EP_TX_STAT(i, USB_EP_TX_STAT_DISABLED);
|
||||
USB_SET_EP_RX_STAT(i, USB_EP_RX_STAT_DISABLED);
|
||||
}
|
||||
dev->pm_top = 0x40 + (2 * dev->desc->bMaxPacketSize0);
|
||||
}
|
||||
|
||||
static void stm32f103_ep_stall_set(usbd_device *dev, uint8_t addr,
|
||||
uint8_t stall)
|
||||
{
|
||||
(void)dev;
|
||||
if (addr == 0) {
|
||||
USB_SET_EP_TX_STAT(addr, stall ? USB_EP_TX_STAT_STALL :
|
||||
USB_EP_TX_STAT_NAK);
|
||||
}
|
||||
|
||||
if (addr & 0x80) {
|
||||
addr &= 0x7F;
|
||||
|
||||
USB_SET_EP_TX_STAT(addr, stall ? USB_EP_TX_STAT_STALL :
|
||||
USB_EP_TX_STAT_NAK);
|
||||
|
||||
/* Reset to DATA0 if clearing stall condition. */
|
||||
if (!stall) {
|
||||
USB_CLR_EP_TX_DTOG(addr);
|
||||
}
|
||||
} else {
|
||||
/* Reset to DATA0 if clearing stall condition. */
|
||||
if (!stall) {
|
||||
USB_CLR_EP_RX_DTOG(addr);
|
||||
}
|
||||
|
||||
USB_SET_EP_RX_STAT(addr, stall ? USB_EP_RX_STAT_STALL :
|
||||
USB_EP_RX_STAT_VALID);
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t stm32f103_ep_stall_get(usbd_device *dev, uint8_t addr)
|
||||
{
|
||||
(void)dev;
|
||||
if (addr & 0x80) {
|
||||
if ((*USB_EP_REG(addr & 0x7F) & USB_EP_TX_STAT) ==
|
||||
USB_EP_TX_STAT_STALL) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if ((*USB_EP_REG(addr) & USB_EP_RX_STAT) ==
|
||||
USB_EP_RX_STAT_STALL) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stm32f103_ep_nak_set(usbd_device *dev, uint8_t addr, uint8_t nak)
|
||||
{
|
||||
(void)dev;
|
||||
/* It does not make sence to force NAK on IN endpoints. */
|
||||
if (addr & 0x80) {
|
||||
return;
|
||||
}
|
||||
|
||||
force_nak[addr] = nak;
|
||||
|
||||
if (nak) {
|
||||
USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_NAK);
|
||||
} else {
|
||||
USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a data buffer to packet memory.
|
||||
*
|
||||
* @param vPM Destination pointer into packet memory.
|
||||
* @param buf Source pointer to data buffer.
|
||||
* @param len Number of bytes to copy.
|
||||
*/
|
||||
static void usb_copy_to_pm(volatile void *vPM, const void *buf, uint16_t len)
|
||||
{
|
||||
const uint16_t *lbuf = buf;
|
||||
volatile uint16_t *PM = vPM;
|
||||
|
||||
for (len = (len + 1) >> 1; len; PM += 2, lbuf++, len--) {
|
||||
*PM = *lbuf;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t stm32f103_ep_write_packet(usbd_device *dev, uint8_t addr,
|
||||
const void *buf, uint16_t len)
|
||||
{
|
||||
(void)dev;
|
||||
addr &= 0x7F;
|
||||
|
||||
if ((*USB_EP_REG(addr) & USB_EP_TX_STAT) == USB_EP_TX_STAT_VALID) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
usb_copy_to_pm(USB_GET_EP_TX_BUFF(addr), buf, len);
|
||||
USB_SET_EP_TX_COUNT(addr, len);
|
||||
USB_SET_EP_TX_STAT(addr, USB_EP_TX_STAT_VALID);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a data buffer from packet memory.
|
||||
*
|
||||
* @param buf Source pointer to data buffer.
|
||||
* @param vPM Destination pointer into packet memory.
|
||||
* @param len Number of bytes to copy.
|
||||
*/
|
||||
static void usb_copy_from_pm(void *buf, const volatile void *vPM, uint16_t len)
|
||||
{
|
||||
uint16_t *lbuf = buf;
|
||||
const volatile uint16_t *PM = vPM;
|
||||
uint8_t odd = len & 1;
|
||||
|
||||
for (len >>= 1; len; PM += 2, lbuf++, len--) {
|
||||
*lbuf = *PM;
|
||||
}
|
||||
|
||||
if (odd) {
|
||||
*(uint8_t *) lbuf = *(uint8_t *) PM;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t stm32f103_ep_read_packet(usbd_device *dev, uint8_t addr,
|
||||
void *buf, uint16_t len)
|
||||
{
|
||||
(void)dev;
|
||||
if ((*USB_EP_REG(addr) & USB_EP_RX_STAT) == USB_EP_RX_STAT_VALID) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
len = MIN(USB_GET_EP_RX_COUNT(addr) & 0x3ff, len);
|
||||
usb_copy_from_pm(buf, USB_GET_EP_RX_BUFF(addr), len);
|
||||
USB_CLR_EP_RX_CTR(addr);
|
||||
|
||||
if (!force_nak[addr]) {
|
||||
USB_SET_EP_RX_STAT(addr, USB_EP_RX_STAT_VALID);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static void stm32f103_poll(usbd_device *dev)
|
||||
{
|
||||
uint16_t istr = *USB_ISTR_REG;
|
||||
|
||||
if (istr & USB_ISTR_RESET) {
|
||||
dev->pm_top = 0x40;
|
||||
_usbd_reset(dev);
|
||||
USB_CLR_ISTR_RESET();
|
||||
return;
|
||||
}
|
||||
|
||||
if (istr & USB_ISTR_CTR) {
|
||||
uint8_t ep = istr & USB_ISTR_EP_ID;
|
||||
uint8_t type = (istr & USB_ISTR_DIR) ? 1 : 0;
|
||||
|
||||
if (type) { /* OUT or SETUP transaction */
|
||||
type += (*USB_EP_REG(ep) & USB_EP_SETUP) ? 1 : 0;
|
||||
} else { /* IN transaction */
|
||||
USB_CLR_EP_TX_CTR(ep);
|
||||
}
|
||||
|
||||
if (dev->user_callback_ctr[ep][type]) {
|
||||
dev->user_callback_ctr[ep][type] (dev, ep);
|
||||
} else {
|
||||
USB_CLR_EP_RX_CTR(ep);
|
||||
}
|
||||
}
|
||||
|
||||
if (istr & USB_ISTR_SUSP) {
|
||||
USB_CLR_ISTR_SUSP();
|
||||
if (dev->user_callback_suspend) {
|
||||
dev->user_callback_suspend();
|
||||
}
|
||||
}
|
||||
|
||||
if (istr & USB_ISTR_WKUP) {
|
||||
USB_CLR_ISTR_WKUP();
|
||||
if (dev->user_callback_resume) {
|
||||
dev->user_callback_resume();
|
||||
}
|
||||
}
|
||||
|
||||
if (istr & USB_ISTR_SOF) {
|
||||
if (dev->user_callback_sof) {
|
||||
dev->user_callback_sof();
|
||||
}
|
||||
USB_CLR_ISTR_SOF();
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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/otg_fs.h>
|
||||
#include <libopencm3/usb/usbd.h>
|
||||
#include "usb_private.h"
|
||||
#include "usb_fx07_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 = stm32fx07_set_address,
|
||||
.ep_setup = stm32fx07_ep_setup,
|
||||
.ep_reset = stm32fx07_endpoints_reset,
|
||||
.ep_stall_set = stm32fx07_ep_stall_set,
|
||||
.ep_stall_get = stm32fx07_ep_stall_get,
|
||||
.ep_nak_set = stm32fx07_ep_nak_set,
|
||||
.ep_write_packet = stm32fx07_ep_write_packet,
|
||||
.ep_read_packet = stm32fx07_ep_read_packet,
|
||||
.poll = stm32fx07_poll,
|
||||
.disconnect = stm32fx07_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)
|
||||
{
|
||||
OTG_FS_GINTSTS = OTG_FS_GINTSTS_MMIS;
|
||||
|
||||
OTG_FS_GUSBCFG |= OTG_FS_GUSBCFG_PHYSEL;
|
||||
/* Enable VBUS sensing in device mode and power down the PHY. */
|
||||
OTG_FS_GCCFG |= OTG_FS_GCCFG_VBUSBSEN | OTG_FS_GCCFG_PWRDWN;
|
||||
|
||||
/* Wait for AHB idle. */
|
||||
while (!(OTG_FS_GRSTCTL & OTG_FS_GRSTCTL_AHBIDL));
|
||||
/* Do core soft reset. */
|
||||
OTG_FS_GRSTCTL |= OTG_FS_GRSTCTL_CSRST;
|
||||
while (OTG_FS_GRSTCTL & OTG_FS_GRSTCTL_CSRST);
|
||||
|
||||
/* Force peripheral only mode. */
|
||||
OTG_FS_GUSBCFG |= OTG_FS_GUSBCFG_FDMOD | OTG_FS_GUSBCFG_TRDT_MASK;
|
||||
|
||||
/* Full speed device. */
|
||||
OTG_FS_DCFG |= OTG_FS_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_FS_GAHBCFG_GINT;
|
||||
OTG_FS_GINTMSK = OTG_FS_GINTMSK_ENUMDNEM |
|
||||
OTG_FS_GINTMSK_RXFLVLM |
|
||||
OTG_FS_GINTMSK_IEPINT |
|
||||
OTG_FS_GINTMSK_USBSUSPM |
|
||||
OTG_FS_GINTMSK_WUIM |
|
||||
OTG_FS_GINTMSK_SOFM;
|
||||
OTG_FS_DAINTMSK = 0xF;
|
||||
OTG_FS_DIEPMSK = OTG_FS_DIEPMSK_XFRCM;
|
||||
|
||||
return &usbd_dev;
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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/otg_hs.h>
|
||||
#include <libopencm3/usb/usbd.h>
|
||||
#include "usb_private.h"
|
||||
#include "usb_fx07_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 = stm32fx07_set_address,
|
||||
.ep_setup = stm32fx07_ep_setup,
|
||||
.ep_reset = stm32fx07_endpoints_reset,
|
||||
.ep_stall_set = stm32fx07_ep_stall_set,
|
||||
.ep_stall_get = stm32fx07_ep_stall_get,
|
||||
.ep_nak_set = stm32fx07_ep_nak_set,
|
||||
.ep_write_packet = stm32fx07_ep_write_packet,
|
||||
.ep_read_packet = stm32fx07_ep_read_packet,
|
||||
.poll = stm32fx07_poll,
|
||||
.disconnect = stm32fx07_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)
|
||||
{
|
||||
OTG_HS_GINTSTS = OTG_HS_GINTSTS_MMIS;
|
||||
|
||||
OTG_HS_GUSBCFG |= OTG_HS_GUSBCFG_PHYSEL;
|
||||
/* Enable VBUS sensing in device mode and power down the PHY. */
|
||||
OTG_HS_GCCFG |= OTG_HS_GCCFG_VBUSBSEN | OTG_HS_GCCFG_PWRDWN;
|
||||
|
||||
/* Wait for AHB idle. */
|
||||
while (!(OTG_HS_GRSTCTL & OTG_HS_GRSTCTL_AHBIDL));
|
||||
/* Do core soft reset. */
|
||||
OTG_HS_GRSTCTL |= OTG_HS_GRSTCTL_CSRST;
|
||||
while (OTG_HS_GRSTCTL & OTG_HS_GRSTCTL_CSRST);
|
||||
|
||||
/* Force peripheral only mode. */
|
||||
OTG_HS_GUSBCFG |= OTG_HS_GUSBCFG_FDMOD | OTG_HS_GUSBCFG_TRDT_MASK;
|
||||
|
||||
/* Full speed device. */
|
||||
OTG_HS_DCFG |= OTG_HS_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_HS_GAHBCFG_GINT;
|
||||
OTG_HS_GINTMSK = OTG_HS_GINTMSK_ENUMDNEM |
|
||||
OTG_HS_GINTMSK_RXFLVLM |
|
||||
OTG_HS_GINTMSK_IEPINT |
|
||||
OTG_HS_GINTMSK_USBSUSPM |
|
||||
OTG_HS_GINTMSK_WUIM |
|
||||
OTG_HS_GINTMSK_SOFM;
|
||||
OTG_HS_DAINTMSK = 0xF;
|
||||
OTG_HS_DIEPMSK = OTG_HS_DIEPMSK_XFRCM;
|
||||
|
||||
return &usbd_dev;
|
||||
}
|
@ -0,0 +1,338 @@
|
||||
/*
|
||||
* 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/otg_fs.h>
|
||||
#include <libopencm3/stm32/otg_hs.h>
|
||||
#include <libopencm3/usb/usbd.h>
|
||||
#include "usb_private.h"
|
||||
#include "usb_fx07_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))
|
||||
#define REBASE_FIFO(x) (&MMIO32((dev_base_address) + (OTG_FIFO(x))))
|
||||
|
||||
void stm32fx07_set_address(usbd_device *usbd_dev, uint8_t addr)
|
||||
{
|
||||
REBASE(OTG_DCFG) = (REBASE(OTG_DCFG) & ~OTG_FS_DCFG_DAD) | (addr << 4);
|
||||
}
|
||||
|
||||
void stm32fx07_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 funciton.
|
||||
*/
|
||||
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_FS_DIEPCTL0_MPSIZ_64;
|
||||
} else if (max_size >= 32) {
|
||||
REBASE(OTG_DIEPCTL0) = OTG_FS_DIEPCTL0_MPSIZ_32;
|
||||
} else if (max_size >= 16) {
|
||||
REBASE(OTG_DIEPCTL0) = OTG_FS_DIEPCTL0_MPSIZ_16;
|
||||
} else {
|
||||
REBASE(OTG_DIEPCTL0) = OTG_FS_DIEPCTL0_MPSIZ_8;
|
||||
}
|
||||
|
||||
REBASE(OTG_DIEPTSIZ0) =
|
||||
(max_size & OTG_FS_DIEPSIZ0_XFRSIZ_MASK);
|
||||
REBASE(OTG_DIEPCTL0) |=
|
||||
OTG_FS_DIEPCTL0_EPENA | OTG_FS_DIEPCTL0_SNAK;
|
||||
|
||||
/* Configure OUT part. */
|
||||
usbd_dev->doeptsiz[0] = OTG_FS_DIEPSIZ0_STUPCNT_1 |
|
||||
OTG_FS_DIEPSIZ0_PKTCNT |
|
||||
(max_size & OTG_FS_DIEPSIZ0_XFRSIZ_MASK);
|
||||
REBASE(OTG_DOEPTSIZ(0)) = usbd_dev->doeptsiz[0];
|
||||
REBASE(OTG_DOEPCTL(0)) |=
|
||||
OTG_FS_DOEPCTL0_EPENA | OTG_FS_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_FS_DIEPSIZ0_XFRSIZ_MASK);
|
||||
REBASE(OTG_DIEPCTL(addr)) |=
|
||||
OTG_FS_DIEPCTL0_EPENA | OTG_FS_DIEPCTL0_SNAK | (type << 18)
|
||||
| OTG_FS_DIEPCTL0_USBAEP | OTG_FS_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_FS_DIEPSIZ0_PKTCNT |
|
||||
(max_size & OTG_FS_DIEPSIZ0_XFRSIZ_MASK);
|
||||
REBASE(OTG_DOEPTSIZ(addr)) = usbd_dev->doeptsiz[addr];
|
||||
REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_EPENA |
|
||||
OTG_FS_DOEPCTL0_USBAEP | OTG_FS_DIEPCTL0_CNAK |
|
||||
OTG_FS_DOEPCTLX_SD0PID | (type << 18) | max_size;
|
||||
|
||||
if (callback) {
|
||||
usbd_dev->user_callback_ctr[addr][USB_TRANSACTION_OUT] =
|
||||
(void *)callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stm32fx07_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;
|
||||
}
|
||||
|
||||
void stm32fx07_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, uint8_t stall)
|
||||
{
|
||||
if (addr == 0) {
|
||||
if (stall) {
|
||||
REBASE(OTG_DIEPCTL(addr)) |= OTG_FS_DIEPCTL0_STALL;
|
||||
} else {
|
||||
REBASE(OTG_DIEPCTL(addr)) &= ~OTG_FS_DIEPCTL0_STALL;
|
||||
}
|
||||
}
|
||||
|
||||
if (addr & 0x80) {
|
||||
addr &= 0x7F;
|
||||
|
||||
if (stall) {
|
||||
REBASE(OTG_DIEPCTL(addr)) |= OTG_FS_DIEPCTL0_STALL;
|
||||
} else {
|
||||
REBASE(OTG_DIEPCTL(addr)) &= ~OTG_FS_DIEPCTL0_STALL;
|
||||
REBASE(OTG_DIEPCTL(addr)) |= OTG_FS_DIEPCTLX_SD0PID;
|
||||
}
|
||||
} else {
|
||||
if (stall) {
|
||||
REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_STALL;
|
||||
} else {
|
||||
REBASE(OTG_DOEPCTL(addr)) &= ~OTG_FS_DOEPCTL0_STALL;
|
||||
REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTLX_SD0PID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t stm32fx07_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_FS_DIEPCTL0_STALL) ? 1 : 0;
|
||||
} else {
|
||||
return (REBASE(OTG_DOEPCTL(addr)) &
|
||||
OTG_FS_DOEPCTL0_STALL) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
void stm32fx07_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) {
|
||||
REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_SNAK;
|
||||
} else {
|
||||
REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_CNAK;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t stm32fx07_ep_write_packet(usbd_device *usbd_dev, uint8_t addr,
|
||||
const void *buf, uint16_t len)
|
||||
{
|
||||
const uint32_t *buf32 = buf;
|
||||
int i;
|
||||
|
||||
addr &= 0x7F;
|
||||
|
||||
/* Return if endpoint is already enabled. */
|
||||
if (REBASE(OTG_DIEPTSIZ(addr)) & OTG_FS_DIEPSIZ0_PKTCNT) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Enable endpoint for transmission. */
|
||||
REBASE(OTG_DIEPTSIZ(addr)) = OTG_FS_DIEPSIZ0_PKTCNT | len;
|
||||
REBASE(OTG_DIEPCTL(addr)) |= OTG_FS_DIEPCTL0_EPENA |
|
||||
OTG_FS_DIEPCTL0_CNAK;
|
||||
volatile uint32_t *fifo = REBASE_FIFO(addr);
|
||||
|
||||
/* Copy buffer to endpoint FIFO, note - memcpy does not work */
|
||||
for (i = len; i > 0; i -= 4) {
|
||||
*fifo++ = *buf32++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
uint16_t stm32fx07_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 = REBASE_FIFO(addr);
|
||||
for (i = len; i >= 4; i -= 4) {
|
||||
*buf32++ = *fifo++;
|
||||
}
|
||||
|
||||
if (i) {
|
||||
extra = *fifo++;
|
||||
memcpy(buf32, &extra, i);
|
||||
}
|
||||
|
||||
REBASE(OTG_DOEPTSIZ(addr)) = usbd_dev->doeptsiz[addr];
|
||||
REBASE(OTG_DOEPCTL(addr)) |= OTG_FS_DOEPCTL0_EPENA |
|
||||
(usbd_dev->force_nak[addr] ?
|
||||
OTG_FS_DOEPCTL0_SNAK : OTG_FS_DOEPCTL0_CNAK);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void stm32fx07_poll(usbd_device *usbd_dev)
|
||||
{
|
||||
/* Read interrupt status register. */
|
||||
uint32_t intsts = REBASE(OTG_GINTSTS);
|
||||
int i;
|
||||
|
||||
if (intsts & OTG_FS_GINTSTS_ENUMDNE) {
|
||||
/* Handle USB RESET condition. */
|
||||
REBASE(OTG_GINTSTS) = OTG_FS_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 & OTG_FS_GINTSTS_RXFLVL) {
|
||||
/* Receive FIFO non-empty. */
|
||||
uint32_t rxstsp = REBASE(OTG_GRXSTSP);
|
||||
uint32_t pktsts = rxstsp & OTG_FS_GRXSTSP_PKTSTS_MASK;
|
||||
if ((pktsts != OTG_FS_GRXSTSP_PKTSTS_OUT) &&
|
||||
(pktsts != OTG_FS_GRXSTSP_PKTSTS_SETUP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t ep = rxstsp & OTG_FS_GRXSTSP_EPNUM_MASK;
|
||||
uint8_t type;
|
||||
if (pktsts == OTG_FS_GRXSTSP_PKTSTS_SETUP) {
|
||||
type = USB_TRANSACTION_SETUP;
|
||||
} else {
|
||||
type = USB_TRANSACTION_OUT;
|
||||
}
|
||||
|
||||
/* Save packet size for stm32f107_ep_read_packet(). */
|
||||
usbd_dev->rxbcnt = (rxstsp & OTG_FS_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)*REBASE_FIFO(ep);
|
||||
}
|
||||
|
||||
usbd_dev->rxbcnt = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* There is no global interrupt flag for transmit complete.
|
||||
* The XFRC bit must be checked in each OTG_FS_DIEPINT(x).
|
||||
*/
|
||||
for (i = 0; i < 4; i++) { /* Iterate over endpoints. */
|
||||
if (REBASE(OTG_DIEPINT(i)) & OTG_FS_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_FS_DIEPINTX_XFRC;
|
||||
}
|
||||
}
|
||||
|
||||
if (intsts & OTG_FS_GINTSTS_USBSUSP) {
|
||||
if (usbd_dev->user_callback_suspend) {
|
||||
usbd_dev->user_callback_suspend();
|
||||
}
|
||||
REBASE(OTG_GINTSTS) = OTG_FS_GINTSTS_USBSUSP;
|
||||
}
|
||||
|
||||
if (intsts & OTG_FS_GINTSTS_WKUPINT) {
|
||||
if (usbd_dev->user_callback_resume) {
|
||||
usbd_dev->user_callback_resume();
|
||||
}
|
||||
REBASE(OTG_GINTSTS) = OTG_FS_GINTSTS_WKUPINT;
|
||||
}
|
||||
|
||||
if (intsts & OTG_FS_GINTSTS_SOF) {
|
||||
if (usbd_dev->user_callback_sof) {
|
||||
usbd_dev->user_callback_sof();
|
||||
}
|
||||
REBASE(OTG_GINTSTS) = OTG_FS_GINTSTS_SOF;
|
||||
}
|
||||
}
|
||||
|
||||
void stm32fx07_disconnect(usbd_device *usbd_dev, bool disconnected)
|
||||
{
|
||||
if (disconnected) {
|
||||
REBASE(OTG_DCTL) |= OTG_FS_DCTL_SDIS;
|
||||
} else {
|
||||
REBASE(OTG_DCTL) &= ~OTG_FS_DCTL_SDIS;
|
||||
}
|
||||
}
|
@ -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_FX07_COMMON_H_
|
||||
#define __USB_FX07_COMMON_H_
|
||||
|
||||
void stm32fx07_set_address(usbd_device *usbd_dev, uint8_t addr);
|
||||
void stm32fx07_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 stm32fx07_endpoints_reset(usbd_device *usbd_dev);
|
||||
void stm32fx07_ep_stall_set(usbd_device *usbd_dev, uint8_t addr, uint8_t stall);
|
||||
uint8_t stm32fx07_ep_stall_get(usbd_device *usbd_dev, uint8_t addr);
|
||||
void stm32fx07_ep_nak_set(usbd_device *usbd_dev, uint8_t addr, uint8_t nak);
|
||||
uint16_t stm32fx07_ep_write_packet(usbd_device *usbd_dev, uint8_t addr,
|
||||
const void *buf, uint16_t len);
|
||||
uint16_t stm32fx07_ep_read_packet(usbd_device *usbd_dev, uint8_t addr,
|
||||
void *buf, uint16_t len);
|
||||
void stm32fx07_poll(usbd_device *usbd_dev);
|
||||
void stm32fx07_disconnect(usbd_device *usbd_dev, bool disconnected);
|
||||
|
||||
|
||||
#endif /* __USB_FX07_COMMON_H_ */
|
@ -0,0 +1,162 @@
|
||||
/** @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 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 **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;
|
||||
void (*complete)(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req);
|
||||
} control_state;
|
||||
|
||||
struct user_control_callback {
|
||||
usbd_control_callback cb;
|
||||
uint8_t type;
|
||||
uint8_t type_mask;
|
||||
} user_control_callback[MAX_USER_CONTROL_CALLBACK];
|
||||
|
||||
void (*user_callback_ctr[8][3])(usbd_device *usbd_dev, uint8_t ea);
|
||||
|
||||
/* User callback function for some standard USB function hooks */
|
||||
void (*user_callback_set_config)(usbd_device *usbd_dev,
|
||||
uint16_t wValue);
|
||||
|
||||
const struct _usbd_driver *driver;
|
||||
|
||||
/* 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);
|
||||
|
||||
int _usbd_standard_request_device(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req, uint8_t **buf,
|
||||
uint16_t *len);
|
||||
int _usbd_standard_request_interface(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req, uint8_t **buf,
|
||||
uint16_t *len);
|
||||
int _usbd_standard_request_endpoint(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req, uint8_t **buf,
|
||||
uint16_t *len);
|
||||
int _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,
|
||||
void (*cb)(usbd_device *usbd_dev, uint8_t ep));
|
||||
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
|
||||
|
@ -0,0 +1,517 @@
|
||||
/** @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"
|
||||
|
||||
void usbd_register_set_config_callback(usbd_device *usbd_dev,
|
||||
void (*callback)(usbd_device *usbd_dev,
|
||||
uint16_t wValue))
|
||||
{
|
||||
usbd_dev->user_callback_set_config = 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). */
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill in wTotalLength. */
|
||||
*(uint16_t *)(tmpbuf + 2) = totallen;
|
||||
|
||||
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 int 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 {
|
||||
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;
|
||||
}
|
||||
|
||||
/* Ths 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 int 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 0;
|
||||
}
|
||||
|
||||
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 1;
|
||||
}
|
||||
|
||||
static int usb_standard_set_configuration(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req,
|
||||
uint8_t **buf, uint16_t *len)
|
||||
{
|
||||
int i;
|
||||
|
||||
(void)req;
|
||||
(void)buf;
|
||||
(void)len;
|
||||
|
||||
/* Is this correct, or should we reset alternate settings. */
|
||||
if (req->wValue == usbd_dev->current_config) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
usbd_dev->current_config = req->wValue;
|
||||
|
||||
/* Reset all endpoints. */
|
||||
usbd_dev->driver->ep_reset(usbd_dev);
|
||||
|
||||
if (usbd_dev->user_callback_set_config) {
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
usbd_dev->user_callback_set_config(usbd_dev, req->wValue);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int 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;
|
||||
}
|
||||
(*buf)[0] = usbd_dev->current_config;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int usb_standard_set_interface(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req,
|
||||
uint8_t **buf, uint16_t *len)
|
||||
{
|
||||
(void)usbd_dev;
|
||||
(void)req;
|
||||
(void)buf;
|
||||
|
||||
/* FIXME: Adapt if we have more than one interface. */
|
||||
if (req->wValue != 0) {
|
||||
return 0;
|
||||
}
|
||||
*len = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int usb_standard_get_interface(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req,
|
||||
uint8_t **buf, uint16_t *len)
|
||||
{
|
||||
(void)usbd_dev;
|
||||
(void)req;
|
||||
(void)buf;
|
||||
|
||||
/* FIXME: Adapt if we have more than one interface. */
|
||||
*len = 1;
|
||||
(*buf)[0] = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int 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 1;
|
||||
}
|
||||
|
||||
static int 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 1;
|
||||
}
|
||||
|
||||
static int 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 1;
|
||||
}
|
||||
|
||||
static int 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 1;
|
||||
}
|
||||
|
||||
static int 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 1;
|
||||
}
|
||||
|
||||
/* Do not appear to belong to the API, so are omitted from docs */
|
||||
/**@}*/
|
||||
|
||||
int _usbd_standard_request_device(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req, uint8_t **buf,
|
||||
uint16_t *len)
|
||||
{
|
||||
int (*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 0;
|
||||
}
|
||||
|
||||
return command(usbd_dev, req, buf, len);
|
||||
}
|
||||
|
||||
int _usbd_standard_request_interface(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req, uint8_t **buf,
|
||||
uint16_t *len)
|
||||
{
|
||||
int (*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 0;
|
||||
}
|
||||
|
||||
return command(usbd_dev, req, buf, len);
|
||||
}
|
||||
|
||||
int _usbd_standard_request_endpoint(usbd_device *usbd_dev,
|
||||
struct usb_setup_data *req, uint8_t **buf,
|
||||
uint16_t *len)
|
||||
{
|
||||
int (*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 0;
|
||||
}
|
||||
|
||||
return command(usbd_dev, req, buf, len);
|
||||
}
|
||||
|
||||
int _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 0;
|
||||
}
|
||||
|
||||
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 0;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user