| /** |
| * \file |
| * |
| * \brief SAM USB HPL |
| * |
| * Copyright (c) 2015-2018 Microchip Technology Inc. and its subsidiaries. |
| * |
| * \asf_license_start |
| * |
| * \page License |
| * |
| * Subject to your compliance with these terms, you may use Microchip |
| * software and any derivatives exclusively with Microchip products. |
| * It is your responsibility to comply with third party license terms applicable |
| * to your use of third party software (including open source software) that |
| * may accompany Microchip software. |
| * |
| * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, |
| * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, |
| * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, |
| * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE |
| * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL |
| * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE |
| * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE |
| * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT |
| * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY |
| * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, |
| * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. |
| * |
| * \asf_license_stop |
| * |
| */ |
| |
| #include <compiler.h> |
| #include <hal_atomic.h> |
| #include <hpl_usb.h> |
| #include <hpl_usb_device.h> |
| |
| #include <hpl_usb_config.h> |
| #include <string.h> |
| #include <utils_assert.h> |
| |
| /** |
| * \brief Dummy callback function |
| * \return Always false. |
| */ |
| static bool _dummy_func_no_return(uint32_t unused0, uint32_t unused1) |
| { |
| (void)unused0; |
| (void)unused1; |
| return false; |
| } |
| |
| /** |
| * \brief Load USB calibration value from NVM |
| */ |
| static void _usb_load_calib(void) |
| { |
| #define NVM_USB_PAD_TRANSN_POS 32 |
| #define NVM_USB_PAD_TRANSN_SIZE 5 |
| #define NVM_USB_PAD_TRANSP_POS 37 |
| #define NVM_USB_PAD_TRANSP_SIZE 5 |
| #define NVM_USB_PAD_TRIM_POS 42 |
| #define NVM_USB_PAD_TRIM_SIZE 3 |
| Usb * hw = USB; |
| uint32_t pad_transn |
| = (*((uint32_t *)(NVMCTRL_SW0) + (NVM_USB_PAD_TRANSN_POS / 32)) >> (NVM_USB_PAD_TRANSN_POS % 32)) |
| & ((1 << NVM_USB_PAD_TRANSN_SIZE) - 1); |
| uint32_t pad_transp |
| = (*((uint32_t *)(NVMCTRL_SW0) + (NVM_USB_PAD_TRANSP_POS / 32)) >> (NVM_USB_PAD_TRANSP_POS % 32)) |
| & ((1 << NVM_USB_PAD_TRANSP_SIZE) - 1); |
| uint32_t pad_trim = (*((uint32_t *)(NVMCTRL_SW0) + (NVM_USB_PAD_TRIM_POS / 32)) >> (NVM_USB_PAD_TRIM_POS % 32)) |
| & ((1 << NVM_USB_PAD_TRIM_SIZE) - 1); |
| if (pad_transn == 0 || pad_transn == 0x1F) { |
| pad_transn = 9; |
| } |
| if (pad_transp == 0 || pad_transp == 0x1F) { |
| pad_transp = 25; |
| } |
| if (pad_trim == 0 || pad_trim == 0x7) { |
| pad_trim = 6; |
| } |
| |
| hw->DEVICE.PADCAL.reg = USB_PADCAL_TRANSN(pad_transn) | USB_PADCAL_TRANSP(pad_transp) | USB_PADCAL_TRIM(pad_trim); |
| |
| hw->DEVICE.QOSCTRL.bit.CQOS = 3; |
| hw->DEVICE.QOSCTRL.bit.DQOS = 3; |
| } |
| |
| /** \name USB clock source management */ |
| /*@{*/ |
| |
| /** USB clock is generated by DFLL. */ |
| #define USB_CLK_SRC_DFLL 0 |
| |
| /** USB clock is generated by DPLL. */ |
| #define USB_CLK_SRC_DPLL 1 |
| |
| /** Uses DFLL as USB clock source. */ |
| #define CONF_USB_D_CLK_SRC USB_CLK_SRC_DFLL |
| |
| /** Retry for USB remote wakeup sending. */ |
| #define CONF_USB_RMT_WKUP_RETRY 5 |
| |
| /** |
| * \brief Wait DPLL clock to be ready |
| */ |
| static inline void _usb_d_dev_wait_dpll_rdy(void) |
| { |
| #define DPLL_READY_FLAG (OSCCTRL_DPLLSTATUS_CLKRDY | OSCCTRL_DPLLSTATUS_LOCK) |
| while (hri_oscctrl_get_DPLLSTATUS_reg(OSCCTRL, 0, DPLL_READY_FLAG) != DPLL_READY_FLAG) |
| ; |
| } |
| |
| /** |
| * \brief Wait DFLL clock to be ready |
| */ |
| static inline void _usb_d_dev_wait_dfll_rdy(void) |
| { |
| if (hri_oscctrl_get_DFLLCTRLB_MODE_bit(OSCCTRL)) { |
| while (hri_oscctrl_get_STATUS_reg(OSCCTRL, (OSCCTRL_STATUS_DFLLRDY | OSCCTRL_STATUS_DFLLLCKC)) |
| != (OSCCTRL_STATUS_DFLLRDY | OSCCTRL_STATUS_DFLLLCKC)) |
| ; |
| } else { |
| while (hri_oscctrl_get_STATUS_reg(OSCCTRL, OSCCTRL_STATUS_DFLLRDY) != OSCCTRL_STATUS_DFLLRDY) |
| ; |
| } |
| } |
| |
| /** |
| * \brief Wait USB source clock to be ready |
| * \param[in] clk_src Clock source, could be \ref USB_CLK_SRC_DFLL or |
| * \ref USB_CLK_SRC_DPLL. |
| */ |
| static inline void _usb_d_dev_wait_clk_rdy(const uint8_t clk_src) |
| { |
| if (clk_src == USB_CLK_SRC_DFLL) { |
| _usb_d_dev_wait_dfll_rdy(); |
| } else if (clk_src == USB_CLK_SRC_DPLL) { |
| _usb_d_dev_wait_dpll_rdy(); |
| } |
| } |
| |
| /*@}*/ |
| |
| /** \name USB general settings */ |
| /*@{*/ |
| |
| /** Increase the value to be aligned. */ |
| #define _usb_align_up(val) (((val)&0x3) ? (((val) + 4 - ((val)&0x3))) : (val)) |
| |
| /** Check if the buffer is in RAM (can DMA), or cache needed |
| * \param[in] a Buffer start address. |
| * \param[in] s Buffer size, in number of bytes. |
| * \return \c true If the buffer is in RAM. |
| */ |
| #define _IN_RAM(a, s) ((0x20000000 <= (uint32_t)(a)) && (((uint32_t)(a) + (s)) < (0x20000000 + 0x00032000))) |
| |
| /** Check if the address should be placed in RAM. */ |
| #define _usb_is_addr4dma(addr, size) _IN_RAM((addr), (size)) |
| |
| /** Check if the address is 32-bit aligned. */ |
| #define _usb_is_aligned(val) (((uint32_t)(val)&0x3) == 0) |
| /*@}*/ |
| |
| /* Cache static configurations. |
| * By default, all OUT endpoint have 64 bytes cache. */ |
| #ifndef CONF_USB_EP0_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP0_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP0_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP0_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP1_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP1_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP1_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP1_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP2_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP2_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP2_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP2_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP3_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP3_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP3_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP3_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP4_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP4_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP4_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP4_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP5_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP5_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP5_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP5_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP6_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP6_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP6_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP6_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP7_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP7_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP7_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP7_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP8_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP8_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP8_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP8_I_CACHE 0 |
| #endif |
| |
| #ifndef CONF_USB_EP9_CACHE |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #define CONF_USB_EP9_CACHE 64 |
| #endif |
| |
| #ifndef CONF_USB_EP9_I_CACHE |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define CONF_USB_EP9_I_CACHE 0 |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP0_CACHE |
| static uint32_t _usb_ep0_cache[_usb_align_up(CONF_USB_EP0_CACHE) / 4]; |
| #else |
| #define _usb_ep0_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #define _usb_ep0_i_cache NULL |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP1_CACHE && CONF_USB_D_MAX_EP_N >= 1 |
| static uint32_t _usb_ep1_cache[_usb_align_up(CONF_USB_EP1_CACHE) / 4]; |
| #else |
| #define _usb_ep1_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP1_I_CACHE && CONF_USB_D_MAX_EP_N >= 1 |
| static uint32_t _usb_ep1_i_cache[_usb_align_up(CONF_USB_EP1_I_CACHE) / 4]; |
| #else |
| #define _usb_ep1_i_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP2_CACHE && CONF_USB_D_MAX_EP_N >= 2 |
| static uint32_t _usb_ep2_cache[_usb_align_up(CONF_USB_EP2_CACHE) / 4]; |
| #else |
| #define _usb_ep2_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP2_I_CACHE && CONF_USB_D_MAX_EP_N >= 2 |
| static uint32_t _usb_ep2_i_cache[_usb_align_up(CONF_USB_EP2_I_CACHE) / 4]; |
| #else |
| #define _usb_ep2_i_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP3_CACHE && CONF_USB_D_MAX_EP_N >= 3 |
| static uint32_t _usb_ep3_cache[_usb_align_up(CONF_USB_EP3_CACHE) / 4]; |
| #else |
| #define _usb_ep3_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP3_I_CACHE && CONF_USB_D_MAX_EP_N >= 3 |
| static uint32_t _usb_ep3_i_cache[_usb_align_up(CONF_USB_EP3_I_CACHE) / 4]; |
| #else |
| #define _usb_ep3_i_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP4_CACHE && CONF_USB_D_MAX_EP_N >= 4 |
| static uint32_t _usb_ep4_cache[_usb_align_up(CONF_USB_EP4_CACHE) / 4]; |
| #else |
| #define _usb_ep4_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP4_I_CACHE && CONF_USB_D_MAX_EP_N >= 4 |
| static uint32_t _usb_ep4_i_cache[_usb_align_up(CONF_USB_EP4_I_CACHE) / 4]; |
| #else |
| #define _usb_ep4_i_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP5_CACHE && CONF_USB_D_MAX_EP_N >= 5 |
| static uint32_t _usb_ep5_cache[_usb_align_up(CONF_USB_EP5_CACHE) / 4]; |
| #else |
| #define _usb_ep5_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP5_I_CACHE && CONF_USB_D_MAX_EP_N >= 5 |
| static uint32_t _usb_ep5_i_cache[_usb_align_up(CONF_USB_EP5_I_CACHE) / 4]; |
| #else |
| #define _usb_ep5_i_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP6_CACHE && CONF_USB_D_MAX_EP_N >= 6 |
| static uint32_t _usb_ep6_cache[_usb_align_up(CONF_USB_EP6_CACHE) / 4]; |
| #else |
| #define _usb_ep6_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP6_I_CACHE && CONF_USB_D_MAX_EP_N >= 6 |
| static uint32_t _usb_ep6_i_cache[_usb_align_up(CONF_USB_EP6_I_CACHE) / 4]; |
| #else |
| #define _usb_ep6_i_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP7_CACHE && CONF_USB_D_MAX_EP_N >= 7 |
| static uint32_t _usb_ep7_cache[_usb_align_up(CONF_USB_EP7_CACHE) / 4]; |
| #else |
| #define _usb_ep7_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP7_I_CACHE && CONF_USB_D_MAX_EP_N >= 7 |
| static uint32_t _usb_ep7_i_cache[_usb_align_up(CONF_USB_EP7_I_CACHE) / 4]; |
| #else |
| #define _usb_ep7_i_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP8_CACHE && CONF_USB_D_MAX_EP_N >= 8 |
| static uint32_t _usb_ep8_cache[_usb_align_up(CONF_USB_EP8_CACHE) / 4]; |
| #else |
| #define _usb_ep8_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP8_I_CACHE && CONF_USB_D_MAX_EP_N >= 8 |
| static uint32_t _usb_ep8_i_cache[_usb_align_up(CONF_USB_EP8_I_CACHE) / 4]; |
| #else |
| #define _usb_ep8_i_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for OUT transactions (none-control) or SETUP/IN/OUT |
| * transactions (control). */ |
| #if CONF_USB_EP9_CACHE && CONF_USB_D_MAX_EP_N >= 9 |
| static uint32_t _usb_ep9_cache[_usb_align_up(CONF_USB_EP9_CACHE) / 4]; |
| #else |
| #define _usb_ep9_cache NULL |
| #endif |
| |
| /** Endpoint cache buffer for IN transactions (none-control). */ |
| #if CONF_USB_EP9_I_CACHE && CONF_USB_D_MAX_EP_N >= 9 |
| static uint32_t _usb_ep9_i_cache[_usb_align_up(CONF_USB_EP9_I_CACHE) / 4]; |
| #else |
| #define _usb_ep9_i_cache NULL |
| #endif |
| |
| /** Access endpoint cache buffer for OUT transactions (none-control) or |
| * SETUP/IN/OUT transactions (control). */ |
| #define _USB_EP_CACHE(n) ((void *)_usb_ep##n##_cache) |
| |
| /** Access endpoint cache buffer for IN transactions (none-control). */ |
| #define _USB_EP_I_CACHE(n) ((void *)_usb_ep##n##_i_cache) |
| |
| /** The configuration settings for one of the endpoint hardware. */ |
| struct _usb_ep_cfg_item { |
| /* Endpoint cache buffer for OUT transactions (none-control) or |
| * SETUP/IN/OUT transactions (control). */ |
| void *cache; |
| /* endpoint cache buffer for IN transactions (none-control). */ |
| void *i_cache; |
| /* Cache buffer size for OUT transactions (none-control) or |
| * SETUP/IN/OUT transactions (control). */ |
| uint16_t size; |
| /* Cache buffer size for IN transactions (none-control). */ |
| uint16_t i_size; |
| }; |
| |
| /** Build the endpoint configuration settings for one endpoint. */ |
| #define _USB_EP_CFG_ITEM(n) \ |
| { \ |
| _USB_EP_CACHE(n), _USB_EP_I_CACHE(n), CONF_USB_EP##n##_CACHE, CONF_USB_EP##n##_I_CACHE, \ |
| } |
| |
| /** The configuration settings for all endpoint. */ |
| static const struct _usb_ep_cfg_item _usb_ep_cfgs[] = {_USB_EP_CFG_ITEM(0) |
| #if CONF_USB_D_MAX_EP_N >= 1 |
| , |
| _USB_EP_CFG_ITEM(1) |
| #endif |
| #if CONF_USB_D_MAX_EP_N >= 2 |
| , |
| _USB_EP_CFG_ITEM(2) |
| #endif |
| #if CONF_USB_D_MAX_EP_N >= 3 |
| , |
| _USB_EP_CFG_ITEM(3) |
| #endif |
| #if CONF_USB_D_MAX_EP_N >= 4 |
| , |
| _USB_EP_CFG_ITEM(4) |
| #endif |
| #if CONF_USB_D_MAX_EP_N >= 5 |
| , |
| _USB_EP_CFG_ITEM(5) |
| #endif |
| #if CONF_USB_D_MAX_EP_N >= 6 |
| , |
| _USB_EP_CFG_ITEM(6) |
| #endif |
| #if CONF_USB_D_MAX_EP_N >= 7 |
| , |
| _USB_EP_CFG_ITEM(7) |
| #endif |
| #if CONF_USB_D_MAX_EP_N >= 8 |
| , |
| _USB_EP_CFG_ITEM(8) |
| #endif |
| #if CONF_USB_D_MAX_EP_N >= 9 |
| , |
| _USB_EP_CFG_ITEM(9) |
| #endif |
| }; |
| |
| /** \name HW specific settings and implements */ |
| /*@{*/ |
| |
| /** Number of endpoints supported. */ |
| #define USB_D_N_EP (1 + CONF_USB_D_NUM_EP_SP * 2) |
| |
| /** HPL USB device endpoint struct. */ |
| struct _usb_d_dev_ep { |
| /** Pointer to transaction buffer. */ |
| uint8_t *trans_buf; |
| /** Transaction size. */ |
| uint32_t trans_size; |
| /** Transaction transferred count. */ |
| uint32_t trans_count; |
| |
| /** Pointer to cache buffer, must be aligned. */ |
| uint8_t *cache; |
| |
| /** Endpoint size. */ |
| uint16_t size; |
| /** Endpoint address. */ |
| uint8_t ep; |
| /** Feature flags. */ |
| union { |
| /** Interpreted by bit fields. */ |
| struct { |
| /** EPCFG.ETYPE. */ |
| uint8_t eptype : 3; |
| /** Stall status. */ |
| uint8_t is_stalled : 1; |
| /** Transaction auto ZLP. */ |
| uint8_t need_zlp : 1; |
| /** Transaction with cache */ |
| uint8_t use_cache : 1; |
| /** Endpoint is busy. */ |
| uint8_t is_busy : 1; |
| /** Transaction direction. */ |
| uint8_t dir : 1; |
| } bits; |
| uint8_t u8; |
| } flags; |
| }; |
| |
| /** Check if the endpoint is used. */ |
| #define _usb_d_dev_ep_is_used(ept) ((ept)->ep != 0xFF) |
| |
| /** Check if the endpoint is busy doing transactions. */ |
| #define _usb_d_dev_ep_is_busy(ept) ((ept)->flags.bits.is_busy) |
| |
| /** Check if the endpoint is control endpoint. */ |
| #define _usb_d_dev_ep_is_ctrl(ept) ((ept)->flags.bits.eptype == USB_D_EPTYPE_CTRL) |
| |
| /** Check if the endpoint transactions are IN. */ |
| #define _usb_d_dev_ep_is_in(ept) ((ept)->flags.bits.dir) |
| |
| /** Interrupt flags for SETUP transaction. */ |
| #define USB_D_SETUP_INT_FLAGS (USB_DEVICE_EPINTFLAG_RXSTP) |
| |
| /** Interrupt flags for BANK1 transactions. */ |
| #define USB_D_BANK1_INT_FLAGS (USB_DEVICE_EPINTFLAG_TRCPT1 | USB_DEVICE_EPINTFLAG_TRFAIL1 | USB_DEVICE_EPINTFLAG_STALL1) |
| |
| /** Interrupt flags for BANK0 transactions. */ |
| #define USB_D_BANK0_INT_FLAGS (USB_DEVICE_EPINTFLAG_TRCPT0 | USB_DEVICE_EPINTFLAG_TRFAIL0 | USB_DEVICE_EPINTFLAG_STALL0) |
| |
| /** Interrupt flags for SETUP/IN/OUT transactions. */ |
| #define USB_D_ALL_INT_FLAGS (0x7F) |
| |
| /** Interrupt flags for WAKEUP event. */ |
| #define USB_D_WAKEUP_INT_FLAGS (USB_DEVICE_INTFLAG_UPRSM | USB_DEVICE_INTFLAG_EORSM | USB_DEVICE_INTFLAG_WAKEUP) |
| |
| /** Interrupt flags for SUSPEND event. */ |
| #define USB_D_SUSPEND_INT_FLAGS (USB_DEVICE_INTFLAG_LPMSUSP | USB_DEVICE_INTFLAG_SUSPEND) |
| |
| /** Max data bytes for a single DMA transfer. */ |
| #define USB_D_DEV_TRANS_MAX 8192 /* 14-bits, uses 13-bits. */ |
| |
| /** Endpoint type setting to disable. */ |
| #define USB_D_EPTYPE_DISABLE 0 |
| |
| /** Endpoint type setting to work as control endpoint. */ |
| #define USB_D_EPTYPE_CTRL 1 |
| |
| /** Endpoint type setting to work as isochronous endpoint. */ |
| #define USB_D_EPTYPE_ISOCH 2 |
| |
| /** Endpoint type setting to work as interrupt endpoint. */ |
| #define USB_D_EPTYPE_INT 3 |
| |
| /** Endpoint type setting to work as bulk endpoint. */ |
| #define USB_D_EPTYPE_BULK 4 |
| |
| /** Endpoint type setting for dual bank endpoint. */ |
| #define USB_D_EPTYPE_DUAL 5 |
| |
| /** EPCFG register value for control endpoints. */ |
| #define USB_D_EPCFG_CTRL 0x11 |
| |
| /** HPL USB device struct. */ |
| struct _usb_d_dev { |
| /** Callbacks of USB device. */ |
| struct _usb_d_dev_callbacks callbacks; |
| /** Endpoint transaction callbacks. */ |
| struct _usb_d_dev_ep_callbacks ep_callbacks; |
| /** Endpoints (ep0 + others). */ |
| struct _usb_d_dev_ep ep[USB_D_N_EP]; |
| }; |
| |
| /** Private data for SAM0 USB peripheral. |
| */ |
| typedef struct _usb_d_dev_prvt { |
| /** USB device descriptor table for peripheral to work. */ |
| UsbDeviceDescriptor desc_table[CONF_USB_D_MAX_EP_N + 1]; |
| } usb_d_dev_prvt_t; |
| |
| /*@}*/ |
| |
| /** USB device driver instance. */ |
| static struct _usb_d_dev dev_inst; |
| |
| /** USB device driver private data instance. */ |
| static struct _usb_d_dev_prvt prvt_inst; |
| |
| static void _usb_d_dev_reset_epts(void); |
| |
| static void _usb_d_dev_trans_done(struct _usb_d_dev_ep *ept, const int32_t status); |
| static void _usb_d_dev_trans_stop(struct _usb_d_dev_ep *ept, bool dir, const int32_t code); |
| |
| static void _usb_d_dev_in_next(struct _usb_d_dev_ep *ept, bool isr); |
| static void _usb_d_dev_out_next(struct _usb_d_dev_ep *ept, bool isr); |
| |
| static inline void _usb_d_dev_trans_setup(struct _usb_d_dev_ep *ept); |
| |
| /** \brief ACK the endpoint interrupt |
| * \param[in] epn Endpoint number. |
| * \param[in] flags Interrupt flags. |
| */ |
| static inline void _usbd_ep_int_ack(uint8_t epn, uint32_t flags) |
| { |
| hri_usbendpoint_clear_EPINTFLAG_reg(USB, epn, flags); |
| } |
| |
| /** \brief Enable the endpoint interrupt |
| * \param[in] epn Endpoint number. |
| * \param[in] flags Interrupt flags. |
| */ |
| static inline void _usbd_ep_int_en(uint8_t epn, uint32_t flags) |
| { |
| hri_usbendpoint_set_EPINTEN_reg(USB, epn, flags); |
| } |
| |
| /** \brief Disable the endpoint interrupt |
| * \param[in] epn Endpoint number. |
| * \param[in] flags Interrupt flags. |
| */ |
| static inline void _usbd_ep_int_dis(uint8_t epn, uint32_t flags) |
| { |
| hri_usbendpoint_clear_EPINTEN_reg(USB, epn, flags); |
| } |
| |
| /** \brief Check if endpoint is control endpoint |
| * \param[in] epn Endpoint number. |
| */ |
| static inline bool _usbd_ep_is_ctrl(uint8_t epn) |
| { |
| return (hri_usbendpoint_read_EPCFG_reg(USB, epn) == USB_D_EPCFG_CTRL); |
| } |
| |
| /** \brief Set endpoint stall |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] st Stall status. |
| */ |
| static inline void _usbd_ep_set_stall(uint8_t epn, uint8_t bank_n, bool st) |
| { |
| if (st) { |
| hri_usbendpoint_set_EPSTATUS_reg(USB, epn, (USB_DEVICE_EPSTATUS_STALLRQ0 << bank_n)); |
| } else { |
| hri_usbendpoint_clear_EPSTATUS_reg(USB, epn, (USB_DEVICE_EPSTATUS_STALLRQ0 << bank_n)); |
| } |
| } |
| |
| /** \brief Check if the endpoint is stalled |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \return \c true if it's stalled. |
| */ |
| static inline bool _usbd_ep_is_stalled(uint8_t epn, uint8_t bank_n) |
| { |
| Usb *hw = USB; |
| return (hri_usbendpoint_read_EPSTATUS_reg(hw, epn) & (USB_DEVICE_EPSTATUS_STALLRQ0 << bank_n)); |
| } |
| |
| /** \brief Check if stall has been sent from the endpoint |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \return \c true if it's sent. |
| */ |
| static inline bool _usbd_ep_is_stall_sent(uint8_t epn, uint8_t bank_n) |
| { |
| Usb *hw = USB; |
| return (hri_usbendpoint_read_EPINTFLAG_reg(hw, epn) & (USB_DEVICE_EPINTFLAG_STALL0 << bank_n)); |
| } |
| |
| /** \brief ACK endpoint STALL interrupt |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| */ |
| static inline void _usbd_ep_ack_stall(uint8_t epn, uint8_t bank_n) |
| { |
| _usbd_ep_int_ack(epn, (USB_DEVICE_EPINTFLAG_STALL0 << bank_n)); |
| } |
| |
| /** \brief Enable/disable endpoint STALL interrupt |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] en \c true to enable, \c false to disable. |
| */ |
| static inline void _usbd_ep_int_stall_en(uint8_t epn, uint8_t bank_n, const bool en) |
| { |
| if (en) { |
| _usbd_ep_int_en(epn, USB_DEVICE_EPINTFLAG_STALL0 << bank_n); |
| } else { |
| _usbd_ep_int_dis(epn, USB_DEVICE_EPINTFLAG_STALL0 << bank_n); |
| } |
| } |
| |
| /** \brief Stop SETUP transactions |
| * \param[in] epn Endpoint number. |
| */ |
| static inline void _usbd_ep_stop_setup(uint8_t epn) |
| { |
| hri_usbendpoint_clear_EPINTEN_RXSTP_bit(USB, epn); |
| } |
| |
| /** \brief Check if SETUP packet is ready in cache |
| * \param[in] epn Endpoint number. |
| */ |
| static inline bool _usbd_ep_is_setup(uint8_t epn) |
| { |
| return hri_usbendpoint_get_EPINTFLAG_reg(USB, epn, USB_DEVICE_EPINTFLAG_RXSTP); |
| } |
| |
| /** \brief ACK endpoint SETUP interrupt |
| * \param[in] epn Endpoint number. |
| */ |
| static inline void _usbd_ep_ack_setup(uint8_t epn) |
| { |
| _usbd_ep_int_ack(epn, USB_DEVICE_EPINTFLAG_RXSTP); |
| } |
| |
| /** \brief Set endpoint toggle value |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] tgl Toggle value. |
| */ |
| static inline void _usbd_ep_set_toggle(uint8_t epn, uint8_t bank_n, uint8_t tgl) |
| { |
| if (tgl) { |
| hri_usbendpoint_set_EPSTATUS_reg(USB, epn, (USB_DEVICE_EPSTATUS_DTGLOUT << bank_n)); |
| } else { |
| hri_usbendpoint_clear_EPSTATUS_reg(USB, epn, (USB_DEVICE_EPSTATUS_DTGLOUT << bank_n)); |
| } |
| } |
| |
| /** \brief ACK IN/OUT complete interrupt |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| */ |
| static inline void _usbd_ep_ack_io_cpt(uint8_t epn, uint8_t bank_n) |
| { |
| _usbd_ep_int_ack(epn, USB_DEVICE_EPINTFLAG_TRCPT0 << bank_n); |
| } |
| |
| /** \brief Set DMA buffer used for bank data |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] addr DMA buffer address to set. |
| */ |
| static inline void _usbd_ep_set_buf(uint8_t epn, uint8_t bank_n, uint32_t addr) |
| { |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[epn].DeviceDescBank[bank_n]; |
| bank->ADDR.reg = addr; |
| } |
| |
| /** \brief Set bank count for IN transactions |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] count Data count for IN. |
| */ |
| static inline void _usbd_ep_set_in_count(uint8_t epn, uint8_t bank_n, uint16_t count) |
| { |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[epn].DeviceDescBank[bank_n]; |
| bank->PCKSIZE.bit.MULTI_PACKET_SIZE = count; |
| } |
| |
| /** \brief Set bank size for IN transactions |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] size Data size for IN. |
| */ |
| static inline void _usbd_ep_set_in_size(uint8_t epn, uint8_t bank_n, uint16_t size) |
| { |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[epn].DeviceDescBank[bank_n]; |
| bank->PCKSIZE.bit.BYTE_COUNT = size; |
| } |
| |
| /** \brief Set bank count for OUT transaction |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] count Data count for OUT. |
| */ |
| static inline void _usbd_ep_set_out_count(uint8_t epn, uint8_t bank_n, uint16_t count) |
| { |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[epn].DeviceDescBank[bank_n]; |
| bank->PCKSIZE.bit.BYTE_COUNT = count; |
| } |
| |
| /** \brief Set bank size for OUT transactions |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] size Data size for OUT. |
| */ |
| static inline void _usbd_ep_set_out_size(uint8_t epn, uint8_t bank_n, uint16_t size) |
| { |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[epn].DeviceDescBank[bank_n]; |
| bank->PCKSIZE.bit.MULTI_PACKET_SIZE = size; |
| } |
| |
| /** Set bank size and count for IN transactions |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] size Data size. |
| * \param[in] count Initial data count. |
| */ |
| static inline void _usbd_ep_set_in_trans(uint8_t epn, uint8_t bank_n, uint32_t size, uint32_t count) |
| { |
| _usbd_ep_set_in_size(epn, bank_n, size); |
| _usbd_ep_set_in_count(epn, bank_n, count); |
| } |
| |
| /** \brief Set bank size and count for OUT transaction |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] size Data size. |
| * \param[in] count Initial data count. |
| */ |
| static inline void _usbd_ep_set_out_trans(uint8_t epn, uint8_t bank_n, uint32_t size, uint32_t count) |
| { |
| _usbd_ep_set_out_size(epn, bank_n, size); |
| _usbd_ep_set_out_count(epn, bank_n, count); |
| } |
| |
| /** \brief Clear bank status |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| */ |
| static inline void _usbd_ep_clear_bank_status(uint8_t epn, uint8_t bank_n) |
| { |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[epn].DeviceDescBank[bank_n]; |
| bank->STATUS_BK.reg = 0; |
| } |
| |
| /** Set IN ready for IN transactions |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] rdy Set to \c true to indicate IN packet ready to TX. |
| */ |
| static inline void _usbd_ep_set_in_rdy(uint8_t epn, uint8_t bank_n, const bool rdy) |
| { |
| if (rdy) { |
| hri_usbendpoint_set_EPSTATUS_reg(USB, epn, USB_DEVICE_EPSTATUS_BK0RDY << bank_n); |
| } else { |
| hri_usbendpoint_clear_EPSTATUS_reg(USB, epn, USB_DEVICE_EPSTATUS_BK0RDY << bank_n); |
| } |
| } |
| |
| /** \brief Set bank ready for OUT transactions |
| * \param[in] epn Endpoint number. |
| * \param[in] bank_n Endpoint bank number. |
| * \param[in] rdy Set to \c true to indicate OUT bank ready to RX. |
| */ |
| static inline void _usbd_ep_set_out_rdy(uint8_t epn, uint8_t bank_n, const bool rdy) |
| { |
| if (rdy) { |
| hri_usbendpoint_clear_EPSTATUS_reg(USB, epn, USB_DEVICE_EPSTATUS_BK0RDY << bank_n); |
| } else { |
| hri_usbendpoint_set_EPSTATUS_reg(USB, epn, USB_DEVICE_EPSTATUS_BK0RDY << bank_n); |
| } |
| } |
| |
| /** |
| * \brief Convert USB endpoint size to HW PCKSIZE.SIZE |
| * \param[in] n Number of bytes of endpoint size. |
| */ |
| static inline uint8_t _usbd_ep_pcksize_size(uint16_t n) |
| { |
| return ( |
| (n > 512) |
| ? 7 |
| : ((n > 256) ? 6 : ((n > 128) ? 5 : ((n > 64) ? 4 : ((n > 32) ? 3 : ((n > 16) ? 2 : ((n > 8) ? 1 : 0))))))); |
| } |
| |
| /** |
| * \brief Obtain endpoint descriptor pointer |
| * \param[in] epn Endpoint number. |
| * \param[in] dir Endpoint direction. |
| */ |
| static inline struct _usb_d_dev_ep *_usb_d_dev_ept(uint8_t epn, bool dir) |
| { |
| uint8_t ep_index = (epn == 0) ? 0 : (dir ? (epn + CONF_USB_D_MAX_EP_N) : epn); |
| return &dev_inst.ep[ep_index]; |
| } |
| |
| /** |
| * \brief Handles USB SOF interrupt |
| */ |
| static inline void _usb_d_dev_sof(void) |
| { |
| /* ACK SOF interrupt. */ |
| hri_usbdevice_clear_INTFLAG_reg(USB, USB_DEVICE_INTFLAG_SOF); |
| dev_inst.callbacks.sof(); |
| } |
| |
| /** |
| * \brief Handles USB LPM Suspend interrupt |
| */ |
| static inline void _usb_d_dev_lpmsusp(void) |
| { |
| uint8_t i; |
| uint32_t lpm_variable = 0; |
| |
| /* ACK LPMSUSP interrupt. */ |
| hri_usbdevice_clear_INTFLAG_reg(USB, USB_D_SUSPEND_INT_FLAGS); |
| /* Change interrupt masks */ |
| hri_usbdevice_clear_INTEN_reg(USB, USB_D_SUSPEND_INT_FLAGS); |
| hri_usbdevice_set_INTEN_reg(USB, USB_D_WAKEUP_INT_FLAGS); |
| |
| /* Find LPM data */ |
| for (i = 0; i < CONF_USB_D_MAX_EP_N; i++) { |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[i].DeviceDescBank[0]; |
| if (bank->EXTREG.bit.SUBPID == 0x3) { |
| /* Save LPM variable */ |
| lpm_variable = bank->EXTREG.bit.VARIABLE; |
| /* Clear */ |
| bank->EXTREG.reg = 0; |
| break; |
| } |
| } |
| dev_inst.callbacks.event(USB_EV_LPM_SUSPEND, lpm_variable); |
| } |
| |
| /** |
| * \brief Handles USB RAM Error interrupt |
| */ |
| static inline void _usb_d_dev_ramerr(void) |
| { |
| hri_usbdevice_clear_INTFLAG_reg(USB, USB_DEVICE_INTFLAG_RAMACER); |
| dev_inst.callbacks.event(USB_EV_ERROR, 0); |
| } |
| |
| /** |
| * \brief Handles USB resume/wakeup interrupts |
| */ |
| static inline void _usb_d_dev_wakeup(void) |
| { |
| hri_usbdevice_clear_INTFLAG_reg(USB, USB_D_WAKEUP_INT_FLAGS); |
| hri_usbdevice_clear_INTEN_reg(USB, USB_D_WAKEUP_INT_FLAGS); |
| hri_usbdevice_set_INTEN_reg(USB, USB_D_SUSPEND_INT_FLAGS); |
| |
| _usb_d_dev_wait_clk_rdy(CONF_USB_D_CLK_SRC); |
| dev_inst.callbacks.event(USB_EV_WAKEUP, 0); |
| } |
| |
| /** |
| * \brief Handles USB signal reset interrupt |
| */ |
| static inline void _usb_d_dev_reset(void) |
| { |
| /* EP0 will not be reseted by USB RESET, disable manually. */ |
| hri_usbendpoint_write_EPCFG_reg(USB, 0, 0); |
| |
| hri_usbdevice_clear_INTFLAG_reg(USB, USB_DEVICE_INTFLAG_EORST); |
| hri_usbdevice_clear_INTEN_reg(USB, USB_D_WAKEUP_INT_FLAGS); |
| hri_usbdevice_set_INTEN_reg(USB, USB_D_SUSPEND_INT_FLAGS); |
| |
| _usb_d_dev_reset_epts(); |
| dev_inst.callbacks.event(USB_EV_RESET, 0); |
| } |
| |
| static inline void _usb_d_dev_suspend(void) |
| { |
| hri_usbdevice_clear_INTFLAG_reg(USB, USB_D_SUSPEND_INT_FLAGS); |
| hri_usbdevice_clear_INTEN_reg(USB, USB_D_SUSPEND_INT_FLAGS); |
| hri_usbdevice_set_INTEN_reg(USB, USB_D_WAKEUP_INT_FLAGS); |
| |
| dev_inst.callbacks.event(USB_EV_SUSPEND, 0); |
| } |
| |
| /** |
| * \brief Handles USB non-endpoint interrupt |
| */ |
| static inline bool _usb_d_dev_handle_nep(void) |
| { |
| bool rc = true; |
| uint16_t flags = hri_usbdevice_read_INTFLAG_reg(USB); |
| flags &= hri_usbdevice_read_INTEN_reg(USB); |
| |
| if (flags & USB_DEVICE_INTFLAG_SOF) { |
| _usb_d_dev_sof(); |
| return true; |
| } |
| if (flags & USB_DEVICE_INTFLAG_LPMSUSP) { |
| _usb_d_dev_lpmsusp(); |
| } else if (flags & USB_DEVICE_INTFLAG_RAMACER) { |
| _usb_d_dev_ramerr(); |
| } else if (flags & USB_D_WAKEUP_INT_FLAGS) { |
| _usb_d_dev_wakeup(); |
| } else if (flags & USB_DEVICE_INTFLAG_EORST) { |
| _usb_d_dev_reset(); |
| } else if (flags & USB_DEVICE_INTFLAG_SUSPEND) { |
| _usb_d_dev_suspend(); |
| } else { |
| rc = false; |
| } |
| return rc; |
| } |
| |
| /** |
| * \brief Prepare next IN transactions |
| * \param[in] ept Pointer to endpoint information. |
| * \param[in] isr Invoked from ISR. |
| */ |
| static void _usb_d_dev_in_next(struct _usb_d_dev_ep *ept, bool isr) |
| { |
| Usb * hw = USB; |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[epn].DeviceDescBank[0]; |
| uint16_t trans_count = isr ? bank[1].PCKSIZE.bit.BYTE_COUNT : 0; |
| uint16_t trans_next; |
| uint16_t last_pkt = trans_count & ((ept->size == 1023) ? ept->size : (ept->size - 1)); |
| uint8_t inten = 0; |
| bool is_ctrl = _usb_d_dev_ep_is_ctrl(ept); |
| |
| if (isr) { |
| _usbd_ep_ack_io_cpt(epn, 1); |
| } |
| |
| ept->trans_count += trans_count; |
| /* Send more data. */ |
| if (ept->trans_count < ept->trans_size) { |
| trans_next = ept->trans_size - ept->trans_count; |
| if (ept->flags.bits.use_cache) { |
| if (trans_next > ept->size) { |
| trans_next = ept->size; |
| } |
| memcpy(ept->cache, &ept->trans_buf[ept->trans_count], trans_next); |
| _usbd_ep_set_buf(epn, 1, (uint32_t)ept->cache); |
| } else { |
| if (trans_next > USB_D_DEV_TRANS_MAX) { |
| trans_next = USB_D_DEV_TRANS_MAX; |
| } |
| _usbd_ep_set_buf(epn, 1, (uint32_t)&ept->trans_buf[ept->trans_count]); |
| } |
| _usbd_ep_set_in_trans(epn, 1, trans_next, 0); |
| goto _in_tx_exec; |
| } else if (ept->flags.bits.need_zlp) { |
| ept->flags.bits.need_zlp = 0; |
| _usbd_ep_set_in_trans(epn, 1, 0, 0); |
| goto _in_tx_exec; |
| } |
| /* Complete. */ |
| if (is_ctrl) { |
| hri_usbendpoint_clear_EPINTEN_reg(hw, epn, USB_D_BANK1_INT_FLAGS | USB_DEVICE_EPINTFLAG_TRCPT0); |
| } else { |
| hri_usbendpoint_clear_EPINTEN_reg(hw, epn, USB_D_BANK1_INT_FLAGS); |
| } |
| |
| /* No ping-pong, so ask more data without background transfer. */ |
| if (last_pkt == ept->size) { |
| ept->flags.bits.is_busy = 0; |
| if (dev_inst.ep_callbacks.more(ept->ep, ept->trans_count)) { |
| /* More data added. */ |
| return; |
| } |
| ept->flags.bits.is_busy = 1; |
| } |
| /* Finish normally. */ |
| _usb_d_dev_trans_done(ept, USB_TRANS_DONE); |
| return; |
| |
| _in_tx_exec: |
| if (!isr) { |
| if (is_ctrl) { |
| /* Control endpoint: SETUP or OUT will abort IN transaction. |
| * SETUP: terminate the IN without any notification. Trigger |
| * SETUP callback. |
| * OUT NAK: terminate IN. |
| */ |
| inten = USB_D_BANK1_INT_FLAGS | USB_DEVICE_EPINTFLAG_TRFAIL0; |
| } else { |
| /* Initialize normal IN transaction. */ |
| inten = USB_D_BANK1_INT_FLAGS; |
| } |
| hri_usbendpoint_set_EPINTEN_reg(hw, epn, inten); |
| } |
| _usbd_ep_set_in_rdy(epn, 1, true); |
| } |
| |
| /** |
| * \brief Prepare next OUT transactions |
| * \param[in] ept Pointer to endpoint information. |
| * \param[in] isr Invoked from ISR. |
| */ |
| static void _usb_d_dev_out_next(struct _usb_d_dev_ep *ept, bool isr) |
| { |
| Usb * hw = USB; |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| UsbDeviceDescBank *bank = &prvt_inst.desc_table[epn].DeviceDescBank[0]; |
| uint16_t trans_size = isr ? bank->PCKSIZE.bit.MULTI_PACKET_SIZE : 0; |
| uint16_t last_trans = isr ? bank->PCKSIZE.bit.BYTE_COUNT : 0; |
| uint16_t size_mask = (ept->size == 1023) ? 1023 : (ept->size - 1); |
| uint16_t last_pkt = last_trans & size_mask; |
| uint16_t trans_next; |
| uint8_t inten; |
| bool is_ctrl = _usb_d_dev_ep_is_ctrl(ept); |
| |
| if (isr) { |
| _usbd_ep_ack_io_cpt(epn, 0); |
| } |
| |
| /* If cache is used, copy data to buffer. */ |
| if (ept->flags.bits.use_cache && ept->trans_size) { |
| uint16_t buf_remain = ept->trans_size - ept->trans_count; |
| memcpy(&ept->trans_buf[ept->trans_count], ept->cache, (buf_remain > last_pkt) ? last_pkt : buf_remain); |
| } |
| |
| /* Force wait ZLP */ |
| if (ept->trans_size == 0 && ept->flags.bits.need_zlp) { |
| ept->flags.bits.need_zlp = 0; |
| ept->flags.bits.use_cache = 1; |
| _usbd_ep_set_buf(epn, 0, (uint32_t)ept->cache); |
| _usbd_ep_set_out_trans(epn, 0, ept->size, 0); |
| goto _out_rx_exec; |
| } else if (isr && last_pkt < ept->size) { |
| /* Short packet. */ |
| ept->flags.bits.need_zlp = 0; |
| ept->trans_count += last_trans; |
| } else { |
| /* Full packets. */ |
| ept->trans_count += trans_size; |
| |
| /* Wait more data */ |
| if (ept->trans_count < ept->trans_size) { |
| /* Continue OUT */ |
| trans_next = ept->trans_size - ept->trans_count; |
| if (ept->flags.bits.use_cache) { |
| /* Expect single packet each time. */ |
| if (trans_next > ept->size) { |
| trans_next = ept->size; |
| } |
| _usbd_ep_set_buf(epn, 0, (uint32_t)ept->cache); |
| } else { |
| /* Multiple packets each time. */ |
| if (trans_next > ept->size) { |
| if (trans_next > USB_D_DEV_TRANS_MAX) { |
| trans_next = USB_D_DEV_TRANS_MAX; |
| } else { |
| /* Must expect multiple of ep size. */ |
| trans_next -= trans_next & size_mask; |
| } |
| } else if (trans_next < ept->size) { |
| /* Last un-aligned packet should be cached. */ |
| ept->flags.bits.use_cache = 1; |
| } |
| _usbd_ep_set_buf(epn, 0, (uint32_t)&ept->trans_buf[ept->trans_count]); |
| } |
| _usbd_ep_set_out_trans(epn, 0, trans_next, 0); |
| goto _out_rx_exec; |
| } |
| } |
| /* Finish normally. */ |
| if (is_ctrl) { |
| hri_usbendpoint_clear_EPINTEN_reg(hw, epn, USB_D_BANK0_INT_FLAGS | USB_DEVICE_EPINTFLAG_TRFAIL1); |
| } else { |
| hri_usbendpoint_clear_EPINTEN_reg(hw, epn, USB_D_BANK0_INT_FLAGS); |
| } |
| /* Use ep0 out cache for next setup packets */ |
| if (0 == epn) { |
| _usbd_ep_set_buf(epn, 0, (uint32_t)ept->cache); |
| } |
| _usb_d_dev_trans_done(ept, USB_TRANS_DONE); |
| return; |
| |
| _out_rx_exec: |
| if (!isr) { |
| if (is_ctrl) { |
| /* Initialize control OUT transaction. */ |
| |
| /* Control transfer: SETUP or IN request will abort the |
| * OUT transactions. |
| * SETUP: terminate OUT without any notification. |
| * Trigger SETUP notification. |
| * IN NAK: finish OUT normally. Notify data done. |
| */ |
| _usbd_ep_clear_bank_status(epn, 1); |
| /* Detect OUT, SETUP, NAK IN */ |
| inten = USB_D_BANK0_INT_FLAGS | USB_DEVICE_EPINTFLAG_TRFAIL1; |
| } else { |
| /* Initialize normal OUT transaction. */ |
| inten = USB_D_BANK0_INT_FLAGS; |
| } |
| hri_usbendpoint_set_EPINTEN_reg(hw, epn, inten); |
| } |
| _usbd_ep_set_out_rdy(epn, 0, true); |
| } |
| |
| /** |
| * \brief Handles setup received interrupt |
| * \param[in] ept Pointer to endpoint information. |
| */ |
| static void _usb_d_dev_handle_setup(struct _usb_d_dev_ep *ept) |
| { |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| bool is_ctrl = _usb_d_dev_ep_is_ctrl(ept); |
| |
| if (!is_ctrl) { |
| /* Should never be here! */ |
| _usbd_ep_ack_setup(epn); |
| _usbd_ep_stop_setup(epn); |
| return; |
| } |
| /* Control transfer: |
| * SETUP transaction will terminate IN/OUT transaction, |
| * and start new transaction with received SETUP packet. |
| */ |
| if (_usb_d_dev_ep_is_busy(ept)) { |
| ept->flags.bits.is_busy = 0; |
| |
| /* Stop transfer on either direction. */ |
| _usbd_ep_set_in_rdy(epn, 1, false); |
| _usbd_ep_set_out_rdy(epn, 0, false); |
| } |
| ept->flags.bits.is_stalled = 0; |
| |
| /* Clear status and notify SETUP */ |
| _usbd_ep_clear_bank_status(epn, 0); |
| _usbd_ep_clear_bank_status(epn, 1); |
| _usbd_ep_int_ack(epn, USB_D_BANK0_INT_FLAGS | USB_D_BANK1_INT_FLAGS); |
| _usbd_ep_int_dis(epn, USB_D_BANK0_INT_FLAGS | USB_D_BANK1_INT_FLAGS); |
| /* Invoke callback. */ |
| dev_inst.ep_callbacks.setup(ept->ep); |
| } |
| |
| /** |
| * \brief Handles stall sent interrupt |
| * \param[in] ept Pointer to endpoint information. |
| * \param[in] bank_n Bank number. |
| */ |
| static void _usb_d_dev_handle_stall(struct _usb_d_dev_ep *ept, const uint8_t bank_n) |
| { |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| /* Clear interrupt enable. Leave status there for status check. */ |
| _usbd_ep_int_stall_en(epn, bank_n, false); |
| dev_inst.ep_callbacks.done(ept->ep, USB_TRANS_STALL, ept->trans_count); |
| } |
| |
| /** |
| * \brief Handles transaction fail interrupt |
| * \param[in] ept Pointer to endpoint information. |
| * \param[in] bank_n Bank number. |
| */ |
| static void _usb_d_dev_handle_trfail(struct _usb_d_dev_ep *ept, const uint8_t bank_n) |
| { |
| Usb * hw = USB; |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| const uint8_t fail[2] = {USB_DEVICE_EPINTFLAG_TRFAIL0, USB_DEVICE_EPINTFLAG_TRFAIL1}; |
| UsbDeviceDescBank *bank = prvt_inst.desc_table[epn].DeviceDescBank; |
| uint8_t eptype |
| = bank_n ? hri_usbendpoint_read_EPCFG_EPTYPE1_bf(hw, epn) : hri_usbendpoint_read_EPCFG_EPTYPE0_bf(hw, epn); |
| bool is_ctrl = _usb_d_dev_ep_is_ctrl(ept); |
| USB_DEVICE_STATUS_BK_Type st; |
| st.reg = bank[bank_n].STATUS_BK.reg; |
| |
| if ((eptype == USB_D_EPTYPE_ISOCH) && st.bit.CRCERR) { |
| bank[bank_n].STATUS_BK.bit.CRCERR = 0; |
| hri_usbendpoint_clear_EPINTFLAG_reg(hw, epn, fail[bank_n]); |
| hri_usbendpoint_clear_EPINTEN_reg(hw, epn, fail[bank_n]); |
| _usb_d_dev_trans_stop(ept, bank_n, USB_TRANS_ERROR); |
| } else if (st.bit.ERRORFLOW) { |
| bank[bank_n].STATUS_BK.bit.ERRORFLOW = 0; |
| hri_usbendpoint_clear_EPINTFLAG_reg(hw, epn, fail[bank_n]); |
| hri_usbendpoint_clear_EPINTEN_reg(hw, epn, fail[bank_n]); |
| /* Abort control transfer. */ |
| if (is_ctrl && _usb_d_dev_ep_is_busy(ept)) { |
| if (bank_n != _usb_d_dev_ep_is_in(ept)) { |
| _usb_d_dev_trans_stop(ept, _usb_d_dev_ep_is_in(ept), USB_TRANS_DONE); |
| } |
| } |
| } else { |
| _usbd_ep_clear_bank_status(epn, bank_n); |
| hri_usbendpoint_clear_EPINTFLAG_reg(hw, epn, fail[bank_n]); |
| hri_usbendpoint_clear_EPINTEN_reg(hw, epn, fail[bank_n]); |
| } |
| } |
| |
| /** |
| * \brief Analyze flags for setup transaction |
| * \param[in] ept Pointer to endpoint information. |
| * \param[in] flags Endpoint interrupt flags. |
| */ |
| static inline void _usb_d_dev_trans_setup_isr(struct _usb_d_dev_ep *ept, const uint8_t flags) |
| { |
| /* |
| * SETPU is automatically ACKed by hardware |
| * OUT & IN should be set to NAK when checking SETUP |
| * No need to check OUT & IN status. |
| */ |
| if (flags & USB_DEVICE_EPINTFLAG_RXSTP) { |
| _usb_d_dev_handle_setup(ept); |
| } else if (flags & USB_DEVICE_EPINTFLAG_STALL1) { |
| _usb_d_dev_handle_stall(ept, 1); |
| } else if (flags & USB_DEVICE_EPINTFLAG_STALL0) { |
| _usb_d_dev_handle_stall(ept, 0); |
| } |
| } |
| |
| /** |
| * \brief Analyze flags for IN transactions |
| * \param[in] ept Pointer to endpoint information. |
| * \param[in] flags Endpoint interrupt flags. |
| */ |
| static inline void _usb_d_dev_trans_in_isr(struct _usb_d_dev_ep *ept, const uint8_t flags) |
| { |
| /* |
| * Check IN flags |
| * If control endpoint, SETUP & OUT is checked to see if abort |
| */ |
| if (flags & USB_DEVICE_EPINTFLAG_STALL1) { |
| _usb_d_dev_handle_stall(ept, 1); |
| } else if (flags & USB_DEVICE_EPINTFLAG_TRFAIL1) { |
| _usb_d_dev_handle_trfail(ept, 1); |
| } else if (flags & USB_DEVICE_EPINTFLAG_TRCPT1) { |
| _usb_d_dev_in_next(ept, true); |
| } else if (_usb_d_dev_ep_is_ctrl(ept)) { |
| /* Check OUT NAK |
| * Check SETUP |
| */ |
| if (flags & USB_DEVICE_EPINTFLAG_TRFAIL0) { |
| _usb_d_dev_handle_trfail(ept, 0); |
| } else if (flags & USB_DEVICE_EPINTFLAG_RXSTP) { |
| _usb_d_dev_handle_setup(ept); |
| } |
| } |
| } |
| |
| /** |
| * \brief Analyze flags for OUT transactions |
| * \param[in] ept Pointer to endpoint information. |
| * \param[in] flags Endpoint interrupt flags. |
| */ |
| static inline void _usb_d_dev_trans_out_isr(struct _usb_d_dev_ep *ept, const uint8_t flags) |
| { |
| /* |
| * Check OUT flags. |
| * If control endpoint, SETUP & IN NAK is checked to see if abort |
| */ |
| if (flags & USB_DEVICE_EPINTFLAG_STALL0) { |
| _usb_d_dev_handle_stall(ept, 0); |
| } else if (flags & USB_DEVICE_EPINTFLAG_TRFAIL0) { |
| _usb_d_dev_handle_trfail(ept, 0); |
| } else if (flags & USB_DEVICE_EPINTFLAG_TRCPT0) { |
| _usb_d_dev_out_next(ept, true); |
| } else if (_usb_d_dev_ep_is_ctrl(ept)) { |
| /* Check IN NAK |
| * Check SETUP |
| */ |
| if (flags & USB_DEVICE_EPINTFLAG_TRFAIL1) { |
| _usb_d_dev_handle_trfail(ept, 1); |
| } else if (flags & USB_DEVICE_EPINTFLAG_RXSTP) { |
| _usb_d_dev_handle_setup(ept); |
| } |
| } |
| } |
| |
| /** |
| * \brief Handles the endpoint interrupts. |
| * \param[in] epint Endpoint interrupt summary (by bits). |
| * \param[in] ept Pointer to endpoint information. |
| */ |
| static inline void _usb_d_dev_handle_eps(uint32_t epint, struct _usb_d_dev_ep *ept) |
| { |
| Usb *hw = USB; |
| |
| uint8_t flags, mask; |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| |
| if (!(epint & (1u << epn))) { |
| return; |
| } |
| flags = hw->DEVICE.DeviceEndpoint[epn].EPINTFLAG.reg; |
| mask = hw->DEVICE.DeviceEndpoint[epn].EPINTENSET.reg; |
| flags &= mask; |
| if (flags) { |
| if ((ept->flags.bits.eptype == 0x1) && !_usb_d_dev_ep_is_busy(ept)) { |
| _usb_d_dev_trans_setup_isr(ept, flags); |
| } else if (_usb_d_dev_ep_is_in(ept)) { |
| _usb_d_dev_trans_in_isr(ept, flags); |
| } else { |
| _usb_d_dev_trans_out_isr(ept, flags); |
| } |
| } |
| } |
| |
| /** |
| * \brief USB device interrupt handler |
| * \param[in] unused The parameter is not used |
| */ |
| static void _usb_d_dev_handler(void) |
| { |
| Usb * hw = USB; |
| uint8_t i; |
| |
| uint16_t epint = hw->DEVICE.EPINTSMRY.reg; |
| if (0 == epint) { |
| if (_usb_d_dev_handle_nep()) { |
| return; |
| } |
| } |
| /* Handle endpoints */ |
| for (i = 0; i < USB_D_N_EP; i++) { |
| struct _usb_d_dev_ep *ept = &dev_inst.ep[i]; |
| if (ept->ep == 0xFF) { |
| continue; |
| } |
| _usb_d_dev_handle_eps(epint, ept); |
| } |
| } |
| |
| /** |
| * \brief Reset all endpoint software instances |
| */ |
| static void _usb_d_dev_reset_epts(void) |
| { |
| uint8_t i; |
| for (i = 0; i < USB_D_N_EP; i++) { |
| _usb_d_dev_trans_done(&dev_inst.ep[i], USB_TRANS_RESET); |
| dev_inst.ep[i].ep = 0xFF; |
| dev_inst.ep[i].flags.u8 = 0; |
| } |
| memset(prvt_inst.desc_table, 0, sizeof(UsbDeviceDescriptor) * (CONF_USB_D_MAX_EP_N + 1)); |
| } |
| |
| int32_t _usb_d_dev_init(void) |
| { |
| Usb * hw = USB; |
| uint8_t speed = CONF_USB_D_SPEED; |
| const uint8_t spdconf[4] = { |
| USB_DEVICE_CTRLB_SPDCONF(1), /* LS */ |
| USB_DEVICE_CTRLB_SPDCONF(0), /* FS */ |
| 0, |
| 0 /* Reserved */ |
| }; |
| |
| if (!hri_usbdevice_is_syncing(hw, USB_SYNCBUSY_SWRST)) { |
| if (hri_usbdevice_get_CTRLA_reg(hw, USB_CTRLA_ENABLE)) { |
| hri_usbdevice_clear_CTRLA_ENABLE_bit(hw); |
| hri_usbdevice_wait_for_sync(hw, USB_SYNCBUSY_ENABLE); |
| } |
| hri_usbdevice_write_CTRLA_reg(hw, USB_CTRLA_SWRST); |
| } |
| hri_usbdevice_wait_for_sync(hw, USB_SYNCBUSY_SWRST); |
| |
| dev_inst.callbacks.sof = (_usb_d_dev_sof_cb_t)_dummy_func_no_return; |
| dev_inst.callbacks.event = (_usb_d_dev_event_cb_t)_dummy_func_no_return; |
| |
| dev_inst.ep_callbacks.setup = (_usb_d_dev_ep_cb_setup_t)_dummy_func_no_return; |
| dev_inst.ep_callbacks.more = (_usb_d_dev_ep_cb_more_t)_dummy_func_no_return; |
| dev_inst.ep_callbacks.done = (_usb_d_dev_ep_cb_done_t)_dummy_func_no_return; |
| |
| _usb_d_dev_reset_epts(); |
| |
| _usb_load_calib(); |
| |
| hri_usbdevice_write_CTRLA_reg(hw, USB_CTRLA_RUNSTDBY); |
| hri_usbdevice_write_DESCADD_reg(hw, (uint32_t)prvt_inst.desc_table); |
| hri_usbdevice_write_CTRLB_reg(hw, spdconf[speed] | USB_DEVICE_CTRLB_DETACH); |
| |
| return ERR_NONE; |
| } |
| |
| void _usb_d_dev_deinit(void) |
| { |
| Usb *hw = USB; |
| |
| while (_usb_d_dev_disable() < 0) |
| ; |
| |
| hri_usbdevice_write_CTRLA_reg(hw, USB_CTRLA_SWRST); |
| |
| NVIC_DisableIRQ(USB_0_IRQn); |
| NVIC_ClearPendingIRQ(USB_0_IRQn); |
| NVIC_DisableIRQ(USB_1_IRQn); |
| NVIC_ClearPendingIRQ(USB_1_IRQn); |
| NVIC_DisableIRQ(USB_2_IRQn); |
| NVIC_ClearPendingIRQ(USB_2_IRQn); |
| NVIC_DisableIRQ(USB_3_IRQn); |
| NVIC_ClearPendingIRQ(USB_3_IRQn); |
| } |
| |
| int32_t _usb_d_dev_enable(void) |
| { |
| Usb * hw = USB; |
| uint8_t ctrla; |
| |
| if (hri_usbdevice_get_SYNCBUSY_reg(hw, (USB_SYNCBUSY_ENABLE | USB_SYNCBUSY_SWRST))) { |
| return -USB_ERR_DENIED; |
| } |
| ctrla = hri_usbdevice_read_CTRLA_reg(hw); |
| if ((ctrla & USB_CTRLA_ENABLE) == 0) { |
| hri_usbdevice_write_CTRLA_reg(hw, ctrla | USB_CTRLA_ENABLE); |
| } |
| |
| NVIC_EnableIRQ(USB_0_IRQn); |
| NVIC_EnableIRQ(USB_1_IRQn); |
| NVIC_EnableIRQ(USB_2_IRQn); |
| NVIC_EnableIRQ(USB_3_IRQn); |
| |
| hri_usbdevice_set_INTEN_reg(hw, |
| USB_DEVICE_INTENSET_SOF | USB_DEVICE_INTENSET_EORST | USB_DEVICE_INTENSET_RAMACER |
| | USB_D_SUSPEND_INT_FLAGS); |
| |
| return ERR_NONE; |
| } |
| |
| int32_t _usb_d_dev_disable(void) |
| { |
| Usb * hw = USB; |
| uint8_t ctrla; |
| |
| if (hri_usbdevice_get_SYNCBUSY_reg(hw, (USB_SYNCBUSY_ENABLE | USB_SYNCBUSY_SWRST))) { |
| return -USB_ERR_DENIED; |
| } |
| |
| ctrla = hri_usbdevice_read_CTRLA_reg(hw); |
| if (ctrla & USB_CTRLA_ENABLE) { |
| hri_usbdevice_write_CTRLA_reg(hw, ctrla & ~USB_CTRLA_ENABLE); |
| } |
| |
| NVIC_DisableIRQ(USB_0_IRQn); |
| NVIC_DisableIRQ(USB_1_IRQn); |
| NVIC_DisableIRQ(USB_2_IRQn); |
| NVIC_DisableIRQ(USB_3_IRQn); |
| |
| hri_usbdevice_clear_INTEN_reg(hw, |
| USB_DEVICE_INTENSET_SOF | USB_DEVICE_INTENSET_EORST | USB_DEVICE_INTENSET_RAMACER |
| | USB_D_SUSPEND_INT_FLAGS | USB_D_WAKEUP_INT_FLAGS); |
| |
| return ERR_NONE; |
| } |
| |
| void _usb_d_dev_attach(void) |
| { |
| hri_usbdevice_clear_CTRLB_DETACH_bit(USB); |
| } |
| |
| void _usb_d_dev_detach(void) |
| { |
| hri_usbdevice_set_CTRLB_DETACH_bit(USB); |
| } |
| |
| #ifndef USB_FSMSTATUS_FSMSTATE_ON |
| #define USB_FSMSTATUS_FSMSTATE_ON USB_FSMSTATUS_FSMSTATE(2ul) |
| #endif |
| void _usb_d_dev_send_remotewakeup(void) |
| { |
| uint32_t retry = CONF_USB_RMT_WKUP_RETRY; |
| _usb_d_dev_wait_clk_rdy(CONF_USB_D_CLK_SRC); |
| while ((USB_FSMSTATUS_FSMSTATE_ON != hri_usbdevice_read_FSMSTATUS_FSMSTATE_bf(USB)) && (retry--)) { |
| USB->DEVICE.CTRLB.bit.UPRSM = 1; |
| } |
| } |
| |
| enum usb_speed _usb_d_dev_get_speed(void) |
| { |
| uint8_t sp = (enum usb_speed)hri_usbdevice_read_STATUS_SPEED_bf(USB); |
| const enum usb_speed speed[2] = {USB_SPEED_FS, USB_SPEED_LS}; |
| |
| return speed[sp]; |
| } |
| |
| void _usb_d_dev_set_address(uint8_t addr) |
| { |
| hri_usbdevice_write_DADD_reg(USB, USB_DEVICE_DADD_ADDEN | USB_DEVICE_DADD_DADD(addr)); |
| } |
| |
| uint8_t _usb_d_dev_get_address(void) |
| { |
| uint8_t addr = hri_usbdevice_read_DADD_DADD_bf(USB); |
| return addr; |
| } |
| |
| uint16_t _usb_d_dev_get_frame_n(void) |
| { |
| uint16_t fn = hri_usbdevice_read_FNUM_FNUM_bf(USB); |
| return fn; |
| } |
| |
| uint8_t _usb_d_dev_get_uframe_n(void) |
| { |
| uint8_t ufn = hri_usbdevice_read_FNUM_MFNUM_bf(USB); |
| return ufn; |
| } |
| |
| /** |
| * \brief Start a setup transaction |
| * \param[in] ept Endpoint information. |
| */ |
| static inline void _usb_d_dev_trans_setup(struct _usb_d_dev_ep *ept) |
| { |
| Usb * hw = USB; |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| |
| _usbd_ep_set_buf(epn, 0, (uint32_t)ept->cache); |
| _usbd_ep_set_out_trans(epn, 0, ept->size, 0); |
| |
| hri_usbendpoint_clear_EPSTATUS_reg(hw, epn, USB_DEVICE_EPSTATUS_STALLRQ(0x3) | USB_DEVICE_EPSTATUS_BK1RDY); |
| _usbd_ep_set_out_rdy(epn, 0, false); |
| |
| hri_usbendpoint_set_EPINTEN_reg(hw, epn, USB_D_SETUP_INT_FLAGS); |
| } |
| |
| int32_t _usb_d_dev_ep0_init(const uint8_t max_pkt_siz) |
| { |
| return _usb_d_dev_ep_init(0, USB_EP_XTYPE_CTRL, max_pkt_siz); |
| } |
| |
| int32_t _usb_d_dev_ep_init(const uint8_t ep, const uint8_t attr, const uint16_t max_pkt_siz) |
| { |
| uint8_t epn = USB_EP_GET_N(ep); |
| bool dir = USB_EP_GET_DIR(ep); |
| struct _usb_d_dev_ep *ept = _usb_d_dev_ept(epn, dir); |
| |
| uint8_t ep_type = attr & USB_EP_XTYPE_MASK; |
| const struct _usb_ep_cfg_item *pcfg = &_usb_ep_cfgs[epn]; |
| |
| if (epn > CONF_USB_D_MAX_EP_N) { |
| return -USB_ERR_PARAM; |
| } |
| if (ept->ep != 0xFF) { |
| return -USB_ERR_REDO; |
| } |
| if (ep_type == USB_EP_XTYPE_CTRL) { |
| struct _usb_d_dev_ep *ept_in = _usb_d_dev_ept(epn, !dir); |
| if (ept_in->ep != 0xFF) { |
| return -USB_ERR_REDO; |
| } |
| if (pcfg->cache == NULL) { |
| return -USB_ERR_FUNC; |
| } |
| } |
| if ((dir ? pcfg->i_cache : pcfg->cache) && ((dir ? pcfg->i_size : pcfg->size) < max_pkt_siz)) { |
| return -USB_ERR_FUNC; |
| } |
| |
| /* Initialize EP n settings */ |
| ept->cache = (uint8_t *)(dir ? pcfg->i_cache : pcfg->cache); |
| ept->size = max_pkt_siz; |
| ept->flags.u8 = (ep_type + 1); |
| ept->ep = ep; |
| |
| return USB_OK; |
| } |
| |
| void _usb_d_dev_ep_deinit(uint8_t ep) |
| { |
| Usb * hw = USB; |
| uint8_t epn = USB_EP_GET_N(ep); |
| bool dir = USB_EP_GET_DIR(ep); |
| struct _usb_d_dev_ep *ept = _usb_d_dev_ept(epn, dir); |
| |
| if (epn > CONF_USB_D_MAX_EP_N || !_usb_d_dev_ep_is_used(ept)) { |
| return; |
| } |
| |
| /* Finish pending transactions. */ |
| _usb_d_dev_trans_stop(ept, dir, USB_TRANS_RESET); |
| |
| /* Disable the endpoint. */ |
| if (_usb_d_dev_ep_is_ctrl(ept)) { |
| hw->DEVICE.DeviceEndpoint[ep].EPCFG.reg = 0; |
| } else if (USB_EP_GET_DIR(ep)) { |
| hw->DEVICE.DeviceEndpoint[USB_EP_GET_N(ep)].EPCFG.reg &= ~USB_DEVICE_EPCFG_EPTYPE1_Msk; |
| } else { |
| hw->DEVICE.DeviceEndpoint[ep].EPCFG.reg &= ~USB_DEVICE_EPCFG_EPTYPE0_Msk; |
| } |
| ept->flags.u8 = 0; |
| ept->ep = 0xFF; |
| } |
| |
| int32_t _usb_d_dev_ep_enable(const uint8_t ep) |
| { |
| Usb * hw = USB; |
| uint8_t epn = USB_EP_GET_N(ep); |
| bool dir = USB_EP_GET_DIR(ep); |
| struct _usb_d_dev_ep *ept = _usb_d_dev_ept(epn, dir); |
| uint8_t epcfg = hri_usbendpoint_read_EPCFG_reg(hw, epn); |
| UsbDeviceDescBank * bank; |
| |
| if (epn > CONF_USB_D_MAX_EP_N || !_usb_d_dev_ep_is_used(ept)) { |
| return -USB_ERR_PARAM; |
| } |
| |
| bank = prvt_inst.desc_table[epn].DeviceDescBank; |
| if (ept->flags.bits.eptype == USB_D_EPTYPE_CTRL) { |
| if (epcfg & (USB_DEVICE_EPCFG_EPTYPE1_Msk | USB_DEVICE_EPCFG_EPTYPE0_Msk)) { |
| return -USB_ERR_REDO; |
| } |
| hri_usbendpoint_write_EPCFG_reg(hw, epn, USB_D_EPCFG_CTRL); |
| bank[0].PCKSIZE.reg = USB_DEVICE_PCKSIZE_MULTI_PACKET_SIZE(ept->size) |
| | USB_DEVICE_PCKSIZE_SIZE(_usbd_ep_pcksize_size(ept->size)); |
| bank[1].PCKSIZE.reg |
| = USB_DEVICE_PCKSIZE_BYTE_COUNT(ept->size) | USB_DEVICE_PCKSIZE_SIZE(_usbd_ep_pcksize_size(ept->size)); |
| /* By default, control endpoint accept SETUP and NAK all other token. */ |
| _usbd_ep_set_out_rdy(epn, 0, false); |
| _usbd_ep_set_in_rdy(epn, 1, false); |
| |
| _usbd_ep_clear_bank_status(epn, 0); |
| _usbd_ep_clear_bank_status(epn, 1); |
| |
| /* Enable SETUP reception for control endpoint. */ |
| _usb_d_dev_trans_setup(ept); |
| |
| } else if (dir) { |
| if (epcfg & USB_DEVICE_EPCFG_EPTYPE1_Msk) { |
| return -USB_ERR_REDO; |
| } |
| epcfg |= USB_DEVICE_EPCFG_EPTYPE1(ept->flags.bits.eptype); |
| hri_usbendpoint_write_EPCFG_reg(hw, epn, epcfg); |
| |
| bank[1].PCKSIZE.reg |
| = USB_DEVICE_PCKSIZE_BYTE_COUNT(ept->size) | USB_DEVICE_PCKSIZE_SIZE(_usbd_ep_pcksize_size(ept->size)); |
| |
| /* By default, IN endpoint will NAK all token. */ |
| _usbd_ep_set_in_rdy(epn, 1, false); |
| _usbd_ep_clear_bank_status(epn, 1); |
| |
| } else { |
| |
| if (epcfg & USB_DEVICE_EPCFG_EPTYPE0_Msk) { |
| return -USB_ERR_REDO; |
| } |
| epcfg |= USB_DEVICE_EPCFG_EPTYPE0(ept->flags.bits.eptype); |
| hri_usbendpoint_write_EPCFG_reg(hw, epn, epcfg); |
| |
| bank[0].PCKSIZE.reg = USB_DEVICE_PCKSIZE_MULTI_PACKET_SIZE(ept->size) |
| | USB_DEVICE_PCKSIZE_SIZE(_usbd_ep_pcksize_size(ept->size)); |
| |
| /* By default, OUT endpoint will NAK all token. */ |
| _usbd_ep_set_out_rdy(epn, 0, false); |
| _usbd_ep_clear_bank_status(epn, 0); |
| } |
| |
| return USB_OK; |
| } |
| |
| void _usb_d_dev_ep_disable(const uint8_t ep) |
| { |
| Usb * hw = USB; |
| uint8_t epn = USB_EP_GET_N(ep); |
| bool dir = USB_EP_GET_DIR(ep); |
| struct _usb_d_dev_ep *ept = _usb_d_dev_ept(epn, dir); |
| |
| _usb_d_dev_trans_stop(ept, dir, USB_TRANS_RESET); |
| if (_usb_d_dev_ep_is_ctrl(ept)) { |
| hri_usbendpoint_clear_EPINTEN_reg(hw, epn, USB_D_ALL_INT_FLAGS); |
| } |
| } |
| |
| /** |
| * \brief Get endpoint stall status |
| * \param[in] ept Pointer to endpoint information. |
| * \param[in] dir Endpoint direction. |
| * \return Stall status. |
| * \retval \c true Endpoint is stalled. |
| * \retval \c false Endpoint is not stalled. |
| */ |
| static inline int32_t _usb_d_dev_ep_stall_get(struct _usb_d_dev_ep *ept, bool dir) |
| { |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| return _usbd_ep_is_stalled(epn, dir); |
| } |
| |
| /** |
| * \brief Set endpoint stall |
| * \param[in, out] ept Pointer to endpoint information. |
| * \param[in] dir Endpoint direction. |
| * \return Always 0, success. |
| */ |
| static inline int32_t _usb_d_dev_ep_stall_set(struct _usb_d_dev_ep *ept, bool dir) |
| { |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| _usbd_ep_set_stall(epn, dir, true); |
| _usbd_ep_int_en(epn, USB_DEVICE_EPINTFLAG_STALL0 << dir); |
| ept->flags.bits.is_stalled = 1; |
| /* In stall interrupt abort the transfer. */ |
| return ERR_NONE; |
| } |
| |
| /** |
| * \brief Clear endpoint stall |
| * \param[in, out] ept Pointer to endpoint information. |
| * \param[in] dir Endpoint direction. |
| * \return Always 0, success. |
| */ |
| static inline int32_t _usb_d_dev_ep_stall_clr(struct _usb_d_dev_ep *ept, bool dir) |
| { |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| bool is_stalled = _usbd_ep_is_stalled(epn, dir); |
| if (!is_stalled) { |
| return ERR_NONE; |
| } |
| _usbd_ep_set_stall(epn, dir, false); |
| _usbd_ep_int_dis(epn, USB_DEVICE_EPINTFLAG_STALL0 << dir); |
| if (_usbd_ep_is_stall_sent(epn, dir)) { |
| _usbd_ep_ack_stall(epn, dir); |
| _usbd_ep_set_toggle(epn, dir, 0); |
| } |
| if (_usb_d_dev_ep_is_ctrl(ept)) { |
| if ((hri_usbendpoint_read_EPSTATUS_reg(USB, epn) & USB_DEVICE_EPSTATUS_STALLRQ_Msk) == 0) { |
| ept->flags.bits.is_stalled = 0; |
| } |
| } else { |
| ept->flags.bits.is_stalled = 0; |
| } |
| return ERR_NONE; |
| } |
| |
| int32_t _usb_d_dev_ep_stall(const uint8_t ep, const enum usb_ep_stall_ctrl ctrl) |
| { |
| uint8_t epn = USB_EP_GET_N(ep); |
| bool dir = USB_EP_GET_DIR(ep); |
| struct _usb_d_dev_ep *ept = _usb_d_dev_ept(epn, dir); |
| int32_t rc; |
| |
| if (epn > CONF_USB_D_MAX_EP_N) { |
| return -USB_ERR_PARAM; |
| } |
| |
| if (USB_EP_STALL_SET == ctrl) { |
| rc = _usb_d_dev_ep_stall_set(ept, dir); |
| } else if (USB_EP_STALL_CLR == ctrl) { |
| rc = _usb_d_dev_ep_stall_clr(ept, dir); |
| } else { |
| rc = _usb_d_dev_ep_stall_get(ept, dir); |
| } |
| return rc; |
| } |
| |
| /** |
| * \brief Finish the transaction and invoke callback |
| * \param[in, out] ept Pointer to endpoint information. |
| * \param[in] code Information code passed. |
| */ |
| static void _usb_d_dev_trans_done(struct _usb_d_dev_ep *ept, const int32_t code) |
| { |
| if (!(_usb_d_dev_ep_is_used(ept) && _usb_d_dev_ep_is_busy(ept))) { |
| return; |
| } |
| ept->flags.bits.is_busy = 0; |
| dev_inst.ep_callbacks.done(ept->ep, code, ept->trans_count); |
| } |
| |
| /** |
| * \brief Terminate the transaction with specific status code |
| * \param[in, out] ept Pointer to endpoint information. |
| * \param[in] dir Endpoint direction. |
| * \param[in] code Information code passed. |
| */ |
| static void _usb_d_dev_trans_stop(struct _usb_d_dev_ep *ept, bool dir, const int32_t code) |
| { |
| uint8_t epn = USB_EP_GET_N(ept->ep); |
| ; |
| const uint8_t intflags[2] = {USB_D_BANK0_INT_FLAGS, USB_D_BANK1_INT_FLAGS}; |
| if (!(_usb_d_dev_ep_is_used(ept) && _usb_d_dev_ep_is_busy(ept))) { |
| return; |
| } |
| /* Stop transfer */ |
| if (dir) { |
| /* NAK IN */ |
| _usbd_ep_set_in_rdy(epn, 1, false); |
| } else { |
| /* NAK OUT */ |
| _usbd_ep_set_out_rdy(epn, 0, false); |
| } |
| _usbd_ep_int_ack(epn, intflags[dir]); |
| _usbd_ep_int_dis(epn, intflags[dir]); |
| _usb_d_dev_trans_done(ept, code); |
| } |
| |
| int32_t _usb_d_dev_ep_read_req(const uint8_t ep, uint8_t *req_buf) |
| { |
| uint8_t epn = USB_EP_GET_N(ep); |
| UsbDeviceDescBank *bank = prvt_inst.desc_table[epn].DeviceDescBank; |
| uint32_t addr = bank[0].ADDR.reg; |
| uint16_t bytes = bank[0].PCKSIZE.bit.BYTE_COUNT; |
| |
| if (epn > CONF_USB_D_MAX_EP_N || !req_buf) { |
| return -USB_ERR_PARAM; |
| } |
| if (!_usbd_ep_is_ctrl(epn)) { |
| return -USB_ERR_FUNC; |
| } |
| if (!_usbd_ep_is_setup(epn)) { |
| return ERR_NONE; |
| } |
| memcpy(req_buf, (void *)addr, 8); |
| _usbd_ep_ack_setup(epn); |
| |
| return bytes; |
| } |
| |
| int32_t _usb_d_dev_ep_trans(const struct usb_d_transfer *trans) |
| { |
| uint8_t epn = USB_EP_GET_N(trans->ep); |
| bool dir = USB_EP_GET_DIR(trans->ep); |
| struct _usb_d_dev_ep *ept = _usb_d_dev_ept(epn, dir); |
| |
| uint16_t size_mask = (ept->size == 1023) ? 1023 : (ept->size - 1); |
| bool size_n_aligned = (trans->size & size_mask); |
| |
| bool use_cache = false; |
| |
| volatile hal_atomic_t flags; |
| |
| if (epn > CONF_USB_D_MAX_EP_N) { |
| return -USB_ERR_PARAM; |
| } |
| |
| /* Cases that needs cache: |
| * 1. Buffer not in RAM (cache all). |
| * 2. IN/OUT with unaligned buffer (cache all). |
| * 3. OUT with unaligned packet size (cache last packet). |
| * 4. OUT size < 8 (sub-case for 3). |
| */ |
| if (!_usb_is_addr4dma(trans->buf, trans->size) || (!_usb_is_aligned(trans->buf)) |
| || (!dir && (trans->size < ept->size))) { |
| if (!ept->cache) { |
| return -USB_ERR_FUNC; |
| } |
| /* Use cache all the time. */ |
| use_cache = true; |
| } |
| if (!dir && size_n_aligned) { |
| if (!ept->cache) { |
| return -USB_ERR_PARAM; |
| } |
| /* Set 'use_cache' on last packet. */ |
| } |
| |
| /* Check halt */ |
| if (ept->flags.bits.is_stalled) { |
| return USB_HALTED; |
| } |
| |
| /* Try to start transactions. */ |
| |
| atomic_enter_critical(&flags); |
| if (_usb_d_dev_ep_is_busy(ept)) { |
| atomic_leave_critical(&flags); |
| return USB_BUSY; |
| } |
| ept->flags.bits.is_busy = 1; |
| atomic_leave_critical(&flags); |
| |
| /* Copy transaction information. */ |
| ept->trans_buf = trans->buf; |
| ept->trans_size = trans->size; |
| ept->trans_count = 0; |
| |
| ept->flags.bits.dir = dir; |
| ept->flags.bits.use_cache = use_cache; |
| ept->flags.bits.need_zlp = (trans->zlp && (!size_n_aligned)); |
| |
| if (dir) { |
| _usb_d_dev_in_next(ept, false); |
| } else { |
| _usb_d_dev_out_next(ept, false); |
| } |
| |
| return ERR_NONE; |
| } |
| |
| void _usb_d_dev_ep_abort(const uint8_t ep) |
| { |
| uint8_t epn = USB_EP_GET_N(ep); |
| bool dir = USB_EP_GET_DIR(ep); |
| struct _usb_d_dev_ep *ept = _usb_d_dev_ept(epn, dir); |
| if (epn > CONF_USB_D_MAX_EP_N) { |
| return; |
| } |
| _usb_d_dev_trans_stop(ept, dir, USB_TRANS_ABORT); |
| } |
| |
| int32_t _usb_d_dev_ep_get_status(const uint8_t ep, struct usb_d_trans_status *stat) |
| { |
| uint8_t epn = USB_EP_GET_N(ep); |
| bool dir = USB_EP_GET_DIR(ep); |
| struct _usb_d_dev_ep *ept = _usb_d_dev_ept(epn, dir); |
| bool busy, stall; |
| |
| if (epn > CONF_USB_D_MAX_EP_N) { |
| return USB_ERR_PARAM; |
| } |
| busy = ept->flags.bits.is_busy; |
| stall = ept->flags.bits.is_stalled; |
| if (stat) { |
| stat->stall = stall; |
| stat->busy = busy; |
| stat->setup = USB->DEVICE.DeviceEndpoint[epn].EPINTFLAG.bit.RXSTP; |
| stat->dir = ept->flags.bits.dir; |
| stat->size = ept->trans_size; |
| stat->count = ept->trans_count; |
| stat->ep = ep; |
| stat->xtype = ept->flags.bits.eptype - 1; |
| } |
| if (stall) { |
| return USB_HALTED; |
| } |
| if (busy) { |
| return USB_BUSY; |
| } |
| return USB_OK; |
| } |
| |
| void _usb_d_dev_register_callback(const enum usb_d_cb_type type, const FUNC_PTR func) |
| { |
| FUNC_PTR f = (func == NULL) ? (FUNC_PTR)_dummy_func_no_return : (FUNC_PTR)func; |
| if (type == USB_D_CB_EVENT) { |
| dev_inst.callbacks.event = (_usb_d_dev_event_cb_t)f; |
| } else if (type == USB_D_CB_SOF) { |
| dev_inst.callbacks.sof = (_usb_d_dev_sof_cb_t)f; |
| } |
| } |
| |
| void _usb_d_dev_register_ep_callback(const enum usb_d_dev_ep_cb_type type, const FUNC_PTR func) |
| { |
| FUNC_PTR f = (func == NULL) ? (FUNC_PTR)_dummy_func_no_return : (FUNC_PTR)func; |
| if (type == USB_D_DEV_EP_CB_SETUP) { |
| dev_inst.ep_callbacks.setup = (_usb_d_dev_ep_cb_setup_t)f; |
| } else if (type == USB_D_DEV_EP_CB_MORE) { |
| dev_inst.ep_callbacks.more = (_usb_d_dev_ep_cb_more_t)f; |
| } else if (type == USB_D_DEV_EP_CB_DONE) { |
| dev_inst.ep_callbacks.done = (_usb_d_dev_ep_cb_done_t)f; |
| } |
| } |
| |
| /** |
| * \brief USB interrupt handler |
| */ |
| void USB_0_Handler(void) |
| { |
| |
| _usb_d_dev_handler(); |
| } |
| /** |
| * \brief USB interrupt handler |
| */ |
| void USB_1_Handler(void) |
| { |
| |
| _usb_d_dev_handler(); |
| } |
| /** |
| * \brief USB interrupt handler |
| */ |
| void USB_2_Handler(void) |
| { |
| |
| _usb_d_dev_handler(); |
| } |
| /** |
| * \brief USB interrupt handler |
| */ |
| void USB_3_Handler(void) |
| { |
| |
| _usb_d_dev_handler(); |
| } |