aboutsummaryrefslogtreecommitdiffstats
path: root/libmaple/usb/stm32f1/usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmaple/usb/stm32f1/usb.c')
-rw-r--r--libmaple/usb/stm32f1/usb.c381
1 files changed, 381 insertions, 0 deletions
diff --git a/libmaple/usb/stm32f1/usb.c b/libmaple/usb/stm32f1/usb.c
new file mode 100644
index 0000000..0130bab
--- /dev/null
+++ b/libmaple/usb/stm32f1/usb.c
@@ -0,0 +1,381 @@
+/******************************************************************************
+ * 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/usb.c
+ * @brief USB support.
+ *
+ * This is a mess. What we need almost amounts to a ground-up rewrite.
+ */
+
+#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,
+ .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->state = USB_SUSPENDED;
+}
+
+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;
+ 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;
+ ResumeS.eState = RESUME_OFF;
+ }
+ break;
+ case RESUME_OFF:
+ case RESUME_ESOF:
+ default:
+ ResumeS.eState = RESUME_OFF;
+ break;
+ }
+}
+
+#define SUSPEND_ENABLED 1
+void __irq_usb_lp_can_rx0(void) {
+ 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) {
+ USB_BASE->ISTR = ~USB_ISTR_WKUP;
+ 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 */
+ USB_BASE->ISTR = ~USB_ISTR_SUSP;
+}
+#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;
+ 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);
+ }
+ }
+}
+
+/* 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();
+}