/*
*
* phy-hi3516ev300-usb.c
*
* usb phy driver special for hi3516ev300
*
* Copyright (c) 2020 HiSilicon (Shanghai) Technologies CO., LIMITED.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include
#include
#include
#include
#include "phy-hisi-usb.h"
/* offset 0x140 */
#define USB2_CTRL 0x140
#define USB2_CRG_DEFAULT_VAL 0x3b2f
#define USB2_UTMI_CKEN (0x1 << 12)
#define USB2_PHY_APB_CKEN (0x1 << 11)
#define USB2_REF_CKEN (0x1 << 9)
#define USB2_BUS_CKEN (0x1 << 8)
#define USB2_PHY_PLL_CKEN (0x1 << 4)
#define USB2_PHY_XTAL_CKEN (0x1 << 2)
#define USB2_FREECLK_CKSEL (0x1 << 13)
#define USB2_PHY_APB_RST (0x1 << 10)
#define USB2_VCC_SRST_REQ (0x1 << 3)
#define USB2_PHY_REQ (0x1 << 0)
#define USB2_PHY_PORT_TREQ (0x1 << 1)
#define GTXTHRCFG 0xc108
#define GRXTHRCFG 0xc10c
#define REG_GCTL 0xc110
#define REG_GUSB3PIPECTL0 0xc2c0
#define PCS_SSP_SOFT_RESET (0x1 << 31)
#define PORT_CAP_DIR (0x3 << 12)
#define PORT_SET_HOST (0x1 << 12)
#define PORT_DISABLE_SUSPEND (0x1 << 17)
#define USB2_G_TXTHRCFG 0x23100000
#define USB2_G_RXTHRCFG 0x23100000
/* PHY base register */
#define USB2_PHY_BASE_REG 0x100D0000
#define RG_PLL_EN_MASK 0x0003
#define RG_PLL_EN_VAL 0x0003
#define PHY_PLL_OFFSET 0x0014
#define USB_VBUS_IO_CONFIG_REG 0x100c007C
#define USB_VBUS_IO_CONFIG_VAL 0x0431
#define USB_PWREN_CONFIG_REG 0x100c0080
#define USB_PWREN_CONFIG_VAL 0x1
/* PHY eye config */
#define HIXVP_PHY_ANA_CFG_0_OFFSET 0x00
#define HIXVP_PHY_PRE_DRIVE_MASK (0xf << 24)
#define HIXVP_PHY_PRE_DRIVE_VAL (0x4 << 24)
#define HIXVP_PHY_ANA_CFG_2_OFFSET 0x08
#define HIXVP_PHY_TX_TEST_BIT (0x1 << 20)
#define HIXVP_PHY_DISCONNECT_REFERENCE_MASK (0x7 << 16)
#define HIXVP_PHY_DISCONNECT_REFERENCE_VAL (0x2 << 16)
#define HIXVP_PHY_ANA_CFG_4_OFFSET 0x10
#define HIXVP_PHY_TX_REFERENCE_MASK (0x7 << 4)
#define HIXVP_PHY_TX_REFERENCE_VAL (0x5 << 4)
#define HIXVP_PHY_SQUELCH_REFERENCE_MASK (0x7 << 0)
#define HIXVP_PHY_SQUELCH_REFERENCE_VAL (0x5 << 0)
/* PHY trim config */
#define USB_TRIM_BASE_REG 0x12028004
#define USB_TRIM_VAL_MASK 0x001F
#define USB_TRIM_VAL_MIN 0x0009
#define USB_TRIM_VAL_MAX 0x001D
#define USB2_TRIM_OFFSET 0x0008
#define USB2_TRIM_MASK 0x1f00
#define usb2_trim_val(a) (((a) << 8) & USB2_TRIM_MASK)
#define USB2_TRIM_DEFAULT_VAL 0x000e
/* PHY svb config */
#define USB_SVB_BASE_REG 0x12020158
#define USB_SVB_OFFSET 0x00
#define USB_SVB_MASK (0x0f << 24)
#define USB_SVB_PREDEV_5_MIN 0x2bc
#define USB_SVB_PREDEV_5_MAX_4_MIN 0x32a
#define USB_SVB_PREDEV_4_MAX_3_MIN 0x398
#define USB_SVB_PREDEV_3_MAX_2_MIN 0x3ca
#define USB_SVB_PREDEV_2_MAX 0x44c
#define USB_SVB_PREDEV_5_PHY_VAL (0x05 << 24)
#define USB_SVB_PREDEV_4_PHY_VAL (0x04 << 24)
#define USB_SVB_PREDEV_3_PHY_VAL (0x03 << 24)
#define USB_SVB_PREDEV_2_PHY_VAL (0x02 << 24)
static uintptr_t xhci_base = 0;
int xhci_hcd_init(int index, struct xhci_hccr **hccr, struct xhci_hcor **hcor)
{
if ((hccr == NULL) || (hcor == NULL))
return -EINVAL;
xhci_base = USB3_CTRL_REG_BASE;
*hccr = (struct xhci_hccr *)(xhci_base);
*hcor = (struct xhci_hcor *)((uintptr_t) *hccr +
HC_LENGTH(xhci_readl(&(*hccr)->cr_capbase)));
return 0;
}
void usb2_eye_config(void)
{
unsigned int reg;
/* HSTX pre-drive strength */
reg = readl(USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_0_OFFSET);
reg &= ~HIXVP_PHY_PRE_DRIVE_MASK;
reg |= HIXVP_PHY_PRE_DRIVE_VAL;
writel(reg, USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_0_OFFSET);
/* TX test bit */
reg = readl(USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_2_OFFSET);
reg |= HIXVP_PHY_TX_TEST_BIT;
writel(reg, USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_2_OFFSET);
/* Disconnect reference voltage sel */
reg = readl(USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_2_OFFSET);
reg &= ~HIXVP_PHY_DISCONNECT_REFERENCE_MASK;
reg |= HIXVP_PHY_DISCONNECT_REFERENCE_VAL;
writel(reg, USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_2_OFFSET);
/* TX reference voltage sel */
reg = readl(USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_4_OFFSET);
reg &= ~HIXVP_PHY_TX_REFERENCE_MASK;
reg |= HIXVP_PHY_TX_REFERENCE_VAL;
writel(reg, USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_4_OFFSET);
/* Squlech reference voltage sel */
reg = readl(USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_4_OFFSET);
reg &= ~HIXVP_PHY_SQUELCH_REFERENCE_MASK;
reg |= HIXVP_PHY_SQUELCH_REFERENCE_VAL;
writel(reg, USB2_PHY_BASE_REG + HIXVP_PHY_ANA_CFG_4_OFFSET);
}
void usb2_trim_config(void)
{
unsigned int ret;
unsigned int reg;
unsigned int trim_val;
ret = readl(USB_TRIM_BASE_REG);
trim_val = (ret & USB_TRIM_VAL_MASK); /* get usb trim value */
reg = readl(USB2_PHY_BASE_REG + USB2_TRIM_OFFSET);
reg &= ~USB2_TRIM_MASK;
/* set trim value to HiXVPV100 phy */
if ((trim_val >= USB_TRIM_VAL_MIN) && (trim_val <= USB_TRIM_VAL_MAX))
reg |= usb2_trim_val(trim_val);
else
reg |= usb2_trim_val(USB2_TRIM_DEFAULT_VAL);
writel(reg, USB2_PHY_BASE_REG + USB2_TRIM_OFFSET);
}
void usb2_svb_config(void)
{
unsigned int ret;
unsigned int reg;
ret = readl(USB_SVB_BASE_REG);
reg = readl(USB2_PHY_BASE_REG + USB_SVB_OFFSET);
reg &= ~USB_SVB_MASK;
/* set svb value to HiXVPV100 phy */
if ((ret >= USB_SVB_PREDEV_5_MIN) && (ret < USB_SVB_PREDEV_5_MAX_4_MIN))
reg |= USB_SVB_PREDEV_5_PHY_VAL;
else if ((ret >= USB_SVB_PREDEV_5_MAX_4_MIN) && (ret < USB_SVB_PREDEV_4_MAX_3_MIN))
reg |= USB_SVB_PREDEV_4_PHY_VAL;
else if ((ret >= USB_SVB_PREDEV_4_MAX_3_MIN) && (ret <= USB_SVB_PREDEV_3_MAX_2_MIN))
reg |= USB_SVB_PREDEV_3_PHY_VAL;
else if ((ret > USB_SVB_PREDEV_3_MAX_2_MIN) && (ret <= USB_SVB_PREDEV_2_MAX))
reg |= USB_SVB_PREDEV_2_PHY_VAL;
else
reg |= USB_SVB_PREDEV_4_PHY_VAL;
writel(reg, USB2_PHY_BASE_REG + USB_SVB_OFFSET);
}
void phy_hiusb_init_crg_clk(int index)
{
unsigned int reg;
/* set usb2 CRG default val */
reg = USB2_CRG_DEFAULT_VAL;
writel(reg, CRG_REG_BASE + USB2_CTRL);
udelay(U_LEVEL6);
/* open UTMI clk */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_UTMI_CKEN;
writel(reg, CRG_REG_BASE + USB2_CTRL);
/* open phy apb clk */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_PHY_APB_CKEN;
writel(reg, CRG_REG_BASE + USB2_CTRL);
/* open ctrl ref clk */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_REF_CKEN;
writel(reg, CRG_REG_BASE + USB2_CTRL);
/* open bus clk */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_BUS_CKEN;
writel(reg, CRG_REG_BASE + USB2_CTRL);
/* open phy pll clk */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_PHY_PLL_CKEN;
writel(reg, CRG_REG_BASE + USB2_CTRL);
/* open phy xtal clk */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_PHY_XTAL_CKEN;
writel(reg, CRG_REG_BASE + USB2_CTRL);
/* freeclk_cksel_free */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_FREECLK_CKSEL;
writel(reg, CRG_REG_BASE + USB2_CTRL);
udelay(U_LEVEL5);
}
void phy_hiusb_init(int index)
{
unsigned int reg;
/* VBUS config */
reg = USB_VBUS_IO_CONFIG_VAL;
writel(reg, USB_VBUS_IO_CONFIG_REG);
reg = USB_PWREN_CONFIG_VAL;
writel(reg, USB_PWREN_CONFIG_REG);
/* init crg and clk */
phy_hiusb_init_crg_clk(index);
/* release phy apb */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg &= ~USB2_PHY_APB_RST;
writel(reg, CRG_REG_BASE + USB2_CTRL);
udelay(U_LEVEL5);
/* por noreset */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg &= ~USB2_PHY_REQ;
writel(reg, CRG_REG_BASE + USB2_CTRL);
reg = readl(USB2_PHY_BASE_REG + PHY_PLL_OFFSET);
reg &= ~RG_PLL_EN_MASK;
reg |= RG_PLL_EN_VAL;
writel(reg, USB2_PHY_BASE_REG + PHY_PLL_OFFSET);
udelay(U_LEVEL10);
/* cancel TPOR */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg &= ~USB2_PHY_PORT_TREQ;
writel(reg, CRG_REG_BASE + USB2_CTRL);
udelay(U_LEVEL6);
/* vcc reset */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg &= ~USB2_VCC_SRST_REQ;
writel(reg, CRG_REG_BASE + USB2_CTRL);
/* USB2 Controller configs */
reg = readl(USB3_CTRL_REG_BASE + REG_GUSB3PIPECTL0);
reg |= PCS_SSP_SOFT_RESET;
writel(reg, USB3_CTRL_REG_BASE + REG_GUSB3PIPECTL0);
udelay(U_LEVEL2);
reg = readl(USB3_CTRL_REG_BASE + REG_GCTL);
reg &= ~PORT_CAP_DIR;
reg |= PORT_SET_HOST; /* [13:12] 01: Host; 10: Device; 11: OTG */
writel(reg, USB3_CTRL_REG_BASE + REG_GCTL);
udelay(U_LEVEL2);
reg = readl(USB3_CTRL_REG_BASE + REG_GUSB3PIPECTL0);
reg &= ~PCS_SSP_SOFT_RESET;
reg &= ~PORT_DISABLE_SUSPEND; /* disable suspend */
writel(reg, USB3_CTRL_REG_BASE + REG_GUSB3PIPECTL0);
udelay(U_LEVEL2);
writel(USB2_G_TXTHRCFG, USB3_CTRL_REG_BASE + GTXTHRCFG);
writel(USB2_G_RXTHRCFG, USB3_CTRL_REG_BASE + GRXTHRCFG);
udelay(U_LEVEL2);
/* USB2 eye config */
usb2_eye_config();
/* USB2 trim config */
usb2_trim_config();
/* USB2 svb config */
usb2_svb_config();
}
EXPORT_SYMBOL(phy_hiusb_init);
void xhci_hcd_stop(int index)
{
unsigned int reg;
/* por noreset */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_PHY_REQ;
writel(reg, CRG_REG_BASE + USB2_CTRL);
udelay(U_LEVEL10);
/* cancel TPOR */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_PHY_PORT_TREQ;
writel(reg, CRG_REG_BASE + USB2_CTRL);
udelay(U_LEVEL6);
/* vcc reset */
reg = readl(CRG_REG_BASE + USB2_CTRL);
reg |= USB2_VCC_SRST_REQ;
writel(reg, CRG_REG_BASE + USB2_CTRL);
}
EXPORT_SYMBOL(xhci_hcd_stop);