diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/phy/hisilicon | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/phy/hisilicon')
-rw-r--r-- | drivers/phy/hisilicon/Kconfig | 71 | ||||
-rw-r--r-- | drivers/phy/hisilicon/Makefile | 8 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hi3660-usb3.c | 234 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hi3670-pcie.c | 845 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hi3670-usb3.c | 662 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hi6220-usb.c | 165 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hisi-inno-usb2.c | 220 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-histb-combphy.c | 285 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hix5hd2-sata.c | 188 |
9 files changed, 2678 insertions, 0 deletions
diff --git a/drivers/phy/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig new file mode 100644 index 0000000000..6c89136fc8 --- /dev/null +++ b/drivers/phy/hisilicon/Kconfig @@ -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. diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile new file mode 100644 index 0000000000..4029d3813b --- /dev/null +++ b/drivers/phy/hisilicon/Makefile @@ -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 diff --git a/drivers/phy/hisilicon/phy-hi3660-usb3.c b/drivers/phy/hisilicon/phy-hi3660-usb3.c new file mode 100644 index 0000000000..e2a09d67fa --- /dev/null +++ b/drivers/phy/hisilicon/phy-hi3660-usb3.c @@ -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"); diff --git a/drivers/phy/hisilicon/phy-hi3670-pcie.c b/drivers/phy/hisilicon/phy-hi3670-pcie.c new file mode 100644 index 0000000000..0ac9634b39 --- /dev/null +++ b/drivers/phy/hisilicon/phy-hi3670-pcie.c @@ -0,0 +1,845 @@ +// 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/clk.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_gpio.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.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"); diff --git a/drivers/phy/hisilicon/phy-hi3670-usb3.c b/drivers/phy/hisilicon/phy-hi3670-usb3.c new file mode 100644 index 0000000000..40d3cf128b --- /dev/null +++ b/drivers/phy/hisilicon/phy-hi3670-usb3.c @@ -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, ®); + 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, ®); + 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, ®); + 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, ®); + 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, ®)) { + 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"); diff --git a/drivers/phy/hisilicon/phy-hi6220-usb.c b/drivers/phy/hisilicon/phy-hi6220-usb.c new file mode 100644 index 0000000000..97bd363dfe --- /dev/null +++ b/drivers/phy/hisilicon/phy-hi6220-usb.c @@ -0,0 +1,165 @@ +// 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/mod_devicetable.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_ALIAS("platform:hi6220-usb-phy"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/hisilicon/phy-hisi-inno-usb2.c b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c new file mode 100644 index 0000000000..c138cd4807 --- /dev/null +++ b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c @@ -0,0 +1,220 @@ +// 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; + struct device_node *child; + 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(np, child) { + struct reset_control *rst; + struct phy *phy; + + rst = of_reset_control_get_exclusive(child, NULL); + if (IS_ERR(rst)) { + of_node_put(child); + 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)) { + of_node_put(child); + 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); + of_node_put(child); + 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"); diff --git a/drivers/phy/hisilicon/phy-histb-combphy.c b/drivers/phy/hisilicon/phy-histb-combphy.c new file mode 100644 index 0000000000..c44588fd5a --- /dev/null +++ b/drivers/phy/hisilicon/phy-histb-combphy.c @@ -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) ? true : false; +} + +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, + 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"); diff --git a/drivers/phy/hisilicon/phy-hix5hd2-sata.c b/drivers/phy/hisilicon/phy-hix5hd2-sata.c new file mode 100644 index 0000000000..1b26ddb4c8 --- /dev/null +++ b/drivers/phy/hisilicon/phy-hix5hd2-sata.c @@ -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"); |