diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
| commit | 3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch) | |
| tree | b767d7f6e26bc64334d3f14f8422d187239dc45d /drivers/phy/freescale | |
| download | linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip | |
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe:
- Fix a task_work add use-after-free with SQPOLL.
The sqpoll thread could pop and complete the last request while
io_req_normal_work_add() was still looking at them after the mpscq
push.
Use the same approach as DEFER_TASKRUN to protect from that, holding
an RCU read lock across the add, and have exit wait for an RCU grace
period for SQPOLL rings as well.
- CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the
big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED
rings
- Mark the source filter table as COW when cloning bpf filters, so
registering another filter on the source doesn't modify the shared
table in place
- Initialize the task context before running the BPF loop
- Requeue zcrx multishot receives stopped by a local resource
- End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit)
* tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux:
io_uring: fix task_work add use-after-free with SQPOLL
io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full
io_uring/zcrx: requeue multishot receives stopped by a local resource
io_uring: initialize task context before running the BPF loop
io_uring: zero big_cqe for aux CQEs on CQE32 rings
io_uring: fix free entry check for 32b CQEs on CQE32 rings
io_uring: only post the dummy skip CQE on CQE_MIXED rings
io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'drivers/phy/freescale')
| -rw-r--r-- | drivers/phy/freescale/Kconfig | 79 | ||||
| -rw-r--r-- | drivers/phy/freescale/Makefile | 10 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-imx8-mipi-dphy.c | 749 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-imx8m-pcie.c | 291 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-imx8mq-usb.c | 849 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-imx8qm-hsio.c | 629 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-imx8qm-lvds-phy.c | 436 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-lynx-10g.c | 1326 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-lynx-28g.c | 1224 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-lynx-core.c | 445 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-lynx-core.h | 134 | ||||
| -rw-r--r-- | drivers/phy/freescale/phy-fsl-samsung-hdmi.c | 746 |
12 files changed, 6918 insertions, 0 deletions
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig new file mode 100644 index 000000000..7557b624b --- /dev/null +++ b/drivers/phy/freescale/Kconfig @@ -0,0 +1,79 @@ +# 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 + depends on TYPEC || TYPEC=n + select GENERIC_PHY + select REGMAP_MMIO + 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. + +config PHY_FSL_IMX8QM_HSIO + tristate "Freescale i.MX8QM HSIO PHY" + depends on OF && HAS_IOMEM + select GENERIC_PHY + help + Enable this to add support for the HSIO PHY as found on + i.MX8QM family of SOCs. + +config PHY_FSL_SAMSUNG_HDMI_PHY + tristate "Samsung HDMI PHY support" + depends on OF && HAS_IOMEM && COMMON_CLK + help + Enable this to add support for the Samsung HDMI PHY in i.MX8MP. +endif + +config PHY_FSL_LYNX_CORE + tristate + +config PHY_FSL_LYNX_10G + tristate "Freescale Layerscape Lynx 10G SerDes PHY support" + depends on OF + depends on ARCH_LAYERSCAPE || COMPILE_TEST + select FSL_GUTS + select GENERIC_PHY + select PHY_FSL_LYNX_CORE + help + Enable this to add support for the Lynx 10G SerDes PHY as found on + NXP's Layerscape platform such as LS1088A or LS1028A. + +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 + select PHY_FSL_LYNX_CORE + 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 000000000..5b0e180d6 --- /dev/null +++ b/drivers/phy/freescale/Makefile @@ -0,0 +1,10 @@ +# 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_IMX8QM_HSIO) += phy-fsl-imx8qm-hsio.o +obj-$(CONFIG_PHY_FSL_LYNX_CORE) += phy-fsl-lynx-core.o +obj-$(CONFIG_PHY_FSL_LYNX_10G) += phy-fsl-lynx-10g.o +obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o +obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) += phy-fsl-samsung-hdmi.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 000000000..0928a526e --- /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 000000000..7f5600103 --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-imx8m-pcie.c @@ -0,0 +1,291 @@ +// 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: + reset_control_assert(imx8_phy->reset); + 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); + if (imx8_phy->drvdata->variant != IMX8MM) { + 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); + + reset_control_deassert(imx8_phy->perst); + reset_control_deassert(imx8_phy->reset); + usleep_range(200, 500); + + /* 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); + + /* 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_power_off(struct phy *phy) +{ + struct imx8_pcie_phy *imx8_phy = phy_get_drvdata(phy); + + reset_control_assert(imx8_phy->reset); + reset_control_assert(imx8_phy->perst); + + return 0; +} + +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, + .power_off = imx8_pcie_phy_power_off, + .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)) + return dev_err_probe(dev, PTR_ERR(imx8_phy->clk), + "failed to get imx pcie phy clock\n"); + + /* 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)) + return dev_err_probe(dev, PTR_ERR(imx8_phy->iomuxc_gpr), + "unable to find iomuxc registers\n"); + + imx8_phy->reset = devm_reset_control_get_exclusive(dev, "pciephy"); + if (IS_ERR(imx8_phy->reset)) + return dev_err_probe(dev, PTR_ERR(imx8_phy->reset), + "Failed to get PCIEPHY reset control\n"); + + 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 000000000..885983554 --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-imx8mq-usb.c @@ -0,0 +1,849 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright 2017-2026 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/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> +#include <linux/regmap.h> +#include <linux/usb/typec_mux.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_CTRL0_FSEL_100M 0x27 +#define PHY_CTRL0_SSC_RANGE_MASK GENMASK(23, 21) +#define PHY_CTRL0_SSC_RANGE_4003PPM 0x2 +#define PHY_CTRL0_SSC_RANGE_4492PPM 0x1 +#define PHY_CTRL0_SSC_RANGE_4980PPM 0x0 + +#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_RXTERM_OVERRIDE_SEL BIT(29) +#define PHY_CTRL6_ALT_CLK_EN BIT(1) +#define PHY_CTRL6_ALT_CLK_SEL BIT(0) + +#define PHY_CRCTL 0x30 + +#define PHY_TUNE_DEFAULT 0xffffffff + +#define TCA_CLK_RST 0x00 +#define TCA_CLK_RST_SW BIT(9) +#define TCA_CLK_RST_REF_CLK_EN BIT(1) +#define TCA_CLK_RST_SUSPEND_CLK_EN BIT(0) + +#define TCA_INTR_EN 0x04 +#define TCA_INTR_STS 0x08 + +#define TCA_GCFG 0x10 +#define TCA_GCFG_ROLE_HSTDEV BIT(4) +#define TCA_GCFG_OP_MODE GENMASK(1, 0) +#define TCA_GCFG_OP_MODE_SYSMODE 0 +#define TCA_GCFG_OP_MODE_SYNCMODE 1 + +#define TCA_TCPC 0x14 +#define TCA_TCPC_VALID BIT(4) +#define TCA_TCPC_LOW_POWER_EN BIT(3) +#define TCA_TCPC_ORIENTATION_NORMAL BIT(2) +#define TCA_TCPC_MUX_CONTRL GENMASK(1, 0) +#define TCA_TCPC_MUX_CONTRL_NO_CONN 0 +#define TCA_TCPC_MUX_CONTRL_USB_CONN 1 + +#define TCA_SYSMODE_CFG 0x18 +#define TCA_SYSMODE_TCPC_DISABLE BIT(3) +#define TCA_SYSMODE_TCPC_FLIP BIT(2) + +#define TCA_CTRLSYNCMODE_CFG0 0x20 +#define TCA_CTRLSYNCMODE_CFG1 0x20 + +#define TCA_PSTATE 0x30 +#define TCA_PSTATE_CM_STS BIT(4) +#define TCA_PSTATE_TX_STS BIT(3) +#define TCA_PSTATE_RX_PLL_STS BIT(2) +#define TCA_PSTATE_PIPE0_POWER_DOWN GENMASK(1, 0) + +#define TCA_GEN_STATUS 0x34 +#define TCA_GEN_DEV_POR BIT(12) +#define TCA_GEN_REF_CLK_SEL BIT(8) +#define TCA_GEN_TYPEC_FLIP_INVERT BIT(4) +#define TCA_GEN_PHY_TYPEC_DISABLE BIT(3) +#define TCA_GEN_PHY_TYPEC_FLIP BIT(2) + +#define TCA_VBUS_CTRL 0x40 +#define TCA_VBUS_STATUS 0x44 + +#define TCA_INFO 0xfc + +struct tca_blk { + struct typec_switch_dev *sw; + void __iomem *base; + struct mutex mutex; + enum typec_orientation orientation; +}; + +struct imx8mq_usb_phy { + struct phy *phy; + struct clk *clk; + struct clk *alt_clk; + void __iomem *base; + struct regulator *vbus; + struct tca_blk *tca; + struct regmap *cr_regmap; + 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; +}; + +struct imx8mq_usb_phy_drvdata { + const struct phy_ops *ops; + bool need_genpd_rpm_on; +}; + +static void tca_blk_orientation_set(struct tca_blk *tca, + enum typec_orientation orientation); + +static int tca_blk_typec_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct imx8mq_usb_phy *imx_phy = typec_switch_get_drvdata(sw); + struct tca_blk *tca = imx_phy->tca; + + if (tca->orientation == orientation) + return 0; + + PM_RUNTIME_ACQUIRE_IF_ENABLED(&imx_phy->phy->dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + tca_blk_orientation_set(tca, orientation); + + return 0; +} + +static struct typec_switch_dev *tca_blk_get_typec_switch(struct platform_device *pdev, + struct imx8mq_usb_phy *imx_phy) +{ + struct device *dev = &pdev->dev; + struct typec_switch_dev *sw; + struct typec_switch_desc sw_desc = { }; + + sw_desc.drvdata = imx_phy; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = tca_blk_typec_switch_set; + sw_desc.name = NULL; + + sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(sw)) { + dev_err(dev, "Error register tca orientation switch: %ld", + PTR_ERR(sw)); + return NULL; + } + + return sw; +} + +static void tca_blk_put_typec_switch(void *data) +{ + typec_switch_unregister(data); +} + +static void tca_blk_orientation_set(struct tca_blk *tca, + enum typec_orientation orientation) +{ + u32 val; + + mutex_lock(&tca->mutex); + + if (orientation == TYPEC_ORIENTATION_NONE) { + /* + * use Controller Synced Mode for TCA low power enable and + * put PHY to USB safe state. + */ + val = FIELD_PREP(TCA_GCFG_OP_MODE, TCA_GCFG_OP_MODE_SYNCMODE); + writel(val, tca->base + TCA_GCFG); + + val = TCA_TCPC_VALID | TCA_TCPC_LOW_POWER_EN; + writel(val, tca->base + TCA_TCPC); + + goto out; + } + + /* use System Configuration Mode for TCA mux control. */ + val = FIELD_PREP(TCA_GCFG_OP_MODE, TCA_GCFG_OP_MODE_SYSMODE); + writel(val, tca->base + TCA_GCFG); + + /* Disable TCA module */ + val = readl(tca->base + TCA_SYSMODE_CFG); + val |= TCA_SYSMODE_TCPC_DISABLE; + writel(val, tca->base + TCA_SYSMODE_CFG); + + if (orientation == TYPEC_ORIENTATION_REVERSE) + val |= TCA_SYSMODE_TCPC_FLIP; + else if (orientation == TYPEC_ORIENTATION_NORMAL) + val &= ~TCA_SYSMODE_TCPC_FLIP; + + writel(val, tca->base + TCA_SYSMODE_CFG); + + /* Enable TCA module */ + val &= ~TCA_SYSMODE_TCPC_DISABLE; + writel(val, tca->base + TCA_SYSMODE_CFG); + +out: + tca->orientation = orientation; + mutex_unlock(&tca->mutex); +} + +static void tca_blk_init(struct tca_blk *tca) +{ + u32 val; + + /* reset XBar block */ + val = readl(tca->base + TCA_CLK_RST); + val &= ~TCA_CLK_RST_SW; + writel(val, tca->base + TCA_CLK_RST); + + udelay(100); + + /* clear reset */ + val |= TCA_CLK_RST_SW; + writel(val, tca->base + TCA_CLK_RST); + + tca_blk_orientation_set(tca, tca->orientation); +} + +static struct tca_blk *imx95_usb_phy_get_tca(struct platform_device *pdev, + struct imx8mq_usb_phy *imx_phy) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct tca_blk *tca; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return NULL; + + tca = devm_kzalloc(dev, sizeof(*tca), GFP_KERNEL); + if (!tca) + return ERR_PTR(-ENOMEM); + + tca->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(tca->base)) + return ERR_CAST(tca->base); + + mutex_init(&tca->mutex); + + tca->orientation = TYPEC_ORIENTATION_NORMAL; + tca->sw = tca_blk_get_typec_switch(pdev, imx_phy); + + ret = devm_add_action_or_reset(&pdev->dev, tca_blk_put_typec_switch, tca->sw); + if (ret) + return ERR_PTR(ret); + + return tca; +} + +static u32 phy_tx_vref_tune_from_property(u32 percent) +{ + percent = clamp(percent, 94U, 124U); + + return DIV_ROUND_CLOSEST(percent - 94U, 2); +} + +static u32 imx95_phy_tx_vref_tune_from_property(u32 percent) +{ + percent = clamp(percent, 90U, 108U); + + switch (percent) { + case 90 ... 91: + percent = 0; + break; + case 92 ... 96: + percent -= 91; + break; + case 97 ... 104: + percent -= 92; + break; + case 105 ... 108: + percent -= 93; + break; + } + + return percent; +} + +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 imx95_phy_tx_rise_tune_from_property(u32 percent) +{ + percent = clamp(percent, 90U, 120U); + + switch (percent) { + case 90 ... 99: + return 3; + case 101 ... 115: + return 1; + case 116 ... 120: + return 0; + default: + return 2; + } +} + +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 1156: + return 5; + case 844: + return 3; + default: + return 4; + } +} + +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 imx95_phy_comp_dis_tune_from_property(u32 percent) +{ + percent = clamp(percent, 94, 104); + + switch (percent) { + case 94 ... 95: + percent = 0; + break; + case 96 ... 98: + percent -= 95; + break; + case 99 ... 102: + percent -= 96; + break; + case 103 ... 104: + percent -= 97; + break; + } + + return percent; +} + +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; + bool is_imx95 = false; + + if (device_is_compatible(dev, "fsl,imx95-usb-phy")) + is_imx95 = true; + + 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 if (is_imx95) + imx_phy->tx_vref_tune = + imx95_phy_tx_vref_tune_from_property(imx_phy->tx_vref_tune); + 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 if (is_imx95) + imx_phy->tx_rise_tune = + imx95_phy_tx_rise_tune_from_property(imx_phy->tx_rise_tune); + 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 if (is_imx95) + imx_phy->comp_dis_tune = + imx95_phy_comp_dis_tune_from_property(imx_phy->comp_dis_tune); + else + imx_phy->comp_dis_tune = + phy_comp_dis_tune_from_property(imx_phy->comp_dis_tune); + + if (device_property_read_u32(dev, "fsl,phy-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 &= ~PHY_CTRL5_PCS_TX_SWING_FULL_MASK; + 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, imx_phy->alt_clk ? + PHY_CTRL0_FSEL_100M : 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; + value &= ~PHY_CTRL0_SSC_RANGE_MASK; + value |= FIELD_PREP(PHY_CTRL0_SSC_RANGE_MASK, + PHY_CTRL0_SSC_RANGE_4003PPM); + 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); + + if (imx_phy->tca) + tca_blk_init(imx_phy->tca); + + return 0; +} + +static int imx8mq_phy_power_on(struct phy *phy) +{ + struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + u32 value; + int ret; + + ret = regulator_enable(imx_phy->vbus); + if (ret) + return ret; + + /* Disable rx term override */ + value = readl(imx_phy->base + PHY_CTRL6); + value &= ~PHY_CTRL6_RXTERM_OVERRIDE_SEL; + writel(value, imx_phy->base + PHY_CTRL6); + + return 0; +} + +static int imx8mq_phy_power_off(struct phy *phy) +{ + struct imx8mq_usb_phy *imx_phy = phy_get_drvdata(phy); + u32 value; + + /* Override rx term to be 0 */ + value = readl(imx_phy->base + PHY_CTRL6); + value |= PHY_CTRL6_RXTERM_OVERRIDE_SEL; + writel(value, imx_phy->base + PHY_CTRL6); + + 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 imx8mq_usb_phy_drvdata imx8mq_usb_phy_data = { + .ops = &imx8mq_usb_phy_ops, +}; + +static const struct imx8mq_usb_phy_drvdata imx8mp_usb_phy_data = { + .ops = &imx8mp_usb_phy_ops, + .need_genpd_rpm_on = true, +}; + +static const struct imx8mq_usb_phy_drvdata imx95_usb_phy_data = { + .ops = &imx8mp_usb_phy_ops, +}; + +static const struct of_device_id imx8mq_usb_phy_of_match[] = { + {.compatible = "fsl,imx8mq-usb-phy", + .data = &imx8mq_usb_phy_data,}, + {.compatible = "fsl,imx8mp-usb-phy", + .data = &imx8mp_usb_phy_data,}, + {.compatible = "fsl,imx95-usb-phy", + .data = &imx95_usb_phy_data,}, + { } +}; +MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match); + +static const struct regmap_config imx_cr_regmap_config = { + .name = "cr", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x7, +}; + +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 imx8mq_usb_phy_drvdata *phy_data; + int ret; + + imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL); + if (!imx_phy) + return -ENOMEM; + + platform_set_drvdata(pdev, imx_phy); + + imx_phy->clk = devm_clk_get_enabled(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->alt_clk = devm_clk_get_optional_enabled(dev, "alt"); + if (IS_ERR(imx_phy->alt_clk)) + return dev_err_probe(dev, PTR_ERR(imx_phy->alt_clk), + "Failed to get alt clk\n"); + + imx_phy->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(imx_phy->base)) + return PTR_ERR(imx_phy->base); + + imx_phy->cr_regmap = devm_regmap_init_mmio(dev, imx_phy->base + PHY_CRCTL, + &imx_cr_regmap_config); + if (IS_ERR(imx_phy->cr_regmap)) { + dev_warn(dev, "Fail to init debug register regmap\n"); + imx_phy->cr_regmap = NULL; + } + + 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_data = of_device_get_match_data(dev); + if (!phy_data) + return -EINVAL; + + if (phy_data->need_genpd_rpm_on) { + ret = dev_pm_genpd_rpm_always_on(dev, true); + if (ret && ret != -EOPNOTSUPP) + dev_warn(dev, "failed to set genpd rpm always on\n"); + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + imx_phy->phy = devm_phy_create(dev, NULL, phy_data->ops); + if (IS_ERR(imx_phy->phy)) { + ret = dev_err_probe(dev, PTR_ERR(imx_phy->phy), + "failed to create PHY\n"); + goto disable_rpm; + } + + phy_set_drvdata(imx_phy->phy, imx_phy); + + imx_phy->tca = imx95_usb_phy_get_tca(pdev, imx_phy); + if (IS_ERR(imx_phy->tca)) { + ret = dev_err_probe(dev, PTR_ERR(imx_phy->tca), + "failed to get tca\n"); + goto disable_rpm; + } + + imx8m_get_phy_tuning_data(imx_phy); + device_set_wakeup_capable(dev, true); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + ret = dev_err_probe(dev, PTR_ERR(phy_provider), + "failed to register phy provider\n"); + goto disable_rpm; + } + + return 0; + +disable_rpm: + pm_runtime_disable(dev); + return ret; +} + +static void imx8mq_usb_phy_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + dev_warn(dev, "failed to resume on remove: %d\n", ret); + + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); +} + +static int imx8mq_usb_phy_runtime_suspend(struct device *dev) +{ + struct imx8mq_usb_phy *imx_phy = dev_get_drvdata(dev); + + if (imx_phy->cr_regmap) + regcache_cache_only(imx_phy->cr_regmap, true); + + clk_disable_unprepare(imx_phy->alt_clk); + clk_disable_unprepare(imx_phy->clk); + + return 0; +} + +static int imx8mq_usb_phy_runtime_resume(struct device *dev) +{ + struct imx8mq_usb_phy *imx_phy = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(imx_phy->clk); + if (ret) + return ret; + + ret = clk_prepare_enable(imx_phy->alt_clk); + if (ret) { + clk_disable_unprepare(imx_phy->clk); + return ret; + } + + if (imx_phy->cr_regmap) + regcache_cache_only(imx_phy->cr_regmap, false); + + return 0; +} + +static DEFINE_RUNTIME_DEV_PM_OPS(imx8mq_usb_phy_pm_ops, imx8mq_usb_phy_runtime_suspend, + imx8mq_usb_phy_runtime_resume, NULL); + +static struct platform_driver imx8mq_usb_phy_driver = { + .probe = imx8mq_usb_phy_probe, + .remove = imx8mq_usb_phy_remove, + .driver = { + .name = "imx8mq-usb-phy", + .of_match_table = imx8mq_usb_phy_of_match, + .pm = pm_ptr(&imx8mq_usb_phy_pm_ops), + .suppress_bind_attrs = true, + } +}; +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-hsio.c b/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c new file mode 100644 index 000000000..4ab45c9f5 --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-imx8qm-hsio.c @@ -0,0 +1,629 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2024 NXP + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pci_regs.h> +#include <linux/phy/phy.h> +#include <linux/phy/pcie.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <dt-bindings/phy/phy.h> +#include <dt-bindings/phy/phy-imx8-pcie.h> + +#define MAX_NUM_LANE 3 +#define LANE_NUM_CLKS 5 + +/* Parameters for the waiting for PCIe PHY PLL to lock */ +#define PHY_INIT_WAIT_USLEEP_MAX 10 +#define PHY_INIT_WAIT_TIMEOUT (1000 * PHY_INIT_WAIT_USLEEP_MAX) + +/* i.MX8Q HSIO registers */ +#define HSIO_CTRL0 0x0 +#define HSIO_APB_RSTN_0 BIT(0) +#define HSIO_APB_RSTN_1 BIT(1) +#define HSIO_PIPE_RSTN_0_MASK GENMASK(25, 24) +#define HSIO_PIPE_RSTN_1_MASK GENMASK(27, 26) +#define HSIO_MODE_MASK GENMASK(20, 17) +#define HSIO_MODE_PCIE 0x0 +#define HSIO_MODE_SATA 0x4 +#define HSIO_DEVICE_TYPE_MASK GENMASK(27, 24) +#define HSIO_EPCS_TXDEEMP BIT(5) +#define HSIO_EPCS_TXDEEMP_SEL BIT(6) +#define HSIO_EPCS_PHYRESET_N BIT(7) +#define HSIO_RESET_N BIT(12) + +#define HSIO_IOB_RXENA BIT(0) +#define HSIO_IOB_TXENA BIT(1) +#define HSIO_IOB_A_0_TXOE BIT(2) +#define HSIO_IOB_A_0_M1M0_2 BIT(4) +#define HSIO_IOB_A_0_M1M0_MASK GENMASK(4, 3) +#define HSIO_PHYX1_EPCS_SEL BIT(12) +#define HSIO_PCIE_AB_SELECT BIT(13) + +#define HSIO_PHY_STS0 0x4 +#define HSIO_LANE0_TX_PLL_LOCK BIT(4) +#define HSIO_LANE1_TX_PLL_LOCK BIT(12) + +#define HSIO_CTRL2 0x8 +#define HSIO_LTSSM_ENABLE BIT(4) +#define HSIO_BUTTON_RST_N BIT(21) +#define HSIO_PERST_N BIT(22) +#define HSIO_POWER_UP_RST_N BIT(23) + +#define HSIO_PCIE_STS0 0xc +#define HSIO_PM_REQ_CORE_RST BIT(19) + +#define HSIO_REG48_PMA_STATUS 0x30 +#define HSIO_REG48_PMA_RDY BIT(7) + +struct imx_hsio_drvdata { + int lane_num; +}; + +struct imx_hsio_lane { + u32 ctrl_index; + u32 ctrl_off; + u32 idx; + u32 phy_off; + u32 phy_type; + const char * const *clk_names; + struct clk_bulk_data clks[LANE_NUM_CLKS]; + struct imx_hsio_priv *priv; + struct phy *phy; + enum phy_mode phy_mode; +}; + +struct imx_hsio_priv { + void __iomem *base; + struct device *dev; + struct mutex lock; + const char *hsio_cfg; + const char *refclk_pad; + u32 open_cnt; + struct regmap *phy; + struct regmap *ctrl; + struct regmap *misc; + const struct imx_hsio_drvdata *drvdata; + struct imx_hsio_lane lane[MAX_NUM_LANE]; +}; + +static const char * const lan0_pcie_clks[] = {"apb_pclk0", "pclk0", "ctl0_crr", + "phy0_crr", "misc_crr"}; +static const char * const lan1_pciea_clks[] = {"apb_pclk1", "pclk1", "ctl0_crr", + "phy0_crr", "misc_crr"}; +static const char * const lan1_pcieb_clks[] = {"apb_pclk1", "pclk1", "ctl1_crr", + "phy0_crr", "misc_crr"}; +static const char * const lan2_pcieb_clks[] = {"apb_pclk2", "pclk2", "ctl1_crr", + "phy1_crr", "misc_crr"}; +static const char * const lan2_sata_clks[] = {"pclk2", "epcs_tx", "epcs_rx", + "phy1_crr", "misc_crr"}; + +static const struct regmap_config regmap_phy_config = { + .name = "phy", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static const struct regmap_config regmap_ctrl_config = { + .name = "ctrl", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static const struct regmap_config regmap_misc_config = { + .name = "misc", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int imx_hsio_init(struct phy *phy) +{ + int ret, i; + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + struct device *dev = priv->dev; + + /* Assign clocks refer to different modes */ + switch (lane->phy_type) { + case PHY_TYPE_PCIE: + lane->phy_mode = PHY_MODE_PCIE; + if (lane->ctrl_index == 0) { /* PCIEA */ + lane->ctrl_off = 0; + lane->phy_off = 0; + + for (i = 0; i < LANE_NUM_CLKS; i++) { + if (lane->idx == 0) + lane->clks[i].id = lan0_pcie_clks[i]; + else + lane->clks[i].id = lan1_pciea_clks[i]; + } + } else { /* PCIEB */ + if (lane->idx == 0) { /* i.MX8QXP */ + lane->ctrl_off = 0; + lane->phy_off = 0; + } else { + /* + * On i.MX8QM, only second or third lane can be + * bound to PCIEB. + */ + lane->ctrl_off = SZ_64K; + if (lane->idx == 1) + lane->phy_off = 0; + else /* the third lane is bound to PCIEB */ + lane->phy_off = SZ_64K; + } + + for (i = 0; i < LANE_NUM_CLKS; i++) { + if (lane->idx == 1) + lane->clks[i].id = lan1_pcieb_clks[i]; + else if (lane->idx == 2) + lane->clks[i].id = lan2_pcieb_clks[i]; + else /* i.MX8QXP only has PCIEB, idx is 0 */ + lane->clks[i].id = lan0_pcie_clks[i]; + } + } + break; + case PHY_TYPE_SATA: + /* On i.MX8QM, only the third lane can be bound to SATA */ + lane->phy_mode = PHY_MODE_SATA; + lane->ctrl_off = SZ_128K; + lane->phy_off = SZ_64K; + + for (i = 0; i < LANE_NUM_CLKS; i++) + lane->clks[i].id = lan2_sata_clks[i]; + break; + default: + return -EINVAL; + } + + /* Fetch clocks and enable them */ + ret = devm_clk_bulk_get(dev, LANE_NUM_CLKS, lane->clks); + if (ret) + return ret; + ret = clk_bulk_prepare_enable(LANE_NUM_CLKS, lane->clks); + if (ret) + return ret; + + /* allow the clocks to stabilize */ + usleep_range(200, 500); + return 0; +} + +static int imx_hsio_exit(struct phy *phy) +{ + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + + clk_bulk_disable_unprepare(LANE_NUM_CLKS, lane->clks); + + return 0; +} + +static void imx_hsio_pcie_phy_resets(struct phy *phy) +{ + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + regmap_clear_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL2, + HSIO_BUTTON_RST_N); + regmap_clear_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL2, + HSIO_PERST_N); + regmap_clear_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL2, + HSIO_POWER_UP_RST_N); + regmap_set_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL2, + HSIO_BUTTON_RST_N); + regmap_set_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL2, + HSIO_PERST_N); + regmap_set_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL2, + HSIO_POWER_UP_RST_N); + + if (lane->idx == 1) { + regmap_set_bits(priv->phy, lane->phy_off + HSIO_CTRL0, + HSIO_APB_RSTN_1); + regmap_set_bits(priv->phy, lane->phy_off + HSIO_CTRL0, + HSIO_PIPE_RSTN_1_MASK); + } else { + regmap_set_bits(priv->phy, lane->phy_off + HSIO_CTRL0, + HSIO_APB_RSTN_0); + regmap_set_bits(priv->phy, lane->phy_off + HSIO_CTRL0, + HSIO_PIPE_RSTN_0_MASK); + } +} + +static void imx_hsio_sata_phy_resets(struct phy *phy) +{ + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + /* clear PHY RST, then set it */ + regmap_clear_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL0, + HSIO_EPCS_PHYRESET_N); + regmap_set_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL0, + HSIO_EPCS_PHYRESET_N); + + /* CTRL RST: SET -> delay 1 us -> CLEAR -> SET */ + regmap_set_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL0, HSIO_RESET_N); + udelay(1); + regmap_clear_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL0, + HSIO_RESET_N); + regmap_set_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL0, HSIO_RESET_N); +} + +static void imx_hsio_configure_clk_pad(struct phy *phy) +{ + bool pll = false; + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + if (priv->refclk_pad && strncmp(priv->refclk_pad, "output", 6) == 0) { + pll = true; + regmap_update_bits(priv->misc, HSIO_CTRL0, + HSIO_IOB_A_0_TXOE | HSIO_IOB_A_0_M1M0_MASK, + HSIO_IOB_A_0_TXOE | HSIO_IOB_A_0_M1M0_2); + } else { + regmap_update_bits(priv->misc, HSIO_CTRL0, + HSIO_IOB_A_0_TXOE | HSIO_IOB_A_0_M1M0_MASK, + 0); + } + + regmap_update_bits(priv->misc, HSIO_CTRL0, HSIO_IOB_RXENA, + pll ? 0 : HSIO_IOB_RXENA); + regmap_update_bits(priv->misc, HSIO_CTRL0, HSIO_IOB_TXENA, + pll ? HSIO_IOB_TXENA : 0); +} + +static void imx_hsio_pre_set(struct phy *phy) +{ + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + if (strncmp(priv->hsio_cfg, "pciea-x2-pcieb", 14) == 0) { + regmap_set_bits(priv->misc, HSIO_CTRL0, HSIO_PCIE_AB_SELECT); + } else if (strncmp(priv->hsio_cfg, "pciea-x2-sata", 13) == 0) { + regmap_set_bits(priv->misc, HSIO_CTRL0, HSIO_PHYX1_EPCS_SEL); + } else if (strncmp(priv->hsio_cfg, "pciea-pcieb-sata", 16) == 0) { + regmap_set_bits(priv->misc, HSIO_CTRL0, HSIO_PCIE_AB_SELECT); + regmap_set_bits(priv->misc, HSIO_CTRL0, HSIO_PHYX1_EPCS_SEL); + } + + imx_hsio_configure_clk_pad(phy); +} + +static int imx_hsio_pcie_power_on(struct phy *phy) +{ + int ret; + u32 val, addr, cond; + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + imx_hsio_pcie_phy_resets(phy); + + /* Toggle apb_pclk to make sure PM_REQ_CORE_RST is cleared. */ + clk_disable_unprepare(lane->clks[0].clk); + mdelay(1); + ret = clk_prepare_enable(lane->clks[0].clk); + if (ret) { + dev_err(priv->dev, "unable to enable phy apb_pclk\n"); + return ret; + } + + addr = lane->ctrl_off + HSIO_PCIE_STS0; + cond = HSIO_PM_REQ_CORE_RST; + ret = regmap_read_poll_timeout(priv->ctrl, addr, val, + (val & cond) == 0, + PHY_INIT_WAIT_USLEEP_MAX, + PHY_INIT_WAIT_TIMEOUT); + if (ret) + dev_err(priv->dev, "HSIO_PM_REQ_CORE_RST is set\n"); + return ret; +} + +static int imx_hsio_sata_power_on(struct phy *phy) +{ + int ret; + u32 val, cond; + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + regmap_set_bits(priv->phy, lane->phy_off + HSIO_CTRL0, HSIO_APB_RSTN_0); + regmap_set_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL0, + HSIO_EPCS_TXDEEMP); + regmap_set_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL0, + HSIO_EPCS_TXDEEMP_SEL); + + imx_hsio_sata_phy_resets(phy); + + cond = HSIO_REG48_PMA_RDY; + ret = read_poll_timeout(readb, val, ((val & cond) == cond), + PHY_INIT_WAIT_USLEEP_MAX, + PHY_INIT_WAIT_TIMEOUT, false, + priv->base + HSIO_REG48_PMA_STATUS); + if (ret) + dev_err(priv->dev, "PHY calibration is timeout\n"); + else + dev_dbg(priv->dev, "PHY calibration is done\n"); + + return ret; +} + +static int imx_hsio_power_on(struct phy *phy) +{ + int ret; + u32 val, cond; + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + scoped_guard(mutex, &priv->lock) { + if (!priv->open_cnt) + imx_hsio_pre_set(phy); + priv->open_cnt++; + } + + if (lane->phy_mode == PHY_MODE_PCIE) + ret = imx_hsio_pcie_power_on(phy); + else /* SATA */ + ret = imx_hsio_sata_power_on(phy); + if (ret) + return ret; + + /* Polling to check the PHY is ready or not. */ + if (lane->idx == 1) + cond = HSIO_LANE1_TX_PLL_LOCK; + else + /* + * Except the phy_off, the bit-offset of lane2 is same to lane0. + * Merge the lane0 and lane2 bit-operations together. + */ + cond = HSIO_LANE0_TX_PLL_LOCK; + + ret = regmap_read_poll_timeout(priv->phy, lane->phy_off + HSIO_PHY_STS0, + val, ((val & cond) == cond), + PHY_INIT_WAIT_USLEEP_MAX, + PHY_INIT_WAIT_TIMEOUT); + if (ret) { + dev_err(priv->dev, "IMX8Q PHY%d PLL lock timeout\n", lane->idx); + return ret; + } + dev_dbg(priv->dev, "IMX8Q PHY%d PLL is locked\n", lane->idx); + + return ret; +} + +static int imx_hsio_power_off(struct phy *phy) +{ + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + scoped_guard(mutex, &priv->lock) { + priv->open_cnt--; + if (priv->open_cnt == 0) { + regmap_clear_bits(priv->misc, HSIO_CTRL0, + HSIO_PCIE_AB_SELECT); + regmap_clear_bits(priv->misc, HSIO_CTRL0, + HSIO_PHYX1_EPCS_SEL); + + if (lane->phy_mode == PHY_MODE_PCIE) { + regmap_clear_bits(priv->ctrl, + lane->ctrl_off + HSIO_CTRL2, + HSIO_BUTTON_RST_N); + regmap_clear_bits(priv->ctrl, + lane->ctrl_off + HSIO_CTRL2, + HSIO_PERST_N); + regmap_clear_bits(priv->ctrl, + lane->ctrl_off + HSIO_CTRL2, + HSIO_POWER_UP_RST_N); + } else { + regmap_clear_bits(priv->ctrl, + lane->ctrl_off + HSIO_CTRL0, + HSIO_EPCS_TXDEEMP); + regmap_clear_bits(priv->ctrl, + lane->ctrl_off + HSIO_CTRL0, + HSIO_EPCS_TXDEEMP_SEL); + regmap_clear_bits(priv->ctrl, + lane->ctrl_off + HSIO_CTRL0, + HSIO_RESET_N); + } + + if (lane->idx == 1) { + regmap_clear_bits(priv->phy, + lane->phy_off + HSIO_CTRL0, + HSIO_APB_RSTN_1); + regmap_clear_bits(priv->phy, + lane->phy_off + HSIO_CTRL0, + HSIO_PIPE_RSTN_1_MASK); + } else { + /* + * Except the phy_off, the bit-offset of lane2 is same + * to lane0. Merge the lane0 and lane2 bit-operations + * together. + */ + regmap_clear_bits(priv->phy, + lane->phy_off + HSIO_CTRL0, + HSIO_APB_RSTN_0); + regmap_clear_bits(priv->phy, + lane->phy_off + HSIO_CTRL0, + HSIO_PIPE_RSTN_0_MASK); + } + } + } + + return 0; +} + +static int imx_hsio_set_mode(struct phy *phy, enum phy_mode mode, + int submode) +{ + u32 val; + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + if (lane->phy_mode != mode) + return -EINVAL; + + val = (mode == PHY_MODE_PCIE) ? HSIO_MODE_PCIE : HSIO_MODE_SATA; + val = FIELD_PREP(HSIO_MODE_MASK, val); + regmap_update_bits(priv->phy, lane->phy_off + HSIO_CTRL0, + HSIO_MODE_MASK, val); + + switch (submode) { + case PHY_MODE_PCIE_RC: + val = FIELD_PREP(HSIO_DEVICE_TYPE_MASK, PCI_EXP_TYPE_ROOT_PORT); + break; + case PHY_MODE_PCIE_EP: + val = FIELD_PREP(HSIO_DEVICE_TYPE_MASK, PCI_EXP_TYPE_ENDPOINT); + break; + default: /* Support only PCIe EP and RC now. */ + return 0; + } + if (submode) + regmap_update_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL0, + HSIO_DEVICE_TYPE_MASK, val); + + return 0; +} + +static int imx_hsio_set_speed(struct phy *phy, int speed) +{ + struct imx_hsio_lane *lane = phy_get_drvdata(phy); + struct imx_hsio_priv *priv = lane->priv; + + regmap_update_bits(priv->ctrl, lane->ctrl_off + HSIO_CTRL2, + HSIO_LTSSM_ENABLE, + speed ? HSIO_LTSSM_ENABLE : 0); + return 0; +} + +static const struct phy_ops imx_hsio_ops = { + .init = imx_hsio_init, + .exit = imx_hsio_exit, + .power_on = imx_hsio_power_on, + .power_off = imx_hsio_power_off, + .set_mode = imx_hsio_set_mode, + .set_speed = imx_hsio_set_speed, + .owner = THIS_MODULE, +}; + +static const struct imx_hsio_drvdata imx8qxp_hsio_drvdata = { + .lane_num = 0x1, +}; + +static const struct imx_hsio_drvdata imx8qm_hsio_drvdata = { + .lane_num = 0x3, +}; + +static const struct of_device_id imx_hsio_of_match[] = { + {.compatible = "fsl,imx8qm-hsio", .data = &imx8qm_hsio_drvdata}, + {.compatible = "fsl,imx8qxp-hsio", .data = &imx8qxp_hsio_drvdata}, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_hsio_of_match); + +static struct phy *imx_hsio_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct imx_hsio_priv *priv = dev_get_drvdata(dev); + int idx = args->args[0]; + int phy_type = args->args[1]; + int ctrl_index = args->args[2]; + + if (idx < 0 || idx >= priv->drvdata->lane_num) + return ERR_PTR(-EINVAL); + priv->lane[idx].idx = idx; + priv->lane[idx].phy_type = phy_type; + priv->lane[idx].ctrl_index = ctrl_index; + + return priv->lane[idx].phy; +} + +static int imx_hsio_probe(struct platform_device *pdev) +{ + int i, ret; + void __iomem *off; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct imx_hsio_priv *priv; + struct phy_provider *provider; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev = &pdev->dev; + priv->drvdata = of_device_get_match_data(dev); + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + + /* Get HSIO configuration mode */ + if (of_property_read_string(np, "fsl,hsio-cfg", &priv->hsio_cfg)) + priv->hsio_cfg = "pciea-pcieb-sata"; + /* Get PHY refclk pad mode */ + if (of_property_read_string(np, "fsl,refclk-pad-mode", + &priv->refclk_pad)) + priv->refclk_pad = NULL; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + off = devm_platform_ioremap_resource_byname(pdev, "phy"); + priv->phy = devm_regmap_init_mmio(dev, off, ®map_phy_config); + if (IS_ERR(priv->phy)) + return dev_err_probe(dev, PTR_ERR(priv->phy), + "unable to find phy csr registers\n"); + + off = devm_platform_ioremap_resource_byname(pdev, "ctrl"); + priv->ctrl = devm_regmap_init_mmio(dev, off, ®map_ctrl_config); + if (IS_ERR(priv->ctrl)) + return dev_err_probe(dev, PTR_ERR(priv->ctrl), + "unable to find ctrl csr registers\n"); + + off = devm_platform_ioremap_resource_byname(pdev, "misc"); + priv->misc = devm_regmap_init_mmio(dev, off, ®map_misc_config); + if (IS_ERR(priv->misc)) + return dev_err_probe(dev, PTR_ERR(priv->misc), + "unable to find misc csr registers\n"); + + for (i = 0; i < priv->drvdata->lane_num; i++) { + struct imx_hsio_lane *lane = &priv->lane[i]; + struct phy *phy; + + phy = devm_phy_create(&pdev->dev, NULL, &imx_hsio_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + lane->priv = priv; + lane->phy = phy; + lane->idx = i; + phy_set_drvdata(phy, lane); + } + + dev_set_drvdata(dev, priv); + dev_set_drvdata(&pdev->dev, priv); + + provider = devm_of_phy_provider_register(&pdev->dev, imx_hsio_xlate); + + return PTR_ERR_OR_ZERO(provider); +} + +static struct platform_driver imx_hsio_driver = { + .probe = imx_hsio_probe, + .driver = { + .name = "imx8qm-hsio-phy", + .of_match_table = imx_hsio_of_match, + } +}; +module_platform_driver(imx_hsio_driver); + +MODULE_DESCRIPTION("FSL IMX8QM HSIO SERDES 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 000000000..368679436 --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-imx8qm-lvds-phy.c @@ -0,0 +1,436 @@ +// 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); + + pm_runtime_put_sync(dev); + + return 0; +} + +static struct phy *mixel_lvds_phy_xlate(struct device *dev, + const 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); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + 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) + return -ENOMEM; + + 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); + return ret; + } + + 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); + return ret; + } + + return 0; +} + +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, + .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-10g.c b/drivers/phy/freescale/phy-fsl-lynx-10g.c new file mode 100644 index 000000000..54717465f --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-lynx-10g.c @@ -0,0 +1,1326 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright 2021-2026 NXP */ + +#include <linux/delay.h> +#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> +#include <linux/fsl/guts.h> + +#include "phy-fsl-lynx-core.h" + +/* SoC IP wrapper for protocol converters */ +#define PCCR8 0x220 +#define PCCR8_SGMIIa_KX BIT(3) +#define PCCR8_SGMIIa_CFG GENMASK(2, 0) + +#define PCCR9 0x224 +#define PCCR9_QSGMIIa_CFG GENMASK(2, 0) +#define PCCR9_QXGMIIa_CFG GENMASK(2, 0) + +#define PCCRB 0x22c +#define PCCRB_XFIa_CFG GENMASK(2, 0) +#define PCCRB_SXGMIIa_CFG GENMASK(2, 0) + +#define SGMII_CFG(id) (28 - (id) * 4) +#define QSGMII_CFG(id) (28 - (id) * 4) +#define SXGMII_CFG(id) (28 - (id) * 4) +#define QXGMII_CFG(id) (12 - (id) * 4) +#define XFI_CFG(id) (28 - (id) * 4) + +#define CR(x) ((x) * 4) + +#define A 0 +#define B 1 +#define C 2 +#define D 3 +#define E 4 +#define F 5 +#define G 6 +#define H 7 + +#define SGMIIaCR0(id) (0x1800 + (id) * 0x10) +#define QSGMIIaCR0(id) (0x1880 + (id) * 0x10) +#define XAUIaCR0(id) (0x1900 + (id) * 0x10) +#define XFIaCR0(id) (0x1980 + (id) * 0x10) +#define SXGMIIaCR0(id) (0x1a80 + (id) * 0x10) +#define QXGMIIaCR0(id) (0x1b00 + (id) * 0x20) + +#define SGMIIaCR0_RST_SGM BIT(31) +#define SGMIIaCR0_RST_SGM_OFF SGMIIaCR0_RST_SGM +#define SGMIIaCR0_RST_SGM_ON 0 +#define SGMIIaCR0_PD_SGM BIT(30) +#define SGMIIaCR1_SGPCS_EN BIT(11) +#define SGMIIaCR1_SGPCS_DIS 0x0 + +#define QSGMIIaCR0_RST_QSGM BIT(31) +#define QSGMIIaCR0_RST_QSGM_OFF QSGMIIaCR0_RST_QSGM +#define QSGMIIaCR0_RST_QSGM_ON 0 +#define QSGMIIaCR0_PD_QSGM BIT(30) + +/* Per PLL registers */ +#define PLLnCR0(pll) ((pll) * 0x20 + 0x4) + +#define PLLnCR0_POFF BIT(31) + +#define PLLnCR0_REFCLK_SEL GENMASK(30, 28) +#define PLLnCR0_REFCLK_SEL_100MHZ 0x0 +#define PLLnCR0_REFCLK_SEL_125MHZ 0x1 +#define PLLnCR0_REFCLK_SEL_156MHZ 0x2 +#define PLLnCR0_REFCLK_SEL_150MHZ 0x3 +#define PLLnCR0_REFCLK_SEL_161MHZ 0x4 +#define PLLnCR0_PLL_LCK BIT(23) +#define PLLnCR0_FRATE_SEL GENMASK(19, 16) +#define PLLnCR0_FRATE_5G 0x0 +#define PLLnCR0_FRATE_5_15625G 0x6 +#define PLLnCR0_FRATE_4G 0x7 +#define PLLnCR0_FRATE_3_125G 0x9 +#define PLLnCR0_FRATE_3G 0xa + +/* Per SerDes lane registers */ + +/* Lane a Protocol Select status register */ +#define LNaPSSR0(lane) (0x100 + (lane) * 0x20) +#define LNaPSSR0_TYPE GENMASK(30, 26) +#define LNaPSSR0_IS_QUAD GENMASK(25, 24) +#define LNaPSSR0_MAC GENMASK(19, 16) +#define LNaPSSR0_PCS GENMASK(10, 8) +#define LNaPSSR0_LANE GENMASK(2, 0) + +/* Lane a General Control Register */ +#define LNaGCR0(lane) (0x800 + (lane) * 0x40 + 0x0) +#define LNaGCR0_RPLL_PLLF BIT(31) +#define LNaGCR0_RPLL_PLLS 0x0 +#define LNaGCR0_RPLL_MSK BIT(31) +#define LNaGCR0_RRAT_SEL GENMASK(29, 28) +#define LNaGCR0_TRAT_SEL GENMASK(25, 24) +#define LNaGCR0_TPLL_PLLF BIT(27) +#define LNaGCR0_TPLL_PLLS 0x0 +#define LNaGCR0_TPLL_MSK BIT(27) +#define LNaGCR0_RRST_OFF LNaGCR0_RRST +#define LNaGCR0_TRST_OFF LNaGCR0_TRST +#define LNaGCR0_RRST_ON 0x0 +#define LNaGCR0_TRST_ON 0x0 +#define LNaGCR0_RRST BIT(22) +#define LNaGCR0_TRST BIT(21) +#define LNaGCR0_RX_PD BIT(20) +#define LNaGCR0_TX_PD BIT(19) +#define LNaGCR0_IF20BIT_EN BIT(18) +#define LNaGCR0_PROTS GENMASK(11, 7) + +#define LNaGCR1(lane) (0x800 + (lane) * 0x40 + 0x4) +#define LNaGCR1_RDAT_INV BIT(31) +#define LNaGCR1_TDAT_INV BIT(30) +#define LNaGCR1_OPAD_CTL BIT(26) +#define LNaGCR1_REIDL_TH GENMASK(22, 20) +#define LNaGCR1_REIDL_EX_SEL GENMASK(19, 18) +#define LNaGCR1_REIDL_ET_SEL GENMASK(17, 16) +#define LNaGCR1_REIDL_EX_MSB BIT(15) +#define LNaGCR1_REIDL_ET_MSB BIT(14) +#define LNaGCR1_REQ_CTL_SNP BIT(13) +#define LNaGCR1_REQ_CDR_SNP BIT(12) +#define LNaGCR1_TRSTDIR BIT(7) +#define LNaGCR1_REQ_BIN_SNP BIT(6) +#define LNaGCR1_ISLEW_RCTL GENMASK(5, 4) +#define LNaGCR1_OSLEW_RCTL GENMASK(1, 0) + +#define LNaRECR0(lane) (0x800 + (lane) * 0x40 + 0x10) +#define LNaRECR0_RXEQ_BST BIT(28) +#define LNaRECR0_GK2OVD GENMASK(27, 24) +#define LNaRECR0_GK3OVD GENMASK(19, 16) +#define LNaRECR0_GK2OVD_EN BIT(15) +#define LNaRECR0_GK3OVD_EN BIT(14) +#define LNaRECR0_OSETOVD_EN BIT(13) +#define LNaRECR0_BASE_WAND GENMASK(11, 10) +#define LNaRECR0_OSETOVD GENMASK(6, 0) + +#define LNaTECR0(lane) (0x800 + (lane) * 0x40 + 0x18) +#define LNaTECR0_TEQ_TYPE GENMASK(29, 28) +#define LNaTECR0_SGN_PREQ BIT(26) +#define LNaTECR0_RATIO_PREQ GENMASK(25, 22) +#define LNaTECR0_SGN_POST1Q BIT(21) +#define LNaTECR0_RATIO_PST1Q GENMASK(20, 16) +#define LNaTECR0_ADPT_EQ GENMASK(13, 8) +#define LNaTECR0_AMP_RED GENMASK(5, 0) + +#define LNaTTLCR0(lane) (0x800 + (lane) * 0x40 + 0x20) +#define LNaTTLCR1(lane) (0x800 + (lane) * 0x40 + 0x24) +#define LNaTTLCR2(lane) (0x800 + (lane) * 0x40 + 0x28) + +#define LNaTCSR3(lane) (0x800 + (lane) * 0x40 + 0x3C) +#define LNaTCSR3_CDR_LCK BIT(27) + +enum lynx_10g_rat_sel { + RAT_SEL_FULL = 0x0, + RAT_SEL_HALF = 0x1, + RAT_SEL_QUARTER = 0x2, + RAT_SEL_DOUBLE = 0x3, +}; + +enum lynx_10g_eq_type { + EQ_TYPE_NO_EQ = 0, + EQ_TYPE_2TAP = 1, + EQ_TYPE_3TAP = 2, +}; + +enum lynx_10g_proto_sel { + PROTO_SEL_PCIE = 0, + PROTO_SEL_SGMII_BASEX_KX_QSGMII = 1, + PROTO_SEL_SATA = 2, + PROTO_SEL_XAUI = 4, + PROTO_SEL_XFI_10GBASER_KR_SXGMII = 0xa, +}; + +struct lynx_10g_proto_conf { + int proto_sel; + int if20bit_en; + int reidl_th; + int reidl_et_msb; + int reidl_et_sel; + int reidl_ex_msb; + int reidl_ex_sel; + int islew_rctl; + int oslew_rctl; + int rxeq_bst; + int gk2ovd; + int gk3ovd; + int gk2ovd_en; + int gk3ovd_en; + int base_wand; + int teq_type; + int sgn_preq; + int ratio_preq; + int sgn_post1q; + int ratio_post1q; + int adpt_eq; + int amp_red; + int ttlcr0; +}; + +static const struct lynx_10g_proto_conf lynx_10g_proto_conf[LANE_MODE_MAX] = { + [LANE_MODE_1000BASEX_SGMII] = { + .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII, + .reidl_th = 1, + .reidl_ex_sel = 3, + .reidl_et_msb = 1, + .islew_rctl = 1, + .oslew_rctl = 1, + .gk2ovd = 15, + .gk3ovd = 15, + .gk2ovd_en = 1, + .gk3ovd_en = 1, + .teq_type = EQ_TYPE_NO_EQ, + .adpt_eq = 48, + .amp_red = 6, + .ttlcr0 = 0x39000400, + }, + [LANE_MODE_2500BASEX] = { + .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII, + .islew_rctl = 2, + .oslew_rctl = 2, + .teq_type = EQ_TYPE_2TAP, + .sgn_post1q = 1, + .ratio_post1q = 6, + .adpt_eq = 48, + .ttlcr0 = 0x00000400, + }, + [LANE_MODE_QSGMII] = { + .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII, + .islew_rctl = 1, + .oslew_rctl = 1, + .teq_type = EQ_TYPE_2TAP, + .sgn_post1q = 1, + .ratio_post1q = 6, + .adpt_eq = 48, + .amp_red = 2, + .ttlcr0 = 0x00000400, + }, + [LANE_MODE_10G_QXGMII] = { + .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII, + .if20bit_en = 1, + .islew_rctl = 1, + .oslew_rctl = 1, + .base_wand = 1, + .teq_type = EQ_TYPE_NO_EQ, + .adpt_eq = 48, + .ttlcr0 = 0x00000400, + }, + [LANE_MODE_USXGMII] = { + .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII, + .if20bit_en = 1, + .islew_rctl = 1, + .oslew_rctl = 1, + .base_wand = 1, + .teq_type = EQ_TYPE_NO_EQ, + .sgn_post1q = 1, + .adpt_eq = 48, + .ttlcr0 = 0x00000400, + }, + [LANE_MODE_10GBASER] = { + .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII, + .if20bit_en = 1, + .islew_rctl = 2, + .oslew_rctl = 2, + .rxeq_bst = 1, + .base_wand = 1, + .teq_type = EQ_TYPE_2TAP, + .sgn_post1q = 1, + .ratio_post1q = 3, + .adpt_eq = 48, + .amp_red = 7, + .ttlcr0 = 0x00000400, + }, +}; + +static void lynx_10g_cdr_lock_check(struct lynx_lane *lane) +{ + u32 tcsr3 = lynx_lane_read(lane, LNaTCSR3); + + if (tcsr3 & LNaTCSR3_CDR_LCK) + return; + + dev_dbg(&lane->phy->dev, + "Lane %c CDR unlocked, resetting receiver...\n", + 'A' + lane->id); + + lynx_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_ON, LNaGCR0_RRST); + usleep_range(1, 2); + lynx_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_OFF, LNaGCR0_RRST); + + usleep_range(1, 2); +} + +static void lynx_10g_pll_read_configuration(struct lynx_pll *pll) +{ + u32 val; + + val = lynx_pll_read(pll, PLLnCR0); + pll->frate_sel = FIELD_GET(PLLnCR0_FRATE_SEL, val); + pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val); + pll->enabled = !(val & PLLnCR0_POFF); + pll->locked = !!(val & PLLnCR0_PLL_LCK); + + if (!pll->enabled) + return; + + switch (pll->frate_sel) { + case PLLnCR0_FRATE_5G: + /* 5GHz clock net */ + __set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported); + __set_bit(LANE_MODE_QSGMII, pll->supported); + break; + case PLLnCR0_FRATE_3_125G: + __set_bit(LANE_MODE_2500BASEX, pll->supported); + break; + case PLLnCR0_FRATE_5_15625G: + /* 10.3125GHz clock net */ + __set_bit(LANE_MODE_10GBASER, pll->supported); + __set_bit(LANE_MODE_USXGMII, pll->supported); + __set_bit(LANE_MODE_10G_QXGMII, pll->supported); + break; + default: + break; + } +} + +/* On LS1028A, SGMIIA_CFG, SGMIIB_CFG, and SGMIIC_CFG from PCCR8 have the + * ability to map either an ENETC PCS (PCCR8_SGMIIa_CFG=2) or a Felix switch + * PCS (PCCR8_SGMIIa_CFG=1) to the same lane. + * + * On LS1088A, the same QSGMII PCS B can be connected to SerDes lane 1 + * (PCCR9_QSGMIIa_CFG=1) or to lane 3 (PCCR9_QSGMIIa_CFG=2). + * + * The PHY API lacks the capability to distinguish anything about the consumer, + * so we don't support changing the initial muxing done by the RCW. + * + * However, after disabling a PCS through PCCR8, we need to properly restore + * the original value to keep the same muxing, and for that we need to back + * it up (here). + */ +static void lynx_10g_backup_pccr_val(struct lynx_lane *lane) +{ + u32 val; + int err; + + if (lane->mode == LANE_MODE_UNKNOWN) + return; + + err = lynx_pccr_read(lane, lane->mode, &val); + if (err) { + dev_warn(&lane->phy->dev, + "The driver doesn't know how to access the PCCR for lane mode %s\n", + lynx_lane_mode_str(lane->mode)); + lane->mode = LANE_MODE_UNKNOWN; + return; + } + + lane->default_pccr[lane->mode] = val; + + /* 1000Base-X, 1000Base-KX, 2500Base-KX and SGMII use the same PCCR8. + * Only the KX bit differs (set for 1000Base-KX). Since we back up PCCR + * values per lane mode, make sure to not back up the PCCR8 value with + * the KX bit set for the non-KX modes, if the lane was in KX mode at + * boot time. Just preserve bits 2:0, which tell whether the (and + * which) 1G PCS was enabled. + */ + switch (lane->mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + lane->default_pccr[LANE_MODE_1000BASEX_SGMII] = val & ~PCCR8_SGMIIa_KX; + lane->default_pccr[LANE_MODE_2500BASEX] = val & ~PCCR8_SGMIIa_KX; + break; + default: + break; + } +} + +/* Is the PCS enabled, according to the value backed up from the PCCR register + * for this lane mode? + * + * Normally we'd need to ask "what lane mode are we talking about?", but the + * answer is invariably the same regardless - PCCR8_SGMIIa_CFG has the same + * layout as PCCR9_QSGMIIa_CFG, PCCRB_XFIa_CFG etc etc, and the value 0 + * universally means "PCS disabled". So this is just a shorthand answer. + */ +static bool lynx_10g_pccr_val_enabled(u32 pccr) +{ + return FIELD_GET(PCCR8_SGMIIa_CFG, pccr) != 0; +} + +static bool lynx_10g_lane_is_3_125g(struct lynx_lane *lane) +{ + struct lynx_priv *priv = lane->priv; + struct lynx_pll *pll; + u32 gcr0; + + gcr0 = lynx_lane_read(lane, LNaGCR0); + + if (gcr0 & LNaGCR0_TPLL_PLLF) + pll = &priv->pll[0]; + else + pll = &priv->pll[1]; + + if (pll->frate_sel != PLLnCR0_FRATE_3_125G) + return false; + + if (FIELD_GET(LNaGCR0_TRAT_SEL, gcr0) != RAT_SEL_FULL || + FIELD_GET(LNaGCR0_RRAT_SEL, gcr0) != RAT_SEL_FULL) + return false; + + return true; +} + +static void lynx_10g_lane_read_configuration(struct lynx_lane *lane) +{ + u32 pssr0 = lynx_lane_read(lane, LNaPSSR0); + struct lynx_priv *priv = lane->priv; + int proto; + + proto = FIELD_GET(LNaPSSR0_TYPE, pssr0); + switch (proto) { + case PROTO_SEL_SGMII_BASEX_KX_QSGMII: + if (lynx_10g_lane_is_3_125g(lane)) + lane->mode = LANE_MODE_2500BASEX; + else if (FIELD_GET(LNaPSSR0_IS_QUAD, pssr0)) + lane->mode = LANE_MODE_QSGMII; + else + lane->mode = LANE_MODE_1000BASEX_SGMII; + break; + case PROTO_SEL_XFI_10GBASER_KR_SXGMII: + if (FIELD_GET(LNaPSSR0_IS_QUAD, pssr0)) + lane->mode = LANE_MODE_10G_QXGMII; + else if (priv->info->quirks & LYNX_QUIRK_HAS_HARDCODED_USXGMII) + lane->mode = LANE_MODE_USXGMII; + else + lane->mode = LANE_MODE_10GBASER; + break; + case PROTO_SEL_PCIE: + case PROTO_SEL_SATA: + case PROTO_SEL_XAUI: + break; + default: + dev_warn(&lane->phy->dev, "Unknown lane protocol 0x%x\n", + proto); + } + + lynx_10g_backup_pccr_val(lane); +} + +static int ls1028a_get_pccr(enum lynx_lane_mode lane_mode, int lane, + struct lynx_pccr *pccr) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + pccr->offset = PCCR8; + pccr->width = 4; + pccr->shift = SGMII_CFG(lane); + break; + case LANE_MODE_QSGMII: + if (lane != 1) + return -EINVAL; + + pccr->offset = PCCR9; + pccr->width = 3; + pccr->shift = QSGMII_CFG(A); + break; + case LANE_MODE_10G_QXGMII: + if (lane != 1) + return -EINVAL; + + pccr->offset = PCCR9; + pccr->width = 3; + pccr->shift = QXGMII_CFG(A); + break; + case LANE_MODE_USXGMII: + if (lane != 0) + return -EINVAL; + + pccr->offset = PCCRB; + pccr->width = 3; + pccr->shift = SXGMII_CFG(A); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ls1028a_get_pcvt_offset(int lane, enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + return SGMIIaCR0(lane); + case LANE_MODE_QSGMII: + return lane == 1 ? QSGMIIaCR0(A) : -EINVAL; + case LANE_MODE_USXGMII: + return lane == 0 ? SXGMIIaCR0(A) : -EINVAL; + case LANE_MODE_10G_QXGMII: + return lane == 1 ? QXGMIIaCR0(A) : -EINVAL; + default: + return -EINVAL; + } +} + +static const struct lynx_info lynx_info_ls1028a = { + .get_pccr = ls1028a_get_pccr, + .get_pcvt_offset = ls1028a_get_pcvt_offset, + .pll_read_configuration = lynx_10g_pll_read_configuration, + .lane_read_configuration = lynx_10g_lane_read_configuration, + .cdr_lock_check = lynx_10g_cdr_lock_check, + .num_lanes = 4, + .index = 1, + .quirks = LYNX_QUIRK_HAS_HARDCODED_USXGMII, +}; + +static int ls1046a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane, + struct lynx_pccr *pccr) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + pccr->offset = PCCR8; + pccr->width = 4; + pccr->shift = SGMII_CFG(lane); + break; + case LANE_MODE_QSGMII: + if (lane != 1) + return -EINVAL; + + pccr->offset = PCCR9; + pccr->width = 3; + pccr->shift = QSGMII_CFG(B); + break; + case LANE_MODE_10GBASER: + switch (lane) { + case 2: + pccr->shift = XFI_CFG(A); + break; + case 3: + pccr->shift = XFI_CFG(B); + break; + default: + return -EINVAL; + } + + pccr->offset = PCCRB; + pccr->width = 3; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ls1046a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + return SGMIIaCR0(lane); + case LANE_MODE_QSGMII: + if (lane != 1) + return -EINVAL; + + return QSGMIIaCR0(B); + case LANE_MODE_10GBASER: + switch (lane) { + case 2: + return XFIaCR0(A); + case 3: + return XFIaCR0(B); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct lynx_info lynx_info_ls1046a_serdes1 = { + .get_pccr = ls1046a_serdes1_get_pccr, + .get_pcvt_offset = ls1046a_serdes1_get_pcvt_offset, + .pll_read_configuration = lynx_10g_pll_read_configuration, + .lane_read_configuration = lynx_10g_lane_read_configuration, + .cdr_lock_check = lynx_10g_cdr_lock_check, + .num_lanes = 4, + .index = 1, +}; + +static int ls1046a_serdes2_get_pccr(enum lynx_lane_mode lane_mode, int lane, + struct lynx_pccr *pccr) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + if (lane != 1) + return -EINVAL; + + pccr->offset = PCCR8; + pccr->width = 4; + pccr->shift = SGMII_CFG(B); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ls1046a_serdes2_get_pcvt_offset(int lane, enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + if (lane != 1) + return -EINVAL; + + return SGMIIaCR0(B); + default: + return -EINVAL; + } +} + +static const struct lynx_info lynx_info_ls1046a_serdes2 = { + .get_pccr = ls1046a_serdes2_get_pccr, + .get_pcvt_offset = ls1046a_serdes2_get_pcvt_offset, + .pll_read_configuration = lynx_10g_pll_read_configuration, + .lane_read_configuration = lynx_10g_lane_read_configuration, + .cdr_lock_check = lynx_10g_cdr_lock_check, + .num_lanes = 4, + .index = 2, +}; + +static int ls1088a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane, + struct lynx_pccr *pccr) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + pccr->offset = PCCR8; + pccr->width = 4; + pccr->shift = SGMII_CFG(lane); + break; + case LANE_MODE_QSGMII: + switch (lane) { + case 0: + pccr->shift = QSGMII_CFG(A); + break; + case 1: + case 3: + pccr->shift = QSGMII_CFG(B); + break; + default: + return -EINVAL; + } + + pccr->offset = PCCR9; + pccr->width = 3; + break; + case LANE_MODE_10GBASER: + switch (lane) { + case 2: + pccr->shift = XFI_CFG(A); + break; + case 3: + pccr->shift = XFI_CFG(B); + break; + default: + return -EINVAL; + } + + pccr->offset = PCCRB; + pccr->width = 3; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ls1088a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + return SGMIIaCR0(lane); + case LANE_MODE_QSGMII: + switch (lane) { + case 0: + return QSGMIIaCR0(A); + case 1: + case 3: + return QSGMIIaCR0(B); + default: + return -EINVAL; + } + case LANE_MODE_10GBASER: + switch (lane) { + case 2: + return XFIaCR0(A); + case 3: + return XFIaCR0(B); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct lynx_info lynx_info_ls1088a_serdes1 = { + .get_pccr = ls1088a_serdes1_get_pccr, + .get_pcvt_offset = ls1088a_serdes1_get_pcvt_offset, + .pll_read_configuration = lynx_10g_pll_read_configuration, + .lane_read_configuration = lynx_10g_lane_read_configuration, + .cdr_lock_check = lynx_10g_cdr_lock_check, + .num_lanes = 4, + .index = 1, +}; + +static int ls2088a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane, + struct lynx_pccr *pccr) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + pccr->offset = PCCR8; + pccr->width = 4; + pccr->shift = SGMII_CFG(lane); + break; + case LANE_MODE_QSGMII: + switch (lane) { + case 2: + case 6: + pccr->shift = QSGMII_CFG(A); + break; + case 7: + pccr->shift = QSGMII_CFG(B); + break; + case 0: + case 4: + pccr->shift = QSGMII_CFG(C); + break; + case 1: + case 5: + pccr->shift = QSGMII_CFG(D); + break; + default: + return -EINVAL; + } + + pccr->offset = PCCR9; + pccr->width = 3; + break; + case LANE_MODE_10GBASER: + pccr->offset = PCCRB; + pccr->width = 3; + pccr->shift = XFI_CFG(lane); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ls2088a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + return SGMIIaCR0(lane); + case LANE_MODE_QSGMII: + switch (lane) { + case 2: + case 6: + return QSGMIIaCR0(A); + case 7: + return QSGMIIaCR0(B); + case 0: + case 4: + return QSGMIIaCR0(C); + case 1: + case 5: + return QSGMIIaCR0(D); + default: + return -EINVAL; + } + case LANE_MODE_10GBASER: + return XFIaCR0(lane); + default: + return -EINVAL; + } +} + +static const struct lynx_info lynx_info_ls2088a_serdes1 = { + .get_pccr = ls2088a_serdes1_get_pccr, + .get_pcvt_offset = ls2088a_serdes1_get_pcvt_offset, + .pll_read_configuration = lynx_10g_pll_read_configuration, + .lane_read_configuration = lynx_10g_lane_read_configuration, + .cdr_lock_check = lynx_10g_cdr_lock_check, + .num_lanes = 8, + .index = 1, +}; + +static int ls2088a_serdes2_get_pccr(enum lynx_lane_mode lane_mode, int lane, + struct lynx_pccr *pccr) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + pccr->offset = PCCR8; + pccr->width = 4; + pccr->shift = SGMII_CFG(lane); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ls2088a_serdes2_get_pcvt_offset(int lane, enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + return SGMIIaCR0(lane); + default: + return -EINVAL; + } +} + +static const struct lynx_info lynx_info_ls2088a_serdes2 = { + .get_pccr = ls2088a_serdes2_get_pccr, + .get_pcvt_offset = ls2088a_serdes2_get_pcvt_offset, + .pll_read_configuration = lynx_10g_pll_read_configuration, + .lane_read_configuration = lynx_10g_lane_read_configuration, + .cdr_lock_check = lynx_10g_cdr_lock_check, + .num_lanes = 8, + .index = 2, +}; + +/* Halting puts the lane in a mode in which it can be reconfigured */ +static void lynx_10g_lane_halt(struct phy *phy) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + + /* Issue a reset request */ + lynx_lane_rmw(lane, LNaGCR0, + LNaGCR0_RRST_ON | LNaGCR0_TRST_ON, + LNaGCR0_RRST | LNaGCR0_TRST); + + /* The RM says to wait for at least 50ns */ + usleep_range(1, 2); +} + +static void lynx_10g_lane_reset(struct phy *phy) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + + /* Finalize the reset request */ + lynx_lane_rmw(lane, LNaGCR0, + LNaGCR0_RRST_OFF | LNaGCR0_TRST_OFF, + LNaGCR0_RRST | LNaGCR0_TRST); +} + +static int lynx_10g_power_off(struct phy *phy) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + + if (!lane->powered_up) + return 0; + + /* Issue a reset request with the power down bits set */ + lynx_lane_rmw(lane, LNaGCR0, + LNaGCR0_RRST_ON | LNaGCR0_TRST_ON | + LNaGCR0_RX_PD | LNaGCR0_TX_PD, + LNaGCR0_RRST | LNaGCR0_TRST | + LNaGCR0_RX_PD | LNaGCR0_TX_PD); + + /* The RM says to wait for at least 50ns */ + usleep_range(1, 2); + + lane->powered_up = false; + + return 0; +} + +static int lynx_10g_power_on(struct phy *phy) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + + if (lane->powered_up) + return 0; + + /* RM says that to enable a previously powered down lane, set + * LNmGCR0[{R,T}X_PD]=0, wait 15 us, then set LNmGCR0[{R,T}RST]=1. + */ + lynx_lane_rmw(lane, LNaGCR0, 0, LNaGCR0_RX_PD | LNaGCR0_TX_PD); + usleep_range(150, 300); + lynx_10g_lane_reset(phy); + + lane->powered_up = true; + + return 0; +} + +static void lynx_10g_lane_set_nrate(struct lynx_lane *lane, + struct lynx_pll *pll, + enum lynx_lane_mode mode) +{ + enum lynx_10g_rat_sel nrate; + + switch (pll->frate_sel) { + case PLLnCR0_FRATE_5G: + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + nrate = RAT_SEL_QUARTER; + break; + case LANE_MODE_QSGMII: + nrate = RAT_SEL_FULL; + break; + default: + return; + } + break; + case PLLnCR0_FRATE_3_125G: + switch (mode) { + case LANE_MODE_2500BASEX: + nrate = RAT_SEL_FULL; + break; + default: + return; + } + break; + case PLLnCR0_FRATE_5_15625G: + switch (mode) { + case LANE_MODE_10GBASER: + case LANE_MODE_USXGMII: + case LANE_MODE_10G_QXGMII: + nrate = RAT_SEL_DOUBLE; + break; + default: + return; + } + break; + default: + return; + } + + lynx_lane_rmw(lane, LNaGCR0, + FIELD_PREP(LNaGCR0_TRAT_SEL, nrate) | + FIELD_PREP(LNaGCR0_RRAT_SEL, nrate), + LNaGCR0_RRAT_SEL | LNaGCR0_TRAT_SEL); +} + +static void lynx_10g_lane_set_pll(struct lynx_lane *lane, + struct lynx_pll *pll) +{ + if (pll->id == 0) { + lynx_lane_rmw(lane, LNaGCR0, + LNaGCR0_RPLL_PLLF | LNaGCR0_TPLL_PLLF, + LNaGCR0_RPLL_MSK | LNaGCR0_TPLL_MSK); + } else { + lynx_lane_rmw(lane, LNaGCR0, + LNaGCR0_RPLL_PLLS | LNaGCR0_TPLL_PLLS, + LNaGCR0_RPLL_MSK | LNaGCR0_TPLL_MSK); + } +} + +static void lynx_10g_lane_remap_pll(struct lynx_lane *lane, + enum lynx_lane_mode lane_mode) +{ + struct lynx_priv *priv = lane->priv; + struct lynx_pll *pll; + + /* Switch to the PLL that works with this interface type */ + pll = lynx_pll_get(priv, lane_mode); + if (unlikely(!pll)) + return; + + lynx_10g_lane_set_pll(lane, pll); + + /* Choose the portion of clock net to be used on this lane */ + lynx_10g_lane_set_nrate(lane, pll, lane_mode); +} + +static void lynx_10g_lane_change_proto_conf(struct lynx_lane *lane, + enum lynx_lane_mode mode) +{ + const struct lynx_10g_proto_conf *conf = &lynx_10g_proto_conf[mode]; + + lynx_lane_rmw(lane, LNaGCR0, + FIELD_PREP(LNaGCR0_PROTS, conf->proto_sel) | + FIELD_PREP(LNaGCR0_IF20BIT_EN, conf->if20bit_en), + LNaGCR0_PROTS | LNaGCR0_IF20BIT_EN); + lynx_lane_rmw(lane, LNaGCR1, + FIELD_PREP(LNaGCR1_REIDL_TH, conf->reidl_th) | + FIELD_PREP(LNaGCR1_REIDL_ET_MSB, conf->reidl_et_msb) | + FIELD_PREP(LNaGCR1_REIDL_ET_SEL, conf->reidl_et_sel) | + FIELD_PREP(LNaGCR1_REIDL_EX_MSB, conf->reidl_ex_msb) | + FIELD_PREP(LNaGCR1_REIDL_EX_SEL, conf->reidl_ex_sel) | + FIELD_PREP(LNaGCR1_ISLEW_RCTL, conf->islew_rctl) | + FIELD_PREP(LNaGCR1_OSLEW_RCTL, conf->oslew_rctl), + LNaGCR1_REIDL_TH | + LNaGCR1_REIDL_ET_MSB | LNaGCR1_REIDL_ET_SEL | + LNaGCR1_REIDL_EX_MSB | LNaGCR1_REIDL_EX_SEL | + LNaGCR1_ISLEW_RCTL | LNaGCR1_OSLEW_RCTL); + lynx_lane_rmw(lane, LNaRECR0, + FIELD_PREP(LNaRECR0_RXEQ_BST, conf->rxeq_bst) | + FIELD_PREP(LNaRECR0_GK2OVD, conf->gk2ovd) | + FIELD_PREP(LNaRECR0_GK3OVD, conf->gk3ovd) | + FIELD_PREP(LNaRECR0_GK2OVD_EN, conf->gk2ovd_en) | + FIELD_PREP(LNaRECR0_GK3OVD_EN, conf->gk3ovd_en) | + FIELD_PREP(LNaRECR0_BASE_WAND, conf->base_wand), + LNaRECR0_RXEQ_BST | LNaRECR0_GK2OVD | LNaRECR0_GK3OVD | + LNaRECR0_GK2OVD_EN | LNaRECR0_GK3OVD_EN | + LNaRECR0_BASE_WAND); + lynx_lane_rmw(lane, LNaTECR0, + FIELD_PREP(LNaTECR0_TEQ_TYPE, conf->teq_type) | + FIELD_PREP(LNaTECR0_SGN_PREQ, conf->sgn_preq) | + FIELD_PREP(LNaTECR0_RATIO_PREQ, conf->ratio_preq) | + FIELD_PREP(LNaTECR0_SGN_POST1Q, conf->sgn_post1q) | + FIELD_PREP(LNaTECR0_RATIO_PST1Q, conf->ratio_post1q) | + FIELD_PREP(LNaTECR0_ADPT_EQ, conf->adpt_eq) | + FIELD_PREP(LNaTECR0_AMP_RED, conf->amp_red), + LNaTECR0_TEQ_TYPE | LNaTECR0_SGN_PREQ | + LNaTECR0_RATIO_PREQ | LNaTECR0_SGN_POST1Q | + LNaTECR0_RATIO_PST1Q | LNaTECR0_ADPT_EQ | + LNaTECR0_AMP_RED); + lynx_lane_write(lane, LNaTTLCR0, conf->ttlcr0); +} + +static int lynx_10g_lane_disable_pcvt(struct lynx_lane *lane, + enum lynx_lane_mode mode) +{ + struct lynx_priv *priv = lane->priv; + int err; + + spin_lock(&priv->pcc_lock); + + err = lynx_pccr_write(lane, mode, 0); + if (err) + goto out; + + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + err = lynx_pcvt_rmw(lane, mode, CR(1), SGMIIaCR1_SGPCS_DIS, + SGMIIaCR1_SGPCS_EN); + if (err) + goto out; + + lynx_pcvt_rmw(lane, mode, CR(0), + SGMIIaCR0_RST_SGM_ON | SGMIIaCR0_PD_SGM, + SGMIIaCR0_RST_SGM | SGMIIaCR0_PD_SGM); + break; + case LANE_MODE_QSGMII: + err = lynx_pcvt_rmw(lane, mode, CR(0), + QSGMIIaCR0_RST_QSGM_ON | QSGMIIaCR0_PD_QSGM, + QSGMIIaCR0_RST_QSGM | QSGMIIaCR0_PD_QSGM); + if (err) + goto out; + break; + default: + err = 0; + } + +out: + spin_unlock(&priv->pcc_lock); + + return err; +} + +static int lynx_10g_lane_enable_pcvt(struct lynx_lane *lane, + enum lynx_lane_mode mode) +{ + struct lynx_priv *priv = lane->priv; + u32 val; + int err; + + spin_lock(&priv->pcc_lock); + + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + err = lynx_pcvt_rmw(lane, mode, CR(1), SGMIIaCR1_SGPCS_EN, + SGMIIaCR1_SGPCS_EN); + if (err) + goto out; + + lynx_pcvt_rmw(lane, mode, CR(0), SGMIIaCR0_RST_SGM_OFF, + SGMIIaCR0_RST_SGM | SGMIIaCR0_PD_SGM); + break; + case LANE_MODE_QSGMII: + err = lynx_pcvt_rmw(lane, mode, CR(0), QSGMIIaCR0_RST_QSGM_OFF, + QSGMIIaCR0_RST_QSGM | QSGMIIaCR0_PD_QSGM); + if (err) + goto out; + break; + default: + err = 0; + } + + /* If the PCS was enabled at boot time, use the backed up PCCR value to + * re-enable it here, to preserve the muxing. + */ + if (lynx_10g_pccr_val_enabled(lane->default_pccr[mode])) { + err = lynx_pccr_write(lane, mode, lane->default_pccr[mode]); + goto out; + } + + /* If the PCS was not enabled, set the PCCR to a default value which + * enables it (1). The assumption is that this is the only PCS <-> + * SerDes lane muxing value possible. + * + * This is mostly useful for SGMII <-> 10GBase-R major protocol + * reconfiguration, where at boot time, either the SGMII or the + * 10GBase-R PCS is enabled for the lane, but not both. + * + * In fact, if there are multiple lane muxing options, this function + * will most likely not choose the right one. For correct functionality + * there, we assume that the PCS we are enabling here was found enabled + * at boot time (reset default, or through PBL, or...), and we preserve + * its muxing through the default_pccr branch above. + */ + val = 0; + + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_2500BASEX: + val |= FIELD_PREP(PCCR8_SGMIIa_CFG, 1); + break; + case LANE_MODE_QSGMII: + val |= FIELD_PREP(PCCR9_QSGMIIa_CFG, 1); + break; + case LANE_MODE_10G_QXGMII: + val |= FIELD_PREP(PCCR9_QXGMIIa_CFG, 1); + break; + case LANE_MODE_10GBASER: + val |= FIELD_PREP(PCCRB_XFIa_CFG, 1); + break; + case LANE_MODE_USXGMII: + val |= FIELD_PREP(PCCRB_SXGMIIa_CFG, 1); + break; + default: + err = 0; + goto out; + } + + err = lynx_pccr_write(lane, mode, val); +out: + spin_unlock(&priv->pcc_lock); + + return err; +} + +static bool lynx_10g_lane_mode_needs_rcw_override(struct lynx_lane *lane, + enum lynx_lane_mode new) +{ + enum lynx_lane_mode curr = lane->mode; + + /* Major protocol changes, which involve changing the PCS connection to + * the GMII MAC with the one to the XGMII MAC, require an RCW override + * procedure to reconfigure an internal mux. + */ + if ((lynx_lane_mode_uses_gmii_mac(curr) && + lynx_lane_mode_uses_xgmii_mac(new)) || + (lynx_lane_mode_uses_xgmii_mac(curr) && + lynx_lane_mode_uses_gmii_mac(new))) + return true; + + return false; +} + +static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + struct lynx_priv *priv = lane->priv; + enum lynx_lane_mode lane_mode; + int err; + + err = lynx_phy_mode_to_lane_mode(phy, mode, submode, &lane_mode); + if (err) + return err; + + if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode)) + return fsl_guts_lane_validate(priv->info->index, lane->id, + lane_mode); + + return 0; +} + +static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + struct lynx_priv *priv = lane->priv; + bool powered_up = lane->powered_up; + enum lynx_lane_mode lane_mode; + int err; + + err = lynx_10g_validate(phy, mode, submode, NULL); + if (err) + return err; + + lane_mode = phy_interface_to_lane_mode(submode); + /* lynx_10g_validate() already made sure the lane_mode is supported */ + + if (lane_mode == lane->mode) + return 0; + + /* If the lane is powered up, put the lane into the halt state while + * the reconfiguration is being done. + */ + if (powered_up) + lynx_10g_lane_halt(phy); + + if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode)) { + err = fsl_guts_lane_set_mode(priv->info->index, lane->id, + lane_mode); + if (err) + goto out; + } + + err = lynx_10g_lane_disable_pcvt(lane, lane->mode); + if (err) + goto out; + + lynx_10g_lane_change_proto_conf(lane, lane_mode); + lynx_10g_lane_remap_pll(lane, lane_mode); + WARN_ON(lynx_10g_lane_enable_pcvt(lane, lane_mode)); + + lane->mode = lane_mode; + +out: + if (powered_up) { + /* The RM says to wait for at least 120 ns */ + usleep_range(1, 2); + lynx_10g_lane_reset(phy); + } + + return err; +} + +static int lynx_10g_init(struct phy *phy) +{ + struct lynx_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 off when its consumer + * calls phy_init(). + */ + lane->powered_up = true; + lynx_10g_power_off(phy); + + return 0; +} + +static int lynx_10g_exit(struct phy *phy) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + + /* The lane returns to the state where it isn't managed by the + * consumer, so we must treat is as if it isn't initialized, and always + * powered on. + */ + lane->init = false; + lane->powered_up = false; + lynx_10g_power_on(phy); + + return 0; +} + +static const struct phy_ops lynx_10g_ops = { + .init = lynx_10g_init, + .exit = lynx_10g_exit, + .power_on = lynx_10g_power_on, + .power_off = lynx_10g_power_off, + .set_mode = lynx_10g_set_mode, + .validate = lynx_10g_validate, + .owner = THIS_MODULE, +}; + +static int lynx_10g_probe(struct platform_device *pdev) +{ + return lynx_probe(pdev, of_device_get_match_data(&pdev->dev), + &lynx_10g_ops); +} + +static const struct of_device_id lynx_10g_of_match_table[] = { + { .compatible = "fsl,ls1028a-serdes", .data = &lynx_info_ls1028a }, + { .compatible = "fsl,ls1046a-serdes1", .data = &lynx_info_ls1046a_serdes1 }, + { .compatible = "fsl,ls1046a-serdes2", .data = &lynx_info_ls1046a_serdes2 }, + { .compatible = "fsl,ls1088a-serdes1", .data = &lynx_info_ls1088a_serdes1 }, + { .compatible = "fsl,ls2088a-serdes1", .data = &lynx_info_ls2088a_serdes1 }, + { .compatible = "fsl,ls2088a-serdes2", .data = &lynx_info_ls2088a_serdes2 }, + {} +}; +MODULE_DEVICE_TABLE(of, lynx_10g_of_match_table); + +static struct platform_driver lynx_10g_driver = { + .probe = lynx_10g_probe, + .remove = lynx_remove, + .driver = { + .name = "lynx-10g", + .of_match_table = lynx_10g_of_match_table, + }, +}; +module_platform_driver(lynx_10g_driver); + +MODULE_IMPORT_NS("FSL_GUTS"); +MODULE_IMPORT_NS("PHY_FSL_LYNX"); +MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>"); +MODULE_AUTHOR("Vladimir Oltean <vladimir.oltean@nxp.com>"); +MODULE_DESCRIPTION("Lynx 10G SerDes PHY driver for Layerscape SoCs"); +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 000000000..38afcd081 --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-lynx-28g.c @@ -0,0 +1,1224 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (c) 2021-2022 NXP. */ + +#include <linux/bitfield.h> +#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> + +#include "phy-fsl-lynx-core.h" + +#define LYNX_28G_NUM_LANE 8 + +/* SoC IP wrapper for protocol converters */ +#define PCC8 0x10a0 +#define PCC8_SGMIIa_KX BIT(3) +#define PCC8_SGMIIa_CFG BIT(0) + +#define PCCC 0x10b0 +#define PCCC_SXGMIIn_XFI BIT(3) +#define PCCC_SXGMIIn_CFG BIT(0) + +#define PCCD 0x10b4 +#define PCCD_E25Gn_CFG BIT(0) + +#define PCCE 0x10b8 +#define PCCE_E40Gn_LRV BIT(3) +#define PCCE_E40Gn_CFG BIT(0) +#define PCCE_E50Gn_LRV BIT(3) +#define PCCE_E50GnCFG BIT(0) +#define PCCE_E100Gn_LRV BIT(3) +#define PCCE_E100Gn_CFG BIT(0) + +#define SGMII_CFG(id) (28 - (id) * 4) /* Offset into PCC8 */ +#define SXGMII_CFG(id) (28 - (id) * 4) /* Offset into PCCC */ +#define E25G_CFG(id) (28 - (id) * 4) /* Offset into PCCD */ +#define E40G_CFG(id) (28 - (id) * 4) /* Offset into PCCE */ +#define E50G_CFG(id) (20 - (id) * 4) /* Offset into PCCE */ +#define E100G_CFG(id) (12 - (id) * 4) /* Offset into PCCE */ + +/* Per PLL registers */ +#define PLLnRSTCTL(pll) (0x400 + (pll) * 0x100 + 0x0) +#define PLLnRSTCTL_DIS BIT(24) +#define PLLnRSTCTL_LOCK BIT(23) + +#define PLLnCR0(pll) (0x400 + (pll) * 0x100 + 0x4) +#define PLLnCR0_REFCLK_SEL GENMASK(20, 16) +#define PLLnCR0_REFCLK_SEL_100MHZ 0x0 +#define PLLnCR0_REFCLK_SEL_125MHZ 0x1 +#define PLLnCR0_REFCLK_SEL_156MHZ 0x2 +#define PLLnCR0_REFCLK_SEL_150MHZ 0x3 +#define PLLnCR0_REFCLK_SEL_161MHZ 0x4 + +#define PLLnCR1(pll) (0x400 + (pll) * 0x100 + 0x8) +#define PLLnCR1_FRATE_SEL GENMASK(28, 24) +#define PLLnCR1_FRATE_5G_10GVCO 0x0 +#define PLLnCR1_FRATE_5G_25GVCO 0x10 +#define PLLnCR1_FRATE_10G_20GVCO 0x6 +#define PLLnCR1_FRATE_12G_25GVCO 0x16 + +/* Per SerDes lane registers */ +/* Lane a General Control Register */ +#define LNaGCR0(lane) (0x800 + (lane) * 0x100 + 0x0) +#define LNaGCR0_PROTO_SEL GENMASK(7, 3) +#define LNaGCR0_PROTO_SEL_SGMII 0x1 +#define LNaGCR0_PROTO_SEL_XFI 0xa +#define LNaGCR0_PROTO_SEL_25G 0x1a +#define LNaGCR0_IF_WIDTH GENMASK(2, 0) +#define LNaGCR0_IF_WIDTH_10_BIT 0x0 +#define LNaGCR0_IF_WIDTH_20_BIT 0x2 +#define LNaGCR0_IF_WIDTH_40_BIT 0x4 + +/* Lane a Tx Reset Control Register */ +#define LNaTRSTCTL(lane) (0x800 + (lane) * 0x100 + 0x20) +#define LNaTRSTCTL_RST_REQ BIT(31) +#define LNaTRSTCTL_RST_DONE BIT(30) +#define LNaTRSTCTL_HLT_REQ BIT(27) +#define LNaTRSTCTL_STP_REQ BIT(26) +#define LNaTRSTCTL_DIS BIT(24) + +/* Lane a Tx General Control Register */ +#define LNaTGCR0(lane) (0x800 + (lane) * 0x100 + 0x24) +#define LNaTGCR0_USE_PLL BIT(28) +#define LNaTGCR0_USE_PLLF 0x0 +#define LNaTGCR0_USE_PLLS 0x1 +#define LNaTGCR0_N_RATE GENMASK(26, 24) +#define LNaTGCR0_N_RATE_FULL 0x0 +#define LNaTGCR0_N_RATE_HALF 0x1 +#define LNaTGCR0_N_RATE_QUARTER 0x2 +#define LNaTGCR0_N_RATE_DOUBLE 0x3 + +#define LNaTECR0(lane) (0x800 + (lane) * 0x100 + 0x30) +#define LNaTECR0_EQ_TYPE GENMASK(30, 28) +#define LNaTECR0_EQ_SGN_PREQ BIT(23) +#define LNaTECR0_EQ_PREQ GENMASK(19, 16) +#define LNaTECR0_EQ_SGN_POST1Q BIT(15) +#define LNaTECR0_EQ_POST1Q GENMASK(12, 8) +#define LNaTECR0_EQ_AMP_RED GENMASK(5, 0) + +#define LNaTECR1(lane) (0x800 + (lane) * 0x100 + 0x34) +#define LNaTECR1_EQ_ADPT_EQ_DRVR_DIS BIT(31) +#define LNaTECR1_EQ_ADPT_EQ GENMASK(29, 24) + +/* Lane a Rx Reset Control Register */ +#define LNaRRSTCTL(lane) (0x800 + (lane) * 0x100 + 0x40) +#define LNaRRSTCTL_RST_REQ BIT(31) +#define LNaRRSTCTL_RST_DONE BIT(30) +#define LNaRRSTCTL_HLT_REQ BIT(27) +#define LNaRRSTCTL_STP_REQ BIT(26) +#define LNaRRSTCTL_DIS BIT(24) +#define LNaRRSTCTL_CDR_LOCK BIT(12) + +/* Lane a Rx General Control Register */ +#define LNaRGCR0(lane) (0x800 + (lane) * 0x100 + 0x44) +#define LNaRGCR0_USE_PLL BIT(28) +#define LNaRGCR0_USE_PLLF 0x0 +#define LNaRGCR0_USE_PLLS 0x1 +#define LNaRGCR0_N_RATE GENMASK(26, 24) +#define LNaRGCR0_N_RATE_FULL 0x0 +#define LNaRGCR0_N_RATE_HALF 0x1 +#define LNaRGCR0_N_RATE_QUARTER 0x2 +#define LNaRGCR0_N_RATE_DOUBLE 0x3 + +#define LNaRGCR1(lane) (0x800 + (lane) * 0x100 + 0x48) +#define LNaRGCR1_RX_ORD_ELECIDLE BIT(31) +#define LNaRGCR1_DATA_LOST_FLT BIT(30) +#define LNaRGCR1_DATA_LOST BIT(29) +#define LNaRGCR1_IDLE_CONFIG BIT(28) +#define LNaRGCR1_ENTER_IDLE_FLT_SEL GENMASK(26, 24) +#define LNaRGCR1_EXIT_IDLE_FLT_SEL GENMASK(22, 20) +#define LNaRGCR1_DATA_LOST_TH_SEL GENMASK(18, 16) +#define LNaRGCR1_EXT_REC_CLK_SEL GENMASK(10, 8) +#define LNaRGCR1_WAKE_TX_DIS BIT(5) +#define LNaRGCR1_PHY_RDY BIT(4) +#define LNaRGCR1_CHANGE_RX_CLK BIT(3) +#define LNaRGCR1_PWR_MGT GENMASK(2, 0) + +#define LNaRECR0(lane) (0x800 + (lane) * 0x100 + 0x50) +#define LNaRECR0_EQ_GAINK2_HF_OV_EN BIT(31) +#define LNaRECR0_EQ_GAINK2_HF_OV GENMASK(28, 24) +#define LNaRECR0_EQ_GAINK3_MF_OV_EN BIT(23) +#define LNaRECR0_EQ_GAINK3_MF_OV GENMASK(20, 16) +#define LNaRECR0_EQ_GAINK4_LF_OV_EN BIT(7) +#define LNaRECR0_EQ_GAINK4_LF_DIS BIT(6) +#define LNaRECR0_EQ_GAINK4_LF_OV GENMASK(4, 0) + +#define LNaRECR1(lane) (0x800 + (lane) * 0x100 + 0x54) +#define LNaRECR1_EQ_BLW_OV_EN BIT(31) +#define LNaRECR1_EQ_BLW_OV GENMASK(28, 24) +#define LNaRECR1_EQ_OFFSET_OV_EN BIT(23) +#define LNaRECR1_EQ_OFFSET_OV GENMASK(21, 16) + +#define LNaRECR2(lane) (0x800 + (lane) * 0x100 + 0x58) +#define LNaRECR2_EQ_OFFSET_RNG_DBL BIT(31) +#define LNaRECR2_EQ_BOOST GENMASK(29, 28) +#define LNaRECR2_EQ_BLW_SEL GENMASK(25, 24) +#define LNaRECR2_EQ_ZERO GENMASK(17, 16) +#define LNaRECR2_EQ_IND GENMASK(13, 12) +#define LNaRECR2_EQ_BIN_DATA_AVG_TC GENMASK(5, 4) +#define LNaRECR2_SPARE_IN GENMASK(1, 0) + +#define LNaRECR3(lane) (0x800 + (lane) * 0x100 + 0x5c) +#define LNaRECR3_EQ_SNAP_START BIT(31) +#define LNaRECR3_EQ_SNAP_DONE BIT(30) +#define LNaRECR3_EQ_GAINK2_HF_STAT GENMASK(28, 24) +#define LNaRECR3_EQ_GAINK3_MF_STAT GENMASK(20, 16) +#define LNaRECR3_SPARE_OUT GENMASK(13, 12) +#define LNaRECR3_EQ_GAINK4_LF_STAT GENMASK(4, 0) + +#define LNaRECR4(lane) (0x800 + (lane) * 0x100 + 0x60) +#define LNaRECR4_BLW_STAT GENMASK(28, 24) +#define LNaRECR4_EQ_OFFSET_STAT GENMASK(21, 16) +#define LNaRECR4_EQ_BIN_DATA_SEL GENMASK(15, 12) +#define LNaRECR4_EQ_BIN_DATA GENMASK(8, 0) /* bit 9 is reserved */ +#define LNaRECR4_EQ_BIN_DATA_SGN BIT(8) + +#define LNaRCCR0(lane) (0x800 + (lane) * 0x100 + 0x68) +#define LNaRCCR0_CAL_EN BIT(31) +#define LNaRCCR0_MEAS_EN BIT(30) +#define LNaRCCR0_CAL_BIN_SEL BIT(28) +#define LNaRCCR0_CAL_DC3_DIS BIT(27) +#define LNaRCCR0_CAL_DC2_DIS BIT(26) +#define LNaRCCR0_CAL_DC1_DIS BIT(25) +#define LNaRCCR0_CAL_DC0_DIS BIT(24) +#define LNaRCCR0_CAL_AC3_OV_EN BIT(15) +#define LNaRCCR0_CAL_AC3_OV GENMASK(11, 8) +#define LNaRCCR0_CAL_AC2_OV_EN BIT(7) + +#define LNaRSCCR0(lane) (0x800 + (lane) * 0x100 + 0x74) +#define LNaRSCCR0_SMP_OFF_EN BIT(31) +#define LNaRSCCR0_SMP_OFF_OV_EN BIT(30) +#define LNaRSCCR0_SMP_MAN_OFF_EN BIT(29) +#define LNaRSCCR0_SMP_OFF_RNG_OV_EN BIT(27) +#define LNaRSCCR0_SMP_OFF_RNG_4X_OV BIT(25) +#define LNaRSCCR0_SMP_OFF_RNG_2X_OV BIT(24) +#define LNaRSCCR0_SMP_AUTOZ_PD BIT(23) +#define LNaRSCCR0_SMP_AUTOZ_CTRL GENMASK(19, 16) +#define LNaRSCCR0_SMP_AUTOZ_D1R GENMASK(13, 12) +#define LNaRSCCR0_SMP_AUTOZ_D1F GENMASK(9, 8) +#define LNaRSCCR0_SMP_AUTOZ_EG1R GENMASK(5, 4) +#define LNaRSCCR0_SMP_AUTOZ_EG1F GENMASK(1, 0) + +#define LNaTTLCR0(lane) (0x800 + (lane) * 0x100 + 0x80) +#define LNaTTLCR0_TTL_FLT_SEL GENMASK(29, 24) +#define LNaTTLCR0_TTL_SLO_PM_BYP BIT(22) +#define LNaTTLCR0_STALL_DET_DIS BIT(21) +#define LNaTTLCR0_INACT_MON_DIS BIT(20) +#define LNaTTLCR0_CDR_OV GENMASK(18, 16) +#define LNaTTLCR0_DATA_IN_SSC BIT(15) +#define LNaTTLCR0_CDR_MIN_SMP_ON GENMASK(1, 0) + +#define LNaTCSR0(lane) (0x800 + (lane) * 0x100 + 0xa0) +#define LNaTCSR0_SD_STAT_OBS_EN BIT(31) +#define LNaTCSR0_SD_LPBK_SEL GENMASK(29, 28) + +#define LNaPSS(lane) (0x1000 + (lane) * 0x4) +#define LNaPSS_TYPE GENMASK(30, 24) +#define LNaPSS_TYPE_SGMII (PROTO_SEL_SGMII_BASEX_KX << 2) +#define LNaPSS_TYPE_XFI (PROTO_SEL_XFI_10GBASER_KR_SXGMII << 2) +#define LNaPSS_TYPE_40G ((PROTO_SEL_XFI_10GBASER_KR_SXGMII << 2) | 3) +#define LNaPSS_TYPE_25G (PROTO_SEL_25G_50G_100G << 2) +#define LNaPSS_TYPE_100G ((PROTO_SEL_25G_50G_100G << 2) | 2) + +/* MDEV_PORT is at the same bitfield address for all protocol converters */ +#define MDEV_PORT GENMASK(31, 27) + +#define SGMIIaCR0(lane) (0x1800 + (lane) * 0x10) +#define SGMIIaCR1(lane) (0x1804 + (lane) * 0x10) +#define SGMIIaCR1_SGPCS_EN BIT(11) + +#define ANLTaCR0(lane) (0x1a00 + (lane) * 0x10) +#define ANLTaCR1(lane) (0x1a04 + (lane) * 0x10) + +#define SXGMIIaCR0(lane) (0x1a80 + (lane) * 0x10) +#define SXGMIIaCR0_RST BIT(31) +#define SXGMIIaCR0_PD BIT(30) + +#define SXGMIIaCR1(lane) (0x1a84 + (lane) * 0x10) + +#define E25GaCR0(lane) (0x1b00 + (lane) * 0x10) +#define E25GaCR0_RST BIT(31) +#define E25GaCR0_PD BIT(30) + +#define E25GaCR1(lane) (0x1b04 + (lane) * 0x10) + +#define E25GaCR2(lane) (0x1b08 + (lane) * 0x10) +#define E25GaCR2_FEC_ENA BIT(23) +#define E25GaCR2_FEC_ERR_ENA BIT(22) +#define E25GaCR2_FEC91_ENA BIT(20) + +#define E40GaCR0(pcvt) (0x1b40 + (pcvt) * 0x20) +#define E40GaCR1(pcvt) (0x1b44 + (pcvt) * 0x20) + +#define E50GaCR1(pcvt) (0x1b84 + (pcvt) * 0x10) + +#define E100GaCR1(pcvt) (0x1c04 + (pcvt) * 0x20) + +#define CR(x) ((x) * 4) + +#define LYNX_28G_LANE_HALT_SLEEP_US 100 +#define LYNX_28G_LANE_HALT_TIMEOUT_US 1000000 + +#define LYNX_28G_LANE_RESET_SLEEP_US 100 +#define LYNX_28G_LANE_RESET_TIMEOUT_US 1000000 + +#define LYNX_28G_LANE_STOP_SLEEP_US 100 +#define LYNX_28G_LANE_STOP_TIMEOUT_US 1000000 + +#define lynx_28g_lane_rmw lynx_lane_rmw +#define lynx_28g_lane_read lynx_lane_read +#define lynx_28g_lane_write lynx_lane_write + +#define lynx_28g_priv lynx_priv +#define lynx_28g_lane lynx_lane +#define lynx_28g_pll lynx_pll + +enum lynx_28g_eq_type { + EQ_TYPE_NO_EQ = 0, + EQ_TYPE_2TAP = 1, + EQ_TYPE_3TAP = 2, +}; + +enum lynx_28g_proto_sel { + PROTO_SEL_PCIE = 0, + PROTO_SEL_SGMII_BASEX_KX = 1, + PROTO_SEL_SATA = 2, + PROTO_SEL_XAUI = 4, + PROTO_SEL_XFI_10GBASER_KR_SXGMII = 0xa, + PROTO_SEL_25G_50G_100G = 0x1a, +}; + +struct lynx_28g_proto_conf { + /* LNaGCR0 */ + int proto_sel; + int if_width; + /* LNaTECR0 */ + int teq_type; + int sgn_preq; + int ratio_preq; + int sgn_post1q; + int ratio_post1q; + int amp_red; + /* LNaTECR1 */ + int adpt_eq; + /* LNaRGCR1 */ + int enter_idle_flt_sel; + int exit_idle_flt_sel; + int data_lost_th_sel; + /* LNaRECR0 */ + int gk2ovd; + int gk3ovd; + int gk4ovd; + int gk2ovd_en; + int gk3ovd_en; + int gk4ovd_en; + /* LNaRECR1 ? */ + int eq_offset_ovd; + int eq_offset_ovd_en; + /* LNaRECR2 */ + int eq_offset_rng_dbl; + int eq_blw_sel; + int eq_boost; + int spare_in; + /* LNaRSCCR0 */ + int smp_autoz_d1r; + int smp_autoz_eg1r; + /* LNaRCCR0 */ + int rccr0; + /* LNaTTLCR0 */ + int ttlcr0; +}; + +static const struct lynx_28g_proto_conf lynx_28g_proto_conf[LANE_MODE_MAX] = { + [LANE_MODE_1000BASEX_SGMII] = { + .proto_sel = LNaGCR0_PROTO_SEL_SGMII, + .if_width = LNaGCR0_IF_WIDTH_10_BIT, + .teq_type = EQ_TYPE_NO_EQ, + .sgn_preq = 1, + .ratio_preq = 0, + .sgn_post1q = 1, + .ratio_post1q = 0, + .amp_red = 6, + .adpt_eq = 48, + .enter_idle_flt_sel = 4, + .exit_idle_flt_sel = 3, + .data_lost_th_sel = 1, + .gk2ovd = 0x1f, + .gk3ovd = 0, + .gk4ovd = 0, + .gk2ovd_en = 1, + .gk3ovd_en = 1, + .gk4ovd_en = 0, + .eq_offset_ovd = 0x1f, + .eq_offset_ovd_en = 0, + .eq_offset_rng_dbl = 0, + .eq_blw_sel = 0, + .eq_boost = 0, + .spare_in = 0, + .smp_autoz_d1r = 0, + .smp_autoz_eg1r = 0, + .rccr0 = LNaRCCR0_CAL_EN, + .ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP | + LNaTTLCR0_DATA_IN_SSC, + }, + [LANE_MODE_USXGMII] = { + .proto_sel = LNaGCR0_PROTO_SEL_XFI, + .if_width = LNaGCR0_IF_WIDTH_20_BIT, + .teq_type = EQ_TYPE_2TAP, + .sgn_preq = 1, + .ratio_preq = 0, + .sgn_post1q = 1, + .ratio_post1q = 3, + .amp_red = 7, + .adpt_eq = 48, + .enter_idle_flt_sel = 0, + .exit_idle_flt_sel = 0, + .data_lost_th_sel = 0, + .gk2ovd = 0, + .gk3ovd = 0, + .gk4ovd = 0, + .gk2ovd_en = 0, + .gk3ovd_en = 0, + .gk4ovd_en = 0, + .eq_offset_ovd = 0x1f, + .eq_offset_ovd_en = 0, + .eq_offset_rng_dbl = 1, + .eq_blw_sel = 1, + .eq_boost = 0, + .spare_in = 0, + .smp_autoz_d1r = 2, + .smp_autoz_eg1r = 0, + .rccr0 = LNaRCCR0_CAL_EN, + .ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP | + LNaTTLCR0_DATA_IN_SSC, + }, + [LANE_MODE_10GBASER] = { + .proto_sel = LNaGCR0_PROTO_SEL_XFI, + .if_width = LNaGCR0_IF_WIDTH_20_BIT, + .teq_type = EQ_TYPE_2TAP, + .sgn_preq = 1, + .ratio_preq = 0, + .sgn_post1q = 1, + .ratio_post1q = 3, + .amp_red = 7, + .adpt_eq = 48, + .enter_idle_flt_sel = 0, + .exit_idle_flt_sel = 0, + .data_lost_th_sel = 0, + .gk2ovd = 0, + .gk3ovd = 0, + .gk4ovd = 0, + .gk2ovd_en = 0, + .gk3ovd_en = 0, + .gk4ovd_en = 0, + .eq_offset_ovd = 0x1f, + .eq_offset_ovd_en = 0, + .eq_offset_rng_dbl = 1, + .eq_blw_sel = 1, + .eq_boost = 0, + .spare_in = 0, + .smp_autoz_d1r = 2, + .smp_autoz_eg1r = 0, + .rccr0 = LNaRCCR0_CAL_EN, + .ttlcr0 = LNaTTLCR0_TTL_SLO_PM_BYP | + LNaTTLCR0_DATA_IN_SSC, + }, + [LANE_MODE_25GBASER] = { + .proto_sel = LNaGCR0_PROTO_SEL_25G, + .if_width = LNaGCR0_IF_WIDTH_40_BIT, + .teq_type = EQ_TYPE_3TAP, + .sgn_preq = 1, + .ratio_preq = 2, + .sgn_post1q = 1, + .ratio_post1q = 7, + .amp_red = 0, + .adpt_eq = 48, + .enter_idle_flt_sel = 0, + .exit_idle_flt_sel = 0, + .data_lost_th_sel = 0, + .gk2ovd = 0, + .gk3ovd = 0, + .gk4ovd = 5, + .gk2ovd_en = 0, + .gk3ovd_en = 0, + .gk4ovd_en = 1, + .eq_offset_ovd = 0x1f, + .eq_offset_ovd_en = 0, + .eq_offset_rng_dbl = 1, + .eq_blw_sel = 1, + .eq_boost = 2, + .spare_in = 3, + .smp_autoz_d1r = 2, + .smp_autoz_eg1r = 2, + .rccr0 = LNaRCCR0_CAL_EN | + LNaRCCR0_CAL_DC3_DIS | + LNaRCCR0_CAL_DC2_DIS | + LNaRCCR0_CAL_DC1_DIS | + LNaRCCR0_CAL_DC0_DIS, + .ttlcr0 = LNaTTLCR0_DATA_IN_SSC | + FIELD_PREP_CONST(LNaTTLCR0_CDR_MIN_SMP_ON, 1), + }, +}; + +static void lynx_28g_lane_set_nrate(struct lynx_28g_lane *lane, + struct lynx_28g_pll *pll, + enum lynx_lane_mode lane_mode) +{ + switch (pll->frate_sel) { + case PLLnCR1_FRATE_5G_10GVCO: + case PLLnCR1_FRATE_5G_25GVCO: + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + lynx_28g_lane_rmw(lane, LNaTGCR0, + FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_QUARTER), + LNaTGCR0_N_RATE); + lynx_28g_lane_rmw(lane, LNaRGCR0, + FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_QUARTER), + LNaRGCR0_N_RATE); + break; + default: + break; + } + break; + case PLLnCR1_FRATE_10G_20GVCO: + switch (lane_mode) { + case LANE_MODE_10GBASER: + case LANE_MODE_USXGMII: + lynx_28g_lane_rmw(lane, LNaTGCR0, + FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_FULL), + LNaTGCR0_N_RATE); + lynx_28g_lane_rmw(lane, LNaRGCR0, + FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_FULL), + LNaRGCR0_N_RATE); + break; + default: + break; + } + break; + case PLLnCR1_FRATE_12G_25GVCO: + switch (lane_mode) { + case LANE_MODE_25GBASER: + lynx_28g_lane_rmw(lane, LNaTGCR0, + FIELD_PREP(LNaTGCR0_N_RATE, LNaTGCR0_N_RATE_DOUBLE), + LNaTGCR0_N_RATE); + lynx_28g_lane_rmw(lane, LNaRGCR0, + FIELD_PREP(LNaRGCR0_N_RATE, LNaRGCR0_N_RATE_DOUBLE), + LNaRGCR0_N_RATE); + 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, + FIELD_PREP(LNaTGCR0_USE_PLL, LNaTGCR0_USE_PLLF), + LNaTGCR0_USE_PLL); + lynx_28g_lane_rmw(lane, LNaRGCR0, + FIELD_PREP(LNaRGCR0_USE_PLL, LNaRGCR0_USE_PLLF), + LNaRGCR0_USE_PLL); + } else { + lynx_28g_lane_rmw(lane, LNaTGCR0, + FIELD_PREP(LNaTGCR0_USE_PLL, LNaTGCR0_USE_PLLS), + LNaTGCR0_USE_PLL); + lynx_28g_lane_rmw(lane, LNaRGCR0, + FIELD_PREP(LNaRGCR0_USE_PLL, LNaRGCR0_USE_PLLS), + LNaRGCR0_USE_PLL); + } +} + +static bool lynx_28g_lane_halt_done(struct lynx_28g_lane *lane) +{ + u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL); + u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL); + + return !(trstctl & LNaTRSTCTL_HLT_REQ) && + !(rrstctl & LNaRRSTCTL_HLT_REQ); +} + +static bool lynx_28g_lane_stop_done(struct lynx_28g_lane *lane) +{ + u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL); + u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL); + + return !(trstctl & LNaTRSTCTL_STP_REQ) && + !(rrstctl & LNaRRSTCTL_STP_REQ); +} + +static bool lynx_28g_lane_reset_done(struct lynx_28g_lane *lane) +{ + u32 trstctl = lynx_28g_lane_read(lane, LNaTRSTCTL); + u32 rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL); + + return (trstctl & LNaTRSTCTL_RST_DONE) && + (rrstctl & LNaRRSTCTL_RST_DONE); +} + +/* Halting puts the lane in a mode in which it can be reconfigured */ +static int lynx_28g_lane_halt(struct phy *phy) +{ + struct lynx_28g_lane *lane = phy_get_drvdata(phy); + bool done; + int err; + + /* Issue a halt request */ + lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_HLT_REQ, + LNaTRSTCTL_HLT_REQ); + lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_HLT_REQ, + LNaRRSTCTL_HLT_REQ); + + /* Wait until the halting process is complete */ + err = read_poll_timeout(lynx_28g_lane_halt_done, done, done, + LYNX_28G_LANE_HALT_SLEEP_US, + LYNX_28G_LANE_HALT_TIMEOUT_US, + false, lane); + if (err) { + dev_err(&phy->dev, "Lane %c halt failed: %pe\n", + 'A' + lane->id, ERR_PTR(err)); + } + + return err; +} + +static int lynx_28g_lane_reset(struct phy *phy) +{ + struct lynx_28g_lane *lane = phy_get_drvdata(phy); + bool done; + int err; + + /* Issue a reset request on the lane */ + lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_RST_REQ, + LNaTRSTCTL_RST_REQ); + lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ, + LNaRRSTCTL_RST_REQ); + + /* Wait until the reset sequence is completed */ + err = read_poll_timeout(lynx_28g_lane_reset_done, done, done, + LYNX_28G_LANE_RESET_SLEEP_US, + LYNX_28G_LANE_RESET_TIMEOUT_US, + false, lane); + if (err) { + dev_err(&phy->dev, "Lane %c reset failed: %pe\n", + 'A' + lane->id, ERR_PTR(err)); + } + + return err; +} + +static int lynx_28g_power_off(struct phy *phy) +{ + struct lynx_28g_lane *lane = phy_get_drvdata(phy); + bool done; + int err; + + if (!lane->powered_up) + return 0; + + /* Issue a stop request */ + lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_STP_REQ, + LNaTRSTCTL_STP_REQ); + lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_STP_REQ, + LNaRRSTCTL_STP_REQ); + + /* Wait until the stop process is complete */ + err = read_poll_timeout(lynx_28g_lane_stop_done, done, done, + LYNX_28G_LANE_STOP_SLEEP_US, + LYNX_28G_LANE_STOP_TIMEOUT_US, + false, lane); + if (err) { + dev_err(&phy->dev, "Lane %c stop failed: %pe\n", + 'A' + lane->id, ERR_PTR(err)); + } + + /* Power down the RX and TX portions of the lane */ + lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_DIS, + LNaRRSTCTL_DIS); + lynx_28g_lane_rmw(lane, LNaTRSTCTL, LNaTRSTCTL_DIS, + LNaTRSTCTL_DIS); + + lane->powered_up = false; + + return 0; +} + +static int lynx_28g_power_on(struct phy *phy) +{ + struct lynx_28g_lane *lane = phy_get_drvdata(phy); + int err; + + if (lane->powered_up) + return 0; + + /* Power up the RX and TX portions of the lane */ + lynx_28g_lane_rmw(lane, LNaRRSTCTL, 0, LNaRRSTCTL_DIS); + lynx_28g_lane_rmw(lane, LNaTRSTCTL, 0, LNaTRSTCTL_DIS); + + err = lynx_28g_lane_reset(phy); + if (err) + return err; + + lane->powered_up = true; + + return 0; +} + +static int lynx_28g_e25g_pcvt(int lane) +{ + return 7 - lane; +} + +static int lynx_28g_get_pccr(enum lynx_lane_mode lane_mode, int lane, + struct lynx_pccr *pccr) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + pccr->offset = PCC8; + pccr->width = 4; + pccr->shift = SGMII_CFG(lane); + break; + case LANE_MODE_USXGMII: + case LANE_MODE_10GBASER: + pccr->offset = PCCC; + pccr->width = 4; + pccr->shift = SXGMII_CFG(lane); + break; + case LANE_MODE_25GBASER: + pccr->offset = PCCD; + pccr->width = 4; + pccr->shift = E25G_CFG(lynx_28g_e25g_pcvt(lane)); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int lynx_28g_get_pcvt_offset(int lane, enum lynx_lane_mode lane_mode) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + return SGMIIaCR0(lane); + case LANE_MODE_USXGMII: + case LANE_MODE_10GBASER: + return SXGMIIaCR0(lane); + case LANE_MODE_25GBASER: + return E25GaCR0(lynx_28g_e25g_pcvt(lane)); + default: + return -EOPNOTSUPP; + } +} + +static bool lx2160a_serdes1_lane_supports_mode(int lane, + enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_25GBASER: + return lane != 2 && lane != 3; + default: + return true; + } +} + +static bool lx2160a_serdes2_lane_supports_mode(int lane, + enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + return true; + case LANE_MODE_USXGMII: + case LANE_MODE_10GBASER: + return lane == 6 || lane == 7; + default: + return false; + } +} + +static bool lx2160a_serdes3_lane_supports_mode(int lane, + enum lynx_lane_mode mode) +{ + /* + * Non-networking SerDes, and this driver supports only + * networking protocols + */ + return false; +} + +static bool lx2162a_serdes1_lane_supports_mode(int lane, + enum lynx_lane_mode mode) +{ + return true; +} + +static bool lx2162a_serdes2_lane_supports_mode(int lane, + enum lynx_lane_mode mode) +{ + return lx2160a_serdes2_lane_supports_mode(lane, mode); +} + +/* Feature set is not expected to grow for the deprecated compatible string */ +static bool lynx_28g_compat_lane_supports_mode(int lane, + enum lynx_lane_mode mode) +{ + switch (mode) { + case LANE_MODE_1000BASEX_SGMII: + case LANE_MODE_USXGMII: + case LANE_MODE_10GBASER: + return true; + default: + return false; + } +} + +static void lynx_28g_cdr_lock_check(struct lynx_lane *lane) +{ + u32 rrstctl; + int err; + + rrstctl = lynx_28g_lane_read(lane, LNaRRSTCTL); + if (!!(rrstctl & LNaRRSTCTL_CDR_LOCK)) + return; + + lynx_28g_lane_rmw(lane, LNaRRSTCTL, LNaRRSTCTL_RST_REQ, + LNaRRSTCTL_RST_REQ); + + err = read_poll_timeout(lynx_28g_lane_read, rrstctl, + !!(rrstctl & LNaRRSTCTL_RST_DONE), + LYNX_28G_LANE_RESET_SLEEP_US, + LYNX_28G_LANE_RESET_TIMEOUT_US, + false, lane, LNaRRSTCTL); + if (err) { + dev_warn_once(&lane->phy->dev, + "Lane %c receiver reset failed: %pe\n", + 'A' + lane->id, ERR_PTR(err)); + } +} + +static void lynx_28g_lane_remap_pll(struct lynx_lane *lane, + enum lynx_lane_mode lane_mode) +{ + struct lynx_priv *priv = lane->priv; + struct lynx_pll *pll; + + /* Switch to the PLL that works with this interface type */ + pll = lynx_pll_get(priv, lane_mode); + if (unlikely(!pll)) + return; + + 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, lane_mode); +} + +static void lynx_28g_lane_change_proto_conf(struct lynx_28g_lane *lane, + enum lynx_lane_mode lane_mode) +{ + const struct lynx_28g_proto_conf *conf = &lynx_28g_proto_conf[lane_mode]; + + lynx_28g_lane_rmw(lane, LNaGCR0, + FIELD_PREP(LNaGCR0_PROTO_SEL, conf->proto_sel) | + FIELD_PREP(LNaGCR0_IF_WIDTH, conf->if_width), + LNaGCR0_PROTO_SEL | LNaGCR0_IF_WIDTH); + + lynx_28g_lane_rmw(lane, LNaTECR0, + FIELD_PREP(LNaTECR0_EQ_TYPE, conf->teq_type) | + FIELD_PREP(LNaTECR0_EQ_SGN_PREQ, conf->sgn_preq) | + FIELD_PREP(LNaTECR0_EQ_PREQ, conf->ratio_preq) | + FIELD_PREP(LNaTECR0_EQ_SGN_POST1Q, conf->sgn_post1q) | + FIELD_PREP(LNaTECR0_EQ_POST1Q, conf->ratio_post1q) | + FIELD_PREP(LNaTECR0_EQ_AMP_RED, conf->amp_red), + LNaTECR0_EQ_TYPE | + LNaTECR0_EQ_SGN_PREQ | + LNaTECR0_EQ_PREQ | + LNaTECR0_EQ_SGN_POST1Q | + LNaTECR0_EQ_POST1Q | + LNaTECR0_EQ_AMP_RED); + + lynx_28g_lane_rmw(lane, LNaTECR1, + FIELD_PREP(LNaTECR1_EQ_ADPT_EQ, conf->adpt_eq), + LNaTECR1_EQ_ADPT_EQ); + + lynx_28g_lane_rmw(lane, LNaRGCR1, + FIELD_PREP(LNaRGCR1_ENTER_IDLE_FLT_SEL, conf->enter_idle_flt_sel) | + FIELD_PREP(LNaRGCR1_EXIT_IDLE_FLT_SEL, conf->exit_idle_flt_sel) | + FIELD_PREP(LNaRGCR1_DATA_LOST_TH_SEL, conf->data_lost_th_sel), + LNaRGCR1_ENTER_IDLE_FLT_SEL | + LNaRGCR1_EXIT_IDLE_FLT_SEL | + LNaRGCR1_DATA_LOST_TH_SEL); + + lynx_28g_lane_rmw(lane, LNaRECR0, + FIELD_PREP(LNaRECR0_EQ_GAINK2_HF_OV_EN, conf->gk2ovd_en) | + FIELD_PREP(LNaRECR0_EQ_GAINK3_MF_OV_EN, conf->gk3ovd_en) | + FIELD_PREP(LNaRECR0_EQ_GAINK4_LF_OV_EN, conf->gk4ovd_en) | + FIELD_PREP(LNaRECR0_EQ_GAINK2_HF_OV, conf->gk2ovd) | + FIELD_PREP(LNaRECR0_EQ_GAINK3_MF_OV, conf->gk3ovd) | + FIELD_PREP(LNaRECR0_EQ_GAINK4_LF_OV, conf->gk4ovd), + LNaRECR0_EQ_GAINK2_HF_OV | + LNaRECR0_EQ_GAINK3_MF_OV | + LNaRECR0_EQ_GAINK4_LF_OV | + LNaRECR0_EQ_GAINK2_HF_OV_EN | + LNaRECR0_EQ_GAINK3_MF_OV_EN | + LNaRECR0_EQ_GAINK4_LF_OV_EN); + + lynx_28g_lane_rmw(lane, LNaRECR1, + FIELD_PREP(LNaRECR1_EQ_OFFSET_OV, conf->eq_offset_ovd) | + FIELD_PREP(LNaRECR1_EQ_OFFSET_OV_EN, conf->eq_offset_ovd_en), + LNaRECR1_EQ_OFFSET_OV | + LNaRECR1_EQ_OFFSET_OV_EN); + + lynx_28g_lane_rmw(lane, LNaRECR2, + FIELD_PREP(LNaRECR2_EQ_OFFSET_RNG_DBL, conf->eq_offset_rng_dbl) | + FIELD_PREP(LNaRECR2_EQ_BLW_SEL, conf->eq_blw_sel) | + FIELD_PREP(LNaRECR2_EQ_BOOST, conf->eq_boost) | + FIELD_PREP(LNaRECR2_SPARE_IN, conf->spare_in), + LNaRECR2_EQ_OFFSET_RNG_DBL | + LNaRECR2_EQ_BLW_SEL | + LNaRECR2_EQ_BOOST | + LNaRECR2_SPARE_IN); + + lynx_28g_lane_rmw(lane, LNaRSCCR0, + FIELD_PREP(LNaRSCCR0_SMP_AUTOZ_D1R, conf->smp_autoz_d1r) | + FIELD_PREP(LNaRSCCR0_SMP_AUTOZ_EG1R, conf->smp_autoz_eg1r), + LNaRSCCR0_SMP_AUTOZ_D1R | + LNaRSCCR0_SMP_AUTOZ_EG1R); + + lynx_28g_lane_write(lane, LNaRCCR0, conf->rccr0); + lynx_28g_lane_write(lane, LNaTTLCR0, conf->ttlcr0); +} + +static int lynx_28g_lane_disable_pcvt(struct lynx_28g_lane *lane, + enum lynx_lane_mode lane_mode) +{ + struct lynx_28g_priv *priv = lane->priv; + int err; + + spin_lock(&priv->pcc_lock); + + err = lynx_pccr_write(lane, lane_mode, 0); + if (err) + goto out; + + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + err = lynx_pcvt_rmw(lane, lane_mode, CR(1), 0, + SGMIIaCR1_SGPCS_EN); + break; + default: + err = 0; + } + +out: + spin_unlock(&priv->pcc_lock); + + return err; +} + +static int lynx_28g_lane_enable_pcvt(struct lynx_28g_lane *lane, + enum lynx_lane_mode lane_mode) +{ + struct lynx_28g_priv *priv = lane->priv; + u32 val; + int err; + + spin_lock(&priv->pcc_lock); + + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + err = lynx_pcvt_rmw(lane, lane_mode, CR(1), SGMIIaCR1_SGPCS_EN, + SGMIIaCR1_SGPCS_EN); + break; + default: + err = 0; + } + + val = 0; + + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + val |= PCC8_SGMIIa_CFG; + break; + case LANE_MODE_10GBASER: + val |= PCCC_SXGMIIn_XFI; + fallthrough; + case LANE_MODE_USXGMII: + val |= PCCC_SXGMIIn_CFG; + break; + case LANE_MODE_25GBASER: + val |= PCCD_E25Gn_CFG; + break; + default: + break; + } + + err = lynx_pccr_write(lane, lane_mode, val); + + spin_unlock(&priv->pcc_lock); + + return err; +} + +static int lynx_28g_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts) +{ + return lynx_phy_mode_to_lane_mode(phy, mode, submode, NULL); +} + +static int lynx_28g_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + int powered_up = lane->powered_up; + enum lynx_lane_mode lane_mode; + int err; + + err = lynx_phy_mode_to_lane_mode(phy, mode, submode, &lane_mode); + if (err) + return err; + + if (lane_mode == lane->mode) + return 0; + + /* If the lane is powered up, put the lane into the halt state while + * the reconfiguration is being done. + */ + if (powered_up) { + err = lynx_28g_lane_halt(phy); + if (err) + goto out; + } + + err = lynx_28g_lane_disable_pcvt(lane, lane->mode); + if (err) + goto out; + + lynx_28g_lane_change_proto_conf(lane, lane_mode); + lynx_28g_lane_remap_pll(lane, lane_mode); + WARN_ON(lynx_28g_lane_enable_pcvt(lane, lane_mode)); + + lane->mode = lane_mode; + +out: + if (powered_up) + lynx_28g_lane_reset(phy); + + return err; +} + +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 int lynx_28g_exit(struct phy *phy) +{ + struct lynx_28g_lane *lane = phy_get_drvdata(phy); + + /* The lane returns to the state where it isn't managed by the + * consumer, so we must treat is as if it isn't initialized, and + * always powered on. + */ + lane->init = false; + lane->powered_up = false; + + lynx_28g_power_on(phy); + + return 0; +} + +static const struct phy_ops lynx_28g_ops = { + .init = lynx_28g_init, + .exit = lynx_28g_exit, + .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_pll *pll) +{ + u32 val; + + val = lynx_pll_read(pll, PLLnRSTCTL); + pll->enabled = !(val & PLLnRSTCTL_DIS); + pll->locked = !!(val & PLLnRSTCTL_LOCK); + + val = lynx_pll_read(pll, PLLnCR0); + pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val); + + val = lynx_pll_read(pll, PLLnCR1); + pll->frate_sel = FIELD_GET(PLLnCR1_FRATE_SEL, val); + + if (!pll->enabled) + return; + + switch (pll->frate_sel) { + case PLLnCR1_FRATE_5G_10GVCO: + case PLLnCR1_FRATE_5G_25GVCO: + /* 5GHz clock net */ + __set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported); + break; + case PLLnCR1_FRATE_10G_20GVCO: + /* 10.3125GHz clock net */ + __set_bit(LANE_MODE_10GBASER, pll->supported); + __set_bit(LANE_MODE_USXGMII, pll->supported); + break; + case PLLnCR1_FRATE_12G_25GVCO: + /* 12.890625GHz clock net */ + __set_bit(LANE_MODE_25GBASER, pll->supported); + break; + default: + /* 6GHz, 8GHz */ + break; + } +} + +static void lynx_28g_lane_read_configuration(struct lynx_28g_lane *lane) +{ + u32 pccr, pss, protocol; + + pss = lynx_28g_lane_read(lane, LNaPSS); + protocol = FIELD_GET(LNaPSS_TYPE, pss); + switch (protocol) { + case LNaPSS_TYPE_SGMII: + lane->mode = LANE_MODE_1000BASEX_SGMII; + break; + case LNaPSS_TYPE_XFI: + lynx_pccr_read(lane, LANE_MODE_10GBASER, &pccr); + if (pccr & PCCC_SXGMIIn_XFI) + lane->mode = LANE_MODE_10GBASER; + else + lane->mode = LANE_MODE_USXGMII; + break; + case LNaPSS_TYPE_25G: + lane->mode = LANE_MODE_25GBASER; + break; + default: + lane->mode = LANE_MODE_UNKNOWN; + } +} + +static const struct lynx_info lynx_info_compat = { + .get_pccr = lynx_28g_get_pccr, + .get_pcvt_offset = lynx_28g_get_pcvt_offset, + .lane_supports_mode = lynx_28g_compat_lane_supports_mode, + .pll_read_configuration = lynx_28g_pll_read_configuration, + .lane_read_configuration = lynx_28g_lane_read_configuration, + .cdr_lock_check = lynx_28g_cdr_lock_check, + .num_lanes = LYNX_28G_NUM_LANE, +}; + +static const struct lynx_info lynx_info_lx2160a_serdes1 = { + .get_pccr = lynx_28g_get_pccr, + .get_pcvt_offset = lynx_28g_get_pcvt_offset, + .lane_supports_mode = lx2160a_serdes1_lane_supports_mode, + .pll_read_configuration = lynx_28g_pll_read_configuration, + .lane_read_configuration = lynx_28g_lane_read_configuration, + .cdr_lock_check = lynx_28g_cdr_lock_check, + .num_lanes = LYNX_28G_NUM_LANE, +}; + +static const struct lynx_info lynx_info_lx2160a_serdes2 = { + .get_pccr = lynx_28g_get_pccr, + .get_pcvt_offset = lynx_28g_get_pcvt_offset, + .lane_supports_mode = lx2160a_serdes2_lane_supports_mode, + .pll_read_configuration = lynx_28g_pll_read_configuration, + .lane_read_configuration = lynx_28g_lane_read_configuration, + .cdr_lock_check = lynx_28g_cdr_lock_check, + .num_lanes = LYNX_28G_NUM_LANE, +}; + +static const struct lynx_info lynx_info_lx2160a_serdes3 = { + .get_pccr = lynx_28g_get_pccr, + .get_pcvt_offset = lynx_28g_get_pcvt_offset, + .lane_supports_mode = lx2160a_serdes3_lane_supports_mode, + .pll_read_configuration = lynx_28g_pll_read_configuration, + .lane_read_configuration = lynx_28g_lane_read_configuration, + .cdr_lock_check = lynx_28g_cdr_lock_check, + .num_lanes = LYNX_28G_NUM_LANE, +}; + +static const struct lynx_info lynx_info_lx2162a_serdes1 = { + .get_pccr = lynx_28g_get_pccr, + .get_pcvt_offset = lynx_28g_get_pcvt_offset, + .lane_supports_mode = lx2162a_serdes1_lane_supports_mode, + .pll_read_configuration = lynx_28g_pll_read_configuration, + .lane_read_configuration = lynx_28g_lane_read_configuration, + .cdr_lock_check = lynx_28g_cdr_lock_check, + .first_lane = 4, + .num_lanes = LYNX_28G_NUM_LANE, +}; + +static const struct lynx_info lynx_info_lx2162a_serdes2 = { + .get_pccr = lynx_28g_get_pccr, + .get_pcvt_offset = lynx_28g_get_pcvt_offset, + .lane_supports_mode = lx2162a_serdes2_lane_supports_mode, + .pll_read_configuration = lynx_28g_pll_read_configuration, + .lane_read_configuration = lynx_28g_lane_read_configuration, + .cdr_lock_check = lynx_28g_cdr_lock_check, + .num_lanes = LYNX_28G_NUM_LANE, +}; + +static int lynx_28g_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct lynx_info *info; + + /* + * If we get here it means we probed on a device tree where + * "fsl,lynx-28g" wasn't the fallback, but the sole compatible string. + */ + info = of_device_get_match_data(dev); + if (info == &lynx_info_compat) + dev_warn(dev, "Please update device tree to use per-device compatible strings\n"); + + return lynx_probe(pdev, info, &lynx_28g_ops); +} + +static const struct of_device_id lynx_28g_of_match_table[] = { + { .compatible = "fsl,lx2160a-serdes1", .data = &lynx_info_lx2160a_serdes1 }, + { .compatible = "fsl,lx2160a-serdes2", .data = &lynx_info_lx2160a_serdes2 }, + { .compatible = "fsl,lx2160a-serdes3", .data = &lynx_info_lx2160a_serdes3 }, + { .compatible = "fsl,lx2162a-serdes1", .data = &lynx_info_lx2162a_serdes1 }, + { .compatible = "fsl,lx2162a-serdes2", .data = &lynx_info_lx2162a_serdes2 }, + { .compatible = "fsl,lynx-28g", .data = &lynx_info_compat }, /* fallback, keep last */ + { }, +}; +MODULE_DEVICE_TABLE(of, lynx_28g_of_match_table); + +static struct platform_driver lynx_28g_driver = { + .probe = lynx_28g_probe, + .remove = lynx_remove, + .driver = { + .name = "lynx-28g", + .of_match_table = lynx_28g_of_match_table, + }, +}; +module_platform_driver(lynx_28g_driver); + +MODULE_IMPORT_NS("PHY_FSL_LYNX"); +MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>"); +MODULE_DESCRIPTION("Lynx 28G SerDes PHY driver for Layerscape SoCs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c new file mode 100644 index 000000000..2cfe9236f --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-lynx-core.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright 2025-2026 NXP */ + +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "phy-fsl-lynx-core.h" + +const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode) +{ + switch (lane_mode) { + case LANE_MODE_1000BASEX_SGMII: + return "1000Base-X/SGMII"; + case LANE_MODE_2500BASEX: + return "2500Base-X"; + case LANE_MODE_QSGMII: + return "QSGMII"; + case LANE_MODE_10G_QXGMII: + return "10G-QXGMII"; + case LANE_MODE_10GBASER: + return "10GBase-R"; + case LANE_MODE_USXGMII: + return "USXGMII"; + case LANE_MODE_25GBASER: + return "25GBase-R"; + default: + return "unknown"; + } +} +EXPORT_SYMBOL_NS_GPL(lynx_lane_mode_str, "PHY_FSL_LYNX"); + +enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf) +{ + switch (intf) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: + return LANE_MODE_1000BASEX_SGMII; + case PHY_INTERFACE_MODE_2500BASEX: + return LANE_MODE_2500BASEX; + case PHY_INTERFACE_MODE_QSGMII: + return LANE_MODE_QSGMII; + case PHY_INTERFACE_MODE_10G_QXGMII: + return LANE_MODE_10G_QXGMII; + case PHY_INTERFACE_MODE_10GBASER: + return LANE_MODE_10GBASER; + case PHY_INTERFACE_MODE_USXGMII: + return LANE_MODE_USXGMII; + case PHY_INTERFACE_MODE_25GBASER: + return LANE_MODE_25GBASER; + default: + return LANE_MODE_UNKNOWN; + } +} +EXPORT_SYMBOL_NS_GPL(phy_interface_to_lane_mode, "PHY_FSL_LYNX"); + +/* By default, assume that if we know how to get the PCCR register and + * protocol converter for a lane, that protocol is supported. + */ +static bool lynx_lane_supports_mode_default(struct lynx_lane *lane, + enum lynx_lane_mode mode) +{ + struct lynx_priv *priv = lane->priv; + struct lynx_pccr pccr; + + if (!priv->info->get_pccr || !priv->info->get_pcvt_offset) + return false; + + if (priv->info->get_pccr(mode, lane->id, &pccr) < 0) + return false; + + if (priv->info->get_pcvt_offset(lane->id, mode) < 0) + return false; + + return true; +} + +/* A lane mode is supported if we have a PLL that can provide its required + * clock net, and if there is a protocol converter for that mode on that lane. + */ +bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode) +{ + struct lynx_priv *priv = lane->priv; + int i; + + if (priv->info->lane_supports_mode) { + if (!priv->info->lane_supports_mode(lane->id, mode)) + return false; + } else if (!lynx_lane_supports_mode_default(lane, mode)) { + return false; + } + + for (i = 0; i < LYNX_NUM_PLL; i++) { + if (!priv->pll[i].enabled) + continue; + + if (test_bit(mode, priv->pll[i].supported)) + return true; + } + + return false; +} +EXPORT_SYMBOL_NS_GPL(lynx_lane_supports_mode, "PHY_FSL_LYNX"); + +/* The quad protocols are fixed because the lane has multiple consumers, and + * one phy_set_mode_ext() affects the other consumers as well. We have no use + * case for dynamic protocol changing here, so disallow it. + */ +static enum lynx_lane_mode lynx_fixed_protocols[] = { + LANE_MODE_QSGMII, + LANE_MODE_10G_QXGMII, +}; + +static bool lynx_lane_restrict_fixed_mode_change(struct lynx_lane *lane, + enum lynx_lane_mode new) +{ + enum lynx_lane_mode curr = lane->mode; + + for (int i = 0; i < ARRAY_SIZE(lynx_fixed_protocols); i++) + if ((curr == lynx_fixed_protocols[i] || + new == lynx_fixed_protocols[i]) && + curr != new) + return true; + + return false; +} + +/* Translate the mode/submode from phy_validate() and phy_set_mode_ext() to a + * lane_mode and return 0 if it is supported and we can transition to it from + * the current lane mode, or return negative error otherwise. + */ +int lynx_phy_mode_to_lane_mode(struct phy *phy, enum phy_mode mode, + int submode, enum lynx_lane_mode *lane_mode) +{ + struct lynx_lane *lane = phy_get_drvdata(phy); + enum lynx_lane_mode tmp_lane_mode; + + /* The protocol configuration tables are incomplete for full lane + * reconfiguration from an arbitrary protocol. + */ + if (lane->mode == LANE_MODE_UNKNOWN) + return -EINVAL; + + if (mode != PHY_MODE_ETHERNET) + return -EINVAL; + + tmp_lane_mode = phy_interface_to_lane_mode(submode); + if (!lynx_lane_supports_mode(lane, tmp_lane_mode)) + return -EINVAL; + + if (lynx_lane_restrict_fixed_mode_change(lane, tmp_lane_mode)) + return -EINVAL; + + if (lane_mode) + *lane_mode = tmp_lane_mode; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(lynx_phy_mode_to_lane_mode, "PHY_FSL_LYNX"); + +struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode) +{ + struct lynx_pll *pll; + int i; + + for (i = 0; i < LYNX_NUM_PLL; i++) { + pll = &priv->pll[i]; + + if (!pll->enabled) + continue; + + if (test_bit(mode, pll->supported)) + return pll; + } + + /* no pll supports requested mode, either caller forgot to check + * lynx_lane_supports_mode(), or this is a bug. + */ + dev_WARN_ONCE(priv->dev, 1, "no pll for lane mode %s\n", + lynx_lane_mode_str(mode)); + return NULL; +} +EXPORT_SYMBOL_NS_GPL(lynx_pll_get, "PHY_FSL_LYNX"); + +int lynx_pccr_read(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 *val) +{ + struct lynx_priv *priv = lane->priv; + struct lynx_pccr pccr; + u32 tmp; + int err; + + err = priv->info->get_pccr(mode, lane->id, &pccr); + if (err) + return err; + + tmp = lynx_read(priv, pccr.offset); + *val = (tmp >> pccr.shift) & GENMASK(pccr.width - 1, 0); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(lynx_pccr_read, "PHY_FSL_LYNX"); + +int lynx_pccr_write(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 val) +{ + struct lynx_priv *priv = lane->priv; + struct lynx_pccr pccr; + u32 old, tmp, mask; + int err; + + err = priv->info->get_pccr(mode, lane->id, &pccr); + if (err) + return err; + + old = lynx_read(priv, pccr.offset); + mask = GENMASK(pccr.width - 1, 0) << pccr.shift; + tmp = (old & ~mask) | (val << pccr.shift); + lynx_write(priv, pccr.offset, tmp); + + dev_dbg(&lane->phy->dev, "PCCR@0x%x: 0x%x -> 0x%x\n", + pccr.offset, old, tmp); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(lynx_pccr_write, "PHY_FSL_LYNX"); + +int lynx_pcvt_read(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr, + u32 *val) +{ + struct lynx_priv *priv = lane->priv; + int offset; + + offset = priv->info->get_pcvt_offset(lane->id, mode); + if (offset < 0) + return offset; + + *val = lynx_read(priv, offset + cr); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(lynx_pcvt_read, "PHY_FSL_LYNX"); + +int lynx_pcvt_write(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr, + u32 val) +{ + struct lynx_priv *priv = lane->priv; + int offset; + + offset = priv->info->get_pcvt_offset(lane->id, mode); + if (offset < 0) + return offset; + + lynx_write(priv, offset + cr, val); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(lynx_pcvt_write, "PHY_FSL_LYNX"); + +int lynx_pcvt_rmw(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr, + u32 val, u32 mask) +{ + int err; + u32 tmp; + + err = lynx_pcvt_read(lane, mode, cr, &tmp); + if (err) + return err; + + tmp &= ~mask; + tmp |= val; + + return lynx_pcvt_write(lane, mode, cr, tmp); +} +EXPORT_SYMBOL_NS_GPL(lynx_pcvt_rmw, "PHY_FSL_LYNX"); + +#define work_to_lynx(w) container_of((w), struct lynx_priv, cdr_check.work) + +static void lynx_cdr_lock_check(struct work_struct *work) +{ + struct lynx_priv *priv = work_to_lynx(work); + struct lynx_lane *lane; + + for (int i = priv->info->first_lane; i < priv->info->num_lanes; i++) { + lane = &priv->lane[i]; + if (!lane->phy) + continue; + + mutex_lock(&lane->phy->mutex); + + if (!lane->init || !lane->powered_up) { + mutex_unlock(&lane->phy->mutex); + continue; + } + + priv->info->cdr_lock_check(lane); + + mutex_unlock(&lane->phy->mutex); + } + + queue_delayed_work(system_power_efficient_wq, &priv->cdr_check, + msecs_to_jiffies(1000)); +} + +static struct phy *lynx_xlate(struct device *dev, + const struct of_phandle_args *args) +{ + struct lynx_priv *priv = dev_get_drvdata(dev); + int idx; + + if (args->args_count == 0) + return of_phy_simple_xlate(dev, args); + else if (args->args_count != 1) + return ERR_PTR(-ENODEV); + + idx = args->args[0]; + + if (WARN_ON(idx >= priv->info->num_lanes || + idx < priv->info->first_lane)) + return ERR_PTR(-EINVAL); + + return priv->lane[idx].phy ?: ERR_PTR(-ENODEV); +} + +static int lynx_probe_lane(struct lynx_priv *priv, int id, + struct device_node *dn, + const struct phy_ops *phy_ops) +{ + struct lynx_lane *lane = &priv->lane[id]; + struct phy *phy; + + phy = devm_phy_create(priv->dev, dn, phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + lane->priv = priv; + lane->phy = phy; + lane->id = id; + phy_set_drvdata(phy, lane); + priv->info->lane_read_configuration(lane); + + return 0; +} + +int lynx_probe(struct platform_device *pdev, const struct lynx_info *info, + const struct phy_ops *phy_ops) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct device_node *dn; + struct lynx_priv *priv; + int err; + + dn = dev_of_node(dev); + if (!dn) { + dev_err(dev, "Device requires an OF node\n"); + return -EINVAL; + } + + if (!info) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->info = info; + priv->big_endian = device_property_read_bool(dev, "big-endian"); + dev_set_drvdata(dev, priv); + spin_lock_init(&priv->pcc_lock); + INIT_DELAYED_WORK(&priv->cdr_check, lynx_cdr_lock_check); + + priv->lane = devm_kcalloc(dev, priv->info->num_lanes, + sizeof(*priv->lane), GFP_KERNEL); + if (!priv->lane) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + for (int i = 0; i < LYNX_NUM_PLL; i++) { + struct lynx_pll *pll = &priv->pll[i]; + + pll->priv = priv; + pll->id = i; + priv->info->pll_read_configuration(pll); + } + + if (of_get_child_count(dn)) { + struct device_node *child; + + for_each_available_child_of_node(dn, child) { + u32 reg; + + /* PHY subnode name must be 'phy'. */ + if (!(of_node_name_eq(child, "phy"))) + continue; + + if (of_property_read_u32(child, "reg", ®)) { + dev_err(dev, "No \"reg\" property for %pOF\n", child); + of_node_put(child); + return -EINVAL; + } + + if (reg < priv->info->first_lane || reg >= priv->info->num_lanes) { + dev_err(dev, "\"reg\" property out of range for %pOF\n", child); + of_node_put(child); + return -EINVAL; + } + + err = lynx_probe_lane(priv, reg, child, phy_ops); + if (err) { + of_node_put(child); + return err; + } + } + } else { + for (int i = priv->info->first_lane; i < priv->info->num_lanes; i++) { + err = lynx_probe_lane(priv, i, NULL, phy_ops); + if (err) + return err; + } + } + + provider = devm_of_phy_provider_register(dev, lynx_xlate); + if (IS_ERR(provider)) + return PTR_ERR(provider); + + queue_delayed_work(system_power_efficient_wq, &priv->cdr_check, + msecs_to_jiffies(1000)); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(lynx_probe, "PHY_FSL_LYNX"); + +void lynx_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lynx_priv *priv = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&priv->cdr_check); +} +EXPORT_SYMBOL_NS_GPL(lynx_remove, "PHY_FSL_LYNX"); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Freescale Lynx SerDes core functionality"); diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h new file mode 100644 index 000000000..a60429ba9 --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-lynx-core.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright 2025-2026 NXP */ + +#ifndef _PHY_FSL_LYNX_CORE_H +#define _PHY_FSL_LYNX_CORE_H + +#include <linux/phy/phy.h> +#include <linux/phy.h> +#include <soc/fsl/phy-fsl-lynx.h> + +#define LYNX_NUM_PLL 2 +#define LYNX_QUIRK_HAS_HARDCODED_USXGMII BIT(0) + +struct lynx_priv; +struct lynx_lane; + +struct lynx_pccr { + int offset; + int width; + int shift; +}; + +struct lynx_pll { + struct lynx_priv *priv; + int id; + int refclk_sel; + int frate_sel; + bool enabled; + bool locked; + DECLARE_BITMAP(supported, LANE_MODE_MAX); +}; + +struct lynx_lane { + struct lynx_priv *priv; + struct phy *phy; + bool powered_up; + bool init; + unsigned int id; + enum lynx_lane_mode mode; + u32 default_pccr[LANE_MODE_MAX]; +}; + +struct lynx_info { + int (*get_pccr)(enum lynx_lane_mode lane_mode, int lane, + struct lynx_pccr *pccr); + int (*get_pcvt_offset)(int lane, enum lynx_lane_mode mode); + bool (*lane_supports_mode)(int lane, enum lynx_lane_mode mode); + void (*pll_read_configuration)(struct lynx_pll *pll); + void (*lane_read_configuration)(struct lynx_lane *lane); + void (*cdr_lock_check)(struct lynx_lane *lane); + int first_lane; + int num_lanes; + int index; + unsigned long quirks; +}; + +struct lynx_priv { + void __iomem *base; + struct device *dev; + const struct lynx_info *info; + /* Serialize concurrent access to registers shared between lanes, + * like PCCn + */ + spinlock_t pcc_lock; + bool big_endian; + struct lynx_pll pll[LYNX_NUM_PLL]; + struct lynx_lane *lane; + + struct delayed_work cdr_check; +}; + +static inline u32 lynx_read(struct lynx_priv *priv, unsigned long off) +{ + void __iomem *reg = priv->base + off; + + if (priv->big_endian) + return ioread32be(reg); + + return ioread32(reg); +} + +static inline void lynx_write(struct lynx_priv *priv, unsigned long off, u32 val) +{ + void __iomem *reg = priv->base + off; + + if (priv->big_endian) + return iowrite32be(val, reg); + + return iowrite32(val, reg); +} + +static inline void lynx_rmw(struct lynx_priv *priv, unsigned long off, u32 val, + u32 mask) +{ + u32 orig, tmp; + + orig = lynx_read(priv, off); + tmp = orig & ~mask; + tmp |= val; + if (orig != tmp) + lynx_write(priv, off, tmp); +} + +#define lynx_lane_rmw(lane, reg, val, mask) \ + lynx_rmw((lane)->priv, reg(lane->id), val, mask) +#define lynx_lane_read(lane, reg) \ + lynx_read((lane)->priv, reg((lane)->id)) +#define lynx_lane_write(lane, reg, val) \ + lynx_write((lane)->priv, reg((lane)->id), val) +#define lynx_pll_read(pll, reg) \ + lynx_read((pll)->priv, reg((pll)->id)) + +int lynx_probe(struct platform_device *pdev, const struct lynx_info *info, + const struct phy_ops *phy_ops); +void lynx_remove(struct platform_device *pdev); + +const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode); +enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf); +bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode); +int lynx_phy_mode_to_lane_mode(struct phy *phy, enum phy_mode mode, + int submode, enum lynx_lane_mode *lane_mode); + +struct lynx_pll *lynx_pll_get(struct lynx_priv *priv, enum lynx_lane_mode mode); + +int lynx_pccr_read(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 *val); +int lynx_pccr_write(struct lynx_lane *lane, enum lynx_lane_mode mode, u32 val); +int lynx_pcvt_read(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr, + u32 *val); +int lynx_pcvt_write(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr, + u32 val); +int lynx_pcvt_rmw(struct lynx_lane *lane, enum lynx_lane_mode mode, int cr, + u32 val, u32 mask); + +#endif diff --git a/drivers/phy/freescale/phy-fsl-samsung-hdmi.c b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c new file mode 100644 index 000000000..0f25d81de --- /dev/null +++ b/drivers/phy/freescale/phy-fsl-samsung-hdmi.c @@ -0,0 +1,746 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020 NXP + * Copyright 2022 Pengutronix, Lucas Stach <kernel@pengutronix.de> + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#define PHY_REG(reg) (reg * 4) + +#define REG01_PMS_P_MASK GENMASK(3, 0) +#define REG03_PMS_S_MASK GENMASK(7, 4) +#define REG12_CK_DIV_MASK GENMASK(5, 4) + +#define REG13_TG_CODE_LOW_MASK GENMASK(7, 0) + +#define REG14_TOL_MASK GENMASK(7, 4) +#define REG14_RP_CODE_MASK GENMASK(3, 1) +#define REG14_TG_CODE_HIGH_MASK GENMASK(0, 0) + +#define REG21_SEL_TX_CK_INV BIT(7) +#define REG21_PMS_S_MASK GENMASK(3, 0) +/* + * REG33 does not match the ref manual. According to Sandor Yu from NXP, + * "There is a doc issue on the i.MX8MP latest RM" + * REG33 is being used per guidance from Sandor + */ +#define REG33_MODE_SET_DONE BIT(7) +#define REG33_FIX_DA BIT(1) + +#define REG34_PHY_READY BIT(7) +#define REG34_PLL_LOCK BIT(6) +#define REG34_PHY_CLK_READY BIT(5) + +#ifndef MHZ +#define MHZ (1000UL * 1000UL) +#endif + +#define PHY_PLL_DIV_REGS_NUM 7 + +struct phy_config { + u32 pixclk; + u8 pll_div_regs[PHY_PLL_DIV_REGS_NUM]; +}; + +/* + * The calculated_phy_pll_cfg only handles integer divider for PMS, + * meaning the last four entries will be fixed, but the first three will + * be calculated by the PMS calculator. + */ +static struct phy_config calculated_phy_pll_cfg = { + .pixclk = 0, + .pll_div_regs = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00 }, +}; + +/* The lookup table contains values for which the fractional divder is used */ +static const struct phy_config phy_pll_cfg[] = { + { + .pixclk = 22250000, + .pll_div_regs = { 0xd1, 0x4b, 0xf1, 0x89, 0x88, 0x80, 0x40 }, + }, { + .pixclk = 23750000, + .pll_div_regs = { 0xd1, 0x50, 0xf1, 0x86, 0x85, 0x80, 0x40 }, + }, { + .pixclk = 24024000, + .pll_div_regs = { 0xd1, 0x50, 0xf1, 0x99, 0x02, 0x80, 0x40 }, + }, { + .pixclk = 25175000, + .pll_div_regs = { 0xd1, 0x54, 0xfc, 0xcc, 0x91, 0x80, 0x40 }, + }, { + .pixclk = 26750000, + .pll_div_regs = { 0xd1, 0x5a, 0xf2, 0x89, 0x88, 0x80, 0x40 }, + }, { + .pixclk = 27027000, + .pll_div_regs = { 0xd1, 0x5a, 0xf2, 0xfd, 0x0c, 0x80, 0x40 }, + }, { + .pixclk = 29500000, + .pll_div_regs = { 0xd1, 0x62, 0xf4, 0x95, 0x08, 0x80, 0x40 }, + }, { + .pixclk = 30750000, + .pll_div_regs = { 0xd1, 0x66, 0xf4, 0x82, 0x01, 0x88, 0x45 }, + }, { + .pixclk = 30888000, + .pll_div_regs = { 0xd1, 0x66, 0xf4, 0x99, 0x18, 0x88, 0x45 }, + }, { + .pixclk = 33750000, + .pll_div_regs = { 0xd1, 0x70, 0xf4, 0x82, 0x01, 0x80, 0x40 }, + }, { + .pixclk = 35000000, + .pll_div_regs = { 0xd1, 0x58, 0xb8, 0x8b, 0x88, 0x80, 0x40 }, + }, { + .pixclk = 36036000, + .pll_div_regs = { 0xd1, 0x5a, 0xb2, 0xfd, 0x0c, 0x80, 0x40 }, + }, { + .pixclk = 43243200, + .pll_div_regs = { 0xd1, 0x5a, 0x92, 0xfd, 0x0c, 0x80, 0x40 }, + }, { + .pixclk = 44500000, + .pll_div_regs = { 0xd1, 0x5c, 0x92, 0x98, 0x11, 0x84, 0x41 }, + }, { + .pixclk = 47000000, + .pll_div_regs = { 0xd1, 0x62, 0x94, 0x95, 0x82, 0x80, 0x40 }, + }, { + .pixclk = 47500000, + .pll_div_regs = { 0xd1, 0x63, 0x96, 0xa1, 0x82, 0x80, 0x40 }, + }, { + .pixclk = 50349650, + .pll_div_regs = { 0xd1, 0x54, 0x7c, 0xc3, 0x8f, 0x80, 0x40 }, + }, { + .pixclk = 53250000, + .pll_div_regs = { 0xd1, 0x58, 0x72, 0x84, 0x03, 0x82, 0x41 }, + }, { + .pixclk = 53500000, + .pll_div_regs = { 0xd1, 0x5a, 0x72, 0x89, 0x88, 0x80, 0x40 }, + }, { + .pixclk = 54054000, + .pll_div_regs = { 0xd1, 0x5a, 0x72, 0xfd, 0x0c, 0x80, 0x40 }, + }, { + .pixclk = 59000000, + .pll_div_regs = { 0xd1, 0x62, 0x74, 0x95, 0x08, 0x80, 0x40 }, + }, { + .pixclk = 59340659, + .pll_div_regs = { 0xd1, 0x62, 0x74, 0xdb, 0x52, 0x88, 0x47 }, + }, { + .pixclk = 61500000, + .pll_div_regs = { 0xd1, 0x66, 0x74, 0x82, 0x01, 0x88, 0x45 }, + }, { + .pixclk = 63500000, + .pll_div_regs = { 0xd1, 0x69, 0x74, 0x89, 0x08, 0x80, 0x40 }, + }, { + .pixclk = 67500000, + .pll_div_regs = { 0xd1, 0x54, 0x52, 0x87, 0x03, 0x80, 0x40 }, + }, { + .pixclk = 70000000, + .pll_div_regs = { 0xd1, 0x58, 0x58, 0x8b, 0x88, 0x80, 0x40 }, + }, { + .pixclk = 72072000, + .pll_div_regs = { 0xd1, 0x5a, 0x52, 0xfd, 0x0c, 0x80, 0x40 }, + }, { + .pixclk = 74176000, + .pll_div_regs = { 0xd1, 0x5d, 0x58, 0xdb, 0xA2, 0x88, 0x41 }, + }, { + .pixclk = 74250000, + .pll_div_regs = { 0xd1, 0x5c, 0x52, 0x90, 0x0d, 0x84, 0x41 }, + }, { + .pixclk = 78500000, + .pll_div_regs = { 0xd1, 0x62, 0x54, 0x87, 0x01, 0x80, 0x40 }, + }, { + .pixclk = 82000000, + .pll_div_regs = { 0xd1, 0x66, 0x54, 0x82, 0x01, 0x88, 0x45 }, + }, { + .pixclk = 82500000, + .pll_div_regs = { 0xd1, 0x67, 0x54, 0x88, 0x01, 0x90, 0x49 }, + }, { + .pixclk = 89000000, + .pll_div_regs = { 0xd1, 0x70, 0x54, 0x84, 0x83, 0x80, 0x40 }, + }, { + .pixclk = 90000000, + .pll_div_regs = { 0xd1, 0x70, 0x54, 0x82, 0x01, 0x80, 0x40 }, + }, { + .pixclk = 94000000, + .pll_div_regs = { 0xd1, 0x4e, 0x32, 0xa7, 0x10, 0x80, 0x40 }, + }, { + .pixclk = 95000000, + .pll_div_regs = { 0xd1, 0x50, 0x31, 0x86, 0x85, 0x80, 0x40 }, + }, { + .pixclk = 98901099, + .pll_div_regs = { 0xd1, 0x52, 0x3a, 0xdb, 0x4c, 0x88, 0x47 }, + }, { + .pixclk = 99000000, + .pll_div_regs = { 0xd1, 0x52, 0x32, 0x82, 0x01, 0x88, 0x47 }, + }, { + .pixclk = 100699300, + .pll_div_regs = { 0xd1, 0x54, 0x3c, 0xc3, 0x8f, 0x80, 0x40 }, + }, { + .pixclk = 102500000, + .pll_div_regs = { 0xd1, 0x55, 0x32, 0x8c, 0x05, 0x90, 0x4b }, + }, { + .pixclk = 104750000, + .pll_div_regs = { 0xd1, 0x57, 0x32, 0x98, 0x07, 0x90, 0x49 }, + }, { + .pixclk = 106500000, + .pll_div_regs = { 0xd1, 0x58, 0x32, 0x84, 0x03, 0x82, 0x41 }, + }, { + .pixclk = 107000000, + .pll_div_regs = { 0xd1, 0x5a, 0x32, 0x89, 0x88, 0x80, 0x40 }, + }, { + .pixclk = 108108000, + .pll_div_regs = { 0xd1, 0x5a, 0x32, 0xfd, 0x0c, 0x80, 0x40 }, + }, { + .pixclk = 118000000, + .pll_div_regs = { 0xd1, 0x62, 0x34, 0x95, 0x08, 0x80, 0x40 }, + }, { + .pixclk = 123000000, + .pll_div_regs = { 0xd1, 0x66, 0x34, 0x82, 0x01, 0x88, 0x45 }, + }, { + .pixclk = 127000000, + .pll_div_regs = { 0xd1, 0x69, 0x34, 0x89, 0x08, 0x80, 0x40 }, + }, { + .pixclk = 135000000, + .pll_div_regs = { 0xd1, 0x70, 0x34, 0x82, 0x01, 0x80, 0x40 }, + }, { + .pixclk = 135580000, + .pll_div_regs = { 0xd1, 0x71, 0x39, 0xe9, 0x82, 0x9c, 0x5b }, + }, { + .pixclk = 137520000, + .pll_div_regs = { 0xd1, 0x72, 0x38, 0x99, 0x10, 0x85, 0x41 }, + }, { + .pixclk = 138750000, + .pll_div_regs = { 0xd1, 0x73, 0x35, 0x88, 0x05, 0x90, 0x4d }, + }, { + .pixclk = 140000000, + .pll_div_regs = { 0xd1, 0x75, 0x36, 0xa7, 0x90, 0x80, 0x40 }, + }, { + .pixclk = 148352000, + .pll_div_regs = { 0xd1, 0x7b, 0x35, 0xdb, 0x39, 0x90, 0x45 }, + }, { + .pixclk = 148500000, + .pll_div_regs = { 0xd1, 0x7b, 0x35, 0x84, 0x03, 0x90, 0x45 }, + }, { + .pixclk = 154000000, + .pll_div_regs = { 0xd1, 0x40, 0x18, 0x83, 0x01, 0x00, 0x40 }, + }, { + .pixclk = 157000000, + .pll_div_regs = { 0xd1, 0x41, 0x11, 0xa7, 0x14, 0x80, 0x40 }, + }, { + .pixclk = 160000000, + .pll_div_regs = { 0xd1, 0x42, 0x12, 0xa1, 0x20, 0x80, 0x40 }, + }, { + .pixclk = 162000000, + .pll_div_regs = { 0xd1, 0x43, 0x18, 0x8b, 0x08, 0x96, 0x55 }, + }, { + .pixclk = 164000000, + .pll_div_regs = { 0xd1, 0x45, 0x11, 0x83, 0x82, 0x90, 0x4b }, + }, { + .pixclk = 165000000, + .pll_div_regs = { 0xd1, 0x45, 0x11, 0x84, 0x81, 0x90, 0x4b }, + }, { + .pixclk = 185625000, + .pll_div_regs = { 0xd1, 0x4e, 0x12, 0x9a, 0x95, 0x80, 0x40 }, + }, { + .pixclk = 188000000, + .pll_div_regs = { 0xd1, 0x4e, 0x12, 0xa7, 0x10, 0x80, 0x40 }, + }, { + .pixclk = 198000000, + .pll_div_regs = { 0xd1, 0x52, 0x12, 0x82, 0x01, 0x88, 0x47 }, + }, { + .pixclk = 205000000, + .pll_div_regs = { 0xd1, 0x55, 0x12, 0x8c, 0x05, 0x90, 0x4b }, + }, { + .pixclk = 209500000, + .pll_div_regs = { 0xd1, 0x57, 0x12, 0x98, 0x07, 0x90, 0x49 }, + }, { + .pixclk = 213000000, + .pll_div_regs = { 0xd1, 0x58, 0x12, 0x84, 0x03, 0x82, 0x41 }, + }, { + .pixclk = 216216000, + .pll_div_regs = { 0xd1, 0x5a, 0x12, 0xfd, 0x0c, 0x80, 0x40 }, + }, { + .pixclk = 254000000, + .pll_div_regs = { 0xd1, 0x69, 0x14, 0x89, 0x08, 0x80, 0x40 }, + }, { + .pixclk = 277500000, + .pll_div_regs = { 0xd1, 0x73, 0x15, 0x88, 0x05, 0x90, 0x4d }, + }, { + .pixclk = 297000000, + .pll_div_regs = { 0xd1, 0x7b, 0x15, 0x84, 0x03, 0x90, 0x45 }, + }, +}; + +struct reg_settings { + u8 reg; + u8 val; +}; + +static const struct reg_settings common_phy_cfg[] = { + { PHY_REG(0), 0x00 }, + /* PHY_REG(1-7) pix clk specific */ + { PHY_REG(8), 0x4f }, { PHY_REG(9), 0x30 }, + { PHY_REG(10), 0x33 }, { PHY_REG(11), 0x65 }, + /* REG12 pixclk specific */ + /* REG13 pixclk specific */ + /* REG14 pixclk specific */ + { PHY_REG(15), 0x80 }, { PHY_REG(16), 0x6c }, + { PHY_REG(17), 0xf2 }, { PHY_REG(18), 0x67 }, + { PHY_REG(19), 0x00 }, { PHY_REG(20), 0x10 }, + /* REG21 pixclk specific */ + { PHY_REG(22), 0x30 }, { PHY_REG(23), 0x32 }, + { PHY_REG(24), 0x60 }, { PHY_REG(25), 0x8f }, + { PHY_REG(26), 0x00 }, { PHY_REG(27), 0x00 }, + { PHY_REG(28), 0x08 }, { PHY_REG(29), 0x00 }, + { PHY_REG(30), 0x00 }, { PHY_REG(31), 0x00 }, + { PHY_REG(32), 0x00 }, { PHY_REG(33), 0x80 }, + { PHY_REG(34), 0x00 }, { PHY_REG(35), 0x00 }, + { PHY_REG(36), 0x00 }, { PHY_REG(37), 0x00 }, + { PHY_REG(38), 0x00 }, { PHY_REG(39), 0x00 }, + { PHY_REG(40), 0x00 }, { PHY_REG(41), 0xe0 }, + { PHY_REG(42), 0x83 }, { PHY_REG(43), 0x0f }, + { PHY_REG(44), 0x3E }, { PHY_REG(45), 0xf8 }, + { PHY_REG(46), 0x00 }, { PHY_REG(47), 0x00 } +}; + +struct fsl_samsung_hdmi_phy { + struct device *dev; + void __iomem *regs; + struct clk *apbclk; + struct clk *refclk; + + /* clk provider */ + struct clk_hw hw; + const struct phy_config *cur_cfg; +}; + +static inline struct fsl_samsung_hdmi_phy * +to_fsl_samsung_hdmi_phy(struct clk_hw *hw) +{ + return container_of(hw, struct fsl_samsung_hdmi_phy, hw); +} + +static int +fsl_samsung_hdmi_phy_configure_pll_lock_det(struct fsl_samsung_hdmi_phy *phy, + const struct phy_config *cfg) +{ + u32 pclk = cfg->pixclk; + u32 fld_tg_code; + u32 int_pllclk; + u8 div; + + /* Find int_pllclk speed */ + for (div = 0; div < 4; div++) { + int_pllclk = pclk / (1 << div); + if (int_pllclk < (50 * MHZ)) + break; + } + + if (unlikely(div == 4)) + return -EINVAL; + + writeb(FIELD_PREP(REG12_CK_DIV_MASK, div), phy->regs + PHY_REG(12)); + + /* + * Calculation for the frequency lock detector target code (fld_tg_code) + * is based on reference manual register description of PHY_REG13 + * (13.10.3.1.14.2): + * 1st) Calculate int_pllclk which is determinded by FLD_CK_DIV + * 2nd) Increase resolution to avoid rounding issues + * 3th) Do the div (256 / Freq. of int_pllclk) * 24 + * 4th) Reduce the resolution and always round up since the NXP + * settings rounding up always too. TODO: Check if that is + * correct. + */ + + fld_tg_code = DIV_ROUND_UP(24 * MHZ * 256, int_pllclk); + + /* FLD_TOL and FLD_RP_CODE taken from downstream driver */ + writeb(FIELD_PREP(REG13_TG_CODE_LOW_MASK, fld_tg_code), + phy->regs + PHY_REG(13)); + writeb(FIELD_PREP(REG14_TOL_MASK, 2) | + FIELD_PREP(REG14_RP_CODE_MASK, 2) | + FIELD_PREP(REG14_TG_CODE_HIGH_MASK, fld_tg_code >> 8), + phy->regs + PHY_REG(14)); + + return 0; +} + +static unsigned long fsl_samsung_hdmi_phy_find_pms(unsigned long fout, u8 *p, u16 *m, u8 *s) +{ + unsigned long best_freq = 0; + u32 min_delta = 0xffffffff; + u8 _p, best_p; + u16 _m, best_m; + u8 _s, best_s; + + /* + * Figure 13-78 of the reference manual states the PLL should be TMDS x 5 + * while the TMDS_CLKO should be the PLL / 5. So to calculate the PLL, + * take the pix clock x 5, then return the value of the PLL / 5. + */ + fout *= 5; + + /* The ref manual states the values of 'P' range from 1 to 11 */ + for (_p = 1; _p <= 11; ++_p) { + for (_s = 1; _s <= 16; ++_s) { + u64 tmp; + u32 delta; + + /* s must be one or even */ + if (_s > 1 && (_s & 0x01) == 1) + _s++; + + /* _s cannot be 14 per the TRM */ + if (_s == 14) + continue; + + /* + * The Ref manual doesn't explicitly state the range of M, + * but it does show it as an 8-bit value, so reject + * any value above 255. + */ + tmp = (u64)fout * (_p * _s); + do_div(tmp, 24 * MHZ); + if (tmp > 255) + continue; + _m = tmp; + + /* + * Rev 2 of the Ref Manual states the + * VCO can range between 750MHz and + * 3GHz. The VCO is assumed to be + * Fvco = (M * f_ref) / P, + * where f_ref is 24MHz. + */ + tmp = div64_ul((u64)_m * 24 * MHZ, _p); + if (tmp < 750 * MHZ || + tmp > 3000 * MHZ) + continue; + + /* Final frequency after post-divider */ + do_div(tmp, _s); + + delta = abs(fout - tmp); + if (delta < min_delta) { + best_p = _p; + best_s = _s; + best_m = _m; + min_delta = delta; + best_freq = tmp; + } + + /* If we have an exact match, stop looking for a better value */ + if (!delta) + goto done; + } + } +done: + if (best_freq) { + *p = best_p; + *m = best_m; + *s = best_s; + } + + return best_freq / 5; +} + +static int fsl_samsung_hdmi_phy_configure(struct fsl_samsung_hdmi_phy *phy, + const struct phy_config *cfg) +{ + int i, ret; + u8 val; + + phy->cur_cfg = cfg; + + /* HDMI PHY init */ + writeb(REG33_FIX_DA, phy->regs + PHY_REG(33)); + + /* common PHY registers */ + for (i = 0; i < ARRAY_SIZE(common_phy_cfg); i++) + writeb(common_phy_cfg[i].val, phy->regs + common_phy_cfg[i].reg); + + /* set individual PLL registers PHY_REG1 ... PHY_REG7 */ + for (i = 0; i < PHY_PLL_DIV_REGS_NUM; i++) + writeb(cfg->pll_div_regs[i], phy->regs + PHY_REG(1) + i * 4); + + /* High nibble of PHY_REG3 and low nibble of PHY_REG21 both contain 'S' */ + writeb(REG21_SEL_TX_CK_INV | FIELD_PREP(REG21_PMS_S_MASK, + cfg->pll_div_regs[2] >> 4), phy->regs + PHY_REG(21)); + + ret = fsl_samsung_hdmi_phy_configure_pll_lock_det(phy, cfg); + if (ret) { + dev_err(phy->dev, "pixclock too large\n"); + return ret; + } + + writeb(REG33_FIX_DA | REG33_MODE_SET_DONE, phy->regs + PHY_REG(33)); + + ret = readb_poll_timeout(phy->regs + PHY_REG(34), val, + val & REG34_PLL_LOCK, 50, 20000); + if (ret) + dev_err(phy->dev, "PLL failed to lock\n"); + + return ret; +} + +static unsigned long phy_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw); + + if (!phy->cur_cfg) + return 74250000; + + return phy->cur_cfg->pixclk; +} + +/* Helper function to lookup the available fractional-divider rate */ +static const struct phy_config *fsl_samsung_hdmi_phy_lookup_rate(unsigned long rate) +{ + int i; + + /* Search the lookup table */ + for (i = ARRAY_SIZE(phy_pll_cfg) - 1; i >= 0; i--) + if (phy_pll_cfg[i].pixclk <= rate) + break; + + /* If there is an exact match, or the array has been searched, return the value*/ + if (phy_pll_cfg[i].pixclk == rate || i + 1 > ARRAY_SIZE(phy_pll_cfg) - 1) + return &phy_pll_cfg[i]; + + /* See if the next entry is closer to nominal than this one */ + return (abs((long) rate - (long) phy_pll_cfg[i].pixclk) < + abs((long) rate - (long) phy_pll_cfg[i+1].pixclk) ? + &phy_pll_cfg[i] : &phy_pll_cfg[i+1]); +} + +static void fsl_samsung_hdmi_calculate_phy(struct phy_config *cal_phy, unsigned long rate, + u8 p, u16 m, u8 s) +{ + cal_phy->pixclk = rate; + cal_phy->pll_div_regs[0] = FIELD_PREP(REG01_PMS_P_MASK, p); + cal_phy->pll_div_regs[1] = m; + cal_phy->pll_div_regs[2] = FIELD_PREP(REG03_PMS_S_MASK, s-1); + /* pll_div_regs 3-6 are fixed and pre-defined already */ +} + +static +const struct phy_config *fsl_samsung_hdmi_phy_find_settings(struct fsl_samsung_hdmi_phy *phy, + unsigned long rate) +{ + const struct phy_config *fract_div_phy; + u32 int_div_clk; + u16 m; + u8 p, s; + + /* If the clock is out of range return error instead of searching */ + if (rate > 297000000 || rate < 22250000) + return NULL; + + /* Search the fractional divider lookup table */ + fract_div_phy = fsl_samsung_hdmi_phy_lookup_rate(rate); + if (fract_div_phy->pixclk == rate) { + dev_dbg(phy->dev, "fractional divider match = %u\n", fract_div_phy->pixclk); + return fract_div_phy; + } + + /* Calculate the integer divider */ + int_div_clk = fsl_samsung_hdmi_phy_find_pms(rate, &p, &m, &s); + fsl_samsung_hdmi_calculate_phy(&calculated_phy_pll_cfg, int_div_clk, p, m, s); + if (int_div_clk == rate) { + dev_dbg(phy->dev, "integer divider match = %u\n", calculated_phy_pll_cfg.pixclk); + return &calculated_phy_pll_cfg; + } + + /* Calculate the absolute value of the differences and return whichever is closest */ + if (abs((long)rate - (long)int_div_clk) < + abs((long)rate - (long)fract_div_phy->pixclk)) { + dev_dbg(phy->dev, "integer divider = %u\n", calculated_phy_pll_cfg.pixclk); + return &calculated_phy_pll_cfg; + } + + dev_dbg(phy->dev, "fractional divider = %u\n", phy->cur_cfg->pixclk); + + return fract_div_phy; +} + +static int fsl_samsung_hdmi_phy_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw); + const struct phy_config *target_settings = fsl_samsung_hdmi_phy_find_settings(phy, + req->rate); + + if (target_settings == NULL) + return -EINVAL; + + dev_dbg(phy->dev, "round_rate, closest rate = %u\n", target_settings->pixclk); + req->rate = target_settings->pixclk; + + return 0; +} + +static int fsl_samsung_hdmi_phy_clk_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct fsl_samsung_hdmi_phy *phy = to_fsl_samsung_hdmi_phy(hw); + const struct phy_config *target_settings = fsl_samsung_hdmi_phy_find_settings(phy, rate); + + if (target_settings == NULL) + return -EINVAL; + + dev_dbg(phy->dev, "set_rate, closest rate = %u\n", target_settings->pixclk); + + return fsl_samsung_hdmi_phy_configure(phy, target_settings); +} + +static const struct clk_ops phy_clk_ops = { + .recalc_rate = phy_clk_recalc_rate, + .determine_rate = fsl_samsung_hdmi_phy_clk_determine_rate, + .set_rate = fsl_samsung_hdmi_phy_clk_set_rate, +}; + +static int phy_clk_register(struct fsl_samsung_hdmi_phy *phy) +{ + struct device *dev = phy->dev; + struct device_node *np = dev->of_node; + struct clk_init_data init; + const char *parent_name; + struct clk *phyclk; + int ret; + + parent_name = __clk_get_name(phy->refclk); + + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = 0; + init.name = "hdmi_pclk"; + init.ops = &phy_clk_ops; + + phy->hw.init = &init; + + phyclk = devm_clk_register(dev, &phy->hw); + if (IS_ERR(phyclk)) + return dev_err_probe(dev, PTR_ERR(phyclk), + "failed to register clock\n"); + + ret = of_clk_add_hw_provider(np, of_clk_hw_simple_get, phyclk); + if (ret) + return dev_err_probe(dev, ret, + "failed to register clock provider\n"); + + return 0; +} + +static int fsl_samsung_hdmi_phy_probe(struct platform_device *pdev) +{ + struct fsl_samsung_hdmi_phy *phy; + int ret; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + platform_set_drvdata(pdev, phy); + phy->dev = &pdev->dev; + + phy->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(phy->regs)) + return PTR_ERR(phy->regs); + + phy->apbclk = devm_clk_get_enabled(phy->dev, "apb"); + if (IS_ERR(phy->apbclk)) + return dev_err_probe(phy->dev, PTR_ERR(phy->apbclk), + "failed to get apb clk\n"); + + phy->refclk = devm_clk_get(phy->dev, "ref"); + if (IS_ERR(phy->refclk)) + return dev_err_probe(phy->dev, PTR_ERR(phy->refclk), + "failed to get ref clk\n"); + + pm_runtime_get_noresume(phy->dev); + pm_runtime_set_active(phy->dev); + pm_runtime_enable(phy->dev); + + ret = phy_clk_register(phy); + if (ret) { + dev_err(&pdev->dev, "register clk failed\n"); + goto register_clk_failed; + } + + pm_runtime_put(phy->dev); + + return 0; + +register_clk_failed: + pm_runtime_disable(phy->dev); + pm_runtime_put_noidle(phy->dev); + return ret; +} + +static void fsl_samsung_hdmi_phy_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + pm_runtime_disable(&pdev->dev); +} + +static int __maybe_unused fsl_samsung_hdmi_phy_suspend(struct device *dev) +{ + struct fsl_samsung_hdmi_phy *phy = dev_get_drvdata(dev); + + clk_disable_unprepare(phy->apbclk); + + return 0; +} + +static int __maybe_unused fsl_samsung_hdmi_phy_resume(struct device *dev) +{ + struct fsl_samsung_hdmi_phy *phy = dev_get_drvdata(dev); + int ret = 0; + + ret = clk_prepare_enable(phy->apbclk); + if (ret) { + dev_err(phy->dev, "failed to enable apbclk\n"); + return ret; + } + + if (phy->cur_cfg) + ret = fsl_samsung_hdmi_phy_configure(phy, phy->cur_cfg); + + return ret; + +} + +static DEFINE_RUNTIME_DEV_PM_OPS(fsl_samsung_hdmi_phy_pm_ops, + fsl_samsung_hdmi_phy_suspend, + fsl_samsung_hdmi_phy_resume, NULL); + +static const struct of_device_id fsl_samsung_hdmi_phy_of_match[] = { + { + .compatible = "fsl,imx8mp-hdmi-phy", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, fsl_samsung_hdmi_phy_of_match); + +static struct platform_driver fsl_samsung_hdmi_phy_driver = { + .probe = fsl_samsung_hdmi_phy_probe, + .remove = fsl_samsung_hdmi_phy_remove, + .driver = { + .name = "fsl-samsung-hdmi-phy", + .of_match_table = fsl_samsung_hdmi_phy_of_match, + .pm = pm_ptr(&fsl_samsung_hdmi_phy_pm_ops), + }, +}; +module_platform_driver(fsl_samsung_hdmi_phy_driver); + +MODULE_AUTHOR("Sandor Yu <Sandor.yu@nxp.com>"); +MODULE_DESCRIPTION("SAMSUNG HDMI 2.0 Transmitter PHY Driver"); +MODULE_LICENSE("GPL"); |
