/* SPDX-License-Identifier: GPL-2.0-only */ #include <delay.h> #include <device/mmio.h> #include <console/console.h> #include <soc/clock.h> #include "usb.h" struct utmip_ctlr { u32 pll0; u32 pll1; u32 xcvr0; u32 bias0; u32 hsrx0; u32 hsrx1; u32 fslsrx0; u32 fslsrx1; u32 tx; u32 misc0; u32 misc1; u32 debounce; u32 batchrgr; u32 spare; u32 xcvr1; u32 bias1; u32 bias_sts; u32 chrgr_debounce; u32 misc_sts; u32 pmc_wakeup; }; check_member(utmip_ctlr, pmc_wakeup, 0x84c - 0x800); struct usb_ctlr { u32 id; u32 _rsv0; u32 host; u32 device; u32 txbuf; /* 0x010 */ u32 rxbuf; u32 _rsv1[58]; u16 ehci_caplen; /* 0x100 */ u16 ehci_version; u32 ehci_hcsp; u32 ehci_hccp; u32 _rsv2[5]; u32 dci_version; /* 0x120 */ u32 dcc_params; u32 extsts; u32 extintr; u32 ehci_usbcmd; /* 0x130 */ u32 ehci_usbsts; u32 ehci_usbintr; u32 ehci_frindex; u32 _rsv3; /* 0x140 */ u32 ehci_periodic_base; u32 ehci_async_base; u32 async_ttsts; u32 burst_size; /* 0x150 */ u32 tx_fill_tuning; u32 _rsv4; u32 icusb_ctrl; u32 ulpi_viewport; /* 0x160 */ u32 _rsv5[4]; u32 ehci_portsc; u32 _rsv6[15]; u32 lpm_ctrl; u32 _rsv7[15]; u32 otgsc; u32 usb_mode; u32 _rsv8; u32 ep_nak; /* 0x200 */ u32 ep_nak_enable; u32 ep_setup; u32 ep_init; u32 ep_deinit; u32 ep_sts; u32 ep_complete; u32 ep_ctrl[16]; u32 _rsv9[105]; u32 suspend_ctrl; /* 0x400 */ u32 vbus_sensors; u32 vbus_wakeup_id; u32 alt_vbus_sts; u32 legacy_ctrl; u32 _rsv10[3]; u32 interpacket_delay; u32 _rsv11[27]; u32 resume_delay; u32 _rsv12; u32 spare; u32 _rsv13[9]; u32 new_ctrl; u32 _rsv14[207]; struct utmip_ctlr utmip; /* 0x800 */ }; check_member(usb_ctlr, utmip, 0x800); /* * Tegra EHCI controllers need their usb_mode, lpm_ctrl and tx_fill_tuning * registers initialized after every EHCI reset and before any other actions * (such as Run/Stop bit) are taken. We reset the controller here, set those * registers and rely on the fact that libpayload doesn't reset EHCI controllers * on initialization for whatever weird reason. This is ugly, fragile, and I * really don't like it, but making this work will require an ugly hack one way * or another so we might as well take the path of least resistance for now. */ static void usb_ehci_reset_and_prepare(struct usb_ctlr *usb, enum usb_phy_type type) { int timeout = 1000; write32(&usb->ehci_usbcmd, 1 << 1); /* Host Controller Reset */ /* TODO: Resets are long, find way to parallelize... or just use XHCI */ while (--timeout && (read32(&usb->ehci_usbcmd) & 1 << 1)) /* wait for HC to reset */; if (!timeout) { printk(BIOS_ERR, "ERROR: EHCI(%p) reset timeout", usb); return; } /* Controller mode: HOST */ write32(&usb->usb_mode, 3 << 0); /* Parallel transceiver selct */ write32(&usb->lpm_ctrl, type << 29); /* Tx FIFO Burst thresh */ write32(&usb->tx_fill_tuning, 0x10 << 16); } /* Assume USBx clocked, out of reset, UTMI+ PLL set up, SAMP_x out of pwrdn */ void usb_setup_utmip(void *usb_base) { struct usb_ctlr *usb = (struct usb_ctlr *)usb_base; /* KHz formulas were guessed from U-Boot constants. Formats unclear. */ int khz = clock_get_pll_input_khz(); /* Stop UTMI+ crystal clock while we mess with its settings */ clrbits32(&usb->utmip.misc1, 1 << 30); /* PHY_XTAL_CLKEN */ udelay(1); /* Take stuff out of pwrdn and add some magic numbers from U-Boot */ write32(&usb->utmip.xcvr0, 0x8 << 25 | /* HS slew rate [10:4] */ 0x3 << 22 | /* HS driver output 'SETUP' [6:4] */ 0 << 21 | /* LS bias selection */ 0 << 18 | /* PDZI pwrdn */ 0 << 16 | /* PD2 pwrdn */ 0 << 14 | /* PD pwrdn */ 1 << 13 | /* (rst) HS receiver terminations */ 0x1 << 10 | /* (rst) LS falling slew rate */ 0x1 << 8 | /* (rst) LS rising slew rate */ 0x4 << 0); /* HS driver output 'SETUP' [3:0] */ write32(&usb->utmip.xcvr1, 0x7 << 18 | /* Termination range adjustment */ 0 << 4 | /* PDDR pwrdn */ 0 << 2 | /* PDCHRP pwrdn */ 0 << 0); /* PDDISC pwrdn */ write32(&usb->utmip.tx, 1 << 19 | /* FS send initial J before sync(?) */ 1 << 16 | /* (rst) Allow stuff error on SoP */ 1 << 9); /* (rst) Check disc only on EoP */ write32(&usb->utmip.hsrx0, 0x2 << 30 | /* (rst) Keep pattern on active */ 1 << 28 | /* (rst) Realign inertia on pkt */ 0x0 << 24 | /* (rst) edges-1 to move sampling */ 0x3 << 21 | /* (rst) squelch delay on EoP */ 0x11 << 15 | /* cycles until IDLE */ 0x10 << 10); /* elastic input depth */ /* U-Boot claims the USBD values for these are used across all UTMI+ * PHYs. That sounds so horribly wrong that I'm not going to implement * it, but keep it in mind if we're ever not using the USBD port. */ write32(&usb->utmip.bias0, 0x1 << 24 | /* HS disconnect detect level [2] */ 1 << 23 | /* (rst) IDPD value */ 1 << 22 | /* (rst) IDPD select */ 1 << 11 | /* (rst) OTG pwrdn */ 0 << 10 | /* bias pwrdn */ 0x1 << 2 | /* HS disconnect detect level [1:0] */ 0x2 << 0); /* HS squelch detect level */ write32(&usb->utmip.bias1, khz / 2200 << 3 | /* bias pwrdn cycles (20us?) */ 1 << 2 | /* (rst) VBUS wakeup pwrdn */ 0 << 0); /* PDTRK pwrdn */ write32(&usb->utmip.debounce, 0xffff << 16 | /* (rst) */ 25 * khz / 10 << 0); /* TODO: what's this, really? */ udelay(1); setbits32(&usb->utmip.misc1, 1 << 30); /* PHY_XTAL_CLKEN */ write32(&usb->suspend_ctrl, 1 << 12 | /* UTMI+ enable */ 0 << 11); /* UTMI+ reset */ usb_ehci_reset_and_prepare(usb, USB_PHY_UTMIP); printk(BIOS_DEBUG, "USB controller @ %p set up with UTMI+ PHY\n",usb_base); }