armbian-build/patch/kernel/archive/sunxi-6.18/patches.armbian/drv-phy-allwinner-add-pcie-usb3-driver.patch

1239 lines
33 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Marvin Wewer <mwewer37@proton.me>
Date: Fri, 27 Jun 2025 12:40:09 +0200
Subject: phy: allwinner: add INNO PCIe/USB3 combo PHY driver
Signed-off-by: Marvin Wewer <mwewer37@proton.me>
---
drivers/phy/allwinner/Kconfig | 8 +
drivers/phy/allwinner/Makefile | 1 +
drivers/phy/allwinner/sunxi-inno-combophy.c | 1193 ++++++++++
3 files changed, 1202 insertions(+)
diff --git a/drivers/phy/allwinner/Kconfig b/drivers/phy/allwinner/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/phy/allwinner/Kconfig
+++ b/drivers/phy/allwinner/Kconfig
@@ -67,3 +67,11 @@ config AC200_PHY_CTL
Enable this to support the Ethernet PHY operation of the AC200
mixed signal chip. This driver just enables and configures the
PHY, the PHY itself is supported by a standard driver.
+
+config AW_INNO_COMBOPHY
+ tristate "Allwinner INNO COMBO PHY Driver"
+ depends on ARCH_SUNXI && OF
+ select GENERIC_PHY
+ help
+ Enable this to support the Allwinner PCIe/USB3.0 combo PHY
+ with INNOSILICON IP block.
diff --git a/drivers/phy/allwinner/Makefile b/drivers/phy/allwinner/Makefile
index 111111111111..222222222222 100644
--- a/drivers/phy/allwinner/Makefile
+++ b/drivers/phy/allwinner/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_PHY_SUN6I_MIPI_DPHY) += phy-sun6i-mipi-dphy.o
obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o
obj-$(CONFIG_PHY_SUN50I_USB3) += phy-sun50i-usb3.o
obj-$(CONFIG_AC200_PHY_CTL) += ac200-ephy-ctl.o
+obj-$(CONFIG_AW_INNO_COMBOPHY) += sunxi-inno-combophy.o
diff --git a/drivers/phy/allwinner/sunxi-inno-combophy.c b/drivers/phy/allwinner/sunxi-inno-combophy.c
new file mode 100644
index 000000000000..111111111111
--- /dev/null
+++ b/drivers/phy/allwinner/sunxi-inno-combophy.c
@@ -0,0 +1,1193 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
+/*
+ * Allwinner PIPE USB3.0 PCIE Combo Phy driver
+ *
+ * Copyright (C) 2022 Allwinner Electronics Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/reset.h>
+#include <linux/notifier.h>
+#include <dt-bindings/phy/phy.h>
+
+
+/* PCIE USB3 Sub-System Registers */
+/* Sub-System Version Reset Register */
+#define PCIE_USB3_SYS_VER 0x00
+
+/* Sub-System PCIE Bus Gating Reset Register */
+#define PCIE_COMBO_PHY_BGR 0x04
+#define PCIE_SLV_ACLK_EN BIT(18)
+#define PCIE_ACLK_EN BIT(17)
+#define PCIE_HCLK_EN BIT(16)
+#define PCIE_PERSTN BIT(1)
+#define PCIE_PW_UP_RSTN BIT(0)
+
+/* Sub-System USB3 Bus Gating Reset Register */
+#define USB3_COMBO_PHY_BGR 0x08
+#define USB3_ACLK_EN BIT(17)
+#define USB3_HCLK_EN BIT(16)
+#define USB3_U2_PHY_RSTN BIT(4)
+#define USB3_U2_PHY_MUX_EN BIT(3)
+#define USB3_U2_PHY_MUX_SEL BIT(0)
+#define USB3_RESETN BIT(0)
+
+/* Sub-System PCIE PHY Control Register */
+#define PCIE_COMBO_PHY_CTL 0x10
+#define PHY_USE_SEL BIT(31) /* 0:PCIE; 1:USB3 */
+#define PHY_CLK_SEL BIT(30) /* 0:internal clk; 1:external clk */
+#define PHY_BIST_EN BIT(16)
+#define PHY_PIPE_SW BIT(9)
+#define PHY_PIPE_SEL BIT(8) /* 0:rstn by PCIE or USB3; 1:rstn by PHY_PIPE_SW */
+#define PHY_PIPE_CLK_INVERT BIT(4)
+#define PHY_FPGA_SYS_RSTN BIT(1) /* for FPGA */
+#define PHY_RSTN BIT(0)
+
+/* Registers */
+#define COMBO_REG_SYSVER(comb_base_addr) ((comb_base_addr) \
+ + PCIE_USB3_SYS_VER)
+#define COMBO_REG_PCIEBGR(comb_base_addr) ((comb_base_addr) \
+ + PCIE_COMBO_PHY_BGR)
+#define COMBO_REG_USB3BGR(comb_base_addr) ((comb_base_addr) \
+ + USB3_COMBO_PHY_BGR)
+#define COMBO_REG_PHYCTRL(comb_base_addr) ((comb_base_addr) \
+ + PCIE_COMBO_PHY_CTL)
+/* Sub-System Version Number */
+#define COMBO_VERSION_01 (0x10000)
+#define COMBO_VERSION_ANY (0x0)
+
+#define KEY_PHY_USE_SEL "phy_use_sel"
+#define KEY_PHY_REFCLK_SEL "phy_refclk_sel"
+
+enum phy_use_sel {
+ PHY_USE_BY_PCIE = 0, /* PHY used by PCIE */
+ PHY_USE_BY_USB3, /* PHY used by USB3 */
+ PHY_USE_BY_PCIE_USB3_U2,/* PHY used by PCIE & USB3_U2 */
+};
+
+enum phy_refclk_sel {
+ INTER_SIG_REF_CLK = 0, /* PHY use internal single end reference clock */
+ EXTER_DIF_REF_CLK, /* PHY use external single end reference clock */
+};
+
+extern struct atomic_notifier_head inno_subsys_notifier_list;
+
+struct sunxi_combophy_of_data {
+ bool has_cfg_clk;
+ bool has_slv_clk;
+ bool has_phy_mbus_clk;
+ bool has_phy_ahb_clk;
+ bool has_pcie_axi_clk;
+ bool has_u2_phy_mux;
+ bool need_noppu_rst;
+ bool has_u3_phy_data_quirk;
+ bool need_optimize_jitter;
+};
+
+struct sunxi_combphy {
+ struct device *dev;
+ struct phy *phy;
+ void __iomem *phy_ctl; /* parse dts, control the phy mode, reset and power */
+ void __iomem *phy_clk; /* parse dts, set the phy clock */
+ struct reset_control *reset;
+ struct reset_control *noppu_reset;
+
+ struct clk *phyclk_ref;
+ struct clk *refclk_par;
+ struct clk *phyclk_cfg;
+ struct clk *cfgclk_par;
+ struct clk *phy_mclk;
+ struct clk *phy_hclk;
+ struct clk *phy_axi;
+ struct clk *phy_axi_par;
+ __u8 mode;
+ __u32 vernum; /* version number */
+ enum phy_use_sel user;
+ enum phy_refclk_sel ref;
+ struct notifier_block pwr_nb;
+ const struct sunxi_combophy_of_data *drvdata;
+
+ struct regulator *select3v3_supply;
+ bool initialized;
+};
+
+ATOMIC_NOTIFIER_HEAD(inno_subsys_notifier_list);
+EXPORT_SYMBOL(inno_subsys_notifier_list);
+
+/* PCIE USB3 Sub-system Application */
+static void combo_pcie_clk_set(struct sunxi_combphy *combphy, bool enable)
+{
+ u32 val, tmp = 0;
+
+ val = readl(COMBO_REG_PCIEBGR(combphy->phy_ctl));
+ if (combphy->drvdata->has_slv_clk)
+ tmp = PCIE_SLV_ACLK_EN | PCIE_ACLK_EN | PCIE_HCLK_EN | PCIE_PERSTN | PCIE_PW_UP_RSTN;
+ else
+ tmp = PCIE_ACLK_EN | PCIE_HCLK_EN | PCIE_PERSTN | PCIE_PW_UP_RSTN;
+ if (enable)
+ val |= tmp;
+ else
+ val &= ~tmp;
+ writel(val, COMBO_REG_PCIEBGR(combphy->phy_ctl));
+}
+
+static void combo_usb3_clk_set(struct sunxi_combphy *combphy, bool enable)
+{
+ u32 val, tmp = 0;
+
+ val = readl(COMBO_REG_USB3BGR(combphy->phy_ctl));
+ if (combphy->drvdata->has_u2_phy_mux)
+ tmp = USB3_ACLK_EN | USB3_HCLK_EN | USB3_U2_PHY_MUX_SEL | USB3_U2_PHY_RSTN | USB3_U2_PHY_MUX_EN;
+ else
+ tmp = USB3_ACLK_EN | USB3_HCLK_EN | USB3_RESETN;
+ if (enable)
+ val |= tmp;
+ else
+ val &= ~tmp;
+ writel(val, COMBO_REG_USB3BGR(combphy->phy_ctl));
+}
+
+static void combo_phy_mode_set(struct sunxi_combphy *combphy, bool enable)
+{
+ u32 val;
+
+ val = readl(COMBO_REG_PHYCTRL(combphy->phy_ctl));
+
+ if (combphy->user == PHY_USE_BY_PCIE || combphy->user == PHY_USE_BY_PCIE_USB3_U2)
+ val &= ~PHY_USE_SEL;
+ else if (combphy->user == PHY_USE_BY_USB3)
+ val |= PHY_USE_SEL;
+
+ if (combphy->ref == INTER_SIG_REF_CLK)
+ val &= ~PHY_CLK_SEL;
+ else if (combphy->ref == EXTER_DIF_REF_CLK)
+ val |= PHY_CLK_SEL;
+
+ if (enable)
+ val |= PHY_RSTN;
+ else
+ val &= ~PHY_RSTN;
+
+ writel(val, COMBO_REG_PHYCTRL(combphy->phy_ctl));
+}
+
+static u32 combo_sysver_get(struct sunxi_combphy *combphy)
+{
+ u32 reg;
+
+ reg = readl(COMBO_REG_SYSVER(combphy->phy_ctl));
+
+ return reg;
+}
+
+static void pcie_usb3_sub_system_enable(struct sunxi_combphy *combphy)
+{
+ combo_phy_mode_set(combphy, true);
+
+ if (combphy->user == PHY_USE_BY_PCIE)
+ combo_pcie_clk_set(combphy, true);
+ else if (combphy->user == PHY_USE_BY_USB3)
+ combo_usb3_clk_set(combphy, true);
+ else if (combphy->user == PHY_USE_BY_PCIE_USB3_U2) {
+ combo_pcie_clk_set(combphy, true);
+ combo_usb3_clk_set(combphy, true);
+ }
+
+ combphy->vernum = combo_sysver_get(combphy);
+}
+
+static void pcie_usb3_sub_system_disable(struct sunxi_combphy *combphy)
+{
+ combo_phy_mode_set(combphy, false);
+
+ if (combphy->user == PHY_USE_BY_PCIE)
+ combo_pcie_clk_set(combphy, false);
+ else if (combphy->user == PHY_USE_BY_USB3)
+ combo_usb3_clk_set(combphy, false);
+ else if (combphy->user == PHY_USE_BY_PCIE_USB3_U2) {
+ combo_pcie_clk_set(combphy, false);
+ combo_usb3_clk_set(combphy, false);
+ }
+}
+
+static int sunxi_combphy_enable_clocks(struct sunxi_combphy *combphy)
+{
+ int ret;
+ struct device *dev = combphy->dev;
+
+ if (!IS_ERR_OR_NULL(combphy->phyclk_ref)) {
+ ret = clk_prepare_enable(combphy->phyclk_ref);
+ if (ret) return ret;
+ }
+
+ if (combphy->drvdata->has_cfg_clk && !IS_ERR_OR_NULL(combphy->phyclk_cfg)) {
+ ret = clk_prepare_enable(combphy->phyclk_cfg);
+ if (ret) goto err_cfg;
+ }
+
+ if (combphy->drvdata->has_phy_ahb_clk && !IS_ERR_OR_NULL(combphy->phy_hclk)) {
+ ret = clk_prepare_enable(combphy->phy_hclk);
+ if (ret) {
+ dev_err(dev, "cannot enable phy_hclk\n");
+ goto err_hclk;
+ }
+ }
+
+ if (combphy->drvdata->has_pcie_axi_clk && !IS_ERR_OR_NULL(combphy->phy_axi)) {
+ ret = clk_prepare_enable(combphy->phy_axi);
+ if (ret) {
+ dev_err(dev, "cannot enable phy_axi\n");
+ goto err_axi;
+ }
+ }
+
+ if (combphy->drvdata->has_phy_mbus_clk && !IS_ERR_OR_NULL(combphy->phy_mclk)) {
+ ret = clk_prepare_enable(combphy->phy_mclk);
+ if (ret) {
+ dev_err(dev, "cannot enable phy_mclk\n");
+ goto err_mclk;
+ }
+ }
+
+ return 0;
+
+err_mclk:
+ clk_disable_unprepare(combphy->phy_axi);
+err_axi:
+ clk_disable_unprepare(combphy->phy_hclk);
+err_hclk:
+ clk_disable_unprepare(combphy->phyclk_cfg);
+err_cfg:
+ clk_disable_unprepare(combphy->phyclk_ref);
+ return ret;
+}
+
+static int sunxi_combphy_reset_deassert(struct sunxi_combphy *combphy)
+{
+ int ret;
+
+ if (!IS_ERR_OR_NULL(combphy->reset)) {
+ ret = reset_control_deassert(combphy->reset);
+ if (ret)
+ return ret;
+ }
+
+ if (combphy->drvdata->need_noppu_rst && !IS_ERR_OR_NULL(combphy->noppu_reset)) {
+ ret = reset_control_deassert(combphy->noppu_reset);
+ if (ret) {
+ if (!IS_ERR_OR_NULL(combphy->reset))
+ reset_control_assert(combphy->reset);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int pcie_usb3_sub_system_init(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sunxi_combphy *combphy = platform_get_drvdata(pdev);
+ bool already_enabled = false;
+ int ret;
+
+ if (!combphy || combphy->initialized)
+ return 0;
+
+
+ if (combphy->phy_ctl) {
+ if (readl(combphy->phy_ctl + PCIE_COMBO_PHY_CTL) & PHY_RSTN) {
+ dev_info(dev, "PHY already enabled by Bootloader.\n");
+ already_enabled = true;
+ }
+ }
+
+
+ if (!IS_ERR_OR_NULL(combphy->select3v3_supply)) {
+ ret = regulator_enable(combphy->select3v3_supply);
+ if (ret) return ret;
+ }
+
+
+ if (!already_enabled) {
+
+ ret = sunxi_combphy_reset_deassert(combphy);
+ if (ret) goto err_regulator;
+
+
+ if (!IS_ERR_OR_NULL(combphy->phyclk_ref))
+ clk_set_rate(combphy->phyclk_ref, 100000000);
+
+ if (combphy->drvdata->has_cfg_clk && !IS_ERR_OR_NULL(combphy->phyclk_cfg))
+ clk_set_rate(combphy->phyclk_cfg, 200000000);
+ }
+
+
+ ret = sunxi_combphy_enable_clocks(combphy);
+ if (ret) goto err_regulator;
+
+
+ if (!already_enabled)
+ pcie_usb3_sub_system_enable(combphy);
+
+ combphy->initialized = true;
+ return 0;
+
+err_regulator:
+ if (!IS_ERR_OR_NULL(combphy->select3v3_supply))
+ regulator_disable(combphy->select3v3_supply);
+ return ret;
+}
+
+static int pcie_usb3_sub_system_exit(struct platform_device *pdev)
+{
+ struct sunxi_combphy *combphy = platform_get_drvdata(pdev);
+
+ if (!combphy || !combphy->initialized)
+ return 0;
+
+
+ pcie_usb3_sub_system_disable(combphy);
+
+
+ if (combphy->drvdata->has_phy_mbus_clk && !IS_ERR_OR_NULL(combphy->phy_mclk))
+ clk_disable_unprepare(combphy->phy_mclk);
+
+ if (combphy->drvdata->has_pcie_axi_clk && !IS_ERR_OR_NULL(combphy->phy_axi))
+ clk_disable_unprepare(combphy->phy_axi);
+
+ if (combphy->drvdata->has_phy_ahb_clk && !IS_ERR_OR_NULL(combphy->phy_hclk))
+ clk_disable_unprepare(combphy->phy_hclk);
+
+ if (combphy->drvdata->has_cfg_clk && !IS_ERR_OR_NULL(combphy->phyclk_cfg))
+ clk_disable_unprepare(combphy->phyclk_cfg);
+
+ if (!IS_ERR_OR_NULL(combphy->phyclk_ref))
+ clk_disable_unprepare(combphy->phyclk_ref);
+
+
+ if (combphy->drvdata->need_noppu_rst && !IS_ERR_OR_NULL(combphy->noppu_reset))
+ reset_control_assert(combphy->noppu_reset);
+
+ if (!IS_ERR_OR_NULL(combphy->reset))
+ reset_control_assert(combphy->reset);
+
+
+ if (!IS_ERR_OR_NULL(combphy->select3v3_supply))
+ regulator_disable(combphy->select3v3_supply);
+
+ combphy->initialized = false;
+ return 0;
+}
+
+static int sunxi_inno_combophy_power_event(struct notifier_block *nb, unsigned long event, void *p)
+{
+ struct sunxi_combphy *combphy = container_of(nb, struct sunxi_combphy, pwr_nb);
+ struct platform_device *pdev = to_platform_device(combphy->dev);
+
+ dev_dbg(combphy->dev, "event %s\n", event ? "on" : "off");
+ if (event) {
+ if (combphy->initialized) {
+ pcie_usb3_sub_system_exit(pdev);
+ }
+ pcie_usb3_sub_system_init(pdev);
+ }
+ else
+ pcie_usb3_sub_system_exit(pdev);
+
+ return NOTIFY_DONE;
+}
+
+static void sunxi_combphy_pcie_phy_enable(struct sunxi_combphy *combphy)
+{
+ u32 val;
+
+ /* Enable clocks and power for PCIe */
+ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_BGR);
+ val &= ~((0x03 << 0) | (0x07 << 16)); /* Clear power/reset and clock bits */
+ val |= (0x03 << 0); /* Set PCIE_PERSTN and PCIE_PW_UP_RSTN */
+ if (combphy->drvdata->has_slv_clk)
+ val |= (0x07 << 16); /* Enable all clocks: SLV_ACLK, ACLK, HCLK */
+ else
+ val |= (0x03 << 16); /* Enable ACLK, HCLK */
+ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_BGR);
+
+ /* Assert PHY reset */
+ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_CTL);
+ val &= ~PHY_USE_SEL; /* Select PCIe mode */
+ val &= ~(0x03 << 8); /* Clear PHY_PIPE_SEL and PHY_PIPE_SW */
+ val &= ~PHY_RSTN; /* Assert PHY reset */
+ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_CTL);
+
+ /* Wait for reset to propagate */
+ udelay(10);
+
+ /* De-assert PHY reset */
+ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_CTL);
+ val &= ~PHY_CLK_SEL; /* Select internal clock */
+ val |= PHY_RSTN; /* De-assert PHY reset */
+ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_CTL);
+}
+
+static void sunxi_combphy_usb3_phy_set(struct sunxi_combphy *combphy, bool enable)
+{
+ u32 val, tmp = 0;
+
+ val = readl(combphy->phy_clk + 0x1418);
+ tmp = GENMASK(17, 16);
+ if (enable) {
+ val &= ~tmp;
+ val |= BIT(25);
+ } else {
+ val |= tmp;
+ val &= ~BIT(25);
+ }
+ writel(val, combphy->phy_clk + 0x1418);
+
+ /* reg_rx_eq_bypass[3]=1, rx_ctle_res_cal_bypass */
+ val = readl(combphy->phy_clk + 0x0674);
+ if (enable)
+ val |= BIT(3);
+ else
+ val &= ~BIT(3);
+ writel(val, combphy->phy_clk + 0x0674);
+
+ /* rx_ctle_res_cal=0xf, 0x4->0xf */
+ val = readl(combphy->phy_clk + 0x0704);
+ tmp = GENMASK(9, 8) | BIT(11);
+ if (enable)
+ val |= tmp;
+ else
+ val &= ~tmp;
+ writel(val, combphy->phy_clk + 0x0704);
+
+ /* CDR_div_fin_gain1 */
+ val = readl(combphy->phy_clk + 0x0400);
+ if (enable)
+ val |= BIT(4);
+ else
+ val &= ~BIT(4);
+ writel(val, combphy->phy_clk + 0x0400);
+
+ /* CDR_div1_fin_gain1 */
+ val = readl(combphy->phy_clk + 0x0404);
+ tmp = GENMASK(3, 0) | BIT(5);
+ if (enable)
+ val |= tmp;
+ else
+ val &= ~tmp;
+ writel(val, combphy->phy_clk + 0x0404);
+
+ /* CDR_div3_fin_gain1 */
+ val = readl(combphy->phy_clk + 0x0408);
+ if (enable)
+ val |= BIT(5);
+ else
+ val &= ~BIT(5);
+ writel(val, combphy->phy_clk + 0x0408);
+
+ val = readl(combphy->phy_clk + 0x109c);
+ if (enable)
+ val |= BIT(1);
+ else
+ val &= ~BIT(1);
+ writel(val, combphy->phy_clk + 0x109c);
+
+ /* balance parm configure */
+ if (combphy->drvdata->has_u3_phy_data_quirk) {
+ val = readl(combphy->phy_clk + 0x0804);
+ if (enable)
+ val |= (0x6<<4);
+ else
+ val &= ~(0xf<<4);
+ writel(val, combphy->phy_clk + 0x0804);
+ }
+
+ /* SSC configure */
+ val = readl(combphy->phy_clk + 0x107c);
+ tmp = 0x3f << 12;
+ val = val & (~tmp);
+ val |= ((0x1 << 12) & tmp); /* div_N */
+ writel(val, combphy->phy_clk + 0x107c);
+
+ val = readl(combphy->phy_clk + 0x1020);
+ tmp = 0x1f << 0;
+ val = val & (~tmp);
+ val |= ((0x6 << 0) & tmp); /* modulation freq div */
+ writel(val, combphy->phy_clk + 0x1020);
+
+ val = readl(combphy->phy_clk + 0x1034);
+ tmp = 0x7f << 16;
+ val = val & (~tmp);
+ val |= ((0x9 << 16) & tmp); /* spread[6:0], 400*9=4410ppm ssc */
+ writel(val, combphy->phy_clk + 0x1034);
+
+ val = readl(combphy->phy_clk + 0x101c);
+ tmp = 0x1 << 27;
+ val = val & (~tmp);
+ val |= ((0x1 << 27) & tmp); /* choose downspread */
+
+ tmp = 0x1 << 28;
+ val = val & (~tmp);
+ if (enable)
+ val |= ((0x0 << 28) & tmp); /* don't disable ssc = 0 */
+ else
+ val |= ((0x1 << 28) & tmp); /* don't enable ssc = 1 */
+ writel(val, combphy->phy_clk + 0x101c);
+
+#ifdef SUNXI_INNO_COMMBOPHY_DEBUG
+ /* TX Eye configure bypass_en */
+ val = readl(combphy->phy_clk + 0x0ddc);
+ if (enable)
+ val |= BIT(4); /* 0x0ddc[4]=1 */
+ else
+ val &= ~BIT(4);
+ writel(val, combphy->phy_clk + 0x0ddc);
+
+ /* Leg_cur[6:0] - 7'd84 */
+ val = readl(combphy->phy_clk + 0x0ddc);
+ val |= ((0x54 & BIT(6)) >> 3); /* 0x0ddc[3] */
+ writel(val, combphy->phy_clk + 0x0ddc);
+
+ val = readl(combphy->phy_clk + 0x0de0);
+ val |= ((0x54 & GENMASK(5, 0)) << 2); /* 0x0de0[7:2] */
+ writel(val, combphy->phy_clk + 0x0de0);
+
+ /* Leg_curb[5:0] - 6'd18 */
+ val = readl(combphy->phy_clk + 0x0de4);
+ val |= ((0x12 & GENMASK(5, 1)) >> 1); /* 0x0de4[4:0] */
+ writel(val, combphy->phy_clk + 0x0de4);
+
+ val = readl(combphy->phy_clk + 0x0de8);
+ val |= ((0x12 & BIT(0)) << 7); /* 0x0de8[7] */
+ writel(val, combphy->phy_clk + 0x0de8);
+
+ /* Exswing_isel */
+ val = readl(combphy->phy_clk + 0x0028);
+ val |= (0x4 << 28); /* 0x28[30:28] */
+ writel(val, combphy->phy_clk + 0x0028);
+
+ /* Exswing_en */
+ val = readl(combphy->phy_clk + 0x0028);
+ if (enable)
+ val |= BIT(31); /* 0x28[31]=1 */
+ else
+ val &= ~BIT(31);
+ writel(val, combphy->phy_clk + 0x0028);
+#endif
+}
+
+static void sunxi_combphy_usb3_power_set(struct sunxi_combphy *combphy, bool enable)
+{
+ u32 val;
+
+ dev_dbg(combphy->dev, "set power %s\n", enable ? "on" : "off");
+ val = readl(combphy->phy_clk + 0x14);
+ if (enable)
+ val &= ~BIT(26);
+ else
+ val |= BIT(26);
+ writel(val, combphy->phy_clk + 0x14);
+
+ val = readl(combphy->phy_clk + 0x0);
+ if (enable)
+ val &= ~BIT(10);
+ else
+ val |= BIT(10);
+ writel(val, combphy->phy_clk + 0x0);
+}
+
+static void sunxi_combphy_pcie_phy_100M(struct sunxi_combphy *combphy)
+{
+ u32 val;
+
+ val = readl(combphy->phy_clk + 0x1004);
+ val &= ~(0x3<<3);
+ val &= ~(0x1<<0);
+ val |= (0x1<<0);
+ val |= (0x1<<2);
+ val |= (0x1<<4);
+ writel(val, combphy->phy_clk + 0x1004);
+
+ val = readl(combphy->phy_clk + 0x1018);
+ val &= ~(0x3<<4);
+ val |= (0x3<<4);
+ writel(val, combphy->phy_clk + 0x1018);
+
+ val = readl(combphy->phy_clk + 0x101c);
+ val &= ~(0x0fffffff);
+ writel(val, combphy->phy_clk + 0x101c);
+
+ /* if need optimize jitter parm*/
+ if (combphy->drvdata->need_optimize_jitter) {
+ val = readl(combphy->phy_clk + 0x107c);
+ val &= ~(0x3ffff);
+ val |= (0x4<<12);
+ val |= 0x64;
+ writel(val, combphy->phy_clk + 0x107c);
+
+ val = readl(combphy->phy_clk + 0x1030);
+ val &= ~(0x3<<20);
+ writel(val, combphy->phy_clk + 0x1030);
+
+ val = readl(combphy->phy_clk + 0x1050);
+ val &= ~(0x7<<0);
+ val &= ~(0x7<<5);
+ val &= ~(0x3<<3);
+ val |= (0x3<<3);
+ writel(val, combphy->phy_clk + 0x1050);
+ } else {
+ val = readl(combphy->phy_clk + 0x107c);
+ val &= ~(0x3ffff);
+ val |= (0x2<<12);
+ val |= 0x32;
+ writel(val, combphy->phy_clk + 0x107c);
+
+ val = readl(combphy->phy_clk + 0x1030);
+ val &= ~(0x3<<20);
+ writel(val, combphy->phy_clk + 0x1030);
+
+ val = readl(combphy->phy_clk + 0x1050);
+ val &= ~(0x7<<5);
+ val |= (0x1<<5);
+ writel(val, combphy->phy_clk + 0x1050);
+ }
+
+ val = readl(combphy->phy_clk + 0x1054);
+ val &= ~(0x7<<5);
+ val |= (0x1<<5);
+ writel(val, combphy->phy_clk + 0x1054);
+
+ val = readl(combphy->phy_clk + 0x0804);
+ val &= ~(0xf<<4);
+ val |= (0xc<<4);
+ writel(val, combphy->phy_clk + 0x0804);
+
+ val = readl(combphy->phy_clk + 0x109c);
+ val &= ~(0x3<<8);
+ val |= (0x1<<1);
+ writel(val, combphy->phy_clk + 0x109c);
+
+ writel(0x80540a0a, combphy->phy_clk + 0x1418);
+}
+
+static int sunxi_combphy_pcie_init(struct sunxi_combphy *combphy)
+{
+ sunxi_combphy_pcie_phy_100M(combphy);
+
+ sunxi_combphy_pcie_phy_enable(combphy);
+
+ return 0;
+}
+
+static int sunxi_combphy_pcie_exit(struct sunxi_combphy *combphy)
+{
+ u32 val;
+
+ /* set the phy:
+ * bit(17): aclk enable
+ * bit(16): hclk enbale
+ * bit(1) : pcie_presetn
+ * bit(0) : pcie_power_up_rstn
+ */
+ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_BGR);
+ val &= (~(0x03<<0));
+ val &= (~(0x03<<16));
+ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_BGR);
+
+ /* Assert the phy */
+ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_CTL);
+ val &= (~PHY_USE_SEL);
+ val &= (~(0x03<<8));
+ val &= (~PHY_RSTN);
+ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_CTL);
+
+ return 0;
+}
+
+static int sunxi_combphy_usb3_init(struct sunxi_combphy *combphy)
+{
+ sunxi_combphy_usb3_phy_set(combphy, true);
+
+ return 0;
+}
+
+static int sunxi_combphy_usb3_exit(struct sunxi_combphy *combphy)
+{
+ sunxi_combphy_usb3_phy_set(combphy, false);
+
+ return 0;
+}
+
+static int sunxi_combphy_usb3_power_on(struct sunxi_combphy *combphy)
+{
+ int ret;
+
+ sunxi_combphy_usb3_power_set(combphy, true);
+
+ if (combphy->select3v3_supply) {
+ ret = regulator_set_voltage(combphy->select3v3_supply, 3300000, 3300000);
+ if (ret) {
+ dev_err(combphy->dev, "set select3v3-supply failed\n");
+ goto err0;
+ }
+
+ ret = regulator_enable(combphy->select3v3_supply);
+ if (ret) {
+ dev_err(combphy->dev, "enable select3v3-supply failed\n");
+ goto err0;
+ }
+ }
+
+ return 0;
+err0:
+ sunxi_combphy_usb3_power_set(combphy, false);
+
+ return ret;
+}
+
+static int sunxi_combphy_usb3_power_off(struct sunxi_combphy *combphy)
+{
+ sunxi_combphy_usb3_power_set(combphy, false);
+
+ if (combphy->select3v3_supply)
+ regulator_disable(combphy->select3v3_supply);
+
+ return 0;
+}
+
+static int sunxi_combphy_set_mode(struct sunxi_combphy *combphy)
+{
+ switch (combphy->mode) {
+ case PHY_TYPE_PCIE:
+ sunxi_combphy_pcie_init(combphy);
+ break;
+ case PHY_TYPE_USB3:
+ if (combphy->user == PHY_USE_BY_PCIE_USB3_U2) {
+ sunxi_combphy_pcie_init(combphy);
+ } else if (combphy->user == PHY_USE_BY_USB3) {
+ sunxi_combphy_usb3_init(combphy);
+ }
+ break;
+ default:
+ dev_err(combphy->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sunxi_combphy_init(struct phy *phy)
+{
+ struct sunxi_combphy *combphy = phy_get_drvdata(phy);
+ int ret;
+
+ ret = sunxi_combphy_set_mode(combphy);
+ if (ret) {
+ dev_err(combphy->dev, "invalid number of arguments\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int sunxi_combphy_exit(struct phy *phy)
+{
+ struct sunxi_combphy *combphy = phy_get_drvdata(phy);
+
+ switch (combphy->mode) {
+ case PHY_TYPE_PCIE:
+ sunxi_combphy_pcie_exit(combphy);
+ break;
+ case PHY_TYPE_USB3:
+ sunxi_combphy_usb3_exit(combphy);
+ break;
+ default:
+ dev_err(combphy->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sunxi_combphy_power_on(struct phy *phy)
+{
+ struct sunxi_combphy *combphy = phy_get_drvdata(phy);
+ int ret;
+ switch (combphy->mode) {
+ case PHY_TYPE_PCIE:
+ break;
+ case PHY_TYPE_USB3:
+ ret = sunxi_combphy_usb3_power_on(combphy);
+ if (ret)
+ return ret;
+ break;
+ default:
+ dev_err(combphy->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sunxi_combphy_power_off(struct phy *phy)
+{
+ struct sunxi_combphy *combphy = phy_get_drvdata(phy);
+ int ret;
+
+ switch (combphy->mode) {
+ case PHY_TYPE_PCIE:
+ break;
+ case PHY_TYPE_USB3:
+ ret = sunxi_combphy_usb3_power_off(combphy);
+ if (ret)
+ return ret;
+ break;
+ default:
+ dev_err(combphy->dev, "incompatible PHY type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct phy_ops sunxi_combphy_ops = {
+ .init = sunxi_combphy_init,
+ .exit = sunxi_combphy_exit,
+ .power_on = sunxi_combphy_power_on,
+ .power_off = sunxi_combphy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static struct phy *sunxi_combphy_xlate(struct device *dev,
+ const struct of_phandle_args *args)
+{
+ struct sunxi_combphy *combphy = dev_get_drvdata(dev);
+
+ if (args->args_count != 1) {
+ dev_err(dev, "invalid number of arguments\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (combphy->mode != PHY_NONE && combphy->mode != args->args[0])
+ dev_warn(dev, "phy type select %d overwriting type %d\n",
+ args->args[0], combphy->mode);
+
+ combphy->mode = args->args[0];
+
+ return combphy->phy;
+}
+
+static int sunxi_combphy_parse_dt(struct platform_device *pdev,
+ struct sunxi_combphy *combphy)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ int ret = -1;
+ struct resource *res_ctl;
+ struct resource *res_clk;
+
+ /* combo phy use sel */
+ ret = of_property_read_u32(np, KEY_PHY_USE_SEL, &combphy->user);
+ if (ret)
+ dev_err(dev, "get phy_use_sel is fail, %d\n", ret);
+
+ combphy->phyclk_ref = devm_clk_get(&pdev->dev, "phyclk_ref");
+ if (IS_ERR(combphy->phyclk_ref))
+ dev_dbg(dev, "failed to get phyclk_ref\n");
+
+ combphy->reset = devm_reset_control_get(dev, "phy_rst");
+ if (IS_ERR(combphy->reset)) {
+ dev_err(dev, "failed to get reset control\n");
+ return PTR_ERR(combphy->reset);
+ }
+
+ if (combphy->drvdata->need_noppu_rst) {
+ combphy->noppu_reset = devm_reset_control_get(dev, "noppu_rst");
+ if (IS_ERR(combphy->noppu_reset)) {
+ dev_err(dev, "failed to get noppu_reset control\n");
+ return PTR_ERR(combphy->noppu_reset);
+ }
+ }
+ if (combphy->user == PHY_USE_BY_PCIE || combphy->user == PHY_USE_BY_PCIE_USB3_U2) {
+
+ combphy->refclk_par = devm_clk_get(&pdev->dev, "refclk_par");
+ if (IS_ERR(combphy->refclk_par))
+ dev_dbg(dev, "failed to get refclk_par\n");
+
+ if (IS_ERR(combphy->phyclk_ref) || IS_ERR(combphy->refclk_par)) {
+ dev_err(dev, "failed to get required clocks for ref\n");
+ return -EINVAL;
+ }
+
+ ret = clk_set_parent(combphy->phyclk_ref, combphy->refclk_par);
+ if (ret) {
+ dev_err(dev, "failed to set refclk parent\n");
+ return -EINVAL;
+ }
+ }
+
+ if (combphy->drvdata->has_cfg_clk) {
+ combphy->phyclk_cfg = devm_clk_get(&pdev->dev, "phyclk_cfg");
+ if (IS_ERR(combphy->phyclk_cfg))
+ dev_dbg(dev, "failed to get phyclk_cfg\n");
+
+ combphy->cfgclk_par = devm_clk_get(&pdev->dev, "cfgclk_par");
+ if (IS_ERR(combphy->cfgclk_par))
+ dev_dbg(dev, "failed to get cfgclk_par\n");
+
+ if (IS_ERR(combphy->phyclk_cfg) || IS_ERR(combphy->cfgclk_par)) {
+ dev_err(dev, "failed to get required clocks for cfg\n");
+ return -EINVAL;
+ }
+
+ ret = clk_set_parent(combphy->phyclk_cfg, combphy->cfgclk_par);
+ if (ret) {
+ dev_err(dev, "failed to set cfgclk parent\n");
+ return -EINVAL;
+ }
+ }
+
+ if (combphy->drvdata->has_pcie_axi_clk) {
+ combphy->phy_axi = devm_clk_get(&pdev->dev, "pclk_axi");
+ if (IS_ERR(combphy->phy_axi)) {
+ dev_err(dev, "failed to get pclk_axi\n");
+ return PTR_ERR(combphy->phy_axi);
+ }
+
+ combphy->phy_axi_par = devm_clk_get(&pdev->dev, "pclk_axi_par");
+ if (IS_ERR(combphy->phy_axi_par)) {
+ dev_err(dev, "failed to get pcie_axi_par\n");
+ return PTR_ERR(combphy->phy_axi_par);
+ }
+
+ ret = clk_set_parent(combphy->phy_axi, combphy->phy_axi_par);
+ if (ret) {
+ dev_err(dev, "failed to set parent\n");
+ return -EINVAL;
+ }
+ }
+
+ if (combphy->drvdata->has_phy_mbus_clk) {
+ combphy->phy_mclk = devm_clk_get(&pdev->dev, "phy_mclk");
+ if (IS_ERR(combphy->phy_mclk)) {
+ dev_err(dev, "fail to get phy_mclk\n");
+ return PTR_ERR(combphy->phy_mclk);
+ }
+ }
+
+ if (combphy->drvdata->has_phy_ahb_clk) {
+ combphy->phy_hclk = devm_clk_get(&pdev->dev, "phy_hclk");
+ if (IS_ERR(combphy->phy_hclk)) {
+ dev_err(dev, "fail to get phy_hclk\n");
+ return PTR_ERR(combphy->phy_hclk);
+ }
+ }
+
+ res_ctl = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy-ctl");
+ if (!res_ctl) {
+ dev_err(&pdev->dev, "get phy-ctl failed\n");
+ return -ENODEV;
+ }
+
+ combphy->phy_ctl = devm_ioremap_resource(&pdev->dev, res_ctl);
+ if (IS_ERR(combphy->phy_ctl)) {
+ dev_err(&pdev->dev, "ioremap phy-ctl failed\n");
+ return PTR_ERR(combphy->phy_ctl);
+ }
+
+ res_clk = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy-clk");
+ if (!res_clk) {
+ dev_err(&pdev->dev, "get phy-clk failed\n");
+ return -ENODEV;
+ }
+
+ combphy->phy_clk = devm_ioremap_resource(&pdev->dev, res_clk);
+ if (IS_ERR(combphy->phy_clk)) {
+ dev_err(&pdev->dev, "ioremap phy-clk failed\n");
+ return PTR_ERR(combphy->phy_clk);
+ }
+
+ /* combo phy refclk sel */
+ ret = of_property_read_u32(np, KEY_PHY_REFCLK_SEL, &combphy->ref);
+ if (ret)
+ dev_err(dev, "get phy_refclk_sel is fail, %d\n", ret);
+
+ /* select ic supply */
+ combphy->select3v3_supply = devm_regulator_get_optional(&pdev->dev, "select3v3");
+ if (IS_ERR(combphy->select3v3_supply)) {
+ if (PTR_ERR(combphy->select3v3_supply) == -EPROBE_DEFER)
+ return PTR_ERR(combphy->select3v3_supply);
+ combphy->select3v3_supply = NULL;
+ }
+
+ return 0;
+}
+
+static int sunxi_combphy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct sunxi_combphy *combphy;
+ const struct sunxi_combophy_of_data *data;
+ int ret;
+
+ data = of_device_get_match_data(&pdev->dev);
+
+ combphy = devm_kzalloc(dev, sizeof(*combphy), GFP_KERNEL);
+ if (!combphy)
+ return -ENOMEM;
+
+ combphy->dev = dev;
+ combphy->mode = PHY_NONE;
+ combphy->drvdata = data;
+
+ ret = sunxi_combphy_parse_dt(pdev, combphy);
+ if (ret) {
+ dev_err(dev, "failed to parse dts of combphy\n");
+ return ret;
+ }
+
+ combphy->phy = devm_phy_create(dev, NULL, &sunxi_combphy_ops);
+ if (IS_ERR(combphy->phy)) {
+ dev_err(dev, "failed to create combphy\n");
+ return PTR_ERR(combphy->phy);
+ }
+
+ platform_set_drvdata(pdev, combphy);
+
+ ret = pcie_usb3_sub_system_init(pdev);
+ if (ret) {
+ dev_err(dev, "failed to init sub system\n");
+ return ret;
+ }
+
+ dev_info(dev, "Sub System Version: 0x%x\n", combphy->vernum);
+
+ phy_set_drvdata(combphy->phy, combphy);
+
+ phy_provider = devm_of_phy_provider_register(dev, sunxi_combphy_xlate);
+
+ combphy->pwr_nb.notifier_call = sunxi_inno_combophy_power_event;
+ /* register inno power notifier */
+ atomic_notifier_chain_register(&inno_subsys_notifier_list, &combphy->pwr_nb);
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static void sunxi_combphy_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sunxi_combphy *combphy = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = pcie_usb3_sub_system_exit(pdev);
+ if (ret) {
+ dev_err(dev, "failed to exit sub system\n");
+
+ }
+
+ /* unregister inno power notifier */
+ atomic_notifier_chain_unregister(&inno_subsys_notifier_list, &combphy->pwr_nb);
+
+ pm_runtime_disable(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_set_suspended(dev);
+
+
+}
+
+static int __maybe_unused sunxi_combo_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ int ret;
+
+ ret = pcie_usb3_sub_system_exit(pdev);
+
+ if (ret) {
+ dev_err(dev, "failed to suspend sub system\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused sunxi_combo_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ int ret;
+
+ ret = pcie_usb3_sub_system_init(pdev);
+ if (ret) {
+ dev_err(dev, "failed to resume sub system\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct dev_pm_ops sunxi_combo_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sunxi_combo_suspend, sunxi_combo_resume)
+};
+
+/*
+ * inno-combphy: innosilicon combo phy
+ */
+static const struct sunxi_combophy_of_data sunxi_inno_v1_of_data = {
+ .has_cfg_clk = false,
+};
+
+static const struct sunxi_combophy_of_data sunxi_inno_v2_of_data = {
+ .has_cfg_clk = true,
+ .has_slv_clk = true,
+ .has_phy_mbus_clk = true,
+ .has_phy_ahb_clk = true,
+ .has_pcie_axi_clk = true,
+ .has_u2_phy_mux = true,
+ .need_noppu_rst = true,
+ .has_u3_phy_data_quirk = true,
+ .need_optimize_jitter = true,
+};
+
+static const struct of_device_id sunxi_combphy_of_match[] = {
+ {
+ .compatible = "allwinner,inno-combphy",
+ .data = &sunxi_inno_v1_of_data,
+ },
+ {
+ .compatible = "allwinner,inno-v2-combphy",
+ .data = &sunxi_inno_v2_of_data,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sunxi_combphy_of_match);
+
+static struct platform_driver sunxi_combphy_driver = {
+ .probe = sunxi_combphy_probe,
+ .remove = sunxi_combphy_remove,
+ .driver = {
+ .name = "inno-combphy",
+ .of_match_table = sunxi_combphy_of_match,
+ .pm = &sunxi_combo_pm_ops,
+ },
+};
+module_platform_driver(sunxi_combphy_driver);
+
+MODULE_DESCRIPTION("Allwinner INNO COMBOPHY driver");
+MODULE_AUTHOR("songjundong@allwinnertech.com");
+MODULE_VERSION("0.0.20");
+MODULE_LICENSE("GPL v2");
--
Armbian