[PATCH v12 3/3] phy: airoha: Add support for Airoha AN7581 USB PHY
sashiko-bot at kernel.org
sashiko-bot at kernel.org
Wed Sep 30 00:30:59 PDT 2026
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] phy: airoha: Missing phy_init() and phy_power_on() for serdes_phy
- [Medium] phy: airoha: regmap_set_bits() fails to clear multi-bit field
- [Low] phy: airoha: Silent truncation of dynamic srctrl calibration value
--
commit 4a0c27f02fe4864e660c9a34276ee9067ec33998
Author: Christian Marangi <ansuelsmth at gmail.com>
phy: airoha: Add support for Airoha AN7581 USB PHY
Add support for Airoha AN7581 USB PHY driver. AN7581 supports up to 2
USB port with USB 2.0 mode always supported and USB 3.0 mode available
only if the Serdes port is correctly configured for USB 3.0.
> diff --git a/drivers/phy/airoha/phy-an7581-usb.c b/drivers/phy/airoha/phy-an7581-usb.c
> new file mode 100644
> index 0000000000000..92c5e5c2fbf3b
> --- /dev/null
> +++ b/drivers/phy/airoha/phy-an7581-usb.c
[ ... ]
> +static void an7581_usb_phy_u2_slew_rate_calibration(struct an7581_usb_phy_priv *priv)
> +{
> + u32 fm_out = 0;
> + u32 srctrl;
[ ... ]
> + /* Frequency was not detected, use default SR calibration value */
> + if (!fm_out) {
> + srctrl = AIROHA_USB_PHY_DEFAULT_SR_CALIBRATION;
> + dev_err(priv->dev, "Frequency not detected, using default SR calibration.\n");
> + } else {
> + /* (1024 / FM_OUT) * REF_CK * U2_SR_COEF (round to the nearest digits) */
> + srctrl = AIROHA_USB_PHY_REF_CK * AIROHA_USB_PHY_U2_SR_COEF;
> + srctrl = (srctrl * AIROHA_USB_PHY_U2_FM_DET_CYCLE_CNT) / fm_out;
> + srctrl = DIV_ROUND_CLOSEST(srctrl, AIROHA_USB_PHY_U2_SR_COEF_DIVISOR);
> + dev_dbg(priv->dev, "SR calibration applied: %x\n", srctrl);
> + }
> +
> + regmap_update_bits(priv->regmap, AIROHA_USB_PHY_USBPHYACR5,
> + AIROHA_USB_PHY_USB20_HSTX_SRCTRL,
> + FIELD_PREP(AIROHA_USB_PHY_USB20_HSTX_SRCTRL, srctrl));
[Severity: Low]
Does this dynamically calculated srctrl value need bounds checking?
If a glitchy or slow clock results in a small non-zero fm_out, srctrl
might exceed the 3-bit maximum (7) defined by
AIROHA_USB_PHY_USB20_HSTX_SRCTRL. Since it is passed to FIELD_PREP()
without clamping, the upper bits will be silently truncated, applying
a potentially incorrect slew rate calibration rather than a safe default.
[ ... ]
> +static void an7581_usb_phy_u3_init(struct an7581_usb_phy_priv *priv)
> +{
> + regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_REG8,
> + AIROHA_USB_PHY_SSUSB_CDR_RST_DLY,
> + AIROHA_USB_PHY_SSUSB_CDR_RST_DLY_32);
> +
> + regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_REG6,
> + AIROHA_USB_PHY_SSUSB_CDR_RESERVE,
> + FIELD_PREP(AIROHA_USB_PHY_SSUSB_CDR_RESERVE, 0xe));
> +
> + regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_REG0,
> + AIROHA_USB_PHY_SSUSB_BG_DIV,
> + AIROHA_USB_PHY_SSUSB_BG_DIV_4);
> +
> + regmap_set_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_REG1,
> + FIELD_PREP(AIROHA_USB_PHY_SSUSB_XTAL_TOP_RESERVE, 0x600));
[Severity: Medium]
Should this be using regmap_update_bits() instead of regmap_set_bits()?
AIROHA_USB_PHY_SSUSB_XTAL_TOP_RESERVE is a 16-bit mask (GENMASK(25, 10)).
Using regmap_set_bits() with FIELD_PREP() performs a simple bitwise OR.
This will set the bits corresponding to 1s in 0x600, but it will fail to
clear any other bits across the rest of the 16-bit mask if they happen
to be set by default. This could leave stale bits in the register.
> +
> + regmap_update_bits(priv->regmap, AIROHA_USB_PHY_U3_PHYA_DA_REG19,
> + AIROHA_USB_PHY_SSUSB_PLL_SSC_DELTA1_U3,
> + FIELD_PREP(AIROHA_USB_PHY_SSUSB_PLL_SSC_DELTA1_U3, 0x43));
> +}
> +
> +static int an7581_usb_phy_init(struct phy *phy)
> +{
> + struct an7581_usb_phy_instance *instance = phy_get_drvdata(phy);
> + struct an7581_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
> + int ret;
> +
> + switch (instance->type) {
> + case PHY_TYPE_USB2:
> + an7581_usb_phy_u2_init(priv);
> + break;
> + case PHY_TYPE_USB3:
> + ret = phy_set_mode(priv->serdes_phy, PHY_MODE_USB_DEVICE_SS);
> + if (ret)
> + return ret;
> +
> + an7581_usb_phy_u3_init(priv);
[Severity: High]
Is a call to phy_init(priv->serdes_phy) missing here?
The serdes_phy mode is set, but the mandatory phy_init() lifecycle call
is omitted. The downstream controller (e.g., xHCI or DWC3) requests this
wrapper PHY and invokes phy_init(). The wrapper receives this call via
an7581_usb_phy_init() but completely fails to propagate it to its consumed
priv->serdes_phy, leaving the hardware unconfigured and breaking USB 3.0
functionality.
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
[ ... ]
> +static int an7581_usb_phy_power_on(struct phy *phy)
> +{
> + struct an7581_usb_phy_instance *instance = phy_get_drvdata(phy);
> + struct an7581_usb_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
> +
> + switch (instance->type) {
> + case PHY_TYPE_USB2:
> + an7581_usb_phy_u2_power_on(priv);
> + break;
> + case PHY_TYPE_USB3:
> + an7581_usb_phy_u3_power_on(priv);
> + break;
[Severity: High]
Does this function also need to call phy_power_on(priv->serdes_phy)?
The local USB3 registers are powered on via an7581_usb_phy_u3_power_on(),
but the driver fails to propagate the power_on call to the underlying
priv->serdes_phy. The PHY subsystem API strictly requires a consumer of a
PHY to call phy_init() and phy_power_on() before the PHY can be functional.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260930071826.48811-1-ansuelsmth@gmail.com?part=3
More information about the linux-phy
mailing list