summaryrefslogtreecommitdiffstats
path: root/drivers/phy/freescale
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/phy/freescale
parentInitial commit. (diff)
downloadlinux-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/freescale')
-rw-r--r--drivers/phy/freescale/Kconfig49
-rw-r--r--drivers/phy/freescale/Makefile6
-rw-r--r--drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c749
-rw-r--r--drivers/phy/freescale/phy-fsl-imx8m-pcie.c286
-rw-r--r--drivers/phy/freescale/phy-fsl-imx8mq-usb.c418
-rw-r--r--drivers/phy/freescale/phy-fsl-imx8qm-lvds-phy.c448
-rw-r--r--drivers/phy/freescale/phy-fsl-lynx-28g.c645
7 files changed, 2601 insertions, 0 deletions
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
new file mode 100644
index 0000000000..853958fb2c
--- /dev/null
+++ b/drivers/phy/freescale/Kconfig
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+if (ARCH_MXC && ARM64) || COMPILE_TEST
+
+config PHY_FSL_IMX8MQ_USB
+ tristate "Freescale i.MX8M USB3 PHY"
+ depends on OF && HAS_IOMEM
+ select GENERIC_PHY
+ default ARCH_MXC && ARM64
+
+config PHY_MIXEL_LVDS_PHY
+ tristate "Mixel LVDS PHY support"
+ depends on OF
+ select GENERIC_PHY
+ select REGMAP_MMIO
+ help
+ Enable this to add support for the Mixel LVDS PHY as found
+ on NXP's i.MX8qm SoC.
+
+config PHY_MIXEL_MIPI_DPHY
+ tristate "Mixel MIPI DSI PHY support"
+ depends on OF && HAS_IOMEM
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ select REGMAP_MMIO
+ help
+ Enable this to add support for the Mixel DSI PHY as found
+ on NXP's i.MX8 family of SOCs.
+
+config PHY_FSL_IMX8M_PCIE
+ tristate "Freescale i.MX8M PCIE PHY"
+ depends on OF && HAS_IOMEM
+ select GENERIC_PHY
+ help
+ Enable this to add support for the PCIE PHY as found on
+ i.MX8M family of SOCs.
+
+endif
+
+config PHY_FSL_LYNX_28G
+ tristate "Freescale Layerscape Lynx 28G SerDes PHY support"
+ depends on OF
+ depends on ARCH_LAYERSCAPE || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ Enable this to add support for the Lynx SerDes 28G PHY as
+ found on NXP's Layerscape platforms such as LX2160A.
+ Used to change the protocol running on SerDes lanes at runtime.
+ Only useful for a restricted set of Ethernet protocols.
diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
new file mode 100644
index 0000000000..cedb328bc4
--- /dev/null
+++ b/drivers/phy/freescale/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_PHY_FSL_IMX8MQ_USB) += phy-fsl-imx8mq-usb.o
+obj-$(CONFIG_PHY_MIXEL_LVDS_PHY) += phy-fsl-imx8qm-lvds-phy.o
+obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o
+obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) += phy-fsl-imx8m-pcie.o
+obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o
diff --git a/drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c b/drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c
new file mode 100644
index 0000000000..e625b32889
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c
@@ -0,0 +1,749 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2017,2018 NXP
+ * Copyright 2019 Purism SPC
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/firmware/imx/ipc.h>
+#include <linux/firmware/imx/svc/misc.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+
+/* Control and Status Registers(CSR) */
+#define PHY_CTRL 0x00
+#define CCM_MASK GENMASK(7, 5)
+#define CCM(n) FIELD_PREP(CCM_MASK, (n))
+#define CCM_1_2V 0x5
+#define CA_MASK GENMASK(4, 2)
+#define CA_3_51MA 0x4
+#define CA(n) FIELD_PREP(CA_MASK, (n))
+#define RFB BIT(1)
+#define LVDS_EN BIT(0)
+
+/* DPHY registers */
+#define DPHY_PD_DPHY 0x00
+#define DPHY_M_PRG_HS_PREPARE 0x04
+#define DPHY_MC_PRG_HS_PREPARE 0x08
+#define DPHY_M_PRG_HS_ZERO 0x0c
+#define DPHY_MC_PRG_HS_ZERO 0x10
+#define DPHY_M_PRG_HS_TRAIL 0x14
+#define DPHY_MC_PRG_HS_TRAIL 0x18
+#define DPHY_PD_PLL 0x1c
+#define DPHY_TST 0x20
+#define DPHY_CN 0x24
+#define DPHY_CM 0x28
+#define DPHY_CO 0x2c
+#define DPHY_LOCK 0x30
+#define DPHY_LOCK_BYP 0x34
+#define DPHY_REG_BYPASS_PLL 0x4C
+
+#define MBPS(x) ((x) * 1000000)
+
+#define DATA_RATE_MAX_SPEED MBPS(1500)
+#define DATA_RATE_MIN_SPEED MBPS(80)
+
+#define PLL_LOCK_SLEEP 10
+#define PLL_LOCK_TIMEOUT 1000
+
+#define CN_BUF 0xcb7a89c0
+#define CO_BUF 0x63
+#define CM(x) ( \
+ ((x) < 32) ? 0xe0 | ((x) - 16) : \
+ ((x) < 64) ? 0xc0 | ((x) - 32) : \
+ ((x) < 128) ? 0x80 | ((x) - 64) : \
+ ((x) - 128))
+#define CN(x) (((x) == 1) ? 0x1f : (((CN_BUF) >> ((x) - 1)) & 0x1f))
+#define CO(x) ((CO_BUF) >> (8 - (x)) & 0x03)
+
+/* PHY power on is active low */
+#define PWR_ON 0
+#define PWR_OFF 1
+
+#define MIN_VCO_FREQ 640000000
+#define MAX_VCO_FREQ 1500000000
+
+#define MIN_LVDS_REFCLK_FREQ 24000000
+#define MAX_LVDS_REFCLK_FREQ 150000000
+
+enum mixel_dphy_devtype {
+ MIXEL_IMX8MQ,
+ MIXEL_IMX8QXP,
+};
+
+struct mixel_dphy_devdata {
+ u8 reg_tx_rcal;
+ u8 reg_auto_pd_en;
+ u8 reg_rxlprp;
+ u8 reg_rxcdrp;
+ u8 reg_rxhs_settle;
+ bool is_combo; /* MIPI DPHY and LVDS PHY combo */
+};
+
+static const struct mixel_dphy_devdata mixel_dphy_devdata[] = {
+ [MIXEL_IMX8MQ] = {
+ .reg_tx_rcal = 0x38,
+ .reg_auto_pd_en = 0x3c,
+ .reg_rxlprp = 0x40,
+ .reg_rxcdrp = 0x44,
+ .reg_rxhs_settle = 0x48,
+ .is_combo = false,
+ },
+ [MIXEL_IMX8QXP] = {
+ .is_combo = true,
+ },
+};
+
+struct mixel_dphy_cfg {
+ /* DPHY PLL parameters */
+ u32 cm;
+ u32 cn;
+ u32 co;
+ /* DPHY register values */
+ u8 mc_prg_hs_prepare;
+ u8 m_prg_hs_prepare;
+ u8 mc_prg_hs_zero;
+ u8 m_prg_hs_zero;
+ u8 mc_prg_hs_trail;
+ u8 m_prg_hs_trail;
+ u8 rxhs_settle;
+};
+
+struct mixel_dphy_priv {
+ struct mixel_dphy_cfg cfg;
+ struct regmap *regmap;
+ struct regmap *lvds_regmap;
+ struct clk *phy_ref_clk;
+ const struct mixel_dphy_devdata *devdata;
+ struct imx_sc_ipc *ipc_handle;
+ bool is_slave;
+ int id;
+};
+
+static const struct regmap_config mixel_dphy_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = DPHY_REG_BYPASS_PLL,
+ .name = "mipi-dphy",
+};
+
+static int phy_write(struct phy *phy, u32 value, unsigned int reg)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = regmap_write(priv->regmap, reg, value);
+ if (ret < 0)
+ dev_err(&phy->dev, "Failed to write DPHY reg %d: %d\n", reg,
+ ret);
+ return ret;
+}
+
+/*
+ * Find a ratio close to the desired one using continued fraction
+ * approximation ending either at exact match or maximum allowed
+ * nominator, denominator.
+ */
+static void get_best_ratio(u32 *pnum, u32 *pdenom, u32 max_n, u32 max_d)
+{
+ u32 a = *pnum;
+ u32 b = *pdenom;
+ u32 c;
+ u32 n[] = {0, 1};
+ u32 d[] = {1, 0};
+ u32 whole;
+ unsigned int i = 1;
+
+ while (b) {
+ i ^= 1;
+ whole = a / b;
+ n[i] += (n[i ^ 1] * whole);
+ d[i] += (d[i ^ 1] * whole);
+ if ((n[i] > max_n) || (d[i] > max_d)) {
+ i ^= 1;
+ break;
+ }
+ c = a - (b * whole);
+ a = b;
+ b = c;
+ }
+ *pnum = n[i];
+ *pdenom = d[i];
+}
+
+static int mixel_dphy_config_from_opts(struct phy *phy,
+ struct phy_configure_opts_mipi_dphy *dphy_opts,
+ struct mixel_dphy_cfg *cfg)
+{
+ struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent);
+ unsigned long ref_clk = clk_get_rate(priv->phy_ref_clk);
+ u32 lp_t, numerator, denominator;
+ unsigned long long tmp;
+ u32 n;
+ int i;
+
+ if (dphy_opts->hs_clk_rate > DATA_RATE_MAX_SPEED ||
+ dphy_opts->hs_clk_rate < DATA_RATE_MIN_SPEED)
+ return -EINVAL;
+
+ numerator = dphy_opts->hs_clk_rate;
+ denominator = ref_clk;
+ get_best_ratio(&numerator, &denominator, 255, 256);
+ if (!numerator || !denominator) {
+ dev_err(&phy->dev, "Invalid %d/%d for %ld/%ld\n",
+ numerator, denominator,
+ dphy_opts->hs_clk_rate, ref_clk);
+ return -EINVAL;
+ }
+
+ while ((numerator < 16) && (denominator <= 128)) {
+ numerator <<= 1;
+ denominator <<= 1;
+ }
+ /*
+ * CM ranges between 16 and 255
+ * CN ranges between 1 and 32
+ * CO is power of 2: 1, 2, 4, 8
+ */
+ i = __ffs(denominator);
+ if (i > 3)
+ i = 3;
+ cfg->cn = denominator >> i;
+ cfg->co = 1 << i;
+ cfg->cm = numerator;
+
+ if (cfg->cm < 16 || cfg->cm > 255 ||
+ cfg->cn < 1 || cfg->cn > 32 ||
+ cfg->co < 1 || cfg->co > 8) {
+ dev_err(&phy->dev, "Invalid CM/CN/CO values: %u/%u/%u\n",
+ cfg->cm, cfg->cn, cfg->co);
+ dev_err(&phy->dev, "for hs_clk/ref_clk=%ld/%ld ~ %d/%d\n",
+ dphy_opts->hs_clk_rate, ref_clk,
+ numerator, denominator);
+ return -EINVAL;
+ }
+
+ dev_dbg(&phy->dev, "hs_clk/ref_clk=%ld/%ld ~ %d/%d\n",
+ dphy_opts->hs_clk_rate, ref_clk, numerator, denominator);
+
+ /* LP clock period */
+ tmp = 1000000000000LL;
+ do_div(tmp, dphy_opts->lp_clk_rate); /* ps */
+ if (tmp > ULONG_MAX)
+ return -EINVAL;
+
+ lp_t = tmp;
+ dev_dbg(&phy->dev, "LP clock %lu, period: %u ps\n",
+ dphy_opts->lp_clk_rate, lp_t);
+
+ /* hs_prepare: in lp clock periods */
+ if (2 * dphy_opts->hs_prepare > 5 * lp_t) {
+ dev_err(&phy->dev,
+ "hs_prepare (%u) > 2.5 * lp clock period (%u)\n",
+ dphy_opts->hs_prepare, lp_t);
+ return -EINVAL;
+ }
+ /* 00: lp_t, 01: 1.5 * lp_t, 10: 2 * lp_t, 11: 2.5 * lp_t */
+ if (dphy_opts->hs_prepare < lp_t) {
+ n = 0;
+ } else {
+ tmp = 2 * (dphy_opts->hs_prepare - lp_t);
+ do_div(tmp, lp_t);
+ n = tmp;
+ }
+ cfg->m_prg_hs_prepare = n;
+
+ /* clk_prepare: in lp clock periods */
+ if (2 * dphy_opts->clk_prepare > 3 * lp_t) {
+ dev_err(&phy->dev,
+ "clk_prepare (%u) > 1.5 * lp clock period (%u)\n",
+ dphy_opts->clk_prepare, lp_t);
+ return -EINVAL;
+ }
+ /* 00: lp_t, 01: 1.5 * lp_t */
+ cfg->mc_prg_hs_prepare = dphy_opts->clk_prepare > lp_t ? 1 : 0;
+
+ /* hs_zero: formula from NXP BSP */
+ n = (144 * (dphy_opts->hs_clk_rate / 1000000) - 47500) / 10000;
+ cfg->m_prg_hs_zero = n < 1 ? 1 : n;
+
+ /* clk_zero: formula from NXP BSP */
+ n = (34 * (dphy_opts->hs_clk_rate / 1000000) - 2500) / 1000;
+ cfg->mc_prg_hs_zero = n < 1 ? 1 : n;
+
+ /* clk_trail, hs_trail: formula from NXP BSP */
+ n = (103 * (dphy_opts->hs_clk_rate / 1000000) + 10000) / 10000;
+ if (n > 15)
+ n = 15;
+ if (n < 1)
+ n = 1;
+ cfg->m_prg_hs_trail = n;
+ cfg->mc_prg_hs_trail = n;
+
+ /* rxhs_settle: formula from NXP BSP */
+ if (dphy_opts->hs_clk_rate < MBPS(80))
+ cfg->rxhs_settle = 0x0d;
+ else if (dphy_opts->hs_clk_rate < MBPS(90))
+ cfg->rxhs_settle = 0x0c;
+ else if (dphy_opts->hs_clk_rate < MBPS(125))
+ cfg->rxhs_settle = 0x0b;
+ else if (dphy_opts->hs_clk_rate < MBPS(150))
+ cfg->rxhs_settle = 0x0a;
+ else if (dphy_opts->hs_clk_rate < MBPS(225))
+ cfg->rxhs_settle = 0x09;
+ else if (dphy_opts->hs_clk_rate < MBPS(500))
+ cfg->rxhs_settle = 0x08;
+ else
+ cfg->rxhs_settle = 0x07;
+
+ dev_dbg(&phy->dev, "phy_config: %u %u %u %u %u %u %u\n",
+ cfg->m_prg_hs_prepare, cfg->mc_prg_hs_prepare,
+ cfg->m_prg_hs_zero, cfg->mc_prg_hs_zero,
+ cfg->m_prg_hs_trail, cfg->mc_prg_hs_trail,
+ cfg->rxhs_settle);
+
+ return 0;
+}
+
+static void mixel_phy_set_hs_timings(struct phy *phy)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+
+ phy_write(phy, priv->cfg.m_prg_hs_prepare, DPHY_M_PRG_HS_PREPARE);
+ phy_write(phy, priv->cfg.mc_prg_hs_prepare, DPHY_MC_PRG_HS_PREPARE);
+ phy_write(phy, priv->cfg.m_prg_hs_zero, DPHY_M_PRG_HS_ZERO);
+ phy_write(phy, priv->cfg.mc_prg_hs_zero, DPHY_MC_PRG_HS_ZERO);
+ phy_write(phy, priv->cfg.m_prg_hs_trail, DPHY_M_PRG_HS_TRAIL);
+ phy_write(phy, priv->cfg.mc_prg_hs_trail, DPHY_MC_PRG_HS_TRAIL);
+ phy_write(phy, priv->cfg.rxhs_settle, priv->devdata->reg_rxhs_settle);
+}
+
+static int mixel_dphy_set_pll_params(struct phy *phy)
+{
+ struct mixel_dphy_priv *priv = dev_get_drvdata(phy->dev.parent);
+
+ if (priv->cfg.cm < 16 || priv->cfg.cm > 255 ||
+ priv->cfg.cn < 1 || priv->cfg.cn > 32 ||
+ priv->cfg.co < 1 || priv->cfg.co > 8) {
+ dev_err(&phy->dev, "Invalid CM/CN/CO values! (%u/%u/%u)\n",
+ priv->cfg.cm, priv->cfg.cn, priv->cfg.co);
+ return -EINVAL;
+ }
+ dev_dbg(&phy->dev, "Using CM:%u CN:%u CO:%u\n",
+ priv->cfg.cm, priv->cfg.cn, priv->cfg.co);
+ phy_write(phy, CM(priv->cfg.cm), DPHY_CM);
+ phy_write(phy, CN(priv->cfg.cn), DPHY_CN);
+ phy_write(phy, CO(priv->cfg.co), DPHY_CO);
+ return 0;
+}
+
+static int
+mixel_dphy_configure_mipi_dphy(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+ struct mixel_dphy_cfg cfg = { 0 };
+ int ret;
+
+ ret = mixel_dphy_config_from_opts(phy, &opts->mipi_dphy, &cfg);
+ if (ret)
+ return ret;
+
+ /* Update the configuration */
+ memcpy(&priv->cfg, &cfg, sizeof(struct mixel_dphy_cfg));
+
+ phy_write(phy, 0x00, DPHY_LOCK_BYP);
+ phy_write(phy, 0x01, priv->devdata->reg_tx_rcal);
+ phy_write(phy, 0x00, priv->devdata->reg_auto_pd_en);
+ phy_write(phy, 0x02, priv->devdata->reg_rxlprp);
+ phy_write(phy, 0x02, priv->devdata->reg_rxcdrp);
+ phy_write(phy, 0x25, DPHY_TST);
+
+ mixel_phy_set_hs_timings(phy);
+ ret = mixel_dphy_set_pll_params(phy);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int
+mixel_dphy_configure_lvds_phy(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+ struct phy_configure_opts_lvds *lvds_opts = &opts->lvds;
+ unsigned long data_rate;
+ unsigned long fvco;
+ u32 rsc;
+ u32 co;
+ int ret;
+
+ priv->is_slave = lvds_opts->is_slave;
+
+ /* LVDS interface pins */
+ regmap_write(priv->lvds_regmap, PHY_CTRL,
+ CCM(CCM_1_2V) | CA(CA_3_51MA) | RFB);
+
+ /* enable MODE8 only for slave LVDS PHY */
+ rsc = priv->id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0;
+ ret = imx_sc_misc_set_control(priv->ipc_handle, rsc, IMX_SC_C_DUAL_MODE,
+ lvds_opts->is_slave);
+ if (ret) {
+ dev_err(&phy->dev, "Failed to configure MODE8: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Choose an appropriate divider ratio to meet the requirement of
+ * PLL VCO frequency range.
+ *
+ * ----- 640MHz ~ 1500MHz ------------ ---------------
+ * | VCO | ----------------> | CO divider | -> | LVDS data rate|
+ * ----- FVCO ------------ ---------------
+ * 1/2/4/8 div 7 * differential_clk_rate
+ */
+ data_rate = 7 * lvds_opts->differential_clk_rate;
+ for (co = 1; co <= 8; co *= 2) {
+ fvco = data_rate * co;
+
+ if (fvco >= MIN_VCO_FREQ)
+ break;
+ }
+
+ if (fvco < MIN_VCO_FREQ || fvco > MAX_VCO_FREQ) {
+ dev_err(&phy->dev, "VCO frequency %lu is out of range\n", fvco);
+ return -ERANGE;
+ }
+
+ /*
+ * CO is configurable, while CN and CM are not,
+ * as fixed ratios 1 and 7 are applied respectively.
+ */
+ phy_write(phy, __ffs(co), DPHY_CO);
+
+ /* set reference clock rate */
+ clk_set_rate(priv->phy_ref_clk, lvds_opts->differential_clk_rate);
+
+ return ret;
+}
+
+static int mixel_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+ if (!opts) {
+ dev_err(&phy->dev, "No configuration options\n");
+ return -EINVAL;
+ }
+
+ if (phy->attrs.mode == PHY_MODE_MIPI_DPHY)
+ return mixel_dphy_configure_mipi_dphy(phy, opts);
+ else if (phy->attrs.mode == PHY_MODE_LVDS)
+ return mixel_dphy_configure_lvds_phy(phy, opts);
+
+ dev_err(&phy->dev,
+ "Failed to configure PHY with invalid PHY mode: %d\n", phy->attrs.mode);
+
+ return -EINVAL;
+}
+
+static int
+mixel_dphy_validate_lvds_phy(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct phy_configure_opts_lvds *lvds_cfg = &opts->lvds;
+
+ if (lvds_cfg->bits_per_lane_and_dclk_cycle != 7) {
+ dev_err(&phy->dev, "Invalid bits per LVDS data lane: %u\n",
+ lvds_cfg->bits_per_lane_and_dclk_cycle);
+ return -EINVAL;
+ }
+
+ if (lvds_cfg->lanes != 4) {
+ dev_err(&phy->dev, "Invalid LVDS data lanes: %u\n", lvds_cfg->lanes);
+ return -EINVAL;
+ }
+
+ if (lvds_cfg->differential_clk_rate < MIN_LVDS_REFCLK_FREQ ||
+ lvds_cfg->differential_clk_rate > MAX_LVDS_REFCLK_FREQ) {
+ dev_err(&phy->dev,
+ "Invalid LVDS differential clock rate: %lu\n",
+ lvds_cfg->differential_clk_rate);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mixel_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
+ union phy_configure_opts *opts)
+{
+ if (mode == PHY_MODE_MIPI_DPHY) {
+ struct mixel_dphy_cfg mipi_dphy_cfg = { 0 };
+
+ return mixel_dphy_config_from_opts(phy, &opts->mipi_dphy,
+ &mipi_dphy_cfg);
+ } else if (mode == PHY_MODE_LVDS) {
+ return mixel_dphy_validate_lvds_phy(phy, opts);
+ }
+
+ dev_err(&phy->dev,
+ "Failed to validate PHY with invalid PHY mode: %d\n", mode);
+ return -EINVAL;
+}
+
+static int mixel_dphy_init(struct phy *phy)
+{
+ phy_write(phy, PWR_OFF, DPHY_PD_PLL);
+ phy_write(phy, PWR_OFF, DPHY_PD_DPHY);
+
+ return 0;
+}
+
+static int mixel_dphy_exit(struct phy *phy)
+{
+ phy_write(phy, 0, DPHY_CM);
+ phy_write(phy, 0, DPHY_CN);
+ phy_write(phy, 0, DPHY_CO);
+
+ return 0;
+}
+
+static int mixel_dphy_power_on_mipi_dphy(struct phy *phy)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+ u32 locked;
+ int ret;
+
+ phy_write(phy, PWR_ON, DPHY_PD_PLL);
+ ret = regmap_read_poll_timeout(priv->regmap, DPHY_LOCK, locked,
+ locked, PLL_LOCK_SLEEP,
+ PLL_LOCK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(&phy->dev, "Could not get DPHY lock (%d)!\n", ret);
+ return ret;
+ }
+ phy_write(phy, PWR_ON, DPHY_PD_DPHY);
+
+ return 0;
+}
+
+static int mixel_dphy_power_on_lvds_phy(struct phy *phy)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+ u32 locked;
+ int ret;
+
+ regmap_update_bits(priv->lvds_regmap, PHY_CTRL, LVDS_EN, LVDS_EN);
+
+ phy_write(phy, PWR_ON, DPHY_PD_DPHY);
+ phy_write(phy, PWR_ON, DPHY_PD_PLL);
+
+ /* do not wait for slave LVDS PHY being locked */
+ if (priv->is_slave)
+ return 0;
+
+ ret = regmap_read_poll_timeout(priv->regmap, DPHY_LOCK, locked,
+ locked, PLL_LOCK_SLEEP,
+ PLL_LOCK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(&phy->dev, "Could not get LVDS PHY lock (%d)!\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mixel_dphy_power_on(struct phy *phy)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ ret = clk_prepare_enable(priv->phy_ref_clk);
+ if (ret < 0)
+ return ret;
+
+ if (phy->attrs.mode == PHY_MODE_MIPI_DPHY) {
+ ret = mixel_dphy_power_on_mipi_dphy(phy);
+ } else if (phy->attrs.mode == PHY_MODE_LVDS) {
+ ret = mixel_dphy_power_on_lvds_phy(phy);
+ } else {
+ dev_err(&phy->dev,
+ "Failed to power on PHY with invalid PHY mode: %d\n",
+ phy->attrs.mode);
+ ret = -EINVAL;
+ }
+
+ if (ret)
+ goto clock_disable;
+
+ return 0;
+clock_disable:
+ clk_disable_unprepare(priv->phy_ref_clk);
+ return ret;
+}
+
+static int mixel_dphy_power_off(struct phy *phy)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+
+ phy_write(phy, PWR_OFF, DPHY_PD_PLL);
+ phy_write(phy, PWR_OFF, DPHY_PD_DPHY);
+
+ if (phy->attrs.mode == PHY_MODE_LVDS)
+ regmap_update_bits(priv->lvds_regmap, PHY_CTRL, LVDS_EN, 0);
+
+ clk_disable_unprepare(priv->phy_ref_clk);
+
+ return 0;
+}
+
+static int mixel_dphy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct mixel_dphy_priv *priv = phy_get_drvdata(phy);
+ int ret;
+
+ if (priv->devdata->is_combo && mode != PHY_MODE_LVDS) {
+ dev_err(&phy->dev, "Failed to set PHY mode for combo PHY\n");
+ return -EINVAL;
+ }
+
+ if (!priv->devdata->is_combo && mode != PHY_MODE_MIPI_DPHY) {
+ dev_err(&phy->dev, "Failed to set PHY mode to MIPI DPHY\n");
+ return -EINVAL;
+ }
+
+ if (priv->devdata->is_combo) {
+ u32 rsc = priv->id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0;
+
+ ret = imx_sc_misc_set_control(priv->ipc_handle,
+ rsc, IMX_SC_C_MODE,
+ mode == PHY_MODE_LVDS);
+ if (ret) {
+ dev_err(&phy->dev,
+ "Failed to set PHY mode via SCU ipc: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct phy_ops mixel_dphy_phy_ops = {
+ .init = mixel_dphy_init,
+ .exit = mixel_dphy_exit,
+ .power_on = mixel_dphy_power_on,
+ .power_off = mixel_dphy_power_off,
+ .set_mode = mixel_dphy_set_mode,
+ .configure = mixel_dphy_configure,
+ .validate = mixel_dphy_validate,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id mixel_dphy_of_match[] = {
+ { .compatible = "fsl,imx8mq-mipi-dphy",
+ .data = &mixel_dphy_devdata[MIXEL_IMX8MQ] },
+ { .compatible = "fsl,imx8qxp-mipi-dphy",
+ .data = &mixel_dphy_devdata[MIXEL_IMX8QXP] },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mixel_dphy_of_match);
+
+static int mixel_dphy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct phy_provider *phy_provider;
+ struct mixel_dphy_priv *priv;
+ struct phy *phy;
+ void __iomem *base;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->devdata = of_device_get_match_data(&pdev->dev);
+ if (!priv->devdata)
+ return -EINVAL;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &mixel_dphy_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ dev_err(dev, "Couldn't create the DPHY regmap\n");
+ return PTR_ERR(priv->regmap);
+ }
+
+ priv->phy_ref_clk = devm_clk_get(&pdev->dev, "phy_ref");
+ if (IS_ERR(priv->phy_ref_clk)) {
+ dev_err(dev, "No phy_ref clock found\n");
+ return PTR_ERR(priv->phy_ref_clk);
+ }
+ dev_dbg(dev, "phy_ref clock rate: %lu\n",
+ clk_get_rate(priv->phy_ref_clk));
+
+ if (priv->devdata->is_combo) {
+ priv->lvds_regmap =
+ syscon_regmap_lookup_by_phandle(np, "fsl,syscon");
+ if (IS_ERR(priv->lvds_regmap)) {
+ ret = PTR_ERR(priv->lvds_regmap);
+ dev_err_probe(dev, ret, "Failed to get LVDS regmap\n");
+ return ret;
+ }
+
+ priv->id = of_alias_get_id(np, "mipi_dphy");
+ if (priv->id < 0) {
+ dev_err(dev, "Failed to get phy node alias id: %d\n",
+ priv->id);
+ return priv->id;
+ }
+
+ ret = imx_scu_get_handle(&priv->ipc_handle);
+ if (ret) {
+ dev_err_probe(dev, ret,
+ "Failed to get SCU ipc handle\n");
+ return ret;
+ }
+ }
+
+ dev_set_drvdata(dev, priv);
+
+ phy = devm_phy_create(dev, np, &mixel_dphy_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "Failed to create phy %ld\n", PTR_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 struct platform_driver mixel_dphy_driver = {
+ .probe = mixel_dphy_probe,
+ .driver = {
+ .name = "mixel-mipi-dphy",
+ .of_match_table = mixel_dphy_of_match,
+ }
+};
+module_platform_driver(mixel_dphy_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("Mixel MIPI-DSI PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/freescale/phy-fsl-imx8m-pcie.c b/drivers/phy/freescale/phy-fsl-imx8m-pcie.c
new file mode 100644
index 0000000000..b700f52b7b
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-imx8m-pcie.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx7-iomuxc-gpr.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-imx8-pcie.h>
+
+#define IMX8MM_PCIE_PHY_CMN_REG061 0x184
+#define ANA_PLL_CLK_OUT_TO_EXT_IO_EN BIT(0)
+#define IMX8MM_PCIE_PHY_CMN_REG062 0x188
+#define ANA_PLL_CLK_OUT_TO_EXT_IO_SEL BIT(3)
+#define IMX8MM_PCIE_PHY_CMN_REG063 0x18C
+#define AUX_PLL_REFCLK_SEL_SYS_PLL GENMASK(7, 6)
+#define IMX8MM_PCIE_PHY_CMN_REG064 0x190
+#define ANA_AUX_RX_TX_SEL_TX BIT(7)
+#define ANA_AUX_RX_TERM_GND_EN BIT(3)
+#define ANA_AUX_TX_TERM BIT(2)
+#define IMX8MM_PCIE_PHY_CMN_REG065 0x194
+#define ANA_AUX_RX_TERM (BIT(7) | BIT(4))
+#define ANA_AUX_TX_LVL GENMASK(3, 0)
+#define IMX8MM_PCIE_PHY_CMN_REG075 0x1D4
+#define ANA_PLL_DONE 0x3
+#define PCIE_PHY_TRSV_REG5 0x414
+#define PCIE_PHY_TRSV_REG6 0x418
+
+#define IMX8MM_GPR_PCIE_REF_CLK_SEL GENMASK(25, 24)
+#define IMX8MM_GPR_PCIE_REF_CLK_PLL FIELD_PREP(IMX8MM_GPR_PCIE_REF_CLK_SEL, 0x3)
+#define IMX8MM_GPR_PCIE_REF_CLK_EXT FIELD_PREP(IMX8MM_GPR_PCIE_REF_CLK_SEL, 0x2)
+#define IMX8MM_GPR_PCIE_AUX_EN BIT(19)
+#define IMX8MM_GPR_PCIE_CMN_RST BIT(18)
+#define IMX8MM_GPR_PCIE_POWER_OFF BIT(17)
+#define IMX8MM_GPR_PCIE_SSC_EN BIT(16)
+#define IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE BIT(9)
+
+enum imx8_pcie_phy_type {
+ IMX8MM,
+ IMX8MP,
+};
+
+struct imx8_pcie_phy_drvdata {
+ const char *gpr;
+ enum imx8_pcie_phy_type variant;
+};
+
+struct imx8_pcie_phy {
+ void __iomem *base;
+ struct clk *clk;
+ struct phy *phy;
+ struct regmap *iomuxc_gpr;
+ struct reset_control *perst;
+ struct reset_control *reset;
+ u32 refclk_pad_mode;
+ u32 tx_deemph_gen1;
+ u32 tx_deemph_gen2;
+ bool clkreq_unused;
+ const struct imx8_pcie_phy_drvdata *drvdata;
+};
+
+static int imx8_pcie_phy_power_on(struct phy *phy)
+{
+ int ret;
+ u32 val, pad_mode;
+ struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy);
+
+ pad_mode = imx8_phy->refclk_pad_mode;
+ switch (imx8_phy->drvdata->variant) {
+ case IMX8MM:
+ reset_control_assert(imx8_phy->reset);
+
+ /* Tune PHY de-emphasis setting to pass PCIe compliance. */
+ if (imx8_phy->tx_deemph_gen1)
+ writel(imx8_phy->tx_deemph_gen1,
+ imx8_phy->base + PCIE_PHY_TRSV_REG5);
+ if (imx8_phy->tx_deemph_gen2)
+ writel(imx8_phy->tx_deemph_gen2,
+ imx8_phy->base + PCIE_PHY_TRSV_REG6);
+ break;
+ case IMX8MP: /* Do nothing. */
+ break;
+ }
+
+ if (pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT ||
+ pad_mode == IMX8_PCIE_REFCLK_PAD_UNUSED) {
+ /* Configure the pad as input */
+ val = readl(imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061);
+ writel(val & ~ANA_PLL_CLK_OUT_TO_EXT_IO_EN,
+ imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061);
+ } else {
+ /* Configure the PHY to output the refclock via pad */
+ writel(ANA_PLL_CLK_OUT_TO_EXT_IO_EN,
+ imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG061);
+ }
+
+ if (pad_mode == IMX8_PCIE_REFCLK_PAD_OUTPUT ||
+ pad_mode == IMX8_PCIE_REFCLK_PAD_UNUSED) {
+ /* Source clock from SoC internal PLL */
+ writel(ANA_PLL_CLK_OUT_TO_EXT_IO_SEL,
+ imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG062);
+ writel(AUX_PLL_REFCLK_SEL_SYS_PLL,
+ imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG063);
+ val = ANA_AUX_RX_TX_SEL_TX | ANA_AUX_TX_TERM;
+ writel(val | ANA_AUX_RX_TERM_GND_EN,
+ imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG064);
+ writel(ANA_AUX_RX_TERM | ANA_AUX_TX_LVL,
+ imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG065);
+ }
+
+ /* Set AUX_EN_OVERRIDE 1'b0, when the CLKREQ# isn't hooked */
+ regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
+ IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE,
+ imx8_phy->clkreq_unused ?
+ 0 : IMX8MM_GPR_PCIE_AUX_EN_OVERRIDE);
+ regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
+ IMX8MM_GPR_PCIE_AUX_EN,
+ IMX8MM_GPR_PCIE_AUX_EN);
+ regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
+ IMX8MM_GPR_PCIE_POWER_OFF, 0);
+ regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
+ IMX8MM_GPR_PCIE_SSC_EN, 0);
+
+ regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
+ IMX8MM_GPR_PCIE_REF_CLK_SEL,
+ pad_mode == IMX8_PCIE_REFCLK_PAD_INPUT ?
+ IMX8MM_GPR_PCIE_REF_CLK_EXT :
+ IMX8MM_GPR_PCIE_REF_CLK_PLL);
+ usleep_range(100, 200);
+
+ /* Do the PHY common block reset */
+ regmap_update_bits(imx8_phy->iomuxc_gpr, IOMUXC_GPR14,
+ IMX8MM_GPR_PCIE_CMN_RST,
+ IMX8MM_GPR_PCIE_CMN_RST);
+
+ switch (imx8_phy->drvdata->variant) {
+ case IMX8MP:
+ reset_control_deassert(imx8_phy->perst);
+ fallthrough;
+ case IMX8MM:
+ reset_control_deassert(imx8_phy->reset);
+ usleep_range(200, 500);
+ break;
+ }
+
+ /* Polling to check the phy is ready or not. */
+ ret = readl_poll_timeout(imx8_phy->base + IMX8MM_PCIE_PHY_CMN_REG075,
+ val, val == ANA_PLL_DONE, 10, 20000);
+ return ret;
+}
+
+static int imx8_pcie_phy_init(struct phy *phy)
+{
+ struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy);
+
+ return clk_prepare_enable(imx8_phy->clk);
+}
+
+static int imx8_pcie_phy_exit(struct phy *phy)
+{
+ struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(imx8_phy->clk);
+
+ return 0;
+}
+
+static const struct phy_ops imx8_pcie_phy_ops = {
+ .init = imx8_pcie_phy_init,
+ .exit = imx8_pcie_phy_exit,
+ .power_on = imx8_pcie_phy_power_on,
+ .owner = THIS_MODULE,
+};
+
+static const struct imx8_pcie_phy_drvdata imx8mm_drvdata = {
+ .gpr = "fsl,imx8mm-iomuxc-gpr",
+ .variant = IMX8MM,
+};
+
+static const struct imx8_pcie_phy_drvdata imx8mp_drvdata = {
+ .gpr = "fsl,imx8mp-iomuxc-gpr",
+ .variant = IMX8MP,
+};
+
+static const struct of_device_id imx8_pcie_phy_of_match[] = {
+ {.compatible = "fsl,imx8mm-pcie-phy", .data = &imx8mm_drvdata, },
+ {.compatible = "fsl,imx8mp-pcie-phy", .data = &imx8mp_drvdata, },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx8_pcie_phy_of_match);
+
+static int imx8_pcie_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct imx8_pcie_phy *imx8_phy;
+
+ imx8_phy = devm_kzalloc(dev, sizeof(*imx8_phy), GFP_KERNEL);
+ if (!imx8_phy)
+ return -ENOMEM;
+
+ imx8_phy->drvdata = of_device_get_match_data(dev);
+
+ /* get PHY refclk pad mode */
+ of_property_read_u32(np, "fsl,refclk-pad-mode",
+ &imx8_phy->refclk_pad_mode);
+
+ if (of_property_read_u32(np, "fsl,tx-deemph-gen1",
+ &imx8_phy->tx_deemph_gen1))
+ imx8_phy->tx_deemph_gen1 = 0;
+
+ if (of_property_read_u32(np, "fsl,tx-deemph-gen2",
+ &imx8_phy->tx_deemph_gen2))
+ imx8_phy->tx_deemph_gen2 = 0;
+
+ if (of_property_read_bool(np, "fsl,clkreq-unsupported"))
+ imx8_phy->clkreq_unused = true;
+ else
+ imx8_phy->clkreq_unused = false;
+
+ imx8_phy->clk = devm_clk_get(dev, "ref");
+ if (IS_ERR(imx8_phy->clk)) {
+ dev_err(dev, "failed to get imx pcie phy clock\n");
+ return PTR_ERR(imx8_phy->clk);
+ }
+
+ /* Grab GPR config register range */
+ imx8_phy->iomuxc_gpr =
+ syscon_regmap_lookup_by_compatible(imx8_phy->drvdata->gpr);
+ if (IS_ERR(imx8_phy->iomuxc_gpr)) {
+ dev_err(dev, "unable to find iomuxc registers\n");
+ return PTR_ERR(imx8_phy->iomuxc_gpr);
+ }
+
+ imx8_phy->reset = devm_reset_control_get_exclusive(dev, "pciephy");
+ if (IS_ERR(imx8_phy->reset)) {
+ dev_err(dev, "Failed to get PCIEPHY reset control\n");
+ return PTR_ERR(imx8_phy->reset);
+ }
+
+ if (imx8_phy->drvdata->variant == IMX8MP) {
+ imx8_phy->perst =
+ devm_reset_control_get_exclusive(dev, "perst");
+ if (IS_ERR(imx8_phy->perst))
+ return dev_err_probe(dev, PTR_ERR(imx8_phy->perst),
+ "Failed to get PCIE PHY PERST control\n");
+ }
+
+ imx8_phy->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(imx8_phy->base))
+ return PTR_ERR(imx8_phy->base);
+
+ imx8_phy->phy = devm_phy_create(dev, NULL, &imx8_pcie_phy_ops);
+ if (IS_ERR(imx8_phy->phy))
+ return PTR_ERR(imx8_phy->phy);
+
+ phy_set_drvdata(imx8_phy->phy, imx8_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver imx8_pcie_phy_driver = {
+ .probe = imx8_pcie_phy_probe,
+ .driver = {
+ .name = "imx8-pcie-phy",
+ .of_match_table = imx8_pcie_phy_of_match,
+ }
+};
+module_platform_driver(imx8_pcie_phy_driver);
+
+MODULE_DESCRIPTION("FSL IMX8 PCIE PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/phy/freescale/phy-fsl-imx8mq-usb.c b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
new file mode 100644
index 0000000000..0b9a59d5b8
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2017 NXP. */
+
+#include <linux/bitfield.h>
+#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/regulator/consumer.h>
+
+#define PHY_CTRL0 0x0
+#define PHY_CTRL0_REF_SSP_EN BIT(2)
+#define PHY_CTRL0_FSEL_MASK GENMASK(10, 5)
+#define PHY_CTRL0_FSEL_24M 0x2a
+
+#define PHY_CTRL1 0x4
+#define PHY_CTRL1_RESET BIT(0)
+#define PHY_CTRL1_COMMONONN BIT(1)
+#define PHY_CTRL1_ATERESET BIT(3)
+#define PHY_CTRL1_VDATSRCENB0 BIT(19)
+#define PHY_CTRL1_VDATDETENB0 BIT(20)
+
+#define PHY_CTRL2 0x8
+#define PHY_CTRL2_TXENABLEN0 BIT(8)
+#define PHY_CTRL2_OTG_DISABLE BIT(9)
+
+#define PHY_CTRL3 0xc
+#define PHY_CTRL3_COMPDISTUNE_MASK GENMASK(2, 0)
+#define PHY_CTRL3_TXPREEMP_TUNE_MASK GENMASK(16, 15)
+#define PHY_CTRL3_TXRISE_TUNE_MASK GENMASK(21, 20)
+#define PHY_CTRL3_TXVREF_TUNE_MASK GENMASK(25, 22)
+#define PHY_CTRL3_TX_VBOOST_LEVEL_MASK GENMASK(31, 29)
+
+#define PHY_CTRL4 0x10
+#define PHY_CTRL4_PCS_TX_DEEMPH_3P5DB_MASK GENMASK(20, 15)
+
+#define PHY_CTRL5 0x14
+#define PHY_CTRL5_DMPWD_OVERRIDE_SEL BIT(23)
+#define PHY_CTRL5_DMPWD_OVERRIDE BIT(22)
+#define PHY_CTRL5_DPPWD_OVERRIDE_SEL BIT(21)
+#define PHY_CTRL5_DPPWD_OVERRIDE BIT(20)
+#define PHY_CTRL5_PCS_TX_SWING_FULL_MASK GENMASK(6, 0)
+
+#define PHY_CTRL6 0x18
+#define PHY_CTRL6_ALT_CLK_EN BIT(1)
+#define PHY_CTRL6_ALT_CLK_SEL BIT(0)
+
+#define PHY_TUNE_DEFAULT 0xffffffff
+
+struct imx8mq_usb_phy {
+ struct phy *phy;
+ struct clk *clk;
+ void __iomem *base;
+ struct regulator *vbus;
+ u32 pcs_tx_swing_full;
+ u32 pcs_tx_deemph_3p5db;
+ u32 tx_vref_tune;
+ u32 tx_rise_tune;
+ u32 tx_preemp_amp_tune;
+ u32 tx_vboost_level;
+ u32 comp_dis_tune;
+};
+
+static u32 phy_tx_vref_tune_from_property(u32 percent)
+{
+ percent = clamp(percent, 94U, 124U);
+
+ return DIV_ROUND_CLOSEST(percent - 94U, 2);
+}
+
+static u32 phy_tx_rise_tune_from_property(u32 percent)
+{
+ switch (percent) {
+ case 0 ... 98:
+ return 3;
+ case 99:
+ return 2;
+ case 100 ... 101:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static u32 phy_tx_preemp_amp_tune_from_property(u32 microamp)
+{
+ microamp = min(microamp, 1800U);
+
+ return microamp / 600;
+}
+
+static u32 phy_tx_vboost_level_from_property(u32 microvolt)
+{
+ switch (microvolt) {
+ case 0 ... 960:
+ return 0;
+ case 961 ... 1160:
+ return 2;
+ default:
+ return 3;
+ }
+}
+
+static u32 phy_pcs_tx_deemph_3p5db_from_property(u32 decibel)
+{
+ return min(decibel, 36U);
+}
+
+static u32 phy_comp_dis_tune_from_property(u32 percent)
+{
+ switch (percent) {
+ case 0 ... 92:
+ return 0;
+ case 93 ... 95:
+ return 1;
+ case 96 ... 97:
+ return 2;
+ case 98 ... 102:
+ return 3;
+ case 103 ... 105:
+ return 4;
+ case 106 ... 109:
+ return 5;
+ case 110 ... 113:
+ return 6;
+ default:
+ return 7;
+ }
+}
+static u32 phy_pcs_tx_swing_full_from_property(u32 percent)
+{
+ percent = min(percent, 100U);
+
+ return (percent * 127) / 100;
+}
+
+static void imx8m_get_phy_tuning_data(struct imx8mq_usb_phy *imx_phy)
+{
+ struct device *dev = imx_phy->phy->dev.parent;
+
+ if (device_property_read_u32(dev, "fsl,phy-tx-vref-tune-percent",
+ &imx_phy->tx_vref_tune))
+ imx_phy->tx_vref_tune = PHY_TUNE_DEFAULT;
+ else
+ imx_phy->tx_vref_tune =
+ phy_tx_vref_tune_from_property(imx_phy->tx_vref_tune);
+
+ if (device_property_read_u32(dev, "fsl,phy-tx-rise-tune-percent",
+ &imx_phy->tx_rise_tune))
+ imx_phy->tx_rise_tune = PHY_TUNE_DEFAULT;
+ else
+ imx_phy->tx_rise_tune =
+ phy_tx_rise_tune_from_property(imx_phy->tx_rise_tune);
+
+ if (device_property_read_u32(dev, "fsl,phy-tx-preemp-amp-tune-microamp",
+ &imx_phy->tx_preemp_amp_tune))
+ imx_phy->tx_preemp_amp_tune = PHY_TUNE_DEFAULT;
+ else
+ imx_phy->tx_preemp_amp_tune =
+ phy_tx_preemp_amp_tune_from_property(imx_phy->tx_preemp_amp_tune);
+
+ if (device_property_read_u32(dev, "fsl,phy-tx-vboost-level-microvolt",
+ &imx_phy->tx_vboost_level))
+ imx_phy->tx_vboost_level = PHY_TUNE_DEFAULT;
+ else
+ imx_phy->tx_vboost_level =
+ phy_tx_vboost_level_from_property(imx_phy->tx_vboost_level);
+
+ if (device_property_read_u32(dev, "fsl,phy-comp-dis-tune-percent",
+ &imx_phy->comp_dis_tune))
+ imx_phy->comp_dis_tune = PHY_TUNE_DEFAULT;
+ else
+ imx_phy->comp_dis_tune =
+ phy_comp_dis_tune_from_property(imx_phy->comp_dis_tune);
+
+ if (device_property_read_u32(dev, "fsl,pcs-tx-deemph-3p5db-attenuation-db",
+ &imx_phy->pcs_tx_deemph_3p5db))
+ imx_phy->pcs_tx_deemph_3p5db = PHY_TUNE_DEFAULT;
+ else
+ imx_phy->pcs_tx_deemph_3p5db =
+ phy_pcs_tx_deemph_3p5db_from_property(imx_phy->pcs_tx_deemph_3p5db);
+
+ if (device_property_read_u32(dev, "fsl,phy-pcs-tx-swing-full-percent",
+ &imx_phy->pcs_tx_swing_full))
+ imx_phy->pcs_tx_swing_full = PHY_TUNE_DEFAULT;
+ else
+ imx_phy->pcs_tx_swing_full =
+ phy_pcs_tx_swing_full_from_property(imx_phy->pcs_tx_swing_full);
+}
+
+static void imx8m_phy_tune(struct imx8mq_usb_phy *imx_phy)
+{
+ u32 value;
+
+ /* PHY tuning */
+ if (imx_phy->pcs_tx_deemph_3p5db != PHY_TUNE_DEFAULT) {
+ value = readl(imx_phy->base + PHY_CTRL4);
+ value &= ~PHY_CTRL4_PCS_TX_DEEMPH_3P5DB_MASK;
+ value |= FIELD_PREP(PHY_CTRL4_PCS_TX_DEEMPH_3P5DB_MASK,
+ imx_phy->pcs_tx_deemph_3p5db);
+ writel(value, imx_phy->base + PHY_CTRL4);
+ }
+
+ if (imx_phy->pcs_tx_swing_full != PHY_TUNE_DEFAULT) {
+ value = readl(imx_phy->base + PHY_CTRL5);
+ value |= FIELD_PREP(PHY_CTRL5_PCS_TX_SWING_FULL_MASK,
+ imx_phy->pcs_tx_swing_full);
+ writel(value, imx_phy->base + PHY_CTRL5);
+ }
+
+ if ((imx_phy->tx_vref_tune & imx_phy->tx_rise_tune &
+ imx_phy->tx_preemp_amp_tune & imx_phy->comp_dis_tune &
+ imx_phy->tx_vboost_level) == PHY_TUNE_DEFAULT)
+ /* If all are the default values, no need update. */
+ return;
+
+ value = readl(imx_phy->base + PHY_CTRL3);
+
+ if (imx_phy->tx_vref_tune != PHY_TUNE_DEFAULT) {
+ value &= ~PHY_CTRL3_TXVREF_TUNE_MASK;
+ value |= FIELD_PREP(PHY_CTRL3_TXVREF_TUNE_MASK,
+ imx_phy->tx_vref_tune);
+ }
+
+ if (imx_phy->tx_rise_tune != PHY_TUNE_DEFAULT) {
+ value &= ~PHY_CTRL3_TXRISE_TUNE_MASK;
+ value |= FIELD_PREP(PHY_CTRL3_TXRISE_TUNE_MASK,
+ imx_phy->tx_rise_tune);
+ }
+
+ if (imx_phy->tx_preemp_amp_tune != PHY_TUNE_DEFAULT) {
+ value &= ~PHY_CTRL3_TXPREEMP_TUNE_MASK;
+ value |= FIELD_PREP(PHY_CTRL3_TXPREEMP_TUNE_MASK,
+ imx_phy->tx_preemp_amp_tune);
+ }
+
+ if (imx_phy->comp_dis_tune != PHY_TUNE_DEFAULT) {
+ value &= ~PHY_CTRL3_COMPDISTUNE_MASK;
+ value |= FIELD_PREP(PHY_CTRL3_COMPDISTUNE_MASK,
+ imx_phy->comp_dis_tune);
+ }
+
+ if (imx_phy->tx_vboost_level != PHY_TUNE_DEFAULT) {
+ value &= ~PHY_CTRL3_TX_VBOOST_LEVEL_MASK;
+ value |= FIELD_PREP(PHY_CTRL3_TX_VBOOST_LEVEL_MASK,
+ imx_phy->tx_vboost_level);
+ }
+
+ writel(value, imx_phy->base + PHY_CTRL3);
+}
+
+static int imx8mq_usb_phy_init(struct phy *phy)
+{
+ struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
+ u32 value;
+
+ value = readl(imx_phy->base + PHY_CTRL1);
+ value &= ~(PHY_CTRL1_VDATSRCENB0 | PHY_CTRL1_VDATDETENB0 |
+ PHY_CTRL1_COMMONONN);
+ value |= PHY_CTRL1_RESET | PHY_CTRL1_ATERESET;
+ writel(value, imx_phy->base + PHY_CTRL1);
+
+ value = readl(imx_phy->base + PHY_CTRL0);
+ value |= PHY_CTRL0_REF_SSP_EN;
+ writel(value, imx_phy->base + PHY_CTRL0);
+
+ value = readl(imx_phy->base + PHY_CTRL2);
+ value |= PHY_CTRL2_TXENABLEN0;
+ writel(value, imx_phy->base + PHY_CTRL2);
+
+ value = readl(imx_phy->base + PHY_CTRL1);
+ value &= ~(PHY_CTRL1_RESET | PHY_CTRL1_ATERESET);
+ writel(value, imx_phy->base + PHY_CTRL1);
+
+ return 0;
+}
+
+static int imx8mp_usb_phy_init(struct phy *phy)
+{
+ struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
+ u32 value;
+
+ /* USB3.0 PHY signal fsel for 24M ref */
+ value = readl(imx_phy->base + PHY_CTRL0);
+ value &= ~PHY_CTRL0_FSEL_MASK;
+ value |= FIELD_PREP(PHY_CTRL0_FSEL_MASK, PHY_CTRL0_FSEL_24M);
+ writel(value, imx_phy->base + PHY_CTRL0);
+
+ /* Disable alt_clk_en and use internal MPLL clocks */
+ value = readl(imx_phy->base + PHY_CTRL6);
+ value &= ~(PHY_CTRL6_ALT_CLK_SEL | PHY_CTRL6_ALT_CLK_EN);
+ writel(value, imx_phy->base + PHY_CTRL6);
+
+ value = readl(imx_phy->base + PHY_CTRL1);
+ value &= ~(PHY_CTRL1_VDATSRCENB0 | PHY_CTRL1_VDATDETENB0);
+ value |= PHY_CTRL1_RESET | PHY_CTRL1_ATERESET;
+ writel(value, imx_phy->base + PHY_CTRL1);
+
+ value = readl(imx_phy->base + PHY_CTRL0);
+ value |= PHY_CTRL0_REF_SSP_EN;
+ writel(value, imx_phy->base + PHY_CTRL0);
+
+ value = readl(imx_phy->base + PHY_CTRL2);
+ value |= PHY_CTRL2_TXENABLEN0 | PHY_CTRL2_OTG_DISABLE;
+ writel(value, imx_phy->base + PHY_CTRL2);
+
+ udelay(10);
+
+ value = readl(imx_phy->base + PHY_CTRL1);
+ value &= ~(PHY_CTRL1_RESET | PHY_CTRL1_ATERESET);
+ writel(value, imx_phy->base + PHY_CTRL1);
+
+ imx8m_phy_tune(imx_phy);
+
+ return 0;
+}
+
+static int imx8mq_phy_power_on(struct phy *phy)
+{
+ struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
+ int ret;
+
+ ret = regulator_enable(imx_phy->vbus);
+ if (ret)
+ return ret;
+
+ return clk_prepare_enable(imx_phy->clk);
+}
+
+static int imx8mq_phy_power_off(struct phy *phy)
+{
+ struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy);
+
+ clk_disable_unprepare(imx_phy->clk);
+ regulator_disable(imx_phy->vbus);
+
+ return 0;
+}
+
+static const struct phy_ops imx8mq_usb_phy_ops = {
+ .init = imx8mq_usb_phy_init,
+ .power_on = imx8mq_phy_power_on,
+ .power_off = imx8mq_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct phy_ops imx8mp_usb_phy_ops = {
+ .init = imx8mp_usb_phy_init,
+ .power_on = imx8mq_phy_power_on,
+ .power_off = imx8mq_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id imx8mq_usb_phy_of_match[] = {
+ {.compatible = "fsl,imx8mq-usb-phy",
+ .data = &imx8mq_usb_phy_ops,},
+ {.compatible = "fsl,imx8mp-usb-phy",
+ .data = &imx8mp_usb_phy_ops,},
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match);
+
+static int imx8mq_usb_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct imx8mq_usb_phy *imx_phy;
+ const struct phy_ops *phy_ops;
+
+ imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL);
+ if (!imx_phy)
+ return -ENOMEM;
+
+ imx_phy->clk = devm_clk_get(dev, "phy");
+ if (IS_ERR(imx_phy->clk)) {
+ dev_err(dev, "failed to get imx8mq usb phy clock\n");
+ return PTR_ERR(imx_phy->clk);
+ }
+
+ imx_phy->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(imx_phy->base))
+ return PTR_ERR(imx_phy->base);
+
+ phy_ops = of_device_get_match_data(dev);
+ if (!phy_ops)
+ return -EINVAL;
+
+ imx_phy->phy = devm_phy_create(dev, NULL, phy_ops);
+ if (IS_ERR(imx_phy->phy))
+ return PTR_ERR(imx_phy->phy);
+
+ imx_phy->vbus = devm_regulator_get(dev, "vbus");
+ if (IS_ERR(imx_phy->vbus))
+ return dev_err_probe(dev, PTR_ERR(imx_phy->vbus), "failed to get vbus\n");
+
+ phy_set_drvdata(imx_phy->phy, imx_phy);
+
+ imx8m_get_phy_tuning_data(imx_phy);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver imx8mq_usb_phy_driver = {
+ .probe = imx8mq_usb_phy_probe,
+ .driver = {
+ .name = "imx8mq-usb-phy",
+ .of_match_table = imx8mq_usb_phy_of_match,
+ }
+};
+module_platform_driver(imx8mq_usb_phy_driver);
+
+MODULE_DESCRIPTION("FSL IMX8MQ USB PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/freescale/phy-fsl-imx8qm-lvds-phy.c b/drivers/phy/freescale/phy-fsl-imx8qm-lvds-phy.c
new file mode 100644
index 0000000000..0ae052df37
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-imx8qm-lvds-phy.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2017-2020,2022 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.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/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#define REG_SET 0x4
+#define REG_CLR 0x8
+
+#define PHY_CTRL 0x0
+#define M_MASK GENMASK(18, 17)
+#define M(n) FIELD_PREP(M_MASK, (n))
+#define CCM_MASK GENMASK(16, 14)
+#define CCM(n) FIELD_PREP(CCM_MASK, (n))
+#define CA_MASK GENMASK(13, 11)
+#define CA(n) FIELD_PREP(CA_MASK, (n))
+#define TST_MASK GENMASK(10, 5)
+#define TST(n) FIELD_PREP(TST_MASK, (n))
+#define CH_EN(id) BIT(3 + (id))
+#define NB BIT(2)
+#define RFB BIT(1)
+#define PD BIT(0)
+
+/* Power On Reset(POR) value */
+#define CTRL_RESET_VAL (M(0x0) | CCM(0x4) | CA(0x4) | TST(0x25))
+
+/* PHY initialization value and mask */
+#define CTRL_INIT_MASK (M_MASK | CCM_MASK | CA_MASK | TST_MASK | NB | RFB)
+#define CTRL_INIT_VAL (M(0x0) | CCM(0x5) | CA(0x4) | TST(0x25) | RFB)
+
+#define PHY_STATUS 0x10
+#define LOCK BIT(0)
+
+#define PHY_NUM 2
+
+#define MIN_CLKIN_FREQ (25 * MEGA)
+#define MAX_CLKIN_FREQ (165 * MEGA)
+
+#define PLL_LOCK_SLEEP 10
+#define PLL_LOCK_TIMEOUT 1000
+
+struct mixel_lvds_phy {
+ struct phy *phy;
+ struct phy_configure_opts_lvds cfg;
+ unsigned int id;
+};
+
+struct mixel_lvds_phy_priv {
+ struct regmap *regmap;
+ struct mutex lock; /* protect remap access and cfg of our own */
+ struct clk *phy_ref_clk;
+ struct mixel_lvds_phy *phys[PHY_NUM];
+};
+
+static int mixel_lvds_phy_init(struct phy *phy)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+
+ mutex_lock(&priv->lock);
+ regmap_update_bits(priv->regmap,
+ PHY_CTRL, CTRL_INIT_MASK, CTRL_INIT_VAL);
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static int mixel_lvds_phy_power_on(struct phy *phy)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+ struct mixel_lvds_phy *companion = priv->phys[lvds_phy->id ^ 1];
+ struct phy_configure_opts_lvds *cfg = &lvds_phy->cfg;
+ u32 val = 0;
+ u32 locked;
+ int ret;
+
+ /* The master PHY would power on the slave PHY. */
+ if (cfg->is_slave)
+ return 0;
+
+ ret = clk_prepare_enable(priv->phy_ref_clk);
+ if (ret < 0) {
+ dev_err(&phy->dev,
+ "failed to enable PHY reference clock: %d\n", ret);
+ return ret;
+ }
+
+ mutex_lock(&priv->lock);
+ if (cfg->bits_per_lane_and_dclk_cycle == 7) {
+ if (cfg->differential_clk_rate < 44000000)
+ val |= M(0x2);
+ else if (cfg->differential_clk_rate < 90000000)
+ val |= M(0x1);
+ else
+ val |= M(0x0);
+ } else {
+ val = NB;
+
+ if (cfg->differential_clk_rate < 32000000)
+ val |= M(0x2);
+ else if (cfg->differential_clk_rate < 63000000)
+ val |= M(0x1);
+ else
+ val |= M(0x0);
+ }
+ regmap_update_bits(priv->regmap, PHY_CTRL, M_MASK | NB, val);
+
+ /*
+ * Enable two channels synchronously,
+ * if the companion PHY is a slave PHY.
+ */
+ if (companion->cfg.is_slave)
+ val = CH_EN(0) | CH_EN(1);
+ else
+ val = CH_EN(lvds_phy->id);
+ regmap_write(priv->regmap, PHY_CTRL + REG_SET, val);
+
+ ret = regmap_read_poll_timeout(priv->regmap, PHY_STATUS, locked,
+ locked, PLL_LOCK_SLEEP,
+ PLL_LOCK_TIMEOUT);
+ if (ret < 0) {
+ dev_err(&phy->dev, "failed to get PHY lock: %d\n", ret);
+ clk_disable_unprepare(priv->phy_ref_clk);
+ }
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int mixel_lvds_phy_power_off(struct phy *phy)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+ struct mixel_lvds_phy *companion = priv->phys[lvds_phy->id ^ 1];
+ struct phy_configure_opts_lvds *cfg = &lvds_phy->cfg;
+
+ /* The master PHY would power off the slave PHY. */
+ if (cfg->is_slave)
+ return 0;
+
+ mutex_lock(&priv->lock);
+ if (companion->cfg.is_slave)
+ regmap_write(priv->regmap, PHY_CTRL + REG_CLR,
+ CH_EN(0) | CH_EN(1));
+ else
+ regmap_write(priv->regmap, PHY_CTRL + REG_CLR,
+ CH_EN(lvds_phy->id));
+ mutex_unlock(&priv->lock);
+
+ clk_disable_unprepare(priv->phy_ref_clk);
+
+ return 0;
+}
+
+static int mixel_lvds_phy_configure(struct phy *phy,
+ union phy_configure_opts *opts)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+ struct phy_configure_opts_lvds *cfg = &opts->lvds;
+ int ret;
+
+ ret = clk_set_rate(priv->phy_ref_clk, cfg->differential_clk_rate);
+ if (ret)
+ dev_err(&phy->dev, "failed to set PHY reference clock rate(%lu): %d\n",
+ cfg->differential_clk_rate, ret);
+
+ return ret;
+}
+
+/* Assume the master PHY's configuration set is cached first. */
+static int mixel_lvds_phy_check_slave(struct phy *slave_phy)
+{
+ struct device *dev = &slave_phy->dev;
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev->parent);
+ struct mixel_lvds_phy *slv = phy_get_drvdata(slave_phy);
+ struct mixel_lvds_phy *mst = priv->phys[slv->id ^ 1];
+ struct phy_configure_opts_lvds *mst_cfg = &mst->cfg;
+ struct phy_configure_opts_lvds *slv_cfg = &slv->cfg;
+
+ if (mst_cfg->bits_per_lane_and_dclk_cycle !=
+ slv_cfg->bits_per_lane_and_dclk_cycle) {
+ dev_err(dev, "number bits mismatch(mst: %u vs slv: %u)\n",
+ mst_cfg->bits_per_lane_and_dclk_cycle,
+ slv_cfg->bits_per_lane_and_dclk_cycle);
+ return -EINVAL;
+ }
+
+ if (mst_cfg->differential_clk_rate !=
+ slv_cfg->differential_clk_rate) {
+ dev_err(dev, "dclk rate mismatch(mst: %lu vs slv: %lu)\n",
+ mst_cfg->differential_clk_rate,
+ slv_cfg->differential_clk_rate);
+ return -EINVAL;
+ }
+
+ if (mst_cfg->lanes != slv_cfg->lanes) {
+ dev_err(dev, "lanes mismatch(mst: %u vs slv: %u)\n",
+ mst_cfg->lanes, slv_cfg->lanes);
+ return -EINVAL;
+ }
+
+ if (mst_cfg->is_slave == slv_cfg->is_slave) {
+ dev_err(dev, "master PHY is not found\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mixel_lvds_phy_validate(struct phy *phy, enum phy_mode mode,
+ int submode, union phy_configure_opts *opts)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
+ struct mixel_lvds_phy *lvds_phy = phy_get_drvdata(phy);
+ struct phy_configure_opts_lvds *cfg = &opts->lvds;
+ int ret = 0;
+
+ if (mode != PHY_MODE_LVDS) {
+ dev_err(&phy->dev, "invalid PHY mode(%d)\n", mode);
+ return -EINVAL;
+ }
+
+ if (cfg->bits_per_lane_and_dclk_cycle != 7 &&
+ cfg->bits_per_lane_and_dclk_cycle != 10) {
+ dev_err(&phy->dev, "invalid bits per data lane(%u)\n",
+ cfg->bits_per_lane_and_dclk_cycle);
+ return -EINVAL;
+ }
+
+ if (cfg->lanes != 4 && cfg->lanes != 3) {
+ dev_err(&phy->dev, "invalid data lanes(%u)\n", cfg->lanes);
+ return -EINVAL;
+ }
+
+ if (cfg->differential_clk_rate < MIN_CLKIN_FREQ ||
+ cfg->differential_clk_rate > MAX_CLKIN_FREQ) {
+ dev_err(&phy->dev, "invalid differential clock rate(%lu)\n",
+ cfg->differential_clk_rate);
+ return -EINVAL;
+ }
+
+ mutex_lock(&priv->lock);
+ /* cache configuration set of our own for check */
+ memcpy(&lvds_phy->cfg, cfg, sizeof(*cfg));
+
+ if (cfg->is_slave) {
+ ret = mixel_lvds_phy_check_slave(phy);
+ if (ret)
+ dev_err(&phy->dev, "failed to check slave PHY: %d\n", ret);
+ }
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static const struct phy_ops mixel_lvds_phy_ops = {
+ .init = mixel_lvds_phy_init,
+ .power_on = mixel_lvds_phy_power_on,
+ .power_off = mixel_lvds_phy_power_off,
+ .configure = mixel_lvds_phy_configure,
+ .validate = mixel_lvds_phy_validate,
+ .owner = THIS_MODULE,
+};
+
+static int mixel_lvds_phy_reset(struct device *dev)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0) {
+ dev_err(dev, "failed to get PM runtime: %d\n", ret);
+ return ret;
+ }
+
+ regmap_write(priv->regmap, PHY_CTRL, CTRL_RESET_VAL);
+
+ ret = pm_runtime_put(dev);
+ if (ret < 0)
+ dev_err(dev, "failed to put PM runtime: %d\n", ret);
+
+ return ret;
+}
+
+static struct phy *mixel_lvds_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev);
+ unsigned int phy_id;
+
+ if (args->args_count != 1) {
+ dev_err(dev,
+ "invalid argument number(%d) for 'phys' property\n",
+ args->args_count);
+ return ERR_PTR(-EINVAL);
+ }
+
+ phy_id = args->args[0];
+
+ if (phy_id >= PHY_NUM) {
+ dev_err(dev, "invalid PHY index(%d)\n", phy_id);
+ return ERR_PTR(-ENODEV);
+ }
+
+ return priv->phys[phy_id]->phy;
+}
+
+static int mixel_lvds_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *phy_provider;
+ struct mixel_lvds_phy_priv *priv;
+ struct mixel_lvds_phy *lvds_phy;
+ struct phy *phy;
+ int i;
+ int ret;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = syscon_node_to_regmap(dev->of_node->parent);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "failed to get regmap\n");
+
+ priv->phy_ref_clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->phy_ref_clk))
+ return dev_err_probe(dev, PTR_ERR(priv->phy_ref_clk),
+ "failed to get PHY reference clock\n");
+
+ mutex_init(&priv->lock);
+
+ dev_set_drvdata(dev, priv);
+
+ pm_runtime_enable(dev);
+
+ ret = mixel_lvds_phy_reset(dev);
+ if (ret) {
+ dev_err(dev, "failed to do POR reset: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < PHY_NUM; i++) {
+ lvds_phy = devm_kzalloc(dev, sizeof(*lvds_phy), GFP_KERNEL);
+ if (!lvds_phy) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ phy = devm_phy_create(dev, NULL, &mixel_lvds_phy_ops);
+ if (IS_ERR(phy)) {
+ ret = PTR_ERR(phy);
+ dev_err(dev, "failed to create PHY for channel%d: %d\n",
+ i, ret);
+ goto err;
+ }
+
+ lvds_phy->phy = phy;
+ lvds_phy->id = i;
+ priv->phys[i] = lvds_phy;
+
+ phy_set_drvdata(phy, lvds_phy);
+ }
+
+ phy_provider = devm_of_phy_provider_register(dev, mixel_lvds_phy_xlate);
+ if (IS_ERR(phy_provider)) {
+ ret = PTR_ERR(phy_provider);
+ dev_err(dev, "failed to register PHY provider: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+err:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static void mixel_lvds_phy_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+}
+
+static int __maybe_unused mixel_lvds_phy_runtime_suspend(struct device *dev)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev);
+
+ /* power down */
+ mutex_lock(&priv->lock);
+ regmap_write(priv->regmap, PHY_CTRL + REG_SET, PD);
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static int __maybe_unused mixel_lvds_phy_runtime_resume(struct device *dev)
+{
+ struct mixel_lvds_phy_priv *priv = dev_get_drvdata(dev);
+
+ /* power up + control initialization */
+ mutex_lock(&priv->lock);
+ regmap_update_bits(priv->regmap, PHY_CTRL,
+ CTRL_INIT_MASK | PD, CTRL_INIT_VAL);
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static const struct dev_pm_ops mixel_lvds_phy_pm_ops = {
+ SET_RUNTIME_PM_OPS(mixel_lvds_phy_runtime_suspend,
+ mixel_lvds_phy_runtime_resume, NULL)
+};
+
+static const struct of_device_id mixel_lvds_phy_of_match[] = {
+ { .compatible = "fsl,imx8qm-lvds-phy" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mixel_lvds_phy_of_match);
+
+static struct platform_driver mixel_lvds_phy_driver = {
+ .probe = mixel_lvds_phy_probe,
+ .remove_new = mixel_lvds_phy_remove,
+ .driver = {
+ .pm = &mixel_lvds_phy_pm_ops,
+ .name = "mixel-lvds-phy",
+ .of_match_table = mixel_lvds_phy_of_match,
+ }
+};
+module_platform_driver(mixel_lvds_phy_driver);
+
+MODULE_DESCRIPTION("Mixel LVDS PHY driver");
+MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-28g.c b/drivers/phy/freescale/phy-fsl-lynx-28g.c
new file mode 100644
index 0000000000..e2187767ce
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c
@@ -0,0 +1,645 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2021-2022 NXP. */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+
+#define LYNX_28G_NUM_LANE 8
+#define LYNX_28G_NUM_PLL 2
+
+/* General registers per SerDes block */
+#define LYNX_28G_PCC8 0x10a0
+#define LYNX_28G_PCC8_SGMII 0x1
+#define LYNX_28G_PCC8_SGMII_DIS 0x0
+
+#define LYNX_28G_PCCC 0x10b0
+#define LYNX_28G_PCCC_10GBASER 0x9
+#define LYNX_28G_PCCC_USXGMII 0x1
+#define LYNX_28G_PCCC_SXGMII_DIS 0x0
+
+#define LYNX_28G_LNa_PCC_OFFSET(lane) (4 * (LYNX_28G_NUM_LANE - (lane->id) - 1))
+
+/* Per PLL registers */
+#define LYNX_28G_PLLnRSTCTL(pll) (0x400 + (pll) * 0x100 + 0x0)
+#define LYNX_28G_PLLnRSTCTL_DIS(rstctl) (((rstctl) & BIT(24)) >> 24)
+#define LYNX_28G_PLLnRSTCTL_LOCK(rstctl) (((rstctl) & BIT(23)) >> 23)
+
+#define LYNX_28G_PLLnCR0(pll) (0x400 + (pll) * 0x100 + 0x4)
+#define LYNX_28G_PLLnCR0_REFCLK_SEL(cr0) (((cr0) & GENMASK(20, 16)))
+#define LYNX_28G_PLLnCR0_REFCLK_SEL_100MHZ 0x0
+#define LYNX_28G_PLLnCR0_REFCLK_SEL_125MHZ 0x10000
+#define LYNX_28G_PLLnCR0_REFCLK_SEL_156MHZ 0x20000
+#define LYNX_28G_PLLnCR0_REFCLK_SEL_150MHZ 0x30000
+#define LYNX_28G_PLLnCR0_REFCLK_SEL_161MHZ 0x40000
+
+#define LYNX_28G_PLLnCR1(pll) (0x400 + (pll) * 0x100 + 0x8)
+#define LYNX_28G_PLLnCR1_FRATE_SEL(cr1) (((cr1) & GENMASK(28, 24)))
+#define LYNX_28G_PLLnCR1_FRATE_5G_10GVCO 0x0
+#define LYNX_28G_PLLnCR1_FRATE_5G_25GVCO 0x10000000
+#define LYNX_28G_PLLnCR1_FRATE_10G_20GVCO 0x6000000
+
+/* Per SerDes lane registers */
+/* Lane a General Control Register */
+#define LYNX_28G_LNaGCR0(lane) (0x800 + (lane) * 0x100 + 0x0)
+#define LYNX_28G_LNaGCR0_PROTO_SEL_MSK GENMASK(7, 3)
+#define LYNX_28G_LNaGCR0_PROTO_SEL_SGMII 0x8
+#define LYNX_28G_LNaGCR0_PROTO_SEL_XFI 0x50
+#define LYNX_28G_LNaGCR0_IF_WIDTH_MSK GENMASK(2, 0)
+#define LYNX_28G_LNaGCR0_IF_WIDTH_10_BIT 0x0
+#define LYNX_28G_LNaGCR0_IF_WIDTH_20_BIT 0x2
+
+/* Lane a Tx Reset Control Register */
+#define LYNX_28G_LNaTRSTCTL(lane) (0x800 + (lane) * 0x100 + 0x20)
+#define LYNX_28G_LNaTRSTCTL_HLT_REQ BIT(27)
+#define LYNX_28G_LNaTRSTCTL_RST_DONE BIT(30)
+#define LYNX_28G_LNaTRSTCTL_RST_REQ BIT(31)
+
+/* Lane a Tx General Control Register */
+#define LYNX_28G_LNaTGCR0(lane) (0x800 + (lane) * 0x100 + 0x24)
+#define LYNX_28G_LNaTGCR0_USE_PLLF 0x0
+#define LYNX_28G_LNaTGCR0_USE_PLLS BIT(28)
+#define LYNX_28G_LNaTGCR0_USE_PLL_MSK BIT(28)
+#define LYNX_28G_LNaTGCR0_N_RATE_FULL 0x0
+#define LYNX_28G_LNaTGCR0_N_RATE_HALF 0x1000000
+#define LYNX_28G_LNaTGCR0_N_RATE_QUARTER 0x2000000
+#define LYNX_28G_LNaTGCR0_N_RATE_MSK GENMASK(26, 24)
+
+#define LYNX_28G_LNaTECR0(lane) (0x800 + (lane) * 0x100 + 0x30)
+
+/* Lane a Rx Reset Control Register */
+#define LYNX_28G_LNaRRSTCTL(lane) (0x800 + (lane) * 0x100 + 0x40)
+#define LYNX_28G_LNaRRSTCTL_HLT_REQ BIT(27)
+#define LYNX_28G_LNaRRSTCTL_RST_DONE BIT(30)
+#define LYNX_28G_LNaRRSTCTL_RST_REQ BIT(31)
+#define LYNX_28G_LNaRRSTCTL_CDR_LOCK BIT(12)
+
+/* Lane a Rx General Control Register */
+#define LYNX_28G_LNaRGCR0(lane) (0x800 + (lane) * 0x100 + 0x44)
+#define LYNX_28G_LNaRGCR0_USE_PLLF 0x0
+#define LYNX_28G_LNaRGCR0_USE_PLLS BIT(28)
+#define LYNX_28G_LNaRGCR0_USE_PLL_MSK BIT(28)
+#define LYNX_28G_LNaRGCR0_N_RATE_MSK GENMASK(26, 24)
+#define LYNX_28G_LNaRGCR0_N_RATE_FULL 0x0
+#define LYNX_28G_LNaRGCR0_N_RATE_HALF 0x1000000
+#define LYNX_28G_LNaRGCR0_N_RATE_QUARTER 0x2000000
+#define LYNX_28G_LNaRGCR0_N_RATE_MSK GENMASK(26, 24)
+
+#define LYNX_28G_LNaRGCR1(lane) (0x800 + (lane) * 0x100 + 0x48)
+
+#define LYNX_28G_LNaRECR0(lane) (0x800 + (lane) * 0x100 + 0x50)
+#define LYNX_28G_LNaRECR1(lane) (0x800 + (lane) * 0x100 + 0x54)
+#define LYNX_28G_LNaRECR2(lane) (0x800 + (lane) * 0x100 + 0x58)
+
+#define LYNX_28G_LNaRSCCR0(lane) (0x800 + (lane) * 0x100 + 0x74)
+
+#define LYNX_28G_LNaPSS(lane) (0x1000 + (lane) * 0x4)
+#define LYNX_28G_LNaPSS_TYPE(pss) (((pss) & GENMASK(30, 24)) >> 24)
+#define LYNX_28G_LNaPSS_TYPE_SGMII 0x4
+#define LYNX_28G_LNaPSS_TYPE_XFI 0x28
+
+#define LYNX_28G_SGMIIaCR1(lane) (0x1804 + (lane) * 0x10)
+#define LYNX_28G_SGMIIaCR1_SGPCS_EN BIT(11)
+#define LYNX_28G_SGMIIaCR1_SGPCS_DIS 0x0
+#define LYNX_28G_SGMIIaCR1_SGPCS_MSK BIT(11)
+
+struct lynx_28g_priv;
+
+struct lynx_28g_pll {
+ struct lynx_28g_priv *priv;
+ u32 rstctl, cr0, cr1;
+ int id;
+ DECLARE_PHY_INTERFACE_MASK(supported);
+};
+
+struct lynx_28g_lane {
+ struct lynx_28g_priv *priv;
+ struct phy *phy;
+ bool powered_up;
+ bool init;
+ unsigned int id;
+ phy_interface_t interface;
+};
+
+struct lynx_28g_priv {
+ void __iomem *base;
+ struct device *dev;
+ /* Serialize concurrent access to registers shared between lanes,
+ * like PCCn
+ */
+ spinlock_t pcc_lock;
+ struct lynx_28g_pll pll[LYNX_28G_NUM_PLL];
+ struct lynx_28g_lane lane[LYNX_28G_NUM_LANE];
+
+ struct delayed_work cdr_check;
+};
+
+static void lynx_28g_rmw(struct lynx_28g_priv *priv, unsigned long off,
+ u32 val, u32 mask)
+{
+ void __iomem *reg = priv->base + off;
+ u32 orig, tmp;
+
+ orig = ioread32(reg);
+ tmp = orig & ~mask;
+ tmp |= val;
+ iowrite32(tmp, reg);
+}
+
+#define lynx_28g_lane_rmw(lane, reg, val, mask) \
+ lynx_28g_rmw((lane)->priv, LYNX_28G_##reg(lane->id), \
+ LYNX_28G_##reg##_##val, LYNX_28G_##reg##_##mask)
+#define lynx_28g_lane_read(lane, reg) \
+ ioread32((lane)->priv->base + LYNX_28G_##reg((lane)->id))
+#define lynx_28g_pll_read(pll, reg) \
+ ioread32((pll)->priv->base + LYNX_28G_##reg((pll)->id))
+
+static bool lynx_28g_supports_interface(struct lynx_28g_priv *priv, int intf)
+{
+ int i;
+
+ for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
+ if (LYNX_28G_PLLnRSTCTL_DIS(priv->pll[i].rstctl))
+ continue;
+
+ if (test_bit(intf, priv->pll[i].supported))
+ return true;
+ }
+
+ return false;
+}
+
+static struct lynx_28g_pll *lynx_28g_pll_get(struct lynx_28g_priv *priv,
+ phy_interface_t intf)
+{
+ struct lynx_28g_pll *pll;
+ int i;
+
+ for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
+ pll = &priv->pll[i];
+
+ if (LYNX_28G_PLLnRSTCTL_DIS(pll->rstctl))
+ continue;
+
+ if (test_bit(intf, pll->supported))
+ return pll;
+ }
+
+ return NULL;
+}
+
+static void lynx_28g_lane_set_nrate(struct lynx_28g_lane *lane,
+ struct lynx_28g_pll *pll,
+ phy_interface_t intf)
+{
+ switch (LYNX_28G_PLLnCR1_FRATE_SEL(pll->cr1)) {
+ case LYNX_28G_PLLnCR1_FRATE_5G_10GVCO:
+ case LYNX_28G_PLLnCR1_FRATE_5G_25GVCO:
+ switch (intf) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ lynx_28g_lane_rmw(lane, LNaTGCR0, N_RATE_QUARTER, N_RATE_MSK);
+ lynx_28g_lane_rmw(lane, LNaRGCR0, N_RATE_QUARTER, N_RATE_MSK);
+ break;
+ default:
+ break;
+ }
+ break;
+ case LYNX_28G_PLLnCR1_FRATE_10G_20GVCO:
+ switch (intf) {
+ case PHY_INTERFACE_MODE_10GBASER:
+ case PHY_INTERFACE_MODE_USXGMII:
+ lynx_28g_lane_rmw(lane, LNaTGCR0, N_RATE_FULL, N_RATE_MSK);
+ lynx_28g_lane_rmw(lane, LNaRGCR0, N_RATE_FULL, N_RATE_MSK);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void lynx_28g_lane_set_pll(struct lynx_28g_lane *lane,
+ struct lynx_28g_pll *pll)
+{
+ if (pll->id == 0) {
+ lynx_28g_lane_rmw(lane, LNaTGCR0, USE_PLLF, USE_PLL_MSK);
+ lynx_28g_lane_rmw(lane, LNaRGCR0, USE_PLLF, USE_PLL_MSK);
+ } else {
+ lynx_28g_lane_rmw(lane, LNaTGCR0, USE_PLLS, USE_PLL_MSK);
+ lynx_28g_lane_rmw(lane, LNaRGCR0, USE_PLLS, USE_PLL_MSK);
+ }
+}
+
+static void lynx_28g_cleanup_lane(struct lynx_28g_lane *lane)
+{
+ u32 lane_offset = LYNX_28G_LNa_PCC_OFFSET(lane);
+ struct lynx_28g_priv *priv = lane->priv;
+
+ /* Cleanup the protocol configuration registers of the current protocol */
+ switch (lane->interface) {
+ case PHY_INTERFACE_MODE_10GBASER:
+ lynx_28g_rmw(priv, LYNX_28G_PCCC,
+ LYNX_28G_PCCC_SXGMII_DIS << lane_offset,
+ GENMASK(3, 0) << lane_offset);
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ lynx_28g_rmw(priv, LYNX_28G_PCC8,
+ LYNX_28G_PCC8_SGMII_DIS << lane_offset,
+ GENMASK(3, 0) << lane_offset);
+ break;
+ default:
+ break;
+ }
+}
+
+static void lynx_28g_lane_set_sgmii(struct lynx_28g_lane *lane)
+{
+ u32 lane_offset = LYNX_28G_LNa_PCC_OFFSET(lane);
+ struct lynx_28g_priv *priv = lane->priv;
+ struct lynx_28g_pll *pll;
+
+ lynx_28g_cleanup_lane(lane);
+
+ /* Setup the lane to run in SGMII */
+ lynx_28g_rmw(priv, LYNX_28G_PCC8,
+ LYNX_28G_PCC8_SGMII << lane_offset,
+ GENMASK(3, 0) << lane_offset);
+
+ /* Setup the protocol select and SerDes parallel interface width */
+ lynx_28g_lane_rmw(lane, LNaGCR0, PROTO_SEL_SGMII, PROTO_SEL_MSK);
+ lynx_28g_lane_rmw(lane, LNaGCR0, IF_WIDTH_10_BIT, IF_WIDTH_MSK);
+
+ /* Switch to the PLL that works with this interface type */
+ pll = lynx_28g_pll_get(priv, PHY_INTERFACE_MODE_SGMII);
+ lynx_28g_lane_set_pll(lane, pll);
+
+ /* Choose the portion of clock net to be used on this lane */
+ lynx_28g_lane_set_nrate(lane, pll, PHY_INTERFACE_MODE_SGMII);
+
+ /* Enable the SGMII PCS */
+ lynx_28g_lane_rmw(lane, SGMIIaCR1, SGPCS_EN, SGPCS_MSK);
+
+ /* Configure the appropriate equalization parameters for the protocol */
+ iowrite32(0x00808006, priv->base + LYNX_28G_LNaTECR0(lane->id));
+ iowrite32(0x04310000, priv->base + LYNX_28G_LNaRGCR1(lane->id));
+ iowrite32(0x9f800000, priv->base + LYNX_28G_LNaRECR0(lane->id));
+ iowrite32(0x001f0000, priv->base + LYNX_28G_LNaRECR1(lane->id));
+ iowrite32(0x00000000, priv->base + LYNX_28G_LNaRECR2(lane->id));
+ iowrite32(0x00000000, priv->base + LYNX_28G_LNaRSCCR0(lane->id));
+}
+
+static void lynx_28g_lane_set_10gbaser(struct lynx_28g_lane *lane)
+{
+ u32 lane_offset = LYNX_28G_LNa_PCC_OFFSET(lane);
+ struct lynx_28g_priv *priv = lane->priv;
+ struct lynx_28g_pll *pll;
+
+ lynx_28g_cleanup_lane(lane);
+
+ /* Enable the SXGMII lane */
+ lynx_28g_rmw(priv, LYNX_28G_PCCC,
+ LYNX_28G_PCCC_10GBASER << lane_offset,
+ GENMASK(3, 0) << lane_offset);
+
+ /* Setup the protocol select and SerDes parallel interface width */
+ lynx_28g_lane_rmw(lane, LNaGCR0, PROTO_SEL_XFI, PROTO_SEL_MSK);
+ lynx_28g_lane_rmw(lane, LNaGCR0, IF_WIDTH_20_BIT, IF_WIDTH_MSK);
+
+ /* Switch to the PLL that works with this interface type */
+ pll = lynx_28g_pll_get(priv, PHY_INTERFACE_MODE_10GBASER);
+ lynx_28g_lane_set_pll(lane, pll);
+
+ /* Choose the portion of clock net to be used on this lane */
+ lynx_28g_lane_set_nrate(lane, pll, PHY_INTERFACE_MODE_10GBASER);
+
+ /* Disable the SGMII PCS */
+ lynx_28g_lane_rmw(lane, SGMIIaCR1, SGPCS_DIS, SGPCS_MSK);
+
+ /* Configure the appropriate equalization parameters for the protocol */
+ iowrite32(0x10808307, priv->base + LYNX_28G_LNaTECR0(lane->id));
+ iowrite32(0x10000000, priv->base + LYNX_28G_LNaRGCR1(lane->id));
+ iowrite32(0x00000000, priv->base + LYNX_28G_LNaRECR0(lane->id));
+ iowrite32(0x001f0000, priv->base + LYNX_28G_LNaRECR1(lane->id));
+ iowrite32(0x81000020, priv->base + LYNX_28G_LNaRECR2(lane->id));
+ iowrite32(0x00002000, priv->base + LYNX_28G_LNaRSCCR0(lane->id));
+}
+
+static int lynx_28g_power_off(struct phy *phy)
+{
+ struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+ u32 trstctl, rrstctl;
+
+ if (!lane->powered_up)
+ return 0;
+
+ /* Issue a halt request */
+ lynx_28g_lane_rmw(lane, LNaTRSTCTL, HLT_REQ, HLT_REQ);
+ lynx_28g_lane_rmw(lane, LNaRRSTCTL, HLT_REQ, HLT_REQ);
+
+ /* Wait until the halting process is complete */
+ do {
+ trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
+ rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+ } while ((trstctl & LYNX_28G_LNaTRSTCTL_HLT_REQ) ||
+ (rrstctl & LYNX_28G_LNaRRSTCTL_HLT_REQ));
+
+ lane->powered_up = false;
+
+ return 0;
+}
+
+static int lynx_28g_power_on(struct phy *phy)
+{
+ struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+ u32 trstctl, rrstctl;
+
+ if (lane->powered_up)
+ return 0;
+
+ /* Issue a reset request on the lane */
+ lynx_28g_lane_rmw(lane, LNaTRSTCTL, RST_REQ, RST_REQ);
+ lynx_28g_lane_rmw(lane, LNaRRSTCTL, RST_REQ, RST_REQ);
+
+ /* Wait until the reset sequence is completed */
+ do {
+ trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL);
+ rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+ } while (!(trstctl & LYNX_28G_LNaTRSTCTL_RST_DONE) ||
+ !(rrstctl & LYNX_28G_LNaRRSTCTL_RST_DONE));
+
+ lane->powered_up = true;
+
+ return 0;
+}
+
+static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+ struct lynx_28g_priv *priv = lane->priv;
+ int powered_up = lane->powered_up;
+ int err = 0;
+
+ if (mode != PHY_MODE_ETHERNET)
+ return -EOPNOTSUPP;
+
+ if (lane->interface == PHY_INTERFACE_MODE_NA)
+ return -EOPNOTSUPP;
+
+ if (!lynx_28g_supports_interface(priv, submode))
+ return -EOPNOTSUPP;
+
+ /* If the lane is powered up, put the lane into the halt state while
+ * the reconfiguration is being done.
+ */
+ if (powered_up)
+ lynx_28g_power_off(phy);
+
+ spin_lock(&priv->pcc_lock);
+
+ switch (submode) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ lynx_28g_lane_set_sgmii(lane);
+ break;
+ case PHY_INTERFACE_MODE_10GBASER:
+ lynx_28g_lane_set_10gbaser(lane);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ lane->interface = submode;
+
+out:
+ spin_unlock(&priv->pcc_lock);
+
+ /* Power up the lane if necessary */
+ if (powered_up)
+ lynx_28g_power_on(phy);
+
+ return err;
+}
+
+static int lynx_28g_validate(struct phy *phy, enum phy_mode mode, int submode,
+ union phy_configure_opts *opts __always_unused)
+{
+ struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+ struct lynx_28g_priv *priv = lane->priv;
+
+ if (mode != PHY_MODE_ETHERNET)
+ return -EOPNOTSUPP;
+
+ if (!lynx_28g_supports_interface(priv, submode))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int lynx_28g_init(struct phy *phy)
+{
+ struct lynx_28g_lane *lane = phy_get_drvdata(phy);
+
+ /* Mark the fact that the lane was init */
+ lane->init = true;
+
+ /* SerDes lanes are powered on at boot time. Any lane that is managed
+ * by this driver will get powered down at init time aka at dpaa2-eth
+ * probe time.
+ */
+ lane->powered_up = true;
+ lynx_28g_power_off(phy);
+
+ return 0;
+}
+
+static const struct phy_ops lynx_28g_ops = {
+ .init = lynx_28g_init,
+ .power_on = lynx_28g_power_on,
+ .power_off = lynx_28g_power_off,
+ .set_mode = lynx_28g_set_mode,
+ .validate = lynx_28g_validate,
+ .owner = THIS_MODULE,
+};
+
+static void lynx_28g_pll_read_configuration(struct lynx_28g_priv *priv)
+{
+ struct lynx_28g_pll *pll;
+ int i;
+
+ for (i = 0; i < LYNX_28G_NUM_PLL; i++) {
+ pll = &priv->pll[i];
+ pll->priv = priv;
+ pll->id = i;
+
+ pll->rstctl = lynx_28g_pll_read(pll, PLLnRSTCTL);
+ pll->cr0 = lynx_28g_pll_read(pll, PLLnCR0);
+ pll->cr1 = lynx_28g_pll_read(pll, PLLnCR1);
+
+ if (LYNX_28G_PLLnRSTCTL_DIS(pll->rstctl))
+ continue;
+
+ switch (LYNX_28G_PLLnCR1_FRATE_SEL(pll->cr1)) {
+ case LYNX_28G_PLLnCR1_FRATE_5G_10GVCO:
+ case LYNX_28G_PLLnCR1_FRATE_5G_25GVCO:
+ /* 5GHz clock net */
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, pll->supported);
+ __set_bit(PHY_INTERFACE_MODE_SGMII, pll->supported);
+ break;
+ case LYNX_28G_PLLnCR1_FRATE_10G_20GVCO:
+ /* 10.3125GHz clock net */
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, pll->supported);
+ break;
+ default:
+ /* 6GHz, 12.890625GHz, 8GHz */
+ break;
+ }
+ }
+}
+
+#define work_to_lynx(w) container_of((w), struct lynx_28g_priv, cdr_check.work)
+
+static void lynx_28g_cdr_lock_check(struct work_struct *work)
+{
+ struct lynx_28g_priv *priv = work_to_lynx(work);
+ struct lynx_28g_lane *lane;
+ u32 rrstctl;
+ int i;
+
+ for (i = 0; i < LYNX_28G_NUM_LANE; i++) {
+ lane = &priv->lane[i];
+
+ mutex_lock(&lane->phy->mutex);
+
+ if (!lane->init || !lane->powered_up) {
+ mutex_unlock(&lane->phy->mutex);
+ continue;
+ }
+
+ rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+ if (!(rrstctl & LYNX_28G_LNaRRSTCTL_CDR_LOCK)) {
+ lynx_28g_lane_rmw(lane, LNaRRSTCTL, RST_REQ, RST_REQ);
+ do {
+ rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL);
+ } while (!(rrstctl & LYNX_28G_LNaRRSTCTL_RST_DONE));
+ }
+
+ mutex_unlock(&lane->phy->mutex);
+ }
+ queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
+ msecs_to_jiffies(1000));
+}
+
+static void lynx_28g_lane_read_configuration(struct lynx_28g_lane *lane)
+{
+ u32 pss, protocol;
+
+ pss = lynx_28g_lane_read(lane, LNaPSS);
+ protocol = LYNX_28G_LNaPSS_TYPE(pss);
+ switch (protocol) {
+ case LYNX_28G_LNaPSS_TYPE_SGMII:
+ lane->interface = PHY_INTERFACE_MODE_SGMII;
+ break;
+ case LYNX_28G_LNaPSS_TYPE_XFI:
+ lane->interface = PHY_INTERFACE_MODE_10GBASER;
+ break;
+ default:
+ lane->interface = PHY_INTERFACE_MODE_NA;
+ }
+}
+
+static struct phy *lynx_28g_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct lynx_28g_priv *priv = dev_get_drvdata(dev);
+ int idx = args->args[0];
+
+ if (WARN_ON(idx >= LYNX_28G_NUM_LANE))
+ return ERR_PTR(-EINVAL);
+
+ return priv->lane[idx].phy;
+}
+
+static int lynx_28g_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct phy_provider *provider;
+ struct lynx_28g_priv *priv;
+ int i;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->dev = &pdev->dev;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ lynx_28g_pll_read_configuration(priv);
+
+ for (i = 0; i < LYNX_28G_NUM_LANE; i++) {
+ struct lynx_28g_lane *lane = &priv->lane[i];
+ struct phy *phy;
+
+ memset(lane, 0, sizeof(*lane));
+
+ phy = devm_phy_create(&pdev->dev, NULL, &lynx_28g_ops);
+ if (IS_ERR(phy))
+ return PTR_ERR(phy);
+
+ lane->priv = priv;
+ lane->phy = phy;
+ lane->id = i;
+ phy_set_drvdata(phy, lane);
+ lynx_28g_lane_read_configuration(lane);
+ }
+
+ dev_set_drvdata(dev, priv);
+
+ spin_lock_init(&priv->pcc_lock);
+ INIT_DELAYED_WORK(&priv->cdr_check, lynx_28g_cdr_lock_check);
+
+ queue_delayed_work(system_power_efficient_wq, &priv->cdr_check,
+ msecs_to_jiffies(1000));
+
+ dev_set_drvdata(&pdev->dev, priv);
+ provider = devm_of_phy_provider_register(&pdev->dev, lynx_28g_xlate);
+
+ return PTR_ERR_OR_ZERO(provider);
+}
+
+static void lynx_28g_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lynx_28g_priv *priv = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&priv->cdr_check);
+}
+
+static const struct of_device_id lynx_28g_of_match_table[] = {
+ { .compatible = "fsl,lynx-28g" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, lynx_28g_of_match_table);
+
+static struct platform_driver lynx_28g_driver = {
+ .probe = lynx_28g_probe,
+ .remove_new = lynx_28g_remove,
+ .driver = {
+ .name = "lynx-28g",
+ .of_match_table = lynx_28g_of_match_table,
+ },
+};
+module_platform_driver(lynx_28g_driver);
+
+MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
+MODULE_DESCRIPTION("Lynx 28G SerDes PHY driver for Layerscape SoCs");
+MODULE_LICENSE("GPL v2");