/* SPDX-License-Identifier: GPL-2.0-only */

#include <console/console.h>
#include <device/mmio.h>
#include <soc/usb/usb_common.h>
#include <soc/addressmap.h>
#include <soc/clock.h>

struct usb_dwc3 {
	u32 sbuscfg0;
	u32 sbuscfg1;
	u32 txthrcfg;
	u32 rxthrcfg;
	u32 ctl;
	u32 pmsts;
	u32 sts;
	u32 uctl1;
	u32 snpsid;
	u32 gpio;
	u32 uid;
	u32 uctl;
	u64 buserraddr;
	u64 prtbimap;
	u8 reserved1[32];
	u32 dbgfifospace;
	u32 dbgltssm;
	u32 dbglnmcc;
	u32 dbgbmu;
	u32 dbglspmux;
	u32 dbglsp;
	u32 dbgepinfo0;
	u32 dbgepinfo1;
	u64 prtbimap_hs;
	u64 prtbimap_fs;
	u8 reserved2[112];
	u32 usb2phycfg;
	u8 reserved3[124];
	u32 usb2phyacc;
	u8 reserved4[60];
	u32 usb3pipectl;
	u8 reserved5[60];
};
check_member(usb_dwc3, usb2phycfg, 0x100);
check_member(usb_dwc3, usb3pipectl, 0x1c0);

struct usb_dwc3_cfg {
	struct usb_dwc3 *usb_host_dwc3;
	u32 *usb3_bcr;
	u32 *qusb2phy_bcr;
	u32 *gcc_usb3phy_bcr_reg;
	u32 *gcc_qmpphy_bcr_reg;
};

static struct usb_dwc3_cfg usb_port0 = {
	.usb_host_dwc3 =	(void *)USB_HOST_DWC3_BASE,
	.usb3_bcr =		&gcc->usb30_prim_bcr,
	.qusb2phy_bcr =		&gcc->qusb2phy_prim_bcr,
	.gcc_usb3phy_bcr_reg =	&gcc->usb3_dp_phy_prim_bcr,
	.gcc_qmpphy_bcr_reg =	&gcc->usb3_phy_prim_bcr,
};

static void reset_usb(struct usb_dwc3_cfg *dwc3)
{
	/* Assert Core reset */
	clock_reset_bcr(dwc3->usb3_bcr, 1);

	/* Assert HS PHY reset */
	clock_reset_bcr(dwc3->qusb2phy_bcr, 1);

	/* Assert QMP PHY reset */
	clock_reset_bcr(dwc3->gcc_usb3phy_bcr_reg, 1);
	clock_reset_bcr(dwc3->gcc_qmpphy_bcr_reg, 1);
}

void reset_usb0(void)
{
	/* Before Resetting PHY, put Core in Reset */
	printk(BIOS_INFO, "Starting DWC3 and PHY resets for USB(0)\n");

	reset_usb(&usb_port0);
}

static void setup_dwc3(struct usb_dwc3 *dwc3)
{
	/* core exits U1/U2/U3 only in PHY power state P1/P2/P3 respectively */
	clrsetbits32(&dwc3->usb3pipectl,
		DWC3_GUSB3PIPECTL_DELAYP1TRANS,
		DWC3_GUSB3PIPECTL_UX_EXIT_IN_PX);

	/*
	 * Configure USB phy interface of DWC3 core.
	 * 1. Select UTMI+ PHY with 16-bit interface.
	 * 2. Set USBTRDTIM to the corresponding value
	 * according to the UTMI+ PHY interface.
	 */
	clrsetbits32(&dwc3->usb2phycfg,
			(DWC3_GUSB2PHYCFG_USB2TRDTIM_MASK |
			DWC3_GUSB2PHYCFG_PHYIF_MASK),
			(DWC3_GUSB2PHYCFG_PHYIF(UTMI_PHYIF_8_BIT) |
			DWC3_GUSB2PHYCFG_USBTRDTIM(USBTRDTIM_UTMI_8_BIT)));

	clrsetbits32(&dwc3->ctl, (DWC3_GCTL_SCALEDOWN_MASK |
			DWC3_GCTL_DISSCRAMBLE),
			DWC3_GCTL_U2EXIT_LFPS | DWC3_GCTL_DSBLCLKGTNG);

	/* configure controller in Host mode */
	clrsetbits32(&dwc3->ctl, (DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG)),
			DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_HOST));
	printk(BIOS_SPEW, "Configure USB in Host mode\n");
}

/* Initialization of DWC3 Core and PHY */

static void setup_usb_host(struct usb_dwc3_cfg *dwc3,
			void  *board_data)
{
	 /* Clear core reset. */
	clock_reset_bcr(dwc3->usb3_bcr, 0);

	/* Clear QUSB PHY reset. */
	clock_reset_bcr(dwc3->qusb2phy_bcr, 0);

	/* Initialize HS PHY */
	hs_usb_phy_init(board_data);

	/* Clear QMP PHY resets. */
	clock_reset_bcr(dwc3->gcc_usb3phy_bcr_reg, 0);
	clock_reset_bcr(dwc3->gcc_qmpphy_bcr_reg, 0);

	/* Initialize QMP PHY */
	ss_qmp_phy_init();

	setup_dwc3(dwc3->usb_host_dwc3);

	printk(BIOS_INFO, "DWC3 and PHY setup finished\n");
}
void setup_usb_host0(void  *board_data)
{
	printk(BIOS_INFO, "Setting up USB HOST0 controller.\n");
	setup_usb_host(&usb_port0, board_data);
}