Rob's Technical Article: How To ULPI in TivaWare for the TM4C1294


In today's article, I will describe how to set up the ULPI USB for the Tiva TM4C1294 microcontroller. I will be discussing the device mode USB, but you can construe from this article what should be done for host or OTG.

The original code for this example is from the TivaWare C Series version 2.1.0.12573. Since then, there have been significant changes to the USBlib, however, I imagine this code still applies, or can be easily made to fit.

We'll be using the Microchip USB3320 phy. The reason why I'm not using a Texas Instruments part is because the TI ULPI interfaces are all 1.8 volt devices, and the Tiva's ULPI interface is 3.3 volts.

I am operating under the assumption that the reader has connected the ULPI properly, which should be pretty clear from the Microchip and Tiva datasheets.

The Microchip USB 3320 datasheet suggests that it can operate at 3.0 volts, however, I had sincere difficulty getting it to operate at that voltage. Also, in my work, I had success having the ULPI feed clocks to the Tiva, rather than have the Tiva generate the clocks. This was easiest for me because the ULPI clock generation was pretty cut and dried; if you hold CLKIN high, the ULPI generates clocks. No fuss, no muss. I admit that once I got things working I didn't want to mess around with it anymore... if it ain't broke, don't fix it.

In actuality, getting the ULPI to work was quite simple, however, the TivaWare USBlib has the ULPI configured to run as an OTG device. Also, the USBlib only supports clocking out of the Tiva; not the other way around, so we'll swap that around here. The Tiva guys did most of the work in getting the ULPI to work, but once they were able to test it, they dropped the ball on supporting it. So the code in the USBlib certainly isn't vestigal, but it takes a tad bit of massaging to get it working for a device.


In the function USBDCDInit in usbdenum.c, the USB clock is configured regardless of the ULPI's configuration. Perhaps one could put a configuration option in to define who generates clocks for whom, but I hard coded it. The original code reads like so:

        //
        // Reset the USB controller.
        //
        MAP_SysCtlPeripheralReset(SYSCTL_PERIPH_USB0);

        //
        // Enable Clocking to the USB controller.
        //
        MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);

        //
        // Turn on USB Phy clock.
        //
        MAP_SysCtlUSBPLLEnable();

        //
        // Set the PLL to USB clock divider.
        //
        USBClockEnable(USB0_BASE, g_ui32PLLDiv, USB_CLOCK_INTERNAL);

        //
        // Configure ULPI support.
        //
        if(g_ui32ULPISupport != USBLIB_FEATURE_ULPI_NONE)
        {
            USBULPIEnable(USB0_BASE);

            if(g_ui32ULPISupport & USBLIB_FEATURE_ULPI_HS)
            {
                ULPIConfigSet(USB0_BASE, ULPI_CFG_HS);
            }
            else
            {
                ULPIConfigSet(USB0_BASE, ULPI_CFG_FS);
            }
        }
        else
        {
            USBULPIDisable(USB0_BASE);
        }

And I changed it to be:

        //
        // Reset the USB controller.
        //
        MAP_SysCtlPeripheralReset(SYSCTL_PERIPH_USB0);

        //
        // Enable Clocking to the USB controller.
        //
        MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);

        //
        // Turn on USB Phy clock.
        //
        MAP_SysCtlUSBPLLEnable();


        // Configure ULPI support.
        if(g_ui32ULPISupport != USBLIB_FEATURE_ULPI_NONE)
        {
            // Set the PLL to USB clock divider... if using the PHY's clock
            USBClockEnable(USB0_BASE, 2, USB_CLOCK_EXTERNAL);
            USBULPIEnable(USB0_BASE);

            if(g_ui32ULPISupport & USBLIB_FEATURE_ULPI_HS)
            {
                ULPIConfigSet(USB0_BASE, ULPI_CFG_HS);
            }
            else
            {
                ULPIConfigSet(USB0_BASE, ULPI_CFG_FS);
            }
        }
        else
        {
            // Set the PLL to USB clock divider.
            USBClockEnable(USB0_BASE, g_ui32PLLDiv, USB_CLOCK_INTERNAL);
            USBULPIDisable(USB0_BASE);
        }

This change is unnecessary if you generate clocks from the Tiva. Also note, I hard coded the PLL divider. This might not be ideal, however, it works with the presented configuration.


Next, I needed to configure the USB3320's registers properly.

I found in my travails that the defaults presented on the datasheet were not the defaults presented within the device. The datasheet defaults do not disable the drivers, but when I inspected the values in the device, I realized that the drivers were disabled. So I changed the code a bit to make sure the drivers were enabled. The original code in usbulpi.c:

void ULPIConfigSet(uint32_t ui32Base, uint32_t ui32Config)
{
    uint8_t ui8Val;

    ui8Val = USBULPIRegRead(ui32Base, ULPI_FCTL);
    ui8Val &= ~(ULPI_FCTL_XCVR_M);
    ui8Val = ui8Val | (uint8_t)ui32Config;

    USBULPIRegWrite(ui32Base, ULPI_FCTL, ui8Val);

    ui8Val = USBULPIRegRead(ui32Base, ULPI_ICTL);
    ui8Val &= ~(ULPI_ICTL_AUTORESUME | ULPI_ICTL_INDINV |
                ULPI_ICTL_INDPASSTHRU);
    ui8Val = ui8Val | (uint8_t)((ui32Config >> 8) & 0xff);

    USBULPIRegWrite(ui32Base, ULPI_ICTL, ui8Val);

    ui8Val = USBULPIRegRead(ui32Base, ULPI_OTGCTL);
    ui8Val &= ~(ULPI_OTGCTL_VBUSINT_EN | ULPI_OTGCTL_VBUSEXT_EN |
                ULPI_OTGCTL_VBUSEXT_IND);
    ui8Val = ui8Val | (uint8_t)((ui32Config >> 16) & 0xff);

    USBULPIRegWrite(ui32Base, ULPI_OTGCTL, ui8Val);
}

I changed that code to:

void ULPIConfigSet(uint32_t ui32Base, uint32_t ui32Config)
{
    uint8_t ui8Val;

    ui8Val = USBULPIRegRead(ui32Base, ULPI_FCTL);
    ui8Val &= ~(ULPI_FCTL_XCVR_M | ULPI_FCTL_OPMODE_NODRV);   // rob is clearing ULPI_FCTL_OPMODE_NODRV
    ui8Val = ui8Val | (uint8_t)ui32Config;

    USBULPIRegWrite(ui32Base, ULPI_FCTL, ui8Val);

    ui8Val = USBULPIRegRead(ui32Base, ULPI_ICTL);
    ui8Val &= ~(ULPI_ICTL_AUTORESUME | ULPI_ICTL_INDINV |
                ULPI_ICTL_INDPASSTHRU);
    ui8Val = ui8Val | (uint8_t)((ui32Config >> 8) & 0xff);

    USBULPIRegWrite(ui32Base, ULPI_ICTL, ui8Val);

    ui8Val = USBULPIRegRead(ui32Base, ULPI_OTGCTL);
    ui8Val &= ~(ULPI_OTGCTL_VBUSINT_EN | ULPI_OTGCTL_VBUSEXT_EN |
                ULPI_OTGCTL_VBUSEXT_IND);
    ui8Val = ui8Val | (uint8_t)((ui32Config >> 16) & 0xff);

    USBULPIRegWrite(ui32Base, ULPI_OTGCTL, ui8Val);
}

This leaves us with the final configuration. The original base code of the following function has been taken from the Bulk USB example, but has weathered a lot of abuse since then. I imagine that it's not really recognizable, anymore, so I won't bother digging it up.

When you set up your device, you'll need to ensure that all the pins for the ULPI interface are allocated properly and that the appropriate options are enabled:

void InitUSB(const bool ulpi)
{
	volatile uint32_t setting;   // must be volatile for reset delay loop.
	USBConfigured = false;
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);

	USBBufferInit(&txBuffer);
	USBBufferInit(&rxBuffer);
	USBStackModeSet(0, eUSBModeForceDevice, 0);

	if(ulpi)
	{
		    // Enable all the peripherals that are used by the ULPI interface.
		    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
		    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOK);
		    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOP);

		    GPIOPinTypeGPIOOutput(GPIO_PORTK_BASE, GPIO_PIN_0);
		    GPIOPinWrite(GPIO_PORTK_BASE, GPIO_PIN_0, 0);  // assert a reset on the ULPI chip.


			/*  At this point, the ULPI is in reset.  A 10ms delay should be sufficient to allow this to complete. */
			/*  The GPIO configurations happening later might take long enough, but to be safe, I'm implementing a delay. */
		    for(setting = 0; setting < 1200; setting++);  // hopefully this makes a long enough pause to reset the ULPI

		    // ULPI Port B pins.
		    GPIOPinConfigure(GPIO_PB2_USB0STP);
		    GPIOPinConfigure(GPIO_PB3_USB0CLK);
		    GPIOPinTypeUSBDigital(GPIO_PORTB_BASE, GPIO_PIN_2 | GPIO_PIN_3);
		    GPIOPadConfigSet(GPIO_PORTB_BASE, GPIO_PIN_2 | GPIO_PIN_3,
		                     GPIO_STRENGTH_12MA, GPIO_PIN_TYPE_STD);

		    // ULPI Port P pins.
		    GPIOPinConfigure(GPIO_PP2_USB0NXT);
		    GPIOPinConfigure(GPIO_PP3_USB0DIR);
		    GPIOPinConfigure(GPIO_PP4_USB0D7);
		    GPIOPinConfigure(GPIO_PP5_USB0D6);
		    GPIOPinTypeUSBDigital(GPIO_PORTP_BASE, GPIO_PIN_2 | GPIO_PIN_3 |
		                                               GPIO_PIN_4 | GPIO_PIN_5);
		    GPIOPadConfigSet(GPIO_PORTP_BASE,
		                     GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5,
		                     GPIO_STRENGTH_12MA, GPIO_PIN_TYPE_STD);

		    // ULPI Port L pins.
		    GPIOPinConfigure(GPIO_PL5_USB0D5);
		    GPIOPinConfigure(GPIO_PL4_USB0D4);
		    GPIOPinConfigure(GPIO_PL3_USB0D3);
		    GPIOPinConfigure(GPIO_PL2_USB0D2);
		    GPIOPinConfigure(GPIO_PL1_USB0D1);
		    GPIOPinConfigure(GPIO_PL0_USB0D0);
		    GPIOPinTypeUSBDigital(GPIO_PORTL_BASE, GPIO_PIN_0 | GPIO_PIN_1 |
		                                               GPIO_PIN_2 | GPIO_PIN_3 |
		                                               GPIO_PIN_4 | GPIO_PIN_5);
		    GPIOPadConfigSet(GPIO_PORTL_BASE,
		                     GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 |
		                     GPIO_PIN_4 | GPIO_PIN_5,
		                     GPIO_STRENGTH_12MA, GPIO_PIN_TYPE_STD);


		    // PK0 - enable the USB phy.  Could be any pin really.
		    GPIOPinWrite(GPIO_PORTK_BASE, GPIO_PIN_0, GPIO_PIN_0);

		    setting = USBLIB_FEATURE_ULPI_HS;

		    // This function is a good read...   You should check it out for more options!
		    USBDCDFeatureSet(0, USBLIB_FEATURE_USBULPI, &setting);

//		ULPIPowerTransceiver(USB_PORT, true);  // necessary if OTG or host.
	}
	else
	{
		// maybe I should still set these pins when the ULPI is not in use??
		GPIOPinTypeUSBAnalog(USB_PORT, USB_PINS);
	}
	USBDBulkInit(0, &bulkDevice);
}

Note that the function USBDCDFeatureSet() in the USBlib is a good thing to read. This will show you all the options that are available for the ULPI, including host and OTG related options.


With that, you should have a working ULPI interface.


By: Rob Stoddard