* [PATCH] phy: tegra: xusb: Fix per-pad high-speed termination calibration
@ 2026-03-04 10:26 Wei-Cheng Chen
2026-03-24 9:11 ` Jon Hunter
0 siblings, 1 reply; 2+ messages in thread
From: Wei-Cheng Chen @ 2026-03-04 10:26 UTC (permalink / raw)
To: jckuo, vkoul, neil.armstrong, thierry.reding, jonathanh
Cc: linux-phy, linux-tegra, linux-kernel, waynec, wtsai,
Wei-Cheng Chen
From: Wayne Chang <waynec@nvidia.com>
The existing code reads a single hs_term_range_adj value from bit field
[10:7] of FUSE_SKU_CALIB_0 and applies it to all USB2 pads uniformly.
However, on SoCs that support per-pad termination, each pad has its own
hs_term_range_adj field: pad 0 in FUSE_SKU_CALIB_0[10:7], and pads 1-3
in FUSE_USB_CALIB_EXT_0 at bit offsets [8:5], [12:9], and [16:13]
respectively.
Fix the calibration by reading per-pad values from the appropriate fuse
registers. For SoCs that do not support per-pad termination, replicate
pad 0's value to all pads to maintain existing behavior.
Add a has_per_pad_term flag to the SoC data to indicate whether per-pad
termination values are available in FUSE_USB_CALIB_EXT_0.
Fixes: 1ef535c6ba8e ("phy: tegra: xusb: Add Tegra194 support")
Cc: stable@vger.kernel.org
Signed-off-by: Wayne Chang <waynec@nvidia.com>
Signed-off-by: Wei-Cheng Chen <weichengc@nvidia.com>
---
drivers/phy/tegra/xusb-tegra186.c | 33 ++++++++++++++++++++++++-------
drivers/phy/tegra/xusb.h | 1 +
2 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c
index bec9616c4a2..4452e73fb82 100644
--- a/drivers/phy/tegra/xusb-tegra186.c
+++ b/drivers/phy/tegra/xusb-tegra186.c
@@ -20,8 +20,8 @@
/* FUSE USB_CALIB registers */
#define HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? (11 + (x - 1) * 6) : 0)
#define HS_CURR_LEVEL_PAD_MASK 0x3f
-#define HS_TERM_RANGE_ADJ_SHIFT 7
-#define HS_TERM_RANGE_ADJ_MASK 0xf
+#define HS_TERM_RANGE_ADJ_PADX_SHIFT(x) ((x) ? (5 + (x - 1) * 4) : 7)
+#define HS_TERM_RANGE_ADJ_PAD_MASK 0xf
#define HS_SQUELCH_SHIFT 29
#define HS_SQUELCH_MASK 0x7
@@ -253,7 +253,7 @@
struct tegra_xusb_fuse_calibration {
u32 *hs_curr_level;
u32 hs_squelch;
- u32 hs_term_range_adj;
+ u32 *hs_term_range_adj;
u32 rpd_ctrl;
};
@@ -930,7 +930,7 @@ static int tegra186_utmi_phy_power_on(struct phy *phy)
value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
value &= ~TERM_RANGE_ADJ(~0);
- value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj);
+ value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj[index]);
value &= ~RPD_CTRL(~0);
value |= RPD_CTRL(priv->calib.rpd_ctrl);
padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
@@ -1464,17 +1464,23 @@ static const char * const tegra186_usb3_functions[] = {
static int
tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
{
+ const struct tegra_xusb_padctl_soc *soc = padctl->base.soc;
struct device *dev = padctl->base.dev;
unsigned int i, count;
u32 value, *level;
+ u32 *hs_term_range_adj;
int err;
- count = padctl->base.soc->ports.usb2.count;
+ count = soc->ports.usb2.count;
level = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL);
if (!level)
return -ENOMEM;
+ hs_term_range_adj = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL);
+ if (!hs_term_range_adj)
+ return -ENOMEM;
+
err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
if (err)
return dev_err_probe(dev, err,
@@ -1490,8 +1496,8 @@ tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
padctl->calib.hs_squelch = (value >> HS_SQUELCH_SHIFT) &
HS_SQUELCH_MASK;
- padctl->calib.hs_term_range_adj = (value >> HS_TERM_RANGE_ADJ_SHIFT) &
- HS_TERM_RANGE_ADJ_MASK;
+ hs_term_range_adj[0] = (value >> HS_TERM_RANGE_ADJ_PADX_SHIFT(0)) &
+ HS_TERM_RANGE_ADJ_PAD_MASK;
err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value);
if (err) {
@@ -1503,6 +1509,17 @@ tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
padctl->calib.rpd_ctrl = (value >> RPD_CTRL_SHIFT) & RPD_CTRL_MASK;
+ for (i = 1; i < count; i++) {
+ if (soc->has_per_pad_term)
+ hs_term_range_adj[i] =
+ (value >> HS_TERM_RANGE_ADJ_PADX_SHIFT(i)) &
+ HS_TERM_RANGE_ADJ_PAD_MASK;
+ else
+ hs_term_range_adj[i] = hs_term_range_adj[0];
+ }
+
+ padctl->calib.hs_term_range_adj = hs_term_range_adj;
+
return 0;
}
@@ -1708,6 +1725,7 @@ const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc = {
.num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names),
.supports_gen2 = true,
.poll_trk_completed = true,
+ .has_per_pad_term = true,
};
EXPORT_SYMBOL_GPL(tegra194_xusb_padctl_soc);
@@ -1732,6 +1750,7 @@ const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc = {
.trk_hw_mode = false,
.trk_update_on_idle = true,
.supports_lp_cfg_en = true,
+ .has_per_pad_term = true,
};
EXPORT_SYMBOL_GPL(tegra234_xusb_padctl_soc);
#endif
diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h
index d2b5f956513..810b410672f 100644
--- a/drivers/phy/tegra/xusb.h
+++ b/drivers/phy/tegra/xusb.h
@@ -436,6 +436,7 @@ struct tegra_xusb_padctl_soc {
bool trk_hw_mode;
bool trk_update_on_idle;
bool supports_lp_cfg_en;
+ bool has_per_pad_term;
};
struct tegra_xusb_padctl {
--
2.43.0
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply related [flat|nested] 2+ messages in thread* Re: [PATCH] phy: tegra: xusb: Fix per-pad high-speed termination calibration
2026-03-04 10:26 [PATCH] phy: tegra: xusb: Fix per-pad high-speed termination calibration Wei-Cheng Chen
@ 2026-03-24 9:11 ` Jon Hunter
0 siblings, 0 replies; 2+ messages in thread
From: Jon Hunter @ 2026-03-24 9:11 UTC (permalink / raw)
To: Wei-Cheng Chen, jckuo, vkoul, neil.armstrong, thierry.reding
Cc: linux-phy, linux-tegra, linux-kernel, waynec, wtsai
On 04/03/2026 10:26, Wei-Cheng Chen wrote:
> From: Wayne Chang <waynec@nvidia.com>
>
> The existing code reads a single hs_term_range_adj value from bit field
> [10:7] of FUSE_SKU_CALIB_0 and applies it to all USB2 pads uniformly.
> However, on SoCs that support per-pad termination, each pad has its own
> hs_term_range_adj field: pad 0 in FUSE_SKU_CALIB_0[10:7], and pads 1-3
> in FUSE_USB_CALIB_EXT_0 at bit offsets [8:5], [12:9], and [16:13]
> respectively.
>
> Fix the calibration by reading per-pad values from the appropriate fuse
> registers. For SoCs that do not support per-pad termination, replicate
> pad 0's value to all pads to maintain existing behavior.
>
> Add a has_per_pad_term flag to the SoC data to indicate whether per-pad
> termination values are available in FUSE_USB_CALIB_EXT_0.
>
> Fixes: 1ef535c6ba8e ("phy: tegra: xusb: Add Tegra194 support")
> Cc: stable@vger.kernel.org
> Signed-off-by: Wayne Chang <waynec@nvidia.com>
> Signed-off-by: Wei-Cheng Chen <weichengc@nvidia.com>
> ---
> drivers/phy/tegra/xusb-tegra186.c | 33 ++++++++++++++++++++++++-------
> drivers/phy/tegra/xusb.h | 1 +
> 2 files changed, 27 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/phy/tegra/xusb-tegra186.c b/drivers/phy/tegra/xusb-tegra186.c
> index bec9616c4a2..4452e73fb82 100644
> --- a/drivers/phy/tegra/xusb-tegra186.c
> +++ b/drivers/phy/tegra/xusb-tegra186.c
> @@ -20,8 +20,8 @@
> /* FUSE USB_CALIB registers */
> #define HS_CURR_LEVEL_PADX_SHIFT(x) ((x) ? (11 + (x - 1) * 6) : 0)
> #define HS_CURR_LEVEL_PAD_MASK 0x3f
> -#define HS_TERM_RANGE_ADJ_SHIFT 7
> -#define HS_TERM_RANGE_ADJ_MASK 0xf
> +#define HS_TERM_RANGE_ADJ_PADX_SHIFT(x) ((x) ? (5 + (x - 1) * 4) : 7)
> +#define HS_TERM_RANGE_ADJ_PAD_MASK 0xf
> #define HS_SQUELCH_SHIFT 29
> #define HS_SQUELCH_MASK 0x7
>
> @@ -253,7 +253,7 @@
> struct tegra_xusb_fuse_calibration {
> u32 *hs_curr_level;
> u32 hs_squelch;
> - u32 hs_term_range_adj;
> + u32 *hs_term_range_adj;
> u32 rpd_ctrl;
> };
>
> @@ -930,7 +930,7 @@ static int tegra186_utmi_phy_power_on(struct phy *phy)
>
> value = padctl_readl(padctl, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
> value &= ~TERM_RANGE_ADJ(~0);
> - value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj);
> + value |= TERM_RANGE_ADJ(priv->calib.hs_term_range_adj[index]);
> value &= ~RPD_CTRL(~0);
> value |= RPD_CTRL(priv->calib.rpd_ctrl);
> padctl_writel(padctl, value, XUSB_PADCTL_USB2_OTG_PADX_CTL1(index));
> @@ -1464,17 +1464,23 @@ static const char * const tegra186_usb3_functions[] = {
> static int
> tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
> {
> + const struct tegra_xusb_padctl_soc *soc = padctl->base.soc;
> struct device *dev = padctl->base.dev;
> unsigned int i, count;
> u32 value, *level;
> + u32 *hs_term_range_adj;
> int err;
>
> - count = padctl->base.soc->ports.usb2.count;
> + count = soc->ports.usb2.count;
>
> level = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL);
> if (!level)
> return -ENOMEM;
>
> + hs_term_range_adj = devm_kcalloc(dev, count, sizeof(u32), GFP_KERNEL);
> + if (!hs_term_range_adj)
> + return -ENOMEM;
> +
> err = tegra_fuse_readl(TEGRA_FUSE_SKU_CALIB_0, &value);
> if (err)
> return dev_err_probe(dev, err,
> @@ -1490,8 +1496,8 @@ tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
>
> padctl->calib.hs_squelch = (value >> HS_SQUELCH_SHIFT) &
> HS_SQUELCH_MASK;
> - padctl->calib.hs_term_range_adj = (value >> HS_TERM_RANGE_ADJ_SHIFT) &
> - HS_TERM_RANGE_ADJ_MASK;
> + hs_term_range_adj[0] = (value >> HS_TERM_RANGE_ADJ_PADX_SHIFT(0)) &
> + HS_TERM_RANGE_ADJ_PAD_MASK;
>
> err = tegra_fuse_readl(TEGRA_FUSE_USB_CALIB_EXT_0, &value);
> if (err) {
> @@ -1503,6 +1509,17 @@ tegra186_xusb_read_fuse_calibration(struct tegra186_xusb_padctl *padctl)
>
> padctl->calib.rpd_ctrl = (value >> RPD_CTRL_SHIFT) & RPD_CTRL_MASK;
>
> + for (i = 1; i < count; i++) {
> + if (soc->has_per_pad_term)
> + hs_term_range_adj[i] =
> + (value >> HS_TERM_RANGE_ADJ_PADX_SHIFT(i)) &
> + HS_TERM_RANGE_ADJ_PAD_MASK;
> + else
> + hs_term_range_adj[i] = hs_term_range_adj[0];
> + }
> +
> + padctl->calib.hs_term_range_adj = hs_term_range_adj;
> +
> return 0;
> }
>
> @@ -1708,6 +1725,7 @@ const struct tegra_xusb_padctl_soc tegra194_xusb_padctl_soc = {
> .num_supplies = ARRAY_SIZE(tegra194_xusb_padctl_supply_names),
> .supports_gen2 = true,
> .poll_trk_completed = true,
> + .has_per_pad_term = true,
> };
> EXPORT_SYMBOL_GPL(tegra194_xusb_padctl_soc);
>
> @@ -1732,6 +1750,7 @@ const struct tegra_xusb_padctl_soc tegra234_xusb_padctl_soc = {
> .trk_hw_mode = false,
> .trk_update_on_idle = true,
> .supports_lp_cfg_en = true,
> + .has_per_pad_term = true,
> };
> EXPORT_SYMBOL_GPL(tegra234_xusb_padctl_soc);
> #endif
> diff --git a/drivers/phy/tegra/xusb.h b/drivers/phy/tegra/xusb.h
> index d2b5f956513..810b410672f 100644
> --- a/drivers/phy/tegra/xusb.h
> +++ b/drivers/phy/tegra/xusb.h
> @@ -436,6 +436,7 @@ struct tegra_xusb_padctl_soc {
> bool trk_hw_mode;
> bool trk_update_on_idle;
> bool supports_lp_cfg_en;
> + bool has_per_pad_term;
> };
>
> struct tegra_xusb_padctl {
Reviewed-by: Jon Hunter <jonathanh@nvidia.com>
Tested-by: Jon Hunter <jonathanh@nvidia.com>
Thanks
Jon
--
nvpublic
--
linux-phy mailing list
linux-phy@lists.infradead.org
https://lists.infradead.org/mailman/listinfo/linux-phy
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-03-24 9:11 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-04 10:26 [PATCH] phy: tegra: xusb: Fix per-pad high-speed termination calibration Wei-Cheng Chen
2026-03-24 9:11 ` Jon Hunter
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox