From: Stefan Eichenberger <[email protected]>

Register the ChipIdea gadget as a UCLASS_USB_GADGET_GENERIC driver so it
can be used on boards with DM_USB_GADGET enabled, which CI_UDC no longer
excludes. The driver enables the clocks and the PHY of its device tree
node and registers the gadget with usb_add_gadget_udc().

It has no of_match of its own. The controller node is shared with the
EHCI host driver, which owns it and binds this driver by name, so CI_UDC
depends on that driver once DM_USB_GADGET is enabled.

Boards without DM_USB_GADGET keep the existing behaviour.

Signed-off-by: Stefan Eichenberger <[email protected]>
---
 drivers/usb/gadget/Kconfig  |   8 +-
 drivers/usb/gadget/ci_udc.c | 152 +++++++++++++++++++++++++++++++++++-
 2 files changed, 158 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index e42d5a43696..67826ee5150 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -153,12 +153,18 @@ config USB_GADGET_OS_DESCRIPTORS
 
 config CI_UDC
        bool "ChipIdea device controller"
-       depends on !DM_USB_GADGET
+       depends on !DM_USB_GADGET || USB_EHCI_MX6 || USB_EHCI_MX7
        select USB_GADGET_DUALSPEED
        help
          Say Y here to enable device controller functionality of the
          ChipIdea driver.
 
+         With DM_USB_GADGET enabled this driver has no device tree match of
+         its own. The EHCI host driver owns the controller node and binds
+         this driver by name for the ports whose dr_mode is "peripheral" or
+         "otg", which is why it is required. Without DM_USB_GADGET the
+         controller is picked up through the EHCI host driver as before.
+
 config USB_GADGET_VBUS_DRAW
        int "Maximum VBUS Power usage (2-500 mA)"
        range 2 500
diff --git a/drivers/usb/gadget/ci_udc.c b/drivers/usb/gadget/ci_udc.c
index 53796887dac..f63cb17b58f 100644
--- a/drivers/usb/gadget/ci_udc.c
+++ b/drivers/usb/gadget/ci_udc.c
@@ -7,14 +7,18 @@
  * [email protected], 27-Jan-01.
  */
 
+#include <clk.h>
 #include <command.h>
 #include <config.h>
 #include <cpu_func.h>
+#include <dm.h>
+#include <generic-phy.h>
 #include <net.h>
 #include <malloc.h>
 #include <wait_bit.h>
 #include <asm/byteorder.h>
 #include <asm/cache.h>
+#include <dm/device_compat.h>
 #include <linux/delay.h>
 #include <linux/errno.h>
 #include <asm/io.h>
@@ -23,6 +27,7 @@
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
 #include <usb/ci_udc.h>
+#include <usb/ehci-ci.h>
 #include "../host/ehci.h"
 #include "ci_udc.h"
 
@@ -93,9 +98,18 @@ static int ci_ep_dequeue(struct usb_ep *ep, struct 
usb_request *req);
 static struct usb_request *
 ci_ep_alloc_request(struct usb_ep *ep, unsigned int gfp_flags);
 static void ci_ep_free_request(struct usb_ep *ep, struct usb_request *_req);
+#if CONFIG_IS_ENABLED(DM_USB_GADGET)
+static int ci_udc_start(struct usb_gadget *gadget,
+                       struct usb_gadget_driver *driver);
+static int ci_udc_stop(struct usb_gadget *gadget);
+#endif
 
 static const struct usb_gadget_ops ci_udc_ops = {
        .pullup = ci_pullup,
+#if CONFIG_IS_ENABLED(DM_USB_GADGET)
+       .udc_start = ci_udc_start,
+       .udc_stop = ci_udc_stop,
+#endif
 };
 
 static const struct usb_ep_ops ci_ep_ops = {
@@ -994,7 +1008,7 @@ void udc_irq(void)
        }
 }
 
-int dm_usb_gadget_handle_interrupts(struct udevice *dev)
+static int ci_udc_gadget_handle_interrupts(struct udevice *dev)
 {
        struct ci_udc *udc = (struct ci_udc *)controller.ctrl->hcor;
        u32 value;
@@ -1139,6 +1153,12 @@ static int ci_udc_probe(void)
        return 0;
 }
 
+#if !CONFIG_IS_ENABLED(DM_USB_GADGET)
+int dm_usb_gadget_handle_interrupts(struct udevice *dev)
+{
+       return ci_udc_gadget_handle_interrupts(dev);
+}
+
 int usb_gadget_register_driver(struct usb_gadget_driver *driver)
 {
        int ret;
@@ -1192,6 +1212,7 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver 
*driver)
 
        return 0;
 }
+#endif /* !DM_USB_GADGET */
 
 bool dfu_usb_get_reset(void)
 {
@@ -1199,3 +1220,132 @@ bool dfu_usb_get_reset(void)
 
        return !!(readl(&udc->usbsts) & STS_URI);
 }
+
+#if CONFIG_IS_ENABLED(DM_USB_GADGET)
+struct ci_udc_priv {
+       /* Only hccr and hcor are used, the gadget does not drive EHCI. */
+       struct ehci_ctrl        ctrl;
+       struct clk_bulk         clks;
+       struct phy              phy;
+};
+
+static int ci_udc_start(struct usb_gadget *gadget,
+                       struct usb_gadget_driver *driver)
+{
+       if (!driver || !driver->bind || !driver->setup || !driver->disconnect)
+               return -EINVAL;
+
+       controller.driver = driver;
+
+       return 0;
+}
+
+static int ci_udc_stop(struct usb_gadget *gadget)
+{
+       controller.driver = NULL;
+
+       return 0;
+}
+
+static int ci_udc_clk_init(struct udevice *dev, struct clk_bulk *clks)
+{
+       int ret;
+
+       ret = clk_get_bulk(dev, clks);
+       if (ret == -ENOSYS || ret == -ENOENT)
+               return 0;
+       if (ret)
+               return ret;
+
+       ret = clk_enable_bulk(clks);
+       if (ret) {
+               clk_release_bulk(clks);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int ci_udc_dm_probe(struct udevice *dev)
+{
+       struct ci_udc_priv *priv = dev_get_priv(dev);
+       struct usb_ehci *ehci;
+       int ret;
+
+       ehci = dev_read_addr_ptr(dev);
+       if (!ehci)
+               return -EINVAL;
+
+       ret = ci_udc_clk_init(dev, &priv->clks);
+       if (ret)
+               return ret;
+
+       ret = generic_setup_phy(dev, &priv->phy, 0, PHY_MODE_USB_DEVICE, 0);
+       if (ret)
+               goto err_clk;
+
+       /* Same register layout the EHCI host driver derives its regs from. */
+       priv->ctrl.hccr = (struct ehci_hccr *)&ehci->caplength;
+       priv->ctrl.hcor = (struct ehci_hcor *)((uintptr_t)priv->ctrl.hccr +
+                         HC_LENGTH(ehci_readl(&priv->ctrl.hccr->cr_capbase)));
+       controller.ctrl = &priv->ctrl;
+
+       ret = ci_udc_probe();
+       if (ret) {
+               dev_err(dev, "udc probe failed, returned %d\n", ret);
+               goto err_phy;
+       }
+
+       ret = usb_add_gadget_udc((struct device *)dev, &controller.gadget);
+       if (ret)
+               goto err_udc;
+
+       return 0;
+
+err_udc:
+       ci_ep_free_request(&controller.ep[0].ep, &controller.ep0_req->req);
+       free(controller.items_mem);
+       free(controller.epts);
+err_phy:
+       generic_shutdown_phy(&priv->phy);
+err_clk:
+       clk_release_bulk(&priv->clks);
+       return ret;
+}
+
+static int ci_udc_dm_remove(struct udevice *dev)
+{
+       struct ci_udc_priv *priv = dev_get_priv(dev);
+
+       usb_del_gadget_udc(&controller.gadget);
+
+       ci_ep_free_request(&controller.ep[0].ep, &controller.ep0_req->req);
+       free(controller.items_mem);
+       free(controller.epts);
+       controller.ctrl = NULL;
+
+       generic_shutdown_phy(&priv->phy);
+       clk_release_bulk(&priv->clks);
+
+       return 0;
+}
+
+static const struct usb_gadget_generic_ops ci_udc_gadget_ops = {
+       .handle_interrupts      = ci_udc_gadget_handle_interrupts,
+};
+
+/*
+ * No of_match: the ChipIdea controller shares its device tree node with the
+ * EHCI host driver, which owns the node and binds this driver by name for
+ * the ports that are usable in device mode.
+ */
+U_BOOT_DRIVER(ci_udc) = {
+       .name           = "ci_udc",
+       .id             = UCLASS_USB_GADGET_GENERIC,
+       .ops            = &ci_udc_gadget_ops,
+       .probe          = ci_udc_dm_probe,
+       .remove         = ci_udc_dm_remove,
+       .priv_auto      = sizeof(struct ci_udc_priv),
+       .flags          = DM_FLAG_ALLOC_PRIV_DMA,
+};
+#endif /* DM_USB_GADGET */
-- 
2.55.0

Reply via email to