You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
358 lines
9.3 KiB
358 lines
9.3 KiB
/* |
|
* Copyright 2024 NXP |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <soc.h> |
|
#include <string.h> |
|
#include <stdio.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/sys/byteorder.h> |
|
#include <zephyr/init.h> |
|
#include <zephyr/sys/byteorder.h> |
|
#include <zephyr/drivers/usb/uhc.h> |
|
|
|
#include "uhc_common.h" |
|
#include "usb.h" |
|
#include "usb_host_config.h" |
|
#include "usb_host_mcux_drv_port.h" |
|
#include "uhc_mcux_common.h" |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(uhc_mcux, CONFIG_UHC_DRIVER_LOG_LEVEL); |
|
|
|
#define PRV_DATA_HANDLE(_handle) CONTAINER_OF(_handle, struct uhc_mcux_data, mcux_host) |
|
|
|
int uhc_mcux_lock(const struct device *dev) |
|
{ |
|
struct uhc_data *data = dev->data; |
|
|
|
return k_mutex_lock(&data->mutex, K_FOREVER); |
|
} |
|
|
|
int uhc_mcux_unlock(const struct device *dev) |
|
{ |
|
struct uhc_data *data = dev->data; |
|
|
|
return k_mutex_unlock(&data->mutex); |
|
} |
|
|
|
int uhc_mcux_control(const struct device *dev, uint32_t control, void *param) |
|
{ |
|
struct uhc_mcux_data *priv = uhc_get_private(dev); |
|
usb_status_t status; |
|
|
|
status = priv->mcux_if->controllerIoctl(priv->mcux_host.controllerHandle, control, param); |
|
if (status != kStatus_USB_Success) { |
|
return -EIO; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int uhc_mcux_bus_control(const struct device *dev, usb_host_bus_control_t type) |
|
{ |
|
return uhc_mcux_control(dev, kUSB_HostBusControl, &type); |
|
} |
|
|
|
int uhc_mcux_enable(const struct device *dev) |
|
{ |
|
const struct uhc_mcux_config *config = dev->config; |
|
|
|
config->irq_enable_func(dev); |
|
|
|
return 0; |
|
} |
|
|
|
int uhc_mcux_disable(const struct device *dev) |
|
{ |
|
const struct uhc_mcux_config *config = dev->config; |
|
|
|
config->irq_disable_func(dev); |
|
|
|
return 0; |
|
} |
|
|
|
int uhc_mcux_shutdown(const struct device *dev) |
|
{ |
|
const struct uhc_mcux_config *config = dev->config; |
|
struct uhc_mcux_data *priv = uhc_get_private(dev); |
|
|
|
config->irq_disable_func(dev); |
|
k_thread_abort(&priv->drv_stack_data); |
|
priv->mcux_if->controllerDestory(priv->mcux_host.controllerHandle); |
|
|
|
return 0; |
|
} |
|
|
|
/* Signal bus reset, 50ms SE0 signal */ |
|
int uhc_mcux_bus_reset(const struct device *dev) |
|
{ |
|
return uhc_mcux_bus_control(dev, kUSB_HostBusReset); |
|
} |
|
|
|
/* Enable SOF generator */ |
|
int uhc_mcux_sof_enable(const struct device *dev) |
|
{ |
|
/* MCUX doesn't need it. */ |
|
return 0; |
|
} |
|
|
|
/* Disable SOF generator and suspend bus */ |
|
int uhc_mcux_bus_suspend(const struct device *dev) |
|
{ |
|
return uhc_mcux_bus_control(dev, kUSB_HostBusSuspend); |
|
} |
|
|
|
/* Signal bus resume event, 20ms K-state + low-speed EOP */ |
|
int uhc_mcux_bus_resume(const struct device *dev) |
|
{ |
|
return uhc_mcux_bus_control(dev, kUSB_HostBusResume); |
|
} |
|
|
|
int uhc_mcux_dequeue(const struct device *dev, struct uhc_transfer *const xfer) |
|
{ |
|
usb_host_pipe_t *mcux_ep; |
|
usb_host_cancel_param_t cancel_param; |
|
|
|
mcux_ep = uhc_mcux_init_hal_ep(dev, xfer); |
|
if (mcux_ep == NULL) { |
|
return -ENOMEM; |
|
} |
|
|
|
cancel_param.pipeHandle = mcux_ep; |
|
cancel_param.transfer = NULL; /* cancel all the transfers on the pipe */ |
|
return uhc_mcux_control(dev, kUSB_HostCancelTransfer, &cancel_param); |
|
} |
|
|
|
/* Controller driver calls this function when device attach. */ |
|
usb_status_t USB_HostAttachDevice(usb_host_handle hostHandle, uint8_t speed, uint8_t hubNumber, |
|
uint8_t portNumber, uint8_t level, |
|
usb_device_handle *deviceHandle) |
|
{ |
|
enum uhc_event_type type; |
|
struct uhc_mcux_data *priv; |
|
|
|
if (speed == USB_SPEED_HIGH) { |
|
type = UHC_EVT_DEV_CONNECTED_HS; |
|
} else if (speed == USB_SPEED_FULL) { |
|
type = UHC_EVT_DEV_CONNECTED_FS; |
|
} else { |
|
type = UHC_EVT_DEV_CONNECTED_LS; |
|
} |
|
|
|
priv = (struct uhc_mcux_data *)(PRV_DATA_HANDLE(hostHandle)); |
|
uhc_submit_event(priv->dev, type, 0); |
|
|
|
return kStatus_USB_Success; |
|
} |
|
|
|
/* Controller driver calls this function when device detaches. */ |
|
usb_status_t USB_HostDetachDevice(usb_host_handle hostHandle, uint8_t hubNumber, uint8_t portNumber) |
|
{ |
|
struct uhc_mcux_data *priv; |
|
|
|
priv = (struct uhc_mcux_data *)(PRV_DATA_HANDLE(hostHandle)); |
|
uhc_submit_event(priv->dev, UHC_EVT_DEV_REMOVED, 0); |
|
uhc_mcux_bus_control(priv->dev, kUSB_HostBusEnableAttach); |
|
|
|
return kStatus_USB_Success; |
|
} |
|
|
|
/* MCUX Controller driver calls this function to get the device information. */ |
|
usb_status_t USB_HostHelperGetPeripheralInformation(usb_device_handle deviceHandle, |
|
uint32_t infoCode, uint32_t *infoValue) |
|
{ |
|
/* The deviceHandle is struct usb_device pointer */ |
|
struct usb_device *udev = (struct usb_device *)deviceHandle; |
|
usb_host_dev_info_t info_code; |
|
|
|
if ((deviceHandle == NULL) || (infoValue == NULL)) { |
|
return kStatus_USB_InvalidParameter; |
|
} |
|
info_code = (usb_host_dev_info_t)infoCode; |
|
switch (info_code) { |
|
case kUSB_HostGetDeviceAddress: |
|
*infoValue = udev->addr; |
|
break; |
|
|
|
case kUSB_HostGetDeviceHubNumber: |
|
case kUSB_HostGetDevicePortNumber: |
|
case kUSB_HostGetDeviceHSHubNumber: |
|
case kUSB_HostGetDeviceHSHubPort: |
|
case kUSB_HostGetHubThinkTime: |
|
*infoValue = 0; |
|
break; |
|
case kUSB_HostGetDeviceLevel: |
|
*infoValue = 1; |
|
break; |
|
|
|
case kUSB_HostGetDeviceSpeed: |
|
switch (udev->speed) { |
|
case USB_SPEED_SPEED_LS: |
|
*infoValue = USB_SPEED_LOW; |
|
break; |
|
case USB_SPEED_SPEED_FS: |
|
*infoValue = USB_SPEED_FULL; |
|
break; |
|
case USB_SPEED_SPEED_HS: |
|
*infoValue = USB_SPEED_HIGH; |
|
break; |
|
case USB_SPEED_UNKNOWN: |
|
case USB_SPEED_SPEED_SS: |
|
default: |
|
*infoValue = USB_SPEED_HIGH; |
|
/* TODO: report error */ |
|
break; |
|
} |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
return kStatus_USB_Success; |
|
} |
|
|
|
static usb_host_pipe_handle uhc_mcux_check_hal_ep(const struct device *dev, |
|
struct uhc_transfer *const xfer) |
|
{ |
|
struct uhc_mcux_data *priv = uhc_get_private(dev); |
|
usb_host_pipe_t *mcux_ep = NULL; |
|
uint8_t i; |
|
usb_status_t status; |
|
|
|
/* if already initialized */ |
|
for (i = 0; i < USB_HOST_CONFIG_MAX_PIPES; i++) { |
|
uint8_t direction; |
|
|
|
if (USB_EP_GET_IDX(xfer->ep) == 0) { |
|
direction = 0; |
|
} else { |
|
direction = USB_EP_GET_DIR(xfer->ep) ? USB_IN : USB_OUT; |
|
} |
|
|
|
if (priv->mcux_eps[i] != NULL && |
|
priv->mcux_eps[i]->endpointAddress == USB_EP_GET_IDX(xfer->ep) && |
|
priv->mcux_eps[i]->direction == direction && |
|
priv->mcux_eps[i]->deviceHandle == xfer->udev) { |
|
mcux_ep = priv->mcux_eps[i]; |
|
break; |
|
} |
|
} |
|
|
|
/* TODO: need to check endpoint type too */ |
|
if (mcux_ep != NULL && |
|
(mcux_ep->maxPacketSize != xfer->mps || |
|
mcux_ep->interval != xfer->interval)) { |
|
/* re-initialize the ep */ |
|
status = priv->mcux_if->controllerClosePipe(priv->mcux_host.controllerHandle, |
|
mcux_ep); |
|
if (status != kStatus_USB_Success) { |
|
return NULL; |
|
} |
|
|
|
uhc_mcux_lock(dev); |
|
priv->mcux_eps[i] = NULL; |
|
uhc_mcux_unlock(dev); |
|
mcux_ep = NULL; |
|
} |
|
|
|
return mcux_ep; |
|
} |
|
|
|
usb_host_pipe_t *uhc_mcux_init_hal_ep(const struct device *dev, struct uhc_transfer *const xfer) |
|
{ |
|
usb_status_t status; |
|
usb_host_pipe_t *mcux_ep; |
|
struct uhc_mcux_data *priv = uhc_get_private(dev); |
|
usb_host_pipe_init_t pipe_init; |
|
uint8_t i; |
|
|
|
/* if already initialized */ |
|
mcux_ep = uhc_mcux_check_hal_ep(dev, xfer); |
|
if (mcux_ep != NULL) { |
|
return mcux_ep; |
|
} |
|
|
|
/* USB_HostHelperGetPeripheralInformation uses this value as first parameter */ |
|
pipe_init.devInstance = xfer->udev; |
|
pipe_init.nakCount = USB_HOST_CONFIG_MAX_NAK; |
|
pipe_init.maxPacketSize = xfer->mps; |
|
pipe_init.endpointAddress = USB_EP_GET_IDX(xfer->ep); |
|
pipe_init.direction = USB_EP_GET_IDX(xfer->ep) == 0 ? USB_OUT : |
|
USB_EP_GET_DIR(xfer->ep) ? USB_IN : USB_OUT; |
|
/* Current Zephyr Host stack is experimental, the endpoint's interval, |
|
* 'number per uframe' and the endpoint type cannot be got yet. |
|
*/ |
|
pipe_init.numberPerUframe = 0; /* TODO: need right way to implement it. */ |
|
pipe_init.interval = xfer->interval; |
|
/* TODO: need right way to implement it. */ |
|
if (pipe_init.endpointAddress == 0) { |
|
pipe_init.pipeType = USB_ENDPOINT_CONTROL; |
|
} else { |
|
pipe_init.pipeType = USB_ENDPOINT_BULK; |
|
} |
|
|
|
status = priv->mcux_if->controllerOpenPipe(priv->mcux_host.controllerHandle, |
|
(usb_host_pipe_handle *)&mcux_ep, &pipe_init); |
|
|
|
if (status != kStatus_USB_Success) { |
|
return NULL; |
|
} |
|
|
|
/* Initialize mcux hal endpoint pipe |
|
* TODO: Need one way to release the pipe. |
|
* Otherwise the priv->mcux_eps will be used up after |
|
* supporting hub and connecting/disconnecting multiple times. |
|
* For example: add endpoint/pipe init and de-init controller |
|
* interafce to resolve the issue. |
|
*/ |
|
uhc_mcux_lock(dev); |
|
for (i = 0; i < USB_HOST_CONFIG_MAX_PIPES; i++) { |
|
if (priv->mcux_eps[i] == NULL) { |
|
priv->mcux_eps[i] = mcux_ep; |
|
break; |
|
} |
|
} |
|
uhc_mcux_unlock(dev); |
|
|
|
if (i >= USB_HOST_CONFIG_MAX_PIPES) { |
|
priv->mcux_if->controllerClosePipe(priv->mcux_host.controllerHandle, mcux_ep); |
|
mcux_ep = NULL; |
|
} |
|
|
|
return mcux_ep; |
|
} |
|
|
|
int uhc_mcux_hal_init_transfer_common(const struct device *dev, usb_host_transfer_t *mcux_xfer, |
|
usb_host_pipe_handle mcux_ep_handle, |
|
struct uhc_transfer *const xfer, |
|
host_inner_transfer_callback_t cb) |
|
{ |
|
mcux_xfer->uhc_xfer = xfer; |
|
mcux_xfer->transferPipe = mcux_ep_handle; |
|
mcux_xfer->transferSofar = 0; |
|
mcux_xfer->next = NULL; |
|
mcux_xfer->setupStatus = 0; |
|
mcux_xfer->callbackFn = cb; |
|
mcux_xfer->callbackParam = (void *)dev; |
|
mcux_xfer->setupPacket = (usb_setup_struct_t *)&xfer->setup_pkt[0]; |
|
if (xfer->buf != NULL) { |
|
mcux_xfer->transferLength = xfer->buf->size; |
|
mcux_xfer->transferBuffer = xfer->buf->__buf; |
|
} else { |
|
mcux_xfer->transferBuffer = NULL; |
|
mcux_xfer->transferLength = 0; |
|
} |
|
|
|
if (USB_EP_GET_IDX(xfer->ep) == 0) { |
|
mcux_xfer->direction = USB_REQTYPE_GET_DIR(mcux_xfer->setupPacket->bmRequestType) |
|
? USB_IN |
|
: USB_OUT; |
|
} |
|
|
|
return 0; |
|
}
|
|
|