| /* |
| * Copyright (c) 2018-2019, NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <soc.h> |
| #include <string.h> |
| #include <usb/usb_dc.h> |
| #include <soc.h> |
| #include <device.h> |
| #include "usb_dc_mcux.h" |
| #include "usb_device_ehci.h" |
| |
| #ifdef CONFIG_HAS_MCUX_CACHE |
| #include <fsl_cache.h> |
| #endif |
| |
| #define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(usb_dc_mcux_ehci); |
| |
| #define CONTROLLER_ID kUSB_ControllerEhci0 |
| |
| static void usb_isr_handler(void); |
| extern void USB_DeviceEhciIsrFunction(void *deviceHandle); |
| |
| /* the setup transfer state */ |
| #define SETUP_DATA_STAGE_DONE (0) |
| #define SETUP_DATA_STAGE_IN (1) |
| #define SETUP_DATA_STAGE_OUT (2) |
| /* Then endpoint number/index calculation */ |
| #define EP_ADDR2IDX(ep) ((ep) & ~USB_EP_DIR_MASK) |
| #define EP_ADDR2DIR(ep) ((ep) & USB_EP_DIR_MASK) |
| #define EP_ABS_IDX(ep) (((ep) & ~USB_EP_DIR_MASK) * 2 + (((ep) & USB_EP_DIR_MASK) >> 7)) |
| #define NUM_OF_EP_MAX DT_USBD_MCUX_EHCI_NUM_BIDIR_EP |
| /* The minimum value is 1 */ |
| #define EP_BUF_NUMOF_BLOCKS ((NUM_OF_EP_MAX + 3) / 4) |
| /* The max MPS is 1023 for FS, 1024 for HS. */ |
| #if defined(CONFIG_NOCACHE_MEMORY) |
| #define EP_BUF_NONCACHED |
| __nocache K_MEM_POOL_DEFINE(ep_buf_pool, 16, 1024, EP_BUF_NUMOF_BLOCKS, 4); |
| #else |
| K_MEM_POOL_DEFINE(ep_buf_pool, 16, 1024, EP_BUF_NUMOF_BLOCKS, 4); |
| #endif |
| |
| static usb_ep_ctrl_data_t s_ep_ctrl[NUM_OF_EP_MAX]; |
| static usb_device_struct_t s_Device; |
| |
| #if ((defined(USB_DEVICE_CONFIG_EHCI)) && (USB_DEVICE_CONFIG_EHCI > 0U)) |
| /* EHCI device driver interface */ |
| static const usb_device_controller_interface_struct_t s_UsbDeviceEhciInterface = { |
| USB_DeviceEhciInit, USB_DeviceEhciDeinit, USB_DeviceEhciSend, |
| USB_DeviceEhciRecv, USB_DeviceEhciCancel, USB_DeviceEhciControl |
| }; |
| #endif |
| |
| int usb_dc_reset(void) |
| { |
| if (s_Device.controllerHandle != NULL) { |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlStop, NULL); |
| s_Device.interface->deviceDeinit(s_Device.controllerHandle); |
| s_Device.controllerHandle = NULL; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_attach(void) |
| { |
| s_Device.eps = &s_ep_ctrl[0]; |
| if (s_Device.attached) { |
| LOG_WRN("already attached"); |
| return 0; |
| } |
| s_Device.interface = (const usb_device_controller_interface_struct_t *)(&s_UsbDeviceEhciInterface); |
| if (kStatus_USB_Success != s_Device.interface->deviceInit(CONTROLLER_ID, &s_Device, &s_Device.controllerHandle)) { |
| return -EINVAL; |
| } |
| |
| /* Connect and enable USB interrupt */ |
| IRQ_CONNECT(DT_USBD_MCUX_EHCI_IRQ, DT_USBD_MCUX_EHCI_IRQ_PRI, |
| usb_isr_handler, 0, 0); |
| irq_enable(DT_USBD_MCUX_EHCI_IRQ); |
| s_Device.attached = 1; |
| LOG_DBG("attached"); |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlRun, NULL); |
| return 0; |
| } |
| |
| int usb_dc_detach(void) |
| { |
| LOG_DBG("detached."); |
| if (s_Device.controllerHandle != NULL) { |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlStop, NULL); |
| s_Device.interface->deviceDeinit(s_Device.controllerHandle); |
| s_Device.controllerHandle = NULL; |
| } |
| s_Device.attached = 0; |
| return 0; |
| } |
| |
| int usb_dc_set_address(const u8_t addr) |
| { |
| LOG_DBG(""); |
| s_Device.address = addr; |
| return 0; |
| } |
| |
| int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data *const cfg) |
| { |
| u8_t ep_idx = EP_ADDR2IDX(cfg->ep_addr); |
| |
| if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { |
| LOG_ERR("invalid endpoint configuration"); |
| return -1; |
| } |
| |
| if (ep_idx >= NUM_OF_EP_MAX) { |
| LOG_ERR("endpoint index/address out of range"); |
| return -1; |
| } |
| |
| if (ep_idx & BIT(0)) { |
| if (EP_ADDR2DIR(cfg->ep_addr) != USB_EP_DIR_IN) { |
| LOG_INF("pre-selected as IN endpoint"); |
| return -1; |
| } |
| } else { |
| if (EP_ADDR2DIR(cfg->ep_addr) != USB_EP_DIR_OUT) { |
| LOG_INF("pre-selected as OUT endpoint"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data *const cfg) |
| { |
| u8_t ep_abs_idx = EP_ABS_IDX(cfg->ep_addr); |
| usb_device_endpoint_init_struct_t epInit; |
| struct k_mem_block *block; |
| struct usb_ep_ctrl_data *eps = &s_Device.eps[ep_abs_idx]; |
| |
| epInit.zlt = 0U; |
| epInit.endpointAddress = cfg->ep_addr; |
| epInit.maxPacketSize = cfg->ep_mps; |
| epInit.transferType = cfg->ep_type; |
| s_Device.eps[ep_abs_idx].ep_type = cfg->ep_type; |
| |
| if (ep_abs_idx >= NUM_OF_EP_MAX) { |
| LOG_ERR("Wrong endpoint index/address"); |
| return -EINVAL; |
| } |
| |
| block = &(eps->block); |
| if (block->data) { |
| k_mem_pool_free(block); |
| block->data = NULL; |
| } |
| |
| if (k_mem_pool_alloc(&ep_buf_pool, block, cfg->ep_mps, 10) == 0) { |
| memset(block->data, 0, cfg->ep_mps); |
| } else { |
| LOG_ERR("Memory allocation time-out"); |
| return -ENOMEM; |
| } |
| |
| s_Device.eps[ep_abs_idx].ep_mps = cfg->ep_mps; |
| if (s_Device.eps[ep_abs_idx].ep_enabled) { |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlEndpointDeinit, (void *)(&cfg->ep_addr)); |
| } |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlEndpointInit, &epInit); |
| |
| /* if it is controlendpoint, controller will prime setup |
| * here set the occupied flag. |
| */ |
| if ((EP_ADDR2IDX(cfg->ep_addr) == USB_CONTROL_ENDPOINT) && (EP_ADDR2DIR(cfg->ep_addr) == USB_EP_DIR_OUT)) { |
| s_Device.eps[ep_abs_idx].ep_occupied = true; |
| } |
| s_Device.eps[ep_abs_idx].ep_enabled = true; |
| return 0; |
| } |
| |
| int usb_dc_ep_set_stall(const u8_t ep) |
| { |
| u8_t endpoint = ep; |
| |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlEndpointStall, &endpoint); |
| return 0; |
| } |
| |
| int usb_dc_ep_clear_stall(const u8_t ep) |
| { |
| u8_t endpoint = ep; |
| |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlEndpointUnstall, &endpoint); |
| return 0; |
| } |
| |
| int usb_dc_ep_is_stalled(const u8_t ep, u8_t *const stalled) |
| { |
| if (!stalled) { |
| return -EINVAL; |
| } |
| *stalled = 0; |
| /* Get the endpoint status */ |
| usb_device_endpoint_status_struct_t endpointStatus; |
| |
| endpointStatus.endpointAddress = ep; |
| endpointStatus.endpointStatus = kUSB_DeviceEndpointStateIdle; |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlGetEndpointStatus, &endpointStatus); |
| *stalled = endpointStatus.endpointStatus; |
| return 0; |
| } |
| |
| int usb_dc_ep_halt(const u8_t ep) |
| { |
| return usb_dc_ep_set_stall(ep); |
| } |
| |
| int usb_dc_ep_enable(const u8_t ep) |
| { |
| u8_t ep_abs_idx = EP_ABS_IDX(ep); |
| |
| /* endpoint0 OUT is primed by controller driver when configure this |
| * endpoint. |
| */ |
| if (!ep_abs_idx) { |
| return 0; |
| } |
| if (s_Device.eps[ep_abs_idx].ep_occupied) { |
| LOG_WRN("endpoint 0x%x already enabled", ep); |
| return -EBUSY; |
| } |
| |
| if ((EP_ADDR2IDX(ep) != USB_CONTROL_ENDPOINT) && (EP_ADDR2DIR(ep) == USB_EP_DIR_OUT)) { |
| s_Device.interface->deviceRecv(s_Device.controllerHandle, |
| ep, |
| (u8_t *)s_Device.eps[ep_abs_idx].block.data, |
| (uint32_t)s_Device.eps[ep_abs_idx].ep_mps); |
| s_Device.eps[ep_abs_idx].ep_occupied = true; |
| } else { |
| /* control enpoint just be enabled before enumeration, |
| * when running here, setup has been primed. |
| */ |
| s_Device.eps[ep_abs_idx].ep_occupied = true; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_disable(const u8_t ep) |
| { |
| u8_t ep_abs_idx = EP_ABS_IDX(ep); |
| |
| s_Device.interface->deviceCancel(s_Device.controllerHandle, ep); |
| s_Device.eps[ep_abs_idx].ep_enabled = false; |
| return 0; |
| } |
| |
| int usb_dc_ep_flush(const u8_t ep) |
| { |
| u8_t ep_idx = EP_ADDR2IDX(ep); |
| |
| if (ep_idx >= NUM_OF_EP_MAX) { |
| LOG_ERR("Wrong endpoint index/address"); |
| return -EINVAL; |
| } |
| |
| LOG_DBG("ep %x, idx %d", ep_idx, ep); |
| |
| return 0; |
| } |
| |
| /* send data to the host */ |
| int usb_dc_ep_write(const u8_t ep, const u8_t *const data, |
| const u32_t data_len, u32_t *const ret_bytes) |
| { |
| u8_t ep_abs_idx = EP_ABS_IDX(ep); |
| u8_t *buffer = (u8_t *)s_Device.eps[ep_abs_idx].block.data; |
| u32_t len_to_send; |
| |
| if (data_len > s_Device.eps[ep_abs_idx].ep_mps) { |
| len_to_send = s_Device.eps[ep_abs_idx].ep_mps; |
| } else { |
| len_to_send = data_len; |
| } |
| for (u32_t n = 0; n < len_to_send; n++) { |
| buffer[n] = data[n]; |
| } |
| |
| #if defined(CONFIG_HAS_MCUX_CACHE) && !defined(EP_BUF_NONCACHED) |
| DCACHE_CleanByRange((uint32_t)buffer, len_to_send); |
| #endif |
| s_Device.interface->deviceSend(s_Device.controllerHandle, |
| ep, |
| buffer, |
| len_to_send); |
| if (ret_bytes) { |
| *ret_bytes = len_to_send; |
| } |
| return 0; |
| } |
| |
| int usb_dc_ep_read_wait(u8_t ep, u8_t *data, u32_t max_data_len, |
| u32_t *read_bytes) |
| { |
| u8_t ep_idx = EP_ADDR2IDX(ep); |
| u8_t ep_abs_idx = EP_ABS_IDX(ep); |
| u32_t data_len; |
| u8_t *bufp = NULL; |
| |
| while (s_Device.eps[ep_abs_idx].ep_occupied) { |
| LOG_ERR("Endpoint is occupied by the controller"); |
| return -EBUSY; |
| } |
| if ((ep_idx >= NUM_OF_EP_MAX) || (EP_ADDR2DIR(ep) != USB_EP_DIR_OUT)) { |
| LOG_ERR("Wrong endpoint index/address/direction"); |
| return -EINVAL; |
| } |
| /* Allow to read 0 bytes */ |
| if (!data && max_data_len) { |
| LOG_ERR("Wrong arguments"); |
| return -EINVAL; |
| } |
| /* it is control setup, we should use message.buffer, |
| * this buffer is from internal setup array. |
| */ |
| bufp = s_Device.eps[ep_abs_idx].transfer_message.buffer; |
| data_len = s_Device.eps[ep_abs_idx].transfer_message.length; |
| if (data_len == USB_UNINITIALIZED_VAL_32) { |
| if (read_bytes) { |
| *read_bytes = 0; |
| } |
| return -EINVAL; |
| } |
| if (!data && !max_data_len) { |
| /* When both buffer and max data to read are zero return the |
| * available data in buffer. |
| */ |
| if (read_bytes) { |
| *read_bytes = data_len; |
| } |
| return 0; |
| } |
| if (data_len > max_data_len) { |
| LOG_WRN("Not enough room to copy all the data!"); |
| data_len = max_data_len; |
| } |
| if (data != NULL) { |
| for (u32_t i = 0; i < data_len; i++) { |
| data[i] = bufp[i]; |
| } |
| } |
| if (read_bytes) { |
| *read_bytes = data_len; |
| } |
| |
| if (EP_ADDR2IDX(ep) == USB_ENDPOINT_CONTROL) { |
| u8_t isSetup = s_Device.eps[0].transfer_message.isSetup; |
| u8_t *buffer = s_Device.eps[0].transfer_message.buffer; |
| |
| if (isSetup) { |
| if (((usb_setup_struct_t *)buffer)->wLength == 0) { |
| s_Device.setupDataStage = SETUP_DATA_STAGE_DONE; |
| } else if (((usb_setup_struct_t *)buffer)->bmRequestType & USB_REQUEST_TYPE_DIR_MASK) { |
| s_Device.setupDataStage = SETUP_DATA_STAGE_IN; |
| } else { |
| s_Device.setupDataStage = SETUP_DATA_STAGE_OUT; |
| } |
| } else { |
| if (s_Device.setupDataStage != SETUP_DATA_STAGE_DONE) { |
| if ((data_len >= max_data_len) || (data_len < s_Device.eps[0].ep_mps)) { |
| s_Device.setupDataStage = SETUP_DATA_STAGE_DONE; |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| int usb_dc_ep_read_continue(u8_t ep) |
| { |
| /* select the index of the next endpoint buffer */ |
| u8_t ep_abs_idx = EP_ABS_IDX(ep); |
| |
| if (s_Device.eps[ep_abs_idx].ep_occupied) { |
| LOG_WRN("endpoint 0x%x already occupied", ep); |
| return -EBUSY; |
| } |
| |
| if (EP_ADDR2IDX(ep) == USB_ENDPOINT_CONTROL) { |
| if (s_Device.setupDataStage == SETUP_DATA_STAGE_DONE) { |
| return 0; |
| } |
| if (s_Device.setupDataStage == SETUP_DATA_STAGE_IN) { |
| s_Device.setupDataStage = SETUP_DATA_STAGE_DONE; |
| } |
| } |
| s_Device.interface->deviceRecv(s_Device.controllerHandle, |
| ep, |
| (u8_t *)s_Device.eps[ep_abs_idx].block.data, |
| s_Device.eps[ep_abs_idx].ep_mps); |
| s_Device.eps[ep_abs_idx].ep_occupied = true; |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_read(const u8_t ep, u8_t *const data, |
| const u32_t max_data_len, u32_t *const read_bytes) |
| { |
| int retval = usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes); |
| |
| if (retval) { |
| return retval; |
| } |
| if (!data && !max_data_len) { |
| /* When both buffer and max data to read are zero the above |
| * call would fetch the data len and we simply return. |
| */ |
| return 0; |
| } |
| |
| if (usb_dc_ep_read_continue(ep) != 0) { |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_set_callback(const u8_t ep, const usb_dc_ep_callback cb) |
| { |
| u8_t ep_abs_idx = EP_ABS_IDX(ep); |
| |
| if (!s_Device.attached) { |
| return -EINVAL; |
| } |
| s_Device.eps[ep_abs_idx].callback = cb; |
| |
| return 0; |
| } |
| |
| void usb_dc_set_status_callback(const usb_dc_status_callback cb) |
| { |
| s_Device.status_callback = cb; |
| } |
| |
| int usb_dc_ep_mps(const u8_t ep) |
| { |
| u8_t ep_abs_idx = EP_ABS_IDX(ep); |
| |
| return s_Device.eps[ep_abs_idx].ep_mps; |
| } |
| |
| static void control_endpoint_enable(void) |
| { |
| usb_device_endpoint_init_struct_t epInit; |
| u8_t ep_abs_idx = 0; |
| |
| epInit.zlt = 0U; |
| epInit.transferType = USB_ENDPOINT_CONTROL; |
| epInit.maxPacketSize = EP0_MAX_PACKET_SIZE; |
| |
| epInit.endpointAddress = EP0_OUT; |
| ep_abs_idx = EP_ABS_IDX(epInit.endpointAddress); |
| s_Device.eps[ep_abs_idx].ep_mps = EP0_MAX_PACKET_SIZE; |
| |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlEndpointInit, &epInit); |
| s_Device.eps[ep_abs_idx].ep_occupied = false; |
| s_Device.eps[ep_abs_idx].ep_enabled = true; |
| |
| epInit.endpointAddress = EP0_IN; |
| ep_abs_idx = EP_ABS_IDX(epInit.endpointAddress); |
| s_Device.eps[ep_abs_idx].ep_mps = EP0_MAX_PACKET_SIZE; |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlEndpointInit, &epInit); |
| s_Device.eps[ep_abs_idx].ep_occupied = false; |
| s_Device.eps[ep_abs_idx].ep_enabled = true; |
| } |
| |
| /* Notify the up layer the KHCI status changed. */ |
| void USB_DeviceNotificationTrigger(void *handle, void *msg) |
| { |
| usb_device_callback_message_struct_t *message = (usb_device_callback_message_struct_t *)msg; |
| |
| switch (message->code) { |
| case kUSB_DeviceNotifyBusReset: |
| s_Device.address = 0; |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlSetDefaultStatus, NULL); |
| for (int i = 0; i < NUM_OF_EP_MAX; i++) { |
| s_Device.eps[i].ep_occupied = false; |
| s_Device.eps[i].ep_enabled = false; |
| } |
| control_endpoint_enable(); |
| s_Device.status_callback(USB_DC_RESET, NULL); |
| break; |
| case kUSB_DeviceNotifyError: |
| s_Device.status_callback(USB_DC_ERROR, NULL); |
| break; |
| case kUSB_DeviceNotifySuspend: |
| s_Device.status_callback(USB_DC_SUSPEND, NULL); |
| break; |
| case kUSB_DeviceNotifyResume: |
| s_Device.status_callback(USB_DC_RESUME, NULL); |
| break; |
| default: |
| { |
| u8_t ep_packet_type = 0; |
| u8_t ep_abs_idx = EP_ABS_IDX(message->code); |
| s_Device.eps[ep_abs_idx].transfer_message.length = message->length; |
| s_Device.eps[ep_abs_idx].transfer_message.isSetup = message->isSetup; |
| s_Device.eps[ep_abs_idx].transfer_message.code = message->code; |
| s_Device.eps[ep_abs_idx].transfer_message.buffer = message->buffer; |
| s_Device.eps[ep_abs_idx].ep_occupied = false; |
| if (message->isSetup) { |
| ep_packet_type = USB_DC_EP_SETUP; |
| } else { |
| /* IN TOKEN */ |
| if ((message->code & USB_REQUEST_TYPE_DIR_MASK) == USB_REQUEST_TYPE_DIR_IN) { |
| /* control endpoint 0 and status stage for setAddr transfer */ |
| if ((s_Device.address != 0) && (ep_abs_idx == 1)) { |
| /* SET ADDRESS in the status stage in the IN transfer*/ |
| s_Device.interface->deviceControl(s_Device.controllerHandle, kUSB_DeviceControlSetDeviceAddress, &s_Device.address); |
| s_Device.address = 0; |
| } |
| ep_packet_type = USB_DC_EP_DATA_IN; |
| } |
| /* OUT TOKEN */ |
| else { |
| ep_packet_type = USB_DC_EP_DATA_OUT; |
| } |
| } |
| if (s_Device.eps[ep_abs_idx].callback) { |
| #if defined(CONFIG_HAS_MCUX_CACHE) && !defined(EP_BUF_NONCACHED) |
| if (message->length) { |
| DCACHE_InvalidateByRange((uint32_t)message->buffer, |
| message->length); |
| } |
| #endif |
| s_Device.eps[ep_abs_idx].callback(message->code, |
| ep_packet_type); |
| } |
| } |
| } |
| } |
| |
| static void usb_isr_handler(void) |
| { |
| USB_DeviceEhciIsrFunction(&s_Device); |
| } |