/* $NetBSD: tegra210_xusbpad.c,v 1.16 2021/08/07 16:18:44 thorpej Exp $ */

/*-
 * Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: tegra210_xusbpad.c,v 1.16 2021/08/07 16:18:44 thorpej Exp $");

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/kernel.h>

#include <arm/nvidia/tegra_reg.h>
#include <arm/nvidia/tegra_var.h>
#include <arm/nvidia/tegra_xusbpad.h>

#include <dev/fdt/fdtvar.h>

#define	XUSB_PADCTL_USB2_PAD_MUX_REG		0x04
#define	 XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD			__BITS(19,18)
#define	  XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB		1

#define	XUSB_PADCTL_VBUS_OC_MAP_REG		0x18
#define	 XUSB_PADCTL_VBUS_OC_MAP_VBUS_ENABLE(n)			__BIT((n) * 5)

#define	XUSB_PADCTL_OC_DET_REG			0x1c
#define	 XUSB_PADCTL_OC_DET_OC_DETECTED_VBUS_PAD(n)		__BIT(12 + (n))
#define	 XUSB_PADCTL_OC_DET_OC_DETECTED(n)			__BIT(8 + (n))
#define	 XUSB_PADCTL_OC_DET_SET_OC_DETECTED(n)			__BIT(0 + (n))

#define	XUSB_PADCTL_ELPG_PROGRAM_1_REG		0x24
#define	 XUSB_PADCTL_ELPG_PROGRAM_1_AUX_MUX_LP0_VCORE_DOWN	__BIT(31)
#define	 XUSB_PADCTL_ELPG_PROGRAM_1_AUX_MUX_LP0_CLAMP_EN_EARLY	__BIT(30)
#define	 XUSB_PADCTL_ELPG_PROGRAM_1_AUX_MUX_LP0_CLAMP_EN	__BIT(29)
#define	 XUSB_PADCTL_ELPG_PROGRAM_1_SSPn_ELPG_VCORE_DOWN(n)	__BIT((n) * 3 + 2)
#define	 XUSB_PADCTL_ELPG_PROGRAM_1_SSPn_ELPG_CLAMP_EN_EARLY(n)	__BIT((n) * 3 + 1)
#define	 XUSB_PADCTL_ELPG_PROGRAM_1_SSPn_ELPG_CLAMP_EN(n)	__BIT((n) * 3 + 0)

#define	XUSB_PADCTL_USB3_PAD_MUX_REG		0x28
#define	 XUSB_PADCTL_USB3_PAD_MUX_FORCE_SATA_PAD_IDDQ_DISABLE(n)	__BIT(8 + (n))
#define	 XUSB_PADCTL_USB3_PAD_MUX_FORCE_PCIE_PAD_IDDQ_DISABLE(n)	__BIT(1 + (n))

#define	XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADn_CTL_1_REG(n)	(0x84 + (n) * 0x40)
#define	 XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADn_CTL_1_VREG_LEV	__BITS(8,7)
#define	 XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADn_CTL_1_VREG_FIX18	__BIT(6)

#define	XUSB_PADCTL_USB2_OTG_PADn_CTL_0_REG(n)	(0x88 + (n) * 0x40)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_0_PD_ZI			__BIT(29)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_0_PD2			__BIT(27)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_0_PD			__BIT(26)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_0_HS_CURR_LEVEL		__BITS(5,0)

#define	XUSB_PADCTL_USB2_OTG_PADn_CTL_1_REG(n)	(0x8c + (n) * 0x40)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_1_RPD_CTRL		__BITS(30,26)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_1_TERM_RANGE_ADJ		__BITS(6,3)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_1_PD_DR			__BIT(2)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_1_PD_DISC_OVRD		__BIT(1)
#define	 XUSB_PADCTL_USB2_OTG_PADn_CTL_1_PD_CHRP_OVRD		__BIT(0)

#define	XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_REG	0x284
#define	 XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_PD			__BIT(11)
#define	 XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_HS_DISCON_LEVEL	__BITS(5,3)
#define	 XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_HS_SQUELCH_LEVEL	__BITS(2,0)

#define	XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_REG	0x288
#define	 XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_PD_TRK			__BIT(26)
#define	 XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_TRK_DONE_RESET_TIMER	__BITS(25,19)
#define	 XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_TRK_START_TIMER	__BITS(18,12)

#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG		0x360
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_PSDIV	__BITS(29,28)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_NDIV	__BITS(27,20)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_MDIV	__BITS(17,16)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_LOCKDET_STATUS	__BIT(15)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_MODE		__BITS(9,8)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_BYPASS_ENABLE	__BIT(7)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREERUN_ENABLE	__BIT(6)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_PWR_OVRD		__BIT(4)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_ENABLE		__BIT(3)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_SLEEP		__BITS(2,1)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_1_IDDQ		__BIT(0)
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_2_REG		0x364
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_CTRL		__BITS(27,4)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_RESET	__BIT(3)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_OVRD		__BIT(2)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_DONE		__BIT(1)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_EN		__BIT(0)
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_3_REG		0x368
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_3_LOCKDET_CTRL	__BITS(27,4)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_3_LOCKDET_RESET	__BIT(0)
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REG		0x36c
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_4_TCLKOUT_EN	__BIT(28)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_4_CLKDIST_CTRL	__BITS(23,20)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_4_TXCLKREF_EN	__BIT(15)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_4_TXCLKREF_SEL	__BITS(13,12)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REFCLKBUF_EN	__BIT(8)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REFCLK_SEL	__BITS(7,4)
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_5_REG		0x370
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_5_DCO_CTRL		__BITS(23,16)
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_6_REG		0x374
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_7_REG		0x378
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG		0x37c
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_DONE	__BIT(31)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_OVRD	__BIT(15)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_CLK_EN	__BIT(13)
#define	 XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_EN		__BIT(12)
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_9_REG		0x380
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_10_REG		0x384
#define	XUSB_PADCTL_UPHY_PLL_P0_CTL_11_REG		0x388

#define	XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG		0x860
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_PSDIV	__BITS(29,28)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_NDIV	__BITS(27,20)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_MDIV	__BITS(17,16)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_LOCKDET_STATUS	__BIT(15)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_MODE		__BITS(9,8)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_BYPASS_ENABLE	__BIT(7)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREERUN_ENABLE	__BIT(6)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_PWR_OVRD		__BIT(4)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_ENABLE		__BIT(3)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_SLEEP		__BITS(2,1)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_1_IDDQ		__BIT(0)
#define	XUSB_PADCTL_UPHY_PLL_S0_CTL_2_REG		0x864
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_CTRL		__BITS(27,4)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_RESET	__BIT(3)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_OVRD		__BIT(2)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_DONE		__BIT(1)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_EN		__BIT(0)
#define	XUSB_PADCTL_UPHY_PLL_S0_CTL_3_REG		0x868
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_3_LOCKDET_CTRL	__BITS(27,4)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_3_LOCKDET_RESET	__BIT(0)
#define	XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REG		0x86c
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_4_TCLKOUT_EN	__BIT(28)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_4_CLKDIST_CTRL	__BITS(23,20)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_4_TXCLKREF_EN	__BIT(15)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_4_TXCLKREF_SEL	__BITS(13,12)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REFCLKBUF_EN	__BIT(8)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REFCLK_SEL	__BITS(7,4)
#define	XUSB_PADCTL_UPHY_PLL_S0_CTL_5_REG		0x870
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_5_DCO_CTRL		__BITS(23,16)
#define	XUSB_PADCTL_UPHY_PLL_S0_CTL_6_REG		0x874
#define	XUSB_PADCTL_UPHY_PLL_S0_CTL_7_REG		0x878
#define	XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG		0x87c
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_DONE	__BIT(31)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_OVRD	__BIT(15)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_CLK_EN	__BIT(13)
#define	 XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_EN		__BIT(12)

#define	XUSB_PADCTL_UPHY_USB3_PADn_ECTL_1_REG(n)	(0xa60 + (n) * 0x40)
#define	 XUSB_PADCTL_UPHY_USB3_PADn_ECTL_2_TX_TERM_CTRL		__BITS(19,18)

#define	XUSB_PADCTL_UPHY_USB3_PADn_ECTL_2_REG(n)	(0xa64 + (n) * 0x40)
#define	 XUSB_PADCTL_UPHY_USB3_PADn_ECTL_2_RX_CTLE		__BITS(15,0)

#define	XUSB_PADCTL_UPHY_USB3_PADn_ECTL_3_REG(n)	(0xa68 + (n) * 0x40)

#define	XUSB_PADCTL_UPHY_USB3_PADn_ECTL_4_REG(n)	(0xa6c + (n) * 0x40)
#define	 XUSB_PADCTL_UPHY_USB3_PADn_ECTL_4_RX_CDR_CTRL		__BITS(31,16)

#define	XUSB_PADCTL_UPHY_USB3_PADn_ECTL_6_REG(n)	(0xa74 + (n) * 0x40)

#define	FUSE_SKUCALIB_REG				0xf0
#define	 FUSE_SKUCALIB_HS_CURR_LEVEL(n)			\
	 ((n) == 0 ? __BITS(6,0) : __BITS(((n) - 1) * 6 + 17, ((n) - 1) * 6 + 11))
#define	 FUSE_SKUCALIB_HS_TERM_RANGE_ADJ			__BITS(10,7)

#define	FUSE_USBCALIB_REG				0x250
#define	 FUSE_USBCALIB_EXT_RPD_CTRL			__BITS(4,0)

struct tegra210_xusbpad_softc {
	device_t		sc_dev;
	int			sc_phandle;
	bus_space_tag_t		sc_bst;
	bus_space_handle_t	sc_bsh;

	struct fdtbus_reset	*sc_rst;

	bool			sc_enabled;
};

struct tegra210_xusbpad_phy_softc {
	device_t		sc_dev;
	int			sc_phandle;
	struct tegra210_xusbpad_softc *sc_xusbpad;
};

struct tegra210_xusbpad_phy_attach_args {
	struct tegra210_xusbpad_softc	*paa_xusbpad;
	int			paa_phandle;
};

#define	RD4(sc, reg)					\
	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
#define	WR4(sc, reg, val)				\
	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
#define	SETCLR4(sc, reg, set, clr)			\
	tegra_reg_set_clear((sc)->sc_bst, (sc)->sc_bsh, (reg), (set), (clr))

static const char * tegra210_xusbpad_usb2_func[] = { "snps", "xusb", "uart" };
static const char * tegra210_xusbpad_hsic_func[] = { "snps", "xusb" };
static const char * tegra210_xusbpad_pcie_func[] = { "pcie-x1", "usb3-ss", "sata", "pcie-x4" };

static void
tegra210_xusbpad_uphy_enable_pcie(struct tegra210_xusbpad_softc *sc)
{
	uint32_t val;
	int retry;

	/* UPHY PLLs */
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_REG,
	    __SHIFTIN(0x136, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_CTRL),
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_CTRL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_5_REG,
	    __SHIFTIN(0x2a, XUSB_PADCTL_UPHY_PLL_P0_CTL_5_DCO_CTRL),
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_5_DCO_CTRL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_1_PWR_OVRD, 0);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_OVRD, 0);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_OVRD, 0);

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REG,
	    __SHIFTIN(0, XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REFCLK_SEL),
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REFCLK_SEL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REG,
	    __SHIFTIN(2, XUSB_PADCTL_UPHY_PLL_P0_CTL_4_TXCLKREF_SEL),
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_4_TXCLKREF_SEL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_4_TXCLKREF_EN, 0);

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG,
	    __SHIFTIN(0, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_MDIV),
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_MDIV);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG,
	    __SHIFTIN(0x19, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_NDIV),
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_NDIV);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG,
	    __SHIFTIN(0, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_PSDIV),
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_1_FREQ_PSDIV);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG,
	    0, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_IDDQ);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG,
	    0, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_SLEEP);

	delay(20);

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_4_REFCLKBUF_EN, 0);

	/* Calibration */
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_EN, 0);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_DONE) != 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout calibrating UPHY PLL (1)\n");
		return;
	}

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_REG,
	    0, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_EN);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_DONE) == 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout calibrating UPHY PLL (2)\n");
		return;
	}

	/* Enable the PLL */
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_1_ENABLE, 0);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_P0_CTL_1_LOCKDET_STATUS) != 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout enabling UPHY PLL\n");
		return;
	}

	/* RCAL */
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_EN, 0);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG,
	    XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_CLK_EN, 0);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_DONE) != 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout calibrating UPHY PLL (3)\n");
		return;
	}

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG,
	    0, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_EN);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_DONE) == 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout calibrating UPHY PLL (4)\n");
		return;
	}

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG,
	    0, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_CLK_EN);

	tegra210_car_xusbio_enable_hw_control();

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_REG,
	    0, XUSB_PADCTL_UPHY_PLL_P0_CTL_1_PWR_OVRD);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_REG,
	    0, XUSB_PADCTL_UPHY_PLL_P0_CTL_2_CAL_OVRD);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_REG,
	    0, XUSB_PADCTL_UPHY_PLL_P0_CTL_8_RCAL_OVRD);

	delay(1);

	tegra210_car_xusbio_enable_hw_seq();
}

static void
tegra210_xusbpad_lane_enable_pcie(struct tegra210_xusbpad_softc *sc, int index)
{
	tegra210_xusbpad_uphy_enable_pcie(sc);

	SETCLR4(sc, XUSB_PADCTL_USB3_PAD_MUX_REG,
	    XUSB_PADCTL_USB3_PAD_MUX_FORCE_PCIE_PAD_IDDQ_DISABLE(index), 0);
}

static void
tegra210_xusbpad_lane_enable_usb2(struct tegra210_xusbpad_softc *sc, int index)
{
	uint32_t skucalib, usbcalib;

	skucalib = tegra_fuse_read(FUSE_SKUCALIB_REG);
	const u_int hs_curr_level = __SHIFTOUT(skucalib, FUSE_SKUCALIB_HS_CURR_LEVEL((u_int)index));
	const u_int hs_term_range_adj = __SHIFTOUT(skucalib, FUSE_SKUCALIB_HS_TERM_RANGE_ADJ);

	usbcalib = tegra_fuse_read(FUSE_USBCALIB_REG);
	const u_int ext_rpd_ctrl = __SHIFTOUT(usbcalib, FUSE_USBCALIB_EXT_RPD_CTRL);

	SETCLR4(sc, XUSB_PADCTL_USB2_PAD_MUX_REG,
	    __SHIFTIN(XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB,
		      XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD),
	    XUSB_PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD);

	SETCLR4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_REG,
	    __SHIFTIN(0x7, XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_HS_DISCON_LEVEL) |
	    __SHIFTIN(0x0, XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_HS_SQUELCH_LEVEL),
	    XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_HS_DISCON_LEVEL |
	    XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_HS_SQUELCH_LEVEL);
	SETCLR4(sc, XUSB_PADCTL_USB2_OTG_PADn_CTL_0_REG(index),
	    __SHIFTIN(hs_curr_level, XUSB_PADCTL_USB2_OTG_PADn_CTL_0_HS_CURR_LEVEL),
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_0_HS_CURR_LEVEL |
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_0_PD |
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_0_PD2 |
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_0_PD_ZI);
	SETCLR4(sc, XUSB_PADCTL_USB2_OTG_PADn_CTL_1_REG(index),
	    __SHIFTIN(hs_term_range_adj, XUSB_PADCTL_USB2_OTG_PADn_CTL_1_TERM_RANGE_ADJ) |
	    __SHIFTIN(ext_rpd_ctrl, XUSB_PADCTL_USB2_OTG_PADn_CTL_1_RPD_CTRL),
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_1_TERM_RANGE_ADJ |
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_1_RPD_CTRL |
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_1_PD_DR |
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_1_PD_CHRP_OVRD |
	    XUSB_PADCTL_USB2_OTG_PADn_CTL_1_PD_DISC_OVRD);
	SETCLR4(sc, XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADn_CTL_1_REG(index),
	    XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADn_CTL_1_VREG_FIX18,
	    XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPADn_CTL_1_VREG_LEV);

	SETCLR4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_REG,
	    __SHIFTIN(0x1e, XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_TRK_START_TIMER) |
	    __SHIFTIN(0xa, XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_TRK_DONE_RESET_TIMER),
	    XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_TRK_START_TIMER |
	    XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_TRK_DONE_RESET_TIMER);
	SETCLR4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_REG,
	    0, XUSB_PADCTL_USB2_BIAS_PAD_CTL_0_PD);
	delay(1);
	SETCLR4(sc, XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_REG,
	    0, XUSB_PADCTL_USB2_BIAS_PAD_CTL_1_PD_TRK);
	delay(50);
}


static void
tegra210_xusbpad_uphy_enable_sata(struct tegra210_xusbpad_softc *sc)
{
	uint32_t val;
	int retry;

	/* UPHY PLLs */
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_REG,
	    __SHIFTIN(0x136, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_CTRL),
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_CTRL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_5_REG,
	    __SHIFTIN(0x2a, XUSB_PADCTL_UPHY_PLL_S0_CTL_5_DCO_CTRL),
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_5_DCO_CTRL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_1_PWR_OVRD, 0);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_OVRD, 0);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_OVRD, 0);

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REG,
	    __SHIFTIN(0, XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REFCLK_SEL),
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REFCLK_SEL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REG,
	    __SHIFTIN(0, XUSB_PADCTL_UPHY_PLL_S0_CTL_4_TXCLKREF_SEL),
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_4_TXCLKREF_SEL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_4_TXCLKREF_EN, 0);

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG,
	    __SHIFTIN(0, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_MDIV),
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_MDIV);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG,
	    __SHIFTIN(0x1e, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_NDIV),
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_NDIV);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG,
	    __SHIFTIN(0, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_PSDIV),
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_1_FREQ_PSDIV);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG,
	    0, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_IDDQ);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG,
	    0, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_SLEEP);

	delay(20);

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_4_REFCLKBUF_EN, 0);

	/* Calibration */
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_EN, 0);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_DONE) != 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout calibrating UPHY PLL (1)\n");
		return;
	}

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_REG,
	    0, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_EN);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_DONE) == 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout calibrating UPHY PLL (2)\n");
		return;
	}

	/* Enable the PLL */
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_1_ENABLE, 0);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_S0_CTL_1_LOCKDET_STATUS) != 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout enabling UPHY PLL\n");
		return;
	}

	/* RCAL */
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_EN, 0);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG,
	    XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_CLK_EN, 0);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_DONE) != 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout calibrating UPHY PLL (3)\n");
		return;
	}

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG,
	    0, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_EN);
	for (retry = 10000; retry > 0; retry--) {
		delay(2);
		val = RD4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG);
		if ((val & XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_DONE) == 0)
			break;
	}
	if (retry == 0) {
		aprint_error_dev(sc->sc_dev, "timeout calibrating UPHY PLL (4)\n");
		return;
	}

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG,
	    0, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_CLK_EN);

	tegra210_car_sata_enable_hw_control();

	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_REG,
	    0, XUSB_PADCTL_UPHY_PLL_S0_CTL_1_PWR_OVRD);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_REG,
	    0, XUSB_PADCTL_UPHY_PLL_S0_CTL_2_CAL_OVRD);
	SETCLR4(sc, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_REG,
	    0, XUSB_PADCTL_UPHY_PLL_S0_CTL_8_RCAL_OVRD);

	delay(1);

	tegra210_car_sata_enable_hw_seq();
}

static void
tegra210_xusbpad_lane_enable_sata(struct tegra210_xusbpad_softc *sc, int index)
{
	tegra210_xusbpad_uphy_enable_sata(sc);

	KASSERT(index == 0);
	SETCLR4(sc, XUSB_PADCTL_USB3_PAD_MUX_REG,
	    XUSB_PADCTL_USB3_PAD_MUX_FORCE_SATA_PAD_IDDQ_DISABLE(index), 0);
}


#define	XUSBPAD_LANE(n, i, r, m, f, ef)		\
	{					\
		.name = (n),			\
		.index = (i),			\
		.reg = (r),			\
		.mask = (m),			\
		.funcs = (f),			\
		.nfuncs = __arraycount(f),	\
		.enable = (ef)			\
	}

static const struct tegra210_xusbpad_lane {
	const char		*name;
	int			index;
	bus_size_t		reg;
	uint32_t		mask;
	const char		**funcs;
	int			nfuncs;
	void			(*enable)(struct tegra210_xusbpad_softc *, int);
} tegra210_xusbpad_lanes[] = {
	XUSBPAD_LANE("usb2-0", 0, 0x04, __BITS(1,0), tegra210_xusbpad_usb2_func,
		     tegra210_xusbpad_lane_enable_usb2),
	XUSBPAD_LANE("usb2-1", 1, 0x04, __BITS(3,2), tegra210_xusbpad_usb2_func,
		     tegra210_xusbpad_lane_enable_usb2),
	XUSBPAD_LANE("usb2-2", 2, 0x04, __BITS(5,4), tegra210_xusbpad_usb2_func,
		     tegra210_xusbpad_lane_enable_usb2),
	XUSBPAD_LANE("usb2-3", 3, 0x04, __BITS(7,6), tegra210_xusbpad_usb2_func,
		     tegra210_xusbpad_lane_enable_usb2),

	XUSBPAD_LANE("hsic-0", 0, 0x04, __BIT(14), tegra210_xusbpad_hsic_func,
		     NULL),
	XUSBPAD_LANE("hsic-1", 1, 0x04, __BIT(15), tegra210_xusbpad_hsic_func,
		     NULL),

	XUSBPAD_LANE("pcie-0", 0, 0x28, __BITS(13,12), tegra210_xusbpad_pcie_func,
		     tegra210_xusbpad_lane_enable_pcie),
	XUSBPAD_LANE("pcie-1", 1, 0x28, __BITS(15,14), tegra210_xusbpad_pcie_func,
		     tegra210_xusbpad_lane_enable_pcie),
	XUSBPAD_LANE("pcie-2", 2, 0x28, __BITS(17,16), tegra210_xusbpad_pcie_func,
		     tegra210_xusbpad_lane_enable_pcie),
	XUSBPAD_LANE("pcie-3", 3, 0x28, __BITS(19,18), tegra210_xusbpad_pcie_func,
		     tegra210_xusbpad_lane_enable_pcie),
	XUSBPAD_LANE("pcie-4", 4, 0x28, __BITS(21,20), tegra210_xusbpad_pcie_func,
		     tegra210_xusbpad_lane_enable_pcie),
	XUSBPAD_LANE("pcie-5", 5, 0x28, __BITS(23,22), tegra210_xusbpad_pcie_func,
		     tegra210_xusbpad_lane_enable_pcie),
	XUSBPAD_LANE("pcie-6", 6, 0x28, __BITS(25,24), tegra210_xusbpad_pcie_func,
		     tegra210_xusbpad_lane_enable_pcie),

	XUSBPAD_LANE("sata-0", 0, XUSB_PADCTL_USB3_PAD_MUX_REG, __BITS(31,30),
	    tegra210_xusbpad_pcie_func, tegra210_xusbpad_lane_enable_sata),
};

#define	XUSBPAD_PORT(n, i, r, m, im)		\
	{					\
		.name = (n),			\
		.index = (i),			\
		.reg = (r),			\
		.mask = (m),			\
		.internal_mask = (im)		\
	}

struct tegra210_xusbpad_port {
	const char		*name;
	int			index;
	bus_size_t		reg;
	uint32_t		mask;
	uint32_t		internal_mask;
};

static const struct tegra210_xusbpad_port tegra210_xusbpad_usb2_ports[] = {
	XUSBPAD_PORT("usb2-0", 0, 0x08, __BITS(1,0), __BIT(2)),
	XUSBPAD_PORT("usb2-1", 1, 0x08, __BITS(5,4), __BIT(6)),
	XUSBPAD_PORT("usb2-2", 2, 0x08, __BITS(9,8), __BIT(10)),
	XUSBPAD_PORT("usb2-3", 3, 0x08, __BITS(13,12), __BIT(14)),
};

static const struct tegra210_xusbpad_port tegra210_xusbpad_usb3_ports[] = {
	XUSBPAD_PORT("usb3-0", 0, 0x14, __BITS(3,0), __BIT(4)),
	XUSBPAD_PORT("usb3-1", 1, 0x14, __BITS(8,5), __BIT(9)),
	XUSBPAD_PORT("usb3-2", 2, 0x14, __BITS(13,10), __BIT(14)),
	XUSBPAD_PORT("usb3-3", 3, 0x14, __BITS(18,15), __BIT(19)),
};

static const struct tegra210_xusbpad_port tegra210_xusbpad_hsic_ports[] = {
	XUSBPAD_PORT("hsic-0", 0, 0, 0, 0),
	XUSBPAD_PORT("hsic-1", 1, 0, 0, 0),
};

static int
tegra210_xusbpad_find_func(const struct tegra210_xusbpad_lane *lane,
    const char *func)
{
	for (int n = 0; n < lane->nfuncs; n++)
		if (strcmp(lane->funcs[n], func) == 0)
			return n;
	return -1;
}

static const struct tegra210_xusbpad_lane *
tegra210_xusbpad_find_lane(const char *name)
{
	for (int n = 0; n < __arraycount(tegra210_xusbpad_lanes); n++)
		if (strcmp(tegra210_xusbpad_lanes[n].name, name) == 0)
			return &tegra210_xusbpad_lanes[n];
	return NULL;
}

static void
tegra210_xusbpad_configure_lane(struct tegra210_xusbpad_softc *sc,
    int phandle)
{
	const struct tegra210_xusbpad_lane *lane;
	const char *name, *function;
	int func;

	name = fdtbus_get_string(phandle, "name");
	if (name == NULL) {
		aprint_error_dev(sc->sc_dev, "no 'name' property\n");
		return;
	}
	function = fdtbus_get_string(phandle, "nvidia,function");
	if (function == NULL) {
		aprint_error_dev(sc->sc_dev, "no 'nvidia,function' property\n");
		return;
	}

	lane = tegra210_xusbpad_find_lane(name);
	if (lane == NULL) {
		aprint_error_dev(sc->sc_dev, "unsupported lane '%s'\n", name);
		return;
	}
	func = tegra210_xusbpad_find_func(lane, function);
	if (func == -1) {
		aprint_error_dev(sc->sc_dev, "unsupported function '%s'\n", function);
		return;
	}

	aprint_normal_dev(sc->sc_dev, "lane %s: set func %s\n", name, function);
	SETCLR4(sc, lane->reg, __SHIFTIN(func, lane->mask), lane->mask);

	if (lane->enable)
		lane->enable(sc, lane->index);
}

static void
tegra210_xusbpad_configure_pads(struct tegra210_xusbpad_softc *sc,
    const char *name)
{
	struct fdtbus_reset *rst;
	struct clk *clk;
	int phandle, child;

	/* Search for the pad's node */
	phandle = of_find_firstchild_byname(sc->sc_phandle, "pads");
	if (phandle == -1) {
		aprint_error_dev(sc->sc_dev, "no 'pads' node\n");
		return;
	}
	phandle = of_find_firstchild_byname(phandle, name);
	if (phandle == -1) {
		aprint_error_dev(sc->sc_dev, "no 'pads/%s' node\n", name);
		return;
	}

	if (!fdtbus_status_okay(phandle))
		return;		/* pad is disabled */

	/* Enable the pad's resources */
	if (of_hasprop(phandle, "clocks")) {
		clk = fdtbus_clock_get_index(phandle, 0);
		if (clk == NULL || clk_enable(clk) != 0) {
			aprint_error_dev(sc->sc_dev, "couldn't enable %s's clock\n", name);
			return;
		}
	}
	if (of_hasprop(phandle, "resets")) {
		rst = fdtbus_reset_get_index(phandle, 0);
		if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
			aprint_error_dev(sc->sc_dev, "couldn't de-assert %s's reset\n", name);
			return;
		}
	}

	/* Attach PHYs */
	phandle = of_find_firstchild_byname(phandle, "lanes");
	if (phandle == -1) {
		aprint_error_dev(sc->sc_dev, "no 'pads/%s/lanes' node\n", name);
		return;
	}
	for (child = OF_child(phandle); child; child = OF_peer(child)) {
		struct tegra210_xusbpad_phy_attach_args paa = {
			.paa_xusbpad = sc,
			.paa_phandle = child
		};
		config_found(sc->sc_dev, &paa, NULL, CFARGS_NONE);
	}
}

static const struct tegra210_xusbpad_port *
tegra210_xusbpad_find_port(const char *name, const struct tegra210_xusbpad_port *ports,
    int nports)
{
	for (int n = 0; n < nports; n++)
		if (strcmp(name, ports[n].name) == 0)
			return &ports[n];
	return NULL;
}

static const struct tegra210_xusbpad_port *
tegra210_xusbpad_find_usb2_port(const char *name)
{
	return tegra210_xusbpad_find_port(name, tegra210_xusbpad_usb2_ports,
	    __arraycount(tegra210_xusbpad_usb2_ports));
}

static const struct tegra210_xusbpad_port *
tegra210_xusbpad_find_usb3_port(const char *name)
{
	return tegra210_xusbpad_find_port(name, tegra210_xusbpad_usb3_ports,
	    __arraycount(tegra210_xusbpad_usb3_ports));
}

static const struct tegra210_xusbpad_port *
tegra210_xusbpad_find_hsic_port(const char *name)
{
	return tegra210_xusbpad_find_port(name, tegra210_xusbpad_hsic_ports,
	    __arraycount(tegra210_xusbpad_hsic_ports));
}

static void
tegra210_xusbpad_enable_vbus(struct tegra210_xusbpad_softc *sc,
    const struct tegra210_xusbpad_port *port, int phandle)
{
	struct fdtbus_regulator *vbus_reg;

	if (!of_hasprop(phandle, "vbus-supply"))
		return;

	vbus_reg = fdtbus_regulator_acquire(phandle, "vbus-supply");
	if (vbus_reg == NULL || fdtbus_regulator_enable(vbus_reg) != 0) {
		aprint_error_dev(sc->sc_dev,
		    "couldn't enable vbus regulator for port %s\n",
		    port->name);
	}
}

static void
tegra210_xusbpad_configure_usb2_port(struct tegra210_xusbpad_softc *sc,
    int phandle, const struct tegra210_xusbpad_port *port)
{
	u_int modeval, internal;
	const char *mode;

	mode = fdtbus_get_string(phandle, "mode");
	if (mode == NULL) {
		aprint_error_dev(sc->sc_dev, "no 'mode' property on port %s\n", port->name);
		return;
	}
	if (strcmp(mode, "host") == 0)
		modeval = 1;
	else if (strcmp(mode, "device") == 0)
		modeval = 2;
	else if (strcmp(mode, "otg") == 0)
		modeval = 3;
	else {
		aprint_error_dev(sc->sc_dev, "unsupported mode '%s' on port %s\n", mode, port->name);
		return;
	}

	internal = of_hasprop(phandle, "nvidia,internal");

	tegra210_xusbpad_enable_vbus(sc, port, phandle);

	aprint_normal_dev(sc->sc_dev, "port %s: set mode %s, %s\n", port->name, mode,
	    internal ? "internal" : "external");
	SETCLR4(sc, port->reg, __SHIFTIN(internal, port->internal_mask), port->internal_mask);
	SETCLR4(sc, port->reg, __SHIFTIN(modeval, port->mask), port->mask);
}

static void
tegra210_xusbpad_configure_usb3_port(struct tegra210_xusbpad_softc *sc,
    int phandle, const struct tegra210_xusbpad_port *port)
{
	u_int companion, internal;

	if (of_getprop_uint32(phandle, "nvidia,usb2-companion", &companion)) {
		aprint_error_dev(sc->sc_dev, "no 'nvidia,usb2-companion' property on port %s\n", port->name);
		return;
	}
	internal = of_hasprop(phandle, "nvidia,internal");

	tegra210_xusbpad_enable_vbus(sc, port, phandle);

	aprint_normal_dev(sc->sc_dev, "port %s: set companion usb2-%d, %s\n", port->name,
	    companion, internal ? "internal" : "external");
	SETCLR4(sc, port->reg, __SHIFTIN(internal, port->internal_mask), port->internal_mask);
	SETCLR4(sc, port->reg, __SHIFTIN(companion, port->mask), port->mask);

	SETCLR4(sc, XUSB_PADCTL_UPHY_USB3_PADn_ECTL_1_REG(port->index),
	    __SHIFTIN(2, XUSB_PADCTL_UPHY_USB3_PADn_ECTL_2_TX_TERM_CTRL),
	    XUSB_PADCTL_UPHY_USB3_PADn_ECTL_2_TX_TERM_CTRL);
	SETCLR4(sc, XUSB_PADCTL_UPHY_USB3_PADn_ECTL_2_REG(port->index),
	    __SHIFTIN(0xfc, XUSB_PADCTL_UPHY_USB3_PADn_ECTL_2_RX_CTLE),
	    XUSB_PADCTL_UPHY_USB3_PADn_ECTL_2_RX_CTLE);
	WR4(sc, XUSB_PADCTL_UPHY_USB3_PADn_ECTL_3_REG(port->index), 0xc0077f1f);
	SETCLR4(sc, XUSB_PADCTL_UPHY_USB3_PADn_ECTL_4_REG(port->index),
	    __SHIFTIN(0x01c7, XUSB_PADCTL_UPHY_USB3_PADn_ECTL_4_RX_CDR_CTRL),
	    XUSB_PADCTL_UPHY_USB3_PADn_ECTL_4_RX_CDR_CTRL);
	WR4(sc, XUSB_PADCTL_UPHY_USB3_PADn_ECTL_6_REG(port->index), 0xfcf01368);

	SETCLR4(sc, XUSB_PADCTL_ELPG_PROGRAM_1_REG,
	    0, XUSB_PADCTL_ELPG_PROGRAM_1_SSPn_ELPG_CLAMP_EN(port->index));
	delay(200);
	SETCLR4(sc, XUSB_PADCTL_ELPG_PROGRAM_1_REG,
	    0, XUSB_PADCTL_ELPG_PROGRAM_1_SSPn_ELPG_CLAMP_EN_EARLY(port->index));
	delay(200);
	SETCLR4(sc, XUSB_PADCTL_ELPG_PROGRAM_1_REG,
	    0, XUSB_PADCTL_ELPG_PROGRAM_1_SSPn_ELPG_VCORE_DOWN(port->index));

	SETCLR4(sc, XUSB_PADCTL_VBUS_OC_MAP_REG,
	    XUSB_PADCTL_VBUS_OC_MAP_VBUS_ENABLE(port->index), 0);
}

static void
tegra210_xusbpad_configure_hsic_port(struct tegra210_xusbpad_softc *sc,
    int phandle, const struct tegra210_xusbpad_port *port)
{
	tegra210_xusbpad_enable_vbus(sc, port, phandle);
}

static void
tegra210_xusbpad_configure_ports(struct tegra210_xusbpad_softc *sc)
{
	const struct tegra210_xusbpad_port *port;
	const char *port_name;
	int phandle, child;

	/* Search for the ports node */
	phandle = of_find_firstchild_byname(sc->sc_phandle, "ports");

	/* Configure ports */
	for (child = OF_child(phandle); child; child = OF_peer(child)) {
		if (!fdtbus_status_okay(child))
			continue;
		port_name = fdtbus_get_string(child, "name");

		if ((port = tegra210_xusbpad_find_usb2_port(port_name)) != NULL)
			tegra210_xusbpad_configure_usb2_port(sc, child, port);
		else if ((port = tegra210_xusbpad_find_usb3_port(port_name)) != NULL)
			tegra210_xusbpad_configure_usb3_port(sc, child, port);
		else if ((port = tegra210_xusbpad_find_hsic_port(port_name)) != NULL)
			tegra210_xusbpad_configure_hsic_port(sc, child, port);
		else
			aprint_error_dev(sc->sc_dev, "unsupported port '%s'\n", port_name);
	}
}

static void
tegra210_xusbpad_enable(struct tegra210_xusbpad_softc *sc)
{
	if (sc->sc_enabled)
		return;

	SETCLR4(sc, XUSB_PADCTL_ELPG_PROGRAM_1_REG, 0, XUSB_PADCTL_ELPG_PROGRAM_1_AUX_MUX_LP0_CLAMP_EN);
	delay(200);
	SETCLR4(sc, XUSB_PADCTL_ELPG_PROGRAM_1_REG, 0, XUSB_PADCTL_ELPG_PROGRAM_1_AUX_MUX_LP0_CLAMP_EN_EARLY);
	delay(200);
	SETCLR4(sc, XUSB_PADCTL_ELPG_PROGRAM_1_REG, 0, XUSB_PADCTL_ELPG_PROGRAM_1_AUX_MUX_LP0_VCORE_DOWN);

	sc->sc_enabled = true;
}

static void
tegra210_xusbpad_sata_enable(device_t dev)
{
	struct tegra210_xusbpad_softc * const sc = device_private(dev);

	tegra210_xusbpad_enable(sc);
}

static void
tegra210_xusbpad_xhci_enable(device_t dev)
{
	struct tegra210_xusbpad_softc * const sc = device_private(dev);

	tegra210_xusbpad_enable(sc);
}

static const struct tegra_xusbpad_ops tegra210_xusbpad_ops = {
	.sata_enable = tegra210_xusbpad_sata_enable,
	.xhci_enable = tegra210_xusbpad_xhci_enable,
};

static const struct device_compatible_entry compat_data[] = {
	{ .compat = "nvidia,tegra210-xusb-padctl" },
	DEVICE_COMPAT_EOL
};

static int
tegra210_xusbpad_match(device_t parent, cfdata_t cf, void *aux)
{
	struct fdt_attach_args * const faa = aux;

	return of_compatible_match(faa->faa_phandle, compat_data);
}

static void
tegra210_xusbpad_attach(device_t parent, device_t self, void *aux)
{
	struct tegra210_xusbpad_softc * const sc = device_private(self);
	struct fdt_attach_args * const faa = aux;
	bus_addr_t addr;
	bus_size_t size;
	int error;

	if (fdtbus_get_reg(faa->faa_phandle, 0, &addr, &size) != 0) {
		aprint_error(": couldn't get registers\n");
		return;
	}
	sc->sc_rst = fdtbus_reset_get(faa->faa_phandle, "padctl");
	if (sc->sc_rst == NULL) {
		aprint_error(": couldn't get reset padctl\n");
		return;
	}

	sc->sc_dev = self;
	sc->sc_phandle = faa->faa_phandle;
	sc->sc_bst = faa->faa_bst;
	error = bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh);
	if (error) {
		aprint_error(": couldn't map %#" PRIxBUSADDR ": %d", addr, error);
		return;
	}

	aprint_naive("\n");
	aprint_normal(": XUSB PADCTL\n");

	fdtbus_reset_deassert(sc->sc_rst);

	tegra_xusbpad_register(self, &tegra210_xusbpad_ops);

	tegra210_xusbpad_configure_pads(sc, "usb2");
	tegra210_xusbpad_configure_pads(sc, "hsic");
	tegra210_xusbpad_configure_pads(sc, "pcie");
	tegra210_xusbpad_configure_pads(sc, "sata");

	tegra210_xusbpad_configure_ports(sc);
}

static void *
tegra210_xusbpad_phy_acquire(device_t dev, const void *data, size_t len)
{
	struct tegra210_xusbpad_phy_softc * const sc = device_private(dev);

	if (len != 0)
		return NULL;

	return sc;
}

static void
tegra210_xusbpad_phy_release(device_t dev, void *priv)
{
};

static int
tegra210_xusbpad_phy_enable(device_t dev, void *priv, bool enable)
{
	struct tegra210_xusbpad_phy_softc * const sc = device_private(dev);

	if (enable == false)
		return ENXIO;	/* not implemented */

	tegra210_xusbpad_configure_lane(sc->sc_xusbpad, sc->sc_phandle);

	return 0;
}

static const struct fdtbus_phy_controller_func tegra210_xusbpad_phy_funcs = {
	.acquire = tegra210_xusbpad_phy_acquire,
	.release = tegra210_xusbpad_phy_release,
	.enable = tegra210_xusbpad_phy_enable,
};

CFATTACH_DECL_NEW(tegra210_xusbpad, sizeof(struct tegra210_xusbpad_softc),
	tegra210_xusbpad_match, tegra210_xusbpad_attach, NULL, NULL);

static int
tegra210_xusbpad_phy_match(device_t parent, cfdata_t cf, void *aux)
{
	struct tegra210_xusbpad_phy_attach_args * const paa = aux;

	if (!fdtbus_status_okay(paa->paa_phandle))
		return 0;

	return 1;
}

static void
tegra210_xusbpad_phy_attach(device_t parent, device_t self, void *aux)
{
	struct tegra210_xusbpad_phy_softc * const sc = device_private(self);
	struct tegra210_xusbpad_phy_attach_args * const paa = aux;

	sc->sc_dev = self;
	sc->sc_phandle = paa->paa_phandle;
	sc->sc_xusbpad = paa->paa_xusbpad;

	aprint_naive("\n");
	aprint_normal(": %s\n", fdtbus_get_string(sc->sc_phandle, "name"));

	fdtbus_register_phy_controller(self, sc->sc_phandle, &tegra210_xusbpad_phy_funcs);
}

CFATTACH_DECL_NEW(tegra210xphy, sizeof(struct tegra210_xusbpad_phy_softc),
	tegra210_xusbpad_phy_match, tegra210_xusbpad_phy_attach, NULL, NULL);
