drivers: udc_dwc2: use devicetree to configure endpoint capabilities

Although we can get the number of configured OUT and IN endpoints and
endpoint capabilities from the DWC GHWCFGn registers, we need to
configure the number of endpoint configuration structs at build time. On
some platforms, we cannot access the hardware register at pre-init, so
we use the GHWCFGn values from the devicetree to provide endpoint
capabilities. This can be considered a workaround, and we may change the
upper layer internals to avoid it in the future.

Also, add a new vendor quirk to fill in platform-specific controller
capabilities.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c
index 0f744ca..f533490 100644
--- a/drivers/usb/udc/udc_dwc2.c
+++ b/drivers/usb/udc/udc_dwc2.c
@@ -54,12 +54,6 @@
 /* TX FIFO0 depth in 32-bit words (used by control IN endpoint) */
 #define UDC_DWC2_FIFO0_DEPTH		16U
 
-/* Number of endpoints supported by the driver.
- * This must be equal to or greater than the number supported by the hardware.
- * (FIXME)
- */
-#define UDC_DWC2_DRV_EP_NUM		8
-
 /* Get Data FIFO access register */
 #define UDC_DWC2_EP_FIFO(base, idx)	((mem_addr_t)base + 0x1000 * (idx + 1))
 
@@ -1613,6 +1607,8 @@
 	const struct udc_dwc2_config *config = dev->config;
 	struct udc_data *data = dev->data;
 	uint16_t mps = 1023;
+	uint32_t numdeveps;
+	uint32_t ineps;
 	int err;
 
 	k_mutex_init(&data->mutex);
@@ -1620,49 +1616,93 @@
 	data->caps.rwup = true;
 	data->caps.addr_before_status = true;
 	data->caps.mps0 = UDC_MPS0_64;
-	if (config->speed_idx == 2) {
-		data->caps.hs = true;
+
+	(void)dwc2_quirk_caps(dev);
+	if (data->caps.hs) {
 		mps = 1024;
 	}
 
-	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 = mps;
+	/*
+	 * At this point, we cannot or do not want to access the hardware
+	 * registers to get GHWCFGn values. For now, we will use devicetree to
+	 * get GHWCFGn values and use them to determine the number and type of
+	 * configured endpoints in the hardware. This can be considered a
+	 * workaround, and we may change the upper layer internals to avoid it
+	 * in the future.
+	 */
+	ineps = usb_dwc2_get_ghwcfg4_ineps(config->ghwcfg4) + 1U;
+	numdeveps = usb_dwc2_get_ghwcfg2_numdeveps(config->ghwcfg2) + 1U;
+	LOG_DBG("Number of endpoints (NUMDEVEPS + 1) %u", numdeveps);
+	LOG_DBG("Number of IN endpoints (INEPS + 1) %u", ineps);
+
+	for (uint32_t i = 0, n = 0; i < numdeveps; i++) {
+		uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i);
+
+		if (epdir != USB_DWC2_GHWCFG1_EPDIR_OUT &&
+		    epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) {
+			continue;
 		}
 
-		config->ep_cfg_out[i].addr = USB_EP_DIR_OUT | i;
-		err = udc_register_ep(dev, &config->ep_cfg_out[i]);
+		if (i == 0) {
+			config->ep_cfg_out[n].caps.control = 1;
+			config->ep_cfg_out[n].caps.mps = 64;
+		} else {
+			config->ep_cfg_out[n].caps.bulk = 1;
+			config->ep_cfg_out[n].caps.interrupt = 1;
+			config->ep_cfg_out[n].caps.iso = 1;
+			config->ep_cfg_out[n].caps.mps = mps;
+		}
+
+		config->ep_cfg_out[n].caps.out = 1;
+		config->ep_cfg_out[n].addr = USB_EP_DIR_OUT | i;
+
+		LOG_DBG("Register ep 0x%02x (%u)", i, n);
+		err = udc_register_ep(dev, &config->ep_cfg_out[n]);
 		if (err != 0) {
 			LOG_ERR("Failed to register endpoint");
 			return err;
 		}
+
+		n++;
+		/* Also check the number of desired OUT endpoints in devicetree. */
+		if (n >= config->num_out_eps) {
+			break;
+		}
 	}
 
-	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 = mps;
+	for (uint32_t i = 0, n = 0; i < numdeveps; i++) {
+		uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i);
+
+		if (epdir != USB_DWC2_GHWCFG1_EPDIR_IN &&
+		    epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) {
+			continue;
 		}
 
-		config->ep_cfg_in[i].addr = USB_EP_DIR_IN | i;
-		err = udc_register_ep(dev, &config->ep_cfg_in[i]);
+		if (i == 0) {
+			config->ep_cfg_in[n].caps.control = 1;
+			config->ep_cfg_in[n].caps.mps = 64;
+		} else {
+			config->ep_cfg_in[n].caps.bulk = 1;
+			config->ep_cfg_in[n].caps.interrupt = 1;
+			config->ep_cfg_in[n].caps.iso = 1;
+			config->ep_cfg_in[n].caps.mps = mps;
+		}
+
+		config->ep_cfg_in[n].caps.in = 1;
+		config->ep_cfg_in[n].addr = USB_EP_DIR_IN | i;
+
+		LOG_DBG("Register ep 0x%02x (%u)", USB_EP_DIR_IN | i, n);
+		err = udc_register_ep(dev, &config->ep_cfg_in[n]);
 		if (err != 0) {
 			LOG_ERR("Failed to register endpoint");
 			return err;
 		}
+
+		n++;
+		/* Also check the number of desired IN endpoints in devicetree. */
+		if (n >= MIN(ineps, config->num_in_eps)) {
+			break;
+		}
 	}
 
 	config->make_thread(dev);
@@ -1770,19 +1810,23 @@
 		irq_disable(DT_INST_IRQN(n));					\
 	}									\
 										\
-	static struct udc_ep_config ep_cfg_out[UDC_DWC2_DRV_EP_NUM];		\
-	static struct udc_ep_config ep_cfg_in[UDC_DWC2_DRV_EP_NUM];		\
+	static struct udc_ep_config ep_cfg_out[DT_INST_PROP(n, num_out_eps)];	\
+	static struct udc_ep_config ep_cfg_in[DT_INST_PROP(n, num_in_eps)];	\
 										\
 	static const struct udc_dwc2_config udc_dwc2_config_##n = {		\
-		.num_of_eps = UDC_DWC2_DRV_EP_NUM,				\
-		.ep_cfg_in = ep_cfg_out,					\
-		.ep_cfg_out = ep_cfg_in,					\
+		.num_out_eps = DT_INST_PROP(n, num_out_eps),			\
+		.num_in_eps = DT_INST_PROP(n, num_in_eps),			\
+		.ep_cfg_in = ep_cfg_in,						\
+		.ep_cfg_out = ep_cfg_out,					\
 		.make_thread = udc_dwc2_make_thread_##n,			\
 		.base = (struct usb_dwc2_reg *)UDC_DWC2_DT_INST_REG_ADDR(n),	\
 		.pcfg = UDC_DWC2_PINCTRL_DT_INST_DEV_CONFIG_GET(n),		\
 		.irq_enable_func = udc_dwc2_irq_enable_func_##n,		\
 		.irq_disable_func = udc_dwc2_irq_disable_func_##n,		\
 		.quirks = UDC_DWC2_VENDOR_QUIRK_GET(n),				\
+		.ghwcfg1 = DT_INST_PROP(n, ghwcfg1),				\
+		.ghwcfg2 = DT_INST_PROP(n, ghwcfg2),				\
+		.ghwcfg4 = DT_INST_PROP(n, ghwcfg4),				\
 	};									\
 										\
 	static struct udc_dwc2_data udc_priv_##n = {				\
diff --git a/drivers/usb/udc/udc_dwc2.h b/drivers/usb/udc/udc_dwc2.h
index 37c9a1ec..b54cb82 100644
--- a/drivers/usb/udc/udc_dwc2.h
+++ b/drivers/usb/udc/udc_dwc2.h
@@ -26,14 +26,16 @@
 	int (*shutdown)(const struct device *dev);
 	/* Called at the end of IRQ handling */
 	int (*irq_clear)(const struct device *dev);
+	/* Called on driver pre-init */
+	int (*caps)(const struct device *dev);
 };
 
 /* Driver configuration per instance */
 struct udc_dwc2_config {
-	size_t num_of_eps;
+	size_t num_in_eps;
+	size_t num_out_eps;
 	struct udc_ep_config *ep_cfg_in;
 	struct udc_ep_config *ep_cfg_out;
-	int speed_idx;
 	struct usb_dwc2_reg *const base;
 	/* Pointer to pin control configuration or NULL */
 	struct pinctrl_dev_config *const pcfg;
@@ -42,6 +44,9 @@
 	void (*make_thread)(const struct device *dev);
 	void (*irq_enable_func)(const struct device *dev);
 	void (*irq_disable_func)(const struct device *dev);
+	uint32_t ghwcfg1;
+	uint32_t ghwcfg2;
+	uint32_t ghwcfg4;
 };
 
 #define DWC2_QUIRK_FUNC_DEFINE(fname)						\
@@ -63,5 +68,6 @@
 DWC2_QUIRK_FUNC_DEFINE(disable)
 DWC2_QUIRK_FUNC_DEFINE(shutdown)
 DWC2_QUIRK_FUNC_DEFINE(irq_clear)
+DWC2_QUIRK_FUNC_DEFINE(caps)
 
 #endif /* ZEPHYR_DRIVERS_USB_UDC_DWC2_H */
diff --git a/dts/arm/intel_socfpga_std/socfpga.dtsi b/dts/arm/intel_socfpga_std/socfpga.dtsi
index 7e1f372..ad36609 100644
--- a/dts/arm/intel_socfpga_std/socfpga.dtsi
+++ b/dts/arm/intel_socfpga_std/socfpga.dtsi
@@ -227,6 +227,11 @@
 			reg = <0xffb30000 0xffff>;
 			interrupts = <0 127 4 IRQ_DEFAULT_PRIORITY>;
 			interrupt-parent = <&intc>;
+			num-out-eps = <16>;
+			num-in-eps = <16>;
+			ghwcfg1 = <0x00000000>;
+			ghwcfg2 = <0x208ffc90>;
+			ghwcfg4 = <0xfe0f0020>;
 			status = "disabled";
 			};
 
@@ -235,6 +240,11 @@
 			reg = <0xffb40000 0xffff>;
 			interrupts = <0 128 4 IRQ_DEFAULT_PRIORITY>;
 			interrupt-parent = <&intc>;
+			num-out-eps = <16>;
+			num-in-eps = <16>;
+			ghwcfg1 = <0x00000000>;
+			ghwcfg2 = <0x208ffc90>;
+			ghwcfg4 = <0xfe0f0020>;
 			status = "okay";
 			};
 
diff --git a/dts/bindings/usb/snps,dwc2.yaml b/dts/bindings/usb/snps,dwc2.yaml
index 8af5544..d5b0167 100644
--- a/dts/bindings/usb/snps,dwc2.yaml
+++ b/dts/bindings/usb/snps,dwc2.yaml
@@ -18,3 +18,36 @@
 
   phys:
     type: phandle
+
+  num-in-eps:
+    type: int
+    required: true
+    description: |
+      Number of configured OUT endpoints including control endpoint.
+
+  num-out-eps:
+    type: int
+    required: true
+    description: |
+      Number of configured IN endpoints including control endpoint.
+
+  ghwcfg1:
+    type: int
+    required: true
+    description: |
+      Value of the GHWCFG1 register. It is used to determine available endpoint
+      types during driver pre-initialization.
+
+  ghwcfg2:
+    type: int
+    required: true
+    description: |
+      Value of the GHWCFG2 register. It is used to determine available endpoint
+      types during driver pre-initialization.
+
+  ghwcfg4:
+    type: int
+    required: true
+    description: |
+      Value of the GHWCFG4 register. It is used to determine available endpoint
+      types during driver pre-initialization.
diff --git a/samples/subsys/usb/cdc_acm/nucleo_f413zh_dwc2.overlay b/samples/subsys/usb/cdc_acm/nucleo_f413zh_dwc2.overlay
index 8cd2525..d1e6b29 100644
--- a/samples/subsys/usb/cdc_acm/nucleo_f413zh_dwc2.overlay
+++ b/samples/subsys/usb/cdc_acm/nucleo_f413zh_dwc2.overlay
@@ -15,6 +15,11 @@
 			interrupt-names = "fsotg";
 			clocks = <&rcc STM32_CLOCK_BUS_AHB2 0x00000080>,
 				 <&rcc STM32_SRC_PLL_Q NO_SEL>;
+			num-out-eps = <6>;
+			num-in-eps = <6>;
+			ghwcfg1 = <0x00000000>;
+			ghwcfg2 = <0x229ed520>;
+			ghwcfg4 = <0x17f08030>;
 			status = "disabled";
 		};
 	};
diff --git a/samples/subsys/usb/shell/nucleo_f413zh_dwc2.overlay b/samples/subsys/usb/shell/nucleo_f413zh_dwc2.overlay
index ff51b08..2e95462 100644
--- a/samples/subsys/usb/shell/nucleo_f413zh_dwc2.overlay
+++ b/samples/subsys/usb/shell/nucleo_f413zh_dwc2.overlay
@@ -15,6 +15,11 @@
 			interrupt-names = "fsotg";
 			clocks = <&rcc STM32_CLOCK_BUS_AHB2 0x00000080>,
 				 <&rcc STM32_SRC_PLL_Q NO_SEL>;
+			num-out-eps = <6>;
+			num-in-eps = <6>;
+			ghwcfg1 = <0x00000000>;
+			ghwcfg2 = <0x229ed520>;
+			ghwcfg4 = <0x17f08030>;
 		};
 	};
 };