Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PHY Framework

The PHY framework in the Linux kernel provides a unified interface for managing physical layer transceivers — the hardware that converts digital signals to analog (and vice versa) for communication buses like USB, SATA, PCIe, Ethernet, and MIPI. The framework decouples PHY consumers (controllers) from PHY providers (platform drivers), enabling code reuse and clean device tree bindings.


1. Background: What Is a PHY?

A PHY (physical layer transceiver) sits between the digital logic of a controller and the physical medium (copper cable, fiber, RF). It handles:

  • SerDes (serializer/deserializer) — parallel-to-serial conversion
  • Equalization — signal conditioning
  • Clock recovery — extracting timing from the signal
  • Link training — negotiating speed, lanes, and encoding

Examples: USB 3.0 PHY, SATA PHY, PCIe PHY, Ethernet SGMII/RGMII PHY, MIPI D-PHY, HDMI PHY.


2. Architecture

┌─────────────────┐         ┌─────────────────┐
│  PHY Consumer   │         │  PHY Provider   │
│  (USB host ctrl)│         │  (platform PHY  │
│                 │         │   driver)       │
│  phy =          │         │                 │
│  devm_phy_get() │────────►│  .xlate()       │
│  phy_init()     │         │  .init()        │
│  phy_power_on() │         │  .power_on()    │
│  phy_set_mode() │         │  .set_mode()    │
│                 │         │                 │
└─────────────────┘         └─────────────────┘
         │                          │
         │    ┌──────────────┐      │
         └───►│  PHY Core    │◄─────┘
              │  (genphy)    │
              │              │
              │  struct phy  │
              │  struct      │
              │  phy_provider│
              └──────────────┘

2.1 Key Structures

StructureRole
struct phyRepresents a single PHY instance
struct phy_opsOperations the provider implements
struct phy_providerRegisters the provider with the framework
struct phy_configure_optsConfiguration for MIPI/DP PHYs

3. PHY Provider

The PHY provider is the driver that owns the physical hardware. It registers with the PHY framework and implements the callback operations.

3.1 Registering a Provider

static struct phy *my_phy_xlate(struct device *dev,
                                struct of_phandle_args *args)
{
    /* Parse device tree args, return a phy pointer */
    struct my_phy *priv = dev_get_drvdata(dev);
    return priv->phy;
}

static int my_phy_probe(struct platform_device *pdev)
{
    struct phy_provider *provider;

    provider = devm_of_phy_provider_register(&pdev->dev, my_phy_xlate);
    if (IS_ERR(provider))
        return PTR_ERR(provider);

    return 0;
}

3.2 Provider Operations

static const struct phy_ops my_phy_ops = {
    .init       = my_phy_init,
    .exit       = my_phy_exit,
    .power_on   = my_phy_power_on,
    .power_off  = my_phy_power_off,
    .set_mode   = my_phy_set_mode,
    .configure  = my_phy_configure,   /* MIPI/DP */
    .validate   = my_phy_validate,
    .owner      = THIS_MODULE,
};
CallbackWhen CalledTypical Action
initphy_init()Configure clocks, reset, basic setup
exitphy_exit()Undo init
power_onphy_power_on()Enable PHY power, start PLL
power_offphy_power_off()Disable PHY power
set_modephy_set_mode()Configure for USB/PCIe/SATA/etc.
configureMIPI/DP configurationSet D-PHY timings, voltage swing
validateSanity checkVerify the requested mode is supported

3.3 Device Tree Binding

usb_phy: phy@10000000 {
    compatible = "vendor,usb-phy";
    reg = <0x10000000 0x1000>;
    clocks = <&clks 12>;
    clock-names = "phy";
    #phy-cells = <0>;
};

usb_controller: usb@20000000 {
    phys = <&usb_phy>;
    phy-names = "usb";
};

#phy-cells = <0> means no arguments. For multi-lane PHYs:

pcie_phy: phy@30000000 {
    compatible = "vendor,pcie-phy";
    #phy-cells = <1>;   /* argument = lane number */
};

pcie_controller: pcie@40000000 {
    phys = <&pcie_phy 0>, <&pcie_phy 1>;
    phy-names = "pcie-lane0", "pcie-lane1";
};

4. PHY Consumer

The consumer is the controller driver that uses the PHY (USB host, SATA controller, PCIe RC, etc.).

4.1 Getting a PHY

/* Single PHY */
struct phy *phy = devm_phy_get(&pdev->dev, "usb");
if (IS_ERR(phy))
    return PTR_ERR(phy);

/* Optional PHY (may be NULL on some boards) */
struct phy *phy = devm_phy_optional_get(&pdev->dev, "sata");

4.2 Using a PHY

/* Initialize the PHY */
ret = phy_init(phy);
if (ret)
    return ret;

/* Set the operating mode */
ret = phy_set_mode(phy, PHY_MODE_USB_HOST);
if (ret)
    goto err_exit;

/* Power on */
ret = phy_power_on(phy);
if (ret)
    goto err_exit;

/* ... use the controller ... */

/* Power off and exit */
phy_power_off(phy);
phy_exit(phy);

4.3 PHY Modes

ModeEnumUse Case
USB HostPHY_MODE_USB_HOSTUSB host controller
USB DevicePHY_MODE_USB_DEVICEUSB gadget
PCIePHY_MODE_PCIEPCIe root complex / endpoint
SATAPHY_MODE_SATASATA host
EthernetPHY_MODE_ETHERNETMAC-to-PHY
MIPI DSIPHY_MODE_MIPI_DSIDisplay
MIPI CSIPHY_MODE_MIPI_CSICamera

5. Generic PHY (genphy)

The generic PHY driver (drivers/phy/phy-core.c) provides default implementations for common operations. If a provider doesn’t implement a callback, the generic version is used.

5.1 Generic Operations

OperationGeneric Implementation
initNo-op (returns 0)
exitNo-op
power_onDeassert reset, enable clocks
power_offDisable clocks, assert reset

5.2 When to Use Genphy

Simple PHYs that only need:

  • Clock enable/disable
  • Reset control
  • Power domain on/off

More complex PHYs (SerDes, multi-lane, MIPI) must implement custom ops.


6. PHY Configure Options (MIPI/DP)

For MIPI D-PHY and DisplayPort PHYs, the framework provides structured configuration:

union phy_configure_opts {
    struct phy_configure_opts_mipi_dphy dphy;
    struct phy_configure_opts_dp dp;
};

/* Example: configure MIPI D-PHY */
union phy_configure_opts opts = {
    .dphy = {
        .clk_miss = 0,
        .clk_post = 60,
        .clk_pre = 8,
        .clk_prepare = 40,
        .clk_settle = 95,
        .clk_term_enable = 0,
        .clk_trail = 60,
        .hs_exit = 100,
        .hs_prepare = 40,
        .hs_settle = 85,
        .hs_skip = 40,
        .hs_trail = 60,
        .lp_exit = 24,
    },
};

phy_configure(phy, PHY_MODE_MIPI_DPHY, 0, &opts);

6.1 Validation

Before applying, the consumer can validate:

ret = phy_validate(phy, PHY_MODE_MIPI_DPHY, 0, &opts);
if (ret) {
    /* configuration is not supported by this PHY */
}

7. Multi-Lane PHYs

Some interfaces use multiple PHY lanes:

  • PCIe: 1, 2, 4, 8, or 16 lanes
  • MIPI DSI: 1-4 data lanes
  • Ethernet: SGMII (1 lane), XAUI (4 lanes)

The framework handles this by:

  1. The provider returns different struct phy pointers for each lane.
  2. The consumer requests each lane separately.
  3. The provider coordinates lane initialization internally.
/* Consumer requests two lanes */
phy0 = devm_phy_get(dev, "lane0");
phy1 = devm_phy_get(dev, "lane1");

phy_init(phy0);
phy_init(phy1);
phy_power_on(phy0);
phy_power_on(phy1);

8. SerDes (Serializer/Deserializer)

Many SoCs have generic SerDes blocks that can be configured for different protocols:

SerDes lane 0 → PCIe
SerDes lane 1 → SATA
SerDes lane 2 → SGMII Ethernet
SerDes lane 3 → USB 3.0

The PHY framework handles this through phy_set_mode():

/* Configure SerDes for PCIe */
phy_set_mode(serdes_phy, PHY_MODE_PCIE);

/* Later, reconfigure for SATA */
phy_set_mode(serdes_phy, PHY_MODE_SATA);

The provider driver writes the appropriate PLL, equalization, and encoding registers for the selected mode.


9. Power Management

9.1 Runtime PM

PHYs integrate with Linux runtime PM:

/* In the consumer's runtime_suspend: */
phy_power_off(phy);

/* In the consumer's runtime_resume: */
phy_power_on(phy);

9.2 System Suspend/Resume

/* In the consumer's suspend callback: */
phy_power_off(phy);
phy_exit(phy);

/* In resume: */
phy_init(phy);
phy_power_on(phy);

The PHY framework handles the reference counting so that multiple consumers sharing a PHY don’t conflict.


10. PHY Subsystem Layout

drivers/phy/
├── phy-core.c              # Core framework
├── generic/
│   └── phy.c               # Generic PHY operations
├── freescale/
│   ├── phy-fsl-imx8mq-usb.c
│   └── phy-fsl-lynx-28g.c
├── samsung/
│   └── phy-exynos-usb.c
├── ti/
│   └── phy-ti-pipe3.c
├── qualcomm/
│   └── phy-qcom-qmp.c
├── broadcom/
│   └── phy-bcm-ns-usb.c
└── ...

14. USB Type-C and Alt Mode

USB Type-C connectors support alternate modes (Alt Modes) that repurpose the USB-C pins for DisplayPort, Thunderbolt, or other protocols. The PHY framework handles the mode switching.

Type-C Mode Switching

graph LR
    subgraph "USB-C Connector"
        DP["DP Lane 0-3"]
        USB["USB 2.0/3.0"]
    end
    subgraph "Alt Modes"
        USB_MODE["USB Mode<br>SS TX/RX"]
        DP_MODE["DisplayPort<br>4 lanes"]
        TB_MODE["Thunderbolt<br>4 lanes"]
    end
    USB_MODE --> USB
    DP_MODE --> DP
    TB_MODE --> DP

Type-C PHY Operations

/* Type-C mode switch callback */
static int my_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
    struct my_phy_priv *priv = phy_get_drvdata(phy);

    switch (mode) {
    case PHY_MODE_USB_HOST:
        /* Configure for USB 3.0 host */
        my_phy_set_usb3(priv);
        break;
    case PHY_MODE_USB_DEVICE:
        /* Configure for USB 3.0 device */
        my_phy_set_usb3_device(priv);
        break;
    case PHY_MODE_DP:
        /* Configure for DisplayPort Alt Mode */
        my_phy_set_dp(priv, submode);
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

15. PHY Calibration and Tuning

Some PHYs require runtime calibration to compensate for process variations, temperature, and voltage:

Calibration Types

TypeDescriptionTypical PHY
ImpedanceAdjust output impedanceEthernet RGMII
SkewAdjust TX/RX timingUSB 3.0, PCIe
EqualizationSignal conditioningSerDes, PCIe
Eye diagramOptimize signal qualityHigh-speed SerDes

Device Tree Calibration Properties

phy@10000000 {
    compatible = "vendor,usb-phy";
    /* Calibration parameters */
    vendor,tx-impedance = <45>;     /* ohms */
    vendor,rx-impedance = <50>;
    vendor,tx-skew = <2>;           /* delay taps */
    vendor,rx-skew = <3>;
    vendor,eq-pre = <5>;            /* pre-emphasis */
    vendor,eq-post = <3>;           /* post-cursor */
};

Runtime Tuning via sysfs

# View PHY attributes (if exposed)
$ ls /sys/class/phy/phy*/
# power  subsystem  uevent

# Some vendors expose tuning via debugfs
$ ls /sys/kernel/debug/phy*/
# tx_swing  rx_equalization  lane_polarity

16. PHY Error Handling

Common PHY Errors

ErrorCauseSolution
PLL lock timeoutClock not stableCheck reference clock
Signal detect failNo link partnerCheck cable/connector
Training failureSpeed negotiation failLower speed, check signals
Elastic buffer overflowClock domain crossingCheck rate matching config

Error Recovery

static int my_phy_init(struct phy *phy)
{
    struct my_phy_priv *priv = phy_get_drvdata(phy);
    int ret;

    /* Attempt PLL lock */
    ret = my_phy_wait_pll_lock(priv, 1000);  /* 1ms timeout */
    if (ret) {
        dev_err(&phy->dev, "PLL lock timeout\n");
        /* Try recovery: reset and retry */
        my_phy_reset(priv);
        ret = my_phy_wait_pll_lock(priv, 2000);
        if (ret)
            return ret;
    }

    return 0;
}

17. PHY Power Domains

PHYs often share power domains with their associated controllers. The PHY framework integrates with the power domain framework:

/* PHY automatically powers on its power domain when phy_power_on() is called */
/* If the PHY uses a power domain, the framework handles PM runtime */

/* Device tree */
phy: phy@10000000 {
    power-domains = <&power 0>;
    /* ... */
};

18. PHY Debugging Extended

Common Diagnostic Commands

# List all registered PHYs
$ ls /sys/class/phy/
phy-usb-0  phy-pcie-0  phy-sata-0

# Check PHY power state
$ cat /sys/class/phy/phy-usb-0/power/runtime_status
active

# Device tree PHY references
$ ls /sys/firmware/devicetree/base/soc/phy*/
compatible  reg  clocks  resets  #phy-cells

# Trace PHY calls
$ echo 'module phy_core =p' > /sys/kernel/debug/dynamic_debug/control
$ dmesg -w | grep phy

# Check PHY provider
$ cat /sys/class/phy/phy-usb-0/of_node/compatible
vendor,usb-phy

PHY Health Monitoring

/* Check PHY link status */
static bool my_phy_link_is_up(struct phy *phy)
{
    struct my_phy_priv *priv = phy_get_drvdata(phy);
    u32 status;

    regmap_read(priv->regs, PHY_STATUS_REG, &status);
    return !!(status & PHY_LINK_UP_BIT);
}

19. Comparison with Other Subsystems

SubsystemPurposeScope
PHY frameworkPhysical layer transceiversUSB, SATA, PCIe, MIPI
Generic PHYSimple clock/resetAll simple PHYs
MDIO/PHYLIBEthernet MDIO bus PHYsEthernet only
Clock frameworkClock treesClocks only
Regulator frameworkPower suppliesPower only
Reset frameworkReset linesReset control only

Note: Ethernet PHYs use the MDIO/PHYLIB subsystem (drivers/net/phy/), not the generic PHY framework. The generic PHY framework handles the SerDes between the MAC and the MDIO PHY.

20. Further Reading

  • Documentation: Documentation/driver-api/phy/phy.rst
  • Source: drivers/phy/phy-core.c
  • Device tree bindings: Documentation/devicetree/bindings/phy/
  • LWN: The PHY subsystem
  • Kishon Vijay Abraham’s PHY framework patches (2013)

Cross-References

  • USB — USB host and device controllers
  • PCIe — PCI Express
  • SATA — Serial ATA
  • Ethernet — MDIO PHY subsystem
  • Clock Framework — clock management
  • Regulator Framework — power supply management
  • Device Tree — hardware description