Update Linux to v5.4.2
Change-Id: Idf6911045d9d382da2cfe01b1edff026404ac8fd
diff --git a/drivers/phy/cadence/Kconfig b/drivers/phy/cadence/Kconfig
new file mode 100644
index 0000000..b2db916
--- /dev/null
+++ b/drivers/phy/cadence/Kconfig
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for Cadence PHYs
+#
+
+config PHY_CADENCE_DP
+ tristate "Cadence MHDP DisplayPort PHY driver"
+ depends on OF
+ depends on HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Support for Cadence MHDP DisplayPort PHY.
+
+config PHY_CADENCE_DPHY
+ tristate "Cadence D-PHY Support"
+ depends on HAS_IOMEM && OF
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ Choose this option if you have a Cadence D-PHY in your
+ system. If M is selected, the module will be called
+ cdns-dphy.
+
+config PHY_CADENCE_SIERRA
+ tristate "Cadence Sierra PHY Driver"
+ depends on OF && HAS_IOMEM && RESET_CONTROLLER
+ select GENERIC_PHY
+ help
+ Enable this to support the Cadence Sierra PHY driver
diff --git a/drivers/phy/cadence/Makefile b/drivers/phy/cadence/Makefile
new file mode 100644
index 0000000..8f89560
--- /dev/null
+++ b/drivers/phy/cadence/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_PHY_CADENCE_DP) += phy-cadence-dp.o
+obj-$(CONFIG_PHY_CADENCE_DPHY) += cdns-dphy.o
+obj-$(CONFIG_PHY_CADENCE_SIERRA) += phy-cadence-sierra.o
diff --git a/drivers/phy/cadence/cdns-dphy.c b/drivers/phy/cadence/cdns-dphy.c
new file mode 100644
index 0000000..90c4e9b
--- /dev/null
+++ b/drivers/phy/cadence/cdns-dphy.c
@@ -0,0 +1,391 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright: 2017-2018 Cadence Design Systems, Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+
+#define REG_WAKEUP_TIME_NS 800
+#define DPHY_PLL_RATE_HZ 108000000
+
+/* DPHY registers */
+#define DPHY_PMA_CMN(reg) (reg)
+#define DPHY_PMA_LCLK(reg) (0x100 + (reg))
+#define DPHY_PMA_LDATA(lane, reg) (0x200 + ((lane) * 0x100) + (reg))
+#define DPHY_PMA_RCLK(reg) (0x600 + (reg))
+#define DPHY_PMA_RDATA(lane, reg) (0x700 + ((lane) * 0x100) + (reg))
+#define DPHY_PCS(reg) (0xb00 + (reg))
+
+#define DPHY_CMN_SSM DPHY_PMA_CMN(0x20)
+#define DPHY_CMN_SSM_EN BIT(0)
+#define DPHY_CMN_TX_MODE_EN BIT(9)
+
+#define DPHY_CMN_PWM DPHY_PMA_CMN(0x40)
+#define DPHY_CMN_PWM_DIV(x) ((x) << 20)
+#define DPHY_CMN_PWM_LOW(x) ((x) << 10)
+#define DPHY_CMN_PWM_HIGH(x) (x)
+
+#define DPHY_CMN_FBDIV DPHY_PMA_CMN(0x4c)
+#define DPHY_CMN_FBDIV_VAL(low, high) (((high) << 11) | ((low) << 22))
+#define DPHY_CMN_FBDIV_FROM_REG (BIT(10) | BIT(21))
+
+#define DPHY_CMN_OPIPDIV DPHY_PMA_CMN(0x50)
+#define DPHY_CMN_IPDIV_FROM_REG BIT(0)
+#define DPHY_CMN_IPDIV(x) ((x) << 1)
+#define DPHY_CMN_OPDIV_FROM_REG BIT(6)
+#define DPHY_CMN_OPDIV(x) ((x) << 7)
+
+#define DPHY_PSM_CFG DPHY_PCS(0x4)
+#define DPHY_PSM_CFG_FROM_REG BIT(0)
+#define DPHY_PSM_CLK_DIV(x) ((x) << 1)
+
+#define DSI_HBP_FRAME_OVERHEAD 12
+#define DSI_HSA_FRAME_OVERHEAD 14
+#define DSI_HFP_FRAME_OVERHEAD 6
+#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4
+#define DSI_BLANKING_FRAME_OVERHEAD 6
+#define DSI_NULL_FRAME_OVERHEAD 6
+#define DSI_EOT_PKT_SIZE 4
+
+struct cdns_dphy_cfg {
+ u8 pll_ipdiv;
+ u8 pll_opdiv;
+ u16 pll_fbdiv;
+ unsigned int nlanes;
+};
+
+enum cdns_dphy_clk_lane_cfg {
+ DPHY_CLK_CFG_LEFT_DRIVES_ALL = 0,
+ DPHY_CLK_CFG_LEFT_DRIVES_RIGHT = 1,
+ DPHY_CLK_CFG_LEFT_DRIVES_LEFT = 2,
+ DPHY_CLK_CFG_RIGHT_DRIVES_ALL = 3,
+};
+
+struct cdns_dphy;
+struct cdns_dphy_ops {
+ int (*probe)(struct cdns_dphy *dphy);
+ void (*remove)(struct cdns_dphy *dphy);
+ void (*set_psm_div)(struct cdns_dphy *dphy, u8 div);
+ void (*set_clk_lane_cfg)(struct cdns_dphy *dphy,
+ enum cdns_dphy_clk_lane_cfg cfg);
+ void (*set_pll_cfg)(struct cdns_dphy *dphy,
+ const struct cdns_dphy_cfg *cfg);
+ unsigned long (*get_wakeup_time_ns)(struct cdns_dphy *dphy);
+};
+
+struct cdns_dphy {
+ struct cdns_dphy_cfg cfg;
+ void __iomem *regs;
+ struct clk *psm_clk;
+ struct clk *pll_ref_clk;
+ const struct cdns_dphy_ops *ops;
+ struct phy *phy;
+};
+
+static int cdns_dsi_get_dphy_pll_cfg(struct cdns_dphy *dphy,
+ struct cdns_dphy_cfg *cfg,
+ struct phy_configure_opts_mipi_dphy *opts,
+ unsigned int *dsi_hfp_ext)
+{
+ unsigned long pll_ref_hz = clk_get_rate(dphy->pll_ref_clk);
+ u64 dlane_bps;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ if (pll_ref_hz < 9600000 || pll_ref_hz >= 150000000)
+ return -EINVAL;
+ else if (pll_ref_hz < 19200000)
+ cfg->pll_ipdiv = 1;
+ else if (pll_ref_hz < 38400000)
+ cfg->pll_ipdiv = 2;
+ else if (pll_ref_hz < 76800000)
+ cfg->pll_ipdiv = 4;
+ else
+ cfg->pll_ipdiv = 8;
+
+ dlane_bps = opts->hs_clk_rate;
+
+ if (dlane_bps > 2500000000UL || dlane_bps < 160000000UL)
+ return -EINVAL;
+ else if (dlane_bps >= 1250000000)
+ cfg->pll_opdiv = 1;
+ else if (dlane_bps >= 630000000)
+ cfg->pll_opdiv = 2;
+ else if (dlane_bps >= 320000000)
+ cfg->pll_opdiv = 4;
+ else if (dlane_bps >= 160000000)
+ cfg->pll_opdiv = 8;
+
+ cfg->pll_fbdiv = DIV_ROUND_UP_ULL(dlane_bps * 2 * cfg->pll_opdiv *
+ cfg->pll_ipdiv,
+ pll_ref_hz);
+
+ return 0;
+}
+
+static int cdns_dphy_setup_psm(struct cdns_dphy *dphy)
+{
+ unsigned long psm_clk_hz = clk_get_rate(dphy->psm_clk);
+ unsigned long psm_div;
+
+ if (!psm_clk_hz || psm_clk_hz > 100000000)
+ return -EINVAL;
+
+ psm_div = DIV_ROUND_CLOSEST(psm_clk_hz, 1000000);
+ if (dphy->ops->set_psm_div)
+ dphy->ops->set_psm_div(dphy, psm_div);
+
+ return 0;
+}
+
+static void cdns_dphy_set_clk_lane_cfg(struct cdns_dphy *dphy,
+ enum cdns_dphy_clk_lane_cfg cfg)
+{
+ if (dphy->ops->set_clk_lane_cfg)
+ dphy->ops->set_clk_lane_cfg(dphy, cfg);
+}
+
+static void cdns_dphy_set_pll_cfg(struct cdns_dphy *dphy,
+ const struct cdns_dphy_cfg *cfg)
+{
+ if (dphy->ops->set_pll_cfg)
+ dphy->ops->set_pll_cfg(dphy, cfg);
+}
+
+static unsigned long cdns_dphy_get_wakeup_time_ns(struct cdns_dphy *dphy)
+{
+ return dphy->ops->get_wakeup_time_ns(dphy);
+}
+
+static unsigned long cdns_dphy_ref_get_wakeup_time_ns(struct cdns_dphy *dphy)
+{
+ /* Default wakeup time is 800 ns (in a simulated environment). */
+ return 800;
+}
+
+static void cdns_dphy_ref_set_pll_cfg(struct cdns_dphy *dphy,
+ const struct cdns_dphy_cfg *cfg)
+{
+ u32 fbdiv_low, fbdiv_high;
+
+ fbdiv_low = (cfg->pll_fbdiv / 4) - 2;
+ fbdiv_high = cfg->pll_fbdiv - fbdiv_low - 2;
+
+ writel(DPHY_CMN_IPDIV_FROM_REG | DPHY_CMN_OPDIV_FROM_REG |
+ DPHY_CMN_IPDIV(cfg->pll_ipdiv) |
+ DPHY_CMN_OPDIV(cfg->pll_opdiv),
+ dphy->regs + DPHY_CMN_OPIPDIV);
+ writel(DPHY_CMN_FBDIV_FROM_REG |
+ DPHY_CMN_FBDIV_VAL(fbdiv_low, fbdiv_high),
+ dphy->regs + DPHY_CMN_FBDIV);
+ writel(DPHY_CMN_PWM_HIGH(6) | DPHY_CMN_PWM_LOW(0x101) |
+ DPHY_CMN_PWM_DIV(0x8),
+ dphy->regs + DPHY_CMN_PWM);
+}
+
+static void cdns_dphy_ref_set_psm_div(struct cdns_dphy *dphy, u8 div)
+{
+ writel(DPHY_PSM_CFG_FROM_REG | DPHY_PSM_CLK_DIV(div),
+ dphy->regs + DPHY_PSM_CFG);
+}
+
+/*
+ * This is the reference implementation of DPHY hooks. Specific integration of
+ * this IP may have to re-implement some of them depending on how they decided
+ * to wire things in the SoC.
+ */
+static const struct cdns_dphy_ops ref_dphy_ops = {
+ .get_wakeup_time_ns = cdns_dphy_ref_get_wakeup_time_ns,
+ .set_pll_cfg = cdns_dphy_ref_set_pll_cfg,
+ .set_psm_div = cdns_dphy_ref_set_psm_div,
+};
+
+static int cdns_dphy_config_from_opts(struct phy *phy,
+ struct phy_configure_opts_mipi_dphy *opts,
+ struct cdns_dphy_cfg *cfg)
+{
+ struct cdns_dphy *dphy = phy_get_drvdata(phy);
+ unsigned int dsi_hfp_ext = 0;
+ int ret;
+
+ ret = phy_mipi_dphy_config_validate(opts);
+ if (ret)
+ return ret;
+
+ ret = cdns_dsi_get_dphy_pll_cfg(dphy, cfg,
+ opts, &dsi_hfp_ext);
+ if (ret)
+ return ret;
+
+ opts->wakeup = cdns_dphy_get_wakeup_time_ns(dphy) / 1000;
+
+ return 0;
+}
+
+static int cdns_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
+ union phy_configure_opts *opts)
+{
+ struct cdns_dphy_cfg cfg = { 0 };
+
+ if (mode != PHY_MODE_MIPI_DPHY)
+ return -EINVAL;
+
+ return cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
+}
+
+static int cdns_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct cdns_dphy *dphy = phy_get_drvdata(phy);
+ struct cdns_dphy_cfg cfg = { 0 };
+ int ret;
+
+ ret = cdns_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure the internal PSM clk divider so that the DPHY has a
+ * 1MHz clk (or something close).
+ */
+ ret = cdns_dphy_setup_psm(dphy);
+ if (ret)
+ return ret;
+
+ /*
+ * Configure attach clk lanes to data lanes: the DPHY has 2 clk lanes
+ * and 8 data lanes, each clk lane can be attache different set of
+ * data lanes. The 2 groups are named 'left' and 'right', so here we
+ * just say that we want the 'left' clk lane to drive the 'left' data
+ * lanes.
+ */
+ cdns_dphy_set_clk_lane_cfg(dphy, DPHY_CLK_CFG_LEFT_DRIVES_LEFT);
+
+ /*
+ * Configure the DPHY PLL that will be used to generate the TX byte
+ * clk.
+ */
+ cdns_dphy_set_pll_cfg(dphy, &cfg);
+
+ return 0;
+}
+
+static int cdns_dphy_power_on(struct phy *phy)
+{
+ struct cdns_dphy *dphy = phy_get_drvdata(phy);
+
+ clk_prepare_enable(dphy->psm_clk);
+ clk_prepare_enable(dphy->pll_ref_clk);
+
+ /* Start TX state machine. */
+ writel(DPHY_CMN_SSM_EN | DPHY_CMN_TX_MODE_EN,
+ dphy->regs + DPHY_CMN_SSM);
+
+ return 0;
+}
+
+static int cdns_dphy_power_off(struct phy *phy)
+{
+ struct cdns_dphy *dphy = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(dphy->pll_ref_clk);
+ clk_disable_unprepare(dphy->psm_clk);
+
+ return 0;
+}
+
+static const struct phy_ops cdns_dphy_ops = {
+ .configure = cdns_dphy_configure,
+ .validate = cdns_dphy_validate,
+ .power_on = cdns_dphy_power_on,
+ .power_off = cdns_dphy_power_off,
+};
+
+static int cdns_dphy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct cdns_dphy *dphy;
+ struct resource *res;
+ int ret;
+
+ dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
+ if (!dphy)
+ return -ENOMEM;
+ dev_set_drvdata(&pdev->dev, dphy);
+
+ dphy->ops = of_device_get_match_data(&pdev->dev);
+ if (!dphy->ops)
+ return -EINVAL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dphy->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dphy->regs))
+ return PTR_ERR(dphy->regs);
+
+ dphy->psm_clk = devm_clk_get(&pdev->dev, "psm");
+ if (IS_ERR(dphy->psm_clk))
+ return PTR_ERR(dphy->psm_clk);
+
+ dphy->pll_ref_clk = devm_clk_get(&pdev->dev, "pll_ref");
+ if (IS_ERR(dphy->pll_ref_clk))
+ return PTR_ERR(dphy->pll_ref_clk);
+
+ if (dphy->ops->probe) {
+ ret = dphy->ops->probe(dphy);
+ if (ret)
+ return ret;
+ }
+
+ dphy->phy = devm_phy_create(&pdev->dev, NULL, &cdns_dphy_ops);
+ if (IS_ERR(dphy->phy)) {
+ dev_err(&pdev->dev, "failed to create PHY\n");
+ if (dphy->ops->remove)
+ dphy->ops->remove(dphy);
+ return PTR_ERR(dphy->phy);
+ }
+
+ phy_set_drvdata(dphy->phy, dphy);
+ phy_provider = devm_of_phy_provider_register(&pdev->dev,
+ of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static int cdns_dphy_remove(struct platform_device *pdev)
+{
+ struct cdns_dphy *dphy = dev_get_drvdata(&pdev->dev);
+
+ if (dphy->ops->remove)
+ dphy->ops->remove(dphy);
+
+ return 0;
+}
+
+static const struct of_device_id cdns_dphy_of_match[] = {
+ { .compatible = "cdns,dphy", .data = &ref_dphy_ops },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, cdns_dphy_of_match);
+
+static struct platform_driver cdns_dphy_platform_driver = {
+ .probe = cdns_dphy_probe,
+ .remove = cdns_dphy_remove,
+ .driver = {
+ .name = "cdns-mipi-dphy",
+ .of_match_table = cdns_dphy_of_match,
+ },
+};
+module_platform_driver(cdns_dphy_platform_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin.com>");
+MODULE_DESCRIPTION("Cadence MIPI D-PHY Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/cadence/phy-cadence-dp.c b/drivers/phy/cadence/phy-cadence-dp.c
new file mode 100644
index 0000000..bc10cb2
--- /dev/null
+++ b/drivers/phy/cadence/phy-cadence-dp.c
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Cadence MHDP DisplayPort SD0801 PHY driver.
+ *
+ * Copyright 2018 Cadence Design Systems, Inc.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+
+#define DEFAULT_NUM_LANES 2
+#define MAX_NUM_LANES 4
+#define DEFAULT_MAX_BIT_RATE 8100 /* in Mbps */
+
+#define POLL_TIMEOUT_US 2000
+#define LANE_MASK 0x7
+
+/*
+ * register offsets from DPTX PHY register block base (i.e MHDP
+ * register base + 0x30a00)
+ */
+#define PHY_AUX_CONFIG 0x00
+#define PHY_AUX_CTRL 0x04
+#define PHY_RESET 0x20
+#define PHY_PMA_XCVR_PLLCLK_EN 0x24
+#define PHY_PMA_XCVR_PLLCLK_EN_ACK 0x28
+#define PHY_PMA_XCVR_POWER_STATE_REQ 0x2c
+#define PHY_POWER_STATE_LN_0 0x0000
+#define PHY_POWER_STATE_LN_1 0x0008
+#define PHY_POWER_STATE_LN_2 0x0010
+#define PHY_POWER_STATE_LN_3 0x0018
+#define PHY_PMA_XCVR_POWER_STATE_ACK 0x30
+#define PHY_PMA_CMN_READY 0x34
+#define PHY_PMA_XCVR_TX_VMARGIN 0x38
+#define PHY_PMA_XCVR_TX_DEEMPH 0x3c
+
+/*
+ * register offsets from SD0801 PHY register block base (i.e MHDP
+ * register base + 0x500000)
+ */
+#define CMN_SSM_BANDGAP_TMR 0x00084
+#define CMN_SSM_BIAS_TMR 0x00088
+#define CMN_PLLSM0_PLLPRE_TMR 0x000a8
+#define CMN_PLLSM0_PLLLOCK_TMR 0x000b0
+#define CMN_PLLSM1_PLLPRE_TMR 0x000c8
+#define CMN_PLLSM1_PLLLOCK_TMR 0x000d0
+#define CMN_BGCAL_INIT_TMR 0x00190
+#define CMN_BGCAL_ITER_TMR 0x00194
+#define CMN_IBCAL_INIT_TMR 0x001d0
+#define CMN_PLL0_VCOCAL_INIT_TMR 0x00210
+#define CMN_PLL0_VCOCAL_ITER_TMR 0x00214
+#define CMN_PLL0_VCOCAL_REFTIM_START 0x00218
+#define CMN_PLL0_VCOCAL_PLLCNT_START 0x00220
+#define CMN_PLL0_INTDIV_M0 0x00240
+#define CMN_PLL0_FRACDIVL_M0 0x00244
+#define CMN_PLL0_FRACDIVH_M0 0x00248
+#define CMN_PLL0_HIGH_THR_M0 0x0024c
+#define CMN_PLL0_DSM_DIAG_M0 0x00250
+#define CMN_PLL0_LOCK_PLLCNT_START 0x00278
+#define CMN_PLL1_VCOCAL_INIT_TMR 0x00310
+#define CMN_PLL1_VCOCAL_ITER_TMR 0x00314
+#define CMN_PLL1_DSM_DIAG_M0 0x00350
+#define CMN_TXPUCAL_INIT_TMR 0x00410
+#define CMN_TXPUCAL_ITER_TMR 0x00414
+#define CMN_TXPDCAL_INIT_TMR 0x00430
+#define CMN_TXPDCAL_ITER_TMR 0x00434
+#define CMN_RXCAL_INIT_TMR 0x00450
+#define CMN_RXCAL_ITER_TMR 0x00454
+#define CMN_SD_CAL_INIT_TMR 0x00490
+#define CMN_SD_CAL_ITER_TMR 0x00494
+#define CMN_SD_CAL_REFTIM_START 0x00498
+#define CMN_SD_CAL_PLLCNT_START 0x004a0
+#define CMN_PDIAG_PLL0_CTRL_M0 0x00680
+#define CMN_PDIAG_PLL0_CLK_SEL_M0 0x00684
+#define CMN_PDIAG_PLL0_CP_PADJ_M0 0x00690
+#define CMN_PDIAG_PLL0_CP_IADJ_M0 0x00694
+#define CMN_PDIAG_PLL0_FILT_PADJ_M0 0x00698
+#define CMN_PDIAG_PLL0_CP_PADJ_M1 0x006d0
+#define CMN_PDIAG_PLL0_CP_IADJ_M1 0x006d4
+#define CMN_PDIAG_PLL1_CLK_SEL_M0 0x00704
+#define XCVR_DIAG_PLLDRC_CTRL 0x10394
+#define XCVR_DIAG_HSCLK_SEL 0x10398
+#define XCVR_DIAG_HSCLK_DIV 0x1039c
+#define TX_PSC_A0 0x10400
+#define TX_PSC_A1 0x10404
+#define TX_PSC_A2 0x10408
+#define TX_PSC_A3 0x1040c
+#define RX_PSC_A0 0x20000
+#define RX_PSC_A1 0x20004
+#define RX_PSC_A2 0x20008
+#define RX_PSC_A3 0x2000c
+#define PHY_PLL_CFG 0x30038
+
+struct cdns_dp_phy {
+ void __iomem *base; /* DPTX registers base */
+ void __iomem *sd_base; /* SD0801 registers base */
+ u32 num_lanes; /* Number of lanes to use */
+ u32 max_bit_rate; /* Maximum link bit rate to use (in Mbps) */
+ struct device *dev;
+};
+
+static int cdns_dp_phy_init(struct phy *phy);
+static void cdns_dp_phy_run(struct cdns_dp_phy *cdns_phy);
+static void cdns_dp_phy_wait_pma_cmn_ready(struct cdns_dp_phy *cdns_phy);
+static void cdns_dp_phy_pma_cfg(struct cdns_dp_phy *cdns_phy);
+static void cdns_dp_phy_pma_cmn_cfg_25mhz(struct cdns_dp_phy *cdns_phy);
+static void cdns_dp_phy_pma_lane_cfg(struct cdns_dp_phy *cdns_phy,
+ unsigned int lane);
+static void cdns_dp_phy_pma_cmn_vco_cfg_25mhz(struct cdns_dp_phy *cdns_phy);
+static void cdns_dp_phy_pma_cmn_rate(struct cdns_dp_phy *cdns_phy);
+static void cdns_dp_phy_write_field(struct cdns_dp_phy *cdns_phy,
+ unsigned int offset,
+ unsigned char start_bit,
+ unsigned char num_bits,
+ unsigned int val);
+
+static const struct phy_ops cdns_dp_phy_ops = {
+ .init = cdns_dp_phy_init,
+ .owner = THIS_MODULE,
+};
+
+static int cdns_dp_phy_init(struct phy *phy)
+{
+ unsigned char lane_bits;
+
+ struct cdns_dp_phy *cdns_phy = phy_get_drvdata(phy);
+
+ writel(0x0003, cdns_phy->base + PHY_AUX_CTRL); /* enable AUX */
+
+ /* PHY PMA registers configuration function */
+ cdns_dp_phy_pma_cfg(cdns_phy);
+
+ /*
+ * Set lines power state to A0
+ * Set lines pll clk enable to 0
+ */
+
+ cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_POWER_STATE_REQ,
+ PHY_POWER_STATE_LN_0, 6, 0x0000);
+
+ if (cdns_phy->num_lanes >= 2) {
+ cdns_dp_phy_write_field(cdns_phy,
+ PHY_PMA_XCVR_POWER_STATE_REQ,
+ PHY_POWER_STATE_LN_1, 6, 0x0000);
+
+ if (cdns_phy->num_lanes == 4) {
+ cdns_dp_phy_write_field(cdns_phy,
+ PHY_PMA_XCVR_POWER_STATE_REQ,
+ PHY_POWER_STATE_LN_2, 6, 0);
+ cdns_dp_phy_write_field(cdns_phy,
+ PHY_PMA_XCVR_POWER_STATE_REQ,
+ PHY_POWER_STATE_LN_3, 6, 0);
+ }
+ }
+
+ cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_PLLCLK_EN,
+ 0, 1, 0x0000);
+
+ if (cdns_phy->num_lanes >= 2) {
+ cdns_dp_phy_write_field(cdns_phy, PHY_PMA_XCVR_PLLCLK_EN,
+ 1, 1, 0x0000);
+ if (cdns_phy->num_lanes == 4) {
+ cdns_dp_phy_write_field(cdns_phy,
+ PHY_PMA_XCVR_PLLCLK_EN,
+ 2, 1, 0x0000);
+ cdns_dp_phy_write_field(cdns_phy,
+ PHY_PMA_XCVR_PLLCLK_EN,
+ 3, 1, 0x0000);
+ }
+ }
+
+ /*
+ * release phy_l0*_reset_n and pma_tx_elec_idle_ln_* based on
+ * used lanes
+ */
+ lane_bits = (1 << cdns_phy->num_lanes) - 1;
+ writel(((0xF & ~lane_bits) << 4) | (0xF & lane_bits),
+ cdns_phy->base + PHY_RESET);
+
+ /* release pma_xcvr_pllclk_en_ln_*, only for the master lane */
+ writel(0x0001, cdns_phy->base + PHY_PMA_XCVR_PLLCLK_EN);
+
+ /* PHY PMA registers configuration functions */
+ cdns_dp_phy_pma_cmn_vco_cfg_25mhz(cdns_phy);
+ cdns_dp_phy_pma_cmn_rate(cdns_phy);
+
+ /* take out of reset */
+ cdns_dp_phy_write_field(cdns_phy, PHY_RESET, 8, 1, 1);
+ cdns_dp_phy_wait_pma_cmn_ready(cdns_phy);
+ cdns_dp_phy_run(cdns_phy);
+
+ return 0;
+}
+
+static void cdns_dp_phy_wait_pma_cmn_ready(struct cdns_dp_phy *cdns_phy)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_CMN_READY, reg,
+ reg & 1, 0, 500);
+ if (ret == -ETIMEDOUT)
+ dev_err(cdns_phy->dev,
+ "timeout waiting for PMA common ready\n");
+}
+
+static void cdns_dp_phy_pma_cfg(struct cdns_dp_phy *cdns_phy)
+{
+ unsigned int i;
+
+ /* PMA common configuration */
+ cdns_dp_phy_pma_cmn_cfg_25mhz(cdns_phy);
+
+ /* PMA lane configuration to deal with multi-link operation */
+ for (i = 0; i < cdns_phy->num_lanes; i++)
+ cdns_dp_phy_pma_lane_cfg(cdns_phy, i);
+}
+
+static void cdns_dp_phy_pma_cmn_cfg_25mhz(struct cdns_dp_phy *cdns_phy)
+{
+ /* refclock registers - assumes 25 MHz refclock */
+ writel(0x0019, cdns_phy->sd_base + CMN_SSM_BIAS_TMR);
+ writel(0x0032, cdns_phy->sd_base + CMN_PLLSM0_PLLPRE_TMR);
+ writel(0x00D1, cdns_phy->sd_base + CMN_PLLSM0_PLLLOCK_TMR);
+ writel(0x0032, cdns_phy->sd_base + CMN_PLLSM1_PLLPRE_TMR);
+ writel(0x00D1, cdns_phy->sd_base + CMN_PLLSM1_PLLLOCK_TMR);
+ writel(0x007D, cdns_phy->sd_base + CMN_BGCAL_INIT_TMR);
+ writel(0x007D, cdns_phy->sd_base + CMN_BGCAL_ITER_TMR);
+ writel(0x0019, cdns_phy->sd_base + CMN_IBCAL_INIT_TMR);
+ writel(0x001E, cdns_phy->sd_base + CMN_TXPUCAL_INIT_TMR);
+ writel(0x0006, cdns_phy->sd_base + CMN_TXPUCAL_ITER_TMR);
+ writel(0x001E, cdns_phy->sd_base + CMN_TXPDCAL_INIT_TMR);
+ writel(0x0006, cdns_phy->sd_base + CMN_TXPDCAL_ITER_TMR);
+ writel(0x02EE, cdns_phy->sd_base + CMN_RXCAL_INIT_TMR);
+ writel(0x0006, cdns_phy->sd_base + CMN_RXCAL_ITER_TMR);
+ writel(0x0002, cdns_phy->sd_base + CMN_SD_CAL_INIT_TMR);
+ writel(0x0002, cdns_phy->sd_base + CMN_SD_CAL_ITER_TMR);
+ writel(0x000E, cdns_phy->sd_base + CMN_SD_CAL_REFTIM_START);
+ writel(0x012B, cdns_phy->sd_base + CMN_SD_CAL_PLLCNT_START);
+ /* PLL registers */
+ writel(0x0409, cdns_phy->sd_base + CMN_PDIAG_PLL0_CP_PADJ_M0);
+ writel(0x1001, cdns_phy->sd_base + CMN_PDIAG_PLL0_CP_IADJ_M0);
+ writel(0x0F08, cdns_phy->sd_base + CMN_PDIAG_PLL0_FILT_PADJ_M0);
+ writel(0x0004, cdns_phy->sd_base + CMN_PLL0_DSM_DIAG_M0);
+ writel(0x00FA, cdns_phy->sd_base + CMN_PLL0_VCOCAL_INIT_TMR);
+ writel(0x0004, cdns_phy->sd_base + CMN_PLL0_VCOCAL_ITER_TMR);
+ writel(0x00FA, cdns_phy->sd_base + CMN_PLL1_VCOCAL_INIT_TMR);
+ writel(0x0004, cdns_phy->sd_base + CMN_PLL1_VCOCAL_ITER_TMR);
+ writel(0x0318, cdns_phy->sd_base + CMN_PLL0_VCOCAL_REFTIM_START);
+}
+
+static void cdns_dp_phy_pma_cmn_vco_cfg_25mhz(struct cdns_dp_phy *cdns_phy)
+{
+ /* Assumes 25 MHz refclock */
+ switch (cdns_phy->max_bit_rate) {
+ /* Setting VCO for 10.8GHz */
+ case 2700:
+ case 5400:
+ writel(0x01B0, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0);
+ writel(0x0000, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0);
+ writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0);
+ writel(0x0120, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0);
+ break;
+ /* Setting VCO for 9.72GHz */
+ case 2430:
+ case 3240:
+ writel(0x0184, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0);
+ writel(0xCCCD, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0);
+ writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0);
+ writel(0x0104, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0);
+ break;
+ /* Setting VCO for 8.64GHz */
+ case 2160:
+ case 4320:
+ writel(0x0159, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0);
+ writel(0x999A, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0);
+ writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0);
+ writel(0x00E7, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0);
+ break;
+ /* Setting VCO for 8.1GHz */
+ case 8100:
+ writel(0x0144, cdns_phy->sd_base + CMN_PLL0_INTDIV_M0);
+ writel(0x0000, cdns_phy->sd_base + CMN_PLL0_FRACDIVL_M0);
+ writel(0x0002, cdns_phy->sd_base + CMN_PLL0_FRACDIVH_M0);
+ writel(0x00D8, cdns_phy->sd_base + CMN_PLL0_HIGH_THR_M0);
+ break;
+ }
+
+ writel(0x0002, cdns_phy->sd_base + CMN_PDIAG_PLL0_CTRL_M0);
+ writel(0x0318, cdns_phy->sd_base + CMN_PLL0_VCOCAL_PLLCNT_START);
+}
+
+static void cdns_dp_phy_pma_cmn_rate(struct cdns_dp_phy *cdns_phy)
+{
+ unsigned int clk_sel_val = 0;
+ unsigned int hsclk_div_val = 0;
+ unsigned int i;
+
+ /* 16'h0000 for single DP link configuration */
+ writel(0x0000, cdns_phy->sd_base + PHY_PLL_CFG);
+
+ switch (cdns_phy->max_bit_rate) {
+ case 1620:
+ clk_sel_val = 0x0f01;
+ hsclk_div_val = 2;
+ break;
+ case 2160:
+ case 2430:
+ case 2700:
+ clk_sel_val = 0x0701;
+ hsclk_div_val = 1;
+ break;
+ case 3240:
+ clk_sel_val = 0x0b00;
+ hsclk_div_val = 2;
+ break;
+ case 4320:
+ case 5400:
+ clk_sel_val = 0x0301;
+ hsclk_div_val = 0;
+ break;
+ case 8100:
+ clk_sel_val = 0x0200;
+ hsclk_div_val = 0;
+ break;
+ }
+
+ writel(clk_sel_val, cdns_phy->sd_base + CMN_PDIAG_PLL0_CLK_SEL_M0);
+
+ /* PMA lane configuration to deal with multi-link operation */
+ for (i = 0; i < cdns_phy->num_lanes; i++) {
+ writel(hsclk_div_val,
+ cdns_phy->sd_base + (XCVR_DIAG_HSCLK_DIV | (i<<11)));
+ }
+}
+
+static void cdns_dp_phy_pma_lane_cfg(struct cdns_dp_phy *cdns_phy,
+ unsigned int lane)
+{
+ unsigned int lane_bits = (lane & LANE_MASK) << 11;
+
+ /* Writing Tx/Rx Power State Controllers registers */
+ writel(0x00FB, cdns_phy->sd_base + (TX_PSC_A0 | lane_bits));
+ writel(0x04AA, cdns_phy->sd_base + (TX_PSC_A2 | lane_bits));
+ writel(0x04AA, cdns_phy->sd_base + (TX_PSC_A3 | lane_bits));
+ writel(0x0000, cdns_phy->sd_base + (RX_PSC_A0 | lane_bits));
+ writel(0x0000, cdns_phy->sd_base + (RX_PSC_A2 | lane_bits));
+ writel(0x0000, cdns_phy->sd_base + (RX_PSC_A3 | lane_bits));
+
+ writel(0x0001, cdns_phy->sd_base + (XCVR_DIAG_PLLDRC_CTRL | lane_bits));
+ writel(0x0000, cdns_phy->sd_base + (XCVR_DIAG_HSCLK_SEL | lane_bits));
+}
+
+static void cdns_dp_phy_run(struct cdns_dp_phy *cdns_phy)
+{
+ unsigned int read_val;
+ u32 write_val1 = 0;
+ u32 write_val2 = 0;
+ u32 mask = 0;
+ int ret;
+
+ /*
+ * waiting for ACK of pma_xcvr_pllclk_en_ln_*, only for the
+ * master lane
+ */
+ ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_PLLCLK_EN_ACK,
+ read_val, read_val & 1, 0, POLL_TIMEOUT_US);
+ if (ret == -ETIMEDOUT)
+ dev_err(cdns_phy->dev,
+ "timeout waiting for link PLL clock enable ack\n");
+
+ ndelay(100);
+
+ switch (cdns_phy->num_lanes) {
+
+ case 1: /* lane 0 */
+ write_val1 = 0x00000004;
+ write_val2 = 0x00000001;
+ mask = 0x0000003f;
+ break;
+ case 2: /* lane 0-1 */
+ write_val1 = 0x00000404;
+ write_val2 = 0x00000101;
+ mask = 0x00003f3f;
+ break;
+ case 4: /* lane 0-3 */
+ write_val1 = 0x04040404;
+ write_val2 = 0x01010101;
+ mask = 0x3f3f3f3f;
+ break;
+ }
+
+ writel(write_val1, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ);
+
+ ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_ACK,
+ read_val, (read_val & mask) == write_val1, 0,
+ POLL_TIMEOUT_US);
+ if (ret == -ETIMEDOUT)
+ dev_err(cdns_phy->dev,
+ "timeout waiting for link power state ack\n");
+
+ writel(0, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ);
+ ndelay(100);
+
+ writel(write_val2, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ);
+
+ ret = readl_poll_timeout(cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_ACK,
+ read_val, (read_val & mask) == write_val2, 0,
+ POLL_TIMEOUT_US);
+ if (ret == -ETIMEDOUT)
+ dev_err(cdns_phy->dev,
+ "timeout waiting for link power state ack\n");
+
+ writel(0, cdns_phy->base + PHY_PMA_XCVR_POWER_STATE_REQ);
+ ndelay(100);
+}
+
+static void cdns_dp_phy_write_field(struct cdns_dp_phy *cdns_phy,
+ unsigned int offset,
+ unsigned char start_bit,
+ unsigned char num_bits,
+ unsigned int val)
+{
+ unsigned int read_val;
+
+ read_val = readl(cdns_phy->base + offset);
+ writel(((val << start_bit) | (read_val & ~(((1 << num_bits) - 1) <<
+ start_bit))), cdns_phy->base + offset);
+}
+
+static int cdns_dp_phy_probe(struct platform_device *pdev)
+{
+ struct resource *regs;
+ struct cdns_dp_phy *cdns_phy;
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct phy *phy;
+ int err;
+
+ cdns_phy = devm_kzalloc(dev, sizeof(*cdns_phy), GFP_KERNEL);
+ if (!cdns_phy)
+ return -ENOMEM;
+
+ cdns_phy->dev = &pdev->dev;
+
+ phy = devm_phy_create(dev, NULL, &cdns_dp_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create DisplayPort PHY\n");
+ return PTR_ERR(phy);
+ }
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ cdns_phy->base = devm_ioremap_resource(&pdev->dev, regs);
+ if (IS_ERR(cdns_phy->base))
+ return PTR_ERR(cdns_phy->base);
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ cdns_phy->sd_base = devm_ioremap_resource(&pdev->dev, regs);
+ if (IS_ERR(cdns_phy->sd_base))
+ return PTR_ERR(cdns_phy->sd_base);
+
+ err = device_property_read_u32(dev, "num_lanes",
+ &(cdns_phy->num_lanes));
+ if (err)
+ cdns_phy->num_lanes = DEFAULT_NUM_LANES;
+
+ switch (cdns_phy->num_lanes) {
+ case 1:
+ case 2:
+ case 4:
+ /* valid number of lanes */
+ break;
+ default:
+ dev_err(dev, "unsupported number of lanes: %d\n",
+ cdns_phy->num_lanes);
+ return -EINVAL;
+ }
+
+ err = device_property_read_u32(dev, "max_bit_rate",
+ &(cdns_phy->max_bit_rate));
+ if (err)
+ cdns_phy->max_bit_rate = DEFAULT_MAX_BIT_RATE;
+
+ switch (cdns_phy->max_bit_rate) {
+ case 2160:
+ case 2430:
+ case 2700:
+ case 3240:
+ case 4320:
+ case 5400:
+ case 8100:
+ /* valid bit rate */
+ break;
+ default:
+ dev_err(dev, "unsupported max bit rate: %dMbps\n",
+ cdns_phy->max_bit_rate);
+ return -EINVAL;
+ }
+
+ phy_set_drvdata(phy, cdns_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ dev_info(dev, "%d lanes, max bit rate %d.%03d Gbps\n",
+ cdns_phy->num_lanes,
+ cdns_phy->max_bit_rate / 1000,
+ cdns_phy->max_bit_rate % 1000);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static const struct of_device_id cdns_dp_phy_of_match[] = {
+ {
+ .compatible = "cdns,dp-phy"
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cdns_dp_phy_of_match);
+
+static struct platform_driver cdns_dp_phy_driver = {
+ .probe = cdns_dp_phy_probe,
+ .driver = {
+ .name = "cdns-dp-phy",
+ .of_match_table = cdns_dp_phy_of_match,
+ }
+};
+module_platform_driver(cdns_dp_phy_driver);
+
+MODULE_AUTHOR("Cadence Design Systems, Inc.");
+MODULE_DESCRIPTION("Cadence MHDP PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/cadence/phy-cadence-sierra.c b/drivers/phy/cadence/phy-cadence-sierra.c
new file mode 100644
index 0000000..de10402
--- /dev/null
+++ b/drivers/phy/cadence/phy-cadence-sierra.c
@@ -0,0 +1,395 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cadence Sierra PHY Driver
+ *
+ * Copyright (c) 2018 Cadence Design Systems
+ * Author: Alan Douglas <adouglas@cadence.com>
+ *
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <dt-bindings/phy/phy.h>
+
+/* PHY register offsets */
+#define SIERRA_PHY_PLL_CFG (0xc00e << 2)
+#define SIERRA_DET_STANDEC_A (0x4000 << 2)
+#define SIERRA_DET_STANDEC_B (0x4001 << 2)
+#define SIERRA_DET_STANDEC_C (0x4002 << 2)
+#define SIERRA_DET_STANDEC_D (0x4003 << 2)
+#define SIERRA_DET_STANDEC_E (0x4004 << 2)
+#define SIERRA_PSM_LANECAL (0x4008 << 2)
+#define SIERRA_PSM_DIAG (0x4015 << 2)
+#define SIERRA_PSC_TX_A0 (0x4028 << 2)
+#define SIERRA_PSC_TX_A1 (0x4029 << 2)
+#define SIERRA_PSC_TX_A2 (0x402A << 2)
+#define SIERRA_PSC_TX_A3 (0x402B << 2)
+#define SIERRA_PSC_RX_A0 (0x4030 << 2)
+#define SIERRA_PSC_RX_A1 (0x4031 << 2)
+#define SIERRA_PSC_RX_A2 (0x4032 << 2)
+#define SIERRA_PSC_RX_A3 (0x4033 << 2)
+#define SIERRA_PLLCTRL_SUBRATE (0x403A << 2)
+#define SIERRA_PLLCTRL_GEN_D (0x403E << 2)
+#define SIERRA_DRVCTRL_ATTEN (0x406A << 2)
+#define SIERRA_CLKPATHCTRL_TMR (0x4081 << 2)
+#define SIERRA_RX_CREQ_FLTR_A_MODE1 (0x4087 << 2)
+#define SIERRA_RX_CREQ_FLTR_A_MODE0 (0x4088 << 2)
+#define SIERRA_CREQ_CCLKDET_MODE01 (0x408E << 2)
+#define SIERRA_RX_CTLE_MAINTENANCE (0x4091 << 2)
+#define SIERRA_CREQ_FSMCLK_SEL (0x4092 << 2)
+#define SIERRA_CTLELUT_CTRL (0x4098 << 2)
+#define SIERRA_DFE_ECMP_RATESEL (0x40C0 << 2)
+#define SIERRA_DFE_SMP_RATESEL (0x40C1 << 2)
+#define SIERRA_DEQ_VGATUNE_CTRL (0x40E1 << 2)
+#define SIERRA_TMRVAL_MODE3 (0x416E << 2)
+#define SIERRA_TMRVAL_MODE2 (0x416F << 2)
+#define SIERRA_TMRVAL_MODE1 (0x4170 << 2)
+#define SIERRA_TMRVAL_MODE0 (0x4171 << 2)
+#define SIERRA_PICNT_MODE1 (0x4174 << 2)
+#define SIERRA_CPI_OUTBUF_RATESEL (0x417C << 2)
+#define SIERRA_LFPSFILT_NS (0x418A << 2)
+#define SIERRA_LFPSFILT_RD (0x418B << 2)
+#define SIERRA_LFPSFILT_MP (0x418C << 2)
+#define SIERRA_SDFILT_H2L_A (0x4191 << 2)
+
+#define SIERRA_MACRO_ID 0x00007364
+#define SIERRA_MAX_LANES 4
+
+struct cdns_sierra_inst {
+ struct phy *phy;
+ u32 phy_type;
+ u32 num_lanes;
+ u32 mlane;
+ struct reset_control *lnk_rst;
+};
+
+struct cdns_reg_pairs {
+ u16 val;
+ u32 off;
+};
+
+struct cdns_sierra_data {
+ u32 id_value;
+ u32 pcie_regs;
+ u32 usb_regs;
+ struct cdns_reg_pairs *pcie_vals;
+ struct cdns_reg_pairs *usb_vals;
+};
+
+struct cdns_sierra_phy {
+ struct device *dev;
+ void __iomem *base;
+ struct cdns_sierra_data *init_data;
+ struct cdns_sierra_inst phys[SIERRA_MAX_LANES];
+ struct reset_control *phy_rst;
+ struct reset_control *apb_rst;
+ struct clk *clk;
+ int nsubnodes;
+ bool autoconf;
+};
+
+static void cdns_sierra_phy_init(struct phy *gphy)
+{
+ struct cdns_sierra_inst *ins = phy_get_drvdata(gphy);
+ struct cdns_sierra_phy *phy = dev_get_drvdata(gphy->dev.parent);
+ int i, j;
+ struct cdns_reg_pairs *vals;
+ u32 num_regs;
+
+ if (ins->phy_type == PHY_TYPE_PCIE) {
+ num_regs = phy->init_data->pcie_regs;
+ vals = phy->init_data->pcie_vals;
+ } else if (ins->phy_type == PHY_TYPE_USB3) {
+ num_regs = phy->init_data->usb_regs;
+ vals = phy->init_data->usb_vals;
+ } else {
+ return;
+ }
+ for (i = 0; i < ins->num_lanes; i++)
+ for (j = 0; j < num_regs ; j++)
+ writel(vals[j].val, phy->base +
+ vals[j].off + (i + ins->mlane) * 0x800);
+}
+
+static int cdns_sierra_phy_on(struct phy *gphy)
+{
+ struct cdns_sierra_inst *ins = phy_get_drvdata(gphy);
+
+ /* Take the PHY lane group out of reset */
+ return reset_control_deassert(ins->lnk_rst);
+}
+
+static int cdns_sierra_phy_off(struct phy *gphy)
+{
+ struct cdns_sierra_inst *ins = phy_get_drvdata(gphy);
+
+ return reset_control_assert(ins->lnk_rst);
+}
+
+static const struct phy_ops ops = {
+ .power_on = cdns_sierra_phy_on,
+ .power_off = cdns_sierra_phy_off,
+ .owner = THIS_MODULE,
+};
+
+static int cdns_sierra_get_optional(struct cdns_sierra_inst *inst,
+ struct device_node *child)
+{
+ if (of_property_read_u32(child, "reg", &inst->mlane))
+ return -EINVAL;
+
+ if (of_property_read_u32(child, "cdns,num-lanes", &inst->num_lanes))
+ return -EINVAL;
+
+ if (of_property_read_u32(child, "cdns,phy-type", &inst->phy_type))
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct of_device_id cdns_sierra_id_table[];
+
+static int cdns_sierra_phy_probe(struct platform_device *pdev)
+{
+ struct cdns_sierra_phy *sp;
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct resource *res;
+ int i, ret, node = 0;
+ struct device_node *dn = dev->of_node, *child;
+
+ if (of_get_child_count(dn) == 0)
+ return -ENODEV;
+
+ sp = devm_kzalloc(dev, sizeof(*sp), GFP_KERNEL);
+ if (!sp)
+ return -ENOMEM;
+ dev_set_drvdata(dev, sp);
+ sp->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ sp->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(sp->base)) {
+ dev_err(dev, "missing \"reg\"\n");
+ return PTR_ERR(sp->base);
+ }
+
+ /* Get init data for this PHY */
+ match = of_match_device(cdns_sierra_id_table, dev);
+ if (!match)
+ return -EINVAL;
+ sp->init_data = (struct cdns_sierra_data *)match->data;
+
+ platform_set_drvdata(pdev, sp);
+
+ sp->clk = devm_clk_get(dev, "phy_clk");
+ if (IS_ERR(sp->clk)) {
+ dev_err(dev, "failed to get clock phy_clk\n");
+ return PTR_ERR(sp->clk);
+ }
+
+ sp->phy_rst = devm_reset_control_get(dev, "sierra_reset");
+ if (IS_ERR(sp->phy_rst)) {
+ dev_err(dev, "failed to get reset\n");
+ return PTR_ERR(sp->phy_rst);
+ }
+
+ sp->apb_rst = devm_reset_control_get(dev, "sierra_apb");
+ if (IS_ERR(sp->apb_rst)) {
+ dev_err(dev, "failed to get apb reset\n");
+ return PTR_ERR(sp->apb_rst);
+ }
+
+ ret = clk_prepare_enable(sp->clk);
+ if (ret)
+ return ret;
+
+ /* Enable APB */
+ reset_control_deassert(sp->apb_rst);
+
+ /* Check that PHY is present */
+ if (sp->init_data->id_value != readl(sp->base)) {
+ ret = -EINVAL;
+ goto clk_disable;
+ }
+
+ sp->autoconf = of_property_read_bool(dn, "cdns,autoconf");
+
+ for_each_available_child_of_node(dn, child) {
+ struct phy *gphy;
+
+ sp->phys[node].lnk_rst =
+ of_reset_control_get_exclusive_by_index(child, 0);
+
+ if (IS_ERR(sp->phys[node].lnk_rst)) {
+ dev_err(dev, "failed to get reset %s\n",
+ child->full_name);
+ ret = PTR_ERR(sp->phys[node].lnk_rst);
+ goto put_child2;
+ }
+
+ if (!sp->autoconf) {
+ ret = cdns_sierra_get_optional(&sp->phys[node], child);
+ if (ret) {
+ dev_err(dev, "missing property in node %s\n",
+ child->name);
+ goto put_child;
+ }
+ }
+
+ gphy = devm_phy_create(dev, child, &ops);
+
+ if (IS_ERR(gphy)) {
+ ret = PTR_ERR(gphy);
+ goto put_child;
+ }
+ sp->phys[node].phy = gphy;
+ phy_set_drvdata(gphy, &sp->phys[node]);
+
+ /* Initialise the PHY registers, unless auto configured */
+ if (!sp->autoconf)
+ cdns_sierra_phy_init(gphy);
+
+ node++;
+ }
+ sp->nsubnodes = node;
+
+ /* If more than one subnode, configure the PHY as multilink */
+ if (!sp->autoconf && sp->nsubnodes > 1)
+ writel(2, sp->base + SIERRA_PHY_PLL_CFG);
+
+ pm_runtime_enable(dev);
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ reset_control_deassert(sp->phy_rst);
+ return PTR_ERR_OR_ZERO(phy_provider);
+
+put_child:
+ node++;
+put_child2:
+ for (i = 0; i < node; i++)
+ reset_control_put(sp->phys[i].lnk_rst);
+ of_node_put(child);
+clk_disable:
+ clk_disable_unprepare(sp->clk);
+ reset_control_assert(sp->apb_rst);
+ return ret;
+}
+
+static int cdns_sierra_phy_remove(struct platform_device *pdev)
+{
+ struct cdns_sierra_phy *phy = dev_get_drvdata(pdev->dev.parent);
+ int i;
+
+ reset_control_assert(phy->phy_rst);
+ reset_control_assert(phy->apb_rst);
+ pm_runtime_disable(&pdev->dev);
+
+ /*
+ * The device level resets will be put automatically.
+ * Need to put the subnode resets here though.
+ */
+ for (i = 0; i < phy->nsubnodes; i++) {
+ reset_control_assert(phy->phys[i].lnk_rst);
+ reset_control_put(phy->phys[i].lnk_rst);
+ }
+ return 0;
+}
+
+static struct cdns_reg_pairs cdns_usb_regs[] = {
+ /*
+ * Write USB configuration parameters to the PHY.
+ * These values are specific to this specific hardware
+ * configuration.
+ */
+ {0xFE0A, SIERRA_DET_STANDEC_A},
+ {0x000F, SIERRA_DET_STANDEC_B},
+ {0x55A5, SIERRA_DET_STANDEC_C},
+ {0x69AD, SIERRA_DET_STANDEC_D},
+ {0x0241, SIERRA_DET_STANDEC_E},
+ {0x0110, SIERRA_PSM_LANECAL},
+ {0xCF00, SIERRA_PSM_DIAG},
+ {0x001F, SIERRA_PSC_TX_A0},
+ {0x0007, SIERRA_PSC_TX_A1},
+ {0x0003, SIERRA_PSC_TX_A2},
+ {0x0003, SIERRA_PSC_TX_A3},
+ {0x0FFF, SIERRA_PSC_RX_A0},
+ {0x0003, SIERRA_PSC_RX_A1},
+ {0x0003, SIERRA_PSC_RX_A2},
+ {0x0001, SIERRA_PSC_RX_A3},
+ {0x0001, SIERRA_PLLCTRL_SUBRATE},
+ {0x0406, SIERRA_PLLCTRL_GEN_D},
+ {0x0000, SIERRA_DRVCTRL_ATTEN},
+ {0x823E, SIERRA_CLKPATHCTRL_TMR},
+ {0x078F, SIERRA_RX_CREQ_FLTR_A_MODE1},
+ {0x078F, SIERRA_RX_CREQ_FLTR_A_MODE0},
+ {0x7B3C, SIERRA_CREQ_CCLKDET_MODE01},
+ {0x023C, SIERRA_RX_CTLE_MAINTENANCE},
+ {0x3232, SIERRA_CREQ_FSMCLK_SEL},
+ {0x8452, SIERRA_CTLELUT_CTRL},
+ {0x4121, SIERRA_DFE_ECMP_RATESEL},
+ {0x4121, SIERRA_DFE_SMP_RATESEL},
+ {0x9999, SIERRA_DEQ_VGATUNE_CTRL},
+ {0x0330, SIERRA_TMRVAL_MODE0},
+ {0x01FF, SIERRA_PICNT_MODE1},
+ {0x0009, SIERRA_CPI_OUTBUF_RATESEL},
+ {0x000F, SIERRA_LFPSFILT_NS},
+ {0x0009, SIERRA_LFPSFILT_RD},
+ {0x0001, SIERRA_LFPSFILT_MP},
+ {0x8013, SIERRA_SDFILT_H2L_A},
+ {0x0400, SIERRA_TMRVAL_MODE1},
+};
+
+static struct cdns_reg_pairs cdns_pcie_regs[] = {
+ /*
+ * Write PCIe configuration parameters to the PHY.
+ * These values are specific to this specific hardware
+ * configuration.
+ */
+ {0x891f, SIERRA_DET_STANDEC_D},
+ {0x0053, SIERRA_DET_STANDEC_E},
+ {0x0400, SIERRA_TMRVAL_MODE2},
+ {0x0200, SIERRA_TMRVAL_MODE3},
+};
+
+static const struct cdns_sierra_data cdns_map_sierra = {
+ SIERRA_MACRO_ID,
+ ARRAY_SIZE(cdns_pcie_regs),
+ ARRAY_SIZE(cdns_usb_regs),
+ cdns_pcie_regs,
+ cdns_usb_regs
+};
+
+static const struct of_device_id cdns_sierra_id_table[] = {
+ {
+ .compatible = "cdns,sierra-phy-t0",
+ .data = &cdns_map_sierra,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, cdns_sierra_id_table);
+
+static struct platform_driver cdns_sierra_driver = {
+ .probe = cdns_sierra_phy_probe,
+ .remove = cdns_sierra_phy_remove,
+ .driver = {
+ .name = "cdns-sierra-phy",
+ .of_match_table = cdns_sierra_id_table,
+ },
+};
+module_platform_driver(cdns_sierra_driver);
+
+MODULE_ALIAS("platform:cdns_sierra");
+MODULE_AUTHOR("Cadence Design Systems");
+MODULE_DESCRIPTION("CDNS sierra phy driver");
+MODULE_LICENSE("GPL v2");