usb: device_next: hid: allow to set polling period at runtime

Allow to set input or output report polling period at runtime.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
diff --git a/include/zephyr/usb/class/usbd_hid.h b/include/zephyr/usb/class/usbd_hid.h
index 5539369..79b52dd 100644
--- a/include/zephyr/usb/class/usbd_hid.h
+++ b/include/zephyr/usb/class/usbd_hid.h
@@ -213,6 +213,38 @@
 			     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/subsys/usb/device_next/class/Kconfig.hid b/subsys/usb/device_next/class/Kconfig.hid
index 8e3133a..a6f2980 100644
--- a/subsys/usb/device_next/class/Kconfig.hid
+++ b/subsys/usb/device_next/class/Kconfig.hid
@@ -30,6 +30,11 @@
 	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 212b409..2ea3a08 100644
--- a/subsys/usb/device_next/class/usbd_hid.c
+++ b/subsys/usb/device_next/class/usbd_hid.c
@@ -632,6 +632,56 @@
 	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)
@@ -706,6 +756,10 @@
 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 f2efa4e..fd9f27a 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 @@
 	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 d049b0c..dcb7474 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 @@
 	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);
 };