diff --git a/drivers/usb/udc/Kconfig.dwc2 b/drivers/usb/udc/Kconfig.dwc2 index 1bb0e99fedd..ff3b169e66a 100644 --- a/drivers/usb/udc/Kconfig.dwc2 +++ b/drivers/usb/udc/Kconfig.dwc2 @@ -12,23 +12,22 @@ config UDC_DWC2 help DWC2 USB device controller driver. +if UDC_DWC2 + config UDC_DWC2_DMA bool "DWC2 USB DMA support" default y - depends on UDC_DWC2 help Enable Buffer DMA if DWC2 USB controller supports Internal DMA. config UDC_DWC2_HIBERNATION bool "DWC2 USB Hibernation support" default y - depends on UDC_DWC2 help Enable Hibernation if DWC2 USB controller supports hibernation. config UDC_DWC2_PTI bool "DWC2 USB Periodic Transfer Interrupt" - depends on UDC_DWC2 help Ignore frame number when scheduling isochronous endpoints. Use this when isochronous transfers should finish only after receiving token @@ -38,24 +37,23 @@ config UDC_DWC2_PTI config UDC_DWC2_STACK_SIZE int "UDC DWC2 driver internal thread stack size" - depends on UDC_DWC2 default 512 help DWC2 driver internal thread stack size. config UDC_DWC2_THREAD_PRIORITY int "UDC DWC2 driver thread priority" - depends on UDC_DWC2 default 8 help DWC2 driver thread priority. config UDC_DWC2_USBHS_VBUS_READY_TIMEOUT int "UDC DWC2 USBHS VBUS ready event timeout in ms" - depends on UDC_DWC2 - depends on NRFS_HAS_VBUS_DETECTOR_SERVICE + depends on SOC_SERIES_NRF54HX || SOC_SERIES_NRF54LX || SOC_SERIES_NRF92X default 0 help UDC DWC2 USBHS VBUS ready event timeout. If the VBUS is not ready and the Nordic USBHS controller is used, the udc_enable() is blocked for this amount of time. Set it to zero to wait forever. + +endif # UDC_DWC2 diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index 35fb78adecf..eb7368e1178 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -434,6 +434,11 @@ static void dwc2_ensure_setup_ready(const struct device *dev) } else { struct udc_dwc2_data *const priv = udc_get_private(dev); + if (udc_ep_is_busy(udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT))) { + /* There is already buffer queued */ + return; + } + /* Enable EP0 OUT only if there is no pending EP0 IN transfer * after which the stack has to enable EP0 OUT. */ @@ -1581,9 +1586,11 @@ static int dwc2_unset_dedicated_fifo(const struct device *dev, *diepctl &= ~USB_DWC2_DEPCTL_TXFNUM_MASK; if (priv->dynfifosizing) { - if (priv->txf_set & ~BIT_MASK(ep_idx)) { - LOG_WRN("Some of the FIFOs higher than %u are set, %lx", - ep_idx, priv->txf_set & ~BIT_MASK(ep_idx)); + uint16_t higher_mask = ~BIT_MASK(ep_idx + 1); + + if (priv->txf_set & higher_mask) { + LOG_WRN("Some of the FIFOs higher than %u are set, %x", + ep_idx, priv->txf_set & higher_mask); return 0; } @@ -1723,7 +1730,19 @@ static int udc_dwc2_ep_deactivate(const struct device *dev, mem_addr_t dxepctl_reg; uint32_t dxepctl; - dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); + if (priv->hibernated) { + /* If usbd_disable() is called when core is hibernated, modify + * backup registers instead of real ones. + */ + if (USB_EP_DIR_IS_OUT(cfg->addr)) { + dxepctl_reg = (mem_addr_t)&priv->backup.doepctl[ep_idx]; + } else { + dxepctl_reg = (mem_addr_t)&priv->backup.diepctl[ep_idx]; + } + } else { + dxepctl_reg = dwc2_get_dxepctl_reg(dev, cfg->addr); + } + dxepctl = sys_read32(dxepctl_reg); if (dxepctl & USB_DWC2_DEPCTL_USBACTEP) { diff --git a/drivers/usb/udc/udc_dwc2_vendor_quirks.h b/drivers/usb/udc/udc_dwc2_vendor_quirks.h index 330ff8d113f..cd25f0ec9d3 100644 --- a/drivers/usb/udc/udc_dwc2_vendor_quirks.h +++ b/drivers/usb/udc/udc_dwc2_vendor_quirks.h @@ -381,6 +381,10 @@ static inline int usbhs_enable_core(const struct device *dev) k_timeout_t timeout = K_FOREVER; int err; + if (CONFIG_UDC_DWC2_USBHS_VBUS_READY_TIMEOUT) { + timeout = K_MSEC(CONFIG_UDC_DWC2_USBHS_VBUS_READY_TIMEOUT); + } + if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, K_NO_WAIT)) { LOG_WRN("VBUS is not ready, block udc_enable()"); if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, timeout)) { diff --git a/include/zephyr/usb/class/usbd_hid.h b/include/zephyr/usb/class/usbd_hid.h index 5539369d5b0..79b52dd8fd9 100644 --- a/include/zephyr/usb/class/usbd_hid.h +++ b/include/zephyr/usb/class/usbd_hid.h @@ -212,6 +212,38 @@ int hid_device_register(const struct device *dev, int hid_device_submit_report(const struct device *dev, const uint16_t size, const uint8_t *const report); +/** + * @brief Set input report polling period + * + * Similar to devicetree property in-polling-period-us, but it allows setting + * different polling periods at runtime. + * + * @kconfig_dep{CONFIG_USBD_HID_SET_POLLING_PERIOD} + * + * @param[in] dev Pointer to HID device + * @param[in] period_us Polling period in microseconds + * + * @return 0 on success, negative errno code on failure. + * @retval -ENOTSUP If API is not enabled. + */ +int hid_device_set_in_polling(const struct device *dev, const unsigned int period_us); + +/** + * @brief Set output report polling period + * + * Similar to devicetree property out-polling-period-us, but it allows setting + * different polling periods at runtime. + * + * @kconfig_dep{CONFIG_USBD_HID_SET_POLLING_PERIOD} + * + * @param[in] dev Pointer to HID device + * @param[in] period_us Polling period in microseconds + * + * @return 0 on success, negative errno code on failure. + * @retval -ENOTSUP If API is not enabled. + */ +int hid_device_set_out_polling(const struct device *dev, const unsigned int period_us); + /** * @} */ diff --git a/samples/subsys/usb/hid-keyboard/sample.yaml b/samples/subsys/usb/hid-keyboard/sample.yaml index f53ae514f97..2998f84807d 100644 --- a/samples/subsys/usb/hid-keyboard/sample.yaml +++ b/samples/subsys/usb/hid-keyboard/sample.yaml @@ -33,3 +33,5 @@ tests: sample.usbd.hid-keyboard.large-out-report: extra_args: - EXTRA_DTC_OVERLAY_FILE="large_out_report.overlay" + extra_configs: + - CONFIG_USBD_HID_SET_POLLING_PERIOD=y diff --git a/samples/subsys/usb/hid-keyboard/src/main.c b/samples/subsys/usb/hid-keyboard/src/main.c index 45580c95a84..39ce8836ff6 100644 --- a/samples/subsys/usb/hid-keyboard/src/main.c +++ b/samples/subsys/usb/hid-keyboard/src/main.c @@ -206,6 +206,18 @@ int main(void) return ret; } + if (IS_ENABLED(CONFIG_USBD_HID_SET_POLLING_PERIOD)) { + ret = hid_device_set_in_polling(hid_dev, 1000); + if (ret) { + LOG_WRN("Failed to set IN report polling period, %d", ret); + } + + ret = hid_device_set_out_polling(hid_dev, 1000); + if (ret != 0 && ret != -ENOTSUP) { + LOG_WRN("Failed to set OUT report polling period, %d", ret); + } + } + sample_usbd = sample_usbd_init_device(msg_cb); if (sample_usbd == NULL) { LOG_ERR("Failed to initialize USB device"); diff --git a/subsys/usb/device_next/class/Kconfig.hid b/subsys/usb/device_next/class/Kconfig.hid index 8e3133a1dde..a6f2980ec3a 100644 --- a/subsys/usb/device_next/class/Kconfig.hid +++ b/subsys/usb/device_next/class/Kconfig.hid @@ -30,6 +30,11 @@ config USBD_HID_INIT_PRIORITY help HID device initialization priority +config USBD_HID_SET_POLLING_PERIOD + bool "Allow to set polling period at runtime" + help + Allow to set input or output report polling period at runtime. + module = USBD_HID module-str = usbd hid source "subsys/logging/Kconfig.template.log_config" diff --git a/subsys/usb/device_next/class/usbd_hid.c b/subsys/usb/device_next/class/usbd_hid.c index d978aea0d27..2ea3a08db76 100644 --- a/subsys/usb/device_next/class/usbd_hid.c +++ b/subsys/usb/device_next/class/usbd_hid.c @@ -57,6 +57,7 @@ struct usbd_hid_descriptor { }; enum { + HID_DEV_CLASS_INITIALIZED, HID_DEV_CLASS_ENABLED, }; @@ -503,7 +504,14 @@ static int usbd_hid_init(struct usbd_class_data *const c_data) const struct device *dev = usbd_class_get_private(c_data); const struct hid_device_config *dcfg = dev->config; struct usbd_hid_descriptor *const desc = dcfg->desc; + struct hid_device_data *const ddata = dev->data; + if (ddata->ops == NULL || ddata->rdesc == NULL || !ddata->rsize) { + LOG_ERR("HID device does not seem to be registered"); + return -EINVAL; + } + + atomic_set_bit(&ddata->state, HID_DEV_CLASS_INITIALIZED); LOG_DBG("HID class %s init", c_data->name); if (dcfg->if_desc_data != NULL && desc->if0.iInterface == 0) { @@ -519,6 +527,10 @@ static int usbd_hid_init(struct usbd_class_data *const c_data) static void usbd_hid_shutdown(struct usbd_class_data *const c_data) { + const struct device *dev = usbd_class_get_private(c_data); + struct hid_device_data *const ddata = dev->data; + + atomic_clear_bit(&ddata->state, HID_DEV_CLASS_INITIALIZED); LOG_DBG("HID class %s shutdown", c_data->name); } @@ -620,6 +632,56 @@ static int hid_dev_submit_report(const struct device *dev, return 0; } +static inline int hid_dev_set_out_polling(const struct device *dev, + const unsigned int period_us) +{ + const struct hid_device_config *const dcfg = dev->config; + struct hid_device_data *const ddata = dev->data; + struct usbd_hid_descriptor *const desc = dcfg->desc; + + if (atomic_test_bit(&ddata->state, HID_DEV_CLASS_INITIALIZED)) { + return -EBUSY; + } + + if (USBD_SUPPORTS_HIGH_SPEED) { + if (desc->hs_out_ep.bLength == 0) { + /* This device does not have output reports. */ + return -ENOTSUP; + } + + desc->hs_out_ep.bInterval = USB_HS_INT_EP_INTERVAL(period_us); + } + + if (desc->out_ep.bLength == 0) { + /* This device does not have output reports. */ + return -ENOTSUP; + } + + desc->out_ep.bInterval = USB_FS_INT_EP_INTERVAL(period_us); + + return 0; +} + +static inline int hid_dev_set_in_polling(const struct device *dev, + const unsigned int period_us) +{ + const struct hid_device_config *const dcfg = dev->config; + struct hid_device_data *const ddata = dev->data; + struct usbd_hid_descriptor *const desc = dcfg->desc; + + if (atomic_test_bit(&ddata->state, HID_DEV_CLASS_INITIALIZED)) { + return -EBUSY; + } + + if (USBD_SUPPORTS_HIGH_SPEED) { + desc->hs_in_ep.bInterval = USB_HS_INT_EP_INTERVAL(period_us); + } + + desc->in_ep.bInterval = USB_FS_INT_EP_INTERVAL(period_us); + + return 0; +} + static int hid_dev_register(const struct device *dev, const uint8_t *const rdesc, const uint16_t rsize, const struct hid_device_ops *const ops) @@ -628,7 +690,7 @@ static int hid_dev_register(const struct device *dev, struct hid_device_data *const ddata = dev->data; struct usbd_hid_descriptor *const desc = dcfg->desc; - if (atomic_test_bit(&ddata->state, HID_DEV_CLASS_ENABLED)) { + if (atomic_test_bit(&ddata->state, HID_DEV_CLASS_INITIALIZED)) { return -EALREADY; } @@ -694,6 +756,10 @@ struct usbd_class_api usbd_hid_api = { static const struct hid_device_driver_api hid_device_api = { .submit_report = hid_dev_submit_report, .dev_register = hid_dev_register, +#if CONFIG_USBD_HID_SET_POLLING_PERIOD + .set_out_polling = hid_dev_set_out_polling, + .set_in_polling = hid_dev_set_in_polling, +#endif }; #include "usbd_hid_macros.h" diff --git a/subsys/usb/device_next/class/usbd_hid_api.c b/subsys/usb/device_next/class/usbd_hid_api.c index f2efa4e3cb1..fd9f27a23ba 100644 --- a/subsys/usb/device_next/class/usbd_hid_api.c +++ b/subsys/usb/device_next/class/usbd_hid_api.c @@ -34,6 +34,28 @@ int hid_device_register(const struct device *dev, return api->dev_register(dev, rdesc, rsize, ops); } +int hid_device_set_in_polling(const struct device *dev, const unsigned int period_us) +{ + const struct hid_device_driver_api *const api = dev->api; + + if (IS_ENABLED(CONFIG_USBD_HID_SET_POLLING_PERIOD)) { + return api->set_in_polling(dev, period_us); + } + + return -ENOTSUP; +} + +int hid_device_set_out_polling(const struct device *dev, const unsigned int period_us) +{ + const struct hid_device_driver_api *const api = dev->api; + + if (IS_ENABLED(CONFIG_USBD_HID_SET_POLLING_PERIOD)) { + return api->set_out_polling(dev, period_us); + } + + return -ENOTSUP; +} + /* Legacy HID API wrapper below */ struct legacy_wrapper { diff --git a/subsys/usb/device_next/class/usbd_hid_internal.h b/subsys/usb/device_next/class/usbd_hid_internal.h index d049b0c22a3..dcb74748627 100644 --- a/subsys/usb/device_next/class/usbd_hid_internal.h +++ b/subsys/usb/device_next/class/usbd_hid_internal.h @@ -20,4 +20,6 @@ struct hid_device_driver_api { int (*dev_register)(const struct device *dev, const uint8_t *const rdesc, const uint16_t rsize, const struct hid_device_ops *const ops); + int (*set_in_polling)(const struct device *dev, const unsigned int period_us); + int (*set_out_polling)(const struct device *dev, const unsigned int period_us); };