Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Phy drivers for Hisilicon platforms
#
config PHY_HI6220_USB
tristate "hi6220 USB PHY support"
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
depends on HAS_IOMEM
select GENERIC_PHY
select MFD_SYSCON
help
Enable this to support the HISILICON HI6220 USB PHY.
To compile this driver as a module, choose M here.
config PHY_HI3660_USB
tristate "hi3660 USB PHY support"
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
select GENERIC_PHY
select MFD_SYSCON
help
Enable this to support the HISILICON HI3660 USB PHY.
To compile this driver as a module, choose M here.
config PHY_HI3670_USB
tristate "hi3670 USB PHY support"
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
select GENERIC_PHY
select MFD_SYSCON
help
Enable this to support the HISILICON HI3670 USB PHY.
To compile this driver as a module, choose M here.
config PHY_HI3670_PCIE
tristate "hi3670 PCIe PHY support"
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
select GENERIC_PHY
select MFD_SYSCON
help
Enable this to support the HiSilicon hi3670 PCIe PHY.
To compile this driver as a module, choose M here.
config PHY_HISTB_COMBPHY
tristate "HiSilicon STB SoCs COMBPHY support"
depends on (ARCH_HISI && ARM64) || COMPILE_TEST
select GENERIC_PHY
select MFD_SYSCON
help
Enable this to support the HISILICON STB SoCs COMBPHY.
If unsure, say N.
config PHY_HISI_INNO_USB2
tristate "HiSilicon INNO USB2 PHY support"
depends on ARCH_HISI || COMPILE_TEST
select GENERIC_PHY
select MFD_SYSCON
help
Support for INNO USB2 PHY on HiSilicon SoCs. This Phy supports
USB 1.5Mb/s, USB 12Mb/s, USB 480Mb/s speeds. It supports one
USB host port to accept one USB device.
config PHY_HIX5HD2_SATA
tristate "HIX5HD2 SATA PHY Driver"
depends on ARCH_HIX5HD2 && OF && HAS_IOMEM
select GENERIC_PHY
select MFD_SYSCON
help
Support for SATA PHY on Hisilicon hix5hd2 Soc.
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o
obj-$(CONFIG_PHY_HI3660_USB) += phy-hi3660-usb3.o
obj-$(CONFIG_PHY_HI3670_USB) += phy-hi3670-usb3.o
obj-$(CONFIG_PHY_HI3670_PCIE) += phy-hi3670-pcie.o
obj-$(CONFIG_PHY_HISTB_COMBPHY) += phy-histb-combphy.o
obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o
obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o
@@ -0,0 +1,234 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Phy provider for USB 3.0 controller on HiSilicon 3660 platform
*
* Copyright (C) 2017-2018 Hilisicon Electronics Co., Ltd.
* http://www.huawei.com
*
* Authors: Yu Chen <chenyu56@huawei.com>
*/
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#define PERI_CRG_CLK_EN4 0x40
#define PERI_CRG_CLK_DIS4 0x44
#define GT_CLK_USB3OTG_REF BIT(0)
#define GT_ACLK_USB3OTG BIT(1)
#define PERI_CRG_RSTEN4 0x90
#define PERI_CRG_RSTDIS4 0x94
#define IP_RST_USB3OTGPHY_POR BIT(3)
#define IP_RST_USB3OTG BIT(5)
#define PERI_CRG_ISODIS 0x148
#define USB_REFCLK_ISO_EN BIT(25)
#define PCTRL_PERI_CTRL3 0x10
#define PCTRL_PERI_CTRL3_MSK_START 16
#define USB_TCXO_EN BIT(1)
#define PCTRL_PERI_CTRL24 0x64
#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25)
#define USBOTG3_CTRL0 0x00
#define SC_USB3PHY_ABB_GT_EN BIT(15)
#define USBOTG3_CTRL2 0x08
#define USBOTG3CTRL2_POWERDOWN_HSP BIT(0)
#define USBOTG3CTRL2_POWERDOWN_SSP BIT(1)
#define USBOTG3_CTRL3 0x0C
#define USBOTG3_CTRL3_VBUSVLDEXT BIT(6)
#define USBOTG3_CTRL3_VBUSVLDEXTSEL BIT(5)
#define USBOTG3_CTRL4 0x10
#define USBOTG3_CTRL7 0x1c
#define REF_SSP_EN BIT(16)
/* This value config the default txtune parameter of the usb 2.0 phy */
#define HI3660_USB_DEFAULT_PHY_PARAM 0x1c466e3
struct hi3660_priv {
struct device *dev;
struct regmap *peri_crg;
struct regmap *pctrl;
struct regmap *otg_bc;
u32 eye_diagram_param;
};
static int hi3660_phy_init(struct phy *phy)
{
struct hi3660_priv *priv = phy_get_drvdata(phy);
u32 val, mask;
int ret;
/* usb refclk iso disable */
ret = regmap_write(priv->peri_crg, PERI_CRG_ISODIS, USB_REFCLK_ISO_EN);
if (ret)
goto out;
/* enable usb_tcxo_en */
val = USB_TCXO_EN | (USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START);
ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, val);
if (ret)
goto out;
/* assert phy */
val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG;
ret = regmap_write(priv->peri_crg, PERI_CRG_RSTEN4, val);
if (ret)
goto out;
/* enable phy ref clk */
val = SC_USB3PHY_ABB_GT_EN;
mask = val;
ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL0, mask, val);
if (ret)
goto out;
val = REF_SSP_EN;
mask = val;
ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL7, mask, val);
if (ret)
goto out;
/* exit from IDDQ mode */
mask = USBOTG3CTRL2_POWERDOWN_HSP | USBOTG3CTRL2_POWERDOWN_SSP;
ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL2, mask, 0);
if (ret)
goto out;
/* delay for exit from IDDQ mode */
usleep_range(100, 120);
/* deassert phy */
val = IP_RST_USB3OTGPHY_POR | IP_RST_USB3OTG;
ret = regmap_write(priv->peri_crg, PERI_CRG_RSTDIS4, val);
if (ret)
goto out;
/* delay for phy deasserted */
usleep_range(10000, 15000);
/* fake vbus valid signal */
val = USBOTG3_CTRL3_VBUSVLDEXT | USBOTG3_CTRL3_VBUSVLDEXTSEL;
mask = val;
ret = regmap_update_bits(priv->otg_bc, USBOTG3_CTRL3, mask, val);
if (ret)
goto out;
/* delay for vbus valid */
usleep_range(100, 120);
ret = regmap_write(priv->otg_bc, USBOTG3_CTRL4,
priv->eye_diagram_param);
if (ret)
goto out;
return 0;
out:
dev_err(priv->dev, "failed to init phy ret: %d\n", ret);
return ret;
}
static int hi3660_phy_exit(struct phy *phy)
{
struct hi3660_priv *priv = phy_get_drvdata(phy);
u32 val;
int ret;
/* assert phy */
val = IP_RST_USB3OTGPHY_POR;
ret = regmap_write(priv->peri_crg, PERI_CRG_RSTEN4, val);
if (ret)
goto out;
/* disable usb_tcxo_en */
val = USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START;
ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3, val);
if (ret)
goto out;
return 0;
out:
dev_err(priv->dev, "failed to exit phy ret: %d\n", ret);
return ret;
}
static const struct phy_ops hi3660_phy_ops = {
.init = hi3660_phy_init,
.exit = hi3660_phy_exit,
.owner = THIS_MODULE,
};
static int hi3660_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct phy *phy;
struct hi3660_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->peri_crg = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,pericrg-syscon");
if (IS_ERR(priv->peri_crg)) {
dev_err(dev, "no hisilicon,pericrg-syscon\n");
return PTR_ERR(priv->peri_crg);
}
priv->pctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,pctrl-syscon");
if (IS_ERR(priv->pctrl)) {
dev_err(dev, "no hisilicon,pctrl-syscon\n");
return PTR_ERR(priv->pctrl);
}
/* node of hi3660 phy is a sub-node of usb3_otg_bc */
priv->otg_bc = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(priv->otg_bc)) {
dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n");
return PTR_ERR(priv->otg_bc);
}
if (of_property_read_u32(dev->of_node, "hisilicon,eye-diagram-param",
&(priv->eye_diagram_param)))
priv->eye_diagram_param = HI3660_USB_DEFAULT_PHY_PARAM;
phy = devm_phy_create(dev, NULL, &hi3660_phy_ops);
if (IS_ERR(phy))
return PTR_ERR(phy);
phy_set_drvdata(phy, priv);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id hi3660_phy_of_match[] = {
{.compatible = "hisilicon,hi3660-usb-phy",},
{ }
};
MODULE_DEVICE_TABLE(of, hi3660_phy_of_match);
static struct platform_driver hi3660_phy_driver = {
.probe = hi3660_phy_probe,
.driver = {
.name = "hi3660-usb-phy",
.of_match_table = hi3660_phy_of_match,
}
};
module_platform_driver(hi3660_phy_driver);
MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Hilisicon Hi3660 USB3 PHY Driver");
@@ -0,0 +1,849 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PCIe phy driver for Kirin 970
*
* Copyright (C) 2017 HiSilicon Electronics Co., Ltd.
* https://www.huawei.com
* Copyright (C) 2021 Huawei Technologies Co., Ltd.
* https://www.huawei.com
*
* Authors:
* Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
* Manivannan Sadhasivam <mani@kernel.org>
*
* Based on:
* https://lore.kernel.org/lkml/4c9d6581478aa966698758c0420933f5defab4dd.1612335031.git.mchehab+huawei@kernel.org/
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/types.h>
#define AXI_CLK_FREQ 207500000
#define REF_CLK_FREQ 100000000
/* PCIe CTRL registers */
#define SOC_PCIECTRL_CTRL7_ADDR 0x01c
#define SOC_PCIECTRL_CTRL12_ADDR 0x030
#define SOC_PCIECTRL_CTRL20_ADDR 0x050
#define SOC_PCIECTRL_CTRL21_ADDR 0x054
#define PCIE_OUTPUT_PULL_BITS GENMASK(3, 0)
#define SOC_PCIECTRL_CTRL20_2P_MEM_CTRL 0x02605550
#define SOC_PCIECTRL_CTRL21_DEFAULT 0x20000070
#define PCIE_PULL_UP_SYS_AUX_PWR_DET BIT(10)
#define PCIE_OUTPUT_PULL_DOWN BIT(1)
/* PCIe PHY registers */
#define SOC_PCIEPHY_CTRL0_ADDR 0x000
#define SOC_PCIEPHY_CTRL1_ADDR 0x004
#define SOC_PCIEPHY_CTRL38_ADDR 0x0098
#define SOC_PCIEPHY_STATE0_ADDR 0x400
#define RAWLANEN_DIG_PCS_XF_TX_OVRD_IN_1 0xc004
#define SUP_DIG_LVL_OVRD_IN 0x003c
#define LANEN_DIG_ASIC_TX_OVRD_IN_1 0x4008
#define LANEN_DIG_ASIC_TX_OVRD_IN_2 0x400c
#define PCIEPHY_RESET_BIT BIT(17)
#define PCIEPHY_PIPE_LINE0_RESET_BIT BIT(19)
#define PCIE_TXDETECT_RX_FAIL BIT(2)
#define PCIE_CLK_SOURCE BIT(8)
#define PCIE_IS_CLOCK_STABLE BIT(19)
#define PCIE_PULL_DOWN_PHY_TEST_POWERDOWN BIT(22)
#define PCIE_DEASSERT_CONTROLLER_PERST BIT(2)
#define EYEPARAM_NOCFG 0xffffffff
#define EYE_PARM0_MASK GENMASK(8, 6)
#define EYE_PARM1_MASK GENMASK(11, 8)
#define EYE_PARM2_MASK GENMASK(5, 0)
#define EYE_PARM3_MASK GENMASK(12, 7)
#define EYE_PARM4_MASK GENMASK(14, 9)
#define EYE_PARM0_EN BIT(9)
#define EYE_PARM1_EN BIT(12)
#define EYE_PARM2_EN BIT(6)
#define EYE_PARM3_EN BIT(13)
#define EYE_PARM4_EN BIT(15)
/* hi3670 pciephy register */
#define APB_PHY_START_ADDR 0x40000
#define SOC_PCIEPHY_MMC1PLL_CTRL1 0xc04
#define SOC_PCIEPHY_MMC1PLL_CTRL16 0xC40
#define SOC_PCIEPHY_MMC1PLL_CTRL17 0xC44
#define SOC_PCIEPHY_MMC1PLL_CTRL20 0xC50
#define SOC_PCIEPHY_MMC1PLL_CTRL21 0xC54
#define SOC_PCIEPHY_MMC1PLL_STAT0 0xE00
#define CRGPERIPH_PEREN12 0x470
#define CRGPERIPH_PERDIS12 0x474
#define CRGPERIPH_PCIECTRL0 0x800
#define PCIE_FNPLL_FBDIV_MASK GENMASK(27, 16)
#define PCIE_FNPLL_FRACDIV_MASK GENMASK(23, 0)
#define PCIE_FNPLL_POSTDIV1_MASK GENMASK(10, 8)
#define PCIE_FNPLL_POSTDIV2_MASK GENMASK(14, 12)
#define PCIE_FNPLL_PLL_MODE_MASK BIT(25)
#define PCIE_FNPLL_DLL_EN BIT(27)
#define PCIE_FNPLL_FBDIV 0xd0
#define PCIE_FNPLL_FRACDIV 0x555555
#define PCIE_FNPLL_POSTDIV1 0x5
#define PCIE_FNPLL_POSTDIV2 0x4
#define PCIE_FNPLL_PLL_MODE 0x0
#define PCIE_PHY_MMC1PLL 0x20
#define PCIE_PHY_CHOOSE_FNPLL BIT(27)
#define PCIE_PHY_MMC1PLL_DISABLE BIT(0)
#define PCIE_PHY_PCIEPL_BP BIT(16)
/* define ie,oe cfg */
#define IO_OE_HARD_GT_MODE BIT(1)
#define IO_IE_EN_HARD_BYPASS BIT(27)
#define IO_OE_EN_HARD_BYPASS BIT(11)
#define IO_HARD_CTRL_DEBOUNCE_BYPASS BIT(10)
#define IO_OE_GT_MODE BIT(8)
#define DEBOUNCE_WAITCFG_IN GENMASK(23, 20)
#define DEBOUNCE_WAITCFG_OUT GENMASK(16, 13)
#define IO_HP_DEBOUNCE_GT (BIT(12) | BIT(15))
#define IO_PHYREF_SOFT_GT_MODE BIT(14)
#define IO_REF_SOFT_GT_MODE BIT(13)
#define IO_REF_HARD_GT_MODE BIT(0)
/* noc power domain */
#define NOC_POWER_IDLEREQ_1 0x38c
#define NOC_POWER_IDLE_1 0x394
#define NOC_PW_MASK 0x10000
#define NOC_PW_SET_BIT 0x1
#define NUM_EYEPARAM 5
/* info located in sysctrl */
#define SCTRL_PCIE_CMOS_OFFSET 0x60
#define SCTRL_PCIE_CMOS_BIT 0x10
#define SCTRL_PCIE_ISO_OFFSET 0x44
#define SCTRL_PCIE_ISO_BIT 0x30
#define SCTRL_PCIE_HPCLK_OFFSET 0x190
#define SCTRL_PCIE_HPCLK_BIT 0x184000
#define SCTRL_PCIE_OE_OFFSET 0x14a
#define PCIE_DEBOUNCE_PARAM 0xf0f400
#define PCIE_OE_BYPASS GENMASK(29, 28)
/* peri_crg ctrl */
#define CRGCTRL_PCIE_ASSERT_OFFSET 0x88
#define CRGCTRL_PCIE_ASSERT_BIT 0x8c000000
#define FNPLL_HAS_LOCKED BIT(4)
/* Time for delay */
#define TIME_CMOS_MIN 100
#define TIME_CMOS_MAX 105
#define PIPE_CLK_STABLE_TIME 100
#define PLL_CTRL_WAIT_TIME 200
#define NOC_POWER_TIME 100
struct hi3670_pcie_phy {
struct device *dev;
void __iomem *base;
struct regmap *apb;
struct regmap *crgctrl;
struct regmap *sysctrl;
struct regmap *pmctrl;
struct clk *apb_sys_clk;
struct clk *apb_phy_clk;
struct clk *phy_ref_clk;
struct clk *aclk;
struct clk *aux_clk;
u32 eye_param[NUM_EYEPARAM];
};
/* Registers in PCIePHY */
static inline void hi3670_apb_phy_writel(struct hi3670_pcie_phy *phy, u32 val,
u32 reg)
{
writel(val, phy->base + APB_PHY_START_ADDR + reg);
}
static inline u32 hi3670_apb_phy_readl(struct hi3670_pcie_phy *phy, u32 reg)
{
return readl(phy->base + APB_PHY_START_ADDR + reg);
}
static inline void hi3670_apb_phy_updatel(struct hi3670_pcie_phy *phy,
u32 val, u32 mask, u32 reg)
{
u32 regval;
regval = hi3670_apb_phy_readl(phy, reg);
regval &= ~mask;
regval |= val;
hi3670_apb_phy_writel(phy, regval, reg);
}
static inline void kirin_apb_natural_phy_writel(struct hi3670_pcie_phy *phy,
u32 val, u32 reg)
{
writel(val, phy->base + reg);
}
static inline u32 kirin_apb_natural_phy_readl(struct hi3670_pcie_phy *phy,
u32 reg)
{
return readl(phy->base + reg);
}
static void hi3670_pcie_phy_oe_enable(struct hi3670_pcie_phy *phy, bool enable)
{
u32 val;
regmap_read(phy->sysctrl, SCTRL_PCIE_OE_OFFSET, &val);
val |= PCIE_DEBOUNCE_PARAM;
if (enable)
val &= ~PCIE_OE_BYPASS;
else
val |= PCIE_OE_BYPASS;
regmap_write(phy->sysctrl, SCTRL_PCIE_OE_OFFSET, val);
}
static void hi3670_pcie_get_eyeparam(struct hi3670_pcie_phy *phy)
{
struct device *dev = phy->dev;
struct device_node *np;
int ret, i;
np = dev->of_node;
ret = of_property_read_u32_array(np, "hisilicon,eye-diagram-param",
phy->eye_param, NUM_EYEPARAM);
if (!ret)
return;
/* There's no optional eye_param property. Set array to default */
for (i = 0; i < NUM_EYEPARAM; i++)
phy->eye_param[i] = EYEPARAM_NOCFG;
}
static void hi3670_pcie_set_eyeparam(struct hi3670_pcie_phy *phy)
{
u32 val;
val = kirin_apb_natural_phy_readl(phy, RAWLANEN_DIG_PCS_XF_TX_OVRD_IN_1);
if (phy->eye_param[1] != EYEPARAM_NOCFG) {
val &= ~EYE_PARM1_MASK;
val |= FIELD_PREP(EYE_PARM1_MASK, phy->eye_param[1]);
val |= EYE_PARM1_EN;
}
kirin_apb_natural_phy_writel(phy, val,
RAWLANEN_DIG_PCS_XF_TX_OVRD_IN_1);
val = kirin_apb_natural_phy_readl(phy, LANEN_DIG_ASIC_TX_OVRD_IN_2);
val &= ~(EYE_PARM2_MASK | EYE_PARM3_MASK);
if (phy->eye_param[2] != EYEPARAM_NOCFG) {
val |= FIELD_PREP(EYE_PARM2_MASK, phy->eye_param[2]);
val |= EYE_PARM2_EN;
}
if (phy->eye_param[3] != EYEPARAM_NOCFG) {
val |= FIELD_PREP(EYE_PARM3_MASK, phy->eye_param[3]);
val |= EYE_PARM3_EN;
}
kirin_apb_natural_phy_writel(phy, val, LANEN_DIG_ASIC_TX_OVRD_IN_2);
val = kirin_apb_natural_phy_readl(phy, SUP_DIG_LVL_OVRD_IN);
if (phy->eye_param[0] != EYEPARAM_NOCFG) {
val &= ~EYE_PARM0_MASK;
val |= FIELD_PREP(EYE_PARM0_MASK, phy->eye_param[0]);
val |= EYE_PARM0_EN;
}
kirin_apb_natural_phy_writel(phy, val, SUP_DIG_LVL_OVRD_IN);
val = kirin_apb_natural_phy_readl(phy, LANEN_DIG_ASIC_TX_OVRD_IN_1);
if (phy->eye_param[4] != EYEPARAM_NOCFG) {
val &= ~EYE_PARM4_MASK;
val |= FIELD_PREP(EYE_PARM4_MASK, phy->eye_param[4]);
val |= EYE_PARM4_EN;
}
kirin_apb_natural_phy_writel(phy, val, LANEN_DIG_ASIC_TX_OVRD_IN_1);
}
static void hi3670_pcie_natural_cfg(struct hi3670_pcie_phy *phy)
{
u32 val;
/* change 2p mem_ctrl */
regmap_write(phy->apb, SOC_PCIECTRL_CTRL20_ADDR,
SOC_PCIECTRL_CTRL20_2P_MEM_CTRL);
regmap_read(phy->apb, SOC_PCIECTRL_CTRL7_ADDR, &val);
val |= PCIE_PULL_UP_SYS_AUX_PWR_DET;
regmap_write(phy->apb, SOC_PCIECTRL_CTRL7_ADDR, val);
/* output, pull down */
regmap_read(phy->apb, SOC_PCIECTRL_CTRL12_ADDR, &val);
val &= ~PCIE_OUTPUT_PULL_BITS;
val |= PCIE_OUTPUT_PULL_DOWN;
regmap_write(phy->apb, SOC_PCIECTRL_CTRL12_ADDR, val);
/* Handle phy_reset and lane0_reset to HW */
hi3670_apb_phy_updatel(phy, PCIEPHY_RESET_BIT,
PCIEPHY_PIPE_LINE0_RESET_BIT | PCIEPHY_RESET_BIT,
SOC_PCIEPHY_CTRL1_ADDR);
/* fix chip bug: TxDetectRx fail */
hi3670_apb_phy_updatel(phy, PCIE_TXDETECT_RX_FAIL, PCIE_TXDETECT_RX_FAIL,
SOC_PCIEPHY_CTRL38_ADDR);
}
static void hi3670_pcie_pll_init(struct hi3670_pcie_phy *phy)
{
hi3670_apb_phy_updatel(phy, PCIE_PHY_CHOOSE_FNPLL, PCIE_PHY_CHOOSE_FNPLL,
SOC_PCIEPHY_MMC1PLL_CTRL1);
hi3670_apb_phy_updatel(phy,
FIELD_PREP(PCIE_FNPLL_FBDIV_MASK, PCIE_FNPLL_FBDIV),
PCIE_FNPLL_FBDIV_MASK,
SOC_PCIEPHY_MMC1PLL_CTRL16);
hi3670_apb_phy_updatel(phy,
FIELD_PREP(PCIE_FNPLL_FRACDIV_MASK, PCIE_FNPLL_FRACDIV),
PCIE_FNPLL_FRACDIV_MASK, SOC_PCIEPHY_MMC1PLL_CTRL17);
hi3670_apb_phy_updatel(phy,
PCIE_FNPLL_DLL_EN |
FIELD_PREP(PCIE_FNPLL_POSTDIV1_MASK, PCIE_FNPLL_POSTDIV1) |
FIELD_PREP(PCIE_FNPLL_POSTDIV2_MASK, PCIE_FNPLL_POSTDIV2) |
FIELD_PREP(PCIE_FNPLL_PLL_MODE_MASK, PCIE_FNPLL_PLL_MODE),
PCIE_FNPLL_POSTDIV1_MASK |
PCIE_FNPLL_POSTDIV2_MASK |
PCIE_FNPLL_PLL_MODE_MASK | PCIE_FNPLL_DLL_EN,
SOC_PCIEPHY_MMC1PLL_CTRL20);
hi3670_apb_phy_writel(phy, PCIE_PHY_MMC1PLL,
SOC_PCIEPHY_MMC1PLL_CTRL21);
}
static int hi3670_pcie_pll_ctrl(struct hi3670_pcie_phy *phy, bool enable)
{
struct device *dev = phy->dev;
u32 val;
int time = PLL_CTRL_WAIT_TIME;
if (enable) {
/* pd = 0 */
hi3670_apb_phy_updatel(phy, 0, PCIE_PHY_MMC1PLL_DISABLE,
SOC_PCIEPHY_MMC1PLL_CTRL16);
/* choose FNPLL */
val = hi3670_apb_phy_readl(phy, SOC_PCIEPHY_MMC1PLL_STAT0);
while (!(val & FNPLL_HAS_LOCKED)) {
if (!time) {
dev_err(dev, "wait for pll_lock timeout\n");
return -EINVAL;
}
time--;
udelay(1);
val = hi3670_apb_phy_readl(phy, SOC_PCIEPHY_MMC1PLL_STAT0);
}
hi3670_apb_phy_updatel(phy, 0, PCIE_PHY_PCIEPL_BP,
SOC_PCIEPHY_MMC1PLL_CTRL20);
} else {
hi3670_apb_phy_updatel(phy,
PCIE_PHY_MMC1PLL_DISABLE,
PCIE_PHY_MMC1PLL_DISABLE,
SOC_PCIEPHY_MMC1PLL_CTRL16);
hi3670_apb_phy_updatel(phy, PCIE_PHY_PCIEPL_BP,
PCIE_PHY_PCIEPL_BP,
SOC_PCIEPHY_MMC1PLL_CTRL20);
}
return 0;
}
static void hi3670_pcie_hp_debounce_gt(struct hi3670_pcie_phy *phy, bool open)
{
if (open)
/* gt_clk_pcie_hp/gt_clk_pcie_debounce open */
regmap_write(phy->crgctrl, CRGPERIPH_PEREN12,
IO_HP_DEBOUNCE_GT);
else
/* gt_clk_pcie_hp/gt_clk_pcie_debounce close */
regmap_write(phy->crgctrl, CRGPERIPH_PERDIS12,
IO_HP_DEBOUNCE_GT);
}
static void hi3670_pcie_phyref_gt(struct hi3670_pcie_phy *phy, bool open)
{
unsigned int val;
regmap_read(phy->crgctrl, CRGPERIPH_PCIECTRL0, &val);
if (open)
val &= ~IO_OE_HARD_GT_MODE; /* enable hard gt mode */
else
val |= IO_OE_HARD_GT_MODE; /* disable hard gt mode */
regmap_write(phy->crgctrl, CRGPERIPH_PCIECTRL0, val);
/* disable soft gt mode */
regmap_write(phy->crgctrl, CRGPERIPH_PERDIS12, IO_PHYREF_SOFT_GT_MODE);
}
static void hi3670_pcie_oe_ctrl(struct hi3670_pcie_phy *phy, bool en_flag)
{
unsigned int val;
regmap_read(phy->crgctrl, CRGPERIPH_PCIECTRL0, &val);
/* set ie cfg */
val |= IO_IE_EN_HARD_BYPASS;
/* set oe cfg */
val &= ~IO_HARD_CTRL_DEBOUNCE_BYPASS;
/* set phy_debounce in&out time */
val |= (DEBOUNCE_WAITCFG_IN | DEBOUNCE_WAITCFG_OUT);
/* select oe_gt_mode */
val |= IO_OE_GT_MODE;
if (en_flag)
val &= ~IO_OE_EN_HARD_BYPASS;
else
val |= IO_OE_EN_HARD_BYPASS;
regmap_write(phy->crgctrl, CRGPERIPH_PCIECTRL0, val);
}
static void hi3670_pcie_ioref_gt(struct hi3670_pcie_phy *phy, bool open)
{
unsigned int val;
if (open) {
regmap_write(phy->apb, SOC_PCIECTRL_CTRL21_ADDR,
SOC_PCIECTRL_CTRL21_DEFAULT);
hi3670_pcie_oe_ctrl(phy, true);
/* en hard gt mode */
regmap_read(phy->crgctrl, CRGPERIPH_PCIECTRL0, &val);
val &= ~IO_REF_HARD_GT_MODE;
regmap_write(phy->crgctrl, CRGPERIPH_PCIECTRL0, val);
/* disable soft gt mode */
regmap_write(phy->crgctrl, CRGPERIPH_PERDIS12,
IO_REF_SOFT_GT_MODE);
} else {
/* disable hard gt mode */
regmap_read(phy->crgctrl, CRGPERIPH_PCIECTRL0, &val);
val |= IO_REF_HARD_GT_MODE;
regmap_write(phy->crgctrl, CRGPERIPH_PCIECTRL0, val);
/* disable soft gt mode */
regmap_write(phy->crgctrl, CRGPERIPH_PERDIS12,
IO_REF_SOFT_GT_MODE);
hi3670_pcie_oe_ctrl(phy, false);
}
}
static int hi3670_pcie_allclk_ctrl(struct hi3670_pcie_phy *phy, bool clk_on)
{
struct device *dev = phy->dev;
int ret = 0;
if (!clk_on)
goto close_clocks;
/* choose 100MHz clk src: Bit[8]==1 pad, Bit[8]==0 pll */
hi3670_apb_phy_updatel(phy, 0, PCIE_CLK_SOURCE,
SOC_PCIEPHY_CTRL1_ADDR);
hi3670_pcie_pll_init(phy);
ret = hi3670_pcie_pll_ctrl(phy, true);
if (ret) {
dev_err(dev, "Failed to enable pll\n");
return -EINVAL;
}
hi3670_pcie_hp_debounce_gt(phy, true);
hi3670_pcie_phyref_gt(phy, true);
hi3670_pcie_ioref_gt(phy, true);
ret = clk_set_rate(phy->aclk, AXI_CLK_FREQ);
if (ret) {
dev_err(dev, "Failed to set rate\n");
goto close_clocks;
}
return 0;
close_clocks:
hi3670_pcie_ioref_gt(phy, false);
hi3670_pcie_phyref_gt(phy, false);
hi3670_pcie_hp_debounce_gt(phy, false);
hi3670_pcie_pll_ctrl(phy, false);
return ret;
}
static bool is_pipe_clk_stable(struct hi3670_pcie_phy *phy)
{
struct device *dev = phy->dev;
u32 val;
u32 time = PIPE_CLK_STABLE_TIME;
u32 pipe_clk_stable = PCIE_IS_CLOCK_STABLE;
val = hi3670_apb_phy_readl(phy, SOC_PCIEPHY_STATE0_ADDR);
while (val & pipe_clk_stable) {
mdelay(1);
if (!time) {
dev_err(dev, "PIPE clk is not stable\n");
return false;
}
time--;
val = hi3670_apb_phy_readl(phy, SOC_PCIEPHY_STATE0_ADDR);
}
return true;
}
static int hi3670_pcie_noc_power(struct hi3670_pcie_phy *phy, bool enable)
{
struct device *dev = phy->dev;
u32 time = NOC_POWER_TIME;
unsigned int val = NOC_PW_MASK;
int rst;
if (enable)
val = NOC_PW_MASK | NOC_PW_SET_BIT;
else
val = NOC_PW_MASK;
rst = enable ? 1 : 0;
regmap_write(phy->pmctrl, NOC_POWER_IDLEREQ_1, val);
time = NOC_POWER_TIME;
regmap_read(phy->pmctrl, NOC_POWER_IDLE_1, &val);
while ((val & NOC_PW_SET_BIT) != rst) {
udelay(10);
if (!time) {
dev_err(dev, "Failed to reverse noc power-status\n");
return -EINVAL;
}
time--;
regmap_read(phy->pmctrl, NOC_POWER_IDLE_1, &val);
}
return 0;
}
static int hi3670_pcie_get_resources_from_pcie(struct hi3670_pcie_phy *phy)
{
struct device_node *pcie_port;
struct device *dev = phy->dev;
struct device *pcie_dev;
pcie_port = of_get_child_by_name(dev->parent->of_node, "pcie");
if (!pcie_port) {
dev_err(dev, "no pcie node found in %s\n",
dev->parent->of_node->full_name);
return -ENODEV;
}
pcie_dev = bus_find_device_by_of_node(&platform_bus_type, pcie_port);
if (!pcie_dev) {
dev_err(dev, "Didn't find pcie device\n");
return -ENODEV;
}
/*
* We might just use NULL instead of the APB name, as the
* pcie-kirin currently registers directly just one regmap (although
* the DWC driver register other regmaps).
*
* Yet, it sounds safer to warrant that it will be accessing the
* right regmap. So, let's use the named version.
*/
phy->apb = dev_get_regmap(pcie_dev, "kirin_pcie_apb");
if (!phy->apb) {
dev_err(dev, "Failed to get APB regmap\n");
return -ENODEV;
}
return 0;
}
static int kirin_pcie_clk_ctrl(struct hi3670_pcie_phy *phy, bool enable)
{
int ret = 0;
if (!enable)
goto close_clk;
ret = clk_set_rate(phy->phy_ref_clk, REF_CLK_FREQ);
if (ret)
return ret;
ret = clk_prepare_enable(phy->phy_ref_clk);
if (ret)
return ret;
ret = clk_prepare_enable(phy->apb_sys_clk);
if (ret)
goto apb_sys_fail;
ret = clk_prepare_enable(phy->apb_phy_clk);
if (ret)
goto apb_phy_fail;
ret = clk_prepare_enable(phy->aclk);
if (ret)
goto aclk_fail;
ret = clk_prepare_enable(phy->aux_clk);
if (ret)
goto aux_clk_fail;
return 0;
close_clk:
clk_disable_unprepare(phy->aux_clk);
aux_clk_fail:
clk_disable_unprepare(phy->aclk);
aclk_fail:
clk_disable_unprepare(phy->apb_phy_clk);
apb_phy_fail:
clk_disable_unprepare(phy->apb_sys_clk);
apb_sys_fail:
clk_disable_unprepare(phy->phy_ref_clk);
return ret;
}
static int hi3670_pcie_phy_init(struct phy *generic_phy)
{
struct hi3670_pcie_phy *phy = phy_get_drvdata(generic_phy);
int ret;
/*
* The code under hi3670_pcie_get_resources_from_pcie() need to
* access the reset-gpios and the APB registers, both from the
* pcie-kirin driver.
*
* The APB is obtained via the pcie driver's regmap
* Such kind of resource can only be obtained during the PCIe
* power_on sequence, as the code inside pcie-kirin needs to
* be already probed, as it needs to register the APB regmap.
*/
ret = hi3670_pcie_get_resources_from_pcie(phy);
if (ret)
return ret;
return 0;
}
static int hi3670_pcie_phy_power_on(struct phy *generic_phy)
{
struct hi3670_pcie_phy *phy = phy_get_drvdata(generic_phy);
int val, ret;
/* Power supply for Host */
regmap_write(phy->sysctrl, SCTRL_PCIE_CMOS_OFFSET, SCTRL_PCIE_CMOS_BIT);
usleep_range(TIME_CMOS_MIN, TIME_CMOS_MAX);
hi3670_pcie_phy_oe_enable(phy, true);
ret = kirin_pcie_clk_ctrl(phy, true);
if (ret)
return ret;
/* ISO disable, PCIeCtrl, PHY assert and clk gate clear */
regmap_write(phy->sysctrl, SCTRL_PCIE_ISO_OFFSET, SCTRL_PCIE_ISO_BIT);
regmap_write(phy->crgctrl, CRGCTRL_PCIE_ASSERT_OFFSET,
CRGCTRL_PCIE_ASSERT_BIT);
regmap_write(phy->sysctrl, SCTRL_PCIE_HPCLK_OFFSET,
SCTRL_PCIE_HPCLK_BIT);
hi3670_pcie_natural_cfg(phy);
ret = hi3670_pcie_allclk_ctrl(phy, true);
if (ret)
goto disable_clks;
/* pull down phy_test_powerdown signal */
hi3670_apb_phy_updatel(phy, 0, PCIE_PULL_DOWN_PHY_TEST_POWERDOWN,
SOC_PCIEPHY_CTRL0_ADDR);
/* deassert controller perst_n */
regmap_read(phy->apb, SOC_PCIECTRL_CTRL12_ADDR, &val);
val |= PCIE_DEASSERT_CONTROLLER_PERST;
regmap_write(phy->apb, SOC_PCIECTRL_CTRL12_ADDR, val);
udelay(10);
ret = is_pipe_clk_stable(phy);
if (!ret)
goto disable_clks;
hi3670_pcie_set_eyeparam(phy);
ret = hi3670_pcie_noc_power(phy, false);
if (ret)
goto disable_clks;
return 0;
disable_clks:
kirin_pcie_clk_ctrl(phy, false);
return ret;
}
static int hi3670_pcie_phy_power_off(struct phy *generic_phy)
{
struct hi3670_pcie_phy *phy = phy_get_drvdata(generic_phy);
hi3670_pcie_phy_oe_enable(phy, false);
hi3670_pcie_allclk_ctrl(phy, false);
/* Drop power supply for Host */
regmap_write(phy->sysctrl, SCTRL_PCIE_CMOS_OFFSET, 0);
/*
* FIXME: The enabled clocks should be disabled here by calling
* kirin_pcie_clk_ctrl(phy, false);
* However, some clocks used at Kirin 970 should be marked as
* CLK_IS_CRITICAL at clk-hi3670 driver, as powering such clocks off
* cause an Asynchronous SError interrupt, which produces panic().
* While clk-hi3670 is not fixed, we cannot risk disabling clocks here.
*/
return 0;
}
static const struct phy_ops hi3670_phy_ops = {
.init = hi3670_pcie_phy_init,
.power_on = hi3670_pcie_phy_power_on,
.power_off = hi3670_pcie_phy_power_off,
.owner = THIS_MODULE,
};
static int hi3670_pcie_phy_get_resources(struct hi3670_pcie_phy *phy,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
/* syscon */
phy->crgctrl = syscon_regmap_lookup_by_compatible("hisilicon,hi3670-crgctrl");
if (IS_ERR(phy->crgctrl))
return PTR_ERR(phy->crgctrl);
phy->sysctrl = syscon_regmap_lookup_by_compatible("hisilicon,hi3670-sctrl");
if (IS_ERR(phy->sysctrl))
return PTR_ERR(phy->sysctrl);
phy->pmctrl = syscon_regmap_lookup_by_compatible("hisilicon,hi3670-pmctrl");
if (IS_ERR(phy->pmctrl))
return PTR_ERR(phy->pmctrl);
/* clocks */
phy->phy_ref_clk = devm_clk_get(dev, "phy_ref");
if (IS_ERR(phy->phy_ref_clk))
return PTR_ERR(phy->phy_ref_clk);
phy->aux_clk = devm_clk_get(dev, "aux");
if (IS_ERR(phy->aux_clk))
return PTR_ERR(phy->aux_clk);
phy->apb_phy_clk = devm_clk_get(dev, "apb_phy");
if (IS_ERR(phy->apb_phy_clk))
return PTR_ERR(phy->apb_phy_clk);
phy->apb_sys_clk = devm_clk_get(dev, "apb_sys");
if (IS_ERR(phy->apb_sys_clk))
return PTR_ERR(phy->apb_sys_clk);
phy->aclk = devm_clk_get(dev, "aclk");
if (IS_ERR(phy->aclk))
return PTR_ERR(phy->aclk);
/* registers */
phy->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(phy->base))
return PTR_ERR(phy->base);
hi3670_pcie_get_eyeparam(phy);
return 0;
}
static int hi3670_pcie_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct hi3670_pcie_phy *phy;
struct phy *generic_phy;
int ret;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->dev = dev;
ret = hi3670_pcie_phy_get_resources(phy, pdev);
if (ret)
return ret;
generic_phy = devm_phy_create(dev, dev->of_node, &hi3670_phy_ops);
if (IS_ERR(generic_phy)) {
dev_err(dev, "failed to create PHY\n");
return PTR_ERR(generic_phy);
}
phy_set_drvdata(generic_phy, phy);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id hi3670_pcie_phy_match[] = {
{
.compatible = "hisilicon,hi970-pcie-phy",
},
{},
};
static struct platform_driver hi3670_pcie_phy_driver = {
.probe = hi3670_pcie_phy_probe,
.driver = {
.of_match_table = hi3670_pcie_phy_match,
.name = "hi3670_pcie_phy",
.suppress_bind_attrs = true,
}
};
builtin_platform_driver(hi3670_pcie_phy_driver);
MODULE_DEVICE_TABLE(of, hi3670_pcie_phy_match);
MODULE_DESCRIPTION("PCIe phy driver for Kirin 970");
MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@kernel.org>");
MODULE_AUTHOR("Manivannan Sadhasivam <mani@kernel.org>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,662 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Phy provider for USB 3.1 controller on HiSilicon Kirin970 platform
*
* Copyright (C) 2017-2020 Hilisicon Electronics Co., Ltd.
* http://www.huawei.com
*
* Authors: Yu Chen <chenyu56@huawei.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#define SCTRL_SCDEEPSLEEPED (0x0)
#define USB_CLK_SELECTED BIT(20)
#define PERI_CRG_PEREN0 (0x00)
#define PERI_CRG_PERDIS0 (0x04)
#define PERI_CRG_PEREN4 (0x40)
#define PERI_CRG_PERDIS4 (0x44)
#define PERI_CRG_PERRSTEN4 (0x90)
#define PERI_CRG_PERRSTDIS4 (0x94)
#define PERI_CRG_ISODIS (0x148)
#define PERI_CRG_PEREN6 (0x410)
#define PERI_CRG_PERDIS6 (0x414)
#define USB_REFCLK_ISO_EN BIT(25)
#define GT_CLK_USB2PHY_REF BIT(19)
#define PCTRL_PERI_CTRL3 (0x10)
#define PCTRL_PERI_CTRL3_MSK_START (16)
#define USB_TCXO_EN BIT(1)
#define PCTRL_PERI_CTRL24 (0x64)
#define SC_CLK_USB3PHY_3MUX1_SEL BIT(25)
#define USB3OTG_CTRL0 (0x00)
#define USB3OTG_CTRL3 (0x0c)
#define USB3OTG_CTRL4 (0x10)
#define USB3OTG_CTRL5 (0x14)
#define USB3OTG_CTRL7 (0x1c)
#define USB_MISC_CFG50 (0x50)
#define USB_MISC_CFG54 (0x54)
#define USB_MISC_CFG58 (0x58)
#define USB_MISC_CFG5C (0x5c)
#define USB_MISC_CFGA0 (0xa0)
#define TCA_CLK_RST (0x200)
#define TCA_INTR_EN (0x204)
#define TCA_INTR_STS (0x208)
#define TCA_GCFG (0x210)
#define TCA_TCPC (0x214)
#define TCA_SYSMODE_CFG (0x218)
#define TCA_VBUS_CTRL (0x240)
#define CTRL0_USB3_VBUSVLD BIT(7)
#define CTRL0_USB3_VBUSVLD_SEL BIT(6)
#define CTRL3_USB2_VBUSVLDEXT0 BIT(6)
#define CTRL3_USB2_VBUSVLDEXTSEL0 BIT(5)
#define CTRL5_USB2_SIDDQ BIT(0)
#define CTRL7_USB2_REFCLKSEL_MASK GENMASK(4, 3)
#define CTRL7_USB2_REFCLKSEL_ABB (BIT(4) | BIT(3))
#define CTRL7_USB2_REFCLKSEL_PAD BIT(4)
#define CFG50_USB3_PHY_TEST_POWERDOWN BIT(23)
#define CFG54_USB31PHY_CR_ADDR_MASK GENMASK(31, 16)
#define CFG54_USB3PHY_REF_USE_PAD BIT(12)
#define CFG54_PHY0_PMA_PWR_STABLE BIT(11)
#define CFG54_PHY0_PCS_PWR_STABLE BIT(9)
#define CFG54_USB31PHY_CR_ACK BIT(7)
#define CFG54_USB31PHY_CR_WR_EN BIT(5)
#define CFG54_USB31PHY_CR_SEL BIT(4)
#define CFG54_USB31PHY_CR_RD_EN BIT(3)
#define CFG54_USB31PHY_CR_CLK BIT(2)
#define CFG54_USB3_PHY0_ANA_PWR_EN BIT(1)
#define CFG58_USB31PHY_CR_DATA_MASK GENMASK(31, 16)
#define CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN BIT(1)
#define CFGA0_VAUX_RESET BIT(9)
#define CFGA0_USB31C_RESET BIT(8)
#define CFGA0_USB2PHY_REFCLK_SELECT BIT(4)
#define CFGA0_USB3PHY_RESET BIT(1)
#define CFGA0_USB2PHY_POR BIT(0)
#define INTR_EN_XA_TIMEOUT_EVT_EN BIT(1)
#define INTR_EN_XA_ACK_EVT_EN BIT(0)
#define CLK_RST_TCA_REF_CLK_EN BIT(1)
#define CLK_RST_SUSPEND_CLK_EN BIT(0)
#define GCFG_ROLE_HSTDEV BIT(4)
#define GCFG_OP_MODE GENMASK(1, 0)
#define GCFG_OP_MODE_CTRL_SYNC_MODE BIT(0)
#define TCPC_VALID BIT(4)
#define TCPC_LOW_POWER_EN BIT(3)
#define TCPC_MUX_CONTROL_MASK GENMASK(1, 0)
#define TCPC_MUX_CONTROL_USB31 BIT(0)
#define SYSMODE_CFG_TYPEC_DISABLE BIT(3)
#define VBUS_CTRL_POWERPRESENT_OVERRD GENMASK(3, 2)
#define VBUS_CTRL_VBUSVALID_OVERRD GENMASK(1, 0)
#define KIRIN970_USB_DEFAULT_PHY_PARAM (0xfdfee4)
#define KIRIN970_USB_DEFAULT_PHY_VBOOST (0x5)
#define TX_VBOOST_LVL_REG (0xf)
#define TX_VBOOST_LVL_START (6)
#define TX_VBOOST_LVL_ENABLE BIT(9)
struct hi3670_priv {
struct device *dev;
struct regmap *peri_crg;
struct regmap *pctrl;
struct regmap *sctrl;
struct regmap *usb31misc;
u32 eye_diagram_param;
u32 tx_vboost_lvl;
u32 peri_crg_offset;
u32 pctrl_offset;
u32 usb31misc_offset;
};
static int hi3670_phy_cr_clk(struct regmap *usb31misc)
{
int ret;
/* Clock up */
ret = regmap_update_bits(usb31misc, USB_MISC_CFG54,
CFG54_USB31PHY_CR_CLK, CFG54_USB31PHY_CR_CLK);
if (ret)
return ret;
/* Clock down */
return regmap_update_bits(usb31misc, USB_MISC_CFG54,
CFG54_USB31PHY_CR_CLK, 0);
}
static int hi3670_phy_cr_set_sel(struct regmap *usb31misc)
{
return regmap_update_bits(usb31misc, USB_MISC_CFG54,
CFG54_USB31PHY_CR_SEL, CFG54_USB31PHY_CR_SEL);
}
static int hi3670_phy_cr_start(struct regmap *usb31misc, int direction)
{
int ret, reg;
if (direction)
reg = CFG54_USB31PHY_CR_WR_EN;
else
reg = CFG54_USB31PHY_CR_RD_EN;
ret = regmap_update_bits(usb31misc, USB_MISC_CFG54, reg, reg);
if (ret)
return ret;
ret = hi3670_phy_cr_clk(usb31misc);
if (ret)
return ret;
return regmap_update_bits(usb31misc, USB_MISC_CFG54,
CFG54_USB31PHY_CR_RD_EN | CFG54_USB31PHY_CR_WR_EN, 0);
}
static int hi3670_phy_cr_wait_ack(struct regmap *usb31misc)
{
u32 reg;
int retry = 10;
int ret;
while (retry-- > 0) {
ret = regmap_read(usb31misc, USB_MISC_CFG54, &reg);
if (ret)
return ret;
if ((reg & CFG54_USB31PHY_CR_ACK) == CFG54_USB31PHY_CR_ACK)
return 0;
ret = hi3670_phy_cr_clk(usb31misc);
if (ret)
return ret;
usleep_range(10, 20);
}
return -ETIMEDOUT;
}
static int hi3670_phy_cr_set_addr(struct regmap *usb31misc, u32 addr)
{
u32 reg;
int ret;
ret = regmap_read(usb31misc, USB_MISC_CFG54, &reg);
if (ret)
return ret;
reg = FIELD_PREP(CFG54_USB31PHY_CR_ADDR_MASK, addr);
return regmap_update_bits(usb31misc, USB_MISC_CFG54,
CFG54_USB31PHY_CR_ADDR_MASK, reg);
}
static int hi3670_phy_cr_read(struct regmap *usb31misc, u32 addr, u32 *val)
{
int reg, i, ret;
for (i = 0; i < 100; i++) {
ret = hi3670_phy_cr_clk(usb31misc);
if (ret)
return ret;
}
ret = hi3670_phy_cr_set_sel(usb31misc);
if (ret)
return ret;
ret = hi3670_phy_cr_set_addr(usb31misc, addr);
if (ret)
return ret;
ret = hi3670_phy_cr_start(usb31misc, 0);
if (ret)
return ret;
ret = hi3670_phy_cr_wait_ack(usb31misc);
if (ret)
return ret;
ret = regmap_read(usb31misc, USB_MISC_CFG58, &reg);
if (ret)
return ret;
*val = FIELD_GET(CFG58_USB31PHY_CR_DATA_MASK, reg);
return 0;
}
static int hi3670_phy_cr_write(struct regmap *usb31misc, u32 addr, u32 val)
{
int i;
int ret;
for (i = 0; i < 100; i++) {
ret = hi3670_phy_cr_clk(usb31misc);
if (ret)
return ret;
}
ret = hi3670_phy_cr_set_sel(usb31misc);
if (ret)
return ret;
ret = hi3670_phy_cr_set_addr(usb31misc, addr);
if (ret)
return ret;
ret = regmap_write(usb31misc, USB_MISC_CFG58,
FIELD_PREP(CFG58_USB31PHY_CR_DATA_MASK, val));
if (ret)
return ret;
ret = hi3670_phy_cr_start(usb31misc, 1);
if (ret)
return ret;
return hi3670_phy_cr_wait_ack(usb31misc);
}
static int hi3670_phy_set_params(struct hi3670_priv *priv)
{
u32 reg;
int ret;
int retry = 3;
ret = regmap_write(priv->usb31misc, USB3OTG_CTRL4,
priv->eye_diagram_param);
if (ret) {
dev_err(priv->dev, "set USB3OTG_CTRL4 failed\n");
return ret;
}
while (retry-- > 0) {
ret = hi3670_phy_cr_read(priv->usb31misc,
TX_VBOOST_LVL_REG, &reg);
if (!ret)
break;
if (ret != -ETIMEDOUT) {
dev_err(priv->dev, "read TX_VBOOST_LVL_REG failed\n");
return ret;
}
}
if (ret)
return ret;
reg |= (TX_VBOOST_LVL_ENABLE | (priv->tx_vboost_lvl << TX_VBOOST_LVL_START));
ret = hi3670_phy_cr_write(priv->usb31misc, TX_VBOOST_LVL_REG, reg);
if (ret)
dev_err(priv->dev, "write TX_VBOOST_LVL_REG failed\n");
return ret;
}
static bool hi3670_is_abbclk_selected(struct hi3670_priv *priv)
{
u32 reg;
if (!priv->sctrl) {
dev_err(priv->dev, "priv->sctrl is null!\n");
return false;
}
if (regmap_read(priv->sctrl, SCTRL_SCDEEPSLEEPED, &reg)) {
dev_err(priv->dev, "SCTRL_SCDEEPSLEEPED read failed!\n");
return false;
}
if ((reg & USB_CLK_SELECTED) == 0)
return false;
return true;
}
static int hi3670_config_phy_clock(struct hi3670_priv *priv)
{
u32 val, mask;
int ret;
if (!hi3670_is_abbclk_selected(priv)) {
/* usb refclk iso disable */
ret = regmap_write(priv->peri_crg, PERI_CRG_ISODIS,
USB_REFCLK_ISO_EN);
if (ret)
goto out;
/* enable usb_tcxo_en */
ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3,
USB_TCXO_EN |
(USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START));
/* select usbphy clk from abb */
mask = SC_CLK_USB3PHY_3MUX1_SEL;
ret = regmap_update_bits(priv->pctrl,
PCTRL_PERI_CTRL24, mask, 0);
if (ret)
goto out;
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0,
CFGA0_USB2PHY_REFCLK_SELECT, 0);
if (ret)
goto out;
ret = regmap_read(priv->usb31misc, USB3OTG_CTRL7, &val);
if (ret)
goto out;
val &= ~CTRL7_USB2_REFCLKSEL_MASK;
val |= CTRL7_USB2_REFCLKSEL_ABB;
ret = regmap_write(priv->usb31misc, USB3OTG_CTRL7, val);
if (ret)
goto out;
return 0;
}
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG54,
CFG54_USB3PHY_REF_USE_PAD,
CFG54_USB3PHY_REF_USE_PAD);
if (ret)
goto out;
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0,
CFGA0_USB2PHY_REFCLK_SELECT,
CFGA0_USB2PHY_REFCLK_SELECT);
if (ret)
goto out;
ret = regmap_read(priv->usb31misc, USB3OTG_CTRL7, &val);
if (ret)
goto out;
val &= ~CTRL7_USB2_REFCLKSEL_MASK;
val |= CTRL7_USB2_REFCLKSEL_PAD;
ret = regmap_write(priv->usb31misc, USB3OTG_CTRL7, val);
if (ret)
goto out;
ret = regmap_write(priv->peri_crg,
PERI_CRG_PEREN6, GT_CLK_USB2PHY_REF);
if (ret)
goto out;
return 0;
out:
dev_err(priv->dev, "failed to config phy clock ret: %d\n", ret);
return ret;
}
static int hi3670_config_tca(struct hi3670_priv *priv)
{
u32 val, mask;
int ret;
ret = regmap_write(priv->usb31misc, TCA_INTR_STS, 0xffff);
if (ret)
goto out;
ret = regmap_write(priv->usb31misc, TCA_INTR_EN,
INTR_EN_XA_TIMEOUT_EVT_EN | INTR_EN_XA_ACK_EVT_EN);
if (ret)
goto out;
mask = CLK_RST_TCA_REF_CLK_EN | CLK_RST_SUSPEND_CLK_EN;
ret = regmap_update_bits(priv->usb31misc, TCA_CLK_RST, mask, 0);
if (ret)
goto out;
ret = regmap_update_bits(priv->usb31misc, TCA_GCFG,
GCFG_ROLE_HSTDEV | GCFG_OP_MODE,
GCFG_ROLE_HSTDEV | GCFG_OP_MODE_CTRL_SYNC_MODE);
if (ret)
goto out;
ret = regmap_update_bits(priv->usb31misc, TCA_SYSMODE_CFG,
SYSMODE_CFG_TYPEC_DISABLE, 0);
if (ret)
goto out;
ret = regmap_read(priv->usb31misc, TCA_TCPC, &val);
if (ret)
goto out;
val &= ~(TCPC_VALID | TCPC_LOW_POWER_EN | TCPC_MUX_CONTROL_MASK);
val |= (TCPC_VALID | TCPC_MUX_CONTROL_USB31);
ret = regmap_write(priv->usb31misc, TCA_TCPC, val);
if (ret)
goto out;
ret = regmap_write(priv->usb31misc, TCA_VBUS_CTRL,
VBUS_CTRL_POWERPRESENT_OVERRD | VBUS_CTRL_VBUSVALID_OVERRD);
if (ret)
goto out;
return 0;
out:
dev_err(priv->dev, "failed to config phy clock ret: %d\n", ret);
return ret;
}
static int hi3670_phy_init(struct phy *phy)
{
struct hi3670_priv *priv = phy_get_drvdata(phy);
u32 val;
int ret;
/* assert controller */
val = CFGA0_VAUX_RESET | CFGA0_USB31C_RESET |
CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR;
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, 0);
if (ret)
goto out;
ret = hi3670_config_phy_clock(priv);
if (ret)
goto out;
/* Exit from IDDQ mode */
ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL5,
CTRL5_USB2_SIDDQ, 0);
if (ret)
goto out;
/* Release USB31 PHY out of TestPowerDown mode */
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG50,
CFG50_USB3_PHY_TEST_POWERDOWN, 0);
if (ret)
goto out;
/* Deassert phy */
val = CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR;
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, val);
if (ret)
goto out;
usleep_range(100, 120);
/* Tell the PHY power is stable */
val = CFG54_USB3_PHY0_ANA_PWR_EN | CFG54_PHY0_PCS_PWR_STABLE |
CFG54_PHY0_PMA_PWR_STABLE;
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG54,
val, val);
if (ret)
goto out;
ret = hi3670_config_tca(priv);
if (ret)
goto out;
/* Enable SSC */
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFG5C,
CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN,
CFG5C_USB3_PHY0_SS_MPLLA_SSC_EN);
if (ret)
goto out;
/* Deassert controller */
val = CFGA0_VAUX_RESET | CFGA0_USB31C_RESET;
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, val, val);
if (ret)
goto out;
usleep_range(100, 120);
/* Set fake vbus valid signal */
val = CTRL0_USB3_VBUSVLD | CTRL0_USB3_VBUSVLD_SEL;
ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL0, val, val);
if (ret)
goto out;
val = CTRL3_USB2_VBUSVLDEXT0 | CTRL3_USB2_VBUSVLDEXTSEL0;
ret = regmap_update_bits(priv->usb31misc, USB3OTG_CTRL3, val, val);
if (ret)
goto out;
usleep_range(100, 120);
ret = hi3670_phy_set_params(priv);
if (ret)
goto out;
return 0;
out:
dev_err(priv->dev, "failed to init phy ret: %d\n", ret);
return ret;
}
static int hi3670_phy_exit(struct phy *phy)
{
struct hi3670_priv *priv = phy_get_drvdata(phy);
u32 mask;
int ret;
/* Assert phy */
mask = CFGA0_USB3PHY_RESET | CFGA0_USB2PHY_POR;
ret = regmap_update_bits(priv->usb31misc, USB_MISC_CFGA0, mask, 0);
if (ret)
goto out;
if (!hi3670_is_abbclk_selected(priv)) {
/* disable usb_tcxo_en */
ret = regmap_write(priv->pctrl, PCTRL_PERI_CTRL3,
USB_TCXO_EN << PCTRL_PERI_CTRL3_MSK_START);
} else {
ret = regmap_write(priv->peri_crg, PERI_CRG_PERDIS6,
GT_CLK_USB2PHY_REF);
if (ret)
goto out;
}
return 0;
out:
dev_err(priv->dev, "failed to exit phy ret: %d\n", ret);
return ret;
}
static const struct phy_ops hi3670_phy_ops = {
.init = hi3670_phy_init,
.exit = hi3670_phy_exit,
.owner = THIS_MODULE,
};
static int hi3670_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct phy *phy;
struct hi3670_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->peri_crg = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,pericrg-syscon");
if (IS_ERR(priv->peri_crg)) {
dev_err(dev, "no hisilicon,pericrg-syscon\n");
return PTR_ERR(priv->peri_crg);
}
priv->pctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,pctrl-syscon");
if (IS_ERR(priv->pctrl)) {
dev_err(dev, "no hisilicon,pctrl-syscon\n");
return PTR_ERR(priv->pctrl);
}
priv->sctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,sctrl-syscon");
if (IS_ERR(priv->sctrl)) {
dev_err(dev, "no hisilicon,sctrl-syscon\n");
return PTR_ERR(priv->sctrl);
}
/* node of hi3670 phy is a sub-node of usb3_otg_bc */
priv->usb31misc = syscon_node_to_regmap(dev->parent->of_node);
if (IS_ERR(priv->usb31misc)) {
dev_err(dev, "no hisilicon,usb3-otg-bc-syscon\n");
return PTR_ERR(priv->usb31misc);
}
if (of_property_read_u32(dev->of_node, "hisilicon,eye-diagram-param",
&priv->eye_diagram_param))
priv->eye_diagram_param = KIRIN970_USB_DEFAULT_PHY_PARAM;
if (of_property_read_u32(dev->of_node, "hisilicon,tx-vboost-lvl",
&priv->tx_vboost_lvl))
priv->tx_vboost_lvl = KIRIN970_USB_DEFAULT_PHY_VBOOST;
phy = devm_phy_create(dev, NULL, &hi3670_phy_ops);
if (IS_ERR(phy))
return PTR_ERR(phy);
phy_set_drvdata(phy, priv);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id hi3670_phy_of_match[] = {
{ .compatible = "hisilicon,hi3670-usb-phy" },
{ },
};
MODULE_DEVICE_TABLE(of, hi3670_phy_of_match);
static struct platform_driver hi3670_phy_driver = {
.probe = hi3670_phy_probe,
.driver = {
.name = "hi3670-usb-phy",
.of_match_table = hi3670_phy_of_match,
}
};
module_platform_driver(hi3670_phy_driver);
MODULE_AUTHOR("Yu Chen <chenyu56@huawei.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Hilisicon Kirin970 USB31 PHY Driver");
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2015 Linaro Ltd.
* Copyright (c) 2015 HiSilicon Limited.
*/
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/regmap.h>
#define SC_PERIPH_CTRL4 0x00c
#define CTRL4_PICO_SIDDQ BIT(6)
#define CTRL4_PICO_OGDISABLE BIT(8)
#define CTRL4_PICO_VBUSVLDEXT BIT(10)
#define CTRL4_PICO_VBUSVLDEXTSEL BIT(11)
#define CTRL4_OTG_PHY_SEL BIT(21)
#define SC_PERIPH_CTRL5 0x010
#define CTRL5_USBOTG_RES_SEL BIT(3)
#define CTRL5_PICOPHY_ACAENB BIT(4)
#define CTRL5_PICOPHY_BC_MODE BIT(5)
#define CTRL5_PICOPHY_CHRGSEL BIT(6)
#define CTRL5_PICOPHY_VDATSRCEND BIT(7)
#define CTRL5_PICOPHY_VDATDETENB BIT(8)
#define CTRL5_PICOPHY_DCDENB BIT(9)
#define CTRL5_PICOPHY_IDDIG BIT(10)
#define SC_PERIPH_CTRL8 0x018
#define SC_PERIPH_RSTEN0 0x300
#define SC_PERIPH_RSTDIS0 0x304
#define RST0_USBOTG_BUS BIT(4)
#define RST0_POR_PICOPHY BIT(5)
#define RST0_USBOTG BIT(6)
#define RST0_USBOTG_32K BIT(7)
#define EYE_PATTERN_PARA 0x7053348c
struct hi6220_priv {
struct regmap *reg;
struct device *dev;
};
static void hi6220_phy_init(struct hi6220_priv *priv)
{
struct regmap *reg = priv->reg;
u32 val, mask;
val = RST0_USBOTG_BUS | RST0_POR_PICOPHY |
RST0_USBOTG | RST0_USBOTG_32K;
mask = val;
regmap_update_bits(reg, SC_PERIPH_RSTEN0, mask, val);
regmap_update_bits(reg, SC_PERIPH_RSTDIS0, mask, val);
}
static int hi6220_phy_setup(struct hi6220_priv *priv, bool on)
{
struct regmap *reg = priv->reg;
u32 val, mask;
int ret;
if (on) {
val = CTRL5_USBOTG_RES_SEL | CTRL5_PICOPHY_ACAENB;
mask = val | CTRL5_PICOPHY_BC_MODE;
ret = regmap_update_bits(reg, SC_PERIPH_CTRL5, mask, val);
if (ret)
goto out;
val = CTRL4_PICO_VBUSVLDEXT | CTRL4_PICO_VBUSVLDEXTSEL |
CTRL4_OTG_PHY_SEL;
mask = val | CTRL4_PICO_SIDDQ | CTRL4_PICO_OGDISABLE;
ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val);
if (ret)
goto out;
ret = regmap_write(reg, SC_PERIPH_CTRL8, EYE_PATTERN_PARA);
if (ret)
goto out;
} else {
val = CTRL4_PICO_SIDDQ;
mask = val;
ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val);
if (ret)
goto out;
}
return 0;
out:
dev_err(priv->dev, "failed to setup phy ret: %d\n", ret);
return ret;
}
static int hi6220_phy_start(struct phy *phy)
{
struct hi6220_priv *priv = phy_get_drvdata(phy);
return hi6220_phy_setup(priv, true);
}
static int hi6220_phy_exit(struct phy *phy)
{
struct hi6220_priv *priv = phy_get_drvdata(phy);
return hi6220_phy_setup(priv, false);
}
static const struct phy_ops hi6220_phy_ops = {
.init = hi6220_phy_start,
.exit = hi6220_phy_exit,
.owner = THIS_MODULE,
};
static int hi6220_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct phy *phy;
struct hi6220_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->reg = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,peripheral-syscon");
if (IS_ERR(priv->reg)) {
dev_err(dev, "no hisilicon,peripheral-syscon\n");
return PTR_ERR(priv->reg);
}
hi6220_phy_init(priv);
phy = devm_phy_create(dev, NULL, &hi6220_phy_ops);
if (IS_ERR(phy))
return PTR_ERR(phy);
phy_set_drvdata(phy, priv);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id hi6220_phy_of_match[] = {
{.compatible = "hisilicon,hi6220-usb-phy",},
{ },
};
MODULE_DEVICE_TABLE(of, hi6220_phy_of_match);
static struct platform_driver hi6220_phy_driver = {
.probe = hi6220_phy_probe,
.driver = {
.name = "hi6220-usb-phy",
.of_match_table = hi6220_phy_of_match,
}
};
module_platform_driver(hi6220_phy_driver);
MODULE_DESCRIPTION("HISILICON HI6220 USB PHY driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,214 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HiSilicon INNO USB2 PHY Driver.
*
* Copyright (c) 2016-2017 HiSilicon Technologies Co., Ltd.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#define INNO_PHY_PORT_NUM 2
#define REF_CLK_STABLE_TIME 100 /* unit:us */
#define UTMI_CLK_STABLE_TIME 200 /* unit:us */
#define TEST_CLK_STABLE_TIME 2 /* unit:ms */
#define PHY_CLK_STABLE_TIME 2 /* unit:ms */
#define UTMI_RST_COMPLETE_TIME 2 /* unit:ms */
#define POR_RST_COMPLETE_TIME 300 /* unit:us */
#define PHY_TYPE_0 0
#define PHY_TYPE_1 1
#define PHY_TEST_DATA GENMASK(7, 0)
#define PHY_TEST_ADDR_OFFSET 8
#define PHY0_TEST_ADDR GENMASK(15, 8)
#define PHY0_TEST_PORT_OFFSET 16
#define PHY0_TEST_PORT GENMASK(18, 16)
#define PHY0_TEST_WREN BIT(21)
#define PHY0_TEST_CLK BIT(22) /* rising edge active */
#define PHY0_TEST_RST BIT(23) /* low active */
#define PHY1_TEST_ADDR GENMASK(11, 8)
#define PHY1_TEST_PORT_OFFSET 12
#define PHY1_TEST_PORT BIT(12)
#define PHY1_TEST_WREN BIT(13)
#define PHY1_TEST_CLK BIT(14) /* rising edge active */
#define PHY1_TEST_RST BIT(15) /* low active */
#define PHY_CLK_ENABLE BIT(2)
struct hisi_inno_phy_port {
struct reset_control *utmi_rst;
struct hisi_inno_phy_priv *priv;
};
struct hisi_inno_phy_priv {
void __iomem *mmio;
struct clk *ref_clk;
struct reset_control *por_rst;
unsigned int type;
struct hisi_inno_phy_port ports[INNO_PHY_PORT_NUM];
};
static void hisi_inno_phy_write_reg(struct hisi_inno_phy_priv *priv,
u8 port, u32 addr, u32 data)
{
void __iomem *reg = priv->mmio;
u32 val;
u32 value;
if (priv->type == PHY_TYPE_0)
val = (data & PHY_TEST_DATA) |
((addr << PHY_TEST_ADDR_OFFSET) & PHY0_TEST_ADDR) |
((port << PHY0_TEST_PORT_OFFSET) & PHY0_TEST_PORT) |
PHY0_TEST_WREN | PHY0_TEST_RST;
else
val = (data & PHY_TEST_DATA) |
((addr << PHY_TEST_ADDR_OFFSET) & PHY1_TEST_ADDR) |
((port << PHY1_TEST_PORT_OFFSET) & PHY1_TEST_PORT) |
PHY1_TEST_WREN | PHY1_TEST_RST;
writel(val, reg);
value = val;
if (priv->type == PHY_TYPE_0)
value |= PHY0_TEST_CLK;
else
value |= PHY1_TEST_CLK;
writel(value, reg);
writel(val, reg);
}
static void hisi_inno_phy_setup(struct hisi_inno_phy_priv *priv)
{
/* The phy clk is controlled by the port0 register 0x06. */
hisi_inno_phy_write_reg(priv, 0, 0x06, PHY_CLK_ENABLE);
msleep(PHY_CLK_STABLE_TIME);
}
static int hisi_inno_phy_init(struct phy *phy)
{
struct hisi_inno_phy_port *port = phy_get_drvdata(phy);
struct hisi_inno_phy_priv *priv = port->priv;
int ret;
ret = clk_prepare_enable(priv->ref_clk);
if (ret)
return ret;
udelay(REF_CLK_STABLE_TIME);
reset_control_deassert(priv->por_rst);
udelay(POR_RST_COMPLETE_TIME);
/* Set up phy registers */
hisi_inno_phy_setup(priv);
reset_control_deassert(port->utmi_rst);
udelay(UTMI_RST_COMPLETE_TIME);
return 0;
}
static int hisi_inno_phy_exit(struct phy *phy)
{
struct hisi_inno_phy_port *port = phy_get_drvdata(phy);
struct hisi_inno_phy_priv *priv = port->priv;
reset_control_assert(port->utmi_rst);
reset_control_assert(priv->por_rst);
clk_disable_unprepare(priv->ref_clk);
return 0;
}
static const struct phy_ops hisi_inno_phy_ops = {
.init = hisi_inno_phy_init,
.exit = hisi_inno_phy_exit,
.owner = THIS_MODULE,
};
static int hisi_inno_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct hisi_inno_phy_priv *priv;
struct phy_provider *provider;
int i = 0;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->mmio)) {
ret = PTR_ERR(priv->mmio);
return ret;
}
priv->ref_clk = devm_clk_get(dev, NULL);
if (IS_ERR(priv->ref_clk))
return PTR_ERR(priv->ref_clk);
priv->por_rst = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(priv->por_rst))
return PTR_ERR(priv->por_rst);
priv->type = (uintptr_t) of_device_get_match_data(dev);
for_each_child_of_node_scoped(np, child) {
struct reset_control *rst;
struct phy *phy;
rst = of_reset_control_get_exclusive(child, NULL);
if (IS_ERR(rst))
return PTR_ERR(rst);
priv->ports[i].utmi_rst = rst;
priv->ports[i].priv = priv;
phy = devm_phy_create(dev, child, &hisi_inno_phy_ops);
if (IS_ERR(phy))
return PTR_ERR(phy);
phy_set_bus_width(phy, 8);
phy_set_drvdata(phy, &priv->ports[i]);
i++;
if (i >= INNO_PHY_PORT_NUM) {
dev_warn(dev, "Support %d ports in maximum\n", i);
break;
}
}
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(provider);
}
static const struct of_device_id hisi_inno_phy_of_match[] = {
{ .compatible = "hisilicon,inno-usb2-phy",
.data = (void *) PHY_TYPE_0 },
{ .compatible = "hisilicon,hi3798cv200-usb2-phy",
.data = (void *) PHY_TYPE_0 },
{ .compatible = "hisilicon,hi3798mv100-usb2-phy",
.data = (void *) PHY_TYPE_1 },
{ },
};
MODULE_DEVICE_TABLE(of, hisi_inno_phy_of_match);
static struct platform_driver hisi_inno_phy_driver = {
.probe = hisi_inno_phy_probe,
.driver = {
.name = "hisi-inno-phy",
.of_match_table = hisi_inno_phy_of_match,
}
};
module_platform_driver(hisi_inno_phy_driver);
MODULE_DESCRIPTION("HiSilicon INNO USB2 PHY Driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,285 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* COMBPHY driver for HiSilicon STB SoCs
*
* Copyright (C) 2016-2017 HiSilicon Co., Ltd. http://www.hisilicon.com
*
* Authors: Jianguo Sun <sunjianguo1@huawei.com>
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <dt-bindings/phy/phy.h>
#define COMBPHY_MODE_PCIE 0
#define COMBPHY_MODE_USB3 1
#define COMBPHY_MODE_SATA 2
#define COMBPHY_CFG_REG 0x0
#define COMBPHY_BYPASS_CODEC BIT(31)
#define COMBPHY_TEST_WRITE BIT(24)
#define COMBPHY_TEST_DATA_SHIFT 20
#define COMBPHY_TEST_DATA_MASK GENMASK(23, 20)
#define COMBPHY_TEST_ADDR_SHIFT 12
#define COMBPHY_TEST_ADDR_MASK GENMASK(16, 12)
#define COMBPHY_CLKREF_OUT_OEN BIT(0)
struct histb_combphy_mode {
int fixed;
int select;
u32 reg;
u32 shift;
u32 mask;
};
struct histb_combphy_priv {
void __iomem *mmio;
struct regmap *syscon;
struct reset_control *por_rst;
struct clk *ref_clk;
struct phy *phy;
struct histb_combphy_mode mode;
};
static void nano_register_write(struct histb_combphy_priv *priv,
u32 addr, u32 data)
{
void __iomem *reg = priv->mmio + COMBPHY_CFG_REG;
u32 val;
/* Set up address and data for the write */
val = readl(reg);
val &= ~COMBPHY_TEST_ADDR_MASK;
val |= addr << COMBPHY_TEST_ADDR_SHIFT;
val &= ~COMBPHY_TEST_DATA_MASK;
val |= data << COMBPHY_TEST_DATA_SHIFT;
writel(val, reg);
/* Flip strobe control to trigger the write */
val &= ~COMBPHY_TEST_WRITE;
writel(val, reg);
val |= COMBPHY_TEST_WRITE;
writel(val, reg);
}
static int is_mode_fixed(struct histb_combphy_mode *mode)
{
return mode->fixed != PHY_NONE;
}
static int histb_combphy_set_mode(struct histb_combphy_priv *priv)
{
struct histb_combphy_mode *mode = &priv->mode;
struct regmap *syscon = priv->syscon;
u32 hw_sel;
if (is_mode_fixed(mode))
return 0;
switch (mode->select) {
case PHY_TYPE_SATA:
hw_sel = COMBPHY_MODE_SATA;
break;
case PHY_TYPE_PCIE:
hw_sel = COMBPHY_MODE_PCIE;
break;
case PHY_TYPE_USB3:
hw_sel = COMBPHY_MODE_USB3;
break;
default:
return -EINVAL;
}
return regmap_update_bits(syscon, mode->reg, mode->mask,
hw_sel << mode->shift);
}
static int histb_combphy_init(struct phy *phy)
{
struct histb_combphy_priv *priv = phy_get_drvdata(phy);
u32 val;
int ret;
ret = histb_combphy_set_mode(priv);
if (ret)
return ret;
/* Clear bypass bit to enable encoding/decoding */
val = readl(priv->mmio + COMBPHY_CFG_REG);
val &= ~COMBPHY_BYPASS_CODEC;
writel(val, priv->mmio + COMBPHY_CFG_REG);
ret = clk_prepare_enable(priv->ref_clk);
if (ret)
return ret;
reset_control_deassert(priv->por_rst);
/* Enable EP clock */
val = readl(priv->mmio + COMBPHY_CFG_REG);
val |= COMBPHY_CLKREF_OUT_OEN;
writel(val, priv->mmio + COMBPHY_CFG_REG);
/* Need to wait for EP clock stable */
mdelay(5);
/* Configure nano phy registers as suggested by vendor */
nano_register_write(priv, 0x1, 0x8);
nano_register_write(priv, 0xc, 0x9);
nano_register_write(priv, 0x1a, 0x4);
return 0;
}
static int histb_combphy_exit(struct phy *phy)
{
struct histb_combphy_priv *priv = phy_get_drvdata(phy);
u32 val;
/* Disable EP clock */
val = readl(priv->mmio + COMBPHY_CFG_REG);
val &= ~COMBPHY_CLKREF_OUT_OEN;
writel(val, priv->mmio + COMBPHY_CFG_REG);
reset_control_assert(priv->por_rst);
clk_disable_unprepare(priv->ref_clk);
return 0;
}
static const struct phy_ops histb_combphy_ops = {
.init = histb_combphy_init,
.exit = histb_combphy_exit,
.owner = THIS_MODULE,
};
static struct phy *histb_combphy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct histb_combphy_priv *priv = dev_get_drvdata(dev);
struct histb_combphy_mode *mode = &priv->mode;
if (args->args_count < 1) {
dev_err(dev, "invalid number of arguments\n");
return ERR_PTR(-EINVAL);
}
mode->select = args->args[0];
if (mode->select < PHY_TYPE_SATA || mode->select > PHY_TYPE_USB3) {
dev_err(dev, "invalid phy mode select argument\n");
return ERR_PTR(-EINVAL);
}
if (is_mode_fixed(mode) && mode->select != mode->fixed) {
dev_err(dev, "mode select %d mismatch fixed phy mode %d\n",
mode->select, mode->fixed);
return ERR_PTR(-EINVAL);
}
return priv->phy;
}
static int histb_combphy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct histb_combphy_priv *priv;
struct device_node *np = dev->of_node;
struct histb_combphy_mode *mode;
u32 vals[3];
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->mmio)) {
ret = PTR_ERR(priv->mmio);
return ret;
}
priv->syscon = syscon_node_to_regmap(np->parent);
if (IS_ERR(priv->syscon)) {
dev_err(dev, "failed to find peri_ctrl syscon regmap\n");
return PTR_ERR(priv->syscon);
}
mode = &priv->mode;
mode->fixed = PHY_NONE;
ret = of_property_read_u32(np, "hisilicon,fixed-mode", &mode->fixed);
if (ret == 0)
dev_dbg(dev, "found fixed phy mode %d\n", mode->fixed);
ret = of_property_read_u32_array(np, "hisilicon,mode-select-bits",
vals, ARRAY_SIZE(vals));
if (ret == 0) {
if (is_mode_fixed(mode)) {
dev_err(dev, "found select bits for fixed mode phy\n");
return -EINVAL;
}
mode->reg = vals[0];
mode->shift = vals[1];
mode->mask = vals[2];
dev_dbg(dev, "found mode select bits\n");
} else {
if (!is_mode_fixed(mode)) {
dev_err(dev, "no valid select bits found for non-fixed phy\n");
return -ENODEV;
}
}
priv->ref_clk = devm_clk_get(dev, NULL);
if (IS_ERR(priv->ref_clk)) {
dev_err(dev, "failed to find ref clock\n");
return PTR_ERR(priv->ref_clk);
}
priv->por_rst = devm_reset_control_get(dev, NULL);
if (IS_ERR(priv->por_rst)) {
dev_err(dev, "failed to get poweron reset\n");
return PTR_ERR(priv->por_rst);
}
priv->phy = devm_phy_create(dev, NULL, &histb_combphy_ops);
if (IS_ERR(priv->phy)) {
dev_err(dev, "failed to create combphy\n");
return PTR_ERR(priv->phy);
}
dev_set_drvdata(dev, priv);
phy_set_drvdata(priv->phy, priv);
phy_provider = devm_of_phy_provider_register(dev, histb_combphy_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id histb_combphy_of_match[] = {
{ .compatible = "hisilicon,hi3798cv200-combphy" },
{ },
};
MODULE_DEVICE_TABLE(of, histb_combphy_of_match);
static struct platform_driver histb_combphy_driver = {
.probe = histb_combphy_probe,
.driver = {
.name = "combphy",
.of_match_table = histb_combphy_of_match,
},
};
module_platform_driver(histb_combphy_driver);
MODULE_DESCRIPTION("HiSilicon STB COMBPHY driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2014 Linaro Ltd.
* Copyright (c) 2014 HiSilicon Limited.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#define SATA_PHY0_CTLL 0xa0
#define MPLL_MULTIPLIER_SHIFT 1
#define MPLL_MULTIPLIER_MASK 0xfe
#define MPLL_MULTIPLIER_50M 0x3c
#define MPLL_MULTIPLIER_100M 0x1e
#define PHY_RESET BIT(0)
#define REF_SSP_EN BIT(9)
#define SSC_EN BIT(10)
#define REF_USE_PAD BIT(23)
#define SATA_PORT_PHYCTL 0x174
#define SPEED_MODE_MASK 0x6f0000
#define HALF_RATE_SHIFT 16
#define PHY_CONFIG_SHIFT 18
#define GEN2_EN_SHIFT 21
#define SPEED_CTRL BIT(20)
#define SATA_PORT_PHYCTL1 0x148
#define AMPLITUDE_MASK 0x3ffffe
#define AMPLITUDE_GEN3 0x68
#define AMPLITUDE_GEN3_SHIFT 15
#define AMPLITUDE_GEN2 0x56
#define AMPLITUDE_GEN2_SHIFT 8
#define AMPLITUDE_GEN1 0x56
#define AMPLITUDE_GEN1_SHIFT 1
#define SATA_PORT_PHYCTL2 0x14c
#define PREEMPH_MASK 0x3ffff
#define PREEMPH_GEN3 0x20
#define PREEMPH_GEN3_SHIFT 12
#define PREEMPH_GEN2 0x15
#define PREEMPH_GEN2_SHIFT 6
#define PREEMPH_GEN1 0x5
#define PREEMPH_GEN1_SHIFT 0
struct hix5hd2_priv {
void __iomem *base;
struct regmap *peri_ctrl;
};
enum phy_speed_mode {
SPEED_MODE_GEN1 = 0,
SPEED_MODE_GEN2 = 1,
SPEED_MODE_GEN3 = 2,
};
static int hix5hd2_sata_phy_init(struct phy *phy)
{
struct hix5hd2_priv *priv = phy_get_drvdata(phy);
u32 val, data[2];
int ret;
if (priv->peri_ctrl) {
ret = of_property_read_u32_array(phy->dev.of_node,
"hisilicon,power-reg",
&data[0], 2);
if (ret) {
dev_err(&phy->dev, "Fail read hisilicon,power-reg\n");
return ret;
}
regmap_update_bits(priv->peri_ctrl, data[0],
BIT(data[1]), BIT(data[1]));
}
/* reset phy */
val = readl_relaxed(priv->base + SATA_PHY0_CTLL);
val &= ~(MPLL_MULTIPLIER_MASK | REF_USE_PAD);
val |= MPLL_MULTIPLIER_50M << MPLL_MULTIPLIER_SHIFT |
REF_SSP_EN | PHY_RESET;
writel_relaxed(val, priv->base + SATA_PHY0_CTLL);
msleep(20);
val &= ~PHY_RESET;
writel_relaxed(val, priv->base + SATA_PHY0_CTLL);
val = readl_relaxed(priv->base + SATA_PORT_PHYCTL1);
val &= ~AMPLITUDE_MASK;
val |= AMPLITUDE_GEN3 << AMPLITUDE_GEN3_SHIFT |
AMPLITUDE_GEN2 << AMPLITUDE_GEN2_SHIFT |
AMPLITUDE_GEN1 << AMPLITUDE_GEN1_SHIFT;
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL1);
val = readl_relaxed(priv->base + SATA_PORT_PHYCTL2);
val &= ~PREEMPH_MASK;
val |= PREEMPH_GEN3 << PREEMPH_GEN3_SHIFT |
PREEMPH_GEN2 << PREEMPH_GEN2_SHIFT |
PREEMPH_GEN1 << PREEMPH_GEN1_SHIFT;
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL2);
/* ensure PHYCTRL setting takes effect */
val = readl_relaxed(priv->base + SATA_PORT_PHYCTL);
val &= ~SPEED_MODE_MASK;
val |= SPEED_MODE_GEN1 << HALF_RATE_SHIFT |
SPEED_MODE_GEN1 << PHY_CONFIG_SHIFT |
SPEED_MODE_GEN1 << GEN2_EN_SHIFT | SPEED_CTRL;
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
msleep(20);
val &= ~SPEED_MODE_MASK;
val |= SPEED_MODE_GEN3 << HALF_RATE_SHIFT |
SPEED_MODE_GEN3 << PHY_CONFIG_SHIFT |
SPEED_MODE_GEN3 << GEN2_EN_SHIFT | SPEED_CTRL;
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
val &= ~(SPEED_MODE_MASK | SPEED_CTRL);
val |= SPEED_MODE_GEN2 << HALF_RATE_SHIFT |
SPEED_MODE_GEN2 << PHY_CONFIG_SHIFT |
SPEED_MODE_GEN2 << GEN2_EN_SHIFT;
writel_relaxed(val, priv->base + SATA_PORT_PHYCTL);
return 0;
}
static const struct phy_ops hix5hd2_sata_phy_ops = {
.init = hix5hd2_sata_phy_init,
.owner = THIS_MODULE,
};
static int hix5hd2_sata_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct resource *res;
struct phy *phy;
struct hix5hd2_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EINVAL;
priv->base = devm_ioremap(dev, res->start, resource_size(res));
if (!priv->base)
return -ENOMEM;
priv->peri_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node,
"hisilicon,peripheral-syscon");
if (IS_ERR(priv->peri_ctrl))
priv->peri_ctrl = NULL;
phy = devm_phy_create(dev, NULL, &hix5hd2_sata_phy_ops);
if (IS_ERR(phy)) {
dev_err(dev, "failed to create PHY\n");
return PTR_ERR(phy);
}
phy_set_drvdata(phy, priv);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct of_device_id hix5hd2_sata_phy_of_match[] = {
{.compatible = "hisilicon,hix5hd2-sata-phy",},
{ },
};
MODULE_DEVICE_TABLE(of, hix5hd2_sata_phy_of_match);
static struct platform_driver hix5hd2_sata_phy_driver = {
.probe = hix5hd2_sata_phy_probe,
.driver = {
.name = "hix5hd2-sata-phy",
.of_match_table = hix5hd2_sata_phy_of_match,
}
};
module_platform_driver(hix5hd2_sata_phy_driver);
MODULE_AUTHOR("Jiancheng Xue <xuejiancheng@huawei.com>");
MODULE_DESCRIPTION("HISILICON HIX5HD2 SATA PHY driver");
MODULE_ALIAS("platform:hix5hd2-sata-phy");
MODULE_LICENSE("GPL v2");