diff --git a/driver/ch32/usb_drv.c b/driver/ch32/usb_drv.c new file mode 100644 index 0000000..0efdbe0 --- /dev/null +++ b/driver/ch32/usb_drv.c @@ -0,0 +1,646 @@ +#include "usb_drv.h" + +#include +#include + +#include "flatUSB_config.h" +#include "usb_driver_common.h" + +#include FLATUSB_DESCRIPTOR_HEADER + +#include "usb_common.h" + +#include "ch32f20x_usb.h" + +#ifndef SET_BIT +#define SET_BIT(r, b) (r) |= (b) +#endif + +#ifndef GET_BIT +#define GET_BIT(r, b) (((r) & (b)) != 0) +#endif + +#ifndef CLEAR_BIT +#define CLEAR_BIT(r, b) (r) &= ~(b) +#endif + +#ifndef __weak +#define __weak __attribute__((weak)) +#endif + +// ------------------------ + +// combined buffer: TX1 | RX1 | TX2 | RX2 (TODO: ennek még utána kell nézni) + +#define USB_EP_TX_BUF_SIZE (64) ///< Transmit buffer size +#define USB_EP_RX_BUF_SIZE (64) ///< Receive buffer size +#define USB_EP_COMBINED_BUF_SIZE ((USB_EP_TX_BUF_SIZE + USB_EP_RX_BUF_SIZE) * 2) ///< Combined buffer size in a single direction +#define USB_EP_SUMMED_BUF_SIZE (USB_EP_COMBINED_BUF_SIZE * USB_NUM_OF_ENDPOINTS * 2) ///< Summed size for each endpoint in each direction + +static USBDRV_GlobalState gs; ///< Global USB state +static uint8_t buf[USB_EP_SUMMED_BUF_SIZE] DWORD_ALIGN; ///< Transmit/Receive buffer +static UsbDrv_IN_cb cbs[USB_NUM_OF_ENDPOINTS]; ///< Callbacks for IN completion + +// FIXME: ez lehet, hogy pont fordítva van... +#define USB_EP_GET_EP0_BUFFER() (gs.buf) +#define USB_EP_GET_TX_BUFFER(ep) (gs.buf + ((ep) * USB_EP_COMBINED_BUF_SIZE)) +#define USB_EP_GET_RX_BUFFER(ep) (gs.buf + ((ep) * USB_EP_COMBINED_BUF_SIZE) + USB_EP_TX_BUF_SIZE) + +/** \cond false */ +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +/** \endcond */ + +// ------------------------ + +void usbdrv_register_IN_complete_cb(uint8_t ep, UsbDrv_IN_cb cb) { + cbs[ep] = cb; +} + +// ------------------------ + +static UsbDrv_DrvIntf drvIntf; + +void usbdrv_init_intf() { + drvIntf.init = usbdrv_init; + drvIntf.reset = usbdrv_reset; + drvIntf.stall = usbdrv_stall_endpoint; + drvIntf.arm_IN = usbdrv_arm_IN_endpoint; + drvIntf.arm_OUT = usbdrv_arm_OUT_endpoint; + drvIntf.set_address = usbdrv_set_address; + drvIntf.set_config = usbdrv_fetch_endpoint_configuration; + drvIntf.autoarm = usbdrv_autoarm_OUT_endpoint; + drvIntf.en_ep_irq = usbdrv_enable_endpoint_interrupt; + drvIntf.reg_IN_cb = usbdrv_register_IN_complete_cb; +} + +UsbDrv_DrvIntf *usbdrv_get_intf() { + return &drvIntf; +} + +// ------------------------ + +// issue a reset +static void usbdrv_hw_reset() { + SET_BIT(USBG->BASE_CTRL, USBFS_UC_RESET_SIE); // assert reset + USB_DELAY(1); // insert some delay + CLEAR_BIT(USBG->BASE_CTRL, USBFS_UC_RESET_SIE); // release reset + return; +} + +// initialize USB peripheral +void usbdrv_periph_init(bool reset) { + if (!reset) { + // enable USB clock + USB_CLOCK_ENABLE(); + + // trigger a reset (FIXME: biztos jó ez így?) + usbdrv_hw_reset(); + } + + // turn off the transciever + usbdrv_power_and_connect(false); + + // select device mode with internal pullups + CLEAR_BIT(USBG->BASE_CTRL, USBFS_UC_HOST_MODE); // device mode + SET_BIT(USBG->BASE_CTRL, USBFS_UC_SYS_CTRL3); // pullup activation + + // set Full-Speed operation (TODO: lehet, hogy a low-speed-et is érdemes megírni) + CLEAR_BIT(USBG->BASE_CTRL, USBFS_UC_LOW_SPEED); // (global) + CLEAR_BIT(USBG->UDEV_CTRL, USBFS_UD_LOW_SPEED); // (device) + + // set automatic NAK reporting + SET_BIT(USBG->BASE_CTRL, USBFS_UC_INT_BUSY); + + // clear all interrupts + USBG->INT_FG = 0xFF; + + // allow specific interrupts + uint32_t intmask = + USBFS_UIE_BUS_RST | // Bus reset + USBFS_UIE_TRANSFER | // Transfer complete interrupt + USBFS_UIE_SUSPEND | // Bues suspend or wakeup interrupt + USBFS_UIE_FIFO_OV; // FIFO overflow interrupt + USBG->INT_EN = intmask; + + if (!reset) { + // flush Tx and Rx FIFOs + usbdrv_clear_all_fifos(); + } +} + +// initialize global state +void usbdrv_init_global_state() { + // clear state + memset(&gs, 0, sizeof(USBDRV_GlobalState)); + + // clear IN complete callbacks + memset(&cbs, 0, sizeof(UsbDrv_IN_cb) * USB_NUM_OF_ENDPOINTS); + + // initialize receive buffer + gs.buf = buf; +} + +// initialize USB subsystem +void usbdrv_init() { + USB_IRQ_DISABLE(USB_IRQ_N); // disable USB interrupts + + usbdrv_init_global_state(); + usbdrv_init_intf(); + usbdrv_gpio_init(); + usbdrv_periph_init(false); + usbdrv_initial_ep0_setup(); + usbdrv_power_and_connect(true); + + USB_IRQ_SET_PRIORITY(USB_IRQ_N, USB_IRQ_PRIORITY); + USB_IRQ_ENABLE(USB_IRQ_N); +} + +void usbdrv_reset() { + USB_IRQ_DISABLE(USB_IRQ_N); // disable USB interrupts + + usbdrv_hw_reset(); // hardware reset + usbdrv_init_global_state(); + usbdrv_periph_init(true); + usbdrv_initial_ep0_setup(); + usbdrv_power_and_connect(true); + + USB_IRQ_SET_PRIORITY(USB_IRQ_N, USB_IRQ_PRIORITY); + USB_IRQ_ENABLE(USB_IRQ_N); +} + +// connect to or disconnect from the bus +void usbdrv_power_and_connect(bool en) { + if (en) { // ON + SET_BIT(USBG->BASE_CTRL, USBFS_UC_DEV_PU_EN); // enable USB device and internal pull-ups (global) + SET_BIT(USBG->UDEV_CTRL, USBFS_UD_PORT_EN); // enable USB device physical port (device) + SET_BIT(USBG->BASE_CTRL, USBFS_UC_DMA_EN); // enable DMA operation and interrupts + } else { // OFF + CLEAR_BIT(USBG->BASE_CTRL, USBFS_UC_DEV_PU_EN); // disable... + CLEAR_BIT(USBG->UDEV_CTRL, USBFS_UD_PORT_EN); // ... + CLEAR_BIT(USBG->BASE_CTRL, USBFS_UC_DMA_EN); // ... + } +} + +// ------------------- + +// clear all FIFOs +void usbdrv_clear_all_fifos() { + SET_BIT(USBG->BASE_CTRL, USBFS_UC_CLR_ALL); + USB_DELAY(1); + CLEAR_BIT(USBG->BASE_CTRL, USBFS_UC_CLR_ALL); +} + +// --------------------- + +typedef struct { + volatile uint8_t *UEP_MOD; ///< EP mode control register + uint8_t RX_EN; ///< EP receive enable flag + uint8_t TX_EN; ///< EP transmit enable flag + uint8_t BUF_MOD; ///< EP buffer mode flag + volatile uint32_t *BUF_START_ADDR; ///< EP buffer start address + volatile uint16_t *TRANSMIT_LENGTH; ///< EP transmit length register + volatile uint8_t *TX_CTRL; ///< EP transmit control register + volatile uint8_t *RX_CTRL; ///< EP receive control register +} USB_EP_Ctrl; + +// clang-format off +static const USB_EP_Ctrl USB_EPCtrl[USB_NUM_OF_ENDPOINTS] = { + {NULL, 0, 0, 0, &(USBG->UEP0_DMA), &(USBG->UEP0_TX_LEN), &(USBG->UEP0_TX_CTRL), &(USBG->UEP0_RX_CTRL)}, // EP0 + {&(USBG->UEP4_1_MOD), USBFS_UEP1_RX_EN, USBFS_UEP1_TX_EN, USBFS_UEP1_BUF_MOD, &(USBG->UEP1_DMA), &(USBG->UEP1_TX_LEN), &(USBG->UEP1_TX_CTRL), &(USBG->UEP1_RX_CTRL) }, // EP1 + {&(USBG->UEP2_3_MOD), USBFS_UEP2_RX_EN, USBFS_UEP2_TX_EN, USBFS_UEP2_BUF_MOD, &(USBG->UEP2_DMA), &(USBG->UEP2_TX_LEN), &(USBG->UEP2_TX_CTRL), &(USBG->UEP2_RX_CTRL) }, // EP2 + {&(USBG->UEP2_3_MOD), USBFS_UEP3_RX_EN, USBFS_UEP3_TX_EN, USBFS_UEP3_BUF_MOD, &(USBG->UEP3_DMA), &(USBG->UEP3_TX_LEN), &(USBG->UEP3_TX_CTRL), &(USBG->UEP3_RX_CTRL) }, // EP3 + {&(USBG->UEP4_1_MOD), USBFS_UEP4_RX_EN, USBFS_UEP4_TX_EN, USBFS_UEP4_BUF_MOD, &(USBG->UEP4_DMA), &(USBG->UEP4_TX_LEN), &(USBG->UEP4_TX_CTRL), &(USBG->UEP4_RX_CTRL) }, // EP4 + {&(USBG->UEP5_6_MOD), USBFS_UEP5_RX_EN, USBFS_UEP5_TX_EN, USBFS_UEP5_BUF_MOD, &(USBG->UEP5_DMA), &(USBG->UEP5_TX_LEN), &(USBG->UEP5_TX_CTRL), &(USBG->UEP5_RX_CTRL) }, // EP5 + {&(USBG->UEP5_6_MOD), USBFS_UEP6_RX_EN, USBFS_UEP6_TX_EN, USBFS_UEP6_BUF_MOD, &(USBG->UEP6_DMA), &(USBG->UEP6_TX_LEN), &(USBG->UEP6_TX_CTRL), &(USBG->UEP6_RX_CTRL) }, // EP6 + {&(USBG->UEP7_MOD), USBFS_UEP7_RX_EN, USBFS_UEP7_TX_EN, USBFS_UEP7_BUF_MOD, &(USBG->UEP7_DMA), &(USBG->UEP7_TX_LEN), &(USBG->UEP7_TX_CTRL), &(USBG->UEP7_RX_CTRL) }, // EP7 +}; + +// clang-format on + +// configure USB endpoint +void usbdrv_configure_endpoint(uint8_t ep, uint8_t dir, const USBDRV_EpConfig *cfg) { + const USB_EP_Ctrl *epc = USB_EPCtrl + ep; // get Endpoint control + + if (dir == USB_OUT) { // ---- OUT ---- + + if (ep != 0) { // SPECIAL treatment to EP0, that one cannot be turned on or off + CLEAR_BIT(*epc->UEP_MOD, epc->BUF_MOD); // clear BUFMOD bit + SET_BIT(*epc->UEP_MOD, epc->RX_EN); // set RX enable bit + } + + SET_BIT(*epc->RX_CTRL, USBFS_UEP_R_AUTO_TOG); // turn on automatic sync bit toggling + + // ---- common for all endpoints ---- + + // NAK processing + CLEAR_BIT(*epc->RX_CTRL, USBFS_UEP_R_RES_MASK); + if (cfg->responding_NAK) { + SET_BIT(*epc->RX_CTRL, USBFS_UEP_R_RES_NAK); // send NAK + } else { + usbdrv_arm_OUT_endpoint(ep, cfg->max_packet_size); + } + } else { // ---- IN ---- + + if (ep != 0) { // SPECIAL treatment to EP0, that one cannot be turned on or off + CLEAR_BIT(*epc->UEP_MOD, epc->BUF_MOD); // clear BUFMOD bit + SET_BIT(*epc->UEP_MOD, epc->TX_EN); // set TX enable bit + SET_BIT(*epc->TX_CTRL, USBFS_UEP_T_AUTO_TOG); // turn on automatic sync bit toggling + } + + // ---- common for all endpoints ---- + + // NAK processing + CLEAR_BIT(*epc->TX_CTRL, USBFS_UEP_T_RES_MASK); + // if (cfg->responding_NAK) { + SET_BIT(*epc->TX_CTRL, USBFS_UEP_T_RES_NAK); // send NAK by default + //} + } +} + +// deconfigure USB endpoint +void usbdrv_deconfigure_endpoint(uint8_t ep, uint8_t dir) { + if (ep == 0) { // EP0 cannot be deconfigured + return; + } + + const USB_EP_Ctrl *epc = USB_EPCtrl + ep; // get Endpoint control + + if (dir == USB_OUT) { // ---- OUT ---- + CLEAR_BIT(*epc->UEP_MOD, epc->BUF_MOD); // clear BUFMOD bit + CLEAR_BIT(*epc->UEP_MOD, epc->RX_EN); // clear RX enable bit + } else { // ---- IN ---- + CLEAR_BIT(*epc->UEP_MOD, epc->BUF_MOD); // clear BUFMOD bit + CLEAR_BIT(*epc->UEP_MOD, epc->TX_EN); // clear TX enable bit + } +} + +// --------------------- + +// stall endpoint +void usbdrv_stall_endpoint(uint8_t ep, uint8_t dir, bool stall) { + const USB_EP_Ctrl *epc = USB_EPCtrl + ep; + + if (dir == USB_IN) { + CLEAR_BIT(*epc->RX_CTRL, USBFS_UEP_R_RES_MASK); + if (stall) { + SET_BIT(*epc->RX_CTRL, USBFS_UEP_R_RES_STALL); // stall endpoint + } + } else { + CLEAR_BIT(*epc->RX_CTRL, USBFS_UEP_T_RES_MASK); + if (stall) { + SET_BIT(*epc->TX_CTRL, USBFS_UEP_T_RES_STALL); // stall endpoint + } + } +} + +// ---------------- + +// write data to specific endpoint FIFO +uint32_t usbdrv_arm_IN_endpoint(uint8_t ep, const uint8_t *data, uint16_t len) { + // proceed only if it was not armed before + if (gs.ep_IN[ep].txp) { + return 0; + } + + // get Endpoint control data + const USB_EP_Ctrl *epc = USB_EPCtrl + ep; + + // constrain copy length + uint16_t txLen = MIN(len, USB_EP_TX_BUF_SIZE); + + // copy data to the output buffer + if (txLen > 0) { + uint8_t *txBuf = USB_EP_GET_EP0_BUFFER(); + memcpy(txBuf, data, txLen); + } + + // set transmission length + *epc->TRANSMIT_LENGTH = txLen; + + // append ZLP only, if packet size is MAX_PCKT_SIZE (this way to ZLP is injected in a longer packet whose first part is limited to 64 bytes) + gs.ep_IN[ep].zlp_next = (len == USB_MAX_FS_PCKT_SIZE_NON_ISOCHRONOUS); + + // EP0 must begin responding with DATA1 + // if (ep == 0) { + // SET_BIT(*epc->TX_CTRL, USBFS_UEP_T_TOG); + // } + + // signal that transmission is in progress + gs.ep_IN[ep].txp = true; + + // enable transmission + CLEAR_BIT(*epc->TX_CTRL, USBFS_UEP_T_RES_MASK); + + // return with written size + return txLen; +} + +// arm OUT endpoint +uint32_t usbdrv_arm_OUT_endpoint(uint8_t ep, uint16_t size) { + // proceed only if it was not armed before + if (gs.ep_OUT[ep].txp) { + return 0; + } + + // cap size at max packet size defined for this endpoint + size = MIN(gs.ep_OUT[ep].max_packet_size, size); + + // get Endpoint control data + const USB_EP_Ctrl *epc = USB_EPCtrl + ep; + + // copy data to the output buffer + // uint32_t *rxBuf = (uint32_t *)(((uint32_t)(*epc->BUF_START_ADDR)) + USB_EP_TX_BUF_SIZE); + + // enable transmission + CLEAR_BIT(*epc->RX_CTRL, USBFS_UEP_R_RES_MASK); + + // signal that transmission is in progress + gs.ep_OUT[ep].txp = true; + + // return with armed size + return size; +} + +void usbdrv_autoarm_OUT_endpoint(uint8_t ep) { + gs.ep_OUT[ep].autoarm = true; +} + +// ---------------- + +void usbdrv_enable_endpoint_interrupt(uint8_t ep, uint8_t dir, bool en) { + return; // FIXME +} + +// preload usb endpoint config +void usbdrv_preload_endpoint_config(uint8_t ep, uint8_t dir, const USBDRV_EpConfig *cfg) { + USBMSG("PRELOAD: %u %s\n", ep, dir ? "IN" : "OUT"); + if (dir == USB_OUT) { + gs.ep_OUT[ep] = *cfg; + gs.ep_OUT[ep].is_configured = true; + } else { + gs.ep_IN[ep] = *cfg; + gs.ep_IN[ep].is_configured = true; + } +} + +// clear endpoint config +void usbdrv_clear_endpoint_config() { + memset(&gs.ep_OUT, 0, USB_NUM_OF_ENDPOINTS * sizeof(USBDRV_EpConfig)); + memset(&gs.ep_IN, 0, USB_NUM_OF_ENDPOINTS * sizeof(USBDRV_EpConfig)); +} + +// apply preloaded endpoint configuration +void usbdrv_apply_endpoint_config() { + for (uint8_t i = 0; i < USB_NUM_OF_ENDPOINTS; i++) { + // OUT EPs + if (gs.ep_OUT[i].is_configured) { + usbdrv_configure_endpoint(i, USB_OUT, gs.ep_OUT + i); + } + + // IN EPs + if (gs.ep_IN[i].is_configured) { + usbdrv_configure_endpoint(i, USB_IN, gs.ep_IN + i); + } + } +} + +// fetch endpoint configuration from descriptor dump +void usbdrv_fetch_endpoint_configuration(uint8_t config_index) { + const uint8_t *fullConfDesc = (const uint8_t *)confDescs[config_index]; // point an array to the beginning of the full configuration + const USB_ConfigurationDesc *confDesc = (const USB_ConfigurationDesc *)confDescs[config_index]; // fetch the leading configuration descriptor + + // look up endpoint descriptors + const uint8_t *iter = fullConfDesc; + while (iter < (fullConfDesc + confDesc->wTotalLength)) { + if (iter[1] == UD_Endpoint) { // Endpoint descriptor found + USB_EndpointDesc epDesc; + memcpy(&epDesc, iter, iter[0]); // fetch EP descriptor by copy, since desciptor start address is NOT aligned + USBDRV_EpConfig cfg; // fill-in configuration + cfg.max_packet_size = epDesc.wMaxPacketSize; + cfg.responding_NAK = false; + cfg.type = epDesc.bmAttributes & 0b11; + cfg.service_interval = epDesc.bInterval; + + // fetch endpoint address + uint8_t dir = (epDesc.bEndpointAddress >> 7); + uint8_t n = epDesc.bEndpointAddress & 0x7F; + + // apply configuration + usbdrv_preload_endpoint_config(n, dir, &cfg); + } + + // advance iterator using the bLength field (first byte in a descriptor) + iter += iter[0]; + } + + usbdrv_allocate_buffers(); + + usbdrv_apply_endpoint_config(); +} + +// #define USBDRV_ADDR_TABLE_STR_LEN (255) +// static char usbdrv_addr_table[USBDRV_ADDR_TABLE_STR_LEN + 1]; + +// build buffer structure, allocate buffers (compute addresses) +void usbdrv_allocate_buffers() { + for (uint8_t i = 0; i < USB_NUM_OF_ENDPOINTS; i++) { + *USB_EPCtrl[i].BUF_START_ADDR = (uint32_t)USB_EP_GET_TX_BUFFER(i); + + USBDRV_EpConfig *cfg = &gs.ep_IN[i]; + if (cfg->is_configured) { + cfg->buffer_address = (uint32_t)(*USB_EPCtrl[i].BUF_START_ADDR); // store DMA start address + cfg->zlp_next = false; + cfg->txp = false; // no transfer is in progress + } + } +} + +// --------------------- + +// create an initial setup for EP0 in both directions +void usbdrv_initial_ep0_setup() { + // setup EP0 OUT and IN + USBDRV_EpConfig ep_cfg; + ep_cfg.max_packet_size = 64; + ep_cfg.responding_NAK = false; + usbdrv_preload_endpoint_config(0, USB_OUT, &ep_cfg); + usbdrv_preload_endpoint_config(0, USB_IN, &ep_cfg); + + // build FIFO + usbdrv_allocate_buffers(); + + // configure endpoints + usbdrv_apply_endpoint_config(); +} + +// --------------------- + +void usbdrv_set_address(uint8_t addr) { + gs.address = addr; + gs.address_pending = true; // cannot set address immediately, have to wait for next IN transaction +} + +// --------------------- + +/** + * Function prototype for processing SETUP packets. This function is expected to be + * overridden by the application. + * + * @param data pointer to the SETUP packet + * @param size size of the packet + * @param stage stage of the SETUP transaction + */ +__weak void usbcore_process_setup_pckt(const uint8_t *data, uint16_t size, uint8_t stage) { + // always pass ALIGNED data! + return; +} + +/** + * Function prototype for processing non-SETUP packets. This function is expected to be + * overridden by the application. + * + * @param cbcpd pointer to callback compound + */ +__weak void usbcore_process_nonsetup_event(UsbDrv_CallbackCompound *cbcpd) { + return; +} + +#define USB_INT_STATUS_GET_EP(s) ((s) & USBFS_UIS_ENDP_MASK) ///< Get EP number from interrupt status +#define USB_INT_STATUS_GET_TOKEN(s) ((s) & USBFS_UIS_TOKEN_MASK) ///< Get token of the interrupt (no shifting!) +#define USB_INT_STATUS_IS_NAK(s) (((s) & USBFS_UIS_IS_NAK) != 0) ///< Was it a NAK transmission? + +// TODO: egy input buffer kellene ide, mert különben egy lassú feldolgozás felborít mindent! + +// process USB event +void usbdrv_process_event(uint8_t evt_code, USBDRV_EventData *evt_data) { + if (evt_code == USB_EVT_USB_RESET) { // reset takes precedence over anything else TODO + usbdrv_reset(); + + // invoke Reset Done notification + if (drvIntf.rst_notify != NULL) { + drvIntf.rst_notify(); + } + + USBMSG("RESET\n"); + return; + } else if (evt_code == USB_EVT_TRANSFER_COMPLETION) { // transfer complete + uint8_t status = USBG->INT_ST; // read interrupt status register + uint8_t token = USB_INT_STATUS_GET_TOKEN(status); + uint8_t ep = USB_INT_STATUS_GET_EP(status); + bool nak = USB_INT_STATUS_IS_NAK(status); + uint16_t len = USBG->RX_LEN; + + UsbDrv_CallbackCompound cbcpd; + cbcpd.ep = ep; + + switch (token) { + case USBFS_UIS_TOKEN_OUT: { + if (ep == 0) { + uint8_t *rxBuf = USB_EP_GET_EP0_BUFFER(); + usbcore_process_setup_pckt(rxBuf, len, UST_DATA); + } else { + cbcpd.dir = USB_OUT; + cbcpd.code = USB_CBC_OUT; + cbcpd.data = USB_EP_GET_RX_BUFFER(ep); + cbcpd.size = len; + usbcore_process_nonsetup_event(&cbcpd); + } + + if (ep != 0) { + SET_BIT(*USB_EPCtrl[ep].RX_CTRL, USBFS_UEP_R_RES_NAK); // send NAK + gs.ep_OUT[ep].txp = false; // transfer concluded + } + + if ((ep == 0) || (gs.ep_OUT[ep].autoarm)) { // EP0 must always be armed + usbdrv_arm_OUT_endpoint(ep, gs.ep_OUT[ep].max_packet_size); // arm endpoint + gs.ep_OUT[ep].autoarm = false; // clear autoarm flag + } + + } break; + case USBFS_UIS_TOKEN_IN: { + if (gs.ep_IN[ep].zlp_next) { + usbdrv_arm_IN_endpoint(ep, NULL, 0); // send ZLP + } else { // no ZLP + cbcpd.dir = USB_IN; + cbcpd.code = USB_CBC_IN_DONE; + cbcpd.data = NULL; + cbcpd.size = 0; + + SET_BIT(*USB_EPCtrl[ep].TX_CTRL, USBFS_UEP_T_RES_NAK); // send NAK + *USB_EPCtrl[ep].TRANSMIT_LENGTH = 0; // zero transmit length + + // usbcore_process_nonsetup_event(&cbcpd); + + // change address + if (gs.address_pending) { + USBG->DEV_ADDR = gs.address; + gs.address_pending = false; + } + + gs.ep_IN[ep].txp = false; // transfer concluded + + // toggle EP0 synchronization bit + if (ep == 0) { + bool tog = !GET_BIT(*USB_EPCtrl[0].TX_CTRL, USBFS_UEP_T_TOG); + if (tog) { // DATA1 is the next transfer + SET_BIT(*USB_EPCtrl[0].TX_CTRL, USBFS_UEP_T_TOG); + } else { // DATA0 is the next transfer + CLEAR_BIT(*USB_EPCtrl[0].TX_CTRL, USBFS_UEP_T_TOG); + } + } + + if (nak) { + __NOP(); + } + + // invoke callback if registered + if (cbs[ep] != NULL) { + UsbDrv_IN_cb cb = cbs[ep]; // fetch function pointer + cbs[ep] = NULL; // clear callback + cb(ep); // invoke the callback + } + } + } break; + case USBFS_UIS_TOKEN_SETUP: { + SET_BIT(*USB_EPCtrl[0].TX_CTRL, USBFS_UEP_T_TOG); // secure starting with DATA1 if an IN follows + uint8_t *rxBuf = USB_EP_GET_EP0_BUFFER(); // the receive buffer + usbcore_process_setup_pckt(rxBuf, 8, UST_SETUP); // process the setup packet + } break; + } + } +} + +// --------------------- + +#define PROCESS_EVENT(evt, data) usbdrv_process_event((evt), (data)) + +void USB_IRQ_HANDLER() { + + uint32_t ints = USBG->INT_FG; + + // USB reset + if (ints & USBFS_UIF_BUS_RST) { + SET_BIT(USBG->INT_FG, USBFS_UIF_BUS_RST); // clear interrupt + // PROCESS_EVENT(USB_EVT_USB_RESET, NULL); // process event + } + + // USB Suspend + if (ints & USBFS_UIF_SUSPEND) { + SET_BIT(USBG->INT_FG, USBFS_UIF_SUSPEND); // clear interrupt + USBMSG("SUSPEND\n"); + } + + // FIFO overflow + if (ints & USBFS_UIF_FIFO_OV) { + SET_BIT(USBG->INT_FG, USBFS_UIF_FIFO_OV); // clear interrupt + } + + // Transfer complete + if (ints & USBFS_UIF_TRANSFER) { + PROCESS_EVENT(USB_EVT_TRANSFER_COMPLETION, NULL); // process event + SET_BIT(USBG->INT_FG, USBFS_UIF_TRANSFER); // clear interrupt + } + + return; +} \ No newline at end of file diff --git a/driver/ch32/usb_drv.h b/driver/ch32/usb_drv.h new file mode 100644 index 0000000..0b8a309 --- /dev/null +++ b/driver/ch32/usb_drv.h @@ -0,0 +1,241 @@ +#ifndef CH32_USB_DRV +#define CH32_USB_DRV + +#include "../../usb_common_types.h" + +#include "../../usb_driver_common.h" + +// number of supported endpoints +#define USB_NUM_OF_ENDPOINTS (8) // set it to the maximum that this type of module can support + +// non isochronous transfers +#define USB_MAX_FS_PCKT_SIZE_NON_ISOCHRONOUS (64) + +// isochronous transfers +#define USB_MAX_FS_PCKT_SIZE_ISOCHRONOUS (1023) + +// endpoint configuration structure +typedef struct { + uint16_t max_packet_size; // maximum packet size + bool8_t responding_NAK; // indicates if endpoint responds with NAK + uint8_t type; // endpoint type + uint8_t service_interval; // service interval + + // calculated values + + uint8_t is_configured; // the endpoint is in use + uint16_t buffer_address; // address in the FIFO + bool8_t zlp_next; // indicates, that ZLP should be transmitted at the end of the current transfer (for IN endpoints) + bool8_t autoarm; // automatically arm endpoint (for OUT endpoints) + bool8_t txp; // transfer in progress +} USBDRV_EpConfig; + +typedef enum { + USB_EVT_USB_RESET, // bus reset received + USB_EVT_TRANSFER_COMPLETION, +} USBDRV_EventCode; + +// USB device state +typedef struct { + USBDRV_EpConfig ep_OUT[USB_NUM_OF_ENDPOINTS]; // OUT endpoint configs + USBDRV_EpConfig ep_IN[USB_NUM_OF_ENDPOINTS]; // IN endpoint configs + uint8_t *buf; // pointer to the receive buffer (this way declaration can be separated) + uint8_t address; // device address + bool address_pending; // address change is pending +} USBDRV_GlobalState; + +// event data +typedef union { + struct { + uint8_t pckt_status; // read packet status + uint8_t data_pid; // data PID + uint8_t byte_count; // byte count + uint8_t ep_num; // read endpoint number + } rx; // receive event data +} USBDRV_EventData; + +// event compound for queueing +typedef struct { + uint32_t code; // event code + USBDRV_EventData data; // accompaining event data +} USBDRV_EventCompound; + +// ------------ + +/** + * Fully initialize USB driver. + */ +void usbdrv_init(); + +/** + * Reset USB driver. + */ +void usbdrv_reset(); + +/** + * Init driver's global state. + */ +void usbdrv_init_global_state(); + +/** + * Initialize driver interface. + */ +void usbdrv_init_intf(); + +/** + * USB peripheral initialization. + * + * @param reset only perform an after-reset reinitialization + */ +void usbdrv_periph_init(bool reset); + +/** + * Clear all TX and RX FIFOs. + */ +void usbdrv_clear_all_fifos(); + +/** + * Power down or up the USB peripheral. Also control built-in transciever. + * + * @param en power on/off + */ +void usbdrv_power_and_connect(bool en); + +/** + * Configure USB Endpoint. + * + * @param ep index of the endpoint + * @param dir direction of the endpoint to be configured + * @param cfg pointer to the configuration details + */ +void usbdrv_configure_endpoint(uint8_t ep, uint8_t dir, const USBDRV_EpConfig *cfg); + +/** + * Deconfigure a specific USB Endpoint. + * + * @param ep index of the endpoint + * @param dir direction of the endpoint to be deconfigured + */ +void usbdrv_deconfigure_endpoint(uint8_t ep, uint8_t dir); + +/** + * Stall a particular USB Endpoint. + * + * @param ep index of the endpoint to be stalled + * @param dir direction of the endpoint + * @param stall enable/disable stalling + */ +void usbdrv_stall_endpoint(uint8_t ep, uint8_t dir, bool stall); + +/** + * Setup and prepare Endpoint to transfer data towards the host. + * May be called multiple times in a single transmission, since + * large messages do not have to fit into a single buffer. + * + * @param ep index of the Endpoint + * @param data pointer to the data + * @param len length of the data to be read + * @return number of bytes read from the data buffer + */ +uint32_t usbdrv_arm_IN_endpoint(uint8_t ep, const uint8_t *data, uint16_t len); + +/** + * Prepare Endpoint to expect data reception from the host. + * + * @param ep index of the Endpoint + * @param size expected reception length + * @return message length that the endpoint can support during next reception + */ +uint32_t usbdrv_arm_OUT_endpoint(uint8_t ep, uint16_t size); + +/** + * Enable or disable OUT Endpoint auto arming. + * Right after a transaction completes the USB core + * automatically prepares the endpoint for the next + * reception if this feature is enabled. + * + * @param ep index of the Endpoint + */ +void usbdrv_autoarm_OUT_endpoint(uint8_t ep); + +/** + * Mask or unmask Endpoint interrupt generation. + * + * @param ep index of the Endpoint + * @param dir direction of the Endpoint + * @param en enable/disable interrupt + */ +void usbdrv_enable_endpoint_interrupt(uint8_t ep, uint8_t dir, bool en); + +/** + * Preload Endpoint config. Do not write configuration + * to the hardware right away, just buffer them in. + * + * @param ep index of the Endpoint + * @param dir direction of the Endpint + * @param cfg pointer to Endpoint configuration + */ +void usbdrv_preload_endpoint_config(uint8_t ep, uint8_t dir, const USBDRV_EpConfig *cfg); + +/** + * Clear ALL Endpoints' preload configurations. + * Does NOT clear configuration in the hardware! + */ +void usbdrv_clear_endpoint_config(); + +/** + * Apply preloaded Endpoint configuration, write + * Endpoint config to the hardware. + */ +void usbdrv_apply_endpoint_config(); + +/** + * Select a specific configuration from the descriptor + * dump, preload Endpoint settings and finally apply them. + * + * @param config_index configuration index + */ +void usbdrv_fetch_endpoint_configuration(uint8_t config_index); + +/** + * Construct FIFO considering configured Endpoints. + */ +void usbdrv_allocate_buffers(); + +/** + * Apply an initial EP0 setup. + */ +void usbdrv_initial_ep0_setup(); + +/** + * Set USB device address. + * + * @param addr device address + */ +void usbdrv_set_address(uint8_t addr); + +/** + * Get driver interface. + * + * @return pointer to the driver interface + */ +UsbDrv_DrvIntf *usbdrv_get_intf(); + +/** + * Register callback for IN transaction complete. + * + * @param ep index of the Endpoint + * @param cb pointer to the callback function + */ +void usbdrv_register_IN_complete_cb(uint8_t ep, UsbDrv_IN_cb cb); + +/** + * Get Endpoint interrupt flag. + * + * @param ep index of the Endpoint + * @param dir direction of the Endpoint + * @return flag was set + */ +bool usbdrv_get_endpoint_interrupt_flag(uint8_t ep, uint8_t dir); + +#endif /* CH32_USB_DRV */