2014-10-29 06:36:47 -07:00
|
|
|
/******************************************************************************
|
|
|
|
* The MIT License
|
|
|
|
*
|
|
|
|
* Copyright (c) 2010 LeafLabs LLC.
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person
|
|
|
|
* obtaining a copy of this software and associated documentation
|
|
|
|
* files (the "Software"), to deal in the Software without
|
|
|
|
* restriction, including without limitation the rights to use, copy,
|
|
|
|
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
|
|
* of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in
|
|
|
|
* all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
* SOFTWARE.
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file libmaple/usb/stm32f1/usb.c
|
|
|
|
* @brief USB support.
|
|
|
|
*
|
|
|
|
* This is a mess.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <libmaple/usb.h>
|
|
|
|
|
|
|
|
#include <libmaple/libmaple.h>
|
|
|
|
#include <libmaple/rcc.h>
|
|
|
|
|
|
|
|
/* Private headers */
|
|
|
|
#include "usb_reg_map.h"
|
|
|
|
#include "usb_lib_globals.h"
|
|
|
|
|
|
|
|
/* usb_lib headers */
|
|
|
|
#include "usb_type.h"
|
|
|
|
#include "usb_core.h"
|
|
|
|
|
|
|
|
static void dispatch_ctr_lp(void);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* usb_lib/ globals
|
|
|
|
*/
|
|
|
|
|
|
|
|
uint16 SaveTState; /* caches TX status for later use */
|
|
|
|
uint16 SaveRState; /* caches RX status for later use */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Other state
|
|
|
|
*/
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
RESUME_EXTERNAL,
|
|
|
|
RESUME_INTERNAL,
|
|
|
|
RESUME_LATER,
|
|
|
|
RESUME_WAIT,
|
|
|
|
RESUME_START,
|
|
|
|
RESUME_ON,
|
|
|
|
RESUME_OFF,
|
|
|
|
RESUME_ESOF
|
|
|
|
} RESUME_STATE;
|
|
|
|
|
|
|
|
struct {
|
|
|
|
volatile RESUME_STATE eState;
|
|
|
|
volatile uint8 bESOFcnt;
|
|
|
|
} ResumeS;
|
|
|
|
|
|
|
|
static usblib_dev usblib = {
|
|
|
|
.irq_mask = USB_ISR_MSK,
|
|
|
|
.state = USB_UNCONNECTED,
|
|
|
|
.prevState = USB_UNCONNECTED,
|
|
|
|
.clk_id = RCC_USB,
|
|
|
|
};
|
|
|
|
usblib_dev *USBLIB = &usblib;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Routines
|
|
|
|
*/
|
|
|
|
|
|
|
|
void usb_init_usblib(usblib_dev *dev,
|
|
|
|
void (**ep_int_in)(void),
|
|
|
|
void (**ep_int_out)(void)) {
|
|
|
|
rcc_clk_enable(dev->clk_id);
|
|
|
|
|
|
|
|
dev->ep_int_in = ep_int_in;
|
|
|
|
dev->ep_int_out = ep_int_out;
|
|
|
|
|
|
|
|
/* usb_lib/ declares both and then assumes that pFoo points to Foo
|
|
|
|
* (even though the names don't always match), which is stupid for
|
|
|
|
* all of the obvious reasons, but whatever. Here we are. */
|
|
|
|
pInformation = &Device_Info;
|
|
|
|
pProperty = &Device_Property;
|
|
|
|
pUser_Standard_Requests = &User_Standard_Requests;
|
|
|
|
|
|
|
|
pInformation->ControlState = 2; /* FIXME [0.0.12] use
|
|
|
|
CONTROL_STATE enumerator */
|
|
|
|
pProperty->Init();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void usb_suspend(void) {
|
|
|
|
uint16 cntr;
|
|
|
|
|
|
|
|
/* TODO decide if read/modify/write is really what we want
|
|
|
|
* (e.g. usb_resume_init() reconfigures CNTR). */
|
|
|
|
cntr = USB_BASE->CNTR;
|
|
|
|
cntr |= USB_CNTR_FSUSP;
|
|
|
|
USB_BASE->CNTR = cntr;
|
|
|
|
cntr |= USB_CNTR_LP_MODE;
|
|
|
|
USB_BASE->CNTR = cntr;
|
|
|
|
|
|
|
|
USBLIB->prevState = USBLIB->state;
|
|
|
|
USBLIB->state = USB_SUSPENDED;
|
|
|
|
}
|
|
|
|
|
2018-01-20 20:34:24 -08:00
|
|
|
void usb_power_off(void) {
|
|
|
|
USB_BASE->CNTR = USB_CNTR_FRES;
|
|
|
|
USB_BASE->ISTR = 0;
|
|
|
|
USB_BASE->CNTR = USB_CNTR_FRES + USB_CNTR_PDWN;
|
|
|
|
}
|
|
|
|
|
2014-10-29 06:36:47 -07:00
|
|
|
static void usb_resume_init(void) {
|
|
|
|
uint16 cntr;
|
|
|
|
|
|
|
|
cntr = USB_BASE->CNTR;
|
|
|
|
cntr &= ~USB_CNTR_LP_MODE;
|
|
|
|
USB_BASE->CNTR = cntr;
|
|
|
|
|
|
|
|
/* Enable interrupt lines */
|
|
|
|
USB_BASE->CNTR = USB_ISR_MSK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void usb_resume(RESUME_STATE eResumeSetVal) {
|
|
|
|
uint16 cntr;
|
|
|
|
|
|
|
|
if (eResumeSetVal != RESUME_ESOF) {
|
|
|
|
ResumeS.eState = eResumeSetVal;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (ResumeS.eState) {
|
|
|
|
case RESUME_EXTERNAL:
|
|
|
|
usb_resume_init();
|
|
|
|
ResumeS.eState = RESUME_OFF;
|
|
|
|
USBLIB->state = USBLIB->prevState;
|
|
|
|
break;
|
|
|
|
case RESUME_INTERNAL:
|
|
|
|
usb_resume_init();
|
|
|
|
ResumeS.eState = RESUME_START;
|
|
|
|
break;
|
|
|
|
case RESUME_LATER:
|
|
|
|
ResumeS.bESOFcnt = 2;
|
|
|
|
ResumeS.eState = RESUME_WAIT;
|
|
|
|
break;
|
|
|
|
case RESUME_WAIT:
|
|
|
|
ResumeS.bESOFcnt--;
|
|
|
|
if (ResumeS.bESOFcnt == 0) {
|
|
|
|
ResumeS.eState = RESUME_START;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case RESUME_START:
|
|
|
|
cntr = USB_BASE->CNTR;
|
|
|
|
cntr |= USB_CNTR_RESUME;
|
|
|
|
USB_BASE->CNTR = cntr;
|
|
|
|
ResumeS.eState = RESUME_ON;
|
|
|
|
ResumeS.bESOFcnt = 10;
|
|
|
|
break;
|
|
|
|
case RESUME_ON:
|
|
|
|
ResumeS.bESOFcnt--;
|
|
|
|
if (ResumeS.bESOFcnt == 0) {
|
|
|
|
cntr = USB_BASE->CNTR;
|
|
|
|
cntr &= ~USB_CNTR_RESUME;
|
|
|
|
USB_BASE->CNTR = cntr;
|
|
|
|
USBLIB->state = USBLIB->prevState;
|
|
|
|
ResumeS.eState = RESUME_OFF;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case RESUME_OFF:
|
|
|
|
case RESUME_ESOF:
|
|
|
|
default:
|
|
|
|
ResumeS.eState = RESUME_OFF;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SUSPEND_ENABLED 1
|
2018-04-17 02:19:36 -07:00
|
|
|
__weak void __irq_usb_lp_can_rx0(void) {
|
2014-10-29 06:36:47 -07:00
|
|
|
uint16 istr = USB_BASE->ISTR;
|
|
|
|
|
|
|
|
/* Use USB_ISR_MSK to only include code for bits we care about. */
|
|
|
|
|
|
|
|
#if (USB_ISR_MSK & USB_ISTR_RESET)
|
|
|
|
if (istr & USB_ISTR_RESET & USBLIB->irq_mask) {
|
|
|
|
USB_BASE->ISTR = ~USB_ISTR_RESET;
|
|
|
|
pProperty->Reset();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if (USB_ISR_MSK & USB_ISTR_PMAOVR)
|
|
|
|
if (istr & ISTR_PMAOVR & USBLIB->irq_mask) {
|
|
|
|
USB_BASE->ISTR = ~USB_ISTR_PMAOVR;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if (USB_ISR_MSK & USB_ISTR_ERR)
|
|
|
|
if (istr & USB_ISTR_ERR & USBLIB->irq_mask) {
|
|
|
|
USB_BASE->ISTR = ~USB_ISTR_ERR;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if (USB_ISR_MSK & USB_ISTR_WKUP)
|
|
|
|
if (istr & USB_ISTR_WKUP & USBLIB->irq_mask) {
|
2016-02-13 03:35:44 -08:00
|
|
|
USB_BASE->ISTR = ~(USB_ISTR_WKUP | USB_ISTR_SUSP);
|
2014-10-29 06:36:47 -07:00
|
|
|
usb_resume(RESUME_EXTERNAL);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if (USB_ISR_MSK & USB_ISTR_SUSP)
|
|
|
|
if (istr & USB_ISTR_SUSP & USBLIB->irq_mask) {
|
|
|
|
/* check if SUSPEND is possible */
|
|
|
|
if (SUSPEND_ENABLED) {
|
|
|
|
usb_suspend();
|
|
|
|
} else {
|
|
|
|
/* if not possible then resume after xx ms */
|
|
|
|
usb_resume(RESUME_LATER);
|
|
|
|
}
|
|
|
|
/* clear of the ISTR bit must be done after setting of CNTR_FSUSP */
|
2016-02-13 03:35:44 -08:00
|
|
|
USB_BASE->ISTR = ~(USB_ISTR_WKUP | USB_ISTR_SUSP);
|
2014-10-29 06:36:47 -07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if (USB_ISR_MSK & USB_ISTR_SOF)
|
|
|
|
if (istr & USB_ISTR_SOF & USBLIB->irq_mask) {
|
|
|
|
USB_BASE->ISTR = ~USB_ISTR_SOF;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if (USB_ISR_MSK & USB_ISTR_ESOF)
|
|
|
|
if (istr & USB_ISTR_ESOF & USBLIB->irq_mask) {
|
|
|
|
USB_BASE->ISTR = ~USB_ISTR_ESOF;
|
|
|
|
/* resume handling timing is made with ESOFs */
|
|
|
|
usb_resume(RESUME_ESOF); /* request without change of the machine state */
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Service the correct transfer interrupt.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#if (USB_ISR_MSK & USB_ISTR_CTR)
|
|
|
|
if (istr & USB_ISTR_CTR & USBLIB->irq_mask) {
|
|
|
|
dispatch_ctr_lp();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Auxiliary routines
|
|
|
|
*/
|
|
|
|
|
|
|
|
static inline uint8 dispatch_endpt_zero(uint16 istr_dir);
|
|
|
|
static inline void dispatch_endpt(uint8 ep);
|
|
|
|
static inline void set_rx_tx_status0(uint16 rx, uint16 tx);
|
|
|
|
|
|
|
|
static void handle_setup0(void);
|
|
|
|
static void handle_in0(void);
|
|
|
|
static void handle_out0(void);
|
|
|
|
|
|
|
|
static void dispatch_ctr_lp() {
|
|
|
|
uint16 istr;
|
2018-01-20 20:34:24 -08:00
|
|
|
|
2014-10-29 06:36:47 -07:00
|
|
|
while (((istr = USB_BASE->ISTR) & USB_ISTR_CTR) != 0) {
|
|
|
|
/* TODO WTF, figure this out: RM0008 says CTR is read-only,
|
|
|
|
* but ST's firmware claims it's clear-only, and emphasizes
|
|
|
|
* the importance of clearing it in more than one place. */
|
|
|
|
USB_BASE->ISTR = ~USB_ISTR_CTR;
|
|
|
|
uint8 ep_id = istr & USB_ISTR_EP_ID;
|
|
|
|
if (ep_id == 0) {
|
|
|
|
/* TODO figure out why it's OK to break out of the loop
|
|
|
|
* once we're done serving endpoint zero, but not okay if
|
|
|
|
* there are multiple nonzero endpoint transfers to
|
|
|
|
* handle. */
|
|
|
|
if (dispatch_endpt_zero(istr & USB_ISTR_DIR)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dispatch_endpt(ep_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-20 20:34:24 -08:00
|
|
|
|
2014-10-29 06:36:47 -07:00
|
|
|
/* FIXME Dataflow on endpoint 0 RX/TX status is based off of ST's
|
|
|
|
* code, and is ugly/confusing in its use of SaveRState/SaveTState.
|
|
|
|
* Fixing this requires filling in handle_in0(), handle_setup0(),
|
|
|
|
* handle_out0(). */
|
|
|
|
static inline uint8 dispatch_endpt_zero(uint16 istr_dir) {
|
|
|
|
uint32 epr = (uint16)USB_BASE->EP[0];
|
|
|
|
|
|
|
|
if (!(epr & (USB_EP_CTR_TX | USB_EP_SETUP | USB_EP_CTR_RX))) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Cache RX/TX statuses in SaveRState/SaveTState, respectively.
|
|
|
|
* The various handle_foo0() may clobber these values
|
|
|
|
* before we reset them at the end of this routine. */
|
|
|
|
SaveRState = epr & USB_EP_STAT_RX;
|
|
|
|
SaveTState = epr & USB_EP_STAT_TX;
|
|
|
|
|
|
|
|
/* Set actual RX/TX statuses to NAK while we're thinking */
|
|
|
|
set_rx_tx_status0(USB_EP_STAT_RX_NAK, USB_EP_STAT_TX_NAK);
|
|
|
|
|
|
|
|
if (istr_dir == 0) {
|
|
|
|
/* ST RM0008: "If DIR bit=0, CTR_TX bit is set in the USB_EPnR
|
|
|
|
* register related to the interrupting endpoint. The
|
|
|
|
* interrupting transaction is of IN type (data transmitted by
|
|
|
|
* the USB peripheral to the host PC)." */
|
|
|
|
ASSERT_FAULT(epr & USB_EP_CTR_TX);
|
|
|
|
usb_clear_ctr_tx(USB_EP0);
|
|
|
|
handle_in0();
|
|
|
|
} else {
|
|
|
|
/* RM0008: "If DIR bit=1, CTR_RX bit or both CTR_TX/CTR_RX
|
|
|
|
* are set in the USB_EPnR register related to the
|
|
|
|
* interrupting endpoint. The interrupting transaction is of
|
|
|
|
* OUT type (data received by the USB peripheral from the host
|
|
|
|
* PC) or two pending transactions are waiting to be
|
|
|
|
* processed."
|
|
|
|
*
|
|
|
|
* [mbolivar] Note how the following control flow (which
|
|
|
|
* replicates ST's) doesn't seem to actually handle both
|
|
|
|
* interrupts that are ostensibly pending when both CTR_RX and
|
|
|
|
* CTR_TX are set.
|
|
|
|
*
|
|
|
|
* TODO sort this mess out.
|
|
|
|
*/
|
|
|
|
if (epr & USB_EP_CTR_TX) {
|
|
|
|
usb_clear_ctr_tx(USB_EP0);
|
|
|
|
handle_in0();
|
|
|
|
} else { /* SETUP or CTR_RX */
|
|
|
|
/* SETUP is held constant while CTR_RX is set, so clear it
|
|
|
|
* either way */
|
|
|
|
usb_clear_ctr_rx(USB_EP0);
|
|
|
|
if (epr & USB_EP_SETUP) {
|
|
|
|
handle_setup0();
|
|
|
|
} else { /* CTR_RX */
|
|
|
|
handle_out0();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set_rx_tx_status0(SaveRState, SaveTState);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void dispatch_endpt(uint8 ep) {
|
|
|
|
uint32 epr = USB_BASE->EP[ep];
|
|
|
|
/* If ISTR_CTR is set and the ISTR gave us this EP_ID to handle,
|
|
|
|
* then presumably at least one of CTR_RX and CTR_TX is set, but
|
|
|
|
* again, ST's control flow allows for the possibility of neither.
|
|
|
|
*
|
|
|
|
* TODO try to find out if neither being set is possible. */
|
|
|
|
if (epr & USB_EP_CTR_RX) {
|
|
|
|
usb_clear_ctr_rx(ep);
|
|
|
|
(USBLIB->ep_int_out[ep - 1])();
|
|
|
|
}
|
|
|
|
if (epr & USB_EP_CTR_TX) {
|
|
|
|
usb_clear_ctr_tx(ep);
|
|
|
|
(USBLIB->ep_int_in[ep - 1])();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void set_rx_tx_status0(uint16 rx, uint16 tx) {
|
|
|
|
usb_set_ep_rx_stat(USB_EP0, rx);
|
|
|
|
usb_set_ep_tx_stat(USB_EP0, tx);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO Rip out usb_lib/ dependency from the following functions: */
|
|
|
|
|
|
|
|
static void handle_setup0(void) {
|
|
|
|
Setup0_Process();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_in0(void) {
|
|
|
|
In0_Process();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_out0(void) {
|
|
|
|
Out0_Process();
|
|
|
|
}
|