From 4397f9009cdafd1ed65f0d6b0f304c55a92c9213 Mon Sep 17 00:00:00 2001 From: sakumisu <1203593632@qq.com> Date: Fri, 15 Oct 2021 23:02:42 +0800 Subject: [PATCH] add mcu port without test,just for reference --- port/README.md | 1 + port/atmel/usb_dc_sam.c | 924 ++++++++++++ port/atmel/usb_dc_sam0.c | 728 ++++++++++ port/atmel/usbd_ATSAM3U2C.c | 807 +++++++++++ port/dw/usb_dc_dw.c | 1212 ++++++++++++++++ port/freescale/usb_dc_kinetis.c | 1048 ++++++++++++++ port/freescale/usbd_kinetis.c | 819 +++++++++++ port/maxim/max32620/usbd_max32620.c | 622 ++++++++ port/maxim/max32625/usbd_max32625.c | 647 +++++++++ port/nordic/usb_dc_nrfx.c | 1928 +++++++++++++++++++++++++ port/nuvoton/m48ssidae/usbd_m480.c | 835 +++++++++++ port/nxp/lpc11u35/usbd_LPC11Uxx.c | 825 +++++++++++ port/nxp/lpc4322/usbd_LPC43xx_USB0.c | 901 ++++++++++++ port/stm32/back/usb_dc_stm32.c | 1111 ++++++++++++++ port/stm32/back/usbd_STM32F103.c | 770 ++++++++++ port/stm32/back/usbd_STM32F205.c | 669 +++++++++ port/stm32/usbd_stm32f103_devfs.c | 549 +++++++ port/stm32/usbd_stm32f103_devfs_asm.S | 866 +++++++++++ port/stm32/usbd_stm32f105_otgfs.c | 485 +++++++ port/stm32/usbd_stm32f429_otgfs.c | 482 +++++++ port/stm32/usbd_stm32f429_otghs.c | 486 +++++++ port/stm32/usbd_stm32f446_otgfs.c | 467 ++++++ port/stm32/usbd_stm32f446_otghs.c | 467 ++++++ port/stm32/usbd_stm32l052_devfs.c | 472 ++++++ port/stm32/usbd_stm32l052_devfs_asm.S | 850 +++++++++++ port/stm32/usbd_stm32l100_devfs.c | 457 ++++++ port/stm32/usbd_stm32l100_devfs_asm.S | 831 +++++++++++ port/stm32/usbd_stm32l433_devfs.c | 473 ++++++ port/stm32/usbd_stm32l476_otgfs.c | 500 +++++++ 29 files changed, 21232 insertions(+) create mode 100644 port/README.md create mode 100644 port/atmel/usb_dc_sam.c create mode 100644 port/atmel/usb_dc_sam0.c create mode 100644 port/atmel/usbd_ATSAM3U2C.c create mode 100644 port/dw/usb_dc_dw.c create mode 100644 port/freescale/usb_dc_kinetis.c create mode 100644 port/freescale/usbd_kinetis.c create mode 100644 port/maxim/max32620/usbd_max32620.c create mode 100644 port/maxim/max32625/usbd_max32625.c create mode 100644 port/nordic/usb_dc_nrfx.c create mode 100644 port/nuvoton/m48ssidae/usbd_m480.c create mode 100644 port/nxp/lpc11u35/usbd_LPC11Uxx.c create mode 100644 port/nxp/lpc4322/usbd_LPC43xx_USB0.c create mode 100644 port/stm32/back/usb_dc_stm32.c create mode 100644 port/stm32/back/usbd_STM32F103.c create mode 100644 port/stm32/back/usbd_STM32F205.c create mode 100644 port/stm32/usbd_stm32f103_devfs.c create mode 100644 port/stm32/usbd_stm32f103_devfs_asm.S create mode 100644 port/stm32/usbd_stm32f105_otgfs.c create mode 100644 port/stm32/usbd_stm32f429_otgfs.c create mode 100644 port/stm32/usbd_stm32f429_otghs.c create mode 100644 port/stm32/usbd_stm32f446_otgfs.c create mode 100644 port/stm32/usbd_stm32f446_otghs.c create mode 100644 port/stm32/usbd_stm32l052_devfs.c create mode 100644 port/stm32/usbd_stm32l052_devfs_asm.S create mode 100644 port/stm32/usbd_stm32l100_devfs.c create mode 100644 port/stm32/usbd_stm32l100_devfs_asm.S create mode 100644 port/stm32/usbd_stm32l433_devfs.c create mode 100644 port/stm32/usbd_stm32l476_otgfs.c diff --git a/port/README.md b/port/README.md new file mode 100644 index 00000000..6535aaf2 --- /dev/null +++ b/port/README.md @@ -0,0 +1 @@ +## 提取 port 接口备用 \ No newline at end of file diff --git a/port/atmel/usb_dc_sam.c b/port/atmel/usb_dc_sam.c new file mode 100644 index 00000000..e4744239 --- /dev/null +++ b/port/atmel/usb_dc_sam.c @@ -0,0 +1,924 @@ +/* + * Copyright (c) 2018 Aurelien Jarno + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT atmel_sam_usbhs + +#include +#include +#include + +#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(usb_dc_sam); + +/* + * This is defined in the support files for the SAM S7x, but not for + * the SAM E7x nor SAM V7x. + */ +#ifndef USBHS_RAM_ADDR +#define USBHS_RAM_ADDR (0xA0100000) +#endif + +/* + * The new Atmel DFP headers provide mode-specific interrupt register field + * definitions. Map the existing generic definitions to these. + */ +#ifndef USBHS_DEVEPTISR_CTRL_RXSTPI +#define USBHS_DEVEPTISR_CTRL_RXSTPI USBHS_DEVEPTISR_RXSTPI +#endif +#ifndef USBHS_DEVEPTICR_CTRL_RXSTPIC +#define USBHS_DEVEPTICR_CTRL_RXSTPIC USBHS_DEVEPTICR_RXSTPIC +#endif +#ifndef USBHS_DEVEPTIMR_CTRL_STALLRQ +#define USBHS_DEVEPTIMR_CTRL_STALLRQ USBHS_DEVEPTIMR_STALLRQ +#endif +#ifndef USBHS_DEVEPTIER_CTRL_RXSTPES +#define USBHS_DEVEPTIER_CTRL_RXSTPES USBHS_DEVEPTIER_RXSTPES +#endif +#ifndef USBHS_DEVEPTIER_CTRL_STALLRQS +#define USBHS_DEVEPTIER_CTRL_STALLRQS USBHS_DEVEPTIER_STALLRQS +#endif +#ifndef USBHS_DEVEPTIDR_CTRL_STALLRQC +#define USBHS_DEVEPTIDR_CTRL_STALLRQC USBHS_DEVEPTIDR_STALLRQC +#endif + +#define NUM_OF_EP_MAX DT_INST_PROP(0, num_bidir_endpoints) +#if DT_INST_NODE_HAS_PROP(0, maximum_speed) +#define USB_MAXIMUM_SPEED DT_ENUM_IDX(DT_DRV_INST(0), maximum_speed) +#else +#define USB_MAXIMUM_SPEED 2 /* Default to high-speed */ +#endif + +struct usb_device_ep_data { + uint16_t mps; + usb_dc_ep_callback cb_in; + usb_dc_ep_callback cb_out; + uint8_t *fifo; +}; + +struct usb_device_data { + bool addr_enabled; + usb_dc_status_callback status_cb; + struct usb_device_ep_data ep_data[NUM_OF_EP_MAX]; +}; + +static struct usb_device_data dev_data; + +/* Enable the USB device clock */ +static void usb_dc_enable_clock(void) +{ + /* Start the USB PLL */ + PMC->CKGR_UCKR |= CKGR_UCKR_UPLLEN; + + /* Wait for it to be ready */ + while (!(PMC->PMC_SR & PMC_SR_LOCKU)) { + k_yield(); + } + + /* In low power mode, provide a 48MHZ clock instead of the 480MHz one */ + if ((USBHS->USBHS_DEVCTRL & USBHS_DEVCTRL_SPDCONF_Msk) + == USBHS_DEVCTRL_SPDCONF_LOW_POWER) { + /* Configure the USB_48M clock to be UPLLCK/10 */ + PMC->PMC_MCKR &= ~PMC_MCKR_UPLLDIV2; + PMC->PMC_USB = PMC_USB_USBDIV(9) | PMC_USB_USBS; + + /* Enable USB_48M clock */ + PMC->PMC_SCER |= PMC_SCER_USBCLK; + } +} + +/* Disable the USB device clock */ +static void usb_dc_disable_clock(void) +{ + /* Disable USB_48M clock */ + PMC->PMC_SCER &= ~PMC_SCER_USBCLK; + + /* Disable the USB PLL */ + PMC->CKGR_UCKR &= ~CKGR_UCKR_UPLLEN; +} + +/* Check if the USB device is attached */ +static bool usb_dc_is_attached(void) +{ + return (USBHS->USBHS_DEVCTRL & USBHS_DEVCTRL_DETACH) == 0; +} + +/* Check if an endpoint is configured */ +static bool usb_dc_ep_is_configured(uint8_t ep_idx) +{ + return USBHS->USBHS_DEVEPTISR[ep_idx] & USBHS_DEVEPTISR_CFGOK; +} + +/* Check if an endpoint is enabled */ +static bool usb_dc_ep_is_enabled(uint8_t ep_idx) +{ + return USBHS->USBHS_DEVEPT & BIT(USBHS_DEVEPT_EPEN0_Pos + ep_idx); +} + +/* Reset and endpoint */ +static void usb_dc_ep_reset(uint8_t ep_idx) +{ + USBHS->USBHS_DEVEPT |= BIT(USBHS_DEVEPT_EPRST0_Pos + ep_idx); + USBHS->USBHS_DEVEPT &= ~BIT(USBHS_DEVEPT_EPRST0_Pos + ep_idx); + __DSB(); +} + +/* Enable endpoint interrupts, depending of the type and direction */ +static void usb_dc_ep_enable_interrupts(uint8_t ep_idx) +{ + if (ep_idx == 0U) { + /* Control endpoint: enable SETUP and OUT */ + USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_CTRL_RXSTPES; + USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_RXOUTES; + } else if ((USBHS->USBHS_DEVEPTCFG[ep_idx] & USBHS_DEVEPTCFG_EPDIR_Msk) + == USBHS_DEVEPTCFG_EPDIR_IN) { + /* IN direction: acknowledge FIFO empty interrupt */ + USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_TXINIC; + USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_TXINES; + } else { + /* OUT direction */ + USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_RXOUTES; + } +} + +/* Reset the endpoint FIFO pointer to the beginning of the endpoint memory */ +static void usb_dc_ep_fifo_reset(uint8_t ep_idx) +{ + uint8_t *p; + + p = (uint8_t *)(USBHS_RAM_ADDR + 0x8000 * ep_idx); + dev_data.ep_data[ep_idx].fifo = p; +} + +/* Fetch a byte from the endpoint FIFO */ +static uint8_t usb_dc_ep_fifo_get(uint8_t ep_idx) +{ + return *(dev_data.ep_data[ep_idx].fifo++); +} + +/* Put a byte from the endpoint FIFO */ +static void usb_dc_ep_fifo_put(uint8_t ep_idx, uint8_t data) +{ + *(dev_data.ep_data[ep_idx].fifo++) = data; +} + +/* Handle interrupts on a control endpoint */ +static void usb_dc_ep0_isr(void) +{ + uint32_t sr = USBHS->USBHS_DEVEPTISR[0] & USBHS->USBHS_DEVEPTIMR[0]; + uint32_t dev_ctrl = USBHS->USBHS_DEVCTRL; + + if (sr & USBHS_DEVEPTISR_CTRL_RXSTPI) { + /* SETUP data received */ + usb_dc_ep_fifo_reset(0); + dev_data.ep_data[0].cb_out(USB_EP_DIR_OUT, USB_DC_EP_SETUP); + } + if (sr & USBHS_DEVEPTISR_RXOUTI) { + /* OUT (to device) data received */ + usb_dc_ep_fifo_reset(0); + dev_data.ep_data[0].cb_out(USB_EP_DIR_OUT, USB_DC_EP_DATA_OUT); + } + if (sr & USBHS_DEVEPTISR_TXINI) { + /* Disable the interrupt */ + USBHS->USBHS_DEVEPTIDR[0] = USBHS_DEVEPTIDR_TXINEC; + + /* IN (to host) transmit complete */ + usb_dc_ep_fifo_reset(0); + dev_data.ep_data[0].cb_in(USB_EP_DIR_IN, USB_DC_EP_DATA_IN); + + if (!(dev_ctrl & USBHS_DEVCTRL_ADDEN) && + (dev_ctrl & USBHS_DEVCTRL_UADD_Msk) != 0U) { + /* Commit the pending address update. This + * must be done after the ack to the host + * completes else the ack will get dropped. + */ + USBHS->USBHS_DEVCTRL = dev_ctrl | USBHS_DEVCTRL_ADDEN; + } + } +} + +/* Handle interrupts on a non-control endpoint */ +static void usb_dc_ep_isr(uint8_t ep_idx) +{ + uint32_t sr = USBHS->USBHS_DEVEPTISR[ep_idx] & + USBHS->USBHS_DEVEPTIMR[ep_idx]; + + if (sr & USBHS_DEVEPTISR_RXOUTI) { + uint8_t ep = ep_idx | USB_EP_DIR_OUT; + + /* Acknowledge the interrupt */ + USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_RXOUTIC; + + /* OUT (to device) data received */ + usb_dc_ep_fifo_reset(ep_idx); + dev_data.ep_data[ep_idx].cb_out(ep, USB_DC_EP_DATA_OUT); + } + if (sr & USBHS_DEVEPTISR_TXINI) { + uint8_t ep = ep_idx | USB_EP_DIR_IN; + + /* Acknowledge the interrupt */ + USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_TXINIC; + + /* IN (to host) transmit complete */ + usb_dc_ep_fifo_reset(ep_idx); + dev_data.ep_data[ep_idx].cb_in(ep, USB_DC_EP_DATA_IN); + } +} + +/* Top level interrupt handler */ +static void usb_dc_isr(void) +{ + uint32_t sr = USBHS->USBHS_DEVISR & USBHS->USBHS_DEVIMR; + + /* End of resume interrupt */ + if (sr & USBHS_DEVISR_EORSM) { + /* Acknowledge the interrupt */ + USBHS->USBHS_DEVICR = USBHS_DEVICR_EORSMC; + + /* Callback function */ + dev_data.status_cb(USB_DC_RESUME, NULL); + } + + /* End of reset interrupt */ + if (sr & USBHS_DEVISR_EORST) { + /* Acknowledge the interrupt */ + USBHS->USBHS_DEVICR = USBHS_DEVICR_EORSTC; + + if (usb_dc_ep_is_enabled(0)) { + /* The device clears some of the configuration of EP0 + * when it receives the EORST. Re-enable interrupts. + */ + usb_dc_ep_enable_interrupts(0); + } + + /* Free all endpoint memory */ + for (int idx = 1; idx < NUM_OF_EP_MAX; idx++) { + usb_dc_ep_disable(idx); + USBHS->USBHS_DEVEPTCFG[idx] &= ~USBHS_DEVEPTCFG_ALLOC; + } + + /* Callback function */ + dev_data.status_cb(USB_DC_RESET, NULL); + } + + /* Suspend interrupt */ + if (sr & USBHS_DEVISR_SUSP) { + /* Acknowledge the interrupt */ + USBHS->USBHS_DEVICR = USBHS_DEVICR_SUSPC; + + /* Callback function */ + dev_data.status_cb(USB_DC_SUSPEND, NULL); + } + +#ifdef CONFIG_USB_DEVICE_SOF + /* SOF interrupt */ + if (sr & USBHS_DEVISR_SOF) { + /* Acknowledge the interrupt */ + USBHS->USBHS_DEVICR = USBHS_DEVICR_SOFC; + + /* Callback function */ + dev_data.status_cb(USB_DC_SOF, NULL); + } +#endif + + /* EP0 endpoint interrupt */ + if (sr & USBHS_DEVISR_PEP_0) { + usb_dc_ep0_isr(); + } + + /* Other endpoints interrupt */ + for (int ep_idx = 1; ep_idx < NUM_OF_EP_MAX; ep_idx++) { + if (sr & BIT(USBHS_DEVISR_PEP_0_Pos + ep_idx)) { + usb_dc_ep_isr(ep_idx); + } + } +} + +/* Attach USB for device connection */ +int usb_dc_attach(void) +{ + uint32_t regval; + + /* Start the peripheral clock */ + soc_pmc_peripheral_enable(DT_INST_PROP(0, peripheral_id)); + + /* Enable the USB controller in device mode with the clock frozen */ + USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD | USBHS_CTRL_USBE | + USBHS_CTRL_FRZCLK; + __DSB(); + + /* Select the speed */ + regval = USBHS_DEVCTRL_DETACH; +#if USB_MAXIMUM_SPEED == 0 + /* low-speed */ + regval |= USBHS_DEVCTRL_LS; + regval |= USBHS_DEVCTRL_SPDCONF_LOW_POWER; +#elif USB_MAXIMUM_SPEED == 1 + /* full-speed */ + regval |= USBHS_DEVCTRL_SPDCONF_LOW_POWER; +#elif USB_MAXIMUM_SPEED == 2 + /* high-speed */ + regval |= USBHS_DEVCTRL_SPDCONF_NORMAL; +#else +#error "Unsupported maximum speed defined in device tree." +#endif + USBHS->USBHS_DEVCTRL = regval; + + /* Enable the USB clock */ + usb_dc_enable_clock(); + + /* Unfreeze the clock */ + USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD | USBHS_CTRL_USBE; + + /* Enable device interrupts */ + USBHS->USBHS_DEVIER = USBHS_DEVIER_EORSMES; + USBHS->USBHS_DEVIER = USBHS_DEVIER_EORSTES; + USBHS->USBHS_DEVIER = USBHS_DEVIER_SUSPES; +#ifdef CONFIG_USB_DEVICE_SOF + USBHS->USBHS_DEVIER = USBHS_DEVIER_SOFES; +#endif + + /* Connect and enable the interrupt */ + IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), + usb_dc_isr, 0, 0); + irq_enable(DT_INST_IRQN(0)); + + /* Attach the device */ + USBHS->USBHS_DEVCTRL &= ~USBHS_DEVCTRL_DETACH; + + LOG_DBG(""); + return 0; +} + +/* Detach the USB device */ +int usb_dc_detach(void) +{ + /* Detach the device */ + USBHS->USBHS_DEVCTRL &= ~USBHS_DEVCTRL_DETACH; + + /* Disable the USB clock */ + usb_dc_disable_clock(); + + /* Disable the USB controller and freeze the clock */ + USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD | USBHS_CTRL_FRZCLK; + + /* Disable the peripheral clock */ + soc_pmc_peripheral_enable(DT_INST_PROP(0, peripheral_id)); + + /* Disable interrupt */ + irq_disable(DT_INST_IRQN(0)); + + LOG_DBG(""); + return 0; +} + +/* Reset the USB device */ +int usb_dc_reset(void) +{ + /* Reset the controller */ + USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD | USBHS_CTRL_FRZCLK; + + /* Clear private data */ + (void)memset(&dev_data, 0, sizeof(dev_data)); + + LOG_DBG(""); + return 0; +} + +/* Set USB device address */ +int usb_dc_set_address(uint8_t addr) +{ + /* + * Set the address but keep it disabled for now. It should be enabled + * only after the ack to the host completes. + */ + USBHS->USBHS_DEVCTRL &= ~(USBHS_DEVCTRL_UADD_Msk | USBHS_DEVCTRL_ADDEN); + USBHS->USBHS_DEVCTRL |= USBHS_DEVCTRL_UADD(addr); + LOG_DBG(""); + + return 0; +} + +/* Set USB device controller status callback */ +void usb_dc_set_status_callback(const usb_dc_status_callback cb) +{ + LOG_DBG(""); + + dev_data.status_cb = cb; +} + +/* Check endpoint capabilities */ +int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) +{ + uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + if (ep_idx == 0U) { + if (cfg->ep_type != USB_DC_EP_CONTROL) { + LOG_ERR("pre-selected as control endpoint"); + return -1; + } + } else if (ep_idx & BIT(0)) { + if (USB_EP_GET_DIR(cfg->ep_addr) != USB_EP_DIR_IN) { + LOG_INF("pre-selected as IN endpoint"); + return -1; + } + } else { + if (USB_EP_GET_DIR(cfg->ep_addr) != USB_EP_DIR_OUT) { + LOG_INF("pre-selected as OUT endpoint"); + return -1; + } + } + + if (cfg->ep_mps < 1 || cfg->ep_mps > 1024 || + (cfg->ep_type == USB_DC_EP_CONTROL && cfg->ep_mps > 64)) { + LOG_ERR("invalid endpoint size"); + return -1; + } + + return 0; +} + +/* Configure endpoint */ +int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data *const cfg) +{ + uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); + bool ep_configured[NUM_OF_EP_MAX]; + bool ep_enabled[NUM_OF_EP_MAX]; + uint32_t regval = 0U; + int log2ceil_mps; + + if (usb_dc_ep_check_cap(cfg) != 0) { + return -EINVAL; + } + + if (!usb_dc_is_attached()) { + LOG_ERR("device not attached"); + return -ENODEV; + } + + if (usb_dc_ep_is_enabled(ep_idx)) { + LOG_WRN("endpoint already configured & enabled 0x%x", ep_idx); + return -EBUSY; + } + + LOG_INF("Configure ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps, + cfg->ep_type); + + /* Reset the endpoint */ + usb_dc_ep_reset(ep_idx); + + /* Map the endpoint type */ + switch (cfg->ep_type) { + case USB_DC_EP_CONTROL: + regval |= USBHS_DEVEPTCFG_EPTYPE_CTRL; + break; + case USB_DC_EP_ISOCHRONOUS: + regval |= USBHS_DEVEPTCFG_EPTYPE_ISO; + break; + case USB_DC_EP_BULK: + regval |= USBHS_DEVEPTCFG_EPTYPE_BLK; + break; + case USB_DC_EP_INTERRUPT: + regval |= USBHS_DEVEPTCFG_EPTYPE_INTRPT; + break; + default: + return -EINVAL; + } + + /* Map the endpoint direction */ + if (USB_EP_DIR_IS_OUT(cfg->ep_addr) || + cfg->ep_type == USB_DC_EP_CONTROL) { + regval |= USBHS_DEVEPTCFG_EPDIR_OUT; + } else { + regval |= USBHS_DEVEPTCFG_EPDIR_IN; + } + + /* + * Map the endpoint size to the buffer size. Only power of 2 buffer + * sizes between 8 and 1024 are possible, get the next power of 2. + */ + log2ceil_mps = 32 - __builtin_clz((MAX(cfg->ep_mps, 8) << 1) - 1) - 1; + regval |= USBHS_DEVEPTCFG_EPSIZE(log2ceil_mps - 3); + dev_data.ep_data[ep_idx].mps = cfg->ep_mps; + + /* Use double bank buffering for isochronous endpoints */ + if (cfg->ep_type == USB_DC_EP_ISOCHRONOUS) { + regval |= USBHS_DEVEPTCFG_EPBK_2_BANK; + } else { + regval |= USBHS_DEVEPTCFG_EPBK_1_BANK; + } + + /* Configure the endpoint */ + USBHS->USBHS_DEVEPTCFG[ep_idx] = regval; + + /* + * Allocate the memory. This part is a bit tricky as memory can only be + * allocated if all above endpoints are disabled and not allocated. Loop + * backward through the above endpoints, disable them if they are + * enabled, deallocate their memory if needed. Then loop again through + * all the above endpoints to allocate and enabled them. + */ + for (int i = NUM_OF_EP_MAX - 1; i > ep_idx; i--) { + ep_configured[i] = usb_dc_ep_is_configured(i); + ep_enabled[i] = usb_dc_ep_is_enabled(i); + + if (ep_enabled[i]) { + LOG_INF("Temporary disable ep idx %x", i); + usb_dc_ep_disable(i); + } + if (ep_configured[i]) { + USBHS->USBHS_DEVEPTCFG[i] &= ~USBHS_DEVEPTCFG_ALLOC; + } + } + ep_configured[ep_idx] = true; + ep_enabled[ep_idx] = false; + for (int i = ep_idx; i < NUM_OF_EP_MAX; i++) { + if (ep_configured[i]) { + USBHS->USBHS_DEVEPTCFG[i] |= USBHS_DEVEPTCFG_ALLOC; + } + if (ep_enabled[i]) { + usb_dc_ep_enable(i); + } + } + + /* Check that the endpoint is correctly configured */ + if (!usb_dc_ep_is_configured(ep_idx)) { + LOG_ERR("endpoint configurationf failed"); + return -EINVAL; + } + + return 0; +} + +/* Set stall condition for the selected endpoint */ +int usb_dc_ep_set_stall(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_CTRL_STALLRQS; + + LOG_DBG("ep 0x%x", ep); + return 0; +} + +/* Clear stall condition for the selected endpoint */ +int usb_dc_ep_clear_stall(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + USBHS->USBHS_DEVEPTIDR[ep_idx] = USBHS_DEVEPTIDR_CTRL_STALLRQC; + + LOG_DBG("ep 0x%x", ep); + return 0; +} + +/* Check if the selected endpoint is stalled */ +int usb_dc_ep_is_stalled(uint8_t ep, uint8_t *stalled) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + if (!stalled) { + return -EINVAL; + } + + *stalled = (USBHS->USBHS_DEVEPTIMR[ep_idx] & + USBHS_DEVEPTIMR_CTRL_STALLRQ) != 0; + + LOG_DBG("ep 0x%x", ep); + return 0; +} + +/* Halt the selected endpoint */ +int usb_dc_ep_halt(uint8_t ep) +{ + return usb_dc_ep_set_stall(ep); +} + +/* Enable the selected endpoint */ +int usb_dc_ep_enable(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + if (!usb_dc_ep_is_configured(ep_idx)) { + LOG_ERR("endpoint not configured"); + return -ENODEV; + } + + /* Enable endpoint */ + USBHS->USBHS_DEVEPT |= BIT(USBHS_DEVEPT_EPEN0_Pos + ep_idx); + + /* Enable endpoint interrupts */ + USBHS->USBHS_DEVIER = BIT(USBHS_DEVIER_PEP_0_Pos + ep_idx); + + /* Enable SETUP, IN or OUT endpoint interrupts */ + usb_dc_ep_enable_interrupts(ep_idx); + + LOG_INF("Enable ep 0x%x", ep); + + return 0; +} + +/* Disable the selected endpoint */ +int usb_dc_ep_disable(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + /* Disable endpoint interrupt */ + USBHS->USBHS_DEVIDR = BIT(USBHS_DEVIDR_PEP_0_Pos + ep_idx); + + /* Disable endpoint and SETUP, IN or OUT interrupts */ + USBHS->USBHS_DEVEPT &= ~BIT(USBHS_DEVEPT_EPEN0_Pos + ep_idx); + + LOG_INF("Disable ep 0x%x", ep); + + return 0; +} + +/* Flush the selected endpoint */ +int usb_dc_ep_flush(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + if (!usb_dc_ep_is_enabled(ep_idx)) { + LOG_ERR("endpoint not enabled"); + return -ENODEV; + } + + /* Disable the IN interrupt */ + USBHS->USBHS_DEVEPTIDR[ep_idx] = USBHS_DEVEPTIDR_TXINEC; + + /* Kill the last written bank if needed */ + if (USBHS->USBHS_DEVEPTISR[ep_idx] & USBHS_DEVEPTISR_NBUSYBK_Msk) { + USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_KILLBKS; + __DSB(); + while (USBHS->USBHS_DEVEPTIMR[ep_idx] & + USBHS_DEVEPTIMR_KILLBK) { + k_yield(); + } + } + + /* Reset the endpoint */ + usb_dc_ep_reset(ep_idx); + + /* Reenable interrupts */ + usb_dc_ep_enable_interrupts(ep_idx); + + LOG_DBG("ep 0x%x", ep); + return 0; +} + +/* Write data to the specified endpoint */ +int usb_dc_ep_write(uint8_t ep, const uint8_t *data, uint32_t data_len, uint32_t *ret_bytes) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint32_t packet_len; + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + if (!usb_dc_ep_is_enabled(ep_idx)) { + LOG_ERR("endpoint not enabled"); + return -ENODEV; + } + + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_IN) { + LOG_ERR("wrong endpoint direction"); + return -EINVAL; + } + + if ((USBHS->USBHS_DEVEPTIMR[ep_idx] & USBHS_DEVEPTIMR_CTRL_STALLRQ) + != 0) { + LOG_WRN("endpoint is stalled"); + return -EBUSY; + } + + /* Write the data to the FIFO */ + packet_len = MIN(data_len, dev_data.ep_data[ep_idx].mps); + for (int i = 0; i < packet_len; i++) { + usb_dc_ep_fifo_put(ep_idx, data[i]); + } + __DSB(); + + if (ep_idx == 0U) { + /* + * Control endpoint: clear the interrupt flag to send the data, + * and re-enable the interrupts to trigger an interrupt at the + * end of the transfer. + */ + USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_TXINIC; + USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_TXINES; + } else { + /* + * Other endpoint types: clear the FIFO control flag to send + * the data. + */ + USBHS->USBHS_DEVEPTIDR[ep_idx] = USBHS_DEVEPTIDR_FIFOCONC; + } + + if (ret_bytes) { + *ret_bytes = packet_len; + } + + LOG_DBG("ep 0x%x write %d bytes from %d", ep, packet_len, data_len); + return 0; +} + +/* Read data from the specified endpoint */ +int usb_dc_ep_read(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *read_bytes) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + int rc; + + rc = usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes); + + if (rc) { + return rc; + } + + if (!data && !max_data_len) { + /* When both buffer and max data to read are zero the above + * call would fetch the data len and we simply return. + */ + return 0; + } + + /* If the packet has been read entirely, get the next one */ + if (!(USBHS->USBHS_DEVEPTISR[ep_idx] & USBHS_DEVEPTISR_RWALL)) { + rc = usb_dc_ep_read_continue(ep); + } + + LOG_DBG("ep 0x%x", ep); + return rc; +} + +/* Set callback function for the specified endpoint */ +int usb_dc_ep_set_callback(uint8_t ep, const usb_dc_ep_callback cb) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + if (USB_EP_DIR_IS_IN(ep)) { + dev_data.ep_data[ep_idx].cb_in = cb; + } else { + dev_data.ep_data[ep_idx].cb_out = cb; + } + + LOG_DBG("ep 0x%x", ep); + return 0; +} + +/* Read data from the specified endpoint */ +int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, + uint32_t *read_bytes) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint32_t data_len = (USBHS->USBHS_DEVEPTISR[ep_idx] & + USBHS_DEVEPTISR_BYCT_Msk) >> USBHS_DEVEPTISR_BYCT_Pos; + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + if (!usb_dc_ep_is_enabled(ep_idx)) { + LOG_ERR("endpoint not enabled"); + return -ENODEV; + } + + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { + LOG_ERR("wrong endpoint direction"); + return -EINVAL; + } + + if ((USBHS->USBHS_DEVEPTIMR[ep_idx] & USBHS_DEVEPTIMR_CTRL_STALLRQ) + != 0) { + LOG_WRN("endpoint is stalled"); + return -EBUSY; + } + + if (!data && !max_data_len) { + /* + * When both buffer and max data to read are zero return + * the available data in buffer. + */ + if (read_bytes) { + *read_bytes = data_len; + } + return 0; + } + + if (data_len > max_data_len) { + LOG_WRN("Not enough space to copy all the data!"); + data_len = max_data_len; + } + + if (data != NULL) { + for (int i = 0; i < data_len; i++) { + data[i] = usb_dc_ep_fifo_get(ep_idx); + } + } + + if (read_bytes) { + *read_bytes = data_len; + } + + LOG_DBG("ep 0x%x read %d bytes", ep, data_len); + return 0; +} + +/* Continue reading data from the endpoint */ +int usb_dc_ep_read_continue(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + if (!usb_dc_ep_is_enabled(ep_idx)) { + LOG_ERR("endpoint not enabled"); + return -ENODEV; + } + + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { + LOG_ERR("wrong endpoint direction"); + return -EINVAL; + } + + if (ep_idx == 0U) { + /* + * Control endpoint: clear the interrupt flag to send the data. + * It is easier to clear both SETUP and OUT flag than checking + * the stage of the transfer. + */ + USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_RXOUTIC; + USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_CTRL_RXSTPIC; + } else { + /* + * Other endpoint types: clear the FIFO control flag to + * receive more data. + */ + USBHS->USBHS_DEVEPTIDR[ep_idx] = USBHS_DEVEPTIDR_FIFOCONC; + } + + LOG_DBG("ep 0x%x continue", ep); + return 0; +} + +/* Endpoint max packet size (mps) */ +int usb_dc_ep_mps(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= NUM_OF_EP_MAX) { + LOG_ERR("wrong endpoint index/address"); + return -EINVAL; + } + + return dev_data.ep_data[ep_idx].mps; +} diff --git a/port/atmel/usb_dc_sam0.c b/port/atmel/usb_dc_sam0.c new file mode 100644 index 00000000..911ab799 --- /dev/null +++ b/port/atmel/usb_dc_sam0.c @@ -0,0 +1,728 @@ +/* + * Copyright (c) 2018 Google LLC. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT atmel_sam0_usb + +#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(usb_dc_sam0); + +#include +#include +#include + +#define NVM_USB_PAD_TRANSN_POS 45 +#define NVM_USB_PAD_TRANSN_SIZE 5 +#define NVM_USB_PAD_TRANSP_POS 50 +#define NVM_USB_PAD_TRANSP_SIZE 5 +#define NVM_USB_PAD_TRIM_POS 55 +#define NVM_USB_PAD_TRIM_SIZE 3 + +#define USB_SAM0_IN_EP 0x80 + +#define REGS ((Usb *)DT_INST_REG_ADDR(0)) +#define USB_NUM_ENDPOINTS DT_INST_PROP(0, num_bidir_endpoints) + +/* The endpoint size stored in USB.PCKSIZE.SIZE */ +enum usb_sam0_pcksize_size { + USB_SAM0_PCKSIZE_SIZE_8 = 0, + USB_SAM0_PCKSIZE_SIZE_16, + USB_SAM0_PCKSIZE_SIZE_32, + USB_SAM0_PCKSIZE_SIZE_64, + USB_SAM0_PCKSIZE_SIZE_128, + USB_SAM0_PCKSIZE_SIZE_256, + USB_SAM0_PCKSIZE_SIZE_512, + USB_SAM0_PCKSIZE_SIZE_1023, +}; + +static const uint16_t usb_sam0_pcksize_bytes[] = { + [USB_SAM0_PCKSIZE_SIZE_8] = 8, + [USB_SAM0_PCKSIZE_SIZE_16] = 16, + [USB_SAM0_PCKSIZE_SIZE_32] = 32, + [USB_SAM0_PCKSIZE_SIZE_64] = 64, + [USB_SAM0_PCKSIZE_SIZE_128] = 128, + [USB_SAM0_PCKSIZE_SIZE_256] = 256, + [USB_SAM0_PCKSIZE_SIZE_512] = 512, + [USB_SAM0_PCKSIZE_SIZE_1023] = 1023, +}; + +BUILD_ASSERT(ARRAY_SIZE(usb_sam0_pcksize_bytes) == 8); + +struct usb_sam0_data { + UsbDeviceDescriptor descriptors[USB_NUM_ENDPOINTS]; + + usb_dc_status_callback cb; + usb_dc_ep_callback ep_cb[2][USB_NUM_ENDPOINTS]; + + uint8_t addr; + uint32_t out_at; +}; + +static struct usb_sam0_data usb_sam0_data_0; + +static struct usb_sam0_data *usb_sam0_get_data(void) +{ + return &usb_sam0_data_0; +} + +/* Handles interrupts on an endpoint */ +static void usb_sam0_ep_isr(uint8_t ep) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + UsbDevice *regs = ®S->DEVICE; + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep]; + uint32_t intflag = endpoint->EPINTFLAG.reg; + + endpoint->EPINTFLAG.reg = intflag; + + if ((intflag & USB_DEVICE_EPINTFLAG_RXSTP) != 0U) { + /* Setup */ + data->ep_cb[0][ep](ep, USB_DC_EP_SETUP); + } + + if ((intflag & USB_DEVICE_EPINTFLAG_TRCPT0) != 0U) { + /* Out (to device) data received */ + data->ep_cb[0][ep](ep, USB_DC_EP_DATA_OUT); + } + + if ((intflag & USB_DEVICE_EPINTFLAG_TRCPT1) != 0U) { + /* In (to host) transmit complete */ + data->ep_cb[1][ep](ep | USB_SAM0_IN_EP, USB_DC_EP_DATA_IN); + + if (data->addr != 0U) { + /* Commit the pending address update. This + * must be done after the ack to the host + * completes else the ack will get dropped. + */ + regs->DADD.reg = data->addr; + data->addr = 0U; + } + } +} + +/* Top level interrupt handler */ +static void usb_sam0_isr(void) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + UsbDevice *regs = ®S->DEVICE; + uint32_t intflag = regs->INTFLAG.reg; + uint32_t epint = regs->EPINTSMRY.reg; + uint8_t ep; + + /* Acknowledge all interrupts */ + regs->INTFLAG.reg = intflag; + + if ((intflag & USB_DEVICE_INTFLAG_EORST) != 0U) { + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[0]; + + /* The device clears some of the configuration of EP0 + * when it receives the EORST. Re-enable interrupts. + */ + endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | + USB_DEVICE_EPINTENSET_TRCPT1 | + USB_DEVICE_EPINTENSET_RXSTP; + + data->cb(USB_DC_RESET, NULL); + } + + /* Dispatch the endpoint interrupts */ + for (ep = 0U; epint != 0U; epint >>= 1) { + /* Scan bit-by-bit as the Cortex-M0 doesn't have ffs */ + if ((epint & 1) != 0U) { + usb_sam0_ep_isr(ep); + } + ep++; + } +} + +/* Wait for the device to process the last config write */ +static void usb_sam0_wait_syncbusy(void) +{ + UsbDevice *regs = ®S->DEVICE; + + while (regs->SYNCBUSY.reg != 0) { + } +} + +/* Load the pad calibration from the built-in fuses */ +static void usb_sam0_load_padcal(void) +{ + UsbDevice *regs = ®S->DEVICE; + uint32_t pad_transn; + uint32_t pad_transp; + uint32_t pad_trim; + +#ifdef USB_FUSES_TRANSN_ADDR + pad_transn = *(uint32_t *)USB_FUSES_TRANSN_ADDR; +#else + pad_transn = (*((uint32_t *)(NVMCTRL_OTP4) + + (NVM_USB_PAD_TRANSN_POS / 32)) >> + (NVM_USB_PAD_TRANSN_POS % 32)) & + ((1 << NVM_USB_PAD_TRANSN_SIZE) - 1); + + if (pad_transn == 0x1F) { + pad_transn = 5U; + } +#endif + + regs->PADCAL.bit.TRANSN = pad_transn; + +#ifdef USB_FUSES_TRANSP_ADDR + pad_transp = *(uint32_t *)USB_FUSES_TRANSP_ADDR; +#else + pad_transp = (*((uint32_t *)(NVMCTRL_OTP4) + + (NVM_USB_PAD_TRANSP_POS / 32)) >> + (NVM_USB_PAD_TRANSP_POS % 32)) & + ((1 << NVM_USB_PAD_TRANSP_SIZE) - 1); + + if (pad_transp == 0x1F) { + pad_transp = 29U; + } +#endif + + regs->PADCAL.bit.TRANSP = pad_transp; + +#ifdef USB_FUSES_TRIM_ADDR + pad_trim = *(uint32_t *)USB_FUSES_TRIM_ADDR; +#else + pad_trim = (*((uint32_t *)(NVMCTRL_OTP4) + + (NVM_USB_PAD_TRIM_POS / 32)) >> + (NVM_USB_PAD_TRIM_POS % 32)) & + ((1 << NVM_USB_PAD_TRIM_SIZE) - 1); + + if (pad_trim == 0x7) { + pad_trim = 3U; + } +#endif + + regs->PADCAL.bit.TRIM = pad_trim; +} + +#define SAM0_USB_IRQ_CONNECT(n) \ + do { \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(0, n, irq), \ + DT_INST_IRQ_BY_IDX(0, n, priority), \ + usb_sam0_isr, 0, 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(0, n, irq)); \ + } while (0) + +/* Attach by initializing the device */ +int usb_dc_attach(void) +{ + UsbDevice *regs = ®S->DEVICE; + struct usb_sam0_data *data = usb_sam0_get_data(); + +#ifdef MCLK + /* Enable the clock in MCLK */ + MCLK->APBBMASK.bit.USB_ = 1; + + /* Enable the GCLK - use 48 MHz source */ + GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_GEN(2) + | GCLK_PCHCTRL_CHEN; + + while (GCLK->SYNCBUSY.reg) { + } +#else + /* Enable the clock in PM */ + PM->APBBMASK.bit.USB_ = 1; + + /* Enable the GCLK */ + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_USB | GCLK_CLKCTRL_GEN_GCLK0 | + GCLK_CLKCTRL_CLKEN; + + while (GCLK->STATUS.bit.SYNCBUSY) { + } +#endif /* !MCLK */ + + /* Configure */ + regs->CTRLA.bit.SWRST = 1; + usb_sam0_wait_syncbusy(); + + /* Change QOS values to have the best performance and correct USB + * behaviour + */ + regs->QOSCTRL.bit.CQOS = 2; + regs->QOSCTRL.bit.DQOS = 2; + + usb_sam0_load_padcal(); + + regs->CTRLA.reg = USB_CTRLA_MODE_DEVICE | USB_CTRLA_RUNSTDBY; + regs->CTRLB.reg = USB_DEVICE_CTRLB_SPDCONF_HS; + + (void)memset(data->descriptors, 0, sizeof(data->descriptors)); + regs->DESCADD.reg = (uintptr_t)&data->descriptors[0]; + + regs->INTENSET.reg = USB_DEVICE_INTENSET_EORST; + + /* Connect and enable the interrupt */ +#if DT_INST_IRQ_HAS_CELL(0, irq) + SAM0_USB_IRQ_CONNECT(0); +#endif +#if DT_INST_IRQ_HAS_IDX(0, 1) + SAM0_USB_IRQ_CONNECT(1); +#endif +#if DT_INST_IRQ_HAS_IDX(0, 2) + SAM0_USB_IRQ_CONNECT(2); +#endif +#if DT_INST_IRQ_HAS_IDX(0, 3) + SAM0_USB_IRQ_CONNECT(3); +#endif + + /* Enable and attach */ + regs->CTRLA.bit.ENABLE = 1; + usb_sam0_wait_syncbusy(); + regs->CTRLB.bit.DETACH = 0; + + return 0; +} + +/* Detach from the bus */ +int usb_dc_detach(void) +{ + UsbDevice *regs = ®S->DEVICE; + + regs->CTRLB.bit.DETACH = 1; + usb_sam0_wait_syncbusy(); + + return 0; +} + +/* Remove the interrupt and reset the device */ +int usb_dc_reset(void) +{ + UsbDevice *regs = ®S->DEVICE; + + irq_disable(DT_INST_IRQN(0)); + + regs->CTRLA.bit.SWRST = 1; + usb_sam0_wait_syncbusy(); + + return 0; +} + +/* Queue a change in address. This is processed later when the + * current transfers are compelete. + */ +int usb_dc_set_address(const uint8_t addr) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + + data->addr = addr | USB_DEVICE_DADD_ADDEN; + + return 0; +} + +void usb_dc_set_status_callback(const usb_dc_status_callback cb) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + + data->cb = cb; +} + +int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) +{ + uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); + + if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { + LOG_ERR("invalid endpoint configuration"); + return -1; + } + + if (ep_idx > USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address too high"); + return -1; + } + + return 0; +} + +int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data *const cfg) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + UsbDevice *regs = ®S->DEVICE; + uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + UsbDeviceDescriptor *desc = &data->descriptors[ep_idx]; + UsbDeviceDescBank *bank; + void *buf; + int type; + int size = -1; + int i; + + /* Map the type to native type */ + switch (cfg->ep_type) { + case USB_DC_EP_CONTROL: + type = 1; + break; + case USB_DC_EP_ISOCHRONOUS: + type = 2; + break; + case USB_DC_EP_BULK: + type = 3; + break; + case USB_DC_EP_INTERRUPT: + type = 4; + break; + default: + return -EINVAL; + } + + /* Map the endpoint size to native size */ + for (i = 0; i < ARRAY_SIZE(usb_sam0_pcksize_bytes); i++) { + if (usb_sam0_pcksize_bytes[i] == cfg->ep_mps) { + size = i; + break; + } + } + + if (size < 0) { + return -EINVAL; + } + + if (USB_EP_DIR_IS_IN(cfg->ep_addr)) { + bank = &desc->DeviceDescBank[1]; + } else { + bank = &desc->DeviceDescBank[0]; + } + + buf = (void *)bank->ADDR.reg; + + if (bank->PCKSIZE.bit.SIZE != size || buf == NULL) { + /* Release the previous buffer, if any */ + k_free(buf); + + buf = k_malloc(cfg->ep_mps); + if (buf == NULL) { + return -ENOMEM; + } + bank->PCKSIZE.bit.SIZE = size; + bank->ADDR.reg = (uintptr_t)buf; + } + + if (USB_EP_DIR_IS_IN(cfg->ep_addr)) { + endpoint->EPCFG.bit.EPTYPE1 = type; + endpoint->EPSTATUSCLR.bit.BK1RDY = 1; + } else { + endpoint->EPCFG.bit.EPTYPE0 = type; + endpoint->EPSTATUSCLR.bit.BK0RDY = 1; + } + + return 0; +} + +int usb_dc_ep_set_stall(const uint8_t ep) +{ + UsbDevice *regs = ®S->DEVICE; + uint8_t for_in = USB_EP_GET_DIR(ep); + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + if (for_in) { + endpoint->EPSTATUSSET.bit.STALLRQ1 = 1; + } else { + endpoint->EPSTATUSSET.bit.STALLRQ0 = 1; + } + + return 0; +} + +int usb_dc_ep_clear_stall(const uint8_t ep) +{ + UsbDevice *regs = ®S->DEVICE; + uint8_t for_in = USB_EP_GET_DIR(ep); + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + if (for_in) { + endpoint->EPSTATUSCLR.bit.STALLRQ1 = 1; + } else { + endpoint->EPSTATUSCLR.bit.STALLRQ0 = 1; + } + + return 0; +} + +int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *stalled) +{ + UsbDevice *regs = ®S->DEVICE; + uint8_t for_in = USB_EP_GET_DIR(ep); + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + if (stalled == NULL) { + LOG_ERR("parameter must not be NULL"); + return -1; + } + + if (for_in) { + *stalled = endpoint->EPSTATUS.bit.STALLRQ1; + } else { + *stalled = endpoint->EPSTATUS.bit.STALLRQ0; + } + + return 0; +} + +/* Halt the selected endpoint */ +int usb_dc_ep_halt(uint8_t ep) +{ + return usb_dc_ep_set_stall(ep); +} + +/* Flush the selected endpoint */ +int usb_dc_ep_flush(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + /* TODO */ + LOG_WRN("flush not implemented"); + + return 0; +} + +/* Enable an endpoint and the endpoint interrupts */ +int usb_dc_ep_enable(const uint8_t ep) +{ + UsbDevice *regs = ®S->DEVICE; + uint8_t for_in = USB_EP_GET_DIR(ep); + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -EINVAL; + } + + if (for_in) { + endpoint->EPSTATUSCLR.bit.BK1RDY = 1; + } else { + endpoint->EPSTATUSCLR.bit.BK0RDY = 1; + } + + endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | + USB_DEVICE_EPINTENSET_TRCPT1 | + USB_DEVICE_EPINTENSET_RXSTP; + + return 0; +} + +/* Disable the selected endpoint */ +int usb_dc_ep_disable(uint8_t ep) +{ + UsbDevice *regs = ®S->DEVICE; + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -EINVAL; + } + + endpoint->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT0 + | USB_DEVICE_EPINTENCLR_TRCPT1 + | USB_DEVICE_EPINTENCLR_RXSTP; + + return 0; +} + +/* Write a single payload to the IN buffer on the endpoint */ +int usb_dc_ep_write(uint8_t ep, const uint8_t *buf, uint32_t len, uint32_t *ret_bytes) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + UsbDevice *regs = ®S->DEVICE; + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + UsbDeviceDescriptor *desc = &data->descriptors[ep_idx]; + uint32_t addr = desc->DeviceDescBank[1].ADDR.reg; + uint32_t capacity = usb_sam0_pcksize_bytes[ + desc->DeviceDescBank[1].PCKSIZE.bit.SIZE]; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + if (endpoint->EPSTATUS.bit.BK1RDY) { + /* Write in progress, drop */ + return -EAGAIN; + } + + len = Z_MIN(len, capacity); + + /* Note that this code does not use the hardware's + * multi-packet and automatic zero-length packet features as + * the upper layers in Zephyr implement these in code. + */ + memcpy((void *)addr, buf, len); + desc->DeviceDescBank[1].PCKSIZE.bit.MULTI_PACKET_SIZE = 0; + desc->DeviceDescBank[1].PCKSIZE.bit.BYTE_COUNT = len; + endpoint->EPINTFLAG.reg = + USB_DEVICE_EPINTFLAG_TRCPT1 | USB_DEVICE_EPINTFLAG_TRFAIL1; + endpoint->EPSTATUSSET.bit.BK1RDY = 1; + + if (ret_bytes != NULL) { + *ret_bytes = len; + } + + return 0; +} + +/* Read data from an OUT endpoint */ +int usb_dc_ep_read_ex(uint8_t ep, uint8_t *buf, uint32_t max_data_len, + uint32_t *read_bytes, bool wait) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + UsbDevice *regs = ®S->DEVICE; + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + UsbDeviceDescriptor *desc = &data->descriptors[ep_idx]; + uint32_t addr = desc->DeviceDescBank[0].ADDR.reg; + uint32_t bytes = desc->DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT; + uint32_t take; + int remain; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + if (!endpoint->EPSTATUS.bit.BK0RDY) { + return -EAGAIN; + } + + /* The code below emulates the Quark FIFO which the Zephyr USB + * API is based on. Reading with buf == NULL returns the + * number of bytes available and starts the read. The caller + * then keeps calling until all bytes are consumed which + * also marks the OUT buffer as freed. + */ + if (buf == NULL) { + data->out_at = 0U; + + if (read_bytes != NULL) { + *read_bytes = bytes; + } + + return 0; + } + + remain = bytes - data->out_at; + take = MIN(max_data_len, remain); + memcpy(buf, (uint8_t *)addr + data->out_at, take); + + if (read_bytes != NULL) { + *read_bytes = take; + } + + if (take == remain) { + if (!wait) { + endpoint->EPSTATUSCLR.bit.BK0RDY = 1; + data->out_at = 0U; + } + } else { + data->out_at += take; + } + + return 0; +} + +int usb_dc_ep_read(uint8_t ep, uint8_t *buf, uint32_t max_data_len, uint32_t *read_bytes) +{ + return usb_dc_ep_read_ex(ep, buf, max_data_len, read_bytes, false); +} + +int usb_dc_ep_read_wait(uint8_t ep, uint8_t *buf, uint32_t max_data_len, + uint32_t *read_bytes) +{ + return usb_dc_ep_read_ex(ep, buf, max_data_len, read_bytes, true); +} + +int usb_dc_ep_read_continue(uint8_t ep) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + UsbDevice *regs = ®S->DEVICE; + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + endpoint->EPSTATUSCLR.bit.BK0RDY = 1; + data->out_at = 0U; + + return 0; +} + +int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + uint8_t for_in = USB_EP_GET_DIR(ep); + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + data->ep_cb[for_in ? 1 : 0][ep_idx] = cb; + + return 0; +} + +int usb_dc_ep_mps(const uint8_t ep) +{ + struct usb_sam0_data *data = usb_sam0_get_data(); + UsbDevice *regs = ®S->DEVICE; + uint8_t for_in = USB_EP_GET_DIR(ep); + uint8_t ep_idx = USB_EP_GET_IDX(ep); + UsbDeviceDescriptor *desc = &data->descriptors[ep_idx]; + UsbDeviceEndpoint *endpoint = ®s->DeviceEndpoint[ep_idx]; + + if (ep_idx >= USB_NUM_ENDPOINTS) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + if (for_in) { + /* if endpoint is not configured, this should return 0 */ + if (endpoint->EPCFG.bit.EPTYPE1 == 0) { + return 0; + } + + return usb_sam0_pcksize_bytes[ + desc->DeviceDescBank[1].PCKSIZE.bit.SIZE]; + } else { + /* if endpoint is not configured, this should return 0 */ + if (endpoint->EPCFG.bit.EPTYPE0 == 0) { + return 0; + } + + return usb_sam0_pcksize_bytes[ + desc->DeviceDescBank[0].PCKSIZE.bit.SIZE]; + } +} diff --git a/port/atmel/usbd_ATSAM3U2C.c b/port/atmel/usbd_ATSAM3U2C.c new file mode 100644 index 00000000..b556a612 --- /dev/null +++ b/port/atmel/usbd_ATSAM3U2C.c @@ -0,0 +1,807 @@ +/** + * @file usbd_ATSAM3U2C.c + * @brief + * + * DAPLink Interface Firmware + * Copyright (c) 2009-2016, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rl_usb.h" +#include "sam3u.h" +#include "util.h" + +#define __NO_USB_LIB_C +#include "usb_config.c" + +#define UDPHS_EPTFIFO_BASE (0x20180000) /* (UDPHS_EPTFIFO) Base Address */ +uint32_t eptsta_copy[USBD_EP_NUM + 1]; + +/* + * Calculate EP size code Function + * Called during EndPoint configuration + * Return Value: EP size code for given EP size + */ + +static int USBD_CalcSizeEP(uint32_t size) +{ + if (size <= 8) { + return (0); + } else if (size <= 16) { + return (1); + } else if (size <= 32) { + return (2); + } else if (size <= 64) { + return (3); + } else if (size <= 128) { + return (4); + } else if (size <= 256) { + return (5); + } else if (size <= 512) { + return (6); + } else if (size <= 1024) { + return (7); + } + + return (0); +} + + +/* + * Retrieve maximum EP size Function + * Called during EndPoint configuration + * Return Value: maximum size for given EP + */ + +static int USBD_GetSizeEP(uint32_t EPNum) +{ + switch (EPNum & 0x0F) { + case 0: + return (64); /* Maximum size is 64 bytes */ + + case 1: + case 2: + return (512); /* Maximum size is 512 bytes */ + + case 3: + case 4: + return (64); /* Maximum size is 64 bytes */ + + case 5: + case 6: + return (1024); /* Maximum size is 1024 bytes */ + + default: + return (0); /* Non existant endpoint */ + } +} + + +/* + * USB Device Interrupt enable + * Called by USBD_Init to enable the USB Interrupt + * Return Value: None + */ + +void USBD_IntrEna(void) +{ + NVIC_EnableIRQ(UDPHS_IRQn); /* Enable USB interrupt */ +} + + +/* + * USB Device Initialize Function + * Called by the User to initialize USB Device + * Return Value: None + */ + +void USBD_Init(void) +{ + uint32_t n; + /* Enables the 48MHz USB Clock UDPCK and System Peripheral USB Clock */ + PMC->PMC_WPMR = 0x504D4300; /* Disable write protect */ + PMC->PMC_PCER0 = (1 << ID_UDPHS); /* enable clock for UPDHS */ + PMC->CKGR_UCKR = (CKGR_UCKR_UPLLCOUNT(15) | CKGR_UCKR_UPLLEN); + + while (!(PMC->PMC_SR & PMC_SR_LOCKU)); /* wait until PLL is locked */ + + PMC->PMC_WPMR = 0x504D4301; /* Enable write protect */ + /* Configure the pull-up on D+ and disconnect it */ + UDPHS->UDPHS_CTRL |= UDPHS_CTRL_DETACH; /* Detach */ + UDPHS->UDPHS_CTRL |= UDPHS_CTRL_PULLD_DIS; /* Disable Pull Down */ + /* Reset IP UDPHS */ + UDPHS->UDPHS_CTRL &= ~UDPHS_CTRL_EN_UDPHS; + UDPHS->UDPHS_CTRL |= UDPHS_CTRL_EN_UDPHS; +#if (!USBD_HS_ENABLE) /* If HS disabled */ + UDPHS->UDPHS_TST |= (3 & UDPHS_TST_SPEED_CFG_Msk); +#endif + + /* Disable DMA for UDPHS */ + for (n = 1; n < (UDPHSDMA_NUMBER); n++) { + /* RESET endpoint canal DMA: */ + UDPHS->UDPHS_DMA[n].UDPHS_DMACONTROL = 0; /* STOP command */ + /* Disable endpoint */ + UDPHS->UDPHS_EPT[n].UDPHS_EPTCTLDIS = 0xFFFFFFFF; + /* Clear status endpoint */ + UDPHS->UDPHS_EPT[n].UDPHS_EPTCLRSTA = 0xFFFFFFFF; + /* Reset endpoint config */ + UDPHS->UDPHS_EPT[n].UDPHS_EPTCTLENB = 0; + /* Reset DMA channel (Buff count and Control field) */ + UDPHS->UDPHS_DMA[n].UDPHS_DMACONTROL = (0x1 << 1); /* NON STOP command */ + /* Reset DMA channel 0 (STOP) */ + UDPHS->UDPHS_DMA[n].UDPHS_DMACONTROL = 0; /* STOP command */ + /* Clear DMA channel status (read the register for clear it) */ + UDPHS->UDPHS_DMA[n].UDPHS_DMASTATUS = UDPHS->UDPHS_DMA[n].UDPHS_DMASTATUS; + } + + UDPHS->UDPHS_IEN = 0; + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_UPSTR_RES | + UDPHS_CLRINT_ENDOFRSM | + UDPHS_CLRINT_WAKE_UP | + UDPHS_CLRINT_ENDRESET | + UDPHS_CLRINT_INT_SOF | + UDPHS_CLRINT_MICRO_SOF | + UDPHS_CLRINT_DET_SUSPD; + USBD_IntrEna(); /* Enable USB interrupt */ +} + + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ + +void USBD_Connect(BOOL con) +{ + if (con) { + UDPHS->UDPHS_CTRL &= ~UDPHS_CTRL_DETACH; /* Pull Up on DP */ + UDPHS->UDPHS_CTRL |= UDPHS_CTRL_PULLD_DIS; /* Disable Pull Down */ + } else { + UDPHS->UDPHS_CTRL |= UDPHS_CTRL_DETACH; /* Detach */ + UDPHS->UDPHS_CTRL &= ~UDPHS_CTRL_PULLD_DIS; /* Enable Pull Down */ + } +} + +/* + * USB Device Reset Function + * Called automatically on USB Device Reset + * Return Value: None + */ +extern U8 USBD_ConfigDescriptor_HS[]; +extern U8 USBD_ConfigDescriptor[]; +void USBD_Reset(void) +{ + uint32_t ep, EPMask; + EPMask = ((1 << (USBD_EP_NUM + 1)) - 1); + + /* Reset & Disable USB Endpoints */ + for (ep = 0; ep <= USBD_EP_NUM; ep++) { + UDPHS->UDPHS_EPT[ep].UDPHS_EPTCFG = 0; + UDPHS->UDPHS_EPT[ep].UDPHS_EPTCTLDIS = (0x1 << 0); /* Disable EP */ + eptsta_copy[ep] = 0; + } + + UDPHS->UDPHS_EPTRST = EPMask; /* Reset EPs */ + UDPHS->UDPHS_EPTRST = 0; + /* Setup USB Interrupts */ /* note: Micro_SOF not yet handled */ +#ifdef __RTX + UDPHS->UDPHS_IEN = ((USBD_RTX_DevTask != 0) ? UDPHS_IEN_DET_SUSPD : 0) | + ((USBD_RTX_DevTask != 0) ? UDPHS_IEN_MICRO_SOF : 0) | + ((USBD_RTX_DevTask != 0) ? UDPHS_IEN_INT_SOF : 0) | + ((USBD_RTX_DevTask != 0) ? UDPHS_IEN_ENDRESET : 0) | +// ((USBD_RTX_DevTask != 0) ? UDPHS_IEN_WAKE_UP : 0) | +// ((USBD_RTX_DevTask != 0) ? UDPHS_IEN_UPSTR_RES : 0) | +#else + UDPHS->UDPHS_IEN = ((USBD_P_Suspend_Event != 0) ? UDPHS_IEN_DET_SUSPD : 0) | + ((USBD_P_SOF_Event != 0) ? UDPHS_IEN_MICRO_SOF : 0) | + ((USBD_P_SOF_Event != 0) ? UDPHS_IEN_INT_SOF : 0) | + ((USBD_P_Reset_Event != 0) ? UDPHS_IEN_ENDRESET : 0) | +// ((USBD_P_WakeUp_Event != 0) ? UDPHS_IEN_WAKE_UP : 0) | +// ((USBD_P_Resume_Event != 0) ? UDPHS_IEN_UPSTR_RES : 0) | +#endif + (EPMask << 8); + /* Setup Control Endpoint 0 */ + UDPHS->UDPHS_EPT[0].UDPHS_EPTCFG = UDPHS_EPTCFG_BK_NUMBER_1 | + UDPHS_EPTCFG_EPT_TYPE_CTRL8 | + USBD_CalcSizeEP(USBD_MAX_PACKET0) ; + UDPHS->UDPHS_EPT[0].UDPHS_EPTCTLENB = UDPHS_EPTCTLENB_RXRDY_TXKL | + UDPHS_EPTCTLENB_TX_COMPLT | + UDPHS_EPTCTLENB_RX_SETUP | + UDPHS_EPTCTLENB_STALL_SNT | + UDPHS_EPTCTLENB_NYET_DIS | + UDPHS_EPTCTLENB_EPT_ENABL; + + +#if (USBD_HS_ENABLE == 1) + U8 * config_desc = USBD_ConfigDescriptor_HS; +#else + U8 * config_desc = USBD_ConfigDescriptor; +#endif + + while (((USB_ENDPOINT_DESCRIPTOR *)config_desc)->bLength > 0) { + if (((USB_ENDPOINT_DESCRIPTOR *)config_desc)->bDescriptorType == USB_ENDPOINT_DESCRIPTOR_TYPE) { + uint32_t num, type, dir, size, banks, interval; + USB_ENDPOINT_DESCRIPTOR *pEPD = (USB_ENDPOINT_DESCRIPTOR *)config_desc; + num = pEPD->bEndpointAddress & 0x0F; + type = pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK; + dir = pEPD->bEndpointAddress >> 7; + interval = pEPD->bInterval; + size = USBD_CalcSizeEP(pEPD->wMaxPacketSize); + banks = 1; + UDPHS->UDPHS_EPT[num].UDPHS_EPTCFG = (interval << 8) | + (banks << 6) | + (type << 4) | + (dir << 3) | + (size << 0) ; + } + config_desc += ((USB_ENDPOINT_DESCRIPTOR *)config_desc)->bLength; + } +} + + +/* + * USB Device Suspend Function + * Called automatically on USB Device Suspend + * Return Value: None + */ + +void USBD_Suspend(void) +{ + UDPHS->UDPHS_IEN &= ~UDPHS_IEN_DET_SUSPD; + UDPHS->UDPHS_IEN |= UDPHS_IEN_WAKE_UP; +} + + +/* + * USB Device Resume Function + * Called automatically on USB Device Resume + * Return Value: None + */ + +void USBD_Resume(void) +{ + UDPHS->UDPHS_IEN &= ~UDPHS_IEN_WAKE_UP; + UDPHS->UDPHS_IEN |= UDPHS_IEN_DET_SUSPD; +} + + +/* + * USB Device Remote Wakeup Function + * Called automatically on USB Device Remote Wakeup + * Return Value: None + */ + +void USBD_WakeUp(void) +{ + UDPHS->UDPHS_IEN |= UDPHS_IEN_UPSTR_RES; + UDPHS->UDPHS_CTRL |= UDPHS_CTRL_REWAKEUP; +} + + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ + +void USBD_WakeUpCfg(BOOL cfg) +{ + if (cfg) { + /* Enable wakeup mechanism */ + } else { + UDPHS->UDPHS_CTRL &= ~UDPHS_CTRL_REWAKEUP; + UDPHS->UDPHS_IEN &= ~UDPHS_IEN_UPSTR_RES; + } +} + + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * setup: Called in setup stage (!=0), else after status stage + * Return Value: None + */ + +void USBD_SetAddress(uint32_t adr, uint32_t setup) +{ + if (setup) { + return; + } + + if (adr) { + UDPHS->UDPHS_CTRL |= (UDPHS_CTRL_FADDR_EN | adr); + } else { + UDPHS->UDPHS_CTRL &= ~(UDPHS_CTRL_FADDR_EN | UDPHS_CTRL_DEV_ADDR_Msk); + } +} + + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ + +void USBD_Configure(BOOL cfg) +{ + /* Performed by Hardware */ +} + + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ + +void USBD_ConfigEP(USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + uint32_t num;//, type, dir, size, banks, interval; + num = pEPD->bEndpointAddress & 0x0F; + /*type = pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK; + dir = pEPD->bEndpointAddress >> 7; + interval = pEPD->bInterval; + size = USBD_CalcSizeEP(pEPD->wMaxPacketSize); + banks = 1; + */ + /* Check if MaxPacketSize fits for EndPoint */ + if (pEPD->wMaxPacketSize <= USBD_GetSizeEP(num)) { + /*UDPHS->UDPHS_EPT[num].UDPHS_EPTCFG = (interval << 8) | + (banks << 6) | + (type << 4) | + (dir << 3) | + //(size << 0) ; + 6;*/ + UDPHS->UDPHS_EPT[num].UDPHS_EPTCTLENB = + (0x1 << 9) | /* Received OUT Data Interrupt Enable */ + (0x1 << 10) | /* Transmitted IN Data Complete Interrupt Enable */ + (0x0 << 4) | /* NYET Disable (Only for High Speed Bulk OUT endpoints) */ + (0x1 << 13) ; /* Stall Sent /ISO CRC Error/Number of Transaction Error */ + } +} + + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ + +void USBD_DirCtrlEP(uint32_t dir) +{ + /* Performed by Hardware */ +} + + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_EnableEP(uint32_t EPNum) +{ + UDPHS->UDPHS_EPT[EPNum & 0x0F].UDPHS_EPTCTLENB = (0x1 << 0); /* EP Enable */ + eptsta_copy[EPNum & 0x0F] = UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTSETSTA; +} + + +/* + * Disable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_DisableEP(uint32_t EPNum) +{ + UDPHS->UDPHS_EPT[EPNum & 0x0F].UDPHS_EPTCTLDIS = (0x1 << 0); /* EP Disable */ + eptsta_copy[EPNum & 0x0F] = UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTSETSTA; +} + + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ResetEP(uint32_t EPNum) +{ + EPNum &= 0x0F; + UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTCLRSTA = (0x1 << 6) | /* Data Toggle Clear*/ + (0x1 << 5); /* Stall Req Set */ + UDPHS->UDPHS_EPTRST |= (1 << EPNum); /* Reset endpoint */ + UDPHS->UDPHS_EPTRST &= ~(1 << EPNum); + eptsta_copy[EPNum] = UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTSETSTA; +} + + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_SetStallEP(uint32_t EPNum) +{ + UDPHS->UDPHS_EPT[EPNum & 0x0F].UDPHS_EPTSETSTA = (0x1 << 5); /* Stall Set */ + eptsta_copy[EPNum & 0x0F] |= 0x1 << 5; +} + + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClrStallEP(uint32_t EPNum) +{ + UDPHS->UDPHS_EPT[EPNum & 0x0F].UDPHS_EPTCLRSTA = (0x1 << 6) | /* Clr Toggle */ + (0x1 << 5); /* Stall Clear*/ + eptsta_copy[EPNum & 0x0F] &= ~(0x1 << 5); +} + + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ + +uint32_t USBD_ReadEP(uint32_t EPNum, uint8_t *pData, uint32_t size) +{ + uint32_t cnt, n, copy_sz; + uint8_t *pEPFIFO; /* Pointer to EP FIFO */ + uint32_t eptsta; + EPNum &= 0x0F; + eptsta = eptsta_copy[EPNum]; + pEPFIFO = (uint8_t *)((uint32_t *)UDPHS_EPTFIFO_BASE + (16384 * EPNum)); + cnt = (eptsta >> 20) & 0x07FF; /* Get by */ + copy_sz = cnt > size ? size : cnt; + + for (n = 0; n < copy_sz; n++) { + *pData++ = *pEPFIFO++; + } + + util_assert(cnt == copy_sz); + + if ((cnt == copy_sz) && (eptsta & (0x1 << 9))) { + UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTCLRSTA = (0x1 << 9); /* Rece OUT Clear */ + } + + /* RX_Setup must be cleared after Setup packet is read */ + if (eptsta & (0x1 << 12)) { + UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTCLRSTA = (0x1 << 12); /* Rece SETUP Clear */ + } + UDPHS->UDPHS_IEN |= (1 << (EPNum + 8)); /* Enable EP int after data read*/ + return (cnt); +} + + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ + +uint32_t USBD_WriteEP(uint32_t EPNum, uint8_t *pData, uint32_t cnt) +{ + uint32_t n; + uint8_t *pEPFIFO; /* Pointer to the endpoint FIFO */ + uint32_t eptsta; + EPNum &= 0x0F; + eptsta = eptsta_copy[EPNum]; + + // Cached value should match the real value + util_assert((UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTSTA & (0x1 << 5)) == (eptsta & (0x1 << 5))); + if (eptsta & (0x1 << 5)) { /* If EP is stall */ + return (cnt); + } + + // Both register and cached value should indicate that the bank is ready (bit 11 clear) + util_assert(!(UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTSTA & (0x1 << 11))); + util_assert(!(eptsta & (0x1 << 11))); + + pEPFIFO = (uint8_t *)((uint32_t *)UDPHS_EPTFIFO_BASE + (16384 * EPNum)); + + for (n = 0; n < cnt; n++) { + *pEPFIFO++ = *pData++; /* Write data to FIFO */ + } + + UDPHS->UDPHS_EPT[EPNum].UDPHS_EPTSETSTA = (0x1 << 11); /* Set packet ready */ + return (cnt); +} + + +/* + * Get USB Device Last Frame Number + * Parameters: None + * Return Value: Frame Number + */ + +uint32_t USBD_GetFrame(void) +{ + uint32_t val; + + if ((UDPHS->UDPHS_FNUM & (1UL << 31)) == 0) { + if (USBD_HighSpeed) { + val = UDPHS->UDPHS_FNUM & 0x7FFFFFFF; + } else { + val = (UDPHS->UDPHS_FNUM & UDPHS_FNUM_FRAME_NUMBER_Msk) >> 3; + } + } else { + val = 0xFFFFFFFF; + } + + return (val); +} + + +#ifdef __RTX +uint32_t LastError; /* Last Error */ + +/* + * Get USB Device Last Error Code + * Parameters: None + * Return Value: Error Code + */ + +uint32_t USBD_GetError(void) +{ + return (LastError); +} +#endif + + +/* + * USB Device Interrupt Service Routine + */ + +void UDPHS_IRQHandler(void) +{ + NVIC_DisableIRQ(UDPHS_IRQn); + USBD_SignalHandler(); +} + +/* + * USB Device Service Routine + */ + +void USBD_Handler(void) +{ + uint32_t intsta, eptsta, n; + intsta = UDPHS->UDPHS_INTSTA & UDPHS->UDPHS_IEN; + + /* End of Bus Reset Interrupt */ + if (intsta & UDPHS_INTSTA_ENDRESET) { + /* Get used speed (HighSpeed or FullSpeed) */ + USBD_HighSpeed = (UDPHS->UDPHS_INTSTA & UDPHS_INTSTA_SPEED) ? 1 : 0; + USBD_Reset(); + usbd_reset_core(); +#ifdef __RTX + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_ENDRESET; + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESET, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Reset_Event) { + USBD_P_Reset_Event(); + } + + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_ENDRESET; +#endif + } + + /* USB Suspend Interrupt */ + if (intsta & UDPHS_INTSTA_DET_SUSPD) { + USBD_Suspend(); +#ifdef __RTX + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_DET_SUSPD; + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SUSPEND, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Suspend_Event) { + USBD_P_Suspend_Event(); + } + + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_DET_SUSPD; +#endif + } + + /* USB Resume Interrupt */ + if (intsta & UDPHS_INTSTA_WAKE_UP) { + USBD_Resume(); +#ifdef __RTX + UDPHS->UDPHS_CLRINT = UDPHS_INTSTA_WAKE_UP; + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESUME, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Resume_Event) { + USBD_P_Resume_Event(); + } + + UDPHS->UDPHS_CLRINT = UDPHS_INTSTA_WAKE_UP; +#endif + } + + /* USB Remote Wakeup Interrupt */ + if (intsta & UDPHS_INTSTA_UPSTR_RES) { + UDPHS->UDPHS_CLRINT = UDPHS_INTSTA_UPSTR_RES; + } + + /* Start of Frame Interrupt */ + if (intsta & UDPHS_INTSTA_INT_SOF) { + /* Process the SOF interrupt even in high speed mode. + The SOF and MICRO_SOF interrupt are never generated at the same + time. Instead, when in high speed mode there is 1 SOF + interrupt and 7 MICRO_SOF interrupts every 1ms. */ + +#ifdef __RTX + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_INT_SOF; + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SOF, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_INT_SOF; +#endif + } + + /* Micro Frame Interrupt */ + if (intsta & UDPHS_INTSTA_MICRO_SOF) { + if (USBD_HighSpeed == 1) { +#ifdef __RTX + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_MICRO_SOF; + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SOF, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_MICRO_SOF; +#endif + + } else { + UDPHS->UDPHS_CLRINT = UDPHS_CLRINT_MICRO_SOF; + } + } + + /* Endpoint Interrupts */ + for (n = 0; n <= USBD_EP_NUM; n++) { + if (intsta & (1 << (n + 8))) { + eptsta = UDPHS->UDPHS_EPT[n].UDPHS_EPTSTA; /* Read EP status */ + eptsta_copy[n] = eptsta; + + /* Data Packet Sent Interrupt */ + if (eptsta & (1 << 10)) { /* Transmitted IN Data Complete Int */ + UDPHS->UDPHS_EPT[n].UDPHS_EPTCLRSTA = (1 << 10); /* Tx IN Clear */ +#ifdef __RTX + + if (USBD_RTX_EPTask[n]) { /* IN Packet */ + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[n]); + } + +#else + + if (USBD_P_EP[n]) { + USBD_P_EP[n](USBD_EVT_IN); + } + +#endif + } + + /* Data Packet Received Interrupt */ + if (eptsta & (1 << 9)) { /* Received OUT Data Interrupt */ + UDPHS->UDPHS_IEN &= ~(1 << (n + 8)); /* Disable EP int until read*/ +#ifdef __RTX + + if (USBD_RTX_EPTask[n]) { /* OUT Packet */ + isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[n]); + } + +#else + + if (USBD_P_EP[n]) { + USBD_P_EP[n](USBD_EVT_OUT); + } + +#endif + } + + /* STALL Packet Sent Interrupt */ + if (eptsta & (0x1 << 13)) { /* Stall Sent */ + if ((UDPHS->UDPHS_EPT[n].UDPHS_EPTCFG & UDPHS_EPTCFG_EPT_TYPE_Msk) == UDPHS_EPTCFG_EPT_TYPE_CTRL8) { +#ifdef __RTX + + if (USBD_RTX_EPTask[n]) { + isr_evt_set(USBD_EVT_IN_STALL, USBD_RTX_EPTask[n]); + } + +#else + + if (USBD_P_EP[n]) { + USBD_P_EP[n](USBD_EVT_IN_STALL); + } + +#endif + } + + UDPHS->UDPHS_EPT[n].UDPHS_EPTCLRSTA = UDPHS_EPTCLRSTA_STALL_SNT; + } + + /* Setup Packet Received Interrupt */ + if (eptsta & (0x1 << 12)) { /* Received SETUP Interrupt */ + UDPHS->UDPHS_IEN &= ~(1 << (n + 8)); /* Disable EP int until read*/ +#ifdef __RTX + + if (USBD_RTX_EPTask[n]) { /* SETUP Packet */ + isr_evt_set(USBD_EVT_SETUP, USBD_RTX_EPTask[n]); + } + +#else + + if (USBD_P_EP[n]) { + USBD_P_EP[n](USBD_EVT_SETUP); + } + +#endif + } + } + } + + NVIC_EnableIRQ(UDPHS_IRQn); +} diff --git a/port/dw/usb_dc_dw.c b/port/dw/usb_dc_dw.c new file mode 100644 index 00000000..b427342d --- /dev/null +++ b/port/dw/usb_dc_dw.c @@ -0,0 +1,1212 @@ +/* usb_dc_dw.c - USB DesignWare device controller driver */ + +#define DT_DRV_COMPAT snps_designware_usb + +/* + * Copyright (c) 2016 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB DesignWare device controller driver + * + * USB DesignWare device controller driver. The driver implements the low + * level control routines to deal directly with the hardware. + */ + +#include +#include +#include +#include +#include "usb_dw_registers.h" +#include + +#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(usb_dc_dw); + +/* Number of SETUP back-to-back packets */ +#define USB_DW_SUP_CNT (1) + +/* + * USB endpoint private structure. + */ +struct usb_ep_ctrl_prv { + uint8_t ep_ena; + uint8_t fifo_num; + uint32_t fifo_size; + uint16_t mps; /* Max ep pkt size */ + usb_dc_ep_callback cb;/* Endpoint callback function */ + uint32_t data_len; +}; + +/* + * USB controller private structure. + */ +struct usb_dw_ctrl_prv { + usb_dc_status_callback status_cb; + struct usb_ep_ctrl_prv in_ep_ctrl[USB_DW_IN_EP_NUM]; + struct usb_ep_ctrl_prv out_ep_ctrl[USB_DW_OUT_EP_NUM]; + int n_tx_fifos; + uint8_t attached; +}; + + +static struct usb_dw_ctrl_prv usb_dw_ctrl; + +static void usb_dw_reg_dump(void) +{ + uint8_t i; + + LOG_DBG("USB registers: GOTGCTL : 0x%x GOTGINT : 0x%x GAHBCFG : " + "0x%x", USB_DW->gotgctl, USB_DW->gotgint, USB_DW->gahbcfg); + LOG_DBG(" GUSBCFG : 0x%x GINTSTS : 0x%x GINTMSK : 0x%x", + USB_DW->gusbcfg, USB_DW->gintsts, USB_DW->gintmsk); + LOG_DBG(" DCFG : 0x%x DCTL : 0x%x DSTS : 0x%x", + USB_DW->dcfg, USB_DW->dctl, USB_DW->dsts); + LOG_DBG(" DIEPMSK : 0x%x DOEPMSK : 0x%x DAINT : 0x%x", + USB_DW->diepmsk, USB_DW->doepmsk, USB_DW->daint); + LOG_DBG(" DAINTMSK: 0x%x GHWCFG1 : 0x%x GHWCFG2 : 0x%x", + USB_DW->daintmsk, USB_DW->ghwcfg1, USB_DW->ghwcfg2); + LOG_DBG(" GHWCFG3 : 0x%x GHWCFG4 : 0x%x", + USB_DW->ghwcfg3, USB_DW->ghwcfg4); + + for (i = 0U; i < USB_DW_OUT_EP_NUM; i++) { + LOG_DBG("\n EP %d registers: DIEPCTL : 0x%x DIEPINT : " + "0x%x", i, USB_DW->in_ep_reg[i].diepctl, + USB_DW->in_ep_reg[i].diepint); + LOG_DBG(" DIEPTSIZ: 0x%x DIEPDMA : 0x%x DOEPCTL : " + "0x%x", USB_DW->in_ep_reg[i].dieptsiz, + USB_DW->in_ep_reg[i].diepdma, + USB_DW->out_ep_reg[i].doepctl); + LOG_DBG(" DOEPINT : 0x%x DOEPTSIZ: 0x%x DOEPDMA : " + "0x%x", USB_DW->out_ep_reg[i].doepint, + USB_DW->out_ep_reg[i].doeptsiz, + USB_DW->out_ep_reg[i].doepdma); + } +} + +static uint8_t usb_dw_ep_is_valid(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + /* Check if ep enabled */ + if ((USB_EP_DIR_IS_OUT(ep)) && ep_idx < USB_DW_OUT_EP_NUM) { + return 1; + } else if ((USB_EP_DIR_IS_IN(ep)) && ep_idx < USB_DW_IN_EP_NUM) { + return 1; + } + + return 0; +} + +static uint8_t usb_dw_ep_is_enabled(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + /* Check if ep enabled */ + if ((USB_EP_DIR_IS_OUT(ep)) && + usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena) { + return 1; + } else if ((USB_EP_DIR_IS_IN(ep)) && + usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena) { + return 1; + } + + return 0; +} + +static inline void usb_dw_udelay(uint32_t us) +{ + k_busy_wait(us); +} + +static int usb_dw_reset(void) +{ + uint32_t cnt = 0U; + + /* Wait for AHB master idle state. */ + while (!(USB_DW->grstctl & USB_DW_GRSTCTL_AHB_IDLE)) { + usb_dw_udelay(1); + + if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { + LOG_ERR("USB reset HANG! AHB Idle GRSTCTL=0x%08x", + USB_DW->grstctl); + return -EIO; + } + } + + /* Core Soft Reset */ + cnt = 0U; + USB_DW->grstctl |= USB_DW_GRSTCTL_C_SFT_RST; + + do { + if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { + LOG_DBG("USB reset HANG! Soft Reset GRSTCTL=0x%08x", + USB_DW->grstctl); + return -EIO; + } + usb_dw_udelay(1); + } while (USB_DW->grstctl & USB_DW_GRSTCTL_C_SFT_RST); + + /* Wait for 3 PHY Clocks */ + usb_dw_udelay(100); + + return 0; +} + +static int usb_dw_num_dev_eps(void) +{ + return (USB_DW->ghwcfg2 >> 10) & 0xf; +} + +static void usb_dw_flush_tx_fifo(int ep) +{ + int fnum = usb_dw_ctrl.in_ep_ctrl[ep].fifo_num; + + USB_DW->grstctl = (fnum << 6) | (1<<5); + while (USB_DW->grstctl & (1<<5)) { + } +} + +static int usb_dw_tx_fifo_avail(int ep) +{ + return USB_DW->in_ep_reg[ep].dtxfsts & + USB_DW_DTXFSTS_TXF_SPC_AVAIL_MASK; +} + +/* Choose a FIFO number for an IN endpoint */ +static int usb_dw_set_fifo(uint8_t ep) +{ + int ep_idx = USB_EP_GET_IDX(ep); + volatile uint32_t *reg = &USB_DW->in_ep_reg[ep_idx].diepctl; + uint32_t val; + int fifo = 0; + int ded_fifo = !!(USB_DW->ghwcfg4 & USB_DW_HWCFG4_DEDFIFOMODE); + + if (!ded_fifo) { + /* No support for shared-FIFO mode yet, existing + * Zephyr hardware doesn't use it + */ + return -ENOTSUP; + } + + /* In dedicated-FIFO mode, all IN endpoints must have a unique + * FIFO number associated with them in the TXFNUM field of + * DIEPCTLx, with EP0 always being assigned to FIFO zero (the + * reset default, so we don't touch it). + * + * FIXME: would be better (c.f. the dwc2 driver in Linux) to + * choose a FIFO based on the hardware depth: we want the + * smallest one that fits our configured maximum packet size + * for the endpoint. This just picks the next available one. + */ + if (ep_idx != 0) { + fifo = ++usb_dw_ctrl.n_tx_fifos; + if (fifo >= usb_dw_num_dev_eps()) { + return -EINVAL; + } + + reg = &USB_DW->in_ep_reg[ep_idx].diepctl; + val = *reg & ~USB_DW_DEPCTL_TXFNUM_MASK; + val |= fifo << USB_DW_DEPCTL_TXFNUM_OFFSET; + *reg = val; + } + + usb_dw_ctrl.in_ep_ctrl[ep_idx].fifo_num = fifo; + + usb_dw_flush_tx_fifo(ep_idx); + + val = usb_dw_tx_fifo_avail(ep_idx); + usb_dw_ctrl.in_ep_ctrl[ep_idx].fifo_size = val; + + return 0; +} + +static int usb_dw_ep_set(uint8_t ep, + uint32_t ep_mps, enum usb_dc_ep_transfer_type ep_type) +{ + volatile uint32_t *p_depctl; + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + LOG_DBG("%s ep %x, mps %d, type %d", __func__, ep, ep_mps, ep_type); + + if (USB_EP_DIR_IS_OUT(ep)) { + p_depctl = &USB_DW->out_ep_reg[ep_idx].doepctl; + usb_dw_ctrl.out_ep_ctrl[ep_idx].mps = ep_mps; + } else { + p_depctl = &USB_DW->in_ep_reg[ep_idx].diepctl; + usb_dw_ctrl.in_ep_ctrl[ep_idx].mps = ep_mps; + } + + if (!ep_idx) { + /* Set max packet size for EP0 */ + *p_depctl &= ~USB_DW_DEPCTL0_MSP_MASK; + + switch (ep_mps) { + case 8: + *p_depctl |= USB_DW_DEPCTL0_MSP_8 << + USB_DW_DEPCTL_MSP_OFFSET; + break; + case 16: + *p_depctl |= USB_DW_DEPCTL0_MSP_16 << + USB_DW_DEPCTL_MSP_OFFSET; + break; + case 32: + *p_depctl |= USB_DW_DEPCTL0_MSP_32 << + USB_DW_DEPCTL_MSP_OFFSET; + break; + case 64: + *p_depctl |= USB_DW_DEPCTL0_MSP_64 << + USB_DW_DEPCTL_MSP_OFFSET; + break; + default: + return -EINVAL; + } + /* No need to set EP0 type */ + } else { + /* Set max packet size for EP */ + if (ep_mps > (USB_DW_DEPCTLn_MSP_MASK >> + USB_DW_DEPCTL_MSP_OFFSET)) { + return -EINVAL; + } + + *p_depctl &= ~USB_DW_DEPCTLn_MSP_MASK; + *p_depctl |= ep_mps << USB_DW_DEPCTL_MSP_OFFSET; + + /* Set endpoint type */ + *p_depctl &= ~USB_DW_DEPCTL_EP_TYPE_MASK; + + switch (ep_type) { + case USB_DC_EP_CONTROL: + *p_depctl |= USB_DW_DEPCTL_EP_TYPE_CONTROL << + USB_DW_DEPCTL_EP_TYPE_OFFSET; + break; + case USB_DC_EP_BULK: + *p_depctl |= USB_DW_DEPCTL_EP_TYPE_BULK << + USB_DW_DEPCTL_EP_TYPE_OFFSET; + break; + case USB_DC_EP_INTERRUPT: + *p_depctl |= USB_DW_DEPCTL_EP_TYPE_INTERRUPT << + USB_DW_DEPCTL_EP_TYPE_OFFSET; + break; + default: + return -EINVAL; + } + + /* sets the Endpoint Data PID to DATA0 */ + *p_depctl |= USB_DW_DEPCTL_SETDOPID; + } + + if (USB_EP_DIR_IS_IN(ep)) { + int ret = usb_dw_set_fifo(ep); + + if (ret) { + return ret; + } + } + + return 0; +} + +static void usb_dw_prep_rx(const uint8_t ep, uint8_t setup) +{ + enum usb_dw_out_ep_idx ep_idx = USB_EP_GET_IDX(ep); + uint32_t ep_mps = usb_dw_ctrl.out_ep_ctrl[ep_idx].mps; + + /* Set max RX size to EP mps so we get an interrupt + * each time a packet is received + */ + + USB_DW->out_ep_reg[ep_idx].doeptsiz = + (USB_DW_SUP_CNT << USB_DW_DOEPTSIZ_SUP_CNT_OFFSET) | + (1 << USB_DW_DEPTSIZ_PKT_CNT_OFFSET) | ep_mps; + + /* Clear NAK and enable ep */ + if (!setup) { + USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_CNAK; + } + + USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_EP_ENA; + + LOG_DBG("USB OUT EP%d armed", ep_idx); +} + +static int usb_dw_tx(uint8_t ep, const uint8_t *const data, + uint32_t data_len) +{ + enum usb_dw_in_ep_idx ep_idx = USB_EP_GET_IDX(ep); + uint32_t max_xfer_size, max_pkt_cnt, pkt_cnt, avail_space; + uint32_t ep_mps = usb_dw_ctrl.in_ep_ctrl[ep_idx].mps; + unsigned int key; + uint32_t i; + + /* Wait for FIFO space available */ + do { + avail_space = usb_dw_tx_fifo_avail(ep_idx); + if (avail_space == usb_dw_ctrl.in_ep_ctrl[ep_idx].fifo_size) { + break; + } + /* Make sure we don't hog the CPU */ + k_yield(); + } while (1); + + key = irq_lock(); + + avail_space *= 4U; + if (!avail_space) { + LOG_ERR("USB IN EP%d no space available, DTXFSTS %x", ep_idx, + USB_DW->in_ep_reg[ep_idx].dtxfsts); + irq_unlock(key); + return -EAGAIN; + } + + /* For now tx-fifo sizes are not configured (cf usb_dw_set_fifo). Here + * we force available fifo size to be a multiple of ep mps in order to + * prevent splitting data incorrectly. + */ + avail_space -= avail_space % ep_mps; + if (data_len > avail_space) { + data_len = avail_space; + } + + if (data_len != 0U) { + /* Get max packet size and packet count for ep */ + if (ep_idx == USB_DW_IN_EP_0) { + max_pkt_cnt = + USB_DW_DIEPTSIZ0_PKT_CNT_MASK >> + USB_DW_DEPTSIZ_PKT_CNT_OFFSET; + max_xfer_size = + USB_DW_DEPTSIZ0_XFER_SIZE_MASK >> + USB_DW_DEPTSIZ_XFER_SIZE_OFFSET; + } else { + max_pkt_cnt = + USB_DW_DIEPTSIZn_PKT_CNT_MASK >> + USB_DW_DEPTSIZ_PKT_CNT_OFFSET; + max_xfer_size = + USB_DW_DEPTSIZn_XFER_SIZE_MASK >> + USB_DW_DEPTSIZ_XFER_SIZE_OFFSET; + } + + /* Check if transfer len is too big */ + if (data_len > max_xfer_size) { + LOG_WRN("USB IN EP%d len too big (%d->%d)", ep_idx, + data_len, max_xfer_size); + data_len = max_xfer_size; + } + + /* + * Program the transfer size and packet count as follows: + * + * transfer size = N * ep_maxpacket + short_packet + * pktcnt = N + (short_packet exist ? 1 : 0) + */ + + pkt_cnt = (data_len + ep_mps - 1) / ep_mps; + if (pkt_cnt > max_pkt_cnt) { + LOG_WRN("USB IN EP%d pkt count too big (%d->%d)", + ep_idx, pkt_cnt, pkt_cnt); + pkt_cnt = max_pkt_cnt; + data_len = pkt_cnt * ep_mps; + } + } else { + /* Zero length packet */ + pkt_cnt = 1U; + } + + /* Set number of packets and transfer size */ + USB_DW->in_ep_reg[ep_idx].dieptsiz = + (pkt_cnt << USB_DW_DEPTSIZ_PKT_CNT_OFFSET) | data_len; + + /* Clear NAK and enable ep */ + USB_DW->in_ep_reg[ep_idx].diepctl |= (USB_DW_DEPCTL_EP_ENA | + USB_DW_DEPCTL_CNAK); + + /* + * Write data to FIFO, make sure that we are protected against + * other USB register accesses. According to "DesignWare Cores + * USB 1.1/2.0 Device Subsystem-AHB/VCI Databook": "During FIFO + * access, the application must not access the UDC/Subsystem + * registers or vendor registers (for ULPI mode). After starting + * to access a FIFO, the application must complete the transaction + * before accessing the register." + */ + for (i = 0U; i < data_len; i += 4U) { + uint32_t val = data[i]; + + if (i + 1 < data_len) { + val |= ((uint32_t)data[i+1]) << 8; + } + if (i + 2 < data_len) { + val |= ((uint32_t)data[i+2]) << 16; + } + if (i + 3 < data_len) { + val |= ((uint32_t)data[i+3]) << 24; + } + + USB_DW_EP_FIFO(ep_idx) = val; + } + + irq_unlock(key); + + LOG_DBG("USB IN EP%d write %u bytes", ep_idx, data_len); + + return data_len; +} + +static int usb_dw_init(void) +{ + uint8_t ep; + int ret; + + ret = usb_dw_reset(); + if (ret) { + return ret; + } + +#ifdef CONFIG_USB_DW_USB_2_0 + /* set the PHY interface to be 16-bit UTMI */ + USB_DW->gusbcfg = (USB_DW->gusbcfg & ~USB_DW_GUSBCFG_PHY_IF_MASK) | + USB_DW_GUSBCFG_PHY_IF_16_BIT; + + /* Set USB2.0 High Speed */ + USB_DW->dcfg |= USB_DW_DCFG_DEV_SPD_USB2_HS; +#else + /* Set device speed to Full Speed */ + USB_DW->dcfg |= USB_DW_DCFG_DEV_SPD_FS; +#endif + + /* Set NAK for all OUT EPs */ + for (ep = 0U; ep < USB_DW_OUT_EP_NUM; ep++) { + USB_DW->out_ep_reg[ep].doepctl = USB_DW_DEPCTL_SNAK; + } + + /* Enable global interrupts */ + USB_DW->gintmsk = USB_DW_GINTSTS_OEP_INT | + USB_DW_GINTSTS_IEP_INT | + USB_DW_GINTSTS_ENUM_DONE | + USB_DW_GINTSTS_USB_RST | + USB_DW_GINTSTS_WK_UP_INT | + USB_DW_GINTSTS_USB_SUSP; + + /* Enable global interrupt */ + USB_DW->gahbcfg |= USB_DW_GAHBCFG_GLB_INTR_MASK; + + /* Disable soft disconnect */ + USB_DW->dctl &= ~USB_DW_DCTL_SFT_DISCON; + + usb_dw_reg_dump(); + + return 0; +} + +static void usb_dw_handle_reset(void) +{ + LOG_DBG("USB RESET event"); + + /* Inform upper layers */ + if (usb_dw_ctrl.status_cb) { + usb_dw_ctrl.status_cb(USB_DC_RESET, NULL); + } + + /* Clear device address during reset. */ + USB_DW->dcfg &= ~USB_DW_DCFG_DEV_ADDR_MASK; + + /* enable global EP interrupts */ + USB_DW->doepmsk = 0U; + USB_DW->gintmsk |= USB_DW_GINTSTS_RX_FLVL; + USB_DW->diepmsk |= USB_DW_DIEPINT_XFER_COMPL; +} + +static void usb_dw_handle_enum_done(void) +{ + uint32_t speed; + + speed = (USB_DW->dsts & ~USB_DW_DSTS_ENUM_SPD_MASK) >> + USB_DW_DSTS_ENUM_SPD_OFFSET; + + LOG_DBG("USB ENUM DONE event, %s speed detected", + speed == USB_DW_DSTS_ENUM_LS ? "Low" : "Full"); + + /* Inform upper layers */ + if (usb_dw_ctrl.status_cb) { + usb_dw_ctrl.status_cb(USB_DC_CONNECTED, NULL); + } +} + +/* USB ISR handler */ +static inline void usb_dw_int_rx_flvl_handler(void) +{ + uint32_t grxstsp = USB_DW->grxstsp; + uint32_t status, xfer_size; + uint8_t ep_idx; + usb_dc_ep_callback ep_cb; + + /* Packet in RX FIFO */ + + ep_idx = grxstsp & USB_DW_GRXSTSR_EP_NUM_MASK; + status = (grxstsp & USB_DW_GRXSTSR_PKT_STS_MASK) >> + USB_DW_GRXSTSR_PKT_STS_OFFSET; + xfer_size = (grxstsp & USB_DW_GRXSTSR_PKT_CNT_MASK) >> + USB_DW_GRXSTSR_PKT_CNT_OFFSET; + + LOG_DBG("USB OUT EP%u: RX_FLVL status %u, size %u", + ep_idx, status, xfer_size); + + usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len = xfer_size; + ep_cb = usb_dw_ctrl.out_ep_ctrl[ep_idx].cb; + + switch (status) { + case USB_DW_GRXSTSR_PKT_STS_SETUP: + /* Call the registered callback if any */ + if (ep_cb) { + ep_cb(USB_EP_GET_ADDR(ep_idx, USB_EP_DIR_OUT), + USB_DC_EP_SETUP); + } + + break; + case USB_DW_GRXSTSR_PKT_STS_OUT_DATA: + if (ep_cb) { + ep_cb(USB_EP_GET_ADDR(ep_idx, USB_EP_DIR_OUT), + USB_DC_EP_DATA_OUT); + } + + break; + case USB_DW_GRXSTSR_PKT_STS_OUT_DATA_DONE: + case USB_DW_GRXSTSR_PKT_STS_SETUP_DONE: + break; + default: + break; + } +} + +static inline void usb_dw_int_iep_handler(void) +{ + uint32_t ep_int_status; + uint8_t ep_idx; + usb_dc_ep_callback ep_cb; + + for (ep_idx = 0U; ep_idx < USB_DW_IN_EP_NUM; ep_idx++) { + if (USB_DW->daint & USB_DW_DAINT_IN_EP_INT(ep_idx)) { + /* Read IN EP interrupt status */ + ep_int_status = USB_DW->in_ep_reg[ep_idx].diepint & + USB_DW->diepmsk; + + /* Clear IN EP interrupts */ + USB_DW->in_ep_reg[ep_idx].diepint = ep_int_status; + + LOG_DBG("USB IN EP%u interrupt status: 0x%x", + ep_idx, ep_int_status); + + ep_cb = usb_dw_ctrl.in_ep_ctrl[ep_idx].cb; + if (ep_cb && + (ep_int_status & USB_DW_DIEPINT_XFER_COMPL)) { + + /* Call the registered callback */ + ep_cb(USB_EP_GET_ADDR(ep_idx, USB_EP_DIR_IN), + USB_DC_EP_DATA_IN); + } + } + } + + /* Clear interrupt. */ + USB_DW->gintsts = USB_DW_GINTSTS_IEP_INT; +} + +static inline void usb_dw_int_oep_handler(void) +{ + uint32_t ep_int_status; + uint8_t ep_idx; + + for (ep_idx = 0U; ep_idx < USB_DW_OUT_EP_NUM; ep_idx++) { + if (USB_DW->daint & USB_DW_DAINT_OUT_EP_INT(ep_idx)) { + /* Read OUT EP interrupt status */ + ep_int_status = USB_DW->out_ep_reg[ep_idx].doepint & + USB_DW->doepmsk; + + /* Clear OUT EP interrupts */ + USB_DW->out_ep_reg[ep_idx].doepint = ep_int_status; + + LOG_DBG("USB OUT EP%u interrupt status: 0x%x\n", + ep_idx, ep_int_status); + } + } + + /* Clear interrupt. */ + USB_DW->gintsts = USB_DW_GINTSTS_OEP_INT; +} + +static void usb_dw_isr_handler(const void *unused) +{ + uint32_t int_status; + + ARG_UNUSED(unused); + + /* Read interrupt status */ + while ((int_status = (USB_DW->gintsts & USB_DW->gintmsk))) { + + LOG_DBG("USB GINTSTS 0x%x", int_status); + + if (int_status & USB_DW_GINTSTS_USB_RST) { + /* Clear interrupt. */ + USB_DW->gintsts = USB_DW_GINTSTS_USB_RST; + + /* Reset detected */ + usb_dw_handle_reset(); + } + + if (int_status & USB_DW_GINTSTS_ENUM_DONE) { + /* Clear interrupt. */ + USB_DW->gintsts = USB_DW_GINTSTS_ENUM_DONE; + + /* Enumeration done detected */ + usb_dw_handle_enum_done(); + } + + if (int_status & USB_DW_GINTSTS_USB_SUSP) { + /* Clear interrupt. */ + USB_DW->gintsts = USB_DW_GINTSTS_USB_SUSP; + + if (usb_dw_ctrl.status_cb) { + usb_dw_ctrl.status_cb(USB_DC_SUSPEND, NULL); + } + } + + if (int_status & USB_DW_GINTSTS_WK_UP_INT) { + /* Clear interrupt. */ + USB_DW->gintsts = USB_DW_GINTSTS_WK_UP_INT; + + if (usb_dw_ctrl.status_cb) { + usb_dw_ctrl.status_cb(USB_DC_RESUME, NULL); + } + } + + if (int_status & USB_DW_GINTSTS_RX_FLVL) { + /* Packet in RX FIFO */ + usb_dw_int_rx_flvl_handler(); + } + + if (int_status & USB_DW_GINTSTS_IEP_INT) { + /* IN EP interrupt */ + usb_dw_int_iep_handler(); + } + + if (int_status & USB_DW_GINTSTS_OEP_INT) { + /* No OUT interrupt expected in FIFO mode, + * just clear interruot + */ + usb_dw_int_oep_handler(); + } + } +} + +int usb_dc_attach(void) +{ + int ret; + + if (usb_dw_ctrl.attached) { + return 0; + } + + ret = usb_dw_init(); + if (ret) { + return ret; + } + + /* Connect and enable USB interrupt */ + IRQ_CONNECT(DT_INST_IRQN(0), + DT_INST_IRQ(0, priority), + usb_dw_isr_handler, 0, + DT_INST_IRQ(0, sense)); + irq_enable(DT_INST_IRQN(0)); + + usb_dw_ctrl.attached = 1U; + + return 0; +} + +int usb_dc_detach(void) +{ + if (!usb_dw_ctrl.attached) { + return 0; + } + + irq_disable(DT_INST_IRQN(0)); + + /* Enable soft disconnect */ + USB_DW->dctl |= USB_DW_DCTL_SFT_DISCON; + + usb_dw_ctrl.attached = 0U; + + return 0; +} + +int usb_dc_reset(void) +{ + int ret; + + ret = usb_dw_reset(); + + /* Clear private data */ + (void)memset(&usb_dw_ctrl, 0, sizeof(usb_dw_ctrl)); + + return ret; +} + +int usb_dc_set_address(const uint8_t addr) +{ + if (addr > (USB_DW_DCFG_DEV_ADDR_MASK >> USB_DW_DCFG_DEV_ADDR_OFFSET)) { + return -EINVAL; + } + + USB_DW->dcfg &= ~USB_DW_DCFG_DEV_ADDR_MASK; + USB_DW->dcfg |= addr << USB_DW_DCFG_DEV_ADDR_OFFSET; + + return 0; +} + +int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) +{ + uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); + + LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps, + cfg->ep_type); + + if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { + LOG_ERR("invalid endpoint configuration"); + return -1; + } + + if (cfg->ep_mps > DW_USB_MAX_PACKET_SIZE) { + LOG_WRN("unsupported packet size"); + return -1; + } + + if ((USB_EP_DIR_IS_OUT(cfg->ep_addr)) && + (ep_idx >= DW_USB_OUT_EP_NUM)) { + LOG_WRN("OUT endpoint address out of range"); + return -1; + } + + if ((USB_EP_DIR_IS_IN(cfg->ep_addr)) && + (ep_idx >= DW_USB_IN_EP_NUM)) { + LOG_WRN("IN endpoint address out of range"); + return -1; + } + + return 0; +} + +int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg) +{ + uint8_t ep; + + if (!ep_cfg) { + return -EINVAL; + } + + ep = ep_cfg->ep_addr; + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + usb_dw_ep_set(ep, ep_cfg->ep_mps, ep_cfg->ep_type); + + return 0; +} + +int usb_dc_ep_set_stall(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + if (USB_EP_DIR_IS_OUT(ep)) { + USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_STALL; + } else { + USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_STALL; + } + + return 0; +} + +int usb_dc_ep_clear_stall(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + if (!ep_idx) { + /* Not possible to clear stall for EP0 */ + return -EINVAL; + } + + if (USB_EP_DIR_IS_OUT(ep)) { + USB_DW->out_ep_reg[ep_idx].doepctl &= ~USB_DW_DEPCTL_STALL; + } else { + USB_DW->in_ep_reg[ep_idx].diepctl &= ~USB_DW_DEPCTL_STALL; + } + + return 0; +} + +int usb_dc_ep_halt(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + volatile uint32_t *p_depctl; + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + if (!ep_idx) { + /* Cannot disable EP0, just set stall */ + usb_dc_ep_set_stall(ep); + } else { + if (USB_EP_DIR_IS_OUT(ep)) { + p_depctl = &USB_DW->out_ep_reg[ep_idx].doepctl; + } else { + p_depctl = &USB_DW->in_ep_reg[ep_idx].diepctl; + } + + /* Set STALL and disable endpoint if enabled */ + if (*p_depctl & USB_DW_DEPCTL_EP_ENA) { + *p_depctl |= USB_DW_DEPCTL_EP_DIS | USB_DW_DEPCTL_STALL; + } else { + *p_depctl |= USB_DW_DEPCTL_STALL; + } + } + + return 0; +} + +int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + if (!stalled) { + return -EINVAL; + } + + *stalled = 0U; + if (USB_EP_DIR_IS_OUT(ep)) { + if (USB_DW->out_ep_reg[ep_idx].doepctl & USB_DW_DEPCTL_STALL) { + *stalled = 1U; + } + } else { + if (USB_DW->in_ep_reg[ep_idx].diepctl & USB_DW_DEPCTL_STALL) { + *stalled = 1U; + } + } + + return 0; +} + +int usb_dc_ep_enable(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + /* enable EP interrupts */ + if (USB_EP_DIR_IS_OUT(ep)) { + USB_DW->daintmsk |= USB_DW_DAINT_OUT_EP_INT(ep_idx); + } else { + USB_DW->daintmsk |= USB_DW_DAINT_IN_EP_INT(ep_idx); + } + + /* Activate Ep */ + if (USB_EP_DIR_IS_OUT(ep)) { + USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_USB_ACT_EP; + usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena = 1U; + } else { + USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_USB_ACT_EP; + usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena = 1U; + } + + if (USB_EP_DIR_IS_OUT(ep) && + usb_dw_ctrl.out_ep_ctrl[ep_idx].cb != usb_transfer_ep_callback) { + /* Start reading now, except for transfer managed eps */ + usb_dw_prep_rx(ep, 0); + } + + return 0; +} + +int usb_dc_ep_disable(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + /* Disable EP interrupts */ + if (USB_EP_DIR_IS_OUT(ep)) { + USB_DW->daintmsk &= ~USB_DW_DAINT_OUT_EP_INT(ep_idx); + USB_DW->doepmsk &= ~USB_DW_DOEPINT_SET_UP; + } else { + USB_DW->daintmsk &= ~USB_DW_DAINT_IN_EP_INT(ep_idx); + USB_DW->diepmsk &= ~USB_DW_DIEPINT_XFER_COMPL; + USB_DW->gintmsk &= ~USB_DW_GINTSTS_RX_FLVL; + } + + /* De-activate, disable and set NAK for Ep */ + if (USB_EP_DIR_IS_OUT(ep)) { + USB_DW->out_ep_reg[ep_idx].doepctl &= + ~(USB_DW_DEPCTL_USB_ACT_EP | + USB_DW_DEPCTL_EP_ENA | + USB_DW_DEPCTL_SNAK); + usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena = 0U; + } else { + USB_DW->in_ep_reg[ep_idx].diepctl &= + ~(USB_DW_DEPCTL_USB_ACT_EP | + USB_DW_DEPCTL_EP_ENA | + USB_DW_DEPCTL_SNAK); + usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena = 0U; + } + + return 0; +} + +int usb_dc_ep_flush(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint32_t cnt; + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + if (USB_EP_DIR_IS_OUT(ep)) { + /* RX FIFO is global and cannot be flushed per EP */ + return -EINVAL; + } + + /* Each endpoint has dedicated Tx FIFO */ + USB_DW->grstctl |= ep_idx << USB_DW_GRSTCTL_TX_FNUM_OFFSET; + USB_DW->grstctl |= USB_DW_GRSTCTL_TX_FFLSH; + + cnt = 0U; + + do { + if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { + LOG_ERR("USB TX FIFO flush HANG!"); + return -EIO; + } + usb_dw_udelay(1); + } while (USB_DW->grstctl & USB_DW_GRSTCTL_TX_FFLSH); + + return 0; +} + +int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data, + const uint32_t data_len, uint32_t * const ret_bytes) +{ + int ret; + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + /* Check if IN ep */ + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_IN) { + return -EINVAL; + } + + /* Check if ep enabled */ + if (!usb_dw_ep_is_enabled(ep)) { + return -EINVAL; + } + + ret = usb_dw_tx(ep, data, data_len); + if (ret < 0) { + return ret; + } + + if (ret_bytes) { + *ret_bytes = ret; + } + + return 0; +} + +int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, + uint32_t *read_bytes) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint32_t i, j, data_len, bytes_to_copy; + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + /* Check if OUT ep */ + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { + LOG_ERR("Wrong endpoint direction"); + return -EINVAL; + } + + /* Allow to read 0 bytes */ + if (!data && max_data_len) { + LOG_ERR("Wrong arguments"); + return -EINVAL; + } + + /* Check if ep enabled */ + if (!usb_dw_ep_is_enabled(ep)) { + LOG_ERR("Not enabled endpoint"); + return -EINVAL; + } + + data_len = usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len; + + if (!data && !max_data_len) { + /* When both buffer and max data to read are zero return + * the available data in buffer + */ + if (read_bytes) { + *read_bytes = data_len; + } + return 0; + } + + + if (data_len > max_data_len) { + LOG_ERR("Not enough room to copy all the rcvd data!"); + bytes_to_copy = max_data_len; + } else { + bytes_to_copy = data_len; + } + + LOG_DBG("Read EP%d, req %d, read %d bytes", ep, max_data_len, + bytes_to_copy); + + /* Data in the FIFOs is always stored per 32-bit words */ + for (i = 0U; i < (bytes_to_copy & ~0x3); i += 4U) { + *(uint32_t *)(data + i) = USB_DW_EP_FIFO(ep_idx); + } + if (bytes_to_copy & 0x3) { + /* Not multiple of 4 */ + uint32_t last_dw = USB_DW_EP_FIFO(ep_idx); + + for (j = 0U; j < (bytes_to_copy & 0x3); j++) { + *(data + i + j) = + (sys_cpu_to_le32(last_dw) >> (j * 8U)) & 0xFF; + } + } + + usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len -= bytes_to_copy; + + if (read_bytes) { + *read_bytes = bytes_to_copy; + } + + return 0; + +} + +int usb_dc_ep_read_continue(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + /* Check if OUT ep */ + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { + LOG_ERR("Wrong endpoint direction"); + return -EINVAL; + } + + if (!usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len) { + usb_dw_prep_rx(ep_idx, 0); + } + + return 0; +} + +int usb_dc_ep_read(const uint8_t ep, uint8_t *const data, + const uint32_t max_data_len, uint32_t * const read_bytes) +{ + if (usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes) != 0) { + return -EINVAL; + } + + if (!data && !max_data_len) { + /* When both buffer and max data to read are zero the above + * call would fetch the data len and we simply return. + */ + return 0; + } + + if (usb_dc_ep_read_continue(ep) != 0) { + return -EINVAL; + } + + return 0; +} + +int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + if (USB_EP_DIR_IS_IN(ep)) { + usb_dw_ctrl.in_ep_ctrl[ep_idx].cb = cb; + } else { + usb_dw_ctrl.out_ep_ctrl[ep_idx].cb = cb; + } + + return 0; +} + +void usb_dc_set_status_callback(const usb_dc_status_callback cb) +{ + usb_dw_ctrl.status_cb = cb; +} + +int usb_dc_ep_mps(const uint8_t ep) +{ + enum usb_dw_out_ep_idx ep_idx = USB_EP_GET_IDX(ep); + + if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { + LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); + return -EINVAL; + } + + if (USB_EP_DIR_IS_OUT(ep)) { + return usb_dw_ctrl.out_ep_ctrl[ep_idx].mps; + } else { + return usb_dw_ctrl.in_ep_ctrl[ep_idx].mps; + } +} diff --git a/port/freescale/usb_dc_kinetis.c b/port/freescale/usb_dc_kinetis.c new file mode 100644 index 00000000..24cef356 --- /dev/null +++ b/port/freescale/usb_dc_kinetis.c @@ -0,0 +1,1048 @@ +/* usb_dc_kinetis.c - Kinetis USBFSOTG usb device driver */ + +/* + * Copyright (c) 2017 PHYTEC Messtechnik GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_kinetis_usbd + +#include +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(usb_dc_kinetis); + +#define NUM_OF_EP_MAX DT_INST_PROP(0, num_bidir_endpoints) + +#define BD_OWN_MASK (1 << 5) +#define BD_DATA01_MASK (1 << 4) +#define BD_KEEP_MASK (1 << 3) +#define BD_NINC_MASK (1 << 2) +#define BD_DTS_MASK (1 << 1) +#define BD_STALL_MASK (1 << 0) + +#define KINETIS_SETUP_TOKEN 0x0d +#define KINETIS_IN_TOKEN 0x09 +#define KINETIS_OUT_TOKEN 0x01 + +#define USBFSOTG_PERID 0x04 +#define USBFSOTG_REV 0x33 + +#define KINETIS_EP_NUMOF_MASK 0xf +#define KINETIS_ADDR2IDX(addr) ((addr) & (KINETIS_EP_NUMOF_MASK)) + +/* + * Buffer Descriptor (BD) entry provides endpoint buffer control + * information for USBFS controller. Every endpoint direction requires + * two BD entries. + */ +struct buf_descriptor { + union { + uint32_t bd_fields; + + struct { + uint32_t reserved_1_0 : 2; + uint32_t tok_pid : 4; + uint32_t data01 : 1; + uint32_t own : 1; + uint32_t reserved_15_8 : 8; + uint32_t bc : 16; + } get __packed; + + struct { + uint32_t reserved_1_0 : 2; + uint32_t bd_ctrl : 6; + uint32_t reserved_15_8 : 8; + uint32_t bc : 16; + } set __packed; + + } __packed; + uint32_t buf_addr; +} __packed; + +/* + * Buffer Descriptor Table for the endpoints buffer management. + * The driver configuration with 16 fully bidirectional endpoints would require + * four BD entries per endpoint and 512 bytes of memory. + */ +static struct buf_descriptor __aligned(512) bdt[(NUM_OF_EP_MAX) * 2 * 2]; + +#define BD_IDX_EP0TX_EVEN 2 +#define BD_IDX_EP0TX_ODD 3 + +#define EP_BUF_NUMOF_BLOCKS (NUM_OF_EP_MAX / 2) + +K_HEAP_DEFINE(ep_buf_pool, 512 * EP_BUF_NUMOF_BLOCKS + 128); + +struct ep_mem_block { + void *data; +}; + +struct usb_ep_ctrl_data { + struct ep_status { + uint16_t in_enabled : 1; + uint16_t out_enabled : 1; + uint16_t in_data1 : 1; + uint16_t out_data1 : 1; + uint16_t in_odd : 1; + uint16_t out_odd : 1; + uint16_t in_stalled : 1; + uint16_t out_stalled : 1; + } status; + uint16_t mps_in; + uint16_t mps_out; + struct ep_mem_block mblock_in; + struct ep_mem_block mblock_out; + usb_dc_ep_callback cb_in; + usb_dc_ep_callback cb_out; +}; + +#define USBD_THREAD_STACK_SIZE 1024 + +struct usb_device_data { + usb_dc_status_callback status_cb; + uint8_t address; + uint32_t bd_active; + struct usb_ep_ctrl_data ep_ctrl[NUM_OF_EP_MAX]; + bool attached; + + K_KERNEL_STACK_MEMBER(thread_stack, USBD_THREAD_STACK_SIZE); + struct k_thread thread; +}; + +static struct usb_device_data dev_data; + +#define USB_DC_CB_TYPE_MGMT 0 +#define USB_DC_CB_TYPE_EP 1 + +struct cb_msg { + uint8_t ep; + uint8_t type; + uint32_t cb; +}; + +K_MSGQ_DEFINE(usb_dc_msgq, sizeof(struct cb_msg), 10, 4); +static void usb_kinetis_isr_handler(void); +static void usb_kinetis_thread_main(void *arg1, void *unused1, void *unused2); + +/* + * This function returns the BD element index based on + * endpoint address and the odd bit. + */ +static inline uint8_t get_bdt_idx(uint8_t ep, uint8_t odd) +{ + if (ep & USB_EP_DIR_IN) { + return ((((KINETIS_ADDR2IDX(ep)) * 4) + 2 + (odd & 1))); + } + return ((((KINETIS_ADDR2IDX(ep)) * 4) + (odd & 1))); +} + +static int kinetis_usb_init(void) +{ + /* enable USB voltage regulator */ + SIM->SOPT1 |= SIM_SOPT1_USBREGEN_MASK; + + USB0->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; + k_busy_wait(2000); + + USB0->CTL = 0; + /* enable USB module, AKA USBEN bit in CTL1 register */ + USB0->CTL |= USB_CTL_USBENSOFEN_MASK; + + if ((USB0->PERID != USBFSOTG_PERID) || + (USB0->REV != USBFSOTG_REV)) { + return -1; + } + + USB0->BDTPAGE1 = (uint8_t)(((uint32_t)bdt) >> 8); + USB0->BDTPAGE2 = (uint8_t)(((uint32_t)bdt) >> 16); + USB0->BDTPAGE3 = (uint8_t)(((uint32_t)bdt) >> 24); + + /* clear interrupt flags */ + USB0->ISTAT = 0xFF; + + /* enable reset interrupt */ + USB0->INTEN = USB_INTEN_USBRSTEN_MASK; + + USB0->USBCTRL = USB_USBCTRL_PDE_MASK; + + k_thread_create(&dev_data.thread, dev_data.thread_stack, + USBD_THREAD_STACK_SIZE, + usb_kinetis_thread_main, NULL, NULL, NULL, + K_PRIO_COOP(2), 0, K_NO_WAIT); + + /* Connect and enable USB interrupt */ + IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), + usb_kinetis_isr_handler, 0, 0); + + irq_enable(DT_INST_IRQN(0)); + + LOG_DBG(""); + + return 0; +} + +int usb_dc_reset(void) +{ + for (uint8_t i = 0; i < 16; i++) { + USB0->ENDPOINT[i].ENDPT = 0; + } + (void)memset(bdt, 0, sizeof(bdt)); + dev_data.bd_active = 0U; + dev_data.address = 0U; + + USB0->CTL |= USB_CTL_ODDRST_MASK; + USB0->CTL &= ~USB_CTL_ODDRST_MASK; + + /* Clear interrupt status flags */ + USB0->ISTAT = 0xFF; + /* Clear error flags */ + USB0->ERRSTAT = 0xFF; + /* Enable all error interrupt sources */ + USB0->ERREN = 0xFF; + /* Reset default address */ + USB0->ADDR = 0x00; + + USB0->INTEN = (USB_INTEN_USBRSTEN_MASK | + USB_INTEN_TOKDNEEN_MASK | + USB_INTEN_SLEEPEN_MASK | + USB_INTEN_SOFTOKEN_MASK | + USB_INTEN_STALLEN_MASK | + USB_INTEN_ERROREN_MASK); + + LOG_DBG(""); + + return 0; +} + +int usb_dc_attach(void) +{ + if (dev_data.attached) { + LOG_WRN("already attached"); + } + + kinetis_usb_init(); + + /* + * Call usb_dc_reset here because the device stack does not make it + * after USB_DC_RESET status event. + */ + usb_dc_reset(); + + dev_data.attached = 1; + LOG_DBG("attached"); + + /* non-OTG device mode, enable DP Pullup */ + USB0->CONTROL = USB_CONTROL_DPPULLUPNONOTG_MASK; + + return 0; +} + +int usb_dc_detach(void) +{ + LOG_DBG(""); + /* disable USB and DP Pullup */ + USB0->CTL &= ~USB_CTL_USBENSOFEN_MASK; + USB0->CONTROL &= ~USB_CONTROL_DPPULLUPNONOTG_MASK; + + return 0; +} + +int usb_dc_set_address(const uint8_t addr) +{ + LOG_DBG(""); + + if (!dev_data.attached) { + return -EINVAL; + } + + /* + * The device stack tries to set the address before + * sending the ACK with ZLP, which is totally stupid, + * as workaround the addresse will be buffered and + * placed later inside isr handler (see KINETIS_IN_TOKEN). + */ + dev_data.address = 0x80 | (addr & 0x7f); + + return 0; +} + +int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) +{ + uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("endpoint index/address out of range"); + return -EINVAL; + } + + switch (cfg->ep_type) { + case USB_DC_EP_CONTROL: + if (cfg->ep_mps > USB_MAX_CTRL_MPS) { + return -EINVAL; + } + return 0; + case USB_DC_EP_BULK: + if (cfg->ep_mps > USB_MAX_FS_BULK_MPS) { + return -EINVAL; + } + break; + case USB_DC_EP_INTERRUPT: + if (cfg->ep_mps > USB_MAX_FS_INT_MPS) { + return -EINVAL; + } + break; + case USB_DC_EP_ISOCHRONOUS: + if (cfg->ep_mps > USB_MAX_FS_ISO_MPS) { + return -EINVAL; + } + break; + default: + LOG_ERR("Unknown endpoint type!"); + return -EINVAL; + } + + if (ep_idx & BIT(0)) { + if (USB_EP_GET_DIR(cfg->ep_addr) != USB_EP_DIR_IN) { + LOG_INF("pre-selected as IN endpoint"); + return -1; + } + } else { + if (USB_EP_GET_DIR(cfg->ep_addr) != USB_EP_DIR_OUT) { + LOG_INF("pre-selected as OUT endpoint"); + return -1; + } + } + + return 0; +} + +int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const cfg) +{ + uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); + struct usb_ep_ctrl_data *ep_ctrl; + struct ep_mem_block *block; + uint8_t idx_even; + uint8_t idx_odd; + + if (usb_dc_ep_check_cap(cfg)) { + return -EINVAL; + } + + idx_even = get_bdt_idx(cfg->ep_addr, 0); + idx_odd = get_bdt_idx(cfg->ep_addr, 1); + ep_ctrl = &dev_data.ep_ctrl[ep_idx]; + + if (ep_idx && (dev_data.ep_ctrl[ep_idx].status.in_enabled || + dev_data.ep_ctrl[ep_idx].status.out_enabled)) { + LOG_WRN("endpoint already configured"); + return -EALREADY; + } + + LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps, + cfg->ep_type); + + if (USB_EP_DIR_IS_OUT(cfg->ep_addr)) { + block = &(ep_ctrl->mblock_out); + } else { + block = &(ep_ctrl->mblock_in); + } + + if (bdt[idx_even].buf_addr) { + k_heap_free(&ep_buf_pool, block->data); + } + + USB0->ENDPOINT[ep_idx].ENDPT = 0; + (void)memset(&bdt[idx_even], 0, sizeof(struct buf_descriptor)); + (void)memset(&bdt[idx_odd], 0, sizeof(struct buf_descriptor)); + + block->data = k_heap_alloc(&ep_buf_pool, cfg->ep_mps * 2U, K_MSEC(10)); + if (block->data != NULL) { + (void)memset(block->data, 0, cfg->ep_mps * 2U); + } else { + LOG_ERR("Memory allocation time-out"); + return -ENOMEM; + } + + bdt[idx_even].buf_addr = (uint32_t)block->data; + LOG_INF("idx_even %x", (uint32_t)block->data); + bdt[idx_odd].buf_addr = (uint32_t)((uint8_t *)block->data + cfg->ep_mps); + LOG_INF("idx_odd %x", (uint32_t)((uint8_t *)block->data + cfg->ep_mps)); + + if (cfg->ep_addr & USB_EP_DIR_IN) { + dev_data.ep_ctrl[ep_idx].mps_in = cfg->ep_mps; + } else { + dev_data.ep_ctrl[ep_idx].mps_out = cfg->ep_mps; + } + + bdt[idx_even].set.bc = cfg->ep_mps; + bdt[idx_odd].set.bc = cfg->ep_mps; + + dev_data.ep_ctrl[ep_idx].status.out_data1 = false; + dev_data.ep_ctrl[ep_idx].status.in_data1 = false; + + switch (cfg->ep_type) { + case USB_DC_EP_CONTROL: + LOG_DBG("configure control endpoint"); + USB0->ENDPOINT[ep_idx].ENDPT |= (USB_ENDPT_EPHSHK_MASK | + USB_ENDPT_EPRXEN_MASK | + USB_ENDPT_EPTXEN_MASK); + break; + case USB_DC_EP_BULK: + case USB_DC_EP_INTERRUPT: + USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPHSHK_MASK; + if (USB_EP_DIR_IS_OUT(cfg->ep_addr)) { + USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPRXEN_MASK; + } else { + USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPTXEN_MASK; + } + break; + case USB_DC_EP_ISOCHRONOUS: + if (USB_EP_DIR_IS_OUT(cfg->ep_addr)) { + USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPRXEN_MASK; + } else { + USB0->ENDPOINT[ep_idx].ENDPT |= USB_ENDPT_EPTXEN_MASK; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +int usb_dc_ep_set_stall(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint8_t bd_idx; + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + LOG_DBG("ep %x, idx %d", ep, ep_idx); + + if (USB_EP_DIR_IS_OUT(ep)) { + dev_data.ep_ctrl[ep_idx].status.out_stalled = 1U; + bd_idx = get_bdt_idx(ep, + ~dev_data.ep_ctrl[ep_idx].status.out_odd); + } else { + dev_data.ep_ctrl[ep_idx].status.in_stalled = 1U; + bd_idx = get_bdt_idx(ep, + dev_data.ep_ctrl[ep_idx].status.in_odd); + } + + bdt[bd_idx].set.bd_ctrl = BD_STALL_MASK | BD_DTS_MASK | BD_OWN_MASK; + + return 0; +} + +int usb_dc_ep_clear_stall(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint8_t bd_idx; + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + LOG_DBG("ep %x, idx %d", ep, ep_idx); + USB0->ENDPOINT[ep_idx].ENDPT &= ~USB_ENDPT_EPSTALL_MASK; + + if (USB_EP_DIR_IS_OUT(ep)) { + dev_data.ep_ctrl[ep_idx].status.out_stalled = 0U; + dev_data.ep_ctrl[ep_idx].status.out_data1 = false; + bd_idx = get_bdt_idx(ep, + ~dev_data.ep_ctrl[ep_idx].status.out_odd); + bdt[bd_idx].set.bd_ctrl = 0U; + bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK | BD_OWN_MASK; + } else { + dev_data.ep_ctrl[ep_idx].status.in_stalled = 0U; + dev_data.ep_ctrl[ep_idx].status.in_data1 = false; + bd_idx = get_bdt_idx(ep, + dev_data.ep_ctrl[ep_idx].status.in_odd); + bdt[bd_idx].set.bd_ctrl = 0U; + } + + /* Resume TX token processing, see USBx_CTL field descriptions */ + if (ep == 0U) { + USB0->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK; + } + + return 0; +} + +int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + LOG_DBG("ep %x, idx %d", ep_idx, ep); + if (!stalled) { + return -EINVAL; + } + + *stalled = 0U; + if (USB_EP_DIR_IS_OUT(ep)) { + *stalled = dev_data.ep_ctrl[ep_idx].status.out_stalled; + } else { + *stalled = dev_data.ep_ctrl[ep_idx].status.in_stalled; + } + + uint8_t bd_idx = get_bdt_idx(ep, + dev_data.ep_ctrl[ep_idx].status.in_odd); + LOG_WRN("active bd ctrl: %x", bdt[bd_idx].set.bd_ctrl); + bd_idx = get_bdt_idx(ep, + ~dev_data.ep_ctrl[ep_idx].status.in_odd); + LOG_WRN("next bd ctrl: %x", bdt[bd_idx].set.bd_ctrl); + + return 0; +} + +int usb_dc_ep_halt(const uint8_t ep) +{ + return usb_dc_ep_set_stall(ep); +} + +int usb_dc_ep_enable(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint8_t idx_even; + uint8_t idx_odd; + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + idx_even = get_bdt_idx(ep, 0); + idx_odd = get_bdt_idx(ep, 1); + + if (ep_idx && (dev_data.ep_ctrl[ep_idx].status.in_enabled || + dev_data.ep_ctrl[ep_idx].status.out_enabled)) { + LOG_WRN("endpoint 0x%x already enabled", ep); + return -EALREADY; + } + + if (USB_EP_DIR_IS_OUT(ep)) { + bdt[idx_even].set.bd_ctrl = BD_DTS_MASK | BD_OWN_MASK; + bdt[idx_odd].set.bd_ctrl = 0U; + dev_data.ep_ctrl[ep_idx].status.out_odd = 0U; + dev_data.ep_ctrl[ep_idx].status.out_stalled = 0U; + dev_data.ep_ctrl[ep_idx].status.out_data1 = false; + dev_data.ep_ctrl[ep_idx].status.out_enabled = true; + } else { + bdt[idx_even].bd_fields = 0U; + bdt[idx_odd].bd_fields = 0U; + dev_data.ep_ctrl[ep_idx].status.in_odd = 0U; + dev_data.ep_ctrl[ep_idx].status.in_stalled = 0U; + dev_data.ep_ctrl[ep_idx].status.in_data1 = false; + dev_data.ep_ctrl[ep_idx].status.in_enabled = true; + } + + LOG_INF("ep 0x%x, ep_idx %d", ep, ep_idx); + + return 0; +} + +int usb_dc_ep_disable(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint8_t idx_even; + uint8_t idx_odd; + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + idx_even = get_bdt_idx(ep, 0); + idx_odd = get_bdt_idx(ep, 1); + + LOG_INF("ep %x, idx %d", ep_idx, ep); + + bdt[idx_even].bd_fields = 0U; + bdt[idx_odd].bd_fields = 0U; + if (USB_EP_DIR_IS_OUT(ep)) { + dev_data.ep_ctrl[ep_idx].status.out_enabled = false; + } else { + dev_data.ep_ctrl[ep_idx].status.in_enabled = false; + } + + return 0; +} + +int usb_dc_ep_flush(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + LOG_DBG("ep %x, idx %d", ep_idx, ep); + + return 0; +} + +int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data, + const uint32_t data_len, uint32_t * const ret_bytes) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint32_t len_to_send = data_len; + uint8_t odd; + uint8_t bd_idx; + uint8_t *bufp; + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + odd = dev_data.ep_ctrl[ep_idx].status.in_odd; + bd_idx = get_bdt_idx(ep, odd); + bufp = (uint8_t *)bdt[bd_idx].buf_addr; + + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_IN) { + LOG_ERR("Wrong endpoint direction"); + return -EINVAL; + } + + if (dev_data.ep_ctrl[ep_idx].status.in_stalled) { + LOG_WRN("endpoint is stalled"); + return -EBUSY; + } + + while (bdt[bd_idx].get.own) { + LOG_DBG("ep 0x%x is busy", ep); + k_yield(); + } + + LOG_DBG("bd idx %x bufp %p odd %d", bd_idx, bufp, odd); + + if (data_len > dev_data.ep_ctrl[ep_idx].mps_in) { + len_to_send = dev_data.ep_ctrl[ep_idx].mps_in; + } + + bdt[bd_idx].set.bc = len_to_send; + + for (uint32_t n = 0; n < len_to_send; n++) { + bufp[n] = data[n]; + } + + dev_data.ep_ctrl[ep_idx].status.in_odd = ~odd; + if (dev_data.ep_ctrl[ep_idx].status.in_data1) { + bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK | + BD_DATA01_MASK | + BD_OWN_MASK; + } else { + bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK | BD_OWN_MASK; + } + + /* Toggle next Data1 */ + dev_data.ep_ctrl[ep_idx].status.in_data1 ^= 1; + + LOG_DBG("ep 0x%x write %d bytes from %d", ep, len_to_send, data_len); + + if (ret_bytes) { + *ret_bytes = len_to_send; + } + + return 0; +} + +int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, + uint32_t *read_bytes) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint32_t data_len; + uint8_t bd_idx; + uint8_t *bufp; + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + /* select the index of active endpoint buffer */ + bd_idx = get_bdt_idx(ep, dev_data.ep_ctrl[ep_idx].status.out_odd); + bufp = (uint8_t *)bdt[bd_idx].buf_addr; + + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { + LOG_ERR("Wrong endpoint direction"); + return -EINVAL; + } + + if (dev_data.ep_ctrl[ep_idx].status.out_stalled) { + LOG_WRN("endpoint is stalled"); + return -EBUSY; + } + + /* Allow to read 0 bytes */ + if (!data && max_data_len) { + LOG_ERR("Wrong arguments"); + return -EINVAL; + } + + while (bdt[bd_idx].get.own) { + LOG_ERR("Endpoint is occupied by the controller"); + return -EBUSY; + } + + data_len = bdt[bd_idx].get.bc; + + if (!data && !max_data_len) { + /* + * When both buffer and max data to read are zero return + * the available data in buffer. + */ + if (read_bytes) { + *read_bytes = data_len; + } + return 0; + } + + if (data_len > max_data_len) { + LOG_WRN("Not enough room to copy all the data!"); + data_len = max_data_len; + } + + if (data != NULL) { + for (uint32_t i = 0; i < data_len; i++) { + data[i] = bufp[i]; + } + } + + LOG_DBG("Read idx %d, req %d, read %d bytes", bd_idx, max_data_len, + data_len); + + if (read_bytes) { + *read_bytes = data_len; + } + + return 0; +} + + +int usb_dc_ep_read_continue(uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + uint8_t bd_idx; + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + bd_idx = get_bdt_idx(ep, dev_data.ep_ctrl[ep_idx].status.out_odd); + + if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { + LOG_ERR("Wrong endpoint direction"); + return -EINVAL; + } + + if (bdt[bd_idx].get.own) { + /* May occur when usb_transfer initializes the OUT transfer */ + LOG_WRN("Current buffer is claimed by the controller"); + return 0; + } + + /* select the index of the next endpoint buffer */ + bd_idx = get_bdt_idx(ep, ~dev_data.ep_ctrl[ep_idx].status.out_odd); + /* Update next toggle bit */ + dev_data.ep_ctrl[ep_idx].status.out_data1 ^= 1; + bdt[bd_idx].set.bc = dev_data.ep_ctrl[ep_idx].mps_out; + + /* Reset next buffer descriptor and set next toggle bit */ + if (dev_data.ep_ctrl[ep_idx].status.out_data1) { + bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK | + BD_DATA01_MASK | + BD_OWN_MASK; + } else { + bdt[bd_idx].set.bd_ctrl = BD_DTS_MASK | BD_OWN_MASK; + } + + /* Resume TX token processing, see USBx_CTL field descriptions */ + if (ep_idx == 0U) { + USB0->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK; + } + + LOG_DBG("idx next %x", bd_idx); + + return 0; +} + +int usb_dc_ep_read(const uint8_t ep, uint8_t *const data, + const uint32_t max_data_len, uint32_t *const read_bytes) +{ + int retval = usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes); + + if (retval) { + return retval; + } + + if (!data && !max_data_len) { + /* When both buffer and max data to read are zero the above + * call would fetch the data len and we simply return. + */ + return 0; + } + + if (usb_dc_ep_read_continue(ep) != 0) { + return -EINVAL; + } + + LOG_DBG(""); + + return 0; +} + +int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + if (!dev_data.attached) { + return -EINVAL; + } + + if (ep & USB_EP_DIR_IN) { + dev_data.ep_ctrl[ep_idx].cb_in = cb; + } else { + dev_data.ep_ctrl[ep_idx].cb_out = cb; + } + LOG_DBG("ep_idx %x", ep_idx); + + return 0; +} + +void usb_dc_set_status_callback(const usb_dc_status_callback cb) +{ + LOG_DBG(""); + + dev_data.status_cb = cb; +} + +int usb_dc_ep_mps(const uint8_t ep) +{ + uint8_t ep_idx = USB_EP_GET_IDX(ep); + + if (ep_idx > (NUM_OF_EP_MAX - 1)) { + LOG_ERR("Wrong endpoint index/address"); + return -EINVAL; + } + + if (ep & USB_EP_DIR_IN) { + return dev_data.ep_ctrl[ep_idx].mps_in; + } else { + return dev_data.ep_ctrl[ep_idx].mps_out; + } +} + +static inline void reenable_all_endpoints(void) +{ + for (uint8_t ep_idx = 0; ep_idx < NUM_OF_EP_MAX; ep_idx++) { + if (dev_data.ep_ctrl[ep_idx].status.out_enabled) { + usb_dc_ep_enable(ep_idx); + } + if (dev_data.ep_ctrl[ep_idx].status.in_enabled) { + usb_dc_ep_enable(ep_idx | USB_EP_DIR_IN); + } + } +} + +static void usb_kinetis_isr_handler(void) +{ + uint8_t istatus = USB0->ISTAT; + uint8_t status = USB0->STAT; + struct cb_msg msg; + + + if (istatus & USB_ISTAT_USBRST_MASK) { + dev_data.address = 0U; + USB0->ADDR = (uint8_t)0; + /* + * Device reset is not possible because the stack does not + * configure the endpoints after the USB_DC_RESET event, + * therefore, reenable all endpoints and set they BDT into a + * defined state. + */ + USB0->CTL |= USB_CTL_ODDRST_MASK; + USB0->CTL &= ~USB_CTL_ODDRST_MASK; + reenable_all_endpoints(); + msg.ep = 0U; + msg.type = USB_DC_CB_TYPE_MGMT; + msg.cb = USB_DC_RESET; + k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT); + } + + if (istatus == USB_ISTAT_ERROR_MASK) { + USB0->ERRSTAT = 0xFF; + msg.ep = 0U; + msg.type = USB_DC_CB_TYPE_MGMT; + msg.cb = USB_DC_ERROR; + k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT); + } + + if (istatus & USB_ISTAT_STALL_MASK) { + if (dev_data.ep_ctrl[0].status.out_stalled) { + usb_dc_ep_clear_stall(0); + } + if (dev_data.ep_ctrl[0].status.in_stalled) { + usb_dc_ep_clear_stall(0x80); + } + } + + if (istatus & USB_ISTAT_TOKDNE_MASK) { + + uint8_t ep_idx = status >> USB_STAT_ENDP_SHIFT; + uint8_t ep = ((status << 4) & USB_EP_DIR_IN) | ep_idx; + uint8_t odd = (status & USB_STAT_ODD_MASK) >> USB_STAT_ODD_SHIFT; + uint8_t idx = get_bdt_idx(ep, odd); + uint8_t token_pid = bdt[idx].get.tok_pid; + + msg.ep = ep; + msg.type = USB_DC_CB_TYPE_EP; + + switch (token_pid) { + case KINETIS_SETUP_TOKEN: + dev_data.ep_ctrl[ep_idx].status.out_odd = odd; + /* clear tx entries */ + bdt[BD_IDX_EP0TX_EVEN].bd_fields = 0U; + bdt[BD_IDX_EP0TX_ODD].bd_fields = 0U; + /* + * Set/Reset here the toggle bits for control endpoint + * because the device stack does not care about it. + */ + dev_data.ep_ctrl[ep_idx].status.in_data1 = true; + dev_data.ep_ctrl[ep_idx].status.out_data1 = false; + dev_data.ep_ctrl[ep_idx].status.out_odd = odd; + + msg.cb = USB_DC_EP_SETUP; + k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT); + break; + case KINETIS_OUT_TOKEN: + dev_data.ep_ctrl[ep_idx].status.out_odd = odd; + + msg.cb = USB_DC_EP_DATA_OUT; + k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT); + break; + case KINETIS_IN_TOKEN: + /* SET ADDRESS workaround */ + if (dev_data.address & 0x80) { + USB0->ADDR = dev_data.address & 0x7f; + dev_data.address = 0U; + } + + msg.cb = USB_DC_EP_DATA_IN; + k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT); + break; + default: + break; + } + } + + if (istatus & USB_ISTAT_SLEEP_MASK) { + /* Enable resume interrupt */ + USB0->INTEN |= USB_INTEN_RESUMEEN_MASK; + msg.ep = 0U; + msg.type = USB_DC_CB_TYPE_MGMT; + msg.cb = USB_DC_SUSPEND; + k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT); + } + + if (istatus & USB_ISTAT_RESUME_MASK) { + /* Disable resume interrupt */ + USB0->INTEN &= ~USB_INTEN_RESUMEEN_MASK; + msg.ep = 0U; + msg.type = USB_DC_CB_TYPE_MGMT; + msg.cb = USB_DC_RESUME; + k_msgq_put(&usb_dc_msgq, &msg, K_NO_WAIT); + } + + /* Clear interrupt status bits */ + USB0->ISTAT = istatus; +} + +/* + * This thread is only used to not run the USB device stack and endpoint + * callbacks in the ISR context, which happens when an callback function + * is called. TODO: something similar should be implemented in the USB + * device stack so that it can be used by all drivers. + */ +static void usb_kinetis_thread_main(void *arg1, void *unused1, void *unused2) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(unused1); + ARG_UNUSED(unused2); + struct cb_msg msg; + uint8_t ep_idx; + + while (true) { + k_msgq_get(&usb_dc_msgq, &msg, K_FOREVER); + ep_idx = USB_EP_GET_IDX(msg.ep); + + if (msg.type == USB_DC_CB_TYPE_EP) { + switch (msg.cb) { + case USB_DC_EP_SETUP: + if (dev_data.ep_ctrl[ep_idx].cb_out) { + dev_data.ep_ctrl[ep_idx].cb_out(msg.ep, + USB_DC_EP_SETUP); + } + break; + case USB_DC_EP_DATA_OUT: + if (dev_data.ep_ctrl[ep_idx].cb_out) { + dev_data.ep_ctrl[ep_idx].cb_out(msg.ep, + USB_DC_EP_DATA_OUT); + } + break; + case USB_DC_EP_DATA_IN: + if (dev_data.ep_ctrl[ep_idx].cb_in) { + dev_data.ep_ctrl[ep_idx].cb_in(msg.ep, + USB_DC_EP_DATA_IN); + } + break; + default: + LOG_ERR("unknown msg"); + break; + } + } else if (dev_data.status_cb) { + switch (msg.cb) { + case USB_DC_RESET: + dev_data.status_cb(USB_DC_RESET, NULL); + break; + case USB_DC_ERROR: + dev_data.status_cb(USB_DC_ERROR, NULL); + break; + case USB_DC_SUSPEND: + dev_data.status_cb(USB_DC_SUSPEND, NULL); + break; + case USB_DC_RESUME: + dev_data.status_cb(USB_DC_RESUME, NULL); + break; + default: + LOG_ERR("unknown msg"); + break; + } + } + } +} diff --git a/port/freescale/usbd_kinetis.c b/port/freescale/usbd_kinetis.c new file mode 100644 index 00000000..1bfb9347 --- /dev/null +++ b/port/freescale/usbd_kinetis.c @@ -0,0 +1,819 @@ +/** + * @file usbd_kinetis.c + * @brief + * + * DAPLink Interface Firmware + * Copyright (c) 2009-2016, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rl_usb.h" +#include "fsl_device_registers.h" +#include "cortex_m.h" +#include "util.h" +#include "string.h" + +#define __NO_USB_LIB_C +#include "usb_config.c" + +typedef struct __BUF_DESC { + uint8_t stat; + uint8_t reserved; + uint16_t bc; + uint32_t buf_addr; +} BUF_DESC; + +BUF_DESC __align(512) BD[(USBD_EP_NUM + 1) * 2 * 2]; +uint8_t EPBuf[(USBD_EP_NUM + 1) * 2 * 2][64]; +uint8_t OutEpSize[USBD_EP_NUM + 1]; +uint8_t StatQueue[(USBD_EP_NUM + 1) * 2 * 2 + 1]; +uint32_t StatQueueHead = 0; +uint32_t StatQueueTail = 0; +uint32_t LastIstat = 0; +uint8_t UsbSuspended = 0; +uint8_t Ep0ZlpOut = 0; + +uint32_t Data1 = 0x55555555; + +#define BD_OWN_MASK 0x80 +#define BD_DATA01_MASK 0x40 +#define BD_KEEP_MASK 0x20 +#define BD_NINC_MASK 0x10 +#define BD_DTS_MASK 0x08 +#define BD_STALL_MASK 0x04 + +#define TX 1 +#define RX 0 +#define ODD 0 +#define EVEN 1 +#define IDX(Ep, dir, Ev_Odd) ((((Ep & 0x0F) * 4) + (2 * dir) + (1 * Ev_Odd))) + +#define SETUP_TOKEN 0x0D +#define IN_TOKEN 0x09 +#define OUT_TOKEN 0x01 +#define TOK_PID(idx) ((BD[idx].stat >> 2) & 0x0F) + +inline static void protected_and(uint32_t *addr, uint32_t val) +{ + cortex_int_state_t state; + state = cortex_int_get_and_disable(); + *addr = *addr & val; + cortex_int_restore(state); +} +inline static void protected_or(uint32_t *addr, uint32_t val) +{ + cortex_int_state_t state; + state = cortex_int_get_and_disable(); + *addr = *addr | val; + cortex_int_restore(state); +} +inline static void protected_xor(uint32_t *addr, uint32_t val) +{ + cortex_int_state_t state; + state = cortex_int_get_and_disable(); + *addr = *addr ^ val; + cortex_int_restore(state); +} + +inline static void stat_enque(uint32_t stat) +{ + cortex_int_state_t state; + state = cortex_int_get_and_disable(); + StatQueue[StatQueueTail] = stat; + StatQueueTail = (StatQueueTail + 1) % sizeof(StatQueue); + cortex_int_restore(state); +} + +inline static uint32_t stat_deque() +{ + cortex_int_state_t state; + uint32_t stat; + state = cortex_int_get_and_disable(); + stat = StatQueue[StatQueueHead]; + StatQueueHead = (StatQueueHead + 1) % sizeof(StatQueue); + cortex_int_restore(state); + + return stat; +} + +inline static uint32_t stat_is_empty() +{ + cortex_int_state_t state; + uint32_t empty; + state = cortex_int_get_and_disable(); + empty = StatQueueHead == StatQueueTail; + cortex_int_restore(state); + return empty; +} + +/* + * USB Device Interrupt enable + * Called by USBD_Init to enable the USB Interrupt + * Return Value: None + */ + +void USBD_IntrEna(void) +{ + NVIC_EnableIRQ(USB0_IRQn); /* Enable OTG interrupt */ +} + + +/* + * USB Device Initialize Function + * Called by the User to initialize USB + * Return Value: None + */ + +void USBD_Init(void) +{ + OutEpSize[0] = USBD_MAX_PACKET0; + /* Enable all clocks needed for USB to function */ + /* Set USB clock to 48 MHz */ + SIM->SOPT2 |= SIM_SOPT2_USBSRC_MASK | /* MCGPLLCLK used as src */ + SIM_SOPT2_PLLFLLSEL_MASK ; /* Select MCGPLLCLK as clock */ +#if defined(TARGET_MK20D5) + SIM->CLKDIV2 &= ~(SIM_CLKDIV2_USBFRAC_MASK | /* Clear CLKDIV2 FS values */ + SIM_CLKDIV2_USBDIV_MASK); + SIM->CLKDIV2 = SIM_CLKDIV2_USBDIV(0) ; /* USB clk = (PLL*1/2) */ + /* = ( 48*1/1)=48 */ +#endif // TARGET_MK20D5 + SIM->SCGC4 |= SIM_SCGC4_USBOTG_MASK; /* Enable USBOTG clock */ + USBD_IntrEna(); + USB0->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; + + while (USB0->USBTRC0 & USB_USBTRC0_USBRESET_MASK); + + USB0->BDTPAGE1 = (uint8_t)((uint32_t) BD >> 8); + USB0->BDTPAGE2 = (uint8_t)((uint32_t) BD >> 16); + USB0->BDTPAGE3 = (uint8_t)((uint32_t) BD >> 24); + USB0->ISTAT = 0xFF; /* clear interrupt flags */ + /* enable interrupts */ + USB0->INTEN = USB_INTEN_USBRSTEN_MASK | + USB_INTEN_TOKDNEEN_MASK | + USB_INTEN_SLEEPEN_MASK | +#ifdef __RTX + ((USBD_RTX_DevTask != 0) ? USB_INTEN_SOFTOKEN_MASK : 0) | + ((USBD_RTX_DevTask != 0) ? USB_INTEN_ERROREN_MASK : 0) ; +#else + ((USBD_P_SOF_Event != 0) ? USB_INTEN_SOFTOKEN_MASK : 0) | + ((USBD_P_Error_Event != 0) ? USB_INTEN_ERROREN_MASK : 0) ; +#endif + USB0->USBCTRL = USB_USBCTRL_PDE_MASK;/* pull dawn on D+ and D- */ + USB0->USBTRC0 |= (1 << 6); /* bit 6 must be set to 1 */ +} + + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ + +void USBD_Connect(uint32_t con) +{ + if (con) { + USB0->CTL |= USB_CTL_USBENSOFEN_MASK; /* enable USB */ + USB0->CONTROL = USB_CONTROL_DPPULLUPNONOTG_MASK; /* pull up on D+ */ + } else { + USB0->CTL &= ~USB_CTL_USBENSOFEN_MASK; /* disable USB */ + USB0->CONTROL &= ~USB_CONTROL_DPPULLUPNONOTG_MASK;/* pull down on D+ */ + } +} + + +/* + * USB Device Reset Function + * Called automatically on USB Device Reset + * Return Value: None + */ + +void USBD_Reset(void) +{ + uint32_t i; + + NVIC_DisableIRQ(USB0_IRQn); + + for (i = 1; i < 16; i++) { + USB0->ENDPOINT[i].ENDPT = 0x00; + } + + memset(StatQueue, 0, sizeof(StatQueue)); + StatQueueHead = 0; + StatQueueTail = 0; + LastIstat = 0; + UsbSuspended = 0; + Ep0ZlpOut = 0; + + /* EP0 control endpoint */ + BD[IDX(0, RX, ODD)].bc = USBD_MAX_PACKET0; + BD[IDX(0, RX, ODD)].buf_addr = (uint32_t) & (EPBuf[IDX(0, RX, ODD)][0]); + BD[IDX(0, RX, ODD)].stat = BD_OWN_MASK | BD_DTS_MASK | BD_DATA01_MASK; + BD[IDX(0, RX, EVEN)].stat = 0; + BD[IDX(0, TX, ODD)].buf_addr = (uint32_t) & (EPBuf[IDX(0, TX, ODD)][0]); + BD[IDX(0, TX, EVEN)].buf_addr = 0; + USB0->ENDPOINT[0].ENDPT = USB_ENDPT_EPHSHK_MASK | /* enable ep handshaking */ + USB_ENDPT_EPTXEN_MASK | /* enable TX (IN) tran. */ + USB_ENDPT_EPRXEN_MASK; /* enable RX (OUT) tran. */ + Data1 = 0x55555555; + USB0->CTL |= USB_CTL_ODDRST_MASK; + USB0->ISTAT = 0xFF; /* clear all interrupt status flags */ + USB0->ERRSTAT = 0xFF; /* clear all error flags */ + USB0->ERREN = 0xFF; /* enable error interrupt sources */ + USB0->ADDR = 0x00; /* set default address */ + + NVIC_EnableIRQ(USB0_IRQn); +} + + +/* + * USB Device Suspend Function + * Called automatically on USB Device Suspend + * Return Value: None + */ + +void USBD_Suspend(void) +{ + USB0->INTEN |= USB_INTEN_RESUMEEN_MASK; +} + + +/* + * USB Device Resume Function + * Called automatically on USB Device Resume + * Return Value: None + */ + +void USBD_Resume(void) +{ + USB0->INTEN &= ~USB_INTEN_RESUMEEN_MASK; +} + + +/* + * USB Device Remote Wakeup Function + * Called automatically on USB Device Remote Wakeup + * Return Value: None + */ + +void USBD_WakeUp(void) +{ + uint32_t i = 50000; + + if (USBD_DeviceStatus & USB_GETSTATUS_REMOTE_WAKEUP) { + USB0->CTL |= USB_CTL_RESUME_MASK; + + while (i--) { + __nop(); + } + + USB0->CTL &= ~USB_CTL_RESUME_MASK; + } +} + + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ + +void USBD_WakeUpCfg(uint32_t cfg) +{ + /* Not needed */ +} + + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * Return Value: None + */ + +void USBD_SetAddress(uint32_t adr, uint32_t setup) +{ + if (!setup) { + USB0->ADDR = adr & 0x7F; + } +} + + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ + +void USBD_Configure(uint32_t cfg) +{ +} + + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ + +void USBD_ConfigEP(USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + uint32_t num, val; + num = pEPD->bEndpointAddress; + val = pEPD->wMaxPacketSize; + + if (!(pEPD->bEndpointAddress & 0x80)) { + OutEpSize[num] = val; + } + + USBD_ResetEP(num); +} + + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ + +void USBD_DirCtrlEP(uint32_t dir) +{ + /* Not needed */ +} + + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_EnableEP(uint32_t EPNum) +{ + if (EPNum & 0x80) { + EPNum &= 0x0F; + USB0->ENDPOINT[EPNum].ENDPT |= USB_ENDPT_EPHSHK_MASK | /*en ep handshaking*/ + USB_ENDPT_EPTXEN_MASK; /*en TX (IN) tran */ + } else { + USB0->ENDPOINT[EPNum].ENDPT |= USB_ENDPT_EPHSHK_MASK | /*en ep handshaking*/ + USB_ENDPT_EPRXEN_MASK; /*en RX (OUT) tran.*/ + } +} + + +/* + * Disable USB Endpoint + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_DisableEP(uint32_t EPNum) +{ + if (EPNum & 0x80) { + EPNum &= 0x0F; + USB0->ENDPOINT[EPNum].ENDPT &= ~(USB_ENDPT_EPHSHK_MASK |/*dis handshaking */ + USB_ENDPT_EPTXEN_MASK);/*dis TX(IN) tran */ + } else { + USB0->ENDPOINT[EPNum].ENDPT &= ~(USB_ENDPT_EPHSHK_MASK |/*dis handshaking */ + USB_ENDPT_EPRXEN_MASK);/*dis RX(OUT) tran*/ + } +} + + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ResetEP(uint32_t EPNum) +{ + if (EPNum & 0x80) { + EPNum &= 0x0F; + protected_or(&Data1, (1 << ((EPNum * 2) + 1))); + BD[IDX(EPNum, TX, ODD)].buf_addr = (uint32_t) & (EPBuf[IDX(EPNum, TX, ODD)][0]); + BD[IDX(EPNum, TX, EVEN)].buf_addr = 0; + } else { + protected_and(&Data1, ~(1 << (EPNum * 2))); + BD[IDX(EPNum, RX, ODD)].bc = OutEpSize[EPNum]; + BD[IDX(EPNum, RX, ODD)].buf_addr = (uint32_t) & (EPBuf[IDX(EPNum, RX, ODD)][0]); + BD[IDX(EPNum, RX, ODD)].stat = BD_OWN_MASK | BD_DTS_MASK; + BD[IDX(EPNum, RX, EVEN)].stat = 0; + } +} + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_SetStallEP(uint32_t EPNum) +{ + EPNum &= 0x0F; + USB0->ENDPOINT[EPNum].ENDPT |= USB_ENDPT_EPSTALL_MASK; +} + + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClrStallEP(uint32_t EPNum) +{ + USB0->ENDPOINT[EPNum & 0x0F].ENDPT &= ~USB_ENDPT_EPSTALL_MASK; + USBD_ResetEP(EPNum); +} + + +/* + * Clear USB Device Endpoint Buffer + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClearEPBuf(uint32_t EPNum) +{ +} + + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ + +uint32_t USBD_ReadEP(uint32_t EPNum, uint8_t *pData, uint32_t size) +{ + uint32_t n, sz, idx, setup = 0; + idx = IDX(EPNum, RX, 0); + sz = BD[idx].bc; + + if ((EPNum == 0) && Ep0ZlpOut) { + // This packet was a zero length data out packet. It has already + // been processed by USB0_IRQHandler. Only toggle the DATAx bit + // and return a size of 0. + protected_xor(&Data1, (1 << (idx / 2))); + return 0; + } + + if ((EPNum == 0) && (TOK_PID(idx) == SETUP_TOKEN)) { + setup = 1; + } + + if (size < sz) { + util_assert(0); + sz = size; + } + + for (n = 0; n < sz; n++) { + pData[n] = EPBuf[idx][n]; + } + + BD[idx].bc = OutEpSize[EPNum]; + + if ((Data1 >> (idx / 2) & 1) == ((BD[idx].stat >> 6) & 1)) { + uint32_t xfer_size = (pData[7] << 8) | (pData[6] << 0); + if (setup && (0 == xfer_size)) { /* if no setup data stage, */ + protected_and(&Data1, ~1); /* set DATA0 */ + } else { + protected_xor(&Data1, (1 << (idx / 2))); + } + } + + if ((Data1 >> (idx / 2)) & 1) { + BD[idx].stat = BD_DTS_MASK | BD_DATA01_MASK; + BD[idx].stat |= BD_OWN_MASK; + } else { + BD[idx].stat = BD_DTS_MASK; + BD[idx].stat |= BD_OWN_MASK; + } + + return (sz); +} + + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ + +uint32_t USBD_WriteEP(uint32_t EPNum, uint8_t *pData, uint32_t cnt) +{ + uint32_t idx, n; + EPNum &= 0x0F; + idx = IDX(EPNum, TX, 0); + BD[idx].bc = cnt; + + for (n = 0; n < cnt; n++) { + EPBuf[idx][n] = pData[n]; + } + + if ((Data1 >> (idx / 2)) & 1) { + BD[idx].stat = BD_OWN_MASK | BD_DTS_MASK; + } else { + BD[idx].stat = BD_OWN_MASK | BD_DTS_MASK | BD_DATA01_MASK; + } + + protected_xor(&Data1, (1 << (idx / 2))); + return (cnt); +} + +/* + * Get USB Device Last Frame Number + * Parameters: None + * Return Value: Frame Number + */ + +uint32_t USBD_GetFrame(void) +{ + return ((USB0->FRMNUML | (USB0->FRMNUMH << 8) & 0x07FF)); +} + +#ifdef __RTX +U32 LastError; /* Last Error */ + +/* + * Get USB Device Last Error Code + * Parameters: None + * Return Value: Error Code + */ + +U32 USBD_GetError(void) +{ + return (LastError); +} +#endif + + +/* + * USB Device Interrupt Service Routine + */ +void USB0_IRQHandler(void) +{ + uint32_t istat, num, dir, ev_odd; + uint32_t new_istat; + uint8_t suspended = 0; + + new_istat = istat = USB0->ISTAT; + + // Read all tokens + if (istat & USB_ISTAT_TOKDNE_MASK) { + while (istat & USB_ISTAT_TOKDNE_MASK) { + uint8_t stat = USB0->STAT; + num = (stat >> 4) & 0x0F; + dir = (stat >> 3) & 0x01; + ev_odd = (stat >> 2) & 0x01; + + // Consume all zero length OUT packets on endpoint 0 to prevent + // a subsequent SETUP packet from being dropped + if ((0 == num) && (RX == dir)) { + uint32_t idx; + idx = IDX(num, dir, ev_odd); + if ((TOK_PID(idx) == OUT_TOKEN) && (BD[idx].bc == 0)) { + BD[idx].bc = OutEpSize[num]; + if (BD[idx].stat & BD_DATA01_MASK) { + BD[idx].stat = BD_OWN_MASK | BD_DTS_MASK; + } else { + BD[idx].stat = BD_OWN_MASK | BD_DTS_MASK | BD_DATA01_MASK; + } + stat |= 1 << 0; + } + } + + stat_enque(stat); + USB0->ISTAT = USB_ISTAT_TOKDNE_MASK; + + // Check if USB is suspending before checking istat + suspended = suspended || USB0->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK; + istat = USB0->ISTAT; + } + } + + // Set global istat and suspended flags + new_istat |= istat; + protected_or(&LastIstat, new_istat); + UsbSuspended |= suspended ? 1 : 0; + USB0->ISTAT = istat; + + USBD_SignalHandler(); +} + +/* + * USB Device Service Routine + */ + +void USBD_Handler(void) +{ + uint32_t istr, num, dir, ev_odd; + cortex_int_state_t state; + uint8_t suspended = 0; + + // Get ISTAT + state = cortex_int_get_and_disable(); + istr = LastIstat; + LastIstat = 0; + suspended = UsbSuspended; + UsbSuspended = 0; + cortex_int_restore(state); + + + /* reset interrupt */ + if (istr & USB_ISTAT_USBRST_MASK) { + USBD_Reset(); + usbd_reset_core(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESET, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Reset_Event) { + USBD_P_Reset_Event(); + } + +#endif + } + + /* suspend interrupt */ + if (istr & USB_ISTAT_SLEEP_MASK) { + USBD_Suspend(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SUSPEND, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Suspend_Event) { + USBD_P_Suspend_Event(); + } + +#endif + } + + /* resume interrupt */ + if (istr & USB_ISTAT_RESUME_MASK) { + USBD_Resume(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESUME, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Resume_Event) { + USBD_P_Resume_Event(); + } + +#endif + } + + /* Start Of Frame */ + if (istr & USB_ISTAT_SOFTOK_MASK) { +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SOF, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + +#endif + } + + /* Error interrupt */ + if (istr == USB_ISTAT_ERROR_MASK) { +#ifdef __RTX + LastError = USB0->ERRSTAT; + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_ERROR, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Error_Event) { + USBD_P_Error_Event(USB0->ERRSTAT); + } + +#endif + USB0->ERRSTAT = 0xFF; + } + + /* token interrupt */ + if (istr & USB_ISTAT_TOKDNE_MASK) { + while (!stat_is_empty()) { + uint32_t stat; + + stat = stat_deque(); + num = (stat >> 4) & 0x0F; + dir = (stat >> 3) & 0x01; + ev_odd = (stat >> 2) & 0x01; + + /* setup packet */ + if ((num == 0) && (TOK_PID((IDX(num, dir, ev_odd))) == SETUP_TOKEN)) { + Data1 &= ~0x02; + BD[IDX(0, TX, EVEN)].stat &= ~BD_OWN_MASK; + BD[IDX(0, TX, ODD)].stat &= ~BD_OWN_MASK; + Ep0ZlpOut = 0; +#ifdef __RTX + + if (USBD_RTX_EPTask[num]) { + isr_evt_set(USBD_EVT_SETUP, USBD_RTX_EPTask[num]); + } + +#else + + if (USBD_P_EP[num]) { + USBD_P_EP[num](USBD_EVT_SETUP); + } + +#endif + + } else { + /* OUT packet */ + if (TOK_PID((IDX(num, dir, ev_odd))) == OUT_TOKEN) { + if (0 == num) { + Ep0ZlpOut = stat & (1 << 0); + } +#ifdef __RTX + + if (USBD_RTX_EPTask[num]) { + isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[num]); + } + +#else + + if (USBD_P_EP[num]) { + USBD_P_EP[num](USBD_EVT_OUT); + } + +#endif + } + + /* IN packet */ + if (TOK_PID((IDX(num, dir, ev_odd))) == IN_TOKEN) { +#ifdef __RTX + + if (USBD_RTX_EPTask[num]) { + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[num]); + } + +#else + + if (USBD_P_EP[num]) { + USBD_P_EP[num](USBD_EVT_IN); + } + +#endif + } + } + } + } + + if (suspended) { + USB0->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK; + } +} diff --git a/port/maxim/max32620/usbd_max32620.c b/port/maxim/max32620/usbd_max32620.c new file mode 100644 index 00000000..f2e627b0 --- /dev/null +++ b/port/maxim/max32620/usbd_max32620.c @@ -0,0 +1,622 @@ +/* CMSIS-DAP Interface Firmware + * Copyright (c) 2009-2013 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "rl_usb.h" +#include "util.h" + +#include "max32620.h" +#include "usb_regs.h" +#include "clkman_regs.h" +#include "pwrman_regs.h" +#include "tmr_regs.h" + +#define __NO_USB_LIB_C +#include "usb_config.c" + +#define EPNUM_MASK (~USB_ENDPOINT_DIRECTION_MASK) + +#define INIT_INTS (MXC_F_USB_DEV_INTEN_BRST | MXC_F_USB_DEV_INTFL_BRST_DN | MXC_F_USB_DEV_INTEN_VBUS | MXC_F_USB_DEV_INTFL_NO_VBUS) +#define CONNECT_INTS (MXC_F_USB_DEV_INTEN_SETUP | MXC_F_USB_DEV_INTEN_EP_IN | MXC_F_USB_DEV_INTEN_EP_OUT | MXC_F_USB_DEV_INTEN_DMA_ERR) + +typedef struct { + volatile uint32_t buf0_desc; + volatile uint32_t buf0_address; + volatile uint32_t buf1_desc; + volatile uint32_t buf1_address; +} ep_buffer_t; + +typedef struct { + ep_buffer_t out_buffer; + ep_buffer_t in_buffer; +} ep0_buffer_t; + +typedef struct { + ep0_buffer_t ep0; + ep_buffer_t ep[MXC_USB_NUM_EP - 1]; +} ep_buffer_descriptor_t; + +typedef struct { + U8 type; + U16 len; +} ep_info_t; + +/* static storage for endpoint buffer descriptor table, must be 512 byte aligned for DMA */ +__attribute__ ((aligned (512))) +ep_buffer_descriptor_t ep_buffer_descriptor; + +static uint32_t ep_buffer[MXC_USB_NUM_EP][MXC_USB_MAX_PACKET / sizeof(uint32_t)]; +static ep_info_t ep_info[MXC_USB_NUM_EP]; +static volatile int suspended; +static volatile int setup_waiting; +static volatile int ep0_expect_zlp; + +#if CDC_ENDPOINT +/* CDC-ACM class processes FIFOs in the SOF interrupt. The USB Device interface + * of Maxim's microcontrollers does not provide and SOF interrupt. A periodic + * timer interrupt is used instead. + */ +/******************************************************************************/ +void TMR0_IRQHandler(void) +{ + MXC_TMR0->intfl = MXC_TMR0->intfl; + + if (usbd_configured()) { + USBD_CDC_ACM_SOF_Event(); + } +} +#endif + +/******************************************************************************/ +static ep_buffer_t *get_desc(U32 EPNum) +{ + ep_buffer_t *desc; + + if (EPNum == 0x80) { + desc = &ep_buffer_descriptor.ep0.in_buffer; + } else if (EPNum == 0x00) { + desc = &ep_buffer_descriptor.ep0.out_buffer; + } else { + desc = &ep_buffer_descriptor.ep[(EPNum & EPNUM_MASK) - 1]; + } + + return desc; +} + +/* + * USB Device Interrupt enable + * Called by USBD_Init to enable the USB Interrupt + * Return Value: None + */ + +void USBD_IntrEna(void) +{ + NVIC_EnableIRQ(USB_IRQn); /* Enable OTG interrupt */ +} + +/******************************************************************************/ +/* + * Usb interrupt enable/disable + * Parameters: ena: enable/disable + * 0: disable interrupt + * 1: enable interrupt + */ +#ifdef __RTX +void __svc(1) USBD_Intr (int ena); +void __SVC_1 (int ena) +{ + if (ena) { + NVIC_EnableIRQ(USB_IRQn); /* Enable USB interrupt */ + } else { + NVIC_DisableIRQ(USB_IRQn); /* Disable USB interrupt */ + } +} +#endif + +/******************************************************************************/ +static void reset_state(void) +{ + unsigned int ep; + + suspended = 0; + setup_waiting = 0; + ep0_expect_zlp = 0; + memset(ep_info, 0, sizeof(ep_info)); + + MXC_USB->ep[0] |= (MXC_S_USB_EP_DIR_CONTROL | MXC_F_USB_EP_INT_EN | MXC_F_USB_EP_DT); + for (ep = 1; ep < MXC_USB_NUM_EP; ep++) { + MXC_USB->ep[ep] = MXC_F_USB_EP_DT; + } +} + +/* + * USB Device Initialize Function + * Called by the User to initialize USB Device + * Return Value: None + */ +void USBD_Init (void) +{ + uint32_t reg; + + /* Enable USB power domain */ + MXC_PWRMAN->pwr_rst_ctrl |= MXC_F_PWRMAN_PWR_RST_CTRL_USB_POWERED; + /* Setup the USB clocking, select */ + MXC_CLKMAN->clk_ctrl |= MXC_F_CLKMAN_CLK_CTRL_USB_CLOCK_ENABLE; + /* Force USB clock gater */ + reg = MXC_CLKMAN->clk_gate_ctrl0; + reg &= ~MXC_F_CLKMAN_CLK_GATE_CTRL0_USB_CLK_GATER; + reg |= (0x2 << MXC_F_CLKMAN_CLK_GATE_CTRL0_USB_CLK_GATER_POS); + MXC_CLKMAN->clk_gate_ctrl0 = reg; + + MXC_USB->cn = 0; + MXC_USB->cn = MXC_F_USB_CN_USB_EN; + MXC_USB->dev_inten = 0; + MXC_USB->dev_intfl = 0xFFFF; // clear interrupts + MXC_USB->dev_cn = 0; + MXC_USB->dev_cn |= MXC_F_USB_DEV_CN_URST; + MXC_USB->dev_cn = 0; + + reset_state(); + + /* set the descriptor location */ + MXC_USB->ep_base = (uint32_t)&ep_buffer_descriptor; + + /* enable some interrupts */ + MXC_USB->dev_inten = INIT_INTS; + NVIC_EnableIRQ(USB_IRQn); +} + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ +void USBD_Connect (BOOL con) +{ + if (con) { + MXC_USB->dev_intfl = 0xFFFF; // clear interrupts + MXC_USB->dev_inten |= CONNECT_INTS; + MXC_USB->ep[0] |= MXC_F_USB_EP_INT_EN; + MXC_USB->dev_cn |= (MXC_F_USB_DEV_CN_CONNECT | MXC_F_USB_DEV_CN_FIFO_MODE); + } else { + MXC_USB->dev_inten &= ~CONNECT_INTS; + MXC_USB->ep[0] &= ~MXC_F_USB_EP_INT_EN; + MXC_USB->dev_cn &= ~MXC_F_USB_DEV_CN_CONNECT; + } +} + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ +void USBD_WakeUpCfg (BOOL cfg) +{ +} + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * Return Value: None + */ +void USBD_SetAddress (U32 adr, U32 setup) +{ + /* Performed by Hardware */ +} + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ +void USBD_Configure (BOOL cfg) +{ +#if CDC_ENDPOINT + /* CDC-ACM class processes FIFOs in the SOF interrupt. The USB Device interface + * of Maxim's microcontrollers does not provide and SOF interrupt. A periodic + * timer interrupt is used instead. + */ + + #define SOF_INT_US 1000 + + if (cfg) { + // Setup timer interrupt for SOF + MXC_TMR0->ctrl = MXC_S_TMR_CTRL_MODE_CONTINUOUS; + MXC_TMR0->count32 = 0; + MXC_TMR0->term_cnt32 = (SystemCoreClock / 1000000) * SOF_INT_US; + + // Enable the interrupt + MXC_TMR0->intfl = MXC_TMR0->intfl; + NVIC_EnableIRQ(TMR0_0_IRQn); + MXC_TMR0->inten = MXC_F_TMR_INTEN_TIMER0; + + // Start the timer + MXC_TMR0->ctrl |= MXC_F_TMR_CTRL_ENABLE0; + + } else { + // Disable tmr + MXC_TMR0->ctrl &= ~(MXC_F_TMR_CTRL_ENABLE0); + } +#endif +} + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ +void USBD_ConfigEP (USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + U32 EPNum; + + EPNum = pEPD->bEndpointAddress & EPNUM_MASK; + + if (EPNum < MXC_USB_NUM_EP) { + + // Clear existing configurations + MXC_USB->ep[EPNum] = MXC_F_USB_EP_DT; + + if (pEPD->bEndpointAddress & USB_ENDPOINT_DIRECTION_MASK) { + ep_info[EPNum].type = MXC_S_USB_EP_DIR_IN; + } else { + ep_info[EPNum].type = MXC_S_USB_EP_DIR_OUT; + } + + ep_info[EPNum].len = pEPD->wMaxPacketSize; + } +} + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ +void USBD_DirCtrlEP (U32 dir) +{ + /* Not needed */ +} + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_EnableEP (U32 EPNum) +{ + ep_buffer_t *desc = get_desc(EPNum); + + EPNum &= EPNUM_MASK; + MXC_USB->ep[EPNum] |= (MXC_F_USB_EP_INT_EN | ep_info[EPNum].type | MXC_F_USB_EP_DT); + + if (ep_info[EPNum].type == MXC_S_USB_EP_DIR_OUT) { + // This is an OUT endpoint. Go ahead and register a request. + desc = get_desc(EPNum); + desc->buf0_address = (uint32_t)ep_buffer[EPNum]; + desc->buf0_desc = sizeof(ep_buffer[EPNum]); + MXC_USB->out_owner = (1 << EPNum); + } +} + +/* + * Disable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_DisableEP (U32 EPNum) +{ + EPNum &= EPNUM_MASK; + MXC_USB->ep[EPNum] = 0; +} + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_ResetEP (U32 EPNum) +{ + ep_buffer_t *desc = get_desc(EPNum); + + EPNum &= EPNUM_MASK; + MXC_USB->ep[EPNum] |= MXC_F_USB_EP_DT; + + if (ep_info[EPNum].type == MXC_S_USB_EP_DIR_OUT) { + // This is an OUT endpoint. Go ahead and register a request. + desc = get_desc(EPNum); + desc->buf0_address = (uint32_t)ep_buffer[EPNum]; + desc->buf0_desc = sizeof(ep_buffer[EPNum]); + MXC_USB->out_owner = (1 << EPNum); + } +} + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_SetStallEP (U32 EPNum) +{ + EPNum &= EPNUM_MASK; + + if (EPNum == 0) { + MXC_USB->ep[0] |= (MXC_F_USB_EP_ST_STALL | MXC_F_USB_EP_STALL); + } else { + MXC_USB->ep[EPNum] |= MXC_F_USB_EP_STALL; + } +} + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_ClrStallEP (U32 EPNum) +{ + USBD_ResetEP(EPNum); + MXC_USB->ep[EPNum & EPNUM_MASK] &= ~MXC_F_USB_EP_STALL; +} + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ +U32 USBD_ReadEP (U32 EPNum, U8 *pData, U32 size) +{ + U32 cnt; + ep_buffer_t *desc = get_desc(EPNum); + USB_SETUP_PACKET *sup; + + EPNum &= EPNUM_MASK; + + if ((EPNum == 0) && setup_waiting) { + cnt = USBD_MAX_PACKET0; + + if (size < cnt) { + cnt = size; + } + setup_waiting = 0; + memcpy(pData, (void*)&MXC_USB->setup0, cnt); + sup = (USB_SETUP_PACKET*)pData; + + if ( (sup->bmRequestType.Dir == REQUEST_HOST_TO_DEVICE) && (sup->wLength > 0) ) { + // There is an OUT stage for this setup packet. Register a request. + if (!(MXC_USB->out_owner & 1)) { + desc = &ep_buffer_descriptor.ep0.out_buffer; + desc->buf0_address = (uint32_t)ep_buffer[0]; + desc->buf0_desc = sup->wLength; + MXC_USB->out_owner = 1; + } + } + } else { + cnt = desc->buf0_desc; + + if (size < cnt) { + cnt = size; + } + memcpy(pData, ep_buffer[EPNum], cnt); + + // Register the next request. + desc->buf0_address = (uint32_t)ep_buffer[EPNum]; + desc->buf0_desc = sizeof(ep_buffer[EPNum]); + MXC_USB->out_owner = (1 << EPNum); + } + + return cnt; +} + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ +U32 USBD_WriteEP (U32 EPNum, U8 *pData, U32 cnt) +{ + ep_buffer_t *desc = get_desc(EPNum); + uint32_t mask; + + EPNum &= EPNUM_MASK; + mask = (1 << EPNum); + + if (MXC_USB->in_owner & mask) { + return 0; + } + + if (EPNum == 0) { + // Prepare to ACK the status stage. + MXC_USB->ep[0] |= MXC_F_USB_EP_ST_ACK; + + if ((cnt == 0) && !ep0_expect_zlp) { + // This is a status stage ACK. Handled in hardware. + return 0; + } else if (cnt == USBD_MAX_PACKET0) { + ep0_expect_zlp = 1; + } else { + ep0_expect_zlp = 0; + } + } + + if (cnt > MXC_USB_MAX_PACKET) { + cnt = MXC_USB_MAX_PACKET; + } + + /* prepare data to be sent */ + memcpy(ep_buffer[EPNum], pData, cnt); + desc->buf0_address = (uint32_t)ep_buffer[EPNum]; + desc->buf0_desc = cnt; + + /* start the transaction */ + MXC_USB->in_owner = mask; + + return cnt; +} + +/* + * USB Device Interrupt Service Routine + */ +void USB_IRQHandler (void) +{ + NVIC_DisableIRQ(USB_IRQn); + USBD_SignalHandler(); +} + +void USBD_Handler(void) +{ + +#ifdef __RTX + while(1) {} +#else + +#endif + + uint32_t irq_flags; + unsigned int ep; + uint32_t ep_int, mask; + + // Read and clear interrupts + irq_flags = MXC_USB->dev_intfl; + MXC_USB->dev_intfl = irq_flags; + + /* reset interrupt */ + if (irq_flags & MXC_F_USB_DEV_INTFL_BRST) { + if (suspended) { + suspended = 0; +#ifdef __RTX + if (USBD_RTX_DevTask) { isr_evt_set(USBD_EVT_RESUME, USBD_RTX_DevTask); } +#else + if (USBD_P_Resume_Event) { USBD_P_Resume_Event(); } +#endif + } + + reset_state(); + usbd_reset_core(); + +#ifdef __RTX + if (USBD_RTX_DevTask) { isr_evt_set(USBD_EVT_RESET, USBD_RTX_DevTask); } +#else + if (USBD_P_Reset_Event) { USBD_P_Reset_Event(); } +#endif + + } + + /* reset done interrupt */ + if (irq_flags & MXC_F_USB_DEV_INTFL_BRST_DN) { + reset_state(); + } + + /* suspend interrupt */ + if (irq_flags & MXC_F_USB_DEV_INTFL_SUSP) { + suspended = 1; +#ifdef __RTX + if (USBD_RTX_DevTask) { isr_evt_set(USBD_EVT_SUSPEND, USBD_RTX_DevTask); } +#else + if (USBD_P_Suspend_Event) { USBD_P_Suspend_Event(); } +#endif + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_VBUS) { +#ifdef __RTX + if (USBD_RTX_DevTask) { isr_evt_set(USBD_EVT_POWER_ON, USBD_RTX_DevTask); } +#else + if (USBD_P_Power_Event) { USBD_P_Power_Event(1); } +#endif + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_NO_VBUS) { +#ifdef __RTX + if (USBD_RTX_DevTask) { isr_evt_set(USBD_EVT_POWER_OFF, USBD_RTX_DevTask); } +#else + if (USBD_P_Power_Event) { USBD_P_Power_Event(0); } +#endif + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_SETUP) { + setup_waiting = 1; +#ifdef __RTX + if (USBD_RTX_EPTask[0]) { isr_evt_set(USBD_EVT_SETUP, USBD_RTX_EPTask[0]); } +#else + if (USBD_P_EP[0]) { USBD_P_EP[0](USBD_EVT_SETUP); } +#endif + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_EP_IN) { + + // Read and clear endpoint interrupts + ep_int = MXC_USB->in_int; + MXC_USB->in_int = ep_int; + + mask = 1; + for (ep = 0; ep < MXC_USB_NUM_EP; ep++) { + if (ep_int & mask) { +#ifdef __RTX + if (USBD_RTX_EPTask[ep]) { isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[ep]); } +#else + if (USBD_P_EP[ep]) { USBD_P_EP[ep](USBD_EVT_IN); } +#endif + } + + mask <<= 1; + } + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_EP_OUT) { + + // Read and clear endpoint interrupts + ep_int = MXC_USB->out_int; + MXC_USB->out_int = ep_int; + + mask = 1; + for (ep = 0; ep < MXC_USB_NUM_EP; ep++) { + if (ep_int & mask) { +#ifdef __RTX + if (USBD_RTX_EPTask[ep]) { isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[ep]); } +#else + if (USBD_P_EP[ep]) { USBD_P_EP[ep](USBD_EVT_OUT); } +#endif + } + + mask <<= 1; + } + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_DMA_ERR) { + // Read and clear endpoint interrupts + ep_int = MXC_USB->dma_err_int; + MXC_USB->dma_err_int = ep_int; + while(1); // not recoverable + } + + NVIC_EnableIRQ(USB_IRQn); + +} diff --git a/port/maxim/max32625/usbd_max32625.c b/port/maxim/max32625/usbd_max32625.c new file mode 100644 index 00000000..c9fdf188 --- /dev/null +++ b/port/maxim/max32625/usbd_max32625.c @@ -0,0 +1,647 @@ +/* CMSIS-DAP Interface Firmware + * Copyright (c) 2009-2013 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "rl_usb.h" +#include "util.h" + +#include "max32625.h" +#include "usb_regs.h" +#include "clkman_regs.h" +#include "pwrman_regs.h" +#include "tmr_regs.h" + +#define __NO_USB_LIB_C +#include "usb_config.c" + +#define EPNUM_MASK (~USB_ENDPOINT_DIRECTION_MASK) + +#define INIT_INTS (MXC_F_USB_DEV_INTEN_BRST | MXC_F_USB_DEV_INTFL_BRST_DN | MXC_F_USB_DEV_INTEN_VBUS | MXC_F_USB_DEV_INTFL_NO_VBUS) +#define CONNECT_INTS (MXC_F_USB_DEV_INTEN_SETUP | MXC_F_USB_DEV_INTEN_EP_IN | MXC_F_USB_DEV_INTEN_EP_OUT | MXC_F_USB_DEV_INTEN_DMA_ERR) + +typedef struct { + volatile uint32_t buf0_desc; + volatile uint32_t buf0_address; + volatile uint32_t buf1_desc; + volatile uint32_t buf1_address; +} ep_buffer_t; + +typedef struct { + ep_buffer_t out_buffer; + ep_buffer_t in_buffer; +} ep0_buffer_t; + +typedef struct { + ep0_buffer_t ep0; + ep_buffer_t ep[MXC_USB_NUM_EP - 1]; +} ep_buffer_descriptor_t; + +typedef struct { + U8 type; + U16 len; +} ep_info_t; + +/* static storage for endpoint buffer descriptor table, must be 512 byte aligned for DMA */ +__attribute__ ((aligned (512))) +ep_buffer_descriptor_t ep_buffer_descriptor; + +static uint32_t ep_buffer[MXC_USB_NUM_EP][MXC_USB_MAX_PACKET / sizeof(uint32_t)]; +static ep_info_t ep_info[MXC_USB_NUM_EP]; +static volatile int suspended; +static volatile int setup_waiting; +static volatile int ep0_expect_zlp; + +#if CDC_ENDPOINT +/* CDC-ACM class processes FIFOs in the SOF interrupt. The USB Device interface + * of Maxim's microcontrollers does not provide and SOF interrupt. A periodic + * timer interrupt is used instead. + */ +/******************************************************************************/ +void TMR0_IRQHandler(void) +{ + MXC_TMR0->intfl = MXC_TMR0->intfl; + + if (usbd_configured()) { + USBD_CDC_ACM_SOF_Event(); + } +} +#endif + +/******************************************************************************/ +static ep_buffer_t *get_desc(U32 EPNum) +{ + ep_buffer_t *desc; + + if (EPNum == 0x80) { + desc = &ep_buffer_descriptor.ep0.in_buffer; + } else if (EPNum == 0x00) { + desc = &ep_buffer_descriptor.ep0.out_buffer; + } else { + desc = &ep_buffer_descriptor.ep[(EPNum & EPNUM_MASK) - 1]; + } + + return desc; +} + +/* + * USB Device Interrupt enable + * Called by USBD_Init to enable the USB Interrupt + * Return Value: None + */ + +void USBD_IntrEna(void) +{ + NVIC_EnableIRQ(USB_IRQn); /* Enable OTG interrupt */ +} + +/******************************************************************************/ +/* + * Usb interrupt enable/disable + * Parameters: ena: enable/disable + * 0: disable interrupt + * 1: enable interrupt + */ +#ifdef __RTX +void __svc(1) USBD_Intr (int ena); +void __SVC_1 (int ena) +{ + if (ena) { + NVIC_EnableIRQ(USB_IRQn); /* Enable USB interrupt */ + } else { + NVIC_DisableIRQ(USB_IRQn); /* Disable USB interrupt */ + } +} +#endif + +/******************************************************************************/ +static void reset_state(void) +{ + unsigned int ep; + + suspended = 0; + setup_waiting = 0; + ep0_expect_zlp = 0; + memset(ep_info, 0, sizeof(ep_info)); + + MXC_USB->ep[0] |= (MXC_S_USB_EP_DIR_CONTROL | MXC_F_USB_EP_INT_EN | MXC_F_USB_EP_DT); + for (ep = 1; ep < MXC_USB_NUM_EP; ep++) { + MXC_USB->ep[ep] = MXC_F_USB_EP_DT; + } +} + +/* + * USB Device Initialize Function + * Called by the User to initialize USB Device + * Return Value: None + */ +void USBD_Init (void) +{ + uint32_t reg; + + /* Enable USB power domain */ + MXC_PWRMAN->pwr_rst_ctrl |= MXC_F_PWRMAN_PWR_RST_CTRL_USB_POWERED; + /* Setup the USB clocking, select */ + MXC_CLKMAN->clk_ctrl |= MXC_F_CLKMAN_CLK_CTRL_USB_CLOCK_ENABLE; + /* Force USB clock gater */ + reg = MXC_CLKMAN->clk_gate_ctrl0; + reg &= ~MXC_F_CLKMAN_CLK_GATE_CTRL0_USB_CLK_GATER; + reg |= (0x2 << MXC_F_CLKMAN_CLK_GATE_CTRL0_USB_CLK_GATER_POS); + MXC_CLKMAN->clk_gate_ctrl0 = reg; + + MXC_USB->cn = 0; + MXC_USB->cn = MXC_F_USB_CN_USB_EN; + MXC_USB->dev_inten = 0; + MXC_USB->dev_intfl = 0xFFFF; // clear interrupts + MXC_USB->dev_cn = 0; + MXC_USB->dev_cn |= MXC_F_USB_DEV_CN_URST; + MXC_USB->dev_cn = 0; + + reset_state(); + + /* set the descriptor location */ + MXC_USB->ep_base = (uint32_t)&ep_buffer_descriptor; + + /* enable some interrupts */ + MXC_USB->dev_inten = INIT_INTS; + NVIC_EnableIRQ(USB_IRQn); +} + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ +void USBD_Connect (BOOL con) +{ + if (con) { + MXC_USB->dev_intfl = 0xFFFF; // clear interrupts + MXC_USB->dev_inten |= CONNECT_INTS; + MXC_USB->ep[0] |= MXC_F_USB_EP_INT_EN; + MXC_USB->dev_cn |= (MXC_F_USB_DEV_CN_CONNECT | MXC_F_USB_DEV_CN_FIFO_MODE); + } else { + MXC_USB->dev_inten &= ~CONNECT_INTS; + MXC_USB->ep[0] &= ~MXC_F_USB_EP_INT_EN; + MXC_USB->dev_cn &= ~MXC_F_USB_DEV_CN_CONNECT; + } +} + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ +void USBD_WakeUpCfg (BOOL cfg) +{ +} + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * Return Value: None + */ +void USBD_SetAddress (U32 adr, U32 setup) +{ + /* Performed by Hardware */ +} + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ +void USBD_Configure (BOOL cfg) +{ +#if CDC_ENDPOINT + /* CDC-ACM class processes FIFOs in the SOF interrupt. The USB Device interface + * of Maxim's microcontrollers does not provide and SOF interrupt. A periodic + * timer interrupt is used instead. + */ + + #define SOF_INT_US 1000 + + if (cfg) { + // Setup timer interrupt for SOF + MXC_TMR0->ctrl = MXC_S_TMR_CTRL_MODE_CONTINUOUS; + MXC_TMR0->count32 = 0; + MXC_TMR0->term_cnt32 = (SystemCoreClock / 1000000) * SOF_INT_US; + + // Enable the interrupt + MXC_TMR0->intfl = MXC_TMR0->intfl; + NVIC_EnableIRQ(TMR0_0_IRQn); + MXC_TMR0->inten = MXC_F_TMR_INTEN_TIMER0; + + // Start the timer + MXC_TMR0->ctrl |= MXC_F_TMR_CTRL_ENABLE0; + + } else { + // Disable tmr + MXC_TMR0->ctrl &= ~(MXC_F_TMR_CTRL_ENABLE0); + } +#endif +} + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ +void USBD_ConfigEP (USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + U32 EPNum; + + EPNum = pEPD->bEndpointAddress & EPNUM_MASK; + + if (EPNum < MXC_USB_NUM_EP) { + + // Clear existing configurations + MXC_USB->ep[EPNum] = MXC_F_USB_EP_DT; + + if (pEPD->bEndpointAddress & USB_ENDPOINT_DIRECTION_MASK) { + ep_info[EPNum].type = MXC_S_USB_EP_DIR_IN; + } else { + ep_info[EPNum].type = MXC_S_USB_EP_DIR_OUT; + } + + ep_info[EPNum].len = pEPD->wMaxPacketSize; + } +} + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ +void USBD_DirCtrlEP (U32 dir) +{ + /* Not needed */ +} + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_EnableEP (U32 EPNum) +{ + ep_buffer_t *desc = get_desc(EPNum); + + EPNum &= EPNUM_MASK; + MXC_USB->ep[EPNum] |= (MXC_F_USB_EP_INT_EN | ep_info[EPNum].type | MXC_F_USB_EP_DT); + + if (ep_info[EPNum].type == MXC_S_USB_EP_DIR_OUT) { + // This is an OUT endpoint. Go ahead and register a request. + desc = get_desc(EPNum); + desc->buf0_address = (uint32_t)ep_buffer[EPNum]; + desc->buf0_desc = sizeof(ep_buffer[EPNum]); + MXC_USB->out_owner = (1 << EPNum); + } +} + +/* + * Disable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_DisableEP (U32 EPNum) +{ + EPNum &= EPNUM_MASK; + MXC_USB->ep[EPNum] = 0; +} + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_ResetEP (U32 EPNum) +{ + ep_buffer_t *desc = get_desc(EPNum); + + EPNum &= EPNUM_MASK; + MXC_USB->ep[EPNum] |= MXC_F_USB_EP_DT; + + if (ep_info[EPNum].type == MXC_S_USB_EP_DIR_OUT) { + // This is an OUT endpoint. Go ahead and register a request. + desc = get_desc(EPNum); + desc->buf0_address = (uint32_t)ep_buffer[EPNum]; + desc->buf0_desc = sizeof(ep_buffer[EPNum]); + MXC_USB->out_owner = (1 << EPNum); + } +} + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_SetStallEP (U32 EPNum) +{ + EPNum &= EPNUM_MASK; + + if (EPNum == 0) { + MXC_USB->ep[0] |= (MXC_F_USB_EP_ST_STALL | MXC_F_USB_EP_STALL); + } else { + MXC_USB->ep[EPNum] |= MXC_F_USB_EP_STALL; + } +} + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ +void USBD_ClrStallEP (U32 EPNum) +{ + USBD_ResetEP(EPNum); + MXC_USB->ep[EPNum & EPNUM_MASK] &= ~MXC_F_USB_EP_STALL; +} + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ +U32 USBD_ReadEP (U32 EPNum, U8 *pData, U32 size) +{ + U32 cnt; + ep_buffer_t *desc = get_desc(EPNum); + USB_SETUP_PACKET *sup; + + EPNum &= EPNUM_MASK; + + if ((EPNum == 0) && setup_waiting) { + cnt = USBD_MAX_PACKET0; + + if (size < cnt) { + cnt = size; + } + setup_waiting = 0; + memcpy(pData, (void*)&MXC_USB->setup0, cnt); + sup = (USB_SETUP_PACKET*)pData; + + if ( (sup->bmRequestType.Dir == REQUEST_HOST_TO_DEVICE) && (sup->wLength > 0) ) { + // There is an OUT stage for this setup packet. Register a request. + if (!(MXC_USB->out_owner & 1)) { + desc = &ep_buffer_descriptor.ep0.out_buffer; + desc->buf0_address = (uint32_t)ep_buffer[0]; + desc->buf0_desc = sup->wLength; + MXC_USB->out_owner = 1; + } + } + } else { + cnt = desc->buf0_desc; + + if (size < cnt) { + cnt = size; + } + memcpy(pData, ep_buffer[EPNum], cnt); + + // Register the next request. + desc->buf0_address = (uint32_t)ep_buffer[EPNum]; + desc->buf0_desc = sizeof(ep_buffer[EPNum]); + MXC_USB->out_owner = (1 << EPNum); + } + + return cnt; +} + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ +U32 USBD_WriteEP (U32 EPNum, U8 *pData, U32 cnt) +{ + ep_buffer_t *desc = get_desc(EPNum); + uint32_t mask; + + EPNum &= EPNUM_MASK; + mask = (1 << EPNum); + + if (MXC_USB->in_owner & mask) { + return 0; + } + + if (EPNum == 0) { + // Prepare to ACK the status stage. + MXC_USB->ep[0] |= MXC_F_USB_EP_ST_ACK; + + if ((cnt == 0) && !ep0_expect_zlp) { + // This is a status stage ACK. Handled in hardware. + return 0; + } else if (cnt == USBD_MAX_PACKET0) { + ep0_expect_zlp = 1; + } else { + ep0_expect_zlp = 0; + } + } + + if (cnt > MXC_USB_MAX_PACKET) { + cnt = MXC_USB_MAX_PACKET; + } + + /* prepare data to be sent */ + memcpy(ep_buffer[EPNum], pData, cnt); + desc->buf0_address = (uint32_t)ep_buffer[EPNum]; + desc->buf0_desc = cnt; + + /* start the transaction */ + MXC_USB->in_owner = mask; + + return cnt; +} + +/* + * USB Device Interrupt Service Routine + */ +void USB_IRQHandler (void) +{ + NVIC_DisableIRQ(USB_IRQn); + USBD_SignalHandler(); +} + +void USBD_Handler(void) +{ + uint32_t irq_flags; + unsigned int ep; + uint32_t ep_int, mask; + + // Read and clear interrupts + irq_flags = MXC_USB->dev_intfl; + MXC_USB->dev_intfl = irq_flags; + + /* reset interrupt */ + if (irq_flags & MXC_F_USB_DEV_INTFL_BRST) { + if (suspended) { + suspended = 0; +#ifdef __RTX + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESUME, USBD_RTX_DevTask); + } +#else + if (USBD_P_Resume_Event) { + USBD_P_Resume_Event(); + } +#endif + } + + reset_state(); + usbd_reset_core(); + +#ifdef __RTX + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESET, USBD_RTX_DevTask); + } +#else + if (USBD_P_Reset_Event) { + USBD_P_Reset_Event(); + } +#endif + + } + + /* reset done interrupt */ + if (irq_flags & MXC_F_USB_DEV_INTFL_BRST_DN) { + reset_state(); + } + + /* suspend interrupt */ + if (irq_flags & MXC_F_USB_DEV_INTFL_SUSP) { + suspended = 1; +#ifdef __RTX + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SUSPEND, USBD_RTX_DevTask); + } +#else + if (USBD_P_Suspend_Event) { + USBD_P_Suspend_Event(); + } +#endif + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_VBUS) { +#ifdef __RTX + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_POWER_ON, USBD_RTX_DevTask); + } +#else + if (USBD_P_Power_Event) { + USBD_P_Power_Event(1); + } +#endif + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_NO_VBUS) { +#ifdef __RTX + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_POWER_OFF, USBD_RTX_DevTask); + } +#else + if (USBD_P_Power_Event) { + USBD_P_Power_Event(0); + } +#endif + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_SETUP) { + setup_waiting = 1; +#ifdef __RTX + if (USBD_RTX_EPTask[0]) { + isr_evt_set(USBD_EVT_SETUP, USBD_RTX_EPTask[0]); + } +#else + if (USBD_P_EP[0]) { + USBD_P_EP[0](USBD_EVT_SETUP); + } +#endif + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_EP_IN) { + + // Read and clear endpoint interrupts + ep_int = MXC_USB->in_int; + MXC_USB->in_int = ep_int; + + mask = 1; + for (ep = 0; ep < MXC_USB_NUM_EP; ep++) { + if (ep_int & mask) { +#ifdef __RTX + if (USBD_RTX_EPTask[ep]) { + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[ep]); + } +#else + if (USBD_P_EP[ep]) { + USBD_P_EP[ep](USBD_EVT_IN); + } +#endif + } + + mask <<= 1; + } + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_EP_OUT) { + + // Read and clear endpoint interrupts + ep_int = MXC_USB->out_int; + MXC_USB->out_int = ep_int; + + mask = 1; + for (ep = 0; ep < MXC_USB_NUM_EP; ep++) { + if (ep_int & mask) { +#ifdef __RTX + if (USBD_RTX_EPTask[ep]) { + isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[ep]); + } +#else + if (USBD_P_EP[ep]) { + USBD_P_EP[ep](USBD_EVT_OUT); + } +#endif + } + + mask <<= 1; + } + } + + if (irq_flags & MXC_F_USB_DEV_INTFL_DMA_ERR) { + // Read and clear endpoint interrupts + ep_int = MXC_USB->dma_err_int; + MXC_USB->dma_err_int = ep_int; + while(1); // not recoverable + } + + NVIC_EnableIRQ(USB_IRQn); + +} diff --git a/port/nordic/usb_dc_nrfx.c b/port/nordic/usb_dc_nrfx.c new file mode 100644 index 00000000..eb6c6c9c --- /dev/null +++ b/port/nordic/usb_dc_nrfx.c @@ -0,0 +1,1928 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor ASA + * Copyright (c) 2018 Sundar Subramaniyan + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file usb_dc_nrfx.c + * @brief Nordic USB device controller driver + * + * The driver implements the interface between the USBD peripheral + * driver from nrfx package and the operating system. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(usb_nrfx); + +/* USB device controller access from devicetree */ +#define DT_DRV_COMPAT nordic_nrf_usbd + +/** + * @brief nRF USBD peripheral states + */ +enum usbd_periph_state { + USBD_DETACHED, + USBD_ATTACHED, + USBD_POWERED, + USBD_SUSPENDED, + USBD_RESUMED, + USBD_DEFAULT, + USBD_ADDRESS_SET, + USBD_CONFIGURED, +}; + +/** + * @brief Endpoint event types. + */ +enum usbd_ep_event_type { + EP_EVT_SETUP_RECV, + EP_EVT_RECV_REQ, + EP_EVT_RECV_COMPLETE, + EP_EVT_WRITE_COMPLETE, +}; + +/** + * @brief USBD peripheral event types. + */ +enum usbd_event_type { + USBD_EVT_POWER, + USBD_EVT_EP, + USBD_EVT_RESET, + USBD_EVT_SOF, + USBD_EVT_REINIT +}; + +/** + * @brief Endpoint configuration. + * + * @param cb Endpoint callback. + * @param max_sz Max packet size supported by endpoint. + * @param en Enable/Disable flag. + * @param addr Endpoint address. + * @param type Endpoint transfer type. + */ +struct nrf_usbd_ep_cfg { + usb_dc_ep_callback cb; + uint32_t max_sz; + bool en; + uint8_t addr; + enum usb_dc_ep_transfer_type type; + +}; + +struct usbd_mem_block { + void *data; +}; + +/** + * @brief Endpoint buffer + * + * @param len Remaining length to be read/written. + * @param block Mempool block, for freeing up buffer after use. + * @param data Pointer to the data buffer for the endpoint. + * @param curr Pointer to the current offset in the endpoint buffer. + */ +struct nrf_usbd_ep_buf { + uint32_t len; + struct usbd_mem_block block; + uint8_t *data; + uint8_t *curr; +}; + +/** + * @brief Endpoint context + * + * @param cfg Endpoint configuration + * @param buf Endpoint buffer + * @param read_complete A flag indicating that DMA read operation + * has been completed. + * @param read_pending A flag indicating that the Host has requested + * a data transfer. + * @param write_in_progress A flag indicating that write operation has + * been scheduled. + * @param trans_zlp Flag required for Control IN Endpoint. It + * indicates that ZLP is required to end data + * stage of the control request. + */ +struct nrf_usbd_ep_ctx { + struct nrf_usbd_ep_cfg cfg; + struct nrf_usbd_ep_buf buf; + volatile bool read_complete; + volatile bool read_pending; + volatile bool write_in_progress; + bool trans_zlp; +}; + +/** + * @brief Endpoint event structure + * + * @param ep Endpoint control block pointer + * @param evt_type Event type + */ +struct usbd_ep_event { + struct nrf_usbd_ep_ctx *ep; + enum usbd_ep_event_type evt_type; +}; + +/** + * @brief Power event structure + * + * @param state New USBD peripheral state. + */ +struct usbd_pwr_event { + enum usbd_periph_state state; +}; + +/** + * @brief Endpoint USB event + * Used by ISR to send events to work handler + * + * @param node Used by the kernel for FIFO management + * @param block Mempool block pointer for freeing up after use + * @param evt Event data field + * @param evt_type Type of event that has occurred from the USBD peripheral + */ +struct usbd_event { + sys_snode_t node; + struct usbd_mem_block block; + union { + struct usbd_ep_event ep_evt; + struct usbd_pwr_event pwr_evt; + } evt; + enum usbd_event_type evt_type; +}; + +/** + * @brief Fifo element slab + * Used for allocating fifo elements to pass from ISR to work handler + * TODO: The number of FIFO elements is an arbitrary number now but it should + * be derived from the theoretical number of backlog events possible depending + * on the number of endpoints configured. + */ +#define FIFO_ELEM_SZ sizeof(struct usbd_event) +#define FIFO_ELEM_ALIGN sizeof(unsigned int) + +K_MEM_SLAB_DEFINE(fifo_elem_slab, FIFO_ELEM_SZ, + CONFIG_USB_NRFX_EVT_QUEUE_SIZE, FIFO_ELEM_ALIGN); + + +/** Number of IN Endpoints configured (including control) */ +#define CFG_EPIN_CNT (DT_INST_PROP(0, num_in_endpoints) + \ + DT_INST_PROP(0, num_bidir_endpoints)) + +/** Number of OUT Endpoints configured (including control) */ +#define CFG_EPOUT_CNT (DT_INST_PROP(0, num_out_endpoints) + \ + DT_INST_PROP(0, num_bidir_endpoints)) + +/** Number of ISO IN Endpoints */ +#define CFG_EP_ISOIN_CNT DT_INST_PROP(0, num_isoin_endpoints) + +/** Number of ISO OUT Endpoints */ +#define CFG_EP_ISOOUT_CNT DT_INST_PROP(0, num_isoout_endpoints) + +/** ISO endpoint index */ +#define EP_ISOIN_INDEX CFG_EPIN_CNT +#define EP_ISOOUT_INDEX (CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + CFG_EPOUT_CNT) + +#define EP_BUF_MAX_SZ 64UL +#define ISO_EP_BUF_MAX_SZ 1024UL + +/** + * @brief Output endpoint buffers + * Used as buffers for the endpoints' data transfer + * Max buffers size possible: 1536 Bytes (8 EP * 64B + 1 ISO * 1024B) + */ +static uint8_t ep_out_bufs[CFG_EPOUT_CNT][EP_BUF_MAX_SZ] + __aligned(sizeof(uint32_t)); +static uint8_t ep_isoout_bufs[CFG_EP_ISOOUT_CNT][ISO_EP_BUF_MAX_SZ] + __aligned(sizeof(uint32_t)); + +/** Total endpoints configured */ +#define CFG_EP_CNT (CFG_EPIN_CNT + CFG_EP_ISOIN_CNT + \ + CFG_EPOUT_CNT + CFG_EP_ISOOUT_CNT) + +/** + * @brief USBD control structure + * + * @param status_cb Status callback for USB DC notifications + * @param setup Setup packet for Control requests + * @param hfxo_cli Onoff client used to control HFXO + * @param hfxo_mgr Pointer to onoff manager associated with HFXO. + * @param clk_requested Flag used to protect against double stop. + * @param attached USBD Attached flag + * @param ready USBD Ready flag set after pullup + * @param usb_work USBD work item + * @param drv_lock Mutex for thread-safe nrfx driver use + * @param ep_ctx Endpoint contexts + * @param ctrl_read_len State of control read operation (EP0). + */ +struct nrf_usbd_ctx { + usb_dc_status_callback status_cb; + struct usb_setup_packet setup; + struct onoff_client hfxo_cli; + struct onoff_manager *hfxo_mgr; + atomic_t clk_requested; + + bool attached; + bool ready; + + struct k_work usb_work; + struct k_mutex drv_lock; + + struct nrf_usbd_ep_ctx ep_ctx[CFG_EP_CNT]; + + uint16_t ctrl_read_len; +}; + + +/* FIFO used for queuing up events from ISR. */ +K_FIFO_DEFINE(usbd_evt_fifo); + +/* Work queue used for handling the ISR events (i.e. for notifying the USB + * device stack, for executing the endpoints callbacks, etc.) out of the ISR + * context. + * The system work queue cannot be used for this purpose as it might be used in + * applications for scheduling USB transfers and this could lead to a deadlock + * when the USB device stack would not be notified about certain event because + * of a system work queue item waiting for a USB transfer to be finished. + */ +static struct k_work_q usbd_work_queue; +static K_KERNEL_STACK_DEFINE(usbd_work_queue_stack, + CONFIG_USB_NRFX_WORK_QUEUE_STACK_SIZE); + + +static struct nrf_usbd_ctx usbd_ctx = { + .attached = false, + .ready = false, +}; + +static inline struct nrf_usbd_ctx *get_usbd_ctx(void) +{ + return &usbd_ctx; +} + +static inline bool dev_attached(void) +{ + return get_usbd_ctx()->attached; +} + +static inline bool dev_ready(void) +{ + return get_usbd_ctx()->ready; +} + +static inline nrfx_usbd_ep_t ep_addr_to_nrfx(uint8_t ep) +{ + return (nrfx_usbd_ep_t)ep; +} + +static inline uint8_t nrfx_addr_to_ep(nrfx_usbd_ep_t ep) +{ + return (uint8_t)ep; +} + +static inline bool ep_is_valid(const uint8_t ep) +{ + uint8_t ep_num = USB_EP_GET_IDX(ep); + + if (NRF_USBD_EPIN_CHECK(ep)) { + if (unlikely(ep_num == NRF_USBD_EPISO_FIRST)) { + if (CFG_EP_ISOIN_CNT == 0) { + return false; + } + } else { + if (ep_num >= CFG_EPIN_CNT) { + return false; + } + } + } else { + if (unlikely(ep_num == NRF_USBD_EPISO_FIRST)) { + if (CFG_EP_ISOOUT_CNT == 0) { + return false; + } + } else { + if (ep_num >= CFG_EPOUT_CNT) { + return false; + } + } + } + + return true; +} + +static struct nrf_usbd_ep_ctx *endpoint_ctx(const uint8_t ep) +{ + struct nrf_usbd_ctx *ctx; + uint8_t ep_num; + + if (!ep_is_valid(ep)) { + return NULL; + } + + ctx = get_usbd_ctx(); + ep_num = NRF_USBD_EP_NR_GET(ep); + + if (NRF_USBD_EPIN_CHECK(ep)) { + if (unlikely(NRF_USBD_EPISO_CHECK(ep))) { + return &ctx->ep_ctx[EP_ISOIN_INDEX]; + } else { + return &ctx->ep_ctx[ep_num]; + } + } else { + if (unlikely(NRF_USBD_EPISO_CHECK(ep))) { + return &ctx->ep_ctx[EP_ISOOUT_INDEX]; + } else { + return &ctx->ep_ctx[CFG_EPIN_CNT + + CFG_EP_ISOIN_CNT + + ep_num]; + } + } + + return NULL; +} + +static struct nrf_usbd_ep_ctx *in_endpoint_ctx(const uint8_t ep) +{ + return endpoint_ctx(NRF_USBD_EPIN(ep)); +} + +static struct nrf_usbd_ep_ctx *out_endpoint_ctx(const uint8_t ep) +{ + return endpoint_ctx(NRF_USBD_EPOUT(ep)); +} + +/** + * @brief Schedule USBD event processing. + * + * Should be called after usbd_evt_put(). + */ +static inline void usbd_work_schedule(void) +{ + k_work_submit_to_queue(&usbd_work_queue, &get_usbd_ctx()->usb_work); +} + +/** + * @brief Free previously allocated USBD event. + * + * Should be called after usbd_evt_get(). + * + * @param Pointer to the USBD event structure. + */ +static inline void usbd_evt_free(struct usbd_event *ev) +{ + k_mem_slab_free(&fifo_elem_slab, (void **)&ev->block.data); +} + +/** + * @brief Enqueue USBD event. + * + * @param Pointer to the previously allocated and filled event structure. + */ +static inline void usbd_evt_put(struct usbd_event *ev) +{ + k_fifo_put(&usbd_evt_fifo, ev); +} + +/** + * @brief Get next enqueued USBD event if present. + */ +static inline struct usbd_event *usbd_evt_get(void) +{ + return k_fifo_get(&usbd_evt_fifo, K_NO_WAIT); +} + +/** + * @brief Drop all enqueued events. + */ +static inline void usbd_evt_flush(void) +{ + struct usbd_event *ev; + + do { + ev = usbd_evt_get(); + if (ev) { + usbd_evt_free(ev); + } + } while (ev != NULL); +} + +/** + * @brief Allocate USBD event. + * + * This function should be called prior to usbd_evt_put(). + * + * @returns Pointer to the allocated event or NULL if there was no space left. + */ +static inline struct usbd_event *usbd_evt_alloc(void) +{ + struct usbd_event *ev; + struct usbd_mem_block block; + + if (k_mem_slab_alloc(&fifo_elem_slab, + (void **)&block.data, K_NO_WAIT)) { + LOG_ERR("USBD event allocation failed!"); + + /* + * Allocation may fail if workqueue thread is starved or event + * queue size is too small (CONFIG_USB_NRFX_EVT_QUEUE_SIZE). + * Wipe all events, free the space and schedule + * reinitialization. + */ + usbd_evt_flush(); + + if (k_mem_slab_alloc(&fifo_elem_slab, (void **)&block.data, K_NO_WAIT)) { + LOG_ERR("USBD event memory corrupted"); + __ASSERT_NO_MSG(0); + return NULL; + } + + ev = (struct usbd_event *)block.data; + ev->block = block; + ev->evt_type = USBD_EVT_REINIT; + usbd_evt_put(ev); + usbd_work_schedule(); + + return NULL; + } + + ev = (struct usbd_event *)block.data; + ev->block = block; + + return ev; +} + +static void usb_dc_power_event_handler(nrfx_power_usb_evt_t event) +{ + enum usbd_periph_state new_state; + + switch (event) { + case NRFX_POWER_USB_EVT_DETECTED: + new_state = USBD_ATTACHED; + break; + case NRFX_POWER_USB_EVT_READY: + new_state = USBD_POWERED; + break; + case NRFX_POWER_USB_EVT_REMOVED: + new_state = USBD_DETACHED; + break; + default: + LOG_ERR("Unknown USB power event %d", event); + return; + } + + struct usbd_event *ev = usbd_evt_alloc(); + + if (!ev) { + return; + } + + ev->evt_type = USBD_EVT_POWER; + ev->evt.pwr_evt.state = new_state; + + + usbd_evt_put(ev); + + if (usbd_ctx.attached) { + usbd_work_schedule(); + } +} + +/* Stopping HFXO, algorithm supports case when stop comes before clock is + * started. In that case, it is stopped from the callback context. + */ +static int hfxo_stop(struct nrf_usbd_ctx *ctx) +{ + if (atomic_cas(&ctx->clk_requested, 1, 0)) { + return onoff_cancel_or_release(ctx->hfxo_mgr, &ctx->hfxo_cli); + } + + return 0; +} + +static int hfxo_start(struct nrf_usbd_ctx *ctx) +{ + if (atomic_cas(&ctx->clk_requested, 0, 1)) { + sys_notify_init_spinwait(&ctx->hfxo_cli.notify); + + return onoff_request(ctx->hfxo_mgr, &ctx->hfxo_cli); + } + + return 0; +} + +static void usbd_enable_endpoints(struct nrf_usbd_ctx *ctx) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + int i; + + for (i = 0; i < CFG_EPIN_CNT; i++) { + ep_ctx = in_endpoint_ctx(i); + __ASSERT_NO_MSG(ep_ctx); + + if (ep_ctx->cfg.en) { + nrfx_usbd_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); + } + } + + if (CFG_EP_ISOIN_CNT) { + ep_ctx = in_endpoint_ctx(NRF_USBD_EPIN(8)); + __ASSERT_NO_MSG(ep_ctx); + + if (ep_ctx->cfg.en) { + nrfx_usbd_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); + } + } + + for (i = 0; i < CFG_EPOUT_CNT; i++) { + ep_ctx = out_endpoint_ctx(i); + __ASSERT_NO_MSG(ep_ctx); + + if (ep_ctx->cfg.en) { + nrfx_usbd_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); + } + } + + if (CFG_EP_ISOOUT_CNT) { + ep_ctx = out_endpoint_ctx(NRF_USBD_EPOUT(8)); + __ASSERT_NO_MSG(ep_ctx); + + if (ep_ctx->cfg.en) { + nrfx_usbd_ep_enable(ep_addr_to_nrfx(ep_ctx->cfg.addr)); + } + } +} + +/** + * @brief Reset endpoint state. + * + * Resets the internal logic state for a given endpoint. + * + * @param[in] ep_cts Endpoint structure control block + */ +static void ep_ctx_reset(struct nrf_usbd_ep_ctx *ep_ctx) +{ + ep_ctx->buf.data = ep_ctx->buf.block.data; + ep_ctx->buf.curr = ep_ctx->buf.data; + ep_ctx->buf.len = 0U; + + /* Abort ongoing write operation. */ + if (ep_ctx->write_in_progress) { + nrfx_usbd_ep_abort(ep_addr_to_nrfx(ep_ctx->cfg.addr)); + } + + ep_ctx->read_complete = true; + ep_ctx->read_pending = false; + ep_ctx->write_in_progress = false; + ep_ctx->trans_zlp = false; +} + +/** + * @brief Initialize all endpoint structures. + * + * Endpoint buffers are allocated during the first call of this function. + * This function may also be called again on every USB reset event + * to reinitialize the state of all endpoints. + */ +static int eps_ctx_init(void) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + uint32_t i; + + for (i = 0U; i < CFG_EPIN_CNT; i++) { + ep_ctx = in_endpoint_ctx(i); + __ASSERT_NO_MSG(ep_ctx); + ep_ctx_reset(ep_ctx); + } + + for (i = 0U; i < CFG_EPOUT_CNT; i++) { + ep_ctx = out_endpoint_ctx(i); + __ASSERT_NO_MSG(ep_ctx); + + if (!ep_ctx->buf.block.data) { + ep_ctx->buf.block.data = ep_out_bufs[i]; + } + + ep_ctx_reset(ep_ctx); + } + + if (CFG_EP_ISOIN_CNT) { + ep_ctx = in_endpoint_ctx(NRF_USBD_EPIN(8)); + __ASSERT_NO_MSG(ep_ctx); + ep_ctx_reset(ep_ctx); + } + + if (CFG_EP_ISOOUT_CNT) { + BUILD_ASSERT(CFG_EP_ISOOUT_CNT <= 1); + + ep_ctx = out_endpoint_ctx(NRF_USBD_EPOUT(8)); + __ASSERT_NO_MSG(ep_ctx); + + if (!ep_ctx->buf.block.data) { + ep_ctx->buf.block.data = ep_isoout_bufs[0]; + } + + ep_ctx_reset(ep_ctx); + } + + return 0; +} + +static void eps_ctx_uninit(void) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + uint32_t i; + + for (i = 0U; i < CFG_EPIN_CNT; i++) { + ep_ctx = in_endpoint_ctx(i); + __ASSERT_NO_MSG(ep_ctx); + memset(ep_ctx, 0, sizeof(*ep_ctx)); + } + + for (i = 0U; i < CFG_EPOUT_CNT; i++) { + ep_ctx = out_endpoint_ctx(i); + __ASSERT_NO_MSG(ep_ctx); + memset(ep_ctx, 0, sizeof(*ep_ctx)); + } + + if (CFG_EP_ISOIN_CNT) { + ep_ctx = in_endpoint_ctx(NRF_USBD_EPIN(8)); + __ASSERT_NO_MSG(ep_ctx); + memset(ep_ctx, 0, sizeof(*ep_ctx)); + } + + if (CFG_EP_ISOOUT_CNT) { + ep_ctx = out_endpoint_ctx(NRF_USBD_EPOUT(8)); + __ASSERT_NO_MSG(ep_ctx); + memset(ep_ctx, 0, sizeof(*ep_ctx)); + } +} + +static inline void usbd_work_process_pwr_events(struct usbd_pwr_event *pwr_evt) +{ + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + int err; + + switch (pwr_evt->state) { + case USBD_ATTACHED: + if (!nrfx_usbd_is_enabled()) { + LOG_DBG("USB detected"); + nrfx_usbd_enable(); + err = hfxo_start(ctx); + __ASSERT_NO_MSG(err >= 0); + } + + /* No callback here. + * Stack will be notified when the peripheral is ready. + */ + break; + + case USBD_POWERED: + usbd_enable_endpoints(ctx); + nrfx_usbd_start(true); + ctx->ready = true; + + LOG_DBG("USB Powered"); + + if (ctx->status_cb) { + ctx->status_cb(USB_DC_CONNECTED, NULL); + } + break; + + case USBD_DETACHED: + ctx->ready = false; + nrfx_usbd_disable(); + err = hfxo_stop(ctx); + __ASSERT_NO_MSG(err >= 0); + + LOG_DBG("USB Removed"); + + if (ctx->status_cb) { + ctx->status_cb(USB_DC_DISCONNECTED, NULL); + } + break; + + case USBD_SUSPENDED: + if (dev_ready()) { + nrfx_usbd_suspend(); + LOG_DBG("USB Suspend state"); + + if (ctx->status_cb) { + ctx->status_cb(USB_DC_SUSPEND, NULL); + } + } + break; + case USBD_RESUMED: + if (ctx->status_cb && dev_ready()) { + LOG_DBG("USB resume"); + ctx->status_cb(USB_DC_RESUME, NULL); + } + break; + + default: + break; + } +} + +static inline void usbd_work_process_setup(struct nrf_usbd_ep_ctx *ep_ctx) +{ + __ASSERT_NO_MSG(ep_ctx); + __ASSERT(ep_ctx->cfg.type == USB_DC_EP_CONTROL, + "Invalid event on CTRL EP."); + + struct usb_setup_packet *usbd_setup; + + /* SETUP packets are handled by USBD hardware. + * For compatibility with the USB stack, + * SETUP packet must be reassembled. + */ + usbd_setup = (struct usb_setup_packet *)ep_ctx->buf.data; + memset(usbd_setup, 0, sizeof(struct usb_setup_packet)); + usbd_setup->bmRequestType = nrf_usbd_setup_bmrequesttype_get(NRF_USBD); + usbd_setup->bRequest = nrf_usbd_setup_brequest_get(NRF_USBD); + usbd_setup->wValue = nrf_usbd_setup_wvalue_get(NRF_USBD); + usbd_setup->wIndex = nrf_usbd_setup_windex_get(NRF_USBD); + usbd_setup->wLength = nrf_usbd_setup_wlength_get(NRF_USBD); + ep_ctx->buf.len = sizeof(struct usb_setup_packet); + + /* Copy setup packet to driver internal structure */ + memcpy(&usbd_ctx.setup, usbd_setup, sizeof(struct usb_setup_packet)); + + LOG_DBG("SETUP: bR:0x%02x bmRT:0x%02x wV:0x%04x wI:0x%04x wL:%d", + (uint32_t)usbd_setup->bRequest, + (uint32_t)usbd_setup->bmRequestType, + (uint32_t)usbd_setup->wValue, + (uint32_t)usbd_setup->wIndex, + (uint32_t)usbd_setup->wLength); + + /* Inform the stack. */ + ep_ctx->cfg.cb(ep_ctx->cfg.addr, USB_DC_EP_SETUP); + + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + + if ((REQTYPE_GET_DIR(usbd_setup->bmRequestType) + == REQTYPE_DIR_TO_DEVICE) + && (usbd_setup->wLength)) { + ctx->ctrl_read_len = usbd_setup->wLength; + /* Allow data chunk on EP0 OUT */ + nrfx_usbd_setup_data_clear(); + } else { + ctx->ctrl_read_len = 0U; + } +} + +static inline void usbd_work_process_recvreq(struct nrf_usbd_ctx *ctx, + struct nrf_usbd_ep_ctx *ep_ctx) +{ + if (!ep_ctx->read_pending) { + return; + } + if (!ep_ctx->read_complete) { + return; + } + + ep_ctx->read_pending = false; + ep_ctx->read_complete = false; + + k_mutex_lock(&ctx->drv_lock, K_FOREVER); + NRFX_USBD_TRANSFER_OUT(transfer, ep_ctx->buf.data, + ep_ctx->cfg.max_sz); + nrfx_err_t err = nrfx_usbd_ep_transfer( + ep_addr_to_nrfx(ep_ctx->cfg.addr), &transfer); + if (err != NRFX_SUCCESS) { + LOG_ERR("nRF USBD transfer error (OUT): 0x%02x", err); + } + k_mutex_unlock(&ctx->drv_lock); +} + + +static inline void usbd_work_process_ep_events(struct usbd_ep_event *ep_evt) +{ + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + struct nrf_usbd_ep_ctx *ep_ctx = ep_evt->ep; + + __ASSERT_NO_MSG(ep_ctx); + + switch (ep_evt->evt_type) { + case EP_EVT_SETUP_RECV: + usbd_work_process_setup(ep_ctx); + break; + + case EP_EVT_RECV_REQ: + usbd_work_process_recvreq(ctx, ep_ctx); + break; + + case EP_EVT_RECV_COMPLETE: + ep_ctx->cfg.cb(ep_ctx->cfg.addr, + USB_DC_EP_DATA_OUT); + break; + + case EP_EVT_WRITE_COMPLETE: + if (ep_ctx->cfg.type == USB_DC_EP_CONTROL && + !ep_ctx->trans_zlp) { + /* Trigger the hardware to perform + * status stage, but only if there is + * no ZLP required. + */ + k_mutex_lock(&ctx->drv_lock, K_FOREVER); + nrfx_usbd_setup_clear(); + k_mutex_unlock(&ctx->drv_lock); + } + ep_ctx->cfg.cb(ep_ctx->cfg.addr, + USB_DC_EP_DATA_IN); + break; + default: + break; + } +} + +static void usbd_event_transfer_ctrl(nrfx_usbd_evt_t const *const p_event) +{ + struct nrf_usbd_ep_ctx *ep_ctx = + endpoint_ctx(p_event->data.eptransfer.ep); + + if (NRF_USBD_EPIN_CHECK(p_event->data.eptransfer.ep)) { + switch (p_event->data.eptransfer.status) { + case NRFX_USBD_EP_OK: { + struct usbd_event *ev = usbd_evt_alloc(); + + if (!ev) { + return; + } + + ep_ctx->write_in_progress = false; + ev->evt_type = USBD_EVT_EP; + ev->evt.ep_evt.evt_type = EP_EVT_WRITE_COMPLETE; + ev->evt.ep_evt.ep = ep_ctx; + + LOG_DBG("ctrl write complete"); + usbd_evt_put(ev); + usbd_work_schedule(); + } + break; + + default: { + LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", + p_event->data.eptransfer.status, + p_event->data.eptransfer.ep); + } + break; + } + } else { + switch (p_event->data.eptransfer.status) { + case NRFX_USBD_EP_WAITING: { + struct usbd_event *ev = usbd_evt_alloc(); + + if (!ev) { + return; + } + + LOG_DBG("ctrl read request"); + + ep_ctx->read_pending = true; + ev->evt_type = USBD_EVT_EP; + ev->evt.ep_evt.evt_type = EP_EVT_RECV_REQ; + ev->evt.ep_evt.ep = ep_ctx; + + usbd_evt_put(ev); + usbd_work_schedule(); + } + break; + + case NRFX_USBD_EP_OK: { + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + struct usbd_event *ev = usbd_evt_alloc(); + + if (!ev) { + return; + } + nrfx_usbd_ep_status_t err_code; + + ev->evt_type = USBD_EVT_EP; + ev->evt.ep_evt.evt_type = EP_EVT_RECV_COMPLETE; + ev->evt.ep_evt.ep = ep_ctx; + + err_code = nrfx_usbd_ep_status_get( + p_event->data.eptransfer.ep, &ep_ctx->buf.len); + + if (err_code != NRFX_USBD_EP_OK) { + LOG_ERR("_ep_status_get failed! Code: %d", + err_code); + __ASSERT_NO_MSG(0); + } + LOG_DBG("ctrl read done: %d", ep_ctx->buf.len); + + if (ctx->ctrl_read_len > ep_ctx->buf.len) { + ctx->ctrl_read_len -= ep_ctx->buf.len; + /* Allow next data chunk on EP0 OUT */ + nrfx_usbd_setup_data_clear(); + } else { + ctx->ctrl_read_len = 0U; + } + + usbd_evt_put(ev); + usbd_work_schedule(); + } + break; + + default: { + LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", + p_event->data.eptransfer.status, + p_event->data.eptransfer.ep); + } + break; + } + } +} + +static void usbd_event_transfer_data(nrfx_usbd_evt_t const *const p_event) +{ + struct nrf_usbd_ep_ctx *ep_ctx = + endpoint_ctx(p_event->data.eptransfer.ep); + + if (NRF_USBD_EPIN_CHECK(p_event->data.eptransfer.ep)) { + switch (p_event->data.eptransfer.status) { + case NRFX_USBD_EP_OK: { + struct usbd_event *ev = usbd_evt_alloc(); + + if (!ev) { + return; + } + + LOG_DBG("write complete, ep 0x%02x", + (uint32_t)p_event->data.eptransfer.ep); + + ep_ctx->write_in_progress = false; + ev->evt_type = USBD_EVT_EP; + ev->evt.ep_evt.evt_type = EP_EVT_WRITE_COMPLETE; + ev->evt.ep_evt.ep = ep_ctx; + usbd_evt_put(ev); + usbd_work_schedule(); + } + break; + + case NRFX_USBD_EP_ABORTED: { + LOG_DBG("Endpoint 0x%02x write aborted", + p_event->data.eptransfer.ep); + } + break; + + default: { + LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", + p_event->data.eptransfer.status, + p_event->data.eptransfer.ep); + } + break; + } + + } else { + switch (p_event->data.eptransfer.status) { + case NRFX_USBD_EP_WAITING: { + struct usbd_event *ev = usbd_evt_alloc(); + + if (!ev) { + return; + } + + LOG_DBG("read request, ep 0x%02x", + (uint32_t)p_event->data.eptransfer.ep); + + ep_ctx->read_pending = true; + ev->evt_type = USBD_EVT_EP; + ev->evt.ep_evt.evt_type = EP_EVT_RECV_REQ; + ev->evt.ep_evt.ep = ep_ctx; + + usbd_evt_put(ev); + usbd_work_schedule(); + } + break; + + case NRFX_USBD_EP_OK: { + struct usbd_event *ev = usbd_evt_alloc(); + + if (!ev) { + return; + } + + ep_ctx->buf.len = nrf_usbd_ep_amount_get(NRF_USBD, + p_event->data.eptransfer.ep); + + LOG_DBG("read complete, ep 0x%02x, len %d", + (uint32_t)p_event->data.eptransfer.ep, + ep_ctx->buf.len); + + ev->evt_type = USBD_EVT_EP; + ev->evt.ep_evt.evt_type = EP_EVT_RECV_COMPLETE; + ev->evt.ep_evt.ep = ep_ctx; + + usbd_evt_put(ev); + usbd_work_schedule(); + } + break; + + default: { + LOG_ERR("Unexpected event (nrfx_usbd): %d, ep 0x%02x", + p_event->data.eptransfer.status, + p_event->data.eptransfer.ep); + } + break; + } + } +} + +/** + * @brief nRFx USBD driver event handler function. + */ +static void usbd_event_handler(nrfx_usbd_evt_t const *const p_event) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + struct usbd_event evt = {0}; + bool put_evt = false; + + switch (p_event->type) { + case NRFX_USBD_EVT_SUSPEND: + LOG_DBG("SUSPEND state detected"); + evt.evt_type = USBD_EVT_POWER; + evt.evt.pwr_evt.state = USBD_SUSPENDED; + put_evt = true; + break; + case NRFX_USBD_EVT_RESUME: + LOG_DBG("RESUMING from suspend"); + evt.evt_type = USBD_EVT_POWER; + evt.evt.pwr_evt.state = USBD_RESUMED; + put_evt = true; + break; + case NRFX_USBD_EVT_WUREQ: + LOG_DBG("RemoteWU initiated"); + evt.evt_type = USBD_EVT_POWER; + evt.evt.pwr_evt.state = USBD_RESUMED; + put_evt = true; + break; + case NRFX_USBD_EVT_RESET: + evt.evt_type = USBD_EVT_RESET; + put_evt = true; + break; + case NRFX_USBD_EVT_SOF: + if (IS_ENABLED(CONFIG_USB_DEVICE_SOF)) { + evt.evt_type = USBD_EVT_SOF; + put_evt = true; + } + break; + + case NRFX_USBD_EVT_EPTRANSFER: + ep_ctx = endpoint_ctx(p_event->data.eptransfer.ep); + switch (ep_ctx->cfg.type) { + case USB_DC_EP_CONTROL: + usbd_event_transfer_ctrl(p_event); + break; + case USB_DC_EP_BULK: + case USB_DC_EP_INTERRUPT: + usbd_event_transfer_data(p_event); + break; + case USB_DC_EP_ISOCHRONOUS: + usbd_event_transfer_data(p_event); + break; + default: + break; + } + break; + + case NRFX_USBD_EVT_SETUP: { + nrfx_usbd_setup_t drv_setup; + + nrfx_usbd_setup_get(&drv_setup); + if ((drv_setup.bRequest != REQ_SET_ADDRESS) + || (REQTYPE_GET_TYPE(drv_setup.bmRequestType) + != REQTYPE_TYPE_STANDARD)) { + /* SetAddress is habdled by USBD hardware. + * No software action required. + */ + + struct nrf_usbd_ep_ctx *ep_ctx = + endpoint_ctx(NRF_USBD_EPOUT(0)); + + evt.evt_type = USBD_EVT_EP; + evt.evt.ep_evt.ep = ep_ctx; + evt.evt.ep_evt.evt_type = EP_EVT_SETUP_RECV; + put_evt = true; + } + break; + } + + default: + break; + } + + if (put_evt) { + struct usbd_event *ev; + + ev = usbd_evt_alloc(); + if (!ev) { + return; + } + ev->evt_type = evt.evt_type; + ev->evt = evt.evt; + usbd_evt_put(ev); + usbd_work_schedule(); + } +} + +static inline void usbd_reinit(void) +{ + int ret; + nrfx_err_t err; + + nrfx_power_usbevt_disable(); + nrfx_usbd_disable(); + nrfx_usbd_uninit(); + + usbd_evt_flush(); + + ret = eps_ctx_init(); + __ASSERT_NO_MSG(ret == 0); + + nrfx_power_usbevt_enable(); + err = nrfx_usbd_init(usbd_event_handler); + + if (err != NRFX_SUCCESS) { + LOG_DBG("nRF USBD driver reinit failed. Code: %d", err); + __ASSERT_NO_MSG(0); + } +} + +/** + * @brief funciton to generate fake receive request for + * ISO OUT EP. + * + * ISO OUT endpoint does not generate irq by itself and reading + * from ISO OUT ep is sunchronized with SOF frame. For more details + * refer to Nordic usbd specification. + */ +static void usbd_sof_trigger_iso_read(void) +{ + struct usbd_event *ev; + struct nrf_usbd_ep_ctx *ep_ctx; + + ep_ctx = endpoint_ctx(NRFX_USBD_EPOUT8); + if (!ep_ctx) { + LOG_ERR("There is no ISO ep"); + return; + } + + if (ep_ctx->cfg.en) { + /* Dissect receive request + * if the iso OUT ep is enabled + */ + ep_ctx->read_pending = true; + ep_ctx->read_complete = true; + ev = usbd_evt_alloc(); + if (!ev) { + LOG_ERR("Failed to alloc evt"); + return; + } + ev->evt_type = USBD_EVT_EP; + ev->evt.ep_evt.evt_type = EP_EVT_RECV_REQ; + ev->evt.ep_evt.ep = ep_ctx; + usbd_evt_put(ev); + usbd_work_schedule(); + } else { + LOG_DBG("Endpoint is not enabled"); + } +} + +/* Work handler */ +static void usbd_work_handler(struct k_work *item) +{ + struct nrf_usbd_ctx *ctx; + struct usbd_event *ev; + + ctx = CONTAINER_OF(item, struct nrf_usbd_ctx, usb_work); + + while ((ev = usbd_evt_get()) != NULL) { + if (!dev_ready() && ev->evt_type != USBD_EVT_POWER) { + /* Drop non-power events when cable is detached. */ + usbd_evt_free(ev); + continue; + } + + switch (ev->evt_type) { + case USBD_EVT_EP: + if (!ctx->attached) { + LOG_ERR("not attached, EP 0x%02x event dropped", + (uint32_t)ev->evt.ep_evt.ep->cfg.addr); + } + usbd_work_process_ep_events(&ev->evt.ep_evt); + break; + case USBD_EVT_POWER: + usbd_work_process_pwr_events(&ev->evt.pwr_evt); + break; + case USBD_EVT_RESET: + LOG_DBG("USBD reset event"); + k_mutex_lock(&ctx->drv_lock, K_FOREVER); + eps_ctx_init(); + k_mutex_unlock(&ctx->drv_lock); + + if (ctx->status_cb) { + ctx->status_cb(USB_DC_RESET, NULL); + } + break; + case USBD_EVT_SOF: + usbd_sof_trigger_iso_read(); + + if (ctx->status_cb) { + ctx->status_cb(USB_DC_SOF, NULL); + } + break; + case USBD_EVT_REINIT: { + /* + * Reinitialize the peripheral after queue + * overflow. + */ + LOG_ERR("USBD event queue full!"); + usbd_reinit(); + break; + } + default: + LOG_ERR("Unknown USBD event: %"PRId16, ev->evt_type); + break; + } + usbd_evt_free(ev); + } +} + +int usb_dc_attach(void) +{ + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + nrfx_err_t err; + int ret; + + if (ctx->attached) { + return 0; + } + + k_mutex_init(&ctx->drv_lock); + ctx->hfxo_mgr = + z_nrf_clock_control_get_onoff( + COND_CODE_1(NRF_CLOCK_HAS_HFCLK192M, + (CLOCK_CONTROL_NRF_SUBSYS_HF192M), + (CLOCK_CONTROL_NRF_SUBSYS_HF))); + + IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), + nrfx_isr, nrfx_usbd_irq_handler, 0); + + err = nrfx_usbd_init(usbd_event_handler); + + if (err != NRFX_SUCCESS) { + LOG_DBG("nRF USBD driver init failed. Code: %d", (uint32_t)err); + return -EIO; + } + nrfx_power_usbevt_enable(); + + ret = eps_ctx_init(); + if (ret == 0) { + ctx->attached = true; + } + + if (!k_fifo_is_empty(&usbd_evt_fifo)) { + usbd_work_schedule(); + } + + if (nrfx_power_usbstatus_get() != NRFX_POWER_USB_STATE_DISCONNECTED) { + /* USBDETECTED event is be generated on cable attachment and + * when cable is already attached during reset, but not when + * the peripheral is re-enabled. + * When USB-enabled bootloader is used, target application + * will not receive this event and it needs to be generated + * again here. + */ + usb_dc_power_event_handler(NRFX_POWER_USB_EVT_DETECTED); + } + + return ret; +} + +int usb_dc_detach(void) +{ + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + + k_mutex_lock(&ctx->drv_lock, K_FOREVER); + + usbd_evt_flush(); + eps_ctx_uninit(); + + if (nrfx_usbd_is_enabled()) { + nrfx_usbd_disable(); + } + + if (nrfx_usbd_is_initialized()) { + nrfx_usbd_uninit(); + } + + (void)hfxo_stop(ctx); + nrfx_power_usbevt_disable(); + + ctx->attached = false; + k_mutex_unlock(&ctx->drv_lock); + + return 0; +} + +int usb_dc_reset(void) +{ + int ret; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + LOG_DBG("USBD Reset"); + + ret = usb_dc_detach(); + if (ret) { + return ret; + } + + ret = usb_dc_attach(); + if (ret) { + return ret; + } + + return 0; +} + +int usb_dc_set_address(const uint8_t addr) +{ + struct nrf_usbd_ctx *ctx; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + /** + * Nothing to do here. The USBD HW already takes care of initiating + * STATUS stage. Just double check the address for sanity. + */ + __ASSERT(addr == (uint8_t)NRF_USBD->USBADDR, "USB Address incorrect!"); + + ctx = get_usbd_ctx(); + + LOG_DBG("Address set to: %d", addr); + + return 0; +} + + +int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data *const ep_cfg) +{ + uint8_t ep_idx = NRF_USBD_EP_NR_GET(ep_cfg->ep_addr); + + LOG_DBG("ep 0x%02x, mps %d, type %d", ep_cfg->ep_addr, ep_cfg->ep_mps, + ep_cfg->ep_type); + + if ((ep_cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { + LOG_ERR("invalid endpoint configuration"); + return -1; + } + + if (!NRF_USBD_EP_VALIDATE(ep_cfg->ep_addr)) { + LOG_ERR("invalid endpoint index/address"); + return -1; + } + + if ((ep_cfg->ep_type == USB_DC_EP_ISOCHRONOUS) && + (!NRF_USBD_EPISO_CHECK(ep_cfg->ep_addr))) { + LOG_WRN("invalid endpoint type"); + return -1; + } + + return 0; +} + +int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data *const ep_cfg) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached()) { + return -ENODEV; + } + + /** + * TODO: + * For ISO endpoints, application has to use EPIN/OUT 8 + * but right now there's no standard way of knowing the + * ISOIN/ISOOUT endpoint number in advance to configure + * accordingly. So either this needs to be chosen in the + * menuconfig in application area or perhaps in device tree + * at compile time or introduce a new API to read the endpoint + * configuration at runtime before configuring them. + */ + ep_ctx = endpoint_ctx(ep_cfg->ep_addr); + if (!ep_ctx) { + return -EINVAL; + } + + ep_ctx->cfg.addr = ep_cfg->ep_addr; + ep_ctx->cfg.type = ep_cfg->ep_type; + ep_ctx->cfg.max_sz = ep_cfg->ep_mps; + + if (!NRF_USBD_EPISO_CHECK(ep_cfg->ep_addr)) { + if ((ep_cfg->ep_mps & (ep_cfg->ep_mps - 1)) != 0U) { + LOG_ERR("EP max packet size must be a power of 2"); + return -EINVAL; + } + } + + nrfx_usbd_ep_max_packet_size_set(ep_addr_to_nrfx(ep_cfg->ep_addr), + ep_cfg->ep_mps); + + return 0; +} + +int usb_dc_ep_set_stall(const uint8_t ep) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + switch (ep_ctx->cfg.type) { + case USB_DC_EP_CONTROL: + nrfx_usbd_setup_stall(); + break; + case USB_DC_EP_BULK: + case USB_DC_EP_INTERRUPT: + nrfx_usbd_ep_stall(ep_addr_to_nrfx(ep)); + break; + case USB_DC_EP_ISOCHRONOUS: + LOG_ERR("STALL unsupported on ISO endpoint"); + return -EINVAL; + } + + ep_ctx->buf.len = 0U; + ep_ctx->buf.curr = ep_ctx->buf.data; + + LOG_DBG("STALL on EP 0x%02x", ep); + + return 0; +} + +int usb_dc_ep_clear_stall(const uint8_t ep) +{ + + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + if (NRF_USBD_EPISO_CHECK(ep)) { + /* ISO transactions do not support a handshake phase. */ + return -EINVAL; + } + + nrfx_usbd_ep_dtoggle_clear(ep_addr_to_nrfx(ep)); + nrfx_usbd_ep_stall_clear(ep_addr_to_nrfx(ep)); + LOG_DBG("Unstall on EP 0x%02x", ep); + + return 0; +} + +int usb_dc_ep_halt(const uint8_t ep) +{ + return usb_dc_ep_set_stall(ep); +} + +int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + if (!stalled) { + return -EINVAL; + } + + *stalled = (uint8_t) nrfx_usbd_ep_stall_check(ep_addr_to_nrfx(ep)); + + return 0; +} + +int usb_dc_ep_enable(const uint8_t ep) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached()) { + return -ENODEV; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + if (!NRF_USBD_EPISO_CHECK(ep)) { + /* ISO transactions for full-speed device do not support + * toggle sequencing and should only send DATA0 PID. + */ + nrfx_usbd_ep_dtoggle_clear(ep_addr_to_nrfx(ep)); + /** Endpoint is enabled on SetInterface request. + * This should also clear EP's halt status. + */ + nrfx_usbd_ep_stall_clear(ep_addr_to_nrfx(ep)); + } + if (ep_ctx->cfg.en) { + return -EALREADY; + } + + LOG_DBG("EP enable: 0x%02x", ep); + + ep_ctx->cfg.en = true; + + /* Defer the endpoint enable if USBD is not ready yet. */ + if (dev_ready()) { + nrfx_usbd_ep_enable(ep_addr_to_nrfx(ep)); + } + + return 0; +} + +int usb_dc_ep_disable(const uint8_t ep) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + if (!ep_ctx->cfg.en) { + return -EALREADY; + } + + LOG_DBG("EP disable: 0x%02x", ep); + + nrfx_usbd_ep_disable(ep_addr_to_nrfx(ep)); + ep_ctx->cfg.en = false; + + return 0; +} + +int usb_dc_ep_flush(const uint8_t ep) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + ep_ctx->buf.len = 0U; + ep_ctx->buf.curr = ep_ctx->buf.data; + + nrfx_usbd_transfer_out_drop(ep_addr_to_nrfx(ep)); + + return 0; +} + +int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data, + const uint32_t data_len, uint32_t *const ret_bytes) +{ + LOG_DBG("ep_write: ep 0x%02x, len %d", ep, data_len); + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + struct nrf_usbd_ep_ctx *ep_ctx; + int result = 0; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + if (NRF_USBD_EPOUT_CHECK(ep)) { + return -EINVAL; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + if (!ep_ctx->cfg.en) { + LOG_ERR("Endpoint 0x%02x is not enabled", ep); + return -EINVAL; + } + + k_mutex_lock(&ctx->drv_lock, K_FOREVER); + + /* USBD driver does not allow scheduling multiple DMA transfers + * for one EP at a time. Next USB transfer on this endpoint can be + * triggered after the completion of previous one. + */ + if (ep_ctx->write_in_progress) { + k_mutex_unlock(&ctx->drv_lock); + return -EAGAIN; + } + + /** Clear the ZLP flag if current write is ZLP. After the ZLP will be + * send the driver will perform status stage. + */ + if (!data_len && ep_ctx->trans_zlp) { + ep_ctx->trans_zlp = false; + } + + /** If writing to a Control Endpoint there might be a need to transfer + * ZLP. If the Hosts asks for more data that the device may return and + * the last packet is wMaxPacketSize long. The driver must send ZLP. + * For consistance with the Zephyr USB stack sending ZLP must be issued + * from the stack level. Making trans_zlp flag true results in blocking + * the driver from starting setup stage without required ZLP. + */ + if (ep_ctx->cfg.type == USB_DC_EP_CONTROL) { + if (data_len && usbd_ctx.setup.wLength > data_len && + !(data_len % ep_ctx->cfg.max_sz)) { + ep_ctx->trans_zlp = true; + } + } + + /* Setup stage is handled by hardware. + * Detect the setup stage initiated by the stack + * and perform appropriate action. + */ + if ((ep_ctx->cfg.type == USB_DC_EP_CONTROL) + && (nrfx_usbd_last_setup_dir_get() != ep)) { + nrfx_usbd_setup_clear(); + k_mutex_unlock(&ctx->drv_lock); + return 0; + } + + ep_ctx->write_in_progress = true; + NRFX_USBD_TRANSFER_IN(transfer, data, data_len, 0); + nrfx_err_t err = nrfx_usbd_ep_transfer(ep_addr_to_nrfx(ep), &transfer); + + if (err != NRFX_SUCCESS) { + ep_ctx->write_in_progress = false; + if (ret_bytes) { + *ret_bytes = 0; + } + result = -EIO; + LOG_ERR("nRF USBD write error: %d", (uint32_t)err); + } else { + if (ret_bytes) { + *ret_bytes = data_len; + } + } + + k_mutex_unlock(&ctx->drv_lock); + return result; +} + +int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, + uint32_t *read_bytes) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + uint32_t bytes_to_copy; + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + if (NRF_USBD_EPIN_CHECK(ep)) { + return -EINVAL; + } + + if (!data && max_data_len) { + return -EINVAL; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + if (!ep_ctx->cfg.en) { + LOG_ERR("Endpoint 0x%02x is not enabled", ep); + return -EINVAL; + } + + k_mutex_lock(&ctx->drv_lock, K_FOREVER); + + bytes_to_copy = MIN(max_data_len, ep_ctx->buf.len); + + if (!data && !max_data_len) { + if (read_bytes) { + *read_bytes = ep_ctx->buf.len; + } + k_mutex_unlock(&ctx->drv_lock); + return 0; + } + + memcpy(data, ep_ctx->buf.curr, bytes_to_copy); + + ep_ctx->buf.curr += bytes_to_copy; + ep_ctx->buf.len -= bytes_to_copy; + if (read_bytes) { + *read_bytes = bytes_to_copy; + } + + k_mutex_unlock(&ctx->drv_lock); + return 0; +} + +int usb_dc_ep_read_continue(uint8_t ep) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + + if (!dev_attached() || !dev_ready()) { + return -ENODEV; + } + + if (NRF_USBD_EPIN_CHECK(ep)) { + return -EINVAL; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + if (!ep_ctx->cfg.en) { + LOG_ERR("Endpoint 0x%02x is not enabled", ep); + return -EINVAL; + } + + k_mutex_lock(&ctx->drv_lock, K_FOREVER); + if (!ep_ctx->buf.len) { + ep_ctx->buf.curr = ep_ctx->buf.data; + ep_ctx->read_complete = true; + + if (ep_ctx->read_pending) { + struct usbd_event *ev = usbd_evt_alloc(); + + if (!ev) { + k_mutex_unlock(&ctx->drv_lock); + return -ENOMEM; + } + + ev->evt_type = USBD_EVT_EP; + ev->evt.ep_evt.ep = ep_ctx; + ev->evt.ep_evt.evt_type = EP_EVT_RECV_REQ; + usbd_evt_put(ev); + usbd_work_schedule(); + } + } + k_mutex_unlock(&ctx->drv_lock); + + return 0; +} + +int usb_dc_ep_read(const uint8_t ep, uint8_t *const data, + const uint32_t max_data_len, uint32_t *const read_bytes) +{ + LOG_DBG("ep_read: ep 0x%02x, maxlen %d", ep, max_data_len); + int ret; + + ret = usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes); + if (ret) { + return ret; + } + + if (!data && !max_data_len) { + return ret; + } + + ret = usb_dc_ep_read_continue(ep); + return ret; +} + +int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached()) { + return -ENODEV; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + ep_ctx->cfg.cb = cb; + + return 0; +} + +void usb_dc_set_status_callback(const usb_dc_status_callback cb) +{ + get_usbd_ctx()->status_cb = cb; +} + +int usb_dc_ep_mps(const uint8_t ep) +{ + struct nrf_usbd_ep_ctx *ep_ctx; + + if (!dev_attached()) { + return -ENODEV; + } + + ep_ctx = endpoint_ctx(ep); + if (!ep_ctx) { + return -EINVAL; + } + + return ep_ctx->cfg.max_sz; +} + +int usb_dc_wakeup_request(void) +{ + bool res = nrfx_usbd_wakeup_req(); + + if (!res) { + return -EAGAIN; + } + return 0; +} + +static int usb_init(const struct device *arg) +{ + struct nrf_usbd_ctx *ctx = get_usbd_ctx(); + +#ifdef CONFIG_HAS_HW_NRF_USBREG + /* Use CLOCK/POWER priority for compatibility with other series where + * USB events are handled by CLOCK interrupt handler. + */ + IRQ_CONNECT(USBREGULATOR_IRQn, + DT_IRQ(DT_INST(0, nordic_nrf_clock), priority), + nrfx_isr, nrfx_usbreg_irq_handler, 0); + irq_enable(USBREGULATOR_IRQn); +#endif + + static const nrfx_power_config_t power_config = { + .dcdcen = IS_ENABLED(CONFIG_SOC_DCDC_NRF52X) || + IS_ENABLED(CONFIG_SOC_DCDC_NRF53X_APP), +#if NRFX_POWER_SUPPORTS_DCDCEN_VDDH + .dcdcenhv = IS_ENABLED(CONFIG_SOC_DCDC_NRF53X_HV), +#endif + }; + + static const nrfx_power_usbevt_config_t usbevt_config = { + .handler = usb_dc_power_event_handler + }; + + /* Ignore the return value, as NRFX_ERROR_ALREADY_INITIALIZED is not + * a problem here. + */ + (void)nrfx_power_init(&power_config); + nrfx_power_usbevt_init(&usbevt_config); + + k_work_q_start(&usbd_work_queue, + usbd_work_queue_stack, + K_KERNEL_STACK_SIZEOF(usbd_work_queue_stack), + CONFIG_SYSTEM_WORKQUEUE_PRIORITY); + + k_work_init(&ctx->usb_work, usbd_work_handler); + + return 0; +} + +SYS_INIT(usb_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE); diff --git a/port/nuvoton/m48ssidae/usbd_m480.c b/port/nuvoton/m48ssidae/usbd_m480.c new file mode 100644 index 00000000..a4ec824a --- /dev/null +++ b/port/nuvoton/m48ssidae/usbd_m480.c @@ -0,0 +1,835 @@ +/* + * Copyright (c) 2004-2016 ARM Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "NuMicro.h" + +#define __NO_USB_LIB_C +#include "usb_config.c" + +/* Bit definition of CEPCTL register */ +#define HSUSBD_CEPCTL_NAKCLR ((uint32_t)0x00000000UL) +#define HSUSBD_CEPCTL_STALL ((uint32_t)0x00000002UL) +#define HSUSBD_CEPCTL_ZEROLEN ((uint32_t)0x00000004UL) +#define HSUSBD_CEPCTL_FLUSH ((uint32_t)0x00000008UL) + +/* Bit definition of EPxRSPCTL register */ +#define HSUSBD_EP_RSPCTL_FLUSH ((uint32_t)0x00000001UL) +#define HSUSBD_EP_RSPCTL_MODE_AUTO ((uint32_t)0x00000000UL) +#define HSUSBD_EP_RSPCTL_MODE_MANUAL ((uint32_t)0x00000002UL) +#define HSUSBD_EP_RSPCTL_MODE_FLY ((uint32_t)0x00000004UL) +#define HSUSBD_EP_RSPCTL_MODE_MASK ((uint32_t)0x00000006UL) +#define HSUSBD_EP_RSPCTL_TOGGLE ((uint32_t)0x00000008UL) +#define HSUSBD_EP_RSPCTL_HALT ((uint32_t)0x00000010UL) +#define HSUSBD_EP_RSPCTL_ZEROLEN ((uint32_t)0x00000020UL) +#define HSUSBD_EP_RSPCTL_SHORTTXEN ((uint32_t)0x00000040UL) +#define HSUSBD_EP_RSPCTL_DISBUF ((uint32_t)0x00000080UL) + +/* Bit definition of EPxCFG register */ +#define HSUSBD_EP_CFG_VALID ((uint32_t)0x00000001UL) +#define HSUSBD_EP_CFG_TYPE_BULK ((uint32_t)0x00000002UL) +#define HSUSBD_EP_CFG_TYPE_INT ((uint32_t)0x00000004UL) +#define HSUSBD_EP_CFG_TYPE_ISO ((uint32_t)0x00000006UL) +#define HSUSBD_EP_CFG_TYPE_MASK ((uint32_t)0x00000006UL) +#define HSUSBD_EP_CFG_DIR_OUT ((uint32_t)0x00000000UL) +#define HSUSBD_EP_CFG_DIR_IN ((uint32_t)0x00000008UL) + +#define HSUSBD_ENABLE_USB() ((uint32_t)(HSUSBD->PHYCTL |= (HSUSBD_PHYCTL_PHYEN_Msk|HSUSBD_PHYCTL_DPPUEN_Msk))) +#define HSUSBD_DISABLE_USB() ((uint32_t)(HSUSBD->PHYCTL &= ~HSUSBD_PHYCTL_DPPUEN_Msk)) +#define HSUSBD_ENABLE_PHY() ((uint32_t)(HSUSBD->PHYCTL |= HSUSBD_PHYCTL_PHYEN_Msk)) +#define HSUSBD_DISABLE_PHY() ((uint32_t)(HSUSBD->PHYCTL &= ~HSUSBD_PHYCTL_PHYEN_Msk)) +#define HSUSBD_SET_SE0() ((uint32_t)(HSUSBD->PHYCTL &= ~HSUSBD_PHYCTL_DPPUEN_Msk)) +#define HSUSBD_CLR_SE0() ((uint32_t)(HSUSBD->PHYCTL |= HSUSBD_PHYCTL_DPPUEN_Msk)) +#define HSUSBD_SET_ADDR(addr) (HSUSBD->FADDR = (addr)) +#define HSUSBD_GET_ADDR() ((uint32_t)(HSUSBD->FADDR)) +#define HSUSBD_ENABLE_USB_INT(intr) (HSUSBD->GINTEN = (intr)) +#define HSUSBD_ENABLE_BUS_INT(intr) (HSUSBD->BUSINTEN = (intr)) +#define HSUSBD_GET_BUS_INT_FLAG() (HSUSBD->BUSINTSTS) +#define HSUSBD_CLR_BUS_INT_FLAG(flag) (HSUSBD->BUSINTSTS = (flag)) +#define HSUSBD_ENABLE_CEP_INT(intr) (HSUSBD->CEPINTEN = (intr)) +#define HSUSBD_CLR_CEP_INT_FLAG(flag) (HSUSBD->CEPINTSTS = (flag)) +#define HSUSBD_SET_CEP_STATE(flag) (HSUSBD->CEPCTL = (flag)) +#define HSUSBD_START_CEP_IN(size) (HSUSBD->CEPTXCNT = (size)) +#define HSUSBD_SET_MAX_PAYLOAD(ep, size) (HSUSBD->EP[(ep)].EPMPS = (size)) +#define HSUSBD_ENABLE_EP_INT(ep, intr) (HSUSBD->EP[(ep)].EPINTEN = (intr)) +#define HSUSBD_GET_EP_INT_FLAG(ep) (HSUSBD->EP[(ep)].EPINTSTS) +#define HSUSBD_CLR_EP_INT_FLAG(ep, flag) (HSUSBD->EP[(ep)].EPINTSTS = (flag)) +#define HSUSBD_SET_DMA_LEN(len) (HSUSBD->DMACNT = (len)) +#define HSUSBD_SET_DMA_ADDR(addr) (HSUSBD->DMAADDR = (addr)) +#define HSUSBD_SET_DMA_READ(epnum) (HSUSBD->DMACTL = (HSUSBD->DMACTL & ~HSUSBD_DMACTL_EPNUM_Msk) | HSUSBD_DMACTL_DMARD_Msk | (epnum) | 0x100) +#define HSUSBD_SET_DMA_WRITE(epnum) (HSUSBD->DMACTL = (HSUSBD->DMACTL & ~(HSUSBD_DMACTL_EPNUM_Msk | HSUSBD_DMACTL_DMARD_Msk | 0x100)) | (epnum)) +#define HSUSBD_ENABLE_DMA() (HSUSBD->DMACTL |= HSUSBD_DMACTL_DMAEN_Msk) +#define HSUSBD_IS_ATTACHED() ((uint32_t)(HSUSBD->PHYCTL & HSUSBD_PHYCTL_VBUSDET_Msk)) + +#define HSUSBD_MAX_EP 12UL +#define CEP 0xFFUL +#define EPA 0UL +#define EPB 1UL +#define EPC 2UL +#define EPD 3UL +#define EPE 4UL +#define EPF 5UL +#define EPG 6UL +#define EPH 7UL +#define EPI 8UL +#define EPJ 9UL +#define EPK 10UL +#define EPL 11UL + +#define USBD_EP_TO_NUM(ep) ((ep == CEP) ? (0) : (1 + (ep - EPA))) +#define USBD_NUM_TO_EP(num) ((num == 0) ? (CEP) : (EPA + (num - 1))) + +#define CEP_BUF_BASE 0 +#define CEP_BUF_LEN USBD_MAX_PACKET0 + +static uint32_t g_u32FreeBufAddr; +static uint8_t g_u8StatusIn; + +/* + * USB Device Interrupt enable + * Called by USBD_Init to enable the USB Interrupt + * Return Value: None + */ + +#ifdef __RTX +void __svc(1) USBD_IntrEna(void); +void __SVC_1(void) +{ +#else +void USBD_IntrEna(void) +{ +#endif + NVIC_EnableIRQ(USBD20_IRQn); +} + + +/* + * USB Device Initialize Function + * Called by the User to initialize USB + * Return Value: None + */ + +void USBD_Init(void) +{ + uint32_t volatile i; + /* Initial USB engine */ + HSUSBD_ENABLE_PHY(); + + /* wait PHY clock ready */ + while (1) { + HSUSBD->EP[EPA].EPMPS = 0x20UL; + + if (HSUSBD->EP[EPA].EPMPS == 0x20UL) { + break; + } + } + + for (i = 0; i < 0x10000; i++); + + if (HSUSBD->OPER & HSUSBD_OPER_CURSPD_Msk) { + USBD_HighSpeed = __TRUE; + } else { + USBD_HighSpeed = __FALSE; + } + + /* Enable USB BUS, CEP global interrupt */ + HSUSBD_ENABLE_USB_INT(HSUSBD_GINTEN_USBIEN_Msk | HSUSBD_GINTEN_CEPIEN_Msk); + /* Enable BUS interrupt */ + HSUSBD_ENABLE_BUS_INT(HSUSBD_BUSINTEN_RESUMEIEN_Msk | HSUSBD_BUSINTEN_RSTIEN_Msk | HSUSBD_BUSINTEN_VBUSDETIEN_Msk | HSUSBD_BUSINTEN_SOFIEN_Msk); + /* Reset Address to 0 */ + HSUSBD_SET_ADDR(0); + /* Control endpoint */ + HSUSBD->CEPBUFST = CEP_BUF_BASE; + HSUSBD->CEPBUFEND = CEP_BUF_BASE + CEP_BUF_LEN - 1UL; + HSUSBD_ENABLE_CEP_INT(HSUSBD_CEPINTEN_SETUPPKIEN_Msk | HSUSBD_CEPINTEN_STSDONEIEN_Msk | HSUSBD_CEPINTEN_RXPKIEN_Msk | HSUSBD_CEPINTEN_TXPKIEN_Msk); + USBD_IntrEna(); +} + + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ + +void USBD_Connect(BOOL con) +{ + if (con) { + HSUSBD_CLR_SE0(); + } else { + HSUSBD_SET_SE0(); + } +} + + +/* + * USB Device Reset Function + * Called automatically on USB Device Reset + * Return Value: None + */ + +void USBD_Reset(void) +{ + uint32_t i; + + for (i = 0; i < HSUSBD_MAX_EP; i++) { + HSUSBD->EP[EPA + i].EPRSPCTL = HSUSBD_EPRSPCTL_FLUSH_Msk; + } + + if (HSUSBD->OPER & HSUSBD_OPER_CURSPD_Msk) { + USBD_HighSpeed = __TRUE; + } else { + USBD_HighSpeed = __FALSE; + } + + g_u32FreeBufAddr = CEP_BUF_BASE + CEP_BUF_LEN; + g_u8StatusIn = 0; + HSUSBD_SET_ADDR(0); +} + + +/* + * USB Device Suspend Function + * Called automatically on USB Device Suspend + * Return Value: None + */ + +void USBD_Suspend(void) +{ +} + + +/* + * USB Device Resume Function + * Called automatically on USB Device Resume + * Return Value: None + */ + +void USBD_Resume(void) +{ +} + + +/* + * USB Device Remote Wakeup Function + * Called automatically on USB Device Remote Wakeup + * Return Value: None + */ + +void USBD_WakeUp(void) +{ +} + + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ + +void USBD_WakeUpCfg(BOOL cfg) +{ +} + + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * setup: Called in setup stage (!=0), else after status stage + * Return Value: None + */ + +void USBD_SetAddress(U32 adr, U32 setup) +{ + if (setup) { + return; + } + + HSUSBD_SET_ADDR(adr); +} + + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ + +void USBD_Configure(BOOL cfg) +{ + if (cfg == __FALSE) { + g_u32FreeBufAddr = CEP_BUF_BASE + CEP_BUF_LEN; + } +} + + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ + +void USBD_ConfigEP(USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + uint32_t u32Num, u32Ep, u32Size, u32Type, u32Dir; + u32Num = pEPD->bEndpointAddress & 0x0F; + u32Ep = USBD_NUM_TO_EP(u32Num); + u32Size = pEPD->wMaxPacketSize; + + switch (pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK) { + case USB_ENDPOINT_TYPE_ISOCHRONOUS: + u32Type = HSUSBD_EP_CFG_TYPE_ISO; + break; + + case USB_ENDPOINT_TYPE_BULK: + u32Type = HSUSBD_EP_CFG_TYPE_BULK; + break; + + case USB_ENDPOINT_TYPE_INTERRUPT: + u32Type = HSUSBD_EP_CFG_TYPE_INT; + break; + } + + if (pEPD->bEndpointAddress & USB_ENDPOINT_DIRECTION_MASK) { + u32Dir = HSUSBD_EP_CFG_DIR_IN; + } else { + u32Dir = HSUSBD_EP_CFG_DIR_OUT; + } + + HSUSBD->EP[u32Ep].EPBUFST = g_u32FreeBufAddr; + HSUSBD->EP[u32Ep].EPBUFEND = g_u32FreeBufAddr + u32Size - 1UL; + HSUSBD_SET_MAX_PAYLOAD(u32Ep, u32Size); + HSUSBD->EP[u32Ep].EPRSPCTL = (HSUSBD_EP_RSPCTL_FLUSH | HSUSBD_EP_RSPCTL_MODE_AUTO); + HSUSBD->EP[u32Ep].EPCFG = (u32Type | u32Dir | HSUSBD_EP_CFG_VALID | (u32Num << 4)); + g_u32FreeBufAddr += u32Size; +} + + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ + +void USBD_DirCtrlEP(U32 dir) +{ +} + + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_EnableEP(U32 EPNum) +{ + uint32_t u32Ep, u32Num, u32Intr; + u32Num = EPNum & 0x0F; + u32Ep = USBD_NUM_TO_EP(u32Num); + HSUSBD->GINTEN |= (0x1UL << (HSUSBD_GINTEN_EPAIEN_Pos + (u32Num - USBD_EP_TO_NUM(EPA)))); + + if (EPNum & 0x80) { + u32Intr = HSUSBD_EPINTEN_TXPKIEN_Msk; + } else { + u32Intr = HSUSBD_EPINTEN_RXPKIEN_Msk | HSUSBD_EPINTEN_SHORTRXIEN_Msk | HSUSBD_EPINTEN_BUFFULLIEN_Msk; + } + + HSUSBD_ENABLE_EP_INT(u32Ep, u32Intr); +} + + +/* + * Disable USB Endpoint + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_DisableEP(U32 EPNum) +{ + uint32_t u32Ep, u32Num; + u32Num = EPNum & 0x0F; + u32Ep = USBD_NUM_TO_EP(u32Num); + HSUSBD->GINTEN &= ~(0x1UL << (HSUSBD_GINTEN_EPAIEN_Pos + (u32Num - USBD_EP_TO_NUM(EPA)))); + HSUSBD_ENABLE_EP_INT(u32Ep, 0); +} + + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ResetEP(U32 EPNum) +{ + uint32_t u32Ep, u32Num; + u32Num = EPNum & 0x0F; + u32Ep = USBD_NUM_TO_EP(u32Num); + HSUSBD->EP[u32Ep].EPRSPCTL = (HSUSBD->EP[u32Ep].EPRSPCTL & HSUSBD_EP_RSPCTL_MODE_MASK) | HSUSBD_EPRSPCTL_FLUSH_Msk; +} + + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_SetStallEP(U32 EPNum) +{ + uint32_t u32Ep, u32Num; + u32Num = EPNum & 0x0F; + u32Ep = USBD_NUM_TO_EP(u32Num); + + if (u32Ep == CEP) { + HSUSBD_SET_CEP_STATE(HSUSBD_CEPCTL_STALL); + } else { + HSUSBD->EP[u32Ep].EPRSPCTL = (HSUSBD->EP[u32Ep].EPRSPCTL & 0xF7UL) | HSUSBD_EP_RSPCTL_HALT; + } +} + + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClrStallEP(U32 EPNum) +{ + uint32_t u32Ep, u32Num; + u32Num = EPNum & 0x0F; + u32Ep = USBD_NUM_TO_EP(u32Num); + + if (u32Ep == CEP) { + HSUSBD_SET_CEP_STATE(HSUSBD_CEPCTL_NAKCLR_Msk); + } else { + HSUSBD->EP[u32Ep].EPRSPCTL = (HSUSBD->EP[u32Ep].EPRSPCTL & HSUSBD_EP_RSPCTL_MODE_MASK) | HSUSBD_EP_RSPCTL_TOGGLE; + } +} + + +/* + * Clear USB Device Endpoint Buffer + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClearEPBuf(U32 EPNum) +{ +} + + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ + +U32 USBD_ReadEP(U32 EPNum, U8 *pData, U32 bufsz) +{ + uint32_t u32Ep, u32Num, u32Len, i; + u32Num = EPNum & 0x0F; + u32Ep = USBD_NUM_TO_EP(u32Num); + + if (u32Num == 0) { + if (pData == (uint8_t *)&USBD_SetupPacket) { + *((uint16_t *)(pData + 0)) = (uint16_t)(HSUSBD->SETUP1_0 & 0xFFFFUL); + *((uint16_t *)(pData + 2)) = (uint16_t)(HSUSBD->SETUP3_2 & 0xFFFFUL); + *((uint16_t *)(pData + 4)) = (uint16_t)(HSUSBD->SETUP5_4 & 0xFFFFUL); + *((uint16_t *)(pData + 6)) = (uint16_t)(HSUSBD->SETUP7_6 & 0xFFFFUL); + return 8; + } else { + u32Len = HSUSBD->CEPDATCNT & 0xFFFFUL; + + if (u32Len > bufsz) { + u32Len = bufsz; + } + + for (i = 0; i < bufsz; i++) { + pData[i] = inpb(&HSUSBD->CEPDAT); + } + + return u32Len; + } + } else { + u32Len = HSUSBD->EP[u32Ep].EPDATCNT & 0xFFFFUL; + + if (u32Len > bufsz) { + u32Len = bufsz; + } + + for (i = 0; i < u32Len; i++) { + pData[i] = HSUSBD->EP[u32Ep].EPDAT_BYTE; + } + + return u32Len; + } +} + + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ + +U32 USBD_WriteEP(U32 EPNum, U8 *pData, U32 cnt) +{ + uint32_t u32Ep, u32Num, i; + u32Num = EPNum & 0x0F; + u32Ep = USBD_NUM_TO_EP(u32Num); + + if (u32Num == 0) { + if (pData != NULL) { + if (cnt > 0) { + for (i = 0; i < cnt; i++) { + HSUSBD->CEPDAT_BYTE = pData[i]; + } + + HSUSBD_START_CEP_IN(cnt); + } else { + HSUSBD_SET_CEP_STATE(HSUSBD_CEPCTL_ZEROLEN); + } + } else if (cnt == 0) { + g_u8StatusIn = 1; + HSUSBD_CLR_CEP_INT_FLAG(HSUSBD_CEPINTSTS_STSDONEIF_Msk); + HSUSBD_SET_CEP_STATE(HSUSBD_CEPCTL_NAKCLR); + } + + return cnt; + } else { + if (cnt > 0) { + for (i = 0; i < cnt; i++) { + HSUSBD->EP[u32Ep].EPDAT_BYTE = pData[i]; + } + + HSUSBD->EP[u32Ep].EPRSPCTL = (HSUSBD->EP[u32Ep].EPRSPCTL & HSUSBD_EP_RSPCTL_HALT) | HSUSBD_EP_RSPCTL_SHORTTXEN; + } else { + HSUSBD->EP[u32Ep].EPRSPCTL = (HSUSBD->EP[u32Ep].EPRSPCTL & HSUSBD_EP_RSPCTL_HALT) | HSUSBD_EP_RSPCTL_ZEROLEN; + } + + return cnt; + } +} + + +/* + * Get USB Device Last Frame Number + * Parameters: None + * Return Value: Frame Number + */ + +U32 USBD_GetFrame(void) +{ + return 0; +} + + +#ifdef __RTX +U32 LastError; + +/* + * Get USB Last Error Code + * Parameters: None + * Return Value: Error Code + */ + +U32 USBD_GetError(void) +{ + return (LastError); +} +#endif + + +/* + * USB Device Interrupt Service Routine + */ + +void USBD20_IRQHandler(void) +{ + NVIC_DisableIRQ(USBD20_IRQn); + USBD_SignalHandler(); +} + +void USBD_Handler_Main() +{ + __IO uint32_t IrqStL, IrqSt; + uint32_t u32Ep, u32Num, i; + IrqStL = HSUSBD->GINTSTS & HSUSBD->GINTEN; + + if (!IrqStL) { + return; + } + + if (IrqStL & HSUSBD_GINTSTS_USBIF_Msk) { + IrqSt = HSUSBD->BUSINTSTS & HSUSBD->BUSINTEN; + + if (IrqSt & HSUSBD_BUSINTSTS_SOFIF_Msk) { +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SOF, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + +#endif + HSUSBD_CLR_BUS_INT_FLAG(HSUSBD_BUSINTSTS_SOFIF_Msk); + } + + if (IrqSt & HSUSBD_BUSINTSTS_RSTIF_Msk) { + USBD_Reset(); + usbd_reset_core(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESET, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Reset_Event) { + USBD_P_Reset_Event(); + } + +#endif + HSUSBD_ENABLE_BUS_INT(HSUSBD_BUSINTEN_RSTIEN_Msk | HSUSBD_BUSINTEN_RESUMEIEN_Msk | HSUSBD_BUSINTEN_SUSPENDIEN_Msk | HSUSBD_BUSINTEN_SOFIEN_Msk); + HSUSBD_CLR_BUS_INT_FLAG(HSUSBD_BUSINTSTS_RSTIF_Msk); + HSUSBD_CLR_CEP_INT_FLAG(0x1FFC); + } + + if (IrqSt & HSUSBD_BUSINTSTS_RESUMEIF_Msk) { + USBD_WakeUp(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESUME, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Resume_Event) { + USBD_P_Resume_Event(); + } + +#endif + HSUSBD_ENABLE_BUS_INT(HSUSBD_BUSINTEN_RSTIEN_Msk | HSUSBD_BUSINTEN_SUSPENDIEN_Msk | HSUSBD_BUSINTEN_SOFIEN_Msk); + HSUSBD_CLR_BUS_INT_FLAG(HSUSBD_BUSINTSTS_RESUMEIF_Msk); + } + + if (IrqSt & HSUSBD_BUSINTSTS_SUSPENDIF_Msk) { + USBD_Suspend(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SUSPEND, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Suspend_Event) { + USBD_P_Suspend_Event(); + } + +#endif + HSUSBD_ENABLE_BUS_INT(HSUSBD_BUSINTEN_RSTIEN_Msk | HSUSBD_BUSINTEN_RESUMEIEN_Msk | HSUSBD_BUSINTEN_SOFIEN_Msk); + HSUSBD_CLR_BUS_INT_FLAG(HSUSBD_BUSINTSTS_SUSPENDIF_Msk); + } + + if (IrqSt & HSUSBD_BUSINTSTS_VBUSDETIF_Msk) { + if (HSUSBD_IS_ATTACHED()) { + HSUSBD_ENABLE_USB(); + } else { + HSUSBD_DISABLE_USB(); + } + + HSUSBD_CLR_BUS_INT_FLAG(HSUSBD_BUSINTSTS_VBUSDETIF_Msk); + } + } + + if (IrqStL & HSUSBD_GINTSTS_CEPIF_Msk) { + IrqSt = HSUSBD->CEPINTSTS & HSUSBD->CEPINTEN; + + if (IrqSt & HSUSBD_CEPINTSTS_SETUPPKIF_Msk) { +#ifdef __RTX + + if (USBD_RTX_EPTask[0]) { + isr_evt_set(USBD_EVT_SETUP, USBD_RTX_EPTask[0]); + } + +#else + + if (USBD_P_EP[0]) { + USBD_P_EP[0](USBD_EVT_SETUP); + } + +#endif + HSUSBD_CLR_CEP_INT_FLAG(HSUSBD_CEPINTSTS_SETUPPKIF_Msk); + return; + } + + if (IrqSt & HSUSBD_CEPINTSTS_TXPKIF_Msk) { + HSUSBD_CLR_CEP_INT_FLAG(HSUSBD_CEPINTSTS_STSDONEIF_Msk); + HSUSBD_SET_CEP_STATE(HSUSBD_CEPCTL_NAKCLR); +#ifdef __RTX + + if (USBD_RTX_EPTask[0]) { + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[0]); + } + +#else + + if (USBD_P_EP[0]) { + USBD_P_EP[0](USBD_EVT_IN); + } + +#endif + HSUSBD_CLR_CEP_INT_FLAG(HSUSBD_CEPINTSTS_TXPKIF_Msk); + return; + } + + if (IrqSt & HSUSBD_CEPINTSTS_RXPKIF_Msk) { +#ifdef __RTX + + if (USBD_RTX_EPTask[0]) { + isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[0]); + } + +#else + + if (USBD_P_EP[0]) { + USBD_P_EP[0](USBD_EVT_OUT); + } + +#endif + HSUSBD_CLR_CEP_INT_FLAG(HSUSBD_CEPINTSTS_RXPKIF_Msk); + return; + } + + if (IrqSt & HSUSBD_CEPINTSTS_STSDONEIF_Msk) { + if (g_u8StatusIn == 0) { +#ifdef __RTX + + if (USBD_RTX_EPTask[0]) { + isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[0]); + } + +#else + + if (USBD_P_EP[0]) { + USBD_P_EP[0](USBD_EVT_OUT); + } + +#endif + } else { +#ifdef __RTX + + if (USBD_RTX_EPTask[0]) { + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[0]); + } + +#else + + if (USBD_P_EP[0]) { + USBD_P_EP[0](USBD_EVT_IN); + } + +#endif + } + + g_u8StatusIn = 0; + HSUSBD_CLR_CEP_INT_FLAG(HSUSBD_CEPINTSTS_STSDONEIF_Msk); + return; + } + } + + for (i = 0; i < HSUSBD_MAX_EP; i++) { + u32Ep = EPA + i; + u32Num = USBD_EP_TO_NUM(u32Ep); + + if (IrqStL & (0x1UL << (HSUSBD_GINTSTS_EPAIF_Pos + i))) { + IrqSt = HSUSBD->EP[u32Ep].EPINTSTS & HSUSBD->EP[u32Ep].EPINTEN; + + if (IrqSt & HSUSBD_EPINTSTS_TXPKIF_Msk) { +#ifdef __RTX + + if (USBD_RTX_EPTask[u32Num]) { + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[u32Num]); + } + +#else + + if (USBD_P_EP[u32Num]) { + USBD_P_EP[u32Num](USBD_EVT_IN); + } + +#endif + } + + if (IrqSt & (HSUSBD_EPINTSTS_RXPKIF_Msk | HSUSBD_EPINTSTS_SHORTRXIF_Msk | HSUSBD_EPINTEN_BUFFULLIEN_Msk)) { +#ifdef __RTX + + if (USBD_RTX_EPTask[u32Num]) { + isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[u32Num]); + } + +#else + + if (USBD_P_EP[u32Num]) { + USBD_P_EP[u32Num](USBD_EVT_OUT); + } + +#endif + } + + HSUSBD_CLR_EP_INT_FLAG(u32Ep, IrqSt); + } + } +} + +void USBD_Handler(void) +{ + USBD_Handler_Main(); + NVIC_EnableIRQ(USBD20_IRQn); +} diff --git a/port/nxp/lpc11u35/usbd_LPC11Uxx.c b/port/nxp/lpc11u35/usbd_LPC11Uxx.c new file mode 100644 index 00000000..c609675f --- /dev/null +++ b/port/nxp/lpc11u35/usbd_LPC11Uxx.c @@ -0,0 +1,825 @@ +/** + * @file usbd_LPC11Uxx.c + * @brief + * + * DAPLink Interface Firmware + * Copyright (c) 2009-2016, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rl_usb.h" +#include "LPC11Uxx.h" +#include "compiler.h" +#include "util.h" + +#define __NO_USB_LIB_C +#include "usb_config.c" + + +#define BUF_ACTIVE (1UL << 31) +#define EP_DISABLED (1UL << 30) +#define EP_STALL (1UL << 29) +#define TOOGLE_RESET (1UL << 28) +#define EP_TYPE (1UL << 26) + +#define N_BYTES(n) ((n & 0x3FF) << 16) +#define BUF_ADDR(addr) (((addr) >> 6) & 0xFFFF) + +#define EP_OUT_IDX(EPNum) (EPNum * 2 ) +#define EP_IN_IDX(EPNum) (EPNum * 2 + 1) + +#define EP_LIST_BASE 0x20004000 +#define EP_BUF_BASE (U32)(EP_LIST_BASE + 0x100) + +typedef struct BUF_INFO { + U32 buf_len; + U32 buf_ptr; +} EP_BUF_INFO; + +EP_BUF_INFO EPBufInfo[(USBD_EP_NUM + 1) * 2]; +volatile U32 EPList[(USBD_EP_NUM + 1) * 2] __at(EP_LIST_BASE); + +static U32 addr = 3 * 64 + EP_BUF_BASE; +static U32 ctrl_out_next = 0; + +/* + * Get EP CmdStat pointer + * Parameters: EPNum: endpoint number + * + */ + +U32 *GetEpCmdStatPtr(U32 EPNum) +{ + U32 ptr = 0; + + if (EPNum & 0x80) { + EPNum &= ~0x80; + ptr = 8; + } + + ptr += EP_LIST_BASE + EPNum * 16; + return ((U32 *)ptr); +} + + +/* + * Usb interrupt enable/disable + * Parameters: ena: enable/disable + * 0: disable interrupt + * 1: enable interrupt + */ + +#ifdef __RTX +void __svc(1) USBD_Intr(int ena); +void __SVC_1(int ena) +{ + if (ena) { + NVIC_EnableIRQ(USB_IRQn); /* Enable USB interrupt */ + } else { + NVIC_DisableIRQ(USB_IRQn); /* Disable USB interrupt */ + } +} +#endif + + + +/* + * USB Device Initialize Function + * Called by the User to initialize USB Device + * Return Value: None + */ + +void USBD_Init(void) +{ + LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 6); + LPC_SYSCON->SYSAHBCLKCTRL |= (1UL << 14) | + (1UL << 27); + LPC_USB->DEVCMDSTAT |= (1UL << 9); /* PLL ON */ + LPC_IOCON->PIO0_3 &= ~(0x1F); + LPC_IOCON->PIO0_3 |= (1UL << 0); /* Secondary function VBUS */ + LPC_IOCON->PIO0_6 &= ~7; + LPC_IOCON->PIO0_6 |= (1UL << 0); /* Secondary function USB CON */ + LPC_SYSCON->PDRUNCFG &= ~((1UL << 8) | /* USB PLL powered */ + (1UL << 10)); /* USB transceiver powered */ + LPC_USB->DATABUFSTART = EP_BUF_BASE & 0xFFC00000; + LPC_USB->EPLISTSTART = EP_LIST_BASE; + NVIC_EnableIRQ(USB_IRQn); + USBD_Reset(); +} + + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ + +void USBD_Connect(BOOL con) +{ + if (con) { + LPC_USB->DEVCMDSTAT |= (1UL << 16); /* Set device connect status */ + } else { + LPC_USB->DEVCMDSTAT &= ~(1UL << 16); /* Clear device connect status */ + } + + return; +} + + +/* + * USB Device Reset Function + * Called automatically on USB Device Reset + * Return Value: None + */ + +void USBD_Reset(void) +{ + U32 i; + U32 *ptr; + addr = 3 * 64 + EP_BUF_BASE; + + for (i = 2; i < (5 * 4); i++) { + EPList[i] = (1UL << 30); /* EPs disabled */ + } + + ctrl_out_next = 0; + EPBufInfo[0].buf_len = USBD_MAX_PACKET0; + EPBufInfo[0].buf_ptr = EP_BUF_BASE; + EPBufInfo[1].buf_len = USBD_MAX_PACKET0; + EPBufInfo[1].buf_ptr = EP_BUF_BASE + 2 * 64; + ptr = GetEpCmdStatPtr(0); + *ptr = N_BYTES(EPBufInfo[0].buf_len) | /* EP0 OUT */ + BUF_ADDR(EPBufInfo[0].buf_ptr) | + BUF_ACTIVE; + ptr++; + *ptr = BUF_ADDR(EPBufInfo[0].buf_ptr + 64);/* SETUP */ + LPC_USB->DEVCMDSTAT |= (1UL << 7); /*USB device enable */ + LPC_USB->INTSTAT = 0x2FC; /* clear EP interrupt flags */ + LPC_USB->INTEN = ((1UL << 30) | /* SOF intr enable */ + (1UL << 0) | /* EP0 OUT intr enable */ + (1UL << 1) | /* EP0 IN intr enable */ + (1UL << 31)); /* stat change int en */ +} + + +/* + * USB Device Suspend Function + * Called automatically on USB Device Suspend + * Return Value: None + */ + +void USBD_Suspend(void) +{ + /* Performed by Hardware */ +} + + +/* + * USB Device Resume Function + * Called automatically on USB Device Resume + * Return Value: None + */ + +void USBD_Resume(void) +{ + /* Performed by Hardware */ +} + + +/* + * USB Device Remote Wakeup Function + * Called automatically on USB Device Remote Wakeup + * Return Value: None + */ + +void USBD_WakeUp(void) +{ + LPC_SYSCON->USBCLKCTRL = 1; + LPC_USB->DEVCMDSTAT &= ~(1UL << 17); /*clear device suspend status */ + + while (LPC_USB->DEVCMDSTAT & (1UL << 17)); + + LPC_SYSCON->USBCLKCTRL = 0; +} + + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ + +void USBD_WakeUpCfg(BOOL cfg) +{ + if (cfg == __TRUE) { + LPC_USB->DEVCMDSTAT &= ~(1UL << 9); /*PPL_ON=0, in suspend clk is stoped */ + } else { + LPC_USB->DEVCMDSTAT |= (1UL << 9); /*PPL_ON=1, in suspend clk isnt stoped */ + LPC_SYSCON->USBCLKCTRL = 0; + } +} + + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * Return Value: None + */ + +void USBD_SetAddress(U32 adr, U32 setup) +{ + if (!setup) { + LPC_USB->DEVCMDSTAT &= ~0x7F; + LPC_USB->DEVCMDSTAT |= adr | (1UL << 7); + } +} + + + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ + +void USBD_Configure(BOOL cfg) +{ + addr = 3 * 64 + EP_BUF_BASE; +} + + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ + +void USBD_ConfigEP(USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + U32 num, val, type; + U32 *ptr; + num = pEPD->bEndpointAddress; + val = pEPD->wMaxPacketSize; + type = pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK; + + /* IN EPs */ + if (num & 0x80) { + num &= ~0x80; + EPBufInfo[EP_IN_IDX(num)].buf_len = val; + EPBufInfo[EP_IN_IDX(num)].buf_ptr = addr; + addr += ((val + 63) >> 6) * 64; /* calc new free buffer address */ + ptr = GetEpCmdStatPtr(num | 0x80); + *ptr = EP_DISABLED; + + if (type == USB_ENDPOINT_TYPE_ISOCHRONOUS) { + *ptr |= EP_TYPE; + } + } + + /* OUT EPs */ + else { + EPBufInfo[EP_OUT_IDX(num)].buf_len = val; + EPBufInfo[EP_OUT_IDX(num)].buf_ptr = addr; + ptr = GetEpCmdStatPtr(num); + *ptr = N_BYTES(EPBufInfo[EP_OUT_IDX(num)].buf_len) | + BUF_ADDR(EPBufInfo[EP_OUT_IDX(num)].buf_ptr) | + EP_DISABLED; + + if (type == USB_ENDPOINT_TYPE_ISOCHRONOUS) { + *ptr |= EP_TYPE; + } + + addr += ((val + 63) >> 6) * 64; /* calc new free buffer address */ + } +} + + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ + +void USBD_DirCtrlEP(U32 dir) +{ + /* Not needed */ +} + + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_EnableEP(U32 EPNum) +{ + U32 *ptr;; + ptr = GetEpCmdStatPtr(EPNum); + + /* IN EP */ + if (EPNum & 0x80) { + EPNum &= ~0x80; + *ptr &= ~EP_DISABLED; + LPC_USB->INTSTAT = (1 << EP_IN_IDX(EPNum)); + LPC_USB->INTEN |= (1 << EP_IN_IDX(EPNum)); + } + + /* OUT EP */ + else { + *ptr &= ~EP_DISABLED; + *ptr |= BUF_ACTIVE; + LPC_USB->INTSTAT = (1 << EP_OUT_IDX(EPNum)); + LPC_USB->INTEN |= (1 << EP_OUT_IDX(EPNum)); + } +} + + +/* + * Disable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_DisableEP(U32 EPNum) +{ + U32 *ptr; + ptr = GetEpCmdStatPtr(EPNum); + *ptr = EP_DISABLED; + + if (EPNum & 0x80) { + EPNum &= ~0x80; + LPC_USB->INTEN &= ~(1 << EP_IN_IDX(EPNum)); + + } else { + LPC_USB->INTEN &= ~(1 << EP_OUT_IDX(EPNum)); + } +} + + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ResetEP(U32 EPNum) +{ + U32 *ptr; + ptr = GetEpCmdStatPtr(EPNum); + *ptr |= TOOGLE_RESET; +} + + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_SetStallEP(U32 EPNum) +{ + U32 *ptr; + ptr = GetEpCmdStatPtr(EPNum); + + if (EPNum & 0x7F) { + if (*ptr & BUF_ACTIVE) { + *ptr &= ~(BUF_ACTIVE); + } + + } else { + if (EPNum & 0x80) { + EPNum &= ~0x80; + LPC_USB->EPSKIP |= (1 << EP_IN_IDX(EPNum)); + + while (LPC_USB->EPSKIP & (1 << EP_IN_IDX(EPNum))); + + } else { + LPC_USB->EPSKIP |= (1 << EP_OUT_IDX(EPNum)); + + while (LPC_USB->EPSKIP & (1 << EP_OUT_IDX(EPNum))); + } + } + + if ((EPNum & 0x7F) == 0) { + /* Endpoint is stalled so control out won't be next */ + ctrl_out_next = 0; + } + + *ptr |= EP_STALL; +} + + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClrStallEP(U32 EPNum) +{ + U32 *ptr; + ptr = GetEpCmdStatPtr(EPNum); + + if (EPNum & 0x80) { + *ptr &= ~EP_STALL; + + } else { + *ptr &= ~EP_STALL; + *ptr |= BUF_ACTIVE; + } + + USBD_ResetEP(EPNum); +} + + +/* + * Clear USB Device Endpoint Buffer + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClearEPBuf(U32 EPNum) +{ + U32 cnt, i; + U8 *dataptr; + + if (EPNum & 0x80) { + EPNum &= ~0x80; + dataptr = (U8 *)EPBufInfo[EP_IN_IDX(EPNum)].buf_ptr; + cnt = EPBufInfo[EP_IN_IDX(EPNum)].buf_len; + + for (i = 0; i < cnt; i++) { + dataptr[i] = 0; + } + + } else { + dataptr = (U8 *)EPBufInfo[EP_OUT_IDX(EPNum)].buf_ptr; + cnt = EPBufInfo[EP_OUT_IDX(EPNum)].buf_len; + + for (i = 0; i < cnt; i++) { + dataptr[i] = 0; + } + } +} + + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ + +U32 USBD_ReadEP(U32 EPNum, U8 *pData, U32 size) +{ + U32 cnt, i, xfer_size; + volatile U32 *ptr; + U8 *dataptr; + ptr = GetEpCmdStatPtr(EPNum); + int timeout = 256; + + /* Setup packet */ + if ((EPNum == 0) && !ctrl_out_next && (LPC_USB->DEVCMDSTAT & (1UL << 8))) { + cnt = USBD_MAX_PACKET0; + + if (size < cnt) { + util_assert(0); + cnt = size; + } + + dataptr = (U8 *)(EPBufInfo[EP_OUT_IDX(EPNum)].buf_ptr + 64); + + for (i = 0; i < cnt; i++) { + pData[i] = dataptr[i]; + } + + xfer_size = (pData[7] << 8) | (pData[6] << 0); + if ((xfer_size > 0) && (pData[0] & (1 << 7))) { + /* This control transfer has a data IN stage */ + /* and ends with a zero length data OUT transfer. */ + /* Ensure the data OUT token is not skipped even if */ + /* a SETUP token arrives before USBD_ReadEP has */ + /* been called. */ + ctrl_out_next = 1; + } + + LPC_USB->EPSKIP |= (1 << EP_IN_IDX(EPNum)); + + while (LPC_USB->EPSKIP & (1 << EP_IN_IDX(EPNum))); + + if (*(ptr + 2) & EP_STALL) { + *(ptr + 2) &= ~(EP_STALL); + } + + if (*ptr & EP_STALL) { + *ptr &= ~(EP_STALL); + } + + LPC_USB->DEVCMDSTAT |= (1UL << 8); + } + + /* OUT packet */ + else { + ptr = GetEpCmdStatPtr(EPNum); + cnt = EPBufInfo[EP_OUT_IDX(EPNum)].buf_len - ((*ptr >> 16) & 0x3FF); + dataptr = (U8 *)EPBufInfo[EP_OUT_IDX(EPNum)].buf_ptr; + + while ((timeout-- > 0) && (*ptr & BUF_ACTIVE)); //spin on the hardware until it's done + util_assert(!(*ptr & BUF_ACTIVE)); //check for timeout + + if (size < cnt) { + util_assert(0); + cnt = size; + } + + cnt = cnt < size ? cnt : size; + + for (i = 0; i < cnt; i++) { + pData[i] = dataptr[i]; + } + + *ptr = N_BYTES(EPBufInfo[EP_OUT_IDX(EPNum)].buf_len) | + BUF_ADDR(EPBufInfo[EP_OUT_IDX(EPNum)].buf_ptr) | + BUF_ACTIVE; + + if (EPNum == 0) { + /* If ctrl_out_next is set then this should be a zero length */ + /* data OUT packet. */ + util_assert(!ctrl_out_next || (cnt == 0)); + ctrl_out_next = 0; + if (LPC_USB->DEVCMDSTAT & (1UL << 8)) { + // A setup packet is still pending so trigger another interrupt + LPC_USB->INTSETSTAT |= (1 << 0); + } + } + } + + return (cnt); +} + + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ + +U32 USBD_WriteEP(U32 EPNum, U8 *pData, U32 cnt) +{ + U32 i; + volatile U32 *ptr; + U32 *dataptr; + ptr = GetEpCmdStatPtr(EPNum); + EPNum &= ~0x80; + + while (*ptr & BUF_ACTIVE); + + *ptr &= ~(0x3FFFFFF); + *ptr |= BUF_ADDR(EPBufInfo[EP_IN_IDX(EPNum)].buf_ptr) | + N_BYTES(cnt); + dataptr = (U32 *)EPBufInfo[EP_IN_IDX(EPNum)].buf_ptr; + + for (i = 0; i < (cnt + 3) / 4; i++) { + dataptr[i] = * ((__packed U32 *)pData); + pData += 4; + } + + if (EPNum && (*ptr & EP_STALL)) { + return (0); + } + + *ptr |= BUF_ACTIVE; + return (cnt); +} + + +/* + * Get USB Device Last Frame Number + * Parameters: None + * Return Value: Frame Number + */ + +U32 USBD_GetFrame(void) +{ + return (LPC_USB->INFO & 0x7FF); +} + + +/* + * USB Device Interrupt Service Routine + */ + +void USB_IRQHandler(void) +{ + NVIC_DisableIRQ(USB_IRQn); + USBD_SignalHandler(); +} + +void USBD_Handler(void) +{ + U32 sts, val, num, i; + sts = LPC_USB->INTSTAT; + LPC_USB->INTSTAT = sts; + + /* Device Status Interrupt (Reset, Connect change, Suspend/Resume) */ + if (sts & (1UL << 31)) { + val = LPC_USB->DEVCMDSTAT; + + /* reset interrupt */ + if (val & (1UL << 26)) { + LPC_USB->DEVCMDSTAT |= (1UL << 26); + USBD_Reset(); + usbd_reset_core(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESET, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Reset_Event) { + USBD_P_Reset_Event(); + } + +#endif + } + + /* connect interrupt */ + if (val & (1UL << 24)) { + LPC_USB->DEVCMDSTAT |= (1UL << 24); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + if (val & (1UL << 16)) { + isr_evt_set(USBD_EVT_POWER_ON, USBD_RTX_DevTask); + } else { + isr_evt_set(USBD_EVT_POWER_OFF, USBD_RTX_DevTask); + } + } + +#else + + if (USBD_P_Power_Event) { + USBD_P_Power_Event((val >> 16) & 1); + } + +#endif + } + + /* suspend/resume interrupt */ + if (val & (1 << 25)) { + LPC_USB->DEVCMDSTAT |= (1UL << 25); + + /* suspend interrupt */ + if (val & (1UL << 17)) { + USBD_Suspend(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SUSPEND, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Suspend_Event) { + USBD_P_Suspend_Event(); + } + +#endif + } + + /* resume interrupt */ + else { +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESUME, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Resume_Event) { + USBD_P_Resume_Event(); + } + +#endif + } + } + } + + /* Start of Frame */ + if (sts & (1UL << 30)) { +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SOF, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + +#endif + } + + /* EndPoint Interrupt */ + if (sts & 0x3FF) { + const uint32_t endpoint_count = ((USBD_EP_NUM + 1) * 2); + + for (i = 0; i < endpoint_count; i++) { + // Iterate through endpoints in the reverse order so IN endpoints + // get processed before OUT endpoints if they are both pending. + num = endpoint_count - i - 1; + + if (sts & (1UL << num)) { + /* Setup */ + if ((num == 0) && !ctrl_out_next && (LPC_USB->DEVCMDSTAT & (1UL << 8))) { +#ifdef __RTX + + if (USBD_RTX_EPTask[num / 2]) { + isr_evt_set(USBD_EVT_SETUP, USBD_RTX_EPTask[num / 2]); + } + +#else + + if (USBD_P_EP[num / 2]) { + USBD_P_EP[num / 2](USBD_EVT_SETUP); + } + +#endif + } + + /* OUT */ + else if ((num % 2) == 0) { +#ifdef __RTX + + if (USBD_RTX_EPTask[num / 2]) { + isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[num / 2]); + } + +#else + + if (USBD_P_EP[num / 2]) { + USBD_P_EP[num / 2](USBD_EVT_OUT); + } + +#endif + } + + /* IN */ + else { +#ifdef __RTX + + if (USBD_RTX_EPTask[num / 2]) { + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[num / 2]); + } + +#else + + if (USBD_P_EP[num / 2]) { + USBD_P_EP[num / 2](USBD_EVT_IN); + } + +#endif + } + } + } + } + + NVIC_EnableIRQ(USB_IRQn); +} diff --git a/port/nxp/lpc4322/usbd_LPC43xx_USB0.c b/port/nxp/lpc4322/usbd_LPC43xx_USB0.c new file mode 100644 index 00000000..b9d09f5a --- /dev/null +++ b/port/nxp/lpc4322/usbd_LPC43xx_USB0.c @@ -0,0 +1,901 @@ +/** + * @file usbd_LPC43xx_USBD0.c + * @brief + * + * DAPLink Interface Firmware + * Copyright (c) 2009-2016, ARM Limited, All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rl_usb.h" +#include "usb.h" +#include "LPC43xx.h" +#include "compiler.h" + +#define __NO_USB_LIB_C +#include "usb_config.c" + +/* Endpoint queue head */ +typedef struct __EPQH { + uint32_t cap; + uint32_t curr_dTD; + uint32_t next_dTD; + uint32_t dTD_token; + uint32_t buf[5]; + uint32_t reserved; + uint32_t setup[2]; + uint32_t reserved1[4]; +} EPQH; + +/* Endpoint transfer descriptor */ +typedef struct __dTD { + uint32_t next_dTD; + uint32_t dTD_token; + uint32_t buf[5]; + uint32_t reserved; +} dTD; + +/* Endpoint */ +typedef struct __EP { + uint8_t *buf; + uint32_t maxPacket; +} EP; + +EPQH __align(2048) EPQHx[(USBD_EP_NUM + 1) * 2]; +dTD __align(32) dTDx[(USBD_EP_NUM + 1) * 2]; + +EP Ep[(USBD_EP_NUM + 1) * 2]; +uint32_t BufUsed; +uint32_t IsoEp; +uint32_t cmpl_pnd; + +#define LPC_USBx LPC_USB0 +#define ENDPTCTRL(EPNum) *(volatile uint32_t *)((uint32_t)(&LPC_USBx->ENDPTCTRL0) + 4 * EPNum) +#define EP_OUT_IDX(EPNum) (EPNum * 2 ) +#define EP_IN_IDX(EPNum) (EPNum * 2 + 1) +#define HS(en) (USBD_HS_ENABLE * en) + +/* reserve RAM for endpoint buffers */ +#if USBD_VENDOR_ENABLE +/* custom class: user defined buffer size */ +#define EP_BUF_POOL_SIZE 0x1000 +uint8_t __align(4096) EPBufPool[EP_BUF_POOL_SIZE] +#else +/* supported classes are used */ +uint8_t __align(4096) EPBufPool[ + USBD_MAX_PACKET0 * 2 + + USBD_HID_ENABLE * (HS(USBD_HID_HS_ENABLE) ? USBD_HID_HS_WMAXPACKETSIZE : USBD_HID_WMAXPACKETSIZE) * 2 + + USBD_MSC_ENABLE * (HS(USBD_MSC_HS_ENABLE) ? USBD_MSC_HS_WMAXPACKETSIZE : USBD_MSC_WMAXPACKETSIZE) * 2 + + USBD_ADC_ENABLE * (HS(USBD_ADC_HS_ENABLE) ? USBD_ADC_HS_WMAXPACKETSIZE : USBD_ADC_WMAXPACKETSIZE) + + USBD_CDC_ACM_ENABLE * ((HS(USBD_CDC_ACM_HS_ENABLE) ? USBD_CDC_ACM_HS_WMAXPACKETSIZE : USBD_CDC_ACM_WMAXPACKETSIZE) + + (HS(USBD_CDC_ACM_HS_ENABLE) ? USBD_CDC_ACM_HS_WMAXPACKETSIZE1 : USBD_CDC_ACM_WMAXPACKETSIZE1) * 2) + + USBD_BULK_ENABLE * (HS(USBD_BULK_HS_ENABLE) ? USBD_BULK_HS_WMAXPACKETSIZE : USBD_BULK_WMAXPACKETSIZE) * 2 +]; +#endif + +void USBD_PrimeEp(uint32_t EPNum, uint32_t cnt); + +/* + * Usb interrupt enable/disable + * Parameters: ena: enable/disable + * 0: disable interrupt + * 1: enable interrupt + */ + +#ifdef __RTX +void __svc(1) USBD_Intr(int ena); +void __SVC_1(int ena) +{ +#else +void USBD_Intr(int ena) +{ +#endif + + if (ena) { + NVIC_EnableIRQ(USB0_IRQn); /* Enable USB interrupt */ + } else { + NVIC_DisableIRQ(USB0_IRQn); /* Disable USB interrupt */ + } +} + + +/* + * USB Device Initialize Function + * Called by the User to initialize USB Device + * Return Value: None + */ + +void USBD_Init(void) +{ + USBD_Intr(0); + /* BASE_USB0_CLK */ + LPC_CGU->BASE_USB0_CLK = (0x01 << 11) | /* Autoblock En */ + (0x07 << 24) ; /* Clock source: PLL0 */ + LPC_CCU1->CLK_M4_USB0_CFG |= 1; + + while (!(LPC_CCU1->CLK_M4_USB0_STAT & 1)); + + LPC_SCU->SFSP6_3 = 1; /* pwr en */ + LPC_SCU->SFSP6_6 = 3; /* pwr fault */ + LPC_SCU->SFSP8_1 = 1; /* port indicator LED control out 1 */ + LPC_SCU->SFSP8_2 = 1; /* port indicator LED control out 0 */ + LPC_USBx->USBCMD_D |= (1UL << 1); /* usb reset */ + + while (LPC_USBx->USBCMD_D & (1UL << 1)); + + LPC_CREG->CREG0 &= ~(1 << 5); + LPC_USBx->USBMODE_D = 2 | (1UL << 3);/* device mode */ +#if USBD_HS_ENABLE + LPC_USBx->PORTSC1_D &= ~(1UL << 24); +#else + LPC_USBx->PORTSC1_D |= (1UL << 24); +#endif + LPC_USBx->OTGSC = 1 | (1UL << 3); + Ep[EP_OUT_IDX(0)].maxPacket = USBD_MAX_PACKET0; + LPC_USBx->USBINTR_D = (1UL << 0) | /* usb int enable */ + (1UL << 2) | /* port change detect int enable */ + (1UL << 8) | /* suspend int enable */ + (1UL << 16) | /* nak int enable */ + (1UL << 6) | /* reset int enable */ +#ifdef __RTX + ((USBD_RTX_DevTask != 0) ? (1UL << 7) : 0) | /* SOF */ + ((USBD_RTX_DevTask != 0) ? (1UL << 1) : 0) ; /* Error */ +#else + ((USBD_P_SOF_Event != 0) ? (1UL << 7) : 0) | /* SOF */ + ((USBD_P_Error_Event != 0) ? (1UL << 1) : 0) ; /* Error */ +#endif + USBD_Reset(); + USBD_Intr(1); +} + + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ + +void USBD_Connect(uint32_t con) +{ + if (con) { + LPC_USBx->USBCMD_D |= 1; /* run */ + } else { + LPC_USBx->USBCMD_D &= ~1; /* stop */ + } +} + + +/* + * USB Device Reset Function + * Called automatically on USB Device Reset + * Return Value: None + */ + +void USBD_Reset(void) +{ + uint32_t i; + uint8_t *ptr; + cmpl_pnd = 0; + + for (i = 1; i < USBD_EP_NUM + 1; i++) { + ENDPTCTRL(i) &= ~((1UL << 7) | (1UL << 23)); + } + + /* clear interrupts */ + LPC_USBx->ENDPTNAK = 0xFFFFFFFF; + LPC_USBx->ENDPTNAKEN = 0; + LPC_USBx->USBSTS_D = 0xFFFFFFFF; + LPC_USBx->ENDPTSETUPSTAT = LPC_USBx->ENDPTSETUPSTAT; + LPC_USBx->ENDPTCOMPLETE = LPC_USBx->ENDPTCOMPLETE; + + while (LPC_USBx->ENDPTPRIME); + + LPC_USBx->ENDPTFLUSH = 0xFFFFFFFF; + + while (LPC_USBx->ENDPTFLUSH); + + LPC_USBx->USBCMD_D &= ~0x00FF0000; /* immediate intrrupt treshold */ + /* clear endpoint queue heads */ + ptr = (uint8_t *)EPQHx; + + for (i = 0; i < sizeof(EPQHx); i++) { + ptr[i] = 0; + } + + /* clear endpoint transfer descriptors */ + ptr = (uint8_t *)dTDx; + + for (i = 0; i < sizeof(dTDx); i++) { + ptr[i] = 0; + } + + Ep[EP_OUT_IDX(0)].maxPacket = USBD_MAX_PACKET0; + Ep[EP_OUT_IDX(0)].buf = EPBufPool; + BufUsed = USBD_MAX_PACKET0; + Ep[EP_IN_IDX(0)].maxPacket = USBD_MAX_PACKET0; + Ep[EP_IN_IDX(0)].buf = &(EPBufPool[BufUsed]); + BufUsed += USBD_MAX_PACKET0; + dTDx[EP_OUT_IDX(0)].next_dTD = 1; + dTDx[EP_IN_IDX(0)].next_dTD = 1; + dTDx[EP_OUT_IDX(0)].dTD_token = (USBD_MAX_PACKET0 << 16) | /* total bytes */ + (1UL << 15); /* int on compl */ + dTDx[EP_IN_IDX(0)].dTD_token = (USBD_MAX_PACKET0 << 16) | /* total bytes */ + (1UL << 15); /* int on compl */ + EPQHx[EP_OUT_IDX(0)].next_dTD = (uint32_t) &dTDx[EP_OUT_IDX(0)]; + EPQHx[EP_IN_IDX(0)].next_dTD = (uint32_t) &dTDx[EP_IN_IDX(0)]; + EPQHx[EP_OUT_IDX(0)].cap = ((USBD_MAX_PACKET0 & 0x0EFF) << 16) | + (1UL << 29) | + (1UL << 15); /* int on setup */ + EPQHx[EP_IN_IDX(0)].cap = (USBD_MAX_PACKET0 << 16) | + (1UL << 29) | + (1UL << 15); /* int on setup */ + LPC_USBx->ENDPOINTLISTADDR = (uint32_t)EPQHx; + LPC_USBx->USBMODE_D |= (1UL << 3); /* Setup lockouts off */ + LPC_USBx->ENDPTCTRL0 = 0x00C000C0; + USBD_PrimeEp(0, Ep[EP_OUT_IDX(0)].maxPacket); +} + + +/* + * USB Device Suspend Function + * Called automatically on USB Device Suspend + * Return Value: None + */ + +void USBD_Suspend(void) +{ + /* Performed by Hardware */ +} + + +/* + * USB Device Resume Function + * Called automatically on USB Device Resume + * Return Value: None + */ + +void USBD_Resume(void) +{ + /* Performed by Hardware */ +} + + +/* + * USB Device Remote Wakeup Function + * Called automatically on USB Device Remote Wakeup + * Return Value: None + */ + +void USBD_WakeUp(void) +{ + LPC_USBx->PORTSC1_D |= (1UL << 6); +} + + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ + +void USBD_WakeUpCfg(uint32_t cfg) +{ + /* Not needed */ +} + + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * Return Value: None + */ + +void USBD_SetAddress(uint32_t adr, uint32_t setup) +{ + if (setup == 0) { + LPC_USBx->DEVICEADDR = (adr << 25); + LPC_USBx->DEVICEADDR |= (1UL << 24); + } +} + + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ + +void USBD_Configure(uint32_t cfg) +{ + uint32_t i; + + if (!cfg) { + for (i = 2; i < (2 * (USBD_EP_NUM + 1)); i++) { + Ep[i].buf = 0; + Ep[i].maxPacket = 0; + } + + BufUsed = 2 * USBD_MAX_PACKET0; + } +} + + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ + +void USBD_ConfigEP(USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + uint32_t num, val, type, idx; + + if ((pEPD->bEndpointAddress & USB_ENDPOINT_DIRECTION_MASK)) { + val = 16; + num = pEPD->bEndpointAddress & ~0x80; + idx = EP_IN_IDX(num); + + } else { + val = 0; + num = pEPD->bEndpointAddress; + idx = EP_OUT_IDX(num); + } + + type = pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK; + + if (!(Ep[idx].buf)) { + Ep[idx].buf = &(EPBufPool[BufUsed]); + Ep[idx].maxPacket = pEPD->wMaxPacketSize; + BufUsed += pEPD->wMaxPacketSize; + + /* Isochronous endpoint */ + if (type == USB_ENDPOINT_TYPE_ISOCHRONOUS) { + IsoEp |= (1UL << (num + val)); + } + } + + dTDx[idx].buf[0] = (uint32_t)(Ep[idx].buf); + dTDx[idx].next_dTD = 1; + EPQHx[idx].cap = (Ep[idx].maxPacket << 16) | + (1UL << 29); + ENDPTCTRL(num) &= ~(0xFFFF << val); + ENDPTCTRL(num) |= ((type << 2) << val) | + ((1UL << 6) << val); /* Data toogle reset */ +} + + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ + +void USBD_DirCtrlEP(uint32_t dir) +{ + /* Not needed */ +} + + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_EnableEP(uint32_t EPNum) +{ + if (EPNum & 0x80) { + EPNum &= 0x7F; + ENDPTCTRL(EPNum) |= (1UL << 23); /* EP enabled */ + } else { + ENDPTCTRL(EPNum) |= (1UL << 7); /* EP enabled */ + } +} + + +/* + * Disable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_DisableEP(uint32_t EPNum) +{ + if (EPNum & 0x80) { + EPNum &= 0x7F; + ENDPTCTRL(EPNum) &= ~(1UL << 23); /* EP disabled */ + } else { + ENDPTCTRL(EPNum) &= ~(1UL << 7); /* EP disabled */ + } +} + + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ResetEP(uint32_t EPNum) +{ + if (EPNum & 0x80) { + EPNum &= 0x7F; + EPQHx[EP_IN_IDX(EPNum)].dTD_token &= 0xC0; + LPC_USBx->ENDPTFLUSH = (1UL << (EPNum + 16)); /* flush endpoint */ + + while (LPC_USBx->ENDPTFLUSH & (1UL << (EPNum + 16))); + + ENDPTCTRL(EPNum) |= (1UL << 22); /* data toggle reset */ + + } else { + EPQHx[EP_OUT_IDX(EPNum)].dTD_token &= 0xC0; + LPC_USBx->ENDPTFLUSH = (1UL << EPNum); /* flush endpoint */ + + while (LPC_USBx->ENDPTFLUSH & (1UL << EPNum)); + + ENDPTCTRL(EPNum) |= (1UL << 6); /* data toggle reset */ + USBD_PrimeEp(EPNum, Ep[EP_OUT_IDX(EPNum)].maxPacket); + } +} + + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_SetStallEP(uint32_t EPNum) +{ + if (EPNum & 0x80) { + EPNum &= 0x7F; + ENDPTCTRL(EPNum) |= (1UL << 16); /* IN endpoint stall */ + } else { + ENDPTCTRL(EPNum) |= (1UL << 0); /* OUT endpoint stall */ + } +} + + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClrStallEP(uint32_t EPNum) +{ + if (EPNum & 0x80) { + EPNum &= 0x7F; + ENDPTCTRL(EPNum) &= ~(1UL << 16); /* clear stall */ + ENDPTCTRL(EPNum) |= (1UL << 22); /* data toggle reset */ + + while (ENDPTCTRL(EPNum) & (1UL << 16)); + + USBD_ResetEP(EPNum | 0x80); + + } else { + ENDPTCTRL(EPNum) &= ~(1UL << 0); /* clear stall */ + ENDPTCTRL(EPNum) |= (1UL << 6); /* data toggle reset */ + } +} + + +/* + * Clear USB Device Endpoint Buffer + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClearEPBuf(uint32_t EPNum) +{ +} + + +/* + * USB Device Prime endpoint function + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * cnt: Bytes to transfer/receive + * Return Value: None + */ + +void USBD_PrimeEp(uint32_t EPNum, uint32_t cnt) +{ + uint32_t idx, val; + + /* IN endpoint */ + if (EPNum & 0x80) { + EPNum &= 0x7F; + idx = EP_IN_IDX(EPNum); + val = (1UL << (EPNum + 16)); + } + + /* OUT endpoint */ + else { + val = (1UL << EPNum); + idx = EP_OUT_IDX(EPNum); + } + + dTDx[idx].buf[0] = (uint32_t)(Ep[idx].buf); + dTDx[idx].next_dTD = 1; + + if (IsoEp & val) { + if (Ep[idx].maxPacket <= cnt) { + dTDx[idx].dTD_token = (1 << 10); /* MultO = 1 */ + + } else if ((Ep[idx].maxPacket * 2) <= cnt) { + dTDx[idx].dTD_token = (2 << 10); /* MultO = 2 */ + + } else { + dTDx[idx].dTD_token = (3 << 10); /* MultO = 3 */ + } + + } else { + dTDx[idx].dTD_token = 0; + } + + dTDx[idx].dTD_token |= (cnt << 16) | /* bytes to transfer */ + (1UL << 15) | /* int on complete */ + 0x80; /* status - active */ + EPQHx[idx].next_dTD = (uint32_t)(&dTDx[idx]); + EPQHx[idx].dTD_token &= ~0xC0; + LPC_USBx->ENDPTPRIME = (val); + + while ((LPC_USBx->ENDPTPRIME & val)); +} + + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ + +uint32_t USBD_ReadEP(uint32_t EPNum, uint8_t *pData, U32 size) +{ + uint32_t cnt = 0; + uint32_t i; + + /* Setup packet */ + if ((LPC_USBx->ENDPTSETUPSTAT & 1) && (!EPNum)) { + LPC_USBx->ENDPTSETUPSTAT = 1; + + while (LPC_USBx->ENDPTSETUPSTAT & 1); + + do { + *((__packed uint32_t *) pData) = EPQHx[EP_OUT_IDX(0)].setup[0]; + *((__packed uint32_t *)(pData + 4)) = EPQHx[EP_OUT_IDX(0)].setup[1]; + cnt = 8; + LPC_USBx->USBCMD_D |= (1UL << 13); + } while (!(LPC_USBx->USBCMD_D & (1UL << 13))); + + LPC_USBx->USBCMD_D &= (~(1UL << 13)); + LPC_USBx->ENDPTFLUSH = (1UL << EPNum) | (1UL << (EPNum + 16)); + + while (LPC_USBx->ENDPTFLUSH & ((1UL << (EPNum + 16)) | (1UL << EPNum))); + + while (LPC_USBx->ENDPTSETUPSTAT & 1); + + USBD_PrimeEp(EPNum, Ep[EP_OUT_IDX(EPNum)].maxPacket); + } + + /* OUT Packet */ + else { + if (Ep[EP_OUT_IDX(EPNum)].buf) { + cnt = Ep[EP_OUT_IDX(EPNum)].maxPacket - + ((dTDx[EP_OUT_IDX(EPNum)].dTD_token >> 16) & 0x7FFF); + + cnt = cnt < size ? cnt : size; + + for (i = 0; i < cnt; i++) { + pData[i] = Ep[EP_OUT_IDX(EPNum)].buf[i]; + } + } + + LPC_USBx->ENDPTCOMPLETE = (1UL << EPNum); + cmpl_pnd &= ~(1UL << EPNum); + USBD_PrimeEp(EPNum, Ep[EP_OUT_IDX(EPNum)].maxPacket); + } + + return (cnt); +} + + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ + +uint32_t USBD_WriteEP(uint32_t EPNum, uint8_t *pData, uint32_t cnt) +{ + uint32_t i; + EPNum &= 0x7f; + + for (i = 0; i < cnt; i++) { + Ep[EP_IN_IDX(EPNum)].buf[i] = pData[i]; + } + + USBD_PrimeEp(EPNum | 0x80, cnt); + return (cnt); +} + + +/* + * Get USB Device Last Frame Number + * Parameters: None + * Return Value: Frame Number + */ + +uint32_t USBD_GetFrame(void) +{ + return ((LPC_USBx->FRINDEX_D >> 3) & 0x0FFF); +} + + +#ifdef __RTX +uint32_t LastError; /* Last Error */ + +/* + * Get USB Device Last Error Code + * Parameters: None + * Return Value: Error Code + */ + +uint32_t USBD_GetError(void) +{ + return (LastError); +} +#endif + + +/* + * USB Device Interrupt Service Routine + */ + +void USB0_IRQHandler(void) +{ + NVIC_DisableIRQ(USB0_IRQn); + USBD_SignalHandler(); +} + +void USBD_Handler(void) +{ + uint32_t sts, cmpl, num; + sts = LPC_USBx->USBSTS_D & LPC_USBx->USBINTR_D; + cmpl = LPC_USBx->ENDPTCOMPLETE; + LPC_USBx->USBSTS_D = sts; /* clear interupt flags */ + + /* reset interrupt */ + if (sts & (1UL << 6)) { + USBD_Reset(); + usbd_reset_core(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESET, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Reset_Event) { + USBD_P_Reset_Event(); + } + +#endif + } + + /* suspend interrupt */ + if (sts & (1UL << 8)) { + USBD_Suspend(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SUSPEND, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Suspend_Event) { + USBD_P_Suspend_Event(); + } + +#endif + } + + /* SOF interrupt */ + if (sts & (1UL << 7)) { + if (IsoEp) { + for (num = 0; num < USBD_EP_NUM + 1; num++) { + if (IsoEp & (1UL << num)) { + USBD_PrimeEp(num, Ep[EP_OUT_IDX(num)].maxPacket); + } + } + + } else { +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SOF, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + +#endif + } + } + + /* port change detect interrupt */ + if (sts & (1UL << 2)) { + if (((LPC_USBx->PORTSC1_D >> 26) & 0x03) == 2) { + USBD_HighSpeed = __TRUE; + } + + USBD_Resume(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESUME, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Resume_Event) { + USBD_P_Resume_Event(); + } + +#endif + } + + /* USB interrupt - completed transfer */ + if (sts & 1) { + /* Setup Packet */ + if (LPC_USBx->ENDPTSETUPSTAT) { +#ifdef __RTX + + if (USBD_RTX_EPTask[0]) { + isr_evt_set(USBD_EVT_SETUP, USBD_RTX_EPTask[0]); + } + +#else + + if (USBD_P_EP[0]) { + USBD_P_EP[0](USBD_EVT_SETUP); + } + +#endif + } + + /* IN Packet */ + if (cmpl & (0x3F << 16)) { + for (num = 0; num < USBD_EP_NUM + 1; num++) { + if (((cmpl >> 16) & 0x3F) & (1UL << num)) { + LPC_USBx->ENDPTCOMPLETE = (1UL << (num + 16)); /* Clear completed */ +#ifdef __RTX + + if (USBD_RTX_EPTask[num]) { + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[num]); + } + +#else + + if (USBD_P_EP[num]) { + USBD_P_EP[num](USBD_EVT_IN); + } + +#endif + } + } + } + + /* OUT Packet */ + if (cmpl & 0x3F) { + for (num = 0; num < USBD_EP_NUM + 1; num++) { + if ((cmpl ^ cmpl_pnd) & cmpl & (1UL << num)) { + cmpl_pnd |= 1UL << num; +#ifdef __RTX + + if (USBD_RTX_EPTask[num]) { + isr_evt_set(USBD_EVT_OUT, USBD_RTX_EPTask[num]); + + } else if (IsoEp & (1UL << num)) { + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SOF, USBD_RTX_DevTask); + } + } + +#else + + if (USBD_P_EP[num]) { + USBD_P_EP[num](USBD_EVT_OUT); + + } else if (IsoEp & (1UL << num)) { + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + } + +#endif + } + } + } + } + + /* error interrupt */ + if (sts & (1UL << 1)) { + for (num = 0; num < USBD_EP_NUM + 1; num++) { + if (cmpl & (1UL << num)) { +#ifdef __RTX + + if (USBD_RTX_DevTask) { + LastError = dTDx[EP_OUT_IDX(num)].dTD_token & 0xE8; + isr_evt_set(USBD_EVT_ERROR, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Error_Event) { + USBD_P_Error_Event(dTDx[EP_OUT_IDX(num)].dTD_token & 0xE8); + } + +#endif + } + + if (cmpl & (1UL << (num + 16))) { +#ifdef __RTX + + if (USBD_RTX_DevTask) { + LastError = dTDx[EP_IN_IDX(num)].dTD_token & 0xE8; + isr_evt_set(USBD_EVT_ERROR, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Error_Event) { + USBD_P_Error_Event(dTDx[EP_IN_IDX(num)].dTD_token & 0xE8); + } + +#endif + } + } + } + + NVIC_EnableIRQ(USB0_IRQn); +} diff --git a/port/stm32/back/usb_dc_stm32.c b/port/stm32/back/usb_dc_stm32.c new file mode 100644 index 00000000..6677dbf7 --- /dev/null +++ b/port/stm32/back/usb_dc_stm32.c @@ -0,0 +1,1111 @@ +/* USB device controller driver for STM32 devices */ + +/* + * Copyright (c) 2017 Christer Weinigel. + * Copyright (c) 2017, I-SENSE group of ICCS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB device controller driver for STM32 devices + * + * This driver uses the STM32 Cube low level drivers to talk to the USB + * device controller on the STM32 family of devices using the + * STM32Cube HAL layer. + * + * There is a bit of an impedance mismatch between the Zephyr + * usb_device and the STM32 Cube HAL layer where higher levels make + * assumptions about the low level drivers that don't quite match how + * the low level drivers actually work. + * + * The usb_dc_ep_read function expects to get the data it wants + * immediately while the HAL_PCD_EP_Receive function only starts a + * read transaction and the data won't be available until call to + * HAL_PCD_DataOutStageCallback. To work around this I've + * had to add an extra packet buffer in the driver which wastes memory + * and also leads to an extra copy of all received data. It would be + * better if higher drivers could call start_read and get_read_count + * in this driver directly. + * + * To enable the driver together with the CDC_ACM high level driver, + * add the following to your board's defconfig: + * + * CONFIG_USB=y + * CONFIG_USB_DC_STM32=y + * CONFIG_USB_CDC_ACM=y + * CONFIG_USB_DEVICE_STACK=y + * + * To use the USB device as a console, also add: + * + * CONFIG_UART_CONSOLE_ON_DEV_NAME="CDC_ACM" + * CONFIG_USB_UART_CONSOLE=y + * CONFIG_UART_LINE_CTRL=y + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "stm32_hsem.h" + +#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(usb_dc_stm32); + +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) && DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) +#error "Only one interface should be enabled at a time, OTG FS or OTG HS" +#endif + +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) +#define DT_DRV_COMPAT st_stm32_otghs +#define USB_IRQ_NAME otghs +#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) +#define DT_DRV_COMPAT st_stm32_otgfs +#define USB_IRQ_NAME otgfs +#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb) +#define DT_DRV_COMPAT st_stm32_usb +#define USB_IRQ_NAME usb +#if DT_INST_NODE_HAS_PROP(0, enable_pin_remap) +#define USB_ENABLE_PIN_REMAP DT_INST_PROP(0, enable_pin_remap) +#endif +#endif + +#define USB_BASE_ADDRESS DT_INST_REG_ADDR(0) +#define USB_IRQ DT_INST_IRQ_BY_NAME(0, USB_IRQ_NAME, irq) +#define USB_IRQ_PRI DT_INST_IRQ_BY_NAME(0, USB_IRQ_NAME, priority) +#define USB_NUM_BIDIR_ENDPOINTS DT_INST_PROP(0, num_bidir_endpoints) +#define USB_RAM_SIZE DT_INST_PROP(0, ram_size) +#define USB_CLOCK_BITS DT_INST_CLOCKS_CELL(0, bits) +#define USB_CLOCK_BUS DT_INST_CLOCKS_CELL(0, bus) +#if DT_INST_NODE_HAS_PROP(0, maximum_speed) +#define USB_MAXIMUM_SPEED DT_INST_PROP(0, maximum_speed) +#endif +static const struct soc_gpio_pinctrl usb_pinctrl[] = + ST_STM32_DT_INST_PINCTRL(0, 0); + + +#define USB_OTG_HS_EMB_PHY (DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usbphyc) && \ + DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)) + +/* + * USB and USB_OTG_FS are defined in STM32Cube HAL and allows to distinguish + * between two kind of USB DC. STM32 F0, F3, L0 and G4 series support USB device + * controller. STM32 F4 and F7 series support USB_OTG_FS device controller. + * STM32 F1 and L4 series support either USB or USB_OTG_FS device controller. + * + * WARNING: Don't mix USB defined in STM32Cube HAL and CONFIG_USB from Zephyr + * Kconfig system. + */ +#ifdef USB + +#define EP0_MPS 64U +#define EP_MPS 64U + +/* + * USB BTABLE is stored in the PMA. The size of BTABLE is 4 bytes + * per endpoint. + * + */ +#define USB_BTABLE_SIZE (8 * USB_NUM_BIDIR_ENDPOINTS) + +#else /* USB_OTG_FS */ + +/* + * STM32L4 series USB LL API doesn't provide HIGH and HIGH_IN_FULL speed + * defines. + */ +#if defined(CONFIG_SOC_SERIES_STM32L4X) +#define USB_OTG_SPEED_HIGH 0U +#define USB_OTG_SPEED_HIGH_IN_FULL 1U +#endif /* CONFIG_SOC_SERIES_STM32L4X */ + +#define EP0_MPS USB_OTG_MAX_EP0_SIZE + +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) +#define EP_MPS USB_OTG_HS_MAX_PACKET_SIZE +#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) || DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb) +#define EP_MPS USB_OTG_FS_MAX_PACKET_SIZE +#endif + +/* We need one RX FIFO and n TX-IN FIFOs */ +#define FIFO_NUM (1 + USB_NUM_BIDIR_ENDPOINTS) + +/* 4-byte words FIFO */ +#define FIFO_WORDS (USB_RAM_SIZE / 4) + +/* Allocate FIFO memory evenly between the FIFOs */ +#define FIFO_EP_WORDS (FIFO_WORDS / FIFO_NUM) + +#endif /* USB */ + +/* Size of a USB SETUP packet */ +#define SETUP_SIZE 8 + +/* Helper macros to make it easier to work with endpoint numbers */ +#define EP0_IDX 0 +#define EP0_IN (EP0_IDX | USB_EP_DIR_IN) +#define EP0_OUT (EP0_IDX | USB_EP_DIR_OUT) + +/* Endpoint state */ +struct usb_dc_stm32_ep_state { + uint16_t ep_mps; /** Endpoint max packet size */ + uint16_t ep_pma_buf_len; /** Previously allocated buffer size */ + uint8_t ep_type; /** Endpoint type (STM32 HAL enum) */ + uint8_t ep_stalled; /** Endpoint stall flag */ + usb_dc_ep_callback cb; /** Endpoint callback function */ + uint32_t read_count; /** Number of bytes in read buffer */ + uint32_t read_offset; /** Current offset in read buffer */ + struct k_sem write_sem; /** Write boolean semaphore */ +}; + +/* Driver state */ +struct usb_dc_stm32_state { + PCD_HandleTypeDef pcd; /* Storage for the HAL_PCD api */ + usb_dc_status_callback status_cb; /* Status callback */ + struct usb_dc_stm32_ep_state out_ep_state[USB_NUM_BIDIR_ENDPOINTS]; + struct usb_dc_stm32_ep_state in_ep_state[USB_NUM_BIDIR_ENDPOINTS]; + uint8_t ep_buf[USB_NUM_BIDIR_ENDPOINTS][EP_MPS]; + +#ifdef USB + uint32_t pma_offset; +#endif /* USB */ +}; + +static struct usb_dc_stm32_state usb_dc_stm32_state; + +/* Internal functions */ + +static struct usb_dc_stm32_ep_state *usb_dc_stm32_get_ep_state(uint8_t ep) +{ + struct usb_dc_stm32_ep_state *ep_state_base; + + if (USB_EP_GET_IDX(ep) >= USB_NUM_BIDIR_ENDPOINTS) { + return NULL; + } + + if (USB_EP_DIR_IS_OUT(ep)) { + ep_state_base = usb_dc_stm32_state.out_ep_state; + } else { + ep_state_base = usb_dc_stm32_state.in_ep_state; + } + + return ep_state_base + USB_EP_GET_IDX(ep); +} + +static void usb_dc_stm32_isr(const void *arg) +{ + HAL_PCD_IRQHandler(&usb_dc_stm32_state.pcd); +} + +#ifdef CONFIG_USB_DEVICE_SOF +void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd) +{ + usb_dc_stm32_state.status_cb(USB_DC_SOF, NULL); +} +#endif + +static int usb_dc_stm32_clock_enable(void) +{ + const struct device *clk = device_get_binding(STM32_CLOCK_CONTROL_NAME); + struct stm32_pclken pclken = { + .bus = USB_CLOCK_BUS, + .enr = USB_CLOCK_BITS, + }; + + /* + * Some SoCs in STM32F0/L0/L4 series disable USB clock by + * default. We force USB clock source to MSI or PLL clock for this + * SoCs. However, if these parts have an HSI48 clock, use + * that instead. Example reference manual RM0360 for + * STM32F030x4/x6/x8/xC and STM32F070x6/xB. + */ +#if defined(RCC_HSI48_SUPPORT) || defined(CONFIG_SOC_SERIES_STM32WBX) + + /* + * In STM32L0 series, HSI48 requires VREFINT and its buffer + * with 48 MHz RC to be enabled. + * See ENREF_HSI48 in referenc maual RM0367 section10.2.3: + * "Reference control and status register (SYSCFG_CFGR3)" + */ +#ifdef CONFIG_SOC_SERIES_STM32L0X + if (LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_SYSCFG)) { + LL_SYSCFG_VREFINT_EnableHSI48(); + } else { + LOG_ERR("System Configuration Controller clock is " + "disabled. Unable to enable VREFINT which " + "is required by HSI48."); + } +#endif /* CONFIG_SOC_SERIES_STM32L0X */ + + z_stm32_hsem_lock(CFG_HW_CLK48_CONFIG_SEMID, HSEM_LOCK_DEFAULT_RETRY); + + LL_RCC_HSI48_Enable(); + while (!LL_RCC_HSI48_IsReady()) { + /* Wait for HSI48 to become ready */ + } + + LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_HSI48); + +#if !defined(CONFIG_SOC_SERIES_STM32WBX) + /* Specially for STM32WB, don't unlock the HSEM to prevent M0 core + * to disable HSI48 clock used for RNG. + */ + z_stm32_hsem_unlock(CFG_HW_CLK48_CONFIG_SEMID); +#endif /* CONFIG_SOC_SERIES_STM32WBX */ + +#elif defined(LL_RCC_USB_CLKSOURCE_NONE) + /* When MSI is configured in PLL mode with a 32.768 kHz clock source, + * the MSI frequency can be automatically trimmed by hardware to reach + * better than ±0.25% accuracy. In this mode the MSI can feed the USB + * device. For now, we only use MSI for USB if not already used as + * system clock source. + */ +#if defined(CONFIG_CLOCK_STM32_MSI_PLL_MODE) && !defined(CONFIG_CLOCK_STM32_SYSCLK_SRC_MSI) + LL_RCC_MSI_Enable(); + while (!LL_RCC_MSI_IsReady()) { + /* Wait for MSI to become ready */ + } + /* Force 48 MHz mode */ + LL_RCC_MSI_EnableRangeSelection(); + LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_11); + LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_MSI); +#else + if (LL_RCC_PLL_IsReady()) { + LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLL); + } else { + LOG_ERR("Unable to set USB clock source to PLL."); + } +#endif /* CONFIG_CLOCK_STM32_MSI_PLL_MODE && !CONFIG_CLOCK_STM32_SYSCLK_SRC_MSI */ + +#elif defined(RCC_CFGR_OTGFSPRE) + /* On STM32F105 and STM32F107 parts the USB OTGFSCLK is derived from + * PLL1, and must result in a 48 MHz clock... the options to achieve + * this are as below, controlled by the RCC_CFGR_OTGFSPRE bit. + * - PLLCLK * 2 / 2 i.e: PLLCLK == 48 MHz + * - PLLCLK * 2 / 3 i.e: PLLCLK == 72 MHz + * + * this requires that the system is running from PLLCLK + */ + if (LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL) { + switch (sys_clock_hw_cycles_per_sec()) { + case 48000000U: + LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLL_DIV_2); + break; + case 72000000U: + LL_RCC_SetUSBClockSource(LL_RCC_USB_CLKSOURCE_PLL_DIV_3); + break; + default: + LOG_ERR("Unable to set USB clock source (incompatible PLLCLK rate)"); + return -EIO; + } + } else { + LOG_ERR("Unable to set USB clock source (not using PLL1)"); + return -EIO; + } + +#endif /* RCC_HSI48_SUPPORT / LL_RCC_USB_CLKSOURCE_NONE / RCC_CFGR_OTGFSPRE */ + + if (clock_control_on(clk, (clock_control_subsys_t *)&pclken) != 0) { + LOG_ERR("Unable to enable USB clock"); + return -EIO; + } + +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usbphyc) + LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_OTGHSULPI); + LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_OTGPHYC); +#else + /* Disable ULPI interface (for external high-speed PHY) clock */ + LL_AHB1_GRP1_DisableClock(LL_AHB1_GRP1_PERIPH_OTGHSULPI); + LL_AHB1_GRP1_DisableClockLowPower(LL_AHB1_GRP1_PERIPH_OTGHSULPI); +#endif +#endif + + return 0; +} + +#if defined(USB_OTG_FS) || defined(USB_OTG_HS) +static uint32_t usb_dc_stm32_get_maximum_speed(void) +{ + /* + * If max-speed is not passed via DT, set it to USB controller's + * maximum hardware capability. + */ +#if USB_OTG_HS_EMB_PHY + uint32_t speed = USB_OTG_SPEED_HIGH; +#else + uint32_t speed = USB_OTG_SPEED_FULL; +#endif + +#ifdef USB_MAXIMUM_SPEED + + if (!strncmp(USB_MAXIMUM_SPEED, "high-speed", 10)) { + speed = USB_OTG_SPEED_HIGH; + } else if (!strncmp(USB_MAXIMUM_SPEED, "full-speed", 10)) { +#if USB_OTG_HS_EMB_PHY + speed = USB_OTG_SPEED_HIGH_IN_FULL; +#else + speed = USB_OTG_SPEED_FULL; +#endif + } else { + LOG_DBG("Unsupported maximum speed defined in device tree. " + "USB controller will default to its maximum HW " + "capability"); + } +#endif + + return speed; +} +#endif /* USB_OTG_FS || USB_OTG_HS */ + +static int usb_dc_stm32_init(void) +{ + HAL_StatusTypeDef status; + unsigned int i; + +#ifdef USB + usb_dc_stm32_state.pcd.Instance = USB; + usb_dc_stm32_state.pcd.Init.speed = PCD_SPEED_FULL; + usb_dc_stm32_state.pcd.Init.dev_endpoints = USB_NUM_BIDIR_ENDPOINTS; + usb_dc_stm32_state.pcd.Init.phy_itface = PCD_PHY_EMBEDDED; + usb_dc_stm32_state.pcd.Init.ep0_mps = PCD_EP0MPS_64; + usb_dc_stm32_state.pcd.Init.low_power_enable = 0; +#else /* USB_OTG_FS || USB_OTG_HS */ +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) + usb_dc_stm32_state.pcd.Instance = USB_OTG_HS; +#else + usb_dc_stm32_state.pcd.Instance = USB_OTG_FS; +#endif + usb_dc_stm32_state.pcd.Init.dev_endpoints = USB_NUM_BIDIR_ENDPOINTS; + usb_dc_stm32_state.pcd.Init.speed = usb_dc_stm32_get_maximum_speed(); +#if USB_OTG_HS_EMB_PHY + usb_dc_stm32_state.pcd.Init.phy_itface = USB_OTG_HS_EMBEDDED_PHY; +#else + usb_dc_stm32_state.pcd.Init.phy_itface = PCD_PHY_EMBEDDED; +#endif + usb_dc_stm32_state.pcd.Init.ep0_mps = USB_OTG_MAX_EP0_SIZE; + usb_dc_stm32_state.pcd.Init.vbus_sensing_enable = DISABLE; + +#ifndef CONFIG_SOC_SERIES_STM32F1X + usb_dc_stm32_state.pcd.Init.dma_enable = DISABLE; +#endif + +#endif /* USB */ + +#ifdef CONFIG_USB_DEVICE_SOF + usb_dc_stm32_state.pcd.Init.Sof_enable = 1; +#endif /* CONFIG_USB_DEVICE_SOF */ + + LOG_DBG("Pinctrl signals configuration"); + status = stm32_dt_pinctrl_configure(usb_pinctrl, + ARRAY_SIZE(usb_pinctrl), + (uint32_t)usb_dc_stm32_state.pcd.Instance); + if (status < 0) { + LOG_ERR("USB pinctrl setup failed (%d)", status); + return status; + } + + LOG_DBG("HAL_PCD_Init"); + status = HAL_PCD_Init(&usb_dc_stm32_state.pcd); + if (status != HAL_OK) { + LOG_ERR("PCD_Init failed, %d", (int)status); + return -EIO; + } + + LOG_DBG("HAL_PCD_Start"); + status = HAL_PCD_Start(&usb_dc_stm32_state.pcd); + if (status != HAL_OK) { + LOG_ERR("PCD_Start failed, %d", (int)status); + return -EIO; + } + + usb_dc_stm32_state.out_ep_state[EP0_IDX].ep_mps = EP0_MPS; + usb_dc_stm32_state.out_ep_state[EP0_IDX].ep_type = EP_TYPE_CTRL; + usb_dc_stm32_state.in_ep_state[EP0_IDX].ep_mps = EP0_MPS; + usb_dc_stm32_state.in_ep_state[EP0_IDX].ep_type = EP_TYPE_CTRL; + +#ifdef USB + /* Start PMA configuration for the endpoints after the BTABLE. */ + usb_dc_stm32_state.pma_offset = USB_BTABLE_SIZE; + + for (i = 0U; i < USB_NUM_BIDIR_ENDPOINTS; i++) { + k_sem_init(&usb_dc_stm32_state.in_ep_state[i].write_sem, 1, 1); + } +#else /* USB_OTG_FS */ + /* TODO: make this dynamic (depending usage) */ + HAL_PCDEx_SetRxFiFo(&usb_dc_stm32_state.pcd, FIFO_EP_WORDS); + for (i = 0U; i < USB_NUM_BIDIR_ENDPOINTS; i++) { + HAL_PCDEx_SetTxFiFo(&usb_dc_stm32_state.pcd, i, + FIFO_EP_WORDS); + k_sem_init(&usb_dc_stm32_state.in_ep_state[i].write_sem, 1, 1); + } +#endif /* USB */ + + IRQ_CONNECT(USB_IRQ, USB_IRQ_PRI, + usb_dc_stm32_isr, 0, 0); + irq_enable(USB_IRQ); + return 0; +} + +/* Zephyr USB device controller API implementation */ + +int usb_dc_attach(void) +{ + int ret; + + LOG_DBG(""); + +#ifdef SYSCFG_CFGR1_USB_IT_RMP + /* + * STM32F302/F303: USB IRQ collides with CAN_1 IRQ (§14.1.3, RM0316) + * Remap IRQ by default to enable use of both IPs simultaneoulsy + * This should be done before calling any HAL function + */ + if (LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_SYSCFG)) { + LL_SYSCFG_EnableRemapIT_USB(); + } else { + LOG_ERR("System Configuration Controller clock is " + "disabled. Unable to enable IRQ remapping."); + } +#endif + + /* + * For STM32F0 series SoCs on QFN28 and TSSOP20 packages enable PIN + * pair PA11/12 mapped instead of PA9/10 (e.g. stm32f070x6) + */ +#if USB_ENABLE_PIN_REMAP == 1 + if (LL_APB1_GRP2_IsEnabledClock(LL_APB1_GRP2_PERIPH_SYSCFG)) { + LL_SYSCFG_EnablePinRemap(); + } else { + LOG_ERR("System Configuration Controller clock is " + "disabled. Unable to enable pin remapping."); + } +#endif + + ret = usb_dc_stm32_clock_enable(); + if (ret) { + return ret; + } + + ret = usb_dc_stm32_init(); + if (ret) { + return ret; + } + + /* + * Required for at least STM32L4 devices as they electrically + * isolate USB features from VDDUSB. It must be enabled before + * USB can function. Refer to section 5.1.3 in DM00083560 or + * DM00310109. + */ +#ifdef PWR_CR2_USV +#if defined(LL_APB1_GRP1_PERIPH_PWR) + if (LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_PWR)) { + LL_PWR_EnableVddUSB(); + } else { + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR); + LL_PWR_EnableVddUSB(); + LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_PWR); + } +#else + LL_PWR_EnableVddUSB(); +#endif /* defined(LL_APB1_GRP1_PERIPH_PWR) */ +#endif /* PWR_CR2_USV */ + + return 0; +} + +int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + + LOG_DBG("ep 0x%02x", ep); + + if (!ep_state) { + return -EINVAL; + } + + ep_state->cb = cb; + + return 0; +} + +void usb_dc_set_status_callback(const usb_dc_status_callback cb) +{ + LOG_DBG(""); + + usb_dc_stm32_state.status_cb = cb; +} + +int usb_dc_set_address(const uint8_t addr) +{ + HAL_StatusTypeDef status; + + LOG_DBG("addr %u (0x%02x)", addr, addr); + + status = HAL_PCD_SetAddress(&usb_dc_stm32_state.pcd, addr); + if (status != HAL_OK) { + LOG_ERR("HAL_PCD_SetAddress failed(0x%02x), %d", addr, + (int)status); + return -EIO; + } + + return 0; +} + +int usb_dc_ep_start_read(uint8_t ep, uint8_t *data, uint32_t max_data_len) +{ + HAL_StatusTypeDef status; + + LOG_DBG("ep 0x%02x, len %u", ep, max_data_len); + + /* we flush EP0_IN by doing a 0 length receive on it */ + if (!USB_EP_DIR_IS_OUT(ep) && (ep != EP0_IN || max_data_len)) { + LOG_ERR("invalid ep 0x%02x", ep); + return -EINVAL; + } + + if (max_data_len > EP_MPS) { + max_data_len = EP_MPS; + } + + status = HAL_PCD_EP_Receive(&usb_dc_stm32_state.pcd, ep, + usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)], + max_data_len); + if (status != HAL_OK) { + LOG_ERR("HAL_PCD_EP_Receive failed(0x%02x), %d", ep, + (int)status); + return -EIO; + } + + return 0; +} + +int usb_dc_ep_get_read_count(uint8_t ep, uint32_t *read_bytes) +{ + if (!USB_EP_DIR_IS_OUT(ep) || !read_bytes) { + LOG_ERR("invalid ep 0x%02x", ep); + return -EINVAL; + } + + *read_bytes = HAL_PCD_EP_GetRxCount(&usb_dc_stm32_state.pcd, ep); + + return 0; +} + +int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) +{ + uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); + + LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps, + cfg->ep_type); + + if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { + LOG_ERR("invalid endpoint configuration"); + return -1; + } + + if (ep_idx > (USB_NUM_BIDIR_ENDPOINTS - 1)) { + LOG_ERR("endpoint index/address out of range"); + return -1; + } + + return 0; +} + +int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg) +{ + uint8_t ep = ep_cfg->ep_addr; + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + + LOG_DBG("ep 0x%02x, previous ep_mps %u, ep_mps %u, ep_type %u", + ep_cfg->ep_addr, ep_state->ep_mps, ep_cfg->ep_mps, + ep_cfg->ep_type); + + if (!ep_state) { + return -EINVAL; + } + +#ifdef USB + if (ep_cfg->ep_mps > ep_state->ep_pma_buf_len) { + if (USB_RAM_SIZE <= + (usb_dc_stm32_state.pma_offset + ep_cfg->ep_mps)) { + return -EINVAL; + } + HAL_PCDEx_PMAConfig(&usb_dc_stm32_state.pcd, ep, PCD_SNG_BUF, + usb_dc_stm32_state.pma_offset); + ep_state->ep_pma_buf_len = ep_cfg->ep_mps; + usb_dc_stm32_state.pma_offset += ep_cfg->ep_mps; + } +#endif + ep_state->ep_mps = ep_cfg->ep_mps; + + switch (ep_cfg->ep_type) { + case USB_DC_EP_CONTROL: + ep_state->ep_type = EP_TYPE_CTRL; + break; + case USB_DC_EP_ISOCHRONOUS: + ep_state->ep_type = EP_TYPE_ISOC; + break; + case USB_DC_EP_BULK: + ep_state->ep_type = EP_TYPE_BULK; + break; + case USB_DC_EP_INTERRUPT: + ep_state->ep_type = EP_TYPE_INTR; + break; + default: + return -EINVAL; + } + + return 0; +} + +int usb_dc_ep_set_stall(const uint8_t ep) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + HAL_StatusTypeDef status; + + LOG_DBG("ep 0x%02x", ep); + + if (!ep_state) { + return -EINVAL; + } + + status = HAL_PCD_EP_SetStall(&usb_dc_stm32_state.pcd, ep); + if (status != HAL_OK) { + LOG_ERR("HAL_PCD_EP_SetStall failed(0x%02x), %d", ep, + (int)status); + return -EIO; + } + + ep_state->ep_stalled = 1U; + + return 0; +} + +int usb_dc_ep_clear_stall(const uint8_t ep) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + HAL_StatusTypeDef status; + + LOG_DBG("ep 0x%02x", ep); + + if (!ep_state) { + return -EINVAL; + } + + status = HAL_PCD_EP_ClrStall(&usb_dc_stm32_state.pcd, ep); + if (status != HAL_OK) { + LOG_ERR("HAL_PCD_EP_ClrStall failed(0x%02x), %d", ep, + (int)status); + return -EIO; + } + + ep_state->ep_stalled = 0U; + ep_state->read_count = 0U; + + return 0; +} + +int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + + LOG_DBG("ep 0x%02x", ep); + + if (!ep_state || !stalled) { + return -EINVAL; + } + + *stalled = ep_state->ep_stalled; + + return 0; +} + +int usb_dc_ep_enable(const uint8_t ep) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + HAL_StatusTypeDef status; + + LOG_DBG("ep 0x%02x", ep); + + if (!ep_state) { + return -EINVAL; + } + + LOG_DBG("HAL_PCD_EP_Open(0x%02x, %u, %u)", ep, ep_state->ep_mps, + ep_state->ep_type); + + status = HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, ep, + ep_state->ep_mps, ep_state->ep_type); + if (status != HAL_OK) { + LOG_ERR("HAL_PCD_EP_Open failed(0x%02x), %d", ep, + (int)status); + return -EIO; + } + + if (USB_EP_DIR_IS_OUT(ep) && ep != EP0_OUT) { + return usb_dc_ep_start_read(ep, + usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)], + EP_MPS); + } + + return 0; +} + +int usb_dc_ep_disable(const uint8_t ep) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + HAL_StatusTypeDef status; + + LOG_DBG("ep 0x%02x", ep); + + if (!ep_state) { + return -EINVAL; + } + + status = HAL_PCD_EP_Close(&usb_dc_stm32_state.pcd, ep); + if (status != HAL_OK) { + LOG_ERR("HAL_PCD_EP_Close failed(0x%02x), %d", ep, + (int)status); + return -EIO; + } + + return 0; +} + +int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data, + const uint32_t data_len, uint32_t * const ret_bytes) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + HAL_StatusTypeDef status; + uint32_t len = data_len; + int ret = 0; + + LOG_DBG("ep 0x%02x, len %u", ep, data_len); + + if (!ep_state || !USB_EP_DIR_IS_IN(ep)) { + LOG_ERR("invalid ep 0x%02x", ep); + return -EINVAL; + } + + ret = k_sem_take(&ep_state->write_sem, K_NO_WAIT); + if (ret) { + LOG_ERR("Unable to get write lock (%d)", ret); + return -EAGAIN; + } + + if (!k_is_in_isr()) { + irq_disable(USB_IRQ); + } + + if (ep == EP0_IN && len > USB_MAX_CTRL_MPS) { + len = USB_MAX_CTRL_MPS; + } + + status = HAL_PCD_EP_Transmit(&usb_dc_stm32_state.pcd, ep, + (void *)data, len); + if (status != HAL_OK) { + LOG_ERR("HAL_PCD_EP_Transmit failed(0x%02x), %d", ep, + (int)status); + k_sem_give(&ep_state->write_sem); + ret = -EIO; + } + + if (!ret && ep == EP0_IN && len > 0) { + /* Wait for an empty package as from the host. + * This also flushes the TX FIFO to the host. + */ + usb_dc_ep_start_read(ep, NULL, 0); + } + + if (!k_is_in_isr()) { + irq_enable(USB_IRQ); + } + + if (!ret && ret_bytes) { + *ret_bytes = len; + } + + return ret; +} + +int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, + uint32_t *read_bytes) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + uint32_t read_count; + + if (!ep_state) { + LOG_ERR("Invalid Endpoint %x", ep); + return -EINVAL; + } + + read_count = ep_state->read_count; + + LOG_DBG("ep 0x%02x, %u bytes, %u+%u, %p", ep, max_data_len, + ep_state->read_offset, read_count, data); + + if (!USB_EP_DIR_IS_OUT(ep)) { /* check if OUT ep */ + LOG_ERR("Wrong endpoint direction: 0x%02x", ep); + return -EINVAL; + } + + /* When both buffer and max data to read are zero, just ingore reading + * and return available data in buffer. Otherwise, return data + * previously stored in the buffer. + */ + if (data) { + read_count = MIN(read_count, max_data_len); + memcpy(data, usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)] + + ep_state->read_offset, read_count); + ep_state->read_count -= read_count; + ep_state->read_offset += read_count; + } else if (max_data_len) { + LOG_ERR("Wrong arguments"); + } + + if (read_bytes) { + *read_bytes = read_count; + } + + return 0; +} + +int usb_dc_ep_read_continue(uint8_t ep) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + + if (!ep_state || !USB_EP_DIR_IS_OUT(ep)) { /* Check if OUT ep */ + LOG_ERR("Not valid endpoint: %02x", ep); + return -EINVAL; + } + + /* If no more data in the buffer, start a new read transaction. + * DataOutStageCallback will called on transaction complete. + */ + if (!ep_state->read_count) { + usb_dc_ep_start_read(ep, usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)], + EP_MPS); + } + + return 0; +} + +int usb_dc_ep_read(const uint8_t ep, uint8_t *const data, const uint32_t max_data_len, + uint32_t * const read_bytes) +{ + if (usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes) != 0) { + return -EINVAL; + } + + if (usb_dc_ep_read_continue(ep) != 0) { + return -EINVAL; + } + + return 0; +} + +int usb_dc_ep_halt(const uint8_t ep) +{ + return usb_dc_ep_set_stall(ep); +} + +int usb_dc_ep_flush(const uint8_t ep) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + + if (!ep_state) { + return -EINVAL; + } + + LOG_ERR("Not implemented"); + + return 0; +} + +int usb_dc_ep_mps(const uint8_t ep) +{ + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + + if (!ep_state) { + return -EINVAL; + } + + return ep_state->ep_mps; +} + +int usb_dc_detach(void) +{ + LOG_ERR("Not implemented"); + +#ifdef CONFIG_SOC_SERIES_STM32WBX + /* Specially for STM32WB, unlock the HSEM when USB is no more used. */ + z_stm32_hsem_unlock(CFG_HW_CLK48_CONFIG_SEMID); + + /* + * TODO: AN5289 notes a process of locking Sem0, with possible waits + * via interrupts before switching off CLK48, but lacking any actual + * examples of that, that remains to be implemented. See + * https://github.com/zephyrproject-rtos/zephyr/pull/25850 + */ +#endif /* CONFIG_SOC_SERIES_STM32WBX */ + + return 0; +} + +int usb_dc_reset(void) +{ + LOG_ERR("Not implemented"); + + return 0; +} + +/* Callbacks from the STM32 Cube HAL code */ + +void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd) +{ + int i; + + LOG_DBG(""); + + HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, EP0_IN, EP0_MPS, EP_TYPE_CTRL); + HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, EP0_OUT, EP0_MPS, + EP_TYPE_CTRL); + + /* The DataInCallback will never be called at this point for any pending + * transactions. Reset the IN semaphores to prevent perpetual locked state. + * */ + for (i = 0; i < USB_NUM_BIDIR_ENDPOINTS; i++) { + k_sem_give(&usb_dc_stm32_state.in_ep_state[i].write_sem); + } + + if (usb_dc_stm32_state.status_cb) { + usb_dc_stm32_state.status_cb(USB_DC_RESET, NULL); + } +} + +void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *hpcd) +{ + LOG_DBG(""); + + if (usb_dc_stm32_state.status_cb) { + usb_dc_stm32_state.status_cb(USB_DC_CONNECTED, NULL); + } +} + +void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd) +{ + LOG_DBG(""); + + if (usb_dc_stm32_state.status_cb) { + usb_dc_stm32_state.status_cb(USB_DC_DISCONNECTED, NULL); + } +} + +void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *hpcd) +{ + LOG_DBG(""); + + if (usb_dc_stm32_state.status_cb) { + usb_dc_stm32_state.status_cb(USB_DC_SUSPEND, NULL); + } +} + +void HAL_PCD_ResumeCallback(PCD_HandleTypeDef *hpcd) +{ + LOG_DBG(""); + + if (usb_dc_stm32_state.status_cb) { + usb_dc_stm32_state.status_cb(USB_DC_RESUME, NULL); + } +} + +void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd) +{ + struct usb_setup_packet *setup = (void *)usb_dc_stm32_state.pcd.Setup; + struct usb_dc_stm32_ep_state *ep_state; + + LOG_DBG(""); + + ep_state = usb_dc_stm32_get_ep_state(EP0_OUT); /* can't fail for ep0 */ + __ASSERT(ep_state, "No corresponding ep_state for EP0"); + + ep_state->read_count = SETUP_SIZE; + ep_state->read_offset = 0U; + memcpy(&usb_dc_stm32_state.ep_buf[EP0_IDX], + usb_dc_stm32_state.pcd.Setup, ep_state->read_count); + + if (ep_state->cb) { + ep_state->cb(EP0_OUT, USB_DC_EP_SETUP); + + if (!(setup->wLength == 0U) && + !(REQTYPE_GET_DIR(setup->bmRequestType) == + REQTYPE_DIR_TO_HOST)) { + usb_dc_ep_start_read(EP0_OUT, + usb_dc_stm32_state.ep_buf[EP0_IDX], + setup->wLength); + } + } +} + +void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum) +{ + uint8_t ep_idx = USB_EP_GET_IDX(epnum); + uint8_t ep = ep_idx | USB_EP_DIR_OUT; + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + + LOG_DBG("epnum 0x%02x, rx_count %u", epnum, + HAL_PCD_EP_GetRxCount(&usb_dc_stm32_state.pcd, epnum)); + + /* Transaction complete, data is now stored in the buffer and ready + * for the upper stack (usb_dc_ep_read to retrieve). + */ + usb_dc_ep_get_read_count(ep, &ep_state->read_count); + ep_state->read_offset = 0U; + + if (ep_state->cb) { + ep_state->cb(ep, USB_DC_EP_DATA_OUT); + } +} + +void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum) +{ + uint8_t ep_idx = USB_EP_GET_IDX(epnum); + uint8_t ep = ep_idx | USB_EP_DIR_IN; + struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep); + + LOG_DBG("epnum 0x%02x", epnum); + + __ASSERT(ep_state, "No corresponding ep_state for ep"); + + k_sem_give(&ep_state->write_sem); + + if (ep_state->cb) { + ep_state->cb(ep, USB_DC_EP_DATA_IN); + } +} + +#if defined(USB) && defined(CONFIG_USB_DC_STM32_DISCONN_ENABLE) +void HAL_PCDEx_SetConnectionState(PCD_HandleTypeDef *hpcd, uint8_t state) +{ + const struct device *usb_disconnect; + + usb_disconnect = device_get_binding( + DT_GPIO_LABEL(DT_INST(0, st_stm32_usb), disconnect_gpios)); + + gpio_pin_configure(usb_disconnect, + DT_GPIO_PIN(DT_INST(0, st_stm32_usb), disconnect_gpios), + DT_GPIO_FLAGS(DT_INST(0, st_stm32_usb), disconnect_gpios) | + (state ? GPIO_OUTPUT_ACTIVE : GPIO_OUTPUT_INACTIVE)); +} +#endif /* USB && CONFIG_USB_DC_STM32_DISCONN_ENABLE */ diff --git a/port/stm32/back/usbd_STM32F103.c b/port/stm32/back/usbd_STM32F103.c new file mode 100644 index 00000000..319d1166 --- /dev/null +++ b/port/stm32/back/usbd_STM32F103.c @@ -0,0 +1,770 @@ +/* + * Copyright (c) 2004-2016 ARM Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*---------------------------------------------------------------------------- +* RL-ARM - USB +*---------------------------------------------------------------------------- +* Name: usbd_STM32F103.c +* Purpose: Hardware Layer module for ST STM32F103 +* Rev.: V4.70 +*---------------------------------------------------------------------------*/ + +/* Double Buffering is not supported */ + +#include +#include "stm32f1xx.h" +#include "usbreg.h" +#include "IO_Config.h" +#include "cortex_m.h" +#include "string.h" + +#define __NO_USB_LIB_C +#include "usb_config.c" + +#define USB_ISTR_W0C_MASK (ISTR_PMAOVR | ISTR_ERR | ISTR_WKUP | ISTR_SUSP | ISTR_RESET | ISTR_SOF | ISTR_ESOF) +#define VAL_MASK 0xFFFF +#define VAL_SHIFT 16 +#define EP_NUM_MASK 0xFFFF +#define EP_NUM_SHIFT 0 + +#define USB_DBL_BUF_EP 0x0000 + +#define EP_BUF_ADDR (sizeof(EP_BUF_DSCR)*(USBD_EP_NUM+1)) /* Endpoint Buf Adr */ + +EP_BUF_DSCR *pBUF_DSCR = (EP_BUF_DSCR *)USB_PMA_ADDR; /* Ptr to EP Buf Desc */ + +U16 FreeBufAddr; /* Endpoint Free Buffer Address */ + +uint32_t StatQueue[(USBD_EP_NUM + 1) * 2 + 1]; +uint32_t StatQueueHead = 0; +uint32_t StatQueueTail = 0; +uint32_t LastIstr = 0; + + +inline static void stat_enque(uint32_t stat) +{ + cortex_int_state_t state; + state = cortex_int_get_and_disable(); + StatQueue[StatQueueTail] = stat; + StatQueueTail = (StatQueueTail + 1) % (sizeof(StatQueue) / sizeof(StatQueue[0])); + cortex_int_restore(state); +} + +inline static uint32_t stat_deque() +{ + cortex_int_state_t state; + uint32_t stat; + state = cortex_int_get_and_disable(); + stat = StatQueue[StatQueueHead]; + StatQueueHead = (StatQueueHead + 1) % (sizeof(StatQueue) / sizeof(StatQueue[0])); + cortex_int_restore(state); + + return stat; +} + +inline static uint32_t stat_is_empty() +{ + cortex_int_state_t state; + uint32_t empty; + state = cortex_int_get_and_disable(); + empty = StatQueueHead == StatQueueTail; + cortex_int_restore(state); + return empty; +} + + +/* + * Reset Endpoint + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void EP_Reset(U32 EPNum) +{ + U32 num, val; + num = EPNum & 0x0F; + val = EPxREG(num); + + if (EPNum & 0x80) { /* IN Endpoint */ + EPxREG(num) = val & (EP_MASK | EP_DTOG_TX); + } else { /* OUT Endpoint */ + EPxREG(num) = val & (EP_MASK | EP_DTOG_RX); + } +} + + +/* + * Set Endpoint Status + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * stat: New Status + * Return Value: None + */ + +void EP_Status(U32 EPNum, U32 stat) +{ + U32 num, val; + num = EPNum & 0x0F; + val = EPxREG(num); + + if (EPNum & 0x80) { /* IN Endpoint */ + EPxREG(num) = EP_VAL_UNCHANGED(val) | ((val ^ stat) & EP_STAT_TX); + } else { /* OUT Endpoint */ + EPxREG(num) = EP_VAL_UNCHANGED(val) | ((val ^ stat) & EP_STAT_RX); + } +} + + +/* + * USB Device Interrupt enable + * Called by USBD_Init to enable the USB Interrupt + * Return Value: None + */ + +#ifdef __RTX +void __svc(1) USBD_IntrEna(void); +void __SVC_1(void) +{ +#else +void USBD_IntrEna(void) +{ +#endif + NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); +} + + +/* + * USB Device Initialize Function + * Called by the User to initialize USB + * Return Value: None + */ + +void USBD_Init(void) +{ + RCC->APB1ENR |= (1 << 23); /* enable clock for USB */ + USBD_IntrEna(); /* Enable USB Interrupts */ + /* Control USB connecting via SW */ + USB_CONNECT_OFF(); +} + + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ + +void USBD_Connect(BOOL con) +{ + if (con) { + CNTR = CNTR_FRES; /* Force USB Reset */ + CNTR = 0; + ISTR = 0; /* Clear Interrupt Status */ + CNTR = CNTR_RESETM | CNTR_SUSPM | CNTR_WKUPM; /* USB Interrupt Mask */ + USB_CONNECT_ON(); + } else { + CNTR = CNTR_FRES | CNTR_PDWN; /* Switch Off USB Device */ + USB_CONNECT_OFF(); + } +} + + +/* + * USB Device Reset Function + * Called automatically on USB Device Reset + * Return Value: None + */ + +void USBD_Reset(void) +{ + NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn); + + /* Double Buffering is not yet supported */ + ISTR = 0; /* Clear Interrupt Status */ + CNTR = CNTR_CTRM | CNTR_RESETM | CNTR_SUSPM | CNTR_WKUPM | +#ifdef __RTX + ((USBD_RTX_DevTask != 0) ? CNTR_ERRM : 0) | + ((USBD_RTX_DevTask != 0) ? CNTR_PMAOVRM : 0) | + ((USBD_RTX_DevTask != 0) ? CNTR_SOFM : 0) | + ((USBD_RTX_DevTask != 0) ? CNTR_ESOFM : 0); +#else + ((USBD_P_Error_Event != 0) ? CNTR_ERRM : 0) | + ((USBD_P_Error_Event != 0) ? CNTR_PMAOVRM : 0) | + ((USBD_P_SOF_Event != 0) ? CNTR_SOFM : 0) | + ((USBD_P_SOF_Event != 0) ? CNTR_ESOFM : 0); +#endif + FreeBufAddr = EP_BUF_ADDR; + BTABLE = 0x00; /* set BTABLE Address */ + /* Setup Control Endpoint 0 */ + pBUF_DSCR->ADDR_TX = FreeBufAddr; + FreeBufAddr += USBD_MAX_PACKET0; + pBUF_DSCR->ADDR_RX = FreeBufAddr; + FreeBufAddr += USBD_MAX_PACKET0; + + if (USBD_MAX_PACKET0 > 62) { + pBUF_DSCR->COUNT_RX = ((USBD_MAX_PACKET0 << 5) - 1) | 0x8000; + } else { + pBUF_DSCR->COUNT_RX = USBD_MAX_PACKET0 << 9; + } + + EPxREG(0) = EP_CONTROL | EP_RX_VALID; + DADDR = DADDR_EF | 0; /* Enable USB Default Address */ + + NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); +} + + +/* + * USB Device Suspend Function + * Called automatically on USB Device Suspend + * Return Value: None + */ + +void USBD_Suspend(void) +{ + CNTR |= CNTR_FSUSP; /* Force Suspend */ + CNTR |= CNTR_LPMODE; /* Low Power Mode */ +} + + +/* + * USB Device Resume Function + * Called automatically on USB Device Resume + * Return Value: None + */ + +void USBD_Resume(void) +{ + /* Performed by Hardware */ +} + + +/* + * USB Device Remote Wakeup Function + * Called automatically on USB Device Remote Wakeup + * Return Value: None + */ + +void USBD_WakeUp(void) +{ + CNTR &= ~CNTR_FSUSP; /* Clear Suspend */ +} + + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ + +void USBD_WakeUpCfg(BOOL cfg) +{ + /* Not needed */ +} + + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * setup: Called in setup stage (!=0), else after status stage + * Return Value: None + */ + +void USBD_SetAddress(U32 adr, U32 setup) +{ + if (setup) { + return; + } + + DADDR = DADDR_EF | adr; +} + + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ + +void USBD_Configure(BOOL cfg) +{ + if (cfg == __FALSE) { + FreeBufAddr = EP_BUF_ADDR; + FreeBufAddr += 2 * USBD_MAX_PACKET0; /* reset Buffer address */ + } +} + + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ + +void USBD_ConfigEP(USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + /* Double Buffering is not yet supported */ + U32 num, val; + num = pEPD->bEndpointAddress & 0x0F; + val = pEPD->wMaxPacketSize; + + if (pEPD->bEndpointAddress & USB_ENDPOINT_DIRECTION_MASK) { + (pBUF_DSCR + num)->ADDR_TX = FreeBufAddr; + val = (val + 1) & ~1; + } else { + (pBUF_DSCR + num)->ADDR_RX = FreeBufAddr; + + if (val > 62) { + val = (val + 31) & ~31; + (pBUF_DSCR + num)->COUNT_RX = ((val << 5) - 1) | 0x8000; + } else { + val = (val + 1) & ~1; + (pBUF_DSCR + num)->COUNT_RX = val << 9; + } + } + + FreeBufAddr += val; + + switch (pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK) { + case USB_ENDPOINT_TYPE_CONTROL: + val = EP_CONTROL; + break; + + case USB_ENDPOINT_TYPE_ISOCHRONOUS: + val = EP_ISOCHRONOUS; + break; + + case USB_ENDPOINT_TYPE_BULK: + val = EP_BULK; + + if (USB_DBL_BUF_EP & (1 << num)) { + val |= EP_KIND; + } + + break; + + case USB_ENDPOINT_TYPE_INTERRUPT: + val = EP_INTERRUPT; + break; + } + + val |= num; + EPxREG(num) = val; +} + + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ + +void USBD_DirCtrlEP(U32 dir) +{ + /* Not needed */ +} + + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_EnableEP(U32 EPNum) +{ + EP_Status(EPNum, EP_TX_NAK | EP_RX_VALID); /* EP is able to receive */ +} + + +/* + * Disable USB Endpoint + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_DisableEP(U32 EPNum) +{ + EP_Status(EPNum, EP_TX_DIS | EP_RX_DIS); +} + + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ResetEP(U32 EPNum) +{ + EP_Reset(EPNum); +} + + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_SetStallEP(U32 EPNum) +{ + EP_Status(EPNum, EP_TX_STALL | EP_RX_STALL); +} + + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClrStallEP(U32 EPNum) +{ + EP_Reset(EPNum); /* reset DTog Bits */ + EP_Status(EPNum, EP_TX_VALID | EP_RX_VALID); +} + + +/* + * Clear USB Device Endpoint Buffer + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClearEPBuf(U32 EPNum) +{ + ; +} + + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ + +U32 USBD_ReadEP(U32 EPNum, U8 *pData, U32 bufsz) +{ + /* Double Buffering is not yet supported */ + U32 num, cnt, *pv, n; + num = EPNum & 0x0F; + pv = (U32 *)(USB_PMA_ADDR + 2 * ((pBUF_DSCR + num)->ADDR_RX)); + cnt = (pBUF_DSCR + num)->COUNT_RX & EP_COUNT_MASK; + if (cnt > bufsz) { + cnt = bufsz; + } + + for (n = 0; n < (cnt + 1) / 2; n++) { + *((__packed U16 *)pData) = *pv++; + pData += 2; + } + + EP_Status(EPNum, EP_RX_VALID); + return (cnt); +} + + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ + +U32 USBD_WriteEP(U32 EPNum, U8 *pData, U32 cnt) +{ + /* Double Buffering is not yet supported */ + U32 num, *pv, n; + U16 statusEP; + num = EPNum & 0x0F; + pv = (U32 *)(USB_PMA_ADDR + 2 * ((pBUF_DSCR + num)->ADDR_TX)); + + for (n = 0; n < (cnt + 1) / 2; n++) { + *pv++ = *((__packed U16 *)pData); + pData += 2; + } + + (pBUF_DSCR + num)->COUNT_TX = cnt; + statusEP = EPxREG(num); + + if ((statusEP & EP_STAT_TX) != EP_TX_STALL) { + EP_Status(EPNum, EP_TX_VALID); /* do not make EP valid if stalled */ + } + + return (cnt); +} + + +/* + * Get USB Device Last Frame Number + * Parameters: None + * Return Value: Frame Number + */ + +U32 USBD_GetFrame(void) +{ + return (FNR & FNR_FN); +} + + +#ifdef __RTX +U32 LastError; /* Last Error */ + +/* + * Get USB Last Error Code + * Parameters: None + * Return Value: Error Code + */ + +U32 USBD_GetError(void) +{ + return (LastError); +} +#endif + + +/* + * USB Device Interrupt Service Routine + */ + +void USB_LP_CAN1_RX0_IRQHandler(void) +{ + uint32_t istr; + uint32_t num; + uint32_t val; + + istr = ISTR; + // Zero out endpoint ID since this is read from the queue + LastIstr |= istr & ~(ISTR_DIR | ISTR_EP_ID); + // Clear interrupts that are pending + ISTR = ~(istr & USB_ISTR_W0C_MASK); + if (istr & ISTR_CTR) { + while ((istr = ISTR) & ISTR_CTR) { + num = istr & ISTR_EP_ID; + val = EPxREG(num); + + // Process and filter out the zero length status out endpoint to prevent + // the next SETUP packet from being dropped. + if ((0 == num) && (val & EP_CTR_RX) && !(val & EP_SETUP) + && (0 == ((pBUF_DSCR + num)->COUNT_RX & EP_COUNT_MASK))) { + if (val & EP_CTR_TX) { + // Drop the RX event but not TX + stat_enque((((val & VAL_MASK) & ~EP_CTR_RX) << VAL_SHIFT) | + ((num & EP_NUM_MASK) << EP_NUM_SHIFT)); + } else { + // Drop the event + } + } else { + stat_enque(((val & VAL_MASK) << VAL_SHIFT) | + ((num & EP_NUM_MASK) << EP_NUM_SHIFT)); + } + + + if (val & EP_CTR_RX) { + EPxREG(num) = EP_VAL_UNCHANGED(val) & ~EP_CTR_RX; + } + + if (val & EP_CTR_TX) { + EPxREG(num) = EP_VAL_UNCHANGED(val) & ~EP_CTR_TX; + } + } + } + + USBD_SignalHandler(); +} + +void USBD_Handler(void) +{ + U32 istr, num, val, num_val; + cortex_int_state_t state; + + // Get ISTR + state = cortex_int_get_and_disable(); + istr = LastIstr; + LastIstr = 0; + cortex_int_restore(state); + + /* USB Reset Request */ + if (istr & ISTR_RESET) { + USBD_Reset(); + usbd_reset_core(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESET, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Reset_Event) { + USBD_P_Reset_Event(); + } + +#endif + } + + /* USB Suspend Request */ + if (istr & ISTR_SUSP) { + USBD_Suspend(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SUSPEND, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Suspend_Event) { + USBD_P_Suspend_Event(); + } + +#endif + } + + /* USB Wakeup */ + if (istr & ISTR_WKUP) { + USBD_WakeUp(); +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_RESUME, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Resume_Event) { + USBD_P_Resume_Event(); + } + +#endif + } + + /* Start of Frame */ + if (istr & ISTR_SOF) { +#ifdef __RTX + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_SOF, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + +#endif + } + + /* PMA Over/underrun */ + if (istr & ISTR_PMAOVR) { +#ifdef __RTX + LastError = 2; + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_ERROR, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Error_Event) { + USBD_P_Error_Event(2); + } + +#endif + } + + /* Error: No Answer, CRC Error, Bit Stuff Error, Frame Format Error */ + if (istr & ISTR_ERR) { +#ifdef __RTX + LastError = 1; + + if (USBD_RTX_DevTask) { + isr_evt_set(USBD_EVT_ERROR, USBD_RTX_DevTask); + } + +#else + + if (USBD_P_Error_Event) { + USBD_P_Error_Event(1); + } + +#endif + } + + /* Endpoint Interrupts */ + while ((istr & ISTR_CTR) && !stat_is_empty()) { + num_val = stat_deque(); + num = (num_val >> EP_NUM_SHIFT) & EP_NUM_MASK; + val = (num_val >> VAL_SHIFT) & VAL_MASK; + if (val & EP_CTR_TX) { +#ifdef __RTX + + if (USBD_RTX_EPTask[num]) { + isr_evt_set(USBD_EVT_IN, USBD_RTX_EPTask[num]); + } + +#else + + if (USBD_P_EP[num]) { + USBD_P_EP[num](USBD_EVT_IN); + } + +#endif + } + + if (val & EP_CTR_RX) { +#ifdef __RTX + + if (USBD_RTX_EPTask[num]) { + isr_evt_set((val & EP_SETUP) ? USBD_EVT_SETUP : USBD_EVT_OUT, USBD_RTX_EPTask[num]); + } + +#else + + if (USBD_P_EP[num]) { + USBD_P_EP[num]((val & EP_SETUP) ? USBD_EVT_SETUP : USBD_EVT_OUT); + } + +#endif + } + } +} diff --git a/port/stm32/back/usbd_STM32F205.c b/port/stm32/back/usbd_STM32F205.c new file mode 100644 index 00000000..bd3878d6 --- /dev/null +++ b/port/stm32/back/usbd_STM32F205.c @@ -0,0 +1,669 @@ +/* + * Copyright (c) 2004-2016 ARM Limited. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*---------------------------------------------------------------------------- +* RL-ARM - USB +*---------------------------------------------------------------------------- +* Name: usbd_STM32F103.c +* Purpose: Hardware Layer module for ST STM32F103 +* Rev.: V4.70 +*---------------------------------------------------------------------------*/ + +/* Double Buffering is not supported */ + +#include +#include "stm32f2xx.h" +#include "IO_Config.h" +#include "cortex_m.h" +#include "string.h" +#include + +#define __NO_USB_LIB_C +#include "usb_config.c" + +#define USB_ISTR_W0C_MASK (ISTR_PMAOVR | ISTR_ERR | ISTR_WKUP | ISTR_SUSP | ISTR_RESET | ISTR_SOF | ISTR_ESOF) +#define VAL_MASK 0xFFFF +#define VAL_SHIFT 16 +#define EP_NUM_MASK 0xFFFF +#define EP_NUM_SHIFT 0 + +#define USB_DBL_BUF_EP 0x0000 +#define USB_DEVICE ((USB_OTG_DeviceTypeDef *)(USB_OTG_HS_PERIPH_BASE + USB_OTG_DEVICE_BASE)) +#define USB_INEP(i) ((USB_OTG_INEndpointTypeDef *)(USB_OTG_HS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE + ((i) * USB_OTG_EP_REG_SIZE))) +#define USB_OUTEP(i) ((USB_OTG_OUTEndpointTypeDef *)(USB_OTG_HS_PERIPH_BASE + USB_OTG_OUT_ENDPOINT_BASE + ((i) * USB_OTG_EP_REG_SIZE))) +#define USB_DFIFO(i) *(__IO uint32_t *)(USB_OTG_HS_PERIPH_BASE + USB_OTG_FIFO_BASE + ((i) * USB_OTG_FIFO_SIZE)) + + +UART_HandleTypeDef huart1; +uint32_t gint_flag = 0; +uint32_t rx_state = 0; +uint32_t recbuf32[5][512]; +uint32_t recstate[5]; + +/* + * USB Device Interrupt enable + * Called by USBD_Init to enable the USB Interrupt + * Return Value: None + */ + +#ifdef __RTX +void __svc(1) USBD_IntrEna(void); +void __SVC_1(void) +{ +#else +void USBD_IntrEna(void) +{ +#endif + HAL_NVIC_SetPriority(OTG_HS_EP1_OUT_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(OTG_HS_EP1_OUT_IRQn); + HAL_NVIC_SetPriority(OTG_HS_EP1_IN_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(OTG_HS_EP1_IN_IRQn); + HAL_NVIC_SetPriority(OTG_HS_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(OTG_HS_IRQn); +} + +/* + * USB Device Initialize Function + * Called by the User to initialize USB + * Return Value: None + */ +void USBD_Init(void) +{ + U8 i; + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; + GPIOA->MODER &= ~(GPIO_MODER_MODE3 | GPIO_MODER_MODE5); + GPIOA->MODER |= 2U << GPIO_MODER_MODER3_Pos | 2U << GPIO_MODER_MODER5_Pos; + GPIOA->OTYPER &= ~(GPIO_OTYPER_OT_3 | GPIO_OTYPER_OT_5); + GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR3 | GPIO_OSPEEDER_OSPEEDR5; + GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR3 | GPIO_PUPDR_PUPDR5); + GPIOA->AFR[0] &= ~(GPIO_AFRL_AFRL3 | GPIO_AFRL_AFRL5); + GPIOA->AFR[0] |= 0xAU << 12 | 0xAU << 20; + + GPIOB->MODER &= ~(GPIO_MODER_MODE0 | GPIO_MODER_MODE1 | GPIO_MODER_MODE5 | GPIO_MODER_MODE10 + | GPIO_MODER_MODE11 | GPIO_MODER_MODE12 | GPIO_MODER_MODE13); + GPIOB->MODER |= 2U << GPIO_MODER_MODER0_Pos | 2U << GPIO_MODER_MODER1_Pos | 2U << GPIO_MODER_MODER5_Pos + | 2U << GPIO_MODER_MODER10_Pos | 2U << GPIO_MODER_MODER11_Pos | 2U << GPIO_MODER_MODER12_Pos + | 2U << GPIO_MODER_MODER13_Pos; + GPIOB->OTYPER &= ~(GPIO_OTYPER_OT_0 | GPIO_OTYPER_OT_1 | GPIO_OTYPER_OT_5 | GPIO_OTYPER_OT_10 + | GPIO_OTYPER_OT_11 | GPIO_OTYPER_OT_12 | GPIO_OTYPER_OT_13); + GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR0 | GPIO_OSPEEDER_OSPEEDR1 | GPIO_OSPEEDER_OSPEEDR5 + | GPIO_OSPEEDER_OSPEEDR10 | GPIO_OSPEEDER_OSPEEDR11 | GPIO_OSPEEDER_OSPEEDR12 | GPIO_OSPEEDER_OSPEEDR13; + GPIOB->PUPDR &= ~(GPIO_PUPDR_PUPDR0 | GPIO_PUPDR_PUPDR1 | GPIO_PUPDR_PUPDR5 | GPIO_PUPDR_PUPDR10 + | GPIO_PUPDR_PUPDR11 | GPIO_PUPDR_PUPDR12 | GPIO_PUPDR_PUPDR13); + GPIOB->AFR[0] &= ~(GPIO_AFRL_AFRL0 | GPIO_AFRL_AFRL1 | GPIO_AFRL_AFRL5); + GPIOB->AFR[0] |= 0xAU << 0 | 0xAU << 4 | 0xAU << 20; + GPIOB->AFR[1] &= ~(GPIO_AFRH_AFRH2| GPIO_AFRH_AFRH3 | GPIO_AFRH_AFRH4 | GPIO_AFRH_AFRH5); + GPIOB->AFR[1] |= 0xAU << 8 | 0xAU << 12 | 0xAU << 16 | 0xAU << 20; + + GPIOC->MODER &= ~(GPIO_MODER_MODE0 | GPIO_MODER_MODE2 | GPIO_MODER_MODE3); + GPIOC->MODER |= 2U << GPIO_MODER_MODER0_Pos | 2U << GPIO_MODER_MODER2_Pos | 2U << GPIO_MODER_MODER3_Pos; + GPIOC->OTYPER &= ~(GPIO_OTYPER_OT_0 | GPIO_OTYPER_OT_2 | GPIO_OTYPER_OT_3); + GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR0 | GPIO_OSPEEDER_OSPEEDR2 | GPIO_OSPEEDER_OSPEEDR3; + GPIOC->PUPDR &= ~(GPIO_PUPDR_PUPDR0 | GPIO_PUPDR_PUPDR2 | GPIO_PUPDR_PUPDR3); + GPIOC->AFR[0] &= ~(GPIO_AFRL_AFRL0 | GPIO_AFRL_AFRL2 | GPIO_AFRL_AFRL3); + GPIOC->AFR[0] |= 0xAU << 0 | 0xAU << 8 | 0xAU << 12; + + + RCC->AHB1ENR |= RCC_AHB1ENR_OTGHSEN; + RCC->AHB1ENR |= RCC_AHB1ENR_OTGHSULPIEN; + + USB_OTG_HS->GCCFG = USB_OTG_GCCFG_NOVBUSSENS; + USB_OTG_HS->GAHBCFG = USB_OTG_GAHBCFG_GINT | 7 << USB_OTG_GAHBCFG_HBSTLEN_Pos; + USB_OTG_HS->GUSBCFG = 9U << USB_OTG_GUSBCFG_TRDT_Pos | USB_OTG_GUSBCFG_FDMOD; + USB_DEVICE->DCFG = 0U << USB_OTG_DCFG_DSPD_Pos; + USB_OTG_HS->GINTMSK |= USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | USB_OTG_GINTMSK_USBSUSPM | + USB_OTG_GINTMSK_OEPINT | USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_RXFLVLM | USB_OTG_GINTMSK_WUIM | +#ifdef __RTX + ((USBD_RTX_DevTask != 0) ? USB_OTG_GINTMSK_SOFM : 0) | + ((USBD_RTX_DevTask != 0) ? USB_OTG_GINTMSK_ESUSPM : 0); +#else + ((USBD_P_SOF_Event != 0) ? USB_OTG_GINTMSK_SOFM : 0) | + ((USBD_P_SOF_Event != 0) ? USB_OTG_GINTMSK_ESUSPM : 0); +#endif + for (i = 0U; i < 15U; i++) + { + USB_OTG_HS->DIEPTXF[i] = 0U; + } + USB_OTG_HS->GRXFSIZ = 0x180; + USB_OTG_HS->DIEPTXF0_HNPTXFSIZ = 0x80 << 16 | 0x180; + USB_OTG_HS->DIEPTXF[0] = 0x80 << 16 | 0x200; + USB_OTG_HS->DIEPTXF[1] = 0x80 << 16 | 0x280; + USB_OTG_HS->DIEPTXF[2] = 0x80 << 16 | 0x300; + USB_OTG_HS->DIEPTXF[3] = 0x80 << 16 | 0x380; + + USB_OTG_HS->GINTSTS = 0xFFFFFFFF; + USBD_IntrEna(); + USB_DEVICE->DCTL |= USB_OTG_DCTL_SDIS; +} + +/* + * USB Device Connect Function + * Called by the User to Connect/Disconnect USB Device + * Parameters: con: Connect/Disconnect + * Return Value: None + */ + +void USBD_Connect(BOOL con) +{ + USB_DEVICE->DCTL &= ~USB_OTG_DCTL_SDIS; +} +/* + * USB Device Suspend Function + * Called automatically on USB Device Suspend + * Return Value: None + */ + +void USBD_Suspend(void) +{ +} + + +/* + * USB Device Resume Function + * Called automatically on USB Device Resume + * Return Value: None + */ + +void USBD_Resume(void) +{ +} + + +/* + * USB Device Remote Wakeup Function + * Called automatically on USB Device Remote Wakeup + * Return Value: None + */ + +void USBD_WakeUp(void) +{ +} + + +/* + * USB Device Remote Wakeup Configuration Function + * Parameters: cfg: Device Enable/Disable + * Return Value: None + */ + +void USBD_WakeUpCfg(BOOL cfg) +{ +} + + +/* + * USB Device Set Address Function + * Parameters: adr: USB Device Address + * setup: Called in setup stage (!=0), else after status stage + * Return Value: None + */ + +void USBD_SetAddress(U32 adr, U32 setup) +{ + USB_DEVICE->DCFG &= ~(USB_OTG_DCFG_DAD); + USB_DEVICE->DCFG |= ((uint32_t)adr << 4) & USB_OTG_DCFG_DAD; +} + + +/* + * USB Device Configure Function + * Parameters: cfg: Device Configure/Deconfigure + * Return Value: None + */ + +void USBD_Configure(BOOL cfg) +{ +} + + +/* + * Configure USB Device Endpoint according to Descriptor + * Parameters: pEPD: Pointer to Device Endpoint Descriptor + * Return Value: None + */ +void USBD_ConfigEP(USB_ENDPOINT_DESCRIPTOR *pEPD) +{ + uint32_t num, mpsize, ep_type; + num = pEPD->bEndpointAddress & 0x0F; + mpsize = pEPD->wMaxPacketSize; + switch (pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK) { + case USB_ENDPOINT_TYPE_CONTROL: + ep_type = 0; + break; + + case USB_ENDPOINT_TYPE_ISOCHRONOUS: + ep_type = 1; + break; + + case USB_ENDPOINT_TYPE_BULK: + ep_type = 2; + break; + + case USB_ENDPOINT_TYPE_INTERRUPT: + ep_type = 3; + break; + } + if (pEPD->bEndpointAddress & 0x80) { + USB_DEVICE->DAINTMSK |= USB_OTG_DAINTMSK_IEPM & (uint32_t)(1UL << num); + USB_INEP(num)->DIEPCTL = (mpsize & USB_OTG_DIEPCTL_MPSIZ) | + ((uint32_t)ep_type << USB_OTG_DIEPCTL_EPTYP_Pos) | + (num << USB_OTG_DIEPCTL_TXFNUM_Pos) | + USB_OTG_DIEPCTL_SD0PID_SEVNFRM; + } else { + USB_DEVICE->DAINTMSK |= USB_OTG_DAINTMSK_OEPM & ((uint32_t)(1UL << num) << 16); + USB_OUTEP(num)->DOEPCTL = (mpsize & USB_OTG_DOEPCTL_MPSIZ) | + ((uint32_t)ep_type << USB_OTG_DOEPCTL_EPTYP_Pos) | + USB_OTG_DOEPCTL_SD0PID_SEVNFRM; + } +} + + +/* + * Set Direction for USB Device Control Endpoint + * Parameters: dir: Out (dir == 0), In (dir <> 0) + * Return Value: None + */ + +void USBD_DirCtrlEP(U32 dir) +{ +} + + +/* + * Enable USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_EnableEP(U32 EPNum) +{ + if (EPNum & 0x80) { + USB_INEP(EPNum & 0x0F)->DIEPCTL |= USB_OTG_DIEPCTL_USBAEP; + } else { + USB_OUTEP(EPNum & 0x0F)->DOEPCTL |= USB_OTG_DOEPCTL_USBAEP; + } +} + + +/* + * Disable USB Endpoint + * Parameters: EPNum: Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_DisableEP(U32 EPNum) +{ +} + + +/* + * Reset USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ResetEP(U32 EPNum) +{ + if (EPNum & 0x80) { + if ((EPNum & 0xF) == 0) { + USB_OUTEP(EPNum & 0x0F)->DOEPTSIZ = 1U << USB_OTG_DOEPTSIZ_STUPCNT_Pos | + (USB_OUTEP(EPNum & 0x0F)->DOEPCTL & USB_OTG_DOEPCTL_MPSIZ) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos; + USB_OUTEP(EPNum & 0x0F)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + } else { + USB_OUTEP(EPNum & 0x0F)->DOEPTSIZ = 1U << USB_OTG_DOEPTSIZ_PKTCNT_Pos | + (USB_OUTEP(EPNum & 0x0F)->DOEPCTL & USB_OTG_DOEPCTL_MPSIZ) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos; + USB_OUTEP(EPNum & 0x0F)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + } + + } +} + + +/* + * Set Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_SetStallEP(U32 EPNum) +{ + if (EPNum & 0x80) { + USB_INEP(EPNum & 0xF)->DIEPCTL |= USB_OTG_DIEPCTL_STALL; + } else + USB_OUTEP(EPNum & 0xF)->DOEPCTL |= USB_OTG_DIEPCTL_STALL; +} + + +/* + * Clear Stall for USB Device Endpoint + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClrStallEP(U32 EPNum) +{ + if (EPNum & 0x80) { + USB_INEP(EPNum & 0xF)->DIEPCTL &= ~USB_OTG_DIEPCTL_STALL; + if ((USB_INEP(EPNum & 0xF)->DIEPCTL & (0x2U << USB_OTG_DIEPCTL_EPTYP_Pos) )) { + USB_INEP(EPNum & 0xF)->DIEPCTL |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM; + } + } else { + USB_OUTEP(EPNum & 0xF)->DOEPCTL &= ~USB_OTG_DIEPCTL_STALL; + if ((USB_OUTEP(EPNum & 0xF)->DOEPCTL & (0x2U << USB_OTG_DOEPCTL_EPTYP_Pos) )) { + USB_OUTEP(EPNum & 0xF)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM; + } + } + USBD_ClearEPBuf(EPNum); +} + + +/* + * Clear USB Device Endpoint Buffer + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * Return Value: None + */ + +void USBD_ClearEPBuf(U32 EPNum) +{ + EPNum &= 0x0F; + USB_OTG_HS->GRSTCTL = (USB_OTG_GRSTCTL_TXFFLSH | (EPNum << 6)); + while ((USB_OTG_HS->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH) == USB_OTG_GRSTCTL_TXFFLSH); +} + + +/* + * Read USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * Return Value: Number of bytes read + */ + +U32 USBD_ReadEP(U32 EPNum, U8 *pData, U32 bufsz) +{ + uint32_t count32b; + uint32_t i; + if ((EPNum & 0xF) == 2 || (EPNum & 0xF) == 3) { + i = (recstate[EPNum & 0xF] & USB_OTG_GRXSTSP_BCNT) >> USB_OTG_GRXSTSP_BCNT_Pos; + if (i < bufsz) + bufsz = i; + count32b = ((uint32_t)bufsz + 3U) / 4U; + for (i = 0U; i < count32b; i++) { + ((__packed uint32_t *)pData)[i] = recbuf32[EPNum & 0xF][i]; + } + } else { + i = (rx_state & USB_OTG_GRXSTSP_BCNT) >> USB_OTG_GRXSTSP_BCNT_Pos; + if (i < bufsz) + bufsz = i; + count32b = ((uint32_t)bufsz + 3U) / 4U; + for (i = 0U; i < count32b; i++) { + ((__packed uint32_t *)pData)[i] = USB_DFIFO(0); + } + } + USB_OTG_HS->GINTMSK |= USB_OTG_GINTMSK_RXFLVLM; + if ((EPNum & 0xF) != 0) { + USB_OUTEP(EPNum & 0xF)->DOEPINT = (USB_OTG_DOEPINT_XFRC); + USB_OUTEP(EPNum & 0xF)->DOEPTSIZ = 1U << USB_OTG_DOEPTSIZ_PKTCNT_Pos | + (USB_OUTEP(EPNum & 0xF)->DOEPCTL & USB_OTG_DOEPCTL_MPSIZ) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos; + USB_OUTEP(EPNum & 0xF)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + } + return bufsz; +} + + +/* + * Write USB Device Endpoint Data + * Parameters: EPNum: Device Endpoint Number + * EPNum.0..3: Address + * EPNum.7: Dir + * pData: Pointer to Data Buffer + * cnt: Number of bytes to write + * Return Value: Number of bytes written + */ + +U32 USBD_WriteEP(U32 EPNum, U8 *pData, U32 cnt) +{ + uint32_t len32b; + uint32_t i; + EPNum &= 0x0F; + USB_INEP(EPNum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_PKTCNT); + USB_INEP(EPNum)->DIEPTSIZ |= 1U << USB_OTG_DIEPTSIZ_PKTCNT_Pos; + USB_INEP(EPNum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_XFRSIZ); + USB_INEP(EPNum)->DIEPTSIZ |= cnt; + USB_INEP(EPNum)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA); + if (cnt != 0) { + len32b = (cnt + 3U) / 4U; + while (USB_INEP(EPNum)->DTXFSTS < len32b); + for (i = 0U; i < len32b; i++) { + USB_DFIFO(EPNum) = ((uint32_t*)pData)[i]; + } + } + return cnt; +} + + +/* + * Get USB Device Last Frame Number + * Parameters: None + * Return Value: Frame Number + */ + +U32 USBD_GetFrame(void) +{ +} + + +#ifdef __RTX +U32 LastError; /* Last Error */ + +/* + * Get USB Last Error Code + * Parameters: None + * Return Value: Error Code + */ + +U32 USBD_GetError(void) +{ + return (LastError); +} +#endif + + + +void USBD_Handler(void) +{ + uint32_t i; + uint32_t ep_intr; + uint32_t epnum; + uint32_t epint; + + if (gint_flag & USB_OTG_GINTSTS_SOF) { + USB_OTG_HS->GINTSTS |= USB_OTG_GINTSTS_SOF; + if (USBD_P_SOF_Event) { + USBD_P_SOF_Event(); + } + } + + if (gint_flag & USB_OTG_GINTSTS_MMIS) { + USB_OTG_HS->GINTSTS |= USB_OTG_GINTSTS_MMIS; + } + + if (gint_flag & USB_OTG_GINTSTS_ESUSP) { + USB_OTG_HS->GINTSTS |= USB_OTG_GINTSTS_ESUSP; + } + + if (gint_flag & USB_OTG_GINTSTS_USBSUSP) { + USB_OTG_HS->GINTSTS |= USB_OTG_GINTSTS_USBSUSP; + if (USBD_P_Suspend_Event) { + USBD_P_Suspend_Event(); + } + } + + if (gint_flag & USB_OTG_GINTSTS_RXFLVL) { + rx_state = USB_OTG_HS->GRXSTSP; + epnum = rx_state & USB_OTG_GRXSTSP_EPNUM; + if (((rx_state & USB_OTG_GRXSTSP_PKTSTS) >> USB_OTG_GRXSTSP_PKTSTS_Pos) == 2U) + { + if (epnum == 2 || epnum == 3) { + uint32_t count32; + count32 = (rx_state & (USB_OTG_GRXSTSP_BCNT)) >> USB_OTG_GRXSTSP_BCNT_Pos; + for (i = 0U; i < (count32 + 3) / 4; i++) { + recbuf32[epnum][i] = USB_DFIFO(0U); + } + recstate[epnum] = rx_state; + } + + if ((rx_state & USB_OTG_GRXSTSP_BCNT) != 0U) + { + USB_OTG_HS->GINTMSK &= ~USB_OTG_GINTMSK_RXFLVLM; + if (USBD_P_EP[epnum]) { + USBD_P_EP[epnum](USBD_EVT_OUT); + } + } + } + else if (((rx_state & USB_OTG_GRXSTSP_PKTSTS) >> USB_OTG_GRXSTSP_PKTSTS_Pos) == 6U) + { + if (epnum == 2 || epnum == 3) { + uint32_t count32; + count32 = (rx_state & (USB_OTG_GRXSTSP_BCNT)) >> USB_OTG_GRXSTSP_BCNT_Pos; + for (i = 0U; i < (count32 + 3) / 4; i++) { + recbuf32[epnum][i] = USB_DFIFO(0U); + } + recstate[epnum] = rx_state; + } + + USB_OTG_HS->GINTMSK &= ~USB_OTG_GINTMSK_RXFLVLM; + if (USBD_P_EP[epnum]) { + USBD_P_EP[epnum](USBD_EVT_SETUP); + } + } + } + + if (gint_flag & USB_OTG_GINTSTS_USBRST) { + USB_OTG_HS->GINTSTS |= USB_OTG_GINTSTS_USBRST; + for (i = 0; i < 5; i++) + USB_OUTEP(i)->DOEPCTL = USB_OTG_DOEPCTL_SNAK; + USB_DEVICE->DAINTMSK = 0x10001; + USB_DEVICE->DOEPMSK = USB_OTG_DOEPMSK_XFRCM | USB_OTG_DOEPMSK_STUPM; + USB_DEVICE->DIEPMSK = USB_OTG_DIEPMSK_XFRCM | USB_OTG_DIEPMSK_TOM; + USB_OUTEP(0)->DOEPTSIZ = 1U << USB_OTG_DOEPTSIZ_STUPCNT_Pos; + usbd_reset_core(); + if (USBD_P_Reset_Event) { + USBD_P_Reset_Event(); + } + } + + if (gint_flag & USB_OTG_GINTSTS_ENUMDNE) { + USBD_HighSpeed = (USB_DEVICE->DSTS & USB_OTG_DSTS_ENUMSPD) ? 0 : 1; + USB_OTG_HS->GINTSTS |= USB_OTG_GINTSTS_ENUMDNE; + USB_INEP(0)->DIEPTSIZ = 64 << USB_OTG_DIEPCTL_MPSIZ_Pos; + USB_OUTEP(0)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA; + } + + if (gint_flag & USB_OTG_GINTMSK_IEPINT) { + epnum = 0U; + ep_intr = USB_DEVICE->DAINT &0xFFFF; + ep_intr &= USB_DEVICE->DAINTMSK; + while (ep_intr != 0U) { + if ((ep_intr & 0x1U) != 0U) { + epint = USB_INEP((uint32_t)epnum)->DIEPINT; + epint &= USB_DEVICE->DIEPMSK; + if ((epint & USB_OTG_DIEPINT_XFRC) == USB_OTG_DIEPINT_XFRC) { + USB_INEP(epnum)->DIEPINT = USB_OTG_DIEPINT_XFRC; + if (USBD_P_EP[epnum]) { + USBD_P_EP[epnum](USBD_EVT_IN); + } + } + } + ep_intr >>= 1U; + epnum++; + } + } + + if (gint_flag & USB_OTG_GINTMSK_OEPINT) { + epnum = 0U; + ep_intr = USB_DEVICE->DAINT; + ep_intr &= USB_DEVICE->DAINTMSK; + ep_intr >>= 16; + while (ep_intr != 0U) { + if ((ep_intr & 0x1U) != 0U) { + epint = USB_OUTEP((uint32_t)epnum)->DOEPINT; + epint &= USB_DEVICE->DOEPMSK; + if ((epint & USB_OTG_DOEPINT_STUP) == USB_OTG_DOEPINT_STUP) { + USB_OUTEP(epnum)->DOEPINT = (USB_OTG_DOEPINT_STUP); + USB_OUTEP(epnum)->DOEPTSIZ = 1U << USB_OTG_DOEPTSIZ_STUPCNT_Pos; + USB_OUTEP(epnum)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + } + if ((epint & USB_OTG_DOEPINT_XFRC) == USB_OTG_DOEPINT_XFRC && epnum == 0) { + USB_OUTEP(epnum)->DOEPINT = (USB_OTG_DOEPINT_XFRC); + USB_OUTEP(epnum)->DOEPTSIZ = 1U << USB_OTG_DOEPTSIZ_PKTCNT_Pos | + (USB_OUTEP(epnum)->DOEPCTL & USB_OTG_DOEPCTL_MPSIZ) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos; + USB_OUTEP(epnum)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + } + } + ep_intr >>= 1U; + epnum++; + } + } + + HAL_NVIC_EnableIRQ(OTG_HS_EP1_OUT_IRQn); + HAL_NVIC_EnableIRQ(OTG_HS_EP1_IN_IRQn); + HAL_NVIC_EnableIRQ(OTG_HS_IRQn); +} + +void OTG_HS_EP1_OUT_IRQHandler(void) +{ + gint_flag = USB_OTG_HS->GINTSTS; + HAL_NVIC_DisableIRQ(OTG_HS_EP1_OUT_IRQn); + HAL_NVIC_DisableIRQ(OTG_HS_EP1_IN_IRQn); + HAL_NVIC_DisableIRQ(OTG_HS_IRQn); + USBD_SignalHandler(); +} + +void OTG_HS_EP1_IN_IRQHandler(void) +{ + gint_flag = USB_OTG_HS->GINTSTS; + HAL_NVIC_DisableIRQ(OTG_HS_EP1_OUT_IRQn); + HAL_NVIC_DisableIRQ(OTG_HS_EP1_IN_IRQn); + HAL_NVIC_DisableIRQ(OTG_HS_IRQn); + USBD_SignalHandler(); +} + +void OTG_HS_IRQHandler(void) +{ + gint_flag = USB_OTG_HS->GINTSTS; + HAL_NVIC_DisableIRQ(OTG_HS_EP1_OUT_IRQn); + HAL_NVIC_DisableIRQ(OTG_HS_EP1_IN_IRQn); + HAL_NVIC_DisableIRQ(OTG_HS_IRQn); + USBD_SignalHandler(); +} diff --git a/port/stm32/usbd_stm32f103_devfs.c b/port/stm32/usbd_stm32f103_devfs.c new file mode 100644 index 00000000..0ed54d42 --- /dev/null +++ b/port/stm32/usbd_stm32f103_devfs.c @@ -0,0 +1,549 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * Copyright ©2017 Max Chan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32F103) + +#define USB_EP_SWBUF_TX USB_EP_DTOG_RX +#define USB_EP_SWBUF_RX USB_EP_DTOG_TX + +#define EP_TOGGLE_SET(epr, bits, mask) *(epr) = (*(epr) ^ (bits)) & (USB_EPREG_MASK | (mask)) + +#define EP_TX_STALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_STALL, USB_EPTX_STAT) +#define EP_RX_STALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_STALL, USB_EPRX_STAT) +#define EP_TX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_NAK, USB_EPTX_STAT | USB_EP_DTOG_TX) +#define EP_RX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID, USB_EPRX_STAT | USB_EP_DTOG_RX) +#define EP_DTX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID, USB_EPTX_STAT | USB_EP_DTOG_TX | USB_EP_SWBUF_TX) +#define EP_DRX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID | USB_EP_SWBUF_RX, USB_EPRX_STAT | USB_EP_DTOG_RX | USB_EP_SWBUF_RX) +#define EP_TX_VALID(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID, USB_EPTX_STAT) +#define EP_RX_VALID(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID, USB_EPRX_STAT) + +#define STATUS_VAL(x) (x) + +typedef union _pma_table pma_table; + +#if defined(STM32F302x8) || defined(STM32F302xE) || defined(STM32F303xE) + #if !defined(USB_PMASIZE) + #pragma message "PMA memory size is not defined. Use 768 bytes by default" + #define USB_PMASIZE 0x300 + #endif + #define PMA_STEP 1 + + typedef struct { + uint16_t addr; + uint16_t cnt; + } pma_rec; + + inline static pma_table *EPT(uint8_t ep) { + return (pma_table*)((ep & 0x07) * 8 + USB_PMAADDR); + } + + inline static uint16_t *PMA(uint16_t addr) { + return (uint16_t*)(USB_PMAADDR + addr); + } + +#else + #if !defined(USB_PMASIZE) + #pragma message "PMA memory size is not defined. Use 512 bytes by default" + #define USB_PMASIZE 0x200 + #endif + #define PMA_STEP 2 + + typedef struct { + uint16_t addr; + uint16_t :16; + uint16_t cnt; + uint16_t :16; + } pma_rec; + + inline static pma_table *EPT(uint8_t ep) { + return (pma_table*)((ep & 0x07) * 16 + USB_PMAADDR); + } + + inline static uint16_t *PMA(uint16_t addr) { + return (uint16_t*)(USB_PMAADDR + 2 * addr); + } +#endif + +union _pma_table { + struct { + pma_rec tx; + pma_rec rx; + }; + struct { + pma_rec tx0; + pma_rec tx1; + }; + struct { + pma_rec rx0; + pma_rec rx1; + }; +}; + +/** \brief Helper function. Enables GPIOx for DP. + * Looks ugly. But compiler should optimize this + * to single line + */ +inline static void set_gpiox() { +#if defined(STM32F1) && defined(USBD_DP_PORT) + if (USBD_DP_PORT == GPIOA) {RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; return;} + if (USBD_DP_PORT == GPIOB) {RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; return;} + if (USBD_DP_PORT == GPIOC) {RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; return;} + if (USBD_DP_PORT == GPIOD) {RCC->APB2ENR |= RCC_APB2ENR_IOPDEN; return;} + #if defined(GPIOE) + if (USBD_DP_PORT == GPIOE) {RCC->APB2ENR |= RCC_APB2ENR_IOPEEN; return;} + #endif + #if defined(GPIOF) + if (USBD_DP_PORT == GPIOF) {RCC->APB2ENR |= RCC_APB2ENR_IOPFEN; return;} + #endif +#elif defined(STM32F3) && defined(USBD_DP_PORT) + if (USBD_DP_PORT == GPIOA) {RCC->AHBENR |= RCC_AHBENR_GPIOAEN; return;} + if (USBD_DP_PORT == GPIOB) {RCC->AHBENR |= RCC_AHBENR_GPIOBEN; return;} + if (USBD_DP_PORT == GPIOC) {RCC->AHBENR |= RCC_AHBENR_GPIOCEN; return;} + if (USBD_DP_PORT == GPIOD) {RCC->AHBENR |= RCC_AHBENR_GPIODEN; return;} + #if defined(GPIOE) + if (USBD_DP_PORT == GPIOE) {RCC->AHBENR |= RCC_AHBENR_GPIOEEN; return;} + #endif + #if defined(GPIOF) + if (USBD_DP_PORT == GPIOF) {RCC->AHBENR |= RCC_AHBENR_GPIOFEN; return;} + #endif + #if defined(GPIOG) + if (USBD_DP_PORT == GPIOG) {RCC->AHBENR |= RCC_AHBENR_GPIOGEN; return;} + #endif + #if defined(GPIOH) + if (USBD_DP_PORT == GPIOH) {RCC->AHBENR |= RCC_AHBENR_GPIOHEN; return;} + #endif +#endif + return; +} + +/** \brief Helper function. Returns pointer to the endpoint control register. + */ +inline static volatile uint16_t *EPR(uint8_t ep) { + return (uint16_t*)((ep & 0x07) * 4 + USB_BASE); +} + +/** \brief Helper function. Returns next available PMA buffer. + * + * \param sz uint16_t Requested buffer size. + * \return uint16_t Buffer address for PMA table. + * \note PMA buffers grown from top to bottom like stack. + */ +static uint16_t get_next_pma(uint16_t sz) { + unsigned _result = USB_PMASIZE; + for (int i = 0; i < 8; i++) { + pma_table *tbl = EPT(i); + if ((tbl->tx.addr) && (tbl->tx.addr < _result)) _result = tbl->tx.addr; + if ((tbl->rx.addr) && (tbl->rx.addr < _result)) _result = tbl->rx.addr; + } + return (_result < (0x020 + sz)) ? 0 : (_result - sz); +} + +static uint32_t getinfo(void) { + if (!(RCC->APB1ENR & RCC_APB1ENR_USBEN)) return STATUS_VAL(0); +#if defined(USBD_DP_PORT) && defined(USBD_DP_PIN) + if (USBD_DP_PORT->IDR & _BV(USBD_DP_PIN)) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +#else + return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); +#endif +} + +static void ep_setstall(uint8_t ep, bool stall) { + volatile uint16_t *reg = EPR(ep); + /* ISOCHRONOUS endpoint can't be stalled or unstalled */ + if (USB_EP_ISOCHRONOUS == (*reg & USB_EP_T_FIELD)) return; + /* If it's an IN endpoint */ + if (ep & 0x80) { + /* DISABLED endpoint can't be stalled or unstalled */ + if (USB_EP_TX_DIS == (*reg & USB_EPTX_STAT)) return; + if (stall) { + EP_TX_STALL(reg); + } else { + /* if it's a doublebuffered endpoint */ + if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) { + /* set endpoint to VALID and clear DTOG_TX & SWBUF_TX */ + EP_DTX_UNSTALL(reg); + } else { + /* set endpoint to NAKED and clear DTOG_TX */ + EP_TX_UNSTALL(reg); + } + } + } else { + if (USB_EP_RX_DIS == (*reg & USB_EPRX_STAT)) return; + if (stall) { + EP_RX_STALL(reg); + } else { + /* if it's a doublebuffered endpoint */ + if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) { + /* set endpoint to VALID, clear DTOG_RX, set SWBUF_RX */ + EP_DRX_UNSTALL(reg); + } else { + /* set endpoint to VALID and clear DTOG_RX */ + EP_RX_UNSTALL(reg); + } + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + return (USB_EP_TX_STALL == (USB_EPTX_STAT & *EPR(ep))); + } else { + return (USB_EP_RX_STALL == (USB_EPRX_STAT & *EPR(ep))); + } +} + +static uint8_t connect(bool connect) { +#if defined(USBD_DP_PORT) && defined(USBD_DP_PIN) && defined(STM32F3) + uint32_t _t = USBD_DP_PORT->MODER & ~(0x03 << (2 * USBD_DP_PIN)); + if (connect) { + _t |= (0x01 << (2 * USBD_DP_PIN)); + USBD_DP_PORT->BSRR = (0x0001 << USBD_DP_PIN); + } + USBD_DP_PORT->MODER = _t; +#elif defined(USBD_DP_PORT) && defined(USBD_DP_PIN) && defined(STM32F1) +#if (USBD_DP_PIN < 8) + uint32_t _t = USBD_DP_PORT->CRL & ~(0x0F << (4 * USBD_DP_PIN)); + if (connect) { + _t |= (0x02 << (4 * USBD_DP_PIN)); + USBD_DP_PORT->BSRR = (0x0001 << USBD_DP_PIN); + } else { + _t |= (0x04 << (4 * USBD_DP_PIN)); + } + USBD_DP_PORT->CRL = _t; +#else + uint32_t _t = USBD_DP_PORT->CRH & ~(0x0F << (4 * (USBD_DP_PIN - 8))); + if (connect) { + _t |= (0x02 << (4 * (USBD_DP_PIN - 8))); + USBD_DP_PORT->BSRR = (0x0001 << USBD_DP_PIN); + } else { + _t |= (0x04 << (4 * (USBD_DP_PIN - 8))); + } + USBD_DP_PORT->CRH = _t; +#endif +#endif + return usbd_lane_unk; +} + +static void enable(bool enable) { + if (enable) { + set_gpiox(); + RCC->APB1ENR |= RCC_APB1ENR_USBEN; + RCC->APB1RSTR |= RCC_APB1RSTR_USBRST; + RCC->APB1RSTR &= ~RCC_APB1RSTR_USBRST; + USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM | USB_CNTR_ERRM | +#if !defined(USBD_SOF_DISABLED) + USB_CNTR_SOFM | +#endif + USB_CNTR_SUSPM | USB_CNTR_WKUPM; + } else if (RCC->APB1ENR & RCC_APB1ENR_USBEN) { + RCC->APB1RSTR |= RCC_APB1RSTR_USBRST; + RCC->APB1ENR &= ~RCC_APB1ENR_USBEN; + /* disconnecting DP if configured */ + connect(0); + } +} + +static void setaddr (uint8_t addr) { + USB->DADDR = USB_DADDR_EF | addr; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + volatile uint16_t *reg = EPR(ep); + pma_table *tbl = EPT(ep); + /* epsize must be 2-byte aligned */ + epsize = (~0x01U) & (epsize + 1); + + switch (eptype) { + case USB_EPTYPE_CONTROL: + *reg = USB_EP_CONTROL | (ep & 0x07); + break; + case USB_EPTYPE_ISOCHRONUS: + *reg = USB_EP_ISOCHRONOUS | (ep & 0x07); + break; + case USB_EPTYPE_BULK: + *reg = USB_EP_BULK | (ep & 0x07); + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + *reg = USB_EP_BULK | USB_EP_KIND | (ep & 0x07); + break; + default: + *reg = USB_EP_INTERRUPT | (ep & 0x07); + break; + } + /* if it TX or CONTROL endpoint */ + if ((ep & 0x80) || (eptype == USB_EPTYPE_CONTROL)) { + uint16_t _pma; + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->tx.addr = _pma; + tbl->tx.cnt = 0; + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->tx1.addr = _pma; + tbl->tx1.cnt = 0; + EP_DTX_UNSTALL(reg); + } else { + EP_TX_UNSTALL(reg); + } + } + if (!(ep & 0x80)) { + uint16_t _rxcnt; + uint16_t _pma; + if (epsize > 62) { + /* using 32-byte blocks. epsize must be 32-byte aligned */ + epsize = (~0x1FU) & (epsize + 0x1FU); + _rxcnt = 0x8000U - 0x400U + (epsize << 5); + } else { + _rxcnt = epsize << 9; + } + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->rx.addr = _pma; + tbl->rx.cnt = _rxcnt; + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->rx0.addr = _pma; + tbl->rx0.cnt = _rxcnt; + EP_DRX_UNSTALL(reg); + } else { + EP_RX_UNSTALL(reg); + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + pma_table *ept = EPT(ep); + *EPR(ep) &= ~USB_EPREG_MASK; + ept->rx.addr = 0; + ept->rx.cnt = 0; + ept->tx.addr = 0; + ept->tx.cnt = 0; +} + +static uint16_t pma_read (uint8_t *buf, uint16_t blen, pma_rec *rx) { + uint16_t tmp; + uint16_t *pma = PMA(rx->addr); + uint16_t rxcnt = rx->cnt & 0x03FF; + rx->cnt &= ~0x3FF; + for(int idx = 0; idx < rxcnt; idx++) { + if ((idx & 0x01) == 0) { + tmp = *pma; + pma += PMA_STEP; + } + if (idx < blen) { + buf[idx] = tmp & 0xFF; + tmp >>= 8; + } else { + return blen; + } + } + return rxcnt; +} + +static int32_t ep_read(uint8_t ep, void *buf, uint16_t blen) { + pma_table *tbl = EPT(ep); + volatile uint16_t *reg = EPR(ep); + switch (*reg & (USB_EPRX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) { + /* doublebuffered bulk endpoint */ + case (USB_EP_RX_VALID | USB_EP_BULK | USB_EP_KIND): + /* switching SWBUF if EP is NAKED */ + switch (*reg & (USB_EP_DTOG_RX | USB_EP_SWBUF_RX)) { + case 0: + case (USB_EP_DTOG_RX | USB_EP_SWBUF_RX): + *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_RX; + break; + default: + break; + } + if (*reg & USB_EP_SWBUF_RX) { + return pma_read(buf, blen, &(tbl->rx1)); + } else { + return pma_read(buf, blen, &(tbl->rx0)); + } + /* isochronous endpoint */ + case (USB_EP_RX_VALID | USB_EP_ISOCHRONOUS): + if (*reg & USB_EP_DTOG_RX) { + return pma_read(buf, blen, &(tbl->rx1)); + } else { + return pma_read(buf, blen, &(tbl->rx0)); + } + /* regular endpoint */ + case (USB_EP_RX_NAK | USB_EP_BULK): + case (USB_EP_RX_NAK | USB_EP_CONTROL): + case (USB_EP_RX_NAK | USB_EP_INTERRUPT): + { + int32_t res = pma_read(buf, blen, &(tbl->rx)); + /* setting endpoint to VALID state */ + EP_RX_VALID(reg); + return res; + } + /* invalid or not ready */ + default: + return -1; + } +} + +static void pma_write(const uint8_t *buf, uint16_t blen, pma_rec *tx) { + uint16_t *pma = PMA(tx->addr); + uint16_t tmp = 0; + tx->cnt = blen; + for (int idx=0; idx < blen; idx++) { + tmp |= buf[idx] << ((idx & 0x01) ? 8 : 0); + if ((idx & 0x01) || (idx + 1) == blen) { + *pma = tmp; + pma += PMA_STEP; + tmp = 0; + } + } +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + pma_table *tbl = EPT(ep); + volatile uint16_t *reg = EPR(ep); + switch (*reg & (USB_EPTX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) { + /* doublebuffered bulk endpoint */ + case (USB_EP_TX_NAK | USB_EP_BULK | USB_EP_KIND): + if (*reg & USB_EP_SWBUF_TX) { + pma_write(buf, blen, &(tbl->tx1)); + } else { + pma_write(buf, blen, &(tbl->tx0)); + } + *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_TX; + break; + /* isochronous endpoint */ + case (USB_EP_TX_VALID | USB_EP_ISOCHRONOUS): + if (!(*reg & USB_EP_DTOG_TX)) { + pma_write(buf, blen, &(tbl->tx1)); + } else { + pma_write(buf, blen, &(tbl->tx0)); + } + break; + /* regular endpoint */ + case (USB_EP_TX_NAK | USB_EP_BULK): + case (USB_EP_TX_NAK | USB_EP_CONTROL): + case (USB_EP_TX_NAK | USB_EP_INTERRUPT): + pma_write(buf, blen, &(tbl->tx)); + EP_TX_VALID(reg); + break; + /* invalid or not ready */ + default: + return -1; + } + return blen; +} + +static uint16_t get_frame (void) { + return USB->FNR & USB_FNR_FN; +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint8_t _ev, _ep; + uint16_t _istr = USB->ISTR; + _ep = _istr & USB_ISTR_EP_ID; + + if (_istr & USB_ISTR_CTR) { + volatile uint16_t *reg = EPR(_ep); + if (*reg & USB_EP_CTR_TX) { + *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_TX); + _ep |= 0x80; + _ev = usbd_evt_eptx; + } else { + *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_RX); + _ev = (*reg & USB_EP_SETUP) ? usbd_evt_epsetup : usbd_evt_eprx; + } + } else if (_istr & USB_ISTR_RESET) { + USB->ISTR &= ~USB_ISTR_RESET; + USB->BTABLE = 0; + for (int i = 0; i < 8; i++) { + ep_deconfig(i); + } + _ev = usbd_evt_reset; +#if !defined(USBD_SOF_DISABLED) + } else if (_istr & USB_ISTR_SOF) { + _ev = usbd_evt_sof; + USB->ISTR &= ~USB_ISTR_SOF; +#endif + } else if (_istr & USB_ISTR_WKUP) { + _ev = usbd_evt_wkup; + USB->CNTR &= ~USB_CNTR_FSUSP; + USB->ISTR &= ~USB_ISTR_WKUP; + } else if (_istr & USB_ISTR_SUSP) { + _ev = usbd_evt_susp; + USB->CNTR |= USB_CNTR_FSUSP; + USB->ISTR &= ~USB_ISTR_SUSP; + } else if (_istr & USB_ISTR_ERR) { + USB->ISTR &= ~USB_ISTR_ERR; + _ev = usbd_evt_error; + } else { + return; + } + callback(dev, _ev, _ep); +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x08)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_devfs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32F103 diff --git a/port/stm32/usbd_stm32f103_devfs_asm.S b/port/stm32/usbd_stm32f103_devfs_asm.S new file mode 100644 index 00000000..ee7bcf23 --- /dev/null +++ b/port/stm32/usbd_stm32f103_devfs_asm.S @@ -0,0 +1,866 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined (__ASSEMBLER__) + #define __ASSEMBLER__ +#endif + +#include "usb.h" +#if defined(USBD_STM32F103) +#include "memmap.inc" + +#define EP_SETUP 0x0800 +#define EP_TYPE 0x0600 +#define EP_KIND 0x0100 +#define EP_ADDR 0x000F + +#define EP_RX_CTR 0x8000 +#define EP_RX_DTOG 0x4000 +#define EP_RX_STAT 0x3000 +#define EP_RX_SWBUF 0x0040 + +#define EP_RX_DIS 0x0000 +#define EP_RX_STAL 0x1000 +#define EP_RX_NAK 0x2000 +#define EP_RX_VAL 0x3000 + +#define EP_TX_CTR 0x0080 +#define EP_TX_DTOG 0x0040 +#define EP_TX_STAT 0x0030 +#define EP_TX_SWBUF 0x4000 + +#define EP_TX_DIS 0x0000 +#define EP_TX_STAL 0x0010 +#define EP_TX_NAK 0x0020 +#define EP_TX_VAL 0x0030 + +#if defined(STM32F302x8) || defined(STM32F302xE) || defined(STM32F303xE) + #define RXADDR0 0x00 + #define RXCOUNT0 0x02 + #define RXADDR1 0x04 + #define RXCOUNT1 0x06 + #define USB_PMASZ 0x300 + #define USB_PMASTP 0x02 + #define EPT_SHIFT 3 + +#else + #define RXADDR0 0x00 + #define RXCOUNT0 0x04 + #define RXADDR1 0x08 + #define RXCOUNT1 0x0C + #define USB_PMASZ 0x200 + #define USB_PMASTP 0x04 + #define EPT_SHIFT 4 + +#endif + +#define TXADDR0 RXADDR0 +#define TXCOUNT0 RXCOUNT0 +#define TXADDR1 RXADDR1 +#define TXCOUNT1 RXCOUNT1 + +#define TXADDR RXADDR0 +#define TXCOUNT RXCOUNT0 +#define RXADDR RXADDR1 +#define RXCOUNT RXCOUNT1 + +#define EP_NOTOG (EP_RX_CTR | EP_TX_CTR | EP_SETUP | EP_TYPE | EP_KIND | EP_ADDR) + +#define TGL_SET(mask, bits) ((EP_NOTOG | (mask))<<16 | (bits)) + +#define TX_STALL TGL_SET(EP_TX_STAT, EP_TX_STAL) +#define RX_STALL TGL_SET(EP_RX_STAT, EP_RX_STAL) +#define TX_USTALL TGL_SET(EP_TX_STAT | EP_TX_DTOG, EP_TX_NAK) +#define RX_USTALL TGL_SET(EP_RX_STAT | EP_RX_DTOG, EP_RX_VAL) +#define DTX_USTALL TGL_SET(EP_TX_STAT | EP_TX_DTOG | EP_TX_SWBUF, EP_TX_VAL) +#define DRX_USTALL TGL_SET(EP_RX_STAT | EP_RX_DTOG | EP_RX_SWBUF, EP_RX_VAL | EP_RX_SWBUF) + + +#if (USBD_DP_PORT == GPIOA) + #define RCC_GPIOxEN RCC_GPIOAEN +#elif (USBD_DP_PORT == GPIOB) + #define RCC_GPIOxEN RCC_GPIOBEN +#elif (USBD_DP_PORT == GPIOC) + #define RCC_GPIOxEN RCC_GPIOCEN +#elif (USBD_DP_PORT == GPIOD) + #define RCC_GPIOxEN RCC_GPIODEN +#elif (USBD_DP_PORT == GPIOE) + #define RCC_GPIOxEN RCC_GPIOEEN +#elif (USBD_DP_PORT == GPIOF) + #define RCC_GPIOxEN RCC_GPIOFEN +#elif (USBD_DP_PORT == GPIOG) + #define RCC_GPIOxEN RCC_GPIOGEN +#elif (USBD_DP_PORT == GPIOH) + #define RCC_GPIOxEN RCC_GPIOHEN +#else + #warning "No USB DP control used for F303" + #undef USBD_DP_PORT + #undef USBD_DP_PIN +#endif + + .syntax unified + .cpu cortex-m3 + .thumb + + .section .rodata.usbd_devfs_asm + .align 4 + .globl usbd_devfs_asm +usbd_devfs_asm: + .long _getinfo + .long _enable + .long _connect + .long _setaddr + .long _ep_config + .long _ep_deconfig + .long _ep_read + .long _ep_write + .long _ep_setstall + .long _ep_isstalled + .long _evt_poll + .long _get_frame + .long _get_serial_desc + .size usbd_devfs_asm, . - usbd_devfs_asm + + .text + .align 2 + .thumb_func + .type _get_serial_desc, %function + +/* uint16_t get_serial_desc (void *buffer) + * R0 <- buffer for the string descriptor + * descrpitor size -> R0 + */ +_get_serial_desc: + push {r4, r5, lr} + movs r1, #18 //descriptor size 18 bytes + strb r1, [r0] + movs r1, #0x03 //DTYPE_STRING + strb r1, [r0, #0x01] + ldr r5, .L_uid_base //UID3 this is the serial number + ldr r4, .L_fnv1a_offset //FNV1A offset + ldr r2, [r5, 0x00] //UID0 + bl .L_fnv1a + ldr r2, [r5, 0x04] //UID1 + bl .L_fnv1a + ldr r2, [r5, 0x08] //UID2 + bl .L_fnv1a + movs r3, #28 +.L_gsn_loop: + lsrs r1, r4, r3 + and r1, #0x0F + cmp r1, #0x09 + ite gt + addgt r1, #55 + addle r1, #48 +.L_gsn_store: + adds r0, #0x02 + strb r1, [r0] + lsrs r1, #0x08 + strb r1, [r0, #0x01] + subs r3, #0x04 + bpl .L_gsn_loop + movs r0, #18 + pop {r4, r5, pc} + +.L_fnv1a: + movs r3, #0x04 +.L_fnv1a_loop: + uxtb r1, r2 + eors r4, r1 + ldr r1, .L_fnv1a_prime //FNV1A prime + muls r4, r1 + lsrs r2, #0x08 + subs r3, #0x01 + bne .L_fnv1a_loop + bx lr + + .align 2 +.L_uid_base: .long UID_BASE +.L_fnv1a_offset: .long 2166136261 +.L_fnv1a_prime: .long 16777619 + + .size _get_serial_desc, . - _get_serial_desc + + .thumb_func + .type _connect, %function +_connect: +#if defined(USBD_DP_PORT) && defined(USBD_DP_PIN) && defined(STM32F1) + #if (USBD_DP_PIN < 8) + #define GPIO_CRx GPIO_CRL + #define DP_PIN (4 * USBD_DP_PIN) + #else + #define GPIO_CRx GPIO_CRH + #define DP_PIN (4 * (USBD_DP_PIN - 8)) + #endif + ldr r3, =USBD_DP_PORT + ldr r2, [r3, #GPIO_CRx] + movs r1, #0x0F + bics r2, r2, r1, LSL #DP_PIN + movs r1, #0x04 + cbz r0, .L_store + movs r1, #0x01 + lsls r1, #USBD_DP_PIN + str r1, [r3, #GPIO_BSRR] + movs r1, #0x02 +.L_store: + orrs r2, r2, r1, LSL #DP_PIN + str r2, [r3, #GPIO_CRx] +#elif defined(USBD_DP_PORT) && defined(USBD_DP_PIN) && defined(STM32F3) + ldr r3, =USBD_DP_PORT + ldr r2, [r3, #GPIO_MODER] + movs r1, #0x03 + bics r2, r2, r1, LSL #(2 * USBD_DP_PIN) + movs r1, #0x01 + cbz r0, .L_store + orrs r2, r2, r1, LSL #(2 * USBD_DP_PIN) + lsls r1, #USBD_DP_PIN + str r1, [r3, #GPIO_BSRR] +.L_store: + str r2, [r3, #GPIO_MODER] +#endif + movs r0, #usbd_lane_unk + bx lr + .size _connect, . - _connect + + .thumb_func + .type _setaddr, %function +_setaddr: + ldr r1, =USB_REGBASE + adds r0, #0x80 + strh r0, [r1, #USB_DADDR] //USB->DADDR + bx lr + .size _setaddr, . - _setaddr + + .thumb_func + .type _get_frame, %function +_get_frame: + ldr r0, =USB_REGBASE + ldrh r0, [r0, #USB_FNR] //FNR + lsls r0, #21 + lsrs r0, #21 + bx lr + .size _get_frame, . - _get_frame + + .thumb_func + .type _enable, %function +_enable: + ldr r2, =RCC_BASE //RCC + movs r1, #0x01 + lsls r3, r1, #23 //USBEN or USBRST + cbz r0, .L_disable +.L_enable: +/* enabling and resetting USB peripheral */ +/* enabling DP control GPIO port */ +#if defined(USBD_DP_PORT) && defined(USBD_DP_PIN) && defined(STM32F3) + lsls r1, #RCC_GPIOxEN + ldr r0, [r2, #RCC_AHBENR] + orrs r0, r1 + str r0, [r2, #RCC_AHBENR] +#elif defined(USBD_DP_PORT) && defined(USBD_DP_PIN) + lsls r1, #RCC_GPIOxEN + ldr r0, [r2, #RCC_APB2ENR] + orrs r0, r1 + str r0, [r2, #RCC_APB2ENR] +#endif + ldr r1, =USB_REGBASE + ldr r0, [r2, #RCC_APB1ENR] + orrs r0, r3 + str r0, [r2, #RCC_APB1ENR] //RCC->APB1ENR |= USBEN + ldr r0, [r2, #RCC_APB1RSTR] + orrs r0, r3 + str r0, [r2, #RCC_APB1RSTR] //RCC->APB1RSTR |= USBRST + bics r0, r3 + str r0, [r2, #RCC_APB1RSTR] //RCC->APB1RSTR &= ~USBRST +/* setting up USB CNTR */ +#if !defined(USBD_SOF_DISABLED) + movs r0, #0xBE // CTRM | ERRM | WKUPM | SUSPM | RESETM | SOFM +#else + movs r0, #0xBC // CTRM | ERRM | WKUPM | SUSPM | RESETM +#endif + lsls r0, #0x08 + strh r0, [r1, #USB_CNTR] //set USB->CNTR + bx lr +.L_disable: + ldr r0, [r2, #RCC_APB1ENR] + tst r0, r3 + beq .L_enable_end // usb is already disabled +/* disabling USB peripheral */ + bics r0, r3 + str r0, [r2, #RCC_APB1ENR] + movs r0, #0 + b _connect // jump to disconnect subroutine +.L_enable_end: + bx lr + .size _enable, . - _enable + + .thumb_func + .type _getinfo, %function +_getinfo: + movs r0, #0 + ldr r2, =RCC_BASE + ldr r1, [r2, #RCC_APB1ENR] + lsrs r1, #24 //USBEN -> CF + bcc .L_getinfo_end + adds r0, #USBD_HW_ENABLED +#if defined(USBD_DP_PORT) && defined(USBD_DP_PIN) + ldr r2, =USBD_DP_PORT + ldr r1, [r2, #GPIO_IDR] + lsrs r1, #USBD_DP_PIN //USBD_DP_PIN -> CF + bcc .L_getinfo_end +#endif + adds r0, #USBD_HW_SPEED_FS +.L_getinfo_end: + bx lr + .size _getinfo, . - _getinfo + + .thumb_func + .type _ep_setstall, %function +/*void ep_settall(uint8_t ep, bool stall) + * in R0 <- endpoint number + * in R1 <- 0 if unstall, !0 if stall + */ +_ep_setstall: + push {r4, lr} + lsls r2, r0, #28 + lsrs r2, #26 + ldr r3, =USB_EPBASE + adds r3, r2 // epr -> r3 + movs r2, 0x30 // TX_STAT_MASK -> r2 + ldrh r4, [r3] + lsls r4, #21 + lsrs r4, #29 // EP_TYPE | EP_KIND -> R4 LSB + cmp r4, #0x04 // ISO ? + beq .L_eps_exit + cmp r0, #0x80 + blo .L_eps_rx +.L_eps_tx: + ldr r0, =TX_STALL //stall TX + cmp r1, #0x00 + bne .L_eps_reg_set +.L_eps_tx_unstall: + ldr r0, =DTX_USTALL //unstall dblbulk or iso TX (VALID and clr DTOG_TX & SWBUF_TX) + cmp r4, #0x01 // if doublebuffered bulk endpoint + beq .L_eps_reg_set + ldr r0, =TX_USTALL // unstall other TX (NAKED + clr DTOG_TX) + b .L_eps_reg_set +.L_eps_rx: + lsls r2, #8 // RX_STAT_MASK -> R2 + ldr r0,=RX_STALL //stall RX + cmp r1, #0x00 + bne .L_eps_reg_set +.L_eps_rx_unstall: + ldr r0, =DRX_USTALL //unstall dblbulk or iso (VALID. clr DTOG_RX set SWBUF_RX) + cmp r4, #0x01 // if dblbulk + beq .L_eps_reg_set + ldr r0, =RX_USTALL // unstall other RX (VALID + clr +/* R0 - mask and toggle bits + * R2 - mask for STAT bits + * R3 - endpoint register pointer + */ +.L_eps_reg_set: + ldrh r1, [r3] // *epr -> r1 + ands r2, r1 // check if endpoint disabled + beq .L_eps_exit // do nothing + eors r1, r0 + lsrs r0, #16 + ands r1, r0 + strh r1, [r3] +.L_eps_exit: + pop {r4, pc} + .size _ep_setstall, . - _ep_setstall + + + .thumb_func + .type _ep_isstalled, %function +/* bool ep_isstalled(uint8t ep) */ +_ep_isstalled: + ldr r1, =USB_EPBASE + lsls r2, r0, #28 + lsrs r2, #26 + ldr r1, [r1, r2] + lsls r1, #17 + cmp r0, #0x80 + bhs .L_eis_check + lsls r1, #8 +.L_eis_check: + lsrs r1, r1, #28 + subs r1, #0x01 + subs r0, r1, #0x01 + sbcs r1, r1 + rsbs r0, r1, #0 + bx lr + .size _ep_isstalled, . - _ep_isstalled + + + .thumb_func + .type _ep_read, %function +/* int32_t _ep_read(uint8_t ep, void *buf, uint16_t blen) + * in R0 <- endpoint + * in R1 <- *buffer + * in R2 <- length of the buffer + * out length of the recieved data -> R0 or 0 on error + */ +_ep_read: + push {r4, r5, r6, lr} + ldr r3, =USB_EPBASE + ldr r6, =USB_PMABASE + lsls r0, #28 + add r3, r3, r0, LSR #26 //*EPR -> R3 + add r4, r6, r0, LSR (28 - EPT_SHIFT) //*EPT -> R4 + ldrh r5, [r3] // reading epr +/* validating endpoint */ + movs r0, #0x37 + ands r0, r0, r5, LSR #8 + cmp r0, #0x34 // (OK) RX_VALID + ISO + beq .L_epr_iso + cmp r0, #0x31 // (OK) RX_VALID + DBLBULK + beq .L_epr_dbl + cmp r0, #0x20 // (OK) RX_NAKED + BULK + beq .L_epr_sngl + cmp r0, #0x22 // (OK) RX_NAKED + CTRL + beq .L_epr_sngl + cmp r0, #0x26 // (OK) RX_NAKED + INTR + beq .L_epr_sngl + movs r0, #0xFF // endpoint contains no valid data + sxtb r0, r0 + b .L_epr_exit +/* processing */ +.L_epr_dbl: + eors r0, r5, r5, LSR #8 + lsrs r0, #7 // SW_RX ^ DTOG_RX -> CF + bcs .L_epr_notog // jmp if SW_RX != DTOG_RX (VALID) + ldr r0, =EP_NOTOG + ands r5, r0 + adds r5, #EP_RX_SWBUF + strh r5, [r3] // toggling SW_RX +.L_epr_notog: + ldrh r5, [r3] + lsls r5, #8 // shift SW_RX to DTOG_RX +.L_epr_iso: + lsrs r5, #15 // DTOG_RX -> CF + bcs .L_epr_sngl + subs r4, #RXADDR1 // set RXADDR0 +.L_epr_sngl: + ldrh r0, [r4, #RXCOUNT] + lsrs r5, r0, #0x0A + lsls r5, #0x0A // r5 = r5 & ~0x03FF + strh r5, [r4, #RXCOUNT] + eors r0, r5 // r0 &= 0x3FF (RX count) + ldrh r5, [r4, #RXADDR] + adds r5, r4, r5, LSL (EPT_SHIFT - 3) + cmp r2, r0 + blo .L_epr_read + mov r2, r0 // if buffer is larger +.L_epr_read: + cmp r2, #1 + blo .L_epr_read_end + ldrh r4, [r5] + strb r4, [r1] + beq .L_epr_read_end + lsrs r4, #8 + strb r4, [r1, #1] + adds r1, #2 + adds r5, #USB_PMASTP + subs r2, #2 + bhi .L_epr_read +.L_epr_read_end: + ldrh r5, [r3] // reload EPR + lsls r1, r5, #21 + lsrs r1, #29 + cmp r1, #0x04 + beq .L_epr_exit // ep is iso. no needs to set it to valid + cmp r1, #0x01 + beq .L_epr_exit // ep is dblbulk. no needs to set it to valid + ldr r2, =TGL_SET(EP_RX_STAT , EP_RX_VAL) + eors r5, r2 + and r5, r5, r2, LSR #16 + strh r5, [r3] // set ep to VALID state +.L_epr_exit: + pop {r4, r5, r6, pc} + .size _ep_read, . - _ep_read + + + .thumb_func + .type _ep_write, %function +/* int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) + * R0 -> endpoint + * R1 -> *buffer + * R2 -> data length + * result -> R0 + */ +_ep_write: + push {r4, r5, r6, lr} + ldr r3, =USB_EPBASE + ldr r6, =USB_PMABASE + lsls r0, #28 + add r3, r3, r0, LSR #26 //*EPR -> R3 + add r4, r6, r0, LSR (28 - EPT_SHIFT) //*EPT -> R4 + ldrh r5, [r3] // reading epr + movs r0, #0x73 + and r0, r0, r5, LSR #4 + cmp r0, #0x43 // (OK) TX_VALID + ISO + beq .L_epw_iso + cmp r0, #0x12 // (OK) TX_NAK + DBLBULK + beq .L_epw_dbl + cmp r0, #0x02 // (OK) TX_NAK + BULK + beq .L_epw_sngl + cmp r0, #0x22 // (OK) TX_NAK + CONTROL + beq .L_epw_sngl + cmp r0, #0x62 // (OK) TX_NAK + INTERRUPT + beq .L_epw_sngl + movs r0, #0xFF // -1 error + sxtb r0, r0 + b .L_epw_exit +.L_epw_dbl: + mvns r5, r5 + lsrs r5, #8 // ~SWBUF_TX -> DTOG_TX +.L_epw_iso: + lsrs r5, #7 // DTOG_TX -> CF + bcs .L_epw_sngl + adds r4, #RXADDR1 // TXADDR1 -> R4 +.L_epw_sngl: + strh r2, [r4, #TXCOUNT] + mov r0, r2 // save count for return + ldrh r5, [r4, #TXADDR] + adds r5, r6, r5, LSL (EPT_SHIFT - 3) +.L_epw_write: + cmp r2, #1 + blo .L_epw_write_end + ldrb r4, [r1] + beq .L_epw_store + ldrb r6, [r1, #1] + orr r4, r4, r6, LSL #8 +.L_epw_store: + strh r4, [r5] + adds r5, #USB_PMASTP + adds r1, #2 + subs r2, #2 + bhi .L_epw_write +.L_epw_write_end: + ldrh r5, [r3] // reload EPR + lsls r1, r5, #21 + lsrs r1, #29 + cmp r1, #0x04 + beq .L_epw_exit // isochronous ep. do nothing + ldr r2, =TGL_SET(EP_TX_STAT, EP_TX_VAL) + cmp r1, #0x01 + bne .L_epw_setstate // NOT a doublebuffered bulk + ldr r2, =TGL_SET(EP_TX_SWBUF, EP_TX_SWBUF) + bics r5, r2 // clear TX_SWBUF +.L_epw_setstate: + eors r5, r2 + and r5, r5, r2, LSR #16 + strh r5, [r3] +.L_epw_exit: + pop {r4, r5, r6, pc} + .size _ep_write, .- _ep_write + + + +/* internal function */ +/* requester size passed in R2 */ +/* result returns in R0 CF=1 if OK*/ + +_get_next_pma: + push {r1, r3, r4, lr} + movs r1, #16 + ldr r3, =USB_PMASZ + ldr r0, =USB_PMABASE +.L_gnp_chkaddr: + ldrh r4, [r0, #0] //txaddr + tst r4, r4 + beq .L_gnp_nxtaddr + cmp r3, r4 + blo .L_gnp_nxtaddr + mov r3, r4 +.L_gnp_nxtaddr: + adds r0, #RXADDR1 + subs r1, #1 + bne .L_gnp_chkaddr + subs r0, r3, r2 + blo .L_gnp_exit + cmp r0, #0x20 //check for the pma table overlap +.L_gnp_exit: + pop {r1, r3, r4, pc} + .size _get_next_pma, . - _get_next_pma + + .thumb_func + .type _ep_config, %function +/* bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) + * R0 <- ep + * R1 <- eptype + * R2 <- epsize + * result -> R0 + */ +_ep_config: + push {r4, r5, lr} + movs r3, #0x01 + adds r2, r3 + bics r2, r3 //R2 -> halfword aligned epsize + cmp r1, #0x06 //DBLBULK (0x01) + beq .L_epc_settype + movs r3, #0x00 + cmp r1, #0x02 //BULK + beq .L_epc_settype + movs r3, #0x02 + cmp r1, #0x00 //CONTROL + beq .L_epc_settype + movs r3, #0x04 + cmp r1, #0x01 //ISO + beq .L_epc_settype + movs r3, #0x06 //INTERRUPT +.L_epc_settype: + lsls r3, #8 + movs r4, #0x07 + ands r4, r0 + orrs r3, r4 + lsls r4, #2 + ldr r5, =USB_EPBASE + strh r3, [r5, r4] //setup EPTYPE EPKIND EPADDR + cmp r1, #0x00 // is a control ep ? + beq .L_epc_setuptx + cmp r0, #0x80 + blo .L_epc_setuprx +.L_epc_setuptx: + ldr r5, =USB_PMABASE + adds r5, r5, r4, LSL (EPT_SHIFT - 2) + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #TXADDR] //store txaddr or txaddr0 + movs r0, #0x00 + strh r0, [r5, #TXCOUNT] //store txcnt + cmp r1, #0x06 // is DBLBULK + beq .L_epc_txdbl + ldr r3, =TX_USTALL //set state NAKED , clr DTOG_TX + cmp r1, #0x01 // is ISO + bne .L_epc_txsetstate // +.L_epc_txdbl: + ldr r3, =DTX_USTALL //set state VALID clr DTOG_TX & SWBUF_TX + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #TXADDR1] //store txaddr1 + movs r0, #0x00 + strh r0, [r5, #TXCOUNT1] //store txcnt +.L_epc_txsetstate: + ldr r5, =USB_EPBASE + ldrh r0, [r5, r4] + eors r0, r3 + lsrs r3, #16 + ands r0, r3 + strh r0, [r5, r4] + cmp r1, #0x00 //is a control ep ? + bne .L_epc_exit +.L_epc_setuprx: + movs r3, r2 + cmp r2, #62 + bls .L_epc_rxbb + movs r3, #0x1F + adds r2, r3 + bics r2, r3 + lsrs r3, r2, #4 + adds r3, #0x3E +.L_epc_rxbb: + lsls r3, #9 + ldr r5, =USB_PMABASE + adds r5, r5, r4, LSL (EPT_SHIFT - 2) +/* RX or RX1 */ + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #RXADDR] + strh r3, [r5, #RXCOUNT] + ldr r0, =RX_USTALL +/* check if doublebuffered */ + cmp r1, 0x06 //if dblbulk + beq .L_epc_rxdbl + cmp r1, 0x01 // iso + bne .L_epc_rxsetstate +.L_epc_rxdbl: + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #RXADDR0] //store rxaddr0 + strh r3, [r5, #RXCOUNT0] //store rxcnt0 + ldr r0, =DRX_USTALL +.L_epc_rxsetstate: + ldr r5, =USB_EPBASE + ldrh r3, [r5, r4] + eors r3, r0 + lsrs r0, #16 + ands r3, r0 + strh r3, [r5, r4] +.L_epc_exit: + movs r0, #0x01 + pop {r4, r5, pc} +.L_epc_fail: + movs r0, #0x00 + pop {r4, r5, pc} + + .size _ep_config, . - _ep_config + + .thumb_func + .type _ep_deconfig, %function + + +/* void ep_deconfig( uint8_t ep) + * R0 <- ep + */ +_ep_deconfig: + lsls r1, r0, #28 + lsrs r1, #26 + ldr r2, =USB_EPBASE + ldr r3, =USB_PMABASE + adds r2, r1 + adds r3, r3, r1, LSL (EPT_SHIFT - 2) +/* clearing endpoint register */ + ldr r1, =EP_NOTOG + ldrh r0, [r2] + bics r0, r1 + strh r0, [r2] +/* clearing PMA data */ + movs r0, #0x00 + strh r0, [r3, #TXADDR] + strh r0, [r3, #TXCOUNT] + strh r0, [r3, #RXADDR] + strh r0, [r3, #RXCOUNT] + bx lr + + .size _ep_deconfig, . - _ep_config + + + + +#define ISTRSHIFT 8 +#define ISTRBIT(bit) ((1 << bit) >> ISTRSHIFT) + + + .thumb_func + .type _evt_poll, %function +/*void evt_poll(usbd_device *dev, usbd_evt_callback callback)*/ +_evt_poll: + push {r0, r1, r4, r5} + ldr r3, =USB_REGBASE + ldrh r0, [r3, #4] //USB->ISTR -> R2 +/* ep_index -> R2 */ + movs r2, 0x07 + ands r2, r0 +/* checking USB->ISTR for events */ +#if !defined(USBD_SOF_DISABLED) + lsrs r1, r0, #10 //SOFM -> CF + bcs .L_ep_sofm +#endif + lsrs r1, r0, #11 //RESETM -> CF + bcs .L_ep_resetm + lsrs r1, r0, #16 //CTRM -> CF + bcs .L_ep_ctrm + lsrs r1, r0, #14 //ERRM -> CF + bcs .L_ep_errm + lsrs r1, r0, #13 //WKUPM -> CF + bcs .L_ep_wkupm + lsrs r1, r0, #12 //SUSPM -> CF + bcs .L_ep_suspm + /* exit with no callback */ + pop {r0, r1, r4 , r5} + bx lr + +.L_ep_ctrm: + movs r5, #0x80 // CTR_TX mask to R5 + ldr r0,=USB_EPBASE + add r0, r0, r2, LSL #2 // R0 ep register address + ldrh r4, [r0] // R4 EPR valur + lsrs r3, r4, #8 // CTR_TX -> CF + bcc .L_ep_ctr_rx +/* CTR_TX event */ + movs r1, #usbd_evt_eptx + orrs r2, r5 // set endpoint tx + b .L_ep_clr_ctr +.L_ep_ctr_rx: +/* CTR_RX RX or SETUP */ + lsls r5, #0x08 // set mask to CRT_RX + movs r1, #usbd_evt_eprx + lsls r3, r4, #21 //SETUP -> CF + bcc .L_ep_clr_ctr + movs r1, #usbd_evt_epsetup +.L_ep_clr_ctr: + bics r4, r5 //clear CTR flag + ldr r5, =EP_NOTOG + ands r4, r5 + strh r4, [r0] // store + b .L_ep_callback +.L_ep_errm: + movs r1, #usbd_evt_error + movs r4, #ISTRBIT(13) + b .L_ep_clristr +#if !defined(USBD_SOF_DISABLED) +.L_ep_sofm: + movs r1, #usbd_evt_sof + movs r4, #ISTRBIT(9) + b .L_ep_clristr +#endif +.L_ep_wkupm: + ldrh r1, [r3, #0] //R1 USB->CNTR + movs r5, #0x08 + bics r1, r5 //clr FSUSP + strh r1, [r3, #0] //USB->CNTR R2 + movs r1, #usbd_evt_wkup + movs r4, #ISTRBIT(12) + b .L_ep_clristr + +.L_ep_suspm: + ldrh r1, [r3, #0] //R1 USB->CNTR + movs r5, #0x08 + orrs r1, r5 //set FSUSP + strh r1, [r3, #0] //USB->CNTR R2 + movs r1, #usbd_evt_susp + movs r4, #ISTRBIT(11) + b .L_ep_clristr + +/* do reset routine */ +.L_ep_resetm: + movs r1, #7 + ldr r2, =USB_EPBASE + ldr r0, =USB_PMABASE + ldr r5, =EP_NOTOG +.L_ep_reset_loop: + ldrh r4, [r2] + bics r4, r5 + strh r4, [r2] + movs r4, #0 + strh r4, [r0, #TXADDR] + strh r4, [r0, #TXCOUNT] + strh r4, [r0, #RXADDR] + strh r4, [r0, #RXCOUNT] + adds r2, #0x04 + adds r0, #(0x04 * USB_PMASTP) + subs r1, #1 + bpl .L_ep_reset_loop + movs r2, #0x00 + strh r2, [r3, #0x10] // 0 -> USB->BTABLE + movs r1, #usbd_evt_reset + movs r4, #ISTRBIT(10) +.L_ep_clristr: + lsls r4, #ISTRSHIFT + ldrh r0, [r3, #4] + bics r0, r4 + strh r0, [r3, #4] +.L_ep_callback: + pop {r0, r3, r4, r5 } + bx r3 + + .size _evt_poll, . - _evt_poll + + .pool + + .end + +#endif //USBD_STM32F103 diff --git a/port/stm32/usbd_stm32f105_otgfs.c b/port/stm32/usbd_stm32f105_otgfs.c new file mode 100644 index 00000000..54530dac --- /dev/null +++ b/port/stm32/usbd_stm32f105_otgfs.c @@ -0,0 +1,485 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * Adapted from the stm32f429 driver by Fabian Inostroza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32F105) + +#define MAX_EP 4 +#define MAX_RX_PACKET 128 +#define MAX_CONTROL_EP 1 +#define MAX_FIFO_SZ 320 /*in 32-bit chunks */ + +#define RX_FIFO_SZ ((4 * MAX_CONTROL_EP + 6) + ((MAX_RX_PACKET / 4) + 1) + (MAX_EP * 2) + 1) + +#define STATUS_VAL(x) (USBD_HW_ADDRFST | (x)) + +static USB_OTG_GlobalTypeDef * const OTG = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_GLOBAL_BASE); +static USB_OTG_DeviceTypeDef * const OTGD = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_DEVICE_BASE); +static volatile uint32_t * const OTGPCTL = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_PCGCCTL_BASE); + + +inline static uint32_t* EPFIFO(uint32_t ep) { + return (uint32_t*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_FIFO_BASE + (ep << 12)); +} + +inline static USB_OTG_INEndpointTypeDef* EPIN(uint32_t ep) { + return (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE + (ep << 5)); +} + +inline static USB_OTG_OUTEndpointTypeDef* EPOUT(uint32_t ep) { + return (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_OUT_ENDPOINT_BASE + (ep << 5)); +} + +inline static void Flush_RX(void) { + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); +} + +inline static void Flush_TX(uint8_t ep) { + _BMD(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFNUM, + _VAL2FLD(USB_OTG_GRSTCTL_TXFNUM, ep) | USB_OTG_GRSTCTL_TXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFFLSH); +} + +static uint32_t getinfo(void) { + if (!(RCC->AHBENR & RCC_AHBENR_OTGFSEN)) return STATUS_VAL(0); + if (!(OTGD->DCTL & USB_OTG_DCTL_SDIS)) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +} + +static void ep_setstall(uint8_t ep, bool stall) { + if (ep & 0x80) { + ep &= 0x7F; + uint32_t _t = EPIN(ep)->DIEPCTL; + if (_t & USB_OTG_DIEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DIEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DIEPCTL_STALL, + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_SNAK); + } + EPIN(ep)->DIEPCTL = _t; + } + } else { + uint32_t _t = EPOUT(ep)->DOEPCTL; + if (_t & USB_OTG_DOEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DOEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DOEPCTL_STALL, + USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK); + } + EPOUT(ep)->DOEPCTL = _t; + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + ep &= 0x7F; + return (EPIN(ep)->DIEPCTL & USB_OTG_DIEPCTL_STALL) ? true : false; + } else { + return (EPOUT(ep)->DOEPCTL & USB_OTG_DOEPCTL_STALL) ? true : false; + } +} + +static void enable(bool enable) { + if (enable) { + /* enabling USB_OTG in RCC */ + _BST(RCC->AHBENR, RCC_AHBENR_OTGFSEN); + /* do core soft reset */ + _WBS(OTG->GRSTCTL, USB_OTG_GRSTCTL_AHBIDL); + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_CSRST); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_CSRST); + /* configure OTG as device */ + OTG->GUSBCFG = USB_OTG_GUSBCFG_FDMOD | USB_OTG_GUSBCFG_PHYSEL | + _VAL2FLD(USB_OTG_GUSBCFG_TRDT, 0x06); + /* configuring Vbus sense and SOF output */ +#if defined (USBD_VBUS_DETECT) && defined(USBD_SOF_OUT) + OTG->GCCFG = USB_OTG_GCCFG_VBUSBSEN | USB_OTG_GCCFG_SOFOUTEN; +#elif defined(USBD_VBUS_DETECT) + OTG->GCCFG = USB_OTG_GCCFG_VBUSBSEN; +#elif defined(USBD_SOF_OUT) + OTG->GCCFG = USB_OTG_GCCFG_SOFOUTEN; +#else + OTG->GCCFG &= ~(USB_OTG_GCCFG_SOFOUTEN | USB_OTG_GCCFG_VBUSBSEN); +#endif + /* enable PHY clock */ + *OTGPCTL = 0; + /* soft disconnect device */ + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + /* Setup USB FS speed and frame interval */ + _BMD(OTGD->DCFG, USB_OTG_DCFG_PERSCHIVL | USB_OTG_DCFG_DSPD, + _VAL2FLD(USB_OTG_DCFG_PERSCHIVL, 0) | _VAL2FLD(USB_OTG_DCFG_DSPD, 0x03)); + /* setting max RX FIFO size */ + OTG->GRXFSIZ = RX_FIFO_SZ; + /* setting up EP0 TX FIFO SZ as 64 byte */ + OTG->DIEPTXF0_HNPTXFSIZ = RX_FIFO_SZ | (0x10 << 16); + /* unmask EP interrupts */ + OTGD->DIEPMSK = USB_OTG_DIEPMSK_XFRCM; + /* unmask core interrupts */ + OTG->GINTMSK = USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | +#if !defined(USBD_SOF_DISABLED) + USB_OTG_GINTMSK_SOFM | +#endif + USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | + USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_RXFLVLM; + /* clear pending interrupts */ + OTG->GINTSTS = 0xFFFFFFFF; + /* unmask global interrupt */ + _BST(OTG->GAHBCFG, USB_OTG_GAHBCFG_GINT); + } else { + if (RCC->AHBENR & RCC_AHBENR_OTGFSEN) { + _BST(RCC->AHBRSTR, RCC_AHBRSTR_OTGFSRST); + _BCL(RCC->AHBRSTR, RCC_AHBRSTR_OTGFSRST); + _BCL(RCC->AHBENR, RCC_AHBENR_OTGFSEN); + } + } +} + +static uint8_t connect(bool connect) { + if (connect) { +/* The ST made a strange thing again. Really i dont'understand what is the reason to name + signal as PWRDWN (Power down PHY) when it works as "Power up" */ + _BST(OTG->GCCFG, USB_OTG_GCCFG_PWRDWN); + _BCL(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } else { + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + _BCL(OTG->GCCFG, USB_OTG_GCCFG_PWRDWN); + } + return usbd_lane_unk; +} + +static void setaddr (uint8_t addr) { + _BMD(OTGD->DCFG, USB_OTG_DCFG_DAD, addr << 4); +} + +/**\brief Helper. Set up TX fifo + * \param ep endpoint index + * \param epsize required max packet size in bytes + * \return true if TX fifo is successfully set + */ +static bool set_tx_fifo(uint8_t ep, uint16_t epsize) { + uint32_t _fsa = OTG->DIEPTXF0_HNPTXFSIZ; + /* calculating initial TX FIFO address. next from EP0 TX fifo */ + _fsa = 0xFFFF & (_fsa + (_fsa >> 16)); + /* looking for next free TX fifo address */ + for (int i = 0; i < (MAX_EP - 1); i++) { + uint32_t _t = OTG->DIEPTXF[i]; + if ((_t & 0xFFFF) < 0x200) { + _t = 0xFFFF & (_t + (_t >> 16)); + if (_t > _fsa) { + _fsa = _t; + } + } + } + /* calculating requited TX fifo size */ + /* getting in 32 bit terms */ + epsize = (epsize + 0x03) >> 2; + /* it must be 16 32-bit words minimum */ + if (epsize < 0x10) epsize = 0x10; + /* checking for the available fifo */ + if ((_fsa + epsize) > MAX_FIFO_SZ) return false; + /* programming fifo register */ + _fsa |= (epsize << 16); + OTG->DIEPTXF[ep - 1] = _fsa; + return true; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + if (ep == 0) { + /* configuring control endpoint EP0 */ + uint32_t mpsize; + if (epsize <= 0x08) { + epsize = 0x08; + mpsize = 0x03; + } else if (epsize <= 0x10) { + epsize = 0x10; + mpsize = 0x02; + } else if (epsize <= 0x20) { + epsize = 0x20; + mpsize = 0x01; + } else { + epsize = 0x40; + mpsize = 0x00; + } + /* EP0 TX FIFO size is setted on init level */ + /* enabling RX and TX interrupts from EP0 */ + OTGD->DAINTMSK |= 0x00010001; + /* setting up EP0 TX and RX registers */ + /*EPIN(ep)->DIEPTSIZ = epsize;*/ + EPIN(ep)->DIEPCTL = mpsize | USB_OTG_DIEPCTL_SNAK; + /* 1 setup packet, 1 packets total */ + EPOUT(ep)->DOEPTSIZ = epsize | (1 << USB_OTG_DOEPTSIZ_STUPCNT_Pos) | \ + (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos); + EPOUT(ep)->DOEPCTL = mpsize | USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + return true; + } + if (ep & 0x80) { + ep &= 0x7F; + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* configuring TX endpoint */ + /* setting up TX fifo and size register */ + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + if (!set_tx_fifo(ep, epsize << 1)) return false; + } else { + if (!set_tx_fifo(ep, epsize)) return false; + } + /* enabling EP TX interrupt */ + OTGD->DAINTMSK |= (0x0001UL << ep); + /* setting up TX control register*/ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epi->DIEPCTL = USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK | + (0x01 << 18) | USB_OTG_DIEPCTL_USBAEP | + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + case USB_EPTYPE_BULK: + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x02 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + default: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x03 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + } + } else { + /* configuring RX endpoint */ + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* setting up RX control register */ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x01 << 18) | epsize; + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + case USB_EPTYPE_BULK: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x02 << 18) | epsize; + break; + default: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x03 << 18) | epsize; + break; + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + ep &= 0x7F; + volatile USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + volatile USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* deconfiguring TX part */ + /* disable interrupt */ + OTGD->DAINTMSK &= ~(0x10001 << ep); + /* decativating endpoint */ + _BCL(epi->DIEPCTL, USB_OTG_DIEPCTL_USBAEP); + /* flushing FIFO */ + Flush_TX(ep); + /* disabling endpoint */ + if ((epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) && (ep != 0)) { + epi->DIEPCTL = USB_OTG_DIEPCTL_EPDIS; + } + /* clean EP interrupts */ + epi->DIEPINT = 0xFF; + /* deconfiguring TX FIFO */ + if (ep > 0) { + OTG->DIEPTXF[ep-1] = 0x02000200 + 0x200 * ep; + } + /* deconfigureing RX part */ + _BCL(epo->DOEPCTL, USB_OTG_DOEPCTL_USBAEP); + if ((epo->DOEPCTL & USB_OTG_DOEPCTL_EPENA) && (ep != 0)) { + epo->DOEPCTL = USB_OTG_DOEPCTL_EPDIS; + } + epo->DOEPINT = 0xFF; +} + +static int32_t ep_read(uint8_t ep, void* buf, uint16_t blen) { + uint32_t len, tmp; + volatile uint32_t *fifo = EPFIFO(0); + /* no data in RX FIFO */ + if (!(OTG->GINTSTS & USB_OTG_GINTSTS_RXFLVL)) return -1; + ep &= 0x7F; + if ((OTG->GRXSTSR & USB_OTG_GRXSTSP_EPNUM) != ep) return -1; + /* pop data from fifo */ + len = _FLD2VAL(USB_OTG_GRXSTSP_BCNT, OTG->GRXSTSP); + for (int idx = 0; idx < len; idx++) { + if ((idx & 0x03) == 0x00) { + tmp = *fifo; + } + if (idx < blen) { + ((uint8_t*)buf)[idx] = tmp & 0xFF; + tmp >>= 8; + } + } + return (len < blen) ? len : blen; +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t* fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* transfer data size in 32-bit words */ + len = (blen + 3) >> 2; + /* no enough space in TX fifo */ + if (len > epi->DTXFSTS) return -1; + if (ep != 0 && epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) { + return -1; + } + epi->DIEPTSIZ = 0; + epi->DIEPTSIZ = (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos) + blen; + _BMD(epi->DIEPCTL, USB_OTG_DIEPCTL_STALL, USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK); + /* push data to FIFO */ + tmp = 0; + for (int idx = 0; idx < blen; idx++) { + tmp |= (uint32_t)((uint8_t*)buf)[idx] << ((idx & 0x03) << 3); + if ((idx & 0x03) == 0x03 || (idx + 1) == blen) { + *fifo = tmp; + tmp = 0; + } + } + return blen; +} + +static uint16_t get_frame (void) { + return _FLD2VAL(USB_OTG_DSTS_FNSOF, OTGD->DSTS); +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint32_t evt; + uint32_t ep = 0; + while (1) { + uint32_t _t = OTG->GINTSTS; + /* bus RESET event */ + if (_t & USB_OTG_GINTSTS_USBRST) { + OTG->GINTSTS = USB_OTG_GINTSTS_USBRST; + for (uint8_t i = 0; i < MAX_EP; i++ ) { + ep_deconfig(i); + } + Flush_RX(); + continue; + } else if (_t & USB_OTG_GINTSTS_ENUMDNE) { + OTG->GINTSTS = USB_OTG_GINTSTS_ENUMDNE; + evt = usbd_evt_reset; + } else if (_t & USB_OTG_GINTSTS_IEPINT) { + for (;; ep++) { + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + if (ep >= MAX_EP) return; + if (epi->DIEPINT & USB_OTG_DIEPINT_XFRC) { + epi->DIEPINT = USB_OTG_DIEPINT_XFRC; + evt = usbd_evt_eptx; + ep |= 0x80; + break; + } + } + } else if (_t & USB_OTG_GINTSTS_RXFLVL) { + _t = OTG->GRXSTSR; + ep = _t & USB_OTG_GRXSTSP_EPNUM; + switch (_FLD2VAL(USB_OTG_GRXSTSP_PKTSTS, _t)) { + case 0x02: /* OUT recieved */ + evt = usbd_evt_eprx; + break; + case 0x06: /* SETUP recieved */ + /* flushing TX if sonething stuck in control endpoint */ + if (EPIN(ep)->DIEPTSIZ & USB_OTG_DIEPTSIZ_PKTCNT) { + Flush_TX(ep); + } + evt = usbd_evt_epsetup; + break; + case 0x03: /* OUT completed */ + case 0x04: /* SETUP completed */ + _BST(EPOUT(ep)->DOEPCTL, USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA); + default: + /* pop GRXSTSP */ + OTG->GRXSTSP; + continue; + } +#if !defined(USBD_SOF_DISABLED) + } else if (_t & USB_OTG_GINTSTS_SOF) { + OTG->GINTSTS = USB_OTG_GINTSTS_SOF; + evt = usbd_evt_sof; +#endif + } else if (_t & USB_OTG_GINTSTS_USBSUSP) { + evt = usbd_evt_susp; + OTG->GINTSTS = USB_OTG_GINTSTS_USBSUSP; + } else if (_t & USB_OTG_GINTSTS_WKUINT) { + OTG->GINTSTS = USB_OTG_GINTSTS_WKUINT; + evt = usbd_evt_wkup; + } else { + /* no more supported events */ + return; + } + return callback(dev, evt, ep); + } +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x08)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_otgfs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32F105 diff --git a/port/stm32/usbd_stm32f429_otgfs.c b/port/stm32/usbd_stm32f429_otgfs.c new file mode 100644 index 00000000..1633e041 --- /dev/null +++ b/port/stm32/usbd_stm32f429_otgfs.c @@ -0,0 +1,482 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32F429FS) + +#define MAX_EP 4 +#define MAX_RX_PACKET 128 +#define MAX_CONTROL_EP 1 +#define MAX_FIFO_SZ 320 /*in 32-bit chunks */ + +#define RX_FIFO_SZ ((4 * MAX_CONTROL_EP + 6) + ((MAX_RX_PACKET / 4) + 1) + (MAX_EP * 2) + 1) + +#define STATUS_VAL(x) (USBD_HW_ADDRFST | (x)) + +static USB_OTG_GlobalTypeDef * const OTG = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_GLOBAL_BASE); +static USB_OTG_DeviceTypeDef * const OTGD = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_DEVICE_BASE); +static volatile uint32_t * const OTGPCTL = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_PCGCCTL_BASE); + + +inline static uint32_t* EPFIFO(uint32_t ep) { + return (uint32_t*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_FIFO_BASE + (ep << 12)); +} + +inline static USB_OTG_INEndpointTypeDef* EPIN(uint32_t ep) { + return (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE + (ep << 5)); +} + +inline static USB_OTG_OUTEndpointTypeDef* EPOUT(uint32_t ep) { + return (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_OUT_ENDPOINT_BASE + (ep << 5)); +} + +inline static void Flush_RX(void) { + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); +} + +inline static void Flush_TX(uint8_t ep) { + _BMD(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFNUM, + _VAL2FLD(USB_OTG_GRSTCTL_TXFNUM, ep) | USB_OTG_GRSTCTL_TXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFFLSH); +} + +static uint32_t getinfo(void) { + if (!(RCC->AHB2ENR & RCC_AHB2ENR_OTGFSEN)) return STATUS_VAL(0); + if (!(OTGD->DCTL & USB_OTG_DCTL_SDIS)) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +} + +static void ep_setstall(uint8_t ep, bool stall) { + if (ep & 0x80) { + ep &= 0x7F; + uint32_t _t = EPIN(ep)->DIEPCTL; + if (_t & USB_OTG_DIEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DIEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DIEPCTL_STALL, + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_SNAK); + } + EPIN(ep)->DIEPCTL = _t; + } + } else { + uint32_t _t = EPOUT(ep)->DOEPCTL; + if (_t & USB_OTG_DOEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DOEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DOEPCTL_STALL, + USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK); + } + EPOUT(ep)->DOEPCTL = _t; + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + ep &= 0x7F; + return (EPIN(ep)->DIEPCTL & USB_OTG_DIEPCTL_STALL) ? true : false; + } else { + return (EPOUT(ep)->DOEPCTL & USB_OTG_DOEPCTL_STALL) ? true : false; + } +} + +static void enable(bool enable) { + if (enable) { + /* enabling USB_OTG in RCC */ + _BST(RCC->AHB2ENR, RCC_AHB2ENR_OTGFSEN); + /* waiting AHB ready */ + _WBS(OTG->GRSTCTL, USB_OTG_GRSTCTL_AHBIDL); + /* configure OTG as device */ + _BMD(OTG->GUSBCFG, + USB_OTG_GUSBCFG_SRPCAP | _VAL2FLD(USB_OTG_GUSBCFG_TRDT, 0x0F), + USB_OTG_GUSBCFG_FDMOD | _VAL2FLD(USB_OTG_GUSBCFG_TRDT, 0x06)); + /* configuring Vbus sense and SOF output */ +#if defined (USBD_VBUS_DETECT) && defined(USBD_SOF_OUT) + OTG->GCCFG = USB_OTG_GCCFG_VBUSBSEN | USB_OTG_GCCFG_SOFOUTEN; +#elif defined(USBD_VBUS_DETECT) + OTG->GCCFG = USB_OTG_GCCFG_VBUSBSEN; +#elif defined(USBD_SOF_OUT) + OTG->GCCFG = USB_OTG_GCCFG_NOVBUSSENS | USB_OTG_GCCFG_SOFOUTEN; +#else + OTG->GCCFG = USB_OTG_GCCFG_NOVBUSSENS; +#endif + /* enable PHY clock */ + *OTGPCTL = 0; + /* soft disconnect device */ + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + /* Setup USB FS speed and frame interval */ + _BMD(OTGD->DCFG, USB_OTG_DCFG_PERSCHIVL | USB_OTG_DCFG_DSPD, + _VAL2FLD(USB_OTG_DCFG_PERSCHIVL, 0) | _VAL2FLD(USB_OTG_DCFG_DSPD, 0x03)); + /* setting max RX FIFO size */ + OTG->GRXFSIZ = RX_FIFO_SZ; + /* setting up EP0 TX FIFO SZ as 64 byte */ + OTG->DIEPTXF0_HNPTXFSIZ = RX_FIFO_SZ | (0x10 << 16); + /* unmask EP interrupts */ + OTGD->DIEPMSK = USB_OTG_DIEPMSK_XFRCM; + /* unmask core interrupts */ + OTG->GINTMSK = USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | +#if !defined(USBD_SOF_DISABLED) + USB_OTG_GINTMSK_SOFM | +#endif + USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | + USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_RXFLVLM; + /* clear pending interrupts */ + OTG->GINTSTS = 0xFFFFFFFF; + /* unmask global interrupt */ + _BST(OTG->GAHBCFG, USB_OTG_GAHBCFG_GINT); + } else { + if (RCC->AHB2ENR & RCC_AHB2ENR_OTGFSEN) { + _BST(RCC->AHB2RSTR, RCC_AHB2RSTR_OTGFSRST); + _BCL(RCC->AHB2RSTR, RCC_AHB2RSTR_OTGFSRST); + _BCL(RCC->AHB2ENR, RCC_AHB2ENR_OTGFSEN); + } + } +} + +static uint8_t connect(bool connect) { + if (connect) { +/* The ST made a strange thing again. Really i dont'understand what is the reason to name + signal as PWRDWN (Power down PHY) when it works as "Power up" */ + _BST(OTG->GCCFG, USB_OTG_GCCFG_PWRDWN); + _BCL(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } else { + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + _BCL(OTG->GCCFG, USB_OTG_GCCFG_PWRDWN); + } + return usbd_lane_unk; +} + +static void setaddr (uint8_t addr) { + _BMD(OTGD->DCFG, USB_OTG_DCFG_DAD, addr << 4); +} + +/**\brief Helper. Set up TX fifo + * \param ep endpoint index + * \param epsize required max packet size in bytes + * \return true if TX fifo is successfully set + */ +static bool set_tx_fifo(uint8_t ep, uint16_t epsize) { + uint32_t _fsa = OTG->DIEPTXF0_HNPTXFSIZ; + /* calculating initial TX FIFO address. next from EP0 TX fifo */ + _fsa = 0xFFFF & (_fsa + (_fsa >> 16)); + /* looking for next free TX fifo address */ + for (int i = 0; i < (MAX_EP - 1); i++) { + uint32_t _t = OTG->DIEPTXF[i]; + if ((_t & 0xFFFF) < 0x200) { + _t = 0xFFFF & (_t + (_t >> 16)); + if (_t > _fsa) { + _fsa = _t; + } + } + } + /* calculating requited TX fifo size */ + /* getting in 32 bit terms */ + epsize = (epsize + 0x03) >> 2; + /* it must be 16 32-bit words minimum */ + if (epsize < 0x10) epsize = 0x10; + /* checking for the available fifo */ + if ((_fsa + epsize) > MAX_FIFO_SZ) return false; + /* programming fifo register */ + _fsa |= (epsize << 16); + OTG->DIEPTXF[ep - 1] = _fsa; + return true; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + if (ep == 0) { + /* configureing control endpoint EP0 */ + uint32_t mpsize; + if (epsize <= 0x08) { + epsize = 0x08; + mpsize = 0x03; + } else if (epsize <= 0x10) { + epsize = 0x10; + mpsize = 0x02; + } else if (epsize <= 0x20) { + epsize = 0x20; + mpsize = 0x01; + } else { + epsize = 0x40; + mpsize = 0x00; + } + /* EP0 TX FIFO size is setted on init level */ + /* enabling RX and TX interrupts from EP0 */ + OTGD->DAINTMSK |= 0x00010001; + /* setting up EP0 TX and RX registers */ + /*EPIN(ep)->DIEPTSIZ = epsize;*/ + EPIN(ep)->DIEPCTL = mpsize | USB_OTG_DIEPCTL_SNAK; + /* 1 setup packet, 1 packets total */ + EPOUT(ep)->DOEPTSIZ = epsize | (1 << 29) | (1 << 19); + EPOUT(ep)->DOEPCTL = mpsize | USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + return true; + } + if (ep & 0x80) { + ep &= 0x7F; + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* configuring TX endpoint */ + /* setting up TX fifo and size register */ + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + if (!set_tx_fifo(ep, epsize << 1)) return false; + } else { + if (!set_tx_fifo(ep, epsize)) return false; + } + /* enabling EP TX interrupt */ + OTGD->DAINTMSK |= (0x0001UL << ep); + /* setting up TX control register*/ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epi->DIEPCTL = USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK | + (0x01 << 18) | USB_OTG_DIEPCTL_USBAEP | + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + case USB_EPTYPE_BULK: + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x02 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + default: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x03 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + } + } else { + /* configuring RX endpoint */ + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* setting up RX control register */ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x01 << 18) | epsize; + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + case USB_EPTYPE_BULK: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x02 << 18) | epsize; + break; + default: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x03 << 18) | epsize; + break; + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + ep &= 0x7F; + volatile USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + volatile USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* deconfiguring TX part */ + /* disable interrupt */ + OTGD->DAINTMSK &= ~(0x10001 << ep); + /* decativating endpoint */ + _BCL(epi->DIEPCTL, USB_OTG_DIEPCTL_USBAEP); + /* flushing FIFO */ + Flush_TX(ep); + /* disabling endpoint */ + if ((epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) && (ep != 0)) { + epi->DIEPCTL = USB_OTG_DIEPCTL_EPDIS; + } + /* clean EP interrupts */ + epi->DIEPINT = 0xFF; + /* deconfiguring TX FIFO */ + if (ep > 0) { + OTG->DIEPTXF[ep-1] = 0x02000200 + 0x200 * ep; + } + /* deconfigureing RX part */ + _BCL(epo->DOEPCTL, USB_OTG_DOEPCTL_USBAEP); + if ((epo->DOEPCTL & USB_OTG_DOEPCTL_EPENA) && (ep != 0)) { + epo->DOEPCTL = USB_OTG_DOEPCTL_EPDIS; + } + epo->DOEPINT = 0xFF; +} + +static int32_t ep_read(uint8_t ep, void* buf, uint16_t blen) { + uint32_t len, tmp; + volatile uint32_t *fifo = EPFIFO(0); + /* no data in RX FIFO */ + if (!(OTG->GINTSTS & USB_OTG_GINTSTS_RXFLVL)) return -1; + ep &= 0x7F; + if ((OTG->GRXSTSR & USB_OTG_GRXSTSP_EPNUM) != ep) return -1; + /* pop data from fifo */ + len = _FLD2VAL(USB_OTG_GRXSTSP_BCNT, OTG->GRXSTSP); + for (int idx = 0; idx < len; idx++) { + if ((idx & 0x03) == 0x00) { + tmp = *fifo; + } + if (idx < blen) { + ((uint8_t*)buf)[idx] = tmp & 0xFF; + tmp >>= 8; + } + } + return (len < blen) ? len : blen; +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t* fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* transfer data size in 32-bit words */ + len = (blen + 3) >> 2; + /* no enough space in TX fifo */ + if (len > epi->DTXFSTS) return -1; + if (ep != 0 && epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) { + return -1; + } + epi->DIEPTSIZ = 0; + epi->DIEPTSIZ = (1 << 19) + blen; + _BMD(epi->DIEPCTL, USB_OTG_DIEPCTL_STALL, USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK); + /* push data to FIFO */ + tmp = 0; + for (int idx = 0; idx < blen; idx++) { + tmp |= (uint32_t)((uint8_t*)buf)[idx] << ((idx & 0x03) << 3); + if ((idx & 0x03) == 0x03 || (idx + 1) == blen) { + *fifo = tmp; + tmp = 0; + } + } + return blen; +} + +static uint16_t get_frame (void) { + return _FLD2VAL(USB_OTG_DSTS_FNSOF, OTGD->DSTS); +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint32_t evt; + uint32_t ep = 0; + while (1) { + uint32_t _t = OTG->GINTSTS; + /* bus RESET event */ + if (_t & USB_OTG_GINTSTS_USBRST) { + OTG->GINTSTS = USB_OTG_GINTSTS_USBRST; + for (uint8_t i = 0; i < MAX_EP; i++ ) { + ep_deconfig(i); + } + Flush_RX(); + continue; + } else if (_t & USB_OTG_GINTSTS_ENUMDNE) { + OTG->GINTSTS = USB_OTG_GINTSTS_ENUMDNE; + evt = usbd_evt_reset; + } else if (_t & USB_OTG_GINTSTS_IEPINT) { + for (;; ep++) { + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + if (ep >= MAX_EP) return; + if (epi->DIEPINT & USB_OTG_DIEPINT_XFRC) { + epi->DIEPINT = USB_OTG_DIEPINT_XFRC; + evt = usbd_evt_eptx; + ep |= 0x80; + break; + } + } + } else if (_t & USB_OTG_GINTSTS_RXFLVL) { + _t = OTG->GRXSTSR; + ep = _t & USB_OTG_GRXSTSP_EPNUM; + switch (_FLD2VAL(USB_OTG_GRXSTSP_PKTSTS, _t)) { + case 0x02: /* OUT recieved */ + evt = usbd_evt_eprx; + break; + case 0x06: /* SETUP recieved */ + /* flushing TX if sonething stuck in control endpoint */ + if (EPIN(ep)->DIEPTSIZ & USB_OTG_DIEPTSIZ_PKTCNT) { + Flush_TX(ep); + } + evt = usbd_evt_epsetup; + break; + case 0x03: /* OUT completed */ + case 0x04: /* SETUP completed */ + _BST(EPOUT(ep)->DOEPCTL, USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA); + default: + /* pop GRXSTSP */ + OTG->GRXSTSP; + continue; + } +#if !defined(USBD_SOF_DISABLED) + } else if (_t & USB_OTG_GINTSTS_SOF) { + OTG->GINTSTS = USB_OTG_GINTSTS_SOF; + evt = usbd_evt_sof; +#endif + } else if (_t & USB_OTG_GINTSTS_USBSUSP) { + evt = usbd_evt_susp; + OTG->GINTSTS = USB_OTG_GINTSTS_USBSUSP; + } else if (_t & USB_OTG_GINTSTS_WKUINT) { + OTG->GINTSTS = USB_OTG_GINTSTS_WKUINT; + evt = usbd_evt_wkup; + } else { + /* no more supported events */ + return; + } + return callback(dev, evt, ep); + } +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x08)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_otgfs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32F429FS diff --git a/port/stm32/usbd_stm32f429_otghs.c b/port/stm32/usbd_stm32f429_otghs.c new file mode 100644 index 00000000..edab38ff --- /dev/null +++ b/port/stm32/usbd_stm32f429_otghs.c @@ -0,0 +1,486 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32F429HS) + +#define MAX_EP 6 +#define MAX_RX_PACKET 128 +#define MAX_CONTROL_EP 1 +#define MAX_FIFO_SZ 1024 /*in 32-bit chunks */ + +#define RX_FIFO_SZ (10 + (2 * (MAX_RX_PACKET / 4) + 1)) + +#define STATUS_VAL(x) (USBD_HW_ADDRFST | (x)) + +static USB_OTG_GlobalTypeDef * const OTG = (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_GLOBAL_BASE); +static USB_OTG_DeviceTypeDef * const OTGD = (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_DEVICE_BASE); +static volatile uint32_t * const OTGPCTL = (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_PCGCCTL_BASE); + + +inline static uint32_t* EPFIFO(uint32_t ep) { + return (uint32_t*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_FIFO_BASE + (ep << 12)); +} + +inline static USB_OTG_INEndpointTypeDef* EPIN(uint32_t ep) { + return (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE + (ep << 5)); +} + +inline static USB_OTG_OUTEndpointTypeDef* EPOUT(uint32_t ep) { + return (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_OUT_ENDPOINT_BASE + (ep << 5)); +} + +inline static void Flush_RX(void) { + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); +} + +inline static void Flush_TX(uint8_t ep) { + _BMD(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFNUM, + _VAL2FLD(USB_OTG_GRSTCTL_TXFNUM, ep) | USB_OTG_GRSTCTL_TXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFFLSH); +} + +static uint32_t getinfo(void) { + if (!(RCC->AHB1ENR & RCC_AHB1ENR_OTGHSEN)) return STATUS_VAL(0); + if (!(OTGD->DCTL & USB_OTG_DCTL_SDIS)) { + if (_FLD2VAL(USB_OTG_DSTS_ENUMSPD, OTGD->DSTS) == 0x00) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_HS); + if (_FLD2VAL(USB_OTG_DSTS_ENUMSPD, OTGD->DSTS) == 0x03) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + } + return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_NC); +} + +static void ep_setstall(uint8_t ep, bool stall) { + if (ep & 0x80) { + ep &= 0x7F; + uint32_t _t = EPIN(ep)->DIEPCTL; + if (_t & USB_OTG_DIEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DIEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DIEPCTL_STALL, + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_SNAK); + } + EPIN(ep)->DIEPCTL = _t; + } + } else { + uint32_t _t = EPOUT(ep)->DOEPCTL; + if (_t & USB_OTG_DOEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DOEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DOEPCTL_STALL, + USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK); + } + EPOUT(ep)->DOEPCTL = _t; + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + ep &= 0x7F; + return (EPIN(ep)->DIEPCTL & USB_OTG_DIEPCTL_STALL) ? true : false; + } else { + return (EPOUT(ep)->DOEPCTL & USB_OTG_DOEPCTL_STALL) ? true : false; + } +} + +static void enable(bool enable) { + if (enable) { + /* enabling USB_OTG in RCC */ + _BST(RCC->AHB1ENR, RCC_AHB1ENR_OTGHSEN); + _WBS(OTG->GRSTCTL, USB_OTG_GRSTCTL_AHBIDL); + /* configure OTG as device */ + OTG->GUSBCFG = USB_OTG_GUSBCFG_FDMOD | USB_OTG_GUSBCFG_PHYSEL | + _VAL2FLD(USB_OTG_GUSBCFG_TRDT, 0x09) | + _VAL2FLD(USB_OTG_GUSBCFG_TOCAL, 0x01); +/* VBUS detect alternafe function AF12 for PB13 is missed in tech documentation */ +#if defined(USBD_VBUS_DETECT) && defined(USBD_SOF_OUT) + OTG->GCCFG = USB_OTG_GCCFG_VBUSBSEN | USB_OTG_GCCFG_SOFOUTEN; +#elif defined(USBD_VBUS_DETECT) + OTG->GCCFG = USB_OTG_GCCFG_VBUSBSEN; +#elif defined(USBD_SOF_OUT) + OTG->GCCFG = USB_OTG_GCCFG_NOVBUSSENS | USB_OTG_GCCFG_SOFOUTEN; +#else + OTG->GCCFG = USB_OTG_GCCFG_NOVBUSSENS; +#endif + /* do core soft reset */ + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_CSRST); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_CSRST); + /* Setup USB FS speed */ + _BMD(OTGD->DCFG, USB_OTG_DCFG_DSPD, _VAL2FLD(USB_OTG_DCFG_DSPD, 0x03)); + /* start PHY clock */ + *OTGPCTL = 0; + /* soft disconnect device */ + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + /* setting max RX FIFO size */ + OTG->GRXFSIZ = RX_FIFO_SZ; + /* setting up EP0 TX FIFO SZ as 64 byte */ + OTG->DIEPTXF0_HNPTXFSIZ = RX_FIFO_SZ | (0x10 << 16); + /* unmask EP interrupts */ + OTGD->DIEPMSK = USB_OTG_DIEPMSK_XFRCM; + /* unmask core interrupts */ + OTG->GINTMSK = USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | +#if !defined(USBD_SOF_DISABLED) + USB_OTG_GINTMSK_SOFM | +#endif + USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | + USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_RXFLVLM; + /* clear pending interrupts */ + OTG->GINTSTS = 0xFFFFFFFF; + /* unmask global interrupt */ + _BST(OTG->GAHBCFG, USB_OTG_GAHBCFG_GINT); + } else { + if (RCC->AHB1ENR & RCC_AHB1ENR_OTGHSEN) { + _BST(RCC->AHB1RSTR, RCC_AHB1RSTR_OTGHRST); + _BCL(RCC->AHB1RSTR, RCC_AHB1RSTR_OTGHRST); + _BCL(RCC->AHB1ENR, RCC_AHB1ENR_OTGHSEN); + } + } +} + +static uint8_t connect(bool connect) { + if (connect) { + _BST(OTG->GCCFG, USB_OTG_GCCFG_PWRDWN); + _BCL(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } else { + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + _BCL(OTG->GCCFG, USB_OTG_GCCFG_PWRDWN); + } + return usbd_lane_unk; +} + +static void setaddr (uint8_t addr) { + _BMD(OTGD->DCFG, USB_OTG_DCFG_DAD, addr << 4); +} + +/**\brief Helper. Set up TX fifo + * \param ep endpoint index + * \param epsize required max packet size in bytes + * \return true if TX fifo is successfully set + */ +static bool set_tx_fifo(uint8_t ep, uint16_t epsize) { + uint32_t _fsa = OTG->DIEPTXF0_HNPTXFSIZ; + /* calculating initial TX FIFO address. next from EP0 TX fifo */ + _fsa = 0xFFFF & (_fsa + (_fsa >> 16)); + /* looking for next free TX fifo address */ + for (int i = 0; i < (MAX_EP - 1); i++) { + uint32_t _t = OTG->DIEPTXF[i]; + if ((_t & 0xFFFF) < 0x200) { + _t = 0xFFFF & (_t + (_t >> 16)); + if (_t > _fsa) { + _fsa = _t; + } + } + } + /* calculating requited TX fifo size */ + /* getting in 32 bit terms */ + epsize = (epsize + 0x03) >> 2; + /* it must be 16 32-bit words minimum */ + if (epsize < 0x10) epsize = 0x10; + /* checking for the available fifo */ + if ((_fsa + epsize) > MAX_FIFO_SZ) return false; + /* programming fifo register */ + _fsa |= (epsize << 16); + OTG->DIEPTXF[ep - 1] = _fsa; + return true; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + if (ep == 0) { + /* configureing control endpoint EP0 */ + uint32_t mpsize; + if (epsize <= 0x08) { + epsize = 0x08; + mpsize = 0x03; + } else if (epsize <= 0x10) { + epsize = 0x10; + mpsize = 0x02; + } else if (epsize <= 0x20) { + epsize = 0x20; + mpsize = 0x01; + } else { + epsize = 0x40; + mpsize = 0x00; + } + /* EP0 TX FIFO size is setted on init level */ + /* enabling RX and TX interrupts from EP0 */ + OTGD->DAINTMSK |= 0x00010001; + /* setting up EP0 TX and RX registers */ + /*EPIN(ep)->DIEPTSIZ = epsize;*/ + EPIN(ep)->DIEPCTL = mpsize | USB_OTG_DIEPCTL_SNAK; + /* 1 setup packet, 1 packets total */ + EPOUT(ep)->DOEPTSIZ = epsize | (1 << 29) | (1 << 19); + EPOUT(ep)->DOEPCTL = mpsize | USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + return true; + } + if (ep & 0x80) { + ep &= 0x7F; + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* configuring TX endpoint */ + /* setting up TX fifo and size register */ + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + if (!set_tx_fifo(ep, epsize << 1)) return false; + } else { + if (!set_tx_fifo(ep, epsize)) return false; + } + /* enabling EP TX interrupt */ + OTGD->DAINTMSK |= (0x0001UL << ep); + /* setting up TX control register*/ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epi->DIEPCTL = USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK | + (0x01 << 18) | USB_OTG_DIEPCTL_USBAEP | + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + case USB_EPTYPE_BULK: + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x02 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + default: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x03 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + } + } else { + /* configuring RX endpoint */ + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* setting up RX control register */ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x01 << 18) | epsize; + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + case USB_EPTYPE_BULK: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x02 << 18) | epsize; + break; + default: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x03 << 18) | epsize; + break; + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + ep &= 0x7F; + volatile USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + volatile USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* deconfiguring TX part */ + /* disable interrupt */ + OTGD->DAINTMSK &= ~(0x10001 << ep); + /* decativating endpoint */ + _BCL(epi->DIEPCTL, USB_OTG_DIEPCTL_USBAEP); + /* flushing FIFO */ + Flush_TX(ep); + /* disabling endpoint */ + if ((epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) && (ep != 0)) { + epi->DIEPCTL = USB_OTG_DIEPCTL_EPDIS; + } + /* clean EP interrupts */ + epi->DIEPINT = 0xFF; + /* deconfiguring TX FIFO */ + if (ep > 0) { + OTG->DIEPTXF[ep-1] = 0x02000200 + 0x200 * ep; + } + /* deconfigureing RX part */ + _BCL(epo->DOEPCTL, USB_OTG_DOEPCTL_USBAEP); + if ((epo->DOEPCTL & USB_OTG_DOEPCTL_EPENA) && (ep != 0)) { + epo->DOEPCTL = USB_OTG_DOEPCTL_EPDIS; + } + epo->DOEPINT = 0xFF; +} + +static int32_t ep_read(uint8_t ep, void* buf, uint16_t blen) { + uint32_t len, tmp; + volatile uint32_t *fifo = EPFIFO(0); + /* no data in RX FIFO */ + if (!(OTG->GINTSTS & USB_OTG_GINTSTS_RXFLVL)) return -1; + ep &= 0x7F; + if ((OTG->GRXSTSR & USB_OTG_GRXSTSP_EPNUM) != ep) return -1; + /* pop data from fifo */ + len = _FLD2VAL(USB_OTG_GRXSTSP_BCNT, OTG->GRXSTSP); + for (int idx = 0; idx < len; idx++) { + if ((idx & 0x03) == 0x00) { + tmp = *fifo; + } + if (idx < blen) { + ((uint8_t*)buf)[idx] = tmp & 0xFF; + tmp >>= 8; + } + } + return (len < blen) ? len : blen; +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t* fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* transfer data size in 32-bit words */ + len = (blen + 3) >> 2; + /* no enough space in TX fifo */ + if (len > _FLD2VAL(USB_OTG_DTXFSTS_INEPTFSAV, epi->DTXFSTS)) return -1; + if (ep != 0 && epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) { + return -1; + } + _BMD(epi->DIEPTSIZ, + USB_OTG_DIEPTSIZ_PKTCNT | USB_OTG_DIEPTSIZ_MULCNT | USB_OTG_DIEPTSIZ_XFRSIZ, + _VAL2FLD(USB_OTG_DIEPTSIZ_PKTCNT, 1) | _VAL2FLD(USB_OTG_DIEPTSIZ_MULCNT, 1 ) | _VAL2FLD(USB_OTG_DIEPTSIZ_XFRSIZ, blen)); + _BMD(epi->DIEPCTL, USB_OTG_DIEPCTL_STALL, USB_OTG_DOEPCTL_CNAK); + _BST(epi->DIEPCTL, USB_OTG_DOEPCTL_EPENA); + /* push data to FIFO */ + tmp = 0; + for (int idx = 0; idx < blen; idx++) { + tmp |= (uint32_t)((uint8_t*)buf)[idx] << ((idx & 0x03) << 3); + if ((idx & 0x03) == 0x03 || (idx + 1) == blen) { + *fifo = tmp; + tmp = 0; + } + } + return blen; +} + +static uint16_t get_frame (void) { + return _FLD2VAL(USB_OTG_DSTS_FNSOF, OTGD->DSTS); +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint32_t evt; + uint32_t ep = 0; + while (1) { + uint32_t _t = OTG->GINTSTS; + /* bus RESET event */ + if (_t & USB_OTG_GINTSTS_USBRST) { + OTG->GINTSTS = USB_OTG_GINTSTS_USBRST; + for (uint8_t i = 0; i < MAX_EP; i++ ) { + ep_deconfig(i); + } + Flush_RX(); + continue; + } else if (_t & USB_OTG_GINTSTS_ENUMDNE) { + OTG->GINTSTS = USB_OTG_GINTSTS_ENUMDNE; + evt = usbd_evt_reset; + } else if (_t & USB_OTG_GINTSTS_IEPINT) { + for (;; ep++) { + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + if (ep >= MAX_EP) return; + if (epi->DIEPINT & USB_OTG_DIEPINT_XFRC) { + _BST(epi->DIEPINT, USB_OTG_DIEPINT_XFRC); + evt = usbd_evt_eptx; + ep |= 0x80; + break; + } + } + } else if (_t & USB_OTG_GINTSTS_RXFLVL) { + _t = OTG->GRXSTSR; + ep = _t & USB_OTG_GRXSTSP_EPNUM; + switch (_FLD2VAL(USB_OTG_GRXSTSP_PKTSTS, _t)) { + case 0x02: /* OUT recieved */ + evt = usbd_evt_eprx; + break; + case 0x06: /* SETUP recieved */ + /* flushing TX if something stuck in control endpoint */ + if (EPIN(ep)->DIEPTSIZ & USB_OTG_DIEPTSIZ_PKTCNT) { + Flush_TX(ep); + } + evt = usbd_evt_epsetup; + break; + case 0x03: /* OUT completed */ + case 0x04: /* SETUP completed */ + _BST(EPOUT(ep)->DOEPCTL, USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA); + default: + /* pop GRXSTSP */ + OTG->GRXSTSP; + continue; + } +#if !defined(USBD_SOF_DISABLED) + } else if (_t & USB_OTG_GINTSTS_SOF) { + OTG->GINTSTS = USB_OTG_GINTSTS_SOF; + evt = usbd_evt_sof; +#endif + } else if (_t & USB_OTG_GINTSTS_USBSUSP) { + evt = usbd_evt_susp; + OTG->GINTSTS = USB_OTG_GINTSTS_USBSUSP; + } else if (_t & USB_OTG_GINTSTS_WKUINT) { + OTG->GINTSTS = USB_OTG_GINTSTS_WKUINT; + evt = usbd_evt_wkup; + } else { + /* no more supported events */ + return; + } + return callback(dev, evt, ep); + } +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x08)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + +__attribute__((externally_visible)) const struct usbd_driver usbd_otghs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32F429HS diff --git a/port/stm32/usbd_stm32f446_otgfs.c b/port/stm32/usbd_stm32f446_otgfs.c new file mode 100644 index 00000000..93cb2c33 --- /dev/null +++ b/port/stm32/usbd_stm32f446_otgfs.c @@ -0,0 +1,467 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32F446FS) + +#define MAX_EP 6 +#define MAX_RX_PACKET 128 +#define MAX_CONTROL_EP 1 +#define MAX_FIFO_SZ 320 /*in 32-bit chunks */ + +#define RX_FIFO_SZ ((4 * MAX_CONTROL_EP + 6) + ((MAX_RX_PACKET / 4) + 1) + (MAX_EP * 2) + 1) + +#define STATUS_VAL(x) (USBD_HW_ADDRFST | (x)) + +static USB_OTG_GlobalTypeDef * const OTG = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_GLOBAL_BASE); +static USB_OTG_DeviceTypeDef * const OTGD = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_DEVICE_BASE); +static volatile uint32_t * const OTGPCTL = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_PCGCCTL_BASE); + + +inline static uint32_t* EPFIFO(uint32_t ep) { + return (uint32_t*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_FIFO_BASE + (ep << 12)); +} + +inline static USB_OTG_INEndpointTypeDef* EPIN(uint32_t ep) { + return (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE + (ep << 5)); +} + +inline static USB_OTG_OUTEndpointTypeDef* EPOUT(uint32_t ep) { + return (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_OUT_ENDPOINT_BASE + (ep << 5)); +} + +inline static void Flush_RX(void) { + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); +} + +inline static void Flush_TX(uint8_t ep) { + _BMD(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFNUM, + _VAL2FLD(USB_OTG_GRSTCTL_TXFNUM, ep) | USB_OTG_GRSTCTL_TXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFFLSH); +} + +static uint32_t getinfo(void) { + if (!(RCC->AHB2ENR & RCC_AHB2ENR_OTGFSEN)) return STATUS_VAL(0); + if (!(OTGD->DCTL & USB_OTG_DCTL_SDIS)) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +} + +static void ep_setstall(uint8_t ep, bool stall) { + if (ep & 0x80) { + ep &= 0x7F; + uint32_t _t = EPIN(ep)->DIEPCTL; + if (_t & USB_OTG_DIEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DIEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DIEPCTL_STALL, + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_SNAK); + } + EPIN(ep)->DIEPCTL = _t; + } + } else { + uint32_t _t = EPOUT(ep)->DOEPCTL; + if (_t & USB_OTG_DOEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DOEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DOEPCTL_STALL, + USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK); + } + EPOUT(ep)->DOEPCTL = _t; + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + ep &= 0x7F; + return (EPIN(ep)->DIEPCTL & USB_OTG_DIEPCTL_STALL) ? true : false; + } else { + return (EPOUT(ep)->DOEPCTL & USB_OTG_DOEPCTL_STALL) ? true : false; + } +} + +static void enable(bool enable) { + if (enable) { + /* enabling USB_OTG in RCC */ + _BST(RCC->AHB2ENR, RCC_AHB2ENR_OTGFSEN); + _WBS(OTG->GRSTCTL, USB_OTG_GRSTCTL_AHBIDL); + /* configure OTG as device */ + OTG->GUSBCFG = USB_OTG_GUSBCFG_FDMOD | USB_OTG_GUSBCFG_PHYSEL | + _VAL2FLD(USB_OTG_GUSBCFG_TRDT, 0x06); + /* configuring Vbus sense and powerup PHY */ +#if defined(USBD_VBUS_DETECT) + OTG->GCCFG |= USB_OTG_GCCFG_VBDEN | USB_OTG_GCCFG_PWRDWN; +#else + OTG->GOTGCTL |= USB_OTG_GOTGCTL_BVALOEN | USB_OTG_GOTGCTL_BVALOVAL; + OTG->GCCFG = USB_OTG_GCCFG_PWRDWN; +#endif + /* restart PHY*/ + *OTGPCTL = 0; + /* soft disconnect device */ + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + /* Setup USB FS speed and frame interval */ + _BMD(OTGD->DCFG, USB_OTG_DCFG_PERSCHIVL | USB_OTG_DCFG_DSPD, + _VAL2FLD(USB_OTG_DCFG_PERSCHIVL, 0) | _VAL2FLD(USB_OTG_DCFG_DSPD, 0x03)); + /* unmask EP interrupts */ + OTGD->DIEPMSK = USB_OTG_DIEPMSK_XFRCM; + /* unmask core interrupts */ + OTG->GINTMSK = USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | +#if !defined(USBD_SOF_DISABLED) + USB_OTG_GINTMSK_SOFM | +#endif + USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | + USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_RXFLVLM; + /* clear pending interrupts */ + OTG->GINTSTS = 0xFFFFFFFF; + /* unmask global interrupt */ + OTG->GAHBCFG = USB_OTG_GAHBCFG_GINT; + /* setting max RX FIFO size */ + OTG->GRXFSIZ = RX_FIFO_SZ; + /* setting up EP0 TX FIFO SZ as 64 byte */ + OTG->DIEPTXF0_HNPTXFSIZ = RX_FIFO_SZ | (0x10 << 16); + } else { + if (RCC->AHB2ENR & RCC_AHB2ENR_OTGFSEN) { + _BST(RCC->AHB2RSTR, RCC_AHB2RSTR_OTGFSRST); + _BCL(RCC->AHB2RSTR, RCC_AHB2RSTR_OTGFSRST); + _BCL(RCC->AHB2ENR, RCC_AHB2ENR_OTGFSEN); + } + } +} + +static uint8_t connect(bool connect) { + if (connect) { + _BCL(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } else { + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } + return usbd_lane_unk; +} + +static void setaddr (uint8_t addr) { + _BMD(OTGD->DCFG, USB_OTG_DCFG_DAD, addr << 4); +} + +/**\brief Helper. Set up TX fifo + * \param ep endpoint index + * \param epsize required max packet size in bytes + * \return true if TX fifo is successfully set + */ +static bool set_tx_fifo(uint8_t ep, uint16_t epsize) { + uint32_t _fsa = OTG->DIEPTXF0_HNPTXFSIZ; + /* calculating initial TX FIFO address. next from EP0 TX fifo */ + _fsa = 0xFFFF & (_fsa + (_fsa >> 16)); + /* looking for next free TX fifo address */ + for (int i = 0; i < (MAX_EP - 1); i++) { + uint32_t _t = OTG->DIEPTXF[i]; + if ((_t & 0xFFFF) < 0x200) { + _t = 0xFFFF & (_t + (_t >> 16)); + if (_t > _fsa) { + _fsa = _t; + } + } + } + /* calculating requited TX fifo size */ + /* getting in 32 bit terms */ + epsize = (epsize + 0x03) >> 2; + /* it must be 16 32-bit words minimum */ + if (epsize < 0x10) epsize = 0x10; + /* checking for the available fifo */ + if ((_fsa + epsize) > MAX_FIFO_SZ) return false; + /* programming fifo register */ + _fsa |= (epsize << 16); + OTG->DIEPTXF[ep - 1] = _fsa; + return true; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + if (ep == 0) { + /* configureing control endpoint EP0 */ + uint32_t mpsize; + if (epsize <= 0x08) { + epsize = 0x08; + mpsize = 0x03; + } else if (epsize <= 0x10) { + epsize = 0x10; + mpsize = 0x02; + } else if (epsize <= 0x20) { + epsize = 0x20; + mpsize = 0x01; + } else { + epsize = 0x40; + mpsize = 0x00; + } + /* EP0 TX FIFO size is setted on init level */ + /* enabling RX and TX interrupts from EP0 */ + OTGD->DAINTMSK |= 0x00010001; + /* setting up EP0 TX and RX registers */ + /*EPIN(ep)->DIEPTSIZ = epsize;*/ + EPIN(ep)->DIEPCTL = mpsize | USB_OTG_DIEPCTL_SNAK; + /* 1 setup packet, 1 packets total */ + EPOUT(ep)->DOEPTSIZ = epsize | (1 << 29) | (1 << 19); + EPOUT(ep)->DOEPCTL = mpsize | USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + return true; + } + if (ep & 0x80) { + ep &= 0x7F; + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* configuring TX endpoint */ + /* setting up TX fifo and size register */ + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + if (!set_tx_fifo(ep, epsize << 1)) return false; + } else { + if (!set_tx_fifo(ep, epsize)) return false; + } + /* enabling EP TX interrupt */ + OTGD->DAINTMSK |= (0x0001UL << ep); + /* setting up TX control register*/ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epi->DIEPCTL = USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK | + (0x01 << 18) | USB_OTG_DIEPCTL_USBAEP | + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + case USB_EPTYPE_BULK: + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x02 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + default: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x03 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + } + } else { + /* configuring RX endpoint */ + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* setting up RX control register */ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x01 << 18) | epsize; + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + case USB_EPTYPE_BULK: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x02 << 18) | epsize; + break; + default: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x03 << 18) | epsize; + break; + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + ep &= 0x7F; + volatile USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + volatile USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* deconfiguring TX part */ + /* disable interrupt */ + OTGD->DAINTMSK &= ~(0x10001 << ep); + /* decativating endpoint */ + _BCL(epi->DIEPCTL, USB_OTG_DIEPCTL_USBAEP); + /* flushing FIFO */ + Flush_TX(ep); + /* disabling endpoint */ + if ((epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) && (ep != 0)) { + epi->DIEPCTL = USB_OTG_DIEPCTL_EPDIS; + } + /* clean EP interrupts */ + epi->DIEPINT = 0xFF; + /* deconfiguring TX FIFO */ + if (ep > 0) { + OTG->DIEPTXF[ep-1] = 0x02000200 + 0x200 * ep; + } + /* deconfigureing RX part */ + _BCL(epo->DOEPCTL, USB_OTG_DOEPCTL_USBAEP); + if ((epo->DOEPCTL & USB_OTG_DOEPCTL_EPENA) && (ep != 0)) { + epo->DOEPCTL = USB_OTG_DOEPCTL_EPDIS; + } + epo->DOEPINT = 0xFF; +} + +static int32_t ep_read(uint8_t ep, void* buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t *fifo = EPFIFO(0); + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* no data in RX FIFO */ + if (!(OTG->GINTSTS & USB_OTG_GINTSTS_RXFLVL)) return -1; + if ((OTG->GRXSTSR & USB_OTG_GRXSTSP_EPNUM) != ep) return -1; + /* pop data from fifo */ + len = _FLD2VAL(USB_OTG_GRXSTSP_BCNT, OTG->GRXSTSP); + for (int idx = 0; idx < len; idx++) { + if ((idx & 0x03) == 0x00) { + tmp = *fifo; + } + if (idx < blen) { + ((uint8_t*)buf)[idx] = tmp & 0xFF; + tmp >>= 8; + } + } + _BST(epo->DOEPCTL, USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA); + return (len < blen) ? len : blen; +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t* fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* transfer data size in 32-bit words */ + len = (blen + 3) >> 2; + /* no enough space in TX fifo */ + if (len > epi->DTXFSTS) return -1; + if (ep != 0 && epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) { + return -1; + } + epi->DIEPTSIZ = 0; + epi->DIEPTSIZ = (1 << 19) + blen; + _BMD(epi->DIEPCTL, USB_OTG_DIEPCTL_STALL, USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK); + /* push data to FIFO */ + tmp = 0; + for (int idx = 0; idx < blen; idx++) { + tmp |= (uint32_t)((uint8_t*)buf)[idx] << ((idx & 0x03) << 3); + if ((idx & 0x03) == 0x03 || (idx + 1) == blen) { + *fifo = tmp; + tmp = 0; + } + } + return blen; +} + +static uint16_t get_frame (void) { + return _FLD2VAL(USB_OTG_DSTS_FNSOF, OTGD->DSTS); +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint32_t evt; + uint32_t ep = 0; + while (1) { + uint32_t _t = OTG->GINTSTS; + /* bus RESET event */ + if (_t & USB_OTG_GINTSTS_USBRST) { + OTG->GINTSTS = USB_OTG_GINTSTS_USBRST; + for (uint8_t i = 0; i < MAX_EP; i++ ) { + ep_deconfig(i); + } + Flush_RX(); + continue; + } else if (_t & USB_OTG_GINTSTS_ENUMDNE) { + OTG->GINTSTS = USB_OTG_GINTSTS_ENUMDNE; + evt = usbd_evt_reset; + } else if (_t & USB_OTG_GINTSTS_IEPINT) { + for (;; ep++) { + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + if (ep >= MAX_EP) return; + if (epi->DIEPINT & USB_OTG_DIEPINT_XFRC) { + epi->DIEPINT = USB_OTG_DIEPINT_XFRC; + evt = usbd_evt_eptx; + ep |= 0x80; + break; + } + } + } else if (_t & USB_OTG_GINTSTS_RXFLVL) { + _t = OTG->GRXSTSR; + ep = _t & USB_OTG_GRXSTSP_EPNUM; + switch (_FLD2VAL(USB_OTG_GRXSTSP_PKTSTS, _t)) { + case 0x02: + evt = usbd_evt_eprx; + break; + case 0x06: + evt = usbd_evt_epsetup; + break; + default: + OTG->GRXSTSP; + continue; + } +#if !defined(USBD_SOF_DISABLED) + } else if (_t & USB_OTG_GINTSTS_SOF) { + OTG->GINTSTS = USB_OTG_GINTSTS_SOF; + evt = usbd_evt_sof; +#endif + } else if (_t & USB_OTG_GINTSTS_USBSUSP) { + evt = usbd_evt_susp; + OTG->GINTSTS = USB_OTG_GINTSTS_USBSUSP; + } else if (_t & USB_OTG_GINTSTS_WKUINT) { + OTG->GINTSTS = USB_OTG_GINTSTS_WKUINT; + evt = usbd_evt_wkup; + } else { + /* no more supported events */ + return; + } + return callback(dev, evt, ep); + } +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x08)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_otgfs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32L446FS diff --git a/port/stm32/usbd_stm32f446_otghs.c b/port/stm32/usbd_stm32f446_otghs.c new file mode 100644 index 00000000..d65d2e28 --- /dev/null +++ b/port/stm32/usbd_stm32f446_otghs.c @@ -0,0 +1,467 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32F446HS) + +#define MAX_EP 9 +#define MAX_RX_PACKET 128 +#define MAX_CONTROL_EP 1 +#define MAX_FIFO_SZ 1024 /*in 32-bit chunks */ + +#define RX_FIFO_SZ ((4 * MAX_CONTROL_EP + 6) + ((MAX_RX_PACKET / 4) + 1) + (MAX_EP * 2) + 1) + +#define STATUS_VAL(x) (USBD_HW_ADDRFST | (x)) + +static USB_OTG_GlobalTypeDef * const OTG = (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_GLOBAL_BASE); +static USB_OTG_DeviceTypeDef * const OTGD = (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_DEVICE_BASE); +static volatile uint32_t * const OTGPCTL = (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_PCGCCTL_BASE); + + +inline static uint32_t* EPFIFO(uint32_t ep) { + return (uint32_t*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_FIFO_BASE + (ep << 12)); +} + +inline static USB_OTG_INEndpointTypeDef* EPIN(uint32_t ep) { + return (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE + (ep << 5)); +} + +inline static USB_OTG_OUTEndpointTypeDef* EPOUT(uint32_t ep) { + return (void*)(USB_OTG_HS_PERIPH_BASE + USB_OTG_OUT_ENDPOINT_BASE + (ep << 5)); +} + +inline static void Flush_RX(void) { + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); +} + +inline static void Flush_TX(uint8_t ep) { + _BMD(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFNUM, + _VAL2FLD(USB_OTG_GRSTCTL_TXFNUM, ep) | USB_OTG_GRSTCTL_TXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFFLSH); +} + +static uint32_t getinfo(void) { + if (!(RCC->AHB2ENR & RCC_AHB2ENR_OTGFSEN)) return STATUS_VAL(0); + if (!(OTGD->DCTL & USB_OTG_DCTL_SDIS)) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +} + +static void ep_setstall(uint8_t ep, bool stall) { + if (ep & 0x80) { + ep &= 0x7F; + uint32_t _t = EPIN(ep)->DIEPCTL; + if (_t & USB_OTG_DIEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DIEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DIEPCTL_STALL, + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_SNAK); + } + EPIN(ep)->DIEPCTL = _t; + } + } else { + uint32_t _t = EPOUT(ep)->DOEPCTL; + if (_t & USB_OTG_DOEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DOEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DOEPCTL_STALL, + USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK); + } + EPOUT(ep)->DOEPCTL = _t; + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + ep &= 0x7F; + return (EPIN(ep)->DIEPCTL & USB_OTG_DIEPCTL_STALL) ? true : false; + } else { + return (EPOUT(ep)->DOEPCTL & USB_OTG_DOEPCTL_STALL) ? true : false; + } +} + +static void enable(bool enable) { + if (enable) { + /* enabling USB_OTG in RCC */ + _BST(RCC->AHB1ENR, RCC_AHB1ENR_OTGHSEN); + _WBS(OTG->GRSTCTL, USB_OTG_GRSTCTL_AHBIDL); + /* configure OTG as device */ + OTG->GUSBCFG = USB_OTG_GUSBCFG_FDMOD | USB_OTG_GUSBCFG_PHYSEL | + _VAL2FLD(USB_OTG_GUSBCFG_TRDT, 0x06); + /* configuring Vbus sense and powerup PHY */ +#if defined(USBD_VBUS_DETECT) + OTG->GCCFG |= USB_OTG_GCCFG_VBDEN | USB_OTG_GCCFG_PWRDWN; +#else + OTG->GOTGCTL |= USB_OTG_GOTGCTL_BVALOEN | USB_OTG_GOTGCTL_BVALOVAL; + OTG->GCCFG = USB_OTG_GCCFG_PWRDWN; +#endif + /* restart PHY*/ + *OTGPCTL = 0; + /* soft disconnect device */ + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + /* Setup USB FS speed and frame interval */ + _BMD(OTGD->DCFG, USB_OTG_DCFG_PERSCHIVL | USB_OTG_DCFG_DSPD, + _VAL2FLD(USB_OTG_DCFG_PERSCHIVL, 0) | _VAL2FLD(USB_OTG_DCFG_DSPD, 0x03)); + /* setting max RX FIFO size */ + OTG->GRXFSIZ = RX_FIFO_SZ; + /* setting up EP0 TX FIFO SZ as 64 byte */ + OTG->DIEPTXF0_HNPTXFSIZ = RX_FIFO_SZ | (0x10 << 16); + /* unmask EP interrupts */ + OTGD->DIEPMSK = USB_OTG_DIEPMSK_XFRCM; + /* unmask core interrupts */ + OTG->GINTMSK = USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | +#if !defined(USBD_SOF_DISABLED) + USB_OTG_GINTMSK_SOFM | +#endif + USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | + USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_RXFLVLM; + /* clear pending interrupts */ + OTG->GINTSTS = 0xFFFFFFFF; + /* unmask global interrupt */ + OTG->GAHBCFG = USB_OTG_GAHBCFG_GINT; + } else { + if (RCC->AHB1ENR & RCC_AHB1ENR_OTGHSEN) { + _BST(RCC->AHB1RSTR, RCC_AHB1RSTR_OTGHRST); + _BCL(RCC->AHB1RSTR, RCC_AHB1RSTR_OTGHRST); + _BCL(RCC->AHB1ENR, RCC_AHB1ENR_OTGHSEN); + } + } +} + +static uint8_t connect(bool connect) { + if (connect) { + _BCL(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } else { + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } + return usbd_lane_unk; +} + +static void setaddr (uint8_t addr) { + _BMD(OTGD->DCFG, USB_OTG_DCFG_DAD, addr << 4); +} + +/**\brief Helper. Set up TX fifo + * \param ep endpoint index + * \param epsize required max packet size in bytes + * \return true if TX fifo is successfully set + */ +static bool set_tx_fifo(uint8_t ep, uint16_t epsize) { + uint32_t _fsa = OTG->DIEPTXF0_HNPTXFSIZ; + /* calculating initial TX FIFO address. next from EP0 TX fifo */ + _fsa = 0xFFFF & (_fsa + (_fsa >> 16)); + /* looking for next free TX fifo address */ + for (int i = 0; i < (MAX_EP - 1); i++) { + uint32_t _t = OTG->DIEPTXF[i]; + if ((_t & 0xFFFF) < 0x200) { + _t = 0xFFFF & (_t + (_t >> 16)); + if (_t > _fsa) { + _fsa = _t; + } + } + } + /* calculating requited TX fifo size */ + /* getting in 32 bit terms */ + epsize = (epsize + 0x03) >> 2; + /* it must be 16 32-bit words minimum */ + if (epsize < 0x10) epsize = 0x10; + /* checking for the available fifo */ + if ((_fsa + epsize) > MAX_FIFO_SZ) return false; + /* programming fifo register */ + _fsa |= (epsize << 16); + OTG->DIEPTXF[ep - 1] = _fsa; + return true; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + if (ep == 0) { + /* configureing control endpoint EP0 */ + uint32_t mpsize; + if (epsize <= 0x08) { + epsize = 0x08; + mpsize = 0x03; + } else if (epsize <= 0x10) { + epsize = 0x10; + mpsize = 0x02; + } else if (epsize <= 0x20) { + epsize = 0x20; + mpsize = 0x01; + } else { + epsize = 0x40; + mpsize = 0x00; + } + /* EP0 TX FIFO size is setted on init level */ + /* enabling RX and TX interrupts from EP0 */ + OTGD->DAINTMSK |= 0x00010001; + /* setting up EP0 TX and RX registers */ + /*EPIN(ep)->DIEPTSIZ = epsize;*/ + EPIN(ep)->DIEPCTL = mpsize | USB_OTG_DIEPCTL_SNAK; + /* 1 setup packet, 1 packets total */ + EPOUT(ep)->DOEPTSIZ = epsize | (1 << 29) | (1 << 19); + EPOUT(ep)->DOEPCTL = mpsize | USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + return true; + } + if (ep & 0x80) { + ep &= 0x7F; + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* configuring TX endpoint */ + /* setting up TX fifo and size register */ + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + if (!set_tx_fifo(ep, epsize << 1)) return false; + } else { + if (!set_tx_fifo(ep, epsize)) return false; + } + /* enabling EP TX interrupt */ + OTGD->DAINTMSK |= (0x0001UL << ep); + /* setting up TX control register*/ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epi->DIEPCTL = USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK | + (0x01 << 18) | USB_OTG_DIEPCTL_USBAEP | + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + case USB_EPTYPE_BULK: + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x02 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + default: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x03 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + } + } else { + /* configuring RX endpoint */ + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* setting up RX control register */ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x01 << 18) | epsize; + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + case USB_EPTYPE_BULK: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x02 << 18) | epsize; + break; + default: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x03 << 18) | epsize; + break; + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + ep &= 0x7F; + volatile USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + volatile USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* deconfiguring TX part */ + /* disable interrupt */ + OTGD->DAINTMSK &= ~(0x10001 << ep); + /* decativating endpoint */ + _BCL(epi->DIEPCTL, USB_OTG_DIEPCTL_USBAEP); + /* flushing FIFO */ + Flush_TX(ep); + /* disabling endpoint */ + if ((epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) && (ep != 0)) { + epi->DIEPCTL = USB_OTG_DIEPCTL_EPDIS; + } + /* clean EP interrupts */ + epi->DIEPINT = 0xFF; + /* deconfiguring TX FIFO */ + if (ep > 0) { + OTG->DIEPTXF[ep-1] = 0x02000200 + 0x200 * ep; + } + /* deconfigureing RX part */ + _BCL(epo->DOEPCTL, USB_OTG_DOEPCTL_USBAEP); + if ((epo->DOEPCTL & USB_OTG_DOEPCTL_EPENA) && (ep != 0)) { + epo->DOEPCTL = USB_OTG_DOEPCTL_EPDIS; + } + epo->DOEPINT = 0xFF; +} + +static int32_t ep_read(uint8_t ep, void* buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t *fifo = EPFIFO(0); + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* no data in RX FIFO */ + if (!(OTG->GINTSTS & USB_OTG_GINTSTS_RXFLVL)) return -1; + if ((OTG->GRXSTSR & USB_OTG_GRXSTSP_EPNUM) != ep) return -1; + /* pop data from fifo */ + len = _FLD2VAL(USB_OTG_GRXSTSP_BCNT, OTG->GRXSTSP); + for (int idx = 0; idx < len; idx++) { + if ((idx & 0x03) == 0x00) { + tmp = *fifo; + } + if (idx < blen) { + ((uint8_t*)buf)[idx] = tmp & 0xFF; + tmp >>= 8; + } + } + _BST(epo->DOEPCTL, USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA); + return (len < blen) ? len : blen; +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t* fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* transfer data size in 32-bit words */ + len = (blen + 3) >> 2; + /* no enough space in TX fifo */ + if (len > epi->DTXFSTS) return -1; + if (ep != 0 && epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) { + return -1; + } + epi->DIEPTSIZ = 0; + epi->DIEPTSIZ = (1 << 19) + blen; + _BMD(epi->DIEPCTL, USB_OTG_DIEPCTL_STALL, USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK); + /* push data to FIFO */ + tmp = 0; + for (int idx = 0; idx < blen; idx++) { + tmp |= (uint32_t)((uint8_t*)buf)[idx] << ((idx & 0x03) << 3); + if ((idx & 0x03) == 0x03 || (idx + 1) == blen) { + *fifo = tmp; + tmp = 0; + } + } + return blen; +} + +static uint16_t get_frame (void) { + return _FLD2VAL(USB_OTG_DSTS_FNSOF, OTGD->DSTS); +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint32_t evt; + uint32_t ep = 0; + while (1) { + uint32_t _t = OTG->GINTSTS; + /* bus RESET event */ + if (_t & USB_OTG_GINTSTS_USBRST) { + OTG->GINTSTS = USB_OTG_GINTSTS_USBRST; + for (uint8_t i = 0; i < MAX_EP; i++ ) { + ep_deconfig(i); + } + Flush_RX(); + continue; + } else if (_t & USB_OTG_GINTSTS_ENUMDNE) { + OTG->GINTSTS = USB_OTG_GINTSTS_ENUMDNE; + evt = usbd_evt_reset; + } else if (_t & USB_OTG_GINTSTS_IEPINT) { + for (;; ep++) { + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + if (ep >= MAX_EP) return; + if (epi->DIEPINT & USB_OTG_DIEPINT_XFRC) { + epi->DIEPINT = USB_OTG_DIEPINT_XFRC; + evt = usbd_evt_eptx; + ep |= 0x80; + break; + } + } + } else if (_t & USB_OTG_GINTSTS_RXFLVL) { + _t = OTG->GRXSTSR; + ep = _t & USB_OTG_GRXSTSP_EPNUM; + switch (_FLD2VAL(USB_OTG_GRXSTSP_PKTSTS, _t)) { + case 0x02: + evt = usbd_evt_eprx; + break; + case 0x06: + evt = usbd_evt_epsetup; + break; + default: + OTG->GRXSTSP; + continue; + } +#if !defined(USBD_SOF_DISABLED) + } else if (_t & USB_OTG_GINTSTS_SOF) { + OTG->GINTSTS = USB_OTG_GINTSTS_SOF; + evt = usbd_evt_sof; +#endif + } else if (_t & USB_OTG_GINTSTS_USBSUSP) { + evt = usbd_evt_susp; + OTG->GINTSTS = USB_OTG_GINTSTS_USBSUSP; + } else if (_t & USB_OTG_GINTSTS_WKUINT) { + OTG->GINTSTS = USB_OTG_GINTSTS_WKUINT; + evt = usbd_evt_wkup; + } else { + /* no more supported events */ + return; + } + return callback(dev, evt, ep); + } +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x08)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_otghs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32L446FS diff --git a/port/stm32/usbd_stm32l052_devfs.c b/port/stm32/usbd_stm32l052_devfs.c new file mode 100644 index 00000000..1b9e0eb3 --- /dev/null +++ b/port/stm32/usbd_stm32l052_devfs.c @@ -0,0 +1,472 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32L052) + +#ifndef USB_PMASIZE + #pragma message "PMA memory size is not defined. Use 1k by default" + #define USB_PMASIZE 0x400 +#endif + +#define USB_EP_SWBUF_TX USB_EP_DTOG_RX +#define USB_EP_SWBUF_RX USB_EP_DTOG_TX + +#define EP_TOGGLE_SET(epr, bits, mask) *(epr) = (*(epr) ^ (bits)) & (USB_EPREG_MASK | (mask)) + +#define EP_TX_STALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_STALL, USB_EPTX_STAT) +#define EP_RX_STALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_STALL, USB_EPRX_STAT) +#define EP_TX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_NAK, USB_EPTX_STAT | USB_EP_DTOG_TX) +#define EP_RX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID, USB_EPRX_STAT | USB_EP_DTOG_RX) +#define EP_DTX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID, USB_EPTX_STAT | USB_EP_DTOG_TX | USB_EP_SWBUF_TX) +#define EP_DRX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID | USB_EP_SWBUF_RX, USB_EPRX_STAT | USB_EP_DTOG_RX | USB_EP_SWBUF_RX) +#define EP_TX_VALID(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID, USB_EPTX_STAT) +#define EP_RX_VALID(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID, USB_EPRX_STAT) + +#define STATUS_VAL(x) (USBD_HW_BC | (x)) + +typedef struct { + uint16_t addr; + uint16_t cnt; +} pma_rec; + +typedef union pma_table { + struct { + pma_rec tx; + pma_rec rx; + }; + struct { + pma_rec tx0; + pma_rec tx1; + }; + struct { + pma_rec rx0; + pma_rec rx1; + }; +} pma_table; + + +/** \brief Helper function. Returns pointer to the buffer descriptor table. + */ +inline static pma_table *EPT(uint8_t ep) { + return (pma_table*)((ep & 0x07) * 8 + USB_PMAADDR); + +} + +/** \brief Helper function. Returns pointer to the endpoint control register. + */ +inline static volatile uint16_t *EPR(uint8_t ep) { + return (uint16_t*)((ep & 0x07) * 4 + USB_BASE); +} + + +/** \brief Helper function. Returns next available PMA buffer. + * + * \param sz uint16_t Requested buffer size. + * \return uint16_t Buffer address for PMA table. + * \note PMA buffers grown from top to bottom like stack. + */ +static uint16_t get_next_pma(uint16_t sz) { + unsigned _result = USB_PMASIZE; + for (int i = 0; i < 8; i++) { + pma_table *tbl = EPT(i); + if ((tbl->rx.addr) && (tbl->rx.addr < _result)) _result = tbl->rx.addr; + if ((tbl->tx.addr) && (tbl->tx.addr < _result)) _result = tbl->tx.addr; + } + return (_result < (0x020 + sz)) ? 0 : (_result - sz); +} + +static uint32_t getinfo(void) { + if (!(RCC->APB1ENR & RCC_APB1ENR_USBEN)) return STATUS_VAL(0); + if (USB->BCDR & USB_BCDR_DPPU) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +} + +static void ep_setstall(uint8_t ep, bool stall) { + volatile uint16_t *reg = EPR(ep); + /* ISOCHRONOUS endpoint can't be stalled or unstalled */ + if (USB_EP_ISOCHRONOUS == (*reg & USB_EP_T_FIELD)) return; + /* If it's an IN endpoint */ + if (ep & 0x80) { + /* DISABLED endpoint can't be stalled or unstalled */ + if (USB_EP_TX_DIS == (*reg & USB_EPTX_STAT)) return; + if (stall) { + EP_TX_STALL(reg); + } else { + /* if it's a doublebuffered endpoint */ + if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) { + /* set endpoint to VALID and clear DTOG_TX & SWBUF_TX */ + EP_DTX_UNSTALL(reg); + } else { + /* set endpoint to NAKED and clear DTOG_TX */ + EP_TX_UNSTALL(reg); + } + } + } else { + if (USB_EP_RX_DIS == (*reg & USB_EPRX_STAT)) return; + if (stall) { + EP_RX_STALL(reg); + } else { + /* if it's a doublebuffered endpoint */ + if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) { + /* set endpoint to VALID, clear DTOG_RX, set SWBUF_RX */ + EP_DRX_UNSTALL(reg); + } else { + /* set endpoint to VALID and clear DTOG_RX */ + EP_RX_UNSTALL(reg); + } + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + return (USB_EP_TX_STALL == (USB_EPTX_STAT & *EPR(ep))); + } else { + return (USB_EP_RX_STALL == (USB_EPRX_STAT & *EPR(ep))); + } +} + +static void enable(bool enable) { + if (enable) { + RCC->APB1ENR |= RCC_APB1ENR_USBEN; + RCC->APB1RSTR |= RCC_APB1RSTR_USBRST; + RCC->APB1RSTR &= ~RCC_APB1RSTR_USBRST; +#if defined(USBD_PINS_REMAP) && (defined(STM32F042x6) || defined(STM32F048xx) || defined(STM32F070x6)) + RCC->APB2ENR |= RCC_APB2ENR_SYSCFGCOMPEN; + SYSCFG->CFGR1 |= SYSCFG_CFGR1_PA11_PA12_RMP; // remap USB pins for small packages +#endif + USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM | USB_CNTR_ERRM | +#if !defined(USBD_SOF_DISABLED) + USB_CNTR_SOFM | +#endif + USB_CNTR_SUSPM | USB_CNTR_WKUPM; + } else if (RCC->APB1ENR & RCC_APB1ENR_USBEN) { + USB->BCDR = 0; + RCC->APB1RSTR |= RCC_APB1RSTR_USBRST; + RCC->APB1ENR &= ~RCC_APB1ENR_USBEN; + } +} + +static uint8_t connect(bool connect) { + uint8_t res; + USB->BCDR = USB_BCDR_BCDEN | USB_BCDR_DCDEN; + if (USB->BCDR & USB_BCDR_DCDET) { + USB->BCDR = USB_BCDR_BCDEN | USB_BCDR_PDEN; + if (USB->BCDR & USB_BCDR_PS2DET) { + res = usbd_lane_unk; + } else if (USB->BCDR & USB_BCDR_PDET) { + USB->BCDR = USB_BCDR_BCDEN | USB_BCDR_SDEN; + if (USB->BCDR & USB_BCDR_SDET) { + res = usbd_lane_dcp; + } else { + res = usbd_lane_cdp; + } + } else { + res = usbd_lane_sdp; + } + } else { + res = usbd_lane_dsc; + } + USB->BCDR = (connect) ? USB_BCDR_DPPU : 0; + return res; +} + +static void setaddr (uint8_t addr) { + USB->DADDR = USB_DADDR_EF | addr; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + volatile uint16_t *reg = EPR(ep); + pma_table *tbl = EPT(ep); + /* epsize must be 2-byte aligned */ + epsize = (~0x01U) & (epsize + 1); + + switch (eptype) { + case USB_EPTYPE_CONTROL: + *reg = USB_EP_CONTROL | (ep & 0x07); + break; + case USB_EPTYPE_ISOCHRONUS: + *reg = USB_EP_ISOCHRONOUS | (ep & 0x07); + break; + case USB_EPTYPE_BULK: + *reg = USB_EP_BULK | (ep & 0x07); + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + *reg = USB_EP_BULK | USB_EP_KIND | (ep & 0x07); + break; + default: + *reg = USB_EP_INTERRUPT | (ep & 0x07); + break; + } + /* if it TX or CONTROL endpoint */ + if ((ep & 0x80) || (eptype == USB_EPTYPE_CONTROL)) { + uint16_t _pma; + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->tx.addr = _pma; + tbl->tx.cnt = 0; + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->tx1.addr = _pma; + tbl->tx1.cnt = 0; + EP_DTX_UNSTALL(reg); + } else { + EP_TX_UNSTALL(reg); + } + } + if (!(ep & 0x80)) { + uint16_t _rxcnt; + uint16_t _pma; + if (epsize > 62) { + /* using 32-byte blocks. epsize must be 32-byte aligned */ + epsize = (~0x1FU) & (epsize + 0x1FU); + _rxcnt = 0x8000 - 0x400 + (epsize << 5); + } else { + _rxcnt = epsize << 9; + } + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->rx.addr = _pma; + tbl->rx.cnt = _rxcnt; + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->rx0.addr = _pma; + tbl->rx0.cnt = _rxcnt; + EP_DRX_UNSTALL(reg); + } else { + EP_RX_UNSTALL(reg); + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + pma_table *ept = EPT(ep); + *EPR(ep) &= ~USB_EPREG_MASK; + ept->rx.addr = 0; + ept->rx.cnt = 0; + ept->tx.addr = 0; + ept->tx.cnt = 0; +} + +static uint16_t pma_read (uint8_t *buf, uint16_t blen, pma_rec *rx) { + uint16_t tmp; + uint16_t *pma = (void*)(USB_PMAADDR + rx->addr); + uint16_t rxcnt = rx->cnt & 0x03FF; + rx->cnt &= ~0x3FF; + for(int idx = 0; idx < rxcnt; idx++) { + if ((idx & 0x01) == 0) { + tmp = *pma++; + } + if (idx < blen) { + buf[idx] = tmp & 0xFF; + tmp >>= 8; + } else { + return blen; + } + } + return rxcnt; +} + +static int32_t ep_read(uint8_t ep, void *buf, uint16_t blen) { + pma_table *tbl = EPT(ep); + volatile uint16_t *reg = EPR(ep); + switch (*reg & (USB_EPRX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) { + /* doublebuffered bulk endpoint */ + case (USB_EP_RX_VALID | USB_EP_BULK | USB_EP_KIND): + /* switching SWBUF if EP is NAKED */ + switch (*reg & (USB_EP_DTOG_RX | USB_EP_SWBUF_RX)) { + case 0: + case (USB_EP_DTOG_RX | USB_EP_SWBUF_RX): + *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_RX; + break; + default: + break; + } + if (*reg & USB_EP_SWBUF_RX) { + return pma_read(buf, blen, &(tbl->rx1)); + } else { + return pma_read(buf, blen, &(tbl->rx0)); + } + /* isochronous endpoint */ + case (USB_EP_RX_VALID | USB_EP_ISOCHRONOUS): + if (*reg & USB_EP_DTOG_RX) { + return pma_read(buf, blen, &(tbl->rx1)); + } else { + return pma_read(buf, blen, &(tbl->rx0)); + } + /* regular endpoint */ + case (USB_EP_RX_NAK | USB_EP_BULK): + case (USB_EP_RX_NAK | USB_EP_CONTROL): + case (USB_EP_RX_NAK | USB_EP_INTERRUPT): + { + int32_t res = pma_read(buf, blen, &(tbl->rx)); + /* setting endpoint to VALID state */ + EP_RX_VALID(reg); + return res; + } + /* invalid or not ready */ + default: + return -1; + } +} + +static void pma_write(uint8_t *buf, uint16_t blen, pma_rec *tx) { + uint16_t *pma = (void*)(USB_PMAADDR + tx->addr); + uint16_t tmp = 0; + tx->cnt = blen; + for (int idx=0; idx < blen; idx++) { + tmp |= buf[idx] << ((idx & 0x01) ? 8 : 0); + if ((idx & 0x01) || (idx + 1) == blen) { + *pma++ = tmp; + tmp = 0; + } + } +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + pma_table *tbl = EPT(ep); + volatile uint16_t *reg = EPR(ep); + switch (*reg & (USB_EPTX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) { + /* doublebuffered bulk endpoint */ + case (USB_EP_TX_NAK | USB_EP_BULK | USB_EP_KIND): + if (*reg & USB_EP_SWBUF_TX) { + pma_write(buf, blen, &(tbl->tx1)); + } else { + pma_write(buf, blen, &(tbl->tx0)); + } + *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_TX; + break; + /* isochronous endpoint */ + case (USB_EP_TX_VALID | USB_EP_ISOCHRONOUS): + if (!(*reg & USB_EP_DTOG_TX)) { + pma_write(buf, blen, &(tbl->tx1)); + } else { + pma_write(buf, blen, &(tbl->tx0)); + } + break; + /* regular endpoint */ + case (USB_EP_TX_NAK | USB_EP_BULK): + case (USB_EP_TX_NAK | USB_EP_CONTROL): + case (USB_EP_TX_NAK | USB_EP_INTERRUPT): + pma_write(buf, blen, &(tbl->tx)); + EP_TX_VALID(reg); + break; + /* invalid or not ready */ + default: + return -1; + } + return blen; +} + +static uint16_t get_frame (void) { + return USB->FNR & USB_FNR_FN; +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint8_t _ev, _ep; + uint16_t _istr = USB->ISTR; + _ep = _istr & USB_ISTR_EP_ID; + if (_istr & USB_ISTR_CTR) { + volatile uint16_t *reg = EPR(_ep); + if (*reg & USB_EP_CTR_TX) { + *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_TX); + _ep |= 0x80; + _ev = usbd_evt_eptx; + } else { + *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_RX); + _ev = (*reg & USB_EP_SETUP) ? usbd_evt_epsetup : usbd_evt_eprx; + } + } else if (_istr & USB_ISTR_RESET) { + USB->ISTR &= ~USB_ISTR_RESET; + USB->BTABLE = 0; + for (int i = 0; i < 8; i++) { + ep_deconfig(i); + } + _ev = usbd_evt_reset; +#if !defined(USBD_SOF_DISABLED) + } else if (_istr & USB_ISTR_SOF) { + _ev = usbd_evt_sof; + USB->ISTR &= ~USB_ISTR_SOF; +#endif + } else if (_istr & USB_ISTR_WKUP) { + _ev = usbd_evt_wkup; + USB->CNTR &= ~USB_CNTR_FSUSP; + USB->ISTR &= ~USB_ISTR_WKUP; + } else if (_istr & USB_ISTR_SUSP) { + _ev = usbd_evt_susp; + USB->CNTR |= USB_CNTR_FSUSP; + USB->ISTR &= ~USB_ISTR_SUSP; + } else if (_istr & USB_ISTR_ERR) { + USB->ISTR &= ~USB_ISTR_ERR; + _ev = usbd_evt_error; + } else { + return; + } + callback(dev, _ev, _ep); +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x14)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_devfs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32L052 diff --git a/port/stm32/usbd_stm32l052_devfs_asm.S b/port/stm32/usbd_stm32l052_devfs_asm.S new file mode 100644 index 00000000..a5a0ab4c --- /dev/null +++ b/port/stm32/usbd_stm32l052_devfs_asm.S @@ -0,0 +1,850 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined (__ASSEMBLER__) + #define __ASSEMBLER__ +#endif + +#include "usb.h" +#if defined(USBD_STM32L052) || defined(USBD_STM32L433) +#include "memmap.inc" + +#define EP_SETUP 0x0800 +#define EP_TYPE 0x0600 +#define EP_KIND 0x0100 +#define EP_ADDR 0x000F + +#define EP_RX_CTR 0x8000 +#define EP_RX_DTOG 0x4000 +#define EP_RX_STAT 0x3000 +#define EP_RX_SWBUF 0x0040 + +#define EP_RX_DIS 0x0000 +#define EP_RX_STAL 0x1000 +#define EP_RX_NAK 0x2000 +#define EP_RX_VAL 0x3000 + +#define EP_TX_CTR 0x0080 +#define EP_TX_DTOG 0x0040 +#define EP_TX_STAT 0x0030 +#define EP_TX_SWBUF 0x4000 + +#define EP_TX_DIS 0x0000 +#define EP_TX_STAL 0x0010 +#define EP_TX_NAK 0x0020 +#define EP_TX_VAL 0x0030 + +#define RXADDR0 0x00 +#define RXCOUNT0 0x02 +#define RXADDR1 0x04 +#define RXCOUNT1 0x06 + +#define TXADDR0 0x00 +#define TXCOUNT0 0x02 +#define TXADDR1 0x04 +#define TXCOUNT1 0x06 + +#define TXADDR 0x00 +#define TXCOUNT 0x02 +#define RXADDR 0x04 +#define RXCOUNT 0x06 + + +#define EP_NOTOG (EP_RX_CTR | EP_TX_CTR | EP_SETUP | EP_TYPE | EP_KIND | EP_ADDR) + +#define TGL_SET(mask, bits) ((EP_NOTOG | (mask))<<16 | (bits)) + +#define TX_STALL TGL_SET(EP_TX_STAT, EP_TX_STAL) +#define RX_STALL TGL_SET(EP_RX_STAT, EP_RX_STAL) +#define TX_USTALL TGL_SET(EP_TX_STAT | EP_TX_DTOG, EP_TX_NAK) +#define RX_USTALL TGL_SET(EP_RX_STAT | EP_RX_DTOG, EP_RX_VAL) +#define DTX_USTALL TGL_SET(EP_TX_STAT | EP_TX_DTOG | EP_TX_SWBUF, EP_TX_VAL) +#define DRX_USTALL TGL_SET(EP_RX_STAT | EP_RX_DTOG | EP_RX_SWBUF, EP_RX_VAL | EP_RX_SWBUF) + + + .syntax unified + .cpu cortex-m0plus + .thumb + + .align 4 + .section .rodata.usbd_devfs_asm + .globl usbd_devfs_asm +usbd_devfs_asm: + .long _getinfo + .long _enable + .long _connect + .long _setaddr + .long _ep_config + .long _ep_deconfig + .long _ep_read + .long _ep_write + .long _ep_setstall + .long _ep_isstalled + .long _evt_poll + .long _get_frame + .long _get_serial_desc + .size usbd_devfs_asm, . - usbd_devfs_asm + + .text + .align 2 + .thumb_func + .type _get_serial_desc, %function + +/* uint16_t get_serial_desc (void *buffer) + * R0 <- buffer for the string descriptor + * descrpitor size -> R0 + */ +_get_serial_desc: + push {r4, r5, lr} + movs r1,18 //descriptor size 18 bytes + strb r1,[r0] + movs r1, #0x03 //DTYPE_STRING + strb r1,[r0, #0x01] + ldr r5, .L_uid_base //UID3 this is the serial number + ldr r4, .L_fnv1a_offset //FNV1A offset + ldr r2, [r5, 0x00] //UID0 + bl .L_fnv1a + ldr r2, [r5, 0x04] //UID1 + bl .L_fnv1a + ldr r2, [r5, 0x14] //UID2 + bl .L_fnv1a + movs r3, #28 +.L_gsn_loop: + movs r1, r4 + lsrs r1, r3 + lsls r1, #28 + lsrs r1, #28 + adds r1, #0x30 //'0' + cmp r1, #0x3A + blo .L_gsn_store + adds r1, #0x07 //'A' - '0' +.L_gsn_store: + adds r0, #0x02 + strb r1, [r0] + lsrs r1, #0x08 + strb r1, [r0, #0x01] + subs r3, #0x04 + bpl .L_gsn_loop + movs r0, #18 + pop {r4, r5, pc} + +.L_fnv1a: + movs r3, #0x04 +.L_fnv1a_loop: + uxtb r1, r2 + eors r4, r1 + ldr r1, .L_fnv1a_prime //FNV1A prime + muls r4, r1 + lsrs r2, #0x08 + subs r3, #0x01 + bne .L_fnv1a_loop + bx lr + + .align 2 +.L_fnv1a_prime: .long 16777619 +.L_fnv1a_offset: .long 2166136261 +.L_uid_base: .long UID_BASE + + .size _get_serial_desc, . - _get_serial_desc + + .thumb_func + .type _connect, %function +_connect: + ldr r3, =USB_REGBASE + movs r1, #0x03 //BCDEN + DCDEN + movs r2, #usbd_lane_dsc + strh r1, [r3, #USB_BCDR] + ldrh r1, [r3, #USB_BCDR] + lsrs r1, #0x05 //DCDET->CF + bcc .L_connect + movs r1, #0x05 //BCDEN + PDEN + movs r2, #usbd_lane_unk + strh r1, [r3, #USB_BCDR] + ldrh r1, [r3, #USB_BCDR] + lsls r1, #25 //PS2DET->CF + bcs .L_connect + movs r2, #usbd_lane_sdp + lsls r1, #2 //PDET->CF + bcc .L_connect + movs r1, #0x09 //BCDEN + SDET + movs r2, #usbd_lane_cdp + strh r1, [r3, #USB_BCDR] + ldrh r1, [r3, #USB_BCDR] + lsrs r1, #7 //SDET->CF + bcc .L_connect + movs r2, #usbd_lane_dcp +.L_connect: + subs r1, r0, #1 + sbcs r0, r1 + lsls r0, #15 + strh r0, [r3, #USB_BCDR] + mov r0, r2 + bx lr + .size _connect, . - _connect + + .thumb_func + .type _setaddr, %function +_setaddr: + ldr r1, =USB_REGBASE + adds r0, #0x80 + strh r0, [r1, #USB_DADDR] //USB->DADDR + bx lr + .size _setaddr, . - _setaddr + + .thumb_func + .type _get_frame, %function +_get_frame: + ldr r0, =USB_REGBASE + ldrh r0, [r0, #USB_FNR] //FNR + lsls r0, #21 + lsrs r0, #21 + bx lr + .size _get_frame, . - _get_frame + + .thumb_func + .type _enable, %function +_enable: + ldr r1, =USB_REGBASE //USB->CNTR + ldr r2, =RCC_BASE //RCC + movs r3, #0x01 + lsls r3, #RCC_USBEN //USBEN or USBRST + tst r0, r0 + beq .L_disable +.L_enable: + ldr r0, [r2, #RCC_APB1ENR] + orrs r0, r3 + str r0, [r2, #RCC_APB1ENR] //RCC->APB1ENR |= USBEN + ldr r0, [r2, #RCC_APB1RSTR] + orrs r0, r3 + str r0, [r2, #RCC_APB1RSTR] //RCC->APB1RSTR |= USBRST + bics r0, r3 + str r0, [r2, #RCC_APB1RSTR] //RCC->APB1RSTR &= ~USBRST + +#if defined(USBD_PINS_REMAP) && (defined(STM32F042x6) || defined(STM32F048xx) || defined(STM32F070x6)) + ldr r0, [r2, #RCC_APB2ENR] + movs r3, #0x01 + orrs r0, r3 + str r0, [r2, #RCC_APB2ENR] //RCC->APB2ENR |= RCC_APB2ENR_SYSCFGCOMPEN + + ldr r2, =SYSCFG_BASE //SYSCFG->CFGR1 + ldr r0, [r2] + lsls r3, #SYSCFG_USBREMAP + orrs r0, r3 + str r0, [r2] //SYSCFG->CFGR1 |= SYSCFG_CFGR1_PA11_PA12_RMP; // remap USB pins for small packages +#endif + +#if !defined(USBD_SOF_DISABLED) + movs r0, #0xBE // CTRM | ERRM | WKUPM | SUSPM | RESETM | SOFM +#else + movs r0, #0xBC // CTRM | ERRM | WKUPM | SUSPM | RESETM +#endif + lsls r0, #0x08 + strh r0, [r1] //set USB->CNTR + bx lr +.L_disable: + ldr r0, [r2, #RCC_APB1ENR] + tst r0, r3 + beq .L_enable_end // usb is disabled + movs r0, #0x00 + strh r0, [r1, #USB_BCDR] //USB->BCDR disable USB I/O + ldr r0, [r2, #RCC_APB1RSTR] + orrs r0, r3 + str r0, [r2, #RCC_APB1RSTR] //RCC->APB1RSTR |= USBRST + ldr r0, [r2, #RCC_APB1ENR] + bics r0, r3 + str r0, [r2, #RCC_APB1ENR] //RCC->APB1ENR &= ~USBEN +.L_enable_end: + bx lr + .size _enable, . - _enable + + .thumb_func + .type _getinfo, %function +_getinfo: + movs r0, #USBD_HW_BC + ldr r2, =RCC_BASE + ldr r1, [r2, #RCC_APB1ENR] + lsrs r1, #24 //USBEN -> CF + bcc .L_getinfo_end + adds r0, #USBD_HW_ENABLED + ldr r2, =USB_REGBASE + ldr r1, [r2, #USB_BCDR] + lsrs r1, #15 //DPPU -> CF + bcc .L_getinfo_end + adds r0, #USBD_HW_SPEED_FS +.L_getinfo_end: + bx lr + .size _getinfo, . - _getinfo + + .thumb_func + .type _ep_setstall, %function +/*void ep_settall(uint8_t ep, bool stall) + * in R0 <- endpoint number + * in R1 <- 0 if unstall, !0 if stall + */ +_ep_setstall: + push {r4, lr} + lsls r2, r0, #28 + lsrs r2, #26 + ldr r3, =USB_EPBASE + adds r3, r2 // epr -> r3 + movs r2, 0x30 // TX_STAT_MASK -> r2 + ldrh r4, [r3] + lsls r4, #21 + lsrs r4, #29 // EP_TYPE | EP_KIND -> R4 LSB + cmp r4, #0x04 // ISO ? + beq .L_eps_exit + cmp r0, #0x80 + blo .L_eps_rx +.L_eps_tx: + ldr r0, =TX_STALL //stall TX + cmp r1, #0x00 + bne .L_eps_reg_set +.L_eps_tx_unstall: + ldr r0, =DTX_USTALL //unstall dblbulk or iso TX (VALID and clr DTOG_TX & SWBUF_TX) + cmp r4, #0x01 // if doublebuffered bulk endpoint + beq .L_eps_reg_set + ldr r0, =TX_USTALL // unstall other TX (NAKED + clr DTOG_TX) + b .L_eps_reg_set +.L_eps_rx: + lsls r2, #8 // RX_STAT_MASK -> R2 + ldr r0,=RX_STALL //stall RX + cmp r1, #0x00 + bne .L_eps_reg_set +.L_eps_rx_unstall: + ldr r0, =DRX_USTALL //unstall dblbulk or iso (VALID. clr DTOG_RX set SWBUF_RX) + cmp r4, #0x01 // if dblbulk + beq .L_eps_reg_set + ldr r0, =RX_USTALL // unstall other RX (VALID + clr +/* R0 - mask and toggle bits + * R2 - mask for STAT bits + * R3 - endpoint register pointer + */ +.L_eps_reg_set: + ldrh r1, [r3] // *epr -> r1 + ands r2, r1 // check if endpoint disabled + beq .L_eps_exit // do nothing + eors r1, r0 + lsrs r0, #16 + ands r1, r0 + strh r1, [r3] +.L_eps_exit: + pop {r4, pc} + .size _ep_setstall, . - _ep_setstall + + + .thumb_func + .type _ep_isstalled, %function +/* bool ep_isstalled(uint8t ep) */ +_ep_isstalled: + ldr r1, =USB_EPBASE + lsls r2, r0, #28 + lsrs r2, #26 + ldr r1, [r1, r2] + lsls r1, #17 + cmp r0, #0x80 + bhs .L_eis_check + lsls r1, #8 +.L_eis_check: + lsrs r1, r1, #28 + subs r1, #0x01 + subs r0, r1, #0x01 + sbcs r1, r1 + rsbs r0, r1, #0 + bx lr + .size _ep_isstalled, . - _ep_isstalled + + + .thumb_func + .type _ep_read, %function +/* int32_t _ep_read(uint8_t ep, void *buf, uint16_t blen) + * in R0 <- endpoint + * in R1 <- *buffer + * in R2 <- length of the buffer + * out length of the recieved data -> R0 or -1 on error + */ +_ep_read: + push {r4, r5, lr} + ldr r3, =USB_EPBASE + ldr r4, =USB_PMABASE + lsls r0, #28 + lsrs r0, #26 + adds r3, r0 // *EPR -> R3 + lsls r0, #1 + adds r4, r0 // *EPT -> R4 + ldrh r5, [r3] // reading epr +/* validating endpoint */ + movs r0, #0x37 + lsls r0, #0x08 + ands r0, r5 + lsrs r0, #0x08 + cmp r0, #0x34 // (OK) RX_VALID + ISO + beq .L_epr_iso + cmp r0, #0x31 // (OK) RX_VALID + DBLBULK + beq .L_epr_dbl + cmp r0, #0x20 // (OK) RX_NAKED + BULK + beq .L_epr_sngl + cmp r0, #0x22 // (OK) RX_NAKED + CTRL + beq .L_epr_sngl + cmp r0, #0x26 // (OK) RX_NAKED + INTR + beq .L_epr_sngl + movs r0, #0xFF // endpoint contains no valid data + sxtb r0, r0 + b .L_epr_exit +/* processing */ +.L_epr_dbl: + lsrs r0, r5, #8 + eors r0, r5 + lsrs r0, #7 // SW_RX ^ DTOG_RX -> CF + bcs .L_epr_notog // jmp if SW_RX != DTOG_RX (VALID) + ldr r0, =EP_NOTOG + ands r5, r0 + adds r5, #EP_RX_SWBUF + strh r5, [r3] // toggling SW_RX +.L_epr_notog: + ldrh r5, [r3] + lsls r5, #8 // shift SW_RX to DTOG_RX +.L_epr_iso: + lsrs r5, #15 // DTOG_RX -> CF + bcs .L_epr_sngl + subs r4, #0x04 // set RXADDR0 +.L_epr_sngl: + ldrh r0, [r4, #RXCOUNT] + lsrs r5, r0, #0x0A + lsls r5, #0x0A // r5 = r0 & ~0x03FF + strh r5, [r4, #RXCOUNT] + lsls r0, #22 + lsrs r0, #22 // r0 &= 0x3FF (RX count) + ldrh r5, [r4, #RXADDR] + ldr r4, =USB_PMABASE + adds r5, r4 // R5 now has a physical address + cmp r2, r0 + blo .L_epr_read + mov r2, r0 // if buffer is larger +.L_epr_read: + cmp r2, #1 + blo .L_epr_read_end + ldrh r4, [r5] + strb r4, [r1] + beq .L_epr_read_end + lsrs r4, #8 + strb r4, [r1, #1] + adds r1, #2 + adds r5, #2 + subs r2, #2 + bhi .L_epr_read +.L_epr_read_end: + ldrh r5, [r3] // reload EPR + lsls r1, r5, #21 + lsrs r1, #29 + cmp r1, #0x04 + beq .L_epr_exit // ep is iso. no needs to set it to valid + cmp r1, #0x01 + beq .L_epr_exit // ep is dblbulk. no needs to set it to valid + ldr r2, =TGL_SET(EP_RX_STAT , EP_RX_VAL) + eors r5, r2 + lsrs r2, #16 + ands r5, r2 + strh r5, [r3] // set ep to VALID state +.L_epr_exit: + pop {r4, r5, pc} + .size _ep_read, . - _ep_read + + + + .thumb_func + .type _ep_write, %function +/* int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) + * R0 -> endpoint + * R1 -> *buffer + * R2 -> data length + * + */ +_ep_write: + push {r4, r5, r6, lr} + ldr r3, =USB_EPBASE + ldr r4, =USB_PMABASE + lsls r0, #28 + lsrs r0, #26 + adds r3, r0 // *EPR -> R3 + lsls r0, #1 + adds r4, r0 // TXADDR0 -> R4 + ldrh r5, [r3] // reading epr + movs r0, #0x73 + lsls r0, #4 + ands r0, r5 + lsrs r0, #4 + cmp r0, #0x43 // (OK) TX_VALID + ISO + beq .L_epw_iso + cmp r0, #0x12 // (OK) TX_NAK + DBLBULK + beq .L_epw_dbl + cmp r0, #0x02 // (OK) TX_NAK + BULK + beq .L_epw_sngl + cmp r0, #0x22 // (OK) TX_NAK + CONTROL + beq .L_epw_sngl + cmp r0, #0x62 // (OK) TX_NAK + INTERRUPT + beq .L_epw_sngl + movs r0, #0xFF + sxtb r0, r0 + b .L_epw_exit +.L_epw_dbl: + mvns r5, r5 + lsrs r5, #8 // ~SWBUF_TX -> DTOG_TX +.L_epw_iso: + lsrs r5, #7 // DTOG_TX -> CF + bcs .L_epw_sngl + adds r4, #4 // TXADDR1 -> R4 +.L_epw_sngl: + strh r2, [r4, #TXCOUNT] + mov r0, r2 // save count for return + ldrh r5, [r4, #TXADDR] + ldr r4, =USB_PMABASE + adds r5, r4 // PMA BUFFER -> R5 +.L_epw_write: + cmp r2, #1 + blo .L_epw_write_end + ldrb r4, [r1] + beq .L_epw_store + ldrb r6, [r1, #1] + lsls r6, #8 + orrs r4, r6 +.L_epw_store: + strh r4, [r5] + adds r5, #2 + adds r1, #2 + subs r2, #2 + bhi .L_epw_write +.L_epw_write_end: + ldrh r5, [r3] // reload EPR + lsls r1, r5, #21 + lsrs r1, #29 + cmp r1, #0x04 + beq .L_epw_exit // isochronous ep. do nothing + ldr r2, =TGL_SET(EP_TX_STAT, EP_TX_VAL) + cmp r1, #0x01 + bne .L_epw_setstate // NOT a doublebuffered bulk + ldr r2, =TGL_SET(EP_TX_SWBUF, EP_TX_SWBUF) + bics r5, r2 // clear TX_SWBUF +.L_epw_setstate: + eors r5, r2 + lsrs r2, #16 + ands r5, r2 + strh r5, [r3] +.L_epw_exit: + pop {r4, r5, r6, pc} + .size _ep_write, .- _ep_write + +/* internal function */ +/* requester size passed in R2 */ +/* result returns in R0 CF=1 if OK*/ + +_get_next_pma: + push {r1, r3, r4, lr} + movs r1, #0x3C + movs r3, #1 + lsls r3, #10 //R3 MAX_PMA_SIZE + ldr r0, =USB_PMABASE +.L_gnp_chkaddr: + ldrh r4, [r0, r1] + tst r4, r4 + beq .L_gnp_nxtaddr + cmp r3, r4 + blo .L_gnp_nxtaddr + mov r3, r4 +.L_gnp_nxtaddr: + subs r1, #0x04 + bhs .L_gnp_chkaddr + subs r0, r3, r2 + blo .L_gnp_exit + cmp r0, #0x20 //check for the pma table overlap +.L_gnp_exit: + pop {r1, r3, r4, pc} + .size _get_next_pma, . - _get_next_pma + + .thumb_func + .type _ep_config, %function +/* bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) + * R0 <- ep + * R1 <- eptype + * R2 <- epsize + * result -> R0 + */ +_ep_config: + push {r4, r5, lr} + movs r3, 0x01 + ands r3, r2 + adds r2, r3 //R2 -> halfword aligned epsize + movs r3, #0x00 //BULK + cmp r1, #0x02 // is eptype bulk ? + beq .L_epc_settype + movs r3, #0x01 //DBLBULK + cmp r1, #0x06 + beq .L_epc_settype + movs r3, #0x02 //CONTROL + cmp r1, #0x00 + beq .L_epc_settype + movs r3, #0x04 //ISO + cmp r1, #0x01 + beq .L_epc_settype + movs r3, #0x06 //INTERRUPT +.L_epc_settype: + lsls r3, #8 + lsls r4, r0, #28 + lsrs r4, #28 + orrs r3, r4 + lsls r4, #2 + ldr r5, =USB_EPBASE + strh r3, [r5, r4] //setup EPTYPE EPKIND EPADDR + cmp r1, #0x00 // is a control ep ? + beq .L_epc_setuptx + cmp r0, #0x80 + blo .L_epc_setuprx +.L_epc_setuptx: + ldr r5, =USB_PMABASE + lsls r4, #1 + adds r5, r4 +/* setup buffer table */ +/* TX or TX0 */ + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #TXADDR] //store txaddr or txaddr0 + movs r0, #0x00 + strh r0, [r5, #TXCOUNT] //store txcnt + cmp r1, #0x06 // is DBLBULK + beq .L_epc_txdbl + ldr r3, =TX_USTALL //set state NAKED , clr DTOG_TX + cmp r1, #0x01 + bne .L_epc_txsetstate //if single buffered +.L_epc_txdbl: +/* TX1 */ + ldr r3, =DTX_USTALL //set state VALID clr DTOG_TX & SWBUF_TX + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #TXADDR1] //store txaddr1 + movs r0, #0x00 + strh r0, [r5, #TXCOUNT1] //store txcnt +.L_epc_txsetstate: + ldr r5, =USB_EPBASE + lsrs r4, #1 + ldrh r0, [r5, r4] + eors r0, r3 + lsrs r3, #16 + ands r0, r3 + strh r0, [r5, r4] + cmp r1, #0x00 //is a control ep ? + bne .L_epc_exit +.L_epc_setuprx: +/* calculating RX_COUNT field. result in R3*/ + movs r3, r2 + cmp r2, #62 + bls .L_epc_rxbb + /* ep size must be 32-byte aligned if >= 64 */ + movs r3, #0x1F + adds r2, r3 + bics r2, r3 + lsrs r3, r2, #4 + adds r3, #0x3E +.L_epc_rxbb: + lsls r3, #9 + ldr r5, =USB_PMABASE + lsls r4, #1 + adds r5, r4 +/* setup buffer table */ + bl _get_next_pma + bcc .L_epc_fail +/* set RX or RX1 */ + strh r0, [r5, #RXADDR] + strh r3, [r5, #RXCOUNT] + ldr r0, =RX_USTALL +/* check if doublebuffered */ + cmp r1, 0x06 //if dblbulk + beq .L_epc_rxdbl + cmp r1, 0x01 // iso + bne .L_epc_rxsetstate +.L_epc_rxdbl: + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #RXADDR0] //store rxaddr0 + strh r3, [r5, #RXCOUNT0] //store rxcnt0 + ldr r0, =DRX_USTALL +.L_epc_rxsetstate: + ldr r5, =USB_EPBASE + lsrs r4, #1 + ldrh r3, [r5, r4] + eors r3, r0 + lsrs r0, #16 + ands r3, r0 + strh r3, [r5, r4] +.L_epc_exit: + movs r0, #0x01 + pop {r4, r5, pc} +.L_epc_fail: + movs r0, #0x00 + pop {r4, r5, pc} + + .size _ep_config, . - _ep_config + + .thumb_func + .type _ep_deconfig, %function + + +/* void ep_deconfig( uint8_t ep) + * R0 <- ep + */ +_ep_deconfig: + lsls r1, r0, #28 + lsrs r1, #26 + ldr r2, =USB_EPBASE + ldr r3, =USB_PMABASE + adds r2, r1 + lsls r1, #1 + adds r3, r1 +/* clearing endpoint register */ + ldr r1, =EP_NOTOG + ldrh r0, [r2] + bics r0, r1 + strh r0, [r2] +/* clearing PMA data */ + movs r0, #0x00 + strh r0, [r3, #TXADDR] + strh r0, [r3, #TXCOUNT] + strh r0, [r3, #RXADDR] + strh r0, [r3, #RXCOUNT] + bx lr + .size _ep_deconfig, . - _ep_config + + +#define ISTRSHIFT 8 +#define ISTRBIT(bit) ((1 << bit) >> ISTRSHIFT) + + + .thumb_func + .type _evt_poll, %function +/*void evt_poll(usbd_device *dev, usbd_evt_callback callback)*/ +_evt_poll: + push {r0, r1, r4, r5} + ldr r3, =USB_REGBASE + ldrh r0, [r3, #4] //USB->ISTR -> R2 +/* ep_index -> R2 */ + movs r2, 0x07 + ands r2, r0 +/* checking USB->ISTR for events */ +#if !defined(USBD_SOF_DISABLED) + lsrs r1, r0, #10 //SOFM -> CF + bcs .L_ep_sofm +#endif + lsrs r1, r0, #16 //CTRM -> CF + bcs .L_ep_ctrm + lsrs r1, r0, #14 //ERRM -> CF + bcs .L_ep_errm + lsrs r1, r0, #13 //WKUPM -> CF + bcs .L_ep_wkupm + lsrs r1, r0, #12 //SUSPM -> CF + bcs .L_ep_suspm + lsrs r1, r0, #11 //RESETM -> CF + bcs .L_ep_resetm + /* exit with no callback */ + pop {r0, r1, r4 , r5} + bx lr + +.L_ep_ctrm: + movs r5, #0x80 // CTR_TX mask to R5 + ldr r0,=USB_EPBASE + lsrs r0, #2 + adds r0, r2 + lsls r0, #2 // R0 ep register address + ldrh r4, [r0] // R4 EPR valur + lsrs r3, r4, #8 // CTR_TX -> CF + bcc .L_ep_ctr_rx +/* CTR_TX event */ + movs r1, #usbd_evt_eptx + orrs r2, r5 // set endpoint tx + b .L_ep_clr_ctr +.L_ep_ctr_rx: +/* CTR_RX RX or SETUP */ + lsls r5, #0x08 // set mask to CRT_RX + movs r1, #usbd_evt_eprx + lsls r3, r4, #21 //SETUP -> CF + bcc .L_ep_clr_ctr + movs r1, #usbd_evt_epsetup +.L_ep_clr_ctr: + bics r4, r5 //clear CTR flag + ldr r5, =EP_NOTOG + ands r4, r5 + strh r4, [r0] // store + b .L_ep_callback +.L_ep_errm: + movs r1, #usbd_evt_error + movs r4, #ISTRBIT(13) + b .L_ep_clristr +#if !defined(USBD_SOF_DISABLED) +.L_ep_sofm: + movs r1, #usbd_evt_sof + movs r4, #ISTRBIT(9) + b .L_ep_clristr +#endif +.L_ep_wkupm: + ldrh r1, [r3, #USB_CNTR] //R1 USB->CNTR + movs r5, #0x08 + bics r1, r5 //clr FSUSP + strh r1, [r3, #USB_CNTR] //USB->CNTR R2 + movs r1, #usbd_evt_wkup + movs r4, #ISTRBIT(12) + b .L_ep_clristr + +.L_ep_suspm: + ldrh r1, [r3, #USB_CNTR] //R1 USB->CNTR + movs r5, #0x08 + orrs r1, r5 //set FSUSP + strh r1, [r3, #USB_CNTR] //USB->CNTR R2 + movs r1, #usbd_evt_susp + movs r4, #ISTRBIT(11) + b .L_ep_clristr +/* do reset routine */ +.L_ep_resetm: + movs r1, #7 + ldr r2, =USB_EPBASE + ldr r0, =USB_PMABASE + ldr r5, =EP_NOTOG +.L_ep_reset_loop: + ldrh r4, [r2] + bics r4, r5 + strh r4, [r2] + movs r4, #0x00 + strh r4, [r0, #TXADDR] + strh r4, [r0, #TXCOUNT] + strh r4, [r0, #RXADDR] + strh r4, [r0, #RXCOUNT] + adds r2, #4 + adds r0, #8 + subs r1, #1 + bhs .L_ep_reset_loop + strh r4, [r3, #USB_BTABLE] + movs r1, #usbd_evt_reset + movs r4, #ISTRBIT(10) +.L_ep_clristr: + lsls r4, #ISTRSHIFT + ldrh r0, [r3, #4] + bics r0, r4 + strh r0, [r3, #4] +.L_ep_callback: + pop {r0, r3, r4, r5 } + bx r3 + .size _evt_poll, . - _evt_poll + + .pool + + .end + +#endif //USBD_STM32L052 diff --git a/port/stm32/usbd_stm32l100_devfs.c b/port/stm32/usbd_stm32l100_devfs.c new file mode 100644 index 00000000..29bd2598 --- /dev/null +++ b/port/stm32/usbd_stm32l100_devfs.c @@ -0,0 +1,457 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32L100) + +#ifndef USB_PMASIZE + #pragma message "PMA memory size is not defined. Use 512 bytes by default" + #define USB_PMASIZE 0x200 +#endif + +#define USB_EP_SWBUF_TX USB_EP_DTOG_RX +#define USB_EP_SWBUF_RX USB_EP_DTOG_TX + +#define EP_TOGGLE_SET(epr, bits, mask) *(epr) = (*(epr) ^ (bits)) & (USB_EPREG_MASK | (mask)) + +#define EP_TX_STALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_STALL, USB_EPTX_STAT) +#define EP_RX_STALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_STALL, USB_EPRX_STAT) +#define EP_TX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_NAK, USB_EPTX_STAT | USB_EP_DTOG_TX) +#define EP_RX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID, USB_EPRX_STAT | USB_EP_DTOG_RX) +#define EP_DTX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID, USB_EPTX_STAT | USB_EP_DTOG_TX | USB_EP_SWBUF_TX) +#define EP_DRX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID | USB_EP_SWBUF_RX, USB_EPRX_STAT | USB_EP_DTOG_RX | USB_EP_SWBUF_RX) +#define EP_TX_VALID(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID, USB_EPTX_STAT) +#define EP_RX_VALID(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID, USB_EPRX_STAT) + +#define STATUS_VAL(x) (x) + +typedef struct { + uint16_t addr; + uint16_t :16; + uint16_t cnt; + uint16_t :16; +} pma_rec; + +typedef union pma_table { + struct { + pma_rec tx; + pma_rec rx; + }; + struct { + pma_rec tx0; + pma_rec tx1; + }; + struct { + pma_rec rx0; + pma_rec rx1; + }; +} pma_table; + + +/** \brief Helper function. Returns pointer to the buffer descriptor table. + */ +inline static pma_table *EPT(uint8_t ep) { + return (pma_table*)((ep & 0x07) * 16 + USB_PMAADDR); +} + +/** \brief Helper function. Returns pointer to the endpoint control register. + */ +inline static volatile uint16_t *EPR(uint8_t ep) { + return (uint16_t*)((ep & 0x07) * 4 + USB_BASE); +} + + +/** \brief Helper function. Returns next available PMA buffer. + * + * \param sz uint16_t Requested buffer size. + * \return uint16_t Buffer address for PMA table. + * \note PMA buffers grown from top to bottom like stack. + */ +static uint16_t get_next_pma(uint16_t sz) { + unsigned _result = USB_PMASIZE; + for (int i = 0; i < 8; i++) { + pma_table *tbl = EPT(i); + if ((tbl->tx.addr) && (tbl->tx.addr < _result)) _result = tbl->tx.addr; + if ((tbl->rx.addr) && (tbl->rx.addr < _result)) _result = tbl->rx.addr; + } + return (_result < (0x020 + sz)) ? 0 : (_result - sz); +} + +static uint32_t getinfo(void) { + if (!(RCC->APB1ENR & RCC_APB1ENR_USBEN)) return STATUS_VAL(0); + if (SYSCFG->PMC & SYSCFG_PMC_USB_PU) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +} + +static void ep_setstall(uint8_t ep, bool stall) { + volatile uint16_t *reg = EPR(ep); + /* ISOCHRONOUS endpoint can't be stalled or unstalled */ + if (USB_EP_ISOCHRONOUS == (*reg & USB_EP_T_FIELD)) return; + /* If it's an IN endpoint */ + if (ep & 0x80) { + /* DISABLED endpoint can't be stalled or unstalled */ + if (USB_EP_TX_DIS == (*reg & USB_EPTX_STAT)) return; + if (stall) { + EP_TX_STALL(reg); + } else { + /* if it's a doublebuffered endpoint */ + if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) { + /* set endpoint to VALID and clear DTOG_TX & SWBUF_TX */ + EP_DTX_UNSTALL(reg); + } else { + /* set endpoint to NAKED and clear DTOG_TX */ + EP_TX_UNSTALL(reg); + } + } + } else { + if (USB_EP_RX_DIS == (*reg & USB_EPRX_STAT)) return; + if (stall) { + EP_RX_STALL(reg); + } else { + /* if it's a doublebuffered endpoint */ + if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) { + /* set endpoint to VALID, clear DTOG_RX, set SWBUF_RX */ + EP_DRX_UNSTALL(reg); + } else { + /* set endpoint to VALID and clear DTOG_RX */ + EP_RX_UNSTALL(reg); + } + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + return (USB_EP_TX_STALL == (USB_EPTX_STAT & *EPR(ep))); + } else { + return (USB_EP_RX_STALL == (USB_EPRX_STAT & *EPR(ep))); + } +} + +static void enable(bool enable) { + if (enable) { + RCC->APB1ENR |= RCC_APB1ENR_USBEN; + RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; + RCC->APB1RSTR |= RCC_APB1RSTR_USBRST; + RCC->APB1RSTR &= ~RCC_APB1RSTR_USBRST; + USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM | USB_CNTR_ERRM | +#if !defined(USBD_SOF_DISABLED) + USB_CNTR_SOFM | +#endif + USB_CNTR_SUSPM | USB_CNTR_WKUPM; + } else if (RCC->APB1ENR & RCC_APB1ENR_USBEN) { + SYSCFG->PMC &= ~SYSCFG_PMC_USB_PU; + RCC->APB1RSTR |= RCC_APB1RSTR_USBRST; + RCC->APB1ENR &= ~RCC_APB1ENR_USBEN; + } +} + +static uint8_t connect(bool connect) { + if (connect) { + SYSCFG->PMC |= SYSCFG_PMC_USB_PU; + } else { + SYSCFG->PMC &= ~SYSCFG_PMC_USB_PU; + } + return usbd_lane_unk; +} + +static void setaddr (uint8_t addr) { + USB->DADDR = USB_DADDR_EF | addr; +} + + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + volatile uint16_t *reg = EPR(ep); + pma_table *tbl = EPT(ep); + /* epsize must be 2-byte aligned */ + epsize = (~0x01U) & (epsize + 1); + + switch (eptype) { + case USB_EPTYPE_CONTROL: + *reg = USB_EP_CONTROL | (ep & 0x07); + break; + case USB_EPTYPE_ISOCHRONUS: + *reg = USB_EP_ISOCHRONOUS | (ep & 0x07); + break; + case USB_EPTYPE_BULK: + *reg = USB_EP_BULK | (ep & 0x07); + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + *reg = USB_EP_BULK | USB_EP_KIND | (ep & 0x07); + break; + default: + *reg = USB_EP_INTERRUPT | (ep & 0x07); + break; + } + /* if it TX or CONTROL endpoint */ + if ((ep & 0x80) || (eptype == USB_EPTYPE_CONTROL)) { + uint16_t _pma; + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->tx.addr = _pma; + tbl->tx.cnt = 0; + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->tx1.addr = _pma; + tbl->tx1.cnt = 0; + EP_DTX_UNSTALL(reg); + } else { + EP_TX_UNSTALL(reg); + } + } + if (!(ep & 0x80)) { + uint16_t _rxcnt; + uint16_t _pma; + if (epsize > 62) { + /* using 32-byte blocks. epsize must be 32-byte aligned */ + epsize = (~0x1FU) & (epsize + 0x1FU); + _rxcnt = 0x8000 - 0x400 + (epsize << 5); + } else { + _rxcnt = epsize << 9; + } + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->rx.addr = _pma; + tbl->rx.cnt = _rxcnt; + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->rx0.addr = _pma; + tbl->rx0.cnt = _rxcnt; + EP_DRX_UNSTALL(reg); + } else { + EP_RX_UNSTALL(reg); + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + pma_table *ept = EPT(ep); + *EPR(ep) &= ~USB_EPREG_MASK; + ept->rx.addr = 0; + ept->rx.cnt = 0; + ept->tx.addr = 0; + ept->tx.cnt = 0; +} + +static uint16_t pma_read (uint8_t *buf, uint16_t blen, pma_rec *rx) { + uint16_t tmp; + uint16_t *pma = (void*)(USB_PMAADDR + 2 * rx->addr); + uint16_t rxcnt = rx->cnt & 0x03FF; + rx->cnt &= ~0x3FF; + for(int idx = 0; idx < rxcnt; idx++) { + if ((idx & 0x01) == 0) { + tmp = *pma++; + } + if (idx < blen) { + buf[idx] = tmp & 0xFF; + tmp >>= 8; + } else { + return blen; + } + } + return rxcnt; +} + +static int32_t ep_read(uint8_t ep, void *buf, uint16_t blen) { + pma_table *tbl = EPT(ep); + volatile uint16_t *reg = EPR(ep); + switch (*reg & (USB_EPRX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) { + /* doublebuffered bulk endpoint */ + case (USB_EP_RX_VALID | USB_EP_BULK | USB_EP_KIND): + /* switching SWBUF if EP is NAKED */ + switch (*reg & (USB_EP_DTOG_RX | USB_EP_SWBUF_RX)) { + case 0: + case (USB_EP_DTOG_RX | USB_EP_SWBUF_RX): + *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_RX; + break; + default: + break; + } + if (*reg & USB_EP_SWBUF_RX) { + return pma_read(buf, blen, &(tbl->rx1)); + } else { + return pma_read(buf, blen, &(tbl->rx0)); + } + /* isochronous endpoint */ + case (USB_EP_RX_VALID | USB_EP_ISOCHRONOUS): + if (*reg & USB_EP_DTOG_RX) { + return pma_read(buf, blen, &(tbl->rx1)); + } else { + return pma_read(buf, blen, &(tbl->rx0)); + } + /* regular endpoint */ + case (USB_EP_RX_NAK | USB_EP_BULK): + case (USB_EP_RX_NAK | USB_EP_CONTROL): + case (USB_EP_RX_NAK | USB_EP_INTERRUPT): + { + int32_t res = pma_read(buf, blen, &(tbl->rx)); + /* setting endpoint to VALID state */ + EP_RX_VALID(reg); + return res; + } + /* invalid or not ready */ + default: + return -1; + } +} + +static void pma_write(const uint8_t *buf, uint16_t blen, pma_rec *tx) { + uint16_t *pma = (void*)(USB_PMAADDR + 2 * (tx->addr)); + uint16_t tmp = 0; + tx->cnt = blen; + for (int idx=0; idx < blen; idx++) { + tmp |= buf[idx] << ((idx & 0x01) ? 8 : 0); + if ((idx & 0x01) || (idx + 1) == blen) { + *pma++ = tmp; + tmp = 0; + } + } +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + pma_table *tbl = EPT(ep); + volatile uint16_t *reg = EPR(ep); + switch (*reg & (USB_EPTX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) { + /* doublebuffered bulk endpoint */ + case (USB_EP_TX_NAK | USB_EP_BULK | USB_EP_KIND): + if (*reg & USB_EP_SWBUF_TX) { + pma_write(buf, blen, &(tbl->tx1)); + } else { + pma_write(buf, blen, &(tbl->tx0)); + } + *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_TX; + break; + /* isochronous endpoint */ + case (USB_EP_TX_VALID | USB_EP_ISOCHRONOUS): + if (!(*reg & USB_EP_DTOG_TX)) { + pma_write(buf, blen, &(tbl->tx1)); + } else { + pma_write(buf, blen, &(tbl->tx0)); + } + break; + /* regular endpoint */ + case (USB_EP_TX_NAK | USB_EP_BULK): + case (USB_EP_TX_NAK | USB_EP_CONTROL): + case (USB_EP_TX_NAK | USB_EP_INTERRUPT): + pma_write(buf, blen, &(tbl->tx)); + EP_TX_VALID(reg); + break; + /* invalid or not ready */ + default: + return -1; + } + return blen; +} + +static uint16_t get_frame (void) { + return USB->FNR & USB_FNR_FN; +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint8_t _ev, _ep; + uint16_t _istr = USB->ISTR; + _ep = _istr & USB_ISTR_EP_ID; + + if (_istr & USB_ISTR_CTR) { + volatile uint16_t *reg = EPR(_ep); + if (*reg & USB_EP_CTR_TX) { + *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_TX); + _ep |= 0x80; + _ev = usbd_evt_eptx; + } else { + *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_RX); + _ev = (*reg & USB_EP_SETUP) ? usbd_evt_epsetup : usbd_evt_eprx; + } + } else if (_istr & USB_ISTR_RESET) { + USB->ISTR &= ~USB_ISTR_RESET; + USB->BTABLE = 0; + for (int i = 0; i < 8; i++) { + ep_deconfig(i); + } + _ev = usbd_evt_reset; +#if !defined(USBD_SOF_DISABLED) + } else if (_istr & USB_ISTR_SOF) { + _ev = usbd_evt_sof; + USB->ISTR &= ~USB_ISTR_SOF; +#endif + } else if (_istr & USB_ISTR_WKUP) { + _ev = usbd_evt_wkup; + USB->CNTR &= ~USB_CNTR_FSUSP; + USB->ISTR &= ~USB_ISTR_WKUP; + } else if (_istr & USB_ISTR_SUSP) { + _ev = usbd_evt_susp; + USB->CNTR |= USB_CNTR_FSUSP; + USB->ISTR &= ~USB_ISTR_SUSP; + } else if (_istr & USB_ISTR_ERR) { + USB->ISTR &= ~USB_ISTR_ERR; + _ev = usbd_evt_error; + } else { + return; + } + callback(dev, _ev, _ep); +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x14)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_devfs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32L100 diff --git a/port/stm32/usbd_stm32l100_devfs_asm.S b/port/stm32/usbd_stm32l100_devfs_asm.S new file mode 100644 index 00000000..dc3c48b2 --- /dev/null +++ b/port/stm32/usbd_stm32l100_devfs_asm.S @@ -0,0 +1,831 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined (__ASSEMBLER__) + #define __ASSEMBLER__ +#endif + +#include "usb.h" +#if defined(USBD_STM32L100) +#include "memmap.inc" + +#define EP_SETUP 0x0800 +#define EP_TYPE 0x0600 +#define EP_KIND 0x0100 +#define EP_ADDR 0x000F + +#define EP_RX_CTR 0x8000 +#define EP_RX_DTOG 0x4000 +#define EP_RX_STAT 0x3000 +#define EP_RX_SWBUF 0x0040 + +#define EP_RX_DIS 0x0000 +#define EP_RX_STAL 0x1000 +#define EP_RX_NAK 0x2000 +#define EP_RX_VAL 0x3000 + +#define EP_TX_CTR 0x0080 +#define EP_TX_DTOG 0x0040 +#define EP_TX_STAT 0x0030 +#define EP_TX_SWBUF 0x4000 + +#define EP_TX_DIS 0x0000 +#define EP_TX_STAL 0x0010 +#define EP_TX_NAK 0x0020 +#define EP_TX_VAL 0x0030 + +#define RXADDR0 0x00 +#define RXCOUNT0 0x04 +#define RXADDR1 0x08 +#define RXCOUNT1 0x0C + +#define TXADDR0 0x00 +#define TXCOUNT0 0x04 +#define TXADDR1 0x08 +#define TXCOUNT1 0x0C + +#define TXADDR 0x00 +#define TXCOUNT 0x04 +#define RXADDR 0x08 +#define RXCOUNT 0x0C + + + +#define EP_NOTOG (EP_RX_CTR | EP_TX_CTR | EP_SETUP | EP_TYPE | EP_KIND | EP_ADDR) + +#define TGL_SET(mask, bits) ((EP_NOTOG | (mask))<<16 | (bits)) + +#define TX_STALL TGL_SET(EP_TX_STAT, EP_TX_STAL) +#define RX_STALL TGL_SET(EP_RX_STAT, EP_RX_STAL) +#define TX_USTALL TGL_SET(EP_TX_STAT | EP_TX_DTOG, EP_TX_NAK) +#define RX_USTALL TGL_SET(EP_RX_STAT | EP_RX_DTOG, EP_RX_VAL) +#define DTX_USTALL TGL_SET(EP_TX_STAT | EP_TX_DTOG | EP_TX_SWBUF, EP_TX_VAL) +#define DRX_USTALL TGL_SET(EP_RX_STAT | EP_RX_DTOG | EP_RX_SWBUF, EP_RX_VAL | EP_RX_SWBUF) + + + .syntax unified + .cpu cortex-m3 + .thumb + + .section .rodata.usbd_devfs_asm + .align 4 + .globl usbd_devfs_asm +usbd_devfs_asm: + .long _getinfo + .long _enable + .long _connect + .long _setaddr + .long _ep_config + .long _ep_deconfig + .long _ep_read + .long _ep_write + .long _ep_setstall + .long _ep_isstalled + .long _evt_poll + .long _get_frame + .long _get_serial_desc + .size usbd_devfs_asm, . - usbd_devfs_asm + + .text + .align 2 + .thumb_func + .type _get_serial_desc, %function + +/* uint16_t get_serial_desc (void *buffer) + * R0 <- buffer for the string descriptor + * descrpitor size -> R0 + */ +_get_serial_desc: + push {r4, r5, lr} + movs r1, #18 //descriptor size 18 bytes + strb r1, [r0] + movs r1, #0x03 //DTYPE_STRING + strb r1, [r0, #0x01] + ldr r5, .L_uid_base //UID3 this is the serial number + ldr r4, .L_fnv1a_offset //FNV1A offset + ldr r2, [r5, 0x00] //UID0 + bl .L_fnv1a + ldr r2, [r5, 0x04] //UID1 + bl .L_fnv1a + ldr r2, [r5, 0x14] //UID2 + bl .L_fnv1a + movs r3, #28 +.L_gsn_loop: + lsrs r1, r4, r3 + and r1, #0x0F + cmp r1, #0x09 + ite gt + addgt r1, #55 + addle r1, #48 +.L_gsn_store: + adds r0, #0x02 + strb r1, [r0] + lsrs r1, #0x08 + strb r1, [r0, #0x01] + subs r3, #0x04 + bpl .L_gsn_loop + movs r0, #18 + pop {r4, r5, pc} + +.L_fnv1a: + movs r3, #0x04 +.L_fnv1a_loop: + uxtb r1, r2 + eors r4, r1 + ldr r1, .L_fnv1a_prime //FNV1A prime + muls r4, r1 + lsrs r2, #0x08 + subs r3, #0x01 + bne .L_fnv1a_loop + bx lr + + .align 2 +.L_uid_base: .long UID_BASE +.L_fnv1a_offset: .long 2166136261 +.L_fnv1a_prime: .long 16777619 + + .size _get_serial_desc, . - _get_serial_desc + + .thumb_func + .type _connect, %function +_connect: + ldr r1, =SYSCFG_BASE + movs r3, #0x01 + ldr r2, [r1, #SYSCFG_PMC] + bics r2, r3 + cbz r0, .L_conn_store + orrs r2, r3 +.L_conn_store: + str r2, [r1, #SYSCFG_PMC] + movs r0, #usbd_lane_unk + bx lr + .size _connect, . - _connect + + .thumb_func + .type _getinfo, %function +_getinfo: + movs r0, 0 + ldr r2, =RCC_BASE + ldr r1, [r2, #RCC_APB1ENR] + lsrs r1, #24 //USBEN -> CF + bcc .L_getinfo_end + adds r0, #USBD_HW_ENABLED + ldr r2, =SYSCFG_BASE + ldr r1, [r2, #SYSCFG_PMC] + lsrs r1, #1 //PU -> CF + bcc .L_getinfo_end + adds r0, #USBD_HW_SPEED_FS +.L_getinfo_end: + bx lr + .size _getinfo, . - _getinfo + + .thumb_func + .type _setaddr, %function +_setaddr: + ldr r1, =USB_REGBASE + adds r0, #0x80 + strh r0, [r1, #USB_DADDR] //USB->DADDR + bx lr + .size _setaddr, . - _setaddr + + .thumb_func + .type _get_frame, %function +_get_frame: + ldr r0, =USB_REGBASE + ldrh r0, [r0, #USB_FNR] //FNR + lsls r0, #21 + lsrs r0, #21 + bx lr + .size _get_frame, . - _get_frame + + .thumb_func + .type _enable, %function +_enable: + ldr r2, =RCC_BASE //RCC + movs r3, #0x01 + lsls r3, #23 //USBEN or USBRST + cbz r0, .L_disable +.L_enable: +/* enabling and resetting USB peripheral */ + ldr r1, =USB_REGBASE + ldr r0, [r2, #RCC_APB1ENR] + orrs r0, r3 + str r0, [r2, #RCC_APB1ENR] //RCC->APB1ENR |= USBEN + ldr r0, [r2, #RCC_APB1RSTR] + orrs r0, r3 + str r0, [r2, #RCC_APB1RSTR] //RCC->APB1RSTR |= USBRST + bics r0, r3 + str r0, [r2, #RCC_APB1RSTR] //RCC->APB1RSTR &= ~USBRST +/* enabling SYSCFG peripheral */ + movs r3, #0x01 //SYSCFGEN + ldr r0, [r2, #RCC_APB2ENR] + orrs r0, r3 + str r0, [r2, #RCC_APB2ENR] +/* setting up USB CNTR */ +#if !defined(USBD_SOF_DISABLED) + movs r0, #0xBE // CTRM | ERRM | WKUPM | SUSPM | RESETM | SOFM +#else + movs r0, #0xBC // CTRM | ERRM | WKUPM | SUSPM | RESETM +#endif + lsls r0, #0x08 + strh r0, [r1, #USB_CNTR] //set USB->CNTR + bx lr +.L_disable: + ldr r0, [r2, #RCC_APB1ENR] + tst r0, r3 + beq .L_enable_end // usb is already disabled +/* disabling USB peripheral */ + bics r0, r3 + str r0, [r2, #RCC_APB1ENR] +/* disabling USB_PU in SYSCFG_PMC */ + movs r3, #0x01 + ldr r1, =SYSCFG_BASE + ldr r0, [r1, #SYSCFG_PMC] + bics r0, r3 + str r0, [r1, #SYSCFG_PMC] + bx lr +.L_enable_end: + bx lr + .size _enable, . - _enable + + .thumb_func + .type _ep_setstall, %function + +/*void ep_settall(uint8_t ep, bool stall) + * in R0 <- endpoint number + * in R1 <- 0 if unstall, !0 if stall + */ +_ep_setstall: + push {r4, lr} + lsls r2, r0, #28 + lsrs r2, #26 + ldr r3, =USB_EPBASE + adds r3, r2 // epr -> r3 + movs r2, 0x30 // TX_STAT_MASK -> r2 + ldrh r4, [r3] + lsls r4, #21 + lsrs r4, #29 // EP_TYPE | EP_KIND -> R4 LSB + cmp r4, #0x04 // ISO ? + beq .L_eps_exit + cmp r0, #0x80 + blo .L_eps_rx +.L_eps_tx: + ldr r0, =TX_STALL //stall TX + cmp r1, #0x00 + bne .L_eps_reg_set +.L_eps_tx_unstall: + ldr r0, =DTX_USTALL //unstall dblbulk or iso TX (VALID and clr DTOG_TX & SWBUF_TX) + cmp r4, #0x01 // if doublebuffered bulk endpoint + beq .L_eps_reg_set + ldr r0, =TX_USTALL // unstall other TX (NAKED + clr DTOG_TX) + b .L_eps_reg_set +.L_eps_rx: + lsls r2, #8 // RX_STAT_MASK -> R2 + ldr r0,=RX_STALL //stall RX + cmp r1, #0x00 + bne .L_eps_reg_set +.L_eps_rx_unstall: + ldr r0, =DRX_USTALL //unstall dblbulk or iso (VALID. clr DTOG_RX set SWBUF_RX) + cmp r4, #0x01 // if dblbulk + beq .L_eps_reg_set + ldr r0, =RX_USTALL // unstall other RX (VALID + clr +/* R0 - mask and toggle bits + * R2 - mask for STAT bits + * R3 - endpoint register pointer + */ +.L_eps_reg_set: + ldrh r1, [r3] // *epr -> r1 + ands r2, r1 // check if endpoint disabled + beq .L_eps_exit // do nothing + eors r1, r0 + lsrs r0, #16 + ands r1, r0 + strh r1, [r3] +.L_eps_exit: + pop {r4, pc} + .size _ep_setstall, . - _ep_setstall + + + .thumb_func + .type _ep_isstalled, %function +/* bool ep_isstalled(uint8t ep) */ +_ep_isstalled: + ldr r1, =USB_EPBASE + lsls r2, r0, #28 + lsrs r2, #26 + ldr r1, [r1, r2] + lsls r1, #17 + cmp r0, #0x80 + bhs .L_eis_check + lsls r1, #8 +.L_eis_check: + lsrs r1, r1, #28 + subs r1, #0x01 + subs r0, r1, #0x01 + sbcs r1, r1 + rsbs r0, r1, #0 + bx lr + .size _ep_isstalled, . - _ep_isstalled + + + .thumb_func + .type _ep_read, %function +/* int32_t _ep_read(uint8_t ep, void *buf, uint16_t blen) + * in R0 <- endpoint + * in R1 <- *buffer + * in R2 <- length of the buffer + * out length of the recieved data -> R0 or 0 on error + */ +_ep_read: + push {r4, r5, lr} + ldr r3, =USB_EPBASE + ldr r4, =USB_PMABASE + lsls r0, #28 + lsrs r0, #26 + adds r3, r0 // *EPR -> R3 + lsls r0, #2 + adds r4, r0 // *EPT -> R4 + ldrh r5, [r3] // reading epr +/* validating endpoint */ + movs r0, #0x37 + lsls r0, #0x08 + ands r0, r5 + lsrs r0, #0x08 + cmp r0, #0x34 // (OK) RX_VALID + ISO + beq .L_epr_iso + cmp r0, #0x31 // (OK) RX_VALID + DBLBULK + beq .L_epr_dbl + cmp r0, #0x20 // (OK) RX_NAKED + BULK + beq .L_epr_sngl + cmp r0, #0x22 // (OK) RX_NAKED + CTRL + beq .L_epr_sngl + cmp r0, #0x26 // (OK) RX_NAKED + INTR + beq .L_epr_sngl + movs r0, #0xFF // endpoint contains no valid data + sxtb r0, r0 + b .L_epr_exit +/* processing */ +.L_epr_dbl: + lsrs r0, r5, #8 + eors r0, r5 + lsrs r0, #7 // SW_RX ^ DTOG_RX -> CF + bcs .L_epr_notog // jmp if SW_RX != DTOG_RX (VALID) + ldr r0, =EP_NOTOG + ands r5, r0 + adds r5, #EP_RX_SWBUF + strh r5, [r3] // toggling SW_RX +.L_epr_notog: + ldrh r5, [r3] + lsls r5, #8 // shift SW_RX to DTOG_RX +.L_epr_iso: + lsrs r5, #15 // DTOG_RX -> CF + bcs .L_epr_sngl + subs r4, #0x08 // set RXADDR0 +.L_epr_sngl: + ldrh r0, [r4, #RXCOUNT] + lsrs r5, r0, #0x0A + lsls r5, #0x0A // r5 = r5 & ~0x03FF + strh r5, [r4, #RXCOUNT] + lsls r0, #22 + lsrs r0, #22 // r0 &= 0x3FF (RX count) + ldrh r5, [r4, #RXADDR] + ldr r4, =USB_PMABASE + lsls r5, #0x01 + adds r5, r4 // R5 now has a physical address + cmp r2, r0 + blo .L_epr_read + mov r2, r0 // if buffer is larger +.L_epr_read: + cmp r2, #1 + blo .L_epr_read_end + ldrh r4, [r5] + strb r4, [r1] + beq .L_epr_read_end + lsrs r4, #8 + strb r4, [r1, #1] + adds r1, #2 + adds r5, #4 + subs r2, #2 + bhi .L_epr_read +.L_epr_read_end: + ldrh r5, [r3] // reload EPR + lsls r1, r5, #21 + lsrs r1, #29 + cmp r1, #0x04 + beq .L_epr_exit // ep is iso. no needs to set it to valid + cmp r1, #0x01 + beq .L_epr_exit // ep is dblbulk. no needs to set it to valid + ldr r2, =TGL_SET(EP_RX_STAT , EP_RX_VAL) + eors r5, r2 + lsrs r2, #16 + ands r5, r2 + strh r5, [r3] // set ep to VALID state +.L_epr_exit: + pop {r4, r5, pc} + .size _ep_read, . - _ep_read + + + .thumb_func + .type _ep_write, %function +/* int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) + * R0 -> endpoint + * R1 -> *buffer + * R2 -> data length + * result -> R0 + */ +_ep_write: + push {r4, r5, r6, lr} + ldr r3, =USB_EPBASE + ldr r4, =USB_PMABASE + lsls r0, #28 + lsrs r0, #26 + adds r3, r0 // *EPR -> R3 + lsls r0, #2 + adds r4, r0 // TXADDR0 -> R4 + ldrh r5, [r3] // reading epr + movs r0, #0x73 + lsls r0, #4 + ands r0, r5 + lsrs r0, #4 + cmp r0, #0x43 // (OK) TX_VALID + ISO + beq .L_epw_iso + cmp r0, #0x12 // (OK) TX_NAK + DBLBULK + beq .L_epw_dbl + cmp r0, #0x02 // (OK) TX_NAK + BULK + beq .L_epw_sngl + cmp r0, #0x22 // (OK) TX_NAK + CONTROL + beq .L_epw_sngl + cmp r0, #0x62 // (OK) TX_NAK + INTERRUPT + beq .L_epw_sngl + movs r0, #0xFF + sxtb r0, r0 + b .L_epw_exit +.L_epw_dbl: + mvns r5, r5 + lsrs r5, #8 // ~SWBUF_TX -> DTOG_TX +.L_epw_iso: + lsrs r5, #7 // DTOG_TX -> CF + bcs .L_epw_sngl + adds r4, #8 // TXADDR1 -> R4 +.L_epw_sngl: + strh r2, [r4, #TXCOUNT] + mov r0, r2 // save count for return + ldrh r5, [r4, #TXADDR] + ldr r4, =USB_PMABASE + lsls r5, #1 + adds r5, r4 // PMA BUFFER -> R5 +.L_epw_write: + cmp r2, #1 + blo .L_epw_write_end + ldrb r4, [r1] + beq .L_epw_store + ldrb r6, [r1, #1] + lsls r6, #8 + orrs r4, r6 +.L_epw_store: + strh r4, [r5] + adds r5, #4 + adds r1, #2 + subs r2, #2 + bhi .L_epw_write +.L_epw_write_end: + ldrh r5, [r3] // reload EPR + lsls r1, r5, #21 + lsrs r1, #29 + cmp r1, #0x04 + beq .L_epw_exit // isochronous ep. do nothing + ldr r2, =TGL_SET(EP_TX_STAT, EP_TX_VAL) + cmp r1, #0x01 + bne .L_epw_setstate // NOT a doublebuffered bulk + ldr r2, =TGL_SET(EP_TX_SWBUF, EP_TX_SWBUF) + bics r5, r2 // clear TX_SWBUF +.L_epw_setstate: + eors r5, r2 + lsrs r2, #16 + ands r5, r2 + strh r5, [r3] +.L_epw_exit: + pop {r4, r5, r6, pc} + .size _ep_write, .- _ep_write + + + +/* internal function */ +/* requester size passed in R2 */ +/* result returns in R0 CF=1 if OK*/ + +_get_next_pma: + push {r1, r3, r4, lr} + movs r1, #16 + movs r3, #1 + lsls r3, #9 //R3 MAX_PMA_SIZE 512b + ldr r0, =USB_PMABASE +.L_gnp_chkaddr: + ldrh r4, [r0, #0] //txaddr + tst r4, r4 + beq .L_gnp_nxtaddr + cmp r3, r4 + blo .L_gnp_nxtaddr + mov r3, r4 +.L_gnp_nxtaddr: + adds r0, #8 + subs r1, #1 + bne .L_gnp_chkaddr + subs r0, r3, r2 + blo .L_gnp_exit + cmp r0, #0x20 //check for the pma table overlap +.L_gnp_exit: + pop {r1, r3, r4, pc} + + + + .size _get_next_pma, . - _get_next_pma + + .thumb_func + .type _ep_config, %function +/* bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) + * R0 <- ep + * R1 <- eptype + * R2 <- epsize + * result -> R0 + */ +_ep_config: + push {r4, r5, lr} + movs r3, 0x01 + ands r3, r2 + adds r2, r3 //R2 -> halfword aligned epsize + movs r3, #0x00 //BULK + cmp r1, #0x02 // is eptype bulk ? + beq .L_epc_settype + movs r3, #0x01 //DBLBULK + cmp r1, #0x06 + beq .L_epc_settype + movs r3, #0x02 //CONTROL + cmp r1, #0x00 + beq .L_epc_settype + movs r3, #0x04 //ISO + cmp r1, #0x01 + beq .L_epc_settype + movs r3, #0x06 //INTERRUPT +.L_epc_settype: + lsls r3, #8 + lsls r4, r0, #28 + lsrs r4, #28 + orrs r3, r4 + lsls r4, #2 + ldr r5, =USB_EPBASE + strh r3, [r5, r4] //setup EPTYPE EPKIND EPADDR + cmp r1, #0x00 // is a control ep ? + beq .L_epc_setuptx + cmp r0, #0x80 + blo .L_epc_setuprx +.L_epc_setuptx: + ldr r5, =USB_PMABASE + lsls r4, #2 + adds r5, r4 + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #TXADDR] //store txaddr or txaddr0 + movs r0, #0x00 + strh r0, [r5, #TXCOUNT] //store txcnt + cmp r1, #0x06 // is DBLBULK + beq .L_epc_txdbl + ldr r3, =TX_USTALL //set state NAKED , clr DTOG_TX + cmp r1, #0x01 // is ISO + bne .L_epc_txsetstate // +.L_epc_txdbl: + ldr r3, =DTX_USTALL //set state VALID clr DTOG_TX & SWBUF_TX + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #TXADDR1] //store txaddr1 + movs r0, #0x00 + strh r0, [r5, #TXCOUNT1] //store txcnt +.L_epc_txsetstate: + ldr r5, =USB_EPBASE + lsrs r4, #2 + ldrh r0, [r5, r4] + eors r0, r3 + lsrs r3, #16 + ands r0, r3 + strh r0, [r5, r4] + cmp r1, #0x00 //is a control ep ? + bne .L_epc_exit +.L_epc_setuprx: + movs r3, r2 + cmp r2, #62 + bls .L_epc_rxbb + movs r3, #0x1F + adds r2, r3 + bics r2, r3 + lsrs r3, r2, #5 + adds r3, #0x3E +.L_epc_rxbb: + lsls r3, #9 + ldr r5, =USB_PMABASE + lsls r4, #2 + adds r5, r4 +/* RX or RX1 */ + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #RXADDR] + strh r3, [r5, #RXCOUNT] + ldr r0, =RX_USTALL +/* check if doublebuffered */ + cmp r1, 0x06 //if dblbulk + beq .L_epc_rxdbl + cmp r1, 0x01 // iso + bne .L_epc_rxsetstate +.L_epc_rxdbl: + bl _get_next_pma + bcc .L_epc_fail + strh r0, [r5, #RXADDR0] //store rxaddr0 + strh r3, [r5, #RXCOUNT0] //store rxcnt0 + ldr r0, =DRX_USTALL +.L_epc_rxsetstate: + ldr r5, =USB_EPBASE + lsrs r4, #2 + ldrh r3, [r5, r4] + eors r3, r0 + lsrs r0, #16 + ands r3, r0 + strh r3, [r5, r4] +.L_epc_exit: + movs r0, #0x01 + pop {r4, r5, pc} +.L_epc_fail: + movs r0, #0x00 + pop {r4, r5, pc} + + .size _ep_config, . - _ep_config + + .thumb_func + .type _ep_deconfig, %function + + +/* void ep_deconfig( uint8_t ep) + * R0 <- ep + */ +_ep_deconfig: + lsls r1, r0, #28 + lsrs r1, #26 + ldr r2, =USB_EPBASE + ldr r3, =USB_PMABASE + adds r2, r1 + lsls r1, #1 + adds r3, r1 +/* clearing endpoint register */ + ldr r1, =EP_NOTOG + ldrh r0, [r2] + bics r0, r1 + strh r0, [r2] +/* clearing PMA data */ + movs r0, #0x00 + strh r0, [r3, #TXADDR] + strh r0, [r3, #TXCOUNT] + strh r0, [r3, #RXADDR] + strh r0, [r3, #RXCOUNT] + bx lr + + .size _ep_deconfig, . - _ep_config + + + + +#define ISTRSHIFT 8 +#define ISTRBIT(bit) ((1 << bit) >> ISTRSHIFT) + + + .thumb_func + .type _evt_poll, %function +/*void evt_poll(usbd_device *dev, usbd_evt_callback callback)*/ +_evt_poll: + push {r0, r1, r4, r5} + ldr r3, =USB_REGBASE + ldrh r0, [r3, #4] //USB->ISTR -> R2 +/* ep_index -> R2 */ + movs r2, 0x07 + ands r2, r0 +/* checking USB->ISTR for events */ +#if !defined(USBD_SOF_DISABLED) + lsrs r1, r0, #10 //SOFM -> CF + bcs .L_ep_sofm +#endif + lsrs r1, r0, #11 //RESETM -> CF + bcs .L_ep_resetm + lsrs r1, r0, #16 //CTRM -> CF + bcs .L_ep_ctrm + lsrs r1, r0, #14 //ERRM -> CF + bcs .L_ep_errm + lsrs r1, r0, #13 //WKUPM -> CF + bcs .L_ep_wkupm + lsrs r1, r0, #12 //SUSPM -> CF + bcs .L_ep_suspm + /* exit with no callback */ + pop {r0, r1, r4 , r5} + bx lr + +.L_ep_ctrm: + movs r5, #0x80 // CTR_TX mask to R5 + ldr r0,=USB_EPBASE + lsrs r0, #2 + adds r0, r2 + lsls r0, #2 // R0 ep register address + ldrh r4, [r0] // R4 EPR valur + lsrs r3, r4, #8 // CTR_TX -> CF + bcc .L_ep_ctr_rx +/* CTR_TX event */ + movs r1, #usbd_evt_eptx + orrs r2, r5 // set endpoint tx + b .L_ep_clr_ctr +.L_ep_ctr_rx: +/* CTR_RX RX or SETUP */ + lsls r5, #0x08 // set mask to CRT_RX + movs r1, #usbd_evt_eprx + lsls r3, r4, #21 //SETUP -> CF + bcc .L_ep_clr_ctr + movs r1, #usbd_evt_epsetup +.L_ep_clr_ctr: + bics r4, r5 //clear CTR flag + ldr r5, =EP_NOTOG + ands r4, r5 + strh r4, [r0] // store + b .L_ep_callback +.L_ep_errm: + movs r1, #usbd_evt_error + movs r4, #ISTRBIT(13) + b .L_ep_clristr +#if !defined(USBD_SOF_DISABLED) +.L_ep_sofm: + movs r1, #usbd_evt_sof + movs r4, #ISTRBIT(9) + b .L_ep_clristr +#endif +.L_ep_wkupm: + ldrh r1, [r3, #0] //R1 USB->CNTR + movs r5, #0x08 + bics r1, r5 //clr FSUSP + strh r1, [r3, #0] //USB->CNTR R2 + movs r1, #usbd_evt_wkup + movs r4, #ISTRBIT(12) + b .L_ep_clristr + +.L_ep_suspm: + ldrh r1, [r3, #0] //R1 USB->CNTR + movs r5, #0x08 + orrs r1, r5 //set FSUSP + strh r1, [r3, #0] //USB->CNTR R2 + movs r1, #usbd_evt_susp + movs r4, #ISTRBIT(11) + b .L_ep_clristr + +/* do reset routine */ +.L_ep_resetm: + movs r1, #7 + ldr r2, =USB_EPBASE + ldr r0, =USB_PMABASE + ldr r5, =EP_NOTOG +.L_ep_reset_loop: + ldrh r4, [r2] + bics r4, r5 + strh r4, [r2] + movs r4, #0 + strh r4, [r0, #TXADDR] + strh r4, [r0, #TXCOUNT] + strh r4, [r0, #RXADDR] + strh r4, [r0, #RXCOUNT] + adds r2, #0x04 + adds r0, #0x10 + subs r1, #1 + bpl .L_ep_reset_loop + movs r2, #0x00 + strh r2, [r3, #0x10] // 0 -> USB->BTABLE + movs r1, #usbd_evt_reset + movs r4, #ISTRBIT(10) +.L_ep_clristr: + lsls r4, #ISTRSHIFT + ldrh r0, [r3, #4] + bics r0, r4 + strh r0, [r3, #4] +.L_ep_callback: + pop {r0, r3, r4, r5 } + bx r3 + + .size _evt_poll, . - _evt_poll + + .pool + + .end + +#endif //USBD_STM32L100 diff --git a/port/stm32/usbd_stm32l433_devfs.c b/port/stm32/usbd_stm32l433_devfs.c new file mode 100644 index 00000000..c738c67f --- /dev/null +++ b/port/stm32/usbd_stm32l433_devfs.c @@ -0,0 +1,473 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32L433) + +#ifndef USB_PMASIZE + #pragma message "PMA memory size is not defined. Use 1k by default" + #define USB_PMASIZE 0x400 +#endif + +#if !defined(RCC_APB1ENR1_USBFSEN) + #define RCC_APB1ENR1_USBFSEN RCC_APB1ENR1_USBEN + #define RCC_APB1RSTR1_USBFSRST RCC_APB1RSTR1_USBRST +#endif + +#define USB_EP_SWBUF_TX USB_EP_DTOG_RX +#define USB_EP_SWBUF_RX USB_EP_DTOG_TX + +#define EP_TOGGLE_SET(epr, bits, mask) *(epr) = (*(epr) ^ (bits)) & (USB_EPREG_MASK | (mask)) + +#define EP_TX_STALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_STALL, USB_EPTX_STAT) +#define EP_RX_STALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_STALL, USB_EPRX_STAT) +#define EP_TX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_NAK, USB_EPTX_STAT | USB_EP_DTOG_TX) +#define EP_RX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID, USB_EPRX_STAT | USB_EP_DTOG_RX) +#define EP_DTX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID, USB_EPTX_STAT | USB_EP_DTOG_TX | USB_EP_SWBUF_TX) +#define EP_DRX_UNSTALL(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID | USB_EP_SWBUF_RX, USB_EPRX_STAT | USB_EP_DTOG_RX | USB_EP_SWBUF_RX) +#define EP_TX_VALID(epr) EP_TOGGLE_SET((epr), USB_EP_TX_VALID, USB_EPTX_STAT) +#define EP_RX_VALID(epr) EP_TOGGLE_SET((epr), USB_EP_RX_VALID, USB_EPRX_STAT) + +#define STATUS_VAL(x) (USBD_HW_BC | (x)) + +typedef struct { + uint16_t addr; + uint16_t cnt; +} pma_rec; + +typedef union pma_table { + struct { + pma_rec tx; + pma_rec rx; + }; + struct { + pma_rec tx0; + pma_rec tx1; + }; + struct { + pma_rec rx0; + pma_rec rx1; + }; +} pma_table; + + +/** \brief Helper function. Returns pointer to the buffer descriptor table. + */ +inline static pma_table *EPT(uint8_t ep) { + return (pma_table*)((ep & 0x07) * 8 + USB_PMAADDR); + +} + +/** \brief Helper function. Returns pointer to the endpoint control register. + */ +inline static volatile uint16_t *EPR(uint8_t ep) { + return (uint16_t*)((ep & 0x07) * 4 + USB_BASE); +} + + +/** \brief Helper function. Returns next available PMA buffer. + * + * \param sz uint16_t Requested buffer size. + * \return uint16_t Buffer address for PMA table. + * \note PMA buffers grown from top to bottom like stack. + */ +static uint16_t get_next_pma(uint16_t sz) { + unsigned _result = USB_PMASIZE; + for (int i = 0; i < 8; i++) { + pma_table *tbl = EPT(i); + if ((tbl->rx.addr) && (tbl->rx.addr < _result)) _result = tbl->rx.addr; + if ((tbl->tx.addr) && (tbl->tx.addr < _result)) _result = tbl->tx.addr; + } + return (_result < (0x020 + sz)) ? 0 : (_result - sz); +} + +static uint32_t getinfo(void) { + if (!(RCC->APB1ENR1 & RCC_APB1ENR1_USBFSEN)) return STATUS_VAL(0); + if (USB->BCDR & USB_BCDR_DPPU) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +} + +static void ep_setstall(uint8_t ep, bool stall) { + volatile uint16_t *reg = EPR(ep); + /* ISOCHRONOUS endpoint can't be stalled or unstalled */ + if (USB_EP_ISOCHRONOUS == (*reg & USB_EP_T_FIELD)) return; + /* If it's an IN endpoint */ + if (ep & 0x80) { + /* DISABLED endpoint can't be stalled or unstalled */ + if (USB_EP_TX_DIS == (*reg & USB_EPTX_STAT)) return; + if (stall) { + EP_TX_STALL(reg); + } else { + /* if it's a doublebuffered endpoint */ + if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) { + /* set endpoint to VALID and clear DTOG_TX & SWBUF_TX */ + EP_DTX_UNSTALL(reg); + } else { + /* set endpoint to NAKED and clear DTOG_TX */ + EP_TX_UNSTALL(reg); + } + } + } else { + if (USB_EP_RX_DIS == (*reg & USB_EPRX_STAT)) return; + if (stall) { + EP_RX_STALL(reg); + } else { + /* if it's a doublebuffered endpoint */ + if ((USB_EP_KIND | USB_EP_BULK) == (*reg & (USB_EP_T_FIELD | USB_EP_KIND))) { + /* set endpoint to VALID, clear DTOG_RX, set SWBUF_RX */ + EP_DRX_UNSTALL(reg); + } else { + /* set endpoint to VALID and clear DTOG_RX */ + EP_RX_UNSTALL(reg); + } + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + return (USB_EP_TX_STALL == (USB_EPTX_STAT & *EPR(ep))); + } else { + return (USB_EP_RX_STALL == (USB_EPRX_STAT & *EPR(ep))); + } +} + +static void enable(bool enable) { + if (enable) { + RCC->APB1ENR1 |= RCC_APB1ENR1_USBFSEN; + RCC->APB1RSTR1 |= RCC_APB1RSTR1_USBFSRST; + RCC->APB1RSTR1 &= ~RCC_APB1RSTR1_USBFSRST; + USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM | USB_CNTR_ERRM | +#if !defined(USBD_SOF_DISABLED) + USB_CNTR_SOFM | +#endif + USB_CNTR_SUSPM | USB_CNTR_WKUPM; + } else if (RCC->APB1ENR1 & RCC_APB1ENR1_USBFSEN) { + USB->BCDR = 0; + RCC->APB1RSTR1 |= RCC_APB1RSTR1_USBFSRST; + RCC->APB1ENR1 &= ~RCC_APB1ENR1_USBFSEN; + } +} + +static uint8_t connect(bool connect) { + uint8_t res; + USB->BCDR = USB_BCDR_BCDEN | USB_BCDR_DCDEN; + if (USB->BCDR & USB_BCDR_DCDET) { + USB->BCDR = USB_BCDR_BCDEN | USB_BCDR_PDEN; + if (USB->BCDR & USB_BCDR_PS2DET) { + res = usbd_lane_unk; + } else if (USB->BCDR & USB_BCDR_PDET) { + USB->BCDR = USB_BCDR_BCDEN | USB_BCDR_SDEN; + if (USB->BCDR & USB_BCDR_SDET) { + res = usbd_lane_dcp; + } else { + res = usbd_lane_cdp; + } + } else { + res = usbd_lane_sdp; + } + } else { + res = usbd_lane_dsc; + } + USB->BCDR = (connect) ? USB_BCDR_DPPU : 0; + return res; +} + +static void setaddr (uint8_t addr) { + USB->DADDR = USB_DADDR_EF | addr; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + volatile uint16_t *reg = EPR(ep); + pma_table *tbl = EPT(ep); + /* epsize must be 2-byte aligned */ + epsize = (~0x01U) & (epsize + 1); + + switch (eptype) { + case USB_EPTYPE_CONTROL: + *reg = USB_EP_CONTROL | (ep & 0x07); + break; + case USB_EPTYPE_ISOCHRONUS: + *reg = USB_EP_ISOCHRONOUS | (ep & 0x07); + break; + case USB_EPTYPE_BULK: + *reg = USB_EP_BULK | (ep & 0x07); + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + *reg = USB_EP_BULK | USB_EP_KIND | (ep & 0x07); + break; + default: + *reg = USB_EP_INTERRUPT | (ep & 0x07); + break; + } + /* if it TX or CONTROL endpoint */ + if ((ep & 0x80) || (eptype == USB_EPTYPE_CONTROL)) { + uint16_t _pma; + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->tx.addr = _pma; + tbl->tx.cnt = 0; + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->tx1.addr = _pma; + tbl->tx1.cnt = 0; + EP_DTX_UNSTALL(reg); + } else { + EP_TX_UNSTALL(reg); + } + } + if (!(ep & 0x80)) { + uint16_t _rxcnt; + uint16_t _pma; + if (epsize > 62) { + /* using 32-byte blocks. epsize must be 32-byte aligned */ + epsize = (~0x1FU) & (epsize + 0x1FU); + _rxcnt = 0x8000 - 0x400 + (epsize << 5); + } else { + _rxcnt = epsize << 9; + } + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->rx.addr = _pma; + tbl->rx.cnt = _rxcnt; + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + _pma = get_next_pma(epsize); + if (_pma == 0) return false; + tbl->rx0.addr = _pma; + tbl->rx0.cnt = _rxcnt; + EP_DRX_UNSTALL(reg); + } else { + EP_RX_UNSTALL(reg); + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + pma_table *ept = EPT(ep); + *EPR(ep) &= ~USB_EPREG_MASK; + ept->rx.addr = 0; + ept->rx.cnt = 0; + ept->tx.addr = 0; + ept->tx.cnt = 0; +} + +static uint16_t pma_read (uint8_t *buf, uint16_t blen, pma_rec *rx) { + uint16_t *pma = (void*)(USB_PMAADDR + rx->addr); + uint16_t rxcnt = rx->cnt & 0x03FF; + rx->cnt &= ~0x3FF; + + if (blen > rxcnt) { + blen = rxcnt; + } + rxcnt = blen; + while (blen) { + uint16_t _t = *pma; + *buf++ = _t & 0xFF; + if (--blen) { + *buf++ = _t >> 8; + pma++; + blen--; + } else break; + } + return rxcnt; +} + +static int32_t ep_read(uint8_t ep, void *buf, uint16_t blen) { + pma_table *tbl = EPT(ep); + volatile uint16_t *reg = EPR(ep); + switch (*reg & (USB_EPRX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) { + /* doublebuffered bulk endpoint */ + case (USB_EP_RX_VALID | USB_EP_BULK | USB_EP_KIND): + /* switching SWBUF if EP is NAKED */ + switch (*reg & (USB_EP_DTOG_RX | USB_EP_SWBUF_RX)) { + case 0: + case (USB_EP_DTOG_RX | USB_EP_SWBUF_RX): + *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_RX; + break; + default: + break; + } + if (*reg & USB_EP_SWBUF_RX) { + return pma_read(buf, blen, &(tbl->rx1)); + } else { + return pma_read(buf, blen, &(tbl->rx0)); + } + /* isochronous endpoint */ + case (USB_EP_RX_VALID | USB_EP_ISOCHRONOUS): + if (*reg & USB_EP_DTOG_RX) { + return pma_read(buf, blen, &(tbl->rx1)); + } else { + return pma_read(buf, blen, &(tbl->rx0)); + } + /* regular endpoint */ + case (USB_EP_RX_NAK | USB_EP_BULK): + case (USB_EP_RX_NAK | USB_EP_CONTROL): + case (USB_EP_RX_NAK | USB_EP_INTERRUPT): + { + int32_t res = pma_read(buf, blen, &(tbl->rx)); + /* setting endpoint to VALID state */ + EP_RX_VALID(reg); + return res; + } + /* invalid or not ready */ + default: + return -1; + } +} + +static void pma_write(uint8_t *buf, uint16_t blen, pma_rec *tx) { + uint16_t *pma = (void*)(USB_PMAADDR + tx->addr); + tx->cnt = blen; + while (blen > 1) { + *pma++ = buf[1] << 8 | buf[0]; + buf += 2; + blen -= 2; + } + if (blen) *pma = *buf; +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + pma_table *tbl = EPT(ep); + volatile uint16_t *reg = EPR(ep); + switch (*reg & (USB_EPTX_STAT | USB_EP_T_FIELD | USB_EP_KIND)) { + /* doublebuffered bulk endpoint */ + case (USB_EP_TX_NAK | USB_EP_BULK | USB_EP_KIND): + if (*reg & USB_EP_SWBUF_TX) { + pma_write(buf, blen, &(tbl->tx1)); + } else { + pma_write(buf, blen, &(tbl->tx0)); + } + *reg = (*reg & USB_EPREG_MASK) | USB_EP_SWBUF_TX; + break; + /* isochronous endpoint */ + case (USB_EP_TX_VALID | USB_EP_ISOCHRONOUS): + if (!(*reg & USB_EP_DTOG_TX)) { + pma_write(buf, blen, &(tbl->tx1)); + } else { + pma_write(buf, blen, &(tbl->tx0)); + } + break; + /* regular endpoint */ + case (USB_EP_TX_NAK | USB_EP_BULK): + case (USB_EP_TX_NAK | USB_EP_CONTROL): + case (USB_EP_TX_NAK | USB_EP_INTERRUPT): + pma_write(buf, blen, &(tbl->tx)); + EP_TX_VALID(reg); + break; + /* invalid or not ready */ + default: + return -1; + } + return blen; +} + +static uint16_t get_frame (void) { + return USB->FNR & USB_FNR_FN; +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint8_t _ev, _ep; + uint16_t _istr = USB->ISTR; + _ep = _istr & USB_ISTR_EP_ID; + if (_istr & USB_ISTR_CTR) { + volatile uint16_t *reg = EPR(_ep); + if (*reg & USB_EP_CTR_TX) { + *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_TX); + _ep |= 0x80; + _ev = usbd_evt_eptx; + } else { + *reg &= (USB_EPREG_MASK ^ USB_EP_CTR_RX); + _ev = (*reg & USB_EP_SETUP) ? usbd_evt_epsetup : usbd_evt_eprx; + } + } else if (_istr & USB_ISTR_RESET) { + USB->ISTR &= ~USB_ISTR_RESET; + USB->BTABLE = 0; + for (int i = 0; i < 8; i++) { + ep_deconfig(i); + } + _ev = usbd_evt_reset; +#if !defined(USBD_SOF_DISABLED) + } else if (_istr & USB_ISTR_SOF) { + _ev = usbd_evt_sof; + USB->ISTR &= ~USB_ISTR_SOF; +#endif + } else if (_istr & USB_ISTR_WKUP) { + _ev = usbd_evt_wkup; + USB->CNTR &= ~USB_CNTR_FSUSP; + USB->ISTR &= ~USB_ISTR_WKUP; + } else if (_istr & USB_ISTR_SUSP) { + _ev = usbd_evt_susp; + USB->CNTR |= USB_CNTR_FSUSP; + USB->ISTR &= ~USB_ISTR_SUSP; + } else if (_istr & USB_ISTR_ERR) { + USB->ISTR &= ~USB_ISTR_ERR; + _ev = usbd_evt_error; + } else { + return; + } + callback(dev, _ev, _ep); +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x14)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_devfs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32L052 diff --git a/port/stm32/usbd_stm32l476_otgfs.c b/port/stm32/usbd_stm32l476_otgfs.c new file mode 100644 index 00000000..a0246b50 --- /dev/null +++ b/port/stm32/usbd_stm32l476_otgfs.c @@ -0,0 +1,500 @@ +/* This file is the part of the Lightweight USB device Stack for STM32 microcontrollers + * + * Copyright ©2016 Dmitry Filimonchuk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "stm32.h" +#include "usb.h" + +#if defined(USBD_STM32L476) + +#define MAX_EP 6 +#define MAX_RX_PACKET 128 +#define MAX_CONTROL_EP 1 +#define MAX_FIFO_SZ 320 /*in 32-bit chunks */ + +#define RX_FIFO_SZ ((4 * MAX_CONTROL_EP + 6) + ((MAX_RX_PACKET / 4) + 1) + (MAX_EP * 2) + 1) + +#define STATUS_VAL(x) (USBD_HW_BC | USBD_HW_ADDRFST | (x)) + +static USB_OTG_GlobalTypeDef * const OTG = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_GLOBAL_BASE); +static USB_OTG_DeviceTypeDef * const OTGD = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_DEVICE_BASE); +static volatile uint32_t * const OTGPCTL = (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_PCGCCTL_BASE); + + +inline static uint32_t* EPFIFO(uint32_t ep) { + return (uint32_t*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_FIFO_BASE + (ep << 12)); +} + +inline static USB_OTG_INEndpointTypeDef* EPIN(uint32_t ep) { + return (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE + (ep << 5)); +} + +inline static USB_OTG_OUTEndpointTypeDef* EPOUT(uint32_t ep) { + return (void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_OUT_ENDPOINT_BASE + (ep << 5)); +} + +inline static void Flush_RX(void) { + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); +} + +inline static void Flush_TX(uint8_t ep) { + _BMD(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFNUM, + _VAL2FLD(USB_OTG_GRSTCTL_TXFNUM, ep) | USB_OTG_GRSTCTL_TXFFLSH); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_TXFFLSH); +} + +static uint32_t getinfo(void) { + if (!(RCC->AHB2ENR & RCC_AHB2ENR_OTGFSEN)) return STATUS_VAL(0); + if (!(OTGD->DCTL & USB_OTG_DCTL_SDIS)) return STATUS_VAL(USBD_HW_ENABLED | USBD_HW_SPEED_FS); + return STATUS_VAL(USBD_HW_ENABLED); +} + +static void ep_setstall(uint8_t ep, bool stall) { + if (ep & 0x80) { + ep &= 0x7F; + uint32_t _t = EPIN(ep)->DIEPCTL; + if (_t & USB_OTG_DIEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DIEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DIEPCTL_STALL, + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_SNAK); + } + EPIN(ep)->DIEPCTL = _t; + } + } else { + uint32_t _t = EPOUT(ep)->DOEPCTL; + if (_t & USB_OTG_DOEPCTL_USBAEP) { + if (stall) { + _BST(_t, USB_OTG_DOEPCTL_STALL); + } else { + _BMD(_t, USB_OTG_DOEPCTL_STALL, + USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK); + } + EPOUT(ep)->DOEPCTL = _t; + } + } +} + +static bool ep_isstalled(uint8_t ep) { + if (ep & 0x80) { + ep &= 0x7F; + return (EPIN(ep)->DIEPCTL & USB_OTG_DIEPCTL_STALL) ? true : false; + } else { + return (EPOUT(ep)->DOEPCTL & USB_OTG_DOEPCTL_STALL) ? true : false; + } +} + +static void enable(bool enable) { + if (enable) { + /* enabling USB_OTG in RCC */ + _BST(RCC->AHB2ENR, RCC_AHB2ENR_OTGFSEN); + /* Set Vbus enabled for USB */ + _BST(PWR->CR2, PWR_CR2_USV); + /* select Internal PHY */ + OTG->GUSBCFG |= USB_OTG_GUSBCFG_PHYSEL; + /* do core soft reset */ + _WBS(OTG->GRSTCTL, USB_OTG_GRSTCTL_AHBIDL); + _BST(OTG->GRSTCTL, USB_OTG_GRSTCTL_CSRST); + _WBC(OTG->GRSTCTL, USB_OTG_GRSTCTL_CSRST); + /* configure OTG as device */ + OTG->GUSBCFG = USB_OTG_GUSBCFG_FDMOD | USB_OTG_GUSBCFG_PHYSEL | + _VAL2FLD(USB_OTG_GUSBCFG_TRDT, 0x06); + /* configuring Vbus sense and powerup PHY */ +#if defined(USBD_VBUS_DETECT) + OTG->GCCFG |= USB_OTG_GCCFG_VBDEN | USB_OTG_GCCFG_PWRDWN; +#else + OTG->GOTGCTL |= USB_OTG_GOTGCTL_BVALOEN | USB_OTG_GOTGCTL_BVALOVAL; + OTG->GCCFG = USB_OTG_GCCFG_PWRDWN; +#endif + /* restart PHY*/ + *OTGPCTL = 0; + /* soft disconnect device */ + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + /* Setup USB FS speed and frame interval */ + _BMD(OTGD->DCFG, USB_OTG_DCFG_PERSCHIVL | USB_OTG_DCFG_DSPD, + _VAL2FLD(USB_OTG_DCFG_PERSCHIVL, 0) | _VAL2FLD(USB_OTG_DCFG_DSPD, 0x03)); + /* unmask EP interrupts */ + OTGD->DIEPMSK = USB_OTG_DIEPMSK_XFRCM; + /* unmask core interrupts */ + OTG->GINTMSK = USB_OTG_GINTMSK_USBRST | USB_OTG_GINTMSK_ENUMDNEM | +#if !defined(USBD_SOF_DISABLED) + USB_OTG_GINTMSK_SOFM | +#endif + USB_OTG_GINTMSK_USBSUSPM | USB_OTG_GINTMSK_WUIM | + USB_OTG_GINTMSK_IEPINT | USB_OTG_GINTMSK_RXFLVLM; + /* clear pending interrupts */ + OTG->GINTSTS = 0xFFFFFFFF; + /* unmask global interrupt */ + OTG->GAHBCFG = USB_OTG_GAHBCFG_GINT; + /* setting max RX FIFO size */ + OTG->GRXFSIZ = RX_FIFO_SZ; + /* setting up EP0 TX FIFO SZ as 64 byte */ + OTG->DIEPTXF0_HNPTXFSIZ = RX_FIFO_SZ | (0x10 << 16); + } else { + if (RCC->AHB2ENR & RCC_AHB2ENR_OTGFSEN) { + _BCL(PWR->CR2, PWR_CR2_USV); + _BST(RCC->AHB2RSTR, RCC_AHB2RSTR_OTGFSRST); + _BCL(RCC->AHB2RSTR, RCC_AHB2RSTR_OTGFSRST); + _BCL(RCC->AHB2ENR, RCC_AHB2ENR_OTGFSEN); + } + } +} + +static uint8_t connect(bool connect) { + uint8_t res; +#if defined(USBD_VBUS_DETECT) + #define SET_GCCFG(x) OTG->GCCFG = USB_OTG_GCCFG_VBDEN | (x) +#else + #define SET_GCCFG(x) OTG->GCCFG = (x) +#endif + SET_GCCFG(USB_OTG_GCCFG_BCDEN | USB_OTG_GCCFG_DCDEN); + if (OTG->GCCFG & USB_OTG_GCCFG_DCDET) { + SET_GCCFG(USB_OTG_GCCFG_BCDEN | USB_OTG_GCCFG_PDEN); + if (OTG->GCCFG & USB_OTG_GCCFG_PS2DET) { + res = usbd_lane_unk; + } else if (OTG->GCCFG & USB_OTG_GCCFG_PDET) { + SET_GCCFG(USB_OTG_GCCFG_BCDEN | USB_OTG_GCCFG_SDEN); + if (OTG->GCCFG & USB_OTG_GCCFG_SDET) { + res = usbd_lane_dcp; + } else { + res = usbd_lane_cdp; + } + } else { + res = usbd_lane_sdp; + } + } else { + res = usbd_lane_dsc; + } + SET_GCCFG(USB_OTG_GCCFG_PWRDWN); + if (connect) { + _BCL(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } else { + _BST(OTGD->DCTL, USB_OTG_DCTL_SDIS); + } + return res; +} + +static void setaddr (uint8_t addr) { + _BMD(OTGD->DCFG, USB_OTG_DCFG_DAD, addr << 4); +} + +/**\brief Helper. Set up TX fifo + * \param ep endpoint index + * \param epsize required max packet size in bytes + * \return true if TX fifo is successfully set + */ +static bool set_tx_fifo(uint8_t ep, uint16_t epsize) { + uint32_t _fsa = OTG->DIEPTXF0_HNPTXFSIZ; + /* calculating initial TX FIFO address. next from EP0 TX fifo */ + _fsa = 0xFFFF & (_fsa + (_fsa >> 16)); + /* looking for next free TX fifo address */ + for (int i = 0; i < (MAX_EP - 1); i++) { + uint32_t _t = OTG->DIEPTXF[i]; + if ((_t & 0xFFFF) < 0x200) { + _t = 0xFFFF & (_t + (_t >> 16)); + if (_t > _fsa) { + _fsa = _t; + } + } + } + /* calculating requited TX fifo size */ + /* getting in 32 bit terms */ + epsize = (epsize + 0x03) >> 2; + /* it must be 16 32-bit words minimum */ + if (epsize < 0x10) epsize = 0x10; + /* checking for the available fifo */ + if ((_fsa + epsize) > MAX_FIFO_SZ) return false; + /* programming fifo register */ + _fsa |= (epsize << 16); + OTG->DIEPTXF[ep - 1] = _fsa; + return true; +} + +static bool ep_config(uint8_t ep, uint8_t eptype, uint16_t epsize) { + if (ep == 0) { + /* configureing control endpoint EP0 */ + uint32_t mpsize; + if (epsize <= 0x08) { + epsize = 0x08; + mpsize = 0x03; + } else if (epsize <= 0x10) { + epsize = 0x10; + mpsize = 0x02; + } else if (epsize <= 0x20) { + epsize = 0x20; + mpsize = 0x01; + } else { + epsize = 0x40; + mpsize = 0x00; + } + /* EP0 TX FIFO size is setted on init level */ + /* enabling RX and TX interrupts from EP0 */ + OTGD->DAINTMSK |= 0x00010001; + /* setting up EP0 TX and RX registers */ + /*EPIN(ep)->DIEPTSIZ = epsize;*/ + EPIN(ep)->DIEPCTL = mpsize | USB_OTG_DIEPCTL_SNAK; + /* 1 setup packet, 1 packets total */ + EPOUT(ep)->DOEPTSIZ = epsize | (1 << 29) | (1 << 19); + EPOUT(ep)->DOEPCTL = mpsize | USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + return true; + } + if (ep & 0x80) { + ep &= 0x7F; + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* configuring TX endpoint */ + /* setting up TX fifo and size register */ + if ((eptype == USB_EPTYPE_ISOCHRONUS) || + (eptype == (USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF))) { + if (!set_tx_fifo(ep, epsize << 1)) return false; + } else { + if (!set_tx_fifo(ep, epsize)) return false; + } + /* enabling EP TX interrupt */ + OTGD->DAINTMSK |= (0x0001UL << ep); + /* setting up TX control register*/ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epi->DIEPCTL = USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK | + (0x01 << 18) | USB_OTG_DIEPCTL_USBAEP | + USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + case USB_EPTYPE_BULK: + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x02 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + default: + epi->DIEPCTL = USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP | + (0x03 << 18) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM | + (ep << 22) | epsize; + break; + } + } else { + /* configuring RX endpoint */ + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* setting up RX control register */ + switch (eptype) { + case USB_EPTYPE_ISOCHRONUS: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x01 << 18) | epsize; + break; + case USB_EPTYPE_BULK | USB_EPTYPE_DBLBUF: + case USB_EPTYPE_BULK: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x02 << 18) | epsize; + break; + default: + epo->DOEPCTL = USB_OTG_DOEPCTL_SD0PID_SEVNFRM | USB_OTG_DOEPCTL_CNAK | + USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_USBAEP | + (0x03 << 18) | epsize; + break; + } + } + return true; +} + +static void ep_deconfig(uint8_t ep) { + ep &= 0x7F; + volatile USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + volatile USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* deconfiguring TX part */ + /* disable interrupt */ + OTGD->DAINTMSK &= ~(0x10001 << ep); + /* decativating endpoint */ + _BCL(epi->DIEPCTL, USB_OTG_DIEPCTL_USBAEP); + /* flushing FIFO */ + Flush_TX(ep); + /* disabling endpoint */ + if ((epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) && (ep != 0)) { + epi->DIEPCTL = USB_OTG_DIEPCTL_EPDIS; + } + /* clean EP interrupts */ + epi->DIEPINT = 0xFF; + /* deconfiguring TX FIFO */ + if (ep > 0) { + OTG->DIEPTXF[ep-1] = 0x02000200 + 0x200 * ep; + } + /* deconfigureing RX part */ + _BCL(epo->DOEPCTL, USB_OTG_DOEPCTL_USBAEP); + if ((epo->DOEPCTL & USB_OTG_DOEPCTL_EPENA) && (ep != 0)) { + epo->DOEPCTL = USB_OTG_DOEPCTL_EPDIS; + } + epo->DOEPINT = 0xFF; +} + +static int32_t ep_read(uint8_t ep, void* buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t *fifo = EPFIFO(0); + USB_OTG_OUTEndpointTypeDef* epo = EPOUT(ep); + /* no data in RX FIFO */ + if (!(OTG->GINTSTS & USB_OTG_GINTSTS_RXFLVL)) return -1; + if ((OTG->GRXSTSR & USB_OTG_GRXSTSP_EPNUM) != ep) return -1; + /* pop data from fifo */ + len = _FLD2VAL(USB_OTG_GRXSTSP_BCNT, OTG->GRXSTSP); + for (int idx = 0; idx < len; idx++) { + if ((idx & 0x03) == 0x00) { + tmp = *fifo; + } + if (idx < blen) { + ((uint8_t*)buf)[idx] = tmp & 0xFF; + tmp >>= 8; + } + } + _BST(epo->DOEPCTL, USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA); + return (len < blen) ? len : blen; +} + +static int32_t ep_write(uint8_t ep, void *buf, uint16_t blen) { + uint32_t len, tmp; + ep &= 0x7F; + volatile uint32_t* fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + /* transfer data size in 32-bit words */ + len = (blen + 3) >> 2; + /* no enough space in TX fifo */ + if (len > epi->DTXFSTS) return -1; + if (ep != 0 && epi->DIEPCTL & USB_OTG_DIEPCTL_EPENA) { + return -1; + } + epi->DIEPTSIZ = 0; + epi->DIEPTSIZ = (1 << 19) + blen; + _BMD(epi->DIEPCTL, USB_OTG_DIEPCTL_STALL, USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK); + /* push data to FIFO */ + tmp = 0; + for (int idx = 0; idx < blen; idx++) { + tmp |= (uint32_t)((uint8_t*)buf)[idx] << ((idx & 0x03) << 3); + if ((idx & 0x03) == 0x03 || (idx + 1) == blen) { + *fifo = tmp; + tmp = 0; + } + } + return blen; +} + +static uint16_t get_frame (void) { + return _FLD2VAL(USB_OTG_DSTS_FNSOF, OTGD->DSTS); +} + +static void evt_poll(usbd_device *dev, usbd_evt_callback callback) { + uint32_t evt; + uint32_t ep = 0; + while (1) { + uint32_t _t = OTG->GINTSTS; + /* bus RESET event */ + if (_t & USB_OTG_GINTSTS_USBRST) { + OTG->GINTSTS = USB_OTG_GINTSTS_USBRST; + for (uint8_t i = 0; i < MAX_EP; i++ ) { + ep_deconfig(i); + } + Flush_RX(); + continue; + } else if (_t & USB_OTG_GINTSTS_ENUMDNE) { + OTG->GINTSTS = USB_OTG_GINTSTS_ENUMDNE; + evt = usbd_evt_reset; + } else if (_t & USB_OTG_GINTSTS_IEPINT) { + for (;; ep++) { + USB_OTG_INEndpointTypeDef* epi = EPIN(ep); + if (ep >= MAX_EP) return; + if (epi->DIEPINT & USB_OTG_DIEPINT_XFRC) { + epi->DIEPINT = USB_OTG_DIEPINT_XFRC; + evt = usbd_evt_eptx; + ep |= 0x80; + break; + } + } + } else if (_t & USB_OTG_GINTSTS_RXFLVL) { + _t = OTG->GRXSTSR; + ep = _t & USB_OTG_GRXSTSP_EPNUM; + switch (_FLD2VAL(USB_OTG_GRXSTSP_PKTSTS, _t)) { + case 0x02: + evt = usbd_evt_eprx; + break; + case 0x06: + evt = usbd_evt_epsetup; + break; + default: + OTG->GRXSTSP; + continue; + } +#if !defined(USBD_SOF_DISABLED) + } else if (_t & USB_OTG_GINTSTS_SOF) { + OTG->GINTSTS = USB_OTG_GINTSTS_SOF; + evt = usbd_evt_sof; +#endif + } else if (_t & USB_OTG_GINTSTS_USBSUSP) { + evt = usbd_evt_susp; + OTG->GINTSTS = USB_OTG_GINTSTS_USBSUSP; + } else if (_t & USB_OTG_GINTSTS_WKUINT) { + OTG->GINTSTS = USB_OTG_GINTSTS_WKUINT; + evt = usbd_evt_wkup; + } else { + /* no more supported events */ + return; + } + return callback(dev, evt, ep); + } +} + +static uint32_t fnv1a32_turn (uint32_t fnv, uint32_t data ) { + for (int i = 0; i < 4 ; i++) { + fnv ^= (data & 0xFF); + fnv *= 16777619; + data >>= 8; + } + return fnv; +} + +static uint16_t get_serialno_desc(void *buffer) { + struct usb_string_descriptor *dsc = buffer; + uint16_t *str = dsc->wString; + uint32_t fnv = 2166136261; + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x00)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x04)); + fnv = fnv1a32_turn(fnv, *(uint32_t*)(UID_BASE + 0x08)); + for (int i = 28; i >= 0; i -= 4 ) { + uint16_t c = (fnv >> i) & 0x0F; + c += (c < 10) ? '0' : ('A' - 10); + *str++ = c; + } + dsc->bDescriptorType = USB_DTYPE_STRING; + dsc->bLength = 18; + return 18; +} + + __attribute__((externally_visible)) const struct usbd_driver usbd_otgfs = { + getinfo, + enable, + connect, + setaddr, + ep_config, + ep_deconfig, + ep_read, + ep_write, + ep_setstall, + ep_isstalled, + evt_poll, + get_frame, + get_serialno_desc, +}; + +#endif //USBD_STM32L476