From nobody Fri Sep 25 12:34:02 2026 Received: from mail-wm2-f13.google.com (mail-wm2-f13.google.com [74.125.225.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 057C647D931 for ; Sat, 12 Sep 2026 13:04:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.141 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218281; cv=none; b=muWSFZa8c7ZauWz5GYAiCE2N16vdCVYNBiw/WwJH+J7cZMV1av0v3f3begYNbc/cAr2UaXFChRCSIhZi+VhS0qFSmXqGe1m1EIkUqm0KFlWHe34fNrVG6TTQf1Z8a8xHhqQXwTB24JHhBhJxO6XmAhdniJK9v8UR4U6fTax3MUA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218281; c=relaxed/simple; bh=dYtekyfdPkvjbIvhRVWQ56pAdussIAg2Ik5Q1WhEgy8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Rwat4ptknh+ZwJAkZC0ammd7p5k4DBf5g/32rx39G2iyxtc828aF3I3P+7WuBKHP+vG3umyRPHtDmR6dWjm6VZFOE2LUaNgbkfzxnBa+0UFNk4PjZHJ+bHE1uZ3mOksi8kznQ7T//vHffC1uToqdpEzT7ko1c9ynCVont9Td9Mc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la; spf=pass smtp.mailfrom=lex.la; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b=Ics/Nckp; arc=none smtp.client-ip=74.125.225.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lex.la Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b="Ics/Nckp" Received: by mail-wm2-f13.google.com with SMTP id 5b1f17b1804b1-49ccf3ca94bso2025275e9.3 for ; Sat, 12 Sep 2026 06:04:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lex.la; s=google; t=1789218278; x=1789823078; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=RfMHM791MR18SdZtnthEsjb4fNyTobmnPZ1nyemJ/LA=; b=Ics/Nckpralq0wWNTN2YXxdlPJ2Lb33iYnyOeIlZsm2Y4i+cRBqEhFLCcZXemZhV4S Fe860Y7uqYaPex6WGA89nK8UNYvo0EZ6J1M/70shiAVhdIsMbUD75zln6V+rHmWfMiYi ebpjEeyWh8wv45cEFOy1g+h7/7te5MCzvANVJewKT/PVRUbJj7iyFkpsVw2/juxbIYmD g4Hf9TxxYyRFVniGDCBki38eWgxwLieOyE4QpkUWdyxDn5Uo2W22XUMG43pCpwzNMID4 hwDM16SEoXezDEbg98QF7AzSSO3t9MJNTEugwir68cl+aaCtAgJ4Dji8NNzoH1vA0Gm+ 82Sg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789218278; x=1789823078; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=RfMHM791MR18SdZtnthEsjb4fNyTobmnPZ1nyemJ/LA=; b=AdmT0zKFV37a5L9OhPEOjqoF46VjnREXTf1vuiS2GISkvPYPbu47EoPTf1mnqIUbvr nJlaZPQZywGJVQWmh9B2g+ukNd8W8FLBQ9i9KhxQUl+XHzOXAzbF7ZgowwUsifT6rsPC Pop8v+ClAs5OA+SeaicIWAn/GJs7ASKYFv6Bpv90qZVg6xnU5vYYzhNIM4cm670Gfx6q 2Y3txwKNvAWbNq8NViGukQ0dWJ2N1/wcoRmpJOldX5xj3zCaRbLmzSGTTHYa1CceiJ73 ufe8CgT7DgptS/napt04Z4XgVfXzhQDqR2eHa+4ZJ6YsZ/Z3lfJ1GRSfrHJIfA0Qtdwz lp6w== X-Forwarded-Encrypted: i=1; AKwUvBx3yKLnWc3E9NS+lgGy7LiYWAnggiySuddVHyUhMydND6E4Vh/V2nADqJi4FqnKkXWlQlMRYsGSW5ww74c=@vger.kernel.org X-Gm-Message-State: AFuF++lmwvFFrJ3qUQ3+eLOmuh/0G1WQoDTO1CDDn9dVUEx5bYl2oVGc V1DDfqP3INU8If9umsfWx7XH2zT/qecVcQM47/G6uTvhzvWKV7YVg/42vvfvKBEIXAJcNynQ1sJ lrfyYVA3pnM7G5w4= X-Gm-Gg: AYBFou0PF9BxxWAYAhCvCMdOdZTKUssZVW0pWkpur4LbuL3+eO5icsAJAJf7SiVaG1x k1MRhsu8pzol+AP+0zJgYnSuivtJA6D2g1cNv/bi0KOoVPTyZzMqxtl7468x44sveL2ByxxvoBq MbxnCWUVY3O+2lf4aO0bjH9qYtQua/HhmnQ73IMaI++SAlQgJpPpHvwl/gs67UB0HErAb9Vg1uQ 7+BmH0sW0HjaaCbKOvVwCpCa6frCJc/mIaEcBRdXusJMaUmaEaRWOFG5XnaxFM6rCJtQa3RF8Ho IWYJnIL3bz5CMKkmw1b2iJFrgO5c2ZApuyJimW2q7qDkhDoiEGPJVBW6KNH/B9mWOBHayKE+K4C 56voj1tgGH182BQlxqoxu2Ohd32P++ubekspISCSJ7vSS1S5CLaaf/wyVVEGZ0Ve4+Q0MQRfkFa 9k4uLesgjxy2YqXNcmWAyTi6OktZWipkr6VrnPxFulqAJ0arbu4ObOdfZmwFft X-Received: by 2002:a05:600c:4ecf:b0:49c:fc6c:bdfc with SMTP id 5b1f17b1804b1-49e6cbec26cmr23657665e9.19.1789218277887; Sat, 12 Sep 2026 06:04:37 -0700 (PDT) Received: from remote-01 ([84.17.55.229]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49e6a06cfb8sm80985785e9.5.2026.09.12.06.04.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 06:04:37 -0700 (PDT) From: Aleksei Sviridkin To: andrew@lunn.ch, andrew+netdev@lunn.ch, hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, horms@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ericwouds@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Aleksei Sviridkin Subject: [RFC PATCH net-next v3 1/6] dt-bindings: net: add Airoha EN8811H PHY MCU Date: Sat, 12 Sep 2026 16:04:25 +0300 Message-ID: <20260912130430.2246285-2-f@lex.la> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260912130430.2246285-1-f@lex.la> References: <20260912130430.2246285-1-f@lex.la> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The EN8811H answers its PHY ID from power-on, but a link is only possible once its MD32 MCU runs firmware that the host loads over MDIO into volatile RAM. On systems that keep the firmware files in a filesystem, the files arrive long after the MDIO bus was scanned, and the PHY node alone cannot express that gap. Describe the MCU as an MDIO device of its own, with the PHY node on a child bus underneath it. The device downloads the firmware when the files appear, or adopts firmware the bootloader left running, and registers the child bus only then, so the PHY never becomes visible before the chip can serve it. The reset line belongs to this node, not to the PHY node: firmware lives in volatile RAM, so reset may be asserted only while the MCU is not executing it. On a PHY node the line would be pulsed by every phy_detach() and wipe the running firmware. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- .../bindings/net/airoha,en8811h-mcu.yaml | 127 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 133 insertions(+) create mode 100644 Documentation/devicetree/bindings/net/airoha,en8811h-mc= u.yaml diff --git a/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml = b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml new file mode 100644 index 000000000000..7c204b5f29c9 --- /dev/null +++ b/Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml @@ -0,0 +1,127 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/airoha,en8811h-mcu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha EN8811H PHY MCU + +maintainers: + - Aleksei Sviridkin + +description: | + The Airoha EN8811H 2.5G PHY is built around an MD32 core that executes + firmware from volatile RAM. The chip answers an MDIO address from + power-on, but the PHY function does not exist until firmware has been + loaded over MDIO, and it disappears again if that RAM is cleared. + + This node describes the MD32 core. The PHY function answers on a child + MDIO bus below it, at the same address the core occupies on the parent + bus, and only while firmware is running. + + The reset line is wired to the core, and asserting it clears the + firmware RAM. It therefore belongs to this node: a reset described on + the PHY node would be pulsed by that node's own lifecycle, whatever + the core is doing. + + A board that has firmware in hand while its MDIO bus is enumerated can + describe the PHY on its own, as airoha,en8811h.yaml does, and that form + stays valid. This binding is for the boards where firmware arrives + later, so that there is no PHY to describe until the core has it. + +properties: + $nodename: + pattern: "^mcu@[0-9a-f]+$" + + compatible: + const: airoha,en8811h-mcu + + reg: + maxItems: 1 + + reset-gpios: + maxItems: 1 + description: + The GPIO phandle and specifier for the reset signal wired to the + MD32 core. + + reset-assert-us: + description: + Delay after the reset was asserted in microseconds. If this + property is missing the delay will be skipped. + + reset-deassert-us: + description: + Delay after the reset was deasserted in microseconds. If this + property is missing the delay will be skipped. + + mdio: + $ref: mdio.yaml# + description: + The bus the PHY function answers on while firmware is running, at + the same address this node occupies on the parent bus. Only that + one address is passed through. The bus-level reset properties + mdio.yaml allows are refused here, because the MDIO core asserts + such a line when the bus registers, and that erases the firmware + this PHY is made of. + + properties: + reset-gpios: false + reset-delay-us: false + reset-post-delay-us: false + + patternProperties: + '^ethernet-phy@[0-9a-f]+$': + type: object + $ref: airoha,en8811h.yaml# + unevaluatedProperties: false + + properties: + reset-gpios: false + reset-assert-us: false + reset-deassert-us: false + resets: false + reset-names: false + + description: + The reset line belongs to the MCU node above. A reset described + here would be pulsed by this node's own lifecycle, and that eras= es + the firmware that created this PHY. + + unevaluatedProperties: false + +required: + - compatible + - reg + - mdio + +additionalProperties: false + +examples: + - | + #include + #include + + mdio { + #address-cells =3D <1>; + #size-cells =3D <0>; + + mcu@d { + compatible =3D "airoha,en8811h-mcu"; + reg =3D <0xd>; + reset-gpios =3D <&pio 14 GPIO_ACTIVE_LOW>; + reset-assert-us =3D <10000>; + reset-deassert-us =3D <20000>; + + mdio { + #address-cells =3D <1>; + #size-cells =3D <0>; + + ethernet-phy@d { + compatible =3D "ethernet-phy-id03a2.a411"; + reg =3D <0xd>; + interrupts-extended =3D <&pio 15 IRQ_TYPE_LEVEL_LOW>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index b23fb6f2f4ef..4ca19275f4d0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -750,6 +750,12 @@ S: Supported F: fs/aio.c F: include/linux/*aio*.h =20 +AIROHA EN8811H MCU MDIO DRIVER +M: Aleksei Sviridkin +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml + AIROHA ETHERNET DRIVER M: Lorenzo Bianconi L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) --=20 2.53.0 From nobody Fri Sep 25 12:34:02 2026 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D4C1B47DD71 for ; Sat, 12 Sep 2026 13:04:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218285; cv=none; b=pQ79/BN0MX+v1DGSnOZrfs5Nk/uE6SB5PYaQ14PGJnqPxfnExe2ScrA8rx7B1Qiay5OhyaJui4bnwrXXldRwUkaVRnkuD3JEJ+jc8Yz8IPNRSyAU1ROOTmYBjNCFaklEzcJBgOfbz0BxrNb7TpHT1SPdVhiKVRgn9/rJ2LsukGA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218285; c=relaxed/simple; bh=mzH/5XaUhv6NQCYgufBy5oWC9jpITM+/znfPg5sAlv0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=phFy/Pk/DXuWfreAxeRgzYgKSggKOmfEV792Nzhv0OkPrFVn54926kfbMNembZ/38UHPIhvorDOCYNmnuvkLebEXiSjkua6ip/v9SL/DBiah9L5X5bGM1AER+SSMQi73jU8yz9Ykd3w5ekFvBaSqDkgdrScznCAQ7UoNnEXwVq4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la; spf=pass smtp.mailfrom=lex.la; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b=NBliq2ix; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lex.la Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b="NBliq2ix" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-49e6b280cadso9278975e9.1 for ; Sat, 12 Sep 2026 06:04:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lex.la; s=google; t=1789218279; x=1789823079; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=YfiP06w66PJnEqo2UKKQtS6GK/rrfhVqIhL6B6Ho9XA=; b=NBliq2ixrN3Ov7S5s54bPA15I9ggBI9nUKZCinbE7ysbkYMtvr6nScr6lTjsGun9dC LIJ46fT52goShsnzgDnZ2sF3EMlm0LuG5FFFZh40ZfkL0bUQ6+uaGfrr0LJT1eov1Xwq qGPV3xTlP5VMzMhhD/mGENdGUdBjNPhG5n6zOwNJ65C1xkRGocFMTpSnBE5BYgl7WxdV Cb5pXuK7GuQSCfilsJmuPJIMrllpAdraQ9r2nJLACdswUDC6U0jclASzQvKV6yjQh1Z9 shSWwo2MzUKtz8vZ6Li/kefY+phZBRTQUUnxlcyAy+LbXclCXZMRtXDoHopxAh1MDz9p d88Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789218279; x=1789823079; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=YfiP06w66PJnEqo2UKKQtS6GK/rrfhVqIhL6B6Ho9XA=; b=a9bmbo1kWQwwfToa3mlz0eS9uv2FyPznuaW75lLEgwi9f0ndU5hJaw4lZkqcvDL+f6 ISusCt5ZWunHcjpZHzFlu0Dh/tKWIVgvW0bZVRh05thsSzmxRstzJoi5WMT0qCEe+njc SqjXYRp3+53SWSujT1pguYoMxFdLaujq1+9ap106y2YXOWQWqVYoetzZjK7gzJDtDbHb 0EukcMrg2OUxao7D02nQ4AUJWC5kXh9uEtLqbSxAldAQTgLJD2mlkYK1EEVXwCpMZTOp 7nfPwkYIJ/+F5DVRhCnDS6xC2vdlE6cB4W+AcD6usdELvt5b29zFD6A41z+heQRhuRQH /7Rw== X-Forwarded-Encrypted: i=1; AKwUvBx0qdyy2bA60SymmT9BDxMFJmQY/Jr2BP5LaID+TNql+Z+7OHujZtNKM4F323rhVBjPSQlWWkJ4J+v3sr4=@vger.kernel.org X-Gm-Message-State: AFuF++k/LEcXIV18o5BQ4acNDncq0MrLjRDJDOOb8c01PIv3MvkXcDb2 MvyFMoeXyIgLPjO7CgnnHSwauJmn/4R10uL2YTj+gRXr2ox3+bnLb+VsK4WcuppZQKI= X-Gm-Gg: AYBFou2tsIZ/tatIFbkBGV5f8W7YLLgBcuuywjpo7IRK9tQtL+hjMvIUvC76J5tamyd BMvR9Cd5+mio4VcqytkTEg3muroSyHsPh8eIif6fIaJxni+U1Gy67nVQRWuH0rLXCNNyYvgml2O zQSSLcze1YC2nHGCBR50E3dC/vSP8vnxZuN4pVSgbe3FxE37u22yIPnUSt6uNGcZ36gQW5HcUZe PT+/mxls9TQim3HM/7n/KxTRRcYODv+9p7Z/hCskZIyY6MK4nbGZSMAovrUAa7Gp+XWRFP6S6uK HR2DzPr2gr2qgk+GaCxDxnQIU38uRt01py1TMTA9JmnblKgwxPKgVtcBwlgb+Op/sQOoXZ7PoQ5 JztwLinFvppI0+ZWz6dQ/UvX8dbpNLhJQjYFaF2jYafcqMtnjPmiIoSiAUKWgVDXTDBhN0iFZr7 r3UTmfyG/WqjCO/G5tFMkBfYmQI4XTeLIU8+jUT+7PYNRbRmNl/Q== X-Received: by 2002:a05:600c:a07:b0:49c:fc6e:8cb1 with SMTP id 5b1f17b1804b1-49e619bbcefmr96203785e9.21.1789218279377; Sat, 12 Sep 2026 06:04:39 -0700 (PDT) Received: from remote-01 ([84.17.55.229]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49e6a06cfb8sm80985785e9.5.2026.09.12.06.04.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 06:04:39 -0700 (PDT) From: Aleksei Sviridkin To: andrew@lunn.ch, andrew+netdev@lunn.ch, hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, horms@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ericwouds@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Aleksei Sviridkin Subject: [RFC PATCH net-next v3 2/6] net: phy: air: type the buckpbus core on the mdio device Date: Sat, 12 Sep 2026 16:04:26 +0300 Message-ID: <20260912130430.2246285-3-f@lex.la> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260912130430.2246285-1-f@lex.la> References: <20260912130430.2246285-1-f@lex.la> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The buckpbus accessors only need an MDIO device, but they take a phy_device, which ties them to a probed PHY. An upcoming MDIO device driver needs the same register access before any phy_device exists, since it runs precisely to make the PHY presentable. Retype the internal helpers onto an mdio_device and keep the exported phy_device API as page-selecting wrappers that pass &phydev->mdio. The EN8811H driver already reaches the AN8811HB pbus through an mdio_device, so this follows an existing direction rather than opening a new one. No functional change. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- drivers/net/phy/air_phy_lib.c | 80 ++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c index 5141db19fa5e..e314717843ae 100644 --- a/drivers/net/phy/air_phy_lib.c +++ b/drivers/net/phy/air_phy_lib.c @@ -14,31 +14,32 @@ =20 #include "air_phy_lib.h" =20 -static int __air_buckpbus_reg_read(struct phy_device *phydev, +static int __air_buckpbus_reg_read(struct mdio_device *mdiodev, u32 pbus_address, u32 *pbus_data) { int pbus_data_low, pbus_data_high; int ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, - upper_16_bits(pbus_address)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, - lower_16_bits(pbus_address)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); if (ret < 0) return ret; =20 - pbus_data_high =3D __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); + pbus_data_high =3D __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_HIGH); if (pbus_data_high < 0) return pbus_data_high; =20 - pbus_data_low =3D __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); + pbus_data_low =3D __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_LOW); if (pbus_data_low < 0) return pbus_data_low; =20 @@ -46,64 +47,66 @@ static int __air_buckpbus_reg_read(struct phy_device *p= hydev, return 0; } =20 -static int __air_buckpbus_reg_write(struct phy_device *phydev, +static int __air_buckpbus_reg_write(struct mdio_device *mdiodev, u32 pbus_address, u32 pbus_data) { int ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, - upper_16_bits(pbus_address)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, - lower_16_bits(pbus_address)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, - upper_16_bits(pbus_data)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data)); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, - lower_16_bits(pbus_data)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data)); if (ret < 0) return ret; =20 return 0; } =20 -static int __air_buckpbus_reg_modify(struct phy_device *phydev, +static int __air_buckpbus_reg_modify(struct mdio_device *mdiodev, u32 pbus_address, u32 mask, u32 set) { int pbus_data_low, pbus_data_high; u32 pbus_data_old, pbus_data_new; int ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_RD_ADDR_HIGH, - upper_16_bits(pbus_address)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_RD_ADDR_LOW, - lower_16_bits(pbus_address)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); if (ret < 0) return ret; =20 - pbus_data_high =3D __phy_read(phydev, AIR_BPBUS_RD_DATA_HIGH); + pbus_data_high =3D __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_HIGH); if (pbus_data_high < 0) return pbus_data_high; =20 - pbus_data_low =3D __phy_read(phydev, AIR_BPBUS_RD_DATA_LOW); + pbus_data_low =3D __mdiodev_read(mdiodev, AIR_BPBUS_RD_DATA_LOW); if (pbus_data_low < 0) return pbus_data_low; =20 @@ -112,23 +115,23 @@ static int __air_buckpbus_reg_modify(struct phy_devic= e *phydev, if (pbus_data_new =3D=3D pbus_data_old) return 0; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, - upper_16_bits(pbus_address)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, - lower_16_bits(pbus_address)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, - upper_16_bits(pbus_data_new)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data_new)); if (ret < 0) return ret; =20 - ret =3D __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, - lower_16_bits(pbus_data_new)); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data_new)); if (ret < 0) return ret; =20 @@ -144,7 +147,8 @@ int air_phy_buckpbus_reg_read(struct phy_device *phydev= , u32 pbus_address, saved_page =3D phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); =20 if (saved_page >=3D 0) { - ret =3D __air_buckpbus_reg_read(phydev, pbus_address, pbus_data); + ret =3D __air_buckpbus_reg_read(&phydev->mdio, pbus_address, + pbus_data); if (ret < 0) phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, pbus_address, ret); @@ -163,7 +167,7 @@ int air_phy_buckpbus_reg_write(struct phy_device *phyde= v, u32 pbus_address, saved_page =3D phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); =20 if (saved_page >=3D 0) { - ret =3D __air_buckpbus_reg_write(phydev, pbus_address, + ret =3D __air_buckpbus_reg_write(&phydev->mdio, pbus_address, pbus_data); if (ret < 0) phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, @@ -183,8 +187,8 @@ int air_phy_buckpbus_reg_modify(struct phy_device *phyd= ev, u32 pbus_address, saved_page =3D phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); =20 if (saved_page >=3D 0) { - ret =3D __air_buckpbus_reg_modify(phydev, pbus_address, mask, - set); + ret =3D __air_buckpbus_reg_modify(&phydev->mdio, pbus_address, + mask, set); if (ret < 0) phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, pbus_address, ret); --=20 2.53.0 From nobody Fri Sep 25 12:34:02 2026 Received: from mail-wr1-f47.google.com (mail-wr1-f47.google.com [209.85.221.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 85EF447DD4B for ; Sat, 12 Sep 2026 13:04:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218289; cv=none; b=jkTtH3s/HqqqQqGYBENeHEHR7BmuFcIQJZqbHfJ8Wcc+UigD6gG26dSuK1tQ/kdSSe+EzDz2V9BBF/Q8HMoeenStfHNtDuxDthBdNAobq6rDnT5hhpR+ngA1/ChmuQvW9gz2w1/0WjzCty/G3sYPV4LJeK2gWIMI9UXt/3tq2Ik= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218289; c=relaxed/simple; bh=wSi/zVmdNoLSb3xhhxipZmgQjKIK1uUkLkPwJbRGNG0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=imoZ5wl86Yr+1/yYX8+EY04GwpGMO7AqiR7s/bAfzamU/GrIGzJJFdm3n3Ni5K/uUinqWNPSfsNc8qkhelPHWdql0Mw+O4UBbmVc0/Cc4kjrxRrot3eGx1EilE/sFEdS5UwGmjA/LTcP7/eBPgKDdFlLSJsKGmsBmz6hx9ZqnBQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la; spf=pass smtp.mailfrom=lex.la; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b=MI6lzi2z; arc=none smtp.client-ip=209.85.221.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lex.la Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b="MI6lzi2z" Received: by mail-wr1-f47.google.com with SMTP id ffacd0b85a97d-486955ae01eso1676435f8f.0 for ; Sat, 12 Sep 2026 06:04:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lex.la; s=google; t=1789218281; x=1789823081; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=DIc/2dc1TDQyNegTPxQApWX+hmIcLgk99V9rw78CEXY=; b=MI6lzi2zm8wo0ImK9hFsndekX/xQv4yKvpKjz0daLS2TQmZEJych5owG9NJ44lyhxM 5+6/0ZLfXlA6mOpJzZBZ6I+BflsDqoEF3NiLGt9ln6KjdQHNLfs1r/qcaE6tp5B4U5oO XP/9PLcxJzxFxWDFYGQjNqKtf85ayoLvFsL7JXw2s1ZTHQ4fTHKaigHl6jD+NfK21HwB 0L5KDJ09FPXWFDheoYONnlbqiqwnY1wZ4lRir/1kHqOQZ7Vg7rT/L5Rm/KWXrz2Nu7M+ wbwiUqw7h5MloyYXOm24Ivd9sWMM/xM1CihbjO+Av1fNB4MGSEdAWcrLP3fV0P0kKVmC DGQw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789218281; x=1789823081; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=DIc/2dc1TDQyNegTPxQApWX+hmIcLgk99V9rw78CEXY=; b=Icw0mvZ2sPFdQGp9RMlZxmG/d18yMxQcREWXxyqaAsBhIEI951Z3uSPZs5NMnbnE/a e4+j2DR8SmS5NTmd/r373hO9bVIQCqkMyrwGNtzU7ZVi31YlXpgT+nvu3OwPQfFHSsaX ftulo7vDUDen66HLannNOf66dzqM9cQ7fJgEbjY9JD5ounwN70p9N7eSIUeWjJdEfVF0 5/JX5t80Zp8Z+fTcWPslYl9cyic8Uaw5vwlquogXXDXi1GbLuH00+f1u9ObauBiW26yI WZGL2zPR411djUhN6S5tesns0hbTafwsSo3ngeeIUGv3VQ7dTCYhZfHSqr7UTLqR2WBx /wIg== X-Forwarded-Encrypted: i=1; AKwUvBy2FwkbkzzB0ML7FMef3c1XaLm0BF0ZNW8KSqhOx9UfU1mNpPqCyBf3R/SrgtwdwBFQK8jBm13A97lhVpM=@vger.kernel.org X-Gm-Message-State: AFuF++mDIvmDjDTy9H2VvADbsL9Hn8V7xxz43yYKhK7vLPt2r9x6L4Xt blHttiX2eAIsnc5o991jd4DCFShSB+rTnY3GKnELs39di4LMtWIQc4RfZ98M0pc1LoQ= X-Gm-Gg: AYBFou1NN1YJv9AbgmJG3zhoE8kqHzpJEQeYUY+/gB1iwaXi9tiHbae8Y8j71Dni6lH quYkn8O1IfLEZ2sLlfuSgDRwUPjwLHjE/fdihEM3wyGOcZ5GoXzf+n05WGrcVv3ShXinlBFUyAT jMMeop3vCmZmTqY11cBbu7Qfkh2FekCQ+IDtCP+pxICx2ilJstfNB/UaG339ayCsQkT2gpjvOPI ive86vHfl7eHNRBWiWTZRDNMPRpUZ5e8BOkusYb9CNQAQeKG7WYLg3w6gf465xDQwF2dGLy1qru Ez3C7rBJE2QoUwWu4V6oirJyikuQGWICO5A0i5e6JTUs41HS94Q/rlPeP/SQeLxSVtB/rC9F8zH pM27d1lEf8BoNoTWtQJKj9MFwZk2Ug2mN5v0KUvg4JV5lEuOO5EPaej9X2nTBzU45kkbi0EqY9S MbuZRXeqs81o3SOTTfRgQlgBSbwIThOYAi5td0Zq7fisqZTcUzVw== X-Received: by 2002:a05:600c:4714:b0:499:79b9:e220 with SMTP id 5b1f17b1804b1-49e61988b9emr92906245e9.10.1789218280939; Sat, 12 Sep 2026 06:04:40 -0700 (PDT) Received: from remote-01 ([84.17.55.229]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49e6a06cfb8sm80985785e9.5.2026.09.12.06.04.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 06:04:40 -0700 (PDT) From: Aleksei Sviridkin To: andrew@lunn.ch, andrew+netdev@lunn.ch, hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, horms@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ericwouds@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Aleksei Sviridkin Subject: [RFC PATCH net-next v3 3/6] net: phy: air: move the EN8811H firmware download into the library Date: Sat, 12 Sep 2026 16:04:27 +0300 Message-ID: <20260912130430.2246285-4-f@lex.la> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260912130430.2246285-1-f@lex.la> References: <20260912130430.2246285-1-f@lex.la> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The EN8811H firmware loader is welded to the PHY driver, but the next patch adds an MDIO device driver that must perform the same download before any phy_device exists. Move the download engine into the shared library, typed on the MDIO device like the buckpbus core. The moved helpers take the bus lock and save and restore the page register themselves, since phy_select_page() needs a phy_device. The write loop is broken into chunks that each take the bus lock on their own, because the larger of the two blobs held it for 65536 register writes at a stretch and stalled every other PHY on the bus. It did not matter while this only ran from phy_probe() on a quiet bus at boot; the next patches run it on a live system. The status poll asks for the MMD register through the Clause 22 indirection unconditionally, which is what the Clause 22 probe path was already doing. Behaviour changes where the library now reports instead of printing: the version read at the end of the download is checked, the readiness poll hands back the errno from a failed status read rather than -ENODEV, and the lines the old helpers printed on failure move to their callers or to debug level. The AN8811HB probe shares the readiness wait and the block writer, so it sees those too. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- Notes: The status read does its three Clause 22 selection writes itself rather than through mmd_phy_read(), which discards their errors and would let a failed selection read a different register back as a success. Asking for Clause 22 unconditionally records what was already happening rather than choosing it: phy_read_mmd() resolved the question from phydev->is_c45, which the Clause 22 probe path leaves false. =20 The readiness poll's timeout message moves out to the PHY driver's wrapper, which is where the errno was already being reported to a human. The poll is about to gain a caller that retries forever, and an unconditional message there would repeat with every retry; the status value it used to name stays behind at debug level. =20 The behaviour changes, one by one. The version read at the end of the download is now checked, so a bus error there fails the load instead of leaving the version at zero; en8811h_print_fw_version() still discards = it on the AN8811HB path, which does not go through this download. The readiness poll hands back the errno from a failed status read rather th= an -ENODEV. Its failure is now reported once, by the wrapper and naming th= at errno, where phy_read_mmd_poll_timeout() used to print a line of its own before the wrapper printed the status register. A failed block write no longer names its address, since both callers report the failure anyway. The version print moves to the wrapper for the same reason as the timeo= ut message: the library hands the number back through fw_version, and the caller decides whether a human sees it. drivers/net/phy/air_en8811h.c | 150 ++--------------- drivers/net/phy/air_phy_lib.c | 297 ++++++++++++++++++++++++++++++++++ drivers/net/phy/air_phy_lib.h | 29 ++++ 3 files changed, 337 insertions(+), 139 deletions(-) diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c index 16a9171cb838..43d2eff808f1 100644 --- a/drivers/net/phy/air_en8811h.c +++ b/drivers/net/phy/air_en8811h.c @@ -20,21 +20,15 @@ #include #include #include -#include =20 #include "air_phy_lib.h" =20 #define EN8811H_PHY_ID 0x03a2a411 #define AN8811HB_PHY_ID 0xc0ff04a0 =20 -#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" -#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin" #define AN8811HB_MD32_DM "airoha/an8811hb/EthMD32_CRC.DM.bin" #define AN8811HB_MD32_DSP "airoha/an8811hb/EthMD32_CRC.DSP.bin" =20 -#define AIR_FW_ADDR_DM 0x00000000 -#define AIR_FW_ADDR_DSP 0x00100000 - /* MII Registers */ #define AIR_AUX_CTRL_STATUS 0x1d #define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2) @@ -44,8 +38,6 @@ #define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc =20 /* Registers on MDIO_MMD_VEND1 */ -#define EN8811H_PHY_FW_STATUS 0x8009 -#define EN8811H_PHY_READY 0x02 =20 #define AIR_PHY_MCU_CMD_0 0x800b #define AIR_PHY_MCU_CMD_1 0x800c @@ -108,8 +100,6 @@ #define EN8811H_2P5G_LPA 0x3b30 #define EN8811H_2P5G_LPA_2P5G BIT(0) =20 -#define EN8811H_FW_VERSION 0x3b3c - #define EN8811H_POLARITY 0xca0f8 #define EN8811H_POLARITY_TX_NORMAL BIT(0) #define EN8811H_POLARITY_RX_REVERSE BIT(1) @@ -122,12 +112,6 @@ #define EN8811H_CLK_CGM 0xcf958 #define EN8811H_CLK_CGM_CKO BIT(26) =20 -#define EN8811H_FW_CTRL_1 0x0f0018 -#define EN8811H_FW_CTRL_1_START 0x0 -#define EN8811H_FW_CTRL_1_FINISH 0x1 -#define EN8811H_FW_CTRL_2 0x800000 -#define EN8811H_FW_CTRL_2_LOADING BIT(11) - #define AN8811HB_CRC_PM_SET1 0xf020c #define AN8811HB_CRC_PM_MON2 0xf0218 #define AN8811HB_CRC_PM_MON3 0xf021c @@ -270,86 +254,15 @@ static int __air_pbus_reg_write(struct mdio_device *m= diodev, upper_16_bits(pbus_data)); } =20 -static int __air_write_buf(struct phy_device *phydev, u32 address, - const struct firmware *fw) -{ - unsigned int offset; - int ret; - u16 val; - - ret =3D __phy_write(phydev, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_INCR); - if (ret < 0) - return ret; - - ret =3D __phy_write(phydev, AIR_BPBUS_WR_ADDR_HIGH, - upper_16_bits(address)); - if (ret < 0) - return ret; - - ret =3D __phy_write(phydev, AIR_BPBUS_WR_ADDR_LOW, - lower_16_bits(address)); - if (ret < 0) - return ret; - - for (offset =3D 0; offset < fw->size; offset +=3D 4) { - val =3D get_unaligned_le16(&fw->data[offset + 2]); - ret =3D __phy_write(phydev, AIR_BPBUS_WR_DATA_HIGH, val); - if (ret < 0) - return ret; - - val =3D get_unaligned_le16(&fw->data[offset]); - ret =3D __phy_write(phydev, AIR_BPBUS_WR_DATA_LOW, val); - if (ret < 0) - return ret; - } - - return 0; -} - -static int air_write_buf(struct phy_device *phydev, u32 address, - const struct firmware *fw) -{ - int saved_page; - int ret =3D 0; - - if (fw->size % 4) { - phydev_err(phydev, "firmware size %zu is not a multiple of 4\n", - fw->size); - return -EINVAL; - } - - saved_page =3D phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); - - if (saved_page >=3D 0) { - ret =3D __air_write_buf(phydev, address, fw); - if (ret < 0) - phydev_err(phydev, "%s 0x%08x failed: %d\n", __func__, - address, ret); - } - - return phy_restore_page(phydev, saved_page, ret); -} - static int en8811h_wait_mcu_ready(struct phy_device *phydev) { - int ret, reg_value; + int ret; =20 - ret =3D air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_FINISH); - if (ret) - return ret; + ret =3D air_en8811h_wait_mcu_ready(&phydev->mdio); + if (ret < 0) + phydev_err(phydev, "MCU not ready: %pe\n", ERR_PTR(ret)); =20 - /* Because of mdio-lock, may have to wait for multiple loads */ - ret =3D phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, - EN8811H_PHY_FW_STATUS, reg_value, - reg_value =3D=3D EN8811H_PHY_READY, - 20000, 7500000, true); - if (ret) { - phydev_err(phydev, "MCU not ready: 0x%x\n", reg_value); - return -ENODEV; - } - - return 0; + return ret; } =20 static int an8811hb_check_crc(struct phy_device *phydev, u32 set1, @@ -411,7 +324,7 @@ static int an8811hb_load_file(struct phy_device *phydev= , const char *name, if (ret < 0) return ret; =20 - ret =3D air_write_buf(phydev, address, fw); + ret =3D air_fw_write_buf(&phydev->mdio, address, fw); release_firmware(fw); return ret; } @@ -507,56 +420,15 @@ static int an8811hb_load_firmware(struct phy_device *= phydev) =20 static int en8811h_load_firmware(struct phy_device *phydev) { - struct device *dev =3D &phydev->mdio.dev; - const struct firmware *fw1, *fw2; + struct en8811h_priv *priv =3D phydev->priv; int ret; =20 - ret =3D request_firmware_direct(&fw1, EN8811H_MD32_DM, dev); - if (ret < 0) - return ret; - - ret =3D request_firmware_direct(&fw2, EN8811H_MD32_DSP, dev); - if (ret < 0) - goto en8811h_load_firmware_rel1; - - ret =3D air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_START); - if (ret < 0) - goto en8811h_load_firmware_out; - - ret =3D air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, - EN8811H_FW_CTRL_2_LOADING, - EN8811H_FW_CTRL_2_LOADING); - if (ret < 0) - goto en8811h_load_firmware_out; - - ret =3D air_write_buf(phydev, AIR_FW_ADDR_DM, fw1); - if (ret < 0) - goto en8811h_load_firmware_out; - - ret =3D air_write_buf(phydev, AIR_FW_ADDR_DSP, fw2); - if (ret < 0) - goto en8811h_load_firmware_out; - - ret =3D air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, - EN8811H_FW_CTRL_2_LOADING, 0); - if (ret < 0) - goto en8811h_load_firmware_out; - - ret =3D en8811h_wait_mcu_ready(phydev); - if (ret < 0) - goto en8811h_load_firmware_out; - - en8811h_print_fw_version(phydev); - -en8811h_load_firmware_out: - release_firmware(fw2); - -en8811h_load_firmware_rel1: - release_firmware(fw1); - + ret =3D air_en8811h_fw_download(&phydev->mdio, &priv->firmware_version); if (ret < 0) phydev_err(phydev, "Load firmware failed: %d\n", ret); + else + phydev_info(phydev, "MD32 firmware version: %08x\n", + priv->firmware_version); =20 return ret; } diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c index e314717843ae..672a82539dbe 100644 --- a/drivers/net/phy/air_phy_lib.c +++ b/drivers/net/phy/air_phy_lib.c @@ -8,8 +8,12 @@ */ =20 #include +#include +#include +#include #include #include +#include #include =20 #include "air_phy_lib.h" @@ -198,6 +202,299 @@ int air_phy_buckpbus_reg_modify(struct phy_device *ph= ydev, u32 pbus_address, } EXPORT_SYMBOL_GPL(air_phy_buckpbus_reg_modify); =20 +static int __air_write_buf(struct mdio_device *mdiodev, u32 address, + const u8 *data, size_t len) +{ + unsigned int offset; + int ret; + u16 val; + + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_INCR); + if (ret < 0) + return ret; + + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(address)); + if (ret < 0) + return ret; + + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(address)); + if (ret < 0) + return ret; + + for (offset =3D 0; offset < len; offset +=3D 4) { + val =3D get_unaligned_le16(&data[offset + 2]); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_HIGH, val); + if (ret < 0) + return ret; + + val =3D get_unaligned_le16(&data[offset]); + ret =3D __mdiodev_write(mdiodev, AIR_BPBUS_WR_DATA_LOW, val); + if (ret < 0) + return ret; + } + + return 0; +} + +/* phy_select_page() needs a phy_device, which does not exist yet. */ +static int __air_mdio_select_page(struct mdio_device *mdiodev, int page) +{ + int saved_page, ret; + + saved_page =3D __mdiodev_read(mdiodev, AIR_EXT_PAGE_ACCESS); + if (saved_page < 0) + return saved_page; + + if (saved_page !=3D page) { + ret =3D __mdiodev_write(mdiodev, AIR_EXT_PAGE_ACCESS, page); + if (ret < 0) + return ret; + } + + return saved_page; +} + +static int __air_mdio_restore_page(struct mdio_device *mdiodev, + int saved_page, int page, int ret) +{ + int restore; + + if (saved_page !=3D page) { + restore =3D __mdiodev_write(mdiodev, AIR_EXT_PAGE_ACCESS, + saved_page); + if (ret >=3D 0 && restore < 0) + ret =3D restore; + } + + return ret; +} + +int air_fw_write_buf(struct mdio_device *mdiodev, u32 address, + const struct firmware *fw) +{ + size_t chunk, done =3D 0; + int saved_page, ret; + + if (fw->size % 4) { + dev_err(&mdiodev->dev, "firmware size %zu is not a multiple of 4\n", + fw->size); + return -EINVAL; + } + + while (done < fw->size) { + chunk =3D min_t(size_t, fw->size - done, AIR_FW_CHUNK_BYTES); + + mutex_lock(&mdiodev->bus->mdio_lock); + + saved_page =3D __air_mdio_select_page(mdiodev, + AIR_PHY_PAGE_EXTENDED_4); + if (saved_page < 0) { + ret =3D saved_page; + } else { + ret =3D __air_write_buf(mdiodev, address + done, + fw->data + done, chunk); + ret =3D __air_mdio_restore_page(mdiodev, saved_page, + AIR_PHY_PAGE_EXTENDED_4, + ret); + } + + mutex_unlock(&mdiodev->bus->mdio_lock); + if (ret < 0) + return ret; + + done +=3D chunk; + } + + return 0; +} +EXPORT_SYMBOL_GPL(air_fw_write_buf); + +static int air_mdio_buckpbus_reg_read(struct mdio_device *mdiodev, + u32 pbus_address, u32 *pbus_data) +{ + int saved_page, ret; + + mutex_lock(&mdiodev->bus->mdio_lock); + + saved_page =3D __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4); + if (saved_page < 0) { + ret =3D saved_page; + } else { + ret =3D __air_buckpbus_reg_read(mdiodev, pbus_address, pbus_data); + ret =3D __air_mdio_restore_page(mdiodev, saved_page, + AIR_PHY_PAGE_EXTENDED_4, ret); + } + + mutex_unlock(&mdiodev->bus->mdio_lock); + return ret; +} + +static int air_mdio_buckpbus_reg_write(struct mdio_device *mdiodev, + u32 pbus_address, u32 pbus_data) +{ + int saved_page, ret; + + mutex_lock(&mdiodev->bus->mdio_lock); + + saved_page =3D __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4); + if (saved_page < 0) { + ret =3D saved_page; + } else { + ret =3D __air_buckpbus_reg_write(mdiodev, pbus_address, + pbus_data); + ret =3D __air_mdio_restore_page(mdiodev, saved_page, + AIR_PHY_PAGE_EXTENDED_4, ret); + } + + mutex_unlock(&mdiodev->bus->mdio_lock); + return ret; +} + +static int air_mdio_buckpbus_reg_modify(struct mdio_device *mdiodev, + u32 pbus_address, u32 mask, u32 set) +{ + int saved_page, ret; + + mutex_lock(&mdiodev->bus->mdio_lock); + + saved_page =3D __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4); + if (saved_page < 0) { + ret =3D saved_page; + } else { + ret =3D __air_buckpbus_reg_modify(mdiodev, pbus_address, + mask, set); + ret =3D __air_mdio_restore_page(mdiodev, saved_page, + AIR_PHY_PAGE_EXTENDED_4, ret); + } + + mutex_unlock(&mdiodev->bus->mdio_lock); + return ret; +} + +/* mmd_phy_read() drops the errors from the three writes that select the + * register, so a failed selection reads a different one back as success. + */ +static int __air_mmd_read(struct mdio_device *mdiodev, u16 devad, u16 regn= um) +{ + struct mii_bus *bus =3D mdiodev->bus; + int addr =3D mdiodev->addr; + int ret; + + ret =3D __mdiobus_write(bus, addr, MII_MMD_CTRL, devad); + if (ret < 0) + return ret; + + ret =3D __mdiobus_write(bus, addr, MII_MMD_DATA, regnum); + if (ret < 0) + return ret; + + ret =3D __mdiobus_write(bus, addr, MII_MMD_CTRL, + devad | MII_MMD_CTRL_NOINCR); + if (ret < 0) + return ret; + + return __mdiobus_read(bus, addr, MII_MMD_DATA); +} + +static int air_mmd_status_read(struct mdio_device *mdiodev) +{ + int ret; + + mutex_lock(&mdiodev->bus->mdio_lock); + ret =3D __air_mmd_read(mdiodev, MDIO_MMD_VEND1, EN8811H_PHY_FW_STATUS); + mutex_unlock(&mdiodev->bus->mdio_lock); + + return ret; +} + +int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev) +{ + int ret, reg_value; + + ret =3D air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_FINISH); + if (ret) + return ret; + + /* Because of mdio-lock, may have to wait for multiple loads. A read + * error ends the poll at once, like phy_read_mmd_poll_timeout(). + */ + ret =3D read_poll_timeout(air_mmd_status_read, reg_value, + reg_value < 0 || + reg_value =3D=3D EN8811H_PHY_READY, + 20000, 7500000, true, mdiodev); + if (reg_value < 0) + return reg_value; + if (ret) { + dev_dbg(&mdiodev->dev, "MCU not ready: 0x%x\n", reg_value); + return -ENODEV; + } + + return 0; +} +EXPORT_SYMBOL_GPL(air_en8811h_wait_mcu_ready); + +int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version) +{ + struct device *dev =3D &mdiodev->dev; + const struct firmware *fw1, *fw2; + int ret; + + ret =3D request_firmware_direct(&fw1, EN8811H_MD32_DM, dev); + if (ret < 0) + return ret; + + ret =3D request_firmware_direct(&fw2, EN8811H_MD32_DSP, dev); + if (ret < 0) + goto air_fw_download_rel1; + + ret =3D air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); + if (ret < 0) + goto air_fw_download_out; + + ret =3D air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, + EN8811H_FW_CTRL_2_LOADING); + if (ret < 0) + goto air_fw_download_out; + + ret =3D air_fw_write_buf(mdiodev, AIR_FW_ADDR_DM, fw1); + if (ret < 0) + goto air_fw_download_out; + + ret =3D air_fw_write_buf(mdiodev, AIR_FW_ADDR_DSP, fw2); + if (ret < 0) + goto air_fw_download_out; + + ret =3D air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, 0); + if (ret < 0) + goto air_fw_download_out; + + ret =3D air_en8811h_wait_mcu_ready(mdiodev); + if (ret < 0) + goto air_fw_download_out; + + ret =3D air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION, + fw_version); + +air_fw_download_out: + release_firmware(fw2); + +air_fw_download_rel1: + release_firmware(fw1); + + /* No error print: callers log on their own terms, and a poller + * would repeat it on every retry. + */ + return ret; +} +EXPORT_SYMBOL_GPL(air_en8811h_fw_download); + int air_phy_read_page(struct phy_device *phydev) { return __phy_read(phydev, AIR_EXT_PAGE_ACCESS); diff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h index 01bb32e7c7c9..c288b34f2ffc 100644 --- a/drivers/net/phy/air_phy_lib.h +++ b/drivers/net/phy/air_phy_lib.h @@ -10,12 +10,19 @@ =20 #include =20 +struct firmware; + #define AIR_EXT_PAGE_ACCESS 0x1f =20 #define AIR_PHY_PAGE_STANDARD 0x0000 #define AIR_PHY_PAGE_EXTENDED_1 0x0001 #define AIR_PHY_PAGE_EXTENDED_4 0x0004 =20 +/* Bytes written between bus releases, so the other PHYs on it get a turn. + * At a 2.5 MHz MDC this size holds the bus for about 30 ms. + */ +#define AIR_FW_CHUNK_BYTES 2304 + /* MII Registers Page 4*/ #define AIR_BPBUS_MODE 0x10 #define AIR_BPBUS_MODE_ADDR_FIXED 0x0000 @@ -29,6 +36,23 @@ #define AIR_BPBUS_RD_DATA_HIGH 0x17 #define AIR_BPBUS_RD_DATA_LOW 0x18 =20 +#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" +#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin" + +#define AIR_FW_ADDR_DM 0x00000000 +#define AIR_FW_ADDR_DSP 0x00100000 + +#define EN8811H_FW_CTRL_1 0x0f0018 +#define EN8811H_FW_CTRL_1_START 0x0 +#define EN8811H_FW_CTRL_1_FINISH 0x1 +#define EN8811H_FW_CTRL_2 0x800000 +#define EN8811H_FW_CTRL_2_LOADING BIT(11) + +#define EN8811H_PHY_FW_STATUS 0x8009 +#define EN8811H_PHY_READY 0x02 + +#define EN8811H_FW_VERSION 0x3b3c + int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_addres= s, u32 mask, u32 set); int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address, @@ -38,4 +62,9 @@ int air_phy_buckpbus_reg_write(struct phy_device *phydev,= u32 pbus_address, int air_phy_read_page(struct phy_device *phydev); int air_phy_write_page(struct phy_device *phydev, int page); =20 +int air_fw_write_buf(struct mdio_device *mdiodev, u32 address, + const struct firmware *fw); +int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev); +int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version); + #endif /* __AIR_PHY_LIB_H */ --=20 2.53.0 From nobody Fri Sep 25 12:34:02 2026 Received: from mail-wm1-f52.google.com (mail-wm1-f52.google.com [209.85.128.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 685CB47ECD3 for ; Sat, 12 Sep 2026 13:04:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218287; cv=none; b=tdLZmOKlB844ctKEiQVH8/RzLUWxPKhY7C+yDYXeG+iEwBMoag3XIWreyqY9UKFeZUSv1O5xnrEmhRW6HWVXgOZMkOe7t7ewEcR9cU1d4Kub+W1ljXHsyKjgFooFcAU0hF/MOSBesltwAKTvpMu0K3T6CoJMBcVgFk/Dpxd7J9Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218287; c=relaxed/simple; bh=0nzfD1yEyPbsKp/gVbB5i/nz0U65iWYGKzyKw2h1xAY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=KTXGpS+xuH4Jnzx6RvuA5ZSy7zWlyB4g3IbjPQDhMMNIPIb0/jOZIR08dOL75k8alXTejkrROzzBSSaqalMZ+A5nmTgyI2girY61ZUXHTrFAFBdE215FgTMLpvUPq2XAOonecfFVsHpEk2+pvGczJB12agFG46Yopc+u/ESktnw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la; spf=pass smtp.mailfrom=lex.la; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b=jzTA9YUX; arc=none smtp.client-ip=209.85.128.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lex.la Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b="jzTA9YUX" Received: by mail-wm1-f52.google.com with SMTP id 5b1f17b1804b1-4998b5a63e2so20127125e9.1 for ; Sat, 12 Sep 2026 06:04:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lex.la; s=google; t=1789218282; x=1789823082; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=8krarzmlflz0yDgWDl2bVIlnYuu42nINyDR+t6rfVXw=; b=jzTA9YUX07s7enm8sckO2rgDGrTXkJJOg9II7IjIVFXgKG5smxkTDe7l0aB2MKFalO 67rqh1ccVxVdcdlDXPds+GjY4oivkZPfoShibYVzsqx/uxTW8tBbpi1CYg4NvqbjLn6M j6aYwraiN6/tGHmJseFSZXb8N43iss+1MM+7QFagd8QbtdMYGKlTGOtwH6iTB31VlPpu kszxRP89fAPmEd8synnEJQaaDiEVOvH5Z0QjKXgxcKP6Ez0+Dg9JU3Ar1roK4rc92qPO /dlk7OeH6oQqHSghcKv6BAHLi0XogWhiDClvVKWkzqbbwSZXBGElkQhGvvgPx4sydOOs GHKQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789218282; x=1789823082; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=8krarzmlflz0yDgWDl2bVIlnYuu42nINyDR+t6rfVXw=; b=RUCsPJoqIbaJ9ur9/ymyx1HkHYsoVqkMS5fP5M4cl5zfdeS0H414TWPff2NKr8RBBV S9AqHTapqj6DnFPXXCLUfttnNFJ7w0HrQhiAEMgA43Xpuljs2FB+Cv9FsEQmf2Jrsm2E xYUI4WY4p6jmy6JEo6cykeeRtrelUOB8GXjVCZe2wJLdAowHXYolQChXfGa23dxQQUWV I8HOPQcekWE9WGM7+tqKoJV94o4vvfjtWqABufr5J//2ZgBwqnHmKBkD/Rq3ubDhI7Nj zDvgmxPhVOAftjhGA1HwMAJd/DmBsK4ZjUu7gYf6pxnThAiHcCQDhqw6oDGfJUa97V9t 1jiA== X-Forwarded-Encrypted: i=1; AKwUvBxvDezw6JCKkK359AmnH4v8+8mWPeJare/ZBQu/VgJEviOMWFnpJt7AFViTqPeB7KhYhfMhgyVLKMyCFyk=@vger.kernel.org X-Gm-Message-State: AFuF++k0cl88OsKD9CwgrqbmEokVSUk5Tdj35Ylsgg7FuZ7ArJTQpfv9 N4lDHbef+UU6v/fgBTNjKMnBGEOxYhcAUi/j87RYlgH5a21WAB/kSdDZ7FzJtydJbIs= X-Gm-Gg: AYBFou2b/rqYsoJHbb2c9iTBH6jYy+bZwwzJkt+q8phUxudgVq0rt8gWC/8eGiThyhS FbJt5Gj/cahUu2qg8pCZQ3psO0hC08FsrCZCOMlNGaP9XwO66fhDmt9bTd95TjQqx5gw1Wsz6MM N5+mNtlI13KkvuUtf6gj7n2/K1R2z/P3TIUFimRT8gASkIN+xjyZ+pwaHhEqaYWV8ry6uvHTCkg /eYo0nZggiTClvbVOpwwSQ+Ri+wZRPOcqxwoDiqW2GO/nhVDifOXMd/C+aUFtpGVml4PSZTkDSY KNGoMCjmVdn6u5rXVL/xKdInXZiUjO/Vwi0MJkvZ5ENkipSGKn9ANy5kryBBF8jk5QF1lE/CY/w xxz+WPo7tLnZAwZBqujMCtDtpWekYo66pFgePQcONMK/6sMI8gabM8pFw5WNSoBVKbByTT+o0MA RTcGZ3X2jGj0TSRfp5sBjv40QBEaEnBf+hPxs/YHSm7wk9e1aS7Q== X-Received: by 2002:a05:600c:6085:b0:49e:6cc1:14d9 with SMTP id 5b1f17b1804b1-49e6cc11680mr27415325e9.32.1789218282324; Sat, 12 Sep 2026 06:04:42 -0700 (PDT) Received: from remote-01 ([84.17.55.229]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49e6a06cfb8sm80985785e9.5.2026.09.12.06.04.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 06:04:42 -0700 (PDT) From: Aleksei Sviridkin To: andrew@lunn.ch, andrew+netdev@lunn.ch, hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, horms@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ericwouds@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Aleksei Sviridkin Subject: [RFC PATCH net-next v3 4/6] net: phy: air: skip the download when the MD32 is already running Date: Sat, 12 Sep 2026 16:04:28 +0300 Message-ID: <20260912130430.2246285-5-f@lex.la> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260912130430.2246285-1-f@lex.la> References: <20260912130430.2246285-1-f@lex.la> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The download is unconditional, so a chip whose firmware was loaded by something else - a bootloader, an earlier bind of the PHY driver, or an MDIO device serving the chip - is reprogrammed at 144KB per probe. Read the status register the loader already polls for readiness and skip the download when it reports ready, only picking up the running firmware's version. Adopting is reported back distinctly from loading, because nothing on that path touches FW_CTRL_1: the PHY driver must leave the MCU restart to .config_init() rather than assume probe already did it. The image in RAM now wins over the files on disk. Where the PHY node carries no reset-gpios nothing clears that RAM on unbind, so writing new firmware files and rebinding keeps the old image running and reports its version as the current one. A power cycle, or a reset line on the PHY node for phy_detach() to assert, brings the reload back. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- drivers/net/phy/air_en8811h.c | 4 ++-- drivers/net/phy/air_phy_lib.c | 23 +++++++++++++++++++++++ drivers/net/phy/air_phy_lib.h | 3 +++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c index 43d2eff808f1..c9b46cfc854f 100644 --- a/drivers/net/phy/air_en8811h.c +++ b/drivers/net/phy/air_en8811h.c @@ -1028,8 +1028,8 @@ static int en8811h_probe(struct phy_device *phydev) if (ret < 0) return ret; =20 - /* mcu has just restarted after firmware load */ - priv->mcu_needs_restart =3D false; + /* Firmware that was already running was never restarted here. */ + priv->mcu_needs_restart =3D (ret =3D=3D 1); =20 /* MDIO_DEVS1/2 empty, so set mmds_present bits here */ phydev->c45_ids.mmds_present |=3D MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c index 672a82539dbe..2b1a73beca8d 100644 --- a/drivers/net/phy/air_phy_lib.c +++ b/drivers/net/phy/air_phy_lib.c @@ -410,6 +410,16 @@ static int air_mmd_status_read(struct mdio_device *mdi= odev) return ret; } =20 +int air_en8811h_mcu_running(struct mdio_device *mdiodev) +{ + int ret =3D air_mmd_status_read(mdiodev); + + if (ret < 0) + return ret; + + return ret =3D=3D EN8811H_PHY_READY; +} + int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev) { int ret, reg_value; @@ -443,6 +453,19 @@ int air_en8811h_fw_download(struct mdio_device *mdiode= v, u32 *fw_version) const struct firmware *fw1, *fw2; int ret; =20 + ret =3D air_en8811h_mcu_running(mdiodev); + if (ret < 0) + return ret; + + if (ret) { + ret =3D air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION, + fw_version); + if (ret < 0) + return ret; + + return 1; + } + ret =3D request_firmware_direct(&fw1, EN8811H_MD32_DM, dev); if (ret < 0) return ret; diff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h index c288b34f2ffc..47b4bd0b7c1e 100644 --- a/drivers/net/phy/air_phy_lib.h +++ b/drivers/net/phy/air_phy_lib.h @@ -64,7 +64,10 @@ int air_phy_write_page(struct phy_device *phydev, int pa= ge); =20 int air_fw_write_buf(struct mdio_device *mdiodev, u32 address, const struct firmware *fw); +/* Returns 1 running, 0 dormant, negative on a failed status read. */ +int air_en8811h_mcu_running(struct mdio_device *mdiodev); int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev); +/* Returns 1 when it adopted firmware that was already running. */ int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version); =20 #endif /* __AIR_PHY_LIB_H */ --=20 2.53.0 From nobody Fri Sep 25 12:34:02 2026 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7466E47ECF4 for ; Sat, 12 Sep 2026 13:04:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218291; cv=none; b=XS6ULhfeAaK6uSPgqPqN6/qXcGezHxvMiWut+XmWjR0jweK/eqfqHg3HsCzpW70cuENgPxDBS3XMK1L7ifWvoRYQ9bfNpbqial9NNqEhNyMDsdPJZHVKGBiAoMh5GRVhFw8oNZV2mgFj0nePuqTCmIzwXU8hPT3MjCHapcTYu4A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218291; c=relaxed/simple; bh=XAoueMXgvCKdDmh9hu+fwEppagChbFelaFT9vHw64Yc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Pa7tom8+sgFQNF2nAvNSLnziOLmtShk6eXItdqfwtmuOIcCbV6tuogRLZABC3EKj12Ou8FAmhoGiUisTV+9QmrukFH587FpVl3wS9RE+P/WiCCPW16cD2pmm4TnREZREZexOs1jg3/B3BfwwFmZtFVaGnBJziVbEd4jWAdm/4dQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la; spf=pass smtp.mailfrom=lex.la; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b=X+PfcoD4; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lex.la Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b="X+PfcoD4" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-49cd77e0f95so13706635e9.3 for ; Sat, 12 Sep 2026 06:04:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lex.la; s=google; t=1789218284; x=1789823084; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=rdBss+NKC17hYBCRngZWh8I9bnRJtJ0u4Ve5Alj1nxA=; b=X+PfcoD4L7HWqjJkQKy+8mYzPklhFYYKXLU4v97xXAgqy1Xo8me9A1oNKZNmxwzdGw 7uMkyCSUXRKi+fE3ko+4c+8caiTzDr/rRF1wgU7VLhSdNLQtG8Mc5vk9b8faqYQJBv8Q oie2TOoBYCiMtP0cHA3FbnfFem82HKtDMrJThQOluTYc+eyYWCjoN3I1sIct/3nI1bRq i4ceX2esExHNGwHjJYkjKZp0vIGIxMkgoiuuOKw3mVgZim3b6YAmCjpes5YJp/TWiDD2 yHAJlAZjiytm8/ieyZs9nqsqnVPxmqHVOilgpOFUqSKJ6lvvPyapxwdg3maLI8m2exk2 kAIQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789218284; x=1789823084; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=rdBss+NKC17hYBCRngZWh8I9bnRJtJ0u4Ve5Alj1nxA=; b=Wy/hsp4aO0YAZ9qejLNZhmUOZS/VAgiCK1Oa9JV/YXvJldFN1F1VjwX0oJ3FLEjsrl AoYwg+q6TiA0DjEUYTM459hXWBGj3s2TQdt1wLxGSPwDcbPTMuJyhvhnHZj3oDDVqu82 QEMAj2Ft+XMKTI6UExoaoVqdt9RRWk51GDvvRpAAWP1Xva3MtiTKurEOrXiWkJTAa+kp zS1Lw1YrtJiJJuBJWoKGPBlT2u7BGLX8Yw/VLugwEjUPC8K07V1drDyXsqUjpR855lya DjWR+1V1yLTh4zhtNg2t9cjaiVSynKNpYIvwryY/uaUXJCDtX4SyZPUjciP8iBWjIFj3 Ng8w== X-Forwarded-Encrypted: i=1; AKwUvBwZvOo/ewhv1rWIYn6u2uiXlOQwyIgothvMy+RlKZ6V+xhYxO4TvcPqswf505LPAt4c4mlG/mYAsMEXwPI=@vger.kernel.org X-Gm-Message-State: AFuF++mK/03G3xROu0RuCx+O+jkKM6c8baREZW1ixDEn84pK7GP1JIrv 1rMGW2NZFDdOUlYAR7/SNZBsU2hH6X5+W5ozL/c2II2r/MbgARBD0+59TUIUh79niu0= X-Gm-Gg: AYBFou1bTZ6gD5DP6R0tNLs2I1GZ9IvUDMgWhRyGAbA7gFtfeizSxHAvC4U4l1NYlN4 iq+iR4FTBQvSJe+eaZifTORXNrgQiRFXXEwNDJ8ilqQvYk4T4aZ4UyJXSDqu+km0dmTLXWqWvBI rhEzQ0N2SPpvelEXe7EvirMAZt1mFbYQ3mhClT2lHqhv0p7n5nrMeBQo6n17K+GEWwj8zAxGI+b 3eCuy4P+dIyJ7CGAgQA/75MGkyNtoaV1jTjigX9o++/RGSvh+AXsj+9mXUV95gbJ0iGi+NqkbHG L3MJOQRK+ifrFRuyy8tT710apl4nkzsyQcJzgRdvM7b/6LB8pBEyG5qGeP38sNhNILttJbV3mBY L96HjLdCCVeiMhJNFjgtKxCog6UDvYPGfu2iwXYP9FHMK0wxSsgktFz2kJS6AiWnsB9XmLPXalt yZmeaw6KziUB4mu5cX+d8+EmFSLJDiw4A1miwsdlRec6aGwDBxCG9QS/Yh1zyS X-Received: by 2002:a05:600c:8705:b0:49d:17a8:211a with SMTP id 5b1f17b1804b1-49e619d89a7mr105000865e9.28.1789218283881; Sat, 12 Sep 2026 06:04:43 -0700 (PDT) Received: from remote-01 ([84.17.55.229]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49e6a06cfb8sm80985785e9.5.2026.09.12.06.04.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 06:04:43 -0700 (PDT) From: Aleksei Sviridkin To: andrew@lunn.ch, andrew+netdev@lunn.ch, hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, horms@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ericwouds@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Aleksei Sviridkin Subject: [RFC PATCH net-next v3 5/6] net: mdio: add Airoha EN8811H MDIO device driver Date: Sat, 12 Sep 2026 16:04:29 +0300 Message-ID: <20260912130430.2246285-6-f@lex.la> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260912130430.2246285-1-f@lex.la> References: <20260912130430.2246285-1-f@lex.la> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Until its firmware has been downloaded the EN8811H is not an Ethernet PHY, it is an MD32 microcontroller waiting in its bootloader. On systems that keep the firmware files in a filesystem, the files become readable long after the MDIO bus was scanned, and the PHY driver's probe-time download then cannot work at boot. Describe the chip as an MDIO device that polls for the files and downloads through the shared library helper once they can be read; a chip left running by the bootloader is adopted as-is. Polling rather than deferred probing because request_firmware_direct() has no usermode-helper fallback: an unmounted rootfs fails at once, and a deferral only leaves the device on the deferred-probe list until something else retries it, which need not happen when the files appear. There is no give-up path, since installing the firmware package on a running system is a normal thing to do. The reset line is claimed here rather than on the PHY node, and it is cycled only while the MD32 does not report a running firmware: that firmware lives in volatile RAM, so an assert on a running chip - such as the one phy_detach() performs on a PHY-node reset - would wipe it. Resume repeats the reset decision and the download in line, since a suspend that cut power leaves the MD32 back in its bootloader. Nothing yet publishes a PHY: this driver takes the chip over, and the bus that exposes it to the device tree is the next patch. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- Notes: Splitting the series there keeps the download and its retry policy separate from what registering a bus costs, which is its own failure mode with its own backoff. =20 A status read that fails says nothing about the firmware, so the reset line is left alone unless it is already asserted; clearing an asserted one is worth a try, since nothing answers through it. =20 The download registers both blobs with the firmware cache, so the reload on resume reaches a filesystem only if that registration failed. On the download path that registration is already done: request_firmware_direc= t() passes no FW_OPT_NOCACHE, so assign_fw() has called fw_add_devm_name() for both names. It is the adoption path, which never calls request_firmware at all, that needs it. =20 The reload on resume is not cheap: 144 KB over MDIO is about two second= s, and the readiness poll after it allows another seven and a half. The re= st of the resume chain waits for it, which is the price of having the firmware back before the PHY below runs its own resume. MAINTAINERS | 2 + drivers/net/mdio/Kconfig | 13 ++ drivers/net/mdio/Makefile | 1 + drivers/net/mdio/mdio-airoha-en8811h.c | 197 +++++++++++++++++++++++ drivers/net/phy/air_phy_lib.c | 1 + drivers/net/phy/air_phy_lib.h | 8 +- include/linux/mdio/mdio-airoha-en8811h.h | 24 +++ 7 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 drivers/net/mdio/mdio-airoha-en8811h.c create mode 100644 include/linux/mdio/mdio-airoha-en8811h.h diff --git a/MAINTAINERS b/MAINTAINERS index 4ca19275f4d0..3bd86df2be57 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -755,6 +755,8 @@ M: Aleksei Sviridkin L: netdev@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/net/airoha,en8811h-mcu.yaml +F: drivers/net/mdio/mdio-airoha-en8811h.c +F: include/linux/mdio/mdio-airoha-en8811h.h =20 AIROHA ETHERNET DRIVER M: Lorenzo Bianconi diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig index d44278f26fab..57c83bfd4090 100644 --- a/drivers/net/mdio/Kconfig +++ b/drivers/net/mdio/Kconfig @@ -29,6 +29,19 @@ config MDIO_AIROHA This module provides a driver for the MDIO busses found in the Airoha AN7583 SoC's. =20 +config MDIO_AIROHA_EN8811H + tristate "Airoha EN8811H MDIO device support" + depends on OF_MDIO + depends on FW_LOADER + select AIR_NET_PHYLIB + imply AIR_EN8811H_PHY + help + This module provides a driver for the Airoha EN8811H, which is an + MD32 microcontroller until firmware is downloaded into it and only + becomes an Ethernet PHY afterwards. The driver downloads that + firmware once it becomes readable, or leaves in place firmware a + bootloader already started. + config MDIO_SUN4I tristate "Allwinner sun4i MDIO interface support" depends on ARCH_SUNXI || COMPILE_TEST diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile index 048586746026..06d096675dac 100644 --- a/drivers/net/mdio/Makefile +++ b/drivers/net/mdio/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_FWNODE_MDIO) +=3D fwnode_mdio.o obj-$(CONFIG_OF_MDIO) +=3D of_mdio.o =20 obj-$(CONFIG_MDIO_AIROHA) +=3D mdio-airoha.o +obj-$(CONFIG_MDIO_AIROHA_EN8811H) +=3D mdio-airoha-en8811h.o obj-$(CONFIG_MDIO_ASPEED) +=3D mdio-aspeed.o obj-$(CONFIG_MDIO_BCM_IPROC) +=3D mdio-bcm-iproc.o obj-$(CONFIG_MDIO_BCM_UNIMAC) +=3D mdio-bcm-unimac.o diff --git a/drivers/net/mdio/mdio-airoha-en8811h.c b/drivers/net/mdio/mdio= -airoha-en8811h.c new file mode 100644 index 000000000000..b88e3c014d15 --- /dev/null +++ b/drivers/net/mdio/mdio-airoha-en8811h.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Airoha EN8811H MDIO device driver + * + * The EN8811H is an MD32 microcontroller until firmware is downloaded into + * it, and only then an Ethernet PHY. + * + * Copyright (C) 2026 Aleksei Sviridkin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define EN8811H_FW_POLL_MIN_MS 1000 +#define EN8811H_FW_POLL_MAX_MS 30000 +#define EN8811H_FW_WARN_MS 60000 + +struct en8811h_mcu { + struct mdio_device *mdiodev; + struct gpio_desc *reset_gpio; + struct delayed_work fw_poll; + unsigned int poll_ms; + unsigned int waited_ms; + u32 fw_version; + bool warned; +}; + +static void en8811h_mcu_fw_poll(struct work_struct *work) +{ + struct en8811h_mcu *mcu =3D container_of(to_delayed_work(work), + struct en8811h_mcu, fw_poll); + struct device *dev =3D &mcu->mdiodev->dev; + int cached, ret; + + ret =3D air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version); + if (ret >=3D 0) { + dev_dbg(dev, "firmware %08x running after %ums\n", + mcu->fw_version, mcu->waited_ms); + cached =3D firmware_request_cache(dev, EN8811H_MD32_DM); + ret =3D firmware_request_cache(dev, EN8811H_MD32_DSP) ?: cached; + if (ret) + dev_warn(dev, "not cached, resume will read the files off a filesystem:= %pe\n", + ERR_PTR(ret)); + return; + } + + if (!mcu->warned && mcu->waited_ms >=3D EN8811H_FW_WARN_MS) { + if (ret =3D=3D -ENOENT) + dev_warn(dev, "still waiting for %s and %s\n", + EN8811H_MD32_DM, EN8811H_MD32_DSP); + else + dev_warn(dev, "firmware download keeps failing: %pe\n", + ERR_PTR(ret)); + mcu->warned =3D true; + } + + /* Count the sleep ahead: the first run was immediate. */ + mcu->waited_ms +=3D mcu->poll_ms; + queue_delayed_work(system_freezable_wq, &mcu->fw_poll, + msecs_to_jiffies(mcu->poll_ms)); + mcu->poll_ms =3D min(mcu->poll_ms * 2, EN8811H_FW_POLL_MAX_MS); +} + +/* The firmware lives in volatile RAM: no reset while the MD32 reports rea= dy. */ +static void en8811h_mcu_reset_unless_running(struct en8811h_mcu *mcu) +{ + struct mdio_device *mdiodev =3D mcu->mdiodev; + struct device *dev =3D &mdiodev->dev; + u32 assert_us =3D 0, deassert_us =3D 0; + int ret; + + ret =3D air_en8811h_mcu_running(mdiodev); + if (ret > 0) { + dev_dbg(dev, "MD32 already running, leaving reset alone\n"); + return; + } + + if (!mcu->reset_gpio) + return; + + /* A failed read does not mean the firmware is gone: leave a + * deasserted line alone, and clear an asserted one before giving up. + */ + if (ret < 0 && gpiod_get_value_cansleep(mcu->reset_gpio) <=3D 0) { + dev_dbg(dev, "MD32 state unknown (%d), leaving reset alone\n", + ret); + return; + } + + device_property_read_u32(dev, "reset-assert-us", &assert_us); + device_property_read_u32(dev, "reset-deassert-us", &deassert_us); + + ret =3D gpiod_direction_output(mcu->reset_gpio, 1); + if (ret) { + dev_warn(dev, "reset not asserted: %pe\n", ERR_PTR(ret)); + return; + } + + if (assert_us) + fsleep(assert_us); + + gpiod_set_value_cansleep(mcu->reset_gpio, 0); + if (deassert_us) + fsleep(deassert_us); +} + +static int en8811h_mcu_probe(struct mdio_device *mdiodev) +{ + struct device *dev =3D &mdiodev->dev; + struct en8811h_mcu *mcu; + + mcu =3D devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL); + if (!mcu) + return -ENOMEM; + + mcu->mdiodev =3D mdiodev; + mdiodev_set_drvdata(mdiodev, mcu); + + /* The core claims reset-gpios only for devices flagged as PHYs. */ + mcu->reset_gpio =3D devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); + if (IS_ERR(mcu->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(mcu->reset_gpio), + "failed to get reset GPIO\n"); + + if (mcu->reset_gpio) + gpiod_set_consumer_name(mcu->reset_gpio, "EN8811H reset"); + + en8811h_mcu_reset_unless_running(mcu); + + mcu->poll_ms =3D EN8811H_FW_POLL_MIN_MS; + INIT_DELAYED_WORK(&mcu->fw_poll, en8811h_mcu_fw_poll); + /* Freezable: neither the file lookup nor the download may land on + * a suspending bus. + */ + queue_delayed_work(system_freezable_wq, &mcu->fw_poll, 0); + + return 0; +} + +static void en8811h_mcu_remove(struct mdio_device *mdiodev) +{ + struct en8811h_mcu *mcu =3D mdiodev_get_drvdata(mdiodev); + + cancel_delayed_work_sync(&mcu->fw_poll); +} + +static int en8811h_mcu_resume(struct device *dev) +{ + struct en8811h_mcu *mcu =3D dev_get_drvdata(dev); + int ret; + + /* Synchronous: the poll's workqueue is freezable and thaws only + * after the resume callbacks have run. + */ + en8811h_mcu_reset_unless_running(mcu); + ret =3D air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version); + if (ret < 0) + dev_err(dev, "firmware not restored: %pe\n", ERR_PTR(ret)); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(en8811h_mcu_pm_ops, NULL, en8811h_mcu_resu= me); + +static const struct of_device_id en8811h_mcu_of_match[] =3D { + { .compatible =3D "airoha,en8811h-mcu" }, + { } +}; +MODULE_DEVICE_TABLE(of, en8811h_mcu_of_match); + +static struct mdio_driver en8811h_mcu_driver =3D { + .probe =3D en8811h_mcu_probe, + .remove =3D en8811h_mcu_remove, + .mdiodrv.driver =3D { + .name =3D "airoha-en8811h-mcu", + .of_match_table =3D en8811h_mcu_of_match, + .pm =3D pm_sleep_ptr(&en8811h_mcu_pm_ops), + }, +}; + +mdio_module_driver(en8811h_mcu_driver); + +MODULE_FIRMWARE(EN8811H_MD32_DM); +MODULE_FIRMWARE(EN8811H_MD32_DSP); + +MODULE_DESCRIPTION("Airoha EN8811H MDIO device driver"); +MODULE_AUTHOR("Aleksei Sviridkin "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c index 2b1a73beca8d..c71322f16c21 100644 --- a/drivers/net/phy/air_phy_lib.c +++ b/drivers/net/phy/air_phy_lib.c @@ -419,6 +419,7 @@ int air_en8811h_mcu_running(struct mdio_device *mdiodev) =20 return ret =3D=3D EN8811H_PHY_READY; } +EXPORT_SYMBOL_GPL(air_en8811h_mcu_running); =20 int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev) { diff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h index 47b4bd0b7c1e..764f832a02a7 100644 --- a/drivers/net/phy/air_phy_lib.h +++ b/drivers/net/phy/air_phy_lib.h @@ -8,6 +8,7 @@ #ifndef __AIR_PHY_LIB_H #define __AIR_PHY_LIB_H =20 +#include #include =20 struct firmware; @@ -36,9 +37,6 @@ struct firmware; #define AIR_BPBUS_RD_DATA_HIGH 0x17 #define AIR_BPBUS_RD_DATA_LOW 0x18 =20 -#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" -#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin" - #define AIR_FW_ADDR_DM 0x00000000 #define AIR_FW_ADDR_DSP 0x00100000 =20 @@ -64,10 +62,6 @@ int air_phy_write_page(struct phy_device *phydev, int pa= ge); =20 int air_fw_write_buf(struct mdio_device *mdiodev, u32 address, const struct firmware *fw); -/* Returns 1 running, 0 dormant, negative on a failed status read. */ -int air_en8811h_mcu_running(struct mdio_device *mdiodev); int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev); -/* Returns 1 when it adopted firmware that was already running. */ -int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version); =20 #endif /* __AIR_PHY_LIB_H */ diff --git a/include/linux/mdio/mdio-airoha-en8811h.h b/include/linux/mdio/= mdio-airoha-en8811h.h new file mode 100644 index 000000000000..8a073a1b7ee6 --- /dev/null +++ b/include/linux/mdio/mdio-airoha-en8811h.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023, 2026 Airoha Technology Corp. + * Copyright (C) 2026 Aleksei Sviridkin + */ + +#ifndef __LINUX_MDIO_AIROHA_EN8811H_H +#define __LINUX_MDIO_AIROHA_EN8811H_H + +#include + +struct mdio_device; + +#define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" +#define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin" + +/* Returns 1 when the firmware runs, 0 when the MD32 is still in its + * bootloader, and negative on a failed status read. + */ +int air_en8811h_mcu_running(struct mdio_device *mdiodev); +/* Returns 1 when firmware was already running and was left in place. */ +int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version); + +#endif /* __LINUX_MDIO_AIROHA_EN8811H_H */ --=20 2.53.0 From nobody Fri Sep 25 12:34:02 2026 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BDE3E47F2C3 for ; Sat, 12 Sep 2026 13:04:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218292; cv=none; b=IHuPUQ2punhyI6JtursjQxSYobSNE9+R7YlRHEe2zCIPXzyu3szJiVJVzFv4AhWdw6EnaTB87HOzm2598DF9cI04/IBGEiIGiDDDGQurtOE50SaGWuT7ppmqNvJftz7eb1U7UJMhMrXdQmYVMkAT3SbP95Dy1YJmZnqd0uK/BeI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789218292; c=relaxed/simple; bh=Ftirs6/ayPMY1If4qF6MlBmHCpROhxXe9tVg2E2pjgM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=X7enekqu1Rdm5CWWQ4Ftw7JbqmnlNaLtmS323dXKM5BsjTHZ6uxqft+6hFJafzFqzwzZj8iPBxSn+mzqwY3YQWX5BI1Y46fOh97TRKtCsjPGidJqNJGm4V8mNncnmX3xo6bdQMjTnzO0RqYuGKW4hH60kTmy+fBsOwNTQzmC/5w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la; spf=pass smtp.mailfrom=lex.la; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b=H2X1pNjI; arc=none smtp.client-ip=209.85.128.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=lex.la Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lex.la Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=lex.la header.i=@lex.la header.b="H2X1pNjI" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-49b965570d7so23080155e9.0 for ; Sat, 12 Sep 2026 06:04:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=lex.la; s=google; t=1789218285; x=1789823085; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=AzYYCi2wC429RRKE9FxFr02zCF+c7miGEDqNGISzODc=; b=H2X1pNjItyep1hiI5GPckgp1aVz2KcIv/VRpYQt+KYAUZTEz1WhDU2mEzTuPke5qke /XOaRadroLiqX7r1vjlwyYufUs08J6nSuDOswwRXa1GUG2G7AOxC5nr5gbSpKoKf7hOT iiTdODc8wv1Gy1+zG9YellSG7UdB/7DL+hbHUsmKrgpCR011LFW7pqcSZNhUoP1sQgT+ vG94u+LnUfjv33Gxrl2GXnAchANocc/5B9aEYKOxtC6WAg4/Oo62aJLJ2Z/uFH6P77Sg ULSy3voLspAeajk1jCKEvcTEbGjG2eKDoBSvcVQvstvL4ZYHf1zg2n9j5u9iK6p4xjJQ egOw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789218285; x=1789823085; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=AzYYCi2wC429RRKE9FxFr02zCF+c7miGEDqNGISzODc=; b=B8hwL1MX6tYah2SncBn6gvCeMMKmzhjfMD+lH1xXStl2ttzXcybGpZHhm5t/h6Lv+A h7bnqrOHPpEFfhg+Tq6207wDJAkMk7eNvsKhuzgRL9/MRq9EkYm7vX9U1CZWvi0iNr6u SJXO6GyP0KDTBCCG+GWEf1eEMJIK9HhFgbChwzQwfYVq2TqF4rzqQ8dwvU5/91zZen0R O95JKc70T8YMD43Oy1itYeDHyiEpe8KRaSbaDYWdrTZ3pk0YYHei8aGRRmG1s5FKxDuQ 49iC2NtrRBlN95HU6et/7XLkCK4eQCS1p7qGscO3D/vQKUu68arBqzllMGdtK/cMKCNJ P0hg== X-Forwarded-Encrypted: i=1; AKwUvBwEXIIhLswTX2/xWpw/KiFmA2X/2NH+2z7723fuD0/2Agy3VIvwLm5qFsrCuvxPJGc33UO6YxBJPluHAAw=@vger.kernel.org X-Gm-Message-State: AFuF++mqCdUpbp1h9K8TBMI/AKxexxaI/+LDnbRIYGusZw9A7xClRx7o uzvhw+bwjmewW9p4EHE/tzvA93AOjE3nqS88QPCG6XRo+zwpob6VxLoIQTD6gp711hA= X-Gm-Gg: AYBFou2FMEhU0V7CZo4zAkNJUtKBSpG/BLw0TgmzmX1zzDSLWLHRvOQPSqYuOFQoyUs gjWsxffycbaU0AXGvkboPpcVSc0uq1f96YV36EEkmERzy42+FtOCb9jZhWQp719oVImJHCBPs8C MVh/6zUPC8AwS+n39qE45iR4JByVqIeXjQEi4A2TEZMxrd9hSnQ4qefKaGckYWVNNKsbeS13499 8E7tbv90hpxtzpXpBtuP84kJTR55qqhcEvnuwwGC2pv+rTDHWwbeQxCFg6zeUHqCtj4rZaxWcx9 IAvjLCnGWnejykCpZViT/+tV0mLhtoDZYMiLAEFqpte6KT4qRqmK4p/oHUaBRE3pk28biSnywFl yeqisHtqraUtnEz61nx8WlDSd/oqbkv1BK6hQ2JClwVQhhzZazHK9F578/ler/JLW9HwK3GydBX mIyfnc6yda5glepBvRVJ34on45auKkXujJdt1zjVbv3fYAV3Jx8g== X-Received: by 2002:a05:600c:34d5:b0:49e:6b7c:63b4 with SMTP id 5b1f17b1804b1-49e6b7c659dmr33857935e9.29.1789218285390; Sat, 12 Sep 2026 06:04:45 -0700 (PDT) Received: from remote-01 ([84.17.55.229]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49e6a06cfb8sm80985785e9.5.2026.09.12.06.04.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 06:04:45 -0700 (PDT) From: Aleksei Sviridkin To: andrew@lunn.ch, andrew+netdev@lunn.ch, hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, horms@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: ericwouds@gmail.com, netdev@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Aleksei Sviridkin Subject: [RFC PATCH net-next v3 6/6] net: mdio: en8811h: add the nested bus Date: Sat, 12 Sep 2026 16:04:30 +0300 Message-ID: <20260912130430.2246285-7-f@lex.la> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260912130430.2246285-1-f@lex.la> References: <20260912130430.2246285-1-f@lex.la> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Registering the bus is what publishes the PHY, so it can only happen once the MD32 runs its firmware. Giving the PHY a bus of its own rather than the parent lets the device tree describe it normally, interrupt included, and leaves the reset line owned by the MCU, where the PHY cannot assert it. Only the address the MD32 answers on is passed through. That split puts the PHY driver and this one on two mutexes: the PHY holds the child bus lock across a whole paged sequence while each frame under it takes only the parent, so a chip access holding the parent alone would land between two of those frames and step on the state the PHY left mid-sequence - the buckpbus mode and address registers it had already written. The library therefore holds the child lock across the whole of its own chip access, and the caller hands it the bus to take or nothing when there is none. Only the firmware read stays outside: those files may have to come from a filesystem reached over that very PHY. Registering the bus can fail on its own, so it becomes a second retry phase. Where the wait for firmware files never gives up, this one does once its minute is spent - except on a deferred probe, which nothing but a retry resolves. A failed reload on resume goes back to that poller, which restores the chip and not the port. The sysfs bind attributes are suppressed, because unregistering this bus while its PHY is attached leaves the consumer holding a phy_device whose driver is gone, and this driver cannot tell a consumer it does not know to let go. Module unload needs no such guard, since attaching a PHY takes a reference on the child bus owner, which is this module. Removing the parent MDIO bus still reaches this driver's remove and is not covered; that wants a way for a bus to tell its PHYs' consumers to let go. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin --- Notes: Why the registration phase gives up where the wait for firmware does not: every failed registration repeats a bus creation, a message from the MDIO core and a pair of uevents. Firmware files are the opposite case, since the package carrying them can be installed at any time. =20 Why the deferral is exempt, since the default configuration hides the reason. fwnode_mdiobus_phy_device_register() runs fwnode_irq_get()'s deferral through driver_deferred_probe_check_state(), which answers -ETIMEDOUT once deferred_probe_timeout has expired, or -ENODEV when the kernel is built without modules; neither escapes, and the PHY is registered with the bus default that mdiobus_alloc() filled with PHY_POLL. So a PHY that asks for an interrupt gets one only while its controller can still defer the registration. With the timeout at its default of ten seconds this driver, which registers only once firmware files can be read, is always past that point and never sees a deferral at all. But the timeout is a kernel parameter: a negative value is documented as infinite, the timer is then never armed, and every successful driver registration restarts it while it is pending. Under either setting the deferral persists until the dependency shows up, and a driver that stopped retrying after a minute would leave the PHY absent until reboot, with fw_running still true so neither the poller nor resume tries again. =20 Why a failed reload on resume restores the chip and not the port: the PHY below resumes next, its phy_init_hw() runs against a chip still in its bootloader and fails, and when the poller later gets the firmware back nothing runs that resume again. =20 Both lock helpers in the library take mdio_lock directly rather than through phylib's phy_lock_mdio_bus(), which keys off a phy_device: this code runs before one exists for the chip, and the child bus never has one at all. drivers/net/mdio/mdio-airoha-en8811h.c | 203 +++++++++++++++++++++-- drivers/net/phy/air_en8811h.c | 7 +- drivers/net/phy/air_phy_lib.c | 89 +++++++--- drivers/net/phy/air_phy_lib.h | 5 +- include/linux/mdio/mdio-airoha-en8811h.h | 11 +- 5 files changed, 271 insertions(+), 44 deletions(-) diff --git a/drivers/net/mdio/mdio-airoha-en8811h.c b/drivers/net/mdio/mdio= -airoha-en8811h.c index b88e3c014d15..7785525c57a6 100644 --- a/drivers/net/mdio/mdio-airoha-en8811h.c +++ b/drivers/net/mdio/mdio-airoha-en8811h.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -28,12 +30,109 @@ struct en8811h_mcu { struct mdio_device *mdiodev; struct gpio_desc *reset_gpio; struct delayed_work fw_poll; + struct mii_bus *bus; unsigned int poll_ms; unsigned int waited_ms; u32 fw_version; bool warned; + bool fw_running; }; =20 +static int en8811h_mcu_read(struct mii_bus *bus, int addr, int regnum) +{ + struct en8811h_mcu *mcu =3D bus->priv; + + if (addr !=3D mcu->mdiodev->addr) + return -ENODEV; + + return mdiobus_read_nested(mcu->mdiodev->bus, addr, regnum); +} + +static int en8811h_mcu_write(struct mii_bus *bus, int addr, int regnum, u1= 6 val) +{ + struct en8811h_mcu *mcu =3D bus->priv; + + if (addr !=3D mcu->mdiodev->addr) + return -ENODEV; + + return mdiobus_write_nested(mcu->mdiodev->bus, addr, regnum, val); +} + +static int en8811h_mcu_read_c45(struct mii_bus *bus, int addr, int devad, + int regnum) +{ + struct en8811h_mcu *mcu =3D bus->priv; + + if (addr !=3D mcu->mdiodev->addr) + return -ENODEV; + + return mdiobus_c45_read_nested(mcu->mdiodev->bus, addr, devad, regnum); +} + +static int en8811h_mcu_write_c45(struct mii_bus *bus, int addr, int devad, + int regnum, u16 val) +{ + struct en8811h_mcu *mcu =3D bus->priv; + + if (addr !=3D mcu->mdiodev->addr) + return -ENODEV; + + return mdiobus_c45_write_nested(mcu->mdiodev->bus, addr, devad, regnum, + val); +} + +static int en8811h_mcu_bus_register(struct en8811h_mcu *mcu) +{ + struct mii_bus *parent =3D mcu->mdiodev->bus; + struct device *dev =3D &mcu->mdiodev->dev; + struct device_node *np; + struct mii_bus *bus; + int ret; + + np =3D of_get_child_by_name(dev->of_node, "mdio"); + if (!np) + return -ENODEV; + + /* Not devm: this is retried, and a devm bus would only be freed at + * detach. + */ + bus =3D mdiobus_alloc(); + if (!bus) { + of_node_put(np); + return -ENOMEM; + } + + bus->name =3D "airoha-en8811h"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); + bus->priv =3D mcu; + bus->parent =3D dev; + if (parent->read && parent->write) { + bus->read =3D en8811h_mcu_read; + bus->write =3D en8811h_mcu_write; + } + if (parent->read_c45 && parent->write_c45) { + bus->read_c45 =3D en8811h_mcu_read_c45; + bus->write_c45 =3D en8811h_mcu_write_c45; + } + + ret =3D of_mdiobus_register(bus, np); + of_node_put(np); + if (!ret && !mdiobus_get_phy(bus, mcu->mdiodev->addr)) { + /* An ID read that failed leaves the bus registered and the + * PHY absent; of_mdiobus_register() returns 0 either way. + */ + mdiobus_unregister(bus); + ret =3D -ENODEV; + } + if (ret) { + mdiobus_free(bus); + return ret; + } + + mcu->bus =3D bus; + return 0; +} + static void en8811h_mcu_fw_poll(struct work_struct *work) { struct en8811h_mcu *mcu =3D container_of(to_delayed_work(work), @@ -41,8 +140,12 @@ static void en8811h_mcu_fw_poll(struct work_struct *wor= k) struct device *dev =3D &mcu->mdiodev->dev; int cached, ret; =20 - ret =3D air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version); - if (ret >=3D 0) { + if (!mcu->fw_running) { + ret =3D air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version, + mcu->bus); + if (ret < 0) + goto retry; + dev_dbg(dev, "firmware %08x running after %ums\n", mcu->fw_version, mcu->waited_ms); cached =3D firmware_request_cache(dev, EN8811H_MD32_DM); @@ -50,19 +153,38 @@ static void en8811h_mcu_fw_poll(struct work_struct *wo= rk) if (ret) dev_warn(dev, "not cached, resume will read the files off a filesystem:= %pe\n", ERR_PTR(ret)); - return; + mcu->fw_running =3D true; + mcu->poll_ms =3D EN8811H_FW_POLL_MIN_MS; + mcu->waited_ms =3D 0; + mcu->warned =3D false; } =20 + /* Resume re-runs the download, so the bus can already be here. */ + ret =3D mcu->bus ? 0 : en8811h_mcu_bus_register(mcu); + if (!ret) + return; + +retry: if (!mcu->warned && mcu->waited_ms >=3D EN8811H_FW_WARN_MS) { - if (ret =3D=3D -ENOENT) - dev_warn(dev, "still waiting for %s and %s\n", - EN8811H_MD32_DM, EN8811H_MD32_DSP); + if (!mcu->fw_running) + dev_warn(dev, "no firmware after %ums of waiting for %s and %s: %pe\n", + mcu->waited_ms, EN8811H_MD32_DM, + EN8811H_MD32_DSP, ERR_PTR(ret)); else - dev_warn(dev, "firmware download keeps failing: %pe\n", + dev_warn(dev, "still no PHY at address %d, %ums after the firmware star= ted: %pe\n", + mcu->mdiodev->addr, mcu->waited_ms, ERR_PTR(ret)); mcu->warned =3D true; } =20 + /* Registering again repeats the MDIO core's message and its uevents, + * so this phase gives up - except on a deferral, which only a retry + * resolves and which deferred_probe_timeout can make permanent. + */ + if (mcu->fw_running && ret !=3D -EPROBE_DEFER && + mcu->waited_ms >=3D EN8811H_FW_WARN_MS) + return; + /* Count the sleep ahead: the first run was immediate. */ mcu->waited_ms +=3D mcu->poll_ms; queue_delayed_work(system_freezable_wq, &mcu->fw_poll, @@ -78,7 +200,7 @@ static void en8811h_mcu_reset_unless_running(struct en88= 11h_mcu *mcu) u32 assert_us =3D 0, deassert_us =3D 0; int ret; =20 - ret =3D air_en8811h_mcu_running(mdiodev); + ret =3D air_en8811h_mcu_running(mdiodev, mcu->bus); if (ret > 0) { dev_dbg(dev, "MD32 already running, leaving reset alone\n"); return; @@ -117,6 +239,8 @@ static int en8811h_mcu_probe(struct mdio_device *mdiode= v) { struct device *dev =3D &mdiodev->dev; struct en8811h_mcu *mcu; + struct device_node *np; + unsigned int phys =3D 0; =20 mcu =3D devm_kzalloc(dev, sizeof(*mcu), GFP_KERNEL); if (!mcu) @@ -125,6 +249,43 @@ static int en8811h_mcu_probe(struct mdio_device *mdiod= ev) mcu->mdiodev =3D mdiodev; mdiodev_set_drvdata(mdiodev, mcu); =20 + /* Registration needs this only once the firmware runs, but a DT + * hole should fail the bind now, not as a work-item error later. + */ + np =3D of_get_child_by_name(dev->of_node, "mdio"); + if (!np || !of_device_is_available(np)) { + of_node_put(np); + return dev_err_probe(dev, -ENODEV, + "no usable mdio node describing the PHY\n"); + } + + /* Only the address this core occupies on the parent bus is passed + * through, so a node at any other one will never respond. + */ + for_each_available_child_of_node_scoped(np, child) { + u32 addr; + + if (of_property_read_u32(child, "reg", &addr)) + continue; + if (addr !=3D mdiodev->addr) { + dev_err(dev, "%pOF: address %u is not answered here, only %u\n", + child, addr, mdiodev->addr); + of_node_put(np); + return -EINVAL; + } + phys++; + } + of_node_put(np); + + if (!phys) + return dev_err_probe(dev, -ENODEV, + "mdio node describes no PHY\n"); + + /* A truncated bus name loses the address that makes it unique. */ + if (strlen(dev_name(dev)) >=3D MII_BUS_ID_SIZE) + return dev_err_probe(dev, -ENAMETOOLONG, + "name does not fit an MDIO bus id\n"); + /* The core claims reset-gpios only for devices flagged as PHYs. */ mcu->reset_gpio =3D devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); if (IS_ERR(mcu->reset_gpio)) @@ -151,6 +312,10 @@ static void en8811h_mcu_remove(struct mdio_device *mdi= odev) struct en8811h_mcu *mcu =3D mdiodev_get_drvdata(mdiodev); =20 cancel_delayed_work_sync(&mcu->fw_poll); + if (mcu->bus) { + mdiobus_unregister(mcu->bus); + mdiobus_free(mcu->bus); + } } =20 static int en8811h_mcu_resume(struct device *dev) @@ -158,13 +323,27 @@ static int en8811h_mcu_resume(struct device *dev) struct en8811h_mcu *mcu =3D dev_get_drvdata(dev); int ret; =20 + /* Nothing to redo: the poll is armed and thaws with everything else. */ + if (!mcu->fw_running) + return 0; + /* Synchronous: the poll's workqueue is freezable and thaws only * after the resume callbacks have run. */ en8811h_mcu_reset_unless_running(mcu); - ret =3D air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version); - if (ret < 0) - dev_err(dev, "firmware not restored: %pe\n", ERR_PTR(ret)); + ret =3D air_en8811h_fw_download(mcu->mdiodev, &mcu->fw_version, mcu->bus); + if (ret < 0) { + dev_err(dev, "firmware not restored, reloading: %pe\n", + ERR_PTR(ret)); + mcu->fw_running =3D false; + mcu->poll_ms =3D EN8811H_FW_POLL_MIN_MS; + mcu->waited_ms =3D 0; + mcu->warned =3D false; + /* The poll may already be pending on a long backoff, and + * queue_delayed_work() would leave that timer alone. + */ + mod_delayed_work(system_freezable_wq, &mcu->fw_poll, 0); + } =20 return 0; } @@ -184,6 +363,8 @@ static struct mdio_driver en8811h_mcu_driver =3D { .name =3D "airoha-en8811h-mcu", .of_match_table =3D en8811h_mcu_of_match, .pm =3D pm_sleep_ptr(&en8811h_mcu_pm_ops), + /* No safe teardown under an attached PHY. */ + .suppress_bind_attrs =3D true, }, }; =20 diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c index c9b46cfc854f..940379e2737b 100644 --- a/drivers/net/phy/air_en8811h.c +++ b/drivers/net/phy/air_en8811h.c @@ -258,7 +258,7 @@ static int en8811h_wait_mcu_ready(struct phy_device *ph= ydev) { int ret; =20 - ret =3D air_en8811h_wait_mcu_ready(&phydev->mdio); + ret =3D air_en8811h_wait_mcu_ready(&phydev->mdio, false); if (ret < 0) phydev_err(phydev, "MCU not ready: %pe\n", ERR_PTR(ret)); =20 @@ -324,7 +324,7 @@ static int an8811hb_load_file(struct phy_device *phydev= , const char *name, if (ret < 0) return ret; =20 - ret =3D air_fw_write_buf(&phydev->mdio, address, fw); + ret =3D air_fw_write_buf(&phydev->mdio, address, fw, false); release_firmware(fw); return ret; } @@ -423,7 +423,8 @@ static int en8811h_load_firmware(struct phy_device *phy= dev) struct en8811h_priv *priv =3D phydev->priv; int ret; =20 - ret =3D air_en8811h_fw_download(&phydev->mdio, &priv->firmware_version); + ret =3D air_en8811h_fw_download(&phydev->mdio, &priv->firmware_version, + NULL); if (ret < 0) phydev_err(phydev, "Load firmware failed: %d\n", ret); else diff --git a/drivers/net/phy/air_phy_lib.c b/drivers/net/phy/air_phy_lib.c index c71322f16c21..5902a6ba3643 100644 --- a/drivers/net/phy/air_phy_lib.c +++ b/drivers/net/phy/air_phy_lib.c @@ -272,8 +272,31 @@ static int __air_mdio_restore_page(struct mdio_device = *mdiodev, return ret; } =20 +static void air_mdiodev_lock(struct mdio_device *mdiodev, bool nested) +{ + if (nested) + mutex_lock_nested(&mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); + else + mutex_lock(&mdiodev->bus->mdio_lock); +} + +/* The PHY below reaches these registers under this lock through + * phy_select_page(); the firmware read must stay outside it. + */ +static void air_child_bus_lock(struct mii_bus *child_bus) +{ + if (child_bus) + mutex_lock(&child_bus->mdio_lock); +} + +static void air_child_bus_unlock(struct mii_bus *child_bus) +{ + if (child_bus) + mutex_unlock(&child_bus->mdio_lock); +} + int air_fw_write_buf(struct mdio_device *mdiodev, u32 address, - const struct firmware *fw) + const struct firmware *fw, bool nested) { size_t chunk, done =3D 0; int saved_page, ret; @@ -287,7 +310,7 @@ int air_fw_write_buf(struct mdio_device *mdiodev, u32 a= ddress, while (done < fw->size) { chunk =3D min_t(size_t, fw->size - done, AIR_FW_CHUNK_BYTES); =20 - mutex_lock(&mdiodev->bus->mdio_lock); + air_mdiodev_lock(mdiodev, nested); =20 saved_page =3D __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4); @@ -313,11 +336,12 @@ int air_fw_write_buf(struct mdio_device *mdiodev, u32= address, EXPORT_SYMBOL_GPL(air_fw_write_buf); =20 static int air_mdio_buckpbus_reg_read(struct mdio_device *mdiodev, - u32 pbus_address, u32 *pbus_data) + u32 pbus_address, u32 *pbus_data, + bool nested) { int saved_page, ret; =20 - mutex_lock(&mdiodev->bus->mdio_lock); + air_mdiodev_lock(mdiodev, nested); =20 saved_page =3D __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4); if (saved_page < 0) { @@ -333,11 +357,12 @@ static int air_mdio_buckpbus_reg_read(struct mdio_dev= ice *mdiodev, } =20 static int air_mdio_buckpbus_reg_write(struct mdio_device *mdiodev, - u32 pbus_address, u32 pbus_data) + u32 pbus_address, u32 pbus_data, + bool nested) { int saved_page, ret; =20 - mutex_lock(&mdiodev->bus->mdio_lock); + air_mdiodev_lock(mdiodev, nested); =20 saved_page =3D __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4); if (saved_page < 0) { @@ -354,11 +379,12 @@ static int air_mdio_buckpbus_reg_write(struct mdio_de= vice *mdiodev, } =20 static int air_mdio_buckpbus_reg_modify(struct mdio_device *mdiodev, - u32 pbus_address, u32 mask, u32 set) + u32 pbus_address, u32 mask, u32 set, + bool nested) { int saved_page, ret; =20 - mutex_lock(&mdiodev->bus->mdio_lock); + air_mdiodev_lock(mdiodev, nested); =20 saved_page =3D __air_mdio_select_page(mdiodev, AIR_PHY_PAGE_EXTENDED_4); if (saved_page < 0) { @@ -399,20 +425,25 @@ static int __air_mmd_read(struct mdio_device *mdiodev= , u16 devad, u16 regnum) return __mdiobus_read(bus, addr, MII_MMD_DATA); } =20 -static int air_mmd_status_read(struct mdio_device *mdiodev) +static int air_mmd_status_read(struct mdio_device *mdiodev, bool nested) { int ret; =20 - mutex_lock(&mdiodev->bus->mdio_lock); + air_mdiodev_lock(mdiodev, nested); ret =3D __air_mmd_read(mdiodev, MDIO_MMD_VEND1, EN8811H_PHY_FW_STATUS); mutex_unlock(&mdiodev->bus->mdio_lock); =20 return ret; } =20 -int air_en8811h_mcu_running(struct mdio_device *mdiodev) +int air_en8811h_mcu_running(struct mdio_device *mdiodev, + struct mii_bus *child_bus) { - int ret =3D air_mmd_status_read(mdiodev); + int ret; + + air_child_bus_lock(child_bus); + ret =3D air_mmd_status_read(mdiodev, !!child_bus); + air_child_bus_unlock(child_bus); =20 if (ret < 0) return ret; @@ -421,12 +452,12 @@ int air_en8811h_mcu_running(struct mdio_device *mdiod= ev) } EXPORT_SYMBOL_GPL(air_en8811h_mcu_running); =20 -int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev) +int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev, bool nested) { int ret, reg_value; =20 ret =3D air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_FINISH); + EN8811H_FW_CTRL_1_FINISH, nested); if (ret) return ret; =20 @@ -436,7 +467,7 @@ int air_en8811h_wait_mcu_ready(struct mdio_device *mdio= dev) ret =3D read_poll_timeout(air_mmd_status_read, reg_value, reg_value < 0 || reg_value =3D=3D EN8811H_PHY_READY, - 20000, 7500000, true, mdiodev); + 20000, 7500000, true, mdiodev, nested); if (reg_value < 0) return reg_value; if (ret) { @@ -448,19 +479,23 @@ int air_en8811h_wait_mcu_ready(struct mdio_device *md= iodev) } EXPORT_SYMBOL_GPL(air_en8811h_wait_mcu_ready); =20 -int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version) +int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version, + struct mii_bus *child_bus) { struct device *dev =3D &mdiodev->dev; const struct firmware *fw1, *fw2; + bool nested =3D !!child_bus; int ret; =20 - ret =3D air_en8811h_mcu_running(mdiodev); + ret =3D air_en8811h_mcu_running(mdiodev, child_bus); if (ret < 0) return ret; =20 if (ret) { + air_child_bus_lock(child_bus); ret =3D air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION, - fw_version); + fw_version, nested); + air_child_bus_unlock(child_bus); if (ret < 0) return ret; =20 @@ -475,38 +510,42 @@ int air_en8811h_fw_download(struct mdio_device *mdiod= ev, u32 *fw_version) if (ret < 0) goto air_fw_download_rel1; =20 + air_child_bus_lock(child_bus); + ret =3D air_mdio_buckpbus_reg_write(mdiodev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_START); + EN8811H_FW_CTRL_1_START, nested); if (ret < 0) goto air_fw_download_out; =20 ret =3D air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2, EN8811H_FW_CTRL_2_LOADING, - EN8811H_FW_CTRL_2_LOADING); + EN8811H_FW_CTRL_2_LOADING, nested); if (ret < 0) goto air_fw_download_out; =20 - ret =3D air_fw_write_buf(mdiodev, AIR_FW_ADDR_DM, fw1); + ret =3D air_fw_write_buf(mdiodev, AIR_FW_ADDR_DM, fw1, nested); if (ret < 0) goto air_fw_download_out; =20 - ret =3D air_fw_write_buf(mdiodev, AIR_FW_ADDR_DSP, fw2); + ret =3D air_fw_write_buf(mdiodev, AIR_FW_ADDR_DSP, fw2, nested); if (ret < 0) goto air_fw_download_out; =20 ret =3D air_mdio_buckpbus_reg_modify(mdiodev, EN8811H_FW_CTRL_2, - EN8811H_FW_CTRL_2_LOADING, 0); + EN8811H_FW_CTRL_2_LOADING, 0, + nested); if (ret < 0) goto air_fw_download_out; =20 - ret =3D air_en8811h_wait_mcu_ready(mdiodev); + ret =3D air_en8811h_wait_mcu_ready(mdiodev, nested); if (ret < 0) goto air_fw_download_out; =20 ret =3D air_mdio_buckpbus_reg_read(mdiodev, EN8811H_FW_VERSION, - fw_version); + fw_version, nested); =20 air_fw_download_out: + air_child_bus_unlock(child_bus); release_firmware(fw2); =20 air_fw_download_rel1: diff --git a/drivers/net/phy/air_phy_lib.h b/drivers/net/phy/air_phy_lib.h index 764f832a02a7..5f6f09446008 100644 --- a/drivers/net/phy/air_phy_lib.h +++ b/drivers/net/phy/air_phy_lib.h @@ -60,8 +60,9 @@ int air_phy_buckpbus_reg_write(struct phy_device *phydev,= u32 pbus_address, int air_phy_read_page(struct phy_device *phydev); int air_phy_write_page(struct phy_device *phydev, int page); =20 +/* nested: the caller already holds the child bus this chip publishes. */ int air_fw_write_buf(struct mdio_device *mdiodev, u32 address, - const struct firmware *fw); -int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev); + const struct firmware *fw, bool nested); +int air_en8811h_wait_mcu_ready(struct mdio_device *mdiodev, bool nested); =20 #endif /* __AIR_PHY_LIB_H */ diff --git a/include/linux/mdio/mdio-airoha-en8811h.h b/include/linux/mdio/= mdio-airoha-en8811h.h index 8a073a1b7ee6..383ff4a7f6a1 100644 --- a/include/linux/mdio/mdio-airoha-en8811h.h +++ b/include/linux/mdio/mdio-airoha-en8811h.h @@ -10,15 +10,20 @@ #include =20 struct mdio_device; +struct mii_bus; =20 #define EN8811H_MD32_DM "airoha/EthMD32.dm.bin" #define EN8811H_MD32_DSP "airoha/EthMD32.DSP.bin" =20 -/* Returns 1 when the firmware runs, 0 when the MD32 is still in its +/* child_bus is the bus this chip publishes below mdiodev, or NULL. + * + * Returns 1 when the firmware runs, 0 when the MD32 is still in its * bootloader, and negative on a failed status read. */ -int air_en8811h_mcu_running(struct mdio_device *mdiodev); +int air_en8811h_mcu_running(struct mdio_device *mdiodev, + struct mii_bus *child_bus); /* Returns 1 when firmware was already running and was left in place. */ -int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version); +int air_en8811h_fw_download(struct mdio_device *mdiodev, u32 *fw_version, + struct mii_bus *child_bus); =20 #endif /* __LINUX_MDIO_AIROHA_EN8811H_H */ --=20 2.53.0