Implement RV1106 USB2 PHY signal tuning

Cherry-picked onto the pinned SDK so the ONLY delta versus the pin is this
patch -- the xHCI controller-death investigation needs a controlled build.

The forward-port omitted the vendor driver's rv1106_usb2phy_tuning with the
note "the phy is functional without it". It is functional, but not reliable:
with these registers at reset defaults the HS link is electrically marginal
under sustained traffic, a bulk transfer eventually stalls, and the r8152
Tx-timeout recovery issues an xHCI Stop Endpoint command the controller never
answers. Measured carrier-on to death over four runs: 14s, 519s, 521s, 584s.

Refs flare-edge#160

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MVGTC78dgCGfRANNKjoPea
This commit is contained in:
BFE Engineering
2026-09-22 12:11:14 -06:00
committed by Noah
co-authored by Claude Opus 5
parent ac8658d1d5
commit e12bb2c554
2 changed files with 132 additions and 2 deletions
@@ -1,12 +1,14 @@
* RV1106 USB2 PHY: single OTG port at 0xff3e0000 (ported from the vendor driver; * RV1106 USB2 PHY: single OTG port at 0xff3e0000 (ported from the vendor driver;
* fields map 1:1 to 6.18 except utmi_iddig -> utmi_id, and the 5.10-only * fields map 1:1 to 6.18 except utmi_iddig -> utmi_id, and the 5.10-only
* iddig_output/iddig_en/bvalid_grf_sel are dropped). Signal-quality phy_tuning is * iddig_output/iddig_en/bvalid_grf_sel are dropped). Signal-quality phy_tuning
* left off for now (guarded, so NULL is safe): the phy is functional without it. * was originally left off here; it is now implemented (patches/27-usb-phy-tuning.patch)
* because "functional without it" turned out not to mean reliable -- see #160.
*/ */
static const struct rockchip_usb2phy_cfg rv1106_phy_cfgs[] = { static const struct rockchip_usb2phy_cfg rv1106_phy_cfgs[] = {
{ {
.reg = 0xff3e0000, .reg = 0xff3e0000,
.num_ports = 1, .num_ports = 1,
.phy_tuning = rv1106_usb2phy_tuning,
.clkout_ctl = { 0x0058, 4, 4, 1, 0 }, .clkout_ctl = { 0x0058, 4, 4, 1, 0 },
.port_cfgs = { .port_cfgs = {
[USB2PHY_PORT_OTG] = { [USB2PHY_PORT_OTG] = {
+128
View File
@@ -0,0 +1,128 @@
diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
--- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
+++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
@@ -247,6 +247,7 @@
struct device *dev;
struct regmap *grf;
struct regmap *usbgrf;
+ void __iomem *phy_base;
struct clk_bulk_data *clks;
struct clk *clk480m;
struct clk_hw clk480m_hw;
@@ -1417,6 +1418,19 @@
return -EINVAL;
}
+ /*
+ * RV1106 tunes signal quality through the PHY's own register block, which
+ * is this node's "reg" resource and is distinct from the GRF regmap above.
+ * Map it only where a tuning callback needs it; every other SoC here tunes
+ * through the GRF and must keep working with phy_base left NULL.
+ */
+ if (of_device_is_compatible(np, "rockchip,rv1106-usb2phy")) {
+ rphy->phy_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rphy->phy_base))
+ return dev_err_probe(dev, PTR_ERR(rphy->phy_base),
+ "failed to map phy registers\n");
+ }
+
rphy->phy_reset = devm_reset_control_get_optional(dev, "phy");
if (IS_ERR(rphy->phy_reset))
return PTR_ERR(rphy->phy_reset);
@@ -1506,6 +1520,30 @@
return ret;
}
+/*
+ * Direct PHY-register accessors. Most SoCs here tune through the GRF regmap,
+ * but the RV1106's signal-quality registers live in the PHY's own block, which
+ * is mapped separately into rphy->phy_base.
+ */
+static inline void phy_update_bits(void __iomem *reg, u32 mask, u32 val)
+{
+ u32 tmp = readl(reg);
+
+ tmp &= ~mask;
+ tmp |= val & mask;
+ writel(tmp, reg);
+}
+
+static inline void phy_set_bits(void __iomem *reg, u32 bits)
+{
+ writel(readl(reg) | bits, reg);
+}
+
+static inline void phy_clear_bits(void __iomem *reg, u32 bits)
+{
+ writel(readl(reg) & ~bits, reg);
+}
+
static int rk3128_usb2phy_tuning(struct rockchip_usb2phy *rphy)
{
/* Turn off differential receiver in suspend mode */
@@ -2291,10 +2329,65 @@
* iddig_output/iddig_en/bvalid_grf_sel are dropped). Signal-quality phy_tuning is
* left off for now (guarded, so NULL is safe) — the phy is functional without it.
*/
+/*
+ * RV1106 USB2 PHY signal-quality tuning, ported from the vendor 5.10 driver
+ * (drivers/phy/rockchip/phy-rockchip-inno-usb2.c, rv1106_usb2phy_tuning).
+ *
+ * The 6.18 forward-port originally omitted this ("the phy is functional without
+ * it"). It is functional, but not reliable: with these registers left at reset
+ * defaults the HS link is electrically marginal under sustained traffic. On a
+ * bench panel a USB-Ethernet dongle would stall a bulk transfer after a few
+ * minutes; the r8152 Tx-timeout recovery then issues an xHCI Stop Endpoint
+ * command which the controller never answers, and xhci-hcd declares the
+ * controller dead and tears the bus down (flare-edge issue #160). Measured
+ * carrier-on to death across four runs: 14s, 519s, 521s, 584s -- a stochastic
+ * spread, not a timer, which is what marginal signalling looks like.
+ *
+ * Pre-emphasis strength is silicon-revision dependent. The vendor driver picks
+ * it from rockchip_get_cpu_version(), which does not exist in mainline; it
+ * derives from RV1106_OS_REG1 (0xff020204) & 0x7. Our panels read 1, i.e. the
+ * non-zero branch, so 3'b001 is pinned here. If a board ever reports version 0
+ * it wants 3'b011 instead -- see the vendor driver.
+ */
+static int rv1106_usb2phy_tuning(struct rockchip_usb2phy *rphy)
+{
+ /* Mapped only for this compatible; refuse to poke a NULL base. */
+ if (!rphy->phy_base)
+ return -ENODEV;
+
+ /* Always enable pre-emphasis in SOF & EOP & chirp & non-chirp state */
+ phy_update_bits(rphy->phy_base + 0x30, GENMASK(2, 0), 0x07);
+
+ /* Set Tx HS pre_emphasize strength to 3'b001 (cpu_version != 0) */
+ phy_update_bits(rphy->phy_base + 0x40, GENMASK(5, 3), (0x01 << 3));
+
+ /* Set RX Squelch trigger point configure to 4'b0000(112.5 mV) */
+ phy_update_bits(rphy->phy_base + 0x64, GENMASK(6, 3), (0x00 << 3));
+
+ /* Turn off differential receiver by default to save power */
+ phy_clear_bits(rphy->phy_base + 0x100, BIT(6));
+
+ /* Set 45ohm HS ODT value to 5'b10111 to increase driver strength */
+ phy_update_bits(rphy->phy_base + 0x11c, GENMASK(4, 0), 0x17);
+
+ /* Set Tx HS eye height tuning to 3'b011(462 mV) */
+ phy_update_bits(rphy->phy_base + 0x124, GENMASK(4, 2), (0x03 << 2));
+
+ /* Bypass Squelch detector calibration */
+ phy_update_bits(rphy->phy_base + 0x1a4, GENMASK(7, 4), (0x01 << 4));
+ phy_update_bits(rphy->phy_base + 0x1b4, GENMASK(7, 4), (0x01 << 4));
+
+ /* Set HS disconnect detect mode to single ended detect mode */
+ phy_set_bits(rphy->phy_base + 0x70, BIT(2));
+
+ return 0;
+}
+
static const struct rockchip_usb2phy_cfg rv1106_phy_cfgs[] = {
{
.reg = 0xff3e0000,
.num_ports = 1,
+ .phy_tuning = rv1106_usb2phy_tuning,
.clkout_ctl = { 0x0058, 4, 4, 1, 0 },
.port_cfgs = {
[USB2PHY_PORT_OTG] = {