Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# 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
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 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.
@@ -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
@@ -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");
@@ -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");
@@ -0,0 +1,761 @@
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) 2017 NXP. */
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#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_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;
u32 pcs_tx_swing_full;
u32 pcs_tx_deemph_3p5db;
u32 tx_vref_tune;
u32 tx_rise_tune;
u32 tx_preemp_amp_tune;
u32 tx_vboost_level;
u32 comp_dis_tune;
};
static 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;
int ret;
if (tca->orientation == orientation)
return 0;
ret = clk_prepare_enable(imx_phy->clk);
if (ret)
return ret;
tca_blk_orientation_set(tca, orientation);
clk_disable_unprepare(imx_phy->clk);
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(struct typec_switch_dev *sw)
{
typec_switch_unregister(sw);
}
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;
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);
return tca;
}
static void imx95_usb_phy_put_tca(struct imx8mq_usb_phy *imx_phy)
{
struct tca_blk *tca = imx_phy->tca;
if (!tca)
return;
tca_blk_put_typec_switch(tca->sw);
}
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;
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;
}
/* 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);
clk_disable_unprepare(imx_phy->alt_clk);
clk_disable_unprepare(imx_phy->clk);
regulator_disable(imx_phy->vbus);
return 0;
}
static const struct phy_ops imx8mq_usb_phy_ops = {
.init = imx8mq_usb_phy_init,
.power_on = imx8mq_phy_power_on,
.power_off = imx8mq_phy_power_off,
.owner = THIS_MODULE,
};
static const struct phy_ops imx8mp_usb_phy_ops = {
.init = imx8mp_usb_phy_init,
.power_on = imx8mq_phy_power_on,
.power_off = imx8mq_phy_power_off,
.owner = THIS_MODULE,
};
static const struct of_device_id imx8mq_usb_phy_of_match[] = {
{.compatible = "fsl,imx8mq-usb-phy",
.data = &imx8mq_usb_phy_ops,},
{.compatible = "fsl,imx8mp-usb-phy",
.data = &imx8mp_usb_phy_ops,},
{.compatible = "fsl,imx95-usb-phy",
.data = &imx8mp_usb_phy_ops,},
{ }
};
MODULE_DEVICE_TABLE(of, imx8mq_usb_phy_of_match);
static int imx8mq_usb_phy_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct imx8mq_usb_phy *imx_phy;
const struct phy_ops *phy_ops;
imx_phy = devm_kzalloc(dev, sizeof(*imx_phy), GFP_KERNEL);
if (!imx_phy)
return -ENOMEM;
platform_set_drvdata(pdev, imx_phy);
imx_phy->clk = devm_clk_get(dev, "phy");
if (IS_ERR(imx_phy->clk)) {
dev_err(dev, "failed to get imx8mq usb phy clock\n");
return PTR_ERR(imx_phy->clk);
}
imx_phy->alt_clk = devm_clk_get_optional(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);
phy_ops = of_device_get_match_data(dev);
if (!phy_ops)
return -EINVAL;
imx_phy->phy = devm_phy_create(dev, NULL, phy_ops);
if (IS_ERR(imx_phy->phy))
return PTR_ERR(imx_phy->phy);
imx_phy->vbus = devm_regulator_get(dev, "vbus");
if (IS_ERR(imx_phy->vbus))
return dev_err_probe(dev, PTR_ERR(imx_phy->vbus), "failed to get vbus\n");
phy_set_drvdata(imx_phy->phy, imx_phy);
imx_phy->tca = imx95_usb_phy_get_tca(pdev, imx_phy);
if (IS_ERR(imx_phy->tca))
return dev_err_probe(dev, PTR_ERR(imx_phy->tca),
"failed to get tca\n");
imx8m_get_phy_tuning_data(imx_phy);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static void imx8mq_usb_phy_remove(struct platform_device *pdev)
{
struct imx8mq_usb_phy *imx_phy = platform_get_drvdata(pdev);
imx95_usb_phy_put_tca(imx_phy);
}
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,
.suppress_bind_attrs = true,
}
};
module_platform_driver(imx8mq_usb_phy_driver);
MODULE_DESCRIPTION("FSL IMX8MQ USB PHY driver");
MODULE_LICENSE("GPL");
@@ -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, &regmap_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, &regmap_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, &regmap_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");
@@ -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");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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", &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");
@@ -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
@@ -0,0 +1,743 @@
// 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:
return ret;
}
static void fsl_samsung_hdmi_phy_remove(struct platform_device *pdev)
{
of_clk_del_provider(pdev->dev.of_node);
}
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");