]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
usb: tegra: support device mode
authorStephen Warren <swarren@nvidia.com>
Wed, 30 Apr 2014 21:09:57 +0000 (15:09 -0600)
committerMarek Vasut <marex@denx.de>
Wed, 14 May 2014 22:21:17 +0000 (00:21 +0200)
A few changes are made to the Tegra EHCI driver so that it can set
everything up for device-mode operation on the first USB controller.
This can be used in conjunction with ci_udc.c to operate as a USB
device.

Detailed changes are:

* Rename set_host_mode() to set_up_vbus() since that's really what it
  does.

* Modify set_up_vbus() to know whether it's initializing in host or
  device mode, and:

  - Skip the external VBUS check in device mode, since external VBUS is
    expected in this case.

  - Disable VBUS output in device mode.

* Modify init_phy_mux() to know whether it's initializing in host or
  device mode, and hence skip setting USBMODE_CM_HC (which enables host
  mode) in device mode. See the comments in that function for why this
  is safe w.r.t. the ordering requirements of PHY selection.

* Modify init_utmi_usb_controller() to force "b session valid" in device
  mode, since the HW requires this. This is done in UTMI-specific code,
  since we only support device mode on the first USB controller, and that
  controller can only talk to a UTMI PHY.

* Enhance ehci_hcd_init() to error-check the requested host-/device-mode
  vs. the dr_mode (dual-role mode) value present in device tree, and the
  HW configurations which support device mode.

* Enhance ehci_hcd_init() not to skip HW initialization when switching
  between host and device mode on a controller. This requires remembering
  which mode the last initialization used.

Cc: Jim Lin <jilin@nvidia.com>
Cc: Stefan Agner <stefan@agner.ch>
Signed-off-by: Stephen Warren <swarren@nvidia.com>
arch/arm/include/asm/arch-tegra/usb.h
drivers/usb/host/ehci-tegra.c

index ceb7bcd9cfb9431bd3e9cd848b8574b3ce42ee1b..c817088fa57e30a81902f333fae35b362a8fb44d 100644 (file)
@@ -349,6 +349,8 @@ struct usb_ctlr {
 
 /* USB3_IF_USB_PHY_VBUS_SENSORS_0 */
 #define VBUS_VLD_STS                   (1 << 26)
+#define VBUS_B_SESS_VLD_SW_VALUE       (1 << 12)
+#define VBUS_B_SESS_VLD_SW_EN          (1 << 11)
 
 /* Setup USB on the board */
 int usb_process_devicetree(const void *blob);
index 55a546e4d3da2f7263633ccdee7f994e442a0521..33e5ea9ebdd0d836d4412af5971f558b790dc8bd 100644 (file)
@@ -69,6 +69,7 @@ struct fdt_usb {
        unsigned enabled:1;     /* 1 to enable, 0 to disable */
        unsigned has_legacy_mode:1; /* 1 if this port has legacy mode */
        unsigned initialized:1; /* has this port already been initialized? */
+       enum usb_init_type init_type;
        enum dr_mode dr_mode;   /* dual role mode */
        enum periph_id periph_id;/* peripheral id */
        struct fdt_gpio_state vbus_gpio;        /* GPIO for vbus enable */
@@ -237,29 +238,31 @@ int ehci_get_port_speed(struct ehci_hcor *hcor, uint32_t reg)
                return PORTSC_PSPD(reg);
 }
 
-/* Put the port into host mode */
-static void set_host_mode(struct fdt_usb *config)
+/* Set up VBUS for host/device mode */
+static void set_up_vbus(struct fdt_usb *config, enum usb_init_type init)
 {
        /*
-        * If we are an OTG port, check if remote host is driving VBus and
-        * bail out in this case.
+        * If we are an OTG port initializing in host mode,
+        * check if remote host is driving VBus and bail out in this case.
         */
-       if (config->dr_mode == DR_MODE_OTG &&
-               (readl(&config->reg->phy_vbus_sensors) & VBUS_VLD_STS))
+       if (init == USB_INIT_HOST &&
+           config->dr_mode == DR_MODE_OTG &&
+           (readl(&config->reg->phy_vbus_sensors) & VBUS_VLD_STS)) {
+               printf("tegrausb: VBUS input active; not enabling as host\n");
                return;
+       }
 
-       /*
-        * If not driving, we set the GPIO to enable VBUS. We assume
-        * that the pinmux is set up correctly for this.
-        */
        if (fdt_gpio_isvalid(&config->vbus_gpio)) {
+               int vbus_value;
+
                fdtdec_setup_gpio(&config->vbus_gpio);
-               gpio_direction_output(config->vbus_gpio.gpio,
-                       (config->vbus_gpio.flags & FDT_GPIO_ACTIVE_LOW) ?
-                                0 : 1);
-               debug("set_host_mode: GPIO %d %s\n", config->vbus_gpio.gpio,
-                       (config->vbus_gpio.flags & FDT_GPIO_ACTIVE_LOW) ?
-                               "low" : "high");
+
+               vbus_value = (init == USB_INIT_HOST) ^
+                            !!(config->vbus_gpio.flags & FDT_GPIO_ACTIVE_LOW);
+               gpio_direction_output(config->vbus_gpio.gpio, vbus_value);
+
+               debug("set_up_vbus: GPIO %d %d\n", config->vbus_gpio.gpio,
+                     vbus_value);
        }
 }
 
@@ -294,7 +297,8 @@ static const unsigned *get_pll_timing(void)
 }
 
 /* select the PHY to use with a USB controller */
-static void init_phy_mux(struct fdt_usb *config, uint pts)
+static void init_phy_mux(struct fdt_usb *config, uint pts,
+                        enum usb_init_type init)
 {
        struct usb_ctlr *usbctlr = config->reg;
 
@@ -309,10 +313,16 @@ static void init_phy_mux(struct fdt_usb *config, uint pts)
                clrbits_le32(&usbctlr->port_sc1, STS);
        }
 #else
-       /* Set to Host mode after Controller Reset was done */
+       /* Set to Host mode (if applicable) after Controller Reset was done */
        clrsetbits_le32(&usbctlr->usb_mode, USBMODE_CM_HC,
-                       USBMODE_CM_HC);
-       /* Select PHY interface after setting host mode */
+                       (init == USB_INIT_HOST) ? USBMODE_CM_HC : 0);
+       /*
+        * Select PHY interface after setting host mode.
+        * For device mode, the ordering requirement is not an issue, since
+        * only the first USB controller supports device mode, and that USB
+        * controller can only talk to a UTMI PHY, so the PHY selection is
+        * already made at reset time, so this write is a no-op.
+        */
        clrsetbits_le32(&usbctlr->hostpc1_devlc, PTS_MASK,
                        pts << PTS_SHIFT);
        clrbits_le32(&usbctlr->hostpc1_devlc, STS);
@@ -320,9 +330,10 @@ static void init_phy_mux(struct fdt_usb *config, uint pts)
 }
 
 /* set up the UTMI USB controller with the parameters provided */
-static int init_utmi_usb_controller(struct fdt_usb *config)
+static int init_utmi_usb_controller(struct fdt_usb *config,
+                                   enum usb_init_type init)
 {
-       u32 val;
+       u32 b_sess_valid_mask, val;
        int loop_count;
        const unsigned *timing;
        struct usb_ctlr *usbctlr = config->reg;
@@ -340,6 +351,10 @@ static int init_utmi_usb_controller(struct fdt_usb *config)
        /* Follow the crystal clock disable by >100ns delay */
        udelay(1);
 
+       b_sess_valid_mask = (VBUS_B_SESS_VLD_SW_VALUE | VBUS_B_SESS_VLD_SW_EN);
+       clrsetbits_le32(&usbctlr->phy_vbus_sensors, b_sess_valid_mask,
+                       (init == USB_INIT_DEVICE) ? b_sess_valid_mask : 0);
+
        /*
         * To Use the A Session Valid for cable detection logic, VBUS_WAKEUP
         * mux must be switched to actually use a_sess_vld threshold.
@@ -511,7 +526,7 @@ static int init_utmi_usb_controller(struct fdt_usb *config)
        clrbits_le32(&usbctlr->icusb_ctrl, IC_ENB1);
 
        /* Select UTMI parallel interface */
-       init_phy_mux(config, PTS_UTMI);
+       init_phy_mux(config, PTS_UTMI, init);
 
        /* Deassert power down state */
        clrbits_le32(&usbctlr->utmip_xcvr_cfg0, UTMIP_FORCE_PD_POWERDOWN |
@@ -541,7 +556,8 @@ static int init_utmi_usb_controller(struct fdt_usb *config)
 #endif
 
 /* set up the ULPI USB controller with the parameters provided */
-static int init_ulpi_usb_controller(struct fdt_usb *config)
+static int init_ulpi_usb_controller(struct fdt_usb *config,
+                                   enum usb_init_type init)
 {
        u32 val;
        int loop_count;
@@ -569,7 +585,7 @@ static int init_ulpi_usb_controller(struct fdt_usb *config)
                        ULPI_CLKOUT_PINMUX_BYP | ULPI_OUTPUT_PINMUX_BYP);
 
        /* Select ULPI parallel interface */
-       init_phy_mux(config, PTS_ULPI);
+       init_phy_mux(config, PTS_ULPI, init);
 
        /* enable ULPI transceiver */
        setbits_le32(&usbctlr->susp_ctrl, ULPI_PHY_ENB);
@@ -618,7 +634,8 @@ static int init_ulpi_usb_controller(struct fdt_usb *config)
        return 0;
 }
 #else
-static int init_ulpi_usb_controller(struct fdt_usb *config)
+static int init_ulpi_usb_controller(struct fdt_usb *config,
+                                   enum usb_init_type init)
 {
        printf("No code to set up ULPI controller, please enable"
                        "CONFIG_USB_ULPI and CONFIG_USB_ULPI_VIEWPORT");
@@ -771,23 +788,60 @@ int ehci_hcd_init(int index, enum usb_init_type init,
 
        config = &port[index];
 
+       switch (init) {
+       case USB_INIT_HOST:
+               switch (config->dr_mode) {
+               case DR_MODE_HOST:
+               case DR_MODE_OTG:
+                       break;
+               default:
+                       printf("tegrausb: Invalid dr_mode %d for host mode\n",
+                              config->dr_mode);
+                       return -1;
+               }
+               break;
+       case USB_INIT_DEVICE:
+               if (config->periph_id != PERIPH_ID_USBD) {
+                       printf("tegrausb: Device mode only supported on first USB controller\n");
+                       return -1;
+               }
+               if (!config->utmi) {
+                       printf("tegrausb: Device mode only supported with UTMI PHY\n");
+                       return -1;
+               }
+               switch (config->dr_mode) {
+               case DR_MODE_DEVICE:
+               case DR_MODE_OTG:
+                       break;
+               default:
+                       printf("tegrausb: Invalid dr_mode %d for device mode\n",
+                              config->dr_mode);
+                       return -1;
+               }
+               break;
+       default:
+               printf("tegrausb: Unknown USB_INIT_* %d\n", init);
+               return -1;
+       }
+
        /* skip init, if the port is already initialized */
-       if (config->initialized)
+       if (config->initialized && config->init_type == init)
                goto success;
 
-       if (config->utmi && init_utmi_usb_controller(config)) {
+       if (config->utmi && init_utmi_usb_controller(config, init)) {
                printf("tegrausb: Cannot init port %d\n", index);
                return -1;
        }
 
-       if (config->ulpi && init_ulpi_usb_controller(config)) {
+       if (config->ulpi && init_ulpi_usb_controller(config, init)) {
                printf("tegrausb: Cannot init port %d\n", index);
                return -1;
        }
 
-       set_host_mode(config);
+       set_up_vbus(config, init);
 
        config->initialized = 1;
+       config->init_type = init;
 
 success:
        usbctlr = config->reg;