|  | /* | 
|  | * Copyright 2024 NXP | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  | #define DT_DRV_COMPAT nxp_ehci | 
|  |  | 
|  | #include <soc.h> | 
|  | #include <string.h> | 
|  | #include <stdio.h> | 
|  |  | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  | #include <zephyr/drivers/usb/udc.h> | 
|  | #include <zephyr/drivers/pinctrl.h> | 
|  |  | 
|  | #include "udc_common.h" | 
|  | #include "usb.h" | 
|  | #include "usb_device_config.h" | 
|  | #include "usb_device_mcux_drv_port.h" | 
|  | #include "usb_device_ehci.h" | 
|  | #include "usb_phy.h" | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(udc_mcux, CONFIG_UDC_DRIVER_LOG_LEVEL); | 
|  |  | 
|  | /* | 
|  | * There is no real advantage to change control endpoint size | 
|  | * but we can use it for testing UDC driver API and higher layers. | 
|  | */ | 
|  | #define USB_MCUX_MPS0		UDC_MPS0_64 | 
|  | #define USB_MCUX_EP0_SIZE	64 | 
|  |  | 
|  | #define PRV_DATA_HANDLE(_handle) CONTAINER_OF(_handle, struct udc_mcux_data, mcux_device) | 
|  |  | 
|  | struct udc_mcux_config { | 
|  | const usb_device_controller_interface_struct_t *mcux_if; | 
|  | void (*irq_enable_func)(const struct device *dev); | 
|  | void (*irq_disable_func)(const struct device *dev); | 
|  | size_t num_of_eps; | 
|  | struct udc_ep_config *ep_cfg_in; | 
|  | struct udc_ep_config *ep_cfg_out; | 
|  | uintptr_t base; | 
|  | const struct pinctrl_dev_config *pincfg; | 
|  | usb_phy_config_struct_t *phy_config; | 
|  | }; | 
|  |  | 
|  | struct udc_mcux_data { | 
|  | const struct device *dev; | 
|  | usb_device_struct_t mcux_device; | 
|  | struct k_work work; | 
|  | struct k_fifo fifo; | 
|  | uint8_t controller_id; /* 0xFF is invalid value */ | 
|  | }; | 
|  |  | 
|  | /* Structure for driver's events */ | 
|  | struct udc_mcux_event { | 
|  | sys_snode_t node; | 
|  | const struct device *dev; | 
|  | usb_device_callback_message_struct_t mcux_msg; | 
|  | }; | 
|  |  | 
|  | K_MEM_SLAB_DEFINE(udc_event_slab, sizeof(struct udc_mcux_event), | 
|  | CONFIG_UDC_NXP_EVENT_COUNT, sizeof(void *)); | 
|  |  | 
|  | static void udc_mcux_lock(const struct device *dev) | 
|  | { | 
|  | udc_lock_internal(dev, K_FOREVER); | 
|  | } | 
|  |  | 
|  | static void udc_mcux_unlock(const struct device *dev) | 
|  | { | 
|  | udc_unlock_internal(dev); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_control(const struct device *dev, usb_device_control_type_t command, | 
|  | void *param) | 
|  | { | 
|  | const struct udc_mcux_config *config = dev->config; | 
|  | const usb_device_controller_interface_struct_t *mcux_if = config->mcux_if; | 
|  | struct udc_mcux_data *priv = udc_get_private(dev); | 
|  | usb_status_t status; | 
|  |  | 
|  | status = mcux_if->deviceControl(priv->mcux_device.controllerHandle, | 
|  | command, param); | 
|  |  | 
|  | if (status != kStatus_USB_Success) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* If ep is busy, return busy. Otherwise feed the buf to controller */ | 
|  | static int udc_mcux_ep_feed(const struct device *dev, | 
|  | struct udc_ep_config *const cfg, | 
|  | struct net_buf *const buf) | 
|  | { | 
|  | const struct udc_mcux_config *config = dev->config; | 
|  | const usb_device_controller_interface_struct_t *mcux_if = config->mcux_if; | 
|  | struct udc_mcux_data *priv = udc_get_private(dev); | 
|  | usb_status_t status = kStatus_USB_Success; | 
|  | uint8_t *data; | 
|  | uint32_t len; | 
|  | usb_device_endpoint_status_struct_t ep_status; | 
|  |  | 
|  | ep_status.endpointAddress = cfg->addr; | 
|  | udc_mcux_control(dev, kUSB_DeviceControlGetEndpointStatus, &ep_status); | 
|  | if (ep_status.endpointStatus == kUSB_DeviceEndpointStateStalled) { | 
|  | return -EACCES; /* stalled */ | 
|  | } | 
|  |  | 
|  | udc_mcux_lock(dev); | 
|  | if (!udc_ep_is_busy(dev, cfg->addr)) { | 
|  | udc_ep_set_busy(dev, cfg->addr, true); | 
|  | udc_mcux_unlock(dev); | 
|  |  | 
|  | if (USB_EP_DIR_IS_OUT(cfg->addr)) { | 
|  | len = net_buf_tailroom(buf); | 
|  | data = net_buf_tail(buf); | 
|  | status = mcux_if->deviceRecv(priv->mcux_device.controllerHandle, | 
|  | cfg->addr, data, len); | 
|  | } else { | 
|  | len = buf->len; | 
|  | data = buf->data; | 
|  | status = mcux_if->deviceSend(priv->mcux_device.controllerHandle, | 
|  | cfg->addr, data, len); | 
|  | } | 
|  |  | 
|  | udc_mcux_lock(dev); | 
|  | if (status != kStatus_USB_Success) { | 
|  | udc_ep_set_busy(dev, cfg->addr, false); | 
|  | } | 
|  | udc_mcux_unlock(dev); | 
|  | } else { | 
|  | udc_mcux_unlock(dev); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | return (status == kStatus_USB_Success ? 0 : -EIO); | 
|  | } | 
|  |  | 
|  | /* return success if the ep is busy or stalled. */ | 
|  | static int udc_mcux_ep_try_feed(const struct device *dev, | 
|  | struct udc_ep_config *const cfg) | 
|  | { | 
|  | struct net_buf *feed_buf; | 
|  |  | 
|  | feed_buf = udc_buf_peek(dev, cfg->addr); | 
|  | if (feed_buf) { | 
|  | int ret = udc_mcux_ep_feed(dev, cfg, feed_buf); | 
|  |  | 
|  | return ((ret == -EBUSY || ret == -EACCES || ret == 0) ? 0 : -EIO); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Allocate buffer and initiate a new control OUT transfer. | 
|  | */ | 
|  | static int udc_mcux_ctrl_feed_dout(const struct device *dev, | 
|  | const size_t length) | 
|  | { | 
|  | struct net_buf *buf; | 
|  | struct udc_ep_config *cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); | 
|  | int ret; | 
|  |  | 
|  | buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length); | 
|  | if (buf == NULL) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | k_fifo_put(&cfg->fifo, buf); | 
|  |  | 
|  | ret = udc_mcux_ep_feed(dev, cfg, buf); | 
|  |  | 
|  | if (ret) { | 
|  | net_buf_unref(buf); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_handler_setup(const struct device *dev, struct usb_setup_packet *setup) | 
|  | { | 
|  | int err; | 
|  | struct net_buf *buf; | 
|  |  | 
|  | LOG_DBG("setup packet"); | 
|  | buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, | 
|  | sizeof(struct usb_setup_packet)); | 
|  | if (buf == NULL) { | 
|  | LOG_ERR("Failed to allocate for setup"); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | udc_ep_buf_set_setup(buf); | 
|  | memcpy(buf->data, setup, 8); | 
|  | net_buf_add(buf, 8); | 
|  |  | 
|  | if (setup->RequestType.type == USB_REQTYPE_TYPE_STANDARD && | 
|  | setup->RequestType.direction == USB_REQTYPE_DIR_TO_DEVICE && | 
|  | setup->bRequest == USB_SREQ_SET_ADDRESS && | 
|  | setup->wLength == 0) { | 
|  | udc_mcux_control(dev, kUSB_DeviceControlPreSetDeviceAddress, | 
|  | &setup->wValue); | 
|  | } | 
|  |  | 
|  | /* Update to next stage of control transfer */ | 
|  | udc_ctrl_update_stage(dev, buf); | 
|  |  | 
|  | if (!buf->len) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | if (udc_ctrl_stage_is_data_out(dev)) { | 
|  | /*  Allocate and feed buffer for data OUT stage */ | 
|  | LOG_DBG("s:%p|feed for -out-", buf); | 
|  | err = udc_mcux_ctrl_feed_dout(dev, udc_data_stage_length(buf)); | 
|  | if (err == -ENOMEM) { | 
|  | err = udc_submit_ep_event(dev, buf, err); | 
|  | } | 
|  | } else if (udc_ctrl_stage_is_data_in(dev)) { | 
|  | err = udc_ctrl_submit_s_in_status(dev); | 
|  | } else { | 
|  | err = udc_ctrl_submit_s_status(dev); | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_handler_ctrl_out(const struct device *dev, struct net_buf *buf, | 
|  | uint8_t *mcux_buf, uint16_t mcux_len) | 
|  | { | 
|  | int err = 0; | 
|  | uint32_t len; | 
|  |  | 
|  | len = MIN(net_buf_tailroom(buf), mcux_len); | 
|  | net_buf_add(buf, len); | 
|  | if (udc_ctrl_stage_is_status_out(dev)) { | 
|  | /* Update to next stage of control transfer */ | 
|  | udc_ctrl_update_stage(dev, buf); | 
|  | /* Status stage finished, notify upper layer */ | 
|  | err = udc_ctrl_submit_status(dev, buf); | 
|  | } else { | 
|  | /* Update to next stage of control transfer */ | 
|  | udc_ctrl_update_stage(dev, buf); | 
|  | } | 
|  |  | 
|  | if (udc_ctrl_stage_is_status_in(dev)) { | 
|  | err = udc_ctrl_submit_s_out_status(dev, buf); | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_handler_ctrl_in(const struct device *dev, struct net_buf *buf, | 
|  | uint8_t *mcux_buf, uint16_t mcux_len) | 
|  | { | 
|  | int err = 0; | 
|  | uint32_t len; | 
|  |  | 
|  | len = MIN(buf->len, mcux_len); | 
|  | buf->data += len; | 
|  | buf->len -= len; | 
|  |  | 
|  | if (udc_ctrl_stage_is_status_in(dev) || | 
|  | udc_ctrl_stage_is_no_data(dev)) { | 
|  | /* Status stage finished, notify upper layer */ | 
|  | err = udc_ctrl_submit_status(dev, buf); | 
|  | } | 
|  |  | 
|  | /* Update to next stage of control transfer */ | 
|  | udc_ctrl_update_stage(dev, buf); | 
|  |  | 
|  | if (udc_ctrl_stage_is_status_out(dev)) { | 
|  | /* | 
|  | * IN transfer finished, release buffer, | 
|  | * control OUT buffer should be already fed. | 
|  | */ | 
|  | net_buf_unref(buf); | 
|  | err = udc_mcux_ctrl_feed_dout(dev, 0u); | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_handler_non_ctrl_in(const struct device *dev, uint8_t ep, | 
|  | struct net_buf *buf, uint8_t *mcux_buf, uint16_t mcux_len) | 
|  | { | 
|  | int err; | 
|  | uint32_t len; | 
|  |  | 
|  | len = MIN(buf->len, mcux_len); | 
|  | buf->data += len; | 
|  | buf->len -= len; | 
|  |  | 
|  | err = udc_submit_ep_event(dev, buf, 0); | 
|  | udc_mcux_ep_try_feed(dev, udc_get_ep_cfg(dev, ep)); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_handler_non_ctrl_out(const struct device *dev, uint8_t ep, | 
|  | struct net_buf *buf, uint8_t *mcux_buf, uint16_t mcux_len) | 
|  | { | 
|  | int err; | 
|  | uint32_t len; | 
|  |  | 
|  | len = MIN(net_buf_tailroom(buf), mcux_len); | 
|  | net_buf_add(buf, len); | 
|  |  | 
|  | err = udc_submit_ep_event(dev, buf, 0); | 
|  | udc_mcux_ep_try_feed(dev, udc_get_ep_cfg(dev, ep)); | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_handler_out(const struct device *dev, uint8_t ep, | 
|  | uint8_t *mcux_buf, uint16_t mcux_len) | 
|  | { | 
|  | int err; | 
|  | struct net_buf *buf; | 
|  |  | 
|  | buf = udc_buf_get(dev, ep); | 
|  |  | 
|  | udc_mcux_lock(dev); | 
|  | udc_ep_set_busy(dev, ep, false); | 
|  | udc_mcux_unlock(dev); | 
|  |  | 
|  | if (buf == NULL) { | 
|  | udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); | 
|  | return -ENOBUFS; | 
|  | } | 
|  |  | 
|  | if (ep == USB_CONTROL_EP_OUT) { | 
|  | err = udc_mcux_handler_ctrl_out(dev, buf, mcux_buf, mcux_len); | 
|  | } else { | 
|  | err = udc_mcux_handler_non_ctrl_out(dev, ep, buf, mcux_buf, mcux_len); | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | /* return true - zlp is feed; false - no zlp */ | 
|  | static bool udc_mcux_handler_zlt(const struct device *dev, uint8_t ep, struct net_buf *buf, | 
|  | uint16_t mcux_len) | 
|  | { | 
|  | const struct udc_mcux_config *config = dev->config; | 
|  | const usb_device_controller_interface_struct_t *mcux_if = config->mcux_if; | 
|  | struct udc_mcux_data *priv = udc_get_private(dev); | 
|  |  | 
|  | /* The whole transfer is already done by MCUX controller driver. */ | 
|  | if (mcux_len >= buf->len) { | 
|  | if (udc_ep_buf_has_zlp(buf)) { | 
|  | usb_status_t status; | 
|  |  | 
|  | udc_ep_buf_clear_zlp(buf); | 
|  | status = mcux_if->deviceSend(priv->mcux_device.controllerHandle, | 
|  | ep, NULL, 0); | 
|  | if (status != kStatus_USB_Success) { | 
|  | udc_submit_event(dev, UDC_EVT_ERROR, -EIO); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_handler_in(const struct device *dev, uint8_t ep, | 
|  | uint8_t *mcux_buf, uint16_t mcux_len) | 
|  | { | 
|  | int err; | 
|  | struct net_buf *buf; | 
|  |  | 
|  | buf = udc_buf_peek(dev, ep); | 
|  | if (buf == NULL) { | 
|  | udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); | 
|  | return -ENOBUFS; | 
|  | } | 
|  |  | 
|  | if (udc_mcux_handler_zlt(dev, ep, buf, mcux_len)) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | buf = udc_buf_get(dev, ep); | 
|  |  | 
|  | udc_mcux_lock(dev); | 
|  | udc_ep_set_busy(dev, ep, false); | 
|  | udc_mcux_unlock(dev); | 
|  |  | 
|  | if (buf == NULL) { | 
|  | udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); | 
|  | return -ENOBUFS; | 
|  | } | 
|  | if (ep == USB_CONTROL_EP_IN) { | 
|  | err = udc_mcux_handler_ctrl_in(dev, buf, mcux_buf, mcux_len); | 
|  | } else { | 
|  | err = udc_mcux_handler_non_ctrl_in(dev, ep, buf, mcux_buf, mcux_len); | 
|  | } | 
|  |  | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static void udc_mcux_event_submit(const struct device *dev, | 
|  | const usb_device_callback_message_struct_t *mcux_msg) | 
|  | { | 
|  | struct udc_mcux_data *priv = udc_get_private(dev); | 
|  | struct udc_mcux_event *ev; | 
|  | int ret; | 
|  |  | 
|  | ret = k_mem_slab_alloc(&udc_event_slab, (void **)&ev, K_NO_WAIT); | 
|  | if (ret) { | 
|  | udc_submit_event(dev, UDC_EVT_ERROR, ret); | 
|  | LOG_ERR("Failed to allocate slab"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ev->dev = dev; | 
|  | ev->mcux_msg = *mcux_msg; | 
|  | k_fifo_put(&priv->fifo, ev); | 
|  | k_work_submit_to_queue(udc_get_work_q(), &priv->work); | 
|  | } | 
|  |  | 
|  | static void udc_mcux_work_handler(struct k_work *item) | 
|  | { | 
|  | struct udc_mcux_event *ev; | 
|  | struct udc_mcux_data *priv; | 
|  | usb_device_callback_message_struct_t *mcux_msg; | 
|  | int err; | 
|  | uint8_t ep; | 
|  |  | 
|  | priv = CONTAINER_OF(item, struct udc_mcux_data, work); | 
|  | while ((ev = k_fifo_get(&priv->fifo, K_NO_WAIT)) != NULL) { | 
|  | mcux_msg = &ev->mcux_msg; | 
|  |  | 
|  | if (mcux_msg->code == kUSB_DeviceNotifyBusReset) { | 
|  | struct udc_ep_config *cfg; | 
|  |  | 
|  | udc_mcux_control(ev->dev, kUSB_DeviceControlSetDefaultStatus, NULL); | 
|  | cfg = udc_get_ep_cfg(ev->dev, USB_CONTROL_EP_OUT); | 
|  | if (cfg->stat.enabled) { | 
|  | udc_ep_disable_internal(ev->dev, USB_CONTROL_EP_OUT); | 
|  | } | 
|  | cfg = udc_get_ep_cfg(ev->dev, USB_CONTROL_EP_IN); | 
|  | if (cfg->stat.enabled) { | 
|  | udc_ep_disable_internal(ev->dev, USB_CONTROL_EP_IN); | 
|  | } | 
|  | if (udc_ep_enable_internal(ev->dev, USB_CONTROL_EP_OUT, | 
|  | USB_EP_TYPE_CONTROL, | 
|  | USB_MCUX_EP0_SIZE, 0)) { | 
|  | LOG_ERR("Failed to enable control endpoint"); | 
|  | } | 
|  |  | 
|  | if (udc_ep_enable_internal(ev->dev, USB_CONTROL_EP_IN, | 
|  | USB_EP_TYPE_CONTROL, | 
|  | USB_MCUX_EP0_SIZE, 0)) { | 
|  | LOG_ERR("Failed to enable control endpoint"); | 
|  | } | 
|  | udc_submit_event(ev->dev, UDC_EVT_RESET, 0); | 
|  | } else { | 
|  | ep  = mcux_msg->code; | 
|  |  | 
|  | if (mcux_msg->isSetup) { | 
|  | struct usb_setup_packet *setup = | 
|  | (struct usb_setup_packet *)mcux_msg->buffer; | 
|  |  | 
|  | err = udc_mcux_handler_setup(ev->dev, setup); | 
|  | } else if (USB_EP_DIR_IS_IN(ep)) { | 
|  | err = udc_mcux_handler_in(ev->dev, ep, mcux_msg->buffer, | 
|  | mcux_msg->length); | 
|  | } else { | 
|  | err = udc_mcux_handler_out(ev->dev, ep, mcux_msg->buffer, | 
|  | mcux_msg->length); | 
|  | } | 
|  |  | 
|  | if (unlikely(err)) { | 
|  | udc_submit_event(ev->dev, UDC_EVT_ERROR, err); | 
|  | } | 
|  | } | 
|  |  | 
|  | k_mem_slab_free(&udc_event_slab, (void *)ev); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* NXP MCUX controller driver notify transfers/status through this interface */ | 
|  | usb_status_t USB_DeviceNotificationTrigger(void *handle, void *msg) | 
|  | { | 
|  | usb_device_callback_message_struct_t *mcux_msg = msg; | 
|  | usb_device_notification_t mcux_notify; | 
|  | struct udc_mcux_data *priv; | 
|  | const struct device *dev; | 
|  | usb_status_t mcux_status = kStatus_USB_Success; | 
|  |  | 
|  | if ((NULL == msg) || (NULL == handle)) { | 
|  | return kStatus_USB_InvalidHandle; | 
|  | } | 
|  |  | 
|  | mcux_notify = (usb_device_notification_t)mcux_msg->code; | 
|  | priv = (struct udc_mcux_data *)(PRV_DATA_HANDLE(handle)); | 
|  | dev = priv->dev; | 
|  |  | 
|  | switch (mcux_notify) { | 
|  | case kUSB_DeviceNotifyBusReset: | 
|  | udc_mcux_event_submit(dev, mcux_msg); | 
|  | break; | 
|  | case kUSB_DeviceNotifyError: | 
|  | udc_submit_event(dev, UDC_EVT_ERROR, -EIO); | 
|  | break; | 
|  | case kUSB_DeviceNotifySuspend: | 
|  | udc_set_suspended(dev, true); | 
|  | udc_submit_event(dev, UDC_EVT_SUSPEND, 0); | 
|  | break; | 
|  | case kUSB_DeviceNotifyResume: | 
|  | udc_set_suspended(dev, false); | 
|  | udc_submit_event(dev, UDC_EVT_RESUME, 0); | 
|  | break; | 
|  | case kUSB_DeviceNotifyLPMSleep: | 
|  | break; | 
|  | case kUSB_DeviceNotifyDetach: | 
|  | udc_submit_event(dev, UDC_EVT_VBUS_REMOVED, 0); | 
|  | break; | 
|  | case kUSB_DeviceNotifyAttach: | 
|  | udc_submit_event(dev, UDC_EVT_VBUS_READY, 0); | 
|  | break; | 
|  | case kUSB_DeviceNotifySOF: | 
|  | udc_submit_event(dev, UDC_EVT_SOF, 0); | 
|  | break; | 
|  | default: | 
|  | udc_mcux_event_submit(dev, mcux_msg); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return mcux_status; | 
|  | } | 
|  |  | 
|  | static void udc_mcux_isr(const struct device *dev) | 
|  | { | 
|  | struct udc_mcux_data *priv = udc_get_private(dev); | 
|  |  | 
|  | USB_DeviceEhciIsrFunction((void *)(&priv->mcux_device)); | 
|  | } | 
|  |  | 
|  | /* Return actual USB device speed */ | 
|  | static enum udc_bus_speed udc_mcux_device_speed(const struct device *dev) | 
|  | { | 
|  | int err; | 
|  | uint8_t mcux_speed; | 
|  |  | 
|  | err = udc_mcux_control(dev, kUSB_DeviceControlGetSpeed, &mcux_speed); | 
|  | if (err) { | 
|  | /* | 
|  | * In the current version of all NXP USB device drivers, | 
|  | * no error is returned if the parameter is correct. | 
|  | */ | 
|  | return UDC_BUS_SPEED_FS; | 
|  | } | 
|  |  | 
|  | switch (mcux_speed) { | 
|  | case USB_SPEED_HIGH: | 
|  | return UDC_BUS_SPEED_HS; | 
|  | case USB_SPEED_LOW: | 
|  | __ASSERT(false, "Low speed mode not supported"); | 
|  | __fallthrough; | 
|  | case USB_SPEED_FULL: | 
|  | __fallthrough; | 
|  | default: | 
|  | return UDC_BUS_SPEED_FS; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int udc_mcux_ep_enqueue(const struct device *dev, | 
|  | struct udc_ep_config *const cfg, | 
|  | struct net_buf *const buf) | 
|  | { | 
|  | udc_buf_put(cfg, buf); | 
|  | if (cfg->stat.halted) { | 
|  | LOG_DBG("ep 0x%02x halted", cfg->addr); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return udc_mcux_ep_try_feed(dev, cfg); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_ep_dequeue(const struct device *dev, | 
|  | struct udc_ep_config *const cfg) | 
|  | { | 
|  | struct net_buf *buf; | 
|  |  | 
|  | cfg->stat.halted = false; | 
|  | buf = udc_buf_get_all(dev, cfg->addr); | 
|  | if (buf) { | 
|  | udc_submit_ep_event(dev, buf, -ECONNABORTED); | 
|  | } | 
|  |  | 
|  | udc_mcux_lock(dev); | 
|  | udc_ep_set_busy(dev, cfg->addr, false); | 
|  | udc_mcux_unlock(dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_ep_set_halt(const struct device *dev, | 
|  | struct udc_ep_config *const cfg) | 
|  | { | 
|  | return udc_mcux_control(dev, kUSB_DeviceControlEndpointStall, &cfg->addr); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_ep_clear_halt(const struct device *dev, | 
|  | struct udc_ep_config *const cfg) | 
|  | { | 
|  | (void)udc_mcux_control(dev, kUSB_DeviceControlEndpointUnstall, &cfg->addr); | 
|  | /* transfer is enqueued after stalled */ | 
|  | return udc_mcux_ep_try_feed(dev, cfg); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_ep_enable(const struct device *dev, | 
|  | struct udc_ep_config *const cfg) | 
|  | { | 
|  | usb_device_endpoint_init_struct_t ep_init; | 
|  |  | 
|  | LOG_DBG("Enable ep 0x%02x", cfg->addr); | 
|  |  | 
|  | ep_init.zlt             = 0U; | 
|  | ep_init.interval        = cfg->interval; | 
|  | ep_init.endpointAddress = cfg->addr; | 
|  | /* HAL expects wMaxPacketSize value directly in maxPacketSize field */ | 
|  | ep_init.maxPacketSize   = cfg->mps; | 
|  |  | 
|  | switch (cfg->attributes & USB_EP_TRANSFER_TYPE_MASK) { | 
|  | case USB_EP_TYPE_CONTROL: | 
|  | ep_init.transferType = USB_ENDPOINT_CONTROL; | 
|  | break; | 
|  | case USB_EP_TYPE_BULK: | 
|  | ep_init.transferType = USB_ENDPOINT_BULK; | 
|  | break; | 
|  | case USB_EP_TYPE_INTERRUPT: | 
|  | ep_init.transferType = USB_ENDPOINT_INTERRUPT; | 
|  | break; | 
|  | case USB_EP_TYPE_ISO: | 
|  | ep_init.transferType = USB_ENDPOINT_ISOCHRONOUS; | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return udc_mcux_control(dev, kUSB_DeviceControlEndpointInit, &ep_init); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_ep_disable(const struct device *dev, | 
|  | struct udc_ep_config *const cfg) | 
|  | { | 
|  | LOG_DBG("Disable ep 0x%02x", cfg->addr); | 
|  |  | 
|  | return udc_mcux_control(dev, kUSB_DeviceControlEndpointDeinit, &cfg->addr); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_host_wakeup(const struct device *dev) | 
|  | { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_set_address(const struct device *dev, const uint8_t addr) | 
|  | { | 
|  | uint8_t temp_addr = addr; | 
|  |  | 
|  | return udc_mcux_control(dev, kUSB_DeviceControlSetDeviceAddress, &temp_addr); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_enable(const struct device *dev) | 
|  | { | 
|  | return udc_mcux_control(dev, kUSB_DeviceControlRun, NULL); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_disable(const struct device *dev) | 
|  | { | 
|  | return udc_mcux_control(dev, kUSB_DeviceControlStop, NULL); | 
|  | } | 
|  |  | 
|  | static int udc_mcux_init(const struct device *dev) | 
|  | { | 
|  | const struct udc_mcux_config *config = dev->config; | 
|  | const usb_device_controller_interface_struct_t *mcux_if = config->mcux_if; | 
|  | struct udc_mcux_data *priv = udc_get_private(dev); | 
|  | usb_status_t status; | 
|  |  | 
|  | if (priv->controller_id == 0xFFu) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_DT_HAS_NXP_USBPHY_ENABLED | 
|  | if (config->phy_config != NULL) { | 
|  | USB_EhciPhyInit(priv->controller_id, 0u, config->phy_config); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Init MCUX USB device driver. */ | 
|  | status = mcux_if->deviceInit(priv->controller_id, | 
|  | &priv->mcux_device, &(priv->mcux_device.controllerHandle)); | 
|  | if (status != kStatus_USB_Success) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | /* enable USB interrupt */ | 
|  | config->irq_enable_func(dev); | 
|  |  | 
|  | LOG_DBG("Initialized USB controller %x", (uint32_t)config->base); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int udc_mcux_shutdown(const struct device *dev) | 
|  | { | 
|  | const struct udc_mcux_config *config = dev->config; | 
|  | const usb_device_controller_interface_struct_t *mcux_if = config->mcux_if; | 
|  | struct udc_mcux_data *priv = udc_get_private(dev); | 
|  | usb_status_t status; | 
|  |  | 
|  | /* Disable interrupt */ | 
|  | config->irq_disable_func(dev); | 
|  |  | 
|  | /* De-init MCUX USB device driver. */ | 
|  | status = mcux_if->deviceDeinit(priv->mcux_device.controllerHandle); | 
|  | if (status != kStatus_USB_Success) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static inline void udc_mcux_get_hal_driver_id(struct udc_mcux_data *priv, | 
|  | const struct udc_mcux_config *config) | 
|  | { | 
|  | /* | 
|  | * MCUX USB controller drivers use an ID to tell the HAL drivers | 
|  | * which controller is being used. This part of the code converts | 
|  | * the base address to the ID value. | 
|  | */ | 
|  | #ifdef USBHS_STACK_BASE_ADDRS | 
|  | uintptr_t usb_base_addrs[] = USBHS_STACK_BASE_ADDRS; | 
|  | #else | 
|  | uintptr_t usb_base_addrs[] = USBHS_BASE_ADDRS; | 
|  | #endif | 
|  |  | 
|  | /* get the right controller id */ | 
|  | priv->controller_id = 0xFFu; /* invalid value */ | 
|  | for (uint8_t i = 0; i < ARRAY_SIZE(usb_base_addrs); i++) { | 
|  | if (usb_base_addrs[i] == config->base) { | 
|  | priv->controller_id = kUSB_ControllerEhci0 + i; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static int udc_mcux_driver_preinit(const struct device *dev) | 
|  | { | 
|  | const struct udc_mcux_config *config = dev->config; | 
|  | struct udc_data *data = dev->data; | 
|  | struct udc_mcux_data *priv = data->priv; | 
|  | int err; | 
|  |  | 
|  | udc_mcux_get_hal_driver_id(priv, config); | 
|  | if (priv->controller_id == 0xFFu) { | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | k_mutex_init(&data->mutex); | 
|  | k_fifo_init(&priv->fifo); | 
|  | k_work_init(&priv->work, udc_mcux_work_handler); | 
|  |  | 
|  | for (int i = 0; i < config->num_of_eps; i++) { | 
|  | config->ep_cfg_out[i].caps.out = 1; | 
|  | if (i == 0) { | 
|  | config->ep_cfg_out[i].caps.control = 1; | 
|  | config->ep_cfg_out[i].caps.mps = 64; | 
|  | } else { | 
|  | config->ep_cfg_out[i].caps.bulk = 1; | 
|  | config->ep_cfg_out[i].caps.interrupt = 1; | 
|  | config->ep_cfg_out[i].caps.iso = 1; | 
|  | config->ep_cfg_out[i].caps.mps = 1024; | 
|  | config->ep_cfg_out[i].caps.high_bandwidth = 1; | 
|  | } | 
|  |  | 
|  | config->ep_cfg_out[i].addr = USB_EP_DIR_OUT | i; | 
|  | err = udc_register_ep(dev, &config->ep_cfg_out[i]); | 
|  | if (err != 0) { | 
|  | LOG_ERR("Failed to register endpoint"); | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < config->num_of_eps; i++) { | 
|  | config->ep_cfg_in[i].caps.in = 1; | 
|  | if (i == 0) { | 
|  | config->ep_cfg_in[i].caps.control = 1; | 
|  | config->ep_cfg_in[i].caps.mps = 64; | 
|  | } else { | 
|  | config->ep_cfg_in[i].caps.bulk = 1; | 
|  | config->ep_cfg_in[i].caps.interrupt = 1; | 
|  | config->ep_cfg_in[i].caps.iso = 1; | 
|  | config->ep_cfg_in[i].caps.mps = 1024; | 
|  | config->ep_cfg_in[i].caps.high_bandwidth = 1; | 
|  | } | 
|  |  | 
|  | config->ep_cfg_in[i].addr = USB_EP_DIR_IN | i; | 
|  | err = udc_register_ep(dev, &config->ep_cfg_in[i]); | 
|  | if (err != 0) { | 
|  | LOG_ERR("Failed to register endpoint"); | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Requires udc_mcux_host_wakeup() implementation */ | 
|  | data->caps.rwup = false; | 
|  | data->caps.mps0 = USB_MCUX_MPS0; | 
|  | data->caps.hs = true; | 
|  | priv->dev = dev; | 
|  |  | 
|  | pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct udc_api udc_mcux_api = { | 
|  | .device_speed = udc_mcux_device_speed, | 
|  | .ep_enqueue = udc_mcux_ep_enqueue, | 
|  | .ep_dequeue = udc_mcux_ep_dequeue, | 
|  | .ep_set_halt = udc_mcux_ep_set_halt, | 
|  | .ep_clear_halt = udc_mcux_ep_clear_halt, | 
|  | .ep_try_config = NULL, | 
|  | .ep_enable = udc_mcux_ep_enable, | 
|  | .ep_disable = udc_mcux_ep_disable, | 
|  | .host_wakeup = udc_mcux_host_wakeup, | 
|  | .set_address = udc_mcux_set_address, | 
|  | .enable = udc_mcux_enable, | 
|  | .disable = udc_mcux_disable, | 
|  | .init = udc_mcux_init, | 
|  | .shutdown = udc_mcux_shutdown, | 
|  | .lock = udc_mcux_lock, | 
|  | .unlock = udc_mcux_unlock, | 
|  | }; | 
|  |  | 
|  | /* EHCI device driver interface */ | 
|  | static const usb_device_controller_interface_struct_t udc_mcux_if = { | 
|  | USB_DeviceEhciInit, USB_DeviceEhciDeinit, USB_DeviceEhciSend, | 
|  | USB_DeviceEhciRecv, USB_DeviceEhciCancel, USB_DeviceEhciControl | 
|  | }; | 
|  |  | 
|  | #define UDC_MCUX_PHY_DEFINE(n)								\ | 
|  | static usb_phy_config_struct_t phy_config_##n = {					\ | 
|  | .D_CAL = DT_PROP_OR(DT_INST_PHANDLE(n, phy_handle), tx_d_cal, 0),		\ | 
|  | .TXCAL45DP = DT_PROP_OR(DT_INST_PHANDLE(n, phy_handle), tx_cal_45_dp_ohms, 0),	\ | 
|  | .TXCAL45DM = DT_PROP_OR(DT_INST_PHANDLE(n, phy_handle), tx_cal_45_dm_ohms, 0),	\ | 
|  | } | 
|  |  | 
|  | #define UDC_MCUX_PHY_DEFINE_OR(n)							\ | 
|  | COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(n), phy_handle),			\ | 
|  | (UDC_MCUX_PHY_DEFINE(n)), ()) | 
|  |  | 
|  | #define UDC_MCUX_PHY_CFG_PTR_OR_NULL(n)							\ | 
|  | COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(n), phy_handle),			\ | 
|  | (&phy_config_##n), (NULL)) | 
|  |  | 
|  | #define USB_MCUX_EHCI_DEVICE_DEFINE(n)							\ | 
|  | UDC_MCUX_PHY_DEFINE_OR(n);							\ | 
|  | \ | 
|  | static void udc_irq_enable_func##n(const struct device *dev)			\ | 
|  | {										\ | 
|  | IRQ_CONNECT(DT_INST_IRQN(n),						\ | 
|  | DT_INST_IRQ(n, priority),					\ | 
|  | udc_mcux_isr,						\ | 
|  | DEVICE_DT_INST_GET(n), 0);					\ | 
|  | \ | 
|  | irq_enable(DT_INST_IRQN(n));						\ | 
|  | }										\ | 
|  | \ | 
|  | static void udc_irq_disable_func##n(const struct device *dev)			\ | 
|  | {										\ | 
|  | irq_disable(DT_INST_IRQN(n));						\ | 
|  | }										\ | 
|  | \ | 
|  | static struct udc_ep_config							\ | 
|  | ep_cfg_out##n[DT_INST_PROP(n, num_bidir_endpoints)];			\ | 
|  | static struct udc_ep_config							\ | 
|  | ep_cfg_in##n[DT_INST_PROP(n, num_bidir_endpoints)];			\ | 
|  | \ | 
|  | PINCTRL_DT_INST_DEFINE(n);							\ | 
|  | \ | 
|  | static struct udc_mcux_config priv_config_##n = {				\ | 
|  | .base = DT_INST_REG_ADDR(n),						\ | 
|  | .irq_enable_func = udc_irq_enable_func##n,				\ | 
|  | .irq_disable_func = udc_irq_disable_func##n,				\ | 
|  | .num_of_eps = DT_INST_PROP(n, num_bidir_endpoints),			\ | 
|  | .ep_cfg_in = ep_cfg_in##n,						\ | 
|  | .ep_cfg_out = ep_cfg_out##n,						\ | 
|  | .mcux_if = &udc_mcux_if,						\ | 
|  | .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),				\ | 
|  | .phy_config = UDC_MCUX_PHY_CFG_PTR_OR_NULL(n),				\ | 
|  | };										\ | 
|  | \ | 
|  | static struct udc_mcux_data priv_data_##n = {					\ | 
|  | };										\ | 
|  | \ | 
|  | static struct udc_data udc_data_##n = {						\ | 
|  | .mutex = Z_MUTEX_INITIALIZER(udc_data_##n.mutex),			\ | 
|  | .priv = &priv_data_##n,							\ | 
|  | };										\ | 
|  | \ | 
|  | DEVICE_DT_INST_DEFINE(n, udc_mcux_driver_preinit, NULL,				\ | 
|  | &udc_data_##n, &priv_config_##n,				\ | 
|  | POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,		\ | 
|  | &udc_mcux_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(USB_MCUX_EHCI_DEVICE_DEFINE) |