This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

USB Enumeration in OTG Mode

Hi,

We are working on an AM335x based board which draws heavily from the Beaglebone Black. The USB1 circuit is slightly different however. Notable points are:

--> The ID pin is permanently grounded.

--> The USB VBUS is not connected outside. We have power directly being supplied through a TPS27082 switch to the USB device USB1 connects to.

--> USB_DRVVBUS is left open.

With this circuit, our basic functionality of USB Host mode operation was fine. I also tried to operate as a USB Peripheral, thus asking the other side to operate as Host. For this, the following was done:

--> in the file "am335x-bone-common.dtsi", "dr_mode" was set to "otg" and built. Corresponding DTB was used.

--> Through SW option, I made the IDDIG signal High/Not connected. IDDIG and IDDIG_MUX Bits were set accordingly in the USB1_MODE Register.

--> Upon attaching a Gadget (e.g. - g_mass_storage) - a PC or a BBB recognized our device as a Mass storage gadget.

(The USB connector was changed appropriately in the two different scenarios).

Now, we face the situation of switching from the first condition to the second dynamically, i.e. - a dynamic "role switch". In order to do this, the first hurdle we are facing is that our device is not enumerating the device connected to the other end when our Kernel/DTB is operating in "otg" mode. I understand that MUSB driver in OTG Mode does not start unless a gadget is attached. Upon doing the following:

--> Attaching g_mass_storage and dependent modules.

--> Not changing the ID Pin - which is by default tied to GND.

Upon doing this, the Gadget attaches and goes into Suspend Mode. Upon attaching a device, there is no change in this state. My expectation was that it will start as a USB Host. The first issue we are facing here is how to enumerate a device while our MUSB has started in "OTG" Mode.

While looking at posts on this forum, I found that there is a lot of talk about Capacitance connected to VBUS line. Honestly, I do not know anything about this. I checked our schematic to find that a 100N Capacitor is connected at VBUS side of the external Device. Since the design is such that our board's VBUS and the device VBUS are not connected - I am confused about which side the capacitor should be on.

Any advise would be greatly helpful. It would be very helpful if I know if we have to change our schematic for the VBUS/Capacitance issue.

Thanks in advance for the help,

Best Regards,

Avinash

  • Avinash,

    The OTG mode will not work if USBx_VBUS and USBx_DRVVBUS pins are not connected. Please refer to the TRM for the requirement, also refer to the USB0 port design in the AM335x GP EVM schematics for OTG mode.
  • Hi,

    For OTG mode the ID pin must be connected to the USB connector ID pin. There are other requirements too. Please refer to AM335X GP EVM Schematics for correct connections (http://processors.wiki.ti.com/images/7/77/Am335x_gpevm_zczbaseboard_3H0002_schematic_rev1_5c.zip look at page 12).

  • Hi Biser, Bin,

    Thank you both for your answers. I took some time to go through the TRM, Schematic and other E2E Forums and got some new perspective. Here are my thoughts:

    Firstly, "ID pin must be connected to the USB connector ID Pin". If this happens, then we let the device connecting to us to decide whether we enter Host mode or Peripheral Mode based on whether it ties the ID pin to GND or not. Is this correct? - If it is, the issue is that we are not exposing any connector on our board. We just have a cable which has only the 4 wires of standard USB. So, the entire logic of Host or Peripheral Mode will be controlled by Software (IDDIG and IDDIG_MUX Bits). Hence, is it ok to ignore this point - Is it ok if ID Pin is not connected to the outside?

    Now, I am thinking I got some new insights into the VBUS/DRVVBUS related concept.

    A rather trivial question first - At various points, the TRM makes a reference to a "USBx_VBUSIN" pin. Is this the same as the "USBx_VBUS" pin that is talked about on the forum and also in Bin's answer?  I am operating under the assumption that the above is true.

    My understanding is as follows:

    1. For OTG Host Mode - DRVVBUS should be connected to a power switch input, with Output being USBx_VBUS (USBx_VBUSIN) pin. Thus, when controller is going to Host Mode, it drives DRVVBUS High and waits for USBx_VBUS to become High within 100 ms. The USBx_VBUS should NOT be High already

    2. In Device Mode, the DRVVBUS is low, but the USBx_VBUS will be high because the other device (who is now USB Host) will be driving it High.

    Does the above understanding seem correct?

    Now in our device, this USBx_VBUS is not directly connected to the external VBUS (to the other device). The external VBUS is a separate circuit wherein power can be transferred to the device even when AM335x is in deep sleep mode, switched off, rebooting, etc. I understand that the DRVVBUS is having an effect on the USBx_VBUS line of the AM335x. To the best of my understanding of HNP - it did not seem to matter to the other device (there were no pulses or signals on the VBUS line which should have to be driven by DRVVBUS).

    Another point is that while operating in device mode, though the DRVVBUS is low - we need USBx_VBUS High, when it is not connected to the other device VBUS. Is it possible (somewhat easy) to implement this in software through a GPIO or any other method)? We can have a GPIO with XOR of something of that sort connecting the DRVVBUS and GPIO together to the USBx_VBUS. I am really interested in knowing if the timing requirements are too difficult to be met by a software implementation.

    Again, does the above understanding seem correct?

    If my assumptions and conclusions are off mark in any of the above - requesting either of you to please suggest any material where I could read more about the relation between DRVVBUS, VBUS (Both out and In).

    Thank you very much for your continued help. Thank you for the patience in going through the long posts.

    Best Regards,

    Avinash

  • 1) DRVVBUS should be connected to the enable pin of a power switch, not the input. I feel like you know this but I wanted to be clear. The rest of #1 is correct.

    2) Correct. This is how the SoC knows when to signal attach to the bus when acting as a device. If we never see VBUS, we will never know that we need to activate our pullups and we will never begin the attach process. This is the main requirement for USB_VBUS in Device mode.

    For Host mode, there are two requirements...first that VBUS is not present prior to our asserting DRVVBUS. This ensures that there will not be VBUS contention in the event that we are already connected to another host which is already providing VBUS. Second is that VBUS must reach 4.7V within 100ms after we assert DRVVBUS or we will assume that the power switch is OC or malfunctioning. We will de-assert DRVVBUS in this case to prevent system damage.

    As for the rest of this, you are operating outside our supported implementation scenarios....for OTG implementations we require complete control over the state of VBUS via the DRVVBUS pin. The ID pin can be spoofed in SW, but there are HW requirements (outlined above) surrounding DRVVBUS and USB_VBUS that cannot.

  • Hi DK,

    Thank you for your detailed explanation. Taking a cue from your second point, I added a software hack to read USB1_STAT Register (DRVVBUS) and accordingly SET the VBUSIN through Software (Forgive me for a mistake in my initial description - we have GPIO control over VBUSIN and it is not tied to 5V Supply).

    After adding the Hack for both DRVVBUS High and Low - we are able to do a role switch with an Apple device (iPhone). However, we are working towards circuit modification if needed, etc.

    Thanks for all the help!

    Regards,

    Avinash

  • This code snippet would be quite useful to me (and presumably a handful of others who don't have the DRVVBUS available.) Do you think you could provide it?
  • Hi Eric,

    The situation we were working is as follows:

    1. The DRVVBUS was left open (circuit design was done assuming we will never operate in OTG/Device Mode).

    2. A small FET based circuit was there which took input of a GPIO to provide/cut off 5V power supply to VBUSIN pin.

    All I did in the Software was to try and mimick the HW behaviour. I ran a background process which continuously read the DRVVBUS state and SET/CLEARED the GPIO controlling VBUSIN accordingly.

    Operation logic would be something like this:

    1. Start the Kernel in OTG Mode (DRVVBUS will be low by default).

    2. Set GPIO - 0, so that VBUSIN goes down to 0.

    3. Run a program - which continuously reads the REG - (0x47401818, USB1_STAT). Its Bit-0 shows the current status of DRVVBUS.

    4. insmod g_zero.ko (Any gadget driver will do - This will kick start the MUSB driver in OTG Mode to start operation in either Host/Device Mode based on the ID Pin) At this point, if the ID Pin is tied to GND or ID Signal is 0 - the DRVVBUS will be raised by the MUSB Controller.

    5. Upon seeing this rise in DRVVBUS, my background program automatically raises VBUSIN, thus satisfying the condition that the controller should see a HIGH on VBUSIN within 100 ms of raising DRVVBUS.

    6. Now, any USB Device which is connected can get enumerated.

    The above process is a hack, and a rather dirty one. Firstly, the background process is CPU hungry, and also if you plan on continuously disconnecting and connecting your USB Device, you should have such a background process running continuously.

    It's code is pretty straightforward:

    ptr_acc - is a virtual address for (0x47401818) after doing necessary mmap.

    while (1)
    {
    if ( (*(ptr_acc) & 0x01) == 0)
    {
    printf (" DRVVBUS is 0 - Waiting for it to become 1 ... \n");
    while ( (*(ptr_acc) & 0x01) == 0)
    {
    usleep (10000);
    }

    /* If we come here, then DRVVBUS was 0 and has now become 1. We should raise USB1_VBUSIN. */
    system (" echo 1 > /sys/class/gpio/gpio0/value");
    printf (" USB1_VBUS - Made HIGH ... \n");
    }

    if ( (*(ptr_acc) & 0x01) == 1)
    {
    printf (" DRVVBUS is 1 - Waiting for it to become 0 ... \n");
    while ( (*(ptr_acc) & 0x01) == 0x01)
    {
    usleep (10000);
    }

    /* If we come here, then DRVVBUS was 1 and has now become 0. We should pull USB1_VBUSIN Down. */
    system (" echo 0 > /sys/class/gpio/gpio0/value");
    printf (" USB1_VBUSIN - Made LOW ... \n");
    }
    }

    However, the above code will not work if you do not have GPIO or some other control over VBUSIN. The other thing we did was Apple role switch - that code is bound by an NDA with Apple - the MFi program, if you may have heard it.

    Regards,

    Avinash

  • Thanks so much! Turns out this is exactly what I needed.

    Here is a more fleshed-out piece of code that effectively re-maps DRVVBUS to a GPIO pin. It is useful if you don't have access to the dedicated DRVVBUS pin for any reason, and need that functionality on another pin. You can either connect that new pin to VBUSIN or to the enable pin of the VBUS switch.

    Like you said, this is somewhat of a hack, and uses unnecessary CPU and memory. It should really be replaced by a kernel module that is triggered on the VBUS interrupt (USB_8, on page 2624 of the reference manual.) However, it appears to work quite well.

    // AM335x DRVVBUS to GPIO remapper
    // by Eric Van Albert
    
    // This program runs in user-space and effectively re-maps the DRVVBUS line of
    // the USB PHY to any GPIO pin.
    
    // It uses polling and should probably be re-written as a kernel module that
    // uses interrupts.
    
    // This code is released into the public domain.
    
    #include <stdio.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    
    #define MAP_SIZE 4096UL
    #define MAP_MASK (MAP_SIZE-1)
    
    // USB PHY to remap
    #define USB_BASE 0x47400000UL
    
    // GPIO to remap to
    #define GPIO_BASE 0x481AE000UL
    #define GPIO_PIN 0
    
    // Frequency with which to poll
    #define USECS_SLEEP 10000
    
    // These probably don't need to change
    #define DRVVBUS_PIN 0
    #define DRVVBUS_ADDR 0x1818UL
    #define GPIO_OE 0x0134UL
    #define GPIO_CLEARDATAOUT 0x0190UL
    #define GPIO_SETDATAOUT 0x0194UL
    
    //#define DEBUG
    
    int main()
    {
        int mem_fno;
        unsigned long usb_addr;
        unsigned long gpio_addr;
    
        mem_fno = open("/dev/mem", O_RDWR | O_SYNC);
        if(mem_fno < 0)
        {
            fprintf(stderr, "Could not open /dev/mem\n");
            return 1;
        }
    
        usb_addr = (unsigned long)mmap(0, MAP_SIZE, PROT_READ, MAP_SHARED, mem_fno,
            (USB_BASE+DRVVBUS_ADDR) & (~MAP_MASK));
    
        if(!usb_addr)
        {
            fprintf(stderr, "Could not map USB memory\n");
            return 1;
        }
    
        gpio_addr=(unsigned long)mmap(0, MAP_SIZE,PROT_READ | PROT_WRITE,
            MAP_SHARED, mem_fno,GPIO_BASE & (~MAP_MASK));
    
        if(!gpio_addr)
        {
            fprintf(stderr, "Could not map GPIO memory\n");
            return 1;
        }
    
        #ifdef DEBUG
        printf("Setting GPIO as output...\n");
        #endif
    
        (*((int*)(gpio_addr + (GPIO_OE & MAP_MASK)))) &= ~(1 << GPIO_PIN);
    
        int last = -1;
        int drvvbus_state;
        for(;;)
        {
            drvvbus_state = (*((int*)(usb_addr + (DRVVBUS_ADDR & MAP_MASK)))) &
                (1 << DRVVBUS_PIN);
            if(drvvbus_state != last)
            {
                if(drvvbus_state)
                {
                    #ifdef DEBUG
                    printf("DRVVBUS = 1\n");
                    #endif
                    (*(int*)(gpio_addr + (GPIO_SETDATAOUT & MAP_MASK))) =
                        (1 << GPIO_PIN);
                }
                else
                {
                    #ifdef DEBUG
                    printf("DRVVBUS = 0\n");
                    #endif
                    (*(int*)(gpio_addr + (GPIO_CLEARDATAOUT & MAP_MASK))) =
                        (1 << GPIO_PIN);
                }
                last = drvvbus_state;
            }
            usleep(USECS_SLEEP);
        }
    }
    
    

  • Hi Eric,

    Just to continue this ongoing discussion, we recently spent some time implementing a non-CPU hungry solution for this. We are using the "udev" daemon to detect disconnect events and immediately, we poll the DRVVBUS - for it to go low and then go HIGH. We follow suit with the GPIO controlling the VBUSIN.

    Taking a cure from your answer, I actually went ahead with USB_8 interrupt implementation but I was able to get a USB_8 interrupt only on a LOW->HIGH transition of the DRVVBUS and not the other way around.
    Anyway, our current solution makes sure that we are continuously polling only for about 60-80 ms everytime there is a USB disconnect which is acceptable for our Use-case.

    Thanks and Regards,
    Avinash