Soft Linux kernel hacking for dumping ULPI commands to USB PHY

This post was written by eli on September 11, 2018
Posted Under: Linux kernel,USB,Zynq

Ever wanted to see how the a Linux USB host talks with its PHY with ULPI commands? Probably not. But if you do, here’s how I did it on a Zynq device, connected to an USB3320 USB 2.0 PHY chip. Note that:

  • The relevant sources must be compiled into the kernel. Modules are loaded too late. The choice of PHY frontend is made when the USB driver is initialized, and if the relevant driver isn’t handy, a generic PHY is picked instead…
  • … which is most likely as good. In retrospect, there’s is very little reason to load the actual driver.
  • In particular, my system works great without the dedicated USB PHY driver.

So it’s about adding a plain pr_info() into the kernel’s drivers/usb/phy/phy-ulpi-viewport.c, so it prints every ULPI register write command to the kernel log. Added code marked in red:

static int ulpi_viewport_write(struct usb_phy *otg, u32 val, u32 reg)
{
	int ret;
	void __iomem *view = otg->io_priv;

	pr_info("ulpi_viewport_write: reg 0x%04x = 0x%02x\n",
		reg, val);

	writel(ULPI_VIEW_WAKEUP | ULPI_VIEW_WRITE, view);
	ret = ulpi_viewport_wait(view, ULPI_VIEW_WAKEUP);
	if (ret)
		return ret;

	writel(ULPI_VIEW_RUN | ULPI_VIEW_WRITE | ULPI_VIEW_DATA_WRITE(val) |
						 ULPI_VIEW_ADDR(reg), view);

	return ulpi_viewport_wait(view, ULPI_VIEW_RUN);
}

And that’s it. One can also cover the ulpi_viewport_read() method in the same way, but it wasn’t important to me (I wanted to the powering on of Vbus).

The relevant part in my device tree read:

	usb_phy0: phy0 {
		compatible = "ulpi-phy";
		#phy-cells = <0>;
		reg = <0xe0002000 0x1000>;
		view-port = <0x0170>;
		drv-vbus;
	};

	usb0: usb@e0002000 {
		compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2";
		clocks = <&clkc 28>;
		interrupt-parent = <&ps7_scugic_0>;
		interrupts = <0 21 4>;
		reg = <0xe0002000 0x1000>;
		phy_type = "ulpi";
		dr_mode = "host";
		usb-phy = <&usb_phy0>;
	};

And this is what I got in the dmesg log:

[    1.396317] ulpi_phy_probe() invoked
[    1.399968] ulpi_phy_probe() returns successfully
[    1.405148] ehci_hcd: USB 2.0 'Enhanced' Host Controller (EHCI) Driver
[    1.418505] ehci-pci: EHCI PCI platform driver
[    1.429765] ehci-platform: EHCI generic platform driver
[    1.441924] ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
[    1.454951] ohci-pci: OHCI PCI platform driver
[    1.466237] ohci-platform: OHCI generic platform driver
[    1.478504] uhci_hcd: USB Universal Host Controller Interface driver
[    1.492047] usbcore: registered new interface driver usb-storage
[    1.505250] chipidea-usb2 e0002000.usb: ci_hdrc_usb2_probe invoked
[    1.518410] e0002000.usb supply vbus not found, using dummy regulator
[    1.532049] ci_hdrc ci_hdrc.0: ChipIdea HDRC found, revision: 22, lpm: 0; cap: e0d0a100 op: e0d0a140
[    1.532062] ulpi_init() invoked
[    1.542033] ULPI transceiver vendor/product ID 0x0424/0x0007
[    1.554611] Found SMSC USB3320 ULPI transceiver.
[    1.566118] ulpi_viewport_write: reg 0x0016 = 0x55
[    1.577825] ulpi_viewport_write: reg 0x0016 = 0xaa
[    1.589383] ULPI integrity check: passed.
[    1.600057] ulpi_viewport_write: reg 0x000a = 0x06
[    1.611516] ulpi_viewport_write: reg 0x0007 = 0x00
[    1.622872] ulpi_viewport_write: reg 0x0004 = 0x41
[    1.634203] ci_hdrc ci_hdrc.0: It is OTG capable controller
[    1.634233] ci_hdrc ci_hdrc.0: EHCI Host Controller
[    1.645628] ci_hdrc ci_hdrc.0: new USB bus registered, assigned bus number 1
[    1.672482] ci_hdrc ci_hdrc.0: USB 2.0 started, EHCI 1.00
[    1.684475] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002
[    1.697770] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    1.711521] usb usb1: Product: EHCI Host Controller
[    1.722832] usb usb1: Manufacturer: Linux 4.4.30-xillinux-2.0 ehci_hcd
[    1.735797] usb usb1: SerialNumber: ci_hdrc.0
[    1.747236] hub 1-0:1.0: USB hub found
[    1.757421] hub 1-0:1.0: 1 port detected
[    1.767881] ulpi_viewport_write: reg 0x000a = 0x67

The log entries in green above are just some other similar debug outputs I made, and they pretty much explain themselves.

Did you note that the ULPI was detected by vendor ID / product ID? It’s for real. These were obtained by ULPI registers read (not shown above). I’m not all that convinced that this detection made any difference, except for printing out the name of the device.

As for the meaning of these ulpi_viewport_write dumps, most is pretty boring: The first two writes to address 0x16 do nothing. It’s a scratch pad register. Most likely used by the driver to test the ULPI interface.

The following three writes just assign the default values. So this does effectively nothing as well.

The last write to register 0x0a (OTG register) sets bits 6, 5 and 0, which are DrvVbusExternal, DrvVbus and IdPullup. The interesting part to me was DrvVbusExternal and DrvVbus, because setting any of these two (or both) causes the chip’s CPEN pin to go high, which turns on the power supply for Vbus. This is the point where the USB port starts behaving like a host and feeds power.

Add a Comment

required, use real name
required, will not be published
optional, your blog address